feat: v2.7.8 — Artifacts hub, clone related repos as extra folders, quieter resume prompt
- Artifacts (/artifacts): claude.ai pages sessions publish (captured from the Artifact tool's hook), pinned workspace files, skills and plugins, each kept as a snapshot; shared ones appear in a Hub and a skill or plugin installs into the viewer's own account (server/lib/artifacts.js, /api/artifacts, docs/plans/artifacts/). - Workspace extra folders: clone one or more related repos beside the workspace and add them in one step (POST /api/lanes/:id/sources/clone). - A pane that ended by itself while tmux kept running is no longer offered for resume (tmux.serverStartedAt in reconcileOrphans). - Changes panel: a folder that is not a git repo opens on Files, and its files open from there. - Plus the parallel sessions' work in this tree: File Explorer button in the panel header, user delete, RSA SSH keys, chat/terminal and pet fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -288,6 +288,17 @@ const userExitStmt = db.prepare(`
|
||||
LIMIT 1
|
||||
`);
|
||||
|
||||
const anyEndStmt = db.prepare(`
|
||||
SELECT 1 FROM events WHERE session_id = ? AND event_type = 'SessionEnd' LIMIT 1
|
||||
`);
|
||||
// Whether the pane's tmux server was already up when the run started (and is
|
||||
// still): then the pane ended on its own, not with its server.
|
||||
function tmuxOutlived(row, serverStartedAt) {
|
||||
const since = serverStartedAt(row.id);
|
||||
const started = Date.parse(row.started_at || "");
|
||||
return Number.isFinite(since) && Number.isFinite(started) && since <= started;
|
||||
}
|
||||
|
||||
// A shell has no session, so its row only tracks the open tab: once closed it
|
||||
// is dropped — the sidebar keeps no list of closed Terminal tabs.
|
||||
const pruneShellsStmt = db.prepare(`
|
||||
@@ -303,12 +314,16 @@ const pruneShellsStmt = db.prepare(`
|
||||
* A pane AgentHub closed itself (Stop, close tab, Quit) is already `killed`.
|
||||
* Of the rest, an agent session the user did not exit is `interrupted` — a
|
||||
* reboot, a killed tmux, a crash — and offered for resume
|
||||
* (`listInterrupted`); everything else is `abandoned`. Only Claude reports
|
||||
* how it ended, so any other tool's vanished pane counts as interrupted.
|
||||
* (`listInterrupted`); everything else is `abandoned`. A pane whose tmux
|
||||
* server kept running since before it started (`serverStartedAt(id)`, epoch
|
||||
* ms or null) ended by itself — the user quit Codex, `exit`ed, closed it —
|
||||
* so it is not offered either: for Claude only when it said it ended (any
|
||||
* SessionEnd; none at all is a crash), for any other tool, which says
|
||||
* nothing, always. A server that cannot be asked counts as gone.
|
||||
*
|
||||
* Returns the number of rows updated.
|
||||
*/
|
||||
function reconcileOrphans(isAlive = () => false) {
|
||||
function reconcileOrphans(isAlive = () => false, serverStartedAt = () => null) {
|
||||
try {
|
||||
let changes = 0;
|
||||
const { unseenMemberFolder } = require("./linux-accounts");
|
||||
@@ -317,7 +332,11 @@ function reconcileOrphans(isAlive = () => false) {
|
||||
const interrupted =
|
||||
row.tool !== "shell" &&
|
||||
!!row.session_id &&
|
||||
!(row.tool === "claude" && userExitStmt.get(row.session_id));
|
||||
!(row.tool === "claude" && userExitStmt.get(row.session_id)) &&
|
||||
!(
|
||||
tmuxOutlived(row, serverStartedAt) &&
|
||||
(row.tool !== "claude" || anyEndStmt.get(row.session_id))
|
||||
);
|
||||
changes += endStmt.run({
|
||||
id: row.id,
|
||||
status: interrupted ? "interrupted" : "abandoned",
|
||||
|
||||
Reference in New Issue
Block a user