@adhdev/daemon-core 0.5.3

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 (217) hide show
  1. package/dist/index.d.ts +2662 -0
  2. package/dist/index.js +11341 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +48 -0
  5. package/providers/_builtin/.github/workflows/generate-registry.yml +57 -0
  6. package/providers/_builtin/COMPATIBILITY.md +217 -0
  7. package/providers/_builtin/CONTRIBUTING.md +200 -0
  8. package/providers/_builtin/README.md +119 -0
  9. package/providers/_builtin/_helpers/index.js +188 -0
  10. package/providers/_builtin/acp/agentpool/provider.json +54 -0
  11. package/providers/_builtin/acp/amp/provider.json +52 -0
  12. package/providers/_builtin/acp/auggie/provider.json +57 -0
  13. package/providers/_builtin/acp/autodev/provider.json +54 -0
  14. package/providers/_builtin/acp/autohand/provider.json +52 -0
  15. package/providers/_builtin/acp/blackbox-ai/provider.json +54 -0
  16. package/providers/_builtin/acp/claude-agent/provider.json +57 -0
  17. package/providers/_builtin/acp/cline-acp/provider.json +54 -0
  18. package/providers/_builtin/acp/codebuddy/provider.json +54 -0
  19. package/providers/_builtin/acp/codex-cli/provider.json +57 -0
  20. package/providers/_builtin/acp/corust-agent/provider.json +52 -0
  21. package/providers/_builtin/acp/crow-cli/provider.json +54 -0
  22. package/providers/_builtin/acp/cursor-acp/provider.json +54 -0
  23. package/providers/_builtin/acp/deepagents/provider.json +52 -0
  24. package/providers/_builtin/acp/dimcode/provider.json +54 -0
  25. package/providers/_builtin/acp/docker-cagent/provider.json +57 -0
  26. package/providers/_builtin/acp/factory-droid/provider.json +60 -0
  27. package/providers/_builtin/acp/fast-agent/provider.json +52 -0
  28. package/providers/_builtin/acp/gemini-cli/provider.json +114 -0
  29. package/providers/_builtin/acp/github-copilot/provider.json +54 -0
  30. package/providers/_builtin/acp/goose/provider.json +57 -0
  31. package/providers/_builtin/acp/junie/provider.json +52 -0
  32. package/providers/_builtin/acp/kilo/provider.json +54 -0
  33. package/providers/_builtin/acp/kimi-cli/provider.json +57 -0
  34. package/providers/_builtin/acp/minion-code/provider.json +52 -0
  35. package/providers/_builtin/acp/mistral-vibe/provider.json +57 -0
  36. package/providers/_builtin/acp/nova/provider.json +54 -0
  37. package/providers/_builtin/acp/openclaw/provider.json +54 -0
  38. package/providers/_builtin/acp/opencode/provider.json +52 -0
  39. package/providers/_builtin/acp/openhands/provider.json +54 -0
  40. package/providers/_builtin/acp/pi-acp/provider.json +52 -0
  41. package/providers/_builtin/acp/qoder/provider.json +54 -0
  42. package/providers/_builtin/acp/qwen-code/provider.json +60 -0
  43. package/providers/_builtin/acp/stakpak/provider.json +54 -0
  44. package/providers/_builtin/acp/vtcode/provider.json +54 -0
  45. package/providers/_builtin/cli/claude-cli/provider.json +100 -0
  46. package/providers/_builtin/cli/codex-cli/provider.json +89 -0
  47. package/providers/_builtin/cli/gemini-cli/provider.json +93 -0
  48. package/providers/_builtin/docs/CDP_SELECTOR_GUIDE.md +370 -0
  49. package/providers/_builtin/docs/PROVIDER_GUIDE.md +916 -0
  50. package/providers/_builtin/extension/cline/provider.json +35 -0
  51. package/providers/_builtin/extension/cline/scripts/focus_editor.js +48 -0
  52. package/providers/_builtin/extension/cline/scripts/list_chats.js +100 -0
  53. package/providers/_builtin/extension/cline/scripts/list_models.js +43 -0
  54. package/providers/_builtin/extension/cline/scripts/list_modes.js +35 -0
  55. package/providers/_builtin/extension/cline/scripts/new_session.js +85 -0
  56. package/providers/_builtin/extension/cline/scripts/open_panel.js +25 -0
  57. package/providers/_builtin/extension/cline/scripts/read_chat.js +257 -0
  58. package/providers/_builtin/extension/cline/scripts/resolve_action.js +83 -0
  59. package/providers/_builtin/extension/cline/scripts/send_message.js +95 -0
  60. package/providers/_builtin/extension/cline/scripts/set_mode.js +36 -0
  61. package/providers/_builtin/extension/cline/scripts/set_model.js +36 -0
  62. package/providers/_builtin/extension/cline/scripts/switch_session.js +206 -0
  63. package/providers/_builtin/extension/cline/scripts.js +73 -0
  64. package/providers/_builtin/extension/roo-code/provider.json +35 -0
  65. package/providers/_builtin/extension/roo-code/scripts.js +659 -0
  66. package/providers/_builtin/ide/antigravity/provider.json +68 -0
  67. package/providers/_builtin/ide/antigravity/scripts/1.106/focus_editor.js +20 -0
  68. package/providers/_builtin/ide/antigravity/scripts/1.106/list_chats.js +137 -0
  69. package/providers/_builtin/ide/antigravity/scripts/1.106/list_models.js +38 -0
  70. package/providers/_builtin/ide/antigravity/scripts/1.106/list_modes.js +48 -0
  71. package/providers/_builtin/ide/antigravity/scripts/1.106/new_session.js +75 -0
  72. package/providers/_builtin/ide/antigravity/scripts/1.106/read_chat.js +262 -0
  73. package/providers/_builtin/ide/antigravity/scripts/1.106/resolve_action.js +68 -0
  74. package/providers/_builtin/ide/antigravity/scripts/1.106/scripts.js +57 -0
  75. package/providers/_builtin/ide/antigravity/scripts/1.106/send_message.js +56 -0
  76. package/providers/_builtin/ide/antigravity/scripts/1.106/set_mode.js +34 -0
  77. package/providers/_builtin/ide/antigravity/scripts/1.106/set_model.js +47 -0
  78. package/providers/_builtin/ide/antigravity/scripts/1.106/switch_session.js +114 -0
  79. package/providers/_builtin/ide/antigravity/scripts/1.107/focus_editor.js +20 -0
  80. package/providers/_builtin/ide/antigravity/scripts/1.107/list_chats.js +137 -0
  81. package/providers/_builtin/ide/antigravity/scripts/1.107/list_models.js +61 -0
  82. package/providers/_builtin/ide/antigravity/scripts/1.107/list_modes.js +72 -0
  83. package/providers/_builtin/ide/antigravity/scripts/1.107/new_session.js +75 -0
  84. package/providers/_builtin/ide/antigravity/scripts/1.107/read_chat.js +262 -0
  85. package/providers/_builtin/ide/antigravity/scripts/1.107/resolve_action.js +68 -0
  86. package/providers/_builtin/ide/antigravity/scripts/1.107/scripts.js +67 -0
  87. package/providers/_builtin/ide/antigravity/scripts/1.107/send_message.js +56 -0
  88. package/providers/_builtin/ide/antigravity/scripts/1.107/set_mode.js +67 -0
  89. package/providers/_builtin/ide/antigravity/scripts/1.107/set_model.js +72 -0
  90. package/providers/_builtin/ide/antigravity/scripts/1.107/switch_session.js +114 -0
  91. package/providers/_builtin/ide/cursor/provider.json +70 -0
  92. package/providers/_builtin/ide/cursor/scripts/0.49/dismiss_notification.js +30 -0
  93. package/providers/_builtin/ide/cursor/scripts/0.49/focus_editor.js +13 -0
  94. package/providers/_builtin/ide/cursor/scripts/0.49/list_models.js +78 -0
  95. package/providers/_builtin/ide/cursor/scripts/0.49/list_modes.js +40 -0
  96. package/providers/_builtin/ide/cursor/scripts/0.49/list_notifications.js +23 -0
  97. package/providers/_builtin/ide/cursor/scripts/0.49/list_sessions.js +42 -0
  98. package/providers/_builtin/ide/cursor/scripts/0.49/new_session.js +20 -0
  99. package/providers/_builtin/ide/cursor/scripts/0.49/open_panel.js +23 -0
  100. package/providers/_builtin/ide/cursor/scripts/0.49/read_chat.js +75 -0
  101. package/providers/_builtin/ide/cursor/scripts/0.49/resolve_action.js +19 -0
  102. package/providers/_builtin/ide/cursor/scripts/0.49/scripts.js +78 -0
  103. package/providers/_builtin/ide/cursor/scripts/0.49/send_message.js +23 -0
  104. package/providers/_builtin/ide/cursor/scripts/0.49/set_mode.js +38 -0
  105. package/providers/_builtin/ide/cursor/scripts/0.49/set_model.js +81 -0
  106. package/providers/_builtin/ide/cursor/scripts/0.49/switch_session.js +28 -0
  107. package/providers/_builtin/ide/kiro/provider.json +67 -0
  108. package/providers/_builtin/ide/kiro/scripts/focus_editor.js +20 -0
  109. package/providers/_builtin/ide/kiro/scripts/open_panel.js +47 -0
  110. package/providers/_builtin/ide/kiro/scripts/resolve_action.js +54 -0
  111. package/providers/_builtin/ide/kiro/scripts/send_message.js +29 -0
  112. package/providers/_builtin/ide/kiro/scripts/webview_list_models.js +39 -0
  113. package/providers/_builtin/ide/kiro/scripts/webview_list_modes.js +39 -0
  114. package/providers/_builtin/ide/kiro/scripts/webview_list_sessions.js +21 -0
  115. package/providers/_builtin/ide/kiro/scripts/webview_new_session.js +34 -0
  116. package/providers/_builtin/ide/kiro/scripts/webview_read_chat.js +68 -0
  117. package/providers/_builtin/ide/kiro/scripts/webview_send_message.js +72 -0
  118. package/providers/_builtin/ide/kiro/scripts/webview_set_mode.js +15 -0
  119. package/providers/_builtin/ide/kiro/scripts/webview_set_model.js +15 -0
  120. package/providers/_builtin/ide/kiro/scripts/webview_switch_session.js +26 -0
  121. package/providers/_builtin/ide/kiro/scripts.js +62 -0
  122. package/providers/_builtin/ide/pearai/provider.json +67 -0
  123. package/providers/_builtin/ide/pearai/scripts/focus_editor.js +20 -0
  124. package/providers/_builtin/ide/pearai/scripts/list_sessions.js +38 -0
  125. package/providers/_builtin/ide/pearai/scripts/new_session.js +55 -0
  126. package/providers/_builtin/ide/pearai/scripts/open_panel.js +46 -0
  127. package/providers/_builtin/ide/pearai/scripts/resolve_action.js +54 -0
  128. package/providers/_builtin/ide/pearai/scripts/send_message.js +29 -0
  129. package/providers/_builtin/ide/pearai/scripts/webview_list_models.js +43 -0
  130. package/providers/_builtin/ide/pearai/scripts/webview_list_modes.js +35 -0
  131. package/providers/_builtin/ide/pearai/scripts/webview_list_sessions.js +62 -0
  132. package/providers/_builtin/ide/pearai/scripts/webview_new_session.js +49 -0
  133. package/providers/_builtin/ide/pearai/scripts/webview_read_chat.js +92 -0
  134. package/providers/_builtin/ide/pearai/scripts/webview_resolve_action.js +59 -0
  135. package/providers/_builtin/ide/pearai/scripts/webview_send_message.js +72 -0
  136. package/providers/_builtin/ide/pearai/scripts/webview_set_mode.js +36 -0
  137. package/providers/_builtin/ide/pearai/scripts/webview_set_model.js +36 -0
  138. package/providers/_builtin/ide/pearai/scripts/webview_switch_session.js +34 -0
  139. package/providers/_builtin/ide/pearai/scripts.js +74 -0
  140. package/providers/_builtin/ide/trae/provider.json +66 -0
  141. package/providers/_builtin/ide/trae/scripts/focus_editor.js +20 -0
  142. package/providers/_builtin/ide/trae/scripts/list_chats.js +24 -0
  143. package/providers/_builtin/ide/trae/scripts/list_models.js +39 -0
  144. package/providers/_builtin/ide/trae/scripts/list_modes.js +39 -0
  145. package/providers/_builtin/ide/trae/scripts/new_session.js +30 -0
  146. package/providers/_builtin/ide/trae/scripts/open_panel.js +44 -0
  147. package/providers/_builtin/ide/trae/scripts/read_chat.js +113 -0
  148. package/providers/_builtin/ide/trae/scripts/resolve_action.js +54 -0
  149. package/providers/_builtin/ide/trae/scripts/send_message.js +69 -0
  150. package/providers/_builtin/ide/trae/scripts/set_mode.js +15 -0
  151. package/providers/_builtin/ide/trae/scripts/set_model.js +15 -0
  152. package/providers/_builtin/ide/trae/scripts/switch_session.js +23 -0
  153. package/providers/_builtin/ide/trae/scripts.js +57 -0
  154. package/providers/_builtin/ide/vscode/provider.json +64 -0
  155. package/providers/_builtin/ide/vscode-insiders/provider.json +62 -0
  156. package/providers/_builtin/ide/vscodium/provider.json +63 -0
  157. package/providers/_builtin/ide/windsurf/provider.json +53 -0
  158. package/providers/_builtin/ide/windsurf/scripts/focus_editor.js +30 -0
  159. package/providers/_builtin/ide/windsurf/scripts/list_chats.js +117 -0
  160. package/providers/_builtin/ide/windsurf/scripts/list_models.js +39 -0
  161. package/providers/_builtin/ide/windsurf/scripts/list_modes.js +39 -0
  162. package/providers/_builtin/ide/windsurf/scripts/new_session.js +69 -0
  163. package/providers/_builtin/ide/windsurf/scripts/open_panel.js +58 -0
  164. package/providers/_builtin/ide/windsurf/scripts/read_chat.js +297 -0
  165. package/providers/_builtin/ide/windsurf/scripts/resolve_action.js +68 -0
  166. package/providers/_builtin/ide/windsurf/scripts/send_message.js +87 -0
  167. package/providers/_builtin/ide/windsurf/scripts/set_mode.js +15 -0
  168. package/providers/_builtin/ide/windsurf/scripts/set_model.js +15 -0
  169. package/providers/_builtin/ide/windsurf/scripts/switch_session.js +58 -0
  170. package/providers/_builtin/ide/windsurf/scripts.js +57 -0
  171. package/providers/_builtin/registry.json +266 -0
  172. package/providers/_builtin/validate.js +156 -0
  173. package/src/agent-stream/index.ts +6 -0
  174. package/src/agent-stream/manager.ts +286 -0
  175. package/src/agent-stream/poller.ts +154 -0
  176. package/src/agent-stream/provider-adapter.ts +138 -0
  177. package/src/agent-stream/types.ts +61 -0
  178. package/src/boot/daemon-lifecycle.ts +252 -0
  179. package/src/cdp/devtools.ts +335 -0
  180. package/src/cdp/initializer.ts +191 -0
  181. package/src/cdp/manager.ts +897 -0
  182. package/src/cdp/scanner.ts +185 -0
  183. package/src/cdp/setup.ts +150 -0
  184. package/src/cli-adapter-types.ts +25 -0
  185. package/src/cli-adapters/provider-cli-adapter.ts +448 -0
  186. package/src/commands/cdp-commands.ts +208 -0
  187. package/src/commands/chat-commands.ts +675 -0
  188. package/src/commands/cli-manager.ts +353 -0
  189. package/src/commands/handler.ts +328 -0
  190. package/src/commands/router.ts +258 -0
  191. package/src/commands/stream-commands.ts +325 -0
  192. package/src/config/chat-history.ts +211 -0
  193. package/src/config/config.ts +219 -0
  194. package/src/daemon/dev-server.ts +2378 -0
  195. package/src/daemon/scaffold-template.ts +394 -0
  196. package/src/daemon-core.ts +50 -0
  197. package/src/detection/cli-detector.ts +89 -0
  198. package/src/detection/ide-detector.ts +157 -0
  199. package/src/index.ts +103 -0
  200. package/src/installer.ts +263 -0
  201. package/src/ipc-protocol.ts +133 -0
  202. package/src/launch.ts +433 -0
  203. package/src/logging/command-log.ts +180 -0
  204. package/src/logging/logger.ts +316 -0
  205. package/src/providers/acp-provider-instance.ts +1140 -0
  206. package/src/providers/cli-provider-instance.ts +207 -0
  207. package/src/providers/contracts.ts +524 -0
  208. package/src/providers/extension-provider-instance.ts +156 -0
  209. package/src/providers/ide-provider-instance.ts +377 -0
  210. package/src/providers/index.ts +18 -0
  211. package/src/providers/provider-instance-manager.ts +182 -0
  212. package/src/providers/provider-instance.ts +112 -0
  213. package/src/providers/provider-loader.ts +1031 -0
  214. package/src/providers/status-monitor.ts +125 -0
  215. package/src/providers/version-archive.ts +266 -0
  216. package/src/status/reporter.ts +294 -0
  217. package/src/types.ts +206 -0
