@adhdev/daemon-core 0.6.52 → 0.6.55
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 +77 -35
- package/dist/index.js +717 -275
- package/dist/index.js.map +1 -1
- package/package.json +1 -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 +267 -251
- package/src/cli-adapters/terminal-screen.ts +268 -0
- package/src/commands/chat-commands.ts +41 -15
- package/src/commands/cli-manager.ts +4 -2
- package/src/daemon/dev-server.ts +245 -35
- package/src/providers/acp-provider-instance.ts +1 -1
- package/src/providers/cli-provider-instance.ts +21 -8
- package/src/providers/provider-loader.ts +6 -6
- 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,6 +59,33 @@ 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 }) => 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;
|
|
@@ -68,12 +97,6 @@ export interface CliProviderModule {
|
|
|
68
97
|
shell: boolean;
|
|
69
98
|
env: Record<string, string>;
|
|
70
99
|
};
|
|
71
|
-
patterns: {
|
|
72
|
-
prompt: RegExp[];
|
|
73
|
-
generating: RegExp[];
|
|
74
|
-
approval: RegExp[];
|
|
75
|
-
ready: RegExp[];
|
|
76
|
-
};
|
|
77
100
|
timeouts?: {
|
|
78
101
|
/** PTY output batch transmit interval (default 50ms) */
|
|
79
102
|
ptyFlush?: number;
|
|
@@ -89,12 +112,13 @@ export interface CliProviderModule {
|
|
|
89
112
|
maxResponse?: number;
|
|
90
113
|
/** shutdown after kill wait (default 1000ms) */
|
|
91
114
|
shutdownGrace?: number;
|
|
92
|
-
/** Output settle debounce before evaluating
|
|
115
|
+
/** Output settle debounce before evaluating status (default 300ms) */
|
|
93
116
|
outputSettle?: number;
|
|
94
117
|
};
|
|
95
|
-
cleanOutput(raw: string, lastUserInput?: string): string;
|
|
96
118
|
}
|
|
97
119
|
|
|
120
|
+
// ─── Utility Functions ──────────────────────────────
|
|
121
|
+
|
|
98
122
|
function stripAnsi(str: string): string {
|
|
99
123
|
// eslint-disable-next-line no-control-regex
|
|
100
124
|
return str
|
|
@@ -168,9 +192,8 @@ function shSingleQuote(arg: string): string {
|
|
|
168
192
|
}
|
|
169
193
|
|
|
170
194
|
/**
|
|
171
|
-
* provider.json
|
|
172
|
-
*
|
|
173
|
-
* (including `{ "source": "...", "flags": "i" }`) or fall back to generic TUIs (Claude Code, etc.).
|
|
195
|
+
* Normalize provider.json for auto-implement approval detection.
|
|
196
|
+
* Kept for backward compat with dev-server auto-impl pipeline only.
|
|
174
197
|
*/
|
|
175
198
|
function parsePatternEntry(x: unknown): RegExp | null {
|
|
176
199
|
if (x instanceof RegExp) return x;
|
|
@@ -185,55 +208,26 @@ function parsePatternEntry(x: unknown): RegExp | null {
|
|
|
185
208
|
return null;
|
|
186
209
|
}
|
|
187
210
|
|
|
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();
|
|
211
|
+
function coercePatternArray(raw: unknown): RegExp[] {
|
|
212
|
+
if (!Array.isArray(raw)) return [];
|
|
213
|
+
return raw.map(parsePatternEntry).filter((r): r is RegExp => r != null);
|
|
221
214
|
}
|
|
222
215
|
|
|
223
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Normalize raw provider JSON for auto-implement approval patterns.
|
|
218
|
+
* Used by dev-server only — ProviderCliAdapter itself uses scripts.
|
|
219
|
+
*/
|
|
220
|
+
export function normalizeCliProviderForRuntime(raw: any): { patterns: { approval: RegExp[] } } {
|
|
224
221
|
const patterns = raw?.patterns || {};
|
|
225
222
|
return {
|
|
226
|
-
...raw,
|
|
227
223
|
patterns: {
|
|
228
|
-
|
|
229
|
-
generating: coercePatternArray(patterns.generating, FALLBACK_GENERATING),
|
|
230
|
-
approval: coercePatternArray(patterns.approval, FALLBACK_APPROVAL),
|
|
231
|
-
ready: coercePatternArray(patterns.ready, []),
|
|
224
|
+
approval: coercePatternArray(patterns.approval),
|
|
232
225
|
},
|
|
233
|
-
cleanOutput: typeof raw?.cleanOutput === 'function' ? raw.cleanOutput : defaultCleanOutput,
|
|
234
226
|
};
|
|
235
227
|
}
|
|
236
228
|
|
|
229
|
+
// ─── Adapter ────────────────────────────────────────
|
|
230
|
+
|
|
237
231
|
export class ProviderCliAdapter implements CliAdapter {
|
|
238
232
|
readonly cliType: string;
|
|
239
233
|
readonly cliName: string;
|
|
@@ -242,6 +236,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
242
236
|
private provider: CliProviderModule;
|
|
243
237
|
private ptyProcess: any = null;
|
|
244
238
|
private messages: CliChatMessage[] = [];
|
|
239
|
+
private structuredMessages: CliChatMessage[] = [];
|
|
245
240
|
private currentStatus: CliSessionStatus['status'] = 'starting';
|
|
246
241
|
private onStatusChange: (() => void) | null = null;
|
|
247
242
|
|
|
@@ -253,7 +248,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
253
248
|
private idleTimeout: NodeJS.Timeout | null = null;
|
|
254
249
|
private ready = false;
|
|
255
250
|
private startupBuffer = '';
|
|
256
|
-
/** After spawn: briefly skip generating/settle so splash/redraw does not flip status; not used to gate sendMessage */
|
|
257
251
|
private startupParseGate = false;
|
|
258
252
|
private spawnAt = 0;
|
|
259
253
|
|
|
@@ -275,7 +269,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
275
269
|
|
|
276
270
|
// Output settle debounce — fires after PTY output goes quiet
|
|
277
271
|
private settleTimer: NodeJS.Timeout | null = null;
|
|
278
|
-
private settledBuffer: string = '';
|
|
272
|
+
private settledBuffer: string = '';
|
|
279
273
|
|
|
280
274
|
// Resize redraw suppression
|
|
281
275
|
private resizeSuppressUntil: number = 0;
|
|
@@ -283,6 +277,17 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
283
277
|
// Debug: status transition history
|
|
284
278
|
private statusHistory: { status: string; at: number; trigger?: string }[] = [];
|
|
285
279
|
|
|
280
|
+
// ─── CLI Scripts (script-based parsing) ───
|
|
281
|
+
private cliScripts: CliScripts;
|
|
282
|
+
/** Full accumulated ANSI-stripped PTY output */
|
|
283
|
+
private accumulatedBuffer: string = '';
|
|
284
|
+
/** Full accumulated raw PTY output (with ANSI) */
|
|
285
|
+
private accumulatedRawBuffer: string = '';
|
|
286
|
+
/** Current visible terminal screen snapshot */
|
|
287
|
+
private terminalScreen = new TerminalScreen(40, 120);
|
|
288
|
+
/** Max accumulated buffer size (last 50KB) */
|
|
289
|
+
private static readonly MAX_ACCUMULATED_BUFFER = 50000;
|
|
290
|
+
|
|
286
291
|
private setStatus(status: CliSessionStatus['status'], trigger?: string): void {
|
|
287
292
|
const prev = this.currentStatus;
|
|
288
293
|
if (prev === status) return;
|
|
@@ -292,21 +297,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
292
297
|
LOG.info('CLI', `[${this.cliType}] status: ${prev} → ${status}${trigger ? ` (${trigger})` : ''}`);
|
|
293
298
|
}
|
|
294
299
|
|
|
295
|
-
// Resolved timeouts
|
|
300
|
+
// Resolved timeouts
|
|
296
301
|
private readonly timeouts: Required<NonNullable<CliProviderModule['timeouts']>>;
|
|
297
302
|
|
|
298
|
-
// Provider approval key mapping
|
|
303
|
+
// Provider approval key mapping
|
|
299
304
|
private readonly approvalKeys: Record<number, string>;
|
|
300
305
|
|
|
301
306
|
constructor(provider: CliProviderModule, workingDir: string, private extraArgs: string[] = []) {
|
|
302
|
-
this.provider =
|
|
307
|
+
this.provider = provider;
|
|
303
308
|
this.cliType = provider.type;
|
|
304
309
|
this.cliName = provider.name;
|
|
305
310
|
this.workingDir = workingDir.startsWith('~')
|
|
306
311
|
? workingDir.replace(/^~/, os.homedir())
|
|
307
312
|
: workingDir;
|
|
308
313
|
|
|
309
|
-
// Apply timeout overrides from Provider
|
|
310
314
|
const t = provider.timeouts || {};
|
|
311
315
|
this.timeouts = {
|
|
312
316
|
ptyFlush: t.ptyFlush ?? 50,
|
|
@@ -319,9 +323,24 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
319
323
|
outputSettle: t.outputSettle ?? 300,
|
|
320
324
|
};
|
|
321
325
|
|
|
322
|
-
// Load approval key mapping from provider (e.g. approvalKeys: {"0":"1","1":"2","2":"3"})
|
|
323
326
|
const rawKeys = (provider as any).approvalKeys;
|
|
324
327
|
this.approvalKeys = (rawKeys && typeof rawKeys === 'object') ? rawKeys : {};
|
|
328
|
+
|
|
329
|
+
// Scripts are required — loaded by ProviderLoader via compatibility array
|
|
330
|
+
this.cliScripts = (provider as any).scripts || {};
|
|
331
|
+
const scriptNames = Object.keys(this.cliScripts).filter(k => typeof (this.cliScripts as any)[k] === 'function');
|
|
332
|
+
if (scriptNames.length > 0) {
|
|
333
|
+
LOG.info('CLI', `[${this.cliType}] CLI scripts: [${scriptNames.join(', ')}]`);
|
|
334
|
+
} else {
|
|
335
|
+
LOG.warn('CLI', `[${this.cliType}] ⚠ No CLI scripts loaded! Provider needs scripts/{version}/scripts.js`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Inject CLI scripts after construction (e.g. when resolved by ProviderLoader) */
|
|
340
|
+
setCliScripts(scripts: CliScripts): void {
|
|
341
|
+
this.cliScripts = scripts;
|
|
342
|
+
const scriptNames = Object.keys(scripts).filter(k => typeof (scripts as any)[k] === 'function');
|
|
343
|
+
LOG.info('CLI', `[${this.cliType}] CLI scripts injected: [${scriptNames.join(', ')}]`);
|
|
325
344
|
}
|
|
326
345
|
|
|
327
346
|
// ─── Lifecycle ─────────────────────────────────
|
|
@@ -364,7 +383,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
364
383
|
const useShell = isWin ? !!spawnConfig.shell : useShellUnix;
|
|
365
384
|
|
|
366
385
|
if (useShell) {
|
|
367
|
-
// Execute via shell (npm shims, shebang scripts, anything that is not a bare Mach-O/ELF)
|
|
368
386
|
if (!spawnConfig.shell && !isWin) {
|
|
369
387
|
LOG.info('CLI', `[${this.cliType}] Using login shell (script shim or non-native binary)`);
|
|
370
388
|
}
|
|
@@ -404,7 +422,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
404
422
|
|
|
405
423
|
this.ptyProcess.onData((data: string) => {
|
|
406
424
|
this.handleOutput(data);
|
|
407
|
-
// PTY output batch transmit (50ms)
|
|
408
425
|
if (this.onPtyDataCallback) {
|
|
409
426
|
this.ptyOutputBuffer += data;
|
|
410
427
|
if (!this.ptyOutputFlushTimer) {
|
|
@@ -432,21 +449,25 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
432
449
|
this.spawnAt = Date.now();
|
|
433
450
|
this.startupParseGate = true;
|
|
434
451
|
this.startupBuffer = '';
|
|
452
|
+
this.terminalScreen.reset(40, 120);
|
|
435
453
|
this.ready = true;
|
|
436
454
|
this.setStatus('idle', 'pty_ready');
|
|
437
455
|
this.onStatusChange?.();
|
|
438
456
|
}
|
|
439
457
|
|
|
440
|
-
// ─── Output
|
|
458
|
+
// ─── Output Handling ────────────────────────────
|
|
441
459
|
|
|
442
460
|
private handleOutput(rawData: string): void {
|
|
443
|
-
// Suppress output processing briefly after resize to avoid false triggers from screen redraws
|
|
444
461
|
if (Date.now() < this.resizeSuppressUntil) return;
|
|
445
462
|
|
|
463
|
+
this.terminalScreen.write(rawData);
|
|
446
464
|
const cleanData = stripAnsi(rawData);
|
|
447
|
-
const { patterns } = this.provider;
|
|
448
465
|
|
|
449
|
-
|
|
466
|
+
if (this.isWaitingForResponse && cleanData) {
|
|
467
|
+
this.responseBuffer = (this.responseBuffer + cleanData).slice(-8000);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Server log forwarding
|
|
450
471
|
if (cleanData.trim()) {
|
|
451
472
|
if (this.serverConn) {
|
|
452
473
|
this.serverConn.sendMessage('log', { message: cleanData.trim(), level: 'info' });
|
|
@@ -455,13 +476,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
455
476
|
}
|
|
456
477
|
}
|
|
457
478
|
|
|
458
|
-
|
|
479
|
+
// Rolling buffers
|
|
459
480
|
this.recentOutputBuffer = (this.recentOutputBuffer + cleanData).slice(-1000);
|
|
481
|
+
this.accumulatedBuffer = (this.accumulatedBuffer + cleanData).slice(-ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
|
|
482
|
+
this.accumulatedRawBuffer = (this.accumulatedRawBuffer + rawData).slice(-ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
|
|
460
483
|
|
|
461
|
-
|
|
484
|
+
// ─── Startup window: auto-proceed first-run dialogs
|
|
462
485
|
if (this.startupParseGate) {
|
|
463
486
|
this.startupBuffer += cleanData;
|
|
464
|
-
LOG.info('CLI', `[${this.cliType}] startup chunk (${cleanData.length} chars): ${cleanData.slice(0, 200).replace(/\n/g, '\\n')}`);
|
|
465
487
|
|
|
466
488
|
const dialogPatterns = [
|
|
467
489
|
/Do you want to connect/i,
|
|
@@ -478,67 +500,23 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
478
500
|
|
|
479
501
|
const elapsed = Date.now() - this.spawnAt;
|
|
480
502
|
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
|
-
|
|
494
|
-
if (cleanData.trim().length > 5) {
|
|
495
|
-
LOG.debug('CLI', `[${this.cliType}] output chunk (${cleanData.length}): ${cleanData.slice(0, 300).replace(/\n/g, '\\n')}`);
|
|
496
|
-
}
|
|
497
503
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
}
|
|
504
|
+
// Use detectStatus script to check if CLI is ready (shows a prompt)
|
|
505
|
+
const scriptStatus = this.runDetectStatus(this.startupBuffer);
|
|
506
|
+
const isReady = scriptStatus === 'idle' || elapsed > 8000 || bufCap;
|
|
508
507
|
|
|
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; }
|
|
508
|
+
if (isReady) {
|
|
509
|
+
this.startupParseGate = false;
|
|
510
|
+
LOG.info('CLI', `[${this.cliType}] Startup gate end (${elapsed}ms, scriptStatus=${scriptStatus})`);
|
|
511
|
+
} else {
|
|
522
512
|
return;
|
|
523
513
|
}
|
|
524
514
|
}
|
|
525
515
|
|
|
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
|
|
516
|
+
// ─── Script-based status detection
|
|
535
517
|
this.scheduleSettle();
|
|
536
518
|
}
|
|
537
519
|
|
|
538
|
-
/**
|
|
539
|
-
* Fired after output goes quiet for outputSettle ms.
|
|
540
|
-
* Evaluates the stabilised buffer for approval, prompt (idle), or timeout.
|
|
541
|
-
*/
|
|
542
520
|
private scheduleSettle(): void {
|
|
543
521
|
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
544
522
|
this.settleTimer = setTimeout(() => {
|
|
@@ -549,62 +527,30 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
549
527
|
}
|
|
550
528
|
|
|
551
529
|
private evaluateSettled(): void {
|
|
552
|
-
const
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
-
}
|
|
530
|
+
const tail = this.settledBuffer;
|
|
531
|
+
const scriptStatus = this.runDetectStatus(tail);
|
|
532
|
+
if (!scriptStatus) return;
|
|
533
|
+
|
|
534
|
+
const prevStatus = this.currentStatus;
|
|
578
535
|
|
|
579
|
-
|
|
580
|
-
const hasApproval = patterns.approval.some(p => p.test(buf));
|
|
581
|
-
if (hasApproval) {
|
|
536
|
+
if (scriptStatus === 'waiting_approval') {
|
|
582
537
|
const inCooldown = this.lastApprovalResolvedAt && (Date.now() - this.lastApprovalResolvedAt) < this.timeouts.approvalCooldown;
|
|
583
538
|
if (!inCooldown) {
|
|
584
|
-
const ctxLines = buf.split('\n')
|
|
585
|
-
.map(l => l.trim())
|
|
586
|
-
.filter(l => l && !/^[─═╭╮╰╯│]+$/.test(l));
|
|
587
539
|
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
|
-
};
|
|
540
|
+
this.setStatus('waiting_approval', 'script_detect');
|
|
541
|
+
|
|
542
|
+
// Use parseApproval script for modal info
|
|
543
|
+
const modal = this.runParseApproval(tail);
|
|
544
|
+
this.activeModal = modal || { message: 'Approval required', buttons: ['Allow', 'Deny'] };
|
|
545
|
+
|
|
597
546
|
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
598
547
|
if (this.approvalExitTimeout) clearTimeout(this.approvalExitTimeout);
|
|
599
548
|
this.approvalExitTimeout = setTimeout(() => {
|
|
600
549
|
if (this.currentStatus === 'waiting_approval') {
|
|
601
|
-
LOG.warn('CLI', `[${this.cliType}] Approval timeout — auto-
|
|
550
|
+
LOG.warn('CLI', `[${this.cliType}] Approval timeout — auto-clearing`);
|
|
602
551
|
this.activeModal = null;
|
|
603
552
|
this.lastApprovalResolvedAt = Date.now();
|
|
604
|
-
this.
|
|
605
|
-
this.approvalTransitionBuffer = '';
|
|
606
|
-
this.approvalExitTimeout = null;
|
|
607
|
-
this.setStatus(this.isWaitingForResponse ? 'generating' : 'idle', 'approval_cleared');
|
|
553
|
+
this.setStatus('idle', 'approval_timeout');
|
|
608
554
|
this.onStatusChange?.();
|
|
609
555
|
}
|
|
610
556
|
}, 60000);
|
|
@@ -613,22 +559,39 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
613
559
|
}
|
|
614
560
|
}
|
|
615
561
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
this.
|
|
622
|
-
} else if (!patterns.generating.some(p => p.test(buf))) {
|
|
623
|
-
// Output has settled with no generating signal and no prompt — schedule idle finish
|
|
624
|
-
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
625
|
-
this.idleTimeout = setTimeout(() => {
|
|
626
|
-
if (this.isWaitingForResponse && this.responseBuffer.trim()) {
|
|
627
|
-
this.finishResponse();
|
|
628
|
-
}
|
|
629
|
-
}, this.timeouts.idleFinish);
|
|
562
|
+
if (scriptStatus === 'generating') {
|
|
563
|
+
if (prevStatus === 'waiting_approval') {
|
|
564
|
+
// Transitioned out of approval → generating
|
|
565
|
+
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
566
|
+
this.activeModal = null;
|
|
567
|
+
this.lastApprovalResolvedAt = Date.now();
|
|
630
568
|
}
|
|
569
|
+
if (!this.isWaitingForResponse) {
|
|
570
|
+
this.isWaitingForResponse = true;
|
|
571
|
+
this.responseBuffer = '';
|
|
572
|
+
}
|
|
573
|
+
this.setStatus('generating', 'script_detect');
|
|
574
|
+
// Reset idle timeout
|
|
575
|
+
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
576
|
+
this.idleTimeout = setTimeout(() => {
|
|
577
|
+
if (this.isWaitingForResponse) this.finishResponse();
|
|
578
|
+
}, this.timeouts.generatingIdle);
|
|
631
579
|
this.onStatusChange?.();
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (scriptStatus === 'idle') {
|
|
584
|
+
if (prevStatus === 'waiting_approval') {
|
|
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.finishResponse();
|
|
591
|
+
} else if (prevStatus !== 'idle') {
|
|
592
|
+
this.setStatus('idle', 'script_detect');
|
|
593
|
+
this.onStatusChange?.();
|
|
594
|
+
}
|
|
632
595
|
}
|
|
633
596
|
}
|
|
634
597
|
|
|
@@ -637,23 +600,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
637
600
|
if (this.idleTimeout) { clearTimeout(this.idleTimeout); this.idleTimeout = null; }
|
|
638
601
|
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
639
602
|
|
|
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
603
|
this.responseBuffer = '';
|
|
658
604
|
this.isWaitingForResponse = false;
|
|
659
605
|
this.activeModal = null;
|
|
@@ -661,9 +607,50 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
661
607
|
this.onStatusChange?.();
|
|
662
608
|
}
|
|
663
609
|
|
|
664
|
-
// ───
|
|
610
|
+
// ─── Script Execution ──────────────────────────
|
|
611
|
+
|
|
612
|
+
private runDetectStatus(text: string): string | null {
|
|
613
|
+
if (!this.cliScripts?.detectStatus) return null;
|
|
614
|
+
try {
|
|
615
|
+
return this.cliScripts.detectStatus({ tail: text.slice(-500) });
|
|
616
|
+
} catch (e: any) {
|
|
617
|
+
LOG.warn('CLI', `[${this.cliType}] detectStatus error: ${e.message}`);
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
private runParseApproval(tail: string): { message: string; buttons: string[] } | null {
|
|
623
|
+
if (!this.cliScripts?.parseApproval) return null;
|
|
624
|
+
try {
|
|
625
|
+
return this.cliScripts.parseApproval({
|
|
626
|
+
buffer: this.terminalScreen.getText() || this.accumulatedBuffer,
|
|
627
|
+
rawBuffer: this.accumulatedRawBuffer,
|
|
628
|
+
tail,
|
|
629
|
+
});
|
|
630
|
+
} catch (e: any) {
|
|
631
|
+
LOG.warn('CLI', `[${this.cliType}] parseApproval error: ${e.message}`);
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// ─── Public API (CliAdapter) ───────────────────
|
|
665
637
|
|
|
666
638
|
getStatus(): CliSessionStatus {
|
|
639
|
+
// Use parseOutput script for full result when available
|
|
640
|
+
const scriptResult = this.getScriptParsedStatus();
|
|
641
|
+
if (scriptResult) {
|
|
642
|
+
return {
|
|
643
|
+
status: this.currentStatus,
|
|
644
|
+
messages: (scriptResult.messages || []).map((m: any) => ({
|
|
645
|
+
role: m.role,
|
|
646
|
+
content: m.content,
|
|
647
|
+
timestamp: m.timestamp,
|
|
648
|
+
})),
|
|
649
|
+
workingDir: this.workingDir,
|
|
650
|
+
activeModal: this.activeModal,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
667
654
|
return {
|
|
668
655
|
status: this.currentStatus,
|
|
669
656
|
messages: [...this.messages],
|
|
@@ -672,12 +659,72 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
672
659
|
};
|
|
673
660
|
}
|
|
674
661
|
|
|
662
|
+
/**
|
|
663
|
+
* Script-based full parse — returns ReadChatResult.
|
|
664
|
+
* Called by command handler / dashboard for rich content rendering.
|
|
665
|
+
*/
|
|
666
|
+
getScriptParsedStatus(): any {
|
|
667
|
+
if (!this.cliScripts?.parseOutput) return null;
|
|
668
|
+
try {
|
|
669
|
+
const input: CliScriptInput = {
|
|
670
|
+
buffer: this.accumulatedBuffer,
|
|
671
|
+
rawBuffer: this.accumulatedRawBuffer,
|
|
672
|
+
recentBuffer: this.recentOutputBuffer,
|
|
673
|
+
screenText: this.terminalScreen.getText(),
|
|
674
|
+
messages: [...(this.structuredMessages.length > 0 ? this.structuredMessages : this.messages)],
|
|
675
|
+
partialResponse: this.responseBuffer,
|
|
676
|
+
};
|
|
677
|
+
const result = this.cliScripts.parseOutput(input);
|
|
678
|
+
if (result && typeof result === 'object') {
|
|
679
|
+
if (Array.isArray((result as any).messages)) {
|
|
680
|
+
this.structuredMessages = (result as any).messages.map((m: any) => ({
|
|
681
|
+
role: m.role,
|
|
682
|
+
content: m.content,
|
|
683
|
+
timestamp: m.timestamp,
|
|
684
|
+
}));
|
|
685
|
+
}
|
|
686
|
+
return result;
|
|
687
|
+
}
|
|
688
|
+
} catch (e: any) {
|
|
689
|
+
LOG.warn('CLI', `[${this.cliType}] parseOutput error: ${e.message}`);
|
|
690
|
+
}
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/** Whether this adapter has CLI scripts loaded */
|
|
695
|
+
hasCliScripts(): boolean {
|
|
696
|
+
return typeof this.cliScripts?.detectStatus === 'function';
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Resolves an action (like 'fix' lint error) from the dashboard.
|
|
701
|
+
* Uses resolveAction script if available, otherwise falls back to standard text.
|
|
702
|
+
*/
|
|
703
|
+
async resolveAction(data: any): Promise<void> {
|
|
704
|
+
let promptText = '';
|
|
705
|
+
if (this.cliScripts && typeof this.cliScripts.resolveAction === 'function') {
|
|
706
|
+
try {
|
|
707
|
+
promptText = this.cliScripts.resolveAction(data);
|
|
708
|
+
} catch (e: any) {
|
|
709
|
+
LOG.warn('CLI', `[${this.cliType}] resolveAction error: ${e.message}`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (!promptText && data) {
|
|
713
|
+
// Default fallback
|
|
714
|
+
promptText = `Please fix the following issue:\n${data.title || ''}\n${data.explanation || ''}\n\n${data.message || ''}`.trim();
|
|
715
|
+
}
|
|
716
|
+
if (promptText) {
|
|
717
|
+
await this.sendMessage(promptText);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
675
721
|
async sendMessage(text: string): Promise<void> {
|
|
676
722
|
if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
|
|
677
723
|
if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
|
|
678
724
|
if (this.isWaitingForResponse) return;
|
|
679
725
|
|
|
680
726
|
this.messages.push({ role: 'user', content: text, timestamp: Date.now() });
|
|
727
|
+
this.structuredMessages.push({ role: 'user', content: text, timestamp: Date.now() });
|
|
681
728
|
this.isWaitingForResponse = true;
|
|
682
729
|
this.responseBuffer = '';
|
|
683
730
|
this.setStatus('generating', 'sendMessage');
|
|
@@ -692,8 +739,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
692
739
|
|
|
693
740
|
getPartialResponse(): string {
|
|
694
741
|
if (!this.isWaitingForResponse) return '';
|
|
695
|
-
|
|
696
|
-
return partial || (this.isWaitingForResponse ? '(generating...)' : '');
|
|
742
|
+
return this.responseBuffer;
|
|
697
743
|
}
|
|
698
744
|
|
|
699
745
|
cancel(): void { this.shutdown(); }
|
|
@@ -717,6 +763,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
717
763
|
|
|
718
764
|
clearHistory(): void {
|
|
719
765
|
this.messages = [];
|
|
766
|
+
this.structuredMessages = [];
|
|
767
|
+
this.accumulatedBuffer = '';
|
|
768
|
+
this.accumulatedRawBuffer = '';
|
|
769
|
+
this.terminalScreen.reset();
|
|
720
770
|
this.onStatusChange?.();
|
|
721
771
|
}
|
|
722
772
|
|
|
@@ -727,19 +777,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
727
777
|
this.ptyProcess?.write(data);
|
|
728
778
|
}
|
|
729
779
|
|
|
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
780
|
resolveModal(buttonIndex: number): void {
|
|
736
781
|
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
782
|
if (buttonIndex in this.approvalKeys) {
|
|
740
783
|
this.ptyProcess.write(this.approvalKeys[buttonIndex]);
|
|
741
784
|
} else {
|
|
742
|
-
// Generic fallback: Arrow Down to navigate + Enter to select
|
|
743
785
|
const DOWN = '\x1B[B';
|
|
744
786
|
const keys = DOWN.repeat(Math.max(0, buttonIndex)) + '\r';
|
|
745
787
|
this.ptyProcess.write(keys);
|
|
@@ -750,27 +792,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
750
792
|
if (this.ptyProcess) {
|
|
751
793
|
try {
|
|
752
794
|
this.ptyProcess.resize(cols, rows);
|
|
753
|
-
|
|
754
|
-
// the redrawn content (spinners, status text) would falsely trigger generating detection
|
|
795
|
+
this.terminalScreen.resize(rows, cols);
|
|
755
796
|
this.resizeSuppressUntil = Date.now() + 300;
|
|
756
797
|
} catch { }
|
|
757
798
|
}
|
|
758
799
|
}
|
|
759
800
|
|
|
760
|
-
/**
|
|
761
|
-
* Full debug state — exposes all internal buffers, status, and patterns for debugging.
|
|
762
|
-
* Used by DevServer /api/cli/debug endpoint.
|
|
763
|
-
*/
|
|
764
801
|
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
802
|
return {
|
|
775
803
|
type: this.cliType,
|
|
776
804
|
name: this.cliName,
|
|
@@ -780,32 +808,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
780
808
|
spawnAt: this.spawnAt,
|
|
781
809
|
workingDir: this.workingDir,
|
|
782
810
|
messages: this.messages.slice(-20),
|
|
811
|
+
structuredMessages: this.structuredMessages.slice(-20),
|
|
783
812
|
messageCount: this.messages.length,
|
|
784
|
-
|
|
785
|
-
startupBuffer: sb.slice(-4000),
|
|
786
|
-
startupBufferLength: sb.length,
|
|
787
|
-
promptDiagnostics,
|
|
813
|
+
startupBuffer: this.startupBuffer.slice(-4000),
|
|
788
814
|
recentOutputBuffer: this.recentOutputBuffer.slice(-500),
|
|
789
815
|
settledBuffer: this.settledBuffer.slice(-500),
|
|
790
|
-
|
|
791
|
-
approvalTransitionBuffer: this.approvalTransitionBuffer.slice(-500),
|
|
792
|
-
// State
|
|
816
|
+
accumulatedBufferLength: this.accumulatedBuffer.length,
|
|
793
817
|
isWaitingForResponse: this.isWaitingForResponse,
|
|
794
818
|
activeModal: this.activeModal,
|
|
795
819
|
lastApprovalResolvedAt: this.lastApprovalResolvedAt,
|
|
796
820
|
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
|
|
821
|
+
hasCliScripts: this.hasCliScripts(),
|
|
822
|
+
scriptNames: Object.keys(this.cliScripts).filter(k => typeof (this.cliScripts as any)[k] === 'function'),
|
|
805
823
|
statusHistory: this.statusHistory.slice(-30),
|
|
806
|
-
// Timeouts config
|
|
807
824
|
timeouts: this.timeouts,
|
|
808
|
-
// PTY alive
|
|
809
825
|
ptyAlive: !!this.ptyProcess,
|
|
810
826
|
};
|
|
811
827
|
}
|