Merge branch 'main' into dev

Surgical merge of 74 commits from main into dev. Key features ported:

- Provider visibility gating (appVersion filtering for Pi/Copilot)
- Pi agent provider + Copilot re-enabled
- Provider-declared features system (Codex fast mode)
- Workspace dedup by worktree root (adapted to integer IDs)
- Bulk close archiving fix for stored agents
- Agent creation timeout increase to 60s
- Audio/voice crash fixes (external buffer copies)
- Multi-host setup fix
- Reload agent tab action

Dev architecture preserved: SQLite storage, service proxy,
workspace setup dialog, hover cards, removed launcher tabs.
This commit is contained in:
Mohamed Boudra
2026-04-05 12:59:07 +07:00
173 changed files with 8873 additions and 2316 deletions
@@ -10,6 +10,11 @@ import {
createPersistedWorkspaceRecord,
} from "./workspace-registry.js";
vi.mock("@getpaseo/highlight", () => ({
highlightCode: vi.fn(async () => ""),
isLanguageSupported: vi.fn(() => false),
}));
function makeAgent(input: {
id: string;
cwd: string;
@@ -243,34 +248,46 @@ function createTempGitRepo(options?: {
}
describe("workspace aggregation", () => {
test("archive request emits agent_archived using the snapshot archive flow", async () => {
test("archive request emits agent_archived and an authoritative agent_update", async () => {
const emitted: Array<{ type: string; payload: any }> = [];
const archiveSnapshot = vi.fn(async (_agentId: string, archivedAt: string) => {
return {
id: "agent-1",
const archivedRecord = {
id: "agent-1",
provider: "codex",
cwd: "/tmp/repo",
createdAt: "2026-03-30T15:00:00.000Z",
updatedAt: "2026-03-30T15:00:00.000Z",
lastActivityAt: "2026-03-30T15:00:00.000Z",
lastUserMessageAt: null,
lastStatus: "idle" as const,
lastModeId: null,
runtimeInfo: null,
config: {
provider: "codex",
cwd: "/tmp/repo",
createdAt: "2026-03-30T15:00:00.000Z",
updatedAt: archivedAt,
lastActivityAt: "2026-03-30T15:00:00.000Z",
lastUserMessageAt: null,
lastStatus: "idle" as const,
lastModeId: null,
runtimeInfo: null,
config: {
provider: "codex",
cwd: "/tmp/repo",
},
persistence: null,
title: "Archive me",
labels: {},
requiresAttention: false,
attentionReason: null,
attentionTimestamp: null,
archivedAt,
};
},
persistence: null,
title: "Archive me",
labels: {},
requiresAttention: false,
attentionReason: null,
attentionTimestamp: null,
archivedAt: null as string | null,
};
const projects = new Map<number, ReturnType<typeof createPersistedProjectRecord>>();
const workspaces = new Map<number, ReturnType<typeof createPersistedWorkspaceRecord>>();
seedProject({
projects,
id: 1,
directory: "/tmp/repo",
displayName: "repo",
});
seedWorkspace({
workspaces,
id: 10,
projectId: 1,
directory: "/tmp/repo",
displayName: "repo",
});
const logger = {
child: () => logger,
trace: vi.fn(),
@@ -292,19 +309,23 @@ describe("workspace aggregation", () => {
subscribe: () => () => {},
listAgents: () => [],
getAgent: (agentId: string) => (agentId === "agent-1" ? { id: agentId } : null),
archiveSnapshot,
closeAgent,
archiveAgent: async () => {
const archivedAt = new Date().toISOString();
archivedRecord.archivedAt = archivedAt;
archivedRecord.updatedAt = archivedAt;
return { archivedAt };
},
clearAgentAttention: async () => {},
} as any,
agentStorage: {
list: async () => [],
get: async () => null,
get: async (agentId: string) => (agentId === archivedRecord.id ? archivedRecord : null),
} as any,
projectRegistry: {
initialize: async () => {},
existsOnDisk: async () => true,
list: async () => [],
get: async () => null,
list: async () => Array.from(projects.values()),
get: async (id: number) => projects.get(id) ?? null,
upsert: async () => {},
archive: async () => {},
remove: async () => {},
@@ -312,8 +333,8 @@ describe("workspace aggregation", () => {
workspaceRegistry: {
initialize: async () => {},
existsOnDisk: async () => true,
list: async () => [],
get: async () => null,
list: async () => Array.from(workspaces.values()),
get: async (id: number) => workspaces.get(id) ?? null,
upsert: async () => {},
archive: async () => {},
remove: async () => {},
@@ -340,29 +361,43 @@ describe("workspace aggregation", () => {
terminalManager: null,
}) as any;
session.agentUpdatesSubscription = {
subscriptionId: "sub-agents",
filter: { includeArchived: true },
isBootstrapping: false,
pendingUpdatesByAgentId: new Map(),
};
session.interruptAgentIfRunning = vi.fn();
await session.handleArchiveAgentRequest("agent-1", "req-archive");
expect(session.interruptAgentIfRunning).toHaveBeenCalledWith("agent-1");
expect(closeAgent).toHaveBeenCalledWith("agent-1");
const update = emitted.find((message) => message.type === "agent_update");
expect(update?.payload).toMatchObject({
kind: "upsert",
agent: {
id: "agent-1",
archivedAt: expect.any(String),
},
});
const archivedPayload = emitted.find((message) => message.type === "agent_archived")?.payload;
expect(archivedPayload).toMatchObject({
agentId: "agent-1",
archivedAt: expect.any(String),
requestId: "req-archive",
});
expect(archiveSnapshot).toHaveBeenCalledWith("agent-1", archivedPayload.archivedAt);
expect(archivedRecord.archivedAt).toBe(archivedPayload.archivedAt);
});
test("close_items_request archives agents and kills terminals in one batch", async () => {
const emitted: Array<{ type: string; payload: any }> = [];
const archiveSnapshot = vi.fn(async (_agentId: string, archivedAt: string) => ({
const archivedAt = "2026-04-01T00:00:00.000Z";
const archivedRecord = {
id: "agent-1",
provider: "codex",
cwd: "/tmp/repo",
createdAt: "2026-03-01T12:00:00.000Z",
updatedAt: archivedAt,
updatedAt: "2026-03-01T12:00:00.000Z",
lastActivityAt: "2026-03-01T12:00:00.000Z",
lastUserMessageAt: null,
lastStatus: "idle" as const,
@@ -375,8 +410,23 @@ describe("workspace aggregation", () => {
requiresAttention: false,
attentionReason: null,
attentionTimestamp: null,
archivedAt,
}));
archivedAt: null as string | null,
};
const projects = new Map<number, ReturnType<typeof createPersistedProjectRecord>>();
const workspaces = new Map<number, ReturnType<typeof createPersistedWorkspaceRecord>>();
seedProject({
projects,
id: 2,
directory: "/tmp/repo",
displayName: "repo",
});
seedWorkspace({
workspaces,
id: 20,
projectId: 2,
directory: "/tmp/repo",
displayName: "repo",
});
const sessionLogger = {
child: () => sessionLogger,
trace: vi.fn(),
@@ -397,19 +447,22 @@ describe("workspace aggregation", () => {
subscribe: () => () => {},
listAgents: () => [],
getAgent: (agentId: string) => (agentId === "agent-1" ? { id: agentId } : null),
archiveSnapshot,
closeAgent: async () => undefined,
archiveAgent: async () => {
archivedRecord.archivedAt = archivedAt;
archivedRecord.updatedAt = archivedAt;
return { archivedAt };
},
clearAgentAttention: async () => {},
} as any,
agentStorage: {
list: async () => [],
get: async () => null,
get: async (agentId: string) => (agentId === archivedRecord.id ? archivedRecord : null),
} as any,
projectRegistry: {
initialize: async () => {},
existsOnDisk: async () => true,
list: async () => [],
get: async () => null,
list: async () => Array.from(projects.values()),
get: async (id: number) => projects.get(id) ?? null,
upsert: async () => {},
archive: async () => {},
remove: async () => {},
@@ -417,8 +470,8 @@ describe("workspace aggregation", () => {
workspaceRegistry: {
initialize: async () => {},
existsOnDisk: async () => true,
list: async () => [],
get: async () => null,
list: async () => Array.from(workspaces.values()),
get: async (id: number) => workspaces.get(id) ?? null,
upsert: async () => {},
archive: async () => {},
remove: async () => {},
@@ -448,6 +501,12 @@ describe("workspace aggregation", () => {
} as any,
}) as any;
session.agentUpdatesSubscription = {
subscriptionId: "sub-agents",
filter: { includeArchived: true },
isBootstrapping: false,
pendingUpdatesByAgentId: new Map(),
};
session.interruptAgentIfRunning = vi.fn();
await session.handleMessage({
@@ -465,10 +524,16 @@ describe("workspace aggregation", () => {
terminals: [{ terminalId: "term-1", success: true }],
requestId: "req-close-items",
});
expect(archiveSnapshot).toHaveBeenCalledWith("agent-1", closePayload.agents[0].archivedAt);
expect(emitted.find((message) => message.type === "agent_update")?.payload).toMatchObject({
kind: "upsert",
agent: {
id: "agent-1",
archivedAt,
},
});
});
test("close_items_request continues after an archive failure", async () => {
test("close_items_request archives stored agents that are not currently loaded", async () => {
const emitted: Array<{ type: string; payload: any }> = [];
const sessionLogger = {
child: () => sessionLogger,
@@ -478,30 +543,73 @@ describe("workspace aggregation", () => {
warn: vi.fn(),
error: vi.fn(),
};
const archiveSnapshot = vi.fn(async (agentId: string, archivedAt: string) => {
if (agentId === "agent-bad") {
throw new Error("archive failed");
const liveArchivedAt = "2026-04-01T00:00:00.000Z";
const storedAgentId = "agent-stored";
const liveRecord = {
id: "agent-live",
provider: "codex",
cwd: "/tmp/repo",
createdAt: "2026-03-01T12:00:00.000Z",
updatedAt: "2026-03-01T12:00:00.000Z",
lastActivityAt: "2026-03-01T12:00:00.000Z",
lastUserMessageAt: null,
lastStatus: "idle" as const,
lastModeId: null,
runtimeInfo: null,
config: null,
persistence: null,
title: null,
labels: {},
requiresAttention: false,
attentionReason: null,
attentionTimestamp: null,
archivedAt: null as string | null,
};
const storedRecord = {
id: storedAgentId,
provider: "codex",
cwd: "/tmp/repo",
createdAt: "2026-03-01T12:05:00.000Z",
updatedAt: "2026-03-01T12:05:00.000Z",
lastActivityAt: "2026-03-01T12:05:00.000Z",
lastUserMessageAt: null,
lastStatus: "idle" as const,
lastModeId: null,
runtimeInfo: null,
config: null,
persistence: null,
title: null,
labels: {},
requiresAttention: false,
attentionReason: null,
attentionTimestamp: null,
archivedAt: null as string | null,
};
const upsertStoredRecord = vi.fn(async (record: typeof storedRecord) => {
if (record.id !== storedAgentId) {
return;
}
return {
id: "agent-good",
provider: "codex",
cwd: "/tmp/repo",
createdAt: "2026-03-01T12:00:00.000Z",
updatedAt: archivedAt,
lastActivityAt: "2026-03-01T12:00:00.000Z",
lastUserMessageAt: null,
lastStatus: "idle" as const,
lastModeId: null,
runtimeInfo: null,
config: null,
persistence: null,
title: null,
labels: {},
requiresAttention: false,
attentionReason: null,
attentionTimestamp: null,
archivedAt,
};
storedRecord.archivedAt = record.archivedAt;
storedRecord.updatedAt = record.updatedAt;
storedRecord.lastStatus = record.lastStatus;
storedRecord.requiresAttention = record.requiresAttention;
storedRecord.attentionReason = record.attentionReason;
storedRecord.attentionTimestamp = record.attentionTimestamp;
});
const projects = new Map<number, ReturnType<typeof createPersistedProjectRecord>>();
const workspaces = new Map<number, ReturnType<typeof createPersistedWorkspaceRecord>>();
seedProject({
projects,
id: 3,
directory: "/tmp/repo",
displayName: "repo",
});
seedWorkspace({
workspaces,
id: 30,
projectId: 3,
directory: "/tmp/repo",
displayName: "repo",
});
const session = new Session({
@@ -514,21 +622,35 @@ describe("workspace aggregation", () => {
agentManager: {
subscribe: () => () => {},
listAgents: () => [],
getAgent: (agentId: string) =>
agentId === "agent-bad" || agentId === "agent-good" ? { id: agentId } : null,
archiveSnapshot,
closeAgent: async () => undefined,
getAgent: (agentId: string) => (agentId === "agent-live" ? { id: agentId } : null),
archiveAgent: async (agentId: string) => {
if (agentId !== "agent-live") {
throw new Error(`Unexpected live archive: ${agentId}`);
}
liveRecord.archivedAt = liveArchivedAt;
liveRecord.updatedAt = liveArchivedAt;
return { archivedAt: liveArchivedAt };
},
clearAgentAttention: async () => {},
} as any,
agentStorage: {
list: async () => [],
get: async () => null,
get: async (agentId: string) => {
if (agentId === "agent-live") {
return liveRecord;
}
if (agentId === storedAgentId) {
return storedRecord;
}
return null;
},
upsert: upsertStoredRecord,
} as any,
projectRegistry: {
initialize: async () => {},
existsOnDisk: async () => true,
list: async () => [],
get: async () => null,
list: async () => Array.from(projects.values()),
get: async (id: number) => projects.get(id) ?? null,
upsert: async () => {},
archive: async () => {},
remove: async () => {},
@@ -536,8 +658,8 @@ describe("workspace aggregation", () => {
workspaceRegistry: {
initialize: async () => {},
existsOnDisk: async () => true,
list: async () => [],
get: async () => null,
list: async () => Array.from(workspaces.values()),
get: async (id: number) => workspaces.get(id) ?? null,
upsert: async () => {},
archive: async () => {},
remove: async () => {},
@@ -567,6 +689,156 @@ describe("workspace aggregation", () => {
} as any,
}) as any;
session.agentUpdatesSubscription = {
subscriptionId: "sub-agents",
filter: { includeArchived: true },
isBootstrapping: false,
pendingUpdatesByAgentId: new Map(),
};
session.interruptAgentIfRunning = vi.fn();
await session.handleMessage({
type: "close_items_request",
agentIds: ["agent-live", storedAgentId],
terminalIds: [],
requestId: "req-close-stored",
});
expect(upsertStoredRecord).toHaveBeenCalledTimes(1);
expect(storedRecord.archivedAt).toEqual(expect.any(String));
expect(emitted.find((message) => message.type === "close_items_response")?.payload).toEqual({
agents: [
{ agentId: "agent-live", archivedAt: liveArchivedAt },
{ agentId: storedAgentId, archivedAt: storedRecord.archivedAt },
],
terminals: [],
requestId: "req-close-stored",
});
expect(sessionLogger.warn).not.toHaveBeenCalled();
});
test("close_items_request continues after an archive failure", async () => {
const emitted: Array<{ type: string; payload: any }> = [];
const sessionLogger = {
child: () => sessionLogger,
trace: vi.fn(),
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
const archivedAt = "2026-04-01T00:00:00.000Z";
const goodRecord = {
id: "agent-good",
provider: "codex",
cwd: "/tmp/repo",
createdAt: "2026-03-01T12:00:00.000Z",
updatedAt: "2026-03-01T12:00:00.000Z",
lastActivityAt: "2026-03-01T12:00:00.000Z",
lastUserMessageAt: null,
lastStatus: "idle" as const,
lastModeId: null,
runtimeInfo: null,
config: null,
persistence: null,
title: null,
labels: {},
requiresAttention: false,
attentionReason: null,
attentionTimestamp: null,
archivedAt: null as string | null,
};
const projects = new Map<number, ReturnType<typeof createPersistedProjectRecord>>();
const workspaces = new Map<number, ReturnType<typeof createPersistedWorkspaceRecord>>();
seedProject({
projects,
id: 4,
directory: "/tmp/repo",
displayName: "repo",
});
seedWorkspace({
workspaces,
id: 40,
projectId: 4,
directory: "/tmp/repo",
displayName: "repo",
});
const session = new Session({
clientId: "test-client",
onMessage: (message) => emitted.push(message as any),
logger: sessionLogger as any,
downloadTokenStore: {} as any,
pushTokenStore: {} as any,
paseoHome: "/tmp/paseo-test",
agentManager: {
subscribe: () => () => {},
listAgents: () => [],
getAgent: (agentId: string) =>
agentId === "agent-bad" || agentId === "agent-good" ? { id: agentId } : null,
archiveAgent: async (agentId: string) => {
if (agentId === "agent-bad") {
throw new Error("archive failed");
}
goodRecord.archivedAt = archivedAt;
goodRecord.updatedAt = archivedAt;
return { archivedAt };
},
clearAgentAttention: async () => {},
} as any,
agentStorage: {
list: async () => [],
get: async (agentId: string) => (agentId === "agent-good" ? goodRecord : null),
} as any,
projectRegistry: {
initialize: async () => {},
existsOnDisk: async () => true,
list: async () => Array.from(projects.values()),
get: async (id: number) => projects.get(id) ?? null,
upsert: async () => {},
archive: async () => {},
remove: async () => {},
} as any,
workspaceRegistry: {
initialize: async () => {},
existsOnDisk: async () => true,
list: async () => Array.from(workspaces.values()),
get: async (id: number) => workspaces.get(id) ?? null,
upsert: async () => {},
archive: async () => {},
remove: async () => {},
} as any,
checkoutDiffManager: {
subscribe: async () => ({
initial: { cwd: "/tmp", files: [], error: null },
unsubscribe: () => {},
}),
scheduleRefreshForCwd: () => {},
getMetrics: () => ({
checkoutDiffTargetCount: 0,
checkoutDiffSubscriptionCount: 0,
checkoutDiffWatcherCount: 0,
checkoutDiffFallbackRefreshTargetCount: 0,
}),
dispose: () => {},
} as any,
createAgentMcpTransport: async () => {
throw new Error("not used");
},
stt: null,
tts: null,
terminalManager: {
killTerminal: vi.fn(),
subscribeTerminalsChanged: () => () => {},
} as any,
}) as any;
session.agentUpdatesSubscription = {
subscriptionId: "sub-agents",
filter: { includeArchived: true },
isBootstrapping: false,
pendingUpdatesByAgentId: new Map(),
};
session.interruptAgentIfRunning = vi.fn();
await session.handleMessage({
@@ -585,7 +857,13 @@ describe("workspace aggregation", () => {
terminals: [{ terminalId: "term-1", success: true }],
requestId: "req-close-best-effort",
});
expect(archiveSnapshot).toHaveBeenCalledWith("agent-good", closePayload.agents[0].archivedAt);
expect(emitted.find((message) => message.type === "agent_update")?.payload).toMatchObject({
kind: "upsert",
agent: {
id: "agent-good",
archivedAt,
},
});
expect(sessionLogger.warn).toHaveBeenCalled();
});