@@ -0,0 +1,448 @@
1
+ /**
2
+ * ProviderCliAdapter — generic CLI Adapter based on provider.js
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.
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 }
14
+ * timeouts?: { idleFinish, generatingIdle, maxResponse, approvalCooldown, ... }
15
+ * cleanOutput(raw, lastUserInput): string
16
+ */
17
+
18
+ import * as os from 'os';
19
+ import { execSync } from 'child_process';
20
+ import type { CliAdapter } from '../cli-adapter-types.js';
21
+ import { LOG } from '../logging/logger.js';
22
+
23
+ let pty: any;
24
+ try {
25
+ pty = require('node-pty');
26
+ } catch {
27
+ LOG.error('CLI', '[ProviderCliAdapter] node-pty not found. Install: npm install node-pty@1.0.0');
28
+ }
29
+
30
+ export interface CliChatMessage {
31
+ role: 'user' | 'assistant';
32
+ content: string;
33
+ timestamp?: number;
34
+ }
35
+
36
+ export interface CliSessionStatus {
37
+ status: 'idle' | 'generating' | 'waiting_approval' | 'error' | 'stopped' | 'starting';
38
+ messages: CliChatMessage[];
39
+ workingDir: string;
40
+ activeModal: { message: string; buttons: string[] } | null;
41
+ }
42
+
43
+ export interface CliProviderModule {
44
+ type: string;
45
+ name: string;
46
+ category: 'cli';
47
+ binary: string;
48
+ spawn: {
49
+ command: string;
50
+ args: string[];
51
+ shell: boolean;
52
+ env: Record<string, string>;
53
+ };
54
+ patterns: {
55
+ prompt: RegExp[];
56
+ generating: RegExp[];
57
+ approval: RegExp[];
58
+ ready: RegExp[];
59
+ };
60
+ timeouts?: {
61
+ /** PTY output batch transmit interval (default 50ms) */
62
+ ptyFlush?: number;
63
+ /** Wait for startup dialog auto-proceed (default 300ms) */
64
+ dialogAccept?: number;
65
+ /** Approval detect cooldown (default 2000ms) */
66
+ approvalCooldown?: number;
67
+ /** Check for completion on no-response during generating (default 6000ms) */
68
+ generatingIdle?: number;
69
+ /** Check for completion on no-response (default 5000ms) */
70
+ idleFinish?: number;
71
+ /** Max response wait (default 300000ms = 5min) */
72
+ maxResponse?: number;
73
+ /** shutdown after kill wait (default 1000ms) */
74
+ shutdownGrace?: number;
75
+ };
76
+ cleanOutput(raw: string, lastUserInput?: string): string;
77
+ }
78
+
79
+ function stripAnsi(str: string): string {
80
+ // eslint-disable-next-line no-control-regex
81
+ return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '')
82
+ .replace(/\x1B\][^\x07]*\x07/g, '')
83
+ .replace(/\x1B\][^\x1B]*\x1B\\/g, '');
84
+ }
85
+
86
+ function findBinary(name: string): string {
87
+ const isWin = os.platform() === 'win32';
88
+ try {
89
+ const cmd = isWin ? `where ${name}` : `which ${name}`;
90
+ return execSync(cmd, { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim().split('\n')[0].trim();
91
+ } catch {
92
+ return isWin ? `${name}.cmd` : name;
93
+ }
94
+ }
95
+
96
+ export class ProviderCliAdapter implements CliAdapter {
97
+ readonly cliType: string;
98
+ readonly cliName: string;
99
+ public workingDir: string;
100
+
101
+ private provider: CliProviderModule;
102
+ private ptyProcess: any = null;
103
+ private messages: CliChatMessage[] = [];
104
+ private currentStatus: CliSessionStatus['status'] = 'starting';
105
+ private onStatusChange: (() => void) | null = null;
106
+
107
+ private responseBuffer = '';
108
+ private recentOutputBuffer = '';
109
+ private isWaitingForResponse = false;
110
+ private activeModal: { message: string; buttons: string[] } | null = null;
111
+ private responseTimeout: NodeJS.Timeout | null = null;
112
+ private idleTimeout: NodeJS.Timeout | null = null;
113
+ private ready = false;
114
+ private startupBuffer = '';
115
+
116
+ // PTY I/O
117
+ private onPtyDataCallback: ((data: string) => void) | null = null;
118
+ private ptyOutputBuffer = '';
119
+ private ptyOutputFlushTimer: NodeJS.Timeout | null = null;
120
+
121
+ // Server log forwarding
122
+ private serverConn: any = null;
123
+ private logBuffer: { message: string; level: string }[] = [];
124
+
125
+ // Approval cooldown
126
+ private lastApprovalResolvedAt: number = 0;
127
+
128
+ // Resolved timeouts (provider defaults + overrides)
129
+ private readonly timeouts: Required<NonNullable<CliProviderModule['timeouts']>>;
130
+
131
+ constructor(provider: CliProviderModule, workingDir: string, private extraArgs: string[] = []) {
132
+ this.provider = provider;
133
+ this.cliType = provider.type;
134
+ this.cliName = provider.name;
135
+ this.workingDir = workingDir.startsWith('~')
136
+ ? workingDir.replace(/^~/, os.homedir())
137
+ : workingDir;
138
+
139
+ // Apply timeout overrides from Provider
140
+ const t = provider.timeouts || {};
141
+ this.timeouts = {
142
+ ptyFlush: t.ptyFlush ?? 50,
143
+ dialogAccept: t.dialogAccept ?? 300,
144
+ approvalCooldown: t.approvalCooldown ?? 2000,
145
+ generatingIdle: t.generatingIdle ?? 6000,
146
+ idleFinish: t.idleFinish ?? 5000,
147
+ maxResponse: t.maxResponse ?? 300000,
148
+ shutdownGrace: t.shutdownGrace ?? 1000,
149
+ };
150
+ }
151
+
152
+ // ─── Lifecycle ─────────────────────────────────
153
+
154
+ setServerConn(serverConn: any): void {
155
+ this.serverConn = serverConn;
156
+ if (this.serverConn && this.logBuffer.length > 0) {
157
+ this.logBuffer.forEach(log => this.serverConn.sendMessage('log', log));
158
+ this.logBuffer = [];
159
+ }
160
+ }
161
+
162
+ setOnStatusChange(callback: () => void): void {
163
+ this.onStatusChange = callback;
164
+ }
165
+
166
+ setOnPtyData(callback: (data: string) => void): void {
167
+ this.onPtyDataCallback = callback;
168
+ }
169
+
170
+ async spawn(): Promise<void> {
171
+ if (this.ptyProcess) return;
172
+ if (!pty) throw new Error('node-pty is not installed');
173
+
174
+ const { spawn: spawnConfig } = this.provider;
175
+ const binaryPath = findBinary(spawnConfig.command);
176
+ const isWin = os.platform() === 'win32';
177
+ const allArgs = [...spawnConfig.args, ...this.extraArgs];
178
+
179
+ LOG.info('CLI', `[${this.cliType}] Spawning in ${this.workingDir}`);
180
+
181
+ let shellCmd: string;
182
+ let shellArgs: string[];
183
+
184
+ if (spawnConfig.shell) {
185
+ // Execute via shell (for gemini etc npm shim compat)
186
+ shellCmd = isWin ? 'cmd.exe' : (process.env.SHELL || '/bin/zsh');
187
+ const fullCmd = [binaryPath, ...allArgs].join(' ');
188
+ shellArgs = isWin ? ['/c', fullCmd] : ['-l', '-c', fullCmd];
189
+ } else {
190
+ shellCmd = binaryPath;
191
+ shellArgs = allArgs;
192
+ }
193
+
194
+ this.ptyProcess = pty.spawn(shellCmd, shellArgs, {
195
+ name: 'xterm-256color',
196
+ cols: 120,
197
+ rows: 40,
198
+ cwd: this.workingDir,
199
+ env: {
200
+ ...process.env,
201
+ ...spawnConfig.env,
202
+ } as Record<string, string>,
203
+ });
204
+
205
+ this.ptyProcess.onData((data: string) => {
206
+ this.handleOutput(data);
207
+ // PTY output batch transmit (50ms)
208
+ if (this.onPtyDataCallback) {
209
+ this.ptyOutputBuffer += data;
210
+ if (!this.ptyOutputFlushTimer) {
211
+ this.ptyOutputFlushTimer = setTimeout(() => {
212
+ if (this.ptyOutputBuffer && this.onPtyDataCallback) {
213
+ this.onPtyDataCallback(this.ptyOutputBuffer);
214
+ }
215
+ this.ptyOutputBuffer = '';
216
+ this.ptyOutputFlushTimer = null;
217
+ }, this.timeouts.ptyFlush);
218
+ }
219
+ }
220
+ });
221
+
222
+ this.ptyProcess.onExit(({ exitCode }: { exitCode: number }) => {
223
+ LOG.info('CLI', `[${this.cliType}] Exit code ${exitCode}`);
224
+ this.ptyProcess = null;
225
+ this.currentStatus = 'stopped';
226
+ this.ready = false;
227
+ this.onStatusChange?.();
228
+ });
229
+
230
+ this.currentStatus = 'starting';
231
+ this.onStatusChange?.();
232
+ }
233
+
234
+ // ─── Output state machine ────────────────────────────
235
+
236
+ private handleOutput(rawData: string): void {
237
+ const cleanData = stripAnsi(rawData);
238
+ const { patterns } = this.provider;
239
+
240
+ // Server log forwarding
241
+ if (cleanData.trim()) {
242
+ if (this.serverConn) {
243
+ this.serverConn.sendMessage('log', { message: cleanData.trim(), level: 'info' });
244
+ } else {
245
+ this.logBuffer.push({ message: cleanData.trim(), level: 'info' });
246
+ }
247
+ }
248
+
249
+ // Rolling buffer (recent 1000 chars)
250
+ this.recentOutputBuffer = (this.recentOutputBuffer + cleanData).slice(-1000);
251
+
252
+ // ─── Phase 1: Startup — ready status wait
253
+ if (!this.ready) {
254
+ this.startupBuffer += cleanData;
255
+
256
+ // Startup dialog auto-proceed (Enter)
257
+ const dialogPatterns = [
258
+ /Do you want to connect/i,
259
+ /Do you trust the files/i,
260
+ ];
261
+ if (dialogPatterns.some(p => p.test(this.startupBuffer))) {
262
+ setTimeout(() => this.ptyProcess?.write('\r'), this.timeouts.dialogAccept);
263
+ this.startupBuffer = '';
264
+ return;
265
+ }
266
+
267
+ // Prompt → ready
268
+ if (patterns.prompt.some(p => p.test(this.startupBuffer))) {
269
+ this.ready = true;
270
+ this.currentStatus = 'idle';
271
+ LOG.info('CLI', `[${this.cliType}] ✓ Ready`);
272
+ this.onStatusChange?.();
273
+ }
274
+ return;
275
+ }
276
+
277
+ // ─── Phase 2: Approval detect
278
+ const hasApproval = patterns.approval.some(p => p.test(this.recentOutputBuffer));
279
+ if (hasApproval && this.currentStatus !== 'waiting_approval') {
280
+ if (this.lastApprovalResolvedAt && (Date.now() - this.lastApprovalResolvedAt) < this.timeouts.approvalCooldown) return;
281
+
282
+ this.isWaitingForResponse = true;
283
+ this.currentStatus = 'waiting_approval';
284
+ this.recentOutputBuffer = '';
285
+ const ctxLines = cleanData.split('\n').map(l => l.trim()).filter(l => l && !/^[─═╭╮╰╯│]+$/.test(l));
286
+ this.activeModal = {
287
+ message: ctxLines.slice(-5).join(' ').slice(0, 200) || 'Approval required',
288
+ buttons: ['Allow once', 'Always allow', 'Deny'],
289
+ };
290
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
291
+ this.onStatusChange?.();
292
+ return;
293
+ }
294
+
295
+ // ─── Phase 3: Approval release
296
+ if (this.currentStatus === 'waiting_approval') {
297
+ const genResume = patterns.generating.some(p => p.test(cleanData));
298
+ const promptResume = patterns.prompt.some(p => p.test(cleanData));
299
+ if (genResume) {
300
+ this.currentStatus = 'generating';
301
+ this.activeModal = null;
302
+ this.recentOutputBuffer = '';
303
+ this.lastApprovalResolvedAt = Date.now();
304
+ this.onStatusChange?.();
305
+ } else if (promptResume) {
306
+ this.activeModal = null;
307
+ this.recentOutputBuffer = '';
308
+ this.lastApprovalResolvedAt = Date.now();
309
+ this.finishResponse();
310
+ }
311
+ return;
312
+ }
313
+
314
+ // ─── Phase 4: autonomous generation detection (generating starts without sendMessage)
315
+ if (!this.isWaitingForResponse) {
316
+ if (patterns.generating.some(p => p.test(cleanData))) {
317
+ this.isWaitingForResponse = true;
318
+ this.responseBuffer = '';
319
+ this.currentStatus = 'generating';
320
+ this.onStatusChange?.();
321
+ }
322
+ }
323
+
324
+ // ─── Phase 5: response collect
325
+ if (this.isWaitingForResponse) {
326
+ this.responseBuffer += cleanData;
327
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
328
+
329
+ const stillGenerating = patterns.generating.some(p => p.test(cleanData));
330
+ if (stillGenerating) {
331
+ this.currentStatus = 'generating';
332
+ this.idleTimeout = setTimeout(() => {
333
+ if (this.isWaitingForResponse) this.finishResponse();
334
+ }, this.timeouts.generatingIdle);
335
+ this.onStatusChange?.();
336
+ return;
337
+ }
338
+
339
+ // Prompt → response complete
340
+ if (patterns.prompt.some(p => p.test(this.responseBuffer))) {
341
+ this.finishResponse();
342
+ } else {
343
+ this.idleTimeout = setTimeout(() => {
344
+ if (this.isWaitingForResponse && this.responseBuffer.trim()) {
345
+ this.finishResponse();
346
+ }
347
+ }, this.timeouts.idleFinish);
348
+ }
349
+ this.onStatusChange?.();
350
+ }
351
+ }
352
+
353
+ private finishResponse(): void {
354
+ if (this.responseTimeout) { clearTimeout(this.responseTimeout); this.responseTimeout = null; }
355
+ if (this.idleTimeout) { clearTimeout(this.idleTimeout); this.idleTimeout = null; }
356
+
357
+ const lastUserText = this.messages.filter(m => m.role === 'user').pop()?.content;
358
+ let response = this.provider.cleanOutput(this.responseBuffer, lastUserText);
359
+
360
+ // Remove user input echo
361
+ if (lastUserText && response) {
362
+ const userTrimmed = lastUserText.trim();
363
+ response = response.split('\n')
364
+ .filter(l => l.trim() !== userTrimmed)
365
+ .join('\n').trim();
366
+ }
367
+
368
+ if (response) {
369
+ this.messages.push({ role: 'assistant', content: response, timestamp: Date.now() });
370
+ if (this.messages.length > 200) this.messages = this.messages.slice(-200);
371
+ LOG.info('CLI', `[${this.cliType}] Response (${response.length} chars)`);
372
+ }
373
+
374
+ this.responseBuffer = '';
375
+ this.isWaitingForResponse = false;
376
+ this.activeModal = null;
377
+ this.currentStatus = 'idle';
378
+ this.onStatusChange?.();
379
+ }
380
+
381
+ // ─── Public API (CliAdapter interface) ──────────
382
+
383
+ getStatus(): CliSessionStatus {
384
+ return {
385
+ status: this.currentStatus,
386
+ messages: [...this.messages],
387
+ workingDir: this.workingDir,
388
+ activeModal: this.activeModal,
389
+ };
390
+ }
391
+
392
+ async sendMessage(text: string): Promise<void> {
393
+ if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
394
+ if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
395
+ if (this.isWaitingForResponse) return;
396
+
397
+ this.messages.push({ role: 'user', content: text, timestamp: Date.now() });
398
+ this.isWaitingForResponse = true;
399
+ this.responseBuffer = '';
400
+ this.currentStatus = 'generating';
401
+ this.onStatusChange?.();
402
+
403
+ this.ptyProcess.write(text + '\r');
404
+
405
+ this.responseTimeout = setTimeout(() => {
406
+ if (this.isWaitingForResponse) this.finishResponse();
407
+ }, this.timeouts.maxResponse);
408
+ }
409
+
410
+ getPartialResponse(): string {
411
+ if (!this.isWaitingForResponse) return '';
412
+ const partial = this.provider.cleanOutput(this.responseBuffer);
413
+ return partial || (this.isWaitingForResponse ? '(generating...)' : '');
414
+ }
415
+
416
+ cancel(): void { this.shutdown(); }
417
+
418
+ shutdown(): void {
419
+ if (this.ptyProcess) {
420
+ this.ptyProcess.write('\x03');
421
+ setTimeout(() => {
422
+ try { this.ptyProcess?.kill(); } catch { }
423
+ this.ptyProcess = null;
424
+ this.currentStatus = 'stopped';
425
+ this.ready = false;
426
+ this.onStatusChange?.();
427
+ }, this.timeouts.shutdownGrace);
428
+ }
429
+ }
430
+
431
+ clearHistory(): void {
432
+ this.messages = [];
433
+ this.onStatusChange?.();
434
+ }
435
+
436
+ isProcessing(): boolean { return this.isWaitingForResponse; }
437
+ isReady(): boolean { return this.ready; }
438
+
439
+ writeRaw(data: string): void {
440
+ this.ptyProcess?.write(data);
441
+ }
442
+
443
+ resize(cols: number, rows: number): void {
444
+ if (this.ptyProcess) {
445
+ try { this.ptyProcess.resize(cols, rows); } catch { }
446
+ }
447
+ }
448
+ }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * CDP Commands — cdpEval, screenshot, cdpCommand, cdpBatch, cdpRemoteAction,
3
+ * discoverAgents, file operations
4
+ */
5
+
6
+ import type { CommandResult, CommandHelpers } from './handler.js';
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import * as os from 'os';
10
+
11
+ // ─── CDP direct commands ──────────────────────────
12
+
13
+ export async function handleCdpEval(h: CommandHelpers, args: any): Promise<CommandResult> {
14
+ if (!h.getCdp()?.isConnected) return { success: false, error: 'CDP not connected' };
15
+ const expression = args?.expression || args?.script;
16
+ if (!expression) return { success: false, error: 'expression required' };
17
+ try {
18
+ const result = await h.getCdp()!.evaluate(expression, 50000);
19
+ return { success: true, result };
20
+ } catch (e: any) {
21
+ return { success: false, error: e.message };
22
+ }
23
+ }
24
+
25
+ export async function handleScreenshot(h: CommandHelpers, args: any): Promise<CommandResult> {
26
+ if (!h.getCdp()?.isConnected) return { success: false, error: 'CDP not connected' };
27
+ try {
28
+ const buf = await h.getCdp()!.captureScreenshot();
29
+ if (buf) {
30
+ const b64 = buf.toString('base64');
31
+ return { success: true, result: b64, base64: b64, screenshot: b64, format: 'webp' };
32
+ }
33
+ return { success: false, error: 'Screenshot failed' };
34
+ } catch (e: any) {
35
+ return { success: false, error: e.message };
36
+ }
37
+ }
38
+
39
+ export async function handleCdpCommand(h: CommandHelpers, args: any): Promise<CommandResult> {
40
+ if (!h.getCdp()?.isConnected) return { success: false, error: 'CDP not connected' };
41
+ const method = args?.method;
42
+ const params = args?.params || {};
43
+ if (!method) return { success: false, error: 'method required' };
44
+ try {
45
+ const result = await h.getCdp()!.sendCdpCommand(method, params);
46
+ return { success: true, result };
47
+ } catch (e: any) {
48
+ return { success: false, error: e.message };
49
+ }
50
+ }
51
+
52
+ export async function handleCdpBatch(h: CommandHelpers, args: any): Promise<CommandResult> {
53
+ if (!h.getCdp()?.isConnected) return { success: false, error: 'CDP not connected' };
54
+ const commands = args?.commands as any[];
55
+ const stopOnError = args?.stopOnError !== false;
56
+ if (!commands?.length) return { success: false, error: 'commands array required' };
57
+
58
+ const results: any[] = [];
59
+ for (const cmd of commands) {
60
+ try {
61
+ const result = await h.getCdp()!.sendCdpCommand(cmd.method, cmd.params || {});
62
+ results.push({ method: cmd.method, success: true, result });
63
+ } catch (e: any) {
64
+ results.push({ method: cmd.method, success: false, error: e.message });
65
+ if (stopOnError) break;
66
+ }
67
+ }
68
+ return { success: true, results };
69
+ }
70
+
71
+ export async function handleCdpRemoteAction(h: CommandHelpers, args: any): Promise<CommandResult> {
72
+ if (!h.getCdp()?.isConnected) return { success: false, error: 'CDP not connected' };
73
+ const action = args?.action;
74
+ const params = args?.params || args;
75
+
76
+ try {
77
+ switch (action) {
78
+ case 'input_key': {
79
+ const { key, modifiers } = params;
80
+ await h.getCdp()!.send('Input.dispatchKeyEvent', {
81
+ type: 'keyDown', key,
82
+ ...(modifiers?.ctrl ? { modifiers: 2 } : {}),
83
+ ...(modifiers?.shift ? { modifiers: 8 } : {}),
84
+ });
85
+ await h.getCdp()!.send('Input.dispatchKeyEvent', { type: 'keyUp', key });
86
+ return { success: true };
87
+ }
88
+ case 'input_click': {
89
+ let { x, y, nx, ny, button: btn } = params;
90
+ if ((x === undefined || y === undefined) && nx !== undefined && ny !== undefined) {
91
+ const viewport = await h.getCdp()!.evaluate(
92
+ 'JSON.stringify({ w: window.innerWidth, h: window.innerHeight })'
93
+ ) as string;
94
+ const { w, h: vh } = JSON.parse(viewport);
95
+ x = Math.round(nx * w);
96
+ y = Math.round(ny * vh);
97
+ }
98
+ if (x === undefined || y === undefined) {
99
+ return { success: false, error: 'No coordinates provided (x,y or nx,ny required)' };
100
+ }
101
+ await h.getCdp()!.send('Input.dispatchMouseEvent', {
102
+ type: 'mousePressed', x, y, button: btn || 'left', clickCount: 1,
103
+ });
104
+ await h.getCdp()!.send('Input.dispatchMouseEvent', {
105
+ type: 'mouseReleased', x, y, button: btn || 'left', clickCount: 1,
106
+ });
107
+ return { success: true, x, y };
108
+ }
109
+ case 'input_type': {
110
+ const { text } = params;
111
+ for (const char of text || '') {
112
+ await h.getCdp()!.send('Input.dispatchKeyEvent', {
113
+ type: 'keyDown', text: char, key: char,
114
+ });
115
+ await h.getCdp()!.send('Input.dispatchKeyEvent', { type: 'keyUp', key: char });
116
+ }
117
+ return { success: true };
118
+ }
119
+ case 'page_screenshot': return handleScreenshot(h, args);
120
+ case 'page_eval': return handleCdpEval(h, params);
121
+ case 'dom_query': {
122
+ const html = await h.getCdp()!.querySelector(params?.selector);
123
+ return { success: true, html };
124
+ }
125
+ case 'input_wheel': {
126
+ let { x, y, nx, ny, deltaX, deltaY } = params;
127
+ if ((x === undefined || y === undefined) && nx !== undefined && ny !== undefined) {
128
+ const viewport = await h.getCdp()!.evaluate(
129
+ 'JSON.stringify({ w: window.innerWidth, h: window.innerHeight })'
130
+ ) as string;
131
+ const { w, h: vh } = JSON.parse(viewport);
132
+ x = Math.round(nx * w);
133
+ y = Math.round(ny * vh);
134
+ }
135
+ await h.getCdp()!.send('Input.dispatchMouseEvent', {
136
+ type: 'mouseWheel', x: x || 0, y: y || 0,
137
+ deltaX: deltaX || 0, deltaY: deltaY || 0,
138
+ });
139
+ return { success: true };
140
+ }
141
+ default:
142
+ return { success: false, error: `Unknown remote action: ${action}` };
143
+ }
144
+ } catch (e: any) {
145
+ return { success: false, error: e.message };
146
+ }
147
+ }
148
+
149
+ export async function handleDiscoverAgents(h: CommandHelpers, args: any): Promise<CommandResult> {
150
+ if (!h.getCdp()?.isConnected) return { success: false, error: 'CDP not connected' };
151
+ const agents = await h.getCdp()!.discoverAgentWebviews();
152
+ return { success: true, agents };
153
+ }
154
+
155
+ // ─── File commands ─────────────────────────────
156
+
157
+ function resolveSafePath(requestedPath: string): string {
158
+ const home = os.homedir();
159
+ let resolved: string;
160
+ if (requestedPath.startsWith('~')) {
161
+ resolved = path.join(home, requestedPath.slice(1));
162
+ } else if (path.isAbsolute(requestedPath)) {
163
+ resolved = requestedPath;
164
+ } else {
165
+ resolved = path.resolve(requestedPath);
166
+ }
167
+ return resolved;
168
+ }
169
+
170
+ export async function handleFileRead(h: CommandHelpers, args: any): Promise<CommandResult> {
171
+ try {
172
+ const filePath = resolveSafePath(args?.path);
173
+ const content = fs.readFileSync(filePath, 'utf-8');
174
+ return { success: true, content, path: filePath };
175
+ } catch (e: any) {
176
+ return { success: false, error: e.message };
177
+ }
178
+ }
179
+
180
+ export async function handleFileWrite(h: CommandHelpers, args: any): Promise<CommandResult> {
181
+ try {
182
+ const filePath = resolveSafePath(args?.path);
183
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
184
+ fs.writeFileSync(filePath, args?.content || '', 'utf-8');
185
+ return { success: true, path: filePath };
186
+ } catch (e: any) {
187
+ return { success: false, error: e.message };
188
+ }
189
+ }
190
+
191
+ export async function handleFileList(h: CommandHelpers, args: any): Promise<CommandResult> {
192
+ try {
193
+ const dirPath = resolveSafePath(args?.path || '.');
194
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
195
+ const files = entries.map(e => ({
196
+ name: e.name,
197
+ type: e.isDirectory() ? 'directory' : 'file',
198
+ size: e.isFile() ? fs.statSync(path.join(dirPath, e.name)).size : undefined,
199
+ }));
200
+ return { success: true, files, path: dirPath };
201
+ } catch (e: any) {
202
+ return { success: false, error: e.message };
203
+ }
204
+ }
205
+
206
+ export async function handleFileListBrowse(h: CommandHelpers, args: any): Promise<CommandResult> {
207
+ return handleFileList(h, args);
208
+ }