aicodeman 0.9.7 → 0.9.8

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 (168) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +11 -3
  3. package/dist/file-stream-manager.d.ts.map +1 -1
  4. package/dist/file-stream-manager.js +6 -2
  5. package/dist/file-stream-manager.js.map +1 -1
  6. package/dist/mux-interface.d.ts +8 -0
  7. package/dist/mux-interface.d.ts.map +1 -1
  8. package/dist/session.d.ts +29 -1
  9. package/dist/session.d.ts.map +1 -1
  10. package/dist/session.js +48 -2
  11. package/dist/session.js.map +1 -1
  12. package/dist/tmux-manager.d.ts +30 -2
  13. package/dist/tmux-manager.d.ts.map +1 -1
  14. package/dist/tmux-manager.js +372 -14
  15. package/dist/tmux-manager.js.map +1 -1
  16. package/dist/types/api.d.ts +10 -17
  17. package/dist/types/api.d.ts.map +1 -1
  18. package/dist/types/api.js +32 -3
  19. package/dist/types/api.js.map +1 -1
  20. package/dist/utils/index.d.ts +1 -0
  21. package/dist/utils/index.d.ts.map +1 -1
  22. package/dist/utils/index.js +1 -0
  23. package/dist/utils/index.js.map +1 -1
  24. package/dist/utils/push-endpoint-validation.d.ts +6 -0
  25. package/dist/utils/push-endpoint-validation.d.ts.map +1 -0
  26. package/dist/utils/push-endpoint-validation.js +80 -0
  27. package/dist/utils/push-endpoint-validation.js.map +1 -0
  28. package/dist/web/public/{api-client.3adebdc2.js → api-client.c9b1cddc.js} +10 -1
  29. package/dist/web/public/api-client.c9b1cddc.js.br +0 -0
  30. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  31. package/dist/web/public/app.a2f053a8.js +35 -0
  32. package/dist/web/public/app.a2f053a8.js.br +0 -0
  33. package/dist/web/public/app.a2f053a8.js.gz +0 -0
  34. package/dist/web/public/{constants.5b68d2de.js → constants.74211deb.js} +1 -0
  35. package/dist/web/public/constants.74211deb.js.br +0 -0
  36. package/dist/web/public/constants.74211deb.js.gz +0 -0
  37. package/dist/web/public/{image-input.7cade6a8.js → image-input.0ea86695.js} +1 -1
  38. package/dist/web/public/{image-input.7cade6a8.js.br → image-input.0ea86695.js.br} +0 -0
  39. package/dist/web/public/image-input.0ea86695.js.gz +0 -0
  40. package/dist/web/public/index.html +23 -19
  41. package/dist/web/public/index.html.br +0 -0
  42. package/dist/web/public/index.html.gz +0 -0
  43. package/dist/web/public/input-cjk.b8686b5e.js +1 -0
  44. package/dist/web/public/input-cjk.b8686b5e.js.br +0 -0
  45. package/dist/web/public/input-cjk.b8686b5e.js.gz +0 -0
  46. package/dist/web/public/{keyboard-accessory.cdfd8c04.js → keyboard-accessory.bc753cc7.js} +3 -2
  47. package/dist/web/public/keyboard-accessory.bc753cc7.js.br +0 -0
  48. package/dist/web/public/keyboard-accessory.bc753cc7.js.gz +0 -0
  49. package/dist/web/public/{mobile-handlers.1e2a8ef8.js → mobile-handlers.d54d97d6.js} +26 -10
  50. package/dist/web/public/mobile-handlers.d54d97d6.js.br +0 -0
  51. package/dist/web/public/mobile-handlers.d54d97d6.js.gz +0 -0
  52. package/dist/web/public/mobile.959f6fe2.css +1 -0
  53. package/dist/web/public/mobile.959f6fe2.css.br +0 -0
  54. package/dist/web/public/mobile.959f6fe2.css.gz +0 -0
  55. package/dist/web/public/notification-manager.9c984ac2.js.gz +0 -0
  56. package/dist/web/public/orchestrator-panel.js +3 -3
  57. package/dist/web/public/orchestrator-panel.js.br +0 -0
  58. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  59. package/dist/web/public/{panels-ui.5192a2c0.js → panels-ui.6bb3169f.js} +4 -4
  60. package/dist/web/public/panels-ui.6bb3169f.js.br +0 -0
  61. package/dist/web/public/panels-ui.6bb3169f.js.gz +0 -0
  62. package/dist/web/public/{ralph-panel.61076370.js → ralph-panel.6de2d0f8.js} +2 -2
  63. package/dist/web/public/{ralph-panel.61076370.js.br → ralph-panel.6de2d0f8.js.br} +0 -0
  64. package/dist/web/public/{ralph-panel.61076370.js.gz → ralph-panel.6de2d0f8.js.gz} +0 -0
  65. package/dist/web/public/{ralph-wizard.52d533d2.js → ralph-wizard.a6b2d36b.js} +6 -6
  66. package/dist/web/public/ralph-wizard.a6b2d36b.js.br +0 -0
  67. package/dist/web/public/ralph-wizard.a6b2d36b.js.gz +0 -0
  68. package/dist/web/public/{respawn-ui.5377f958.js → respawn-ui.2d249da9.js} +1 -1
  69. package/dist/web/public/{respawn-ui.5377f958.js.br → respawn-ui.2d249da9.js.br} +0 -0
  70. package/dist/web/public/{respawn-ui.5377f958.js.gz → respawn-ui.2d249da9.js.gz} +0 -0
  71. package/dist/web/public/session-ui.7e2dbbdd.js +36 -0
  72. package/dist/web/public/session-ui.7e2dbbdd.js.br +0 -0
  73. package/dist/web/public/session-ui.7e2dbbdd.js.gz +0 -0
  74. package/dist/web/public/settings-ui.cbedc88a.js +55 -0
  75. package/dist/web/public/settings-ui.cbedc88a.js.br +0 -0
  76. package/dist/web/public/settings-ui.cbedc88a.js.gz +0 -0
  77. package/dist/web/public/styles.d978a628.css +1 -0
  78. package/dist/web/public/styles.d978a628.css.br +0 -0
  79. package/dist/web/public/styles.d978a628.css.gz +0 -0
  80. package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
  81. package/dist/web/public/sw.js.gz +0 -0
  82. package/dist/web/public/terminal-ui.6ce91b0b.js +3 -0
  83. package/dist/web/public/terminal-ui.6ce91b0b.js.br +0 -0
  84. package/dist/web/public/terminal-ui.6ce91b0b.js.gz +0 -0
  85. package/dist/web/public/upload.html.gz +0 -0
  86. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  87. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  88. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  89. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  90. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  91. package/dist/web/public/vendor/xterm.css.gz +0 -0
  92. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  93. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  94. package/dist/web/routes/case-routes.d.ts.map +1 -1
  95. package/dist/web/routes/case-routes.js +1 -2
  96. package/dist/web/routes/case-routes.js.map +1 -1
  97. package/dist/web/routes/clipboard-routes.d.ts.map +1 -1
  98. package/dist/web/routes/clipboard-routes.js +3 -2
  99. package/dist/web/routes/clipboard-routes.js.map +1 -1
  100. package/dist/web/routes/file-routes.d.ts.map +1 -1
  101. package/dist/web/routes/file-routes.js +5 -2
  102. package/dist/web/routes/file-routes.js.map +1 -1
  103. package/dist/web/routes/hook-event-routes.js +1 -1
  104. package/dist/web/routes/hook-event-routes.js.map +1 -1
  105. package/dist/web/routes/mux-routes.js +3 -3
  106. package/dist/web/routes/mux-routes.js.map +1 -1
  107. package/dist/web/routes/plan-routes.js +1 -1
  108. package/dist/web/routes/plan-routes.js.map +1 -1
  109. package/dist/web/routes/push-routes.js +2 -2
  110. package/dist/web/routes/push-routes.js.map +1 -1
  111. package/dist/web/routes/ralph-routes.js +2 -2
  112. package/dist/web/routes/ralph-routes.js.map +1 -1
  113. package/dist/web/routes/respawn-routes.d.ts.map +1 -1
  114. package/dist/web/routes/respawn-routes.js +7 -8
  115. package/dist/web/routes/respawn-routes.js.map +1 -1
  116. package/dist/web/routes/scheduled-routes.js +2 -2
  117. package/dist/web/routes/scheduled-routes.js.map +1 -1
  118. package/dist/web/routes/session-routes.d.ts.map +1 -1
  119. package/dist/web/routes/session-routes.js +28 -22
  120. package/dist/web/routes/session-routes.js.map +1 -1
  121. package/dist/web/routes/system-routes.d.ts.map +1 -1
  122. package/dist/web/routes/system-routes.js +13 -17
  123. package/dist/web/routes/system-routes.js.map +1 -1
  124. package/dist/web/routes/ws-routes.d.ts.map +1 -1
  125. package/dist/web/routes/ws-routes.js +21 -1
  126. package/dist/web/routes/ws-routes.js.map +1 -1
  127. package/dist/web/schemas.d.ts +5 -0
  128. package/dist/web/schemas.d.ts.map +1 -1
  129. package/dist/web/schemas.js +7 -2
  130. package/dist/web/schemas.js.map +1 -1
  131. package/dist/web/server.d.ts.map +1 -1
  132. package/dist/web/server.js +59 -5
  133. package/dist/web/server.js.map +1 -1
  134. package/package.json +6 -3
  135. package/dist/web/public/api-client.3adebdc2.js.br +0 -0
  136. package/dist/web/public/api-client.3adebdc2.js.gz +0 -0
  137. package/dist/web/public/app.c860ea08.js +0 -34
  138. package/dist/web/public/app.c860ea08.js.br +0 -0
  139. package/dist/web/public/app.c860ea08.js.gz +0 -0
  140. package/dist/web/public/constants.5b68d2de.js.br +0 -0
  141. package/dist/web/public/constants.5b68d2de.js.gz +0 -0
  142. package/dist/web/public/image-input.7cade6a8.js.gz +0 -0
  143. package/dist/web/public/input-cjk.88082175.js +0 -1
  144. package/dist/web/public/input-cjk.88082175.js.br +0 -0
  145. package/dist/web/public/input-cjk.88082175.js.gz +0 -0
  146. package/dist/web/public/keyboard-accessory.cdfd8c04.js.br +0 -0
  147. package/dist/web/public/keyboard-accessory.cdfd8c04.js.gz +0 -0
  148. package/dist/web/public/mobile-handlers.1e2a8ef8.js.br +0 -0
  149. package/dist/web/public/mobile-handlers.1e2a8ef8.js.gz +0 -0
  150. package/dist/web/public/mobile.26dc30d6.css +0 -1
  151. package/dist/web/public/mobile.26dc30d6.css.br +0 -0
  152. package/dist/web/public/mobile.26dc30d6.css.gz +0 -0
  153. package/dist/web/public/panels-ui.5192a2c0.js.br +0 -0
  154. package/dist/web/public/panels-ui.5192a2c0.js.gz +0 -0
  155. package/dist/web/public/ralph-wizard.52d533d2.js.br +0 -0
  156. package/dist/web/public/ralph-wizard.52d533d2.js.gz +0 -0
  157. package/dist/web/public/session-ui.3e0cf024.js +0 -36
  158. package/dist/web/public/session-ui.3e0cf024.js.br +0 -0
  159. package/dist/web/public/session-ui.3e0cf024.js.gz +0 -0
  160. package/dist/web/public/settings-ui.2b70e2c8.js +0 -55
  161. package/dist/web/public/settings-ui.2b70e2c8.js.br +0 -0
  162. package/dist/web/public/settings-ui.2b70e2c8.js.gz +0 -0
  163. package/dist/web/public/styles.e87cb785.css +0 -1
  164. package/dist/web/public/styles.e87cb785.css.br +0 -0
  165. package/dist/web/public/styles.e87cb785.css.gz +0 -0
  166. package/dist/web/public/terminal-ui.37caa926.js +0 -3
  167. package/dist/web/public/terminal-ui.37caa926.js.br +0 -0
  168. package/dist/web/public/terminal-ui.37caa926.js.gz +0 -0
