@wrongstack/core 0.9.19 → 0.9.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-DMVOX0cF.d.ts → agent-bridge-Dti3KXGk.d.ts} +1 -1
- package/dist/{agent-subagent-runner-C4qt9e5Y.d.ts → agent-subagent-runner-U-rs7kk7.d.ts} +3 -4
- package/dist/compactor-D7X96RLZ.d.ts +41 -0
- package/dist/{config-CWva0qoL.d.ts → config-CLXMDOSs.d.ts} +1 -1
- package/dist/{context-BRNbHmRM.d.ts → context-zkZeILpr.d.ts} +46 -0
- package/dist/coordination/index.d.ts +13 -13
- package/dist/coordination/index.js +660 -146
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +20 -20
- package/dist/defaults/index.js +918 -350
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-CiG9qUM_.d.ts → events-DH-9r-_C.d.ts} +42 -1
- package/dist/execution/index.d.ts +41 -30
- package/dist/execution/index.js +358 -112
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/extension/index.js.map +1 -1
- package/dist/{index-aizK8olO.d.ts → index-BIHJ4uII.d.ts} +11 -8
- package/dist/{index-p95HQ22A.d.ts → index-CFO9QmJo.d.ts} +8 -8
- package/dist/index.d.ts +311 -35
- package/dist/index.js +1933 -512
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +36 -0
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-BkVEqkRe.d.ts → mcp-servers-DkESgh0G.d.ts} +25 -3
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.js +1 -1
- package/dist/models/index.js.map +1 -1
- package/dist/{multi-agent-3ZnTB1aT.d.ts → multi-agent-DNp6lAzg.d.ts} +35 -23
- package/dist/{multi-agent-coordinator-bRaI_aD1.d.ts → multi-agent-coordinator-CAhsegPz.d.ts} +20 -2
- package/dist/{null-fleet-bus-DKM3Iy9d.d.ts → null-fleet-bus-Dnl19vmf.d.ts} +411 -110
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-TcJfc29Y.d.ts → path-resolver-CHiBL0DD.d.ts} +2 -2
- package/dist/{permission-bPuzAy4x.d.ts → permission-H35s9Evv.d.ts} +1 -1
- package/dist/{permission-policy-BUQSutpl.d.ts → permission-policy-CT-nRmTn.d.ts} +2 -2
- package/dist/{plan-templates-fkQTyz3U.d.ts → plan-templates-Bs8iRwi6.d.ts} +4 -4
- package/dist/{provider-runner-BEpikbbN.d.ts → provider-runner-BZdDrWrS.d.ts} +3 -3
- package/dist/{retry-policy-BYkq0ugs.d.ts → retry-policy-J9N_PM40.d.ts} +1 -1
- package/dist/sdd/index.d.ts +9 -10
- package/dist/sdd/index.js +224 -68
- package/dist/sdd/index.js.map +1 -1
- package/dist/security/index.d.ts +3 -3
- package/dist/{selector-pox8abg0.d.ts → selector-CFTh3Z6p.d.ts} +1 -1
- package/dist/{session-reader-CSWcb5Ga.d.ts → session-reader-C7JJlxOw.d.ts} +2 -2
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +390 -6
- package/dist/storage/index.js +672 -35
- package/dist/storage/index.js.map +1 -1
- package/dist/{system-prompt-Bs-Wliab.d.ts → system-prompt-CneIxVip.d.ts} +1 -1
- package/dist/{tool-executor-Boo3dekH.d.ts → tool-executor-flTuxsqO.d.ts} +9 -4
- package/dist/types/index.d.ts +14 -14
- package/dist/types/index.js +60 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +18 -6
- package/dist/utils/index.js +61 -56
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/compactor-DVTKL7XD.d.ts +0 -23
package/dist/index.js
CHANGED
|
@@ -56,8 +56,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
56
56
|
}
|
|
57
57
|
let mode;
|
|
58
58
|
try {
|
|
59
|
-
const
|
|
60
|
-
mode =
|
|
59
|
+
const stat8 = await fsp2.stat(targetPath);
|
|
60
|
+
mode = stat8.mode & 511;
|
|
61
61
|
} catch {
|
|
62
62
|
mode = opts.mode;
|
|
63
63
|
}
|
|
@@ -1676,6 +1676,7 @@ var HybridCompactor = class {
|
|
|
1676
1676
|
}
|
|
1677
1677
|
async compact(ctx, opts = {}) {
|
|
1678
1678
|
const beforeTokens = this.estimateMessages(ctx.messages);
|
|
1679
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
1679
1680
|
const reductions = [];
|
|
1680
1681
|
const policy = readContextWindowPolicy(ctx);
|
|
1681
1682
|
const preserveK = policy?.preserveK ?? this.preserveK;
|
|
@@ -1691,9 +1692,12 @@ var HybridCompactor = class {
|
|
|
1691
1692
|
ctx.state.replaceMessages(repaired.messages);
|
|
1692
1693
|
}
|
|
1693
1694
|
const afterTokens = this.estimateMessages(ctx.messages);
|
|
1695
|
+
const afterFull = this.estimateFullRequest(ctx);
|
|
1694
1696
|
return {
|
|
1695
1697
|
before: beforeTokens,
|
|
1696
1698
|
after: afterTokens,
|
|
1699
|
+
fullRequestTokensBefore: beforeFull,
|
|
1700
|
+
fullRequestTokensAfter: afterFull,
|
|
1697
1701
|
reductions,
|
|
1698
1702
|
repaired: repaired.report.changed ? {
|
|
1699
1703
|
removedToolUses: repaired.report.removedToolUses,
|
|
@@ -1702,6 +1706,14 @@ var HybridCompactor = class {
|
|
|
1702
1706
|
} : void 0
|
|
1703
1707
|
};
|
|
1704
1708
|
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Estimate the full API request token count: messages + systemPrompt + toolDefs.
|
|
1711
|
+
* This is the accurate figure for context-window pressure monitoring.
|
|
1712
|
+
*/
|
|
1713
|
+
estimateFullRequest(ctx) {
|
|
1714
|
+
const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
|
|
1715
|
+
return breakdown.total;
|
|
1716
|
+
}
|
|
1705
1717
|
eliseOldToolResults(ctx, preserveK = this.preserveK, eliseThreshold = this.eliseThreshold) {
|
|
1706
1718
|
const messages = ctx.messages;
|
|
1707
1719
|
let pairCount = 0;
|
|
@@ -4036,11 +4048,11 @@ function validateAgainstSchema(value, schema) {
|
|
|
4036
4048
|
walk2(value, schema, "", errors);
|
|
4037
4049
|
return { ok: errors.length === 0, errors };
|
|
4038
4050
|
}
|
|
4039
|
-
function walk2(value, schema,
|
|
4051
|
+
function walk2(value, schema, path35, errors) {
|
|
4040
4052
|
if (schema.enum !== void 0) {
|
|
4041
4053
|
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
4042
4054
|
errors.push({
|
|
4043
|
-
path:
|
|
4055
|
+
path: path35 || "<root>",
|
|
4044
4056
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
4045
4057
|
});
|
|
4046
4058
|
return;
|
|
@@ -4049,7 +4061,7 @@ function walk2(value, schema, path31, errors) {
|
|
|
4049
4061
|
if (typeof schema.type === "string") {
|
|
4050
4062
|
if (!checkType(value, schema.type)) {
|
|
4051
4063
|
errors.push({
|
|
4052
|
-
path:
|
|
4064
|
+
path: path35 || "<root>",
|
|
4053
4065
|
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
4054
4066
|
});
|
|
4055
4067
|
return;
|
|
@@ -4059,19 +4071,19 @@ function walk2(value, schema, path31, errors) {
|
|
|
4059
4071
|
const obj = value;
|
|
4060
4072
|
for (const req of schema.required ?? []) {
|
|
4061
4073
|
if (!(req in obj)) {
|
|
4062
|
-
errors.push({ path: joinPath(
|
|
4074
|
+
errors.push({ path: joinPath(path35, req), message: "required property missing" });
|
|
4063
4075
|
}
|
|
4064
4076
|
}
|
|
4065
4077
|
if (schema.properties) {
|
|
4066
4078
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
4067
4079
|
if (key in obj) {
|
|
4068
|
-
walk2(obj[key], subSchema, joinPath(
|
|
4080
|
+
walk2(obj[key], subSchema, joinPath(path35, key), errors);
|
|
4069
4081
|
}
|
|
4070
4082
|
}
|
|
4071
4083
|
}
|
|
4072
4084
|
}
|
|
4073
4085
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
4074
|
-
value.forEach((item, i) => walk2(item, schema.items, `${
|
|
4086
|
+
value.forEach((item, i) => walk2(item, schema.items, `${path35}[${i}]`, errors));
|
|
4075
4087
|
}
|
|
4076
4088
|
}
|
|
4077
4089
|
function checkType(value, type) {
|
|
@@ -4126,80 +4138,85 @@ function deepEqual(a, b) {
|
|
|
4126
4138
|
|
|
4127
4139
|
// src/utils/json-repair.ts
|
|
4128
4140
|
function completePartialObject(s) {
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4141
|
+
if (!s.trim().startsWith("{")) return s;
|
|
4142
|
+
if (tryParse(s).ok) return s;
|
|
4143
|
+
const stack = [];
|
|
4144
|
+
let inString = false;
|
|
4145
|
+
let escaped = false;
|
|
4146
|
+
let sawKey = false;
|
|
4147
|
+
let prevSig = "";
|
|
4148
|
+
let contentEnd = 0;
|
|
4149
|
+
let stringBraceDepth = 0;
|
|
4150
|
+
for (let i = 0; i < s.length; i++) {
|
|
4151
|
+
const ch = s[i];
|
|
4152
|
+
if (inString) {
|
|
4153
|
+
contentEnd = i + 1;
|
|
4154
|
+
if (escaped) {
|
|
4155
|
+
escaped = false;
|
|
4140
4156
|
continue;
|
|
4141
4157
|
}
|
|
4142
4158
|
if (ch === "\\") {
|
|
4143
|
-
|
|
4159
|
+
escaped = true;
|
|
4144
4160
|
continue;
|
|
4145
4161
|
}
|
|
4146
4162
|
if (ch === '"') {
|
|
4147
|
-
|
|
4163
|
+
inString = false;
|
|
4164
|
+
prevSig = '"';
|
|
4165
|
+
stringBraceDepth = 0;
|
|
4148
4166
|
continue;
|
|
4149
4167
|
}
|
|
4150
|
-
if (
|
|
4151
|
-
if (ch === "
|
|
4152
|
-
braceDepth++;
|
|
4153
|
-
foundClose = false;
|
|
4154
|
-
} else if (ch === "}") {
|
|
4155
|
-
braceDepth--;
|
|
4156
|
-
if (braceDepth === 0) foundClose = true;
|
|
4157
|
-
}
|
|
4158
|
-
}
|
|
4159
|
-
if (foundClose || braceDepth <= 0) break;
|
|
4160
|
-
result += "}".repeat(braceDepth);
|
|
4161
|
-
}
|
|
4162
|
-
if (tryParse(result).ok) return result;
|
|
4163
|
-
let inString = false;
|
|
4164
|
-
let escaped = false;
|
|
4165
|
-
for (let i = result.length - 1; i >= 0; i--) {
|
|
4166
|
-
const ch = result[i];
|
|
4167
|
-
if (escaped) {
|
|
4168
|
-
escaped = false;
|
|
4169
|
-
continue;
|
|
4170
|
-
}
|
|
4171
|
-
if (ch === "\\") {
|
|
4172
|
-
escaped = true;
|
|
4168
|
+
if (ch === "{") stringBraceDepth++;
|
|
4169
|
+
else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
|
|
4173
4170
|
continue;
|
|
4174
4171
|
}
|
|
4172
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
|
|
4173
|
+
contentEnd = i + 1;
|
|
4175
4174
|
if (ch === '"') {
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
}
|
|
4175
|
+
inString = true;
|
|
4176
|
+
sawKey = true;
|
|
4177
|
+
stringBraceDepth = 0;
|
|
4178
|
+
prevSig = '"';
|
|
4179
|
+
} else if (ch === "{" || ch === "[") {
|
|
4180
|
+
stack.push(ch);
|
|
4181
|
+
prevSig = ch;
|
|
4182
|
+
} else if (ch === "}" || ch === "]") {
|
|
4183
|
+
stack.pop();
|
|
4184
|
+
prevSig = ch;
|
|
4185
|
+
} else {
|
|
4186
|
+
prevSig = ch;
|
|
4189
4187
|
}
|
|
4190
4188
|
}
|
|
4189
|
+
if (!sawKey && !inString) return s;
|
|
4190
|
+
let result = s.slice(0, contentEnd);
|
|
4191
4191
|
if (inString) {
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
if (ch === "{") depth++;
|
|
4197
|
-
else if (ch === "}") depth = Math.max(0, depth - 1);
|
|
4192
|
+
if (escaped) {
|
|
4193
|
+
result = result.slice(0, -1);
|
|
4194
|
+
} else if (endsWithInvalidEscape(result)) {
|
|
4195
|
+
result = result.slice(0, -2);
|
|
4198
4196
|
}
|
|
4199
|
-
result +=
|
|
4197
|
+
if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
|
|
4198
|
+
result += '"';
|
|
4199
|
+
} else if (prevSig === ":") {
|
|
4200
|
+
result += "null";
|
|
4201
|
+
}
|
|
4202
|
+
for (let k = stack.length - 1; k >= 0; k--) {
|
|
4203
|
+
result += stack[k] === "{" ? "}" : "]";
|
|
4204
|
+
}
|
|
4205
|
+
if (!tryParse(result).ok) {
|
|
4206
|
+
const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
|
|
4207
|
+
if (tryParse(patched).ok) result = patched;
|
|
4200
4208
|
}
|
|
4201
4209
|
return result;
|
|
4202
4210
|
}
|
|
4211
|
+
var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
|
|
4212
|
+
function endsWithInvalidEscape(str) {
|
|
4213
|
+
const last = str[str.length - 1];
|
|
4214
|
+
if (str[str.length - 2] !== "\\" || last === void 0) return false;
|
|
4215
|
+
if (VALID_ESCAPE.has(last)) return false;
|
|
4216
|
+
let backslashes = 0;
|
|
4217
|
+
for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
|
|
4218
|
+
return backslashes % 2 === 1;
|
|
4219
|
+
}
|
|
4203
4220
|
function tryParse(s) {
|
|
4204
4221
|
try {
|
|
4205
4222
|
return { ok: true, value: JSON.parse(s) };
|
|
@@ -4340,8 +4357,8 @@ var DefaultSessionStore = class {
|
|
|
4340
4357
|
return JSON.parse(raw);
|
|
4341
4358
|
} catch {
|
|
4342
4359
|
const full = this.sessionPath(id, ".jsonl");
|
|
4343
|
-
const
|
|
4344
|
-
const summary = await this.summarize(id,
|
|
4360
|
+
const stat8 = await fsp2.stat(full);
|
|
4361
|
+
const summary = await this.summarize(id, stat8.mtime.toISOString());
|
|
4345
4362
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
4346
4363
|
console.warn(
|
|
4347
4364
|
`[session-store] Failed to write manifest for "${id}":`,
|
|
@@ -4534,7 +4551,6 @@ var FileSessionWriter = class {
|
|
|
4534
4551
|
}
|
|
4535
4552
|
return event;
|
|
4536
4553
|
}
|
|
4537
|
-
promptIndex = 0;
|
|
4538
4554
|
pendingFileSnapshots = [];
|
|
4539
4555
|
/** Tracks open tool_use IDs during the current run to serialize on close for resume. */
|
|
4540
4556
|
openToolUses = /* @__PURE__ */ new Set();
|
|
@@ -4625,7 +4641,6 @@ var FileSessionWriter = class {
|
|
|
4625
4641
|
await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);
|
|
4626
4642
|
this.pendingFileSnapshots = [];
|
|
4627
4643
|
}
|
|
4628
|
-
this.promptIndex = promptIndex + 1;
|
|
4629
4644
|
await this.append({
|
|
4630
4645
|
type: "checkpoint",
|
|
4631
4646
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4721,6 +4736,38 @@ var FileSessionWriter = class {
|
|
|
4721
4736
|
`;
|
|
4722
4737
|
await fsp2.writeFile(this.filePath, record, "utf8");
|
|
4723
4738
|
}
|
|
4739
|
+
/**
|
|
4740
|
+
* Idea #1 — write an in-flight marker. The agent loop should call
|
|
4741
|
+
* this at the start of each long-running operation; a matching
|
|
4742
|
+
* `clearInFlightMarker` follows on clean exit. A stale marker
|
|
4743
|
+
* (no end) is what `SessionRecovery.detectStale` looks for.
|
|
4744
|
+
*/
|
|
4745
|
+
async writeInFlightMarker(context) {
|
|
4746
|
+
if (!context || context.length > 500) {
|
|
4747
|
+
throw new Error("In-flight context must be 1..500 chars");
|
|
4748
|
+
}
|
|
4749
|
+
await this.append({
|
|
4750
|
+
type: "in_flight_start",
|
|
4751
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4752
|
+
context
|
|
4753
|
+
});
|
|
4754
|
+
this.events?.emit("in_flight.started", { context, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
4755
|
+
}
|
|
4756
|
+
/**
|
|
4757
|
+
* Idea #1 — close the in-flight marker. Idempotent in spirit
|
|
4758
|
+
* (you can call it after a successful iteration even if you
|
|
4759
|
+
* didn't open one this round) — but the session log records
|
|
4760
|
+
* every call so postmortem tooling can see "the agent finished
|
|
4761
|
+
* cleanly X times, then died without finishing Y".
|
|
4762
|
+
*/
|
|
4763
|
+
async clearInFlightMarker(reason) {
|
|
4764
|
+
await this.append({
|
|
4765
|
+
type: "in_flight_end",
|
|
4766
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4767
|
+
reason
|
|
4768
|
+
});
|
|
4769
|
+
this.events?.emit("in_flight.ended", { reason, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
4770
|
+
}
|
|
4724
4771
|
};
|
|
4725
4772
|
function userInputTitle(content) {
|
|
4726
4773
|
const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
@@ -6976,8 +7023,9 @@ var IntelligentCompactor = class {
|
|
|
6976
7023
|
}
|
|
6977
7024
|
async compact(ctx, opts = {}) {
|
|
6978
7025
|
const beforeTokens = this.estimateTokens(ctx.messages);
|
|
7026
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
6979
7027
|
const reductions = [];
|
|
6980
|
-
const load =
|
|
7028
|
+
const load = beforeFull / this.maxContext;
|
|
6981
7029
|
const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
|
|
6982
7030
|
const saved1 = this.eliseOldToolResults(ctx);
|
|
6983
7031
|
if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
|
|
@@ -6991,9 +7039,12 @@ var IntelligentCompactor = class {
|
|
|
6991
7039
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
6992
7040
|
if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
|
|
6993
7041
|
const afterTokens = this.estimateTokens(ctx.messages);
|
|
7042
|
+
const afterFull = this.estimateFullRequest(ctx);
|
|
6994
7043
|
return {
|
|
6995
7044
|
before: beforeTokens,
|
|
6996
7045
|
after: afterTokens,
|
|
7046
|
+
fullRequestTokensBefore: beforeFull,
|
|
7047
|
+
fullRequestTokensAfter: afterFull,
|
|
6997
7048
|
reductions,
|
|
6998
7049
|
repaired: repaired.report.changed ? {
|
|
6999
7050
|
removedToolUses: repaired.report.removedToolUses,
|
|
@@ -7002,6 +7053,14 @@ var IntelligentCompactor = class {
|
|
|
7002
7053
|
} : void 0
|
|
7003
7054
|
};
|
|
7004
7055
|
}
|
|
7056
|
+
/**
|
|
7057
|
+
* Estimate the full API request token count: messages + systemPrompt + toolDefs.
|
|
7058
|
+
* This is the accurate figure for context-window pressure monitoring.
|
|
7059
|
+
*/
|
|
7060
|
+
estimateFullRequest(ctx) {
|
|
7061
|
+
const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
|
|
7062
|
+
return breakdown.total;
|
|
7063
|
+
}
|
|
7005
7064
|
async summarizeAncientTurns(ctx) {
|
|
7006
7065
|
const messages = ctx.messages;
|
|
7007
7066
|
const cutoff = Math.max(0, messages.length - this.preserveK * 2);
|
|
@@ -7014,29 +7073,26 @@ var IntelligentCompactor = class {
|
|
|
7014
7073
|
try {
|
|
7015
7074
|
summaryText = await this.callSummarizer(toSummarize, ctx);
|
|
7016
7075
|
} catch {
|
|
7017
|
-
const
|
|
7018
|
-
const filePaths = /* @__PURE__ */ new Set();
|
|
7019
|
-
let userTurns = 0;
|
|
7020
|
-
let assistantTurns = 0;
|
|
7076
|
+
const preservedMessages = [];
|
|
7021
7077
|
for (const m of toSummarize) {
|
|
7022
|
-
if (m.role === "
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7078
|
+
if (m.role === "system") continue;
|
|
7079
|
+
if (typeof m.content === "string") {
|
|
7080
|
+
preservedMessages.push(m);
|
|
7081
|
+
} else if (Array.isArray(m.content)) {
|
|
7082
|
+
const textParts = m.content.filter(isTextBlock);
|
|
7083
|
+
const toolParts = m.content.filter((b) => b.type === "tool_use" || b.type === "tool_result");
|
|
7084
|
+
const next = {
|
|
7085
|
+
role: m.role,
|
|
7086
|
+
content: [
|
|
7087
|
+
...textParts,
|
|
7088
|
+
...toolParts.length > 0 ? [{ type: "text", text: `[${toolParts.length} tool call(s) omitted]` }] : []
|
|
7089
|
+
]
|
|
7090
|
+
};
|
|
7091
|
+
preservedMessages.push(next);
|
|
7030
7092
|
}
|
|
7031
|
-
const text = typeof m.content === "string" ? m.content : "";
|
|
7032
|
-
const matches = text.matchAll(/(?:[\w.,\-/@]+\/)*[\w.,\-/@]+\.\w+/g);
|
|
7033
|
-
for (const m_ of matches) filePaths.add(m_[0]);
|
|
7034
7093
|
}
|
|
7035
|
-
|
|
7036
|
-
if (
|
|
7037
|
-
if (filePaths.size > 0) parts.push(`files: ${[...filePaths].slice(0, 10).join(", ")}`);
|
|
7038
|
-
summaryText = parts.join(" | ");
|
|
7039
|
-
if (!summaryText) summaryText = `${toSummarize.length} earlier turns omitted`;
|
|
7094
|
+
summaryText = preservedMessages.map((m) => `[${m.role}]: ${typeof m.content === "string" ? m.content : m.content.filter(isTextBlock).map((b) => b.text).join(" ")}`).join("\n");
|
|
7095
|
+
if (!summaryText) summaryText = `${toSummarize.length} earlier turns (semantic content preserved)`;
|
|
7040
7096
|
}
|
|
7041
7097
|
const summaryMsg = {
|
|
7042
7098
|
role: "system",
|
|
@@ -7282,7 +7338,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
7282
7338
|
const res = await this.provider.complete(req, { signal: ac.signal });
|
|
7283
7339
|
const textBlocks = res.content.filter(isTextBlock);
|
|
7284
7340
|
raw = textBlocks.map((b) => b.text).join("\n").trim();
|
|
7285
|
-
} catch (
|
|
7341
|
+
} catch (_err) {
|
|
7286
7342
|
return this.fallbackSelect(messages, effectiveBudget);
|
|
7287
7343
|
}
|
|
7288
7344
|
return this.parseSelectorOutput(raw, messages.length);
|
|
@@ -7374,15 +7430,24 @@ var SelectiveCompactor = class {
|
|
|
7374
7430
|
}
|
|
7375
7431
|
async compact(ctx, opts = {}) {
|
|
7376
7432
|
const beforeTokens = this.estimateTokens(ctx.messages);
|
|
7433
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
7377
7434
|
const reductions = [];
|
|
7378
|
-
const load =
|
|
7435
|
+
const load = beforeFull / this.maxContext;
|
|
7379
7436
|
const shouldCompact = load >= this.warnThreshold || opts.aggressive;
|
|
7380
7437
|
if (!shouldCompact) {
|
|
7381
7438
|
const saved = this.eliseOldToolResults(ctx);
|
|
7382
7439
|
if (saved > 0) reductions.push({ phase: "elision", saved });
|
|
7383
7440
|
const repair2 = this.repairProtocolAdjacency(ctx);
|
|
7384
7441
|
const afterTokens2 = this.estimateTokens(ctx.messages);
|
|
7385
|
-
|
|
7442
|
+
const afterFull2 = this.estimateFullRequest(ctx);
|
|
7443
|
+
return {
|
|
7444
|
+
before: beforeTokens,
|
|
7445
|
+
after: afterTokens2,
|
|
7446
|
+
fullRequestTokensBefore: beforeFull,
|
|
7447
|
+
fullRequestTokensAfter: afterFull2,
|
|
7448
|
+
reductions,
|
|
7449
|
+
repaired: repair2
|
|
7450
|
+
};
|
|
7386
7451
|
}
|
|
7387
7452
|
const savedElision = this.eliseOldToolResults(ctx);
|
|
7388
7453
|
if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
|
|
@@ -7394,7 +7459,23 @@ var SelectiveCompactor = class {
|
|
|
7394
7459
|
}
|
|
7395
7460
|
const repair = this.repairProtocolAdjacency(ctx);
|
|
7396
7461
|
const afterTokens = this.estimateTokens(ctx.messages);
|
|
7397
|
-
|
|
7462
|
+
const afterFull = this.estimateFullRequest(ctx);
|
|
7463
|
+
return {
|
|
7464
|
+
before: beforeTokens,
|
|
7465
|
+
after: afterTokens,
|
|
7466
|
+
fullRequestTokensBefore: beforeFull,
|
|
7467
|
+
fullRequestTokensAfter: afterFull,
|
|
7468
|
+
reductions,
|
|
7469
|
+
repaired: repair
|
|
7470
|
+
};
|
|
7471
|
+
}
|
|
7472
|
+
/**
|
|
7473
|
+
* Estimate the full API request token count: messages + systemPrompt + toolDefs.
|
|
7474
|
+
* This is the accurate figure for context-window pressure monitoring.
|
|
7475
|
+
*/
|
|
7476
|
+
estimateFullRequest(ctx) {
|
|
7477
|
+
const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
|
|
7478
|
+
return breakdown.total;
|
|
7398
7479
|
}
|
|
7399
7480
|
repairProtocolAdjacency(ctx) {
|
|
7400
7481
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
@@ -7584,7 +7665,8 @@ var LEVEL_RANK2 = { warn: 0, soft: 1, hard: 2 };
|
|
|
7584
7665
|
var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
7585
7666
|
name = "AutoCompaction";
|
|
7586
7667
|
compactor;
|
|
7587
|
-
|
|
7668
|
+
/** Deprecated. Kept for backward compat with tests that pass simpleEstimator. */
|
|
7669
|
+
_estimator;
|
|
7588
7670
|
warnThreshold;
|
|
7589
7671
|
softThreshold;
|
|
7590
7672
|
hardThreshold;
|
|
@@ -7594,19 +7676,6 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
7594
7676
|
events;
|
|
7595
7677
|
failureMode;
|
|
7596
7678
|
policyProvider;
|
|
7597
|
-
/**
|
|
7598
|
-
* Calibration factor applied to the estimator output. The estimator is now
|
|
7599
|
-
* expected to already include messages + system prompt + tool definitions
|
|
7600
|
-
* (see `estimateRequestTokens.total`), so no overhead boost is needed. Kept
|
|
7601
|
-
* as a constant so a future calibration against real provider tokenization
|
|
7602
|
-
* (BPE vs the chars/3.5 rough estimator) can dial this without touching the
|
|
7603
|
-
* threshold-check math.
|
|
7604
|
-
*
|
|
7605
|
-
* Historical note: was 1.3 when the estimator only counted messages — that
|
|
7606
|
-
* double-counted system+tools once the estimator was upgraded, firing
|
|
7607
|
-
* compaction ~30% earlier than intended (e.g. real 56% load showed as 73%).
|
|
7608
|
-
*/
|
|
7609
|
-
static OVERHEAD_FACTOR = 1;
|
|
7610
7679
|
/**
|
|
7611
7680
|
* Once a compaction attempt reduces nothing (preserveK protects everything,
|
|
7612
7681
|
* no oversized tool_results remain to elide), retrying on every iteration
|
|
@@ -7619,8 +7688,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
7619
7688
|
lastNoopAttempt = null;
|
|
7620
7689
|
/**
|
|
7621
7690
|
* @param compactor Compactor to use for compaction.
|
|
7622
|
-
* @param maxContext
|
|
7623
|
-
* @param
|
|
7691
|
+
* @param maxContext Provider's max context window in tokens.
|
|
7692
|
+
* @param _estimator Deprecated parameter kept for backward compatibility.
|
|
7693
|
+
* The middleware now uses `estimateRequestTokens` internally
|
|
7694
|
+
* for accurate full-request token counting (messages +
|
|
7695
|
+
* systemPrompt + toolDefs).
|
|
7624
7696
|
* @param thresholds Threshold fractions (0-1) of maxContext.
|
|
7625
7697
|
* @param opts Optional behavior. By default, failures at the
|
|
7626
7698
|
* hard threshold throw AGENT_CONTEXT_OVERFLOW so
|
|
@@ -7628,11 +7700,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
7628
7700
|
* provider context overflow. Warn/soft failures
|
|
7629
7701
|
* still emit compaction.failed and continue.
|
|
7630
7702
|
*/
|
|
7631
|
-
constructor(compactor, maxContext,
|
|
7703
|
+
constructor(compactor, maxContext, _estimator, thresholds, optsOrAggressiveOn = {}, events) {
|
|
7632
7704
|
const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
|
|
7633
7705
|
this.compactor = compactor;
|
|
7634
7706
|
this._maxContext = maxContext;
|
|
7635
|
-
this.
|
|
7707
|
+
this._estimator = _estimator;
|
|
7636
7708
|
this.warnThreshold = thresholds.warn;
|
|
7637
7709
|
this.softThreshold = thresholds.soft;
|
|
7638
7710
|
this.hardThreshold = thresholds.hard;
|
|
@@ -7648,8 +7720,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
7648
7720
|
}
|
|
7649
7721
|
handler() {
|
|
7650
7722
|
return async (ctx, next) => {
|
|
7651
|
-
const
|
|
7652
|
-
const tokens = Math.ceil(rawTokens * _AutoCompactionMiddleware.OVERHEAD_FACTOR);
|
|
7723
|
+
const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
|
|
7653
7724
|
const load = tokens / this._maxContext;
|
|
7654
7725
|
const policy = this.policyProvider?.(ctx);
|
|
7655
7726
|
const thresholds = policy?.thresholds ?? {
|
|
@@ -7684,7 +7755,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
7684
7755
|
return delta < _AutoCompactionMiddleware.NOOP_RETRY_DELTA_TOKENS;
|
|
7685
7756
|
}
|
|
7686
7757
|
recordAttempt(level, tokens, report) {
|
|
7687
|
-
const reduced = report.before > report.after;
|
|
7758
|
+
const reduced = (report.fullRequestTokensBefore ?? report.before) > (report.fullRequestTokensAfter ?? report.after);
|
|
7688
7759
|
const repaired = !!report.repaired;
|
|
7689
7760
|
if (reduced || repaired) {
|
|
7690
7761
|
this.lastNoopAttempt = null;
|
|
@@ -8617,17 +8688,7 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8617
8688
|
startTime = null;
|
|
8618
8689
|
_onThreshold;
|
|
8619
8690
|
/**
|
|
8620
|
-
*
|
|
8621
|
-
* in flight. While a kind is here, further `checkLimit` calls for the
|
|
8622
|
-
* same kind are no-ops — without this dedup, every `recordIteration`
|
|
8623
|
-
* after the limit is reached spawns a fresh decision Promise (until
|
|
8624
|
-
* the first one lands and patches limits), flooding the FleetBus
|
|
8625
|
-
* with redundant threshold events. Cleared in `negotiateExtension`'s
|
|
8626
|
-
* `finally`.
|
|
8627
|
-
*/
|
|
8628
|
-
pendingExtensions = /* @__PURE__ */ new Set();
|
|
8629
|
-
/**
|
|
8630
|
-
* Hard cap on how long `negotiateExtension` waits for the coordinator to
|
|
8691
|
+
* Hard cap on how long `_negotiateExtension` waits for the coordinator to
|
|
8631
8692
|
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
8632
8693
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
8633
8694
|
* leaves the budget over-limit and never enforces anything.
|
|
@@ -8689,38 +8750,83 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8689
8750
|
* - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
|
|
8690
8751
|
* - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
|
|
8691
8752
|
*/
|
|
8692
|
-
|
|
8753
|
+
/**
|
|
8754
|
+
* Collects all exceeded budget kinds into a single NOOP-free negotiation.
|
|
8755
|
+
* Called by recordIteration / recordToolCall / recordUsage — each may call
|
|
8756
|
+
* this for its own kind. The first call starts the negotiation and stores
|
|
8757
|
+
* the Promise in _pendingNegotiation. Subsequent calls for DIFFERENT
|
|
8758
|
+
* kinds (while a negotiation is in flight) are NOOPs — they don't start
|
|
8759
|
+
* new conversations with the coordinator. This prevents an EventBus flood
|
|
8760
|
+
* when multiple budget kinds are exceeded simultaneously in one iteration.
|
|
8761
|
+
*
|
|
8762
|
+
* Returns the kinds that were found to be exceeded (for logging/debugging).
|
|
8763
|
+
*/
|
|
8764
|
+
checkLimits(elapsedMs) {
|
|
8765
|
+
const exceeded = [];
|
|
8766
|
+
if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
|
|
8767
|
+
exceeded.push({ kind: "iterations", used: this.iterations, limit: this.limits.maxIterations });
|
|
8768
|
+
}
|
|
8769
|
+
if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
|
|
8770
|
+
exceeded.push({ kind: "tool_calls", used: this.toolCalls, limit: this.limits.maxToolCalls });
|
|
8771
|
+
}
|
|
8772
|
+
const totalTokens = this.tokenInput + this.tokenOutput;
|
|
8773
|
+
if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
|
|
8774
|
+
exceeded.push({ kind: "tokens", used: totalTokens, limit: this.limits.maxTokens });
|
|
8775
|
+
}
|
|
8776
|
+
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
8777
|
+
exceeded.push({ kind: "cost", used: this.costUsd, limit: this.limits.maxCostUsd });
|
|
8778
|
+
}
|
|
8779
|
+
if (elapsedMs !== void 0 && this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
|
|
8780
|
+
exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
|
|
8781
|
+
}
|
|
8782
|
+
if (exceeded.length === 0) return [];
|
|
8693
8783
|
if (!this._onThreshold) {
|
|
8694
|
-
|
|
8784
|
+
const first2 = exceeded[0];
|
|
8785
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
8695
8786
|
}
|
|
8696
8787
|
if (this._mode === "sync") {
|
|
8697
|
-
|
|
8788
|
+
const first2 = exceeded[0];
|
|
8789
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
8698
8790
|
}
|
|
8699
8791
|
const bus = this._events;
|
|
8700
8792
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
8701
|
-
|
|
8793
|
+
const first2 = exceeded[0];
|
|
8794
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
8795
|
+
}
|
|
8796
|
+
for (const entry of exceeded) {
|
|
8797
|
+
if (this._pendingNegotiations.has(entry.kind)) continue;
|
|
8798
|
+
const decision2 = this._negotiateExtension(entry.kind, exceeded);
|
|
8799
|
+
this._pendingNegotiations.set(entry.kind, decision2);
|
|
8702
8800
|
}
|
|
8703
|
-
|
|
8704
|
-
this.
|
|
8705
|
-
|
|
8706
|
-
throw new BudgetThresholdSignal(kind, limit, used, decision);
|
|
8801
|
+
const first = exceeded[0];
|
|
8802
|
+
const decision = this._pendingNegotiations.get(first.kind);
|
|
8803
|
+
throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
|
|
8707
8804
|
}
|
|
8805
|
+
/**
|
|
8806
|
+
* Per-kind in-flight negotiation Promises. Each budget kind can have its
|
|
8807
|
+
* own concurrent negotiation — e.g. iterations and timeout can both
|
|
8808
|
+
* be exceeded simultaneously without blocking each other. The same kind
|
|
8809
|
+
* cannot start two concurrent negotiations (dedup guard).
|
|
8810
|
+
* Cleared in `_negotiateExtension`'s `finally` block.
|
|
8811
|
+
*/
|
|
8812
|
+
_pendingNegotiations = /* @__PURE__ */ new Map();
|
|
8708
8813
|
/**
|
|
8709
8814
|
* Drive the threshold handler to a decision. Resolves with `'stop'`
|
|
8710
8815
|
* (signal the runner to abort) or `{ extend: ... }` (limits already
|
|
8711
|
-
* patched in-place; the runner should not abort).
|
|
8712
|
-
*
|
|
8816
|
+
* patched in-place; the runner should not abort). Clears the
|
|
8817
|
+
* per-kind slot in `_pendingNegotiations` in `finally`.
|
|
8713
8818
|
*
|
|
8714
8819
|
* The 'continue' return from a sync handler is treated as
|
|
8715
8820
|
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
8716
8821
|
* a fresh signal.
|
|
8717
8822
|
*/
|
|
8718
|
-
async
|
|
8823
|
+
async _negotiateExtension(kind, exceeded) {
|
|
8719
8824
|
try {
|
|
8825
|
+
const first = exceeded[0];
|
|
8720
8826
|
const result = this._onThreshold({
|
|
8721
|
-
kind,
|
|
8722
|
-
used,
|
|
8723
|
-
limit,
|
|
8827
|
+
kind: first.kind,
|
|
8828
|
+
used: first.used,
|
|
8829
|
+
limit: first.limit,
|
|
8724
8830
|
requestDecision: () => {
|
|
8725
8831
|
const bus = this._events;
|
|
8726
8832
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
@@ -8737,20 +8843,22 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8737
8843
|
() => respond("stop"),
|
|
8738
8844
|
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
8739
8845
|
);
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
8846
|
+
for (const { kind: kind2, used, limit } of exceeded) {
|
|
8847
|
+
bus.emit("budget.threshold_reached", {
|
|
8848
|
+
kind: kind2,
|
|
8849
|
+
used,
|
|
8850
|
+
limit,
|
|
8851
|
+
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
8852
|
+
extend: (extra) => {
|
|
8853
|
+
clearTimeout(fallback);
|
|
8854
|
+
respond({ extend: extra });
|
|
8855
|
+
},
|
|
8856
|
+
deny: () => {
|
|
8857
|
+
clearTimeout(fallback);
|
|
8858
|
+
respond("stop");
|
|
8859
|
+
}
|
|
8860
|
+
});
|
|
8861
|
+
}
|
|
8754
8862
|
});
|
|
8755
8863
|
}
|
|
8756
8864
|
});
|
|
@@ -8776,47 +8884,39 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8776
8884
|
}
|
|
8777
8885
|
return decision;
|
|
8778
8886
|
} finally {
|
|
8779
|
-
this.
|
|
8887
|
+
this._pendingNegotiations.delete(kind);
|
|
8780
8888
|
}
|
|
8781
8889
|
}
|
|
8782
8890
|
recordIteration() {
|
|
8783
8891
|
this.iterations++;
|
|
8784
|
-
|
|
8785
|
-
void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
|
|
8786
|
-
}
|
|
8892
|
+
void this.checkLimits();
|
|
8787
8893
|
}
|
|
8788
8894
|
recordToolCall() {
|
|
8789
8895
|
this.toolCalls++;
|
|
8790
|
-
|
|
8791
|
-
void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
|
|
8792
|
-
}
|
|
8896
|
+
void this.checkLimits();
|
|
8793
8897
|
}
|
|
8794
8898
|
recordUsage(usage, costUsd = 0) {
|
|
8795
8899
|
this.tokenInput += usage.input;
|
|
8796
8900
|
this.tokenOutput += usage.output;
|
|
8797
8901
|
this.costUsd += costUsd;
|
|
8798
|
-
|
|
8799
|
-
if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
|
|
8800
|
-
void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
|
|
8801
|
-
}
|
|
8802
|
-
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
8803
|
-
void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
|
|
8804
|
-
}
|
|
8902
|
+
void this.checkLimits();
|
|
8805
8903
|
}
|
|
8806
8904
|
/**
|
|
8807
|
-
* Wall-clock budget check. Unlike other limits, timeout
|
|
8808
|
-
*
|
|
8809
|
-
*
|
|
8810
|
-
*
|
|
8811
|
-
*
|
|
8812
|
-
*
|
|
8905
|
+
* Wall-clock budget check. Unlike other limits, timeout is always a hard stop
|
|
8906
|
+
* — wall-clock time cannot be "extended" by the coordinator, so it throws
|
|
8907
|
+
* synchronously rather than entering the negotiation flow.
|
|
8908
|
+
*
|
|
8909
|
+
* Decision table:
|
|
8910
|
+
* - no `onThreshold` handler → throw `BudgetExceededError`
|
|
8911
|
+
* - `mode === 'sync'` → throw `BudgetExceededError`
|
|
8912
|
+
* - `mode === 'auto'` + no listener → throw `BudgetExceededError`
|
|
8913
|
+
* - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
|
|
8813
8914
|
*/
|
|
8814
8915
|
checkTimeout() {
|
|
8815
8916
|
if (this.startTime === null || this.limits.timeoutMs === void 0) return;
|
|
8816
8917
|
const elapsed = Date.now() - this.startTime;
|
|
8817
|
-
if (elapsed
|
|
8818
|
-
|
|
8819
|
-
}
|
|
8918
|
+
if (elapsed <= this.limits.timeoutMs) return;
|
|
8919
|
+
void this.checkLimits(elapsed);
|
|
8820
8920
|
}
|
|
8821
8921
|
/** Returns true if a timeout has occurred without throwing. Useful for races. */
|
|
8822
8922
|
isTimedOut() {
|
|
@@ -11604,6 +11704,107 @@ var FLEET_ROSTER_WITHACP = {
|
|
|
11604
11704
|
...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
|
|
11605
11705
|
};
|
|
11606
11706
|
|
|
11707
|
+
// src/coordination/subagent-nicknames.ts
|
|
11708
|
+
var NICKNAME_POOL = {
|
|
11709
|
+
// Physics & fundamental sciences
|
|
11710
|
+
"einstein": { name: "Einstein", domain: "physics" },
|
|
11711
|
+
"newton": { name: "Newton", domain: "physics" },
|
|
11712
|
+
"feynman": { name: "Feynman", domain: "physics" },
|
|
11713
|
+
"dirac": { name: "Dirac", domain: "physics" },
|
|
11714
|
+
"bohr": { name: "Bohr", domain: "physics" },
|
|
11715
|
+
"planck": { name: "Planck", domain: "physics" },
|
|
11716
|
+
"curie": { name: "Curie", domain: "physics" },
|
|
11717
|
+
"fermi": { name: "Fermi", domain: "physics" },
|
|
11718
|
+
"heisenberg": { name: "Heisenberg", domain: "physics" },
|
|
11719
|
+
"schrodinger": { name: "Schr\xF6dinger", domain: "physics" },
|
|
11720
|
+
// Mathematics
|
|
11721
|
+
"euclid": { name: "Euclid", domain: "math" },
|
|
11722
|
+
"gauss": { name: "Gauss", domain: "math" },
|
|
11723
|
+
"turing": { name: "Turing", domain: "math" },
|
|
11724
|
+
"poincare": { name: "Poincar\xE9", domain: "math" },
|
|
11725
|
+
"riemann": { name: "Riemann", domain: "math" },
|
|
11726
|
+
"hilbert": { name: "Hilbert", domain: "math" },
|
|
11727
|
+
"pythagoras": { name: "Pythagoras", domain: "math" },
|
|
11728
|
+
// Computing & information theory
|
|
11729
|
+
"von-neumann": { name: "Von Neumann", domain: "computing" },
|
|
11730
|
+
"shannon": { name: "Shannon", domain: "computing" },
|
|
11731
|
+
"hopper": { name: "Hopper", domain: "computing" },
|
|
11732
|
+
"backus": { name: "Backus", domain: "computing" },
|
|
11733
|
+
"knuth": { name: "Knuth", domain: "computing" },
|
|
11734
|
+
"torvalds": { name: "Torvalds", domain: "computing" },
|
|
11735
|
+
"stallman": { name: "Stallman", domain: "computing" },
|
|
11736
|
+
"berners-lee": { name: "Berners-Lee", domain: "computing" },
|
|
11737
|
+
"babbage": { name: "Babbage", domain: "computing" },
|
|
11738
|
+
"lovelace": { name: "Lovelace", domain: "computing" },
|
|
11739
|
+
"klein": { name: "Klein", domain: "computing" },
|
|
11740
|
+
// Electronics & electrical engineering
|
|
11741
|
+
"edison": { name: "Edison", domain: "ee" },
|
|
11742
|
+
"tesla": { name: "Tesla", domain: "ee" },
|
|
11743
|
+
"faraday": { name: "Faraday", domain: "ee" },
|
|
11744
|
+
"maxwell": { name: "Maxwell", domain: "ee" },
|
|
11745
|
+
"ohm": { name: "Ohm", domain: "ee" },
|
|
11746
|
+
"bell": { name: "Bell", domain: "ee" },
|
|
11747
|
+
"marconi": { name: "Marconi", domain: "ee" },
|
|
11748
|
+
"lamarr": { name: "Lamarr", domain: "ee" },
|
|
11749
|
+
// General science / multi-disciplinary
|
|
11750
|
+
"darwin": { name: "Darwin", domain: "biology" },
|
|
11751
|
+
"mendel": { name: "Mendel", domain: "biology" },
|
|
11752
|
+
"pasteur": { name: "Pasteur", domain: "biology" },
|
|
11753
|
+
"hawking": { name: "Hawking", domain: "cosmology" },
|
|
11754
|
+
"sagan": { name: "Sagan", domain: "cosmology" },
|
|
11755
|
+
// Chemistry / materials
|
|
11756
|
+
"lavoisier": { name: "Lavoisier", domain: "chemistry" },
|
|
11757
|
+
"mendeleev": { name: "Mendeleev", domain: "chemistry" }
|
|
11758
|
+
};
|
|
11759
|
+
var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
|
|
11760
|
+
var DOMAIN_PREFERENCES = {
|
|
11761
|
+
"security": ["shannon", "turing", "lamarr", "stallman"],
|
|
11762
|
+
"bug-hunter": ["darwin", "curie", "feynman", "fermi"],
|
|
11763
|
+
"refactor": ["gauss", "hilbert", "euclid", "planck"],
|
|
11764
|
+
"audit-log": ["sagan", "hawking", "poincare", "newton"],
|
|
11765
|
+
"planner": ["hilbert", "gauss", "turing", "euclid"],
|
|
11766
|
+
"researcher": ["sagan", "hawking", "darwin", "pasteur"],
|
|
11767
|
+
"explorer": ["marconi", "bell", "columbus", "polo"],
|
|
11768
|
+
"testing": ["pasteur", "curie", "fermi", "bohr"],
|
|
11769
|
+
"frontend": ["lovelace", "hopper", "babbage", "backus"],
|
|
11770
|
+
"backend": ["torvalds", "stallman", "von-neumann", "backus"],
|
|
11771
|
+
"database": ["turing", "shannon", "backus", "knuth"],
|
|
11772
|
+
"devops": ["tesla", "edison", "faraday", "bell"],
|
|
11773
|
+
"security-scanner": ["shannon", "turing", "lamarr", "stallman"],
|
|
11774
|
+
"refactor-planner": ["gauss", "hilbert", "planck", "newton"],
|
|
11775
|
+
"architect": ["von-neumann", "turing", "gauss", "hilbert"],
|
|
11776
|
+
"critic": ["einstein", "feynman", "dirac", "bohr"],
|
|
11777
|
+
"e2e": ["hopper", "bell", "marconi", "tesla"],
|
|
11778
|
+
"performance": ["knuth", "gauss", "planck", "feynman"],
|
|
11779
|
+
"chaos": ["tesla", "edison", "curie", "fermi"],
|
|
11780
|
+
"cost": ["ohm", "bell", "marconi", "tesla"],
|
|
11781
|
+
// default fallback
|
|
11782
|
+
"default": ["einstein", "newton", "curie", "tesla", "edison", "turing", "shannon", "hopper", "knuth", "stallman"]
|
|
11783
|
+
};
|
|
11784
|
+
function assignNickname(role, used) {
|
|
11785
|
+
const preferences = [
|
|
11786
|
+
...DOMAIN_PREFERENCES[role] ?? [],
|
|
11787
|
+
...DOMAIN_PREFERENCES["default"] ?? []
|
|
11788
|
+
];
|
|
11789
|
+
for (const key of preferences) {
|
|
11790
|
+
const entry = NICKNAME_POOL[key];
|
|
11791
|
+
if (entry && !used.has(key)) {
|
|
11792
|
+
return `${entry.name} (${formatRole(role)})`;
|
|
11793
|
+
}
|
|
11794
|
+
}
|
|
11795
|
+
for (const entry of ALL_NICKNAMES) {
|
|
11796
|
+
const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
|
|
11797
|
+
if (key && !used.has(key)) {
|
|
11798
|
+
return `${entry.name} (${formatRole(role)})`;
|
|
11799
|
+
}
|
|
11800
|
+
}
|
|
11801
|
+
const counter = used.size + 1;
|
|
11802
|
+
return `Scientist #${counter} (${formatRole(role)})`;
|
|
11803
|
+
}
|
|
11804
|
+
function formatRole(role) {
|
|
11805
|
+
return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
11806
|
+
}
|
|
11807
|
+
|
|
11607
11808
|
// src/coordination/multi-agent-coordinator.ts
|
|
11608
11809
|
var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
11609
11810
|
coordinatorId;
|
|
@@ -11611,6 +11812,15 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11611
11812
|
runner;
|
|
11612
11813
|
fleetBus;
|
|
11613
11814
|
subagents = /* @__PURE__ */ new Map();
|
|
11815
|
+
/**
|
|
11816
|
+
* Base nickname keys already handed out this run (e.g. `einstein`, `tesla`).
|
|
11817
|
+
* Prevents two workers sharing a name. Direct `coordinator.spawn()` callers
|
|
11818
|
+
* (parallel/eternal engine, SDD parallel run) don't go through
|
|
11819
|
+
* `Director.spawn()` where nicknames are normally assigned, so the
|
|
11820
|
+
* coordinator upgrades placeholder names ("Executor", "slot-ab12cd", role
|
|
11821
|
+
* names) to memorable ones here — that's what surfaces in the fleet monitor.
|
|
11822
|
+
*/
|
|
11823
|
+
usedNicknames = /* @__PURE__ */ new Set();
|
|
11614
11824
|
pendingTasks = [];
|
|
11615
11825
|
completedResults = [];
|
|
11616
11826
|
totalIterations = 0;
|
|
@@ -11659,7 +11869,26 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11659
11869
|
this.config.maxConcurrent = Math.floor(n);
|
|
11660
11870
|
this.tryDispatchNext();
|
|
11661
11871
|
}
|
|
11872
|
+
/**
|
|
11873
|
+
* Upgrade a placeholder/role-derived name to a memorable scientist nickname
|
|
11874
|
+
* (e.g. "Einstein (Executor)"). A name is treated as a placeholder when it is
|
|
11875
|
+
* empty, equals the role (case-insensitive), is a generic default
|
|
11876
|
+
* ("subagent"/"adhoc"/"generic"), or is an auto-generated `slot-…` id.
|
|
11877
|
+
* Explicit, human-chosen names — including nicknames already assigned by
|
|
11878
|
+
* `Director.spawn()` — are left untouched, so this never double-assigns.
|
|
11879
|
+
*/
|
|
11880
|
+
withNickname(subagent) {
|
|
11881
|
+
const role = subagent.role ?? "subagent";
|
|
11882
|
+
const name = subagent.name?.trim() ?? "";
|
|
11883
|
+
const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
|
|
11884
|
+
if (!isPlaceholder) return subagent;
|
|
11885
|
+
const nickname = assignNickname(role, this.usedNicknames);
|
|
11886
|
+
const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
11887
|
+
this.usedNicknames.add(baseKey);
|
|
11888
|
+
return { ...subagent, name: nickname };
|
|
11889
|
+
}
|
|
11662
11890
|
async spawn(subagent) {
|
|
11891
|
+
subagent = this.withNickname(subagent);
|
|
11663
11892
|
const id = subagent.id || randomUUID();
|
|
11664
11893
|
if (this.subagents.has(id)) {
|
|
11665
11894
|
throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
|
|
@@ -12253,12 +12482,12 @@ var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
|
|
|
12253
12482
|
var ParallelEternalEngine = class {
|
|
12254
12483
|
constructor(opts) {
|
|
12255
12484
|
this.opts = opts;
|
|
12256
|
-
this.goalPath = goalFilePath(opts.projectRoot);
|
|
12485
|
+
this.goalPath = opts.goalPath ?? goalFilePath(opts.projectRoot);
|
|
12257
12486
|
this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
|
|
12258
12487
|
this.timeoutMs = opts.iterationTimeoutMs ?? 3e5;
|
|
12259
12488
|
this.dispatchEnabled = opts.dispatch !== false;
|
|
12260
12489
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
12261
|
-
this.agentFactory = opts.subagentFactory ?? (async (
|
|
12490
|
+
this.agentFactory = opts.subagentFactory ?? (async (_config) => ({
|
|
12262
12491
|
agent: this.opts.agent,
|
|
12263
12492
|
events: this.opts.agent.events
|
|
12264
12493
|
}));
|
|
@@ -12352,7 +12581,6 @@ var ParallelEternalEngine = class {
|
|
|
12352
12581
|
}
|
|
12353
12582
|
const tasks = await this.decomposeGoal(goal);
|
|
12354
12583
|
if (!tasks || tasks.length === 0) {
|
|
12355
|
-
await sleep2(5e3);
|
|
12356
12584
|
return false;
|
|
12357
12585
|
}
|
|
12358
12586
|
const fanOut = await this.fanOut(goal, tasks);
|
|
@@ -12373,6 +12601,7 @@ var ParallelEternalEngine = class {
|
|
|
12373
12601
|
});
|
|
12374
12602
|
if (fanOut.goalComplete) {
|
|
12375
12603
|
this.stopRequested = true;
|
|
12604
|
+
this.state = "stopped";
|
|
12376
12605
|
return true;
|
|
12377
12606
|
}
|
|
12378
12607
|
await this.maybeCompact();
|
|
@@ -12553,7 +12782,11 @@ ${lastFew}` : "No prior iterations.",
|
|
|
12553
12782
|
} finally {
|
|
12554
12783
|
clearTimeout(timer);
|
|
12555
12784
|
}
|
|
12556
|
-
} catch {
|
|
12785
|
+
} catch (err) {
|
|
12786
|
+
this.opts.onError?.(
|
|
12787
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
12788
|
+
this.consecutiveFailures
|
|
12789
|
+
);
|
|
12557
12790
|
return [];
|
|
12558
12791
|
}
|
|
12559
12792
|
}
|
|
@@ -12770,7 +13003,11 @@ Working rules:
|
|
|
12770
13003
|
thrashing, terminate it rather than letting cost climb silently.
|
|
12771
13004
|
6. Never claim a subagent's work as your own without verifying it. If a
|
|
12772
13005
|
result looks wrong, ask_subagent for clarification before passing it
|
|
12773
|
-
to the user
|
|
13006
|
+
to the user.
|
|
13007
|
+
7. Wind down when satisfied. When the results are good enough, call
|
|
13008
|
+
work_complete \u2014 no new subagents will spawn and queued tasks complete
|
|
13009
|
+
as aborted. Running subagents finish naturally. Call terminate_subagent
|
|
13010
|
+
only for ones you need to stop immediately.`;
|
|
12774
13011
|
var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
|
|
12775
13012
|
a specific slice of a larger plan \u2014 do that slice well and report back.
|
|
12776
13013
|
|
|
@@ -12915,14 +13152,12 @@ var FleetBus = class {
|
|
|
12915
13152
|
};
|
|
12916
13153
|
var FleetUsageAggregator = class {
|
|
12917
13154
|
constructor(bus, priceLookup, metaLookup) {
|
|
12918
|
-
this.bus = bus;
|
|
12919
13155
|
this.priceLookup = priceLookup;
|
|
12920
13156
|
this.metaLookup = metaLookup;
|
|
12921
13157
|
bus.filter("provider.response", (e) => this.onProviderResponse(e));
|
|
12922
13158
|
bus.filter("tool.executed", (e) => this.onToolExecuted(e));
|
|
12923
13159
|
bus.filter("iteration.started", (e) => this.onIterationStarted(e));
|
|
12924
13160
|
}
|
|
12925
|
-
bus;
|
|
12926
13161
|
priceLookup;
|
|
12927
13162
|
metaLookup;
|
|
12928
13163
|
perSubagent = /* @__PURE__ */ new Map();
|
|
@@ -12990,106 +13225,6 @@ var FleetUsageAggregator = class {
|
|
|
12990
13225
|
snap.lastEventAt = e.ts;
|
|
12991
13226
|
}
|
|
12992
13227
|
};
|
|
12993
|
-
|
|
12994
|
-
// src/coordination/subagent-nicknames.ts
|
|
12995
|
-
var NICKNAME_POOL = {
|
|
12996
|
-
// Physics & fundamental sciences
|
|
12997
|
-
"einstein": { name: "Einstein", domain: "physics" },
|
|
12998
|
-
"newton": { name: "Newton", domain: "physics" },
|
|
12999
|
-
"feynman": { name: "Feynman", domain: "physics" },
|
|
13000
|
-
"dirac": { name: "Dirac", domain: "physics" },
|
|
13001
|
-
"bohr": { name: "Bohr", domain: "physics" },
|
|
13002
|
-
"planck": { name: "Planck", domain: "physics" },
|
|
13003
|
-
"curie": { name: "Curie", domain: "physics" },
|
|
13004
|
-
"fermi": { name: "Fermi", domain: "physics" },
|
|
13005
|
-
"heisenberg": { name: "Heisenberg", domain: "physics" },
|
|
13006
|
-
"schrodinger": { name: "Schr\xF6dinger", domain: "physics" },
|
|
13007
|
-
// Mathematics
|
|
13008
|
-
"euclid": { name: "Euclid", domain: "math" },
|
|
13009
|
-
"gauss": { name: "Gauss", domain: "math" },
|
|
13010
|
-
"turing": { name: "Turing", domain: "math" },
|
|
13011
|
-
"poincare": { name: "Poincar\xE9", domain: "math" },
|
|
13012
|
-
"riemann": { name: "Riemann", domain: "math" },
|
|
13013
|
-
"hilbert": { name: "Hilbert", domain: "math" },
|
|
13014
|
-
"pythagoras": { name: "Pythagoras", domain: "math" },
|
|
13015
|
-
// Computing & information theory
|
|
13016
|
-
"von-neumann": { name: "Von Neumann", domain: "computing" },
|
|
13017
|
-
"shannon": { name: "Shannon", domain: "computing" },
|
|
13018
|
-
"hopper": { name: "Hopper", domain: "computing" },
|
|
13019
|
-
"backus": { name: "Backus", domain: "computing" },
|
|
13020
|
-
"knuth": { name: "Knuth", domain: "computing" },
|
|
13021
|
-
"torvalds": { name: "Torvalds", domain: "computing" },
|
|
13022
|
-
"stallman": { name: "Stallman", domain: "computing" },
|
|
13023
|
-
"berners-lee": { name: "Berners-Lee", domain: "computing" },
|
|
13024
|
-
"babbage": { name: "Babbage", domain: "computing" },
|
|
13025
|
-
"lovelace": { name: "Lovelace", domain: "computing" },
|
|
13026
|
-
"klein": { name: "Klein", domain: "computing" },
|
|
13027
|
-
// Electronics & electrical engineering
|
|
13028
|
-
"edison": { name: "Edison", domain: "ee" },
|
|
13029
|
-
"tesla": { name: "Tesla", domain: "ee" },
|
|
13030
|
-
"faraday": { name: "Faraday", domain: "ee" },
|
|
13031
|
-
"maxwell": { name: "Maxwell", domain: "ee" },
|
|
13032
|
-
"ohm": { name: "Ohm", domain: "ee" },
|
|
13033
|
-
"bell": { name: "Bell", domain: "ee" },
|
|
13034
|
-
"marconi": { name: "Marconi", domain: "ee" },
|
|
13035
|
-
"lamarr": { name: "Lamarr", domain: "ee" },
|
|
13036
|
-
// General science / multi-disciplinary
|
|
13037
|
-
"darwin": { name: "Darwin", domain: "biology" },
|
|
13038
|
-
"mendel": { name: "Mendel", domain: "biology" },
|
|
13039
|
-
"pasteur": { name: "Pasteur", domain: "biology" },
|
|
13040
|
-
"hawking": { name: "Hawking", domain: "cosmology" },
|
|
13041
|
-
"sagan": { name: "Sagan", domain: "cosmology" },
|
|
13042
|
-
// Chemistry / materials
|
|
13043
|
-
"lavoisier": { name: "Lavoisier", domain: "chemistry" },
|
|
13044
|
-
"mendeleev": { name: "Mendeleev", domain: "chemistry" }
|
|
13045
|
-
};
|
|
13046
|
-
var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
|
|
13047
|
-
var DOMAIN_PREFERENCES = {
|
|
13048
|
-
"security": ["shannon", "turing", "lamarr", "stallman"],
|
|
13049
|
-
"bug-hunter": ["darwin", "curie", "feynman", "fermi"],
|
|
13050
|
-
"refactor": ["gauss", "hilbert", "euclid", "planck"],
|
|
13051
|
-
"audit-log": ["sagan", "hawking", "poincare", "newton"],
|
|
13052
|
-
"planner": ["hilbert", "gauss", "turing", "euclid"],
|
|
13053
|
-
"researcher": ["sagan", "hawking", "darwin", "pasteur"],
|
|
13054
|
-
"explorer": ["marconi", "bell", "columbus", "polo"],
|
|
13055
|
-
"testing": ["pasteur", "curie", "fermi", "bohr"],
|
|
13056
|
-
"frontend": ["lovelace", "hopper", "babbage", "backus"],
|
|
13057
|
-
"backend": ["torvalds", "stallman", "von-neumann", "backus"],
|
|
13058
|
-
"database": ["turing", "shannon", "backus", "knuth"],
|
|
13059
|
-
"devops": ["tesla", "edison", "faraday", "bell"],
|
|
13060
|
-
"security-scanner": ["shannon", "turing", "lamarr", "stallman"],
|
|
13061
|
-
"refactor-planner": ["gauss", "hilbert", "planck", "newton"],
|
|
13062
|
-
"architect": ["von-neumann", "turing", "gauss", "hilbert"],
|
|
13063
|
-
"critic": ["einstein", "feynman", "dirac", "bohr"],
|
|
13064
|
-
"e2e": ["hopper", "bell", "marconi", "tesla"],
|
|
13065
|
-
"performance": ["knuth", "gauss", "planck", "feynman"],
|
|
13066
|
-
"chaos": ["tesla", "edison", "curie", "fermi"],
|
|
13067
|
-
"cost": ["oh", "bell", "marconi", "tesla"],
|
|
13068
|
-
// default fallback
|
|
13069
|
-
"default": ["einstein", "newton", "curie", "tesla", "edison", "turing", "shannon", "hopper", "knuth", "stallman"]
|
|
13070
|
-
};
|
|
13071
|
-
function assignNickname(role, used) {
|
|
13072
|
-
const preferences = [
|
|
13073
|
-
...DOMAIN_PREFERENCES[role] ?? [],
|
|
13074
|
-
...DOMAIN_PREFERENCES["default"] ?? []
|
|
13075
|
-
];
|
|
13076
|
-
for (const key of preferences) {
|
|
13077
|
-
if (!used.has(key)) {
|
|
13078
|
-
return `${NICKNAME_POOL[key].name} (${formatRole(role)})`;
|
|
13079
|
-
}
|
|
13080
|
-
}
|
|
13081
|
-
for (const entry of ALL_NICKNAMES) {
|
|
13082
|
-
const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
|
|
13083
|
-
if (key && !used.has(key)) {
|
|
13084
|
-
return `${entry.name} (${formatRole(role)})`;
|
|
13085
|
-
}
|
|
13086
|
-
}
|
|
13087
|
-
const counter = used.size + 1;
|
|
13088
|
-
return `Scientist #${counter} (${formatRole(role)})`;
|
|
13089
|
-
}
|
|
13090
|
-
function formatRole(role) {
|
|
13091
|
-
return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
13092
|
-
}
|
|
13093
13228
|
function makeSpawnTool(director, roster) {
|
|
13094
13229
|
const inputSchema = {
|
|
13095
13230
|
type: "object",
|
|
@@ -13260,7 +13395,7 @@ function makeRollUpTool(director) {
|
|
|
13260
13395
|
function makeTerminateTool(director) {
|
|
13261
13396
|
return {
|
|
13262
13397
|
name: "terminate_subagent",
|
|
13263
|
-
description:
|
|
13398
|
+
description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
|
|
13264
13399
|
permission: "auto",
|
|
13265
13400
|
mutating: true,
|
|
13266
13401
|
inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
|
|
@@ -13271,6 +13406,19 @@ function makeTerminateTool(director) {
|
|
|
13271
13406
|
}
|
|
13272
13407
|
};
|
|
13273
13408
|
}
|
|
13409
|
+
function makeTerminateAllTool(director) {
|
|
13410
|
+
return {
|
|
13411
|
+
name: "terminate_all",
|
|
13412
|
+
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.',
|
|
13413
|
+
permission: "auto",
|
|
13414
|
+
mutating: true,
|
|
13415
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
13416
|
+
async execute() {
|
|
13417
|
+
await director.terminateAll();
|
|
13418
|
+
return { ok: true, message: `Fleet shutdown complete \u2014 all subagents stopped, pending tasks drained.` };
|
|
13419
|
+
}
|
|
13420
|
+
};
|
|
13421
|
+
}
|
|
13274
13422
|
function makeFleetStatusTool(director) {
|
|
13275
13423
|
return {
|
|
13276
13424
|
name: "fleet_status",
|
|
@@ -13413,7 +13561,7 @@ function makeCollabDebugTool(director) {
|
|
|
13413
13561
|
function makeFleetEmitTool(director) {
|
|
13414
13562
|
return {
|
|
13415
13563
|
name: "fleet_emit",
|
|
13416
|
-
description: "Emit a structured event on the FleetBus.
|
|
13564
|
+
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.",
|
|
13417
13565
|
permission: "auto",
|
|
13418
13566
|
mutating: false,
|
|
13419
13567
|
inputSchema: {
|
|
@@ -13421,14 +13569,14 @@ function makeFleetEmitTool(director) {
|
|
|
13421
13569
|
properties: {
|
|
13422
13570
|
type: {
|
|
13423
13571
|
type: "string",
|
|
13424
|
-
description: "Event type
|
|
13572
|
+
description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
|
|
13425
13573
|
},
|
|
13426
13574
|
payload: {
|
|
13427
13575
|
type: "object",
|
|
13428
|
-
description: "Event payload
|
|
13576
|
+
description: "Event payload. Structure depends on event type. Use null if no payload."
|
|
13429
13577
|
}
|
|
13430
13578
|
},
|
|
13431
|
-
required: ["type"
|
|
13579
|
+
required: ["type"]
|
|
13432
13580
|
},
|
|
13433
13581
|
async execute(input) {
|
|
13434
13582
|
const i = input;
|
|
@@ -13436,12 +13584,25 @@ function makeFleetEmitTool(director) {
|
|
|
13436
13584
|
subagentId: director.id,
|
|
13437
13585
|
ts: Date.now(),
|
|
13438
13586
|
type: i.type,
|
|
13439
|
-
payload: i.payload
|
|
13587
|
+
payload: i.payload ?? {}
|
|
13440
13588
|
});
|
|
13441
13589
|
return { ok: true, event: i.type };
|
|
13442
13590
|
}
|
|
13443
13591
|
};
|
|
13444
13592
|
}
|
|
13593
|
+
function makeWorkCompleteTool(director) {
|
|
13594
|
+
return {
|
|
13595
|
+
name: "work_complete",
|
|
13596
|
+
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.",
|
|
13597
|
+
permission: "auto",
|
|
13598
|
+
mutating: false,
|
|
13599
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
13600
|
+
async execute() {
|
|
13601
|
+
director.workComplete();
|
|
13602
|
+
return { ok: true, message: "Fleet wind-down signaled. No new spawns or task dispatches." };
|
|
13603
|
+
}
|
|
13604
|
+
};
|
|
13605
|
+
}
|
|
13445
13606
|
var CollabSession = class extends EventEmitter {
|
|
13446
13607
|
sessionId;
|
|
13447
13608
|
options;
|
|
@@ -13456,6 +13617,14 @@ var CollabSession = class extends EventEmitter {
|
|
|
13456
13617
|
disposers = new Array();
|
|
13457
13618
|
settled = false;
|
|
13458
13619
|
timeoutMs;
|
|
13620
|
+
cancelled = false;
|
|
13621
|
+
alerts = [];
|
|
13622
|
+
/** Tracks tool call counts per subagent for progress-based timeout decisions. */
|
|
13623
|
+
progressBySubagent = /* @__PURE__ */ new Map();
|
|
13624
|
+
/** Last tool call count when a timeout warning was handled. */
|
|
13625
|
+
lastTimeoutProgress = /* @__PURE__ */ new Map();
|
|
13626
|
+
/** Session-level timeout timer handle (cleared on cancel or natural completion). */
|
|
13627
|
+
_timeoutTimer;
|
|
13459
13628
|
constructor(director, fleetBus, options) {
|
|
13460
13629
|
super();
|
|
13461
13630
|
this.sessionId = randomUUID();
|
|
@@ -13473,11 +13642,23 @@ var CollabSession = class extends EventEmitter {
|
|
|
13473
13642
|
};
|
|
13474
13643
|
}
|
|
13475
13644
|
}
|
|
13645
|
+
get id() {
|
|
13646
|
+
return this.sessionId;
|
|
13647
|
+
}
|
|
13648
|
+
getSessionAlerts() {
|
|
13649
|
+
return [...this.alerts];
|
|
13650
|
+
}
|
|
13651
|
+
isCancelled() {
|
|
13652
|
+
return this.cancelled;
|
|
13653
|
+
}
|
|
13476
13654
|
/**
|
|
13477
|
-
*
|
|
13478
|
-
*
|
|
13479
|
-
*
|
|
13655
|
+
* Snapshot of role → subagentId map. The Director calls coordinator.stop()
|
|
13656
|
+
* for each agent when cancelling the session, using this map to enumerate
|
|
13657
|
+
* all three collab agents.
|
|
13480
13658
|
*/
|
|
13659
|
+
getSubagentIds() {
|
|
13660
|
+
return new Map(this.subagentIds);
|
|
13661
|
+
}
|
|
13481
13662
|
async buildSnapshot() {
|
|
13482
13663
|
if (this.snapshot.files.length > 0) return this.snapshot;
|
|
13483
13664
|
for (const filePath of this.options.targetPaths) {
|
|
@@ -13493,9 +13674,30 @@ var CollabSession = class extends EventEmitter {
|
|
|
13493
13674
|
return this.snapshot;
|
|
13494
13675
|
}
|
|
13495
13676
|
/**
|
|
13496
|
-
*
|
|
13497
|
-
*
|
|
13677
|
+
* Cancel the session. Emits director.cancel_collab on the FleetBus so all
|
|
13678
|
+
* collab agents finish early. The session-level timeout timer is also cleared.
|
|
13679
|
+
* Safe to call multiple times (idempotent after first call).
|
|
13498
13680
|
*/
|
|
13681
|
+
cancel(reason = "Director cancelled collab session") {
|
|
13682
|
+
if (this.settled) return;
|
|
13683
|
+
this.cancelled = true;
|
|
13684
|
+
if (this._timeoutTimer) {
|
|
13685
|
+
clearTimeout(this._timeoutTimer);
|
|
13686
|
+
this._timeoutTimer = void 0;
|
|
13687
|
+
}
|
|
13688
|
+
this.fleetBus.emit({
|
|
13689
|
+
subagentId: this.director.id,
|
|
13690
|
+
ts: Date.now(),
|
|
13691
|
+
type: "director.cancel_collab",
|
|
13692
|
+
payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
13693
|
+
});
|
|
13694
|
+
this.fleetBus.emit({
|
|
13695
|
+
subagentId: this.director.id,
|
|
13696
|
+
ts: Date.now(),
|
|
13697
|
+
type: "collab.cancelled",
|
|
13698
|
+
payload: { sessionId: this.sessionId, reason }
|
|
13699
|
+
});
|
|
13700
|
+
}
|
|
13499
13701
|
async start() {
|
|
13500
13702
|
if (this.settled) throw new Error("session already settled");
|
|
13501
13703
|
this.settled = true;
|
|
@@ -13510,10 +13712,10 @@ var CollabSession = class extends EventEmitter {
|
|
|
13510
13712
|
this.subagentIds.set("refactor-planner", refactorPlannerId);
|
|
13511
13713
|
this.subagentIds.set("critic", criticId);
|
|
13512
13714
|
const timeout = new Promise((_, reject) => {
|
|
13513
|
-
setTimeout(
|
|
13514
|
-
(
|
|
13515
|
-
this.timeoutMs
|
|
13516
|
-
);
|
|
13715
|
+
this._timeoutTimer = setTimeout(() => {
|
|
13716
|
+
this.cancel("Session-level timeout reached");
|
|
13717
|
+
reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
|
|
13718
|
+
}, this.timeoutMs);
|
|
13517
13719
|
});
|
|
13518
13720
|
let results;
|
|
13519
13721
|
try {
|
|
@@ -13526,6 +13728,10 @@ var CollabSession = class extends EventEmitter {
|
|
|
13526
13728
|
timeout
|
|
13527
13729
|
]);
|
|
13528
13730
|
} catch (err) {
|
|
13731
|
+
if (this._timeoutTimer) {
|
|
13732
|
+
clearTimeout(this._timeoutTimer);
|
|
13733
|
+
this._timeoutTimer = void 0;
|
|
13734
|
+
}
|
|
13529
13735
|
this.cleanup();
|
|
13530
13736
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
13531
13737
|
this.emit("session.error", error);
|
|
@@ -13539,17 +13745,6 @@ var CollabSession = class extends EventEmitter {
|
|
|
13539
13745
|
this.emit("session.done", report);
|
|
13540
13746
|
return report;
|
|
13541
13747
|
}
|
|
13542
|
-
/**
|
|
13543
|
-
* Parse a completed agent's final text output and route the structured
|
|
13544
|
-
* findings onto the FleetBus. Agents are instructed to emit one JSON object
|
|
13545
|
-
* per finding / plan / evaluation; we scan the output for balanced JSON
|
|
13546
|
-
* objects (tolerating prose, markdown, and ```json fences around them) and
|
|
13547
|
-
* dispatch each by its discriminating key. The wired FleetBus listeners then
|
|
13548
|
-
* collect them into the bugs / plans / evaluations maps.
|
|
13549
|
-
*
|
|
13550
|
-
* Failed or empty results are ignored, and a single malformed object never
|
|
13551
|
-
* aborts the collation pass — see {@link extractJsonObjects}.
|
|
13552
|
-
*/
|
|
13553
13748
|
async parseAndEmit(result) {
|
|
13554
13749
|
if (result.status !== "success" || result.result == null) return;
|
|
13555
13750
|
const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
|
|
@@ -13565,12 +13760,6 @@ var CollabSession = class extends EventEmitter {
|
|
|
13565
13760
|
});
|
|
13566
13761
|
}
|
|
13567
13762
|
}
|
|
13568
|
-
/**
|
|
13569
|
-
* Extract every top-level JSON object embedded in free-form agent text.
|
|
13570
|
-
* Scans for balanced braces while respecting string literals and escape
|
|
13571
|
-
* sequences, so prose, markdown headers, and code fences around the JSON
|
|
13572
|
-
* are skipped. Malformed spans are dropped silently rather than thrown.
|
|
13573
|
-
*/
|
|
13574
13763
|
extractJsonObjects(text) {
|
|
13575
13764
|
const objects = [];
|
|
13576
13765
|
let depth = 0;
|
|
@@ -13607,25 +13796,33 @@ var CollabSession = class extends EventEmitter {
|
|
|
13607
13796
|
}
|
|
13608
13797
|
return objects;
|
|
13609
13798
|
}
|
|
13799
|
+
budgetForRole(role) {
|
|
13800
|
+
if (this.options.budgetOverrides?.[role]) {
|
|
13801
|
+
return this.options.budgetOverrides[role];
|
|
13802
|
+
}
|
|
13803
|
+
const defaults = {
|
|
13804
|
+
"bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
|
|
13805
|
+
"refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
|
|
13806
|
+
"critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
|
|
13807
|
+
};
|
|
13808
|
+
return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
|
|
13809
|
+
}
|
|
13610
13810
|
async spawnAgent(role, taskBrief) {
|
|
13811
|
+
const budget = this.budgetForRole(role);
|
|
13611
13812
|
const cfg = {
|
|
13612
13813
|
id: `${role}-${this.sessionId}`,
|
|
13613
13814
|
name: role,
|
|
13614
|
-
role
|
|
13615
|
-
|
|
13616
|
-
|
|
13617
|
-
|
|
13815
|
+
role,
|
|
13816
|
+
tools: ["fleet_emit", "fleet_status", "read", "grep", "glob", "bash", "write"],
|
|
13817
|
+
maxIterations: budget.maxIterations,
|
|
13818
|
+
maxToolCalls: budget.maxToolCalls,
|
|
13819
|
+
timeoutMs: budget.timeoutMs
|
|
13618
13820
|
};
|
|
13619
13821
|
const subagentId = await this.director.spawn(cfg);
|
|
13620
|
-
await this.director.assign({
|
|
13621
|
-
id: randomUUID(),
|
|
13622
|
-
subagentId,
|
|
13623
|
-
description: taskBrief
|
|
13624
|
-
});
|
|
13822
|
+
await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
|
|
13625
13823
|
return subagentId;
|
|
13626
13824
|
}
|
|
13627
13825
|
buildBugHunterTask() {
|
|
13628
|
-
this.options.targetPaths.join(", ");
|
|
13629
13826
|
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
13630
13827
|
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
13631
13828
|
${f.content}`).join("\n\n");
|
|
@@ -13634,13 +13831,17 @@ ${f.content}`).join("\n\n");
|
|
|
13634
13831
|
Target files:
|
|
13635
13832
|
${fileContents}
|
|
13636
13833
|
|
|
13637
|
-
For each bug found,
|
|
13638
|
-
{ "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } }
|
|
13639
|
-
|
|
13640
|
-
|
|
13834
|
+
For each bug found, emit it using the fleet_emit tool immediately:
|
|
13835
|
+
{ "type": "bug.found", "payload": { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } } }
|
|
13836
|
+
|
|
13837
|
+
After scanning all files, write your full markdown bug report to:
|
|
13838
|
+
${scratchpad}/bug-hunter-report-${this.sessionId}.md
|
|
13839
|
+
|
|
13840
|
+
Important: emit each finding as soon as you find it. Do not batch or wait until the end.`;
|
|
13641
13841
|
}
|
|
13642
13842
|
buildRefactorPlannerTask() {
|
|
13643
13843
|
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
13844
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
13644
13845
|
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
13645
13846
|
${f.content}`).join("\n\n");
|
|
13646
13847
|
return `You are RefactorPlanner. Plan refactorings for the following files.
|
|
@@ -13648,26 +13849,146 @@ ${f.content}`).join("\n\n");
|
|
|
13648
13849
|
Target files:
|
|
13649
13850
|
${fileContents}
|
|
13650
13851
|
|
|
13651
|
-
|
|
13652
|
-
|
|
13653
|
-
|
|
13654
|
-
|
|
13852
|
+
Read the BugHunter report at: ${bugHunterReportPath}
|
|
13853
|
+
|
|
13854
|
+
For each bug you can address, emit a refactor plan using fleet_emit:
|
|
13855
|
+
{ "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>" } } }
|
|
13856
|
+
|
|
13857
|
+
Also write your full markdown plan to:
|
|
13858
|
+
${scratchpad}/refactor-plan-${this.sessionId}.md
|
|
13859
|
+
|
|
13860
|
+
Emit each plan immediately. Do not wait until planning is complete.`;
|
|
13655
13861
|
}
|
|
13656
13862
|
buildCriticTask() {
|
|
13657
13863
|
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
13864
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
13865
|
+
const refactorPlanPath = `${scratchpad}/refactor-plan-${this.sessionId}.md`;
|
|
13658
13866
|
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
13659
13867
|
${f.content}`).join("\n\n");
|
|
13660
|
-
return `You are Critic. Evaluate bug findings and refactor plans
|
|
13868
|
+
return `You are Critic. Evaluate bug findings and refactor plans.
|
|
13661
13869
|
|
|
13662
13870
|
Target files:
|
|
13663
13871
|
${fileContents}
|
|
13664
13872
|
|
|
13665
|
-
|
|
13666
|
-
|
|
13667
|
-
|
|
13668
|
-
|
|
13873
|
+
Read the BugHunter report at: ${bugHunterReportPath}
|
|
13874
|
+
Read the RefactorPlanner report at: ${refactorPlanPath}
|
|
13875
|
+
|
|
13876
|
+
For each bug and refactor plan, emit your evaluation using fleet_emit:
|
|
13877
|
+
{ "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>" }] } } }
|
|
13878
|
+
|
|
13879
|
+
After all evaluations, write your markdown report to:
|
|
13880
|
+
${scratchpad}/critic-report-${this.sessionId}.md
|
|
13881
|
+
|
|
13882
|
+
Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
13669
13883
|
}
|
|
13670
13884
|
wireFleetBus() {
|
|
13885
|
+
const dTool = this.fleetBus.filter("tool.executed", (e) => {
|
|
13886
|
+
this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
|
|
13887
|
+
});
|
|
13888
|
+
this.disposers.push(dTool);
|
|
13889
|
+
const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
|
|
13890
|
+
const payload = e.payload;
|
|
13891
|
+
const role = this.roleFromSubagentId(e.subagentId);
|
|
13892
|
+
if (!role) return;
|
|
13893
|
+
const btwNotes = this.director.getLeaderBtwNotes();
|
|
13894
|
+
const alert = {
|
|
13895
|
+
sessionId: this.sessionId,
|
|
13896
|
+
subagentId: e.subagentId,
|
|
13897
|
+
role,
|
|
13898
|
+
level: "warning" /* WARNING */,
|
|
13899
|
+
message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
|
|
13900
|
+
budgetKind: payload.kind,
|
|
13901
|
+
elapsedMs: payload.timeoutMs,
|
|
13902
|
+
limit: payload.limit,
|
|
13903
|
+
btwNotes
|
|
13904
|
+
};
|
|
13905
|
+
this.alerts.push(alert);
|
|
13906
|
+
this.fleetBus.emit({
|
|
13907
|
+
subagentId: e.subagentId,
|
|
13908
|
+
ts: Date.now(),
|
|
13909
|
+
type: "collab.warning",
|
|
13910
|
+
payload: alert
|
|
13911
|
+
});
|
|
13912
|
+
const decision = this.options.onBudgetWarning?.(alert) ?? "ignore";
|
|
13913
|
+
if (decision === "cancel") {
|
|
13914
|
+
this.cancel(`Director cancelled: ${role} ${payload.kind} threshold`);
|
|
13915
|
+
return;
|
|
13916
|
+
}
|
|
13917
|
+
if (payload.kind === "timeout") {
|
|
13918
|
+
const progress = this.progressBySubagent.get(e.subagentId) ?? 0;
|
|
13919
|
+
const lastProgress = this.lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
13920
|
+
if (progress <= lastProgress) {
|
|
13921
|
+
payload.deny();
|
|
13922
|
+
return;
|
|
13923
|
+
}
|
|
13924
|
+
this.lastTimeoutProgress.set(e.subagentId, progress);
|
|
13925
|
+
const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
|
|
13926
|
+
setImmediate(() => {
|
|
13927
|
+
payload.extend({ timeoutMs: newLimit });
|
|
13928
|
+
});
|
|
13929
|
+
return;
|
|
13930
|
+
}
|
|
13931
|
+
if (decision === "extend") {
|
|
13932
|
+
setImmediate(() => {
|
|
13933
|
+
const base = Math.max(payload.limit, payload.used);
|
|
13934
|
+
const extra = {};
|
|
13935
|
+
switch (payload.kind) {
|
|
13936
|
+
case "iterations":
|
|
13937
|
+
extra.maxIterations = Math.min(Math.ceil(base * 1.5), 5e4);
|
|
13938
|
+
break;
|
|
13939
|
+
case "tool_calls":
|
|
13940
|
+
extra.maxToolCalls = Math.min(Math.ceil(base * 1.5), 1e5);
|
|
13941
|
+
break;
|
|
13942
|
+
case "tokens":
|
|
13943
|
+
extra.maxTokens = Math.min(Math.ceil(base * 1.5), 5e6);
|
|
13944
|
+
break;
|
|
13945
|
+
case "cost":
|
|
13946
|
+
extra.maxCostUsd = Math.min(base * 1.5, 100);
|
|
13947
|
+
break;
|
|
13948
|
+
}
|
|
13949
|
+
payload.extend(extra);
|
|
13950
|
+
});
|
|
13951
|
+
return;
|
|
13952
|
+
}
|
|
13953
|
+
if (payload.kind !== "timeout") {
|
|
13954
|
+
setImmediate(() => {
|
|
13955
|
+
const base = Math.max(payload.limit, payload.used);
|
|
13956
|
+
const extra = {};
|
|
13957
|
+
switch (payload.kind) {
|
|
13958
|
+
case "iterations":
|
|
13959
|
+
extra.maxIterations = Math.min(Math.ceil(base * 1.25), 5e4);
|
|
13960
|
+
break;
|
|
13961
|
+
case "tool_calls":
|
|
13962
|
+
extra.maxToolCalls = Math.min(Math.ceil(base * 1.25), 1e5);
|
|
13963
|
+
break;
|
|
13964
|
+
case "tokens":
|
|
13965
|
+
extra.maxTokens = Math.min(Math.ceil(base * 1.25), 5e6);
|
|
13966
|
+
break;
|
|
13967
|
+
case "cost":
|
|
13968
|
+
extra.maxCostUsd = Math.min(base * 1.25, 100);
|
|
13969
|
+
break;
|
|
13970
|
+
}
|
|
13971
|
+
payload.extend(extra);
|
|
13972
|
+
});
|
|
13973
|
+
}
|
|
13974
|
+
});
|
|
13975
|
+
this.disposers.push(dBudget);
|
|
13976
|
+
const dCancel = this.fleetBus.filter("director.cancel_collab", (e) => {
|
|
13977
|
+
const payload = e.payload;
|
|
13978
|
+
if (payload.sessionId !== this.sessionId) return;
|
|
13979
|
+
this.cancelled = true;
|
|
13980
|
+
if (this._timeoutTimer) {
|
|
13981
|
+
clearTimeout(this._timeoutTimer);
|
|
13982
|
+
this._timeoutTimer = void 0;
|
|
13983
|
+
}
|
|
13984
|
+
this.fleetBus.emit({
|
|
13985
|
+
subagentId: this.director.id,
|
|
13986
|
+
ts: Date.now(),
|
|
13987
|
+
type: "collab.cancelled",
|
|
13988
|
+
payload: { sessionId: this.sessionId, reason: payload.reason }
|
|
13989
|
+
});
|
|
13990
|
+
});
|
|
13991
|
+
this.disposers.push(dCancel);
|
|
13671
13992
|
const d1 = this.fleetBus.filter("bug.found", (e) => {
|
|
13672
13993
|
const payload = e.payload;
|
|
13673
13994
|
if (payload?.finding) {
|
|
@@ -13693,10 +14014,19 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
|
|
|
13693
14014
|
});
|
|
13694
14015
|
this.disposers.push(d3);
|
|
13695
14016
|
}
|
|
14017
|
+
roleFromSubagentId(subagentId) {
|
|
14018
|
+
for (const [role, id] of this.subagentIds) {
|
|
14019
|
+
if (id === subagentId) return role;
|
|
14020
|
+
}
|
|
14021
|
+
const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
|
|
14022
|
+
return match?.[1] ?? null;
|
|
14023
|
+
}
|
|
13696
14024
|
assembleReport() {
|
|
13697
14025
|
const bugList = Array.from(this.bugs.values());
|
|
13698
14026
|
const planList = Array.from(this.plans.values());
|
|
13699
14027
|
const evalList = Array.from(this.evaluations.values());
|
|
14028
|
+
let disposition = "completed";
|
|
14029
|
+
if (this.cancelled) disposition = "cancelled";
|
|
13700
14030
|
const verdictOrder = {
|
|
13701
14031
|
approve: 0,
|
|
13702
14032
|
needs_revision: 1,
|
|
@@ -13710,33 +14040,41 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
|
|
|
13710
14040
|
},
|
|
13711
14041
|
"approve"
|
|
13712
14042
|
);
|
|
13713
|
-
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict);
|
|
14043
|
+
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
|
|
13714
14044
|
return {
|
|
13715
14045
|
sessionId: this.sessionId,
|
|
13716
14046
|
startedAt: this.snapshot.createdAt,
|
|
13717
14047
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13718
14048
|
targetPaths: this.options.targetPaths,
|
|
14049
|
+
disposition,
|
|
13719
14050
|
bugs: bugList,
|
|
13720
14051
|
refactorPlans: planList,
|
|
13721
14052
|
evaluations: evalList,
|
|
14053
|
+
alerts: [...this.alerts],
|
|
13722
14054
|
overallVerdict,
|
|
13723
14055
|
summary
|
|
13724
14056
|
};
|
|
13725
14057
|
}
|
|
13726
|
-
buildMarkdownSummary(bugs, plans, evals, overallVerdict) {
|
|
14058
|
+
buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
|
|
13727
14059
|
const lines = [
|
|
13728
14060
|
`## Collaborative Debugging Report \u2014 ${this.sessionId}`,
|
|
13729
14061
|
"",
|
|
13730
14062
|
`**Target:** ${this.options.targetPaths.join(", ")}`,
|
|
14063
|
+
`**Disposition:** ${disposition.toUpperCase()}`,
|
|
13731
14064
|
`**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
|
|
13732
14065
|
""
|
|
13733
14066
|
];
|
|
14067
|
+
if (this.alerts.length > 0) {
|
|
14068
|
+
lines.push("### Alerts", "");
|
|
14069
|
+
for (const alert of this.alerts) {
|
|
14070
|
+
lines.push(`- **[${alert.level.toUpperCase()}]** ${alert.role}: ${alert.message}`);
|
|
14071
|
+
}
|
|
14072
|
+
lines.push("");
|
|
14073
|
+
}
|
|
13734
14074
|
if (bugs.length > 0) {
|
|
13735
14075
|
lines.push("### Bugs Found", "");
|
|
13736
14076
|
for (const b of bugs) {
|
|
13737
|
-
lines.push(
|
|
13738
|
-
`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`
|
|
13739
|
-
);
|
|
14077
|
+
lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
|
|
13740
14078
|
}
|
|
13741
14079
|
lines.push("");
|
|
13742
14080
|
}
|
|
@@ -13755,9 +14093,7 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
|
|
|
13755
14093
|
for (const e of evals) {
|
|
13756
14094
|
lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
|
|
13757
14095
|
for (const c of e.concerns) {
|
|
13758
|
-
if (c.severity === "blocking") {
|
|
13759
|
-
lines.push(` - ${c.description}`);
|
|
13760
|
-
}
|
|
14096
|
+
if (c.severity === "blocking") lines.push(` - ${c.description}`);
|
|
13761
14097
|
}
|
|
13762
14098
|
}
|
|
13763
14099
|
lines.push("");
|
|
@@ -13775,10 +14111,9 @@ var FleetSpawnBudgetError = class extends Error {
|
|
|
13775
14111
|
kind;
|
|
13776
14112
|
limit;
|
|
13777
14113
|
observed;
|
|
13778
|
-
constructor(kind, limit, observed) {
|
|
13779
|
-
|
|
13780
|
-
|
|
13781
|
-
);
|
|
14114
|
+
constructor(kind, limit, observed, message) {
|
|
14115
|
+
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}`;
|
|
14116
|
+
super(message ?? defaultMsg);
|
|
13782
14117
|
this.name = "FleetSpawnBudgetError";
|
|
13783
14118
|
this.kind = kind;
|
|
13784
14119
|
this.limit = limit;
|
|
@@ -13799,6 +14134,20 @@ var FleetCostCapError = class extends Error {
|
|
|
13799
14134
|
this.observed = observed;
|
|
13800
14135
|
}
|
|
13801
14136
|
};
|
|
14137
|
+
var FleetContextOverflowError = class extends Error {
|
|
14138
|
+
kind;
|
|
14139
|
+
limit;
|
|
14140
|
+
observed;
|
|
14141
|
+
constructor(limit, observed) {
|
|
14142
|
+
super(
|
|
14143
|
+
`Leader context overflow: leader has ${observed} tokens in flight (limit: ${limit}). Compact the leader context before spawning more subagents.`
|
|
14144
|
+
);
|
|
14145
|
+
this.name = "FleetContextOverflowError";
|
|
14146
|
+
this.kind = "max_context_load";
|
|
14147
|
+
this.limit = limit;
|
|
14148
|
+
this.observed = observed;
|
|
14149
|
+
}
|
|
14150
|
+
};
|
|
13802
14151
|
var Director = class {
|
|
13803
14152
|
/** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
|
|
13804
14153
|
get coordinatorId() {
|
|
@@ -13815,6 +14164,25 @@ var Director = class {
|
|
|
13815
14164
|
* injected; otherwise own FleetUsageAggregator.
|
|
13816
14165
|
*/
|
|
13817
14166
|
usage;
|
|
14167
|
+
/**
|
|
14168
|
+
* Update the leader agent's current context pressure (full request tokens:
|
|
14169
|
+
* messages + systemPrompt + toolDefs). The director checks this before every
|
|
14170
|
+
* spawn — if the pressure exceeds `maxLeaderContextLoad * maxContext`,
|
|
14171
|
+
* spawning is refused with a `FleetContextOverflowError`.
|
|
14172
|
+
*
|
|
14173
|
+
* Call this after each leader agent iteration to keep the pressure current.
|
|
14174
|
+
* The compactor's `CompactReport.fullRequestTokensAfter` is a natural source.
|
|
14175
|
+
*/
|
|
14176
|
+
setLeaderContextPressure(tokens) {
|
|
14177
|
+
this.leaderContextPressure = tokens;
|
|
14178
|
+
this.fleetManager?.setLeaderContextPressure(tokens);
|
|
14179
|
+
}
|
|
14180
|
+
/**
|
|
14181
|
+
* Read the leader agent's current context pressure.
|
|
14182
|
+
*/
|
|
14183
|
+
getLeaderContextPressure() {
|
|
14184
|
+
return this.leaderContextPressure;
|
|
14185
|
+
}
|
|
13818
14186
|
/**
|
|
13819
14187
|
* Optional fleet-level policy container. When provided the Director
|
|
13820
14188
|
* delegates spawn budgeting, manifest entries, and checkpointing to it
|
|
@@ -13900,6 +14268,23 @@ var Director = class {
|
|
|
13900
14268
|
taskCompletedListener = null;
|
|
13901
14269
|
/** Optional LLM classifier for smart dispatch. Passed from options. */
|
|
13902
14270
|
dispatchClassifier;
|
|
14271
|
+
/** Leader agent's current context pressure (full request tokens). */
|
|
14272
|
+
leaderContextPressure = 0;
|
|
14273
|
+
/** Maximum context load fraction before spawn is refused. */
|
|
14274
|
+
maxLeaderContextLoad;
|
|
14275
|
+
/** Provider's max context window in tokens. */
|
|
14276
|
+
maxContext;
|
|
14277
|
+
/**
|
|
14278
|
+
* When set by `workComplete()`, the director stops dispatching new tasks
|
|
14279
|
+
* and terminates all running subagents. Used when the director's LLM decides
|
|
14280
|
+
* the goal is satisfied and no further spawns are needed — prevents the
|
|
14281
|
+
* coordinator from keeping workers alive for tasks that will never arrive.
|
|
14282
|
+
*/
|
|
14283
|
+
workCompleteFlag = false;
|
|
14284
|
+
/** Pending /btw notes stashed by the leader agent (see setLeaderBtwNote). */
|
|
14285
|
+
_leaderBtwNotes = [];
|
|
14286
|
+
/** Active collab sessions tracked by sessionId (see spawnCollab). */
|
|
14287
|
+
_activeCollabSessions = /* @__PURE__ */ new Map();
|
|
13903
14288
|
constructor(opts) {
|
|
13904
14289
|
this.id = opts.config.coordinatorId || randomUUID();
|
|
13905
14290
|
this.manifestPath = opts.manifestPath;
|
|
@@ -13915,6 +14300,8 @@ var Director = class {
|
|
|
13915
14300
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
13916
14301
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
13917
14302
|
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
|
|
14303
|
+
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
14304
|
+
this.maxContext = opts.maxContext ?? 128e3;
|
|
13918
14305
|
this.sessionsRoot = opts.sessionsRoot;
|
|
13919
14306
|
this.directorRunId = opts.directorRunId ?? this.id;
|
|
13920
14307
|
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
|
|
@@ -13942,7 +14329,7 @@ var Director = class {
|
|
|
13942
14329
|
this.fleet = new FleetBus();
|
|
13943
14330
|
this.usage = new FleetUsageAggregator(
|
|
13944
14331
|
this.fleet,
|
|
13945
|
-
(
|
|
14332
|
+
(_id, provider, model) => {
|
|
13946
14333
|
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
13947
14334
|
return void 0;
|
|
13948
14335
|
},
|
|
@@ -14004,6 +14391,9 @@ var Director = class {
|
|
|
14004
14391
|
});
|
|
14005
14392
|
this.fleet.filter("budget.threshold_reached", (e) => {
|
|
14006
14393
|
const payload = e.payload;
|
|
14394
|
+
if (e.subagentId.startsWith("bug-hunter-") || e.subagentId.startsWith("refactor-planner-") || e.subagentId.startsWith("critic-")) {
|
|
14395
|
+
return;
|
|
14396
|
+
}
|
|
14007
14397
|
if (payload.kind === "timeout") {
|
|
14008
14398
|
const progress = progressBySubagent.get(e.subagentId) ?? 0;
|
|
14009
14399
|
const lastProgress = lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
@@ -14082,6 +14472,107 @@ var Director = class {
|
|
|
14082
14472
|
extensionsFor(subagentId) {
|
|
14083
14473
|
return this.extendTotals.get(subagentId) ?? 0;
|
|
14084
14474
|
}
|
|
14475
|
+
/**
|
|
14476
|
+
* Signal that the director's work is done. Once called:
|
|
14477
|
+
* - `spawn()` throws `FleetSpawnBudgetError('max_spawns', …)` — no new
|
|
14478
|
+
* subagents can be created
|
|
14479
|
+
* - Running subagents are NOT forcibly stopped — they finish naturally,
|
|
14480
|
+
* but no new tasks are dispatched to them
|
|
14481
|
+
*
|
|
14482
|
+
* This lets the director LLM say "I'm satisfied with the results, stop
|
|
14483
|
+
* spawning and wind down" — without killing in-flight work mid-execution.
|
|
14484
|
+
* Call `terminateAll()` separately if you need immediate teardown.
|
|
14485
|
+
*
|
|
14486
|
+
* Idempotent — calling twice is a no-op.
|
|
14487
|
+
*/
|
|
14488
|
+
workComplete() {
|
|
14489
|
+
this.workCompleteFlag = true;
|
|
14490
|
+
this.fleet.emit({ subagentId: this.id, ts: Date.now(), type: "director.work_complete", payload: {} });
|
|
14491
|
+
}
|
|
14492
|
+
/** Returns true if `workComplete()` has been called on this director. */
|
|
14493
|
+
isWorkComplete() {
|
|
14494
|
+
return this.workCompleteFlag;
|
|
14495
|
+
}
|
|
14496
|
+
/**
|
|
14497
|
+
* Stashes a /btw note on the leader agent's context. The leader's agent loop
|
|
14498
|
+
* calls `consumeBtwNotes()` at each iteration boundary and folds pending notes
|
|
14499
|
+
* into the conversation as a visible block — no abort, no restart, just a
|
|
14500
|
+
* "by the way" nudge the model picks up on its next turn.
|
|
14501
|
+
*
|
|
14502
|
+
* This is the entry point for the host (CLI, TUI) to inject /btw notes
|
|
14503
|
+
* programmatically without going through the slash-command path.
|
|
14504
|
+
*/
|
|
14505
|
+
setLeaderBtwNote(note) {
|
|
14506
|
+
const trimmed = note.trim();
|
|
14507
|
+
if (!trimmed) return this._leaderBtwNotes.length;
|
|
14508
|
+
this._leaderBtwNotes = [...this._leaderBtwNotes, trimmed].slice(-20);
|
|
14509
|
+
return this._leaderBtwNotes.length;
|
|
14510
|
+
}
|
|
14511
|
+
/**
|
|
14512
|
+
* Read and clear all pending /btw notes the leader has stashed.
|
|
14513
|
+
* Returns them in FIFO order (empty array when none).
|
|
14514
|
+
*
|
|
14515
|
+
* Called by CollabSession when a budget threshold event fires so the
|
|
14516
|
+
* Director can inspect accumulated /btw notes before deciding whether
|
|
14517
|
+
* to cancel the collab session or let it continue.
|
|
14518
|
+
*/
|
|
14519
|
+
getLeaderBtwNotes() {
|
|
14520
|
+
const notes = this._leaderBtwNotes;
|
|
14521
|
+
this._leaderBtwNotes = [];
|
|
14522
|
+
return notes;
|
|
14523
|
+
}
|
|
14524
|
+
/**
|
|
14525
|
+
* Peek at pending /btw notes without consuming them.
|
|
14526
|
+
* Useful for UI to show "N pending notes" without clearing them.
|
|
14527
|
+
*/
|
|
14528
|
+
peekLeaderBtwNotes() {
|
|
14529
|
+
return [...this._leaderBtwNotes];
|
|
14530
|
+
}
|
|
14531
|
+
/**
|
|
14532
|
+
* Drain (read + clear) all /btw notes in one call.
|
|
14533
|
+
* Alias for getLeaderBtwNotes() — kept for symmetry with consumeBtwNotes()
|
|
14534
|
+
* in the agent's btw.ts. The Director calls this at the point where it
|
|
14535
|
+
* makes a cancellation decision, not on every budget event.
|
|
14536
|
+
*/
|
|
14537
|
+
drainLeaderBtwNotes() {
|
|
14538
|
+
return this.getLeaderBtwNotes();
|
|
14539
|
+
}
|
|
14540
|
+
/**
|
|
14541
|
+
* Cancel an active collab session by its id.
|
|
14542
|
+
* Emits `director.cancel_collab` on the FleetBus so the session's agents
|
|
14543
|
+
* finish early with a 'cancelled' disposition.
|
|
14544
|
+
*
|
|
14545
|
+
* Returns silently if the session id is not tracked or already settled.
|
|
14546
|
+
* The CollabDebugReport will reflect 'cancelled' disposition when awaited.
|
|
14547
|
+
*/
|
|
14548
|
+
cancelCollabSession(sessionId, reason = "Director cancelled") {
|
|
14549
|
+
const session = this._activeCollabSessions.get(sessionId);
|
|
14550
|
+
if (!session || session.isCancelled()) return;
|
|
14551
|
+
session.cancel(reason);
|
|
14552
|
+
for (const [_role, subagentId] of session.getSubagentIds()) {
|
|
14553
|
+
this.coordinator.stop(subagentId).catch(() => {
|
|
14554
|
+
});
|
|
14555
|
+
}
|
|
14556
|
+
}
|
|
14557
|
+
/**
|
|
14558
|
+
* Subscribe a callback to be notified whenever a collab session raises
|
|
14559
|
+
* an alert (warning level). The callback receives the full DirectorAlert
|
|
14560
|
+
* payload so the host (CLI, TUI) can display it to the user.
|
|
14561
|
+
* Returns an unsubscribe function.
|
|
14562
|
+
*/
|
|
14563
|
+
onCollabAlert(handler) {
|
|
14564
|
+
return this.fleet.filter("collab.warning", (e) => {
|
|
14565
|
+
handler(e.payload);
|
|
14566
|
+
});
|
|
14567
|
+
}
|
|
14568
|
+
/**
|
|
14569
|
+
* Returns all active (not yet settled) collab session ids.
|
|
14570
|
+
* Useful for the TUI to render a "N active sessions" badge and for
|
|
14571
|
+
* the host to know what can be cancelled.
|
|
14572
|
+
*/
|
|
14573
|
+
activeCollabSessions() {
|
|
14574
|
+
return Array.from(this._activeCollabSessions.keys());
|
|
14575
|
+
}
|
|
14085
14576
|
/** Best-effort session-writer append. Swallows failures — the director
|
|
14086
14577
|
* must not break a fleet run because the session JSONL handle closed. */
|
|
14087
14578
|
async appendSessionEvent(event) {
|
|
@@ -14120,12 +14611,21 @@ var Director = class {
|
|
|
14120
14611
|
* it the `cost` column in `usage.snapshot()` stays at 0.
|
|
14121
14612
|
*/
|
|
14122
14613
|
async spawn(config, priceLookup) {
|
|
14614
|
+
if (this.workCompleteFlag) {
|
|
14615
|
+
throw new FleetSpawnBudgetError(
|
|
14616
|
+
"max_spawns",
|
|
14617
|
+
this.maxSpawns,
|
|
14618
|
+
this.spawnCount + 1,
|
|
14619
|
+
"workComplete() has been called \u2014 director closed further spawning"
|
|
14620
|
+
);
|
|
14621
|
+
}
|
|
14123
14622
|
if (this.fleetManager) {
|
|
14124
14623
|
const rejection = this.fleetManager.canSpawn(config);
|
|
14125
14624
|
if (rejection) {
|
|
14126
14625
|
if (rejection.kind === "max_spawn_depth") throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
|
|
14127
14626
|
if (rejection.kind === "max_spawns") throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
|
|
14128
14627
|
if (rejection.kind === "max_cost_usd") throw new FleetCostCapError(rejection.limit, rejection.observed);
|
|
14628
|
+
if (rejection.kind === "max_context_load") throw new FleetContextOverflowError(rejection.limit, rejection.observed);
|
|
14129
14629
|
}
|
|
14130
14630
|
} else {
|
|
14131
14631
|
if (this.spawnDepth >= this.maxSpawnDepth) {
|
|
@@ -14140,9 +14640,15 @@ var Director = class {
|
|
|
14140
14640
|
throw new FleetCostCapError(this.maxFleetCostUsd, totalCost);
|
|
14141
14641
|
}
|
|
14142
14642
|
}
|
|
14643
|
+
if (this.maxLeaderContextLoad < 1) {
|
|
14644
|
+
const threshold = this.maxContext * this.maxLeaderContextLoad;
|
|
14645
|
+
if (this.leaderContextPressure >= threshold) {
|
|
14646
|
+
throw new FleetContextOverflowError(threshold, this.leaderContextPressure);
|
|
14647
|
+
}
|
|
14648
|
+
}
|
|
14143
14649
|
}
|
|
14144
14650
|
let result;
|
|
14145
|
-
const needsNickname = config.name === config.role || !config.name || config.name === "subagent";
|
|
14651
|
+
const needsNickname = config.name === config.role || !config.name || config.name === "subagent" || config.name === "adhoc";
|
|
14146
14652
|
if (needsNickname) {
|
|
14147
14653
|
const role = config.role ?? "subagent";
|
|
14148
14654
|
if (this.fleetManager) {
|
|
@@ -14367,6 +14873,24 @@ var Director = class {
|
|
|
14367
14873
|
*/
|
|
14368
14874
|
async assign(task) {
|
|
14369
14875
|
const taskWithId = task.id ? task : { ...task, id: randomUUID() };
|
|
14876
|
+
if (this.workCompleteFlag) {
|
|
14877
|
+
const synthetic = {
|
|
14878
|
+
subagentId: taskWithId.subagentId ?? "unassigned",
|
|
14879
|
+
taskId: taskWithId.id,
|
|
14880
|
+
status: "stopped",
|
|
14881
|
+
error: { kind: "aborted_by_parent", message: "Director called workComplete() \u2014 no further tasks will run", retryable: false },
|
|
14882
|
+
iterations: 0,
|
|
14883
|
+
toolCalls: 0,
|
|
14884
|
+
durationMs: 0
|
|
14885
|
+
};
|
|
14886
|
+
this.completed.set(taskWithId.id, synthetic);
|
|
14887
|
+
const waiter = this.taskWaiters.get(taskWithId.id);
|
|
14888
|
+
if (waiter) {
|
|
14889
|
+
waiter.resolve(synthetic);
|
|
14890
|
+
this.taskWaiters.delete(taskWithId.id);
|
|
14891
|
+
}
|
|
14892
|
+
return taskWithId.id;
|
|
14893
|
+
}
|
|
14370
14894
|
if (task.subagentId) {
|
|
14371
14895
|
if (this.fleetManager) {
|
|
14372
14896
|
this.fleetManager.addTaskToSubagent(task.subagentId, taskWithId.id);
|
|
@@ -14589,12 +15113,14 @@ var Director = class {
|
|
|
14589
15113
|
makeAskTool(this),
|
|
14590
15114
|
makeRollUpTool(this),
|
|
14591
15115
|
makeTerminateTool(this),
|
|
15116
|
+
makeTerminateAllTool(this),
|
|
14592
15117
|
makeFleetStatusTool(this),
|
|
14593
15118
|
makeFleetUsageTool(this),
|
|
14594
15119
|
makeFleetSessionTool(this),
|
|
14595
15120
|
makeFleetHealthTool(this),
|
|
14596
15121
|
makeCollabDebugTool(this),
|
|
14597
|
-
makeFleetEmitTool(this)
|
|
15122
|
+
makeFleetEmitTool(this),
|
|
15123
|
+
makeWorkCompleteTool(this)
|
|
14598
15124
|
];
|
|
14599
15125
|
return t2;
|
|
14600
15126
|
}
|
|
@@ -14614,7 +15140,15 @@ var Director = class {
|
|
|
14614
15140
|
* three agents complete or the session times out.
|
|
14615
15141
|
*/
|
|
14616
15142
|
async spawnCollab(options) {
|
|
14617
|
-
const session = new CollabSession(this, this.fleet,
|
|
15143
|
+
const session = new CollabSession(this, this.fleet, {
|
|
15144
|
+
...options,
|
|
15145
|
+
onBudgetWarning: (alert) => {
|
|
15146
|
+
return options.onBudgetWarning?.(alert) ?? "ignore";
|
|
15147
|
+
}
|
|
15148
|
+
});
|
|
15149
|
+
this._activeCollabSessions.set(session.sessionId, session);
|
|
15150
|
+
session.on("session.done", () => this._activeCollabSessions.delete(session.sessionId));
|
|
15151
|
+
session.on("session.error", () => this._activeCollabSessions.delete(session.sessionId));
|
|
14618
15152
|
return session.start();
|
|
14619
15153
|
}
|
|
14620
15154
|
/**
|
|
@@ -15104,8 +15638,8 @@ async function loadProjectModes(modesDir) {
|
|
|
15104
15638
|
for (const entry of entries) {
|
|
15105
15639
|
if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
|
|
15106
15640
|
const filePath = path6.join(modesDir, entry);
|
|
15107
|
-
const
|
|
15108
|
-
if (!
|
|
15641
|
+
const stat8 = await fsp2.stat(filePath);
|
|
15642
|
+
if (!stat8.isFile()) continue;
|
|
15109
15643
|
const content = await fsp2.readFile(filePath, "utf8");
|
|
15110
15644
|
const id = path6.basename(entry, path6.extname(entry));
|
|
15111
15645
|
modes.push({
|
|
@@ -16379,9 +16913,9 @@ var AISpecBuilder = class {
|
|
|
16379
16913
|
if (!this.sessionPath) return;
|
|
16380
16914
|
try {
|
|
16381
16915
|
const fsp19 = await import('fs/promises');
|
|
16382
|
-
const
|
|
16916
|
+
const path35 = await import('path');
|
|
16383
16917
|
const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
|
|
16384
|
-
await fsp19.mkdir(
|
|
16918
|
+
await fsp19.mkdir(path35.dirname(this.sessionPath), { recursive: true });
|
|
16385
16919
|
await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
|
|
16386
16920
|
} catch {
|
|
16387
16921
|
}
|
|
@@ -17033,7 +17567,7 @@ function analyzeCriticalPath(graph) {
|
|
|
17033
17567
|
blockedTasks
|
|
17034
17568
|
};
|
|
17035
17569
|
}
|
|
17036
|
-
function getTransitiveBlocked(
|
|
17570
|
+
function getTransitiveBlocked(_graph, taskId, blocksMap) {
|
|
17037
17571
|
const visited = /* @__PURE__ */ new Set();
|
|
17038
17572
|
const queue = [taskId];
|
|
17039
17573
|
while (queue.length > 0) {
|
|
@@ -17049,7 +17583,7 @@ function getTransitiveBlocked(graph, taskId, blocksMap) {
|
|
|
17049
17583
|
}
|
|
17050
17584
|
return visited;
|
|
17051
17585
|
}
|
|
17052
|
-
function computeCriticalPath(graph,
|
|
17586
|
+
function computeCriticalPath(graph, _topoOrder, blockedByMap) {
|
|
17053
17587
|
const allIds = Array.from(graph.nodes.keys());
|
|
17054
17588
|
if (allIds.length === 0) return [];
|
|
17055
17589
|
const dist = /* @__PURE__ */ new Map();
|
|
@@ -17091,15 +17625,15 @@ function computeCriticalPath(graph, topoOrder, blockedByMap) {
|
|
|
17091
17625
|
maxId = id;
|
|
17092
17626
|
}
|
|
17093
17627
|
}
|
|
17094
|
-
const
|
|
17628
|
+
const path35 = [];
|
|
17095
17629
|
let current = maxId;
|
|
17096
17630
|
const visited = /* @__PURE__ */ new Set();
|
|
17097
17631
|
while (current && !visited.has(current)) {
|
|
17098
17632
|
visited.add(current);
|
|
17099
|
-
|
|
17633
|
+
path35.unshift(current);
|
|
17100
17634
|
current = prev.get(current) ?? null;
|
|
17101
17635
|
}
|
|
17102
|
-
return
|
|
17636
|
+
return path35;
|
|
17103
17637
|
}
|
|
17104
17638
|
function computeParallelGroups(graph, blockedByMap) {
|
|
17105
17639
|
const groups = [];
|
|
@@ -17467,13 +18001,11 @@ function createAutoExecutor(opts) {
|
|
|
17467
18001
|
|
|
17468
18002
|
// src/sdd/sdd-task-decomposer.ts
|
|
17469
18003
|
var SddTaskDecomposer = class {
|
|
17470
|
-
constructor(tracker,
|
|
18004
|
+
constructor(tracker, _graph, opts = {}) {
|
|
17471
18005
|
this.tracker = tracker;
|
|
17472
|
-
this.graph = graph;
|
|
17473
18006
|
this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
|
|
17474
18007
|
}
|
|
17475
18008
|
tracker;
|
|
17476
|
-
graph;
|
|
17477
18009
|
slots;
|
|
17478
18010
|
wave = 0;
|
|
17479
18011
|
// -------------------------------------------------------------------
|
|
@@ -17626,7 +18158,7 @@ var SddParallelRun = class {
|
|
|
17626
18158
|
this.coordinator.setRunner?.(runner);
|
|
17627
18159
|
}
|
|
17628
18160
|
defaultFactory() {
|
|
17629
|
-
return async (
|
|
18161
|
+
return async (_config) => ({
|
|
17630
18162
|
agent: this.opts.agent,
|
|
17631
18163
|
events: this.opts.agent.events
|
|
17632
18164
|
});
|
|
@@ -18079,7 +18611,7 @@ async function startMetricsServer(opts) {
|
|
|
18079
18611
|
const tls = opts.tls;
|
|
18080
18612
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
18081
18613
|
const host = opts.host ?? "127.0.0.1";
|
|
18082
|
-
const
|
|
18614
|
+
const path35 = opts.path ?? "/metrics";
|
|
18083
18615
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
18084
18616
|
const healthRegistry = opts.healthRegistry;
|
|
18085
18617
|
const listener = (req, res) => {
|
|
@@ -18089,7 +18621,7 @@ async function startMetricsServer(opts) {
|
|
|
18089
18621
|
return;
|
|
18090
18622
|
}
|
|
18091
18623
|
const url = req.url.split("?")[0];
|
|
18092
|
-
if (url ===
|
|
18624
|
+
if (url === path35) {
|
|
18093
18625
|
let body;
|
|
18094
18626
|
try {
|
|
18095
18627
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -18153,7 +18685,7 @@ async function startMetricsServer(opts) {
|
|
|
18153
18685
|
const protocol = useHttps ? "https" : "http";
|
|
18154
18686
|
return {
|
|
18155
18687
|
port: boundPort,
|
|
18156
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
18688
|
+
url: `${protocol}://${host}:${boundPort}${path35}`,
|
|
18157
18689
|
close: () => new Promise((resolve9, reject) => {
|
|
18158
18690
|
server.close((err) => err ? reject(err) : resolve9());
|
|
18159
18691
|
})
|
|
@@ -18440,6 +18972,12 @@ function roughEstimate(messages) {
|
|
|
18440
18972
|
return total;
|
|
18441
18973
|
}
|
|
18442
18974
|
function createContextManagerTool(opts = {}) {
|
|
18975
|
+
const minCompactThreshold = opts.minCompactThreshold ?? 0;
|
|
18976
|
+
const noopRetryDeltaTokens = opts.noopRetryDeltaTokens ?? 2e3;
|
|
18977
|
+
const maxContext = opts.maxContext ?? 128e3;
|
|
18978
|
+
const compactThresholdFraction = opts.compactThresholdFraction ?? 0.5;
|
|
18979
|
+
const effectiveThreshold = minCompactThreshold > 0 ? minCompactThreshold : Math.floor(maxContext * compactThresholdFraction);
|
|
18980
|
+
let lastNoopTokens = 0;
|
|
18443
18981
|
return {
|
|
18444
18982
|
name: CONTEXT_MANAGER_TOOL_NAME,
|
|
18445
18983
|
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.',
|
|
@@ -18530,11 +19068,41 @@ function createContextManagerTool(opts = {}) {
|
|
|
18530
19068
|
notes: "No compactor registered. Use /compact aggressive via slash command instead."
|
|
18531
19069
|
};
|
|
18532
19070
|
}
|
|
19071
|
+
const fullEstimate = input.systemPrompt != null && Array.isArray(input.tools) ? estimateRequestTokens(messages, input.systemPrompt, input.tools) : { total: beforeTokens};
|
|
19072
|
+
const currentTokens = fullEstimate.total;
|
|
19073
|
+
if (lastNoopTokens > 0) {
|
|
19074
|
+
const delta = currentTokens - lastNoopTokens;
|
|
19075
|
+
if (delta < noopRetryDeltaTokens) {
|
|
19076
|
+
return {
|
|
19077
|
+
action: "compact",
|
|
19078
|
+
beforeTokens,
|
|
19079
|
+
afterTokens: beforeTokens,
|
|
19080
|
+
messageCount: messages.length,
|
|
19081
|
+
notes: `Compact is a NOOP retry: context grew only ${delta} tokens since the last no-op attempt (threshold: ${noopRetryDeltaTokens}). Skip until more content accumulates.`
|
|
19082
|
+
};
|
|
19083
|
+
}
|
|
19084
|
+
}
|
|
19085
|
+
if (currentTokens < effectiveThreshold) {
|
|
19086
|
+
return {
|
|
19087
|
+
action: "compact",
|
|
19088
|
+
beforeTokens,
|
|
19089
|
+
afterTokens: beforeTokens,
|
|
19090
|
+
messageCount: messages.length,
|
|
19091
|
+
notes: `Context tokens (${currentTokens}) below compact threshold (${effectiveThreshold}). Skipping.`
|
|
19092
|
+
};
|
|
19093
|
+
}
|
|
18533
19094
|
const report = await opts.compactor.compact(ctx);
|
|
18534
19095
|
ctx.clearFileTracking();
|
|
18535
19096
|
const repair = applyMessages([...ctx.messages]);
|
|
18536
19097
|
const afterTokens = repair.changed ? roughEstimate(ctx.messages) : report.after;
|
|
18537
19098
|
const repaired = report.repaired ?? (repair.changed ? repair : void 0);
|
|
19099
|
+
const reduced = report.fullRequestTokensBefore > report.fullRequestTokensAfter;
|
|
19100
|
+
const repairedSomething = !!report.repaired;
|
|
19101
|
+
if (reduced || repairedSomething) {
|
|
19102
|
+
lastNoopTokens = 0;
|
|
19103
|
+
} else {
|
|
19104
|
+
lastNoopTokens = currentTokens;
|
|
19105
|
+
}
|
|
18538
19106
|
return {
|
|
18539
19107
|
action: "compact",
|
|
18540
19108
|
beforeTokens,
|
|
@@ -19000,10 +19568,10 @@ var SkillInstaller = class {
|
|
|
19000
19568
|
if (!resolved.startsWith(path6.resolve(destDir))) {
|
|
19001
19569
|
throw new Error(`Path traversal detected in skill file: ${file}`);
|
|
19002
19570
|
}
|
|
19003
|
-
const
|
|
19004
|
-
if (
|
|
19571
|
+
const stat8 = await fsp2.stat(srcPath);
|
|
19572
|
+
if (stat8.size > MAX_SKILL_FILE_SIZE) {
|
|
19005
19573
|
throw new Error(
|
|
19006
|
-
`Skill file "${file}" is too large (${(
|
|
19574
|
+
`Skill file "${file}" is too large (${(stat8.size / 1024).toFixed(1)}KB). Max: ${MAX_SKILL_FILE_SIZE / 1024}KB`
|
|
19007
19575
|
);
|
|
19008
19576
|
}
|
|
19009
19577
|
await fsp2.mkdir(path6.dirname(destPath), { recursive: true });
|
|
@@ -19042,7 +19610,7 @@ var SkillInstaller = class {
|
|
|
19042
19610
|
* - Name: update that specific skill
|
|
19043
19611
|
* - Name + newRef: update to a different ref
|
|
19044
19612
|
*/
|
|
19045
|
-
async update(nameOrRef,
|
|
19613
|
+
async update(nameOrRef, _opts) {
|
|
19046
19614
|
const result = { updated: [], unchanged: [], errors: [] };
|
|
19047
19615
|
const allEntries = await this.manifest.listAll();
|
|
19048
19616
|
let targets;
|
|
@@ -19112,140 +19680,756 @@ var SkillInstaller = class {
|
|
|
19112
19680
|
return result;
|
|
19113
19681
|
}
|
|
19114
19682
|
/**
|
|
19115
|
-
* Uninstall a skill by name.
|
|
19683
|
+
* Uninstall a skill by name.
|
|
19684
|
+
*/
|
|
19685
|
+
async uninstall(name, opts) {
|
|
19686
|
+
const scope = opts?.global ? "user" : "project";
|
|
19687
|
+
const entries = await this.manifest.findByName(name);
|
|
19688
|
+
const entry = entries.find((e) => e.scope === scope);
|
|
19689
|
+
if (!entry) {
|
|
19690
|
+
throw new Error(`Skill "${name}" is not installed${scope === "user" ? " (global)" : ""}.`);
|
|
19691
|
+
}
|
|
19692
|
+
await this.removeSkillFiles(name, scope);
|
|
19693
|
+
await this.manifest.removeEntry(name, scope);
|
|
19694
|
+
this.invalidateLoaderCache();
|
|
19695
|
+
}
|
|
19696
|
+
/**
|
|
19697
|
+
* List all installed skills from the manifest.
|
|
19698
|
+
*/
|
|
19699
|
+
async listInstalled() {
|
|
19700
|
+
return this.manifest.listAll();
|
|
19701
|
+
}
|
|
19702
|
+
// ── Private helpers ──────────────────────────────────────────────
|
|
19703
|
+
/**
|
|
19704
|
+
* Detect skills in an extracted repository.
|
|
19705
|
+
* Returns an array of detected skills with their files.
|
|
19706
|
+
*/
|
|
19707
|
+
async detectSkills(baseDir) {
|
|
19708
|
+
const results = [];
|
|
19709
|
+
const rootSkillMd = path6.join(baseDir, "SKILL.md");
|
|
19710
|
+
try {
|
|
19711
|
+
await fsp2.access(rootSkillMd);
|
|
19712
|
+
const content = await fsp2.readFile(rootSkillMd, "utf8");
|
|
19713
|
+
const meta = parseFrontmatter2(content);
|
|
19714
|
+
if (meta.name && meta.description) {
|
|
19715
|
+
results.push({
|
|
19716
|
+
name: meta.name,
|
|
19717
|
+
baseDir,
|
|
19718
|
+
files: ["SKILL.md"]
|
|
19719
|
+
});
|
|
19720
|
+
return results;
|
|
19721
|
+
}
|
|
19722
|
+
} catch {
|
|
19723
|
+
}
|
|
19724
|
+
const skillsDir = path6.join(baseDir, "skills");
|
|
19725
|
+
try {
|
|
19726
|
+
const entries = await fsp2.readdir(skillsDir, { withFileTypes: true });
|
|
19727
|
+
for (const entry of entries) {
|
|
19728
|
+
if (!entry.isDirectory()) continue;
|
|
19729
|
+
const skillFile = path6.join(skillsDir, entry.name, "SKILL.md");
|
|
19730
|
+
try {
|
|
19731
|
+
const content = await fsp2.readFile(skillFile, "utf8");
|
|
19732
|
+
const meta = parseFrontmatter2(content);
|
|
19733
|
+
if (meta.name && meta.description) {
|
|
19734
|
+
const skillDir = path6.join(skillsDir, entry.name);
|
|
19735
|
+
const files = await collectFiles(skillDir, skillDir);
|
|
19736
|
+
results.push({
|
|
19737
|
+
name: meta.name,
|
|
19738
|
+
baseDir: skillDir,
|
|
19739
|
+
files
|
|
19740
|
+
});
|
|
19741
|
+
}
|
|
19742
|
+
} catch {
|
|
19743
|
+
}
|
|
19744
|
+
}
|
|
19745
|
+
} catch {
|
|
19746
|
+
}
|
|
19747
|
+
return results;
|
|
19748
|
+
}
|
|
19749
|
+
/**
|
|
19750
|
+
* Remove all files for an installed skill.
|
|
19751
|
+
*/
|
|
19752
|
+
async removeSkillFiles(name, scope) {
|
|
19753
|
+
const targetDir = scope === "project" ? this.opts.projectSkillsDir : this.opts.globalSkillsDir;
|
|
19754
|
+
const skillDir = path6.join(targetDir, name);
|
|
19755
|
+
await fsp2.rm(skillDir, { recursive: true, force: true });
|
|
19756
|
+
}
|
|
19757
|
+
/**
|
|
19758
|
+
* Invalidate the skill loader's cache so newly installed skills appear.
|
|
19759
|
+
*/
|
|
19760
|
+
invalidateLoaderCache() {
|
|
19761
|
+
const loader = this.opts.skillLoader;
|
|
19762
|
+
if (loader && typeof loader.invalidateCache === "function") {
|
|
19763
|
+
loader.invalidateCache();
|
|
19764
|
+
}
|
|
19765
|
+
}
|
|
19766
|
+
};
|
|
19767
|
+
function parseFrontmatter2(raw) {
|
|
19768
|
+
if (!raw.startsWith("---")) return {};
|
|
19769
|
+
const end = raw.indexOf("\n---", 4);
|
|
19770
|
+
if (end === -1) return {};
|
|
19771
|
+
const block = raw.slice(4, end);
|
|
19772
|
+
const out = {};
|
|
19773
|
+
let key = null;
|
|
19774
|
+
let value = [];
|
|
19775
|
+
const flush = () => {
|
|
19776
|
+
if (key) {
|
|
19777
|
+
out[key] = value.join("\n").trim();
|
|
19778
|
+
}
|
|
19779
|
+
key = null;
|
|
19780
|
+
value = [];
|
|
19781
|
+
};
|
|
19782
|
+
for (const line of block.split("\n")) {
|
|
19783
|
+
const m = /^([a-zA-Z_]+):\s*(\|?)\s*(.*)$/.exec(line);
|
|
19784
|
+
if (m) {
|
|
19785
|
+
flush();
|
|
19786
|
+
key = m[1] ?? "";
|
|
19787
|
+
const pipe = m[2];
|
|
19788
|
+
const rest = m[3] ?? "";
|
|
19789
|
+
if (pipe === "|") {
|
|
19790
|
+
value = [];
|
|
19791
|
+
} else if (rest) {
|
|
19792
|
+
value = [rest];
|
|
19793
|
+
} else {
|
|
19794
|
+
value = [];
|
|
19795
|
+
}
|
|
19796
|
+
} else if (key) {
|
|
19797
|
+
value.push(line.replace(/^\s+/, ""));
|
|
19798
|
+
}
|
|
19799
|
+
}
|
|
19800
|
+
flush();
|
|
19801
|
+
return out;
|
|
19802
|
+
}
|
|
19803
|
+
async function collectFiles(dir, baseDir) {
|
|
19804
|
+
const results = [];
|
|
19805
|
+
const entries = await fsp2.readdir(dir, { withFileTypes: true });
|
|
19806
|
+
for (const entry of entries) {
|
|
19807
|
+
const fullPath = path6.join(dir, entry.name);
|
|
19808
|
+
const relPath = path6.relative(baseDir, fullPath);
|
|
19809
|
+
if (entry.isDirectory()) {
|
|
19810
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
19811
|
+
results.push(...await collectFiles(fullPath, baseDir));
|
|
19812
|
+
} else {
|
|
19813
|
+
results.push(relPath);
|
|
19814
|
+
}
|
|
19815
|
+
}
|
|
19816
|
+
return results;
|
|
19817
|
+
}
|
|
19818
|
+
|
|
19819
|
+
// src/storage/annotations-store.ts
|
|
19820
|
+
init_atomic_write();
|
|
19821
|
+
var FILE_VERSION = 1;
|
|
19822
|
+
var MAX_TEXT_LENGTH = 2e3;
|
|
19823
|
+
var MAX_ANNOTATIONS = 1e3;
|
|
19824
|
+
var AnnotationsStore = class {
|
|
19825
|
+
dir;
|
|
19826
|
+
/** Per-session write queue. Created lazily on first add. */
|
|
19827
|
+
writeChains = /* @__PURE__ */ new Map();
|
|
19828
|
+
constructor(opts) {
|
|
19829
|
+
this.dir = opts.dir;
|
|
19830
|
+
}
|
|
19831
|
+
// ── Reads ──────────────────────────────────────────────────────────────
|
|
19832
|
+
/**
|
|
19833
|
+
* Return all annotations for `sessionId` in insertion order
|
|
19834
|
+
* (oldest first). Returns an empty array when no file exists
|
|
19835
|
+
* yet (the normal case for a fresh session).
|
|
19836
|
+
*/
|
|
19837
|
+
async list(sessionId) {
|
|
19838
|
+
const file = await this.readFile(sessionId);
|
|
19839
|
+
return file ? file.annotations : [];
|
|
19840
|
+
}
|
|
19841
|
+
/**
|
|
19842
|
+
* Convenience: only unresolved annotations, newest first — the
|
|
19843
|
+
* common UI rendering for "what still needs attention?".
|
|
19844
|
+
*/
|
|
19845
|
+
async listOpen(sessionId) {
|
|
19846
|
+
const all = await this.list(sessionId);
|
|
19847
|
+
return all.filter((a) => !a.resolved).reverse();
|
|
19848
|
+
}
|
|
19849
|
+
// ── Writes ─────────────────────────────────────────────────────────────
|
|
19850
|
+
/**
|
|
19851
|
+
* Add a new annotation. Returns the persisted record (with id
|
|
19852
|
+
* and timestamps filled in). Throws when `text` is empty or
|
|
19853
|
+
* exceeds `MAX_TEXT_LENGTH`.
|
|
19854
|
+
*/
|
|
19855
|
+
async add(input) {
|
|
19856
|
+
const text = input.text.trim();
|
|
19857
|
+
if (text.length === 0) {
|
|
19858
|
+
throw new Error("Annotation text must be non-empty");
|
|
19859
|
+
}
|
|
19860
|
+
if (text.length > MAX_TEXT_LENGTH) {
|
|
19861
|
+
throw new Error(
|
|
19862
|
+
`Annotation text exceeds ${MAX_TEXT_LENGTH} chars (got ${text.length})`
|
|
19863
|
+
);
|
|
19864
|
+
}
|
|
19865
|
+
if (!Number.isInteger(input.atEventIndex) || input.atEventIndex < 0) {
|
|
19866
|
+
throw new Error("atEventIndex must be a non-negative integer");
|
|
19867
|
+
}
|
|
19868
|
+
const annotation = {
|
|
19869
|
+
id: randomUUID(),
|
|
19870
|
+
sessionId: input.sessionId,
|
|
19871
|
+
atEventIndex: input.atEventIndex,
|
|
19872
|
+
authorId: input.authorId,
|
|
19873
|
+
authorRole: "annotator",
|
|
19874
|
+
text,
|
|
19875
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19876
|
+
resolved: false
|
|
19877
|
+
};
|
|
19878
|
+
await this.enqueue(input.sessionId, async () => {
|
|
19879
|
+
const all = await this.list(input.sessionId);
|
|
19880
|
+
all.push(annotation);
|
|
19881
|
+
if (all.length > MAX_ANNOTATIONS) {
|
|
19882
|
+
const sorted = all.map((a, i) => ({ a, i })).sort((x, y) => {
|
|
19883
|
+
if (x.a.resolved !== y.a.resolved) return x.a.resolved ? 1 : -1;
|
|
19884
|
+
return x.a.createdAt.localeCompare(y.a.createdAt);
|
|
19885
|
+
});
|
|
19886
|
+
const evictCount = all.length - MAX_ANNOTATIONS;
|
|
19887
|
+
const toEvict = new Set(sorted.slice(0, evictCount).map((s) => s.a.id));
|
|
19888
|
+
const kept = all.filter((a) => !toEvict.has(a.id));
|
|
19889
|
+
await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: kept });
|
|
19890
|
+
} else {
|
|
19891
|
+
await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
|
|
19892
|
+
}
|
|
19893
|
+
});
|
|
19894
|
+
return annotation;
|
|
19895
|
+
}
|
|
19896
|
+
/**
|
|
19897
|
+
* Mark an annotation as resolved. Returns the updated record, or
|
|
19898
|
+
* `null` if no annotation with that id exists in this session.
|
|
19899
|
+
* Idempotent: resolving an already-resolved annotation refreshes
|
|
19900
|
+
* `resolvedAt` / `resolvedBy` to the latest call.
|
|
19901
|
+
*/
|
|
19902
|
+
async resolve(input) {
|
|
19903
|
+
let updated = null;
|
|
19904
|
+
await this.enqueue(input.sessionId, async () => {
|
|
19905
|
+
const all = await this.list(input.sessionId);
|
|
19906
|
+
const idx = all.findIndex((a) => a.id === input.annotationId);
|
|
19907
|
+
if (idx === -1) {
|
|
19908
|
+
updated = null;
|
|
19909
|
+
return;
|
|
19910
|
+
}
|
|
19911
|
+
const next = {
|
|
19912
|
+
...all[idx],
|
|
19913
|
+
resolved: true,
|
|
19914
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19915
|
+
resolvedBy: input.resolvedBy
|
|
19916
|
+
};
|
|
19917
|
+
all[idx] = next;
|
|
19918
|
+
await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
|
|
19919
|
+
updated = next;
|
|
19920
|
+
});
|
|
19921
|
+
return updated;
|
|
19922
|
+
}
|
|
19923
|
+
// ── Internals ──────────────────────────────────────────────────────────
|
|
19924
|
+
filePath(sessionId) {
|
|
19925
|
+
if (!sessionId || sessionId.includes("/") || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
19926
|
+
throw new Error(`Invalid sessionId: ${sessionId}`);
|
|
19927
|
+
}
|
|
19928
|
+
return path6.join(this.dir, `${sessionId}.annotations.json`);
|
|
19929
|
+
}
|
|
19930
|
+
async readFile(sessionId) {
|
|
19931
|
+
const fp = this.filePath(sessionId);
|
|
19932
|
+
try {
|
|
19933
|
+
const raw = await fsp2.readFile(fp, "utf8");
|
|
19934
|
+
const parsed = JSON.parse(raw);
|
|
19935
|
+
if (parsed.version !== FILE_VERSION) {
|
|
19936
|
+
return { version: FILE_VERSION, annotations: [] };
|
|
19937
|
+
}
|
|
19938
|
+
return parsed;
|
|
19939
|
+
} catch (err) {
|
|
19940
|
+
if (err.code === "ENOENT") return null;
|
|
19941
|
+
return { version: FILE_VERSION, annotations: [] };
|
|
19942
|
+
}
|
|
19943
|
+
}
|
|
19944
|
+
async writeFile(sessionId, file) {
|
|
19945
|
+
const fp = this.filePath(sessionId);
|
|
19946
|
+
await atomicWrite(fp, JSON.stringify(file, null, 2));
|
|
19947
|
+
}
|
|
19948
|
+
/**
|
|
19949
|
+
* Serialize writes per-sessionId. We chain promises instead of
|
|
19950
|
+
* using a Mutex class so the contract is obvious from the
|
|
19951
|
+
* call-site: `enqueue(sid, fn)` runs `fn` after every prior
|
|
19952
|
+
* enqueue for `sid` has settled.
|
|
19953
|
+
*/
|
|
19954
|
+
enqueue(sessionId, fn) {
|
|
19955
|
+
const prev = this.writeChains.get(sessionId) ?? Promise.resolve();
|
|
19956
|
+
const next = prev.then(fn, fn);
|
|
19957
|
+
this.writeChains.set(
|
|
19958
|
+
sessionId,
|
|
19959
|
+
next.catch(() => void 0)
|
|
19960
|
+
);
|
|
19961
|
+
return next;
|
|
19962
|
+
}
|
|
19963
|
+
};
|
|
19964
|
+
|
|
19965
|
+
// src/storage/replay-log-store.ts
|
|
19966
|
+
init_atomic_write();
|
|
19967
|
+
function stableStringify(value) {
|
|
19968
|
+
return JSON.stringify(sortKeys(value));
|
|
19969
|
+
}
|
|
19970
|
+
function sortKeys(value) {
|
|
19971
|
+
if (Array.isArray(value)) return value.map(sortKeys);
|
|
19972
|
+
if (value && typeof value === "object") {
|
|
19973
|
+
const obj = value;
|
|
19974
|
+
const sorted = {};
|
|
19975
|
+
for (const key of Object.keys(obj).sort()) {
|
|
19976
|
+
sorted[key] = sortKeys(obj[key]);
|
|
19977
|
+
}
|
|
19978
|
+
return sorted;
|
|
19979
|
+
}
|
|
19980
|
+
return value;
|
|
19981
|
+
}
|
|
19982
|
+
function hashRequest(request) {
|
|
19983
|
+
const payload = {
|
|
19984
|
+
model: request.model,
|
|
19985
|
+
system: request.system,
|
|
19986
|
+
messages: request.messages,
|
|
19987
|
+
tools: request.tools,
|
|
19988
|
+
maxTokens: request.maxTokens,
|
|
19989
|
+
temperature: request.temperature,
|
|
19990
|
+
topP: request.topP,
|
|
19991
|
+
stopSequences: request.stopSequences,
|
|
19992
|
+
toolChoice: request.toolChoice
|
|
19993
|
+
};
|
|
19994
|
+
const json = stableStringify(payload);
|
|
19995
|
+
const digest = createHash("sha256").update(json, "utf8").digest("hex");
|
|
19996
|
+
return `sha256:${digest}`;
|
|
19997
|
+
}
|
|
19998
|
+
var DEFAULT_MAX_ENTRIES = 1e3;
|
|
19999
|
+
var ReplayLogStore = class {
|
|
20000
|
+
dir;
|
|
20001
|
+
writeChains = /* @__PURE__ */ new Map();
|
|
20002
|
+
/** Per-session hash → entry index, kept in memory after the first load. */
|
|
20003
|
+
cache = /* @__PURE__ */ new Map();
|
|
20004
|
+
maxEntries;
|
|
20005
|
+
constructor(opts) {
|
|
20006
|
+
this.dir = opts.dir;
|
|
20007
|
+
this.maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
20008
|
+
}
|
|
20009
|
+
// ── Writes ──────────────────────────────────────────────────────────────
|
|
20010
|
+
/**
|
|
20011
|
+
* Record a request/response pair. Idempotent on hash: a second
|
|
20012
|
+
* `record` for the same hash is a no-op (the existing entry wins).
|
|
20013
|
+
* Returns the hash.
|
|
20014
|
+
*/
|
|
20015
|
+
async record(input) {
|
|
20016
|
+
const hash = hashRequest(input.request);
|
|
20017
|
+
await this.enqueue(input.sessionId, async () => {
|
|
20018
|
+
const cache = await this.ensureCache(input.sessionId);
|
|
20019
|
+
if (cache.has(hash)) return;
|
|
20020
|
+
const entry = {
|
|
20021
|
+
hash,
|
|
20022
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20023
|
+
request: input.request,
|
|
20024
|
+
response: input.response
|
|
20025
|
+
};
|
|
20026
|
+
let all = [...cache.values(), entry];
|
|
20027
|
+
if (all.length > this.maxEntries) {
|
|
20028
|
+
const evictCount = all.length - this.maxEntries;
|
|
20029
|
+
all = all.slice(evictCount);
|
|
20030
|
+
}
|
|
20031
|
+
await this.writeAll(input.sessionId, all);
|
|
20032
|
+
cache.clear();
|
|
20033
|
+
for (const e of all) cache.set(e.hash, e);
|
|
20034
|
+
});
|
|
20035
|
+
return hash;
|
|
20036
|
+
}
|
|
20037
|
+
// ── Reads ───────────────────────────────────────────────────────────────
|
|
20038
|
+
/**
|
|
20039
|
+
* Look up an entry by hash. Returns `null` when the request has
|
|
20040
|
+
* not been recorded for this session. O(1) after the first call
|
|
20041
|
+
* per session (in-memory cache).
|
|
20042
|
+
*/
|
|
20043
|
+
async lookup(sessionId, hash) {
|
|
20044
|
+
const cache = await this.ensureCache(sessionId);
|
|
20045
|
+
return cache.get(hash) ?? null;
|
|
20046
|
+
}
|
|
20047
|
+
/** All recorded entries for a session, in insertion order. */
|
|
20048
|
+
async load(sessionId) {
|
|
20049
|
+
const cache = await this.ensureCache(sessionId);
|
|
20050
|
+
return [...cache.values()];
|
|
20051
|
+
}
|
|
20052
|
+
/**
|
|
20053
|
+
* List every session id that has a replay log in the store dir.
|
|
20054
|
+
* Returns an array of `{ sessionId, entryCount, path }` sorted
|
|
20055
|
+
* by sessionId for stable output. Used by `wstack replay --list`.
|
|
20056
|
+
*/
|
|
20057
|
+
async list() {
|
|
20058
|
+
let entries;
|
|
20059
|
+
try {
|
|
20060
|
+
entries = await fsp2.readdir(this.dir);
|
|
20061
|
+
} catch (err) {
|
|
20062
|
+
if (err.code === "ENOENT") return [];
|
|
20063
|
+
return [];
|
|
20064
|
+
}
|
|
20065
|
+
const out = [];
|
|
20066
|
+
for (const name of entries) {
|
|
20067
|
+
if (!name.endsWith(".replay.jsonl")) continue;
|
|
20068
|
+
const sessionId = name.slice(0, -".replay.jsonl".length);
|
|
20069
|
+
const all = await this.load(sessionId);
|
|
20070
|
+
out.push({
|
|
20071
|
+
sessionId,
|
|
20072
|
+
entryCount: all.length,
|
|
20073
|
+
path: path6.join(this.dir, name)
|
|
20074
|
+
});
|
|
20075
|
+
}
|
|
20076
|
+
return out.sort((a, b) => a.sessionId.localeCompare(b.sessionId));
|
|
20077
|
+
}
|
|
20078
|
+
// ── Internals ───────────────────────────────────────────────────────────
|
|
20079
|
+
filePath(sessionId) {
|
|
20080
|
+
if (!sessionId || sessionId.includes("/") || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
20081
|
+
throw new Error(`Invalid sessionId: ${sessionId}`);
|
|
20082
|
+
}
|
|
20083
|
+
return path6.join(this.dir, `${sessionId}.replay.jsonl`);
|
|
20084
|
+
}
|
|
20085
|
+
async readAll(sessionId) {
|
|
20086
|
+
const fp = this.filePath(sessionId);
|
|
20087
|
+
try {
|
|
20088
|
+
const raw = await fsp2.readFile(fp, "utf8");
|
|
20089
|
+
const out = [];
|
|
20090
|
+
for (const line of raw.split("\n")) {
|
|
20091
|
+
if (!line.trim()) continue;
|
|
20092
|
+
try {
|
|
20093
|
+
const parsed = JSON.parse(line);
|
|
20094
|
+
if ("entry" in parsed && parsed.entry) {
|
|
20095
|
+
out.push(parsed.entry);
|
|
20096
|
+
} else {
|
|
20097
|
+
out.push(parsed);
|
|
20098
|
+
}
|
|
20099
|
+
} catch {
|
|
20100
|
+
}
|
|
20101
|
+
}
|
|
20102
|
+
return out;
|
|
20103
|
+
} catch (err) {
|
|
20104
|
+
if (err.code === "ENOENT") return [];
|
|
20105
|
+
return [];
|
|
20106
|
+
}
|
|
20107
|
+
}
|
|
20108
|
+
async writeAll(sessionId, entries) {
|
|
20109
|
+
const fp = this.filePath(sessionId);
|
|
20110
|
+
const body = entries.map((e) => JSON.stringify(e)).join("\n") + (entries.length ? "\n" : "");
|
|
20111
|
+
await atomicWrite(fp, body);
|
|
20112
|
+
}
|
|
20113
|
+
async ensureCache(sessionId) {
|
|
20114
|
+
let cache = this.cache.get(sessionId);
|
|
20115
|
+
if (cache) return cache;
|
|
20116
|
+
const all = await this.readAll(sessionId);
|
|
20117
|
+
cache = /* @__PURE__ */ new Map();
|
|
20118
|
+
for (const e of all) cache.set(e.hash, e);
|
|
20119
|
+
this.cache.set(sessionId, cache);
|
|
20120
|
+
return cache;
|
|
20121
|
+
}
|
|
20122
|
+
enqueue(sessionId, fn) {
|
|
20123
|
+
const prev = this.writeChains.get(sessionId) ?? Promise.resolve();
|
|
20124
|
+
const next = prev.then(fn, fn);
|
|
20125
|
+
this.writeChains.set(
|
|
20126
|
+
sessionId,
|
|
20127
|
+
next.catch(() => void 0)
|
|
20128
|
+
);
|
|
20129
|
+
return next;
|
|
20130
|
+
}
|
|
20131
|
+
};
|
|
20132
|
+
var SessionRecovery = class {
|
|
20133
|
+
constructor(dir) {
|
|
20134
|
+
this.dir = dir;
|
|
20135
|
+
}
|
|
20136
|
+
dir;
|
|
20137
|
+
/**
|
|
20138
|
+
* Scan a session log and return a `StaleSession` if and only
|
|
20139
|
+
* if the last event is an `in_flight_start` without a matching
|
|
20140
|
+
* `in_flight_end`. Returns `null` when:
|
|
20141
|
+
* - the log does not exist;
|
|
20142
|
+
* - the log is empty;
|
|
20143
|
+
* - the last event is `in_flight_end` (clean shutdown);
|
|
20144
|
+
* - the last event is something else (e.g. an unannotated
|
|
20145
|
+
* legacy log without in-flight markers).
|
|
20146
|
+
*/
|
|
20147
|
+
async detectStale(sessionId) {
|
|
20148
|
+
const fp = this.filePath(sessionId);
|
|
20149
|
+
let raw;
|
|
20150
|
+
try {
|
|
20151
|
+
raw = await fsp2.readFile(fp, "utf8");
|
|
20152
|
+
} catch (err) {
|
|
20153
|
+
if (err.code === "ENOENT") return null;
|
|
20154
|
+
return null;
|
|
20155
|
+
}
|
|
20156
|
+
return this.parseForStale(sessionId, fp, raw);
|
|
20157
|
+
}
|
|
20158
|
+
/**
|
|
20159
|
+
* Generate a recovery plan for a session. The plan describes
|
|
20160
|
+
* "what would be re-executed" if the user chose to resume —
|
|
20161
|
+
* everything after the last `checkpoint` event, plus the
|
|
20162
|
+
* dangling in-flight marker if present.
|
|
20163
|
+
*
|
|
20164
|
+
* Returns a non-null plan for ANY session that has at least
|
|
20165
|
+
* one event after a checkpoint (or, for legacy sessions, at
|
|
20166
|
+
* least one event). Pure read; no mutation.
|
|
20167
|
+
*/
|
|
20168
|
+
async recover(sessionId) {
|
|
20169
|
+
const fp = this.filePath(sessionId);
|
|
20170
|
+
let raw;
|
|
20171
|
+
try {
|
|
20172
|
+
raw = await fsp2.readFile(fp, "utf8");
|
|
20173
|
+
} catch (err) {
|
|
20174
|
+
if (err.code === "ENOENT") return null;
|
|
20175
|
+
return null;
|
|
20176
|
+
}
|
|
20177
|
+
const events = [];
|
|
20178
|
+
for (const line of raw.split("\n")) {
|
|
20179
|
+
if (!line.trim()) continue;
|
|
20180
|
+
try {
|
|
20181
|
+
events.push(JSON.parse(line));
|
|
20182
|
+
} catch {
|
|
20183
|
+
}
|
|
20184
|
+
}
|
|
20185
|
+
if (events.length === 0) return null;
|
|
20186
|
+
let lastCheckpoint = null;
|
|
20187
|
+
let lastCheckpointIdx = -1;
|
|
20188
|
+
for (let i = 0; i < events.length; i++) {
|
|
20189
|
+
if (events[i].type === "checkpoint") {
|
|
20190
|
+
lastCheckpoint = events[i];
|
|
20191
|
+
lastCheckpointIdx = i;
|
|
20192
|
+
}
|
|
20193
|
+
}
|
|
20194
|
+
const pendingEvents = lastCheckpointIdx >= 0 ? events.slice(lastCheckpointIdx + 1) : events;
|
|
20195
|
+
const lastEv = events[events.length - 1];
|
|
20196
|
+
const inFlightStart = lastEv.type === "in_flight_start" ? lastEv : null;
|
|
20197
|
+
const context = inFlightStart && inFlightStart.type === "in_flight_start" ? inFlightStart.context : null;
|
|
20198
|
+
return {
|
|
20199
|
+
sessionId,
|
|
20200
|
+
stale: inFlightStart !== null,
|
|
20201
|
+
lastCheckpoint,
|
|
20202
|
+
pendingEvents,
|
|
20203
|
+
inFlightStart,
|
|
20204
|
+
context
|
|
20205
|
+
};
|
|
20206
|
+
}
|
|
20207
|
+
/**
|
|
20208
|
+
* List every stale session in a directory. Returns an array
|
|
20209
|
+
* (possibly empty) sorted by `lastEventTs` descending — most
|
|
20210
|
+
* recent crash first.
|
|
19116
20211
|
*/
|
|
19117
|
-
async
|
|
19118
|
-
|
|
19119
|
-
|
|
19120
|
-
|
|
19121
|
-
|
|
19122
|
-
|
|
20212
|
+
async listResumable() {
|
|
20213
|
+
let entries;
|
|
20214
|
+
try {
|
|
20215
|
+
entries = await fsp2.readdir(this.dir);
|
|
20216
|
+
} catch (err) {
|
|
20217
|
+
if (err.code === "ENOENT") return [];
|
|
20218
|
+
return [];
|
|
19123
20219
|
}
|
|
19124
|
-
|
|
19125
|
-
|
|
19126
|
-
|
|
20220
|
+
const out = [];
|
|
20221
|
+
for (const name of entries) {
|
|
20222
|
+
if (!name.endsWith(".jsonl")) continue;
|
|
20223
|
+
const sessionId = name.slice(0, -".jsonl".length);
|
|
20224
|
+
if (sessionId.includes(".replay") || sessionId.includes(".annotations")) continue;
|
|
20225
|
+
const stale = await this.detectStale(sessionId);
|
|
20226
|
+
if (stale) out.push(stale);
|
|
20227
|
+
}
|
|
20228
|
+
return out.sort((a, b) => b.lastEventTs.localeCompare(a.lastEventTs));
|
|
19127
20229
|
}
|
|
19128
|
-
|
|
19129
|
-
|
|
19130
|
-
|
|
19131
|
-
|
|
19132
|
-
|
|
20230
|
+
// ── Internals ──────────────────────────────────────────────────────────
|
|
20231
|
+
filePath(sessionId) {
|
|
20232
|
+
if (!sessionId || sessionId.includes("/") || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
20233
|
+
throw new Error(`Invalid sessionId: ${sessionId}`);
|
|
20234
|
+
}
|
|
20235
|
+
return path6.join(this.dir, `${sessionId}.jsonl`);
|
|
19133
20236
|
}
|
|
19134
|
-
// ── Private helpers ──────────────────────────────────────────────
|
|
19135
20237
|
/**
|
|
19136
|
-
*
|
|
19137
|
-
*
|
|
20238
|
+
* Stream-parse the last few lines of a JSONL log. We do NOT load
|
|
20239
|
+
* the whole file into memory — for long-running sessions the log
|
|
20240
|
+
* can be megabytes. Instead we read tail-ward and find the last
|
|
20241
|
+
* `in_flight_start` / `in_flight_end` pair.
|
|
19138
20242
|
*/
|
|
19139
|
-
async
|
|
19140
|
-
const
|
|
19141
|
-
|
|
19142
|
-
|
|
19143
|
-
|
|
19144
|
-
|
|
19145
|
-
|
|
19146
|
-
|
|
19147
|
-
|
|
19148
|
-
|
|
19149
|
-
|
|
19150
|
-
files: ["SKILL.md"]
|
|
19151
|
-
});
|
|
19152
|
-
return results;
|
|
20243
|
+
async parseForStale(sessionId, fp, raw) {
|
|
20244
|
+
const lines = raw.split("\n");
|
|
20245
|
+
let lastEvent = null;
|
|
20246
|
+
let eventCount = 0;
|
|
20247
|
+
for (const line of lines) {
|
|
20248
|
+
if (!line.trim()) continue;
|
|
20249
|
+
try {
|
|
20250
|
+
const ev = JSON.parse(line);
|
|
20251
|
+
lastEvent = ev;
|
|
20252
|
+
eventCount++;
|
|
20253
|
+
} catch {
|
|
19153
20254
|
}
|
|
19154
|
-
} catch {
|
|
19155
20255
|
}
|
|
19156
|
-
|
|
19157
|
-
|
|
19158
|
-
|
|
19159
|
-
|
|
19160
|
-
|
|
19161
|
-
|
|
19162
|
-
|
|
19163
|
-
|
|
19164
|
-
|
|
19165
|
-
if (meta.name && meta.description) {
|
|
19166
|
-
const skillDir = path6.join(skillsDir, entry.name);
|
|
19167
|
-
const files = await collectFiles(skillDir, skillDir);
|
|
19168
|
-
results.push({
|
|
19169
|
-
name: meta.name,
|
|
19170
|
-
baseDir: skillDir,
|
|
19171
|
-
files
|
|
19172
|
-
});
|
|
19173
|
-
}
|
|
19174
|
-
} catch {
|
|
19175
|
-
}
|
|
19176
|
-
}
|
|
19177
|
-
} catch {
|
|
20256
|
+
if (!lastEvent) return null;
|
|
20257
|
+
if (lastEvent.type === "in_flight_start") {
|
|
20258
|
+
return {
|
|
20259
|
+
sessionId,
|
|
20260
|
+
path: fp,
|
|
20261
|
+
lastEventTs: lastEvent.ts,
|
|
20262
|
+
context: lastEvent.context,
|
|
20263
|
+
eventCount
|
|
20264
|
+
};
|
|
19178
20265
|
}
|
|
19179
|
-
return
|
|
20266
|
+
return null;
|
|
20267
|
+
}
|
|
20268
|
+
};
|
|
20269
|
+
|
|
20270
|
+
// src/storage/tool-audit-log.ts
|
|
20271
|
+
init_atomic_write();
|
|
20272
|
+
var GENESIS_PREV = "0".repeat(64);
|
|
20273
|
+
var ToolAuditLog = class {
|
|
20274
|
+
dir;
|
|
20275
|
+
/** In-memory cache of the last entry's hash (per session), to compute chains efficiently. */
|
|
20276
|
+
tailHash = /* @__PURE__ */ new Map();
|
|
20277
|
+
writeChains = /* @__PURE__ */ new Map();
|
|
20278
|
+
constructor(opts) {
|
|
20279
|
+
this.dir = opts.dir;
|
|
19180
20280
|
}
|
|
19181
20281
|
/**
|
|
19182
|
-
*
|
|
20282
|
+
* Append a tool call/result pair to the chain. Returns the
|
|
20283
|
+
* resulting entry. Idempotency is not guaranteed — if you
|
|
20284
|
+
* record the same tool_use twice you get two entries. That's
|
|
20285
|
+
* intentional: the audit log is a record, not a cache.
|
|
19183
20286
|
*/
|
|
19184
|
-
async
|
|
19185
|
-
|
|
19186
|
-
|
|
19187
|
-
|
|
20287
|
+
async record(input) {
|
|
20288
|
+
let entry = null;
|
|
20289
|
+
await this.enqueue(input.sessionId, async () => {
|
|
20290
|
+
const prevHash = this.tailHash.get(input.sessionId) ?? GENESIS_PREV;
|
|
20291
|
+
const id = randomUUID();
|
|
20292
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
20293
|
+
const content = {
|
|
20294
|
+
id,
|
|
20295
|
+
ts,
|
|
20296
|
+
prevHash,
|
|
20297
|
+
toolName: input.toolName,
|
|
20298
|
+
toolUseId: input.toolUseId,
|
|
20299
|
+
input: input.input,
|
|
20300
|
+
output: input.output,
|
|
20301
|
+
isError: input.isError
|
|
20302
|
+
};
|
|
20303
|
+
const hash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
|
|
20304
|
+
entry = {
|
|
20305
|
+
index: this.tailHash.has(input.sessionId) ? -1 : 0,
|
|
20306
|
+
// placeholder; recomputed below
|
|
20307
|
+
id,
|
|
20308
|
+
ts,
|
|
20309
|
+
prevHash,
|
|
20310
|
+
hash,
|
|
20311
|
+
toolName: input.toolName,
|
|
20312
|
+
toolUseId: input.toolUseId,
|
|
20313
|
+
input: input.input,
|
|
20314
|
+
output: input.output,
|
|
20315
|
+
isError: input.isError
|
|
20316
|
+
};
|
|
20317
|
+
const existing = await this.readAll(input.sessionId);
|
|
20318
|
+
const index = existing.length;
|
|
20319
|
+
const finalContent = { ...content, index };
|
|
20320
|
+
const finalHash = createHash("sha256").update(stableStringify2(finalContent), "utf8").digest("hex");
|
|
20321
|
+
entry = { ...entry, index, hash: finalHash };
|
|
20322
|
+
await this.appendLine(input.sessionId, entry);
|
|
20323
|
+
this.tailHash.set(input.sessionId, finalHash);
|
|
20324
|
+
});
|
|
20325
|
+
return entry;
|
|
19188
20326
|
}
|
|
19189
20327
|
/**
|
|
19190
|
-
*
|
|
20328
|
+
* Walk the chain and verify every entry's hash and prevHash.
|
|
20329
|
+
* Returns a structured verdict — never throws.
|
|
19191
20330
|
*/
|
|
19192
|
-
|
|
19193
|
-
const
|
|
19194
|
-
if (
|
|
19195
|
-
|
|
20331
|
+
async verify(sessionId) {
|
|
20332
|
+
const entries = await this.readAll(sessionId);
|
|
20333
|
+
if (entries.length === 0) return { ok: true, entries: 0 };
|
|
20334
|
+
if (entries[0].prevHash !== GENESIS_PREV) {
|
|
20335
|
+
return {
|
|
20336
|
+
ok: false,
|
|
20337
|
+
brokenAt: 0,
|
|
20338
|
+
reason: "first entry is not the genesis (prevHash != 0\u20260)"
|
|
20339
|
+
};
|
|
20340
|
+
}
|
|
20341
|
+
let prevHash = GENESIS_PREV;
|
|
20342
|
+
for (let i = 0; i < entries.length; i++) {
|
|
20343
|
+
const e = entries[i];
|
|
20344
|
+
if (e.prevHash !== prevHash) {
|
|
20345
|
+
return {
|
|
20346
|
+
ok: false,
|
|
20347
|
+
brokenAt: i,
|
|
20348
|
+
reason: `prevHash mismatch at entry ${i} (expected ${prevHash.slice(0, 8)}\u2026, got ${e.prevHash.slice(0, 8)}\u2026)`
|
|
20349
|
+
};
|
|
20350
|
+
}
|
|
20351
|
+
const content = {
|
|
20352
|
+
id: e.id,
|
|
20353
|
+
ts: e.ts,
|
|
20354
|
+
prevHash: e.prevHash,
|
|
20355
|
+
toolName: e.toolName,
|
|
20356
|
+
toolUseId: e.toolUseId,
|
|
20357
|
+
input: e.input,
|
|
20358
|
+
output: e.output,
|
|
20359
|
+
isError: e.isError,
|
|
20360
|
+
index: e.index
|
|
20361
|
+
};
|
|
20362
|
+
const expectedHash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
|
|
20363
|
+
if (expectedHash !== e.hash) {
|
|
20364
|
+
return {
|
|
20365
|
+
ok: false,
|
|
20366
|
+
brokenAt: i,
|
|
20367
|
+
reason: `hash mismatch at entry ${i} (entry content was modified)`
|
|
20368
|
+
};
|
|
20369
|
+
}
|
|
20370
|
+
prevHash = e.hash;
|
|
19196
20371
|
}
|
|
20372
|
+
return { ok: true, entries: entries.length };
|
|
19197
20373
|
}
|
|
19198
|
-
|
|
19199
|
-
|
|
19200
|
-
|
|
19201
|
-
|
|
19202
|
-
|
|
19203
|
-
|
|
19204
|
-
|
|
19205
|
-
|
|
19206
|
-
let value = [];
|
|
19207
|
-
const flush = () => {
|
|
19208
|
-
if (key) {
|
|
19209
|
-
out[key] = value.join("\n").trim();
|
|
20374
|
+
/** All entries for a session, in insertion order. */
|
|
20375
|
+
async load(sessionId) {
|
|
20376
|
+
return this.readAll(sessionId);
|
|
20377
|
+
}
|
|
20378
|
+
// ── Internals ────────────────────────────────────────────────────────────
|
|
20379
|
+
filePath(sessionId) {
|
|
20380
|
+
if (!sessionId || sessionId.includes("/") || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
20381
|
+
throw new Error(`Invalid sessionId: ${sessionId}`);
|
|
19210
20382
|
}
|
|
19211
|
-
|
|
19212
|
-
|
|
19213
|
-
|
|
19214
|
-
|
|
19215
|
-
|
|
19216
|
-
|
|
19217
|
-
|
|
19218
|
-
|
|
19219
|
-
|
|
19220
|
-
|
|
19221
|
-
|
|
19222
|
-
|
|
19223
|
-
|
|
19224
|
-
value = [rest];
|
|
19225
|
-
} else {
|
|
19226
|
-
value = [];
|
|
20383
|
+
return path6.join(this.dir, `${sessionId}.audit.jsonl`);
|
|
20384
|
+
}
|
|
20385
|
+
async readAll(sessionId) {
|
|
20386
|
+
const fp = this.filePath(sessionId);
|
|
20387
|
+
try {
|
|
20388
|
+
const raw = await fsp2.readFile(fp, "utf8");
|
|
20389
|
+
const out = [];
|
|
20390
|
+
for (const line of raw.split("\n")) {
|
|
20391
|
+
if (!line.trim()) continue;
|
|
20392
|
+
try {
|
|
20393
|
+
out.push(JSON.parse(line));
|
|
20394
|
+
} catch {
|
|
20395
|
+
}
|
|
19227
20396
|
}
|
|
19228
|
-
|
|
19229
|
-
|
|
20397
|
+
return out;
|
|
20398
|
+
} catch (err) {
|
|
20399
|
+
if (err.code === "ENOENT") return [];
|
|
20400
|
+
return [];
|
|
19230
20401
|
}
|
|
19231
20402
|
}
|
|
19232
|
-
|
|
19233
|
-
|
|
20403
|
+
async appendLine(sessionId, entry) {
|
|
20404
|
+
const fp = this.filePath(sessionId);
|
|
20405
|
+
const existing = await this.readAll(sessionId);
|
|
20406
|
+
const all = [...existing, entry];
|
|
20407
|
+
await atomicWrite(fp, all.map((e) => JSON.stringify(e)).join("\n") + (all.length ? "\n" : ""));
|
|
20408
|
+
}
|
|
20409
|
+
enqueue(sessionId, fn) {
|
|
20410
|
+
const prev = this.writeChains.get(sessionId) ?? Promise.resolve();
|
|
20411
|
+
const next = prev.then(fn, fn);
|
|
20412
|
+
this.writeChains.set(
|
|
20413
|
+
sessionId,
|
|
20414
|
+
next.catch(() => void 0)
|
|
20415
|
+
);
|
|
20416
|
+
return next;
|
|
20417
|
+
}
|
|
20418
|
+
};
|
|
20419
|
+
function stableStringify2(value) {
|
|
20420
|
+
return JSON.stringify(sortKeys2(value));
|
|
19234
20421
|
}
|
|
19235
|
-
|
|
19236
|
-
|
|
19237
|
-
|
|
19238
|
-
|
|
19239
|
-
const
|
|
19240
|
-
const
|
|
19241
|
-
|
|
19242
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
19243
|
-
results.push(...await collectFiles(fullPath, baseDir));
|
|
19244
|
-
} else {
|
|
19245
|
-
results.push(relPath);
|
|
20422
|
+
function sortKeys2(value) {
|
|
20423
|
+
if (Array.isArray(value)) return value.map(sortKeys2);
|
|
20424
|
+
if (value && typeof value === "object") {
|
|
20425
|
+
const obj = value;
|
|
20426
|
+
const sorted = {};
|
|
20427
|
+
for (const key of Object.keys(obj).sort()) {
|
|
20428
|
+
sorted[key] = sortKeys2(obj[key]);
|
|
19246
20429
|
}
|
|
20430
|
+
return sorted;
|
|
19247
20431
|
}
|
|
19248
|
-
return
|
|
20432
|
+
return value;
|
|
19249
20433
|
}
|
|
19250
20434
|
|
|
19251
20435
|
// src/storage/session-rewinder.ts
|
|
@@ -19421,7 +20605,7 @@ var DefaultPromptStore = class {
|
|
|
19421
20605
|
}
|
|
19422
20606
|
async list() {
|
|
19423
20607
|
await ensureDir(this.dir);
|
|
19424
|
-
|
|
20608
|
+
const entries = [];
|
|
19425
20609
|
try {
|
|
19426
20610
|
const files = await fsp2.readdir(this.dir);
|
|
19427
20611
|
for (const file of files) {
|
|
@@ -19697,8 +20881,8 @@ var CloudSync = class {
|
|
|
19697
20881
|
const localPath = this.categoryToPath(cat);
|
|
19698
20882
|
if (!localPath) continue;
|
|
19699
20883
|
try {
|
|
19700
|
-
const
|
|
19701
|
-
if (
|
|
20884
|
+
const stat8 = await fsp2.stat(localPath);
|
|
20885
|
+
if (stat8.isDirectory()) {
|
|
19702
20886
|
const files = await this.walkDir(localPath, localPath);
|
|
19703
20887
|
for (const file of files) {
|
|
19704
20888
|
const content = await fsp2.readFile(file, "utf8");
|
|
@@ -19723,8 +20907,8 @@ var CloudSync = class {
|
|
|
19723
20907
|
const localPath = this.categoryToPath(cat);
|
|
19724
20908
|
if (!localPath) continue;
|
|
19725
20909
|
try {
|
|
19726
|
-
const
|
|
19727
|
-
if (
|
|
20910
|
+
const stat8 = await fsp2.stat(localPath);
|
|
20911
|
+
if (stat8.isDirectory()) {
|
|
19728
20912
|
const files = await this.walkDir(localPath, localPath);
|
|
19729
20913
|
for (const file of files) {
|
|
19730
20914
|
const content = await fsp2.readFile(file);
|
|
@@ -20325,7 +21509,7 @@ var SkillGenerator = class {
|
|
|
20325
21509
|
};
|
|
20326
21510
|
return [commonInjection, ...stackSpecific[stack] ?? []];
|
|
20327
21511
|
}
|
|
20328
|
-
getConfigPatterns(
|
|
21512
|
+
getConfigPatterns(_stack) {
|
|
20329
21513
|
const commonConfig = [
|
|
20330
21514
|
{
|
|
20331
21515
|
id: "insecure-tls",
|
|
@@ -20907,7 +22091,6 @@ var SecurityScannerOrchestrator = class {
|
|
|
20907
22091
|
retryPolicy;
|
|
20908
22092
|
errorHandler;
|
|
20909
22093
|
detector = defaultTechStackDetector;
|
|
20910
|
-
reportGenerator = defaultReportGenerator;
|
|
20911
22094
|
gitignoreUpdater = defaultGitignoreUpdater;
|
|
20912
22095
|
/**
|
|
20913
22096
|
* Wraps provider.complete with retry logic using the injected RetryPolicy.
|
|
@@ -21091,7 +22274,7 @@ Return ONLY the JSON object, no markdown, no explanation.`;
|
|
|
21091
22274
|
/**
|
|
21092
22275
|
* Scan a batch of files using LLM.
|
|
21093
22276
|
*/
|
|
21094
|
-
async scanFileBatchLLM(provider, model, files, skill,
|
|
22277
|
+
async scanFileBatchLLM(provider, model, files, skill, _techStack) {
|
|
21095
22278
|
const fileContents = [];
|
|
21096
22279
|
for (const file of files) {
|
|
21097
22280
|
try {
|
|
@@ -21288,7 +22471,7 @@ Be specific about the vulnerabilities found and how to fix them.`;
|
|
|
21288
22471
|
/**
|
|
21289
22472
|
* Gather project info for skill generation.
|
|
21290
22473
|
*/
|
|
21291
|
-
async gatherProjectInfo(projectRoot,
|
|
22474
|
+
async gatherProjectInfo(projectRoot, _techStack) {
|
|
21292
22475
|
const info = [];
|
|
21293
22476
|
const keyFiles = [
|
|
21294
22477
|
"package.json",
|
|
@@ -21320,7 +22503,7 @@ ${dirs.join(", ")}`);
|
|
|
21320
22503
|
/**
|
|
21321
22504
|
* Gather files to scan based on patterns.
|
|
21322
22505
|
*/
|
|
21323
|
-
async gatherFiles(root,
|
|
22506
|
+
async gatherFiles(root, _patterns, depth) {
|
|
21324
22507
|
const files = [];
|
|
21325
22508
|
const maxDepth = depth === "quick" ? 2 : depth === "deep" ? 20 : 5;
|
|
21326
22509
|
const extensions = [".ts", ".js", ".jsx", ".tsx", ".py", ".go", ".java", ".cs", ".rs"];
|
|
@@ -21579,22 +22762,22 @@ Use \`/security report <number>\` to view a specific report.` };
|
|
|
21579
22762
|
}
|
|
21580
22763
|
const index = Number.parseInt(reportId, 10) - 1;
|
|
21581
22764
|
if (!Number.isNaN(index) && reports[index]) {
|
|
21582
|
-
const { readFile:
|
|
21583
|
-
const content = await
|
|
22765
|
+
const { readFile: readFile37 } = await import('fs/promises');
|
|
22766
|
+
const content = await readFile37(join(reportsDir, reports[index]), "utf-8");
|
|
21584
22767
|
return { message: `# Security Report
|
|
21585
22768
|
|
|
21586
22769
|
${content}` };
|
|
21587
22770
|
}
|
|
21588
22771
|
const match = reports.find((r) => r.includes(reportId));
|
|
21589
22772
|
if (match) {
|
|
21590
|
-
const { readFile:
|
|
21591
|
-
const content = await
|
|
22773
|
+
const { readFile: readFile37 } = await import('fs/promises');
|
|
22774
|
+
const content = await readFile37(join(reportsDir, match), "utf-8");
|
|
21592
22775
|
return { message: `# Security Report
|
|
21593
22776
|
|
|
21594
22777
|
${content}` };
|
|
21595
22778
|
}
|
|
21596
22779
|
return { message: `\u274C Report "${reportId}" not found. Use \`/security report\` to see available reports.` };
|
|
21597
|
-
} catch (
|
|
22780
|
+
} catch (_error) {
|
|
21598
22781
|
return { message: "\u{1F4ED} No security reports found. Run `/security scan` first." };
|
|
21599
22782
|
}
|
|
21600
22783
|
}
|
|
@@ -21647,7 +22830,6 @@ var FleetManager = class {
|
|
|
21647
22830
|
/** Usage rollup across all subagents. */
|
|
21648
22831
|
usage;
|
|
21649
22832
|
manifestPath;
|
|
21650
|
-
sessionsRoot;
|
|
21651
22833
|
directorRunId;
|
|
21652
22834
|
/** Spawn cap (lifetime total). Infinity means unlimited. */
|
|
21653
22835
|
maxSpawns;
|
|
@@ -21675,9 +22857,14 @@ var FleetManager = class {
|
|
|
21675
22857
|
_usedNicknames = /* @__PURE__ */ new Set();
|
|
21676
22858
|
/** The coordinator (wired via setCoordinator by Director after construction). */
|
|
21677
22859
|
coordinator = null;
|
|
22860
|
+
/** Leader agent's current context pressure (full request tokens). */
|
|
22861
|
+
leaderContextPressure = 0;
|
|
22862
|
+
/** Maximum context load fraction before spawn is refused. */
|
|
22863
|
+
maxLeaderContextLoad;
|
|
22864
|
+
/** Provider's max context window in tokens. */
|
|
22865
|
+
maxContext;
|
|
21678
22866
|
constructor(opts = {}) {
|
|
21679
22867
|
this.manifestPath = opts.manifestPath;
|
|
21680
|
-
this.sessionsRoot = opts.sessionsRoot;
|
|
21681
22868
|
this.directorRunId = opts.directorRunId ?? randomUUID();
|
|
21682
22869
|
this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
|
|
21683
22870
|
this.maxSpawnDepth = opts.maxSpawnDepth ?? 2;
|
|
@@ -21685,6 +22872,8 @@ var FleetManager = class {
|
|
|
21685
22872
|
this.sessionWriter = opts.sessionWriter ?? null;
|
|
21686
22873
|
this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
|
|
21687
22874
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
22875
|
+
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
22876
|
+
this.maxContext = opts.maxContext ?? 128e3;
|
|
21688
22877
|
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(
|
|
21689
22878
|
opts.stateCheckpointPath,
|
|
21690
22879
|
{
|
|
@@ -21699,7 +22888,7 @@ var FleetManager = class {
|
|
|
21699
22888
|
this.fleet = new FleetBus();
|
|
21700
22889
|
this.usage = new FleetUsageAggregator(
|
|
21701
22890
|
this.fleet,
|
|
21702
|
-
(
|
|
22891
|
+
(_id, provider, model) => {
|
|
21703
22892
|
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
21704
22893
|
return void 0;
|
|
21705
22894
|
},
|
|
@@ -21738,7 +22927,7 @@ var FleetManager = class {
|
|
|
21738
22927
|
* which cap was exceeded. Does NOT throw — the caller decides
|
|
21739
22928
|
* how to surface the rejection.
|
|
21740
22929
|
*/
|
|
21741
|
-
canSpawn(
|
|
22930
|
+
canSpawn(_config) {
|
|
21742
22931
|
if (this.spawnDepth >= this.maxSpawnDepth) {
|
|
21743
22932
|
return { kind: "max_spawn_depth", limit: this.maxSpawnDepth, observed: this.spawnDepth };
|
|
21744
22933
|
}
|
|
@@ -21751,8 +22940,21 @@ var FleetManager = class {
|
|
|
21751
22940
|
return { kind: "max_cost_usd", limit: this.maxFleetCostUsd, observed: totalCost };
|
|
21752
22941
|
}
|
|
21753
22942
|
}
|
|
22943
|
+
if (this.maxLeaderContextLoad < 1) {
|
|
22944
|
+
const threshold = this.maxContext * this.maxLeaderContextLoad;
|
|
22945
|
+
if (this.leaderContextPressure >= threshold) {
|
|
22946
|
+
return {
|
|
22947
|
+
kind: "max_context_load",
|
|
22948
|
+
limit: threshold,
|
|
22949
|
+
observed: this.leaderContextPressure
|
|
22950
|
+
};
|
|
22951
|
+
}
|
|
22952
|
+
}
|
|
21754
22953
|
return null;
|
|
21755
22954
|
}
|
|
22955
|
+
setLeaderContextPressure(tokens) {
|
|
22956
|
+
this.leaderContextPressure = tokens;
|
|
22957
|
+
}
|
|
21756
22958
|
/**
|
|
21757
22959
|
* Assign a memorable nickname (e.g. "Einstein (Bug Hunter)") to the config,
|
|
21758
22960
|
* record it so the same name is never reused, then record the spawn.
|
|
@@ -21977,7 +23179,7 @@ function createMcpControlTool(opts) {
|
|
|
21977
23179
|
mutating: true,
|
|
21978
23180
|
riskTier: "standard",
|
|
21979
23181
|
inputSchema,
|
|
21980
|
-
async execute(raw
|
|
23182
|
+
async execute(raw) {
|
|
21981
23183
|
const input = raw;
|
|
21982
23184
|
return mcpControlDispatch(input, { getConfig, configPath, registry });
|
|
21983
23185
|
}
|
|
@@ -22525,7 +23727,6 @@ var Agent = class {
|
|
|
22525
23727
|
pipelines;
|
|
22526
23728
|
ctx;
|
|
22527
23729
|
maxIterations;
|
|
22528
|
-
iterationTimeoutMs;
|
|
22529
23730
|
executionStrategy;
|
|
22530
23731
|
perIterationOutputCapBytes;
|
|
22531
23732
|
plugins = [];
|
|
@@ -22543,7 +23744,6 @@ var Agent = class {
|
|
|
22543
23744
|
this.pipelines = init.pipelines;
|
|
22544
23745
|
this.ctx = init.context;
|
|
22545
23746
|
this.maxIterations = init.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
22546
|
-
this.iterationTimeoutMs = init.iterationTimeoutMs ?? 3e5;
|
|
22547
23747
|
this.executionStrategy = init.executionStrategy ?? "smart";
|
|
22548
23748
|
this.perIterationOutputCapBytes = init.perIterationOutputCapBytes ?? 1e5;
|
|
22549
23749
|
this.autoExtendLimit = init.autoExtendLimit ?? true;
|
|
@@ -22565,9 +23765,6 @@ var Agent = class {
|
|
|
22565
23765
|
get permission() {
|
|
22566
23766
|
return this.container.resolve(TOKENS.PermissionPolicy);
|
|
22567
23767
|
}
|
|
22568
|
-
get scrubber() {
|
|
22569
|
-
return this.container.resolve(TOKENS.SecretScrubber);
|
|
22570
|
-
}
|
|
22571
23768
|
get renderer() {
|
|
22572
23769
|
return this.container.has(TOKENS.Renderer) ? this.container.resolve(TOKENS.Renderer) : void 0;
|
|
22573
23770
|
}
|
|
@@ -22697,6 +23894,11 @@ var Agent = class {
|
|
|
22697
23894
|
if (controller.signal.aborted) {
|
|
22698
23895
|
return { status: "aborted", iterations };
|
|
22699
23896
|
}
|
|
23897
|
+
await this.ctx.session.writeInFlightMarker(`iteration ${i} / max ${this.maxIterations}`).catch((err) => {
|
|
23898
|
+
this.logger.debug?.(
|
|
23899
|
+
`in-flight marker write failed: ${err instanceof Error ? err.message : String(err)}`
|
|
23900
|
+
);
|
|
23901
|
+
});
|
|
22700
23902
|
if (autonomousContinue) {
|
|
22701
23903
|
consumeAutonomousContinue(this.ctx);
|
|
22702
23904
|
}
|
|
@@ -22778,6 +23980,7 @@ var Agent = class {
|
|
|
22778
23980
|
finalText = responseResult.finalText;
|
|
22779
23981
|
const toolUses = res.content.filter(isToolUseBlock);
|
|
22780
23982
|
if (toolUses.length === 0) {
|
|
23983
|
+
this.emitContextPct();
|
|
22781
23984
|
this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
|
|
22782
23985
|
if (autonomousContinue && responseResult.directive === "continue") {
|
|
22783
23986
|
await this.compactContextIfNeeded();
|
|
@@ -22791,11 +23994,13 @@ var Agent = class {
|
|
|
22791
23994
|
}
|
|
22792
23995
|
await this.executeTools(toolUses);
|
|
22793
23996
|
if (autonomousContinue && consumeAutonomousContinue(this.ctx)) {
|
|
23997
|
+
this.emitContextPct();
|
|
22794
23998
|
this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
|
|
22795
23999
|
await this.compactContextIfNeeded();
|
|
22796
24000
|
await this.extensions.runAfterIteration(this.ctx, i);
|
|
22797
24001
|
continue;
|
|
22798
24002
|
}
|
|
24003
|
+
this.emitContextPct();
|
|
22799
24004
|
this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
|
|
22800
24005
|
await this.compactContextIfNeeded();
|
|
22801
24006
|
await this.extensions.runAfterIteration(this.ctx, i);
|
|
@@ -22808,6 +24013,12 @@ var Agent = class {
|
|
|
22808
24013
|
}
|
|
22809
24014
|
} finally {
|
|
22810
24015
|
offSubagentDone();
|
|
24016
|
+
const reason = controller.signal.aborted ? "aborted" : "clean";
|
|
24017
|
+
await this.ctx.session.clearInFlightMarker(reason).catch((err) => {
|
|
24018
|
+
this.logger.debug?.(
|
|
24019
|
+
`in-flight marker clear failed: ${err instanceof Error ? err.message : String(err)}`
|
|
24020
|
+
);
|
|
24021
|
+
});
|
|
22811
24022
|
}
|
|
22812
24023
|
}
|
|
22813
24024
|
/**
|
|
@@ -23100,6 +24311,19 @@ var Agent = class {
|
|
|
23100
24311
|
async compactContextIfNeeded() {
|
|
23101
24312
|
await this.pipelines.contextWindow.run(this.ctx);
|
|
23102
24313
|
}
|
|
24314
|
+
/**
|
|
24315
|
+
* Emit the current context window load as a `ctx.pct` event so subscribers
|
|
24316
|
+
* (FleetBus → TUI) can render a live fill bar per agent.
|
|
24317
|
+
*/
|
|
24318
|
+
emitContextPct() {
|
|
24319
|
+
const maxContext = this.ctx.provider.capabilities.maxContext ?? 2e5;
|
|
24320
|
+
const { total } = estimateRequestTokens(
|
|
24321
|
+
this.ctx.messages,
|
|
24322
|
+
this.ctx.systemPrompt,
|
|
24323
|
+
this.ctx.tools ?? []
|
|
24324
|
+
);
|
|
24325
|
+
this.events.emit("ctx.pct", { load: total / maxContext, tokens: total, maxContext });
|
|
24326
|
+
}
|
|
23103
24327
|
};
|
|
23104
24328
|
function toError(err) {
|
|
23105
24329
|
return err instanceof Error ? err : new Error(String(err));
|
|
@@ -23776,8 +25000,8 @@ ${mem}`);
|
|
|
23776
25000
|
}
|
|
23777
25001
|
async dirExists(p) {
|
|
23778
25002
|
try {
|
|
23779
|
-
const
|
|
23780
|
-
return
|
|
25003
|
+
const stat8 = await fsp2.stat(p);
|
|
25004
|
+
return stat8.isDirectory();
|
|
23781
25005
|
} catch {
|
|
23782
25006
|
return false;
|
|
23783
25007
|
}
|
|
@@ -26113,6 +27337,203 @@ function assertSafePath(dir, projectRoot) {
|
|
|
26113
27337
|
}
|
|
26114
27338
|
}
|
|
26115
27339
|
|
|
27340
|
+
// src/coordination/collab-bus.ts
|
|
27341
|
+
var CollaborationBus = class {
|
|
27342
|
+
pausePromise = null;
|
|
27343
|
+
pauseResolve = null;
|
|
27344
|
+
pausedAtMs = null;
|
|
27345
|
+
pausedBy = null;
|
|
27346
|
+
// ── State queries ──────────────────────────────────────────────────────
|
|
27347
|
+
isPaused() {
|
|
27348
|
+
return this.pausePromise !== null;
|
|
27349
|
+
}
|
|
27350
|
+
getState() {
|
|
27351
|
+
return {
|
|
27352
|
+
paused: this.isPaused(),
|
|
27353
|
+
pausedAt: this.pausedAtMs ? new Date(this.pausedAtMs).toISOString() : null,
|
|
27354
|
+
pausedBy: this.pausedBy
|
|
27355
|
+
};
|
|
27356
|
+
}
|
|
27357
|
+
// ── Pause / resume control ─────────────────────────────────────────────
|
|
27358
|
+
/**
|
|
27359
|
+
* Pause the agent loop. Idempotent: a second `requestPause` while
|
|
27360
|
+
* already paused is a no-op (the original pause wins; we do not
|
|
27361
|
+
* overwrite `pausedBy`). Returns true when the state actually
|
|
27362
|
+
* transitioned, false when it was already paused.
|
|
27363
|
+
*/
|
|
27364
|
+
requestPause(byParticipant) {
|
|
27365
|
+
if (this.isPaused()) return false;
|
|
27366
|
+
this.pausedAtMs = Date.now();
|
|
27367
|
+
this.pausedBy = byParticipant;
|
|
27368
|
+
this.pausePromise = new Promise((resolve9) => {
|
|
27369
|
+
this.pauseResolve = resolve9;
|
|
27370
|
+
});
|
|
27371
|
+
return true;
|
|
27372
|
+
}
|
|
27373
|
+
/**
|
|
27374
|
+
* Resume the agent loop. Returns true when the state actually
|
|
27375
|
+
* transitioned from paused → running, false when it was already
|
|
27376
|
+
* running (no-op).
|
|
27377
|
+
*/
|
|
27378
|
+
resume() {
|
|
27379
|
+
if (!this.isPaused()) return false;
|
|
27380
|
+
if (this.pauseResolve) this.pauseResolve();
|
|
27381
|
+
this.pausePromise = null;
|
|
27382
|
+
this.pauseResolve = null;
|
|
27383
|
+
this.pausedAtMs = null;
|
|
27384
|
+
this.pausedBy = null;
|
|
27385
|
+
return true;
|
|
27386
|
+
}
|
|
27387
|
+
// ── Wait semantics (consumed by the middleware) ────────────────────────
|
|
27388
|
+
/**
|
|
27389
|
+
* Block until the bus is resumed or the timeout fires. Returns:
|
|
27390
|
+
* - `true` → bus was resumed in time
|
|
27391
|
+
* - `false` → timeout fired; bus was auto-resumed as a side effect
|
|
27392
|
+
*
|
|
27393
|
+
* When `timeoutMs` is `0` the wait is unbounded (the middleware must
|
|
27394
|
+
* be paired with an external AbortSignal in that case — we don't
|
|
27395
|
+
* expose one here to keep the API simple).
|
|
27396
|
+
*/
|
|
27397
|
+
async waitForResume(timeoutMs) {
|
|
27398
|
+
if (!this.isPaused()) return true;
|
|
27399
|
+
if (!this.pausePromise) return true;
|
|
27400
|
+
if (timeoutMs === 0) {
|
|
27401
|
+
await this.pausePromise;
|
|
27402
|
+
return true;
|
|
27403
|
+
}
|
|
27404
|
+
let timer;
|
|
27405
|
+
const timeoutPromise = new Promise((resolve9) => {
|
|
27406
|
+
timer = setTimeout(() => resolve9("timeout"), timeoutMs);
|
|
27407
|
+
});
|
|
27408
|
+
const resumedPromise = this.pausePromise.then(() => "resumed");
|
|
27409
|
+
const winner = await Promise.race([resumedPromise, timeoutPromise]);
|
|
27410
|
+
if (timer) clearTimeout(timer);
|
|
27411
|
+
if (winner === "timeout") {
|
|
27412
|
+
this.resume();
|
|
27413
|
+
return false;
|
|
27414
|
+
}
|
|
27415
|
+
return true;
|
|
27416
|
+
}
|
|
27417
|
+
// ── Manual tool-call injection (Phase 4) ─────────────────────────────────
|
|
27418
|
+
//
|
|
27419
|
+
// A controller can ask the agent loop to use a specific tool_result
|
|
27420
|
+
// for a given tool_use_id — bypassing the real tool execution. This
|
|
27421
|
+
// is "I want the agent to think the read returned THIS content" or
|
|
27422
|
+
// "skip the bash call, just give it the answer I typed". The
|
|
27423
|
+
// injection is matched by tool_use_id, consumed once, and discarded.
|
|
27424
|
+
injectionQueue = /* @__PURE__ */ new Map();
|
|
27425
|
+
/**
|
|
27426
|
+
* Queue a manual tool result. The next time the agent's toolCall
|
|
27427
|
+
* pipeline sees a matching `toolUse.id`, the
|
|
27428
|
+
* `collabInjectMiddleware` consumes this entry and replaces the
|
|
27429
|
+
* real tool execution. Returns `false` if an injection for the
|
|
27430
|
+
* same id is already queued (idempotent — first write wins).
|
|
27431
|
+
*/
|
|
27432
|
+
injectToolResult(input) {
|
|
27433
|
+
if (this.injectionQueue.has(input.toolUseId)) return false;
|
|
27434
|
+
this.injectionQueue.set(input.toolUseId, input);
|
|
27435
|
+
return true;
|
|
27436
|
+
}
|
|
27437
|
+
/**
|
|
27438
|
+
* Pop an injection from the queue. Returns the payload (and
|
|
27439
|
+
* removes it) if one is pending, or `null` when nothing matches.
|
|
27440
|
+
* Called by the middleware on every tool call.
|
|
27441
|
+
*/
|
|
27442
|
+
takeInjection(toolUseId) {
|
|
27443
|
+
const v = this.injectionQueue.get(toolUseId);
|
|
27444
|
+
if (!v) return null;
|
|
27445
|
+
this.injectionQueue.delete(toolUseId);
|
|
27446
|
+
return v;
|
|
27447
|
+
}
|
|
27448
|
+
/** Inspect the queue size (for observability / tests). */
|
|
27449
|
+
pendingInjectionCount() {
|
|
27450
|
+
return this.injectionQueue.size;
|
|
27451
|
+
}
|
|
27452
|
+
};
|
|
27453
|
+
|
|
27454
|
+
// src/middleware/collab-pause.ts
|
|
27455
|
+
function collabPauseMiddleware(bus, opts = {}) {
|
|
27456
|
+
const timeoutMs = opts.defaultTimeoutMs ?? 6e4;
|
|
27457
|
+
const logger = opts.logger;
|
|
27458
|
+
return async function collabPause(payload, next) {
|
|
27459
|
+
if (!bus.isPaused()) {
|
|
27460
|
+
return next();
|
|
27461
|
+
}
|
|
27462
|
+
const state = bus.getState();
|
|
27463
|
+
logger?.debug?.(
|
|
27464
|
+
`collab-pause: tool '${payload.toolUse.name}' blocked \u2014 bus paused by ${state.pausedBy ?? "?"} at ${state.pausedAt ?? "?"}, waiting up to ${timeoutMs}ms for resume`
|
|
27465
|
+
);
|
|
27466
|
+
const resumed = await bus.waitForResume(timeoutMs);
|
|
27467
|
+
if (!resumed) {
|
|
27468
|
+
logger?.warn?.(
|
|
27469
|
+
`collab-pause: timeout after ${timeoutMs}ms \u2014 auto-resuming the bus to unblock the agent loop`
|
|
27470
|
+
);
|
|
27471
|
+
} else {
|
|
27472
|
+
logger?.debug?.(`collab-pause: resumed \u2014 proceeding with tool '${payload.toolUse.name}'`);
|
|
27473
|
+
}
|
|
27474
|
+
return next();
|
|
27475
|
+
};
|
|
27476
|
+
}
|
|
27477
|
+
function collabInjectMiddleware(bus, opts = {}) {
|
|
27478
|
+
const logger = opts.logger;
|
|
27479
|
+
return async function collabInject(payload, next) {
|
|
27480
|
+
const injected = bus.takeInjection(payload.toolUse.id);
|
|
27481
|
+
if (!injected) {
|
|
27482
|
+
return next();
|
|
27483
|
+
}
|
|
27484
|
+
logger?.debug?.(
|
|
27485
|
+
`collab-inject: tool '${payload.toolUse.name}' (id ${payload.toolUse.id}) \u2014 using controller-injected result (reason: ${injected.reason})`
|
|
27486
|
+
);
|
|
27487
|
+
payload.result = {
|
|
27488
|
+
type: "tool_result",
|
|
27489
|
+
tool_use_id: payload.toolUse.id,
|
|
27490
|
+
content: typeof injected.content === "string" ? injected.content : JSON.stringify(injected.content),
|
|
27491
|
+
is_error: injected.isError
|
|
27492
|
+
};
|
|
27493
|
+
};
|
|
27494
|
+
}
|
|
27495
|
+
|
|
27496
|
+
// src/replay/replay-provider-runner.ts
|
|
27497
|
+
var ReplayProviderRunner = class {
|
|
27498
|
+
constructor(inner, opts) {
|
|
27499
|
+
this.inner = inner;
|
|
27500
|
+
this.opts = opts;
|
|
27501
|
+
}
|
|
27502
|
+
inner;
|
|
27503
|
+
opts;
|
|
27504
|
+
async run(runOpts) {
|
|
27505
|
+
const hash = hashRequest(runOpts.request);
|
|
27506
|
+
const cached = await this.opts.log.lookup(this.opts.sessionId, hash);
|
|
27507
|
+
if (this.opts.mode === "replay") {
|
|
27508
|
+
if (!cached) {
|
|
27509
|
+
this.opts.logger?.warn?.(
|
|
27510
|
+
`replay: no recorded response for hash ${hash} (model ${runOpts.request.model})`
|
|
27511
|
+
);
|
|
27512
|
+
throw new Error(
|
|
27513
|
+
`ReplayProviderRunner: no recorded response for hash ${hash} in session ${this.opts.sessionId}. Either the request changed since recording, or this session has no replay log.`
|
|
27514
|
+
);
|
|
27515
|
+
}
|
|
27516
|
+
this.opts.logger?.debug?.(
|
|
27517
|
+
`replay: served cached response for hash ${hash} (recorded ${cached.ts})`
|
|
27518
|
+
);
|
|
27519
|
+
return cached.response;
|
|
27520
|
+
}
|
|
27521
|
+
if (this.opts.mode === "auto" && cached) {
|
|
27522
|
+
this.opts.logger?.debug?.(
|
|
27523
|
+
`replay: auto-hit hash ${hash}, served cached response`
|
|
27524
|
+
);
|
|
27525
|
+
return cached.response;
|
|
27526
|
+
}
|
|
27527
|
+
const response = await this.inner.run(runOpts);
|
|
27528
|
+
await this.opts.log.record({
|
|
27529
|
+
sessionId: this.opts.sessionId,
|
|
27530
|
+
request: runOpts.request,
|
|
27531
|
+
response
|
|
27532
|
+
});
|
|
27533
|
+
return response;
|
|
27534
|
+
}
|
|
27535
|
+
};
|
|
27536
|
+
|
|
26116
27537
|
// src/plugins/prompts-plugin.ts
|
|
26117
27538
|
function createPromptsPlugin(opts) {
|
|
26118
27539
|
let store = null;
|
|
@@ -27170,6 +28591,6 @@ ${formatPlan(updated)}`
|
|
|
27170
28591
|
};
|
|
27171
28592
|
}
|
|
27172
28593
|
|
|
27173
|
-
export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, ALL_SYNC_CATEGORIES, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutoPhasePlanner, AutoPhaseRunner, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, CheckpointManager, CloudSync, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultPromptStore, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorStateCheckpoint, DoneConditionChecker, ERROR_CODES, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, FsError, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, MAX_JOURNAL_ENTRIES, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, PhaseGraphBuilder, PhaseOrchestrator, PhaseStore, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, ScopedEventBus, SddParallelRun, SddTaskDecomposer, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolError, ToolExecutor, ToolRegistry, WorktreeManager, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyRosterBudget, asBlocks, asText, assertSafePath, atomicWrite, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildBtwBlock, buildChildEnv, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, compileUserRegex, completePartialObject, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, consumeBtwNotes, context7Server, contextManagerTool, createAutoExecutor, createAutoPhaseFromTaskGraph, createContextManagerTool, createDefaultPipelines, createDelegateTool, createGitPlugin, createMcpControlTool, createMessage, createObservabilityPlugin, createPlanPlugin, createPromptsPlugin, createSecurityPlugin, createSecuritySlashCommand, createSkillsPlugin, createSyncPlugin, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, dispatchAgent, downloadGitHubTarball, emptyGoal, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatGoal, formatPlan, formatPlanTemplates, formatTodosList, getAgentDefinition, getContextWindowMode, getPlanTemplate, getTemplate, githubServer, goalFilePath, googleMapsServer, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeContinueToNextIterationTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, matchAny, matchGlob, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, parseContinueDirective, parseSkillRef, pendingBtwCount, projectHash, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resolveContextWindowPolicy, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTodosCheckpoint, scoreAgents, securitySlashCommand, sentinelServer, setBtwNote, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, summarizeUsage, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
|
|
28594
|
+
export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, ALL_SYNC_CATEGORIES, AUDIT_LOG_AGENT, Agent, AgentError, AnnotationsStore, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutoPhasePlanner, AutoPhaseRunner, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, CheckpointManager, CloudSync, CollaborationBus, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultPromptStore, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorStateCheckpoint, DoneConditionChecker, ERROR_CODES, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, FsError, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, MAX_JOURNAL_ENTRIES, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, PhaseGraphBuilder, PhaseOrchestrator, PhaseStore, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReplayLogStore, ReplayProviderRunner, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, ScopedEventBus, SddParallelRun, SddTaskDecomposer, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SessionRecovery, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolAuditLog, ToolError, ToolExecutor, ToolRegistry, WorktreeManager, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyRosterBudget, asBlocks, asText, assertSafePath, atomicWrite, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildBtwBlock, buildChildEnv, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, collabInjectMiddleware, collabPauseMiddleware, color, compileGlob, compileUserRegex, completePartialObject, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, consumeBtwNotes, context7Server, contextManagerTool, createAutoExecutor, createAutoPhaseFromTaskGraph, createContextManagerTool, createDefaultPipelines, createDelegateTool, createGitPlugin, createMcpControlTool, createMessage, createObservabilityPlugin, createPlanPlugin, createPromptsPlugin, createSecurityPlugin, createSecuritySlashCommand, createSkillsPlugin, createSyncPlugin, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, dispatchAgent, downloadGitHubTarball, emptyGoal, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatGoal, formatPlan, formatPlanTemplates, formatTodosList, getAgentDefinition, getContextWindowMode, getPlanTemplate, getTemplate, githubServer, goalFilePath, googleMapsServer, hashRequest, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeContinueToNextIterationTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, matchAny, matchGlob, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, parseContinueDirective, parseSkillRef, pendingBtwCount, projectHash, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resolveContextWindowPolicy, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, runProviderWithRetry, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTodosCheckpoint, scoreAgents, securitySlashCommand, sentinelServer, setBtwNote, setPlanItemStatus, slackServer, stableStringify, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, summarizeUsage, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
|
|
27174
28595
|
//# sourceMappingURL=index.js.map
|
|
27175
28596
|
//# sourceMappingURL=index.js.map
|