@adhdev/daemon-core 0.6.53 → 0.6.56
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/index.d.ts +84 -34
- package/dist/index.js +631 -305
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/providers/_builtin/_helpers/index.js +26 -26
- package/providers/_builtin/cli/aider-cli/provider.json +37 -0
- package/providers/_builtin/cli/aider-cli/scripts/1.0/detect_status.js +20 -0
- package/providers/_builtin/cli/aider-cli/scripts/1.0/parse_approval.js +10 -0
- package/providers/_builtin/cli/aider-cli/scripts/1.0/parse_output.js +49 -0
- package/providers/_builtin/cli/aider-cli/scripts/1.0/scripts.js +7 -0
- package/providers/_builtin/cli/claude-cli/provider.json +9 -2
- package/providers/_builtin/cli/claude-cli/scripts/1.0/detect_status.js +31 -0
- package/providers/_builtin/cli/claude-cli/scripts/1.0/parse_approval.js +38 -0
- package/providers/_builtin/cli/claude-cli/scripts/1.0/parse_output.js +164 -0
- package/providers/_builtin/cli/claude-cli/scripts/1.0/scripts.js +42 -0
- package/providers/_builtin/cli/codex-cli/provider.json +9 -2
- package/providers/_builtin/cli/codex-cli/scripts/1.0/detect_status.js +20 -0
- package/providers/_builtin/cli/codex-cli/scripts/1.0/parse_approval.js +15 -0
- package/providers/_builtin/cli/codex-cli/scripts/1.0/parse_output.js +68 -0
- package/providers/_builtin/cli/codex-cli/scripts/1.0/scripts.js +11 -0
- package/providers/_builtin/cli/cursor-cli/provider.json +37 -0
- package/providers/_builtin/cli/cursor-cli/scripts/1.0/detect_status.js +19 -0
- package/providers/_builtin/cli/cursor-cli/scripts/1.0/parse_approval.js +10 -0
- package/providers/_builtin/cli/cursor-cli/scripts/1.0/parse_output.js +48 -0
- package/providers/_builtin/cli/cursor-cli/scripts/1.0/scripts.js +7 -0
- package/providers/_builtin/cli/gemini-cli/provider.json +9 -2
- package/providers/_builtin/cli/gemini-cli/scripts/1.0/detect_status.js +26 -0
- package/providers/_builtin/cli/gemini-cli/scripts/1.0/parse_approval.js +22 -0
- package/providers/_builtin/cli/gemini-cli/scripts/1.0/parse_output.js +71 -0
- package/providers/_builtin/cli/gemini-cli/scripts/1.0/scripts.js +12 -0
- package/providers/_builtin/cli/github-copilot-cli/provider.json +37 -0
- package/providers/_builtin/cli/github-copilot-cli/scripts/1.0/detect_status.js +19 -0
- package/providers/_builtin/cli/github-copilot-cli/scripts/1.0/parse_approval.js +9 -0
- package/providers/_builtin/cli/github-copilot-cli/scripts/1.0/parse_output.js +48 -0
- package/providers/_builtin/cli/github-copilot-cli/scripts/1.0/scripts.js +7 -0
- package/providers/_builtin/cli/goose-cli/provider.json +37 -0
- package/providers/_builtin/cli/goose-cli/scripts/1.0/detect_status.js +20 -0
- package/providers/_builtin/cli/goose-cli/scripts/1.0/parse_approval.js +10 -0
- package/providers/_builtin/cli/goose-cli/scripts/1.0/parse_output.js +48 -0
- package/providers/_builtin/cli/goose-cli/scripts/1.0/scripts.js +7 -0
- package/providers/_builtin/cli/opencode-cli/provider.json +37 -0
- package/providers/_builtin/cli/opencode-cli/scripts/1.0/detect_status.js +15 -0
- package/providers/_builtin/cli/opencode-cli/scripts/1.0/parse_approval.js +9 -0
- package/providers/_builtin/cli/opencode-cli/scripts/1.0/parse_output.js +48 -0
- package/providers/_builtin/cli/opencode-cli/scripts/1.0/scripts.js +7 -0
- package/providers/_builtin/extension/cline/scripts/1.0/focus_editor.js +5 -5
- package/providers/_builtin/extension/cline/scripts/1.0/list_chats.js +11 -11
- package/providers/_builtin/extension/cline/scripts/1.0/list_models.js +6 -6
- package/providers/_builtin/extension/cline/scripts/1.0/list_modes.js +5 -5
- package/providers/_builtin/extension/cline/scripts/1.0/new_session.js +12 -12
- package/providers/_builtin/extension/cline/scripts/1.0/open_panel.js +7 -7
- package/providers/_builtin/extension/cline/scripts/1.0/read_chat.js +29 -29
- package/providers/_builtin/extension/cline/scripts/1.0/resolve_action.js +19 -19
- package/providers/_builtin/extension/cline/scripts/1.0/send_message.js +15 -15
- package/providers/_builtin/extension/cline/scripts/1.0/set_mode.js +4 -4
- package/providers/_builtin/extension/cline/scripts/1.0/set_model.js +4 -4
- package/providers/_builtin/extension/cline/scripts/1.0/switch_session.js +21 -21
- package/providers/_builtin/extension/codex/scripts/1.0/explore_controls_webview.js +2 -2
- package/providers/_builtin/extension/codex/scripts/1.0/list_models.js +1 -1
- package/providers/_builtin/extension/codex/scripts/1.0/list_modes.js +1 -1
- package/providers/_builtin/extension/codex/scripts/1.0/new_session.js +2 -2
- package/providers/_builtin/extension/codex/scripts/1.0/read_chat.js +6 -6
- package/providers/_builtin/extension/codex/scripts/1.0/resolve_action.js +3 -3
- package/providers/_builtin/extension/codex/scripts/1.0/set_model.js +1 -1
- package/providers/_builtin/ide/antigravity/scripts/1.0/focus_editor.js +2 -2
- package/providers/_builtin/ide/antigravity/scripts/1.0/list_chats.js +19 -19
- package/providers/_builtin/ide/antigravity/scripts/1.0/list_models.js +1 -1
- package/providers/_builtin/ide/antigravity/scripts/1.0/list_modes.js +1 -1
- package/providers/_builtin/ide/antigravity/scripts/1.0/new_session.js +17 -17
- package/providers/_builtin/ide/antigravity/scripts/1.0/read_chat.js +41 -41
- package/providers/_builtin/ide/antigravity/scripts/1.0/resolve_action.js +6 -6
- package/providers/_builtin/ide/antigravity/scripts/1.0/send_message.js +13 -13
- package/providers/_builtin/ide/antigravity/scripts/1.0/set_model.js +1 -1
- package/providers/_builtin/ide/antigravity/scripts/1.0/switch_session.js +13 -13
- package/providers/_builtin/ide/cursor/scripts/1.0/dismiss_notification.js +3 -3
- package/providers/_builtin/ide/cursor/scripts/1.0/focus_editor.js +1 -1
- package/providers/_builtin/ide/cursor/scripts/1.0/list_models.js +12 -12
- package/providers/_builtin/ide/cursor/scripts/1.0/list_modes.js +5 -5
- package/providers/_builtin/ide/cursor/scripts/1.0/list_notifications.js +2 -2
- package/providers/_builtin/ide/cursor/scripts/1.0/list_sessions.js +5 -5
- package/providers/_builtin/ide/cursor/scripts/1.0/new_session.js +1 -1
- package/providers/_builtin/ide/cursor/scripts/1.0/open_panel.js +3 -3
- package/providers/_builtin/ide/cursor/scripts/1.0/read_chat.js +9 -9
- package/providers/_builtin/ide/cursor/scripts/1.0/resolve_action.js +4 -4
- package/providers/_builtin/ide/cursor/scripts/1.0/send_message.js +5 -5
- package/providers/_builtin/ide/cursor/scripts/1.0/set_mode.js +4 -4
- package/providers/_builtin/ide/cursor/scripts/1.0/set_model.js +12 -12
- package/providers/_builtin/ide/cursor/scripts/1.0/switch_session.js +2 -2
- package/providers/_builtin/ide/kiro/scripts/1.0/focus_editor.js +2 -2
- package/providers/_builtin/ide/kiro/scripts/1.0/open_panel.js +7 -7
- package/providers/_builtin/ide/kiro/scripts/1.0/resolve_action.js +3 -3
- package/providers/_builtin/ide/kiro/scripts/1.0/send_message.js +4 -4
- package/providers/_builtin/ide/kiro/scripts/1.0/webview_list_sessions.js +2 -2
- package/providers/_builtin/ide/kiro/scripts/1.0/webview_new_session.js +5 -5
- package/providers/_builtin/ide/kiro/scripts/1.0/webview_read_chat.js +13 -13
- package/providers/_builtin/ide/kiro/scripts/1.0/webview_resolve_action.js +1 -1
- package/providers/_builtin/ide/kiro/scripts/1.0/webview_send_message.js +8 -8
- package/providers/_builtin/ide/kiro/scripts/1.0/webview_set_mode.js +1 -1
- package/providers/_builtin/ide/kiro/scripts/1.0/webview_set_model.js +1 -1
- package/providers/_builtin/ide/kiro/scripts/1.0/webview_switch_session.js +3 -3
- package/providers/_builtin/ide/pearai/.tmp/after_rich.json +157 -0
- package/providers/_builtin/ide/pearai/.tmp/after_rich_raw.json +161 -0
- package/providers/_builtin/ide/pearai/.tmp/after_switch.json +592 -0
- package/providers/_builtin/ide/pearai/.tmp/after_switch_raw.json +596 -0
- package/providers/_builtin/ide/pearai/.tmp/home_dump_raw.json +176 -0
- package/providers/_builtin/ide/pearai/.tmp/items_raw.json +778 -0
- package/providers/_builtin/ide/pearai/.tmp/webview_dump.json +157 -0
- package/providers/_builtin/ide/pearai/.tmp/webview_dump_raw.json +161 -0
- package/providers/_builtin/ide/pearai/provider.json +1 -3
- package/providers/_builtin/ide/pearai/scripts/1.0/focus_editor.js +12 -16
- package/providers/_builtin/ide/pearai/scripts/1.0/list_models.js +45 -0
- package/providers/_builtin/ide/pearai/scripts/1.0/list_modes.js +44 -0
- package/providers/_builtin/ide/pearai/scripts/1.0/list_sessions.js +82 -27
- package/providers/_builtin/ide/pearai/scripts/1.0/new_session.js +26 -43
- package/providers/_builtin/ide/pearai/scripts/1.0/open_panel.js +10 -33
- package/providers/_builtin/ide/pearai/scripts/1.0/read_chat.js +169 -0
- package/providers/_builtin/ide/pearai/scripts/1.0/resolve_action.js +47 -41
- package/providers/_builtin/ide/pearai/scripts/1.0/scripts.js +36 -7
- package/providers/_builtin/ide/pearai/scripts/1.0/send_message.js +97 -22
- package/providers/_builtin/ide/pearai/scripts/1.0/set_mode.js +51 -0
- package/providers/_builtin/ide/pearai/scripts/1.0/set_model.js +54 -0
- package/providers/_builtin/ide/pearai/scripts/1.0/switch_session.js +81 -0
- package/providers/_builtin/ide/trae/scripts/1.0/focus_editor.js +2 -2
- package/providers/_builtin/ide/trae/scripts/1.0/list_chats.js +2 -2
- package/providers/_builtin/ide/trae/scripts/1.0/new_session.js +4 -4
- package/providers/_builtin/ide/trae/scripts/1.0/open_panel.js +6 -6
- package/providers/_builtin/ide/trae/scripts/1.0/read_chat.js +13 -13
- package/providers/_builtin/ide/trae/scripts/1.0/resolve_action.js +3 -3
- package/providers/_builtin/ide/trae/scripts/1.0/send_message.js +11 -11
- package/providers/_builtin/ide/trae/scripts/1.0/switch_session.js +2 -2
- package/providers/_builtin/ide/windsurf/scripts/1.0/focus_editor.js +6 -6
- package/providers/_builtin/ide/windsurf/scripts/1.0/list_chats.js +4 -4
- package/providers/_builtin/ide/windsurf/scripts/1.0/list_sessions.js +6 -6
- package/providers/_builtin/ide/windsurf/scripts/1.0/new_session.js +9 -9
- package/providers/_builtin/ide/windsurf/scripts/1.0/open_panel.js +6 -6
- package/providers/_builtin/ide/windsurf/scripts/1.0/resolve_action.js +8 -8
- package/providers/_builtin/ide/windsurf/scripts/1.0/switch_session.js +8 -8
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/focus_editor.js +6 -6
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/list_chats.js +12 -12
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/new_session.js +15 -15
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/open_panel.js +10 -10
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/read_chat.js +33 -33
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/resolve_action.js +6 -6
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/send_message.js +16 -16
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/switch_session.js +7 -7
- package/providers/_builtin/registry.json +76 -3
- package/src/cli-adapters/provider-cli-adapter.ts +312 -258
- package/src/cli-adapters/terminal-screen.ts +95 -0
- package/src/commands/chat-commands.ts +41 -15
- package/src/commands/cli-manager.ts +4 -2
- package/src/daemon/dev-server.ts +306 -62
- package/src/providers/acp-provider-instance.ts +1 -1
- package/src/providers/cli-provider-instance.ts +23 -9
- package/src/providers/provider-loader.ts +3 -2
- package/providers/_builtin/ide/pearai/scripts/1.0/webview_list_models.js +0 -43
- package/providers/_builtin/ide/pearai/scripts/1.0/webview_list_modes.js +0 -35
- package/providers/_builtin/ide/pearai/scripts/1.0/webview_list_sessions.js +0 -62
- package/providers/_builtin/ide/pearai/scripts/1.0/webview_new_session.js +0 -49
- package/providers/_builtin/ide/pearai/scripts/1.0/webview_read_chat.js +0 -92
- package/providers/_builtin/ide/pearai/scripts/1.0/webview_resolve_action.js +0 -59
- package/providers/_builtin/ide/pearai/scripts/1.0/webview_send_message.js +0 -72
- package/providers/_builtin/ide/pearai/scripts/1.0/webview_set_mode.js +0 -36
- package/providers/_builtin/ide/pearai/scripts/1.0/webview_set_model.js +0 -36
- package/providers/_builtin/ide/pearai/scripts/1.0/webview_switch_session.js +0 -34
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ProviderCliAdapter —
|
|
2
|
+
* ProviderCliAdapter — Script-based CLI Adapter
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* All CLI providers use versioned scripts (like IDE providers).
|
|
5
|
+
* Scripts are Node.js functions that receive PTY buffer data and return structured results.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* cleanOutput(raw, lastUserInput): string
|
|
7
|
+
* Required scripts in scripts/{version}/scripts.js:
|
|
8
|
+
* - detectStatus(input) → AgentStatus string ('idle' | 'generating' | 'waiting_approval')
|
|
9
|
+
* - parseOutput(input) → ReadChatResult { messages, status, activeModal, ... }
|
|
10
|
+
* - parseApproval(input) → ModalInfo | null
|
|
11
|
+
*
|
|
12
|
+
* provider.json contract:
|
|
13
|
+
* type, name, category: 'cli', binary, spawn, approvalKeys
|
|
14
|
+
* compatibility: [{ ideVersion, scriptDir }] ← versioned scripts
|
|
16
15
|
*/
|
|
17
16
|
|
|
18
17
|
import * as os from 'os';
|
|
@@ -20,6 +19,7 @@ import * as path from 'path';
|
|
|
20
19
|
import { execSync } from 'child_process';
|
|
21
20
|
import type { CliAdapter } from '../cli-adapter-types.js';
|
|
22
21
|
import { LOG } from '../logging/logger.js';
|
|
22
|
+
import { TerminalScreen } from './terminal-screen.js';
|
|
23
23
|
|
|
24
24
|
let pty: any;
|
|
25
25
|
try {
|
|
@@ -44,6 +44,8 @@ try {
|
|
|
44
44
|
LOG.error('CLI', '[ProviderCliAdapter] node-pty not found. Terminal features disabled.');
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// ─── Types ──────────────────────────────────────────
|
|
48
|
+
|
|
47
49
|
export interface CliChatMessage {
|
|
48
50
|
role: 'user' | 'assistant';
|
|
49
51
|
content: string;
|
|
@@ -57,23 +59,46 @@ export interface CliSessionStatus {
|
|
|
57
59
|
activeModal: { message: string; buttons: string[] } | null;
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
/**
|
|
63
|
+
* CLI Script Functions.
|
|
64
|
+
* Unlike IDE scripts (which return JS code strings for CDP evaluate),
|
|
65
|
+
* CLI scripts are Node.js functions that receive PTY buffer data and return structured results.
|
|
66
|
+
*/
|
|
67
|
+
export interface CliScripts {
|
|
68
|
+
/** Full PTY buffer → ReadChatResult (messages, status, activeModal) */
|
|
69
|
+
parseOutput?: (input: CliScriptInput) => any;
|
|
70
|
+
/** Lightweight status detection (high-frequency polling) → AgentStatus string */
|
|
71
|
+
detectStatus?: (input: { tail: string; screenText?: string; rawBuffer?: string }) => string | null;
|
|
72
|
+
/** Parse approval modal from PTY output → ModalInfo | null */
|
|
73
|
+
parseApproval?: (input: { buffer: string; rawBuffer?: string; tail: string }) => { message: string; buttons: string[] } | null;
|
|
74
|
+
/** Produce a cli-specific prompt from a dashboard action payload */
|
|
75
|
+
resolveAction?: (data: any) => string;
|
|
76
|
+
/** Custom scripts */
|
|
77
|
+
[name: string]: ((input: any) => any) | undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface CliScriptInput {
|
|
81
|
+
buffer: string; // Full ANSI-stripped accumulated PTY output
|
|
82
|
+
rawBuffer: string; // Raw PTY output (with ANSI)
|
|
83
|
+
recentBuffer: string; // Recent 1000 chars (ANSI-stripped)
|
|
84
|
+
screenText: string; // Current visible screen snapshot
|
|
85
|
+
messages: CliChatMessage[]; // Previously parsed messages
|
|
86
|
+
partialResponse: string; // Current partial response being generated
|
|
87
|
+
}
|
|
88
|
+
|
|
60
89
|
export interface CliProviderModule {
|
|
61
90
|
type: string;
|
|
62
91
|
name: string;
|
|
63
92
|
category: 'cli';
|
|
64
93
|
binary: string;
|
|
94
|
+
sendDelayMs?: number;
|
|
95
|
+
sendKey?: string;
|
|
65
96
|
spawn: {
|
|
66
97
|
command: string;
|
|
67
98
|
args: string[];
|
|
68
99
|
shell: boolean;
|
|
69
100
|
env: Record<string, string>;
|
|
70
101
|
};
|
|
71
|
-
patterns: {
|
|
72
|
-
prompt: RegExp[];
|
|
73
|
-
generating: RegExp[];
|
|
74
|
-
approval: RegExp[];
|
|
75
|
-
ready: RegExp[];
|
|
76
|
-
};
|
|
77
102
|
timeouts?: {
|
|
78
103
|
/** PTY output batch transmit interval (default 50ms) */
|
|
79
104
|
ptyFlush?: number;
|
|
@@ -89,12 +114,13 @@ export interface CliProviderModule {
|
|
|
89
114
|
maxResponse?: number;
|
|
90
115
|
/** shutdown after kill wait (default 1000ms) */
|
|
91
116
|
shutdownGrace?: number;
|
|
92
|
-
/** Output settle debounce before evaluating
|
|
117
|
+
/** Output settle debounce before evaluating status (default 300ms) */
|
|
93
118
|
outputSettle?: number;
|
|
94
119
|
};
|
|
95
|
-
cleanOutput(raw: string, lastUserInput?: string): string;
|
|
96
120
|
}
|
|
97
121
|
|
|
122
|
+
// ─── Utility Functions ──────────────────────────────
|
|
123
|
+
|
|
98
124
|
function stripAnsi(str: string): string {
|
|
99
125
|
// eslint-disable-next-line no-control-regex
|
|
100
126
|
return str
|
|
@@ -168,9 +194,8 @@ function shSingleQuote(arg: string): string {
|
|
|
168
194
|
}
|
|
169
195
|
|
|
170
196
|
/**
|
|
171
|
-
* provider.json
|
|
172
|
-
*
|
|
173
|
-
* (including `{ "source": "...", "flags": "i" }`) or fall back to generic TUIs (Claude Code, etc.).
|
|
197
|
+
* Normalize provider.json for auto-implement approval detection.
|
|
198
|
+
* Kept for backward compat with dev-server auto-impl pipeline only.
|
|
174
199
|
*/
|
|
175
200
|
function parsePatternEntry(x: unknown): RegExp | null {
|
|
176
201
|
if (x instanceof RegExp) return x;
|
|
@@ -185,55 +210,26 @@ function parsePatternEntry(x: unknown): RegExp | null {
|
|
|
185
210
|
return null;
|
|
186
211
|
}
|
|
187
212
|
|
|
188
|
-
function coercePatternArray(raw: unknown
|
|
189
|
-
if (!Array.isArray(raw)) return [
|
|
190
|
-
|
|
191
|
-
return parsed.length > 0 ? parsed : [...fallbacks];
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/** Defaults tuned for Claude Code / similar agent CLIs when provider.json patterns are empty. */
|
|
195
|
-
const FALLBACK_PROMPT: RegExp[] = [
|
|
196
|
-
/Type your message/i,
|
|
197
|
-
/for\s*shortcuts/i, // Claude Code prompt
|
|
198
|
-
/\?\s*for\s*help/i, // Claude Code help prompt
|
|
199
|
-
/Press enter/i,
|
|
200
|
-
/^[>›❯]\s*$/i, // Prompt char as the complete evaluated string
|
|
201
|
-
/[>›❯]\s*$/, // Prompt char at the very end of evaluated string
|
|
202
|
-
];
|
|
203
|
-
|
|
204
|
-
const FALLBACK_GENERATING: RegExp[] = [
|
|
205
|
-
/[\u2800-\u28ff]/, // Braille spinner blocks (universal TUI)
|
|
206
|
-
/esc to (cancel|interrupt|stop)/i, // Common TUI generation status line
|
|
207
|
-
/generating\.\.\./i,
|
|
208
|
-
/Claude is (?:thinking|processing|working)/i, // Specific Claude Code status
|
|
209
|
-
];
|
|
210
|
-
|
|
211
|
-
const FALLBACK_APPROVAL: RegExp[] = [
|
|
212
|
-
/Allow\s*once/i, // ANSI strip may remove spaces
|
|
213
|
-
/Always\s*allow/i,
|
|
214
|
-
/\(y\/n\)/i,
|
|
215
|
-
/\[Y\/n\]/i,
|
|
216
|
-
/Yes,?\s*don'?t\s*ask/i, // "Yes, don't ask again" (Claude Code)
|
|
217
|
-
];
|
|
218
|
-
|
|
219
|
-
function defaultCleanOutput(raw: string, _lastUserInput?: string): string {
|
|
220
|
-
return stripAnsi(raw).trim();
|
|
213
|
+
function coercePatternArray(raw: unknown): RegExp[] {
|
|
214
|
+
if (!Array.isArray(raw)) return [];
|
|
215
|
+
return raw.map(parsePatternEntry).filter((r): r is RegExp => r != null);
|
|
221
216
|
}
|
|
222
217
|
|
|
223
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Normalize raw provider JSON for auto-implement approval patterns.
|
|
220
|
+
* Used by dev-server only — ProviderCliAdapter itself uses scripts.
|
|
221
|
+
*/
|
|
222
|
+
export function normalizeCliProviderForRuntime(raw: any): { patterns: { approval: RegExp[] } } {
|
|
224
223
|
const patterns = raw?.patterns || {};
|
|
225
224
|
return {
|
|
226
|
-
...raw,
|
|
227
225
|
patterns: {
|
|
228
|
-
|
|
229
|
-
generating: coercePatternArray(patterns.generating, FALLBACK_GENERATING),
|
|
230
|
-
approval: coercePatternArray(patterns.approval, FALLBACK_APPROVAL),
|
|
231
|
-
ready: coercePatternArray(patterns.ready, []),
|
|
226
|
+
approval: coercePatternArray(patterns.approval),
|
|
232
227
|
},
|
|
233
|
-
cleanOutput: typeof raw?.cleanOutput === 'function' ? raw.cleanOutput : defaultCleanOutput,
|
|
234
228
|
};
|
|
235
229
|
}
|
|
236
230
|
|
|
231
|
+
// ─── Adapter ────────────────────────────────────────
|
|
232
|
+
|
|
237
233
|
export class ProviderCliAdapter implements CliAdapter {
|
|
238
234
|
readonly cliType: string;
|
|
239
235
|
readonly cliName: string;
|
|
@@ -242,6 +238,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
242
238
|
private provider: CliProviderModule;
|
|
243
239
|
private ptyProcess: any = null;
|
|
244
240
|
private messages: CliChatMessage[] = [];
|
|
241
|
+
private structuredMessages: CliChatMessage[] = [];
|
|
245
242
|
private currentStatus: CliSessionStatus['status'] = 'starting';
|
|
246
243
|
private onStatusChange: (() => void) | null = null;
|
|
247
244
|
|
|
@@ -253,7 +250,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
253
250
|
private idleTimeout: NodeJS.Timeout | null = null;
|
|
254
251
|
private ready = false;
|
|
255
252
|
private startupBuffer = '';
|
|
256
|
-
/** After spawn: briefly skip generating/settle so splash/redraw does not flip status; not used to gate sendMessage */
|
|
257
253
|
private startupParseGate = false;
|
|
258
254
|
private spawnAt = 0;
|
|
259
255
|
|
|
@@ -275,7 +271,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
275
271
|
|
|
276
272
|
// Output settle debounce — fires after PTY output goes quiet
|
|
277
273
|
private settleTimer: NodeJS.Timeout | null = null;
|
|
278
|
-
private settledBuffer: string = '';
|
|
274
|
+
private settledBuffer: string = '';
|
|
275
|
+
private submitPendingUntil = 0;
|
|
279
276
|
|
|
280
277
|
// Resize redraw suppression
|
|
281
278
|
private resizeSuppressUntil: number = 0;
|
|
@@ -283,6 +280,17 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
283
280
|
// Debug: status transition history
|
|
284
281
|
private statusHistory: { status: string; at: number; trigger?: string }[] = [];
|
|
285
282
|
|
|
283
|
+
// ─── CLI Scripts (script-based parsing) ───
|
|
284
|
+
private cliScripts: CliScripts;
|
|
285
|
+
/** Full accumulated ANSI-stripped PTY output */
|
|
286
|
+
private accumulatedBuffer: string = '';
|
|
287
|
+
/** Full accumulated raw PTY output (with ANSI) */
|
|
288
|
+
private accumulatedRawBuffer: string = '';
|
|
289
|
+
/** Current visible terminal screen snapshot */
|
|
290
|
+
private terminalScreen = new TerminalScreen(40, 120);
|
|
291
|
+
/** Max accumulated buffer size (last 50KB) */
|
|
292
|
+
private static readonly MAX_ACCUMULATED_BUFFER = 50000;
|
|
293
|
+
|
|
286
294
|
private setStatus(status: CliSessionStatus['status'], trigger?: string): void {
|
|
287
295
|
const prev = this.currentStatus;
|
|
288
296
|
if (prev === status) return;
|
|
@@ -292,21 +300,22 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
292
300
|
LOG.info('CLI', `[${this.cliType}] status: ${prev} → ${status}${trigger ? ` (${trigger})` : ''}`);
|
|
293
301
|
}
|
|
294
302
|
|
|
295
|
-
// Resolved timeouts
|
|
303
|
+
// Resolved timeouts
|
|
296
304
|
private readonly timeouts: Required<NonNullable<CliProviderModule['timeouts']>>;
|
|
297
305
|
|
|
298
|
-
// Provider approval key mapping
|
|
306
|
+
// Provider approval key mapping
|
|
299
307
|
private readonly approvalKeys: Record<number, string>;
|
|
308
|
+
private readonly sendDelayMs: number;
|
|
309
|
+
private readonly sendKey: string;
|
|
300
310
|
|
|
301
311
|
constructor(provider: CliProviderModule, workingDir: string, private extraArgs: string[] = []) {
|
|
302
|
-
this.provider =
|
|
312
|
+
this.provider = provider;
|
|
303
313
|
this.cliType = provider.type;
|
|
304
314
|
this.cliName = provider.name;
|
|
305
315
|
this.workingDir = workingDir.startsWith('~')
|
|
306
316
|
? workingDir.replace(/^~/, os.homedir())
|
|
307
317
|
: workingDir;
|
|
308
318
|
|
|
309
|
-
// Apply timeout overrides from Provider
|
|
310
319
|
const t = provider.timeouts || {};
|
|
311
320
|
this.timeouts = {
|
|
312
321
|
ptyFlush: t.ptyFlush ?? 50,
|
|
@@ -319,9 +328,28 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
319
328
|
outputSettle: t.outputSettle ?? 300,
|
|
320
329
|
};
|
|
321
330
|
|
|
322
|
-
// Load approval key mapping from provider (e.g. approvalKeys: {"0":"1","1":"2","2":"3"})
|
|
323
331
|
const rawKeys = (provider as any).approvalKeys;
|
|
324
332
|
this.approvalKeys = (rawKeys && typeof rawKeys === 'object') ? rawKeys : {};
|
|
333
|
+
this.sendDelayMs = typeof (provider as any).sendDelayMs === 'number' ? Math.max(0, (provider as any).sendDelayMs) : 0;
|
|
334
|
+
this.sendKey = typeof (provider as any).sendKey === 'string' && (provider as any).sendKey.length > 0
|
|
335
|
+
? (provider as any).sendKey
|
|
336
|
+
: '\r';
|
|
337
|
+
|
|
338
|
+
// Scripts are required — loaded by ProviderLoader via compatibility array
|
|
339
|
+
this.cliScripts = (provider as any).scripts || {};
|
|
340
|
+
const scriptNames = Object.keys(this.cliScripts).filter(k => typeof (this.cliScripts as any)[k] === 'function');
|
|
341
|
+
if (scriptNames.length > 0) {
|
|
342
|
+
LOG.info('CLI', `[${this.cliType}] CLI scripts: [${scriptNames.join(', ')}]`);
|
|
343
|
+
} else {
|
|
344
|
+
LOG.warn('CLI', `[${this.cliType}] ⚠ No CLI scripts loaded! Provider needs scripts/{version}/scripts.js`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Inject CLI scripts after construction (e.g. when resolved by ProviderLoader) */
|
|
349
|
+
setCliScripts(scripts: CliScripts): void {
|
|
350
|
+
this.cliScripts = scripts;
|
|
351
|
+
const scriptNames = Object.keys(scripts).filter(k => typeof (scripts as any)[k] === 'function');
|
|
352
|
+
LOG.info('CLI', `[${this.cliType}] CLI scripts injected: [${scriptNames.join(', ')}]`);
|
|
325
353
|
}
|
|
326
354
|
|
|
327
355
|
// ─── Lifecycle ─────────────────────────────────
|
|
@@ -364,7 +392,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
364
392
|
const useShell = isWin ? !!spawnConfig.shell : useShellUnix;
|
|
365
393
|
|
|
366
394
|
if (useShell) {
|
|
367
|
-
// Execute via shell (npm shims, shebang scripts, anything that is not a bare Mach-O/ELF)
|
|
368
395
|
if (!spawnConfig.shell && !isWin) {
|
|
369
396
|
LOG.info('CLI', `[${this.cliType}] Using login shell (script shim or non-native binary)`);
|
|
370
397
|
}
|
|
@@ -404,7 +431,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
404
431
|
|
|
405
432
|
this.ptyProcess.onData((data: string) => {
|
|
406
433
|
this.handleOutput(data);
|
|
407
|
-
// PTY output batch transmit (50ms)
|
|
408
434
|
if (this.onPtyDataCallback) {
|
|
409
435
|
this.ptyOutputBuffer += data;
|
|
410
436
|
if (!this.ptyOutputFlushTimer) {
|
|
@@ -432,21 +458,30 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
432
458
|
this.spawnAt = Date.now();
|
|
433
459
|
this.startupParseGate = true;
|
|
434
460
|
this.startupBuffer = '';
|
|
461
|
+
this.terminalScreen.reset(40, 120);
|
|
435
462
|
this.ready = true;
|
|
436
463
|
this.setStatus('idle', 'pty_ready');
|
|
437
464
|
this.onStatusChange?.();
|
|
438
465
|
}
|
|
439
466
|
|
|
440
|
-
// ─── Output
|
|
467
|
+
// ─── Output Handling ────────────────────────────
|
|
441
468
|
|
|
442
469
|
private handleOutput(rawData: string): void {
|
|
443
|
-
// Suppress output processing briefly after resize to avoid false triggers from screen redraws
|
|
444
470
|
if (Date.now() < this.resizeSuppressUntil) return;
|
|
445
471
|
|
|
472
|
+
if (rawData.includes('\x1b[6n') || rawData.includes('\x1b[?6n')) {
|
|
473
|
+
// Some TUIs probe cursor position during startup; node-pty does not answer automatically.
|
|
474
|
+
this.ptyProcess?.write('\x1b[1;1R');
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
this.terminalScreen.write(rawData);
|
|
446
478
|
const cleanData = stripAnsi(rawData);
|
|
447
|
-
const { patterns } = this.provider;
|
|
448
479
|
|
|
449
|
-
|
|
480
|
+
if (this.isWaitingForResponse && cleanData) {
|
|
481
|
+
this.responseBuffer = (this.responseBuffer + cleanData).slice(-8000);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Server log forwarding
|
|
450
485
|
if (cleanData.trim()) {
|
|
451
486
|
if (this.serverConn) {
|
|
452
487
|
this.serverConn.sendMessage('log', { message: cleanData.trim(), level: 'info' });
|
|
@@ -455,13 +490,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
455
490
|
}
|
|
456
491
|
}
|
|
457
492
|
|
|
458
|
-
|
|
493
|
+
// Rolling buffers
|
|
459
494
|
this.recentOutputBuffer = (this.recentOutputBuffer + cleanData).slice(-1000);
|
|
495
|
+
this.accumulatedBuffer = (this.accumulatedBuffer + cleanData).slice(-ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
|
|
496
|
+
this.accumulatedRawBuffer = (this.accumulatedRawBuffer + rawData).slice(-ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
|
|
460
497
|
|
|
461
|
-
|
|
498
|
+
// ─── Startup window: auto-proceed first-run dialogs
|
|
462
499
|
if (this.startupParseGate) {
|
|
463
500
|
this.startupBuffer += cleanData;
|
|
464
|
-
LOG.info('CLI', `[${this.cliType}] startup chunk (${cleanData.length} chars): ${cleanData.slice(0, 200).replace(/\n/g, '\\n')}`);
|
|
465
501
|
|
|
466
502
|
const dialogPatterns = [
|
|
467
503
|
/Do you want to connect/i,
|
|
@@ -478,133 +514,63 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
478
514
|
|
|
479
515
|
const elapsed = Date.now() - this.spawnAt;
|
|
480
516
|
const bufCap = this.startupBuffer.length > 12000;
|
|
481
|
-
const promptMatched = patterns.prompt.some(p => p.test(this.startupBuffer));
|
|
482
|
-
if (promptMatched || elapsed > 8000 || bufCap) {
|
|
483
|
-
this.startupParseGate = false;
|
|
484
|
-
if (promptMatched) {
|
|
485
|
-
LOG.info('CLI', `[${this.cliType}] ✓ Startup gate end (prompt matched)`);
|
|
486
|
-
} else {
|
|
487
|
-
LOG.info('CLI', `[${this.cliType}] startup gate end (${elapsed}ms, cap=${bufCap}, prompt=${promptMatched})`);
|
|
488
|
-
}
|
|
489
|
-
} else {
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
517
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
// ─── Phase 2: generating — immediate detection (fast UX response)
|
|
499
|
-
if (!this.isWaitingForResponse) {
|
|
500
|
-
if (patterns.generating.some(p => p.test(cleanData))) {
|
|
501
|
-
if (this.settleTimer) { clearTimeout(this.settleTimer); this.settleTimer = null; }
|
|
502
|
-
this.isWaitingForResponse = true;
|
|
503
|
-
this.responseBuffer = '';
|
|
504
|
-
this.setStatus('generating', 'autonomous_gen');
|
|
505
|
-
this.onStatusChange?.();
|
|
506
|
-
}
|
|
507
|
-
}
|
|
518
|
+
// Use detectStatus script to check if CLI is ready (shows a prompt)
|
|
519
|
+
const scriptStatus = this.runDetectStatus(this.startupBuffer);
|
|
520
|
+
const isReady = scriptStatus === 'idle' || elapsed > 8000 || bufCap;
|
|
508
521
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
// ─── Phase 3: still generating
|
|
514
|
-
if (patterns.generating.some(p => p.test(cleanData))) {
|
|
515
|
-
this.setStatus('generating', 'still_generating');
|
|
516
|
-
this.idleTimeout = setTimeout(() => {
|
|
517
|
-
if (this.isWaitingForResponse) this.finishResponse();
|
|
518
|
-
}, this.timeouts.generatingIdle);
|
|
519
|
-
this.onStatusChange?.();
|
|
520
|
-
// Cancel any pending settle — we're clearly still generating
|
|
521
|
-
if (this.settleTimer) { clearTimeout(this.settleTimer); this.settleTimer = null; }
|
|
522
|
+
if (isReady) {
|
|
523
|
+
this.startupParseGate = false;
|
|
524
|
+
LOG.info('CLI', `[${this.cliType}] Startup gate end (${elapsed}ms, scriptStatus=${scriptStatus})`);
|
|
525
|
+
} else {
|
|
522
526
|
return;
|
|
523
527
|
}
|
|
524
528
|
}
|
|
525
529
|
|
|
526
|
-
|
|
527
|
-
if (this.currentStatus === 'waiting_approval') {
|
|
528
|
-
this.approvalTransitionBuffer = (this.approvalTransitionBuffer + cleanData).slice(-500);
|
|
529
|
-
// Schedule settle check — output may still be arriving (ANSI redraws etc)
|
|
530
|
-
this.scheduleSettle();
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// ─── Phase 5: settle debounce — schedule idle/approval evaluation
|
|
530
|
+
// ─── Script-based status detection
|
|
535
531
|
this.scheduleSettle();
|
|
536
532
|
}
|
|
537
533
|
|
|
538
|
-
/**
|
|
539
|
-
* Fired after output goes quiet for outputSettle ms.
|
|
540
|
-
* Evaluates the stabilised buffer for approval, prompt (idle), or timeout.
|
|
541
|
-
*/
|
|
542
534
|
private scheduleSettle(): void {
|
|
543
535
|
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
536
|
+
const delay = Math.max(
|
|
537
|
+
this.timeouts.outputSettle,
|
|
538
|
+
this.submitPendingUntil > Date.now()
|
|
539
|
+
? (this.submitPendingUntil - Date.now()) + this.timeouts.outputSettle
|
|
540
|
+
: 0,
|
|
541
|
+
);
|
|
544
542
|
this.settleTimer = setTimeout(() => {
|
|
545
543
|
this.settleTimer = null;
|
|
546
544
|
this.settledBuffer = this.recentOutputBuffer;
|
|
547
545
|
this.evaluateSettled();
|
|
548
|
-
},
|
|
546
|
+
}, delay);
|
|
549
547
|
}
|
|
550
548
|
|
|
551
549
|
private evaluateSettled(): void {
|
|
552
|
-
const
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
// ─── waiting_approval: check for transition out
|
|
556
|
-
if (this.currentStatus === 'waiting_approval') {
|
|
557
|
-
const genResume = patterns.generating.some(p => p.test(this.approvalTransitionBuffer));
|
|
558
|
-
const promptResume = patterns.prompt.some(p => p.test(this.approvalTransitionBuffer));
|
|
559
|
-
if (genResume) {
|
|
560
|
-
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
561
|
-
this.setStatus('generating', 'approval_gen_resume');
|
|
562
|
-
this.activeModal = null;
|
|
563
|
-
this.recentOutputBuffer = '';
|
|
564
|
-
this.approvalTransitionBuffer = '';
|
|
565
|
-
this.lastApprovalResolvedAt = Date.now();
|
|
566
|
-
this.onStatusChange?.();
|
|
567
|
-
} else if (promptResume) {
|
|
568
|
-
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
569
|
-
this.activeModal = null;
|
|
570
|
-
this.recentOutputBuffer = '';
|
|
571
|
-
this.approvalTransitionBuffer = '';
|
|
572
|
-
this.lastApprovalResolvedAt = Date.now();
|
|
573
|
-
this.finishResponse();
|
|
574
|
-
}
|
|
575
|
-
// else: still waiting — approvalExitTimeout will handle the 60s safety case
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
550
|
+
const tail = this.settledBuffer;
|
|
551
|
+
const scriptStatus = this.runDetectStatus(tail);
|
|
552
|
+
if (!scriptStatus) return;
|
|
578
553
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
if (
|
|
554
|
+
const prevStatus = this.currentStatus;
|
|
555
|
+
|
|
556
|
+
if (scriptStatus === 'waiting_approval') {
|
|
582
557
|
const inCooldown = this.lastApprovalResolvedAt && (Date.now() - this.lastApprovalResolvedAt) < this.timeouts.approvalCooldown;
|
|
583
558
|
if (!inCooldown) {
|
|
584
|
-
const ctxLines = buf.split('\n')
|
|
585
|
-
.map(l => l.trim())
|
|
586
|
-
.filter(l => l && !/^[─═╭╮╰╯│]+$/.test(l));
|
|
587
559
|
this.isWaitingForResponse = true;
|
|
588
|
-
this.setStatus('waiting_approval', '
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
? ['Yes (y)', 'Always allow (a)', 'Deny (Esc)']
|
|
595
|
-
: ['Allow once', 'Always allow', 'Deny'],
|
|
596
|
-
};
|
|
560
|
+
this.setStatus('waiting_approval', 'script_detect');
|
|
561
|
+
|
|
562
|
+
// Use parseApproval script for modal info
|
|
563
|
+
const modal = this.runParseApproval(tail);
|
|
564
|
+
this.activeModal = modal || { message: 'Approval required', buttons: ['Allow', 'Deny'] };
|
|
565
|
+
|
|
597
566
|
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
598
567
|
if (this.approvalExitTimeout) clearTimeout(this.approvalExitTimeout);
|
|
599
568
|
this.approvalExitTimeout = setTimeout(() => {
|
|
600
569
|
if (this.currentStatus === 'waiting_approval') {
|
|
601
|
-
LOG.warn('CLI', `[${this.cliType}] Approval timeout — auto-
|
|
570
|
+
LOG.warn('CLI', `[${this.cliType}] Approval timeout — auto-clearing`);
|
|
602
571
|
this.activeModal = null;
|
|
603
572
|
this.lastApprovalResolvedAt = Date.now();
|
|
604
|
-
this.
|
|
605
|
-
this.approvalTransitionBuffer = '';
|
|
606
|
-
this.approvalExitTimeout = null;
|
|
607
|
-
this.setStatus(this.isWaitingForResponse ? 'generating' : 'idle', 'approval_cleared');
|
|
573
|
+
this.setStatus('idle', 'approval_timeout');
|
|
608
574
|
this.onStatusChange?.();
|
|
609
575
|
}
|
|
610
576
|
}, 60000);
|
|
@@ -613,22 +579,39 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
613
579
|
}
|
|
614
580
|
}
|
|
615
581
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
this.
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
this.
|
|
626
|
-
if (this.isWaitingForResponse && this.responseBuffer.trim()) {
|
|
627
|
-
this.finishResponse();
|
|
628
|
-
}
|
|
629
|
-
}, this.timeouts.idleFinish);
|
|
582
|
+
if (scriptStatus === 'generating') {
|
|
583
|
+
if (prevStatus === 'waiting_approval') {
|
|
584
|
+
// Transitioned out of approval → generating
|
|
585
|
+
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
586
|
+
this.activeModal = null;
|
|
587
|
+
this.lastApprovalResolvedAt = Date.now();
|
|
588
|
+
}
|
|
589
|
+
if (!this.isWaitingForResponse) {
|
|
590
|
+
this.isWaitingForResponse = true;
|
|
591
|
+
this.responseBuffer = '';
|
|
630
592
|
}
|
|
593
|
+
this.setStatus('generating', 'script_detect');
|
|
594
|
+
// Reset idle timeout
|
|
595
|
+
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
596
|
+
this.idleTimeout = setTimeout(() => {
|
|
597
|
+
if (this.isWaitingForResponse) this.finishResponse();
|
|
598
|
+
}, this.timeouts.generatingIdle);
|
|
631
599
|
this.onStatusChange?.();
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (scriptStatus === 'idle') {
|
|
604
|
+
if (prevStatus === 'waiting_approval') {
|
|
605
|
+
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
606
|
+
this.activeModal = null;
|
|
607
|
+
this.lastApprovalResolvedAt = Date.now();
|
|
608
|
+
}
|
|
609
|
+
if (this.isWaitingForResponse) {
|
|
610
|
+
this.finishResponse();
|
|
611
|
+
} else if (prevStatus !== 'idle') {
|
|
612
|
+
this.setStatus('idle', 'script_detect');
|
|
613
|
+
this.onStatusChange?.();
|
|
614
|
+
}
|
|
632
615
|
}
|
|
633
616
|
}
|
|
634
617
|
|
|
@@ -637,23 +620,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
637
620
|
if (this.idleTimeout) { clearTimeout(this.idleTimeout); this.idleTimeout = null; }
|
|
638
621
|
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
639
622
|
|
|
640
|
-
const lastUserText = this.messages.filter(m => m.role === 'user').pop()?.content;
|
|
641
|
-
let response = this.provider.cleanOutput(this.responseBuffer, lastUserText);
|
|
642
|
-
|
|
643
|
-
// Remove user input echo
|
|
644
|
-
if (lastUserText && response) {
|
|
645
|
-
const userTrimmed = lastUserText.trim();
|
|
646
|
-
response = response.split('\n')
|
|
647
|
-
.filter(l => l.trim() !== userTrimmed)
|
|
648
|
-
.join('\n').trim();
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
if (response) {
|
|
652
|
-
this.messages.push({ role: 'assistant', content: response, timestamp: Date.now() });
|
|
653
|
-
if (this.messages.length > 200) this.messages = this.messages.slice(-200);
|
|
654
|
-
LOG.info('CLI', `[${this.cliType}] Response (${response.length} chars)`);
|
|
655
|
-
}
|
|
656
|
-
|
|
657
623
|
this.responseBuffer = '';
|
|
658
624
|
this.isWaitingForResponse = false;
|
|
659
625
|
this.activeModal = null;
|
|
@@ -661,9 +627,54 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
661
627
|
this.onStatusChange?.();
|
|
662
628
|
}
|
|
663
629
|
|
|
664
|
-
// ───
|
|
630
|
+
// ─── Script Execution ──────────────────────────
|
|
631
|
+
|
|
632
|
+
private runDetectStatus(text: string): string | null {
|
|
633
|
+
if (!this.cliScripts?.detectStatus) return null;
|
|
634
|
+
try {
|
|
635
|
+
return this.cliScripts.detectStatus({
|
|
636
|
+
tail: text.slice(-500),
|
|
637
|
+
screenText: this.terminalScreen.getText(),
|
|
638
|
+
rawBuffer: this.accumulatedRawBuffer,
|
|
639
|
+
});
|
|
640
|
+
} catch (e: any) {
|
|
641
|
+
LOG.warn('CLI', `[${this.cliType}] detectStatus error: ${e.message}`);
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
private runParseApproval(tail: string): { message: string; buttons: string[] } | null {
|
|
647
|
+
if (!this.cliScripts?.parseApproval) return null;
|
|
648
|
+
try {
|
|
649
|
+
return this.cliScripts.parseApproval({
|
|
650
|
+
buffer: this.terminalScreen.getText() || this.accumulatedBuffer,
|
|
651
|
+
rawBuffer: this.accumulatedRawBuffer,
|
|
652
|
+
tail,
|
|
653
|
+
});
|
|
654
|
+
} catch (e: any) {
|
|
655
|
+
LOG.warn('CLI', `[${this.cliType}] parseApproval error: ${e.message}`);
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// ─── Public API (CliAdapter) ───────────────────
|
|
665
661
|
|
|
666
662
|
getStatus(): CliSessionStatus {
|
|
663
|
+
// Use parseOutput script for full result when available
|
|
664
|
+
const scriptResult = this.getScriptParsedStatus();
|
|
665
|
+
if (scriptResult) {
|
|
666
|
+
return {
|
|
667
|
+
status: this.currentStatus,
|
|
668
|
+
messages: (scriptResult.messages || []).map((m: any) => ({
|
|
669
|
+
role: m.role,
|
|
670
|
+
content: m.content,
|
|
671
|
+
timestamp: m.timestamp,
|
|
672
|
+
})),
|
|
673
|
+
workingDir: this.workingDir,
|
|
674
|
+
activeModal: this.activeModal,
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
667
678
|
return {
|
|
668
679
|
status: this.currentStatus,
|
|
669
680
|
messages: [...this.messages],
|
|
@@ -672,28 +683,97 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
672
683
|
};
|
|
673
684
|
}
|
|
674
685
|
|
|
686
|
+
/**
|
|
687
|
+
* Script-based full parse — returns ReadChatResult.
|
|
688
|
+
* Called by command handler / dashboard for rich content rendering.
|
|
689
|
+
*/
|
|
690
|
+
getScriptParsedStatus(): any {
|
|
691
|
+
if (!this.cliScripts?.parseOutput) return null;
|
|
692
|
+
try {
|
|
693
|
+
const input: CliScriptInput = {
|
|
694
|
+
buffer: this.accumulatedBuffer,
|
|
695
|
+
rawBuffer: this.accumulatedRawBuffer,
|
|
696
|
+
recentBuffer: this.recentOutputBuffer,
|
|
697
|
+
screenText: this.terminalScreen.getText(),
|
|
698
|
+
messages: [...(this.structuredMessages.length > 0 ? this.structuredMessages : this.messages)],
|
|
699
|
+
partialResponse: this.responseBuffer,
|
|
700
|
+
};
|
|
701
|
+
const result = this.cliScripts.parseOutput(input);
|
|
702
|
+
if (result && typeof result === 'object') {
|
|
703
|
+
if (Array.isArray((result as any).messages)) {
|
|
704
|
+
this.structuredMessages = (result as any).messages.map((m: any) => ({
|
|
705
|
+
role: m.role,
|
|
706
|
+
content: m.content,
|
|
707
|
+
timestamp: m.timestamp,
|
|
708
|
+
}));
|
|
709
|
+
}
|
|
710
|
+
return result;
|
|
711
|
+
}
|
|
712
|
+
} catch (e: any) {
|
|
713
|
+
LOG.warn('CLI', `[${this.cliType}] parseOutput error: ${e.message}`);
|
|
714
|
+
}
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/** Whether this adapter has CLI scripts loaded */
|
|
719
|
+
hasCliScripts(): boolean {
|
|
720
|
+
return typeof this.cliScripts?.detectStatus === 'function';
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Resolves an action (like 'fix' lint error) from the dashboard.
|
|
725
|
+
* Uses resolveAction script if available, otherwise falls back to standard text.
|
|
726
|
+
*/
|
|
727
|
+
async resolveAction(data: any): Promise<void> {
|
|
728
|
+
let promptText = '';
|
|
729
|
+
if (this.cliScripts && typeof this.cliScripts.resolveAction === 'function') {
|
|
730
|
+
try {
|
|
731
|
+
promptText = this.cliScripts.resolveAction(data);
|
|
732
|
+
} catch (e: any) {
|
|
733
|
+
LOG.warn('CLI', `[${this.cliType}] resolveAction error: ${e.message}`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (!promptText && data) {
|
|
737
|
+
// Default fallback
|
|
738
|
+
promptText = `Please fix the following issue:\n${data.title || ''}\n${data.explanation || ''}\n\n${data.message || ''}`.trim();
|
|
739
|
+
}
|
|
740
|
+
if (promptText) {
|
|
741
|
+
await this.sendMessage(promptText);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
675
745
|
async sendMessage(text: string): Promise<void> {
|
|
676
746
|
if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
|
|
677
747
|
if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
|
|
678
748
|
if (this.isWaitingForResponse) return;
|
|
679
749
|
|
|
680
750
|
this.messages.push({ role: 'user', content: text, timestamp: Date.now() });
|
|
751
|
+
this.structuredMessages.push({ role: 'user', content: text, timestamp: Date.now() });
|
|
681
752
|
this.isWaitingForResponse = true;
|
|
682
753
|
this.responseBuffer = '';
|
|
683
754
|
this.setStatus('generating', 'sendMessage');
|
|
684
755
|
this.onStatusChange?.();
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
756
|
+
this.ptyProcess.write(text);
|
|
757
|
+
|
|
758
|
+
const submit = () => {
|
|
759
|
+
if (!this.ptyProcess) return;
|
|
760
|
+
this.submitPendingUntil = 0;
|
|
761
|
+
this.ptyProcess.write(this.sendKey);
|
|
762
|
+
this.responseTimeout = setTimeout(() => {
|
|
763
|
+
if (this.isWaitingForResponse) this.finishResponse();
|
|
764
|
+
}, this.timeouts.maxResponse);
|
|
765
|
+
};
|
|
766
|
+
if (this.sendDelayMs > 0) {
|
|
767
|
+
this.submitPendingUntil = Date.now() + this.sendDelayMs;
|
|
768
|
+
setTimeout(submit, this.sendDelayMs);
|
|
769
|
+
} else {
|
|
770
|
+
submit();
|
|
771
|
+
}
|
|
691
772
|
}
|
|
692
773
|
|
|
693
774
|
getPartialResponse(): string {
|
|
694
775
|
if (!this.isWaitingForResponse) return '';
|
|
695
|
-
|
|
696
|
-
return partial || (this.isWaitingForResponse ? '(generating...)' : '');
|
|
776
|
+
return this.responseBuffer;
|
|
697
777
|
}
|
|
698
778
|
|
|
699
779
|
cancel(): void { this.shutdown(); }
|
|
@@ -717,6 +797,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
717
797
|
|
|
718
798
|
clearHistory(): void {
|
|
719
799
|
this.messages = [];
|
|
800
|
+
this.structuredMessages = [];
|
|
801
|
+
this.accumulatedBuffer = '';
|
|
802
|
+
this.accumulatedRawBuffer = '';
|
|
803
|
+
this.terminalScreen.reset();
|
|
720
804
|
this.onStatusChange?.();
|
|
721
805
|
}
|
|
722
806
|
|
|
@@ -727,19 +811,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
727
811
|
this.ptyProcess?.write(data);
|
|
728
812
|
}
|
|
729
813
|
|
|
730
|
-
/**
|
|
731
|
-
* Resolve an approval modal by navigating to the button at `buttonIndex` and pressing Enter.
|
|
732
|
-
* Index 0 = first option (already selected by default — just Enter).
|
|
733
|
-
* Index N = press Arrow Down N times, then Enter.
|
|
734
|
-
*/
|
|
735
814
|
resolveModal(buttonIndex: number): void {
|
|
736
815
|
if (!this.ptyProcess || this.currentStatus !== 'waiting_approval') return;
|
|
737
|
-
// Use provider-defined approval keys if available
|
|
738
|
-
// Each provider.json can define: { "approvalKeys": { "0": "1", "1": "2", "2": "3" } }
|
|
739
816
|
if (buttonIndex in this.approvalKeys) {
|
|
740
817
|
this.ptyProcess.write(this.approvalKeys[buttonIndex]);
|
|
741
818
|
} else {
|
|
742
|
-
// Generic fallback: Arrow Down to navigate + Enter to select
|
|
743
819
|
const DOWN = '\x1B[B';
|
|
744
820
|
const keys = DOWN.repeat(Math.max(0, buttonIndex)) + '\r';
|
|
745
821
|
this.ptyProcess.write(keys);
|
|
@@ -750,27 +826,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
750
826
|
if (this.ptyProcess) {
|
|
751
827
|
try {
|
|
752
828
|
this.ptyProcess.resize(cols, rows);
|
|
753
|
-
|
|
754
|
-
// the redrawn content (spinners, status text) would falsely trigger generating detection
|
|
829
|
+
this.terminalScreen.resize(rows, cols);
|
|
755
830
|
this.resizeSuppressUntil = Date.now() + 300;
|
|
756
831
|
} catch { }
|
|
757
832
|
}
|
|
758
833
|
}
|
|
759
834
|
|
|
760
|
-
/**
|
|
761
|
-
* Full debug state — exposes all internal buffers, status, and patterns for debugging.
|
|
762
|
-
* Used by DevServer /api/cli/debug endpoint.
|
|
763
|
-
*/
|
|
764
835
|
getDebugState(): Record<string, any> {
|
|
765
|
-
const sb = this.startupBuffer;
|
|
766
|
-
const testOnStartup = (p: RegExp): boolean => {
|
|
767
|
-
const flags = p.flags.includes('g') ? p.flags.replace(/g/g, '') : p.flags;
|
|
768
|
-
return new RegExp(p.source, flags).test(sb);
|
|
769
|
-
};
|
|
770
|
-
const promptDiagnostics = this.provider.patterns.prompt.map((p) => ({
|
|
771
|
-
pattern: p.toString(),
|
|
772
|
-
matchedAgainstStartupBuffer: testOnStartup(p),
|
|
773
|
-
}));
|
|
774
836
|
return {
|
|
775
837
|
type: this.cliType,
|
|
776
838
|
name: this.cliName,
|
|
@@ -780,32 +842,24 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
780
842
|
spawnAt: this.spawnAt,
|
|
781
843
|
workingDir: this.workingDir,
|
|
782
844
|
messages: this.messages.slice(-20),
|
|
845
|
+
structuredMessages: this.structuredMessages.slice(-20),
|
|
783
846
|
messageCount: this.messages.length,
|
|
784
|
-
|
|
785
|
-
startupBuffer:
|
|
786
|
-
startupBufferLength: sb.length,
|
|
787
|
-
promptDiagnostics,
|
|
847
|
+
screenText: this.terminalScreen.getText().slice(-4000),
|
|
848
|
+
startupBuffer: this.startupBuffer.slice(-4000),
|
|
788
849
|
recentOutputBuffer: this.recentOutputBuffer.slice(-500),
|
|
789
850
|
settledBuffer: this.settledBuffer.slice(-500),
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
851
|
+
accumulatedBufferLength: this.accumulatedBuffer.length,
|
|
852
|
+
accumulatedRawBufferLength: this.accumulatedRawBuffer.length,
|
|
853
|
+
rawBufferPreview: this.accumulatedRawBuffer.slice(-1000),
|
|
854
|
+
responseBuffer: this.responseBuffer.slice(-1000),
|
|
793
855
|
isWaitingForResponse: this.isWaitingForResponse,
|
|
794
856
|
activeModal: this.activeModal,
|
|
795
857
|
lastApprovalResolvedAt: this.lastApprovalResolvedAt,
|
|
796
858
|
resizeSuppressUntil: this.resizeSuppressUntil,
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
prompt: this.provider.patterns.prompt.map(p => p.toString()),
|
|
800
|
-
generating: this.provider.patterns.generating.map(p => p.toString()),
|
|
801
|
-
approval: this.provider.patterns.approval.map(p => p.toString()),
|
|
802
|
-
ready: this.provider.patterns.ready.map(p => p.toString()),
|
|
803
|
-
},
|
|
804
|
-
// Status history
|
|
859
|
+
hasCliScripts: this.hasCliScripts(),
|
|
860
|
+
scriptNames: Object.keys(this.cliScripts).filter(k => typeof (this.cliScripts as any)[k] === 'function'),
|
|
805
861
|
statusHistory: this.statusHistory.slice(-30),
|
|
806
|
-
// Timeouts config
|
|
807
862
|
timeouts: this.timeouts,
|
|
808
|
-
// PTY alive
|
|
809
863
|
ptyAlive: !!this.ptyProcess,
|
|
810
864
|
};
|
|
811
865
|
}
|