feat: workspaces own their sessions; desktop parity; sidebar and settings cleanup
Workspace sessions (docs/plans/desktop-parity, phases 1-5 and 7): - sessions.lane_id: a workspace lists only the sessions its panes held, stamped by bindRunSession (first writer wins, nested `claude -p` excluded); GET /api/sessions?lane=&archived= - session row state dot, double-click rename, archive (sessions.archived_at, hides only); the activity bar's duplicate Sessions tab removed - Fork beside Resume (--fork-session, Claude only), Ctrl/Cmd+click a live session opens it in a second pane, a diff line's comment is pasted into the workspace's last-focused pane (lib/panePaste.ts) dashboard_runs stability: - a pane's previous run is archived as <pane>@<started_at> instead of being overwritten by INSERT OR REPLACE - a run is flagged abandoned only once its tmux session is gone, at boot and on the run-heal beat (panes outlive a server restart) UI: - sidebar: no footer, language or theme block; small collapse button in the brand corner; version + update check in the connection details modal, a status-bar dot while a release is out; language in Settings -> General - one Switch component for every switch, the themed Checkbox for every checkbox (no native checkboxes) Also: AI Tools catalog trimmed to 10 built-ins (custom-file prompt modes tested through fixtures), and the workspace plans under docs/plans/ (the multi-user server plan lives in the AgentHub-Server repo).
This commit is contained in:
@@ -196,6 +196,40 @@ describe("pty-run", () => {
|
||||
assert.ok(newSessionArgv.includes("abc12345"));
|
||||
});
|
||||
|
||||
it("spawnRun forks a resumed Claude session into a new id, and refuses a fork elsewhere", () => {
|
||||
let newSessionArgv = null;
|
||||
tmux.__setExecImpl((args) => {
|
||||
if (args[0] === "has-session") {
|
||||
const e = new Error("gone");
|
||||
e.status = 1;
|
||||
throw e;
|
||||
}
|
||||
if (args[0] === "new-session") newSessionArgv = args;
|
||||
return "";
|
||||
});
|
||||
pty.spawnRun({ laneId: 31, cwd: "/tmp/repo", resumeSessionId: "abc12345", forkSession: true });
|
||||
const at = newSessionArgv.indexOf("--resume");
|
||||
assert.deepEqual(newSessionArgv.slice(at, at + 3), ["--resume", "abc12345", "--fork-session"]);
|
||||
// The pane holds the FORK, whose id only its first hook reports.
|
||||
const dashboardRuns = require("../lib/dashboard-runs");
|
||||
assert.equal(dashboardRuns.getRun("agenthub-lane-31").session_id, null);
|
||||
assert.throws(
|
||||
() => pty.spawnRun({ laneId: 32, cwd: "/tmp/repo", forkSession: true }),
|
||||
(e) => e.code === "EBADSESSION"
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
pty.spawnRun({
|
||||
laneId: 33,
|
||||
cwd: "/tmp/repo",
|
||||
tool: "codex",
|
||||
resumeSessionId: "abc12345",
|
||||
forkSession: true,
|
||||
}),
|
||||
(e) => e.code === "EUNSUPPORTED"
|
||||
);
|
||||
});
|
||||
|
||||
it("spawnRun offers bypass in the pane's shift+tab cycle without starting in it", () => {
|
||||
let newSessionArgv = null;
|
||||
tmux.__setExecImpl((args) => {
|
||||
@@ -427,11 +461,16 @@ describe("pty-run", () => {
|
||||
}
|
||||
savedPath = process.env.PATH;
|
||||
process.env.PATH = `${BIN}:/usr/bin:/bin`;
|
||||
aiTools.refresh();
|
||||
// The built-in catalog plus three test-only tools for the prompt modes
|
||||
// and bypassEnv no built-in tool uses any more (custom files may).
|
||||
aiTools.__setCatalogDirs([
|
||||
path.join(__dirname, "..", "data", "ai-tools"),
|
||||
path.join(__dirname, "fixtures", "ai-tools"),
|
||||
]);
|
||||
});
|
||||
after(() => {
|
||||
process.env.PATH = savedPath;
|
||||
aiTools.refresh();
|
||||
aiTools.__setCatalogDirs(null);
|
||||
});
|
||||
const at = (name) => path.join(BIN, name);
|
||||
const envOf = (call, key) => {
|
||||
|
||||
Reference in New Issue
Block a user