@wrongstack/core 0.1.4 → 0.1.8
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 +155 -0
- package/dist/defaults/index.d.ts +557 -33
- package/dist/defaults/index.js +1421 -244
- package/dist/defaults/index.js.map +1 -1
- package/dist/index.d.ts +31 -13
- package/dist/index.js +1614 -334
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +5 -4
- package/dist/kernel/index.js +2 -1
- package/dist/kernel/index.js.map +1 -1
- package/dist/{secret-scrubber-qU3AwEiI.d.ts → mode-Pjt5vMS6.d.ts} +94 -3
- package/dist/{provider-DovtyuM8.d.ts → provider-txgB0Oq9.d.ts} +27 -30
- package/dist/{session-reader-DR4u3bu9.d.ts → session-reader-9sOTgmeC.d.ts} +44 -32
- package/dist/system-prompt-vAB0F54-.d.ts +23 -0
- package/dist/types/index.d.ts +4 -4
- package/dist/types/index.js +34 -15
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +16 -11
- package/dist/utils/index.js +40 -13
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-D24ynAz1.d.ts → wstack-paths-BGu2INTm.d.ts} +7 -0
- package/package.json +7 -4
- package/dist/system-prompt--mzZnenv.d.ts +0 -16
package/dist/defaults/index.js
CHANGED
|
@@ -180,9 +180,11 @@ var DefaultPathResolver = class {
|
|
|
180
180
|
ensureInsideRoot(absPath) {
|
|
181
181
|
const resolved = this.resolve(absPath);
|
|
182
182
|
if (!this.isInsideRoot(resolved)) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
const display = path2.isAbsolute(absPath) ? path2.basename(absPath) : absPath;
|
|
184
|
+
const err = new Error(`Path "${display}" resolves outside the project root`);
|
|
185
|
+
err.fullPath = absPath;
|
|
186
|
+
err.projectRoot = this.projectRoot;
|
|
187
|
+
throw err;
|
|
186
188
|
}
|
|
187
189
|
return resolved;
|
|
188
190
|
}
|
|
@@ -352,7 +354,9 @@ var DefaultSessionStore = class {
|
|
|
352
354
|
try {
|
|
353
355
|
handle = await fsp.open(file, "a", 384);
|
|
354
356
|
} catch (err) {
|
|
355
|
-
throw new Error(`Failed to open session file: ${err instanceof Error ? err.message : String(err)}
|
|
357
|
+
throw new Error(`Failed to open session file: ${err instanceof Error ? err.message : String(err)}`, {
|
|
358
|
+
cause: err
|
|
359
|
+
});
|
|
356
360
|
}
|
|
357
361
|
try {
|
|
358
362
|
return new FileSessionWriter(id, handle, startedAt, meta, { dir: this.dir, filePath: file });
|
|
@@ -370,7 +374,8 @@ var DefaultSessionStore = class {
|
|
|
370
374
|
handle = await fsp.open(file, "a", 384);
|
|
371
375
|
} catch (err) {
|
|
372
376
|
throw new Error(
|
|
373
|
-
`Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}
|
|
377
|
+
`Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}`,
|
|
378
|
+
{ cause: err }
|
|
374
379
|
);
|
|
375
380
|
}
|
|
376
381
|
const writer = new FileSessionWriter(
|
|
@@ -393,7 +398,10 @@ var DefaultSessionStore = class {
|
|
|
393
398
|
const events = [];
|
|
394
399
|
for (const line of lines) {
|
|
395
400
|
try {
|
|
396
|
-
|
|
401
|
+
const parsed = JSON.parse(line);
|
|
402
|
+
if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
|
|
403
|
+
events.push(parsed);
|
|
404
|
+
}
|
|
397
405
|
} catch {
|
|
398
406
|
}
|
|
399
407
|
}
|
|
@@ -410,7 +418,11 @@ var DefaultSessionStore = class {
|
|
|
410
418
|
ids.map((id) => this.summaryFor(id).catch(() => null))
|
|
411
419
|
);
|
|
412
420
|
const out = sessions.filter((s) => s !== null);
|
|
413
|
-
out.sort((a, b) =>
|
|
421
|
+
out.sort((a, b) => {
|
|
422
|
+
if (a.startedAt < b.startedAt) return 1;
|
|
423
|
+
if (a.startedAt > b.startedAt) return -1;
|
|
424
|
+
return a.id.localeCompare(b.id);
|
|
425
|
+
});
|
|
414
426
|
return out.slice(0, limit);
|
|
415
427
|
} catch {
|
|
416
428
|
return [];
|
|
@@ -556,6 +568,8 @@ var FileSessionWriter = class {
|
|
|
556
568
|
filePath;
|
|
557
569
|
initDone = false;
|
|
558
570
|
resumed;
|
|
571
|
+
appendFailCount = 0;
|
|
572
|
+
lastAppendWarnAt = 0;
|
|
559
573
|
async writeSessionStart() {
|
|
560
574
|
if (this.initDone || this.closed) return;
|
|
561
575
|
this.initDone = true;
|
|
@@ -584,7 +598,19 @@ var FileSessionWriter = class {
|
|
|
584
598
|
await this.handle.appendFile(`${JSON.stringify(event)}
|
|
585
599
|
`, "utf8");
|
|
586
600
|
} catch (err) {
|
|
587
|
-
|
|
601
|
+
this.appendFailCount++;
|
|
602
|
+
const now = Date.now();
|
|
603
|
+
if (now - this.lastAppendWarnAt > 5e3) {
|
|
604
|
+
const suppressed = this.appendFailCount - 1;
|
|
605
|
+
const tail = suppressed > 0 ? ` (+${suppressed} suppressed)` : "";
|
|
606
|
+
console.warn(
|
|
607
|
+
"[session] append failed:",
|
|
608
|
+
err instanceof Error ? err.message : String(err),
|
|
609
|
+
tail
|
|
610
|
+
);
|
|
611
|
+
this.lastAppendWarnAt = now;
|
|
612
|
+
this.appendFailCount = 0;
|
|
613
|
+
}
|
|
588
614
|
}
|
|
589
615
|
}
|
|
590
616
|
/**
|
|
@@ -790,6 +816,15 @@ function mergeAdjacentText(blocks) {
|
|
|
790
816
|
var MAX_BYTES_TOTAL = 32e3;
|
|
791
817
|
var DefaultMemoryStore = class {
|
|
792
818
|
files;
|
|
819
|
+
/**
|
|
820
|
+
* Per-scope serialization queue. `remember` / `forget` / `consolidate` /
|
|
821
|
+
* `clear` are read-modify-write against a single file; without a lock,
|
|
822
|
+
* two concurrent calls on the same scope can read the same baseline and
|
|
823
|
+
* the later write silently drops the earlier entry. We chain each
|
|
824
|
+
* mutation onto the prior promise for the same scope so they run in
|
|
825
|
+
* issue order. Different scopes still proceed in parallel.
|
|
826
|
+
*/
|
|
827
|
+
writeChain = /* @__PURE__ */ new Map();
|
|
793
828
|
constructor(opts) {
|
|
794
829
|
this.files = {
|
|
795
830
|
"project-agents": opts.paths.inProjectAgentsFile,
|
|
@@ -797,6 +832,18 @@ var DefaultMemoryStore = class {
|
|
|
797
832
|
"user-memory": opts.paths.globalMemory
|
|
798
833
|
};
|
|
799
834
|
}
|
|
835
|
+
async runSerialized(scope, work) {
|
|
836
|
+
const prior = this.writeChain.get(scope) ?? Promise.resolve();
|
|
837
|
+
const next = prior.catch(() => void 0).then(work);
|
|
838
|
+
this.writeChain.set(scope, next);
|
|
839
|
+
try {
|
|
840
|
+
return await next;
|
|
841
|
+
} finally {
|
|
842
|
+
if (this.writeChain.get(scope) === next) {
|
|
843
|
+
this.writeChain.delete(scope);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
800
847
|
async readAll() {
|
|
801
848
|
const parts = [];
|
|
802
849
|
for (const scope of ["project-agents", "project-memory", "user-memory"]) {
|
|
@@ -815,27 +862,32 @@ ${body.trim()}`);
|
|
|
815
862
|
}
|
|
816
863
|
}
|
|
817
864
|
async remember(text, scope = "project-memory") {
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
865
|
+
return this.runSerialized(scope, async () => {
|
|
866
|
+
const file = this.files[scope];
|
|
867
|
+
await ensureDir(path2.dirname(file));
|
|
868
|
+
let existing = "";
|
|
869
|
+
try {
|
|
870
|
+
existing = await fsp.readFile(file, "utf8");
|
|
871
|
+
} catch {
|
|
872
|
+
}
|
|
873
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
874
|
+
const id = `mem_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
875
|
+
const entry = `
|
|
828
876
|
- [${ts}] ${id} ${text.replace(/\n/g, " ")}
|
|
829
877
|
`;
|
|
830
|
-
|
|
878
|
+
const next = existing.trim() ? existing.replace(/\n+$/, "") + entry : `# WrongStack Memory
|
|
831
879
|
${entry}`;
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
880
|
+
await atomicWrite(file, next);
|
|
881
|
+
const buf = Buffer.byteLength(next, "utf8");
|
|
882
|
+
if (buf > MAX_BYTES_TOTAL) {
|
|
883
|
+
await this.consolidateUnsafe(scope);
|
|
884
|
+
}
|
|
885
|
+
});
|
|
837
886
|
}
|
|
838
887
|
async forget(query, scope = "project-memory") {
|
|
888
|
+
return this.runSerialized(scope, async () => this.forgetUnsafe(query, scope));
|
|
889
|
+
}
|
|
890
|
+
async forgetUnsafe(query, scope) {
|
|
839
891
|
const file = this.files[scope];
|
|
840
892
|
let existing;
|
|
841
893
|
try {
|
|
@@ -872,6 +924,9 @@ ${entry}`;
|
|
|
872
924
|
return removed;
|
|
873
925
|
}
|
|
874
926
|
async consolidate(scope) {
|
|
927
|
+
return this.runSerialized(scope, async () => this.consolidateUnsafe(scope));
|
|
928
|
+
}
|
|
929
|
+
async consolidateUnsafe(scope) {
|
|
875
930
|
const file = this.files[scope];
|
|
876
931
|
let existing;
|
|
877
932
|
try {
|
|
@@ -902,12 +957,14 @@ ${entry}`;
|
|
|
902
957
|
}
|
|
903
958
|
async clear(scope) {
|
|
904
959
|
if (scope) {
|
|
905
|
-
await atomicWrite(this.files[scope], "");
|
|
906
|
-
|
|
907
|
-
for (const s of ["project-agents", "project-memory", "user-memory"]) {
|
|
908
|
-
await atomicWrite(this.files[s], "");
|
|
909
|
-
}
|
|
960
|
+
await this.runSerialized(scope, async () => atomicWrite(this.files[scope], ""));
|
|
961
|
+
return;
|
|
910
962
|
}
|
|
963
|
+
await Promise.all(
|
|
964
|
+
["project-agents", "project-memory", "user-memory"].map(
|
|
965
|
+
(s) => this.runSerialized(s, async () => atomicWrite(this.files[s], ""))
|
|
966
|
+
)
|
|
967
|
+
);
|
|
911
968
|
}
|
|
912
969
|
};
|
|
913
970
|
function labelOf(scope) {
|
|
@@ -955,9 +1012,27 @@ var PATTERNS = [
|
|
|
955
1012
|
regex: /\b([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,})['"]?(?!\s*[A-Za-z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))/g
|
|
956
1013
|
}
|
|
957
1014
|
];
|
|
1015
|
+
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
958
1016
|
var DefaultSecretScrubber = class {
|
|
959
1017
|
scrub(text) {
|
|
960
1018
|
if (!text) return text;
|
|
1019
|
+
if (text.length <= SCRUB_CHUNK_BYTES) {
|
|
1020
|
+
return this.scrubOne(text);
|
|
1021
|
+
}
|
|
1022
|
+
const out = [];
|
|
1023
|
+
let i = 0;
|
|
1024
|
+
while (i < text.length) {
|
|
1025
|
+
let end = Math.min(i + SCRUB_CHUNK_BYTES, text.length);
|
|
1026
|
+
if (end < text.length) {
|
|
1027
|
+
const nl = text.lastIndexOf("\n", end);
|
|
1028
|
+
if (nl > i + SCRUB_CHUNK_BYTES / 2) end = nl + 1;
|
|
1029
|
+
}
|
|
1030
|
+
out.push(this.scrubOne(text.slice(i, end)));
|
|
1031
|
+
i = end;
|
|
1032
|
+
}
|
|
1033
|
+
return out.join("");
|
|
1034
|
+
}
|
|
1035
|
+
scrubOne(text) {
|
|
961
1036
|
let out = text;
|
|
962
1037
|
for (const p of PATTERNS) {
|
|
963
1038
|
out = out.replace(p.regex, (_match, group1, group2) => {
|
|
@@ -1062,7 +1137,17 @@ var DefaultSecretVault = class {
|
|
|
1062
1137
|
}
|
|
1063
1138
|
};
|
|
1064
1139
|
function decryptConfigSecrets(cfg, vault) {
|
|
1065
|
-
return walk(cfg, vault, (v) =>
|
|
1140
|
+
return walk(cfg, vault, (v, key) => {
|
|
1141
|
+
try {
|
|
1142
|
+
return vault.decrypt(v);
|
|
1143
|
+
} catch (err) {
|
|
1144
|
+
console.warn(
|
|
1145
|
+
`[secret-vault] Failed to decrypt "${key}":`,
|
|
1146
|
+
err instanceof Error ? err.message : err
|
|
1147
|
+
);
|
|
1148
|
+
return "";
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1066
1151
|
}
|
|
1067
1152
|
function encryptConfigSecrets(cfg, vault) {
|
|
1068
1153
|
return walk(cfg, vault, (v) => vault.encrypt(v));
|
|
@@ -1076,7 +1161,7 @@ function walk(node, vault, transform) {
|
|
|
1076
1161
|
const out = {};
|
|
1077
1162
|
for (const [k, v] of Object.entries(node)) {
|
|
1078
1163
|
if (typeof v === "string" && isSecretField(k)) {
|
|
1079
|
-
out[k] = transform(v);
|
|
1164
|
+
out[k] = transform(v, k);
|
|
1080
1165
|
} else if (typeof v === "object" && v !== null) {
|
|
1081
1166
|
out[k] = walk(v, vault, transform);
|
|
1082
1167
|
} else {
|
|
@@ -1150,9 +1235,11 @@ function walkCount(node, vault, counter) {
|
|
|
1150
1235
|
}
|
|
1151
1236
|
return out;
|
|
1152
1237
|
}
|
|
1238
|
+
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1153
1239
|
function deepMerge(a, b) {
|
|
1154
1240
|
const out = { ...a };
|
|
1155
1241
|
for (const [k, v] of Object.entries(b)) {
|
|
1242
|
+
if (FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
1156
1243
|
const existing = out[k];
|
|
1157
1244
|
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
1158
1245
|
out[k] = deepMerge(existing, v);
|
|
@@ -1165,7 +1252,22 @@ function deepMerge(a, b) {
|
|
|
1165
1252
|
|
|
1166
1253
|
// src/utils/glob-match.ts
|
|
1167
1254
|
function escapeRegex(s) {
|
|
1168
|
-
return s.replace(/[.+^${}()
|
|
1255
|
+
return s.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
1256
|
+
}
|
|
1257
|
+
var COMPILED_GLOB_CACHE = /* @__PURE__ */ new Map();
|
|
1258
|
+
var CACHE_MAX_SIZE = 2e3;
|
|
1259
|
+
function getCachedGlob(pattern) {
|
|
1260
|
+
const cached = COMPILED_GLOB_CACHE.get(pattern);
|
|
1261
|
+
if (cached) return cached;
|
|
1262
|
+
if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
|
|
1263
|
+
const keys = [...COMPILED_GLOB_CACHE.keys()];
|
|
1264
|
+
for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
|
|
1265
|
+
COMPILED_GLOB_CACHE.delete(keys[i]);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
const re = compileGlob(pattern);
|
|
1269
|
+
COMPILED_GLOB_CACHE.set(pattern, re);
|
|
1270
|
+
return re;
|
|
1169
1271
|
}
|
|
1170
1272
|
function compileGlob(pattern) {
|
|
1171
1273
|
let i = 0;
|
|
@@ -1214,7 +1316,7 @@ function compileGlob(pattern) {
|
|
|
1214
1316
|
return new RegExp(re);
|
|
1215
1317
|
}
|
|
1216
1318
|
function matchGlob(pattern, input) {
|
|
1217
|
-
return
|
|
1319
|
+
return getCachedGlob(pattern).test(input);
|
|
1218
1320
|
}
|
|
1219
1321
|
function matchAny(patterns, input) {
|
|
1220
1322
|
return patterns.some((p) => matchGlob(p, input));
|
|
@@ -1261,7 +1363,7 @@ var DefaultPermissionPolicy = class {
|
|
|
1261
1363
|
if (!this.loaded) await this.reload();
|
|
1262
1364
|
const namespaceEntry = this.findNamespaceEntry(tool.name);
|
|
1263
1365
|
const entry = this.policy[tool.name] ?? namespaceEntry;
|
|
1264
|
-
const subject = this.subjectFor(tool.name, input);
|
|
1366
|
+
const subject = this.subjectFor(tool.name, input, tool.subjectKey);
|
|
1265
1367
|
if (entry?.deny && subject && matchAny(entry.deny, subject)) {
|
|
1266
1368
|
return { permission: "deny", source: "deny", reason: "matched deny pattern" };
|
|
1267
1369
|
}
|
|
@@ -1309,16 +1411,23 @@ var DefaultPermissionPolicy = class {
|
|
|
1309
1411
|
throw err;
|
|
1310
1412
|
}
|
|
1311
1413
|
}
|
|
1312
|
-
subjectFor(toolName, input) {
|
|
1414
|
+
subjectFor(toolName, input, subjectKey) {
|
|
1313
1415
|
if (!input || typeof input !== "object") return void 0;
|
|
1314
1416
|
const obj = input;
|
|
1315
1417
|
const globChars = /[*?\[\]]/g;
|
|
1316
1418
|
const escapeGlob = (s) => s.replace(globChars, (c) => `\\${c}`);
|
|
1419
|
+
const normalizePath = (s) => escapeGlob(s.replace(/\\/g, "/"));
|
|
1420
|
+
if (subjectKey) {
|
|
1421
|
+
const v = obj[subjectKey];
|
|
1422
|
+
if (typeof v === "string") {
|
|
1423
|
+
return subjectKey === "path" || subjectKey === "file" || subjectKey === "files" ? normalizePath(v) : escapeGlob(v);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1317
1426
|
if (toolName === "bash" && typeof obj.command === "string") {
|
|
1318
1427
|
return escapeGlob(obj.command);
|
|
1319
1428
|
}
|
|
1320
1429
|
if (typeof obj.path === "string") {
|
|
1321
|
-
return
|
|
1430
|
+
return normalizePath(obj.path);
|
|
1322
1431
|
}
|
|
1323
1432
|
if (typeof obj.url === "string") {
|
|
1324
1433
|
return escapeGlob(obj.url);
|
|
@@ -1464,14 +1573,15 @@ function providerStatusToCode(status, type) {
|
|
|
1464
1573
|
}
|
|
1465
1574
|
|
|
1466
1575
|
// src/defaults/retry-policy.ts
|
|
1467
|
-
var DefaultRetryPolicy = class {
|
|
1576
|
+
var DefaultRetryPolicy = class _DefaultRetryPolicy {
|
|
1577
|
+
static NETWORK_ERR_RE = /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i;
|
|
1468
1578
|
shouldRetry(err, attempt) {
|
|
1469
1579
|
if (err instanceof ProviderError) {
|
|
1470
1580
|
if (!err.retryable) return false;
|
|
1471
1581
|
return attempt < this.maxAttempts(err);
|
|
1472
1582
|
}
|
|
1473
1583
|
const msg = err.message ?? "";
|
|
1474
|
-
const isNetwork =
|
|
1584
|
+
const isNetwork = _DefaultRetryPolicy.NETWORK_ERR_RE.test(msg);
|
|
1475
1585
|
if (isNetwork) return attempt < 2;
|
|
1476
1586
|
return false;
|
|
1477
1587
|
}
|
|
@@ -1493,55 +1603,43 @@ var DefaultRetryPolicy = class {
|
|
|
1493
1603
|
};
|
|
1494
1604
|
|
|
1495
1605
|
// src/defaults/error-handler.ts
|
|
1606
|
+
var CONTEXT_OVERFLOW_RE = /context|too long|tokens/i;
|
|
1607
|
+
var NETWORK_ERR_RE = /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i;
|
|
1496
1608
|
function buildRecoveryStrategies(opts) {
|
|
1497
1609
|
return [
|
|
1498
1610
|
{
|
|
1499
1611
|
label: "context_overflow_reduce",
|
|
1500
1612
|
compactor: opts?.compactor,
|
|
1501
1613
|
async attempt(err, ctx) {
|
|
1502
|
-
if (err instanceof ProviderError
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
stopReason: "end_turn",
|
|
1510
|
-
usage: { input: 0, output: 0 },
|
|
1511
|
-
model: ctx.model
|
|
1512
|
-
};
|
|
1513
|
-
}
|
|
1514
|
-
} catch {
|
|
1614
|
+
if (!(err instanceof ProviderError)) return null;
|
|
1615
|
+
if (err.status !== 413 && !CONTEXT_OVERFLOW_RE.test(err.message)) return null;
|
|
1616
|
+
if (this.compactor) {
|
|
1617
|
+
try {
|
|
1618
|
+
const report = await this.compactor.compact(ctx, { aggressive: true });
|
|
1619
|
+
if (report.after < report.before) {
|
|
1620
|
+
return { action: "retry", reason: "context_compacted" };
|
|
1515
1621
|
}
|
|
1622
|
+
} catch {
|
|
1516
1623
|
}
|
|
1517
|
-
return null;
|
|
1518
1624
|
}
|
|
1519
1625
|
return null;
|
|
1520
1626
|
}
|
|
1521
1627
|
},
|
|
1522
1628
|
{
|
|
1523
1629
|
label: "rate_limit_backoff",
|
|
1524
|
-
async attempt(err
|
|
1525
|
-
if (err instanceof ProviderError
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
content: [{ type: "text", text: "[rate limit backoff applied \u2014 please retry]" }],
|
|
1531
|
-
stopReason: "end_turn",
|
|
1532
|
-
usage: { input: 0, output: 0 },
|
|
1533
|
-
model: ctx.model
|
|
1534
|
-
};
|
|
1535
|
-
}
|
|
1536
|
-
return null;
|
|
1630
|
+
async attempt(err) {
|
|
1631
|
+
if (!(err instanceof ProviderError) || err.status !== 429) return null;
|
|
1632
|
+
const delayMs = err.body?.retryAfterMs ?? 5e3;
|
|
1633
|
+
const delay = Math.max(1e3, Math.min(delayMs, 6e4));
|
|
1634
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
1635
|
+
return { action: "retry", reason: "rate_limit_backoff" };
|
|
1537
1636
|
}
|
|
1538
1637
|
},
|
|
1539
1638
|
{
|
|
1540
1639
|
label: "downgrade_model",
|
|
1541
1640
|
async attempt(err, ctx) {
|
|
1542
|
-
if (err instanceof ProviderError
|
|
1543
|
-
|
|
1544
|
-
}
|
|
1641
|
+
if (!(err instanceof ProviderError)) return null;
|
|
1642
|
+
if (err.status !== 429 && err.status !== 529 && err.status < 500) return null;
|
|
1545
1643
|
return null;
|
|
1546
1644
|
}
|
|
1547
1645
|
}
|
|
@@ -1564,12 +1662,12 @@ var DefaultErrorHandler = class {
|
|
|
1564
1662
|
if (err.status === 429) return { kind: "rate_limit", retryable: true };
|
|
1565
1663
|
if (err.status === 529) return { kind: "overloaded", retryable: true };
|
|
1566
1664
|
if (err.status >= 500) return { kind: "server", retryable: true };
|
|
1567
|
-
if (err.status === 413 ||
|
|
1665
|
+
if (err.status === 413 || CONTEXT_OVERFLOW_RE.test(err.message)) {
|
|
1568
1666
|
return { kind: "context_overflow", retryable: false };
|
|
1569
1667
|
}
|
|
1570
1668
|
if (err.status >= 400) return { kind: "client", retryable: false };
|
|
1571
1669
|
}
|
|
1572
|
-
if (err instanceof Error &&
|
|
1670
|
+
if (err instanceof Error && NETWORK_ERR_RE.test(err.message)) {
|
|
1573
1671
|
return { kind: "network", retryable: true };
|
|
1574
1672
|
}
|
|
1575
1673
|
return { kind: "unknown", retryable: false };
|
|
@@ -1633,13 +1731,28 @@ var DefaultSkillLoader = class {
|
|
|
1633
1731
|
async manifestText() {
|
|
1634
1732
|
const skills = await this.list();
|
|
1635
1733
|
if (skills.length === 0) return "";
|
|
1734
|
+
const entries = await this.listEntries();
|
|
1636
1735
|
const lines = ["## Available skills"];
|
|
1637
|
-
for (const
|
|
1638
|
-
|
|
1639
|
-
lines.push(
|
|
1736
|
+
for (const e of entries) {
|
|
1737
|
+
const scopeTag = e.scope.length > 0 ? ` \u2014 ${e.scope.slice(0, 3).join(", ")}` : "";
|
|
1738
|
+
lines.push(`- **${e.name}**${scopeTag}`);
|
|
1739
|
+
lines.push(` Use when: ${e.trigger}`);
|
|
1640
1740
|
}
|
|
1641
1741
|
return lines.join("\n");
|
|
1642
1742
|
}
|
|
1743
|
+
async listEntries() {
|
|
1744
|
+
const skills = await this.list();
|
|
1745
|
+
const entries = [];
|
|
1746
|
+
for (const s of skills) {
|
|
1747
|
+
try {
|
|
1748
|
+
const raw = await fsp.readFile(s.path, "utf8");
|
|
1749
|
+
const { trigger, scope } = parseDescription(raw);
|
|
1750
|
+
entries.push({ name: s.name, trigger, scope, source: s.source, path: s.path });
|
|
1751
|
+
} catch {
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
return entries;
|
|
1755
|
+
}
|
|
1643
1756
|
async readBody(name) {
|
|
1644
1757
|
const m = await this.find(name);
|
|
1645
1758
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
@@ -1682,6 +1795,19 @@ function parseFrontmatter(raw) {
|
|
|
1682
1795
|
flush();
|
|
1683
1796
|
return out;
|
|
1684
1797
|
}
|
|
1798
|
+
function parseDescription(raw) {
|
|
1799
|
+
const fm = parseFrontmatter(raw);
|
|
1800
|
+
const desc = fm.description ?? "";
|
|
1801
|
+
const firstSentenceEnd = desc.indexOf(". ");
|
|
1802
|
+
const trigger = firstSentenceEnd !== -1 ? desc.slice(0, firstSentenceEnd + 1).trim() : desc.trim().split("\n")[0] ?? "";
|
|
1803
|
+
const scope = [];
|
|
1804
|
+
const coversMatch = /(?:covers|for|including)\s+([^.]+)/i.exec(desc);
|
|
1805
|
+
if (coversMatch) {
|
|
1806
|
+
const items = coversMatch[1].replace(/[·•]/g, ",").split(",").map((s) => s.trim()).filter(Boolean);
|
|
1807
|
+
scope.push(...items);
|
|
1808
|
+
}
|
|
1809
|
+
return { trigger, scope };
|
|
1810
|
+
}
|
|
1685
1811
|
var BEHAVIOR_DEFAULTS = {
|
|
1686
1812
|
version: 1,
|
|
1687
1813
|
context: {
|
|
@@ -1730,11 +1856,13 @@ var ENV_MAP = {
|
|
|
1730
1856
|
function isPrimitiveArray(a) {
|
|
1731
1857
|
return a.every((v) => v === null || typeof v !== "object");
|
|
1732
1858
|
}
|
|
1859
|
+
var FORBIDDEN_PROTO_KEYS2 = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1733
1860
|
function deepMerge2(base, patch) {
|
|
1734
1861
|
if (typeof base !== "object" || base === null) return patch ?? base;
|
|
1735
1862
|
if (typeof patch !== "object" || patch === null) return base;
|
|
1736
1863
|
const out = { ...base };
|
|
1737
1864
|
for (const [k, v] of Object.entries(patch)) {
|
|
1865
|
+
if (FORBIDDEN_PROTO_KEYS2.has(k)) continue;
|
|
1738
1866
|
const existing = out[k];
|
|
1739
1867
|
if (Array.isArray(v)) {
|
|
1740
1868
|
if (Array.isArray(existing) && isPrimitiveArray(v) && isPrimitiveArray(existing)) {
|
|
@@ -1802,8 +1930,12 @@ var DefaultConfigLoader = class {
|
|
|
1802
1930
|
if (cfg.providers) {
|
|
1803
1931
|
for (const pcfg of Object.values(cfg.providers)) {
|
|
1804
1932
|
if (!pcfg || typeof pcfg !== "object") continue;
|
|
1805
|
-
const
|
|
1806
|
-
if (!Array.isArray(
|
|
1933
|
+
const rawKeys = pcfg.apiKeys;
|
|
1934
|
+
if (!Array.isArray(rawKeys) || rawKeys.length === 0) continue;
|
|
1935
|
+
const keys = rawKeys.filter(
|
|
1936
|
+
(k) => !!k && typeof k === "object" && typeof k.label === "string" && typeof k.apiKey === "string"
|
|
1937
|
+
);
|
|
1938
|
+
if (keys.length === 0) continue;
|
|
1807
1939
|
const existing = pcfg.apiKey;
|
|
1808
1940
|
if (existing && existing.length > 0) continue;
|
|
1809
1941
|
const activeLabel = pcfg.activeKey;
|
|
@@ -1814,23 +1946,42 @@ var DefaultConfigLoader = class {
|
|
|
1814
1946
|
}
|
|
1815
1947
|
}
|
|
1816
1948
|
this.validateBehavior(cfg);
|
|
1817
|
-
if (this.strict)
|
|
1949
|
+
if (this.strict) {
|
|
1950
|
+
this.validateIdentity(cfg);
|
|
1951
|
+
}
|
|
1818
1952
|
return Object.freeze(cfg);
|
|
1819
1953
|
}
|
|
1820
1954
|
async readJson(file) {
|
|
1955
|
+
let raw;
|
|
1821
1956
|
try {
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
if (
|
|
1825
|
-
|
|
1957
|
+
raw = await fsp.readFile(file, "utf8");
|
|
1958
|
+
} catch (err) {
|
|
1959
|
+
if (err.code !== "ENOENT") {
|
|
1960
|
+
console.warn(`[config] Failed to read "${file}":`, err);
|
|
1961
|
+
}
|
|
1962
|
+
return {};
|
|
1963
|
+
}
|
|
1964
|
+
const parsed = safeParse(raw);
|
|
1965
|
+
if (!parsed.ok || !parsed.value) {
|
|
1966
|
+
console.warn(
|
|
1967
|
+
`[config] Failed to parse "${file}": invalid JSON. Falling back to defaults for this layer.`
|
|
1968
|
+
);
|
|
1969
|
+
return {};
|
|
1826
1970
|
}
|
|
1827
|
-
return
|
|
1971
|
+
return parsed.value;
|
|
1828
1972
|
}
|
|
1829
1973
|
validateBehavior(cfg) {
|
|
1830
1974
|
if (cfg.version === void 0) throw new Error("Config: missing version field");
|
|
1831
1975
|
if (cfg.version !== 1) throw new Error(`Config: unsupported version ${cfg.version}`);
|
|
1832
1976
|
const c = cfg.context;
|
|
1833
1977
|
if (!c) throw new Error("Config: missing context section");
|
|
1978
|
+
const fields = ["warnThreshold", "softThreshold", "hardThreshold"];
|
|
1979
|
+
for (const f of fields) {
|
|
1980
|
+
const v = c[f];
|
|
1981
|
+
if (typeof v !== "number" || !Number.isFinite(v)) {
|
|
1982
|
+
throw new Error(`Config: context.${String(f)} must be a finite number (got ${typeof v})`);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1834
1985
|
if (c.warnThreshold >= c.softThreshold || c.softThreshold >= c.hardThreshold) {
|
|
1835
1986
|
throw new Error("Config: context thresholds must satisfy warn < soft < hard");
|
|
1836
1987
|
}
|
|
@@ -1878,7 +2029,8 @@ var DefaultConfigStore = class {
|
|
|
1878
2029
|
for (const w of this.watchers) {
|
|
1879
2030
|
try {
|
|
1880
2031
|
w(next, prev);
|
|
1881
|
-
} catch {
|
|
2032
|
+
} catch (err) {
|
|
2033
|
+
console.error("[config-store] watcher threw:", err);
|
|
1882
2034
|
}
|
|
1883
2035
|
}
|
|
1884
2036
|
return next;
|
|
@@ -1955,21 +2107,33 @@ var DEFAULT_CONFIG_MIGRATIONS = [];
|
|
|
1955
2107
|
|
|
1956
2108
|
// src/utils/token-estimate.ts
|
|
1957
2109
|
var RoughTokenEstimate = (text) => Math.max(1, Math.ceil(text.length / 4));
|
|
2110
|
+
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
2111
|
+
var ESTIMATE_CACHE_MAX_SIZE = 1e4;
|
|
2112
|
+
function getCachedEstimate(key, compute) {
|
|
2113
|
+
const existing = ESTIMATE_CACHE.get(key);
|
|
2114
|
+
if (existing !== void 0) return existing;
|
|
2115
|
+
if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
|
|
2116
|
+
const keys = [...ESTIMATE_CACHE.keys()];
|
|
2117
|
+
for (let i = 0; i < Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4); i++) {
|
|
2118
|
+
ESTIMATE_CACHE.delete(keys[i]);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
const estimate = compute();
|
|
2122
|
+
ESTIMATE_CACHE.set(key, estimate);
|
|
2123
|
+
return estimate;
|
|
2124
|
+
}
|
|
1958
2125
|
function estimateToolInputTokens(input) {
|
|
1959
2126
|
if (typeof input === "string") return RoughTokenEstimate(input);
|
|
1960
|
-
if (input
|
|
1961
|
-
return input
|
|
1962
|
-
}
|
|
1963
|
-
const str = typeof input === "object" ? JSON.stringify(input) : String(input);
|
|
1964
|
-
const estimate = RoughTokenEstimate(str);
|
|
1965
|
-
if (input !== null && typeof input === "object" && !Array.isArray(input)) {
|
|
1966
|
-
input.__tokenEstimate = estimate;
|
|
2127
|
+
if (input === null || typeof input !== "object") {
|
|
2128
|
+
return RoughTokenEstimate(String(input));
|
|
1967
2129
|
}
|
|
1968
|
-
|
|
2130
|
+
const key = JSON.stringify(input);
|
|
2131
|
+
return getCachedEstimate(key, () => RoughTokenEstimate(key));
|
|
1969
2132
|
}
|
|
1970
2133
|
function estimateToolResultTokens(content) {
|
|
1971
2134
|
if (typeof content === "string") return RoughTokenEstimate(content);
|
|
1972
|
-
|
|
2135
|
+
const key = JSON.stringify(content);
|
|
2136
|
+
return getCachedEstimate(key, () => RoughTokenEstimate(key));
|
|
1973
2137
|
}
|
|
1974
2138
|
function estimateTextTokens(text) {
|
|
1975
2139
|
return RoughTokenEstimate(text);
|
|
@@ -2010,9 +2174,18 @@ var HybridCompactor = class {
|
|
|
2010
2174
|
}
|
|
2011
2175
|
}
|
|
2012
2176
|
let saved = 0;
|
|
2013
|
-
|
|
2177
|
+
let changed = false;
|
|
2178
|
+
const nextMessages = new Array(messages.length);
|
|
2179
|
+
for (let i = 0; i < messages.length; i++) {
|
|
2014
2180
|
const msg = messages[i];
|
|
2015
|
-
if (
|
|
2181
|
+
if (i >= preserveStart) {
|
|
2182
|
+
nextMessages[i] = msg;
|
|
2183
|
+
continue;
|
|
2184
|
+
}
|
|
2185
|
+
if (!msg || !Array.isArray(msg.content)) {
|
|
2186
|
+
nextMessages[i] = msg;
|
|
2187
|
+
continue;
|
|
2188
|
+
}
|
|
2016
2189
|
const newContent = msg.content.map((b) => {
|
|
2017
2190
|
if (b.type !== "tool_result") return b;
|
|
2018
2191
|
const tokens = estimateToolResultTokens(b.content);
|
|
@@ -2026,8 +2199,14 @@ var HybridCompactor = class {
|
|
|
2026
2199
|
};
|
|
2027
2200
|
return elided;
|
|
2028
2201
|
});
|
|
2029
|
-
|
|
2202
|
+
if (newContent.length === msg.content.length && newContent.every((b, idx) => b === msg.content[idx])) {
|
|
2203
|
+
nextMessages[i] = msg;
|
|
2204
|
+
} else {
|
|
2205
|
+
nextMessages[i] = { ...msg, content: newContent };
|
|
2206
|
+
changed = true;
|
|
2207
|
+
}
|
|
2030
2208
|
}
|
|
2209
|
+
if (changed) ctx.state.replaceMessages(nextMessages);
|
|
2031
2210
|
return saved;
|
|
2032
2211
|
}
|
|
2033
2212
|
collapseAncientTurns(ctx) {
|
|
@@ -2061,10 +2240,10 @@ var HybridCompactor = class {
|
|
|
2061
2240
|
let total = 0;
|
|
2062
2241
|
for (const m of messages) {
|
|
2063
2242
|
if (typeof m.content === "string") {
|
|
2064
|
-
total +=
|
|
2243
|
+
total += this.estimator(m.content);
|
|
2065
2244
|
} else {
|
|
2066
2245
|
for (const b of m.content) {
|
|
2067
|
-
if (b.type === "text") total +=
|
|
2246
|
+
if (b.type === "text") total += this.estimator(b.text);
|
|
2068
2247
|
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
2069
2248
|
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
2070
2249
|
}
|
|
@@ -2109,7 +2288,7 @@ var IntelligentCompactor = class {
|
|
|
2109
2288
|
const beforeTokens = this.estimateTokens(ctx.messages);
|
|
2110
2289
|
const reductions = [];
|
|
2111
2290
|
const load = beforeTokens / this.maxContext;
|
|
2112
|
-
const aggressive = opts.aggressive ?? load >= this.softThreshold;
|
|
2291
|
+
const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
|
|
2113
2292
|
const saved1 = this.eliseOldToolResults(ctx);
|
|
2114
2293
|
if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
|
|
2115
2294
|
if (aggressive) {
|
|
@@ -2218,9 +2397,18 @@ var IntelligentCompactor = class {
|
|
|
2218
2397
|
}
|
|
2219
2398
|
}
|
|
2220
2399
|
let saved = 0;
|
|
2221
|
-
|
|
2400
|
+
let changed = false;
|
|
2401
|
+
const nextMessages = new Array(messages.length);
|
|
2402
|
+
for (let i = 0; i < messages.length; i++) {
|
|
2222
2403
|
const msg = messages[i];
|
|
2223
|
-
if (
|
|
2404
|
+
if (i >= preserveStart) {
|
|
2405
|
+
nextMessages[i] = msg;
|
|
2406
|
+
continue;
|
|
2407
|
+
}
|
|
2408
|
+
if (!msg || !Array.isArray(msg.content)) {
|
|
2409
|
+
nextMessages[i] = msg;
|
|
2410
|
+
continue;
|
|
2411
|
+
}
|
|
2224
2412
|
const newContent = msg.content.map((b) => {
|
|
2225
2413
|
if (b.type !== "tool_result") return b;
|
|
2226
2414
|
const tokens = estimateToolResultTokens(b.content);
|
|
@@ -2233,8 +2421,14 @@ var IntelligentCompactor = class {
|
|
|
2233
2421
|
is_error: b.is_error
|
|
2234
2422
|
};
|
|
2235
2423
|
});
|
|
2236
|
-
|
|
2424
|
+
if (newContent.length === msg.content.length && newContent.every((b, idx) => b === msg.content[idx])) {
|
|
2425
|
+
nextMessages[i] = msg;
|
|
2426
|
+
} else {
|
|
2427
|
+
nextMessages[i] = { ...msg, content: newContent };
|
|
2428
|
+
changed = true;
|
|
2429
|
+
}
|
|
2237
2430
|
}
|
|
2431
|
+
if (changed) ctx.state.replaceMessages(nextMessages);
|
|
2238
2432
|
return saved;
|
|
2239
2433
|
}
|
|
2240
2434
|
hasTextContent(m) {
|
|
@@ -2387,7 +2581,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
2387
2581
|
const jsonEnd = raw.lastIndexOf("}");
|
|
2388
2582
|
if (jsonStart === -1 || jsonEnd === -1) {
|
|
2389
2583
|
return this.fallbackSelect(
|
|
2390
|
-
Array.from({ length: messageCount }, (
|
|
2584
|
+
Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
|
|
2391
2585
|
this.maxContextTokens
|
|
2392
2586
|
);
|
|
2393
2587
|
}
|
|
@@ -2396,7 +2590,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
2396
2590
|
parsed = JSON.parse(raw.slice(jsonStart, jsonEnd + 1));
|
|
2397
2591
|
} catch {
|
|
2398
2592
|
return this.fallbackSelect(
|
|
2399
|
-
Array.from({ length: messageCount }, (
|
|
2593
|
+
Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
|
|
2400
2594
|
this.maxContextTokens
|
|
2401
2595
|
);
|
|
2402
2596
|
}
|
|
@@ -2449,7 +2643,7 @@ var SelectiveCompactor = class {
|
|
|
2449
2643
|
const savedElision = this.eliseOldToolResults(ctx);
|
|
2450
2644
|
if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
|
|
2451
2645
|
const afterPhase1 = this.estimateTokens(ctx.messages);
|
|
2452
|
-
const targetBudget = this.computeTargetBudget(load
|
|
2646
|
+
const targetBudget = this.computeTargetBudget(load);
|
|
2453
2647
|
if (afterPhase1 > targetBudget) {
|
|
2454
2648
|
const savedSelective = await this.runSelector(ctx, targetBudget);
|
|
2455
2649
|
if (savedSelective > 0) reductions.push({ phase: "selective", saved: savedSelective });
|
|
@@ -2467,7 +2661,7 @@ var SelectiveCompactor = class {
|
|
|
2467
2661
|
try {
|
|
2468
2662
|
result = await this.selector.select(ctx.messages, targetBudget);
|
|
2469
2663
|
} catch {
|
|
2470
|
-
return this.aggressiveRecencyTrim(ctx
|
|
2664
|
+
return this.aggressiveRecencyTrim(ctx);
|
|
2471
2665
|
}
|
|
2472
2666
|
await this.executePlan(ctx, result);
|
|
2473
2667
|
const after = this.estimateTokens(ctx.messages);
|
|
@@ -2522,9 +2716,8 @@ Summarize the following message range:`;
|
|
|
2522
2716
|
* Fallback when selector fails: aggressively trim from the oldest end
|
|
2523
2717
|
* until we hit targetBudget.
|
|
2524
2718
|
*/
|
|
2525
|
-
aggressiveRecencyTrim(ctx
|
|
2719
|
+
aggressiveRecencyTrim(ctx) {
|
|
2526
2720
|
const messages = ctx.messages;
|
|
2527
|
-
this.estimateTokens(messages);
|
|
2528
2721
|
const preserveIdx = Math.max(0, messages.length - this.preserveK * 2);
|
|
2529
2722
|
if (preserveIdx <= 0) return 0;
|
|
2530
2723
|
let boundary = preserveIdx;
|
|
@@ -2545,7 +2738,7 @@ Summarize the following message range:`;
|
|
|
2545
2738
|
ctx.state.replaceMessages([summaryMsg, ...tail]);
|
|
2546
2739
|
return Math.max(0, removedTokens - this.estimateTokens([summaryMsg]));
|
|
2547
2740
|
}
|
|
2548
|
-
computeTargetBudget(load
|
|
2741
|
+
computeTargetBudget(load) {
|
|
2549
2742
|
if (load >= this.hardThreshold) {
|
|
2550
2743
|
return Math.floor(this.maxContext * 0.5);
|
|
2551
2744
|
}
|
|
@@ -2567,9 +2760,18 @@ Summarize the following message range:`;
|
|
|
2567
2760
|
}
|
|
2568
2761
|
}
|
|
2569
2762
|
let saved = 0;
|
|
2570
|
-
|
|
2763
|
+
let changed = false;
|
|
2764
|
+
const nextMessages = new Array(messages.length);
|
|
2765
|
+
for (let i = 0; i < messages.length; i++) {
|
|
2571
2766
|
const msg = messages[i];
|
|
2572
|
-
if (
|
|
2767
|
+
if (i >= preserveStart) {
|
|
2768
|
+
nextMessages[i] = msg;
|
|
2769
|
+
continue;
|
|
2770
|
+
}
|
|
2771
|
+
if (!msg || !Array.isArray(msg.content)) {
|
|
2772
|
+
nextMessages[i] = msg;
|
|
2773
|
+
continue;
|
|
2774
|
+
}
|
|
2573
2775
|
const newContent = msg.content.map((b) => {
|
|
2574
2776
|
if (b.type !== "tool_result") return b;
|
|
2575
2777
|
const text = typeof b.content === "string" ? b.content : JSON.stringify(b.content);
|
|
@@ -2583,8 +2785,14 @@ Summarize the following message range:`;
|
|
|
2583
2785
|
is_error: b.is_error
|
|
2584
2786
|
};
|
|
2585
2787
|
});
|
|
2586
|
-
|
|
2788
|
+
if (newContent.every((b, idx) => b === msg.content[idx])) {
|
|
2789
|
+
nextMessages[i] = msg;
|
|
2790
|
+
} else {
|
|
2791
|
+
nextMessages[i] = { ...msg, content: newContent };
|
|
2792
|
+
changed = true;
|
|
2793
|
+
}
|
|
2587
2794
|
}
|
|
2795
|
+
if (changed) ctx.state.replaceMessages(nextMessages);
|
|
2588
2796
|
return saved;
|
|
2589
2797
|
}
|
|
2590
2798
|
hasTextContent(m) {
|
|
@@ -2620,46 +2828,78 @@ var AutoCompactionMiddleware = class {
|
|
|
2620
2828
|
name = "AutoCompaction";
|
|
2621
2829
|
compactor;
|
|
2622
2830
|
warnThreshold;
|
|
2623
|
-
// fraction of maxContext (0-1)
|
|
2624
2831
|
softThreshold;
|
|
2625
2832
|
hardThreshold;
|
|
2626
2833
|
maxContext;
|
|
2627
2834
|
estimator;
|
|
2628
2835
|
aggressiveOn;
|
|
2836
|
+
events;
|
|
2837
|
+
failureMode;
|
|
2629
2838
|
/**
|
|
2630
|
-
* @param compactor Compactor to use for compaction
|
|
2631
|
-
* @param maxContext Provider's max context window in tokens
|
|
2632
|
-
* @param estimator Token estimation function
|
|
2633
|
-
* @param thresholds
|
|
2634
|
-
* @param
|
|
2839
|
+
* @param compactor Compactor to use for compaction.
|
|
2840
|
+
* @param maxContext Provider's max context window in tokens.
|
|
2841
|
+
* @param estimator Token estimation function.
|
|
2842
|
+
* @param thresholds Threshold fractions (0-1) of maxContext.
|
|
2843
|
+
* @param opts Optional behavior. By default, failures at the
|
|
2844
|
+
* hard threshold throw AGENT_CONTEXT_OVERFLOW so
|
|
2845
|
+
* the agent does not continue into a likely
|
|
2846
|
+
* provider context overflow. Warn/soft failures
|
|
2847
|
+
* still emit compaction.failed and continue.
|
|
2635
2848
|
*/
|
|
2636
|
-
constructor(compactor, maxContext, estimator, thresholds,
|
|
2849
|
+
constructor(compactor, maxContext, estimator, thresholds, optsOrAggressiveOn = {}, events) {
|
|
2850
|
+
const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
|
|
2637
2851
|
this.compactor = compactor;
|
|
2638
2852
|
this.maxContext = maxContext;
|
|
2639
2853
|
this.estimator = estimator;
|
|
2640
2854
|
this.warnThreshold = thresholds.warn;
|
|
2641
2855
|
this.softThreshold = thresholds.soft;
|
|
2642
2856
|
this.hardThreshold = thresholds.hard;
|
|
2643
|
-
this.aggressiveOn = aggressiveOn;
|
|
2857
|
+
this.aggressiveOn = opts.aggressiveOn ?? "soft";
|
|
2858
|
+
this.events = opts.events;
|
|
2859
|
+
this.failureMode = opts.failureMode ?? "throw_on_hard";
|
|
2644
2860
|
}
|
|
2645
2861
|
handler() {
|
|
2646
2862
|
return async (ctx, next) => {
|
|
2647
2863
|
const tokens = this.estimator(ctx);
|
|
2648
2864
|
const load = tokens / this.maxContext;
|
|
2649
2865
|
if (load >= this.hardThreshold) {
|
|
2650
|
-
await this.compact(ctx, true);
|
|
2866
|
+
await this.compact(ctx, true, { level: "hard", tokens, load });
|
|
2651
2867
|
} else if (load >= this.softThreshold) {
|
|
2652
|
-
await this.compact(ctx, this.aggressiveOn !== "hard");
|
|
2868
|
+
await this.compact(ctx, this.aggressiveOn !== "hard", { level: "soft", tokens, load });
|
|
2653
2869
|
} else if (load >= this.warnThreshold) {
|
|
2654
|
-
await this.compact(ctx, false);
|
|
2870
|
+
await this.compact(ctx, false, { level: "warn", tokens, load });
|
|
2655
2871
|
}
|
|
2656
2872
|
return next(ctx);
|
|
2657
2873
|
};
|
|
2658
2874
|
}
|
|
2659
|
-
async compact(ctx, aggressive) {
|
|
2875
|
+
async compact(ctx, aggressive, pressure) {
|
|
2660
2876
|
try {
|
|
2661
2877
|
await this.compactor.compact(ctx, { aggressive });
|
|
2662
|
-
} catch {
|
|
2878
|
+
} catch (err) {
|
|
2879
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
2880
|
+
const fatal = this.failureMode === "throw" || this.failureMode === "throw_on_hard" && pressure.level === "hard";
|
|
2881
|
+
this.events?.emit("compaction.failed", {
|
|
2882
|
+
err: error,
|
|
2883
|
+
aggressive,
|
|
2884
|
+
level: pressure.level,
|
|
2885
|
+
tokens: pressure.tokens,
|
|
2886
|
+
maxContext: this.maxContext,
|
|
2887
|
+
load: pressure.load,
|
|
2888
|
+
fatal
|
|
2889
|
+
});
|
|
2890
|
+
if (fatal) {
|
|
2891
|
+
throw new AgentError({
|
|
2892
|
+
message: `Auto-compaction failed at ${pressure.level} threshold`,
|
|
2893
|
+
code: "AGENT_CONTEXT_OVERFLOW",
|
|
2894
|
+
recoverable: true,
|
|
2895
|
+
context: {
|
|
2896
|
+
level: pressure.level,
|
|
2897
|
+
tokens: pressure.tokens,
|
|
2898
|
+
maxContext: this.maxContext
|
|
2899
|
+
},
|
|
2900
|
+
cause: err
|
|
2901
|
+
});
|
|
2902
|
+
}
|
|
2663
2903
|
}
|
|
2664
2904
|
}
|
|
2665
2905
|
};
|
|
@@ -3173,8 +3413,9 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3173
3413
|
const context = {
|
|
3174
3414
|
subagentId: id,
|
|
3175
3415
|
tasks: [],
|
|
3176
|
-
//
|
|
3177
|
-
// bidirectional bridge is created.
|
|
3416
|
+
// Wired later by the caller via setSubagentBridge() once the
|
|
3417
|
+
// bidirectional bridge is created. Readers must null-check / use
|
|
3418
|
+
// hasParentBridge() — the type now reflects this.
|
|
3178
3419
|
parentBridge: null,
|
|
3179
3420
|
doneCondition: this.config.doneCondition,
|
|
3180
3421
|
maxConcurrent: this.config.maxConcurrent ?? 4
|
|
@@ -3219,9 +3460,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3219
3460
|
this.emit("subagent.stopped", { subagentId, reason: "stopped by coordinator" });
|
|
3220
3461
|
}
|
|
3221
3462
|
async stopAll() {
|
|
3222
|
-
|
|
3223
|
-
await this.stop(id);
|
|
3224
|
-
}
|
|
3463
|
+
await Promise.allSettled([...this.subagents.keys()].map((id) => this.stop(id)));
|
|
3225
3464
|
}
|
|
3226
3465
|
getStatus() {
|
|
3227
3466
|
return {
|
|
@@ -3257,7 +3496,17 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3257
3496
|
if (!subagentId) return;
|
|
3258
3497
|
const task = this.pendingTasks.shift();
|
|
3259
3498
|
if (!task) return;
|
|
3260
|
-
|
|
3499
|
+
this.runDispatched(subagentId, task).catch((err) => {
|
|
3500
|
+
this.recordCompletion({
|
|
3501
|
+
subagentId,
|
|
3502
|
+
taskId: task.id,
|
|
3503
|
+
status: "failed",
|
|
3504
|
+
error: err instanceof Error ? err.message : String(err),
|
|
3505
|
+
iterations: 0,
|
|
3506
|
+
toolCalls: 0,
|
|
3507
|
+
durationMs: 0
|
|
3508
|
+
});
|
|
3509
|
+
});
|
|
3261
3510
|
}
|
|
3262
3511
|
}
|
|
3263
3512
|
canDispatch() {
|
|
@@ -3277,7 +3526,6 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3277
3526
|
subagent.currentTask = task.id;
|
|
3278
3527
|
task.subagentId = subagentId;
|
|
3279
3528
|
subagent.context.tasks.push(task);
|
|
3280
|
-
this.inFlight++;
|
|
3281
3529
|
this.emit("task.assigned", { task, subagentId });
|
|
3282
3530
|
const budget = new SubagentBudget({
|
|
3283
3531
|
maxIterations: subagent.config.maxIterations ?? this.config.defaultBudget?.maxIterations,
|
|
@@ -3287,6 +3535,10 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3287
3535
|
timeoutMs: task.timeoutMs ?? subagent.config.timeoutMs ?? this.config.defaultBudget?.timeoutMs
|
|
3288
3536
|
});
|
|
3289
3537
|
subagent.activeBudget = budget;
|
|
3538
|
+
if (!this.runner) {
|
|
3539
|
+
return;
|
|
3540
|
+
}
|
|
3541
|
+
this.inFlight++;
|
|
3290
3542
|
const startTime = Date.now();
|
|
3291
3543
|
const runCtx = {
|
|
3292
3544
|
subagentId,
|
|
@@ -3296,9 +3548,6 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3296
3548
|
bridge: subagent.context.parentBridge || null
|
|
3297
3549
|
};
|
|
3298
3550
|
let result;
|
|
3299
|
-
if (!this.runner) {
|
|
3300
|
-
return;
|
|
3301
|
-
}
|
|
3302
3551
|
budget.start();
|
|
3303
3552
|
try {
|
|
3304
3553
|
const outcome = await this.executeWithTimeout(this.runner, task, runCtx, budget);
|
|
@@ -3313,13 +3562,14 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3313
3562
|
};
|
|
3314
3563
|
} catch (err) {
|
|
3315
3564
|
const status = err instanceof BudgetExceededError && err.kind === "timeout" ? "timeout" : subagent.abortController.signal.aborted ? "stopped" : "failed";
|
|
3565
|
+
const usage = budget.usage();
|
|
3316
3566
|
result = {
|
|
3317
3567
|
subagentId,
|
|
3318
3568
|
taskId: task.id,
|
|
3319
3569
|
status,
|
|
3320
3570
|
error: err instanceof Error ? err.message : String(err),
|
|
3321
|
-
iterations:
|
|
3322
|
-
toolCalls:
|
|
3571
|
+
iterations: usage.iterations,
|
|
3572
|
+
toolCalls: usage.toolCalls,
|
|
3323
3573
|
durationMs: Date.now() - startTime
|
|
3324
3574
|
};
|
|
3325
3575
|
}
|
|
@@ -3344,11 +3594,24 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3344
3594
|
recordCompletion(result) {
|
|
3345
3595
|
this.completedResults.push(result);
|
|
3346
3596
|
this.totalIterations += result.iterations;
|
|
3347
|
-
|
|
3597
|
+
if (this.inFlight > 0) {
|
|
3598
|
+
this.inFlight--;
|
|
3599
|
+
} else if (this.runner) {
|
|
3600
|
+
this.emit("warning", {
|
|
3601
|
+
type: "inFlight_underflow",
|
|
3602
|
+
taskId: result.taskId,
|
|
3603
|
+
subagentId: result.subagentId
|
|
3604
|
+
});
|
|
3605
|
+
return;
|
|
3606
|
+
}
|
|
3348
3607
|
const subagent = this.subagents.get(result.subagentId);
|
|
3349
3608
|
if (subagent && subagent.status !== "stopped") {
|
|
3350
|
-
|
|
3609
|
+
const failed = result.status === "failed" || result.status === "timeout";
|
|
3610
|
+
subagent.status = failed ? "error" : "idle";
|
|
3351
3611
|
subagent.currentTask = void 0;
|
|
3612
|
+
if (subagent.abortController.signal.aborted) {
|
|
3613
|
+
subagent.abortController = new AbortController();
|
|
3614
|
+
}
|
|
3352
3615
|
if (subagent.status === "error") {
|
|
3353
3616
|
queueMicrotask(() => {
|
|
3354
3617
|
if (subagent.status === "error") subagent.status = "idle";
|
|
@@ -3450,6 +3713,182 @@ function defaultFormatTaskInput(task) {
|
|
|
3450
3713
|
return task.description ?? "";
|
|
3451
3714
|
}
|
|
3452
3715
|
|
|
3716
|
+
// src/defaults/fleet-bus.ts
|
|
3717
|
+
var FleetBus = class {
|
|
3718
|
+
byId = /* @__PURE__ */ new Map();
|
|
3719
|
+
byType = /* @__PURE__ */ new Map();
|
|
3720
|
+
any = /* @__PURE__ */ new Set();
|
|
3721
|
+
/**
|
|
3722
|
+
* Hook a subagent's EventBus into the fleet. EventBus is strongly
|
|
3723
|
+
* typed and doesn't expose an `onAny` hook, so we subscribe to the
|
|
3724
|
+
* canonical set of event types a subagent emits during a run. New
|
|
3725
|
+
* event types added to the kernel must be added here too — but the
|
|
3726
|
+
* cost is a tiny single line per type, and the explicit list keeps
|
|
3727
|
+
* the wire format clear.
|
|
3728
|
+
*
|
|
3729
|
+
* Returns a disposer that detaches every subscription; call on
|
|
3730
|
+
* subagent teardown so the listeners don't outlive the run.
|
|
3731
|
+
*/
|
|
3732
|
+
attach(subagentId, bus, taskId) {
|
|
3733
|
+
const FORWARDED_TYPES = [
|
|
3734
|
+
"tool.started",
|
|
3735
|
+
"tool.executed",
|
|
3736
|
+
"tool.progress",
|
|
3737
|
+
"tool.confirm_needed",
|
|
3738
|
+
"iteration.started",
|
|
3739
|
+
"iteration.completed",
|
|
3740
|
+
"provider.text_delta",
|
|
3741
|
+
"provider.response",
|
|
3742
|
+
"provider.retry",
|
|
3743
|
+
"provider.error",
|
|
3744
|
+
"session.started",
|
|
3745
|
+
"session.ended",
|
|
3746
|
+
"token.threshold"
|
|
3747
|
+
];
|
|
3748
|
+
const offs = [];
|
|
3749
|
+
for (const t of FORWARDED_TYPES) {
|
|
3750
|
+
offs.push(
|
|
3751
|
+
bus.on(t, (payload) => {
|
|
3752
|
+
this.emit({ subagentId, taskId, ts: Date.now(), type: t, payload });
|
|
3753
|
+
})
|
|
3754
|
+
);
|
|
3755
|
+
}
|
|
3756
|
+
return () => {
|
|
3757
|
+
for (const off of offs) off();
|
|
3758
|
+
};
|
|
3759
|
+
}
|
|
3760
|
+
/** Subscribe to every event from one subagent. */
|
|
3761
|
+
subscribe(subagentId, handler) {
|
|
3762
|
+
let set = this.byId.get(subagentId);
|
|
3763
|
+
if (!set) {
|
|
3764
|
+
set = /* @__PURE__ */ new Set();
|
|
3765
|
+
this.byId.set(subagentId, set);
|
|
3766
|
+
}
|
|
3767
|
+
set.add(handler);
|
|
3768
|
+
return () => {
|
|
3769
|
+
set.delete(handler);
|
|
3770
|
+
};
|
|
3771
|
+
}
|
|
3772
|
+
/** Subscribe to one event type across all subagents. */
|
|
3773
|
+
filter(type, handler) {
|
|
3774
|
+
let set = this.byType.get(type);
|
|
3775
|
+
if (!set) {
|
|
3776
|
+
set = /* @__PURE__ */ new Set();
|
|
3777
|
+
this.byType.set(type, set);
|
|
3778
|
+
}
|
|
3779
|
+
set.add(handler);
|
|
3780
|
+
return () => {
|
|
3781
|
+
set.delete(handler);
|
|
3782
|
+
};
|
|
3783
|
+
}
|
|
3784
|
+
/** Subscribe to literally everything. The fleet roll-up uses this. */
|
|
3785
|
+
onAny(handler) {
|
|
3786
|
+
this.any.add(handler);
|
|
3787
|
+
return () => {
|
|
3788
|
+
this.any.delete(handler);
|
|
3789
|
+
};
|
|
3790
|
+
}
|
|
3791
|
+
emit(event) {
|
|
3792
|
+
const byId = this.byId.get(event.subagentId);
|
|
3793
|
+
if (byId) for (const h of byId) {
|
|
3794
|
+
try {
|
|
3795
|
+
h(event);
|
|
3796
|
+
} catch {
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
const byType = this.byType.get(event.type);
|
|
3800
|
+
if (byType) for (const h of byType) {
|
|
3801
|
+
try {
|
|
3802
|
+
h(event);
|
|
3803
|
+
} catch {
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
for (const h of this.any) {
|
|
3807
|
+
try {
|
|
3808
|
+
h(event);
|
|
3809
|
+
} catch {
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
3813
|
+
};
|
|
3814
|
+
var FleetUsageAggregator = class {
|
|
3815
|
+
constructor(bus, priceLookup, metaLookup) {
|
|
3816
|
+
this.bus = bus;
|
|
3817
|
+
this.priceLookup = priceLookup;
|
|
3818
|
+
this.metaLookup = metaLookup;
|
|
3819
|
+
bus.filter("provider.response", (e) => this.onProviderResponse(e));
|
|
3820
|
+
bus.filter("tool.executed", (e) => this.onToolExecuted(e));
|
|
3821
|
+
bus.filter("iteration.started", (e) => this.onIterationStarted(e));
|
|
3822
|
+
}
|
|
3823
|
+
bus;
|
|
3824
|
+
priceLookup;
|
|
3825
|
+
metaLookup;
|
|
3826
|
+
perSubagent = /* @__PURE__ */ new Map();
|
|
3827
|
+
total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
3828
|
+
/** Live snapshot — safe to call from a tool's execute() body. */
|
|
3829
|
+
snapshot() {
|
|
3830
|
+
return {
|
|
3831
|
+
total: { ...this.total },
|
|
3832
|
+
perSubagent: Object.fromEntries(
|
|
3833
|
+
Array.from(this.perSubagent.entries()).map(([k, v]) => [k, { ...v }])
|
|
3834
|
+
)
|
|
3835
|
+
};
|
|
3836
|
+
}
|
|
3837
|
+
ensure(subagentId) {
|
|
3838
|
+
let snap = this.perSubagent.get(subagentId);
|
|
3839
|
+
if (!snap) {
|
|
3840
|
+
const meta = this.metaLookup?.(subagentId);
|
|
3841
|
+
snap = {
|
|
3842
|
+
subagentId,
|
|
3843
|
+
provider: meta?.provider,
|
|
3844
|
+
model: meta?.model,
|
|
3845
|
+
input: 0,
|
|
3846
|
+
output: 0,
|
|
3847
|
+
cacheRead: 0,
|
|
3848
|
+
cacheWrite: 0,
|
|
3849
|
+
cost: 0,
|
|
3850
|
+
toolCalls: 0,
|
|
3851
|
+
iterations: 0,
|
|
3852
|
+
startedAt: Date.now(),
|
|
3853
|
+
lastEventAt: Date.now()
|
|
3854
|
+
};
|
|
3855
|
+
this.perSubagent.set(subagentId, snap);
|
|
3856
|
+
}
|
|
3857
|
+
return snap;
|
|
3858
|
+
}
|
|
3859
|
+
onProviderResponse(e) {
|
|
3860
|
+
const snap = this.ensure(e.subagentId);
|
|
3861
|
+
const p = e.payload;
|
|
3862
|
+
const usage = p?.usage;
|
|
3863
|
+
if (!usage) return;
|
|
3864
|
+
snap.input += usage.input ?? 0;
|
|
3865
|
+
snap.output += usage.output ?? 0;
|
|
3866
|
+
snap.cacheRead += usage.cacheRead ?? 0;
|
|
3867
|
+
snap.cacheWrite += usage.cacheWrite ?? 0;
|
|
3868
|
+
this.total.input += usage.input ?? 0;
|
|
3869
|
+
this.total.output += usage.output ?? 0;
|
|
3870
|
+
this.total.cacheRead += usage.cacheRead ?? 0;
|
|
3871
|
+
this.total.cacheWrite += usage.cacheWrite ?? 0;
|
|
3872
|
+
const price = this.priceLookup?.(e.subagentId);
|
|
3873
|
+
if (price) {
|
|
3874
|
+
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);
|
|
3875
|
+
snap.cost += delta;
|
|
3876
|
+
this.total.cost += delta;
|
|
3877
|
+
}
|
|
3878
|
+
snap.lastEventAt = e.ts;
|
|
3879
|
+
}
|
|
3880
|
+
onToolExecuted(e) {
|
|
3881
|
+
const snap = this.ensure(e.subagentId);
|
|
3882
|
+
snap.toolCalls += 1;
|
|
3883
|
+
snap.lastEventAt = e.ts;
|
|
3884
|
+
}
|
|
3885
|
+
onIterationStarted(e) {
|
|
3886
|
+
const snap = this.ensure(e.subagentId);
|
|
3887
|
+
snap.iterations += 1;
|
|
3888
|
+
snap.lastEventAt = e.ts;
|
|
3889
|
+
}
|
|
3890
|
+
};
|
|
3891
|
+
|
|
3453
3892
|
// src/defaults/transport/in-memory-transport.ts
|
|
3454
3893
|
var InMemoryBridgeTransport = class {
|
|
3455
3894
|
subs = /* @__PURE__ */ new Map();
|
|
@@ -3536,6 +3975,11 @@ var InMemoryAgentBridge = class {
|
|
|
3536
3975
|
if (this.stopped) throw new Error("Bridge is stopped");
|
|
3537
3976
|
const timeout = timeoutMs ?? this.timeoutMs;
|
|
3538
3977
|
const correlationId = msg.id;
|
|
3978
|
+
if (this.pendingRequests.has(correlationId)) {
|
|
3979
|
+
throw new Error(
|
|
3980
|
+
`Bridge request id "${correlationId}" collides with an in-flight request \u2014 caller is reusing message ids`
|
|
3981
|
+
);
|
|
3982
|
+
}
|
|
3539
3983
|
return new Promise((resolve3, reject) => {
|
|
3540
3984
|
const timer = setTimeout(() => {
|
|
3541
3985
|
this.pendingRequests.delete(correlationId);
|
|
@@ -3572,12 +4016,686 @@ function createMessage(type, from, payload, to) {
|
|
|
3572
4016
|
};
|
|
3573
4017
|
}
|
|
3574
4018
|
|
|
4019
|
+
// src/defaults/director-prompts.ts
|
|
4020
|
+
var DEFAULT_DIRECTOR_PREAMBLE = `You are the Director of a multi-agent fleet. You orchestrate worker
|
|
4021
|
+
subagents by spawning them, assigning tasks, awaiting completions, and
|
|
4022
|
+
rolling up their outputs into your next decision.
|
|
4023
|
+
|
|
4024
|
+
Core fleet tools available to you:
|
|
4025
|
+
- spawn_subagent \u2014 create a worker with a chosen provider / model / role
|
|
4026
|
+
- assign_task \u2014 hand a piece of work to a specific subagent
|
|
4027
|
+
- await_tasks \u2014 block until named task ids complete (parallel-safe)
|
|
4028
|
+
- ask_subagent \u2014 synchronously query a running subagent via the bridge
|
|
4029
|
+
- roll_up \u2014 aggregate finished tasks into a markdown/json summary
|
|
4030
|
+
- terminate_subagent \u2014 abort a stuck worker (use sparingly)
|
|
4031
|
+
- fleet_status \u2014 snapshot of all subagents and pending tasks
|
|
4032
|
+
- fleet_usage \u2014 token + cost breakdown per subagent and total
|
|
4033
|
+
|
|
4034
|
+
Working rules:
|
|
4035
|
+
1. Decompose first. Before spawning, decide which sub-tasks are
|
|
4036
|
+
independent and can run in parallel. Sequential work doesn't need a
|
|
4037
|
+
subagent \u2014 do it yourself.
|
|
4038
|
+
2. Match worker to job. Cheap/fast model for triage, capable model for
|
|
4039
|
+
synthesis. Different providers per sibling is allowed and encouraged.
|
|
4040
|
+
3. Always pair an assign with an await. Don't fire-and-forget; you owe
|
|
4041
|
+
the user a single coherent answer at the end.
|
|
4042
|
+
4. Roll up before deciding. After await_tasks resolves, call roll_up so
|
|
4043
|
+
the results are folded back into your context in a compact form.
|
|
4044
|
+
5. Budget is real. Check fleet_usage periodically. If a subagent is
|
|
4045
|
+
thrashing, terminate it rather than letting cost climb silently.
|
|
4046
|
+
6. Never claim a subagent's work as your own without verifying it. If a
|
|
4047
|
+
result looks wrong, ask_subagent for clarification before passing it
|
|
4048
|
+
to the user.`;
|
|
4049
|
+
var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
|
|
4050
|
+
a specific slice of a larger plan \u2014 do that slice well and report back.
|
|
4051
|
+
|
|
4052
|
+
Bridge contract:
|
|
4053
|
+
- You have a parent (the Director). You may call \`request\` on the
|
|
4054
|
+
parent bridge to ask a clarifying question. Use this sparingly; the
|
|
4055
|
+
parent is also working.
|
|
4056
|
+
- You MAY NOT request the parent's system prompt, tool list, or other
|
|
4057
|
+
subagents' context. Those are not yours to read.
|
|
4058
|
+
- Your final task output is what the Director sees. Be concise,
|
|
4059
|
+
structured, and self-contained \u2014 assume the Director will paste your
|
|
4060
|
+
output into its own context.`;
|
|
4061
|
+
function composeDirectorPrompt(parts = {}) {
|
|
4062
|
+
const sections = [];
|
|
4063
|
+
const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
|
|
4064
|
+
if (preamble && preamble.trim().length > 0) sections.push(preamble.trim());
|
|
4065
|
+
if (parts.rosterSummary && parts.rosterSummary.trim().length > 0) {
|
|
4066
|
+
sections.push(`Available roles you can spawn:
|
|
4067
|
+
${parts.rosterSummary.trim()}`);
|
|
4068
|
+
}
|
|
4069
|
+
if (parts.basePrompt && parts.basePrompt.trim().length > 0) {
|
|
4070
|
+
sections.push(parts.basePrompt.trim());
|
|
4071
|
+
}
|
|
4072
|
+
return sections.join("\n\n");
|
|
4073
|
+
}
|
|
4074
|
+
function composeSubagentPrompt(parts = {}) {
|
|
4075
|
+
const sections = [];
|
|
4076
|
+
const baseline = parts.baseline ?? DEFAULT_SUBAGENT_BASELINE;
|
|
4077
|
+
if (baseline && baseline.trim().length > 0) sections.push(baseline.trim());
|
|
4078
|
+
if (parts.role && parts.role.trim().length > 0) {
|
|
4079
|
+
sections.push(`Role:
|
|
4080
|
+
${parts.role.trim()}`);
|
|
4081
|
+
}
|
|
4082
|
+
if (parts.task && parts.task.trim().length > 0) {
|
|
4083
|
+
sections.push(`Task:
|
|
4084
|
+
${parts.task.trim()}`);
|
|
4085
|
+
}
|
|
4086
|
+
if (parts.override && parts.override.trim().length > 0) {
|
|
4087
|
+
sections.push(parts.override.trim());
|
|
4088
|
+
}
|
|
4089
|
+
return sections.join("\n\n");
|
|
4090
|
+
}
|
|
4091
|
+
function rosterSummaryFromConfigs(roster) {
|
|
4092
|
+
const lines = [];
|
|
4093
|
+
for (const [roleId, cfg] of Object.entries(roster)) {
|
|
4094
|
+
const tag = cfg.provider && cfg.model ? ` (${cfg.provider}/${cfg.model})` : "";
|
|
4095
|
+
const headline = cfg.prompt ? (cfg.prompt.split("\n").find((l) => l.trim().length > 0) ?? "").trim().slice(0, 80) : "";
|
|
4096
|
+
const tail = headline ? ` \u2014 ${headline}` : "";
|
|
4097
|
+
lines.push(`- ${roleId}: ${cfg.name}${tag}${tail}`);
|
|
4098
|
+
}
|
|
4099
|
+
return lines.join("\n");
|
|
4100
|
+
}
|
|
4101
|
+
|
|
4102
|
+
// src/defaults/director.ts
|
|
4103
|
+
var Director = class {
|
|
4104
|
+
id;
|
|
4105
|
+
fleet;
|
|
4106
|
+
usage;
|
|
4107
|
+
/**
|
|
4108
|
+
* Director-side bridge endpoint. Subagents are wired to the same
|
|
4109
|
+
* in-memory transport so the director can `ask()` them synchronously
|
|
4110
|
+
* and they can `send()` progress back. Exposed so external code (e.g.
|
|
4111
|
+
* the TUI) can subscribe to inbound messages.
|
|
4112
|
+
*/
|
|
4113
|
+
bridge;
|
|
4114
|
+
transport;
|
|
4115
|
+
coordinator;
|
|
4116
|
+
/** Resolves with the matching `TaskResult` the first time the
|
|
4117
|
+
* coordinator emits `task.completed` for a given task id. Each entry
|
|
4118
|
+
* is created lazily on first poll/await and cleared once consumed. */
|
|
4119
|
+
taskWaiters = /* @__PURE__ */ new Map();
|
|
4120
|
+
/** Cache of completed results in case the consumer asks AFTER the
|
|
4121
|
+
* coordinator already fired the event — `awaitTasks(['t-1'])` after
|
|
4122
|
+
* t-1 finished should resolve immediately, not hang. */
|
|
4123
|
+
completed = /* @__PURE__ */ new Map();
|
|
4124
|
+
/** Per-subagent provider/model metadata, captured at spawn time so the
|
|
4125
|
+
* FleetUsageAggregator's metaLookup can surface readable rows. */
|
|
4126
|
+
subagentMeta = /* @__PURE__ */ new Map();
|
|
4127
|
+
priceLookups = /* @__PURE__ */ new Map();
|
|
4128
|
+
/** Bridge endpoints we created per subagent (so we can `stop()` them
|
|
4129
|
+
* on shutdown and free transport subscriptions). */
|
|
4130
|
+
subagentBridges = /* @__PURE__ */ new Map();
|
|
4131
|
+
/** Tracks per-spawn config + assigned task ids for manifest writing. */
|
|
4132
|
+
manifestEntries = /* @__PURE__ */ new Map();
|
|
4133
|
+
manifestPath;
|
|
4134
|
+
roster;
|
|
4135
|
+
directorPreamble;
|
|
4136
|
+
subagentBaseline;
|
|
4137
|
+
constructor(opts) {
|
|
4138
|
+
this.id = opts.config.coordinatorId || randomUUID();
|
|
4139
|
+
this.manifestPath = opts.manifestPath;
|
|
4140
|
+
this.roster = opts.roster;
|
|
4141
|
+
this.directorPreamble = opts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
|
|
4142
|
+
this.subagentBaseline = opts.subagentBaseline ?? DEFAULT_SUBAGENT_BASELINE;
|
|
4143
|
+
this.transport = new InMemoryBridgeTransport();
|
|
4144
|
+
this.bridge = new InMemoryAgentBridge(
|
|
4145
|
+
{ agentId: this.id, coordinatorId: this.id },
|
|
4146
|
+
this.transport
|
|
4147
|
+
);
|
|
4148
|
+
this.fleet = new FleetBus();
|
|
4149
|
+
this.usage = new FleetUsageAggregator(
|
|
4150
|
+
this.fleet,
|
|
4151
|
+
(id) => this.priceLookups.get(id),
|
|
4152
|
+
(id) => this.subagentMeta.get(id)
|
|
4153
|
+
);
|
|
4154
|
+
this.coordinator = new DefaultMultiAgentCoordinator(
|
|
4155
|
+
{ ...opts.config, coordinatorId: this.id },
|
|
4156
|
+
{ runner: opts.runner }
|
|
4157
|
+
);
|
|
4158
|
+
this.coordinator.on("task.completed", (payload) => {
|
|
4159
|
+
const r = payload.result;
|
|
4160
|
+
this.completed.set(r.taskId, r);
|
|
4161
|
+
const waiter = this.taskWaiters.get(r.taskId);
|
|
4162
|
+
if (waiter) {
|
|
4163
|
+
waiter.resolve(r);
|
|
4164
|
+
this.taskWaiters.delete(r.taskId);
|
|
4165
|
+
}
|
|
4166
|
+
});
|
|
4167
|
+
}
|
|
4168
|
+
/**
|
|
4169
|
+
* Spawn a subagent. Identical to the coordinator's `spawn()` but
|
|
4170
|
+
* captures provider/model metadata for the usage aggregator and
|
|
4171
|
+
* lets the FleetBus attach to the runner's EventBus when the task
|
|
4172
|
+
* actually runs (see `attachSubagentBus`).
|
|
4173
|
+
*
|
|
4174
|
+
* Caller-supplied `priceLookup` is optional but recommended — without
|
|
4175
|
+
* it the `cost` column in `usage.snapshot()` stays at 0.
|
|
4176
|
+
*/
|
|
4177
|
+
async spawn(config, priceLookup) {
|
|
4178
|
+
const result = await this.coordinator.spawn(config);
|
|
4179
|
+
this.subagentMeta.set(result.subagentId, {
|
|
4180
|
+
provider: config.provider,
|
|
4181
|
+
model: config.model
|
|
4182
|
+
});
|
|
4183
|
+
if (priceLookup) this.priceLookups.set(result.subagentId, priceLookup);
|
|
4184
|
+
const subagentBridge = new InMemoryAgentBridge(
|
|
4185
|
+
{ agentId: result.subagentId, coordinatorId: this.id },
|
|
4186
|
+
this.transport
|
|
4187
|
+
);
|
|
4188
|
+
this.coordinator.setSubagentBridge(result.subagentId, subagentBridge);
|
|
4189
|
+
this.subagentBridges.set(result.subagentId, subagentBridge);
|
|
4190
|
+
this.manifestEntries.set(result.subagentId, {
|
|
4191
|
+
subagentId: result.subagentId,
|
|
4192
|
+
name: config.name,
|
|
4193
|
+
role: config.role,
|
|
4194
|
+
provider: config.provider,
|
|
4195
|
+
model: config.model,
|
|
4196
|
+
taskIds: []
|
|
4197
|
+
});
|
|
4198
|
+
return result.subagentId;
|
|
4199
|
+
}
|
|
4200
|
+
/**
|
|
4201
|
+
* Synchronously ask a subagent something via the bridge. Sends a
|
|
4202
|
+
* `task` message addressed to the subagent and awaits a matching
|
|
4203
|
+
* reply (matched by message id). Subagent runners that handle these
|
|
4204
|
+
* requests subscribe to `ctx.bridge` and reply with a message whose
|
|
4205
|
+
* `id` equals the incoming request's id (see `InMemoryAgentBridge`'s
|
|
4206
|
+
* `request<T>` implementation).
|
|
4207
|
+
*
|
|
4208
|
+
* Returns the response payload directly (the bridge wrapper is
|
|
4209
|
+
* unwrapped for ergonomics). Times out after `timeoutMs` (default
|
|
4210
|
+
* matches the bridge's own default of 30s) — surface those rejections
|
|
4211
|
+
* to the caller as actionable errors instead of letting tools hang.
|
|
4212
|
+
*/
|
|
4213
|
+
async ask(subagentId, payload, timeoutMs) {
|
|
4214
|
+
if (!this.subagentBridges.has(subagentId)) {
|
|
4215
|
+
throw new Error(
|
|
4216
|
+
`ask: unknown subagent "${subagentId}" (spawn() it first; current fleet: ${Array.from(this.subagentBridges.keys()).join(", ") || "(empty)"})`
|
|
4217
|
+
);
|
|
4218
|
+
}
|
|
4219
|
+
const msg = {
|
|
4220
|
+
id: randomUUID(),
|
|
4221
|
+
type: "task",
|
|
4222
|
+
from: this.id,
|
|
4223
|
+
to: subagentId,
|
|
4224
|
+
payload,
|
|
4225
|
+
timestamp: Date.now(),
|
|
4226
|
+
priority: "normal"
|
|
4227
|
+
};
|
|
4228
|
+
const reply = await this.bridge.request(msg, timeoutMs);
|
|
4229
|
+
return reply.payload;
|
|
4230
|
+
}
|
|
4231
|
+
/**
|
|
4232
|
+
* Read completed task results and format them as a structured text
|
|
4233
|
+
* block the director's LLM can paste into its own context. The
|
|
4234
|
+
* Director keeps every completed `TaskResult` in `completed` so this
|
|
4235
|
+
* is a pure read — no bridge round-trip, cheap to call.
|
|
4236
|
+
*
|
|
4237
|
+
* The returned string is intentionally markdown-flavored: headers per
|
|
4238
|
+
* subagent, a one-line meta row (iter / tools / ms), and the task's
|
|
4239
|
+
* result text. Pass `style: 'json'` for a programmatic shape instead
|
|
4240
|
+
* (useful when the director model is doing structured-output work).
|
|
4241
|
+
*/
|
|
4242
|
+
rollUp(taskIds, style = "markdown") {
|
|
4243
|
+
const rows = taskIds.map((id) => this.completed.get(id)).filter(
|
|
4244
|
+
(r) => !!r
|
|
4245
|
+
);
|
|
4246
|
+
if (style === "json") {
|
|
4247
|
+
return JSON.stringify(
|
|
4248
|
+
rows.map((r) => ({
|
|
4249
|
+
taskId: r.taskId,
|
|
4250
|
+
subagentId: r.subagentId,
|
|
4251
|
+
status: r.status,
|
|
4252
|
+
iterations: r.iterations,
|
|
4253
|
+
toolCalls: r.toolCalls,
|
|
4254
|
+
durationMs: r.durationMs,
|
|
4255
|
+
result: r.result,
|
|
4256
|
+
error: r.error
|
|
4257
|
+
})),
|
|
4258
|
+
null,
|
|
4259
|
+
2
|
|
4260
|
+
);
|
|
4261
|
+
}
|
|
4262
|
+
if (rows.length === 0) {
|
|
4263
|
+
return "_No completed tasks for the requested ids \u2014 try waiting first._";
|
|
4264
|
+
}
|
|
4265
|
+
const lines = [];
|
|
4266
|
+
for (const r of rows) {
|
|
4267
|
+
const meta = this.subagentMeta.get(r.subagentId);
|
|
4268
|
+
const tag = meta?.provider && meta?.model ? ` \xB7 ${meta.provider}/${meta.model}` : "";
|
|
4269
|
+
lines.push(`### ${r.subagentId}${tag}`);
|
|
4270
|
+
lines.push(
|
|
4271
|
+
`_${r.status} \u2014 ${r.iterations} iter \xB7 ${r.toolCalls} tools \xB7 ${r.durationMs}ms_`
|
|
4272
|
+
);
|
|
4273
|
+
lines.push("");
|
|
4274
|
+
if (r.error) lines.push(`**Error:** ${r.error}`);
|
|
4275
|
+
else if (typeof r.result === "string") lines.push(r.result);
|
|
4276
|
+
else if (r.result !== void 0) lines.push("```json\n" + JSON.stringify(r.result, null, 2) + "\n```");
|
|
4277
|
+
else lines.push("_(no output)_");
|
|
4278
|
+
lines.push("");
|
|
4279
|
+
}
|
|
4280
|
+
return lines.join("\n").trimEnd();
|
|
4281
|
+
}
|
|
4282
|
+
/**
|
|
4283
|
+
* Write the fleet manifest to `manifestPath`. Returns the path written
|
|
4284
|
+
* or null when no path was configured. Captures every spawn + its
|
|
4285
|
+
* assigned tasks — paired with per-subagent JSONLs, this is enough to
|
|
4286
|
+
* replay an entire director run.
|
|
4287
|
+
*/
|
|
4288
|
+
async writeManifest() {
|
|
4289
|
+
if (!this.manifestPath) return null;
|
|
4290
|
+
const manifest = {
|
|
4291
|
+
directorRunId: this.id,
|
|
4292
|
+
writtenAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4293
|
+
children: Array.from(this.manifestEntries.values()).map((e) => ({
|
|
4294
|
+
...e,
|
|
4295
|
+
// Surface final status from `completed` when available — manifest
|
|
4296
|
+
// becomes much more useful for replay when it carries the
|
|
4297
|
+
// success/failure state.
|
|
4298
|
+
results: e.taskIds.map((tid) => {
|
|
4299
|
+
const r = this.completed.get(tid);
|
|
4300
|
+
return r ? {
|
|
4301
|
+
taskId: tid,
|
|
4302
|
+
status: r.status,
|
|
4303
|
+
iterations: r.iterations,
|
|
4304
|
+
toolCalls: r.toolCalls,
|
|
4305
|
+
durationMs: r.durationMs
|
|
4306
|
+
} : { taskId: tid, status: "pending" };
|
|
4307
|
+
})
|
|
4308
|
+
})),
|
|
4309
|
+
usage: this.usage.snapshot()
|
|
4310
|
+
};
|
|
4311
|
+
await fsp.mkdir(path2.dirname(this.manifestPath), { recursive: true });
|
|
4312
|
+
await fsp.writeFile(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
4313
|
+
return this.manifestPath;
|
|
4314
|
+
}
|
|
4315
|
+
/**
|
|
4316
|
+
* Tear down the director: stop every subagent, close every bridge
|
|
4317
|
+
* endpoint, and (when configured) write the final manifest. Idempotent
|
|
4318
|
+
* — calling shutdown twice is a no-op on the second invocation.
|
|
4319
|
+
*/
|
|
4320
|
+
async shutdown() {
|
|
4321
|
+
await this.coordinator.stopAll();
|
|
4322
|
+
for (const b of this.subagentBridges.values()) {
|
|
4323
|
+
await b.stop().catch(() => void 0);
|
|
4324
|
+
}
|
|
4325
|
+
this.subagentBridges.clear();
|
|
4326
|
+
await this.bridge.stop().catch(() => void 0);
|
|
4327
|
+
if (this.manifestPath) await this.writeManifest().catch(() => void 0);
|
|
4328
|
+
}
|
|
4329
|
+
/**
|
|
4330
|
+
* Hand a task to the coordinator. Returns the assigned task id so
|
|
4331
|
+
* callers can wait on it via `awaitTasks([id])`. The coordinator's
|
|
4332
|
+
* concurrency limit applies — the task may queue before running.
|
|
4333
|
+
*/
|
|
4334
|
+
async assign(task) {
|
|
4335
|
+
const taskWithId = task.id ? task : { ...task, id: randomUUID() };
|
|
4336
|
+
if (task.subagentId) {
|
|
4337
|
+
const entry = this.manifestEntries.get(task.subagentId);
|
|
4338
|
+
if (entry) entry.taskIds.push(taskWithId.id);
|
|
4339
|
+
}
|
|
4340
|
+
await this.coordinator.assign(taskWithId);
|
|
4341
|
+
return taskWithId.id;
|
|
4342
|
+
}
|
|
4343
|
+
/**
|
|
4344
|
+
* Block until every task id resolves. Returns results in the same
|
|
4345
|
+
* order as the input. If any task hasn't completed by the time this
|
|
4346
|
+
* is called, the promise hangs until it does — pair with a timeout
|
|
4347
|
+
* at the caller if that's a concern. Resolves immediately for ids
|
|
4348
|
+
* whose results were already cached.
|
|
4349
|
+
*/
|
|
4350
|
+
awaitTasks(taskIds) {
|
|
4351
|
+
return Promise.all(taskIds.map((id) => {
|
|
4352
|
+
const cached = this.completed.get(id);
|
|
4353
|
+
if (cached) return cached;
|
|
4354
|
+
const existing = this.taskWaiters.get(id);
|
|
4355
|
+
if (existing) return existing.promise;
|
|
4356
|
+
let resolve3;
|
|
4357
|
+
const promise = new Promise((res) => {
|
|
4358
|
+
resolve3 = res;
|
|
4359
|
+
});
|
|
4360
|
+
this.taskWaiters.set(id, { promise, resolve: resolve3 });
|
|
4361
|
+
return promise;
|
|
4362
|
+
}));
|
|
4363
|
+
}
|
|
4364
|
+
async terminate(subagentId) {
|
|
4365
|
+
await this.coordinator.stop(subagentId);
|
|
4366
|
+
}
|
|
4367
|
+
async terminateAll() {
|
|
4368
|
+
await this.coordinator.stopAll();
|
|
4369
|
+
}
|
|
4370
|
+
status() {
|
|
4371
|
+
return this.coordinator.getStatus();
|
|
4372
|
+
}
|
|
4373
|
+
/**
|
|
4374
|
+
* Subscribe to coordinator events. Currently only `task.completed` is
|
|
4375
|
+
* exposed (the others are internal lifecycle). Returns an unsubscribe
|
|
4376
|
+
* function. External callers (e.g. the CLI's `MultiAgentHost`) use this
|
|
4377
|
+
* to drive their own pending/results tracking without poking the
|
|
4378
|
+
* coordinator directly.
|
|
4379
|
+
*/
|
|
4380
|
+
on(event, handler) {
|
|
4381
|
+
this.coordinator.on(event, handler);
|
|
4382
|
+
return () => {
|
|
4383
|
+
this.coordinator.off(event, handler);
|
|
4384
|
+
};
|
|
4385
|
+
}
|
|
4386
|
+
/**
|
|
4387
|
+
* Snapshot of every task that has resolved (success, failed, timeout,
|
|
4388
|
+
* stopped) since the director started. Returned in completion order
|
|
4389
|
+
* via the internal map's iteration order. Used by `/fleet status` to
|
|
4390
|
+
* paint the completed table without reaching into private state.
|
|
4391
|
+
*/
|
|
4392
|
+
completedResults() {
|
|
4393
|
+
return Array.from(this.completed.values());
|
|
4394
|
+
}
|
|
4395
|
+
snapshot() {
|
|
4396
|
+
return this.usage.snapshot();
|
|
4397
|
+
}
|
|
4398
|
+
/**
|
|
4399
|
+
* Compose the leader/director-agent system prompt: fleet preamble +
|
|
4400
|
+
* (optional) roster summary + user base prompt. Pass the result to your
|
|
4401
|
+
* leader Agent's `ctx.systemPrompt` when constructing it.
|
|
4402
|
+
*
|
|
4403
|
+
* `basePrompt` defaults to `config.leaderSystemPrompt` so callers can
|
|
4404
|
+
* use the no-arg form when the multi-agent config already carries it.
|
|
4405
|
+
*/
|
|
4406
|
+
leaderSystemPrompt(basePrompt) {
|
|
4407
|
+
return composeDirectorPrompt({
|
|
4408
|
+
basePrompt: basePrompt ?? this.coordinator.config.leaderSystemPrompt,
|
|
4409
|
+
directorPreamble: this.directorPreamble,
|
|
4410
|
+
rosterSummary: this.roster ? rosterSummaryFromConfigs(this.roster) : void 0
|
|
4411
|
+
});
|
|
4412
|
+
}
|
|
4413
|
+
/**
|
|
4414
|
+
* Compose a subagent's system prompt for a given `SubagentConfig`:
|
|
4415
|
+
* baseline + role + task + per-spawn override. Returned by value — does
|
|
4416
|
+
* not mutate the config. Factories (the user-supplied `AgentFactory`)
|
|
4417
|
+
* should call this when building each subagent's Agent so the bridge
|
|
4418
|
+
* contract, role context, and override are all surfaced.
|
|
4419
|
+
*
|
|
4420
|
+
* When `taskBrief` is omitted the Task section is dropped. Pass the
|
|
4421
|
+
* actual task description here to reinforce it in the system prompt
|
|
4422
|
+
* (the runner already passes it as user input — duplicating in the
|
|
4423
|
+
* system prompt is optional but improves anchoring on small models).
|
|
4424
|
+
*/
|
|
4425
|
+
subagentSystemPrompt(config, taskBrief) {
|
|
4426
|
+
return composeSubagentPrompt({
|
|
4427
|
+
baseline: this.subagentBaseline,
|
|
4428
|
+
role: config.prompt,
|
|
4429
|
+
task: taskBrief,
|
|
4430
|
+
override: config.systemPromptOverride
|
|
4431
|
+
});
|
|
4432
|
+
}
|
|
4433
|
+
/**
|
|
4434
|
+
* Build the tool set the LLM-driven director uses to orchestrate.
|
|
4435
|
+
* Returns an array of `Tool` definitions; register these on the
|
|
4436
|
+
* director's `Agent` to expose `spawn_subagent`, `assign_task`, etc.
|
|
4437
|
+
* Each tool's `execute()` delegates straight to the matching method
|
|
4438
|
+
* above.
|
|
4439
|
+
*
|
|
4440
|
+
* Tools all carry `permission: 'auto'` — the *user* has already
|
|
4441
|
+
* approved running the director when they kicked off the run, so
|
|
4442
|
+
* gating individual orchestration calls behind a confirm prompt
|
|
4443
|
+
* would just be noise. The actual subagent tools they spawn are
|
|
4444
|
+
* still permission-checked normally.
|
|
4445
|
+
*/
|
|
4446
|
+
tools(roster) {
|
|
4447
|
+
const t = [
|
|
4448
|
+
makeSpawnTool(this, roster),
|
|
4449
|
+
makeAssignTool(this),
|
|
4450
|
+
makeAwaitTasksTool(this),
|
|
4451
|
+
makeAskTool(this),
|
|
4452
|
+
makeRollUpTool(this),
|
|
4453
|
+
makeTerminateTool(this),
|
|
4454
|
+
makeFleetStatusTool(this),
|
|
4455
|
+
makeFleetUsageTool(this)
|
|
4456
|
+
];
|
|
4457
|
+
return t;
|
|
4458
|
+
}
|
|
4459
|
+
};
|
|
4460
|
+
function makeSpawnTool(director, roster) {
|
|
4461
|
+
const inputSchema = {
|
|
4462
|
+
type: "object",
|
|
4463
|
+
properties: {
|
|
4464
|
+
role: { type: "string", description: "Roster role id (preferred). When set, the spawn uses the matching config from the roster and ignores other fields." },
|
|
4465
|
+
name: { type: "string", description: "Display name for the subagent. Required when not using roster." },
|
|
4466
|
+
provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
|
|
4467
|
+
model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
|
|
4468
|
+
systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
|
|
4469
|
+
maxIterations: { type: "number" },
|
|
4470
|
+
maxToolCalls: { type: "number" },
|
|
4471
|
+
maxCostUsd: { type: "number" }
|
|
4472
|
+
},
|
|
4473
|
+
required: []
|
|
4474
|
+
};
|
|
4475
|
+
return {
|
|
4476
|
+
name: "spawn_subagent",
|
|
4477
|
+
description: "Create a new subagent under this director. Returns the subagent id. Use this when you need a worker with a specific provider, model, or role to handle a piece of the plan.",
|
|
4478
|
+
usageHint: "Either pass `role` (matches the roster) OR pass `name` + optional `provider`/`model`. Returns `{ subagentId }`.",
|
|
4479
|
+
permission: "auto",
|
|
4480
|
+
mutating: false,
|
|
4481
|
+
inputSchema,
|
|
4482
|
+
async execute(input) {
|
|
4483
|
+
const i = input ?? {};
|
|
4484
|
+
const role = typeof i.role === "string" ? i.role : void 0;
|
|
4485
|
+
const base = role && roster ? roster[role] : void 0;
|
|
4486
|
+
if (role && !base) {
|
|
4487
|
+
return { error: `unknown role "${role}". roster has: ${roster ? Object.keys(roster).join(", ") : "(empty)"}` };
|
|
4488
|
+
}
|
|
4489
|
+
const cfg = {
|
|
4490
|
+
...base ?? { name: i.name ?? "subagent" }
|
|
4491
|
+
};
|
|
4492
|
+
if (typeof i.name === "string") cfg.name = i.name;
|
|
4493
|
+
if (typeof i.provider === "string") cfg.provider = i.provider;
|
|
4494
|
+
if (typeof i.model === "string") cfg.model = i.model;
|
|
4495
|
+
if (typeof i.systemPromptOverride === "string") cfg.systemPromptOverride = i.systemPromptOverride;
|
|
4496
|
+
if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
|
|
4497
|
+
if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
|
|
4498
|
+
if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
|
|
4499
|
+
const subagentId = await director.spawn(cfg);
|
|
4500
|
+
return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name };
|
|
4501
|
+
}
|
|
4502
|
+
};
|
|
4503
|
+
}
|
|
4504
|
+
function makeAssignTool(director) {
|
|
4505
|
+
const inputSchema = {
|
|
4506
|
+
type: "object",
|
|
4507
|
+
properties: {
|
|
4508
|
+
subagentId: { type: "string", description: "Target subagent id. Required." },
|
|
4509
|
+
description: { type: "string", description: "The task in natural language \u2014 what you want this subagent to do." },
|
|
4510
|
+
maxToolCalls: { type: "number", description: "Optional per-task tool-call budget override." },
|
|
4511
|
+
timeoutMs: { type: "number", description: "Optional per-task timeout in ms." }
|
|
4512
|
+
},
|
|
4513
|
+
required: ["subagentId", "description"]
|
|
4514
|
+
};
|
|
4515
|
+
return {
|
|
4516
|
+
name: "assign_task",
|
|
4517
|
+
description: "Hand a task to a previously spawned subagent. Returns the task id \u2014 pass it to `await_tasks` to block on completion.",
|
|
4518
|
+
permission: "auto",
|
|
4519
|
+
mutating: false,
|
|
4520
|
+
inputSchema,
|
|
4521
|
+
async execute(input) {
|
|
4522
|
+
const i = input;
|
|
4523
|
+
const task = {
|
|
4524
|
+
id: randomUUID(),
|
|
4525
|
+
description: i.description,
|
|
4526
|
+
subagentId: i.subagentId,
|
|
4527
|
+
maxToolCalls: i.maxToolCalls,
|
|
4528
|
+
timeoutMs: i.timeoutMs
|
|
4529
|
+
};
|
|
4530
|
+
const taskId = await director.assign(task);
|
|
4531
|
+
return { taskId, subagentId: i.subagentId };
|
|
4532
|
+
}
|
|
4533
|
+
};
|
|
4534
|
+
}
|
|
4535
|
+
function makeAwaitTasksTool(director) {
|
|
4536
|
+
const inputSchema = {
|
|
4537
|
+
type: "object",
|
|
4538
|
+
properties: {
|
|
4539
|
+
taskIds: {
|
|
4540
|
+
type: "array",
|
|
4541
|
+
items: { type: "string" },
|
|
4542
|
+
description: "One or more task ids returned by `assign_task`. The call blocks until every id resolves."
|
|
4543
|
+
}
|
|
4544
|
+
},
|
|
4545
|
+
required: ["taskIds"]
|
|
4546
|
+
};
|
|
4547
|
+
return {
|
|
4548
|
+
name: "await_tasks",
|
|
4549
|
+
description: "Block until every named task completes. Returns the array of TaskResult \u2014 use this to gather subagent output before deciding the next step.",
|
|
4550
|
+
permission: "auto",
|
|
4551
|
+
mutating: false,
|
|
4552
|
+
inputSchema,
|
|
4553
|
+
async execute(input) {
|
|
4554
|
+
const i = input;
|
|
4555
|
+
const results = await director.awaitTasks(i.taskIds);
|
|
4556
|
+
return { results };
|
|
4557
|
+
}
|
|
4558
|
+
};
|
|
4559
|
+
}
|
|
4560
|
+
function makeAskTool(director) {
|
|
4561
|
+
const inputSchema = {
|
|
4562
|
+
type: "object",
|
|
4563
|
+
properties: {
|
|
4564
|
+
subagentId: { type: "string", description: "Subagent to ask. Must be a previously spawned id." },
|
|
4565
|
+
question: { type: "string", description: "The question or instruction. Sent as the bridge message payload." },
|
|
4566
|
+
timeoutMs: { type: "number", description: "Optional timeout in ms (default 30s)." }
|
|
4567
|
+
},
|
|
4568
|
+
required: ["subagentId", "question"]
|
|
4569
|
+
};
|
|
4570
|
+
return {
|
|
4571
|
+
name: "ask_subagent",
|
|
4572
|
+
description: "Synchronously ask a subagent a question. Blocks until the subagent replies via the bridge (or the timeout fires). Use this when you need a one-shot answer without spawning a fresh task.",
|
|
4573
|
+
permission: "auto",
|
|
4574
|
+
mutating: false,
|
|
4575
|
+
inputSchema,
|
|
4576
|
+
async execute(input) {
|
|
4577
|
+
const i = input;
|
|
4578
|
+
try {
|
|
4579
|
+
const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
|
|
4580
|
+
return { ok: true, answer };
|
|
4581
|
+
} catch (err) {
|
|
4582
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
};
|
|
4586
|
+
}
|
|
4587
|
+
function makeRollUpTool(director) {
|
|
4588
|
+
const inputSchema = {
|
|
4589
|
+
type: "object",
|
|
4590
|
+
properties: {
|
|
4591
|
+
taskIds: {
|
|
4592
|
+
type: "array",
|
|
4593
|
+
items: { type: "string" },
|
|
4594
|
+
description: "Completed task ids to aggregate. Pass the ids returned by previous `assign_task` calls."
|
|
4595
|
+
},
|
|
4596
|
+
style: {
|
|
4597
|
+
type: "string",
|
|
4598
|
+
enum: ["markdown", "json"],
|
|
4599
|
+
description: "Output flavor \u2014 markdown (default) for in-prompt summarization, json for structured downstream processing."
|
|
4600
|
+
}
|
|
4601
|
+
},
|
|
4602
|
+
required: ["taskIds"]
|
|
4603
|
+
};
|
|
4604
|
+
return {
|
|
4605
|
+
name: "roll_up",
|
|
4606
|
+
description: "Aggregate completed task results into a single formatted summary. Use this after `await_tasks` to fold subagent outputs back into the director's context before deciding the next step.",
|
|
4607
|
+
permission: "auto",
|
|
4608
|
+
mutating: false,
|
|
4609
|
+
inputSchema,
|
|
4610
|
+
async execute(input) {
|
|
4611
|
+
const i = input;
|
|
4612
|
+
const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
|
|
4613
|
+
return { summary, count: i.taskIds.length };
|
|
4614
|
+
}
|
|
4615
|
+
};
|
|
4616
|
+
}
|
|
4617
|
+
function makeTerminateTool(director) {
|
|
4618
|
+
const inputSchema = {
|
|
4619
|
+
type: "object",
|
|
4620
|
+
properties: {
|
|
4621
|
+
subagentId: { type: "string", description: "Subagent to abort." }
|
|
4622
|
+
},
|
|
4623
|
+
required: ["subagentId"]
|
|
4624
|
+
};
|
|
4625
|
+
return {
|
|
4626
|
+
name: "terminate_subagent",
|
|
4627
|
+
description: 'Forcibly abort a subagent. Use sparingly \u2014 prefer waiting on the natural budget to expire. The current task (if any) ends with status "stopped".',
|
|
4628
|
+
permission: "auto",
|
|
4629
|
+
mutating: true,
|
|
4630
|
+
inputSchema,
|
|
4631
|
+
async execute(input) {
|
|
4632
|
+
const i = input;
|
|
4633
|
+
await director.terminate(i.subagentId);
|
|
4634
|
+
return { ok: true };
|
|
4635
|
+
}
|
|
4636
|
+
};
|
|
4637
|
+
}
|
|
4638
|
+
function makeFleetStatusTool(director) {
|
|
4639
|
+
return {
|
|
4640
|
+
name: "fleet_status",
|
|
4641
|
+
description: "Snapshot of the fleet \u2014 every subagent's current status, pending vs. completed task counts, and the running total iteration count. Cheap; call freely.",
|
|
4642
|
+
permission: "auto",
|
|
4643
|
+
mutating: false,
|
|
4644
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
4645
|
+
async execute() {
|
|
4646
|
+
return director.status();
|
|
4647
|
+
}
|
|
4648
|
+
};
|
|
4649
|
+
}
|
|
4650
|
+
function makeFleetUsageTool(director) {
|
|
4651
|
+
return {
|
|
4652
|
+
name: "fleet_usage",
|
|
4653
|
+
description: "Token + cost breakdown across the fleet, per-subagent and totals. Use this to reason about which workers to assign costly tasks to or when to wrap up to stay within budget.",
|
|
4654
|
+
permission: "auto",
|
|
4655
|
+
mutating: false,
|
|
4656
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
4657
|
+
async execute() {
|
|
4658
|
+
return director.snapshot();
|
|
4659
|
+
}
|
|
4660
|
+
};
|
|
4661
|
+
}
|
|
4662
|
+
function makeDirectorSessionFactory(opts) {
|
|
4663
|
+
const runId = opts.directorRunId ?? `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-director`;
|
|
4664
|
+
let store;
|
|
4665
|
+
let dir;
|
|
4666
|
+
if (opts.store) {
|
|
4667
|
+
store = opts.store;
|
|
4668
|
+
dir = opts.sessionsRoot ? path2.join(opts.sessionsRoot, runId) : "(caller-managed)";
|
|
4669
|
+
} else if (opts.sessionsRoot) {
|
|
4670
|
+
dir = path2.join(opts.sessionsRoot, runId);
|
|
4671
|
+
store = new DefaultSessionStore({ dir });
|
|
4672
|
+
} else {
|
|
4673
|
+
throw new Error(
|
|
4674
|
+
"makeDirectorSessionFactory requires either `store` or `sessionsRoot`"
|
|
4675
|
+
);
|
|
4676
|
+
}
|
|
4677
|
+
return {
|
|
4678
|
+
dir,
|
|
4679
|
+
directorRunId: runId,
|
|
4680
|
+
async createSubagentSession({ subagentId, provider, model, title }) {
|
|
4681
|
+
return store.create({
|
|
4682
|
+
id: subagentId,
|
|
4683
|
+
title: title ?? subagentId,
|
|
4684
|
+
provider: provider ?? "unknown",
|
|
4685
|
+
model: model ?? "unknown"
|
|
4686
|
+
});
|
|
4687
|
+
}
|
|
4688
|
+
};
|
|
4689
|
+
}
|
|
4690
|
+
|
|
3575
4691
|
// src/defaults/autonomous-runner.ts
|
|
3576
4692
|
var DoneConditionChecker = class {
|
|
3577
4693
|
constructor(condition) {
|
|
3578
4694
|
this.condition = condition;
|
|
4695
|
+
this.compiledRegex = condition.type === "output_match" && condition.pattern ? new RegExp(condition.pattern) : null;
|
|
3579
4696
|
}
|
|
3580
4697
|
condition;
|
|
4698
|
+
compiledRegex;
|
|
3581
4699
|
check(state) {
|
|
3582
4700
|
switch (this.condition.type) {
|
|
3583
4701
|
case "iterations":
|
|
@@ -3591,11 +4709,8 @@ var DoneConditionChecker = class {
|
|
|
3591
4709
|
}
|
|
3592
4710
|
break;
|
|
3593
4711
|
case "output_match":
|
|
3594
|
-
if (this.
|
|
3595
|
-
|
|
3596
|
-
if (regex.test(state.lastOutput)) {
|
|
3597
|
-
return { done: true, reason: `output matched pattern "${this.condition.pattern}"`, ...state };
|
|
3598
|
-
}
|
|
4712
|
+
if (this.compiledRegex && state.lastOutput && this.compiledRegex.test(state.lastOutput)) {
|
|
4713
|
+
return { done: true, reason: `output matched pattern "${this.condition.pattern}"`, ...state };
|
|
3599
4714
|
}
|
|
3600
4715
|
break;
|
|
3601
4716
|
}
|
|
@@ -3614,6 +4729,16 @@ var AutonomousRunner = class {
|
|
|
3614
4729
|
stopped = false;
|
|
3615
4730
|
doneChecker;
|
|
3616
4731
|
async run() {
|
|
4732
|
+
const offToolExecuted = this.opts.agent.events?.on?.("tool.executed", () => {
|
|
4733
|
+
this.toolCalls++;
|
|
4734
|
+
});
|
|
4735
|
+
try {
|
|
4736
|
+
return await this.runLoop();
|
|
4737
|
+
} finally {
|
|
4738
|
+
offToolExecuted?.();
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
async runLoop() {
|
|
3617
4742
|
while (!this.stopped) {
|
|
3618
4743
|
const check = this.doneChecker.check({
|
|
3619
4744
|
iterations: this.iterations,
|
|
@@ -3640,7 +4765,6 @@ var AutonomousRunner = class {
|
|
|
3640
4765
|
);
|
|
3641
4766
|
this.iterations++;
|
|
3642
4767
|
this.lastOutput = result.finalText;
|
|
3643
|
-
this.toolCalls++;
|
|
3644
4768
|
if (result.status === "failed" || result.status === "aborted") {
|
|
3645
4769
|
const failedResult = {
|
|
3646
4770
|
status: result.status,
|
|
@@ -3652,7 +4776,8 @@ var AutonomousRunner = class {
|
|
|
3652
4776
|
return failedResult;
|
|
3653
4777
|
}
|
|
3654
4778
|
} catch (e) {
|
|
3655
|
-
|
|
4779
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
4780
|
+
if (msg.includes("timeout")) {
|
|
3656
4781
|
const timeoutResult = {
|
|
3657
4782
|
status: "failed",
|
|
3658
4783
|
error: toWrongStackError(e),
|
|
@@ -3681,14 +4806,11 @@ var AutonomousRunner = class {
|
|
|
3681
4806
|
|
|
3682
4807
|
// src/defaults/spec-parser.ts
|
|
3683
4808
|
var SpecParser = class {
|
|
3684
|
-
constructor(opts = {}) {
|
|
3685
|
-
this.opts = opts;
|
|
3686
|
-
}
|
|
3687
|
-
opts;
|
|
3688
4809
|
parse(content) {
|
|
3689
4810
|
const lines = content.split("\n");
|
|
3690
4811
|
const sections = this.extractSections(lines);
|
|
3691
4812
|
const requirements = this.extractRequirements(lines);
|
|
4813
|
+
const now = Date.now();
|
|
3692
4814
|
return {
|
|
3693
4815
|
id: crypto.randomUUID(),
|
|
3694
4816
|
title: this.extractTitle(lines),
|
|
@@ -3697,8 +4819,8 @@ var SpecParser = class {
|
|
|
3697
4819
|
overview: this.extractOverview(lines),
|
|
3698
4820
|
sections,
|
|
3699
4821
|
requirements,
|
|
3700
|
-
createdAt:
|
|
3701
|
-
updatedAt:
|
|
4822
|
+
createdAt: now,
|
|
4823
|
+
updatedAt: now
|
|
3702
4824
|
};
|
|
3703
4825
|
}
|
|
3704
4826
|
extractTitle(lines) {
|
|
@@ -3790,20 +4912,13 @@ var SpecParser = class {
|
|
|
3790
4912
|
parseRequirementLine(line, id) {
|
|
3791
4913
|
const trimmed = line.trim();
|
|
3792
4914
|
if (!trimmed || trimmed.startsWith("#")) return null;
|
|
3793
|
-
const
|
|
3794
|
-
|
|
3795
|
-
"non-functional": "non-functional",
|
|
3796
|
-
"security": "security",
|
|
3797
|
-
"performance": "performance",
|
|
3798
|
-
"ux": "ux"
|
|
3799
|
-
};
|
|
4915
|
+
const lower = trimmed.toLowerCase();
|
|
4916
|
+
const types = ["functional", "non-functional", "security", "performance", "ux"];
|
|
3800
4917
|
let type = "functional";
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
if (trimmed.toLowerCase().includes(`[${key}]`)) {
|
|
3804
|
-
type = val;
|
|
3805
|
-
}
|
|
4918
|
+
for (const t of types) {
|
|
4919
|
+
if (lower.includes(`[${t}]`)) type = t;
|
|
3806
4920
|
}
|
|
4921
|
+
let priority = "medium";
|
|
3807
4922
|
if (trimmed.includes("[critical]") || trimmed.includes("[prio:high]")) {
|
|
3808
4923
|
priority = "critical";
|
|
3809
4924
|
} else if (trimmed.includes("[high]")) {
|
|
@@ -3893,9 +5008,10 @@ var SpecParser = class {
|
|
|
3893
5008
|
warnings.push({ path: `requirement.${req.id}`, message: "No acceptance criteria defined" });
|
|
3894
5009
|
}
|
|
3895
5010
|
}
|
|
5011
|
+
const reqIds = new Set(spec.requirements.map((r) => r.id));
|
|
3896
5012
|
const blockedByIds = new Set(spec.requirements.flatMap((r) => r.blockedBy ?? []));
|
|
3897
5013
|
for (const id of blockedByIds) {
|
|
3898
|
-
if (!
|
|
5014
|
+
if (!reqIds.has(id)) {
|
|
3899
5015
|
errors.push({ path: "requirements", message: `BlockedBy references non-existent requirement: ${id}` });
|
|
3900
5016
|
}
|
|
3901
5017
|
}
|
|
@@ -3925,25 +5041,21 @@ var TaskGenerator = class {
|
|
|
3925
5041
|
status: "pending"
|
|
3926
5042
|
});
|
|
3927
5043
|
}
|
|
3928
|
-
const
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
const task = this.createTaskFromRequirement(req, spec.title);
|
|
3938
|
-
this.opts.taskTracker.addNode(task);
|
|
3939
|
-
}
|
|
3940
|
-
for (const req of mediumReqs) {
|
|
3941
|
-
const task = this.createTaskFromRequirement(req, spec.title);
|
|
3942
|
-
this.opts.taskTracker.addNode(task);
|
|
5044
|
+
const byPriority = {
|
|
5045
|
+
critical: [],
|
|
5046
|
+
high: [],
|
|
5047
|
+
medium: [],
|
|
5048
|
+
low: []
|
|
5049
|
+
};
|
|
5050
|
+
for (const req of spec.requirements) {
|
|
5051
|
+
const bucket = byPriority[req.priority] ?? byPriority.medium;
|
|
5052
|
+
bucket.push(req);
|
|
3943
5053
|
}
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
5054
|
+
const order = ["critical", "high", "medium", "low"];
|
|
5055
|
+
for (const p of order) {
|
|
5056
|
+
for (const req of byPriority[p]) {
|
|
5057
|
+
this.opts.taskTracker.addNode(this.createTaskFromRequirement(req));
|
|
5058
|
+
}
|
|
3947
5059
|
}
|
|
3948
5060
|
if (spec.apiEndpoints && spec.apiEndpoints.length > 0) {
|
|
3949
5061
|
const apiParent = this.opts.taskTracker.addNode({
|
|
@@ -3977,17 +5089,15 @@ var TaskGenerator = class {
|
|
|
3977
5089
|
});
|
|
3978
5090
|
return graph;
|
|
3979
5091
|
}
|
|
3980
|
-
createTaskFromRequirement(req
|
|
3981
|
-
const type = this.mapRequirementType(req.type);
|
|
3982
|
-
const tags = [req.type, req.priority];
|
|
5092
|
+
createTaskFromRequirement(req) {
|
|
3983
5093
|
return {
|
|
3984
5094
|
title: req.description,
|
|
3985
|
-
description: this.buildDescription(req
|
|
3986
|
-
type,
|
|
3987
|
-
priority:
|
|
5095
|
+
description: this.buildDescription(req),
|
|
5096
|
+
type: this.mapRequirementType(req.type),
|
|
5097
|
+
priority: req.priority,
|
|
3988
5098
|
status: "pending",
|
|
3989
5099
|
specRequirementId: req.id,
|
|
3990
|
-
tags,
|
|
5100
|
+
tags: [req.type, req.priority],
|
|
3991
5101
|
estimateHours: this.estimateHours(req)
|
|
3992
5102
|
};
|
|
3993
5103
|
}
|
|
@@ -4002,7 +5112,7 @@ var TaskGenerator = class {
|
|
|
4002
5112
|
estimateHours: this.estimateForEndpoint(endpoint)
|
|
4003
5113
|
};
|
|
4004
5114
|
}
|
|
4005
|
-
buildDescription(req
|
|
5115
|
+
buildDescription(req) {
|
|
4006
5116
|
const lines = [
|
|
4007
5117
|
req.description,
|
|
4008
5118
|
"",
|
|
@@ -4036,20 +5146,6 @@ var TaskGenerator = class {
|
|
|
4036
5146
|
return "feature";
|
|
4037
5147
|
}
|
|
4038
5148
|
}
|
|
4039
|
-
mapPriority(priority) {
|
|
4040
|
-
switch (priority) {
|
|
4041
|
-
case "critical":
|
|
4042
|
-
return "critical";
|
|
4043
|
-
case "high":
|
|
4044
|
-
return "high";
|
|
4045
|
-
case "medium":
|
|
4046
|
-
return "medium";
|
|
4047
|
-
case "low":
|
|
4048
|
-
return "low";
|
|
4049
|
-
default:
|
|
4050
|
-
return "medium";
|
|
4051
|
-
}
|
|
4052
|
-
}
|
|
4053
5149
|
estimateHours(req) {
|
|
4054
5150
|
switch (req.priority) {
|
|
4055
5151
|
case "critical":
|
|
@@ -4120,16 +5216,33 @@ var DefaultTaskStore = class {
|
|
|
4120
5216
|
|
|
4121
5217
|
// src/types/task-graph.ts
|
|
4122
5218
|
function computeTaskProgress(graph) {
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
const
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
5219
|
+
let completed = 0, pending = 0, inProgress = 0, blocked = 0, failed = 0, review = 0;
|
|
5220
|
+
let estimatedHours = 0, actualHours = 0;
|
|
5221
|
+
for (const n of graph.nodes.values()) {
|
|
5222
|
+
switch (n.status) {
|
|
5223
|
+
case "completed":
|
|
5224
|
+
completed++;
|
|
5225
|
+
break;
|
|
5226
|
+
case "pending":
|
|
5227
|
+
pending++;
|
|
5228
|
+
break;
|
|
5229
|
+
case "in_progress":
|
|
5230
|
+
inProgress++;
|
|
5231
|
+
break;
|
|
5232
|
+
case "blocked":
|
|
5233
|
+
blocked++;
|
|
5234
|
+
break;
|
|
5235
|
+
case "failed":
|
|
5236
|
+
failed++;
|
|
5237
|
+
break;
|
|
5238
|
+
case "review":
|
|
5239
|
+
review++;
|
|
5240
|
+
break;
|
|
5241
|
+
}
|
|
5242
|
+
estimatedHours += n.estimateHours ?? 0;
|
|
5243
|
+
actualHours += n.actualHours ?? 0;
|
|
5244
|
+
}
|
|
5245
|
+
const total = graph.nodes.size;
|
|
4133
5246
|
return {
|
|
4134
5247
|
total,
|
|
4135
5248
|
pending,
|
|
@@ -4185,40 +5298,40 @@ var TaskTracker = class {
|
|
|
4185
5298
|
this.graph.rootNodes.push(newNode.id);
|
|
4186
5299
|
}
|
|
4187
5300
|
this.graph.updatedAt = now;
|
|
4188
|
-
this.
|
|
5301
|
+
this.persist();
|
|
4189
5302
|
return newNode;
|
|
4190
5303
|
}
|
|
4191
5304
|
addEdge(from, to, type = "depends_on") {
|
|
4192
5305
|
if (!this.graph) throw new Error("No graph loaded");
|
|
4193
|
-
|
|
5306
|
+
this.graph.edges.push({
|
|
4194
5307
|
id: crypto.randomUUID(),
|
|
4195
5308
|
from,
|
|
4196
5309
|
to,
|
|
4197
5310
|
type
|
|
4198
|
-
};
|
|
4199
|
-
this.graph.edges.push(edge);
|
|
5311
|
+
});
|
|
4200
5312
|
this.graph.updatedAt = Date.now();
|
|
4201
|
-
this.
|
|
5313
|
+
this.persist();
|
|
4202
5314
|
}
|
|
4203
5315
|
updateNodeStatus(id, status, reason) {
|
|
4204
5316
|
if (!this.graph) throw new Error("No graph loaded");
|
|
4205
5317
|
const node = this.graph.nodes.get(id);
|
|
4206
5318
|
if (!node) throw new Error(`Node ${id} not found`);
|
|
4207
5319
|
const from = node.status;
|
|
5320
|
+
const now = Date.now();
|
|
4208
5321
|
node.status = status;
|
|
4209
|
-
node.updatedAt =
|
|
5322
|
+
node.updatedAt = now;
|
|
4210
5323
|
if (status === "completed") {
|
|
4211
|
-
node.completedAt =
|
|
5324
|
+
node.completedAt = now;
|
|
4212
5325
|
}
|
|
4213
|
-
this.transitions.push({ from, to: status, timestamp:
|
|
5326
|
+
this.transitions.push({ from, to: status, timestamp: now, reason });
|
|
4214
5327
|
if (status === "completed") {
|
|
4215
5328
|
this.unblockDependents(id);
|
|
4216
5329
|
}
|
|
4217
5330
|
if (status === "in_progress") {
|
|
4218
5331
|
this.checkAndBlockIfNeeded(id);
|
|
4219
5332
|
}
|
|
4220
|
-
this.graph.updatedAt =
|
|
4221
|
-
this.
|
|
5333
|
+
this.graph.updatedAt = now;
|
|
5334
|
+
this.persist();
|
|
4222
5335
|
}
|
|
4223
5336
|
getNode(id) {
|
|
4224
5337
|
return this.graph?.nodes.get(id);
|
|
@@ -4239,9 +5352,7 @@ var TaskTracker = class {
|
|
|
4239
5352
|
}
|
|
4240
5353
|
if (sort) {
|
|
4241
5354
|
nodes.sort((a, b) => {
|
|
4242
|
-
const
|
|
4243
|
-
const bVal = b[sort.field] ?? "";
|
|
4244
|
-
const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
5355
|
+
const cmp = compareByField(a, b, sort.field);
|
|
4245
5356
|
return sort.direction === "asc" ? cmp : -cmp;
|
|
4246
5357
|
});
|
|
4247
5358
|
}
|
|
@@ -4320,7 +5431,47 @@ var TaskTracker = class {
|
|
|
4320
5431
|
}
|
|
4321
5432
|
}
|
|
4322
5433
|
}
|
|
5434
|
+
/**
|
|
5435
|
+
* Fire-and-forget persistence with attached error handler.
|
|
5436
|
+
* Synchronous mutators (addNode/addEdge/updateNodeStatus) use this to
|
|
5437
|
+
* avoid forcing an async cascade through every caller; if the store
|
|
5438
|
+
* rejects, the configured `onPersistError` is invoked so failures are
|
|
5439
|
+
* surfaced instead of swallowed by an unhandled promise rejection.
|
|
5440
|
+
*/
|
|
5441
|
+
persist() {
|
|
5442
|
+
if (!this.graph) return;
|
|
5443
|
+
this.opts.store.saveGraph(this.graph).catch((err) => {
|
|
5444
|
+
if (this.opts.onPersistError) this.opts.onPersistError(err);
|
|
5445
|
+
else console.warn("[task-tracker] saveGraph failed:", err instanceof Error ? err.message : String(err));
|
|
5446
|
+
});
|
|
5447
|
+
}
|
|
5448
|
+
};
|
|
5449
|
+
var PRIORITY_RANK = {
|
|
5450
|
+
critical: 0,
|
|
5451
|
+
high: 1,
|
|
5452
|
+
medium: 2,
|
|
5453
|
+
low: 3
|
|
4323
5454
|
};
|
|
5455
|
+
var STATUS_RANK = {
|
|
5456
|
+
in_progress: 0,
|
|
5457
|
+
pending: 1,
|
|
5458
|
+
review: 2,
|
|
5459
|
+
blocked: 3,
|
|
5460
|
+
failed: 4,
|
|
5461
|
+
completed: 5
|
|
5462
|
+
};
|
|
5463
|
+
function compareByField(a, b, field) {
|
|
5464
|
+
switch (field) {
|
|
5465
|
+
case "priority":
|
|
5466
|
+
return PRIORITY_RANK[a.priority] - PRIORITY_RANK[b.priority];
|
|
5467
|
+
case "status":
|
|
5468
|
+
return STATUS_RANK[a.status] - STATUS_RANK[b.status];
|
|
5469
|
+
case "createdAt":
|
|
5470
|
+
return a.createdAt - b.createdAt;
|
|
5471
|
+
case "updatedAt":
|
|
5472
|
+
return a.updatedAt - b.updatedAt;
|
|
5473
|
+
}
|
|
5474
|
+
}
|
|
4324
5475
|
|
|
4325
5476
|
// src/defaults/task-flow.ts
|
|
4326
5477
|
var TaskFlow = class {
|
|
@@ -4372,9 +5523,10 @@ var TaskFlow = class {
|
|
|
4372
5523
|
const task = batch[i];
|
|
4373
5524
|
if (!result || !task) continue;
|
|
4374
5525
|
if (result.status === "rejected") {
|
|
4375
|
-
|
|
4376
|
-
this.
|
|
4377
|
-
|
|
5526
|
+
const reason = result.reason;
|
|
5527
|
+
this.opts.tracker.updateNodeStatus(task.id, "failed", reason?.message);
|
|
5528
|
+
this.emit("task.failed", { taskId: task.id, error: reason?.message ?? "unknown" });
|
|
5529
|
+
ctx.onTaskFail?.(task, reason);
|
|
4378
5530
|
} else {
|
|
4379
5531
|
this.opts.tracker.updateNodeStatus(task.id, "completed");
|
|
4380
5532
|
this.emit("task.completed", { taskId: task.id, result: result.value });
|
|
@@ -4657,7 +5809,6 @@ function createToolOutputSerializer(opts = {}) {
|
|
|
4657
5809
|
}
|
|
4658
5810
|
const half = Math.floor(available / 2);
|
|
4659
5811
|
const first = text.slice(0, half);
|
|
4660
|
-
Buffer.byteLength(first, "utf8");
|
|
4661
5812
|
const second = text.slice(text.length - half);
|
|
4662
5813
|
return { text: `${first}${marker}${second}`, newBudget: 0 };
|
|
4663
5814
|
}
|
|
@@ -4707,7 +5858,7 @@ var ToolExecutor = class {
|
|
|
4707
5858
|
return { result, tool, durationMs: Date.now() - start };
|
|
4708
5859
|
}
|
|
4709
5860
|
} else {
|
|
4710
|
-
const suggestedPattern = this.subjectFor(tool.name, use.input) ?? tool.name;
|
|
5861
|
+
const suggestedPattern = this.subjectFor(tool.name, use.input, tool.subjectKey) ?? tool.name;
|
|
4711
5862
|
const pending = { type: "tool_confirm_pending", toolUseId: use.id, toolName: tool.name, input: use.input, suggestedPattern };
|
|
4712
5863
|
return { result: pending, tool, durationMs: Date.now() - start };
|
|
4713
5864
|
}
|
|
@@ -4739,15 +5890,31 @@ var ToolExecutor = class {
|
|
|
4739
5890
|
span?.end();
|
|
4740
5891
|
}
|
|
4741
5892
|
};
|
|
5893
|
+
const safeRun = async (use) => {
|
|
5894
|
+
try {
|
|
5895
|
+
return await runOne(use);
|
|
5896
|
+
} catch (err) {
|
|
5897
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5898
|
+
const scrubbed = this.opts.secretScrubber.scrub(msg);
|
|
5899
|
+
const result = {
|
|
5900
|
+
type: "tool_result",
|
|
5901
|
+
tool_use_id: use.id,
|
|
5902
|
+
content: `Tool "${use.name}" execution failed: ${scrubbed}`,
|
|
5903
|
+
is_error: true
|
|
5904
|
+
};
|
|
5905
|
+
budget = this.decrementBudget(result, budget);
|
|
5906
|
+
return { result, tool: this.registry.get(use.name), durationMs: 0 };
|
|
5907
|
+
}
|
|
5908
|
+
};
|
|
4742
5909
|
if (strategy === "sequential") {
|
|
4743
5910
|
const outputs = [];
|
|
4744
5911
|
for (const use of toolUses) {
|
|
4745
|
-
if (use) outputs.push(await
|
|
5912
|
+
if (use) outputs.push(await safeRun(use));
|
|
4746
5913
|
}
|
|
4747
5914
|
return { outputs, remainingBudget: budget };
|
|
4748
5915
|
}
|
|
4749
5916
|
if (strategy === "parallel") {
|
|
4750
|
-
const outputs = await Promise.all(toolUses.map((use) =>
|
|
5917
|
+
const outputs = await Promise.all(toolUses.map((use) => safeRun(use)));
|
|
4751
5918
|
return { outputs, remainingBudget: budget };
|
|
4752
5919
|
}
|
|
4753
5920
|
const nonMutating = [];
|
|
@@ -4758,10 +5925,10 @@ var ToolExecutor = class {
|
|
|
4758
5925
|
if (tool?.mutating) mutating.push(use);
|
|
4759
5926
|
else nonMutating.push(use);
|
|
4760
5927
|
}
|
|
4761
|
-
const firstPass = await Promise.all(nonMutating.map((use) =>
|
|
5928
|
+
const firstPass = await Promise.all(nonMutating.map((use) => safeRun(use)));
|
|
4762
5929
|
const secondPass = [];
|
|
4763
5930
|
for (const use of mutating) {
|
|
4764
|
-
secondPass.push(await
|
|
5931
|
+
secondPass.push(await safeRun(use));
|
|
4765
5932
|
}
|
|
4766
5933
|
return {
|
|
4767
5934
|
outputs: [...firstPass, ...secondPass],
|
|
@@ -4796,7 +5963,8 @@ var ToolExecutor = class {
|
|
|
4796
5963
|
}
|
|
4797
5964
|
async runWithTimeout(tool, input, parentSignal, ctx, toolUseId) {
|
|
4798
5965
|
if (parentSignal.aborted) {
|
|
4799
|
-
|
|
5966
|
+
if (parentSignal.reason instanceof Error) throw parentSignal.reason;
|
|
5967
|
+
throw new Error(typeof parentSignal.reason === "string" ? parentSignal.reason : "aborted");
|
|
4800
5968
|
}
|
|
4801
5969
|
const timeoutMs = tool.timeoutMs ?? this.iterationTimeoutMs;
|
|
4802
5970
|
const ctrl = new AbortController();
|
|
@@ -4865,16 +6033,23 @@ var ToolExecutor = class {
|
|
|
4865
6033
|
* Matches the logic in DefaultPermissionPolicy so the TUI shows the
|
|
4866
6034
|
* same subject that the trust file would use.
|
|
4867
6035
|
*/
|
|
4868
|
-
subjectFor(toolName, input) {
|
|
6036
|
+
subjectFor(toolName, input, subjectKey) {
|
|
4869
6037
|
if (!input || typeof input !== "object") return void 0;
|
|
4870
6038
|
const obj = input;
|
|
4871
6039
|
const globChars = /[*?\[\]]/g;
|
|
4872
6040
|
const escapeGlob = (s) => s.replace(globChars, (c) => `\\${c}`);
|
|
6041
|
+
const normalizePath = (s) => escapeGlob(s.replace(/\\/g, "/"));
|
|
6042
|
+
if (subjectKey) {
|
|
6043
|
+
const v = obj[subjectKey];
|
|
6044
|
+
if (typeof v === "string") {
|
|
6045
|
+
return subjectKey === "path" || subjectKey === "file" || subjectKey === "files" ? normalizePath(v) : escapeGlob(v);
|
|
6046
|
+
}
|
|
6047
|
+
}
|
|
4873
6048
|
if (toolName === "bash" && typeof obj.command === "string") {
|
|
4874
6049
|
return escapeGlob(obj.command);
|
|
4875
6050
|
}
|
|
4876
6051
|
if (typeof obj.path === "string") {
|
|
4877
|
-
return
|
|
6052
|
+
return normalizePath(obj.path);
|
|
4878
6053
|
}
|
|
4879
6054
|
if (typeof obj.url === "string") {
|
|
4880
6055
|
return escapeGlob(obj.url);
|
|
@@ -5298,8 +6473,9 @@ var DefaultHealthRegistry = class {
|
|
|
5298
6473
|
return { status, timestamp: Date.now(), checks: results };
|
|
5299
6474
|
}
|
|
5300
6475
|
async runOne(check) {
|
|
6476
|
+
let timer = null;
|
|
5301
6477
|
const timeout = new Promise((resolve3) => {
|
|
5302
|
-
setTimeout(
|
|
6478
|
+
timer = setTimeout(
|
|
5303
6479
|
() => resolve3({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
|
|
5304
6480
|
this.timeoutMs
|
|
5305
6481
|
);
|
|
@@ -5308,6 +6484,8 @@ var DefaultHealthRegistry = class {
|
|
|
5308
6484
|
return await Promise.race([check.check(), timeout]);
|
|
5309
6485
|
} catch (err) {
|
|
5310
6486
|
return { status: "unhealthy", detail: err instanceof Error ? err.message : String(err) };
|
|
6487
|
+
} finally {
|
|
6488
|
+
if (timer) clearTimeout(timer);
|
|
5311
6489
|
}
|
|
5312
6490
|
}
|
|
5313
6491
|
};
|
|
@@ -5480,7 +6658,7 @@ var PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
|
|
|
5480
6658
|
async function startMetricsServer(opts) {
|
|
5481
6659
|
const { createServer } = await import('http');
|
|
5482
6660
|
const host = opts.host ?? "127.0.0.1";
|
|
5483
|
-
const
|
|
6661
|
+
const path15 = opts.path ?? "/metrics";
|
|
5484
6662
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
5485
6663
|
const healthRegistry = opts.healthRegistry;
|
|
5486
6664
|
const server = createServer((req, res) => {
|
|
@@ -5490,7 +6668,7 @@ async function startMetricsServer(opts) {
|
|
|
5490
6668
|
return;
|
|
5491
6669
|
}
|
|
5492
6670
|
const url = req.url.split("?")[0];
|
|
5493
|
-
if (url ===
|
|
6671
|
+
if (url === path15) {
|
|
5494
6672
|
let body;
|
|
5495
6673
|
try {
|
|
5496
6674
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -5541,7 +6719,7 @@ async function startMetricsServer(opts) {
|
|
|
5541
6719
|
const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
|
|
5542
6720
|
return {
|
|
5543
6721
|
port: boundPort,
|
|
5544
|
-
url: `http://${host}:${boundPort}${
|
|
6722
|
+
url: `http://${host}:${boundPort}${path15}`,
|
|
5545
6723
|
close: () => new Promise((resolve3, reject) => {
|
|
5546
6724
|
server.close((err) => err ? reject(err) : resolve3());
|
|
5547
6725
|
})
|
|
@@ -5946,7 +7124,6 @@ function createContextManagerTool(opts = {}) {
|
|
|
5946
7124
|
notes: `Invalid range [${from}, ${to}] for ${messages.length} messages.`
|
|
5947
7125
|
};
|
|
5948
7126
|
}
|
|
5949
|
-
messages.slice(from, to + 1);
|
|
5950
7127
|
const summaryText = input.text ?? '[summary placeholder \u2014 provide "text" to record the summary]';
|
|
5951
7128
|
const summaryMsg = {
|
|
5952
7129
|
role: "system",
|
|
@@ -6076,6 +7253,6 @@ var allServers = () => ({
|
|
|
6076
7253
|
sentinel: { ...sentinelServer(), enabled: false }
|
|
6077
7254
|
});
|
|
6078
7255
|
|
|
6079
|
-
export { AutoCompactionMiddleware, AutonomousRunner, BudgetExceededError, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, DefaultTokenCounter, DoneConditionChecker, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, QueueStore, RecoveryLock, SelectiveCompactor, SpecDrivenDev, SpecParser, SubagentBudget, TaskFlow, TaskGenerator, TaskTracker, ToolExecutor, allServers, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, context7Server, contextManagerTool, createContextManagerTool, createMessage, decryptConfigSecrets, encryptConfigSecrets, everArtServer, filesystemServer, githubServer, googleMapsServer, loadProjectModes, loadUserModes, makeAgentSubagentRunner, migratePlaintextSecrets, renderPrometheus, rewriteConfigEncrypted, runConfigMigrations, sentinelServer, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, wireMetricsToEvents };
|
|
7256
|
+
export { AutoCompactionMiddleware, AutonomousRunner, BudgetExceededError, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, DefaultTokenCounter, Director, DoneConditionChecker, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, QueueStore, RecoveryLock, SelectiveCompactor, SpecDrivenDev, SpecParser, SubagentBudget, TaskFlow, TaskGenerator, TaskTracker, ToolExecutor, allServers, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createContextManagerTool, createMessage, decryptConfigSecrets, encryptConfigSecrets, everArtServer, filesystemServer, githubServer, googleMapsServer, loadProjectModes, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, migratePlaintextSecrets, renderPrometheus, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, sentinelServer, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, wireMetricsToEvents };
|
|
6080
7257
|
//# sourceMappingURL=index.js.map
|
|
6081
7258
|
//# sourceMappingURL=index.js.map
|