@adhdev/daemon-core 0.9.82-rc.37 → 0.9.82-rc.370
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/dist/boot/daemon-lifecycle.d.ts +9 -0
- package/dist/boot/process-hardening.d.ts +50 -0
- package/dist/build-info.d.ts +37 -0
- package/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +55 -2
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +198 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +116 -64
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-runtime.d.ts +34 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +63 -0
- package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
- package/dist/cli-adapters/resolve-executable.d.ts +14 -0
- package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +6 -3
- package/dist/cli-adapters/terminal-backends/types.d.ts +10 -2
- package/dist/cli-adapters/terminal-screen.d.ts +4 -11
- package/dist/commands/chat-commands.d.ts +27 -0
- package/dist/commands/cli-manager.d.ts +15 -1
- package/dist/commands/handler.d.ts +125 -0
- package/dist/commands/high-family/index.d.ts +3 -0
- package/dist/commands/high-family/mesh-coordinator-launch.d.ts +2 -0
- package/dist/commands/high-family/mesh-events.d.ts +2 -0
- package/dist/commands/high-family/mesh-status.d.ts +2 -0
- package/dist/commands/high-family/types.d.ts +60 -0
- package/dist/commands/low-family/coordinator-prompt.d.ts +9 -0
- package/dist/commands/low-family/daemon-lifecycle.d.ts +2 -0
- package/dist/commands/low-family/diagnostics.d.ts +2 -0
- package/dist/commands/low-family/index.d.ts +3 -0
- package/dist/commands/low-family/mesh-ledger.d.ts +10 -0
- package/dist/commands/low-family/mesh-node-logs.d.ts +2 -0
- package/dist/commands/low-family/notification.d.ts +2 -0
- package/dist/commands/low-family/refine-config.d.ts +2 -0
- package/dist/commands/low-family/session-host.d.ts +2 -0
- package/dist/commands/low-family/spec-providerdev.d.ts +11 -0
- package/dist/commands/low-family/status-meta.d.ts +2 -0
- package/dist/commands/low-family/types.d.ts +33 -0
- package/dist/commands/med-family/cli-agent.d.ts +2 -0
- package/dist/commands/med-family/fast-forward.d.ts +2 -0
- package/dist/commands/med-family/ide.d.ts +10 -0
- package/dist/commands/med-family/index.d.ts +3 -0
- package/dist/commands/med-family/mesh-crud.d.ts +2 -0
- package/dist/commands/med-family/mesh-host-pairing.d.ts +2 -0
- package/dist/commands/med-family/mesh-queue.d.ts +2 -0
- package/dist/commands/med-family/types.d.ts +116 -0
- package/dist/commands/mesh-coordinator.d.ts +85 -1
- package/dist/commands/router.d.ts +666 -2
- package/dist/commands/upgrade-helper.d.ts +41 -1
- package/dist/config/chat-history.d.ts +9 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +77 -1
- package/dist/daemon/dev-server.d.ts +0 -2
- package/dist/detection/ide-detector.d.ts +13 -0
- package/dist/detection/win32-ide-version.d.ts +37 -0
- package/dist/git/change-impact-config.d.ts +159 -0
- package/dist/git/git-commands.d.ts +11 -1
- package/dist/git/git-diff.d.ts +6 -0
- package/dist/git/git-executor.d.ts +11 -0
- package/dist/git/git-status.d.ts +23 -0
- package/dist/git/git-types.d.ts +2 -50
- package/dist/git/index.d.ts +2 -0
- package/dist/index.d.ts +55 -13
- package/dist/index.js +46516 -19739
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +43516 -16870
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/ipc/local-ipc-server.d.ts +91 -0
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/logging/log-redactor.d.ts +24 -0
- package/dist/logging/log-tail-reader.d.ts +46 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-prompt.d.ts +36 -0
- package/dist/mesh/coordinator-registry.d.ts +59 -0
- package/dist/mesh/mesh-active-work.d.ts +174 -0
- package/dist/mesh/mesh-delivery-policy.d.ts +131 -0
- package/dist/mesh/mesh-event-trace.d.ts +21 -0
- package/dist/mesh/mesh-events-coordinator.d.ts +91 -0
- package/dist/mesh/mesh-events-pending.d.ts +55 -0
- package/dist/mesh/mesh-events-stale.d.ts +40 -0
- package/dist/mesh/mesh-events-utils.d.ts +61 -0
- package/dist/mesh/mesh-events.d.ts +6 -49
- package/dist/mesh/mesh-fast-forward.d.ts +81 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-init.d.ts +86 -0
- package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -3
- package/dist/mesh/mesh-ledger.d.ts +77 -1
- package/dist/mesh/mesh-missions.d.ts +164 -0
- package/dist/mesh/mesh-reconcile-loop.d.ts +7 -0
- package/dist/mesh/mesh-refine-batch.d.ts +68 -0
- package/dist/mesh/mesh-refine-status.d.ts +62 -0
- package/dist/mesh/mesh-review-inbox.d.ts +90 -0
- package/dist/mesh/mesh-routing.d.ts +70 -0
- package/dist/mesh/mesh-runtime-store.d.ts +429 -0
- package/dist/mesh/mesh-task-stats.d.ts +49 -0
- package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +30 -0
- package/dist/mesh/mesh-warmup-deadline.d.ts +68 -0
- package/dist/mesh/mesh-work-queue.d.ts +246 -5
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +216 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +135 -0
- package/dist/providers/acp-provider-instance.d.ts +5 -0
- package/dist/providers/approval-utils.d.ts +20 -0
- package/dist/providers/chat-message-normalization.d.ts +12 -0
- package/dist/providers/cli-provider-instance.d.ts +108 -3
- package/dist/providers/contracts.d.ts +139 -6
- package/dist/providers/external-sources.d.ts +71 -0
- package/dist/providers/manual-attendance.d.ts +63 -0
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
- package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
- package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
- package/dist/providers/native-history/dispatcher.d.ts +27 -0
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
- package/dist/providers/native-history/index.d.ts +13 -0
- package/dist/providers/provider-instance-manager.d.ts +14 -0
- package/dist/providers/provider-instance.d.ts +23 -1
- package/dist/providers/provider-loader.d.ts +26 -4
- package/dist/providers/provider-trust.d.ts +31 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +86 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
- package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
- package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
- package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
- package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
- package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
- package/dist/providers/sdk/v1/index.d.ts +30 -0
- package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
- package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
- package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
- package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
- package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
- package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
- package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
- package/dist/providers/spec/adapter.d.ts +91 -0
- package/dist/providers/spec/cli-adapter.d.ts +223 -0
- package/dist/providers/spec/evaluator.d.ts +45 -0
- package/dist/providers/spec/fsm-driver.d.ts +395 -0
- package/dist/providers/spec/fsm-evaluator.d.ts +76 -0
- package/dist/providers/spec/fsm-loader.d.ts +14 -0
- package/dist/providers/spec/fsm-types.d.ts +174 -0
- package/dist/providers/spec/native-history-executor.d.ts +41 -0
- package/dist/providers/spec/pre-launch-trust.d.ts +16 -0
- package/dist/providers/spec/route.d.ts +4 -0
- package/dist/providers/spec/types.d.ts +222 -0
- package/dist/providers/status-monitor.d.ts +7 -7
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +84 -0
- package/dist/providers/working-dir.d.ts +17 -0
- package/dist/repo-mesh-types.d.ts +295 -13
- package/dist/sessions/registry.d.ts +6 -0
- package/dist/shared-types-extra.d.ts +2 -4
- package/dist/shared-types.d.ts +59 -2
- package/dist/status/normalize.d.ts +1 -1
- package/dist/status/normalize.js +1 -0
- package/dist/status/normalize.js.map +1 -1
- package/dist/status/normalize.mjs +1 -0
- package/dist/status/normalize.mjs.map +1 -1
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +26 -0
- package/dist/system/hash.d.ts +8 -0
- package/dist/system/load-better-sqlite3.d.ts +21 -0
- package/dist/types.d.ts +5 -0
- package/package.json +7 -3
- package/src/agent-stream/poller.ts +2 -3
- package/src/agent-stream/provider-adapter.ts +1 -1
- package/src/boot/daemon-lifecycle.ts +63 -12
- package/src/boot/process-hardening.ts +89 -0
- package/src/build-info.ts +73 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +20 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +56 -2
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1221 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +775 -1141
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +13 -0
- package/src/cli-adapters/provider-cli-runtime.ts +56 -7
- package/src/cli-adapters/provider-cli-shared.d.ts +4 -0
- package/src/cli-adapters/provider-cli-shared.ts +110 -11
- package/src/cli-adapters/pty-transport.ts +2 -1
- package/src/cli-adapters/raw-terminal-io.ts +252 -0
- package/src/cli-adapters/resolve-executable.ts +90 -0
- package/src/cli-adapters/session-host-transport.ts +2 -1
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +53 -29
- package/src/cli-adapters/terminal-backends/types.ts +10 -3
- package/src/cli-adapters/terminal-screen.ts +16 -81
- package/src/commands/WINDOWS-UPGRADE-LOCK-FAILURE.md +198 -0
- package/src/commands/chat-commands.ts +1917 -62
- package/src/commands/cli-manager.ts +502 -24
- package/src/commands/handler.ts +841 -2
- package/src/commands/high-family/index.ts +28 -0
- package/src/commands/high-family/mesh-coordinator-launch.ts +592 -0
- package/src/commands/high-family/mesh-events.ts +47 -0
- package/src/commands/high-family/mesh-status.ts +642 -0
- package/src/commands/high-family/types.ts +76 -0
- package/src/commands/low-family/coordinator-prompt.ts +72 -0
- package/src/commands/low-family/daemon-lifecycle.ts +107 -0
- package/src/commands/low-family/diagnostics.ts +57 -0
- package/src/commands/low-family/index.ts +37 -0
- package/src/commands/low-family/mesh-ledger.ts +62 -0
- package/src/commands/low-family/mesh-node-logs.ts +81 -0
- package/src/commands/low-family/notification.ts +116 -0
- package/src/commands/low-family/refine-config.ts +106 -0
- package/src/commands/low-family/session-host.ts +274 -0
- package/src/commands/low-family/spec-providerdev.ts +217 -0
- package/src/commands/low-family/status-meta.ts +112 -0
- package/src/commands/low-family/types.ts +39 -0
- package/src/commands/med-family/cli-agent.ts +218 -0
- package/src/commands/med-family/fast-forward.ts +198 -0
- package/src/commands/med-family/ide.ts +163 -0
- package/src/commands/med-family/index.ts +35 -0
- package/src/commands/med-family/mesh-crud.ts +788 -0
- package/src/commands/med-family/mesh-host-pairing.ts +234 -0
- package/src/commands/med-family/mesh-queue.ts +131 -0
- package/src/commands/med-family/types.ts +120 -0
- package/src/commands/mesh-coordinator.ts +334 -124
- package/src/commands/router.ts +5121 -2369
- package/src/commands/stream-commands.ts +8 -0
- package/src/commands/upgrade-helper.ts +310 -45
- package/src/config/chat-history.ts +483 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +389 -6
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-auto-implement.ts +3 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/daemon/dev-server.ts +0 -541
- package/src/detection/cli-detector.ts +28 -9
- package/src/detection/ide-detector.ts +55 -16
- package/src/detection/win32-ide-version.ts +106 -0
- package/src/git/change-impact-config.ts +354 -0
- package/src/git/git-commands.ts +55 -14
- package/src/git/git-diff.ts +81 -11
- package/src/git/git-executor.ts +12 -0
- package/src/git/git-status.ts +574 -43
- package/src/git/git-types.ts +14 -62
- package/src/git/git-worktree.ts +8 -1
- package/src/git/index.ts +16 -0
- package/src/index.ts +158 -12
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/ipc/local-ipc-server.ts +278 -0
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/command-log.ts +7 -5
- package/src/logging/log-redactor.ts +100 -0
- package/src/logging/log-tail-reader.ts +220 -0
- package/src/logging/logger.ts +14 -7
- package/src/mesh/contracts.ts +338 -0
- package/src/mesh/coordinator-prompt.ts +234 -31
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +645 -0
- package/src/mesh/mesh-delivery-policy.ts +315 -0
- package/src/mesh/mesh-event-trace.ts +67 -0
- package/src/mesh/mesh-events-coordinator.ts +2866 -0
- package/src/mesh/mesh-events-pending.ts +599 -0
- package/src/mesh/mesh-events-stale.ts +309 -0
- package/src/mesh/mesh-events-utils.ts +387 -0
- package/src/mesh/mesh-events.ts +30 -1035
- package/src/mesh/mesh-fast-forward.ts +839 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-init.ts +260 -0
- package/src/mesh/mesh-ledger-reconciliation.ts +12 -5
- package/src/mesh/mesh-ledger.ts +591 -102
- package/src/mesh/mesh-missions.ts +322 -0
- package/src/mesh/mesh-reconcile-loop.ts +1134 -0
- package/src/mesh/mesh-refine-batch.ts +197 -0
- package/src/mesh/mesh-refine-status.ts +231 -0
- package/src/mesh/mesh-review-inbox.ts +307 -0
- package/src/mesh/mesh-routing.ts +291 -0
- package/src/mesh/mesh-runtime-store.ts +1697 -0
- package/src/mesh/mesh-task-stats.ts +161 -0
- package/src/mesh/mesh-unresolved-forward-outbox.ts +185 -0
- package/src/mesh/mesh-warmup-deadline.ts +152 -0
- package/src/mesh/mesh-work-queue.ts +997 -141
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +423 -0
- package/src/mesh/worktree-bootstrap-config.ts +331 -0
- package/src/providers/acp-provider-instance.ts +43 -10
- package/src/providers/approval-utils.d.ts +5 -0
- package/src/providers/approval-utils.ts +57 -5
- package/src/providers/chat-message-normalization.ts +45 -5
- package/src/providers/cli-provider-instance.ts +1310 -101
- package/src/providers/contracts.d.ts +55 -0
- package/src/providers/contracts.ts +150 -6
- package/src/providers/extension-provider-instance.ts +12 -7
- package/src/providers/external-sources.ts +218 -0
- package/src/providers/ide-provider-instance.ts +35 -12
- package/src/providers/manual-attendance.ts +85 -0
- package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
- package/src/providers/native-history/claude-cli-transcript.ts +396 -0
- package/src/providers/native-history/codex-cli-transcript.ts +479 -0
- package/src/providers/native-history/dispatcher.ts +340 -0
- package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
- package/src/providers/native-history/index.ts +30 -0
- package/src/providers/provider-instance-manager.ts +30 -0
- package/src/providers/provider-instance.ts +19 -1
- package/src/providers/provider-loader.ts +668 -50
- package/src/providers/provider-schema.ts +87 -14
- package/src/providers/provider-trust.ts +114 -0
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/sdk/README.md +49 -0
- package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +313 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
- package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
- package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
- package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
- package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
- package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
- package/src/providers/sdk/v1/index.ts +152 -0
- package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
- package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
- package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
- package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
- package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
- package/src/providers/sdk/v1/types/cli/index.ts +365 -0
- package/src/providers/sdk/v1/types/common/index.ts +229 -0
- package/src/providers/sdk/v1/validators/index.ts +19 -0
- package/src/providers/sdk/v1/validators/manifest.ts +110 -0
- package/src/providers/sdk/v1/validators/taint.ts +309 -0
- package/src/providers/spec/adapter.ts +235 -0
- package/src/providers/spec/cli-adapter.ts +1051 -0
- package/src/providers/spec/evaluator.ts +407 -0
- package/src/providers/spec/fsm-driver.ts +1305 -0
- package/src/providers/spec/fsm-evaluator.ts +272 -0
- package/src/providers/spec/fsm-loader.ts +120 -0
- package/src/providers/spec/fsm-types.ts +273 -0
- package/src/providers/spec/native-history-executor.ts +1136 -0
- package/src/providers/spec/pre-launch-trust.ts +104 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/types.ts +262 -0
- package/src/providers/status-monitor.d.ts +7 -7
- package/src/providers/status-monitor.ts +37 -22
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +536 -0
- package/src/providers/version-archive.ts +64 -24
- package/src/providers/working-dir.ts +23 -0
- package/src/repo-mesh-types.ts +378 -14
- package/src/sessions/registry.ts +6 -0
- package/src/shared-types-extra.ts +2 -4
- package/src/shared-types.d.ts +8 -0
- package/src/shared-types.ts +64 -1
- package/src/status/builders.ts +26 -6
- package/src/status/normalize.ts +2 -0
- package/src/status/reporter.ts +19 -1
- package/src/status/snapshot.ts +95 -26
- package/src/system/hash.ts +23 -0
- package/src/system/host-memory.ts +29 -12
- package/src/system/load-better-sqlite3.ts +68 -0
- package/src/types.ts +5 -0
- package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
- package/dist/mesh/mesh-sync.d.ts +0 -53
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
- package/src/mesh/mesh-sync.ts +0 -111
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import * as os from 'os';
|
|
18
|
-
import type { CliAdapter } from '../cli-adapter-types.js';
|
|
18
|
+
import type { CliAdapter, CliLaunchInfo } from '../cli-adapter-types.js';
|
|
19
|
+
import type { InteractivePromptResponse } from '../providers/types/interactive-prompt.js';
|
|
19
20
|
import { LOG } from '../logging/logger.js';
|
|
20
21
|
import { getDebugRuntimeConfig } from '../logging/debug-config.js';
|
|
21
22
|
import { TerminalScreen } from './terminal-screen.js';
|
|
23
|
+
import { DEFAULT_SESSION_HOST_COLS, DEFAULT_SESSION_HOST_ROWS } from '@adhdev/session-host-core';
|
|
22
24
|
import {
|
|
23
25
|
NodePtyTransportFactory,
|
|
24
26
|
type PtyRuntimeMetadata,
|
|
@@ -44,6 +46,13 @@ import {
|
|
|
44
46
|
type CliTraceEntry,
|
|
45
47
|
type ParsedSession,
|
|
46
48
|
} from './provider-cli-shared.js';
|
|
49
|
+
import { CliScriptRunner } from './cli-script-runner.js';
|
|
50
|
+
import {
|
|
51
|
+
CliStateEngine,
|
|
52
|
+
type CliBufferSnapshot,
|
|
53
|
+
type CliTransportAccess,
|
|
54
|
+
type CliStateEngineCallbacks,
|
|
55
|
+
} from './cli-state-engine.js';
|
|
47
56
|
import {
|
|
48
57
|
buildCliParseInput,
|
|
49
58
|
buildCliTraceParseSnapshot,
|
|
@@ -78,24 +87,6 @@ export {
|
|
|
78
87
|
} from './provider-cli-shared.js';
|
|
79
88
|
|
|
80
89
|
|
|
81
|
-
interface IdleFinishCandidate {
|
|
82
|
-
armedAt: number;
|
|
83
|
-
lastOutputAt: number;
|
|
84
|
-
lastScreenChangeAt: number;
|
|
85
|
-
responseEpoch: number;
|
|
86
|
-
assistantLength: number;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
interface SettledEvalContext {
|
|
90
|
-
now: number;
|
|
91
|
-
modal: any;
|
|
92
|
-
status: string;
|
|
93
|
-
parsedMessages: CliChatMessage[];
|
|
94
|
-
lastParsedAssistant: CliChatMessage | undefined;
|
|
95
|
-
parsedStatus: string | null;
|
|
96
|
-
prevStatus: string;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
90
|
interface SendMessageState {
|
|
100
91
|
text: string;
|
|
101
92
|
normalizedPromptSnippet: string;
|
|
@@ -110,6 +101,14 @@ interface SendMessageCompletion {
|
|
|
110
101
|
rejectOnce: (error: unknown) => void;
|
|
111
102
|
}
|
|
112
103
|
|
|
104
|
+
interface PendingOutboundMessage {
|
|
105
|
+
id: string;
|
|
106
|
+
role: 'user';
|
|
107
|
+
content: string;
|
|
108
|
+
queuedAt: number;
|
|
109
|
+
source: 'sendMessage';
|
|
110
|
+
}
|
|
111
|
+
|
|
113
112
|
export function appendBoundedText(current: string, chunk: string, maxChars: number): string {
|
|
114
113
|
if (!chunk) return current.length <= maxChars ? current : current.slice(-maxChars);
|
|
115
114
|
if (maxChars <= 0) return '';
|
|
@@ -119,26 +118,52 @@ export function appendBoundedText(current: string, chunk: string, maxChars: numb
|
|
|
119
118
|
return current.slice(-keepFromCurrent) + chunk;
|
|
120
119
|
}
|
|
121
120
|
|
|
121
|
+
// Force-send (mesh coordinator dispatch / reconcile redelivery) writes raw
|
|
122
|
+
// keystrokes straight into the PTY, bypassing the normal echo-wait submit
|
|
123
|
+
// pipeline.
|
|
124
|
+
//
|
|
125
|
+
// History of this path's failure modes:
|
|
126
|
+
// 1. Originally it wrote `text + sendKey` in a single PTY write with zero
|
|
127
|
+
// settle time. Injected into an idle TUI input box that was still entering
|
|
128
|
+
// its input-accepting state, the trailing submit key could be swallowed —
|
|
129
|
+
// prompt typed but never submitted ("text injected, Enter not pressed").
|
|
130
|
+
// 2. The first fix split inject from submit: write(text) → echo-gated settle →
|
|
131
|
+
// write(sendKey) as a *separate* PTY write. That broke win32: ConPTY/TUI
|
|
132
|
+
// does not recognize a lone '\r' that arrives in its own PTY chunk
|
|
133
|
+
// 150ms+ after the text as a submit key, and the win32 echo gate never
|
|
134
|
+
// matched (ConPTY echo/parsing differences), so the cap was burned and the
|
|
135
|
+
// same detached lone CR was emitted — adding latency while the Enter still
|
|
136
|
+
// got swallowed.
|
|
137
|
+
//
|
|
138
|
+
// Current behavior: keep a fixed settle gap so the input handler is in its
|
|
139
|
+
// input-accepting state, then write `content + sendKey` as a SINGLE atomic PTY
|
|
140
|
+
// write — the same verified mechanism the normal `immediate` submit strategy
|
|
141
|
+
// uses (submitImmediatePrompt). With the Enter in the same write unit as the
|
|
142
|
+
// text, win32 ConPTY always sees it as a submit and the race that the split was
|
|
143
|
+
// trying to solve cannot happen (the Enter can never be separated from the
|
|
144
|
+
// text). FORCE_SUBMIT_SETTLE_MS is that minimum pre-submit gap.
|
|
145
|
+
const FORCE_SUBMIT_SETTLE_MS = 150;
|
|
146
|
+
|
|
122
147
|
// ─── Adapter ────────────────────────────────────────
|
|
123
148
|
|
|
124
149
|
export class ProviderCliAdapter implements CliAdapter {
|
|
125
|
-
|
|
150
|
+
cliType: string;
|
|
126
151
|
readonly cliName: string;
|
|
127
152
|
public workingDir: string;
|
|
128
153
|
|
|
129
154
|
private provider: CliProviderModule;
|
|
130
155
|
private ptyProcess: PtyRuntimeTransport | null = null;
|
|
131
156
|
private transportFactory: PtyTransportFactory;
|
|
132
|
-
private currentStatus: CliSessionStatus['status'] = 'starting';
|
|
133
157
|
private onStatusChange: (() => void) | null = null;
|
|
134
158
|
|
|
159
|
+
// ─── State machine engine ─────────────────────────
|
|
160
|
+
readonly engine: CliStateEngine;
|
|
161
|
+
|
|
135
162
|
private responseBuffer = '';
|
|
136
163
|
private recentOutputBuffer = '';
|
|
137
|
-
private
|
|
138
|
-
private
|
|
139
|
-
private parseErrorMessage: string | null = null;
|
|
164
|
+
private get parseErrorMessage(): string | null { return this.runner.parseErrorMessage; }
|
|
165
|
+
private providerSessionId: string | null = null;
|
|
140
166
|
private responseTimeout: NodeJS.Timeout | null = null;
|
|
141
|
-
private idleTimeout: NodeJS.Timeout | null = null;
|
|
142
167
|
private ready = false;
|
|
143
168
|
private startupBuffer = '';
|
|
144
169
|
private startupParseGate = false;
|
|
@@ -164,39 +189,48 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
164
189
|
private serverConn: any = null;
|
|
165
190
|
private logBuffer: { message: string; level: string }[] = [];
|
|
166
191
|
|
|
167
|
-
|
|
168
|
-
private
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
private approvalTransitionBuffer: string = '';
|
|
172
|
-
private approvalExitTimeout: NodeJS.Timeout | null = null;
|
|
173
|
-
private pendingScriptStatus: 'generating' | 'waiting_approval' | null = null;
|
|
174
|
-
private pendingScriptStatusSince = 0;
|
|
175
|
-
private pendingScriptStatusTimer: NodeJS.Timeout | null = null;
|
|
176
|
-
|
|
177
|
-
// Output settle debounce — fires after PTY output goes quiet
|
|
178
|
-
private settleTimer: NodeJS.Timeout | null = null;
|
|
179
|
-
private settledBuffer: string = '';
|
|
180
|
-
private submitPendingUntil = 0;
|
|
181
|
-
private responseSettleIgnoreUntil = 0;
|
|
182
|
-
private responseEpoch = 0;
|
|
192
|
+
private pendingOutboundQueue: PendingOutboundMessage[] = [];
|
|
193
|
+
private pendingOutboundFlushTimer: NodeJS.Timeout | null = null;
|
|
194
|
+
private pendingOutboundFlushInFlight = false;
|
|
195
|
+
// Submit retry timer — PTY-level, not state machine
|
|
183
196
|
private submitRetryTimer: NodeJS.Timeout | null = null;
|
|
184
|
-
private submitRetryUsed = false;
|
|
185
|
-
private submitRetryPromptSnippet = '';
|
|
186
|
-
private idleFinishCandidate: IdleFinishCandidate | null = null;
|
|
187
|
-
private finishRetryTimer: NodeJS.Timeout | null = null;
|
|
188
|
-
private finishRetryCount = 0;
|
|
189
197
|
|
|
190
198
|
// Resize redraw suppression
|
|
191
199
|
private resizeSuppressUntil: number = 0;
|
|
192
200
|
|
|
193
|
-
|
|
194
|
-
|
|
201
|
+
// (A2.2) Native transcript anchor moved to CHAT_SOURCE_REGISTRY.
|
|
202
|
+
// ChatSourceMachine holds the lock by state, not by a mutable field on
|
|
203
|
+
// the adapter. Removed entirely; no callers remain after the readChat
|
|
204
|
+
// ladder was replaced.
|
|
205
|
+
|
|
206
|
+
// ─── Script runner (parsing isolated here, adapter stays as transport) ───
|
|
207
|
+
private readonly runner: CliScriptRunner;
|
|
208
|
+
/** @deprecated use runner.cliScripts for direct script access */
|
|
209
|
+
get cliScripts(): CliScripts { return this.runner.cliScripts; }
|
|
195
210
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
211
|
+
/**
|
|
212
|
+
* Recent script invocations (oldest → newest). Exposed via the
|
|
213
|
+
* `get_chat_debug_bundle` daemon command so anyone debugging a
|
|
214
|
+
* stuck-status regression can read what each detectStatus /
|
|
215
|
+
* parseSession call actually saw and returned, instead of having
|
|
216
|
+
* to instrument the daemon.
|
|
217
|
+
*/
|
|
218
|
+
getScriptInvocationTrace() {
|
|
219
|
+
return this.runner.getInvocationTrace();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Returns the full raw PTY byte stream captured since adapter start.
|
|
224
|
+
* Used by the `record_provider_pty` IPC command to produce fixtures.
|
|
225
|
+
* Bounded by MAX_ACCUMULATED_BUFFER; older bytes may have been dropped.
|
|
226
|
+
*/
|
|
227
|
+
getAccumulatedRawBuffer(): { text: string; droppedChars: number } {
|
|
228
|
+
return {
|
|
229
|
+
text: this.accumulatedRawBuffer,
|
|
230
|
+
droppedChars: this.accumulatedRawBufferDroppedChars,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
set cliScripts(scripts: CliScripts) { this.setCliScripts(scripts); }
|
|
200
234
|
private runtimeSettings: Record<string, any> = {};
|
|
201
235
|
/** Full accumulated rendered PTY transcript for parser/readback use */
|
|
202
236
|
private accumulatedBuffer: string = '';
|
|
@@ -205,7 +239,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
205
239
|
/** Full accumulated raw PTY output (with ANSI) */
|
|
206
240
|
private accumulatedRawBuffer: string = '';
|
|
207
241
|
/** Current visible terminal screen snapshot */
|
|
208
|
-
private terminalScreen = new TerminalScreen(
|
|
242
|
+
private terminalScreen = new TerminalScreen(DEFAULT_SESSION_HOST_ROWS, DEFAULT_SESSION_HOST_COLS);
|
|
209
243
|
private static readonly MAX_RESPONSE_BUFFER = 8000;
|
|
210
244
|
private static readonly MAX_RECENT_OUTPUT_BUFFER = 1000;
|
|
211
245
|
private responseBufferDroppedChars = 0;
|
|
@@ -216,10 +250,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
216
250
|
* Hermes turn (tool calls + reasoning + final bubble) without the
|
|
217
251
|
* rolling window pushing the turn's ╭─ opening line out of view. */
|
|
218
252
|
private static readonly MAX_ACCUMULATED_BUFFER = 262144;
|
|
219
|
-
private currentTurnScope: TurnParseScope | null = null;
|
|
220
|
-
private traceEntries: CliTraceEntry[] = [];
|
|
221
|
-
private traceSeq = 0;
|
|
222
|
-
private traceSessionId = '';
|
|
223
253
|
private parsedStatusCache: {
|
|
224
254
|
responseBuffer: string;
|
|
225
255
|
currentTurnScope: TurnParseScope | null;
|
|
@@ -233,11 +263,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
233
263
|
result: any;
|
|
234
264
|
} | null = null;
|
|
235
265
|
private static readonly SCREEN_SNAPSHOT_MIN_INTERVAL_MS = 250;
|
|
236
|
-
private static readonly MAX_TRACE_ENTRIES = 250;
|
|
237
266
|
|
|
238
267
|
private readonly providerResolutionMeta: ProviderResolutionMeta;
|
|
239
|
-
private static readonly FINISH_RETRY_DELAY_MS = 300;
|
|
240
|
-
private static readonly MAX_FINISH_RETRIES = 2;
|
|
241
268
|
|
|
242
269
|
private getBufferState(): NonNullable<CliSessionStatus['bufferState']> | undefined {
|
|
243
270
|
const build = (droppedChars: number, maxChars: number) => droppedChars > 0
|
|
@@ -313,13 +340,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
313
340
|
if (
|
|
314
341
|
cached
|
|
315
342
|
&& cached.responseBuffer === this.responseBuffer
|
|
316
|
-
&& cached.currentTurnScope === this.currentTurnScope
|
|
343
|
+
&& cached.currentTurnScope === this.engine.currentTurnScope
|
|
317
344
|
&& cached.recentOutputBuffer === this.recentOutputBuffer
|
|
318
345
|
&& cached.accumulatedBuffer === this.accumulatedBuffer
|
|
319
346
|
&& cached.accumulatedRawBufferKey === accumulatedRawBufferKey
|
|
320
347
|
&& cached.screenText === this.lastScreenText
|
|
321
|
-
&& cached.currentStatus === this.currentStatus
|
|
322
|
-
&& cached.activeModal === this.activeModal
|
|
348
|
+
&& cached.currentStatus === this.engine.currentStatus
|
|
349
|
+
&& cached.activeModal === this.engine.activeModal
|
|
323
350
|
&& cached.cliName === this.cliName
|
|
324
351
|
) {
|
|
325
352
|
return cached.result;
|
|
@@ -343,86 +370,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
343
370
|
return this.timeouts.statusActivityHold;
|
|
344
371
|
}
|
|
345
372
|
|
|
346
|
-
private setStatus(status: CliSessionStatus['status'], trigger?: string): void {
|
|
347
|
-
const prev = this.currentStatus;
|
|
348
|
-
if (prev === status) return;
|
|
349
|
-
this.currentStatus = status;
|
|
350
|
-
this.statusHistory.push({ status, at: Date.now(), trigger });
|
|
351
|
-
if (this.statusHistory.length > 50) this.statusHistory.shift();
|
|
352
|
-
this.recordTrace('status', {
|
|
353
|
-
previousStatus: prev,
|
|
354
|
-
trigger: trigger || null,
|
|
355
|
-
});
|
|
356
|
-
LOG.info('CLI', `[${this.cliType}] status: ${prev} → ${status}${trigger ? ` (${trigger})` : ''}`);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
private clearIdleFinishCandidate(reason: string): void {
|
|
360
|
-
if (!this.idleFinishCandidate) return;
|
|
361
|
-
this.recordTrace('idle_candidate_reset', {
|
|
362
|
-
reason,
|
|
363
|
-
candidate: this.idleFinishCandidate,
|
|
364
|
-
});
|
|
365
|
-
this.idleFinishCandidate = null;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
private armIdleFinishCandidate(assistantLength: number): void {
|
|
369
|
-
const now = Date.now();
|
|
370
|
-
const idleFinishConfirmMs = this.getIdleFinishConfirmMs();
|
|
371
|
-
this.idleFinishCandidate = {
|
|
372
|
-
armedAt: now,
|
|
373
|
-
lastOutputAt: this.lastOutputAt,
|
|
374
|
-
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
375
|
-
responseEpoch: this.responseEpoch,
|
|
376
|
-
assistantLength,
|
|
377
|
-
};
|
|
378
|
-
this.recordTrace('idle_candidate_armed', {
|
|
379
|
-
confirmMs: idleFinishConfirmMs,
|
|
380
|
-
candidate: this.idleFinishCandidate,
|
|
381
|
-
...buildCliTraceParseSnapshot({
|
|
382
|
-
accumulatedBuffer: this.accumulatedBuffer,
|
|
383
|
-
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
384
|
-
responseBuffer: this.responseBuffer,
|
|
385
|
-
partialResponse: this.responseBuffer,
|
|
386
|
-
scope: this.currentTurnScope,
|
|
387
|
-
}),
|
|
388
|
-
});
|
|
389
|
-
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
390
|
-
this.settleTimer = setTimeout(() => {
|
|
391
|
-
this.settleTimer = null;
|
|
392
|
-
this.settledBuffer = this.recentOutputBuffer;
|
|
393
|
-
this.evaluateSettled();
|
|
394
|
-
}, idleFinishConfirmMs);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
private recordTrace(type: string, payload: Record<string, any> = {}): void {
|
|
399
|
-
const entry: CliTraceEntry = {
|
|
400
|
-
id: ++this.traceSeq,
|
|
401
|
-
at: Date.now(),
|
|
402
|
-
type,
|
|
403
|
-
status: this.currentStatus,
|
|
404
|
-
isWaitingForResponse: this.isWaitingForResponse,
|
|
405
|
-
activeModal: this.activeModal
|
|
406
|
-
? { message: this.activeModal.message, buttons: [...this.activeModal.buttons] }
|
|
407
|
-
: null,
|
|
408
|
-
payload,
|
|
409
|
-
};
|
|
410
|
-
this.traceEntries.push(entry);
|
|
411
|
-
if (this.traceEntries.length > ProviderCliAdapter.MAX_TRACE_ENTRIES) {
|
|
412
|
-
this.traceEntries.splice(0, this.traceEntries.length - ProviderCliAdapter.MAX_TRACE_ENTRIES);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
private resetTraceSession(): void {
|
|
417
|
-
this.traceEntries = [];
|
|
418
|
-
this.traceSeq = 0;
|
|
419
|
-
this.traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
420
|
-
this.recordTrace('session_start', {
|
|
421
|
-
providerType: this.cliType,
|
|
422
|
-
workingDir: this.workingDir,
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
|
|
426
373
|
// Resolved timeouts
|
|
427
374
|
private readonly timeouts: Required<NonNullable<CliProviderModule['timeouts']>>;
|
|
428
375
|
|
|
@@ -432,7 +379,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
432
379
|
private readonly sendKey: string;
|
|
433
380
|
private readonly submitStrategy: 'wait_for_echo' | 'immediate';
|
|
434
381
|
private readonly requirePromptEchoBeforeSubmit: boolean;
|
|
435
|
-
private static readonly SCRIPT_STATUS_DEBOUNCE_MS = 3000;
|
|
436
382
|
|
|
437
383
|
constructor(
|
|
438
384
|
provider: CliProviderModule,
|
|
@@ -441,6 +387,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
441
387
|
private extraEnv: Record<string, string> = {},
|
|
442
388
|
transportFactory: PtyTransportFactory = new NodePtyTransportFactory(),
|
|
443
389
|
) {
|
|
390
|
+
this.runner = new CliScriptRunner(provider.type);
|
|
444
391
|
this.provider = provider;
|
|
445
392
|
this.transportFactory = transportFactory;
|
|
446
393
|
this.cliType = provider.type;
|
|
@@ -458,10 +405,33 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
458
405
|
this.requirePromptEchoBeforeSubmit = resolvedConfig.requirePromptEchoBeforeSubmit;
|
|
459
406
|
this.providerResolutionMeta = resolvedConfig.providerResolutionMeta;
|
|
460
407
|
|
|
461
|
-
//
|
|
462
|
-
this.
|
|
463
|
-
|
|
464
|
-
|
|
408
|
+
// State machine engine — owns all status transitions
|
|
409
|
+
this.engine = new CliStateEngine(
|
|
410
|
+
provider,
|
|
411
|
+
this.runner,
|
|
412
|
+
this as unknown as CliTransportAccess,
|
|
413
|
+
{
|
|
414
|
+
onStatusChange: () => { this.onStatusChange?.(); },
|
|
415
|
+
onApplyParsedSession: (session) => { this.applyParsedSessionMetadata(session); },
|
|
416
|
+
onTurnCompleted: () => { this.responseBuffer = ''; },
|
|
417
|
+
} satisfies CliStateEngineCallbacks,
|
|
418
|
+
resolvedConfig.timeouts,
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
// Scripts delegated to CliScriptRunner — adapter stays as transport.
|
|
422
|
+
// Pass the manifest tui block so the runner can build the
|
|
423
|
+
// declarativeDetectStatus / declarativeParseApproval SDK functions
|
|
424
|
+
// that v1 extended-tier overrides depend on (without these, scripts
|
|
425
|
+
// like codex-cli's detect_status v1 fail-closed with return 'idle'
|
|
426
|
+
// and the session sticks in `generating` forever).
|
|
427
|
+
this.runner.setScripts(provider.scripts || {}, provider.tui);
|
|
428
|
+
// Per-invocation wall-clock budget for provider scripts. Manifests may
|
|
429
|
+
// raise this for genuinely slow parsers, but the default (50ms) is the
|
|
430
|
+
// settle-loop safety net — any script that exceeds it gets flagged in
|
|
431
|
+
// the invocation trace and surfaced via the debug bundle so we can
|
|
432
|
+
// identify the offender instead of guessing why the settle loop hangs.
|
|
433
|
+
this.runner.setScriptCallBudget(provider.scriptCallBudgetMs ?? 50);
|
|
434
|
+
const scriptNames = this.runner.getScriptNames();
|
|
465
435
|
if (scriptNames.length > 0) {
|
|
466
436
|
LOG.info('CLI', `[${this.cliType}] CLI scripts: [${scriptNames.join(', ')}]`);
|
|
467
437
|
LOG.info(
|
|
@@ -487,14 +457,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
487
457
|
|
|
488
458
|
/** Inject CLI scripts after construction (e.g. when resolved by ProviderLoader) */
|
|
489
459
|
setCliScripts(scripts: CliScripts): void {
|
|
490
|
-
this.
|
|
460
|
+
this.runner.setScripts(scripts);
|
|
491
461
|
this.parsedStatusCache = null;
|
|
492
|
-
this.
|
|
493
|
-
// Initialize per-session state: createState() is called once here and on script reload.
|
|
494
|
-
// The returned object lives until the PTY exits (scriptState = null on exit).
|
|
495
|
-
this.scriptState = typeof scripts.createState === 'function' ? (scripts.createState() ?? null) : null;
|
|
496
|
-
const scriptNames = listCliScriptNames(scripts);
|
|
497
|
-
LOG.info('CLI', `[${this.cliType}] CLI scripts injected: [${scriptNames.join(', ')}]`);
|
|
462
|
+
LOG.info('CLI', `[${this.cliType}] CLI scripts injected: [${this.runner.getScriptNames().join(', ')}]`);
|
|
498
463
|
}
|
|
499
464
|
|
|
500
465
|
/** Refresh provider scripts/config used by this adapter without restarting the PTY runtime. */
|
|
@@ -548,15 +513,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
548
513
|
});
|
|
549
514
|
|
|
550
515
|
LOG.info('CLI', `[${this.cliType}] Spawning in ${this.workingDir}`);
|
|
551
|
-
this.resetTraceSession();
|
|
552
|
-
this.recordTrace('spawn', {
|
|
553
|
-
shellCommand: spawnPlan.shellCmd,
|
|
554
|
-
shellArgs: spawnPlan.shellArgs,
|
|
555
|
-
cwd: spawnPlan.ptyOptions.cwd,
|
|
556
|
-
cols: spawnPlan.ptyOptions.cols,
|
|
557
|
-
rows: spawnPlan.ptyOptions.rows,
|
|
558
|
-
providerResolution: this.providerResolutionMeta,
|
|
559
|
-
});
|
|
560
516
|
|
|
561
517
|
try {
|
|
562
518
|
this.ptyProcess = this.transportFactory.spawn(
|
|
@@ -620,13 +576,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
620
576
|
this.ptyProcess.onExit(({ exitCode }: { exitCode: number }) => {
|
|
621
577
|
LOG.info('CLI', `[${this.cliType}] Exit code ${exitCode}`);
|
|
622
578
|
this.flushPendingOutputParse();
|
|
623
|
-
this.recordTrace('exit', { exitCode });
|
|
624
579
|
this.ptyProcess = null;
|
|
625
|
-
this.
|
|
580
|
+
this.engine.onPtyExit();
|
|
626
581
|
this.ready = false;
|
|
627
582
|
this.startupParseGate = false;
|
|
628
583
|
this.spawnAt = 0;
|
|
629
|
-
this.
|
|
584
|
+
this.runner.resetSessionState();
|
|
630
585
|
this.onStatusChange?.();
|
|
631
586
|
});
|
|
632
587
|
|
|
@@ -635,17 +590,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
635
590
|
this.startupBuffer = '';
|
|
636
591
|
this.startupFirstOutputAt = 0;
|
|
637
592
|
if (this.startupSettleTimer) { clearTimeout(this.startupSettleTimer); this.startupSettleTimer = null; }
|
|
638
|
-
this.resetTerminalScreen(
|
|
593
|
+
this.resetTerminalScreen(DEFAULT_SESSION_HOST_ROWS, DEFAULT_SESSION_HOST_COLS);
|
|
639
594
|
this.pendingTerminalQueryTail = '';
|
|
640
|
-
this.currentTurnScope = null;
|
|
641
|
-
this.finishRetryCount = 0;
|
|
642
|
-
if (this.finishRetryTimer) { clearTimeout(this.finishRetryTimer); this.finishRetryTimer = null; }
|
|
643
595
|
this.ready = false;
|
|
644
596
|
await this.ptyProcess.ready;
|
|
645
|
-
this.
|
|
646
|
-
runtimeMeta: this.getRuntimeMetadata(),
|
|
647
|
-
});
|
|
648
|
-
this.setStatus('starting', 'pty_ready');
|
|
597
|
+
this.engine.onSpawnReady();
|
|
649
598
|
this.scheduleStartupSettleCheck();
|
|
650
599
|
this.onStatusChange?.();
|
|
651
600
|
}
|
|
@@ -671,11 +620,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
671
620
|
if (this.startupParseGate && !this.startupFirstOutputAt && (cleanData.trim() || normalizedScreenSnapshot.trim())) {
|
|
672
621
|
this.startupFirstOutputAt = now;
|
|
673
622
|
}
|
|
674
|
-
if (
|
|
675
|
-
this.clearIdleFinishCandidate('new_output');
|
|
623
|
+
if (rawData.length > 0 || cleanData.length > 0) {
|
|
624
|
+
this.engine.clearIdleFinishCandidate('new_output');
|
|
676
625
|
}
|
|
677
626
|
if (getDebugRuntimeConfig().collectDebugTrace) {
|
|
678
|
-
this.
|
|
627
|
+
this.engine.recordExternalTrace('output', {
|
|
679
628
|
rawLength: rawData.length,
|
|
680
629
|
cleanLength: cleanData.length,
|
|
681
630
|
rawPreview: summarizeCliTraceText(rawData, 300),
|
|
@@ -687,7 +636,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
687
636
|
this.scheduleStartupSettleCheck();
|
|
688
637
|
}
|
|
689
638
|
|
|
690
|
-
if (this.isWaitingForResponse && cleanData) {
|
|
639
|
+
if (this.engine.isWaitingForResponse && cleanData) {
|
|
691
640
|
const previousResponseLen = this.responseBuffer.length;
|
|
692
641
|
this.responseBuffer = appendBoundedText(this.responseBuffer, cleanData, ProviderCliAdapter.MAX_RESPONSE_BUFFER);
|
|
693
642
|
this.responseBufferDroppedChars += this.recordBoundedAppendDrop(previousResponseLen, cleanData.length, this.responseBuffer.length);
|
|
@@ -726,19 +675,19 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
726
675
|
// Keep turn-scope offsets aligned with the truncated buffer so scoped
|
|
727
676
|
// parses don't lose the beginning of a long turn (e.g. the Hermes
|
|
728
677
|
// ╭─ opening line) when the rolling window sheds bytes.
|
|
729
|
-
if (this.currentTurnScope) {
|
|
678
|
+
if (this.engine.currentTurnScope) {
|
|
730
679
|
if (droppedClean > 0) {
|
|
731
|
-
this.currentTurnScope.bufferStart = Math.max(0, this.currentTurnScope.bufferStart - droppedClean);
|
|
680
|
+
this.engine.currentTurnScope.bufferStart = Math.max(0, this.engine.currentTurnScope.bufferStart - droppedClean);
|
|
732
681
|
}
|
|
733
682
|
if (droppedRaw > 0) {
|
|
734
|
-
this.currentTurnScope.rawBufferStart = Math.max(0, this.currentTurnScope.rawBufferStart - droppedRaw);
|
|
683
|
+
this.engine.currentTurnScope.rawBufferStart = Math.max(0, this.engine.currentTurnScope.rawBufferStart - droppedRaw);
|
|
735
684
|
}
|
|
736
685
|
}
|
|
737
686
|
|
|
738
687
|
this.resolveStartupState('output', screenText, normalizedScreenSnapshot, now);
|
|
739
688
|
|
|
740
689
|
// ─── Script-based status detection
|
|
741
|
-
this.scheduleSettle();
|
|
690
|
+
this.engine.scheduleSettle();
|
|
742
691
|
}
|
|
743
692
|
|
|
744
693
|
private resolveStartupState(
|
|
@@ -761,6 +710,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
761
710
|
if (stableMs < 2000) return;
|
|
762
711
|
|
|
763
712
|
const startupModal = this.runParseApproval(this.recentOutputBuffer);
|
|
713
|
+
const startupStatus = this.runDetectStatus(screenText || this.recentOutputBuffer);
|
|
714
|
+
if (!startupModal && startupStatus !== 'idle') {
|
|
715
|
+
this.scheduleStartupSettleCheck();
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
764
718
|
this.startupParseGate = false;
|
|
765
719
|
if (this.startupSettleTimer) {
|
|
766
720
|
clearTimeout(this.startupSettleTimer);
|
|
@@ -768,14 +722,22 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
768
722
|
}
|
|
769
723
|
this.ready = true;
|
|
770
724
|
if (startupModal) {
|
|
771
|
-
this.activeModal = startupModal;
|
|
772
|
-
this.setStatus('waiting_approval', `startup_ready:${trigger}`);
|
|
725
|
+
this.engine.activeModal = startupModal;
|
|
726
|
+
this.engine.setStatus('waiting_approval', `startup_ready:${trigger}`);
|
|
773
727
|
} else {
|
|
774
|
-
if (this.currentStatus === 'waiting_approval' || this.activeModal) {
|
|
775
|
-
this.lastApprovalResolvedAt = Date.now();
|
|
728
|
+
if (this.engine.currentStatus === 'waiting_approval' || this.engine.activeModal) {
|
|
729
|
+
this.engine.lastApprovalResolvedAt = Date.now();
|
|
776
730
|
}
|
|
777
|
-
this.activeModal = null;
|
|
778
|
-
|
|
731
|
+
this.engine.activeModal = null;
|
|
732
|
+
// Clear the in-flight turn flag at the same time we declare
|
|
733
|
+
// startup-idle. Otherwise the next settled evaluation sees
|
|
734
|
+
// isWaitingForResponse=true + recent CLI welcome-screen paints
|
|
735
|
+
// and flips us right back to generating via the hold path
|
|
736
|
+
// (the "startup → generating → idle → generating → idle"
|
|
737
|
+
// flicker the user observed on claude-cli launch).
|
|
738
|
+
this.engine.isWaitingForResponse = false;
|
|
739
|
+
this.engine.currentTurnScope = null;
|
|
740
|
+
this.engine.setStatus('idle', `startup_ready:${trigger}`);
|
|
779
741
|
}
|
|
780
742
|
LOG.info(
|
|
781
743
|
'CLI',
|
|
@@ -801,88 +763,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
801
763
|
}, delayMs);
|
|
802
764
|
}
|
|
803
765
|
|
|
804
|
-
private scheduleSettle(): void {
|
|
805
|
-
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
806
|
-
const settleEpoch = this.responseEpoch;
|
|
807
|
-
const delay = Math.max(
|
|
808
|
-
this.timeouts.outputSettle,
|
|
809
|
-
this.submitPendingUntil > Date.now()
|
|
810
|
-
? (this.submitPendingUntil - Date.now()) + this.timeouts.outputSettle
|
|
811
|
-
: 0,
|
|
812
|
-
);
|
|
813
|
-
this.settleTimer = setTimeout(() => {
|
|
814
|
-
this.settleTimer = null;
|
|
815
|
-
if (settleEpoch !== this.responseEpoch) return;
|
|
816
|
-
this.settledBuffer = this.recentOutputBuffer;
|
|
817
|
-
this.evaluateSettled();
|
|
818
|
-
}, delay);
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
private armApprovalExitTimeout(): void {
|
|
822
|
-
if (this.approvalExitTimeout) clearTimeout(this.approvalExitTimeout);
|
|
823
|
-
this.approvalExitTimeout = setTimeout(() => {
|
|
824
|
-
if (!this.hasActionableApproval()) return;
|
|
825
|
-
const tail = this.recentOutputBuffer;
|
|
826
|
-
const screenText = this.terminalScreen.getText() || '';
|
|
827
|
-
const modal = this.runParseApproval(tail);
|
|
828
|
-
const stillWaiting = this.runDetectStatus(tail) === 'waiting_approval' || !!modal;
|
|
829
|
-
if (stillWaiting) {
|
|
830
|
-
if (!modal) {
|
|
831
|
-
LOG.warn('CLI', `[${this.cliType}] approval timeout check found no actionable modal; keeping approval state fail-closed`);
|
|
832
|
-
this.activeModal = null;
|
|
833
|
-
this.onStatusChange?.();
|
|
834
|
-
this.armApprovalExitTimeout();
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
this.activeModal = modal;
|
|
838
|
-
this.onStatusChange?.();
|
|
839
|
-
this.armApprovalExitTimeout();
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
LOG.warn('CLI', `[${this.cliType}] Approval timeout — auto-clearing`);
|
|
843
|
-
this.activeModal = null;
|
|
844
|
-
this.lastApprovalResolvedAt = Date.now();
|
|
845
|
-
this.setStatus('idle', 'approval_timeout');
|
|
846
|
-
this.onStatusChange?.();
|
|
847
|
-
}, 60000);
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
private shouldRetryFinishResponse(commitResult: { hasAssistant: boolean; assistantContent: string }): boolean {
|
|
851
|
-
if (!this.currentTurnScope) return false;
|
|
852
|
-
if (this.hasActionableApproval()) return false;
|
|
853
|
-
if (this.finishRetryCount >= ProviderCliAdapter.MAX_FINISH_RETRIES) return false;
|
|
854
|
-
if (commitResult.hasAssistant && commitResult.assistantContent.trim()) return false;
|
|
855
|
-
|
|
856
|
-
if (this.runDetectStatus(this.recentOutputBuffer) !== 'idle') return false;
|
|
857
|
-
|
|
858
|
-
const now = Date.now();
|
|
859
|
-
const quietForMs = this.lastNonEmptyOutputAt ? (now - this.lastNonEmptyOutputAt) : Number.MAX_SAFE_INTEGER;
|
|
860
|
-
const screenStableMs = this.lastScreenChangeAt ? (now - this.lastScreenChangeAt) : 0;
|
|
861
|
-
return quietForMs < 1200 || screenStableMs < 1200 || !commitResult.hasAssistant;
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
private hasRecentInteractiveActivity(now: number): boolean {
|
|
865
|
-
const quietForMs = this.lastNonEmptyOutputAt ? (now - this.lastNonEmptyOutputAt) : Number.MAX_SAFE_INTEGER;
|
|
866
|
-
const screenStableMs = this.lastScreenChangeAt ? (now - this.lastScreenChangeAt) : Number.MAX_SAFE_INTEGER;
|
|
867
|
-
const holdMs = this.getStatusActivityHoldMs();
|
|
868
|
-
return quietForMs < holdMs
|
|
869
|
-
|| screenStableMs < holdMs;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
private shouldDeferIdleTimeoutFinish(): boolean {
|
|
873
|
-
if (!this.isWaitingForResponse || this.hasActionableApproval()) {
|
|
874
|
-
return false;
|
|
875
|
-
}
|
|
876
|
-
const latestStatus = this.runDetectStatus(this.recentOutputBuffer) || this.currentStatus;
|
|
877
|
-
if (latestStatus === 'generating') {
|
|
878
|
-
this.settledBuffer = this.recentOutputBuffer;
|
|
879
|
-
this.evaluateSettled();
|
|
880
|
-
return true;
|
|
881
|
-
}
|
|
882
|
-
return false;
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
|
|
886
766
|
private async waitForInteractivePrompt(maxWaitMs = 5000): Promise<void> {
|
|
887
767
|
const startedAt = Date.now();
|
|
888
768
|
let loggedWait = false;
|
|
@@ -892,7 +772,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
892
772
|
const screenText = this.terminalScreen.getText() || '';
|
|
893
773
|
const stableMs = this.lastScreenChangeAt ? (Date.now() - this.lastScreenChangeAt) : 0;
|
|
894
774
|
const recentlyOutput = this.lastNonEmptyOutputAt ? (Date.now() - this.lastNonEmptyOutputAt) : Number.MAX_SAFE_INTEGER;
|
|
895
|
-
const status = this.runDetectStatus(this.recentOutputBuffer) || this.currentStatus;
|
|
775
|
+
const status = this.runDetectStatus(this.recentOutputBuffer) || this.engine.currentStatus;
|
|
896
776
|
const interactiveReady = status === 'idle'
|
|
897
777
|
&& stableMs >= 700
|
|
898
778
|
&& recentlyOutput >= 350;
|
|
@@ -926,637 +806,84 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
926
806
|
|
|
927
807
|
private clearAllTimers(): void {
|
|
928
808
|
if (this.responseTimeout) { clearTimeout(this.responseTimeout); this.responseTimeout = null; }
|
|
929
|
-
if (this.idleTimeout) { clearTimeout(this.idleTimeout); this.idleTimeout = null; }
|
|
930
|
-
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
931
809
|
if (this.submitRetryTimer) { clearTimeout(this.submitRetryTimer); this.submitRetryTimer = null; }
|
|
932
|
-
if (this.finishRetryTimer) { clearTimeout(this.finishRetryTimer); this.finishRetryTimer = null; }
|
|
933
|
-
if (this.settleTimer) { clearTimeout(this.settleTimer); this.settleTimer = null; }
|
|
934
|
-
if (this.pendingScriptStatusTimer) { clearTimeout(this.pendingScriptStatusTimer); this.pendingScriptStatusTimer = null; }
|
|
935
810
|
if (this.pendingOutputParseTimer) { clearTimeout(this.pendingOutputParseTimer); this.pendingOutputParseTimer = null; }
|
|
936
811
|
if (this.ptyOutputFlushTimer) { clearTimeout(this.ptyOutputFlushTimer); this.ptyOutputFlushTimer = null; }
|
|
812
|
+
this.engine.clearAllTimers();
|
|
937
813
|
}
|
|
938
814
|
|
|
939
|
-
|
|
940
|
-
const blockingModal = this.activeModal || this.runParseApproval(this.recentOutputBuffer);
|
|
941
|
-
const isIdle = this.runDetectStatus(this.recentOutputBuffer) === 'idle';
|
|
942
|
-
if (!this.isWaitingForResponse || this.currentStatus !== 'idle' || !isIdle || !!blockingModal) {
|
|
943
|
-
return false;
|
|
944
|
-
}
|
|
945
|
-
this.clearAllTimers();
|
|
946
|
-
this.clearIdleFinishCandidate(reason);
|
|
947
|
-
this.responseBuffer = '';
|
|
948
|
-
this.isWaitingForResponse = false;
|
|
949
|
-
this.responseSettleIgnoreUntil = 0;
|
|
950
|
-
this.submitRetryUsed = false;
|
|
951
|
-
this.submitRetryPromptSnippet = '';
|
|
952
|
-
this.finishRetryCount = 0;
|
|
953
|
-
this.currentTurnScope = null;
|
|
954
|
-
this.activeModal = null;
|
|
955
|
-
this.recordTrace('stale_idle_response_cleared', { reason });
|
|
956
|
-
return true;
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
private hasMeaningfulResponseBuffer(promptSnippet: string): boolean {
|
|
960
|
-
const raw = String(this.responseBuffer || '').trim();
|
|
961
|
-
if (!raw) return false;
|
|
962
|
-
const normalizedPrompt = compactPromptText(promptSnippet);
|
|
963
|
-
if (!normalizedPrompt) return true;
|
|
964
|
-
const normalizedBuffer = compactPromptText(raw);
|
|
965
|
-
if (!normalizedBuffer) return false;
|
|
966
|
-
if (normalizedBuffer === normalizedPrompt) return false;
|
|
967
|
-
if (normalizedBuffer.startsWith(normalizedPrompt)) {
|
|
968
|
-
const remainder = normalizedBuffer
|
|
969
|
-
.slice(normalizedPrompt.length)
|
|
970
|
-
.replace(/[─═\-]+/g, '')
|
|
971
|
-
.replace(/⏵⏵accepteditson\([^)]*\)/gi, '')
|
|
972
|
-
.replace(/accepteditson\([^)]*\)/gi, '')
|
|
973
|
-
.replace(/(?:◐|◑|◒|◓|◔|◕|◉|●|·)?(?:x?high|medium|low|max)·?\/effort/gi, '')
|
|
974
|
-
.replace(/updateavailable!run:[a-z0-9:._\-/]+/gi, '')
|
|
975
|
-
.replace(/esctointerrupt/gi, '')
|
|
976
|
-
.replace(/❯/g, '')
|
|
977
|
-
.replace(/^[\s\-–—:;,.!/?]+/, '')
|
|
978
|
-
.trim();
|
|
979
|
-
return remainder.length > 0;
|
|
980
|
-
}
|
|
981
|
-
return true;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
private evaluateSettled(): void {
|
|
985
|
-
const now = Date.now();
|
|
986
|
-
if (this.submitPendingUntil > now || this.responseSettleIgnoreUntil > now) {
|
|
987
|
-
const delayTime = Math.max(this.submitPendingUntil - now, this.responseSettleIgnoreUntil - now) + 50;
|
|
988
|
-
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
989
|
-
this.settleTimer = setTimeout(() => {
|
|
990
|
-
this.settleTimer = null;
|
|
991
|
-
this.settledBuffer = this.recentOutputBuffer;
|
|
992
|
-
this.evaluateSettled();
|
|
993
|
-
}, delayTime);
|
|
994
|
-
return;
|
|
995
|
-
}
|
|
815
|
+
// ─── Script dispatch — builds inputs for CliScriptRunner ──────────────────
|
|
996
816
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
const modal = (session as any).activeModal ?? session.modal ?? null;
|
|
1016
|
-
const parsedMessages = normalizeCliParsedMessages(messages, {
|
|
1017
|
-
scope: null,
|
|
1018
|
-
lastOutputAt: this.lastOutputAt,
|
|
1019
|
-
});
|
|
1020
|
-
|
|
1021
|
-
if (this.maybeCommitVisibleIdleTranscript(session, parsedMessages)) return;
|
|
1022
|
-
|
|
1023
|
-
const lastParsedAssistant = [...parsedMessages].reverse().find((m) => m.role === 'assistant');
|
|
1024
|
-
const normalizedPromptSnippet = normalizePromptText(this.submitRetryPromptSnippet || this.currentTurnScope?.prompt || '');
|
|
1025
|
-
const screenText = this.terminalScreen.getText() || '';
|
|
1026
|
-
|
|
1027
|
-
this.recordTrace('settled', {
|
|
1028
|
-
tail: summarizeCliTraceText(this.settledBuffer, 500),
|
|
1029
|
-
screenText: summarizeCliTraceText(screenText, 1200),
|
|
1030
|
-
detectStatus: status,
|
|
1031
|
-
parsedStatus: parsedStatus || null,
|
|
1032
|
-
parsedMessageCount: parsedMessages.length,
|
|
1033
|
-
parsedLastAssistant: lastParsedAssistant ? summarizeCliTraceText(lastParsedAssistant.content, 280) : '',
|
|
1034
|
-
parsedActiveModal: modal,
|
|
1035
|
-
approval: modal,
|
|
1036
|
-
...buildCliTraceParseSnapshot({
|
|
1037
|
-
accumulatedBuffer: this.accumulatedBuffer,
|
|
1038
|
-
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1039
|
-
responseBuffer: this.responseBuffer,
|
|
1040
|
-
partialResponse: this.responseBuffer,
|
|
1041
|
-
scope: this.currentTurnScope,
|
|
1042
|
-
}),
|
|
817
|
+
runParseSession(): ParsedSession | null {
|
|
818
|
+
const screenText = this.terminalScreen.getText();
|
|
819
|
+
const parseScreenText = this.getParseScreenText(screenText);
|
|
820
|
+
const tail = this.recentOutputBuffer.slice(-500);
|
|
821
|
+
const input = buildCliParseInput({
|
|
822
|
+
accumulatedBuffer: this.accumulatedBuffer,
|
|
823
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
824
|
+
recentOutputBuffer: this.recentOutputBuffer,
|
|
825
|
+
terminalScreenText: parseScreenText,
|
|
826
|
+
workingDir: this.workingDir,
|
|
827
|
+
providerSessionId: this.providerSessionId || undefined,
|
|
828
|
+
historySessionId: this.providerSessionId || undefined,
|
|
829
|
+
baseMessages: [],
|
|
830
|
+
partialResponse: this.responseBuffer,
|
|
831
|
+
isWaitingForResponse: this.engine.isWaitingForResponse,
|
|
832
|
+
scope: this.engine.currentTurnScope,
|
|
833
|
+
runtimeSettings: this.runtimeSettings,
|
|
834
|
+
spawnAt: this.spawnAt,
|
|
1043
835
|
});
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
&& !this.submitRetryUsed
|
|
1049
|
-
&& this.ptyProcess
|
|
1050
|
-
&& !this.hasActionableApproval()
|
|
1051
|
-
&& promptLikelyVisible(screenText, normalizedPromptSnippet)
|
|
1052
|
-
&& !this.hasMeaningfulResponseBuffer(normalizedPromptSnippet)
|
|
1053
|
-
) {
|
|
1054
|
-
this.submitRetryUsed = true;
|
|
1055
|
-
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1056
|
-
LOG.info('CLI', `[${this.cliType}] Retrying submit key from settled parser (no assistant yet)`);
|
|
1057
|
-
this.recordTrace('submit_write', {
|
|
1058
|
-
mode: 'settled_retry',
|
|
1059
|
-
sendKey: this.sendKey,
|
|
1060
|
-
screenText: summarizeCliTraceText(screenText, 500),
|
|
1061
|
-
});
|
|
1062
|
-
this.ptyProcess.write(this.sendKey);
|
|
1063
|
-
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
1064
|
-
this.settleTimer = setTimeout(() => {
|
|
1065
|
-
this.settleTimer = null;
|
|
1066
|
-
this.settledBuffer = this.recentOutputBuffer;
|
|
1067
|
-
this.evaluateSettled();
|
|
1068
|
-
}, this.timeouts.outputSettle + 150);
|
|
1069
|
-
return;
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
if (this.currentTurnScope && !lastParsedAssistant) {
|
|
1073
|
-
LOG.debug(
|
|
1074
|
-
'CLI',
|
|
1075
|
-
`[${this.cliType}] Settled without assistant: prompt=${JSON.stringify(this.currentTurnScope.prompt).slice(0, 140)} responseBuffer=${JSON.stringify(summarizeCliTraceText(this.responseBuffer, 220)).slice(0, 260)} screen=${JSON.stringify(summarizeCliTraceText(screenText, 220)).slice(0, 260)} providerDir=${this.providerResolutionMeta.providerDir || '-'} scriptDir=${this.providerResolutionMeta.scriptDir || '-'}`
|
|
1076
|
-
);
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
if (!status) return;
|
|
1080
|
-
|
|
1081
|
-
const prevStatus = this.currentStatus;
|
|
1082
|
-
const ctx: SettledEvalContext = { now, modal, status, parsedMessages, lastParsedAssistant, parsedStatus: parsedStatus || null, prevStatus };
|
|
1083
|
-
|
|
1084
|
-
if (!this.applyPendingScriptStatusDebounce(ctx)) return;
|
|
1085
|
-
|
|
1086
|
-
const recentInteractiveActivity = this.hasRecentInteractiveActivity(now);
|
|
1087
|
-
LOG.debug(
|
|
1088
|
-
'CLI',
|
|
1089
|
-
`[${this.cliType}] settled diagnostics prompt=${JSON.stringify(this.currentTurnScope?.prompt || '').slice(0, 140)} status=${String(status || '')} parsedStatus=${String(parsedStatus || '')} parsedMsgCount=${parsedMessages.length} lastParsedAssistant=${JSON.stringify(summarizeCliTraceText(lastParsedAssistant?.content || '', 120)).slice(0, 160)} responseBuffer=${JSON.stringify(summarizeCliTraceText(this.responseBuffer, 160)).slice(0, 220)} screen=${JSON.stringify(summarizeCliTraceText(screenText, 160)).slice(0, 220)}`
|
|
1090
|
-
);
|
|
1091
|
-
|
|
1092
|
-
const shouldHoldGenerating =
|
|
1093
|
-
status === 'idle'
|
|
1094
|
-
&& this.isWaitingForResponse
|
|
1095
|
-
&& !modal
|
|
1096
|
-
&& recentInteractiveActivity
|
|
1097
|
-
&& !(parsedStatus === 'idle' && !!lastParsedAssistant);
|
|
1098
|
-
|
|
1099
|
-
if (shouldHoldGenerating) { this.applyHoldGenerating(ctx, recentInteractiveActivity); return; }
|
|
1100
|
-
if (status === 'waiting_approval') { this.applyWaitingApproval(ctx); return; }
|
|
1101
|
-
if (status === 'generating') { this.applyGenerating(ctx); return; }
|
|
1102
|
-
if (status === 'idle') { this.applyIdle(ctx, now); }
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
// Returns false if the caller should bail out (debounce pending).
|
|
1106
|
-
private applyPendingScriptStatusDebounce(ctx: SettledEvalContext): boolean {
|
|
1107
|
-
const { now, status, prevStatus } = ctx;
|
|
1108
|
-
const shouldDebounce =
|
|
1109
|
-
prevStatus === 'idle'
|
|
1110
|
-
&& !this.isWaitingForResponse
|
|
1111
|
-
&& !this.currentTurnScope
|
|
1112
|
-
&& (status === 'generating' || status === 'waiting_approval');
|
|
1113
|
-
|
|
1114
|
-
if (!shouldDebounce) {
|
|
1115
|
-
this.pendingScriptStatus = null;
|
|
1116
|
-
this.pendingScriptStatusSince = 0;
|
|
1117
|
-
if (this.pendingScriptStatusTimer) { clearTimeout(this.pendingScriptStatusTimer); this.pendingScriptStatusTimer = null; }
|
|
1118
|
-
return true;
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
const armPending = (delayMs: number) => {
|
|
1122
|
-
if (this.pendingScriptStatusTimer) clearTimeout(this.pendingScriptStatusTimer);
|
|
1123
|
-
this.pendingScriptStatusTimer = setTimeout(() => {
|
|
1124
|
-
this.pendingScriptStatusTimer = null;
|
|
1125
|
-
this.settledBuffer = this.recentOutputBuffer;
|
|
1126
|
-
this.evaluateSettled();
|
|
1127
|
-
}, delayMs);
|
|
1128
|
-
};
|
|
1129
|
-
|
|
1130
|
-
if (this.pendingScriptStatus !== status) {
|
|
1131
|
-
this.pendingScriptStatus = status as 'generating' | 'waiting_approval';
|
|
1132
|
-
this.pendingScriptStatusSince = now;
|
|
1133
|
-
armPending(ProviderCliAdapter.SCRIPT_STATUS_DEBOUNCE_MS);
|
|
1134
|
-
return false;
|
|
1135
|
-
}
|
|
1136
|
-
const elapsed = now - this.pendingScriptStatusSince;
|
|
1137
|
-
if (elapsed < ProviderCliAdapter.SCRIPT_STATUS_DEBOUNCE_MS) {
|
|
1138
|
-
armPending(ProviderCliAdapter.SCRIPT_STATUS_DEBOUNCE_MS - elapsed);
|
|
1139
|
-
return false;
|
|
1140
|
-
}
|
|
1141
|
-
return true;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
private applyHoldGenerating(ctx: SettledEvalContext, recentInteractiveActivity: boolean): void {
|
|
1145
|
-
const { status } = ctx;
|
|
1146
|
-
this.clearIdleFinishCandidate('hold_generating_recent_activity');
|
|
1147
|
-
this.setStatus('generating', 'recent_activity_hold');
|
|
1148
|
-
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
1149
|
-
this.idleTimeout = setTimeout(() => {
|
|
1150
|
-
if (this.isWaitingForResponse && !this.hasActionableApproval()) {
|
|
1151
|
-
if (this.shouldDeferIdleTimeoutFinish()) return;
|
|
1152
|
-
this.finishResponse();
|
|
1153
|
-
}
|
|
1154
|
-
}, this.timeouts.generatingIdle);
|
|
1155
|
-
this.recordTrace('hold_generating_recent_activity', {
|
|
1156
|
-
scriptStatus: status,
|
|
1157
|
-
recentInteractiveActivity,
|
|
1158
|
-
lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
|
|
1159
|
-
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
1160
|
-
holdMs: this.getStatusActivityHoldMs(),
|
|
1161
|
-
...buildCliTraceParseSnapshot({
|
|
1162
|
-
accumulatedBuffer: this.accumulatedBuffer,
|
|
1163
|
-
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1164
|
-
responseBuffer: this.responseBuffer,
|
|
1165
|
-
partialResponse: this.responseBuffer,
|
|
1166
|
-
scope: this.currentTurnScope,
|
|
1167
|
-
}),
|
|
836
|
+
const session = this.runner.parseSession({
|
|
837
|
+
...input,
|
|
838
|
+
tail,
|
|
839
|
+
tailScreen: buildCliScreenSnapshot(tail),
|
|
1168
840
|
});
|
|
1169
|
-
this.
|
|
841
|
+
if (session && typeof session === 'object') this.applyParsedSessionMetadata(session);
|
|
842
|
+
return session;
|
|
1170
843
|
}
|
|
1171
844
|
|
|
1172
|
-
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
this.
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
this.idleTimeout = setTimeout(() => {
|
|
1183
|
-
if (this.isWaitingForResponse && !this.hasActionableApproval()) {
|
|
1184
|
-
if (this.shouldDeferIdleTimeoutFinish()) return;
|
|
1185
|
-
this.finishResponse();
|
|
1186
|
-
}
|
|
1187
|
-
}, this.timeouts.generatingIdle);
|
|
1188
|
-
} else {
|
|
1189
|
-
this.setStatus('idle', inCooldown ? 'approval_cooldown_non_actionable' : 'approval_prompt_gone_non_actionable');
|
|
1190
|
-
}
|
|
1191
|
-
this.onStatusChange?.();
|
|
1192
|
-
return;
|
|
1193
|
-
}
|
|
1194
|
-
if (!inCooldown) {
|
|
1195
|
-
if (!modal) {
|
|
1196
|
-
LOG.warn('CLI', `[${this.cliType}] detectStatus reported waiting_approval without parseApproval modal; ignoring non-actionable approval state`);
|
|
1197
|
-
return;
|
|
1198
|
-
}
|
|
1199
|
-
this.isWaitingForResponse = true;
|
|
1200
|
-
this.setStatus('waiting_approval', 'script_detect');
|
|
1201
|
-
this.activeModal = modal;
|
|
1202
|
-
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
1203
|
-
this.armApprovalExitTimeout();
|
|
1204
|
-
this.onStatusChange?.();
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
private applyGenerating(ctx: SettledEvalContext): void {
|
|
1209
|
-
const { modal, parsedMessages, lastParsedAssistant, parsedStatus, prevStatus } = ctx;
|
|
1210
|
-
this.clearIdleFinishCandidate('generating');
|
|
1211
|
-
const screenText = this.terminalScreen.getText() || '';
|
|
1212
|
-
const effectiveScreenText = screenText || this.accumulatedBuffer;
|
|
1213
|
-
const noActiveTurn = !this.currentTurnScope;
|
|
1214
|
-
const looksIdleChrome = /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(effectiveScreenText);
|
|
1215
|
-
const parsedShowsLiveAssistantProgress = parsedStatus === 'generating'
|
|
1216
|
-
&& !!lastParsedAssistant
|
|
1217
|
-
;
|
|
1218
|
-
if (prevStatus === 'idle' && !this.isWaitingForResponse && noActiveTurn && !modal && looksIdleChrome && !parsedShowsLiveAssistantProgress) {
|
|
1219
|
-
return;
|
|
1220
|
-
}
|
|
1221
|
-
if (prevStatus === 'waiting_approval') {
|
|
1222
|
-
// Transitioned out of approval → generating
|
|
1223
|
-
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
1224
|
-
this.activeModal = null;
|
|
1225
|
-
this.lastApprovalResolvedAt = Date.now();
|
|
1226
|
-
}
|
|
1227
|
-
if (!this.isWaitingForResponse) {
|
|
1228
|
-
this.isWaitingForResponse = true;
|
|
1229
|
-
this.responseBuffer = '';
|
|
1230
|
-
}
|
|
1231
|
-
this.setStatus('generating', 'script_detect');
|
|
1232
|
-
// Reset idle timeout
|
|
1233
|
-
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
1234
|
-
this.idleTimeout = setTimeout(() => {
|
|
1235
|
-
if (this.isWaitingForResponse) {
|
|
1236
|
-
if (this.shouldDeferIdleTimeoutFinish()) return;
|
|
1237
|
-
this.finishResponse();
|
|
1238
|
-
}
|
|
1239
|
-
}, this.timeouts.generatingIdle);
|
|
1240
|
-
this.onStatusChange?.();
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
private applyIdle(ctx: SettledEvalContext, now: number): void {
|
|
1244
|
-
const { modal, lastParsedAssistant, prevStatus } = ctx;
|
|
1245
|
-
if (prevStatus === 'waiting_approval') {
|
|
1246
|
-
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
1247
|
-
this.activeModal = null;
|
|
1248
|
-
this.lastApprovalResolvedAt = Date.now();
|
|
1249
|
-
this.setStatus('idle', 'approval_prompt_gone_script_idle');
|
|
1250
|
-
}
|
|
1251
|
-
if (!this.isWaitingForResponse) {
|
|
1252
|
-
if (prevStatus !== 'idle') {
|
|
1253
|
-
this.clearIdleFinishCandidate('idle_without_response');
|
|
1254
|
-
this.setStatus('idle', 'script_detect');
|
|
1255
|
-
this.onStatusChange?.();
|
|
1256
|
-
}
|
|
1257
|
-
return;
|
|
1258
|
-
}
|
|
1259
|
-
const quietForMs = this.lastNonEmptyOutputAt ? (now - this.lastNonEmptyOutputAt) : Number.MAX_SAFE_INTEGER;
|
|
1260
|
-
const screenStableMs = this.lastScreenChangeAt ? (now - this.lastScreenChangeAt) : 0;
|
|
1261
|
-
const hasAssistantTurn = !!lastParsedAssistant;
|
|
1262
|
-
const assistantLength = lastParsedAssistant?.content?.length || 0;
|
|
1263
|
-
const idleFinishConfirmMs = this.getIdleFinishConfirmMs();
|
|
1264
|
-
const idleQuietThresholdMs = Math.max(idleFinishConfirmMs, this.timeouts.outputSettle);
|
|
1265
|
-
const idleReady = !modal
|
|
1266
|
-
&& hasAssistantTurn
|
|
1267
|
-
&& quietForMs >= idleQuietThresholdMs
|
|
1268
|
-
&& screenStableMs >= idleFinishConfirmMs;
|
|
1269
|
-
const candidate = this.idleFinishCandidate;
|
|
1270
|
-
const candidateQuiet = !!candidate
|
|
1271
|
-
&& candidate.responseEpoch === this.responseEpoch
|
|
1272
|
-
&& candidate.lastOutputAt === this.lastOutputAt
|
|
1273
|
-
&& candidate.lastScreenChangeAt === this.lastScreenChangeAt
|
|
1274
|
-
&& assistantLength >= candidate.assistantLength
|
|
1275
|
-
&& (now - candidate.armedAt) >= idleFinishConfirmMs;
|
|
1276
|
-
|
|
1277
|
-
this.recordTrace('idle_decision', {
|
|
1278
|
-
quietForMs,
|
|
1279
|
-
screenStableMs,
|
|
1280
|
-
hasAssistantTurn,
|
|
1281
|
-
assistantLength,
|
|
1282
|
-
hasModal: !!modal,
|
|
1283
|
-
idleQuietThresholdMs,
|
|
1284
|
-
idleStableThresholdMs: idleFinishConfirmMs,
|
|
1285
|
-
idleReady,
|
|
1286
|
-
idleFinishConfirmMs,
|
|
1287
|
-
idleFinishCandidate: candidate,
|
|
1288
|
-
candidateQuiet,
|
|
1289
|
-
canFinishImmediately: idleReady && candidateQuiet,
|
|
1290
|
-
submitPendingUntil: this.submitPendingUntil,
|
|
1291
|
-
responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
|
|
1292
|
-
...buildCliTraceParseSnapshot({
|
|
1293
|
-
accumulatedBuffer: this.accumulatedBuffer,
|
|
1294
|
-
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1295
|
-
responseBuffer: this.responseBuffer,
|
|
1296
|
-
partialResponse: this.responseBuffer,
|
|
1297
|
-
scope: this.currentTurnScope,
|
|
1298
|
-
}),
|
|
1299
|
-
});
|
|
1300
|
-
|
|
1301
|
-
if (idleReady && candidateQuiet) {
|
|
1302
|
-
this.clearIdleFinishCandidate('finish_response');
|
|
1303
|
-
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
1304
|
-
this.finishResponse();
|
|
1305
|
-
return;
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
if (idleReady) {
|
|
1309
|
-
if (!candidate) {
|
|
1310
|
-
this.armIdleFinishCandidate(assistantLength);
|
|
1311
|
-
return;
|
|
1312
|
-
}
|
|
1313
|
-
} else {
|
|
1314
|
-
this.clearIdleFinishCandidate('idle_not_ready');
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
1318
|
-
this.idleTimeout = setTimeout(() => {
|
|
1319
|
-
if (this.isWaitingForResponse && !this.hasActionableApproval()) {
|
|
1320
|
-
if (this.shouldDeferIdleTimeoutFinish()) return;
|
|
1321
|
-
this.clearIdleFinishCandidate('idle_timeout_finish');
|
|
1322
|
-
this.finishResponse();
|
|
1323
|
-
}
|
|
1324
|
-
}, this.timeouts.idleFinish);
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
private finishResponse(): void {
|
|
1328
|
-
if (this.submitPendingUntil > Date.now()) return;
|
|
1329
|
-
if (this.responseSettleIgnoreUntil > Date.now()) return;
|
|
1330
|
-
this.clearIdleFinishCandidate('finish_response_enter');
|
|
1331
|
-
this.recordTrace('finish_response', {
|
|
1332
|
-
...buildCliTraceParseSnapshot({
|
|
1333
|
-
accumulatedBuffer: this.accumulatedBuffer,
|
|
1334
|
-
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1335
|
-
responseBuffer: this.responseBuffer,
|
|
1336
|
-
partialResponse: this.responseBuffer,
|
|
1337
|
-
scope: this.currentTurnScope,
|
|
1338
|
-
}),
|
|
845
|
+
runDetectStatus(text: string): string | null {
|
|
846
|
+
const screenText = this.terminalScreen.getText();
|
|
847
|
+
const tail = text.slice(-500);
|
|
848
|
+
return this.runner.detectStatus({
|
|
849
|
+
tail,
|
|
850
|
+
screenText,
|
|
851
|
+
rawBuffer: this.accumulatedRawBuffer,
|
|
852
|
+
isWaitingForResponse: this.engine.isWaitingForResponse,
|
|
853
|
+
screen: buildCliScreenSnapshot(screenText),
|
|
854
|
+
tailScreen: buildCliScreenSnapshot(tail),
|
|
1339
855
|
});
|
|
1340
|
-
const commitResult = this.commitCurrentTranscript();
|
|
1341
|
-
if (this.shouldRetryFinishResponse(commitResult)) {
|
|
1342
|
-
this.finishRetryCount += 1;
|
|
1343
|
-
this.recordTrace('finish_response_retry', {
|
|
1344
|
-
retryCount: this.finishRetryCount,
|
|
1345
|
-
retryDelayMs: ProviderCliAdapter.FINISH_RETRY_DELAY_MS,
|
|
1346
|
-
assistantContent: summarizeCliTraceText(commitResult.assistantContent, 220),
|
|
1347
|
-
...buildCliTraceParseSnapshot({
|
|
1348
|
-
accumulatedBuffer: this.accumulatedBuffer,
|
|
1349
|
-
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1350
|
-
responseBuffer: this.responseBuffer,
|
|
1351
|
-
partialResponse: this.responseBuffer,
|
|
1352
|
-
scope: this.currentTurnScope,
|
|
1353
|
-
}),
|
|
1354
|
-
});
|
|
1355
|
-
if (this.finishRetryTimer) clearTimeout(this.finishRetryTimer);
|
|
1356
|
-
this.finishRetryTimer = setTimeout(() => {
|
|
1357
|
-
this.finishRetryTimer = null;
|
|
1358
|
-
if (this.isWaitingForResponse && !this.hasActionableApproval()) {
|
|
1359
|
-
this.finishResponse();
|
|
1360
|
-
}
|
|
1361
|
-
}, ProviderCliAdapter.FINISH_RETRY_DELAY_MS);
|
|
1362
|
-
return;
|
|
1363
|
-
}
|
|
1364
|
-
this.clearAllTimers();
|
|
1365
|
-
this.responseBuffer = '';
|
|
1366
|
-
this.isWaitingForResponse = false;
|
|
1367
|
-
this.responseSettleIgnoreUntil = 0;
|
|
1368
|
-
this.submitRetryUsed = false;
|
|
1369
|
-
this.submitRetryPromptSnippet = '';
|
|
1370
|
-
this.finishRetryCount = 0;
|
|
1371
|
-
this.currentTurnScope = null;
|
|
1372
|
-
this.activeModal = null;
|
|
1373
|
-
this.setStatus('idle', 'response_finished');
|
|
1374
|
-
this.onStatusChange?.();
|
|
1375
856
|
}
|
|
1376
857
|
|
|
1377
|
-
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
return false;
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
const visibleAssistant = [...parsedMessages].reverse().find((m) => m.role === 'assistant' && m.content.trim());
|
|
1392
|
-
if (!visibleAssistant) return false;
|
|
1393
|
-
|
|
1394
|
-
this.clearAllTimers();
|
|
1395
|
-
this.responseBuffer = '';
|
|
1396
|
-
this.isWaitingForResponse = false;
|
|
1397
|
-
this.responseSettleIgnoreUntil = 0;
|
|
1398
|
-
this.submitRetryUsed = false;
|
|
1399
|
-
this.submitRetryPromptSnippet = '';
|
|
1400
|
-
this.finishRetryCount = 0;
|
|
1401
|
-
this.currentTurnScope = null;
|
|
1402
|
-
this.activeModal = null;
|
|
1403
|
-
this.setStatus('idle', 'script_idle_commit');
|
|
1404
|
-
this.onStatusChange?.();
|
|
1405
|
-
this.recordTrace('script_idle_commit', {
|
|
1406
|
-
messageCount: parsedMessages.length,
|
|
1407
|
-
lastAssistant: summarizeCliTraceText(visibleAssistant.content, 320),
|
|
858
|
+
runParseApproval(tail: string): { message: string; buttons: string[] } | null {
|
|
859
|
+
const screenText = this.terminalScreen.getText();
|
|
860
|
+
const buffer = screenText || this.accumulatedBuffer;
|
|
861
|
+
return this.runner.parseApproval({
|
|
862
|
+
buffer,
|
|
863
|
+
screenText,
|
|
864
|
+
rawBuffer: this.accumulatedRawBuffer,
|
|
865
|
+
tail,
|
|
866
|
+
screen: buildCliScreenSnapshot(screenText),
|
|
867
|
+
bufferScreen: buildCliScreenSnapshot(buffer),
|
|
868
|
+
tailScreen: buildCliScreenSnapshot(tail),
|
|
1408
869
|
});
|
|
1409
|
-
return true;
|
|
1410
870
|
}
|
|
1411
871
|
|
|
1412
|
-
private
|
|
1413
|
-
const
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
const parsedMessages = normalizeCliParsedMessages(parsed.messages, {
|
|
1420
|
-
scope: null,
|
|
1421
|
-
lastOutputAt: this.lastOutputAt,
|
|
1422
|
-
});
|
|
1423
|
-
const lastAssistant = [...parsedMessages].reverse().find((message) => message.role === 'assistant');
|
|
1424
|
-
if (this.currentTurnScope) {
|
|
1425
|
-
LOG.info(
|
|
1426
|
-
'CLI',
|
|
1427
|
-
`[${this.cliType}] commitCurrentTranscript parserMessages=${parsedMessages.length} finalLastAssistant=${JSON.stringify(summarizeCliTraceText(lastAssistant?.content || '', 220)).slice(0, 260)}`
|
|
1428
|
-
);
|
|
1429
|
-
}
|
|
1430
|
-
this.recordTrace('commit_transcript', {
|
|
1431
|
-
parsedStatus: parsed.status || null,
|
|
1432
|
-
messageCount: parsedMessages.length,
|
|
1433
|
-
lastAssistant: lastAssistant ? summarizeCliTraceText(lastAssistant.content, 320) : '',
|
|
1434
|
-
messages: summarizeCliTraceMessages(parsedMessages),
|
|
1435
|
-
...buildCliTraceParseSnapshot({
|
|
1436
|
-
accumulatedBuffer: this.accumulatedBuffer,
|
|
1437
|
-
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1438
|
-
responseBuffer: this.responseBuffer,
|
|
1439
|
-
partialResponse: this.responseBuffer,
|
|
1440
|
-
scope: this.currentTurnScope,
|
|
1441
|
-
}),
|
|
1442
|
-
});
|
|
1443
|
-
if (!lastAssistant && this.currentTurnScope) {
|
|
1444
|
-
LOG.warn(
|
|
1445
|
-
'CLI',
|
|
1446
|
-
`[${this.cliType}] Commit without assistant turn: prompt=${JSON.stringify(this.currentTurnScope.prompt).slice(0, 140)} responseBuffer=${JSON.stringify(summarizeCliTraceText(this.responseBuffer, 220)).slice(0, 260)} providerDir=${this.providerResolutionMeta.providerDir || '-'} scriptDir=${this.providerResolutionMeta.scriptDir || '-'} scriptsPath=${this.providerResolutionMeta.scriptsPath || '-'}`
|
|
1447
|
-
);
|
|
1448
|
-
}
|
|
1449
|
-
const hasAssistant = !!lastAssistant;
|
|
1450
|
-
return {
|
|
1451
|
-
hasAssistant,
|
|
1452
|
-
assistantContent: lastAssistant?.content || '',
|
|
1453
|
-
};
|
|
1454
|
-
}
|
|
1455
|
-
if (this.currentTurnScope) {
|
|
1456
|
-
LOG.info(
|
|
1457
|
-
'CLI',
|
|
1458
|
-
`[${this.cliType}] commitCurrentTranscript parsed.messages=none responseBufferLen=${this.responseBuffer.length} accumulatedBufferLen=${this.accumulatedBuffer.length} parsedStatus=${parsed?.status || '-'} providerDir=${this.providerResolutionMeta.providerDir || '-'} scriptDir=${this.providerResolutionMeta.scriptDir || '-'}`
|
|
1459
|
-
);
|
|
1460
|
-
}
|
|
1461
|
-
return {
|
|
1462
|
-
hasAssistant: false,
|
|
1463
|
-
assistantContent: '',
|
|
1464
|
-
};
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
// ─── Script Execution ──────────────────────────
|
|
1469
|
-
|
|
1470
|
-
private invokeCliScript<T>(script: Function, input: any): T {
|
|
1471
|
-
const hasStateFactory = typeof this.cliScripts?.createState === 'function';
|
|
1472
|
-
const expectsStateArgument = hasStateFactory || this.scriptState !== null || script.length >= 2;
|
|
1473
|
-
return expectsStateArgument
|
|
1474
|
-
? script(this.scriptState, input)
|
|
1475
|
-
: script(input);
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
private runParseSession(): ParsedSession | null {
|
|
1479
|
-
if (typeof this.cliScripts?.parseSession !== 'function') {
|
|
1480
|
-
this.parseErrorMessage = `${this.cliType} parseSession unavailable`;
|
|
1481
|
-
return null;
|
|
1482
|
-
}
|
|
1483
|
-
try {
|
|
1484
|
-
const screenText = this.terminalScreen.getText();
|
|
1485
|
-
const parseScreenText = this.getParseScreenText(screenText);
|
|
1486
|
-
const tail = this.recentOutputBuffer.slice(-500);
|
|
1487
|
-
const input = buildCliParseInput({
|
|
1488
|
-
accumulatedBuffer: this.accumulatedBuffer,
|
|
1489
|
-
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1490
|
-
recentOutputBuffer: this.recentOutputBuffer,
|
|
1491
|
-
terminalScreenText: parseScreenText,
|
|
1492
|
-
baseMessages: [],
|
|
1493
|
-
partialResponse: this.responseBuffer,
|
|
1494
|
-
isWaitingForResponse: this.isWaitingForResponse,
|
|
1495
|
-
scope: this.currentTurnScope,
|
|
1496
|
-
runtimeSettings: this.runtimeSettings,
|
|
1497
|
-
});
|
|
1498
|
-
const session = this.invokeCliScript<ParsedSession | null>(
|
|
1499
|
-
this.cliScripts.parseSession,
|
|
1500
|
-
{ ...input, tail, tailScreen: buildCliScreenSnapshot(tail) },
|
|
1501
|
-
);
|
|
1502
|
-
this.parseErrorMessage = null;
|
|
1503
|
-
return session && typeof session === 'object' ? session : null;
|
|
1504
|
-
} catch (e: any) {
|
|
1505
|
-
const message = e?.message || String(e);
|
|
1506
|
-
this.parseErrorMessage = message;
|
|
1507
|
-
LOG.warn('CLI', `[${this.cliType}] parseSession error: ${message}`);
|
|
1508
|
-
return null;
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1512
|
-
private runDetectStatus(text: string): string | null {
|
|
1513
|
-
if (!this.cliScripts?.detectStatus) return null;
|
|
1514
|
-
try {
|
|
1515
|
-
const screenText = this.terminalScreen.getText();
|
|
1516
|
-
const status = this.invokeCliScript<string | null>(this.cliScripts.detectStatus, {
|
|
1517
|
-
tail: text.slice(-500),
|
|
1518
|
-
screenText,
|
|
1519
|
-
rawBuffer: this.accumulatedRawBuffer,
|
|
1520
|
-
isWaitingForResponse: this.isWaitingForResponse,
|
|
1521
|
-
screen: buildCliScreenSnapshot(screenText),
|
|
1522
|
-
tailScreen: buildCliScreenSnapshot(text.slice(-500)),
|
|
1523
|
-
});
|
|
1524
|
-
return status;
|
|
1525
|
-
} catch (e: any) {
|
|
1526
|
-
LOG.warn('CLI', `[${this.cliType}] detectStatus error: ${e.message}`);
|
|
1527
|
-
return null;
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
private runParseApproval(tail: string): { message: string; buttons: string[] } | null {
|
|
1532
|
-
if (!this.cliScripts?.parseApproval) return null;
|
|
1533
|
-
try {
|
|
1534
|
-
const screenText = this.terminalScreen.getText();
|
|
1535
|
-
const buffer = screenText || this.accumulatedBuffer;
|
|
1536
|
-
return this.invokeCliScript<{ message: string; buttons: string[] } | null>(this.cliScripts.parseApproval, {
|
|
1537
|
-
buffer,
|
|
1538
|
-
screenText,
|
|
1539
|
-
rawBuffer: this.accumulatedRawBuffer,
|
|
1540
|
-
tail,
|
|
1541
|
-
screen: buildCliScreenSnapshot(screenText),
|
|
1542
|
-
bufferScreen: buildCliScreenSnapshot(buffer),
|
|
1543
|
-
tailScreen: buildCliScreenSnapshot(tail),
|
|
1544
|
-
});
|
|
1545
|
-
} catch (e: any) {
|
|
1546
|
-
LOG.warn('CLI', `[${this.cliType}] parseApproval error: ${e.message}`);
|
|
1547
|
-
return null;
|
|
872
|
+
private applyParsedSessionMetadata(parsed: any): void {
|
|
873
|
+
const providerSessionId = typeof parsed?.providerSessionId === 'string' && parsed.providerSessionId.trim()
|
|
874
|
+
? parsed.providerSessionId.trim()
|
|
875
|
+
: '';
|
|
876
|
+
if (providerSessionId && providerSessionId !== this.providerSessionId) {
|
|
877
|
+
this.providerSessionId = providerSessionId;
|
|
878
|
+
this.updateRuntimeMeta({ providerSessionId });
|
|
1548
879
|
}
|
|
1549
880
|
}
|
|
1550
881
|
|
|
1551
|
-
private hasActionableApproval(startupModal: { message: string; buttons: string[] } | null = null): boolean {
|
|
1552
|
-
return !!(startupModal || this.activeModal);
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
882
|
private projectEffectiveStatus(startupModal: { message: string; buttons: string[] } | null = null): CliSessionStatus['status'] {
|
|
1556
883
|
if (this.parseErrorMessage) return 'error';
|
|
1557
|
-
if (this.
|
|
1558
|
-
if (this.isWaitingForResponse && this.currentTurnScope && this.currentStatus !== 'stopped') return 'generating';
|
|
1559
|
-
return this.currentStatus;
|
|
884
|
+
if (!!(startupModal || this.engine.activeModal)) return 'waiting_approval';
|
|
885
|
+
if (this.engine.isWaitingForResponse && this.engine.currentTurnScope && this.engine.currentStatus !== 'stopped') return 'generating';
|
|
886
|
+
return this.engine.currentStatus;
|
|
1560
887
|
}
|
|
1561
888
|
|
|
1562
889
|
// ─── Public API (CliAdapter) ───────────────────
|
|
@@ -1564,17 +891,73 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1564
891
|
getStatus(options: { allowParse?: boolean } = {}): CliSessionStatus {
|
|
1565
892
|
const allowParse = options.allowParse !== false;
|
|
1566
893
|
const startupModal = allowParse && this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
|
|
894
|
+
const startupDetectedStatus = allowParse && this.startupParseGate && !startupModal
|
|
895
|
+
? this.runDetectStatus(this.recentOutputBuffer || this.terminalScreen.getText())
|
|
896
|
+
: null;
|
|
1567
897
|
let effectiveStatus = this.projectEffectiveStatus(startupModal);
|
|
1568
|
-
let effectiveModal = startupModal || this.activeModal;
|
|
898
|
+
let effectiveModal = startupModal || this.engine.activeModal;
|
|
899
|
+
// (fix) When we have no captured modal yet, take one more live attempt
|
|
900
|
+
// with the current screen text — the engine's settle pass can miss
|
|
901
|
+
// the modal when it happens to fire exactly between writes, and
|
|
902
|
+
// without a modal here the dashboard could never show the buttons.
|
|
903
|
+
// We deliberately do NOT overwrite an existing engine.activeModal so
|
|
904
|
+
// a stable matched modal wins. This runs even outside the startup
|
|
905
|
+
// gate because Claude's approval frames can appear long after launch.
|
|
906
|
+
if (allowParse && !effectiveModal && this.engine.isWaitingForResponse) {
|
|
907
|
+
const liveDetect = this.runDetectStatus(this.recentOutputBuffer || this.terminalScreen.getText());
|
|
908
|
+
if (liveDetect === 'waiting_approval') {
|
|
909
|
+
const liveModal = this.runParseApproval(this.terminalScreen.getText())
|
|
910
|
+
|| this.runParseApproval(this.recentOutputBuffer);
|
|
911
|
+
// (fix) Only surface modals that have at least one non-empty
|
|
912
|
+
// button. Half-rendered approval frames produced
|
|
913
|
+
// { message, buttons: [] } briefly — auto-approve would then
|
|
914
|
+
// pick buttonIndex=-1 and resolveModal would write the
|
|
915
|
+
// provider's index-0 key into the prompt. Skip those frames
|
|
916
|
+
// and let a later evaluate find the complete modal.
|
|
917
|
+
const buttonsOk = liveModal && Array.isArray(liveModal.buttons)
|
|
918
|
+
&& liveModal.buttons.some((b: any) => typeof b === 'string' && b.trim());
|
|
919
|
+
if (liveModal && buttonsOk) {
|
|
920
|
+
effectiveModal = liveModal;
|
|
921
|
+
if (!this.engine.activeModal) this.engine.activeModal = liveModal;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
// Only surface waiting_approval when we ALSO have a concrete modal
|
|
926
|
+
// (message + buttons). detectStatus alone can fire while parseApproval
|
|
927
|
+
// is still null — the engine logs "detectStatus=waiting_approval but
|
|
928
|
+
// parseApproval returned null; ignoring". Without this guard getStatus
|
|
929
|
+
// was shipping a bare waiting_approval with activeModal=null and the
|
|
930
|
+
// user perceived the flow as broken.
|
|
931
|
+
if (startupDetectedStatus === 'waiting_approval' && effectiveModal) {
|
|
932
|
+
effectiveStatus = 'waiting_approval';
|
|
933
|
+
} else if (startupDetectedStatus === 'idle' && !startupModal && !effectiveModal) {
|
|
934
|
+
effectiveStatus = 'idle';
|
|
935
|
+
}
|
|
1569
936
|
if (allowParse && !startupModal && !effectiveModal) {
|
|
1570
937
|
const parsed = this.getFreshParsedStatusCache();
|
|
1571
938
|
const parsedModal = parsed?.activeModal && Array.isArray(parsed.activeModal.buttons)
|
|
1572
939
|
&& parsed.activeModal.buttons.some((button: any) => typeof button === 'string' && button.trim())
|
|
1573
940
|
? parsed.activeModal
|
|
1574
941
|
: null;
|
|
942
|
+
const hasFinalAssistant = (p: any) => {
|
|
943
|
+
const msgs = Array.isArray(p?.messages) ? p.messages : [];
|
|
944
|
+
return msgs.some((m: any) => m?.role === 'assistant' && typeof m.content === 'string' && m.content.trim());
|
|
945
|
+
};
|
|
1575
946
|
if (parsed?.status === 'waiting_approval' && parsedModal) {
|
|
1576
947
|
effectiveStatus = 'waiting_approval';
|
|
1577
948
|
effectiveModal = parsedModal;
|
|
949
|
+
} else if (
|
|
950
|
+
effectiveStatus === 'idle'
|
|
951
|
+
&& parsed?.status === 'generating'
|
|
952
|
+
&& !hasFinalAssistant(parsed)
|
|
953
|
+
) {
|
|
954
|
+
effectiveStatus = 'generating';
|
|
955
|
+
} else if (
|
|
956
|
+
effectiveStatus === 'generating'
|
|
957
|
+
&& parsed?.status === 'idle'
|
|
958
|
+
&& hasFinalAssistant(parsed)
|
|
959
|
+
) {
|
|
960
|
+
effectiveStatus = 'idle';
|
|
1578
961
|
}
|
|
1579
962
|
}
|
|
1580
963
|
const bufferState = this.getBufferState();
|
|
@@ -1583,8 +966,21 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1583
966
|
messages: [],
|
|
1584
967
|
workingDir: this.workingDir,
|
|
1585
968
|
activeModal: effectiveModal,
|
|
1586
|
-
|
|
1587
|
-
|
|
969
|
+
approvalEntrySeq: this.engine.approvalEntrySeq,
|
|
970
|
+
lastResolvedEntrySeq: this.engine.lastResolvedEntrySeq,
|
|
971
|
+
pendingOutboundCount: this.pendingOutboundQueue.length,
|
|
972
|
+
pendingOutboundMessages: this.pendingOutboundQueue.map((message) => ({
|
|
973
|
+
id: message.id,
|
|
974
|
+
role: message.role,
|
|
975
|
+
content: message.content,
|
|
976
|
+
queuedAt: message.queuedAt,
|
|
977
|
+
source: message.source,
|
|
978
|
+
})),
|
|
979
|
+
errorMessage: this.parseErrorMessage || this.engine.providerErrorMessage || undefined,
|
|
980
|
+
errorReason: this.parseErrorMessage ? 'parse_error' : (this.engine.providerErrorReason || undefined),
|
|
981
|
+
providerSessionId: this.providerSessionId || undefined,
|
|
982
|
+
lastOutputAt: this.lastOutputAt,
|
|
983
|
+
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
1588
984
|
...(bufferState ? { bufferState } : {}),
|
|
1589
985
|
};
|
|
1590
986
|
}
|
|
@@ -1600,15 +996,16 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1600
996
|
const cached = this.parsedStatusCache;
|
|
1601
997
|
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
1602
998
|
if (
|
|
1603
|
-
|
|
999
|
+
!this.providerOwnsTranscript()
|
|
1000
|
+
&& cached
|
|
1604
1001
|
&& cached.responseBuffer === this.responseBuffer
|
|
1605
|
-
&& cached.currentTurnScope === this.currentTurnScope
|
|
1002
|
+
&& cached.currentTurnScope === this.engine.currentTurnScope
|
|
1606
1003
|
&& cached.recentOutputBuffer === this.recentOutputBuffer
|
|
1607
1004
|
&& cached.accumulatedBuffer === this.accumulatedBuffer
|
|
1608
1005
|
&& cached.accumulatedRawBufferKey === accumulatedRawBufferKey
|
|
1609
1006
|
&& cached.screenText === parseScreenText
|
|
1610
|
-
&& cached.currentStatus === this.currentStatus
|
|
1611
|
-
&& cached.activeModal === this.activeModal
|
|
1007
|
+
&& cached.currentStatus === this.engine.currentStatus
|
|
1008
|
+
&& cached.activeModal === this.engine.activeModal
|
|
1612
1009
|
&& cached.cliName === this.cliName
|
|
1613
1010
|
) {
|
|
1614
1011
|
return cached.result;
|
|
@@ -1623,14 +1020,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1623
1020
|
const bufferState = this.getBufferState();
|
|
1624
1021
|
const result = {
|
|
1625
1022
|
id: (parsed as any).id || 'cli_session',
|
|
1626
|
-
status: parsed.status || this.currentStatus,
|
|
1023
|
+
status: parsed.status || this.engine.currentStatus,
|
|
1627
1024
|
title: (parsed as any).title || this.cliName,
|
|
1628
1025
|
messages: normalizeCliParsedMessages(parsed.messages, {
|
|
1629
1026
|
scope: null,
|
|
1630
1027
|
lastOutputAt: this.lastOutputAt,
|
|
1631
1028
|
}),
|
|
1632
1029
|
activeModal,
|
|
1633
|
-
providerSessionId: typeof (parsed as any).providerSessionId === 'string' ? (parsed as any).providerSessionId : undefined,
|
|
1030
|
+
providerSessionId: this.providerSessionId || (typeof (parsed as any).providerSessionId === 'string' ? (parsed as any).providerSessionId : undefined),
|
|
1031
|
+
errorMessage: typeof (parsed as any).errorMessage === 'string' && (parsed as any).errorMessage.trim()
|
|
1032
|
+
? (parsed as any).errorMessage.trim()
|
|
1033
|
+
: undefined,
|
|
1034
|
+
errorReason: typeof (parsed as any).errorReason === 'string' && (parsed as any).errorReason.trim()
|
|
1035
|
+
? (parsed as any).errorReason.trim()
|
|
1036
|
+
: undefined,
|
|
1634
1037
|
...(bufferState ? { bufferState } : {}),
|
|
1635
1038
|
...((parsed as any).transcriptAuthority === 'provider' || (parsed as any).transcriptAuthority === 'daemon'
|
|
1636
1039
|
? { transcriptAuthority: (parsed as any).transcriptAuthority }
|
|
@@ -1642,13 +1045,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1642
1045
|
|
|
1643
1046
|
this.parsedStatusCache = {
|
|
1644
1047
|
responseBuffer: this.responseBuffer,
|
|
1645
|
-
currentTurnScope: this.currentTurnScope,
|
|
1048
|
+
currentTurnScope: this.engine.currentTurnScope,
|
|
1646
1049
|
recentOutputBuffer: this.recentOutputBuffer,
|
|
1647
1050
|
accumulatedBuffer: this.accumulatedBuffer,
|
|
1648
1051
|
accumulatedRawBufferKey,
|
|
1649
1052
|
screenText: parseScreenText,
|
|
1650
|
-
currentStatus: this.currentStatus,
|
|
1651
|
-
activeModal: this.activeModal,
|
|
1053
|
+
currentStatus: this.engine.currentStatus,
|
|
1054
|
+
activeModal: this.engine.activeModal,
|
|
1652
1055
|
cliName: this.cliName,
|
|
1653
1056
|
result,
|
|
1654
1057
|
};
|
|
@@ -1656,22 +1059,22 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1656
1059
|
}
|
|
1657
1060
|
|
|
1658
1061
|
async invokeScript(scriptName: string, args?: Record<string, any>): Promise<any> {
|
|
1659
|
-
const fn = this.cliScripts?.[scriptName];
|
|
1660
|
-
if (typeof fn !== 'function') {
|
|
1661
|
-
throw new Error(`CLI script '${scriptName}' not available`);
|
|
1662
|
-
}
|
|
1663
1062
|
const input = buildCliParseInput({
|
|
1664
1063
|
accumulatedBuffer: this.accumulatedBuffer,
|
|
1665
1064
|
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1666
1065
|
recentOutputBuffer: this.recentOutputBuffer,
|
|
1667
1066
|
terminalScreenText: this.getParseScreenText(this.terminalScreen.getText()),
|
|
1067
|
+
workingDir: this.workingDir,
|
|
1068
|
+
providerSessionId: this.providerSessionId || undefined,
|
|
1069
|
+
historySessionId: this.providerSessionId || undefined,
|
|
1668
1070
|
baseMessages: [],
|
|
1669
1071
|
partialResponse: this.responseBuffer,
|
|
1670
|
-
isWaitingForResponse: this.isWaitingForResponse,
|
|
1671
|
-
scope: this.currentTurnScope,
|
|
1072
|
+
isWaitingForResponse: this.engine.isWaitingForResponse,
|
|
1073
|
+
scope: this.engine.currentTurnScope,
|
|
1672
1074
|
runtimeSettings: this.runtimeSettings,
|
|
1075
|
+
spawnAt: this.spawnAt,
|
|
1673
1076
|
});
|
|
1674
|
-
return await Promise.resolve(this.
|
|
1077
|
+
return await Promise.resolve(this.runner.invokeByName(scriptName, {
|
|
1675
1078
|
...input,
|
|
1676
1079
|
args: args && typeof args === 'object' ? { ...args } : {},
|
|
1677
1080
|
}));
|
|
@@ -1683,7 +1086,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1683
1086
|
|
|
1684
1087
|
/** Whether this adapter has CLI scripts loaded */
|
|
1685
1088
|
hasCliScripts(): boolean {
|
|
1686
|
-
return
|
|
1089
|
+
return this.runner.hasDetectStatus();
|
|
1687
1090
|
}
|
|
1688
1091
|
|
|
1689
1092
|
/**
|
|
@@ -1692,24 +1095,29 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1692
1095
|
*/
|
|
1693
1096
|
async resolveAction(data: any): Promise<void> {
|
|
1694
1097
|
let promptText = '';
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
}
|
|
1098
|
+
try {
|
|
1099
|
+
promptText = this.runner.invokeByName('resolveAction', data);
|
|
1100
|
+
} catch {
|
|
1101
|
+
LOG.warn('CLI', `[${this.cliType}] resolveAction skipped: provider script not available`);
|
|
1102
|
+
return;
|
|
1701
1103
|
}
|
|
1702
1104
|
if (!promptText) {
|
|
1703
|
-
LOG.warn('CLI', `[${this.cliType}] resolveAction skipped: provider script did not
|
|
1105
|
+
LOG.warn('CLI', `[${this.cliType}] resolveAction skipped: provider script did not return a prompt`);
|
|
1704
1106
|
return;
|
|
1705
1107
|
}
|
|
1706
1108
|
await this.sendMessage(promptText);
|
|
1707
1109
|
}
|
|
1708
1110
|
|
|
1111
|
+
async setInteractivePromptResponse(_response: InteractivePromptResponse): Promise<void> {
|
|
1112
|
+
// Legacy TUI providers do not currently expose an interactive prompt
|
|
1113
|
+
// protocol. Spec-backed claude-cli implements the first real wiring.
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1709
1116
|
private isSubmitStuck(normalizedPromptSnippet: string): boolean {
|
|
1710
|
-
if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return false;
|
|
1711
|
-
if (this.hasActionableApproval()) return false;
|
|
1712
|
-
|
|
1117
|
+
if (!this.ptyProcess || !this.engine.isWaitingForResponse || this.engine.submitRetryUsed) return false;
|
|
1118
|
+
if (this.engine.hasActionableApproval()) return false;
|
|
1119
|
+
// If there's already meaningful response content beyond the echoed prompt, not stuck
|
|
1120
|
+
if (this.hasMeaningfulResponseBufferLocal(normalizedPromptSnippet)) return false;
|
|
1713
1121
|
const screenText = this.terminalScreen.getText();
|
|
1714
1122
|
if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return false;
|
|
1715
1123
|
const liveApproval = this.runParseApproval(screenText) || this.runParseApproval(this.recentOutputBuffer);
|
|
@@ -1718,20 +1126,41 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1718
1126
|
return liveStatus !== 'generating' && liveStatus !== 'waiting_approval';
|
|
1719
1127
|
}
|
|
1720
1128
|
|
|
1129
|
+
private hasMeaningfulResponseBufferLocal(promptSnippet: string): boolean {
|
|
1130
|
+
const raw = String(this.responseBuffer || '').trim();
|
|
1131
|
+
if (!raw) return false;
|
|
1132
|
+
const normalizedPrompt = compactPromptText(promptSnippet);
|
|
1133
|
+
if (!normalizedPrompt) return true;
|
|
1134
|
+
const normalizedBuffer = compactPromptText(raw);
|
|
1135
|
+
if (!normalizedBuffer) return false;
|
|
1136
|
+
if (normalizedBuffer === normalizedPrompt) return false;
|
|
1137
|
+
if (normalizedBuffer.startsWith(normalizedPrompt)) {
|
|
1138
|
+
const remainder = normalizedBuffer
|
|
1139
|
+
.slice(normalizedPrompt.length)
|
|
1140
|
+
.replace(/[─═\-]+/g, '')
|
|
1141
|
+
.replace(/⏵⏵accepteditson\([^)]*\)/gi, '')
|
|
1142
|
+
.replace(/accepteditson\([^)]*\)/gi, '')
|
|
1143
|
+
.replace(/(?:◐|◑|◒|◓|◔|◕|◉|●|·)?(?:x?high|medium|low|max)·?\/effort/gi, '')
|
|
1144
|
+
.replace(/updateavailable!run:[a-z0-9:._\-/]+/gi, '')
|
|
1145
|
+
.replace(/esctointerrupt/gi, '')
|
|
1146
|
+
.replace(/❯/g, '')
|
|
1147
|
+
.replace(/^[\s\-–—:;,.!/?]+/, '')
|
|
1148
|
+
.trim();
|
|
1149
|
+
return remainder.length > 0;
|
|
1150
|
+
}
|
|
1151
|
+
return true;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1721
1154
|
private async writeToPty(data: string): Promise<void> {
|
|
1722
1155
|
if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
|
|
1723
1156
|
await this.ptyProcess.write(data);
|
|
1724
1157
|
}
|
|
1725
1158
|
|
|
1726
1159
|
private resetPendingSendState(reason: string): void {
|
|
1727
|
-
this.isWaitingForResponse = false;
|
|
1728
1160
|
this.responseBuffer = '';
|
|
1729
|
-
this.currentTurnScope = null;
|
|
1730
|
-
this.submitPendingUntil = 0;
|
|
1731
|
-
this.clearIdleFinishCandidate(reason);
|
|
1732
1161
|
if (this.responseTimeout) { clearTimeout(this.responseTimeout); this.responseTimeout = null; }
|
|
1733
|
-
|
|
1734
|
-
|
|
1162
|
+
this.engine.resetActiveTurnState();
|
|
1163
|
+
this.engine.clearIdleFinishCandidate(reason);
|
|
1735
1164
|
}
|
|
1736
1165
|
|
|
1737
1166
|
private commitSendUserTurn(state: SendMessageState): void {
|
|
@@ -1748,42 +1177,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1748
1177
|
}
|
|
1749
1178
|
this.responseTimeout = setTimeout(() => {
|
|
1750
1179
|
this.responseTimeout = null;
|
|
1751
|
-
if (!this.isWaitingForResponse) return;
|
|
1752
|
-
|
|
1753
|
-
const detectedStatusBeforeEval = this.runDetectStatus(this.recentOutputBuffer);
|
|
1754
|
-
this.recordTrace('response_timeout_check', {
|
|
1755
|
-
timeoutMs,
|
|
1756
|
-
detectedStatus: detectedStatusBeforeEval,
|
|
1757
|
-
currentStatus: this.currentStatus,
|
|
1758
|
-
isWaitingForResponse: this.isWaitingForResponse,
|
|
1759
|
-
hasActionableApproval: this.hasActionableApproval(),
|
|
1760
|
-
...buildCliTraceParseSnapshot({
|
|
1761
|
-
accumulatedBuffer: this.accumulatedBuffer,
|
|
1762
|
-
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1763
|
-
responseBuffer: this.responseBuffer,
|
|
1764
|
-
partialResponse: this.responseBuffer,
|
|
1765
|
-
scope: this.currentTurnScope,
|
|
1766
|
-
}),
|
|
1767
|
-
});
|
|
1180
|
+
if (!this.engine.isWaitingForResponse) return;
|
|
1768
1181
|
|
|
1769
|
-
// maxResponse is a watchdog/checkpoint, not a completion signal.
|
|
1770
|
-
// behavior called finishResponse() unconditionally at the default 300s,
|
|
1771
|
-
// which fabricated idle transitions and downstream generating_completed
|
|
1772
|
-
// notifications while long-running CLIs were still generating. Re-run the
|
|
1182
|
+
// maxResponse is a watchdog/checkpoint, not a completion signal. Re-run the
|
|
1773
1183
|
// normal settled parser instead and keep the turn open unless the provider
|
|
1774
1184
|
// actually reports an idle, commit-ready state.
|
|
1775
|
-
this.
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
if (this.isWaitingForResponse && !this.hasActionableApproval()) {
|
|
1779
|
-
const detectedStatusAfterEval = this.runDetectStatus(this.recentOutputBuffer);
|
|
1780
|
-
this.recordTrace('response_timeout_kept_open', {
|
|
1781
|
-
timeoutMs,
|
|
1782
|
-
detectedStatusBeforeEval,
|
|
1783
|
-
detectedStatusAfterEval,
|
|
1784
|
-
currentStatus: this.currentStatus,
|
|
1785
|
-
isWaitingForResponse: this.isWaitingForResponse,
|
|
1786
|
-
});
|
|
1185
|
+
this.engine.evaluateSettled(this.getSnapshot());
|
|
1186
|
+
|
|
1187
|
+
if (this.engine.isWaitingForResponse && !this.engine.hasActionableApproval()) {
|
|
1787
1188
|
this.armResponseTimeout();
|
|
1788
1189
|
}
|
|
1789
1190
|
}, timeoutMs);
|
|
@@ -1798,34 +1199,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1798
1199
|
private retrySubmitIfStuck(state: SendMessageState, attempt: number): void {
|
|
1799
1200
|
this.submitRetryTimer = null;
|
|
1800
1201
|
if (!this.isSubmitStuck(state.normalizedPromptSnippet)) return;
|
|
1801
|
-
|
|
1802
|
-
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1202
|
+
this.engine.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1803
1203
|
LOG.info('CLI', `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
|
|
1804
|
-
this.recordTrace('submit_write', {
|
|
1805
|
-
mode: 'submit_retry',
|
|
1806
|
-
attempt,
|
|
1807
|
-
sendKey: this.sendKey,
|
|
1808
|
-
screenText: summarizeCliTraceText(screenText, 500),
|
|
1809
|
-
});
|
|
1810
1204
|
this.writeSubmitKeyForRetry('submit_retry');
|
|
1811
|
-
if (attempt >= 3) { this.submitRetryUsed = true; return; }
|
|
1205
|
+
if (attempt >= 3) { this.engine.submitRetryUsed = true; return; }
|
|
1812
1206
|
this.submitRetryTimer = setTimeout(() => this.retrySubmitIfStuck(state, attempt + 1), state.retryDelayMs);
|
|
1813
1207
|
}
|
|
1814
1208
|
|
|
1815
1209
|
private retryImmediateSubmitIfStuck(state: SendMessageState): void {
|
|
1816
1210
|
this.submitRetryTimer = null;
|
|
1817
1211
|
if (!this.isSubmitStuck(state.normalizedPromptSnippet)) return;
|
|
1818
|
-
|
|
1819
|
-
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1212
|
+
this.engine.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1820
1213
|
LOG.info('CLI', `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
|
|
1821
|
-
this.recordTrace('submit_write', {
|
|
1822
|
-
mode: 'immediate_retry',
|
|
1823
|
-
attempt: 1,
|
|
1824
|
-
sendKey: this.sendKey,
|
|
1825
|
-
screenText: summarizeCliTraceText(screenText, 500),
|
|
1826
|
-
});
|
|
1827
1214
|
this.writeSubmitKeyForRetry('immediate_retry');
|
|
1828
|
-
this.submitRetryUsed = true;
|
|
1215
|
+
this.engine.submitRetryUsed = true;
|
|
1829
1216
|
}
|
|
1830
1217
|
|
|
1831
1218
|
private submitSendKey(state: SendMessageState, completion: SendMessageCompletion): void {
|
|
@@ -1833,13 +1220,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1833
1220
|
completion.resolveOnce();
|
|
1834
1221
|
return;
|
|
1835
1222
|
}
|
|
1836
|
-
this.submitPendingUntil = 0;
|
|
1837
|
-
const screenText = this.terminalScreen.getText();
|
|
1838
|
-
this.recordTrace('submit_write', {
|
|
1839
|
-
mode: 'submit_key',
|
|
1840
|
-
sendKey: this.sendKey,
|
|
1841
|
-
screenText: summarizeCliTraceText(screenText, 500),
|
|
1842
|
-
});
|
|
1223
|
+
this.engine.submitPendingUntil = 0;
|
|
1843
1224
|
void this.writeToPty(this.sendKey).then(() => {
|
|
1844
1225
|
this.commitSendUserTurn(state);
|
|
1845
1226
|
this.submitRetryTimer = setTimeout(() => this.retrySubmitIfStuck(state, 1), state.retryDelayMs);
|
|
@@ -1849,13 +1230,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1849
1230
|
}
|
|
1850
1231
|
|
|
1851
1232
|
private submitImmediatePrompt(state: SendMessageState, completion: SendMessageCompletion): void {
|
|
1852
|
-
this.submitPendingUntil = 0;
|
|
1853
|
-
this.recordTrace('submit_write', {
|
|
1854
|
-
mode: 'immediate',
|
|
1855
|
-
text: summarizeCliTraceText(state.text, 500),
|
|
1856
|
-
sendKey: this.sendKey,
|
|
1857
|
-
screenText: summarizeCliTraceText(this.terminalScreen.getText(), 500),
|
|
1858
|
-
});
|
|
1233
|
+
this.engine.submitPendingUntil = 0;
|
|
1859
1234
|
void this.writeToPty(state.text + this.sendKey).then(() => {
|
|
1860
1235
|
this.commitSendUserTurn(state);
|
|
1861
1236
|
this.submitRetryTimer = setTimeout(() => this.retryImmediateSubmitIfStuck(state), state.retryDelayMs);
|
|
@@ -1899,7 +1274,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1899
1274
|
requirePromptEchoBeforeSubmit: this.requirePromptEchoBeforeSubmit,
|
|
1900
1275
|
screenText: summarizeCliTraceText(screenText, 1000),
|
|
1901
1276
|
};
|
|
1902
|
-
|
|
1277
|
+
LOG.warn('CLI', `[${this.cliType}] submit_echo_missing: ${JSON.stringify(diagnostic)}`);
|
|
1278
|
+
|
|
1903
1279
|
if (this.requirePromptEchoBeforeSubmit) {
|
|
1904
1280
|
// At this point the prompt text write already completed. Rejecting without
|
|
1905
1281
|
// a submit key can leave the delegated CLI with an unsent prompt sitting at
|
|
@@ -1924,12 +1300,145 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1924
1300
|
), 50);
|
|
1925
1301
|
}
|
|
1926
1302
|
|
|
1927
|
-
async sendMessage(text: string): Promise<void> {
|
|
1303
|
+
async sendMessage(text: string, options: { force?: boolean } = {}): Promise<void> {
|
|
1304
|
+
if (options.force === true) {
|
|
1305
|
+
await this.forceSendMessage(text);
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
await this.sendMessageNow(text, true);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
async forceSendMessage(text: string): Promise<void> {
|
|
1312
|
+
if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
|
|
1313
|
+
const content = String(text || '');
|
|
1314
|
+
if (!content.trim()) return;
|
|
1315
|
+
// Modal-park guard (defense-in-depth — the primary guard is at the
|
|
1316
|
+
// cli-provider-instance force-forward chokepoint). A force-write writes raw
|
|
1317
|
+
// keystrokes into the PTY, bypassing the busy send-guard. If the session is
|
|
1318
|
+
// parked on a tool-consent modal, the modal's key handler eats those bytes
|
|
1319
|
+
// and silently resolves an approval the user never made. Hold the write and
|
|
1320
|
+
// let the mesh reconcile loop redeliver once the modal is resolved. We only
|
|
1321
|
+
// hold for an actionable approval modal; plain generating is still force-written
|
|
1322
|
+
// (that is the deadlock the force path exists to break).
|
|
1323
|
+
if (this.engine.currentStatus === 'waiting_approval' || this.engine.hasActionableApproval()) {
|
|
1324
|
+
LOG.info('CLI', `[${this.cliType}] force-send held — session parked on approval modal (status=${this.engine.currentStatus})`);
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
LOG.info('CLI', `[${this.cliType}] force-sending prompt while status=${this.engine.currentStatus}`);
|
|
1328
|
+
// Settle, then submit atomically. The previous split-write fix
|
|
1329
|
+
// (write text → settle → write a separate '\r') regressed win32:
|
|
1330
|
+
// ConPTY does not treat a lone CR arriving in its own PTY chunk after a
|
|
1331
|
+
// delay as a submit key, so the Enter was swallowed and the prompt sat
|
|
1332
|
+
// typed-but-unsent. Instead, honor a fixed settle gap so the TUI input
|
|
1333
|
+
// handler is in its input-accepting state, then write `content + sendKey`
|
|
1334
|
+
// as ONE PTY write — the same atomic submit the normal `immediate`
|
|
1335
|
+
// strategy uses. Keeping the Enter in the same write unit as the text is
|
|
1336
|
+
// the invariant that makes win32 ConPTY recognize the submit, and it
|
|
1337
|
+
// also makes the original inject↔submit race impossible (the Enter can
|
|
1338
|
+
// never be separated from the text it submits). The settle still guards
|
|
1339
|
+
// the original concern — that a force-dispatch injected into a freshly
|
|
1340
|
+
// idle session lands before the TUI is ready to accept input.
|
|
1341
|
+
await this.waitForForceSubmitSettle();
|
|
1342
|
+
await this.writeToPty(content + this.sendKey);
|
|
1343
|
+
this.onStatusChange?.();
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
private async waitForForceSubmitSettle(): Promise<void> {
|
|
1347
|
+
// A fixed minimum gap so the input handler is ready to accept the paste.
|
|
1348
|
+
// We deliberately do NOT echo-gate here: the submit key is written in the
|
|
1349
|
+
// same atomic PTY write as the text (forceSendMessage), so there is no
|
|
1350
|
+
// separate Enter that could race the echo, and on win32 the echo gate
|
|
1351
|
+
// never reliably matched anyway.
|
|
1352
|
+
await new Promise<void>(resolve => setTimeout(resolve, FORCE_SUBMIT_SETTLE_MS));
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
private enqueuePendingOutboundMessage(text: string, reason: string): void {
|
|
1356
|
+
const content = String(text || '');
|
|
1357
|
+
const duplicate = this.pendingOutboundQueue.some((message) => message.content === content);
|
|
1358
|
+
if (duplicate) {
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
const queuedAt = Date.now();
|
|
1362
|
+
const message: PendingOutboundMessage = {
|
|
1363
|
+
id: `${queuedAt}:${this.pendingOutboundQueue.length}:${Math.random().toString(36).slice(2, 10)}`,
|
|
1364
|
+
role: 'user',
|
|
1365
|
+
content,
|
|
1366
|
+
queuedAt,
|
|
1367
|
+
source: 'sendMessage',
|
|
1368
|
+
};
|
|
1369
|
+
this.pendingOutboundQueue.push(message);
|
|
1370
|
+
LOG.info('CLI', `[${this.cliType}] queued outbound message while busy (${reason}); queue=${this.pendingOutboundQueue.length}`);
|
|
1371
|
+
this.onStatusChange?.();
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
private shouldQueuePendingOutboundMessage(parsedStatusBeforeSend: any | null = null): string | null {
|
|
1375
|
+
if (this.provider.allowInputDuringGeneration === true) return null;
|
|
1376
|
+
if (this.engine.hasActionableApproval()) return null;
|
|
1377
|
+
const parsedSessionStatus = typeof parsedStatusBeforeSend?.status === 'string'
|
|
1378
|
+
? String(parsedStatusBeforeSend.status)
|
|
1379
|
+
: '';
|
|
1380
|
+
const hasFinalAssistant = (p: any) => {
|
|
1381
|
+
const msgs = Array.isArray(p?.messages) ? p.messages : [];
|
|
1382
|
+
return msgs.some((m: any) => m?.role === 'assistant' && typeof m.content === 'string' && m.content.trim());
|
|
1383
|
+
};
|
|
1384
|
+
if (parsedSessionStatus === 'idle' && hasFinalAssistant(parsedStatusBeforeSend)) return null;
|
|
1385
|
+
if (this.engine.currentStatus === 'generating') return 'current_status_generating';
|
|
1386
|
+
if (parsedSessionStatus === 'generating' || parsedSessionStatus === 'no_progress' || parsedSessionStatus === 'long_generating') {
|
|
1387
|
+
const parsedModal = parsedStatusBeforeSend?.activeModal ?? parsedStatusBeforeSend?.modal ?? null;
|
|
1388
|
+
const parsedHasActionableModal = Boolean(
|
|
1389
|
+
parsedModal
|
|
1390
|
+
&& Array.isArray(parsedModal.buttons)
|
|
1391
|
+
&& parsedModal.buttons.some((candidate: unknown) => typeof candidate === 'string' && candidate.trim()),
|
|
1392
|
+
);
|
|
1393
|
+
const terminalLooksIdle = this.engine.currentStatus === 'idle'
|
|
1394
|
+
&& this.runDetectStatus(this.recentOutputBuffer) === 'idle'
|
|
1395
|
+
&& !this.engine.isWaitingForResponse
|
|
1396
|
+
&& !this.engine.currentTurnScope
|
|
1397
|
+
&& !this.engine.hasActionableApproval()
|
|
1398
|
+
&& !parsedHasActionableModal;
|
|
1399
|
+
return terminalLooksIdle ? null : `parsed_status_${parsedSessionStatus}`;
|
|
1400
|
+
}
|
|
1401
|
+
if (this.engine.isWaitingForResponse && this.engine.currentTurnScope) return 'active_turn_in_progress';
|
|
1402
|
+
return null;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
private schedulePendingOutboundFlush(delayMs = 0): void {
|
|
1406
|
+
if (this.pendingOutboundFlushTimer) clearTimeout(this.pendingOutboundFlushTimer);
|
|
1407
|
+
this.pendingOutboundFlushTimer = setTimeout(() => {
|
|
1408
|
+
this.pendingOutboundFlushTimer = null;
|
|
1409
|
+
void this.flushPendingOutboundQueue();
|
|
1410
|
+
}, Math.max(0, delayMs));
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
private async flushPendingOutboundQueue(): Promise<void> {
|
|
1414
|
+
if (this.pendingOutboundFlushInFlight || this.pendingOutboundQueue.length === 0) return;
|
|
1415
|
+
if (this.engine.currentStatus !== 'idle' || this.engine.isWaitingForResponse || this.engine.hasActionableApproval()) return;
|
|
1416
|
+
this.pendingOutboundFlushInFlight = true;
|
|
1417
|
+
try {
|
|
1418
|
+
while (this.pendingOutboundQueue.length > 0) {
|
|
1419
|
+
if (this.engine.currentStatus !== 'idle' || this.engine.isWaitingForResponse || this.engine.hasActionableApproval()) break;
|
|
1420
|
+
const next = this.pendingOutboundQueue[0];
|
|
1421
|
+
try {
|
|
1422
|
+
await this.sendMessageNow(next.content, false);
|
|
1423
|
+
this.pendingOutboundQueue.shift();
|
|
1424
|
+
this.onStatusChange?.();
|
|
1425
|
+
} catch (error: any) {
|
|
1426
|
+
LOG.warn('CLI', `[${this.cliType}] queued outbound flush failed: ${error?.message || error}`);
|
|
1427
|
+
this.schedulePendingOutboundFlush(1000);
|
|
1428
|
+
break;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
} finally {
|
|
1432
|
+
this.pendingOutboundFlushInFlight = false;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
private async sendMessageNow(text: string, allowQueue: boolean): Promise<void> {
|
|
1928
1437
|
if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
|
|
1929
1438
|
const allowInputDuringGeneration = this.provider.allowInputDuringGeneration === true;
|
|
1930
1439
|
const allowInterventionPrompt = allowInputDuringGeneration
|
|
1931
|
-
&& this.isWaitingForResponse
|
|
1932
|
-
&& !this.hasActionableApproval();
|
|
1440
|
+
&& this.engine.isWaitingForResponse
|
|
1441
|
+
&& !this.engine.hasActionableApproval();
|
|
1933
1442
|
if (this.startupParseGate) {
|
|
1934
1443
|
const deadline = Date.now() + 10000;
|
|
1935
1444
|
while (this.startupParseGate && Date.now() < deadline) {
|
|
@@ -1937,76 +1446,85 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1937
1446
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
1938
1447
|
}
|
|
1939
1448
|
}
|
|
1449
|
+
const parsedStatusBeforeSend = !allowInputDuringGeneration
|
|
1450
|
+
? (() => {
|
|
1451
|
+
try {
|
|
1452
|
+
return this.getScriptParsedStatus?.() || null;
|
|
1453
|
+
} catch {
|
|
1454
|
+
return null;
|
|
1455
|
+
}
|
|
1456
|
+
})()
|
|
1457
|
+
: null;
|
|
1458
|
+
const queueReason = this.shouldQueuePendingOutboundMessage(parsedStatusBeforeSend);
|
|
1459
|
+
if (allowQueue && queueReason) {
|
|
1460
|
+
this.enqueuePendingOutboundMessage(text, queueReason);
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1940
1463
|
if (!allowInterventionPrompt) {
|
|
1941
1464
|
await this.waitForInteractivePrompt();
|
|
1942
1465
|
}
|
|
1943
1466
|
if (!this.ready) {
|
|
1944
1467
|
this.resolveStartupState('send_precheck');
|
|
1945
|
-
if (this.runDetectStatus(this.recentOutputBuffer) === 'idle'
|
|
1468
|
+
if (this.runDetectStatus(this.recentOutputBuffer) === 'idle') {
|
|
1946
1469
|
this.ready = true;
|
|
1947
1470
|
this.startupParseGate = false;
|
|
1471
|
+
this.engine.setStatus('idle', 'send_message_idle_prompt_recovery');
|
|
1948
1472
|
LOG.info('CLI', `[${this.cliType}] sendMessage recovered idle prompt readiness`);
|
|
1949
1473
|
}
|
|
1950
1474
|
}
|
|
1951
|
-
if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
|
|
1952
|
-
const parsedStatusBeforeSend = !allowInputDuringGeneration
|
|
1953
|
-
? (() => {
|
|
1954
|
-
try {
|
|
1955
|
-
return this.getScriptParsedStatus?.() || null;
|
|
1956
|
-
} catch {
|
|
1957
|
-
return null;
|
|
1958
|
-
}
|
|
1959
|
-
})()
|
|
1960
|
-
: null;
|
|
1475
|
+
if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.engine.currentStatus})`);
|
|
1961
1476
|
const parsedSessionStatus = typeof parsedStatusBeforeSend?.status === 'string'
|
|
1962
1477
|
? String(parsedStatusBeforeSend.status)
|
|
1963
1478
|
: '';
|
|
1964
|
-
if (!allowInputDuringGeneration && (parsedSessionStatus === 'generating' || parsedSessionStatus === 'long_generating')) {
|
|
1479
|
+
if (!allowInputDuringGeneration && (parsedSessionStatus === 'generating' || parsedSessionStatus === 'no_progress' || parsedSessionStatus === 'long_generating')) {
|
|
1965
1480
|
const parsedModal = parsedStatusBeforeSend?.activeModal ?? parsedStatusBeforeSend?.modal ?? null;
|
|
1966
1481
|
const parsedHasActionableModal = Boolean(
|
|
1967
1482
|
parsedModal
|
|
1968
1483
|
&& Array.isArray(parsedModal.buttons)
|
|
1969
1484
|
&& parsedModal.buttons.some((candidate: unknown) => typeof candidate === 'string' && candidate.trim()),
|
|
1970
1485
|
);
|
|
1971
|
-
const terminalLooksIdle = this.currentStatus === 'idle'
|
|
1486
|
+
const terminalLooksIdle = this.engine.currentStatus === 'idle'
|
|
1972
1487
|
&& this.runDetectStatus(this.recentOutputBuffer) === 'idle'
|
|
1973
|
-
&& !this.isWaitingForResponse
|
|
1974
|
-
&& !this.currentTurnScope
|
|
1975
|
-
&& !this.hasActionableApproval()
|
|
1488
|
+
&& !this.engine.isWaitingForResponse
|
|
1489
|
+
&& !this.engine.currentTurnScope
|
|
1490
|
+
&& !this.engine.hasActionableApproval()
|
|
1976
1491
|
&& !parsedHasActionableModal;
|
|
1977
1492
|
if (!terminalLooksIdle) {
|
|
1493
|
+
if (allowQueue) {
|
|
1494
|
+
this.enqueuePendingOutboundMessage(text, `parsed_status_${parsedSessionStatus}`);
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1978
1497
|
throw new Error(`${this.cliName} is still processing the previous prompt`);
|
|
1979
1498
|
}
|
|
1980
1499
|
}
|
|
1981
|
-
if (this.isWaitingForResponse && !allowInputDuringGeneration) {
|
|
1982
|
-
|
|
1500
|
+
if (this.engine.isWaitingForResponse && !allowInputDuringGeneration) {
|
|
1501
|
+
const snap = this.getSnapshot();
|
|
1502
|
+
if (
|
|
1503
|
+
!this.engine.clearStaleIdleResponseGuard('send_message_guard', snap)
|
|
1504
|
+
&& !this.engine.clearParsedIdleResponseGuard('send_message_parsed_idle_guard', parsedStatusBeforeSend, snap)
|
|
1505
|
+
) {
|
|
1506
|
+
if (allowQueue) {
|
|
1507
|
+
this.enqueuePendingOutboundMessage(text, 'waiting_for_response');
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1983
1510
|
throw new Error(`${this.cliName} is still processing the previous prompt`);
|
|
1984
1511
|
}
|
|
1985
1512
|
}
|
|
1986
|
-
this.isWaitingForResponse = true;
|
|
1987
1513
|
this.responseBuffer = '';
|
|
1988
|
-
|
|
1989
|
-
if (this.finishRetryTimer) { clearTimeout(this.finishRetryTimer); this.finishRetryTimer = null; }
|
|
1990
|
-
this.clearIdleFinishCandidate('send_message');
|
|
1991
|
-
this.currentTurnScope = {
|
|
1514
|
+
const turnScope: TurnParseScope = {
|
|
1992
1515
|
prompt: text,
|
|
1993
1516
|
startedAt: Date.now(),
|
|
1994
1517
|
bufferStart: this.accumulatedBuffer.length,
|
|
1995
1518
|
rawBufferStart: this.accumulatedRawBuffer.length,
|
|
1996
1519
|
};
|
|
1997
|
-
|
|
1998
|
-
text: summarizeCliTraceText(text, 500),
|
|
1999
|
-
estimatedLines: estimatePromptDisplayLines(text),
|
|
2000
|
-
turnScope: this.currentTurnScope,
|
|
2001
|
-
});
|
|
2002
|
-
LOG.info('CLI', `[${this.cliType}] sendMessage turn scope buffer=${this.currentTurnScope.bufferStart} raw=${this.currentTurnScope.rawBufferStart} prompt=${JSON.stringify(text).slice(0, 120)}`);
|
|
2003
|
-
this.submitRetryUsed = false;
|
|
2004
|
-
this.submitRetryPromptSnippet = extractPromptRetrySnippet(text);
|
|
2005
|
-
const normalizedPromptSnippet = normalizePromptText(this.submitRetryPromptSnippet);
|
|
1520
|
+
LOG.info('CLI', `[${this.cliType}] sendMessage turn scope buffer=${turnScope.bufferStart} raw=${turnScope.rawBufferStart} prompt=${JSON.stringify(text).slice(0, 120)}`);
|
|
2006
1521
|
if (this.submitRetryTimer) {
|
|
2007
1522
|
clearTimeout(this.submitRetryTimer);
|
|
2008
1523
|
this.submitRetryTimer = null;
|
|
2009
1524
|
}
|
|
1525
|
+
this.engine.onTurnStarted(turnScope);
|
|
1526
|
+
this.engine.submitRetryPromptSnippet = extractPromptRetrySnippet(text);
|
|
1527
|
+
const normalizedPromptSnippet = normalizePromptText(this.engine.submitRetryPromptSnippet);
|
|
2010
1528
|
const estimatedLines = estimatePromptDisplayLines(text);
|
|
2011
1529
|
const submitDelayMs = this.sendDelayMs + Math.min(2000, Math.max(0, estimatedLines - 1) * 350);
|
|
2012
1530
|
const maxEchoWaitMs = submitDelayMs + Math.max(1500, Math.min(5000, estimatedLines * 500));
|
|
@@ -2019,12 +1537,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2019
1537
|
retryDelayMs,
|
|
2020
1538
|
didCommitUserTurn: false,
|
|
2021
1539
|
};
|
|
2022
|
-
|
|
2023
|
-
clearTimeout(this.settleTimer);
|
|
2024
|
-
this.settleTimer = null;
|
|
2025
|
-
}
|
|
2026
|
-
this.responseEpoch += 1;
|
|
2027
|
-
this.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
|
|
1540
|
+
this.engine.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
|
|
2028
1541
|
await new Promise<void>((resolve, reject) => {
|
|
2029
1542
|
let resolved = false;
|
|
2030
1543
|
const completion: SendMessageCompletion = {
|
|
@@ -2047,24 +1560,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2047
1560
|
}
|
|
2048
1561
|
|
|
2049
1562
|
if (submitDelayMs > 0) {
|
|
2050
|
-
this.submitPendingUntil = Date.now() + submitDelayMs;
|
|
1563
|
+
this.engine.submitPendingUntil = Date.now() + submitDelayMs;
|
|
2051
1564
|
}
|
|
2052
|
-
this.recordTrace('submit_write', {
|
|
2053
|
-
mode: 'type_then_submit',
|
|
2054
|
-
text: summarizeCliTraceText(text, 500),
|
|
2055
|
-
sendKey: this.sendKey,
|
|
2056
|
-
screenText: summarizeCliTraceText(this.terminalScreen.getText(), 500),
|
|
2057
|
-
});
|
|
2058
1565
|
const submitStartedAt = Date.now();
|
|
2059
1566
|
void this.writeToPty(text).then(
|
|
2060
1567
|
() => this.waitForEchoAndSubmit(sendState, completion, submitStartedAt),
|
|
2061
1568
|
completion.rejectOnce,
|
|
2062
1569
|
);
|
|
2063
1570
|
});
|
|
1571
|
+
// Schedule settle after successful send
|
|
1572
|
+
this.engine.scheduleSettle();
|
|
2064
1573
|
}
|
|
2065
1574
|
|
|
2066
1575
|
getPartialResponse(): string {
|
|
2067
|
-
if (!this.isWaitingForResponse) return '';
|
|
1576
|
+
if (!this.engine.isWaitingForResponse) return '';
|
|
2068
1577
|
return this.responseBuffer;
|
|
2069
1578
|
}
|
|
2070
1579
|
|
|
@@ -2077,10 +1586,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2077
1586
|
cliType: this.cliType,
|
|
2078
1587
|
cliName: this.cliName,
|
|
2079
1588
|
workingDir: this.workingDir,
|
|
2080
|
-
currentStatus: this.currentStatus,
|
|
1589
|
+
currentStatus: this.engine.currentStatus,
|
|
2081
1590
|
ready: this.ready,
|
|
2082
|
-
isWaitingForResponse: this.isWaitingForResponse,
|
|
2083
|
-
activeModal: this.activeModal,
|
|
1591
|
+
isWaitingForResponse: this.engine.isWaitingForResponse,
|
|
1592
|
+
activeModal: this.engine.activeModal,
|
|
2084
1593
|
parseErrorMessage: this.parseErrorMessage,
|
|
2085
1594
|
messageCounts: {
|
|
2086
1595
|
parsedCache: Array.isArray(parsedResult?.messages) ? parsedResult.messages.length : undefined,
|
|
@@ -2106,10 +1615,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2106
1615
|
lastScreenSnapshotReadAt: this.lastScreenSnapshotReadAt,
|
|
2107
1616
|
},
|
|
2108
1617
|
parser: {
|
|
2109
|
-
scriptNames:
|
|
2110
|
-
traceSessionId: this.
|
|
2111
|
-
traceSeq: this.
|
|
2112
|
-
currentTurnScope: this.currentTurnScope,
|
|
1618
|
+
scriptNames: this.runner.getScriptNames(),
|
|
1619
|
+
traceSessionId: this.engine.getTraceSessionId(),
|
|
1620
|
+
traceSeq: this.engine.getTraceEntries().length,
|
|
1621
|
+
currentTurnScope: this.engine.currentTurnScope,
|
|
2113
1622
|
parsedStatusCache: parsedResult
|
|
2114
1623
|
? {
|
|
2115
1624
|
id: parsedResult.id,
|
|
@@ -2122,26 +1631,25 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2122
1631
|
activeModal: parsedResult.activeModal,
|
|
2123
1632
|
}
|
|
2124
1633
|
: null,
|
|
2125
|
-
pendingScriptStatus: this.pendingScriptStatus,
|
|
2126
|
-
pendingScriptStatusSince: this.pendingScriptStatusSince,
|
|
1634
|
+
pendingScriptStatus: this.engine.pendingScriptStatus,
|
|
1635
|
+
pendingScriptStatusSince: this.engine.pendingScriptStatusSince,
|
|
2127
1636
|
},
|
|
2128
1637
|
runtimeMetadata: this.getRuntimeMetadata(),
|
|
2129
|
-
statusHistory: this.
|
|
2130
|
-
traceEntries: this.
|
|
1638
|
+
statusHistory: this.engine.getStatusHistory().slice(-80),
|
|
1639
|
+
traceEntries: this.engine.getTraceEntries().slice(-120),
|
|
2131
1640
|
timing: {
|
|
2132
1641
|
spawnAt: this.spawnAt,
|
|
2133
1642
|
startupFirstOutputAt: this.startupFirstOutputAt,
|
|
2134
|
-
submitPendingUntil: this.submitPendingUntil,
|
|
2135
|
-
responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
|
|
2136
|
-
responseEpoch: this.responseEpoch,
|
|
1643
|
+
submitPendingUntil: this.engine.submitPendingUntil,
|
|
1644
|
+
responseSettleIgnoreUntil: this.engine.responseSettleIgnoreUntil,
|
|
1645
|
+
responseEpoch: this.engine.responseEpoch,
|
|
2137
1646
|
resizeSuppressUntil: this.resizeSuppressUntil,
|
|
2138
|
-
lastApprovalResolvedAt: this.lastApprovalResolvedAt,
|
|
1647
|
+
lastApprovalResolvedAt: this.engine.lastApprovalResolvedAt,
|
|
2139
1648
|
},
|
|
2140
1649
|
finish: {
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
submitRetryPromptSnippet: this.submitRetryPromptSnippet,
|
|
1650
|
+
finishRetryCount: this.engine.finishRetryCount,
|
|
1651
|
+
submitRetryUsed: this.engine.submitRetryUsed,
|
|
1652
|
+
submitRetryPromptSnippet: this.engine.submitRetryPromptSnippet,
|
|
2145
1653
|
},
|
|
2146
1654
|
};
|
|
2147
1655
|
}
|
|
@@ -2151,7 +1659,48 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2151
1659
|
return this.ptyProcess.getMetadata();
|
|
2152
1660
|
}
|
|
2153
1661
|
|
|
1662
|
+
/**
|
|
1663
|
+
* Launch metadata for the dashboard Session info panel. Re-derives the spawn plan
|
|
1664
|
+
* (pure — same inputs the live PTY was spawned with) so the dashboard sees the
|
|
1665
|
+
* resolved binary, full arg vector, and cwd without us having to persist it.
|
|
1666
|
+
* extraEnv values are intentionally dropped (keys only) so secrets passed at
|
|
1667
|
+
* launch time never reach the dashboard.
|
|
1668
|
+
*/
|
|
1669
|
+
getLaunchInfo(): CliLaunchInfo {
|
|
1670
|
+
let command: string | undefined;
|
|
1671
|
+
let args: string[] = [...this.extraArgs];
|
|
1672
|
+
try {
|
|
1673
|
+
const plan = resolveCliSpawnPlan({
|
|
1674
|
+
provider: this.provider,
|
|
1675
|
+
runtimeSettings: this.runtimeSettings,
|
|
1676
|
+
workingDir: this.workingDir,
|
|
1677
|
+
extraArgs: this.extraArgs,
|
|
1678
|
+
extraEnv: this.extraEnv,
|
|
1679
|
+
});
|
|
1680
|
+
command = plan.binaryPath;
|
|
1681
|
+
args = plan.allArgs;
|
|
1682
|
+
} catch {
|
|
1683
|
+
// Spawn plan resolution can throw before the binary is resolvable
|
|
1684
|
+
// (e.g. CLI not installed). Fall back to the raw extra args so the
|
|
1685
|
+
// panel still shows what it can rather than failing the whole call.
|
|
1686
|
+
}
|
|
1687
|
+
return {
|
|
1688
|
+
command,
|
|
1689
|
+
args,
|
|
1690
|
+
extraArgs: [...this.extraArgs],
|
|
1691
|
+
cwd: this.workingDir,
|
|
1692
|
+
extraEnvKeys: Object.keys(this.extraEnv || {}),
|
|
1693
|
+
providerSessionId: this.providerSessionId || undefined,
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
|
|
2154
1697
|
updateRuntimeMeta(meta: Record<string, unknown>, replace = false): void {
|
|
1698
|
+
const nextProviderSessionId = typeof meta?.providerSessionId === 'string'
|
|
1699
|
+
? meta.providerSessionId.trim()
|
|
1700
|
+
: '';
|
|
1701
|
+
if (nextProviderSessionId) {
|
|
1702
|
+
this.providerSessionId = nextProviderSessionId;
|
|
1703
|
+
}
|
|
2155
1704
|
if (!this.ptyProcess || typeof this.ptyProcess.updateMeta !== 'function') return;
|
|
2156
1705
|
this.ptyProcess.updateMeta(meta, replace);
|
|
2157
1706
|
}
|
|
@@ -2172,7 +1721,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2172
1721
|
this.timeouts.shutdownGrace,
|
|
2173
1722
|
typeof resume.shutdownGraceMs === 'number' ? resume.shutdownGraceMs : 3000,
|
|
2174
1723
|
);
|
|
2175
|
-
const wasProcessing = this.currentStatus === 'generating' || this.currentStatus === 'waiting_approval';
|
|
1724
|
+
const wasProcessing = this.engine.currentStatus === 'generating' || this.engine.currentStatus === 'waiting_approval';
|
|
2176
1725
|
|
|
2177
1726
|
try {
|
|
2178
1727
|
if (wasProcessing) {
|
|
@@ -2210,7 +1759,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2210
1759
|
return new Promise((resolve) => {
|
|
2211
1760
|
const startedAt = Date.now();
|
|
2212
1761
|
const timer = setInterval(() => {
|
|
2213
|
-
if (!this.ptyProcess || this.currentStatus === 'stopped') {
|
|
1762
|
+
if (!this.ptyProcess || this.engine.currentStatus === 'stopped') {
|
|
2214
1763
|
clearInterval(timer);
|
|
2215
1764
|
resolve(true);
|
|
2216
1765
|
return;
|
|
@@ -2224,18 +1773,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2224
1773
|
}
|
|
2225
1774
|
|
|
2226
1775
|
shutdown(): void {
|
|
2227
|
-
this.clearIdleFinishCandidate('shutdown');
|
|
1776
|
+
this.engine.clearIdleFinishCandidate('shutdown');
|
|
2228
1777
|
this.clearAllTimers();
|
|
2229
1778
|
this.pendingOutputParseChunks = [];
|
|
2230
1779
|
this.pendingTerminalQueryTail = '';
|
|
2231
1780
|
this.ptyOutputChunks = [];
|
|
2232
|
-
this.
|
|
1781
|
+
if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
|
|
1782
|
+
this.pendingOutboundQueue = [];
|
|
1783
|
+
this.pendingOutboundFlushInFlight = false;
|
|
2233
1784
|
if (this.ptyProcess) {
|
|
2234
1785
|
this.ptyProcess.write('\x03');
|
|
2235
1786
|
setTimeout(() => {
|
|
2236
1787
|
try { this.ptyProcess?.kill(); } catch { }
|
|
2237
1788
|
this.ptyProcess = null;
|
|
2238
|
-
this.setStatus('stopped', 'stop_cmd');
|
|
1789
|
+
this.engine.setStatus('stopped', 'stop_cmd');
|
|
2239
1790
|
this.ready = false;
|
|
2240
1791
|
this.startupParseGate = false;
|
|
2241
1792
|
this.spawnAt = 0;
|
|
@@ -2245,12 +1796,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2245
1796
|
}
|
|
2246
1797
|
|
|
2247
1798
|
detach(): void {
|
|
2248
|
-
this.clearIdleFinishCandidate('detach');
|
|
1799
|
+
this.engine.clearIdleFinishCandidate('detach');
|
|
2249
1800
|
this.clearAllTimers();
|
|
2250
1801
|
this.pendingOutputParseChunks = [];
|
|
2251
1802
|
this.pendingTerminalQueryTail = '';
|
|
2252
1803
|
this.ptyOutputChunks = [];
|
|
2253
|
-
this.
|
|
1804
|
+
if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
|
|
1805
|
+
this.pendingOutboundQueue = [];
|
|
1806
|
+
this.pendingOutboundFlushInFlight = false;
|
|
2254
1807
|
if (this.ptyProcess) {
|
|
2255
1808
|
try {
|
|
2256
1809
|
if (typeof this.ptyProcess.detach === 'function') {
|
|
@@ -2268,82 +1821,128 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2268
1821
|
}
|
|
2269
1822
|
|
|
2270
1823
|
clearHistory(): void {
|
|
2271
|
-
this.clearIdleFinishCandidate('clear_history');
|
|
1824
|
+
this.engine.clearIdleFinishCandidate('clear_history');
|
|
2272
1825
|
this.accumulatedBuffer = '';
|
|
2273
1826
|
this.accumulatedRawBuffer = '';
|
|
2274
|
-
this.currentTurnScope = null;
|
|
2275
|
-
this.submitRetryUsed = false;
|
|
2276
|
-
this.submitRetryPromptSnippet = '';
|
|
1827
|
+
this.engine.currentTurnScope = null;
|
|
1828
|
+
this.engine.submitRetryUsed = false;
|
|
1829
|
+
this.engine.submitRetryPromptSnippet = '';
|
|
2277
1830
|
if (this.pendingOutputParseTimer) { clearTimeout(this.pendingOutputParseTimer); this.pendingOutputParseTimer = null; }
|
|
2278
1831
|
this.pendingOutputParseChunks = [];
|
|
2279
1832
|
this.pendingTerminalQueryTail = '';
|
|
2280
1833
|
if (this.ptyOutputFlushTimer) { clearTimeout(this.ptyOutputFlushTimer); this.ptyOutputFlushTimer = null; }
|
|
2281
1834
|
this.ptyOutputChunks = [];
|
|
2282
|
-
if (this.
|
|
2283
|
-
this.
|
|
1835
|
+
if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
|
|
1836
|
+
this.pendingOutboundQueue = [];
|
|
1837
|
+
this.pendingOutboundFlushInFlight = false;
|
|
2284
1838
|
this.resetTerminalScreen();
|
|
2285
1839
|
this.ptyProcess?.clearBuffer?.();
|
|
2286
1840
|
this.onStatusChange?.();
|
|
2287
1841
|
}
|
|
2288
1842
|
|
|
2289
|
-
isProcessing(): boolean { return this.isWaitingForResponse; }
|
|
1843
|
+
isProcessing(): boolean { return this.engine.isWaitingForResponse; }
|
|
2290
1844
|
isReady(): boolean { return this.ready; }
|
|
2291
1845
|
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
1846
|
+
// ─── State machine property accessors (delegate to engine) ──────────────
|
|
1847
|
+
// These expose engine state for external callers (tests, debug tools, etc.)
|
|
1848
|
+
|
|
1849
|
+
get currentStatus(): CliSessionStatus['status'] { return this.engine.currentStatus; }
|
|
1850
|
+
set currentStatus(v: CliSessionStatus['status']) { this.engine.setStatus(v); }
|
|
1851
|
+
|
|
1852
|
+
get isWaitingForResponse(): boolean { return this.engine.isWaitingForResponse; }
|
|
1853
|
+
set isWaitingForResponse(v: boolean) { this.engine.isWaitingForResponse = v; }
|
|
1854
|
+
|
|
1855
|
+
get activeModal(): { message: string; buttons: string[] } | null { return this.engine.activeModal; }
|
|
1856
|
+
set activeModal(v: { message: string; buttons: string[] } | null) { this.engine.activeModal = v; }
|
|
1857
|
+
|
|
1858
|
+
get currentTurnScope(): TurnParseScope | null { return this.engine.currentTurnScope; }
|
|
1859
|
+
set currentTurnScope(v: TurnParseScope | null) { this.engine.currentTurnScope = v; }
|
|
1860
|
+
|
|
1861
|
+
get responseEpoch(): number { return this.engine.responseEpoch; }
|
|
1862
|
+
set responseEpoch(v: number) { this.engine.responseEpoch = v; }
|
|
1863
|
+
|
|
1864
|
+
get submitRetryUsed(): boolean { return this.engine.submitRetryUsed; }
|
|
1865
|
+
set submitRetryUsed(v: boolean) { this.engine.submitRetryUsed = v; }
|
|
1866
|
+
|
|
1867
|
+
get submitRetryPromptSnippet(): string { return this.engine.submitRetryPromptSnippet; }
|
|
1868
|
+
set submitRetryPromptSnippet(v: string) { this.engine.submitRetryPromptSnippet = v; }
|
|
1869
|
+
|
|
1870
|
+
get responseSettleIgnoreUntil(): number { return this.engine.responseSettleIgnoreUntil; }
|
|
1871
|
+
set responseSettleIgnoreUntil(v: number) { this.engine.responseSettleIgnoreUntil = v; }
|
|
1872
|
+
|
|
1873
|
+
get submitPendingUntil(): number { return this.engine.submitPendingUntil; }
|
|
1874
|
+
set submitPendingUntil(v: number) { this.engine.submitPendingUntil = v; }
|
|
1875
|
+
|
|
1876
|
+
get lastApprovalResolvedAt(): number { return this.engine.lastApprovalResolvedAt; }
|
|
1877
|
+
set lastApprovalResolvedAt(v: number) { this.engine.lastApprovalResolvedAt = v; }
|
|
1878
|
+
|
|
1879
|
+
get providerErrorMessage(): string | null { return this.engine.providerErrorMessage; }
|
|
1880
|
+
get providerErrorReason(): string | null { return this.engine.providerErrorReason; }
|
|
1881
|
+
|
|
1882
|
+
get pendingScriptStatus(): 'generating' | 'waiting_approval' | null { return this.engine.pendingScriptStatus; }
|
|
1883
|
+
get pendingScriptStatusSince(): number { return this.engine.pendingScriptStatusSince; }
|
|
1884
|
+
|
|
1885
|
+
get finishRetryCount(): number { return this.engine.finishRetryCount; }
|
|
1886
|
+
set finishRetryCount(v: number) { this.engine.finishRetryCount = v; }
|
|
1887
|
+
|
|
1888
|
+
get traceSessionId(): string { return this.engine.getTraceSessionId(); }
|
|
1889
|
+
get traceEntries(): CliTraceEntry[] { return this.engine.getTraceEntries(); }
|
|
1890
|
+
get statusHistory(): { status: string; at: number; trigger?: string }[] { return this.engine.getStatusHistory(); }
|
|
1891
|
+
get traceSeq(): number { return this.engine.getTraceEntries().length; }
|
|
1892
|
+
|
|
1893
|
+
/** Expose engine's evaluateSettled for test access */
|
|
1894
|
+
evaluateSettled(): void {
|
|
1895
|
+
LOG.debug(
|
|
1896
|
+
'CLI',
|
|
1897
|
+
`[${this.cliType}] settled diagnostics delegated to state engine`);
|
|
1898
|
+
this.engine.evaluateSettled(this.getSnapshot());
|
|
1899
|
+
}
|
|
1900
|
+
/** Expose engine's scheduleSettle for test access */
|
|
1901
|
+
scheduleSettle(): void { this.engine.scheduleSettle(); }
|
|
1902
|
+
/** Expose engine's clearIdleFinishCandidate for test access */
|
|
1903
|
+
clearIdleFinishCandidate(reason: string): void { this.engine.clearIdleFinishCandidate(reason); }
|
|
1904
|
+
/** Expose engine's finishResponse for test access */
|
|
1905
|
+
finishResponse(): void { this.engine.finishResponse(); }
|
|
1906
|
+
/** Returns a point-in-time snapshot of all buffer/screen state for external consumers (e.g. CliStateEngine). */
|
|
1907
|
+
getSnapshot(): CliBufferSnapshot {
|
|
1908
|
+
const screenText = this.terminalScreen.getText() || '';
|
|
1909
|
+
return {
|
|
1910
|
+
accumulatedBuffer: this.accumulatedBuffer,
|
|
1911
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1912
|
+
recentOutputBuffer: this.recentOutputBuffer,
|
|
1913
|
+
responseBuffer: this.responseBuffer,
|
|
1914
|
+
screenText,
|
|
1915
|
+
parseScreenText: this.getParseScreenText(screenText),
|
|
1916
|
+
workingDir: this.workingDir,
|
|
1917
|
+
providerSessionId: this.providerSessionId,
|
|
1918
|
+
runtimeSettings: this.runtimeSettings,
|
|
1919
|
+
isWaitingForResponse: this.engine.isWaitingForResponse,
|
|
1920
|
+
currentTurnScope: this.engine.currentTurnScope,
|
|
1921
|
+
lastOutputAt: this.lastOutputAt,
|
|
1922
|
+
lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
|
|
1923
|
+
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
1924
|
+
lastScreenSnapshot: this.lastScreenSnapshot,
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
isAlive(): boolean { return this.ptyProcess !== null; }
|
|
1928
|
+
flushOutboundQueue(): void { this.schedulePendingOutboundFlush(); }
|
|
1929
|
+
|
|
1930
|
+
async writeRaw(data: string | Buffer): Promise<void> {
|
|
1931
|
+
const str = Buffer.isBuffer(data) ? data.toString('utf8') : data;
|
|
1932
|
+
await this.writeToPty(str);
|
|
2298
1933
|
}
|
|
2299
1934
|
|
|
2300
1935
|
resolveModal(buttonIndex: number): void {
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
this.activeModal = parsedModal;
|
|
2312
|
-
if (this.currentStatus !== 'waiting_approval') {
|
|
2313
|
-
this.setStatus('waiting_approval', 'resolve_modal_parse');
|
|
2314
|
-
this.onStatusChange?.();
|
|
2315
|
-
}
|
|
2316
|
-
}
|
|
2317
|
-
} catch {
|
|
2318
|
-
// Ignore parse failures here; resolveModal falls back to current state.
|
|
2319
|
-
}
|
|
2320
|
-
}
|
|
2321
|
-
if (!this.ptyProcess || ((this.currentStatus !== 'waiting_approval') && !modal)) return;
|
|
2322
|
-
this.clearIdleFinishCandidate('resolve_modal');
|
|
2323
|
-
this.recordTrace('resolve_modal', {
|
|
2324
|
-
buttonIndex,
|
|
2325
|
-
activeModal: modal,
|
|
2326
|
-
});
|
|
2327
|
-
this.activeModal = null;
|
|
2328
|
-
this.lastApprovalResolvedAt = Date.now();
|
|
2329
|
-
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
2330
|
-
if (this.approvalExitTimeout) {
|
|
2331
|
-
clearTimeout(this.approvalExitTimeout);
|
|
2332
|
-
this.approvalExitTimeout = null;
|
|
2333
|
-
}
|
|
2334
|
-
this.setStatus('generating', 'approval_resolved');
|
|
2335
|
-
this.onStatusChange?.();
|
|
2336
|
-
if (buttonIndex in this.approvalKeys) {
|
|
2337
|
-
this.ptyProcess.write(this.approvalKeys[buttonIndex]);
|
|
2338
|
-
} else {
|
|
2339
|
-
const buttonCount = Array.isArray(modal?.buttons) ? modal.buttons.length : 0;
|
|
2340
|
-
const clampedIndex = buttonCount > 0
|
|
2341
|
-
? Math.min(Math.max(0, buttonIndex), buttonCount - 1)
|
|
2342
|
-
: Math.max(0, buttonIndex);
|
|
2343
|
-
const DOWN = '\x1B[B';
|
|
2344
|
-
const keys = DOWN.repeat(clampedIndex) + '\r';
|
|
2345
|
-
this.ptyProcess.write(keys);
|
|
2346
|
-
}
|
|
1936
|
+
this.engine.resolveModal(buttonIndex);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
getApprovalKeyForIndex(buttonIndex: number): string | undefined {
|
|
1940
|
+
return buttonIndex in this.approvalKeys ? this.approvalKeys[buttonIndex] : undefined;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
/** Returns true if an approval was resolved within the adapter's cooldown window. */
|
|
1944
|
+
isApprovalRecentlyResolved(): boolean {
|
|
1945
|
+
return this.engine.isApprovalRecentlyResolved();
|
|
2347
1946
|
}
|
|
2348
1947
|
|
|
2349
1948
|
resize(cols: number, rows: number): void {
|
|
@@ -2357,7 +1956,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2357
1956
|
}
|
|
2358
1957
|
|
|
2359
1958
|
private getParsedDebugState(): Record<string, any> | null {
|
|
2360
|
-
if (this.startupParseGate ||
|
|
1959
|
+
if (this.startupParseGate || !this.runner.hasParseSession()) return null;
|
|
2361
1960
|
try {
|
|
2362
1961
|
const parsed = this.getScriptParsedStatus();
|
|
2363
1962
|
return parsed && typeof parsed === 'object' ? parsed as Record<string, any> : null;
|
|
@@ -2369,18 +1968,43 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2369
1968
|
getDebugState(): Record<string, any> {
|
|
2370
1969
|
const screenText = sanitizeTerminalText(this.terminalScreen.getText());
|
|
2371
1970
|
const startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
|
|
2372
|
-
const
|
|
2373
|
-
|
|
1971
|
+
const startupDetectedStatus = this.startupParseGate && !startupModal
|
|
1972
|
+
? this.runDetectStatus(this.recentOutputBuffer || screenText)
|
|
1973
|
+
: null;
|
|
1974
|
+
const effectiveReady = this.ready || !!startupModal || startupDetectedStatus === 'waiting_approval';
|
|
2374
1975
|
const parsedDebugState = this.getParsedDebugState();
|
|
2375
1976
|
const parsedMessages = Array.isArray(parsedDebugState?.messages) ? parsedDebugState.messages : [];
|
|
1977
|
+
const hasFinalAssistant = (p: any) => {
|
|
1978
|
+
const msgs = Array.isArray(p?.messages) ? p.messages : [];
|
|
1979
|
+
return msgs.some((m: any) => m?.role === 'assistant' && typeof m.content === 'string' && m.content.trim());
|
|
1980
|
+
};
|
|
1981
|
+
let effectiveStatus = this.projectEffectiveStatus(startupModal);
|
|
1982
|
+
if (parsedDebugState?.status === 'error') {
|
|
1983
|
+
effectiveStatus = 'error';
|
|
1984
|
+
}
|
|
1985
|
+
// (fix) Mirror the getStatus contract: never surface waiting_approval
|
|
1986
|
+
// without a concrete modal. detectStatus alone can fire while
|
|
1987
|
+
// parseApproval is still null, and a bare debugState waiting_approval
|
|
1988
|
+
// confused dashboards reading the debug payload.
|
|
1989
|
+
const debugEffectiveModal = startupModal || this.engine.activeModal;
|
|
1990
|
+
if (startupDetectedStatus === 'waiting_approval' && debugEffectiveModal) {
|
|
1991
|
+
effectiveStatus = 'waiting_approval';
|
|
1992
|
+
}
|
|
1993
|
+
if (
|
|
1994
|
+
effectiveStatus === 'idle'
|
|
1995
|
+
&& parsedDebugState?.status === 'generating'
|
|
1996
|
+
&& !hasFinalAssistant(parsedDebugState)
|
|
1997
|
+
) {
|
|
1998
|
+
effectiveStatus = 'generating';
|
|
1999
|
+
}
|
|
2376
2000
|
return {
|
|
2377
2001
|
type: this.cliType,
|
|
2378
2002
|
name: this.cliName,
|
|
2379
2003
|
providerResolution: this.providerResolutionMeta,
|
|
2380
2004
|
status: effectiveStatus,
|
|
2381
2005
|
projectedStatus: effectiveStatus,
|
|
2382
|
-
rawStatus: this.currentStatus,
|
|
2383
|
-
lifecycleStatus: this.isWaitingForResponse ? 'awaiting_response' : 'idle',
|
|
2006
|
+
rawStatus: this.engine.currentStatus,
|
|
2007
|
+
lifecycleStatus: this.engine.isWaitingForResponse ? 'awaiting_response' : 'idle',
|
|
2384
2008
|
ready: effectiveReady,
|
|
2385
2009
|
startupParseGate: this.startupParseGate,
|
|
2386
2010
|
spawnAt: this.spawnAt,
|
|
@@ -2394,38 +2018,47 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2394
2018
|
providerSessionId: parsedDebugState.providerSessionId,
|
|
2395
2019
|
transcriptAuthority: parsedDebugState.transcriptAuthority,
|
|
2396
2020
|
coverage: parsedDebugState.coverage,
|
|
2021
|
+
errorMessage: parsedDebugState.errorMessage,
|
|
2022
|
+
errorReason: parsedDebugState.errorReason,
|
|
2397
2023
|
activeModal: parsedDebugState.activeModal,
|
|
2398
2024
|
messageCount: parsedMessages.length,
|
|
2399
2025
|
} : null,
|
|
2400
2026
|
screenText: screenText.slice(-4000),
|
|
2401
|
-
currentTurnScope: this.currentTurnScope,
|
|
2027
|
+
currentTurnScope: this.engine.currentTurnScope,
|
|
2402
2028
|
startupBuffer: this.startupBuffer.slice(-4000),
|
|
2403
2029
|
recentOutputBuffer: this.recentOutputBuffer.slice(-500),
|
|
2404
|
-
settledBuffer: this.settledBuffer.slice(-500),
|
|
2405
2030
|
accumulatedBufferLength: this.accumulatedBuffer.length,
|
|
2406
2031
|
accumulatedRawBufferLength: this.accumulatedRawBuffer.length,
|
|
2407
2032
|
rawBufferPreview: this.accumulatedRawBuffer.slice(-1000),
|
|
2408
2033
|
sanitizedRawPreview: sanitizeTerminalText(this.accumulatedRawBuffer).slice(-1000),
|
|
2409
2034
|
responseBuffer: this.responseBuffer.slice(-1000),
|
|
2035
|
+
pendingOutboundQueue: this.pendingOutboundQueue.map((message) => ({
|
|
2036
|
+
id: message.id,
|
|
2037
|
+
role: message.role,
|
|
2038
|
+
content: message.content,
|
|
2039
|
+
queuedAt: message.queuedAt,
|
|
2040
|
+
source: message.source,
|
|
2041
|
+
})),
|
|
2042
|
+
pendingOutboundCount: this.pendingOutboundQueue.length,
|
|
2410
2043
|
lastOutputAt: this.lastOutputAt,
|
|
2411
2044
|
lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
|
|
2412
2045
|
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
2413
2046
|
lastScreenSnapshot: this.lastScreenSnapshot.slice(-500),
|
|
2414
|
-
isWaitingForResponse: this.isWaitingForResponse,
|
|
2415
|
-
activeModal: startupModal || this.activeModal,
|
|
2416
|
-
lastApprovalResolvedAt: this.lastApprovalResolvedAt,
|
|
2047
|
+
isWaitingForResponse: this.engine.isWaitingForResponse,
|
|
2048
|
+
activeModal: startupModal || this.engine.activeModal,
|
|
2049
|
+
lastApprovalResolvedAt: this.engine.lastApprovalResolvedAt,
|
|
2417
2050
|
sendDelayMs: this.sendDelayMs,
|
|
2418
2051
|
sendKey: this.sendKey,
|
|
2419
2052
|
submitStrategy: this.submitStrategy,
|
|
2420
2053
|
requirePromptEchoBeforeSubmit: this.requirePromptEchoBeforeSubmit,
|
|
2421
|
-
submitPendingUntil: this.submitPendingUntil,
|
|
2422
|
-
responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
|
|
2054
|
+
submitPendingUntil: this.engine.submitPendingUntil,
|
|
2055
|
+
responseSettleIgnoreUntil: this.engine.responseSettleIgnoreUntil,
|
|
2423
2056
|
resizeSuppressUntil: this.resizeSuppressUntil,
|
|
2424
2057
|
hasCliScripts: this.hasCliScripts(),
|
|
2425
|
-
scriptNames:
|
|
2426
|
-
traceSessionId: this.
|
|
2427
|
-
traceEntryCount: this.
|
|
2428
|
-
statusHistory: this.
|
|
2058
|
+
scriptNames: this.runner.getScriptNames(),
|
|
2059
|
+
traceSessionId: this.engine.getTraceSessionId(),
|
|
2060
|
+
traceEntryCount: this.engine.getTraceEntries().length,
|
|
2061
|
+
statusHistory: this.engine.getStatusHistory().slice(-30),
|
|
2429
2062
|
timeouts: this.timeouts,
|
|
2430
2063
|
pendingOutputParseBufferLength: this.pendingOutputParseChunks.reduce((total, chunk) => total + chunk.length, 0),
|
|
2431
2064
|
pendingOutputParseScheduled: !!this.pendingOutputParseTimer,
|
|
@@ -2435,20 +2068,21 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2435
2068
|
|
|
2436
2069
|
getTraceState(limit = 120): Record<string, any> {
|
|
2437
2070
|
const cappedLimit = Math.max(1, Math.min(500, Number.isFinite(limit) ? Math.floor(limit) : 120));
|
|
2071
|
+
const traceEntries = this.engine.getTraceEntries();
|
|
2438
2072
|
return {
|
|
2439
|
-
sessionId: this.
|
|
2073
|
+
sessionId: this.engine.getTraceSessionId(),
|
|
2440
2074
|
providerResolution: this.providerResolutionMeta,
|
|
2441
|
-
entryCount:
|
|
2442
|
-
entries:
|
|
2075
|
+
entryCount: traceEntries.length,
|
|
2076
|
+
entries: traceEntries.slice(-cappedLimit),
|
|
2443
2077
|
screenText: summarizeCliTraceText(this.terminalScreen.getText(), 4000),
|
|
2444
2078
|
recentOutputBuffer: summarizeCliTraceText(this.recentOutputBuffer, 1000),
|
|
2445
2079
|
responseBuffer: summarizeCliTraceText(this.responseBuffer, 1200),
|
|
2446
2080
|
status: this.projectEffectiveStatus(),
|
|
2447
2081
|
projectedStatus: this.projectEffectiveStatus(),
|
|
2448
|
-
rawStatus: this.currentStatus,
|
|
2449
|
-
lifecycleStatus: this.isWaitingForResponse ? 'awaiting_response' : 'idle',
|
|
2450
|
-
activeModal: this.activeModal,
|
|
2451
|
-
currentTurnScope: this.currentTurnScope,
|
|
2082
|
+
rawStatus: this.engine.currentStatus,
|
|
2083
|
+
lifecycleStatus: this.engine.isWaitingForResponse ? 'awaiting_response' : 'idle',
|
|
2084
|
+
activeModal: this.engine.activeModal,
|
|
2085
|
+
currentTurnScope: this.engine.currentTurnScope,
|
|
2452
2086
|
messages: [],
|
|
2453
2087
|
};
|
|
2454
2088
|
}
|