feat: Stop archives a session, Sleep keeps it; SSH keys per type (#82); update Auto fix

- Stop (a tab's or pane's X, Close tab) now archives the session it held
  (DELETE /api/run/:id?archive=1); the new Sleep (moon left of the X, and in
  the tab, pane and session right-click menus) stops the pane but keeps the
  session listed to resume later. Shared endRuns in client/src/lib/stopSession.ts.
- Git identity (#82): an account keeps an ED25519 AND an RSA key (AWS
  CodeCommit takes only RSA), every ssh git offers both, and a member's
  ~/.gitconfig gets core.sshCommand so a git typed in a terminal uses them.
- Update dialog: Auto fix starts a Claude Code session on the install
  folder with the failed update's error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 11:51:26 +07:00
parent 58be04a4e7
commit 0a1dcfeb7f
28 changed files with 813 additions and 225 deletions
+22
View File
@@ -626,9 +626,31 @@ router.delete("/:id", (req, res) => {
if (!ok) {
return res.status(404).json({ error: { code: "ENOTFOUND", message: "run not found" } });
}
// `?archive=1` is the UI's Stop: the session the pane held leaves its
// workspace's lists as well. Sleep (and the CLI) omit it, so the session
// stays listed to be resumed later. A pane with no known session (a shell,
// a Codex pane whose session was never learned) archives nothing.
if (req.query.archive === "1") {
return res.json({ ok: true, archived: archiveRunSession(req, req.params.id) });
}
return res.json({ ok: true });
});
/** Archive the session a run held; returns its id, or null when none. */
function archiveRunSession(req, runId) {
const sessionId = runs.getRun(runId)?.sessionId;
if (!sessionId || !scope.canSeeSession(req, sessionId)) return null;
const { db, stmts } = require("../db");
const changed = db
.prepare(
"UPDATE sessions SET archived_at = COALESCE(archived_at, strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) WHERE id = ?"
)
.run(sessionId).changes;
if (!changed) return null;
require("../websocket").broadcast("session_updated", stmts.getSession.get(sessionId));
return sessionId;
}
module.exports = router;
// Shared with routes/lanes.js: lane actions spawn processes through the same
// run-spawner, so they must sit behind the same loopback/same-origin check.