@@ -42,6 +42,22 @@ export declare function buildNofileLimitCommand(targetLimit?: number): string;
42
42
  * - Skips entries with a non-numeric pid or empty name.
43
43
  */
44
44
  export declare function parsePaneList(output: string): Map<string, number>;
45
+ /**
46
+ * Resolve a target pane id from `tmux list-panes -F '#{pane_id}:#{pane_active}'`.
47
+ * Prefers the active pane and falls back to the first valid pane.
48
+ */
49
+ export declare function resolveTmuxPaneTarget(muxName: string, paneTarget?: string): string | null;
50
+ /**
51
+ * Pick the active pane id from `tmux list-panes -F '#{pane_id}:#{pane_active}'`
52
+ * output (lines like `%0:1`). Returns the pane id whose active flag is 1.
53
+ */
54
+ export declare function resolveActivePaneTarget(output: string): string | null;
55
+ export declare function formatPaneSnapshot(lines: string[], geometry: {
56
+ cols: number;
57
+ rows: number;
58
+ cursorX: number;
59
+ cursorY: number;
60
+ }): string;
45
61
  /**
46
62
  * Manages tmux sessions that wrap Claude CLI or shell processes.
47
63
  *
@@ -211,10 +227,20 @@ export declare class TmuxManager extends EventEmitter implements TerminalMultipl
211
227
  */
