aicodeman 1.41.1 → 1.43.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 (276) hide show
  1. package/README.md +54 -24
  2. package/dist/claude-session-title.d.ts +6 -4
  3. package/dist/claude-session-title.d.ts.map +1 -1
  4. package/dist/claude-session-title.js +6 -4
  5. package/dist/claude-session-title.js.map +1 -1
  6. package/dist/cli-agent.js +1 -1
  7. package/dist/cli-agent.js.map +1 -1
  8. package/dist/config/cli-registry/patterns.d.ts +1 -1
  9. package/dist/config/cli-registry/patterns.d.ts.map +1 -1
  10. package/dist/config/cli-registry/patterns.js +3 -0
  11. package/dist/config/cli-registry/patterns.js.map +1 -1
  12. package/dist/config/cli-registry/schema.d.ts +3 -0
  13. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  14. package/dist/config/cli-registry/schema.js +19 -3
  15. package/dist/config/cli-registry/schema.js.map +1 -1
  16. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  17. package/dist/config/cli-registry/stock.js +302 -7
  18. package/dist/config/cli-registry/stock.js.map +1 -1
  19. package/dist/config/cli-registry/types.d.ts +37 -3
  20. package/dist/config/cli-registry/types.d.ts.map +1 -1
  21. package/dist/git-worktrees.d.ts +292 -0
  22. package/dist/git-worktrees.d.ts.map +1 -0
  23. package/dist/git-worktrees.js +791 -0
  24. package/dist/git-worktrees.js.map +1 -0
  25. package/dist/hooks-config.d.ts +9 -0
  26. package/dist/hooks-config.d.ts.map +1 -1
  27. package/dist/hooks-config.js +12 -0
  28. package/dist/hooks-config.js.map +1 -1
  29. package/dist/mcp-server.d.ts.map +1 -1
  30. package/dist/mcp-server.js +2 -1
  31. package/dist/mcp-server.js.map +1 -1
  32. package/dist/mcp-sync-targets.d.ts +6 -3
  33. package/dist/mcp-sync-targets.d.ts.map +1 -1
  34. package/dist/mcp-sync-targets.js +6 -3
  35. package/dist/mcp-sync-targets.js.map +1 -1
  36. package/dist/mux-interface.d.ts +7 -3
  37. package/dist/mux-interface.d.ts.map +1 -1
  38. package/dist/services/unified-session-service.d.ts +8 -0
  39. package/dist/services/unified-session-service.d.ts.map +1 -1
  40. package/dist/services/unified-session-service.js +21 -0
  41. package/dist/services/unified-session-service.js.map +1 -1
  42. package/dist/session-activity.d.ts +51 -0
  43. package/dist/session-activity.d.ts.map +1 -1
  44. package/dist/session-activity.js +62 -0
  45. package/dist/session-activity.js.map +1 -1
  46. package/dist/session-cli-registry-bridge.d.ts +2 -1
  47. package/dist/session-cli-registry-bridge.d.ts.map +1 -1
  48. package/dist/session-cli-registry-bridge.js.map +1 -1
  49. package/dist/session-submit-verifier.js +1 -1
  50. package/dist/session.d.ts +84 -9
  51. package/dist/session.d.ts.map +1 -1
  52. package/dist/session.js +232 -18
  53. package/dist/session.js.map +1 -1
  54. package/dist/tmux-manager.d.ts +62 -7
  55. package/dist/tmux-manager.d.ts.map +1 -1
  56. package/dist/tmux-manager.js +98 -20
  57. package/dist/tmux-manager.js.map +1 -1
  58. package/dist/types/session.d.ts +32 -4
  59. package/dist/types/session.d.ts.map +1 -1
  60. package/dist/types/session.js +2 -1
  61. package/dist/types/session.js.map +1 -1
  62. package/dist/utils/cli-installed-probes.d.ts +1 -1
  63. package/dist/utils/cli-installed-probes.d.ts.map +1 -1
  64. package/dist/utils/cli-installed-probes.js +4 -2
  65. package/dist/utils/cli-installed-probes.js.map +1 -1
  66. package/dist/utils/copilot-cli-resolver.d.ts +48 -0
  67. package/dist/utils/copilot-cli-resolver.d.ts.map +1 -0
  68. package/dist/utils/copilot-cli-resolver.js +112 -0
  69. package/dist/utils/copilot-cli-resolver.js.map +1 -0
  70. package/dist/utils/deepseek-cli-resolver.d.ts.map +1 -1
  71. package/dist/utils/deepseek-cli-resolver.js +1 -1
  72. package/dist/utils/deepseek-cli-resolver.js.map +1 -1
  73. package/dist/utils/index.d.ts +1 -0
  74. package/dist/utils/index.d.ts.map +1 -1
  75. package/dist/utils/index.js +1 -0
  76. package/dist/utils/index.js.map +1 -1
  77. package/dist/web/approval-inbox.d.ts +12 -2
  78. package/dist/web/approval-inbox.d.ts.map +1 -1
  79. package/dist/web/approval-inbox.js +7 -2
  80. package/dist/web/approval-inbox.js.map +1 -1
  81. package/dist/web/case-path.d.ts +4 -2
  82. package/dist/web/case-path.d.ts.map +1 -1
  83. package/dist/web/case-path.js +13 -4
  84. package/dist/web/case-path.js.map +1 -1
  85. package/dist/web/public/admin-ui.js.gz +0 -0
  86. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  87. package/dist/web/public/app.ae50029a.js +43 -0
  88. package/dist/web/public/app.ae50029a.js.br +0 -0
  89. package/dist/web/public/app.ae50029a.js.gz +0 -0
  90. package/dist/web/public/approvals-ui.js.gz +0 -0
  91. package/dist/web/public/{constants.17d77b65.js → constants.e5e588db.js} +156 -2
  92. package/dist/web/public/constants.e5e588db.js.br +0 -0
  93. package/dist/web/public/constants.e5e588db.js.gz +0 -0
  94. package/dist/web/public/cron-ui.js.gz +0 -0
  95. package/dist/web/public/entrance-animations.js.gz +0 -0
  96. package/dist/web/public/git-status-ui.js.gz +0 -0
  97. package/dist/web/public/home-sessions.js.gz +0 -0
  98. package/dist/web/public/host-wake-ui.js.gz +0 -0
  99. package/dist/web/public/i18n.8f291f06.js +2 -0
  100. package/dist/web/public/i18n.8f291f06.js.br +0 -0
  101. package/dist/web/public/i18n.8f291f06.js.gz +0 -0
  102. package/dist/web/public/{image-input.9ed1d0d2.js → image-input.941386e7.js} +41 -22
  103. package/dist/web/public/image-input.941386e7.js.br +0 -0
  104. package/dist/web/public/image-input.941386e7.js.gz +0 -0
  105. package/dist/web/public/index.html +88 -16
  106. package/dist/web/public/index.html.br +0 -0
  107. package/dist/web/public/index.html.gz +0 -0
  108. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  109. package/dist/web/public/{keyboard-accessory.c458eea8.js → keyboard-accessory.c283e827.js} +40 -29
  110. package/dist/web/public/keyboard-accessory.c283e827.js.br +0 -0
  111. package/dist/web/public/keyboard-accessory.c283e827.js.gz +0 -0
  112. package/dist/web/public/mobile-handlers.0ef9c311.js.gz +0 -0
  113. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  114. package/dist/web/public/mobile-overview.js +1 -0
  115. package/dist/web/public/mobile-overview.js.br +0 -0
  116. package/dist/web/public/mobile-overview.js.gz +0 -0
  117. package/dist/web/public/mobile.13514863.css +1 -0
  118. package/dist/web/public/mobile.13514863.css.br +0 -0
  119. package/dist/web/public/mobile.13514863.css.gz +0 -0
  120. package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
  121. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  122. package/dist/web/public/{panels-ui.73ec0137.js → panels-ui.b7bfb9da.js} +6 -6
  123. package/dist/web/public/panels-ui.b7bfb9da.js.br +0 -0
  124. package/dist/web/public/panels-ui.b7bfb9da.js.gz +0 -0
  125. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  126. package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
  127. package/dist/web/public/readmymind-ui.js.gz +0 -0
  128. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  129. package/dist/web/public/{respawn-ui.ff0dae4c.js → respawn-ui.d7adcf6a.js} +1 -1
  130. package/dist/web/public/respawn-ui.d7adcf6a.js.br +0 -0
  131. package/dist/web/public/respawn-ui.d7adcf6a.js.gz +0 -0
  132. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  133. package/dist/web/public/security-notices.json +21 -0
  134. package/dist/web/public/session-lineage.js.gz +0 -0
  135. package/dist/web/public/session-ui.be076c88.js +100 -0
  136. package/dist/web/public/session-ui.be076c88.js.br +0 -0
  137. package/dist/web/public/session-ui.be076c88.js.gz +0 -0
  138. package/dist/web/public/settings-ui.fc4cc50a.js +97 -0
  139. package/dist/web/public/settings-ui.fc4cc50a.js.br +0 -0
  140. package/dist/web/public/settings-ui.fc4cc50a.js.gz +0 -0
  141. package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
  142. package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
  143. package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
  144. package/dist/web/public/styles.dcfa907f.css +1 -0
  145. package/dist/web/public/styles.dcfa907f.css.br +0 -0
  146. package/dist/web/public/styles.dcfa907f.css.gz +0 -0
  147. package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
  148. package/dist/web/public/sw.js +2 -2
  149. package/dist/web/public/sw.js.br +0 -0
  150. package/dist/web/public/sw.js.gz +0 -0
  151. package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
  152. package/dist/web/public/tab-rail-resize.7157e0bd.js.gz +0 -0
  153. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
  154. package/dist/web/public/terminal-split.js.gz +0 -0
  155. package/dist/web/public/terminal-tile.js +1 -0
  156. package/dist/web/public/terminal-tile.js.br +0 -0
  157. package/dist/web/public/terminal-tile.js.gz +0 -0
  158. package/dist/web/public/terminal-ui.ae01a7d1.js +2 -0
  159. package/dist/web/public/terminal-ui.ae01a7d1.js.br +0 -0
  160. package/dist/web/public/terminal-ui.ae01a7d1.js.gz +0 -0
  161. package/dist/web/public/tile-grid.js +3 -6
  162. package/dist/web/public/tile-grid.js.br +0 -0
  163. package/dist/web/public/tile-grid.js.gz +0 -0
  164. package/dist/web/public/ultracode-panel.js.gz +0 -0
  165. package/dist/web/public/ultracode-windows.js.gz +0 -0
  166. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  167. package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
  168. package/dist/web/public/vendor/fflate.min.js.gz +0 -0
  169. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  170. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  171. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  172. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  173. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  174. package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.gz +0 -0
  175. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.gz +0 -0
  176. package/dist/web/public/vendor/xterm.css.gz +0 -0
  177. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  178. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  179. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  180. package/dist/web/public/webview-tabs.js.gz +0 -0
  181. package/dist/web/public/whats-new.json +13 -0
  182. package/dist/web/response-viewer-transcript.d.ts +6 -0
  183. package/dist/web/response-viewer-transcript.d.ts.map +1 -1
  184. package/dist/web/response-viewer-transcript.js +134 -1
  185. package/dist/web/response-viewer-transcript.js.map +1 -1
  186. package/dist/web/routes/approval-routes.d.ts.map +1 -1
  187. package/dist/web/routes/approval-routes.js +12 -8
  188. package/dist/web/routes/approval-routes.js.map +1 -1
  189. package/dist/web/routes/case-routes.d.ts.map +1 -1
  190. package/dist/web/routes/case-routes.js +9 -2
  191. package/dist/web/routes/case-routes.js.map +1 -1
  192. package/dist/web/routes/index.d.ts +1 -0
  193. package/dist/web/routes/index.d.ts.map +1 -1
  194. package/dist/web/routes/index.js +1 -0
  195. package/dist/web/routes/index.js.map +1 -1
  196. package/dist/web/routes/mcp-sync-routes.d.ts +4 -4
  197. package/dist/web/routes/mcp-sync-routes.js +4 -4
  198. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  199. package/dist/web/routes/ralph-routes.js +19 -14
  200. package/dist/web/routes/ralph-routes.js.map +1 -1
  201. package/dist/web/routes/session-routes.d.ts +19 -7
  202. package/dist/web/routes/session-routes.d.ts.map +1 -1
  203. package/dist/web/routes/session-routes.js +227 -66
  204. package/dist/web/routes/session-routes.js.map +1 -1
  205. package/dist/web/routes/status-telemetry-routes.d.ts.map +1 -1
  206. package/dist/web/routes/status-telemetry-routes.js +9 -5
  207. package/dist/web/routes/status-telemetry-routes.js.map +1 -1
  208. package/dist/web/routes/system-routes.d.ts +9 -0
  209. package/dist/web/routes/system-routes.d.ts.map +1 -1
  210. package/dist/web/routes/system-routes.js +102 -12
  211. package/dist/web/routes/system-routes.js.map +1 -1
  212. package/dist/web/routes/worktree-routes.d.ts +52 -0
  213. package/dist/web/routes/worktree-routes.d.ts.map +1 -0
  214. package/dist/web/routes/worktree-routes.js +220 -0
  215. package/dist/web/routes/worktree-routes.js.map +1 -0
  216. package/dist/web/schemas.d.ts +30 -1
  217. package/dist/web/schemas.d.ts.map +1 -1
  218. package/dist/web/schemas.js +74 -3
  219. package/dist/web/schemas.js.map +1 -1
  220. package/dist/web/screen-blocked.d.ts +80 -0
  221. package/dist/web/screen-blocked.d.ts.map +1 -0
  222. package/dist/web/screen-blocked.js +106 -0
  223. package/dist/web/screen-blocked.js.map +1 -0
  224. package/dist/web/server.d.ts.map +1 -1
  225. package/dist/web/server.js +23 -2
  226. package/dist/web/server.js.map +1 -1
  227. package/dist/web/session-listener-wiring.d.ts +1 -0
  228. package/dist/web/session-listener-wiring.d.ts.map +1 -1
  229. package/dist/web/session-listener-wiring.js +20 -0
  230. package/dist/web/session-listener-wiring.js.map +1 -1
  231. package/dist/web/session-wait-registry.d.ts +27 -9
  232. package/dist/web/session-wait-registry.d.ts.map +1 -1
  233. package/dist/web/session-wait-registry.js +36 -13
  234. package/dist/web/session-wait-registry.js.map +1 -1
  235. package/dist/web/sse-events.d.ts +7 -2
  236. package/dist/web/sse-events.d.ts.map +1 -1
  237. package/dist/web/sse-events.js +7 -2
  238. package/dist/web/sse-events.js.map +1 -1
  239. package/package.json +2 -2
  240. package/skills/codeman/SKILL.md +21 -17
  241. package/skills/codeman/preamble.sh +14 -11
  242. package/skills/codeman/reference/endpoints.md +8 -8
  243. package/skills/codeman/reference/messaging.md +5 -5
  244. package/skills/codeman/reference/recipes.md +13 -10
  245. package/skills/codeman/reference/verbs.md +7 -5
  246. package/dist/web/public/app.24689381.js +0 -43
  247. package/dist/web/public/app.24689381.js.br +0 -0
  248. package/dist/web/public/app.24689381.js.gz +0 -0
  249. package/dist/web/public/constants.17d77b65.js.br +0 -0
  250. package/dist/web/public/constants.17d77b65.js.gz +0 -0
  251. package/dist/web/public/i18n.9afd8e4d.js +0 -2
  252. package/dist/web/public/i18n.9afd8e4d.js.br +0 -0
  253. package/dist/web/public/i18n.9afd8e4d.js.gz +0 -0
  254. package/dist/web/public/image-input.9ed1d0d2.js.br +0 -0
  255. package/dist/web/public/image-input.9ed1d0d2.js.gz +0 -0
  256. package/dist/web/public/keyboard-accessory.c458eea8.js.br +0 -0
  257. package/dist/web/public/keyboard-accessory.c458eea8.js.gz +0 -0
  258. package/dist/web/public/mobile.2dff1580.css +0 -1
  259. package/dist/web/public/mobile.2dff1580.css.br +0 -0
  260. package/dist/web/public/mobile.2dff1580.css.gz +0 -0
  261. package/dist/web/public/panels-ui.73ec0137.js.br +0 -0
  262. package/dist/web/public/panels-ui.73ec0137.js.gz +0 -0
  263. package/dist/web/public/respawn-ui.ff0dae4c.js.br +0 -0
  264. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  265. package/dist/web/public/session-ui.dbc6819b.js +0 -100
  266. package/dist/web/public/session-ui.dbc6819b.js.br +0 -0
  267. package/dist/web/public/session-ui.dbc6819b.js.gz +0 -0
  268. package/dist/web/public/settings-ui.81ac264e.js +0 -97
  269. package/dist/web/public/settings-ui.81ac264e.js.br +0 -0
  270. package/dist/web/public/settings-ui.81ac264e.js.gz +0 -0
  271. package/dist/web/public/styles.900a28e4.css +0 -1
  272. package/dist/web/public/styles.900a28e4.css.br +0 -0
  273. package/dist/web/public/styles.900a28e4.css.gz +0 -0
  274. package/dist/web/public/terminal-ui.858f5da5.js +0 -2
  275. package/dist/web/public/terminal-ui.858f5da5.js.br +0 -0
  276. package/dist/web/public/terminal-ui.858f5da5.js.gz +0 -0
