skimpyclaw 0.1.0
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/README.md +230 -0
- package/dist/__tests__/agent.test.d.ts +1 -0
- package/dist/__tests__/agent.test.js +131 -0
- package/dist/__tests__/api.test.d.ts +1 -0
- package/dist/__tests__/api.test.js +1227 -0
- package/dist/__tests__/audit.test.d.ts +1 -0
- package/dist/__tests__/audit.test.js +122 -0
- package/dist/__tests__/cache.test.d.ts +1 -0
- package/dist/__tests__/cache.test.js +65 -0
- package/dist/__tests__/channels.test.d.ts +1 -0
- package/dist/__tests__/channels.test.js +85 -0
- package/dist/__tests__/cli.integration.test.d.ts +1 -0
- package/dist/__tests__/cli.integration.test.js +16 -0
- package/dist/__tests__/cli.test.d.ts +1 -0
- package/dist/__tests__/cli.test.js +230 -0
- package/dist/__tests__/code-agents-executor.test.d.ts +1 -0
- package/dist/__tests__/code-agents-executor.test.js +75 -0
- package/dist/__tests__/code-agents-orchestrator.test.d.ts +1 -0
- package/dist/__tests__/code-agents-orchestrator.test.js +149 -0
- package/dist/__tests__/code-agents-parser.test.d.ts +1 -0
- package/dist/__tests__/code-agents-parser.test.js +39 -0
- package/dist/__tests__/code-agents-utils.test.d.ts +1 -0
- package/dist/__tests__/code-agents-utils.test.js +41 -0
- package/dist/__tests__/config.test.d.ts +1 -0
- package/dist/__tests__/config.test.js +46 -0
- package/dist/__tests__/cron.test.d.ts +1 -0
- package/dist/__tests__/cron.test.js +66 -0
- package/dist/__tests__/dashboard-mode.test.d.ts +1 -0
- package/dist/__tests__/dashboard-mode.test.js +145 -0
- package/dist/__tests__/dashboard.test.d.ts +1 -0
- package/dist/__tests__/dashboard.test.js +43 -0
- package/dist/__tests__/doctor.formatters.test.d.ts +1 -0
- package/dist/__tests__/doctor.formatters.test.js +65 -0
- package/dist/__tests__/doctor.index.test.d.ts +1 -0
- package/dist/__tests__/doctor.index.test.js +48 -0
- package/dist/__tests__/doctor.runner.test.d.ts +1 -0
- package/dist/__tests__/doctor.runner.test.js +204 -0
- package/dist/__tests__/exec-approval.test.d.ts +1 -0
- package/dist/__tests__/exec-approval.test.js +323 -0
- package/dist/__tests__/file-lock.test.d.ts +1 -0
- package/dist/__tests__/file-lock.test.js +92 -0
- package/dist/__tests__/langfuse.test.d.ts +1 -0
- package/dist/__tests__/langfuse.test.js +40 -0
- package/dist/__tests__/model-selection.test.d.ts +1 -0
- package/dist/__tests__/model-selection.test.js +62 -0
- package/dist/__tests__/orchestrator.test.d.ts +1 -0
- package/dist/__tests__/orchestrator.test.js +425 -0
- package/dist/__tests__/providers-init.test.d.ts +1 -0
- package/dist/__tests__/providers-init.test.js +32 -0
- package/dist/__tests__/providers-routing.test.d.ts +1 -0
- package/dist/__tests__/providers-routing.test.js +25 -0
- package/dist/__tests__/providers-utils.test.d.ts +1 -0
- package/dist/__tests__/providers-utils.test.js +54 -0
- package/dist/__tests__/security.test.d.ts +1 -0
- package/dist/__tests__/security.test.js +22 -0
- package/dist/__tests__/sessions.test.d.ts +1 -0
- package/dist/__tests__/sessions.test.js +147 -0
- package/dist/__tests__/setup.test.d.ts +1 -0
- package/dist/__tests__/setup.test.js +114 -0
- package/dist/__tests__/skills.test.d.ts +1 -0
- package/dist/__tests__/skills.test.js +333 -0
- package/dist/__tests__/subagent.test.d.ts +1 -0
- package/dist/__tests__/subagent.test.js +240 -0
- package/dist/__tests__/telegram-utils.test.d.ts +1 -0
- package/dist/__tests__/telegram-utils.test.js +22 -0
- package/dist/__tests__/telegram.test.d.ts +1 -0
- package/dist/__tests__/telegram.test.js +42 -0
- package/dist/__tests__/token-efficiency.test.d.ts +1 -0
- package/dist/__tests__/token-efficiency.test.js +38 -0
- package/dist/__tests__/tool-guard.test.d.ts +1 -0
- package/dist/__tests__/tool-guard.test.js +105 -0
- package/dist/__tests__/tools.test.d.ts +1 -0
- package/dist/__tests__/tools.test.js +589 -0
- package/dist/__tests__/usage.test.d.ts +1 -0
- package/dist/__tests__/usage.test.js +197 -0
- package/dist/__tests__/voice.test.d.ts +1 -0
- package/dist/__tests__/voice.test.js +214 -0
- package/dist/agent.d.ts +24 -0
- package/dist/agent.js +269 -0
- package/dist/api.d.ts +3 -0
- package/dist/api.js +943 -0
- package/dist/audit.d.ts +26 -0
- package/dist/audit.js +121 -0
- package/dist/cache.d.ts +8 -0
- package/dist/cache.js +24 -0
- package/dist/channels/telegram/handlers.d.ts +41 -0
- package/dist/channels/telegram/handlers.js +498 -0
- package/dist/channels/telegram/index.d.ts +14 -0
- package/dist/channels/telegram/index.js +326 -0
- package/dist/channels/telegram/types.d.ts +26 -0
- package/dist/channels/telegram/types.js +31 -0
- package/dist/channels/telegram/utils.d.ts +25 -0
- package/dist/channels/telegram/utils.js +256 -0
- package/dist/channels.d.ts +11 -0
- package/dist/channels.js +118 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +768 -0
- package/dist/code-agents/executor.d.ts +5 -0
- package/dist/code-agents/executor.js +463 -0
- package/dist/code-agents/index.d.ts +22 -0
- package/dist/code-agents/index.js +199 -0
- package/dist/code-agents/orchestrator.d.ts +23 -0
- package/dist/code-agents/orchestrator.js +403 -0
- package/dist/code-agents/parser.d.ts +21 -0
- package/dist/code-agents/parser.js +197 -0
- package/dist/code-agents/registry.d.ts +27 -0
- package/dist/code-agents/registry.js +147 -0
- package/dist/code-agents/types.d.ts +66 -0
- package/dist/code-agents/types.js +4 -0
- package/dist/code-agents/utils.d.ts +36 -0
- package/dist/code-agents/utils.js +236 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +123 -0
- package/dist/cron.d.ts +49 -0
- package/dist/cron.js +400 -0
- package/dist/dashboard/assets/index-CZJCvMSN.js +65 -0
- package/dist/dashboard/assets/index-EAg6lqF5.css +1 -0
- package/dist/dashboard/favicon.svg +3 -0
- package/dist/dashboard/index.html +21 -0
- package/dist/dashboard-frontend.d.ts +7 -0
- package/dist/dashboard-frontend.js +86 -0
- package/dist/dashboard.d.ts +8 -0
- package/dist/dashboard.js +4071 -0
- package/dist/digests.d.ts +36 -0
- package/dist/digests.js +338 -0
- package/dist/discord.d.ts +8 -0
- package/dist/discord.js +828 -0
- package/dist/doctor/checks.d.ts +18 -0
- package/dist/doctor/checks.js +368 -0
- package/dist/doctor/formatters.d.ts +3 -0
- package/dist/doctor/formatters.js +44 -0
- package/dist/doctor/index.d.ts +8 -0
- package/dist/doctor/index.js +7 -0
- package/dist/doctor/runner.d.ts +3 -0
- package/dist/doctor/runner.js +109 -0
- package/dist/doctor/types.d.ts +20 -0
- package/dist/doctor/types.js +1 -0
- package/dist/exec-approval.d.ts +101 -0
- package/dist/exec-approval.js +432 -0
- package/dist/file-lock.d.ts +34 -0
- package/dist/file-lock.js +81 -0
- package/dist/gateway.d.ts +8 -0
- package/dist/gateway.js +114 -0
- package/dist/heartbeat.d.ts +4 -0
- package/dist/heartbeat.js +101 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +75 -0
- package/dist/langfuse.d.ts +34 -0
- package/dist/langfuse.js +145 -0
- package/dist/mcp-context-a8c.d.ts +13 -0
- package/dist/mcp-context-a8c.js +34 -0
- package/dist/model-selection.d.ts +18 -0
- package/dist/model-selection.js +50 -0
- package/dist/orchestrator.d.ts +15 -0
- package/dist/orchestrator.js +676 -0
- package/dist/providers/anthropic.d.ts +7 -0
- package/dist/providers/anthropic.js +319 -0
- package/dist/providers/codex.d.ts +17 -0
- package/dist/providers/codex.js +508 -0
- package/dist/providers/content.d.ts +21 -0
- package/dist/providers/content.js +55 -0
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.js +138 -0
- package/dist/providers/observability.d.ts +19 -0
- package/dist/providers/observability.js +94 -0
- package/dist/providers/openai.d.ts +10 -0
- package/dist/providers/openai.js +310 -0
- package/dist/providers/tool-guard.d.ts +30 -0
- package/dist/providers/tool-guard.js +89 -0
- package/dist/providers/types.d.ts +34 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/utils.d.ts +65 -0
- package/dist/providers/utils.js +199 -0
- package/dist/security.d.ts +8 -0
- package/dist/security.js +113 -0
- package/dist/service.d.ts +8 -0
- package/dist/service.js +38 -0
- package/dist/sessions.d.ts +35 -0
- package/dist/sessions.js +142 -0
- package/dist/setup.d.ts +36 -0
- package/dist/setup.js +821 -0
- package/dist/skills-types.d.ts +65 -0
- package/dist/skills-types.js +2 -0
- package/dist/skills.d.ts +32 -0
- package/dist/skills.js +260 -0
- package/dist/subagent.d.ts +19 -0
- package/dist/subagent.js +376 -0
- package/dist/telegram.d.ts +2 -0
- package/dist/telegram.js +11 -0
- package/dist/tools/bash-tool.d.ts +3 -0
- package/dist/tools/bash-tool.js +59 -0
- package/dist/tools/browser-tool.d.ts +3 -0
- package/dist/tools/browser-tool.js +265 -0
- package/dist/tools/definitions.d.ts +432 -0
- package/dist/tools/definitions.js +181 -0
- package/dist/tools/execute-context.d.ts +26 -0
- package/dist/tools/execute-context.js +1 -0
- package/dist/tools/file-tools.d.ts +8 -0
- package/dist/tools/file-tools.js +67 -0
- package/dist/tools/path-utils.d.ts +1 -0
- package/dist/tools/path-utils.js +8 -0
- package/dist/tools.d.ts +24 -0
- package/dist/tools.js +281 -0
- package/dist/types.d.ts +259 -0
- package/dist/types.js +2 -0
- package/dist/usage.d.ts +76 -0
- package/dist/usage.js +150 -0
- package/dist/voice.d.ts +37 -0
- package/dist/voice.js +461 -0
- package/package.json +70 -0
- package/templates/AGENTS.md +38 -0
- package/templates/BOOT.md +23 -0
- package/templates/BOOTSTRAP.md +26 -0
- package/templates/HEARTBEAT.md +5 -0
- package/templates/IDENTITY.md +5 -0
- package/templates/MEMORY.md +24 -0
- package/templates/SOUL.md +92 -0
- package/templates/TOOLS.md +30 -0
- package/templates/USER.md +31 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CodeAgentBackgroundOptions, ValidationResult } from './types.js';
|
|
2
|
+
/** Run build/test validation. Shared by solo agents and team orchestrator. */
|
|
3
|
+
export declare function runValidation(workdir: string): Promise<ValidationResult>;
|
|
4
|
+
/** Background execution of a coding agent. Updates task status throughout. */
|
|
5
|
+
export declare function runCodeAgentBackground(id: string, agent: string, task: string, workdir: string, validate: boolean, input: Record<string, any>, startedAt: Date, options?: CodeAgentBackgroundOptions): Promise<void>;
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
// Code Agent Executor - Background execution logic
|
|
2
|
+
import { spawn, exec } from 'child_process';
|
|
3
|
+
import { createWriteStream } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
// SKIMPYCLAW_ROOT for log paths
|
|
6
|
+
const SKIMPYCLAW_ROOT = join(import.meta.dirname || process.cwd(), '..', '..');
|
|
7
|
+
import { VALIDATE_TIMEOUT_MS } from './types.js';
|
|
8
|
+
import { getCodeAgentsDir, ensureCodeAgentsDir, writeCodeAgentTask, setCodeAgentCanceller, deleteCodeAgentCanceller, getCodeAgent, } from './registry.js';
|
|
9
|
+
import { buildCodeAgentArgs, notifyCodeAgentResult } from './utils.js';
|
|
10
|
+
import { parseStreamJsonForLive, parseClaudeOutput, parseCodexOutput } from './parser.js';
|
|
11
|
+
import { startTrace, addEvent, endTrace } from '../audit.js';
|
|
12
|
+
import { buildUsageRecord, recordUsage } from '../usage.js';
|
|
13
|
+
const CANCELLED_MESSAGE = 'Cancelled by user';
|
|
14
|
+
/** Run build/test validation. Shared by solo agents and team orchestrator. */
|
|
15
|
+
export function runValidation(workdir) {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
exec('pnpm build && pnpm test', {
|
|
18
|
+
cwd: workdir,
|
|
19
|
+
timeout: VALIDATE_TIMEOUT_MS,
|
|
20
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
21
|
+
}, (error, vStdout, vStderr) => {
|
|
22
|
+
if (error) {
|
|
23
|
+
resolve({
|
|
24
|
+
passed: false,
|
|
25
|
+
output: [`VALIDATION FAILED (exit ${error.code}):`, vStdout, vStderr].filter(Boolean).join('\n').slice(0, 8_000),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
resolve({ passed: true, output: 'PASS' });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/** Background execution of a coding agent. Updates task status throughout. */
|
|
35
|
+
export async function runCodeAgentBackground(id, agent, task, workdir, validate, input, startedAt, options) {
|
|
36
|
+
const caTask = getCodeAgent(id);
|
|
37
|
+
if (!caTask) {
|
|
38
|
+
throw new Error(`Task ${id} not found in registry`);
|
|
39
|
+
}
|
|
40
|
+
let cancelled = false;
|
|
41
|
+
let activeProc = null;
|
|
42
|
+
let activeTimer = null;
|
|
43
|
+
let activeExecProc = null;
|
|
44
|
+
const setActiveCanceller = (fn) => {
|
|
45
|
+
setCodeAgentCanceller(id, () => {
|
|
46
|
+
cancelled = true;
|
|
47
|
+
try {
|
|
48
|
+
fn();
|
|
49
|
+
}
|
|
50
|
+
catch { /* best effort */ }
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
setActiveCanceller(() => { });
|
|
54
|
+
const ensureNotCancelled = () => {
|
|
55
|
+
if (cancelled || caTask.status === 'cancelled')
|
|
56
|
+
throw new Error(CANCELLED_MESSAGE);
|
|
57
|
+
};
|
|
58
|
+
// Start audit trace
|
|
59
|
+
const traceId = startTrace('code_agent');
|
|
60
|
+
addEvent(traceId, {
|
|
61
|
+
type: 'spawn',
|
|
62
|
+
summary: `${agent}: ${task.slice(0, 150)}`,
|
|
63
|
+
durationMs: 0,
|
|
64
|
+
detail: { agent, workdir, model: input.model, validate },
|
|
65
|
+
});
|
|
66
|
+
// Per-invocation timeout (configurable defaults for team vs solo)
|
|
67
|
+
const defaultTimeout = options?.defaultTimeoutMinutes ?? 30;
|
|
68
|
+
const maxTimeout = options?.maxTimeoutMinutes ?? 30;
|
|
69
|
+
const timeoutMinutes = Math.min(input.timeout_minutes || defaultTimeout, maxTimeout);
|
|
70
|
+
const timeoutMs = timeoutMinutes * 60 * 1000;
|
|
71
|
+
const { cmd, args } = options?.buildArgs
|
|
72
|
+
? options.buildArgs()
|
|
73
|
+
: buildCodeAgentArgs({
|
|
74
|
+
task,
|
|
75
|
+
agent,
|
|
76
|
+
workdir,
|
|
77
|
+
model: input.model,
|
|
78
|
+
max_turns: input.max_turns,
|
|
79
|
+
});
|
|
80
|
+
let stdout = '';
|
|
81
|
+
let stderr = '';
|
|
82
|
+
// Full log file — untruncated stdout + stderr
|
|
83
|
+
const logPath = join(getCodeAgentsDir(), `${id}.log`);
|
|
84
|
+
ensureCodeAgentsDir();
|
|
85
|
+
const logStream = createWriteStream(logPath, { flags: 'w' });
|
|
86
|
+
logStream.write(`=== ${id} | ${agent} | ${new Date().toISOString()} ===\n`);
|
|
87
|
+
logStream.write(`Task: ${task.slice(0, 500)}\n`);
|
|
88
|
+
logStream.write(`Workdir: ${workdir}\n\n`);
|
|
89
|
+
try {
|
|
90
|
+
ensureNotCancelled();
|
|
91
|
+
const exitCode = await new Promise((resolvePromise, reject) => {
|
|
92
|
+
const spawnEnv = { ...process.env };
|
|
93
|
+
delete spawnEnv.CLAUDECODE;
|
|
94
|
+
// Apply extra env vars (e.g. team mode feature flag)
|
|
95
|
+
if (options?.env)
|
|
96
|
+
Object.assign(spawnEnv, options.env);
|
|
97
|
+
const proc = spawn(cmd, args, {
|
|
98
|
+
cwd: workdir,
|
|
99
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
100
|
+
env: spawnEnv,
|
|
101
|
+
});
|
|
102
|
+
activeProc = proc;
|
|
103
|
+
let lastStatusWrite = 0;
|
|
104
|
+
const STATUS_WRITE_INTERVAL = 3000;
|
|
105
|
+
proc.stdout.on('data', (chunk) => {
|
|
106
|
+
stdout += chunk.toString();
|
|
107
|
+
logStream.write(chunk);
|
|
108
|
+
const now = Date.now();
|
|
109
|
+
if (now - lastStatusWrite > STATUS_WRITE_INTERVAL) {
|
|
110
|
+
lastStatusWrite = now;
|
|
111
|
+
// Parse stream-json into readable live output
|
|
112
|
+
const parsed = parseStreamJsonForLive(stdout);
|
|
113
|
+
const live = stderr ? `[progress]\n${stderr.slice(-2000)}\n\n${parsed}` : parsed;
|
|
114
|
+
caTask.liveOutput = live;
|
|
115
|
+
writeCodeAgentTask(caTask);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
proc.stderr.on('data', (chunk) => {
|
|
119
|
+
stderr += chunk.toString();
|
|
120
|
+
logStream.write(chunk);
|
|
121
|
+
const now = Date.now();
|
|
122
|
+
if (now - lastStatusWrite > STATUS_WRITE_INTERVAL) {
|
|
123
|
+
lastStatusWrite = now;
|
|
124
|
+
const parsedOut = parseStreamJsonForLive(stdout);
|
|
125
|
+
const live = `[progress]\n${stderr.slice(-2000)}\n\n${parsedOut}`;
|
|
126
|
+
caTask.liveOutput = live;
|
|
127
|
+
writeCodeAgentTask(caTask);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
let timedOut = false;
|
|
131
|
+
const timer = setTimeout(() => {
|
|
132
|
+
timedOut = true;
|
|
133
|
+
proc.kill('SIGTERM');
|
|
134
|
+
// SIGKILL fallback after 5s
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
try {
|
|
137
|
+
proc.kill('SIGKILL');
|
|
138
|
+
}
|
|
139
|
+
catch { /* already dead */ }
|
|
140
|
+
}, 5000);
|
|
141
|
+
}, timeoutMs);
|
|
142
|
+
activeTimer = timer;
|
|
143
|
+
setActiveCanceller(() => {
|
|
144
|
+
if (activeTimer)
|
|
145
|
+
clearTimeout(activeTimer);
|
|
146
|
+
activeTimer = null;
|
|
147
|
+
try {
|
|
148
|
+
activeProc?.kill('SIGTERM');
|
|
149
|
+
}
|
|
150
|
+
catch { /* best effort */ }
|
|
151
|
+
});
|
|
152
|
+
proc.on('close', (code) => {
|
|
153
|
+
if (activeTimer)
|
|
154
|
+
clearTimeout(activeTimer);
|
|
155
|
+
activeTimer = null;
|
|
156
|
+
activeProc = null;
|
|
157
|
+
logStream.write(`\n=== EXIT ${code} | ${new Date().toISOString()} ===\n`);
|
|
158
|
+
logStream.end();
|
|
159
|
+
if (cancelled || caTask.status === 'cancelled') {
|
|
160
|
+
reject(new Error(CANCELLED_MESSAGE));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (timedOut) {
|
|
164
|
+
reject(new Error(`${agent} agent timed out after ${timeoutMinutes} minutes`));
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
resolvePromise(code);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
proc.on('error', (err) => {
|
|
171
|
+
if (activeTimer)
|
|
172
|
+
clearTimeout(activeTimer);
|
|
173
|
+
activeTimer = null;
|
|
174
|
+
activeProc = null;
|
|
175
|
+
logStream.write(`\n=== ERROR: ${err.message} ===\n`);
|
|
176
|
+
logStream.end();
|
|
177
|
+
reject(err);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
ensureNotCancelled();
|
|
181
|
+
// Parse output — stream-json format is newline-delimited JSON events
|
|
182
|
+
let agentOutput;
|
|
183
|
+
if (agent === 'claude') {
|
|
184
|
+
const parsed = parseClaudeOutput(stdout);
|
|
185
|
+
agentOutput = parsed.text;
|
|
186
|
+
// Store cost/token data from CLI result event
|
|
187
|
+
if (parsed.totalCost != null)
|
|
188
|
+
caTask.totalCost = (caTask.totalCost ?? 0) + parsed.totalCost;
|
|
189
|
+
if (parsed.inputTokens != null)
|
|
190
|
+
caTask.inputTokens = (caTask.inputTokens ?? 0) + parsed.inputTokens;
|
|
191
|
+
if (parsed.outputTokens != null)
|
|
192
|
+
caTask.outputTokens = (caTask.outputTokens ?? 0) + parsed.outputTokens;
|
|
193
|
+
// Record in usage tracking
|
|
194
|
+
if (parsed.totalCost != null || parsed.inputTokens != null) {
|
|
195
|
+
recordUsage(buildUsageRecord({
|
|
196
|
+
model: input.model || 'claude',
|
|
197
|
+
provider: 'anthropic',
|
|
198
|
+
inputTokens: parsed.inputTokens ?? 0,
|
|
199
|
+
outputTokens: parsed.outputTokens ?? 0,
|
|
200
|
+
inputCost: 0, // CLI doesn't break down input/output cost
|
|
201
|
+
outputCost: 0,
|
|
202
|
+
totalCost: parsed.totalCost ?? 0,
|
|
203
|
+
trigger: 'code_agent',
|
|
204
|
+
agentId: id,
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
agentOutput = parseCodexOutput(stdout);
|
|
210
|
+
}
|
|
211
|
+
if (exitCode !== 0) {
|
|
212
|
+
addEvent(traceId, { type: 'error', summary: `${agent} exited with code ${exitCode}`, durationMs: Date.now() - startedAt.getTime() });
|
|
213
|
+
await endTrace(traceId, 'error');
|
|
214
|
+
Object.assign(caTask, {
|
|
215
|
+
status: 'failed',
|
|
216
|
+
endedAt: new Date().toISOString(),
|
|
217
|
+
durationSeconds: Math.round((Date.now() - startedAt.getTime()) / 1000),
|
|
218
|
+
exitCode,
|
|
219
|
+
outputPreview: agentOutput.slice(0, 5000),
|
|
220
|
+
error: `Exited with code ${exitCode}`,
|
|
221
|
+
liveOutput: undefined,
|
|
222
|
+
});
|
|
223
|
+
writeCodeAgentTask(caTask);
|
|
224
|
+
if (!options?.skipNotification)
|
|
225
|
+
await notifyCodeAgentResult(caTask, (id) => getCodeAgent(id) ?? null);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
// Post-validation gate
|
|
229
|
+
if (validate) {
|
|
230
|
+
caTask.status = 'validating';
|
|
231
|
+
caTask.outputPreview = agentOutput.slice(0, 500);
|
|
232
|
+
caTask.liveOutput = undefined;
|
|
233
|
+
writeCodeAgentTask(caTask);
|
|
234
|
+
const runValidationPromise = () => new Promise((res) => {
|
|
235
|
+
const validationProc = exec('pnpm build && pnpm test', {
|
|
236
|
+
cwd: workdir,
|
|
237
|
+
timeout: VALIDATE_TIMEOUT_MS,
|
|
238
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
239
|
+
}, (error, vStdout, vStderr) => {
|
|
240
|
+
activeExecProc = null;
|
|
241
|
+
if (error) {
|
|
242
|
+
res([`VALIDATION FAILED (exit ${error.code}):`, vStdout, vStderr].filter(Boolean).join('\n').slice(0, 8_000));
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
res('PASS');
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
activeExecProc = validationProc;
|
|
249
|
+
setActiveCanceller(() => {
|
|
250
|
+
try {
|
|
251
|
+
activeExecProc?.kill('SIGTERM');
|
|
252
|
+
}
|
|
253
|
+
catch { /* best effort */ }
|
|
254
|
+
activeExecProc = null;
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
let validateResult = await runValidationPromise();
|
|
258
|
+
ensureNotCancelled();
|
|
259
|
+
// Internal retry: if validation failed, re-run the agent with test errors injected
|
|
260
|
+
if (validateResult !== 'PASS' && !caTask.retryCount) {
|
|
261
|
+
caTask.retryCount = 1;
|
|
262
|
+
caTask.status = 'running';
|
|
263
|
+
caTask.validationOutput = validateResult;
|
|
264
|
+
writeCodeAgentTask(caTask);
|
|
265
|
+
addEvent(traceId, { type: 'validation', summary: 'Validation failed, retrying with error context', durationMs: Date.now() - startedAt.getTime() });
|
|
266
|
+
// Re-run agent with the validation errors appended to the prompt
|
|
267
|
+
const retryTask = `Fix build/test errors in the ${agent} codebase (workdir: ${workdir}).\n\nOriginal task summary: ${task.slice(0, 300)}\n\nErrors to fix:\n${validateResult.slice(0, 4_000)}`;
|
|
268
|
+
stdout = '';
|
|
269
|
+
stderr = '';
|
|
270
|
+
const { cmd: retryCmd, args: retryArgs } = buildCodeAgentArgs({
|
|
271
|
+
task: retryTask,
|
|
272
|
+
agent,
|
|
273
|
+
workdir,
|
|
274
|
+
model: input.model,
|
|
275
|
+
max_turns: input.max_turns,
|
|
276
|
+
});
|
|
277
|
+
const retryExitCode = await new Promise((resolveRetry, rejectRetry) => {
|
|
278
|
+
const spawnEnv = { ...process.env };
|
|
279
|
+
delete spawnEnv.CLAUDECODE;
|
|
280
|
+
const retryProc = spawn(retryCmd, retryArgs, {
|
|
281
|
+
cwd: workdir,
|
|
282
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
283
|
+
env: spawnEnv,
|
|
284
|
+
});
|
|
285
|
+
activeProc = retryProc;
|
|
286
|
+
let lastStatusWrite = 0;
|
|
287
|
+
retryProc.stdout.on('data', (chunk) => {
|
|
288
|
+
stdout += chunk.toString();
|
|
289
|
+
const now = Date.now();
|
|
290
|
+
if (now - lastStatusWrite > 3000) {
|
|
291
|
+
lastStatusWrite = now;
|
|
292
|
+
const retryParsed = parseStreamJsonForLive(stdout);
|
|
293
|
+
const live = stderr ? `[progress]\n${stderr.slice(-2000)}\n\n${retryParsed}` : retryParsed;
|
|
294
|
+
caTask.liveOutput = live;
|
|
295
|
+
writeCodeAgentTask(caTask);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
retryProc.stderr.on('data', (chunk) => {
|
|
299
|
+
stderr += chunk.toString();
|
|
300
|
+
const now = Date.now();
|
|
301
|
+
if (now - lastStatusWrite > 3000) {
|
|
302
|
+
lastStatusWrite = now;
|
|
303
|
+
const parsedOut = parseStreamJsonForLive(stdout);
|
|
304
|
+
const live = `[progress]\n${stderr.slice(-2000)}\n\n${parsedOut}`;
|
|
305
|
+
caTask.liveOutput = live;
|
|
306
|
+
writeCodeAgentTask(caTask);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
const retryTimer = setTimeout(() => {
|
|
310
|
+
retryProc.kill('SIGTERM');
|
|
311
|
+
// SIGKILL fallback after 5s
|
|
312
|
+
setTimeout(() => {
|
|
313
|
+
try {
|
|
314
|
+
retryProc.kill('SIGKILL');
|
|
315
|
+
}
|
|
316
|
+
catch { /* already dead */ }
|
|
317
|
+
}, 5000);
|
|
318
|
+
}, timeoutMs);
|
|
319
|
+
activeTimer = retryTimer;
|
|
320
|
+
setActiveCanceller(() => {
|
|
321
|
+
if (activeTimer)
|
|
322
|
+
clearTimeout(activeTimer);
|
|
323
|
+
activeTimer = null;
|
|
324
|
+
try {
|
|
325
|
+
activeProc?.kill('SIGTERM');
|
|
326
|
+
}
|
|
327
|
+
catch { /* best effort */ }
|
|
328
|
+
});
|
|
329
|
+
retryProc.on('close', (code) => {
|
|
330
|
+
if (activeTimer)
|
|
331
|
+
clearTimeout(activeTimer);
|
|
332
|
+
activeTimer = null;
|
|
333
|
+
activeProc = null;
|
|
334
|
+
if (cancelled || caTask.status === 'cancelled') {
|
|
335
|
+
rejectRetry(new Error(CANCELLED_MESSAGE));
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
resolveRetry(code);
|
|
339
|
+
});
|
|
340
|
+
retryProc.on('error', (err) => {
|
|
341
|
+
if (activeTimer)
|
|
342
|
+
clearTimeout(activeTimer);
|
|
343
|
+
activeTimer = null;
|
|
344
|
+
activeProc = null;
|
|
345
|
+
rejectRetry(err);
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
ensureNotCancelled();
|
|
349
|
+
if (retryExitCode === 0) {
|
|
350
|
+
if (agent === 'claude') {
|
|
351
|
+
const retryParsed = parseClaudeOutput(stdout);
|
|
352
|
+
agentOutput = retryParsed.text;
|
|
353
|
+
// Accumulate cost/tokens from retry run
|
|
354
|
+
if (retryParsed.totalCost != null)
|
|
355
|
+
caTask.totalCost = (caTask.totalCost ?? 0) + retryParsed.totalCost;
|
|
356
|
+
if (retryParsed.inputTokens != null)
|
|
357
|
+
caTask.inputTokens = (caTask.inputTokens ?? 0) + retryParsed.inputTokens;
|
|
358
|
+
if (retryParsed.outputTokens != null)
|
|
359
|
+
caTask.outputTokens = (caTask.outputTokens ?? 0) + retryParsed.outputTokens;
|
|
360
|
+
if (retryParsed.totalCost != null || retryParsed.inputTokens != null) {
|
|
361
|
+
recordUsage(buildUsageRecord({
|
|
362
|
+
model: input.model || 'claude',
|
|
363
|
+
provider: 'anthropic',
|
|
364
|
+
inputTokens: retryParsed.inputTokens ?? 0,
|
|
365
|
+
outputTokens: retryParsed.outputTokens ?? 0,
|
|
366
|
+
inputCost: 0,
|
|
367
|
+
outputCost: 0,
|
|
368
|
+
totalCost: retryParsed.totalCost ?? 0,
|
|
369
|
+
trigger: 'code_agent',
|
|
370
|
+
agentId: id,
|
|
371
|
+
}));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
agentOutput = stdout || '(no output)';
|
|
376
|
+
}
|
|
377
|
+
caTask.status = 'validating';
|
|
378
|
+
caTask.liveOutput = undefined;
|
|
379
|
+
writeCodeAgentTask(caTask);
|
|
380
|
+
validateResult = await runValidationPromise();
|
|
381
|
+
ensureNotCancelled();
|
|
382
|
+
}
|
|
383
|
+
// if retry exit code non-zero, fall through with original validateResult (still !== 'PASS')
|
|
384
|
+
}
|
|
385
|
+
const endedAt = new Date();
|
|
386
|
+
const duration = Math.round((endedAt.getTime() - startedAt.getTime()) / 1000);
|
|
387
|
+
if (validateResult !== 'PASS') {
|
|
388
|
+
addEvent(traceId, { type: 'validation', summary: 'Build/test validation failed', durationMs: Date.now() - startedAt.getTime() });
|
|
389
|
+
await endTrace(traceId, 'error');
|
|
390
|
+
Object.assign(caTask, {
|
|
391
|
+
status: 'failed',
|
|
392
|
+
endedAt: endedAt.toISOString(),
|
|
393
|
+
durationSeconds: duration,
|
|
394
|
+
exitCode,
|
|
395
|
+
validationPassed: false,
|
|
396
|
+
validationOutput: validateResult.slice(0, 8_000),
|
|
397
|
+
outputPreview: agentOutput.slice(0, 5000),
|
|
398
|
+
error: 'Validation failed',
|
|
399
|
+
});
|
|
400
|
+
writeCodeAgentTask(caTask);
|
|
401
|
+
if (!options?.skipNotification)
|
|
402
|
+
await notifyCodeAgentResult(caTask, (id) => getCodeAgent(id) ?? null);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
addEvent(traceId, { type: 'validation', summary: 'Build/test validation passed', durationMs: Date.now() - startedAt.getTime() });
|
|
406
|
+
await endTrace(traceId, 'ok');
|
|
407
|
+
Object.assign(caTask, {
|
|
408
|
+
status: 'completed',
|
|
409
|
+
endedAt: endedAt.toISOString(),
|
|
410
|
+
durationSeconds: duration,
|
|
411
|
+
exitCode,
|
|
412
|
+
validationPassed: true,
|
|
413
|
+
validationOutput: undefined,
|
|
414
|
+
outputPreview: agentOutput.slice(0, 5000),
|
|
415
|
+
});
|
|
416
|
+
writeCodeAgentTask(caTask);
|
|
417
|
+
if (!options?.skipNotification)
|
|
418
|
+
await notifyCodeAgentResult(caTask, (id) => getCodeAgent(id) ?? null);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
// No validation — mark complete
|
|
422
|
+
addEvent(traceId, { type: 'complete', summary: `${agent} completed (no validation)`, durationMs: Date.now() - startedAt.getTime() });
|
|
423
|
+
await endTrace(traceId, 'ok');
|
|
424
|
+
Object.assign(caTask, {
|
|
425
|
+
status: 'completed',
|
|
426
|
+
endedAt: new Date().toISOString(),
|
|
427
|
+
durationSeconds: Math.round((Date.now() - startedAt.getTime()) / 1000),
|
|
428
|
+
exitCode,
|
|
429
|
+
outputPreview: agentOutput.slice(0, 5000),
|
|
430
|
+
liveOutput: undefined,
|
|
431
|
+
});
|
|
432
|
+
writeCodeAgentTask(caTask);
|
|
433
|
+
if (!options?.skipNotification)
|
|
434
|
+
await notifyCodeAgentResult(caTask, (id) => getCodeAgent(id) ?? null);
|
|
435
|
+
}
|
|
436
|
+
catch (err) {
|
|
437
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
438
|
+
addEvent(traceId, { type: 'error', summary: errMsg.slice(0, 200), durationMs: Date.now() - startedAt.getTime() });
|
|
439
|
+
await endTrace(traceId, 'error');
|
|
440
|
+
Object.assign(caTask, {
|
|
441
|
+
status: errMsg.includes(CANCELLED_MESSAGE) || cancelled || caTask.status === 'cancelled'
|
|
442
|
+
? 'cancelled'
|
|
443
|
+
: errMsg.includes('timed out')
|
|
444
|
+
? 'timeout'
|
|
445
|
+
: 'failed',
|
|
446
|
+
endedAt: new Date().toISOString(),
|
|
447
|
+
durationSeconds: Math.round((Date.now() - startedAt.getTime()) / 1000),
|
|
448
|
+
error: errMsg,
|
|
449
|
+
liveOutput: undefined,
|
|
450
|
+
});
|
|
451
|
+
writeCodeAgentTask(caTask);
|
|
452
|
+
if (!options?.skipNotification && caTask.status !== 'cancelled')
|
|
453
|
+
await notifyCodeAgentResult(caTask, getCodeAgent);
|
|
454
|
+
}
|
|
455
|
+
finally {
|
|
456
|
+
if (activeTimer)
|
|
457
|
+
clearTimeout(activeTimer);
|
|
458
|
+
activeTimer = null;
|
|
459
|
+
activeProc = null;
|
|
460
|
+
activeExecProc = null;
|
|
461
|
+
deleteCodeAgentCanceller(id);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ToolConfig } from '../types.js';
|
|
2
|
+
import type { ExecuteToolContext } from '../tools/execute-context.js';
|
|
3
|
+
export type { CodeAgentTask, DecomposedSubtask, CodeAgentBackgroundOptions, BuildCodeAgentArgsInput, ValidationResult, ChildResult, } from './types.js';
|
|
4
|
+
export { CODE_AGENT_TIMEOUT_MS, VALIDATE_TIMEOUT_MS } from './types.js';
|
|
5
|
+
export { getActiveCodeAgents, getRecentCodeAgents, getAllCodeAgents, getCodeAgent, cancelCodeAgent, restoreCodeAgentTasks, getCodeAgentsDir, } from './registry.js';
|
|
6
|
+
export { runCodeAgentBackground, runValidation } from './executor.js';
|
|
7
|
+
export { runTeamOrchestrator, computeWaves, decomposeTask, synthesizeResults, } from './orchestrator.js';
|
|
8
|
+
export { setCodeAgentConfig, getCodeAgentConfig, buildCodeAgentArgs, resolveSelectedCodeAgent, resolveWorkdir, resolveModelAlias, readTeamState, } from './utils.js';
|
|
9
|
+
export { parseStreamJsonForLive, parseClaudeOutput, parseCodexOutput } from './parser.js';
|
|
10
|
+
export type { ClaudeOutputResult } from './parser.js';
|
|
11
|
+
/**
|
|
12
|
+
* Execute check_code_agent tool — list all or get details for one agent.
|
|
13
|
+
*/
|
|
14
|
+
export declare function executeCheckCodeAgent(input: Record<string, any>): string;
|
|
15
|
+
/**
|
|
16
|
+
* Execute code_with_agent tool - single agent mode.
|
|
17
|
+
*/
|
|
18
|
+
export declare function executeCodeWithAgent(input: Record<string, any>, config: ToolConfig, context?: ExecuteToolContext): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Execute code_with_team tool - multi-agent team mode.
|
|
21
|
+
*/
|
|
22
|
+
export declare function executeCodeWithTeam(input: Record<string, any>, config: ToolConfig, context?: ExecuteToolContext): Promise<string>;
|