@wrongstack/core 0.9.7 → 0.9.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-DMVOX0cF.d.ts → agent-bridge-Dti3KXGk.d.ts} +1 -1
- package/dist/{agent-subagent-runner-DaF_EgRG.d.ts → agent-subagent-runner-U-rs7kk7.d.ts} +3 -4
- package/dist/compactor-D7X96RLZ.d.ts +41 -0
- package/dist/{config-SkMIDN9L.d.ts → config-CLXMDOSs.d.ts} +5 -1
- package/dist/{context-BRNbHmRM.d.ts → context-zkZeILpr.d.ts} +46 -0
- package/dist/coordination/index.d.ts +14 -13
- package/dist/coordination/index.js +1263 -123
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +22 -21
- package/dist/defaults/index.js +1477 -152
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-CiG9qUM_.d.ts → events-DH-9r-_C.d.ts} +42 -1
- package/dist/execution/index.d.ts +42 -30
- package/dist/execution/index.js +359 -112
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +7 -6
- package/dist/extension/index.js.map +1 -1
- package/dist/{index-CP8638Wm.d.ts → index-BIHJ4uII.d.ts} +12 -8
- package/dist/{index-Bsha5K4D.d.ts → index-CFO9QmJo.d.ts} +9 -8
- package/dist/index.d.ts +319 -37
- package/dist/index.js +2571 -324
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +36 -0
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +10 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-BouUWYW6.d.ts → mcp-servers-DkESgh0G.d.ts} +25 -3
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.js +1 -1
- package/dist/models/index.js.map +1 -1
- package/dist/{multi-agent-3ZnTB1aT.d.ts → multi-agent-DNp6lAzg.d.ts} +35 -23
- package/dist/{multi-agent-coordinator-DTXF2aAl.d.ts → multi-agent-coordinator-CAhsegPz.d.ts} +20 -2
- package/dist/{null-fleet-bus-Chrc_3Pp.d.ts → null-fleet-bus-Dnl19vmf.d.ts} +496 -15
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-TcJfc29Y.d.ts → path-resolver-CHiBL0DD.d.ts} +2 -2
- package/dist/{secret-scrubber-DttNiGYA.d.ts → permission-H35s9Evv.d.ts} +2 -7
- package/dist/{permission-policy-BpCGYBud.d.ts → permission-policy-CT-nRmTn.d.ts} +9 -2
- package/dist/{plan-templates-envSmNlZ.d.ts → plan-templates-Bs8iRwi6.d.ts} +28 -4
- package/dist/{provider-runner-BEpikbbN.d.ts → provider-runner-BZdDrWrS.d.ts} +3 -3
- package/dist/{retry-policy-BYkq0ugs.d.ts → retry-policy-J9N_PM40.d.ts} +1 -1
- package/dist/sdd/index.d.ts +10 -10
- package/dist/sdd/index.js +225 -68
- package/dist/sdd/index.js.map +1 -1
- package/dist/secret-scrubber-3MHDDAtm.d.ts +6 -0
- package/dist/{secret-scrubber-QSeI0ADi.d.ts → secret-scrubber-7rSC_emZ.d.ts} +1 -1
- package/dist/security/index.d.ts +5 -4
- package/dist/security/index.js +37 -6
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-pox8abg0.d.ts → selector-CFTh3Z6p.d.ts} +1 -1
- package/dist/{session-reader-CSWcb5Ga.d.ts → session-reader-C7JJlxOw.d.ts} +2 -2
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +391 -6
- package/dist/storage/index.js +760 -40
- package/dist/storage/index.js.map +1 -1
- package/dist/{system-prompt-Bs-Wliab.d.ts → system-prompt-CneIxVip.d.ts} +1 -1
- package/dist/{tool-executor-CsktM3h9.d.ts → tool-executor-flTuxsqO.d.ts} +9 -4
- package/dist/types/index.d.ts +16 -15
- package/dist/types/index.js +60 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +25 -2
- package/dist/utils/index.js +90 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/compactor-DVTKL7XD.d.ts +0 -23
package/dist/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) {
|
|
@@ -4124,14 +4136,105 @@ function deepEqual(a, b) {
|
|
|
4124
4136
|
return false;
|
|
4125
4137
|
}
|
|
4126
4138
|
|
|
4139
|
+
// src/utils/json-repair.ts
|
|
4140
|
+
function completePartialObject(s) {
|
|
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;
|
|
4156
|
+
continue;
|
|
4157
|
+
}
|
|
4158
|
+
if (ch === "\\") {
|
|
4159
|
+
escaped = true;
|
|
4160
|
+
continue;
|
|
4161
|
+
}
|
|
4162
|
+
if (ch === '"') {
|
|
4163
|
+
inString = false;
|
|
4164
|
+
prevSig = '"';
|
|
4165
|
+
stringBraceDepth = 0;
|
|
4166
|
+
continue;
|
|
4167
|
+
}
|
|
4168
|
+
if (ch === "{") stringBraceDepth++;
|
|
4169
|
+
else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
|
|
4170
|
+
continue;
|
|
4171
|
+
}
|
|
4172
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
|
|
4173
|
+
contentEnd = i + 1;
|
|
4174
|
+
if (ch === '"') {
|
|
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;
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
if (!sawKey && !inString) return s;
|
|
4190
|
+
let result = s.slice(0, contentEnd);
|
|
4191
|
+
if (inString) {
|
|
4192
|
+
if (escaped) {
|
|
4193
|
+
result = result.slice(0, -1);
|
|
4194
|
+
} else if (endsWithInvalidEscape(result)) {
|
|
4195
|
+
result = result.slice(0, -2);
|
|
4196
|
+
}
|
|
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;
|
|
4208
|
+
}
|
|
4209
|
+
return result;
|
|
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
|
+
}
|
|
4220
|
+
function tryParse(s) {
|
|
4221
|
+
try {
|
|
4222
|
+
return { ok: true, value: JSON.parse(s) };
|
|
4223
|
+
} catch {
|
|
4224
|
+
return { ok: false };
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
|
|
4127
4228
|
// src/storage/session-store.ts
|
|
4128
4229
|
init_atomic_write();
|
|
4129
4230
|
var DefaultSessionStore = class {
|
|
4130
4231
|
dir;
|
|
4131
4232
|
events;
|
|
4233
|
+
secretScrubber;
|
|
4132
4234
|
constructor(opts) {
|
|
4133
4235
|
this.dir = opts.dir;
|
|
4134
4236
|
this.events = opts.events;
|
|
4237
|
+
this.secretScrubber = opts.secretScrubber;
|
|
4135
4238
|
}
|
|
4136
4239
|
/** Join session ID to its absolute path within the store directory. */
|
|
4137
4240
|
sessionPath(id, ext) {
|
|
@@ -4156,7 +4259,11 @@ var DefaultSessionStore = class {
|
|
|
4156
4259
|
);
|
|
4157
4260
|
}
|
|
4158
4261
|
try {
|
|
4159
|
-
return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
|
|
4262
|
+
return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
|
|
4263
|
+
dir: shardDir,
|
|
4264
|
+
filePath: file,
|
|
4265
|
+
secretScrubber: this.secretScrubber
|
|
4266
|
+
});
|
|
4160
4267
|
} catch (err) {
|
|
4161
4268
|
await handle.close().catch(() => {
|
|
4162
4269
|
});
|
|
@@ -4186,7 +4293,7 @@ var DefaultSessionStore = class {
|
|
|
4186
4293
|
provider: data.metadata.provider
|
|
4187
4294
|
},
|
|
4188
4295
|
this.events,
|
|
4189
|
-
{ resumed: true, dir: this.dir, filePath: file }
|
|
4296
|
+
{ resumed: true, dir: this.dir, filePath: file, secretScrubber: this.secretScrubber }
|
|
4190
4297
|
);
|
|
4191
4298
|
return { writer, data };
|
|
4192
4299
|
} catch (err) {
|
|
@@ -4250,8 +4357,8 @@ var DefaultSessionStore = class {
|
|
|
4250
4357
|
return JSON.parse(raw);
|
|
4251
4358
|
} catch {
|
|
4252
4359
|
const full = this.sessionPath(id, ".jsonl");
|
|
4253
|
-
const
|
|
4254
|
-
const summary = await this.summarize(id,
|
|
4360
|
+
const stat8 = await fsp2.stat(full);
|
|
4361
|
+
const summary = await this.summarize(id, stat8.mtime.toISOString());
|
|
4255
4362
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
4256
4363
|
console.warn(
|
|
4257
4364
|
`[session-store] Failed to write manifest for "${id}":`,
|
|
@@ -4392,6 +4499,7 @@ var FileSessionWriter = class {
|
|
|
4392
4499
|
this.resumed = opts.resumed ?? false;
|
|
4393
4500
|
this.manifestFile = opts.dir ? path6.join(opts.dir, `${id}.summary.json`) : "";
|
|
4394
4501
|
this.filePath = opts.filePath ?? "";
|
|
4502
|
+
this.secretScrubber = opts.secretScrubber;
|
|
4395
4503
|
this.summary = {
|
|
4396
4504
|
id,
|
|
4397
4505
|
title: "(empty session)",
|
|
@@ -4420,7 +4528,29 @@ var FileSessionWriter = class {
|
|
|
4420
4528
|
resumed;
|
|
4421
4529
|
appendFailCount = 0;
|
|
4422
4530
|
lastAppendWarnAt = 0;
|
|
4423
|
-
|
|
4531
|
+
secretScrubber;
|
|
4532
|
+
/**
|
|
4533
|
+
* Scrub secrets out of conversation-turn events before they are observed
|
|
4534
|
+
* for the summary, written to the JSONL log, or surfaced on resume. Only
|
|
4535
|
+
* `user_input` / `llm_response` carry free-form user/model text; other event
|
|
4536
|
+
* types either have no secret-bearing content or are already scrubbed
|
|
4537
|
+
* upstream (tool results). Returns the event unchanged when no scrubber is
|
|
4538
|
+
* configured.
|
|
4539
|
+
*/
|
|
4540
|
+
scrubEvent(event) {
|
|
4541
|
+
const s = this.secretScrubber;
|
|
4542
|
+
if (!s) return event;
|
|
4543
|
+
if (event.type === "user_input") {
|
|
4544
|
+
return {
|
|
4545
|
+
...event,
|
|
4546
|
+
content: typeof event.content === "string" ? s.scrub(event.content) : s.scrubObject(event.content)
|
|
4547
|
+
};
|
|
4548
|
+
}
|
|
4549
|
+
if (event.type === "llm_response") {
|
|
4550
|
+
return { ...event, content: s.scrubObject(event.content) };
|
|
4551
|
+
}
|
|
4552
|
+
return event;
|
|
4553
|
+
}
|
|
4424
4554
|
pendingFileSnapshots = [];
|
|
4425
4555
|
/** Tracks open tool_use IDs during the current run to serialize on close for resume. */
|
|
4426
4556
|
openToolUses = /* @__PURE__ */ new Set();
|
|
@@ -4452,9 +4582,10 @@ var FileSessionWriter = class {
|
|
|
4452
4582
|
this.initDone = true;
|
|
4453
4583
|
await this.writeSessionStartLazy();
|
|
4454
4584
|
}
|
|
4455
|
-
this.
|
|
4585
|
+
const scrubbed = this.scrubEvent(event);
|
|
4586
|
+
this.observeForSummary(scrubbed);
|
|
4456
4587
|
try {
|
|
4457
|
-
await this.handle.appendFile(`${JSON.stringify(
|
|
4588
|
+
await this.handle.appendFile(`${JSON.stringify(scrubbed)}
|
|
4458
4589
|
`, "utf8");
|
|
4459
4590
|
} catch (err) {
|
|
4460
4591
|
this.appendFailCount++;
|
|
@@ -4510,7 +4641,6 @@ var FileSessionWriter = class {
|
|
|
4510
4641
|
await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);
|
|
4511
4642
|
this.pendingFileSnapshots = [];
|
|
4512
4643
|
}
|
|
4513
|
-
this.promptIndex = promptIndex + 1;
|
|
4514
4644
|
await this.append({
|
|
4515
4645
|
type: "checkpoint",
|
|
4516
4646
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4606,6 +4736,38 @@ var FileSessionWriter = class {
|
|
|
4606
4736
|
`;
|
|
4607
4737
|
await fsp2.writeFile(this.filePath, record, "utf8");
|
|
4608
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
|
+
}
|
|
4609
4771
|
};
|
|
4610
4772
|
function userInputTitle(content) {
|
|
4611
4773
|
const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
@@ -5077,6 +5239,7 @@ function isSecretField2(name) {
|
|
|
5077
5239
|
}
|
|
5078
5240
|
|
|
5079
5241
|
// src/storage/config-loader.ts
|
|
5242
|
+
init_atomic_write();
|
|
5080
5243
|
var BEHAVIOR_DEFAULTS = {
|
|
5081
5244
|
version: 1,
|
|
5082
5245
|
context: {
|
|
@@ -5237,6 +5400,40 @@ var DefaultConfigLoader = class {
|
|
|
5237
5400
|
}
|
|
5238
5401
|
return Object.freeze(cfg);
|
|
5239
5402
|
}
|
|
5403
|
+
/**
|
|
5404
|
+
* Persist a sync config to ~/.wrongstack/sync.json, with the token encrypted
|
|
5405
|
+
* by the vault (if provided). The file is isolated from the main config
|
|
5406
|
+
* hierarchy to prevent accidental commits.
|
|
5407
|
+
*/
|
|
5408
|
+
async persistSyncConfig(cfg) {
|
|
5409
|
+
let toWrite = { ...cfg };
|
|
5410
|
+
if (this.vault && toWrite.githubToken && !toWrite.githubToken.startsWith("enc:")) {
|
|
5411
|
+
toWrite = { ...toWrite, githubToken: this.vault.encrypt(toWrite.githubToken) };
|
|
5412
|
+
}
|
|
5413
|
+
await atomicWrite(this.paths.syncConfig, JSON.stringify(toWrite, null, 2), { mode: 384 });
|
|
5414
|
+
}
|
|
5415
|
+
/**
|
|
5416
|
+
* Read ~/.wrongstack/sync.json (encrypted GitHub token storage) and decrypt
|
|
5417
|
+
* the token if a vault is available. Returns null if the file doesn't exist.
|
|
5418
|
+
* This is separate from main config loading because sync.json is intentionally
|
|
5419
|
+
* isolated — it should never be part of project-local or env-driven config.
|
|
5420
|
+
*/
|
|
5421
|
+
async loadSyncConfig() {
|
|
5422
|
+
try {
|
|
5423
|
+
const raw = await fsp2.readFile(this.paths.syncConfig, "utf8");
|
|
5424
|
+
const parsed = safeParse(raw);
|
|
5425
|
+
if (!parsed.ok || !parsed.value) return null;
|
|
5426
|
+
if (this.vault) {
|
|
5427
|
+
const decrypted = decryptConfigSecrets2({ sync: parsed.value }, this.vault);
|
|
5428
|
+
return decrypted.sync ?? null;
|
|
5429
|
+
}
|
|
5430
|
+
return parsed.value;
|
|
5431
|
+
} catch (err) {
|
|
5432
|
+
if (err.code === "ENOENT") return null;
|
|
5433
|
+
console.warn("[config] Failed to load sync config:", err);
|
|
5434
|
+
return null;
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5240
5437
|
async readJson(file) {
|
|
5241
5438
|
let raw;
|
|
5242
5439
|
try {
|
|
@@ -6160,7 +6357,11 @@ var DefaultPermissionPolicy = class {
|
|
|
6160
6357
|
return { permission: "deny", source: "deny", reason: "session soft deny (user pressed no)" };
|
|
6161
6358
|
}
|
|
6162
6359
|
if (this.sessionAllowed.has(subjectKey)) {
|
|
6163
|
-
return {
|
|
6360
|
+
return {
|
|
6361
|
+
permission: "auto",
|
|
6362
|
+
source: "trust",
|
|
6363
|
+
reason: "session soft allow (user pressed yes)"
|
|
6364
|
+
};
|
|
6164
6365
|
}
|
|
6165
6366
|
if (entry?.deny && subject && matchAny(entry.deny, subject)) {
|
|
6166
6367
|
return { permission: "deny", source: "deny", reason: "matched deny pattern" };
|
|
@@ -6180,7 +6381,11 @@ var DefaultPermissionPolicy = class {
|
|
|
6180
6381
|
const decision = await this.promptDelegate(tool, input, subject ?? tool.name);
|
|
6181
6382
|
if (decision === "always") {
|
|
6182
6383
|
await this.trust({ tool: tool.name, pattern: subject ?? tool.name });
|
|
6183
|
-
return {
|
|
6384
|
+
return {
|
|
6385
|
+
permission: "auto",
|
|
6386
|
+
source: "user",
|
|
6387
|
+
reason: "destructive yolo always-allowed"
|
|
6388
|
+
};
|
|
6184
6389
|
}
|
|
6185
6390
|
if (decision === "deny") {
|
|
6186
6391
|
await this.deny({ tool: tool.name, pattern: subject ?? tool.name });
|
|
@@ -6188,13 +6393,22 @@ var DefaultPermissionPolicy = class {
|
|
|
6188
6393
|
}
|
|
6189
6394
|
return { permission: decision === "yes" ? "auto" : "deny", source: "user" };
|
|
6190
6395
|
}
|
|
6191
|
-
return {
|
|
6396
|
+
return {
|
|
6397
|
+
permission: "confirm",
|
|
6398
|
+
source: "yolo_destructive",
|
|
6399
|
+
riskTier: "destructive",
|
|
6400
|
+
reason: "destructive tool needs explicit approval even in yolo mode"
|
|
6401
|
+
};
|
|
6192
6402
|
}
|
|
6193
6403
|
return { permission: "auto", source: "yolo" };
|
|
6194
6404
|
}
|
|
6195
6405
|
if (tool.name === "write" && subject) {
|
|
6196
6406
|
if (ctx.hasRead(subject)) {
|
|
6197
|
-
return {
|
|
6407
|
+
return {
|
|
6408
|
+
permission: "auto",
|
|
6409
|
+
source: "context",
|
|
6410
|
+
reason: "file already read in this session"
|
|
6411
|
+
};
|
|
6198
6412
|
}
|
|
6199
6413
|
}
|
|
6200
6414
|
if (tool.permission === "auto" && !tool.mutating) {
|
|
@@ -6302,6 +6516,10 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
6302
6516
|
// arbitrary shell — use exec for constrained shell
|
|
6303
6517
|
"write",
|
|
6304
6518
|
// arbitrary file write
|
|
6519
|
+
"edit",
|
|
6520
|
+
// arbitrary in-project file modification (equivalent to write)
|
|
6521
|
+
"replace",
|
|
6522
|
+
// arbitrary multi-file find/replace (equivalent to write)
|
|
6305
6523
|
"scaffold",
|
|
6306
6524
|
// arbitrary file generation outside project root
|
|
6307
6525
|
"patch",
|
|
@@ -6311,12 +6529,22 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
6311
6529
|
"exec"
|
|
6312
6530
|
// restricted shell but with arbitrary command args
|
|
6313
6531
|
]);
|
|
6532
|
+
/**
|
|
6533
|
+
* Tools from MCP servers (`mcp__<server>__<tool>`) are external code of
|
|
6534
|
+
* unknown capability — they may wrap a shell or filesystem. They are
|
|
6535
|
+
* fail-closed here: not auto-approved for subagents by default, so the
|
|
6536
|
+
* leader must allow them explicitly per-spawn.
|
|
6537
|
+
*/
|
|
6538
|
+
static isMcpTool(name) {
|
|
6539
|
+
return name.startsWith("mcp__");
|
|
6540
|
+
}
|
|
6314
6541
|
async evaluate(tool) {
|
|
6315
|
-
|
|
6542
|
+
const blocked = _AutoApprovePermissionPolicy.DENY.has(tool.name) || _AutoApprovePermissionPolicy.isMcpTool(tool.name);
|
|
6543
|
+
if (tool.permission === "deny" || blocked) {
|
|
6316
6544
|
return {
|
|
6317
6545
|
permission: "deny",
|
|
6318
6546
|
source: "subagent_guard",
|
|
6319
|
-
reason:
|
|
6547
|
+
reason: blocked ? `tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow it explicitly` : "tool default deny"
|
|
6320
6548
|
};
|
|
6321
6549
|
}
|
|
6322
6550
|
return { permission: "auto", source: "yolo" };
|
|
@@ -6561,7 +6789,12 @@ function safeJsonOrRaw(s) {
|
|
|
6561
6789
|
try {
|
|
6562
6790
|
return JSON.parse(s);
|
|
6563
6791
|
} catch {
|
|
6564
|
-
|
|
6792
|
+
const repaired = completePartialObject(s);
|
|
6793
|
+
try {
|
|
6794
|
+
return JSON.parse(repaired);
|
|
6795
|
+
} catch {
|
|
6796
|
+
return { _raw: repaired };
|
|
6797
|
+
}
|
|
6565
6798
|
}
|
|
6566
6799
|
}
|
|
6567
6800
|
function handleToolUseStop(state, ev) {
|
|
@@ -6790,8 +7023,9 @@ var IntelligentCompactor = class {
|
|
|
6790
7023
|
}
|
|
6791
7024
|
async compact(ctx, opts = {}) {
|
|
6792
7025
|
const beforeTokens = this.estimateTokens(ctx.messages);
|
|
7026
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
6793
7027
|
const reductions = [];
|
|
6794
|
-
const load =
|
|
7028
|
+
const load = beforeFull / this.maxContext;
|
|
6795
7029
|
const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
|
|
6796
7030
|
const saved1 = this.eliseOldToolResults(ctx);
|
|
6797
7031
|
if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
|
|
@@ -6805,9 +7039,12 @@ var IntelligentCompactor = class {
|
|
|
6805
7039
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
6806
7040
|
if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
|
|
6807
7041
|
const afterTokens = this.estimateTokens(ctx.messages);
|
|
7042
|
+
const afterFull = this.estimateFullRequest(ctx);
|
|
6808
7043
|
return {
|
|
6809
7044
|
before: beforeTokens,
|
|
6810
7045
|
after: afterTokens,
|
|
7046
|
+
fullRequestTokensBefore: beforeFull,
|
|
7047
|
+
fullRequestTokensAfter: afterFull,
|
|
6811
7048
|
reductions,
|
|
6812
7049
|
repaired: repaired.report.changed ? {
|
|
6813
7050
|
removedToolUses: repaired.report.removedToolUses,
|
|
@@ -6816,6 +7053,14 @@ var IntelligentCompactor = class {
|
|
|
6816
7053
|
} : void 0
|
|
6817
7054
|
};
|
|
6818
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
|
+
}
|
|
6819
7064
|
async summarizeAncientTurns(ctx) {
|
|
6820
7065
|
const messages = ctx.messages;
|
|
6821
7066
|
const cutoff = Math.max(0, messages.length - this.preserveK * 2);
|
|
@@ -6828,29 +7073,26 @@ var IntelligentCompactor = class {
|
|
|
6828
7073
|
try {
|
|
6829
7074
|
summaryText = await this.callSummarizer(toSummarize, ctx);
|
|
6830
7075
|
} catch {
|
|
6831
|
-
const
|
|
6832
|
-
const filePaths = /* @__PURE__ */ new Set();
|
|
6833
|
-
let userTurns = 0;
|
|
6834
|
-
let assistantTurns = 0;
|
|
7076
|
+
const preservedMessages = [];
|
|
6835
7077
|
for (const m of toSummarize) {
|
|
6836
|
-
if (m.role === "
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
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);
|
|
6844
7092
|
}
|
|
6845
|
-
const text = typeof m.content === "string" ? m.content : "";
|
|
6846
|
-
const matches = text.matchAll(/(?:[\w.,\-/@]+\/)*[\w.,\-/@]+\.\w+/g);
|
|
6847
|
-
for (const m_ of matches) filePaths.add(m_[0]);
|
|
6848
7093
|
}
|
|
6849
|
-
|
|
6850
|
-
if (
|
|
6851
|
-
if (filePaths.size > 0) parts.push(`files: ${[...filePaths].slice(0, 10).join(", ")}`);
|
|
6852
|
-
summaryText = parts.join(" | ");
|
|
6853
|
-
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)`;
|
|
6854
7096
|
}
|
|
6855
7097
|
const summaryMsg = {
|
|
6856
7098
|
role: "system",
|
|
@@ -7096,7 +7338,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
7096
7338
|
const res = await this.provider.complete(req, { signal: ac.signal });
|
|
7097
7339
|
const textBlocks = res.content.filter(isTextBlock);
|
|
7098
7340
|
raw = textBlocks.map((b) => b.text).join("\n").trim();
|
|
7099
|
-
} catch (
|
|
7341
|
+
} catch (_err) {
|
|
7100
7342
|
return this.fallbackSelect(messages, effectiveBudget);
|
|
7101
7343
|
}
|
|
7102
7344
|
return this.parseSelectorOutput(raw, messages.length);
|
|
@@ -7188,15 +7430,24 @@ var SelectiveCompactor = class {
|
|
|
7188
7430
|
}
|
|
7189
7431
|
async compact(ctx, opts = {}) {
|
|
7190
7432
|
const beforeTokens = this.estimateTokens(ctx.messages);
|
|
7433
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
7191
7434
|
const reductions = [];
|
|
7192
|
-
const load =
|
|
7435
|
+
const load = beforeFull / this.maxContext;
|
|
7193
7436
|
const shouldCompact = load >= this.warnThreshold || opts.aggressive;
|
|
7194
7437
|
if (!shouldCompact) {
|
|
7195
7438
|
const saved = this.eliseOldToolResults(ctx);
|
|
7196
7439
|
if (saved > 0) reductions.push({ phase: "elision", saved });
|
|
7197
7440
|
const repair2 = this.repairProtocolAdjacency(ctx);
|
|
7198
7441
|
const afterTokens2 = this.estimateTokens(ctx.messages);
|
|
7199
|
-
|
|
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
|
+
};
|
|
7200
7451
|
}
|
|
7201
7452
|
const savedElision = this.eliseOldToolResults(ctx);
|
|
7202
7453
|
if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
|
|
@@ -7208,7 +7459,23 @@ var SelectiveCompactor = class {
|
|
|
7208
7459
|
}
|
|
7209
7460
|
const repair = this.repairProtocolAdjacency(ctx);
|
|
7210
7461
|
const afterTokens = this.estimateTokens(ctx.messages);
|
|
7211
|
-
|
|
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;
|
|
7212
7479
|
}
|
|
7213
7480
|
repairProtocolAdjacency(ctx) {
|
|
7214
7481
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
@@ -7398,7 +7665,8 @@ var LEVEL_RANK2 = { warn: 0, soft: 1, hard: 2 };
|
|
|
7398
7665
|
var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
7399
7666
|
name = "AutoCompaction";
|
|
7400
7667
|
compactor;
|
|
7401
|
-
|
|
7668
|
+
/** Deprecated. Kept for backward compat with tests that pass simpleEstimator. */
|
|
7669
|
+
_estimator;
|
|
7402
7670
|
warnThreshold;
|
|
7403
7671
|
softThreshold;
|
|
7404
7672
|
hardThreshold;
|
|
@@ -7408,19 +7676,6 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
7408
7676
|
events;
|
|
7409
7677
|
failureMode;
|
|
7410
7678
|
policyProvider;
|
|
7411
|
-
/**
|
|
7412
|
-
* Calibration factor applied to the estimator output. The estimator is now
|
|
7413
|
-
* expected to already include messages + system prompt + tool definitions
|
|
7414
|
-
* (see `estimateRequestTokens.total`), so no overhead boost is needed. Kept
|
|
7415
|
-
* as a constant so a future calibration against real provider tokenization
|
|
7416
|
-
* (BPE vs the chars/3.5 rough estimator) can dial this without touching the
|
|
7417
|
-
* threshold-check math.
|
|
7418
|
-
*
|
|
7419
|
-
* Historical note: was 1.3 when the estimator only counted messages — that
|
|
7420
|
-
* double-counted system+tools once the estimator was upgraded, firing
|
|
7421
|
-
* compaction ~30% earlier than intended (e.g. real 56% load showed as 73%).
|
|
7422
|
-
*/
|
|
7423
|
-
static OVERHEAD_FACTOR = 1;
|
|
7424
7679
|
/**
|
|
7425
7680
|
* Once a compaction attempt reduces nothing (preserveK protects everything,
|
|
7426
7681
|
* no oversized tool_results remain to elide), retrying on every iteration
|
|
@@ -7433,8 +7688,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
7433
7688
|
lastNoopAttempt = null;
|
|
7434
7689
|
/**
|
|
7435
7690
|
* @param compactor Compactor to use for compaction.
|
|
7436
|
-
* @param maxContext
|
|
7437
|
-
* @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).
|
|
7438
7696
|
* @param thresholds Threshold fractions (0-1) of maxContext.
|
|
7439
7697
|
* @param opts Optional behavior. By default, failures at the
|
|
7440
7698
|
* hard threshold throw AGENT_CONTEXT_OVERFLOW so
|
|
@@ -7442,11 +7700,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
7442
7700
|
* provider context overflow. Warn/soft failures
|
|
7443
7701
|
* still emit compaction.failed and continue.
|
|
7444
7702
|
*/
|
|
7445
|
-
constructor(compactor, maxContext,
|
|
7703
|
+
constructor(compactor, maxContext, _estimator, thresholds, optsOrAggressiveOn = {}, events) {
|
|
7446
7704
|
const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
|
|
7447
7705
|
this.compactor = compactor;
|
|
7448
7706
|
this._maxContext = maxContext;
|
|
7449
|
-
this.
|
|
7707
|
+
this._estimator = _estimator;
|
|
7450
7708
|
this.warnThreshold = thresholds.warn;
|
|
7451
7709
|
this.softThreshold = thresholds.soft;
|
|
7452
7710
|
this.hardThreshold = thresholds.hard;
|
|
@@ -7462,8 +7720,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
7462
7720
|
}
|
|
7463
7721
|
handler() {
|
|
7464
7722
|
return async (ctx, next) => {
|
|
7465
|
-
const
|
|
7466
|
-
const tokens = Math.ceil(rawTokens * _AutoCompactionMiddleware.OVERHEAD_FACTOR);
|
|
7723
|
+
const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
|
|
7467
7724
|
const load = tokens / this._maxContext;
|
|
7468
7725
|
const policy = this.policyProvider?.(ctx);
|
|
7469
7726
|
const thresholds = policy?.thresholds ?? {
|
|
@@ -7498,7 +7755,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
7498
7755
|
return delta < _AutoCompactionMiddleware.NOOP_RETRY_DELTA_TOKENS;
|
|
7499
7756
|
}
|
|
7500
7757
|
recordAttempt(level, tokens, report) {
|
|
7501
|
-
const reduced = report.before > report.after;
|
|
7758
|
+
const reduced = (report.fullRequestTokensBefore ?? report.before) > (report.fullRequestTokensAfter ?? report.after);
|
|
7502
7759
|
const repaired = !!report.repaired;
|
|
7503
7760
|
if (reduced || repaired) {
|
|
7504
7761
|
this.lastNoopAttempt = null;
|
|
@@ -8431,17 +8688,7 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8431
8688
|
startTime = null;
|
|
8432
8689
|
_onThreshold;
|
|
8433
8690
|
/**
|
|
8434
|
-
*
|
|
8435
|
-
* in flight. While a kind is here, further `checkLimit` calls for the
|
|
8436
|
-
* same kind are no-ops — without this dedup, every `recordIteration`
|
|
8437
|
-
* after the limit is reached spawns a fresh decision Promise (until
|
|
8438
|
-
* the first one lands and patches limits), flooding the FleetBus
|
|
8439
|
-
* with redundant threshold events. Cleared in `negotiateExtension`'s
|
|
8440
|
-
* `finally`.
|
|
8441
|
-
*/
|
|
8442
|
-
pendingExtensions = /* @__PURE__ */ new Set();
|
|
8443
|
-
/**
|
|
8444
|
-
* Hard cap on how long `negotiateExtension` waits for the coordinator to
|
|
8691
|
+
* Hard cap on how long `_negotiateExtension` waits for the coordinator to
|
|
8445
8692
|
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
8446
8693
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
8447
8694
|
* leaves the budget over-limit and never enforces anything.
|
|
@@ -8503,38 +8750,83 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8503
8750
|
* - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
|
|
8504
8751
|
* - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
|
|
8505
8752
|
*/
|
|
8506
|
-
|
|
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 [];
|
|
8507
8783
|
if (!this._onThreshold) {
|
|
8508
|
-
|
|
8784
|
+
const first2 = exceeded[0];
|
|
8785
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
8509
8786
|
}
|
|
8510
8787
|
if (this._mode === "sync") {
|
|
8511
|
-
|
|
8788
|
+
const first2 = exceeded[0];
|
|
8789
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
8512
8790
|
}
|
|
8513
8791
|
const bus = this._events;
|
|
8514
8792
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
8515
|
-
|
|
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);
|
|
8516
8800
|
}
|
|
8517
|
-
|
|
8518
|
-
this.
|
|
8519
|
-
|
|
8520
|
-
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);
|
|
8521
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();
|
|
8522
8813
|
/**
|
|
8523
8814
|
* Drive the threshold handler to a decision. Resolves with `'stop'`
|
|
8524
8815
|
* (signal the runner to abort) or `{ extend: ... }` (limits already
|
|
8525
|
-
* patched in-place; the runner should not abort).
|
|
8526
|
-
*
|
|
8816
|
+
* patched in-place; the runner should not abort). Clears the
|
|
8817
|
+
* per-kind slot in `_pendingNegotiations` in `finally`.
|
|
8527
8818
|
*
|
|
8528
8819
|
* The 'continue' return from a sync handler is treated as
|
|
8529
8820
|
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
8530
8821
|
* a fresh signal.
|
|
8531
8822
|
*/
|
|
8532
|
-
async
|
|
8823
|
+
async _negotiateExtension(kind, exceeded) {
|
|
8533
8824
|
try {
|
|
8825
|
+
const first = exceeded[0];
|
|
8534
8826
|
const result = this._onThreshold({
|
|
8535
|
-
kind,
|
|
8536
|
-
used,
|
|
8537
|
-
limit,
|
|
8827
|
+
kind: first.kind,
|
|
8828
|
+
used: first.used,
|
|
8829
|
+
limit: first.limit,
|
|
8538
8830
|
requestDecision: () => {
|
|
8539
8831
|
const bus = this._events;
|
|
8540
8832
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
@@ -8551,20 +8843,22 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8551
8843
|
() => respond("stop"),
|
|
8552
8844
|
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
8553
8845
|
);
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
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
|
+
}
|
|
8568
8862
|
});
|
|
8569
8863
|
}
|
|
8570
8864
|
});
|
|
@@ -8590,47 +8884,39 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8590
8884
|
}
|
|
8591
8885
|
return decision;
|
|
8592
8886
|
} finally {
|
|
8593
|
-
this.
|
|
8887
|
+
this._pendingNegotiations.delete(kind);
|
|
8594
8888
|
}
|
|
8595
8889
|
}
|
|
8596
8890
|
recordIteration() {
|
|
8597
8891
|
this.iterations++;
|
|
8598
|
-
|
|
8599
|
-
void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
|
|
8600
|
-
}
|
|
8892
|
+
void this.checkLimits();
|
|
8601
8893
|
}
|
|
8602
8894
|
recordToolCall() {
|
|
8603
8895
|
this.toolCalls++;
|
|
8604
|
-
|
|
8605
|
-
void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
|
|
8606
|
-
}
|
|
8896
|
+
void this.checkLimits();
|
|
8607
8897
|
}
|
|
8608
8898
|
recordUsage(usage, costUsd = 0) {
|
|
8609
8899
|
this.tokenInput += usage.input;
|
|
8610
8900
|
this.tokenOutput += usage.output;
|
|
8611
8901
|
this.costUsd += costUsd;
|
|
8612
|
-
|
|
8613
|
-
if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
|
|
8614
|
-
void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
|
|
8615
|
-
}
|
|
8616
|
-
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
8617
|
-
void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
|
|
8618
|
-
}
|
|
8902
|
+
void this.checkLimits();
|
|
8619
8903
|
}
|
|
8620
8904
|
/**
|
|
8621
|
-
* Wall-clock budget check. Unlike other limits, timeout
|
|
8622
|
-
*
|
|
8623
|
-
*
|
|
8624
|
-
*
|
|
8625
|
-
*
|
|
8626
|
-
*
|
|
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)
|
|
8627
8914
|
*/
|
|
8628
8915
|
checkTimeout() {
|
|
8629
8916
|
if (this.startTime === null || this.limits.timeoutMs === void 0) return;
|
|
8630
8917
|
const elapsed = Date.now() - this.startTime;
|
|
8631
|
-
if (elapsed
|
|
8632
|
-
|
|
8633
|
-
}
|
|
8918
|
+
if (elapsed <= this.limits.timeoutMs) return;
|
|
8919
|
+
void this.checkLimits(elapsed);
|
|
8634
8920
|
}
|
|
8635
8921
|
/** Returns true if a timeout has occurred without throwing. Useful for races. */
|
|
8636
8922
|
isTimedOut() {
|
|
@@ -11283,11 +11569,46 @@ Working rules:
|
|
|
11283
11569
|
- When in doubt, flag as medium rather than ignoring potential issues`
|
|
11284
11570
|
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
11285
11571
|
};
|
|
11572
|
+
var CRITIC_AGENT = {
|
|
11573
|
+
id: "critic",
|
|
11574
|
+
name: "Critic",
|
|
11575
|
+
role: "critic",
|
|
11576
|
+
prompt: `You are the Critic agent. Your job is to evaluate code quality,
|
|
11577
|
+
architectural decisions, and proposed changes against project conventions,
|
|
11578
|
+
engineering standards, and known quality gates. You do not write code \u2014
|
|
11579
|
+
you judge it.
|
|
11580
|
+
|
|
11581
|
+
Scope:
|
|
11582
|
+
- Evaluate bug severity and fix quality from Bug Hunter reports
|
|
11583
|
+
- Score refactoring plans from Refactor Planner (risk, completeness, trade-offs)
|
|
11584
|
+
- Flag gaps in test coverage, error handling, and edge case coverage
|
|
11585
|
+
- Assess whether a proposed change aligns with existing project patterns
|
|
11586
|
+
- Detect over-engineering or under-engineering relative to the problem scope
|
|
11587
|
+
|
|
11588
|
+
Input format you accept:
|
|
11589
|
+
{ "task": "evaluate | score | review", "subject": "bug_report | refactor_plan | diff", "focus": "correctness | maintainability | risk | all" }
|
|
11590
|
+
|
|
11591
|
+
Output: Markdown critic report:
|
|
11592
|
+
- ## Overall Score (0-10 with rationale)
|
|
11593
|
+
- ## Strengths (what's solid)
|
|
11594
|
+
- ## Weaknesses (what needs work)
|
|
11595
|
+
- ## Specific Concerns (with file:line when applicable)
|
|
11596
|
+
- ## Verdict: **Approve / Needs Revision / Reject**
|
|
11597
|
+
|
|
11598
|
+
Working rules:
|
|
11599
|
+
- Be specific \u2014 "looks fine" is not a review. Cite concrete evidence.
|
|
11600
|
+
- When scoring, explain the delta from a perfect score.
|
|
11601
|
+
- If you have no basis to evaluate a concern, say so rather than speculating.
|
|
11602
|
+
- Prioritise correctness over style; correctness issues block approval.
|
|
11603
|
+
- Score thresholds: \u22657 = Approve, 4-6 = Needs Revision, <4 = Reject`
|
|
11604
|
+
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
11605
|
+
};
|
|
11286
11606
|
var FLEET_ROSTER = {
|
|
11287
11607
|
"audit-log": AUDIT_LOG_AGENT,
|
|
11288
11608
|
"bug-hunter": BUG_HUNTER_AGENT,
|
|
11289
11609
|
"refactor-planner": REFACTOR_PLANNER_AGENT,
|
|
11290
11610
|
"security-scanner": SECURITY_SCANNER_AGENT,
|
|
11611
|
+
"critic": CRITIC_AGENT,
|
|
11291
11612
|
...Object.fromEntries(
|
|
11292
11613
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
|
|
11293
11614
|
)
|
|
@@ -11297,6 +11618,7 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
11297
11618
|
"bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
11298
11619
|
"refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
|
|
11299
11620
|
"security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
11621
|
+
"critic": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
|
|
11300
11622
|
...Object.fromEntries(
|
|
11301
11623
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
|
|
11302
11624
|
)
|
|
@@ -11377,10 +11699,111 @@ FLEET_ROSTER_BUDGETS["gemini-cli"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterati
|
|
|
11377
11699
|
FLEET_ROSTER_BUDGETS["copilot"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
|
|
11378
11700
|
FLEET_ROSTER_BUDGETS["openhands"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
|
|
11379
11701
|
FLEET_ROSTER_BUDGETS["goose"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
|
|
11380
|
-
|
|
11702
|
+
var FLEET_ROSTER_WITHACP = {
|
|
11381
11703
|
...FLEET_ROSTER,
|
|
11382
11704
|
...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
|
|
11383
|
-
}
|
|
11705
|
+
};
|
|
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
|
+
}
|
|
11384
11807
|
|
|
11385
11808
|
// src/coordination/multi-agent-coordinator.ts
|
|
11386
11809
|
var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
@@ -11389,6 +11812,15 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11389
11812
|
runner;
|
|
11390
11813
|
fleetBus;
|
|
11391
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();
|
|
11392
11824
|
pendingTasks = [];
|
|
11393
11825
|
completedResults = [];
|
|
11394
11826
|
totalIterations = 0;
|
|
@@ -11437,7 +11869,26 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11437
11869
|
this.config.maxConcurrent = Math.floor(n);
|
|
11438
11870
|
this.tryDispatchNext();
|
|
11439
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
|
+
}
|
|
11440
11890
|
async spawn(subagent) {
|
|
11891
|
+
subagent = this.withNickname(subagent);
|
|
11441
11892
|
const id = subagent.id || randomUUID();
|
|
11442
11893
|
if (this.subagents.has(id)) {
|
|
11443
11894
|
throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
|
|
@@ -12031,12 +12482,12 @@ var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
|
|
|
12031
12482
|
var ParallelEternalEngine = class {
|
|
12032
12483
|
constructor(opts) {
|
|
12033
12484
|
this.opts = opts;
|
|
12034
|
-
this.goalPath = goalFilePath(opts.projectRoot);
|
|
12485
|
+
this.goalPath = opts.goalPath ?? goalFilePath(opts.projectRoot);
|
|
12035
12486
|
this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
|
|
12036
12487
|
this.timeoutMs = opts.iterationTimeoutMs ?? 3e5;
|
|
12037
12488
|
this.dispatchEnabled = opts.dispatch !== false;
|
|
12038
12489
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
12039
|
-
this.agentFactory = opts.subagentFactory ?? (async (
|
|
12490
|
+
this.agentFactory = opts.subagentFactory ?? (async (_config) => ({
|
|
12040
12491
|
agent: this.opts.agent,
|
|
12041
12492
|
events: this.opts.agent.events
|
|
12042
12493
|
}));
|
|
@@ -12130,7 +12581,6 @@ var ParallelEternalEngine = class {
|
|
|
12130
12581
|
}
|
|
12131
12582
|
const tasks = await this.decomposeGoal(goal);
|
|
12132
12583
|
if (!tasks || tasks.length === 0) {
|
|
12133
|
-
await sleep2(5e3);
|
|
12134
12584
|
return false;
|
|
12135
12585
|
}
|
|
12136
12586
|
const fanOut = await this.fanOut(goal, tasks);
|
|
@@ -12151,6 +12601,7 @@ var ParallelEternalEngine = class {
|
|
|
12151
12601
|
});
|
|
12152
12602
|
if (fanOut.goalComplete) {
|
|
12153
12603
|
this.stopRequested = true;
|
|
12604
|
+
this.state = "stopped";
|
|
12154
12605
|
return true;
|
|
12155
12606
|
}
|
|
12156
12607
|
await this.maybeCompact();
|
|
@@ -12331,7 +12782,11 @@ ${lastFew}` : "No prior iterations.",
|
|
|
12331
12782
|
} finally {
|
|
12332
12783
|
clearTimeout(timer);
|
|
12333
12784
|
}
|
|
12334
|
-
} catch {
|
|
12785
|
+
} catch (err) {
|
|
12786
|
+
this.opts.onError?.(
|
|
12787
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
12788
|
+
this.consecutiveFailures
|
|
12789
|
+
);
|
|
12335
12790
|
return [];
|
|
12336
12791
|
}
|
|
12337
12792
|
}
|
|
@@ -12548,7 +13003,11 @@ Working rules:
|
|
|
12548
13003
|
thrashing, terminate it rather than letting cost climb silently.
|
|
12549
13004
|
6. Never claim a subagent's work as your own without verifying it. If a
|
|
12550
13005
|
result looks wrong, ask_subagent for clarification before passing it
|
|
12551
|
-
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.`;
|
|
12552
13011
|
var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
|
|
12553
13012
|
a specific slice of a larger plan \u2014 do that slice well and report back.
|
|
12554
13013
|
|
|
@@ -12693,14 +13152,12 @@ var FleetBus = class {
|
|
|
12693
13152
|
};
|
|
12694
13153
|
var FleetUsageAggregator = class {
|
|
12695
13154
|
constructor(bus, priceLookup, metaLookup) {
|
|
12696
|
-
this.bus = bus;
|
|
12697
13155
|
this.priceLookup = priceLookup;
|
|
12698
13156
|
this.metaLookup = metaLookup;
|
|
12699
13157
|
bus.filter("provider.response", (e) => this.onProviderResponse(e));
|
|
12700
13158
|
bus.filter("tool.executed", (e) => this.onToolExecuted(e));
|
|
12701
13159
|
bus.filter("iteration.started", (e) => this.onIterationStarted(e));
|
|
12702
13160
|
}
|
|
12703
|
-
bus;
|
|
12704
13161
|
priceLookup;
|
|
12705
13162
|
metaLookup;
|
|
12706
13163
|
perSubagent = /* @__PURE__ */ new Map();
|
|
@@ -12938,7 +13395,7 @@ function makeRollUpTool(director) {
|
|
|
12938
13395
|
function makeTerminateTool(director) {
|
|
12939
13396
|
return {
|
|
12940
13397
|
name: "terminate_subagent",
|
|
12941
|
-
description:
|
|
13398
|
+
description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
|
|
12942
13399
|
permission: "auto",
|
|
12943
13400
|
mutating: true,
|
|
12944
13401
|
inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
|
|
@@ -12949,6 +13406,19 @@ function makeTerminateTool(director) {
|
|
|
12949
13406
|
}
|
|
12950
13407
|
};
|
|
12951
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
|
+
}
|
|
12952
13422
|
function makeFleetStatusTool(director) {
|
|
12953
13423
|
return {
|
|
12954
13424
|
name: "fleet_status",
|
|
@@ -12992,7 +13462,6 @@ function makeFleetSessionTool(director) {
|
|
|
12992
13462
|
type: "object",
|
|
12993
13463
|
properties: {
|
|
12994
13464
|
subagentId: { type: "string", description: "Subagent id to read the transcript of." },
|
|
12995
|
-
/** Number of trailing lines to return (last N JSONL lines). Default: all. */
|
|
12996
13465
|
tail: { type: "number", description: "Number of trailing JSONL lines to return. Omit for the full transcript." }
|
|
12997
13466
|
},
|
|
12998
13467
|
required: ["subagentId"]
|
|
@@ -13028,8 +13497,6 @@ function makeFleetHealthTool(director) {
|
|
|
13028
13497
|
id: s.id,
|
|
13029
13498
|
status: s.status,
|
|
13030
13499
|
lastEventAt: usage?.lastEventAt,
|
|
13031
|
-
// Budget pressure: fraction of each limit consumed if we have it.
|
|
13032
|
-
// BudgetWarning events carry used/limit ratios; surface them here.
|
|
13033
13500
|
budgetPressure: {
|
|
13034
13501
|
iterations: usage?.iterations,
|
|
13035
13502
|
toolCalls: usage?.toolCalls,
|
|
@@ -13041,20 +13508,616 @@ function makeFleetHealthTool(director) {
|
|
|
13041
13508
|
}
|
|
13042
13509
|
};
|
|
13043
13510
|
}
|
|
13044
|
-
|
|
13045
|
-
|
|
13046
|
-
|
|
13047
|
-
|
|
13048
|
-
|
|
13049
|
-
|
|
13050
|
-
|
|
13051
|
-
|
|
13052
|
-
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13056
|
-
|
|
13057
|
-
|
|
13511
|
+
function makeCollabDebugTool(director) {
|
|
13512
|
+
return {
|
|
13513
|
+
name: "collab_debug",
|
|
13514
|
+
description: "Start a collaborative debugging session: BugHunter, RefactorPlanner, and Critic run in parallel on the same target files. BugHunter finds bugs and emits bug.found events. RefactorPlanner listens for bug.found and emits refactor.plan events. Critic evaluates both and emits critic.evaluation events. Returns a structured report with overall verdict (approve / needs_revision / reject).",
|
|
13515
|
+
permission: "auto",
|
|
13516
|
+
mutating: false,
|
|
13517
|
+
inputSchema: {
|
|
13518
|
+
type: "object",
|
|
13519
|
+
properties: {
|
|
13520
|
+
targetPaths: {
|
|
13521
|
+
type: "array",
|
|
13522
|
+
items: { type: "string" },
|
|
13523
|
+
description: "File paths / glob patterns to scan for bugs."
|
|
13524
|
+
},
|
|
13525
|
+
timeoutMs: {
|
|
13526
|
+
type: "number",
|
|
13527
|
+
description: "Timeout in ms. Default: 600000 (10 minutes)."
|
|
13528
|
+
}
|
|
13529
|
+
},
|
|
13530
|
+
required: ["targetPaths"]
|
|
13531
|
+
},
|
|
13532
|
+
async execute(input) {
|
|
13533
|
+
const i = input;
|
|
13534
|
+
if (!i.targetPaths?.length) {
|
|
13535
|
+
return { error: "collab_debug: targetPaths is required and must be non-empty." };
|
|
13536
|
+
}
|
|
13537
|
+
const options = {
|
|
13538
|
+
targetPaths: i.targetPaths,
|
|
13539
|
+
timeoutMs: i.timeoutMs
|
|
13540
|
+
};
|
|
13541
|
+
try {
|
|
13542
|
+
const report = await director.spawnCollab(options);
|
|
13543
|
+
return {
|
|
13544
|
+
sessionId: report.sessionId,
|
|
13545
|
+
overallVerdict: report.overallVerdict,
|
|
13546
|
+
bugCount: report.bugs.length,
|
|
13547
|
+
planCount: report.refactorPlans.length,
|
|
13548
|
+
evaluationCount: report.evaluations.length,
|
|
13549
|
+
summary: report.summary,
|
|
13550
|
+
bugs: report.bugs,
|
|
13551
|
+
refactorPlans: report.refactorPlans,
|
|
13552
|
+
evaluations: report.evaluations
|
|
13553
|
+
};
|
|
13554
|
+
} catch (err) {
|
|
13555
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
13556
|
+
return { error: "collab_debug failed: " + msg };
|
|
13557
|
+
}
|
|
13558
|
+
}
|
|
13559
|
+
};
|
|
13560
|
+
}
|
|
13561
|
+
function makeFleetEmitTool(director) {
|
|
13562
|
+
return {
|
|
13563
|
+
name: "fleet_emit",
|
|
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.",
|
|
13565
|
+
permission: "auto",
|
|
13566
|
+
mutating: false,
|
|
13567
|
+
inputSchema: {
|
|
13568
|
+
type: "object",
|
|
13569
|
+
properties: {
|
|
13570
|
+
type: {
|
|
13571
|
+
type: "string",
|
|
13572
|
+
description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
|
|
13573
|
+
},
|
|
13574
|
+
payload: {
|
|
13575
|
+
type: "object",
|
|
13576
|
+
description: "Event payload. Structure depends on event type. Use null if no payload."
|
|
13577
|
+
}
|
|
13578
|
+
},
|
|
13579
|
+
required: ["type"]
|
|
13580
|
+
},
|
|
13581
|
+
async execute(input) {
|
|
13582
|
+
const i = input;
|
|
13583
|
+
director.fleet.emit({
|
|
13584
|
+
subagentId: director.id,
|
|
13585
|
+
ts: Date.now(),
|
|
13586
|
+
type: i.type,
|
|
13587
|
+
payload: i.payload ?? {}
|
|
13588
|
+
});
|
|
13589
|
+
return { ok: true, event: i.type };
|
|
13590
|
+
}
|
|
13591
|
+
};
|
|
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
|
+
}
|
|
13606
|
+
var CollabSession = class extends EventEmitter {
|
|
13607
|
+
sessionId;
|
|
13608
|
+
options;
|
|
13609
|
+
snapshot;
|
|
13610
|
+
director;
|
|
13611
|
+
fleetBus;
|
|
13612
|
+
subagentIds = /* @__PURE__ */ new Map();
|
|
13613
|
+
// role → subagentId
|
|
13614
|
+
bugs = /* @__PURE__ */ new Map();
|
|
13615
|
+
plans = /* @__PURE__ */ new Map();
|
|
13616
|
+
evaluations = /* @__PURE__ */ new Map();
|
|
13617
|
+
disposers = new Array();
|
|
13618
|
+
settled = false;
|
|
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;
|
|
13628
|
+
constructor(director, fleetBus, options) {
|
|
13629
|
+
super();
|
|
13630
|
+
this.sessionId = randomUUID();
|
|
13631
|
+
this.options = options;
|
|
13632
|
+
this.director = director;
|
|
13633
|
+
this.fleetBus = fleetBus;
|
|
13634
|
+
this.timeoutMs = options.timeoutMs ?? 10 * 60 * 1e3;
|
|
13635
|
+
if (options.prebuiltSnapshot) {
|
|
13636
|
+
this.snapshot = options.prebuiltSnapshot;
|
|
13637
|
+
} else {
|
|
13638
|
+
this.snapshot = {
|
|
13639
|
+
id: this.sessionId,
|
|
13640
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13641
|
+
files: []
|
|
13642
|
+
};
|
|
13643
|
+
}
|
|
13644
|
+
}
|
|
13645
|
+
get id() {
|
|
13646
|
+
return this.sessionId;
|
|
13647
|
+
}
|
|
13648
|
+
getSessionAlerts() {
|
|
13649
|
+
return [...this.alerts];
|
|
13650
|
+
}
|
|
13651
|
+
isCancelled() {
|
|
13652
|
+
return this.cancelled;
|
|
13653
|
+
}
|
|
13654
|
+
/**
|
|
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.
|
|
13658
|
+
*/
|
|
13659
|
+
getSubagentIds() {
|
|
13660
|
+
return new Map(this.subagentIds);
|
|
13661
|
+
}
|
|
13662
|
+
async buildSnapshot() {
|
|
13663
|
+
if (this.snapshot.files.length > 0) return this.snapshot;
|
|
13664
|
+
for (const filePath of this.options.targetPaths) {
|
|
13665
|
+
try {
|
|
13666
|
+
const content = await fsp2.readFile(filePath, "utf8");
|
|
13667
|
+
const ext = filePath.split(".").pop() ?? "";
|
|
13668
|
+
const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
|
|
13669
|
+
this.snapshot.files.push({ path: filePath, content, language });
|
|
13670
|
+
} catch {
|
|
13671
|
+
this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
|
|
13672
|
+
}
|
|
13673
|
+
}
|
|
13674
|
+
return this.snapshot;
|
|
13675
|
+
}
|
|
13676
|
+
/**
|
|
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).
|
|
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
|
+
}
|
|
13701
|
+
async start() {
|
|
13702
|
+
if (this.settled) throw new Error("session already settled");
|
|
13703
|
+
this.settled = true;
|
|
13704
|
+
await this.buildSnapshot();
|
|
13705
|
+
this.wireFleetBus();
|
|
13706
|
+
const [bugHunterId, refactorPlannerId, criticId] = await Promise.all([
|
|
13707
|
+
this.spawnAgent("bug-hunter", this.buildBugHunterTask()),
|
|
13708
|
+
this.spawnAgent("refactor-planner", this.buildRefactorPlannerTask()),
|
|
13709
|
+
this.spawnAgent("critic", this.buildCriticTask())
|
|
13710
|
+
]);
|
|
13711
|
+
this.subagentIds.set("bug-hunter", bugHunterId);
|
|
13712
|
+
this.subagentIds.set("refactor-planner", refactorPlannerId);
|
|
13713
|
+
this.subagentIds.set("critic", criticId);
|
|
13714
|
+
const timeout = new Promise((_, reject) => {
|
|
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);
|
|
13719
|
+
});
|
|
13720
|
+
let results;
|
|
13721
|
+
try {
|
|
13722
|
+
results = await Promise.race([
|
|
13723
|
+
Promise.all([
|
|
13724
|
+
this.director.awaitTasks([bugHunterId]),
|
|
13725
|
+
this.director.awaitTasks([refactorPlannerId]),
|
|
13726
|
+
this.director.awaitTasks([criticId])
|
|
13727
|
+
]),
|
|
13728
|
+
timeout
|
|
13729
|
+
]);
|
|
13730
|
+
} catch (err) {
|
|
13731
|
+
if (this._timeoutTimer) {
|
|
13732
|
+
clearTimeout(this._timeoutTimer);
|
|
13733
|
+
this._timeoutTimer = void 0;
|
|
13734
|
+
}
|
|
13735
|
+
this.cleanup();
|
|
13736
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
13737
|
+
this.emit("session.error", error);
|
|
13738
|
+
throw error;
|
|
13739
|
+
}
|
|
13740
|
+
for (const result of results.flat()) {
|
|
13741
|
+
await this.parseAndEmit(result);
|
|
13742
|
+
}
|
|
13743
|
+
const report = this.assembleReport();
|
|
13744
|
+
this.cleanup();
|
|
13745
|
+
this.emit("session.done", report);
|
|
13746
|
+
return report;
|
|
13747
|
+
}
|
|
13748
|
+
async parseAndEmit(result) {
|
|
13749
|
+
if (result.status !== "success" || result.result == null) return;
|
|
13750
|
+
const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
|
|
13751
|
+
for (const obj of this.extractJsonObjects(text)) {
|
|
13752
|
+
const type = "finding" in obj ? "bug.found" : "plan" in obj ? "refactor.plan" : "evaluation" in obj ? "critic.evaluation" : null;
|
|
13753
|
+
if (!type) continue;
|
|
13754
|
+
this.fleetBus.emit({
|
|
13755
|
+
subagentId: result.subagentId,
|
|
13756
|
+
taskId: result.taskId,
|
|
13757
|
+
ts: Date.now(),
|
|
13758
|
+
type,
|
|
13759
|
+
payload: obj
|
|
13760
|
+
});
|
|
13761
|
+
}
|
|
13762
|
+
}
|
|
13763
|
+
extractJsonObjects(text) {
|
|
13764
|
+
const objects = [];
|
|
13765
|
+
let depth = 0;
|
|
13766
|
+
let start = -1;
|
|
13767
|
+
let inString = false;
|
|
13768
|
+
let escaped = false;
|
|
13769
|
+
for (let i = 0; i < text.length; i++) {
|
|
13770
|
+
const ch = text[i];
|
|
13771
|
+
if (inString) {
|
|
13772
|
+
if (escaped) escaped = false;
|
|
13773
|
+
else if (ch === "\\") escaped = true;
|
|
13774
|
+
else if (ch === '"') inString = false;
|
|
13775
|
+
continue;
|
|
13776
|
+
}
|
|
13777
|
+
if (ch === '"') {
|
|
13778
|
+
inString = true;
|
|
13779
|
+
} else if (ch === "{") {
|
|
13780
|
+
if (depth === 0) start = i;
|
|
13781
|
+
depth++;
|
|
13782
|
+
} else if (ch === "}" && depth > 0) {
|
|
13783
|
+
depth--;
|
|
13784
|
+
if (depth === 0 && start >= 0) {
|
|
13785
|
+
const candidate = text.slice(start, i + 1);
|
|
13786
|
+
try {
|
|
13787
|
+
const parsed = JSON.parse(candidate);
|
|
13788
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
13789
|
+
objects.push(parsed);
|
|
13790
|
+
}
|
|
13791
|
+
} catch {
|
|
13792
|
+
}
|
|
13793
|
+
start = -1;
|
|
13794
|
+
}
|
|
13795
|
+
}
|
|
13796
|
+
}
|
|
13797
|
+
return objects;
|
|
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
|
+
}
|
|
13810
|
+
async spawnAgent(role, taskBrief) {
|
|
13811
|
+
const budget = this.budgetForRole(role);
|
|
13812
|
+
const cfg = {
|
|
13813
|
+
id: `${role}-${this.sessionId}`,
|
|
13814
|
+
name: role,
|
|
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
|
|
13820
|
+
};
|
|
13821
|
+
const subagentId = await this.director.spawn(cfg);
|
|
13822
|
+
await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
|
|
13823
|
+
return subagentId;
|
|
13824
|
+
}
|
|
13825
|
+
buildBugHunterTask() {
|
|
13826
|
+
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
13827
|
+
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
13828
|
+
${f.content}`).join("\n\n");
|
|
13829
|
+
return `You are BugHunter. Scan the following files for bugs and code smells.
|
|
13830
|
+
|
|
13831
|
+
Target files:
|
|
13832
|
+
${fileContents}
|
|
13833
|
+
|
|
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.`;
|
|
13841
|
+
}
|
|
13842
|
+
buildRefactorPlannerTask() {
|
|
13843
|
+
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
13844
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
13845
|
+
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
13846
|
+
${f.content}`).join("\n\n");
|
|
13847
|
+
return `You are RefactorPlanner. Plan refactorings for the following files.
|
|
13848
|
+
|
|
13849
|
+
Target files:
|
|
13850
|
+
${fileContents}
|
|
13851
|
+
|
|
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.`;
|
|
13861
|
+
}
|
|
13862
|
+
buildCriticTask() {
|
|
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`;
|
|
13866
|
+
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
13867
|
+
${f.content}`).join("\n\n");
|
|
13868
|
+
return `You are Critic. Evaluate bug findings and refactor plans.
|
|
13869
|
+
|
|
13870
|
+
Target files:
|
|
13871
|
+
${fileContents}
|
|
13872
|
+
|
|
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.`;
|
|
13883
|
+
}
|
|
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);
|
|
13992
|
+
const d1 = this.fleetBus.filter("bug.found", (e) => {
|
|
13993
|
+
const payload = e.payload;
|
|
13994
|
+
if (payload?.finding) {
|
|
13995
|
+
this.bugs.set(payload.finding.id, payload.finding);
|
|
13996
|
+
this.emit("bug.found", payload);
|
|
13997
|
+
}
|
|
13998
|
+
});
|
|
13999
|
+
this.disposers.push(d1);
|
|
14000
|
+
const d2 = this.fleetBus.filter("refactor.plan", (e) => {
|
|
14001
|
+
const payload = e.payload;
|
|
14002
|
+
if (payload?.plan) {
|
|
14003
|
+
this.plans.set(payload.plan.id, payload.plan);
|
|
14004
|
+
this.emit("refactor.plan", payload);
|
|
14005
|
+
}
|
|
14006
|
+
});
|
|
14007
|
+
this.disposers.push(d2);
|
|
14008
|
+
const d3 = this.fleetBus.filter("critic.evaluation", (e) => {
|
|
14009
|
+
const payload = e.payload;
|
|
14010
|
+
if (payload?.evaluation) {
|
|
14011
|
+
this.evaluations.set(payload.evaluation.id, payload.evaluation);
|
|
14012
|
+
this.emit("critic.evaluation", payload);
|
|
14013
|
+
}
|
|
14014
|
+
});
|
|
14015
|
+
this.disposers.push(d3);
|
|
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
|
+
}
|
|
14024
|
+
assembleReport() {
|
|
14025
|
+
const bugList = Array.from(this.bugs.values());
|
|
14026
|
+
const planList = Array.from(this.plans.values());
|
|
14027
|
+
const evalList = Array.from(this.evaluations.values());
|
|
14028
|
+
let disposition = "completed";
|
|
14029
|
+
if (this.cancelled) disposition = "cancelled";
|
|
14030
|
+
const verdictOrder = {
|
|
14031
|
+
approve: 0,
|
|
14032
|
+
needs_revision: 1,
|
|
14033
|
+
reject: 2
|
|
14034
|
+
};
|
|
14035
|
+
const overallVerdict = evalList.reduce(
|
|
14036
|
+
(worst, eval_) => {
|
|
14037
|
+
const w = verdictOrder[worst];
|
|
14038
|
+
const c = verdictOrder[eval_.verdict];
|
|
14039
|
+
return c > w ? eval_.verdict : worst;
|
|
14040
|
+
},
|
|
14041
|
+
"approve"
|
|
14042
|
+
);
|
|
14043
|
+
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
|
|
14044
|
+
return {
|
|
14045
|
+
sessionId: this.sessionId,
|
|
14046
|
+
startedAt: this.snapshot.createdAt,
|
|
14047
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14048
|
+
targetPaths: this.options.targetPaths,
|
|
14049
|
+
disposition,
|
|
14050
|
+
bugs: bugList,
|
|
14051
|
+
refactorPlans: planList,
|
|
14052
|
+
evaluations: evalList,
|
|
14053
|
+
alerts: [...this.alerts],
|
|
14054
|
+
overallVerdict,
|
|
14055
|
+
summary
|
|
14056
|
+
};
|
|
14057
|
+
}
|
|
14058
|
+
buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
|
|
14059
|
+
const lines = [
|
|
14060
|
+
`## Collaborative Debugging Report \u2014 ${this.sessionId}`,
|
|
14061
|
+
"",
|
|
14062
|
+
`**Target:** ${this.options.targetPaths.join(", ")}`,
|
|
14063
|
+
`**Disposition:** ${disposition.toUpperCase()}`,
|
|
14064
|
+
`**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
|
|
14065
|
+
""
|
|
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
|
+
}
|
|
14074
|
+
if (bugs.length > 0) {
|
|
14075
|
+
lines.push("### Bugs Found", "");
|
|
14076
|
+
for (const b of bugs) {
|
|
14077
|
+
lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
|
|
14078
|
+
}
|
|
14079
|
+
lines.push("");
|
|
14080
|
+
}
|
|
14081
|
+
if (plans.length > 0) {
|
|
14082
|
+
lines.push("### Refactor Plans", "");
|
|
14083
|
+
for (const p of plans) {
|
|
14084
|
+
lines.push(`- **Phase plan** (risk: ${p.riskScore}, ~${p.estimatedChangeCount} changes)`);
|
|
14085
|
+
for (const phase of p.phases) {
|
|
14086
|
+
lines.push(` - Phase ${phase.number}: ${phase.title} [${phase.risk}]`);
|
|
14087
|
+
}
|
|
14088
|
+
}
|
|
14089
|
+
lines.push("");
|
|
14090
|
+
}
|
|
14091
|
+
if (evals.length > 0) {
|
|
14092
|
+
lines.push("### Critic Evaluations", "");
|
|
14093
|
+
for (const e of evals) {
|
|
14094
|
+
lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
|
|
14095
|
+
for (const c of e.concerns) {
|
|
14096
|
+
if (c.severity === "blocking") lines.push(` - ${c.description}`);
|
|
14097
|
+
}
|
|
14098
|
+
}
|
|
14099
|
+
lines.push("");
|
|
14100
|
+
}
|
|
14101
|
+
return lines.join("\n");
|
|
14102
|
+
}
|
|
14103
|
+
cleanup() {
|
|
14104
|
+
for (const dispose of this.disposers) dispose();
|
|
14105
|
+
this.disposers.length = 0;
|
|
14106
|
+
}
|
|
14107
|
+
};
|
|
14108
|
+
|
|
14109
|
+
// src/coordination/director.ts
|
|
14110
|
+
var FleetSpawnBudgetError = class extends Error {
|
|
14111
|
+
kind;
|
|
14112
|
+
limit;
|
|
14113
|
+
observed;
|
|
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);
|
|
14117
|
+
this.name = "FleetSpawnBudgetError";
|
|
14118
|
+
this.kind = kind;
|
|
14119
|
+
this.limit = limit;
|
|
14120
|
+
this.observed = observed;
|
|
13058
14121
|
}
|
|
13059
14122
|
};
|
|
13060
14123
|
var FleetCostCapError = class extends Error {
|
|
@@ -13071,6 +14134,20 @@ var FleetCostCapError = class extends Error {
|
|
|
13071
14134
|
this.observed = observed;
|
|
13072
14135
|
}
|
|
13073
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
|
+
};
|
|
13074
14151
|
var Director = class {
|
|
13075
14152
|
/** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
|
|
13076
14153
|
get coordinatorId() {
|
|
@@ -13087,6 +14164,25 @@ var Director = class {
|
|
|
13087
14164
|
* injected; otherwise own FleetUsageAggregator.
|
|
13088
14165
|
*/
|
|
13089
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
|
+
}
|
|
13090
14186
|
/**
|
|
13091
14187
|
* Optional fleet-level policy container. When provided the Director
|
|
13092
14188
|
* delegates spawn budgeting, manifest entries, and checkpointing to it
|
|
@@ -13119,6 +14215,8 @@ var Director = class {
|
|
|
13119
14215
|
subagentBridges = /* @__PURE__ */ new Map();
|
|
13120
14216
|
/** Tracks per-spawn config + assigned task ids for manifest writing. */
|
|
13121
14217
|
manifestEntries = /* @__PURE__ */ new Map();
|
|
14218
|
+
/** Tracks assigned nicknames so the same name is never reused in one fleet. */
|
|
14219
|
+
_usedNicknames = /* @__PURE__ */ new Set();
|
|
13122
14220
|
manifestPath;
|
|
13123
14221
|
roster;
|
|
13124
14222
|
directorPreamble;
|
|
@@ -13170,6 +14268,23 @@ var Director = class {
|
|
|
13170
14268
|
taskCompletedListener = null;
|
|
13171
14269
|
/** Optional LLM classifier for smart dispatch. Passed from options. */
|
|
13172
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();
|
|
13173
14288
|
constructor(opts) {
|
|
13174
14289
|
this.id = opts.config.coordinatorId || randomUUID();
|
|
13175
14290
|
this.manifestPath = opts.manifestPath;
|
|
@@ -13185,6 +14300,8 @@ var Director = class {
|
|
|
13185
14300
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
13186
14301
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
13187
14302
|
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
|
|
14303
|
+
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
14304
|
+
this.maxContext = opts.maxContext ?? 128e3;
|
|
13188
14305
|
this.sessionsRoot = opts.sessionsRoot;
|
|
13189
14306
|
this.directorRunId = opts.directorRunId ?? this.id;
|
|
13190
14307
|
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
|
|
@@ -13212,7 +14329,7 @@ var Director = class {
|
|
|
13212
14329
|
this.fleet = new FleetBus();
|
|
13213
14330
|
this.usage = new FleetUsageAggregator(
|
|
13214
14331
|
this.fleet,
|
|
13215
|
-
(
|
|
14332
|
+
(_id, provider, model) => {
|
|
13216
14333
|
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
13217
14334
|
return void 0;
|
|
13218
14335
|
},
|
|
@@ -13274,6 +14391,9 @@ var Director = class {
|
|
|
13274
14391
|
});
|
|
13275
14392
|
this.fleet.filter("budget.threshold_reached", (e) => {
|
|
13276
14393
|
const payload = e.payload;
|
|
14394
|
+
if (e.subagentId.startsWith("bug-hunter-") || e.subagentId.startsWith("refactor-planner-") || e.subagentId.startsWith("critic-")) {
|
|
14395
|
+
return;
|
|
14396
|
+
}
|
|
13277
14397
|
if (payload.kind === "timeout") {
|
|
13278
14398
|
const progress = progressBySubagent.get(e.subagentId) ?? 0;
|
|
13279
14399
|
const lastProgress = lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
@@ -13352,6 +14472,107 @@ var Director = class {
|
|
|
13352
14472
|
extensionsFor(subagentId) {
|
|
13353
14473
|
return this.extendTotals.get(subagentId) ?? 0;
|
|
13354
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
|
+
}
|
|
13355
14576
|
/** Best-effort session-writer append. Swallows failures — the director
|
|
13356
14577
|
* must not break a fleet run because the session JSONL handle closed. */
|
|
13357
14578
|
async appendSessionEvent(event) {
|
|
@@ -13390,12 +14611,21 @@ var Director = class {
|
|
|
13390
14611
|
* it the `cost` column in `usage.snapshot()` stays at 0.
|
|
13391
14612
|
*/
|
|
13392
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
|
+
}
|
|
13393
14622
|
if (this.fleetManager) {
|
|
13394
14623
|
const rejection = this.fleetManager.canSpawn(config);
|
|
13395
14624
|
if (rejection) {
|
|
13396
14625
|
if (rejection.kind === "max_spawn_depth") throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
|
|
13397
14626
|
if (rejection.kind === "max_spawns") throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
|
|
13398
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);
|
|
13399
14629
|
}
|
|
13400
14630
|
} else {
|
|
13401
14631
|
if (this.spawnDepth >= this.maxSpawnDepth) {
|
|
@@ -13410,11 +14640,29 @@ var Director = class {
|
|
|
13410
14640
|
throw new FleetCostCapError(this.maxFleetCostUsd, totalCost);
|
|
13411
14641
|
}
|
|
13412
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
|
+
}
|
|
13413
14649
|
}
|
|
13414
14650
|
let result;
|
|
14651
|
+
const needsNickname = config.name === config.role || !config.name || config.name === "subagent" || config.name === "adhoc";
|
|
14652
|
+
if (needsNickname) {
|
|
14653
|
+
const role = config.role ?? "subagent";
|
|
14654
|
+
if (this.fleetManager) {
|
|
14655
|
+
this.fleetManager.assignNicknameAndRecord("", config, priceLookup);
|
|
14656
|
+
} else {
|
|
14657
|
+
config.name = assignNickname(role, this._usedNicknames);
|
|
14658
|
+
this._usedNicknames.add(config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-"));
|
|
14659
|
+
}
|
|
14660
|
+
}
|
|
13415
14661
|
result = await this.coordinator.spawn(config);
|
|
13416
14662
|
if (this.fleetManager) {
|
|
13417
|
-
|
|
14663
|
+
if (!needsNickname) {
|
|
14664
|
+
this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
|
|
14665
|
+
}
|
|
13418
14666
|
} else {
|
|
13419
14667
|
this.spawnCount += 1;
|
|
13420
14668
|
this.subagentMeta.set(result.subagentId, {
|
|
@@ -13625,6 +14873,24 @@ var Director = class {
|
|
|
13625
14873
|
*/
|
|
13626
14874
|
async assign(task) {
|
|
13627
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
|
+
}
|
|
13628
14894
|
if (task.subagentId) {
|
|
13629
14895
|
if (this.fleetManager) {
|
|
13630
14896
|
this.fleetManager.addTaskToSubagent(task.subagentId, taskWithId.id);
|
|
@@ -13847,10 +15113,14 @@ var Director = class {
|
|
|
13847
15113
|
makeAskTool(this),
|
|
13848
15114
|
makeRollUpTool(this),
|
|
13849
15115
|
makeTerminateTool(this),
|
|
15116
|
+
makeTerminateAllTool(this),
|
|
13850
15117
|
makeFleetStatusTool(this),
|
|
13851
15118
|
makeFleetUsageTool(this),
|
|
13852
15119
|
makeFleetSessionTool(this),
|
|
13853
|
-
makeFleetHealthTool(this)
|
|
15120
|
+
makeFleetHealthTool(this),
|
|
15121
|
+
makeCollabDebugTool(this),
|
|
15122
|
+
makeFleetEmitTool(this),
|
|
15123
|
+
makeWorkCompleteTool(this)
|
|
13854
15124
|
];
|
|
13855
15125
|
return t2;
|
|
13856
15126
|
}
|
|
@@ -13862,6 +15132,25 @@ var Director = class {
|
|
|
13862
15132
|
async acquireCheckpointLock() {
|
|
13863
15133
|
return this.stateCheckpoint ? this.stateCheckpoint.acquireLock() : true;
|
|
13864
15134
|
}
|
|
15135
|
+
/**
|
|
15136
|
+
* Start a collaborative debugging session: BugHunter, RefactorPlanner,
|
|
15137
|
+
* and Critic run in parallel on the same target files, with findings
|
|
15138
|
+
* flowing through the FleetBus (bug.found → refactor.plan →
|
|
15139
|
+
* critic.evaluation). Returns a structured CollabDebugReport when all
|
|
15140
|
+
* three agents complete or the session times out.
|
|
15141
|
+
*/
|
|
15142
|
+
async spawnCollab(options) {
|
|
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));
|
|
15152
|
+
return session.start();
|
|
15153
|
+
}
|
|
13865
15154
|
/**
|
|
13866
15155
|
* Resume from a prior checkpoint snapshot (loaded via
|
|
13867
15156
|
* `loadDirectorState()`). Re-attach to the fleet mid-flight so
|
|
@@ -14349,8 +15638,8 @@ async function loadProjectModes(modesDir) {
|
|
|
14349
15638
|
for (const entry of entries) {
|
|
14350
15639
|
if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
|
|
14351
15640
|
const filePath = path6.join(modesDir, entry);
|
|
14352
|
-
const
|
|
14353
|
-
if (!
|
|
15641
|
+
const stat8 = await fsp2.stat(filePath);
|
|
15642
|
+
if (!stat8.isFile()) continue;
|
|
14354
15643
|
const content = await fsp2.readFile(filePath, "utf8");
|
|
14355
15644
|
const id = path6.basename(entry, path6.extname(entry));
|
|
14356
15645
|
modes.push({
|
|
@@ -15623,10 +16912,10 @@ var AISpecBuilder = class {
|
|
|
15623
16912
|
async saveSession() {
|
|
15624
16913
|
if (!this.sessionPath) return;
|
|
15625
16914
|
try {
|
|
15626
|
-
const
|
|
15627
|
-
const
|
|
16915
|
+
const fsp19 = await import('fs/promises');
|
|
16916
|
+
const path35 = await import('path');
|
|
15628
16917
|
const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
|
|
15629
|
-
await
|
|
16918
|
+
await fsp19.mkdir(path35.dirname(this.sessionPath), { recursive: true });
|
|
15630
16919
|
await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
|
|
15631
16920
|
} catch {
|
|
15632
16921
|
}
|
|
@@ -15635,8 +16924,8 @@ var AISpecBuilder = class {
|
|
|
15635
16924
|
async loadSession() {
|
|
15636
16925
|
if (!this.sessionPath) return false;
|
|
15637
16926
|
try {
|
|
15638
|
-
const
|
|
15639
|
-
const raw = await
|
|
16927
|
+
const fsp19 = await import('fs/promises');
|
|
16928
|
+
const raw = await fsp19.readFile(this.sessionPath, "utf8");
|
|
15640
16929
|
const loaded = JSON.parse(raw);
|
|
15641
16930
|
if (loaded?.id && loaded?.phase && loaded?.title) {
|
|
15642
16931
|
this.session = loaded;
|
|
@@ -15650,8 +16939,8 @@ var AISpecBuilder = class {
|
|
|
15650
16939
|
async deleteSession() {
|
|
15651
16940
|
if (!this.sessionPath) return;
|
|
15652
16941
|
try {
|
|
15653
|
-
const
|
|
15654
|
-
await
|
|
16942
|
+
const fsp19 = await import('fs/promises');
|
|
16943
|
+
await fsp19.unlink(this.sessionPath);
|
|
15655
16944
|
} catch {
|
|
15656
16945
|
}
|
|
15657
16946
|
}
|
|
@@ -16278,7 +17567,7 @@ function analyzeCriticalPath(graph) {
|
|
|
16278
17567
|
blockedTasks
|
|
16279
17568
|
};
|
|
16280
17569
|
}
|
|
16281
|
-
function getTransitiveBlocked(
|
|
17570
|
+
function getTransitiveBlocked(_graph, taskId, blocksMap) {
|
|
16282
17571
|
const visited = /* @__PURE__ */ new Set();
|
|
16283
17572
|
const queue = [taskId];
|
|
16284
17573
|
while (queue.length > 0) {
|
|
@@ -16294,7 +17583,7 @@ function getTransitiveBlocked(graph, taskId, blocksMap) {
|
|
|
16294
17583
|
}
|
|
16295
17584
|
return visited;
|
|
16296
17585
|
}
|
|
16297
|
-
function computeCriticalPath(graph,
|
|
17586
|
+
function computeCriticalPath(graph, _topoOrder, blockedByMap) {
|
|
16298
17587
|
const allIds = Array.from(graph.nodes.keys());
|
|
16299
17588
|
if (allIds.length === 0) return [];
|
|
16300
17589
|
const dist = /* @__PURE__ */ new Map();
|
|
@@ -16336,15 +17625,15 @@ function computeCriticalPath(graph, topoOrder, blockedByMap) {
|
|
|
16336
17625
|
maxId = id;
|
|
16337
17626
|
}
|
|
16338
17627
|
}
|
|
16339
|
-
const
|
|
17628
|
+
const path35 = [];
|
|
16340
17629
|
let current = maxId;
|
|
16341
17630
|
const visited = /* @__PURE__ */ new Set();
|
|
16342
17631
|
while (current && !visited.has(current)) {
|
|
16343
17632
|
visited.add(current);
|
|
16344
|
-
|
|
17633
|
+
path35.unshift(current);
|
|
16345
17634
|
current = prev.get(current) ?? null;
|
|
16346
17635
|
}
|
|
16347
|
-
return
|
|
17636
|
+
return path35;
|
|
16348
17637
|
}
|
|
16349
17638
|
function computeParallelGroups(graph, blockedByMap) {
|
|
16350
17639
|
const groups = [];
|
|
@@ -16712,13 +18001,11 @@ function createAutoExecutor(opts) {
|
|
|
16712
18001
|
|
|
16713
18002
|
// src/sdd/sdd-task-decomposer.ts
|
|
16714
18003
|
var SddTaskDecomposer = class {
|
|
16715
|
-
constructor(tracker,
|
|
18004
|
+
constructor(tracker, _graph, opts = {}) {
|
|
16716
18005
|
this.tracker = tracker;
|
|
16717
|
-
this.graph = graph;
|
|
16718
18006
|
this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
|
|
16719
18007
|
}
|
|
16720
18008
|
tracker;
|
|
16721
|
-
graph;
|
|
16722
18009
|
slots;
|
|
16723
18010
|
wave = 0;
|
|
16724
18011
|
// -------------------------------------------------------------------
|
|
@@ -16871,7 +18158,7 @@ var SddParallelRun = class {
|
|
|
16871
18158
|
this.coordinator.setRunner?.(runner);
|
|
16872
18159
|
}
|
|
16873
18160
|
defaultFactory() {
|
|
16874
|
-
return async (
|
|
18161
|
+
return async (_config) => ({
|
|
16875
18162
|
agent: this.opts.agent,
|
|
16876
18163
|
events: this.opts.agent.events
|
|
16877
18164
|
});
|
|
@@ -17324,7 +18611,7 @@ async function startMetricsServer(opts) {
|
|
|
17324
18611
|
const tls = opts.tls;
|
|
17325
18612
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
17326
18613
|
const host = opts.host ?? "127.0.0.1";
|
|
17327
|
-
const
|
|
18614
|
+
const path35 = opts.path ?? "/metrics";
|
|
17328
18615
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
17329
18616
|
const healthRegistry = opts.healthRegistry;
|
|
17330
18617
|
const listener = (req, res) => {
|
|
@@ -17334,7 +18621,7 @@ async function startMetricsServer(opts) {
|
|
|
17334
18621
|
return;
|
|
17335
18622
|
}
|
|
17336
18623
|
const url = req.url.split("?")[0];
|
|
17337
|
-
if (url ===
|
|
18624
|
+
if (url === path35) {
|
|
17338
18625
|
let body;
|
|
17339
18626
|
try {
|
|
17340
18627
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -17398,7 +18685,7 @@ async function startMetricsServer(opts) {
|
|
|
17398
18685
|
const protocol = useHttps ? "https" : "http";
|
|
17399
18686
|
return {
|
|
17400
18687
|
port: boundPort,
|
|
17401
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
18688
|
+
url: `${protocol}://${host}:${boundPort}${path35}`,
|
|
17402
18689
|
close: () => new Promise((resolve9, reject) => {
|
|
17403
18690
|
server.close((err) => err ? reject(err) : resolve9());
|
|
17404
18691
|
})
|
|
@@ -17685,6 +18972,12 @@ function roughEstimate(messages) {
|
|
|
17685
18972
|
return total;
|
|
17686
18973
|
}
|
|
17687
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;
|
|
17688
18981
|
return {
|
|
17689
18982
|
name: CONTEXT_MANAGER_TOOL_NAME,
|
|
17690
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.',
|
|
@@ -17775,11 +19068,41 @@ function createContextManagerTool(opts = {}) {
|
|
|
17775
19068
|
notes: "No compactor registered. Use /compact aggressive via slash command instead."
|
|
17776
19069
|
};
|
|
17777
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
|
+
}
|
|
17778
19094
|
const report = await opts.compactor.compact(ctx);
|
|
17779
19095
|
ctx.clearFileTracking();
|
|
17780
19096
|
const repair = applyMessages([...ctx.messages]);
|
|
17781
19097
|
const afterTokens = repair.changed ? roughEstimate(ctx.messages) : report.after;
|
|
17782
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
|
+
}
|
|
17783
19106
|
return {
|
|
17784
19107
|
action: "compact",
|
|
17785
19108
|
beforeTokens,
|
|
@@ -18245,10 +19568,10 @@ var SkillInstaller = class {
|
|
|
18245
19568
|
if (!resolved.startsWith(path6.resolve(destDir))) {
|
|
18246
19569
|
throw new Error(`Path traversal detected in skill file: ${file}`);
|
|
18247
19570
|
}
|
|
18248
|
-
const
|
|
18249
|
-
if (
|
|
19571
|
+
const stat8 = await fsp2.stat(srcPath);
|
|
19572
|
+
if (stat8.size > MAX_SKILL_FILE_SIZE) {
|
|
18250
19573
|
throw new Error(
|
|
18251
|
-
`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`
|
|
18252
19575
|
);
|
|
18253
19576
|
}
|
|
18254
19577
|
await fsp2.mkdir(path6.dirname(destPath), { recursive: true });
|
|
@@ -18287,7 +19610,7 @@ var SkillInstaller = class {
|
|
|
18287
19610
|
* - Name: update that specific skill
|
|
18288
19611
|
* - Name + newRef: update to a different ref
|
|
18289
19612
|
*/
|
|
18290
|
-
async update(nameOrRef,
|
|
19613
|
+
async update(nameOrRef, _opts) {
|
|
18291
19614
|
const result = { updated: [], unchanged: [], errors: [] };
|
|
18292
19615
|
const allEntries = await this.manifest.listAll();
|
|
18293
19616
|
let targets;
|
|
@@ -18366,131 +19689,747 @@ var SkillInstaller = class {
|
|
|
18366
19689
|
if (!entry) {
|
|
18367
19690
|
throw new Error(`Skill "${name}" is not installed${scope === "user" ? " (global)" : ""}.`);
|
|
18368
19691
|
}
|
|
18369
|
-
await this.removeSkillFiles(name, scope);
|
|
18370
|
-
await this.manifest.removeEntry(name, scope);
|
|
18371
|
-
this.invalidateLoaderCache();
|
|
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.
|
|
20211
|
+
*/
|
|
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 [];
|
|
20219
|
+
}
|
|
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));
|
|
20229
|
+
}
|
|
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`);
|
|
20236
|
+
}
|
|
20237
|
+
/**
|
|
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.
|
|
20242
|
+
*/
|
|
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 {
|
|
20254
|
+
}
|
|
20255
|
+
}
|
|
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
|
+
};
|
|
20265
|
+
}
|
|
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;
|
|
18372
20280
|
}
|
|
18373
20281
|
/**
|
|
18374
|
-
*
|
|
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.
|
|
18375
20286
|
*/
|
|
18376
|
-
async
|
|
18377
|
-
|
|
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;
|
|
18378
20326
|
}
|
|
18379
|
-
// ── Private helpers ──────────────────────────────────────────────
|
|
18380
20327
|
/**
|
|
18381
|
-
*
|
|
18382
|
-
* Returns
|
|
20328
|
+
* Walk the chain and verify every entry's hash and prevHash.
|
|
20329
|
+
* Returns a structured verdict — never throws.
|
|
18383
20330
|
*/
|
|
18384
|
-
async
|
|
18385
|
-
const
|
|
18386
|
-
|
|
18387
|
-
|
|
18388
|
-
|
|
18389
|
-
|
|
18390
|
-
|
|
18391
|
-
|
|
18392
|
-
|
|
18393
|
-
|
|
18394
|
-
|
|
18395
|
-
|
|
18396
|
-
|
|
18397
|
-
|
|
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
|
+
};
|
|
18398
20350
|
}
|
|
18399
|
-
|
|
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;
|
|
18400
20371
|
}
|
|
18401
|
-
|
|
20372
|
+
return { ok: true, entries: entries.length };
|
|
20373
|
+
}
|
|
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}`);
|
|
20382
|
+
}
|
|
20383
|
+
return path6.join(this.dir, `${sessionId}.audit.jsonl`);
|
|
20384
|
+
}
|
|
20385
|
+
async readAll(sessionId) {
|
|
20386
|
+
const fp = this.filePath(sessionId);
|
|
18402
20387
|
try {
|
|
18403
|
-
const
|
|
18404
|
-
|
|
18405
|
-
|
|
18406
|
-
|
|
20388
|
+
const raw = await fsp2.readFile(fp, "utf8");
|
|
20389
|
+
const out = [];
|
|
20390
|
+
for (const line of raw.split("\n")) {
|
|
20391
|
+
if (!line.trim()) continue;
|
|
18407
20392
|
try {
|
|
18408
|
-
|
|
18409
|
-
const meta = parseFrontmatter2(content);
|
|
18410
|
-
if (meta.name && meta.description) {
|
|
18411
|
-
const skillDir = path6.join(skillsDir, entry.name);
|
|
18412
|
-
const files = await collectFiles(skillDir, skillDir);
|
|
18413
|
-
results.push({
|
|
18414
|
-
name: meta.name,
|
|
18415
|
-
baseDir: skillDir,
|
|
18416
|
-
files
|
|
18417
|
-
});
|
|
18418
|
-
}
|
|
20393
|
+
out.push(JSON.parse(line));
|
|
18419
20394
|
} catch {
|
|
18420
20395
|
}
|
|
18421
20396
|
}
|
|
18422
|
-
|
|
20397
|
+
return out;
|
|
20398
|
+
} catch (err) {
|
|
20399
|
+
if (err.code === "ENOENT") return [];
|
|
20400
|
+
return [];
|
|
18423
20401
|
}
|
|
18424
|
-
return results;
|
|
18425
20402
|
}
|
|
18426
|
-
|
|
18427
|
-
|
|
18428
|
-
|
|
18429
|
-
|
|
18430
|
-
|
|
18431
|
-
const skillDir = path6.join(targetDir, name);
|
|
18432
|
-
await fsp2.rm(skillDir, { recursive: true, force: true });
|
|
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" : ""));
|
|
18433
20408
|
}
|
|
18434
|
-
|
|
18435
|
-
|
|
18436
|
-
|
|
18437
|
-
|
|
18438
|
-
|
|
18439
|
-
|
|
18440
|
-
|
|
18441
|
-
|
|
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;
|
|
18442
20417
|
}
|
|
18443
20418
|
};
|
|
18444
|
-
function
|
|
18445
|
-
|
|
18446
|
-
const end = raw.indexOf("\n---", 4);
|
|
18447
|
-
if (end === -1) return {};
|
|
18448
|
-
const block = raw.slice(4, end);
|
|
18449
|
-
const out = {};
|
|
18450
|
-
let key = null;
|
|
18451
|
-
let value = [];
|
|
18452
|
-
const flush = () => {
|
|
18453
|
-
if (key) {
|
|
18454
|
-
out[key] = value.join("\n").trim();
|
|
18455
|
-
}
|
|
18456
|
-
key = null;
|
|
18457
|
-
value = [];
|
|
18458
|
-
};
|
|
18459
|
-
for (const line of block.split("\n")) {
|
|
18460
|
-
const m = /^([a-zA-Z_]+):\s*(\|?)\s*(.*)$/.exec(line);
|
|
18461
|
-
if (m) {
|
|
18462
|
-
flush();
|
|
18463
|
-
key = m[1] ?? "";
|
|
18464
|
-
const pipe = m[2];
|
|
18465
|
-
const rest = m[3] ?? "";
|
|
18466
|
-
if (pipe === "|") {
|
|
18467
|
-
value = [];
|
|
18468
|
-
} else if (rest) {
|
|
18469
|
-
value = [rest];
|
|
18470
|
-
} else {
|
|
18471
|
-
value = [];
|
|
18472
|
-
}
|
|
18473
|
-
} else if (key) {
|
|
18474
|
-
value.push(line.replace(/^\s+/, ""));
|
|
18475
|
-
}
|
|
18476
|
-
}
|
|
18477
|
-
flush();
|
|
18478
|
-
return out;
|
|
20419
|
+
function stableStringify2(value) {
|
|
20420
|
+
return JSON.stringify(sortKeys2(value));
|
|
18479
20421
|
}
|
|
18480
|
-
|
|
18481
|
-
|
|
18482
|
-
|
|
18483
|
-
|
|
18484
|
-
const
|
|
18485
|
-
const
|
|
18486
|
-
|
|
18487
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
18488
|
-
results.push(...await collectFiles(fullPath, baseDir));
|
|
18489
|
-
} else {
|
|
18490
|
-
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]);
|
|
18491
20429
|
}
|
|
20430
|
+
return sorted;
|
|
18492
20431
|
}
|
|
18493
|
-
return
|
|
20432
|
+
return value;
|
|
18494
20433
|
}
|
|
18495
20434
|
|
|
18496
20435
|
// src/storage/session-rewinder.ts
|
|
@@ -18666,7 +20605,7 @@ var DefaultPromptStore = class {
|
|
|
18666
20605
|
}
|
|
18667
20606
|
async list() {
|
|
18668
20607
|
await ensureDir(this.dir);
|
|
18669
|
-
|
|
20608
|
+
const entries = [];
|
|
18670
20609
|
try {
|
|
18671
20610
|
const files = await fsp2.readdir(this.dir);
|
|
18672
20611
|
for (const file of files) {
|
|
@@ -18785,7 +20724,25 @@ var CloudSync = class {
|
|
|
18785
20724
|
baseTreeSha,
|
|
18786
20725
|
`Sync ${cfg.categories.join(", ")} \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
18787
20726
|
);
|
|
18788
|
-
|
|
20727
|
+
try {
|
|
20728
|
+
await this.updateRef(token, owner, repoName, branch, commitSha);
|
|
20729
|
+
} catch (err) {
|
|
20730
|
+
if (err instanceof Error && err.message.includes("422")) {
|
|
20731
|
+
const remote = await this.getRef(token, owner, repoName, branch);
|
|
20732
|
+
const currentSha = remote.object.sha;
|
|
20733
|
+
const rebasedCommitSha = await this.createCommit(
|
|
20734
|
+
token,
|
|
20735
|
+
owner,
|
|
20736
|
+
repoName,
|
|
20737
|
+
newTreeSha,
|
|
20738
|
+
currentSha,
|
|
20739
|
+
`Sync ${cfg.categories.join(", ")} \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
20740
|
+
);
|
|
20741
|
+
await this.updateRef(token, owner, repoName, branch, rebasedCommitSha);
|
|
20742
|
+
} else {
|
|
20743
|
+
throw err;
|
|
20744
|
+
}
|
|
20745
|
+
}
|
|
18789
20746
|
const syncState = {
|
|
18790
20747
|
version: 1,
|
|
18791
20748
|
sha: commitSha,
|
|
@@ -18924,8 +20881,8 @@ var CloudSync = class {
|
|
|
18924
20881
|
const localPath = this.categoryToPath(cat);
|
|
18925
20882
|
if (!localPath) continue;
|
|
18926
20883
|
try {
|
|
18927
|
-
const
|
|
18928
|
-
if (
|
|
20884
|
+
const stat8 = await fsp2.stat(localPath);
|
|
20885
|
+
if (stat8.isDirectory()) {
|
|
18929
20886
|
const files = await this.walkDir(localPath, localPath);
|
|
18930
20887
|
for (const file of files) {
|
|
18931
20888
|
const content = await fsp2.readFile(file, "utf8");
|
|
@@ -18950,8 +20907,8 @@ var CloudSync = class {
|
|
|
18950
20907
|
const localPath = this.categoryToPath(cat);
|
|
18951
20908
|
if (!localPath) continue;
|
|
18952
20909
|
try {
|
|
18953
|
-
const
|
|
18954
|
-
if (
|
|
20910
|
+
const stat8 = await fsp2.stat(localPath);
|
|
20911
|
+
if (stat8.isDirectory()) {
|
|
18955
20912
|
const files = await this.walkDir(localPath, localPath);
|
|
18956
20913
|
for (const file of files) {
|
|
18957
20914
|
const content = await fsp2.readFile(file);
|
|
@@ -19552,7 +21509,7 @@ var SkillGenerator = class {
|
|
|
19552
21509
|
};
|
|
19553
21510
|
return [commonInjection, ...stackSpecific[stack] ?? []];
|
|
19554
21511
|
}
|
|
19555
|
-
getConfigPatterns(
|
|
21512
|
+
getConfigPatterns(_stack) {
|
|
19556
21513
|
const commonConfig = [
|
|
19557
21514
|
{
|
|
19558
21515
|
id: "insecure-tls",
|
|
@@ -20134,7 +22091,6 @@ var SecurityScannerOrchestrator = class {
|
|
|
20134
22091
|
retryPolicy;
|
|
20135
22092
|
errorHandler;
|
|
20136
22093
|
detector = defaultTechStackDetector;
|
|
20137
|
-
reportGenerator = defaultReportGenerator;
|
|
20138
22094
|
gitignoreUpdater = defaultGitignoreUpdater;
|
|
20139
22095
|
/**
|
|
20140
22096
|
* Wraps provider.complete with retry logic using the injected RetryPolicy.
|
|
@@ -20318,7 +22274,7 @@ Return ONLY the JSON object, no markdown, no explanation.`;
|
|
|
20318
22274
|
/**
|
|
20319
22275
|
* Scan a batch of files using LLM.
|
|
20320
22276
|
*/
|
|
20321
|
-
async scanFileBatchLLM(provider, model, files, skill,
|
|
22277
|
+
async scanFileBatchLLM(provider, model, files, skill, _techStack) {
|
|
20322
22278
|
const fileContents = [];
|
|
20323
22279
|
for (const file of files) {
|
|
20324
22280
|
try {
|
|
@@ -20515,7 +22471,7 @@ Be specific about the vulnerabilities found and how to fix them.`;
|
|
|
20515
22471
|
/**
|
|
20516
22472
|
* Gather project info for skill generation.
|
|
20517
22473
|
*/
|
|
20518
|
-
async gatherProjectInfo(projectRoot,
|
|
22474
|
+
async gatherProjectInfo(projectRoot, _techStack) {
|
|
20519
22475
|
const info = [];
|
|
20520
22476
|
const keyFiles = [
|
|
20521
22477
|
"package.json",
|
|
@@ -20547,7 +22503,7 @@ ${dirs.join(", ")}`);
|
|
|
20547
22503
|
/**
|
|
20548
22504
|
* Gather files to scan based on patterns.
|
|
20549
22505
|
*/
|
|
20550
|
-
async gatherFiles(root,
|
|
22506
|
+
async gatherFiles(root, _patterns, depth) {
|
|
20551
22507
|
const files = [];
|
|
20552
22508
|
const maxDepth = depth === "quick" ? 2 : depth === "deep" ? 20 : 5;
|
|
20553
22509
|
const extensions = [".ts", ".js", ".jsx", ".tsx", ".py", ".go", ".java", ".cs", ".rs"];
|
|
@@ -20806,22 +22762,22 @@ Use \`/security report <number>\` to view a specific report.` };
|
|
|
20806
22762
|
}
|
|
20807
22763
|
const index = Number.parseInt(reportId, 10) - 1;
|
|
20808
22764
|
if (!Number.isNaN(index) && reports[index]) {
|
|
20809
|
-
const { readFile:
|
|
20810
|
-
const content = await
|
|
22765
|
+
const { readFile: readFile37 } = await import('fs/promises');
|
|
22766
|
+
const content = await readFile37(join(reportsDir, reports[index]), "utf-8");
|
|
20811
22767
|
return { message: `# Security Report
|
|
20812
22768
|
|
|
20813
22769
|
${content}` };
|
|
20814
22770
|
}
|
|
20815
22771
|
const match = reports.find((r) => r.includes(reportId));
|
|
20816
22772
|
if (match) {
|
|
20817
|
-
const { readFile:
|
|
20818
|
-
const content = await
|
|
22773
|
+
const { readFile: readFile37 } = await import('fs/promises');
|
|
22774
|
+
const content = await readFile37(join(reportsDir, match), "utf-8");
|
|
20819
22775
|
return { message: `# Security Report
|
|
20820
22776
|
|
|
20821
22777
|
${content}` };
|
|
20822
22778
|
}
|
|
20823
22779
|
return { message: `\u274C Report "${reportId}" not found. Use \`/security report\` to see available reports.` };
|
|
20824
|
-
} catch (
|
|
22780
|
+
} catch (_error) {
|
|
20825
22781
|
return { message: "\u{1F4ED} No security reports found. Run `/security scan` first." };
|
|
20826
22782
|
}
|
|
20827
22783
|
}
|
|
@@ -20874,7 +22830,6 @@ var FleetManager = class {
|
|
|
20874
22830
|
/** Usage rollup across all subagents. */
|
|
20875
22831
|
usage;
|
|
20876
22832
|
manifestPath;
|
|
20877
|
-
sessionsRoot;
|
|
20878
22833
|
directorRunId;
|
|
20879
22834
|
/** Spawn cap (lifetime total). Infinity means unlimited. */
|
|
20880
22835
|
maxSpawns;
|
|
@@ -20898,11 +22853,18 @@ var FleetManager = class {
|
|
|
20898
22853
|
pendingTasks = /* @__PURE__ */ new Map();
|
|
20899
22854
|
subagentMeta = /* @__PURE__ */ new Map();
|
|
20900
22855
|
priceLookups = /* @__PURE__ */ new Map();
|
|
22856
|
+
/** Tracks which nickname keys are already assigned — prevents collisions. */
|
|
22857
|
+
_usedNicknames = /* @__PURE__ */ new Set();
|
|
20901
22858
|
/** The coordinator (wired via setCoordinator by Director after construction). */
|
|
20902
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;
|
|
20903
22866
|
constructor(opts = {}) {
|
|
20904
22867
|
this.manifestPath = opts.manifestPath;
|
|
20905
|
-
this.sessionsRoot = opts.sessionsRoot;
|
|
20906
22868
|
this.directorRunId = opts.directorRunId ?? randomUUID();
|
|
20907
22869
|
this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
|
|
20908
22870
|
this.maxSpawnDepth = opts.maxSpawnDepth ?? 2;
|
|
@@ -20910,6 +22872,8 @@ var FleetManager = class {
|
|
|
20910
22872
|
this.sessionWriter = opts.sessionWriter ?? null;
|
|
20911
22873
|
this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
|
|
20912
22874
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
22875
|
+
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
22876
|
+
this.maxContext = opts.maxContext ?? 128e3;
|
|
20913
22877
|
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(
|
|
20914
22878
|
opts.stateCheckpointPath,
|
|
20915
22879
|
{
|
|
@@ -20924,7 +22888,7 @@ var FleetManager = class {
|
|
|
20924
22888
|
this.fleet = new FleetBus();
|
|
20925
22889
|
this.usage = new FleetUsageAggregator(
|
|
20926
22890
|
this.fleet,
|
|
20927
|
-
(
|
|
22891
|
+
(_id, provider, model) => {
|
|
20928
22892
|
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
20929
22893
|
return void 0;
|
|
20930
22894
|
},
|
|
@@ -20963,7 +22927,7 @@ var FleetManager = class {
|
|
|
20963
22927
|
* which cap was exceeded. Does NOT throw — the caller decides
|
|
20964
22928
|
* how to surface the rejection.
|
|
20965
22929
|
*/
|
|
20966
|
-
canSpawn(
|
|
22930
|
+
canSpawn(_config) {
|
|
20967
22931
|
if (this.spawnDepth >= this.maxSpawnDepth) {
|
|
20968
22932
|
return { kind: "max_spawn_depth", limit: this.maxSpawnDepth, observed: this.spawnDepth };
|
|
20969
22933
|
}
|
|
@@ -20976,8 +22940,45 @@ var FleetManager = class {
|
|
|
20976
22940
|
return { kind: "max_cost_usd", limit: this.maxFleetCostUsd, observed: totalCost };
|
|
20977
22941
|
}
|
|
20978
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
|
+
}
|
|
20979
22953
|
return null;
|
|
20980
22954
|
}
|
|
22955
|
+
setLeaderContextPressure(tokens) {
|
|
22956
|
+
this.leaderContextPressure = tokens;
|
|
22957
|
+
}
|
|
22958
|
+
/**
|
|
22959
|
+
* Assign a memorable nickname (e.g. "Einstein (Bug Hunter)") to the config,
|
|
22960
|
+
* record it so the same name is never reused, then record the spawn.
|
|
22961
|
+
*
|
|
22962
|
+
* Call this INSTEAD of `recordSpawn` when you want automatic nicknames.
|
|
22963
|
+
* The nickname is written back to `config.name` BEFORE the coordinator
|
|
22964
|
+
* sees the config, so the manifest, logs, and fleet UI all show it.
|
|
22965
|
+
*/
|
|
22966
|
+
assignNicknameAndRecord(subagentId, config, priceLookup) {
|
|
22967
|
+
const role = config.role ?? "subagent";
|
|
22968
|
+
const nickname = assignNickname(role, this._usedNicknames);
|
|
22969
|
+
const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
22970
|
+
this._usedNicknames.add(baseKey);
|
|
22971
|
+
config.name = nickname;
|
|
22972
|
+
this.recordSpawn(subagentId, config, priceLookup);
|
|
22973
|
+
return nickname;
|
|
22974
|
+
}
|
|
22975
|
+
/**
|
|
22976
|
+
* Returns the set of already-assigned nickname keys — useful for debugging
|
|
22977
|
+
* and testing.
|
|
22978
|
+
*/
|
|
22979
|
+
get usedNicknames() {
|
|
22980
|
+
return this._usedNicknames;
|
|
22981
|
+
}
|
|
20981
22982
|
/**
|
|
20982
22983
|
* Records a spawn: increments counter, stores metadata, updates state checkpoint,
|
|
20983
22984
|
* and schedules a debounced manifest write. Call AFTER the coordinator
|
|
@@ -21178,7 +23179,7 @@ function createMcpControlTool(opts) {
|
|
|
21178
23179
|
mutating: true,
|
|
21179
23180
|
riskTier: "standard",
|
|
21180
23181
|
inputSchema,
|
|
21181
|
-
async execute(raw
|
|
23182
|
+
async execute(raw) {
|
|
21182
23183
|
const input = raw;
|
|
21183
23184
|
return mcpControlDispatch(input, { getConfig, configPath, registry });
|
|
21184
23185
|
}
|
|
@@ -21726,7 +23727,6 @@ var Agent = class {
|
|
|
21726
23727
|
pipelines;
|
|
21727
23728
|
ctx;
|
|
21728
23729
|
maxIterations;
|
|
21729
|
-
iterationTimeoutMs;
|
|
21730
23730
|
executionStrategy;
|
|
21731
23731
|
perIterationOutputCapBytes;
|
|
21732
23732
|
plugins = [];
|
|
@@ -21744,7 +23744,6 @@ var Agent = class {
|
|
|
21744
23744
|
this.pipelines = init.pipelines;
|
|
21745
23745
|
this.ctx = init.context;
|
|
21746
23746
|
this.maxIterations = init.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
21747
|
-
this.iterationTimeoutMs = init.iterationTimeoutMs ?? 3e5;
|
|
21748
23747
|
this.executionStrategy = init.executionStrategy ?? "smart";
|
|
21749
23748
|
this.perIterationOutputCapBytes = init.perIterationOutputCapBytes ?? 1e5;
|
|
21750
23749
|
this.autoExtendLimit = init.autoExtendLimit ?? true;
|
|
@@ -21766,9 +23765,6 @@ var Agent = class {
|
|
|
21766
23765
|
get permission() {
|
|
21767
23766
|
return this.container.resolve(TOKENS.PermissionPolicy);
|
|
21768
23767
|
}
|
|
21769
|
-
get scrubber() {
|
|
21770
|
-
return this.container.resolve(TOKENS.SecretScrubber);
|
|
21771
|
-
}
|
|
21772
23768
|
get renderer() {
|
|
21773
23769
|
return this.container.has(TOKENS.Renderer) ? this.container.resolve(TOKENS.Renderer) : void 0;
|
|
21774
23770
|
}
|
|
@@ -21898,6 +23894,11 @@ var Agent = class {
|
|
|
21898
23894
|
if (controller.signal.aborted) {
|
|
21899
23895
|
return { status: "aborted", iterations };
|
|
21900
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
|
+
});
|
|
21901
23902
|
if (autonomousContinue) {
|
|
21902
23903
|
consumeAutonomousContinue(this.ctx);
|
|
21903
23904
|
}
|
|
@@ -21979,6 +23980,7 @@ var Agent = class {
|
|
|
21979
23980
|
finalText = responseResult.finalText;
|
|
21980
23981
|
const toolUses = res.content.filter(isToolUseBlock);
|
|
21981
23982
|
if (toolUses.length === 0) {
|
|
23983
|
+
this.emitContextPct();
|
|
21982
23984
|
this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
|
|
21983
23985
|
if (autonomousContinue && responseResult.directive === "continue") {
|
|
21984
23986
|
await this.compactContextIfNeeded();
|
|
@@ -21992,11 +23994,13 @@ var Agent = class {
|
|
|
21992
23994
|
}
|
|
21993
23995
|
await this.executeTools(toolUses);
|
|
21994
23996
|
if (autonomousContinue && consumeAutonomousContinue(this.ctx)) {
|
|
23997
|
+
this.emitContextPct();
|
|
21995
23998
|
this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
|
|
21996
23999
|
await this.compactContextIfNeeded();
|
|
21997
24000
|
await this.extensions.runAfterIteration(this.ctx, i);
|
|
21998
24001
|
continue;
|
|
21999
24002
|
}
|
|
24003
|
+
this.emitContextPct();
|
|
22000
24004
|
this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
|
|
22001
24005
|
await this.compactContextIfNeeded();
|
|
22002
24006
|
await this.extensions.runAfterIteration(this.ctx, i);
|
|
@@ -22009,6 +24013,12 @@ var Agent = class {
|
|
|
22009
24013
|
}
|
|
22010
24014
|
} finally {
|
|
22011
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
|
+
});
|
|
22012
24022
|
}
|
|
22013
24023
|
}
|
|
22014
24024
|
/**
|
|
@@ -22301,6 +24311,19 @@ var Agent = class {
|
|
|
22301
24311
|
async compactContextIfNeeded() {
|
|
22302
24312
|
await this.pipelines.contextWindow.run(this.ctx);
|
|
22303
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
|
+
}
|
|
22304
24327
|
};
|
|
22305
24328
|
function toError(err) {
|
|
22306
24329
|
return err instanceof Error ? err : new Error(String(err));
|
|
@@ -22977,8 +25000,8 @@ ${mem}`);
|
|
|
22977
25000
|
}
|
|
22978
25001
|
async dirExists(p) {
|
|
22979
25002
|
try {
|
|
22980
|
-
const
|
|
22981
|
-
return
|
|
25003
|
+
const stat8 = await fsp2.stat(p);
|
|
25004
|
+
return stat8.isDirectory();
|
|
22982
25005
|
} catch {
|
|
22983
25006
|
return false;
|
|
22984
25007
|
}
|
|
@@ -23391,10 +25414,28 @@ var DefaultPluginAPI = class {
|
|
|
23391
25414
|
}
|
|
23392
25415
|
this.pipelines = readonlyPipelines;
|
|
23393
25416
|
const tr = init.toolRegistry;
|
|
25417
|
+
const isOfficial = init.official === true;
|
|
25418
|
+
const assertCanMutateTool = (name, op) => {
|
|
25419
|
+
if (isOfficial) return;
|
|
25420
|
+
const currentOwner = tr.ownerOf(name);
|
|
25421
|
+
if (currentOwner === void 0) return;
|
|
25422
|
+
const ownedSolelyByMe = currentOwner.split("+").every((seg) => seg === owner);
|
|
25423
|
+
if (!ownedSolelyByMe) {
|
|
25424
|
+
throw new Error(
|
|
25425
|
+
`Plugin "${owner}" may not ${op} tool "${name}" \u2014 it is owned by "${currentOwner}". Only official (first-party) plugins may modify tools they do not own.`
|
|
25426
|
+
);
|
|
25427
|
+
}
|
|
25428
|
+
};
|
|
23394
25429
|
this.tools = {
|
|
23395
25430
|
register: (t2) => tr.register(t2, owner),
|
|
23396
|
-
unregister: (name) =>
|
|
23397
|
-
|
|
25431
|
+
unregister: (name) => {
|
|
25432
|
+
assertCanMutateTool(name, "unregister");
|
|
25433
|
+
return tr.unregister(name);
|
|
25434
|
+
},
|
|
25435
|
+
wrap: (name, wrapper) => {
|
|
25436
|
+
assertCanMutateTool(name, "wrap");
|
|
25437
|
+
tr.wrap(name, wrapper, owner);
|
|
25438
|
+
},
|
|
23398
25439
|
get: (name) => tr.get(name),
|
|
23399
25440
|
list: () => tr.list()
|
|
23400
25441
|
};
|
|
@@ -25296,6 +27337,203 @@ function assertSafePath(dir, projectRoot) {
|
|
|
25296
27337
|
}
|
|
25297
27338
|
}
|
|
25298
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
|
+
|
|
25299
27537
|
// src/plugins/prompts-plugin.ts
|
|
25300
27538
|
function createPromptsPlugin(opts) {
|
|
25301
27539
|
let store = null;
|
|
@@ -25442,9 +27680,12 @@ function dim2(s) {
|
|
|
25442
27680
|
}
|
|
25443
27681
|
|
|
25444
27682
|
// src/plugins/sync-plugin.ts
|
|
27683
|
+
init_atomic_write();
|
|
25445
27684
|
function createSyncPlugin(opts) {
|
|
25446
27685
|
let cloud = null;
|
|
25447
27686
|
let configStore;
|
|
27687
|
+
let vault;
|
|
27688
|
+
let syncConfigPath;
|
|
25448
27689
|
return {
|
|
25449
27690
|
name: "wstack-sync",
|
|
25450
27691
|
version: "1.0.0",
|
|
@@ -25456,6 +27697,8 @@ function createSyncPlugin(opts) {
|
|
|
25456
27697
|
const rawConfig = api.config;
|
|
25457
27698
|
const paths = opts?.paths ?? rawConfig.paths;
|
|
25458
27699
|
configStore = opts?.configStore ?? rawConfig.configStore;
|
|
27700
|
+
vault = opts?.vault ?? rawConfig.vault;
|
|
27701
|
+
syncConfigPath = paths?.syncConfig;
|
|
25459
27702
|
if (!paths || !configStore) {
|
|
25460
27703
|
api.log.warn("[sync] paths or configStore not available \u2014 /sync disabled");
|
|
25461
27704
|
return;
|
|
@@ -25471,7 +27714,7 @@ function createSyncPlugin(opts) {
|
|
|
25471
27714
|
}
|
|
25472
27715
|
);
|
|
25473
27716
|
void cloud.loadState();
|
|
25474
|
-
api.slashCommands.register(buildSyncCommand(cloud, configStore));
|
|
27717
|
+
api.slashCommands.register(buildSyncCommand(cloud, configStore, vault, syncConfigPath));
|
|
25475
27718
|
api.log.info("[sync] loaded \u2014 /sync available. Run /sync to get started.");
|
|
25476
27719
|
},
|
|
25477
27720
|
teardown(api) {
|
|
@@ -25483,7 +27726,7 @@ function createSyncPlugin(opts) {
|
|
|
25483
27726
|
}
|
|
25484
27727
|
};
|
|
25485
27728
|
}
|
|
25486
|
-
function buildSyncCommand(cloud, configStore) {
|
|
27729
|
+
function buildSyncCommand(cloud, configStore, vault, syncConfigPath) {
|
|
25487
27730
|
return {
|
|
25488
27731
|
name: "sync",
|
|
25489
27732
|
description: "Cloud sync: /sync [status|enable|disable|push|pull|categories]",
|
|
@@ -25511,13 +27754,17 @@ function buildSyncCommand(cloud, configStore) {
|
|
|
25511
27754
|
if (!repo || !repo.includes("/")) {
|
|
25512
27755
|
return { message: 'Invalid repo format. Expected "owner/repo".' };
|
|
25513
27756
|
}
|
|
27757
|
+
const storedToken = vault ? vault.encrypt(token) : token;
|
|
25514
27758
|
const syncConfig = {
|
|
25515
27759
|
enabled: true,
|
|
25516
27760
|
repo,
|
|
25517
|
-
githubToken:
|
|
27761
|
+
githubToken: storedToken,
|
|
25518
27762
|
categories: cats.length > 0 ? cats : ALL_SYNC_CATEGORIES,
|
|
25519
27763
|
lastSyncedAt: void 0
|
|
25520
27764
|
};
|
|
27765
|
+
if (syncConfigPath) {
|
|
27766
|
+
await atomicWrite(syncConfigPath, JSON.stringify(syncConfig, null, 2), { mode: 384 });
|
|
27767
|
+
}
|
|
25521
27768
|
configStore.update({ sync: syncConfig });
|
|
25522
27769
|
await cloud.loadState();
|
|
25523
27770
|
return {
|
|
@@ -26344,6 +28591,6 @@ ${formatPlan(updated)}`
|
|
|
26344
28591
|
};
|
|
26345
28592
|
}
|
|
26346
28593
|
|
|
26347
|
-
export { 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, FleetBus, 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, 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, makeAutonomyPromptContributor, makeContinueToNextIterationTool, makeDirectorSessionFactory, makeLLMClassifier, 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 };
|
|
26348
28595
|
//# sourceMappingURL=index.js.map
|
|
26349
28596
|
//# sourceMappingURL=index.js.map
|