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
+7 -20
View File
@@ -81,8 +81,9 @@ import type {
} from "../lib/api";
import type { Lane, LaneFeature, ProofFeature, Session } from "../lib/types";
import { toast } from "../lib/toast";
import { endRuns } from "../lib/stopSession";
import { ContextMenu, MenuItem, useContextMenu } from "../components/ContextMenu";
import { confirmDialog, confirmDiscard } from "../lib/confirm";
import { confirmDiscard } from "../lib/confirm";
import { keepIfSame } from "../lib/keepIfSame";
import { useWorkLanes } from "../hooks/useLanes";
import { LaneConsolePane } from "../components/run/LaneConsolePane";
@@ -423,27 +424,12 @@ function ConsoleArea({
else onNewSession?.();
};
// A pane header's X: stops that one session, as a one-pane tab's X does —
// an AI session may be mid-turn, so it asks first; a shell holds no agent.
const stopPane = async (slot: number) => {
// A pane header's X stops (and archives) that one session, its moon sleeps
// it — as a one-pane tab's buttons do (`endRuns` asks first for an AI pane).
const stopPane = async (slot: number, sleep = false) => {
const run = runOf(slot);
if (!run) return;
if (
run.tool !== "shell" &&
!(await confirmDialog({
title: t("tabs.stopTitle", { name: label(run) }),
message: t("tabs.stopBody"),
confirmLabel: t("tabs.stopConfirm"),
destructive: true,
}))
)
return;
try {
await api.run.kill(run.id);
ended(slot);
} catch (err) {
toast.error(err);
}
if ((await endRuns([run], { sleep, name: label(run), t })).length) ended(slot);
};
// A split menu's "New terminal" or AI tool (bare, as the tab bar's `+`
@@ -632,6 +618,7 @@ function ConsoleArea({
onNewSplit={(target, edge, tool) => void newSplit(target, edge, tool)}
stateOf={stateOf}
onStop={(slot) => void stopPane(slot)}
onSleep={(slot) => void stopPane(slot, true)}
// Keyed by workspace too, so a kept pane coming back is the same one.
keyOf={allTabs ? undefined : (slot) => paneKey(selectedLaneId ?? 0, slot)}
kept={kept}