aicodeman 1.35.0 → 1.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -7
- package/dist/attachment-registry.d.ts +6 -0
- package/dist/attachment-registry.d.ts.map +1 -1
- package/dist/attachment-registry.js +9 -3
- package/dist/attachment-registry.js.map +1 -1
- package/dist/cli-agent.d.ts +225 -0
- package/dist/cli-agent.d.ts.map +1 -0
- package/dist/cli-agent.js +767 -0
- package/dist/cli-agent.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +8 -32
- package/dist/cli.js.map +1 -1
- package/dist/codeman-credentials.d.ts +27 -0
- package/dist/codeman-credentials.d.ts.map +1 -0
- package/dist/codeman-credentials.js +66 -0
- package/dist/codeman-credentials.js.map +1 -0
- package/dist/config/cli-registry/patterns.d.ts +6 -0
- package/dist/config/cli-registry/patterns.d.ts.map +1 -1
- package/dist/config/cli-registry/patterns.js +13 -0
- package/dist/config/cli-registry/patterns.js.map +1 -1
- package/dist/config/cli-registry/schema.d.ts +15 -0
- package/dist/config/cli-registry/schema.d.ts.map +1 -1
- package/dist/config/cli-registry/schema.js +63 -2
- package/dist/config/cli-registry/schema.js.map +1 -1
- package/dist/config/cli-registry/stock.d.ts.map +1 -1
- package/dist/config/cli-registry/stock.js +180 -7
- package/dist/config/cli-registry/stock.js.map +1 -1
- package/dist/config/cli-registry/types.d.ts +105 -7
- package/dist/config/cli-registry/types.d.ts.map +1 -1
- package/dist/deepseek-route-config.d.ts +110 -0
- package/dist/deepseek-route-config.d.ts.map +1 -0
- package/dist/deepseek-route-config.js +477 -0
- package/dist/deepseek-route-config.js.map +1 -0
- package/dist/git-clone.d.ts.map +1 -1
- package/dist/git-clone.js +4 -0
- package/dist/git-clone.js.map +1 -1
- package/dist/git-workspace-status.d.ts +31 -5
- package/dist/git-workspace-status.d.ts.map +1 -1
- package/dist/git-workspace-status.js +49 -29
- package/dist/git-workspace-status.js.map +1 -1
- package/dist/image-watcher.d.ts.map +1 -1
- package/dist/image-watcher.js +6 -2
- package/dist/image-watcher.js.map +1 -1
- package/dist/mcp-sync-targets.d.ts +34 -0
- package/dist/mcp-sync-targets.d.ts.map +1 -0
- package/dist/mcp-sync-targets.js +55 -0
- package/dist/mcp-sync-targets.js.map +1 -0
- package/dist/mcp-sync.d.ts +2 -1
- package/dist/mcp-sync.d.ts.map +1 -1
- package/dist/mcp-sync.js +62 -1
- package/dist/mcp-sync.js.map +1 -1
- package/dist/model-config-resolvers.d.ts +29 -0
- package/dist/model-config-resolvers.d.ts.map +1 -0
- package/dist/model-config-resolvers.js +34 -0
- package/dist/model-config-resolvers.js.map +1 -0
- package/dist/session-display-model.d.ts +110 -0
- package/dist/session-display-model.d.ts.map +1 -0
- package/dist/session-display-model.js +154 -0
- package/dist/session-display-model.js.map +1 -0
- package/dist/session.d.ts +145 -8
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +290 -39
- package/dist/session.js.map +1 -1
- package/dist/subagent-watcher.d.ts.map +1 -1
- package/dist/subagent-watcher.js +29 -6
- package/dist/subagent-watcher.js.map +1 -1
- package/dist/templates/case-template.md +2 -0
- package/dist/tmux-manager.d.ts +5 -0
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +47 -22
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tui/tui-client.d.ts +3 -17
- package/dist/tui/tui-client.d.ts.map +1 -1
- package/dist/tui/tui-client.js +3 -44
- package/dist/tui/tui-client.js.map +1 -1
- package/dist/types/session.d.ts +28 -1
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/types/tools.d.ts +1 -1
- package/dist/types/tools.d.ts.map +1 -1
- package/dist/utils/deepseek-cli-resolver.d.ts +8 -0
- package/dist/utils/deepseek-cli-resolver.d.ts.map +1 -1
- package/dist/utils/deepseek-cli-resolver.js +18 -2
- package/dist/utils/deepseek-cli-resolver.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/process-exit-wait.d.ts +40 -0
- package/dist/utils/process-exit-wait.d.ts.map +1 -0
- package/dist/utils/process-exit-wait.js +68 -0
- package/dist/utils/process-exit-wait.js.map +1 -0
- package/dist/web/launch-defaults.d.ts +22 -0
- package/dist/web/launch-defaults.d.ts.map +1 -0
- package/dist/web/launch-defaults.js +46 -0
- package/dist/web/launch-defaults.js.map +1 -0
- package/dist/web/paste-image-gc.d.ts +46 -6
- package/dist/web/paste-image-gc.d.ts.map +1 -1
- package/dist/web/paste-image-gc.js +106 -32
- package/dist/web/paste-image-gc.js.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.6f8552e1.js +43 -0
- package/dist/web/public/app.6f8552e1.js.br +0 -0
- package/dist/web/public/app.6f8552e1.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.d95fade9.js → constants.bc9c1f63.js} +1276 -127
- package/dist/web/public/constants.bc9c1f63.js.br +0 -0
- package/dist/web/public/constants.bc9c1f63.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js +491 -43
- package/dist/web/public/entrance-animations.js.br +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/git-status-ui.js +55 -9
- package/dist/web/public/git-status-ui.js.br +0 -0
- package/dist/web/public/git-status-ui.js.gz +0 -0
- package/dist/web/public/home-sessions.js +13 -9
- package/dist/web/public/home-sessions.js.br +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/host-wake-ui.js.gz +0 -0
- package/dist/web/public/i18n.a9840ea2.js +2 -0
- package/dist/web/public/i18n.a9840ea2.js.br +0 -0
- package/dist/web/public/i18n.a9840ea2.js.gz +0 -0
- package/dist/web/public/{image-input.a216cf9e.js → image-input.e00471c6.js} +21 -9
- package/dist/web/public/image-input.e00471c6.js.br +0 -0
- package/dist/web/public/image-input.e00471c6.js.gz +0 -0
- package/dist/web/public/index.html +250 -59
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.c1e7c7b7.js.gz +0 -0
- package/dist/web/public/{mobile-handlers.23a85db1.js → mobile-handlers.0ef9c311.js} +20 -0
- package/dist/web/public/mobile-handlers.0ef9c311.js.br +0 -0
- package/dist/web/public/mobile-handlers.0ef9c311.js.gz +0 -0
- package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.111b187d.css +1 -0
- package/dist/web/public/mobile.111b187d.css.br +0 -0
- package/dist/web/public/mobile.111b187d.css.gz +0 -0
- package/dist/web/public/{notification-manager.50bb87d8.js → notification-manager.feb82c04.js} +19 -2
- package/dist/web/public/notification-manager.feb82c04.js.br +0 -0
- package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.6a5898d9.js → panels-ui.71846fcb.js} +4 -4
- package/dist/web/public/panels-ui.71846fcb.js.br +0 -0
- package/dist/web/public/panels-ui.71846fcb.js.gz +0 -0
- package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
- package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/reboot-restore-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
- package/dist/web/public/session-lineage.js +229 -86
- package/dist/web/public/session-lineage.js.br +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/session-ui.f731602c.js +100 -0
- package/dist/web/public/session-ui.f731602c.js.br +0 -0
- package/dist/web/public/session-ui.f731602c.js.gz +0 -0
- package/dist/web/public/settings-ui.4e33390f.js +97 -0
- package/dist/web/public/settings-ui.4e33390f.js.br +0 -0
- package/dist/web/public/settings-ui.4e33390f.js.gz +0 -0
- package/dist/web/public/spreadsheet-preview-worker.js +290 -0
- package/dist/web/public/spreadsheet-preview-worker.js.br +0 -0
- package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
- package/dist/web/public/spreadsheet-preview.af82fa2f.js +555 -0
- package/dist/web/public/spreadsheet-preview.af82fa2f.js.br +0 -0
- package/dist/web/public/spreadsheet-preview.af82fa2f.js.gz +0 -0
- package/dist/web/public/spreadsheet-xlsx-core.js +1006 -0
- package/dist/web/public/spreadsheet-xlsx-core.js.br +0 -0
- package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
- package/dist/web/public/styles.81a72906.css +1 -0
- package/dist/web/public/styles.81a72906.css.br +0 -0
- package/dist/web/public/styles.81a72906.css.gz +0 -0
- package/dist/web/public/{subagent-windows.e6ca799f.js → subagent-windows.07277a58.js} +7 -5
- package/dist/web/public/subagent-windows.07277a58.js.br +0 -0
- package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
- package/dist/web/public/sw.js +2 -2
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-layout-browser.065fcba4.js.gz +0 -0
- package/dist/web/public/{tab-rail-resize.94cd59f0.js → tab-rail-resize.a5867793.js} +1 -1
- package/dist/web/public/tab-rail-resize.a5867793.js.br +0 -0
- package/dist/web/public/{tab-rail-resize.94cd59f0.js.gz → tab-rail-resize.a5867793.js.gz} +0 -0
- package/dist/web/public/terminal-keycode229-recovery.af09f99a.js +1 -0
- package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.br +0 -0
- package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
- package/dist/web/public/terminal-split.js +146 -649
- package/dist/web/public/terminal-split.js.br +0 -0
- package/dist/web/public/terminal-split.js.gz +0 -0
- package/dist/web/public/terminal-tile.js +1688 -0
- package/dist/web/public/terminal-tile.js.br +0 -0
- package/dist/web/public/terminal-tile.js.gz +0 -0
- package/dist/web/public/terminal-ui.b7267005.js +2 -0
- package/dist/web/public/terminal-ui.b7267005.js.br +0 -0
- package/dist/web/public/terminal-ui.b7267005.js.gz +0 -0
- package/dist/web/public/tile-grid.js +2895 -0
- package/dist/web/public/tile-grid.js.br +0 -0
- package/dist/web/public/tile-grid.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js +9 -6
- package/dist/web/public/ultracode-windows.js.br +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/exceljs.min.js +44 -0
- package/dist/web/public/vendor/exceljs.min.js.br +0 -0
- package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
- package/dist/web/public/vendor/fflate.min.js +1 -0
- package/dist/web/public/vendor/fflate.min.js.br +0 -0
- package/dist/web/public/vendor/fflate.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js +4 -0
- package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.br +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/{voice-input.c4b51eb6.js → voice-input.2342c37b.js} +64 -10
- package/dist/web/public/voice-input.2342c37b.js.br +0 -0
- package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js +9 -1
- package/dist/web/public/webview-tabs.js.br +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/cli-registry-routes.d.ts +23 -4
- package/dist/web/routes/cli-registry-routes.d.ts.map +1 -1
- package/dist/web/routes/cli-registry-routes.js +95 -8
- package/dist/web/routes/cli-registry-routes.js.map +1 -1
- package/dist/web/routes/file-routes.d.ts +7 -0
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +34 -4
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/routes/git-status-routes.d.ts.map +1 -1
- package/dist/web/routes/git-status-routes.js +33 -6
- package/dist/web/routes/git-status-routes.js.map +1 -1
- package/dist/web/routes/mcp-sync-routes.d.ts +7 -2
- package/dist/web/routes/mcp-sync-routes.d.ts.map +1 -1
- package/dist/web/routes/mcp-sync-routes.js +10 -3
- package/dist/web/routes/mcp-sync-routes.js.map +1 -1
- package/dist/web/routes/reboot-restore-routes.js +1 -1
- package/dist/web/routes/reboot-restore-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts +18 -0
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +154 -69
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/status-telemetry-routes.d.ts +3 -0
- package/dist/web/routes/status-telemetry-routes.d.ts.map +1 -1
- package/dist/web/routes/status-telemetry-routes.js +12 -0
- package/dist/web/routes/status-telemetry-routes.js.map +1 -1
- package/dist/web/routes/system-routes.d.ts.map +1 -1
- package/dist/web/routes/system-routes.js +13 -0
- package/dist/web/routes/system-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +30 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +43 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +6 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +47 -21
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-listener-wiring.d.ts +1 -0
- package/dist/web/session-listener-wiring.d.ts.map +1 -1
- package/dist/web/session-listener-wiring.js +11 -0
- package/dist/web/session-listener-wiring.js.map +1 -1
- package/package.json +6 -4
- package/scripts/postinstall.js +16 -0
- package/skills/codeman/SKILL.md +6 -0
- package/README.zh-CN.md +0 -1139
- package/dist/web/public/app.b9516c7b.js +0 -43
- package/dist/web/public/app.b9516c7b.js.br +0 -0
- package/dist/web/public/app.b9516c7b.js.gz +0 -0
- package/dist/web/public/constants.d95fade9.js.br +0 -0
- package/dist/web/public/constants.d95fade9.js.gz +0 -0
- package/dist/web/public/i18n.52f5b7cc.js +0 -1
- package/dist/web/public/i18n.52f5b7cc.js.br +0 -0
- package/dist/web/public/i18n.52f5b7cc.js.gz +0 -0
- package/dist/web/public/image-input.a216cf9e.js.br +0 -0
- package/dist/web/public/image-input.a216cf9e.js.gz +0 -0
- package/dist/web/public/mobile-handlers.23a85db1.js.br +0 -0
- package/dist/web/public/mobile-handlers.23a85db1.js.gz +0 -0
- package/dist/web/public/mobile.1aa4952d.css +0 -1
- package/dist/web/public/mobile.1aa4952d.css.br +0 -0
- package/dist/web/public/mobile.1aa4952d.css.gz +0 -0
- package/dist/web/public/notification-manager.50bb87d8.js.br +0 -0
- package/dist/web/public/notification-manager.50bb87d8.js.gz +0 -0
- package/dist/web/public/panels-ui.6a5898d9.js.br +0 -0
- package/dist/web/public/panels-ui.6a5898d9.js.gz +0 -0
- package/dist/web/public/session-ui.6bddf916.js +0 -89
- package/dist/web/public/session-ui.6bddf916.js.br +0 -0
- package/dist/web/public/session-ui.6bddf916.js.gz +0 -0
- package/dist/web/public/settings-ui.6e798b7b.js +0 -97
- package/dist/web/public/settings-ui.6e798b7b.js.br +0 -0
- package/dist/web/public/settings-ui.6e798b7b.js.gz +0 -0
- package/dist/web/public/styles.2d175c60.css +0 -1
- package/dist/web/public/styles.2d175c60.css.br +0 -0
- package/dist/web/public/styles.2d175c60.css.gz +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.br +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.94cd59f0.js.br +0 -0
- package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js +0 -1
- package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.br +0 -0
- package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.gz +0 -0
- package/dist/web/public/terminal-ui.a4535311.js +0 -2
- package/dist/web/public/terminal-ui.a4535311.js.br +0 -0
- package/dist/web/public/terminal-ui.a4535311.js.gz +0 -0
- package/dist/web/public/upload.html +0 -155
- package/dist/web/public/upload.html.br +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js +0 -4
- package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js.br +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js.gz +0 -0
- package/dist/web/public/voice-input.c4b51eb6.js.br +0 -0
- package/dist/web/public/voice-input.c4b51eb6.js.gz +0 -0
package/dist/session.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* @module session
|
|
29
29
|
*/
|
|
30
30
|
import { EventEmitter } from 'node:events';
|
|
31
|
+
import { basename } from 'node:path';
|
|
31
32
|
import { execSync, execFileSync } from 'node:child_process';
|
|
32
33
|
import { v4 as uuidv4 } from 'uuid';
|
|
33
34
|
import * as pty from 'node-pty';
|
|
@@ -42,7 +43,7 @@ import { RalphTracker } from './ralph-tracker.js';
|
|
|
42
43
|
import { BashToolParser } from './bash-tool-parser.js';
|
|
43
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';
|
|
44
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 { BufferAccumulator, ANSI_ESCAPE_PATTERN_FULL, TOKEN_PATTERN, SPINNER_PATTERN, CLAUDE_WORKING_LINE_PATTERN, MAX_SESSION_TOKENS, execPattern, getClaudeCliVersion, getClaudeBinaryPath, spawnPtyWithHelperRepair, resolveLocalShell, } from './utils/index.js';
|
|
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';
|
|
46
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';
|
|
47
48
|
import { DEFAULT_TMUX_HISTORY_LIMIT } from './config/terminal-history.js';
|
|
48
49
|
import { EXEC_TIMEOUT_MS } from './config/exec-timeout.js';
|
|
@@ -58,6 +59,9 @@ import { InteractivePtyExitBreaker } from './session-pty-exit-breaker.js';
|
|
|
58
59
|
import { isGeneratedSessionName, SubmittedPromptTracker } from './session-auto-name.js';
|
|
59
60
|
import { parseTerminalAttachmentRequests } from './attachment-magic.js';
|
|
60
61
|
import { sanitizeAttachmentHistory, upsertAttachmentHistory as upsertAttachmentHistoryList, } from './session-attachment-history.js';
|
|
62
|
+
import { resolveConfigModel } from './model-config-resolvers.js';
|
|
63
|
+
import { legacyConfigForMode } from './session-cli-registry-bridge.js';
|
|
64
|
+
import { launchModelFor, readScreenModel, resolveDisplayModel, restoredReportedModel, sanitizeModelName, } from './session-display-model.js';
|
|
61
65
|
/** Line buffer flush interval (100ms) - forces processing of partial lines */
|
|
62
66
|
const LINE_BUFFER_FLUSH_INTERVAL = 100;
|
|
63
67
|
// ============================================================================
|
|
@@ -75,7 +79,10 @@ const IDLE_DETECTION_DELAY_MS = 2000;
|
|
|
75
79
|
// (input, task assignment, respawn) always stamp through it.
|
|
76
80
|
const WIRE_ACTIVITY_SETTLE_MS = 15_000;
|
|
77
81
|
// Note: Auto-compact/clear timing constants moved to session-auto-ops.ts
|
|
78
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Longest the PTY process gets to exit on SIGTERM before SIGKILL when stopping a
|
|
84
|
+
* session. A deadline, not a sleep: stop() moves on as soon as it has exited.
|
|
85
|
+
*/
|
|
79
86
|
const GRACEFUL_SHUTDOWN_DELAY_MS = 100;
|
|
80
87
|
// Conversations kept in a pane's chain. A pane that /clears repeatedly would
|
|
81
88
|
// otherwise grow state.json without bound; 32 covers any real session's history
|
|
@@ -163,10 +170,11 @@ function cliExportsTruecolor(mode) {
|
|
|
163
170
|
* Codex, Claude Code, and Gemini are known, controlled (Ink/React) TUIs that
|
|
164
171
|
* repaint via cursor positioning, so dropping the alt-screen switch is safe —
|
|
165
172
|
* content stays in the normal buffer. Excluded: `shell` (arbitrary programs like
|
|
166
|
-
* vim/less/htop legitimately need the alt screen), `opencode` (
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
173
|
+
* vim/less/htop legitimately need the alt screen), `opencode` (its own MIDDLE strip,
|
|
174
|
+
* isMuxMouseStripMode), `pi` (below) and `grok` (a fullscreen alt-screen TUI with
|
|
175
|
+
* mouse support). Keep parity with the replay-side strip (`stripReplayBuffer` in
|
|
176
|
+
* session-routes.ts); the table in `CliCapabilities.altScreen` is pinned for every
|
|
177
|
+
* stock CLI in test/claude-scrollback-strip.test.ts.
|
|
170
178
|
*
|
|
171
179
|
* ⚠️ Being excluded here does NOT preserve the alt screen. Every excluded mode
|
|
172
180
|
* falls through to isMuxAltScreenOnlyStripMode(), which strips the alt-screen
|
|
@@ -185,8 +193,10 @@ export function isAltScreenStripMode(mode) {
|
|
|
185
193
|
}
|
|
186
194
|
/**
|
|
187
195
|
* Modes that need the NARROW strip: alt-screen toggles only, leaving `\x1b[3J`
|
|
188
|
-
* and the mouse-tracking DECSETs alone. Applies to every mode
|
|
189
|
-
*
|
|
196
|
+
* and the mouse-tracking DECSETs alone. Applies to every mode that is neither
|
|
197
|
+
* `strip-full` (isAltScreenStripMode) nor `strip-mux-and-mouse` (isMuxMouseStripMode),
|
|
198
|
+
* so `strip-mux-only` and `preserve` alike, but ONLY when the session is tmux-backed
|
|
199
|
+
* (`useMux`).
|
|
190
200
|
*
|
|
191
201
|
* The bug (issue #205): the tmux CLIENT emits `smcup` (`\x1b[?1049h`) as its first
|
|
192
202
|
* bytes on attach, before any program has run. Unstripped, xterm.js parks in the
|
|
@@ -211,7 +221,31 @@ export function isAltScreenStripMode(mode) {
|
|
|
211
221
|
* `\x1b[3J` from a user's own `clear` is a deliberate "wipe my scrollback".
|
|
212
222
|
*/
|
|
213
223
|
export function isMuxAltScreenOnlyStripMode(mode, useMux) {
|
|
214
|
-
|
|
224
|
+
if (!useMux)
|
|
225
|
+
return false;
|
|
226
|
+
const altScreen = getCli(mode)?.capabilities.altScreen;
|
|
227
|
+
return altScreen !== 'strip-full' && altScreen !== 'strip-mux-and-mouse';
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Modes whose mouse-tracking DECSETs must be stripped, leaving `3J` alone:
|
|
231
|
+
* `altScreen: 'strip-mux-and-mouse'`, i.e. a mouse-capable full-screen TUI.
|
|
232
|
+
*
|
|
233
|
+
* Why this exists (opencode, measured 2026-09-16): the TUI enables tracking
|
|
234
|
+
* DECSETs, tmux runs with `mouse off` and therefore passes the PANE's DECSETs
|
|
235
|
+
* straight through to the tmux client, and the browser's xterm obeyed them —
|
|
236
|
+
* `mouseTrackingMode` flipped to `'any'` and xterm then reported DRAGS to the TUI
|
|
237
|
+
* instead of selecting locally. "Mark text, copy on select" silently did nothing
|
|
238
|
+
* (measured 62 `none` / 18 `any` over 16s, and 5/5 dead drags while `any`), and
|
|
239
|
+
* the obvious fallback — Ctrl+C — is opencode's `app_exit`, so the failure also
|
|
240
|
+
* ended sessions. Stripping at the source keeps xterm in selection mode; clicks
|
|
241
|
+
* still reach the CLI through the browser's hand-encoded tap, which this strip
|
|
242
|
+
* publishes as `cliMouseTracking` (`_recordStrippedMouseMode`).
|
|
243
|
+
*
|
|
244
|
+
* Gated on `useMux` for the same reason as the narrow strip: on the direct-PTY
|
|
245
|
+
* fallback the program's own DECSETs really do reach xterm and must be honoured.
|
|
246
|
+
*/
|
|
247
|
+
export function isMuxMouseStripMode(mode, useMux) {
|
|
248
|
+
return useMux && getCli(mode)?.capabilities.altScreen === 'strip-mux-and-mouse';
|
|
215
249
|
}
|
|
216
250
|
// Note: Claude CLI PATH resolution moved to session-cli-builder.ts (buildClaudeEnv)
|
|
217
251
|
/** PTY fallback geometry when tmux can't be queried (matches pre-#80 hardcoded values). */
|
|
@@ -405,6 +439,27 @@ export class Session extends EventEmitter {
|
|
|
405
439
|
_watchingWindow = WATCHING_TAIL_LINES;
|
|
406
440
|
/** Lazily compiled `capabilities.workDetect.awaitingLine`. See _awaitingLinePattern(). */
|
|
407
441
|
_awaitingLineRe = undefined;
|
|
442
|
+
/**
|
|
443
|
+
* The newest model the running CLI reported for itself (its statusline, or its own
|
|
444
|
+
* footer read off the probe's capture), or null when none has. Feeds `displayModel`
|
|
445
|
+
* (src/session-display-model.ts). Persisted through `toState()` and restored after a
|
|
446
|
+
* restart, so an idle session keeps naming its model until the next report.
|
|
447
|
+
*/
|
|
448
|
+
_reportedModel = null;
|
|
449
|
+
/**
|
|
450
|
+
* The model the CLI's own config pins for this session (`modelDetect.configResolver`),
|
|
451
|
+
* read at each pane start, attach or relaunch; null when it pins none. Below any
|
|
452
|
+
* report from the running CLI in `displayModel`. Not persisted: the next start reads it.
|
|
453
|
+
*/
|
|
454
|
+
_configModel = null;
|
|
455
|
+
/** Bumped per config read, so a read that lands after a newer one is dropped. */
|
|
456
|
+
_configModelGen = 0;
|
|
457
|
+
/** Lazily compiled `capabilities.modelDetect.screenLine`. See _modelLinePattern(). */
|
|
458
|
+
_modelLineRe = undefined;
|
|
459
|
+
/** Resolved with the pattern above: how many rows at the foot of the screen it sees. */
|
|
460
|
+
_modelLineRows = 1;
|
|
461
|
+
/** Resolved with the pattern above: the fields it shows that are never the model. */
|
|
462
|
+
_modelRejectWords = [];
|
|
408
463
|
_trustDialogAccepted = false; // Stops the trust-dialog scan (answered, or given up)
|
|
409
464
|
_trustDialogAttempts = 0; // Keystrokes sent at the trust dialog
|
|
410
465
|
_lastTrustDialogScanAt = 0; // Throttle for the trust-dialog screen read
|
|
@@ -697,6 +752,7 @@ export class Session extends EventEmitter {
|
|
|
697
752
|
// replaces it with a first-hand reading. NOT the stats collector, which a
|
|
698
753
|
// browser panel arms and disarms — see `startPaneExitWatcher`.
|
|
699
754
|
this.setPaneExit(config.paneExit);
|
|
755
|
+
this._reportedModel = restoredReportedModel(config.displayModel) ?? null;
|
|
700
756
|
// Never self-parent: a session pointing at itself would draw a zero-length
|
|
701
757
|
// lineage arc under its own tab. Only reachable via the recovery path, where
|
|
702
758
|
// both the id and the saved parent come from disk.
|
|
@@ -964,6 +1020,9 @@ export class Session extends EventEmitter {
|
|
|
964
1020
|
finally {
|
|
965
1021
|
this._paneLifecycleOps--;
|
|
966
1022
|
this._paneStartedAt = Date.now();
|
|
1023
|
+
// A start, attach or relaunch is when the CLI read its config, so it is when
|
|
1024
|
+
// the model that config pins is read here too.
|
|
1025
|
+
this._refreshConfigModel();
|
|
967
1026
|
}
|
|
968
1027
|
}
|
|
969
1028
|
/**
|
|
@@ -1010,7 +1069,7 @@ export class Session extends EventEmitter {
|
|
|
1010
1069
|
/**
|
|
1011
1070
|
* True when this session's PTY is a tmux client rather than the program itself.
|
|
1012
1071
|
* Read by the replay-side alt-screen strip, which must apply the same
|
|
1013
|
-
* `useMux` gate as the live strip (isMuxAltScreenOnlyStripMode).
|
|
1072
|
+
* `useMux` gate as the live strip (isMuxAltScreenOnlyStripMode, isMuxMouseStripMode).
|
|
1014
1073
|
*/
|
|
1015
1074
|
get usesMux() {
|
|
1016
1075
|
return this._useMux;
|
|
@@ -1482,6 +1541,7 @@ export class Session extends EventEmitter {
|
|
|
1482
1541
|
model: cliTakesSessionModel(this.mode) ? this._model : undefined,
|
|
1483
1542
|
advisorModel: this._advisorModel,
|
|
1484
1543
|
customModel: this.customModel,
|
|
1544
|
+
displayModel: this.displayModel,
|
|
1485
1545
|
// COD-118: runtime-only — surfaced so the frontend can require explicit user
|
|
1486
1546
|
// intent before restarting a crash-looped session. Deliberately NOT restored
|
|
1487
1547
|
// by the constructor: a Codeman restart starts with a fresh breaker so boot
|
|
@@ -2094,14 +2154,21 @@ export class Session extends EventEmitter {
|
|
|
2094
2154
|
// redraws overwrite only the cells they target, so non-erased rows keep
|
|
2095
2155
|
// their content. Gated to Codex/Claude/Gemini (isAltScreenStripMode).
|
|
2096
2156
|
//
|
|
2097
|
-
// Every OTHER mode (shell/
|
|
2098
|
-
// is tmux-backed: alt-screen toggles only, because the sequence that breaks
|
|
2157
|
+
// Every OTHER mode (shell/antigravity/pi/grok/deepseek/omp) gets the NARROW strip
|
|
2158
|
+
// when it is tmux-backed: alt-screen toggles only, because the sequence that breaks
|
|
2099
2159
|
// scrollback there is tmux's own client-side smcup at attach, not anything the
|
|
2100
2160
|
// program in the pane emitted (issue #205, see isMuxAltScreenOnlyStripMode).
|
|
2101
2161
|
// 3J and the mouse DECSETs stay, so `clear` and mouse-aware TUIs keep working.
|
|
2162
|
+
//
|
|
2163
|
+
// The MIDDLE case (isMuxMouseStripMode) is a mouse-capable full-screen TUI:
|
|
2164
|
+
// it needs smcup AND the mouse DECSETs gone — otherwise the pane's tracking
|
|
2165
|
+
// reaches xterm and every drag becomes a mouse report instead of a text
|
|
2166
|
+
// selection, which is what killed mark-and-copy in opencode — while 3J stays,
|
|
2167
|
+
// because a TUI is not a `clear` consumer.
|
|
2102
2168
|
const fullStrip = isAltScreenStripMode(this.mode);
|
|
2103
|
-
const
|
|
2104
|
-
|
|
2169
|
+
const mouseStrip = isMuxMouseStripMode(this.mode, this._useMux);
|
|
2170
|
+
const altOnlyStrip = !fullStrip && !mouseStrip && isMuxAltScreenOnlyStripMode(this.mode, this._useMux);
|
|
2171
|
+
if (fullStrip || mouseStrip || altOnlyStrip) {
|
|
2105
2172
|
// Reassemble sequences split across PTY chunk boundaries first: a chunk
|
|
2106
2173
|
// ending mid-sequence ('\x1b[?104' now, '9h' next) would slip past the
|
|
2107
2174
|
// strip below and leave xterm stuck in the scrollback-less alt buffer
|
|
@@ -2121,11 +2188,13 @@ export class Session extends EventEmitter {
|
|
|
2121
2188
|
// eslint-disable-next-line no-control-regex
|
|
2122
2189
|
data = data.replace(/\x1b\[\?(?:47|1047|1049)[hl]/g, '');
|
|
2123
2190
|
if (fullStrip) {
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2191
|
+
// eslint-disable-next-line no-control-regex
|
|
2192
|
+
data = data.replace(/\x1b\[3J/g, '');
|
|
2193
|
+
}
|
|
2194
|
+
if (fullStrip || mouseStrip) {
|
|
2195
|
+
data = data.replace(
|
|
2196
|
+
// eslint-disable-next-line no-control-regex
|
|
2197
|
+
/\x1b\[\?(?:1000|1001|1002|1003|1005|1006|1007)[hl]/g, (seq) => {
|
|
2129
2198
|
this._recordStrippedMouseMode(seq);
|
|
2130
2199
|
return '';
|
|
2131
2200
|
});
|
|
@@ -2337,20 +2406,14 @@ export class Session extends EventEmitter {
|
|
|
2337
2406
|
this._codexConfig?.resumeSessionId ||
|
|
2338
2407
|
this.id;
|
|
2339
2408
|
// For NEW mux sessions: wait for readiness then clean buffer
|
|
2340
|
-
// For RESTORED mux sessions:
|
|
2409
|
+
// For RESTORED mux sessions: leave the buffer alone - client will fetch it on tab switch
|
|
2341
2410
|
if (!isRestored) {
|
|
2342
2411
|
if (isExternalCliMode(this.mode)) {
|
|
2343
2412
|
// External CLIs use custom TUIs — no ❯ prompt to detect.
|
|
2344
2413
|
// Wait for TUI to stabilize (output stops changing), then mark ready.
|
|
2345
2414
|
// Don't clear the buffer — the TUI's initial render IS the useful content.
|
|
2346
2415
|
// Emit needsRefresh so the client fetches the full buffer once the TUI has rendered.
|
|
2347
|
-
this.
|
|
2348
|
-
this._promptCheckTimeout = null;
|
|
2349
|
-
if (this._isStopped)
|
|
2350
|
-
return;
|
|
2351
|
-
this._status = 'idle';
|
|
2352
|
-
this.emit('needsRefresh');
|
|
2353
|
-
}, 3000);
|
|
2416
|
+
this._armPaneSettle(false);
|
|
2354
2417
|
}
|
|
2355
2418
|
else {
|
|
2356
2419
|
// Claude mode: wait for ❯ prompt
|
|
@@ -2384,6 +2447,9 @@ export class Session extends EventEmitter {
|
|
|
2384
2447
|
}, 5000);
|
|
2385
2448
|
}
|
|
2386
2449
|
}
|
|
2450
|
+
else {
|
|
2451
|
+
this._armPaneSettle(true);
|
|
2452
|
+
}
|
|
2387
2453
|
}
|
|
2388
2454
|
catch (err) {
|
|
2389
2455
|
console.error('[Session] Failed to create mux session, falling back to direct PTY:', err);
|
|
@@ -2707,8 +2773,128 @@ export class Session extends EventEmitter {
|
|
|
2707
2773
|
this._lastPaneProbeWorking =
|
|
2708
2774
|
text === null ? null : this._workingLinePattern().test(text) || this._paneAwaitsWorkers(text);
|
|
2709
2775
|
this._readWatching(text);
|
|
2776
|
+
this._readScreenModel(text);
|
|
2710
2777
|
return this._lastPaneProbeWorking;
|
|
2711
2778
|
}
|
|
2779
|
+
/**
|
|
2780
|
+
* Read the model the CLI's own footer names off the same capture, for a CLI whose
|
|
2781
|
+
* registry entry declares `capabilities.modelDetect`.
|
|
2782
|
+
*
|
|
2783
|
+
* Unlike `_readWatching`, a capture that could not be read, or a footer the pattern
|
|
2784
|
+
* does not find (a popup covering it, a footer turned off), KEEPS the last model. The
|
|
2785
|
+
* two are not symmetric: background work ends and its badge must go, while a model does
|
|
2786
|
+
* not stop running because something was drawn over the row that names it.
|
|
2787
|
+
*/
|
|
2788
|
+
_readScreenModel(paneText) {
|
|
2789
|
+
if (paneText === null)
|
|
2790
|
+
return;
|
|
2791
|
+
const pattern = this._modelLinePattern();
|
|
2792
|
+
if (!pattern)
|
|
2793
|
+
return;
|
|
2794
|
+
const model = readScreenModel(paneText, pattern, this._modelLineRows, {
|
|
2795
|
+
rejectWords: this._modelRejectWords,
|
|
2796
|
+
// A footer field equal to the folder this session runs in is the folder, never the
|
|
2797
|
+
// model: the generic half of the rule, for every CLI.
|
|
2798
|
+
cwdBasename: basename(this.workingDir),
|
|
2799
|
+
});
|
|
2800
|
+
if (model)
|
|
2801
|
+
this.noteReportedModel('screen', model);
|
|
2802
|
+
}
|
|
2803
|
+
/**
|
|
2804
|
+
* The regex reading this CLI's model off its footer, or null for a CLI that declares
|
|
2805
|
+
* none. Compiled once per session through `compileVersionRegex()` (null, never a throw,
|
|
2806
|
+
* for a pattern it refuses), like the working- and watching-line patterns.
|
|
2807
|
+
*/
|
|
2808
|
+
_modelLinePattern() {
|
|
2809
|
+
if (this._modelLineRe === undefined) {
|
|
2810
|
+
const detect = getCli(this.mode)?.capabilities.modelDetect;
|
|
2811
|
+
this._modelLineRe = detect?.screenLine ? compileVersionRegex(detect.screenLine) : null;
|
|
2812
|
+
this._modelLineRows = detect?.screenLines ?? 1;
|
|
2813
|
+
this._modelRejectWords = detect?.rejectWords ?? [];
|
|
2814
|
+
}
|
|
2815
|
+
return this._modelLineRe;
|
|
2816
|
+
}
|
|
2817
|
+
/**
|
|
2818
|
+
* Record a model the running CLI reported for itself: its statusline (claude's
|
|
2819
|
+
* exporter, via `POST /api/status-telemetry`) or its own footer. The newest report
|
|
2820
|
+
* wins whatever its source. An empty or unprintable report changes nothing.
|
|
2821
|
+
*
|
|
2822
|
+
* @returns true when the reported model changed (and `displayModelChanged` was emitted)
|
|
2823
|
+
*/
|
|
2824
|
+
noteReportedModel(source, raw) {
|
|
2825
|
+
const model = sanitizeModelName(raw);
|
|
2826
|
+
if (!model)
|
|
2827
|
+
return false;
|
|
2828
|
+
if (this._reportedModel?.model === model && this._reportedModel.source === source)
|
|
2829
|
+
return false;
|
|
2830
|
+
this._reportedModel = { model, source };
|
|
2831
|
+
// The status does not change with it, so it needs a broadcast (and a persist) of its own.
|
|
2832
|
+
this.emit('displayModelChanged');
|
|
2833
|
+
return true;
|
|
2834
|
+
}
|
|
2835
|
+
/**
|
|
2836
|
+
* The model this session runs as far as the server knows, and where that came from:
|
|
2837
|
+
* the custom endpoint's model, else the newest report from the CLI, else the launch
|
|
2838
|
+
* model (src/session-display-model.ts). Undefined when none is known.
|
|
2839
|
+
*/
|
|
2840
|
+
get displayModel() {
|
|
2841
|
+
return resolveDisplayModel({
|
|
2842
|
+
customModelId: this._customModel?.modelId,
|
|
2843
|
+
reported: this._reportedModel,
|
|
2844
|
+
configModel: this._configModel,
|
|
2845
|
+
launchModel: launchModelFor(this.mode, this._launchOptionBag()),
|
|
2846
|
+
});
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* The same option bag the spawn reads its launch params from: `model` at the top for
|
|
2850
|
+
* claude (the `--model` or app-wide default it was created with; inert for every other
|
|
2851
|
+
* CLI, which is why it is not handed over for them), each other CLI's own
|
|
2852
|
+
* `<Mode>Config`. Where a param lives is registry data (`legacyConfigForMode`).
|
|
2853
|
+
*/
|
|
2854
|
+
_launchOptionBag() {
|
|
2855
|
+
return {
|
|
2856
|
+
model: cliTakesSessionModel(this.mode) ? this._model : undefined,
|
|
2857
|
+
openCodeConfig: this._openCodeConfig,
|
|
2858
|
+
codexConfig: this._codexConfig,
|
|
2859
|
+
geminiConfig: this._geminiConfig,
|
|
2860
|
+
antigravityConfig: this._antigravityConfig,
|
|
2861
|
+
piConfig: this._piConfig,
|
|
2862
|
+
grokConfig: this._grokConfig,
|
|
2863
|
+
deepSeekConfig: this._deepSeekConfig,
|
|
2864
|
+
ompConfig: this._ompConfig,
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2867
|
+
/**
|
|
2868
|
+
* Read the model this session's CLI config pins, with the reader its registry entry
|
|
2869
|
+
* names (`capabilities.modelDetect.configResolver`), and announce a change. Async and
|
|
2870
|
+
* bounded (the reader probes before it reads); a read that lands after a newer one,
|
|
2871
|
+
* or after the session stopped, is dropped. A remote or docker session's CLI reads its
|
|
2872
|
+
* config on another machine or in its container, so nothing local is read for it.
|
|
2873
|
+
*/
|
|
2874
|
+
_refreshConfigModel() {
|
|
2875
|
+
const name = getCli(this.mode)?.capabilities.modelDetect?.configResolver;
|
|
2876
|
+
if (!name || this._remote || this._docker)
|
|
2877
|
+
return;
|
|
2878
|
+
const gen = ++this._configModelGen;
|
|
2879
|
+
const overrides = this._envOverrides;
|
|
2880
|
+
resolveConfigModel(name, {
|
|
2881
|
+
config: legacyConfigForMode(this.mode, this._launchOptionBag()),
|
|
2882
|
+
// The session's own env first (already clamped for a non-granted owner), then the
|
|
2883
|
+
// server's: what the pane's CLI inherits.
|
|
2884
|
+
env: (key) => overrides?.[key] ?? process.env[key],
|
|
2885
|
+
}).then((model) => {
|
|
2886
|
+
if (gen !== this._configModelGen || this._isStopped)
|
|
2887
|
+
return;
|
|
2888
|
+
// Sanitized where it is published (resolveDisplayModel), like every source.
|
|
2889
|
+
const next = model || null;
|
|
2890
|
+
if (next === this._configModel)
|
|
2891
|
+
return;
|
|
2892
|
+
this._configModel = next;
|
|
2893
|
+
this.emit('displayModelChanged');
|
|
2894
|
+
}, () => {
|
|
2895
|
+
/* A reader answers null on doubt and never throws; a throw changes nothing. */
|
|
2896
|
+
});
|
|
2897
|
+
}
|
|
2712
2898
|
/**
|
|
2713
2899
|
* Read the background-work chip off the same capture the working probe just took.
|
|
2714
2900
|
*
|
|
@@ -2843,15 +3029,77 @@ export class Session extends EventEmitter {
|
|
|
2843
3029
|
// 1. Claude was working and is now at prompt (normal case)
|
|
2844
3030
|
// 2. Session just started and is ready (status is 'busy' but _isWorking is false)
|
|
2845
3031
|
const wasWorking = this._isWorking;
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
3032
|
+
if (wasWorking || this._status === 'busy')
|
|
3033
|
+
this._concludeIdle(wasWorking);
|
|
3034
|
+
}
|
|
3035
|
+
/**
|
|
3036
|
+
* The one place a pane is concluded idle: status, working flag and prompt stamp
|
|
3037
|
+
* change together, and the change is ANNOUNCED. The `idle` event is what the web
|
|
3038
|
+
* server turns into `session:idle` plus a state broadcast, so a path that flips
|
|
3039
|
+
* `_status` without it leaves every browser on the `busy` it was last sent. A fresh
|
|
3040
|
+
* codex pane used to stay "working" in the UI for its whole life that way.
|
|
3041
|
+
*
|
|
3042
|
+
* @param turnEnded a real turn just finished (not a pane becoming ready at launch)
|
|
3043
|
+
*/
|
|
3044
|
+
_concludeIdle(turnEnded) {
|
|
3045
|
+
this._isWorking = false;
|
|
3046
|
+
this._status = 'idle';
|
|
3047
|
+
this._lastPromptTime = Date.now();
|
|
3048
|
+
// Only a finished turn proves omp has written its session file; a pane that is
|
|
3049
|
+
// merely ready has nothing to resolve yet and could claim a neighbour's file.
|
|
3050
|
+
if (turnEnded)
|
|
3051
|
+
this._maybeCaptureOmpSessionId();
|
|
3052
|
+
this.emit('idle');
|
|
3053
|
+
}
|
|
3054
|
+
/**
|
|
3055
|
+
* Arm the launch settle (`_settlePaneStartup`) for a pane `startInteractive()` just
|
|
3056
|
+
* started or re-attached, when one applies:
|
|
3057
|
+
* - a NEW pane of an external CLI, whose TUI has no ❯ for the Claude wait to find;
|
|
3058
|
+
* - a RESTORED pane (Codeman restart, auto-reattach, tile Attach) of a CLI that
|
|
3059
|
+
* declares no `capabilities.workDetect`. It is `busy` from `_resetBuffers()` like a
|
|
3060
|
+
* new pane, and with no composer glyph to arm `_confirmIdle()` nothing else would
|
|
3061
|
+
* ever settle it. A restored claude or codex pane is left to its glyph, which
|
|
3062
|
+
* reads the screen first, so a restart in mid-turn is never called idle.
|
|
3063
|
+
*/
|
|
3064
|
+
_armPaneSettle(isRestored) {
|
|
3065
|
+
const applies = isRestored ? !getCli(this.mode)?.capabilities.workDetect : isExternalCliMode(this.mode);
|
|
3066
|
+
if (!applies)
|
|
3067
|
+
return;
|
|
3068
|
+
const armedAt = Date.now();
|
|
3069
|
+
this._promptCheckTimeout = setTimeout(() => this._settlePaneStartup(!isRestored, armedAt), 3000);
|
|
3070
|
+
}
|
|
3071
|
+
/**
|
|
3072
|
+
* The launch settle: 3 s after a NEW external-CLI pane spawned, or after ANY pane of a
|
|
3073
|
+
* CLI without work detection was re-attached, its TUI is taken to have rendered. A pane
|
|
3074
|
+
* still in its spawn-time `busy` is concluded idle (announced, see `_concludeIdle`),
|
|
3075
|
+
* then, for a new pane, the browser is told to refetch the rendered screen.
|
|
3076
|
+
*
|
|
3077
|
+
* ⚠️ This used to set `_status = 'idle'` without an event. When the launch paint
|
|
3078
|
+
* never tripped `_markWorking()`, the later `_confirmIdle()` found the status
|
|
3079
|
+
* already idle and emitted nothing, so no browser ever learned the pane was ready.
|
|
3080
|
+
*
|
|
3081
|
+
* A pane is left to `_confirmIdle()` only when its CLI declares
|
|
3082
|
+
* `capabilities.workDetect` (its composer glyph arms that confirmation, which reads
|
|
3083
|
+
* the screen first) AND it is already working, or a prompt was submitted since the
|
|
3084
|
+
* timer was armed: a turn started 2.9 s in is not marked working before the deferred
|
|
3085
|
+
* parsers run, and an idle edge here would end a send-and-wait registered for it.
|
|
3086
|
+
* For every other CLI this timer is the only thing that ever settles a fresh pane,
|
|
3087
|
+
* so it settles it even if a stray spinner glyph in the launch paint latched
|
|
3088
|
+
* `_isWorking`.
|
|
3089
|
+
*
|
|
3090
|
+
* @param refreshScreen emit `needsRefresh` (a new pane; an attach refetches by itself)
|
|
3091
|
+
* @param armedAt when the timer was armed (a submit at or after it means a prompt)
|
|
3092
|
+
*/
|
|
3093
|
+
_settlePaneStartup(refreshScreen, armedAt) {
|
|
3094
|
+
this._promptCheckTimeout = null;
|
|
3095
|
+
if (this._isStopped)
|
|
3096
|
+
return;
|
|
3097
|
+
const busyWithTurn = this._isWorking || this.lastSubmitAt >= armedAt;
|
|
3098
|
+
const leaveToConfirm = busyWithTurn && !!getCli(this.mode)?.capabilities.workDetect;
|
|
3099
|
+
if (this._status === 'busy' && !leaveToConfirm)
|
|
3100
|
+
this._concludeIdle(false);
|
|
3101
|
+
if (refreshScreen)
|
|
3102
|
+
this.emit('needsRefresh');
|
|
2855
3103
|
}
|
|
2856
3104
|
/**
|
|
2857
3105
|
* A brand-new omp session (never yet respawned, so
|
|
@@ -3736,7 +3984,7 @@ export class Session extends EventEmitter {
|
|
|
3736
3984
|
: this._mux.capturePaneText?.(this._muxSession.muxName),
|
|
3737
3985
|
sendEnter: () => this._mux?.sendInput(this.id, '\r'),
|
|
3738
3986
|
// ⚠ NO fallback glyph here, unlike the screen-reading probe elsewhere in this file.
|
|
3739
|
-
// Only claude and
|
|
3987
|
+
// Only claude, codex, pi, opencode, omp and gemini declare a promptGlyph; the other modes would fall back
|
|
3740
3988
|
// to claude's `❯`, which is ALSO starship's default shell prompt (and pure's, and
|
|
3741
3989
|
// spaceship's, and p10k lean's). On a shell session the line `❯ npm run build` sits
|
|
3742
3990
|
// on screen for as long as the command runs, promptStillInComposer() reads that as
|
|
@@ -3990,8 +4238,11 @@ export class Session extends EventEmitter {
|
|
|
3990
4238
|
catch (err) {
|
|
3991
4239
|
console.warn('[Session] Failed to send SIGTERM to PTY process (may already be dead):', err);
|
|
3992
4240
|
}
|
|
3993
|
-
// Give it a moment to terminate gracefully
|
|
3994
|
-
|
|
4241
|
+
// Give it a moment to terminate gracefully. For a tmux-backed session this
|
|
4242
|
+
// is the attach client, gone within a few ms of SIGTERM, and this used to be
|
|
4243
|
+
// a fixed 100ms sleep on every close.
|
|
4244
|
+
if (pid)
|
|
4245
|
+
await waitForProcessesExit([pid], { timeoutMs: GRACEFUL_SHUTDOWN_DELAY_MS });
|
|
3995
4246
|
// Force kill with SIGKILL if still alive
|
|
3996
4247
|
try {
|
|
3997
4248
|
if (pid) {
|