Files
paseo/packages/app/src/stores/navigation-active-workspace-store/navigation.ts
T
Mohamed Boudra 9966e49329 Key workspace status and ownership by ID, not directory
Multiple workspaces can share one checkout directory. Agent and terminal
status, sidebar tabs, and ownership now attribute strictly by workspace ID,
so a running or blocked agent in one workspace no longer shows up on its
same-directory siblings.

A one-time startup migration backfills workspace IDs onto legacy agents, and
every agent and terminal is created with one thereafter. Directory lookups
remain only for genuinely folder-derived git facts (branch, diff) and the
archive-by-path adapter.
2026-06-16 13:37:58 +07:00

99 lines
3.2 KiB
TypeScript

import type { Agent, WorkspaceDescriptor } from "@/stores/session-store";
import { pickAttentionAgent } from "@/utils/agent-attention";
import {
buildHostWorkspaceRoute,
decodeWorkspaceIdFromPathSegment,
parseHostWorkspaceRouteFromPathname,
} from "@/utils/host-routes";
import {
normalizeWorkspaceOpaqueId,
resolveWorkspaceMapKeyByIdentity,
} from "@/utils/workspace-identity";
import type { ActiveWorkspaceSelection } from "@/stores/last-workspace-selection";
export interface RouteSelectionInput {
pathname: string;
params: {
serverId?: string | string[];
workspaceId?: string | string[];
};
}
export interface NavigateToWorkspaceDeps {
getSessionWorkspaces: (serverId: string) => Map<string, WorkspaceDescriptor> | null | undefined;
getSessionAgents: (serverId: string) => Iterable<Agent>;
openWorkspaceAgentTab: (workspaceKey: string, agentId: string) => void;
rememberLastWorkspace: (selection: ActiveWorkspaceSelection) => void;
navigateToRoute: (route: string) => void;
}
export interface NavigateToLastWorkspaceDeps extends NavigateToWorkspaceDeps {
getLastWorkspaceSelection: () => ActiveWorkspaceSelection | null;
}
function getParamValue(value: string | string[] | undefined): string {
if (typeof value === "string") {
return value.trim();
}
if (Array.isArray(value)) {
const firstValue = value[0];
return typeof firstValue === "string" ? firstValue.trim() : "";
}
return "";
}
function parseWorkspaceSelectionFromRouteParams(params: {
serverId?: string | string[];
workspaceId?: string | string[];
}): ActiveWorkspaceSelection | null {
const serverId = getParamValue(params.serverId);
const workspaceValue = getParamValue(params.workspaceId);
const workspaceId = workspaceValue ? decodeWorkspaceIdFromPathSegment(workspaceValue) : null;
if (!serverId || !workspaceId) {
return null;
}
return { serverId, workspaceId };
}
export function parseActiveWorkspaceSelection(
input: RouteSelectionInput,
): ActiveWorkspaceSelection | null {
return (
parseHostWorkspaceRouteFromPathname(input.pathname) ??
parseWorkspaceSelectionFromRouteParams(input.params)
);
}
export function navigateToWorkspace(
serverId: string,
workspaceId: string,
deps: NavigateToWorkspaceDeps,
): void {
const workspaces = deps.getSessionWorkspaces(serverId);
const resolvedWorkspaceId = resolveWorkspaceMapKeyByIdentity({
workspaces,
workspaceId,
});
const workspaceAgents = resolvedWorkspaceId
? Array.from(deps.getSessionAgents(serverId)).filter(
(agent) => normalizeWorkspaceOpaqueId(agent.workspaceId) === resolvedWorkspaceId,
)
: [];
const attentionAgentId = pickAttentionAgent(workspaceAgents);
if (attentionAgentId && resolvedWorkspaceId) {
deps.openWorkspaceAgentTab(`${serverId}:${resolvedWorkspaceId}`, attentionAgentId);
}
deps.rememberLastWorkspace({ serverId, workspaceId });
deps.navigateToRoute(buildHostWorkspaceRoute(serverId, workspaceId));
}
export function navigateToLastWorkspace(deps: NavigateToLastWorkspaceDeps): boolean {
const selection = deps.getLastWorkspaceSelection();
if (!selection) {
return false;
}
navigateToWorkspace(selection.serverId, selection.workspaceId, deps);
return true;
}