aicodeman 1.5.1 → 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.
Files changed (122) hide show
  1. package/dist/remote-hosts.d.ts +61 -1
  2. package/dist/remote-hosts.d.ts.map +1 -1
  3. package/dist/remote-hosts.js +142 -0
  4. package/dist/remote-hosts.js.map +1 -1
  5. package/dist/remote-reconnect.d.ts +129 -0
  6. package/dist/remote-reconnect.d.ts.map +1 -0
  7. package/dist/remote-reconnect.js +119 -0
  8. package/dist/remote-reconnect.js.map +1 -0
  9. package/dist/services/unified-session-service.d.ts +13 -1
  10. package/dist/services/unified-session-service.d.ts.map +1 -1
  11. package/dist/services/unified-session-service.js +88 -3
  12. package/dist/services/unified-session-service.js.map +1 -1
  13. package/dist/session-order.d.ts +40 -0
  14. package/dist/session-order.d.ts.map +1 -0
  15. package/dist/session-order.js +67 -0
  16. package/dist/session-order.js.map +1 -0
  17. package/dist/session.d.ts +39 -0
  18. package/dist/session.d.ts.map +1 -1
  19. package/dist/session.js +87 -19
  20. package/dist/session.js.map +1 -1
  21. package/dist/state-store.d.ts +12 -0
  22. package/dist/state-store.d.ts.map +1 -1
  23. package/dist/state-store.js +33 -0
  24. package/dist/state-store.js.map +1 -1
  25. package/dist/tmux-manager.d.ts +66 -0
  26. package/dist/tmux-manager.d.ts.map +1 -1
  27. package/dist/tmux-manager.js +226 -2
  28. package/dist/tmux-manager.js.map +1 -1
  29. package/dist/types/app-state.d.ts +2 -0
  30. package/dist/types/app-state.d.ts.map +1 -1
  31. package/dist/types/app-state.js.map +1 -1
  32. package/dist/types/session.d.ts +42 -0
  33. package/dist/types/session.d.ts.map +1 -1
  34. package/dist/types/session.js.map +1 -1
  35. package/dist/web/public/admin-ui.js.gz +0 -0
  36. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  37. package/dist/web/public/app.b78b0465.js +38 -0
  38. package/dist/web/public/app.b78b0465.js.br +0 -0
  39. package/dist/web/public/app.b78b0465.js.gz +0 -0
  40. package/dist/web/public/{constants.ad3a8031.js → constants.21a0a83e.js} +9 -0
  41. package/dist/web/public/constants.21a0a83e.js.br +0 -0
  42. package/dist/web/public/{constants.ad3a8031.js.gz → constants.21a0a83e.js.gz} +0 -0
  43. package/dist/web/public/cron-ui.js.gz +0 -0
  44. package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
  45. package/dist/web/public/index.html +29 -10
  46. package/dist/web/public/index.html.br +0 -0
  47. package/dist/web/public/index.html.gz +0 -0
  48. package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
  49. package/dist/web/public/keyboard-accessory.3f91acef.js.gz +0 -0
  50. package/dist/web/public/mobile-handlers.b5a667ef.js.gz +0 -0
  51. package/dist/web/public/mobile.597136a6.css.gz +0 -0
  52. package/dist/web/public/notification-manager.ca9dc40d.js.gz +0 -0
  53. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  54. package/dist/web/public/{panels-ui.3c9b0465.js → panels-ui.58582d4c.js} +2 -2
  55. package/dist/web/public/panels-ui.58582d4c.js.br +0 -0
  56. package/dist/web/public/panels-ui.58582d4c.js.gz +0 -0
  57. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  58. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  59. package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
  60. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  61. package/dist/web/public/{session-ui.e5d4f9b5.js → session-ui.0cf86dd4.js} +30 -22
  62. package/dist/web/public/session-ui.0cf86dd4.js.br +0 -0
  63. package/dist/web/public/session-ui.0cf86dd4.js.gz +0 -0
  64. package/dist/web/public/{settings-ui.29214cf1.js → settings-ui.bc5f4792.js} +2 -2
  65. package/dist/web/public/settings-ui.bc5f4792.js.br +0 -0
  66. package/dist/web/public/settings-ui.bc5f4792.js.gz +0 -0
  67. package/dist/web/public/styles.7295e6f4.css +1 -0
  68. package/dist/web/public/styles.7295e6f4.css.br +0 -0
  69. package/dist/web/public/styles.7295e6f4.css.gz +0 -0
  70. package/dist/web/public/subagent-windows.69b60c35.js.gz +0 -0
  71. package/dist/web/public/sw.js.gz +0 -0
  72. package/dist/web/public/terminal-ui.9ea677e9.js +3 -0
  73. package/dist/web/public/terminal-ui.9ea677e9.js.br +0 -0
  74. package/dist/web/public/terminal-ui.9ea677e9.js.gz +0 -0
  75. package/dist/web/public/ultracode-panel.js.gz +0 -0
  76. package/dist/web/public/ultracode-windows.js.gz +0 -0
  77. package/dist/web/public/upload.html.gz +0 -0
  78. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  79. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  80. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  81. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  82. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  83. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  84. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  85. package/dist/web/public/vendor/xterm.css.gz +0 -0
  86. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  87. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  88. package/dist/web/routes/case-routes.d.ts.map +1 -1
  89. package/dist/web/routes/case-routes.js +19 -1
  90. package/dist/web/routes/case-routes.js.map +1 -1
  91. package/dist/web/routes/session-routes.d.ts.map +1 -1
  92. package/dist/web/routes/session-routes.js +140 -3
  93. package/dist/web/routes/session-routes.js.map +1 -1
  94. package/dist/web/schemas.d.ts +13 -0
  95. package/dist/web/schemas.d.ts.map +1 -1
  96. package/dist/web/schemas.js +31 -0
  97. package/dist/web/schemas.js.map +1 -1
  98. package/dist/web/server.d.ts +9 -0
  99. package/dist/web/server.d.ts.map +1 -1
  100. package/dist/web/server.js +57 -1
  101. package/dist/web/server.js.map +1 -1
  102. package/dist/web/sse-events.d.ts +15 -0
  103. package/dist/web/sse-events.d.ts.map +1 -1
  104. package/dist/web/sse-events.js +18 -0
  105. package/dist/web/sse-events.js.map +1 -1
  106. package/package.json +1 -1
  107. package/dist/web/public/app.1ccca158.js +0 -38
  108. package/dist/web/public/app.1ccca158.js.br +0 -0
  109. package/dist/web/public/app.1ccca158.js.gz +0 -0
  110. package/dist/web/public/constants.ad3a8031.js.br +0 -0
  111. package/dist/web/public/panels-ui.3c9b0465.js.br +0 -0
  112. package/dist/web/public/panels-ui.3c9b0465.js.gz +0 -0
  113. package/dist/web/public/session-ui.e5d4f9b5.js.br +0 -0
  114. package/dist/web/public/session-ui.e5d4f9b5.js.gz +0 -0
  115. package/dist/web/public/settings-ui.29214cf1.js.br +0 -0
  116. package/dist/web/public/settings-ui.29214cf1.js.gz +0 -0
  117. package/dist/web/public/styles.9e6cdb9d.css +0 -1
  118. package/dist/web/public/styles.9e6cdb9d.css.br +0 -0
  119. package/dist/web/public/styles.9e6cdb9d.css.gz +0 -0
  120. package/dist/web/public/terminal-ui.efda6083.js +0 -3
  121. package/dist/web/public/terminal-ui.efda6083.js.br +0 -0
  122. package/dist/web/public/terminal-ui.efda6083.js.gz +0 -0
@@ -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,CA4BhC;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,CAgBvF"}
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"}
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"remote-hosts.js","sourceRoot":"","sources":["../src/remote-hosts.ts"],"names":[],"mappings":"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"}
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":"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"}
1
+ {"version":3,"file":"unified-session-service.d.ts","sourceRoot":"","sources":["../../src/services/unified-session-service.ts"],"names":[],"mappings":"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"}