Keep legacy daemon agents attached to workspaces
Old daemons can report agents by cwd without workspace IDs. Build path-backed workspace rows for that compatibility path and reapply ownership to legacy snapshots so tab reconciliation keeps those agents visible.
This commit is contained in:
@@ -72,6 +72,10 @@ import { isNative } from "@/constants/platform";
|
||||
import { useToast } from "@/contexts/toast-context";
|
||||
import { toErrorMessage } from "@/utils/error-messages";
|
||||
import { applyCheckoutStatusUpdateFromEvent } from "@/git/checkout-status-cache";
|
||||
import {
|
||||
applyLegacyDaemonWorkspaceOwnership,
|
||||
backfillLegacyDaemonWorkspaceDirectoryIfEmpty,
|
||||
} from "@/workspace/legacy-daemon-workspaces";
|
||||
|
||||
// Re-export types from session-store and draft-store for backward compatibility
|
||||
export type { DraftInput } from "@/stores/draft-store";
|
||||
@@ -114,6 +118,56 @@ interface BufferedAudioChunk {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface WorkspaceHydrationSnapshot {
|
||||
workspaces: Map<string, WorkspaceDescriptor>;
|
||||
emptyProjects: Map<string, EmptyProjectDescriptor>;
|
||||
}
|
||||
|
||||
async function fetchWorkspaceHydrationSnapshot(input: {
|
||||
client: DaemonClient;
|
||||
serverId: string;
|
||||
subscribe: boolean;
|
||||
isCancelled?: () => boolean;
|
||||
}): Promise<WorkspaceHydrationSnapshot | null> {
|
||||
const workspaces = new Map<string, WorkspaceDescriptor>();
|
||||
const emptyProjects = new Map<string, EmptyProjectDescriptor>();
|
||||
let cursor: string | null = null;
|
||||
let includeSubscribe = input.subscribe;
|
||||
|
||||
while (true) {
|
||||
const payload = await input.client.fetchWorkspaces({
|
||||
sort: [{ key: "activity_at", direction: "desc" }],
|
||||
...(includeSubscribe ? { subscribe: {} } : {}),
|
||||
page: cursor ? { limit: 200, cursor } : { limit: 200 },
|
||||
});
|
||||
if (input.isCancelled?.()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const entry of payload.entries) {
|
||||
const workspace = normalizeWorkspaceDescriptor(entry);
|
||||
if (shouldSuppressWorkspaceForLocalArchive({ serverId: input.serverId, workspace })) {
|
||||
continue;
|
||||
}
|
||||
workspaces.set(workspace.id, workspace);
|
||||
}
|
||||
|
||||
// Empty project parents only ride on the first page.
|
||||
for (const project of payload.emptyProjects ?? []) {
|
||||
const descriptor = normalizeEmptyProjectDescriptor(project);
|
||||
emptyProjects.set(descriptor.projectId, descriptor);
|
||||
}
|
||||
|
||||
if (!payload.pageInfo.hasMore || !payload.pageInfo.nextCursor) {
|
||||
break;
|
||||
}
|
||||
cursor = payload.pageInfo.nextCursor;
|
||||
includeSubscribe = false;
|
||||
}
|
||||
|
||||
return { workspaces, emptyProjects };
|
||||
}
|
||||
|
||||
function decodeBase64Chunk(base64: string): Uint8Array {
|
||||
return Buffer.from(base64, "base64");
|
||||
}
|
||||
@@ -543,48 +597,29 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
return;
|
||||
}
|
||||
|
||||
const workspaces = new Map<string, WorkspaceDescriptor>();
|
||||
const emptyProjects = new Map<string, EmptyProjectDescriptor>();
|
||||
let cursor: string | null = null;
|
||||
let includeSubscribe = options?.subscribe ?? false;
|
||||
|
||||
while (true) {
|
||||
const payload = await client.fetchWorkspaces({
|
||||
sort: [{ key: "activity_at", direction: "desc" }],
|
||||
...(includeSubscribe ? { subscribe: {} } : {}),
|
||||
page: cursor ? { limit: 200, cursor } : { limit: 200 },
|
||||
});
|
||||
if (options?.isCancelled?.()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of payload.entries) {
|
||||
const workspace = normalizeWorkspaceDescriptor(entry);
|
||||
if (shouldSuppressWorkspaceForLocalArchive({ serverId, workspace })) {
|
||||
continue;
|
||||
}
|
||||
workspaces.set(workspace.id, workspace);
|
||||
}
|
||||
|
||||
// Empty project parents only ride on the first page.
|
||||
for (const project of payload.emptyProjects ?? []) {
|
||||
const descriptor = normalizeEmptyProjectDescriptor(project);
|
||||
emptyProjects.set(descriptor.projectId, descriptor);
|
||||
}
|
||||
|
||||
if (!payload.pageInfo.hasMore || !payload.pageInfo.nextCursor) {
|
||||
break;
|
||||
}
|
||||
cursor = payload.pageInfo.nextCursor;
|
||||
includeSubscribe = false;
|
||||
}
|
||||
|
||||
if (options?.isCancelled?.()) {
|
||||
const snapshot = await fetchWorkspaceHydrationSnapshot({
|
||||
client,
|
||||
serverId,
|
||||
subscribe: options?.subscribe ?? false,
|
||||
isCancelled: options?.isCancelled,
|
||||
});
|
||||
if (!snapshot || options?.isCancelled?.()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setWorkspaces(serverId, workspaces);
|
||||
setEmptyProjects(serverId, emptyProjects.values());
|
||||
const didBackfillLegacy = await backfillLegacyDaemonWorkspaceDirectoryIfEmpty({
|
||||
client,
|
||||
serverId,
|
||||
workspaces: snapshot.workspaces,
|
||||
emptyProjects: snapshot.emptyProjects,
|
||||
isCancelled: options?.isCancelled,
|
||||
});
|
||||
if (didBackfillLegacy) {
|
||||
return;
|
||||
}
|
||||
|
||||
setWorkspaces(serverId, snapshot.workspaces);
|
||||
setEmptyProjects(serverId, snapshot.emptyProjects.values());
|
||||
setHasHydratedWorkspaces(serverId, true);
|
||||
},
|
||||
[client, isConnected, serverId, setEmptyProjects, setHasHydratedWorkspaces, setWorkspaces],
|
||||
@@ -1044,13 +1079,16 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
}
|
||||
|
||||
const normalized = normalizeAgentSnapshot(update.agent, serverId);
|
||||
const agent = {
|
||||
...normalized,
|
||||
projectPlacement: resolveProjectPlacement({
|
||||
projectPlacement: update.project,
|
||||
cwd: normalized.cwd,
|
||||
}),
|
||||
};
|
||||
const agent = applyLegacyDaemonWorkspaceOwnership({
|
||||
serverId,
|
||||
agent: {
|
||||
...normalized,
|
||||
projectPlacement: resolveProjectPlacement({
|
||||
projectPlacement: update.project,
|
||||
cwd: normalized.cwd,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
applyAuthoritativeAgentSnapshot(agent);
|
||||
},
|
||||
@@ -1110,10 +1148,15 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
|
||||
if (payload.agent) {
|
||||
const normalized = normalizeAgentSnapshot(payload.agent, serverId);
|
||||
applyAuthoritativeAgentSnapshot({
|
||||
...normalized,
|
||||
projectPlacement: session?.agents.get(agentId)?.projectPlacement ?? null,
|
||||
});
|
||||
applyAuthoritativeAgentSnapshot(
|
||||
applyLegacyDaemonWorkspaceOwnership({
|
||||
serverId,
|
||||
agent: {
|
||||
...normalized,
|
||||
projectPlacement: session?.agents.get(agentId)?.projectPlacement ?? null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Call pure reducer
|
||||
|
||||
Reference in New Issue
Block a user