package/dist/session.js CHANGED
@@ -42,7 +42,7 @@ import { TaskTracker } from './task-tracker.js';
42
42
  import { RalphTracker } from './ralph-tracker.js';
43
43
  import { BashToolParser } from './bash-tool-parser.js';
44
44
  import { isTrustDialogScreen, trustDialogNextKey, TRUST_KEY_CONFIRM, TRUST_DIALOG_WINDOW_MS, TRUST_DIALOG_RETRY_MS, TRUST_DIALOG_MAX_ATTEMPTS, TRUST_DIALOG_SCAN_BYTES, } from './session-trust-dialog.js';
45
- import { trackActivityStreak, isSustainedActivity, isPaneQuiet, watchingLabel, isAwaitingWorkers, WATCHING_TAIL_LINES, IDLE_RECHECK_MS, PANE_PROBE_MIN_INTERVAL_MS, PANE_PROBE_RECHECK_MS, } from './session-activity.js';
45
+ import { trackActivityStreak, isSustainedActivity, isPaneQuiet, watchingLabel, isAwaitingWorkers, isBlockedScreen, WATCHING_TAIL_LINES, BLOCKED_TAIL_LINES, BLOCKED_RECHECK_MS, BLOCKED_QUIET_RECHECK_MS, BLOCKED_CLEAR_READS, IDLE_RECHECK_MS, PANE_PROBE_MIN_INTERVAL_MS, PANE_PROBE_RECHECK_MS, } from './session-activity.js';
46
46
  import { BufferAccumulator, ANSI_ESCAPE_PATTERN_FULL, TOKEN_PATTERN, SPINNER_PATTERN, CLAUDE_WORKING_LINE_PATTERN, MAX_SESSION_TOKENS, execPattern, getClaudeCliVersion, getClaudeBinaryPath, spawnPtyWithHelperRepair, resolveLocalShell, waitForProcessesExit, } from './utils/index.js';
