@wrongstack/core 0.9.19 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -33
- package/dist/{agent-bridge-DMVOX0cF.d.ts → agent-bridge-DBBGP5O4.d.ts} +1 -1
- package/dist/{agent-subagent-runner-C4qt9e5Y.d.ts → agent-subagent-runner-KwufTWwn.d.ts} +3 -4
- package/dist/compactor-D1RHFRmF.d.ts +41 -0
- package/dist/{config-CWva0qoL.d.ts → config-CJXBka2r.d.ts} +38 -2
- package/dist/{context-BRNbHmRM.d.ts → context-7u93AcGD.d.ts} +140 -0
- package/dist/coordination/index.d.ts +13 -13
- package/dist/coordination/index.js +786 -200
- package/dist/coordination/index.js.map +1 -1
- package/dist/{default-config-Uzla85H-.d.ts → default-config-DEXI4jsl.d.ts} +10 -1
- package/dist/defaults/index.d.ts +24 -24
- package/dist/defaults/index.js +1612 -528
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-CiG9qUM_.d.ts → events-Bw1K5E6K.d.ts} +49 -1
- package/dist/execution/index.d.ts +46 -32
- package/dist/execution/index.js +663 -169
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/extension/index.js.map +1 -1
- package/dist/goal-store-BeRsj7YX.d.ts +188 -0
- package/dist/{index-p95HQ22A.d.ts → index-DIt5TIaF.d.ts} +8 -8
- package/dist/{index-aizK8olO.d.ts → index-DgQWnZxP.d.ts} +11 -8
- package/dist/index.d.ts +338 -41
- package/dist/index.js +2747 -787
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +36 -0
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js +57 -8
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-BkVEqkRe.d.ts → mcp-servers-BgINZzuo.d.ts} +25 -3
- package/dist/models/index.d.ts +3 -3
- package/dist/models/index.js +164 -11
- package/dist/models/index.js.map +1 -1
- package/dist/models-registry-gwMAo6E3.d.ts +86 -0
- package/dist/{multi-agent-3ZnTB1aT.d.ts → multi-agent-C1O8EFzb.d.ts} +77 -24
- package/dist/{multi-agent-coordinator-bRaI_aD1.d.ts → multi-agent-coordinator-DoVSBFNx.d.ts} +20 -2
- package/dist/{null-fleet-bus-DKM3Iy9d.d.ts → null-fleet-bus-T-s3JaOt.d.ts} +413 -110
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-TcJfc29Y.d.ts → path-resolver-BfyK-I7w.d.ts} +2 -2
- package/dist/{permission-bPuzAy4x.d.ts → permission-B6sldrSp.d.ts} +1 -1
- package/dist/{permission-policy-BUQSutpl.d.ts → permission-policy-CtNscWOA.d.ts} +10 -9
- package/dist/{plan-templates-fkQTyz3U.d.ts → plan-templates-BWs6Je7j.d.ts} +8 -8
- package/dist/{provider-runner-BEpikbbN.d.ts → provider-runner-BmPe2RuU.d.ts} +3 -3
- package/dist/{retry-policy-BYkq0ugs.d.ts → retry-policy-KF18W4dg.d.ts} +1 -1
- package/dist/sdd/index.d.ts +9 -10
- package/dist/sdd/index.js +350 -122
- package/dist/sdd/index.js.map +1 -1
- package/dist/security/index.d.ts +65 -4
- package/dist/security/index.js +67 -19
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-pox8abg0.d.ts → selector-DmXxpFyM.d.ts} +1 -1
- package/dist/{session-reader-CSWcb5Ga.d.ts → session-reader-CCSBfQmd.d.ts} +2 -2
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +392 -8
- package/dist/storage/index.js +796 -40
- package/dist/storage/index.js.map +1 -1
- package/dist/{system-prompt-Bs-Wliab.d.ts → system-prompt-CM6zOhd2.d.ts} +19 -1
- package/dist/{tool-executor-Boo3dekH.d.ts → tool-executor-D7RxLtg4.d.ts} +9 -4
- package/dist/types/index.d.ts +16 -16
- package/dist/types/index.js +404 -14
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +40 -8
- package/dist/utils/index.js +127 -57
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-BCgmTNlG.d.ts → wstack-paths-eMXnY1_X.d.ts} +8 -0
- package/package.json +1 -1
- package/dist/compactor-DVTKL7XD.d.ts +0 -23
- package/dist/goal-store-C7jcumEh.d.ts +0 -96
- package/dist/models-registry-OG_30xqZ.d.ts +0 -46
package/dist/execution/index.js
CHANGED
|
@@ -39,6 +39,54 @@ function estimateToolResultTokens(content) {
|
|
|
39
39
|
function estimateTextTokens(text) {
|
|
40
40
|
return RoughTokenEstimate(text);
|
|
41
41
|
}
|
|
42
|
+
function estimateToolDefTokens(tool) {
|
|
43
|
+
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(tool.description ?? "") + RoughTokenEstimate(JSON.stringify(tool.inputSchema));
|
|
44
|
+
}
|
|
45
|
+
function estimateRequestTokens(messages, systemPrompt, tools) {
|
|
46
|
+
let messagesTokens = 0;
|
|
47
|
+
if (typeof messages === "string") {
|
|
48
|
+
messagesTokens = RoughTokenEstimate(messages);
|
|
49
|
+
} else if (Array.isArray(messages)) {
|
|
50
|
+
for (const m of messages) {
|
|
51
|
+
if (typeof m === "object" && m !== null && "content" in m) {
|
|
52
|
+
const content = m.content;
|
|
53
|
+
if (typeof content === "string") {
|
|
54
|
+
messagesTokens += RoughTokenEstimate(content);
|
|
55
|
+
} else if (Array.isArray(content)) {
|
|
56
|
+
for (const b of content) {
|
|
57
|
+
if (typeof b === "object" && b !== null) {
|
|
58
|
+
if (b.type === "text") {
|
|
59
|
+
messagesTokens += RoughTokenEstimate(b.text);
|
|
60
|
+
} else {
|
|
61
|
+
messagesTokens += RoughTokenEstimate(JSON.stringify(b));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
let systemTokens = 0;
|
|
70
|
+
if (typeof systemPrompt === "string") {
|
|
71
|
+
systemTokens = RoughTokenEstimate(systemPrompt);
|
|
72
|
+
} else if (Array.isArray(systemPrompt)) {
|
|
73
|
+
for (const b of systemPrompt) {
|
|
74
|
+
if (typeof b === "object" && b !== null && b.type === "text") {
|
|
75
|
+
systemTokens += RoughTokenEstimate(b.text);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
let toolsTokens = 0;
|
|
80
|
+
for (const t of tools) {
|
|
81
|
+
toolsTokens += estimateToolDefTokens(t);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
messages: messagesTokens,
|
|
85
|
+
systemPrompt: systemTokens,
|
|
86
|
+
tools: toolsTokens,
|
|
87
|
+
total: messagesTokens + systemTokens + toolsTokens
|
|
88
|
+
};
|
|
89
|
+
}
|
|
42
90
|
|
|
43
91
|
// src/utils/message-invariants.ts
|
|
44
92
|
function repairToolUseAdjacency(messages) {
|
|
@@ -144,6 +192,7 @@ var HybridCompactor = class {
|
|
|
144
192
|
}
|
|
145
193
|
async compact(ctx, opts = {}) {
|
|
146
194
|
const beforeTokens = this.estimateMessages(ctx.messages);
|
|
195
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
147
196
|
const reductions = [];
|
|
148
197
|
const policy = readContextWindowPolicy(ctx);
|
|
149
198
|
const preserveK = policy?.preserveK ?? this.preserveK;
|
|
@@ -159,9 +208,12 @@ var HybridCompactor = class {
|
|
|
159
208
|
ctx.state.replaceMessages(repaired.messages);
|
|
160
209
|
}
|
|
161
210
|
const afterTokens = this.estimateMessages(ctx.messages);
|
|
211
|
+
const afterFull = this.estimateFullRequest(ctx);
|
|
162
212
|
return {
|
|
163
213
|
before: beforeTokens,
|
|
164
214
|
after: afterTokens,
|
|
215
|
+
fullRequestTokensBefore: beforeFull,
|
|
216
|
+
fullRequestTokensAfter: afterFull,
|
|
165
217
|
reductions,
|
|
166
218
|
repaired: repaired.report.changed ? {
|
|
167
219
|
removedToolUses: repaired.report.removedToolUses,
|
|
@@ -170,6 +222,14 @@ var HybridCompactor = class {
|
|
|
170
222
|
} : void 0
|
|
171
223
|
};
|
|
172
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Estimate the full API request token count: messages + systemPrompt + toolDefs.
|
|
227
|
+
* This is the accurate figure for context-window pressure monitoring.
|
|
228
|
+
*/
|
|
229
|
+
estimateFullRequest(ctx) {
|
|
230
|
+
const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
|
|
231
|
+
return breakdown.total;
|
|
232
|
+
}
|
|
173
233
|
eliseOldToolResults(ctx, preserveK = this.preserveK, eliseThreshold = this.eliseThreshold) {
|
|
174
234
|
const messages = ctx.messages;
|
|
175
235
|
let pairCount = 0;
|
|
@@ -315,8 +375,9 @@ var IntelligentCompactor = class {
|
|
|
315
375
|
}
|
|
316
376
|
async compact(ctx, opts = {}) {
|
|
317
377
|
const beforeTokens = this.estimateTokens(ctx.messages);
|
|
378
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
318
379
|
const reductions = [];
|
|
319
|
-
const load =
|
|
380
|
+
const load = beforeFull / this.maxContext;
|
|
320
381
|
const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
|
|
321
382
|
const saved1 = this.eliseOldToolResults(ctx);
|
|
322
383
|
if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
|
|
@@ -330,9 +391,12 @@ var IntelligentCompactor = class {
|
|
|
330
391
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
331
392
|
if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
|
|
332
393
|
const afterTokens = this.estimateTokens(ctx.messages);
|
|
394
|
+
const afterFull = this.estimateFullRequest(ctx);
|
|
333
395
|
return {
|
|
334
396
|
before: beforeTokens,
|
|
335
397
|
after: afterTokens,
|
|
398
|
+
fullRequestTokensBefore: beforeFull,
|
|
399
|
+
fullRequestTokensAfter: afterFull,
|
|
336
400
|
reductions,
|
|
337
401
|
repaired: repaired.report.changed ? {
|
|
338
402
|
removedToolUses: repaired.report.removedToolUses,
|
|
@@ -341,6 +405,14 @@ var IntelligentCompactor = class {
|
|
|
341
405
|
} : void 0
|
|
342
406
|
};
|
|
343
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Estimate the full API request token count: messages + systemPrompt + toolDefs.
|
|
410
|
+
* This is the accurate figure for context-window pressure monitoring.
|
|
411
|
+
*/
|
|
412
|
+
estimateFullRequest(ctx) {
|
|
413
|
+
const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
|
|
414
|
+
return breakdown.total;
|
|
415
|
+
}
|
|
344
416
|
async summarizeAncientTurns(ctx) {
|
|
345
417
|
const messages = ctx.messages;
|
|
346
418
|
const cutoff = Math.max(0, messages.length - this.preserveK * 2);
|
|
@@ -353,29 +425,26 @@ var IntelligentCompactor = class {
|
|
|
353
425
|
try {
|
|
354
426
|
summaryText = await this.callSummarizer(toSummarize, ctx);
|
|
355
427
|
} catch {
|
|
356
|
-
const
|
|
357
|
-
const filePaths = /* @__PURE__ */ new Set();
|
|
358
|
-
let userTurns = 0;
|
|
359
|
-
let assistantTurns = 0;
|
|
428
|
+
const preservedMessages = [];
|
|
360
429
|
for (const m of toSummarize) {
|
|
361
|
-
if (m.role === "
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
430
|
+
if (m.role === "system") continue;
|
|
431
|
+
if (typeof m.content === "string") {
|
|
432
|
+
preservedMessages.push(m);
|
|
433
|
+
} else if (Array.isArray(m.content)) {
|
|
434
|
+
const textParts = m.content.filter(isTextBlock);
|
|
435
|
+
const toolParts = m.content.filter((b) => b.type === "tool_use" || b.type === "tool_result");
|
|
436
|
+
const next = {
|
|
437
|
+
role: m.role,
|
|
438
|
+
content: [
|
|
439
|
+
...textParts,
|
|
440
|
+
...toolParts.length > 0 ? [{ type: "text", text: `[${toolParts.length} tool call(s) omitted]` }] : []
|
|
441
|
+
]
|
|
442
|
+
};
|
|
443
|
+
preservedMessages.push(next);
|
|
369
444
|
}
|
|
370
|
-
const text = typeof m.content === "string" ? m.content : "";
|
|
371
|
-
const matches = text.matchAll(/(?:[\w.,\-/@]+\/)*[\w.,\-/@]+\.\w+/g);
|
|
372
|
-
for (const m_ of matches) filePaths.add(m_[0]);
|
|
373
445
|
}
|
|
374
|
-
|
|
375
|
-
if (
|
|
376
|
-
if (filePaths.size > 0) parts.push(`files: ${[...filePaths].slice(0, 10).join(", ")}`);
|
|
377
|
-
summaryText = parts.join(" | ");
|
|
378
|
-
if (!summaryText) summaryText = `${toSummarize.length} earlier turns omitted`;
|
|
446
|
+
summaryText = preservedMessages.map((m) => `[${m.role}]: ${typeof m.content === "string" ? m.content : m.content.filter(isTextBlock).map((b) => b.text).join(" ")}`).join("\n");
|
|
447
|
+
if (!summaryText) summaryText = `${toSummarize.length} earlier turns (semantic content preserved)`;
|
|
379
448
|
}
|
|
380
449
|
const summaryMsg = {
|
|
381
450
|
role: "system",
|
|
@@ -621,7 +690,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
621
690
|
const res = await this.provider.complete(req, { signal: ac.signal });
|
|
622
691
|
const textBlocks = res.content.filter(isTextBlock);
|
|
623
692
|
raw = textBlocks.map((b) => b.text).join("\n").trim();
|
|
624
|
-
} catch (
|
|
693
|
+
} catch (_err) {
|
|
625
694
|
return this.fallbackSelect(messages, effectiveBudget);
|
|
626
695
|
}
|
|
627
696
|
return this.parseSelectorOutput(raw, messages.length);
|
|
@@ -713,15 +782,24 @@ var SelectiveCompactor = class {
|
|
|
713
782
|
}
|
|
714
783
|
async compact(ctx, opts = {}) {
|
|
715
784
|
const beforeTokens = this.estimateTokens(ctx.messages);
|
|
785
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
716
786
|
const reductions = [];
|
|
717
|
-
const load =
|
|
787
|
+
const load = beforeFull / this.maxContext;
|
|
718
788
|
const shouldCompact = load >= this.warnThreshold || opts.aggressive;
|
|
719
789
|
if (!shouldCompact) {
|
|
720
790
|
const saved = this.eliseOldToolResults(ctx);
|
|
721
791
|
if (saved > 0) reductions.push({ phase: "elision", saved });
|
|
722
792
|
const repair2 = this.repairProtocolAdjacency(ctx);
|
|
723
793
|
const afterTokens2 = this.estimateTokens(ctx.messages);
|
|
724
|
-
|
|
794
|
+
const afterFull2 = this.estimateFullRequest(ctx);
|
|
795
|
+
return {
|
|
796
|
+
before: beforeTokens,
|
|
797
|
+
after: afterTokens2,
|
|
798
|
+
fullRequestTokensBefore: beforeFull,
|
|
799
|
+
fullRequestTokensAfter: afterFull2,
|
|
800
|
+
reductions,
|
|
801
|
+
repaired: repair2
|
|
802
|
+
};
|
|
725
803
|
}
|
|
726
804
|
const savedElision = this.eliseOldToolResults(ctx);
|
|
727
805
|
if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
|
|
@@ -733,7 +811,23 @@ var SelectiveCompactor = class {
|
|
|
733
811
|
}
|
|
734
812
|
const repair = this.repairProtocolAdjacency(ctx);
|
|
735
813
|
const afterTokens = this.estimateTokens(ctx.messages);
|
|
736
|
-
|
|
814
|
+
const afterFull = this.estimateFullRequest(ctx);
|
|
815
|
+
return {
|
|
816
|
+
before: beforeTokens,
|
|
817
|
+
after: afterTokens,
|
|
818
|
+
fullRequestTokensBefore: beforeFull,
|
|
819
|
+
fullRequestTokensAfter: afterFull,
|
|
820
|
+
reductions,
|
|
821
|
+
repaired: repair
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Estimate the full API request token count: messages + systemPrompt + toolDefs.
|
|
826
|
+
* This is the accurate figure for context-window pressure monitoring.
|
|
827
|
+
*/
|
|
828
|
+
estimateFullRequest(ctx) {
|
|
829
|
+
const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
|
|
830
|
+
return breakdown.total;
|
|
737
831
|
}
|
|
738
832
|
repairProtocolAdjacency(ctx) {
|
|
739
833
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
@@ -1008,7 +1102,8 @@ var LEVEL_RANK = { warn: 0, soft: 1, hard: 2 };
|
|
|
1008
1102
|
var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
1009
1103
|
name = "AutoCompaction";
|
|
1010
1104
|
compactor;
|
|
1011
|
-
|
|
1105
|
+
/** Deprecated. Kept for backward compat with tests that pass simpleEstimator. */
|
|
1106
|
+
_estimator;
|
|
1012
1107
|
warnThreshold;
|
|
1013
1108
|
softThreshold;
|
|
1014
1109
|
hardThreshold;
|
|
@@ -1018,19 +1113,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1018
1113
|
events;
|
|
1019
1114
|
failureMode;
|
|
1020
1115
|
policyProvider;
|
|
1021
|
-
|
|
1022
|
-
* Calibration factor applied to the estimator output. The estimator is now
|
|
1023
|
-
* expected to already include messages + system prompt + tool definitions
|
|
1024
|
-
* (see `estimateRequestTokens.total`), so no overhead boost is needed. Kept
|
|
1025
|
-
* as a constant so a future calibration against real provider tokenization
|
|
1026
|
-
* (BPE vs the chars/3.5 rough estimator) can dial this without touching the
|
|
1027
|
-
* threshold-check math.
|
|
1028
|
-
*
|
|
1029
|
-
* Historical note: was 1.3 when the estimator only counted messages — that
|
|
1030
|
-
* double-counted system+tools once the estimator was upgraded, firing
|
|
1031
|
-
* compaction ~30% earlier than intended (e.g. real 56% load showed as 73%).
|
|
1032
|
-
*/
|
|
1033
|
-
static OVERHEAD_FACTOR = 1;
|
|
1116
|
+
sessionBridge;
|
|
1034
1117
|
/**
|
|
1035
1118
|
* Once a compaction attempt reduces nothing (preserveK protects everything,
|
|
1036
1119
|
* no oversized tool_results remain to elide), retrying on every iteration
|
|
@@ -1043,8 +1126,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1043
1126
|
lastNoopAttempt = null;
|
|
1044
1127
|
/**
|
|
1045
1128
|
* @param compactor Compactor to use for compaction.
|
|
1046
|
-
* @param maxContext
|
|
1047
|
-
* @param
|
|
1129
|
+
* @param maxContext Provider's max context window in tokens.
|
|
1130
|
+
* @param _estimator Deprecated parameter kept for backward compatibility.
|
|
1131
|
+
* The middleware now uses `estimateRequestTokens` internally
|
|
1132
|
+
* for accurate full-request token counting (messages +
|
|
1133
|
+
* systemPrompt + toolDefs).
|
|
1048
1134
|
* @param thresholds Threshold fractions (0-1) of maxContext.
|
|
1049
1135
|
* @param opts Optional behavior. By default, failures at the
|
|
1050
1136
|
* hard threshold throw AGENT_CONTEXT_OVERFLOW so
|
|
@@ -1052,11 +1138,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1052
1138
|
* provider context overflow. Warn/soft failures
|
|
1053
1139
|
* still emit compaction.failed and continue.
|
|
1054
1140
|
*/
|
|
1055
|
-
constructor(compactor, maxContext,
|
|
1141
|
+
constructor(compactor, maxContext, _estimator, thresholds, optsOrAggressiveOn = {}, events) {
|
|
1056
1142
|
const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
|
|
1057
1143
|
this.compactor = compactor;
|
|
1058
1144
|
this._maxContext = maxContext;
|
|
1059
|
-
this.
|
|
1145
|
+
this._estimator = _estimator;
|
|
1060
1146
|
this.warnThreshold = thresholds.warn;
|
|
1061
1147
|
this.softThreshold = thresholds.soft;
|
|
1062
1148
|
this.hardThreshold = thresholds.hard;
|
|
@@ -1064,6 +1150,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1064
1150
|
this.events = opts.events;
|
|
1065
1151
|
this.failureMode = opts.failureMode ?? "throw_on_hard";
|
|
1066
1152
|
this.policyProvider = opts.policyProvider;
|
|
1153
|
+
this.sessionBridge = opts.sessionBridge;
|
|
1067
1154
|
}
|
|
1068
1155
|
/** Allow callers (e.g. model-switch in WebUI) to update the context window
|
|
1069
1156
|
* denominator when the active model changes. */
|
|
@@ -1072,8 +1159,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1072
1159
|
}
|
|
1073
1160
|
handler() {
|
|
1074
1161
|
return async (ctx, next) => {
|
|
1075
|
-
const
|
|
1076
|
-
const tokens = Math.ceil(rawTokens * _AutoCompactionMiddleware.OVERHEAD_FACTOR);
|
|
1162
|
+
const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
|
|
1077
1163
|
const load = tokens / this._maxContext;
|
|
1078
1164
|
const policy = this.policyProvider?.(ctx);
|
|
1079
1165
|
const thresholds = policy?.thresholds ?? {
|
|
@@ -1108,7 +1194,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1108
1194
|
return delta < _AutoCompactionMiddleware.NOOP_RETRY_DELTA_TOKENS;
|
|
1109
1195
|
}
|
|
1110
1196
|
recordAttempt(level, tokens, report) {
|
|
1111
|
-
const reduced = report.before > report.after;
|
|
1197
|
+
const reduced = (report.fullRequestTokensBefore ?? report.before) > (report.fullRequestTokensAfter ?? report.after);
|
|
1112
1198
|
const repaired = !!report.repaired;
|
|
1113
1199
|
if (reduced || repaired) {
|
|
1114
1200
|
this.lastNoopAttempt = null;
|
|
@@ -1128,6 +1214,15 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1128
1214
|
report,
|
|
1129
1215
|
aggressive
|
|
1130
1216
|
});
|
|
1217
|
+
await this.sessionBridge?.append({
|
|
1218
|
+
type: "compaction",
|
|
1219
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1220
|
+
before: report.before,
|
|
1221
|
+
after: report.after,
|
|
1222
|
+
level: pressure.level,
|
|
1223
|
+
aggressive,
|
|
1224
|
+
reductions: report.reductions?.map((r) => ({ phase: r.phase, saved: r.saved }))
|
|
1225
|
+
});
|
|
1131
1226
|
ctx.clearFileTracking();
|
|
1132
1227
|
} catch (err) {
|
|
1133
1228
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -1158,6 +1253,141 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1158
1253
|
}
|
|
1159
1254
|
};
|
|
1160
1255
|
|
|
1256
|
+
// src/security/capabilities.ts
|
|
1257
|
+
var ToolCapabilities = {
|
|
1258
|
+
/** Can execute arbitrary commands in the user's shell (the `bash` tool). */
|
|
1259
|
+
SHELL_ARBITRARY: "shell.arbitrary",
|
|
1260
|
+
/** Can write / modify / delete files inside the project. */
|
|
1261
|
+
FS_WRITE: "fs.write",
|
|
1262
|
+
/** Can write files outside the current project root (very high risk). */
|
|
1263
|
+
FS_WRITE_OUTSIDE_PROJECT: "fs.write.outside-project",
|
|
1264
|
+
/** Proxies tools from external MCP servers (unknown capability). */
|
|
1265
|
+
MCP_PROXY: "mcp.proxy",
|
|
1266
|
+
/** Can spawn or manage subagents / multi-agent tasks. */
|
|
1267
|
+
SUBAGENT_SPAWN: "subagent.spawn",
|
|
1268
|
+
/** Can mutate global or session configuration / trust state. */
|
|
1269
|
+
CONFIG_MUTATE: "config.mutate",
|
|
1270
|
+
/** Can install packages or run package managers with side effects. */
|
|
1271
|
+
PACKAGE_INSTALL: "package.install"
|
|
1272
|
+
};
|
|
1273
|
+
var DANGEROUS_FOR_SUBAGENTS = [
|
|
1274
|
+
ToolCapabilities.SHELL_ARBITRARY,
|
|
1275
|
+
ToolCapabilities.FS_WRITE,
|
|
1276
|
+
ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
|
|
1277
|
+
ToolCapabilities.MCP_PROXY,
|
|
1278
|
+
ToolCapabilities.SUBAGENT_SPAWN,
|
|
1279
|
+
ToolCapabilities.CONFIG_MUTATE,
|
|
1280
|
+
ToolCapabilities.PACKAGE_INSTALL
|
|
1281
|
+
];
|
|
1282
|
+
function hasDangerousCapabilityForSubagents(toolOrCaps) {
|
|
1283
|
+
if (!toolOrCaps) return false;
|
|
1284
|
+
const input = toolOrCaps;
|
|
1285
|
+
const caps = Array.isArray(input) ? input : input?.capabilities ?? [];
|
|
1286
|
+
return caps.some((c) => DANGEROUS_FOR_SUBAGENTS.includes(c));
|
|
1287
|
+
}
|
|
1288
|
+
function getDangerousCapabilities(toolOrCaps) {
|
|
1289
|
+
if (!toolOrCaps) return [];
|
|
1290
|
+
const input = toolOrCaps;
|
|
1291
|
+
const caps = Array.isArray(input) ? input : input?.capabilities ?? [];
|
|
1292
|
+
return caps.filter(
|
|
1293
|
+
(c) => DANGEROUS_FOR_SUBAGENTS.includes(c)
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// src/utils/json-schema-validate.ts
|
|
1298
|
+
function validateAgainstSchema(value, schema) {
|
|
1299
|
+
const errors = [];
|
|
1300
|
+
walk(value, schema, "", errors);
|
|
1301
|
+
return { ok: errors.length === 0, errors };
|
|
1302
|
+
}
|
|
1303
|
+
function walk(value, schema, path4, errors) {
|
|
1304
|
+
if (schema.enum !== void 0) {
|
|
1305
|
+
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
1306
|
+
errors.push({
|
|
1307
|
+
path: path4 || "<root>",
|
|
1308
|
+
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
1309
|
+
});
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
if (typeof schema.type === "string") {
|
|
1314
|
+
if (!checkType(value, schema.type)) {
|
|
1315
|
+
errors.push({
|
|
1316
|
+
path: path4 || "<root>",
|
|
1317
|
+
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
1318
|
+
});
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
if (schema.type === "object" && isPlainObject(value)) {
|
|
1323
|
+
const obj = value;
|
|
1324
|
+
for (const req of schema.required ?? []) {
|
|
1325
|
+
if (!(req in obj)) {
|
|
1326
|
+
errors.push({ path: joinPath(path4, req), message: "required property missing" });
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
if (schema.properties) {
|
|
1330
|
+
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
1331
|
+
if (key in obj) {
|
|
1332
|
+
walk(obj[key], subSchema, joinPath(path4, key), errors);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
1338
|
+
value.forEach((item, i) => walk(item, schema.items, `${path4}[${i}]`, errors));
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
function checkType(value, type) {
|
|
1342
|
+
switch (type) {
|
|
1343
|
+
case "string":
|
|
1344
|
+
return typeof value === "string";
|
|
1345
|
+
case "number":
|
|
1346
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
1347
|
+
case "integer":
|
|
1348
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
1349
|
+
case "boolean":
|
|
1350
|
+
return typeof value === "boolean";
|
|
1351
|
+
case "null":
|
|
1352
|
+
return value === null;
|
|
1353
|
+
case "array":
|
|
1354
|
+
return Array.isArray(value);
|
|
1355
|
+
case "object":
|
|
1356
|
+
return isPlainObject(value);
|
|
1357
|
+
default:
|
|
1358
|
+
return true;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
function isPlainObject(v) {
|
|
1362
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1363
|
+
}
|
|
1364
|
+
function describeType(v) {
|
|
1365
|
+
if (v === null) return "null";
|
|
1366
|
+
if (Array.isArray(v)) return "array";
|
|
1367
|
+
return typeof v;
|
|
1368
|
+
}
|
|
1369
|
+
function joinPath(parent, key) {
|
|
1370
|
+
if (!parent) return key;
|
|
1371
|
+
return `${parent}.${key}`;
|
|
1372
|
+
}
|
|
1373
|
+
function deepEqual(a, b) {
|
|
1374
|
+
if (a === b) return true;
|
|
1375
|
+
if (typeof a !== typeof b) return false;
|
|
1376
|
+
if (a === null || b === null) return a === b;
|
|
1377
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
1378
|
+
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
1379
|
+
}
|
|
1380
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
1381
|
+
const ak = Object.keys(a);
|
|
1382
|
+
const bk = Object.keys(b);
|
|
1383
|
+
if (ak.length !== bk.length) return false;
|
|
1384
|
+
return ak.every(
|
|
1385
|
+
(k) => deepEqual(a[k], b[k])
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1161
1391
|
// src/utils/tool-output-serializer.ts
|
|
1162
1392
|
function createToolOutputSerializer(opts = {}) {
|
|
1163
1393
|
const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
|
|
@@ -1239,18 +1469,45 @@ var ToolExecutor = class {
|
|
|
1239
1469
|
budget = this.decrementBudget(result, budget);
|
|
1240
1470
|
return { result, tool, durationMs: Date.now() - start };
|
|
1241
1471
|
}
|
|
1472
|
+
const validation = validateAgainstSchema(use.input, tool.inputSchema);
|
|
1473
|
+
if (!validation.ok) {
|
|
1474
|
+
const errorDetails = validation.errors.map((e) => ` - ${e.path || "input"}: ${e.message}`).join("\n");
|
|
1475
|
+
const result = {
|
|
1476
|
+
type: "tool_result",
|
|
1477
|
+
tool_use_id: use.id,
|
|
1478
|
+
content: `Invalid arguments for tool "${tool.name}".
|
|
1479
|
+
|
|
1480
|
+
Validation errors:
|
|
1481
|
+
${errorDetails}
|
|
1482
|
+
|
|
1483
|
+
Please call the tool again with arguments that match its inputSchema. You can use the "tool-help" tool with name="${tool.name}" to see the exact expected schema.`,
|
|
1484
|
+
is_error: true
|
|
1485
|
+
};
|
|
1486
|
+
budget = this.decrementBudget(result, budget);
|
|
1487
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
1488
|
+
}
|
|
1489
|
+
const toolDangerousCaps = getDangerousCapabilities(tool);
|
|
1490
|
+
if (toolDangerousCaps.length > 0) {
|
|
1491
|
+
if (this.opts.events) ;
|
|
1492
|
+
}
|
|
1242
1493
|
if (hasMalformedArguments(use.input)) {
|
|
1243
1494
|
const result = this.malformedInputResult(use, extractMalformedRaw(use.input));
|
|
1244
1495
|
budget = this.decrementBudget(result, budget);
|
|
1245
1496
|
return { result, tool, durationMs: Date.now() - start };
|
|
1246
1497
|
}
|
|
1247
1498
|
const decision = await this.opts.permissionPolicy.evaluate(tool, use.input, ctx);
|
|
1248
|
-
|
|
1499
|
+
let effectivePermission = decision.permission;
|
|
1500
|
+
const policy = this.opts.permissionPolicy;
|
|
1501
|
+
const yolo = policy.getYolo?.() === true || policy.getForceAllYolo?.() === true;
|
|
1502
|
+
if (toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo) {
|
|
1503
|
+
effectivePermission = "confirm";
|
|
1504
|
+
}
|
|
1505
|
+
if (effectivePermission === "deny") {
|
|
1249
1506
|
const result = this.deniedResult(use, decision.reason);
|
|
1250
1507
|
budget = this.decrementBudget(result, budget);
|
|
1251
1508
|
return { result, tool, durationMs: Date.now() - start };
|
|
1252
1509
|
}
|
|
1253
|
-
if (
|
|
1510
|
+
if (effectivePermission === "confirm") {
|
|
1254
1511
|
if (this.opts.confirmAwaiter) {
|
|
1255
1512
|
const choice = await this.opts.confirmAwaiter(tool, use.input, use.id, tool.name);
|
|
1256
1513
|
if (choice !== "yes" && choice !== "always") {
|
|
@@ -1275,10 +1532,13 @@ var ToolExecutor = class {
|
|
|
1275
1532
|
return { result: pending, tool, durationMs: Date.now() - start };
|
|
1276
1533
|
}
|
|
1277
1534
|
}
|
|
1535
|
+
const toolCapsForAudit = hasDangerousCapabilityForSubagents(tool) ? tool.capabilities ?? [] : [];
|
|
1278
1536
|
const span = this.opts.tracer?.startSpan(`tool.${tool.name}`, {
|
|
1279
1537
|
"tool.name": tool.name,
|
|
1280
1538
|
"tool.mutating": tool.mutating,
|
|
1281
|
-
"tool.permission": tool.permission
|
|
1539
|
+
"tool.permission": tool.permission,
|
|
1540
|
+
"tool.capabilities": JSON.stringify(tool.capabilities ?? []),
|
|
1541
|
+
"tool.has_dangerous_capabilities": toolCapsForAudit.length > 0
|
|
1282
1542
|
});
|
|
1283
1543
|
try {
|
|
1284
1544
|
const result = await this.executeTool(tool, use, ctx, budget);
|
|
@@ -2447,19 +2707,16 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
2447
2707
|
tokenOutput = 0;
|
|
2448
2708
|
costUsd = 0;
|
|
2449
2709
|
startTime = null;
|
|
2450
|
-
_onThreshold;
|
|
2451
2710
|
/**
|
|
2452
|
-
*
|
|
2453
|
-
*
|
|
2454
|
-
*
|
|
2455
|
-
*
|
|
2456
|
-
* the first one lands and patches limits), flooding the FleetBus
|
|
2457
|
-
* with redundant threshold events. Cleared in `negotiateExtension`'s
|
|
2458
|
-
* `finally`.
|
|
2711
|
+
* Timestamp of the most recent activity (iteration / tool call / token
|
|
2712
|
+
* usage / streamed progress). Drives the idle timeout — reset by
|
|
2713
|
+
* `markActivity()`. Initialised to `start()` time so a never-active agent
|
|
2714
|
+
* still eventually trips its idle window.
|
|
2459
2715
|
*/
|
|
2460
|
-
|
|
2716
|
+
lastActivityTime = null;
|
|
2717
|
+
_onThreshold;
|
|
2461
2718
|
/**
|
|
2462
|
-
* Hard cap on how long `
|
|
2719
|
+
* Hard cap on how long `_negotiateExtension` waits for the coordinator to
|
|
2463
2720
|
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
2464
2721
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
2465
2722
|
* leaves the budget over-limit and never enforces anything.
|
|
@@ -2500,6 +2757,24 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
2500
2757
|
}
|
|
2501
2758
|
start() {
|
|
2502
2759
|
this.startTime = Date.now();
|
|
2760
|
+
this.lastActivityTime = this.startTime;
|
|
2761
|
+
}
|
|
2762
|
+
/**
|
|
2763
|
+
* Reset the idle clock. Called on any sign of forward progress —
|
|
2764
|
+
* iterations, tool calls, token usage, and streamed tool/text progress —
|
|
2765
|
+
* so a long-but-productive subagent never trips its `idleTimeoutMs`.
|
|
2766
|
+
*/
|
|
2767
|
+
markActivity() {
|
|
2768
|
+
this.lastActivityTime = Date.now();
|
|
2769
|
+
}
|
|
2770
|
+
/**
|
|
2771
|
+
* Milliseconds since the last activity signal. Returns 0 before `start()`
|
|
2772
|
+
* (nothing to measure yet). Used by the coordinator watchdog to decide
|
|
2773
|
+
* whether to re-arm (still active) or reap (genuinely idle).
|
|
2774
|
+
*/
|
|
2775
|
+
idleMs() {
|
|
2776
|
+
const since = this.lastActivityTime ?? this.startTime;
|
|
2777
|
+
return since === null ? 0 : Date.now() - since;
|
|
2503
2778
|
}
|
|
2504
2779
|
/** Returns true if we're within 10% of any limit — useful for pre-flight checks. */
|
|
2505
2780
|
isNearLimit() {
|
|
@@ -2521,38 +2796,88 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
2521
2796
|
* - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
|
|
2522
2797
|
* - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
|
|
2523
2798
|
*/
|
|
2524
|
-
|
|
2799
|
+
/**
|
|
2800
|
+
* Collects all exceeded budget kinds into a single NOOP-free negotiation.
|
|
2801
|
+
* Called by recordIteration / recordToolCall / recordUsage — each may call
|
|
2802
|
+
* this for its own kind. The first call starts the negotiation and stores
|
|
2803
|
+
* the Promise in _pendingNegotiation. Subsequent calls for DIFFERENT
|
|
2804
|
+
* kinds (while a negotiation is in flight) are NOOPs — they don't start
|
|
2805
|
+
* new conversations with the coordinator. This prevents an EventBus flood
|
|
2806
|
+
* when multiple budget kinds are exceeded simultaneously in one iteration.
|
|
2807
|
+
*
|
|
2808
|
+
* Returns the kinds that were found to be exceeded (for logging/debugging).
|
|
2809
|
+
*/
|
|
2810
|
+
checkLimits(elapsedMs) {
|
|
2811
|
+
const exceeded = [];
|
|
2812
|
+
if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
|
|
2813
|
+
exceeded.push({ kind: "iterations", used: this.iterations, limit: this.limits.maxIterations });
|
|
2814
|
+
}
|
|
2815
|
+
if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
|
|
2816
|
+
exceeded.push({ kind: "tool_calls", used: this.toolCalls, limit: this.limits.maxToolCalls });
|
|
2817
|
+
}
|
|
2818
|
+
const totalTokens = this.tokenInput + this.tokenOutput;
|
|
2819
|
+
if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
|
|
2820
|
+
exceeded.push({ kind: "tokens", used: totalTokens, limit: this.limits.maxTokens });
|
|
2821
|
+
}
|
|
2822
|
+
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
2823
|
+
exceeded.push({ kind: "cost", used: this.costUsd, limit: this.limits.maxCostUsd });
|
|
2824
|
+
}
|
|
2825
|
+
if (elapsedMs !== void 0) {
|
|
2826
|
+
const idle = this.idleMs();
|
|
2827
|
+
if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
|
|
2828
|
+
exceeded.push({ kind: "timeout", used: idle, limit: this.limits.idleTimeoutMs });
|
|
2829
|
+
} else if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
|
|
2830
|
+
exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
if (exceeded.length === 0) return [];
|
|
2525
2834
|
if (!this._onThreshold) {
|
|
2526
|
-
|
|
2835
|
+
const first2 = exceeded[0];
|
|
2836
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
2527
2837
|
}
|
|
2528
2838
|
if (this._mode === "sync") {
|
|
2529
|
-
|
|
2839
|
+
const first2 = exceeded[0];
|
|
2840
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
2530
2841
|
}
|
|
2531
2842
|
const bus = this._events;
|
|
2532
2843
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
2533
|
-
|
|
2844
|
+
const first2 = exceeded[0];
|
|
2845
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
2846
|
+
}
|
|
2847
|
+
for (const entry of exceeded) {
|
|
2848
|
+
if (this._pendingNegotiations.has(entry.kind)) continue;
|
|
2849
|
+
const decision2 = this._negotiateExtension(entry.kind, exceeded);
|
|
2850
|
+
this._pendingNegotiations.set(entry.kind, decision2);
|
|
2534
2851
|
}
|
|
2535
|
-
|
|
2536
|
-
this.
|
|
2537
|
-
|
|
2538
|
-
throw new BudgetThresholdSignal(kind, limit, used, decision);
|
|
2852
|
+
const first = exceeded[0];
|
|
2853
|
+
const decision = this._pendingNegotiations.get(first.kind);
|
|
2854
|
+
throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
|
|
2539
2855
|
}
|
|
2856
|
+
/**
|
|
2857
|
+
* Per-kind in-flight negotiation Promises. Each budget kind can have its
|
|
2858
|
+
* own concurrent negotiation — e.g. iterations and timeout can both
|
|
2859
|
+
* be exceeded simultaneously without blocking each other. The same kind
|
|
2860
|
+
* cannot start two concurrent negotiations (dedup guard).
|
|
2861
|
+
* Cleared in `_negotiateExtension`'s `finally` block.
|
|
2862
|
+
*/
|
|
2863
|
+
_pendingNegotiations = /* @__PURE__ */ new Map();
|
|
2540
2864
|
/**
|
|
2541
2865
|
* Drive the threshold handler to a decision. Resolves with `'stop'`
|
|
2542
2866
|
* (signal the runner to abort) or `{ extend: ... }` (limits already
|
|
2543
|
-
* patched in-place; the runner should not abort).
|
|
2544
|
-
*
|
|
2867
|
+
* patched in-place; the runner should not abort). Clears the
|
|
2868
|
+
* per-kind slot in `_pendingNegotiations` in `finally`.
|
|
2545
2869
|
*
|
|
2546
2870
|
* The 'continue' return from a sync handler is treated as
|
|
2547
2871
|
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
2548
2872
|
* a fresh signal.
|
|
2549
2873
|
*/
|
|
2550
|
-
async
|
|
2874
|
+
async _negotiateExtension(kind, exceeded) {
|
|
2551
2875
|
try {
|
|
2876
|
+
const first = exceeded[0];
|
|
2552
2877
|
const result = this._onThreshold({
|
|
2553
|
-
kind,
|
|
2554
|
-
used,
|
|
2555
|
-
limit,
|
|
2878
|
+
kind: first.kind,
|
|
2879
|
+
used: first.used,
|
|
2880
|
+
limit: first.limit,
|
|
2556
2881
|
requestDecision: () => {
|
|
2557
2882
|
const bus = this._events;
|
|
2558
2883
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
@@ -2569,20 +2894,22 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
2569
2894
|
() => respond("stop"),
|
|
2570
2895
|
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
2571
2896
|
);
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2897
|
+
for (const { kind: kind2, used, limit } of exceeded) {
|
|
2898
|
+
bus.emit("budget.threshold_reached", {
|
|
2899
|
+
kind: kind2,
|
|
2900
|
+
used,
|
|
2901
|
+
limit,
|
|
2902
|
+
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
2903
|
+
extend: (extra) => {
|
|
2904
|
+
clearTimeout(fallback);
|
|
2905
|
+
respond({ extend: extra });
|
|
2906
|
+
},
|
|
2907
|
+
deny: () => {
|
|
2908
|
+
clearTimeout(fallback);
|
|
2909
|
+
respond("stop");
|
|
2910
|
+
}
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2586
2913
|
});
|
|
2587
2914
|
}
|
|
2588
2915
|
});
|
|
@@ -2606,54 +2933,59 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
2606
2933
|
if (ext.timeoutMs !== void 0) {
|
|
2607
2934
|
this.limits.timeoutMs = ext.timeoutMs;
|
|
2608
2935
|
}
|
|
2936
|
+
if (ext.idleTimeoutMs !== void 0) {
|
|
2937
|
+
this.limits.idleTimeoutMs = ext.idleTimeoutMs;
|
|
2938
|
+
}
|
|
2609
2939
|
return decision;
|
|
2610
2940
|
} finally {
|
|
2611
|
-
this.
|
|
2941
|
+
this._pendingNegotiations.delete(kind);
|
|
2612
2942
|
}
|
|
2613
2943
|
}
|
|
2614
2944
|
recordIteration() {
|
|
2615
2945
|
this.iterations++;
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
}
|
|
2946
|
+
this.markActivity();
|
|
2947
|
+
void this.checkLimits();
|
|
2619
2948
|
}
|
|
2620
2949
|
recordToolCall() {
|
|
2621
2950
|
this.toolCalls++;
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
}
|
|
2951
|
+
this.markActivity();
|
|
2952
|
+
void this.checkLimits();
|
|
2625
2953
|
}
|
|
2626
2954
|
recordUsage(usage, costUsd = 0) {
|
|
2627
2955
|
this.tokenInput += usage.input;
|
|
2628
2956
|
this.tokenOutput += usage.output;
|
|
2629
2957
|
this.costUsd += costUsd;
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
|
|
2633
|
-
}
|
|
2634
|
-
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
2635
|
-
void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
|
|
2636
|
-
}
|
|
2958
|
+
this.markActivity();
|
|
2959
|
+
void this.checkLimits();
|
|
2637
2960
|
}
|
|
2638
2961
|
/**
|
|
2639
|
-
* Wall-clock budget check. Unlike other limits, timeout
|
|
2640
|
-
*
|
|
2641
|
-
*
|
|
2642
|
-
*
|
|
2643
|
-
*
|
|
2644
|
-
*
|
|
2962
|
+
* Wall-clock budget check. Unlike other limits, timeout is always a hard stop
|
|
2963
|
+
* — wall-clock time cannot be "extended" by the coordinator, so it throws
|
|
2964
|
+
* synchronously rather than entering the negotiation flow.
|
|
2965
|
+
*
|
|
2966
|
+
* Decision table:
|
|
2967
|
+
* - no `onThreshold` handler → throw `BudgetExceededError`
|
|
2968
|
+
* - `mode === 'sync'` → throw `BudgetExceededError`
|
|
2969
|
+
* - `mode === 'auto'` + no listener → throw `BudgetExceededError`
|
|
2970
|
+
* - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
|
|
2645
2971
|
*/
|
|
2646
2972
|
checkTimeout() {
|
|
2647
|
-
if (this.startTime === null
|
|
2973
|
+
if (this.startTime === null) return;
|
|
2974
|
+
const { timeoutMs, idleTimeoutMs } = this.limits;
|
|
2975
|
+
if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
|
|
2648
2976
|
const elapsed = Date.now() - this.startTime;
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2977
|
+
const wallTripped = timeoutMs !== void 0 && elapsed > timeoutMs;
|
|
2978
|
+
const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
|
|
2979
|
+
if (!wallTripped && !idleTripped) return;
|
|
2980
|
+
void this.checkLimits(elapsed);
|
|
2652
2981
|
}
|
|
2653
|
-
/** Returns true if a timeout has occurred without throwing.
|
|
2982
|
+
/** Returns true if a wall-clock or idle timeout has occurred without throwing. */
|
|
2654
2983
|
isTimedOut() {
|
|
2655
|
-
if (this.startTime === null
|
|
2656
|
-
|
|
2984
|
+
if (this.startTime === null) return false;
|
|
2985
|
+
const { timeoutMs, idleTimeoutMs } = this.limits;
|
|
2986
|
+
if (timeoutMs !== void 0 && Date.now() - this.startTime > timeoutMs) return true;
|
|
2987
|
+
if (idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs) return true;
|
|
2988
|
+
return false;
|
|
2657
2989
|
}
|
|
2658
2990
|
usage() {
|
|
2659
2991
|
return {
|
|
@@ -2760,6 +3092,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
2760
3092
|
// Cheap: O(1) per progress event, and the budget short-circuits
|
|
2761
3093
|
// when timeoutMs is unset (most subagents have one set anyway).
|
|
2762
3094
|
events.on("tool.progress", () => {
|
|
3095
|
+
ctx.budget.markActivity();
|
|
2763
3096
|
try {
|
|
2764
3097
|
ctx.budget.checkTimeout();
|
|
2765
3098
|
} catch (e) {
|
|
@@ -2776,6 +3109,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
2776
3109
|
currentToolName = e.name;
|
|
2777
3110
|
}),
|
|
2778
3111
|
events.on("provider.text_delta", (e) => {
|
|
3112
|
+
ctx.budget.markActivity();
|
|
2779
3113
|
streamingTextAcc = (streamingTextAcc + e.text).slice(-200);
|
|
2780
3114
|
})
|
|
2781
3115
|
);
|
|
@@ -5169,6 +5503,7 @@ function providerStatusToCode(status, type) {
|
|
|
5169
5503
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
|
|
5170
5504
|
)
|
|
5171
5505
|
});
|
|
5506
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
5172
5507
|
var FLEET_ROSTER_BUDGETS = {
|
|
5173
5508
|
"audit-log": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 5e3, maxToolCalls: 15e3 },
|
|
5174
5509
|
"bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
@@ -5180,7 +5515,7 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
5180
5515
|
)
|
|
5181
5516
|
};
|
|
5182
5517
|
var GENERIC_SUBAGENT_BUDGET = {
|
|
5183
|
-
|
|
5518
|
+
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
5184
5519
|
maxIterations: 5e3,
|
|
5185
5520
|
maxToolCalls: 15e3
|
|
5186
5521
|
};
|
|
@@ -5190,7 +5525,13 @@ function applyRosterBudget(cfg) {
|
|
|
5190
5525
|
if (!defaultBudget) return cfg;
|
|
5191
5526
|
return {
|
|
5192
5527
|
...cfg,
|
|
5193
|
-
|
|
5528
|
+
// Wall-clock cap is opt-in only: forward an explicit `cfg.timeoutMs`, but
|
|
5529
|
+
// do NOT impose the roster's historical multi-hour wall-clock default — it
|
|
5530
|
+
// killed agents that were still actively working. Reaping is idle-based.
|
|
5531
|
+
timeoutMs: cfg.timeoutMs,
|
|
5532
|
+
// Idle window is the default reaper. Resets on activity, so a long-but-
|
|
5533
|
+
// productive run is never killed; only a genuine stall is reaped.
|
|
5534
|
+
idleTimeoutMs: cfg.idleTimeoutMs ?? defaultBudget.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS,
|
|
5194
5535
|
maxIterations: cfg.maxIterations ?? defaultBudget.maxIterations,
|
|
5195
5536
|
maxToolCalls: cfg.maxToolCalls ?? defaultBudget.maxToolCalls,
|
|
5196
5537
|
maxTokens: cfg.maxTokens ?? defaultBudget.maxTokens,
|
|
@@ -5258,6 +5599,107 @@ FLEET_ROSTER_BUDGETS["goose"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations:
|
|
|
5258
5599
|
...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
|
|
5259
5600
|
});
|
|
5260
5601
|
|
|
5602
|
+
// src/coordination/subagent-nicknames.ts
|
|
5603
|
+
var NICKNAME_POOL = {
|
|
5604
|
+
// Physics & fundamental sciences
|
|
5605
|
+
"einstein": { name: "Einstein", domain: "physics" },
|
|
5606
|
+
"newton": { name: "Newton", domain: "physics" },
|
|
5607
|
+
"feynman": { name: "Feynman", domain: "physics" },
|
|
5608
|
+
"dirac": { name: "Dirac", domain: "physics" },
|
|
5609
|
+
"bohr": { name: "Bohr", domain: "physics" },
|
|
5610
|
+
"planck": { name: "Planck", domain: "physics" },
|
|
5611
|
+
"curie": { name: "Curie", domain: "physics" },
|
|
5612
|
+
"fermi": { name: "Fermi", domain: "physics" },
|
|
5613
|
+
"heisenberg": { name: "Heisenberg", domain: "physics" },
|
|
5614
|
+
"schrodinger": { name: "Schr\xF6dinger", domain: "physics" },
|
|
5615
|
+
// Mathematics
|
|
5616
|
+
"euclid": { name: "Euclid", domain: "math" },
|
|
5617
|
+
"gauss": { name: "Gauss", domain: "math" },
|
|
5618
|
+
"turing": { name: "Turing", domain: "math" },
|
|
5619
|
+
"poincare": { name: "Poincar\xE9", domain: "math" },
|
|
5620
|
+
"riemann": { name: "Riemann", domain: "math" },
|
|
5621
|
+
"hilbert": { name: "Hilbert", domain: "math" },
|
|
5622
|
+
"pythagoras": { name: "Pythagoras", domain: "math" },
|
|
5623
|
+
// Computing & information theory
|
|
5624
|
+
"von-neumann": { name: "Von Neumann", domain: "computing" },
|
|
5625
|
+
"shannon": { name: "Shannon", domain: "computing" },
|
|
5626
|
+
"hopper": { name: "Hopper", domain: "computing" },
|
|
5627
|
+
"backus": { name: "Backus", domain: "computing" },
|
|
5628
|
+
"knuth": { name: "Knuth", domain: "computing" },
|
|
5629
|
+
"torvalds": { name: "Torvalds", domain: "computing" },
|
|
5630
|
+
"stallman": { name: "Stallman", domain: "computing" },
|
|
5631
|
+
"berners-lee": { name: "Berners-Lee", domain: "computing" },
|
|
5632
|
+
"babbage": { name: "Babbage", domain: "computing" },
|
|
5633
|
+
"lovelace": { name: "Lovelace", domain: "computing" },
|
|
5634
|
+
"klein": { name: "Klein", domain: "computing" },
|
|
5635
|
+
// Electronics & electrical engineering
|
|
5636
|
+
"edison": { name: "Edison", domain: "ee" },
|
|
5637
|
+
"tesla": { name: "Tesla", domain: "ee" },
|
|
5638
|
+
"faraday": { name: "Faraday", domain: "ee" },
|
|
5639
|
+
"maxwell": { name: "Maxwell", domain: "ee" },
|
|
5640
|
+
"ohm": { name: "Ohm", domain: "ee" },
|
|
5641
|
+
"bell": { name: "Bell", domain: "ee" },
|
|
5642
|
+
"marconi": { name: "Marconi", domain: "ee" },
|
|
5643
|
+
"lamarr": { name: "Lamarr", domain: "ee" },
|
|
5644
|
+
// General science / multi-disciplinary
|
|
5645
|
+
"darwin": { name: "Darwin", domain: "biology" },
|
|
5646
|
+
"mendel": { name: "Mendel", domain: "biology" },
|
|
5647
|
+
"pasteur": { name: "Pasteur", domain: "biology" },
|
|
5648
|
+
"hawking": { name: "Hawking", domain: "cosmology" },
|
|
5649
|
+
"sagan": { name: "Sagan", domain: "cosmology" },
|
|
5650
|
+
// Chemistry / materials
|
|
5651
|
+
"lavoisier": { name: "Lavoisier", domain: "chemistry" },
|
|
5652
|
+
"mendeleev": { name: "Mendeleev", domain: "chemistry" }
|
|
5653
|
+
};
|
|
5654
|
+
var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
|
|
5655
|
+
var DOMAIN_PREFERENCES = {
|
|
5656
|
+
"security": ["shannon", "turing", "lamarr", "stallman"],
|
|
5657
|
+
"bug-hunter": ["darwin", "curie", "feynman", "fermi"],
|
|
5658
|
+
"refactor": ["gauss", "hilbert", "euclid", "planck"],
|
|
5659
|
+
"audit-log": ["sagan", "hawking", "poincare", "newton"],
|
|
5660
|
+
"planner": ["hilbert", "gauss", "turing", "euclid"],
|
|
5661
|
+
"researcher": ["sagan", "hawking", "darwin", "pasteur"],
|
|
5662
|
+
"explorer": ["marconi", "bell", "columbus", "polo"],
|
|
5663
|
+
"testing": ["pasteur", "curie", "fermi", "bohr"],
|
|
5664
|
+
"frontend": ["lovelace", "hopper", "babbage", "backus"],
|
|
5665
|
+
"backend": ["torvalds", "stallman", "von-neumann", "backus"],
|
|
5666
|
+
"database": ["turing", "shannon", "backus", "knuth"],
|
|
5667
|
+
"devops": ["tesla", "edison", "faraday", "bell"],
|
|
5668
|
+
"security-scanner": ["shannon", "turing", "lamarr", "stallman"],
|
|
5669
|
+
"refactor-planner": ["gauss", "hilbert", "planck", "newton"],
|
|
5670
|
+
"architect": ["von-neumann", "turing", "gauss", "hilbert"],
|
|
5671
|
+
"critic": ["einstein", "feynman", "dirac", "bohr"],
|
|
5672
|
+
"e2e": ["hopper", "bell", "marconi", "tesla"],
|
|
5673
|
+
"performance": ["knuth", "gauss", "planck", "feynman"],
|
|
5674
|
+
"chaos": ["tesla", "edison", "curie", "fermi"],
|
|
5675
|
+
"cost": ["ohm", "bell", "marconi", "tesla"],
|
|
5676
|
+
// default fallback
|
|
5677
|
+
"default": ["einstein", "newton", "curie", "tesla", "edison", "turing", "shannon", "hopper", "knuth", "stallman"]
|
|
5678
|
+
};
|
|
5679
|
+
function assignNickname(role, used) {
|
|
5680
|
+
const preferences = [
|
|
5681
|
+
...DOMAIN_PREFERENCES[role] ?? [],
|
|
5682
|
+
...DOMAIN_PREFERENCES["default"] ?? []
|
|
5683
|
+
];
|
|
5684
|
+
for (const key of preferences) {
|
|
5685
|
+
const entry = NICKNAME_POOL[key];
|
|
5686
|
+
if (entry && !used.has(key)) {
|
|
5687
|
+
return `${entry.name} (${formatRole(role)})`;
|
|
5688
|
+
}
|
|
5689
|
+
}
|
|
5690
|
+
for (const entry of ALL_NICKNAMES) {
|
|
5691
|
+
const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
|
|
5692
|
+
if (key && !used.has(key)) {
|
|
5693
|
+
return `${entry.name} (${formatRole(role)})`;
|
|
5694
|
+
}
|
|
5695
|
+
}
|
|
5696
|
+
const counter = used.size + 1;
|
|
5697
|
+
return `Scientist #${counter} (${formatRole(role)})`;
|
|
5698
|
+
}
|
|
5699
|
+
function formatRole(role) {
|
|
5700
|
+
return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
5701
|
+
}
|
|
5702
|
+
|
|
5261
5703
|
// src/coordination/multi-agent-coordinator.ts
|
|
5262
5704
|
var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
5263
5705
|
coordinatorId;
|
|
@@ -5265,6 +5707,15 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
5265
5707
|
runner;
|
|
5266
5708
|
fleetBus;
|
|
5267
5709
|
subagents = /* @__PURE__ */ new Map();
|
|
5710
|
+
/**
|
|
5711
|
+
* Base nickname keys already handed out this run (e.g. `einstein`, `tesla`).
|
|
5712
|
+
* Prevents two workers sharing a name. Direct `coordinator.spawn()` callers
|
|
5713
|
+
* (parallel/eternal engine, SDD parallel run) don't go through
|
|
5714
|
+
* `Director.spawn()` where nicknames are normally assigned, so the
|
|
5715
|
+
* coordinator upgrades placeholder names ("Executor", "slot-ab12cd", role
|
|
5716
|
+
* names) to memorable ones here — that's what surfaces in the fleet monitor.
|
|
5717
|
+
*/
|
|
5718
|
+
usedNicknames = /* @__PURE__ */ new Set();
|
|
5268
5719
|
pendingTasks = [];
|
|
5269
5720
|
completedResults = [];
|
|
5270
5721
|
totalIterations = 0;
|
|
@@ -5313,7 +5764,26 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
5313
5764
|
this.config.maxConcurrent = Math.floor(n);
|
|
5314
5765
|
this.tryDispatchNext();
|
|
5315
5766
|
}
|
|
5767
|
+
/**
|
|
5768
|
+
* Upgrade a placeholder/role-derived name to a memorable scientist nickname
|
|
5769
|
+
* (e.g. "Einstein (Executor)"). A name is treated as a placeholder when it is
|
|
5770
|
+
* empty, equals the role (case-insensitive), is a generic default
|
|
5771
|
+
* ("subagent"/"adhoc"/"generic"), or is an auto-generated `slot-…` id.
|
|
5772
|
+
* Explicit, human-chosen names — including nicknames already assigned by
|
|
5773
|
+
* `Director.spawn()` — are left untouched, so this never double-assigns.
|
|
5774
|
+
*/
|
|
5775
|
+
withNickname(subagent) {
|
|
5776
|
+
const role = subagent.role ?? "subagent";
|
|
5777
|
+
const name = subagent.name?.trim() ?? "";
|
|
5778
|
+
const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
|
|
5779
|
+
if (!isPlaceholder) return subagent;
|
|
5780
|
+
const nickname = assignNickname(role, this.usedNicknames);
|
|
5781
|
+
const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
5782
|
+
this.usedNicknames.add(baseKey);
|
|
5783
|
+
return { ...subagent, name: nickname };
|
|
5784
|
+
}
|
|
5316
5785
|
async spawn(subagent) {
|
|
5786
|
+
subagent = this.withNickname(subagent);
|
|
5317
5787
|
const id = subagent.id || randomUUID();
|
|
5318
5788
|
if (this.subagents.has(id)) {
|
|
5319
5789
|
throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
|
|
@@ -5621,13 +6091,17 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
5621
6091
|
const rawMaxTokens = subagent.config.maxTokens;
|
|
5622
6092
|
const rawMaxCostUsd = subagent.config.maxCostUsd;
|
|
5623
6093
|
const rawTimeoutMs = subagent.config.timeoutMs;
|
|
6094
|
+
const rawIdleTimeoutMs = subagent.config.idleTimeoutMs;
|
|
5624
6095
|
const configWithRosterDefaults = applyRosterBudget(subagent.config);
|
|
5625
6096
|
const budget = new SubagentBudget({
|
|
5626
6097
|
maxIterations: rawMaxIterations ?? this.config.defaultBudget?.maxIterations ?? configWithRosterDefaults.maxIterations,
|
|
5627
6098
|
maxToolCalls: rawMaxToolCalls ?? this.config.defaultBudget?.maxToolCalls ?? configWithRosterDefaults.maxToolCalls,
|
|
5628
6099
|
maxTokens: rawMaxTokens ?? this.config.defaultBudget?.maxTokens ?? configWithRosterDefaults.maxTokens,
|
|
5629
6100
|
maxCostUsd: rawMaxCostUsd ?? this.config.defaultBudget?.maxCostUsd ?? configWithRosterDefaults.maxCostUsd,
|
|
5630
|
-
|
|
6101
|
+
// Wall-clock cap is opt-in (explicit config / defaultBudget only); the
|
|
6102
|
+
// roster no longer supplies one. Idle is the default reaper.
|
|
6103
|
+
timeoutMs: rawTimeoutMs ?? this.config.defaultBudget?.timeoutMs ?? configWithRosterDefaults.timeoutMs,
|
|
6104
|
+
idleTimeoutMs: rawIdleTimeoutMs ?? this.config.defaultBudget?.idleTimeoutMs ?? configWithRosterDefaults.idleTimeoutMs
|
|
5631
6105
|
});
|
|
5632
6106
|
subagent.activeBudget = budget;
|
|
5633
6107
|
if (!this.runner) {
|
|
@@ -5674,63 +6148,79 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
5674
6148
|
}
|
|
5675
6149
|
async executeWithTimeout(runner, task, ctx, budget) {
|
|
5676
6150
|
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
5677
|
-
|
|
6151
|
+
const idleLimitMs = budget.limits.idleTimeoutMs;
|
|
6152
|
+
if (initialTimeoutMs === void 0 && idleLimitMs === void 0) {
|
|
6153
|
+
return runner(task, ctx);
|
|
6154
|
+
}
|
|
5678
6155
|
const start = Date.now();
|
|
5679
6156
|
let timer = null;
|
|
5680
6157
|
const timeoutPromise = new Promise((_, reject) => {
|
|
5681
6158
|
const armFor = (ms) => {
|
|
5682
6159
|
if (timer) clearTimeout(timer);
|
|
5683
|
-
timer = setTimeout(
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
6160
|
+
timer = setTimeout(onTick, Math.max(0, ms));
|
|
6161
|
+
};
|
|
6162
|
+
const scheduleNext = () => {
|
|
6163
|
+
const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.timeoutMs ?? initialTimeoutMs) - (Date.now() - start);
|
|
6164
|
+
const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
6165
|
+
armFor(Math.max(25, Math.min(wallRemaining, idleRemaining)));
|
|
6166
|
+
};
|
|
6167
|
+
const onTick = async () => {
|
|
6168
|
+
const elapsed = Date.now() - start;
|
|
6169
|
+
const wallLimit = initialTimeoutMs === void 0 ? void 0 : budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
6170
|
+
const idleLimit = idleLimitMs === void 0 ? void 0 : budget.limits.idleTimeoutMs ?? idleLimitMs;
|
|
6171
|
+
const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
|
|
6172
|
+
const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
|
|
6173
|
+
if (idleExceeded && !wallExceeded) {
|
|
6174
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
6175
|
+
reject(new BudgetExceededError("timeout", idleLimit, budget.idleMs()));
|
|
6176
|
+
return;
|
|
6177
|
+
}
|
|
6178
|
+
if (!wallExceeded) {
|
|
6179
|
+
scheduleNext();
|
|
6180
|
+
return;
|
|
6181
|
+
}
|
|
6182
|
+
const limit = wallLimit;
|
|
6183
|
+
if (!budget.onThreshold) {
|
|
6184
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
6185
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
6186
|
+
return;
|
|
6187
|
+
}
|
|
6188
|
+
try {
|
|
6189
|
+
const result = budget.onThreshold({
|
|
6190
|
+
kind: "timeout",
|
|
6191
|
+
used: elapsed,
|
|
6192
|
+
limit,
|
|
6193
|
+
requestDecision: () => new Promise((resolveDecision) => {
|
|
6194
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
6195
|
+
kind: "timeout",
|
|
6196
|
+
used: elapsed,
|
|
6197
|
+
limit,
|
|
6198
|
+
timeoutMs: 6e4,
|
|
6199
|
+
extend: (extra) => resolveDecision({ extend: extra }),
|
|
6200
|
+
deny: () => resolveDecision("stop")
|
|
6201
|
+
});
|
|
6202
|
+
})
|
|
6203
|
+
});
|
|
6204
|
+
const decision = typeof result === "string" ? result : await result;
|
|
6205
|
+
if (decision === "continue" || decision === "throw" || decision === "stop") {
|
|
6206
|
+
armFor(Math.max(1e3, limit));
|
|
5689
6207
|
return;
|
|
5690
6208
|
}
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
limit,
|
|
5696
|
-
requestDecision: () => new Promise((resolveDecision) => {
|
|
5697
|
-
budget._events?.emit("budget.threshold_reached", {
|
|
5698
|
-
kind: "timeout",
|
|
5699
|
-
used: elapsed,
|
|
5700
|
-
limit,
|
|
5701
|
-
timeoutMs: 6e4,
|
|
5702
|
-
extend: (extra) => resolveDecision({ extend: extra }),
|
|
5703
|
-
deny: () => resolveDecision("stop")
|
|
5704
|
-
});
|
|
5705
|
-
})
|
|
5706
|
-
});
|
|
5707
|
-
const decision = typeof result === "string" ? result : await result;
|
|
5708
|
-
if (decision === "continue") {
|
|
5709
|
-
armFor(Math.max(1e3, limit));
|
|
5710
|
-
return;
|
|
5711
|
-
}
|
|
5712
|
-
if (decision === "throw" || decision === "stop") {
|
|
5713
|
-
armFor(Math.max(1e3, limit));
|
|
5714
|
-
return;
|
|
5715
|
-
}
|
|
5716
|
-
if (decision.extend.timeoutMs !== void 0) {
|
|
5717
|
-
budget.limits.timeoutMs = decision.extend.timeoutMs;
|
|
5718
|
-
const newLimit = decision.extend.timeoutMs;
|
|
5719
|
-
const remaining = Math.max(1e3, newLimit - elapsed);
|
|
5720
|
-
armFor(remaining);
|
|
5721
|
-
return;
|
|
5722
|
-
}
|
|
5723
|
-
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
5724
|
-
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
5725
|
-
} catch (err) {
|
|
5726
|
-
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
5727
|
-
reject(
|
|
5728
|
-
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
5729
|
-
);
|
|
6209
|
+
if (decision.extend.timeoutMs !== void 0) {
|
|
6210
|
+
budget.limits.timeoutMs = decision.extend.timeoutMs;
|
|
6211
|
+
scheduleNext();
|
|
6212
|
+
return;
|
|
5730
6213
|
}
|
|
5731
|
-
|
|
6214
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
6215
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
6216
|
+
} catch (err) {
|
|
6217
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
6218
|
+
reject(
|
|
6219
|
+
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
6220
|
+
);
|
|
6221
|
+
}
|
|
5732
6222
|
};
|
|
5733
|
-
|
|
6223
|
+
scheduleNext();
|
|
5734
6224
|
});
|
|
5735
6225
|
try {
|
|
5736
6226
|
return await Promise.race([runner(task, ctx), timeoutPromise]);
|
|
@@ -5907,12 +6397,12 @@ var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
|
|
|
5907
6397
|
var ParallelEternalEngine = class {
|
|
5908
6398
|
constructor(opts) {
|
|
5909
6399
|
this.opts = opts;
|
|
5910
|
-
this.goalPath = goalFilePath(opts.projectRoot);
|
|
6400
|
+
this.goalPath = opts.goalPath ?? goalFilePath(opts.projectRoot);
|
|
5911
6401
|
this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
|
|
5912
6402
|
this.timeoutMs = opts.iterationTimeoutMs ?? 3e5;
|
|
5913
6403
|
this.dispatchEnabled = opts.dispatch !== false;
|
|
5914
6404
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
5915
|
-
this.agentFactory = opts.subagentFactory ?? (async (
|
|
6405
|
+
this.agentFactory = opts.subagentFactory ?? (async (_config) => ({
|
|
5916
6406
|
agent: this.opts.agent,
|
|
5917
6407
|
events: this.opts.agent.events
|
|
5918
6408
|
}));
|
|
@@ -6006,7 +6496,6 @@ var ParallelEternalEngine = class {
|
|
|
6006
6496
|
}
|
|
6007
6497
|
const tasks = await this.decomposeGoal(goal);
|
|
6008
6498
|
if (!tasks || tasks.length === 0) {
|
|
6009
|
-
await sleep2(5e3);
|
|
6010
6499
|
return false;
|
|
6011
6500
|
}
|
|
6012
6501
|
const fanOut = await this.fanOut(goal, tasks);
|
|
@@ -6027,6 +6516,7 @@ var ParallelEternalEngine = class {
|
|
|
6027
6516
|
});
|
|
6028
6517
|
if (fanOut.goalComplete) {
|
|
6029
6518
|
this.stopRequested = true;
|
|
6519
|
+
this.state = "stopped";
|
|
6030
6520
|
return true;
|
|
6031
6521
|
}
|
|
6032
6522
|
await this.maybeCompact();
|
|
@@ -6207,7 +6697,11 @@ ${lastFew}` : "No prior iterations.",
|
|
|
6207
6697
|
} finally {
|
|
6208
6698
|
clearTimeout(timer);
|
|
6209
6699
|
}
|
|
6210
|
-
} catch {
|
|
6700
|
+
} catch (err) {
|
|
6701
|
+
this.opts.onError?.(
|
|
6702
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
6703
|
+
this.consecutiveFailures
|
|
6704
|
+
);
|
|
6211
6705
|
return [];
|
|
6212
6706
|
}
|
|
6213
6707
|
}
|