aicodeman 1.5.0 → 1.6.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 +10 -8
- package/README.zh-CN.md +289 -92
- package/dist/docker-export.d.ts +13 -0
- package/dist/docker-export.d.ts.map +1 -1
- package/dist/docker-export.js +55 -8
- package/dist/docker-export.js.map +1 -1
- package/dist/docker-hosts.d.ts +36 -5
- package/dist/docker-hosts.d.ts.map +1 -1
- package/dist/docker-hosts.js +90 -16
- package/dist/docker-hosts.js.map +1 -1
- package/dist/remote-hosts.d.ts +61 -1
- package/dist/remote-hosts.d.ts.map +1 -1
- package/dist/remote-hosts.js +142 -0
- package/dist/remote-hosts.js.map +1 -1
- package/dist/remote-reconnect.d.ts +129 -0
- package/dist/remote-reconnect.d.ts.map +1 -0
- package/dist/remote-reconnect.js +119 -0
- package/dist/remote-reconnect.js.map +1 -0
- package/dist/services/unified-session-service.d.ts +13 -1
- package/dist/services/unified-session-service.d.ts.map +1 -1
- package/dist/services/unified-session-service.js +88 -3
- package/dist/services/unified-session-service.js.map +1 -1
- package/dist/session-order.d.ts +40 -0
- package/dist/session-order.d.ts.map +1 -0
- package/dist/session-order.js +67 -0
- package/dist/session-order.js.map +1 -0
- package/dist/session.d.ts +39 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +87 -19
- package/dist/session.js.map +1 -1
- package/dist/state-store.d.ts +12 -0
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +33 -0
- package/dist/state-store.js.map +1 -1
- package/dist/tmux-manager.d.ts +66 -0
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +260 -8
- package/dist/tmux-manager.js.map +1 -1
- package/dist/types/app-state.d.ts +2 -0
- package/dist/types/app-state.d.ts.map +1 -1
- package/dist/types/app-state.js.map +1 -1
- package/dist/types/session.d.ts +43 -1
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js +1 -1
- package/dist/types/session.js.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.b78b0465.js +38 -0
- package/dist/web/public/app.b78b0465.js.br +0 -0
- package/dist/web/public/app.b78b0465.js.gz +0 -0
- package/dist/web/public/{constants.5ce97313.js → constants.21a0a83e.js} +10 -0
- package/dist/web/public/constants.21a0a83e.js.br +0 -0
- package/dist/web/public/{constants.5ce97313.js.gz → constants.21a0a83e.js.gz} +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +32 -13
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.3f91acef.js.gz +0 -0
- package/dist/web/public/mobile-handlers.b5a667ef.js.gz +0 -0
- package/dist/web/public/mobile.597136a6.css +1 -0
- package/dist/web/public/mobile.597136a6.css.br +0 -0
- package/dist/web/public/mobile.597136a6.css.gz +0 -0
- package/dist/web/public/notification-manager.ca9dc40d.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.3c9b0465.js → panels-ui.58582d4c.js} +2 -2
- package/dist/web/public/panels-ui.58582d4c.js.br +0 -0
- package/dist/web/public/panels-ui.58582d4c.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/respawn-ui.2d249da9.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-ui.0cf86dd4.js +68 -0
- package/dist/web/public/session-ui.0cf86dd4.js.br +0 -0
- package/dist/web/public/session-ui.0cf86dd4.js.gz +0 -0
- package/dist/web/public/{settings-ui.29214cf1.js → settings-ui.bc5f4792.js} +2 -2
- package/dist/web/public/settings-ui.bc5f4792.js.br +0 -0
- package/dist/web/public/settings-ui.bc5f4792.js.gz +0 -0
- package/dist/web/public/styles.7295e6f4.css +1 -0
- package/dist/web/public/styles.7295e6f4.css.br +0 -0
- package/dist/web/public/styles.7295e6f4.css.gz +0 -0
- package/dist/web/public/subagent-windows.69b60c35.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.9ea677e9.js +3 -0
- package/dist/web/public/terminal-ui.9ea677e9.js.br +0 -0
- package/dist/web/public/terminal-ui.9ea677e9.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-zerolag-input.137ad9f0.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.085e9e73.js.gz +0 -0
- package/dist/web/routes/case-routes.d.ts +2 -2
- package/dist/web/routes/case-routes.d.ts.map +1 -1
- package/dist/web/routes/case-routes.js +75 -21
- package/dist/web/routes/case-routes.js.map +1 -1
- package/dist/web/routes/hook-event-routes.d.ts.map +1 -1
- package/dist/web/routes/hook-event-routes.js +9 -0
- package/dist/web/routes/hook-event-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +172 -7
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +14 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +42 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +9 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +57 -1
- package/dist/web/server.js.map +1 -1
- package/dist/web/sse-events.d.ts +18 -0
- package/dist/web/sse-events.d.ts.map +1 -1
- package/dist/web/sse-events.js +21 -0
- package/dist/web/sse-events.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/public/app.6c4b9893.js +0 -38
- package/dist/web/public/app.6c4b9893.js.br +0 -0
- package/dist/web/public/app.6c4b9893.js.gz +0 -0
- package/dist/web/public/constants.5ce97313.js.br +0 -0
- package/dist/web/public/mobile.0644e3ac.css +0 -1
- package/dist/web/public/mobile.0644e3ac.css.br +0 -0
- package/dist/web/public/mobile.0644e3ac.css.gz +0 -0
- package/dist/web/public/panels-ui.3c9b0465.js.br +0 -0
- package/dist/web/public/panels-ui.3c9b0465.js.gz +0 -0
- package/dist/web/public/session-ui.4851c61a.js +0 -58
- package/dist/web/public/session-ui.4851c61a.js.br +0 -0
- package/dist/web/public/session-ui.4851c61a.js.gz +0 -0
- package/dist/web/public/settings-ui.29214cf1.js.br +0 -0
- package/dist/web/public/settings-ui.29214cf1.js.gz +0 -0
- package/dist/web/public/styles.9e6cdb9d.css +0 -1
- package/dist/web/public/styles.9e6cdb9d.css.br +0 -0
- package/dist/web/public/styles.9e6cdb9d.css.gz +0 -0
- package/dist/web/public/terminal-ui.efda6083.js +0 -3
- package/dist/web/public/terminal-ui.efda6083.js.br +0 -0
- package/dist/web/public/terminal-ui.efda6083.js.gz +0 -0
package/dist/remote-hosts.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RemoteCase, RemoteHost, RemoteSshOptions, SessionMode, SessionRemote } from './types.js';
|
|
1
|
+
import type { RemoteCase, RemoteHost, RemoteSessionInfo, RemoteSshOptions, SessionMode, SessionRemote } from './types.js';
|
|
2
2
|
export declare function remoteHostsPath(configDir: string): string;
|
|
3
3
|
export declare function remoteCasesPath(configDir: string): string;
|
|
4
4
|
export declare function readRemoteHosts(configDir: string): Promise<RemoteHost[]>;
|
|
@@ -57,10 +57,70 @@ export interface RemoteTmuxCheckResult {
|
|
|
57
57
|
* when tmux is missing or the host is unreachable. Never throws.
|
|
58
58
|
*/
|
|
59
59
|
export declare function checkRemoteTmuxAvailable(host: Pick<RemoteHost, 'username' | 'host' | 'port'> & RemoteSshOptions): Promise<RemoteTmuxCheckResult>;
|
|
60
|
+
/**
|
|
61
|
+
* COD-105 — build the SSH command that lists `codeman-*` tmux sessions on a
|
|
62
|
+
* remote host's canonical `-L codeman` socket.
|
|
63
|
+
*
|
|
64
|
+
* `list-sessions` exits NON-ZERO with empty output when no sessions exist (and
|
|
65
|
+
* the server isn't running), so `2>/dev/null` swallows tmux's "no server
|
|
66
|
+
* running" stderr; the caller treats a non-zero exit / empty output as "no
|
|
67
|
+
* sessions" rather than an error.
|
|
68
|
+
*
|
|
69
|
+
* COD-107 — connection options come from the shared `buildSshConnectionArgs`, so
|
|
70
|
+
* discovery connects with the SAME port/identity/proxy/jump-host as the launch
|
|
71
|
+
* and the tmux prereq probe.
|
|
72
|
+
*/
|
|
73
|
+
export declare function buildRemoteListSessionsCommand(host: Pick<RemoteHost, 'username' | 'host' | 'port'> & RemoteSshOptions): string;
|
|
74
|
+
/**
|
|
75
|
+
* COD-105 — pure parser for the `tmux list-sessions -F` output emitted by
|
|
76
|
+
* `buildRemoteListSessionsCommand`. Factored out so the parse is unit-testable
|
|
77
|
+
* without opening a real ssh connection.
|
|
78
|
+
*
|
|
79
|
+
* - Splits each non-empty line into [name, attached, created, windows] on the
|
|
80
|
+
* field separator. IMPORTANT: the remote tmux's `-F "…\t…"` format does NOT
|
|
81
|
+
* expand `\t` to a real tab — it emits the LITERAL two-character sequence
|
|
82
|
+
* `\t` (verified on aa-desktop / tmux next-3.7). So we split on the literal
|
|
83
|
+
* backslash-t sequence; we also tolerate a real tab in case a tmux build
|
|
84
|
+
* does expand it. (A real TAB is the regex `\t`; a literal backslash-t is the
|
|
85
|
+
* regex `\\t`.)
|
|
86
|
+
* - Keeps ONLY sessions whose name starts with `codeman-` (ignores foreign tmux
|
|
87
|
+
* sessions that happen to share the socket).
|
|
88
|
+
* - Coerces: `attached` → boolean (`'1'`), `created`/`windows` → finite ints.
|
|
89
|
+
* - Skips malformed lines (wrong column count or non-numeric created/windows)
|
|
90
|
+
* rather than emitting garbage.
|
|
91
|
+
*/
|
|
92
|
+
export declare function parseRemoteSessionList(stdout: string): RemoteSessionInfo[];
|
|
93
|
+
/**
|
|
94
|
+
* COD-105 — discover `codeman-*` tmux sessions already running on a remote host
|
|
95
|
+
* (created by the remote's own Codeman, another instance, or this one), so the
|
|
96
|
+
* operator can attach to one this Codeman didn't launch.
|
|
97
|
+
*
|
|
98
|
+
* NEVER throws: returns `[]` on unreachable host / no tmux / no sessions
|
|
99
|
+
* (`list-sessions` exits non-zero with empty output when there are none).
|
|
100
|
+
*
|
|
101
|
+
* VITEST guard — like `checkRemoteTmuxAvailable`, returns `[]` under test so a
|
|
102
|
+
* real ssh never runs in a request path (which would make route tests hit a
|
|
103
|
+
* ~10s timeout). The command construction is covered by
|
|
104
|
+
* `buildRemoteListSessionsCommand` and the parse by `parseRemoteSessionList`.
|
|
105
|
+
*/
|
|
106
|
+
export declare function listRemoteCodemanSessions(remote: Pick<RemoteHost, 'username' | 'host' | 'port'> & RemoteSshOptions): Promise<RemoteSessionInfo[]>;
|
|
60
107
|
export declare function remoteDisplayPath(remote: Pick<SessionRemote, 'username' | 'host' | 'remotePath'> | {
|
|
61
108
|
username: string;
|
|
62
109
|
host: string;
|
|
63
110
|
path: string;
|
|
64
111
|
}): string;
|
|
65
112
|
export declare function toSessionRemote(host: RemoteHost, remoteCase: RemoteCase): SessionRemote;
|
|
113
|
+
/**
|
|
114
|
+
* COD-105 — build a NON-owned `SessionRemote` for ATTACHING to a `codeman-*`
|
|
115
|
+
* session already running on a remote host (discovered via
|
|
116
|
+
* `listRemoteCodemanSessions`). The resulting session's pane runs
|
|
117
|
+
* `tmux -L codeman attach -t <remoteSessionName>` (see
|
|
118
|
+
* `buildRemoteAttachCommand`), and because we did NOT create the remote session,
|
|
119
|
+
* `owned: false` means closing the tab DETACHES rather than killing it.
|
|
120
|
+
*
|
|
121
|
+
* `remotePath` is informational here (the attached remote session keeps its own
|
|
122
|
+
* cwd); we record the host's nominal path so display helpers still show
|
|
123
|
+
* `user@host:path`.
|
|
124
|
+
*/
|
|
125
|
+
export declare function toAttachedSessionRemote(host: RemoteHost, remoteSessionName: string, remotePath: string): SessionRemote;
|
|
66
126
|
//# sourceMappingURL=remote-hosts.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-hosts.d.ts","sourceRoot":"","sources":["../src/remote-hosts.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,UAAU,EAEV,UAAU,EACV,gBAAgB,EAChB,WAAW,EACX,aAAa,EACd,MAAM,YAAY,CAAC;AAOpB,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzD;AAiBD,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAE9E;AAED,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5F;AAED,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAE9E;AAED,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5F;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAYrE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAAC,GAAG,MAAM,CAEnF;AA0BD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,CAcpG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,gBAAgB,GACtE,MAAM,CAGR;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,OAAO,CAAC;IACZ,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,gBAAgB,GACtE,OAAO,CAAC,qBAAqB,CAAC,
|
|
1
|
+
{"version":3,"file":"remote-hosts.d.ts","sourceRoot":"","sources":["../src/remote-hosts.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,UAAU,EAEV,UAAU,EACV,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,EACX,aAAa,EACd,MAAM,YAAY,CAAC;AAOpB,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzD;AAiBD,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAE9E;AAED,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5F;AAED,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAE9E;AAED,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5F;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAYrE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAAC,GAAG,MAAM,CAEnF;AA0BD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,CAcpG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,gBAAgB,GACtE,MAAM,CAGR;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,OAAO,CAAC;IACZ,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,gBAAgB,GACtE,OAAO,CAAC,qBAAqB,CAAC,CAoChC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,gBAAgB,GACtE,MAAM,CAUR;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,EAAE,CA0B1E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,gBAAgB,GACxE,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAa9B;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,UAAU,GAAG,MAAM,GAAG,YAAY,CAAC,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjH,MAAM,CAGR;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,GAAG,aAAa,CAoBvF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,UAAU,EAChB,iBAAiB,EAAE,MAAM,EACzB,UAAU,EAAE,MAAM,GACjB,aAAa,CAiBf"}
|
package/dist/remote-hosts.js
CHANGED
|
@@ -146,6 +146,14 @@ export function buildRemoteTmuxCheckCommand(host) {
|
|
|
146
146
|
* when tmux is missing or the host is unreachable. Never throws.
|
|
147
147
|
*/
|
|
148
148
|
export async function checkRemoteTmuxAvailable(host) {
|
|
149
|
+
// Under vitest, never open a real ssh connection — mirrors TmuxManager's
|
|
150
|
+
// no-op-shell-under-VITEST (IS_TEST_MODE). Without this, remote-case
|
|
151
|
+
// create-path tests hit a real ~10s ssh timeout. The command construction is
|
|
152
|
+
// covered by buildRemoteTmuxCheckCommand unit tests; only the live probe is
|
|
153
|
+
// short-circuited here.
|
|
154
|
+
if (process.env.VITEST) {
|
|
155
|
+
return { ok: true, tmuxPath: '(test-mode)' };
|
|
156
|
+
}
|
|
149
157
|
const command = buildRemoteTmuxCheckCommand(host);
|
|
150
158
|
try {
|
|
151
159
|
const { stdout } = await execAsync(command, { timeout: 15_000 });
|
|
@@ -174,6 +182,106 @@ export async function checkRemoteTmuxAvailable(host) {
|
|
|
174
182
|
};
|
|
175
183
|
}
|
|
176
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* COD-105 — build the SSH command that lists `codeman-*` tmux sessions on a
|
|
187
|
+
* remote host's canonical `-L codeman` socket.
|
|
188
|
+
*
|
|
189
|
+
* `list-sessions` exits NON-ZERO with empty output when no sessions exist (and
|
|
190
|
+
* the server isn't running), so `2>/dev/null` swallows tmux's "no server
|
|
191
|
+
* running" stderr; the caller treats a non-zero exit / empty output as "no
|
|
192
|
+
* sessions" rather than an error.
|
|
193
|
+
*
|
|
194
|
+
* COD-107 — connection options come from the shared `buildSshConnectionArgs`, so
|
|
195
|
+
* discovery connects with the SAME port/identity/proxy/jump-host as the launch
|
|
196
|
+
* and the tmux prereq probe.
|
|
197
|
+
*/
|
|
198
|
+
export function buildRemoteListSessionsCommand(host) {
|
|
199
|
+
const [ssh, ...connectionArgs] = buildSshConnectionArgs(host);
|
|
200
|
+
const parts = [ssh, connectionArgs[0], '-o ConnectTimeout=10', ...connectionArgs.slice(1)];
|
|
201
|
+
// The tmux list-sessions invocation is passed as ONE shell-quoted argument so
|
|
202
|
+
// the remote login shell runs it verbatim. The `-F` format uses literal `\t`
|
|
203
|
+
// separators (tmux expands them); `2>/dev/null` is inside the quoted command.
|
|
204
|
+
const remoteCmd = 'tmux -L codeman list-sessions -F "#{session_name}\\t#{session_attached}\\t#{session_created}\\t#{session_windows}" 2>/dev/null';
|
|
205
|
+
parts.push(remoteSshTarget(host), shellescape(remoteCmd));
|
|
206
|
+
return parts.join(' ');
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* COD-105 — pure parser for the `tmux list-sessions -F` output emitted by
|
|
210
|
+
* `buildRemoteListSessionsCommand`. Factored out so the parse is unit-testable
|
|
211
|
+
* without opening a real ssh connection.
|
|
212
|
+
*
|
|
213
|
+
* - Splits each non-empty line into [name, attached, created, windows] on the
|
|
214
|
+
* field separator. IMPORTANT: the remote tmux's `-F "…\t…"` format does NOT
|
|
215
|
+
* expand `\t` to a real tab — it emits the LITERAL two-character sequence
|
|
216
|
+
* `\t` (verified on aa-desktop / tmux next-3.7). So we split on the literal
|
|
217
|
+
* backslash-t sequence; we also tolerate a real tab in case a tmux build
|
|
218
|
+
* does expand it. (A real TAB is the regex `\t`; a literal backslash-t is the
|
|
219
|
+
* regex `\\t`.)
|
|
220
|
+
* - Keeps ONLY sessions whose name starts with `codeman-` (ignores foreign tmux
|
|
221
|
+
* sessions that happen to share the socket).
|
|
222
|
+
* - Coerces: `attached` → boolean (`'1'`), `created`/`windows` → finite ints.
|
|
223
|
+
* - Skips malformed lines (wrong column count or non-numeric created/windows)
|
|
224
|
+
* rather than emitting garbage.
|
|
225
|
+
*/
|
|
226
|
+
export function parseRemoteSessionList(stdout) {
|
|
227
|
+
const out = [];
|
|
228
|
+
for (const rawLine of stdout.split('\n')) {
|
|
229
|
+
const line = rawLine.trim();
|
|
230
|
+
if (!line)
|
|
231
|
+
continue;
|
|
232
|
+
// Split on a literal `\t` (backslash + t, what the remote tmux emits) OR a
|
|
233
|
+
// real tab character. `/\\t|\t/` = the two-char sequence, or a TAB.
|
|
234
|
+
const cols = line.split(/\\t|\t/);
|
|
235
|
+
if (cols.length !== 4)
|
|
236
|
+
continue;
|
|
237
|
+
const [name, attachedStr, createdStr, windowsStr] = cols;
|
|
238
|
+
if (!name.startsWith('codeman-'))
|
|
239
|
+
continue;
|
|
240
|
+
const created = Number(createdStr);
|
|
241
|
+
const windows = Number(windowsStr);
|
|
242
|
+
if (!Number.isFinite(created) || !Number.isFinite(windows))
|
|
243
|
+
continue;
|
|
244
|
+
// COD-106 — `session_attached` is the CLIENT COUNT (not a 0/1 flag); >1 = shared.
|
|
245
|
+
const attachedNum = Number(attachedStr.trim());
|
|
246
|
+
const attachedClients = Number.isFinite(attachedNum) ? Math.max(0, Math.trunc(attachedNum)) : 0;
|
|
247
|
+
out.push({
|
|
248
|
+
name,
|
|
249
|
+
attached: attachedClients > 0,
|
|
250
|
+
attachedClients,
|
|
251
|
+
created: Math.trunc(created),
|
|
252
|
+
windows: Math.trunc(windows),
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* COD-105 — discover `codeman-*` tmux sessions already running on a remote host
|
|
259
|
+
* (created by the remote's own Codeman, another instance, or this one), so the
|
|
260
|
+
* operator can attach to one this Codeman didn't launch.
|
|
261
|
+
*
|
|
262
|
+
* NEVER throws: returns `[]` on unreachable host / no tmux / no sessions
|
|
263
|
+
* (`list-sessions` exits non-zero with empty output when there are none).
|
|
264
|
+
*
|
|
265
|
+
* VITEST guard — like `checkRemoteTmuxAvailable`, returns `[]` under test so a
|
|
266
|
+
* real ssh never runs in a request path (which would make route tests hit a
|
|
267
|
+
* ~10s timeout). The command construction is covered by
|
|
268
|
+
* `buildRemoteListSessionsCommand` and the parse by `parseRemoteSessionList`.
|
|
269
|
+
*/
|
|
270
|
+
export async function listRemoteCodemanSessions(remote) {
|
|
271
|
+
if (process.env.VITEST) {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
const command = buildRemoteListSessionsCommand(remote);
|
|
275
|
+
try {
|
|
276
|
+
const { stdout } = await execAsync(command, { timeout: 15_000 });
|
|
277
|
+
return parseRemoteSessionList(stdout);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
// Unreachable host, no tmux server, or no sessions (non-zero exit). All map
|
|
281
|
+
// to "nothing to attach to" — never surface as an error to the caller.
|
|
282
|
+
return [];
|
|
283
|
+
}
|
|
284
|
+
}
|
|
177
285
|
export function remoteDisplayPath(remote) {
|
|
178
286
|
const path = 'remotePath' in remote ? remote.remotePath : remote.path;
|
|
179
287
|
return `${remote.username}@${remote.host}:${path}`;
|
|
@@ -187,6 +295,10 @@ export function toSessionRemote(host, remoteCase) {
|
|
|
187
295
|
port: host.port,
|
|
188
296
|
remotePath: remoteCase.remotePath,
|
|
189
297
|
commands: host.commands,
|
|
298
|
+
// COD-105 — the COD-104 launch path creates the remote session, so we own it
|
|
299
|
+
// (an explicit kill may propagate a remote kill-session). Discovered+attached
|
|
300
|
+
// sessions go through `toAttachedSessionRemote` with `owned: false`.
|
|
301
|
+
owned: true,
|
|
190
302
|
// COD-107 — carry the advanced SSH options from host config into the session
|
|
191
303
|
// so the launch/prereq commands connect the same way the operator configured.
|
|
192
304
|
identityFile: host.identityFile,
|
|
@@ -195,4 +307,34 @@ export function toSessionRemote(host, remoteCase) {
|
|
|
195
307
|
extraSshOptions: host.extraSshOptions,
|
|
196
308
|
};
|
|
197
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* COD-105 — build a NON-owned `SessionRemote` for ATTACHING to a `codeman-*`
|
|
312
|
+
* session already running on a remote host (discovered via
|
|
313
|
+
* `listRemoteCodemanSessions`). The resulting session's pane runs
|
|
314
|
+
* `tmux -L codeman attach -t <remoteSessionName>` (see
|
|
315
|
+
* `buildRemoteAttachCommand`), and because we did NOT create the remote session,
|
|
316
|
+
* `owned: false` means closing the tab DETACHES rather than killing it.
|
|
317
|
+
*
|
|
318
|
+
* `remotePath` is informational here (the attached remote session keeps its own
|
|
319
|
+
* cwd); we record the host's nominal path so display helpers still show
|
|
320
|
+
* `user@host:path`.
|
|
321
|
+
*/
|
|
322
|
+
export function toAttachedSessionRemote(host, remoteSessionName, remotePath) {
|
|
323
|
+
return {
|
|
324
|
+
hostId: host.id,
|
|
325
|
+
label: host.label,
|
|
326
|
+
host: host.host,
|
|
327
|
+
username: host.username,
|
|
328
|
+
port: host.port,
|
|
329
|
+
remotePath,
|
|
330
|
+
commands: host.commands,
|
|
331
|
+
// Discovered + attached — another Codeman created it. Detach-not-kill.
|
|
332
|
+
owned: false,
|
|
333
|
+
remoteSessionName,
|
|
334
|
+
identityFile: host.identityFile,
|
|
335
|
+
socksProxy: host.socksProxy,
|
|
336
|
+
jumpHost: host.jumpHost,
|
|
337
|
+
extraSshOptions: host.extraSshOptions,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
198
340
|
//# sourceMappingURL=remote-hosts.js.map
|
package/dist/remote-hosts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-hosts.js","sourceRoot":"","sources":["../src/remote-hosts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"remote-hosts.js","sourceRoot":"","sources":["../src/remote-hosts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pure logic for the remote-session auto-reconnect watcher (COD-108).
|
|
3
|
+
*
|
|
4
|
+
* COD-104 made remote tmux sessions durable + idempotently reattachable, but a
|
|
5
|
+
* reconnect only fired at explicit trigger points. COD-108 adds a continuous
|
|
6
|
+
* watcher (in `TmuxManager`) that detects a dead remote pane and emits
|
|
7
|
+
* `remoteSessionDropped`; `SessionManager`/server then reassembles the respawn
|
|
8
|
+
* options and reattaches (re-running the idempotent remote command).
|
|
9
|
+
*
|
|
10
|
+
* This module holds the SIDE-EFFECT-FREE pieces so they can be unit-tested
|
|
11
|
+
* without real tmux:
|
|
12
|
+
* - the bounded exponential **backoff schedule** (attempt → delay, capped),
|
|
13
|
+
* - the per-session **reconnect state** shape,
|
|
14
|
+
* - the **eligibility decision** (`decideReconnect`) given a session + its
|
|
15
|
+
* reconnect state + the current time + the guard set.
|
|
16
|
+
*
|
|
17
|
+
* The watcher in `tmux-manager.ts` owns the live `isPaneDead` probe and the
|
|
18
|
+
* timers; everything here is pure and deterministic (time is injected).
|
|
19
|
+
*
|
|
20
|
+
* @module remote-reconnect
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Bounded exponential backoff delays (ms) between reconnect attempts.
|
|
24
|
+
* Attempt N (1-based) waits `BACKOFF_SCHEDULE_MS[N-1]` from the previous emit
|
|
25
|
+
* before the next emit is eligible. After the last entry the session is
|
|
26
|
+
* considered `reconnect-exhausted` and the watcher stops emitting for it.
|
|
27
|
+
*
|
|
28
|
+
* 5s, 15s, 45s, 2m, 5m, 5m → ~6 attempts spanning ~13 minutes.
|
|
29
|
+
*/
|
|
30
|
+
export declare const BACKOFF_SCHEDULE_MS: readonly number[];
|
|
31
|
+
/** Maximum number of reconnect attempts before exhaustion. */
|
|
32
|
+
export declare const MAX_RECONNECT_ATTEMPTS: number;
|
|
33
|
+
/**
|
|
34
|
+
* Delay (ms) to wait AFTER emitting attempt `attempt` (1-based) before the next
|
|
35
|
+
* attempt is eligible. `attempt <= 0` returns the first delay; an attempt at or
|
|
36
|
+
* beyond the cap returns the last delay (callers should check exhaustion via
|
|
37
|
+
* {@link isExhausted} rather than relying on this for the stop decision).
|
|
38
|
+
*
|
|
39
|
+
* Pure — no clock, no I/O.
|
|
40
|
+
*/
|
|
41
|
+
export declare function reconnectDelayForAttempt(attempt: number): number;
|
|
42
|
+
/** Whether `attempts` reconnect emits have reached/exceeded the cap. Pure. */
|
|
43
|
+
export declare function isExhausted(attempts: number): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Per-session reconnect bookkeeping held by the watcher. All time values are
|
|
46
|
+
* epoch ms. `inFlight` guards against stacking respawns when a tick fires while
|
|
47
|
+
* a previous reattach is still running. `exhaustedEmitted` ensures the
|
|
48
|
+
* `remoteReconnectExhausted` event fires at most once per session.
|
|
49
|
+
*/
|
|
50
|
+
export interface RemoteReconnectState {
|
|
51
|
+
/** Number of `remoteSessionDropped` emits so far (advances per emit). */
|
|
52
|
+
attempts: number;
|
|
53
|
+
/** Earliest time (epoch ms) the next emit is eligible. 0 = eligible now. */
|
|
54
|
+
nextEligibleAt: number;
|
|
55
|
+
/** A reattach triggered by a prior emit is currently running. */
|
|
56
|
+
inFlight: boolean;
|
|
57
|
+
/** Cap reached — stop auto-retrying for this session. */
|
|
58
|
+
exhausted: boolean;
|
|
59
|
+
/** The `remoteReconnectExhausted` SSE event has already been emitted. */
|
|
60
|
+
exhaustedEmitted: boolean;
|
|
61
|
+
}
|
|
62
|
+
/** A fresh reconnect state (no attempts, immediately eligible). Pure. */
|
|
63
|
+
export declare function freshReconnectState(): RemoteReconnectState;
|
|
64
|
+
/**
|
|
65
|
+
* Advance the backoff after an emit at time `now`. Increments `attempts` and
|
|
66
|
+
* schedules `nextEligibleAt = now + delay`. Returns a NEW state object (does
|
|
67
|
+
* not mutate the input). Pure.
|
|
68
|
+
*
|
|
69
|
+
* NOTE: this does NOT set `exhausted`. Exhaustion is a decision the watcher
|
|
70
|
+
* makes on the FOLLOWING tick (via {@link decideReconnect} → `exhaust`), so the
|
|
71
|
+
* `remoteReconnectExhausted` event fires exactly once after the final attempt's
|
|
72
|
+
* backoff window elapses — not pre-emptively on the last emit.
|
|
73
|
+
*/
|
|
74
|
+
export declare function advanceBackoff(state: RemoteReconnectState, now: number): RemoteReconnectState;
|
|
75
|
+
/** Reset after a successful reattach — back to a fresh, eligible state. Pure. */
|
|
76
|
+
export declare function resetReconnectState(): RemoteReconnectState;
|
|
77
|
+
/** Minimal session view the decision needs (avoids importing MuxSession here). */
|
|
78
|
+
export interface ReconnectSessionView {
|
|
79
|
+
sessionId: string;
|
|
80
|
+
/** Truthy when this is a remote (SSH-wrapped) session. */
|
|
81
|
+
isRemote: boolean;
|
|
82
|
+
/** Result of `isPaneDead(muxName)` for this session. */
|
|
83
|
+
paneDead: boolean;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Decision outcomes for a single watcher tick on one session.
|
|
87
|
+
* - `emit` → emit `remoteSessionDropped { sessionId, attempt }`, then
|
|
88
|
+
* advance backoff (attempt = the returned `attempt`).
|
|
89
|
+
* - `exhaust` → cap reached this tick; emit `remoteReconnectExhausted` once.
|
|
90
|
+
* - `skip` → do nothing (not remote / pane alive / guarded / in-flight /
|
|
91
|
+
* not yet due / already exhausted).
|
|
92
|
+
*/
|
|
93
|
+
export type ReconnectAction = {
|
|
94
|
+
kind: 'emit';
|
|
95
|
+
attempt: number;
|
|
96
|
+
} | {
|
|
97
|
+
kind: 'exhaust';
|
|
98
|
+
} | {
|
|
99
|
+
kind: 'skip';
|
|
100
|
+
reason: ReconnectSkipReason;
|
|
101
|
+
};
|
|
102
|
+
export type ReconnectSkipReason = 'not-remote' | 'pane-alive' | 'guarded' | 'in-flight' | 'not-due' | 'exhausted' | 'disabled';
|
|
103
|
+
export interface DecideReconnectInput {
|
|
104
|
+
session: ReconnectSessionView;
|
|
105
|
+
state: RemoteReconnectState | undefined;
|
|
106
|
+
/** Session is in the intentional-teardown guard set (killed/detached/stopping). */
|
|
107
|
+
guarded: boolean;
|
|
108
|
+
/** Kill-switch: `remoteAutoReconnect` setting. When false, never reconnect. */
|
|
109
|
+
enabled: boolean;
|
|
110
|
+
now: number;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* PURE eligibility decision for one session on one tick. No clock, no I/O — all
|
|
114
|
+
* inputs are passed in. The watcher translates the result into emits + state
|
|
115
|
+
* transitions.
|
|
116
|
+
*
|
|
117
|
+
* Order of guards (most-decisive first):
|
|
118
|
+
* 1. kill-switch off → skip:disabled
|
|
119
|
+
* 2. not a remote session → skip:not-remote
|
|
120
|
+
* 3. pane is alive → skip:pane-alive
|
|
121
|
+
* 4. intentional teardown guard → skip:guarded (NEVER revive a killed tab)
|
|
122
|
+
* 5. a reattach already running → skip:in-flight (no stacked respawns)
|
|
123
|
+
* 6. already exhausted → skip:exhausted (one exhaust emit, then quiet)
|
|
124
|
+
* 7. cap reached this tick → exhaust
|
|
125
|
+
* 8. not yet due (backoff) → skip:not-due
|
|
126
|
+
* 9. otherwise → emit (attempt = attempts + 1)
|
|
127
|
+
*/
|
|
128
|
+
export declare function decideReconnect(input: DecideReconnectInput): ReconnectAction;
|
|
129
|
+
//# sourceMappingURL=remote-reconnect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-reconnect.d.ts","sourceRoot":"","sources":["../src/remote-reconnect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAAuD,CAAC;AAEzG,8DAA8D;AAC9D,eAAO,MAAM,sBAAsB,QAA6B,CAAC;AAEjE;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIhE;AAED,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,cAAc,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAC;IAClB,yDAAyD;IACzD,SAAS,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,yEAAyE;AACzE,wBAAgB,mBAAmB,IAAI,oBAAoB,CAE1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,oBAAoB,EAAE,GAAG,EAAE,MAAM,GAAG,oBAAoB,CAQ7F;AAED,iFAAiF;AACjF,wBAAgB,mBAAmB,IAAI,oBAAoB,CAE1D;AAED,kFAAkF;AAClF,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,QAAQ,EAAE,OAAO,CAAC;IAClB,wDAAwD;IACxD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,mBAAmB,GAC3B,YAAY,GACZ,YAAY,GACZ,SAAS,GACT,WAAW,GACX,SAAS,GACT,WAAW,GACX,UAAU,CAAC;AAEf,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,oBAAoB,CAAC;IAC9B,KAAK,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACxC,mFAAmF;IACnF,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,eAAe,CAuB5E"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pure logic for the remote-session auto-reconnect watcher (COD-108).
|
|
3
|
+
*
|
|
4
|
+
* COD-104 made remote tmux sessions durable + idempotently reattachable, but a
|
|
5
|
+
* reconnect only fired at explicit trigger points. COD-108 adds a continuous
|
|
6
|
+
* watcher (in `TmuxManager`) that detects a dead remote pane and emits
|
|
7
|
+
* `remoteSessionDropped`; `SessionManager`/server then reassembles the respawn
|
|
8
|
+
* options and reattaches (re-running the idempotent remote command).
|
|
9
|
+
*
|
|
10
|
+
* This module holds the SIDE-EFFECT-FREE pieces so they can be unit-tested
|
|
11
|
+
* without real tmux:
|
|
12
|
+
* - the bounded exponential **backoff schedule** (attempt → delay, capped),
|
|
13
|
+
* - the per-session **reconnect state** shape,
|
|
14
|
+
* - the **eligibility decision** (`decideReconnect`) given a session + its
|
|
15
|
+
* reconnect state + the current time + the guard set.
|
|
16
|
+
*
|
|
17
|
+
* The watcher in `tmux-manager.ts` owns the live `isPaneDead` probe and the
|
|
18
|
+
* timers; everything here is pure and deterministic (time is injected).
|
|
19
|
+
*
|
|
20
|
+
* @module remote-reconnect
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Bounded exponential backoff delays (ms) between reconnect attempts.
|
|
24
|
+
* Attempt N (1-based) waits `BACKOFF_SCHEDULE_MS[N-1]` from the previous emit
|
|
25
|
+
* before the next emit is eligible. After the last entry the session is
|
|
26
|
+
* considered `reconnect-exhausted` and the watcher stops emitting for it.
|
|
27
|
+
*
|
|
28
|
+
* 5s, 15s, 45s, 2m, 5m, 5m → ~6 attempts spanning ~13 minutes.
|
|
29
|
+
*/
|
|
30
|
+
export const BACKOFF_SCHEDULE_MS = [5_000, 15_000, 45_000, 120_000, 300_000, 300_000];
|
|
31
|
+
/** Maximum number of reconnect attempts before exhaustion. */
|
|
32
|
+
export const MAX_RECONNECT_ATTEMPTS = BACKOFF_SCHEDULE_MS.length;
|
|
33
|
+
/**
|
|
34
|
+
* Delay (ms) to wait AFTER emitting attempt `attempt` (1-based) before the next
|
|
35
|
+
* attempt is eligible. `attempt <= 0` returns the first delay; an attempt at or
|
|
36
|
+
* beyond the cap returns the last delay (callers should check exhaustion via
|
|
37
|
+
* {@link isExhausted} rather than relying on this for the stop decision).
|
|
38
|
+
*
|
|
39
|
+
* Pure — no clock, no I/O.
|
|
40
|
+
*/
|
|
41
|
+
export function reconnectDelayForAttempt(attempt) {
|
|
42
|
+
if (!Number.isFinite(attempt) || attempt <= 1)
|
|
43
|
+
return BACKOFF_SCHEDULE_MS[0];
|
|
44
|
+
const idx = Math.min(Math.floor(attempt) - 1, BACKOFF_SCHEDULE_MS.length - 1);
|
|
45
|
+
return BACKOFF_SCHEDULE_MS[idx];
|
|
46
|
+
}
|
|
47
|
+
/** Whether `attempts` reconnect emits have reached/exceeded the cap. Pure. */
|
|
48
|
+
export function isExhausted(attempts) {
|
|
49
|
+
return attempts >= MAX_RECONNECT_ATTEMPTS;
|
|
50
|
+
}
|
|
51
|
+
/** A fresh reconnect state (no attempts, immediately eligible). Pure. */
|
|
52
|
+
export function freshReconnectState() {
|
|
53
|
+
return { attempts: 0, nextEligibleAt: 0, inFlight: false, exhausted: false, exhaustedEmitted: false };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Advance the backoff after an emit at time `now`. Increments `attempts` and
|
|
57
|
+
* schedules `nextEligibleAt = now + delay`. Returns a NEW state object (does
|
|
58
|
+
* not mutate the input). Pure.
|
|
59
|
+
*
|
|
60
|
+
* NOTE: this does NOT set `exhausted`. Exhaustion is a decision the watcher
|
|
61
|
+
* makes on the FOLLOWING tick (via {@link decideReconnect} → `exhaust`), so the
|
|
62
|
+
* `remoteReconnectExhausted` event fires exactly once after the final attempt's
|
|
63
|
+
* backoff window elapses — not pre-emptively on the last emit.
|
|
64
|
+
*/
|
|
65
|
+
export function advanceBackoff(state, now) {
|
|
66
|
+
const attempts = state.attempts + 1;
|
|
67
|
+
const delay = reconnectDelayForAttempt(attempts);
|
|
68
|
+
return {
|
|
69
|
+
...state,
|
|
70
|
+
attempts,
|
|
71
|
+
nextEligibleAt: now + delay,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Reset after a successful reattach — back to a fresh, eligible state. Pure. */
|
|
75
|
+
export function resetReconnectState() {
|
|
76
|
+
return freshReconnectState();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* PURE eligibility decision for one session on one tick. No clock, no I/O — all
|
|
80
|
+
* inputs are passed in. The watcher translates the result into emits + state
|
|
81
|
+
* transitions.
|
|
82
|
+
*
|
|
83
|
+
* Order of guards (most-decisive first):
|
|
84
|
+
* 1. kill-switch off → skip:disabled
|
|
85
|
+
* 2. not a remote session → skip:not-remote
|
|
86
|
+
* 3. pane is alive → skip:pane-alive
|
|
87
|
+
* 4. intentional teardown guard → skip:guarded (NEVER revive a killed tab)
|
|
88
|
+
* 5. a reattach already running → skip:in-flight (no stacked respawns)
|
|
89
|
+
* 6. already exhausted → skip:exhausted (one exhaust emit, then quiet)
|
|
90
|
+
* 7. cap reached this tick → exhaust
|
|
91
|
+
* 8. not yet due (backoff) → skip:not-due
|
|
92
|
+
* 9. otherwise → emit (attempt = attempts + 1)
|
|
93
|
+
*/
|
|
94
|
+
export function decideReconnect(input) {
|
|
95
|
+
const { session, state, guarded, enabled, now } = input;
|
|
96
|
+
if (!enabled)
|
|
97
|
+
return { kind: 'skip', reason: 'disabled' };
|
|
98
|
+
if (!session.isRemote)
|
|
99
|
+
return { kind: 'skip', reason: 'not-remote' };
|
|
100
|
+
if (!session.paneDead)
|
|
101
|
+
return { kind: 'skip', reason: 'pane-alive' };
|
|
102
|
+
// Intentional kill / detach must NEVER be auto-revived.
|
|
103
|
+
if (guarded)
|
|
104
|
+
return { kind: 'skip', reason: 'guarded' };
|
|
105
|
+
const s = state ?? freshReconnectState();
|
|
106
|
+
// Only one reconnect in flight per session — don't stack respawns.
|
|
107
|
+
if (s.inFlight)
|
|
108
|
+
return { kind: 'skip', reason: 'in-flight' };
|
|
109
|
+
if (s.exhausted)
|
|
110
|
+
return { kind: 'skip', reason: 'exhausted' };
|
|
111
|
+
// Cap reached: surface exhaustion once, then go quiet.
|
|
112
|
+
if (isExhausted(s.attempts))
|
|
113
|
+
return { kind: 'exhaust' };
|
|
114
|
+
// Backoff gate — only emit when due.
|
|
115
|
+
if (now < s.nextEligibleAt)
|
|
116
|
+
return { kind: 'skip', reason: 'not-due' };
|
|
117
|
+
return { kind: 'emit', attempt: s.attempts + 1 };
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=remote-reconnect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-reconnect.js","sourceRoot":"","sources":["../src/remote-reconnect.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,9 +27,15 @@ export type UnifiedSessionItem = {
|
|
|
27
27
|
lastActivityAt?: number;
|
|
28
28
|
claudeSessionId?: string;
|
|
29
29
|
firstPrompt?: string;
|
|
30
|
+
/** Most recent user prompt from the transcript (COD-145), parallel to firstPrompt. */
|
|
31
|
+
lastPrompt?: string;
|
|
30
32
|
sizeBytes?: number;
|
|
31
33
|
projectKey?: string;
|
|
32
34
|
remote?: boolean;
|
|
35
|
+
/** Pinned to the top of the session manager list (COD-139). */
|
|
36
|
+
pinned?: boolean;
|
|
37
|
+
/** When the session was pinned (epoch ms) — orders the pinned group desc. */
|
|
38
|
+
pinnedAt?: number;
|
|
33
39
|
sources: string[];
|
|
34
40
|
stats?: {
|
|
35
41
|
memoryMB: number;
|
|
@@ -47,6 +53,8 @@ export type LiveSessionInput = {
|
|
|
47
53
|
createdAt?: number;
|
|
48
54
|
lastActivityAt?: number;
|
|
49
55
|
claudeSessionId?: string;
|
|
56
|
+
pinned?: boolean;
|
|
57
|
+
pinnedAt?: number;
|
|
50
58
|
};
|
|
51
59
|
/** Persisted session view (subset of `SessionState`). */
|
|
52
60
|
export type PersistedSessionInput = {
|
|
@@ -59,6 +67,8 @@ export type PersistedSessionInput = {
|
|
|
59
67
|
lastActivityAt?: number;
|
|
60
68
|
/** Claude conversation ID this session resumes (`SessionState.resumeSessionId`). */
|
|
61
69
|
claudeSessionId?: string;
|
|
70
|
+
pinned?: boolean;
|
|
71
|
+
pinnedAt?: number;
|
|
62
72
|
};
|
|
63
73
|
/** Lifecycle audit-log view. Entries are expected NEWEST-first (the order `SessionLifecycleLog.query()` returns). */
|
|
64
74
|
export type LifecycleInput = {
|
|
@@ -75,6 +85,8 @@ export type HistoryInput = {
|
|
|
75
85
|
sizeBytes: number;
|
|
76
86
|
lastModified: string;
|
|
77
87
|
firstPrompt?: string;
|
|
88
|
+
/** Most recent user prompt from the transcript (COD-145). */
|
|
89
|
+
lastPrompt?: string;
|
|
78
90
|
projectKey?: string;
|
|
79
91
|
};
|
|
80
92
|
/** Mux process-stat view. */
|
|
@@ -101,7 +113,7 @@ export type UnifiedSources = {
|
|
|
101
113
|
*/
|
|
102
114
|
export declare function mergeUnifiedSessions(sources: UnifiedSources): UnifiedSessionItem[];
|
|
103
115
|
/**
|
|
104
|
-
* Case-insensitive substring filter (name + firstPrompt + workingDir + sessionId)
|
|
116
|
+
* Case-insensitive substring filter (name + firstPrompt + lastPrompt + workingDir + sessionId)
|
|
105
117
|
* with offset/limit paging. `total` is the filtered count BEFORE paging.
|
|
106
118
|
*/
|
|
107
119
|
export declare function filterAndPaginate(items: UnifiedSessionItem[], opts: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unified-session-service.d.ts","sourceRoot":"","sources":["../../src/services/unified-session-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;CAClD,CAAC;AAEF,mEAAmE;AACnE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"unified-session-service.d.ts","sourceRoot":"","sources":["../../src/services/unified-session-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;CAClD,CAAC;AAEF,mEAAmE;AACnE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,yDAAyD;AACzD,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oFAAoF;IACpF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,qHAAqH;AACrH,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,0DAA0D;AAC1D,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,6BAA6B;AAC7B,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC1B,SAAS,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACpC,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;IAC7B,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,GAAG,CAAC,EAAE,YAAY,EAAE,CAAC;CACtB,CAAC;AA0BF;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,cAAc,GAAG,kBAAkB,EAAE,CAyLlF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,kBAAkB,EAAE,EAC3B,IAAI,EAAE;IAAE,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAiBnD"}
|