Merge branch 'investigate/portless' into dev

# Conflicts:
#	packages/app/e2e/helpers/workspace-setup.ts
#	packages/app/src/components/sidebar-workspace-list.tsx
#	packages/app/src/contexts/session-context.tsx
#	packages/app/src/hooks/use-sidebar-workspaces-list.test.ts
#	packages/app/src/screens/workspace/workspace-tab-menu.ts
#	packages/app/src/stores/workspace-setup-store.ts
#	packages/app/src/stores/workspace-tabs-store.ts
#	packages/app/src/utils/sidebar-project-row-model.test.ts
#	packages/app/src/utils/sidebar-shortcuts.test.ts
#	packages/app/src/utils/workspace-tab-identity.ts
#	packages/server/src/server/bootstrap.ts
#	packages/server/src/server/worktree-session.ts
This commit is contained in:
Mohamed Boudra
2026-04-01 22:06:39 +07:00
52 changed files with 5855 additions and 354 deletions

View File

@@ -35,6 +35,7 @@ import {
normalizeWorkspaceDescriptor,
} from "@/stores/session-store";
import { useDraftStore } from "@/stores/draft-store";
import { useWorkspaceSetupStore } from "@/stores/workspace-setup-store";
import type { AgentDirectoryEntry } from "@/types/agent-directory";
import { sendOsNotification } from "@/utils/os-notifications";
import { getIsAppActivelyVisible } from "@/utils/app-visibility";
@@ -50,6 +51,7 @@ import { resolveProjectPlacement } from "@/utils/project-placement";
import { buildDraftStoreKey } from "@/stores/draft-keys";
import type { AttachmentMetadata } from "@/attachments/types";
import { reconcilePreviousAgentStatuses } from "@/contexts/session-status-tracking";
import { patchWorkspaceServices } from "@/contexts/session-workspace-services";
// Re-export types from session-store and draft-store for backward compatibility
export type { DraftInput } from "@/stores/draft-store";
@@ -159,6 +161,10 @@ type WorkspaceUpdatePayload = Extract<
SessionOutboundMessage,
{ type: "workspace_update" }
>["payload"];
type WorkspaceSetupProgressPayload = Extract<
SessionOutboundMessage,
{ type: "workspace_setup_progress" }
>["payload"];
const getAgentIdFromUpdate = (update: AgentUpdatePayload): string =>
update.kind === "remove" ? update.agentId : update.agent.id;
@@ -264,6 +270,9 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
const setQueuedMessages = useSessionStore((state) => state.setQueuedMessages);
const updateSessionClient = useSessionStore((state) => state.updateSessionClient);
const updateSessionServerInfo = useSessionStore((state) => state.updateSessionServerInfo);
const upsertWorkspaceSetupProgress = useWorkspaceSetupStore((state) => state.upsertProgress);
const removeWorkspaceSetup = useWorkspaceSetupStore((state) => state.removeWorkspace);
const clearWorkspaceSetupServer = useWorkspaceSetupStore((state) => state.clearServer);
// Track focused agent for heartbeat
const focusedAgentId = useSessionStore(
@@ -747,6 +756,13 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
],
);
const applyWorkspaceSetupProgress = useCallback(
(payload: WorkspaceSetupProgressPayload) => {
upsertWorkspaceSetupProgress({ serverId, payload });
},
[serverId, upsertWorkspaceSetupProgress],
);
const requestCanonicalCatchUp = useCallback(
(agentId: string, cursor: { endSeq: number }) => {
void client
@@ -1083,12 +1099,34 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
const unsubWorkspaceUpdate = client.on("workspace_update", (message) => {
if (message.type !== "workspace_update") return;
if (message.payload.kind === "remove") {
removeWorkspaceSetup({ serverId, workspaceId: String(message.payload.id) });
removeWorkspace(serverId, String(message.payload.id));
return;
}
mergeWorkspaces(serverId, [normalizeWorkspaceDescriptor(message.payload.workspace)]);
});
const unsubServiceStatusUpdate = client.on("service_status_update", (message) => {
if (message.type !== "service_status_update") return;
setWorkspaces(serverId, (prev) => patchWorkspaceServices(prev, message.payload));
});
const unsubWorkspaceSetupProgress = client.on("workspace_setup_progress", (message) => {
if (message.type !== "workspace_setup_progress") return;
applyWorkspaceSetupProgress(message.payload);
});
const unsubWorkspaceSetupStatusResponse = client.on(
"workspace_setup_status_response",
(message) => {
if (message.type !== "workspace_setup_status_response") return;
const { workspaceId, snapshot } = message.payload;
if (snapshot) {
applyWorkspaceSetupProgress({ workspaceId, ...snapshot });
}
},
);
const unsubStatus = client.on("status", (message) => {
if (message.type !== "status") return;
const serverInfo = parseServerInfoStatusPayload(message.payload);
@@ -1437,6 +1475,9 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
unsubAgentStream();
unsubAgentTimeline();
unsubWorkspaceUpdate();
unsubServiceStatusUpdate();
unsubWorkspaceSetupProgress();
unsubWorkspaceSetupStatusResponse();
unsubStatus();
unsubPermissionRequest();
unsubPermissionResolved();
@@ -1462,8 +1503,10 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
setAgentTimelineCursor,
setInitializingAgents,
setAgents,
setWorkspaces,
mergeWorkspaces,
removeWorkspace,
removeWorkspaceSetup,
setAgentLastActivity,
setPendingPermissions,
setHasHydratedAgents,
@@ -1471,6 +1514,7 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
notifyAgentAttention,
requestCanonicalCatchUp,
applyAgentUpdatePayload,
applyWorkspaceSetupProgress,
applyTimelineResponse,
voiceRuntime,
voiceAudioEngine,
@@ -1674,9 +1718,10 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
// Cleanup on unmount
useEffect(() => {
return () => {
clearWorkspaceSetupServer(serverId);
clearSession(serverId);
};
}, [clearSession, serverId]);
}, [clearSession, clearWorkspaceSetupServer, serverId]);
return children;
}