@wrongstack/core 0.6.0 → 0.6.3
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-BKNiE1VH.d.ts → agent-bridge-eb7qnNrd.d.ts} +1 -1
- package/dist/coordination/index.d.ts +5 -5
- package/dist/coordination/index.js +183 -87
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +10 -10
- package/dist/defaults/index.js +596 -114
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-DPQKFX7W.d.ts → events-BHuIHekD.d.ts} +27 -4
- package/dist/execution/index.d.ts +149 -6
- package/dist/execution/index.js +401 -21
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +2 -2
- package/dist/{goal-store-BQ3YX1h1.d.ts → goal-store-DVCfj7Ff.d.ts} +20 -0
- package/dist/{index-B0qTujQW.d.ts → index-BOn9NK7D.d.ts} +1 -1
- package/dist/{index-DkdRz6yK.d.ts → index-CPcDqvZh.d.ts} +11 -4
- package/dist/index.d.ts +50 -16
- package/dist/index.js +831 -125
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +2 -2
- package/dist/infrastructure/index.js +1 -1
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +2 -2
- package/dist/kernel/index.js.map +1 -1
- package/dist/{multi-agent-Cpp7FXUl.d.ts → multi-agent-CxSb-9dQ.d.ts} +30 -5
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +1 -1
- package/dist/observability/index.js.map +1 -1
- package/dist/{path-resolver--g_hKJ7V.d.ts → path-resolver-CMGNadvq.d.ts} +1 -1
- package/dist/{plan-templates-CKJs_sYh.d.ts → plan-templates-BJflQY2i.d.ts} +1 -1
- package/dist/{provider-runner-hl4Il3xS.d.ts → provider-runner-BFgNXpaP.d.ts} +1 -1
- package/dist/sdd/index.d.ts +2 -2
- package/dist/storage/index.d.ts +3 -3
- package/dist/storage/index.js +3 -0
- package/dist/storage/index.js.map +1 -1
- package/dist/{tool-executor-B03CRwu-.d.ts → tool-executor-FoxBjULX.d.ts} +1 -1
- package/dist/types/index.d.ts +7 -7
- package/dist/types/index.js +30 -4
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +5 -1
package/dist/defaults/index.js
CHANGED
|
@@ -3691,17 +3691,23 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
|
|
|
3691
3691
|
handleTextDelta(state, ev.text);
|
|
3692
3692
|
events.emit("provider.text_delta", { ctx, text: ev.text });
|
|
3693
3693
|
break;
|
|
3694
|
-
case "tool_use_start":
|
|
3695
|
-
|
|
3696
|
-
|
|
3694
|
+
case "tool_use_start": {
|
|
3695
|
+
const idVal = ev.id;
|
|
3696
|
+
const nameVal = ev.name;
|
|
3697
|
+
handleToolUseStart(state, { id: idVal, name: nameVal });
|
|
3698
|
+
const emittedPayload = { ctx, id: idVal ?? "unknown", name: nameVal ?? "unknown" };
|
|
3699
|
+
events.emit("provider.tool_use_start", emittedPayload);
|
|
3697
3700
|
break;
|
|
3701
|
+
}
|
|
3698
3702
|
case "tool_use_input_delta":
|
|
3699
3703
|
handleToolUseInputDelta(state, ev);
|
|
3700
3704
|
break;
|
|
3701
|
-
case "tool_use_stop":
|
|
3705
|
+
case "tool_use_stop": {
|
|
3706
|
+
const stoppedName = state.tools.get(ev.id)?.name ?? "unknown";
|
|
3702
3707
|
handleToolUseStop(state, ev);
|
|
3703
|
-
events.emit("provider.tool_use_stop", { ctx, id: ev.id });
|
|
3708
|
+
events.emit("provider.tool_use_stop", { ctx, id: ev.id, name: stoppedName });
|
|
3704
3709
|
break;
|
|
3710
|
+
}
|
|
3705
3711
|
case "thinking_start":
|
|
3706
3712
|
handleThinkingStart(state, ev);
|
|
3707
3713
|
break;
|
|
@@ -3828,7 +3834,7 @@ var DefaultProviderRunner = class {
|
|
|
3828
3834
|
};
|
|
3829
3835
|
|
|
3830
3836
|
// src/utils/token-estimate.ts
|
|
3831
|
-
var RoughTokenEstimate = (text) => Math.max(1, Math.ceil(text.length /
|
|
3837
|
+
var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
3832
3838
|
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
3833
3839
|
var ESTIMATE_CACHE_MAX_SIZE = 1e4;
|
|
3834
3840
|
function getCachedEstimate(key, compute) {
|
|
@@ -3915,7 +3921,7 @@ var HybridCompactor = class {
|
|
|
3915
3921
|
eliseThreshold;
|
|
3916
3922
|
estimator;
|
|
3917
3923
|
constructor(opts = {}) {
|
|
3918
|
-
this.preserveK = opts.preserveK ??
|
|
3924
|
+
this.preserveK = opts.preserveK ?? 5;
|
|
3919
3925
|
this.eliseThreshold = opts.eliseThreshold ?? 2e3;
|
|
3920
3926
|
this.estimator = opts.estimator ?? estimateTextTokens;
|
|
3921
3927
|
}
|
|
@@ -3959,6 +3965,17 @@ var HybridCompactor = class {
|
|
|
3959
3965
|
preserveStart = i;
|
|
3960
3966
|
}
|
|
3961
3967
|
}
|
|
3968
|
+
for (let i = preserveStart; i < messages.length; i++) {
|
|
3969
|
+
const m = messages[i];
|
|
3970
|
+
if (!m || typeof m.content === "string" || !Array.isArray(m.content)) continue;
|
|
3971
|
+
const hasToolUse2 = m.content.some((b) => b.type === "tool_use");
|
|
3972
|
+
if (hasToolUse2 && i + 1 < messages.length) {
|
|
3973
|
+
const next = messages[i + 1];
|
|
3974
|
+
if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => b.type === "tool_result")) {
|
|
3975
|
+
preserveStart = i + 1;
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3962
3979
|
let saved = 0;
|
|
3963
3980
|
let changed = false;
|
|
3964
3981
|
const nextMessages = new Array(messages.length);
|
|
@@ -3980,7 +3997,7 @@ var HybridCompactor = class {
|
|
|
3980
3997
|
const elided = {
|
|
3981
3998
|
type: "tool_result",
|
|
3982
3999
|
tool_use_id: b.tool_use_id,
|
|
3983
|
-
content: `[elided: ~${tokens} tokens
|
|
4000
|
+
content: `[elided: ~${tokens} tokens]`,
|
|
3984
4001
|
is_error: b.is_error
|
|
3985
4002
|
};
|
|
3986
4003
|
return elided;
|
|
@@ -4119,7 +4136,28 @@ var IntelligentCompactor = class {
|
|
|
4119
4136
|
try {
|
|
4120
4137
|
summaryText = await this.callSummarizer(toSummarize, ctx);
|
|
4121
4138
|
} catch {
|
|
4122
|
-
|
|
4139
|
+
const toolNames = /* @__PURE__ */ new Set();
|
|
4140
|
+
const filePaths = /* @__PURE__ */ new Set();
|
|
4141
|
+
let userTurns = 0, assistantTurns = 0;
|
|
4142
|
+
for (const m of toSummarize) {
|
|
4143
|
+
if (m.role === "user") userTurns++;
|
|
4144
|
+
else if (m.role === "assistant") {
|
|
4145
|
+
assistantTurns++;
|
|
4146
|
+
if (Array.isArray(m.content)) {
|
|
4147
|
+
for (const b of m.content) {
|
|
4148
|
+
if (b.type === "tool_use") toolNames.add(b.name ?? "unknown");
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
4153
|
+
const matches = text.matchAll(/(?:[\w.,\-/@]+\/)*[\w.,\-/@]+\.\w+/g);
|
|
4154
|
+
for (const m_ of matches) filePaths.add(m_[0]);
|
|
4155
|
+
}
|
|
4156
|
+
const parts = [`${toSummarize.length} turns (${userTurns} user, ${assistantTurns} assistant)`];
|
|
4157
|
+
if (toolNames.size > 0) parts.push(`tools: ${[...toolNames].join(", ")}`);
|
|
4158
|
+
if (filePaths.size > 0) parts.push(`files: ${[...filePaths].slice(0, 10).join(", ")}`);
|
|
4159
|
+
summaryText = parts.join(" | ");
|
|
4160
|
+
if (!summaryText) summaryText = `${toSummarize.length} earlier turns omitted`;
|
|
4123
4161
|
}
|
|
4124
4162
|
const summaryMsg = {
|
|
4125
4163
|
role: "system",
|
|
@@ -4203,6 +4241,17 @@ var IntelligentCompactor = class {
|
|
|
4203
4241
|
preserveStart = i;
|
|
4204
4242
|
}
|
|
4205
4243
|
}
|
|
4244
|
+
for (let i = preserveStart; i < messages.length; i++) {
|
|
4245
|
+
const m = messages[i];
|
|
4246
|
+
if (!m || typeof m.content === "string" || !Array.isArray(m.content)) continue;
|
|
4247
|
+
const hasToolUse2 = m.content.some((b) => b.type === "tool_use");
|
|
4248
|
+
if (hasToolUse2 && i + 1 < messages.length) {
|
|
4249
|
+
const next = messages[i + 1];
|
|
4250
|
+
if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => b.type === "tool_result")) {
|
|
4251
|
+
preserveStart = i + 1;
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4206
4255
|
let saved = 0;
|
|
4207
4256
|
let changed = false;
|
|
4208
4257
|
const nextMessages = new Array(messages.length);
|
|
@@ -4717,7 +4766,15 @@ var AutoCompactionMiddleware = class {
|
|
|
4717
4766
|
}
|
|
4718
4767
|
async compact(ctx, aggressive, pressure) {
|
|
4719
4768
|
try {
|
|
4720
|
-
await this.compactor.compact(ctx, { aggressive });
|
|
4769
|
+
const report = await this.compactor.compact(ctx, { aggressive });
|
|
4770
|
+
this.events?.emit("compaction.fired", {
|
|
4771
|
+
level: pressure.level,
|
|
4772
|
+
tokens: pressure.tokens,
|
|
4773
|
+
load: pressure.load,
|
|
4774
|
+
maxContext: this._maxContext,
|
|
4775
|
+
report,
|
|
4776
|
+
aggressive
|
|
4777
|
+
});
|
|
4721
4778
|
} catch (err) {
|
|
4722
4779
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
4723
4780
|
const fatal = this.failureMode === "throw" || this.failureMode === "throw_on_hard" && pressure.level === "hard";
|
|
@@ -4971,13 +5028,17 @@ var ToolExecutor = class {
|
|
|
4971
5028
|
const ctrl = new AbortController();
|
|
4972
5029
|
const timer = setTimeout(() => ctrl.abort(new Error("tool timeout")), timeoutMs);
|
|
4973
5030
|
const combined = AbortSignal.any([parentSignal, ctrl.signal]);
|
|
5031
|
+
let cleanupCalled = false;
|
|
5032
|
+
let caught = false;
|
|
4974
5033
|
try {
|
|
4975
5034
|
if (typeof tool.executeStream === "function") {
|
|
4976
5035
|
return await this.runStreamedTool(tool, input, ctx, combined, toolUseId);
|
|
4977
5036
|
}
|
|
4978
5037
|
return await tool.execute(input, ctx, { signal: combined });
|
|
4979
5038
|
} catch (err) {
|
|
5039
|
+
caught = true;
|
|
4980
5040
|
if (combined.aborted && typeof tool.cleanup === "function") {
|
|
5041
|
+
cleanupCalled = true;
|
|
4981
5042
|
try {
|
|
4982
5043
|
await tool.cleanup(input, ctx);
|
|
4983
5044
|
} catch {
|
|
@@ -4986,6 +5047,16 @@ var ToolExecutor = class {
|
|
|
4986
5047
|
throw err;
|
|
4987
5048
|
} finally {
|
|
4988
5049
|
clearTimeout(timer);
|
|
5050
|
+
if (combined.aborted && !caught) {
|
|
5051
|
+
if (!cleanupCalled && typeof tool.cleanup === "function") {
|
|
5052
|
+
try {
|
|
5053
|
+
await tool.cleanup(input, ctx);
|
|
5054
|
+
} catch {
|
|
5055
|
+
}
|
|
5056
|
+
}
|
|
5057
|
+
const reason = combined.reason instanceof Error ? combined.reason : new Error(typeof combined.reason === "string" ? combined.reason : "aborted");
|
|
5058
|
+
throw reason;
|
|
5059
|
+
}
|
|
4989
5060
|
}
|
|
4990
5061
|
}
|
|
4991
5062
|
async runStreamedTool(tool, input, ctx, signal, toolUseId) {
|
|
@@ -5043,7 +5114,8 @@ var ToolExecutor = class {
|
|
|
5043
5114
|
if (subjectKey) {
|
|
5044
5115
|
const v = obj[subjectKey];
|
|
5045
5116
|
if (typeof v === "string") {
|
|
5046
|
-
|
|
5117
|
+
const isPathKey = subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
|
|
5118
|
+
return isPathKey ? normalizePath(v) : escapeGlob(v);
|
|
5047
5119
|
}
|
|
5048
5120
|
}
|
|
5049
5121
|
if (toolName === "bash" && typeof obj.command === "string") {
|
|
@@ -5269,6 +5341,8 @@ function appendJournal(goal, entry) {
|
|
|
5269
5341
|
|
|
5270
5342
|
// src/execution/eternal-autonomy.ts
|
|
5271
5343
|
var execFileP = promisify(execFile);
|
|
5344
|
+
var BRAINSTORM_DONE = /* @__PURE__ */ Symbol("brainstorm-done");
|
|
5345
|
+
var GOAL_COMPLETE_MARKER = /^\s*\[goal[_\s-]?complete\]\s*$/im;
|
|
5272
5346
|
var EternalAutonomyEngine = class {
|
|
5273
5347
|
constructor(opts) {
|
|
5274
5348
|
this.opts = opts;
|
|
@@ -5278,6 +5352,14 @@ var EternalAutonomyEngine = class {
|
|
|
5278
5352
|
state = "idle";
|
|
5279
5353
|
stopRequested = false;
|
|
5280
5354
|
consecutiveFailures = 0;
|
|
5355
|
+
consecutiveBrainstormDone = 0;
|
|
5356
|
+
/**
|
|
5357
|
+
* Count of consecutive transient (recoverable) provider failures. Drives
|
|
5358
|
+
* the exponential backoff between iterations. Reset on the first
|
|
5359
|
+
* successful iteration so a single bad afternoon doesn't permanently
|
|
5360
|
+
* slow the loop down.
|
|
5361
|
+
*/
|
|
5362
|
+
consecutiveTransientRetries = 0;
|
|
5281
5363
|
currentCtrl = null;
|
|
5282
5364
|
iterationsSinceCompact = 0;
|
|
5283
5365
|
goalPath;
|
|
@@ -5347,9 +5429,16 @@ var EternalAutonomyEngine = class {
|
|
|
5347
5429
|
this.stopRequested = true;
|
|
5348
5430
|
return false;
|
|
5349
5431
|
}
|
|
5432
|
+
const missionState = goal.goalState ?? "active";
|
|
5433
|
+
if (missionState !== "active") {
|
|
5434
|
+
this.stopRequested = true;
|
|
5435
|
+
return false;
|
|
5436
|
+
}
|
|
5350
5437
|
const action = await this.decide(goal);
|
|
5351
5438
|
if (!action) {
|
|
5352
|
-
|
|
5439
|
+
if (!this.stopRequested) {
|
|
5440
|
+
await sleep(5e3);
|
|
5441
|
+
}
|
|
5353
5442
|
return false;
|
|
5354
5443
|
}
|
|
5355
5444
|
const ctrl = new AbortController();
|
|
@@ -5360,13 +5449,29 @@ var EternalAutonomyEngine = class {
|
|
|
5360
5449
|
);
|
|
5361
5450
|
let status = "success";
|
|
5362
5451
|
let note;
|
|
5452
|
+
let finalText = "";
|
|
5453
|
+
let isTransientFailure = false;
|
|
5363
5454
|
const tc = this.opts.agent.ctx?.tokenCounter;
|
|
5364
5455
|
const beforeUsage = tc?.total?.();
|
|
5365
5456
|
const beforeCost = tc?.estimateCost?.().total;
|
|
5366
5457
|
try {
|
|
5367
5458
|
const result = await this.opts.agent.run(
|
|
5368
5459
|
[{ type: "text", text: action.directive }],
|
|
5369
|
-
{
|
|
5460
|
+
{
|
|
5461
|
+
signal: ctrl.signal,
|
|
5462
|
+
// Enable per-call autonomous continuation so the agent can chain
|
|
5463
|
+
// multiple internal tool/response cycles end-to-end on one
|
|
5464
|
+
// directive instead of returning to the engine after a single
|
|
5465
|
+
// round-trip. The model uses `[continue]` / `[done]` markers
|
|
5466
|
+
// (or the `continue_to_next_iteration` tool) to control the
|
|
5467
|
+
// inner loop. Without this flag the engine produced shallow
|
|
5468
|
+
// iterations and almost never let a real task finish.
|
|
5469
|
+
autonomousContinue: true,
|
|
5470
|
+
// Cap the inner loop so a runaway agent.run can't burn through
|
|
5471
|
+
// the iteration timeout — the engine's own outer loop is the
|
|
5472
|
+
// long-running thing, each tick should be bounded.
|
|
5473
|
+
maxIterations: this.opts.iterationMaxAgentSteps ?? 50
|
|
5474
|
+
}
|
|
5370
5475
|
);
|
|
5371
5476
|
if (result.status === "aborted") {
|
|
5372
5477
|
status = "aborted";
|
|
@@ -5374,22 +5479,30 @@ var EternalAutonomyEngine = class {
|
|
|
5374
5479
|
} else if (result.status === "failed") {
|
|
5375
5480
|
status = "failure";
|
|
5376
5481
|
note = result.error?.describe?.() ?? "agent run failed";
|
|
5482
|
+
isTransientFailure = result.error?.recoverable === true;
|
|
5377
5483
|
} else if (result.status === "max_iterations") {
|
|
5378
5484
|
status = "failure";
|
|
5379
5485
|
note = `max iterations (${result.iterations})`;
|
|
5380
5486
|
} else {
|
|
5381
5487
|
status = "success";
|
|
5382
|
-
|
|
5488
|
+
finalText = result.finalText ?? "";
|
|
5489
|
+
const tail = finalText.slice(0, 240).replace(/\s+/g, " ").trim();
|
|
5383
5490
|
if (tail) note = tail;
|
|
5384
5491
|
}
|
|
5385
5492
|
} catch (err) {
|
|
5386
5493
|
const isAbort = err instanceof Error && (err.name === "AbortError" || err.message.includes("abort"));
|
|
5387
5494
|
status = isAbort ? "aborted" : "failure";
|
|
5388
5495
|
note = err instanceof Error ? err.message : String(err);
|
|
5496
|
+
if (!isAbort && typeof err?.recoverable === "boolean") {
|
|
5497
|
+
isTransientFailure = err.recoverable;
|
|
5498
|
+
}
|
|
5389
5499
|
} finally {
|
|
5390
5500
|
clearTimeout(timer);
|
|
5391
5501
|
this.currentCtrl = null;
|
|
5392
5502
|
}
|
|
5503
|
+
if (action.source === "todo" && action.todoId && status !== "success") {
|
|
5504
|
+
await this.bumpTodoAttempt(action.todoId);
|
|
5505
|
+
}
|
|
5393
5506
|
const afterUsage = tc?.total?.();
|
|
5394
5507
|
const afterCost = tc?.estimateCost?.().total;
|
|
5395
5508
|
const tokens = beforeUsage && afterUsage ? {
|
|
@@ -5422,6 +5535,14 @@ var EternalAutonomyEngine = class {
|
|
|
5422
5535
|
costUsd
|
|
5423
5536
|
});
|
|
5424
5537
|
if (status === "failure") {
|
|
5538
|
+
if (isTransientFailure) {
|
|
5539
|
+
this.consecutiveTransientRetries++;
|
|
5540
|
+
const delay = this.computeTransientBackoffMs();
|
|
5541
|
+
if (delay > 0) {
|
|
5542
|
+
await this.sleepInterruptible(delay);
|
|
5543
|
+
}
|
|
5544
|
+
return false;
|
|
5545
|
+
}
|
|
5425
5546
|
this.consecutiveFailures++;
|
|
5426
5547
|
return false;
|
|
5427
5548
|
}
|
|
@@ -5430,6 +5551,12 @@ var EternalAutonomyEngine = class {
|
|
|
5430
5551
|
this.consecutiveFailures++;
|
|
5431
5552
|
return false;
|
|
5432
5553
|
}
|
|
5554
|
+
this.consecutiveTransientRetries = 0;
|
|
5555
|
+
if (GOAL_COMPLETE_MARKER.test(finalText)) {
|
|
5556
|
+
await this.markGoalCompleted(action, finalText);
|
|
5557
|
+
this.stopRequested = true;
|
|
5558
|
+
return true;
|
|
5559
|
+
}
|
|
5433
5560
|
this.iterationsSinceCompact++;
|
|
5434
5561
|
await this.maybeCompact().catch((err) => {
|
|
5435
5562
|
this.opts.onError?.(
|
|
@@ -5493,11 +5620,12 @@ var EternalAutonomyEngine = class {
|
|
|
5493
5620
|
async decide(goal) {
|
|
5494
5621
|
const forceBrainstorm = this.consecutiveFailures >= (this.opts.failureBudget ?? 3);
|
|
5495
5622
|
if (!forceBrainstorm) {
|
|
5496
|
-
const todo = this.pickPendingTodo();
|
|
5623
|
+
const todo = this.pickPendingTodo(goal);
|
|
5497
5624
|
if (todo) {
|
|
5498
5625
|
return {
|
|
5499
5626
|
source: "todo",
|
|
5500
5627
|
task: todo.content,
|
|
5628
|
+
todoId: todo.id,
|
|
5501
5629
|
directive: this.buildDirective(goal, "todo", todo.content)
|
|
5502
5630
|
};
|
|
5503
5631
|
}
|
|
@@ -5511,17 +5639,38 @@ var EternalAutonomyEngine = class {
|
|
|
5511
5639
|
}
|
|
5512
5640
|
}
|
|
5513
5641
|
const brainstormed = await this.brainstormTask(goal);
|
|
5642
|
+
if (brainstormed === BRAINSTORM_DONE) {
|
|
5643
|
+
this.consecutiveBrainstormDone++;
|
|
5644
|
+
const threshold = this.opts.brainstormDoneStopThreshold ?? 3;
|
|
5645
|
+
if (this.consecutiveBrainstormDone >= threshold) {
|
|
5646
|
+
await this.markGoalCompleted(
|
|
5647
|
+
{ source: "brainstorm", task: "no further work", directive: "" },
|
|
5648
|
+
`brainstorm returned DONE ${this.consecutiveBrainstormDone}x in a row`
|
|
5649
|
+
);
|
|
5650
|
+
this.stopRequested = true;
|
|
5651
|
+
}
|
|
5652
|
+
return null;
|
|
5653
|
+
}
|
|
5514
5654
|
if (!brainstormed) return null;
|
|
5655
|
+
this.consecutiveBrainstormDone = 0;
|
|
5515
5656
|
return {
|
|
5516
5657
|
source: "brainstorm",
|
|
5517
5658
|
task: brainstormed,
|
|
5518
5659
|
directive: this.buildDirective(goal, "brainstorm", brainstormed)
|
|
5519
5660
|
};
|
|
5520
5661
|
}
|
|
5521
|
-
pickPendingTodo() {
|
|
5662
|
+
pickPendingTodo(goal) {
|
|
5522
5663
|
const todos = this.opts.agent.ctx.todos;
|
|
5523
5664
|
if (!Array.isArray(todos)) return null;
|
|
5524
|
-
|
|
5665
|
+
const attempts = goal.todoAttempts ?? {};
|
|
5666
|
+
const ceiling = this.opts.todoMaxAttempts ?? 3;
|
|
5667
|
+
for (const t of todos) {
|
|
5668
|
+
if (t.status !== "pending") continue;
|
|
5669
|
+
const used = attempts[t.id] ?? 0;
|
|
5670
|
+
if (used >= ceiling) continue;
|
|
5671
|
+
return t;
|
|
5672
|
+
}
|
|
5673
|
+
return null;
|
|
5525
5674
|
}
|
|
5526
5675
|
async pickGitTask() {
|
|
5527
5676
|
let out;
|
|
@@ -5558,7 +5707,10 @@ ${lastFew}` : "No prior iterations yet.",
|
|
|
5558
5707
|
"- One sentence, imperative form, under 200 chars.",
|
|
5559
5708
|
"- No preamble, no explanation, no markdown \u2014 just the task line.",
|
|
5560
5709
|
"- If recent iterations show repeated failures on the same target, pivot.",
|
|
5561
|
-
"- If the goal appears fully accomplished
|
|
5710
|
+
"- If the goal appears fully accomplished AND you can name a concrete",
|
|
5711
|
+
" artifact / test / output that proves it, output exactly: DONE",
|
|
5712
|
+
"- Be conservative with DONE: if the recent journal contains failures",
|
|
5713
|
+
" or aborted entries, the goal is almost certainly NOT done."
|
|
5562
5714
|
].join("\n");
|
|
5563
5715
|
try {
|
|
5564
5716
|
const ctrl = new AbortController();
|
|
@@ -5570,9 +5722,11 @@ ${lastFew}` : "No prior iterations yet.",
|
|
|
5570
5722
|
);
|
|
5571
5723
|
if (result.status !== "done") return null;
|
|
5572
5724
|
const text = (result.finalText ?? "").trim();
|
|
5573
|
-
if (!text
|
|
5725
|
+
if (!text) return null;
|
|
5726
|
+
if (/^DONE\.?$/i.test(text)) return BRAINSTORM_DONE;
|
|
5574
5727
|
const firstLine = text.split("\n").find((l) => l.trim().length > 0)?.trim();
|
|
5575
5728
|
if (!firstLine) return null;
|
|
5729
|
+
if (/^DONE\.?$/i.test(firstLine)) return BRAINSTORM_DONE;
|
|
5576
5730
|
return firstLine.slice(0, 240);
|
|
5577
5731
|
} finally {
|
|
5578
5732
|
clearTimeout(timer);
|
|
@@ -5582,19 +5736,82 @@ ${lastFew}` : "No prior iterations yet.",
|
|
|
5582
5736
|
}
|
|
5583
5737
|
}
|
|
5584
5738
|
buildDirective(goal, source, task) {
|
|
5739
|
+
const recentJournal = goal.journal.slice(-5).map((e) => ` #${e.iteration} [${e.status}] ${e.task}${e.note ? ` \u2014 ${e.note.slice(0, 80)}` : ""}`).join("\n");
|
|
5585
5740
|
return [
|
|
5586
|
-
"
|
|
5741
|
+
"\u2550\u2550\u2550 ETERNAL AUTONOMY \u2014 iteration directive \u2550\u2550\u2550",
|
|
5587
5742
|
"",
|
|
5588
|
-
`
|
|
5743
|
+
`Mission: ${goal.goal}`,
|
|
5744
|
+
`Iteration: #${goal.iterations + 1}`,
|
|
5589
5745
|
`Source: ${source}`,
|
|
5590
5746
|
`Task: ${task}`,
|
|
5591
5747
|
"",
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
"
|
|
5595
|
-
"
|
|
5748
|
+
recentJournal ? `Recent journal (last 5):
|
|
5749
|
+
${recentJournal}` : "No prior iterations.",
|
|
5750
|
+
"",
|
|
5751
|
+
"\u2500\u2500 EXECUTION PROTOCOL \u2500\u2500",
|
|
5752
|
+
"You are inside a long-running autonomous loop. Each iteration you",
|
|
5753
|
+
"execute ONE concrete task that advances the Mission. No user is",
|
|
5754
|
+
"available to clarify \u2014 make defensible decisions and move forward.",
|
|
5755
|
+
"",
|
|
5756
|
+
"1. EXECUTE END-TO-END",
|
|
5757
|
+
" \u2022 Use multiple tool calls freely. Emit `[continue]` on its own line",
|
|
5758
|
+
" to chain to the next internal step without returning.",
|
|
5759
|
+
" \u2022 When this iteration's Task is finished (real artifact / passing",
|
|
5760
|
+
" test / applied diff / clean output), emit `[done]` on its own line.",
|
|
5761
|
+
" \u2022 Do not stop on the first obstacle \u2014 try at least 3 distinct",
|
|
5762
|
+
" approaches before giving up. YOLO is active; no confirmations.",
|
|
5763
|
+
"",
|
|
5764
|
+
"2. UPDATE TODO STATE (when Source is `todo`)",
|
|
5765
|
+
" \u2022 Mark this todo `in_progress` via the todos tool before tool work.",
|
|
5766
|
+
" \u2022 Mark it `completed` on success, with a one-line outcome note.",
|
|
5767
|
+
" \u2022 If you cannot make progress after 2 distinct attempts, mark it",
|
|
5768
|
+
" `cancelled` with the obstacle. The loop will skip it next time.",
|
|
5769
|
+
"",
|
|
5770
|
+
"3. MISSION-COMPLETE PROTOCOL",
|
|
5771
|
+
" \u2022 If \u2014 and ONLY if \u2014 the OVERALL Mission (not just this Task) is",
|
|
5772
|
+
" verifiably accomplished, emit on its own line:",
|
|
5773
|
+
" [GOAL_COMPLETE]",
|
|
5774
|
+
" followed by a one-paragraph verification recipe (artifact path,",
|
|
5775
|
+
" test command, or 10-second reproduction). This halts the loop.",
|
|
5776
|
+
" \u2022 NEVER emit [GOAL_COMPLETE] on optimism, partial progress, or",
|
|
5777
|
+
' "looks fine". Required: a concrete artifact that proves it AND',
|
|
5778
|
+
" no recent journal failures contradicting completion.",
|
|
5779
|
+
" \u2022 If unsure, emit `[done]` instead and let the next iteration",
|
|
5780
|
+
" decide. The loop is patient; false completion is not.",
|
|
5781
|
+
"",
|
|
5782
|
+
"4. NO INTERACTIVITY",
|
|
5783
|
+
" \u2022 Do not ask questions, do not request confirmation, do not propose",
|
|
5784
|
+
" options. Pick the best path and execute. The user is asleep."
|
|
5596
5785
|
].join("\n");
|
|
5597
5786
|
}
|
|
5787
|
+
/**
|
|
5788
|
+
* Exponential backoff for transient provider errors. `2^N * base`
|
|
5789
|
+
* capped at `transientBackoffMaxMs`. Zero base disables backoff.
|
|
5790
|
+
* Public-private to keep `runOneIteration` readable; the value is
|
|
5791
|
+
* recomputed each call from the current retry count, so callers
|
|
5792
|
+
* don't have to track state.
|
|
5793
|
+
*/
|
|
5794
|
+
computeTransientBackoffMs() {
|
|
5795
|
+
const base = this.opts.transientBackoffBaseMs ?? 2e3;
|
|
5796
|
+
const cap = this.opts.transientBackoffMaxMs ?? 6e4;
|
|
5797
|
+
if (base <= 0) return 0;
|
|
5798
|
+
const exponent = Math.max(0, this.consecutiveTransientRetries - 1);
|
|
5799
|
+
return Math.min(cap, base * Math.pow(2, exponent));
|
|
5800
|
+
}
|
|
5801
|
+
/**
|
|
5802
|
+
* Sleep that wakes early if `stopRequested` flips. Polls every 250 ms
|
|
5803
|
+
* so SIGINT / `/autonomy stop` can land in the middle of a long
|
|
5804
|
+
* backoff instead of waiting up to a minute for the timer.
|
|
5805
|
+
*/
|
|
5806
|
+
async sleepInterruptible(totalMs) {
|
|
5807
|
+
const step = 250;
|
|
5808
|
+
let remaining = totalMs;
|
|
5809
|
+
while (remaining > 0 && !this.stopRequested) {
|
|
5810
|
+
const chunk = Math.min(step, remaining);
|
|
5811
|
+
await sleep(chunk);
|
|
5812
|
+
remaining -= chunk;
|
|
5813
|
+
}
|
|
5814
|
+
}
|
|
5598
5815
|
async appendIterationEntry(entry) {
|
|
5599
5816
|
const current = await loadGoal(this.goalPath);
|
|
5600
5817
|
if (!current) {
|
|
@@ -5603,6 +5820,39 @@ ${lastFew}` : "No prior iterations yet.",
|
|
|
5603
5820
|
const updated = appendJournal(current, entry);
|
|
5604
5821
|
await saveGoal(this.goalPath, updated);
|
|
5605
5822
|
}
|
|
5823
|
+
/**
|
|
5824
|
+
* Persistent per-todo failure counter. Skipped silently when the goal
|
|
5825
|
+
* file has been removed (graceful clear). Each non-success iteration
|
|
5826
|
+
* against a todo source bumps the counter by 1; `pickPendingTodo` reads
|
|
5827
|
+
* the counter to rotate past stuck todos once they cross `todoMaxAttempts`.
|
|
5828
|
+
*/
|
|
5829
|
+
async bumpTodoAttempt(todoId) {
|
|
5830
|
+
const current = await loadGoal(this.goalPath);
|
|
5831
|
+
if (!current) return;
|
|
5832
|
+
const attempts = { ...current.todoAttempts ?? {} };
|
|
5833
|
+
attempts[todoId] = (attempts[todoId] ?? 0) + 1;
|
|
5834
|
+
await saveGoal(this.goalPath, { ...current, todoAttempts: attempts });
|
|
5835
|
+
}
|
|
5836
|
+
/**
|
|
5837
|
+
* Flip the mission to `completed` and journal it. Called from two
|
|
5838
|
+
* paths: (a) `[GOAL_COMPLETE]` marker in a successful iteration's
|
|
5839
|
+
* finalText, (b) `brainstorm` returning DONE consecutively past the
|
|
5840
|
+
* configured threshold. Idempotent — re-entry is a no-op once the
|
|
5841
|
+
* goal is already `completed`.
|
|
5842
|
+
*/
|
|
5843
|
+
async markGoalCompleted(action, note) {
|
|
5844
|
+
const current = await loadGoal(this.goalPath);
|
|
5845
|
+
if (!current) return;
|
|
5846
|
+
if (current.goalState === "completed") return;
|
|
5847
|
+
const withFlag = { ...current, goalState: "completed" };
|
|
5848
|
+
const withEntry = appendJournal(withFlag, {
|
|
5849
|
+
source: action.source,
|
|
5850
|
+
task: `MISSION COMPLETE \u2014 ${action.task}`.slice(0, 240),
|
|
5851
|
+
status: "success",
|
|
5852
|
+
note: note.slice(0, 240)
|
|
5853
|
+
});
|
|
5854
|
+
await saveGoal(this.goalPath, withEntry);
|
|
5855
|
+
}
|
|
5606
5856
|
async appendFailure(task, note) {
|
|
5607
5857
|
await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
|
|
5608
5858
|
}
|
|
@@ -5617,6 +5867,142 @@ function sleep(ms) {
|
|
|
5617
5867
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
5618
5868
|
}
|
|
5619
5869
|
|
|
5870
|
+
// src/execution/autonomy-prompt-contributor.ts
|
|
5871
|
+
function makeAutonomyPromptContributor(opts) {
|
|
5872
|
+
return async (ctx) => {
|
|
5873
|
+
if (ctx.subagent) return [];
|
|
5874
|
+
if (!opts.enabled()) return [];
|
|
5875
|
+
let goal;
|
|
5876
|
+
try {
|
|
5877
|
+
goal = await loadGoal(opts.goalPath);
|
|
5878
|
+
} catch {
|
|
5879
|
+
return [];
|
|
5880
|
+
}
|
|
5881
|
+
if (!goal) return [];
|
|
5882
|
+
const missionState = goal.goalState ?? "active";
|
|
5883
|
+
if (missionState !== "active") return [];
|
|
5884
|
+
const tailSize = opts.journalTailSize ?? 5;
|
|
5885
|
+
const journalTail = goal.journal.slice(-tailSize).map((e) => {
|
|
5886
|
+
const note = e.note ? ` \u2014 ${e.note.slice(0, 80)}` : "";
|
|
5887
|
+
return ` #${e.iteration} [${e.status}] ${e.task}${note}`;
|
|
5888
|
+
});
|
|
5889
|
+
const text = [
|
|
5890
|
+
"## ETERNAL AUTONOMY \u2014 active mission",
|
|
5891
|
+
"",
|
|
5892
|
+
"You are inside a long-running autonomous loop. The user is asleep",
|
|
5893
|
+
"and is not available to confirm decisions. Each turn you receive a",
|
|
5894
|
+
"directive describing one concrete sub-task that advances the mission.",
|
|
5895
|
+
"",
|
|
5896
|
+
`Mission: ${goal.goal}`,
|
|
5897
|
+
`Iteration: #${goal.iterations}`,
|
|
5898
|
+
journalTail.length > 0 ? `Recent journal (last ${journalTail.length}):
|
|
5899
|
+
${journalTail.join("\n")}` : "Recent journal: (none \u2014 this is the first iteration)",
|
|
5900
|
+
"",
|
|
5901
|
+
"### Loop control markers",
|
|
5902
|
+
"Emit these on their own line in your final text \u2014 case-insensitive,",
|
|
5903
|
+
"whitespace-tolerant, but they must occupy the entire line:",
|
|
5904
|
+
"- `[continue]` \u2014 chain to the next internal step without returning.",
|
|
5905
|
+
"- `[done]` \u2014 the current sub-task is finished; return to the engine.",
|
|
5906
|
+
"- `[GOAL_COMPLETE]` \u2014 emit ONLY when the OVERALL mission is",
|
|
5907
|
+
" verifiably done. Must be followed by a one-paragraph verification",
|
|
5908
|
+
" recipe (artifact path, test command, or 10-second reproduction).",
|
|
5909
|
+
" The engine halts on this marker \u2014 false positives waste real",
|
|
5910
|
+
" human time. If unsure, emit `[done]` and let the next iteration",
|
|
5911
|
+
" decide.",
|
|
5912
|
+
"",
|
|
5913
|
+
"### Operating principles",
|
|
5914
|
+
"- YOLO is active. Do NOT ask for confirmation, do NOT propose",
|
|
5915
|
+
" options. Pick the best path and execute it.",
|
|
5916
|
+
"- Use tools freely; multiple calls per turn are normal and expected.",
|
|
5917
|
+
"- When working on a todo, mark it `in_progress` via the todos tool",
|
|
5918
|
+
" before tool work and `completed` (or `cancelled` with a reason)",
|
|
5919
|
+
" when done. The loop reads todo state between iterations.",
|
|
5920
|
+
"- If an approach fails twice in a row, pivot. Don't grind on the",
|
|
5921
|
+
" same wall \u2014 try a different angle, file a cancel on the todo, or",
|
|
5922
|
+
" surface the obstacle via `[done]` and let the next iteration",
|
|
5923
|
+
" re-plan."
|
|
5924
|
+
].join("\n");
|
|
5925
|
+
return [
|
|
5926
|
+
{
|
|
5927
|
+
type: "text",
|
|
5928
|
+
text,
|
|
5929
|
+
cache_control: { type: "ephemeral" }
|
|
5930
|
+
}
|
|
5931
|
+
];
|
|
5932
|
+
};
|
|
5933
|
+
}
|
|
5934
|
+
|
|
5935
|
+
// src/execution/goal-preamble.ts
|
|
5936
|
+
function buildGoalPreamble(goal) {
|
|
5937
|
+
return [
|
|
5938
|
+
"[GOAL \u2014 LOCKED IN. You will work on this until it is verifiably done.",
|
|
5939
|
+
"The user granted you full autonomy. Read these constraints once, then act.",
|
|
5940
|
+
"",
|
|
5941
|
+
"YOUR GOAL:",
|
|
5942
|
+
"---",
|
|
5943
|
+
goal,
|
|
5944
|
+
"---",
|
|
5945
|
+
"",
|
|
5946
|
+
"AUTHORITY YOU HAVE:",
|
|
5947
|
+
"- Spawn as many subagents as the work needs (delegate / spawn_subagent).",
|
|
5948
|
+
" Parallel + recursive fan-out are both fine. There is no spawn budget.",
|
|
5949
|
+
"- Use any provider/model per subagent \u2014 pick the right tool for each",
|
|
5950
|
+
" piece of work. Heavy reasoning model for planning, fast model for",
|
|
5951
|
+
" batch work, specialist model for domain code.",
|
|
5952
|
+
"- Run unlimited tool calls and iterations. There is NO hidden budget.",
|
|
5953
|
+
" The Agent loop auto-extends every 100 iterations forever.",
|
|
5954
|
+
"- Retry failed tools with different inputs, alternative paths, fresh",
|
|
5955
|
+
" subagents. Switch providers mid-run if one is rate-limited.",
|
|
5956
|
+
"- Re-plan freely when an approach hits a dead end. You are not obliged",
|
|
5957
|
+
" to stick with the first plan you proposed.",
|
|
5958
|
+
"",
|
|
5959
|
+
'WHAT "DONE" MEANS \u2014 non-negotiable:',
|
|
5960
|
+
"- You can name a concrete artifact (a passing test, a written file at",
|
|
5961
|
+
" a specific path, a fixed bug verified by re-running the failing case,",
|
|
5962
|
+
" a clean grep that previously had matches).",
|
|
5963
|
+
"- You can tell the user HOW to verify it themselves in 10 seconds.",
|
|
5964
|
+
'- You have NOT hedged. None of: "looks like it should work", "I',
|
|
5965
|
+
' believe this fixes it", "the changes appear correct".',
|
|
5966
|
+
"",
|
|
5967
|
+
"WHAT IS NOT DONE \u2014 never report any of these as completion:",
|
|
5968
|
+
"- An error message you didn't recover from.",
|
|
5969
|
+
'- An empty result, a 0-line file, a "no matches found" you accepted',
|
|
5970
|
+
" without questioning the search.",
|
|
5971
|
+
'- "Should I continue?" / "Want me to also...?" / "Let me know if you',
|
|
5972
|
+
' want X." Those are hedges. The user already told you to finish the',
|
|
5973
|
+
" goal \u2014 just do it.",
|
|
5974
|
+
"- Partial progress dressed up as success. Fixed 3 of 5 bugs = 60%",
|
|
5975
|
+
" done, not done.",
|
|
5976
|
+
"- A subagent's failed/timeout/stopped TaskResult that you didn't",
|
|
5977
|
+
" respond to with a fresh attempt (different role, different model,",
|
|
5978
|
+
" tighter prompt).",
|
|
5979
|
+
"",
|
|
5980
|
+
"PERSISTENCE PROTOCOL:",
|
|
5981
|
+
"- If blocked, try at least 3 different angles before reporting the",
|
|
5982
|
+
" problem to the user. Different tool inputs, different subagent",
|
|
5983
|
+
" roles, different providers, different decomposition of the task.",
|
|
5984
|
+
"- If a tool fails, read its error, alter the input, try again. Do",
|
|
5985
|
+
" not just report the failure back.",
|
|
5986
|
+
"- If a subagent returns useless output, respawn with a tighter prompt",
|
|
5987
|
+
' or a different role. Do not accept "I could not determine\u2026" as the',
|
|
5988
|
+
" final answer.",
|
|
5989
|
+
"- Use `ask_subagent` for one-shot questions when you don't need a",
|
|
5990
|
+
" full delegated task.",
|
|
5991
|
+
"",
|
|
5992
|
+
"REPORTING:",
|
|
5993
|
+
"- Stream short progress notes between major actions so the user can",
|
|
5994
|
+
" monitor. Do not go silent for 50 tool calls then dump a wall of",
|
|
5995
|
+
" text \u2014 but also do not narrate every tool call.",
|
|
5996
|
+
"- Use the shared scratchpad (if available) to leave breadcrumbs",
|
|
5997
|
+
" subagents can read.",
|
|
5998
|
+
"- Final response must include: (a) what was accomplished, (b) how",
|
|
5999
|
+
" to verify, (c) any caveats (residual TODOs, things the user",
|
|
6000
|
+
" should know about).",
|
|
6001
|
+
"",
|
|
6002
|
+
"BEGIN.]"
|
|
6003
|
+
].join("\n");
|
|
6004
|
+
}
|
|
6005
|
+
|
|
5620
6006
|
// src/coordination/director.ts
|
|
5621
6007
|
init_atomic_write();
|
|
5622
6008
|
|
|
@@ -5875,40 +6261,12 @@ var FleetBus = class {
|
|
|
5875
6261
|
* subagent teardown so the listeners don't outlive the run.
|
|
5876
6262
|
*/
|
|
5877
6263
|
attach(subagentId, bus, taskId) {
|
|
5878
|
-
const
|
|
5879
|
-
"
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
"tool.confirm_needed",
|
|
5883
|
-
"iteration.started",
|
|
5884
|
-
"iteration.completed",
|
|
5885
|
-
"provider.text_delta",
|
|
5886
|
-
// Subagent extended-thinking output. Forwarded so the FleetPanel /
|
|
5887
|
-
// /fleet log can surface "the planner is thinking…" instead of a
|
|
5888
|
-
// silent gap between iteration.started and the first text_delta.
|
|
5889
|
-
"provider.thinking_delta",
|
|
5890
|
-
"provider.response",
|
|
5891
|
-
"provider.retry",
|
|
5892
|
-
"provider.error",
|
|
5893
|
-
"session.started",
|
|
5894
|
-
"session.ended",
|
|
5895
|
-
"session.damaged",
|
|
5896
|
-
"compaction.fired",
|
|
5897
|
-
"compaction.failed",
|
|
5898
|
-
"token.threshold",
|
|
5899
|
-
// Subagent hit a soft budget limit — coordinator can extend or stop.
|
|
5900
|
-
"budget.threshold_reached"
|
|
5901
|
-
];
|
|
5902
|
-
const offs = [];
|
|
5903
|
-
for (const t of FORWARDED_TYPES) {
|
|
5904
|
-
offs.push(
|
|
5905
|
-
bus.on(t, (payload) => {
|
|
5906
|
-
this.emit({ subagentId, taskId, ts: Date.now(), type: t, payload });
|
|
5907
|
-
})
|
|
5908
|
-
);
|
|
5909
|
-
}
|
|
6264
|
+
const off = bus.onPattern("*", (type, payload) => {
|
|
6265
|
+
if (type.startsWith("subagent.")) return;
|
|
6266
|
+
this.emit({ subagentId, taskId, ts: Date.now(), type, payload });
|
|
6267
|
+
});
|
|
5910
6268
|
return () => {
|
|
5911
|
-
|
|
6269
|
+
off();
|
|
5912
6270
|
};
|
|
5913
6271
|
}
|
|
5914
6272
|
/** Subscribe to every event from one subagent. */
|
|
@@ -6073,7 +6431,7 @@ var BudgetThresholdSignal = class extends Error {
|
|
|
6073
6431
|
this.decision = decision;
|
|
6074
6432
|
}
|
|
6075
6433
|
};
|
|
6076
|
-
var SubagentBudget = class {
|
|
6434
|
+
var SubagentBudget = class _SubagentBudget {
|
|
6077
6435
|
limits;
|
|
6078
6436
|
iterations = 0;
|
|
6079
6437
|
toolCalls = 0;
|
|
@@ -6082,6 +6440,26 @@ var SubagentBudget = class {
|
|
|
6082
6440
|
costUsd = 0;
|
|
6083
6441
|
startTime = null;
|
|
6084
6442
|
_onThreshold;
|
|
6443
|
+
/**
|
|
6444
|
+
* Tracks which budget kinds currently have an extension request
|
|
6445
|
+
* in flight. While a kind is here, further `checkLimit` calls for the
|
|
6446
|
+
* same kind are no-ops — without this dedup, every `recordIteration`
|
|
6447
|
+
* after the limit is reached spawns a fresh decision Promise (until
|
|
6448
|
+
* the first one lands and patches limits), flooding the FleetBus
|
|
6449
|
+
* with redundant threshold events. Cleared in `checkLimitAsync`'s
|
|
6450
|
+
* `finally`.
|
|
6451
|
+
*/
|
|
6452
|
+
pendingExtensions = /* @__PURE__ */ new Set();
|
|
6453
|
+
/**
|
|
6454
|
+
* Hard cap on how long `checkLimitAsync` waits for the coordinator to
|
|
6455
|
+
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
6456
|
+
* or hung listener (Director not built / event filter detached mid-run)
|
|
6457
|
+
* leaves the budget over-limit and never enforces anything, since
|
|
6458
|
+
* `checkLimit` returns synchronously via `void this.checkLimitAsync`.
|
|
6459
|
+
* Hardcoded for now — most fleets set their own per-task timeout that
|
|
6460
|
+
* dwarfs this anyway. 30 s matches the existing event-emit timeoutMs.
|
|
6461
|
+
*/
|
|
6462
|
+
static DECISION_TIMEOUT_MS = 3e4;
|
|
6085
6463
|
/**
|
|
6086
6464
|
* Injected by the runner when wiring the budget to its EventBus.
|
|
6087
6465
|
* Used by `checkLimitAsync` to emit `budget.threshold_reached` events.
|
|
@@ -6124,56 +6502,92 @@ var SubagentBudget = class {
|
|
|
6124
6502
|
if (kind === "timeout" || !this._onThreshold) {
|
|
6125
6503
|
throw new BudgetExceededError(kind, limit, used);
|
|
6126
6504
|
}
|
|
6127
|
-
|
|
6505
|
+
const bus = this._events;
|
|
6506
|
+
if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
|
|
6507
|
+
throw new BudgetExceededError(kind, limit, used);
|
|
6508
|
+
}
|
|
6509
|
+
if (this.pendingExtensions.has(kind)) return;
|
|
6510
|
+
this.pendingExtensions.add(kind);
|
|
6511
|
+
const decision = this.negotiateExtension(kind, used, limit);
|
|
6512
|
+
throw new BudgetThresholdSignal(kind, limit, used, decision);
|
|
6128
6513
|
}
|
|
6129
6514
|
/**
|
|
6130
|
-
*
|
|
6131
|
-
*
|
|
6132
|
-
*
|
|
6515
|
+
* Drive the threshold handler to a decision. Resolves with `'stop'`
|
|
6516
|
+
* (signal the runner to abort) or `{ extend: ... }` (limits already
|
|
6517
|
+
* patched in-place; the runner should not abort). Always releases the
|
|
6518
|
+
* `pendingExtensions` slot in `finally`.
|
|
6519
|
+
*
|
|
6520
|
+
* The 'continue' return from a sync handler is treated as
|
|
6521
|
+
* `{ extend: {} }` — keep going without patching, next overrun will
|
|
6522
|
+
* fire a fresh signal.
|
|
6133
6523
|
*/
|
|
6134
|
-
async
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6524
|
+
async negotiateExtension(kind, used, limit) {
|
|
6525
|
+
try {
|
|
6526
|
+
const result = this._onThreshold({
|
|
6527
|
+
kind,
|
|
6528
|
+
used,
|
|
6529
|
+
limit,
|
|
6530
|
+
// Inject a requestDecision helper the handler can call to emit the
|
|
6531
|
+
// budget.threshold_reached event and wait for the coordinator's verdict.
|
|
6532
|
+
// A hard fallback timer guarantees the promise eventually resolves
|
|
6533
|
+
// even if no listener responds — without it, an absent/detached
|
|
6534
|
+
// Director would leave the budget permanently in "asking" state.
|
|
6535
|
+
requestDecision: () => {
|
|
6536
|
+
const bus = this._events;
|
|
6537
|
+
if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
|
|
6538
|
+
return Promise.resolve("stop");
|
|
6539
|
+
}
|
|
6540
|
+
return new Promise((resolve2) => {
|
|
6541
|
+
let resolved = false;
|
|
6542
|
+
const respond = (d) => {
|
|
6543
|
+
if (resolved) return;
|
|
6544
|
+
resolved = true;
|
|
6545
|
+
resolve2(d);
|
|
6546
|
+
};
|
|
6547
|
+
const fallback = setTimeout(
|
|
6548
|
+
() => respond("stop"),
|
|
6549
|
+
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
6550
|
+
);
|
|
6551
|
+
bus.emit("budget.threshold_reached", {
|
|
6552
|
+
kind,
|
|
6553
|
+
used,
|
|
6554
|
+
limit,
|
|
6555
|
+
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
6556
|
+
extend: (extra) => {
|
|
6557
|
+
clearTimeout(fallback);
|
|
6558
|
+
respond({ extend: extra });
|
|
6559
|
+
},
|
|
6560
|
+
deny: () => {
|
|
6561
|
+
clearTimeout(fallback);
|
|
6562
|
+
respond("stop");
|
|
6563
|
+
}
|
|
6564
|
+
});
|
|
6151
6565
|
});
|
|
6152
|
-
}
|
|
6566
|
+
}
|
|
6567
|
+
});
|
|
6568
|
+
if (result === "throw") return "stop";
|
|
6569
|
+
if (result === "continue") return { extend: {} };
|
|
6570
|
+
const decision = await result;
|
|
6571
|
+
if (decision === "stop") return "stop";
|
|
6572
|
+
const ext = decision.extend;
|
|
6573
|
+
if (ext.maxIterations !== void 0) {
|
|
6574
|
+
this.limits.maxIterations = ext.maxIterations;
|
|
6153
6575
|
}
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
if (ext.maxToolCalls !== void 0) {
|
|
6170
|
-
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
6171
|
-
}
|
|
6172
|
-
if (ext.maxTokens !== void 0) {
|
|
6173
|
-
this.limits.maxTokens = ext.maxTokens;
|
|
6174
|
-
}
|
|
6175
|
-
if (ext.maxCostUsd !== void 0) {
|
|
6176
|
-
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
6576
|
+
if (ext.maxToolCalls !== void 0) {
|
|
6577
|
+
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
6578
|
+
}
|
|
6579
|
+
if (ext.maxTokens !== void 0) {
|
|
6580
|
+
this.limits.maxTokens = ext.maxTokens;
|
|
6581
|
+
}
|
|
6582
|
+
if (ext.maxCostUsd !== void 0) {
|
|
6583
|
+
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
6584
|
+
}
|
|
6585
|
+
if (ext.timeoutMs !== void 0) {
|
|
6586
|
+
this.limits.timeoutMs = ext.timeoutMs;
|
|
6587
|
+
}
|
|
6588
|
+
return decision;
|
|
6589
|
+
} finally {
|
|
6590
|
+
this.pendingExtensions.delete(kind);
|
|
6177
6591
|
}
|
|
6178
6592
|
}
|
|
6179
6593
|
recordIteration() {
|
|
@@ -6456,6 +6870,19 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
6456
6870
|
setRunner(runner) {
|
|
6457
6871
|
this.runner = runner;
|
|
6458
6872
|
}
|
|
6873
|
+
/**
|
|
6874
|
+
* Change the in-flight dispatch ceiling at runtime. Lowering does NOT
|
|
6875
|
+
* preempt running tasks — already-dispatched subagents finish their
|
|
6876
|
+
* current task; only future dispatches respect the new cap. Raising
|
|
6877
|
+
* immediately tries to fill the freed slots from the pending queue.
|
|
6878
|
+
*/
|
|
6879
|
+
setMaxConcurrent(n) {
|
|
6880
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
6881
|
+
throw new Error(`maxConcurrent must be a finite integer >= 1, got ${n}`);
|
|
6882
|
+
}
|
|
6883
|
+
this.config.maxConcurrent = Math.floor(n);
|
|
6884
|
+
this.tryDispatchNext();
|
|
6885
|
+
}
|
|
6459
6886
|
async spawn(subagent) {
|
|
6460
6887
|
const id = subagent.id || randomUUID();
|
|
6461
6888
|
if (this.subagents.has(id)) {
|
|
@@ -6715,14 +7142,65 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
6715
7142
|
this.recordCompletion(result);
|
|
6716
7143
|
}
|
|
6717
7144
|
async executeWithTimeout(runner, task, ctx, budget) {
|
|
6718
|
-
const
|
|
6719
|
-
if (
|
|
7145
|
+
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
7146
|
+
if (initialTimeoutMs === void 0) return runner(task, ctx);
|
|
7147
|
+
const start = Date.now();
|
|
6720
7148
|
let timer = null;
|
|
6721
7149
|
const timeoutPromise = new Promise((_, reject) => {
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
7150
|
+
const armFor = (ms) => {
|
|
7151
|
+
if (timer) clearTimeout(timer);
|
|
7152
|
+
timer = setTimeout(async () => {
|
|
7153
|
+
const elapsed = Date.now() - start;
|
|
7154
|
+
const limit = budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
7155
|
+
if (!budget.onThreshold) {
|
|
7156
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
7157
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
7158
|
+
return;
|
|
7159
|
+
}
|
|
7160
|
+
try {
|
|
7161
|
+
const result = budget.onThreshold({
|
|
7162
|
+
kind: "timeout",
|
|
7163
|
+
used: elapsed,
|
|
7164
|
+
limit,
|
|
7165
|
+
requestDecision: () => new Promise((resolveDecision) => {
|
|
7166
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
7167
|
+
kind: "timeout",
|
|
7168
|
+
used: elapsed,
|
|
7169
|
+
limit,
|
|
7170
|
+
timeoutMs: 3e4,
|
|
7171
|
+
extend: (extra) => resolveDecision({ extend: extra }),
|
|
7172
|
+
deny: () => resolveDecision("stop")
|
|
7173
|
+
});
|
|
7174
|
+
})
|
|
7175
|
+
});
|
|
7176
|
+
const decision = typeof result === "string" ? result : await result;
|
|
7177
|
+
if (decision === "continue") {
|
|
7178
|
+
armFor(Math.max(1e3, limit));
|
|
7179
|
+
return;
|
|
7180
|
+
}
|
|
7181
|
+
if (decision === "throw" || decision === "stop") {
|
|
7182
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
7183
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
7184
|
+
return;
|
|
7185
|
+
}
|
|
7186
|
+
if (decision.extend.timeoutMs !== void 0) {
|
|
7187
|
+
budget.limits.timeoutMs = decision.extend.timeoutMs;
|
|
7188
|
+
const newLimit = decision.extend.timeoutMs;
|
|
7189
|
+
const remaining = Math.max(1e3, newLimit - elapsed);
|
|
7190
|
+
armFor(remaining);
|
|
7191
|
+
return;
|
|
7192
|
+
}
|
|
7193
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
7194
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
7195
|
+
} catch (err) {
|
|
7196
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
7197
|
+
reject(
|
|
7198
|
+
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
7199
|
+
);
|
|
7200
|
+
}
|
|
7201
|
+
}, ms);
|
|
7202
|
+
};
|
|
7203
|
+
armFor(initialTimeoutMs);
|
|
6726
7204
|
});
|
|
6727
7205
|
try {
|
|
6728
7206
|
return await Promise.race([runner(task, ctx), timeoutPromise]);
|
|
@@ -7333,7 +7811,7 @@ var Director = class {
|
|
|
7333
7811
|
return;
|
|
7334
7812
|
}
|
|
7335
7813
|
extendCounts.set(guardKey, prior + 1);
|
|
7336
|
-
|
|
7814
|
+
setImmediate(() => {
|
|
7337
7815
|
const extra = {};
|
|
7338
7816
|
switch (payload.kind) {
|
|
7339
7817
|
case "iterations":
|
|
@@ -7348,9 +7826,12 @@ var Director = class {
|
|
|
7348
7826
|
case "cost":
|
|
7349
7827
|
extra.maxCostUsd = Math.min(payload.limit * 1.5, 10);
|
|
7350
7828
|
break;
|
|
7829
|
+
case "timeout":
|
|
7830
|
+
extra.timeoutMs = Math.min(Math.ceil(payload.limit * 1.5), 60 * 6e4);
|
|
7831
|
+
break;
|
|
7351
7832
|
}
|
|
7352
7833
|
payload.extend(extra);
|
|
7353
|
-
}
|
|
7834
|
+
});
|
|
7354
7835
|
});
|
|
7355
7836
|
}
|
|
7356
7837
|
/** Best-effort session-writer append. Swallows failures — the director
|
|
@@ -8145,6 +8626,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
8145
8626
|
const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
|
|
8146
8627
|
const aborter = new AbortController();
|
|
8147
8628
|
ctx.budget._events = events;
|
|
8629
|
+
ctx.budget.onThreshold = ({ requestDecision }) => requestDecision();
|
|
8148
8630
|
let budgetError = null;
|
|
8149
8631
|
const onBudgetError = (err) => {
|
|
8150
8632
|
if (err instanceof BudgetThresholdSignal) {
|
|
@@ -11458,7 +11940,7 @@ function wireMetricsToEvents(events, sink) {
|
|
|
11458
11940
|
events.on("token.threshold", (e) => sink.gauge("agent.tokens.used", e.used)),
|
|
11459
11941
|
events.on("compaction.fired", (e) => {
|
|
11460
11942
|
sink.counter("compaction.fired.total");
|
|
11461
|
-
sink.histogram("compaction.reduction_tokens", e.before - e.after);
|
|
11943
|
+
sink.histogram("compaction.reduction_tokens", e.report.before - e.report.after);
|
|
11462
11944
|
}),
|
|
11463
11945
|
events.on(
|
|
11464
11946
|
"mcp.server.connected",
|
|
@@ -12231,6 +12713,6 @@ var allServers = () => ({
|
|
|
12231
12713
|
"minimax-vision": { ...miniMaxVisionServer(), enabled: false }
|
|
12232
12714
|
});
|
|
12233
12715
|
|
|
12234
|
-
export { AISpecBuilder, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets2 as decryptConfigSecrets, deriveTodosFromPlanItem, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, formatPlanTemplates, getPlanTemplate, getTemplate, githubServer, googleMapsServer, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
|
|
12716
|
+
export { AISpecBuilder, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets2 as decryptConfigSecrets, deriveTodosFromPlanItem, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, formatPlanTemplates, getPlanTemplate, getTemplate, githubServer, googleMapsServer, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAutonomyPromptContributor, makeDirectorSessionFactory, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
|
|
12235
12717
|
//# sourceMappingURL=index.js.map
|
|
12236
12718
|
//# sourceMappingURL=index.js.map
|