@adhdev/daemon-core 0.6.53 → 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.
Files changed (164) hide show
  1. package/dist/index.d.ts +76 -34
  2. package/dist/index.js +714 -271
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/providers/_builtin/_helpers/index.js +26 -26
  6. package/providers/_builtin/cli/aider-cli/provider.json +37 -0
  7. package/providers/_builtin/cli/aider-cli/scripts/1.0/detect_status.js +20 -0
  8. package/providers/_builtin/cli/aider-cli/scripts/1.0/parse_approval.js +10 -0
  9. package/providers/_builtin/cli/aider-cli/scripts/1.0/parse_output.js +49 -0
  10. package/providers/_builtin/cli/aider-cli/scripts/1.0/scripts.js +7 -0
  11. package/providers/_builtin/cli/claude-cli/provider.json +9 -2
  12. package/providers/_builtin/cli/claude-cli/scripts/1.0/detect_status.js +31 -0
  13. package/providers/_builtin/cli/claude-cli/scripts/1.0/parse_approval.js +38 -0
  14. package/providers/_builtin/cli/claude-cli/scripts/1.0/parse_output.js +164 -0
  15. package/providers/_builtin/cli/claude-cli/scripts/1.0/scripts.js +42 -0
  16. package/providers/_builtin/cli/codex-cli/provider.json +9 -2
  17. package/providers/_builtin/cli/codex-cli/scripts/1.0/detect_status.js +20 -0
  18. package/providers/_builtin/cli/codex-cli/scripts/1.0/parse_approval.js +15 -0
  19. package/providers/_builtin/cli/codex-cli/scripts/1.0/parse_output.js +68 -0
  20. package/providers/_builtin/cli/codex-cli/scripts/1.0/scripts.js +11 -0
  21. package/providers/_builtin/cli/cursor-cli/provider.json +37 -0
  22. package/providers/_builtin/cli/cursor-cli/scripts/1.0/detect_status.js +19 -0
  23. package/providers/_builtin/cli/cursor-cli/scripts/1.0/parse_approval.js +10 -0
  24. package/providers/_builtin/cli/cursor-cli/scripts/1.0/parse_output.js +48 -0
  25. package/providers/_builtin/cli/cursor-cli/scripts/1.0/scripts.js +7 -0
  26. package/providers/_builtin/cli/gemini-cli/provider.json +9 -2
  27. package/providers/_builtin/cli/gemini-cli/scripts/1.0/detect_status.js +26 -0
  28. package/providers/_builtin/cli/gemini-cli/scripts/1.0/parse_approval.js +22 -0
  29. package/providers/_builtin/cli/gemini-cli/scripts/1.0/parse_output.js +71 -0
  30. package/providers/_builtin/cli/gemini-cli/scripts/1.0/scripts.js +12 -0
  31. package/providers/_builtin/cli/github-copilot-cli/provider.json +37 -0
  32. package/providers/_builtin/cli/github-copilot-cli/scripts/1.0/detect_status.js +19 -0
  33. package/providers/_builtin/cli/github-copilot-cli/scripts/1.0/parse_approval.js +9 -0
  34. package/providers/_builtin/cli/github-copilot-cli/scripts/1.0/parse_output.js +48 -0
  35. package/providers/_builtin/cli/github-copilot-cli/scripts/1.0/scripts.js +7 -0
  36. package/providers/_builtin/cli/goose-cli/provider.json +37 -0
  37. package/providers/_builtin/cli/goose-cli/scripts/1.0/detect_status.js +20 -0
  38. package/providers/_builtin/cli/goose-cli/scripts/1.0/parse_approval.js +10 -0
  39. package/providers/_builtin/cli/goose-cli/scripts/1.0/parse_output.js +48 -0
  40. package/providers/_builtin/cli/goose-cli/scripts/1.0/scripts.js +7 -0
  41. package/providers/_builtin/cli/opencode-cli/provider.json +37 -0
  42. package/providers/_builtin/cli/opencode-cli/scripts/1.0/detect_status.js +15 -0
  43. package/providers/_builtin/cli/opencode-cli/scripts/1.0/parse_approval.js +9 -0
  44. package/providers/_builtin/cli/opencode-cli/scripts/1.0/parse_output.js +48 -0
  45. package/providers/_builtin/cli/opencode-cli/scripts/1.0/scripts.js +7 -0
  46. package/providers/_builtin/extension/cline/scripts/1.0/focus_editor.js +5 -5
  47. package/providers/_builtin/extension/cline/scripts/1.0/list_chats.js +11 -11
  48. package/providers/_builtin/extension/cline/scripts/1.0/list_models.js +6 -6
  49. package/providers/_builtin/extension/cline/scripts/1.0/list_modes.js +5 -5
  50. package/providers/_builtin/extension/cline/scripts/1.0/new_session.js +12 -12
  51. package/providers/_builtin/extension/cline/scripts/1.0/open_panel.js +7 -7
  52. package/providers/_builtin/extension/cline/scripts/1.0/read_chat.js +29 -29
  53. package/providers/_builtin/extension/cline/scripts/1.0/resolve_action.js +19 -19
  54. package/providers/_builtin/extension/cline/scripts/1.0/send_message.js +15 -15
  55. package/providers/_builtin/extension/cline/scripts/1.0/set_mode.js +4 -4
  56. package/providers/_builtin/extension/cline/scripts/1.0/set_model.js +4 -4
  57. package/providers/_builtin/extension/cline/scripts/1.0/switch_session.js +21 -21
  58. package/providers/_builtin/extension/codex/scripts/1.0/explore_controls_webview.js +2 -2
  59. package/providers/_builtin/extension/codex/scripts/1.0/list_models.js +1 -1
  60. package/providers/_builtin/extension/codex/scripts/1.0/list_modes.js +1 -1
  61. package/providers/_builtin/extension/codex/scripts/1.0/new_session.js +2 -2
  62. package/providers/_builtin/extension/codex/scripts/1.0/read_chat.js +6 -6
  63. package/providers/_builtin/extension/codex/scripts/1.0/resolve_action.js +3 -3
  64. package/providers/_builtin/extension/codex/scripts/1.0/set_model.js +1 -1
  65. package/providers/_builtin/ide/antigravity/scripts/1.0/focus_editor.js +2 -2
  66. package/providers/_builtin/ide/antigravity/scripts/1.0/list_chats.js +19 -19
  67. package/providers/_builtin/ide/antigravity/scripts/1.0/list_models.js +1 -1
  68. package/providers/_builtin/ide/antigravity/scripts/1.0/list_modes.js +1 -1
  69. package/providers/_builtin/ide/antigravity/scripts/1.0/new_session.js +17 -17
  70. package/providers/_builtin/ide/antigravity/scripts/1.0/read_chat.js +41 -41
  71. package/providers/_builtin/ide/antigravity/scripts/1.0/resolve_action.js +6 -6
  72. package/providers/_builtin/ide/antigravity/scripts/1.0/send_message.js +13 -13
  73. package/providers/_builtin/ide/antigravity/scripts/1.0/set_model.js +1 -1
  74. package/providers/_builtin/ide/antigravity/scripts/1.0/switch_session.js +13 -13
  75. package/providers/_builtin/ide/cursor/scripts/1.0/dismiss_notification.js +3 -3
  76. package/providers/_builtin/ide/cursor/scripts/1.0/focus_editor.js +1 -1
  77. package/providers/_builtin/ide/cursor/scripts/1.0/list_models.js +12 -12
  78. package/providers/_builtin/ide/cursor/scripts/1.0/list_modes.js +5 -5
  79. package/providers/_builtin/ide/cursor/scripts/1.0/list_notifications.js +2 -2
  80. package/providers/_builtin/ide/cursor/scripts/1.0/list_sessions.js +5 -5
  81. package/providers/_builtin/ide/cursor/scripts/1.0/new_session.js +1 -1
  82. package/providers/_builtin/ide/cursor/scripts/1.0/open_panel.js +3 -3
  83. package/providers/_builtin/ide/cursor/scripts/1.0/read_chat.js +9 -9
  84. package/providers/_builtin/ide/cursor/scripts/1.0/resolve_action.js +4 -4
  85. package/providers/_builtin/ide/cursor/scripts/1.0/send_message.js +5 -5
  86. package/providers/_builtin/ide/cursor/scripts/1.0/set_mode.js +4 -4
  87. package/providers/_builtin/ide/cursor/scripts/1.0/set_model.js +12 -12
  88. package/providers/_builtin/ide/cursor/scripts/1.0/switch_session.js +2 -2
  89. package/providers/_builtin/ide/kiro/scripts/1.0/focus_editor.js +2 -2
  90. package/providers/_builtin/ide/kiro/scripts/1.0/open_panel.js +7 -7
  91. package/providers/_builtin/ide/kiro/scripts/1.0/resolve_action.js +3 -3
  92. package/providers/_builtin/ide/kiro/scripts/1.0/send_message.js +4 -4
  93. package/providers/_builtin/ide/kiro/scripts/1.0/webview_list_sessions.js +2 -2
  94. package/providers/_builtin/ide/kiro/scripts/1.0/webview_new_session.js +5 -5
  95. package/providers/_builtin/ide/kiro/scripts/1.0/webview_read_chat.js +13 -13
  96. package/providers/_builtin/ide/kiro/scripts/1.0/webview_resolve_action.js +1 -1
  97. package/providers/_builtin/ide/kiro/scripts/1.0/webview_send_message.js +8 -8
  98. package/providers/_builtin/ide/kiro/scripts/1.0/webview_set_mode.js +1 -1
  99. package/providers/_builtin/ide/kiro/scripts/1.0/webview_set_model.js +1 -1
  100. package/providers/_builtin/ide/kiro/scripts/1.0/webview_switch_session.js +3 -3
  101. package/providers/_builtin/ide/pearai/.tmp/after_rich.json +157 -0
  102. package/providers/_builtin/ide/pearai/.tmp/after_rich_raw.json +161 -0
  103. package/providers/_builtin/ide/pearai/.tmp/after_switch.json +592 -0
  104. package/providers/_builtin/ide/pearai/.tmp/after_switch_raw.json +596 -0
  105. package/providers/_builtin/ide/pearai/.tmp/home_dump_raw.json +176 -0
  106. package/providers/_builtin/ide/pearai/.tmp/items_raw.json +778 -0
  107. package/providers/_builtin/ide/pearai/.tmp/webview_dump.json +157 -0
  108. package/providers/_builtin/ide/pearai/.tmp/webview_dump_raw.json +161 -0
  109. package/providers/_builtin/ide/pearai/provider.json +1 -3
  110. package/providers/_builtin/ide/pearai/scripts/1.0/focus_editor.js +12 -16
  111. package/providers/_builtin/ide/pearai/scripts/1.0/list_models.js +45 -0
  112. package/providers/_builtin/ide/pearai/scripts/1.0/list_modes.js +44 -0
  113. package/providers/_builtin/ide/pearai/scripts/1.0/list_sessions.js +82 -27
  114. package/providers/_builtin/ide/pearai/scripts/1.0/new_session.js +26 -43
  115. package/providers/_builtin/ide/pearai/scripts/1.0/open_panel.js +10 -33
  116. package/providers/_builtin/ide/pearai/scripts/1.0/read_chat.js +169 -0
  117. package/providers/_builtin/ide/pearai/scripts/1.0/resolve_action.js +47 -41
  118. package/providers/_builtin/ide/pearai/scripts/1.0/scripts.js +36 -7
  119. package/providers/_builtin/ide/pearai/scripts/1.0/send_message.js +97 -22
  120. package/providers/_builtin/ide/pearai/scripts/1.0/set_mode.js +51 -0
  121. package/providers/_builtin/ide/pearai/scripts/1.0/set_model.js +54 -0
  122. package/providers/_builtin/ide/pearai/scripts/1.0/switch_session.js +81 -0
  123. package/providers/_builtin/ide/trae/scripts/1.0/focus_editor.js +2 -2
  124. package/providers/_builtin/ide/trae/scripts/1.0/list_chats.js +2 -2
  125. package/providers/_builtin/ide/trae/scripts/1.0/new_session.js +4 -4
  126. package/providers/_builtin/ide/trae/scripts/1.0/open_panel.js +6 -6
  127. package/providers/_builtin/ide/trae/scripts/1.0/read_chat.js +13 -13
  128. package/providers/_builtin/ide/trae/scripts/1.0/resolve_action.js +3 -3
  129. package/providers/_builtin/ide/trae/scripts/1.0/send_message.js +11 -11
  130. package/providers/_builtin/ide/trae/scripts/1.0/switch_session.js +2 -2
  131. package/providers/_builtin/ide/windsurf/scripts/1.0/focus_editor.js +6 -6
  132. package/providers/_builtin/ide/windsurf/scripts/1.0/list_chats.js +4 -4
  133. package/providers/_builtin/ide/windsurf/scripts/1.0/list_sessions.js +6 -6
  134. package/providers/_builtin/ide/windsurf/scripts/1.0/new_session.js +9 -9
  135. package/providers/_builtin/ide/windsurf/scripts/1.0/open_panel.js +6 -6
  136. package/providers/_builtin/ide/windsurf/scripts/1.0/resolve_action.js +8 -8
  137. package/providers/_builtin/ide/windsurf/scripts/1.0/switch_session.js +8 -8
  138. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/focus_editor.js +6 -6
  139. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/list_chats.js +12 -12
  140. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/new_session.js +15 -15
  141. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/open_panel.js +10 -10
  142. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/read_chat.js +33 -33
  143. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/resolve_action.js +6 -6
  144. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/send_message.js +16 -16
  145. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/switch_session.js +7 -7
  146. package/providers/_builtin/registry.json +76 -3
  147. package/src/cli-adapters/provider-cli-adapter.ts +267 -251
  148. package/src/cli-adapters/terminal-screen.ts +268 -0
  149. package/src/commands/chat-commands.ts +41 -15
  150. package/src/commands/cli-manager.ts +4 -2
  151. package/src/daemon/dev-server.ts +245 -35
  152. package/src/providers/acp-provider-instance.ts +1 -1
  153. package/src/providers/cli-provider-instance.ts +21 -8
  154. package/src/providers/provider-loader.ts +3 -2
  155. package/providers/_builtin/ide/pearai/scripts/1.0/webview_list_models.js +0 -43
  156. package/providers/_builtin/ide/pearai/scripts/1.0/webview_list_modes.js +0 -35
  157. package/providers/_builtin/ide/pearai/scripts/1.0/webview_list_sessions.js +0 -62
  158. package/providers/_builtin/ide/pearai/scripts/1.0/webview_new_session.js +0 -49
  159. package/providers/_builtin/ide/pearai/scripts/1.0/webview_read_chat.js +0 -92
  160. package/providers/_builtin/ide/pearai/scripts/1.0/webview_resolve_action.js +0 -59
  161. package/providers/_builtin/ide/pearai/scripts/1.0/webview_send_message.js +0 -72
  162. package/providers/_builtin/ide/pearai/scripts/1.0/webview_set_mode.js +0 -36
  163. package/providers/_builtin/ide/pearai/scripts/1.0/webview_set_model.js +0 -36
  164. package/providers/_builtin/ide/pearai/scripts/1.0/webview_switch_session.js +0 -34
