@wrongstack/core 0.9.7 → 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-DaF_EgRG.d.ts → agent-subagent-runner-U-rs7kk7.d.ts} +3 -4
- package/dist/compactor-D7X96RLZ.d.ts +41 -0
- package/dist/{config-SkMIDN9L.d.ts → config-CLXMDOSs.d.ts} +5 -1
- package/dist/{context-BRNbHmRM.d.ts → context-zkZeILpr.d.ts} +46 -0
- package/dist/coordination/index.d.ts +14 -13
- package/dist/coordination/index.js +1263 -123
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +22 -21
- package/dist/defaults/index.js +1477 -152
- 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 +42 -30
- package/dist/execution/index.js +359 -112
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +7 -6
- package/dist/extension/index.js.map +1 -1
- package/dist/{index-CP8638Wm.d.ts → index-BIHJ4uII.d.ts} +12 -8
- package/dist/{index-Bsha5K4D.d.ts → index-CFO9QmJo.d.ts} +9 -8
- package/dist/index.d.ts +319 -37
- package/dist/index.js +2571 -324
- 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 +10 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-BouUWYW6.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-DTXF2aAl.d.ts → multi-agent-coordinator-CAhsegPz.d.ts} +20 -2
- package/dist/{null-fleet-bus-Chrc_3Pp.d.ts → null-fleet-bus-Dnl19vmf.d.ts} +496 -15
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-TcJfc29Y.d.ts → path-resolver-CHiBL0DD.d.ts} +2 -2
- package/dist/{secret-scrubber-DttNiGYA.d.ts → permission-H35s9Evv.d.ts} +2 -7
- package/dist/{permission-policy-BpCGYBud.d.ts → permission-policy-CT-nRmTn.d.ts} +9 -2
- package/dist/{plan-templates-envSmNlZ.d.ts → plan-templates-Bs8iRwi6.d.ts} +28 -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 +10 -10
- package/dist/sdd/index.js +225 -68
- package/dist/sdd/index.js.map +1 -1
- package/dist/secret-scrubber-3MHDDAtm.d.ts +6 -0
- package/dist/{secret-scrubber-QSeI0ADi.d.ts → secret-scrubber-7rSC_emZ.d.ts} +1 -1
- package/dist/security/index.d.ts +5 -4
- package/dist/security/index.js +37 -6
- package/dist/security/index.js.map +1 -1
- 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 +391 -6
- package/dist/storage/index.js +760 -40
- 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-CsktM3h9.d.ts → tool-executor-flTuxsqO.d.ts} +9 -4
- package/dist/types/index.d.ts +16 -15
- package/dist/types/index.js +60 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +25 -2
- package/dist/utils/index.js +90 -1
- 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
|
@@ -319,9 +319,11 @@ function isEmptyMessage(msg) {
|
|
|
319
319
|
var DefaultSessionStore = class {
|
|
320
320
|
dir;
|
|
321
321
|
events;
|
|
322
|
+
secretScrubber;
|
|
322
323
|
constructor(opts) {
|
|
323
324
|
this.dir = opts.dir;
|
|
324
325
|
this.events = opts.events;
|
|
326
|
+
this.secretScrubber = opts.secretScrubber;
|
|
325
327
|
}
|
|
326
328
|
/** Join session ID to its absolute path within the store directory. */
|
|
327
329
|
sessionPath(id, ext) {
|
|
@@ -346,7 +348,11 @@ var DefaultSessionStore = class {
|
|
|
346
348
|
);
|
|
347
349
|
}
|
|
348
350
|
try {
|
|
349
|
-
return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
|
|
351
|
+
return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
|
|
352
|
+
dir: shardDir,
|
|
353
|
+
filePath: file,
|
|
354
|
+
secretScrubber: this.secretScrubber
|
|
355
|
+
});
|
|
350
356
|
} catch (err) {
|
|
351
357
|
await handle.close().catch(() => {
|
|
352
358
|
});
|
|
@@ -376,7 +382,7 @@ var DefaultSessionStore = class {
|
|
|
376
382
|
provider: data.metadata.provider
|
|
377
383
|
},
|
|
378
384
|
this.events,
|
|
379
|
-
{ resumed: true, dir: this.dir, filePath: file }
|
|
385
|
+
{ resumed: true, dir: this.dir, filePath: file, secretScrubber: this.secretScrubber }
|
|
380
386
|
);
|
|
381
387
|
return { writer, data };
|
|
382
388
|
} catch (err) {
|
|
@@ -582,6 +588,7 @@ var FileSessionWriter = class {
|
|
|
582
588
|
this.resumed = opts.resumed ?? false;
|
|
583
589
|
this.manifestFile = opts.dir ? path15.join(opts.dir, `${id}.summary.json`) : "";
|
|
584
590
|
this.filePath = opts.filePath ?? "";
|
|
591
|
+
this.secretScrubber = opts.secretScrubber;
|
|
585
592
|
this.summary = {
|
|
586
593
|
id,
|
|
587
594
|
title: "(empty session)",
|
|
@@ -610,7 +617,29 @@ var FileSessionWriter = class {
|
|
|
610
617
|
resumed;
|
|
611
618
|
appendFailCount = 0;
|
|
612
619
|
lastAppendWarnAt = 0;
|
|
613
|
-
|
|
620
|
+
secretScrubber;
|
|
621
|
+
/**
|
|
622
|
+
* Scrub secrets out of conversation-turn events before they are observed
|
|
623
|
+
* for the summary, written to the JSONL log, or surfaced on resume. Only
|
|
624
|
+
* `user_input` / `llm_response` carry free-form user/model text; other event
|
|
625
|
+
* types either have no secret-bearing content or are already scrubbed
|
|
626
|
+
* upstream (tool results). Returns the event unchanged when no scrubber is
|
|
627
|
+
* configured.
|
|
628
|
+
*/
|
|
629
|
+
scrubEvent(event) {
|
|
630
|
+
const s = this.secretScrubber;
|
|
631
|
+
if (!s) return event;
|
|
632
|
+
if (event.type === "user_input") {
|
|
633
|
+
return {
|
|
634
|
+
...event,
|
|
635
|
+
content: typeof event.content === "string" ? s.scrub(event.content) : s.scrubObject(event.content)
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
if (event.type === "llm_response") {
|
|
639
|
+
return { ...event, content: s.scrubObject(event.content) };
|
|
640
|
+
}
|
|
641
|
+
return event;
|
|
642
|
+
}
|
|
614
643
|
pendingFileSnapshots = [];
|
|
615
644
|
/** Tracks open tool_use IDs during the current run to serialize on close for resume. */
|
|
616
645
|
openToolUses = /* @__PURE__ */ new Set();
|
|
@@ -642,9 +671,10 @@ var FileSessionWriter = class {
|
|
|
642
671
|
this.initDone = true;
|
|
643
672
|
await this.writeSessionStartLazy();
|
|
644
673
|
}
|
|
645
|
-
this.
|
|
674
|
+
const scrubbed = this.scrubEvent(event);
|
|
675
|
+
this.observeForSummary(scrubbed);
|
|
646
676
|
try {
|
|
647
|
-
await this.handle.appendFile(`${JSON.stringify(
|
|
677
|
+
await this.handle.appendFile(`${JSON.stringify(scrubbed)}
|
|
648
678
|
`, "utf8");
|
|
649
679
|
} catch (err) {
|
|
650
680
|
this.appendFailCount++;
|
|
@@ -700,7 +730,6 @@ var FileSessionWriter = class {
|
|
|
700
730
|
await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);
|
|
701
731
|
this.pendingFileSnapshots = [];
|
|
702
732
|
}
|
|
703
|
-
this.promptIndex = promptIndex + 1;
|
|
704
733
|
await this.append({
|
|
705
734
|
type: "checkpoint",
|
|
706
735
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -796,6 +825,38 @@ var FileSessionWriter = class {
|
|
|
796
825
|
`;
|
|
797
826
|
await fsp.writeFile(this.filePath, record, "utf8");
|
|
798
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
|
+
}
|
|
799
860
|
};
|
|
800
861
|
function userInputTitle(content) {
|
|
801
862
|
const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
@@ -1266,6 +1327,9 @@ function isSecretField(name) {
|
|
|
1266
1327
|
return SECRET_KEY_PATTERN.test(lc);
|
|
1267
1328
|
}
|
|
1268
1329
|
|
|
1330
|
+
// src/storage/config-loader.ts
|
|
1331
|
+
init_atomic_write();
|
|
1332
|
+
|
|
1269
1333
|
// src/types/context-window.ts
|
|
1270
1334
|
var DEFAULT_CONTEXT_WINDOW_MODE_ID = "balanced";
|
|
1271
1335
|
var CONTEXT_WINDOW_MODES = Object.freeze([
|
|
@@ -1510,6 +1574,40 @@ var DefaultConfigLoader = class {
|
|
|
1510
1574
|
}
|
|
1511
1575
|
return Object.freeze(cfg);
|
|
1512
1576
|
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Persist a sync config to ~/.wrongstack/sync.json, with the token encrypted
|
|
1579
|
+
* by the vault (if provided). The file is isolated from the main config
|
|
1580
|
+
* hierarchy to prevent accidental commits.
|
|
1581
|
+
*/
|
|
1582
|
+
async persistSyncConfig(cfg) {
|
|
1583
|
+
let toWrite = { ...cfg };
|
|
1584
|
+
if (this.vault && toWrite.githubToken && !toWrite.githubToken.startsWith("enc:")) {
|
|
1585
|
+
toWrite = { ...toWrite, githubToken: this.vault.encrypt(toWrite.githubToken) };
|
|
1586
|
+
}
|
|
1587
|
+
await atomicWrite(this.paths.syncConfig, JSON.stringify(toWrite, null, 2), { mode: 384 });
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Read ~/.wrongstack/sync.json (encrypted GitHub token storage) and decrypt
|
|
1591
|
+
* the token if a vault is available. Returns null if the file doesn't exist.
|
|
1592
|
+
* This is separate from main config loading because sync.json is intentionally
|
|
1593
|
+
* isolated — it should never be part of project-local or env-driven config.
|
|
1594
|
+
*/
|
|
1595
|
+
async loadSyncConfig() {
|
|
1596
|
+
try {
|
|
1597
|
+
const raw = await fsp.readFile(this.paths.syncConfig, "utf8");
|
|
1598
|
+
const parsed = safeParse(raw);
|
|
1599
|
+
if (!parsed.ok || !parsed.value) return null;
|
|
1600
|
+
if (this.vault) {
|
|
1601
|
+
const decrypted = decryptConfigSecrets({ sync: parsed.value }, this.vault);
|
|
1602
|
+
return decrypted.sync ?? null;
|
|
1603
|
+
}
|
|
1604
|
+
return parsed.value;
|
|
1605
|
+
} catch (err) {
|
|
1606
|
+
if (err.code === "ENOENT") return null;
|
|
1607
|
+
console.warn("[config] Failed to load sync config:", err);
|
|
1608
|
+
return null;
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1513
1611
|
async readJson(file) {
|
|
1514
1612
|
let raw;
|
|
1515
1613
|
try {
|
|
@@ -3126,7 +3224,11 @@ var DefaultPermissionPolicy = class {
|
|
|
3126
3224
|
return { permission: "deny", source: "deny", reason: "session soft deny (user pressed no)" };
|
|
3127
3225
|
}
|
|
3128
3226
|
if (this.sessionAllowed.has(subjectKey)) {
|
|
3129
|
-
return {
|
|
3227
|
+
return {
|
|
3228
|
+
permission: "auto",
|
|
3229
|
+
source: "trust",
|
|
3230
|
+
reason: "session soft allow (user pressed yes)"
|
|
3231
|
+
};
|
|
3130
3232
|
}
|
|
3131
3233
|
if (entry?.deny && subject && matchAny(entry.deny, subject)) {
|
|
3132
3234
|
return { permission: "deny", source: "deny", reason: "matched deny pattern" };
|
|
@@ -3146,7 +3248,11 @@ var DefaultPermissionPolicy = class {
|
|
|
3146
3248
|
const decision = await this.promptDelegate(tool, input, subject ?? tool.name);
|
|
3147
3249
|
if (decision === "always") {
|
|
3148
3250
|
await this.trust({ tool: tool.name, pattern: subject ?? tool.name });
|
|
3149
|
-
return {
|
|
3251
|
+
return {
|
|
3252
|
+
permission: "auto",
|
|
3253
|
+
source: "user",
|
|
3254
|
+
reason: "destructive yolo always-allowed"
|
|
3255
|
+
};
|
|
3150
3256
|
}
|
|
3151
3257
|
if (decision === "deny") {
|
|
3152
3258
|
await this.deny({ tool: tool.name, pattern: subject ?? tool.name });
|
|
@@ -3154,13 +3260,22 @@ var DefaultPermissionPolicy = class {
|
|
|
3154
3260
|
}
|
|
3155
3261
|
return { permission: decision === "yes" ? "auto" : "deny", source: "user" };
|
|
3156
3262
|
}
|
|
3157
|
-
return {
|
|
3263
|
+
return {
|
|
3264
|
+
permission: "confirm",
|
|
3265
|
+
source: "yolo_destructive",
|
|
3266
|
+
riskTier: "destructive",
|
|
3267
|
+
reason: "destructive tool needs explicit approval even in yolo mode"
|
|
3268
|
+
};
|
|
3158
3269
|
}
|
|
3159
3270
|
return { permission: "auto", source: "yolo" };
|
|
3160
3271
|
}
|
|
3161
3272
|
if (tool.name === "write" && subject) {
|
|
3162
3273
|
if (ctx.hasRead(subject)) {
|
|
3163
|
-
return {
|
|
3274
|
+
return {
|
|
3275
|
+
permission: "auto",
|
|
3276
|
+
source: "context",
|
|
3277
|
+
reason: "file already read in this session"
|
|
3278
|
+
};
|
|
3164
3279
|
}
|
|
3165
3280
|
}
|
|
3166
3281
|
if (tool.permission === "auto" && !tool.mutating) {
|
|
@@ -3268,6 +3383,10 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
3268
3383
|
// arbitrary shell — use exec for constrained shell
|
|
3269
3384
|
"write",
|
|
3270
3385
|
// arbitrary file write
|
|
3386
|
+
"edit",
|
|
3387
|
+
// arbitrary in-project file modification (equivalent to write)
|
|
3388
|
+
"replace",
|
|
3389
|
+
// arbitrary multi-file find/replace (equivalent to write)
|
|
3271
3390
|
"scaffold",
|
|
3272
3391
|
// arbitrary file generation outside project root
|
|
3273
3392
|
"patch",
|
|
@@ -3277,12 +3396,22 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
3277
3396
|
"exec"
|
|
3278
3397
|
// restricted shell but with arbitrary command args
|
|
3279
3398
|
]);
|
|
3399
|
+
/**
|
|
3400
|
+
* Tools from MCP servers (`mcp__<server>__<tool>`) are external code of
|
|
3401
|
+
* unknown capability — they may wrap a shell or filesystem. They are
|
|
3402
|
+
* fail-closed here: not auto-approved for subagents by default, so the
|
|
3403
|
+
* leader must allow them explicitly per-spawn.
|
|
3404
|
+
*/
|
|
3405
|
+
static isMcpTool(name) {
|
|
3406
|
+
return name.startsWith("mcp__");
|
|
3407
|
+
}
|
|
3280
3408
|
async evaluate(tool) {
|
|
3281
|
-
|
|
3409
|
+
const blocked = _AutoApprovePermissionPolicy.DENY.has(tool.name) || _AutoApprovePermissionPolicy.isMcpTool(tool.name);
|
|
3410
|
+
if (tool.permission === "deny" || blocked) {
|
|
3282
3411
|
return {
|
|
3283
3412
|
permission: "deny",
|
|
3284
3413
|
source: "subagent_guard",
|
|
3285
|
-
reason:
|
|
3414
|
+
reason: blocked ? `tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow it explicitly` : "tool default deny"
|
|
3286
3415
|
};
|
|
3287
3416
|
}
|
|
3288
3417
|
return { permission: "auto", source: "yolo" };
|
|
@@ -3697,6 +3826,95 @@ function parseDescription(raw) {
|
|
|
3697
3826
|
return { trigger, scope };
|
|
3698
3827
|
}
|
|
3699
3828
|
|
|
3829
|
+
// src/utils/json-repair.ts
|
|
3830
|
+
function completePartialObject(s) {
|
|
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;
|
|
3846
|
+
continue;
|
|
3847
|
+
}
|
|
3848
|
+
if (ch === "\\") {
|
|
3849
|
+
escaped = true;
|
|
3850
|
+
continue;
|
|
3851
|
+
}
|
|
3852
|
+
if (ch === '"') {
|
|
3853
|
+
inString = false;
|
|
3854
|
+
prevSig = '"';
|
|
3855
|
+
stringBraceDepth = 0;
|
|
3856
|
+
continue;
|
|
3857
|
+
}
|
|
3858
|
+
if (ch === "{") stringBraceDepth++;
|
|
3859
|
+
else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
|
|
3860
|
+
continue;
|
|
3861
|
+
}
|
|
3862
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
|
|
3863
|
+
contentEnd = i + 1;
|
|
3864
|
+
if (ch === '"') {
|
|
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;
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
if (!sawKey && !inString) return s;
|
|
3880
|
+
let result = s.slice(0, contentEnd);
|
|
3881
|
+
if (inString) {
|
|
3882
|
+
if (escaped) {
|
|
3883
|
+
result = result.slice(0, -1);
|
|
3884
|
+
} else if (endsWithInvalidEscape(result)) {
|
|
3885
|
+
result = result.slice(0, -2);
|
|
3886
|
+
}
|
|
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;
|
|
3898
|
+
}
|
|
3899
|
+
return result;
|
|
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
|
+
}
|
|
3910
|
+
function tryParse(s) {
|
|
3911
|
+
try {
|
|
3912
|
+
return { ok: true, value: JSON.parse(s) };
|
|
3913
|
+
} catch {
|
|
3914
|
+
return { ok: false };
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3700
3918
|
// src/core/streaming-response-builder.ts
|
|
3701
3919
|
function buildResponse(state) {
|
|
3702
3920
|
const content = [];
|
|
@@ -3794,7 +4012,12 @@ function safeJsonOrRaw(s) {
|
|
|
3794
4012
|
try {
|
|
3795
4013
|
return JSON.parse(s);
|
|
3796
4014
|
} catch {
|
|
3797
|
-
|
|
4015
|
+
const repaired = completePartialObject(s);
|
|
4016
|
+
try {
|
|
4017
|
+
return JSON.parse(repaired);
|
|
4018
|
+
} catch {
|
|
4019
|
+
return { _raw: repaired };
|
|
4020
|
+
}
|
|
3798
4021
|
}
|
|
3799
4022
|
}
|
|
3800
4023
|
function handleToolUseStop(state, ev) {
|
|
@@ -4093,6 +4316,7 @@ var HybridCompactor = class {
|
|
|
4093
4316
|
}
|
|
4094
4317
|
async compact(ctx, opts = {}) {
|
|
4095
4318
|
const beforeTokens = this.estimateMessages(ctx.messages);
|
|
4319
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
4096
4320
|
const reductions = [];
|
|
4097
4321
|
const policy = readContextWindowPolicy(ctx);
|
|
4098
4322
|
const preserveK = policy?.preserveK ?? this.preserveK;
|
|
@@ -4108,9 +4332,12 @@ var HybridCompactor = class {
|
|
|
4108
4332
|
ctx.state.replaceMessages(repaired.messages);
|
|
4109
4333
|
}
|
|
4110
4334
|
const afterTokens = this.estimateMessages(ctx.messages);
|
|
4335
|
+
const afterFull = this.estimateFullRequest(ctx);
|
|
4111
4336
|
return {
|
|
4112
4337
|
before: beforeTokens,
|
|
4113
4338
|
after: afterTokens,
|
|
4339
|
+
fullRequestTokensBefore: beforeFull,
|
|
4340
|
+
fullRequestTokensAfter: afterFull,
|
|
4114
4341
|
reductions,
|
|
4115
4342
|
repaired: repaired.report.changed ? {
|
|
4116
4343
|
removedToolUses: repaired.report.removedToolUses,
|
|
@@ -4119,6 +4346,14 @@ var HybridCompactor = class {
|
|
|
4119
4346
|
} : void 0
|
|
4120
4347
|
};
|
|
4121
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
|
+
}
|
|
4122
4357
|
eliseOldToolResults(ctx, preserveK = this.preserveK, eliseThreshold = this.eliseThreshold) {
|
|
4123
4358
|
const messages = ctx.messages;
|
|
4124
4359
|
let pairCount = 0;
|
|
@@ -4264,8 +4499,9 @@ var IntelligentCompactor = class {
|
|
|
4264
4499
|
}
|
|
4265
4500
|
async compact(ctx, opts = {}) {
|
|
4266
4501
|
const beforeTokens = this.estimateTokens(ctx.messages);
|
|
4502
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
4267
4503
|
const reductions = [];
|
|
4268
|
-
const load =
|
|
4504
|
+
const load = beforeFull / this.maxContext;
|
|
4269
4505
|
const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
|
|
4270
4506
|
const saved1 = this.eliseOldToolResults(ctx);
|
|
4271
4507
|
if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
|
|
@@ -4279,9 +4515,12 @@ var IntelligentCompactor = class {
|
|
|
4279
4515
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
4280
4516
|
if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
|
|
4281
4517
|
const afterTokens = this.estimateTokens(ctx.messages);
|
|
4518
|
+
const afterFull = this.estimateFullRequest(ctx);
|
|
4282
4519
|
return {
|
|
4283
4520
|
before: beforeTokens,
|
|
4284
4521
|
after: afterTokens,
|
|
4522
|
+
fullRequestTokensBefore: beforeFull,
|
|
4523
|
+
fullRequestTokensAfter: afterFull,
|
|
4285
4524
|
reductions,
|
|
4286
4525
|
repaired: repaired.report.changed ? {
|
|
4287
4526
|
removedToolUses: repaired.report.removedToolUses,
|
|
@@ -4290,6 +4529,14 @@ var IntelligentCompactor = class {
|
|
|
4290
4529
|
} : void 0
|
|
4291
4530
|
};
|
|
4292
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
|
+
}
|
|
4293
4540
|
async summarizeAncientTurns(ctx) {
|
|
4294
4541
|
const messages = ctx.messages;
|
|
4295
4542
|
const cutoff = Math.max(0, messages.length - this.preserveK * 2);
|
|
@@ -4302,29 +4549,26 @@ var IntelligentCompactor = class {
|
|
|
4302
4549
|
try {
|
|
4303
4550
|
summaryText = await this.callSummarizer(toSummarize, ctx);
|
|
4304
4551
|
} catch {
|
|
4305
|
-
const
|
|
4306
|
-
const filePaths = /* @__PURE__ */ new Set();
|
|
4307
|
-
let userTurns = 0;
|
|
4308
|
-
let assistantTurns = 0;
|
|
4552
|
+
const preservedMessages = [];
|
|
4309
4553
|
for (const m of toSummarize) {
|
|
4310
|
-
if (m.role === "
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
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);
|
|
4318
4568
|
}
|
|
4319
|
-
const text = typeof m.content === "string" ? m.content : "";
|
|
4320
|
-
const matches = text.matchAll(/(?:[\w.,\-/@]+\/)*[\w.,\-/@]+\.\w+/g);
|
|
4321
|
-
for (const m_ of matches) filePaths.add(m_[0]);
|
|
4322
4569
|
}
|
|
4323
|
-
|
|
4324
|
-
if (
|
|
4325
|
-
if (filePaths.size > 0) parts.push(`files: ${[...filePaths].slice(0, 10).join(", ")}`);
|
|
4326
|
-
summaryText = parts.join(" | ");
|
|
4327
|
-
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)`;
|
|
4328
4572
|
}
|
|
4329
4573
|
const summaryMsg = {
|
|
4330
4574
|
role: "system",
|
|
@@ -4570,7 +4814,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
4570
4814
|
const res = await this.provider.complete(req, { signal: ac.signal });
|
|
4571
4815
|
const textBlocks = res.content.filter(isTextBlock);
|
|
4572
4816
|
raw = textBlocks.map((b) => b.text).join("\n").trim();
|
|
4573
|
-
} catch (
|
|
4817
|
+
} catch (_err) {
|
|
4574
4818
|
return this.fallbackSelect(messages, effectiveBudget);
|
|
4575
4819
|
}
|
|
4576
4820
|
return this.parseSelectorOutput(raw, messages.length);
|
|
@@ -4662,15 +4906,24 @@ var SelectiveCompactor = class {
|
|
|
4662
4906
|
}
|
|
4663
4907
|
async compact(ctx, opts = {}) {
|
|
4664
4908
|
const beforeTokens = this.estimateTokens(ctx.messages);
|
|
4909
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
4665
4910
|
const reductions = [];
|
|
4666
|
-
const load =
|
|
4911
|
+
const load = beforeFull / this.maxContext;
|
|
4667
4912
|
const shouldCompact = load >= this.warnThreshold || opts.aggressive;
|
|
4668
4913
|
if (!shouldCompact) {
|
|
4669
4914
|
const saved = this.eliseOldToolResults(ctx);
|
|
4670
4915
|
if (saved > 0) reductions.push({ phase: "elision", saved });
|
|
4671
4916
|
const repair2 = this.repairProtocolAdjacency(ctx);
|
|
4672
4917
|
const afterTokens2 = this.estimateTokens(ctx.messages);
|
|
4673
|
-
|
|
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
|
+
};
|
|
4674
4927
|
}
|
|
4675
4928
|
const savedElision = this.eliseOldToolResults(ctx);
|
|
4676
4929
|
if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
|
|
@@ -4682,7 +4935,23 @@ var SelectiveCompactor = class {
|
|
|
4682
4935
|
}
|
|
4683
4936
|
const repair = this.repairProtocolAdjacency(ctx);
|
|
4684
4937
|
const afterTokens = this.estimateTokens(ctx.messages);
|
|
4685
|
-
|
|
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;
|
|
4686
4955
|
}
|
|
4687
4956
|
repairProtocolAdjacency(ctx) {
|
|
4688
4957
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
@@ -4872,7 +5141,8 @@ var LEVEL_RANK2 = { warn: 0, soft: 1, hard: 2 };
|
|
|
4872
5141
|
var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
4873
5142
|
name = "AutoCompaction";
|
|
4874
5143
|
compactor;
|
|
4875
|
-
|
|
5144
|
+
/** Deprecated. Kept for backward compat with tests that pass simpleEstimator. */
|
|
5145
|
+
_estimator;
|
|
4876
5146
|
warnThreshold;
|
|
4877
5147
|
softThreshold;
|
|
4878
5148
|
hardThreshold;
|
|
@@ -4882,19 +5152,6 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
4882
5152
|
events;
|
|
4883
5153
|
failureMode;
|
|
4884
5154
|
policyProvider;
|
|
4885
|
-
/**
|
|
4886
|
-
* Calibration factor applied to the estimator output. The estimator is now
|
|
4887
|
-
* expected to already include messages + system prompt + tool definitions
|
|
4888
|
-
* (see `estimateRequestTokens.total`), so no overhead boost is needed. Kept
|
|
4889
|
-
* as a constant so a future calibration against real provider tokenization
|
|
4890
|
-
* (BPE vs the chars/3.5 rough estimator) can dial this without touching the
|
|
4891
|
-
* threshold-check math.
|
|
4892
|
-
*
|
|
4893
|
-
* Historical note: was 1.3 when the estimator only counted messages — that
|
|
4894
|
-
* double-counted system+tools once the estimator was upgraded, firing
|
|
4895
|
-
* compaction ~30% earlier than intended (e.g. real 56% load showed as 73%).
|
|
4896
|
-
*/
|
|
4897
|
-
static OVERHEAD_FACTOR = 1;
|
|
4898
5155
|
/**
|
|
4899
5156
|
* Once a compaction attempt reduces nothing (preserveK protects everything,
|
|
4900
5157
|
* no oversized tool_results remain to elide), retrying on every iteration
|
|
@@ -4907,8 +5164,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
4907
5164
|
lastNoopAttempt = null;
|
|
4908
5165
|
/**
|
|
4909
5166
|
* @param compactor Compactor to use for compaction.
|
|
4910
|
-
* @param maxContext
|
|
4911
|
-
* @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).
|
|
4912
5172
|
* @param thresholds Threshold fractions (0-1) of maxContext.
|
|
4913
5173
|
* @param opts Optional behavior. By default, failures at the
|
|
4914
5174
|
* hard threshold throw AGENT_CONTEXT_OVERFLOW so
|
|
@@ -4916,11 +5176,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
4916
5176
|
* provider context overflow. Warn/soft failures
|
|
4917
5177
|
* still emit compaction.failed and continue.
|
|
4918
5178
|
*/
|
|
4919
|
-
constructor(compactor, maxContext,
|
|
5179
|
+
constructor(compactor, maxContext, _estimator, thresholds, optsOrAggressiveOn = {}, events) {
|
|
4920
5180
|
const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
|
|
4921
5181
|
this.compactor = compactor;
|
|
4922
5182
|
this._maxContext = maxContext;
|
|
4923
|
-
this.
|
|
5183
|
+
this._estimator = _estimator;
|
|
4924
5184
|
this.warnThreshold = thresholds.warn;
|
|
4925
5185
|
this.softThreshold = thresholds.soft;
|
|
4926
5186
|
this.hardThreshold = thresholds.hard;
|
|
@@ -4936,8 +5196,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
4936
5196
|
}
|
|
4937
5197
|
handler() {
|
|
4938
5198
|
return async (ctx, next) => {
|
|
4939
|
-
const
|
|
4940
|
-
const tokens = Math.ceil(rawTokens * _AutoCompactionMiddleware.OVERHEAD_FACTOR);
|
|
5199
|
+
const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
|
|
4941
5200
|
const load = tokens / this._maxContext;
|
|
4942
5201
|
const policy = this.policyProvider?.(ctx);
|
|
4943
5202
|
const thresholds = policy?.thresholds ?? {
|
|
@@ -4972,7 +5231,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
4972
5231
|
return delta < _AutoCompactionMiddleware.NOOP_RETRY_DELTA_TOKENS;
|
|
4973
5232
|
}
|
|
4974
5233
|
recordAttempt(level, tokens, report) {
|
|
4975
|
-
const reduced = report.before > report.after;
|
|
5234
|
+
const reduced = (report.fullRequestTokensBefore ?? report.before) > (report.fullRequestTokensAfter ?? report.after);
|
|
4976
5235
|
const repaired = !!report.repaired;
|
|
4977
5236
|
if (reduced || repaired) {
|
|
4978
5237
|
this.lastNoopAttempt = null;
|
|
@@ -6217,17 +6476,7 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
6217
6476
|
startTime = null;
|
|
6218
6477
|
_onThreshold;
|
|
6219
6478
|
/**
|
|
6220
|
-
*
|
|
6221
|
-
* in flight. While a kind is here, further `checkLimit` calls for the
|
|
6222
|
-
* same kind are no-ops — without this dedup, every `recordIteration`
|
|
6223
|
-
* after the limit is reached spawns a fresh decision Promise (until
|
|
6224
|
-
* the first one lands and patches limits), flooding the FleetBus
|
|
6225
|
-
* with redundant threshold events. Cleared in `negotiateExtension`'s
|
|
6226
|
-
* `finally`.
|
|
6227
|
-
*/
|
|
6228
|
-
pendingExtensions = /* @__PURE__ */ new Set();
|
|
6229
|
-
/**
|
|
6230
|
-
* Hard cap on how long `negotiateExtension` waits for the coordinator to
|
|
6479
|
+
* Hard cap on how long `_negotiateExtension` waits for the coordinator to
|
|
6231
6480
|
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
6232
6481
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
6233
6482
|
* leaves the budget over-limit and never enforces anything.
|
|
@@ -6289,38 +6538,83 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
6289
6538
|
* - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
|
|
6290
6539
|
* - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
|
|
6291
6540
|
*/
|
|
6292
|
-
|
|
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 [];
|
|
6293
6571
|
if (!this._onThreshold) {
|
|
6294
|
-
|
|
6572
|
+
const first2 = exceeded[0];
|
|
6573
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
6295
6574
|
}
|
|
6296
6575
|
if (this._mode === "sync") {
|
|
6297
|
-
|
|
6576
|
+
const first2 = exceeded[0];
|
|
6577
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
6298
6578
|
}
|
|
6299
6579
|
const bus = this._events;
|
|
6300
6580
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
6301
|
-
|
|
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);
|
|
6302
6588
|
}
|
|
6303
|
-
|
|
6304
|
-
this.
|
|
6305
|
-
|
|
6306
|
-
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);
|
|
6307
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();
|
|
6308
6601
|
/**
|
|
6309
6602
|
* Drive the threshold handler to a decision. Resolves with `'stop'`
|
|
6310
6603
|
* (signal the runner to abort) or `{ extend: ... }` (limits already
|
|
6311
|
-
* patched in-place; the runner should not abort).
|
|
6312
|
-
*
|
|
6604
|
+
* patched in-place; the runner should not abort). Clears the
|
|
6605
|
+
* per-kind slot in `_pendingNegotiations` in `finally`.
|
|
6313
6606
|
*
|
|
6314
6607
|
* The 'continue' return from a sync handler is treated as
|
|
6315
6608
|
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
6316
6609
|
* a fresh signal.
|
|
6317
6610
|
*/
|
|
6318
|
-
async
|
|
6611
|
+
async _negotiateExtension(kind, exceeded) {
|
|
6319
6612
|
try {
|
|
6613
|
+
const first = exceeded[0];
|
|
6320
6614
|
const result = this._onThreshold({
|
|
6321
|
-
kind,
|
|
6322
|
-
used,
|
|
6323
|
-
limit,
|
|
6615
|
+
kind: first.kind,
|
|
6616
|
+
used: first.used,
|
|
6617
|
+
limit: first.limit,
|
|
6324
6618
|
requestDecision: () => {
|
|
6325
6619
|
const bus = this._events;
|
|
6326
6620
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
@@ -6337,20 +6631,22 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
6337
6631
|
() => respond("stop"),
|
|
6338
6632
|
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
6339
6633
|
);
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
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
|
+
}
|
|
6354
6650
|
});
|
|
6355
6651
|
}
|
|
6356
6652
|
});
|
|
@@ -6376,47 +6672,39 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
6376
6672
|
}
|
|
6377
6673
|
return decision;
|
|
6378
6674
|
} finally {
|
|
6379
|
-
this.
|
|
6675
|
+
this._pendingNegotiations.delete(kind);
|
|
6380
6676
|
}
|
|
6381
6677
|
}
|
|
6382
6678
|
recordIteration() {
|
|
6383
6679
|
this.iterations++;
|
|
6384
|
-
|
|
6385
|
-
void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
|
|
6386
|
-
}
|
|
6680
|
+
void this.checkLimits();
|
|
6387
6681
|
}
|
|
6388
6682
|
recordToolCall() {
|
|
6389
6683
|
this.toolCalls++;
|
|
6390
|
-
|
|
6391
|
-
void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
|
|
6392
|
-
}
|
|
6684
|
+
void this.checkLimits();
|
|
6393
6685
|
}
|
|
6394
6686
|
recordUsage(usage, costUsd = 0) {
|
|
6395
6687
|
this.tokenInput += usage.input;
|
|
6396
6688
|
this.tokenOutput += usage.output;
|
|
6397
6689
|
this.costUsd += costUsd;
|
|
6398
|
-
|
|
6399
|
-
if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
|
|
6400
|
-
void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
|
|
6401
|
-
}
|
|
6402
|
-
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
6403
|
-
void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
|
|
6404
|
-
}
|
|
6690
|
+
void this.checkLimits();
|
|
6405
6691
|
}
|
|
6406
6692
|
/**
|
|
6407
|
-
* Wall-clock budget check. Unlike other limits, timeout
|
|
6408
|
-
*
|
|
6409
|
-
*
|
|
6410
|
-
*
|
|
6411
|
-
*
|
|
6412
|
-
*
|
|
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)
|
|
6413
6702
|
*/
|
|
6414
6703
|
checkTimeout() {
|
|
6415
6704
|
if (this.startTime === null || this.limits.timeoutMs === void 0) return;
|
|
6416
6705
|
const elapsed = Date.now() - this.startTime;
|
|
6417
|
-
if (elapsed
|
|
6418
|
-
|
|
6419
|
-
}
|
|
6706
|
+
if (elapsed <= this.limits.timeoutMs) return;
|
|
6707
|
+
void this.checkLimits(elapsed);
|
|
6420
6708
|
}
|
|
6421
6709
|
/** Returns true if a timeout has occurred without throwing. Useful for races. */
|
|
6422
6710
|
isTimedOut() {
|
|
@@ -9069,11 +9357,46 @@ Working rules:
|
|
|
9069
9357
|
- When in doubt, flag as medium rather than ignoring potential issues`
|
|
9070
9358
|
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
9071
9359
|
};
|
|
9360
|
+
var CRITIC_AGENT = {
|
|
9361
|
+
id: "critic",
|
|
9362
|
+
name: "Critic",
|
|
9363
|
+
role: "critic",
|
|
9364
|
+
prompt: `You are the Critic agent. Your job is to evaluate code quality,
|
|
9365
|
+
architectural decisions, and proposed changes against project conventions,
|
|
9366
|
+
engineering standards, and known quality gates. You do not write code \u2014
|
|
9367
|
+
you judge it.
|
|
9368
|
+
|
|
9369
|
+
Scope:
|
|
9370
|
+
- Evaluate bug severity and fix quality from Bug Hunter reports
|
|
9371
|
+
- Score refactoring plans from Refactor Planner (risk, completeness, trade-offs)
|
|
9372
|
+
- Flag gaps in test coverage, error handling, and edge case coverage
|
|
9373
|
+
- Assess whether a proposed change aligns with existing project patterns
|
|
9374
|
+
- Detect over-engineering or under-engineering relative to the problem scope
|
|
9375
|
+
|
|
9376
|
+
Input format you accept:
|
|
9377
|
+
{ "task": "evaluate | score | review", "subject": "bug_report | refactor_plan | diff", "focus": "correctness | maintainability | risk | all" }
|
|
9378
|
+
|
|
9379
|
+
Output: Markdown critic report:
|
|
9380
|
+
- ## Overall Score (0-10 with rationale)
|
|
9381
|
+
- ## Strengths (what's solid)
|
|
9382
|
+
- ## Weaknesses (what needs work)
|
|
9383
|
+
- ## Specific Concerns (with file:line when applicable)
|
|
9384
|
+
- ## Verdict: **Approve / Needs Revision / Reject**
|
|
9385
|
+
|
|
9386
|
+
Working rules:
|
|
9387
|
+
- Be specific \u2014 "looks fine" is not a review. Cite concrete evidence.
|
|
9388
|
+
- When scoring, explain the delta from a perfect score.
|
|
9389
|
+
- If you have no basis to evaluate a concern, say so rather than speculating.
|
|
9390
|
+
- Prioritise correctness over style; correctness issues block approval.
|
|
9391
|
+
- Score thresholds: \u22657 = Approve, 4-6 = Needs Revision, <4 = Reject`
|
|
9392
|
+
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
9393
|
+
};
|
|
9072
9394
|
var FLEET_ROSTER = {
|
|
9073
9395
|
"audit-log": AUDIT_LOG_AGENT,
|
|
9074
9396
|
"bug-hunter": BUG_HUNTER_AGENT,
|
|
9075
9397
|
"refactor-planner": REFACTOR_PLANNER_AGENT,
|
|
9076
9398
|
"security-scanner": SECURITY_SCANNER_AGENT,
|
|
9399
|
+
"critic": CRITIC_AGENT,
|
|
9077
9400
|
...Object.fromEntries(
|
|
9078
9401
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
|
|
9079
9402
|
)
|
|
@@ -9083,6 +9406,7 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
9083
9406
|
"bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
9084
9407
|
"refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
|
|
9085
9408
|
"security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
9409
|
+
"critic": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
|
|
9086
9410
|
...Object.fromEntries(
|
|
9087
9411
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
|
|
9088
9412
|
)
|
|
@@ -9168,6 +9492,107 @@ FLEET_ROSTER_BUDGETS["goose"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations:
|
|
|
9168
9492
|
...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
|
|
9169
9493
|
});
|
|
9170
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
|
+
|
|
9171
9596
|
// src/coordination/multi-agent-coordinator.ts
|
|
9172
9597
|
var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
9173
9598
|
coordinatorId;
|
|
@@ -9175,6 +9600,15 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
9175
9600
|
runner;
|
|
9176
9601
|
fleetBus;
|
|
9177
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();
|
|
9178
9612
|
pendingTasks = [];
|
|
9179
9613
|
completedResults = [];
|
|
9180
9614
|
totalIterations = 0;
|
|
@@ -9223,7 +9657,26 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
9223
9657
|
this.config.maxConcurrent = Math.floor(n);
|
|
9224
9658
|
this.tryDispatchNext();
|
|
9225
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
|
+
}
|
|
9226
9678
|
async spawn(subagent) {
|
|
9679
|
+
subagent = this.withNickname(subagent);
|
|
9227
9680
|
const id = subagent.id || randomUUID();
|
|
9228
9681
|
if (this.subagents.has(id)) {
|
|
9229
9682
|
throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
|
|
@@ -9817,12 +10270,12 @@ var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
|
|
|
9817
10270
|
var ParallelEternalEngine = class {
|
|
9818
10271
|
constructor(opts) {
|
|
9819
10272
|
this.opts = opts;
|
|
9820
|
-
this.goalPath = goalFilePath(opts.projectRoot);
|
|
10273
|
+
this.goalPath = opts.goalPath ?? goalFilePath(opts.projectRoot);
|
|
9821
10274
|
this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
|
|
9822
10275
|
this.timeoutMs = opts.iterationTimeoutMs ?? 3e5;
|
|
9823
10276
|
this.dispatchEnabled = opts.dispatch !== false;
|
|
9824
10277
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
9825
|
-
this.agentFactory = opts.subagentFactory ?? (async (
|
|
10278
|
+
this.agentFactory = opts.subagentFactory ?? (async (_config) => ({
|
|
9826
10279
|
agent: this.opts.agent,
|
|
9827
10280
|
events: this.opts.agent.events
|
|
9828
10281
|
}));
|
|
@@ -9916,7 +10369,6 @@ var ParallelEternalEngine = class {
|
|
|
9916
10369
|
}
|
|
9917
10370
|
const tasks = await this.decomposeGoal(goal);
|
|
9918
10371
|
if (!tasks || tasks.length === 0) {
|
|
9919
|
-
await sleep2(5e3);
|
|
9920
10372
|
return false;
|
|
9921
10373
|
}
|
|
9922
10374
|
const fanOut = await this.fanOut(goal, tasks);
|
|
@@ -9937,6 +10389,7 @@ var ParallelEternalEngine = class {
|
|
|
9937
10389
|
});
|
|
9938
10390
|
if (fanOut.goalComplete) {
|
|
9939
10391
|
this.stopRequested = true;
|
|
10392
|
+
this.state = "stopped";
|
|
9940
10393
|
return true;
|
|
9941
10394
|
}
|
|
9942
10395
|
await this.maybeCompact();
|
|
@@ -10117,7 +10570,11 @@ ${lastFew}` : "No prior iterations.",
|
|
|
10117
10570
|
} finally {
|
|
10118
10571
|
clearTimeout(timer);
|
|
10119
10572
|
}
|
|
10120
|
-
} catch {
|
|
10573
|
+
} catch (err) {
|
|
10574
|
+
this.opts.onError?.(
|
|
10575
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
10576
|
+
this.consecutiveFailures
|
|
10577
|
+
);
|
|
10121
10578
|
return [];
|
|
10122
10579
|
}
|
|
10123
10580
|
}
|
|
@@ -10478,7 +10935,11 @@ Working rules:
|
|
|
10478
10935
|
thrashing, terminate it rather than letting cost climb silently.
|
|
10479
10936
|
6. Never claim a subagent's work as your own without verifying it. If a
|
|
10480
10937
|
result looks wrong, ask_subagent for clarification before passing it
|
|
10481
|
-
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.`;
|
|
10482
10943
|
var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
|
|
10483
10944
|
a specific slice of a larger plan \u2014 do that slice well and report back.
|
|
10484
10945
|
|
|
@@ -10623,14 +11084,12 @@ var FleetBus = class {
|
|
|
10623
11084
|
};
|
|
10624
11085
|
var FleetUsageAggregator = class {
|
|
10625
11086
|
constructor(bus, priceLookup, metaLookup) {
|
|
10626
|
-
this.bus = bus;
|
|
10627
11087
|
this.priceLookup = priceLookup;
|
|
10628
11088
|
this.metaLookup = metaLookup;
|
|
10629
11089
|
bus.filter("provider.response", (e) => this.onProviderResponse(e));
|
|
10630
11090
|
bus.filter("tool.executed", (e) => this.onToolExecuted(e));
|
|
10631
11091
|
bus.filter("iteration.started", (e) => this.onIterationStarted(e));
|
|
10632
11092
|
}
|
|
10633
|
-
bus;
|
|
10634
11093
|
priceLookup;
|
|
10635
11094
|
metaLookup;
|
|
10636
11095
|
perSubagent = /* @__PURE__ */ new Map();
|
|
@@ -10868,7 +11327,7 @@ function makeRollUpTool(director) {
|
|
|
10868
11327
|
function makeTerminateTool(director) {
|
|
10869
11328
|
return {
|
|
10870
11329
|
name: "terminate_subagent",
|
|
10871
|
-
description:
|
|
11330
|
+
description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
|
|
10872
11331
|
permission: "auto",
|
|
10873
11332
|
mutating: true,
|
|
10874
11333
|
inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
|
|
@@ -10879,6 +11338,19 @@ function makeTerminateTool(director) {
|
|
|
10879
11338
|
}
|
|
10880
11339
|
};
|
|
10881
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
|
+
}
|
|
10882
11354
|
function makeFleetStatusTool(director) {
|
|
10883
11355
|
return {
|
|
10884
11356
|
name: "fleet_status",
|
|
@@ -10922,7 +11394,6 @@ function makeFleetSessionTool(director) {
|
|
|
10922
11394
|
type: "object",
|
|
10923
11395
|
properties: {
|
|
10924
11396
|
subagentId: { type: "string", description: "Subagent id to read the transcript of." },
|
|
10925
|
-
/** Number of trailing lines to return (last N JSONL lines). Default: all. */
|
|
10926
11397
|
tail: { type: "number", description: "Number of trailing JSONL lines to return. Omit for the full transcript." }
|
|
10927
11398
|
},
|
|
10928
11399
|
required: ["subagentId"]
|
|
@@ -10958,8 +11429,6 @@ function makeFleetHealthTool(director) {
|
|
|
10958
11429
|
id: s.id,
|
|
10959
11430
|
status: s.status,
|
|
10960
11431
|
lastEventAt: usage?.lastEventAt,
|
|
10961
|
-
// Budget pressure: fraction of each limit consumed if we have it.
|
|
10962
|
-
// BudgetWarning events carry used/limit ratios; surface them here.
|
|
10963
11432
|
budgetPressure: {
|
|
10964
11433
|
iterations: usage?.iterations,
|
|
10965
11434
|
toolCalls: usage?.toolCalls,
|
|
@@ -10971,16 +11440,612 @@ function makeFleetHealthTool(director) {
|
|
|
10971
11440
|
}
|
|
10972
11441
|
};
|
|
10973
11442
|
}
|
|
11443
|
+
function makeCollabDebugTool(director) {
|
|
11444
|
+
return {
|
|
11445
|
+
name: "collab_debug",
|
|
11446
|
+
description: "Start a collaborative debugging session: BugHunter, RefactorPlanner, and Critic run in parallel on the same target files. BugHunter finds bugs and emits bug.found events. RefactorPlanner listens for bug.found and emits refactor.plan events. Critic evaluates both and emits critic.evaluation events. Returns a structured report with overall verdict (approve / needs_revision / reject).",
|
|
11447
|
+
permission: "auto",
|
|
11448
|
+
mutating: false,
|
|
11449
|
+
inputSchema: {
|
|
11450
|
+
type: "object",
|
|
11451
|
+
properties: {
|
|
11452
|
+
targetPaths: {
|
|
11453
|
+
type: "array",
|
|
11454
|
+
items: { type: "string" },
|
|
11455
|
+
description: "File paths / glob patterns to scan for bugs."
|
|
11456
|
+
},
|
|
11457
|
+
timeoutMs: {
|
|
11458
|
+
type: "number",
|
|
11459
|
+
description: "Timeout in ms. Default: 600000 (10 minutes)."
|
|
11460
|
+
}
|
|
11461
|
+
},
|
|
11462
|
+
required: ["targetPaths"]
|
|
11463
|
+
},
|
|
11464
|
+
async execute(input) {
|
|
11465
|
+
const i = input;
|
|
11466
|
+
if (!i.targetPaths?.length) {
|
|
11467
|
+
return { error: "collab_debug: targetPaths is required and must be non-empty." };
|
|
11468
|
+
}
|
|
11469
|
+
const options = {
|
|
11470
|
+
targetPaths: i.targetPaths,
|
|
11471
|
+
timeoutMs: i.timeoutMs
|
|
11472
|
+
};
|
|
11473
|
+
try {
|
|
11474
|
+
const report = await director.spawnCollab(options);
|
|
11475
|
+
return {
|
|
11476
|
+
sessionId: report.sessionId,
|
|
11477
|
+
overallVerdict: report.overallVerdict,
|
|
11478
|
+
bugCount: report.bugs.length,
|
|
11479
|
+
planCount: report.refactorPlans.length,
|
|
11480
|
+
evaluationCount: report.evaluations.length,
|
|
11481
|
+
summary: report.summary,
|
|
11482
|
+
bugs: report.bugs,
|
|
11483
|
+
refactorPlans: report.refactorPlans,
|
|
11484
|
+
evaluations: report.evaluations
|
|
11485
|
+
};
|
|
11486
|
+
} catch (err) {
|
|
11487
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11488
|
+
return { error: "collab_debug failed: " + msg };
|
|
11489
|
+
}
|
|
11490
|
+
}
|
|
11491
|
+
};
|
|
11492
|
+
}
|
|
11493
|
+
function makeFleetEmitTool(director) {
|
|
11494
|
+
return {
|
|
11495
|
+
name: "fleet_emit",
|
|
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.",
|
|
11497
|
+
permission: "auto",
|
|
11498
|
+
mutating: false,
|
|
11499
|
+
inputSchema: {
|
|
11500
|
+
type: "object",
|
|
11501
|
+
properties: {
|
|
11502
|
+
type: {
|
|
11503
|
+
type: "string",
|
|
11504
|
+
description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
|
|
11505
|
+
},
|
|
11506
|
+
payload: {
|
|
11507
|
+
type: "object",
|
|
11508
|
+
description: "Event payload. Structure depends on event type. Use null if no payload."
|
|
11509
|
+
}
|
|
11510
|
+
},
|
|
11511
|
+
required: ["type"]
|
|
11512
|
+
},
|
|
11513
|
+
async execute(input) {
|
|
11514
|
+
const i = input;
|
|
11515
|
+
director.fleet.emit({
|
|
11516
|
+
subagentId: director.id,
|
|
11517
|
+
ts: Date.now(),
|
|
11518
|
+
type: i.type,
|
|
11519
|
+
payload: i.payload ?? {}
|
|
11520
|
+
});
|
|
11521
|
+
return { ok: true, event: i.type };
|
|
11522
|
+
}
|
|
11523
|
+
};
|
|
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
|
+
}
|
|
11538
|
+
var CollabSession = class extends EventEmitter {
|
|
11539
|
+
sessionId;
|
|
11540
|
+
options;
|
|
11541
|
+
snapshot;
|
|
11542
|
+
director;
|
|
11543
|
+
fleetBus;
|
|
11544
|
+
subagentIds = /* @__PURE__ */ new Map();
|
|
11545
|
+
// role → subagentId
|
|
11546
|
+
bugs = /* @__PURE__ */ new Map();
|
|
11547
|
+
plans = /* @__PURE__ */ new Map();
|
|
11548
|
+
evaluations = /* @__PURE__ */ new Map();
|
|
11549
|
+
disposers = new Array();
|
|
11550
|
+
settled = false;
|
|
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;
|
|
11560
|
+
constructor(director, fleetBus, options) {
|
|
11561
|
+
super();
|
|
11562
|
+
this.sessionId = randomUUID();
|
|
11563
|
+
this.options = options;
|
|
11564
|
+
this.director = director;
|
|
11565
|
+
this.fleetBus = fleetBus;
|
|
11566
|
+
this.timeoutMs = options.timeoutMs ?? 10 * 60 * 1e3;
|
|
11567
|
+
if (options.prebuiltSnapshot) {
|
|
11568
|
+
this.snapshot = options.prebuiltSnapshot;
|
|
11569
|
+
} else {
|
|
11570
|
+
this.snapshot = {
|
|
11571
|
+
id: this.sessionId,
|
|
11572
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11573
|
+
files: []
|
|
11574
|
+
};
|
|
11575
|
+
}
|
|
11576
|
+
}
|
|
11577
|
+
get id() {
|
|
11578
|
+
return this.sessionId;
|
|
11579
|
+
}
|
|
11580
|
+
getSessionAlerts() {
|
|
11581
|
+
return [...this.alerts];
|
|
11582
|
+
}
|
|
11583
|
+
isCancelled() {
|
|
11584
|
+
return this.cancelled;
|
|
11585
|
+
}
|
|
11586
|
+
/**
|
|
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.
|
|
11590
|
+
*/
|
|
11591
|
+
getSubagentIds() {
|
|
11592
|
+
return new Map(this.subagentIds);
|
|
11593
|
+
}
|
|
11594
|
+
async buildSnapshot() {
|
|
11595
|
+
if (this.snapshot.files.length > 0) return this.snapshot;
|
|
11596
|
+
for (const filePath of this.options.targetPaths) {
|
|
11597
|
+
try {
|
|
11598
|
+
const content = await fsp.readFile(filePath, "utf8");
|
|
11599
|
+
const ext = filePath.split(".").pop() ?? "";
|
|
11600
|
+
const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
|
|
11601
|
+
this.snapshot.files.push({ path: filePath, content, language });
|
|
11602
|
+
} catch {
|
|
11603
|
+
this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
|
|
11604
|
+
}
|
|
11605
|
+
}
|
|
11606
|
+
return this.snapshot;
|
|
11607
|
+
}
|
|
11608
|
+
/**
|
|
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).
|
|
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
|
+
}
|
|
11633
|
+
async start() {
|
|
11634
|
+
if (this.settled) throw new Error("session already settled");
|
|
11635
|
+
this.settled = true;
|
|
11636
|
+
await this.buildSnapshot();
|
|
11637
|
+
this.wireFleetBus();
|
|
11638
|
+
const [bugHunterId, refactorPlannerId, criticId] = await Promise.all([
|
|
11639
|
+
this.spawnAgent("bug-hunter", this.buildBugHunterTask()),
|
|
11640
|
+
this.spawnAgent("refactor-planner", this.buildRefactorPlannerTask()),
|
|
11641
|
+
this.spawnAgent("critic", this.buildCriticTask())
|
|
11642
|
+
]);
|
|
11643
|
+
this.subagentIds.set("bug-hunter", bugHunterId);
|
|
11644
|
+
this.subagentIds.set("refactor-planner", refactorPlannerId);
|
|
11645
|
+
this.subagentIds.set("critic", criticId);
|
|
11646
|
+
const timeout = new Promise((_, reject) => {
|
|
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);
|
|
11651
|
+
});
|
|
11652
|
+
let results;
|
|
11653
|
+
try {
|
|
11654
|
+
results = await Promise.race([
|
|
11655
|
+
Promise.all([
|
|
11656
|
+
this.director.awaitTasks([bugHunterId]),
|
|
11657
|
+
this.director.awaitTasks([refactorPlannerId]),
|
|
11658
|
+
this.director.awaitTasks([criticId])
|
|
11659
|
+
]),
|
|
11660
|
+
timeout
|
|
11661
|
+
]);
|
|
11662
|
+
} catch (err) {
|
|
11663
|
+
if (this._timeoutTimer) {
|
|
11664
|
+
clearTimeout(this._timeoutTimer);
|
|
11665
|
+
this._timeoutTimer = void 0;
|
|
11666
|
+
}
|
|
11667
|
+
this.cleanup();
|
|
11668
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
11669
|
+
this.emit("session.error", error);
|
|
11670
|
+
throw error;
|
|
11671
|
+
}
|
|
11672
|
+
for (const result of results.flat()) {
|
|
11673
|
+
await this.parseAndEmit(result);
|
|
11674
|
+
}
|
|
11675
|
+
const report = this.assembleReport();
|
|
11676
|
+
this.cleanup();
|
|
11677
|
+
this.emit("session.done", report);
|
|
11678
|
+
return report;
|
|
11679
|
+
}
|
|
11680
|
+
async parseAndEmit(result) {
|
|
11681
|
+
if (result.status !== "success" || result.result == null) return;
|
|
11682
|
+
const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
|
|
11683
|
+
for (const obj of this.extractJsonObjects(text)) {
|
|
11684
|
+
const type = "finding" in obj ? "bug.found" : "plan" in obj ? "refactor.plan" : "evaluation" in obj ? "critic.evaluation" : null;
|
|
11685
|
+
if (!type) continue;
|
|
11686
|
+
this.fleetBus.emit({
|
|
11687
|
+
subagentId: result.subagentId,
|
|
11688
|
+
taskId: result.taskId,
|
|
11689
|
+
ts: Date.now(),
|
|
11690
|
+
type,
|
|
11691
|
+
payload: obj
|
|
11692
|
+
});
|
|
11693
|
+
}
|
|
11694
|
+
}
|
|
11695
|
+
extractJsonObjects(text) {
|
|
11696
|
+
const objects = [];
|
|
11697
|
+
let depth = 0;
|
|
11698
|
+
let start = -1;
|
|
11699
|
+
let inString = false;
|
|
11700
|
+
let escaped = false;
|
|
11701
|
+
for (let i = 0; i < text.length; i++) {
|
|
11702
|
+
const ch = text[i];
|
|
11703
|
+
if (inString) {
|
|
11704
|
+
if (escaped) escaped = false;
|
|
11705
|
+
else if (ch === "\\") escaped = true;
|
|
11706
|
+
else if (ch === '"') inString = false;
|
|
11707
|
+
continue;
|
|
11708
|
+
}
|
|
11709
|
+
if (ch === '"') {
|
|
11710
|
+
inString = true;
|
|
11711
|
+
} else if (ch === "{") {
|
|
11712
|
+
if (depth === 0) start = i;
|
|
11713
|
+
depth++;
|
|
11714
|
+
} else if (ch === "}" && depth > 0) {
|
|
11715
|
+
depth--;
|
|
11716
|
+
if (depth === 0 && start >= 0) {
|
|
11717
|
+
const candidate = text.slice(start, i + 1);
|
|
11718
|
+
try {
|
|
11719
|
+
const parsed = JSON.parse(candidate);
|
|
11720
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
11721
|
+
objects.push(parsed);
|
|
11722
|
+
}
|
|
11723
|
+
} catch {
|
|
11724
|
+
}
|
|
11725
|
+
start = -1;
|
|
11726
|
+
}
|
|
11727
|
+
}
|
|
11728
|
+
}
|
|
11729
|
+
return objects;
|
|
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
|
+
}
|
|
11742
|
+
async spawnAgent(role, taskBrief) {
|
|
11743
|
+
const budget = this.budgetForRole(role);
|
|
11744
|
+
const cfg = {
|
|
11745
|
+
id: `${role}-${this.sessionId}`,
|
|
11746
|
+
name: role,
|
|
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
|
|
11752
|
+
};
|
|
11753
|
+
const subagentId = await this.director.spawn(cfg);
|
|
11754
|
+
await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
|
|
11755
|
+
return subagentId;
|
|
11756
|
+
}
|
|
11757
|
+
buildBugHunterTask() {
|
|
11758
|
+
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
11759
|
+
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
11760
|
+
${f.content}`).join("\n\n");
|
|
11761
|
+
return `You are BugHunter. Scan the following files for bugs and code smells.
|
|
11762
|
+
|
|
11763
|
+
Target files:
|
|
11764
|
+
${fileContents}
|
|
11765
|
+
|
|
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.`;
|
|
11773
|
+
}
|
|
11774
|
+
buildRefactorPlannerTask() {
|
|
11775
|
+
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
11776
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
11777
|
+
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
11778
|
+
${f.content}`).join("\n\n");
|
|
11779
|
+
return `You are RefactorPlanner. Plan refactorings for the following files.
|
|
11780
|
+
|
|
11781
|
+
Target files:
|
|
11782
|
+
${fileContents}
|
|
11783
|
+
|
|
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.`;
|
|
11793
|
+
}
|
|
11794
|
+
buildCriticTask() {
|
|
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`;
|
|
11798
|
+
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
11799
|
+
${f.content}`).join("\n\n");
|
|
11800
|
+
return `You are Critic. Evaluate bug findings and refactor plans.
|
|
11801
|
+
|
|
11802
|
+
Target files:
|
|
11803
|
+
${fileContents}
|
|
11804
|
+
|
|
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.`;
|
|
11815
|
+
}
|
|
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);
|
|
11924
|
+
const d1 = this.fleetBus.filter("bug.found", (e) => {
|
|
11925
|
+
const payload = e.payload;
|
|
11926
|
+
if (payload?.finding) {
|
|
11927
|
+
this.bugs.set(payload.finding.id, payload.finding);
|
|
11928
|
+
this.emit("bug.found", payload);
|
|
11929
|
+
}
|
|
11930
|
+
});
|
|
11931
|
+
this.disposers.push(d1);
|
|
11932
|
+
const d2 = this.fleetBus.filter("refactor.plan", (e) => {
|
|
11933
|
+
const payload = e.payload;
|
|
11934
|
+
if (payload?.plan) {
|
|
11935
|
+
this.plans.set(payload.plan.id, payload.plan);
|
|
11936
|
+
this.emit("refactor.plan", payload);
|
|
11937
|
+
}
|
|
11938
|
+
});
|
|
11939
|
+
this.disposers.push(d2);
|
|
11940
|
+
const d3 = this.fleetBus.filter("critic.evaluation", (e) => {
|
|
11941
|
+
const payload = e.payload;
|
|
11942
|
+
if (payload?.evaluation) {
|
|
11943
|
+
this.evaluations.set(payload.evaluation.id, payload.evaluation);
|
|
11944
|
+
this.emit("critic.evaluation", payload);
|
|
11945
|
+
}
|
|
11946
|
+
});
|
|
11947
|
+
this.disposers.push(d3);
|
|
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
|
+
}
|
|
11956
|
+
assembleReport() {
|
|
11957
|
+
const bugList = Array.from(this.bugs.values());
|
|
11958
|
+
const planList = Array.from(this.plans.values());
|
|
11959
|
+
const evalList = Array.from(this.evaluations.values());
|
|
11960
|
+
let disposition = "completed";
|
|
11961
|
+
if (this.cancelled) disposition = "cancelled";
|
|
11962
|
+
const verdictOrder = {
|
|
11963
|
+
approve: 0,
|
|
11964
|
+
needs_revision: 1,
|
|
11965
|
+
reject: 2
|
|
11966
|
+
};
|
|
11967
|
+
const overallVerdict = evalList.reduce(
|
|
11968
|
+
(worst, eval_) => {
|
|
11969
|
+
const w = verdictOrder[worst];
|
|
11970
|
+
const c = verdictOrder[eval_.verdict];
|
|
11971
|
+
return c > w ? eval_.verdict : worst;
|
|
11972
|
+
},
|
|
11973
|
+
"approve"
|
|
11974
|
+
);
|
|
11975
|
+
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
|
|
11976
|
+
return {
|
|
11977
|
+
sessionId: this.sessionId,
|
|
11978
|
+
startedAt: this.snapshot.createdAt,
|
|
11979
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11980
|
+
targetPaths: this.options.targetPaths,
|
|
11981
|
+
disposition,
|
|
11982
|
+
bugs: bugList,
|
|
11983
|
+
refactorPlans: planList,
|
|
11984
|
+
evaluations: evalList,
|
|
11985
|
+
alerts: [...this.alerts],
|
|
11986
|
+
overallVerdict,
|
|
11987
|
+
summary
|
|
11988
|
+
};
|
|
11989
|
+
}
|
|
11990
|
+
buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
|
|
11991
|
+
const lines = [
|
|
11992
|
+
`## Collaborative Debugging Report \u2014 ${this.sessionId}`,
|
|
11993
|
+
"",
|
|
11994
|
+
`**Target:** ${this.options.targetPaths.join(", ")}`,
|
|
11995
|
+
`**Disposition:** ${disposition.toUpperCase()}`,
|
|
11996
|
+
`**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
|
|
11997
|
+
""
|
|
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
|
+
}
|
|
12006
|
+
if (bugs.length > 0) {
|
|
12007
|
+
lines.push("### Bugs Found", "");
|
|
12008
|
+
for (const b of bugs) {
|
|
12009
|
+
lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
|
|
12010
|
+
}
|
|
12011
|
+
lines.push("");
|
|
12012
|
+
}
|
|
12013
|
+
if (plans.length > 0) {
|
|
12014
|
+
lines.push("### Refactor Plans", "");
|
|
12015
|
+
for (const p of plans) {
|
|
12016
|
+
lines.push(`- **Phase plan** (risk: ${p.riskScore}, ~${p.estimatedChangeCount} changes)`);
|
|
12017
|
+
for (const phase of p.phases) {
|
|
12018
|
+
lines.push(` - Phase ${phase.number}: ${phase.title} [${phase.risk}]`);
|
|
12019
|
+
}
|
|
12020
|
+
}
|
|
12021
|
+
lines.push("");
|
|
12022
|
+
}
|
|
12023
|
+
if (evals.length > 0) {
|
|
12024
|
+
lines.push("### Critic Evaluations", "");
|
|
12025
|
+
for (const e of evals) {
|
|
12026
|
+
lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
|
|
12027
|
+
for (const c of e.concerns) {
|
|
12028
|
+
if (c.severity === "blocking") lines.push(` - ${c.description}`);
|
|
12029
|
+
}
|
|
12030
|
+
}
|
|
12031
|
+
lines.push("");
|
|
12032
|
+
}
|
|
12033
|
+
return lines.join("\n");
|
|
12034
|
+
}
|
|
12035
|
+
cleanup() {
|
|
12036
|
+
for (const dispose of this.disposers) dispose();
|
|
12037
|
+
this.disposers.length = 0;
|
|
12038
|
+
}
|
|
12039
|
+
};
|
|
10974
12040
|
|
|
10975
12041
|
// src/coordination/director.ts
|
|
10976
12042
|
var FleetSpawnBudgetError = class extends Error {
|
|
10977
12043
|
kind;
|
|
10978
12044
|
limit;
|
|
10979
12045
|
observed;
|
|
10980
|
-
constructor(kind, limit, observed) {
|
|
10981
|
-
|
|
10982
|
-
|
|
10983
|
-
);
|
|
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);
|
|
10984
12049
|
this.name = "FleetSpawnBudgetError";
|
|
10985
12050
|
this.kind = kind;
|
|
10986
12051
|
this.limit = limit;
|
|
@@ -11001,6 +12066,20 @@ var FleetCostCapError = class extends Error {
|
|
|
11001
12066
|
this.observed = observed;
|
|
11002
12067
|
}
|
|
11003
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
|
+
};
|
|
11004
12083
|
var Director = class {
|
|
11005
12084
|
/** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
|
|
11006
12085
|
get coordinatorId() {
|
|
@@ -11017,6 +12096,25 @@ var Director = class {
|
|
|
11017
12096
|
* injected; otherwise own FleetUsageAggregator.
|
|
11018
12097
|
*/
|
|
11019
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
|
+
}
|
|
11020
12118
|
/**
|
|
11021
12119
|
* Optional fleet-level policy container. When provided the Director
|
|
11022
12120
|
* delegates spawn budgeting, manifest entries, and checkpointing to it
|
|
@@ -11049,6 +12147,8 @@ var Director = class {
|
|
|
11049
12147
|
subagentBridges = /* @__PURE__ */ new Map();
|
|
11050
12148
|
/** Tracks per-spawn config + assigned task ids for manifest writing. */
|
|
11051
12149
|
manifestEntries = /* @__PURE__ */ new Map();
|
|
12150
|
+
/** Tracks assigned nicknames so the same name is never reused in one fleet. */
|
|
12151
|
+
_usedNicknames = /* @__PURE__ */ new Set();
|
|
11052
12152
|
manifestPath;
|
|
11053
12153
|
roster;
|
|
11054
12154
|
directorPreamble;
|
|
@@ -11100,6 +12200,23 @@ var Director = class {
|
|
|
11100
12200
|
taskCompletedListener = null;
|
|
11101
12201
|
/** Optional LLM classifier for smart dispatch. Passed from options. */
|
|
11102
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();
|
|
11103
12220
|
constructor(opts) {
|
|
11104
12221
|
this.id = opts.config.coordinatorId || randomUUID();
|
|
11105
12222
|
this.manifestPath = opts.manifestPath;
|
|
@@ -11115,6 +12232,8 @@ var Director = class {
|
|
|
11115
12232
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
11116
12233
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
11117
12234
|
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
|
|
12235
|
+
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
12236
|
+
this.maxContext = opts.maxContext ?? 128e3;
|
|
11118
12237
|
this.sessionsRoot = opts.sessionsRoot;
|
|
11119
12238
|
this.directorRunId = opts.directorRunId ?? this.id;
|
|
11120
12239
|
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
|
|
@@ -11142,7 +12261,7 @@ var Director = class {
|
|
|
11142
12261
|
this.fleet = new FleetBus();
|
|
11143
12262
|
this.usage = new FleetUsageAggregator(
|
|
11144
12263
|
this.fleet,
|
|
11145
|
-
(
|
|
12264
|
+
(_id, provider, model) => {
|
|
11146
12265
|
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
11147
12266
|
return void 0;
|
|
11148
12267
|
},
|
|
@@ -11204,6 +12323,9 @@ var Director = class {
|
|
|
11204
12323
|
});
|
|
11205
12324
|
this.fleet.filter("budget.threshold_reached", (e) => {
|
|
11206
12325
|
const payload = e.payload;
|
|
12326
|
+
if (e.subagentId.startsWith("bug-hunter-") || e.subagentId.startsWith("refactor-planner-") || e.subagentId.startsWith("critic-")) {
|
|
12327
|
+
return;
|
|
12328
|
+
}
|
|
11207
12329
|
if (payload.kind === "timeout") {
|
|
11208
12330
|
const progress = progressBySubagent.get(e.subagentId) ?? 0;
|
|
11209
12331
|
const lastProgress = lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
@@ -11282,6 +12404,107 @@ var Director = class {
|
|
|
11282
12404
|
extensionsFor(subagentId) {
|
|
11283
12405
|
return this.extendTotals.get(subagentId) ?? 0;
|
|
11284
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
|
+
}
|
|
11285
12508
|
/** Best-effort session-writer append. Swallows failures — the director
|
|
11286
12509
|
* must not break a fleet run because the session JSONL handle closed. */
|
|
11287
12510
|
async appendSessionEvent(event) {
|
|
@@ -11320,12 +12543,21 @@ var Director = class {
|
|
|
11320
12543
|
* it the `cost` column in `usage.snapshot()` stays at 0.
|
|
11321
12544
|
*/
|
|
11322
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
|
+
}
|
|
11323
12554
|
if (this.fleetManager) {
|
|
11324
12555
|
const rejection = this.fleetManager.canSpawn(config);
|
|
11325
12556
|
if (rejection) {
|
|
11326
12557
|
if (rejection.kind === "max_spawn_depth") throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
|
|
11327
12558
|
if (rejection.kind === "max_spawns") throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
|
|
11328
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);
|
|
11329
12561
|
}
|
|
11330
12562
|
} else {
|
|
11331
12563
|
if (this.spawnDepth >= this.maxSpawnDepth) {
|
|
@@ -11340,11 +12572,29 @@ var Director = class {
|
|
|
11340
12572
|
throw new FleetCostCapError(this.maxFleetCostUsd, totalCost);
|
|
11341
12573
|
}
|
|
11342
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
|
+
}
|
|
11343
12581
|
}
|
|
11344
12582
|
let result;
|
|
12583
|
+
const needsNickname = config.name === config.role || !config.name || config.name === "subagent" || config.name === "adhoc";
|
|
12584
|
+
if (needsNickname) {
|
|
12585
|
+
const role = config.role ?? "subagent";
|
|
12586
|
+
if (this.fleetManager) {
|
|
12587
|
+
this.fleetManager.assignNicknameAndRecord("", config, priceLookup);
|
|
12588
|
+
} else {
|
|
12589
|
+
config.name = assignNickname(role, this._usedNicknames);
|
|
12590
|
+
this._usedNicknames.add(config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-"));
|
|
12591
|
+
}
|
|
12592
|
+
}
|
|
11345
12593
|
result = await this.coordinator.spawn(config);
|
|
11346
12594
|
if (this.fleetManager) {
|
|
11347
|
-
|
|
12595
|
+
if (!needsNickname) {
|
|
12596
|
+
this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
|
|
12597
|
+
}
|
|
11348
12598
|
} else {
|
|
11349
12599
|
this.spawnCount += 1;
|
|
11350
12600
|
this.subagentMeta.set(result.subagentId, {
|
|
@@ -11555,6 +12805,24 @@ var Director = class {
|
|
|
11555
12805
|
*/
|
|
11556
12806
|
async assign(task) {
|
|
11557
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
|
+
}
|
|
11558
12826
|
if (task.subagentId) {
|
|
11559
12827
|
if (this.fleetManager) {
|
|
11560
12828
|
this.fleetManager.addTaskToSubagent(task.subagentId, taskWithId.id);
|
|
@@ -11777,10 +13045,14 @@ var Director = class {
|
|
|
11777
13045
|
makeAskTool(this),
|
|
11778
13046
|
makeRollUpTool(this),
|
|
11779
13047
|
makeTerminateTool(this),
|
|
13048
|
+
makeTerminateAllTool(this),
|
|
11780
13049
|
makeFleetStatusTool(this),
|
|
11781
13050
|
makeFleetUsageTool(this),
|
|
11782
13051
|
makeFleetSessionTool(this),
|
|
11783
|
-
makeFleetHealthTool(this)
|
|
13052
|
+
makeFleetHealthTool(this),
|
|
13053
|
+
makeCollabDebugTool(this),
|
|
13054
|
+
makeFleetEmitTool(this),
|
|
13055
|
+
makeWorkCompleteTool(this)
|
|
11784
13056
|
];
|
|
11785
13057
|
return t;
|
|
11786
13058
|
}
|
|
@@ -11792,6 +13064,25 @@ var Director = class {
|
|
|
11792
13064
|
async acquireCheckpointLock() {
|
|
11793
13065
|
return this.stateCheckpoint ? this.stateCheckpoint.acquireLock() : true;
|
|
11794
13066
|
}
|
|
13067
|
+
/**
|
|
13068
|
+
* Start a collaborative debugging session: BugHunter, RefactorPlanner,
|
|
13069
|
+
* and Critic run in parallel on the same target files, with findings
|
|
13070
|
+
* flowing through the FleetBus (bug.found → refactor.plan →
|
|
13071
|
+
* critic.evaluation). Returns a structured CollabDebugReport when all
|
|
13072
|
+
* three agents complete or the session times out.
|
|
13073
|
+
*/
|
|
13074
|
+
async spawnCollab(options) {
|
|
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));
|
|
13084
|
+
return session.start();
|
|
13085
|
+
}
|
|
11795
13086
|
/**
|
|
11796
13087
|
* Resume from a prior checkpoint snapshot (loaded via
|
|
11797
13088
|
* `loadDirectorState()`). Re-attach to the fleet mid-flight so
|
|
@@ -14025,10 +15316,10 @@ var AISpecBuilder = class {
|
|
|
14025
15316
|
async saveSession() {
|
|
14026
15317
|
if (!this.sessionPath) return;
|
|
14027
15318
|
try {
|
|
14028
|
-
const
|
|
15319
|
+
const fsp15 = await import('fs/promises');
|
|
14029
15320
|
const path18 = await import('path');
|
|
14030
15321
|
const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
|
|
14031
|
-
await
|
|
15322
|
+
await fsp15.mkdir(path18.dirname(this.sessionPath), { recursive: true });
|
|
14032
15323
|
await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
|
|
14033
15324
|
} catch {
|
|
14034
15325
|
}
|
|
@@ -14037,8 +15328,8 @@ var AISpecBuilder = class {
|
|
|
14037
15328
|
async loadSession() {
|
|
14038
15329
|
if (!this.sessionPath) return false;
|
|
14039
15330
|
try {
|
|
14040
|
-
const
|
|
14041
|
-
const raw = await
|
|
15331
|
+
const fsp15 = await import('fs/promises');
|
|
15332
|
+
const raw = await fsp15.readFile(this.sessionPath, "utf8");
|
|
14042
15333
|
const loaded = JSON.parse(raw);
|
|
14043
15334
|
if (loaded?.id && loaded?.phase && loaded?.title) {
|
|
14044
15335
|
this.session = loaded;
|
|
@@ -14052,8 +15343,8 @@ var AISpecBuilder = class {
|
|
|
14052
15343
|
async deleteSession() {
|
|
14053
15344
|
if (!this.sessionPath) return;
|
|
14054
15345
|
try {
|
|
14055
|
-
const
|
|
14056
|
-
await
|
|
15346
|
+
const fsp15 = await import('fs/promises');
|
|
15347
|
+
await fsp15.unlink(this.sessionPath);
|
|
14057
15348
|
} catch {
|
|
14058
15349
|
}
|
|
14059
15350
|
}
|
|
@@ -14680,7 +15971,7 @@ function analyzeCriticalPath(graph) {
|
|
|
14680
15971
|
blockedTasks
|
|
14681
15972
|
};
|
|
14682
15973
|
}
|
|
14683
|
-
function getTransitiveBlocked(
|
|
15974
|
+
function getTransitiveBlocked(_graph, taskId, blocksMap) {
|
|
14684
15975
|
const visited = /* @__PURE__ */ new Set();
|
|
14685
15976
|
const queue = [taskId];
|
|
14686
15977
|
while (queue.length > 0) {
|
|
@@ -14696,7 +15987,7 @@ function getTransitiveBlocked(graph, taskId, blocksMap) {
|
|
|
14696
15987
|
}
|
|
14697
15988
|
return visited;
|
|
14698
15989
|
}
|
|
14699
|
-
function computeCriticalPath(graph,
|
|
15990
|
+
function computeCriticalPath(graph, _topoOrder, blockedByMap) {
|
|
14700
15991
|
const allIds = Array.from(graph.nodes.keys());
|
|
14701
15992
|
if (allIds.length === 0) return [];
|
|
14702
15993
|
const dist = /* @__PURE__ */ new Map();
|
|
@@ -15114,13 +16405,11 @@ function createAutoExecutor(opts) {
|
|
|
15114
16405
|
|
|
15115
16406
|
// src/sdd/sdd-task-decomposer.ts
|
|
15116
16407
|
var SddTaskDecomposer = class {
|
|
15117
|
-
constructor(tracker,
|
|
16408
|
+
constructor(tracker, _graph, opts = {}) {
|
|
15118
16409
|
this.tracker = tracker;
|
|
15119
|
-
this.graph = graph;
|
|
15120
16410
|
this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
|
|
15121
16411
|
}
|
|
15122
16412
|
tracker;
|
|
15123
|
-
graph;
|
|
15124
16413
|
slots;
|
|
15125
16414
|
wave = 0;
|
|
15126
16415
|
// -------------------------------------------------------------------
|
|
@@ -15273,7 +16562,7 @@ var SddParallelRun = class {
|
|
|
15273
16562
|
this.coordinator.setRunner?.(runner);
|
|
15274
16563
|
}
|
|
15275
16564
|
defaultFactory() {
|
|
15276
|
-
return async (
|
|
16565
|
+
return async (_config) => ({
|
|
15277
16566
|
agent: this.opts.agent,
|
|
15278
16567
|
events: this.opts.agent.events
|
|
15279
16568
|
});
|
|
@@ -16087,6 +17376,12 @@ function roughEstimate(messages) {
|
|
|
16087
17376
|
return total;
|
|
16088
17377
|
}
|
|
16089
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;
|
|
16090
17385
|
return {
|
|
16091
17386
|
name: CONTEXT_MANAGER_TOOL_NAME,
|
|
16092
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.',
|
|
@@ -16177,11 +17472,41 @@ function createContextManagerTool(opts = {}) {
|
|
|
16177
17472
|
notes: "No compactor registered. Use /compact aggressive via slash command instead."
|
|
16178
17473
|
};
|
|
16179
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
|
+
}
|
|
16180
17498
|
const report = await opts.compactor.compact(ctx);
|
|
16181
17499
|
ctx.clearFileTracking();
|
|
16182
17500
|
const repair = applyMessages([...ctx.messages]);
|
|
16183
17501
|
const afterTokens = repair.changed ? roughEstimate(ctx.messages) : report.after;
|
|
16184
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
|
+
}
|
|
16185
17510
|
return {
|
|
16186
17511
|
action: "compact",
|
|
16187
17512
|
beforeTokens,
|
|
@@ -16420,6 +17745,6 @@ var allServers = () => ({
|
|
|
16420
17745
|
"minimax-vision": { ...miniMaxVisionServer(), enabled: false }
|
|
16421
17746
|
});
|
|
16422
17747
|
|
|
16423
|
-
export { AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SddParallelRun, SddTaskDecomposer, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets2 as decryptConfigSecrets, deriveTodosFromPlanItem, dispatchAgent, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, formatPlanTemplates, getAgentDefinition, getPlanTemplate, getTemplate, githubServer, googleMapsServer, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAutonomyPromptContributor, makeDirectorSessionFactory, makeLLMClassifier, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, scoreAgents, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
|
|
17748
|
+
export { AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SddParallelRun, SddTaskDecomposer, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets2 as decryptConfigSecrets, deriveTodosFromPlanItem, dispatchAgent, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, formatPlanTemplates, getAgentDefinition, getPlanTemplate, getTemplate, githubServer, googleMapsServer, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, scoreAgents, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
|
|
16424
17749
|
//# sourceMappingURL=index.js.map
|
|
16425
17750
|
//# sourceMappingURL=index.js.map
|