Preserve opened subagents when parents are archived (#3279)

* fix(subagents): preserve opened children on parent archive

Opening a managed child represents user ownership only while that client's tab remains open. Track that intent per client, detach surviving children during parent archive, and keep single and bulk tab-close behavior consistent.

* fix(subagents): mark generic tab openings

Centralize managed child tab ownership in agent navigation so list, command-center, deep-link, notification, and subagent-track openings all mark ownership before creating the tab. Use stable localized errors for lifecycle failures.

* fix(subagents): classify cold tab openings

Fetch uncached agents before deciding whether tab ownership must be recorded, so notification, desktop, and cold-route openings cannot bypass parent archive protection.

* fix(app): translate navigation fallback errors

* fix(navigation): allow offline workspace intents

* refactor(app): derive subagent ownership from open tabs

* fix(server): serialize subagent lifecycle ownership
This commit is contained in:
Mohamed Boudra
2026-08-12 21:18:55 +02:00
committed by GitHub
parent fc1cb82e02
commit 4426a6b11f
21 changed files with 696 additions and 70 deletions
@@ -1,7 +1,10 @@
import { describe, expect, test } from "vitest";
import {
getParentAgentIdFromLabels,
getOpenAgentTabLabel,
hasOpenAgentTab,
isDelegatedAgent,
isOpenAgentTabLabel,
PARENT_AGENT_ID_LABEL,
} from "./agent-labels.js";
@@ -18,4 +21,19 @@ describe("agent label policy", () => {
expect(isDelegatedAgent({ labels: { [PARENT_AGENT_ID_LABEL]: " " } })).toBe(false);
expect(isDelegatedAgent({ labels: { [PARENT_AGENT_ID_LABEL]: 42 } })).toBe(false);
});
test("treats any true client-scoped open-tab label as open", () => {
const desktopLabel = getOpenAgentTabLabel("desktop-client");
const mobileLabel = getOpenAgentTabLabel("mobile-client");
expect(hasOpenAgentTab({ [desktopLabel]: "false", [mobileLabel]: "true" })).toBe(true);
expect(hasOpenAgentTab({ [desktopLabel]: "false", [mobileLabel]: "false" })).toBe(false);
expect(hasOpenAgentTab({})).toBe(false);
});
test("recognizes only client-scoped open-tab labels", () => {
expect(isOpenAgentTabLabel(getOpenAgentTabLabel("client-a"))).toBe(true);
expect(isOpenAgentTabLabel("paseo.open-agent-tab")).toBe(false);
expect(isOpenAgentTabLabel("custom.open-agent-tab.client-a")).toBe(false);
});
});
+15
View File
@@ -1,4 +1,13 @@
export const PARENT_AGENT_ID_LABEL = "paseo.parent-agent-id";
const OPEN_AGENT_TAB_LABEL_PREFIX = "paseo.open-agent-tab.";
export function getOpenAgentTabLabel(clientId: string): string {
return `${OPEN_AGENT_TAB_LABEL_PREFIX}${clientId}`;
}
export function isOpenAgentTabLabel(label: string): boolean {
return label.startsWith(OPEN_AGENT_TAB_LABEL_PREFIX);
}
export interface AgentLabelSource {
labels?: Record<string, unknown> | null;
@@ -14,3 +23,9 @@ export function getParentAgentIdFromLabels(labels: Record<string, unknown> | nul
export function isDelegatedAgent(agent: AgentLabelSource): boolean {
return getParentAgentIdFromLabels(agent.labels) !== null;
}
export function hasOpenAgentTab(labels: Record<string, unknown> | null | undefined): boolean {
return Object.entries(labels ?? {}).some(
([label, value]) => isOpenAgentTabLabel(label) && value === "true",
);
}