@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/dist/index.js +6658 -6513
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/daemon/dev-server.ts +170 -65
- package/src/providers/provider-instance-manager.ts +6 -1
package/package.json
CHANGED
package/src/daemon/dev-server.ts
CHANGED
|
@@ -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
|
-
|
|
2027
|
-
|
|
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
|
|
2031
|
-
args = [...baseArgs, '--print', '--dangerously-skip-permissions', '--add-dir', providerDir];
|
|
2032
|
-
|
|
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:
|
|
2035
|
-
//
|
|
2036
|
-
// -
|
|
2037
|
-
args = [...baseArgs, '-
|
|
2038
|
-
if (model)
|
|
2039
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
2044
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
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
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
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
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
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({
|
|
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) {
|