@adhdev/daemon-core 0.6.13 → 0.6.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.6.13",
3
+ "version": "0.6.16",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -307,8 +307,8 @@ export class DevServer {
307
307
  }
308
308
  }
309
309
 
310
- private async handleRunScript(type: string, req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
311
- const body = await this.readBody(req);
310
+ private async handleRunScript(type: string, req: http.IncomingMessage, res: http.ServerResponse, parsedBody?: any): Promise<void> {
311
+ const body = parsedBody || await this.readBody(req);
312
312
  const { script: scriptName, params, ideType: scriptIdeType } = body;
313
313
 
314
314
  const provider = this.providerLoader.resolve(type);
@@ -495,7 +495,7 @@ export class DevServer {
495
495
  return;
496
496
  }
497
497
  // Delegate to handleRunScript
498
- await this.handleRunScript(type, req, res);
498
+ await this.handleRunScript(type, req, res, body);
499
499
  }
500
500
 
501
501
  private async handleStatus(_req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
@@ -2023,85 +2023,190 @@ export class DevServer {
2023
2023
  // Strip interactive-only flags for auto-implement (non-interactive mode)
2024
2024
  const interactiveFlags = ['--yolo', '--interactive', '-i'];
2025
2025
  const baseArgs: string[] = [...(spawn.args || [])].filter((a: string) => !interactiveFlags.includes(a));
2026
- let args: string[];
2027
- let useStdin = true;
2026
+
2027
+ // 6. Construct the complete shell command per-agent
2028
+ let shellCmd: string;
2028
2029
 
2029
2030
  if (command === 'claude') {
2030
- // Claude Code: --print mode, skip permissions for non-interactive auto-implement
2031
- args = [...baseArgs, '--print', '--dangerously-skip-permissions', '--add-dir', providerDir];
2032
- useStdin = true;
2031
+ // Claude Code: --print mode, skip permissions, prompt via -p "$(cat file)"
2032
+ const args = [...baseArgs, '--print', '--dangerously-skip-permissions', '--add-dir', providerDir];
2033
+ const escapedArgs = args.map(a => `'${a.replace(/'/g, "'\\''")}'`).join(' ');
2034
+ shellCmd = `${command} ${escapedArgs} -p "$(cat '${promptFile}')"`;
2033
2035
  } else if (command === 'gemini') {
2034
- // Gemini CLI: -p (non-interactive mode) with stdin piped prompt
2035
- // -p "" means "non-interactive mode, read prompt from stdin"
2036
- // -y for yolo (auto-approve all), -s false for no sandbox
2037
- args = [...baseArgs, '-p', '', '-y', '-s', 'false'];
2038
- if (model) {
2039
- args.push('-m', model);
2036
+ // Gemini CLI: non-interactive prompt mode
2037
+ // We can't use @file syntax (causes Parts object parsing bug) or $(cat) (arg too long).
2038
+ // Solution: meta-prompt that tells Gemini to read the instructions file itself.
2039
+ const args = [...baseArgs, '-y', '-s', 'false'];
2040
+ if (model) args.push('-m', model);
2041
+ const escapedArgs = args.map(a => `'${a.replace(/'/g, "'\\''")}'`).join(' ');
2042
+ shellCmd = `${command} ${escapedArgs} -p "Read the file at ${promptFile} and follow ALL the instructions in it exactly. Do not ask questions, just execute."`;
2043
+
2044
+ } else if (command === 'codex') {
2045
+ const args = ['exec', ...baseArgs];
2046
+ if (!args.includes('--dangerously-bypass-approvals-and-sandbox')) {
2047
+ args.push('--dangerously-bypass-approvals-and-sandbox');
2048
+ }
2049
+ if (!args.includes('--skip-git-repo-check')) {
2050
+ args.push('--skip-git-repo-check');
2040
2051
  }
2041
- useStdin = true;
2052
+ if (model) args.push('--model', model);
2053
+ const escapedArgs = args.map(a => `'${a.replace(/'/g, "'\\''")}'`).join(' ');
2054
+ const metaPrompt = `Read the file at ${promptFile} and follow ALL the instructions. Implement the specific function requested, then test it via CDP curl targeting 127.0.0.1:19280, wait for confirmation of success, and then close. DO NOT start working on other features not listed in the prompt constraint.`;
2055
+ shellCmd = `${command} ${escapedArgs} "${metaPrompt}"`;
2042
2056
  } else {
2043
- // Codex CLI, etc: pipe prompt via stdin
2044
- args = [...baseArgs];
2057
+ // Generic fallback: pipe prompt via stdin
2058
+ const escapedArgs = baseArgs.map(a => `'${a.replace(/'/g, "'\\''")}'`).join(' ');
2059
+ shellCmd = `cat '${promptFile}' | ${command} ${escapedArgs}`;
2045
2060
  }
2046
2061
 
2047
- // 6. Spawn CLI agent natively passing prompt via -p (avoids pipe deadlock)
2048
- this.sendAutoImplSSE({ event: 'progress', data: { function: '_init', status: 'spawning', message: `에이전트 실행 중: ${command} ${args.join(' ')} (prompt: ${prompt.length} chars)` } });
2062
+ this.sendAutoImplSSE({ event: 'progress', data: { function: '_init', status: 'spawning', message: `에이전트 실행 중: ${shellCmd.substring(0, 200)}... (prompt: ${prompt.length} chars)` } });
2049
2063
 
2050
2064
  this.autoImplStatus = { running: true, type, progress: [] };
2051
2065
 
2066
+ let child: any;
2067
+ let isPty = false;
2052
2068
  const { spawn: spawnFn } = await import('child_process');
2053
2069
 
2054
- // Add prompt file text directly as an argument using cat evaluation in shell
2055
- // This completely bypasses massive stdin pipe blocking while retaining CLI formatting
2056
- const escapedArgs = args.map(a => `'${a.replace(/'/g, "'\\''")}'`).join(' ');
2057
- const shellCmd = `${command} ${escapedArgs} -p "$(cat '${promptFile}')"`;
2058
- this.log(`Auto-implement spawn: ${shellCmd}`);
2059
- const child = spawnFn('sh', ['-c', shellCmd], {
2060
- cwd: providerDir,
2061
- shell: false,
2062
- timeout: 900000, // 15 min timeout
2063
- stdio: ['ignore', 'pipe', 'pipe'],
2064
- env: { ...process.env, ...(spawn.env || {}) },
2065
- });
2066
- this.autoImplProcess = child;
2067
- child.on('error', (err) => {
2068
- this.log(`Auto-implement spawn error: ${err.message}`);
2069
- this.sendAutoImplSSE({ event: 'output', data: { chunk: `[Spawn Error] ${err.message}\n`, stream: 'stderr' } });
2070
- });
2070
+ try {
2071
+ const pty = require('node-pty');
2072
+ this.log(`Auto-implement spawn (PTY): ${shellCmd}`);
2073
+ const isWin = os.platform() === 'win32';
2074
+ child = pty.spawn(isWin ? 'cmd.exe' : (process.env.SHELL || '/bin/zsh'), [isWin ? '/c' : '-c', shellCmd], {
2075
+ name: 'xterm-256color',
2076
+ cols: 120,
2077
+ rows: 40,
2078
+ cwd: providerDir,
2079
+ env: { ...process.env, ...(spawn.env || {}) },
2080
+ });
2081
+ isPty = true;
2082
+ } catch (err: any) {
2083
+ this.log(`PTY not available, using child_process: ${err.message}`);
2084
+ child = spawnFn('sh', ['-c', shellCmd], {
2085
+ cwd: providerDir,
2086
+ shell: false,
2087
+ timeout: 900000,
2088
+ stdio: ['pipe', 'pipe', 'pipe'],
2089
+ env: {
2090
+ ...process.env,
2091
+ ...(spawn.env || {}),
2092
+ ...(command === 'gemini' ? { SANDBOX: '1', GEMINI_CLI_NO_RELAUNCH: '1' } : {}),
2093
+ },
2094
+ });
2095
+ child.on('error', (err: Error) => {
2096
+ this.log(`Auto-implement spawn error: ${err.message}`);
2097
+ this.sendAutoImplSSE({ event: 'output', data: { chunk: `[Spawn Error] ${err.message}\n`, stream: 'stderr' } });
2098
+ });
2099
+ }
2071
2100
 
2101
+ this.autoImplProcess = child;
2072
2102
  let stdout = '';
2073
2103
  let stderr = '';
2074
- child.stdout?.on('data', (d: Buffer) => {
2075
- const chunk = d.toString();
2076
- stdout += chunk;
2077
- this.sendAutoImplSSE({ event: 'output', data: { chunk, stream: 'stdout' } });
2078
- });
2079
- child.stderr?.on('data', (d: Buffer) => {
2080
- const chunk = d.toString();
2081
- stderr += chunk;
2082
- this.sendAutoImplSSE({ event: 'output', data: { chunk, stream: 'stderr' } });
2083
- });
2104
+
2105
+ let approvalPatterns: RegExp[] = [];
2106
+ let approvalKeys: Record<number, string> = { 0: 'y\r' };
2107
+ let approvalBuffer = '';
2108
+ let lastApprovalTime = 0;
2109
+
2110
+ try {
2111
+ const { normalizeCliProviderForRuntime } = await import('../cli-adapters/provider-cli-adapter.js');
2112
+ const normalized = normalizeCliProviderForRuntime(agentProvider);
2113
+ approvalPatterns = normalized.patterns.approval;
2114
+ approvalKeys = (agentProvider as any)?.approvalKeys || { 0: 'y\r', 1: 'a\r' };
2115
+ } catch (err: any) {
2116
+ this.log(`Failed to load approval patterns: ${err.message}`);
2117
+ }
2084
2118
 
2085
- child.on('exit', (code) => {
2086
- this.autoImplProcess = null;
2087
- this.autoImplStatus.running = false;
2088
- const success = code === 0;
2089
- this.sendAutoImplSSE({
2090
- event: 'complete',
2091
- data: {
2092
- success,
2093
- exitCode: code,
2094
- functions,
2095
- message: success ? '✅ Auto-implement 완료' : `❌ 에이전트 종료 (code: ${code})`,
2096
- },
2097
- });
2098
- // Reload providers to pick up new scripts
2099
- try { this.providerLoader.reload(); } catch { /* ignore */ }
2100
- // Cleanup temp prompt file
2101
- try { fs.unlinkSync(promptFile); } catch { /* ignore */ }
2102
- this.log(`Auto-implement ${success ? 'completed' : 'failed'}: ${type} (exit: ${code})`);
2103
- });
2119
+ const checkAutoApproval = (chunk: string, writeFn: (s: string) => void) => {
2120
+ // Strip ANSI
2121
+ const cleanData = chunk.replace(/\x1B\[\d*[A-HJKSTfG]/g, ' ')
2122
+ .replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '')
2123
+ .replace(/\x1B\][^\x07]*\x07/g, '')
2124
+ .replace(/\x1B\][^\x1B]*\x1B\\/g, '')
2125
+ .replace(/ +/g, ' ');
2126
+
2127
+ approvalBuffer = (approvalBuffer + cleanData).slice(-1500);
2128
+
2129
+ // Force exit on completion signal
2130
+ if (approvalBuffer.includes('AUTO_IMPLEMENT_FINISHED')) {
2131
+ this.log('Agent finished task. Terminating interactive CLI session to unblock pipeline.');
2132
+ this.sendAutoImplSSE({ event: 'output', data: { chunk: `\n[🤖 ADHDev Pipeline] Completion token detected. Proceeding...\n`, stream: 'stdout' } });
2133
+ approvalBuffer = '';
2134
+
2135
+ try {
2136
+ (this.autoImplProcess as any).kill('SIGINT');
2137
+ } catch {
2138
+ // ignore
2139
+ }
2140
+ return;
2141
+ }
2142
+
2143
+ // Use a cooldown to prevent overlapping approval submissions
2144
+ if (Date.now() - lastApprovalTime < 2000) return;
2145
+
2146
+ if (approvalPatterns.some(p => p.test(approvalBuffer))) {
2147
+ // Use 'Always allow' (1) if available, otherwise 'Allow once' (0), otherwise hard fallback to 'a\r' for newer CLIs
2148
+ const key = approvalKeys[1] || approvalKeys[0] || 'a\r';
2149
+ writeFn(key);
2150
+ this.log(`Auto-Implement auto-approved prompt! Sending: ${JSON.stringify(key)}`);
2151
+ this.sendAutoImplSSE({ event: 'output', data: { chunk: `\n[🤖 ADHDev Auto-Approve] CLI Action Approved\n`, stream: 'stdout' } });
2152
+ approvalBuffer = '';
2153
+ lastApprovalTime = Date.now();
2154
+ }
2155
+ };
2104
2156
 
2157
+ if (isPty) {
2158
+ child.onData((data: string) => {
2159
+ stdout += data;
2160
+ if (data.includes('\x1b[6n')) {
2161
+ child.write('\x1b[12;1R');
2162
+ this.log('Terminal CPR request (\\x1b[6n) intercepted in PTY, responding with dummy coordinates [12;1R]');
2163
+ }
2164
+ checkAutoApproval(data, (s) => child.write(s));
2165
+ this.sendAutoImplSSE({ event: 'output', data: { chunk: data, stream: 'stdout' } });
2166
+ });
2167
+ child.onExit(({ exitCode: code }: { exitCode: number }) => {
2168
+ this.autoImplProcess = null;
2169
+ this.autoImplStatus.running = false;
2170
+ const success = code === 0;
2171
+ this.sendAutoImplSSE({
2172
+ event: 'complete',
2173
+ data: { success, exitCode: code, functions, message: success ? '✅ Auto-implement 완료' : `❌ 에이전트 종료 (code: ${code})` },
2174
+ });
2175
+ try { this.providerLoader.reload(); } catch { /* ignore */ }
2176
+ try { fs.unlinkSync(promptFile); } catch { /* ignore */ }
2177
+ });
2178
+ } else {
2179
+ child.stdout?.on('data', (d: Buffer) => {
2180
+ const chunk = d.toString();
2181
+ stdout += chunk;
2182
+ if (chunk.includes('\x1b[6n')) child.stdin?.write('\x1b[1;1R');
2183
+ checkAutoApproval(chunk, (s) => child.stdin?.write(s));
2184
+ this.sendAutoImplSSE({ event: 'output', data: { chunk, stream: 'stdout' } });
2185
+ });
2186
+ child.stderr?.on('data', (d: Buffer) => {
2187
+ const chunk = d.toString();
2188
+ stderr += chunk;
2189
+ checkAutoApproval(chunk, (s) => child.stdin?.write(s));
2190
+ this.sendAutoImplSSE({ event: 'output', data: { chunk, stream: 'stderr' } });
2191
+ });
2192
+ child.on('exit', (code: number) => {
2193
+ this.autoImplProcess = null;
2194
+ this.autoImplStatus.running = false;
2195
+ const success = code === 0;
2196
+ this.sendAutoImplSSE({
2197
+ event: 'complete',
2198
+ data: {
2199
+ success,
2200
+ exitCode: code,
2201
+ functions,
2202
+ message: success ? '✅ Auto-implement 완료' : `❌ 에이전트 종료 (code: ${code})`,
2203
+ },
2204
+ });
2205
+ try { this.providerLoader.reload(); } catch { /* ignore */ }
2206
+ try { fs.unlinkSync(promptFile); } catch { /* ignore */ }
2207
+ this.log(`Auto-implement ${success ? 'completed' : 'failed'}: ${type} (exit: ${code})`);
2208
+ });
2209
+ }
2105
2210
  this.json(res, 202, {
2106
2211
  started: true,
2107
2212
  type,
@@ -79,7 +79,12 @@ export class ProviderInstanceManager {
79
79
  // pending events propagation
80
80
  for (const event of state.pendingEvents) {
81
81
  for (const listener of this.eventListeners) {
82
- listener({ ...event, providerType: instance.type });
82
+ listener({
83
+ ...event,
84
+ providerType: instance.type,
85
+ instanceId: state.instanceId,
86
+ providerCategory: state.category,
87
+ });
83
88
  }
84
89
  }
85
90
  } catch (e) {