@wrongstack/core 0.9.19 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -33
- package/dist/{agent-bridge-DMVOX0cF.d.ts → agent-bridge-DBBGP5O4.d.ts} +1 -1
- package/dist/{agent-subagent-runner-C4qt9e5Y.d.ts → agent-subagent-runner-KwufTWwn.d.ts} +3 -4
- package/dist/compactor-D1RHFRmF.d.ts +41 -0
- package/dist/{config-CWva0qoL.d.ts → config-CJXBka2r.d.ts} +38 -2
- package/dist/{context-BRNbHmRM.d.ts → context-7u93AcGD.d.ts} +140 -0
- package/dist/coordination/index.d.ts +13 -13
- package/dist/coordination/index.js +786 -200
- package/dist/coordination/index.js.map +1 -1
- package/dist/{default-config-Uzla85H-.d.ts → default-config-DEXI4jsl.d.ts} +10 -1
- package/dist/defaults/index.d.ts +24 -24
- package/dist/defaults/index.js +1612 -528
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-CiG9qUM_.d.ts → events-Bw1K5E6K.d.ts} +49 -1
- package/dist/execution/index.d.ts +46 -32
- package/dist/execution/index.js +663 -169
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/extension/index.js.map +1 -1
- package/dist/goal-store-BeRsj7YX.d.ts +188 -0
- package/dist/{index-p95HQ22A.d.ts → index-DIt5TIaF.d.ts} +8 -8
- package/dist/{index-aizK8olO.d.ts → index-DgQWnZxP.d.ts} +11 -8
- package/dist/index.d.ts +338 -41
- package/dist/index.js +2747 -787
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +36 -0
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js +57 -8
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-BkVEqkRe.d.ts → mcp-servers-BgINZzuo.d.ts} +25 -3
- package/dist/models/index.d.ts +3 -3
- package/dist/models/index.js +164 -11
- package/dist/models/index.js.map +1 -1
- package/dist/models-registry-gwMAo6E3.d.ts +86 -0
- package/dist/{multi-agent-3ZnTB1aT.d.ts → multi-agent-C1O8EFzb.d.ts} +77 -24
- package/dist/{multi-agent-coordinator-bRaI_aD1.d.ts → multi-agent-coordinator-DoVSBFNx.d.ts} +20 -2
- package/dist/{null-fleet-bus-DKM3Iy9d.d.ts → null-fleet-bus-T-s3JaOt.d.ts} +413 -110
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-TcJfc29Y.d.ts → path-resolver-BfyK-I7w.d.ts} +2 -2
- package/dist/{permission-bPuzAy4x.d.ts → permission-B6sldrSp.d.ts} +1 -1
- package/dist/{permission-policy-BUQSutpl.d.ts → permission-policy-CtNscWOA.d.ts} +10 -9
- package/dist/{plan-templates-fkQTyz3U.d.ts → plan-templates-BWs6Je7j.d.ts} +8 -8
- package/dist/{provider-runner-BEpikbbN.d.ts → provider-runner-BmPe2RuU.d.ts} +3 -3
- package/dist/{retry-policy-BYkq0ugs.d.ts → retry-policy-KF18W4dg.d.ts} +1 -1
- package/dist/sdd/index.d.ts +9 -10
- package/dist/sdd/index.js +350 -122
- package/dist/sdd/index.js.map +1 -1
- package/dist/security/index.d.ts +65 -4
- package/dist/security/index.js +67 -19
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-pox8abg0.d.ts → selector-DmXxpFyM.d.ts} +1 -1
- package/dist/{session-reader-CSWcb5Ga.d.ts → session-reader-CCSBfQmd.d.ts} +2 -2
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +392 -8
- package/dist/storage/index.js +796 -40
- package/dist/storage/index.js.map +1 -1
- package/dist/{system-prompt-Bs-Wliab.d.ts → system-prompt-CM6zOhd2.d.ts} +19 -1
- package/dist/{tool-executor-Boo3dekH.d.ts → tool-executor-D7RxLtg4.d.ts} +9 -4
- package/dist/types/index.d.ts +16 -16
- package/dist/types/index.js +404 -14
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +40 -8
- package/dist/utils/index.js +127 -57
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-BCgmTNlG.d.ts → wstack-paths-eMXnY1_X.d.ts} +8 -0
- package/package.json +1 -1
- package/dist/compactor-DVTKL7XD.d.ts +0 -23
- package/dist/goal-store-C7jcumEh.d.ts +0 -96
- package/dist/models-registry-OG_30xqZ.d.ts +0 -46
package/dist/defaults/index.js
CHANGED
|
@@ -640,7 +640,6 @@ var FileSessionWriter = class {
|
|
|
640
640
|
}
|
|
641
641
|
return event;
|
|
642
642
|
}
|
|
643
|
-
promptIndex = 0;
|
|
644
643
|
pendingFileSnapshots = [];
|
|
645
644
|
/** Tracks open tool_use IDs during the current run to serialize on close for resume. */
|
|
646
645
|
openToolUses = /* @__PURE__ */ new Set();
|
|
@@ -731,7 +730,6 @@ var FileSessionWriter = class {
|
|
|
731
730
|
await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);
|
|
732
731
|
this.pendingFileSnapshots = [];
|
|
733
732
|
}
|
|
734
|
-
this.promptIndex = promptIndex + 1;
|
|
735
733
|
await this.append({
|
|
736
734
|
type: "checkpoint",
|
|
737
735
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -827,6 +825,38 @@ var FileSessionWriter = class {
|
|
|
827
825
|
`;
|
|
828
826
|
await fsp.writeFile(this.filePath, record, "utf8");
|
|
829
827
|
}
|
|
828
|
+
/**
|
|
829
|
+
* Idea #1 — write an in-flight marker. The agent loop should call
|
|
830
|
+
* this at the start of each long-running operation; a matching
|
|
831
|
+
* `clearInFlightMarker` follows on clean exit. A stale marker
|
|
832
|
+
* (no end) is what `SessionRecovery.detectStale` looks for.
|
|
833
|
+
*/
|
|
834
|
+
async writeInFlightMarker(context) {
|
|
835
|
+
if (!context || context.length > 500) {
|
|
836
|
+
throw new Error("In-flight context must be 1..500 chars");
|
|
837
|
+
}
|
|
838
|
+
await this.append({
|
|
839
|
+
type: "in_flight_start",
|
|
840
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
841
|
+
context
|
|
842
|
+
});
|
|
843
|
+
this.events?.emit("in_flight.started", { context, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Idea #1 — close the in-flight marker. Idempotent in spirit
|
|
847
|
+
* (you can call it after a successful iteration even if you
|
|
848
|
+
* didn't open one this round) — but the session log records
|
|
849
|
+
* every call so postmortem tooling can see "the agent finished
|
|
850
|
+
* cleanly X times, then died without finishing Y".
|
|
851
|
+
*/
|
|
852
|
+
async clearInFlightMarker(reason) {
|
|
853
|
+
await this.append({
|
|
854
|
+
type: "in_flight_end",
|
|
855
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
856
|
+
reason
|
|
857
|
+
});
|
|
858
|
+
this.events?.emit("in_flight.ended", { reason, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
859
|
+
}
|
|
830
860
|
};
|
|
831
861
|
function userInputTitle(content) {
|
|
832
862
|
const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
@@ -889,7 +919,7 @@ function isPersistedQueueItem(v) {
|
|
|
889
919
|
// src/storage/attachment-store.ts
|
|
890
920
|
init_atomic_write();
|
|
891
921
|
var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
|
|
892
|
-
var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)\]/g;
|
|
922
|
+
var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)[^\]]*\]|\[file:([^\]]+)\]/g;
|
|
893
923
|
var DefaultAttachmentStore = class {
|
|
894
924
|
items = /* @__PURE__ */ new Map();
|
|
895
925
|
refs = [];
|
|
@@ -943,9 +973,15 @@ var DefaultAttachmentStore = class {
|
|
|
943
973
|
const idx = m.index ?? 0;
|
|
944
974
|
const before = text.slice(lastIndex, idx);
|
|
945
975
|
if (before) blocks.push({ type: "text", text: before });
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
976
|
+
let ref;
|
|
977
|
+
if (m[3] !== void 0) {
|
|
978
|
+
const wantPath = m[3];
|
|
979
|
+
ref = findLast(this.refs, (r) => r.kind === "file" && refPath(r) === wantPath);
|
|
980
|
+
} else {
|
|
981
|
+
const kind = prefixToKind(m[1]);
|
|
982
|
+
const seq = Number(m[2]);
|
|
983
|
+
ref = this.refs.find((r) => r.kind === kind && r.seq === seq);
|
|
984
|
+
}
|
|
949
985
|
const att = ref ? this.items.get(ref.id) : void 0;
|
|
950
986
|
if (!att) {
|
|
951
987
|
blocks.push({ type: "text", text: m[0] });
|
|
@@ -998,6 +1034,15 @@ function prefixToKind(prefix) {
|
|
|
998
1034
|
if (prefix === "image") return "image";
|
|
999
1035
|
return "file";
|
|
1000
1036
|
}
|
|
1037
|
+
function refPath(ref) {
|
|
1038
|
+
return ref.meta.filename ?? ref.meta.label;
|
|
1039
|
+
}
|
|
1040
|
+
function findLast(arr, pred) {
|
|
1041
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
1042
|
+
if (pred(arr[i])) return arr[i];
|
|
1043
|
+
}
|
|
1044
|
+
return void 0;
|
|
1045
|
+
}
|
|
1001
1046
|
function mergeAdjacentText(blocks) {
|
|
1002
1047
|
const out = [];
|
|
1003
1048
|
for (const b of blocks) {
|
|
@@ -1382,6 +1427,14 @@ var DEFAULT_CONTEXT_CONFIG = Object.freeze({
|
|
|
1382
1427
|
var DEFAULT_AUTONOMY_CONFIG = Object.freeze({
|
|
1383
1428
|
autoProceedDelayMs: 45e3
|
|
1384
1429
|
});
|
|
1430
|
+
var DEFAULT_SESSION_LOGGING_CONFIG = Object.freeze({
|
|
1431
|
+
auditLevel: "standard",
|
|
1432
|
+
sampling: {
|
|
1433
|
+
toolProgress: {
|
|
1434
|
+
sampleRate: 8
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1385
1438
|
|
|
1386
1439
|
// src/storage/config-loader.ts
|
|
1387
1440
|
var BEHAVIOR_DEFAULTS = {
|
|
@@ -1410,7 +1463,8 @@ var BEHAVIOR_DEFAULTS = {
|
|
|
1410
1463
|
memory: true,
|
|
1411
1464
|
modelsRegistry: true,
|
|
1412
1465
|
skills: true
|
|
1413
|
-
}
|
|
1466
|
+
},
|
|
1467
|
+
session: { ...DEFAULT_SESSION_LOGGING_CONFIG }
|
|
1414
1468
|
};
|
|
1415
1469
|
var ENV_MAP = {
|
|
1416
1470
|
WRONGSTACK_PROVIDER: (c, v) => {
|
|
@@ -2214,6 +2268,101 @@ var SessionAnalyzer = class {
|
|
|
2214
2268
|
}
|
|
2215
2269
|
};
|
|
2216
2270
|
|
|
2271
|
+
// src/storage/session-event-bridge.ts
|
|
2272
|
+
var CORE_RECONSTRUCT_EVENTS = /* @__PURE__ */ new Set([
|
|
2273
|
+
"session_start",
|
|
2274
|
+
"session_resumed",
|
|
2275
|
+
"user_input",
|
|
2276
|
+
"llm_response",
|
|
2277
|
+
"tool_result",
|
|
2278
|
+
"checkpoint",
|
|
2279
|
+
"file_snapshot",
|
|
2280
|
+
"rewound",
|
|
2281
|
+
"in_flight_start",
|
|
2282
|
+
"in_flight_end",
|
|
2283
|
+
"session_end"
|
|
2284
|
+
]);
|
|
2285
|
+
var STANDARD_AUDIT_EVENTS = /* @__PURE__ */ new Set([
|
|
2286
|
+
"llm_request",
|
|
2287
|
+
"tool_use",
|
|
2288
|
+
"tool_call_start",
|
|
2289
|
+
"tool_call_end",
|
|
2290
|
+
"compaction",
|
|
2291
|
+
"error",
|
|
2292
|
+
"message_truncated",
|
|
2293
|
+
"provider_retry",
|
|
2294
|
+
"provider_error"
|
|
2295
|
+
]);
|
|
2296
|
+
var FULL_ONLY_EVENTS = /* @__PURE__ */ new Set([
|
|
2297
|
+
"tool_progress"
|
|
2298
|
+
]);
|
|
2299
|
+
function isAllowed(type, level) {
|
|
2300
|
+
if (CORE_RECONSTRUCT_EVENTS.has(type)) return true;
|
|
2301
|
+
if (level === "minimal") return false;
|
|
2302
|
+
if (STANDARD_AUDIT_EVENTS.has(type)) return true;
|
|
2303
|
+
if (level === "standard") return false;
|
|
2304
|
+
if (FULL_ONLY_EVENTS.has(type)) {
|
|
2305
|
+
return level === "full";
|
|
2306
|
+
}
|
|
2307
|
+
return true;
|
|
2308
|
+
}
|
|
2309
|
+
function createSessionEventBridge(writer, level = "standard", options = {}) {
|
|
2310
|
+
const normalizedLevel = level ?? "standard";
|
|
2311
|
+
const progressCounters = /* @__PURE__ */ new Map();
|
|
2312
|
+
const toolProgressConfig = options.sampling?.toolProgress ?? {};
|
|
2313
|
+
const TOOL_PROGRESS_SAMPLE_RATE = toolProgressConfig.sampleRate ?? 8;
|
|
2314
|
+
function shouldSample(event) {
|
|
2315
|
+
if (event.type !== "tool_progress") return true;
|
|
2316
|
+
const progEvent = event;
|
|
2317
|
+
const innerType = progEvent.event?.type;
|
|
2318
|
+
if (innerType === "warning" || innerType === "metric" || innerType === "file_changed") {
|
|
2319
|
+
return true;
|
|
2320
|
+
}
|
|
2321
|
+
if (innerType === "log" || innerType === "partial_output") {
|
|
2322
|
+
const key = progEvent.id || progEvent.name;
|
|
2323
|
+
const count = (progressCounters.get(key) || 0) + 1;
|
|
2324
|
+
progressCounters.set(key, count);
|
|
2325
|
+
return count === 1 || count % TOOL_PROGRESS_SAMPLE_RATE === 0;
|
|
2326
|
+
}
|
|
2327
|
+
return true;
|
|
2328
|
+
}
|
|
2329
|
+
return {
|
|
2330
|
+
level: normalizedLevel,
|
|
2331
|
+
allows(type) {
|
|
2332
|
+
return isAllowed(type, normalizedLevel);
|
|
2333
|
+
},
|
|
2334
|
+
async append(event) {
|
|
2335
|
+
if (!writer) return;
|
|
2336
|
+
if (!isAllowed(event.type, normalizedLevel)) return;
|
|
2337
|
+
if (!shouldSample(event)) return;
|
|
2338
|
+
try {
|
|
2339
|
+
await writer.append(event);
|
|
2340
|
+
} catch (err) {
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
function resolveAuditLevel(cfg) {
|
|
2346
|
+
const raw = cfg?.session?.auditLevel;
|
|
2347
|
+
if (raw === "minimal" || raw === "standard" || raw === "full") {
|
|
2348
|
+
return raw;
|
|
2349
|
+
}
|
|
2350
|
+
return "standard";
|
|
2351
|
+
}
|
|
2352
|
+
function resolveSessionLoggingConfig(cfg) {
|
|
2353
|
+
const session = cfg?.session ?? {};
|
|
2354
|
+
const auditLevel = resolveAuditLevel(cfg);
|
|
2355
|
+
const toolProgressSampleRate = session.sampling?.toolProgress?.sampleRate ?? 8;
|
|
2356
|
+
return {
|
|
2357
|
+
auditLevel,
|
|
2358
|
+
sampling: {
|
|
2359
|
+
toolProgress: {
|
|
2360
|
+
sampleRate: Math.max(1, Math.floor(toolProgressSampleRate))
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
};
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2217
2366
|
// src/storage/todos-checkpoint.ts
|
|
2218
2367
|
init_atomic_write();
|
|
2219
2368
|
async function loadTodosCheckpoint(filePath) {
|
|
@@ -3022,6 +3171,47 @@ function deepMerge2(a, b) {
|
|
|
3022
3171
|
return out;
|
|
3023
3172
|
}
|
|
3024
3173
|
|
|
3174
|
+
// src/security/capabilities.ts
|
|
3175
|
+
var ToolCapabilities = {
|
|
3176
|
+
/** Can execute arbitrary commands in the user's shell (the `bash` tool). */
|
|
3177
|
+
SHELL_ARBITRARY: "shell.arbitrary",
|
|
3178
|
+
/** Can write / modify / delete files inside the project. */
|
|
3179
|
+
FS_WRITE: "fs.write",
|
|
3180
|
+
/** Can write files outside the current project root (very high risk). */
|
|
3181
|
+
FS_WRITE_OUTSIDE_PROJECT: "fs.write.outside-project",
|
|
3182
|
+
/** Proxies tools from external MCP servers (unknown capability). */
|
|
3183
|
+
MCP_PROXY: "mcp.proxy",
|
|
3184
|
+
/** Can spawn or manage subagents / multi-agent tasks. */
|
|
3185
|
+
SUBAGENT_SPAWN: "subagent.spawn",
|
|
3186
|
+
/** Can mutate global or session configuration / trust state. */
|
|
3187
|
+
CONFIG_MUTATE: "config.mutate",
|
|
3188
|
+
/** Can install packages or run package managers with side effects. */
|
|
3189
|
+
PACKAGE_INSTALL: "package.install"
|
|
3190
|
+
};
|
|
3191
|
+
var DANGEROUS_FOR_SUBAGENTS = [
|
|
3192
|
+
ToolCapabilities.SHELL_ARBITRARY,
|
|
3193
|
+
ToolCapabilities.FS_WRITE,
|
|
3194
|
+
ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
|
|
3195
|
+
ToolCapabilities.MCP_PROXY,
|
|
3196
|
+
ToolCapabilities.SUBAGENT_SPAWN,
|
|
3197
|
+
ToolCapabilities.CONFIG_MUTATE,
|
|
3198
|
+
ToolCapabilities.PACKAGE_INSTALL
|
|
3199
|
+
];
|
|
3200
|
+
function hasDangerousCapabilityForSubagents(toolOrCaps) {
|
|
3201
|
+
if (!toolOrCaps) return false;
|
|
3202
|
+
const input = toolOrCaps;
|
|
3203
|
+
const caps = Array.isArray(input) ? input : input?.capabilities ?? [];
|
|
3204
|
+
return caps.some((c) => DANGEROUS_FOR_SUBAGENTS.includes(c));
|
|
3205
|
+
}
|
|
3206
|
+
function getDangerousCapabilities(toolOrCaps) {
|
|
3207
|
+
if (!toolOrCaps) return [];
|
|
3208
|
+
const input = toolOrCaps;
|
|
3209
|
+
const caps = Array.isArray(input) ? input : input?.capabilities ?? [];
|
|
3210
|
+
return caps.filter(
|
|
3211
|
+
(c) => DANGEROUS_FOR_SUBAGENTS.includes(c)
|
|
3212
|
+
);
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3025
3215
|
// src/security/permission-policy.ts
|
|
3026
3216
|
init_atomic_write();
|
|
3027
3217
|
|
|
@@ -3341,31 +3531,21 @@ var DefaultPermissionPolicy = class {
|
|
|
3341
3531
|
};
|
|
3342
3532
|
var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
3343
3533
|
/**
|
|
3344
|
-
*
|
|
3345
|
-
*
|
|
3346
|
-
* and cannot answer prompts, but inherited authorization does not
|
|
3347
|
-
* imply blanket permission for destructive or privilege-escalating
|
|
3348
|
-
* operations. These tools remain at their declared `permission`
|
|
3349
|
-
* level so the leader must explicitly allow them per-spawn.
|
|
3534
|
+
* Legacy name-based denylist.
|
|
3535
|
+
* @deprecated Prefer declaring `capabilities` on the Tool and using capability-based checks.
|
|
3350
3536
|
*/
|
|
3351
|
-
static
|
|
3537
|
+
static LEGACY_NAME_DENY = /* @__PURE__ */ new Set([
|
|
3352
3538
|
"bash",
|
|
3353
|
-
// arbitrary shell — use exec for constrained shell
|
|
3354
3539
|
"write",
|
|
3355
|
-
// arbitrary file write
|
|
3356
3540
|
"edit",
|
|
3357
|
-
// arbitrary in-project file modification (equivalent to write)
|
|
3358
3541
|
"replace",
|
|
3359
|
-
// arbitrary multi-file find/replace (equivalent to write)
|
|
3360
3542
|
"scaffold",
|
|
3361
|
-
// arbitrary file generation outside project root
|
|
3362
3543
|
"patch",
|
|
3363
|
-
// arbitrary diff application
|
|
3364
3544
|
"install",
|
|
3365
|
-
// installs from arbitrary package sources
|
|
3366
3545
|
"exec"
|
|
3367
|
-
// restricted shell but with arbitrary command args
|
|
3368
3546
|
]);
|
|
3547
|
+
// Note: hasDangerousCapabilityForSubagents is now the shared helper from capabilities.ts
|
|
3548
|
+
// The old private method was removed in favor of the centralized utility.
|
|
3369
3549
|
/**
|
|
3370
3550
|
* Tools from MCP servers (`mcp__<server>__<tool>`) are external code of
|
|
3371
3551
|
* unknown capability — they may wrap a shell or filesystem. They are
|
|
@@ -3376,12 +3556,16 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
3376
3556
|
return name.startsWith("mcp__");
|
|
3377
3557
|
}
|
|
3378
3558
|
async evaluate(tool) {
|
|
3379
|
-
const
|
|
3380
|
-
|
|
3559
|
+
const hasDangerousCap = hasDangerousCapabilityForSubagents(tool);
|
|
3560
|
+
const legacyNameBlock = _AutoApprovePermissionPolicy.LEGACY_NAME_DENY.has(tool.name);
|
|
3561
|
+
const isMcp = _AutoApprovePermissionPolicy.isMcpTool(tool.name);
|
|
3562
|
+
const blocked = tool.permission === "deny" || hasDangerousCap || legacyNameBlock || isMcp;
|
|
3563
|
+
if (blocked) {
|
|
3564
|
+
const reason = hasDangerousCap ? `tool declares dangerous capability (${tool.capabilities?.join(", ")}) \u2014 not auto-approved for subagents` : legacyNameBlock || isMcp ? `tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow it explicitly` : "tool default deny";
|
|
3381
3565
|
return {
|
|
3382
3566
|
permission: "deny",
|
|
3383
3567
|
source: "subagent_guard",
|
|
3384
|
-
reason
|
|
3568
|
+
reason
|
|
3385
3569
|
};
|
|
3386
3570
|
}
|
|
3387
3571
|
return { permission: "auto", source: "yolo" };
|
|
@@ -3798,80 +3982,85 @@ function parseDescription(raw) {
|
|
|
3798
3982
|
|
|
3799
3983
|
// src/utils/json-repair.ts
|
|
3800
3984
|
function completePartialObject(s) {
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3985
|
+
if (!s.trim().startsWith("{")) return s;
|
|
3986
|
+
if (tryParse(s).ok) return s;
|
|
3987
|
+
const stack = [];
|
|
3988
|
+
let inString = false;
|
|
3989
|
+
let escaped = false;
|
|
3990
|
+
let sawKey = false;
|
|
3991
|
+
let prevSig = "";
|
|
3992
|
+
let contentEnd = 0;
|
|
3993
|
+
let stringBraceDepth = 0;
|
|
3994
|
+
for (let i = 0; i < s.length; i++) {
|
|
3995
|
+
const ch = s[i];
|
|
3996
|
+
if (inString) {
|
|
3997
|
+
contentEnd = i + 1;
|
|
3998
|
+
if (escaped) {
|
|
3999
|
+
escaped = false;
|
|
3812
4000
|
continue;
|
|
3813
4001
|
}
|
|
3814
4002
|
if (ch === "\\") {
|
|
3815
|
-
|
|
4003
|
+
escaped = true;
|
|
3816
4004
|
continue;
|
|
3817
4005
|
}
|
|
3818
4006
|
if (ch === '"') {
|
|
3819
|
-
|
|
4007
|
+
inString = false;
|
|
4008
|
+
prevSig = '"';
|
|
4009
|
+
stringBraceDepth = 0;
|
|
3820
4010
|
continue;
|
|
3821
4011
|
}
|
|
3822
|
-
if (
|
|
3823
|
-
if (ch === "
|
|
3824
|
-
braceDepth++;
|
|
3825
|
-
foundClose = false;
|
|
3826
|
-
} else if (ch === "}") {
|
|
3827
|
-
braceDepth--;
|
|
3828
|
-
if (braceDepth === 0) foundClose = true;
|
|
3829
|
-
}
|
|
3830
|
-
}
|
|
3831
|
-
if (foundClose || braceDepth <= 0) break;
|
|
3832
|
-
result += "}".repeat(braceDepth);
|
|
3833
|
-
}
|
|
3834
|
-
if (tryParse(result).ok) return result;
|
|
3835
|
-
let inString = false;
|
|
3836
|
-
let escaped = false;
|
|
3837
|
-
for (let i = result.length - 1; i >= 0; i--) {
|
|
3838
|
-
const ch = result[i];
|
|
3839
|
-
if (escaped) {
|
|
3840
|
-
escaped = false;
|
|
3841
|
-
continue;
|
|
3842
|
-
}
|
|
3843
|
-
if (ch === "\\") {
|
|
3844
|
-
escaped = true;
|
|
4012
|
+
if (ch === "{") stringBraceDepth++;
|
|
4013
|
+
else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
|
|
3845
4014
|
continue;
|
|
3846
4015
|
}
|
|
4016
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
|
|
4017
|
+
contentEnd = i + 1;
|
|
3847
4018
|
if (ch === '"') {
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
}
|
|
4019
|
+
inString = true;
|
|
4020
|
+
sawKey = true;
|
|
4021
|
+
stringBraceDepth = 0;
|
|
4022
|
+
prevSig = '"';
|
|
4023
|
+
} else if (ch === "{" || ch === "[") {
|
|
4024
|
+
stack.push(ch);
|
|
4025
|
+
prevSig = ch;
|
|
4026
|
+
} else if (ch === "}" || ch === "]") {
|
|
4027
|
+
stack.pop();
|
|
4028
|
+
prevSig = ch;
|
|
4029
|
+
} else {
|
|
4030
|
+
prevSig = ch;
|
|
3861
4031
|
}
|
|
3862
4032
|
}
|
|
4033
|
+
if (!sawKey && !inString) return s;
|
|
4034
|
+
let result = s.slice(0, contentEnd);
|
|
3863
4035
|
if (inString) {
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
if (ch === "{") depth++;
|
|
3869
|
-
else if (ch === "}") depth = Math.max(0, depth - 1);
|
|
4036
|
+
if (escaped) {
|
|
4037
|
+
result = result.slice(0, -1);
|
|
4038
|
+
} else if (endsWithInvalidEscape(result)) {
|
|
4039
|
+
result = result.slice(0, -2);
|
|
3870
4040
|
}
|
|
3871
|
-
result +=
|
|
4041
|
+
if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
|
|
4042
|
+
result += '"';
|
|
4043
|
+
} else if (prevSig === ":") {
|
|
4044
|
+
result += "null";
|
|
4045
|
+
}
|
|
4046
|
+
for (let k = stack.length - 1; k >= 0; k--) {
|
|
4047
|
+
result += stack[k] === "{" ? "}" : "]";
|
|
4048
|
+
}
|
|
4049
|
+
if (!tryParse(result).ok) {
|
|
4050
|
+
const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
|
|
4051
|
+
if (tryParse(patched).ok) result = patched;
|
|
3872
4052
|
}
|
|
3873
4053
|
return result;
|
|
3874
4054
|
}
|
|
4055
|
+
var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
|
|
4056
|
+
function endsWithInvalidEscape(str) {
|
|
4057
|
+
const last = str[str.length - 1];
|
|
4058
|
+
if (str[str.length - 2] !== "\\" || last === void 0) return false;
|
|
4059
|
+
if (VALID_ESCAPE.has(last)) return false;
|
|
4060
|
+
let backslashes = 0;
|
|
4061
|
+
for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
|
|
4062
|
+
return backslashes % 2 === 1;
|
|
4063
|
+
}
|
|
3875
4064
|
function tryParse(s) {
|
|
3876
4065
|
try {
|
|
3877
4066
|
return { ok: true, value: JSON.parse(s) };
|
|
@@ -4281,6 +4470,7 @@ var HybridCompactor = class {
|
|
|
4281
4470
|
}
|
|
4282
4471
|
async compact(ctx, opts = {}) {
|
|
4283
4472
|
const beforeTokens = this.estimateMessages(ctx.messages);
|
|
4473
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
4284
4474
|
const reductions = [];
|
|
4285
4475
|
const policy = readContextWindowPolicy(ctx);
|
|
4286
4476
|
const preserveK = policy?.preserveK ?? this.preserveK;
|
|
@@ -4296,9 +4486,12 @@ var HybridCompactor = class {
|
|
|
4296
4486
|
ctx.state.replaceMessages(repaired.messages);
|
|
4297
4487
|
}
|
|
4298
4488
|
const afterTokens = this.estimateMessages(ctx.messages);
|
|
4489
|
+
const afterFull = this.estimateFullRequest(ctx);
|
|
4299
4490
|
return {
|
|
4300
4491
|
before: beforeTokens,
|
|
4301
4492
|
after: afterTokens,
|
|
4493
|
+
fullRequestTokensBefore: beforeFull,
|
|
4494
|
+
fullRequestTokensAfter: afterFull,
|
|
4302
4495
|
reductions,
|
|
4303
4496
|
repaired: repaired.report.changed ? {
|
|
4304
4497
|
removedToolUses: repaired.report.removedToolUses,
|
|
@@ -4307,6 +4500,14 @@ var HybridCompactor = class {
|
|
|
4307
4500
|
} : void 0
|
|
4308
4501
|
};
|
|
4309
4502
|
}
|
|
4503
|
+
/**
|
|
4504
|
+
* Estimate the full API request token count: messages + systemPrompt + toolDefs.
|
|
4505
|
+
* This is the accurate figure for context-window pressure monitoring.
|
|
4506
|
+
*/
|
|
4507
|
+
estimateFullRequest(ctx) {
|
|
4508
|
+
const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
|
|
4509
|
+
return breakdown.total;
|
|
4510
|
+
}
|
|
4310
4511
|
eliseOldToolResults(ctx, preserveK = this.preserveK, eliseThreshold = this.eliseThreshold) {
|
|
4311
4512
|
const messages = ctx.messages;
|
|
4312
4513
|
let pairCount = 0;
|
|
@@ -4452,8 +4653,9 @@ var IntelligentCompactor = class {
|
|
|
4452
4653
|
}
|
|
4453
4654
|
async compact(ctx, opts = {}) {
|
|
4454
4655
|
const beforeTokens = this.estimateTokens(ctx.messages);
|
|
4656
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
4455
4657
|
const reductions = [];
|
|
4456
|
-
const load =
|
|
4658
|
+
const load = beforeFull / this.maxContext;
|
|
4457
4659
|
const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
|
|
4458
4660
|
const saved1 = this.eliseOldToolResults(ctx);
|
|
4459
4661
|
if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
|
|
@@ -4467,9 +4669,12 @@ var IntelligentCompactor = class {
|
|
|
4467
4669
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
4468
4670
|
if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
|
|
4469
4671
|
const afterTokens = this.estimateTokens(ctx.messages);
|
|
4672
|
+
const afterFull = this.estimateFullRequest(ctx);
|
|
4470
4673
|
return {
|
|
4471
4674
|
before: beforeTokens,
|
|
4472
4675
|
after: afterTokens,
|
|
4676
|
+
fullRequestTokensBefore: beforeFull,
|
|
4677
|
+
fullRequestTokensAfter: afterFull,
|
|
4473
4678
|
reductions,
|
|
4474
4679
|
repaired: repaired.report.changed ? {
|
|
4475
4680
|
removedToolUses: repaired.report.removedToolUses,
|
|
@@ -4478,6 +4683,14 @@ var IntelligentCompactor = class {
|
|
|
4478
4683
|
} : void 0
|
|
4479
4684
|
};
|
|
4480
4685
|
}
|
|
4686
|
+
/**
|
|
4687
|
+
* Estimate the full API request token count: messages + systemPrompt + toolDefs.
|
|
4688
|
+
* This is the accurate figure for context-window pressure monitoring.
|
|
4689
|
+
*/
|
|
4690
|
+
estimateFullRequest(ctx) {
|
|
4691
|
+
const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
|
|
4692
|
+
return breakdown.total;
|
|
4693
|
+
}
|
|
4481
4694
|
async summarizeAncientTurns(ctx) {
|
|
4482
4695
|
const messages = ctx.messages;
|
|
4483
4696
|
const cutoff = Math.max(0, messages.length - this.preserveK * 2);
|
|
@@ -4490,29 +4703,26 @@ var IntelligentCompactor = class {
|
|
|
4490
4703
|
try {
|
|
4491
4704
|
summaryText = await this.callSummarizer(toSummarize, ctx);
|
|
4492
4705
|
} catch {
|
|
4493
|
-
const
|
|
4494
|
-
const filePaths = /* @__PURE__ */ new Set();
|
|
4495
|
-
let userTurns = 0;
|
|
4496
|
-
let assistantTurns = 0;
|
|
4706
|
+
const preservedMessages = [];
|
|
4497
4707
|
for (const m of toSummarize) {
|
|
4498
|
-
if (m.role === "
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4708
|
+
if (m.role === "system") continue;
|
|
4709
|
+
if (typeof m.content === "string") {
|
|
4710
|
+
preservedMessages.push(m);
|
|
4711
|
+
} else if (Array.isArray(m.content)) {
|
|
4712
|
+
const textParts = m.content.filter(isTextBlock);
|
|
4713
|
+
const toolParts = m.content.filter((b) => b.type === "tool_use" || b.type === "tool_result");
|
|
4714
|
+
const next = {
|
|
4715
|
+
role: m.role,
|
|
4716
|
+
content: [
|
|
4717
|
+
...textParts,
|
|
4718
|
+
...toolParts.length > 0 ? [{ type: "text", text: `[${toolParts.length} tool call(s) omitted]` }] : []
|
|
4719
|
+
]
|
|
4720
|
+
};
|
|
4721
|
+
preservedMessages.push(next);
|
|
4506
4722
|
}
|
|
4507
|
-
const text = typeof m.content === "string" ? m.content : "";
|
|
4508
|
-
const matches = text.matchAll(/(?:[\w.,\-/@]+\/)*[\w.,\-/@]+\.\w+/g);
|
|
4509
|
-
for (const m_ of matches) filePaths.add(m_[0]);
|
|
4510
4723
|
}
|
|
4511
|
-
|
|
4512
|
-
if (
|
|
4513
|
-
if (filePaths.size > 0) parts.push(`files: ${[...filePaths].slice(0, 10).join(", ")}`);
|
|
4514
|
-
summaryText = parts.join(" | ");
|
|
4515
|
-
if (!summaryText) summaryText = `${toSummarize.length} earlier turns omitted`;
|
|
4724
|
+
summaryText = preservedMessages.map((m) => `[${m.role}]: ${typeof m.content === "string" ? m.content : m.content.filter(isTextBlock).map((b) => b.text).join(" ")}`).join("\n");
|
|
4725
|
+
if (!summaryText) summaryText = `${toSummarize.length} earlier turns (semantic content preserved)`;
|
|
4516
4726
|
}
|
|
4517
4727
|
const summaryMsg = {
|
|
4518
4728
|
role: "system",
|
|
@@ -4758,7 +4968,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
4758
4968
|
const res = await this.provider.complete(req, { signal: ac.signal });
|
|
4759
4969
|
const textBlocks = res.content.filter(isTextBlock);
|
|
4760
4970
|
raw = textBlocks.map((b) => b.text).join("\n").trim();
|
|
4761
|
-
} catch (
|
|
4971
|
+
} catch (_err) {
|
|
4762
4972
|
return this.fallbackSelect(messages, effectiveBudget);
|
|
4763
4973
|
}
|
|
4764
4974
|
return this.parseSelectorOutput(raw, messages.length);
|
|
@@ -4850,15 +5060,24 @@ var SelectiveCompactor = class {
|
|
|
4850
5060
|
}
|
|
4851
5061
|
async compact(ctx, opts = {}) {
|
|
4852
5062
|
const beforeTokens = this.estimateTokens(ctx.messages);
|
|
5063
|
+
const beforeFull = this.estimateFullRequest(ctx);
|
|
4853
5064
|
const reductions = [];
|
|
4854
|
-
const load =
|
|
5065
|
+
const load = beforeFull / this.maxContext;
|
|
4855
5066
|
const shouldCompact = load >= this.warnThreshold || opts.aggressive;
|
|
4856
5067
|
if (!shouldCompact) {
|
|
4857
5068
|
const saved = this.eliseOldToolResults(ctx);
|
|
4858
5069
|
if (saved > 0) reductions.push({ phase: "elision", saved });
|
|
4859
5070
|
const repair2 = this.repairProtocolAdjacency(ctx);
|
|
4860
5071
|
const afterTokens2 = this.estimateTokens(ctx.messages);
|
|
4861
|
-
|
|
5072
|
+
const afterFull2 = this.estimateFullRequest(ctx);
|
|
5073
|
+
return {
|
|
5074
|
+
before: beforeTokens,
|
|
5075
|
+
after: afterTokens2,
|
|
5076
|
+
fullRequestTokensBefore: beforeFull,
|
|
5077
|
+
fullRequestTokensAfter: afterFull2,
|
|
5078
|
+
reductions,
|
|
5079
|
+
repaired: repair2
|
|
5080
|
+
};
|
|
4862
5081
|
}
|
|
4863
5082
|
const savedElision = this.eliseOldToolResults(ctx);
|
|
4864
5083
|
if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
|
|
@@ -4870,7 +5089,23 @@ var SelectiveCompactor = class {
|
|
|
4870
5089
|
}
|
|
4871
5090
|
const repair = this.repairProtocolAdjacency(ctx);
|
|
4872
5091
|
const afterTokens = this.estimateTokens(ctx.messages);
|
|
4873
|
-
|
|
5092
|
+
const afterFull = this.estimateFullRequest(ctx);
|
|
5093
|
+
return {
|
|
5094
|
+
before: beforeTokens,
|
|
5095
|
+
after: afterTokens,
|
|
5096
|
+
fullRequestTokensBefore: beforeFull,
|
|
5097
|
+
fullRequestTokensAfter: afterFull,
|
|
5098
|
+
reductions,
|
|
5099
|
+
repaired: repair
|
|
5100
|
+
};
|
|
5101
|
+
}
|
|
5102
|
+
/**
|
|
5103
|
+
* Estimate the full API request token count: messages + systemPrompt + toolDefs.
|
|
5104
|
+
* This is the accurate figure for context-window pressure monitoring.
|
|
5105
|
+
*/
|
|
5106
|
+
estimateFullRequest(ctx) {
|
|
5107
|
+
const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
|
|
5108
|
+
return breakdown.total;
|
|
4874
5109
|
}
|
|
4875
5110
|
repairProtocolAdjacency(ctx) {
|
|
4876
5111
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
@@ -5060,7 +5295,8 @@ var LEVEL_RANK2 = { warn: 0, soft: 1, hard: 2 };
|
|
|
5060
5295
|
var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
5061
5296
|
name = "AutoCompaction";
|
|
5062
5297
|
compactor;
|
|
5063
|
-
|
|
5298
|
+
/** Deprecated. Kept for backward compat with tests that pass simpleEstimator. */
|
|
5299
|
+
_estimator;
|
|
5064
5300
|
warnThreshold;
|
|
5065
5301
|
softThreshold;
|
|
5066
5302
|
hardThreshold;
|
|
@@ -5070,19 +5306,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
5070
5306
|
events;
|
|
5071
5307
|
failureMode;
|
|
5072
5308
|
policyProvider;
|
|
5073
|
-
|
|
5074
|
-
* Calibration factor applied to the estimator output. The estimator is now
|
|
5075
|
-
* expected to already include messages + system prompt + tool definitions
|
|
5076
|
-
* (see `estimateRequestTokens.total`), so no overhead boost is needed. Kept
|
|
5077
|
-
* as a constant so a future calibration against real provider tokenization
|
|
5078
|
-
* (BPE vs the chars/3.5 rough estimator) can dial this without touching the
|
|
5079
|
-
* threshold-check math.
|
|
5080
|
-
*
|
|
5081
|
-
* Historical note: was 1.3 when the estimator only counted messages — that
|
|
5082
|
-
* double-counted system+tools once the estimator was upgraded, firing
|
|
5083
|
-
* compaction ~30% earlier than intended (e.g. real 56% load showed as 73%).
|
|
5084
|
-
*/
|
|
5085
|
-
static OVERHEAD_FACTOR = 1;
|
|
5309
|
+
sessionBridge;
|
|
5086
5310
|
/**
|
|
5087
5311
|
* Once a compaction attempt reduces nothing (preserveK protects everything,
|
|
5088
5312
|
* no oversized tool_results remain to elide), retrying on every iteration
|
|
@@ -5095,8 +5319,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
5095
5319
|
lastNoopAttempt = null;
|
|
5096
5320
|
/**
|
|
5097
5321
|
* @param compactor Compactor to use for compaction.
|
|
5098
|
-
* @param maxContext
|
|
5099
|
-
* @param
|
|
5322
|
+
* @param maxContext Provider's max context window in tokens.
|
|
5323
|
+
* @param _estimator Deprecated parameter kept for backward compatibility.
|
|
5324
|
+
* The middleware now uses `estimateRequestTokens` internally
|
|
5325
|
+
* for accurate full-request token counting (messages +
|
|
5326
|
+
* systemPrompt + toolDefs).
|
|
5100
5327
|
* @param thresholds Threshold fractions (0-1) of maxContext.
|
|
5101
5328
|
* @param opts Optional behavior. By default, failures at the
|
|
5102
5329
|
* hard threshold throw AGENT_CONTEXT_OVERFLOW so
|
|
@@ -5104,11 +5331,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
5104
5331
|
* provider context overflow. Warn/soft failures
|
|
5105
5332
|
* still emit compaction.failed and continue.
|
|
5106
5333
|
*/
|
|
5107
|
-
constructor(compactor, maxContext,
|
|
5334
|
+
constructor(compactor, maxContext, _estimator, thresholds, optsOrAggressiveOn = {}, events) {
|
|
5108
5335
|
const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
|
|
5109
5336
|
this.compactor = compactor;
|
|
5110
5337
|
this._maxContext = maxContext;
|
|
5111
|
-
this.
|
|
5338
|
+
this._estimator = _estimator;
|
|
5112
5339
|
this.warnThreshold = thresholds.warn;
|
|
5113
5340
|
this.softThreshold = thresholds.soft;
|
|
5114
5341
|
this.hardThreshold = thresholds.hard;
|
|
@@ -5116,6 +5343,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
5116
5343
|
this.events = opts.events;
|
|
5117
5344
|
this.failureMode = opts.failureMode ?? "throw_on_hard";
|
|
5118
5345
|
this.policyProvider = opts.policyProvider;
|
|
5346
|
+
this.sessionBridge = opts.sessionBridge;
|
|
5119
5347
|
}
|
|
5120
5348
|
/** Allow callers (e.g. model-switch in WebUI) to update the context window
|
|
5121
5349
|
* denominator when the active model changes. */
|
|
@@ -5124,8 +5352,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
5124
5352
|
}
|
|
5125
5353
|
handler() {
|
|
5126
5354
|
return async (ctx, next) => {
|
|
5127
|
-
const
|
|
5128
|
-
const tokens = Math.ceil(rawTokens * _AutoCompactionMiddleware.OVERHEAD_FACTOR);
|
|
5355
|
+
const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
|
|
5129
5356
|
const load = tokens / this._maxContext;
|
|
5130
5357
|
const policy = this.policyProvider?.(ctx);
|
|
5131
5358
|
const thresholds = policy?.thresholds ?? {
|
|
@@ -5160,7 +5387,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
5160
5387
|
return delta < _AutoCompactionMiddleware.NOOP_RETRY_DELTA_TOKENS;
|
|
5161
5388
|
}
|
|
5162
5389
|
recordAttempt(level, tokens, report) {
|
|
5163
|
-
const reduced = report.before > report.after;
|
|
5390
|
+
const reduced = (report.fullRequestTokensBefore ?? report.before) > (report.fullRequestTokensAfter ?? report.after);
|
|
5164
5391
|
const repaired = !!report.repaired;
|
|
5165
5392
|
if (reduced || repaired) {
|
|
5166
5393
|
this.lastNoopAttempt = null;
|
|
@@ -5180,6 +5407,15 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
5180
5407
|
report,
|
|
5181
5408
|
aggressive
|
|
5182
5409
|
});
|
|
5410
|
+
await this.sessionBridge?.append({
|
|
5411
|
+
type: "compaction",
|
|
5412
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5413
|
+
before: report.before,
|
|
5414
|
+
after: report.after,
|
|
5415
|
+
level: pressure.level,
|
|
5416
|
+
aggressive,
|
|
5417
|
+
reductions: report.reductions?.map((r) => ({ phase: r.phase, saved: r.saved }))
|
|
5418
|
+
});
|
|
5183
5419
|
ctx.clearFileTracking();
|
|
5184
5420
|
} catch (err) {
|
|
5185
5421
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -5210,6 +5446,100 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
5210
5446
|
}
|
|
5211
5447
|
};
|
|
5212
5448
|
|
|
5449
|
+
// src/utils/json-schema-validate.ts
|
|
5450
|
+
function validateAgainstSchema(value, schema) {
|
|
5451
|
+
const errors = [];
|
|
5452
|
+
walk3(value, schema, "", errors);
|
|
5453
|
+
return { ok: errors.length === 0, errors };
|
|
5454
|
+
}
|
|
5455
|
+
function walk3(value, schema, path18, errors) {
|
|
5456
|
+
if (schema.enum !== void 0) {
|
|
5457
|
+
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
5458
|
+
errors.push({
|
|
5459
|
+
path: path18 || "<root>",
|
|
5460
|
+
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
5461
|
+
});
|
|
5462
|
+
return;
|
|
5463
|
+
}
|
|
5464
|
+
}
|
|
5465
|
+
if (typeof schema.type === "string") {
|
|
5466
|
+
if (!checkType(value, schema.type)) {
|
|
5467
|
+
errors.push({
|
|
5468
|
+
path: path18 || "<root>",
|
|
5469
|
+
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
5470
|
+
});
|
|
5471
|
+
return;
|
|
5472
|
+
}
|
|
5473
|
+
}
|
|
5474
|
+
if (schema.type === "object" && isPlainObject(value)) {
|
|
5475
|
+
const obj = value;
|
|
5476
|
+
for (const req of schema.required ?? []) {
|
|
5477
|
+
if (!(req in obj)) {
|
|
5478
|
+
errors.push({ path: joinPath(path18, req), message: "required property missing" });
|
|
5479
|
+
}
|
|
5480
|
+
}
|
|
5481
|
+
if (schema.properties) {
|
|
5482
|
+
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
5483
|
+
if (key in obj) {
|
|
5484
|
+
walk3(obj[key], subSchema, joinPath(path18, key), errors);
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
5488
|
+
}
|
|
5489
|
+
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
5490
|
+
value.forEach((item, i) => walk3(item, schema.items, `${path18}[${i}]`, errors));
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
5493
|
+
function checkType(value, type) {
|
|
5494
|
+
switch (type) {
|
|
5495
|
+
case "string":
|
|
5496
|
+
return typeof value === "string";
|
|
5497
|
+
case "number":
|
|
5498
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
5499
|
+
case "integer":
|
|
5500
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
5501
|
+
case "boolean":
|
|
5502
|
+
return typeof value === "boolean";
|
|
5503
|
+
case "null":
|
|
5504
|
+
return value === null;
|
|
5505
|
+
case "array":
|
|
5506
|
+
return Array.isArray(value);
|
|
5507
|
+
case "object":
|
|
5508
|
+
return isPlainObject(value);
|
|
5509
|
+
default:
|
|
5510
|
+
return true;
|
|
5511
|
+
}
|
|
5512
|
+
}
|
|
5513
|
+
function isPlainObject(v) {
|
|
5514
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5515
|
+
}
|
|
5516
|
+
function describeType(v) {
|
|
5517
|
+
if (v === null) return "null";
|
|
5518
|
+
if (Array.isArray(v)) return "array";
|
|
5519
|
+
return typeof v;
|
|
5520
|
+
}
|
|
5521
|
+
function joinPath(parent, key) {
|
|
5522
|
+
if (!parent) return key;
|
|
5523
|
+
return `${parent}.${key}`;
|
|
5524
|
+
}
|
|
5525
|
+
function deepEqual(a, b) {
|
|
5526
|
+
if (a === b) return true;
|
|
5527
|
+
if (typeof a !== typeof b) return false;
|
|
5528
|
+
if (a === null || b === null) return a === b;
|
|
5529
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
5530
|
+
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
5531
|
+
}
|
|
5532
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
5533
|
+
const ak = Object.keys(a);
|
|
5534
|
+
const bk = Object.keys(b);
|
|
5535
|
+
if (ak.length !== bk.length) return false;
|
|
5536
|
+
return ak.every(
|
|
5537
|
+
(k) => deepEqual(a[k], b[k])
|
|
5538
|
+
);
|
|
5539
|
+
}
|
|
5540
|
+
return false;
|
|
5541
|
+
}
|
|
5542
|
+
|
|
5213
5543
|
// src/utils/tool-output-serializer.ts
|
|
5214
5544
|
function createToolOutputSerializer(opts = {}) {
|
|
5215
5545
|
const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
|
|
@@ -5291,18 +5621,45 @@ var ToolExecutor = class {
|
|
|
5291
5621
|
budget = this.decrementBudget(result, budget);
|
|
5292
5622
|
return { result, tool, durationMs: Date.now() - start };
|
|
5293
5623
|
}
|
|
5624
|
+
const validation = validateAgainstSchema(use.input, tool.inputSchema);
|
|
5625
|
+
if (!validation.ok) {
|
|
5626
|
+
const errorDetails = validation.errors.map((e) => ` - ${e.path || "input"}: ${e.message}`).join("\n");
|
|
5627
|
+
const result = {
|
|
5628
|
+
type: "tool_result",
|
|
5629
|
+
tool_use_id: use.id,
|
|
5630
|
+
content: `Invalid arguments for tool "${tool.name}".
|
|
5631
|
+
|
|
5632
|
+
Validation errors:
|
|
5633
|
+
${errorDetails}
|
|
5634
|
+
|
|
5635
|
+
Please call the tool again with arguments that match its inputSchema. You can use the "tool-help" tool with name="${tool.name}" to see the exact expected schema.`,
|
|
5636
|
+
is_error: true
|
|
5637
|
+
};
|
|
5638
|
+
budget = this.decrementBudget(result, budget);
|
|
5639
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
5640
|
+
}
|
|
5641
|
+
const toolDangerousCaps = getDangerousCapabilities(tool);
|
|
5642
|
+
if (toolDangerousCaps.length > 0) {
|
|
5643
|
+
if (this.opts.events) ;
|
|
5644
|
+
}
|
|
5294
5645
|
if (hasMalformedArguments(use.input)) {
|
|
5295
5646
|
const result = this.malformedInputResult(use, extractMalformedRaw(use.input));
|
|
5296
5647
|
budget = this.decrementBudget(result, budget);
|
|
5297
5648
|
return { result, tool, durationMs: Date.now() - start };
|
|
5298
5649
|
}
|
|
5299
5650
|
const decision = await this.opts.permissionPolicy.evaluate(tool, use.input, ctx);
|
|
5300
|
-
|
|
5651
|
+
let effectivePermission = decision.permission;
|
|
5652
|
+
const policy = this.opts.permissionPolicy;
|
|
5653
|
+
const yolo = policy.getYolo?.() === true || policy.getForceAllYolo?.() === true;
|
|
5654
|
+
if (toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo) {
|
|
5655
|
+
effectivePermission = "confirm";
|
|
5656
|
+
}
|
|
5657
|
+
if (effectivePermission === "deny") {
|
|
5301
5658
|
const result = this.deniedResult(use, decision.reason);
|
|
5302
5659
|
budget = this.decrementBudget(result, budget);
|
|
5303
5660
|
return { result, tool, durationMs: Date.now() - start };
|
|
5304
5661
|
}
|
|
5305
|
-
if (
|
|
5662
|
+
if (effectivePermission === "confirm") {
|
|
5306
5663
|
if (this.opts.confirmAwaiter) {
|
|
5307
5664
|
const choice = await this.opts.confirmAwaiter(tool, use.input, use.id, tool.name);
|
|
5308
5665
|
if (choice !== "yes" && choice !== "always") {
|
|
@@ -5327,10 +5684,13 @@ var ToolExecutor = class {
|
|
|
5327
5684
|
return { result: pending, tool, durationMs: Date.now() - start };
|
|
5328
5685
|
}
|
|
5329
5686
|
}
|
|
5687
|
+
const toolCapsForAudit = hasDangerousCapabilityForSubagents(tool) ? tool.capabilities ?? [] : [];
|
|
5330
5688
|
const span = this.opts.tracer?.startSpan(`tool.${tool.name}`, {
|
|
5331
5689
|
"tool.name": tool.name,
|
|
5332
5690
|
"tool.mutating": tool.mutating,
|
|
5333
|
-
"tool.permission": tool.permission
|
|
5691
|
+
"tool.permission": tool.permission,
|
|
5692
|
+
"tool.capabilities": JSON.stringify(tool.capabilities ?? []),
|
|
5693
|
+
"tool.has_dangerous_capabilities": toolCapsForAudit.length > 0
|
|
5334
5694
|
});
|
|
5335
5695
|
try {
|
|
5336
5696
|
const result = await this.executeTool(tool, use, ctx, budget);
|
|
@@ -6403,19 +6763,16 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
6403
6763
|
tokenOutput = 0;
|
|
6404
6764
|
costUsd = 0;
|
|
6405
6765
|
startTime = null;
|
|
6406
|
-
_onThreshold;
|
|
6407
6766
|
/**
|
|
6408
|
-
*
|
|
6409
|
-
*
|
|
6410
|
-
*
|
|
6411
|
-
*
|
|
6412
|
-
* the first one lands and patches limits), flooding the FleetBus
|
|
6413
|
-
* with redundant threshold events. Cleared in `negotiateExtension`'s
|
|
6414
|
-
* `finally`.
|
|
6767
|
+
* Timestamp of the most recent activity (iteration / tool call / token
|
|
6768
|
+
* usage / streamed progress). Drives the idle timeout — reset by
|
|
6769
|
+
* `markActivity()`. Initialised to `start()` time so a never-active agent
|
|
6770
|
+
* still eventually trips its idle window.
|
|
6415
6771
|
*/
|
|
6416
|
-
|
|
6772
|
+
lastActivityTime = null;
|
|
6773
|
+
_onThreshold;
|
|
6417
6774
|
/**
|
|
6418
|
-
* Hard cap on how long `
|
|
6775
|
+
* Hard cap on how long `_negotiateExtension` waits for the coordinator to
|
|
6419
6776
|
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
6420
6777
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
6421
6778
|
* leaves the budget over-limit and never enforces anything.
|
|
@@ -6456,6 +6813,24 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
6456
6813
|
}
|
|
6457
6814
|
start() {
|
|
6458
6815
|
this.startTime = Date.now();
|
|
6816
|
+
this.lastActivityTime = this.startTime;
|
|
6817
|
+
}
|
|
6818
|
+
/**
|
|
6819
|
+
* Reset the idle clock. Called on any sign of forward progress —
|
|
6820
|
+
* iterations, tool calls, token usage, and streamed tool/text progress —
|
|
6821
|
+
* so a long-but-productive subagent never trips its `idleTimeoutMs`.
|
|
6822
|
+
*/
|
|
6823
|
+
markActivity() {
|
|
6824
|
+
this.lastActivityTime = Date.now();
|
|
6825
|
+
}
|
|
6826
|
+
/**
|
|
6827
|
+
* Milliseconds since the last activity signal. Returns 0 before `start()`
|
|
6828
|
+
* (nothing to measure yet). Used by the coordinator watchdog to decide
|
|
6829
|
+
* whether to re-arm (still active) or reap (genuinely idle).
|
|
6830
|
+
*/
|
|
6831
|
+
idleMs() {
|
|
6832
|
+
const since = this.lastActivityTime ?? this.startTime;
|
|
6833
|
+
return since === null ? 0 : Date.now() - since;
|
|
6459
6834
|
}
|
|
6460
6835
|
/** Returns true if we're within 10% of any limit — useful for pre-flight checks. */
|
|
6461
6836
|
isNearLimit() {
|
|
@@ -6477,38 +6852,88 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
6477
6852
|
* - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
|
|
6478
6853
|
* - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
|
|
6479
6854
|
*/
|
|
6480
|
-
|
|
6855
|
+
/**
|
|
6856
|
+
* Collects all exceeded budget kinds into a single NOOP-free negotiation.
|
|
6857
|
+
* Called by recordIteration / recordToolCall / recordUsage — each may call
|
|
6858
|
+
* this for its own kind. The first call starts the negotiation and stores
|
|
6859
|
+
* the Promise in _pendingNegotiation. Subsequent calls for DIFFERENT
|
|
6860
|
+
* kinds (while a negotiation is in flight) are NOOPs — they don't start
|
|
6861
|
+
* new conversations with the coordinator. This prevents an EventBus flood
|
|
6862
|
+
* when multiple budget kinds are exceeded simultaneously in one iteration.
|
|
6863
|
+
*
|
|
6864
|
+
* Returns the kinds that were found to be exceeded (for logging/debugging).
|
|
6865
|
+
*/
|
|
6866
|
+
checkLimits(elapsedMs) {
|
|
6867
|
+
const exceeded = [];
|
|
6868
|
+
if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
|
|
6869
|
+
exceeded.push({ kind: "iterations", used: this.iterations, limit: this.limits.maxIterations });
|
|
6870
|
+
}
|
|
6871
|
+
if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
|
|
6872
|
+
exceeded.push({ kind: "tool_calls", used: this.toolCalls, limit: this.limits.maxToolCalls });
|
|
6873
|
+
}
|
|
6874
|
+
const totalTokens = this.tokenInput + this.tokenOutput;
|
|
6875
|
+
if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
|
|
6876
|
+
exceeded.push({ kind: "tokens", used: totalTokens, limit: this.limits.maxTokens });
|
|
6877
|
+
}
|
|
6878
|
+
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
6879
|
+
exceeded.push({ kind: "cost", used: this.costUsd, limit: this.limits.maxCostUsd });
|
|
6880
|
+
}
|
|
6881
|
+
if (elapsedMs !== void 0) {
|
|
6882
|
+
const idle = this.idleMs();
|
|
6883
|
+
if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
|
|
6884
|
+
exceeded.push({ kind: "timeout", used: idle, limit: this.limits.idleTimeoutMs });
|
|
6885
|
+
} else if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
|
|
6886
|
+
exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
|
|
6887
|
+
}
|
|
6888
|
+
}
|
|
6889
|
+
if (exceeded.length === 0) return [];
|
|
6481
6890
|
if (!this._onThreshold) {
|
|
6482
|
-
|
|
6891
|
+
const first2 = exceeded[0];
|
|
6892
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
6483
6893
|
}
|
|
6484
6894
|
if (this._mode === "sync") {
|
|
6485
|
-
|
|
6895
|
+
const first2 = exceeded[0];
|
|
6896
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
6486
6897
|
}
|
|
6487
6898
|
const bus = this._events;
|
|
6488
6899
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
6489
|
-
|
|
6900
|
+
const first2 = exceeded[0];
|
|
6901
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
6490
6902
|
}
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6903
|
+
for (const entry of exceeded) {
|
|
6904
|
+
if (this._pendingNegotiations.has(entry.kind)) continue;
|
|
6905
|
+
const decision2 = this._negotiateExtension(entry.kind, exceeded);
|
|
6906
|
+
this._pendingNegotiations.set(entry.kind, decision2);
|
|
6907
|
+
}
|
|
6908
|
+
const first = exceeded[0];
|
|
6909
|
+
const decision = this._pendingNegotiations.get(first.kind);
|
|
6910
|
+
throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
|
|
6495
6911
|
}
|
|
6912
|
+
/**
|
|
6913
|
+
* Per-kind in-flight negotiation Promises. Each budget kind can have its
|
|
6914
|
+
* own concurrent negotiation — e.g. iterations and timeout can both
|
|
6915
|
+
* be exceeded simultaneously without blocking each other. The same kind
|
|
6916
|
+
* cannot start two concurrent negotiations (dedup guard).
|
|
6917
|
+
* Cleared in `_negotiateExtension`'s `finally` block.
|
|
6918
|
+
*/
|
|
6919
|
+
_pendingNegotiations = /* @__PURE__ */ new Map();
|
|
6496
6920
|
/**
|
|
6497
6921
|
* Drive the threshold handler to a decision. Resolves with `'stop'`
|
|
6498
6922
|
* (signal the runner to abort) or `{ extend: ... }` (limits already
|
|
6499
|
-
* patched in-place; the runner should not abort).
|
|
6500
|
-
*
|
|
6923
|
+
* patched in-place; the runner should not abort). Clears the
|
|
6924
|
+
* per-kind slot in `_pendingNegotiations` in `finally`.
|
|
6501
6925
|
*
|
|
6502
6926
|
* The 'continue' return from a sync handler is treated as
|
|
6503
6927
|
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
6504
6928
|
* a fresh signal.
|
|
6505
6929
|
*/
|
|
6506
|
-
async
|
|
6930
|
+
async _negotiateExtension(kind, exceeded) {
|
|
6507
6931
|
try {
|
|
6932
|
+
const first = exceeded[0];
|
|
6508
6933
|
const result = this._onThreshold({
|
|
6509
|
-
kind,
|
|
6510
|
-
used,
|
|
6511
|
-
limit,
|
|
6934
|
+
kind: first.kind,
|
|
6935
|
+
used: first.used,
|
|
6936
|
+
limit: first.limit,
|
|
6512
6937
|
requestDecision: () => {
|
|
6513
6938
|
const bus = this._events;
|
|
6514
6939
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
@@ -6525,20 +6950,22 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
6525
6950
|
() => respond("stop"),
|
|
6526
6951
|
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
6527
6952
|
);
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6953
|
+
for (const { kind: kind2, used, limit } of exceeded) {
|
|
6954
|
+
bus.emit("budget.threshold_reached", {
|
|
6955
|
+
kind: kind2,
|
|
6956
|
+
used,
|
|
6957
|
+
limit,
|
|
6958
|
+
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
6959
|
+
extend: (extra) => {
|
|
6960
|
+
clearTimeout(fallback);
|
|
6961
|
+
respond({ extend: extra });
|
|
6962
|
+
},
|
|
6963
|
+
deny: () => {
|
|
6964
|
+
clearTimeout(fallback);
|
|
6965
|
+
respond("stop");
|
|
6966
|
+
}
|
|
6967
|
+
});
|
|
6968
|
+
}
|
|
6542
6969
|
});
|
|
6543
6970
|
}
|
|
6544
6971
|
});
|
|
@@ -6562,54 +6989,59 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
6562
6989
|
if (ext.timeoutMs !== void 0) {
|
|
6563
6990
|
this.limits.timeoutMs = ext.timeoutMs;
|
|
6564
6991
|
}
|
|
6992
|
+
if (ext.idleTimeoutMs !== void 0) {
|
|
6993
|
+
this.limits.idleTimeoutMs = ext.idleTimeoutMs;
|
|
6994
|
+
}
|
|
6565
6995
|
return decision;
|
|
6566
6996
|
} finally {
|
|
6567
|
-
this.
|
|
6997
|
+
this._pendingNegotiations.delete(kind);
|
|
6568
6998
|
}
|
|
6569
6999
|
}
|
|
6570
7000
|
recordIteration() {
|
|
6571
7001
|
this.iterations++;
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
}
|
|
7002
|
+
this.markActivity();
|
|
7003
|
+
void this.checkLimits();
|
|
6575
7004
|
}
|
|
6576
7005
|
recordToolCall() {
|
|
6577
7006
|
this.toolCalls++;
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
}
|
|
7007
|
+
this.markActivity();
|
|
7008
|
+
void this.checkLimits();
|
|
6581
7009
|
}
|
|
6582
7010
|
recordUsage(usage, costUsd = 0) {
|
|
6583
7011
|
this.tokenInput += usage.input;
|
|
6584
7012
|
this.tokenOutput += usage.output;
|
|
6585
7013
|
this.costUsd += costUsd;
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
|
|
6589
|
-
}
|
|
6590
|
-
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
6591
|
-
void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
|
|
6592
|
-
}
|
|
7014
|
+
this.markActivity();
|
|
7015
|
+
void this.checkLimits();
|
|
6593
7016
|
}
|
|
6594
7017
|
/**
|
|
6595
|
-
* Wall-clock budget check. Unlike other limits, timeout
|
|
6596
|
-
*
|
|
6597
|
-
*
|
|
6598
|
-
*
|
|
6599
|
-
*
|
|
6600
|
-
*
|
|
7018
|
+
* Wall-clock budget check. Unlike other limits, timeout is always a hard stop
|
|
7019
|
+
* — wall-clock time cannot be "extended" by the coordinator, so it throws
|
|
7020
|
+
* synchronously rather than entering the negotiation flow.
|
|
7021
|
+
*
|
|
7022
|
+
* Decision table:
|
|
7023
|
+
* - no `onThreshold` handler → throw `BudgetExceededError`
|
|
7024
|
+
* - `mode === 'sync'` → throw `BudgetExceededError`
|
|
7025
|
+
* - `mode === 'auto'` + no listener → throw `BudgetExceededError`
|
|
7026
|
+
* - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
|
|
6601
7027
|
*/
|
|
6602
7028
|
checkTimeout() {
|
|
6603
|
-
if (this.startTime === null
|
|
7029
|
+
if (this.startTime === null) return;
|
|
7030
|
+
const { timeoutMs, idleTimeoutMs } = this.limits;
|
|
7031
|
+
if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
|
|
6604
7032
|
const elapsed = Date.now() - this.startTime;
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
7033
|
+
const wallTripped = timeoutMs !== void 0 && elapsed > timeoutMs;
|
|
7034
|
+
const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
|
|
7035
|
+
if (!wallTripped && !idleTripped) return;
|
|
7036
|
+
void this.checkLimits(elapsed);
|
|
6608
7037
|
}
|
|
6609
|
-
/** Returns true if a timeout has occurred without throwing.
|
|
7038
|
+
/** Returns true if a wall-clock or idle timeout has occurred without throwing. */
|
|
6610
7039
|
isTimedOut() {
|
|
6611
|
-
if (this.startTime === null
|
|
6612
|
-
|
|
7040
|
+
if (this.startTime === null) return false;
|
|
7041
|
+
const { timeoutMs, idleTimeoutMs } = this.limits;
|
|
7042
|
+
if (timeoutMs !== void 0 && Date.now() - this.startTime > timeoutMs) return true;
|
|
7043
|
+
if (idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs) return true;
|
|
7044
|
+
return false;
|
|
6613
7045
|
}
|
|
6614
7046
|
usage() {
|
|
6615
7047
|
return {
|
|
@@ -6716,6 +7148,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
6716
7148
|
// Cheap: O(1) per progress event, and the budget short-circuits
|
|
6717
7149
|
// when timeoutMs is unset (most subagents have one set anyway).
|
|
6718
7150
|
events.on("tool.progress", () => {
|
|
7151
|
+
ctx.budget.markActivity();
|
|
6719
7152
|
try {
|
|
6720
7153
|
ctx.budget.checkTimeout();
|
|
6721
7154
|
} catch (e) {
|
|
@@ -6732,6 +7165,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
6732
7165
|
currentToolName = e.name;
|
|
6733
7166
|
}),
|
|
6734
7167
|
events.on("provider.text_delta", (e) => {
|
|
7168
|
+
ctx.budget.markActivity();
|
|
6735
7169
|
streamingTextAcc = (streamingTextAcc + e.text).slice(-200);
|
|
6736
7170
|
})
|
|
6737
7171
|
);
|
|
@@ -9301,6 +9735,7 @@ var FLEET_ROSTER = {
|
|
|
9301
9735
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
|
|
9302
9736
|
)
|
|
9303
9737
|
};
|
|
9738
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
9304
9739
|
var FLEET_ROSTER_BUDGETS = {
|
|
9305
9740
|
"audit-log": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 5e3, maxToolCalls: 15e3 },
|
|
9306
9741
|
"bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
@@ -9312,7 +9747,7 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
9312
9747
|
)
|
|
9313
9748
|
};
|
|
9314
9749
|
var GENERIC_SUBAGENT_BUDGET = {
|
|
9315
|
-
|
|
9750
|
+
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
9316
9751
|
maxIterations: 5e3,
|
|
9317
9752
|
maxToolCalls: 15e3
|
|
9318
9753
|
};
|
|
@@ -9322,7 +9757,13 @@ function applyRosterBudget(cfg) {
|
|
|
9322
9757
|
if (!defaultBudget) return cfg;
|
|
9323
9758
|
return {
|
|
9324
9759
|
...cfg,
|
|
9325
|
-
|
|
9760
|
+
// Wall-clock cap is opt-in only: forward an explicit `cfg.timeoutMs`, but
|
|
9761
|
+
// do NOT impose the roster's historical multi-hour wall-clock default — it
|
|
9762
|
+
// killed agents that were still actively working. Reaping is idle-based.
|
|
9763
|
+
timeoutMs: cfg.timeoutMs,
|
|
9764
|
+
// Idle window is the default reaper. Resets on activity, so a long-but-
|
|
9765
|
+
// productive run is never killed; only a genuine stall is reaped.
|
|
9766
|
+
idleTimeoutMs: cfg.idleTimeoutMs ?? defaultBudget.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS,
|
|
9326
9767
|
maxIterations: cfg.maxIterations ?? defaultBudget.maxIterations,
|
|
9327
9768
|
maxToolCalls: cfg.maxToolCalls ?? defaultBudget.maxToolCalls,
|
|
9328
9769
|
maxTokens: cfg.maxTokens ?? defaultBudget.maxTokens,
|
|
@@ -9392,74 +9833,203 @@ FLEET_ROSTER_BUDGETS["goose"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations:
|
|
|
9392
9833
|
...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
|
|
9393
9834
|
});
|
|
9394
9835
|
|
|
9395
|
-
// src/coordination/
|
|
9396
|
-
var
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
}
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
}
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9836
|
+
// src/coordination/subagent-nicknames.ts
|
|
9837
|
+
var NICKNAME_POOL = {
|
|
9838
|
+
// Physics & fundamental sciences
|
|
9839
|
+
"einstein": { name: "Einstein", domain: "physics" },
|
|
9840
|
+
"newton": { name: "Newton", domain: "physics" },
|
|
9841
|
+
"feynman": { name: "Feynman", domain: "physics" },
|
|
9842
|
+
"dirac": { name: "Dirac", domain: "physics" },
|
|
9843
|
+
"bohr": { name: "Bohr", domain: "physics" },
|
|
9844
|
+
"planck": { name: "Planck", domain: "physics" },
|
|
9845
|
+
"curie": { name: "Curie", domain: "physics" },
|
|
9846
|
+
"fermi": { name: "Fermi", domain: "physics" },
|
|
9847
|
+
"heisenberg": { name: "Heisenberg", domain: "physics" },
|
|
9848
|
+
"schrodinger": { name: "Schr\xF6dinger", domain: "physics" },
|
|
9849
|
+
// Mathematics
|
|
9850
|
+
"euclid": { name: "Euclid", domain: "math" },
|
|
9851
|
+
"gauss": { name: "Gauss", domain: "math" },
|
|
9852
|
+
"turing": { name: "Turing", domain: "math" },
|
|
9853
|
+
"poincare": { name: "Poincar\xE9", domain: "math" },
|
|
9854
|
+
"riemann": { name: "Riemann", domain: "math" },
|
|
9855
|
+
"hilbert": { name: "Hilbert", domain: "math" },
|
|
9856
|
+
"pythagoras": { name: "Pythagoras", domain: "math" },
|
|
9857
|
+
// Computing & information theory
|
|
9858
|
+
"von-neumann": { name: "Von Neumann", domain: "computing" },
|
|
9859
|
+
"shannon": { name: "Shannon", domain: "computing" },
|
|
9860
|
+
"hopper": { name: "Hopper", domain: "computing" },
|
|
9861
|
+
"backus": { name: "Backus", domain: "computing" },
|
|
9862
|
+
"knuth": { name: "Knuth", domain: "computing" },
|
|
9863
|
+
"torvalds": { name: "Torvalds", domain: "computing" },
|
|
9864
|
+
"stallman": { name: "Stallman", domain: "computing" },
|
|
9865
|
+
"berners-lee": { name: "Berners-Lee", domain: "computing" },
|
|
9866
|
+
"babbage": { name: "Babbage", domain: "computing" },
|
|
9867
|
+
"lovelace": { name: "Lovelace", domain: "computing" },
|
|
9868
|
+
"klein": { name: "Klein", domain: "computing" },
|
|
9869
|
+
// Electronics & electrical engineering
|
|
9870
|
+
"edison": { name: "Edison", domain: "ee" },
|
|
9871
|
+
"tesla": { name: "Tesla", domain: "ee" },
|
|
9872
|
+
"faraday": { name: "Faraday", domain: "ee" },
|
|
9873
|
+
"maxwell": { name: "Maxwell", domain: "ee" },
|
|
9874
|
+
"ohm": { name: "Ohm", domain: "ee" },
|
|
9875
|
+
"bell": { name: "Bell", domain: "ee" },
|
|
9876
|
+
"marconi": { name: "Marconi", domain: "ee" },
|
|
9877
|
+
"lamarr": { name: "Lamarr", domain: "ee" },
|
|
9878
|
+
// General science / multi-disciplinary
|
|
9879
|
+
"darwin": { name: "Darwin", domain: "biology" },
|
|
9880
|
+
"mendel": { name: "Mendel", domain: "biology" },
|
|
9881
|
+
"pasteur": { name: "Pasteur", domain: "biology" },
|
|
9882
|
+
"hawking": { name: "Hawking", domain: "cosmology" },
|
|
9883
|
+
"sagan": { name: "Sagan", domain: "cosmology" },
|
|
9884
|
+
// Chemistry / materials
|
|
9885
|
+
"lavoisier": { name: "Lavoisier", domain: "chemistry" },
|
|
9886
|
+
"mendeleev": { name: "Mendeleev", domain: "chemistry" }
|
|
9887
|
+
};
|
|
9888
|
+
var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
|
|
9889
|
+
var DOMAIN_PREFERENCES = {
|
|
9890
|
+
"security": ["shannon", "turing", "lamarr", "stallman"],
|
|
9891
|
+
"bug-hunter": ["darwin", "curie", "feynman", "fermi"],
|
|
9892
|
+
"refactor": ["gauss", "hilbert", "euclid", "planck"],
|
|
9893
|
+
"audit-log": ["sagan", "hawking", "poincare", "newton"],
|
|
9894
|
+
"planner": ["hilbert", "gauss", "turing", "euclid"],
|
|
9895
|
+
"researcher": ["sagan", "hawking", "darwin", "pasteur"],
|
|
9896
|
+
"explorer": ["marconi", "bell", "columbus", "polo"],
|
|
9897
|
+
"testing": ["pasteur", "curie", "fermi", "bohr"],
|
|
9898
|
+
"frontend": ["lovelace", "hopper", "babbage", "backus"],
|
|
9899
|
+
"backend": ["torvalds", "stallman", "von-neumann", "backus"],
|
|
9900
|
+
"database": ["turing", "shannon", "backus", "knuth"],
|
|
9901
|
+
"devops": ["tesla", "edison", "faraday", "bell"],
|
|
9902
|
+
"security-scanner": ["shannon", "turing", "lamarr", "stallman"],
|
|
9903
|
+
"refactor-planner": ["gauss", "hilbert", "planck", "newton"],
|
|
9904
|
+
"architect": ["von-neumann", "turing", "gauss", "hilbert"],
|
|
9905
|
+
"critic": ["einstein", "feynman", "dirac", "bohr"],
|
|
9906
|
+
"e2e": ["hopper", "bell", "marconi", "tesla"],
|
|
9907
|
+
"performance": ["knuth", "gauss", "planck", "feynman"],
|
|
9908
|
+
"chaos": ["tesla", "edison", "curie", "fermi"],
|
|
9909
|
+
"cost": ["ohm", "bell", "marconi", "tesla"],
|
|
9910
|
+
// default fallback
|
|
9911
|
+
"default": ["einstein", "newton", "curie", "tesla", "edison", "turing", "shannon", "hopper", "knuth", "stallman"]
|
|
9912
|
+
};
|
|
9913
|
+
function assignNickname(role, used) {
|
|
9914
|
+
const preferences = [
|
|
9915
|
+
...DOMAIN_PREFERENCES[role] ?? [],
|
|
9916
|
+
...DOMAIN_PREFERENCES["default"] ?? []
|
|
9917
|
+
];
|
|
9918
|
+
for (const key of preferences) {
|
|
9919
|
+
const entry = NICKNAME_POOL[key];
|
|
9920
|
+
if (entry && !used.has(key)) {
|
|
9921
|
+
return `${entry.name} (${formatRole(role)})`;
|
|
9922
|
+
}
|
|
9923
|
+
}
|
|
9924
|
+
for (const entry of ALL_NICKNAMES) {
|
|
9925
|
+
const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
|
|
9926
|
+
if (key && !used.has(key)) {
|
|
9927
|
+
return `${entry.name} (${formatRole(role)})`;
|
|
9928
|
+
}
|
|
9929
|
+
}
|
|
9930
|
+
const counter = used.size + 1;
|
|
9931
|
+
return `Scientist #${counter} (${formatRole(role)})`;
|
|
9932
|
+
}
|
|
9933
|
+
function formatRole(role) {
|
|
9934
|
+
return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
9935
|
+
}
|
|
9936
|
+
|
|
9937
|
+
// src/coordination/multi-agent-coordinator.ts
|
|
9938
|
+
var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
9939
|
+
coordinatorId;
|
|
9940
|
+
config;
|
|
9941
|
+
runner;
|
|
9942
|
+
fleetBus;
|
|
9943
|
+
subagents = /* @__PURE__ */ new Map();
|
|
9944
|
+
/**
|
|
9945
|
+
* Base nickname keys already handed out this run (e.g. `einstein`, `tesla`).
|
|
9946
|
+
* Prevents two workers sharing a name. Direct `coordinator.spawn()` callers
|
|
9947
|
+
* (parallel/eternal engine, SDD parallel run) don't go through
|
|
9948
|
+
* `Director.spawn()` where nicknames are normally assigned, so the
|
|
9949
|
+
* coordinator upgrades placeholder names ("Executor", "slot-ab12cd", role
|
|
9950
|
+
* names) to memorable ones here — that's what surfaces in the fleet monitor.
|
|
9951
|
+
*/
|
|
9952
|
+
usedNicknames = /* @__PURE__ */ new Set();
|
|
9953
|
+
pendingTasks = [];
|
|
9954
|
+
completedResults = [];
|
|
9955
|
+
totalIterations = 0;
|
|
9956
|
+
inFlight = 0;
|
|
9957
|
+
/**
|
|
9958
|
+
* Subagents currently being stopped. Set on entry to `stop()`, cleared
|
|
9959
|
+
* once `recordCompletion` lands the terminal TaskResult. Used by
|
|
9960
|
+
* `runDispatched` and `findIdleSubagent` to refuse mid-flight dispatch
|
|
9961
|
+
* to a subagent the caller has already asked to terminate — closes the
|
|
9962
|
+
* assign+terminate race where a fresh task could land on a worker that
|
|
9963
|
+
* was about to be killed.
|
|
9964
|
+
*/
|
|
9965
|
+
terminating = /* @__PURE__ */ new Set();
|
|
9966
|
+
constructor(config, options = {}) {
|
|
9967
|
+
super();
|
|
9968
|
+
this.coordinatorId = config.coordinatorId;
|
|
9969
|
+
this.config = config;
|
|
9970
|
+
this.runner = options.runner;
|
|
9971
|
+
}
|
|
9972
|
+
/**
|
|
9973
|
+
* Replace the runner after construction. Used when the runner depends
|
|
9974
|
+
* on infrastructure (e.g. FleetBus) that isn't available until after
|
|
9975
|
+
* the coordinator's owning Director is built.
|
|
9976
|
+
*/
|
|
9977
|
+
setRunner(runner) {
|
|
9978
|
+
this.runner = runner;
|
|
9979
|
+
}
|
|
9980
|
+
/**
|
|
9981
|
+
* Wire a FleetBus for director-mode event emission. Call after the
|
|
9982
|
+
* FleetManager is constructed so the coordinator can emit lifecycle
|
|
9983
|
+
* events the TUI and monitoring tools subscribe to.
|
|
9984
|
+
*/
|
|
9985
|
+
setFleetBus(fleet) {
|
|
9986
|
+
this.fleetBus = fleet;
|
|
9987
|
+
}
|
|
9988
|
+
/**
|
|
9989
|
+
* Change the in-flight dispatch ceiling at runtime. Lowering does NOT
|
|
9990
|
+
* preempt running tasks — already-dispatched subagents finish their
|
|
9991
|
+
* current task; only future dispatches respect the new cap. Raising
|
|
9992
|
+
* immediately tries to fill the freed slots from the pending queue.
|
|
9993
|
+
*/
|
|
9994
|
+
setMaxConcurrent(n) {
|
|
9995
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
9996
|
+
throw new Error(`maxConcurrent must be a finite integer >= 1, got ${n}`);
|
|
9997
|
+
}
|
|
9998
|
+
this.config.maxConcurrent = Math.floor(n);
|
|
9999
|
+
this.tryDispatchNext();
|
|
10000
|
+
}
|
|
10001
|
+
/**
|
|
10002
|
+
* Upgrade a placeholder/role-derived name to a memorable scientist nickname
|
|
10003
|
+
* (e.g. "Einstein (Executor)"). A name is treated as a placeholder when it is
|
|
10004
|
+
* empty, equals the role (case-insensitive), is a generic default
|
|
10005
|
+
* ("subagent"/"adhoc"/"generic"), or is an auto-generated `slot-…` id.
|
|
10006
|
+
* Explicit, human-chosen names — including nicknames already assigned by
|
|
10007
|
+
* `Director.spawn()` — are left untouched, so this never double-assigns.
|
|
10008
|
+
*/
|
|
10009
|
+
withNickname(subagent) {
|
|
10010
|
+
const role = subagent.role ?? "subagent";
|
|
10011
|
+
const name = subagent.name?.trim() ?? "";
|
|
10012
|
+
const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
|
|
10013
|
+
if (!isPlaceholder) return subagent;
|
|
10014
|
+
const nickname = assignNickname(role, this.usedNicknames);
|
|
10015
|
+
const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
10016
|
+
this.usedNicknames.add(baseKey);
|
|
10017
|
+
return { ...subagent, name: nickname };
|
|
10018
|
+
}
|
|
10019
|
+
async spawn(subagent) {
|
|
10020
|
+
subagent = this.withNickname(subagent);
|
|
10021
|
+
const id = subagent.id || randomUUID();
|
|
10022
|
+
if (this.subagents.has(id)) {
|
|
10023
|
+
throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
|
|
10024
|
+
}
|
|
10025
|
+
const context = {
|
|
10026
|
+
subagentId: id,
|
|
10027
|
+
tasks: [],
|
|
10028
|
+
// Wired later by the caller via setSubagentBridge() once the
|
|
10029
|
+
// bidirectional bridge is created. Readers must null-check / use
|
|
10030
|
+
// hasParentBridge() — the type now reflects this.
|
|
10031
|
+
parentBridge: null,
|
|
10032
|
+
doneCondition: this.config.doneCondition,
|
|
9463
10033
|
maxConcurrent: this.config.maxConcurrent ?? 16
|
|
9464
10034
|
};
|
|
9465
10035
|
this.subagents.set(id, {
|
|
@@ -9755,13 +10325,17 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
9755
10325
|
const rawMaxTokens = subagent.config.maxTokens;
|
|
9756
10326
|
const rawMaxCostUsd = subagent.config.maxCostUsd;
|
|
9757
10327
|
const rawTimeoutMs = subagent.config.timeoutMs;
|
|
10328
|
+
const rawIdleTimeoutMs = subagent.config.idleTimeoutMs;
|
|
9758
10329
|
const configWithRosterDefaults = applyRosterBudget(subagent.config);
|
|
9759
10330
|
const budget = new SubagentBudget({
|
|
9760
10331
|
maxIterations: rawMaxIterations ?? this.config.defaultBudget?.maxIterations ?? configWithRosterDefaults.maxIterations,
|
|
9761
10332
|
maxToolCalls: rawMaxToolCalls ?? this.config.defaultBudget?.maxToolCalls ?? configWithRosterDefaults.maxToolCalls,
|
|
9762
10333
|
maxTokens: rawMaxTokens ?? this.config.defaultBudget?.maxTokens ?? configWithRosterDefaults.maxTokens,
|
|
9763
10334
|
maxCostUsd: rawMaxCostUsd ?? this.config.defaultBudget?.maxCostUsd ?? configWithRosterDefaults.maxCostUsd,
|
|
9764
|
-
|
|
10335
|
+
// Wall-clock cap is opt-in (explicit config / defaultBudget only); the
|
|
10336
|
+
// roster no longer supplies one. Idle is the default reaper.
|
|
10337
|
+
timeoutMs: rawTimeoutMs ?? this.config.defaultBudget?.timeoutMs ?? configWithRosterDefaults.timeoutMs,
|
|
10338
|
+
idleTimeoutMs: rawIdleTimeoutMs ?? this.config.defaultBudget?.idleTimeoutMs ?? configWithRosterDefaults.idleTimeoutMs
|
|
9765
10339
|
});
|
|
9766
10340
|
subagent.activeBudget = budget;
|
|
9767
10341
|
if (!this.runner) {
|
|
@@ -9808,63 +10382,79 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
9808
10382
|
}
|
|
9809
10383
|
async executeWithTimeout(runner, task, ctx, budget) {
|
|
9810
10384
|
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
9811
|
-
|
|
10385
|
+
const idleLimitMs = budget.limits.idleTimeoutMs;
|
|
10386
|
+
if (initialTimeoutMs === void 0 && idleLimitMs === void 0) {
|
|
10387
|
+
return runner(task, ctx);
|
|
10388
|
+
}
|
|
9812
10389
|
const start = Date.now();
|
|
9813
10390
|
let timer = null;
|
|
9814
10391
|
const timeoutPromise = new Promise((_, reject) => {
|
|
9815
10392
|
const armFor = (ms) => {
|
|
9816
10393
|
if (timer) clearTimeout(timer);
|
|
9817
|
-
timer = setTimeout(
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
10394
|
+
timer = setTimeout(onTick, Math.max(0, ms));
|
|
10395
|
+
};
|
|
10396
|
+
const scheduleNext = () => {
|
|
10397
|
+
const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.timeoutMs ?? initialTimeoutMs) - (Date.now() - start);
|
|
10398
|
+
const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
10399
|
+
armFor(Math.max(25, Math.min(wallRemaining, idleRemaining)));
|
|
10400
|
+
};
|
|
10401
|
+
const onTick = async () => {
|
|
10402
|
+
const elapsed = Date.now() - start;
|
|
10403
|
+
const wallLimit = initialTimeoutMs === void 0 ? void 0 : budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
10404
|
+
const idleLimit = idleLimitMs === void 0 ? void 0 : budget.limits.idleTimeoutMs ?? idleLimitMs;
|
|
10405
|
+
const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
|
|
10406
|
+
const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
|
|
10407
|
+
if (idleExceeded && !wallExceeded) {
|
|
10408
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
10409
|
+
reject(new BudgetExceededError("timeout", idleLimit, budget.idleMs()));
|
|
10410
|
+
return;
|
|
10411
|
+
}
|
|
10412
|
+
if (!wallExceeded) {
|
|
10413
|
+
scheduleNext();
|
|
10414
|
+
return;
|
|
10415
|
+
}
|
|
10416
|
+
const limit = wallLimit;
|
|
10417
|
+
if (!budget.onThreshold) {
|
|
10418
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
10419
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
10420
|
+
return;
|
|
10421
|
+
}
|
|
10422
|
+
try {
|
|
10423
|
+
const result = budget.onThreshold({
|
|
10424
|
+
kind: "timeout",
|
|
10425
|
+
used: elapsed,
|
|
10426
|
+
limit,
|
|
10427
|
+
requestDecision: () => new Promise((resolveDecision) => {
|
|
10428
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
10429
|
+
kind: "timeout",
|
|
10430
|
+
used: elapsed,
|
|
10431
|
+
limit,
|
|
10432
|
+
timeoutMs: 6e4,
|
|
10433
|
+
extend: (extra) => resolveDecision({ extend: extra }),
|
|
10434
|
+
deny: () => resolveDecision("stop")
|
|
10435
|
+
});
|
|
10436
|
+
})
|
|
10437
|
+
});
|
|
10438
|
+
const decision = typeof result === "string" ? result : await result;
|
|
10439
|
+
if (decision === "continue" || decision === "throw" || decision === "stop") {
|
|
10440
|
+
armFor(Math.max(1e3, limit));
|
|
9823
10441
|
return;
|
|
9824
10442
|
}
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
limit,
|
|
9830
|
-
requestDecision: () => new Promise((resolveDecision) => {
|
|
9831
|
-
budget._events?.emit("budget.threshold_reached", {
|
|
9832
|
-
kind: "timeout",
|
|
9833
|
-
used: elapsed,
|
|
9834
|
-
limit,
|
|
9835
|
-
timeoutMs: 6e4,
|
|
9836
|
-
extend: (extra) => resolveDecision({ extend: extra }),
|
|
9837
|
-
deny: () => resolveDecision("stop")
|
|
9838
|
-
});
|
|
9839
|
-
})
|
|
9840
|
-
});
|
|
9841
|
-
const decision = typeof result === "string" ? result : await result;
|
|
9842
|
-
if (decision === "continue") {
|
|
9843
|
-
armFor(Math.max(1e3, limit));
|
|
9844
|
-
return;
|
|
9845
|
-
}
|
|
9846
|
-
if (decision === "throw" || decision === "stop") {
|
|
9847
|
-
armFor(Math.max(1e3, limit));
|
|
9848
|
-
return;
|
|
9849
|
-
}
|
|
9850
|
-
if (decision.extend.timeoutMs !== void 0) {
|
|
9851
|
-
budget.limits.timeoutMs = decision.extend.timeoutMs;
|
|
9852
|
-
const newLimit = decision.extend.timeoutMs;
|
|
9853
|
-
const remaining = Math.max(1e3, newLimit - elapsed);
|
|
9854
|
-
armFor(remaining);
|
|
9855
|
-
return;
|
|
9856
|
-
}
|
|
9857
|
-
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
9858
|
-
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
9859
|
-
} catch (err) {
|
|
9860
|
-
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
9861
|
-
reject(
|
|
9862
|
-
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
9863
|
-
);
|
|
10443
|
+
if (decision.extend.timeoutMs !== void 0) {
|
|
10444
|
+
budget.limits.timeoutMs = decision.extend.timeoutMs;
|
|
10445
|
+
scheduleNext();
|
|
10446
|
+
return;
|
|
9864
10447
|
}
|
|
9865
|
-
|
|
10448
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
10449
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
10450
|
+
} catch (err) {
|
|
10451
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
10452
|
+
reject(
|
|
10453
|
+
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
10454
|
+
);
|
|
10455
|
+
}
|
|
9866
10456
|
};
|
|
9867
|
-
|
|
10457
|
+
scheduleNext();
|
|
9868
10458
|
});
|
|
9869
10459
|
try {
|
|
9870
10460
|
return await Promise.race([runner(task, ctx), timeoutPromise]);
|
|
@@ -10041,12 +10631,12 @@ var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
|
|
|
10041
10631
|
var ParallelEternalEngine = class {
|
|
10042
10632
|
constructor(opts) {
|
|
10043
10633
|
this.opts = opts;
|
|
10044
|
-
this.goalPath = goalFilePath(opts.projectRoot);
|
|
10634
|
+
this.goalPath = opts.goalPath ?? goalFilePath(opts.projectRoot);
|
|
10045
10635
|
this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
|
|
10046
10636
|
this.timeoutMs = opts.iterationTimeoutMs ?? 3e5;
|
|
10047
10637
|
this.dispatchEnabled = opts.dispatch !== false;
|
|
10048
10638
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
10049
|
-
this.agentFactory = opts.subagentFactory ?? (async (
|
|
10639
|
+
this.agentFactory = opts.subagentFactory ?? (async (_config) => ({
|
|
10050
10640
|
agent: this.opts.agent,
|
|
10051
10641
|
events: this.opts.agent.events
|
|
10052
10642
|
}));
|
|
@@ -10140,7 +10730,6 @@ var ParallelEternalEngine = class {
|
|
|
10140
10730
|
}
|
|
10141
10731
|
const tasks = await this.decomposeGoal(goal);
|
|
10142
10732
|
if (!tasks || tasks.length === 0) {
|
|
10143
|
-
await sleep2(5e3);
|
|
10144
10733
|
return false;
|
|
10145
10734
|
}
|
|
10146
10735
|
const fanOut = await this.fanOut(goal, tasks);
|
|
@@ -10161,6 +10750,7 @@ var ParallelEternalEngine = class {
|
|
|
10161
10750
|
});
|
|
10162
10751
|
if (fanOut.goalComplete) {
|
|
10163
10752
|
this.stopRequested = true;
|
|
10753
|
+
this.state = "stopped";
|
|
10164
10754
|
return true;
|
|
10165
10755
|
}
|
|
10166
10756
|
await this.maybeCompact();
|
|
@@ -10341,7 +10931,11 @@ ${lastFew}` : "No prior iterations.",
|
|
|
10341
10931
|
} finally {
|
|
10342
10932
|
clearTimeout(timer);
|
|
10343
10933
|
}
|
|
10344
|
-
} catch {
|
|
10934
|
+
} catch (err) {
|
|
10935
|
+
this.opts.onError?.(
|
|
10936
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
10937
|
+
this.consecutiveFailures
|
|
10938
|
+
);
|
|
10345
10939
|
return [];
|
|
10346
10940
|
}
|
|
10347
10941
|
}
|
|
@@ -10702,7 +11296,11 @@ Working rules:
|
|
|
10702
11296
|
thrashing, terminate it rather than letting cost climb silently.
|
|
10703
11297
|
6. Never claim a subagent's work as your own without verifying it. If a
|
|
10704
11298
|
result looks wrong, ask_subagent for clarification before passing it
|
|
10705
|
-
to the user
|
|
11299
|
+
to the user.
|
|
11300
|
+
7. Wind down when satisfied. When the results are good enough, call
|
|
11301
|
+
work_complete \u2014 no new subagents will spawn and queued tasks complete
|
|
11302
|
+
as aborted. Running subagents finish naturally. Call terminate_subagent
|
|
11303
|
+
only for ones you need to stop immediately.`;
|
|
10706
11304
|
var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
|
|
10707
11305
|
a specific slice of a larger plan \u2014 do that slice well and report back.
|
|
10708
11306
|
|
|
@@ -10847,14 +11445,12 @@ var FleetBus = class {
|
|
|
10847
11445
|
};
|
|
10848
11446
|
var FleetUsageAggregator = class {
|
|
10849
11447
|
constructor(bus, priceLookup, metaLookup) {
|
|
10850
|
-
this.bus = bus;
|
|
10851
11448
|
this.priceLookup = priceLookup;
|
|
10852
11449
|
this.metaLookup = metaLookup;
|
|
10853
11450
|
bus.filter("provider.response", (e) => this.onProviderResponse(e));
|
|
10854
11451
|
bus.filter("tool.executed", (e) => this.onToolExecuted(e));
|
|
10855
11452
|
bus.filter("iteration.started", (e) => this.onIterationStarted(e));
|
|
10856
11453
|
}
|
|
10857
|
-
bus;
|
|
10858
11454
|
priceLookup;
|
|
10859
11455
|
metaLookup;
|
|
10860
11456
|
perSubagent = /* @__PURE__ */ new Map();
|
|
@@ -10900,128 +11496,28 @@ var FleetUsageAggregator = class {
|
|
|
10900
11496
|
snap.cacheRead += usage.cacheRead ?? 0;
|
|
10901
11497
|
snap.cacheWrite += usage.cacheWrite ?? 0;
|
|
10902
11498
|
this.total.input += usage.input ?? 0;
|
|
10903
|
-
this.total.output += usage.output ?? 0;
|
|
10904
|
-
this.total.cacheRead += usage.cacheRead ?? 0;
|
|
10905
|
-
this.total.cacheWrite += usage.cacheWrite ?? 0;
|
|
10906
|
-
const price = this.priceLookup?.(e.subagentId, snap.provider, snap.model);
|
|
10907
|
-
if (price) {
|
|
10908
|
-
const delta = (usage.input ?? 0) / 1e6 * (price.input ?? 0) + (usage.output ?? 0) / 1e6 * (price.output ?? 0) + (usage.cacheRead ?? 0) / 1e6 * (price.cacheRead ?? 0) + (usage.cacheWrite ?? 0) / 1e6 * (price.cacheWrite ?? 0);
|
|
10909
|
-
snap.cost += delta;
|
|
10910
|
-
this.total.cost += delta;
|
|
10911
|
-
}
|
|
10912
|
-
snap.lastEventAt = e.ts;
|
|
10913
|
-
}
|
|
10914
|
-
onToolExecuted(e) {
|
|
10915
|
-
const snap = this.ensure(e.subagentId);
|
|
10916
|
-
snap.toolCalls += 1;
|
|
10917
|
-
snap.lastEventAt = e.ts;
|
|
10918
|
-
}
|
|
10919
|
-
onIterationStarted(e) {
|
|
10920
|
-
const snap = this.ensure(e.subagentId);
|
|
10921
|
-
snap.iterations += 1;
|
|
10922
|
-
snap.lastEventAt = e.ts;
|
|
10923
|
-
}
|
|
10924
|
-
};
|
|
10925
|
-
|
|
10926
|
-
// src/coordination/subagent-nicknames.ts
|
|
10927
|
-
var NICKNAME_POOL = {
|
|
10928
|
-
// Physics & fundamental sciences
|
|
10929
|
-
"einstein": { name: "Einstein", domain: "physics" },
|
|
10930
|
-
"newton": { name: "Newton", domain: "physics" },
|
|
10931
|
-
"feynman": { name: "Feynman", domain: "physics" },
|
|
10932
|
-
"dirac": { name: "Dirac", domain: "physics" },
|
|
10933
|
-
"bohr": { name: "Bohr", domain: "physics" },
|
|
10934
|
-
"planck": { name: "Planck", domain: "physics" },
|
|
10935
|
-
"curie": { name: "Curie", domain: "physics" },
|
|
10936
|
-
"fermi": { name: "Fermi", domain: "physics" },
|
|
10937
|
-
"heisenberg": { name: "Heisenberg", domain: "physics" },
|
|
10938
|
-
"schrodinger": { name: "Schr\xF6dinger", domain: "physics" },
|
|
10939
|
-
// Mathematics
|
|
10940
|
-
"euclid": { name: "Euclid", domain: "math" },
|
|
10941
|
-
"gauss": { name: "Gauss", domain: "math" },
|
|
10942
|
-
"turing": { name: "Turing", domain: "math" },
|
|
10943
|
-
"poincare": { name: "Poincar\xE9", domain: "math" },
|
|
10944
|
-
"riemann": { name: "Riemann", domain: "math" },
|
|
10945
|
-
"hilbert": { name: "Hilbert", domain: "math" },
|
|
10946
|
-
"pythagoras": { name: "Pythagoras", domain: "math" },
|
|
10947
|
-
// Computing & information theory
|
|
10948
|
-
"von-neumann": { name: "Von Neumann", domain: "computing" },
|
|
10949
|
-
"shannon": { name: "Shannon", domain: "computing" },
|
|
10950
|
-
"hopper": { name: "Hopper", domain: "computing" },
|
|
10951
|
-
"backus": { name: "Backus", domain: "computing" },
|
|
10952
|
-
"knuth": { name: "Knuth", domain: "computing" },
|
|
10953
|
-
"torvalds": { name: "Torvalds", domain: "computing" },
|
|
10954
|
-
"stallman": { name: "Stallman", domain: "computing" },
|
|
10955
|
-
"berners-lee": { name: "Berners-Lee", domain: "computing" },
|
|
10956
|
-
"babbage": { name: "Babbage", domain: "computing" },
|
|
10957
|
-
"lovelace": { name: "Lovelace", domain: "computing" },
|
|
10958
|
-
"klein": { name: "Klein", domain: "computing" },
|
|
10959
|
-
// Electronics & electrical engineering
|
|
10960
|
-
"edison": { name: "Edison", domain: "ee" },
|
|
10961
|
-
"tesla": { name: "Tesla", domain: "ee" },
|
|
10962
|
-
"faraday": { name: "Faraday", domain: "ee" },
|
|
10963
|
-
"maxwell": { name: "Maxwell", domain: "ee" },
|
|
10964
|
-
"ohm": { name: "Ohm", domain: "ee" },
|
|
10965
|
-
"bell": { name: "Bell", domain: "ee" },
|
|
10966
|
-
"marconi": { name: "Marconi", domain: "ee" },
|
|
10967
|
-
"lamarr": { name: "Lamarr", domain: "ee" },
|
|
10968
|
-
// General science / multi-disciplinary
|
|
10969
|
-
"darwin": { name: "Darwin", domain: "biology" },
|
|
10970
|
-
"mendel": { name: "Mendel", domain: "biology" },
|
|
10971
|
-
"pasteur": { name: "Pasteur", domain: "biology" },
|
|
10972
|
-
"hawking": { name: "Hawking", domain: "cosmology" },
|
|
10973
|
-
"sagan": { name: "Sagan", domain: "cosmology" },
|
|
10974
|
-
// Chemistry / materials
|
|
10975
|
-
"lavoisier": { name: "Lavoisier", domain: "chemistry" },
|
|
10976
|
-
"mendeleev": { name: "Mendeleev", domain: "chemistry" }
|
|
10977
|
-
};
|
|
10978
|
-
var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
|
|
10979
|
-
var DOMAIN_PREFERENCES = {
|
|
10980
|
-
"security": ["shannon", "turing", "lamarr", "stallman"],
|
|
10981
|
-
"bug-hunter": ["darwin", "curie", "feynman", "fermi"],
|
|
10982
|
-
"refactor": ["gauss", "hilbert", "euclid", "planck"],
|
|
10983
|
-
"audit-log": ["sagan", "hawking", "poincare", "newton"],
|
|
10984
|
-
"planner": ["hilbert", "gauss", "turing", "euclid"],
|
|
10985
|
-
"researcher": ["sagan", "hawking", "darwin", "pasteur"],
|
|
10986
|
-
"explorer": ["marconi", "bell", "columbus", "polo"],
|
|
10987
|
-
"testing": ["pasteur", "curie", "fermi", "bohr"],
|
|
10988
|
-
"frontend": ["lovelace", "hopper", "babbage", "backus"],
|
|
10989
|
-
"backend": ["torvalds", "stallman", "von-neumann", "backus"],
|
|
10990
|
-
"database": ["turing", "shannon", "backus", "knuth"],
|
|
10991
|
-
"devops": ["tesla", "edison", "faraday", "bell"],
|
|
10992
|
-
"security-scanner": ["shannon", "turing", "lamarr", "stallman"],
|
|
10993
|
-
"refactor-planner": ["gauss", "hilbert", "planck", "newton"],
|
|
10994
|
-
"architect": ["von-neumann", "turing", "gauss", "hilbert"],
|
|
10995
|
-
"critic": ["einstein", "feynman", "dirac", "bohr"],
|
|
10996
|
-
"e2e": ["hopper", "bell", "marconi", "tesla"],
|
|
10997
|
-
"performance": ["knuth", "gauss", "planck", "feynman"],
|
|
10998
|
-
"chaos": ["tesla", "edison", "curie", "fermi"],
|
|
10999
|
-
"cost": ["oh", "bell", "marconi", "tesla"],
|
|
11000
|
-
// default fallback
|
|
11001
|
-
"default": ["einstein", "newton", "curie", "tesla", "edison", "turing", "shannon", "hopper", "knuth", "stallman"]
|
|
11002
|
-
};
|
|
11003
|
-
function assignNickname(role, used) {
|
|
11004
|
-
const preferences = [
|
|
11005
|
-
...DOMAIN_PREFERENCES[role] ?? [],
|
|
11006
|
-
...DOMAIN_PREFERENCES["default"] ?? []
|
|
11007
|
-
];
|
|
11008
|
-
for (const key of preferences) {
|
|
11009
|
-
if (!used.has(key)) {
|
|
11010
|
-
return `${NICKNAME_POOL[key].name} (${formatRole(role)})`;
|
|
11499
|
+
this.total.output += usage.output ?? 0;
|
|
11500
|
+
this.total.cacheRead += usage.cacheRead ?? 0;
|
|
11501
|
+
this.total.cacheWrite += usage.cacheWrite ?? 0;
|
|
11502
|
+
const price = this.priceLookup?.(e.subagentId, snap.provider, snap.model);
|
|
11503
|
+
if (price) {
|
|
11504
|
+
const delta = (usage.input ?? 0) / 1e6 * (price.input ?? 0) + (usage.output ?? 0) / 1e6 * (price.output ?? 0) + (usage.cacheRead ?? 0) / 1e6 * (price.cacheRead ?? 0) + (usage.cacheWrite ?? 0) / 1e6 * (price.cacheWrite ?? 0);
|
|
11505
|
+
snap.cost += delta;
|
|
11506
|
+
this.total.cost += delta;
|
|
11011
11507
|
}
|
|
11508
|
+
snap.lastEventAt = e.ts;
|
|
11012
11509
|
}
|
|
11013
|
-
|
|
11014
|
-
const
|
|
11015
|
-
|
|
11016
|
-
|
|
11017
|
-
}
|
|
11510
|
+
onToolExecuted(e) {
|
|
11511
|
+
const snap = this.ensure(e.subagentId);
|
|
11512
|
+
snap.toolCalls += 1;
|
|
11513
|
+
snap.lastEventAt = e.ts;
|
|
11018
11514
|
}
|
|
11019
|
-
|
|
11020
|
-
|
|
11021
|
-
|
|
11022
|
-
|
|
11023
|
-
|
|
11024
|
-
}
|
|
11515
|
+
onIterationStarted(e) {
|
|
11516
|
+
const snap = this.ensure(e.subagentId);
|
|
11517
|
+
snap.iterations += 1;
|
|
11518
|
+
snap.lastEventAt = e.ts;
|
|
11519
|
+
}
|
|
11520
|
+
};
|
|
11025
11521
|
function makeSpawnTool(director, roster) {
|
|
11026
11522
|
const inputSchema = {
|
|
11027
11523
|
type: "object",
|
|
@@ -11192,7 +11688,7 @@ function makeRollUpTool(director) {
|
|
|
11192
11688
|
function makeTerminateTool(director) {
|
|
11193
11689
|
return {
|
|
11194
11690
|
name: "terminate_subagent",
|
|
11195
|
-
description:
|
|
11691
|
+
description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
|
|
11196
11692
|
permission: "auto",
|
|
11197
11693
|
mutating: true,
|
|
11198
11694
|
inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
|
|
@@ -11203,6 +11699,19 @@ function makeTerminateTool(director) {
|
|
|
11203
11699
|
}
|
|
11204
11700
|
};
|
|
11205
11701
|
}
|
|
11702
|
+
function makeTerminateAllTool(director) {
|
|
11703
|
+
return {
|
|
11704
|
+
name: "terminate_all",
|
|
11705
|
+
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.',
|
|
11706
|
+
permission: "auto",
|
|
11707
|
+
mutating: true,
|
|
11708
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
11709
|
+
async execute() {
|
|
11710
|
+
await director.terminateAll();
|
|
11711
|
+
return { ok: true, message: `Fleet shutdown complete \u2014 all subagents stopped, pending tasks drained.` };
|
|
11712
|
+
}
|
|
11713
|
+
};
|
|
11714
|
+
}
|
|
11206
11715
|
function makeFleetStatusTool(director) {
|
|
11207
11716
|
return {
|
|
11208
11717
|
name: "fleet_status",
|
|
@@ -11345,7 +11854,7 @@ function makeCollabDebugTool(director) {
|
|
|
11345
11854
|
function makeFleetEmitTool(director) {
|
|
11346
11855
|
return {
|
|
11347
11856
|
name: "fleet_emit",
|
|
11348
|
-
description: "Emit a structured event on the FleetBus.
|
|
11857
|
+
description: "Emit a structured event on the FleetBus. Any subagent can emit any event type; the Director routes it to all listeners. Use it to stream findings, progress updates, or final results to other agents in real time.",
|
|
11349
11858
|
permission: "auto",
|
|
11350
11859
|
mutating: false,
|
|
11351
11860
|
inputSchema: {
|
|
@@ -11353,14 +11862,14 @@ function makeFleetEmitTool(director) {
|
|
|
11353
11862
|
properties: {
|
|
11354
11863
|
type: {
|
|
11355
11864
|
type: "string",
|
|
11356
|
-
description: "Event type
|
|
11865
|
+
description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
|
|
11357
11866
|
},
|
|
11358
11867
|
payload: {
|
|
11359
11868
|
type: "object",
|
|
11360
|
-
description: "Event payload
|
|
11869
|
+
description: "Event payload. Structure depends on event type. Use null if no payload."
|
|
11361
11870
|
}
|
|
11362
11871
|
},
|
|
11363
|
-
required: ["type"
|
|
11872
|
+
required: ["type"]
|
|
11364
11873
|
},
|
|
11365
11874
|
async execute(input) {
|
|
11366
11875
|
const i = input;
|
|
@@ -11368,12 +11877,25 @@ function makeFleetEmitTool(director) {
|
|
|
11368
11877
|
subagentId: director.id,
|
|
11369
11878
|
ts: Date.now(),
|
|
11370
11879
|
type: i.type,
|
|
11371
|
-
payload: i.payload
|
|
11880
|
+
payload: i.payload ?? {}
|
|
11372
11881
|
});
|
|
11373
11882
|
return { ok: true, event: i.type };
|
|
11374
11883
|
}
|
|
11375
11884
|
};
|
|
11376
11885
|
}
|
|
11886
|
+
function makeWorkCompleteTool(director) {
|
|
11887
|
+
return {
|
|
11888
|
+
name: "work_complete",
|
|
11889
|
+
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.",
|
|
11890
|
+
permission: "auto",
|
|
11891
|
+
mutating: false,
|
|
11892
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
11893
|
+
async execute() {
|
|
11894
|
+
director.workComplete();
|
|
11895
|
+
return { ok: true, message: "Fleet wind-down signaled. No new spawns or task dispatches." };
|
|
11896
|
+
}
|
|
11897
|
+
};
|
|
11898
|
+
}
|
|
11377
11899
|
var CollabSession = class extends EventEmitter {
|
|
11378
11900
|
sessionId;
|
|
11379
11901
|
options;
|
|
@@ -11388,6 +11910,14 @@ var CollabSession = class extends EventEmitter {
|
|
|
11388
11910
|
disposers = new Array();
|
|
11389
11911
|
settled = false;
|
|
11390
11912
|
timeoutMs;
|
|
11913
|
+
cancelled = false;
|
|
11914
|
+
alerts = [];
|
|
11915
|
+
/** Tracks tool call counts per subagent for progress-based timeout decisions. */
|
|
11916
|
+
progressBySubagent = /* @__PURE__ */ new Map();
|
|
11917
|
+
/** Last tool call count when a timeout warning was handled. */
|
|
11918
|
+
lastTimeoutProgress = /* @__PURE__ */ new Map();
|
|
11919
|
+
/** Session-level timeout timer handle (cleared on cancel or natural completion). */
|
|
11920
|
+
_timeoutTimer;
|
|
11391
11921
|
constructor(director, fleetBus, options) {
|
|
11392
11922
|
super();
|
|
11393
11923
|
this.sessionId = randomUUID();
|
|
@@ -11405,11 +11935,23 @@ var CollabSession = class extends EventEmitter {
|
|
|
11405
11935
|
};
|
|
11406
11936
|
}
|
|
11407
11937
|
}
|
|
11938
|
+
get id() {
|
|
11939
|
+
return this.sessionId;
|
|
11940
|
+
}
|
|
11941
|
+
getSessionAlerts() {
|
|
11942
|
+
return [...this.alerts];
|
|
11943
|
+
}
|
|
11944
|
+
isCancelled() {
|
|
11945
|
+
return this.cancelled;
|
|
11946
|
+
}
|
|
11408
11947
|
/**
|
|
11409
|
-
*
|
|
11410
|
-
*
|
|
11411
|
-
*
|
|
11948
|
+
* Snapshot of role → subagentId map. The Director calls coordinator.stop()
|
|
11949
|
+
* for each agent when cancelling the session, using this map to enumerate
|
|
11950
|
+
* all three collab agents.
|
|
11412
11951
|
*/
|
|
11952
|
+
getSubagentIds() {
|
|
11953
|
+
return new Map(this.subagentIds);
|
|
11954
|
+
}
|
|
11413
11955
|
async buildSnapshot() {
|
|
11414
11956
|
if (this.snapshot.files.length > 0) return this.snapshot;
|
|
11415
11957
|
for (const filePath of this.options.targetPaths) {
|
|
@@ -11425,9 +11967,30 @@ var CollabSession = class extends EventEmitter {
|
|
|
11425
11967
|
return this.snapshot;
|
|
11426
11968
|
}
|
|
11427
11969
|
/**
|
|
11428
|
-
*
|
|
11429
|
-
*
|
|
11970
|
+
* Cancel the session. Emits director.cancel_collab on the FleetBus so all
|
|
11971
|
+
* collab agents finish early. The session-level timeout timer is also cleared.
|
|
11972
|
+
* Safe to call multiple times (idempotent after first call).
|
|
11430
11973
|
*/
|
|
11974
|
+
cancel(reason = "Director cancelled collab session") {
|
|
11975
|
+
if (this.settled) return;
|
|
11976
|
+
this.cancelled = true;
|
|
11977
|
+
if (this._timeoutTimer) {
|
|
11978
|
+
clearTimeout(this._timeoutTimer);
|
|
11979
|
+
this._timeoutTimer = void 0;
|
|
11980
|
+
}
|
|
11981
|
+
this.fleetBus.emit({
|
|
11982
|
+
subagentId: this.director.id,
|
|
11983
|
+
ts: Date.now(),
|
|
11984
|
+
type: "director.cancel_collab",
|
|
11985
|
+
payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
11986
|
+
});
|
|
11987
|
+
this.fleetBus.emit({
|
|
11988
|
+
subagentId: this.director.id,
|
|
11989
|
+
ts: Date.now(),
|
|
11990
|
+
type: "collab.cancelled",
|
|
11991
|
+
payload: { sessionId: this.sessionId, reason }
|
|
11992
|
+
});
|
|
11993
|
+
}
|
|
11431
11994
|
async start() {
|
|
11432
11995
|
if (this.settled) throw new Error("session already settled");
|
|
11433
11996
|
this.settled = true;
|
|
@@ -11442,10 +12005,10 @@ var CollabSession = class extends EventEmitter {
|
|
|
11442
12005
|
this.subagentIds.set("refactor-planner", refactorPlannerId);
|
|
11443
12006
|
this.subagentIds.set("critic", criticId);
|
|
11444
12007
|
const timeout = new Promise((_, reject) => {
|
|
11445
|
-
setTimeout(
|
|
11446
|
-
(
|
|
11447
|
-
this.timeoutMs
|
|
11448
|
-
);
|
|
12008
|
+
this._timeoutTimer = setTimeout(() => {
|
|
12009
|
+
this.cancel("Session-level timeout reached");
|
|
12010
|
+
reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
|
|
12011
|
+
}, this.timeoutMs);
|
|
11449
12012
|
});
|
|
11450
12013
|
let results;
|
|
11451
12014
|
try {
|
|
@@ -11458,6 +12021,10 @@ var CollabSession = class extends EventEmitter {
|
|
|
11458
12021
|
timeout
|
|
11459
12022
|
]);
|
|
11460
12023
|
} catch (err) {
|
|
12024
|
+
if (this._timeoutTimer) {
|
|
12025
|
+
clearTimeout(this._timeoutTimer);
|
|
12026
|
+
this._timeoutTimer = void 0;
|
|
12027
|
+
}
|
|
11461
12028
|
this.cleanup();
|
|
11462
12029
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
11463
12030
|
this.emit("session.error", error);
|
|
@@ -11471,17 +12038,6 @@ var CollabSession = class extends EventEmitter {
|
|
|
11471
12038
|
this.emit("session.done", report);
|
|
11472
12039
|
return report;
|
|
11473
12040
|
}
|
|
11474
|
-
/**
|
|
11475
|
-
* Parse a completed agent's final text output and route the structured
|
|
11476
|
-
* findings onto the FleetBus. Agents are instructed to emit one JSON object
|
|
11477
|
-
* per finding / plan / evaluation; we scan the output for balanced JSON
|
|
11478
|
-
* objects (tolerating prose, markdown, and ```json fences around them) and
|
|
11479
|
-
* dispatch each by its discriminating key. The wired FleetBus listeners then
|
|
11480
|
-
* collect them into the bugs / plans / evaluations maps.
|
|
11481
|
-
*
|
|
11482
|
-
* Failed or empty results are ignored, and a single malformed object never
|
|
11483
|
-
* aborts the collation pass — see {@link extractJsonObjects}.
|
|
11484
|
-
*/
|
|
11485
12041
|
async parseAndEmit(result) {
|
|
11486
12042
|
if (result.status !== "success" || result.result == null) return;
|
|
11487
12043
|
const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
|
|
@@ -11497,12 +12053,6 @@ var CollabSession = class extends EventEmitter {
|
|
|
11497
12053
|
});
|
|
11498
12054
|
}
|
|
11499
12055
|
}
|
|
11500
|
-
/**
|
|
11501
|
-
* Extract every top-level JSON object embedded in free-form agent text.
|
|
11502
|
-
* Scans for balanced braces while respecting string literals and escape
|
|
11503
|
-
* sequences, so prose, markdown headers, and code fences around the JSON
|
|
11504
|
-
* are skipped. Malformed spans are dropped silently rather than thrown.
|
|
11505
|
-
*/
|
|
11506
12056
|
extractJsonObjects(text) {
|
|
11507
12057
|
const objects = [];
|
|
11508
12058
|
let depth = 0;
|
|
@@ -11539,25 +12089,33 @@ var CollabSession = class extends EventEmitter {
|
|
|
11539
12089
|
}
|
|
11540
12090
|
return objects;
|
|
11541
12091
|
}
|
|
12092
|
+
budgetForRole(role) {
|
|
12093
|
+
if (this.options.budgetOverrides?.[role]) {
|
|
12094
|
+
return this.options.budgetOverrides[role];
|
|
12095
|
+
}
|
|
12096
|
+
const defaults = {
|
|
12097
|
+
"bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
|
|
12098
|
+
"refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
|
|
12099
|
+
"critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
|
|
12100
|
+
};
|
|
12101
|
+
return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
|
|
12102
|
+
}
|
|
11542
12103
|
async spawnAgent(role, taskBrief) {
|
|
12104
|
+
const budget = this.budgetForRole(role);
|
|
11543
12105
|
const cfg = {
|
|
11544
12106
|
id: `${role}-${this.sessionId}`,
|
|
11545
12107
|
name: role,
|
|
11546
|
-
role
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
12108
|
+
role,
|
|
12109
|
+
tools: ["fleet_emit", "fleet_status", "read", "grep", "glob", "bash", "write"],
|
|
12110
|
+
maxIterations: budget.maxIterations,
|
|
12111
|
+
maxToolCalls: budget.maxToolCalls,
|
|
12112
|
+
timeoutMs: budget.timeoutMs
|
|
11550
12113
|
};
|
|
11551
12114
|
const subagentId = await this.director.spawn(cfg);
|
|
11552
|
-
await this.director.assign({
|
|
11553
|
-
id: randomUUID(),
|
|
11554
|
-
subagentId,
|
|
11555
|
-
description: taskBrief
|
|
11556
|
-
});
|
|
12115
|
+
await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
|
|
11557
12116
|
return subagentId;
|
|
11558
12117
|
}
|
|
11559
12118
|
buildBugHunterTask() {
|
|
11560
|
-
this.options.targetPaths.join(", ");
|
|
11561
12119
|
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
11562
12120
|
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
11563
12121
|
${f.content}`).join("\n\n");
|
|
@@ -11566,13 +12124,17 @@ ${f.content}`).join("\n\n");
|
|
|
11566
12124
|
Target files:
|
|
11567
12125
|
${fileContents}
|
|
11568
12126
|
|
|
11569
|
-
For each bug found,
|
|
11570
|
-
{ "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } }
|
|
11571
|
-
|
|
11572
|
-
|
|
12127
|
+
For each bug found, emit it using the fleet_emit tool immediately:
|
|
12128
|
+
{ "type": "bug.found", "payload": { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } } }
|
|
12129
|
+
|
|
12130
|
+
After scanning all files, write your full markdown bug report to:
|
|
12131
|
+
${scratchpad}/bug-hunter-report-${this.sessionId}.md
|
|
12132
|
+
|
|
12133
|
+
Important: emit each finding as soon as you find it. Do not batch or wait until the end.`;
|
|
11573
12134
|
}
|
|
11574
12135
|
buildRefactorPlannerTask() {
|
|
11575
12136
|
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
12137
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
11576
12138
|
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
11577
12139
|
${f.content}`).join("\n\n");
|
|
11578
12140
|
return `You are RefactorPlanner. Plan refactorings for the following files.
|
|
@@ -11580,26 +12142,146 @@ ${f.content}`).join("\n\n");
|
|
|
11580
12142
|
Target files:
|
|
11581
12143
|
${fileContents}
|
|
11582
12144
|
|
|
11583
|
-
|
|
11584
|
-
|
|
11585
|
-
|
|
11586
|
-
|
|
12145
|
+
Read the BugHunter report at: ${bugHunterReportPath}
|
|
12146
|
+
|
|
12147
|
+
For each bug you can address, emit a refactor plan using fleet_emit:
|
|
12148
|
+
{ "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>" } } }
|
|
12149
|
+
|
|
12150
|
+
Also write your full markdown plan to:
|
|
12151
|
+
${scratchpad}/refactor-plan-${this.sessionId}.md
|
|
12152
|
+
|
|
12153
|
+
Emit each plan immediately. Do not wait until planning is complete.`;
|
|
11587
12154
|
}
|
|
11588
12155
|
buildCriticTask() {
|
|
11589
12156
|
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
12157
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
12158
|
+
const refactorPlanPath = `${scratchpad}/refactor-plan-${this.sessionId}.md`;
|
|
11590
12159
|
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
11591
12160
|
${f.content}`).join("\n\n");
|
|
11592
|
-
return `You are Critic. Evaluate bug findings and refactor plans
|
|
12161
|
+
return `You are Critic. Evaluate bug findings and refactor plans.
|
|
11593
12162
|
|
|
11594
12163
|
Target files:
|
|
11595
12164
|
${fileContents}
|
|
11596
12165
|
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
12166
|
+
Read the BugHunter report at: ${bugHunterReportPath}
|
|
12167
|
+
Read the RefactorPlanner report at: ${refactorPlanPath}
|
|
12168
|
+
|
|
12169
|
+
For each bug and refactor plan, emit your evaluation using fleet_emit:
|
|
12170
|
+
{ "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>" }] } } }
|
|
12171
|
+
|
|
12172
|
+
After all evaluations, write your markdown report to:
|
|
12173
|
+
${scratchpad}/critic-report-${this.sessionId}.md
|
|
12174
|
+
|
|
12175
|
+
Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
11601
12176
|
}
|
|
11602
12177
|
wireFleetBus() {
|
|
12178
|
+
const dTool = this.fleetBus.filter("tool.executed", (e) => {
|
|
12179
|
+
this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
|
|
12180
|
+
});
|
|
12181
|
+
this.disposers.push(dTool);
|
|
12182
|
+
const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
|
|
12183
|
+
const payload = e.payload;
|
|
12184
|
+
const role = this.roleFromSubagentId(e.subagentId);
|
|
12185
|
+
if (!role) return;
|
|
12186
|
+
const btwNotes = this.director.getLeaderBtwNotes();
|
|
12187
|
+
const alert = {
|
|
12188
|
+
sessionId: this.sessionId,
|
|
12189
|
+
subagentId: e.subagentId,
|
|
12190
|
+
role,
|
|
12191
|
+
level: "warning" /* WARNING */,
|
|
12192
|
+
message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
|
|
12193
|
+
budgetKind: payload.kind,
|
|
12194
|
+
elapsedMs: payload.timeoutMs,
|
|
12195
|
+
limit: payload.limit,
|
|
12196
|
+
btwNotes
|
|
12197
|
+
};
|
|
12198
|
+
this.alerts.push(alert);
|
|
12199
|
+
this.fleetBus.emit({
|
|
12200
|
+
subagentId: e.subagentId,
|
|
12201
|
+
ts: Date.now(),
|
|
12202
|
+
type: "collab.warning",
|
|
12203
|
+
payload: alert
|
|
12204
|
+
});
|
|
12205
|
+
const decision = this.options.onBudgetWarning?.(alert) ?? "ignore";
|
|
12206
|
+
if (decision === "cancel") {
|
|
12207
|
+
this.cancel(`Director cancelled: ${role} ${payload.kind} threshold`);
|
|
12208
|
+
return;
|
|
12209
|
+
}
|
|
12210
|
+
if (payload.kind === "timeout") {
|
|
12211
|
+
const progress = this.progressBySubagent.get(e.subagentId) ?? 0;
|
|
12212
|
+
const lastProgress = this.lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
12213
|
+
if (progress <= lastProgress) {
|
|
12214
|
+
payload.deny();
|
|
12215
|
+
return;
|
|
12216
|
+
}
|
|
12217
|
+
this.lastTimeoutProgress.set(e.subagentId, progress);
|
|
12218
|
+
const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
|
|
12219
|
+
setImmediate(() => {
|
|
12220
|
+
payload.extend({ timeoutMs: newLimit });
|
|
12221
|
+
});
|
|
12222
|
+
return;
|
|
12223
|
+
}
|
|
12224
|
+
if (decision === "extend") {
|
|
12225
|
+
setImmediate(() => {
|
|
12226
|
+
const base = Math.max(payload.limit, payload.used);
|
|
12227
|
+
const extra = {};
|
|
12228
|
+
switch (payload.kind) {
|
|
12229
|
+
case "iterations":
|
|
12230
|
+
extra.maxIterations = Math.min(Math.ceil(base * 1.5), 5e4);
|
|
12231
|
+
break;
|
|
12232
|
+
case "tool_calls":
|
|
12233
|
+
extra.maxToolCalls = Math.min(Math.ceil(base * 1.5), 1e5);
|
|
12234
|
+
break;
|
|
12235
|
+
case "tokens":
|
|
12236
|
+
extra.maxTokens = Math.min(Math.ceil(base * 1.5), 5e6);
|
|
12237
|
+
break;
|
|
12238
|
+
case "cost":
|
|
12239
|
+
extra.maxCostUsd = Math.min(base * 1.5, 100);
|
|
12240
|
+
break;
|
|
12241
|
+
}
|
|
12242
|
+
payload.extend(extra);
|
|
12243
|
+
});
|
|
12244
|
+
return;
|
|
12245
|
+
}
|
|
12246
|
+
if (payload.kind !== "timeout") {
|
|
12247
|
+
setImmediate(() => {
|
|
12248
|
+
const base = Math.max(payload.limit, payload.used);
|
|
12249
|
+
const extra = {};
|
|
12250
|
+
switch (payload.kind) {
|
|
12251
|
+
case "iterations":
|
|
12252
|
+
extra.maxIterations = Math.min(Math.ceil(base * 1.25), 5e4);
|
|
12253
|
+
break;
|
|
12254
|
+
case "tool_calls":
|
|
12255
|
+
extra.maxToolCalls = Math.min(Math.ceil(base * 1.25), 1e5);
|
|
12256
|
+
break;
|
|
12257
|
+
case "tokens":
|
|
12258
|
+
extra.maxTokens = Math.min(Math.ceil(base * 1.25), 5e6);
|
|
12259
|
+
break;
|
|
12260
|
+
case "cost":
|
|
12261
|
+
extra.maxCostUsd = Math.min(base * 1.25, 100);
|
|
12262
|
+
break;
|
|
12263
|
+
}
|
|
12264
|
+
payload.extend(extra);
|
|
12265
|
+
});
|
|
12266
|
+
}
|
|
12267
|
+
});
|
|
12268
|
+
this.disposers.push(dBudget);
|
|
12269
|
+
const dCancel = this.fleetBus.filter("director.cancel_collab", (e) => {
|
|
12270
|
+
const payload = e.payload;
|
|
12271
|
+
if (payload.sessionId !== this.sessionId) return;
|
|
12272
|
+
this.cancelled = true;
|
|
12273
|
+
if (this._timeoutTimer) {
|
|
12274
|
+
clearTimeout(this._timeoutTimer);
|
|
12275
|
+
this._timeoutTimer = void 0;
|
|
12276
|
+
}
|
|
12277
|
+
this.fleetBus.emit({
|
|
12278
|
+
subagentId: this.director.id,
|
|
12279
|
+
ts: Date.now(),
|
|
12280
|
+
type: "collab.cancelled",
|
|
12281
|
+
payload: { sessionId: this.sessionId, reason: payload.reason }
|
|
12282
|
+
});
|
|
12283
|
+
});
|
|
12284
|
+
this.disposers.push(dCancel);
|
|
11603
12285
|
const d1 = this.fleetBus.filter("bug.found", (e) => {
|
|
11604
12286
|
const payload = e.payload;
|
|
11605
12287
|
if (payload?.finding) {
|
|
@@ -11625,10 +12307,19 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
|
|
|
11625
12307
|
});
|
|
11626
12308
|
this.disposers.push(d3);
|
|
11627
12309
|
}
|
|
12310
|
+
roleFromSubagentId(subagentId) {
|
|
12311
|
+
for (const [role, id] of this.subagentIds) {
|
|
12312
|
+
if (id === subagentId) return role;
|
|
12313
|
+
}
|
|
12314
|
+
const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
|
|
12315
|
+
return match?.[1] ?? null;
|
|
12316
|
+
}
|
|
11628
12317
|
assembleReport() {
|
|
11629
12318
|
const bugList = Array.from(this.bugs.values());
|
|
11630
12319
|
const planList = Array.from(this.plans.values());
|
|
11631
12320
|
const evalList = Array.from(this.evaluations.values());
|
|
12321
|
+
let disposition = "completed";
|
|
12322
|
+
if (this.cancelled) disposition = "cancelled";
|
|
11632
12323
|
const verdictOrder = {
|
|
11633
12324
|
approve: 0,
|
|
11634
12325
|
needs_revision: 1,
|
|
@@ -11642,33 +12333,41 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
|
|
|
11642
12333
|
},
|
|
11643
12334
|
"approve"
|
|
11644
12335
|
);
|
|
11645
|
-
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict);
|
|
12336
|
+
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
|
|
11646
12337
|
return {
|
|
11647
12338
|
sessionId: this.sessionId,
|
|
11648
12339
|
startedAt: this.snapshot.createdAt,
|
|
11649
12340
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11650
12341
|
targetPaths: this.options.targetPaths,
|
|
12342
|
+
disposition,
|
|
11651
12343
|
bugs: bugList,
|
|
11652
12344
|
refactorPlans: planList,
|
|
11653
12345
|
evaluations: evalList,
|
|
12346
|
+
alerts: [...this.alerts],
|
|
11654
12347
|
overallVerdict,
|
|
11655
12348
|
summary
|
|
11656
12349
|
};
|
|
11657
12350
|
}
|
|
11658
|
-
buildMarkdownSummary(bugs, plans, evals, overallVerdict) {
|
|
12351
|
+
buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
|
|
11659
12352
|
const lines = [
|
|
11660
12353
|
`## Collaborative Debugging Report \u2014 ${this.sessionId}`,
|
|
11661
12354
|
"",
|
|
11662
12355
|
`**Target:** ${this.options.targetPaths.join(", ")}`,
|
|
12356
|
+
`**Disposition:** ${disposition.toUpperCase()}`,
|
|
11663
12357
|
`**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
|
|
11664
12358
|
""
|
|
11665
12359
|
];
|
|
12360
|
+
if (this.alerts.length > 0) {
|
|
12361
|
+
lines.push("### Alerts", "");
|
|
12362
|
+
for (const alert of this.alerts) {
|
|
12363
|
+
lines.push(`- **[${alert.level.toUpperCase()}]** ${alert.role}: ${alert.message}`);
|
|
12364
|
+
}
|
|
12365
|
+
lines.push("");
|
|
12366
|
+
}
|
|
11666
12367
|
if (bugs.length > 0) {
|
|
11667
12368
|
lines.push("### Bugs Found", "");
|
|
11668
12369
|
for (const b of bugs) {
|
|
11669
|
-
lines.push(
|
|
11670
|
-
`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`
|
|
11671
|
-
);
|
|
12370
|
+
lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
|
|
11672
12371
|
}
|
|
11673
12372
|
lines.push("");
|
|
11674
12373
|
}
|
|
@@ -11687,9 +12386,7 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
|
|
|
11687
12386
|
for (const e of evals) {
|
|
11688
12387
|
lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
|
|
11689
12388
|
for (const c of e.concerns) {
|
|
11690
|
-
if (c.severity === "blocking") {
|
|
11691
|
-
lines.push(` - ${c.description}`);
|
|
11692
|
-
}
|
|
12389
|
+
if (c.severity === "blocking") lines.push(` - ${c.description}`);
|
|
11693
12390
|
}
|
|
11694
12391
|
}
|
|
11695
12392
|
lines.push("");
|
|
@@ -11707,10 +12404,9 @@ var FleetSpawnBudgetError = class extends Error {
|
|
|
11707
12404
|
kind;
|
|
11708
12405
|
limit;
|
|
11709
12406
|
observed;
|
|
11710
|
-
constructor(kind, limit, observed) {
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
);
|
|
12407
|
+
constructor(kind, limit, observed, message) {
|
|
12408
|
+
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}`;
|
|
12409
|
+
super(message ?? defaultMsg);
|
|
11714
12410
|
this.name = "FleetSpawnBudgetError";
|
|
11715
12411
|
this.kind = kind;
|
|
11716
12412
|
this.limit = limit;
|
|
@@ -11731,6 +12427,20 @@ var FleetCostCapError = class extends Error {
|
|
|
11731
12427
|
this.observed = observed;
|
|
11732
12428
|
}
|
|
11733
12429
|
};
|
|
12430
|
+
var FleetContextOverflowError = class extends Error {
|
|
12431
|
+
kind;
|
|
12432
|
+
limit;
|
|
12433
|
+
observed;
|
|
12434
|
+
constructor(limit, observed) {
|
|
12435
|
+
super(
|
|
12436
|
+
`Leader context overflow: leader has ${observed} tokens in flight (limit: ${limit}). Compact the leader context before spawning more subagents.`
|
|
12437
|
+
);
|
|
12438
|
+
this.name = "FleetContextOverflowError";
|
|
12439
|
+
this.kind = "max_context_load";
|
|
12440
|
+
this.limit = limit;
|
|
12441
|
+
this.observed = observed;
|
|
12442
|
+
}
|
|
12443
|
+
};
|
|
11734
12444
|
var Director = class {
|
|
11735
12445
|
/** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
|
|
11736
12446
|
get coordinatorId() {
|
|
@@ -11747,6 +12457,25 @@ var Director = class {
|
|
|
11747
12457
|
* injected; otherwise own FleetUsageAggregator.
|
|
11748
12458
|
*/
|
|
11749
12459
|
usage;
|
|
12460
|
+
/**
|
|
12461
|
+
* Update the leader agent's current context pressure (full request tokens:
|
|
12462
|
+
* messages + systemPrompt + toolDefs). The director checks this before every
|
|
12463
|
+
* spawn — if the pressure exceeds `maxLeaderContextLoad * maxContext`,
|
|
12464
|
+
* spawning is refused with a `FleetContextOverflowError`.
|
|
12465
|
+
*
|
|
12466
|
+
* Call this after each leader agent iteration to keep the pressure current.
|
|
12467
|
+
* The compactor's `CompactReport.fullRequestTokensAfter` is a natural source.
|
|
12468
|
+
*/
|
|
12469
|
+
setLeaderContextPressure(tokens) {
|
|
12470
|
+
this.leaderContextPressure = tokens;
|
|
12471
|
+
this.fleetManager?.setLeaderContextPressure(tokens);
|
|
12472
|
+
}
|
|
12473
|
+
/**
|
|
12474
|
+
* Read the leader agent's current context pressure.
|
|
12475
|
+
*/
|
|
12476
|
+
getLeaderContextPressure() {
|
|
12477
|
+
return this.leaderContextPressure;
|
|
12478
|
+
}
|
|
11750
12479
|
/**
|
|
11751
12480
|
* Optional fleet-level policy container. When provided the Director
|
|
11752
12481
|
* delegates spawn budgeting, manifest entries, and checkpointing to it
|
|
@@ -11832,6 +12561,23 @@ var Director = class {
|
|
|
11832
12561
|
taskCompletedListener = null;
|
|
11833
12562
|
/** Optional LLM classifier for smart dispatch. Passed from options. */
|
|
11834
12563
|
dispatchClassifier;
|
|
12564
|
+
/** Leader agent's current context pressure (full request tokens). */
|
|
12565
|
+
leaderContextPressure = 0;
|
|
12566
|
+
/** Maximum context load fraction before spawn is refused. */
|
|
12567
|
+
maxLeaderContextLoad;
|
|
12568
|
+
/** Provider's max context window in tokens. */
|
|
12569
|
+
maxContext;
|
|
12570
|
+
/**
|
|
12571
|
+
* When set by `workComplete()`, the director stops dispatching new tasks
|
|
12572
|
+
* and terminates all running subagents. Used when the director's LLM decides
|
|
12573
|
+
* the goal is satisfied and no further spawns are needed — prevents the
|
|
12574
|
+
* coordinator from keeping workers alive for tasks that will never arrive.
|
|
12575
|
+
*/
|
|
12576
|
+
workCompleteFlag = false;
|
|
12577
|
+
/** Pending /btw notes stashed by the leader agent (see setLeaderBtwNote). */
|
|
12578
|
+
_leaderBtwNotes = [];
|
|
12579
|
+
/** Active collab sessions tracked by sessionId (see spawnCollab). */
|
|
12580
|
+
_activeCollabSessions = /* @__PURE__ */ new Map();
|
|
11835
12581
|
constructor(opts) {
|
|
11836
12582
|
this.id = opts.config.coordinatorId || randomUUID();
|
|
11837
12583
|
this.manifestPath = opts.manifestPath;
|
|
@@ -11847,6 +12593,8 @@ var Director = class {
|
|
|
11847
12593
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
11848
12594
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
11849
12595
|
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
|
|
12596
|
+
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
12597
|
+
this.maxContext = opts.maxContext ?? 128e3;
|
|
11850
12598
|
this.sessionsRoot = opts.sessionsRoot;
|
|
11851
12599
|
this.directorRunId = opts.directorRunId ?? this.id;
|
|
11852
12600
|
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
|
|
@@ -11874,7 +12622,7 @@ var Director = class {
|
|
|
11874
12622
|
this.fleet = new FleetBus();
|
|
11875
12623
|
this.usage = new FleetUsageAggregator(
|
|
11876
12624
|
this.fleet,
|
|
11877
|
-
(
|
|
12625
|
+
(_id, provider, model) => {
|
|
11878
12626
|
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
11879
12627
|
return void 0;
|
|
11880
12628
|
},
|
|
@@ -11936,6 +12684,9 @@ var Director = class {
|
|
|
11936
12684
|
});
|
|
11937
12685
|
this.fleet.filter("budget.threshold_reached", (e) => {
|
|
11938
12686
|
const payload = e.payload;
|
|
12687
|
+
if (e.subagentId.startsWith("bug-hunter-") || e.subagentId.startsWith("refactor-planner-") || e.subagentId.startsWith("critic-")) {
|
|
12688
|
+
return;
|
|
12689
|
+
}
|
|
11939
12690
|
if (payload.kind === "timeout") {
|
|
11940
12691
|
const progress = progressBySubagent.get(e.subagentId) ?? 0;
|
|
11941
12692
|
const lastProgress = lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
@@ -12014,6 +12765,107 @@ var Director = class {
|
|
|
12014
12765
|
extensionsFor(subagentId) {
|
|
12015
12766
|
return this.extendTotals.get(subagentId) ?? 0;
|
|
12016
12767
|
}
|
|
12768
|
+
/**
|
|
12769
|
+
* Signal that the director's work is done. Once called:
|
|
12770
|
+
* - `spawn()` throws `FleetSpawnBudgetError('max_spawns', …)` — no new
|
|
12771
|
+
* subagents can be created
|
|
12772
|
+
* - Running subagents are NOT forcibly stopped — they finish naturally,
|
|
12773
|
+
* but no new tasks are dispatched to them
|
|
12774
|
+
*
|
|
12775
|
+
* This lets the director LLM say "I'm satisfied with the results, stop
|
|
12776
|
+
* spawning and wind down" — without killing in-flight work mid-execution.
|
|
12777
|
+
* Call `terminateAll()` separately if you need immediate teardown.
|
|
12778
|
+
*
|
|
12779
|
+
* Idempotent — calling twice is a no-op.
|
|
12780
|
+
*/
|
|
12781
|
+
workComplete() {
|
|
12782
|
+
this.workCompleteFlag = true;
|
|
12783
|
+
this.fleet.emit({ subagentId: this.id, ts: Date.now(), type: "director.work_complete", payload: {} });
|
|
12784
|
+
}
|
|
12785
|
+
/** Returns true if `workComplete()` has been called on this director. */
|
|
12786
|
+
isWorkComplete() {
|
|
12787
|
+
return this.workCompleteFlag;
|
|
12788
|
+
}
|
|
12789
|
+
/**
|
|
12790
|
+
* Stashes a /btw note on the leader agent's context. The leader's agent loop
|
|
12791
|
+
* calls `consumeBtwNotes()` at each iteration boundary and folds pending notes
|
|
12792
|
+
* into the conversation as a visible block — no abort, no restart, just a
|
|
12793
|
+
* "by the way" nudge the model picks up on its next turn.
|
|
12794
|
+
*
|
|
12795
|
+
* This is the entry point for the host (CLI, TUI) to inject /btw notes
|
|
12796
|
+
* programmatically without going through the slash-command path.
|
|
12797
|
+
*/
|
|
12798
|
+
setLeaderBtwNote(note) {
|
|
12799
|
+
const trimmed = note.trim();
|
|
12800
|
+
if (!trimmed) return this._leaderBtwNotes.length;
|
|
12801
|
+
this._leaderBtwNotes = [...this._leaderBtwNotes, trimmed].slice(-20);
|
|
12802
|
+
return this._leaderBtwNotes.length;
|
|
12803
|
+
}
|
|
12804
|
+
/**
|
|
12805
|
+
* Read and clear all pending /btw notes the leader has stashed.
|
|
12806
|
+
* Returns them in FIFO order (empty array when none).
|
|
12807
|
+
*
|
|
12808
|
+
* Called by CollabSession when a budget threshold event fires so the
|
|
12809
|
+
* Director can inspect accumulated /btw notes before deciding whether
|
|
12810
|
+
* to cancel the collab session or let it continue.
|
|
12811
|
+
*/
|
|
12812
|
+
getLeaderBtwNotes() {
|
|
12813
|
+
const notes = this._leaderBtwNotes;
|
|
12814
|
+
this._leaderBtwNotes = [];
|
|
12815
|
+
return notes;
|
|
12816
|
+
}
|
|
12817
|
+
/**
|
|
12818
|
+
* Peek at pending /btw notes without consuming them.
|
|
12819
|
+
* Useful for UI to show "N pending notes" without clearing them.
|
|
12820
|
+
*/
|
|
12821
|
+
peekLeaderBtwNotes() {
|
|
12822
|
+
return [...this._leaderBtwNotes];
|
|
12823
|
+
}
|
|
12824
|
+
/**
|
|
12825
|
+
* Drain (read + clear) all /btw notes in one call.
|
|
12826
|
+
* Alias for getLeaderBtwNotes() — kept for symmetry with consumeBtwNotes()
|
|
12827
|
+
* in the agent's btw.ts. The Director calls this at the point where it
|
|
12828
|
+
* makes a cancellation decision, not on every budget event.
|
|
12829
|
+
*/
|
|
12830
|
+
drainLeaderBtwNotes() {
|
|
12831
|
+
return this.getLeaderBtwNotes();
|
|
12832
|
+
}
|
|
12833
|
+
/**
|
|
12834
|
+
* Cancel an active collab session by its id.
|
|
12835
|
+
* Emits `director.cancel_collab` on the FleetBus so the session's agents
|
|
12836
|
+
* finish early with a 'cancelled' disposition.
|
|
12837
|
+
*
|
|
12838
|
+
* Returns silently if the session id is not tracked or already settled.
|
|
12839
|
+
* The CollabDebugReport will reflect 'cancelled' disposition when awaited.
|
|
12840
|
+
*/
|
|
12841
|
+
cancelCollabSession(sessionId, reason = "Director cancelled") {
|
|
12842
|
+
const session = this._activeCollabSessions.get(sessionId);
|
|
12843
|
+
if (!session || session.isCancelled()) return;
|
|
12844
|
+
session.cancel(reason);
|
|
12845
|
+
for (const [_role, subagentId] of session.getSubagentIds()) {
|
|
12846
|
+
this.coordinator.stop(subagentId).catch(() => {
|
|
12847
|
+
});
|
|
12848
|
+
}
|
|
12849
|
+
}
|
|
12850
|
+
/**
|
|
12851
|
+
* Subscribe a callback to be notified whenever a collab session raises
|
|
12852
|
+
* an alert (warning level). The callback receives the full DirectorAlert
|
|
12853
|
+
* payload so the host (CLI, TUI) can display it to the user.
|
|
12854
|
+
* Returns an unsubscribe function.
|
|
12855
|
+
*/
|
|
12856
|
+
onCollabAlert(handler) {
|
|
12857
|
+
return this.fleet.filter("collab.warning", (e) => {
|
|
12858
|
+
handler(e.payload);
|
|
12859
|
+
});
|
|
12860
|
+
}
|
|
12861
|
+
/**
|
|
12862
|
+
* Returns all active (not yet settled) collab session ids.
|
|
12863
|
+
* Useful for the TUI to render a "N active sessions" badge and for
|
|
12864
|
+
* the host to know what can be cancelled.
|
|
12865
|
+
*/
|
|
12866
|
+
activeCollabSessions() {
|
|
12867
|
+
return Array.from(this._activeCollabSessions.keys());
|
|
12868
|
+
}
|
|
12017
12869
|
/** Best-effort session-writer append. Swallows failures — the director
|
|
12018
12870
|
* must not break a fleet run because the session JSONL handle closed. */
|
|
12019
12871
|
async appendSessionEvent(event) {
|
|
@@ -12052,12 +12904,21 @@ var Director = class {
|
|
|
12052
12904
|
* it the `cost` column in `usage.snapshot()` stays at 0.
|
|
12053
12905
|
*/
|
|
12054
12906
|
async spawn(config, priceLookup) {
|
|
12907
|
+
if (this.workCompleteFlag) {
|
|
12908
|
+
throw new FleetSpawnBudgetError(
|
|
12909
|
+
"max_spawns",
|
|
12910
|
+
this.maxSpawns,
|
|
12911
|
+
this.spawnCount + 1,
|
|
12912
|
+
"workComplete() has been called \u2014 director closed further spawning"
|
|
12913
|
+
);
|
|
12914
|
+
}
|
|
12055
12915
|
if (this.fleetManager) {
|
|
12056
12916
|
const rejection = this.fleetManager.canSpawn(config);
|
|
12057
12917
|
if (rejection) {
|
|
12058
12918
|
if (rejection.kind === "max_spawn_depth") throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
|
|
12059
12919
|
if (rejection.kind === "max_spawns") throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
|
|
12060
12920
|
if (rejection.kind === "max_cost_usd") throw new FleetCostCapError(rejection.limit, rejection.observed);
|
|
12921
|
+
if (rejection.kind === "max_context_load") throw new FleetContextOverflowError(rejection.limit, rejection.observed);
|
|
12061
12922
|
}
|
|
12062
12923
|
} else {
|
|
12063
12924
|
if (this.spawnDepth >= this.maxSpawnDepth) {
|
|
@@ -12072,9 +12933,15 @@ var Director = class {
|
|
|
12072
12933
|
throw new FleetCostCapError(this.maxFleetCostUsd, totalCost);
|
|
12073
12934
|
}
|
|
12074
12935
|
}
|
|
12936
|
+
if (this.maxLeaderContextLoad < 1) {
|
|
12937
|
+
const threshold = this.maxContext * this.maxLeaderContextLoad;
|
|
12938
|
+
if (this.leaderContextPressure >= threshold) {
|
|
12939
|
+
throw new FleetContextOverflowError(threshold, this.leaderContextPressure);
|
|
12940
|
+
}
|
|
12941
|
+
}
|
|
12075
12942
|
}
|
|
12076
12943
|
let result;
|
|
12077
|
-
const needsNickname = config.name === config.role || !config.name || config.name === "subagent";
|
|
12944
|
+
const needsNickname = config.name === config.role || !config.name || config.name === "subagent" || config.name === "adhoc";
|
|
12078
12945
|
if (needsNickname) {
|
|
12079
12946
|
const role = config.role ?? "subagent";
|
|
12080
12947
|
if (this.fleetManager) {
|
|
@@ -12299,6 +13166,24 @@ var Director = class {
|
|
|
12299
13166
|
*/
|
|
12300
13167
|
async assign(task) {
|
|
12301
13168
|
const taskWithId = task.id ? task : { ...task, id: randomUUID() };
|
|
13169
|
+
if (this.workCompleteFlag) {
|
|
13170
|
+
const synthetic = {
|
|
13171
|
+
subagentId: taskWithId.subagentId ?? "unassigned",
|
|
13172
|
+
taskId: taskWithId.id,
|
|
13173
|
+
status: "stopped",
|
|
13174
|
+
error: { kind: "aborted_by_parent", message: "Director called workComplete() \u2014 no further tasks will run", retryable: false },
|
|
13175
|
+
iterations: 0,
|
|
13176
|
+
toolCalls: 0,
|
|
13177
|
+
durationMs: 0
|
|
13178
|
+
};
|
|
13179
|
+
this.completed.set(taskWithId.id, synthetic);
|
|
13180
|
+
const waiter = this.taskWaiters.get(taskWithId.id);
|
|
13181
|
+
if (waiter) {
|
|
13182
|
+
waiter.resolve(synthetic);
|
|
13183
|
+
this.taskWaiters.delete(taskWithId.id);
|
|
13184
|
+
}
|
|
13185
|
+
return taskWithId.id;
|
|
13186
|
+
}
|
|
12302
13187
|
if (task.subagentId) {
|
|
12303
13188
|
if (this.fleetManager) {
|
|
12304
13189
|
this.fleetManager.addTaskToSubagent(task.subagentId, taskWithId.id);
|
|
@@ -12521,12 +13406,14 @@ var Director = class {
|
|
|
12521
13406
|
makeAskTool(this),
|
|
12522
13407
|
makeRollUpTool(this),
|
|
12523
13408
|
makeTerminateTool(this),
|
|
13409
|
+
makeTerminateAllTool(this),
|
|
12524
13410
|
makeFleetStatusTool(this),
|
|
12525
13411
|
makeFleetUsageTool(this),
|
|
12526
13412
|
makeFleetSessionTool(this),
|
|
12527
13413
|
makeFleetHealthTool(this),
|
|
12528
13414
|
makeCollabDebugTool(this),
|
|
12529
|
-
makeFleetEmitTool(this)
|
|
13415
|
+
makeFleetEmitTool(this),
|
|
13416
|
+
makeWorkCompleteTool(this)
|
|
12530
13417
|
];
|
|
12531
13418
|
return t;
|
|
12532
13419
|
}
|
|
@@ -12546,7 +13433,15 @@ var Director = class {
|
|
|
12546
13433
|
* three agents complete or the session times out.
|
|
12547
13434
|
*/
|
|
12548
13435
|
async spawnCollab(options) {
|
|
12549
|
-
const session = new CollabSession(this, this.fleet,
|
|
13436
|
+
const session = new CollabSession(this, this.fleet, {
|
|
13437
|
+
...options,
|
|
13438
|
+
onBudgetWarning: (alert) => {
|
|
13439
|
+
return options.onBudgetWarning?.(alert) ?? "ignore";
|
|
13440
|
+
}
|
|
13441
|
+
});
|
|
13442
|
+
this._activeCollabSessions.set(session.sessionId, session);
|
|
13443
|
+
session.on("session.done", () => this._activeCollabSessions.delete(session.sessionId));
|
|
13444
|
+
session.on("session.error", () => this._activeCollabSessions.delete(session.sessionId));
|
|
12550
13445
|
return session.start();
|
|
12551
13446
|
}
|
|
12552
13447
|
/**
|
|
@@ -12963,6 +13858,71 @@ var NULL_FLEET_BUS = new FleetBus();
|
|
|
12963
13858
|
|
|
12964
13859
|
// src/models/models-registry.ts
|
|
12965
13860
|
init_atomic_write();
|
|
13861
|
+
|
|
13862
|
+
// src/utils/merge-models-payload.ts
|
|
13863
|
+
function mergeModelsPayload(base, overlay) {
|
|
13864
|
+
const out = {};
|
|
13865
|
+
for (const [id, provider] of Object.entries(base)) {
|
|
13866
|
+
out[id] = cloneProvider(provider);
|
|
13867
|
+
}
|
|
13868
|
+
for (const [id, ovProvider] of Object.entries(overlay)) {
|
|
13869
|
+
const existing = out[id];
|
|
13870
|
+
out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
|
|
13871
|
+
}
|
|
13872
|
+
return out;
|
|
13873
|
+
}
|
|
13874
|
+
function mergeProvider(base, overlay) {
|
|
13875
|
+
const models = {};
|
|
13876
|
+
for (const [mid, m] of Object.entries(base.models ?? {})) {
|
|
13877
|
+
models[mid] = { ...m };
|
|
13878
|
+
}
|
|
13879
|
+
for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
|
|
13880
|
+
const existing = models[mid];
|
|
13881
|
+
models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
|
|
13882
|
+
}
|
|
13883
|
+
return {
|
|
13884
|
+
...base,
|
|
13885
|
+
// Overlay scalar fields win when explicitly provided; otherwise keep base.
|
|
13886
|
+
...stripUndefined({
|
|
13887
|
+
id: overlay.id,
|
|
13888
|
+
name: overlay.name,
|
|
13889
|
+
npm: overlay.npm,
|
|
13890
|
+
api: overlay.api,
|
|
13891
|
+
env: overlay.env,
|
|
13892
|
+
doc: overlay.doc
|
|
13893
|
+
}),
|
|
13894
|
+
models
|
|
13895
|
+
};
|
|
13896
|
+
}
|
|
13897
|
+
function mergeModel(base, overlay) {
|
|
13898
|
+
const merged = { ...base, ...overlay };
|
|
13899
|
+
if (base.limit || overlay.limit) {
|
|
13900
|
+
merged.limit = { ...base.limit, ...overlay.limit };
|
|
13901
|
+
}
|
|
13902
|
+
if (base.cost || overlay.cost) {
|
|
13903
|
+
merged.cost = { ...base.cost, ...overlay.cost };
|
|
13904
|
+
}
|
|
13905
|
+
if (base.modalities || overlay.modalities) {
|
|
13906
|
+
merged.modalities = { ...base.modalities, ...overlay.modalities };
|
|
13907
|
+
}
|
|
13908
|
+
return merged;
|
|
13909
|
+
}
|
|
13910
|
+
function cloneProvider(p) {
|
|
13911
|
+
const models = {};
|
|
13912
|
+
for (const [mid, m] of Object.entries(p.models ?? {})) {
|
|
13913
|
+
models[mid] = { ...m };
|
|
13914
|
+
}
|
|
13915
|
+
return { ...p, models };
|
|
13916
|
+
}
|
|
13917
|
+
function stripUndefined(obj) {
|
|
13918
|
+
const out = {};
|
|
13919
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
13920
|
+
if (v !== void 0) out[k] = v;
|
|
13921
|
+
}
|
|
13922
|
+
return out;
|
|
13923
|
+
}
|
|
13924
|
+
|
|
13925
|
+
// src/models/models-registry.ts
|
|
12966
13926
|
var DEFAULT_URL = "https://models.dev/api.json";
|
|
12967
13927
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
12968
13928
|
var FAMILY_BY_NPM = {
|
|
@@ -12990,7 +13950,10 @@ function classifyFamily(npm) {
|
|
|
12990
13950
|
return FAMILY_BY_NPM[npm] ?? "unsupported";
|
|
12991
13951
|
}
|
|
12992
13952
|
var DefaultModelsRegistry = class {
|
|
13953
|
+
/** Merged (base + overlay) payload — what every reader sees. */
|
|
12993
13954
|
payload;
|
|
13955
|
+
/** Memoised overlay payload (in-memory / fetched / file). */
|
|
13956
|
+
overlayPayload;
|
|
12994
13957
|
fetchedAt;
|
|
12995
13958
|
cacheFile;
|
|
12996
13959
|
url;
|
|
@@ -12998,6 +13961,10 @@ var DefaultModelsRegistry = class {
|
|
|
12998
13961
|
fetchImpl;
|
|
12999
13962
|
seed;
|
|
13000
13963
|
maxStaleAgeMs;
|
|
13964
|
+
overlay;
|
|
13965
|
+
overlayUrl;
|
|
13966
|
+
overlayFile;
|
|
13967
|
+
overlayCacheFile;
|
|
13001
13968
|
constructor(opts) {
|
|
13002
13969
|
this.cacheFile = opts.cacheFile;
|
|
13003
13970
|
this.url = opts.url ?? DEFAULT_URL;
|
|
@@ -13006,6 +13973,10 @@ var DefaultModelsRegistry = class {
|
|
|
13006
13973
|
this.seed = opts.seed;
|
|
13007
13974
|
const maxStaleSeconds = opts.maxStaleAgeSeconds ?? 7 * 24 * 3600;
|
|
13008
13975
|
this.maxStaleAgeMs = maxStaleSeconds * 1e3;
|
|
13976
|
+
this.overlay = opts.overlay;
|
|
13977
|
+
this.overlayUrl = opts.overlayUrl;
|
|
13978
|
+
this.overlayFile = opts.overlayFile;
|
|
13979
|
+
this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path15.join(path15.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
|
|
13009
13980
|
}
|
|
13010
13981
|
async load(opts = {}) {
|
|
13011
13982
|
if (this.payload && !opts.force) return this.payload;
|
|
@@ -13014,27 +13985,45 @@ var DefaultModelsRegistry = class {
|
|
|
13014
13985
|
this.fetchedAt = /* @__PURE__ */ new Date();
|
|
13015
13986
|
return this.payload;
|
|
13016
13987
|
}
|
|
13988
|
+
const overlay = await this.loadOverlay(opts);
|
|
13989
|
+
const base = await this.loadBase(opts, Object.keys(overlay).length > 0);
|
|
13990
|
+
this.payload = mergeModelsPayload(base, overlay);
|
|
13991
|
+
return this.payload;
|
|
13992
|
+
}
|
|
13993
|
+
/**
|
|
13994
|
+
* Load the models.dev base payload: fresh cache → network → stale cache.
|
|
13995
|
+
* On total failure, degrade to `{}` (so a non-empty overlay still drives
|
|
13996
|
+
* the catalog) rather than throwing — unless there's no curated overlay to
|
|
13997
|
+
* fall back on, in which case the original error propagates so pure-
|
|
13998
|
+
* models.dev setups still surface the problem.
|
|
13999
|
+
*/
|
|
14000
|
+
async loadBase(opts = {}, overlayAvailable = false) {
|
|
13017
14001
|
if (!opts.force) {
|
|
13018
|
-
const cached = await this.
|
|
14002
|
+
const cached = await this.readCacheAt(this.cacheFile);
|
|
13019
14003
|
if (cached && this.isFresh(cached.fetchedAt)) {
|
|
13020
|
-
this.payload = cached.payload;
|
|
13021
14004
|
this.fetchedAt = new Date(cached.fetchedAt);
|
|
13022
14005
|
return cached.payload;
|
|
13023
14006
|
}
|
|
13024
14007
|
}
|
|
13025
14008
|
try {
|
|
13026
|
-
return await this.
|
|
14009
|
+
return await this.refreshBase();
|
|
13027
14010
|
} catch (err) {
|
|
13028
|
-
const cached = await this.
|
|
14011
|
+
const cached = await this.readCacheAt(this.cacheFile);
|
|
13029
14012
|
if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
|
|
13030
|
-
this.payload = cached.payload;
|
|
13031
14013
|
this.fetchedAt = new Date(cached.fetchedAt);
|
|
13032
14014
|
return cached.payload;
|
|
13033
14015
|
}
|
|
14016
|
+
if (overlayAvailable) {
|
|
14017
|
+
console.warn(
|
|
14018
|
+
`ModelsRegistry: models.dev unavailable (${err instanceof Error ? err.message : String(err)}); serving curated overlay only.`
|
|
14019
|
+
);
|
|
14020
|
+
return {};
|
|
14021
|
+
}
|
|
13034
14022
|
throw err;
|
|
13035
14023
|
}
|
|
13036
14024
|
}
|
|
13037
|
-
|
|
14025
|
+
/** Fetch + cache the models.dev base. Throws on failure (used by `refresh`). */
|
|
14026
|
+
async refreshBase() {
|
|
13038
14027
|
const res = await this.fetchImpl(this.url, {
|
|
13039
14028
|
method: "GET",
|
|
13040
14029
|
headers: { accept: "application/json" }
|
|
@@ -13043,7 +14032,6 @@ var DefaultModelsRegistry = class {
|
|
|
13043
14032
|
throw new Error(`ModelsRegistry: HTTP ${res.status} fetching ${this.url}`);
|
|
13044
14033
|
}
|
|
13045
14034
|
const json = await res.json();
|
|
13046
|
-
this.payload = json;
|
|
13047
14035
|
this.fetchedAt = /* @__PURE__ */ new Date();
|
|
13048
14036
|
const envelope = {
|
|
13049
14037
|
fetchedAt: this.fetchedAt.toISOString(),
|
|
@@ -13053,6 +14041,68 @@ var DefaultModelsRegistry = class {
|
|
|
13053
14041
|
await atomicWrite(this.cacheFile, JSON.stringify(envelope));
|
|
13054
14042
|
return json;
|
|
13055
14043
|
}
|
|
14044
|
+
/**
|
|
14045
|
+
* Resolve the curated overlay, memoised. Order: in-memory `overlay` →
|
|
14046
|
+
* fetched `overlayUrl` (cached, same TTL/stale rules) → `overlayFile` on
|
|
14047
|
+
* disk. Never throws — a missing/broken overlay yields `{}`.
|
|
14048
|
+
*/
|
|
14049
|
+
async loadOverlay(opts = {}) {
|
|
14050
|
+
if (this.overlayPayload && !opts.force) return this.overlayPayload;
|
|
14051
|
+
if (this.overlay) {
|
|
14052
|
+
this.overlayPayload = this.overlay;
|
|
14053
|
+
return this.overlayPayload;
|
|
14054
|
+
}
|
|
14055
|
+
const fetched = await this.loadOverlayFromUrl(opts);
|
|
14056
|
+
if (fetched) {
|
|
14057
|
+
this.overlayPayload = fetched;
|
|
14058
|
+
return fetched;
|
|
14059
|
+
}
|
|
14060
|
+
const fromFile = await this.readOverlayFile();
|
|
14061
|
+
this.overlayPayload = fromFile ?? {};
|
|
14062
|
+
return this.overlayPayload;
|
|
14063
|
+
}
|
|
14064
|
+
async loadOverlayFromUrl(opts) {
|
|
14065
|
+
if (!this.overlayUrl || !this.overlayCacheFile) return void 0;
|
|
14066
|
+
if (!opts.force) {
|
|
14067
|
+
const cached = await this.readCacheAt(this.overlayCacheFile);
|
|
14068
|
+
if (cached && this.isFresh(cached.fetchedAt)) return cached.payload;
|
|
14069
|
+
}
|
|
14070
|
+
try {
|
|
14071
|
+
const res = await this.fetchImpl(this.overlayUrl, {
|
|
14072
|
+
method: "GET",
|
|
14073
|
+
headers: { accept: "application/json" }
|
|
14074
|
+
});
|
|
14075
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
14076
|
+
const json = await res.json();
|
|
14077
|
+
const envelope = {
|
|
14078
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14079
|
+
url: this.overlayUrl,
|
|
14080
|
+
payload: json
|
|
14081
|
+
};
|
|
14082
|
+
await atomicWrite(this.overlayCacheFile, JSON.stringify(envelope)).catch(() => {
|
|
14083
|
+
});
|
|
14084
|
+
return json;
|
|
14085
|
+
} catch {
|
|
14086
|
+
const cached = await this.readCacheAt(this.overlayCacheFile);
|
|
14087
|
+
if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) return cached.payload;
|
|
14088
|
+
return void 0;
|
|
14089
|
+
}
|
|
14090
|
+
}
|
|
14091
|
+
async readOverlayFile() {
|
|
14092
|
+
if (!this.overlayFile) return void 0;
|
|
14093
|
+
try {
|
|
14094
|
+
const raw = await fsp.readFile(this.overlayFile, "utf8");
|
|
14095
|
+
return JSON.parse(raw);
|
|
14096
|
+
} catch {
|
|
14097
|
+
return void 0;
|
|
14098
|
+
}
|
|
14099
|
+
}
|
|
14100
|
+
async refresh() {
|
|
14101
|
+
const base = await this.refreshBase();
|
|
14102
|
+
const overlay = await this.loadOverlay({ force: true });
|
|
14103
|
+
this.payload = mergeModelsPayload(base, overlay);
|
|
14104
|
+
return this.payload;
|
|
14105
|
+
}
|
|
13056
14106
|
async listProviders() {
|
|
13057
14107
|
const payload = await this.load();
|
|
13058
14108
|
return Object.values(payload).map((p) => this.resolveProvider(p));
|
|
@@ -13093,7 +14143,7 @@ var DefaultModelsRegistry = class {
|
|
|
13093
14143
|
}
|
|
13094
14144
|
async ageSeconds() {
|
|
13095
14145
|
if (!this.fetchedAt) {
|
|
13096
|
-
const cached = await this.
|
|
14146
|
+
const cached = await this.readCacheAt(this.cacheFile);
|
|
13097
14147
|
if (!cached) return Number.POSITIVE_INFINITY;
|
|
13098
14148
|
return (Date.now() - new Date(cached.fetchedAt).getTime()) / 1e3;
|
|
13099
14149
|
}
|
|
@@ -13117,9 +14167,9 @@ var DefaultModelsRegistry = class {
|
|
|
13117
14167
|
isWithinMaxStaleAge(fetchedAtIso) {
|
|
13118
14168
|
return Date.now() - new Date(fetchedAtIso).getTime() < this.maxStaleAgeMs;
|
|
13119
14169
|
}
|
|
13120
|
-
async
|
|
14170
|
+
async readCacheAt(file) {
|
|
13121
14171
|
try {
|
|
13122
|
-
const raw = await fsp.readFile(
|
|
14172
|
+
const raw = await fsp.readFile(file, "utf8");
|
|
13123
14173
|
return JSON.parse(raw);
|
|
13124
14174
|
} catch {
|
|
13125
14175
|
return void 0;
|
|
@@ -15437,7 +16487,7 @@ function analyzeCriticalPath(graph) {
|
|
|
15437
16487
|
blockedTasks
|
|
15438
16488
|
};
|
|
15439
16489
|
}
|
|
15440
|
-
function getTransitiveBlocked(
|
|
16490
|
+
function getTransitiveBlocked(_graph, taskId, blocksMap) {
|
|
15441
16491
|
const visited = /* @__PURE__ */ new Set();
|
|
15442
16492
|
const queue = [taskId];
|
|
15443
16493
|
while (queue.length > 0) {
|
|
@@ -15453,7 +16503,7 @@ function getTransitiveBlocked(graph, taskId, blocksMap) {
|
|
|
15453
16503
|
}
|
|
15454
16504
|
return visited;
|
|
15455
16505
|
}
|
|
15456
|
-
function computeCriticalPath(graph,
|
|
16506
|
+
function computeCriticalPath(graph, _topoOrder, blockedByMap) {
|
|
15457
16507
|
const allIds = Array.from(graph.nodes.keys());
|
|
15458
16508
|
if (allIds.length === 0) return [];
|
|
15459
16509
|
const dist = /* @__PURE__ */ new Map();
|
|
@@ -15871,13 +16921,11 @@ function createAutoExecutor(opts) {
|
|
|
15871
16921
|
|
|
15872
16922
|
// src/sdd/sdd-task-decomposer.ts
|
|
15873
16923
|
var SddTaskDecomposer = class {
|
|
15874
|
-
constructor(tracker,
|
|
16924
|
+
constructor(tracker, _graph, opts = {}) {
|
|
15875
16925
|
this.tracker = tracker;
|
|
15876
|
-
this.graph = graph;
|
|
15877
16926
|
this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
|
|
15878
16927
|
}
|
|
15879
16928
|
tracker;
|
|
15880
|
-
graph;
|
|
15881
16929
|
slots;
|
|
15882
16930
|
wave = 0;
|
|
15883
16931
|
// -------------------------------------------------------------------
|
|
@@ -16030,7 +17078,7 @@ var SddParallelRun = class {
|
|
|
16030
17078
|
this.coordinator.setRunner?.(runner);
|
|
16031
17079
|
}
|
|
16032
17080
|
defaultFactory() {
|
|
16033
|
-
return async (
|
|
17081
|
+
return async (_config) => ({
|
|
16034
17082
|
agent: this.opts.agent,
|
|
16035
17083
|
events: this.opts.agent.events
|
|
16036
17084
|
});
|
|
@@ -16844,6 +17892,12 @@ function roughEstimate(messages) {
|
|
|
16844
17892
|
return total;
|
|
16845
17893
|
}
|
|
16846
17894
|
function createContextManagerTool(opts = {}) {
|
|
17895
|
+
const minCompactThreshold = opts.minCompactThreshold ?? 0;
|
|
17896
|
+
const noopRetryDeltaTokens = opts.noopRetryDeltaTokens ?? 2e3;
|
|
17897
|
+
const maxContext = opts.maxContext ?? 128e3;
|
|
17898
|
+
const compactThresholdFraction = opts.compactThresholdFraction ?? 0.5;
|
|
17899
|
+
const effectiveThreshold = minCompactThreshold > 0 ? minCompactThreshold : Math.floor(maxContext * compactThresholdFraction);
|
|
17900
|
+
let lastNoopTokens = 0;
|
|
16847
17901
|
return {
|
|
16848
17902
|
name: CONTEXT_MANAGER_TOOL_NAME,
|
|
16849
17903
|
description: 'Inspect or reorganize the conversation context window. Use "check" to see token budget. Use "summary" to collapse a message range into a concise note (provide "text" for custom summary). Use "prune" to remove specific messages by index. Use "add_note" to inject a summary note. Use "compact" to run aggressive compaction. Use "repair" to remove orphan tool_use/tool_result blocks after manual context surgery.',
|
|
@@ -16934,11 +17988,41 @@ function createContextManagerTool(opts = {}) {
|
|
|
16934
17988
|
notes: "No compactor registered. Use /compact aggressive via slash command instead."
|
|
16935
17989
|
};
|
|
16936
17990
|
}
|
|
17991
|
+
const fullEstimate = input.systemPrompt != null && Array.isArray(input.tools) ? estimateRequestTokens(messages, input.systemPrompt, input.tools) : { total: beforeTokens};
|
|
17992
|
+
const currentTokens = fullEstimate.total;
|
|
17993
|
+
if (lastNoopTokens > 0) {
|
|
17994
|
+
const delta = currentTokens - lastNoopTokens;
|
|
17995
|
+
if (delta < noopRetryDeltaTokens) {
|
|
17996
|
+
return {
|
|
17997
|
+
action: "compact",
|
|
17998
|
+
beforeTokens,
|
|
17999
|
+
afterTokens: beforeTokens,
|
|
18000
|
+
messageCount: messages.length,
|
|
18001
|
+
notes: `Compact is a NOOP retry: context grew only ${delta} tokens since the last no-op attempt (threshold: ${noopRetryDeltaTokens}). Skip until more content accumulates.`
|
|
18002
|
+
};
|
|
18003
|
+
}
|
|
18004
|
+
}
|
|
18005
|
+
if (currentTokens < effectiveThreshold) {
|
|
18006
|
+
return {
|
|
18007
|
+
action: "compact",
|
|
18008
|
+
beforeTokens,
|
|
18009
|
+
afterTokens: beforeTokens,
|
|
18010
|
+
messageCount: messages.length,
|
|
18011
|
+
notes: `Context tokens (${currentTokens}) below compact threshold (${effectiveThreshold}). Skipping.`
|
|
18012
|
+
};
|
|
18013
|
+
}
|
|
16937
18014
|
const report = await opts.compactor.compact(ctx);
|
|
16938
18015
|
ctx.clearFileTracking();
|
|
16939
18016
|
const repair = applyMessages([...ctx.messages]);
|
|
16940
18017
|
const afterTokens = repair.changed ? roughEstimate(ctx.messages) : report.after;
|
|
16941
18018
|
const repaired = report.repaired ?? (repair.changed ? repair : void 0);
|
|
18019
|
+
const reduced = report.fullRequestTokensBefore > report.fullRequestTokensAfter;
|
|
18020
|
+
const repairedSomething = !!report.repaired;
|
|
18021
|
+
if (reduced || repairedSomething) {
|
|
18022
|
+
lastNoopTokens = 0;
|
|
18023
|
+
} else {
|
|
18024
|
+
lastNoopTokens = currentTokens;
|
|
18025
|
+
}
|
|
16942
18026
|
return {
|
|
16943
18027
|
action: "compact",
|
|
16944
18028
|
beforeTokens,
|
|
@@ -17177,6 +18261,6 @@ var allServers = () => ({
|
|
|
17177
18261
|
"minimax-vision": { ...miniMaxVisionServer(), enabled: false }
|
|
17178
18262
|
});
|
|
17179
18263
|
|
|
17180
|
-
export { AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SddParallelRun, SddTaskDecomposer, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets2 as decryptConfigSecrets, deriveTodosFromPlanItem, dispatchAgent, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, formatPlanTemplates, getAgentDefinition, getPlanTemplate, getTemplate, githubServer, googleMapsServer, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, scoreAgents, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
|
|
18264
|
+
export { AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SddParallelRun, SddTaskDecomposer, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, createSessionEventBridge, decryptConfigSecrets2 as decryptConfigSecrets, deriveTodosFromPlanItem, dispatchAgent, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, formatPlanTemplates, getAgentDefinition, getPlanTemplate, getTemplate, githubServer, googleMapsServer, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, resolveAuditLevel, resolveSessionLoggingConfig, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, scoreAgents, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
|
|
17181
18265
|
//# sourceMappingURL=index.js.map
|
|
17182
18266
|
//# sourceMappingURL=index.js.map
|