@@ -1,18 +1,17 @@
1
1
  /**
2
- * ProviderCliAdapter — generic CLI Adapter based on provider.js
2
+ * ProviderCliAdapter — Script-based CLI Adapter
3
3
  *
4
- * Replaces individual adapters (gemini-cli.ts, claude-cli.ts, codex-cli.ts).
5
- * Single engine driven by provider.js patterns, spawn, and cleanOutput.
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
- * provider.js contract:
8
- * type: string 'gemini-cli', 'claude-cli', 'codex-cli', ...
9
- * name: string 'Gemini CLI', 'Claude Code', ...
10
- * category: 'cli'
11
- * binary: string — binary name
12
- * spawn: { command, args, shell, env }
13
- * patterns: { prompt, generating, approval, ready } — prompt helps end startup splash-gate early; sendMessage does not wait for it
14
- * timeouts?: { idleFinish, generatingIdle, maxResponse, approvalCooldown, outputSettle, ... }
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 idle/approval (default 300ms) */
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 stores regex placeholders as `{}` — JSON.parse leaves plain objects,
172
- * so `.test` is missing and the PTY handler throws on first output. Coerce to RegExp
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, fallbacks: RegExp[]): RegExp[] {
189
- if (!Array.isArray(raw)) return [...fallbacks];
190
- const parsed = raw.map(parsePatternEntry).filter((r): r is RegExp => r != null);
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
- export function normalizeCliProviderForRuntime(raw: any): CliProviderModule {
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
- prompt: coercePatternArray(patterns.prompt, FALLBACK_PROMPT),
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 = ''; // snapshot of recentOutputBuffer at settle time
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 (provider defaults + overrides)
300
+ // Resolved timeouts
296
301
  private readonly timeouts: Required<NonNullable<CliProviderModule['timeouts']>>;
297
302
 
298
- // Provider approval key mapping (e.g. { 0: '1', 1: '2', 2: '3' }) — loaded from provider.json
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 = normalizeCliProviderForRuntime(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 state machine ────────────────────────────
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
- // Server log forwarding
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
- // Rolling buffer (recent 1000 chars)
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
- // ─── Startup window: first-run dialogs + avoid false generating during splash (sendMessage is not gated on prompt text)
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
- // ─── 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
- }
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
- if (this.isWaitingForResponse) {
510
- this.responseBuffer += cleanData;
511
- if (this.idleTimeout) clearTimeout(this.idleTimeout);
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
- // ─── Phase 4: waiting_approval — accumulate transition buffer
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 { patterns } = this.provider;
553
- const buf = this.settledBuffer;
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
- }
530
+ const tail = this.settledBuffer;
531
+ const scriptStatus = this.runDetectStatus(tail);
532
+ if (!scriptStatus) return;
533
+
534
+ const prevStatus = this.currentStatus;
578
535
 
579
- // ─── check for approval on stabilised buffer
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', 'approval_pattern');
589
- this.recentOutputBuffer = '';
590
- this.approvalTransitionBuffer = '';
591
- this.activeModal = {
592
- message: ctxLines.slice(-5).join(' ').slice(0, 200) || 'Approval required',
593
- buttons: this.cliType === 'claude-cli'
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-exiting waiting_approval`);
550
+ LOG.warn('CLI', `[${this.cliType}] Approval timeout — auto-clearing`);
602
551
  this.activeModal = null;
603
552
  this.lastApprovalResolvedAt = Date.now();
604
- this.recentOutputBuffer = '';
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
- // ─── check for prompt (idle) on stabilised buffer
617
- if (this.isWaitingForResponse) {
618
- // Only look at the trailing portion — prompt appears at the very end
619
- const trailingLines = buf.split('\n').slice(-3).join('\n');
620
- if (patterns.prompt.some(p => p.test(trailingLines)) && !hasApproval) {
621
- this.finishResponse();
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
- // ─── Public API (CliAdapter interface) ──────────
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
- const partial = this.provider.cleanOutput(this.responseBuffer);
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
- // Suppress output for 300ms after resize — PTY redraws the screen and
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
- // Buffers (longer tails here than in periodic logs — for matching provider.json patterns)
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
- responseBuffer: this.responseBuffer.slice(-500),
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
- // Provider patterns (serialized)
798
- patterns: {
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
  }