@adhdev/daemon-core 0.9.82-rc.39 → 0.9.82-rc.390
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 +67 -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 +121 -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 +74 -0
- package/dist/cli-adapters/pty-write-chunking.d.ts +34 -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-debug-bundle.d.ts +14 -0
- package/dist/commands/chat-commands-read.d.ts +7 -0
- package/dist/commands/chat-commands-scope.d.ts +39 -0
- package/dist/commands/chat-commands-shared.d.ts +33 -0
- package/dist/commands/chat-commands-write.d.ts +14 -0
- package/dist/commands/chat-commands.d.ts +10 -23
- 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/mesh-restart.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 +237 -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/git-worktree.d.ts +22 -0
- package/dist/git/index.d.ts +2 -0
- package/dist/index.d.ts +57 -13
- package/dist/index.js +46306 -18259
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +46439 -18533
- 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 +81 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-prompt.d.ts +89 -0
- package/dist/mesh/coordinator-registry.d.ts +59 -0
- package/dist/mesh/mesh-active-work.d.ts +174 -0
- package/dist/mesh/mesh-coordinator-config.d.ts +21 -0
- package/dist/mesh/mesh-delivery-policy.d.ts +131 -0
- package/dist/mesh/mesh-event-classify.d.ts +5 -0
- package/dist/mesh/mesh-event-forwarding.d.ts +19 -0
- package/dist/mesh/mesh-event-trace.d.ts +21 -0
- package/dist/mesh/mesh-events-coordinator.d.ts +4 -0
- package/dist/mesh/mesh-events-pending.d.ts +55 -0
- package/dist/mesh/mesh-events-stale.d.ts +52 -0
- package/dist/mesh/mesh-events-utils.d.ts +64 -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 -4
- package/dist/mesh/mesh-ledger.d.ts +77 -1
- package/dist/mesh/mesh-missions.d.ts +164 -0
- package/dist/mesh/mesh-node-identity.d.ts +289 -0
- package/dist/mesh/mesh-queue-assignment.d.ts +86 -0
- package/dist/mesh/mesh-reconcile-loop.d.ts +65 -0
- package/dist/mesh/mesh-refine-batch.d.ts +68 -0
- package/dist/mesh/mesh-refine-gates.d.ts +428 -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 +447 -0
- package/dist/mesh/mesh-scheduling-runtime.d.ts +78 -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 +247 -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 +13 -1
- package/dist/providers/cli-provider-instance.d.ts +157 -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/constants.d.ts +12 -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 +407 -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 +376 -13
- package/dist/runtime-defaults.d.ts +2 -0
- 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 +68 -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 +836 -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 +121 -11
- package/src/cli-adapters/pty-transport.ts +2 -1
- package/src/cli-adapters/pty-write-chunking.ts +106 -0
- 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-debug-bundle.ts +398 -0
- package/src/commands/chat-commands-read.ts +2327 -0
- package/src/commands/chat-commands-scope.ts +54 -0
- package/src/commands/chat-commands-shared.ts +114 -0
- package/src/commands/chat-commands-write.ts +880 -0
- package/src/commands/chat-commands.ts +19 -1841
- package/src/commands/cli-manager.ts +513 -26
- 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 +657 -0
- package/src/commands/high-family/mesh-events.ts +80 -0
- package/src/commands/high-family/mesh-status.ts +678 -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 +87 -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 +230 -0
- package/src/commands/med-family/fast-forward.ts +230 -0
- package/src/commands/med-family/ide.ts +163 -0
- package/src/commands/med-family/index.ts +37 -0
- package/src/commands/med-family/mesh-crud.ts +836 -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/mesh-restart.ts +92 -0
- package/src/commands/med-family/types.ts +120 -0
- package/src/commands/mesh-coordinator.ts +334 -124
- package/src/commands/router.ts +2476 -3207
- 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 +353 -22
- 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 +76 -1
- package/src/git/index.ts +16 -0
- package/src/index.ts +171 -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 +341 -0
- package/src/logging/logger.ts +14 -7
- package/src/mesh/contracts.ts +338 -0
- package/src/mesh/coordinator-prompt.ts +381 -33
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +645 -0
- package/src/mesh/mesh-coordinator-config.ts +97 -0
- package/src/mesh/mesh-delivery-policy.ts +315 -0
- package/src/mesh/mesh-event-classify.ts +51 -0
- package/src/mesh/mesh-event-forwarding.ts +1665 -0
- package/src/mesh/mesh-event-trace.ts +67 -0
- package/src/mesh/mesh-events-coordinator.ts +30 -0
- package/src/mesh/mesh-events-pending.ts +590 -0
- package/src/mesh/mesh-events-stale.ts +363 -0
- package/src/mesh/mesh-events-utils.ts +425 -0
- package/src/mesh/mesh-events.ts +32 -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 -7
- package/src/mesh/mesh-ledger.ts +596 -102
- package/src/mesh/mesh-missions.ts +322 -0
- package/src/mesh/mesh-node-identity.ts +1887 -0
- package/src/mesh/mesh-queue-assignment.ts +1491 -0
- package/src/mesh/mesh-reconcile-loop.ts +1527 -0
- package/src/mesh/mesh-refine-batch.ts +197 -0
- package/src/mesh/mesh-refine-gates.ts +1652 -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 +1795 -0
- package/src/mesh/mesh-scheduling-runtime.ts +199 -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 +1141 -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 +46 -6
- package/src/providers/cli-provider-instance.ts +1552 -102
- 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/constants.ts +19 -0
- package/src/providers/native-history/dispatcher.ts +339 -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 +1056 -0
- package/src/providers/spec/evaluator.ts +407 -0
- package/src/providers/spec/fsm-driver.ts +1304 -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 +1128 -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 +542 -14
- package/src/runtime-defaults.ts +39 -0
- 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,16 +15,24 @@
|
|
|
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,
|
|
25
27
|
type PtyRuntimeTransport,
|
|
26
28
|
type PtyTransportFactory,
|
|
27
29
|
} from './pty-transport.js';
|
|
30
|
+
import {
|
|
31
|
+
WIN32_PTY_WRITE_CHUNK_CHARS,
|
|
32
|
+
WIN32_PTY_WRITE_CHUNK_GAP_MS,
|
|
33
|
+
chunkPreservingSurrogates,
|
|
34
|
+
shouldChunkWin32Write,
|
|
35
|
+
} from './pty-write-chunking.js';
|
|
28
36
|
import {
|
|
29
37
|
buildCliScreenSnapshot,
|
|
30
38
|
compactPromptText,
|
|
@@ -44,6 +52,13 @@ import {
|
|
|
44
52
|
type CliTraceEntry,
|
|
45
53
|
type ParsedSession,
|
|
46
54
|
} from './provider-cli-shared.js';
|
|
55
|
+
import { CliScriptRunner } from './cli-script-runner.js';
|
|
56
|
+
import {
|
|
57
|
+
CliStateEngine,
|
|
58
|
+
type CliBufferSnapshot,
|
|
59
|
+
type CliTransportAccess,
|
|
60
|
+
type CliStateEngineCallbacks,
|
|
61
|
+
} from './cli-state-engine.js';
|
|
47
62
|
import {
|
|
48
63
|
buildCliParseInput,
|
|
49
64
|
buildCliTraceParseSnapshot,
|
|
@@ -78,24 +93,6 @@ export {
|
|
|
78
93
|
} from './provider-cli-shared.js';
|
|
79
94
|
|
|
80
95
|
|
|
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
96
|
interface SendMessageState {
|
|
100
97
|
text: string;
|
|
101
98
|
normalizedPromptSnippet: string;
|
|
@@ -110,6 +107,14 @@ interface SendMessageCompletion {
|
|
|
110
107
|
rejectOnce: (error: unknown) => void;
|
|
111
108
|
}
|
|
112
109
|
|
|
110
|
+
interface PendingOutboundMessage {
|
|
111
|
+
id: string;
|
|
112
|
+
role: 'user';
|
|
113
|
+
content: string;
|
|
114
|
+
queuedAt: number;
|
|
115
|
+
source: 'sendMessage';
|
|
116
|
+
}
|
|
117
|
+
|
|
113
118
|
export function appendBoundedText(current: string, chunk: string, maxChars: number): string {
|
|
114
119
|
if (!chunk) return current.length <= maxChars ? current : current.slice(-maxChars);
|
|
115
120
|
if (maxChars <= 0) return '';
|
|
@@ -119,26 +124,52 @@ export function appendBoundedText(current: string, chunk: string, maxChars: numb
|
|
|
119
124
|
return current.slice(-keepFromCurrent) + chunk;
|
|
120
125
|
}
|
|
121
126
|
|
|
127
|
+
// Force-send (mesh coordinator dispatch / reconcile redelivery) writes raw
|
|
128
|
+
// keystrokes straight into the PTY, bypassing the normal echo-wait submit
|
|
129
|
+
// pipeline.
|
|
130
|
+
//
|
|
131
|
+
// History of this path's failure modes:
|
|
132
|
+
// 1. Originally it wrote `text + sendKey` in a single PTY write with zero
|
|
133
|
+
// settle time. Injected into an idle TUI input box that was still entering
|
|
134
|
+
// its input-accepting state, the trailing submit key could be swallowed —
|
|
135
|
+
// prompt typed but never submitted ("text injected, Enter not pressed").
|
|
136
|
+
// 2. The first fix split inject from submit: write(text) → echo-gated settle →
|
|
137
|
+
// write(sendKey) as a *separate* PTY write. That broke win32: ConPTY/TUI
|
|
138
|
+
// does not recognize a lone '\r' that arrives in its own PTY chunk
|
|
139
|
+
// 150ms+ after the text as a submit key, and the win32 echo gate never
|
|
140
|
+
// matched (ConPTY echo/parsing differences), so the cap was burned and the
|
|
141
|
+
// same detached lone CR was emitted — adding latency while the Enter still
|
|
142
|
+
// got swallowed.
|
|
143
|
+
//
|
|
144
|
+
// Current behavior: keep a fixed settle gap so the input handler is in its
|
|
145
|
+
// input-accepting state, then write `content + sendKey` as a SINGLE atomic PTY
|
|
146
|
+
// write — the same verified mechanism the normal `immediate` submit strategy
|
|
147
|
+
// uses (submitImmediatePrompt). With the Enter in the same write unit as the
|
|
148
|
+
// text, win32 ConPTY always sees it as a submit and the race that the split was
|
|
149
|
+
// trying to solve cannot happen (the Enter can never be separated from the
|
|
150
|
+
// text). FORCE_SUBMIT_SETTLE_MS is that minimum pre-submit gap.
|
|
151
|
+
const FORCE_SUBMIT_SETTLE_MS = 150;
|
|
152
|
+
|
|
122
153
|
// ─── Adapter ────────────────────────────────────────
|
|
123
154
|
|
|
124
155
|
export class ProviderCliAdapter implements CliAdapter {
|
|
125
|
-
|
|
156
|
+
cliType: string;
|
|
126
157
|
readonly cliName: string;
|
|
127
158
|
public workingDir: string;
|
|
128
159
|
|
|
129
160
|
private provider: CliProviderModule;
|
|
130
161
|
private ptyProcess: PtyRuntimeTransport | null = null;
|
|
131
162
|
private transportFactory: PtyTransportFactory;
|
|
132
|
-
private currentStatus: CliSessionStatus['status'] = 'starting';
|
|
133
163
|
private onStatusChange: (() => void) | null = null;
|
|
134
164
|
|
|
165
|
+
// ─── State machine engine ─────────────────────────
|
|
166
|
+
readonly engine: CliStateEngine;
|
|
167
|
+
|
|
135
168
|
private responseBuffer = '';
|
|
136
169
|
private recentOutputBuffer = '';
|
|
137
|
-
private
|
|
138
|
-
private
|
|
139
|
-
private parseErrorMessage: string | null = null;
|
|
170
|
+
private get parseErrorMessage(): string | null { return this.runner.parseErrorMessage; }
|
|
171
|
+
private providerSessionId: string | null = null;
|
|
140
172
|
private responseTimeout: NodeJS.Timeout | null = null;
|
|
141
|
-
private idleTimeout: NodeJS.Timeout | null = null;
|
|
142
173
|
private ready = false;
|
|
143
174
|
private startupBuffer = '';
|
|
144
175
|
private startupParseGate = false;
|
|
@@ -164,39 +195,48 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
164
195
|
private serverConn: any = null;
|
|
165
196
|
private logBuffer: { message: string; level: string }[] = [];
|
|
166
197
|
|
|
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;
|
|
198
|
+
private pendingOutboundQueue: PendingOutboundMessage[] = [];
|
|
199
|
+
private pendingOutboundFlushTimer: NodeJS.Timeout | null = null;
|
|
200
|
+
private pendingOutboundFlushInFlight = false;
|
|
201
|
+
// Submit retry timer — PTY-level, not state machine
|
|
183
202
|
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
203
|
|
|
190
204
|
// Resize redraw suppression
|
|
191
205
|
private resizeSuppressUntil: number = 0;
|
|
192
206
|
|
|
193
|
-
|
|
194
|
-
|
|
207
|
+
// (A2.2) Native transcript anchor moved to CHAT_SOURCE_REGISTRY.
|
|
208
|
+
// ChatSourceMachine holds the lock by state, not by a mutable field on
|
|
209
|
+
// the adapter. Removed entirely; no callers remain after the readChat
|
|
210
|
+
// ladder was replaced.
|
|
211
|
+
|
|
212
|
+
// ─── Script runner (parsing isolated here, adapter stays as transport) ───
|
|
213
|
+
private readonly runner: CliScriptRunner;
|
|
214
|
+
/** @deprecated use runner.cliScripts for direct script access */
|
|
215
|
+
get cliScripts(): CliScripts { return this.runner.cliScripts; }
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Recent script invocations (oldest → newest). Exposed via the
|
|
219
|
+
* `get_chat_debug_bundle` daemon command so anyone debugging a
|
|
220
|
+
* stuck-status regression can read what each detectStatus /
|
|
221
|
+
* parseSession call actually saw and returned, instead of having
|
|
222
|
+
* to instrument the daemon.
|
|
223
|
+
*/
|
|
224
|
+
getScriptInvocationTrace() {
|
|
225
|
+
return this.runner.getInvocationTrace();
|
|
226
|
+
}
|
|
195
227
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
228
|
+
/**
|
|
229
|
+
* Returns the full raw PTY byte stream captured since adapter start.
|
|
230
|
+
* Used by the `record_provider_pty` IPC command to produce fixtures.
|
|
231
|
+
* Bounded by MAX_ACCUMULATED_BUFFER; older bytes may have been dropped.
|
|
232
|
+
*/
|
|
233
|
+
getAccumulatedRawBuffer(): { text: string; droppedChars: number } {
|
|
234
|
+
return {
|
|
235
|
+
text: this.accumulatedRawBuffer,
|
|
236
|
+
droppedChars: this.accumulatedRawBufferDroppedChars,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
set cliScripts(scripts: CliScripts) { this.setCliScripts(scripts); }
|
|
200
240
|
private runtimeSettings: Record<string, any> = {};
|
|
201
241
|
/** Full accumulated rendered PTY transcript for parser/readback use */
|
|
202
242
|
private accumulatedBuffer: string = '';
|
|
@@ -205,7 +245,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
205
245
|
/** Full accumulated raw PTY output (with ANSI) */
|
|
206
246
|
private accumulatedRawBuffer: string = '';
|
|
207
247
|
/** Current visible terminal screen snapshot */
|
|
208
|
-
private terminalScreen = new TerminalScreen(
|
|
248
|
+
private terminalScreen = new TerminalScreen(DEFAULT_SESSION_HOST_ROWS, DEFAULT_SESSION_HOST_COLS);
|
|
209
249
|
private static readonly MAX_RESPONSE_BUFFER = 8000;
|
|
210
250
|
private static readonly MAX_RECENT_OUTPUT_BUFFER = 1000;
|
|
211
251
|
private responseBufferDroppedChars = 0;
|
|
@@ -216,10 +256,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
216
256
|
* Hermes turn (tool calls + reasoning + final bubble) without the
|
|
217
257
|
* rolling window pushing the turn's ╭─ opening line out of view. */
|
|
218
258
|
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
259
|
private parsedStatusCache: {
|
|
224
260
|
responseBuffer: string;
|
|
225
261
|
currentTurnScope: TurnParseScope | null;
|
|
@@ -233,11 +269,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
233
269
|
result: any;
|
|
234
270
|
} | null = null;
|
|
235
271
|
private static readonly SCREEN_SNAPSHOT_MIN_INTERVAL_MS = 250;
|
|
236
|
-
private static readonly MAX_TRACE_ENTRIES = 250;
|
|
237
272
|
|
|
238
273
|
private readonly providerResolutionMeta: ProviderResolutionMeta;
|
|
239
|
-
private static readonly FINISH_RETRY_DELAY_MS = 300;
|
|
240
|
-
private static readonly MAX_FINISH_RETRIES = 2;
|
|
241
274
|
|
|
242
275
|
private getBufferState(): NonNullable<CliSessionStatus['bufferState']> | undefined {
|
|
243
276
|
const build = (droppedChars: number, maxChars: number) => droppedChars > 0
|
|
@@ -313,13 +346,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
313
346
|
if (
|
|
314
347
|
cached
|
|
315
348
|
&& cached.responseBuffer === this.responseBuffer
|
|
316
|
-
&& cached.currentTurnScope === this.currentTurnScope
|
|
349
|
+
&& cached.currentTurnScope === this.engine.currentTurnScope
|
|
317
350
|
&& cached.recentOutputBuffer === this.recentOutputBuffer
|
|
318
351
|
&& cached.accumulatedBuffer === this.accumulatedBuffer
|
|
319
352
|
&& cached.accumulatedRawBufferKey === accumulatedRawBufferKey
|
|
320
353
|
&& cached.screenText === this.lastScreenText
|
|
321
|
-
&& cached.currentStatus === this.currentStatus
|
|
322
|
-
&& cached.activeModal === this.activeModal
|
|
354
|
+
&& cached.currentStatus === this.engine.currentStatus
|
|
355
|
+
&& cached.activeModal === this.engine.activeModal
|
|
323
356
|
&& cached.cliName === this.cliName
|
|
324
357
|
) {
|
|
325
358
|
return cached.result;
|
|
@@ -343,86 +376,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
343
376
|
return this.timeouts.statusActivityHold;
|
|
344
377
|
}
|
|
345
378
|
|
|
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
379
|
// Resolved timeouts
|
|
427
380
|
private readonly timeouts: Required<NonNullable<CliProviderModule['timeouts']>>;
|
|
428
381
|
|
|
@@ -432,7 +385,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
432
385
|
private readonly sendKey: string;
|
|
433
386
|
private readonly submitStrategy: 'wait_for_echo' | 'immediate';
|
|
434
387
|
private readonly requirePromptEchoBeforeSubmit: boolean;
|
|
435
|
-
private static readonly SCRIPT_STATUS_DEBOUNCE_MS = 3000;
|
|
436
388
|
|
|
437
389
|
constructor(
|
|
438
390
|
provider: CliProviderModule,
|
|
@@ -441,6 +393,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
441
393
|
private extraEnv: Record<string, string> = {},
|
|
442
394
|
transportFactory: PtyTransportFactory = new NodePtyTransportFactory(),
|
|
443
395
|
) {
|
|
396
|
+
this.runner = new CliScriptRunner(provider.type);
|
|
444
397
|
this.provider = provider;
|
|
445
398
|
this.transportFactory = transportFactory;
|
|
446
399
|
this.cliType = provider.type;
|
|
@@ -458,10 +411,33 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
458
411
|
this.requirePromptEchoBeforeSubmit = resolvedConfig.requirePromptEchoBeforeSubmit;
|
|
459
412
|
this.providerResolutionMeta = resolvedConfig.providerResolutionMeta;
|
|
460
413
|
|
|
461
|
-
//
|
|
462
|
-
this.
|
|
463
|
-
|
|
464
|
-
|
|
414
|
+
// State machine engine — owns all status transitions
|
|
415
|
+
this.engine = new CliStateEngine(
|
|
416
|
+
provider,
|
|
417
|
+
this.runner,
|
|
418
|
+
this as unknown as CliTransportAccess,
|
|
419
|
+
{
|
|
420
|
+
onStatusChange: () => { this.onStatusChange?.(); },
|
|
421
|
+
onApplyParsedSession: (session) => { this.applyParsedSessionMetadata(session); },
|
|
422
|
+
onTurnCompleted: () => { this.responseBuffer = ''; },
|
|
423
|
+
} satisfies CliStateEngineCallbacks,
|
|
424
|
+
resolvedConfig.timeouts,
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
// Scripts delegated to CliScriptRunner — adapter stays as transport.
|
|
428
|
+
// Pass the manifest tui block so the runner can build the
|
|
429
|
+
// declarativeDetectStatus / declarativeParseApproval SDK functions
|
|
430
|
+
// that v1 extended-tier overrides depend on (without these, scripts
|
|
431
|
+
// like codex-cli's detect_status v1 fail-closed with return 'idle'
|
|
432
|
+
// and the session sticks in `generating` forever).
|
|
433
|
+
this.runner.setScripts(provider.scripts || {}, provider.tui);
|
|
434
|
+
// Per-invocation wall-clock budget for provider scripts. Manifests may
|
|
435
|
+
// raise this for genuinely slow parsers, but the default (50ms) is the
|
|
436
|
+
// settle-loop safety net — any script that exceeds it gets flagged in
|
|
437
|
+
// the invocation trace and surfaced via the debug bundle so we can
|
|
438
|
+
// identify the offender instead of guessing why the settle loop hangs.
|
|
439
|
+
this.runner.setScriptCallBudget(provider.scriptCallBudgetMs ?? 50);
|
|
440
|
+
const scriptNames = this.runner.getScriptNames();
|
|
465
441
|
if (scriptNames.length > 0) {
|
|
466
442
|
LOG.info('CLI', `[${this.cliType}] CLI scripts: [${scriptNames.join(', ')}]`);
|
|
467
443
|
LOG.info(
|
|
@@ -487,14 +463,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
487
463
|
|
|
488
464
|
/** Inject CLI scripts after construction (e.g. when resolved by ProviderLoader) */
|
|
489
465
|
setCliScripts(scripts: CliScripts): void {
|
|
490
|
-
this.
|
|
466
|
+
this.runner.setScripts(scripts);
|
|
491
467
|
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(', ')}]`);
|
|
468
|
+
LOG.info('CLI', `[${this.cliType}] CLI scripts injected: [${this.runner.getScriptNames().join(', ')}]`);
|
|
498
469
|
}
|
|
499
470
|
|
|
500
471
|
/** Refresh provider scripts/config used by this adapter without restarting the PTY runtime. */
|
|
@@ -548,15 +519,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
548
519
|
});
|
|
549
520
|
|
|
550
521
|
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
522
|
|
|
561
523
|
try {
|
|
562
524
|
this.ptyProcess = this.transportFactory.spawn(
|
|
@@ -620,13 +582,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
620
582
|
this.ptyProcess.onExit(({ exitCode }: { exitCode: number }) => {
|
|
621
583
|
LOG.info('CLI', `[${this.cliType}] Exit code ${exitCode}`);
|
|
622
584
|
this.flushPendingOutputParse();
|
|
623
|
-
this.recordTrace('exit', { exitCode });
|
|
624
585
|
this.ptyProcess = null;
|
|
625
|
-
this.
|
|
586
|
+
this.engine.onPtyExit();
|
|
626
587
|
this.ready = false;
|
|
627
588
|
this.startupParseGate = false;
|
|
628
589
|
this.spawnAt = 0;
|
|
629
|
-
this.
|
|
590
|
+
this.runner.resetSessionState();
|
|
630
591
|
this.onStatusChange?.();
|
|
631
592
|
});
|
|
632
593
|
|
|
@@ -635,17 +596,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
635
596
|
this.startupBuffer = '';
|
|
636
597
|
this.startupFirstOutputAt = 0;
|
|
637
598
|
if (this.startupSettleTimer) { clearTimeout(this.startupSettleTimer); this.startupSettleTimer = null; }
|
|
638
|
-
this.resetTerminalScreen(
|
|
599
|
+
this.resetTerminalScreen(DEFAULT_SESSION_HOST_ROWS, DEFAULT_SESSION_HOST_COLS);
|
|
639
600
|
this.pendingTerminalQueryTail = '';
|
|
640
|
-
this.currentTurnScope = null;
|
|
641
|
-
this.finishRetryCount = 0;
|
|
642
|
-
if (this.finishRetryTimer) { clearTimeout(this.finishRetryTimer); this.finishRetryTimer = null; }
|
|
643
601
|
this.ready = false;
|
|
644
602
|
await this.ptyProcess.ready;
|
|
645
|
-
this.
|
|
646
|
-
runtimeMeta: this.getRuntimeMetadata(),
|
|
647
|
-
});
|
|
648
|
-
this.setStatus('starting', 'pty_ready');
|
|
603
|
+
this.engine.onSpawnReady();
|
|
649
604
|
this.scheduleStartupSettleCheck();
|
|
650
605
|
this.onStatusChange?.();
|
|
651
606
|
}
|
|
@@ -671,11 +626,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
671
626
|
if (this.startupParseGate && !this.startupFirstOutputAt && (cleanData.trim() || normalizedScreenSnapshot.trim())) {
|
|
672
627
|
this.startupFirstOutputAt = now;
|
|
673
628
|
}
|
|
674
|
-
if (
|
|
675
|
-
this.clearIdleFinishCandidate('new_output');
|
|
629
|
+
if (rawData.length > 0 || cleanData.length > 0) {
|
|
630
|
+
this.engine.clearIdleFinishCandidate('new_output');
|
|
676
631
|
}
|
|
677
632
|
if (getDebugRuntimeConfig().collectDebugTrace) {
|
|
678
|
-
this.
|
|
633
|
+
this.engine.recordExternalTrace('output', {
|
|
679
634
|
rawLength: rawData.length,
|
|
680
635
|
cleanLength: cleanData.length,
|
|
681
636
|
rawPreview: summarizeCliTraceText(rawData, 300),
|
|
@@ -687,7 +642,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
687
642
|
this.scheduleStartupSettleCheck();
|
|
688
643
|
}
|
|
689
644
|
|
|
690
|
-
if (this.isWaitingForResponse && cleanData) {
|
|
645
|
+
if (this.engine.isWaitingForResponse && cleanData) {
|
|
691
646
|
const previousResponseLen = this.responseBuffer.length;
|
|
692
647
|
this.responseBuffer = appendBoundedText(this.responseBuffer, cleanData, ProviderCliAdapter.MAX_RESPONSE_BUFFER);
|
|
693
648
|
this.responseBufferDroppedChars += this.recordBoundedAppendDrop(previousResponseLen, cleanData.length, this.responseBuffer.length);
|
|
@@ -726,19 +681,19 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
726
681
|
// Keep turn-scope offsets aligned with the truncated buffer so scoped
|
|
727
682
|
// parses don't lose the beginning of a long turn (e.g. the Hermes
|
|
728
683
|
// ╭─ opening line) when the rolling window sheds bytes.
|
|
729
|
-
if (this.currentTurnScope) {
|
|
684
|
+
if (this.engine.currentTurnScope) {
|
|
730
685
|
if (droppedClean > 0) {
|
|
731
|
-
this.currentTurnScope.bufferStart = Math.max(0, this.currentTurnScope.bufferStart - droppedClean);
|
|
686
|
+
this.engine.currentTurnScope.bufferStart = Math.max(0, this.engine.currentTurnScope.bufferStart - droppedClean);
|
|
732
687
|
}
|
|
733
688
|
if (droppedRaw > 0) {
|
|
734
|
-
this.currentTurnScope.rawBufferStart = Math.max(0, this.currentTurnScope.rawBufferStart - droppedRaw);
|
|
689
|
+
this.engine.currentTurnScope.rawBufferStart = Math.max(0, this.engine.currentTurnScope.rawBufferStart - droppedRaw);
|
|
735
690
|
}
|
|
736
691
|
}
|
|
737
692
|
|
|
738
693
|
this.resolveStartupState('output', screenText, normalizedScreenSnapshot, now);
|
|
739
694
|
|
|
740
695
|
// ─── Script-based status detection
|
|
741
|
-
this.scheduleSettle();
|
|
696
|
+
this.engine.scheduleSettle();
|
|
742
697
|
}
|
|
743
698
|
|
|
744
699
|
private resolveStartupState(
|
|
@@ -761,6 +716,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
761
716
|
if (stableMs < 2000) return;
|
|
762
717
|
|
|
763
718
|
const startupModal = this.runParseApproval(this.recentOutputBuffer);
|
|
719
|
+
const startupStatus = this.runDetectStatus(screenText || this.recentOutputBuffer);
|
|
720
|
+
if (!startupModal && startupStatus !== 'idle') {
|
|
721
|
+
this.scheduleStartupSettleCheck();
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
764
724
|
this.startupParseGate = false;
|
|
765
725
|
if (this.startupSettleTimer) {
|
|
766
726
|
clearTimeout(this.startupSettleTimer);
|
|
@@ -768,20 +728,33 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
768
728
|
}
|
|
769
729
|
this.ready = true;
|
|
770
730
|
if (startupModal) {
|
|
771
|
-
this.activeModal = startupModal;
|
|
772
|
-
this.setStatus('waiting_approval', `startup_ready:${trigger}`);
|
|
731
|
+
this.engine.activeModal = startupModal;
|
|
732
|
+
this.engine.setStatus('waiting_approval', `startup_ready:${trigger}`);
|
|
773
733
|
} else {
|
|
774
|
-
if (this.currentStatus === 'waiting_approval' || this.activeModal) {
|
|
775
|
-
this.lastApprovalResolvedAt = Date.now();
|
|
734
|
+
if (this.engine.currentStatus === 'waiting_approval' || this.engine.activeModal) {
|
|
735
|
+
this.engine.lastApprovalResolvedAt = Date.now();
|
|
776
736
|
}
|
|
777
|
-
this.activeModal = null;
|
|
778
|
-
|
|
737
|
+
this.engine.activeModal = null;
|
|
738
|
+
// Clear the in-flight turn flag at the same time we declare
|
|
739
|
+
// startup-idle. Otherwise the next settled evaluation sees
|
|
740
|
+
// isWaitingForResponse=true + recent CLI welcome-screen paints
|
|
741
|
+
// and flips us right back to generating via the hold path
|
|
742
|
+
// (the "startup → generating → idle → generating → idle"
|
|
743
|
+
// flicker the user observed on claude-cli launch).
|
|
744
|
+
this.engine.isWaitingForResponse = false;
|
|
745
|
+
this.engine.currentTurnScope = null;
|
|
746
|
+
this.engine.setStatus('idle', `startup_ready:${trigger}`);
|
|
779
747
|
}
|
|
780
748
|
LOG.info(
|
|
781
749
|
'CLI',
|
|
782
750
|
`[${this.cliType}] Startup settled (${trigger}, stableMs=${stableMs}, modal=${!!startupModal}) providerDir=${this.providerResolutionMeta.providerDir || '-'} scriptDir=${this.providerResolutionMeta.scriptDir || '-'} scriptsPath=${this.providerResolutionMeta.scriptsPath || '-'}`
|
|
783
751
|
);
|
|
784
752
|
this.onStatusChange?.();
|
|
753
|
+
// Readiness barrier flush: a message queued because the session was not yet
|
|
754
|
+
// ready (sendMessageNow's not_ready_pending_prompt path) has no turn-completion
|
|
755
|
+
// event to trigger its flush. Now that the interactive prompt is up and we are
|
|
756
|
+
// idle, drain it. No-op when the queue is empty or we settled to a modal.
|
|
757
|
+
if (!startupModal) this.schedulePendingOutboundFlush();
|
|
785
758
|
}
|
|
786
759
|
|
|
787
760
|
private scheduleStartupSettleCheck(): void {
|
|
@@ -801,88 +774,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
801
774
|
}, delayMs);
|
|
802
775
|
}
|
|
803
776
|
|
|
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
777
|
private async waitForInteractivePrompt(maxWaitMs = 5000): Promise<void> {
|
|
887
778
|
const startedAt = Date.now();
|
|
888
779
|
let loggedWait = false;
|
|
@@ -892,7 +783,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
892
783
|
const screenText = this.terminalScreen.getText() || '';
|
|
893
784
|
const stableMs = this.lastScreenChangeAt ? (Date.now() - this.lastScreenChangeAt) : 0;
|
|
894
785
|
const recentlyOutput = this.lastNonEmptyOutputAt ? (Date.now() - this.lastNonEmptyOutputAt) : Number.MAX_SAFE_INTEGER;
|
|
895
|
-
const status = this.runDetectStatus(this.recentOutputBuffer) || this.currentStatus;
|
|
786
|
+
const status = this.runDetectStatus(this.recentOutputBuffer) || this.engine.currentStatus;
|
|
896
787
|
const interactiveReady = status === 'idle'
|
|
897
788
|
&& stableMs >= 700
|
|
898
789
|
&& recentlyOutput >= 350;
|
|
@@ -926,637 +817,84 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
926
817
|
|
|
927
818
|
private clearAllTimers(): void {
|
|
928
819
|
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
820
|
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
821
|
if (this.pendingOutputParseTimer) { clearTimeout(this.pendingOutputParseTimer); this.pendingOutputParseTimer = null; }
|
|
936
822
|
if (this.ptyOutputFlushTimer) { clearTimeout(this.ptyOutputFlushTimer); this.ptyOutputFlushTimer = null; }
|
|
823
|
+
this.engine.clearAllTimers();
|
|
937
824
|
}
|
|
938
825
|
|
|
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
|
-
}
|
|
996
|
-
|
|
997
|
-
this.resolveStartupState('settled');
|
|
998
|
-
if (this.startupParseGate) return;
|
|
826
|
+
// ─── Script dispatch — builds inputs for CliScriptRunner ──────────────────
|
|
999
827
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
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
|
-
}),
|
|
828
|
+
runParseSession(): ParsedSession | null {
|
|
829
|
+
const screenText = this.terminalScreen.getText();
|
|
830
|
+
const parseScreenText = this.getParseScreenText(screenText);
|
|
831
|
+
const tail = this.recentOutputBuffer.slice(-500);
|
|
832
|
+
const input = buildCliParseInput({
|
|
833
|
+
accumulatedBuffer: this.accumulatedBuffer,
|
|
834
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
835
|
+
recentOutputBuffer: this.recentOutputBuffer,
|
|
836
|
+
terminalScreenText: parseScreenText,
|
|
837
|
+
workingDir: this.workingDir,
|
|
838
|
+
providerSessionId: this.providerSessionId || undefined,
|
|
839
|
+
historySessionId: this.providerSessionId || undefined,
|
|
840
|
+
baseMessages: [],
|
|
841
|
+
partialResponse: this.responseBuffer,
|
|
842
|
+
isWaitingForResponse: this.engine.isWaitingForResponse,
|
|
843
|
+
scope: this.engine.currentTurnScope,
|
|
844
|
+
runtimeSettings: this.runtimeSettings,
|
|
845
|
+
spawnAt: this.spawnAt,
|
|
1043
846
|
});
|
|
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
|
-
}),
|
|
847
|
+
const session = this.runner.parseSession({
|
|
848
|
+
...input,
|
|
849
|
+
tail,
|
|
850
|
+
tailScreen: buildCliScreenSnapshot(tail),
|
|
1168
851
|
});
|
|
1169
|
-
this.
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
private applyWaitingApproval(ctx: SettledEvalContext): void {
|
|
1173
|
-
const { modal } = ctx;
|
|
1174
|
-
this.clearIdleFinishCandidate('waiting_approval');
|
|
1175
|
-
const inCooldown = this.lastApprovalResolvedAt && (Date.now() - this.lastApprovalResolvedAt) < this.timeouts.approvalCooldown;
|
|
1176
|
-
if (inCooldown && !modal) {
|
|
1177
|
-
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
1178
|
-
this.activeModal = null;
|
|
1179
|
-
if (this.isWaitingForResponse) {
|
|
1180
|
-
this.setStatus('idle', inCooldown ? 'approval_cooldown_non_actionable' : 'approval_prompt_gone_non_actionable');
|
|
1181
|
-
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
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
|
-
}
|
|
852
|
+
if (session && typeof session === 'object') this.applyParsedSessionMetadata(session);
|
|
853
|
+
return session;
|
|
1206
854
|
}
|
|
1207
855
|
|
|
1208
|
-
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
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
|
-
}),
|
|
856
|
+
runDetectStatus(text: string): string | null {
|
|
857
|
+
const screenText = this.terminalScreen.getText();
|
|
858
|
+
const tail = text.slice(-500);
|
|
859
|
+
return this.runner.detectStatus({
|
|
860
|
+
tail,
|
|
861
|
+
screenText,
|
|
862
|
+
rawBuffer: this.accumulatedRawBuffer,
|
|
863
|
+
isWaitingForResponse: this.engine.isWaitingForResponse,
|
|
864
|
+
screen: buildCliScreenSnapshot(screenText),
|
|
865
|
+
tailScreen: buildCliScreenSnapshot(tail),
|
|
1339
866
|
});
|
|
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
867
|
}
|
|
1376
868
|
|
|
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),
|
|
869
|
+
runParseApproval(tail: string): { message: string; buttons: string[] } | null {
|
|
870
|
+
const screenText = this.terminalScreen.getText();
|
|
871
|
+
const buffer = screenText || this.accumulatedBuffer;
|
|
872
|
+
return this.runner.parseApproval({
|
|
873
|
+
buffer,
|
|
874
|
+
screenText,
|
|
875
|
+
rawBuffer: this.accumulatedRawBuffer,
|
|
876
|
+
tail,
|
|
877
|
+
screen: buildCliScreenSnapshot(screenText),
|
|
878
|
+
bufferScreen: buildCliScreenSnapshot(buffer),
|
|
879
|
+
tailScreen: buildCliScreenSnapshot(tail),
|
|
1408
880
|
});
|
|
1409
|
-
return true;
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
private commitCurrentTranscript(): { hasAssistant: boolean; assistantContent: string } {
|
|
1413
|
-
const parsed = this.parseCurrentTranscript(
|
|
1414
|
-
[],
|
|
1415
|
-
this.responseBuffer,
|
|
1416
|
-
this.currentTurnScope,
|
|
1417
|
-
);
|
|
1418
|
-
if (parsed && Array.isArray(parsed.messages)) {
|
|
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
881
|
}
|
|
1511
882
|
|
|
1512
|
-
private
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
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;
|
|
883
|
+
private applyParsedSessionMetadata(parsed: any): void {
|
|
884
|
+
const providerSessionId = typeof parsed?.providerSessionId === 'string' && parsed.providerSessionId.trim()
|
|
885
|
+
? parsed.providerSessionId.trim()
|
|
886
|
+
: '';
|
|
887
|
+
if (providerSessionId && providerSessionId !== this.providerSessionId) {
|
|
888
|
+
this.providerSessionId = providerSessionId;
|
|
889
|
+
this.updateRuntimeMeta({ providerSessionId });
|
|
1548
890
|
}
|
|
1549
891
|
}
|
|
1550
892
|
|
|
1551
|
-
private hasActionableApproval(startupModal: { message: string; buttons: string[] } | null = null): boolean {
|
|
1552
|
-
return !!(startupModal || this.activeModal);
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
893
|
private projectEffectiveStatus(startupModal: { message: string; buttons: string[] } | null = null): CliSessionStatus['status'] {
|
|
1556
894
|
if (this.parseErrorMessage) return 'error';
|
|
1557
|
-
if (this.
|
|
1558
|
-
if (this.isWaitingForResponse && this.currentTurnScope && this.currentStatus !== 'stopped') return 'generating';
|
|
1559
|
-
return this.currentStatus;
|
|
895
|
+
if (!!(startupModal || this.engine.activeModal)) return 'waiting_approval';
|
|
896
|
+
if (this.engine.isWaitingForResponse && this.engine.currentTurnScope && this.engine.currentStatus !== 'stopped') return 'generating';
|
|
897
|
+
return this.engine.currentStatus;
|
|
1560
898
|
}
|
|
1561
899
|
|
|
1562
900
|
// ─── Public API (CliAdapter) ───────────────────
|
|
@@ -1564,17 +902,73 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1564
902
|
getStatus(options: { allowParse?: boolean } = {}): CliSessionStatus {
|
|
1565
903
|
const allowParse = options.allowParse !== false;
|
|
1566
904
|
const startupModal = allowParse && this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
|
|
905
|
+
const startupDetectedStatus = allowParse && this.startupParseGate && !startupModal
|
|
906
|
+
? this.runDetectStatus(this.recentOutputBuffer || this.terminalScreen.getText())
|
|
907
|
+
: null;
|
|
1567
908
|
let effectiveStatus = this.projectEffectiveStatus(startupModal);
|
|
1568
|
-
let effectiveModal = startupModal || this.activeModal;
|
|
909
|
+
let effectiveModal = startupModal || this.engine.activeModal;
|
|
910
|
+
// (fix) When we have no captured modal yet, take one more live attempt
|
|
911
|
+
// with the current screen text — the engine's settle pass can miss
|
|
912
|
+
// the modal when it happens to fire exactly between writes, and
|
|
913
|
+
// without a modal here the dashboard could never show the buttons.
|
|
914
|
+
// We deliberately do NOT overwrite an existing engine.activeModal so
|
|
915
|
+
// a stable matched modal wins. This runs even outside the startup
|
|
916
|
+
// gate because Claude's approval frames can appear long after launch.
|
|
917
|
+
if (allowParse && !effectiveModal && this.engine.isWaitingForResponse) {
|
|
918
|
+
const liveDetect = this.runDetectStatus(this.recentOutputBuffer || this.terminalScreen.getText());
|
|
919
|
+
if (liveDetect === 'waiting_approval') {
|
|
920
|
+
const liveModal = this.runParseApproval(this.terminalScreen.getText())
|
|
921
|
+
|| this.runParseApproval(this.recentOutputBuffer);
|
|
922
|
+
// (fix) Only surface modals that have at least one non-empty
|
|
923
|
+
// button. Half-rendered approval frames produced
|
|
924
|
+
// { message, buttons: [] } briefly — auto-approve would then
|
|
925
|
+
// pick buttonIndex=-1 and resolveModal would write the
|
|
926
|
+
// provider's index-0 key into the prompt. Skip those frames
|
|
927
|
+
// and let a later evaluate find the complete modal.
|
|
928
|
+
const buttonsOk = liveModal && Array.isArray(liveModal.buttons)
|
|
929
|
+
&& liveModal.buttons.some((b: any) => typeof b === 'string' && b.trim());
|
|
930
|
+
if (liveModal && buttonsOk) {
|
|
931
|
+
effectiveModal = liveModal;
|
|
932
|
+
if (!this.engine.activeModal) this.engine.activeModal = liveModal;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
// Only surface waiting_approval when we ALSO have a concrete modal
|
|
937
|
+
// (message + buttons). detectStatus alone can fire while parseApproval
|
|
938
|
+
// is still null — the engine logs "detectStatus=waiting_approval but
|
|
939
|
+
// parseApproval returned null; ignoring". Without this guard getStatus
|
|
940
|
+
// was shipping a bare waiting_approval with activeModal=null and the
|
|
941
|
+
// user perceived the flow as broken.
|
|
942
|
+
if (startupDetectedStatus === 'waiting_approval' && effectiveModal) {
|
|
943
|
+
effectiveStatus = 'waiting_approval';
|
|
944
|
+
} else if (startupDetectedStatus === 'idle' && !startupModal && !effectiveModal) {
|
|
945
|
+
effectiveStatus = 'idle';
|
|
946
|
+
}
|
|
1569
947
|
if (allowParse && !startupModal && !effectiveModal) {
|
|
1570
948
|
const parsed = this.getFreshParsedStatusCache();
|
|
1571
949
|
const parsedModal = parsed?.activeModal && Array.isArray(parsed.activeModal.buttons)
|
|
1572
950
|
&& parsed.activeModal.buttons.some((button: any) => typeof button === 'string' && button.trim())
|
|
1573
951
|
? parsed.activeModal
|
|
1574
952
|
: null;
|
|
953
|
+
const hasFinalAssistant = (p: any) => {
|
|
954
|
+
const msgs = Array.isArray(p?.messages) ? p.messages : [];
|
|
955
|
+
return msgs.some((m: any) => m?.role === 'assistant' && typeof m.content === 'string' && m.content.trim());
|
|
956
|
+
};
|
|
1575
957
|
if (parsed?.status === 'waiting_approval' && parsedModal) {
|
|
1576
958
|
effectiveStatus = 'waiting_approval';
|
|
1577
959
|
effectiveModal = parsedModal;
|
|
960
|
+
} else if (
|
|
961
|
+
effectiveStatus === 'idle'
|
|
962
|
+
&& parsed?.status === 'generating'
|
|
963
|
+
&& !hasFinalAssistant(parsed)
|
|
964
|
+
) {
|
|
965
|
+
effectiveStatus = 'generating';
|
|
966
|
+
} else if (
|
|
967
|
+
effectiveStatus === 'generating'
|
|
968
|
+
&& parsed?.status === 'idle'
|
|
969
|
+
&& hasFinalAssistant(parsed)
|
|
970
|
+
) {
|
|
971
|
+
effectiveStatus = 'idle';
|
|
1578
972
|
}
|
|
1579
973
|
}
|
|
1580
974
|
const bufferState = this.getBufferState();
|
|
@@ -1583,8 +977,21 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1583
977
|
messages: [],
|
|
1584
978
|
workingDir: this.workingDir,
|
|
1585
979
|
activeModal: effectiveModal,
|
|
1586
|
-
|
|
1587
|
-
|
|
980
|
+
approvalEntrySeq: this.engine.approvalEntrySeq,
|
|
981
|
+
lastResolvedEntrySeq: this.engine.lastResolvedEntrySeq,
|
|
982
|
+
pendingOutboundCount: this.pendingOutboundQueue.length,
|
|
983
|
+
pendingOutboundMessages: this.pendingOutboundQueue.map((message) => ({
|
|
984
|
+
id: message.id,
|
|
985
|
+
role: message.role,
|
|
986
|
+
content: message.content,
|
|
987
|
+
queuedAt: message.queuedAt,
|
|
988
|
+
source: message.source,
|
|
989
|
+
})),
|
|
990
|
+
errorMessage: this.parseErrorMessage || this.engine.providerErrorMessage || undefined,
|
|
991
|
+
errorReason: this.parseErrorMessage ? 'parse_error' : (this.engine.providerErrorReason || undefined),
|
|
992
|
+
providerSessionId: this.providerSessionId || undefined,
|
|
993
|
+
lastOutputAt: this.lastOutputAt,
|
|
994
|
+
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
1588
995
|
...(bufferState ? { bufferState } : {}),
|
|
1589
996
|
};
|
|
1590
997
|
}
|
|
@@ -1600,15 +1007,16 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1600
1007
|
const cached = this.parsedStatusCache;
|
|
1601
1008
|
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
1602
1009
|
if (
|
|
1603
|
-
|
|
1010
|
+
!this.providerOwnsTranscript()
|
|
1011
|
+
&& cached
|
|
1604
1012
|
&& cached.responseBuffer === this.responseBuffer
|
|
1605
|
-
&& cached.currentTurnScope === this.currentTurnScope
|
|
1013
|
+
&& cached.currentTurnScope === this.engine.currentTurnScope
|
|
1606
1014
|
&& cached.recentOutputBuffer === this.recentOutputBuffer
|
|
1607
1015
|
&& cached.accumulatedBuffer === this.accumulatedBuffer
|
|
1608
1016
|
&& cached.accumulatedRawBufferKey === accumulatedRawBufferKey
|
|
1609
1017
|
&& cached.screenText === parseScreenText
|
|
1610
|
-
&& cached.currentStatus === this.currentStatus
|
|
1611
|
-
&& cached.activeModal === this.activeModal
|
|
1018
|
+
&& cached.currentStatus === this.engine.currentStatus
|
|
1019
|
+
&& cached.activeModal === this.engine.activeModal
|
|
1612
1020
|
&& cached.cliName === this.cliName
|
|
1613
1021
|
) {
|
|
1614
1022
|
return cached.result;
|
|
@@ -1623,14 +1031,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1623
1031
|
const bufferState = this.getBufferState();
|
|
1624
1032
|
const result = {
|
|
1625
1033
|
id: (parsed as any).id || 'cli_session',
|
|
1626
|
-
status: parsed.status || this.currentStatus,
|
|
1034
|
+
status: parsed.status || this.engine.currentStatus,
|
|
1627
1035
|
title: (parsed as any).title || this.cliName,
|
|
1628
1036
|
messages: normalizeCliParsedMessages(parsed.messages, {
|
|
1629
1037
|
scope: null,
|
|
1630
1038
|
lastOutputAt: this.lastOutputAt,
|
|
1631
1039
|
}),
|
|
1632
1040
|
activeModal,
|
|
1633
|
-
providerSessionId: typeof (parsed as any).providerSessionId === 'string' ? (parsed as any).providerSessionId : undefined,
|
|
1041
|
+
providerSessionId: this.providerSessionId || (typeof (parsed as any).providerSessionId === 'string' ? (parsed as any).providerSessionId : undefined),
|
|
1042
|
+
errorMessage: typeof (parsed as any).errorMessage === 'string' && (parsed as any).errorMessage.trim()
|
|
1043
|
+
? (parsed as any).errorMessage.trim()
|
|
1044
|
+
: undefined,
|
|
1045
|
+
errorReason: typeof (parsed as any).errorReason === 'string' && (parsed as any).errorReason.trim()
|
|
1046
|
+
? (parsed as any).errorReason.trim()
|
|
1047
|
+
: undefined,
|
|
1634
1048
|
...(bufferState ? { bufferState } : {}),
|
|
1635
1049
|
...((parsed as any).transcriptAuthority === 'provider' || (parsed as any).transcriptAuthority === 'daemon'
|
|
1636
1050
|
? { transcriptAuthority: (parsed as any).transcriptAuthority }
|
|
@@ -1642,13 +1056,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1642
1056
|
|
|
1643
1057
|
this.parsedStatusCache = {
|
|
1644
1058
|
responseBuffer: this.responseBuffer,
|
|
1645
|
-
currentTurnScope: this.currentTurnScope,
|
|
1059
|
+
currentTurnScope: this.engine.currentTurnScope,
|
|
1646
1060
|
recentOutputBuffer: this.recentOutputBuffer,
|
|
1647
1061
|
accumulatedBuffer: this.accumulatedBuffer,
|
|
1648
1062
|
accumulatedRawBufferKey,
|
|
1649
1063
|
screenText: parseScreenText,
|
|
1650
|
-
currentStatus: this.currentStatus,
|
|
1651
|
-
activeModal: this.activeModal,
|
|
1064
|
+
currentStatus: this.engine.currentStatus,
|
|
1065
|
+
activeModal: this.engine.activeModal,
|
|
1652
1066
|
cliName: this.cliName,
|
|
1653
1067
|
result,
|
|
1654
1068
|
};
|
|
@@ -1656,22 +1070,22 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1656
1070
|
}
|
|
1657
1071
|
|
|
1658
1072
|
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
1073
|
const input = buildCliParseInput({
|
|
1664
1074
|
accumulatedBuffer: this.accumulatedBuffer,
|
|
1665
1075
|
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1666
1076
|
recentOutputBuffer: this.recentOutputBuffer,
|
|
1667
1077
|
terminalScreenText: this.getParseScreenText(this.terminalScreen.getText()),
|
|
1078
|
+
workingDir: this.workingDir,
|
|
1079
|
+
providerSessionId: this.providerSessionId || undefined,
|
|
1080
|
+
historySessionId: this.providerSessionId || undefined,
|
|
1668
1081
|
baseMessages: [],
|
|
1669
1082
|
partialResponse: this.responseBuffer,
|
|
1670
|
-
isWaitingForResponse: this.isWaitingForResponse,
|
|
1671
|
-
scope: this.currentTurnScope,
|
|
1083
|
+
isWaitingForResponse: this.engine.isWaitingForResponse,
|
|
1084
|
+
scope: this.engine.currentTurnScope,
|
|
1672
1085
|
runtimeSettings: this.runtimeSettings,
|
|
1086
|
+
spawnAt: this.spawnAt,
|
|
1673
1087
|
});
|
|
1674
|
-
return await Promise.resolve(this.
|
|
1088
|
+
return await Promise.resolve(this.runner.invokeByName(scriptName, {
|
|
1675
1089
|
...input,
|
|
1676
1090
|
args: args && typeof args === 'object' ? { ...args } : {},
|
|
1677
1091
|
}));
|
|
@@ -1683,7 +1097,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1683
1097
|
|
|
1684
1098
|
/** Whether this adapter has CLI scripts loaded */
|
|
1685
1099
|
hasCliScripts(): boolean {
|
|
1686
|
-
return
|
|
1100
|
+
return this.runner.hasDetectStatus();
|
|
1687
1101
|
}
|
|
1688
1102
|
|
|
1689
1103
|
/**
|
|
@@ -1692,24 +1106,29 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1692
1106
|
*/
|
|
1693
1107
|
async resolveAction(data: any): Promise<void> {
|
|
1694
1108
|
let promptText = '';
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
}
|
|
1109
|
+
try {
|
|
1110
|
+
promptText = this.runner.invokeByName('resolveAction', data);
|
|
1111
|
+
} catch {
|
|
1112
|
+
LOG.warn('CLI', `[${this.cliType}] resolveAction skipped: provider script not available`);
|
|
1113
|
+
return;
|
|
1701
1114
|
}
|
|
1702
1115
|
if (!promptText) {
|
|
1703
|
-
LOG.warn('CLI', `[${this.cliType}] resolveAction skipped: provider script did not
|
|
1116
|
+
LOG.warn('CLI', `[${this.cliType}] resolveAction skipped: provider script did not return a prompt`);
|
|
1704
1117
|
return;
|
|
1705
1118
|
}
|
|
1706
1119
|
await this.sendMessage(promptText);
|
|
1707
1120
|
}
|
|
1708
1121
|
|
|
1122
|
+
async setInteractivePromptResponse(_response: InteractivePromptResponse): Promise<void> {
|
|
1123
|
+
// Legacy TUI providers do not currently expose an interactive prompt
|
|
1124
|
+
// protocol. Spec-backed claude-cli implements the first real wiring.
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1709
1127
|
private isSubmitStuck(normalizedPromptSnippet: string): boolean {
|
|
1710
|
-
if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return false;
|
|
1711
|
-
if (this.hasActionableApproval()) return false;
|
|
1712
|
-
|
|
1128
|
+
if (!this.ptyProcess || !this.engine.isWaitingForResponse || this.engine.submitRetryUsed) return false;
|
|
1129
|
+
if (this.engine.hasActionableApproval()) return false;
|
|
1130
|
+
// If there's already meaningful response content beyond the echoed prompt, not stuck
|
|
1131
|
+
if (this.hasMeaningfulResponseBufferLocal(normalizedPromptSnippet)) return false;
|
|
1713
1132
|
const screenText = this.terminalScreen.getText();
|
|
1714
1133
|
if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return false;
|
|
1715
1134
|
const liveApproval = this.runParseApproval(screenText) || this.runParseApproval(this.recentOutputBuffer);
|
|
@@ -1718,20 +1137,74 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1718
1137
|
return liveStatus !== 'generating' && liveStatus !== 'waiting_approval';
|
|
1719
1138
|
}
|
|
1720
1139
|
|
|
1140
|
+
private hasMeaningfulResponseBufferLocal(promptSnippet: string): boolean {
|
|
1141
|
+
const raw = String(this.responseBuffer || '').trim();
|
|
1142
|
+
if (!raw) return false;
|
|
1143
|
+
const normalizedPrompt = compactPromptText(promptSnippet);
|
|
1144
|
+
if (!normalizedPrompt) return true;
|
|
1145
|
+
const normalizedBuffer = compactPromptText(raw);
|
|
1146
|
+
if (!normalizedBuffer) return false;
|
|
1147
|
+
if (normalizedBuffer === normalizedPrompt) return false;
|
|
1148
|
+
if (normalizedBuffer.startsWith(normalizedPrompt)) {
|
|
1149
|
+
const remainder = normalizedBuffer
|
|
1150
|
+
.slice(normalizedPrompt.length)
|
|
1151
|
+
.replace(/[─═\-]+/g, '')
|
|
1152
|
+
.replace(/⏵⏵accepteditson\([^)]*\)/gi, '')
|
|
1153
|
+
.replace(/accepteditson\([^)]*\)/gi, '')
|
|
1154
|
+
.replace(/(?:◐|◑|◒|◓|◔|◕|◉|●|·)?(?:x?high|medium|low|max)·?\/effort/gi, '')
|
|
1155
|
+
.replace(/updateavailable!run:[a-z0-9:._\-/]+/gi, '')
|
|
1156
|
+
.replace(/esctointerrupt/gi, '')
|
|
1157
|
+
.replace(/❯/g, '')
|
|
1158
|
+
.replace(/^[\s\-–—:;,.!/?]+/, '')
|
|
1159
|
+
.trim();
|
|
1160
|
+
return remainder.length > 0;
|
|
1161
|
+
}
|
|
1162
|
+
return true;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1721
1165
|
private async writeToPty(data: string): Promise<void> {
|
|
1722
1166
|
if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
|
|
1167
|
+
// win32 ConPTY paced write: a single unbounded write beyond ~1KB overflows
|
|
1168
|
+
// the console input pipe and drops LEADING bytes (the "long message gets
|
|
1169
|
+
// truncated, head lost / tail kept" failure). Split a large payload into
|
|
1170
|
+
// bounded, surrogate-safe chunks written with a short gap so the console
|
|
1171
|
+
// reader keeps up. Small payloads (the common case — short prompts, lone
|
|
1172
|
+
// submit keys) still go out in a single write.
|
|
1173
|
+
//
|
|
1174
|
+
// The submit key, when present, is the TAIL of `data` (callers pass
|
|
1175
|
+
// `body + sendKey` for the atomic-submit paths). Because we chunk the
|
|
1176
|
+
// combined string, the submit key always rides in the SAME final write as
|
|
1177
|
+
// the body's tail — the win32 invariant that ConPTY recognizes it as a
|
|
1178
|
+
// submit — and is never emitted before the whole body has been written
|
|
1179
|
+
// (no partial-body submit). Body-only writes (wait_for_echo strategy) have
|
|
1180
|
+
// their submit key sent separately by the caller afterwards, unchanged.
|
|
1181
|
+
if (process.platform === 'win32' && shouldChunkWin32Write(data.length)) {
|
|
1182
|
+
await this.writeWin32Chunked(data);
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1723
1185
|
await this.ptyProcess.write(data);
|
|
1724
1186
|
}
|
|
1725
1187
|
|
|
1188
|
+
/** Write `data` to the PTY in bounded, surrogate-safe chunks with a short
|
|
1189
|
+
* inter-chunk gap (win32 paced write). Awaits each chunk's write and the gap
|
|
1190
|
+
* so the returned promise resolves only after the FINAL chunk (carrying any
|
|
1191
|
+
* trailing submit key) has been written. */
|
|
1192
|
+
private async writeWin32Chunked(data: string): Promise<void> {
|
|
1193
|
+
const chunks = chunkPreservingSurrogates(data, WIN32_PTY_WRITE_CHUNK_CHARS);
|
|
1194
|
+
for (let i = 0; i < chunks.length; i += 1) {
|
|
1195
|
+
if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
|
|
1196
|
+
await this.ptyProcess.write(chunks[i]);
|
|
1197
|
+
if (i + 1 < chunks.length) {
|
|
1198
|
+
await new Promise<void>(resolve => setTimeout(resolve, WIN32_PTY_WRITE_CHUNK_GAP_MS));
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1726
1203
|
private resetPendingSendState(reason: string): void {
|
|
1727
|
-
this.isWaitingForResponse = false;
|
|
1728
1204
|
this.responseBuffer = '';
|
|
1729
|
-
this.currentTurnScope = null;
|
|
1730
|
-
this.submitPendingUntil = 0;
|
|
1731
|
-
this.clearIdleFinishCandidate(reason);
|
|
1732
1205
|
if (this.responseTimeout) { clearTimeout(this.responseTimeout); this.responseTimeout = null; }
|
|
1733
|
-
|
|
1734
|
-
|
|
1206
|
+
this.engine.resetActiveTurnState();
|
|
1207
|
+
this.engine.clearIdleFinishCandidate(reason);
|
|
1735
1208
|
}
|
|
1736
1209
|
|
|
1737
1210
|
private commitSendUserTurn(state: SendMessageState): void {
|
|
@@ -1748,42 +1221,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1748
1221
|
}
|
|
1749
1222
|
this.responseTimeout = setTimeout(() => {
|
|
1750
1223
|
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
|
-
});
|
|
1224
|
+
if (!this.engine.isWaitingForResponse) return;
|
|
1768
1225
|
|
|
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
|
|
1226
|
+
// maxResponse is a watchdog/checkpoint, not a completion signal. Re-run the
|
|
1773
1227
|
// normal settled parser instead and keep the turn open unless the provider
|
|
1774
1228
|
// 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
|
-
});
|
|
1229
|
+
this.engine.evaluateSettled(this.getSnapshot());
|
|
1230
|
+
|
|
1231
|
+
if (this.engine.isWaitingForResponse && !this.engine.hasActionableApproval()) {
|
|
1787
1232
|
this.armResponseTimeout();
|
|
1788
1233
|
}
|
|
1789
1234
|
}, timeoutMs);
|
|
@@ -1798,34 +1243,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1798
1243
|
private retrySubmitIfStuck(state: SendMessageState, attempt: number): void {
|
|
1799
1244
|
this.submitRetryTimer = null;
|
|
1800
1245
|
if (!this.isSubmitStuck(state.normalizedPromptSnippet)) return;
|
|
1801
|
-
|
|
1802
|
-
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1246
|
+
this.engine.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1803
1247
|
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
1248
|
this.writeSubmitKeyForRetry('submit_retry');
|
|
1811
|
-
if (attempt >= 3) { this.submitRetryUsed = true; return; }
|
|
1249
|
+
if (attempt >= 3) { this.engine.submitRetryUsed = true; return; }
|
|
1812
1250
|
this.submitRetryTimer = setTimeout(() => this.retrySubmitIfStuck(state, attempt + 1), state.retryDelayMs);
|
|
1813
1251
|
}
|
|
1814
1252
|
|
|
1815
1253
|
private retryImmediateSubmitIfStuck(state: SendMessageState): void {
|
|
1816
1254
|
this.submitRetryTimer = null;
|
|
1817
1255
|
if (!this.isSubmitStuck(state.normalizedPromptSnippet)) return;
|
|
1818
|
-
|
|
1819
|
-
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1256
|
+
this.engine.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1820
1257
|
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
1258
|
this.writeSubmitKeyForRetry('immediate_retry');
|
|
1828
|
-
this.submitRetryUsed = true;
|
|
1259
|
+
this.engine.submitRetryUsed = true;
|
|
1829
1260
|
}
|
|
1830
1261
|
|
|
1831
1262
|
private submitSendKey(state: SendMessageState, completion: SendMessageCompletion): void {
|
|
@@ -1833,13 +1264,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1833
1264
|
completion.resolveOnce();
|
|
1834
1265
|
return;
|
|
1835
1266
|
}
|
|
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
|
-
});
|
|
1267
|
+
this.engine.submitPendingUntil = 0;
|
|
1843
1268
|
void this.writeToPty(this.sendKey).then(() => {
|
|
1844
1269
|
this.commitSendUserTurn(state);
|
|
1845
1270
|
this.submitRetryTimer = setTimeout(() => this.retrySubmitIfStuck(state, 1), state.retryDelayMs);
|
|
@@ -1849,13 +1274,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1849
1274
|
}
|
|
1850
1275
|
|
|
1851
1276
|
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
|
-
});
|
|
1277
|
+
this.engine.submitPendingUntil = 0;
|
|
1859
1278
|
void this.writeToPty(state.text + this.sendKey).then(() => {
|
|
1860
1279
|
this.commitSendUserTurn(state);
|
|
1861
1280
|
this.submitRetryTimer = setTimeout(() => this.retryImmediateSubmitIfStuck(state), state.retryDelayMs);
|
|
@@ -1899,7 +1318,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1899
1318
|
requirePromptEchoBeforeSubmit: this.requirePromptEchoBeforeSubmit,
|
|
1900
1319
|
screenText: summarizeCliTraceText(screenText, 1000),
|
|
1901
1320
|
};
|
|
1902
|
-
|
|
1321
|
+
LOG.warn('CLI', `[${this.cliType}] submit_echo_missing: ${JSON.stringify(diagnostic)}`);
|
|
1322
|
+
|
|
1903
1323
|
if (this.requirePromptEchoBeforeSubmit) {
|
|
1904
1324
|
// At this point the prompt text write already completed. Rejecting without
|
|
1905
1325
|
// a submit key can leave the delegated CLI with an unsent prompt sitting at
|
|
@@ -1924,12 +1344,145 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1924
1344
|
), 50);
|
|
1925
1345
|
}
|
|
1926
1346
|
|
|
1927
|
-
async sendMessage(text: string): Promise<void> {
|
|
1347
|
+
async sendMessage(text: string, options: { force?: boolean } = {}): Promise<void> {
|
|
1348
|
+
if (options.force === true) {
|
|
1349
|
+
await this.forceSendMessage(text);
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
await this.sendMessageNow(text, true);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
async forceSendMessage(text: string): Promise<void> {
|
|
1356
|
+
if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
|
|
1357
|
+
const content = String(text || '');
|
|
1358
|
+
if (!content.trim()) return;
|
|
1359
|
+
// Modal-park guard (defense-in-depth — the primary guard is at the
|
|
1360
|
+
// cli-provider-instance force-forward chokepoint). A force-write writes raw
|
|
1361
|
+
// keystrokes into the PTY, bypassing the busy send-guard. If the session is
|
|
1362
|
+
// parked on a tool-consent modal, the modal's key handler eats those bytes
|
|
1363
|
+
// and silently resolves an approval the user never made. Hold the write and
|
|
1364
|
+
// let the mesh reconcile loop redeliver once the modal is resolved. We only
|
|
1365
|
+
// hold for an actionable approval modal; plain generating is still force-written
|
|
1366
|
+
// (that is the deadlock the force path exists to break).
|
|
1367
|
+
if (this.engine.currentStatus === 'waiting_approval' || this.engine.hasActionableApproval()) {
|
|
1368
|
+
LOG.info('CLI', `[${this.cliType}] force-send held — session parked on approval modal (status=${this.engine.currentStatus})`);
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
LOG.info('CLI', `[${this.cliType}] force-sending prompt while status=${this.engine.currentStatus}`);
|
|
1372
|
+
// Settle, then submit atomically. The previous split-write fix
|
|
1373
|
+
// (write text → settle → write a separate '\r') regressed win32:
|
|
1374
|
+
// ConPTY does not treat a lone CR arriving in its own PTY chunk after a
|
|
1375
|
+
// delay as a submit key, so the Enter was swallowed and the prompt sat
|
|
1376
|
+
// typed-but-unsent. Instead, honor a fixed settle gap so the TUI input
|
|
1377
|
+
// handler is in its input-accepting state, then write `content + sendKey`
|
|
1378
|
+
// as ONE PTY write — the same atomic submit the normal `immediate`
|
|
1379
|
+
// strategy uses. Keeping the Enter in the same write unit as the text is
|
|
1380
|
+
// the invariant that makes win32 ConPTY recognize the submit, and it
|
|
1381
|
+
// also makes the original inject↔submit race impossible (the Enter can
|
|
1382
|
+
// never be separated from the text it submits). The settle still guards
|
|
1383
|
+
// the original concern — that a force-dispatch injected into a freshly
|
|
1384
|
+
// idle session lands before the TUI is ready to accept input.
|
|
1385
|
+
await this.waitForForceSubmitSettle();
|
|
1386
|
+
await this.writeToPty(content + this.sendKey);
|
|
1387
|
+
this.onStatusChange?.();
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
private async waitForForceSubmitSettle(): Promise<void> {
|
|
1391
|
+
// A fixed minimum gap so the input handler is ready to accept the paste.
|
|
1392
|
+
// We deliberately do NOT echo-gate here: the submit key is written in the
|
|
1393
|
+
// same atomic PTY write as the text (forceSendMessage), so there is no
|
|
1394
|
+
// separate Enter that could race the echo, and on win32 the echo gate
|
|
1395
|
+
// never reliably matched anyway.
|
|
1396
|
+
await new Promise<void>(resolve => setTimeout(resolve, FORCE_SUBMIT_SETTLE_MS));
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
private enqueuePendingOutboundMessage(text: string, reason: string): void {
|
|
1400
|
+
const content = String(text || '');
|
|
1401
|
+
const duplicate = this.pendingOutboundQueue.some((message) => message.content === content);
|
|
1402
|
+
if (duplicate) {
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
const queuedAt = Date.now();
|
|
1406
|
+
const message: PendingOutboundMessage = {
|
|
1407
|
+
id: `${queuedAt}:${this.pendingOutboundQueue.length}:${Math.random().toString(36).slice(2, 10)}`,
|
|
1408
|
+
role: 'user',
|
|
1409
|
+
content,
|
|
1410
|
+
queuedAt,
|
|
1411
|
+
source: 'sendMessage',
|
|
1412
|
+
};
|
|
1413
|
+
this.pendingOutboundQueue.push(message);
|
|
1414
|
+
LOG.info('CLI', `[${this.cliType}] queued outbound message while busy (${reason}); queue=${this.pendingOutboundQueue.length}`);
|
|
1415
|
+
this.onStatusChange?.();
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
private shouldQueuePendingOutboundMessage(parsedStatusBeforeSend: any | null = null): string | null {
|
|
1419
|
+
if (this.provider.allowInputDuringGeneration === true) return null;
|
|
1420
|
+
if (this.engine.hasActionableApproval()) return null;
|
|
1421
|
+
const parsedSessionStatus = typeof parsedStatusBeforeSend?.status === 'string'
|
|
1422
|
+
? String(parsedStatusBeforeSend.status)
|
|
1423
|
+
: '';
|
|
1424
|
+
const hasFinalAssistant = (p: any) => {
|
|
1425
|
+
const msgs = Array.isArray(p?.messages) ? p.messages : [];
|
|
1426
|
+
return msgs.some((m: any) => m?.role === 'assistant' && typeof m.content === 'string' && m.content.trim());
|
|
1427
|
+
};
|
|
1428
|
+
if (parsedSessionStatus === 'idle' && hasFinalAssistant(parsedStatusBeforeSend)) return null;
|
|
1429
|
+
if (this.engine.currentStatus === 'generating') return 'current_status_generating';
|
|
1430
|
+
if (parsedSessionStatus === 'generating' || parsedSessionStatus === 'no_progress' || parsedSessionStatus === 'long_generating') {
|
|
1431
|
+
const parsedModal = parsedStatusBeforeSend?.activeModal ?? parsedStatusBeforeSend?.modal ?? null;
|
|
1432
|
+
const parsedHasActionableModal = Boolean(
|
|
1433
|
+
parsedModal
|
|
1434
|
+
&& Array.isArray(parsedModal.buttons)
|
|
1435
|
+
&& parsedModal.buttons.some((candidate: unknown) => typeof candidate === 'string' && candidate.trim()),
|
|
1436
|
+
);
|
|
1437
|
+
const terminalLooksIdle = this.engine.currentStatus === 'idle'
|
|
1438
|
+
&& this.runDetectStatus(this.recentOutputBuffer) === 'idle'
|
|
1439
|
+
&& !this.engine.isWaitingForResponse
|
|
1440
|
+
&& !this.engine.currentTurnScope
|
|
1441
|
+
&& !this.engine.hasActionableApproval()
|
|
1442
|
+
&& !parsedHasActionableModal;
|
|
1443
|
+
return terminalLooksIdle ? null : `parsed_status_${parsedSessionStatus}`;
|
|
1444
|
+
}
|
|
1445
|
+
if (this.engine.isWaitingForResponse && this.engine.currentTurnScope) return 'active_turn_in_progress';
|
|
1446
|
+
return null;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
private schedulePendingOutboundFlush(delayMs = 0): void {
|
|
1450
|
+
if (this.pendingOutboundFlushTimer) clearTimeout(this.pendingOutboundFlushTimer);
|
|
1451
|
+
this.pendingOutboundFlushTimer = setTimeout(() => {
|
|
1452
|
+
this.pendingOutboundFlushTimer = null;
|
|
1453
|
+
void this.flushPendingOutboundQueue();
|
|
1454
|
+
}, Math.max(0, delayMs));
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
private async flushPendingOutboundQueue(): Promise<void> {
|
|
1458
|
+
if (this.pendingOutboundFlushInFlight || this.pendingOutboundQueue.length === 0) return;
|
|
1459
|
+
if (this.engine.currentStatus !== 'idle' || this.engine.isWaitingForResponse || this.engine.hasActionableApproval()) return;
|
|
1460
|
+
this.pendingOutboundFlushInFlight = true;
|
|
1461
|
+
try {
|
|
1462
|
+
while (this.pendingOutboundQueue.length > 0) {
|
|
1463
|
+
if (this.engine.currentStatus !== 'idle' || this.engine.isWaitingForResponse || this.engine.hasActionableApproval()) break;
|
|
1464
|
+
const next = this.pendingOutboundQueue[0];
|
|
1465
|
+
try {
|
|
1466
|
+
await this.sendMessageNow(next.content, false);
|
|
1467
|
+
this.pendingOutboundQueue.shift();
|
|
1468
|
+
this.onStatusChange?.();
|
|
1469
|
+
} catch (error: any) {
|
|
1470
|
+
LOG.warn('CLI', `[${this.cliType}] queued outbound flush failed: ${error?.message || error}`);
|
|
1471
|
+
this.schedulePendingOutboundFlush(1000);
|
|
1472
|
+
break;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
} finally {
|
|
1476
|
+
this.pendingOutboundFlushInFlight = false;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
private async sendMessageNow(text: string, allowQueue: boolean): Promise<void> {
|
|
1928
1481
|
if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
|
|
1929
1482
|
const allowInputDuringGeneration = this.provider.allowInputDuringGeneration === true;
|
|
1930
1483
|
const allowInterventionPrompt = allowInputDuringGeneration
|
|
1931
|
-
&& this.isWaitingForResponse
|
|
1932
|
-
&& !this.hasActionableApproval();
|
|
1484
|
+
&& this.engine.isWaitingForResponse
|
|
1485
|
+
&& !this.engine.hasActionableApproval();
|
|
1933
1486
|
if (this.startupParseGate) {
|
|
1934
1487
|
const deadline = Date.now() + 10000;
|
|
1935
1488
|
while (this.startupParseGate && Date.now() < deadline) {
|
|
@@ -1937,76 +1490,102 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1937
1490
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
1938
1491
|
}
|
|
1939
1492
|
}
|
|
1493
|
+
const parsedStatusBeforeSend = !allowInputDuringGeneration
|
|
1494
|
+
? (() => {
|
|
1495
|
+
try {
|
|
1496
|
+
return this.getScriptParsedStatus?.() || null;
|
|
1497
|
+
} catch {
|
|
1498
|
+
return null;
|
|
1499
|
+
}
|
|
1500
|
+
})()
|
|
1501
|
+
: null;
|
|
1502
|
+
const queueReason = this.shouldQueuePendingOutboundMessage(parsedStatusBeforeSend);
|
|
1503
|
+
if (allowQueue && queueReason) {
|
|
1504
|
+
this.enqueuePendingOutboundMessage(text, queueReason);
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1940
1507
|
if (!allowInterventionPrompt) {
|
|
1941
1508
|
await this.waitForInteractivePrompt();
|
|
1942
1509
|
}
|
|
1943
1510
|
if (!this.ready) {
|
|
1944
1511
|
this.resolveStartupState('send_precheck');
|
|
1945
|
-
if (this.runDetectStatus(this.recentOutputBuffer) === 'idle'
|
|
1512
|
+
if (this.runDetectStatus(this.recentOutputBuffer) === 'idle') {
|
|
1946
1513
|
this.ready = true;
|
|
1947
1514
|
this.startupParseGate = false;
|
|
1515
|
+
this.engine.setStatus('idle', 'send_message_idle_prompt_recovery');
|
|
1948
1516
|
LOG.info('CLI', `[${this.cliType}] sendMessage recovered idle prompt readiness`);
|
|
1949
1517
|
}
|
|
1950
1518
|
}
|
|
1951
|
-
if (!this.ready)
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1519
|
+
if (!this.ready) {
|
|
1520
|
+
// Readiness barrier (queue-until-ready). A task dispatched the instant a
|
|
1521
|
+
// freshly-spawned session is launched can arrive BEFORE the PTY prints its
|
|
1522
|
+
// interactive prompt (this.ready flips ~2-6s later). Previously this threw
|
|
1523
|
+
// "not ready" and the delegated-task delivery promise requeued the task,
|
|
1524
|
+
// which on win32 raced the auto-launch cooldown and could strand the worker
|
|
1525
|
+
// idle with no work (the "first big message lost" failure). Instead, when the
|
|
1526
|
+
// caller allows queueing, BUFFER the message in the pending-outbound queue and
|
|
1527
|
+
// return — the startup-settle path flips this.ready and flushes the queue once
|
|
1528
|
+
// the prompt is actually up (see resolveStartupState → flushPendingOutboundQueue),
|
|
1529
|
+
// so the message is delivered late rather than dropped. A non-queueable caller
|
|
1530
|
+
// (e.g. an internal flush) still throws so it isn't silently swallowed.
|
|
1531
|
+
if (allowQueue) {
|
|
1532
|
+
this.enqueuePendingOutboundMessage(text, 'not_ready_pending_prompt');
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
throw new Error(`${this.cliName} not ready (status: ${this.engine.currentStatus})`);
|
|
1536
|
+
}
|
|
1961
1537
|
const parsedSessionStatus = typeof parsedStatusBeforeSend?.status === 'string'
|
|
1962
1538
|
? String(parsedStatusBeforeSend.status)
|
|
1963
1539
|
: '';
|
|
1964
|
-
if (!allowInputDuringGeneration && (parsedSessionStatus === 'generating' || parsedSessionStatus === 'long_generating')) {
|
|
1540
|
+
if (!allowInputDuringGeneration && (parsedSessionStatus === 'generating' || parsedSessionStatus === 'no_progress' || parsedSessionStatus === 'long_generating')) {
|
|
1965
1541
|
const parsedModal = parsedStatusBeforeSend?.activeModal ?? parsedStatusBeforeSend?.modal ?? null;
|
|
1966
1542
|
const parsedHasActionableModal = Boolean(
|
|
1967
1543
|
parsedModal
|
|
1968
1544
|
&& Array.isArray(parsedModal.buttons)
|
|
1969
1545
|
&& parsedModal.buttons.some((candidate: unknown) => typeof candidate === 'string' && candidate.trim()),
|
|
1970
1546
|
);
|
|
1971
|
-
const terminalLooksIdle = this.currentStatus === 'idle'
|
|
1547
|
+
const terminalLooksIdle = this.engine.currentStatus === 'idle'
|
|
1972
1548
|
&& this.runDetectStatus(this.recentOutputBuffer) === 'idle'
|
|
1973
|
-
&& !this.isWaitingForResponse
|
|
1974
|
-
&& !this.currentTurnScope
|
|
1975
|
-
&& !this.hasActionableApproval()
|
|
1549
|
+
&& !this.engine.isWaitingForResponse
|
|
1550
|
+
&& !this.engine.currentTurnScope
|
|
1551
|
+
&& !this.engine.hasActionableApproval()
|
|
1976
1552
|
&& !parsedHasActionableModal;
|
|
1977
1553
|
if (!terminalLooksIdle) {
|
|
1554
|
+
if (allowQueue) {
|
|
1555
|
+
this.enqueuePendingOutboundMessage(text, `parsed_status_${parsedSessionStatus}`);
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1978
1558
|
throw new Error(`${this.cliName} is still processing the previous prompt`);
|
|
1979
1559
|
}
|
|
1980
1560
|
}
|
|
1981
|
-
if (this.isWaitingForResponse && !allowInputDuringGeneration) {
|
|
1982
|
-
|
|
1561
|
+
if (this.engine.isWaitingForResponse && !allowInputDuringGeneration) {
|
|
1562
|
+
const snap = this.getSnapshot();
|
|
1563
|
+
if (
|
|
1564
|
+
!this.engine.clearStaleIdleResponseGuard('send_message_guard', snap)
|
|
1565
|
+
&& !this.engine.clearParsedIdleResponseGuard('send_message_parsed_idle_guard', parsedStatusBeforeSend, snap)
|
|
1566
|
+
) {
|
|
1567
|
+
if (allowQueue) {
|
|
1568
|
+
this.enqueuePendingOutboundMessage(text, 'waiting_for_response');
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1983
1571
|
throw new Error(`${this.cliName} is still processing the previous prompt`);
|
|
1984
1572
|
}
|
|
1985
1573
|
}
|
|
1986
|
-
this.isWaitingForResponse = true;
|
|
1987
1574
|
this.responseBuffer = '';
|
|
1988
|
-
|
|
1989
|
-
if (this.finishRetryTimer) { clearTimeout(this.finishRetryTimer); this.finishRetryTimer = null; }
|
|
1990
|
-
this.clearIdleFinishCandidate('send_message');
|
|
1991
|
-
this.currentTurnScope = {
|
|
1575
|
+
const turnScope: TurnParseScope = {
|
|
1992
1576
|
prompt: text,
|
|
1993
1577
|
startedAt: Date.now(),
|
|
1994
1578
|
bufferStart: this.accumulatedBuffer.length,
|
|
1995
1579
|
rawBufferStart: this.accumulatedRawBuffer.length,
|
|
1996
1580
|
};
|
|
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);
|
|
1581
|
+
LOG.info('CLI', `[${this.cliType}] sendMessage turn scope buffer=${turnScope.bufferStart} raw=${turnScope.rawBufferStart} prompt=${JSON.stringify(text).slice(0, 120)}`);
|
|
2006
1582
|
if (this.submitRetryTimer) {
|
|
2007
1583
|
clearTimeout(this.submitRetryTimer);
|
|
2008
1584
|
this.submitRetryTimer = null;
|
|
2009
1585
|
}
|
|
1586
|
+
this.engine.onTurnStarted(turnScope);
|
|
1587
|
+
this.engine.submitRetryPromptSnippet = extractPromptRetrySnippet(text);
|
|
1588
|
+
const normalizedPromptSnippet = normalizePromptText(this.engine.submitRetryPromptSnippet);
|
|
2010
1589
|
const estimatedLines = estimatePromptDisplayLines(text);
|
|
2011
1590
|
const submitDelayMs = this.sendDelayMs + Math.min(2000, Math.max(0, estimatedLines - 1) * 350);
|
|
2012
1591
|
const maxEchoWaitMs = submitDelayMs + Math.max(1500, Math.min(5000, estimatedLines * 500));
|
|
@@ -2019,12 +1598,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2019
1598
|
retryDelayMs,
|
|
2020
1599
|
didCommitUserTurn: false,
|
|
2021
1600
|
};
|
|
2022
|
-
|
|
2023
|
-
clearTimeout(this.settleTimer);
|
|
2024
|
-
this.settleTimer = null;
|
|
2025
|
-
}
|
|
2026
|
-
this.responseEpoch += 1;
|
|
2027
|
-
this.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
|
|
1601
|
+
this.engine.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
|
|
2028
1602
|
await new Promise<void>((resolve, reject) => {
|
|
2029
1603
|
let resolved = false;
|
|
2030
1604
|
const completion: SendMessageCompletion = {
|
|
@@ -2047,24 +1621,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2047
1621
|
}
|
|
2048
1622
|
|
|
2049
1623
|
if (submitDelayMs > 0) {
|
|
2050
|
-
this.submitPendingUntil = Date.now() + submitDelayMs;
|
|
1624
|
+
this.engine.submitPendingUntil = Date.now() + submitDelayMs;
|
|
2051
1625
|
}
|
|
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
1626
|
const submitStartedAt = Date.now();
|
|
2059
1627
|
void this.writeToPty(text).then(
|
|
2060
1628
|
() => this.waitForEchoAndSubmit(sendState, completion, submitStartedAt),
|
|
2061
1629
|
completion.rejectOnce,
|
|
2062
1630
|
);
|
|
2063
1631
|
});
|
|
1632
|
+
// Schedule settle after successful send
|
|
1633
|
+
this.engine.scheduleSettle();
|
|
2064
1634
|
}
|
|
2065
1635
|
|
|
2066
1636
|
getPartialResponse(): string {
|
|
2067
|
-
if (!this.isWaitingForResponse) return '';
|
|
1637
|
+
if (!this.engine.isWaitingForResponse) return '';
|
|
2068
1638
|
return this.responseBuffer;
|
|
2069
1639
|
}
|
|
2070
1640
|
|
|
@@ -2077,10 +1647,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2077
1647
|
cliType: this.cliType,
|
|
2078
1648
|
cliName: this.cliName,
|
|
2079
1649
|
workingDir: this.workingDir,
|
|
2080
|
-
currentStatus: this.currentStatus,
|
|
1650
|
+
currentStatus: this.engine.currentStatus,
|
|
2081
1651
|
ready: this.ready,
|
|
2082
|
-
isWaitingForResponse: this.isWaitingForResponse,
|
|
2083
|
-
activeModal: this.activeModal,
|
|
1652
|
+
isWaitingForResponse: this.engine.isWaitingForResponse,
|
|
1653
|
+
activeModal: this.engine.activeModal,
|
|
2084
1654
|
parseErrorMessage: this.parseErrorMessage,
|
|
2085
1655
|
messageCounts: {
|
|
2086
1656
|
parsedCache: Array.isArray(parsedResult?.messages) ? parsedResult.messages.length : undefined,
|
|
@@ -2106,10 +1676,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2106
1676
|
lastScreenSnapshotReadAt: this.lastScreenSnapshotReadAt,
|
|
2107
1677
|
},
|
|
2108
1678
|
parser: {
|
|
2109
|
-
scriptNames:
|
|
2110
|
-
traceSessionId: this.
|
|
2111
|
-
traceSeq: this.
|
|
2112
|
-
currentTurnScope: this.currentTurnScope,
|
|
1679
|
+
scriptNames: this.runner.getScriptNames(),
|
|
1680
|
+
traceSessionId: this.engine.getTraceSessionId(),
|
|
1681
|
+
traceSeq: this.engine.getTraceEntries().length,
|
|
1682
|
+
currentTurnScope: this.engine.currentTurnScope,
|
|
2113
1683
|
parsedStatusCache: parsedResult
|
|
2114
1684
|
? {
|
|
2115
1685
|
id: parsedResult.id,
|
|
@@ -2122,26 +1692,25 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2122
1692
|
activeModal: parsedResult.activeModal,
|
|
2123
1693
|
}
|
|
2124
1694
|
: null,
|
|
2125
|
-
pendingScriptStatus: this.pendingScriptStatus,
|
|
2126
|
-
pendingScriptStatusSince: this.pendingScriptStatusSince,
|
|
1695
|
+
pendingScriptStatus: this.engine.pendingScriptStatus,
|
|
1696
|
+
pendingScriptStatusSince: this.engine.pendingScriptStatusSince,
|
|
2127
1697
|
},
|
|
2128
1698
|
runtimeMetadata: this.getRuntimeMetadata(),
|
|
2129
|
-
statusHistory: this.
|
|
2130
|
-
traceEntries: this.
|
|
1699
|
+
statusHistory: this.engine.getStatusHistory().slice(-80),
|
|
1700
|
+
traceEntries: this.engine.getTraceEntries().slice(-120),
|
|
2131
1701
|
timing: {
|
|
2132
1702
|
spawnAt: this.spawnAt,
|
|
2133
1703
|
startupFirstOutputAt: this.startupFirstOutputAt,
|
|
2134
|
-
submitPendingUntil: this.submitPendingUntil,
|
|
2135
|
-
responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
|
|
2136
|
-
responseEpoch: this.responseEpoch,
|
|
1704
|
+
submitPendingUntil: this.engine.submitPendingUntil,
|
|
1705
|
+
responseSettleIgnoreUntil: this.engine.responseSettleIgnoreUntil,
|
|
1706
|
+
responseEpoch: this.engine.responseEpoch,
|
|
2137
1707
|
resizeSuppressUntil: this.resizeSuppressUntil,
|
|
2138
|
-
lastApprovalResolvedAt: this.lastApprovalResolvedAt,
|
|
1708
|
+
lastApprovalResolvedAt: this.engine.lastApprovalResolvedAt,
|
|
2139
1709
|
},
|
|
2140
1710
|
finish: {
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
submitRetryPromptSnippet: this.submitRetryPromptSnippet,
|
|
1711
|
+
finishRetryCount: this.engine.finishRetryCount,
|
|
1712
|
+
submitRetryUsed: this.engine.submitRetryUsed,
|
|
1713
|
+
submitRetryPromptSnippet: this.engine.submitRetryPromptSnippet,
|
|
2145
1714
|
},
|
|
2146
1715
|
};
|
|
2147
1716
|
}
|
|
@@ -2151,7 +1720,48 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2151
1720
|
return this.ptyProcess.getMetadata();
|
|
2152
1721
|
}
|
|
2153
1722
|
|
|
1723
|
+
/**
|
|
1724
|
+
* Launch metadata for the dashboard Session info panel. Re-derives the spawn plan
|
|
1725
|
+
* (pure — same inputs the live PTY was spawned with) so the dashboard sees the
|
|
1726
|
+
* resolved binary, full arg vector, and cwd without us having to persist it.
|
|
1727
|
+
* extraEnv values are intentionally dropped (keys only) so secrets passed at
|
|
1728
|
+
* launch time never reach the dashboard.
|
|
1729
|
+
*/
|
|
1730
|
+
getLaunchInfo(): CliLaunchInfo {
|
|
1731
|
+
let command: string | undefined;
|
|
1732
|
+
let args: string[] = [...this.extraArgs];
|
|
1733
|
+
try {
|
|
1734
|
+
const plan = resolveCliSpawnPlan({
|
|
1735
|
+
provider: this.provider,
|
|
1736
|
+
runtimeSettings: this.runtimeSettings,
|
|
1737
|
+
workingDir: this.workingDir,
|
|
1738
|
+
extraArgs: this.extraArgs,
|
|
1739
|
+
extraEnv: this.extraEnv,
|
|
1740
|
+
});
|
|
1741
|
+
command = plan.binaryPath;
|
|
1742
|
+
args = plan.allArgs;
|
|
1743
|
+
} catch {
|
|
1744
|
+
// Spawn plan resolution can throw before the binary is resolvable
|
|
1745
|
+
// (e.g. CLI not installed). Fall back to the raw extra args so the
|
|
1746
|
+
// panel still shows what it can rather than failing the whole call.
|
|
1747
|
+
}
|
|
1748
|
+
return {
|
|
1749
|
+
command,
|
|
1750
|
+
args,
|
|
1751
|
+
extraArgs: [...this.extraArgs],
|
|
1752
|
+
cwd: this.workingDir,
|
|
1753
|
+
extraEnvKeys: Object.keys(this.extraEnv || {}),
|
|
1754
|
+
providerSessionId: this.providerSessionId || undefined,
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
|
|
2154
1758
|
updateRuntimeMeta(meta: Record<string, unknown>, replace = false): void {
|
|
1759
|
+
const nextProviderSessionId = typeof meta?.providerSessionId === 'string'
|
|
1760
|
+
? meta.providerSessionId.trim()
|
|
1761
|
+
: '';
|
|
1762
|
+
if (nextProviderSessionId) {
|
|
1763
|
+
this.providerSessionId = nextProviderSessionId;
|
|
1764
|
+
}
|
|
2155
1765
|
if (!this.ptyProcess || typeof this.ptyProcess.updateMeta !== 'function') return;
|
|
2156
1766
|
this.ptyProcess.updateMeta(meta, replace);
|
|
2157
1767
|
}
|
|
@@ -2172,7 +1782,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2172
1782
|
this.timeouts.shutdownGrace,
|
|
2173
1783
|
typeof resume.shutdownGraceMs === 'number' ? resume.shutdownGraceMs : 3000,
|
|
2174
1784
|
);
|
|
2175
|
-
const wasProcessing = this.currentStatus === 'generating' || this.currentStatus === 'waiting_approval';
|
|
1785
|
+
const wasProcessing = this.engine.currentStatus === 'generating' || this.engine.currentStatus === 'waiting_approval';
|
|
2176
1786
|
|
|
2177
1787
|
try {
|
|
2178
1788
|
if (wasProcessing) {
|
|
@@ -2210,7 +1820,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2210
1820
|
return new Promise((resolve) => {
|
|
2211
1821
|
const startedAt = Date.now();
|
|
2212
1822
|
const timer = setInterval(() => {
|
|
2213
|
-
if (!this.ptyProcess || this.currentStatus === 'stopped') {
|
|
1823
|
+
if (!this.ptyProcess || this.engine.currentStatus === 'stopped') {
|
|
2214
1824
|
clearInterval(timer);
|
|
2215
1825
|
resolve(true);
|
|
2216
1826
|
return;
|
|
@@ -2224,18 +1834,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2224
1834
|
}
|
|
2225
1835
|
|
|
2226
1836
|
shutdown(): void {
|
|
2227
|
-
this.clearIdleFinishCandidate('shutdown');
|
|
1837
|
+
this.engine.clearIdleFinishCandidate('shutdown');
|
|
2228
1838
|
this.clearAllTimers();
|
|
2229
1839
|
this.pendingOutputParseChunks = [];
|
|
2230
1840
|
this.pendingTerminalQueryTail = '';
|
|
2231
1841
|
this.ptyOutputChunks = [];
|
|
2232
|
-
this.
|
|
1842
|
+
if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
|
|
1843
|
+
this.pendingOutboundQueue = [];
|
|
1844
|
+
this.pendingOutboundFlushInFlight = false;
|
|
2233
1845
|
if (this.ptyProcess) {
|
|
2234
1846
|
this.ptyProcess.write('\x03');
|
|
2235
1847
|
setTimeout(() => {
|
|
2236
1848
|
try { this.ptyProcess?.kill(); } catch { }
|
|
2237
1849
|
this.ptyProcess = null;
|
|
2238
|
-
this.setStatus('stopped', 'stop_cmd');
|
|
1850
|
+
this.engine.setStatus('stopped', 'stop_cmd');
|
|
2239
1851
|
this.ready = false;
|
|
2240
1852
|
this.startupParseGate = false;
|
|
2241
1853
|
this.spawnAt = 0;
|
|
@@ -2245,12 +1857,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2245
1857
|
}
|
|
2246
1858
|
|
|
2247
1859
|
detach(): void {
|
|
2248
|
-
this.clearIdleFinishCandidate('detach');
|
|
1860
|
+
this.engine.clearIdleFinishCandidate('detach');
|
|
2249
1861
|
this.clearAllTimers();
|
|
2250
1862
|
this.pendingOutputParseChunks = [];
|
|
2251
1863
|
this.pendingTerminalQueryTail = '';
|
|
2252
1864
|
this.ptyOutputChunks = [];
|
|
2253
|
-
this.
|
|
1865
|
+
if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
|
|
1866
|
+
this.pendingOutboundQueue = [];
|
|
1867
|
+
this.pendingOutboundFlushInFlight = false;
|
|
2254
1868
|
if (this.ptyProcess) {
|
|
2255
1869
|
try {
|
|
2256
1870
|
if (typeof this.ptyProcess.detach === 'function') {
|
|
@@ -2268,82 +1882,128 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2268
1882
|
}
|
|
2269
1883
|
|
|
2270
1884
|
clearHistory(): void {
|
|
2271
|
-
this.clearIdleFinishCandidate('clear_history');
|
|
1885
|
+
this.engine.clearIdleFinishCandidate('clear_history');
|
|
2272
1886
|
this.accumulatedBuffer = '';
|
|
2273
1887
|
this.accumulatedRawBuffer = '';
|
|
2274
|
-
this.currentTurnScope = null;
|
|
2275
|
-
this.submitRetryUsed = false;
|
|
2276
|
-
this.submitRetryPromptSnippet = '';
|
|
1888
|
+
this.engine.currentTurnScope = null;
|
|
1889
|
+
this.engine.submitRetryUsed = false;
|
|
1890
|
+
this.engine.submitRetryPromptSnippet = '';
|
|
2277
1891
|
if (this.pendingOutputParseTimer) { clearTimeout(this.pendingOutputParseTimer); this.pendingOutputParseTimer = null; }
|
|
2278
1892
|
this.pendingOutputParseChunks = [];
|
|
2279
1893
|
this.pendingTerminalQueryTail = '';
|
|
2280
1894
|
if (this.ptyOutputFlushTimer) { clearTimeout(this.ptyOutputFlushTimer); this.ptyOutputFlushTimer = null; }
|
|
2281
1895
|
this.ptyOutputChunks = [];
|
|
2282
|
-
if (this.
|
|
2283
|
-
this.
|
|
1896
|
+
if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
|
|
1897
|
+
this.pendingOutboundQueue = [];
|
|
1898
|
+
this.pendingOutboundFlushInFlight = false;
|
|
2284
1899
|
this.resetTerminalScreen();
|
|
2285
1900
|
this.ptyProcess?.clearBuffer?.();
|
|
2286
1901
|
this.onStatusChange?.();
|
|
2287
1902
|
}
|
|
2288
1903
|
|
|
2289
|
-
isProcessing(): boolean { return this.isWaitingForResponse; }
|
|
1904
|
+
isProcessing(): boolean { return this.engine.isWaitingForResponse; }
|
|
2290
1905
|
isReady(): boolean { return this.ready; }
|
|
2291
1906
|
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
1907
|
+
// ─── State machine property accessors (delegate to engine) ──────────────
|
|
1908
|
+
// These expose engine state for external callers (tests, debug tools, etc.)
|
|
1909
|
+
|
|
1910
|
+
get currentStatus(): CliSessionStatus['status'] { return this.engine.currentStatus; }
|
|
1911
|
+
set currentStatus(v: CliSessionStatus['status']) { this.engine.setStatus(v); }
|
|
1912
|
+
|
|
1913
|
+
get isWaitingForResponse(): boolean { return this.engine.isWaitingForResponse; }
|
|
1914
|
+
set isWaitingForResponse(v: boolean) { this.engine.isWaitingForResponse = v; }
|
|
1915
|
+
|
|
1916
|
+
get activeModal(): { message: string; buttons: string[] } | null { return this.engine.activeModal; }
|
|
1917
|
+
set activeModal(v: { message: string; buttons: string[] } | null) { this.engine.activeModal = v; }
|
|
1918
|
+
|
|
1919
|
+
get currentTurnScope(): TurnParseScope | null { return this.engine.currentTurnScope; }
|
|
1920
|
+
set currentTurnScope(v: TurnParseScope | null) { this.engine.currentTurnScope = v; }
|
|
1921
|
+
|
|
1922
|
+
get responseEpoch(): number { return this.engine.responseEpoch; }
|
|
1923
|
+
set responseEpoch(v: number) { this.engine.responseEpoch = v; }
|
|
1924
|
+
|
|
1925
|
+
get submitRetryUsed(): boolean { return this.engine.submitRetryUsed; }
|
|
1926
|
+
set submitRetryUsed(v: boolean) { this.engine.submitRetryUsed = v; }
|
|
1927
|
+
|
|
1928
|
+
get submitRetryPromptSnippet(): string { return this.engine.submitRetryPromptSnippet; }
|
|
1929
|
+
set submitRetryPromptSnippet(v: string) { this.engine.submitRetryPromptSnippet = v; }
|
|
1930
|
+
|
|
1931
|
+
get responseSettleIgnoreUntil(): number { return this.engine.responseSettleIgnoreUntil; }
|
|
1932
|
+
set responseSettleIgnoreUntil(v: number) { this.engine.responseSettleIgnoreUntil = v; }
|
|
1933
|
+
|
|
1934
|
+
get submitPendingUntil(): number { return this.engine.submitPendingUntil; }
|
|
1935
|
+
set submitPendingUntil(v: number) { this.engine.submitPendingUntil = v; }
|
|
1936
|
+
|
|
1937
|
+
get lastApprovalResolvedAt(): number { return this.engine.lastApprovalResolvedAt; }
|
|
1938
|
+
set lastApprovalResolvedAt(v: number) { this.engine.lastApprovalResolvedAt = v; }
|
|
1939
|
+
|
|
1940
|
+
get providerErrorMessage(): string | null { return this.engine.providerErrorMessage; }
|
|
1941
|
+
get providerErrorReason(): string | null { return this.engine.providerErrorReason; }
|
|
1942
|
+
|
|
1943
|
+
get pendingScriptStatus(): 'generating' | 'waiting_approval' | null { return this.engine.pendingScriptStatus; }
|
|
1944
|
+
get pendingScriptStatusSince(): number { return this.engine.pendingScriptStatusSince; }
|
|
1945
|
+
|
|
1946
|
+
get finishRetryCount(): number { return this.engine.finishRetryCount; }
|
|
1947
|
+
set finishRetryCount(v: number) { this.engine.finishRetryCount = v; }
|
|
1948
|
+
|
|
1949
|
+
get traceSessionId(): string { return this.engine.getTraceSessionId(); }
|
|
1950
|
+
get traceEntries(): CliTraceEntry[] { return this.engine.getTraceEntries(); }
|
|
1951
|
+
get statusHistory(): { status: string; at: number; trigger?: string }[] { return this.engine.getStatusHistory(); }
|
|
1952
|
+
get traceSeq(): number { return this.engine.getTraceEntries().length; }
|
|
1953
|
+
|
|
1954
|
+
/** Expose engine's evaluateSettled for test access */
|
|
1955
|
+
evaluateSettled(): void {
|
|
1956
|
+
LOG.debug(
|
|
1957
|
+
'CLI',
|
|
1958
|
+
`[${this.cliType}] settled diagnostics delegated to state engine`);
|
|
1959
|
+
this.engine.evaluateSettled(this.getSnapshot());
|
|
1960
|
+
}
|
|
1961
|
+
/** Expose engine's scheduleSettle for test access */
|
|
1962
|
+
scheduleSettle(): void { this.engine.scheduleSettle(); }
|
|
1963
|
+
/** Expose engine's clearIdleFinishCandidate for test access */
|
|
1964
|
+
clearIdleFinishCandidate(reason: string): void { this.engine.clearIdleFinishCandidate(reason); }
|
|
1965
|
+
/** Expose engine's finishResponse for test access */
|
|
1966
|
+
finishResponse(): void { this.engine.finishResponse(); }
|
|
1967
|
+
/** Returns a point-in-time snapshot of all buffer/screen state for external consumers (e.g. CliStateEngine). */
|
|
1968
|
+
getSnapshot(): CliBufferSnapshot {
|
|
1969
|
+
const screenText = this.terminalScreen.getText() || '';
|
|
1970
|
+
return {
|
|
1971
|
+
accumulatedBuffer: this.accumulatedBuffer,
|
|
1972
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1973
|
+
recentOutputBuffer: this.recentOutputBuffer,
|
|
1974
|
+
responseBuffer: this.responseBuffer,
|
|
1975
|
+
screenText,
|
|
1976
|
+
parseScreenText: this.getParseScreenText(screenText),
|
|
1977
|
+
workingDir: this.workingDir,
|
|
1978
|
+
providerSessionId: this.providerSessionId,
|
|
1979
|
+
runtimeSettings: this.runtimeSettings,
|
|
1980
|
+
isWaitingForResponse: this.engine.isWaitingForResponse,
|
|
1981
|
+
currentTurnScope: this.engine.currentTurnScope,
|
|
1982
|
+
lastOutputAt: this.lastOutputAt,
|
|
1983
|
+
lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
|
|
1984
|
+
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
1985
|
+
lastScreenSnapshot: this.lastScreenSnapshot,
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
isAlive(): boolean { return this.ptyProcess !== null; }
|
|
1989
|
+
flushOutboundQueue(): void { this.schedulePendingOutboundFlush(); }
|
|
1990
|
+
|
|
1991
|
+
async writeRaw(data: string | Buffer): Promise<void> {
|
|
1992
|
+
const str = Buffer.isBuffer(data) ? data.toString('utf8') : data;
|
|
1993
|
+
await this.writeToPty(str);
|
|
2298
1994
|
}
|
|
2299
1995
|
|
|
2300
1996
|
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
|
-
}
|
|
1997
|
+
this.engine.resolveModal(buttonIndex);
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
getApprovalKeyForIndex(buttonIndex: number): string | undefined {
|
|
2001
|
+
return buttonIndex in this.approvalKeys ? this.approvalKeys[buttonIndex] : undefined;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
/** Returns true if an approval was resolved within the adapter's cooldown window. */
|
|
2005
|
+
isApprovalRecentlyResolved(): boolean {
|
|
2006
|
+
return this.engine.isApprovalRecentlyResolved();
|
|
2347
2007
|
}
|
|
2348
2008
|
|
|
2349
2009
|
resize(cols: number, rows: number): void {
|
|
@@ -2357,7 +2017,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2357
2017
|
}
|
|
2358
2018
|
|
|
2359
2019
|
private getParsedDebugState(): Record<string, any> | null {
|
|
2360
|
-
if (this.startupParseGate ||
|
|
2020
|
+
if (this.startupParseGate || !this.runner.hasParseSession()) return null;
|
|
2361
2021
|
try {
|
|
2362
2022
|
const parsed = this.getScriptParsedStatus();
|
|
2363
2023
|
return parsed && typeof parsed === 'object' ? parsed as Record<string, any> : null;
|
|
@@ -2369,18 +2029,43 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2369
2029
|
getDebugState(): Record<string, any> {
|
|
2370
2030
|
const screenText = sanitizeTerminalText(this.terminalScreen.getText());
|
|
2371
2031
|
const startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
|
|
2372
|
-
const
|
|
2373
|
-
|
|
2032
|
+
const startupDetectedStatus = this.startupParseGate && !startupModal
|
|
2033
|
+
? this.runDetectStatus(this.recentOutputBuffer || screenText)
|
|
2034
|
+
: null;
|
|
2035
|
+
const effectiveReady = this.ready || !!startupModal || startupDetectedStatus === 'waiting_approval';
|
|
2374
2036
|
const parsedDebugState = this.getParsedDebugState();
|
|
2375
2037
|
const parsedMessages = Array.isArray(parsedDebugState?.messages) ? parsedDebugState.messages : [];
|
|
2038
|
+
const hasFinalAssistant = (p: any) => {
|
|
2039
|
+
const msgs = Array.isArray(p?.messages) ? p.messages : [];
|
|
2040
|
+
return msgs.some((m: any) => m?.role === 'assistant' && typeof m.content === 'string' && m.content.trim());
|
|
2041
|
+
};
|
|
2042
|
+
let effectiveStatus = this.projectEffectiveStatus(startupModal);
|
|
2043
|
+
if (parsedDebugState?.status === 'error') {
|
|
2044
|
+
effectiveStatus = 'error';
|
|
2045
|
+
}
|
|
2046
|
+
// (fix) Mirror the getStatus contract: never surface waiting_approval
|
|
2047
|
+
// without a concrete modal. detectStatus alone can fire while
|
|
2048
|
+
// parseApproval is still null, and a bare debugState waiting_approval
|
|
2049
|
+
// confused dashboards reading the debug payload.
|
|
2050
|
+
const debugEffectiveModal = startupModal || this.engine.activeModal;
|
|
2051
|
+
if (startupDetectedStatus === 'waiting_approval' && debugEffectiveModal) {
|
|
2052
|
+
effectiveStatus = 'waiting_approval';
|
|
2053
|
+
}
|
|
2054
|
+
if (
|
|
2055
|
+
effectiveStatus === 'idle'
|
|
2056
|
+
&& parsedDebugState?.status === 'generating'
|
|
2057
|
+
&& !hasFinalAssistant(parsedDebugState)
|
|
2058
|
+
) {
|
|
2059
|
+
effectiveStatus = 'generating';
|
|
2060
|
+
}
|
|
2376
2061
|
return {
|
|
2377
2062
|
type: this.cliType,
|
|
2378
2063
|
name: this.cliName,
|
|
2379
2064
|
providerResolution: this.providerResolutionMeta,
|
|
2380
2065
|
status: effectiveStatus,
|
|
2381
2066
|
projectedStatus: effectiveStatus,
|
|
2382
|
-
rawStatus: this.currentStatus,
|
|
2383
|
-
lifecycleStatus: this.isWaitingForResponse ? 'awaiting_response' : 'idle',
|
|
2067
|
+
rawStatus: this.engine.currentStatus,
|
|
2068
|
+
lifecycleStatus: this.engine.isWaitingForResponse ? 'awaiting_response' : 'idle',
|
|
2384
2069
|
ready: effectiveReady,
|
|
2385
2070
|
startupParseGate: this.startupParseGate,
|
|
2386
2071
|
spawnAt: this.spawnAt,
|
|
@@ -2394,38 +2079,47 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2394
2079
|
providerSessionId: parsedDebugState.providerSessionId,
|
|
2395
2080
|
transcriptAuthority: parsedDebugState.transcriptAuthority,
|
|
2396
2081
|
coverage: parsedDebugState.coverage,
|
|
2082
|
+
errorMessage: parsedDebugState.errorMessage,
|
|
2083
|
+
errorReason: parsedDebugState.errorReason,
|
|
2397
2084
|
activeModal: parsedDebugState.activeModal,
|
|
2398
2085
|
messageCount: parsedMessages.length,
|
|
2399
2086
|
} : null,
|
|
2400
2087
|
screenText: screenText.slice(-4000),
|
|
2401
|
-
currentTurnScope: this.currentTurnScope,
|
|
2088
|
+
currentTurnScope: this.engine.currentTurnScope,
|
|
2402
2089
|
startupBuffer: this.startupBuffer.slice(-4000),
|
|
2403
2090
|
recentOutputBuffer: this.recentOutputBuffer.slice(-500),
|
|
2404
|
-
settledBuffer: this.settledBuffer.slice(-500),
|
|
2405
2091
|
accumulatedBufferLength: this.accumulatedBuffer.length,
|
|
2406
2092
|
accumulatedRawBufferLength: this.accumulatedRawBuffer.length,
|
|
2407
2093
|
rawBufferPreview: this.accumulatedRawBuffer.slice(-1000),
|
|
2408
2094
|
sanitizedRawPreview: sanitizeTerminalText(this.accumulatedRawBuffer).slice(-1000),
|
|
2409
2095
|
responseBuffer: this.responseBuffer.slice(-1000),
|
|
2096
|
+
pendingOutboundQueue: this.pendingOutboundQueue.map((message) => ({
|
|
2097
|
+
id: message.id,
|
|
2098
|
+
role: message.role,
|
|
2099
|
+
content: message.content,
|
|
2100
|
+
queuedAt: message.queuedAt,
|
|
2101
|
+
source: message.source,
|
|
2102
|
+
})),
|
|
2103
|
+
pendingOutboundCount: this.pendingOutboundQueue.length,
|
|
2410
2104
|
lastOutputAt: this.lastOutputAt,
|
|
2411
2105
|
lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
|
|
2412
2106
|
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
2413
2107
|
lastScreenSnapshot: this.lastScreenSnapshot.slice(-500),
|
|
2414
|
-
isWaitingForResponse: this.isWaitingForResponse,
|
|
2415
|
-
activeModal: startupModal || this.activeModal,
|
|
2416
|
-
lastApprovalResolvedAt: this.lastApprovalResolvedAt,
|
|
2108
|
+
isWaitingForResponse: this.engine.isWaitingForResponse,
|
|
2109
|
+
activeModal: startupModal || this.engine.activeModal,
|
|
2110
|
+
lastApprovalResolvedAt: this.engine.lastApprovalResolvedAt,
|
|
2417
2111
|
sendDelayMs: this.sendDelayMs,
|
|
2418
2112
|
sendKey: this.sendKey,
|
|
2419
2113
|
submitStrategy: this.submitStrategy,
|
|
2420
2114
|
requirePromptEchoBeforeSubmit: this.requirePromptEchoBeforeSubmit,
|
|
2421
|
-
submitPendingUntil: this.submitPendingUntil,
|
|
2422
|
-
responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
|
|
2115
|
+
submitPendingUntil: this.engine.submitPendingUntil,
|
|
2116
|
+
responseSettleIgnoreUntil: this.engine.responseSettleIgnoreUntil,
|
|
2423
2117
|
resizeSuppressUntil: this.resizeSuppressUntil,
|
|
2424
2118
|
hasCliScripts: this.hasCliScripts(),
|
|
2425
|
-
scriptNames:
|
|
2426
|
-
traceSessionId: this.
|
|
2427
|
-
traceEntryCount: this.
|
|
2428
|
-
statusHistory: this.
|
|
2119
|
+
scriptNames: this.runner.getScriptNames(),
|
|
2120
|
+
traceSessionId: this.engine.getTraceSessionId(),
|
|
2121
|
+
traceEntryCount: this.engine.getTraceEntries().length,
|
|
2122
|
+
statusHistory: this.engine.getStatusHistory().slice(-30),
|
|
2429
2123
|
timeouts: this.timeouts,
|
|
2430
2124
|
pendingOutputParseBufferLength: this.pendingOutputParseChunks.reduce((total, chunk) => total + chunk.length, 0),
|
|
2431
2125
|
pendingOutputParseScheduled: !!this.pendingOutputParseTimer,
|
|
@@ -2435,20 +2129,21 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2435
2129
|
|
|
2436
2130
|
getTraceState(limit = 120): Record<string, any> {
|
|
2437
2131
|
const cappedLimit = Math.max(1, Math.min(500, Number.isFinite(limit) ? Math.floor(limit) : 120));
|
|
2132
|
+
const traceEntries = this.engine.getTraceEntries();
|
|
2438
2133
|
return {
|
|
2439
|
-
sessionId: this.
|
|
2134
|
+
sessionId: this.engine.getTraceSessionId(),
|
|
2440
2135
|
providerResolution: this.providerResolutionMeta,
|
|
2441
|
-
entryCount:
|
|
2442
|
-
entries:
|
|
2136
|
+
entryCount: traceEntries.length,
|
|
2137
|
+
entries: traceEntries.slice(-cappedLimit),
|
|
2443
2138
|
screenText: summarizeCliTraceText(this.terminalScreen.getText(), 4000),
|
|
2444
2139
|
recentOutputBuffer: summarizeCliTraceText(this.recentOutputBuffer, 1000),
|
|
2445
2140
|
responseBuffer: summarizeCliTraceText(this.responseBuffer, 1200),
|
|
2446
2141
|
status: this.projectEffectiveStatus(),
|
|
2447
2142
|
projectedStatus: this.projectEffectiveStatus(),
|
|
2448
|
-
rawStatus: this.currentStatus,
|
|
2449
|
-
lifecycleStatus: this.isWaitingForResponse ? 'awaiting_response' : 'idle',
|
|
2450
|
-
activeModal: this.activeModal,
|
|
2451
|
-
currentTurnScope: this.currentTurnScope,
|
|
2143
|
+
rawStatus: this.engine.currentStatus,
|
|
2144
|
+
lifecycleStatus: this.engine.isWaitingForResponse ? 'awaiting_response' : 'idle',
|
|
2145
|
+
activeModal: this.engine.activeModal,
|
|
2146
|
+
currentTurnScope: this.engine.currentTurnScope,
|
|
2452
2147
|
messages: [],
|
|
2453
2148
|
};
|
|
2454
2149
|
}
|