212
228
  sendInputToPane(muxName: string, paneTarget: string, input: string): boolean;
213
229
  /**
214
- * Capture the current buffer of a specific pane.
215
- * Returns the pane content with ANSI escape codes preserved.
230
+ * Capture the current visible text and SGR styles of a specific pane.
231
+ *
232
+ * `capture-pane -e` is sanitized by `formatPaneSnapshot`: SGR color/style
233
+ * codes are preserved, while cursor/erase/scroll-region controls are stripped
234
+ * before rows are repainted at absolute positions in browser xterm.
216
235
  */
217
236
  capturePaneBuffer(muxName: string, paneTarget: string): string | null;
237
+ /**
238
+ * Capture the active pane for a tmux session.
239
+ *
240
+ * Pane ids are not stable across respawns or restores, so callers should not
241
+ * assume the first pane remains `%0`.
242
+ */
243
+ captureActivePaneBuffer(muxName: string): string | null;
218
244
  /**
219
245
  * Start piping pane output to a file using tmux pipe-pane.
220
246
  * Only pipes output direction (-O) to avoid echoing input.
@@ -226,6 +252,8 @@ export declare class TmuxManager extends EventEmitter implements TerminalMultipl
226
252
  stopPipePane(muxName: string, paneTarget: string): boolean;
227
253
  getAttachCommand(): string;
228
254
  getAttachArgs(muxName: string): string[];
255
+ setManualWindowSize(muxName: string): boolean;
256
+ resizeWindow(muxName: string, cols: number, rows: number): boolean;
229
257
  isAvailable(): boolean;
230
258
  /**
231
259
  * Check if tmux is available on the system.
@@ -1 +1 @@
1
- {"version":3,"file":"tmux-manager.d.ts","sourceRoot":"","sources":["../src/tmux-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tmux-manager.d.ts","sourceRoot":"","sources":["../src/tmux-manager.ts"],"names":[],"mappings":"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"}
@@ -126,6 +126,259 @@ export function parsePaneList(output) {
126
126
  }
127
127
  return result;
128
128
  }
129
+ /**
130
+ * Resolve a target pane id from `tmux list-panes -F '#{pane_id}:#{pane_active}'`.
131
+ * Prefers the active pane and falls back to the first valid pane.
132
+ */
133
+ export function resolveTmuxPaneTarget(muxName, paneTarget) {
134
+ if (!isValidMuxName(muxName)) {
135
+ return null;
136
+ }
137
+ if (paneTarget === undefined || paneTarget === 'active') {
138
+ return muxName;
139
+ }
140
+ if (!SAFE_PANE_TARGET_PATTERN.test(paneTarget)) {
141
+ return null;
142
+ }
143
+ return `${muxName}.${paneTarget}`;
144
+ }
145
+ /**
146
+ * Pick the active pane id from `tmux list-panes -F '#{pane_id}:#{pane_active}'`
147
+ * output (lines like `%0:1`). Returns the pane id whose active flag is 1.
148
+ */
149
+ export function resolveActivePaneTarget(output) {
150
+ for (const line of output.split('\n')) {
151
+ const sep = line.indexOf(':');
152
+ if (sep === -1)
153
+ continue;
154
+ const paneId = line.slice(0, sep).trim();
155
+ const active = line.slice(sep + 1).trim();
156
+ if (paneId && active === '1')
157
+ return paneId;
158
+ }
159
+ return null;
160
+ }
161
+ const GRAPHEME_SEGMENTER = (() => {
162
+ try {
163
+ const Segmenter = Intl.Segmenter;
164
+ return Segmenter ? new Segmenter(undefined, { granularity: 'grapheme' }) : null;
165
+ }
166
+ catch {
167
+ return null;
168
+ }
169
+ })();
170
+ function findEscapeEnd(text, start) {
171
+ const type = text[start + 1];
172
+ if (type === '[') {
173
+ for (let i = start + 2; i < text.length; i++) {
174
+ const code = text.charCodeAt(i);
175
+ if (code >= 0x40 && code <= 0x7e)
176
+ return i;
177
+ }
178
+ return text.length - 1;
179
+ }
180
+ if (type === ']') {
181
+ for (let i = start + 2; i < text.length; i++) {
182
+ if (text.charCodeAt(i) === 0x07)
183
+ return i;
184
+ if (text[i] === '\x1b' && text[i + 1] === '\\')
185
+ return i + 1;
186
+ }
187
+ return text.length - 1;
188
+ }
189
+ if (type === 'P' || type === '^' || type === '_' || type === 'X') {
190
+ for (let i = start + 2; i < text.length; i++) {
191
+ if (text.charCodeAt(i) === 0x07)
192
+ return i;
193
+ if (text[i] === '\x1b' && text[i + 1] === '\\')
194
+ return i + 1;
195
+ }
196
+ return text.length - 1;
197
+ }
198
+ return Math.min(start + 1, text.length - 1);
199
+ }
200
+ function sanitizePaneLineStyles(line) {
201
+ let result = '';
202
+ for (let i = 0; i < line.length; i++) {
203
+ if (line[i] !== '\x1b') {
204
+ result += line[i];
205
+ continue;
206
+ }
207
+ const end = findEscapeEnd(line, i);
208
+ const sequence = line.slice(i, end + 1);
209
+ if (isSgrSequence(sequence)) {
210
+ result += sequence;
211
+ }
212
+ i = end;
213
+ }
214
+ return result;
215
+ }
216
+ function isSgrSequence(sequence) {
217
+ return (sequence.length >= 3 &&
218
+ sequence.charCodeAt(0) === 27 &&
219
+ sequence[1] === '[' &&
220
+ sequence.endsWith('m') &&
221
+ /^[0-9;:]*$/.test(sequence.slice(2, -1)));
222
+ }
223
+ function isZeroWidthCodePoint(codePoint) {
224
+ return (codePoint === 0x00ad ||
225
+ codePoint === 0x034f ||
226
+ codePoint === 0x061c ||
227
+ codePoint === 0x115f ||
228
+ codePoint === 0x1160 ||
229
+ codePoint === 0x17b4 ||
230
+ codePoint === 0x17b5 ||
231
+ codePoint === 0x180e ||
232
+ codePoint === 0x200b ||
233
+ codePoint === 0x200c ||
234
+ codePoint === 0x200d ||
235
+ codePoint === 0x2060 ||
236
+ codePoint === 0xfeff ||
237
+ (codePoint >= 0x0300 && codePoint <= 0x036f) ||
238
+ (codePoint >= 0x0483 && codePoint <= 0x0489) ||
239
+ (codePoint >= 0x0591 && codePoint <= 0x05bd) ||
240
+ codePoint === 0x05bf ||
241
+ (codePoint >= 0x05c1 && codePoint <= 0x05c2) ||
242
+ (codePoint >= 0x05c4 && codePoint <= 0x05c5) ||
243
+ codePoint === 0x05c7 ||
244
+ (codePoint >= 0x0610 && codePoint <= 0x061a) ||
245
+ (codePoint >= 0x064b && codePoint <= 0x065f) ||
246
+ codePoint === 0x0670 ||
247
+ (codePoint >= 0x06d6 && codePoint <= 0x06dc) ||
248
+ (codePoint >= 0x06df && codePoint <= 0x06e4) ||
249
+ (codePoint >= 0x06e7 && codePoint <= 0x06e8) ||
250
+ (codePoint >= 0x06ea && codePoint <= 0x06ed) ||
251
+ codePoint === 0x0711 ||
252
+ (codePoint >= 0x0730 && codePoint <= 0x074a) ||
253
+ (codePoint >= 0x07a6 && codePoint <= 0x07b0) ||
254
+ (codePoint >= 0x07eb && codePoint <= 0x07f3) ||
255
+ (codePoint >= 0x0816 && codePoint <= 0x0819) ||
256
+ (codePoint >= 0x081b && codePoint <= 0x0823) ||
257
+ (codePoint >= 0x0825 && codePoint <= 0x0827) ||
258
+ (codePoint >= 0x0829 && codePoint <= 0x082d) ||
259
+ (codePoint >= 0x0859 && codePoint <= 0x085b) ||
260
+ (codePoint >= 0x08d3 && codePoint <= 0x08e1) ||
261
+ (codePoint >= 0x08e3 && codePoint <= 0x0902) ||
262
+ (codePoint >= 0x093a && codePoint <= 0x093c) ||
263
+ codePoint === 0x094d ||
264
+ (codePoint >= 0x0951 && codePoint <= 0x0957) ||
265
+ (codePoint >= 0x0962 && codePoint <= 0x0963) ||
266
+ (codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
267
+ (codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
268
+ (codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
269
+ (codePoint >= 0xfe00 && codePoint <= 0xfe0f) ||
270
+ (codePoint >= 0xfe20 && codePoint <= 0xfe2f) ||
271
+ (codePoint >= 0xe0100 && codePoint <= 0xe01ef));
272
+ }
273
+ function isWideCodePoint(codePoint) {
274
+ return (codePoint >= 0x1100 &&
275
+ (codePoint <= 0x115f ||
276
+ codePoint === 0x2329 ||
277
+ codePoint === 0x232a ||
278
+ (codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
279
+ (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
280
+ (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
281
+ (codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
282
+ (codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
283
+ (codePoint >= 0xff00 && codePoint <= 0xff60) ||
284
+ (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
285
+ (codePoint >= 0x1f300 && codePoint <= 0x1faff) ||
286
+ (codePoint >= 0x20000 && codePoint <= 0x3fffd)));
287
+ }
288
+ function nextGrapheme(text, start) {
289
+ if (GRAPHEME_SEGMENTER) {
290
+ const iterator = GRAPHEME_SEGMENTER.segment(text.slice(start))[Symbol.iterator]();
291
+ const next = iterator.next();
292
+ if (!next.done && next.value.segment) {
293
+ return { value: next.value.segment, nextIndex: start + next.value.segment.length };
294
+ }
295
+ }
296
+ const first = text.codePointAt(start);
297
+ if (first === undefined)
298
+ return { value: '', nextIndex: start + 1 };
299
+ let value = String.fromCodePoint(first);
300
+ let nextIndex = start + value.length;
301
+ while (nextIndex < text.length) {
302
+ const codePoint = text.codePointAt(nextIndex);
303
+ if (codePoint === undefined || !isZeroWidthCodePoint(codePoint))
304
+ break;
305
+ const mark = String.fromCodePoint(codePoint);
306
+ value += mark;
307
+ nextIndex += mark.length;
308
+ }
309
+ return { value, nextIndex };
310
+ }
311
+ function terminalCellWidth(grapheme) {
312
+ let hasVisible = false;
313
+ let hasWide = false;
314
+ for (let i = 0; i < grapheme.length; i++) {
315
+ const codePoint = grapheme.codePointAt(i);
316
+ if (codePoint === undefined)
317
+ continue;
318
+ if (codePoint > 0xffff)
319
+ i++;
320
+ if (isZeroWidthCodePoint(codePoint) || codePoint < 0x20 || (codePoint >= 0x7f && codePoint < 0xa0)) {
321
+ continue;
322
+ }
323
+ hasVisible = true;
324
+ if (isWideCodePoint(codePoint))
325
+ hasWide = true;
326
+ }
327
+ if (!hasVisible)
328
+ return 0;
329
+ return hasWide ? 2 : 1;
330
+ }
331
+ function truncatePaneLineByVisibleColumns(line, maxColumns) {
332
+ let result = '';
333
+ let visibleColumns = 0;
334
+ let sawSgr = false;
335
+ for (let i = 0; i < line.length; i++) {
336
+ if (line[i] === '\x1b') {
337
+ const end = findEscapeEnd(line, i);
338
+ const sequence = line.slice(i, end + 1);
339
+ if (isSgrSequence(sequence)) {
340
+ result += sequence;
341
+ sawSgr = true;
342
+ }
343
+ i = end;
344
+ continue;
345
+ }
346
+ const grapheme = nextGrapheme(line, i);
347
+ const width = terminalCellWidth(grapheme.value);
348
+ if (width === 0) {
349
+ result += grapheme.value;
350
+ }
351
+ else if (visibleColumns + width <= maxColumns) {
352
+ result += grapheme.value;
353
+ visibleColumns += width;
354
+ }
355
+ else {
356
+ break;
357
+ }
358
+ i = grapheme.nextIndex - 1;
359
+ if (visibleColumns >= maxColumns) {
360
+ continue;
361
+ }
362
+ }
363
+ if (sawSgr) {
364
+ result += '\x1b[0m';
365
+ }
366
+ return result;
367
+ }
368
+ export function formatPaneSnapshot(lines, geometry) {
369
+ const cols = Math.max(1, geometry.cols);
370
+ const paintCols = Math.max(1, cols - 1);
371
+ const rows = Math.max(1, geometry.rows);
372
+ const parts = [];
373
+ for (let row = 0; row < Math.min(lines.length, rows); row++) {
374
+ const safeLine = truncatePaneLineByVisibleColumns(sanitizePaneLineStyles(lines[row]), paintCols);
375
+ parts.push(`\x1b[${row + 1};1H${safeLine}`);
376
+ }
377
+ const cursorX = Math.max(0, Math.min(cols - 1, geometry.cursorX));
378
+ const cursorY = Math.max(0, Math.min(rows - 1, geometry.cursorY));
379
+ parts.push(`\x1b[${cursorY + 1};${cursorX + 1}H`);
380
+ return parts.join('');
381
+ }
129
382
  /** Characters unsafe in paths — shell metacharacters, quotes, and control chars */
130
383
  const UNSAFE_PATH_CHARS = /[;&|$`(){}<>'"\n\r]/;
131
384
  /**
@@ -135,6 +388,9 @@ const UNSAFE_PATH_CHARS = /[;&|$`(){}<>'"\n\r]/;
135
388
  function isValidMuxName(name) {
136
389
  return SAFE_MUX_NAME_PATTERN.test(name) || LEGACY_MUX_NAME_PATTERN.test(name);
137
390
  }
391
+ function isValidTerminalDimension(value) {
392
+ return Number.isSafeInteger(value) && value > 0 && value <= 1000;
393
+ }
138
394
  /**
139
395
  * Validates that a path contains only safe characters.
140
396
  * Prevents command injection via malformed paths.
@@ -600,14 +856,17 @@ export class TmuxManager extends EventEmitter {
600
856
  // (Production uses systemd which has a clean env, but dev/test may be nested.)
601
857
  const cleanEnv = { ...process.env };
602
858
  delete cleanEnv.TMUX;
603
- // Start the tmux server from a stable local cwd so FUSE/rclone workspace
604
- // blips do not poison tmux's long-lived getcwd state.
859
+ // Create the session on the dedicated socket (${this.tmux()} = `tmux -L <socket>`),
860
+ // launched in TMUX_LAUNCH_CWD (/tmp) rather than the real workingDir: a FUSE/rclone
861
+ // mount that isn't ready yet makes `getcwd` fail and breaks the spawn (see #110). The
862
+ // pane cd's into workingDir below via respawn-pane.
605
863
  execSync(`${this.tmux()} new-session -ds "${muxName}" -c ${TMUX_LAUNCH_CWD}`, {
606
864
  cwd: TMUX_LAUNCH_CWD,
607
865
  timeout: EXEC_TIMEOUT_MS,
608
866
  stdio: 'ignore',
609
867
  env: cleanEnv,
610
868
  });
869
+ this.resizeWindow(muxName, 120, 40);
611
870
  // Set remain-on-exit now that the server is running — must be before respawn-pane
612
871
  try {
613
872
  execSync(`${this.tmux()} set-option -t "${muxName}" remain-on-exit on`, {
@@ -804,6 +1063,8 @@ export class TmuxManager extends EventEmitter {
804
1063
  sessionExists(muxName) {
805
1064
  if (IS_TEST_MODE)
806
1065
  return false;
1066
+ if (!isValidMuxName(muxName))
1067
+ return false;
807
1068
  try {
808
1069
  execSync(`${this.tmux()} has-session -t "${muxName}" 2>/dev/null`, {
809
1070
  encoding: 'utf-8',
@@ -932,14 +1193,16 @@ export class TmuxManager extends EventEmitter {
932
1193
  // Process group may not exist or already terminated
933
1194
  }
934
1195
  }
935
- // Strategy 3: Kill tmux session by name
936
- try {
937
- execSync(`${this.tmux()} kill-session -t "${session.muxName}" 2>/dev/null`, {
938
- timeout: EXEC_TIMEOUT_MS,
939
- });
940
- }
941
- catch {
942
- // Session may already be dead
1196
+ // Strategy 3: Kill tmux session by name (guard the name before it reaches the shell)
1197
+ if (isValidMuxName(session.muxName)) {
1198
+ try {
1199
+ execSync(`${this.tmux()} kill-session -t "${session.muxName}" 2>/dev/null`, {
1200
+ timeout: EXEC_TIMEOUT_MS,
1201
+ });
1202
+ }
1203
+ catch {
1204
+ // Session may already be dead
1205
+ }
943
1206
  }
944
1207
  // Strategy 4: Direct kill by PID as final fallback
945
1208
  if (this.isProcessAlive(currentPid)) {
@@ -1031,6 +1294,14 @@ export class TmuxManager extends EventEmitter {
1031
1294
  for (const [sessionName, pid] of active) {
1032
1295
  if (!sessionName.startsWith('codeman-') && !sessionName.startsWith('claudeman-'))
1033
1296
  continue;
1297
+ // Only admit names that pass the safe-name pattern. A foreign process on the
1298
+ // shared `tmux -L codeman` socket could create a `codeman-…` session whose name
1299
+ // contains shell metacharacters; rejecting it here keeps it out of this.sessions
1300
+ // and away from the name-interpolating tmux call sites (M1).
1301
+ if (!isValidMuxName(sessionName)) {
1302
+ console.warn(`[TmuxManager] Skipping discovered tmux session with unsafe name: ${sessionName}`);
1303
+ continue;
1304
+ }
1034
1305
  if (knownMuxNames.has(sessionName))
1035
1306
  continue;
1036
1307
  const fragment = sessionName.replace(/^(?:codeman|claudeman)-/, '');
@@ -1488,8 +1759,11 @@ export class TmuxManager extends EventEmitter {
1488
1759
  }
1489
1760
  }
1490
1761
  /**
1491
- * Capture the current buffer of a specific pane.
1492
- * Returns the pane content with ANSI escape codes preserved.
1762
+ * Capture the current visible text and SGR styles of a specific pane.
1763
+ *
1764
+ * `capture-pane -e` is sanitized by `formatPaneSnapshot`: SGR color/style
1765
+ * codes are preserved, while cursor/erase/scroll-region controls are stripped
1766
+ * before rows are repainted at absolute positions in browser xterm.
1493
1767
  */
1494
1768
  capturePaneBuffer(muxName, paneTarget) {
1495
1769
  if (IS_TEST_MODE)
@@ -1504,16 +1778,63 @@ export class TmuxManager extends EventEmitter {
1504
1778
  }
1505
1779
  const target = paneTarget.startsWith('%') ? `${muxName}.${paneTarget}` : `${muxName}.%${paneTarget}`;
1506
1780
  try {
1507
- return execSync(`${this.tmux()} capture-pane -p -e -t ${shellescape(target)} -S -5000`, {
1781
+ const buffer = execSync(`${this.tmux()} capture-pane -p -e -t ${shellescape(target)}`, {
1508
1782
  encoding: 'utf-8',
1509
1783
  timeout: EXEC_TIMEOUT_MS,
1510
- });
1784
+ }).replace(/\n+$/g, '');
1785
+ try {
1786
+ const cursor = execSync(`${this.tmux()} display-message -p -t ${shellescape(target)} '#{cursor_x} #{cursor_y} #{pane_width} #{pane_height}'`, {
1787
+ encoding: 'utf-8',
1788
+ timeout: EXEC_TIMEOUT_MS,
1789
+ }).trim();
1790
+ const [cursorX, cursorY, cols, rows] = cursor.split(/\s+/).map((value) => parseInt(value, 10));
1791
+ if (Number.isFinite(cursorX) &&
1792
+ Number.isFinite(cursorY) &&
1793
+ Number.isFinite(cols) &&
1794
+ Number.isFinite(rows) &&
1795
+ cursorX >= 0 &&
1796
+ cursorY >= 0 &&
1797
+ cols > 0 &&
1798
+ rows > 0) {
1799
+ return formatPaneSnapshot(buffer.split('\n'), { cols, rows, cursorX, cursorY });
1800
+ }
1801
+ }
1802
+ catch (cursorErr) {
1803
+ console.error('[TmuxManager] Failed to query pane cursor after capture:', cursorErr);
1804
+ }
1805
+ return buffer;
1511
1806
  }
1512
1807
  catch (err) {
1513
1808
  console.error('[TmuxManager] Failed to capture pane buffer:', err);
1514
1809
  return null;
1515
1810
  }
1516
1811
  }
1812
+ /**
1813
+ * Capture the active pane for a tmux session.
1814
+ *
1815
+ * Pane ids are not stable across respawns or restores, so callers should not
1816
+ * assume the first pane remains `%0`.
1817
+ */
1818
+ captureActivePaneBuffer(muxName) {
1819
+ if (IS_TEST_MODE)
1820
+ return '';
1821
+ if (!isValidMuxName(muxName)) {
1822
+ console.error('[TmuxManager] Invalid session name in captureActivePaneBuffer:', muxName);
1823
+ return null;
1824
+ }
1825
+ try {
1826
+ const output = execSync(`${this.tmux()} list-panes -t ${shellescape(muxName)} -F '#{pane_id}:#{pane_active}'`, {
1827
+ encoding: 'utf-8',
1828
+ timeout: EXEC_TIMEOUT_MS,
1829
+ }).trim();
1830
+ const target = resolveActivePaneTarget(output);
1831
+ return target ? this.capturePaneBuffer(muxName, target) : null;
1832
+ }
1833
+ catch (err) {
1834
+ console.error('[TmuxManager] Failed to resolve active pane for capture:', err);
1835
+ return null;
1836
+ }
1837
+ }
1517
1838
  /**
1518
1839
  * Start piping pane output to a file using tmux pipe-pane.
1519
1840
  * Only pipes output direction (-O) to avoid echoing input.
@@ -1579,6 +1900,43 @@ export class TmuxManager extends EventEmitter {
1579
1900
  getAttachArgs(muxName) {
1580
1901
  return ['-L', this.tmuxSocket, 'attach-session', '-t', muxName];
1581
1902
  }
1903
+ setManualWindowSize(muxName) {
1904
+ if (!isValidMuxName(muxName)) {
1905
+ console.error('[TmuxManager] Invalid session name in setManualWindowSize:', muxName);
1906
+ return false;
1907
+ }
1908
+ try {
1909
+ execSync(`${this.tmux()} set-window-option -t ${shellescape(muxName)} window-size manual`, {
1910
+ timeout: EXEC_TIMEOUT_MS,
1911
+ stdio: 'ignore',
1912
+ });
1913
+ return true;
1914
+ }
1915
+ catch (err) {
1916
+ console.error('[TmuxManager] Failed to set manual window size:', err);
1917
+ return false;
1918
+ }
1919
+ }
1920
+ resizeWindow(muxName, cols, rows) {
1921
+ if (!isValidMuxName(muxName)) {
1922
+ console.error('[TmuxManager] Invalid session name in resizeWindow:', muxName);
1923
+ return false;
1924
+ }
1925
+ if (!isValidTerminalDimension(cols) || !isValidTerminalDimension(rows)) {
1926
+ console.error('[TmuxManager] Invalid resize dimensions:', { cols, rows });
1927
+ return false;
1928
+ }
1929
+ // Fire-and-forget: this runs on the interactive resize path (WS {t:'z'} and
1930
+ // HTTP /resize), so use a non-blocking exec — a slow/hung tmux must not stall
1931
+ // the Fastify event loop while other sessions' input/SSE are served. The sole
1932
+ // caller (Session.resize) ignores the result, and under `window-size manual`
1933
+ // the subsequent ptyProcess.resize is subordinate to this authoritative size.
1934
+ exec(`${this.tmux()} resize-window -t ${shellescape(muxName)} -x ${cols} -y ${rows}`, { timeout: EXEC_TIMEOUT_MS }, (err) => {
1935
+ if (err)
1936
+ console.error('[TmuxManager] Failed to resize tmux window:', err);
1937
+ });
1938
+ return true;
1939
+ }
1582
1940
  isAvailable() {
1583
1941
  return TmuxManager.isTmuxAvailable();
1584
1942
  }