@wrongstack/core 0.9.19 → 0.9.20
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-DMVOX0cF.d.ts → agent-bridge-Dti3KXGk.d.ts} +1 -1
- package/dist/{agent-subagent-runner-C4qt9e5Y.d.ts → agent-subagent-runner-U-rs7kk7.d.ts} +3 -4
- package/dist/compactor-D7X96RLZ.d.ts +41 -0
- package/dist/{config-CWva0qoL.d.ts → config-CLXMDOSs.d.ts} +1 -1
- package/dist/{context-BRNbHmRM.d.ts → context-zkZeILpr.d.ts} +46 -0
- package/dist/coordination/index.d.ts +13 -13
- package/dist/coordination/index.js +660 -146
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +20 -20
- package/dist/defaults/index.js +918 -350
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-CiG9qUM_.d.ts → events-DH-9r-_C.d.ts} +42 -1
- package/dist/execution/index.d.ts +41 -30
- package/dist/execution/index.js +358 -112
- 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/{index-aizK8olO.d.ts → index-BIHJ4uII.d.ts} +11 -8
- package/dist/{index-p95HQ22A.d.ts → index-CFO9QmJo.d.ts} +8 -8
- package/dist/index.d.ts +311 -35
- package/dist/index.js +1933 -512
- 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.map +1 -1
- package/dist/{mcp-servers-BkVEqkRe.d.ts → mcp-servers-DkESgh0G.d.ts} +25 -3
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.js +1 -1
- package/dist/models/index.js.map +1 -1
- package/dist/{multi-agent-3ZnTB1aT.d.ts → multi-agent-DNp6lAzg.d.ts} +35 -23
- package/dist/{multi-agent-coordinator-bRaI_aD1.d.ts → multi-agent-coordinator-CAhsegPz.d.ts} +20 -2
- package/dist/{null-fleet-bus-DKM3Iy9d.d.ts → null-fleet-bus-Dnl19vmf.d.ts} +411 -110
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-TcJfc29Y.d.ts → path-resolver-CHiBL0DD.d.ts} +2 -2
- package/dist/{permission-bPuzAy4x.d.ts → permission-H35s9Evv.d.ts} +1 -1
- package/dist/{permission-policy-BUQSutpl.d.ts → permission-policy-CT-nRmTn.d.ts} +2 -2
- package/dist/{plan-templates-fkQTyz3U.d.ts → plan-templates-Bs8iRwi6.d.ts} +4 -4
- package/dist/{provider-runner-BEpikbbN.d.ts → provider-runner-BZdDrWrS.d.ts} +3 -3
- package/dist/{retry-policy-BYkq0ugs.d.ts → retry-policy-J9N_PM40.d.ts} +1 -1
- package/dist/sdd/index.d.ts +9 -10
- package/dist/sdd/index.js +224 -68
- package/dist/sdd/index.js.map +1 -1
- package/dist/security/index.d.ts +3 -3
- package/dist/{selector-pox8abg0.d.ts → selector-CFTh3Z6p.d.ts} +1 -1
- package/dist/{session-reader-CSWcb5Ga.d.ts → session-reader-C7JJlxOw.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 +390 -6
- package/dist/storage/index.js +672 -35
- package/dist/storage/index.js.map +1 -1
- package/dist/{system-prompt-Bs-Wliab.d.ts → system-prompt-CneIxVip.d.ts} +1 -1
- package/dist/{tool-executor-Boo3dekH.d.ts → tool-executor-flTuxsqO.d.ts} +9 -4
- package/dist/types/index.d.ts +14 -14
- package/dist/types/index.js +60 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +18 -6
- package/dist/utils/index.js +61 -56
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/compactor-DVTKL7XD.d.ts +0 -23
package/dist/defaults/index.js
CHANGED
|
@@ -640,7 +640,6 @@ var FileSessionWriter = class {
|
|
|
640
640
|
}
|
|
641
641
|
return event;
|
|
642
642
|
}
|
|
643
|
-
promptIndex = 0;
|
|
644
643
|
pendingFileSnapshots = [];
|
|
645
644
|
/** Tracks open tool_use IDs during the current run to serialize on close for resume. */
|
|
646
645
|
openToolUses = /* @__PURE__ */ new Set();
|
|
@@ -731,7 +730,6 @@ var FileSessionWriter = class {
|
|
|
731
730
|
await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);
|
|
732
731
|
this.pendingFileSnapshots = [];
|
|
733
732
|
}
|
|
734
|
-
this.promptIndex = promptIndex + 1;
|
|
735
733
|
await this.append({
|
|
736
734
|
type: "checkpoint",
|
|
737
735
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -827,6 +825,38 @@ var FileSessionWriter = class {
|
|
|
827
825
|
`;
|
|
828
826
|
await fsp.writeFile(this.filePath, record, "utf8");
|
|
829
827
|
}
|
|
828
|
+
/**
|
|
829
|
+
* Idea #1 — write an in-flight marker. The agent loop should call
|
|
830
|
+
* this at the start of each long-running operation; a matching
|
|
831
|
+
* `clearInFlightMarker` follows on clean exit. A stale marker
|
|
832
|
+
* (no end) is what `SessionRecovery.detectStale` looks for.
|
|
833
|
+
*/
|
|
834
|
+
async writeInFlightMarker(context) {
|
|
835
|
+
if (!context || context.length > 500) {
|
|
836
|
+
throw new Error("In-flight context must be 1..500 chars");
|
|
837
|
+
}
|
|
838
|
+
await this.append({
|
|
839
|
+
type: "in_flight_start",
|
|
840
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
841
|
+
context
|
|
842
|
+
});
|
|
843
|
+
this.events?.emit("in_flight.started", { context, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Idea #1 — close the in-flight marker. Idempotent in spirit
|
|
847
|
+
* (you can call it after a successful iteration even if you
|
|
848
|
+
* didn't open one this round) — but the session log records
|
|
849
|
+
* every call so postmortem tooling can see "the agent finished
|
|
850
|
+
* cleanly X times, then died without finishing Y".
|
|
851
|
+
*/
|
|
852
|
+
async clearInFlightMarker(reason) {
|
|
853
|
+
await this.append({
|
|
854
|
+
type: "in_flight_end",
|
|
855
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
856
|
+
reason
|
|
857
|
+
});
|
|
858
|
+
this.events?.emit("in_flight.ended", { reason, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
859
|
+
}
|
|
830
860
|
};
|
|
831
861
|
function userInputTitle(content) {
|
|
832
862
|
const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
@@ -3798,80 +3828,85 @@ function parseDescription(raw) {
|
|
|
3798
3828
|
|
|
3799
3829
|
// src/utils/json-repair.ts
|
|
3800
3830
|
function completePartialObject(s) {
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3831
|
+
if (!s.trim().startsWith("{")) return s;
|
|
3832
|
+
if (tryParse(s).ok) return s;
|
|
3833
|
+
const stack = [];
|
|
3834
|
+
let inString = false;
|
|
3835
|
+
let escaped = false;
|
|
3836
|
+
let sawKey = false;
|
|
3837
|
+
let prevSig = "";
|
|
3838
|
+
let contentEnd = 0;
|
|
3839
|
+
let stringBraceDepth = 0;
|
|
3840
|
+
for (let i = 0; i < s.length; i++) {
|
|
3841
|
+
const ch = s[i];
|
|
3842
|
+
if (inString) {
|
|
3843
|
+
contentEnd = i + 1;
|
|
3844
|
+
if (escaped) {
|
|
3845
|
+
escaped = false;
|
|
3812
3846
|
continue;
|
|
3813
3847
|
}
|
|
3814
3848
|
if (ch === "\\") {
|
|
3815
|
-
|
|
3849
|
+
escaped = true;
|
|
3816
3850
|
continue;
|
|
3817
3851
|
}
|
|
3818
3852
|
if (ch === '"') {
|
|
3819
|
-
|
|
3853
|
+
inString = false;
|
|
3854
|
+
prevSig = '"';
|
|
3855
|
+
stringBraceDepth = 0;
|
|
3820
3856
|
continue;
|
|
3821
3857
|
}
|
|
3822
|
-
if (
|
|
3823
|
-
if (ch === "
|
|
3824
|
-
braceDepth++;
|
|
3825
|
-
foundClose = false;
|
|
3826
|
-
} else if (ch === "}") {
|
|
3827
|
-
braceDepth--;
|
|
3828
|
-
if (braceDepth === 0) foundClose = true;
|
|
3829
|
-
}
|
|
3830
|
-
}
|
|
3831
|
-
if (foundClose || braceDepth <= 0) break;
|
|
3832
|
-
result += "}".repeat(braceDepth);
|
|
3833
|
-
}
|
|
3834
|
-
if (tryParse(result).ok) return result;
|
|
3835
|
-
let inString = false;
|
|
3836
|
-
let escaped = false;
|
|
3837
|
-
for (let i = result.length - 1; i >= 0; i--) {
|
|
3838
|
-
const ch = result[i];
|
|
3839
|
-
if (escaped) {
|
|
3840
|
-
escaped = false;
|
|
3841
|
-
continue;
|
|
3842
|
-
}
|
|
3843
|
-
if (ch === "\\") {
|
|
3844
|
-
escaped = true;
|
|
3858
|
+
if (ch === "{") stringBraceDepth++;
|
|
3859
|
+
else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
|
|
3845
3860
|
continue;
|
|
3846
3861
|
}
|
|
3862
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
|
|
3863
|
+
contentEnd = i + 1;
|
|
3847
3864
|
if (ch === '"') {
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
}
|
|
3865
|
+
inString = true;
|
|
3866
|
+
sawKey = true;
|
|
3867
|
+
stringBraceDepth = 0;
|
|
3868
|
+
prevSig = '"';
|
|
3869
|
+
} else if (ch === "{" || ch === "[") {
|
|
3870
|
+
stack.push(ch);
|
|
3871
|
+
prevSig = ch;
|
|
3872
|
+
} else if (ch === "}" || ch === "]") {
|
|
3873
|
+
stack.pop();
|
|
3874
|
+
prevSig = ch;
|
|
3875
|
+
} else {
|
|
3876
|
+
prevSig = ch;
|
|
3861
3877
|
}
|
|
3862
3878
|
}
|
|
3879
|
+
if (!sawKey && !inString) return s;
|
|
3880
|
+
let result = s.slice(0, contentEnd);
|
|
3863
3881
|
if (inString) {
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
if (ch === "{") depth++;
|
|
3869
|
-
else if (ch === "}") depth = Math.max(0, depth - 1);
|
|
3882
|
+
if (escaped) {
|
|
3883
|
+
result = result.slice(0, -1);
|
|
3884
|
+
} else if (endsWithInvalidEscape(result)) {
|
|
3885
|
+
result = result.slice(0, -2);
|
|
3870
3886
|
}
|
|
3871
|
-
result +=
|
|
3887
|
+
if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
|
|
3888
|
+
result += '"';
|
|
3889
|
+
} else if (prevSig === ":") {
|
|
3890
|
+
result += "null";
|
|
3891
|
+
}
|
|
3892
|
+
for (let k = stack.length - 1; k >= 0; k--) {
|
|
3893
|
+
result += stack[k] === "{" ? "}" : "]";
|
|
3894
|
+
}
|
|
3895
|
+
if (!tryParse(result).ok) {
|
|
3896
|
+
const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
|
|
3897
|
+
if (tryParse(patched).ok) result = patched;
|
|
3872
3898
|
}
|
|
3873
3899
|
return result;
|
|
3874
3900
|
}
|
|
3901
|
+
var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
|
|
3902
|
+
function endsWithInvalidEscape(str) {
|
|
3903
|
+
const last = str[str.length - 1];
|
|
3904
|
+
if (str[str.length - 2] !== "\\" || last === void 0) return false;
|
|
3905
|
+
if (VALID_ESCAPE.has(last)) return false;
|
|
3906
|
+
let backslashes = 0;
|
|
3907
|
+
for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
|
|
3908
|
+
return backslashes % 2 === 1;
|
|
3909
|
+
}
|
|
3875
3910
|
function tryParse(s) {
|
|
3876
3911
|
try {
|
|
3877
3912
|
return { ok: true, value: JSON.parse(s) };
|
|
@@ -4281,6 +4316,7 @@ var HybridCompactor = class {
|
|
|
4281
4316
|
}
|
|
4282
4317
|
async compact(ctx, opts = {}) {
|
|
4283
4318
|
const beforeTokens = this.estimateMessages(ctx.messages);
|
|
4319
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
4284
4320
|
const reductions = [];
|
|
4285
4321
|
const policy = readContextWindowPolicy(ctx);
|
|
4286
4322
|
const preserveK = policy?.preserveK ?? this.preserveK;
|
|
@@ -4296,9 +4332,12 @@ var HybridCompactor = class {
|
|
|
4296
4332
|
ctx.state.replaceMessages(repaired.messages);
|
|
4297
4333
|
}
|
|
4298
4334
|
const afterTokens = this.estimateMessages(ctx.messages);
|
|
4335
|
+
const afterFull = this.estimateFullRequest(ctx);
|
|
4299
4336
|
return {
|
|
4300
4337
|
before: beforeTokens,
|
|
4301
4338
|
after: afterTokens,
|
|
4339
|
+
fullRequestTokensBefore: beforeFull,
|
|
4340
|
+
fullRequestTokensAfter: afterFull,
|
|
4302
4341
|
reductions,
|
|
4303
4342
|
repaired: repaired.report.changed ? {
|
|
4304
4343
|
removedToolUses: repaired.report.removedToolUses,
|
|
@@ -4307,6 +4346,14 @@ var HybridCompactor = class {
|
|
|
4307
4346
|
} : void 0
|
|
4308
4347
|
};
|
|
4309
4348
|
}
|
|
4349
|
+
/**
|
|
4350
|
+
* Estimate the full API request token count: messages + systemPrompt + toolDefs.
|
|
4351
|
+
* This is the accurate figure for context-window pressure monitoring.
|
|
4352
|
+
*/
|
|
4353
|
+
estimateFullRequest(ctx) {
|
|
4354
|
+
const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
|
|
4355
|
+
return breakdown.total;
|
|
4356
|
+
}
|
|
4310
4357
|
eliseOldToolResults(ctx, preserveK = this.preserveK, eliseThreshold = this.eliseThreshold) {
|
|
4311
4358
|
const messages = ctx.messages;
|
|
4312
4359
|
let pairCount = 0;
|
|
@@ -4452,8 +4499,9 @@ var IntelligentCompactor = class {
|
|
|
4452
4499
|
}
|
|
4453
4500
|
async compact(ctx, opts = {}) {
|
|
4454
4501
|
const beforeTokens = this.estimateTokens(ctx.messages);
|
|
4502
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
4455
4503
|
const reductions = [];
|
|
4456
|
-
const load =
|
|
4504
|
+
const load = beforeFull / this.maxContext;
|
|
4457
4505
|
const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
|
|
4458
4506
|
const saved1 = this.eliseOldToolResults(ctx);
|
|
4459
4507
|
if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
|
|
@@ -4467,9 +4515,12 @@ var IntelligentCompactor = class {
|
|
|
4467
4515
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
4468
4516
|
if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
|
|
4469
4517
|
const afterTokens = this.estimateTokens(ctx.messages);
|
|
4518
|
+
const afterFull = this.estimateFullRequest(ctx);
|
|
4470
4519
|
return {
|
|
4471
4520
|
before: beforeTokens,
|
|
4472
4521
|
after: afterTokens,
|
|
4522
|
+
fullRequestTokensBefore: beforeFull,
|
|
4523
|
+
fullRequestTokensAfter: afterFull,
|
|
4473
4524
|
reductions,
|
|
4474
4525
|
repaired: repaired.report.changed ? {
|
|
4475
4526
|
removedToolUses: repaired.report.removedToolUses,
|
|
@@ -4478,6 +4529,14 @@ var IntelligentCompactor = class {
|
|
|
4478
4529
|
} : void 0
|
|
4479
4530
|
};
|
|
4480
4531
|
}
|
|
4532
|
+
/**
|
|
4533
|
+
* Estimate the full API request token count: messages + systemPrompt + toolDefs.
|
|
4534
|
+
* This is the accurate figure for context-window pressure monitoring.
|
|
4535
|
+
*/
|
|
4536
|
+
estimateFullRequest(ctx) {
|
|
4537
|
+
const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
|
|
4538
|
+
return breakdown.total;
|
|
4539
|
+
}
|
|
4481
4540
|
async summarizeAncientTurns(ctx) {
|
|
4482
4541
|
const messages = ctx.messages;
|
|
4483
4542
|
const cutoff = Math.max(0, messages.length - this.preserveK * 2);
|
|
@@ -4490,29 +4549,26 @@ var IntelligentCompactor = class {
|
|
|
4490
4549
|
try {
|
|
4491
4550
|
summaryText = await this.callSummarizer(toSummarize, ctx);
|
|
4492
4551
|
} catch {
|
|
4493
|
-
const
|
|
4494
|
-
const filePaths = /* @__PURE__ */ new Set();
|
|
4495
|
-
let userTurns = 0;
|
|
4496
|
-
let assistantTurns = 0;
|
|
4552
|
+
const preservedMessages = [];
|
|
4497
4553
|
for (const m of toSummarize) {
|
|
4498
|
-
if (m.role === "
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4554
|
+
if (m.role === "system") continue;
|
|
4555
|
+
if (typeof m.content === "string") {
|
|
4556
|
+
preservedMessages.push(m);
|
|
4557
|
+
} else if (Array.isArray(m.content)) {
|
|
4558
|
+
const textParts = m.content.filter(isTextBlock);
|
|
4559
|
+
const toolParts = m.content.filter((b) => b.type === "tool_use" || b.type === "tool_result");
|
|
4560
|
+
const next = {
|
|
4561
|
+
role: m.role,
|
|
4562
|
+
content: [
|
|
4563
|
+
...textParts,
|
|
4564
|
+
...toolParts.length > 0 ? [{ type: "text", text: `[${toolParts.length} tool call(s) omitted]` }] : []
|
|
4565
|
+
]
|
|
4566
|
+
};
|
|
4567
|
+
preservedMessages.push(next);
|
|
4506
4568
|
}
|
|
4507
|
-
const text = typeof m.content === "string" ? m.content : "";
|
|
4508
|
-
const matches = text.matchAll(/(?:[\w.,\-/@]+\/)*[\w.,\-/@]+\.\w+/g);
|
|
4509
|
-
for (const m_ of matches) filePaths.add(m_[0]);
|
|
4510
4569
|
}
|
|
4511
|
-
|
|
4512
|
-
if (
|
|
4513
|
-
if (filePaths.size > 0) parts.push(`files: ${[...filePaths].slice(0, 10).join(", ")}`);
|
|
4514
|
-
summaryText = parts.join(" | ");
|
|
4515
|
-
if (!summaryText) summaryText = `${toSummarize.length} earlier turns omitted`;
|
|
4570
|
+
summaryText = preservedMessages.map((m) => `[${m.role}]: ${typeof m.content === "string" ? m.content : m.content.filter(isTextBlock).map((b) => b.text).join(" ")}`).join("\n");
|
|
4571
|
+
if (!summaryText) summaryText = `${toSummarize.length} earlier turns (semantic content preserved)`;
|
|
4516
4572
|
}
|
|
4517
4573
|
const summaryMsg = {
|
|
4518
4574
|
role: "system",
|
|
@@ -4758,7 +4814,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
4758
4814
|
const res = await this.provider.complete(req, { signal: ac.signal });
|
|
4759
4815
|
const textBlocks = res.content.filter(isTextBlock);
|
|
4760
4816
|
raw = textBlocks.map((b) => b.text).join("\n").trim();
|
|
4761
|
-
} catch (
|
|
4817
|
+
} catch (_err) {
|
|
4762
4818
|
return this.fallbackSelect(messages, effectiveBudget);
|
|
4763
4819
|
}
|
|
4764
4820
|
return this.parseSelectorOutput(raw, messages.length);
|
|
@@ -4850,15 +4906,24 @@ var SelectiveCompactor = class {
|
|
|
4850
4906
|
}
|
|
4851
4907
|
async compact(ctx, opts = {}) {
|
|
4852
4908
|
const beforeTokens = this.estimateTokens(ctx.messages);
|
|
4909
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
4853
4910
|
const reductions = [];
|
|
4854
|
-
const load =
|
|
4911
|
+
const load = beforeFull / this.maxContext;
|
|
4855
4912
|
const shouldCompact = load >= this.warnThreshold || opts.aggressive;
|
|
4856
4913
|
if (!shouldCompact) {
|
|
4857
4914
|
const saved = this.eliseOldToolResults(ctx);
|
|
4858
4915
|
if (saved > 0) reductions.push({ phase: "elision", saved });
|
|
4859
4916
|
const repair2 = this.repairProtocolAdjacency(ctx);
|
|
4860
4917
|
const afterTokens2 = this.estimateTokens(ctx.messages);
|
|
4861
|
-
|
|
4918
|
+
const afterFull2 = this.estimateFullRequest(ctx);
|
|
4919
|
+
return {
|
|
4920
|
+
before: beforeTokens,
|
|
4921
|
+
after: afterTokens2,
|
|
4922
|
+
fullRequestTokensBefore: beforeFull,
|
|
4923
|
+
fullRequestTokensAfter: afterFull2,
|
|
4924
|
+
reductions,
|
|
4925
|
+
repaired: repair2
|
|
4926
|
+
};
|
|
4862
4927
|
}
|
|
4863
4928
|
const savedElision = this.eliseOldToolResults(ctx);
|
|
4864
4929
|
if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
|
|
@@ -4870,7 +4935,23 @@ var SelectiveCompactor = class {
|
|
|
4870
4935
|
}
|
|
4871
4936
|
const repair = this.repairProtocolAdjacency(ctx);
|
|
4872
4937
|
const afterTokens = this.estimateTokens(ctx.messages);
|
|
4873
|
-
|
|
4938
|
+
const afterFull = this.estimateFullRequest(ctx);
|
|
4939
|
+
return {
|
|
4940
|
+
before: beforeTokens,
|
|
4941
|
+
after: afterTokens,
|
|
4942
|
+
fullRequestTokensBefore: beforeFull,
|
|
4943
|
+
fullRequestTokensAfter: afterFull,
|
|
4944
|
+
reductions,
|
|
4945
|
+
repaired: repair
|
|
4946
|
+
};
|
|
4947
|
+
}
|
|
4948
|
+
/**
|
|
4949
|
+
* Estimate the full API request token count: messages + systemPrompt + toolDefs.
|
|
4950
|
+
* This is the accurate figure for context-window pressure monitoring.
|
|
4951
|
+
*/
|
|
4952
|
+
estimateFullRequest(ctx) {
|
|
4953
|
+
const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
|
|
4954
|
+
return breakdown.total;
|
|
4874
4955
|
}
|
|
4875
4956
|
repairProtocolAdjacency(ctx) {
|
|
4876
4957
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
@@ -5060,7 +5141,8 @@ var LEVEL_RANK2 = { warn: 0, soft: 1, hard: 2 };
|
|
|
5060
5141
|
var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
5061
5142
|
name = "AutoCompaction";
|
|
5062
5143
|
compactor;
|
|
5063
|
-
|
|
5144
|
+
/** Deprecated. Kept for backward compat with tests that pass simpleEstimator. */
|
|
5145
|
+
_estimator;
|
|
5064
5146
|
warnThreshold;
|
|
5065
5147
|
softThreshold;
|
|
5066
5148
|
hardThreshold;
|
|
@@ -5070,19 +5152,6 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
5070
5152
|
events;
|
|
5071
5153
|
failureMode;
|
|
5072
5154
|
policyProvider;
|
|
5073
|
-
/**
|
|
5074
|
-
* Calibration factor applied to the estimator output. The estimator is now
|
|
5075
|
-
* expected to already include messages + system prompt + tool definitions
|
|
5076
|
-
* (see `estimateRequestTokens.total`), so no overhead boost is needed. Kept
|
|
5077
|
-
* as a constant so a future calibration against real provider tokenization
|
|
5078
|
-
* (BPE vs the chars/3.5 rough estimator) can dial this without touching the
|
|
5079
|
-
* threshold-check math.
|
|
5080
|
-
*
|
|
5081
|
-
* Historical note: was 1.3 when the estimator only counted messages — that
|
|
5082
|
-
* double-counted system+tools once the estimator was upgraded, firing
|
|
5083
|
-
* compaction ~30% earlier than intended (e.g. real 56% load showed as 73%).
|
|
5084
|
-
*/
|
|
5085
|
-
static OVERHEAD_FACTOR = 1;
|
|
5086
5155
|
/**
|
|
5087
5156
|
* Once a compaction attempt reduces nothing (preserveK protects everything,
|
|
5088
5157
|
* no oversized tool_results remain to elide), retrying on every iteration
|
|
@@ -5095,8 +5164,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
5095
5164
|
lastNoopAttempt = null;
|
|
5096
5165
|
/**
|
|
5097
5166
|
* @param compactor Compactor to use for compaction.
|
|
5098
|
-
* @param maxContext
|
|
5099
|
-
* @param
|
|
5167
|
+
* @param maxContext Provider's max context window in tokens.
|
|
5168
|
+
* @param _estimator Deprecated parameter kept for backward compatibility.
|
|
5169
|
+
* The middleware now uses `estimateRequestTokens` internally
|
|
5170
|
+
* for accurate full-request token counting (messages +
|
|
5171
|
+
* systemPrompt + toolDefs).
|
|
5100
5172
|
* @param thresholds Threshold fractions (0-1) of maxContext.
|
|
5101
5173
|
* @param opts Optional behavior. By default, failures at the
|
|
5102
5174
|
* hard threshold throw AGENT_CONTEXT_OVERFLOW so
|
|
@@ -5104,11 +5176,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
5104
5176
|
* provider context overflow. Warn/soft failures
|
|
5105
5177
|
* still emit compaction.failed and continue.
|
|
5106
5178
|
*/
|
|
5107
|
-
constructor(compactor, maxContext,
|
|
5179
|
+
constructor(compactor, maxContext, _estimator, thresholds, optsOrAggressiveOn = {}, events) {
|
|
5108
5180
|
const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
|
|
5109
5181
|
this.compactor = compactor;
|
|
5110
5182
|
this._maxContext = maxContext;
|
|
5111
|
-
this.
|
|
5183
|
+
this._estimator = _estimator;
|
|
5112
5184
|
this.warnThreshold = thresholds.warn;
|
|
5113
5185
|
this.softThreshold = thresholds.soft;
|
|
5114
5186
|
this.hardThreshold = thresholds.hard;
|
|
@@ -5124,8 +5196,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
5124
5196
|
}
|
|
5125
5197
|
handler() {
|
|
5126
5198
|
return async (ctx, next) => {
|
|
5127
|
-
const
|
|
5128
|
-
const tokens = Math.ceil(rawTokens * _AutoCompactionMiddleware.OVERHEAD_FACTOR);
|
|
5199
|
+
const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
|
|
5129
5200
|
const load = tokens / this._maxContext;
|
|
5130
5201
|
const policy = this.policyProvider?.(ctx);
|
|
5131
5202
|
const thresholds = policy?.thresholds ?? {
|
|
@@ -5160,7 +5231,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
5160
5231
|
return delta < _AutoCompactionMiddleware.NOOP_RETRY_DELTA_TOKENS;
|
|
5161
5232
|
}
|
|
5162
5233
|
recordAttempt(level, tokens, report) {
|
|
5163
|
-
const reduced = report.before > report.after;
|
|
5234
|
+
const reduced = (report.fullRequestTokensBefore ?? report.before) > (report.fullRequestTokensAfter ?? report.after);
|
|
5164
5235
|
const repaired = !!report.repaired;
|
|
5165
5236
|
if (reduced || repaired) {
|
|
5166
5237
|
this.lastNoopAttempt = null;
|
|
@@ -6405,17 +6476,7 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
6405
6476
|
startTime = null;
|
|
6406
6477
|
_onThreshold;
|
|
6407
6478
|
/**
|
|
6408
|
-
*
|
|
6409
|
-
* in flight. While a kind is here, further `checkLimit` calls for the
|
|
6410
|
-
* same kind are no-ops — without this dedup, every `recordIteration`
|
|
6411
|
-
* after the limit is reached spawns a fresh decision Promise (until
|
|
6412
|
-
* the first one lands and patches limits), flooding the FleetBus
|
|
6413
|
-
* with redundant threshold events. Cleared in `negotiateExtension`'s
|
|
6414
|
-
* `finally`.
|
|
6415
|
-
*/
|
|
6416
|
-
pendingExtensions = /* @__PURE__ */ new Set();
|
|
6417
|
-
/**
|
|
6418
|
-
* Hard cap on how long `negotiateExtension` waits for the coordinator to
|
|
6479
|
+
* Hard cap on how long `_negotiateExtension` waits for the coordinator to
|
|
6419
6480
|
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
6420
6481
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
6421
6482
|
* leaves the budget over-limit and never enforces anything.
|
|
@@ -6477,38 +6538,83 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
6477
6538
|
* - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
|
|
6478
6539
|
* - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
|
|
6479
6540
|
*/
|
|
6480
|
-
|
|
6541
|
+
/**
|
|
6542
|
+
* Collects all exceeded budget kinds into a single NOOP-free negotiation.
|
|
6543
|
+
* Called by recordIteration / recordToolCall / recordUsage — each may call
|
|
6544
|
+
* this for its own kind. The first call starts the negotiation and stores
|
|
6545
|
+
* the Promise in _pendingNegotiation. Subsequent calls for DIFFERENT
|
|
6546
|
+
* kinds (while a negotiation is in flight) are NOOPs — they don't start
|
|
6547
|
+
* new conversations with the coordinator. This prevents an EventBus flood
|
|
6548
|
+
* when multiple budget kinds are exceeded simultaneously in one iteration.
|
|
6549
|
+
*
|
|
6550
|
+
* Returns the kinds that were found to be exceeded (for logging/debugging).
|
|
6551
|
+
*/
|
|
6552
|
+
checkLimits(elapsedMs) {
|
|
6553
|
+
const exceeded = [];
|
|
6554
|
+
if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
|
|
6555
|
+
exceeded.push({ kind: "iterations", used: this.iterations, limit: this.limits.maxIterations });
|
|
6556
|
+
}
|
|
6557
|
+
if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
|
|
6558
|
+
exceeded.push({ kind: "tool_calls", used: this.toolCalls, limit: this.limits.maxToolCalls });
|
|
6559
|
+
}
|
|
6560
|
+
const totalTokens = this.tokenInput + this.tokenOutput;
|
|
6561
|
+
if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
|
|
6562
|
+
exceeded.push({ kind: "tokens", used: totalTokens, limit: this.limits.maxTokens });
|
|
6563
|
+
}
|
|
6564
|
+
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
6565
|
+
exceeded.push({ kind: "cost", used: this.costUsd, limit: this.limits.maxCostUsd });
|
|
6566
|
+
}
|
|
6567
|
+
if (elapsedMs !== void 0 && this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
|
|
6568
|
+
exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
|
|
6569
|
+
}
|
|
6570
|
+
if (exceeded.length === 0) return [];
|
|
6481
6571
|
if (!this._onThreshold) {
|
|
6482
|
-
|
|
6572
|
+
const first2 = exceeded[0];
|
|
6573
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
6483
6574
|
}
|
|
6484
6575
|
if (this._mode === "sync") {
|
|
6485
|
-
|
|
6576
|
+
const first2 = exceeded[0];
|
|
6577
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
6486
6578
|
}
|
|
6487
6579
|
const bus = this._events;
|
|
6488
6580
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
6489
|
-
|
|
6581
|
+
const first2 = exceeded[0];
|
|
6582
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
6583
|
+
}
|
|
6584
|
+
for (const entry of exceeded) {
|
|
6585
|
+
if (this._pendingNegotiations.has(entry.kind)) continue;
|
|
6586
|
+
const decision2 = this._negotiateExtension(entry.kind, exceeded);
|
|
6587
|
+
this._pendingNegotiations.set(entry.kind, decision2);
|
|
6490
6588
|
}
|
|
6491
|
-
|
|
6492
|
-
this.
|
|
6493
|
-
|
|
6494
|
-
throw new BudgetThresholdSignal(kind, limit, used, decision);
|
|
6589
|
+
const first = exceeded[0];
|
|
6590
|
+
const decision = this._pendingNegotiations.get(first.kind);
|
|
6591
|
+
throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
|
|
6495
6592
|
}
|
|
6593
|
+
/**
|
|
6594
|
+
* Per-kind in-flight negotiation Promises. Each budget kind can have its
|
|
6595
|
+
* own concurrent negotiation — e.g. iterations and timeout can both
|
|
6596
|
+
* be exceeded simultaneously without blocking each other. The same kind
|
|
6597
|
+
* cannot start two concurrent negotiations (dedup guard).
|
|
6598
|
+
* Cleared in `_negotiateExtension`'s `finally` block.
|
|
6599
|
+
*/
|
|
6600
|
+
_pendingNegotiations = /* @__PURE__ */ new Map();
|
|
6496
6601
|
/**
|
|
6497
6602
|
* Drive the threshold handler to a decision. Resolves with `'stop'`
|
|
6498
6603
|
* (signal the runner to abort) or `{ extend: ... }` (limits already
|
|
6499
|
-
* patched in-place; the runner should not abort).
|
|
6500
|
-
*
|
|
6604
|
+
* patched in-place; the runner should not abort). Clears the
|
|
6605
|
+
* per-kind slot in `_pendingNegotiations` in `finally`.
|
|
6501
6606
|
*
|
|
6502
6607
|
* The 'continue' return from a sync handler is treated as
|
|
6503
6608
|
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
6504
6609
|
* a fresh signal.
|
|
6505
6610
|
*/
|
|
6506
|
-
async
|
|
6611
|
+
async _negotiateExtension(kind, exceeded) {
|
|
6507
6612
|
try {
|
|
6613
|
+
const first = exceeded[0];
|
|
6508
6614
|
const result = this._onThreshold({
|
|
6509
|
-
kind,
|
|
6510
|
-
used,
|
|
6511
|
-
limit,
|
|
6615
|
+
kind: first.kind,
|
|
6616
|
+
used: first.used,
|
|
6617
|
+
limit: first.limit,
|
|
6512
6618
|
requestDecision: () => {
|
|
6513
6619
|
const bus = this._events;
|
|
6514
6620
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
@@ -6525,20 +6631,22 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
6525
6631
|
() => respond("stop"),
|
|
6526
6632
|
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
6527
6633
|
);
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6634
|
+
for (const { kind: kind2, used, limit } of exceeded) {
|
|
6635
|
+
bus.emit("budget.threshold_reached", {
|
|
6636
|
+
kind: kind2,
|
|
6637
|
+
used,
|
|
6638
|
+
limit,
|
|
6639
|
+
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
6640
|
+
extend: (extra) => {
|
|
6641
|
+
clearTimeout(fallback);
|
|
6642
|
+
respond({ extend: extra });
|
|
6643
|
+
},
|
|
6644
|
+
deny: () => {
|
|
6645
|
+
clearTimeout(fallback);
|
|
6646
|
+
respond("stop");
|
|
6647
|
+
}
|
|
6648
|
+
});
|
|
6649
|
+
}
|
|
6542
6650
|
});
|
|
6543
6651
|
}
|
|
6544
6652
|
});
|
|
@@ -6564,47 +6672,39 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
6564
6672
|
}
|
|
6565
6673
|
return decision;
|
|
6566
6674
|
} finally {
|
|
6567
|
-
this.
|
|
6675
|
+
this._pendingNegotiations.delete(kind);
|
|
6568
6676
|
}
|
|
6569
6677
|
}
|
|
6570
6678
|
recordIteration() {
|
|
6571
6679
|
this.iterations++;
|
|
6572
|
-
|
|
6573
|
-
void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
|
|
6574
|
-
}
|
|
6680
|
+
void this.checkLimits();
|
|
6575
6681
|
}
|
|
6576
6682
|
recordToolCall() {
|
|
6577
6683
|
this.toolCalls++;
|
|
6578
|
-
|
|
6579
|
-
void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
|
|
6580
|
-
}
|
|
6684
|
+
void this.checkLimits();
|
|
6581
6685
|
}
|
|
6582
6686
|
recordUsage(usage, costUsd = 0) {
|
|
6583
6687
|
this.tokenInput += usage.input;
|
|
6584
6688
|
this.tokenOutput += usage.output;
|
|
6585
6689
|
this.costUsd += costUsd;
|
|
6586
|
-
|
|
6587
|
-
if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
|
|
6588
|
-
void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
|
|
6589
|
-
}
|
|
6590
|
-
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
6591
|
-
void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
|
|
6592
|
-
}
|
|
6690
|
+
void this.checkLimits();
|
|
6593
6691
|
}
|
|
6594
6692
|
/**
|
|
6595
|
-
* Wall-clock budget check. Unlike other limits, timeout
|
|
6596
|
-
*
|
|
6597
|
-
*
|
|
6598
|
-
*
|
|
6599
|
-
*
|
|
6600
|
-
*
|
|
6693
|
+
* Wall-clock budget check. Unlike other limits, timeout is always a hard stop
|
|
6694
|
+
* — wall-clock time cannot be "extended" by the coordinator, so it throws
|
|
6695
|
+
* synchronously rather than entering the negotiation flow.
|
|
6696
|
+
*
|
|
6697
|
+
* Decision table:
|
|
6698
|
+
* - no `onThreshold` handler → throw `BudgetExceededError`
|
|
6699
|
+
* - `mode === 'sync'` → throw `BudgetExceededError`
|
|
6700
|
+
* - `mode === 'auto'` + no listener → throw `BudgetExceededError`
|
|
6701
|
+
* - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
|
|
6601
6702
|
*/
|
|
6602
6703
|
checkTimeout() {
|
|
6603
6704
|
if (this.startTime === null || this.limits.timeoutMs === void 0) return;
|
|
6604
6705
|
const elapsed = Date.now() - this.startTime;
|
|
6605
|
-
if (elapsed
|
|
6606
|
-
|
|
6607
|
-
}
|
|
6706
|
+
if (elapsed <= this.limits.timeoutMs) return;
|
|
6707
|
+
void this.checkLimits(elapsed);
|
|
6608
6708
|
}
|
|
6609
6709
|
/** Returns true if a timeout has occurred without throwing. Useful for races. */
|
|
6610
6710
|
isTimedOut() {
|
|
@@ -9392,6 +9492,107 @@ FLEET_ROSTER_BUDGETS["goose"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations:
|
|
|
9392
9492
|
...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
|
|
9393
9493
|
});
|
|
9394
9494
|
|
|
9495
|
+
// src/coordination/subagent-nicknames.ts
|
|
9496
|
+
var NICKNAME_POOL = {
|
|
9497
|
+
// Physics & fundamental sciences
|
|
9498
|
+
"einstein": { name: "Einstein", domain: "physics" },
|
|
9499
|
+
"newton": { name: "Newton", domain: "physics" },
|
|
9500
|
+
"feynman": { name: "Feynman", domain: "physics" },
|
|
9501
|
+
"dirac": { name: "Dirac", domain: "physics" },
|
|
9502
|
+
"bohr": { name: "Bohr", domain: "physics" },
|
|
9503
|
+
"planck": { name: "Planck", domain: "physics" },
|
|
9504
|
+
"curie": { name: "Curie", domain: "physics" },
|
|
9505
|
+
"fermi": { name: "Fermi", domain: "physics" },
|
|
9506
|
+
"heisenberg": { name: "Heisenberg", domain: "physics" },
|
|
9507
|
+
"schrodinger": { name: "Schr\xF6dinger", domain: "physics" },
|
|
9508
|
+
// Mathematics
|
|
9509
|
+
"euclid": { name: "Euclid", domain: "math" },
|
|
9510
|
+
"gauss": { name: "Gauss", domain: "math" },
|
|
9511
|
+
"turing": { name: "Turing", domain: "math" },
|
|
9512
|
+
"poincare": { name: "Poincar\xE9", domain: "math" },
|
|
9513
|
+
"riemann": { name: "Riemann", domain: "math" },
|
|
9514
|
+
"hilbert": { name: "Hilbert", domain: "math" },
|
|
9515
|
+
"pythagoras": { name: "Pythagoras", domain: "math" },
|
|
9516
|
+
// Computing & information theory
|
|
9517
|
+
"von-neumann": { name: "Von Neumann", domain: "computing" },
|
|
9518
|
+
"shannon": { name: "Shannon", domain: "computing" },
|
|
9519
|
+
"hopper": { name: "Hopper", domain: "computing" },
|
|
9520
|
+
"backus": { name: "Backus", domain: "computing" },
|
|
9521
|
+
"knuth": { name: "Knuth", domain: "computing" },
|
|
9522
|
+
"torvalds": { name: "Torvalds", domain: "computing" },
|
|
9523
|
+
"stallman": { name: "Stallman", domain: "computing" },
|
|
9524
|
+
"berners-lee": { name: "Berners-Lee", domain: "computing" },
|
|
9525
|
+
"babbage": { name: "Babbage", domain: "computing" },
|
|
9526
|
+
"lovelace": { name: "Lovelace", domain: "computing" },
|
|
9527
|
+
"klein": { name: "Klein", domain: "computing" },
|
|
9528
|
+
// Electronics & electrical engineering
|
|
9529
|
+
"edison": { name: "Edison", domain: "ee" },
|
|
9530
|
+
"tesla": { name: "Tesla", domain: "ee" },
|
|
9531
|
+
"faraday": { name: "Faraday", domain: "ee" },
|
|
9532
|
+
"maxwell": { name: "Maxwell", domain: "ee" },
|
|
9533
|
+
"ohm": { name: "Ohm", domain: "ee" },
|
|
9534
|
+
"bell": { name: "Bell", domain: "ee" },
|
|
9535
|
+
"marconi": { name: "Marconi", domain: "ee" },
|
|
9536
|
+
"lamarr": { name: "Lamarr", domain: "ee" },
|
|
9537
|
+
// General science / multi-disciplinary
|
|
9538
|
+
"darwin": { name: "Darwin", domain: "biology" },
|
|
9539
|
+
"mendel": { name: "Mendel", domain: "biology" },
|
|
9540
|
+
"pasteur": { name: "Pasteur", domain: "biology" },
|
|
9541
|
+
"hawking": { name: "Hawking", domain: "cosmology" },
|
|
9542
|
+
"sagan": { name: "Sagan", domain: "cosmology" },
|
|
9543
|
+
// Chemistry / materials
|
|
9544
|
+
"lavoisier": { name: "Lavoisier", domain: "chemistry" },
|
|
9545
|
+
"mendeleev": { name: "Mendeleev", domain: "chemistry" }
|
|
9546
|
+
};
|
|
9547
|
+
var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
|
|
9548
|
+
var DOMAIN_PREFERENCES = {
|
|
9549
|
+
"security": ["shannon", "turing", "lamarr", "stallman"],
|
|
9550
|
+
"bug-hunter": ["darwin", "curie", "feynman", "fermi"],
|
|
9551
|
+
"refactor": ["gauss", "hilbert", "euclid", "planck"],
|
|
9552
|
+
"audit-log": ["sagan", "hawking", "poincare", "newton"],
|
|
9553
|
+
"planner": ["hilbert", "gauss", "turing", "euclid"],
|
|
9554
|
+
"researcher": ["sagan", "hawking", "darwin", "pasteur"],
|
|
9555
|
+
"explorer": ["marconi", "bell", "columbus", "polo"],
|
|
9556
|
+
"testing": ["pasteur", "curie", "fermi", "bohr"],
|
|
9557
|
+
"frontend": ["lovelace", "hopper", "babbage", "backus"],
|
|
9558
|
+
"backend": ["torvalds", "stallman", "von-neumann", "backus"],
|
|
9559
|
+
"database": ["turing", "shannon", "backus", "knuth"],
|
|
9560
|
+
"devops": ["tesla", "edison", "faraday", "bell"],
|
|
9561
|
+
"security-scanner": ["shannon", "turing", "lamarr", "stallman"],
|
|
9562
|
+
"refactor-planner": ["gauss", "hilbert", "planck", "newton"],
|
|
9563
|
+
"architect": ["von-neumann", "turing", "gauss", "hilbert"],
|
|
9564
|
+
"critic": ["einstein", "feynman", "dirac", "bohr"],
|
|
9565
|
+
"e2e": ["hopper", "bell", "marconi", "tesla"],
|
|
9566
|
+
"performance": ["knuth", "gauss", "planck", "feynman"],
|
|
9567
|
+
"chaos": ["tesla", "edison", "curie", "fermi"],
|
|
9568
|
+
"cost": ["ohm", "bell", "marconi", "tesla"],
|
|
9569
|
+
// default fallback
|
|
9570
|
+
"default": ["einstein", "newton", "curie", "tesla", "edison", "turing", "shannon", "hopper", "knuth", "stallman"]
|
|
9571
|
+
};
|
|
9572
|
+
function assignNickname(role, used) {
|
|
9573
|
+
const preferences = [
|
|
9574
|
+
...DOMAIN_PREFERENCES[role] ?? [],
|
|
9575
|
+
...DOMAIN_PREFERENCES["default"] ?? []
|
|
9576
|
+
];
|
|
9577
|
+
for (const key of preferences) {
|
|
9578
|
+
const entry = NICKNAME_POOL[key];
|
|
9579
|
+
if (entry && !used.has(key)) {
|
|
9580
|
+
return `${entry.name} (${formatRole(role)})`;
|
|
9581
|
+
}
|
|
9582
|
+
}
|
|
9583
|
+
for (const entry of ALL_NICKNAMES) {
|
|
9584
|
+
const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
|
|
9585
|
+
if (key && !used.has(key)) {
|
|
9586
|
+
return `${entry.name} (${formatRole(role)})`;
|
|
9587
|
+
}
|
|
9588
|
+
}
|
|
9589
|
+
const counter = used.size + 1;
|
|
9590
|
+
return `Scientist #${counter} (${formatRole(role)})`;
|
|
9591
|
+
}
|
|
9592
|
+
function formatRole(role) {
|
|
9593
|
+
return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
9594
|
+
}
|
|
9595
|
+
|
|
9395
9596
|
// src/coordination/multi-agent-coordinator.ts
|
|
9396
9597
|
var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
9397
9598
|
coordinatorId;
|
|
@@ -9399,6 +9600,15 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
9399
9600
|
runner;
|
|
9400
9601
|
fleetBus;
|
|
9401
9602
|
subagents = /* @__PURE__ */ new Map();
|
|
9603
|
+
/**
|
|
9604
|
+
* Base nickname keys already handed out this run (e.g. `einstein`, `tesla`).
|
|
9605
|
+
* Prevents two workers sharing a name. Direct `coordinator.spawn()` callers
|
|
9606
|
+
* (parallel/eternal engine, SDD parallel run) don't go through
|
|
9607
|
+
* `Director.spawn()` where nicknames are normally assigned, so the
|
|
9608
|
+
* coordinator upgrades placeholder names ("Executor", "slot-ab12cd", role
|
|
9609
|
+
* names) to memorable ones here — that's what surfaces in the fleet monitor.
|
|
9610
|
+
*/
|
|
9611
|
+
usedNicknames = /* @__PURE__ */ new Set();
|
|
9402
9612
|
pendingTasks = [];
|
|
9403
9613
|
completedResults = [];
|
|
9404
9614
|
totalIterations = 0;
|
|
@@ -9447,7 +9657,26 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
9447
9657
|
this.config.maxConcurrent = Math.floor(n);
|
|
9448
9658
|
this.tryDispatchNext();
|
|
9449
9659
|
}
|
|
9660
|
+
/**
|
|
9661
|
+
* Upgrade a placeholder/role-derived name to a memorable scientist nickname
|
|
9662
|
+
* (e.g. "Einstein (Executor)"). A name is treated as a placeholder when it is
|
|
9663
|
+
* empty, equals the role (case-insensitive), is a generic default
|
|
9664
|
+
* ("subagent"/"adhoc"/"generic"), or is an auto-generated `slot-…` id.
|
|
9665
|
+
* Explicit, human-chosen names — including nicknames already assigned by
|
|
9666
|
+
* `Director.spawn()` — are left untouched, so this never double-assigns.
|
|
9667
|
+
*/
|
|
9668
|
+
withNickname(subagent) {
|
|
9669
|
+
const role = subagent.role ?? "subagent";
|
|
9670
|
+
const name = subagent.name?.trim() ?? "";
|
|
9671
|
+
const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
|
|
9672
|
+
if (!isPlaceholder) return subagent;
|
|
9673
|
+
const nickname = assignNickname(role, this.usedNicknames);
|
|
9674
|
+
const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
9675
|
+
this.usedNicknames.add(baseKey);
|
|
9676
|
+
return { ...subagent, name: nickname };
|
|
9677
|
+
}
|
|
9450
9678
|
async spawn(subagent) {
|
|
9679
|
+
subagent = this.withNickname(subagent);
|
|
9451
9680
|
const id = subagent.id || randomUUID();
|
|
9452
9681
|
if (this.subagents.has(id)) {
|
|
9453
9682
|
throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
|
|
@@ -10041,12 +10270,12 @@ var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
|
|
|
10041
10270
|
var ParallelEternalEngine = class {
|
|
10042
10271
|
constructor(opts) {
|
|
10043
10272
|
this.opts = opts;
|
|
10044
|
-
this.goalPath = goalFilePath(opts.projectRoot);
|
|
10273
|
+
this.goalPath = opts.goalPath ?? goalFilePath(opts.projectRoot);
|
|
10045
10274
|
this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
|
|
10046
10275
|
this.timeoutMs = opts.iterationTimeoutMs ?? 3e5;
|
|
10047
10276
|
this.dispatchEnabled = opts.dispatch !== false;
|
|
10048
10277
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
10049
|
-
this.agentFactory = opts.subagentFactory ?? (async (
|
|
10278
|
+
this.agentFactory = opts.subagentFactory ?? (async (_config) => ({
|
|
10050
10279
|
agent: this.opts.agent,
|
|
10051
10280
|
events: this.opts.agent.events
|
|
10052
10281
|
}));
|
|
@@ -10140,7 +10369,6 @@ var ParallelEternalEngine = class {
|
|
|
10140
10369
|
}
|
|
10141
10370
|
const tasks = await this.decomposeGoal(goal);
|
|
10142
10371
|
if (!tasks || tasks.length === 0) {
|
|
10143
|
-
await sleep2(5e3);
|
|
10144
10372
|
return false;
|
|
10145
10373
|
}
|
|
10146
10374
|
const fanOut = await this.fanOut(goal, tasks);
|
|
@@ -10161,6 +10389,7 @@ var ParallelEternalEngine = class {
|
|
|
10161
10389
|
});
|
|
10162
10390
|
if (fanOut.goalComplete) {
|
|
10163
10391
|
this.stopRequested = true;
|
|
10392
|
+
this.state = "stopped";
|
|
10164
10393
|
return true;
|
|
10165
10394
|
}
|
|
10166
10395
|
await this.maybeCompact();
|
|
@@ -10341,7 +10570,11 @@ ${lastFew}` : "No prior iterations.",
|
|
|
10341
10570
|
} finally {
|
|
10342
10571
|
clearTimeout(timer);
|
|
10343
10572
|
}
|
|
10344
|
-
} catch {
|
|
10573
|
+
} catch (err) {
|
|
10574
|
+
this.opts.onError?.(
|
|
10575
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
10576
|
+
this.consecutiveFailures
|
|
10577
|
+
);
|
|
10345
10578
|
return [];
|
|
10346
10579
|
}
|
|
10347
10580
|
}
|
|
@@ -10702,7 +10935,11 @@ Working rules:
|
|
|
10702
10935
|
thrashing, terminate it rather than letting cost climb silently.
|
|
10703
10936
|
6. Never claim a subagent's work as your own without verifying it. If a
|
|
10704
10937
|
result looks wrong, ask_subagent for clarification before passing it
|
|
10705
|
-
to the user
|
|
10938
|
+
to the user.
|
|
10939
|
+
7. Wind down when satisfied. When the results are good enough, call
|
|
10940
|
+
work_complete \u2014 no new subagents will spawn and queued tasks complete
|
|
10941
|
+
as aborted. Running subagents finish naturally. Call terminate_subagent
|
|
10942
|
+
only for ones you need to stop immediately.`;
|
|
10706
10943
|
var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
|
|
10707
10944
|
a specific slice of a larger plan \u2014 do that slice well and report back.
|
|
10708
10945
|
|
|
@@ -10847,14 +11084,12 @@ var FleetBus = class {
|
|
|
10847
11084
|
};
|
|
10848
11085
|
var FleetUsageAggregator = class {
|
|
10849
11086
|
constructor(bus, priceLookup, metaLookup) {
|
|
10850
|
-
this.bus = bus;
|
|
10851
11087
|
this.priceLookup = priceLookup;
|
|
10852
11088
|
this.metaLookup = metaLookup;
|
|
10853
11089
|
bus.filter("provider.response", (e) => this.onProviderResponse(e));
|
|
10854
11090
|
bus.filter("tool.executed", (e) => this.onToolExecuted(e));
|
|
10855
11091
|
bus.filter("iteration.started", (e) => this.onIterationStarted(e));
|
|
10856
11092
|
}
|
|
10857
|
-
bus;
|
|
10858
11093
|
priceLookup;
|
|
10859
11094
|
metaLookup;
|
|
10860
11095
|
perSubagent = /* @__PURE__ */ new Map();
|
|
@@ -10922,106 +11157,6 @@ var FleetUsageAggregator = class {
|
|
|
10922
11157
|
snap.lastEventAt = e.ts;
|
|
10923
11158
|
}
|
|
10924
11159
|
};
|
|
10925
|
-
|
|
10926
|
-
// src/coordination/subagent-nicknames.ts
|
|
10927
|
-
var NICKNAME_POOL = {
|
|
10928
|
-
// Physics & fundamental sciences
|
|
10929
|
-
"einstein": { name: "Einstein", domain: "physics" },
|
|
10930
|
-
"newton": { name: "Newton", domain: "physics" },
|
|
10931
|
-
"feynman": { name: "Feynman", domain: "physics" },
|
|
10932
|
-
"dirac": { name: "Dirac", domain: "physics" },
|
|
10933
|
-
"bohr": { name: "Bohr", domain: "physics" },
|
|
10934
|
-
"planck": { name: "Planck", domain: "physics" },
|
|
10935
|
-
"curie": { name: "Curie", domain: "physics" },
|
|
10936
|
-
"fermi": { name: "Fermi", domain: "physics" },
|
|
10937
|
-
"heisenberg": { name: "Heisenberg", domain: "physics" },
|
|
10938
|
-
"schrodinger": { name: "Schr\xF6dinger", domain: "physics" },
|
|
10939
|
-
// Mathematics
|
|
10940
|
-
"euclid": { name: "Euclid", domain: "math" },
|
|
10941
|
-
"gauss": { name: "Gauss", domain: "math" },
|
|
10942
|
-
"turing": { name: "Turing", domain: "math" },
|
|
10943
|
-
"poincare": { name: "Poincar\xE9", domain: "math" },
|
|
10944
|
-
"riemann": { name: "Riemann", domain: "math" },
|
|
10945
|
-
"hilbert": { name: "Hilbert", domain: "math" },
|
|
10946
|
-
"pythagoras": { name: "Pythagoras", domain: "math" },
|
|
10947
|
-
// Computing & information theory
|
|
10948
|
-
"von-neumann": { name: "Von Neumann", domain: "computing" },
|
|
10949
|
-
"shannon": { name: "Shannon", domain: "computing" },
|
|
10950
|
-
"hopper": { name: "Hopper", domain: "computing" },
|
|
10951
|
-
"backus": { name: "Backus", domain: "computing" },
|
|
10952
|
-
"knuth": { name: "Knuth", domain: "computing" },
|
|
10953
|
-
"torvalds": { name: "Torvalds", domain: "computing" },
|
|
10954
|
-
"stallman": { name: "Stallman", domain: "computing" },
|
|
10955
|
-
"berners-lee": { name: "Berners-Lee", domain: "computing" },
|
|
10956
|
-
"babbage": { name: "Babbage", domain: "computing" },
|
|
10957
|
-
"lovelace": { name: "Lovelace", domain: "computing" },
|
|
10958
|
-
"klein": { name: "Klein", domain: "computing" },
|
|
10959
|
-
// Electronics & electrical engineering
|
|
10960
|
-
"edison": { name: "Edison", domain: "ee" },
|
|
10961
|
-
"tesla": { name: "Tesla", domain: "ee" },
|
|
10962
|
-
"faraday": { name: "Faraday", domain: "ee" },
|
|
10963
|
-
"maxwell": { name: "Maxwell", domain: "ee" },
|
|
10964
|
-
"ohm": { name: "Ohm", domain: "ee" },
|
|
10965
|
-
"bell": { name: "Bell", domain: "ee" },
|
|
10966
|
-
"marconi": { name: "Marconi", domain: "ee" },
|
|
10967
|
-
"lamarr": { name: "Lamarr", domain: "ee" },
|
|
10968
|
-
// General science / multi-disciplinary
|
|
10969
|
-
"darwin": { name: "Darwin", domain: "biology" },
|
|
10970
|
-
"mendel": { name: "Mendel", domain: "biology" },
|
|
10971
|
-
"pasteur": { name: "Pasteur", domain: "biology" },
|
|
10972
|
-
"hawking": { name: "Hawking", domain: "cosmology" },
|
|
10973
|
-
"sagan": { name: "Sagan", domain: "cosmology" },
|
|
10974
|
-
// Chemistry / materials
|
|
10975
|
-
"lavoisier": { name: "Lavoisier", domain: "chemistry" },
|
|
10976
|
-
"mendeleev": { name: "Mendeleev", domain: "chemistry" }
|
|
10977
|
-
};
|
|
10978
|
-
var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
|
|
10979
|
-
var DOMAIN_PREFERENCES = {
|
|
10980
|
-
"security": ["shannon", "turing", "lamarr", "stallman"],
|
|
10981
|
-
"bug-hunter": ["darwin", "curie", "feynman", "fermi"],
|
|
10982
|
-
"refactor": ["gauss", "hilbert", "euclid", "planck"],
|
|
10983
|
-
"audit-log": ["sagan", "hawking", "poincare", "newton"],
|
|
10984
|
-
"planner": ["hilbert", "gauss", "turing", "euclid"],
|
|
10985
|
-
"researcher": ["sagan", "hawking", "darwin", "pasteur"],
|
|
10986
|
-
"explorer": ["marconi", "bell", "columbus", "polo"],
|
|
10987
|
-
"testing": ["pasteur", "curie", "fermi", "bohr"],
|
|
10988
|
-
"frontend": ["lovelace", "hopper", "babbage", "backus"],
|
|
10989
|
-
"backend": ["torvalds", "stallman", "von-neumann", "backus"],
|
|
10990
|
-
"database": ["turing", "shannon", "backus", "knuth"],
|
|
10991
|
-
"devops": ["tesla", "edison", "faraday", "bell"],
|
|
10992
|
-
"security-scanner": ["shannon", "turing", "lamarr", "stallman"],
|
|
10993
|
-
"refactor-planner": ["gauss", "hilbert", "planck", "newton"],
|
|
10994
|
-
"architect": ["von-neumann", "turing", "gauss", "hilbert"],
|
|
10995
|
-
"critic": ["einstein", "feynman", "dirac", "bohr"],
|
|
10996
|
-
"e2e": ["hopper", "bell", "marconi", "tesla"],
|
|
10997
|
-
"performance": ["knuth", "gauss", "planck", "feynman"],
|
|
10998
|
-
"chaos": ["tesla", "edison", "curie", "fermi"],
|
|
10999
|
-
"cost": ["oh", "bell", "marconi", "tesla"],
|
|
11000
|
-
// default fallback
|
|
11001
|
-
"default": ["einstein", "newton", "curie", "tesla", "edison", "turing", "shannon", "hopper", "knuth", "stallman"]
|
|
11002
|
-
};
|
|
11003
|
-
function assignNickname(role, used) {
|
|
11004
|
-
const preferences = [
|
|
11005
|
-
...DOMAIN_PREFERENCES[role] ?? [],
|
|
11006
|
-
...DOMAIN_PREFERENCES["default"] ?? []
|
|
11007
|
-
];
|
|
11008
|
-
for (const key of preferences) {
|
|
11009
|
-
if (!used.has(key)) {
|
|
11010
|
-
return `${NICKNAME_POOL[key].name} (${formatRole(role)})`;
|
|
11011
|
-
}
|
|
11012
|
-
}
|
|
11013
|
-
for (const entry of ALL_NICKNAMES) {
|
|
11014
|
-
const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
|
|
11015
|
-
if (key && !used.has(key)) {
|
|
11016
|
-
return `${entry.name} (${formatRole(role)})`;
|
|
11017
|
-
}
|
|
11018
|
-
}
|
|
11019
|
-
const counter = used.size + 1;
|
|
11020
|
-
return `Scientist #${counter} (${formatRole(role)})`;
|
|
11021
|
-
}
|
|
11022
|
-
function formatRole(role) {
|
|
11023
|
-
return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
11024
|
-
}
|
|
11025
11160
|
function makeSpawnTool(director, roster) {
|
|
11026
11161
|
const inputSchema = {
|
|
11027
11162
|
type: "object",
|
|
@@ -11192,7 +11327,7 @@ function makeRollUpTool(director) {
|
|
|
11192
11327
|
function makeTerminateTool(director) {
|
|
11193
11328
|
return {
|
|
11194
11329
|
name: "terminate_subagent",
|
|
11195
|
-
description:
|
|
11330
|
+
description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
|
|
11196
11331
|
permission: "auto",
|
|
11197
11332
|
mutating: true,
|
|
11198
11333
|
inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
|
|
@@ -11203,6 +11338,19 @@ function makeTerminateTool(director) {
|
|
|
11203
11338
|
}
|
|
11204
11339
|
};
|
|
11205
11340
|
}
|
|
11341
|
+
function makeTerminateAllTool(director) {
|
|
11342
|
+
return {
|
|
11343
|
+
name: "terminate_all",
|
|
11344
|
+
description: 'Forcibly stop every subagent in the fleet and drain the pending task queue. In-flight tasks are terminated mid-execution; pending tasks receive "aborted_by_parent" completion immediately. Use this when the fleet is wedged, looping, or you need a clean slate. Compare: work_complete stops spawning but lets running agents finish naturally.',
|
|
11345
|
+
permission: "auto",
|
|
11346
|
+
mutating: true,
|
|
11347
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
11348
|
+
async execute() {
|
|
11349
|
+
await director.terminateAll();
|
|
11350
|
+
return { ok: true, message: `Fleet shutdown complete \u2014 all subagents stopped, pending tasks drained.` };
|
|
11351
|
+
}
|
|
11352
|
+
};
|
|
11353
|
+
}
|
|
11206
11354
|
function makeFleetStatusTool(director) {
|
|
11207
11355
|
return {
|
|
11208
11356
|
name: "fleet_status",
|
|
@@ -11345,7 +11493,7 @@ function makeCollabDebugTool(director) {
|
|
|
11345
11493
|
function makeFleetEmitTool(director) {
|
|
11346
11494
|
return {
|
|
11347
11495
|
name: "fleet_emit",
|
|
11348
|
-
description: "Emit a structured event on the FleetBus.
|
|
11496
|
+
description: "Emit a structured event on the FleetBus. Any subagent can emit any event type; the Director routes it to all listeners. Use it to stream findings, progress updates, or final results to other agents in real time.",
|
|
11349
11497
|
permission: "auto",
|
|
11350
11498
|
mutating: false,
|
|
11351
11499
|
inputSchema: {
|
|
@@ -11353,14 +11501,14 @@ function makeFleetEmitTool(director) {
|
|
|
11353
11501
|
properties: {
|
|
11354
11502
|
type: {
|
|
11355
11503
|
type: "string",
|
|
11356
|
-
description: "Event type
|
|
11504
|
+
description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
|
|
11357
11505
|
},
|
|
11358
11506
|
payload: {
|
|
11359
11507
|
type: "object",
|
|
11360
|
-
description: "Event payload
|
|
11508
|
+
description: "Event payload. Structure depends on event type. Use null if no payload."
|
|
11361
11509
|
}
|
|
11362
11510
|
},
|
|
11363
|
-
required: ["type"
|
|
11511
|
+
required: ["type"]
|
|
11364
11512
|
},
|
|
11365
11513
|
async execute(input) {
|
|
11366
11514
|
const i = input;
|
|
@@ -11368,12 +11516,25 @@ function makeFleetEmitTool(director) {
|
|
|
11368
11516
|
subagentId: director.id,
|
|
11369
11517
|
ts: Date.now(),
|
|
11370
11518
|
type: i.type,
|
|
11371
|
-
payload: i.payload
|
|
11519
|
+
payload: i.payload ?? {}
|
|
11372
11520
|
});
|
|
11373
11521
|
return { ok: true, event: i.type };
|
|
11374
11522
|
}
|
|
11375
11523
|
};
|
|
11376
11524
|
}
|
|
11525
|
+
function makeWorkCompleteTool(director) {
|
|
11526
|
+
return {
|
|
11527
|
+
name: "work_complete",
|
|
11528
|
+
description: "Signal that the director is satisfied with the results and the fleet should wind down. After calling this, spawn_subagent will refuse with a budget error and assign_task will instantly complete any queued tasks as aborted. Running subagents finish naturally. Call terminate_subagent separately to stop specific subagents immediately.",
|
|
11529
|
+
permission: "auto",
|
|
11530
|
+
mutating: false,
|
|
11531
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
11532
|
+
async execute() {
|
|
11533
|
+
director.workComplete();
|
|
11534
|
+
return { ok: true, message: "Fleet wind-down signaled. No new spawns or task dispatches." };
|
|
11535
|
+
}
|
|
11536
|
+
};
|
|
11537
|
+
}
|
|
11377
11538
|
var CollabSession = class extends EventEmitter {
|
|
11378
11539
|
sessionId;
|
|
11379
11540
|
options;
|
|
@@ -11388,6 +11549,14 @@ var CollabSession = class extends EventEmitter {
|
|
|
11388
11549
|
disposers = new Array();
|
|
11389
11550
|
settled = false;
|
|
11390
11551
|
timeoutMs;
|
|
11552
|
+
cancelled = false;
|
|
11553
|
+
alerts = [];
|
|
11554
|
+
/** Tracks tool call counts per subagent for progress-based timeout decisions. */
|
|
11555
|
+
progressBySubagent = /* @__PURE__ */ new Map();
|
|
11556
|
+
/** Last tool call count when a timeout warning was handled. */
|
|
11557
|
+
lastTimeoutProgress = /* @__PURE__ */ new Map();
|
|
11558
|
+
/** Session-level timeout timer handle (cleared on cancel or natural completion). */
|
|
11559
|
+
_timeoutTimer;
|
|
11391
11560
|
constructor(director, fleetBus, options) {
|
|
11392
11561
|
super();
|
|
11393
11562
|
this.sessionId = randomUUID();
|
|
@@ -11405,11 +11574,23 @@ var CollabSession = class extends EventEmitter {
|
|
|
11405
11574
|
};
|
|
11406
11575
|
}
|
|
11407
11576
|
}
|
|
11577
|
+
get id() {
|
|
11578
|
+
return this.sessionId;
|
|
11579
|
+
}
|
|
11580
|
+
getSessionAlerts() {
|
|
11581
|
+
return [...this.alerts];
|
|
11582
|
+
}
|
|
11583
|
+
isCancelled() {
|
|
11584
|
+
return this.cancelled;
|
|
11585
|
+
}
|
|
11408
11586
|
/**
|
|
11409
|
-
*
|
|
11410
|
-
*
|
|
11411
|
-
*
|
|
11587
|
+
* Snapshot of role → subagentId map. The Director calls coordinator.stop()
|
|
11588
|
+
* for each agent when cancelling the session, using this map to enumerate
|
|
11589
|
+
* all three collab agents.
|
|
11412
11590
|
*/
|
|
11591
|
+
getSubagentIds() {
|
|
11592
|
+
return new Map(this.subagentIds);
|
|
11593
|
+
}
|
|
11413
11594
|
async buildSnapshot() {
|
|
11414
11595
|
if (this.snapshot.files.length > 0) return this.snapshot;
|
|
11415
11596
|
for (const filePath of this.options.targetPaths) {
|
|
@@ -11425,9 +11606,30 @@ var CollabSession = class extends EventEmitter {
|
|
|
11425
11606
|
return this.snapshot;
|
|
11426
11607
|
}
|
|
11427
11608
|
/**
|
|
11428
|
-
*
|
|
11429
|
-
*
|
|
11609
|
+
* Cancel the session. Emits director.cancel_collab on the FleetBus so all
|
|
11610
|
+
* collab agents finish early. The session-level timeout timer is also cleared.
|
|
11611
|
+
* Safe to call multiple times (idempotent after first call).
|
|
11430
11612
|
*/
|
|
11613
|
+
cancel(reason = "Director cancelled collab session") {
|
|
11614
|
+
if (this.settled) return;
|
|
11615
|
+
this.cancelled = true;
|
|
11616
|
+
if (this._timeoutTimer) {
|
|
11617
|
+
clearTimeout(this._timeoutTimer);
|
|
11618
|
+
this._timeoutTimer = void 0;
|
|
11619
|
+
}
|
|
11620
|
+
this.fleetBus.emit({
|
|
11621
|
+
subagentId: this.director.id,
|
|
11622
|
+
ts: Date.now(),
|
|
11623
|
+
type: "director.cancel_collab",
|
|
11624
|
+
payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
11625
|
+
});
|
|
11626
|
+
this.fleetBus.emit({
|
|
11627
|
+
subagentId: this.director.id,
|
|
11628
|
+
ts: Date.now(),
|
|
11629
|
+
type: "collab.cancelled",
|
|
11630
|
+
payload: { sessionId: this.sessionId, reason }
|
|
11631
|
+
});
|
|
11632
|
+
}
|
|
11431
11633
|
async start() {
|
|
11432
11634
|
if (this.settled) throw new Error("session already settled");
|
|
11433
11635
|
this.settled = true;
|
|
@@ -11442,10 +11644,10 @@ var CollabSession = class extends EventEmitter {
|
|
|
11442
11644
|
this.subagentIds.set("refactor-planner", refactorPlannerId);
|
|
11443
11645
|
this.subagentIds.set("critic", criticId);
|
|
11444
11646
|
const timeout = new Promise((_, reject) => {
|
|
11445
|
-
setTimeout(
|
|
11446
|
-
(
|
|
11447
|
-
this.timeoutMs
|
|
11448
|
-
);
|
|
11647
|
+
this._timeoutTimer = setTimeout(() => {
|
|
11648
|
+
this.cancel("Session-level timeout reached");
|
|
11649
|
+
reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
|
|
11650
|
+
}, this.timeoutMs);
|
|
11449
11651
|
});
|
|
11450
11652
|
let results;
|
|
11451
11653
|
try {
|
|
@@ -11458,6 +11660,10 @@ var CollabSession = class extends EventEmitter {
|
|
|
11458
11660
|
timeout
|
|
11459
11661
|
]);
|
|
11460
11662
|
} catch (err) {
|
|
11663
|
+
if (this._timeoutTimer) {
|
|
11664
|
+
clearTimeout(this._timeoutTimer);
|
|
11665
|
+
this._timeoutTimer = void 0;
|
|
11666
|
+
}
|
|
11461
11667
|
this.cleanup();
|
|
11462
11668
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
11463
11669
|
this.emit("session.error", error);
|
|
@@ -11471,17 +11677,6 @@ var CollabSession = class extends EventEmitter {
|
|
|
11471
11677
|
this.emit("session.done", report);
|
|
11472
11678
|
return report;
|
|
11473
11679
|
}
|
|
11474
|
-
/**
|
|
11475
|
-
* Parse a completed agent's final text output and route the structured
|
|
11476
|
-
* findings onto the FleetBus. Agents are instructed to emit one JSON object
|
|
11477
|
-
* per finding / plan / evaluation; we scan the output for balanced JSON
|
|
11478
|
-
* objects (tolerating prose, markdown, and ```json fences around them) and
|
|
11479
|
-
* dispatch each by its discriminating key. The wired FleetBus listeners then
|
|
11480
|
-
* collect them into the bugs / plans / evaluations maps.
|
|
11481
|
-
*
|
|
11482
|
-
* Failed or empty results are ignored, and a single malformed object never
|
|
11483
|
-
* aborts the collation pass — see {@link extractJsonObjects}.
|
|
11484
|
-
*/
|
|
11485
11680
|
async parseAndEmit(result) {
|
|
11486
11681
|
if (result.status !== "success" || result.result == null) return;
|
|
11487
11682
|
const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
|
|
@@ -11497,12 +11692,6 @@ var CollabSession = class extends EventEmitter {
|
|
|
11497
11692
|
});
|
|
11498
11693
|
}
|
|
11499
11694
|
}
|
|
11500
|
-
/**
|
|
11501
|
-
* Extract every top-level JSON object embedded in free-form agent text.
|
|
11502
|
-
* Scans for balanced braces while respecting string literals and escape
|
|
11503
|
-
* sequences, so prose, markdown headers, and code fences around the JSON
|
|
11504
|
-
* are skipped. Malformed spans are dropped silently rather than thrown.
|
|
11505
|
-
*/
|
|
11506
11695
|
extractJsonObjects(text) {
|
|
11507
11696
|
const objects = [];
|
|
11508
11697
|
let depth = 0;
|
|
@@ -11539,25 +11728,33 @@ var CollabSession = class extends EventEmitter {
|
|
|
11539
11728
|
}
|
|
11540
11729
|
return objects;
|
|
11541
11730
|
}
|
|
11731
|
+
budgetForRole(role) {
|
|
11732
|
+
if (this.options.budgetOverrides?.[role]) {
|
|
11733
|
+
return this.options.budgetOverrides[role];
|
|
11734
|
+
}
|
|
11735
|
+
const defaults = {
|
|
11736
|
+
"bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
|
|
11737
|
+
"refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
|
|
11738
|
+
"critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
|
|
11739
|
+
};
|
|
11740
|
+
return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
|
|
11741
|
+
}
|
|
11542
11742
|
async spawnAgent(role, taskBrief) {
|
|
11743
|
+
const budget = this.budgetForRole(role);
|
|
11543
11744
|
const cfg = {
|
|
11544
11745
|
id: `${role}-${this.sessionId}`,
|
|
11545
11746
|
name: role,
|
|
11546
|
-
role
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11747
|
+
role,
|
|
11748
|
+
tools: ["fleet_emit", "fleet_status", "read", "grep", "glob", "bash", "write"],
|
|
11749
|
+
maxIterations: budget.maxIterations,
|
|
11750
|
+
maxToolCalls: budget.maxToolCalls,
|
|
11751
|
+
timeoutMs: budget.timeoutMs
|
|
11550
11752
|
};
|
|
11551
11753
|
const subagentId = await this.director.spawn(cfg);
|
|
11552
|
-
await this.director.assign({
|
|
11553
|
-
id: randomUUID(),
|
|
11554
|
-
subagentId,
|
|
11555
|
-
description: taskBrief
|
|
11556
|
-
});
|
|
11754
|
+
await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
|
|
11557
11755
|
return subagentId;
|
|
11558
11756
|
}
|
|
11559
11757
|
buildBugHunterTask() {
|
|
11560
|
-
this.options.targetPaths.join(", ");
|
|
11561
11758
|
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
11562
11759
|
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
11563
11760
|
${f.content}`).join("\n\n");
|
|
@@ -11566,13 +11763,17 @@ ${f.content}`).join("\n\n");
|
|
|
11566
11763
|
Target files:
|
|
11567
11764
|
${fileContents}
|
|
11568
11765
|
|
|
11569
|
-
For each bug found,
|
|
11570
|
-
{ "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } }
|
|
11571
|
-
|
|
11572
|
-
|
|
11766
|
+
For each bug found, emit it using the fleet_emit tool immediately:
|
|
11767
|
+
{ "type": "bug.found", "payload": { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } } }
|
|
11768
|
+
|
|
11769
|
+
After scanning all files, write your full markdown bug report to:
|
|
11770
|
+
${scratchpad}/bug-hunter-report-${this.sessionId}.md
|
|
11771
|
+
|
|
11772
|
+
Important: emit each finding as soon as you find it. Do not batch or wait until the end.`;
|
|
11573
11773
|
}
|
|
11574
11774
|
buildRefactorPlannerTask() {
|
|
11575
11775
|
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
11776
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
11576
11777
|
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
11577
11778
|
${f.content}`).join("\n\n");
|
|
11578
11779
|
return `You are RefactorPlanner. Plan refactorings for the following files.
|
|
@@ -11580,26 +11781,146 @@ ${f.content}`).join("\n\n");
|
|
|
11580
11781
|
Target files:
|
|
11581
11782
|
${fileContents}
|
|
11582
11783
|
|
|
11583
|
-
|
|
11584
|
-
|
|
11585
|
-
|
|
11586
|
-
|
|
11784
|
+
Read the BugHunter report at: ${bugHunterReportPath}
|
|
11785
|
+
|
|
11786
|
+
For each bug you can address, emit a refactor plan using fleet_emit:
|
|
11787
|
+
{ "type": "refactor.plan", "payload": { "plan": { "id": "<uuid>", "basedOnBugIds": ["<bug-id>"], "phases": [{ "number": 1, "title": "<phase>", "tasks": ["<task>"], "risk": "<low|medium|high>" }], "riskScore": "<low|medium|high>", "estimatedChangeCount": <n>, "rollbackStrategy": "<text>" } } }
|
|
11788
|
+
|
|
11789
|
+
Also write your full markdown plan to:
|
|
11790
|
+
${scratchpad}/refactor-plan-${this.sessionId}.md
|
|
11791
|
+
|
|
11792
|
+
Emit each plan immediately. Do not wait until planning is complete.`;
|
|
11587
11793
|
}
|
|
11588
11794
|
buildCriticTask() {
|
|
11589
11795
|
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
11796
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
11797
|
+
const refactorPlanPath = `${scratchpad}/refactor-plan-${this.sessionId}.md`;
|
|
11590
11798
|
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
11591
11799
|
${f.content}`).join("\n\n");
|
|
11592
|
-
return `You are Critic. Evaluate bug findings and refactor plans
|
|
11800
|
+
return `You are Critic. Evaluate bug findings and refactor plans.
|
|
11593
11801
|
|
|
11594
11802
|
Target files:
|
|
11595
11803
|
${fileContents}
|
|
11596
11804
|
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
11805
|
+
Read the BugHunter report at: ${bugHunterReportPath}
|
|
11806
|
+
Read the RefactorPlanner report at: ${refactorPlanPath}
|
|
11807
|
+
|
|
11808
|
+
For each bug and refactor plan, emit your evaluation using fleet_emit:
|
|
11809
|
+
{ "type": "critic.evaluation", "payload": { "evaluation": { "id": "<uuid>", "subjectType": "<bug_finding|refactor_plan>", "subjectId": "<id>", "score": <0-10>, "verdict": "<approve|needs_revision|reject>", "strengths": ["<strength>"], "weaknesses": ["<weakness>"], "concerns": [{ "description": "<concern>", "severity": "<blocking|advisory>" }] } } }
|
|
11810
|
+
|
|
11811
|
+
After all evaluations, write your markdown report to:
|
|
11812
|
+
${scratchpad}/critic-report-${this.sessionId}.md
|
|
11813
|
+
|
|
11814
|
+
Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
11601
11815
|
}
|
|
11602
11816
|
wireFleetBus() {
|
|
11817
|
+
const dTool = this.fleetBus.filter("tool.executed", (e) => {
|
|
11818
|
+
this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
|
|
11819
|
+
});
|
|
11820
|
+
this.disposers.push(dTool);
|
|
11821
|
+
const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
|
|
11822
|
+
const payload = e.payload;
|
|
11823
|
+
const role = this.roleFromSubagentId(e.subagentId);
|
|
11824
|
+
if (!role) return;
|
|
11825
|
+
const btwNotes = this.director.getLeaderBtwNotes();
|
|
11826
|
+
const alert = {
|
|
11827
|
+
sessionId: this.sessionId,
|
|
11828
|
+
subagentId: e.subagentId,
|
|
11829
|
+
role,
|
|
11830
|
+
level: "warning" /* WARNING */,
|
|
11831
|
+
message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
|
|
11832
|
+
budgetKind: payload.kind,
|
|
11833
|
+
elapsedMs: payload.timeoutMs,
|
|
11834
|
+
limit: payload.limit,
|
|
11835
|
+
btwNotes
|
|
11836
|
+
};
|
|
11837
|
+
this.alerts.push(alert);
|
|
11838
|
+
this.fleetBus.emit({
|
|
11839
|
+
subagentId: e.subagentId,
|
|
11840
|
+
ts: Date.now(),
|
|
11841
|
+
type: "collab.warning",
|
|
11842
|
+
payload: alert
|
|
11843
|
+
});
|
|
11844
|
+
const decision = this.options.onBudgetWarning?.(alert) ?? "ignore";
|
|
11845
|
+
if (decision === "cancel") {
|
|
11846
|
+
this.cancel(`Director cancelled: ${role} ${payload.kind} threshold`);
|
|
11847
|
+
return;
|
|
11848
|
+
}
|
|
11849
|
+
if (payload.kind === "timeout") {
|
|
11850
|
+
const progress = this.progressBySubagent.get(e.subagentId) ?? 0;
|
|
11851
|
+
const lastProgress = this.lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
11852
|
+
if (progress <= lastProgress) {
|
|
11853
|
+
payload.deny();
|
|
11854
|
+
return;
|
|
11855
|
+
}
|
|
11856
|
+
this.lastTimeoutProgress.set(e.subagentId, progress);
|
|
11857
|
+
const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
|
|
11858
|
+
setImmediate(() => {
|
|
11859
|
+
payload.extend({ timeoutMs: newLimit });
|
|
11860
|
+
});
|
|
11861
|
+
return;
|
|
11862
|
+
}
|
|
11863
|
+
if (decision === "extend") {
|
|
11864
|
+
setImmediate(() => {
|
|
11865
|
+
const base = Math.max(payload.limit, payload.used);
|
|
11866
|
+
const extra = {};
|
|
11867
|
+
switch (payload.kind) {
|
|
11868
|
+
case "iterations":
|
|
11869
|
+
extra.maxIterations = Math.min(Math.ceil(base * 1.5), 5e4);
|
|
11870
|
+
break;
|
|
11871
|
+
case "tool_calls":
|
|
11872
|
+
extra.maxToolCalls = Math.min(Math.ceil(base * 1.5), 1e5);
|
|
11873
|
+
break;
|
|
11874
|
+
case "tokens":
|
|
11875
|
+
extra.maxTokens = Math.min(Math.ceil(base * 1.5), 5e6);
|
|
11876
|
+
break;
|
|
11877
|
+
case "cost":
|
|
11878
|
+
extra.maxCostUsd = Math.min(base * 1.5, 100);
|
|
11879
|
+
break;
|
|
11880
|
+
}
|
|
11881
|
+
payload.extend(extra);
|
|
11882
|
+
});
|
|
11883
|
+
return;
|
|
11884
|
+
}
|
|
11885
|
+
if (payload.kind !== "timeout") {
|
|
11886
|
+
setImmediate(() => {
|
|
11887
|
+
const base = Math.max(payload.limit, payload.used);
|
|
11888
|
+
const extra = {};
|
|
11889
|
+
switch (payload.kind) {
|
|
11890
|
+
case "iterations":
|
|
11891
|
+
extra.maxIterations = Math.min(Math.ceil(base * 1.25), 5e4);
|
|
11892
|
+
break;
|
|
11893
|
+
case "tool_calls":
|
|
11894
|
+
extra.maxToolCalls = Math.min(Math.ceil(base * 1.25), 1e5);
|
|
11895
|
+
break;
|
|
11896
|
+
case "tokens":
|
|
11897
|
+
extra.maxTokens = Math.min(Math.ceil(base * 1.25), 5e6);
|
|
11898
|
+
break;
|
|
11899
|
+
case "cost":
|
|
11900
|
+
extra.maxCostUsd = Math.min(base * 1.25, 100);
|
|
11901
|
+
break;
|
|
11902
|
+
}
|
|
11903
|
+
payload.extend(extra);
|
|
11904
|
+
});
|
|
11905
|
+
}
|
|
11906
|
+
});
|
|
11907
|
+
this.disposers.push(dBudget);
|
|
11908
|
+
const dCancel = this.fleetBus.filter("director.cancel_collab", (e) => {
|
|
11909
|
+
const payload = e.payload;
|
|
11910
|
+
if (payload.sessionId !== this.sessionId) return;
|
|
11911
|
+
this.cancelled = true;
|
|
11912
|
+
if (this._timeoutTimer) {
|
|
11913
|
+
clearTimeout(this._timeoutTimer);
|
|
11914
|
+
this._timeoutTimer = void 0;
|
|
11915
|
+
}
|
|
11916
|
+
this.fleetBus.emit({
|
|
11917
|
+
subagentId: this.director.id,
|
|
11918
|
+
ts: Date.now(),
|
|
11919
|
+
type: "collab.cancelled",
|
|
11920
|
+
payload: { sessionId: this.sessionId, reason: payload.reason }
|
|
11921
|
+
});
|
|
11922
|
+
});
|
|
11923
|
+
this.disposers.push(dCancel);
|
|
11603
11924
|
const d1 = this.fleetBus.filter("bug.found", (e) => {
|
|
11604
11925
|
const payload = e.payload;
|
|
11605
11926
|
if (payload?.finding) {
|
|
@@ -11625,10 +11946,19 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
|
|
|
11625
11946
|
});
|
|
11626
11947
|
this.disposers.push(d3);
|
|
11627
11948
|
}
|
|
11949
|
+
roleFromSubagentId(subagentId) {
|
|
11950
|
+
for (const [role, id] of this.subagentIds) {
|
|
11951
|
+
if (id === subagentId) return role;
|
|
11952
|
+
}
|
|
11953
|
+
const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
|
|
11954
|
+
return match?.[1] ?? null;
|
|
11955
|
+
}
|
|
11628
11956
|
assembleReport() {
|
|
11629
11957
|
const bugList = Array.from(this.bugs.values());
|
|
11630
11958
|
const planList = Array.from(this.plans.values());
|
|
11631
11959
|
const evalList = Array.from(this.evaluations.values());
|
|
11960
|
+
let disposition = "completed";
|
|
11961
|
+
if (this.cancelled) disposition = "cancelled";
|
|
11632
11962
|
const verdictOrder = {
|
|
11633
11963
|
approve: 0,
|
|
11634
11964
|
needs_revision: 1,
|
|
@@ -11642,33 +11972,41 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
|
|
|
11642
11972
|
},
|
|
11643
11973
|
"approve"
|
|
11644
11974
|
);
|
|
11645
|
-
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict);
|
|
11975
|
+
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
|
|
11646
11976
|
return {
|
|
11647
11977
|
sessionId: this.sessionId,
|
|
11648
11978
|
startedAt: this.snapshot.createdAt,
|
|
11649
11979
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11650
11980
|
targetPaths: this.options.targetPaths,
|
|
11981
|
+
disposition,
|
|
11651
11982
|
bugs: bugList,
|
|
11652
11983
|
refactorPlans: planList,
|
|
11653
11984
|
evaluations: evalList,
|
|
11985
|
+
alerts: [...this.alerts],
|
|
11654
11986
|
overallVerdict,
|
|
11655
11987
|
summary
|
|
11656
11988
|
};
|
|
11657
11989
|
}
|
|
11658
|
-
buildMarkdownSummary(bugs, plans, evals, overallVerdict) {
|
|
11990
|
+
buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
|
|
11659
11991
|
const lines = [
|
|
11660
11992
|
`## Collaborative Debugging Report \u2014 ${this.sessionId}`,
|
|
11661
11993
|
"",
|
|
11662
11994
|
`**Target:** ${this.options.targetPaths.join(", ")}`,
|
|
11995
|
+
`**Disposition:** ${disposition.toUpperCase()}`,
|
|
11663
11996
|
`**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
|
|
11664
11997
|
""
|
|
11665
11998
|
];
|
|
11999
|
+
if (this.alerts.length > 0) {
|
|
12000
|
+
lines.push("### Alerts", "");
|
|
12001
|
+
for (const alert of this.alerts) {
|
|
12002
|
+
lines.push(`- **[${alert.level.toUpperCase()}]** ${alert.role}: ${alert.message}`);
|
|
12003
|
+
}
|
|
12004
|
+
lines.push("");
|
|
12005
|
+
}
|
|
11666
12006
|
if (bugs.length > 0) {
|
|
11667
12007
|
lines.push("### Bugs Found", "");
|
|
11668
12008
|
for (const b of bugs) {
|
|
11669
|
-
lines.push(
|
|
11670
|
-
`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`
|
|
11671
|
-
);
|
|
12009
|
+
lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
|
|
11672
12010
|
}
|
|
11673
12011
|
lines.push("");
|
|
11674
12012
|
}
|
|
@@ -11687,9 +12025,7 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
|
|
|
11687
12025
|
for (const e of evals) {
|
|
11688
12026
|
lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
|
|
11689
12027
|
for (const c of e.concerns) {
|
|
11690
|
-
if (c.severity === "blocking") {
|
|
11691
|
-
lines.push(` - ${c.description}`);
|
|
11692
|
-
}
|
|
12028
|
+
if (c.severity === "blocking") lines.push(` - ${c.description}`);
|
|
11693
12029
|
}
|
|
11694
12030
|
}
|
|
11695
12031
|
lines.push("");
|
|
@@ -11707,10 +12043,9 @@ var FleetSpawnBudgetError = class extends Error {
|
|
|
11707
12043
|
kind;
|
|
11708
12044
|
limit;
|
|
11709
12045
|
observed;
|
|
11710
|
-
constructor(kind, limit, observed) {
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
);
|
|
12046
|
+
constructor(kind, limit, observed, message) {
|
|
12047
|
+
const defaultMsg = kind === "max_spawns" ? `Director spawn budget exceeded: tried to spawn #${observed} but maxSpawns is ${limit}` : `Director spawn depth budget exceeded: this director is at depth ${observed} and maxSpawnDepth is ${limit}`;
|
|
12048
|
+
super(message ?? defaultMsg);
|
|
11714
12049
|
this.name = "FleetSpawnBudgetError";
|
|
11715
12050
|
this.kind = kind;
|
|
11716
12051
|
this.limit = limit;
|
|
@@ -11731,6 +12066,20 @@ var FleetCostCapError = class extends Error {
|
|
|
11731
12066
|
this.observed = observed;
|
|
11732
12067
|
}
|
|
11733
12068
|
};
|
|
12069
|
+
var FleetContextOverflowError = class extends Error {
|
|
12070
|
+
kind;
|
|
12071
|
+
limit;
|
|
12072
|
+
observed;
|
|
12073
|
+
constructor(limit, observed) {
|
|
12074
|
+
super(
|
|
12075
|
+
`Leader context overflow: leader has ${observed} tokens in flight (limit: ${limit}). Compact the leader context before spawning more subagents.`
|
|
12076
|
+
);
|
|
12077
|
+
this.name = "FleetContextOverflowError";
|
|
12078
|
+
this.kind = "max_context_load";
|
|
12079
|
+
this.limit = limit;
|
|
12080
|
+
this.observed = observed;
|
|
12081
|
+
}
|
|
12082
|
+
};
|
|
11734
12083
|
var Director = class {
|
|
11735
12084
|
/** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
|
|
11736
12085
|
get coordinatorId() {
|
|
@@ -11747,6 +12096,25 @@ var Director = class {
|
|
|
11747
12096
|
* injected; otherwise own FleetUsageAggregator.
|
|
11748
12097
|
*/
|
|
11749
12098
|
usage;
|
|
12099
|
+
/**
|
|
12100
|
+
* Update the leader agent's current context pressure (full request tokens:
|
|
12101
|
+
* messages + systemPrompt + toolDefs). The director checks this before every
|
|
12102
|
+
* spawn — if the pressure exceeds `maxLeaderContextLoad * maxContext`,
|
|
12103
|
+
* spawning is refused with a `FleetContextOverflowError`.
|
|
12104
|
+
*
|
|
12105
|
+
* Call this after each leader agent iteration to keep the pressure current.
|
|
12106
|
+
* The compactor's `CompactReport.fullRequestTokensAfter` is a natural source.
|
|
12107
|
+
*/
|
|
12108
|
+
setLeaderContextPressure(tokens) {
|
|
12109
|
+
this.leaderContextPressure = tokens;
|
|
12110
|
+
this.fleetManager?.setLeaderContextPressure(tokens);
|
|
12111
|
+
}
|
|
12112
|
+
/**
|
|
12113
|
+
* Read the leader agent's current context pressure.
|
|
12114
|
+
*/
|
|
12115
|
+
getLeaderContextPressure() {
|
|
12116
|
+
return this.leaderContextPressure;
|
|
12117
|
+
}
|
|
11750
12118
|
/**
|
|
11751
12119
|
* Optional fleet-level policy container. When provided the Director
|
|
11752
12120
|
* delegates spawn budgeting, manifest entries, and checkpointing to it
|
|
@@ -11832,6 +12200,23 @@ var Director = class {
|
|
|
11832
12200
|
taskCompletedListener = null;
|
|
11833
12201
|
/** Optional LLM classifier for smart dispatch. Passed from options. */
|
|
11834
12202
|
dispatchClassifier;
|
|
12203
|
+
/** Leader agent's current context pressure (full request tokens). */
|
|
12204
|
+
leaderContextPressure = 0;
|
|
12205
|
+
/** Maximum context load fraction before spawn is refused. */
|
|
12206
|
+
maxLeaderContextLoad;
|
|
12207
|
+
/** Provider's max context window in tokens. */
|
|
12208
|
+
maxContext;
|
|
12209
|
+
/**
|
|
12210
|
+
* When set by `workComplete()`, the director stops dispatching new tasks
|
|
12211
|
+
* and terminates all running subagents. Used when the director's LLM decides
|
|
12212
|
+
* the goal is satisfied and no further spawns are needed — prevents the
|
|
12213
|
+
* coordinator from keeping workers alive for tasks that will never arrive.
|
|
12214
|
+
*/
|
|
12215
|
+
workCompleteFlag = false;
|
|
12216
|
+
/** Pending /btw notes stashed by the leader agent (see setLeaderBtwNote). */
|
|
12217
|
+
_leaderBtwNotes = [];
|
|
12218
|
+
/** Active collab sessions tracked by sessionId (see spawnCollab). */
|
|
12219
|
+
_activeCollabSessions = /* @__PURE__ */ new Map();
|
|
11835
12220
|
constructor(opts) {
|
|
11836
12221
|
this.id = opts.config.coordinatorId || randomUUID();
|
|
11837
12222
|
this.manifestPath = opts.manifestPath;
|
|
@@ -11847,6 +12232,8 @@ var Director = class {
|
|
|
11847
12232
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
11848
12233
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
11849
12234
|
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
|
|
12235
|
+
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
12236
|
+
this.maxContext = opts.maxContext ?? 128e3;
|
|
11850
12237
|
this.sessionsRoot = opts.sessionsRoot;
|
|
11851
12238
|
this.directorRunId = opts.directorRunId ?? this.id;
|
|
11852
12239
|
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
|
|
@@ -11874,7 +12261,7 @@ var Director = class {
|
|
|
11874
12261
|
this.fleet = new FleetBus();
|
|
11875
12262
|
this.usage = new FleetUsageAggregator(
|
|
11876
12263
|
this.fleet,
|
|
11877
|
-
(
|
|
12264
|
+
(_id, provider, model) => {
|
|
11878
12265
|
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
11879
12266
|
return void 0;
|
|
11880
12267
|
},
|
|
@@ -11936,6 +12323,9 @@ var Director = class {
|
|
|
11936
12323
|
});
|
|
11937
12324
|
this.fleet.filter("budget.threshold_reached", (e) => {
|
|
11938
12325
|
const payload = e.payload;
|
|
12326
|
+
if (e.subagentId.startsWith("bug-hunter-") || e.subagentId.startsWith("refactor-planner-") || e.subagentId.startsWith("critic-")) {
|
|
12327
|
+
return;
|
|
12328
|
+
}
|
|
11939
12329
|
if (payload.kind === "timeout") {
|
|
11940
12330
|
const progress = progressBySubagent.get(e.subagentId) ?? 0;
|
|
11941
12331
|
const lastProgress = lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
@@ -12014,6 +12404,107 @@ var Director = class {
|
|
|
12014
12404
|
extensionsFor(subagentId) {
|
|
12015
12405
|
return this.extendTotals.get(subagentId) ?? 0;
|
|
12016
12406
|
}
|
|
12407
|
+
/**
|
|
12408
|
+
* Signal that the director's work is done. Once called:
|
|
12409
|
+
* - `spawn()` throws `FleetSpawnBudgetError('max_spawns', …)` — no new
|
|
12410
|
+
* subagents can be created
|
|
12411
|
+
* - Running subagents are NOT forcibly stopped — they finish naturally,
|
|
12412
|
+
* but no new tasks are dispatched to them
|
|
12413
|
+
*
|
|
12414
|
+
* This lets the director LLM say "I'm satisfied with the results, stop
|
|
12415
|
+
* spawning and wind down" — without killing in-flight work mid-execution.
|
|
12416
|
+
* Call `terminateAll()` separately if you need immediate teardown.
|
|
12417
|
+
*
|
|
12418
|
+
* Idempotent — calling twice is a no-op.
|
|
12419
|
+
*/
|
|
12420
|
+
workComplete() {
|
|
12421
|
+
this.workCompleteFlag = true;
|
|
12422
|
+
this.fleet.emit({ subagentId: this.id, ts: Date.now(), type: "director.work_complete", payload: {} });
|
|
12423
|
+
}
|
|
12424
|
+
/** Returns true if `workComplete()` has been called on this director. */
|
|
12425
|
+
isWorkComplete() {
|
|
12426
|
+
return this.workCompleteFlag;
|
|
12427
|
+
}
|
|
12428
|
+
/**
|
|
12429
|
+
* Stashes a /btw note on the leader agent's context. The leader's agent loop
|
|
12430
|
+
* calls `consumeBtwNotes()` at each iteration boundary and folds pending notes
|
|
12431
|
+
* into the conversation as a visible block — no abort, no restart, just a
|
|
12432
|
+
* "by the way" nudge the model picks up on its next turn.
|
|
12433
|
+
*
|
|
12434
|
+
* This is the entry point for the host (CLI, TUI) to inject /btw notes
|
|
12435
|
+
* programmatically without going through the slash-command path.
|
|
12436
|
+
*/
|
|
12437
|
+
setLeaderBtwNote(note) {
|
|
12438
|
+
const trimmed = note.trim();
|
|
12439
|
+
if (!trimmed) return this._leaderBtwNotes.length;
|
|
12440
|
+
this._leaderBtwNotes = [...this._leaderBtwNotes, trimmed].slice(-20);
|
|
12441
|
+
return this._leaderBtwNotes.length;
|
|
12442
|
+
}
|
|
12443
|
+
/**
|
|
12444
|
+
* Read and clear all pending /btw notes the leader has stashed.
|
|
12445
|
+
* Returns them in FIFO order (empty array when none).
|
|
12446
|
+
*
|
|
12447
|
+
* Called by CollabSession when a budget threshold event fires so the
|
|
12448
|
+
* Director can inspect accumulated /btw notes before deciding whether
|
|
12449
|
+
* to cancel the collab session or let it continue.
|
|
12450
|
+
*/
|
|
12451
|
+
getLeaderBtwNotes() {
|
|
12452
|
+
const notes = this._leaderBtwNotes;
|
|
12453
|
+
this._leaderBtwNotes = [];
|
|
12454
|
+
return notes;
|
|
12455
|
+
}
|
|
12456
|
+
/**
|
|
12457
|
+
* Peek at pending /btw notes without consuming them.
|
|
12458
|
+
* Useful for UI to show "N pending notes" without clearing them.
|
|
12459
|
+
*/
|
|
12460
|
+
peekLeaderBtwNotes() {
|
|
12461
|
+
return [...this._leaderBtwNotes];
|
|
12462
|
+
}
|
|
12463
|
+
/**
|
|
12464
|
+
* Drain (read + clear) all /btw notes in one call.
|
|
12465
|
+
* Alias for getLeaderBtwNotes() — kept for symmetry with consumeBtwNotes()
|
|
12466
|
+
* in the agent's btw.ts. The Director calls this at the point where it
|
|
12467
|
+
* makes a cancellation decision, not on every budget event.
|
|
12468
|
+
*/
|
|
12469
|
+
drainLeaderBtwNotes() {
|
|
12470
|
+
return this.getLeaderBtwNotes();
|
|
12471
|
+
}
|
|
12472
|
+
/**
|
|
12473
|
+
* Cancel an active collab session by its id.
|
|
12474
|
+
* Emits `director.cancel_collab` on the FleetBus so the session's agents
|
|
12475
|
+
* finish early with a 'cancelled' disposition.
|
|
12476
|
+
*
|
|
12477
|
+
* Returns silently if the session id is not tracked or already settled.
|
|
12478
|
+
* The CollabDebugReport will reflect 'cancelled' disposition when awaited.
|
|
12479
|
+
*/
|
|
12480
|
+
cancelCollabSession(sessionId, reason = "Director cancelled") {
|
|
12481
|
+
const session = this._activeCollabSessions.get(sessionId);
|
|
12482
|
+
if (!session || session.isCancelled()) return;
|
|
12483
|
+
session.cancel(reason);
|
|
12484
|
+
for (const [_role, subagentId] of session.getSubagentIds()) {
|
|
12485
|
+
this.coordinator.stop(subagentId).catch(() => {
|
|
12486
|
+
});
|
|
12487
|
+
}
|
|
12488
|
+
}
|
|
12489
|
+
/**
|
|
12490
|
+
* Subscribe a callback to be notified whenever a collab session raises
|
|
12491
|
+
* an alert (warning level). The callback receives the full DirectorAlert
|
|
12492
|
+
* payload so the host (CLI, TUI) can display it to the user.
|
|
12493
|
+
* Returns an unsubscribe function.
|
|
12494
|
+
*/
|
|
12495
|
+
onCollabAlert(handler) {
|
|
12496
|
+
return this.fleet.filter("collab.warning", (e) => {
|
|
12497
|
+
handler(e.payload);
|
|
12498
|
+
});
|
|
12499
|
+
}
|
|
12500
|
+
/**
|
|
12501
|
+
* Returns all active (not yet settled) collab session ids.
|
|
12502
|
+
* Useful for the TUI to render a "N active sessions" badge and for
|
|
12503
|
+
* the host to know what can be cancelled.
|
|
12504
|
+
*/
|
|
12505
|
+
activeCollabSessions() {
|
|
12506
|
+
return Array.from(this._activeCollabSessions.keys());
|
|
12507
|
+
}
|
|
12017
12508
|
/** Best-effort session-writer append. Swallows failures — the director
|
|
12018
12509
|
* must not break a fleet run because the session JSONL handle closed. */
|
|
12019
12510
|
async appendSessionEvent(event) {
|
|
@@ -12052,12 +12543,21 @@ var Director = class {
|
|
|
12052
12543
|
* it the `cost` column in `usage.snapshot()` stays at 0.
|
|
12053
12544
|
*/
|
|
12054
12545
|
async spawn(config, priceLookup) {
|
|
12546
|
+
if (this.workCompleteFlag) {
|
|
12547
|
+
throw new FleetSpawnBudgetError(
|
|
12548
|
+
"max_spawns",
|
|
12549
|
+
this.maxSpawns,
|
|
12550
|
+
this.spawnCount + 1,
|
|
12551
|
+
"workComplete() has been called \u2014 director closed further spawning"
|
|
12552
|
+
);
|
|
12553
|
+
}
|
|
12055
12554
|
if (this.fleetManager) {
|
|
12056
12555
|
const rejection = this.fleetManager.canSpawn(config);
|
|
12057
12556
|
if (rejection) {
|
|
12058
12557
|
if (rejection.kind === "max_spawn_depth") throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
|
|
12059
12558
|
if (rejection.kind === "max_spawns") throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
|
|
12060
12559
|
if (rejection.kind === "max_cost_usd") throw new FleetCostCapError(rejection.limit, rejection.observed);
|
|
12560
|
+
if (rejection.kind === "max_context_load") throw new FleetContextOverflowError(rejection.limit, rejection.observed);
|
|
12061
12561
|
}
|
|
12062
12562
|
} else {
|
|
12063
12563
|
if (this.spawnDepth >= this.maxSpawnDepth) {
|
|
@@ -12072,9 +12572,15 @@ var Director = class {
|
|
|
12072
12572
|
throw new FleetCostCapError(this.maxFleetCostUsd, totalCost);
|
|
12073
12573
|
}
|
|
12074
12574
|
}
|
|
12575
|
+
if (this.maxLeaderContextLoad < 1) {
|
|
12576
|
+
const threshold = this.maxContext * this.maxLeaderContextLoad;
|
|
12577
|
+
if (this.leaderContextPressure >= threshold) {
|
|
12578
|
+
throw new FleetContextOverflowError(threshold, this.leaderContextPressure);
|
|
12579
|
+
}
|
|
12580
|
+
}
|
|
12075
12581
|
}
|
|
12076
12582
|
let result;
|
|
12077
|
-
const needsNickname = config.name === config.role || !config.name || config.name === "subagent";
|
|
12583
|
+
const needsNickname = config.name === config.role || !config.name || config.name === "subagent" || config.name === "adhoc";
|
|
12078
12584
|
if (needsNickname) {
|
|
12079
12585
|
const role = config.role ?? "subagent";
|
|
12080
12586
|
if (this.fleetManager) {
|
|
@@ -12299,6 +12805,24 @@ var Director = class {
|
|
|
12299
12805
|
*/
|
|
12300
12806
|
async assign(task) {
|
|
12301
12807
|
const taskWithId = task.id ? task : { ...task, id: randomUUID() };
|
|
12808
|
+
if (this.workCompleteFlag) {
|
|
12809
|
+
const synthetic = {
|
|
12810
|
+
subagentId: taskWithId.subagentId ?? "unassigned",
|
|
12811
|
+
taskId: taskWithId.id,
|
|
12812
|
+
status: "stopped",
|
|
12813
|
+
error: { kind: "aborted_by_parent", message: "Director called workComplete() \u2014 no further tasks will run", retryable: false },
|
|
12814
|
+
iterations: 0,
|
|
12815
|
+
toolCalls: 0,
|
|
12816
|
+
durationMs: 0
|
|
12817
|
+
};
|
|
12818
|
+
this.completed.set(taskWithId.id, synthetic);
|
|
12819
|
+
const waiter = this.taskWaiters.get(taskWithId.id);
|
|
12820
|
+
if (waiter) {
|
|
12821
|
+
waiter.resolve(synthetic);
|
|
12822
|
+
this.taskWaiters.delete(taskWithId.id);
|
|
12823
|
+
}
|
|
12824
|
+
return taskWithId.id;
|
|
12825
|
+
}
|
|
12302
12826
|
if (task.subagentId) {
|
|
12303
12827
|
if (this.fleetManager) {
|
|
12304
12828
|
this.fleetManager.addTaskToSubagent(task.subagentId, taskWithId.id);
|
|
@@ -12521,12 +13045,14 @@ var Director = class {
|
|
|
12521
13045
|
makeAskTool(this),
|
|
12522
13046
|
makeRollUpTool(this),
|
|
12523
13047
|
makeTerminateTool(this),
|
|
13048
|
+
makeTerminateAllTool(this),
|
|
12524
13049
|
makeFleetStatusTool(this),
|
|
12525
13050
|
makeFleetUsageTool(this),
|
|
12526
13051
|
makeFleetSessionTool(this),
|
|
12527
13052
|
makeFleetHealthTool(this),
|
|
12528
13053
|
makeCollabDebugTool(this),
|
|
12529
|
-
makeFleetEmitTool(this)
|
|
13054
|
+
makeFleetEmitTool(this),
|
|
13055
|
+
makeWorkCompleteTool(this)
|
|
12530
13056
|
];
|
|
12531
13057
|
return t;
|
|
12532
13058
|
}
|
|
@@ -12546,7 +13072,15 @@ var Director = class {
|
|
|
12546
13072
|
* three agents complete or the session times out.
|
|
12547
13073
|
*/
|
|
12548
13074
|
async spawnCollab(options) {
|
|
12549
|
-
const session = new CollabSession(this, this.fleet,
|
|
13075
|
+
const session = new CollabSession(this, this.fleet, {
|
|
13076
|
+
...options,
|
|
13077
|
+
onBudgetWarning: (alert) => {
|
|
13078
|
+
return options.onBudgetWarning?.(alert) ?? "ignore";
|
|
13079
|
+
}
|
|
13080
|
+
});
|
|
13081
|
+
this._activeCollabSessions.set(session.sessionId, session);
|
|
13082
|
+
session.on("session.done", () => this._activeCollabSessions.delete(session.sessionId));
|
|
13083
|
+
session.on("session.error", () => this._activeCollabSessions.delete(session.sessionId));
|
|
12550
13084
|
return session.start();
|
|
12551
13085
|
}
|
|
12552
13086
|
/**
|
|
@@ -15437,7 +15971,7 @@ function analyzeCriticalPath(graph) {
|
|
|
15437
15971
|
blockedTasks
|
|
15438
15972
|
};
|
|
15439
15973
|
}
|
|
15440
|
-
function getTransitiveBlocked(
|
|
15974
|
+
function getTransitiveBlocked(_graph, taskId, blocksMap) {
|
|
15441
15975
|
const visited = /* @__PURE__ */ new Set();
|
|
15442
15976
|
const queue = [taskId];
|
|
15443
15977
|
while (queue.length > 0) {
|
|
@@ -15453,7 +15987,7 @@ function getTransitiveBlocked(graph, taskId, blocksMap) {
|
|
|
15453
15987
|
}
|
|
15454
15988
|
return visited;
|
|
15455
15989
|
}
|
|
15456
|
-
function computeCriticalPath(graph,
|
|
15990
|
+
function computeCriticalPath(graph, _topoOrder, blockedByMap) {
|
|
15457
15991
|
const allIds = Array.from(graph.nodes.keys());
|
|
15458
15992
|
if (allIds.length === 0) return [];
|
|
15459
15993
|
const dist = /* @__PURE__ */ new Map();
|
|
@@ -15871,13 +16405,11 @@ function createAutoExecutor(opts) {
|
|
|
15871
16405
|
|
|
15872
16406
|
// src/sdd/sdd-task-decomposer.ts
|
|
15873
16407
|
var SddTaskDecomposer = class {
|
|
15874
|
-
constructor(tracker,
|
|
16408
|
+
constructor(tracker, _graph, opts = {}) {
|
|
15875
16409
|
this.tracker = tracker;
|
|
15876
|
-
this.graph = graph;
|
|
15877
16410
|
this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
|
|
15878
16411
|
}
|
|
15879
16412
|
tracker;
|
|
15880
|
-
graph;
|
|
15881
16413
|
slots;
|
|
15882
16414
|
wave = 0;
|
|
15883
16415
|
// -------------------------------------------------------------------
|
|
@@ -16030,7 +16562,7 @@ var SddParallelRun = class {
|
|
|
16030
16562
|
this.coordinator.setRunner?.(runner);
|
|
16031
16563
|
}
|
|
16032
16564
|
defaultFactory() {
|
|
16033
|
-
return async (
|
|
16565
|
+
return async (_config) => ({
|
|
16034
16566
|
agent: this.opts.agent,
|
|
16035
16567
|
events: this.opts.agent.events
|
|
16036
16568
|
});
|
|
@@ -16844,6 +17376,12 @@ function roughEstimate(messages) {
|
|
|
16844
17376
|
return total;
|
|
16845
17377
|
}
|
|
16846
17378
|
function createContextManagerTool(opts = {}) {
|
|
17379
|
+
const minCompactThreshold = opts.minCompactThreshold ?? 0;
|
|
17380
|
+
const noopRetryDeltaTokens = opts.noopRetryDeltaTokens ?? 2e3;
|
|
17381
|
+
const maxContext = opts.maxContext ?? 128e3;
|
|
17382
|
+
const compactThresholdFraction = opts.compactThresholdFraction ?? 0.5;
|
|
17383
|
+
const effectiveThreshold = minCompactThreshold > 0 ? minCompactThreshold : Math.floor(maxContext * compactThresholdFraction);
|
|
17384
|
+
let lastNoopTokens = 0;
|
|
16847
17385
|
return {
|
|
16848
17386
|
name: CONTEXT_MANAGER_TOOL_NAME,
|
|
16849
17387
|
description: 'Inspect or reorganize the conversation context window. Use "check" to see token budget. Use "summary" to collapse a message range into a concise note (provide "text" for custom summary). Use "prune" to remove specific messages by index. Use "add_note" to inject a summary note. Use "compact" to run aggressive compaction. Use "repair" to remove orphan tool_use/tool_result blocks after manual context surgery.',
|
|
@@ -16934,11 +17472,41 @@ function createContextManagerTool(opts = {}) {
|
|
|
16934
17472
|
notes: "No compactor registered. Use /compact aggressive via slash command instead."
|
|
16935
17473
|
};
|
|
16936
17474
|
}
|
|
17475
|
+
const fullEstimate = input.systemPrompt != null && Array.isArray(input.tools) ? estimateRequestTokens(messages, input.systemPrompt, input.tools) : { total: beforeTokens};
|
|
17476
|
+
const currentTokens = fullEstimate.total;
|
|
17477
|
+
if (lastNoopTokens > 0) {
|
|
17478
|
+
const delta = currentTokens - lastNoopTokens;
|
|
17479
|
+
if (delta < noopRetryDeltaTokens) {
|
|
17480
|
+
return {
|
|
17481
|
+
action: "compact",
|
|
17482
|
+
beforeTokens,
|
|
17483
|
+
afterTokens: beforeTokens,
|
|
17484
|
+
messageCount: messages.length,
|
|
17485
|
+
notes: `Compact is a NOOP retry: context grew only ${delta} tokens since the last no-op attempt (threshold: ${noopRetryDeltaTokens}). Skip until more content accumulates.`
|
|
17486
|
+
};
|
|
17487
|
+
}
|
|
17488
|
+
}
|
|
17489
|
+
if (currentTokens < effectiveThreshold) {
|
|
17490
|
+
return {
|
|
17491
|
+
action: "compact",
|
|
17492
|
+
beforeTokens,
|
|
17493
|
+
afterTokens: beforeTokens,
|
|
17494
|
+
messageCount: messages.length,
|
|
17495
|
+
notes: `Context tokens (${currentTokens}) below compact threshold (${effectiveThreshold}). Skipping.`
|
|
17496
|
+
};
|
|
17497
|
+
}
|
|
16937
17498
|
const report = await opts.compactor.compact(ctx);
|
|
16938
17499
|
ctx.clearFileTracking();
|
|
16939
17500
|
const repair = applyMessages([...ctx.messages]);
|
|
16940
17501
|
const afterTokens = repair.changed ? roughEstimate(ctx.messages) : report.after;
|
|
16941
17502
|
const repaired = report.repaired ?? (repair.changed ? repair : void 0);
|
|
17503
|
+
const reduced = report.fullRequestTokensBefore > report.fullRequestTokensAfter;
|
|
17504
|
+
const repairedSomething = !!report.repaired;
|
|
17505
|
+
if (reduced || repairedSomething) {
|
|
17506
|
+
lastNoopTokens = 0;
|
|
17507
|
+
} else {
|
|
17508
|
+
lastNoopTokens = currentTokens;
|
|
17509
|
+
}
|
|
16942
17510
|
return {
|
|
16943
17511
|
action: "compact",
|
|
16944
17512
|
beforeTokens,
|