47
47
  import { MAX_TERMINAL_BUFFER_SIZE, TRIM_TERMINAL_TO as TERMINAL_BUFFER_TRIM_SIZE, MAX_TEXT_OUTPUT_SIZE, TRIM_TEXT_TO as TEXT_OUTPUT_TRIM_SIZE, MAX_MESSAGES, MAX_LINE_BUFFER_SIZE, } from './config/buffer-limits.js';
48
48
  import { DEFAULT_TMUX_HISTORY_LIMIT } from './config/terminal-history.js';
@@ -341,6 +341,8 @@ export class Session extends EventEmitter {
341
341
  _taskCache = new SessionTaskCache();
342
342
  _name;
343
343
  _nameSource;
344
+ /** `pinCliSessionName` as of the last spawn (see {@link _refreshCliNamePin}). */
345
+ _pinEveryCliName = false;
344
346
  /**
345
347
  * Reconstructs the composer draft from USER keystrokes so the first real
346
348
  * prompt can name the tab. Fed only when a write says `fromUser`, and never
@@ -408,6 +410,7 @@ export class Session extends EventEmitter {
408
410
  _activityStreak = null; // Unbroken run of PTY repaints (working detection)
409
411
  _lastPaneProbeAt = 0; // Throttle for the tmux screen probe
410
412
  _lastPaneProbeWorking = null; // Its last verdict (null = could not read)
413
+ _lastPaneProbeTrustDialog = false; // Same capture showed the workspace-trust dialog (see _confirmIdle)
411
414
  /**
412
415
  * Background work the pane's own footer reports, e.g. `1 monitor`; null for none.
413
416
  *
@@ -441,6 +444,23 @@ export class Session extends EventEmitter {
441
444
  _watchingWindow = WATCHING_TAIL_LINES;
442
445
  /** Lazily compiled `capabilities.workDetect.awaitingLine`. See _awaitingLinePattern(). */
443
446
  _awaitingLineRe = undefined;
447
+ /** Lazily compiled `capabilities.workDetect.blockedLine`. See _blockedLinePattern(). */
448
+ _blockedLineRe = undefined;
449
+ /** Resolved with the pattern above: how many rows at the foot of the screen it sees. */
450
+ _blockedWindow = BLOCKED_TAIL_LINES;
451
+ /**
452
+ * The pane is showing a dialog the CLI blocks on, read off the screen with
453
+ * `capabilities.workDetect.blockedLine` (codex: an approval, a question, the trust
454
+ * prompt). Live state, not persisted: a restarted server re-reads it from the attach
455
+ * repaint the same way the watching label comes back. See `_noteBlockedReading()`.
456
+ */
457
+ _screenBlocked = false;
458
+ /** Consecutive readable captures without the dialog (cleared at BLOCKED_CLEAR_READS). */
459
+ _blockedGoneReads = 0;
460
+ /** When the last reading that SAW the dialog was taken. See `_armBlockedRecheck()`. */
461
+ _blockedSeenAt = 0;
462
+ /** Re-reads the screen while a dialog is up, so the alert clears with it. */
463
+ _blockedRecheckTimer = null;
444
464
  /**
445
465
  * The newest model the running CLI reported for itself (its statusline, or its own
446
466
  * footer read off the probe's capture), or null when none has. Feeds `displayModel`
@@ -555,6 +575,8 @@ export class Session extends EventEmitter {
555
575
  _piConfig;
556
576
  // Grok configuration (only for mode === 'grok')
557
577
  _grokConfig;
578
+ // GitHub Copilot configuration (only for mode === 'copilot')
579
+ _copilotConfig;
558
580
  // DeepSeek Harness configuration (only for mode === 'deepseek')
559
581
  _deepSeekConfig;
560
582
  // OMP configuration (only for mode === 'omp')
@@ -723,6 +745,10 @@ export class Session extends EventEmitter {
723
745
  if (config.grokConfig) {
724
746
  this._grokConfig = config.grokConfig;
725
747
  }
748
+ // Apply GitHub Copilot configuration
749
+ if (config.copilotConfig) {
750
+ this._copilotConfig = config.copilotConfig;
751
+ }
726
752
  // Apply env overrides (exported at spawn, not persisted to disk).
727
753
  // Legacy migration: pre-0.7.2 carried effort as the CLAUDE_CODE_EFFORT_LEVEL env var,
728
754
  // which hard-locks /effort switching. Extract it into _effort (--settings soft default)
@@ -1104,6 +1130,15 @@ export class Session extends EventEmitter {
1104
1130
  get watching() {
1105
1131
  return this._watching;
1106
1132
  }
1133
+ /**
1134
+ * True while the pane shows a dialog its CLI is blocked on, as read off the screen
1135
+ * (`capabilities.workDetect.blockedLine`). Always false for a CLI that declares none,
1136
+ * Claude included: its dialogs arrive as hook events instead. The `blocked` wait reads
1137
+ * it so a caller that asks after the dialog appeared is answered at once.
1138
+ */
1139
+ get screenBlocked() {
1140
+ return this._screenBlocked;
1141
+ }
1107
1142
  /**
1108
1143
  * Prompt-cache state of the main conversation as Claude Code last reported it on the
1109
1144
  * statusline (v2.1.251+), fed by `POST /api/status-telemetry`. Null until the first
@@ -1407,16 +1442,31 @@ export class Session extends EventEmitter {
1407
1442
  return this._nameSource;
1408
1443
  }
1409
1444
  /**
1410
- * The name to pin on the Claude CLI as `--name`, or undefined to let Claude
1411
- * title the conversation itself. `--name` is the prompt-box label, the
1412
- * `/resume` picker entry and the terminal title all at once, and a pinned
1413
- * title stops Claude generating its own, so only a name the user chose is
1414
- * worth pinning. Pinning the `w1-myapp` placeholder gave every conversation
1415
- * in a case the same `/resume` entry; an auto name is a cut of the first
1416
- * prompt, which Claude's own generated title already beats.
1445
+ * The name to pin on the Claude CLI as `--name`, or undefined to let Claude title the
1446
+ * conversation itself. `--name` is the prompt-box label, the `/resume` picker entry and the
1447
+ * terminal title all at once, and a pinned title stops Claude generating its own.
1448
+ *
1449
+ * By default only a name the user chose is pinned. Pinning the `w1-myapp` placeholder gave
1450
+ * every conversation in a case the same `/resume` entry (the tab number restarts once a
1451
+ * case's tabs are closed), and an auto name is a cut of the first prompt, which Claude's
1452
+ * own generated title already beats.
1453
+ *
1454
+ * With the opt-in `pinCliSessionName` setting every non-blank tab name is pinned, so the
1455
+ * CLI's labels read the same as the tabs, at the cost of those generated titles. A rename
1456
+ * in Codeman then holds only until the next turn: a process started with `--name`
1457
+ * re-appends its in-memory title (see claude-session-title.ts). The setting is re-read
1458
+ * at each spawn and respawn by {@link _refreshCliNamePin}.
1417
1459
  */
1418
1460
  get cliPinnedName() {
1419
- return this._nameSource === 'manual' ? this._name : undefined;
1461
+ if (!this._pinEveryCliName)
1462
+ return this._nameSource === 'manual' ? this._name : undefined;
1463
+ return this._name.trim() ? this._name : undefined;
1464
+ }
1465
+ /** Re-read `pinCliSessionName` so `cliPinnedName` decides per launch, with no server restart. */
1466
+ async _refreshCliNamePin() {
1467
+ // Dynamic: hooks-config -> route-helpers imports Session, so a static import would be a cycle.
1468
+ const { readPinCliSessionNameEnabled } = await import('./hooks-config.js');
1469
+ this._pinEveryCliName = await readPinCliSessionNameEnabled();
1420
1470
  }
1421
1471
  setAutoClear(enabled, threshold) {
1422
1472
  this._autoOps.setAutoClear(enabled, threshold);
@@ -1585,6 +1635,7 @@ export class Session extends EventEmitter {
1585
1635
  antigravityConfig: this._antigravityConfig,
1586
1636
  piConfig: this._piConfig,
1587
1637
  grokConfig: this._grokConfig,
1638
+ copilotConfig: this._copilotConfig,
1588
1639
  deepSeekConfig: this._deepSeekConfig,
1589
1640
  ompConfig: this._ompConfig,
1590
1641
  resumeSessionId: this._resumeSessionId,
@@ -1936,6 +1987,7 @@ export class Session extends EventEmitter {
1936
1987
  antigravityConfig: this._antigravityConfig,
1937
1988
  piConfig: this._piConfig,
1938
1989
  grokConfig: this._grokConfig,
1990
+ copilotConfig: this._copilotConfig,
1939
1991
  deepSeekConfig: this._deepSeekConfig,
1940
1992
  // OMP resolution/pinning does NOT happen here. This object is built
1941
1993
  // EAGERLY — including on every boot-recovery reattach, before anyone
@@ -2029,6 +2081,7 @@ export class Session extends EventEmitter {
2029
2081
  * still inside it and renders no local `--session-id` to collide.
2030
2082
  */
2031
2083
  async _buildRespawnPaneOptionsWithResumePin() {
2084
+ await this._refreshCliNamePin();
2032
2085
  const options = this._buildRespawnPaneOptions();
2033
2086
  if (this._remote || this._docker)
2034
2087
  return options;
@@ -2293,11 +2346,13 @@ export class Session extends EventEmitter {
2293
2346
  if (this._closing) {
2294
2347
  throw new Error('Session is being closed');
2295
2348
  }
2349
+ await this._refreshCliNamePin();
2296
2350
  // Bounds the workspace-trust scan (see _maybeAcceptTrustDialog). Stamped here
2297
2351
  // rather than at PTY spawn so a slow mux attach still counts as startup.
2298
2352
  this._interactiveStartedAt = Date.now();
2299
2353
  this._trustDialogAttempts = 0;
2300
2354
  this._lastTrustDialogScanAt = 0;
2355
+ this._lastPaneProbeTrustDialog = false; // the last pane's verdict, not this one's
2301
2356
  if (this._trustDialogTimer) {
2302
2357
  clearTimeout(this._trustDialogTimer);
2303
2358
  this._trustDialogTimer = null;
@@ -2411,6 +2466,7 @@ export class Session extends EventEmitter {
2411
2466
  antigravityConfig: this._antigravityConfig,
2412
2467
  piConfig: this._piConfig,
2413
2468
  grokConfig: this._grokConfig,
2469
+ copilotConfig: this._copilotConfig,
2414
2470
  deepSeekConfig: this._deepSeekConfig,
2415
2471
  ompConfig: this._ompConfig,
2416
2472
  resumeSessionId: this._resumeSessionId,
@@ -2644,6 +2700,10 @@ export class Session extends EventEmitter {
2644
2700
  this._expensiveProcessTimer = null;
2645
2701
  }
2646
2702
  this._pendingCleanData = '';
2703
+ // Nothing reads this pane any more, so a dialog on it can no longer be seen to
2704
+ // close: drop the state (and its timer) while the listeners are still attached.
2705
+ // A re-attach re-reads the screen and raises it again if the dialog is still up.
2706
+ this._clearScreenBlocked();
2647
2707
  // If using mux, mark the session as detached but don't kill it
2648
2708
  if (this._muxSession && this._mux) {
2649
2709
  this._mux.setAttached(this.id, false);
@@ -2701,15 +2761,26 @@ export class Session extends EventEmitter {
2701
2761
  * a two-marker match (isTrustDialogScreen), and an attempt cap.
2702
2762
  */
2703
2763
  _maybeAcceptTrustDialog() {
2704
- if (this._trustDialogAccepted)
2764
+ // Stopped: the kill itself produces PTY output, and the buffer tail still holds the
2765
+ // dialog, so without this a deleted session kept "answering" it for several seconds.
2766
+ if (this._trustDialogAccepted || this._isStopped)
2705
2767
  return;
2706
2768
  const now = Date.now();
2707
2769
  if (now - this._interactiveStartedAt > TRUST_DIALOG_WINDOW_MS) {
2708
2770
  this._trustDialogAccepted = true; // window closed; anything matching now is not the dialog
2709
2771
  return;
2710
2772
  }
2711
- if (now - this._lastTrustDialogScanAt < TRUST_DIALOG_RETRY_MS)
2773
+ const sinceScan = now - this._lastTrustDialogScanAt;
2774
+ if (sinceScan < TRUST_DIALOG_RETRY_MS) {
2775
+ // ⚠️ Defer a throttled read, never drop it. The dialog's own paint is usually the
2776
+ // last output a launching pane produces, and it tends to land inside the throttle
2777
+ // of the read the blank screen before it triggered: dropped, nothing re-read the
2778
+ // screen and the session sat on the dialog for good (2 of 12 fresh launches on
2779
+ // Claude Code 2.1.294). A read already scheduled covers this one.
2780
+ if (!this._trustDialogTimer)
2781
+ this._rescanTrustDialogIn(TRUST_DIALOG_RETRY_MS - sinceScan + 100);
2712
2782
  return;
2783
+ }
2713
2784
  this._lastTrustDialogScanAt = now;
2714
2785
  // Prefer the pane; fall back to the buffer tail on a direct-PTY session,
2715
2786
  // where there is no screen to read.
@@ -2739,15 +2810,17 @@ export class Session extends EventEmitter {
2739
2810
  // on a live 2.1.252 spawn: cursor moved at 6 s, then nothing). The timer is
2740
2811
  // one-shot and self-rearming through this same path, and every exit route
2741
2812
  // goes through _clearAllTimers().
2742
- // The +100ms puts the re-entry OUTSIDE the scan throttle above; firing at
2743
- // exactly the throttle boundary would let the scan return early and break
2744
- // the chain with the dialog still on screen.
2813
+ // The +100ms puts the re-entry OUTSIDE the scan throttle above.
2814
+ this._rescanTrustDialogIn(TRUST_DIALOG_RETRY_MS + 100);
2815
+ }
2816
+ /** Re-run the trust-dialog scan after `delayMs`, replacing any read already scheduled. */
2817
+ _rescanTrustDialogIn(delayMs) {
2745
2818
  if (this._trustDialogTimer)
2746
2819
  clearTimeout(this._trustDialogTimer);
2747
2820
  this._trustDialogTimer = setTimeout(() => {
2748
2821
  this._trustDialogTimer = null;
2749
2822
  this._maybeAcceptTrustDialog();
2750
- }, TRUST_DIALOG_RETRY_MS + 100);
2823
+ }, delayMs);
2751
2824
  }
2752
2825
  /**
2753
2826
  * Per-chunk working/idle detection for an interactive pane. Split out of the
@@ -2821,10 +2894,29 @@ export class Session extends EventEmitter {
2821
2894
  return this._lastPaneProbeWorking;
2822
2895
  this._lastPaneProbeAt = now;
2823
2896
  const text = this._mux.capturePaneText?.(this._muxSession.muxName) ?? null;
2897
+ // A dialog the CLI is blocked on is not work, whatever else is on screen: codex's
2898
+ // question dialog carries `esc to interrupt` in its own key hints, which is codex's
2899
+ // working line, so without this a session waiting on the user's answer read as busy.
2900
+ // Decided from THIS capture, never from `_screenBlocked`, which lags a vanished dialog
2901
+ // by design (see `_noteBlockedReading`) and would otherwise call a resumed turn idle.
2902
+ const blockedOnScreen = text === null ? null : this._paneShowsBlocked(text);
2903
+ this._noteBlockedReading(blockedOnScreen, text);
2824
2904
  // A turn that ended by handing off to workers the CLI waits for is work too: the
2825
2905
  // composer is up and the pane is quiet, but the next turn starts without the user.
2826
2906
  this._lastPaneProbeWorking =
2827
- text === null ? null : this._workingLinePattern().test(text) || this._paneAwaitsWorkers(text);
2907
+ text === null
2908
+ ? null
2909
+ : !blockedOnScreen && (this._workingLinePattern().test(text) || this._paneAwaitsWorkers(text));
2910
+ // A capture that failed proves nothing either way, so it keeps the last answer.
2911
+ if (text !== null) {
2912
+ const trustDialog = isTrustDialogScreen(text);
2913
+ if (trustDialog &&
2914
+ !this._lastPaneProbeTrustDialog &&
2915
+ now - this._interactiveStartedAt <= TRUST_DIALOG_WINDOW_MS) {
2916
+ console.log(`[Session] Holding idle while the workspace trust dialog is on screen: ${this.id}`);
2917
+ }
2918
+ this._lastPaneProbeTrustDialog = trustDialog;
2919
+ }
2828
2920
  this._readWatching(text);
2829
2921
  this._readScreenModel(text);
2830
2922
  return this._lastPaneProbeWorking;
@@ -2913,6 +3005,7 @@ export class Session extends EventEmitter {
2913
3005
  antigravityConfig: this._antigravityConfig,
2914
3006
  piConfig: this._piConfig,
2915
3007
  grokConfig: this._grokConfig,
3008
+ copilotConfig: this._copilotConfig,
2916
3009
  deepSeekConfig: this._deepSeekConfig,
2917
3010
  ompConfig: this._ompConfig,
2918
3011
  };
@@ -3016,6 +3109,109 @@ export class Session extends EventEmitter {
3016
3109
  const glyph = getCli(this.mode)?.capabilities.workDetect?.promptGlyph ?? '❯';
3017
3110
  return isAwaitingWorkers(paneText, this._awaitingLineRe, glyph);
3018
3111
  }
3112
+ /**
3113
+ * The regex matching the dialog this CLI blocks on, or null for a CLI whose registry
3114
+ * entry declares none (every CLI but codex today, Claude included: its dialogs arrive
3115
+ * as hook events). Compiled once per session through `compileVersionRegex()`, the same
3116
+ * guard the schema applies, so a pattern it refuses yields null rather than a throw.
3117
+ */
3118
+ _blockedLinePattern() {
3119
+ if (this._blockedLineRe === undefined) {
3120
+ const detect = getCli(this.mode)?.capabilities.workDetect;
3121
+ this._blockedLineRe = detect?.blockedLine ? compileVersionRegex(detect.blockedLine) : null;
3122
+ this._blockedWindow = detect?.blockedLines ?? BLOCKED_TAIL_LINES;
3123
+ }
3124
+ return this._blockedLineRe;
3125
+ }
3126
+ /** Whether this capture shows a dialog the CLI is blocked on. See `isBlockedScreen()`. */
3127
+ _paneShowsBlocked(paneText) {
3128
+ const pattern = this._blockedLinePattern();
3129
+ return pattern ? isBlockedScreen(paneText, pattern, this._blockedWindow) : false;
3130
+ }
3131
+ /**
3132
+ * Fold one screen reading into the blocked state, announcing each transition with
3133
+ * `blockedChanged` (the web server turns it into the alert and the `blocked` signal).
3134
+ *
3135
+ * The two directions are deliberately unequal:
3136
+ * - RAISE on one reading. A match needs the dialog's own footer at the foot of the
3137
+ * screen, which is only there when the dialog is fully drawn.
3138
+ * - CLEAR on BLOCKED_CLEAR_READS readings in a row. A single capture can land inside a
3139
+ * full-frame repaint, and clearing on it would re-raise a moment later and send the
3140
+ * alert, push and webhook twice for one dialog.
3141
+ * A capture that could not be read (`null`) is no evidence either way and changes
3142
+ * nothing, so a tmux hiccup can neither raise nor drop an alert. That is the opposite
3143
+ * of `_readWatching`, and for the same reason: each degrades toward the alert.
3144
+ *
3145
+ * While blocked, `_blockedRecheckTimer` keeps reading, since answering resumes the turn
3146
+ * and a busy pane never reaches the settle path that raised it.
3147
+ *
3148
+ * @param frame the capture the reading came from; rides the raise as the alert's context
3149
+ */
3150
+ _noteBlockedReading(onScreen, frame) {
3151
+ if (onScreen === null || !this._blockedLinePattern())
3152
+ return;
3153
+ if (onScreen) {
3154
+ this._blockedGoneReads = 0;
3155
+ // Stamped after the capture returned: output processed later is newer than it.
3156
+ this._blockedSeenAt = Date.now();
3157
+ if (!this._screenBlocked) {
3158
+ this._screenBlocked = true;
3159
+ this.emit('blockedChanged', true, frame);
3160
+ }
3161
+ this._armBlockedRecheck();
3162
+ return;
3163
+ }
3164
+ if (!this._screenBlocked)
3165
+ return;
3166
+ if (++this._blockedGoneReads < BLOCKED_CLEAR_READS) {
3167
+ this._armBlockedRecheck();
3168
+ return;
3169
+ }
3170
+ this._clearScreenBlocked();
3171
+ }
3172
+ /** (Re)arm the while-blocked screen read. One-shot and self-rearming, like the trust scan. */
3173
+ _armBlockedRecheck() {
3174
+ if (this._blockedRecheckTimer)
3175
+ clearTimeout(this._blockedRecheckTimer);
3176
+ this._blockedRecheckTimer = setTimeout(() => {
3177
+ this._blockedRecheckTimer = null;
3178
+ // No pane left to read (detached): nothing can change until a re-attach, whose
3179
+ // `_resetBuffers()` clears the state and whose repaint reads the screen afresh.
3180
+ if (this._isStopped || !this._screenBlocked || !this._mux?.capturePaneText || !this._muxSession)
3181
+ return;
3182
+ // A dialog leaves the screen only through a repaint, and tmux sends every repaint
3183
+ // to this client as PTY output (`_markActivity`). So while the last reading saw
3184
+ // the dialog and nothing has been drawn since, it is still up: skip the capture
3185
+ // (synchronous, see BLOCKED_QUIET_RECHECK_MS) and look again next interval. Never
3186
+ // once a reading has missed it: the confirming read must happen on a quiet pane
3187
+ // too, or the clear would wait for output that may never come.
3188
+ if (this._blockedGoneReads === 0 &&
3189
+ this._lastActivityAt < this._blockedSeenAt &&
3190
+ Date.now() - this._blockedSeenAt < BLOCKED_QUIET_RECHECK_MS) {
3191
+ this._armBlockedRecheck();
3192
+ return;
3193
+ }
3194
+ const text = this._mux.capturePaneText(this._muxSession.muxName) ?? null;
3195
+ // Unreadable this time: keep the state, keep looking (_noteBlockedReading ignores a null).
3196
+ if (text === null) {
3197
+ this._armBlockedRecheck();
3198
+ return;
3199
+ }
3200
+ this._noteBlockedReading(this._paneShowsBlocked(text), text);
3201
+ }, BLOCKED_RECHECK_MS);
3202
+ }
3203
+ /** Drop the blocked state (and its timer), announcing it when it was set. */
3204
+ _clearScreenBlocked() {
3205
+ this._blockedGoneReads = 0;
3206
+ if (this._blockedRecheckTimer) {
3207
+ clearTimeout(this._blockedRecheckTimer);
3208
+ this._blockedRecheckTimer = null;
3209
+ }
3210
+ if (!this._screenBlocked)
3211
+ return;
3212
+ this._screenBlocked = false;
3213
+ this.emit('blockedChanged', false, null);
3214
+ }
3019
3215
  /**
3020
3216
  * The regex matching this CLI's "a turn is running" status line.
3021
3217
  *
@@ -3076,6 +3272,16 @@ export class Session extends EventEmitter {
3076
3272
  this.activityTimeout = setTimeout(() => this._confirmIdle(), PANE_PROBE_RECHECK_MS);
3077
3273
  return;
3078
3274
  }
3275
+ // ⚠️ The workspace-trust dialog is not a composer, although its `❯` (on "No, exit")
3276
+ // arms this check and the dialog sits quiet. Calling it idle let an agent that waited
3277
+ // for idle send its prompt into the dialog, and the prompt's Enter answered *exit*.
3278
+ // Not working either, so the status stays where it is until the dialog is gone.
3279
+ // Bounded by the auto-accept's own startup window: later on, a screen that merely
3280
+ // quotes the dialog (an agent reading session-trust-dialog.ts) must not hold idle off.
3281
+ if (this._lastPaneProbeTrustDialog && Date.now() - this._interactiveStartedAt <= TRUST_DIALOG_WINDOW_MS) {
3282
+ this.activityTimeout = setTimeout(() => this._confirmIdle(), PANE_PROBE_RECHECK_MS);
3283
+ return;
3284
+ }
3079
3285
  this._awaitingIdleConfirmation = false;
3080
3286
  this.activityTimeout = null;
3081
3287
  // Emit idle if either:
@@ -3515,6 +3721,9 @@ export class Session extends EventEmitter {
3515
3721
  // into a program that never enabled tracking.
3516
3722
  this._cliMouseModes.clear();
3517
3723
  this._syncCliMouseTracking();
3724
+ // Nor does it start inside a dialog. Announced, so a pending alert for the old
3725
+ // pane's dialog is resolved rather than left behind it.
3726
+ this._clearScreenBlocked();
3518
3727
  this._markActivity(true);
3519
3728
  }
3520
3729
  _clearAllTimers() {
@@ -3526,6 +3735,11 @@ export class Session extends EventEmitter {
3526
3735
  clearTimeout(this._trustDialogTimer);
3527
3736
  this._trustDialogTimer = null;
3528
3737
  }
3738
+ // Clear the while-blocked screen read (the state itself is left to its owner)
3739
+ if (this._blockedRecheckTimer) {
3740
+ clearTimeout(this._blockedRecheckTimer);
3741
+ this._blockedRecheckTimer = null;
3742
+ }
3529
3743
  // Clear activity timeout to prevent memory leak
3530
3744
  if (this.activityTimeout) {
3531
3745
  clearTimeout(this.activityTimeout);
@@ -3981,7 +4195,7 @@ export class Session extends EventEmitter {
3981
4195
  *
3982
4196
  * More reliable than direct PTY write for programmatic input, especially
3983
4197
  * with Claude CLI which uses Ink (React for terminals).
3984
- * Uses tmux `send-keys -l` to inject text + Enter.
4198
+ * Uses tmux to inject the text (one bracketed paste) + Enter.
3985
4199
  *
3986
4200
  * @param data - Input data with optional `\r` for Enter
3987
4201
  * @returns true if input was sent, false if no mux session or PTY
@@ -4037,7 +4251,7 @@ export class Session extends EventEmitter {
4037
4251
  : this._mux.capturePaneText?.(this._muxSession.muxName),
4038
4252
  sendEnter: () => this._mux?.sendInput(this.id, '\r'),
4039
4253
  // ⚠ NO fallback glyph here, unlike the screen-reading probe elsewhere in this file.
4040
- // Only claude, codex, pi, opencode, omp and gemini declare a promptGlyph; the other modes would fall back
4254
+ // Only claude, codex, pi, opencode, omp, gemini and antigravity declare a promptGlyph; the other modes would fall back
4041
4255
  // to claude's `❯`, which is ALSO starship's default shell prompt (and pure's, and
4042
4256
  // spaceship's, and p10k lean's). On a shell session the line `❯ npm run build` sits
4043
4257
  // on screen for as long as the command runs, promptStillInComposer() reads that as