@wrongstack/core 0.6.4 → 0.6.6
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/{agent-bridge-eb7qnNrd.d.ts → agent-bridge-BBXK_ppx.d.ts} +1 -1
- package/dist/agent-subagent-runner-DsSm9lKN.d.ts +174 -0
- package/dist/{compactor-RIPuTtWK.d.ts → compactor-C8NhpSt5.d.ts} +1 -1
- package/dist/{config-BGGuP_Ar.d.ts → config-DfC6g6KV.d.ts} +1 -1
- package/dist/{context-CDRyrkKQ.d.ts → context-DN5v-uQX.d.ts} +11 -0
- package/dist/coordination/index.d.ts +10 -9
- package/dist/coordination/index.js +113 -20
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +22 -21
- package/dist/defaults/index.js +1359 -919
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-BHuIHekD.d.ts → events-CJqwQl8G.d.ts} +17 -1
- package/dist/execution/index.d.ts +116 -13
- package/dist/execution/index.js +1438 -131
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{goal-store-DVCfj7Ff.d.ts → goal-store-HHgaq5ue.d.ts} +3 -3
- package/dist/{index-CPcDqvZh.d.ts → index-CXnWsGBp.d.ts} +11 -175
- package/dist/{index-BOn9NK7D.d.ts → index-DcnXDPdY.d.ts} +6 -6
- package/dist/index.d.ts +29 -28
- package/dist/index.js +1105 -666
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-DBdh3cee.d.ts → mcp-servers-CevFHHM1.d.ts} +3 -3
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.js +5 -16
- package/dist/models/index.js.map +1 -1
- package/dist/{multi-agent-CxSb-9dQ.d.ts → multi-agent-D5IbASk_.d.ts} +16 -4
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-CMGNadvq.d.ts → path-resolver-CBx_q1HA.d.ts} +2 -2
- package/dist/{plan-templates-BJflQY2i.d.ts → plan-templates-BEOllUJV.d.ts} +5 -4
- package/dist/{provider-runner-BFgNXpaP.d.ts → provider-runner-Byh5TcJs.d.ts} +3 -3
- package/dist/{retry-policy-LKS8MHsB.d.ts → retry-policy-BZSIMxrJ.d.ts} +1 -1
- package/dist/sdd/index.d.ts +9 -4
- package/dist/sdd/index.js +21 -0
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-scrubber-CfMdAJ_l.d.ts → secret-scrubber-CT7wefiO.d.ts} +1 -1
- package/dist/{secret-scrubber-BzQR5BiL.d.ts → secret-scrubber-I0QHY_ob.d.ts} +1 -1
- package/dist/security/index.d.ts +3 -3
- package/dist/{selector-C7HqnZJU.d.ts → selector-DDb_mq9X.d.ts} +1 -1
- package/dist/{session-reader-CzfRA6Vk.d.ts → session-reader-B9nVkziM.d.ts} +1 -1
- package/dist/storage/index.d.ts +6 -6
- package/dist/storage/index.js +29 -3
- package/dist/storage/index.js.map +1 -1
- package/dist/{system-prompt-Dl2QY1_B.d.ts → system-prompt-gL06H9P4.d.ts} +1 -1
- package/dist/{task-graph-BITvWt4t.d.ts → task-graph-D1YQbpxF.d.ts} +1 -0
- package/dist/{tool-executor-FoxBjULX.d.ts → tool-executor-BF7QfYVE.d.ts} +4 -4
- package/dist/types/index.d.ts +16 -16
- package/dist/types/index.js +5 -16
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/package.json +1 -1
- package/skills/audit-log/SKILL.md +57 -28
- package/skills/bug-hunter/SKILL.md +85 -61
- package/skills/git-flow/SKILL.md +73 -18
- package/skills/multi-agent/SKILL.md +69 -40
- package/skills/node-modern/SKILL.md +111 -19
- package/skills/prompt-engineering/SKILL.md +97 -16
- package/skills/react-modern/SKILL.md +104 -18
- package/skills/refactor-planner/SKILL.md +73 -43
- package/skills/sdd/SKILL.md +123 -165
- package/skills/security-scanner/SKILL.md +95 -93
- package/skills/skill-creator/SKILL.md +58 -25
- package/skills/typescript-strict/SKILL.md +107 -15
package/dist/execution/index.js
CHANGED
|
@@ -2,7 +2,8 @@ import { execFile } from 'child_process';
|
|
|
2
2
|
import { promisify } from 'util';
|
|
3
3
|
import * as fs from 'fs/promises';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
-
import { randomBytes } from 'crypto';
|
|
5
|
+
import { randomUUID, randomBytes } from 'crypto';
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
6
7
|
|
|
7
8
|
// src/utils/token-estimate.ts
|
|
8
9
|
var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
@@ -1788,23 +1789,33 @@ var EternalAutonomyEngine = class {
|
|
|
1788
1789
|
* `agent.run()` avoids race conditions on the shared Context.
|
|
1789
1790
|
*/
|
|
1790
1791
|
async runOneIteration() {
|
|
1792
|
+
const emit = (stage) => {
|
|
1793
|
+
this.opts.onStage?.(stage);
|
|
1794
|
+
};
|
|
1791
1795
|
const goal = await loadGoal(this.goalPath);
|
|
1792
1796
|
if (!goal) {
|
|
1797
|
+
emit({ phase: "stopped" });
|
|
1793
1798
|
this.stopRequested = true;
|
|
1794
1799
|
return false;
|
|
1795
1800
|
}
|
|
1796
1801
|
const missionState = goal.goalState ?? "active";
|
|
1797
1802
|
if (missionState !== "active") {
|
|
1803
|
+
emit({ phase: missionState === "paused" ? "paused" : "stopped" });
|
|
1798
1804
|
this.stopRequested = true;
|
|
1799
1805
|
return false;
|
|
1800
1806
|
}
|
|
1807
|
+
emit({ phase: "decide", reason: "picking next task" });
|
|
1801
1808
|
const action = await this.decide(goal);
|
|
1802
1809
|
if (!action) {
|
|
1803
1810
|
if (!this.stopRequested) {
|
|
1811
|
+
emit({ phase: "sleep", ms: 5e3 });
|
|
1804
1812
|
await sleep(5e3);
|
|
1813
|
+
} else {
|
|
1814
|
+
emit({ phase: "stopped" });
|
|
1805
1815
|
}
|
|
1806
1816
|
return false;
|
|
1807
1817
|
}
|
|
1818
|
+
emit({ phase: "execute", task: action.task });
|
|
1808
1819
|
const ctrl = new AbortController();
|
|
1809
1820
|
this.currentCtrl = ctrl;
|
|
1810
1821
|
const timer = setTimeout(
|
|
@@ -1882,6 +1893,7 @@ var EternalAutonomyEngine = class {
|
|
|
1882
1893
|
tokens,
|
|
1883
1894
|
costUsd
|
|
1884
1895
|
});
|
|
1896
|
+
emit({ phase: "reflect", status, note });
|
|
1885
1897
|
let iterationIndex = 0;
|
|
1886
1898
|
try {
|
|
1887
1899
|
const reloaded = await loadGoal(this.goalPath);
|
|
@@ -1903,6 +1915,7 @@ var EternalAutonomyEngine = class {
|
|
|
1903
1915
|
this.consecutiveTransientRetries++;
|
|
1904
1916
|
const delay = this.computeTransientBackoffMs();
|
|
1905
1917
|
if (delay > 0) {
|
|
1918
|
+
emit({ phase: "sleep", ms: delay });
|
|
1906
1919
|
await this.sleepInterruptible(delay);
|
|
1907
1920
|
}
|
|
1908
1921
|
return false;
|
|
@@ -1916,6 +1929,9 @@ var EternalAutonomyEngine = class {
|
|
|
1916
1929
|
return false;
|
|
1917
1930
|
}
|
|
1918
1931
|
this.consecutiveTransientRetries = 0;
|
|
1932
|
+
const cycleGapMs = this.opts.cycleGapMs ?? 1e3;
|
|
1933
|
+
emit({ phase: "sleep", ms: cycleGapMs });
|
|
1934
|
+
await sleep(cycleGapMs);
|
|
1919
1935
|
if (GOAL_COMPLETE_MARKER.test(finalText)) {
|
|
1920
1936
|
await this.markGoalCompleted(action, finalText);
|
|
1921
1937
|
this.stopRequested = true;
|
|
@@ -2231,140 +2247,418 @@ function sleep(ms) {
|
|
|
2231
2247
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2232
2248
|
}
|
|
2233
2249
|
|
|
2234
|
-
// src/
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2250
|
+
// src/coordination/subagent-budget.ts
|
|
2251
|
+
var BudgetExceededError = class extends Error {
|
|
2252
|
+
kind;
|
|
2253
|
+
limit;
|
|
2254
|
+
observed;
|
|
2255
|
+
constructor(kind, limit, observed) {
|
|
2256
|
+
super(`Budget exceeded: ${kind} (limit=${limit}, observed=${observed})`);
|
|
2257
|
+
this.name = "BudgetExceededError";
|
|
2258
|
+
this.kind = kind;
|
|
2259
|
+
this.limit = limit;
|
|
2260
|
+
this.observed = observed;
|
|
2261
|
+
}
|
|
2262
|
+
};
|
|
2263
|
+
var BudgetThresholdSignal = class extends Error {
|
|
2264
|
+
kind;
|
|
2265
|
+
limit;
|
|
2266
|
+
used;
|
|
2267
|
+
/** Resolves to 'extend' (with optional new limits) or 'stop' */
|
|
2268
|
+
decision;
|
|
2269
|
+
constructor(kind, limit, used, decision) {
|
|
2270
|
+
super(`Budget soft limit: ${kind} (limit=${limit}, used=${used})`);
|
|
2271
|
+
this.name = "BudgetThresholdSignal";
|
|
2272
|
+
this.kind = kind;
|
|
2273
|
+
this.limit = limit;
|
|
2274
|
+
this.used = used;
|
|
2275
|
+
this.decision = decision;
|
|
2276
|
+
}
|
|
2277
|
+
};
|
|
2278
|
+
var SubagentBudget = class _SubagentBudget {
|
|
2279
|
+
limits;
|
|
2280
|
+
iterations = 0;
|
|
2281
|
+
toolCalls = 0;
|
|
2282
|
+
tokenInput = 0;
|
|
2283
|
+
tokenOutput = 0;
|
|
2284
|
+
costUsd = 0;
|
|
2285
|
+
startTime = null;
|
|
2286
|
+
_onThreshold;
|
|
2287
|
+
/**
|
|
2288
|
+
* Tracks which budget kinds currently have an extension request
|
|
2289
|
+
* in flight. While a kind is here, further `checkLimit` calls for the
|
|
2290
|
+
* same kind are no-ops — without this dedup, every `recordIteration`
|
|
2291
|
+
* after the limit is reached spawns a fresh decision Promise (until
|
|
2292
|
+
* the first one lands and patches limits), flooding the FleetBus
|
|
2293
|
+
* with redundant threshold events. Cleared in `checkLimitAsync`'s
|
|
2294
|
+
* `finally`.
|
|
2295
|
+
*/
|
|
2296
|
+
pendingExtensions = /* @__PURE__ */ new Set();
|
|
2297
|
+
/**
|
|
2298
|
+
* Hard cap on how long `checkLimitAsync` waits for the coordinator to
|
|
2299
|
+
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
2300
|
+
* or hung listener (Director not built / event filter detached mid-run)
|
|
2301
|
+
* leaves the budget over-limit and never enforces anything, since
|
|
2302
|
+
* `checkLimit` returns synchronously via `void this.checkLimitAsync`.
|
|
2303
|
+
* Hardcoded for now — most fleets set their own per-task timeout that
|
|
2304
|
+
* dwarfs this anyway. 30 s matches the existing event-emit timeoutMs.
|
|
2305
|
+
*/
|
|
2306
|
+
static DECISION_TIMEOUT_MS = 3e4;
|
|
2307
|
+
/**
|
|
2308
|
+
* Injected by the runner when wiring the budget to its EventBus.
|
|
2309
|
+
* Used by `checkLimitAsync` to emit `budget.threshold_reached` events.
|
|
2310
|
+
*/
|
|
2311
|
+
_events;
|
|
2312
|
+
/**
|
|
2313
|
+
* Optional callback for soft-limit handling. When set, the budget will
|
|
2314
|
+
* call this instead of throwing when a limit is first reached. The
|
|
2315
|
+
* handler decides whether to throw, continue, or ask the coordinator.
|
|
2316
|
+
*/
|
|
2317
|
+
get onThreshold() {
|
|
2318
|
+
return this._onThreshold;
|
|
2319
|
+
}
|
|
2320
|
+
set onThreshold(fn) {
|
|
2321
|
+
this._onThreshold = fn;
|
|
2322
|
+
}
|
|
2323
|
+
constructor(limits = {}) {
|
|
2324
|
+
this.limits = Object.freeze({ ...limits });
|
|
2325
|
+
}
|
|
2326
|
+
start() {
|
|
2327
|
+
this.startTime = Date.now();
|
|
2328
|
+
}
|
|
2329
|
+
/** Returns true if we're within 10% of any limit — useful for pre-flight checks. */
|
|
2330
|
+
isNearLimit() {
|
|
2331
|
+
const { maxIterations, maxToolCalls, maxTokens, maxCostUsd } = this.limits;
|
|
2332
|
+
if (maxIterations && this.iterations >= maxIterations * 0.9) return true;
|
|
2333
|
+
if (maxToolCalls && this.toolCalls >= maxToolCalls * 0.9) return true;
|
|
2334
|
+
if (maxTokens && this.tokenInput + this.tokenOutput >= maxTokens * 0.9) return true;
|
|
2335
|
+
if (maxCostUsd && this.costUsd >= maxCostUsd * 0.9) return true;
|
|
2336
|
+
return false;
|
|
2337
|
+
}
|
|
2338
|
+
/**
|
|
2339
|
+
* Synchronous budget check — always throws synchronously so callers
|
|
2340
|
+
* (especially test event handlers using `expect().toThrow()`) get an
|
|
2341
|
+
* unhandled rejection when the budget is exceeded without a handler.
|
|
2342
|
+
* When `onThreshold` IS configured, the actual async threshold-handling
|
|
2343
|
+
* is dispatched as a fire-and-forget promise.
|
|
2344
|
+
*/
|
|
2345
|
+
checkLimit(kind, used, limit) {
|
|
2346
|
+
if (!this._onThreshold) {
|
|
2347
|
+
throw new BudgetExceededError(kind, limit, used);
|
|
2348
|
+
}
|
|
2349
|
+
const bus = this._events;
|
|
2350
|
+
if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
|
|
2351
|
+
throw new BudgetExceededError(kind, limit, used);
|
|
2352
|
+
}
|
|
2353
|
+
if (this.pendingExtensions.has(kind)) return;
|
|
2354
|
+
this.pendingExtensions.add(kind);
|
|
2355
|
+
const decision = this.negotiateExtension(kind, used, limit);
|
|
2356
|
+
throw new BudgetThresholdSignal(kind, limit, used, decision);
|
|
2357
|
+
}
|
|
2358
|
+
/**
|
|
2359
|
+
* Drive the threshold handler to a decision. Resolves with `'stop'`
|
|
2360
|
+
* (signal the runner to abort) or `{ extend: ... }` (limits already
|
|
2361
|
+
* patched in-place; the runner should not abort). Always releases the
|
|
2362
|
+
* `pendingExtensions` slot in `finally`.
|
|
2363
|
+
*
|
|
2364
|
+
* The 'continue' return from a sync handler is treated as
|
|
2365
|
+
* `{ extend: {} }` — keep going without patching, next overrun will
|
|
2366
|
+
* fire a fresh signal.
|
|
2367
|
+
*/
|
|
2368
|
+
async negotiateExtension(kind, used, limit) {
|
|
2240
2369
|
try {
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2370
|
+
const result = this._onThreshold({
|
|
2371
|
+
kind,
|
|
2372
|
+
used,
|
|
2373
|
+
limit,
|
|
2374
|
+
// Inject a requestDecision helper the handler can call to emit the
|
|
2375
|
+
// budget.threshold_reached event and wait for the coordinator's verdict.
|
|
2376
|
+
// A hard fallback timer guarantees the promise eventually resolves
|
|
2377
|
+
// even if no listener responds — without it, an absent/detached
|
|
2378
|
+
// Director would leave the budget permanently in "asking" state.
|
|
2379
|
+
requestDecision: () => {
|
|
2380
|
+
const bus = this._events;
|
|
2381
|
+
if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
|
|
2382
|
+
return Promise.resolve("stop");
|
|
2383
|
+
}
|
|
2384
|
+
return new Promise((resolve) => {
|
|
2385
|
+
let resolved = false;
|
|
2386
|
+
const respond = (d) => {
|
|
2387
|
+
if (resolved) return;
|
|
2388
|
+
resolved = true;
|
|
2389
|
+
resolve(d);
|
|
2390
|
+
};
|
|
2391
|
+
const fallback = setTimeout(
|
|
2392
|
+
() => respond("stop"),
|
|
2393
|
+
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
2394
|
+
);
|
|
2395
|
+
bus.emit("budget.threshold_reached", {
|
|
2396
|
+
kind,
|
|
2397
|
+
used,
|
|
2398
|
+
limit,
|
|
2399
|
+
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
2400
|
+
extend: (extra) => {
|
|
2401
|
+
clearTimeout(fallback);
|
|
2402
|
+
respond({ extend: extra });
|
|
2403
|
+
},
|
|
2404
|
+
deny: () => {
|
|
2405
|
+
clearTimeout(fallback);
|
|
2406
|
+
respond("stop");
|
|
2407
|
+
}
|
|
2408
|
+
});
|
|
2409
|
+
});
|
|
2410
|
+
}
|
|
2411
|
+
});
|
|
2412
|
+
if (result === "throw") return "stop";
|
|
2413
|
+
if (result === "continue") return { extend: {} };
|
|
2414
|
+
const decision = await result;
|
|
2415
|
+
if (decision === "stop") return "stop";
|
|
2416
|
+
const ext = decision.extend;
|
|
2417
|
+
if (ext.maxIterations !== void 0) {
|
|
2418
|
+
this.limits.maxIterations = ext.maxIterations;
|
|
2419
|
+
}
|
|
2420
|
+
if (ext.maxToolCalls !== void 0) {
|
|
2421
|
+
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
2422
|
+
}
|
|
2423
|
+
if (ext.maxTokens !== void 0) {
|
|
2424
|
+
this.limits.maxTokens = ext.maxTokens;
|
|
2425
|
+
}
|
|
2426
|
+
if (ext.maxCostUsd !== void 0) {
|
|
2427
|
+
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
2428
|
+
}
|
|
2429
|
+
if (ext.timeoutMs !== void 0) {
|
|
2430
|
+
this.limits.timeoutMs = ext.timeoutMs;
|
|
2431
|
+
}
|
|
2432
|
+
return decision;
|
|
2433
|
+
} finally {
|
|
2434
|
+
this.pendingExtensions.delete(kind);
|
|
2244
2435
|
}
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
"",
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
"",
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
"
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2436
|
+
}
|
|
2437
|
+
recordIteration() {
|
|
2438
|
+
this.iterations++;
|
|
2439
|
+
if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
|
|
2440
|
+
void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
recordToolCall() {
|
|
2444
|
+
this.toolCalls++;
|
|
2445
|
+
if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
|
|
2446
|
+
void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
recordUsage(usage, costUsd = 0) {
|
|
2450
|
+
this.tokenInput += usage.input;
|
|
2451
|
+
this.tokenOutput += usage.output;
|
|
2452
|
+
this.costUsd += costUsd;
|
|
2453
|
+
const totalTokens = this.tokenInput + this.tokenOutput;
|
|
2454
|
+
if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
|
|
2455
|
+
void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
|
|
2456
|
+
}
|
|
2457
|
+
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
2458
|
+
void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
/**
|
|
2462
|
+
* Wall-clock budget check. Unlike other limits, timeout is treated as a
|
|
2463
|
+
* warning-only event — it NEVER hard-stops the subagent. When the
|
|
2464
|
+
* elapsed time exceeds timeoutMs, emits `budget.threshold_reached` with
|
|
2465
|
+
* kind='timeout' so the Director can decide whether to extend or warn.
|
|
2466
|
+
* Call this from the iteration loop so a hung tool gets a chance to
|
|
2467
|
+
* negotiate more time before the coordinator's Promise.race kills it.
|
|
2468
|
+
*/
|
|
2469
|
+
checkTimeout() {
|
|
2470
|
+
if (this.startTime === null || this.limits.timeoutMs === void 0) return;
|
|
2471
|
+
const elapsed = Date.now() - this.startTime;
|
|
2472
|
+
if (elapsed > this.limits.timeoutMs) {
|
|
2473
|
+
void this.checkLimit("timeout", elapsed, this.limits.timeoutMs);
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
/** Returns true if a timeout has occurred without throwing. Useful for races. */
|
|
2477
|
+
isTimedOut() {
|
|
2478
|
+
if (this.startTime === null || this.limits.timeoutMs === void 0) return false;
|
|
2479
|
+
return Date.now() - this.startTime > this.limits.timeoutMs;
|
|
2480
|
+
}
|
|
2481
|
+
usage() {
|
|
2482
|
+
return {
|
|
2483
|
+
iterations: this.iterations,
|
|
2484
|
+
toolCalls: this.toolCalls,
|
|
2485
|
+
tokens: {
|
|
2486
|
+
input: this.tokenInput,
|
|
2487
|
+
output: this.tokenOutput,
|
|
2488
|
+
total: this.tokenInput + this.tokenOutput
|
|
2489
|
+
},
|
|
2490
|
+
costUsd: this.costUsd,
|
|
2491
|
+
elapsedMs: this.startTime === null ? 0 : Date.now() - this.startTime
|
|
2492
|
+
};
|
|
2493
|
+
}
|
|
2494
|
+
};
|
|
2495
|
+
|
|
2496
|
+
// src/coordination/agent-subagent-runner.ts
|
|
2497
|
+
function makeAgentSubagentRunner(opts) {
|
|
2498
|
+
const format = opts.formatTaskInput ?? defaultFormatTaskInput;
|
|
2499
|
+
return async (task, ctx) => {
|
|
2500
|
+
const factoryResult = await opts.factory(ctx.config);
|
|
2501
|
+
const { agent, events } = factoryResult;
|
|
2502
|
+
const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
|
|
2503
|
+
const aborter = new AbortController();
|
|
2504
|
+
ctx.budget._events = events;
|
|
2505
|
+
ctx.budget.onThreshold = ({ requestDecision }) => requestDecision();
|
|
2506
|
+
let budgetError = null;
|
|
2507
|
+
const onBudgetError = (err) => {
|
|
2508
|
+
if (err instanceof BudgetThresholdSignal) {
|
|
2509
|
+
err.decision.then((decision) => {
|
|
2510
|
+
if (decision === "stop") {
|
|
2511
|
+
budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
|
|
2512
|
+
aborter.abort();
|
|
2513
|
+
}
|
|
2514
|
+
}).catch(() => {
|
|
2515
|
+
budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
|
|
2516
|
+
aborter.abort();
|
|
2517
|
+
});
|
|
2518
|
+
return;
|
|
2294
2519
|
}
|
|
2295
|
-
|
|
2520
|
+
aborter.abort();
|
|
2521
|
+
budgetError = err instanceof BudgetExceededError ? err : new BudgetExceededError(
|
|
2522
|
+
"tool_calls",
|
|
2523
|
+
0,
|
|
2524
|
+
0
|
|
2525
|
+
);
|
|
2526
|
+
if (budgetError !== err && err instanceof Error) {
|
|
2527
|
+
budgetError.message += ` (caused by: ${err.message})`;
|
|
2528
|
+
}
|
|
2529
|
+
};
|
|
2530
|
+
let lastToolFailed = null;
|
|
2531
|
+
const unsub = [];
|
|
2532
|
+
unsub.push(
|
|
2533
|
+
events.on("tool.executed", (e) => {
|
|
2534
|
+
try {
|
|
2535
|
+
ctx.budget.recordToolCall();
|
|
2536
|
+
} catch (eb) {
|
|
2537
|
+
onBudgetError(eb);
|
|
2538
|
+
}
|
|
2539
|
+
if (e.ok === false) {
|
|
2540
|
+
lastToolFailed = e.name;
|
|
2541
|
+
} else if (e.ok === true) {
|
|
2542
|
+
lastToolFailed = null;
|
|
2543
|
+
}
|
|
2544
|
+
}),
|
|
2545
|
+
events.on("provider.response", (e) => {
|
|
2546
|
+
try {
|
|
2547
|
+
ctx.budget.recordUsage(e.usage);
|
|
2548
|
+
} catch (e2) {
|
|
2549
|
+
void onBudgetError(e2);
|
|
2550
|
+
}
|
|
2551
|
+
}),
|
|
2552
|
+
events.on("iteration.started", () => {
|
|
2553
|
+
try {
|
|
2554
|
+
ctx.budget.recordIteration();
|
|
2555
|
+
} catch (e) {
|
|
2556
|
+
void onBudgetError(e);
|
|
2557
|
+
}
|
|
2558
|
+
const u = ctx.budget.usage();
|
|
2559
|
+
const since = u.iterations - lastSummaryAtIteration;
|
|
2560
|
+
if (since >= SUMMARY_INTERVAL) {
|
|
2561
|
+
lastSummaryAtIteration = u.iterations;
|
|
2562
|
+
events.emit("subagent.iteration_summary", {
|
|
2563
|
+
subagentId: ctx.subagentId,
|
|
2564
|
+
iteration: u.iterations,
|
|
2565
|
+
toolCalls: u.toolCalls,
|
|
2566
|
+
costUsd: u.costUsd,
|
|
2567
|
+
currentTool: currentToolName,
|
|
2568
|
+
partialText: streamingTextAcc.trim() || void 0
|
|
2569
|
+
});
|
|
2570
|
+
}
|
|
2571
|
+
}),
|
|
2572
|
+
// D3: cooperative timeout enforcement DURING a long tool call.
|
|
2573
|
+
// The iteration-loop checkTimeout() only fires between agent
|
|
2574
|
+
// iterations — a single `bash sleep 3600` call would otherwise
|
|
2575
|
+
// park inside one tool execution while the timeout silently
|
|
2576
|
+
// passes, relying solely on the coordinator's hard Promise.race
|
|
2577
|
+
// to interrupt. Tools that emit `tool.progress` (bash chunks,
|
|
2578
|
+
// fetch byte progress, spawn-stream stdout) give us a heartbeat
|
|
2579
|
+
// we can hang the check on. When the budget trips here:
|
|
2580
|
+
// 1. onBudgetError sets budgetError + aborter.abort()
|
|
2581
|
+
// 2. aborter signal propagates to agent.run → tool executor
|
|
2582
|
+
// 3. tool's own signal listener kills the child process
|
|
2583
|
+
// Cheap: O(1) per progress event, and the budget short-circuits
|
|
2584
|
+
// when timeoutMs is unset (most subagents have one set anyway).
|
|
2585
|
+
events.on("tool.progress", () => {
|
|
2586
|
+
try {
|
|
2587
|
+
ctx.budget.checkTimeout();
|
|
2588
|
+
} catch (e) {
|
|
2589
|
+
void onBudgetError(e);
|
|
2590
|
+
}
|
|
2591
|
+
})
|
|
2592
|
+
);
|
|
2593
|
+
let currentToolName;
|
|
2594
|
+
let streamingTextAcc = "";
|
|
2595
|
+
let lastSummaryAtIteration = 0;
|
|
2596
|
+
const SUMMARY_INTERVAL = 25;
|
|
2597
|
+
unsub.push(
|
|
2598
|
+
events.on("tool.started", (e) => {
|
|
2599
|
+
currentToolName = e.name;
|
|
2600
|
+
}),
|
|
2601
|
+
events.on("provider.text_delta", (e) => {
|
|
2602
|
+
streamingTextAcc = (streamingTextAcc + e.text).slice(-200);
|
|
2603
|
+
})
|
|
2604
|
+
);
|
|
2605
|
+
const onParentAbort = () => aborter.abort();
|
|
2606
|
+
ctx.signal.addEventListener("abort", onParentAbort);
|
|
2607
|
+
let result;
|
|
2608
|
+
try {
|
|
2609
|
+
result = await agent.run(format(task, ctx.config), { signal: aborter.signal });
|
|
2610
|
+
} finally {
|
|
2611
|
+
detachFleet?.();
|
|
2612
|
+
ctx.signal.removeEventListener("abort", onParentAbort);
|
|
2613
|
+
for (const u of unsub) u();
|
|
2614
|
+
if (factoryResult.dispose) {
|
|
2615
|
+
try {
|
|
2616
|
+
await factoryResult.dispose();
|
|
2617
|
+
} catch {
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
if (budgetError) {
|
|
2622
|
+
if ("decision" in budgetError) {
|
|
2623
|
+
const decision = await budgetError.decision;
|
|
2624
|
+
if (decision === "stop") {
|
|
2625
|
+
budgetError = new BudgetExceededError(
|
|
2626
|
+
budgetError.kind,
|
|
2627
|
+
budgetError.limit,
|
|
2628
|
+
budgetError.used
|
|
2629
|
+
);
|
|
2630
|
+
} else {
|
|
2631
|
+
budgetError = null;
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
if (budgetError) throw budgetError;
|
|
2635
|
+
}
|
|
2636
|
+
if (result.status === "failed") {
|
|
2637
|
+
throw result.error instanceof Error ? result.error : new Error(String(result.error ?? "agent failed"));
|
|
2638
|
+
}
|
|
2639
|
+
if (result.status === "aborted") {
|
|
2640
|
+
throw new Error("agent aborted");
|
|
2641
|
+
}
|
|
2642
|
+
if (result.status === "max_iterations") {
|
|
2643
|
+
throw new Error("agent exhausted iteration limit");
|
|
2644
|
+
}
|
|
2645
|
+
const usage = ctx.budget.usage();
|
|
2646
|
+
const finalText = (result.finalText ?? "").trim();
|
|
2647
|
+
if (finalText.length === 0 && usage.toolCalls === 0) {
|
|
2648
|
+
throw new Error("empty response");
|
|
2649
|
+
}
|
|
2650
|
+
if (finalText.length === 0 && lastToolFailed !== null) {
|
|
2651
|
+
throw new Error(`tool failed: ${lastToolFailed}`);
|
|
2652
|
+
}
|
|
2653
|
+
return {
|
|
2654
|
+
result: result.finalText,
|
|
2655
|
+
iterations: result.iterations,
|
|
2656
|
+
toolCalls: usage.toolCalls
|
|
2657
|
+
};
|
|
2296
2658
|
};
|
|
2297
2659
|
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
function buildGoalPreamble(goal) {
|
|
2301
|
-
return [
|
|
2302
|
-
"[GOAL \u2014 LOCKED IN. You will work on this until it is verifiably done.",
|
|
2303
|
-
"The user granted you full autonomy. Read these constraints once, then act.",
|
|
2304
|
-
"",
|
|
2305
|
-
"YOUR GOAL:",
|
|
2306
|
-
"---",
|
|
2307
|
-
goal,
|
|
2308
|
-
"---",
|
|
2309
|
-
"",
|
|
2310
|
-
"AUTHORITY YOU HAVE:",
|
|
2311
|
-
"- Spawn as many subagents as the work needs (delegate / spawn_subagent).",
|
|
2312
|
-
" Parallel + recursive fan-out are both fine. There is no spawn budget.",
|
|
2313
|
-
"- Use any provider/model per subagent \u2014 pick the right tool for each",
|
|
2314
|
-
" piece of work. Heavy reasoning model for planning, fast model for",
|
|
2315
|
-
" batch work, specialist model for domain code.",
|
|
2316
|
-
"- Run unlimited tool calls and iterations. There is NO hidden budget.",
|
|
2317
|
-
" The Agent loop auto-extends every 100 iterations forever.",
|
|
2318
|
-
"- Retry failed tools with different inputs, alternative paths, fresh",
|
|
2319
|
-
" subagents. Switch providers mid-run if one is rate-limited.",
|
|
2320
|
-
"- Re-plan freely when an approach hits a dead end. You are not obliged",
|
|
2321
|
-
" to stick with the first plan you proposed.",
|
|
2322
|
-
"",
|
|
2323
|
-
'WHAT "DONE" MEANS \u2014 non-negotiable:',
|
|
2324
|
-
"- You can name a concrete artifact (a passing test, a written file at",
|
|
2325
|
-
" a specific path, a fixed bug verified by re-running the failing case,",
|
|
2326
|
-
" a clean grep that previously had matches).",
|
|
2327
|
-
"- You can tell the user HOW to verify it themselves in 10 seconds.",
|
|
2328
|
-
'- You have NOT hedged. None of: "looks like it should work", "I',
|
|
2329
|
-
' believe this fixes it", "the changes appear correct".',
|
|
2330
|
-
"",
|
|
2331
|
-
"WHAT IS NOT DONE \u2014 never report any of these as completion:",
|
|
2332
|
-
"- An error message you didn't recover from.",
|
|
2333
|
-
'- An empty result, a 0-line file, a "no matches found" you accepted',
|
|
2334
|
-
" without questioning the search.",
|
|
2335
|
-
'- "Should I continue?" / "Want me to also...?" / "Let me know if you',
|
|
2336
|
-
' want X." Those are hedges. The user already told you to finish the',
|
|
2337
|
-
" goal \u2014 just do it.",
|
|
2338
|
-
"- Partial progress dressed up as success. Fixed 3 of 5 bugs = 60%",
|
|
2339
|
-
" done, not done.",
|
|
2340
|
-
"- A subagent's failed/timeout/stopped TaskResult that you didn't",
|
|
2341
|
-
" respond to with a fresh attempt (different role, different model,",
|
|
2342
|
-
" tighter prompt).",
|
|
2343
|
-
"",
|
|
2344
|
-
"PERSISTENCE PROTOCOL:",
|
|
2345
|
-
"- If blocked, try at least 3 different angles before reporting the",
|
|
2346
|
-
" problem to the user. Different tool inputs, different subagent",
|
|
2347
|
-
" roles, different providers, different decomposition of the task.",
|
|
2348
|
-
"- If a tool fails, read its error, alter the input, try again. Do",
|
|
2349
|
-
" not just report the failure back.",
|
|
2350
|
-
"- If a subagent returns useless output, respawn with a tighter prompt",
|
|
2351
|
-
' or a different role. Do not accept "I could not determine\u2026" as the',
|
|
2352
|
-
" final answer.",
|
|
2353
|
-
"- Use `ask_subagent` for one-shot questions when you don't need a",
|
|
2354
|
-
" full delegated task.",
|
|
2355
|
-
"",
|
|
2356
|
-
"REPORTING:",
|
|
2357
|
-
"- Stream short progress notes between major actions so the user can",
|
|
2358
|
-
" monitor. Do not go silent for 50 tool calls then dump a wall of",
|
|
2359
|
-
" text \u2014 but also do not narrate every tool call.",
|
|
2360
|
-
"- Use the shared scratchpad (if available) to leave breadcrumbs",
|
|
2361
|
-
" subagents can read.",
|
|
2362
|
-
"- Final response must include: (a) what was accomplished, (b) how",
|
|
2363
|
-
" to verify, (c) any caveats (residual TODOs, things the user",
|
|
2364
|
-
" should know about).",
|
|
2365
|
-
"",
|
|
2366
|
-
"BEGIN.]"
|
|
2367
|
-
].join("\n");
|
|
2660
|
+
function defaultFormatTaskInput(task) {
|
|
2661
|
+
return task.description ?? "";
|
|
2368
2662
|
}
|
|
2369
2663
|
|
|
2370
2664
|
// src/types/provider.ts
|
|
@@ -2438,6 +2732,1019 @@ function providerStatusToCode(status, type) {
|
|
|
2438
2732
|
if (status >= 500) return "PROVIDER_SERVER_ERROR";
|
|
2439
2733
|
return "PROVIDER_INVALID_REQUEST";
|
|
2440
2734
|
}
|
|
2735
|
+
var FLEET_ROSTER_BUDGETS = {
|
|
2736
|
+
"audit-log": { timeoutMs: 8 * 60 * 1e3, maxIterations: 150, maxToolCalls: 500 },
|
|
2737
|
+
"bug-hunter": { timeoutMs: 15 * 60 * 1e3, maxIterations: 200, maxToolCalls: 600 },
|
|
2738
|
+
"refactor-planner": { timeoutMs: 12 * 60 * 1e3, maxIterations: 180, maxToolCalls: 550 },
|
|
2739
|
+
"security-scanner": { timeoutMs: 15 * 60 * 1e3, maxIterations: 200, maxToolCalls: 600 }
|
|
2740
|
+
};
|
|
2741
|
+
function applyRosterBudget(cfg) {
|
|
2742
|
+
const defaultBudget = FLEET_ROSTER_BUDGETS[cfg.role ?? ""];
|
|
2743
|
+
if (!defaultBudget) return cfg;
|
|
2744
|
+
return {
|
|
2745
|
+
...cfg,
|
|
2746
|
+
timeoutMs: cfg.timeoutMs ?? defaultBudget.timeoutMs,
|
|
2747
|
+
maxIterations: cfg.maxIterations ?? defaultBudget.maxIterations,
|
|
2748
|
+
maxToolCalls: cfg.maxToolCalls ?? defaultBudget.maxToolCalls,
|
|
2749
|
+
maxTokens: cfg.maxTokens ?? defaultBudget.maxTokens,
|
|
2750
|
+
maxCostUsd: cfg.maxCostUsd ?? defaultBudget.maxCostUsd
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
// src/coordination/multi-agent-coordinator.ts
|
|
2755
|
+
var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
2756
|
+
coordinatorId;
|
|
2757
|
+
config;
|
|
2758
|
+
runner;
|
|
2759
|
+
subagents = /* @__PURE__ */ new Map();
|
|
2760
|
+
pendingTasks = [];
|
|
2761
|
+
completedResults = [];
|
|
2762
|
+
totalIterations = 0;
|
|
2763
|
+
inFlight = 0;
|
|
2764
|
+
/**
|
|
2765
|
+
* Subagents currently being stopped. Set on entry to `stop()`, cleared
|
|
2766
|
+
* once `recordCompletion` lands the terminal TaskResult. Used by
|
|
2767
|
+
* `runDispatched` and `findIdleSubagent` to refuse mid-flight dispatch
|
|
2768
|
+
* to a subagent the caller has already asked to terminate — closes the
|
|
2769
|
+
* assign+terminate race where a fresh task could land on a worker that
|
|
2770
|
+
* was about to be killed.
|
|
2771
|
+
*/
|
|
2772
|
+
terminating = /* @__PURE__ */ new Set();
|
|
2773
|
+
constructor(config, options = {}) {
|
|
2774
|
+
super();
|
|
2775
|
+
this.coordinatorId = config.coordinatorId;
|
|
2776
|
+
this.config = config;
|
|
2777
|
+
this.runner = options.runner;
|
|
2778
|
+
}
|
|
2779
|
+
/**
|
|
2780
|
+
* Replace the runner after construction. Used when the runner depends
|
|
2781
|
+
* on infrastructure (e.g. FleetBus) that isn't available until after
|
|
2782
|
+
* the coordinator's owning Director is built.
|
|
2783
|
+
*/
|
|
2784
|
+
setRunner(runner) {
|
|
2785
|
+
this.runner = runner;
|
|
2786
|
+
}
|
|
2787
|
+
/**
|
|
2788
|
+
* Change the in-flight dispatch ceiling at runtime. Lowering does NOT
|
|
2789
|
+
* preempt running tasks — already-dispatched subagents finish their
|
|
2790
|
+
* current task; only future dispatches respect the new cap. Raising
|
|
2791
|
+
* immediately tries to fill the freed slots from the pending queue.
|
|
2792
|
+
*/
|
|
2793
|
+
setMaxConcurrent(n) {
|
|
2794
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
2795
|
+
throw new Error(`maxConcurrent must be a finite integer >= 1, got ${n}`);
|
|
2796
|
+
}
|
|
2797
|
+
this.config.maxConcurrent = Math.floor(n);
|
|
2798
|
+
this.tryDispatchNext();
|
|
2799
|
+
}
|
|
2800
|
+
async spawn(subagent) {
|
|
2801
|
+
const id = subagent.id || randomUUID();
|
|
2802
|
+
if (this.subagents.has(id)) {
|
|
2803
|
+
throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
|
|
2804
|
+
}
|
|
2805
|
+
const context = {
|
|
2806
|
+
subagentId: id,
|
|
2807
|
+
tasks: [],
|
|
2808
|
+
// Wired later by the caller via setSubagentBridge() once the
|
|
2809
|
+
// bidirectional bridge is created. Readers must null-check / use
|
|
2810
|
+
// hasParentBridge() — the type now reflects this.
|
|
2811
|
+
parentBridge: null,
|
|
2812
|
+
doneCondition: this.config.doneCondition,
|
|
2813
|
+
maxConcurrent: this.config.maxConcurrent ?? 4
|
|
2814
|
+
};
|
|
2815
|
+
this.subagents.set(id, {
|
|
2816
|
+
config: { ...subagent, id },
|
|
2817
|
+
context,
|
|
2818
|
+
status: "idle",
|
|
2819
|
+
abortController: new AbortController()
|
|
2820
|
+
});
|
|
2821
|
+
this.emit("subagent.started", { subagent: { ...subagent, id } });
|
|
2822
|
+
return { subagentId: id, agentId: id };
|
|
2823
|
+
}
|
|
2824
|
+
async assign(task) {
|
|
2825
|
+
this.pendingTasks.push(task);
|
|
2826
|
+
this.tryDispatchNext();
|
|
2827
|
+
}
|
|
2828
|
+
async delegate(to, msg) {
|
|
2829
|
+
const subagent = this.subagents.get(to);
|
|
2830
|
+
if (!subagent) throw new Error(`Subagent "${to}" not found`);
|
|
2831
|
+
if (!subagent.context.parentBridge) {
|
|
2832
|
+
throw new Error(`Subagent "${to}" has no parentBridge \u2014 call setSubagentBridge() first`);
|
|
2833
|
+
}
|
|
2834
|
+
await subagent.context.parentBridge.send(msg);
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Wire up the communication bridge for a subagent. Call after spawn() once
|
|
2838
|
+
* the caller has created the bidirectional connection.
|
|
2839
|
+
*/
|
|
2840
|
+
setSubagentBridge(subagentId, bridge) {
|
|
2841
|
+
const subagent = this.subagents.get(subagentId);
|
|
2842
|
+
if (!subagent) throw new Error(`Subagent "${subagentId}" not found`);
|
|
2843
|
+
subagent.context.parentBridge = bridge;
|
|
2844
|
+
}
|
|
2845
|
+
async stop(subagentId) {
|
|
2846
|
+
const subagent = this.subagents.get(subagentId);
|
|
2847
|
+
if (!subagent) return;
|
|
2848
|
+
this.terminating.add(subagentId);
|
|
2849
|
+
subagent.abortController.abort();
|
|
2850
|
+
subagent.status = "stopped";
|
|
2851
|
+
subagent.currentTask = void 0;
|
|
2852
|
+
subagent.context.parentBridge = null;
|
|
2853
|
+
this.emit("subagent.stopped", { subagentId, reason: "stopped by coordinator" });
|
|
2854
|
+
}
|
|
2855
|
+
async stopAll() {
|
|
2856
|
+
this.drainPendingAsAborted("Coordinator stopAll() drained the pending queue");
|
|
2857
|
+
await Promise.allSettled([...this.subagents.keys()].map((id) => this.stop(id)));
|
|
2858
|
+
}
|
|
2859
|
+
getStatus() {
|
|
2860
|
+
return {
|
|
2861
|
+
coordinatorId: this.coordinatorId,
|
|
2862
|
+
subagents: Array.from(this.subagents.entries()).map(([id, s]) => ({
|
|
2863
|
+
id,
|
|
2864
|
+
name: s.config.name,
|
|
2865
|
+
status: s.status,
|
|
2866
|
+
currentTask: s.currentTask
|
|
2867
|
+
})),
|
|
2868
|
+
pendingTasks: this.pendingTasks.length,
|
|
2869
|
+
completedTasks: this.completedResults.length,
|
|
2870
|
+
totalIterations: this.totalIterations,
|
|
2871
|
+
done: this.isDone()
|
|
2872
|
+
};
|
|
2873
|
+
}
|
|
2874
|
+
/** Expose snapshot of completed results — useful for callers awaiting all done. */
|
|
2875
|
+
results() {
|
|
2876
|
+
return this.completedResults;
|
|
2877
|
+
}
|
|
2878
|
+
/**
|
|
2879
|
+
* Wait for one or more tasks to complete and return their results.
|
|
2880
|
+
* If a task is already done when called, returns immediately.
|
|
2881
|
+
* Resolves to an array in the same order as `taskIds`.
|
|
2882
|
+
*/
|
|
2883
|
+
async awaitTasks(taskIds) {
|
|
2884
|
+
return Promise.all(
|
|
2885
|
+
taskIds.map((id) => {
|
|
2886
|
+
const cached = this.completedResults.find((r) => r.taskId === id);
|
|
2887
|
+
if (cached) return cached;
|
|
2888
|
+
return new Promise((resolve, reject) => {
|
|
2889
|
+
const timeout = setTimeout(() => {
|
|
2890
|
+
this.off("task.completed", handler);
|
|
2891
|
+
reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
|
|
2892
|
+
}, this.config.timeoutMs ?? 3e5);
|
|
2893
|
+
const handler = ({ result }) => {
|
|
2894
|
+
if (result.taskId === id) {
|
|
2895
|
+
clearTimeout(timeout);
|
|
2896
|
+
this.off("task.completed", handler);
|
|
2897
|
+
resolve(result);
|
|
2898
|
+
}
|
|
2899
|
+
};
|
|
2900
|
+
this.on("task.completed", handler);
|
|
2901
|
+
});
|
|
2902
|
+
})
|
|
2903
|
+
);
|
|
2904
|
+
}
|
|
2905
|
+
/**
|
|
2906
|
+
* Manual completion — for callers that drive subagents without a runner
|
|
2907
|
+
* (e.g. external orchestrators). When a runner is configured the coordinator
|
|
2908
|
+
* calls this itself.
|
|
2909
|
+
*/
|
|
2910
|
+
completeTask(result) {
|
|
2911
|
+
this.recordCompletion(result);
|
|
2912
|
+
}
|
|
2913
|
+
// --- internal dispatching ---------------------------------------------
|
|
2914
|
+
tryDispatchNext() {
|
|
2915
|
+
while (this.canDispatch()) {
|
|
2916
|
+
const dispatchable = this.takeNextDispatchableTask();
|
|
2917
|
+
if (!dispatchable) {
|
|
2918
|
+
if (this.pendingTasks.length > 0 && !this.hasLiveSubagent()) {
|
|
2919
|
+
this.drainPendingAsAborted(
|
|
2920
|
+
"No live subagent available \u2014 all stopped or mid-termination"
|
|
2921
|
+
);
|
|
2922
|
+
}
|
|
2923
|
+
return;
|
|
2924
|
+
}
|
|
2925
|
+
const { subagentId, task } = dispatchable;
|
|
2926
|
+
this.runDispatched(subagentId, task).catch((err) => {
|
|
2927
|
+
this.recordCompletion({
|
|
2928
|
+
subagentId,
|
|
2929
|
+
taskId: task.id,
|
|
2930
|
+
status: "failed",
|
|
2931
|
+
error: classifySubagentError(err),
|
|
2932
|
+
iterations: 0,
|
|
2933
|
+
toolCalls: 0,
|
|
2934
|
+
durationMs: 0
|
|
2935
|
+
});
|
|
2936
|
+
});
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
canDispatch() {
|
|
2940
|
+
const max = this.config.maxConcurrent ?? 4;
|
|
2941
|
+
return this.inFlight < max && this.pendingTasks.length > 0;
|
|
2942
|
+
}
|
|
2943
|
+
takeNextDispatchableTask() {
|
|
2944
|
+
for (let i = 0; i < this.pendingTasks.length; i++) {
|
|
2945
|
+
const task = this.pendingTasks[i];
|
|
2946
|
+
const subagentId = task.subagentId ? this.isIdleSubagent(task.subagentId) ? task.subagentId : null : this.findIdleSubagent();
|
|
2947
|
+
if (!subagentId) continue;
|
|
2948
|
+
this.pendingTasks.splice(i, 1);
|
|
2949
|
+
return { subagentId, task };
|
|
2950
|
+
}
|
|
2951
|
+
return null;
|
|
2952
|
+
}
|
|
2953
|
+
findIdleSubagent() {
|
|
2954
|
+
for (const [id, s] of this.subagents) {
|
|
2955
|
+
if (s.status === "idle" && !this.terminating.has(id)) return id;
|
|
2956
|
+
}
|
|
2957
|
+
return null;
|
|
2958
|
+
}
|
|
2959
|
+
isIdleSubagent(id) {
|
|
2960
|
+
const subagent = this.subagents.get(id);
|
|
2961
|
+
return !!subagent && subagent.status === "idle" && !this.terminating.has(id);
|
|
2962
|
+
}
|
|
2963
|
+
/**
|
|
2964
|
+
* Returns true iff at least one spawned subagent could still
|
|
2965
|
+
* process a task. A "live" subagent is one that is not stopped
|
|
2966
|
+
* AND not mid-termination — `running` workers count because they
|
|
2967
|
+
* will eventually finish and become idle.
|
|
2968
|
+
*
|
|
2969
|
+
* When no subagent has ever been spawned, returns `true` so a
|
|
2970
|
+
* pre-spawn `assign()` simply queues (legacy behaviour). The
|
|
2971
|
+
* dead-end detection only fires after `stop()` has retired every
|
|
2972
|
+
* spawned worker.
|
|
2973
|
+
*
|
|
2974
|
+
* Used by `tryDispatchNext` to detect a dead-end pending queue.
|
|
2975
|
+
*/
|
|
2976
|
+
hasLiveSubagent() {
|
|
2977
|
+
if (this.subagents.size === 0) return true;
|
|
2978
|
+
for (const [id, s] of this.subagents) {
|
|
2979
|
+
if (s.status !== "stopped" && !this.terminating.has(id)) return true;
|
|
2980
|
+
}
|
|
2981
|
+
return false;
|
|
2982
|
+
}
|
|
2983
|
+
/**
|
|
2984
|
+
* Drain every pending task with a synthetic `aborted_by_parent`
|
|
2985
|
+
* completion event. Same shape as the `stopAll()` drain — we go
|
|
2986
|
+
* around `recordCompletion` because pending tasks were never
|
|
2987
|
+
* counted in `inFlight` and routing them through would trip the
|
|
2988
|
+
* underflow guard on every task after the first.
|
|
2989
|
+
*/
|
|
2990
|
+
drainPendingAsAborted(message) {
|
|
2991
|
+
const dropped = this.pendingTasks.splice(0, this.pendingTasks.length);
|
|
2992
|
+
for (const t of dropped) {
|
|
2993
|
+
const synthetic = {
|
|
2994
|
+
subagentId: t.subagentId ?? "unassigned",
|
|
2995
|
+
taskId: t.id,
|
|
2996
|
+
status: "stopped",
|
|
2997
|
+
error: {
|
|
2998
|
+
kind: "aborted_by_parent",
|
|
2999
|
+
message,
|
|
3000
|
+
retryable: false
|
|
3001
|
+
},
|
|
3002
|
+
iterations: 0,
|
|
3003
|
+
toolCalls: 0,
|
|
3004
|
+
durationMs: 0
|
|
3005
|
+
};
|
|
3006
|
+
this.completedResults.push(synthetic);
|
|
3007
|
+
this.emit("task.completed", { task: t, result: synthetic });
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
async runDispatched(subagentId, task) {
|
|
3011
|
+
const subagent = this.subagents.get(subagentId);
|
|
3012
|
+
if (!subagent) return;
|
|
3013
|
+
if (this.terminating.has(subagentId) || subagent.status === "stopped") {
|
|
3014
|
+
this.recordCompletion({
|
|
3015
|
+
subagentId,
|
|
3016
|
+
taskId: task.id,
|
|
3017
|
+
status: "stopped",
|
|
3018
|
+
error: {
|
|
3019
|
+
kind: "aborted_by_parent",
|
|
3020
|
+
message: "Subagent was terminated before task could start",
|
|
3021
|
+
retryable: false
|
|
3022
|
+
},
|
|
3023
|
+
iterations: 0,
|
|
3024
|
+
toolCalls: 0,
|
|
3025
|
+
durationMs: 0
|
|
3026
|
+
});
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
subagent.status = "running";
|
|
3030
|
+
subagent.currentTask = task.id;
|
|
3031
|
+
task.subagentId = subagentId;
|
|
3032
|
+
subagent.context.tasks.push(task);
|
|
3033
|
+
this.emit("task.assigned", { task, subagentId });
|
|
3034
|
+
const configWithRosterDefaults = applyRosterBudget(subagent.config);
|
|
3035
|
+
const budget = new SubagentBudget({
|
|
3036
|
+
maxIterations: configWithRosterDefaults.maxIterations ?? this.config.defaultBudget?.maxIterations,
|
|
3037
|
+
maxToolCalls: task.maxToolCalls ?? configWithRosterDefaults.maxToolCalls ?? this.config.defaultBudget?.maxToolCalls,
|
|
3038
|
+
maxTokens: configWithRosterDefaults.maxTokens ?? this.config.defaultBudget?.maxTokens,
|
|
3039
|
+
maxCostUsd: configWithRosterDefaults.maxCostUsd ?? this.config.defaultBudget?.maxCostUsd,
|
|
3040
|
+
timeoutMs: task.timeoutMs ?? configWithRosterDefaults.timeoutMs ?? this.config.defaultBudget?.timeoutMs
|
|
3041
|
+
});
|
|
3042
|
+
subagent.activeBudget = budget;
|
|
3043
|
+
if (!this.runner) {
|
|
3044
|
+
return;
|
|
3045
|
+
}
|
|
3046
|
+
this.inFlight++;
|
|
3047
|
+
const startTime = Date.now();
|
|
3048
|
+
const runCtx = {
|
|
3049
|
+
subagentId,
|
|
3050
|
+
config: subagent.config,
|
|
3051
|
+
budget,
|
|
3052
|
+
signal: subagent.abortController.signal,
|
|
3053
|
+
bridge: subagent.context.parentBridge || null
|
|
3054
|
+
};
|
|
3055
|
+
let result;
|
|
3056
|
+
budget.start();
|
|
3057
|
+
try {
|
|
3058
|
+
const outcome = await this.executeWithTimeout(this.runner, task, runCtx, budget);
|
|
3059
|
+
result = {
|
|
3060
|
+
subagentId,
|
|
3061
|
+
taskId: task.id,
|
|
3062
|
+
status: "success",
|
|
3063
|
+
result: outcome.result,
|
|
3064
|
+
iterations: outcome.iterations,
|
|
3065
|
+
toolCalls: outcome.toolCalls,
|
|
3066
|
+
durationMs: Date.now() - startTime
|
|
3067
|
+
};
|
|
3068
|
+
} catch (err) {
|
|
3069
|
+
const status = err instanceof BudgetExceededError && err.kind === "timeout" ? "timeout" : subagent.abortController.signal.aborted ? "stopped" : "failed";
|
|
3070
|
+
const usage = budget.usage();
|
|
3071
|
+
result = {
|
|
3072
|
+
subagentId,
|
|
3073
|
+
taskId: task.id,
|
|
3074
|
+
status,
|
|
3075
|
+
error: classifySubagentError(err, {
|
|
3076
|
+
parentAborted: subagent.abortController.signal.aborted
|
|
3077
|
+
}),
|
|
3078
|
+
iterations: usage.iterations,
|
|
3079
|
+
toolCalls: usage.toolCalls,
|
|
3080
|
+
durationMs: Date.now() - startTime
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
this.recordCompletion(result);
|
|
3084
|
+
}
|
|
3085
|
+
async executeWithTimeout(runner, task, ctx, budget) {
|
|
3086
|
+
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
3087
|
+
if (initialTimeoutMs === void 0) return runner(task, ctx);
|
|
3088
|
+
const start = Date.now();
|
|
3089
|
+
let timer = null;
|
|
3090
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
3091
|
+
const armFor = (ms) => {
|
|
3092
|
+
if (timer) clearTimeout(timer);
|
|
3093
|
+
timer = setTimeout(async () => {
|
|
3094
|
+
const elapsed = Date.now() - start;
|
|
3095
|
+
const limit = budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
3096
|
+
if (!budget.onThreshold) {
|
|
3097
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
3098
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
try {
|
|
3102
|
+
const result = budget.onThreshold({
|
|
3103
|
+
kind: "timeout",
|
|
3104
|
+
used: elapsed,
|
|
3105
|
+
limit,
|
|
3106
|
+
requestDecision: () => new Promise((resolveDecision) => {
|
|
3107
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
3108
|
+
kind: "timeout",
|
|
3109
|
+
used: elapsed,
|
|
3110
|
+
limit,
|
|
3111
|
+
timeoutMs: 3e4,
|
|
3112
|
+
extend: (extra) => resolveDecision({ extend: extra }),
|
|
3113
|
+
deny: () => resolveDecision("stop")
|
|
3114
|
+
});
|
|
3115
|
+
})
|
|
3116
|
+
});
|
|
3117
|
+
const decision = typeof result === "string" ? result : await result;
|
|
3118
|
+
if (decision === "continue") {
|
|
3119
|
+
armFor(Math.max(1e3, limit));
|
|
3120
|
+
return;
|
|
3121
|
+
}
|
|
3122
|
+
if (decision === "throw" || decision === "stop") {
|
|
3123
|
+
armFor(Math.max(1e3, limit));
|
|
3124
|
+
return;
|
|
3125
|
+
}
|
|
3126
|
+
if (decision.extend.timeoutMs !== void 0) {
|
|
3127
|
+
budget.limits.timeoutMs = decision.extend.timeoutMs;
|
|
3128
|
+
const newLimit = decision.extend.timeoutMs;
|
|
3129
|
+
const remaining = Math.max(1e3, newLimit - elapsed);
|
|
3130
|
+
armFor(remaining);
|
|
3131
|
+
return;
|
|
3132
|
+
}
|
|
3133
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
3134
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
3135
|
+
} catch (err) {
|
|
3136
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
3137
|
+
reject(
|
|
3138
|
+
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
3139
|
+
);
|
|
3140
|
+
}
|
|
3141
|
+
}, ms);
|
|
3142
|
+
};
|
|
3143
|
+
armFor(initialTimeoutMs);
|
|
3144
|
+
});
|
|
3145
|
+
try {
|
|
3146
|
+
return await Promise.race([runner(task, ctx), timeoutPromise]);
|
|
3147
|
+
} finally {
|
|
3148
|
+
if (timer) clearTimeout(timer);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
recordCompletion(result) {
|
|
3152
|
+
this.completedResults.push(result);
|
|
3153
|
+
this.totalIterations += result.iterations;
|
|
3154
|
+
if (this.inFlight > 0) {
|
|
3155
|
+
this.inFlight--;
|
|
3156
|
+
} else if (this.runner) {
|
|
3157
|
+
this.emit("warning", {
|
|
3158
|
+
type: "inFlight_underflow",
|
|
3159
|
+
taskId: result.taskId,
|
|
3160
|
+
subagentId: result.subagentId
|
|
3161
|
+
});
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
const subagent = this.subagents.get(result.subagentId);
|
|
3165
|
+
if (subagent && subagent.status !== "stopped") {
|
|
3166
|
+
result.status === "failed" || result.status === "timeout";
|
|
3167
|
+
subagent.status = "idle";
|
|
3168
|
+
subagent.currentTask = void 0;
|
|
3169
|
+
if (subagent.abortController.signal.aborted) {
|
|
3170
|
+
subagent.abortController = new AbortController();
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
this.terminating.delete(result.subagentId);
|
|
3174
|
+
this.emit("task.completed", {
|
|
3175
|
+
task: subagent?.context.tasks.find((t) => t.id === result.taskId) ?? { id: result.taskId },
|
|
3176
|
+
result
|
|
3177
|
+
});
|
|
3178
|
+
this.tryDispatchNext();
|
|
3179
|
+
if (this.isDone()) {
|
|
3180
|
+
this.emit("done", {
|
|
3181
|
+
results: this.completedResults,
|
|
3182
|
+
totalIterations: this.totalIterations
|
|
3183
|
+
});
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
isDone() {
|
|
3187
|
+
if (this.config.doneCondition.type === "all_tasks_done") {
|
|
3188
|
+
return this.pendingTasks.length === 0 && this.inFlight === 0;
|
|
3189
|
+
}
|
|
3190
|
+
if (this.config.doneCondition.maxIterations !== void 0 && this.totalIterations >= this.config.doneCondition.maxIterations) {
|
|
3191
|
+
return true;
|
|
3192
|
+
}
|
|
3193
|
+
return false;
|
|
3194
|
+
}
|
|
3195
|
+
};
|
|
3196
|
+
function classifySubagentError(err, hints = {}) {
|
|
3197
|
+
const cause = err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : void 0;
|
|
3198
|
+
if (err instanceof ProviderError) {
|
|
3199
|
+
const baseMessage2 = err.describe();
|
|
3200
|
+
return providerErrorToSubagentError(err, baseMessage2, cause);
|
|
3201
|
+
}
|
|
3202
|
+
const baseMessage = err instanceof Error ? err.message : String(err);
|
|
3203
|
+
if (err instanceof BudgetExceededError) {
|
|
3204
|
+
const map = {
|
|
3205
|
+
iterations: "budget_iterations",
|
|
3206
|
+
tool_calls: "budget_tool_calls",
|
|
3207
|
+
tokens: "budget_tokens",
|
|
3208
|
+
cost: "budget_cost",
|
|
3209
|
+
timeout: "budget_timeout"
|
|
3210
|
+
};
|
|
3211
|
+
return {
|
|
3212
|
+
kind: map[err.kind],
|
|
3213
|
+
message: baseMessage,
|
|
3214
|
+
// Budgets are user-configured ceilings, not transient failures —
|
|
3215
|
+
// retrying with the same budget will hit the same ceiling. The
|
|
3216
|
+
// orchestrator must raise the budget or narrow the task first.
|
|
3217
|
+
retryable: false,
|
|
3218
|
+
cause
|
|
3219
|
+
};
|
|
3220
|
+
}
|
|
3221
|
+
if (hints.parentAborted) {
|
|
3222
|
+
return {
|
|
3223
|
+
kind: "aborted_by_parent",
|
|
3224
|
+
message: baseMessage,
|
|
3225
|
+
retryable: false,
|
|
3226
|
+
cause
|
|
3227
|
+
};
|
|
3228
|
+
}
|
|
3229
|
+
const lower = baseMessage.toLowerCase();
|
|
3230
|
+
if (/agent aborted$/i.test(baseMessage)) {
|
|
3231
|
+
return {
|
|
3232
|
+
kind: "aborted_by_parent",
|
|
3233
|
+
message: baseMessage,
|
|
3234
|
+
retryable: false,
|
|
3235
|
+
cause
|
|
3236
|
+
};
|
|
3237
|
+
}
|
|
3238
|
+
if (/agent exhausted iteration limit$/i.test(baseMessage)) {
|
|
3239
|
+
return { kind: "budget_iterations", message: baseMessage, retryable: false, cause };
|
|
3240
|
+
}
|
|
3241
|
+
if (/empty response$/i.test(baseMessage)) {
|
|
3242
|
+
return { kind: "empty_response", message: baseMessage, retryable: false, cause };
|
|
3243
|
+
}
|
|
3244
|
+
if (/^tool failed: /i.test(baseMessage)) {
|
|
3245
|
+
return { kind: "tool_failed", message: baseMessage, retryable: false, cause };
|
|
3246
|
+
}
|
|
3247
|
+
if (lower.includes("bridge transport") || /bridge.*(closed|disconnect)/i.test(baseMessage)) {
|
|
3248
|
+
return { kind: "bridge_failed", message: baseMessage, retryable: false, cause };
|
|
3249
|
+
}
|
|
3250
|
+
if (/context length|max.*tokens?.*exceeded|prompt is too long/i.test(baseMessage)) {
|
|
3251
|
+
return { kind: "context_overflow", message: baseMessage, retryable: false, cause };
|
|
3252
|
+
}
|
|
3253
|
+
return {
|
|
3254
|
+
kind: "unknown",
|
|
3255
|
+
message: baseMessage,
|
|
3256
|
+
retryable: false,
|
|
3257
|
+
cause
|
|
3258
|
+
};
|
|
3259
|
+
}
|
|
3260
|
+
function providerErrorToSubagentError(err, message, cause) {
|
|
3261
|
+
const status = err.status;
|
|
3262
|
+
if (status === 429 || err.body?.type === "rate_limit_error") {
|
|
3263
|
+
return {
|
|
3264
|
+
kind: "provider_rate_limit",
|
|
3265
|
+
message,
|
|
3266
|
+
retryable: true,
|
|
3267
|
+
// Conservative default: 5s. Provider-specific code can override
|
|
3268
|
+
// by emitting an error whose body carries an explicit hint.
|
|
3269
|
+
backoffMs: 5e3,
|
|
3270
|
+
cause
|
|
3271
|
+
};
|
|
3272
|
+
}
|
|
3273
|
+
if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
|
|
3274
|
+
return { kind: "provider_auth", message, retryable: false, cause };
|
|
3275
|
+
}
|
|
3276
|
+
if (status === 408 || status === 0) {
|
|
3277
|
+
return { kind: "provider_timeout", message, retryable: true, cause };
|
|
3278
|
+
}
|
|
3279
|
+
if (status >= 500 && status < 600) {
|
|
3280
|
+
return {
|
|
3281
|
+
kind: "provider_5xx",
|
|
3282
|
+
message,
|
|
3283
|
+
retryable: true,
|
|
3284
|
+
backoffMs: 3e3,
|
|
3285
|
+
cause
|
|
3286
|
+
};
|
|
3287
|
+
}
|
|
3288
|
+
return { kind: "unknown", message, retryable: err.retryable, cause };
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
// src/execution/parallel-eternal-engine.ts
|
|
3292
|
+
function sleep2(ms) {
|
|
3293
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3294
|
+
}
|
|
3295
|
+
var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
|
|
3296
|
+
var ParallelEternalEngine = class {
|
|
3297
|
+
constructor(opts) {
|
|
3298
|
+
this.opts = opts;
|
|
3299
|
+
this.goalPath = goalFilePath(opts.projectRoot);
|
|
3300
|
+
this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
|
|
3301
|
+
this.timeoutMs = opts.iterationTimeoutMs ?? 3e5;
|
|
3302
|
+
this.agentFactory = opts.subagentFactory ?? (async (config) => ({
|
|
3303
|
+
agent: this.opts.agent,
|
|
3304
|
+
events: this.opts.agent.events
|
|
3305
|
+
}));
|
|
3306
|
+
}
|
|
3307
|
+
opts;
|
|
3308
|
+
state = "idle";
|
|
3309
|
+
stopRequested = false;
|
|
3310
|
+
iterationsSinceCompact = 0;
|
|
3311
|
+
iterations = 0;
|
|
3312
|
+
consecutiveFailures = 0;
|
|
3313
|
+
goalPath;
|
|
3314
|
+
slots;
|
|
3315
|
+
timeoutMs;
|
|
3316
|
+
coordinator = null;
|
|
3317
|
+
agentFactory;
|
|
3318
|
+
get currentState() {
|
|
3319
|
+
return this.state;
|
|
3320
|
+
}
|
|
3321
|
+
stop() {
|
|
3322
|
+
this.stopRequested = true;
|
|
3323
|
+
void this.persistState("stopped").catch(() => {
|
|
3324
|
+
});
|
|
3325
|
+
this.state = "stopped";
|
|
3326
|
+
}
|
|
3327
|
+
async prime() {
|
|
3328
|
+
this.stopRequested = false;
|
|
3329
|
+
this.state = "running";
|
|
3330
|
+
await this.persistState("running");
|
|
3331
|
+
}
|
|
3332
|
+
async run() {
|
|
3333
|
+
this.state = "running";
|
|
3334
|
+
await this.persistState("running");
|
|
3335
|
+
const config = {
|
|
3336
|
+
coordinatorId: `parallel-${randomUUID().slice(0, 8)}`,
|
|
3337
|
+
maxConcurrent: this.slots,
|
|
3338
|
+
doneCondition: { type: "all_tasks_done" }
|
|
3339
|
+
};
|
|
3340
|
+
this.coordinator = new DefaultMultiAgentCoordinator(config);
|
|
3341
|
+
const runner = makeAgentSubagentRunner({ factory: this.agentFactory });
|
|
3342
|
+
this.coordinator.setRunner?.(runner);
|
|
3343
|
+
try {
|
|
3344
|
+
while (!this.stopRequested) {
|
|
3345
|
+
try {
|
|
3346
|
+
await this.runOneIteration();
|
|
3347
|
+
} catch (err) {
|
|
3348
|
+
this.consecutiveFailures++;
|
|
3349
|
+
this.opts.onError?.(err instanceof Error ? err : new Error(String(err)), this.consecutiveFailures);
|
|
3350
|
+
await this.appendFailure("engine error", err instanceof Error ? err.message : String(err));
|
|
3351
|
+
}
|
|
3352
|
+
if (this.stopRequested) break;
|
|
3353
|
+
await sleep2(2e3);
|
|
3354
|
+
}
|
|
3355
|
+
} finally {
|
|
3356
|
+
this.state = "stopped";
|
|
3357
|
+
await this.persistState("stopped").catch(() => {
|
|
3358
|
+
});
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
/**
|
|
3362
|
+
* Execute one tick: decompose → fan-out → aggregate → compact.
|
|
3363
|
+
* Called by the REPL in its main loop (REPL drives, engine is stateless per tick).
|
|
3364
|
+
*/
|
|
3365
|
+
async runOneIteration() {
|
|
3366
|
+
this.iterations++;
|
|
3367
|
+
const goal = await loadGoal(this.goalPath);
|
|
3368
|
+
if (!goal) {
|
|
3369
|
+
this.stopRequested = true;
|
|
3370
|
+
return false;
|
|
3371
|
+
}
|
|
3372
|
+
if (goal.goalState !== "active") {
|
|
3373
|
+
this.stopRequested = true;
|
|
3374
|
+
return false;
|
|
3375
|
+
}
|
|
3376
|
+
if (!this.coordinator) {
|
|
3377
|
+
const config = {
|
|
3378
|
+
coordinatorId: `parallel-${randomUUID().slice(0, 8)}`,
|
|
3379
|
+
maxConcurrent: this.slots,
|
|
3380
|
+
doneCondition: { type: "all_tasks_done" }
|
|
3381
|
+
};
|
|
3382
|
+
this.coordinator = new DefaultMultiAgentCoordinator(config);
|
|
3383
|
+
const runner = makeAgentSubagentRunner({ factory: this.agentFactory });
|
|
3384
|
+
this.coordinator.setRunner?.(runner);
|
|
3385
|
+
}
|
|
3386
|
+
const tasks = await this.decomposeGoal(goal);
|
|
3387
|
+
if (!tasks || tasks.length === 0) {
|
|
3388
|
+
await sleep2(5e3);
|
|
3389
|
+
return false;
|
|
3390
|
+
}
|
|
3391
|
+
const fanOut = await this.fanOut(goal, tasks);
|
|
3392
|
+
this.iterationsSinceCompact++;
|
|
3393
|
+
const successCount = fanOut.results.filter((r) => r.status === "success").length;
|
|
3394
|
+
const status = fanOut.goalComplete ? "success" : fanOut.allSuccessful ? "success" : "failure";
|
|
3395
|
+
const note = [
|
|
3396
|
+
`${successCount}/${fanOut.results.length} subagents succeeded`,
|
|
3397
|
+
fanOut.goalComplete ? "[GOAL_COMPLETE]" : "",
|
|
3398
|
+
fanOut.partialOutput ? `Output: ${fanOut.partialOutput.slice(0, 120)}` : ""
|
|
3399
|
+
].filter(Boolean).join(" | ");
|
|
3400
|
+
await this.appendIterationEntry({
|
|
3401
|
+
source: "parallel",
|
|
3402
|
+
task: `parallel:${tasks.length} slots \u2014 ${tasks.slice(0, 3).join(", ")}${tasks.length > 3 ? "..." : ""}`,
|
|
3403
|
+
status,
|
|
3404
|
+
note
|
|
3405
|
+
});
|
|
3406
|
+
if (fanOut.goalComplete) {
|
|
3407
|
+
this.stopRequested = true;
|
|
3408
|
+
return true;
|
|
3409
|
+
}
|
|
3410
|
+
await this.maybeCompact();
|
|
3411
|
+
return fanOut.allSuccessful;
|
|
3412
|
+
}
|
|
3413
|
+
// -------------------------------------------------------------------------
|
|
3414
|
+
// Fan-out
|
|
3415
|
+
// -------------------------------------------------------------------------
|
|
3416
|
+
async fanOut(goal, tasks) {
|
|
3417
|
+
const coordinator = this.coordinator;
|
|
3418
|
+
const slotCount = Math.min(this.slots, tasks.length);
|
|
3419
|
+
const recentJournal = goal.journal.slice(-5).map((e) => ` #${e.iteration} [${e.status}] ${e.task}${e.note ? ` \u2014 ${e.note.slice(0, 80)}` : ""}`).join("\n");
|
|
3420
|
+
const directivePreamble = [
|
|
3421
|
+
"\u2550\u2550\u2550 ETERNAL AUTONOMY \u2014 parallel task slot \u2550\u2550\u2550",
|
|
3422
|
+
"",
|
|
3423
|
+
`Mission: ${goal.goal}`,
|
|
3424
|
+
`Total parallel slots: ${slotCount}`,
|
|
3425
|
+
"",
|
|
3426
|
+
recentJournal ? `Recent journal (last 5):
|
|
3427
|
+
${recentJournal}` : "No prior iterations.",
|
|
3428
|
+
"",
|
|
3429
|
+
"\u2500\u2500 EXECUTION PROTOCOL \u2500\u2500",
|
|
3430
|
+
"\u2022 Execute the assigned task end-to-end using multiple tool calls.",
|
|
3431
|
+
"\u2022 Emit `[done]` on its own line when the task is complete.",
|
|
3432
|
+
"\u2022 Do not ask for confirmation \u2014 YOLO is active.",
|
|
3433
|
+
"\u2022 If the overall Mission is accomplished, emit `[GOAL_COMPLETE]` followed by a verification recipe.",
|
|
3434
|
+
"\u2022 Keep output concise \u2014 summarize findings, do not transcribe files."
|
|
3435
|
+
].join("\n");
|
|
3436
|
+
const taskIds = [];
|
|
3437
|
+
const subagentIds = [];
|
|
3438
|
+
const spawnPromises = [];
|
|
3439
|
+
for (let i = 0; i < slotCount; i++) {
|
|
3440
|
+
const task = tasks[i];
|
|
3441
|
+
const subagentId = `parallel-${this.iterations}-${i}`;
|
|
3442
|
+
const taskId = randomUUID();
|
|
3443
|
+
const spec = {
|
|
3444
|
+
id: taskId,
|
|
3445
|
+
description: `${directivePreamble}
|
|
3446
|
+
|
|
3447
|
+
\u2500\u2500 SLOT ${i + 1}/${slotCount} \u2500\u2500
|
|
3448
|
+
Task: ${task}
|
|
3449
|
+
`,
|
|
3450
|
+
subagentId
|
|
3451
|
+
};
|
|
3452
|
+
spawnPromises.push((async () => {
|
|
3453
|
+
try {
|
|
3454
|
+
await coordinator.spawn({
|
|
3455
|
+
id: subagentId,
|
|
3456
|
+
name: `slot-${subagentId.slice(-6)}`,
|
|
3457
|
+
maxIterations: 50,
|
|
3458
|
+
maxToolCalls: 200,
|
|
3459
|
+
timeoutMs: this.timeoutMs
|
|
3460
|
+
});
|
|
3461
|
+
subagentIds.push(subagentId);
|
|
3462
|
+
taskIds.push(taskId);
|
|
3463
|
+
await coordinator.assign(spec);
|
|
3464
|
+
} catch {
|
|
3465
|
+
}
|
|
3466
|
+
})());
|
|
3467
|
+
}
|
|
3468
|
+
await Promise.all(spawnPromises);
|
|
3469
|
+
if (taskIds.length === 0) {
|
|
3470
|
+
return { results: [], allSuccessful: false, goalComplete: false, partialOutput: "" };
|
|
3471
|
+
}
|
|
3472
|
+
let results = [];
|
|
3473
|
+
try {
|
|
3474
|
+
const ctrl = new AbortController();
|
|
3475
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs + 6e4);
|
|
3476
|
+
try {
|
|
3477
|
+
results = await coordinator.awaitTasks(taskIds);
|
|
3478
|
+
} finally {
|
|
3479
|
+
clearTimeout(timer);
|
|
3480
|
+
}
|
|
3481
|
+
} catch {
|
|
3482
|
+
results = coordinator.results().slice(-taskIds.length);
|
|
3483
|
+
}
|
|
3484
|
+
const allSuccessful = results.length > 0 && results.every((r) => r.status === "success");
|
|
3485
|
+
const goalComplete = results.some(
|
|
3486
|
+
(r) => r.status === "success" && typeof r.result === "string" && GOAL_COMPLETE_MARKER2.test(r.result)
|
|
3487
|
+
);
|
|
3488
|
+
const partialOutput = results.map((r) => typeof r.result === "string" ? r.result : "").filter(Boolean).join("\n\n");
|
|
3489
|
+
return { results, allSuccessful, goalComplete, partialOutput };
|
|
3490
|
+
}
|
|
3491
|
+
// -------------------------------------------------------------------------
|
|
3492
|
+
// Goal decomposition
|
|
3493
|
+
// -------------------------------------------------------------------------
|
|
3494
|
+
async decomposeGoal(goal) {
|
|
3495
|
+
const todos = this.opts.agent.ctx?.todos;
|
|
3496
|
+
const tasks = [];
|
|
3497
|
+
if (Array.isArray(todos)) {
|
|
3498
|
+
const pending = todos.filter((t) => t.status === "pending").slice(0, this.slots);
|
|
3499
|
+
for (const t of pending) {
|
|
3500
|
+
tasks.push(`[todo] ${t.content}`);
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
if (tasks.length < this.slots) {
|
|
3504
|
+
try {
|
|
3505
|
+
const gitStatus = await (this.opts.gitStatusReader?.() ?? this.readGitStatus());
|
|
3506
|
+
const dirty = gitStatus.trim();
|
|
3507
|
+
if (dirty) {
|
|
3508
|
+
const lines = dirty.split("\n").slice(0, this.slots - tasks.length);
|
|
3509
|
+
for (const line of lines) {
|
|
3510
|
+
const file = line.replace(/^[ MADRUC?]{2}\s*/, "").trim();
|
|
3511
|
+
if (file) tasks.push(`[git] inspect and fix: ${file}`);
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
} catch {
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
if (tasks.length < this.slots) {
|
|
3518
|
+
const remaining = this.slots - tasks.length;
|
|
3519
|
+
const brainstormed = await this.brainstormSubtasks(goal, remaining);
|
|
3520
|
+
tasks.push(...brainstormed);
|
|
3521
|
+
}
|
|
3522
|
+
return tasks.length > 0 ? tasks.slice(0, this.slots) : null;
|
|
3523
|
+
}
|
|
3524
|
+
async readGitStatus() {
|
|
3525
|
+
const { execFile: execFile2 } = await import('child_process');
|
|
3526
|
+
const { promisify: promisify2 } = await import('util');
|
|
3527
|
+
const execFileP2 = promisify2(execFile2);
|
|
3528
|
+
const { stdout } = await execFileP2("git", ["status", "--porcelain"], {
|
|
3529
|
+
cwd: this.opts.projectRoot,
|
|
3530
|
+
timeout: 5e3
|
|
3531
|
+
});
|
|
3532
|
+
return stdout;
|
|
3533
|
+
}
|
|
3534
|
+
async brainstormSubtasks(goal, count) {
|
|
3535
|
+
const lastFew = goal.journal.slice(-5).map((e) => ` - [${e.status}] ${e.task}`).join("\n");
|
|
3536
|
+
const directive = [
|
|
3537
|
+
`Decompose this goal into exactly ${count} independent sub-tasks for parallel execution.`,
|
|
3538
|
+
"",
|
|
3539
|
+
`Goal: ${goal.goal}`,
|
|
3540
|
+
"",
|
|
3541
|
+
lastFew ? `Recent:
|
|
3542
|
+
${lastFew}` : "No prior iterations.",
|
|
3543
|
+
"",
|
|
3544
|
+
`Output exactly ${count} tasks, one per line, under 120 chars each.`,
|
|
3545
|
+
"Format: TASK-1 | TASK-2 | ... (pipe-separated, no numbering, no preamble).",
|
|
3546
|
+
"Each task must be independently actionable with no shared dependencies."
|
|
3547
|
+
].join("\n");
|
|
3548
|
+
try {
|
|
3549
|
+
const ctrl = new AbortController();
|
|
3550
|
+
const timer = setTimeout(() => ctrl.abort(), 6e4);
|
|
3551
|
+
try {
|
|
3552
|
+
const result = await this.opts.agent.run([{ type: "text", text: directive }], {
|
|
3553
|
+
signal: ctrl.signal,
|
|
3554
|
+
maxIterations: 1
|
|
3555
|
+
});
|
|
3556
|
+
if (result.status !== "done") return [];
|
|
3557
|
+
const text = (result.finalText ?? "").trim();
|
|
3558
|
+
if (!text) return [];
|
|
3559
|
+
const tasks = text.split("|").map((t) => t.trim()).filter((t) => t.length > 10 && t.length < 240);
|
|
3560
|
+
return tasks.slice(0, count);
|
|
3561
|
+
} finally {
|
|
3562
|
+
clearTimeout(timer);
|
|
3563
|
+
}
|
|
3564
|
+
} catch {
|
|
3565
|
+
return [];
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
// -------------------------------------------------------------------------
|
|
3569
|
+
// Compaction
|
|
3570
|
+
// -------------------------------------------------------------------------
|
|
3571
|
+
async maybeCompact() {
|
|
3572
|
+
const compactor = this.opts.compactor;
|
|
3573
|
+
if (!compactor) return;
|
|
3574
|
+
const ctx = this.opts.agent.ctx;
|
|
3575
|
+
if (!ctx) return;
|
|
3576
|
+
const shouldRun = this.iterationsSinceCompact >= (this.opts.compactEveryNIterations ?? 25);
|
|
3577
|
+
if (!shouldRun) return;
|
|
3578
|
+
const report = await compactor.compact(ctx, { aggressive: false });
|
|
3579
|
+
this.iterationsSinceCompact = 0;
|
|
3580
|
+
await this.appendIterationEntry({
|
|
3581
|
+
source: "manual",
|
|
3582
|
+
task: "compaction (cadence)",
|
|
3583
|
+
status: "success",
|
|
3584
|
+
note: `saved ~${report.before - report.after} tokens`
|
|
3585
|
+
});
|
|
3586
|
+
}
|
|
3587
|
+
// -------------------------------------------------------------------------
|
|
3588
|
+
// Helpers
|
|
3589
|
+
// -------------------------------------------------------------------------
|
|
3590
|
+
async appendIterationEntry(entry) {
|
|
3591
|
+
const current = await loadGoal(this.goalPath);
|
|
3592
|
+
if (!current) return;
|
|
3593
|
+
const updated = appendJournal(current, entry);
|
|
3594
|
+
await saveGoal(this.goalPath, updated);
|
|
3595
|
+
const entryWithMeta = {
|
|
3596
|
+
at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
3597
|
+
iteration: updated.iterations,
|
|
3598
|
+
...entry
|
|
3599
|
+
};
|
|
3600
|
+
this.opts.onIteration?.(entryWithMeta);
|
|
3601
|
+
}
|
|
3602
|
+
async appendFailure(task, note) {
|
|
3603
|
+
await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
|
|
3604
|
+
}
|
|
3605
|
+
async persistState(state) {
|
|
3606
|
+
const current = await loadGoal(this.goalPath);
|
|
3607
|
+
if (!current) return;
|
|
3608
|
+
if (current.engineState === state) return;
|
|
3609
|
+
await saveGoal(this.goalPath, { ...current, engineState: state });
|
|
3610
|
+
}
|
|
3611
|
+
};
|
|
3612
|
+
|
|
3613
|
+
// src/execution/autonomy-prompt-contributor.ts
|
|
3614
|
+
function makeAutonomyPromptContributor(opts) {
|
|
3615
|
+
return async (ctx) => {
|
|
3616
|
+
if (ctx.subagent) return [];
|
|
3617
|
+
if (!opts.enabled()) return [];
|
|
3618
|
+
let goal;
|
|
3619
|
+
try {
|
|
3620
|
+
goal = await loadGoal(opts.goalPath);
|
|
3621
|
+
} catch {
|
|
3622
|
+
return [];
|
|
3623
|
+
}
|
|
3624
|
+
if (!goal) return [];
|
|
3625
|
+
const missionState = goal.goalState ?? "active";
|
|
3626
|
+
if (missionState !== "active") return [];
|
|
3627
|
+
const tailSize = opts.journalTailSize ?? 5;
|
|
3628
|
+
const journalTail = goal.journal.slice(-tailSize).map((e) => {
|
|
3629
|
+
const note = e.note ? ` \u2014 ${e.note.slice(0, 80)}` : "";
|
|
3630
|
+
return ` #${e.iteration} [${e.status}] ${e.task}${note}`;
|
|
3631
|
+
});
|
|
3632
|
+
const text = [
|
|
3633
|
+
"## ETERNAL AUTONOMY \u2014 active mission",
|
|
3634
|
+
"",
|
|
3635
|
+
"You are inside a long-running autonomous loop. The user is asleep",
|
|
3636
|
+
"and is not available to confirm decisions. Each turn you receive a",
|
|
3637
|
+
"directive describing one concrete sub-task that advances the mission.",
|
|
3638
|
+
"",
|
|
3639
|
+
`Mission: ${goal.goal}`,
|
|
3640
|
+
`Iteration: #${goal.iterations}`,
|
|
3641
|
+
journalTail.length > 0 ? `Recent journal (last ${journalTail.length}):
|
|
3642
|
+
${journalTail.join("\n")}` : "Recent journal: (none \u2014 this is the first iteration)",
|
|
3643
|
+
"",
|
|
3644
|
+
"### Loop control markers",
|
|
3645
|
+
"Emit these on their own line in your final text \u2014 case-insensitive,",
|
|
3646
|
+
"whitespace-tolerant, but they must occupy the entire line:",
|
|
3647
|
+
"- `[continue]` \u2014 chain to the next internal step without returning.",
|
|
3648
|
+
"- `[done]` \u2014 the current sub-task is finished; return to the engine.",
|
|
3649
|
+
"- `[GOAL_COMPLETE]` \u2014 emit ONLY when the OVERALL mission is",
|
|
3650
|
+
" verifiably done. Must be followed by a one-paragraph verification",
|
|
3651
|
+
" recipe (artifact path, test command, or 10-second reproduction).",
|
|
3652
|
+
" The engine halts on this marker \u2014 false positives waste real",
|
|
3653
|
+
" human time. If unsure, emit `[done]` and let the next iteration",
|
|
3654
|
+
" decide.",
|
|
3655
|
+
"",
|
|
3656
|
+
"### Operating principles",
|
|
3657
|
+
"- YOLO is active. Do NOT ask for confirmation, do NOT propose",
|
|
3658
|
+
" options. Pick the best path and execute it.",
|
|
3659
|
+
"- Use tools freely; multiple calls per turn are normal and expected.",
|
|
3660
|
+
"- When working on a todo, mark it `in_progress` via the todos tool",
|
|
3661
|
+
" before tool work and `completed` (or `cancelled` with a reason)",
|
|
3662
|
+
" when done. The loop reads todo state between iterations.",
|
|
3663
|
+
"- If an approach fails twice in a row, pivot. Don't grind on the",
|
|
3664
|
+
" same wall \u2014 try a different angle, file a cancel on the todo, or",
|
|
3665
|
+
" surface the obstacle via `[done]` and let the next iteration",
|
|
3666
|
+
" re-plan."
|
|
3667
|
+
].join("\n");
|
|
3668
|
+
return [
|
|
3669
|
+
{
|
|
3670
|
+
type: "text",
|
|
3671
|
+
text,
|
|
3672
|
+
cache_control: { type: "ephemeral" }
|
|
3673
|
+
}
|
|
3674
|
+
];
|
|
3675
|
+
};
|
|
3676
|
+
}
|
|
3677
|
+
|
|
3678
|
+
// src/execution/goal-preamble.ts
|
|
3679
|
+
function buildGoalPreamble(goal) {
|
|
3680
|
+
return [
|
|
3681
|
+
"[GOAL \u2014 LOCKED IN. You will work on this until it is verifiably done.",
|
|
3682
|
+
"The user granted you full autonomy. Read these constraints once, then act.",
|
|
3683
|
+
"",
|
|
3684
|
+
"YOUR GOAL:",
|
|
3685
|
+
"---",
|
|
3686
|
+
goal,
|
|
3687
|
+
"---",
|
|
3688
|
+
"",
|
|
3689
|
+
"AUTHORITY YOU HAVE:",
|
|
3690
|
+
"- Spawn as many subagents as the work needs (delegate / spawn_subagent).",
|
|
3691
|
+
" Parallel + recursive fan-out are both fine. There is no spawn budget.",
|
|
3692
|
+
"- Use any provider/model per subagent \u2014 pick the right tool for each",
|
|
3693
|
+
" piece of work. Heavy reasoning model for planning, fast model for",
|
|
3694
|
+
" batch work, specialist model for domain code.",
|
|
3695
|
+
"- Run unlimited tool calls and iterations. There is NO hidden budget.",
|
|
3696
|
+
" The Agent loop auto-extends every 100 iterations forever.",
|
|
3697
|
+
"- Retry failed tools with different inputs, alternative paths, fresh",
|
|
3698
|
+
" subagents. Switch providers mid-run if one is rate-limited.",
|
|
3699
|
+
"- Re-plan freely when an approach hits a dead end. You are not obliged",
|
|
3700
|
+
" to stick with the first plan you proposed.",
|
|
3701
|
+
"",
|
|
3702
|
+
'WHAT "DONE" MEANS \u2014 non-negotiable:',
|
|
3703
|
+
"- You can name a concrete artifact (a passing test, a written file at",
|
|
3704
|
+
" a specific path, a fixed bug verified by re-running the failing case,",
|
|
3705
|
+
" a clean grep that previously had matches).",
|
|
3706
|
+
"- You can tell the user HOW to verify it themselves in 10 seconds.",
|
|
3707
|
+
'- You have NOT hedged. None of: "looks like it should work", "I',
|
|
3708
|
+
' believe this fixes it", "the changes appear correct".',
|
|
3709
|
+
"",
|
|
3710
|
+
"WHAT IS NOT DONE \u2014 never report any of these as completion:",
|
|
3711
|
+
"- An error message you didn't recover from.",
|
|
3712
|
+
'- An empty result, a 0-line file, a "no matches found" you accepted',
|
|
3713
|
+
" without questioning the search.",
|
|
3714
|
+
'- "Should I continue?" / "Want me to also...?" / "Let me know if you',
|
|
3715
|
+
' want X." Those are hedges. The user already told you to finish the',
|
|
3716
|
+
" goal \u2014 just do it.",
|
|
3717
|
+
"- Partial progress dressed up as success. Fixed 3 of 5 bugs = 60%",
|
|
3718
|
+
" done, not done.",
|
|
3719
|
+
"- A subagent's failed/timeout/stopped TaskResult that you didn't",
|
|
3720
|
+
" respond to with a fresh attempt (different role, different model,",
|
|
3721
|
+
" tighter prompt).",
|
|
3722
|
+
"",
|
|
3723
|
+
"PERSISTENCE PROTOCOL:",
|
|
3724
|
+
"- If blocked, try at least 3 different angles before reporting the",
|
|
3725
|
+
" problem to the user. Different tool inputs, different subagent",
|
|
3726
|
+
" roles, different providers, different decomposition of the task.",
|
|
3727
|
+
"- If a tool fails, read its error, alter the input, try again. Do",
|
|
3728
|
+
" not just report the failure back.",
|
|
3729
|
+
"- If a subagent returns useless output, respawn with a tighter prompt",
|
|
3730
|
+
' or a different role. Do not accept "I could not determine\u2026" as the',
|
|
3731
|
+
" final answer.",
|
|
3732
|
+
"- Use `ask_subagent` for one-shot questions when you don't need a",
|
|
3733
|
+
" full delegated task.",
|
|
3734
|
+
"",
|
|
3735
|
+
"REPORTING:",
|
|
3736
|
+
"- Stream short progress notes between major actions so the user can",
|
|
3737
|
+
" monitor. Do not go silent for 50 tool calls then dump a wall of",
|
|
3738
|
+
" text \u2014 but also do not narrate every tool call.",
|
|
3739
|
+
"- Use the shared scratchpad (if available) to leave breadcrumbs",
|
|
3740
|
+
" subagents can read.",
|
|
3741
|
+
"- Final response must include: (a) what was accomplished, (b) how",
|
|
3742
|
+
" to verify, (c) any caveats (residual TODOs, things the user",
|
|
3743
|
+
" should know about).",
|
|
3744
|
+
"",
|
|
3745
|
+
"BEGIN.]"
|
|
3746
|
+
].join("\n");
|
|
3747
|
+
}
|
|
2441
3748
|
|
|
2442
3749
|
// src/execution/retry-policy.ts
|
|
2443
3750
|
var DefaultRetryPolicy = class _DefaultRetryPolicy {
|
|
@@ -2679,6 +3986,6 @@ function parseDescription(raw) {
|
|
|
2679
3986
|
return { trigger, scope };
|
|
2680
3987
|
}
|
|
2681
3988
|
|
|
2682
|
-
export { AutoCompactionMiddleware, AutonomousRunner, DefaultErrorHandler, DefaultRetryPolicy, DefaultSkillLoader, DoneConditionChecker, EternalAutonomyEngine, HybridCompactor, IntelligentCompactor, SelectiveCompactor, ToolExecutor, buildGoalPreamble, makeAutonomyPromptContributor };
|
|
3989
|
+
export { AutoCompactionMiddleware, AutonomousRunner, DefaultErrorHandler, DefaultRetryPolicy, DefaultSkillLoader, DoneConditionChecker, EternalAutonomyEngine, HybridCompactor, IntelligentCompactor, ParallelEternalEngine, SelectiveCompactor, ToolExecutor, buildGoalPreamble, makeAutonomyPromptContributor };
|
|
2683
3990
|
//# sourceMappingURL=index.js.map
|
|
2684
3991
|
//# sourceMappingURL=index.js.map
|