aicodeman 1.29.0 → 1.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -30
- package/README.zh-CN.md +207 -51
- package/dist/reboot-restore.d.ts +146 -0
- package/dist/reboot-restore.d.ts.map +1 -0
- package/dist/reboot-restore.js +205 -0
- package/dist/reboot-restore.js.map +1 -0
- package/dist/session-auto-name.d.ts +74 -0
- package/dist/session-auto-name.d.ts.map +1 -0
- package/dist/session-auto-name.js +339 -0
- package/dist/session-auto-name.js.map +1 -0
- package/dist/session-env-clamp.d.ts +80 -0
- package/dist/session-env-clamp.d.ts.map +1 -0
- package/dist/session-env-clamp.js +96 -0
- package/dist/session-env-clamp.js.map +1 -0
- package/dist/session.d.ts +54 -4
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +86 -7
- package/dist/session.js.map +1 -1
- package/dist/types/session.d.ts +18 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/web/ports/session-port.d.ts +31 -0
- package/dist/web/ports/session-port.d.ts.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/{app.556be563.js → app.6d2dc4e8.js} +2 -2
- package/dist/web/public/app.6d2dc4e8.js.br +0 -0
- package/dist/web/public/app.6d2dc4e8.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/constants.258b140f.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/i18n.5f897ed5.js +1 -0
- package/dist/web/public/i18n.5f897ed5.js.br +0 -0
- package/dist/web/public/i18n.5f897ed5.js.gz +0 -0
- package/dist/web/public/image-input.cd4b97c4.js.gz +0 -0
- package/dist/web/public/index.html +34 -8
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.2cf04f17.js.gz +0 -0
- package/dist/web/public/mobile-handlers.6f354a87.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/{mobile.e9d0b53e.css → mobile.6caaa28a.css} +1 -1
- package/dist/web/public/mobile.6caaa28a.css.br +0 -0
- package/dist/web/public/mobile.6caaa28a.css.gz +0 -0
- package/dist/web/public/notification-manager.36ea4624.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.5b07ad14.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/reboot-restore-ui.js +142 -0
- package/dist/web/public/reboot-restore-ui.js.br +0 -0
- package/dist/web/public/reboot-restore-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/session-ui.42b81477.js.gz +0 -0
- package/dist/web/public/settings-ui.58f1d756.js +67 -0
- package/dist/web/public/settings-ui.58f1d756.js.br +0 -0
- package/dist/web/public/settings-ui.58f1d756.js.gz +0 -0
- package/dist/web/public/styles.e6edfb0b.css +1 -0
- package/dist/web/public/styles.e6edfb0b.css.br +0 -0
- package/dist/web/public/styles.e6edfb0b.css.gz +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.42c24949.js.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js +1 -0
- package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.br +0 -0
- package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.gz +0 -0
- package/dist/web/public/{terminal-ui.38c49244.js → terminal-ui.4f8d5820.js} +2 -2
- package/dist/web/public/terminal-ui.4f8d5820.js.br +0 -0
- package/dist/web/public/terminal-ui.4f8d5820.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.bd6882b8.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.c4b51eb6.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/reboot-restore-registry.d.ts +107 -0
- package/dist/web/reboot-restore-registry.d.ts.map +1 -0
- package/dist/web/reboot-restore-registry.js +189 -0
- package/dist/web/reboot-restore-registry.js.map +1 -0
- package/dist/web/routes/index.d.ts +1 -0
- package/dist/web/routes/index.d.ts.map +1 -1
- package/dist/web/routes/index.js +1 -0
- package/dist/web/routes/index.js.map +1 -1
- package/dist/web/routes/reboot-restore-routes.d.ts +30 -0
- package/dist/web/routes/reboot-restore-routes.d.ts.map +1 -0
- package/dist/web/routes/reboot-restore-routes.js +273 -0
- package/dist/web/routes/reboot-restore-routes.js.map +1 -0
- package/dist/web/routes/session-routes.d.ts +1 -18
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +13 -71
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/ws-routes.d.ts.map +1 -1
- package/dist/web/routes/ws-routes.js +2 -1
- package/dist/web/routes/ws-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +12 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +20 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +74 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +243 -2
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-listener-wiring.d.ts +6 -2
- package/dist/web/session-listener-wiring.d.ts.map +1 -1
- package/dist/web/session-listener-wiring.js +33 -2
- package/dist/web/session-listener-wiring.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/public/app.556be563.js.br +0 -0
- package/dist/web/public/app.556be563.js.gz +0 -0
- package/dist/web/public/i18n.561ca09c.js +0 -1
- package/dist/web/public/i18n.561ca09c.js.br +0 -0
- package/dist/web/public/i18n.561ca09c.js.gz +0 -0
- package/dist/web/public/mobile.e9d0b53e.css.br +0 -0
- package/dist/web/public/mobile.e9d0b53e.css.gz +0 -0
- package/dist/web/public/settings-ui.e0c7f6b4.js +0 -67
- package/dist/web/public/settings-ui.e0c7f6b4.js.br +0 -0
- package/dist/web/public/settings-ui.e0c7f6b4.js.gz +0 -0
- package/dist/web/public/styles.6add175d.css +0 -1
- package/dist/web/public/styles.6add175d.css.br +0 -0
- package/dist/web/public/styles.6add175d.css.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js +0 -1
- package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js.br +0 -0
- package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js.gz +0 -0
- package/dist/web/public/terminal-ui.38c49244.js.br +0 -0
- package/dist/web/public/terminal-ui.38c49244.js.gz +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Decide which sessions a host reboot destroyed and may be rebuilt.
|
|
3
|
+
*
|
|
4
|
+
* A server restart and a host reboot both leave `reconcileSessions()` reporting
|
|
5
|
+
* dead sessions, and they need opposite handling. A server restart leaves the
|
|
6
|
+
* tmux panes running, so recovery ATTACHES to them. A host reboot takes the tmux
|
|
7
|
+
* server down with it, so there is nothing to attach to and the pane has to be
|
|
8
|
+
* created again. This module holds the decision half of that second case, kept
|
|
9
|
+
* free of tmux and disk access so it can be unit tested without either. Every
|
|
10
|
+
* observation it reads is gathered by the caller and passed in.
|
|
11
|
+
*
|
|
12
|
+
* "Eligible" here means a session the user did not end on purpose. The rule that
|
|
13
|
+
* an intentional kill or detach is never auto-revived is enforced at runtime by
|
|
14
|
+
* an in-memory guard in `TmuxManager`, and memory does not survive a reboot. The
|
|
15
|
+
* durable equivalent is the record `cleanupSession()` leaves behind. An unpinned
|
|
16
|
+
* kill deletes the record outright, so it is already absent here. A pinned kill
|
|
17
|
+
* goes through `demoteOrRemoveSession()` and lands as `status: 'stopped'`, which
|
|
18
|
+
* is the marker this module refuses. Pruning keeps a pinned record WITHOUT
|
|
19
|
+
* touching its status, so a pinned session a reboot killed still reads `idle` or
|
|
20
|
+
* `busy` and stays eligible.
|
|
21
|
+
*
|
|
22
|
+
* ⚠️ Ending the AGENT rather than the session is a shape this module CANNOT
|
|
23
|
+
* recognise today, and a reboot restores it. `/exit` ends the CLI inside the
|
|
24
|
+
* pane, `remain-on-exit` keeps the pane, and the PTY Codeman owns is the
|
|
25
|
+
* `tmux attach-session` process, which stays alive throughout — so no exit
|
|
26
|
+
* handler runs, no lifecycle `exit` is logged, and the record keeps both its pid
|
|
27
|
+
* and `status: 'idle'`. Nothing durable distinguishes it from a session that was
|
|
28
|
+
* simply idle when the power went. Ark0N/Codeman#446 covers making Codeman
|
|
29
|
+
* notice the dead pane; until a record can say the agent is gone, this pass will
|
|
30
|
+
* offer those sessions back, and the user dismisses or closes them.
|
|
31
|
+
*
|
|
32
|
+
* The `pid` check below is therefore NOT that rule. It refuses a record whose
|
|
33
|
+
* attach process was already gone, which is a session that never started or
|
|
34
|
+
* whose pane died outright.
|
|
35
|
+
*
|
|
36
|
+
* @dependencies types (SessionState), config/cli-registry
|
|
37
|
+
* @consumedby web/server (plan build at boot), web/routes/reboot-restore-routes
|
|
38
|
+
*
|
|
39
|
+
* @module reboot-restore
|
|
40
|
+
*/
|
|
41
|
+
import type { SessionState } from './types.js';
|
|
42
|
+
/** Observations the reboot heuristic reads. Gathered by the caller, never here. */
|
|
43
|
+
export interface RebootEvidence {
|
|
44
|
+
/** Sessions that still had a live pane during reconciliation. */
|
|
45
|
+
livePaneCount: number;
|
|
46
|
+
/** Sessions reconciliation just marked dead. */
|
|
47
|
+
deadSessionCount: number;
|
|
48
|
+
/** `os.uptime()`, in seconds. */
|
|
49
|
+
uptimeSeconds: number;
|
|
50
|
+
/** Newest `lastActivityAt` across the persisted records, in ms since the epoch. */
|
|
51
|
+
newestPersistedActivityAt: number;
|
|
52
|
+
/** `Date.now()` when the evidence was gathered, in ms. */
|
|
53
|
+
now: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Decide whether the machine plausibly rebooted rather than the server restarting.
|
|
57
|
+
*
|
|
58
|
+
* Two signals have to agree. The socket must hold no panes at all while state
|
|
59
|
+
* still lists sessions, which rules out an ordinary server restart. The host
|
|
60
|
+
* must also have booted after the newest persisted session activity, which is
|
|
61
|
+
* the corroboration `os.uptime()` provides cheaply. A wiped tmux socket on a
|
|
62
|
+
* long-uptime host fails the second test, so a user who killed the tmux server
|
|
63
|
+
* by hand does not get every session offered back to them.
|
|
64
|
+
*
|
|
65
|
+
* This heuristic decides whether to ASK, never whether to act. A wrong yes costs
|
|
66
|
+
* the user a banner they dismiss, because the restore itself waits for a click.
|
|
67
|
+
*
|
|
68
|
+
* ⚠️ `os.uptime()` reports the HOST's uptime, which a container shares, and that
|
|
69
|
+
* cuts BOTH ways rather than simply switching the feature off in Docker. After a
|
|
70
|
+
* genuine host reboot a containerized Codeman sees the host's short uptime, so the
|
|
71
|
+
* banner DOES appear and the feature works. What it cannot see is a container-only
|
|
72
|
+
* restart: the host uptime is long, the boot test fails, and no banner appears
|
|
73
|
+
* although every in-container pane is gone (`docker/server.Dockerfile` installs
|
|
74
|
+
* tmux inside the Codeman container, and the self-updater restarts the Compose
|
|
75
|
+
* deployment by exiting the container, so that is the case where this would help
|
|
76
|
+
* most). Failing quiet is the safe direction, and closing the gap needs a boot
|
|
77
|
+
* signal the container owns (PID 1's start time, gated on the existing
|
|
78
|
+
* `isRunningInContainer()`) rather than a wider heuristic.
|
|
79
|
+
*/
|
|
80
|
+
export declare function looksLikeHostReboot(evidence: RebootEvidence): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Pick the conversation the rebuilt pane should resume.
|
|
83
|
+
*
|
|
84
|
+
* The chain's tail is the newest conversation the session was holding, which is
|
|
85
|
+
* what a compact or a clear leaves behind; `resumeSessionId` covers a session
|
|
86
|
+
* that was itself started as a resume, and the session id is the original
|
|
87
|
+
* conversation for everything else.
|
|
88
|
+
*/
|
|
89
|
+
export declare function resolveResumeConversationId(state: SessionState): string;
|
|
90
|
+
/**
|
|
91
|
+
* Why one session was passed over. Reported for logging and shown to the user.
|
|
92
|
+
*
|
|
93
|
+
* The first seven are decided before anything is built. `capacity-reached` and
|
|
94
|
+
* `rebuild-failed` can only happen once a click is spending the plan, and they
|
|
95
|
+
* are the two the banner must not confuse with a missing workspace: one means
|
|
96
|
+
* "try again after closing something", the other means the CLI would not start.
|
|
97
|
+
*/
|
|
98
|
+
export interface RebootRestoreRejection {
|
|
99
|
+
sessionId: string;
|
|
100
|
+
reason: 'no-persisted-record' | 'intentionally-ended' | 'not-running' | 'respawn-blocked' | 'remote-or-docker' | 'unsupported-mode' | 'no-working-dir' | 'workspace-missing' | 'workspace-forbidden' | 'already-live' | 'capacity-reached' | 'rebuild-failed';
|
|
101
|
+
}
|
|
102
|
+
/** One restorable session, as the banner shows it and the rebuild replays it. */
|
|
103
|
+
export interface RebootRestoreEntry {
|
|
104
|
+
sessionId: string;
|
|
105
|
+
name?: string;
|
|
106
|
+
workingDir: string;
|
|
107
|
+
owner?: string;
|
|
108
|
+
mode: string;
|
|
109
|
+
/** The conversation the rebuilt pane resumes. */
|
|
110
|
+
resumeConversationId: string;
|
|
111
|
+
/**
|
|
112
|
+
* The persisted record, kept whole so the rebuild can replay what it held.
|
|
113
|
+
* Read at boot, before pruning deletes it, and held in memory until the click.
|
|
114
|
+
*/
|
|
115
|
+
state: SessionState;
|
|
116
|
+
}
|
|
117
|
+
export interface RebootRestorePlan {
|
|
118
|
+
restore: RebootRestoreEntry[];
|
|
119
|
+
skipped: RebootRestoreRejection[];
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Split the sessions reconciliation just killed into the ones a reboot restore
|
|
123
|
+
* may offer and the ones it must leave alone.
|
|
124
|
+
*
|
|
125
|
+
* @param deadSessionIds Session ids `reconcileSessions()` reported as dead.
|
|
126
|
+
* @param persisted The `state.json` session records, which `cleanupStaleSessions()`
|
|
127
|
+
* has not pruned yet at the point this runs.
|
|
128
|
+
* @param workspaceExists Whether a working directory is still on disk. A tmux
|
|
129
|
+
* session can outlive its deleted repo, and rebuilding one there would scaffold
|
|
130
|
+
* an empty tree. The caller owns the disk access; the click re-checks, because
|
|
131
|
+
* a repo can be deleted between the boot and the click.
|
|
132
|
+
*/
|
|
133
|
+
export declare function planRebootRestore(deadSessionIds: readonly string[], persisted: Readonly<Record<string, SessionState>>, workspaceExists: (workingDir: string) => boolean): RebootRestorePlan;
|
|
134
|
+
/**
|
|
135
|
+
* Drop the entries whose conversation is already on screen.
|
|
136
|
+
*
|
|
137
|
+
* Hours can pass between the boot that built the plan and the click that spends
|
|
138
|
+
* it, and the Resume list can reach the same conversation in the meantime. Two
|
|
139
|
+
* panes running `claude --resume` on one conversation is the failure this
|
|
140
|
+
* prevents, so a match on either the session id or the conversation id is enough
|
|
141
|
+
* to skip the entry.
|
|
142
|
+
*/
|
|
143
|
+
export declare function rejectAlreadyLive(entries: readonly RebootRestoreEntry[], liveSessionIds: ReadonlySet<string>, liveConversationIds: ReadonlySet<string>): RebootRestorePlan;
|
|
144
|
+
/** Newest `lastActivityAt` across persisted records, or 0 when there are none. */
|
|
145
|
+
export declare function newestPersistedActivity(persisted: Readonly<Record<string, SessionState>>): number;
|
|
146
|
+
//# sourceMappingURL=reboot-restore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reboot-restore.d.ts","sourceRoot":"","sources":["../src/reboot-restore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAM/C,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,aAAa,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,gBAAgB,EAAE,MAAM,CAAC;IACzB,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,yBAAyB,EAAE,MAAM,CAAC;IAClC,0DAA0D;IAC1D,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAMrE;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAIvE;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EACF,qBAAqB,GACrB,qBAAqB,GACrB,aAAa,GACb,iBAAiB,GACjB,kBAAkB,GAClB,kBAAkB,GAClB,gBAAgB,GAChB,mBAAmB,GACnB,qBAAqB,GACrB,cAAc,GACd,kBAAkB,GAClB,gBAAgB,CAAC;CACtB;AAED,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,oBAAoB,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,OAAO,EAAE,sBAAsB,EAAE,CAAC;CACnC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,EACjD,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,GAC/C,iBAAiB,CAuEnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,kBAAkB,EAAE,EACtC,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,EACnC,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAC,GACvC,iBAAiB,CAWnB;AAED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,GAAG,MAAM,CAOjG"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Decide which sessions a host reboot destroyed and may be rebuilt.
|
|
3
|
+
*
|
|
4
|
+
* A server restart and a host reboot both leave `reconcileSessions()` reporting
|
|
5
|
+
* dead sessions, and they need opposite handling. A server restart leaves the
|
|
6
|
+
* tmux panes running, so recovery ATTACHES to them. A host reboot takes the tmux
|
|
7
|
+
* server down with it, so there is nothing to attach to and the pane has to be
|
|
8
|
+
* created again. This module holds the decision half of that second case, kept
|
|
9
|
+
* free of tmux and disk access so it can be unit tested without either. Every
|
|
10
|
+
* observation it reads is gathered by the caller and passed in.
|
|
11
|
+
*
|
|
12
|
+
* "Eligible" here means a session the user did not end on purpose. The rule that
|
|
13
|
+
* an intentional kill or detach is never auto-revived is enforced at runtime by
|
|
14
|
+
* an in-memory guard in `TmuxManager`, and memory does not survive a reboot. The
|
|
15
|
+
* durable equivalent is the record `cleanupSession()` leaves behind. An unpinned
|
|
16
|
+
* kill deletes the record outright, so it is already absent here. A pinned kill
|
|
17
|
+
* goes through `demoteOrRemoveSession()` and lands as `status: 'stopped'`, which
|
|
18
|
+
* is the marker this module refuses. Pruning keeps a pinned record WITHOUT
|
|
19
|
+
* touching its status, so a pinned session a reboot killed still reads `idle` or
|
|
20
|
+
* `busy` and stays eligible.
|
|
21
|
+
*
|
|
22
|
+
* ⚠️ Ending the AGENT rather than the session is a shape this module CANNOT
|
|
23
|
+
* recognise today, and a reboot restores it. `/exit` ends the CLI inside the
|
|
24
|
+
* pane, `remain-on-exit` keeps the pane, and the PTY Codeman owns is the
|
|
25
|
+
* `tmux attach-session` process, which stays alive throughout — so no exit
|
|
26
|
+
* handler runs, no lifecycle `exit` is logged, and the record keeps both its pid
|
|
27
|
+
* and `status: 'idle'`. Nothing durable distinguishes it from a session that was
|
|
28
|
+
* simply idle when the power went. Ark0N/Codeman#446 covers making Codeman
|
|
29
|
+
* notice the dead pane; until a record can say the agent is gone, this pass will
|
|
30
|
+
* offer those sessions back, and the user dismisses or closes them.
|
|
31
|
+
*
|
|
32
|
+
* The `pid` check below is therefore NOT that rule. It refuses a record whose
|
|
33
|
+
* attach process was already gone, which is a session that never started or
|
|
34
|
+
* whose pane died outright.
|
|
35
|
+
*
|
|
36
|
+
* @dependencies types (SessionState), config/cli-registry
|
|
37
|
+
* @consumedby web/server (plan build at boot), web/routes/reboot-restore-routes
|
|
38
|
+
*
|
|
39
|
+
* @module reboot-restore
|
|
40
|
+
*/
|
|
41
|
+
import { getCli } from './config/cli-registry/registry.js';
|
|
42
|
+
/** Session statuses a reboot restore may rebuild. `stopped` is the kill marker. */
|
|
43
|
+
const RESTORABLE_STATUSES = new Set(['idle', 'busy', 'error']);
|
|
44
|
+
/**
|
|
45
|
+
* Decide whether the machine plausibly rebooted rather than the server restarting.
|
|
46
|
+
*
|
|
47
|
+
* Two signals have to agree. The socket must hold no panes at all while state
|
|
48
|
+
* still lists sessions, which rules out an ordinary server restart. The host
|
|
49
|
+
* must also have booted after the newest persisted session activity, which is
|
|
50
|
+
* the corroboration `os.uptime()` provides cheaply. A wiped tmux socket on a
|
|
51
|
+
* long-uptime host fails the second test, so a user who killed the tmux server
|
|
52
|
+
* by hand does not get every session offered back to them.
|
|
53
|
+
*
|
|
54
|
+
* This heuristic decides whether to ASK, never whether to act. A wrong yes costs
|
|
55
|
+
* the user a banner they dismiss, because the restore itself waits for a click.
|
|
56
|
+
*
|
|
57
|
+
* ⚠️ `os.uptime()` reports the HOST's uptime, which a container shares, and that
|
|
58
|
+
* cuts BOTH ways rather than simply switching the feature off in Docker. After a
|
|
59
|
+
* genuine host reboot a containerized Codeman sees the host's short uptime, so the
|
|
60
|
+
* banner DOES appear and the feature works. What it cannot see is a container-only
|
|
61
|
+
* restart: the host uptime is long, the boot test fails, and no banner appears
|
|
62
|
+
* although every in-container pane is gone (`docker/server.Dockerfile` installs
|
|
63
|
+
* tmux inside the Codeman container, and the self-updater restarts the Compose
|
|
64
|
+
* deployment by exiting the container, so that is the case where this would help
|
|
65
|
+
* most). Failing quiet is the safe direction, and closing the gap needs a boot
|
|
66
|
+
* signal the container owns (PID 1's start time, gated on the existing
|
|
67
|
+
* `isRunningInContainer()`) rather than a wider heuristic.
|
|
68
|
+
*/
|
|
69
|
+
export function looksLikeHostReboot(evidence) {
|
|
70
|
+
if (evidence.deadSessionCount === 0)
|
|
71
|
+
return false;
|
|
72
|
+
if (evidence.livePaneCount > 0)
|
|
73
|
+
return false;
|
|
74
|
+
if (evidence.newestPersistedActivityAt <= 0)
|
|
75
|
+
return false;
|
|
76
|
+
const bootedAt = evidence.now - evidence.uptimeSeconds * 1000;
|
|
77
|
+
return bootedAt > evidence.newestPersistedActivityAt;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Pick the conversation the rebuilt pane should resume.
|
|
81
|
+
*
|
|
82
|
+
* The chain's tail is the newest conversation the session was holding, which is
|
|
83
|
+
* what a compact or a clear leaves behind; `resumeSessionId` covers a session
|
|
84
|
+
* that was itself started as a resume, and the session id is the original
|
|
85
|
+
* conversation for everything else.
|
|
86
|
+
*/
|
|
87
|
+
export function resolveResumeConversationId(state) {
|
|
88
|
+
const chain = state.claudeSessionChain;
|
|
89
|
+
const chainTail = Array.isArray(chain) && chain.length > 0 ? chain[chain.length - 1] : undefined;
|
|
90
|
+
return chainTail || state.resumeSessionId || state.id;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Split the sessions reconciliation just killed into the ones a reboot restore
|
|
94
|
+
* may offer and the ones it must leave alone.
|
|
95
|
+
*
|
|
96
|
+
* @param deadSessionIds Session ids `reconcileSessions()` reported as dead.
|
|
97
|
+
* @param persisted The `state.json` session records, which `cleanupStaleSessions()`
|
|
98
|
+
* has not pruned yet at the point this runs.
|
|
99
|
+
* @param workspaceExists Whether a working directory is still on disk. A tmux
|
|
100
|
+
* session can outlive its deleted repo, and rebuilding one there would scaffold
|
|
101
|
+
* an empty tree. The caller owns the disk access; the click re-checks, because
|
|
102
|
+
* a repo can be deleted between the boot and the click.
|
|
103
|
+
*/
|
|
104
|
+
export function planRebootRestore(deadSessionIds, persisted, workspaceExists) {
|
|
105
|
+
const restore = [];
|
|
106
|
+
const skipped = [];
|
|
107
|
+
for (const sessionId of deadSessionIds) {
|
|
108
|
+
const state = persisted[sessionId];
|
|
109
|
+
if (!state) {
|
|
110
|
+
// An unpinned kill already deleted the record, so absence IS the guard.
|
|
111
|
+
skipped.push({ sessionId, reason: 'no-persisted-record' });
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (!RESTORABLE_STATUSES.has(state.status)) {
|
|
115
|
+
// A pinned kill was demoted to `stopped`. Reviving it would undo the kill.
|
|
116
|
+
skipped.push({ sessionId, reason: 'intentionally-ended' });
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (state.pid === null || state.pid === undefined) {
|
|
120
|
+
// No attach process when the record was last written: the session never
|
|
121
|
+
// started, or its pane died outright rather than its agent exiting inside a
|
|
122
|
+
// surviving pane. Either way there was nothing running to bring back.
|
|
123
|
+
//
|
|
124
|
+
// ⚠️ This does NOT catch a session the user ended with `/exit`. See the
|
|
125
|
+
// module header: that leaves the pid in place, because the pid is the tmux
|
|
126
|
+
// attach process and `remain-on-exit` keeps it alive.
|
|
127
|
+
//
|
|
128
|
+
// Conservative on purpose. A session that somehow persisted no pid while
|
|
129
|
+
// genuinely running is not offered, and its conversation stays reachable
|
|
130
|
+
// from the Resume list, which is where every session would be without this
|
|
131
|
+
// feature.
|
|
132
|
+
skipped.push({ sessionId, reason: 'not-running' });
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (state.respawnBlocked === true) {
|
|
136
|
+
// The crash-loop breaker tripped on this pane. Re-creating it restarts the loop.
|
|
137
|
+
skipped.push({ sessionId, reason: 'respawn-blocked' });
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (state.remote || state.docker) {
|
|
141
|
+
// Both need another host or a container to be up, which a just-booted machine
|
|
142
|
+
// cannot promise. The remote reconnect watcher owns the remote case already.
|
|
143
|
+
skipped.push({ sessionId, reason: 'remote-or-docker' });
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
// Capability, not a CLI id: this pass resumes by handing the CLI a conversation
|
|
147
|
+
// id through the top-level `resumeSessionId`, which only a CLI whose history the
|
|
148
|
+
// claude-jsonl reader understands can consume that way. Others carry their thread
|
|
149
|
+
// id in their own `<Mode>Config`, which this pass does not thread through.
|
|
150
|
+
if (getCli(state.mode ?? 'claude')?.capabilities.transcript !== 'claude-jsonl') {
|
|
151
|
+
skipped.push({ sessionId, reason: 'unsupported-mode' });
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (!state.workingDir) {
|
|
155
|
+
skipped.push({ sessionId, reason: 'no-working-dir' });
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (!workspaceExists(state.workingDir)) {
|
|
159
|
+
skipped.push({ sessionId, reason: 'workspace-missing' });
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
restore.push({
|
|
163
|
+
sessionId,
|
|
164
|
+
name: state.name,
|
|
165
|
+
workingDir: state.workingDir,
|
|
166
|
+
owner: state.owner,
|
|
167
|
+
mode: state.mode ?? 'claude',
|
|
168
|
+
resumeConversationId: resolveResumeConversationId(state),
|
|
169
|
+
state,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return { restore, skipped };
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Drop the entries whose conversation is already on screen.
|
|
176
|
+
*
|
|
177
|
+
* Hours can pass between the boot that built the plan and the click that spends
|
|
178
|
+
* it, and the Resume list can reach the same conversation in the meantime. Two
|
|
179
|
+
* panes running `claude --resume` on one conversation is the failure this
|
|
180
|
+
* prevents, so a match on either the session id or the conversation id is enough
|
|
181
|
+
* to skip the entry.
|
|
182
|
+
*/
|
|
183
|
+
export function rejectAlreadyLive(entries, liveSessionIds, liveConversationIds) {
|
|
184
|
+
const restore = [];
|
|
185
|
+
const skipped = [];
|
|
186
|
+
for (const entry of entries) {
|
|
187
|
+
if (liveSessionIds.has(entry.sessionId) || liveConversationIds.has(entry.resumeConversationId)) {
|
|
188
|
+
skipped.push({ sessionId: entry.sessionId, reason: 'already-live' });
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
restore.push(entry);
|
|
192
|
+
}
|
|
193
|
+
return { restore, skipped };
|
|
194
|
+
}
|
|
195
|
+
/** Newest `lastActivityAt` across persisted records, or 0 when there are none. */
|
|
196
|
+
export function newestPersistedActivity(persisted) {
|
|
197
|
+
let newest = 0;
|
|
198
|
+
for (const state of Object.values(persisted)) {
|
|
199
|
+
const stamp = state.lastActivityAt ?? state.createdAt ?? 0;
|
|
200
|
+
if (stamp > newest)
|
|
201
|
+
newest = stamp;
|
|
202
|
+
}
|
|
203
|
+
return newest;
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=reboot-restore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reboot-restore.js","sourceRoot":"","sources":["../src/reboot-restore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Automatic session names from the first prompt.
|
|
3
|
+
*
|
|
4
|
+
* A new tab is born as `w3-myapp`, which says where it runs and nothing about
|
|
5
|
+
* what it is doing. Once the user submits a real prompt the tab can carry a
|
|
6
|
+
* title derived from it (`w3-myapp: fix the login redirect`), and this module
|
|
7
|
+
* holds the three pure pieces of that: a tracker that reconstructs the composer
|
|
8
|
+
* text from the keystrokes Codeman forwards, the title heuristic, and the
|
|
9
|
+
* prefix-preserving composition.
|
|
10
|
+
*
|
|
11
|
+
* Deliberately no LLM: the prompt already passes through the input boundary,
|
|
12
|
+
* so a local title is private, deterministic and identical for every CLI.
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ The tracker sits on the raw keystroke stream, which carries far more than
|
|
15
|
+
* the prompt: cursor keys, mouse reports Codeman forwards to the CLI, bracketed
|
|
16
|
+
* pastes, Alt chords, the bare Esc that interrupts a turn. Every one of those
|
|
17
|
+
* once named a tab something wrong (a lone Esc ate the next prompt's first
|
|
18
|
+
* character; a wheel tick mid-word dropped the first half of the prompt), so
|
|
19
|
+
* the rules below are explicit per key. The model is a best-effort transcript:
|
|
20
|
+
* keys whose effect on the composer is knowable are mirrored, keys that leave
|
|
21
|
+
* the text alone are ignored, and keys that replace it with something the
|
|
22
|
+
* tracker cannot see (history recall) TAINT the draft so that Enter submits
|
|
23
|
+
* nothing rather than a fragment. A prompt that yields no title leaves the
|
|
24
|
+
* session eligible for the next one.
|
|
25
|
+
*
|
|
26
|
+
* Only user-originated input is fed here; the Session decides that. Ralph
|
|
27
|
+
* kick-starts, respawn `/clear`s, cron launches and approval answers all go
|
|
28
|
+
* through the same write paths and must never become a tab title.
|
|
29
|
+
*
|
|
30
|
+
* @module session-auto-name
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Reconstructs the composer draft from forwarded keystrokes and reports each
|
|
34
|
+
* submitted prompt. Input arrives in arbitrary chunks (one keystroke, a paste,
|
|
35
|
+
* an agent's whole prompt plus Enter), so all state lives across calls.
|
|
36
|
+
*/
|
|
37
|
+
export declare class SubmittedPromptTracker {
|
|
38
|
+
private buffer;
|
|
39
|
+
private bufferCodePoints;
|
|
40
|
+
/** Code points typed past the cap; backspaces eat these before real text. */
|
|
41
|
+
private overflow;
|
|
42
|
+
/** Escape sequence in progress; a lone ESC means "just saw ESC". */
|
|
43
|
+
private sequence;
|
|
44
|
+
private inPaste;
|
|
45
|
+
/** The composer holds text the tracker never saw (history recall); Enter submits nothing. */
|
|
46
|
+
private tainted;
|
|
47
|
+
feed(data: string): string[];
|
|
48
|
+
private continueSequence;
|
|
49
|
+
private handleKeyOrEscape;
|
|
50
|
+
private handleCsi;
|
|
51
|
+
private handleKey;
|
|
52
|
+
/** One space between lines, never a run of them, and none at the start. */
|
|
53
|
+
private appendSeparator;
|
|
54
|
+
private append;
|
|
55
|
+
private backspace;
|
|
56
|
+
private killWord;
|
|
57
|
+
private reset;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Turns a submitted prompt into a title, or null when the prompt is not a task:
|
|
61
|
+
* empty, a slash command (`/clear`, `/model`), or a `!` shell escape.
|
|
62
|
+
*/
|
|
63
|
+
export declare function deriveAutoSessionName(prompt: string): string | null;
|
|
64
|
+
/**
|
|
65
|
+
* The name a placeholder becomes: `<prefix>: <title>`, so the tab keeps its
|
|
66
|
+
* case identity and its `w<n>` counter (the tab strip already renders that
|
|
67
|
+
* form as the title alone, prefix in the tooltip, and the next-session counter
|
|
68
|
+
* still matches it). A session with no name at all just takes the title. The
|
|
69
|
+
* result honours `maxLength` in UTF-16 units, the unit the rename route caps.
|
|
70
|
+
*/
|
|
71
|
+
export declare function composeAutoSessionName(currentName: string, title: string, maxLength?: number): string;
|
|
72
|
+
/** Codeman's own `w<n>-<case>` / `s<n>-<case>` placeholders, the only names auto-naming replaces. */
|
|
73
|
+
export declare function isGeneratedSessionName(name: string): boolean;
|
|
74
|
+
//# sourceMappingURL=session-auto-name.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-auto-name.d.ts","sourceRoot":"","sources":["../src/session-auto-name.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AA4CH;;;;GAIG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,gBAAgB,CAAK;IAC7B,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,CAAK;IACrB,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,6FAA6F;IAC7F,OAAO,CAAC,OAAO,CAAS;IAExB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE;IAsB5B,OAAO,CAAC,gBAAgB;IA8CxB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,SAAS;IAiBjB,OAAO,CAAC,SAAS;IA8CjB,2EAA2E;IAC3E,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,MAAM;IASd,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,KAAK;CAMd;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAInE;AA8BD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,SAAS,SAA0B,GAClC,MAAM,CAMR;AAeD,qGAAqG;AACrG,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5D"}
|