skimpyclaw 0.3.6 → 0.3.9
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 +14 -6
- package/dist/__tests__/api.test.js +1 -0
- package/dist/__tests__/channels.test.js +1 -1
- package/dist/__tests__/code-agents-orchestrator.test.js +74 -7
- package/dist/__tests__/code-agents-preflight.test.d.ts +1 -0
- package/dist/__tests__/code-agents-preflight.test.js +88 -0
- package/dist/__tests__/code-agents-sandbox.test.d.ts +1 -0
- package/dist/__tests__/code-agents-sandbox.test.js +163 -0
- package/dist/__tests__/code-agents-utils.test.js +12 -1
- package/dist/__tests__/context-manager.test.d.ts +1 -0
- package/dist/__tests__/context-manager.test.js +236 -0
- package/dist/__tests__/package-manager-detection.test.js +5 -5
- package/dist/__tests__/setup.test.js +7 -5
- package/dist/__tests__/skills.test.js +2 -2
- package/dist/__tests__/structured-context.test.d.ts +1 -0
- package/dist/__tests__/structured-context.test.js +100 -0
- package/dist/__tests__/tools.test.js +65 -3
- package/dist/agent.js +4 -5
- package/dist/api.js +10 -58
- package/dist/audit.js +5 -51
- package/dist/channels/telegram/handlers.js +2 -60
- package/dist/channels/telegram/index.js +0 -7
- package/dist/channels.js +1 -1
- package/dist/cli.js +151 -16
- package/dist/code-agents/executor.d.ts +9 -4
- package/dist/code-agents/executor.js +187 -13
- package/dist/code-agents/index.d.ts +1 -1
- package/dist/code-agents/index.js +30 -22
- package/dist/code-agents/orchestrator.d.ts +8 -2
- package/dist/code-agents/orchestrator.js +318 -27
- package/dist/code-agents/structured-context.d.ts +7 -0
- package/dist/code-agents/structured-context.js +54 -0
- package/dist/code-agents/types.d.ts +2 -0
- package/dist/code-agents/utils.d.ts +4 -0
- package/dist/code-agents/utils.js +38 -2
- package/dist/code-agents/worktree.d.ts +40 -0
- package/dist/code-agents/worktree.js +215 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +5 -3
- package/dist/cron.js +18 -4
- package/dist/dashboard/assets/{index-CkonC7Cd.js → index-BoTHPby4.js} +20 -20
- package/dist/dashboard/assets/{index-EAg6lqF5.css → index-D4mufvBg.css} +1 -1
- package/dist/dashboard/index.html +2 -2
- package/dist/discord.js +4 -40
- package/dist/exec-approval.js +1 -1
- package/dist/file-lock.js +1 -1
- package/dist/gateway.js +3 -10
- package/dist/providers/anthropic.js +9 -5
- package/dist/providers/codex.js +10 -6
- package/dist/providers/context-manager.d.ts +22 -0
- package/dist/providers/context-manager.js +100 -0
- package/dist/providers/openai.js +9 -5
- package/dist/providers/types.d.ts +1 -0
- package/dist/security.js +9 -0
- package/dist/setup.js +122 -27
- package/dist/skills.js +9 -2
- package/dist/subagent.js +33 -2
- package/dist/tools/bash-tool.js +8 -0
- package/dist/tools/browser-tool.js +2 -1
- package/dist/tools/definitions.d.ts +0 -27
- package/dist/tools/definitions.js +0 -18
- package/dist/tools/execute-context.d.ts +4 -4
- package/dist/tools/file-tools.d.ts +1 -1
- package/dist/tools/file-tools.js +1 -1
- package/dist/tools.d.ts +5 -5
- package/dist/tools.js +87 -98
- package/dist/types.d.ts +14 -22
- package/dist/usage.d.ts +1 -0
- package/dist/usage.js +30 -46
- package/dist/utils.d.ts +18 -0
- package/dist/utils.js +71 -0
- package/dist/voice.js +9 -7
- package/package.json +26 -21
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// Code Agent Executor - Background execution logic
|
|
2
|
-
import { spawn, exec } from 'child_process';
|
|
2
|
+
import { spawn, exec, execSync } from 'child_process';
|
|
3
3
|
import { createWriteStream, existsSync, readFileSync } from 'fs';
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
// SKIMPYCLAW_ROOT for log paths
|
|
6
6
|
const SKIMPYCLAW_ROOT = join(import.meta.dirname || process.cwd(), '..', '..');
|
|
7
|
+
import { toErrorMessage } from '../utils.js';
|
|
7
8
|
import { VALIDATE_TIMEOUT_MS } from './types.js';
|
|
8
9
|
import { getCodeAgentsDir, ensureCodeAgentsDir, writeCodeAgentTask, setCodeAgentCanceller, deleteCodeAgentCanceller, getCodeAgent, } from './registry.js';
|
|
9
10
|
import { buildCodeAgentArgs, notifyCodeAgentResult } from './utils.js';
|
|
@@ -51,15 +52,184 @@ export function detectPackageManager(workdir) {
|
|
|
51
52
|
// 3. Fallback
|
|
52
53
|
return 'pnpm';
|
|
53
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Detect monorepo workspaces from package.json.
|
|
57
|
+
* Returns workspace glob patterns or null if not a monorepo.
|
|
58
|
+
*/
|
|
59
|
+
function getWorkspacePatterns(workdir) {
|
|
60
|
+
try {
|
|
61
|
+
const pkgPath = join(workdir, 'package.json');
|
|
62
|
+
if (!existsSync(pkgPath))
|
|
63
|
+
return null;
|
|
64
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
65
|
+
// yarn/npm: "workspaces": ["packages/*"] or "workspaces": { "packages": [...] }
|
|
66
|
+
const ws = pkg.workspaces;
|
|
67
|
+
if (Array.isArray(ws))
|
|
68
|
+
return ws;
|
|
69
|
+
if (ws && Array.isArray(ws.packages))
|
|
70
|
+
return ws.packages;
|
|
71
|
+
// pnpm: check pnpm-workspace.yaml
|
|
72
|
+
const pnpmWsPath = join(workdir, 'pnpm-workspace.yaml');
|
|
73
|
+
if (existsSync(pnpmWsPath)) {
|
|
74
|
+
const content = readFileSync(pnpmWsPath, 'utf-8');
|
|
75
|
+
const matches = content.match(/- ['"]?([^'"\n]+)['"]?/g);
|
|
76
|
+
if (matches)
|
|
77
|
+
return matches.map(m => m.replace(/^- ['"]?|['"]?$/g, ''));
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Find which monorepo packages have changed files (git diff).
|
|
87
|
+
* Returns package directories relative to workdir.
|
|
88
|
+
*/
|
|
89
|
+
function getChangedPackageDirs(workdir) {
|
|
90
|
+
try {
|
|
91
|
+
// Get changed files vs HEAD (staged + unstaged + untracked)
|
|
92
|
+
const diff = execSync('git diff --name-only HEAD 2>/dev/null; git diff --name-only --cached 2>/dev/null; git ls-files --others --exclude-standard 2>/dev/null', { cwd: workdir, timeout: 5000, encoding: 'utf-8' }).trim();
|
|
93
|
+
if (!diff)
|
|
94
|
+
return [];
|
|
95
|
+
const files = [...new Set(diff.split('\n').filter(Boolean))];
|
|
96
|
+
// Extract unique top-level package directories (e.g. "packages/image-studio/src/foo.ts" → "packages/image-studio")
|
|
97
|
+
const pkgDirs = new Set();
|
|
98
|
+
for (const f of files) {
|
|
99
|
+
const parts = f.split('/');
|
|
100
|
+
// Look for package.json at each depth to find package boundary
|
|
101
|
+
for (let depth = 1; depth <= Math.min(parts.length - 1, 4); depth++) {
|
|
102
|
+
const candidate = parts.slice(0, depth).join('/');
|
|
103
|
+
if (existsSync(join(workdir, candidate, 'package.json'))) {
|
|
104
|
+
pkgDirs.add(candidate);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return [...pkgDirs];
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Build scoped validation commands for a monorepo by detecting changed packages.
|
|
117
|
+
* Returns a combined command that builds/tests only affected packages, or null
|
|
118
|
+
* if this doesn't look like a monorepo or no packages were changed.
|
|
119
|
+
*/
|
|
120
|
+
function buildMonorepoValidationCommand(workdir) {
|
|
121
|
+
const wsPatterns = getWorkspacePatterns(workdir);
|
|
122
|
+
if (!wsPatterns)
|
|
123
|
+
return null;
|
|
124
|
+
const changedDirs = getChangedPackageDirs(workdir);
|
|
125
|
+
if (changedDirs.length === 0)
|
|
126
|
+
return null;
|
|
127
|
+
const pm = detectPackageManager(workdir);
|
|
128
|
+
const parts = [];
|
|
129
|
+
for (const dir of changedDirs) {
|
|
130
|
+
const pkgJsonPath = join(workdir, dir, 'package.json');
|
|
131
|
+
if (!existsSync(pkgJsonPath))
|
|
132
|
+
continue;
|
|
133
|
+
try {
|
|
134
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
135
|
+
const pkgName = pkg.name;
|
|
136
|
+
const scripts = pkg.scripts || {};
|
|
137
|
+
if (!pkgName)
|
|
138
|
+
continue;
|
|
139
|
+
// Build with workspace command
|
|
140
|
+
if (scripts.build) {
|
|
141
|
+
if (pm === 'pnpm')
|
|
142
|
+
parts.push(`pnpm --filter ${pkgName} run build`);
|
|
143
|
+
else if (pm === 'yarn')
|
|
144
|
+
parts.push(`yarn workspace ${pkgName} build`);
|
|
145
|
+
else if (pm === 'bun')
|
|
146
|
+
parts.push(`bun --filter ${pkgName} run build`);
|
|
147
|
+
else
|
|
148
|
+
parts.push(`npm -w ${pkgName} run build`);
|
|
149
|
+
}
|
|
150
|
+
// Test: prefer package-scoped test, fall back to root test runner scoped to path
|
|
151
|
+
if (scripts.test) {
|
|
152
|
+
if (pm === 'pnpm')
|
|
153
|
+
parts.push(`pnpm --filter ${pkgName} run test`);
|
|
154
|
+
else if (pm === 'yarn')
|
|
155
|
+
parts.push(`yarn workspace ${pkgName} test`);
|
|
156
|
+
else if (pm === 'bun')
|
|
157
|
+
parts.push(`bun --filter ${pkgName} run test`);
|
|
158
|
+
else
|
|
159
|
+
parts.push(`npm -w ${pkgName} run test`);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
// No package-level test script — try running root test scoped to the package path
|
|
163
|
+
// This handles monorepos like wp-calypso with `jest --testPathPattern`
|
|
164
|
+
const rootPkg = JSON.parse(readFileSync(join(workdir, 'package.json'), 'utf-8'));
|
|
165
|
+
const rootScripts = rootPkg.scripts || {};
|
|
166
|
+
// Check for common monorepo test patterns
|
|
167
|
+
if (rootScripts['test-packages']) {
|
|
168
|
+
if (pm === 'yarn')
|
|
169
|
+
parts.push(`yarn test-packages ${dir}`);
|
|
170
|
+
else
|
|
171
|
+
parts.push(`${pm} run test-packages ${dir}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch { /* skip this package */ }
|
|
176
|
+
}
|
|
177
|
+
if (parts.length === 0)
|
|
178
|
+
return null;
|
|
179
|
+
console.log(`[validation] Monorepo: scoped to ${changedDirs.length} package(s): ${changedDirs.join(', ')}`);
|
|
180
|
+
return parts.join(' && ');
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Walk up from a directory to find a monorepo root (directory with workspaces).
|
|
184
|
+
* Returns the root path or null if not inside a monorepo.
|
|
185
|
+
*/
|
|
186
|
+
function findMonorepoRoot(startDir) {
|
|
187
|
+
let dir = startDir;
|
|
188
|
+
const root = '/';
|
|
189
|
+
while (dir !== root) {
|
|
190
|
+
if (getWorkspacePatterns(dir))
|
|
191
|
+
return dir;
|
|
192
|
+
const parent = join(dir, '..');
|
|
193
|
+
if (parent === dir)
|
|
194
|
+
break;
|
|
195
|
+
dir = parent;
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
54
199
|
/**
|
|
55
200
|
* Build the validation command for a project directory.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
201
|
+
*
|
|
202
|
+
* Resolution order:
|
|
203
|
+
* 1. Per-project override from config `codeAgents.validationCommands`
|
|
204
|
+
* 2. Monorepo auto-detection: scope to changed packages only
|
|
205
|
+
* - Works both when workdir is the repo root AND when it's a package subdir
|
|
206
|
+
* 3. Auto-detect from package.json scripts (build + test)
|
|
207
|
+
* 4. Empty string (skip validation) if no scripts found
|
|
58
208
|
*/
|
|
59
|
-
export function buildValidationCommand(workdir) {
|
|
209
|
+
export function buildValidationCommand(workdir, validationCommands) {
|
|
210
|
+
// 1. Check per-project overrides
|
|
211
|
+
if (validationCommands) {
|
|
212
|
+
const dirName = workdir.split('/').pop() || '';
|
|
213
|
+
for (const [key, cmd] of Object.entries(validationCommands)) {
|
|
214
|
+
if (key === dirName || workdir === key || workdir.endsWith(`/${key}`)) {
|
|
215
|
+
return cmd;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// 2. Monorepo auto-detection — check workdir and parent dirs
|
|
220
|
+
const monorepoCmd = buildMonorepoValidationCommand(workdir);
|
|
221
|
+
if (monorepoCmd)
|
|
222
|
+
return monorepoCmd;
|
|
223
|
+
// Also check if workdir is a subpackage inside a monorepo
|
|
224
|
+
const monorepoRoot = findMonorepoRoot(workdir);
|
|
225
|
+
if (monorepoRoot && monorepoRoot !== workdir) {
|
|
226
|
+
const rootCmd = buildMonorepoValidationCommand(monorepoRoot);
|
|
227
|
+
if (rootCmd)
|
|
228
|
+
return rootCmd;
|
|
229
|
+
}
|
|
230
|
+
// 3. Simple project — use root package.json scripts
|
|
60
231
|
const pm = detectPackageManager(workdir);
|
|
61
232
|
const run = pm === 'npm' ? 'npm run' : pm;
|
|
62
|
-
// Check which scripts exist in package.json
|
|
63
233
|
let hasBuild = false;
|
|
64
234
|
let hasTest = false;
|
|
65
235
|
try {
|
|
@@ -77,19 +247,23 @@ export function buildValidationCommand(workdir) {
|
|
|
77
247
|
parts.push(`${run} build`);
|
|
78
248
|
if (hasTest)
|
|
79
249
|
parts.push(`${run} test`);
|
|
80
|
-
// If neither build nor test scripts exist, still try — the scripts
|
|
81
|
-
// might be defined in a workspace root or the commands may work anyway
|
|
82
250
|
if (parts.length === 0) {
|
|
83
|
-
|
|
251
|
+
return '';
|
|
84
252
|
}
|
|
85
253
|
return parts.join(' && ');
|
|
86
254
|
}
|
|
87
255
|
/** Run build/test validation. Shared by solo agents and team orchestrator. */
|
|
88
|
-
export function runValidation(workdir) {
|
|
89
|
-
const cmd = buildValidationCommand(workdir);
|
|
256
|
+
export function runValidation(workdir, validationCommands) {
|
|
257
|
+
const cmd = buildValidationCommand(workdir, validationCommands);
|
|
258
|
+
if (!cmd) {
|
|
259
|
+
// No build/test scripts found — nothing to validate, pass by default
|
|
260
|
+
return Promise.resolve({ passed: true, output: 'PASS (no build/test scripts found)' });
|
|
261
|
+
}
|
|
262
|
+
// If workdir is inside a monorepo, run from the repo root so workspace commands work
|
|
263
|
+
const execDir = findMonorepoRoot(workdir) || workdir;
|
|
90
264
|
return new Promise((resolve) => {
|
|
91
265
|
exec(cmd, {
|
|
92
|
-
cwd:
|
|
266
|
+
cwd: execDir,
|
|
93
267
|
timeout: VALIDATE_TIMEOUT_MS,
|
|
94
268
|
maxBuffer: 5 * 1024 * 1024,
|
|
95
269
|
}, (error, vStdout, vStderr) => {
|
|
@@ -315,7 +489,7 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
|
|
|
315
489
|
caTask.outputPreview = agentOutput.slice(0, 500);
|
|
316
490
|
caTask.liveOutput = undefined;
|
|
317
491
|
writeCodeAgentTask(caTask);
|
|
318
|
-
const validationCmd = buildValidationCommand(workdir);
|
|
492
|
+
const validationCmd = buildValidationCommand(workdir, options?.validationCommands);
|
|
319
493
|
const runValidationPromise = () => new Promise((res) => {
|
|
320
494
|
const validationProc = exec(validationCmd, {
|
|
321
495
|
cwd: workdir,
|
|
@@ -521,7 +695,7 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
|
|
|
521
695
|
await notifyCodeAgentResult(caTask, (id) => getCodeAgent(id) ?? null);
|
|
522
696
|
}
|
|
523
697
|
catch (err) {
|
|
524
|
-
const errMsg =
|
|
698
|
+
const errMsg = toErrorMessage(err);
|
|
525
699
|
addEvent(traceId, { type: 'error', summary: errMsg.slice(0, 200), durationMs: Date.now() - startedAt.getTime() });
|
|
526
700
|
await endTrace(traceId, 'error');
|
|
527
701
|
Object.assign(caTask, {
|
|
@@ -4,7 +4,7 @@ export type { CodeAgentTask, DecomposedSubtask, CodeAgentBackgroundOptions, Buil
|
|
|
4
4
|
export { CODE_AGENT_TIMEOUT_MS, VALIDATE_TIMEOUT_MS } from './types.js';
|
|
5
5
|
export { getActiveCodeAgents, getRecentCodeAgents, getAllCodeAgents, getCodeAgent, cancelCodeAgent, restoreCodeAgentTasks, getCodeAgentsDir, } from './registry.js';
|
|
6
6
|
export { runCodeAgentBackground, runValidation } from './executor.js';
|
|
7
|
-
export { runTeamOrchestrator, computeWaves, decomposeTask, synthesizeResults, } from './orchestrator.js';
|
|
7
|
+
export { runTeamOrchestrator, computeWaves, decomposeTask, synthesizeResults, gatherCodebaseContext, } from './orchestrator.js';
|
|
8
8
|
export { setCodeAgentConfig, getCodeAgentConfig, buildCodeAgentArgs, resolveSelectedCodeAgent, resolveWorkdir, resolveModelAlias, readTeamState, } from './utils.js';
|
|
9
9
|
export { parseStreamJsonForLive, parseClaudeOutput, parseCodexOutput } from './parser.js';
|
|
10
10
|
export type { ClaudeOutputResult } from './parser.js';
|
|
@@ -5,7 +5,7 @@ import { isPathAllowed } from '../tools/path-utils.js';
|
|
|
5
5
|
import { getNextCodeAgentId, storeCodeAgentTask, writeCodeAgentTask, getActiveCodeAgents, getRecentCodeAgents, getCodeAgent, } from './registry.js';
|
|
6
6
|
import { runCodeAgentBackground } from './executor.js';
|
|
7
7
|
import { runTeamOrchestrator } from './orchestrator.js';
|
|
8
|
-
import { resolveSelectedCodeAgent, resolveWorkdir, resolveModelAlias, } from './utils.js';
|
|
8
|
+
import { resolveSelectedCodeAgent, resolveWorkdir, resolveModelAlias, getCodingCliPreflightError, } from './utils.js';
|
|
9
9
|
// Re-export timeout constants
|
|
10
10
|
export { CODE_AGENT_TIMEOUT_MS, VALIDATE_TIMEOUT_MS } from './types.js';
|
|
11
11
|
// Re-export registry functions
|
|
@@ -13,7 +13,7 @@ export { getActiveCodeAgents, getRecentCodeAgents, getAllCodeAgents, getCodeAgen
|
|
|
13
13
|
// Re-export executor functions
|
|
14
14
|
export { runCodeAgentBackground, runValidation } from './executor.js';
|
|
15
15
|
// Re-export orchestrator functions
|
|
16
|
-
export { runTeamOrchestrator, computeWaves, decomposeTask, synthesizeResults, } from './orchestrator.js';
|
|
16
|
+
export { runTeamOrchestrator, computeWaves, decomposeTask, synthesizeResults, gatherCodebaseContext, } from './orchestrator.js';
|
|
17
17
|
// Re-export utility functions
|
|
18
18
|
export { setCodeAgentConfig, getCodeAgentConfig, buildCodeAgentArgs, resolveSelectedCodeAgent, resolveWorkdir, resolveModelAlias, readTeamState, } from './utils.js';
|
|
19
19
|
// Re-export parser functions
|
|
@@ -80,7 +80,7 @@ export async function executeCodeWithAgent(input, config, context) {
|
|
|
80
80
|
catch { /* gateway not running */ }
|
|
81
81
|
}
|
|
82
82
|
const resolvedModel = resolveModelAlias(rawModel, context?.fullConfig?.models?.aliases);
|
|
83
|
-
const configDefault = context?.fullConfig?.
|
|
83
|
+
const configDefault = context?.fullConfig?.codeAgents?.defaultAgent || 'claude';
|
|
84
84
|
const requestedAgent = input.agent;
|
|
85
85
|
const agent = resolveSelectedCodeAgent(requestedAgent, configDefault, resolvedModel);
|
|
86
86
|
if (!agent) {
|
|
@@ -99,12 +99,15 @@ export async function executeCodeWithAgent(input, config, context) {
|
|
|
99
99
|
: '';
|
|
100
100
|
return `Error: Working directory not allowed. Permitted: ${config.allowedPaths.join(', ')}${projectNames}`;
|
|
101
101
|
}
|
|
102
|
-
// Concurrency check
|
|
103
|
-
const maxConcurrent = context?.fullConfig?.
|
|
102
|
+
// Concurrency check
|
|
103
|
+
const maxConcurrent = context?.fullConfig?.codeAgents?.maxConcurrent ?? 5;
|
|
104
104
|
const activeCount = getActiveCodeAgents().length;
|
|
105
105
|
if (activeCount >= maxConcurrent) {
|
|
106
|
-
return `Error: Concurrency limit reached (${activeCount}/${maxConcurrent} coding agents running). Wait for one to finish or increase
|
|
106
|
+
return `Error: Concurrency limit reached (${activeCount}/${maxConcurrent} coding agents running). Wait for one to finish or increase codeAgents.maxConcurrent.`;
|
|
107
107
|
}
|
|
108
|
+
const cliPreflightError = getCodingCliPreflightError();
|
|
109
|
+
if (cliPreflightError)
|
|
110
|
+
return cliPreflightError;
|
|
108
111
|
const validate = input.validate !== false; // default true
|
|
109
112
|
// Create task with unique ID
|
|
110
113
|
const id = getNextCodeAgentId();
|
|
@@ -122,8 +125,14 @@ export async function executeCodeWithAgent(input, config, context) {
|
|
|
122
125
|
storeCodeAgentTask(caTask);
|
|
123
126
|
writeCodeAgentTask(caTask);
|
|
124
127
|
// Fire-and-forget: spawn background process
|
|
125
|
-
const
|
|
126
|
-
|
|
128
|
+
const configTimeout = context?.fullConfig?.codeAgents?.timeoutMinutes ?? 30;
|
|
129
|
+
const soloTimeout = Math.min(input.timeout_minutes || configTimeout, 60);
|
|
130
|
+
const resolvedInput = { ...input, model: resolvedModel, timeout_minutes: soloTimeout };
|
|
131
|
+
runCodeAgentBackground(id, agent, task, workdir, validate, resolvedInput, startedAt, {
|
|
132
|
+
defaultTimeoutMinutes: soloTimeout,
|
|
133
|
+
maxTimeoutMinutes: 60,
|
|
134
|
+
validationCommands: context?.fullConfig?.codeAgents?.validationCommands,
|
|
135
|
+
}).catch((err) => {
|
|
127
136
|
console.error(`[code-agent] Background error for ${id}:`, err);
|
|
128
137
|
});
|
|
129
138
|
const taskPreview = task.length > 100 ? task.slice(0, 100) + '...' : task;
|
|
@@ -136,18 +145,14 @@ export async function executeCodeWithTeam(input, config, context) {
|
|
|
136
145
|
const task = input.task;
|
|
137
146
|
if (!task)
|
|
138
147
|
return 'Error: task is required';
|
|
139
|
-
// Resolve model alias
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
catch { /* gateway not running */ }
|
|
148
|
-
}
|
|
149
|
-
const resolvedModel = resolveModelAlias(rawTeamModel, context?.fullConfig?.models?.aliases);
|
|
150
|
-
const configDefault = context?.fullConfig?.subagents?.defaultCodeAgent || 'claude';
|
|
148
|
+
// Resolve model alias. Only fall back to session model when an explicit model
|
|
149
|
+
// was requested — otherwise the session model (e.g. gpt-5.3-codex) would
|
|
150
|
+
// override agent selection even when the user wants claude.
|
|
151
|
+
const rawTeamModel = input.model;
|
|
152
|
+
const resolvedModel = rawTeamModel
|
|
153
|
+
? resolveModelAlias(rawTeamModel, context?.fullConfig?.models?.aliases)
|
|
154
|
+
: undefined;
|
|
155
|
+
const configDefault = context?.fullConfig?.codeAgents?.defaultAgent || 'claude';
|
|
151
156
|
const requestedAgent = input.agent;
|
|
152
157
|
const agent = resolveSelectedCodeAgent(requestedAgent, configDefault, resolvedModel);
|
|
153
158
|
if (!agent) {
|
|
@@ -167,13 +172,16 @@ export async function executeCodeWithTeam(input, config, context) {
|
|
|
167
172
|
: '';
|
|
168
173
|
return `Error: Working directory not allowed. Permitted: ${config.allowedPaths.join(', ')}${projectNames}`;
|
|
169
174
|
}
|
|
175
|
+
const validate = input.validate !== false;
|
|
170
176
|
// Concurrency check — need room for teamSize children
|
|
171
|
-
const maxConcurrent = context?.fullConfig?.
|
|
177
|
+
const maxConcurrent = context?.fullConfig?.codeAgents?.maxConcurrent ?? 5;
|
|
172
178
|
const activeCount = getActiveCodeAgents().length;
|
|
173
179
|
if (activeCount + teamSize > maxConcurrent) {
|
|
174
180
|
return `Error: Concurrency limit — need ${teamSize} slots but only ${maxConcurrent - activeCount} available (${activeCount}/${maxConcurrent} running). Wait for agents to finish.`;
|
|
175
181
|
}
|
|
176
|
-
const
|
|
182
|
+
const cliPreflightError = getCodingCliPreflightError();
|
|
183
|
+
if (cliPreflightError)
|
|
184
|
+
return cliPreflightError;
|
|
177
185
|
// Create parent task
|
|
178
186
|
const id = getNextCodeAgentId();
|
|
179
187
|
const startedAt = new Date();
|
|
@@ -7,16 +7,22 @@ import type { DecomposedSubtask, ChildResult } from './types.js';
|
|
|
7
7
|
* Throws if there's a cycle in the dependency graph.
|
|
8
8
|
*/
|
|
9
9
|
export declare function computeWaves(subtasks: DecomposedSubtask[]): number[][];
|
|
10
|
+
/**
|
|
11
|
+
* Gather lightweight codebase context to improve task decomposition.
|
|
12
|
+
* Returns a short summary of the project structure (file tree, package.json scripts).
|
|
13
|
+
* Capped at ~2000 chars to keep the decomposition prompt small.
|
|
14
|
+
*/
|
|
15
|
+
export declare function gatherCodebaseContext(workdir: string): string;
|
|
10
16
|
/**
|
|
11
17
|
* Use a quick model call to decompose a complex task into N subtasks with optional dependency info.
|
|
12
18
|
* Falls back to numbered subtask splitting on parse error.
|
|
13
19
|
* Falls back to all-independent if dependency info is missing or invalid.
|
|
14
20
|
*/
|
|
15
|
-
export declare function decomposeTask(task: string, teamSize: number, config: Config): Promise<DecomposedSubtask[]>;
|
|
21
|
+
export declare function decomposeTask(task: string, teamSize: number, config: Config, workdir?: string): Promise<DecomposedSubtask[]>;
|
|
16
22
|
/**
|
|
17
23
|
* Use a quick model call to synthesize results from multiple subtask completions.
|
|
18
24
|
*/
|
|
19
|
-
export declare function synthesizeResults(originalTask: string, results: ChildResult[], config: Config): Promise<string>;
|
|
25
|
+
export declare function synthesizeResults(originalTask: string, results: ChildResult[], config: Config, workdir?: string): Promise<string>;
|
|
20
26
|
/**
|
|
21
27
|
* Team orchestrator — decomposes task, spawns parallel agents, monitors, synthesizes.
|
|
22
28
|
*/
|