@adhdev/daemon-core 0.9.82-rc.21 → 0.9.82-rc.211
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 +1 -0
- package/dist/boot/process-hardening.d.ts +50 -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 +23 -2
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +106 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +29 -0
- package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
- package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +3 -3
- package/dist/cli-adapters/terminal-backends/types.d.ts +1 -2
- package/dist/cli-adapters/terminal-screen.d.ts +2 -11
- package/dist/commands/cli-manager.d.ts +2 -1
- package/dist/commands/handler.d.ts +110 -0
- package/dist/commands/mesh-coordinator.d.ts +85 -1
- package/dist/commands/router.d.ts +32 -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 +72 -1
- package/dist/git/git-commands.d.ts +9 -1
- package/dist/git/git-diff.d.ts +6 -0
- package/dist/git/git-types.d.ts +2 -0
- package/dist/index.d.ts +46 -11
- package/dist/index.js +35173 -15756
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +34880 -15556
- 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/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-prompt.d.ts +36 -0
- package/dist/mesh/coordinator-registry.d.ts +59 -0
- package/dist/mesh/mesh-active-work.d.ts +90 -0
- package/dist/mesh/mesh-delivery-policy.d.ts +131 -0
- package/dist/mesh/mesh-events-coordinator.d.ts +151 -0
- package/dist/mesh/mesh-events-pending.d.ts +33 -0
- package/dist/mesh/mesh-events-stale.d.ts +40 -0
- package/dist/mesh/mesh-events-utils.d.ts +14 -0
- package/dist/mesh/mesh-events.d.ts +5 -40
- package/dist/mesh/mesh-fast-forward.d.ts +41 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -3
- package/dist/mesh/mesh-ledger.d.ts +77 -1
- package/dist/mesh/mesh-missions.d.ts +58 -0
- package/dist/mesh/mesh-refine-status.d.ts +26 -0
- package/dist/mesh/mesh-review-inbox.d.ts +90 -0
- package/dist/mesh/mesh-runtime-store.d.ts +324 -0
- package/dist/mesh/mesh-task-stats.d.ts +49 -0
- package/dist/mesh/mesh-work-queue.d.ts +137 -6
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +215 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +135 -0
- package/dist/providers/acp-provider-instance.d.ts +2 -0
- package/dist/providers/approval-utils.d.ts +13 -0
- package/dist/providers/cli-provider-instance.d.ts +39 -3
- package/dist/providers/contracts.d.ts +130 -6
- package/dist/providers/external-sources.d.ts +71 -0
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
- package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
- package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
- package/dist/providers/native-history/dispatcher.d.ts +27 -0
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
- package/dist/providers/native-history/index.d.ts +13 -0
- package/dist/providers/provider-instance-manager.d.ts +12 -0
- package/dist/providers/provider-instance.d.ts +13 -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 +85 -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 +52 -0
- package/dist/providers/spec/cli-adapter.d.ts +101 -0
- package/dist/providers/spec/driver.d.ts +225 -0
- package/dist/providers/spec/evaluator.d.ts +54 -0
- package/dist/providers/spec/loader.d.ts +15 -0
- package/dist/providers/spec/native-history-executor.d.ts +41 -0
- package/dist/providers/spec/route.d.ts +4 -0
- package/dist/providers/spec/schema.gen.d.ts +2025 -0
- package/dist/providers/spec/types.d.ts +302 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +48 -0
- package/dist/repo-mesh-types.d.ts +109 -1
- package/dist/sessions/registry.d.ts +3 -0
- package/dist/shared-types-extra.d.ts +2 -4
- package/dist/shared-types.d.ts +42 -1
- 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 +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +6 -3
- package/src/agent-stream/poller.ts +2 -3
- package/src/boot/daemon-lifecycle.ts +22 -10
- package/src/boot/process-hardening.ts +89 -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 +22 -2
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1138 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +673 -1139
- 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 +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +59 -11
- package/src/cli-adapters/raw-terminal-io.ts +252 -0
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +35 -29
- package/src/cli-adapters/terminal-backends/types.ts +1 -3
- package/src/cli-adapters/terminal-screen.ts +11 -81
- package/src/commands/chat-commands.ts +1787 -60
- package/src/commands/cli-manager.ts +283 -14
- package/src/commands/handler.ts +809 -2
- package/src/commands/mesh-coordinator.ts +332 -122
- package/src/commands/router.ts +4198 -557
- package/src/config/chat-history.ts +95 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +280 -2
- 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/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +42 -12
- package/src/git/git-diff.ts +53 -0
- package/src/git/git-status.ts +35 -6
- package/src/git/git-types.ts +2 -0
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +126 -10
- 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/logger.ts +2 -1
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +218 -31
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +437 -0
- package/src/mesh/mesh-delivery-policy.ts +315 -0
- package/src/mesh/mesh-events-coordinator.ts +1404 -0
- package/src/mesh/mesh-events-pending.ts +371 -0
- package/src/mesh/mesh-events-stale.ts +283 -0
- package/src/mesh/mesh-events-utils.ts +161 -0
- package/src/mesh/mesh-events.ts +24 -908
- package/src/mesh/mesh-fast-forward.ts +438 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger-reconciliation.ts +12 -5
- package/src/mesh/mesh-ledger.ts +587 -102
- package/src/mesh/mesh-missions.ts +151 -0
- package/src/mesh/mesh-refine-status.ts +144 -0
- package/src/mesh/mesh-review-inbox.ts +307 -0
- package/src/mesh/mesh-runtime-store.ts +1305 -0
- package/src/mesh/mesh-task-stats.ts +154 -0
- package/src/mesh/mesh-work-queue.ts +490 -166
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +403 -0
- package/src/mesh/worktree-bootstrap-config.ts +326 -0
- package/src/providers/acp-provider-instance.ts +15 -1
- package/src/providers/approval-utils.d.ts +4 -0
- package/src/providers/approval-utils.ts +47 -5
- package/src/providers/chat-message-normalization.ts +4 -11
- package/src/providers/cli-provider-instance.ts +833 -64
- package/src/providers/contracts.d.ts +55 -0
- package/src/providers/contracts.ts +141 -6
- package/src/providers/external-sources.ts +218 -0
- package/src/providers/ide-provider-instance.ts +19 -5
- 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 +419 -0
- package/src/providers/native-history/dispatcher.ts +340 -0
- package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
- package/src/providers/native-history/index.ts +30 -0
- package/src/providers/provider-instance-manager.ts +30 -0
- package/src/providers/provider-instance.ts +10 -1
- package/src/providers/provider-loader.ts +582 -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 +262 -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 +138 -0
- package/src/providers/spec/cli-adapter.ts +626 -0
- package/src/providers/spec/driver.ts +922 -0
- package/src/providers/spec/evaluator.ts +597 -0
- package/src/providers/spec/loader.ts +324 -0
- package/src/providers/spec/native-history-executor.ts +939 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/schema.gen.ts +691 -0
- package/src/providers/spec/schema.json +341 -0
- package/src/providers/spec/types.ts +356 -0
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +425 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +119 -1
- package/src/sessions/registry.ts +3 -0
- package/src/shared-types-extra.ts +2 -4
- package/src/shared-types.ts +45 -1
- package/src/status/builders.ts +24 -6
- package/src/status/normalize.ts +2 -0
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +84 -25
- package/src/system/host-memory.ts +29 -12
- 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
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecCliAdapter — bridges SpecDriver into the daemon's CliAdapter
|
|
3
|
+
* interface so an existing CliProviderInstance can drive a spec-backed
|
|
4
|
+
* provider without rewriting the surrounding session machinery.
|
|
5
|
+
*
|
|
6
|
+
* Translation:
|
|
7
|
+
* spec state.id 'approval' (with modal_buttons that produced a modal)
|
|
8
|
+
* → CliAdapterStatus.status='waiting_approval', activeModal={...}
|
|
9
|
+
* spec state.id 'busy' / any non-decision state with a 'busy' label
|
|
10
|
+
* → status='generating'
|
|
11
|
+
* spec state.id 'idle' or default
|
|
12
|
+
* → status='idle'
|
|
13
|
+
*
|
|
14
|
+
* Methods that the round-3 spec model doesn't have an opinion on
|
|
15
|
+
* (transcript reading, slash commands, history, runtime metadata
|
|
16
|
+
* surfacing) are minimal stubs. They satisfy the daemon's call sites
|
|
17
|
+
* without pretending to implement anything.
|
|
18
|
+
*/
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
import { SpecDriver, type DashboardEvent } from './driver.js';
|
|
22
|
+
import { evaluate } from './evaluator.js';
|
|
23
|
+
import { executeNativeHistory } from './native-history-executor.js';
|
|
24
|
+
import { loadSpec } from './loader.js';
|
|
25
|
+
import type { CliSpec } from './types.js';
|
|
26
|
+
import type { CliAdapter, CliAdapterStatus } from '../../cli-adapter-types.js';
|
|
27
|
+
import type { ChatMessage } from '../../types.js';
|
|
28
|
+
import type { PtyTransportFactory } from '../../cli-adapters/pty-transport.js';
|
|
29
|
+
import { LOG } from '../../logging/logger.js';
|
|
30
|
+
import {
|
|
31
|
+
buildClaudeInteractiveTuiAnswerSteps,
|
|
32
|
+
buildClaudeInteractiveToolResult,
|
|
33
|
+
detectClaudeAskUserQuestionPromptFromJson,
|
|
34
|
+
detectClaudeAskUserQuestionPromptFromTuiPages,
|
|
35
|
+
type ClaudeInteractiveTuiPage,
|
|
36
|
+
type InteractivePrompt,
|
|
37
|
+
type InteractivePromptResponse,
|
|
38
|
+
} from '../types/interactive-prompt.js';
|
|
39
|
+
|
|
40
|
+
function stripAnsi(text: string): string {
|
|
41
|
+
// eslint-disable-next-line no-control-regex
|
|
42
|
+
return String(text || '')
|
|
43
|
+
.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, '')
|
|
44
|
+
.replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, '')
|
|
45
|
+
.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class SpecCliAdapter implements CliAdapter {
|
|
49
|
+
readonly cliType: string;
|
|
50
|
+
readonly cliName: string;
|
|
51
|
+
readonly workingDir: string;
|
|
52
|
+
/**
|
|
53
|
+
* Marker the daemon's finalization gate checks: `getStatus()` returns
|
|
54
|
+
* `messages: []` by design here (chat history lives in the daemon's
|
|
55
|
+
* native-history pipeline, not the adapter). Without this flag,
|
|
56
|
+
* cli-provider-instance's `missing_final_assistant` gate would stall
|
|
57
|
+
* every turn until the 30s safety timeout because it expects the
|
|
58
|
+
* adapter to surface the final assistant message.
|
|
59
|
+
*/
|
|
60
|
+
readonly chatMessagesOwnedExternally = true as const;
|
|
61
|
+
|
|
62
|
+
private driver: SpecDriver;
|
|
63
|
+
private spec: CliSpec;
|
|
64
|
+
private lastEvent: DashboardEvent | null = null;
|
|
65
|
+
private latestState: { id: string; label: string; title: string | null } | null = null;
|
|
66
|
+
private latestModal: { title: string | null; buttons: { index: number; label: string }[] } | null = null;
|
|
67
|
+
private statusCallback: (() => void) | null = null;
|
|
68
|
+
private ptyDataCallback: ((data: string) => void) | null = null;
|
|
69
|
+
private partialResponse = '';
|
|
70
|
+
private activeInteractivePrompt: InteractivePrompt | null = null;
|
|
71
|
+
private interactivePromptTransport: 'stream-json' | 'tui' | null = null;
|
|
72
|
+
private claudeTuiPromptCaptureInFlight = false;
|
|
73
|
+
private jsonLineTail = '';
|
|
74
|
+
private exited = false;
|
|
75
|
+
private spawned = false;
|
|
76
|
+
private providerSessionId: string | undefined;
|
|
77
|
+
/** Wall clock at the moment spawn() ran. Used as the cutoff for
|
|
78
|
+
* native-history file selection so a prior session's transcript
|
|
79
|
+
* can't leak into this session before the agent has written its
|
|
80
|
+
* own records. */
|
|
81
|
+
private spawnedAtMs = 0;
|
|
82
|
+
/** Env vars the daemon set on the spawned child. Mesh coordinator
|
|
83
|
+
* points hermes at a per-coordinator HERMES_HOME so the dashboard's
|
|
84
|
+
* native-history reader needs that override to find the right
|
|
85
|
+
* state.db; without it the reader sees ~/.hermes/state.db which
|
|
86
|
+
* the coordinator-launched hermes never writes to. The choice to
|
|
87
|
+
* redirect HERMES_HOME is a workaround for an unresolved hermes
|
|
88
|
+
* upstream feature gap (see hermes-agent#23130 — runtime-supplied
|
|
89
|
+
* MCP config), so this routing keeps the dashboard honest until
|
|
90
|
+
* hermes ships a runtime MCP override. */
|
|
91
|
+
private spawnedEnv: Record<string, string> = {};
|
|
92
|
+
|
|
93
|
+
constructor(
|
|
94
|
+
specPath: string,
|
|
95
|
+
workingDir: string,
|
|
96
|
+
cliArgs: string[],
|
|
97
|
+
extraEnv: Record<string, string>,
|
|
98
|
+
transportFactory?: PtyTransportFactory,
|
|
99
|
+
) {
|
|
100
|
+
const res = loadSpec(specPath);
|
|
101
|
+
if (!res.ok) throw new Error(`spec invalid (${specPath}): ${res.errors.join('; ')}`);
|
|
102
|
+
this.spec = res.spec;
|
|
103
|
+
this.cliType = this.spec.id;
|
|
104
|
+
this.cliName = this.spec.name;
|
|
105
|
+
this.workingDir = workingDir;
|
|
106
|
+
this.spawnedEnv = { ...extraEnv };
|
|
107
|
+
|
|
108
|
+
// cli-manager.ts allocates providerSessionId per launch and threads
|
|
109
|
+
// it through resume.newSessionArgs as additional cliArgs (e.g.
|
|
110
|
+
// ["--session-id", "<uuid>"]). We must hand those to SpecDriver
|
|
111
|
+
// so the agent uses the daemon's id, otherwise (claude case) the
|
|
112
|
+
// agent generates its own id and the chat-history pipeline can't
|
|
113
|
+
// pair the on-disk transcript with the live session.
|
|
114
|
+
this.driver = new SpecDriver({
|
|
115
|
+
specPath,
|
|
116
|
+
workingDir,
|
|
117
|
+
extraEnv,
|
|
118
|
+
hotReload: true,
|
|
119
|
+
emitTrace: false,
|
|
120
|
+
transportFactory,
|
|
121
|
+
extraCliArgs: cliArgs,
|
|
122
|
+
});
|
|
123
|
+
this.driver.subscribe((ev) => this.handleEvent(ev));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async spawn(): Promise<void> {
|
|
127
|
+
if (this.spawned) return;
|
|
128
|
+
this.driver.start();
|
|
129
|
+
this.spawned = true;
|
|
130
|
+
this.spawnedAtMs = Date.now();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async sendMessage(text: string): Promise<void> {
|
|
134
|
+
// Content-free at info — the prompt body is user data.
|
|
135
|
+
LOG.info('SpecAdapter', `[${this.cliType}] sendMessage(len=${text.length})`);
|
|
136
|
+
LOG.debug('SpecAdapter', `[${this.cliType}] sendMessage body=${JSON.stringify(text.slice(0, 80))}${text.length > 80 ? '…' : ''}`);
|
|
137
|
+
this.driver.dispatch({ kind: 'send_message', text });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
getStatus(): CliAdapterStatus {
|
|
141
|
+
const sessionFields = this.providerSessionId ? { providerSessionId: this.providerSessionId } : {};
|
|
142
|
+
if (this.exited) return { status: 'stopped', messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, ...sessionFields };
|
|
143
|
+
if (!this.spawned) return { status: 'starting', messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, ...sessionFields };
|
|
144
|
+
|
|
145
|
+
// Refresh native history lazily — the watch_path is cheap to stat,
|
|
146
|
+
// but parsing a full session.jsonl every call would be wasteful.
|
|
147
|
+
this.maybeRefreshNativeHistory();
|
|
148
|
+
|
|
149
|
+
const state = this.latestState;
|
|
150
|
+
if (!state) return { status: 'starting', messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, ...sessionFields };
|
|
151
|
+
|
|
152
|
+
const modal = this.latestModal;
|
|
153
|
+
const lc = state.id.toLowerCase();
|
|
154
|
+
if (modal) {
|
|
155
|
+
return {
|
|
156
|
+
status: 'waiting_approval',
|
|
157
|
+
messages: [],
|
|
158
|
+
activeModal: {
|
|
159
|
+
message: modal.title ?? state.label,
|
|
160
|
+
buttons: modal.buttons.map(b => b.label),
|
|
161
|
+
},
|
|
162
|
+
activeInteractivePrompt: this.activeInteractivePrompt,
|
|
163
|
+
...sessionFields,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (lc === 'busy' || lc === 'generating') {
|
|
167
|
+
return { status: 'generating', messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, ...sessionFields };
|
|
168
|
+
}
|
|
169
|
+
return { status: 'idle', messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, ...sessionFields };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private maybeRefreshNativeHistory(): void {
|
|
173
|
+
// Native history is now sourced by daemon's chat-history pipeline
|
|
174
|
+
// (which calls provider.scripts.readNativeHistory wired by
|
|
175
|
+
// provider-loader). SpecCliAdapter no longer polls or caches.
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
getScriptParsedStatus(): unknown {
|
|
179
|
+
const providerSessionId = this.extractProviderSessionIdFromScreen();
|
|
180
|
+
if (providerSessionId) this.providerSessionId = providerSessionId;
|
|
181
|
+
const status = this.getStatus();
|
|
182
|
+
return {
|
|
183
|
+
...status,
|
|
184
|
+
messages: this.readClaudeScreenAssistantMessages(),
|
|
185
|
+
...(this.providerSessionId ? { providerSessionId: this.providerSessionId } : {}),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
getPartialResponse(): string {
|
|
190
|
+
return this.partialResponse;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
shutdown(): void {
|
|
194
|
+
try { this.driver.dispatch({ kind: 'shutdown' }); } catch { /* ignore */ }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
cancel(): void {
|
|
198
|
+
try { this.driver.dispatch({ kind: 'cancel' }); } catch { /* ignore */ }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
isProcessing(): boolean {
|
|
202
|
+
return this.getStatus().status === 'generating';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
isReady(): boolean {
|
|
206
|
+
return this.spawned && !this.exited;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
setOnStatusChange(cb: () => void): void {
|
|
210
|
+
this.statusCallback = cb;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
setOnPtyData(cb: (data: string) => void): void {
|
|
214
|
+
this.ptyDataCallback = cb;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
writeRaw(data: string): void {
|
|
218
|
+
// Raw pty input — typed characters, escape codes, arrow keys —
|
|
219
|
+
// goes straight to the underlying terminal. send_message would
|
|
220
|
+
// append submit_key after every chunk, which is why typing in
|
|
221
|
+
// the dashboard terminal felt like "enter on every keystroke".
|
|
222
|
+
this.driver.dispatch({ kind: 'pty_write', data });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
resize(cols: number, rows: number): void {
|
|
226
|
+
this.driver.dispatch({ kind: 'resize', cols, rows });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
resolveModal(buttonIndex: number): void {
|
|
230
|
+
// CliAdapter buttonIndex is 0-based; spec buttons are 1-based.
|
|
231
|
+
this.driver.dispatch({ kind: 'click_modal_button', index: buttonIndex + 1 });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async resolveAction(data: unknown): Promise<void> {
|
|
235
|
+
const args = (data && typeof data === 'object') ? (data as any) : {};
|
|
236
|
+
const explicitIndex = typeof args.buttonIndex === 'number' ? args.buttonIndex : -1;
|
|
237
|
+
if (explicitIndex >= 0) { this.resolveModal(explicitIndex); return; }
|
|
238
|
+
const action = typeof args.action === 'string' ? args.action : 'approve';
|
|
239
|
+
const buttons = this.latestModal?.buttons ?? [];
|
|
240
|
+
if (buttons.length === 0) return;
|
|
241
|
+
let target = -1;
|
|
242
|
+
if (action === 'reject' || action === 'deny') {
|
|
243
|
+
target = buttons.findIndex(b => /^(no|deny|reject|cancel)\b/i.test(b.label));
|
|
244
|
+
if (target < 0) target = buttons.length - 1;
|
|
245
|
+
} else {
|
|
246
|
+
target = buttons.findIndex(b => /^(yes|allow|approve|accept|continue|proceed|update)\b/i.test(b.label));
|
|
247
|
+
if (target < 0) target = 0;
|
|
248
|
+
}
|
|
249
|
+
this.resolveModal(target);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async setInteractivePromptResponse(response: InteractivePromptResponse): Promise<void> {
|
|
253
|
+
const prompt = this.activeInteractivePrompt;
|
|
254
|
+
if (!prompt || prompt.promptId !== response.promptId) throw new Error('Interactive prompt response does not match active prompt');
|
|
255
|
+
if (this.cliType !== 'claude-cli') return;
|
|
256
|
+
if (this.interactivePromptTransport === 'tui') {
|
|
257
|
+
const steps = buildClaudeInteractiveTuiAnswerSteps(prompt, response);
|
|
258
|
+
for (const step of steps) {
|
|
259
|
+
this.driver.dispatch({ kind: 'pty_write', data: step });
|
|
260
|
+
await new Promise(resolve => setTimeout(resolve, 180));
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
this.driver.dispatch({ kind: 'pty_write', data: `${buildClaudeInteractiveToolResult(response)}\n` });
|
|
264
|
+
}
|
|
265
|
+
this.activeInteractivePrompt = null;
|
|
266
|
+
this.interactivePromptTransport = null;
|
|
267
|
+
this.statusCallback?.();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
isApprovalRecentlyResolved(): boolean { return false; }
|
|
271
|
+
clearHistory(): void { /* no transcript buffer yet */ }
|
|
272
|
+
updateRuntimeSettings(): void { /* no runtime settings in spec model yet */ }
|
|
273
|
+
setServerConn(): void { /* server conn unused by SpecDriver */ }
|
|
274
|
+
/**
|
|
275
|
+
* Map an invokeScript(name, args) call onto a control_bar entry.
|
|
276
|
+
*
|
|
277
|
+
* scriptName is matched against control.id. The control's action.type
|
|
278
|
+
* drives the dispatch:
|
|
279
|
+
*
|
|
280
|
+
* send_keys → click_control (e.g. stop)
|
|
281
|
+
* open_picker → click_control then resolve when extract_choices
|
|
282
|
+
* surface; choice index comes from args.choiceIndex
|
|
283
|
+
* or args.choice (string label match), defaulting to 0
|
|
284
|
+
* attach_image → attach_image dispatch; expects args.blob (data url
|
|
285
|
+
* or base64) and args.mime
|
|
286
|
+
*
|
|
287
|
+
* Callers that pass an unknown control id get a { not_found } response.
|
|
288
|
+
* No control matched, no driver call — keeps the surface honest.
|
|
289
|
+
*/
|
|
290
|
+
invokeScript(scriptName: string, args?: Record<string, unknown>): Promise<unknown> {
|
|
291
|
+
const controls = this.spec.control_bar ?? [];
|
|
292
|
+
const ctl = controls.find(c => c.id === scriptName);
|
|
293
|
+
if (!ctl) {
|
|
294
|
+
return Promise.resolve({ ok: false, error: `unknown control: ${scriptName}` });
|
|
295
|
+
}
|
|
296
|
+
// Args may arrive as either { blob, mime } (direct invocation) or
|
|
297
|
+
// { params: { blob, mime } } (when the dashboard wraps script args
|
|
298
|
+
// in a params bag). Look at both.
|
|
299
|
+
const flat: Record<string, unknown> = { ...(args || {}) };
|
|
300
|
+
if (args && typeof args.params === 'object' && args.params) {
|
|
301
|
+
Object.assign(flat, args.params as Record<string, unknown>);
|
|
302
|
+
}
|
|
303
|
+
const action = ctl.action;
|
|
304
|
+
if (action.type === 'attach_image') {
|
|
305
|
+
const blob = typeof flat.blob === 'string' ? flat.blob : '';
|
|
306
|
+
const mime = typeof flat.mime === 'string' ? flat.mime : 'image/png';
|
|
307
|
+
if (!blob) return Promise.resolve({ ok: false, error: 'attach_image requires args.blob (base64 or data URL)' });
|
|
308
|
+
this.driver.dispatch({ kind: 'attach_image', blob, mime });
|
|
309
|
+
return Promise.resolve({ ok: true, effects: [{ type: 'attached_image', controlId: ctl.id }] });
|
|
310
|
+
}
|
|
311
|
+
// send_keys + open_picker both route through click_control. For
|
|
312
|
+
// open_picker the dashboard sees the picker modal arrive via a
|
|
313
|
+
// state_changed event, then submits the choice using
|
|
314
|
+
// resolveModal/click_modal_button. invokeScript's synchronous
|
|
315
|
+
// return is just an acknowledgement that the trigger fired.
|
|
316
|
+
this.driver.dispatch({ kind: 'click_control', control_id: ctl.id, payload: flat });
|
|
317
|
+
const effects: { type: string; controlId: string }[] = [];
|
|
318
|
+
if (action.type === 'open_picker') effects.push({ type: 'opened_picker', controlId: ctl.id });
|
|
319
|
+
else if (action.type === 'send_keys') effects.push({ type: 'sent_keys', controlId: ctl.id });
|
|
320
|
+
return Promise.resolve({ ok: true, effects });
|
|
321
|
+
}
|
|
322
|
+
getDebugSnapshot(): unknown {
|
|
323
|
+
let screen = '';
|
|
324
|
+
let sections: Record<string, string> | undefined;
|
|
325
|
+
try {
|
|
326
|
+
screen = this.driver.snapshot();
|
|
327
|
+
const driverSections = this.driver.getSections?.();
|
|
328
|
+
if (driverSections) {
|
|
329
|
+
sections = Object.fromEntries(driverSections.map(s => [s.id, s.text]));
|
|
330
|
+
} else {
|
|
331
|
+
sections = this.readCurrentScreenSections(screen);
|
|
332
|
+
}
|
|
333
|
+
} catch { /* best-effort */ }
|
|
334
|
+
// Read native transcript messages for the debug snapshot
|
|
335
|
+
let messages: any[] = [];
|
|
336
|
+
if (this.spec.native_history?.source) {
|
|
337
|
+
try {
|
|
338
|
+
const nhResult = executeNativeHistory(this.spec.native_history, {
|
|
339
|
+
agentType: this.cliType,
|
|
340
|
+
providerSessionId: this.providerSessionId,
|
|
341
|
+
sessionStartedAtMs: this.spawnedAtMs,
|
|
342
|
+
envOverrides: this.spawnedEnv,
|
|
343
|
+
workspace: this.workingDir,
|
|
344
|
+
});
|
|
345
|
+
if (nhResult && Array.isArray(nhResult.messages)) messages = nhResult.messages;
|
|
346
|
+
} catch { /* best-effort */ }
|
|
347
|
+
} else {
|
|
348
|
+
messages = this.readClaudeScreenAssistantMessages();
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
cliType: this.cliType,
|
|
352
|
+
spec_id: this.spec.id,
|
|
353
|
+
current_state: this.latestState,
|
|
354
|
+
current_modal: this.latestModal,
|
|
355
|
+
activeInteractivePrompt: this.activeInteractivePrompt,
|
|
356
|
+
exited: this.exited,
|
|
357
|
+
screen,
|
|
358
|
+
sections,
|
|
359
|
+
stateHistory: this.driver.getStateHistory(),
|
|
360
|
+
idleHoldPending: this.driver.hasIdleHoldPending(),
|
|
361
|
+
lastBusyAt: this.driver.getLastBusyAt(),
|
|
362
|
+
specPath: this.driver.getSpecPath(),
|
|
363
|
+
cursorPosition: this.driver.getCursorPosition(),
|
|
364
|
+
completionIdleDebounce: this.driver.getCompletionIdleDebounceState(),
|
|
365
|
+
// Extended fields
|
|
366
|
+
name: this.cliName,
|
|
367
|
+
status: this.getStatus().status,
|
|
368
|
+
workingDir: this.workingDir,
|
|
369
|
+
spawnedAtMs: this.spawnedAtMs,
|
|
370
|
+
providerSessionId: this.providerSessionId ?? null,
|
|
371
|
+
messages,
|
|
372
|
+
committedMessages: messages,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
getRuntimeMetadata(): unknown {
|
|
376
|
+
return {
|
|
377
|
+
runtimeId: this.spec.id,
|
|
378
|
+
runtimeKey: this.spec.id,
|
|
379
|
+
displayName: this.spec.name,
|
|
380
|
+
spawnedAtMs: this.spawnedAtMs,
|
|
381
|
+
spawnedEnv: this.spawnedEnv,
|
|
382
|
+
...(this.providerSessionId ? { providerSessionId: this.providerSessionId } : {}),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
updateRuntimeMeta(meta?: Record<string, unknown>): void {
|
|
386
|
+
if (meta && typeof meta.providerSessionId === 'string') {
|
|
387
|
+
this.providerSessionId = meta.providerSessionId;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
refreshProviderDefinition(): void { /* hot reload handled by SpecDriver fs.watch */ }
|
|
391
|
+
|
|
392
|
+
private handleEvent(ev: DashboardEvent): void {
|
|
393
|
+
this.lastEvent = ev;
|
|
394
|
+
switch (ev.kind) {
|
|
395
|
+
case 'state_changed':
|
|
396
|
+
this.latestState = ev.state;
|
|
397
|
+
this.latestModal = ev.modal;
|
|
398
|
+
// info-level keeps only spec-defined identifiers (state.id /
|
|
399
|
+
// state.label / button count). The extracted title can carry
|
|
400
|
+
// user data — file paths, command text, ticket titles — so
|
|
401
|
+
// it stays at debug.
|
|
402
|
+
LOG.info('SpecAdapter', `[${this.cliType}] state=${ev.state.id} (${ev.state.label}) modal=${ev.modal ? `${ev.modal.buttons.length}-buttons` : 'none'}`);
|
|
403
|
+
if (ev.state.title) {
|
|
404
|
+
LOG.debug('SpecAdapter', `[${this.cliType}] state.title=${JSON.stringify(ev.state.title)}`);
|
|
405
|
+
}
|
|
406
|
+
this.maybeCaptureClaudeTuiPrompt();
|
|
407
|
+
this.statusCallback?.();
|
|
408
|
+
return;
|
|
409
|
+
case 'pty_data':
|
|
410
|
+
this.detectInteractivePromptFromPtyChunk(ev.chunk);
|
|
411
|
+
this.maybeCaptureClaudeTuiPrompt();
|
|
412
|
+
try { this.ptyDataCallback?.(ev.chunk); } catch { /* ignore */ }
|
|
413
|
+
return;
|
|
414
|
+
case 'exit':
|
|
415
|
+
this.exited = true;
|
|
416
|
+
this.statusCallback?.();
|
|
417
|
+
return;
|
|
418
|
+
case 'spec_error':
|
|
419
|
+
LOG.warn('SpecAdapter', `[${this.cliType}] spec reload error: ${ev.errors.join('; ')}`);
|
|
420
|
+
return;
|
|
421
|
+
default:
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
private detectInteractivePromptFromPtyChunk(chunk: string): void {
|
|
427
|
+
if (this.cliType !== 'claude-cli' || !chunk) return;
|
|
428
|
+
this.jsonLineTail += chunk;
|
|
429
|
+
if (this.jsonLineTail.length > 64 * 1024) this.jsonLineTail = this.jsonLineTail.slice(-64 * 1024);
|
|
430
|
+
const lines = this.jsonLineTail.split(/\r?\n/);
|
|
431
|
+
this.jsonLineTail = lines.pop() || '';
|
|
432
|
+
for (const line of lines) {
|
|
433
|
+
const trimmed = line.trim();
|
|
434
|
+
if (!trimmed.startsWith('{') || !trimmed.includes('AskUserQuestion')) continue;
|
|
435
|
+
try {
|
|
436
|
+
const parsed = JSON.parse(trimmed);
|
|
437
|
+
const prompt = detectClaudeAskUserQuestionPromptFromJson(parsed, this.cliType);
|
|
438
|
+
if (!prompt) continue;
|
|
439
|
+
this.activeInteractivePrompt = prompt;
|
|
440
|
+
this.interactivePromptTransport = 'stream-json';
|
|
441
|
+
this.statusCallback?.();
|
|
442
|
+
} catch {
|
|
443
|
+
// PTY output is not guaranteed to be machine JSON.
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private readCurrentScreenSections(screenText: string): Record<string, string> {
|
|
449
|
+
try {
|
|
450
|
+
const ev = evaluate(this.spec, screenText);
|
|
451
|
+
return Object.fromEntries(ev.sections.map(section => [section.id, section.text]));
|
|
452
|
+
} catch {
|
|
453
|
+
return {};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private extractProviderSessionIdFromScreen(): string | undefined {
|
|
458
|
+
if (this.cliType !== 'codex-cli') return this.providerSessionId;
|
|
459
|
+
let screenText = '';
|
|
460
|
+
try {
|
|
461
|
+
screenText = this.driver.snapshot();
|
|
462
|
+
} catch {
|
|
463
|
+
return this.providerSessionId;
|
|
464
|
+
}
|
|
465
|
+
const clean = stripAnsi(screenText);
|
|
466
|
+
const match = clean.match(/(?:gpt-|o\d|codex-)[^\n·]*·[^\n·]*·\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
|
|
467
|
+
return match?.[1] || this.providerSessionId;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
private readClaudeScreenAssistantMessages(): ChatMessage[] {
|
|
471
|
+
if (this.cliType !== 'claude-cli') return [];
|
|
472
|
+
let screenText = '';
|
|
473
|
+
try {
|
|
474
|
+
screenText = this.driver.snapshot();
|
|
475
|
+
} catch {
|
|
476
|
+
return [];
|
|
477
|
+
}
|
|
478
|
+
const sections = this.readCurrentScreenSections(screenText);
|
|
479
|
+
const body = sections.body || screenText;
|
|
480
|
+
const messages: ChatMessage[] = [];
|
|
481
|
+
const seen = new Set<string>();
|
|
482
|
+
for (const line of body.split(/\r?\n/)) {
|
|
483
|
+
const match = line.match(/^\s*⏺\s+(.+?)\s*$/);
|
|
484
|
+
const content = match?.[1]?.trim();
|
|
485
|
+
if (!content || seen.has(content)) continue;
|
|
486
|
+
seen.add(content);
|
|
487
|
+
messages.push({
|
|
488
|
+
role: 'assistant',
|
|
489
|
+
kind: 'standard',
|
|
490
|
+
content,
|
|
491
|
+
source: 'assistant_text',
|
|
492
|
+
userFacing: true,
|
|
493
|
+
bubbleState: 'final',
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
return messages;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
private maybeCaptureClaudeTuiPrompt(): void {
|
|
500
|
+
if (this.cliType !== 'claude-cli'
|
|
501
|
+
|| this.activeInteractivePrompt
|
|
502
|
+
|| this.claudeTuiPromptCaptureInFlight) return;
|
|
503
|
+
const screenText = this.driver.snapshot();
|
|
504
|
+
const headers = this.readClaudeTuiHeaders(screenText);
|
|
505
|
+
if (!screenText.includes('Enter to select')) return;
|
|
506
|
+
if (headers.length === 0) {
|
|
507
|
+
const prompt = detectClaudeAskUserQuestionPromptFromTuiPages([{ screenText }], {
|
|
508
|
+
promptId: `ask-user-${this.providerSessionId || 'claude'}-${Date.now()}`,
|
|
509
|
+
providerType: this.cliType,
|
|
510
|
+
});
|
|
511
|
+
if (!prompt) return;
|
|
512
|
+
this.activeInteractivePrompt = prompt;
|
|
513
|
+
this.interactivePromptTransport = 'tui';
|
|
514
|
+
this.statusCallback?.();
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
this.claudeTuiPromptCaptureInFlight = true;
|
|
518
|
+
void this.captureClaudeTuiPrompt(screenText, headers).finally(() => {
|
|
519
|
+
this.claudeTuiPromptCaptureInFlight = false;
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
private readClaudeTuiHeaders(screenText: string): string[] {
|
|
524
|
+
const navLine = screenText.split(/\r?\n/).find(line => line.includes('✔ Submit') && /[☐☒]/.test(line));
|
|
525
|
+
if (!navLine) return [];
|
|
526
|
+
const headers: string[] = [];
|
|
527
|
+
for (const match of navLine.matchAll(/[☐☒]\s+(.+?)(?=\s+[☐☒]|\s+✔\s+Submit)/g)) {
|
|
528
|
+
const header = match[1]?.trim();
|
|
529
|
+
if (header) headers.push(header);
|
|
530
|
+
}
|
|
531
|
+
return headers;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
private async captureClaudeTuiPrompt(firstScreen: string, headers: string[]): Promise<void> {
|
|
535
|
+
const pages: ClaudeInteractiveTuiPage[] = [{ screenText: firstScreen, header: headers[0] }];
|
|
536
|
+
for (let index = 1; index < headers.length; index += 1) {
|
|
537
|
+
this.driver.dispatch({ kind: 'pty_write', data: '\t' });
|
|
538
|
+
await new Promise(resolve => setTimeout(resolve, 120));
|
|
539
|
+
pages.push({ screenText: this.driver.snapshot(), header: headers[index] });
|
|
540
|
+
}
|
|
541
|
+
for (let index = headers.length - 1; index > 0; index -= 1) {
|
|
542
|
+
this.driver.dispatch({ kind: 'pty_write', data: '\x1b[Z' });
|
|
543
|
+
await new Promise(resolve => setTimeout(resolve, 80));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const prompt = detectClaudeAskUserQuestionPromptFromTuiPages(pages, {
|
|
547
|
+
promptId: `ask-user-${this.providerSessionId || 'claude'}-${Date.now()}`,
|
|
548
|
+
providerType: this.cliType,
|
|
549
|
+
});
|
|
550
|
+
if (!prompt) return;
|
|
551
|
+
this.activeInteractivePrompt = prompt;
|
|
552
|
+
this.interactivePromptTransport = 'tui';
|
|
553
|
+
this.statusCallback?.();
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
getDebugState(): Record<string, any> {
|
|
557
|
+
const screen = this.driver.getScreen?.() ?? '';
|
|
558
|
+
const history = this.driver.getStateHistory();
|
|
559
|
+
const status = this.getStatus();
|
|
560
|
+
|
|
561
|
+
let messages: any[] = [];
|
|
562
|
+
if (this.spec.native_history?.source) {
|
|
563
|
+
try {
|
|
564
|
+
const result = executeNativeHistory(this.spec.native_history, {
|
|
565
|
+
agentType: this.cliType,
|
|
566
|
+
providerSessionId: this.providerSessionId,
|
|
567
|
+
sessionStartedAtMs: this.spawnedAtMs,
|
|
568
|
+
envOverrides: this.spawnedEnv,
|
|
569
|
+
workspace: this.workingDir,
|
|
570
|
+
});
|
|
571
|
+
if (result && Array.isArray(result.messages)) {
|
|
572
|
+
messages = result.messages;
|
|
573
|
+
}
|
|
574
|
+
} catch (e) {
|
|
575
|
+
// Ignore native history read errors in debug state
|
|
576
|
+
}
|
|
577
|
+
} else {
|
|
578
|
+
messages = this.readClaudeScreenAssistantMessages();
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const latestState = this.latestState;
|
|
582
|
+
const latestModal = this.latestModal;
|
|
583
|
+
return {
|
|
584
|
+
type: this.cliType,
|
|
585
|
+
name: this.cliName,
|
|
586
|
+
status: status.status,
|
|
587
|
+
rawStatus: status.status,
|
|
588
|
+
projectedStatus: status.status,
|
|
589
|
+
ready: this.spawned,
|
|
590
|
+
// Legacy snapshot-style fields for panels that read getDebugSnapshot shape
|
|
591
|
+
spec_id: this.spec.id,
|
|
592
|
+
current_state: latestState ?? null,
|
|
593
|
+
current_modal: latestModal ?? null,
|
|
594
|
+
exited: this.exited,
|
|
595
|
+
idleHoldPending: this.driver.hasIdleHoldPending?.() ?? false,
|
|
596
|
+
lastBusyAt: this.driver.getLastBusyAt?.() ?? 0,
|
|
597
|
+
screen: screen,
|
|
598
|
+
screenText: screen,
|
|
599
|
+
workingDir: this.workingDir,
|
|
600
|
+
spawnedAtMs: this.spawnedAtMs,
|
|
601
|
+
providerSessionId: this.providerSessionId ?? null,
|
|
602
|
+
sections: this.driver.getSections?.() ?? null,
|
|
603
|
+
stateHistory: history,
|
|
604
|
+
specPath: (this.driver as any).opts?.specPath ?? null,
|
|
605
|
+
messages,
|
|
606
|
+
committedMessages: messages,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
getTraceState(limit = 120): Record<string, any> {
|
|
611
|
+
const history = this.driver.getStateHistory();
|
|
612
|
+
return {
|
|
613
|
+
status: this.getStatus().status,
|
|
614
|
+
stateHistory: history.slice(-limit),
|
|
615
|
+
screenText: this.driver.getScreen?.() ?? '',
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
getProviderResolutionMeta(): Record<string, any> {
|
|
620
|
+
return {
|
|
621
|
+
type: this.cliType,
|
|
622
|
+
providerDir: null,
|
|
623
|
+
resolvedVersion: null,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
}
|