feat: add multi-session desktop workspace

This commit is contained in:
2026-08-13 21:34:04 +02:00
parent 12b9b83d51
commit 475f95ba70
56 changed files with 8998 additions and 3805 deletions
+43 -15
View File
@@ -134,6 +134,10 @@ test("drains a forget still resolving its canonical path during shutdown", async
await stop();
};
const selectedFeature = registry
.listAgents()
.find((agent) => agent.worktreePath === worktrees.feature);
await registry.closeSessionRuntime(selectedFeature.runtimeId);
const forgetting = registry.forgetDirectory(featureAlias);
const stopping = registry.stop();
await Promise.all([forgetting, stopping]);
@@ -170,6 +174,11 @@ test("forgets a directory without deleting its resumable session history", async
`${JSON.stringify({ sessionPath, worktreePath: worktrees.feature })}\n`,
);
await assert.rejects(
registry.forgetDirectory(worktrees.feature),
/runtimes are open/,
);
await registry.closeSessionRuntime(feature.runtimeId);
await registry.forgetDirectory(worktrees.feature);
assert.equal(fixture.calls[1].adapter.stopped, true);
@@ -227,10 +236,14 @@ test("coordinates forgetting with directory creation and active commands", async
const selecting = registry.selectWorktree(worktrees.feature);
await featureStarted;
const forgettingDuringCreation = registry.forgetDirectory(worktrees.feature);
releaseFeatureCreation();
await selecting;
await forgettingDuringCreation;
const selectedDuringCreation = await selecting;
await assert.rejects(
registry.forgetDirectory(worktrees.feature),
/runtimes are open/,
);
await registry.closeSessionRuntime(selectedDuringCreation.runtimeId);
await registry.forgetDirectory(worktrees.feature);
assert.equal(fixture.calls[1].adapter.stopped, true);
assert.equal(
(await registry.listDirectories()).some(
@@ -252,15 +265,18 @@ test("coordinates forgetting with directory creation and active commands", async
await stopGate;
await stop();
};
const firstForget = registry.forgetDirectory(worktrees.feature);
const secondForget = registry.forgetDirectory(worktrees.feature);
await new Promise((resolve) => setImmediate(resolve));
await assert.rejects(
registry.selectWorktree(worktrees.feature),
/being forgotten/,
registry.forgetDirectory(worktrees.feature),
/runtimes are open/,
);
const closing = registry.closeSessionRuntime(
registry.listAgents().find((agent) => agent.worktreePath === worktrees.feature)
.runtimeId,
);
await new Promise((resolve) => setImmediate(resolve));
releaseStop();
await Promise.all([firstForget, secondForget]);
await closing;
await registry.forgetDirectory(worktrees.feature);
assert.equal(stopCalls, 1);
assert.equal(registry.listAgents().length, 1);
@@ -276,15 +292,15 @@ test("coordinates forgetting with directory creation and active commands", async
const prompt = registry.route(feature.id, "prompt", {
message: "Keep working",
});
const forgettingDuringPrompt = registry.forgetDirectory(worktrees.feature);
await new Promise((resolve) => setImmediate(resolve));
await assert.rejects(
registry.forgetDirectory(worktrees.feature),
/runtimes are open/,
);
assert.equal(adapter.stopped, false);
releasePrompt({ type: "response", success: true });
await prompt;
await assert.rejects(forgettingDuringPrompt, /while Pi is working/);
assert.equal(adapter.stopped, false);
options.onEvent({ type: "agent_state", data: { state: "idle" } });
await registry.closeSessionRuntime(feature.runtimeId);
await registry.forgetDirectory(worktrees.feature);
assert.equal(adapter.stopped, true);
@@ -307,14 +323,19 @@ test("coordinates forgetting with directory creation and active commands", async
});
rejectPrompt(new Error("send failed"));
await failedPromptAssertion;
assert.equal(
registry.listAgents().find((entry) => entry.id === failedFeature.id).state,
"streaming",
);
await assert.rejects(
registry.forgetDirectory(worktrees.feature),
/while Pi is working/,
/runtimes are open/,
);
failedFixture.options.onEvent({
type: "agent_state",
data: { state: "idle" },
});
await registry.closeSessionRuntime(failedFeature.runtimeId);
await registry.forgetDirectory(worktrees.feature);
await registry.stop();
});
@@ -389,6 +410,7 @@ test("catalogues managed directories and exposes only their sessions", async ()
}),
/ENOENT|belong/,
);
await registry.closeSessionRuntime(feature.runtimeId);
await registry.stop();
const restored = createAgentRegistry({
@@ -444,11 +466,17 @@ test("routes commands by explicit agent ID and replays only events after the cur
command: "get_commands",
success: true,
});
await registry.route(agent.id, "set_session_name", { name: "Release UI" });
await registry.route(agent.id, "compact", {
customInstructions: "Preserve implementation details",
});
assert.deepEqual(adapter.sent, [
{ type: "get_state" },
{ type: "prompt", message: "Stay in this worktree" },
{ type: "get_session_stats" },
{ type: "get_commands" },
{ type: "set_session_name", name: "Release UI" },
{ type: "compact", customInstructions: "Preserve implementation details" },
]);
options.onEvent({