@wrongstack/core 0.264.0 → 0.265.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-D8sa1vtv.d.ts → agent-bridge-DrkBxszZ.d.ts} +1 -1
- package/dist/{agent-subagent-runner-c9DLkaas.d.ts → agent-subagent-runner-DM2pP-B6.d.ts} +113 -11
- package/dist/{brain-O1IdKPaK.d.ts → brain-BXd_61kQ.d.ts} +31 -2
- package/dist/{compactor-BBy0rCtB.d.ts → compactor-B8pOf45Y.d.ts} +1 -1
- package/dist/{config-Dz2F3H2K.d.ts → config-BMCj_XDs.d.ts} +80 -12
- package/dist/{context-BGSpZNSE.d.ts → context-MRk5PhNv.d.ts} +26 -12
- package/dist/coordination/index.d.ts +77 -21
- package/dist/coordination/index.js +557 -159
- package/dist/coordination/index.js.map +1 -1
- package/dist/{default-config-CXsDvOmP.d.ts → default-config-B0cj-Hry.d.ts} +11 -1
- package/dist/defaults/index.d.ts +28 -28
- package/dist/defaults/index.js +609 -195
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +16 -16
- package/dist/execution/index.js +394 -155
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +2 -2
- package/dist/execution/prompt-enhancer.js +1 -1
- package/dist/execution/prompt-enhancer.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{goal-preamble-DzjFuN3p.d.ts → goal-preamble-DvHDSKSe.d.ts} +14 -10
- package/dist/{goal-store-CxWmCGbH.d.ts → goal-store-DtLMySNb.d.ts} +1 -1
- package/dist/{index-CYIQrXVF.d.ts → index-B-ch8K9C.d.ts} +8 -8
- package/dist/{index-CbLSI66_.d.ts → index-CEDeNodM.d.ts} +5 -5
- package/dist/index.d.ts +183 -52
- package/dist/index.js +1779 -673
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +12 -8
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js +1 -1
- package/dist/kernel/index.js.map +1 -1
- package/dist/{llm-selector-DzxuZnNz.d.ts → llm-selector-C0tfTCUe.d.ts} +14 -2
- package/dist/{mcp-servers-DC4QRPUI.d.ts → mcp-servers-2x4w6Jn9.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +74 -30
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-B_siPxqN.d.ts → models-registry-DmJlKuNp.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-CK5Jdj9K.d.ts → multi-agent-coordinator-DyCkCZnU.d.ts} +1 -1
- package/dist/{null-fleet-bus-DgvD4SCO.d.ts → null-fleet-bus-CG9QY2aP.d.ts} +6 -6
- package/dist/observability/index.d.ts +2 -2
- package/dist/{parallel-eternal-engine-bK0JQBR_.d.ts → parallel-eternal-engine-Jw9uhEoT.d.ts} +9 -9
- package/dist/{path-resolver-BPEDlN38.d.ts → path-resolver-Dy2ej-gE.d.ts} +3 -3
- package/dist/{permission-4yvGmMRB.d.ts → permission-B9SB45lp.d.ts} +1 -1
- package/dist/{permission-policy-C6XpsBOy.d.ts → permission-policy-CkjSXabK.d.ts} +2 -2
- package/dist/{pipeline-CXCeMz8J.d.ts → pipeline-DPDxH_7m.d.ts} +3 -3
- package/dist/{plan-templates-BvzRBkJc.d.ts → plan-templates-CzD9GnAU.d.ts} +32 -8
- package/dist/{provider-runner-C5aQpDWE.d.ts → provider-runner-DMa70ODu.d.ts} +3 -3
- package/dist/{retry-policy-CFhdtRzz.d.ts → retry-policy-CN0khdlj.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +274 -93
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-CxiVLbt1.d.ts → secret-vault-B2yw84VT.d.ts} +43 -4
- package/dist/secret-vault-BAKpgFw_.d.ts +57 -0
- package/dist/security/index.d.ts +5 -5
- package/dist/security/index.js +204 -23
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-gIuhRTkN.d.ts → selector-CzHh_igB.d.ts} +1 -1
- package/dist/{session-event-bridge-DkvvrpDt.d.ts → session-event-bridge-BUI6Jf-4.d.ts} +1 -1
- package/dist/{session-reader-KdfVwkKP.d.ts → session-reader-CMgdMSRP.d.ts} +1 -1
- package/dist/storage/index.d.ts +112 -15
- package/dist/storage/index.js +419 -81
- package/dist/storage/index.js.map +1 -1
- package/dist/tools/index.d.ts +2 -2
- package/dist/types/index.d.ts +21 -21
- package/dist/types/index.js +261 -53
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +3 -3
- package/dist/utils/index.js +3 -5
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-CJjEwPXn.d.ts → wstack-paths-hOpNLmvf.d.ts} +2 -0
- package/package.json +1 -1
- package/skills/api-design/SKILL.md +1 -1
- package/skills/audit-log/SKILL.md +6 -6
- package/skills/bug-hunter/SKILL.md +5 -5
- package/skills/chimera/SKILL.md +4 -4
- package/skills/docker-deploy/SKILL.md +1 -1
- package/skills/git-flow/SKILL.md +3 -3
- package/skills/multi-agent/SKILL.md +3 -3
- package/skills/node-modern/SKILL.md +1 -0
- package/skills/observability/SKILL.md +2 -2
- package/skills/output-standards/SKILL.md +51 -28
- package/skills/refactor-planner/SKILL.md +3 -3
- package/skills/security-scanner/SKILL.md +4 -3
- package/skills/tech-stack/SKILL.md +1 -2
- package/dist/secret-vault-BJDY28ev.d.ts +0 -25
package/dist/index.js
CHANGED
|
@@ -59,8 +59,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
59
59
|
}
|
|
60
60
|
let mode;
|
|
61
61
|
try {
|
|
62
|
-
const
|
|
63
|
-
mode =
|
|
62
|
+
const stat14 = await fsp3.stat(targetPath);
|
|
63
|
+
mode = stat14.mode & 511;
|
|
64
64
|
} catch {
|
|
65
65
|
mode = opts.mode;
|
|
66
66
|
}
|
|
@@ -100,8 +100,8 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
100
100
|
}
|
|
101
101
|
if (code !== "EEXIST") throw err;
|
|
102
102
|
try {
|
|
103
|
-
const
|
|
104
|
-
if (Date.now() -
|
|
103
|
+
const stat14 = await fsp3.stat(lockPath);
|
|
104
|
+
if (Date.now() - stat14.mtimeMs > staleMs) {
|
|
105
105
|
await fsp3.unlink(lockPath);
|
|
106
106
|
continue;
|
|
107
107
|
}
|
|
@@ -111,7 +111,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
111
111
|
if (Date.now() - started >= timeoutMs) {
|
|
112
112
|
throw new Error(`Timed out waiting for file lock: ${targetPath}`);
|
|
113
113
|
}
|
|
114
|
-
await new Promise((
|
|
114
|
+
await new Promise((resolve15) => setTimeout(resolve15, 25));
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
try {
|
|
@@ -144,7 +144,7 @@ async function renameWithRetry(from, to) {
|
|
|
144
144
|
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
145
145
|
throw err;
|
|
146
146
|
}
|
|
147
|
-
await new Promise((
|
|
147
|
+
await new Promise((resolve15) => setTimeout(resolve15, delays[i]));
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
throw lastErr;
|
|
@@ -183,16 +183,24 @@ function getSessionRegistry(globalRoot) {
|
|
|
183
183
|
function hasSessionRegistry() {
|
|
184
184
|
return _instance !== null;
|
|
185
185
|
}
|
|
186
|
-
var REGISTRY_FILE, HEARTBEAT_INTERVAL_MS, STALE_TIMEOUT_MS, SessionRegistry, _instance;
|
|
186
|
+
var REGISTRY_FILE, HEARTBEAT_INTERVAL_MS, STALE_TIMEOUT_MS, CLOSING_GRACE_MS, STALE_LOCK_MS, SessionRegistry, _instance;
|
|
187
187
|
var init_session_registry = __esm({
|
|
188
188
|
"src/session-registry.ts"() {
|
|
189
189
|
REGISTRY_FILE = "session-registry.json";
|
|
190
190
|
HEARTBEAT_INTERVAL_MS = 5e3;
|
|
191
191
|
STALE_TIMEOUT_MS = 3e4;
|
|
192
|
+
CLOSING_GRACE_MS = 15e3;
|
|
193
|
+
STALE_LOCK_MS = 1e4;
|
|
192
194
|
SessionRegistry = class {
|
|
193
195
|
filePath;
|
|
194
196
|
heartbeatTimer = null;
|
|
195
197
|
currentSessionId = null;
|
|
198
|
+
/**
|
|
199
|
+
* Last full entry this process registered. Kept so the heartbeat can
|
|
200
|
+
* re-create our entry if it ever goes missing — e.g. our initial register()
|
|
201
|
+
* write was dropped (a wedged lock), the file was reset, or we were pruned.
|
|
202
|
+
*/
|
|
203
|
+
lastEntry = null;
|
|
196
204
|
constructor(globalRoot) {
|
|
197
205
|
this.filePath = path2.join(globalRoot, REGISTRY_FILE);
|
|
198
206
|
}
|
|
@@ -214,6 +222,7 @@ var init_session_registry = __esm({
|
|
|
214
222
|
agentCount: entry.agents?.length ?? 0,
|
|
215
223
|
agents: entry.agents ?? []
|
|
216
224
|
};
|
|
225
|
+
this.lastEntry = full;
|
|
217
226
|
await this.atomicUpdate((registry) => {
|
|
218
227
|
const now = Date.now();
|
|
219
228
|
for (const [id, existing] of Object.entries(registry)) {
|
|
@@ -239,16 +248,28 @@ var init_session_registry = __esm({
|
|
|
239
248
|
*/
|
|
240
249
|
async updateAgents(agents) {
|
|
241
250
|
if (!this.currentSessionId) return;
|
|
251
|
+
const hasRunning = agents.some((a) => a.status === "running" || a.status === "streaming");
|
|
252
|
+
const hasWaiting = agents.some((a) => a.status === "waiting_user");
|
|
253
|
+
const hasError = agents.some((a) => a.status === "error");
|
|
254
|
+
const status = hasRunning || hasWaiting || hasError ? "active" : "idle";
|
|
255
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
256
|
+
if (this.lastEntry) {
|
|
257
|
+
this.lastEntry.agents = agents;
|
|
258
|
+
this.lastEntry.agentCount = agents.length;
|
|
259
|
+
this.lastEntry.status = status;
|
|
260
|
+
this.lastEntry.lastHeartbeatAt = nowIso;
|
|
261
|
+
}
|
|
242
262
|
await this.atomicUpdate((registry) => {
|
|
243
|
-
|
|
244
|
-
if (!entry)
|
|
263
|
+
let entry = registry[this.currentSessionId];
|
|
264
|
+
if (!entry) {
|
|
265
|
+
if (!this.lastEntry) return;
|
|
266
|
+
entry = { ...this.lastEntry };
|
|
267
|
+
registry[this.currentSessionId] = entry;
|
|
268
|
+
}
|
|
245
269
|
entry.agents = agents;
|
|
246
270
|
entry.agentCount = agents.length;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const hasError = agents.some((a) => a.status === "error");
|
|
250
|
-
entry.status = hasRunning ? "active" : hasWaiting ? "active" : hasError ? "active" : "idle";
|
|
251
|
-
entry.lastHeartbeatAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
271
|
+
entry.status = status;
|
|
272
|
+
entry.lastHeartbeatAt = nowIso;
|
|
252
273
|
});
|
|
253
274
|
}
|
|
254
275
|
/**
|
|
@@ -326,6 +347,12 @@ var init_session_registry = __esm({
|
|
|
326
347
|
entry.status = hasRunning ? "active" : "idle";
|
|
327
348
|
}
|
|
328
349
|
await this.writeAtomic(registry);
|
|
350
|
+
} else if (this.lastEntry) {
|
|
351
|
+
await this.atomicUpdate((reg) => {
|
|
352
|
+
if (!reg[this.currentSessionId] && this.lastEntry) {
|
|
353
|
+
reg[this.currentSessionId] = { ...this.lastEntry, lastHeartbeatAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
354
|
+
}
|
|
355
|
+
});
|
|
329
356
|
}
|
|
330
357
|
} catch {
|
|
331
358
|
}
|
|
@@ -338,6 +365,11 @@ var init_session_registry = __esm({
|
|
|
338
365
|
let pruned = false;
|
|
339
366
|
for (const [id, entry] of Object.entries(registry)) {
|
|
340
367
|
const heartbeatAge = now - new Date(entry.lastHeartbeatAt).getTime();
|
|
368
|
+
if (entry.status === "closing" && heartbeatAge > CLOSING_GRACE_MS) {
|
|
369
|
+
delete registry[id];
|
|
370
|
+
pruned = true;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
341
373
|
if (heartbeatAge > STALE_TIMEOUT_MS && !pidAlive(entry.pid)) {
|
|
342
374
|
entry.status = "stale";
|
|
343
375
|
const startedAge = now - new Date(entry.startedAt).getTime();
|
|
@@ -357,17 +389,23 @@ var init_session_registry = __esm({
|
|
|
357
389
|
}
|
|
358
390
|
async atomicUpdate(fn) {
|
|
359
391
|
const lockPath = `${this.filePath}.lock`;
|
|
360
|
-
const maxRetries =
|
|
392
|
+
const maxRetries = 8;
|
|
361
393
|
const retryDelayMs = 20;
|
|
362
394
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
363
395
|
try {
|
|
364
396
|
await fsp3.mkdir(path2.dirname(this.filePath), { recursive: true });
|
|
365
|
-
|
|
397
|
+
let lockHandle = await fsp3.open(lockPath, "wx").catch(() => null);
|
|
366
398
|
if (!lockHandle) {
|
|
367
|
-
|
|
368
|
-
|
|
399
|
+
if (await this.breakStaleLock(lockPath)) {
|
|
400
|
+
lockHandle = await fsp3.open(lockPath, "wx").catch(() => null);
|
|
401
|
+
}
|
|
402
|
+
if (!lockHandle) {
|
|
403
|
+
await new Promise((r) => setTimeout(r, retryDelayMs * (attempt + 1)));
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
369
406
|
}
|
|
370
407
|
try {
|
|
408
|
+
await lockHandle.writeFile(String(process.pid)).catch(() => void 0);
|
|
371
409
|
const raw = await fsp3.readFile(this.filePath, "utf8").catch(() => "{}");
|
|
372
410
|
const registry = JSON.parse(raw);
|
|
373
411
|
fn(registry);
|
|
@@ -382,6 +420,31 @@ var init_session_registry = __esm({
|
|
|
382
420
|
}
|
|
383
421
|
}
|
|
384
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* Break a contended lock if it is stale: the recorded owner pid is no longer
|
|
425
|
+
* alive, or the lock is older than {@link STALE_LOCK_MS}. Returns true when the
|
|
426
|
+
* lock was removed (caller should retry acquisition). Best-effort and
|
|
427
|
+
* race-tolerant — a fresh lock (age ~0, live owner) is never broken, so the
|
|
428
|
+
* common concurrent case self-heals on the next heartbeat.
|
|
429
|
+
*/
|
|
430
|
+
async breakStaleLock(lockPath) {
|
|
431
|
+
try {
|
|
432
|
+
const [stat14, content] = await Promise.all([
|
|
433
|
+
fsp3.stat(lockPath),
|
|
434
|
+
fsp3.readFile(lockPath, "utf8").catch(() => "")
|
|
435
|
+
]);
|
|
436
|
+
const ageMs = Date.now() - stat14.mtimeMs;
|
|
437
|
+
const ownerPid = Number.parseInt(content.trim(), 10);
|
|
438
|
+
const ownerDead = Number.isInteger(ownerPid) && ownerPid > 0 && ownerPid !== process.pid && !pidAlive(ownerPid);
|
|
439
|
+
if (ownerDead || ageMs > STALE_LOCK_MS) {
|
|
440
|
+
await fsp3.unlink(lockPath).catch(() => void 0);
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
return false;
|
|
444
|
+
} catch {
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
385
448
|
async writeAtomicLocked(registry) {
|
|
386
449
|
const tmp = `${this.filePath}.${randomUUID().slice(0, 8)}.tmp`;
|
|
387
450
|
await fsp3.writeFile(tmp, JSON.stringify(registry, null, 2), "utf8");
|
|
@@ -1061,7 +1124,8 @@ function resolveWstackPaths(opts) {
|
|
|
1061
1124
|
projectSddSession: path2.join(projectDir, "sdd-session.json"),
|
|
1062
1125
|
projectPlan: path2.join(projectDir, "plan.json"),
|
|
1063
1126
|
projectAutophase: path2.join(projectDir, "autophase"),
|
|
1064
|
-
syncConfig: path2.join(globalRoot, "sync.json")
|
|
1127
|
+
syncConfig: path2.join(globalRoot, "sync.json"),
|
|
1128
|
+
projectStatus: (projectHash2) => path2.join(globalRoot, "projects", projectHash2, "status.json")
|
|
1065
1129
|
};
|
|
1066
1130
|
}
|
|
1067
1131
|
|
|
@@ -1162,7 +1226,7 @@ function buildChildEnv(optsOrSessionId) {
|
|
|
1162
1226
|
|
|
1163
1227
|
// src/utils/sleep.ts
|
|
1164
1228
|
function sleep(ms) {
|
|
1165
|
-
return new Promise((
|
|
1229
|
+
return new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
1166
1230
|
}
|
|
1167
1231
|
|
|
1168
1232
|
// src/utils/assert-never.ts
|
|
@@ -1320,12 +1384,9 @@ function getCachedEstimate(key, compute) {
|
|
|
1320
1384
|
const existing = ESTIMATE_CACHE.get(key);
|
|
1321
1385
|
if (existing !== void 0) return existing;
|
|
1322
1386
|
if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
|
|
1323
|
-
let evicted = 0;
|
|
1324
|
-
const maxEvict = Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4);
|
|
1325
1387
|
for (const k of ESTIMATE_CACHE.keys()) {
|
|
1326
|
-
if (
|
|
1388
|
+
if (ESTIMATE_CACHE.size <= Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) break;
|
|
1327
1389
|
ESTIMATE_CACHE.delete(k);
|
|
1328
|
-
evicted++;
|
|
1329
1390
|
}
|
|
1330
1391
|
}
|
|
1331
1392
|
const estimate = compute(key);
|
|
@@ -1567,11 +1628,11 @@ function validateAgainstSchema(value, schema) {
|
|
|
1567
1628
|
walk(value, schema, "", errors);
|
|
1568
1629
|
return { ok: errors.length === 0, errors };
|
|
1569
1630
|
}
|
|
1570
|
-
function walk(value, schema,
|
|
1631
|
+
function walk(value, schema, path44, errors) {
|
|
1571
1632
|
if (schema.enum !== void 0) {
|
|
1572
1633
|
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
1573
1634
|
errors.push({
|
|
1574
|
-
path:
|
|
1635
|
+
path: path44 || "<root>",
|
|
1575
1636
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
1576
1637
|
});
|
|
1577
1638
|
return;
|
|
@@ -1580,7 +1641,7 @@ function walk(value, schema, path43, errors) {
|
|
|
1580
1641
|
if (typeof schema.type === "string") {
|
|
1581
1642
|
if (!checkType(value, schema.type)) {
|
|
1582
1643
|
errors.push({
|
|
1583
|
-
path:
|
|
1644
|
+
path: path44 || "<root>",
|
|
1584
1645
|
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
1585
1646
|
});
|
|
1586
1647
|
return;
|
|
@@ -1590,20 +1651,20 @@ function walk(value, schema, path43, errors) {
|
|
|
1590
1651
|
const obj = value;
|
|
1591
1652
|
for (const req of schema.required ?? []) {
|
|
1592
1653
|
if (!(req in obj)) {
|
|
1593
|
-
errors.push({ path: joinPath(
|
|
1654
|
+
errors.push({ path: joinPath(path44, req), message: "required property missing" });
|
|
1594
1655
|
}
|
|
1595
1656
|
}
|
|
1596
1657
|
if (schema.properties) {
|
|
1597
1658
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
1598
1659
|
if (key in obj) {
|
|
1599
|
-
walk(obj[key], subSchema, joinPath(
|
|
1660
|
+
walk(obj[key], subSchema, joinPath(path44, key), errors);
|
|
1600
1661
|
}
|
|
1601
1662
|
}
|
|
1602
1663
|
}
|
|
1603
1664
|
}
|
|
1604
1665
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
1605
1666
|
for (let i = 0; i < value.length; i++) {
|
|
1606
|
-
walk(value[i], schema.items, `${
|
|
1667
|
+
walk(value[i], schema.items, `${path44}[${i}]`, errors);
|
|
1607
1668
|
}
|
|
1608
1669
|
}
|
|
1609
1670
|
}
|
|
@@ -1779,8 +1840,8 @@ async function expandGlob(pattern) {
|
|
|
1779
1840
|
for (const e of entries) {
|
|
1780
1841
|
const full = `${dir}${SEP}${e}`;
|
|
1781
1842
|
try {
|
|
1782
|
-
const
|
|
1783
|
-
if (
|
|
1843
|
+
const stat14 = await fsp3.stat(full);
|
|
1844
|
+
if (stat14.isDirectory()) await walk3(full, rest);
|
|
1784
1845
|
} catch {
|
|
1785
1846
|
}
|
|
1786
1847
|
}
|
|
@@ -1797,8 +1858,8 @@ async function expandGlob(pattern) {
|
|
|
1797
1858
|
if (entries.includes(seg)) {
|
|
1798
1859
|
const full = `${dir}${SEP}${seg}`;
|
|
1799
1860
|
try {
|
|
1800
|
-
const
|
|
1801
|
-
if (
|
|
1861
|
+
const stat14 = await fsp3.stat(full);
|
|
1862
|
+
if (stat14.isDirectory()) await walk3(full, rest);
|
|
1802
1863
|
} catch {
|
|
1803
1864
|
}
|
|
1804
1865
|
}
|
|
@@ -2870,7 +2931,7 @@ function makePatternMatcher(pattern) {
|
|
|
2870
2931
|
}
|
|
2871
2932
|
|
|
2872
2933
|
// src/kernel/tokens.ts
|
|
2873
|
-
var t = (name) => Symbol(name);
|
|
2934
|
+
var t = (name) => /* @__PURE__ */ Symbol.for(`@wrongstack/core/kernel#${name}`);
|
|
2874
2935
|
var TOKENS = {
|
|
2875
2936
|
Logger: t("Logger"),
|
|
2876
2937
|
TokenCounter: t("TokenCounter"),
|
|
@@ -3109,6 +3170,20 @@ function providerStatusToCode(status, type) {
|
|
|
3109
3170
|
return ERROR_CODES.PROVIDER_INVALID_REQUEST;
|
|
3110
3171
|
}
|
|
3111
3172
|
|
|
3173
|
+
// src/types/config.ts
|
|
3174
|
+
function normalizeTokenSavingTier(val) {
|
|
3175
|
+
if (val === void 0) return "off";
|
|
3176
|
+
if (typeof val === "boolean") return val ? "medium" : "off";
|
|
3177
|
+
const validTiers = /* @__PURE__ */ new Set([
|
|
3178
|
+
"off",
|
|
3179
|
+
"minimal",
|
|
3180
|
+
"light",
|
|
3181
|
+
"medium",
|
|
3182
|
+
"aggressive"
|
|
3183
|
+
]);
|
|
3184
|
+
return validTiers.has(val) ? val : "off";
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3112
3187
|
// src/types/default-config.ts
|
|
3113
3188
|
var DEFAULT_TOOLS_CONFIG = Object.freeze({
|
|
3114
3189
|
defaultExecutionStrategy: "smart",
|
|
@@ -3116,7 +3191,8 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
|
|
|
3116
3191
|
iterationTimeoutMs: 3e5,
|
|
3117
3192
|
sessionTimeoutMs: 18e5,
|
|
3118
3193
|
perIterationOutputCapBytes: 1e5,
|
|
3119
|
-
autoExtendLimit: true
|
|
3194
|
+
autoExtendLimit: true,
|
|
3195
|
+
restrictToProjectRoot: false
|
|
3120
3196
|
});
|
|
3121
3197
|
var DEFAULT_CONTEXT_CONFIG = Object.freeze({
|
|
3122
3198
|
preserveK: 10,
|
|
@@ -3125,6 +3201,10 @@ var DEFAULT_CONTEXT_CONFIG = Object.freeze({
|
|
|
3125
3201
|
var DEFAULT_AUTONOMY_CONFIG = Object.freeze({
|
|
3126
3202
|
autoProceedDelayMs: 45e3
|
|
3127
3203
|
});
|
|
3204
|
+
var DEFAULT_CIRCUIT_BREAKER_CONFIG = Object.freeze({
|
|
3205
|
+
enabled: false,
|
|
3206
|
+
autoKillResetMs: 6e4
|
|
3207
|
+
});
|
|
3128
3208
|
var DEFAULT_SESSION_LOGGING_CONFIG = Object.freeze({
|
|
3129
3209
|
auditLevel: "standard",
|
|
3130
3210
|
sampling: {
|
|
@@ -3136,11 +3216,19 @@ var DEFAULT_SESSION_LOGGING_CONFIG = Object.freeze({
|
|
|
3136
3216
|
var DEFAULT_SESSION_PRUNE_DAYS = 30;
|
|
3137
3217
|
|
|
3138
3218
|
// src/types/secret-vault.ts
|
|
3139
|
-
var
|
|
3219
|
+
var ENCRYPTED_PREFIX_PATTERN = /^enc:v(\d+):/;
|
|
3220
|
+
function encryptedPrefixForVersion(version) {
|
|
3221
|
+
return `enc:v${version}:`;
|
|
3222
|
+
}
|
|
3223
|
+
function parseEncryptedVersion(value) {
|
|
3224
|
+
const match = value.match(ENCRYPTED_PREFIX_PATTERN);
|
|
3225
|
+
return match ? Number.parseInt(match[1], 10) : void 0;
|
|
3226
|
+
}
|
|
3140
3227
|
var noOpVault = {
|
|
3141
3228
|
encrypt: (v) => v,
|
|
3142
3229
|
decrypt: (v) => v,
|
|
3143
|
-
isEncrypted: () => false
|
|
3230
|
+
isEncrypted: () => false,
|
|
3231
|
+
keyVersion: 1
|
|
3144
3232
|
};
|
|
3145
3233
|
|
|
3146
3234
|
// src/security/secret-vault.ts
|
|
@@ -3150,11 +3238,13 @@ var IV_BYTES = 12;
|
|
|
3150
3238
|
var TAG_BYTES = 16;
|
|
3151
3239
|
var ALGO = "aes-256-gcm";
|
|
3152
3240
|
var KEY_FILE_MODE = 384;
|
|
3241
|
+
var KEY_FILE_MAGIC = Buffer.from("WSKV", "ascii");
|
|
3242
|
+
var VERSIONED_KEY_FILE_SIZE = KEY_FILE_MAGIC.length + 1 + KEY_BYTES;
|
|
3153
3243
|
function checkKeyFilePermissions(keyFile) {
|
|
3154
3244
|
if (process.platform === "win32") return;
|
|
3155
3245
|
try {
|
|
3156
|
-
const
|
|
3157
|
-
const actualMode =
|
|
3246
|
+
const stat14 = fs2.statSync(keyFile);
|
|
3247
|
+
const actualMode = stat14.mode & 511;
|
|
3158
3248
|
if (actualMode !== KEY_FILE_MODE) {
|
|
3159
3249
|
console.warn(JSON.stringify({
|
|
3160
3250
|
level: "warn",
|
|
@@ -3172,11 +3262,17 @@ function checkKeyFilePermissions(keyFile) {
|
|
|
3172
3262
|
var DefaultSecretVault = class {
|
|
3173
3263
|
keyFile;
|
|
3174
3264
|
key;
|
|
3265
|
+
_keyVersion = 1;
|
|
3175
3266
|
constructor(opts) {
|
|
3176
3267
|
this.keyFile = opts.keyFile;
|
|
3177
3268
|
}
|
|
3269
|
+
/** Current key version. Starts at 1; incremented by rotateKey(). */
|
|
3270
|
+
get keyVersion() {
|
|
3271
|
+
if (!this.key) this.loadOrCreateKey();
|
|
3272
|
+
return this._keyVersion;
|
|
3273
|
+
}
|
|
3178
3274
|
isEncrypted(value) {
|
|
3179
|
-
return typeof value === "string" &&
|
|
3275
|
+
return typeof value === "string" && ENCRYPTED_PREFIX_PATTERN.test(value);
|
|
3180
3276
|
}
|
|
3181
3277
|
encrypt(plaintext) {
|
|
3182
3278
|
if (this.isEncrypted(plaintext)) return plaintext;
|
|
@@ -3185,11 +3281,20 @@ var DefaultSecretVault = class {
|
|
|
3185
3281
|
const cipher = createCipheriv(ALGO, key, iv);
|
|
3186
3282
|
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
3187
3283
|
const tag = cipher.getAuthTag();
|
|
3188
|
-
|
|
3284
|
+
const prefix = encryptedPrefixForVersion(this._keyVersion);
|
|
3285
|
+
return `${prefix}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
|
|
3189
3286
|
}
|
|
3190
3287
|
decrypt(value) {
|
|
3191
3288
|
if (!this.isEncrypted(value)) return value;
|
|
3192
|
-
const
|
|
3289
|
+
const prefixMatch = value.match(ENCRYPTED_PREFIX_PATTERN);
|
|
3290
|
+
if (!prefixMatch) {
|
|
3291
|
+
throw new ConfigError({
|
|
3292
|
+
message: "SecretVault: malformed encrypted value",
|
|
3293
|
+
code: ERROR_CODES.CONFIG_PARSE_FAILED,
|
|
3294
|
+
context: { field: "encrypted_value" }
|
|
3295
|
+
});
|
|
3296
|
+
}
|
|
3297
|
+
const rest = value.slice(prefixMatch[0].length);
|
|
3193
3298
|
const parts = rest.split(":");
|
|
3194
3299
|
if (parts.length !== 3) {
|
|
3195
3300
|
throw new ConfigError({
|
|
@@ -3218,20 +3323,64 @@ var DefaultSecretVault = class {
|
|
|
3218
3323
|
const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
3219
3324
|
return pt.toString("utf8");
|
|
3220
3325
|
}
|
|
3326
|
+
/**
|
|
3327
|
+
* Generate a new encryption key, write it to disk, and increment the key version.
|
|
3328
|
+
* After rotation, encrypt() emits the new version prefix (e.g. enc:v2:).
|
|
3329
|
+
* The caller must re-encrypt existing config values (see rotateConfigKeys()).
|
|
3330
|
+
*/
|
|
3331
|
+
rotateKey() {
|
|
3332
|
+
const oldVersion = this._keyVersion;
|
|
3333
|
+
const newKey = randomBytes(KEY_BYTES);
|
|
3334
|
+
const newVersion = oldVersion + 1;
|
|
3335
|
+
const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
|
|
3336
|
+
KEY_FILE_MAGIC.copy(keyFileBuf, 0);
|
|
3337
|
+
keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
|
|
3338
|
+
newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
|
|
3339
|
+
fs2.mkdirSync(path2.dirname(this.keyFile), { recursive: true });
|
|
3340
|
+
fs2.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
|
|
3341
|
+
checkKeyFilePermissions(this.keyFile);
|
|
3342
|
+
this.key = newKey;
|
|
3343
|
+
this._keyVersion = newVersion;
|
|
3344
|
+
return { oldVersion, newVersion };
|
|
3345
|
+
}
|
|
3221
3346
|
loadOrCreateKey() {
|
|
3222
3347
|
if (this.key) return this.key;
|
|
3223
3348
|
try {
|
|
3224
3349
|
const buf = fs2.readFileSync(this.keyFile);
|
|
3225
|
-
if (buf.length
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3350
|
+
if (buf.length === KEY_BYTES) {
|
|
3351
|
+
this.key = buf;
|
|
3352
|
+
this._keyVersion = 1;
|
|
3353
|
+
checkKeyFilePermissions(this.keyFile);
|
|
3354
|
+
return this.key;
|
|
3355
|
+
}
|
|
3356
|
+
if (buf.length === VERSIONED_KEY_FILE_SIZE) {
|
|
3357
|
+
const magic = buf.subarray(0, KEY_FILE_MAGIC.length);
|
|
3358
|
+
if (!magic.equals(KEY_FILE_MAGIC)) {
|
|
3359
|
+
throw new ConfigError({
|
|
3360
|
+
message: `SecretVault: key file ${this.keyFile} has invalid magic header`,
|
|
3361
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
3362
|
+
context: { keyFile: this.keyFile }
|
|
3363
|
+
});
|
|
3364
|
+
}
|
|
3365
|
+
const version = buf[KEY_FILE_MAGIC.length];
|
|
3366
|
+
const key2 = buf.subarray(KEY_FILE_MAGIC.length + 1);
|
|
3367
|
+
if (key2.length !== KEY_BYTES) {
|
|
3368
|
+
throw new ConfigError({
|
|
3369
|
+
message: `SecretVault: key file ${this.keyFile} has wrong key size (${key2.length} bytes, expected ${KEY_BYTES})`,
|
|
3370
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
3371
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: key2.length }
|
|
3372
|
+
});
|
|
3373
|
+
}
|
|
3374
|
+
this.key = Buffer.from(key2);
|
|
3375
|
+
this._keyVersion = version;
|
|
3376
|
+
checkKeyFilePermissions(this.keyFile);
|
|
3377
|
+
return this.key;
|
|
3231
3378
|
}
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3379
|
+
throw new ConfigError({
|
|
3380
|
+
message: `SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES} for v1 or ${VERSIONED_KEY_FILE_SIZE} for v2+). Remove it manually to generate a new key.`,
|
|
3381
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
3382
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
|
|
3383
|
+
});
|
|
3235
3384
|
} catch (err) {
|
|
3236
3385
|
if (err.code !== "ENOENT") throw err;
|
|
3237
3386
|
}
|
|
@@ -3242,18 +3391,36 @@ var DefaultSecretVault = class {
|
|
|
3242
3391
|
} catch (err) {
|
|
3243
3392
|
if (err.code !== "EEXIST") throw err;
|
|
3244
3393
|
const buf = fs2.readFileSync(this.keyFile);
|
|
3245
|
-
if (buf.length
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3394
|
+
if (buf.length === KEY_BYTES) {
|
|
3395
|
+
this.key = buf;
|
|
3396
|
+
this._keyVersion = 1;
|
|
3397
|
+
checkKeyFilePermissions(this.keyFile);
|
|
3398
|
+
return this.key;
|
|
3399
|
+
}
|
|
3400
|
+
if (buf.length === VERSIONED_KEY_FILE_SIZE) {
|
|
3401
|
+
const magic = buf.subarray(0, KEY_FILE_MAGIC.length);
|
|
3402
|
+
if (!magic.equals(KEY_FILE_MAGIC)) {
|
|
3403
|
+
throw new ConfigError({
|
|
3404
|
+
message: `SecretVault: key file ${this.keyFile} has invalid magic header`,
|
|
3405
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
3406
|
+
context: { keyFile: this.keyFile }
|
|
3407
|
+
});
|
|
3408
|
+
}
|
|
3409
|
+
const version = buf[KEY_FILE_MAGIC.length];
|
|
3410
|
+
const winnerKey = buf.subarray(KEY_FILE_MAGIC.length + 1);
|
|
3411
|
+
this.key = Buffer.from(winnerKey);
|
|
3412
|
+
this._keyVersion = version;
|
|
3413
|
+
checkKeyFilePermissions(this.keyFile);
|
|
3414
|
+
return this.key;
|
|
3251
3415
|
}
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3416
|
+
throw new ConfigError({
|
|
3417
|
+
message: `SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES} for v1 or ${VERSIONED_KEY_FILE_SIZE} for v2+). Remove it manually to generate a new key.`,
|
|
3418
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
3419
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
|
|
3420
|
+
});
|
|
3255
3421
|
}
|
|
3256
3422
|
this.key = key;
|
|
3423
|
+
this._keyVersion = 1;
|
|
3257
3424
|
return key;
|
|
3258
3425
|
}
|
|
3259
3426
|
};
|
|
@@ -3334,6 +3501,80 @@ async function migratePlaintextSecrets(configPath, vault, logger) {
|
|
|
3334
3501
|
);
|
|
3335
3502
|
return { migrated: counter.n, file: configPath };
|
|
3336
3503
|
}
|
|
3504
|
+
async function rotateConfigKeys(configPath, vault, logger) {
|
|
3505
|
+
const log = logger?.info ?? (() => {
|
|
3506
|
+
});
|
|
3507
|
+
const warn = logger?.warn ?? ((msg) => console.warn(msg));
|
|
3508
|
+
let raw;
|
|
3509
|
+
try {
|
|
3510
|
+
raw = await fsp3.readFile(configPath, "utf8");
|
|
3511
|
+
} catch {
|
|
3512
|
+
const { oldVersion: oldVersion2, newVersion: newVersion2 } = vault.rotateKey();
|
|
3513
|
+
log(`[secret-vault] Key rotated (v${oldVersion2} \u2192 v${newVersion2}) \u2014 no config file to re-encrypt`);
|
|
3514
|
+
return { rotated: 0, oldVersion: oldVersion2, newVersion: newVersion2, file: configPath };
|
|
3515
|
+
}
|
|
3516
|
+
let parsed;
|
|
3517
|
+
try {
|
|
3518
|
+
parsed = JSON.parse(raw);
|
|
3519
|
+
} catch {
|
|
3520
|
+
warn(`[secret-vault] Config file ${configPath} is not valid JSON \u2014 skipping rotation`);
|
|
3521
|
+
return { rotated: 0, oldVersion: vault.keyVersion, newVersion: vault.keyVersion, file: configPath };
|
|
3522
|
+
}
|
|
3523
|
+
const counter = { n: 0 };
|
|
3524
|
+
const decrypted = walkDecryptCount(parsed, vault, counter);
|
|
3525
|
+
if (counter.n === 0) {
|
|
3526
|
+
const { oldVersion: oldVersion2, newVersion: newVersion2 } = vault.rotateKey();
|
|
3527
|
+
log(`[secret-vault] Key rotated (v${oldVersion2} \u2192 v${newVersion2}) \u2014 no encrypted fields to re-encrypt`);
|
|
3528
|
+
return { rotated: 0, oldVersion: oldVersion2, newVersion: newVersion2, file: configPath };
|
|
3529
|
+
}
|
|
3530
|
+
const { oldVersion, newVersion } = vault.rotateKey();
|
|
3531
|
+
const reencrypted = walkReencrypt(decrypted, vault);
|
|
3532
|
+
await atomicWrite(configPath, JSON.stringify(reencrypted, null, 2), { mode: 384 });
|
|
3533
|
+
await restrictFilePermissions(configPath, { warn });
|
|
3534
|
+
log(`[secret-vault] Key rotated (v${oldVersion} \u2192 v${newVersion}) \u2014 re-encrypted ${counter.n} field(s)`);
|
|
3535
|
+
return { rotated: counter.n, oldVersion, newVersion, file: configPath };
|
|
3536
|
+
}
|
|
3537
|
+
function walkDecryptCount(node, vault, counter) {
|
|
3538
|
+
if (node === null || node === void 0) return node;
|
|
3539
|
+
if (typeof node !== "object") return node;
|
|
3540
|
+
if (Array.isArray(node)) {
|
|
3541
|
+
return node.map((item) => walkDecryptCount(item, vault, counter));
|
|
3542
|
+
}
|
|
3543
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
3544
|
+
for (const [k, v] of Object.entries(node)) {
|
|
3545
|
+
if (typeof v === "string" && vault.isEncrypted(v)) {
|
|
3546
|
+
try {
|
|
3547
|
+
out[k] = vault.decrypt(v);
|
|
3548
|
+
counter.n++;
|
|
3549
|
+
} catch {
|
|
3550
|
+
out[k] = v;
|
|
3551
|
+
}
|
|
3552
|
+
} else if (typeof v === "object" && v !== null) {
|
|
3553
|
+
out[k] = walkDecryptCount(v, vault, counter);
|
|
3554
|
+
} else {
|
|
3555
|
+
out[k] = v;
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
return out;
|
|
3559
|
+
}
|
|
3560
|
+
function walkReencrypt(node, vault) {
|
|
3561
|
+
if (node === null || node === void 0) return node;
|
|
3562
|
+
if (typeof node !== "object") return node;
|
|
3563
|
+
if (Array.isArray(node)) {
|
|
3564
|
+
return node.map((item) => walkReencrypt(item, vault));
|
|
3565
|
+
}
|
|
3566
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
3567
|
+
for (const [k, v] of Object.entries(node)) {
|
|
3568
|
+
if (typeof v === "string" && isSecretField(k) && v.length > 0 && !vault.isEncrypted(v)) {
|
|
3569
|
+
out[k] = vault.encrypt(v);
|
|
3570
|
+
} else if (typeof v === "object" && v !== null) {
|
|
3571
|
+
out[k] = walkReencrypt(v, vault);
|
|
3572
|
+
} else {
|
|
3573
|
+
out[k] = v;
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
return out;
|
|
3577
|
+
}
|
|
3337
3578
|
async function restrictFilePermissions(filePath, opts) {
|
|
3338
3579
|
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
3339
3580
|
if (process.platform === "win32") {
|
|
@@ -3684,7 +3925,11 @@ var MEMORY_TYPE_LABELS = {
|
|
|
3684
3925
|
};
|
|
3685
3926
|
|
|
3686
3927
|
// src/execution/compaction-core.ts
|
|
3928
|
+
function compactionDebugEnabled() {
|
|
3929
|
+
return process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1";
|
|
3930
|
+
}
|
|
3687
3931
|
function emitCompactionMetrics(event, metrics) {
|
|
3932
|
+
if (!compactionDebugEnabled()) return;
|
|
3688
3933
|
console.log(
|
|
3689
3934
|
JSON.stringify({
|
|
3690
3935
|
level: "debug",
|
|
@@ -3739,18 +3984,20 @@ function findPreserveStart(messages, preserveK) {
|
|
|
3739
3984
|
}
|
|
3740
3985
|
}
|
|
3741
3986
|
}
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3987
|
+
if (compactionDebugEnabled()) {
|
|
3988
|
+
console.log(
|
|
3989
|
+
JSON.stringify({
|
|
3990
|
+
level: "debug",
|
|
3991
|
+
event: "compaction.find_preserve_start.ended",
|
|
3992
|
+
messageCount: messages.length,
|
|
3993
|
+
preserveK,
|
|
3994
|
+
preserveStart,
|
|
3995
|
+
forwardWalkIterations,
|
|
3996
|
+
forwardWalkInnerIterations,
|
|
3997
|
+
forwardWalkInnerPerOuter: forwardWalkIterations > 0 ? forwardWalkInnerIterations / forwardWalkIterations : 0
|
|
3998
|
+
})
|
|
3999
|
+
);
|
|
4000
|
+
}
|
|
3754
4001
|
return preserveStart;
|
|
3755
4002
|
}
|
|
3756
4003
|
function eliseOldToolResults(messages, opts) {
|
|
@@ -3817,7 +4064,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
3817
4064
|
changed = true;
|
|
3818
4065
|
}
|
|
3819
4066
|
fullPassInnerIterations += original.length;
|
|
3820
|
-
if (
|
|
4067
|
+
if (compactionDebugEnabled()) {
|
|
3821
4068
|
const ratio = fullPassInnerIterations / fullPassIterations;
|
|
3822
4069
|
if (ratio > 10) {
|
|
3823
4070
|
console.error(
|
|
@@ -4374,6 +4621,27 @@ var PATTERNS = [
|
|
|
4374
4621
|
{ type: "postgres_uri", regex: /postgres(?:ql)?:\/\/[^\s"'`]+/g },
|
|
4375
4622
|
{ type: "mysql_uri", regex: /mysql:\/\/[^\s"'`]+/g },
|
|
4376
4623
|
{ type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g },
|
|
4624
|
+
// AI/ML provider keys — modern LLM services with well-known prefixes
|
|
4625
|
+
{
|
|
4626
|
+
type: "huggingface_token",
|
|
4627
|
+
// HuggingFace tokens: hf_ followed by 34 alphanumeric chars
|
|
4628
|
+
regex: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34}(?![A-Za-z0-9])/g
|
|
4629
|
+
},
|
|
4630
|
+
{
|
|
4631
|
+
type: "replicate_token",
|
|
4632
|
+
// Replicate tokens: r8_ followed by 40+ alphanumeric chars
|
|
4633
|
+
regex: /(?<![A-Za-z0-9])r8_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
|
|
4634
|
+
},
|
|
4635
|
+
{
|
|
4636
|
+
type: "perplexity_key",
|
|
4637
|
+
// Perplexity API keys: pplx- followed by 40+ alphanumeric chars
|
|
4638
|
+
regex: /(?<![A-Za-z0-9])pplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
|
|
4639
|
+
},
|
|
4640
|
+
{
|
|
4641
|
+
type: "groq_key",
|
|
4642
|
+
// Groq API keys: gsk_ followed by 40+ alphanumeric chars
|
|
4643
|
+
regex: /(?<![A-Za-z0-9])gsk_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
|
|
4644
|
+
},
|
|
4377
4645
|
{
|
|
4378
4646
|
type: "bearer_token",
|
|
4379
4647
|
// Anchored with alternation instead of negative lookahead — avoids V8
|
|
@@ -4407,6 +4675,10 @@ function hasCredentialAnchors(text) {
|
|
|
4407
4675
|
text.includes("xox") || // Slack token (xoxa/xoxb/xoxp/xoxo/xoxs)
|
|
4408
4676
|
text.includes("Bearer ") || // Bearer token (space suffix reduces false positives)
|
|
4409
4677
|
text.includes("/bot") || // Telegram bot token (URL path pattern)
|
|
4678
|
+
text.includes("hf_") || // HuggingFace token
|
|
4679
|
+
text.includes("r8_") || // Replicate token
|
|
4680
|
+
text.includes("pplx-") || // Perplexity API key
|
|
4681
|
+
text.includes("gsk_") || // Groq API key
|
|
4410
4682
|
text.includes("_KEY=") || // High-entropy env vars: API_KEY=, SECRET_KEY=, ...
|
|
4411
4683
|
text.includes("_TOKEN=") || // ACCESS_TOKEN=, AUTH_TOKEN=, ...
|
|
4412
4684
|
text.includes("_SECRET=") || // API_SECRET=, CLIENT_SECRET=, ...
|
|
@@ -5243,7 +5515,7 @@ var InMemoryAgentBridge = class {
|
|
|
5243
5515
|
});
|
|
5244
5516
|
}
|
|
5245
5517
|
this.inflightGuards.add(correlationId);
|
|
5246
|
-
return new Promise((
|
|
5518
|
+
return new Promise((resolve15, reject) => {
|
|
5247
5519
|
const timer = setTimeout(() => {
|
|
5248
5520
|
this.inflightGuards.delete(correlationId);
|
|
5249
5521
|
this.pendingRequests.delete(correlationId);
|
|
@@ -5262,7 +5534,7 @@ var InMemoryAgentBridge = class {
|
|
|
5262
5534
|
return;
|
|
5263
5535
|
}
|
|
5264
5536
|
this.pendingRequests.set(correlationId, {
|
|
5265
|
-
resolve:
|
|
5537
|
+
resolve: resolve15,
|
|
5266
5538
|
reject,
|
|
5267
5539
|
timer
|
|
5268
5540
|
});
|
|
@@ -6062,6 +6334,13 @@ var Context = class {
|
|
|
6062
6334
|
projectRoot;
|
|
6063
6335
|
/** Mutable working directory — starts as `cwd`. Change via `setWorkingDir()`. */
|
|
6064
6336
|
workingDir;
|
|
6337
|
+
/**
|
|
6338
|
+
* When true, file tools (via `_util.ts`) and `setWorkingDir()` reject paths
|
|
6339
|
+
* outside `projectRoot`. When false, those boundary checks are bypassed so
|
|
6340
|
+
* tools may reach paths outside the project (still gated by permission
|
|
6341
|
+
* tiers). Mutable so `/settings` can toggle it live on the running session.
|
|
6342
|
+
*/
|
|
6343
|
+
allowOutsideProjectRoot;
|
|
6065
6344
|
model;
|
|
6066
6345
|
tools = [];
|
|
6067
6346
|
meta = {};
|
|
@@ -6075,11 +6354,6 @@ var Context = class {
|
|
|
6075
6354
|
* so storage operations can include it in `storage.*` events.
|
|
6076
6355
|
*/
|
|
6077
6356
|
traceId;
|
|
6078
|
-
/**
|
|
6079
|
-
* When true, tools can access any path on the filesystem.
|
|
6080
|
-
* When false or undefined, tools are restricted to the project root.
|
|
6081
|
-
*/
|
|
6082
|
-
allowOutsideProjectRoot;
|
|
6083
6357
|
/** Callbacks fired when `setWorkingDir()` changes the working directory. */
|
|
6084
6358
|
_onWorkingDirChanged = [];
|
|
6085
6359
|
/**
|
|
@@ -6111,12 +6385,13 @@ var Context = class {
|
|
|
6111
6385
|
this.cwd = init.cwd;
|
|
6112
6386
|
this.projectRoot = init.projectRoot;
|
|
6113
6387
|
this.workingDir = init.workingDir ?? init.cwd;
|
|
6388
|
+
this.allowOutsideProjectRoot = init.allowOutsideProjectRoot ?? false;
|
|
6114
6389
|
this.model = init.model;
|
|
6115
6390
|
this.tools = init.tools ?? [];
|
|
6116
6391
|
this.agentId = init.agentId ?? "unknown";
|
|
6117
6392
|
this.agentName = init.agentName ?? "Unknown Agent";
|
|
6118
6393
|
this.traceId = init.traceId;
|
|
6119
|
-
this.allowOutsideProjectRoot = init.allowOutsideProjectRoot ??
|
|
6394
|
+
this.allowOutsideProjectRoot = init.allowOutsideProjectRoot ?? false;
|
|
6120
6395
|
this.session.traceId = init.traceId;
|
|
6121
6396
|
}
|
|
6122
6397
|
/**
|
|
@@ -6182,12 +6457,14 @@ var Context = class {
|
|
|
6182
6457
|
*/
|
|
6183
6458
|
setWorkingDir(dir) {
|
|
6184
6459
|
const resolved = path2.isAbsolute(dir) ? path2.resolve(dir) : path2.resolve(this.projectRoot, dir);
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6460
|
+
if (!this.allowOutsideProjectRoot) {
|
|
6461
|
+
const root = path2.resolve(this.projectRoot);
|
|
6462
|
+
const rel = path2.relative(root, resolved);
|
|
6463
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel)) {
|
|
6464
|
+
throw new Error(
|
|
6465
|
+
`Working directory "${resolved}" is outside project root "${root}"`
|
|
6466
|
+
);
|
|
6467
|
+
}
|
|
6191
6468
|
}
|
|
6192
6469
|
const old = this.workingDir;
|
|
6193
6470
|
this.workingDir = resolved;
|
|
@@ -6509,11 +6786,34 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
6509
6786
|
dir;
|
|
6510
6787
|
events;
|
|
6511
6788
|
secretScrubber;
|
|
6789
|
+
/**
|
|
6790
|
+
* In-memory cache for load() results, keyed by session ID. The cache is
|
|
6791
|
+
* invalidated when the file's mtimeMs or size changes (indicating the
|
|
6792
|
+
* file was written to). This eliminates redundant full-file reads and
|
|
6793
|
+
* JSON parses when the same session is loaded multiple times within the
|
|
6794
|
+
* store's lifetime (e.g., webui session detail views, list() fallbacks).
|
|
6795
|
+
*
|
|
6796
|
+
* Max size is capped to prevent unbounded memory growth in long-running
|
|
6797
|
+
* processes. When the limit is reached, the oldest entry is evicted.
|
|
6798
|
+
*/
|
|
6799
|
+
_loadCache = /* @__PURE__ */ new Map();
|
|
6800
|
+
static LOAD_CACHE_MAX_ENTRIES = 50;
|
|
6512
6801
|
constructor(opts) {
|
|
6513
6802
|
this.dir = opts.dir;
|
|
6514
6803
|
this.events = opts.events;
|
|
6515
6804
|
this.secretScrubber = opts.secretScrubber;
|
|
6516
6805
|
}
|
|
6806
|
+
/**
|
|
6807
|
+
* Clear the load() cache. Useful for testing or when the caller knows
|
|
6808
|
+
* the file has changed externally (e.g., another process wrote to it).
|
|
6809
|
+
*/
|
|
6810
|
+
clearLoadCache(sessionId) {
|
|
6811
|
+
if (sessionId !== void 0) {
|
|
6812
|
+
this._loadCache.delete(sessionId);
|
|
6813
|
+
} else {
|
|
6814
|
+
this._loadCache.clear();
|
|
6815
|
+
}
|
|
6816
|
+
}
|
|
6517
6817
|
// ── Storage event helpers ───────────────────────────────────────────────────
|
|
6518
6818
|
emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
|
|
6519
6819
|
this.events?.emit("storage.read", {
|
|
@@ -6656,7 +6956,20 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
6656
6956
|
const t0 = Date.now();
|
|
6657
6957
|
let outcome = "success";
|
|
6658
6958
|
let errorMsg;
|
|
6959
|
+
let cacheHit = false;
|
|
6659
6960
|
try {
|
|
6961
|
+
let stat14;
|
|
6962
|
+
try {
|
|
6963
|
+
const s = await fsp3.stat(file);
|
|
6964
|
+
stat14 = { mtimeMs: s.mtimeMs, size: s.size };
|
|
6965
|
+
} catch (err) {
|
|
6966
|
+
throw err;
|
|
6967
|
+
}
|
|
6968
|
+
const cached = this._loadCache.get(id);
|
|
6969
|
+
if (cached && cached.mtimeMs === stat14.mtimeMs && cached.size === stat14.size) {
|
|
6970
|
+
cacheHit = true;
|
|
6971
|
+
return cached.data;
|
|
6972
|
+
}
|
|
6660
6973
|
const raw = await fsp3.readFile(file, "utf8");
|
|
6661
6974
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
6662
6975
|
const events = [];
|
|
@@ -6672,13 +6985,30 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
6672
6985
|
const meta = this.metaFromEvents(id, events);
|
|
6673
6986
|
const { messages, usage } = this.replay(events, id);
|
|
6674
6987
|
const toolCallEnds = extractToolCallEnds(events);
|
|
6675
|
-
|
|
6988
|
+
const data = { metadata: meta, events, messages, usage, toolCallEnds };
|
|
6989
|
+
if (this._loadCache.size >= _DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {
|
|
6990
|
+
const oldest = this._loadCache.keys().next().value;
|
|
6991
|
+
if (oldest !== void 0) {
|
|
6992
|
+
this._loadCache.delete(oldest);
|
|
6993
|
+
}
|
|
6994
|
+
}
|
|
6995
|
+
this._loadCache.set(id, { mtimeMs: stat14.mtimeMs, size: stat14.size, data });
|
|
6996
|
+
return data;
|
|
6676
6997
|
} catch (err) {
|
|
6677
6998
|
outcome = "failure";
|
|
6678
6999
|
errorMsg = toErrorMessage(err);
|
|
6679
7000
|
throw err;
|
|
6680
7001
|
} finally {
|
|
6681
7002
|
this.emitRead(id, file, "load", outcome, Date.now() - t0, errorMsg);
|
|
7003
|
+
if (cacheHit) {
|
|
7004
|
+
this.events?.emit("storage.cache_hit", {
|
|
7005
|
+
sessionId: id,
|
|
7006
|
+
store: "session",
|
|
7007
|
+
filePath: file,
|
|
7008
|
+
operation: "load",
|
|
7009
|
+
durationMs: Date.now() - t0
|
|
7010
|
+
});
|
|
7011
|
+
}
|
|
6682
7012
|
}
|
|
6683
7013
|
}
|
|
6684
7014
|
async list(limit = 20) {
|
|
@@ -6846,8 +7176,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
6846
7176
|
return JSON.parse(raw);
|
|
6847
7177
|
} catch {
|
|
6848
7178
|
const full = this.sessionPath(id, ".jsonl");
|
|
6849
|
-
const
|
|
6850
|
-
const summary = await this.summarize(id,
|
|
7179
|
+
const stat14 = await fsp3.stat(full);
|
|
7180
|
+
const summary = await this.summarize(id, stat14.mtime.toISOString());
|
|
6851
7181
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
6852
7182
|
const msg = toErrorMessage(err);
|
|
6853
7183
|
this.emitError(id, manifest, "summary_fallback", msg, true);
|
|
@@ -6929,8 +7259,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
6929
7259
|
const pruneFile = async (dir, name, prefix) => {
|
|
6930
7260
|
const jsonlPath = path2.join(dir, name);
|
|
6931
7261
|
try {
|
|
6932
|
-
const
|
|
6933
|
-
if (
|
|
7262
|
+
const stat14 = await fsp3.stat(jsonlPath);
|
|
7263
|
+
if (stat14.mtimeMs >= cutoff) return;
|
|
6934
7264
|
} catch {
|
|
6935
7265
|
return;
|
|
6936
7266
|
}
|
|
@@ -8754,7 +9084,8 @@ var BEHAVIOR_DEFAULTS = {
|
|
|
8754
9084
|
iterationTimeoutMs: DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,
|
|
8755
9085
|
sessionTimeoutMs: DEFAULT_TOOLS_CONFIG.sessionTimeoutMs,
|
|
8756
9086
|
perIterationOutputCapBytes: DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,
|
|
8757
|
-
autoExtendLimit: DEFAULT_TOOLS_CONFIG.autoExtendLimit
|
|
9087
|
+
autoExtendLimit: DEFAULT_TOOLS_CONFIG.autoExtendLimit,
|
|
9088
|
+
restrictToProjectRoot: DEFAULT_TOOLS_CONFIG.restrictToProjectRoot
|
|
8758
9089
|
},
|
|
8759
9090
|
log: { level: "info" },
|
|
8760
9091
|
features: {
|
|
@@ -9747,6 +10078,7 @@ async function savePlan(filePath, plan, events) {
|
|
|
9747
10078
|
outcome: "success",
|
|
9748
10079
|
durationMs: Date.now() - t0
|
|
9749
10080
|
});
|
|
10081
|
+
return true;
|
|
9750
10082
|
} catch (err) {
|
|
9751
10083
|
events?.emit("storage.error", {
|
|
9752
10084
|
sessionId: "~boot~",
|
|
@@ -9760,6 +10092,7 @@ async function savePlan(filePath, plan, events) {
|
|
|
9760
10092
|
"[plan-store] save failed:",
|
|
9761
10093
|
toErrorMessage(err)
|
|
9762
10094
|
);
|
|
10095
|
+
return false;
|
|
9763
10096
|
}
|
|
9764
10097
|
}
|
|
9765
10098
|
function emptyPlan(sessionId, title) {
|
|
@@ -9861,7 +10194,10 @@ async function mutatePlan(filePath, sessionId, fn) {
|
|
|
9861
10194
|
return withFileLock(filePath, async () => {
|
|
9862
10195
|
const plan = await loadPlan(filePath) ?? emptyPlan(sessionId);
|
|
9863
10196
|
const updated = await fn(plan);
|
|
9864
|
-
await savePlan(filePath, updated);
|
|
10197
|
+
const persisted = await savePlan(filePath, updated);
|
|
10198
|
+
if (!persisted) {
|
|
10199
|
+
throw new Error(`Failed to persist plan to ${filePath} \u2014 the change was NOT saved.`);
|
|
10200
|
+
}
|
|
9865
10201
|
return updated;
|
|
9866
10202
|
});
|
|
9867
10203
|
}
|
|
@@ -10691,12 +11027,12 @@ var DefaultSkillLoader = class {
|
|
|
10691
11027
|
}
|
|
10692
11028
|
async find(name) {
|
|
10693
11029
|
const all = await this.list();
|
|
10694
|
-
|
|
11030
|
+
const lower = name.toLowerCase();
|
|
11031
|
+
return all.find((s) => s.name.toLowerCase() === lower);
|
|
10695
11032
|
}
|
|
10696
11033
|
async manifestText() {
|
|
10697
|
-
const skills = await this.list();
|
|
10698
|
-
if (skills.length === 0) return "";
|
|
10699
11034
|
const entries = await this.listEntries();
|
|
11035
|
+
if (entries.length === 0) return "";
|
|
10700
11036
|
const lines = ["## Available skills"];
|
|
10701
11037
|
for (const e of entries) {
|
|
10702
11038
|
const scopeTag = e.scope.length > 0 ? ` \u2014 ${e.scope.slice(0, 3).join(", ")}` : "";
|
|
@@ -10710,12 +11046,8 @@ var DefaultSkillLoader = class {
|
|
|
10710
11046
|
const skills = await this.list();
|
|
10711
11047
|
const entries = [];
|
|
10712
11048
|
for (const s of skills) {
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
const { trigger, scope } = parseDescription(raw);
|
|
10716
|
-
entries.push({ name: s.name, trigger, scope, source: s.source, path: s.path });
|
|
10717
|
-
} catch {
|
|
10718
|
-
}
|
|
11049
|
+
const { trigger, scope } = parseDescriptionFromText(s.description ?? "");
|
|
11050
|
+
entries.push({ name: s.name, trigger, scope, source: s.source, path: s.path });
|
|
10719
11051
|
}
|
|
10720
11052
|
this.entriesCache = entries;
|
|
10721
11053
|
return entries;
|
|
@@ -10726,16 +11058,17 @@ var DefaultSkillLoader = class {
|
|
|
10726
11058
|
this.bodyCache.clear();
|
|
10727
11059
|
}
|
|
10728
11060
|
async readBody(name) {
|
|
10729
|
-
const
|
|
11061
|
+
const key = name.toLowerCase();
|
|
11062
|
+
const cached = this.bodyCache.get(key);
|
|
10730
11063
|
if (cached !== void 0) return cached;
|
|
10731
11064
|
const m = await this.find(name);
|
|
10732
11065
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
10733
11066
|
const body = await fsp3.readFile(m.path, "utf8");
|
|
10734
|
-
this.bodyCache.set(
|
|
11067
|
+
this.bodyCache.set(key, body);
|
|
10735
11068
|
return body;
|
|
10736
11069
|
}
|
|
10737
11070
|
async readSaveBody(name) {
|
|
10738
|
-
const key = `save:${name}`;
|
|
11071
|
+
const key = `save:${name.toLowerCase()}`;
|
|
10739
11072
|
const cached = this.bodyCache.get(key);
|
|
10740
11073
|
if (cached !== void 0) return cached;
|
|
10741
11074
|
const m = await this.find(name);
|
|
@@ -10796,9 +11129,7 @@ function parseFrontmatter(raw) {
|
|
|
10796
11129
|
flush();
|
|
10797
11130
|
return out;
|
|
10798
11131
|
}
|
|
10799
|
-
function
|
|
10800
|
-
const fm = parseFrontmatter(raw);
|
|
10801
|
-
const desc = fm.description ?? "";
|
|
11132
|
+
function parseDescriptionFromText(desc) {
|
|
10802
11133
|
const firstSentenceEnd = desc.indexOf(". ");
|
|
10803
11134
|
const trigger = firstSentenceEnd !== -1 ? desc.slice(0, firstSentenceEnd + 1).trim() : desc.trim().split("\n")[0] ?? "";
|
|
10804
11135
|
const scope = [];
|
|
@@ -11056,8 +11387,8 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
11056
11387
|
});
|
|
11057
11388
|
await Promise.race([
|
|
11058
11389
|
drainPromise,
|
|
11059
|
-
new Promise((
|
|
11060
|
-
drainTimer = setTimeout(
|
|
11390
|
+
new Promise((resolve15) => {
|
|
11391
|
+
drainTimer = setTimeout(resolve15, STREAM_DRAIN_TIMEOUT_MS);
|
|
11061
11392
|
})
|
|
11062
11393
|
]);
|
|
11063
11394
|
} finally {
|
|
@@ -11163,7 +11494,7 @@ async function runProviderWithRetry(opts) {
|
|
|
11163
11494
|
description
|
|
11164
11495
|
});
|
|
11165
11496
|
}
|
|
11166
|
-
await new Promise((
|
|
11497
|
+
await new Promise((resolve15, reject) => {
|
|
11167
11498
|
let settled = false;
|
|
11168
11499
|
const onAbort = () => {
|
|
11169
11500
|
if (settled) return;
|
|
@@ -11176,7 +11507,7 @@ async function runProviderWithRetry(opts) {
|
|
|
11176
11507
|
settled = true;
|
|
11177
11508
|
clearTimeout(t2);
|
|
11178
11509
|
signal.removeEventListener("abort", onAbort);
|
|
11179
|
-
|
|
11510
|
+
resolve15();
|
|
11180
11511
|
}, delay);
|
|
11181
11512
|
if (signal.aborted) {
|
|
11182
11513
|
onAbort();
|
|
@@ -11306,7 +11637,12 @@ var IntelligentCompactor = class {
|
|
|
11306
11637
|
};
|
|
11307
11638
|
const ac = ctx.signal ? void 0 : new AbortController();
|
|
11308
11639
|
const signal = ctx.signal ?? ac?.signal;
|
|
11309
|
-
|
|
11640
|
+
let res;
|
|
11641
|
+
try {
|
|
11642
|
+
res = await this.provider.complete(req, { signal });
|
|
11643
|
+
} finally {
|
|
11644
|
+
ac?.abort();
|
|
11645
|
+
}
|
|
11310
11646
|
const textBlocks = res.content.filter(isTextBlock);
|
|
11311
11647
|
return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
|
|
11312
11648
|
}
|
|
@@ -11350,9 +11686,9 @@ Rules:
|
|
|
11350
11686
|
- If unsure, keep rather than collapse (errors are more costly than waste)
|
|
11351
11687
|
|
|
11352
11688
|
Return ONLY the JSON object, no markdown, no explanation outside the JSON.`;
|
|
11353
|
-
function formatMessages(messages,
|
|
11689
|
+
function formatMessages(messages, maxTokens = 2048) {
|
|
11354
11690
|
const lines = [];
|
|
11355
|
-
let
|
|
11691
|
+
let usedTokens = 0;
|
|
11356
11692
|
for (let i = 0; i < messages.length; i++) {
|
|
11357
11693
|
const m = expectDefined(messages[i]);
|
|
11358
11694
|
const role = m.role.padEnd(10, " ");
|
|
@@ -11364,13 +11700,14 @@ function formatMessages(messages, maxChars = 8e3) {
|
|
|
11364
11700
|
text = content.filter(isTextBlock).map((b) => b.text).join(" ");
|
|
11365
11701
|
const toolUses = content.filter((b) => b.type === "tool_use");
|
|
11366
11702
|
if (toolUses.length > 0) {
|
|
11367
|
-
text += ` [tools: ${toolUses.map((b) => b.name).join(", ")}]`;
|
|
11703
|
+
text += ` [tools: ${toolUses.map((b) => b.name).filter(Boolean).join(", ")}]`;
|
|
11368
11704
|
}
|
|
11369
11705
|
}
|
|
11370
11706
|
const line = `[${i}][${role}]: ${text}`;
|
|
11371
|
-
|
|
11707
|
+
const lineTokens = estimateTextTokens(line);
|
|
11708
|
+
if (usedTokens + lineTokens > maxTokens) break;
|
|
11372
11709
|
lines.push(line);
|
|
11373
|
-
|
|
11710
|
+
usedTokens += lineTokens;
|
|
11374
11711
|
}
|
|
11375
11712
|
return lines.join("\n");
|
|
11376
11713
|
}
|
|
@@ -11379,20 +11716,29 @@ var LLMSelector = class {
|
|
|
11379
11716
|
model;
|
|
11380
11717
|
maxContextTokens;
|
|
11381
11718
|
systemPrompt;
|
|
11719
|
+
maxOutputTokens;
|
|
11382
11720
|
constructor(opts) {
|
|
11383
11721
|
this.provider = opts.provider;
|
|
11384
11722
|
this.model = opts.model ?? "unknown";
|
|
11723
|
+
if (this.model === "unknown" && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
|
|
11724
|
+
console.warn(
|
|
11725
|
+
"[LLMSelector] model not set \u2014 selector will use the provider default. Set `model` explicitly in LLMSelectorOptions to silence this warning."
|
|
11726
|
+
);
|
|
11727
|
+
}
|
|
11385
11728
|
this.maxContextTokens = opts.maxContextTokens ?? 4e4;
|
|
11386
11729
|
this.systemPrompt = opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
11730
|
+
this.maxOutputTokens = opts.maxOutputTokens ?? 1024;
|
|
11387
11731
|
}
|
|
11388
11732
|
async select(messages, maxToKeep) {
|
|
11389
11733
|
const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
|
|
11390
|
-
const historyText = formatMessages(messages);
|
|
11391
11734
|
const totalTokens = estimateMessageTokens(messages);
|
|
11392
11735
|
const systemText = `${this.systemPrompt}
|
|
11393
11736
|
|
|
11394
11737
|
Conversation (${messages.length} messages, ~${totalTokens} tokens, budget: ${effectiveBudget}):
|
|
11395
11738
|
`;
|
|
11739
|
+
const systemTokens = estimateTextTokens(systemText);
|
|
11740
|
+
const historyBudget = Math.max(512, effectiveBudget - systemTokens - this.maxOutputTokens);
|
|
11741
|
+
const historyText = formatMessages(messages, historyBudget);
|
|
11396
11742
|
const budgetInstruction = totalTokens > effectiveBudget ? `
|
|
11397
11743
|
|
|
11398
11744
|
IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiveBudget}). You MUST collapse enough to fit. Prefer collapsing older/lower-importance ranges.` : "";
|
|
@@ -11400,18 +11746,26 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
11400
11746
|
model: this.model,
|
|
11401
11747
|
system: [{ type: "text", text: systemText + budgetInstruction }],
|
|
11402
11748
|
messages: [{ role: "user", content: historyText }],
|
|
11403
|
-
maxTokens:
|
|
11749
|
+
maxTokens: this.maxOutputTokens
|
|
11404
11750
|
};
|
|
11405
11751
|
let raw;
|
|
11752
|
+
const ac = new AbortController();
|
|
11406
11753
|
try {
|
|
11407
|
-
const
|
|
11408
|
-
const res = await this.provider.complete(req, {
|
|
11754
|
+
const timeoutSignal = AbortSignal.timeout(3e4);
|
|
11755
|
+
const res = await this.provider.complete(req, {
|
|
11756
|
+
signal: AbortSignal.any([ac.signal, timeoutSignal])
|
|
11757
|
+
});
|
|
11409
11758
|
const textBlocks = res.content.filter(isTextBlock);
|
|
11410
11759
|
raw = textBlocks.map((b) => b.text).join("\n").trim();
|
|
11411
|
-
} catch (
|
|
11760
|
+
} catch (err) {
|
|
11761
|
+
if (err instanceof Error) {
|
|
11762
|
+
console.warn("[LLMSelector] selector call failed, using recency fallback:", err.message);
|
|
11763
|
+
}
|
|
11412
11764
|
return this.fallbackSelect(messages, effectiveBudget);
|
|
11765
|
+
} finally {
|
|
11766
|
+
ac.abort();
|
|
11413
11767
|
}
|
|
11414
|
-
return this.parseSelectorOutput(raw, messages
|
|
11768
|
+
return this.parseSelectorOutput(raw, messages);
|
|
11415
11769
|
}
|
|
11416
11770
|
fallbackSelect(messages, budget) {
|
|
11417
11771
|
const toKeep = [];
|
|
@@ -11438,34 +11792,63 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
11438
11792
|
reasoning: `Fallback: kept last ${messages.length - startIdx} messages within ${budget} token budget`
|
|
11439
11793
|
};
|
|
11440
11794
|
}
|
|
11441
|
-
|
|
11795
|
+
/**
|
|
11796
|
+
* Parse and validate the raw LLM output into a SelectorResult.
|
|
11797
|
+
* Falls back to recency-based selection if the LLM output is malformed,
|
|
11798
|
+
* out-of-bounds, or internally inconsistent.
|
|
11799
|
+
*/
|
|
11800
|
+
parseSelectorOutput(raw, messages) {
|
|
11801
|
+
const messageCount = messages.length;
|
|
11802
|
+
if (messageCount === 0) {
|
|
11803
|
+
return { kept: [], collapsed: [], reasoning: "empty session" };
|
|
11804
|
+
}
|
|
11442
11805
|
const jsonStart = raw.indexOf("{");
|
|
11443
11806
|
const jsonEnd = raw.lastIndexOf("}");
|
|
11444
11807
|
if (jsonStart === -1 || jsonEnd === -1) {
|
|
11445
|
-
return this.fallbackSelect(
|
|
11446
|
-
Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
|
|
11447
|
-
this.maxContextTokens
|
|
11448
|
-
);
|
|
11808
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
11449
11809
|
}
|
|
11450
11810
|
let parsed;
|
|
11451
11811
|
try {
|
|
11452
11812
|
parsed = JSON.parse(raw.slice(jsonStart, jsonEnd + 1));
|
|
11453
11813
|
} catch {
|
|
11454
|
-
return this.fallbackSelect(
|
|
11455
|
-
Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
|
|
11456
|
-
this.maxContextTokens
|
|
11457
|
-
);
|
|
11814
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
11458
11815
|
}
|
|
11459
11816
|
const obj = parsed;
|
|
11460
|
-
const
|
|
11461
|
-
const
|
|
11462
|
-
|
|
11463
|
-
|
|
11817
|
+
const keptRaw = obj.kept ?? [];
|
|
11818
|
+
const collapsedRaw = obj.collapsed ?? [];
|
|
11819
|
+
const kept = [];
|
|
11820
|
+
for (const k of keptRaw) {
|
|
11821
|
+
if (typeof k.from !== "number" || typeof k.to !== "number" || k.from < 0 || k.to >= messageCount || k.from > k.to) {
|
|
11822
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
11823
|
+
}
|
|
11824
|
+
kept.push({
|
|
11464
11825
|
from: k.from,
|
|
11465
11826
|
to: k.to,
|
|
11466
11827
|
importance: k.importance ?? "medium"
|
|
11467
|
-
})
|
|
11468
|
-
|
|
11828
|
+
});
|
|
11829
|
+
}
|
|
11830
|
+
const collapsed = [];
|
|
11831
|
+
for (const c of collapsedRaw) {
|
|
11832
|
+
if (typeof c.from !== "number" || typeof c.to !== "number" || c.from < 0 || c.to >= messageCount || c.from > c.to) {
|
|
11833
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
11834
|
+
}
|
|
11835
|
+
collapsed.push({ from: c.from, to: c.to, summary: c.summary });
|
|
11836
|
+
}
|
|
11837
|
+
const allRanges = [...kept, ...collapsed];
|
|
11838
|
+
for (let i = 0; i < allRanges.length; i++) {
|
|
11839
|
+
const a = allRanges[i];
|
|
11840
|
+
if (!a) continue;
|
|
11841
|
+
for (let j = i + 1; j < allRanges.length; j++) {
|
|
11842
|
+
const b = allRanges[j];
|
|
11843
|
+
if (!b) continue;
|
|
11844
|
+
if (a.from <= b.to && a.to >= b.from) {
|
|
11845
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
11846
|
+
}
|
|
11847
|
+
}
|
|
11848
|
+
}
|
|
11849
|
+
return {
|
|
11850
|
+
kept,
|
|
11851
|
+
collapsed,
|
|
11469
11852
|
reasoning: typeof obj.reasoning === "string" ? obj.reasoning : ""
|
|
11470
11853
|
};
|
|
11471
11854
|
}
|
|
@@ -11485,7 +11868,7 @@ var SelectiveCompactor = class {
|
|
|
11485
11868
|
summarizerPrompt;
|
|
11486
11869
|
constructor(opts) {
|
|
11487
11870
|
this.provider = opts.provider;
|
|
11488
|
-
this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel });
|
|
11871
|
+
this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel, maxOutputTokens: opts.selectorMaxOutputTokens });
|
|
11489
11872
|
this.warnThreshold = opts.warnThreshold ?? 0.6;
|
|
11490
11873
|
this.softThreshold = opts.softThreshold ?? 0.75;
|
|
11491
11874
|
this.hardThreshold = opts.hardThreshold ?? 0.9;
|
|
@@ -11493,6 +11876,11 @@ var SelectiveCompactor = class {
|
|
|
11493
11876
|
this.preserveK = opts.preserveK ?? 4;
|
|
11494
11877
|
this.eliseThreshold = opts.eliseThreshold ?? 500;
|
|
11495
11878
|
this.summarizerModel = opts.summarizerModel ?? opts.selectorModel ?? "unknown";
|
|
11879
|
+
if (this.summarizerModel === "unknown" && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
|
|
11880
|
+
console.warn(
|
|
11881
|
+
"[SelectiveCompactor] summarizerModel not set \u2014 will use provider default. Set `summarizerModel` explicitly to silence this warning."
|
|
11882
|
+
);
|
|
11883
|
+
}
|
|
11496
11884
|
this.summarizerPrompt = opts.summarizerPrompt ?? "You are a context summarizer. Given a list of messages, produce a concise summary that preserves all factual information, decisions, file changes, and state changes. Do not add commentary or opinions.";
|
|
11497
11885
|
}
|
|
11498
11886
|
async compact(ctx, opts = {}) {
|
|
@@ -11604,8 +11992,9 @@ Summarize the following message range:`;
|
|
|
11604
11992
|
maxTokens: 512
|
|
11605
11993
|
};
|
|
11606
11994
|
try {
|
|
11995
|
+
const timeoutSignal = AbortSignal.timeout(3e4);
|
|
11607
11996
|
const res = await this.provider.complete(req, {
|
|
11608
|
-
signal: ctx.signal
|
|
11997
|
+
signal: AbortSignal.any([ctx.signal, timeoutSignal])
|
|
11609
11998
|
});
|
|
11610
11999
|
return res.content.filter(isTextBlock).map((b) => b.text).join("\n").trim() || "(empty)";
|
|
11611
12000
|
} catch {
|
|
@@ -11739,6 +12128,7 @@ var ProviderBackedCompactor = class {
|
|
|
11739
12128
|
return new SelectiveCompactor({
|
|
11740
12129
|
...common,
|
|
11741
12130
|
selectorModel: this.opts.summarizerModel,
|
|
12131
|
+
selectorMaxOutputTokens: this.opts.selectorMaxOutputTokens,
|
|
11742
12132
|
summarizerModel: this.opts.summarizerModel
|
|
11743
12133
|
});
|
|
11744
12134
|
}
|
|
@@ -13282,6 +13672,7 @@ ${recentJournal}` : ""
|
|
|
13282
13672
|
|
|
13283
13673
|
// src/coordination/subagent-budget.ts
|
|
13284
13674
|
var TIMEOUT_PREEMPT_FRACTION = 0.85;
|
|
13675
|
+
var DECISION_TIMEOUT_MS = 6e4;
|
|
13285
13676
|
var BudgetExceededError = class extends Error {
|
|
13286
13677
|
kind;
|
|
13287
13678
|
limit;
|
|
@@ -13311,6 +13702,31 @@ var BudgetThresholdSignal = class extends Error {
|
|
|
13311
13702
|
};
|
|
13312
13703
|
var SubagentBudget = class _SubagentBudget {
|
|
13313
13704
|
limits;
|
|
13705
|
+
/** Patch one or more budget limits in-place after construction.
|
|
13706
|
+
* Used by the coordinator watchdog when granting an extension.
|
|
13707
|
+
* All fields are optional — only provided fields are updated.
|
|
13708
|
+
* This is the single write path for limit mutations so that future
|
|
13709
|
+
* validation or side-effects live in one place (M1). */
|
|
13710
|
+
patchLimits(ext) {
|
|
13711
|
+
if (ext.maxIterations !== void 0) {
|
|
13712
|
+
this.limits.maxIterations = ext.maxIterations;
|
|
13713
|
+
}
|
|
13714
|
+
if (ext.maxToolCalls !== void 0) {
|
|
13715
|
+
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
13716
|
+
}
|
|
13717
|
+
if (ext.maxTokens !== void 0) {
|
|
13718
|
+
this.limits.maxTokens = ext.maxTokens;
|
|
13719
|
+
}
|
|
13720
|
+
if (ext.maxCostUsd !== void 0) {
|
|
13721
|
+
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
13722
|
+
}
|
|
13723
|
+
if (ext.timeoutMs !== void 0) {
|
|
13724
|
+
this.limits.timeoutMs = ext.timeoutMs;
|
|
13725
|
+
}
|
|
13726
|
+
if (ext.idleTimeoutMs !== void 0) {
|
|
13727
|
+
this.limits.idleTimeoutMs = ext.idleTimeoutMs;
|
|
13728
|
+
}
|
|
13729
|
+
}
|
|
13314
13730
|
iterations = 0;
|
|
13315
13731
|
toolCalls = 0;
|
|
13316
13732
|
tokenInput = 0;
|
|
@@ -13331,12 +13747,44 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
13331
13747
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
13332
13748
|
* leaves the budget over-limit and never enforces anything.
|
|
13333
13749
|
*/
|
|
13334
|
-
static DECISION_TIMEOUT_MS =
|
|
13750
|
+
static DECISION_TIMEOUT_MS = DECISION_TIMEOUT_MS;
|
|
13335
13751
|
/**
|
|
13336
13752
|
* Injected by the runner when wiring the budget to its EventBus.
|
|
13337
13753
|
* Used to emit `budget.threshold_reached` events in `'auto'` mode.
|
|
13338
13754
|
*/
|
|
13339
13755
|
_events;
|
|
13756
|
+
/**
|
|
13757
|
+
* Guard against dual-path races between the coordinator watchdog
|
|
13758
|
+
* (`executeWithTimeout`) and the budget's own `checkTimeout()`.
|
|
13759
|
+
* Both paths detect `elapsed >= timeoutMs` and can emit
|
|
13760
|
+
* `budget.threshold_reached` for kind `'timeout'` simultaneously.
|
|
13761
|
+
* Set to the current `timeoutMs` ceiling by the coordinator BEFORE
|
|
13762
|
+
* calling `onThreshold`, and cleared after the negotiation resolves.
|
|
13763
|
+
* `checkTimeout()` skips its wall-clock check while this is set so
|
|
13764
|
+
* the coordinator's watchdog is the sole source of wall-clock timeout
|
|
13765
|
+
* events — `checkTimeout()` focuses exclusively on `idle_timeout`.
|
|
13766
|
+
*/
|
|
13767
|
+
_watchdogActive;
|
|
13768
|
+
/** Returns the timeout ceiling currently being negotiated by the watchdog,
|
|
13769
|
+
* or `undefined` when no wall-clock negotiation is in flight.
|
|
13770
|
+
* Used by `executeWithTimeout` to detect a stale lock (M3). */
|
|
13771
|
+
get watchdogActive() {
|
|
13772
|
+
return this._watchdogActive;
|
|
13773
|
+
}
|
|
13774
|
+
/** Called by the coordinator watchdog BEFORE calling `onThreshold` so that
|
|
13775
|
+
* `checkTimeout()` skips its wall-clock check for this ceiling. Prevents
|
|
13776
|
+
* the budget's own `checkTimeout()` from emitting a second
|
|
13777
|
+
* `budget.threshold_reached` event while the watchdog is already
|
|
13778
|
+
* negotiating the same wall-clock deadline (C1). */
|
|
13779
|
+
setWatchdogNegotiation(timeoutMs) {
|
|
13780
|
+
this._watchdogActive = timeoutMs;
|
|
13781
|
+
}
|
|
13782
|
+
/** Clears the watchdog guard after negotiation resolves. Called in the
|
|
13783
|
+
* `finally` block of both the pre-empt and deadline branches so it fires
|
|
13784
|
+
* on every exit path: grant, deny, throw, or error. */
|
|
13785
|
+
clearWatchdogNegotiation() {
|
|
13786
|
+
this._watchdogActive = void 0;
|
|
13787
|
+
}
|
|
13340
13788
|
/**
|
|
13341
13789
|
* Negotiation mode — controls whether a threshold hit tries to emit
|
|
13342
13790
|
* `budget.threshold_reached` and wait for a coordinator decision, or
|
|
@@ -13437,7 +13885,8 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
13437
13885
|
if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
|
|
13438
13886
|
exceeded.push({ kind: "idle_timeout", used: idle, limit: this.limits.idleTimeoutMs });
|
|
13439
13887
|
}
|
|
13440
|
-
|
|
13888
|
+
const wallOwnedByWatchdog = this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
|
|
13889
|
+
if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs && !wallOwnedByWatchdog) {
|
|
13441
13890
|
exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
|
|
13442
13891
|
}
|
|
13443
13892
|
}
|
|
@@ -13451,19 +13900,99 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
13451
13900
|
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
13452
13901
|
}
|
|
13453
13902
|
const bus = this._events;
|
|
13454
|
-
if (!bus
|
|
13903
|
+
if (!bus) {
|
|
13455
13904
|
const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
|
|
13456
13905
|
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
13457
13906
|
}
|
|
13907
|
+
const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
|
|
13908
|
+
if (bus.hasListenerFor("budget.threshold_reached")) {
|
|
13909
|
+
for (const entry of exceeded) {
|
|
13910
|
+
if (this._pendingNegotiations.has(entry.kind)) continue;
|
|
13911
|
+
this._pendingNegotiations.set(entry.kind, this._negotiateExtension(entry));
|
|
13912
|
+
}
|
|
13913
|
+
const decision = this._pendingNegotiations.get(first.kind);
|
|
13914
|
+
if (!decision) throw new Error(`No pending negotiation for ${first.kind}`);
|
|
13915
|
+
throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
|
|
13916
|
+
}
|
|
13917
|
+
let hardStop = null;
|
|
13458
13918
|
for (const entry of exceeded) {
|
|
13459
13919
|
if (this._pendingNegotiations.has(entry.kind)) continue;
|
|
13460
|
-
const
|
|
13461
|
-
this._pendingNegotiations.set(entry.kind,
|
|
13920
|
+
const marker = Promise.resolve("stop");
|
|
13921
|
+
this._pendingNegotiations.set(entry.kind, marker);
|
|
13922
|
+
void marker.finally(() => this._pendingNegotiations.delete(entry.kind));
|
|
13923
|
+
const sync = this._invokeHandlerSync(entry);
|
|
13924
|
+
if (!sync) hardStop ??= new BudgetExceededError(entry.kind, entry.limit, entry.used);
|
|
13925
|
+
}
|
|
13926
|
+
if (hardStop) throw hardStop;
|
|
13927
|
+
return exceeded;
|
|
13928
|
+
}
|
|
13929
|
+
/**
|
|
13930
|
+
* Invoke `onThreshold` once for `entry` on the NO-LISTENER path and report
|
|
13931
|
+
* whether it decided synchronously. Returns `true` when the handler returned
|
|
13932
|
+
* a synchronous decision (already honored — an `extend` patched the limits),
|
|
13933
|
+
* or `false` when it returned a Promise (async; the caller hard-stops, since
|
|
13934
|
+
* there is no listener to resolve the negotiation). The handler is given the
|
|
13935
|
+
* full info shape (`requestDecision` plus direct `extend`/`deny`) so both
|
|
13936
|
+
* recording handlers and policy handlers work without a wired listener.
|
|
13937
|
+
*/
|
|
13938
|
+
_invokeHandlerSync(entry) {
|
|
13939
|
+
const handler = this._onThreshold;
|
|
13940
|
+
if (!handler) return false;
|
|
13941
|
+
let extendArg;
|
|
13942
|
+
const result = handler({
|
|
13943
|
+
kind: entry.kind,
|
|
13944
|
+
used: entry.used,
|
|
13945
|
+
limit: entry.limit,
|
|
13946
|
+
requestDecision: () => this._busRequestDecision(entry),
|
|
13947
|
+
// Direct hooks for synchronous policy/recording handlers.
|
|
13948
|
+
extend: (extra) => {
|
|
13949
|
+
extendArg = extra;
|
|
13950
|
+
},
|
|
13951
|
+
deny: () => {
|
|
13952
|
+
}
|
|
13953
|
+
});
|
|
13954
|
+
if (result && typeof result.then === "function") return false;
|
|
13955
|
+
if (result === "throw") return false;
|
|
13956
|
+
if (result && typeof result === "object" && "extend" in result) {
|
|
13957
|
+
extendArg = result.extend;
|
|
13462
13958
|
}
|
|
13463
|
-
|
|
13464
|
-
|
|
13465
|
-
|
|
13466
|
-
|
|
13959
|
+
if (extendArg) this.patchLimits(extendArg);
|
|
13960
|
+
return true;
|
|
13961
|
+
}
|
|
13962
|
+
/**
|
|
13963
|
+
* Emit `budget.threshold_reached` and resolve to the listener's verdict.
|
|
13964
|
+
* Resolves to `'stop'` immediately when there is no listener (or no bus) so
|
|
13965
|
+
* no negotiation can hang and no fallback timer leaks. Mirrors the
|
|
13966
|
+
* coordinator watchdog's own request path so both agree on the no-listener
|
|
13967
|
+
* default.
|
|
13968
|
+
*/
|
|
13969
|
+
_busRequestDecision(entry) {
|
|
13970
|
+
const bus = this._events;
|
|
13971
|
+
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
13972
|
+
return Promise.resolve("stop");
|
|
13973
|
+
}
|
|
13974
|
+
return new Promise((resolve15) => {
|
|
13975
|
+
let resolved = false;
|
|
13976
|
+
const respond = (d) => {
|
|
13977
|
+
if (resolved) return;
|
|
13978
|
+
resolved = true;
|
|
13979
|
+
clearTimeout(fallback);
|
|
13980
|
+
resolve15(d);
|
|
13981
|
+
};
|
|
13982
|
+
const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
|
|
13983
|
+
bus.emit("budget.threshold_reached", {
|
|
13984
|
+
kind: entry.kind,
|
|
13985
|
+
used: entry.used,
|
|
13986
|
+
limit: entry.limit,
|
|
13987
|
+
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
13988
|
+
// deny() wins over a same-dispatch extend(): a listener that both grants
|
|
13989
|
+
// and denies (or two listeners disagreeing) is resolved as a stop. The
|
|
13990
|
+
// grant is deferred a microtask so a synchronous deny in the same emit
|
|
13991
|
+
// pre-empts it; async grants still resolve normally.
|
|
13992
|
+
extend: (extra) => queueMicrotask(() => respond({ extend: extra })),
|
|
13993
|
+
deny: () => respond("stop")
|
|
13994
|
+
});
|
|
13995
|
+
});
|
|
13467
13996
|
}
|
|
13468
13997
|
/**
|
|
13469
13998
|
* Per-kind in-flight negotiation Promises. Each budget kind can have its
|
|
@@ -13483,77 +14012,33 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
13483
14012
|
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
13484
14013
|
* a fresh signal.
|
|
13485
14014
|
*/
|
|
13486
|
-
async _negotiateExtension(
|
|
14015
|
+
async _negotiateExtension(entry) {
|
|
13487
14016
|
if (!this._onThreshold) {
|
|
13488
14017
|
return "stop";
|
|
13489
14018
|
}
|
|
13490
14019
|
try {
|
|
13491
|
-
const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
|
|
13492
14020
|
const result = this._onThreshold({
|
|
13493
|
-
kind:
|
|
13494
|
-
used:
|
|
13495
|
-
limit:
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13503
|
-
|
|
13504
|
-
if (resolved) return;
|
|
13505
|
-
resolved = true;
|
|
13506
|
-
resolve14(d);
|
|
13507
|
-
};
|
|
13508
|
-
const fallback = setTimeout(
|
|
13509
|
-
() => respond("stop"),
|
|
13510
|
-
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
13511
|
-
);
|
|
13512
|
-
for (const { kind: kind2, used, limit } of exceeded) {
|
|
13513
|
-
bus.emit("budget.threshold_reached", {
|
|
13514
|
-
kind: kind2,
|
|
13515
|
-
used,
|
|
13516
|
-
limit,
|
|
13517
|
-
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
13518
|
-
extend: (extra) => {
|
|
13519
|
-
clearTimeout(fallback);
|
|
13520
|
-
respond({ extend: extra });
|
|
13521
|
-
},
|
|
13522
|
-
deny: () => {
|
|
13523
|
-
clearTimeout(fallback);
|
|
13524
|
-
respond("stop");
|
|
13525
|
-
}
|
|
13526
|
-
});
|
|
13527
|
-
}
|
|
13528
|
-
});
|
|
14021
|
+
kind: entry.kind,
|
|
14022
|
+
used: entry.used,
|
|
14023
|
+
limit: entry.limit,
|
|
14024
|
+
// One event for THIS kind only — each exceeded kind has its own
|
|
14025
|
+
// negotiation (and its own resolve), so there is no cross-kind
|
|
14026
|
+
// first-wins drop and no O(N^2) re-emission.
|
|
14027
|
+
requestDecision: () => this._busRequestDecision(entry),
|
|
14028
|
+
extend: (extra) => {
|
|
14029
|
+
this.patchLimits(extra);
|
|
14030
|
+
},
|
|
14031
|
+
deny: () => {
|
|
13529
14032
|
}
|
|
13530
14033
|
});
|
|
13531
14034
|
if (result === "throw") return "stop";
|
|
13532
14035
|
if (result === "continue") return { extend: {} };
|
|
13533
14036
|
const decision = await result;
|
|
13534
14037
|
if (decision === "stop") return "stop";
|
|
13535
|
-
|
|
13536
|
-
if (ext.maxIterations !== void 0) {
|
|
13537
|
-
this.limits.maxIterations = ext.maxIterations;
|
|
13538
|
-
}
|
|
13539
|
-
if (ext.maxToolCalls !== void 0) {
|
|
13540
|
-
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
13541
|
-
}
|
|
13542
|
-
if (ext.maxTokens !== void 0) {
|
|
13543
|
-
this.limits.maxTokens = ext.maxTokens;
|
|
13544
|
-
}
|
|
13545
|
-
if (ext.maxCostUsd !== void 0) {
|
|
13546
|
-
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
13547
|
-
}
|
|
13548
|
-
if (ext.timeoutMs !== void 0) {
|
|
13549
|
-
this.limits.timeoutMs = ext.timeoutMs;
|
|
13550
|
-
}
|
|
13551
|
-
if (ext.idleTimeoutMs !== void 0) {
|
|
13552
|
-
this.limits.idleTimeoutMs = ext.idleTimeoutMs;
|
|
13553
|
-
}
|
|
14038
|
+
this.patchLimits(decision.extend);
|
|
13554
14039
|
return decision;
|
|
13555
14040
|
} finally {
|
|
13556
|
-
this._pendingNegotiations.delete(kind);
|
|
14041
|
+
this._pendingNegotiations.delete(entry.kind);
|
|
13557
14042
|
}
|
|
13558
14043
|
}
|
|
13559
14044
|
recordIteration() {
|
|
@@ -13596,7 +14081,8 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
13596
14081
|
const { timeoutMs, idleTimeoutMs } = this.limits;
|
|
13597
14082
|
if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
|
|
13598
14083
|
const elapsed = Date.now() - this.startTime;
|
|
13599
|
-
const
|
|
14084
|
+
const wallSkipped = this._onThreshold !== void 0 && this._watchdogActive !== void 0 && timeoutMs !== void 0 && this._watchdogActive === timeoutMs;
|
|
14085
|
+
const wallTripped = wallSkipped ? false : timeoutMs !== void 0 && elapsed > timeoutMs;
|
|
13600
14086
|
const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
|
|
13601
14087
|
if (!wallTripped && !idleTripped) return;
|
|
13602
14088
|
void this.checkLimits(elapsed);
|
|
@@ -17104,6 +17590,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
17104
17590
|
terminating = /* @__PURE__ */ new Set();
|
|
17105
17591
|
constructor(config, options = {}) {
|
|
17106
17592
|
super();
|
|
17593
|
+
this.setMaxListeners(0);
|
|
17107
17594
|
this.coordinatorId = config.coordinatorId;
|
|
17108
17595
|
this.config = config;
|
|
17109
17596
|
this.runner = options.runner;
|
|
@@ -17301,7 +17788,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
17301
17788
|
taskIds.map((id) => {
|
|
17302
17789
|
const cached = this.completedResults.find((r) => r.taskId === id);
|
|
17303
17790
|
if (cached) return cached;
|
|
17304
|
-
return new Promise((
|
|
17791
|
+
return new Promise((resolve15, reject) => {
|
|
17305
17792
|
const timeout = setTimeout(() => {
|
|
17306
17793
|
this.off("task.completed", handler);
|
|
17307
17794
|
reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
|
|
@@ -17310,7 +17797,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
17310
17797
|
if (result.taskId === id) {
|
|
17311
17798
|
clearTimeout(timeout);
|
|
17312
17799
|
this.off("task.completed", handler);
|
|
17313
|
-
|
|
17800
|
+
resolve15(result);
|
|
17314
17801
|
}
|
|
17315
17802
|
};
|
|
17316
17803
|
this.on("task.completed", handler);
|
|
@@ -17498,7 +17985,13 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
17498
17985
|
let result;
|
|
17499
17986
|
budget.start();
|
|
17500
17987
|
try {
|
|
17501
|
-
const outcome = await this.executeWithTimeout(
|
|
17988
|
+
const outcome = await this.executeWithTimeout(
|
|
17989
|
+
this.runner,
|
|
17990
|
+
task,
|
|
17991
|
+
runCtx,
|
|
17992
|
+
budget,
|
|
17993
|
+
subagent.config.preemptFraction
|
|
17994
|
+
);
|
|
17502
17995
|
result = {
|
|
17503
17996
|
subagentId,
|
|
17504
17997
|
taskId: task.id,
|
|
@@ -17525,7 +18018,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
17525
18018
|
}
|
|
17526
18019
|
this.recordCompletion(result);
|
|
17527
18020
|
}
|
|
17528
|
-
async executeWithTimeout(runner, task, ctx, budget) {
|
|
18021
|
+
async executeWithTimeout(runner, task, ctx, budget, preemptFraction = TIMEOUT_PREEMPT_FRACTION) {
|
|
17529
18022
|
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
17530
18023
|
const idleLimitMs = budget.limits.idleTimeoutMs;
|
|
17531
18024
|
if (initialTimeoutMs === void 0 && idleLimitMs === void 0) {
|
|
@@ -17533,8 +18026,21 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
17533
18026
|
}
|
|
17534
18027
|
const start = Date.now();
|
|
17535
18028
|
let timer = null;
|
|
17536
|
-
let
|
|
18029
|
+
let PreemptState;
|
|
18030
|
+
((PreemptState2) => {
|
|
18031
|
+
PreemptState2["ACTIVE"] = "active";
|
|
18032
|
+
PreemptState2["LOCKED"] = "locked";
|
|
18033
|
+
})(PreemptState || (PreemptState = {}));
|
|
18034
|
+
let preemptedCeiling = null;
|
|
18035
|
+
let preemptState = "active" /* ACTIVE */;
|
|
18036
|
+
let lastGrantActivityTs = -1;
|
|
17537
18037
|
const timeoutPromise = new Promise((_, reject) => {
|
|
18038
|
+
const terminate = (kind, limit, used) => {
|
|
18039
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
18040
|
+
reject(
|
|
18041
|
+
budget._events?.hasListenerFor("budget.threshold_reached") ? new Error(`subagent stopped: budget ${kind} (limit=${limit}, used=${used})`) : new BudgetExceededError(kind, limit, used)
|
|
18042
|
+
);
|
|
18043
|
+
};
|
|
17538
18044
|
const armFor = (ms) => {
|
|
17539
18045
|
if (timer) clearTimeout(timer);
|
|
17540
18046
|
timer = setTimeout(onTick, Math.max(0, ms));
|
|
@@ -17543,7 +18049,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
17543
18049
|
const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
17544
18050
|
const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
|
|
17545
18051
|
const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
17546
|
-
const preemptRemaining = initialTimeoutMs === void 0 ||
|
|
18052
|
+
const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
|
|
17547
18053
|
armFor(Math.max(25, Math.min(wallRemaining, idleRemaining, preemptRemaining)));
|
|
17548
18054
|
};
|
|
17549
18055
|
const negotiateTimeout = async (used, limit) => {
|
|
@@ -17553,16 +18059,42 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
17553
18059
|
kind: "timeout",
|
|
17554
18060
|
used,
|
|
17555
18061
|
limit,
|
|
17556
|
-
requestDecision: () =>
|
|
17557
|
-
budget._events?.
|
|
17558
|
-
|
|
17559
|
-
|
|
17560
|
-
|
|
17561
|
-
|
|
17562
|
-
|
|
17563
|
-
|
|
18062
|
+
requestDecision: () => {
|
|
18063
|
+
if (!budget._events?.hasListenerFor("budget.threshold_reached")) {
|
|
18064
|
+
return Promise.resolve("stop");
|
|
18065
|
+
}
|
|
18066
|
+
return new Promise((resolveDecision) => {
|
|
18067
|
+
let settled = false;
|
|
18068
|
+
const resolve15 = (d) => {
|
|
18069
|
+
if (settled) return;
|
|
18070
|
+
settled = true;
|
|
18071
|
+
resolveDecision(d);
|
|
18072
|
+
};
|
|
18073
|
+
const fallback = setTimeout(() => resolve15("stop"), DECISION_TIMEOUT_MS);
|
|
18074
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
18075
|
+
kind: "timeout",
|
|
18076
|
+
used,
|
|
18077
|
+
limit,
|
|
18078
|
+
// Informational: the budget's own decision deadline. Listeners may use
|
|
18079
|
+
// this to display a countdown. The coordinator does NOT enforce it —
|
|
18080
|
+
// it is the budget's own `setTimeout(fallback)` that races against
|
|
18081
|
+
// the listener's `extend()`/`deny()` call to guarantee progress.
|
|
18082
|
+
timeoutMs: DECISION_TIMEOUT_MS,
|
|
18083
|
+
// deny() wins over a same-dispatch extend(): defer the grant a
|
|
18084
|
+
// microtask so a synchronous deny in the same emit pre-empts it
|
|
18085
|
+
// (a listener that both grants and denies, or two listeners
|
|
18086
|
+
// disagreeing, resolves as a stop). Async grants still resolve.
|
|
18087
|
+
extend: (extra) => {
|
|
18088
|
+
clearTimeout(fallback);
|
|
18089
|
+
queueMicrotask(() => resolve15({ extend: extra }));
|
|
18090
|
+
},
|
|
18091
|
+
deny: () => {
|
|
18092
|
+
clearTimeout(fallback);
|
|
18093
|
+
resolve15("stop");
|
|
18094
|
+
}
|
|
18095
|
+
});
|
|
17564
18096
|
});
|
|
17565
|
-
}
|
|
18097
|
+
}
|
|
17566
18098
|
});
|
|
17567
18099
|
return typeof result === "string" ? result : await result;
|
|
17568
18100
|
};
|
|
@@ -17573,21 +18105,45 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
17573
18105
|
const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
|
|
17574
18106
|
const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
|
|
17575
18107
|
if (idleExceeded && !wallExceeded) {
|
|
18108
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
18109
|
+
kind: "idle_timeout",
|
|
18110
|
+
used: budget.idleMs(),
|
|
18111
|
+
limit: idleLimit ?? 0,
|
|
18112
|
+
timeoutMs: DECISION_TIMEOUT_MS,
|
|
18113
|
+
extend: () => {
|
|
18114
|
+
},
|
|
18115
|
+
deny: () => {
|
|
18116
|
+
}
|
|
18117
|
+
});
|
|
17576
18118
|
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
17577
|
-
reject(new BudgetExceededError("
|
|
18119
|
+
reject(new BudgetExceededError("idle_timeout", idleLimit ?? 0, budget.idleMs()));
|
|
17578
18120
|
return;
|
|
17579
18121
|
}
|
|
17580
|
-
if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold &&
|
|
18122
|
+
if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed >= wallLimit * preemptFraction) {
|
|
18123
|
+
const activityTs = Date.now() - budget.idleMs();
|
|
18124
|
+
if (activityTs <= lastGrantActivityTs) {
|
|
18125
|
+
preemptState = "locked" /* LOCKED */;
|
|
18126
|
+
preemptedCeiling = wallLimit;
|
|
18127
|
+
scheduleNext();
|
|
18128
|
+
return;
|
|
18129
|
+
}
|
|
18130
|
+
budget.setWatchdogNegotiation(wallLimit);
|
|
17581
18131
|
try {
|
|
17582
18132
|
const decision = await negotiateTimeout(elapsed, wallLimit);
|
|
17583
18133
|
if (typeof decision !== "string" && decision.extend.timeoutMs !== void 0) {
|
|
17584
|
-
budget.
|
|
17585
|
-
|
|
18134
|
+
budget.patchLimits({ timeoutMs: decision.extend.timeoutMs });
|
|
18135
|
+
lastGrantActivityTs = Date.now() - budget.idleMs();
|
|
18136
|
+
preemptState = "active" /* ACTIVE */;
|
|
18137
|
+
preemptedCeiling = null;
|
|
17586
18138
|
} else {
|
|
17587
|
-
|
|
18139
|
+
preemptState = "locked" /* LOCKED */;
|
|
18140
|
+
preemptedCeiling = wallLimit;
|
|
17588
18141
|
}
|
|
17589
18142
|
} catch {
|
|
17590
|
-
|
|
18143
|
+
preemptState = "locked" /* LOCKED */;
|
|
18144
|
+
preemptedCeiling = wallLimit;
|
|
18145
|
+
} finally {
|
|
18146
|
+
budget.clearWatchdogNegotiation();
|
|
17591
18147
|
}
|
|
17592
18148
|
scheduleNext();
|
|
17593
18149
|
return;
|
|
@@ -17602,26 +18158,41 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
17602
18158
|
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
17603
18159
|
return;
|
|
17604
18160
|
}
|
|
18161
|
+
budget.setWatchdogNegotiation(limit);
|
|
17605
18162
|
try {
|
|
17606
18163
|
const decision = await negotiateTimeout(elapsed, limit);
|
|
17607
|
-
if (decision === "
|
|
17608
|
-
|
|
18164
|
+
if (decision === "throw") {
|
|
18165
|
+
terminate("timeout", limit, elapsed);
|
|
18166
|
+
return;
|
|
18167
|
+
}
|
|
18168
|
+
if (decision === "continue") {
|
|
18169
|
+
preemptState = "locked" /* LOCKED */;
|
|
18170
|
+
preemptedCeiling = wallLimit;
|
|
17609
18171
|
armFor(Math.max(1e3, limit));
|
|
17610
18172
|
return;
|
|
17611
18173
|
}
|
|
18174
|
+
if (decision === "stop") {
|
|
18175
|
+
terminate("timeout", limit, elapsed);
|
|
18176
|
+
return;
|
|
18177
|
+
}
|
|
17612
18178
|
if (decision.extend.timeoutMs !== void 0) {
|
|
17613
|
-
budget.
|
|
17614
|
-
|
|
18179
|
+
budget.patchLimits({ timeoutMs: decision.extend.timeoutMs });
|
|
18180
|
+
lastGrantActivityTs = Date.now() - budget.idleMs();
|
|
18181
|
+
preemptState = "active" /* ACTIVE */;
|
|
18182
|
+
preemptedCeiling = null;
|
|
17615
18183
|
scheduleNext();
|
|
17616
18184
|
return;
|
|
17617
18185
|
}
|
|
17618
|
-
|
|
17619
|
-
|
|
18186
|
+
terminate("timeout", limit, elapsed);
|
|
18187
|
+
return;
|
|
17620
18188
|
} catch (err) {
|
|
17621
18189
|
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
17622
18190
|
reject(
|
|
17623
18191
|
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
17624
18192
|
);
|
|
18193
|
+
return;
|
|
18194
|
+
} finally {
|
|
18195
|
+
budget.clearWatchdogNegotiation();
|
|
17625
18196
|
}
|
|
17626
18197
|
};
|
|
17627
18198
|
scheduleNext();
|
|
@@ -18398,7 +18969,7 @@ var CollabSession = class extends EventEmitter {
|
|
|
18398
18969
|
}
|
|
18399
18970
|
for (const filePath of allFiles) {
|
|
18400
18971
|
try {
|
|
18401
|
-
const [content,
|
|
18972
|
+
const [content, stat14] = await Promise.all([
|
|
18402
18973
|
fsp3.readFile(filePath, "utf8"),
|
|
18403
18974
|
fsp3.stat(filePath)
|
|
18404
18975
|
]);
|
|
@@ -18408,8 +18979,8 @@ var CollabSession = class extends EventEmitter {
|
|
|
18408
18979
|
path: filePath,
|
|
18409
18980
|
content,
|
|
18410
18981
|
language,
|
|
18411
|
-
snapshotMtimeMs:
|
|
18412
|
-
snapshotSizeBytes:
|
|
18982
|
+
snapshotMtimeMs: stat14.mtimeMs,
|
|
18983
|
+
snapshotSizeBytes: stat14.size
|
|
18413
18984
|
});
|
|
18414
18985
|
} catch {
|
|
18415
18986
|
this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
|
|
@@ -18822,9 +19393,9 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
18822
19393
|
for (const file of this.snapshot.files) {
|
|
18823
19394
|
if (file.snapshotMtimeMs === void 0 && file.snapshotSizeBytes === void 0) continue;
|
|
18824
19395
|
try {
|
|
18825
|
-
const
|
|
18826
|
-
const mtimeChanged = file.snapshotMtimeMs !== void 0 &&
|
|
18827
|
-
const sizeChanged = file.snapshotSizeBytes !== void 0 &&
|
|
19396
|
+
const stat14 = await fsp3.stat(file.path);
|
|
19397
|
+
const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat14.mtimeMs > file.snapshotMtimeMs + 1;
|
|
19398
|
+
const sizeChanged = file.snapshotSizeBytes !== void 0 && stat14.size !== file.snapshotSizeBytes;
|
|
18828
19399
|
if (mtimeChanged || sizeChanged) {
|
|
18829
19400
|
warnings.push(`${file.path} changed after the collab snapshot was captured.`);
|
|
18830
19401
|
}
|
|
@@ -20718,11 +21289,11 @@ var Director = class _Director {
|
|
|
20718
21289
|
if (cached) return cached;
|
|
20719
21290
|
const existing = this.taskWaiters.get(id);
|
|
20720
21291
|
if (existing) return existing.promise;
|
|
20721
|
-
let
|
|
21292
|
+
let resolve15;
|
|
20722
21293
|
const promise = new Promise((res) => {
|
|
20723
|
-
|
|
21294
|
+
resolve15 = res;
|
|
20724
21295
|
});
|
|
20725
|
-
this.taskWaiters.set(id, { promise, resolve:
|
|
21296
|
+
this.taskWaiters.set(id, { promise, resolve: resolve15 });
|
|
20726
21297
|
return promise;
|
|
20727
21298
|
})
|
|
20728
21299
|
);
|
|
@@ -21118,7 +21689,7 @@ function createDelegateTool(opts) {
|
|
|
21118
21689
|
subagentId
|
|
21119
21690
|
});
|
|
21120
21691
|
const dir = director;
|
|
21121
|
-
const result = await new Promise((
|
|
21692
|
+
const result = await new Promise((resolve15) => {
|
|
21122
21693
|
let settled = false;
|
|
21123
21694
|
let timer;
|
|
21124
21695
|
const finish = (value) => {
|
|
@@ -21128,7 +21699,7 @@ function createDelegateTool(opts) {
|
|
|
21128
21699
|
offTool();
|
|
21129
21700
|
offIter();
|
|
21130
21701
|
offProgress();
|
|
21131
|
-
|
|
21702
|
+
resolve15(value);
|
|
21132
21703
|
};
|
|
21133
21704
|
const arm = () => {
|
|
21134
21705
|
if (timer) clearTimeout(timer);
|
|
@@ -21439,6 +22010,7 @@ function attachAutoExtend(events, policy = {}) {
|
|
|
21439
22010
|
const extendCounts = /* @__PURE__ */ new Map();
|
|
21440
22011
|
let progress = 0;
|
|
21441
22012
|
let lastTimeoutProgress = -1;
|
|
22013
|
+
let lastSeenKey = null;
|
|
21442
22014
|
const unsubs = [
|
|
21443
22015
|
events.on("tool.executed", () => {
|
|
21444
22016
|
progress++;
|
|
@@ -21448,6 +22020,9 @@ function attachAutoExtend(events, policy = {}) {
|
|
|
21448
22020
|
}),
|
|
21449
22021
|
events.on("budget.threshold_reached", (e) => {
|
|
21450
22022
|
const { kind, limit, extend, deny } = e;
|
|
22023
|
+
const key = `${kind}:${limit}`;
|
|
22024
|
+
if (key === lastSeenKey) return;
|
|
22025
|
+
lastSeenKey = key;
|
|
21451
22026
|
if (kind === "timeout" || kind === "idle_timeout") {
|
|
21452
22027
|
if (progress > lastTimeoutProgress) {
|
|
21453
22028
|
lastTimeoutProgress = progress;
|
|
@@ -21556,8 +22131,8 @@ async function loadProjectModes(modesDir) {
|
|
|
21556
22131
|
for (const entry of entries) {
|
|
21557
22132
|
if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
|
|
21558
22133
|
const filePath = path2.join(modesDir, entry);
|
|
21559
|
-
const
|
|
21560
|
-
if (!
|
|
22134
|
+
const stat14 = await fsp3.stat(filePath);
|
|
22135
|
+
if (!stat14.isFile()) continue;
|
|
21561
22136
|
const content = await fsp3.readFile(filePath, "utf8");
|
|
21562
22137
|
const id = path2.basename(entry, path2.extname(entry));
|
|
21563
22138
|
modes.push({
|
|
@@ -22866,9 +23441,9 @@ var AISpecBuilder = class {
|
|
|
22866
23441
|
if (!this.sessionPath) return;
|
|
22867
23442
|
try {
|
|
22868
23443
|
const fsp25 = await import('fs/promises');
|
|
22869
|
-
const
|
|
23444
|
+
const path44 = await import('path');
|
|
22870
23445
|
const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
|
|
22871
|
-
await fsp25.mkdir(
|
|
23446
|
+
await fsp25.mkdir(path44.dirname(this.sessionPath), { recursive: true });
|
|
22872
23447
|
await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
|
|
22873
23448
|
} catch {
|
|
22874
23449
|
}
|
|
@@ -23595,15 +24170,15 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
|
|
|
23595
24170
|
maxId = id;
|
|
23596
24171
|
}
|
|
23597
24172
|
}
|
|
23598
|
-
const
|
|
24173
|
+
const path44 = [];
|
|
23599
24174
|
let current = maxId;
|
|
23600
24175
|
const visited = /* @__PURE__ */ new Set();
|
|
23601
24176
|
while (current && !visited.has(current)) {
|
|
23602
24177
|
visited.add(current);
|
|
23603
|
-
|
|
24178
|
+
path44.unshift(current);
|
|
23604
24179
|
current = prev.get(current) ?? null;
|
|
23605
24180
|
}
|
|
23606
|
-
return
|
|
24181
|
+
return path44;
|
|
23607
24182
|
}
|
|
23608
24183
|
function computeParallelGroups(graph, blockedByMap) {
|
|
23609
24184
|
const groups = [];
|
|
@@ -24426,9 +25001,9 @@ var DefaultHealthRegistry = class {
|
|
|
24426
25001
|
}
|
|
24427
25002
|
async runOne(check) {
|
|
24428
25003
|
let timer = null;
|
|
24429
|
-
const timeout = new Promise((
|
|
25004
|
+
const timeout = new Promise((resolve15) => {
|
|
24430
25005
|
timer = setTimeout(
|
|
24431
|
-
() =>
|
|
25006
|
+
() => resolve15({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
|
|
24432
25007
|
this.timeoutMs
|
|
24433
25008
|
);
|
|
24434
25009
|
});
|
|
@@ -24611,7 +25186,7 @@ async function startMetricsServer(opts) {
|
|
|
24611
25186
|
const tls = opts.tls;
|
|
24612
25187
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
24613
25188
|
const host = opts.host ?? "127.0.0.1";
|
|
24614
|
-
const
|
|
25189
|
+
const path44 = opts.path ?? "/metrics";
|
|
24615
25190
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
24616
25191
|
const healthRegistry = opts.healthRegistry;
|
|
24617
25192
|
const listener = (req, res) => {
|
|
@@ -24621,7 +25196,7 @@ async function startMetricsServer(opts) {
|
|
|
24621
25196
|
return;
|
|
24622
25197
|
}
|
|
24623
25198
|
const url = req.url.split("?")[0];
|
|
24624
|
-
if (url ===
|
|
25199
|
+
if (url === path44) {
|
|
24625
25200
|
let body;
|
|
24626
25201
|
try {
|
|
24627
25202
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -24667,14 +25242,14 @@ async function startMetricsServer(opts) {
|
|
|
24667
25242
|
const { createServer } = await import('http');
|
|
24668
25243
|
server = createServer(listener);
|
|
24669
25244
|
}
|
|
24670
|
-
await new Promise((
|
|
25245
|
+
await new Promise((resolve15, reject) => {
|
|
24671
25246
|
const onError = (err) => {
|
|
24672
25247
|
server.off("listening", onListening);
|
|
24673
25248
|
reject(err);
|
|
24674
25249
|
};
|
|
24675
25250
|
const onListening = () => {
|
|
24676
25251
|
server.off("error", onError);
|
|
24677
|
-
|
|
25252
|
+
resolve15();
|
|
24678
25253
|
};
|
|
24679
25254
|
server.once("error", onError);
|
|
24680
25255
|
server.once("listening", onListening);
|
|
@@ -24685,9 +25260,9 @@ async function startMetricsServer(opts) {
|
|
|
24685
25260
|
const protocol = useHttps ? "https" : "http";
|
|
24686
25261
|
return {
|
|
24687
25262
|
port: boundPort,
|
|
24688
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
24689
|
-
close: () => new Promise((
|
|
24690
|
-
server.close((err) => err ? reject(err) :
|
|
25263
|
+
url: `${protocol}://${host}:${boundPort}${path44}`,
|
|
25264
|
+
close: () => new Promise((resolve15, reject) => {
|
|
25265
|
+
server.close((err) => err ? reject(err) : resolve15());
|
|
24691
25266
|
})
|
|
24692
25267
|
};
|
|
24693
25268
|
}
|
|
@@ -25002,11 +25577,12 @@ function createContextManagerTool(opts = {}) {
|
|
|
25002
25577
|
const applyMessages = (next) => {
|
|
25003
25578
|
const repaired = repairToolUseAdjacency(next);
|
|
25004
25579
|
const finalMessages = repaired.messages;
|
|
25580
|
+
if (finalMessages === messages) return repaired.report;
|
|
25005
25581
|
if (ctx.state) {
|
|
25006
25582
|
ctx.state.replaceMessages(finalMessages);
|
|
25007
25583
|
} else {
|
|
25008
25584
|
messages.length = 0;
|
|
25009
|
-
messages.
|
|
25585
|
+
messages.push(...finalMessages);
|
|
25010
25586
|
}
|
|
25011
25587
|
return repaired.report;
|
|
25012
25588
|
};
|
|
@@ -25081,9 +25657,15 @@ function createContextManagerTool(opts = {}) {
|
|
|
25081
25657
|
}
|
|
25082
25658
|
const report = await opts.compactor.compact(ctx);
|
|
25083
25659
|
ctx.clearFileTracking();
|
|
25084
|
-
|
|
25085
|
-
|
|
25086
|
-
|
|
25660
|
+
let repaired = report.repaired;
|
|
25661
|
+
let afterTokens;
|
|
25662
|
+
if (!ctx.state) {
|
|
25663
|
+
const repair = applyMessages([...ctx.messages]);
|
|
25664
|
+
repaired = report.repaired ?? (repair.changed ? repair : void 0);
|
|
25665
|
+
afterTokens = repair.changed ? roughEstimate(ctx.messages) : report.after;
|
|
25666
|
+
} else {
|
|
25667
|
+
afterTokens = report.after;
|
|
25668
|
+
}
|
|
25087
25669
|
const reduced = report.fullRequestTokensBefore > report.fullRequestTokensAfter;
|
|
25088
25670
|
const repairedSomething = !!report.repaired;
|
|
25089
25671
|
if (reduced || repairedSomething) {
|
|
@@ -25603,13 +26185,13 @@ var SkillInstaller = class {
|
|
|
25603
26185
|
context: { reason: "path_traversal", skillName: skill.name }
|
|
25604
26186
|
});
|
|
25605
26187
|
}
|
|
25606
|
-
const
|
|
25607
|
-
if (
|
|
26188
|
+
const stat14 = await fsp3.stat(srcPath);
|
|
26189
|
+
if (stat14.size > MAX_SKILL_FILE_SIZE) {
|
|
25608
26190
|
throw new FsError({
|
|
25609
|
-
message: `Skill file "${file}" is too large (${(
|
|
26191
|
+
message: `Skill file "${file}" is too large (${(stat14.size / 1024).toFixed(1)}KB). Max: ${MAX_SKILL_FILE_SIZE / 1024}KB`,
|
|
25610
26192
|
code: ERROR_CODES.FS_WRITE_FAILED,
|
|
25611
26193
|
path: srcPath,
|
|
25612
|
-
context: { skillName: skill.name, fileSize:
|
|
26194
|
+
context: { skillName: skill.name, fileSize: stat14.size, maxSize: MAX_SKILL_FILE_SIZE }
|
|
25613
26195
|
});
|
|
25614
26196
|
}
|
|
25615
26197
|
await fsp3.mkdir(path2.dirname(destPath), { recursive: true });
|
|
@@ -26833,15 +27415,15 @@ var SessionRecovery = class {
|
|
|
26833
27415
|
async detectStale(sessionId) {
|
|
26834
27416
|
const fp = this.filePath(sessionId);
|
|
26835
27417
|
const TAIL_SIZE = 8192;
|
|
26836
|
-
let
|
|
27418
|
+
let stat14;
|
|
26837
27419
|
try {
|
|
26838
|
-
|
|
27420
|
+
stat14 = await fsp3.stat(fp);
|
|
26839
27421
|
} catch (err) {
|
|
26840
27422
|
if (err.code === "ENOENT") return null;
|
|
26841
27423
|
return null;
|
|
26842
27424
|
}
|
|
26843
|
-
if (
|
|
26844
|
-
const position = Math.max(0,
|
|
27425
|
+
if (stat14.size === 0) return null;
|
|
27426
|
+
const position = Math.max(0, stat14.size - TAIL_SIZE);
|
|
26845
27427
|
const buf = Buffer.alloc(TAIL_SIZE);
|
|
26846
27428
|
let fh;
|
|
26847
27429
|
try {
|
|
@@ -27309,6 +27891,10 @@ function sortKeys2(value) {
|
|
|
27309
27891
|
init_session_registry();
|
|
27310
27892
|
|
|
27311
27893
|
// src/agent-status-tracker.ts
|
|
27894
|
+
var AGENT_REAP_MS = 3e4;
|
|
27895
|
+
var AGENT_SWEEP_INTERVAL_MS = 1e4;
|
|
27896
|
+
var PARTIAL_TEXT_CAP = 1200;
|
|
27897
|
+
var PARTIAL_FLUSH_THROTTLE_MS = 300;
|
|
27312
27898
|
var AgentStatusTracker = class {
|
|
27313
27899
|
events;
|
|
27314
27900
|
registry;
|
|
@@ -27320,11 +27906,21 @@ var AgentStatusTracker = class {
|
|
|
27320
27906
|
leaderCurrentTool;
|
|
27321
27907
|
leaderIterations = 0;
|
|
27322
27908
|
leaderToolCalls = 0;
|
|
27909
|
+
leaderCostUsd = 0;
|
|
27910
|
+
leaderTokensIn = 0;
|
|
27911
|
+
leaderTokensOut = 0;
|
|
27912
|
+
leaderCtxPct;
|
|
27913
|
+
leaderModel;
|
|
27914
|
+
leaderPartialText = "";
|
|
27323
27915
|
unsubscribers = [];
|
|
27916
|
+
onUpdate;
|
|
27917
|
+
sweepTimer = null;
|
|
27918
|
+
partialTimer = null;
|
|
27324
27919
|
constructor(opts) {
|
|
27325
27920
|
this.events = opts.events;
|
|
27326
27921
|
this.registry = opts.registry;
|
|
27327
27922
|
this.leaderName = opts.leaderName ?? "leader";
|
|
27923
|
+
this.onUpdate = opts.onUpdate;
|
|
27328
27924
|
}
|
|
27329
27925
|
start() {
|
|
27330
27926
|
this.unsubscribers.push(
|
|
@@ -27334,10 +27930,22 @@ var AgentStatusTracker = class {
|
|
|
27334
27930
|
this.flush();
|
|
27335
27931
|
})
|
|
27336
27932
|
);
|
|
27933
|
+
this.unsubscribers.push(
|
|
27934
|
+
this.events.onPattern("iteration.started", (_e, payload) => {
|
|
27935
|
+
const ctx = payload?.ctx;
|
|
27936
|
+
if (!ctx) return;
|
|
27937
|
+
if (ctx.model) this.leaderModel = ctx.model;
|
|
27938
|
+
if (typeof ctx.tokenCount === "number" && typeof ctx.maxContext === "number" && ctx.maxContext > 0) {
|
|
27939
|
+
this.leaderCtxPct = Math.round(ctx.tokenCount / ctx.maxContext * 100);
|
|
27940
|
+
}
|
|
27941
|
+
this.flush();
|
|
27942
|
+
})
|
|
27943
|
+
);
|
|
27337
27944
|
this.unsubscribers.push(
|
|
27338
27945
|
this.events.onPattern("agent.run.completed", () => {
|
|
27339
27946
|
this.leaderStatus = "idle";
|
|
27340
27947
|
this.leaderCurrentTool = void 0;
|
|
27948
|
+
this.leaderPartialText = "";
|
|
27341
27949
|
this.flush();
|
|
27342
27950
|
})
|
|
27343
27951
|
);
|
|
@@ -27345,6 +27953,7 @@ var AgentStatusTracker = class {
|
|
|
27345
27953
|
this.events.onPattern("agent.run.error", () => {
|
|
27346
27954
|
this.leaderStatus = "error";
|
|
27347
27955
|
this.leaderCurrentTool = void 0;
|
|
27956
|
+
this.leaderPartialText = "";
|
|
27348
27957
|
this.flush();
|
|
27349
27958
|
})
|
|
27350
27959
|
);
|
|
@@ -27374,74 +27983,120 @@ var AgentStatusTracker = class {
|
|
|
27374
27983
|
this.unsubscribers.push(
|
|
27375
27984
|
this.events.onPattern("llm.stream_started", () => {
|
|
27376
27985
|
this.leaderStatus = "streaming";
|
|
27986
|
+
this.leaderPartialText = "";
|
|
27377
27987
|
this.flush();
|
|
27378
27988
|
})
|
|
27379
27989
|
);
|
|
27380
27990
|
this.unsubscribers.push(
|
|
27381
|
-
this.events.onPattern("
|
|
27991
|
+
this.events.onPattern("provider.text_delta", (_e, payload) => {
|
|
27992
|
+
const text = payload?.text;
|
|
27993
|
+
if (!text) return;
|
|
27994
|
+
this.leaderStatus = "streaming";
|
|
27995
|
+
const next = this.leaderPartialText + text;
|
|
27996
|
+
this.leaderPartialText = next.length > PARTIAL_TEXT_CAP ? next.slice(next.length - PARTIAL_TEXT_CAP) : next;
|
|
27997
|
+
this.schedulePartialFlush();
|
|
27998
|
+
})
|
|
27999
|
+
);
|
|
28000
|
+
this.unsubscribers.push(
|
|
28001
|
+
this.events.onPattern("token.accounted", (_e, payload) => {
|
|
27382
28002
|
const p = payload;
|
|
27383
|
-
if (p
|
|
27384
|
-
|
|
27385
|
-
|
|
27386
|
-
|
|
27387
|
-
|
|
27388
|
-
iterations: 0,
|
|
27389
|
-
toolCalls: 0,
|
|
27390
|
-
lastActivityAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
27391
|
-
});
|
|
27392
|
-
this.flush();
|
|
27393
|
-
}
|
|
28003
|
+
if (!p) return;
|
|
28004
|
+
this.leaderTokensIn += p.usage?.input ?? 0;
|
|
28005
|
+
this.leaderTokensOut += p.usage?.output ?? 0;
|
|
28006
|
+
this.leaderCostUsd += p.cost?.total ?? 0;
|
|
28007
|
+
this.flush();
|
|
27394
28008
|
})
|
|
27395
28009
|
);
|
|
28010
|
+
const touch = (id) => {
|
|
28011
|
+
let entry = this.agents.get(id);
|
|
28012
|
+
if (!entry) {
|
|
28013
|
+
entry = { id, name: id, status: "idle", iterations: 0, toolCalls: 0, lastActivityAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
28014
|
+
this.agents.set(id, entry);
|
|
28015
|
+
}
|
|
28016
|
+
entry.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
28017
|
+
return entry;
|
|
28018
|
+
};
|
|
27396
28019
|
this.unsubscribers.push(
|
|
27397
|
-
this.events.onPattern("
|
|
28020
|
+
this.events.onPattern("subagent.spawned", (_e, payload) => {
|
|
27398
28021
|
const p = payload;
|
|
27399
|
-
if (p?.subagentId)
|
|
27400
|
-
|
|
27401
|
-
|
|
27402
|
-
|
|
27403
|
-
|
|
27404
|
-
|
|
27405
|
-
this.flush();
|
|
27406
|
-
}
|
|
27407
|
-
}
|
|
28022
|
+
if (!p?.subagentId) return;
|
|
28023
|
+
const entry = touch(p.subagentId);
|
|
28024
|
+
entry.name = p.name?.trim() || entry.name;
|
|
28025
|
+
if (p.model) entry.model = p.model;
|
|
28026
|
+
entry.status = "running";
|
|
28027
|
+
this.flush();
|
|
27408
28028
|
})
|
|
27409
28029
|
);
|
|
27410
28030
|
this.unsubscribers.push(
|
|
27411
|
-
this.events.onPattern("
|
|
28031
|
+
this.events.onPattern("subagent.ctx_pct", (_e, payload) => {
|
|
27412
28032
|
const p = payload;
|
|
27413
|
-
if (p?.subagentId)
|
|
27414
|
-
|
|
27415
|
-
|
|
27416
|
-
|
|
27417
|
-
entry.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
27418
|
-
this.flush();
|
|
27419
|
-
}
|
|
27420
|
-
}
|
|
28033
|
+
if (!p?.subagentId) return;
|
|
28034
|
+
const entry = touch(p.subagentId);
|
|
28035
|
+
if (typeof p.load === "number") entry.ctxPct = Math.round(p.load * 100);
|
|
28036
|
+
this.flush();
|
|
27421
28037
|
})
|
|
27422
28038
|
);
|
|
27423
28039
|
this.unsubscribers.push(
|
|
27424
|
-
this.events.onPattern("
|
|
28040
|
+
this.events.onPattern("subagent.task_started", (_e, payload) => {
|
|
27425
28041
|
const p = payload;
|
|
27426
|
-
if (p?.subagentId)
|
|
27427
|
-
|
|
27428
|
-
|
|
27429
|
-
|
|
27430
|
-
|
|
27431
|
-
this.flush();
|
|
27432
|
-
}
|
|
27433
|
-
}
|
|
28042
|
+
if (!p?.subagentId) return;
|
|
28043
|
+
const entry = touch(p.subagentId);
|
|
28044
|
+
entry.status = "running";
|
|
28045
|
+
entry.iterations++;
|
|
28046
|
+
this.flush();
|
|
27434
28047
|
})
|
|
27435
28048
|
);
|
|
27436
28049
|
this.unsubscribers.push(
|
|
27437
|
-
this.events.onPattern("
|
|
28050
|
+
this.events.onPattern("subagent.tool_executed", (_e, payload) => {
|
|
27438
28051
|
const p = payload;
|
|
27439
|
-
if (p?.subagentId)
|
|
27440
|
-
|
|
27441
|
-
|
|
28052
|
+
if (!p?.subagentId) return;
|
|
28053
|
+
const entry = touch(p.subagentId);
|
|
28054
|
+
entry.status = "running";
|
|
28055
|
+
entry.currentTool = p.name;
|
|
28056
|
+
entry.toolCalls++;
|
|
28057
|
+
this.flush();
|
|
28058
|
+
})
|
|
28059
|
+
);
|
|
28060
|
+
this.unsubscribers.push(
|
|
28061
|
+
this.events.onPattern("subagent.iteration_summary", (_e, payload) => {
|
|
28062
|
+
const p = payload;
|
|
28063
|
+
if (!p?.subagentId) return;
|
|
28064
|
+
const entry = touch(p.subagentId);
|
|
28065
|
+
entry.status = "running";
|
|
28066
|
+
if (typeof p.iteration === "number") entry.iterations = p.iteration;
|
|
28067
|
+
if (typeof p.toolCalls === "number") entry.toolCalls = p.toolCalls;
|
|
28068
|
+
if (typeof p.costUsd === "number") entry.costUsd = p.costUsd;
|
|
28069
|
+
if (p.currentTool) entry.currentTool = p.currentTool;
|
|
28070
|
+
if (typeof p.partialText === "string") {
|
|
28071
|
+
entry.partialText = p.partialText.length > PARTIAL_TEXT_CAP ? p.partialText.slice(p.partialText.length - PARTIAL_TEXT_CAP) : p.partialText;
|
|
27442
28072
|
}
|
|
28073
|
+
this.flush();
|
|
28074
|
+
})
|
|
28075
|
+
);
|
|
28076
|
+
this.unsubscribers.push(
|
|
28077
|
+
this.events.onPattern("subagent.task_completed", (_e, payload) => {
|
|
28078
|
+
const p = payload;
|
|
28079
|
+
if (!p?.subagentId) return;
|
|
28080
|
+
const entry = this.agents.get(p.subagentId);
|
|
28081
|
+
if (!entry) return;
|
|
28082
|
+
entry.status = p.status === "failed" || p.status === "timeout" ? "error" : "idle";
|
|
28083
|
+
entry.currentTool = void 0;
|
|
28084
|
+
entry.partialText = void 0;
|
|
28085
|
+
if (typeof p.iterations === "number") entry.iterations = p.iterations;
|
|
28086
|
+
if (typeof p.toolCalls === "number") entry.toolCalls = p.toolCalls;
|
|
28087
|
+
entry.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
28088
|
+
this.flush();
|
|
27443
28089
|
})
|
|
27444
28090
|
);
|
|
28091
|
+
this.unsubscribers.push(
|
|
28092
|
+
this.events.onPattern("subagent.stopped", (_e, payload) => {
|
|
28093
|
+
const p = payload;
|
|
28094
|
+
if (!p?.subagentId) return;
|
|
28095
|
+
if (this.agents.delete(p.subagentId)) this.flush();
|
|
28096
|
+
})
|
|
28097
|
+
);
|
|
28098
|
+
this.sweepTimer = setInterval(() => this.sweep(), AGENT_SWEEP_INTERVAL_MS);
|
|
28099
|
+
if (typeof this.sweepTimer.unref === "function") this.sweepTimer.unref();
|
|
27445
28100
|
}
|
|
27446
28101
|
stop() {
|
|
27447
28102
|
for (const unsub of this.unsubscribers) {
|
|
@@ -27451,6 +28106,45 @@ var AgentStatusTracker = class {
|
|
|
27451
28106
|
}
|
|
27452
28107
|
}
|
|
27453
28108
|
this.unsubscribers = [];
|
|
28109
|
+
if (this.sweepTimer) {
|
|
28110
|
+
clearInterval(this.sweepTimer);
|
|
28111
|
+
this.sweepTimer = null;
|
|
28112
|
+
}
|
|
28113
|
+
if (this.partialTimer) {
|
|
28114
|
+
clearTimeout(this.partialTimer);
|
|
28115
|
+
this.partialTimer = null;
|
|
28116
|
+
}
|
|
28117
|
+
}
|
|
28118
|
+
/**
|
|
28119
|
+
* Coalesce streamed-text flushes: at most one registry write per
|
|
28120
|
+
* {@link PARTIAL_FLUSH_THROTTLE_MS} while text streams in, so per-token
|
|
28121
|
+
* deltas never thrash the cross-process registry file.
|
|
28122
|
+
*/
|
|
28123
|
+
schedulePartialFlush() {
|
|
28124
|
+
if (this.partialTimer) return;
|
|
28125
|
+
this.partialTimer = setTimeout(() => {
|
|
28126
|
+
this.partialTimer = null;
|
|
28127
|
+
this.flush();
|
|
28128
|
+
}, PARTIAL_FLUSH_THROTTLE_MS);
|
|
28129
|
+
if (typeof this.partialTimer.unref === "function") this.partialTimer.unref();
|
|
28130
|
+
}
|
|
28131
|
+
/**
|
|
28132
|
+
* Remove subagents that have been finished (idle/error) for longer than
|
|
28133
|
+
* {@link AGENT_REAP_MS}. Running / streaming / waiting_user agents are kept
|
|
28134
|
+
* regardless of age — only *not-working* agents are reaped.
|
|
28135
|
+
*/
|
|
28136
|
+
sweep() {
|
|
28137
|
+
const now = Date.now();
|
|
28138
|
+
let removed = false;
|
|
28139
|
+
for (const [id, a] of this.agents) {
|
|
28140
|
+
const finished = a.status !== "running" && a.status !== "streaming" && a.status !== "waiting_user";
|
|
28141
|
+
const age = now - Date.parse(a.lastActivityAt);
|
|
28142
|
+
if (finished && Number.isFinite(age) && age > AGENT_REAP_MS) {
|
|
28143
|
+
this.agents.delete(id);
|
|
28144
|
+
removed = true;
|
|
28145
|
+
}
|
|
28146
|
+
}
|
|
28147
|
+
if (removed) this.flush();
|
|
27454
28148
|
}
|
|
27455
28149
|
flush() {
|
|
27456
28150
|
const leaderEntry = {
|
|
@@ -27460,12 +28154,105 @@ var AgentStatusTracker = class {
|
|
|
27460
28154
|
currentTool: this.leaderCurrentTool,
|
|
27461
28155
|
iterations: this.leaderIterations,
|
|
27462
28156
|
toolCalls: this.leaderToolCalls,
|
|
28157
|
+
costUsd: this.leaderCostUsd,
|
|
28158
|
+
tokensIn: this.leaderTokensIn,
|
|
28159
|
+
tokensOut: this.leaderTokensOut,
|
|
28160
|
+
ctxPct: this.leaderCtxPct,
|
|
28161
|
+
model: this.leaderModel,
|
|
28162
|
+
partialText: this.leaderPartialText || void 0,
|
|
27463
28163
|
lastActivityAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
27464
28164
|
};
|
|
27465
28165
|
const allAgents = [leaderEntry, ...this.agents.values()];
|
|
27466
|
-
this.registry.updateAgents(allAgents).
|
|
28166
|
+
this.registry.updateAgents(allAgents).then(() => {
|
|
28167
|
+
try {
|
|
28168
|
+
this.onUpdate?.();
|
|
28169
|
+
} catch {
|
|
28170
|
+
}
|
|
28171
|
+
}).catch(() => void 0);
|
|
28172
|
+
}
|
|
28173
|
+
};
|
|
28174
|
+
var INSTANCES_FILE = "webui-instances.json";
|
|
28175
|
+
var DISCOVERY_TTL_MS = 2500;
|
|
28176
|
+
var COALESCE_MS = 50;
|
|
28177
|
+
var POST_TIMEOUT_MS = 500;
|
|
28178
|
+
function pidAlive2(pid) {
|
|
28179
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
28180
|
+
try {
|
|
28181
|
+
process.kill(pid, 0);
|
|
28182
|
+
return true;
|
|
28183
|
+
} catch (err) {
|
|
28184
|
+
return err.code !== "ESRCH";
|
|
28185
|
+
}
|
|
28186
|
+
}
|
|
28187
|
+
function normRoot(root) {
|
|
28188
|
+
const resolved = path2.resolve(root);
|
|
28189
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
28190
|
+
}
|
|
28191
|
+
var FleetNotifier = class {
|
|
28192
|
+
baseDir;
|
|
28193
|
+
projectRoot;
|
|
28194
|
+
selfPid;
|
|
28195
|
+
doPost;
|
|
28196
|
+
cache = null;
|
|
28197
|
+
timer = null;
|
|
28198
|
+
disposed = false;
|
|
28199
|
+
constructor(opts) {
|
|
28200
|
+
this.baseDir = opts.baseDir;
|
|
28201
|
+
this.projectRoot = normRoot(opts.projectRoot);
|
|
28202
|
+
this.selfPid = opts.selfPid ?? process.pid;
|
|
28203
|
+
this.doPost = opts.post ?? defaultPost;
|
|
28204
|
+
}
|
|
28205
|
+
/** Coalesced, best-effort nudge. Safe to call on every status change. */
|
|
28206
|
+
notify() {
|
|
28207
|
+
if (this.disposed || this.timer) return;
|
|
28208
|
+
this.timer = setTimeout(() => {
|
|
28209
|
+
this.timer = null;
|
|
28210
|
+
void this.flush();
|
|
28211
|
+
}, COALESCE_MS);
|
|
28212
|
+
if (typeof this.timer.unref === "function") this.timer.unref();
|
|
28213
|
+
}
|
|
28214
|
+
/** Resolve same-project WebUI ping URLs (cached briefly). Exposed for tests. */
|
|
28215
|
+
async endpoints() {
|
|
28216
|
+
const now = Date.now();
|
|
28217
|
+
if (this.cache && now - this.cache.at < DISCOVERY_TTL_MS) return this.cache.urls;
|
|
28218
|
+
const urls = await this.discover();
|
|
28219
|
+
this.cache = { at: now, urls };
|
|
28220
|
+
return urls;
|
|
28221
|
+
}
|
|
28222
|
+
dispose() {
|
|
28223
|
+
this.disposed = true;
|
|
28224
|
+
if (this.timer) {
|
|
28225
|
+
clearTimeout(this.timer);
|
|
28226
|
+
this.timer = null;
|
|
28227
|
+
}
|
|
28228
|
+
}
|
|
28229
|
+
async flush() {
|
|
28230
|
+
const urls = await this.endpoints();
|
|
28231
|
+
await Promise.all(urls.map((u) => this.doPost(u).catch(() => void 0)));
|
|
28232
|
+
}
|
|
28233
|
+
async discover() {
|
|
28234
|
+
try {
|
|
28235
|
+
const raw = await fsp3.readFile(path2.join(this.baseDir, INSTANCES_FILE), "utf8");
|
|
28236
|
+
const data = JSON.parse(raw);
|
|
28237
|
+
const list = Array.isArray(data?.instances) ? data.instances : [];
|
|
28238
|
+
return list.filter((i) => i && typeof i.httpPort === "number").filter((i) => i.pid !== this.selfPid).filter((i) => normRoot(i.projectRoot) === this.projectRoot).filter((i) => pidAlive2(i.pid)).map((i) => {
|
|
28239
|
+
const host = i.host === "0.0.0.0" || i.host === "::" || !i.host ? "127.0.0.1" : i.host;
|
|
28240
|
+
return `http://${host}:${i.httpPort}/api/fleet/ping`;
|
|
28241
|
+
});
|
|
28242
|
+
} catch {
|
|
28243
|
+
return [];
|
|
28244
|
+
}
|
|
27467
28245
|
}
|
|
27468
28246
|
};
|
|
28247
|
+
async function defaultPost(url) {
|
|
28248
|
+
const ac = new AbortController();
|
|
28249
|
+
const t2 = setTimeout(() => ac.abort(), POST_TIMEOUT_MS);
|
|
28250
|
+
try {
|
|
28251
|
+
await fetch(url, { method: "POST", signal: ac.signal });
|
|
28252
|
+
} finally {
|
|
28253
|
+
clearTimeout(t2);
|
|
28254
|
+
}
|
|
28255
|
+
}
|
|
27469
28256
|
|
|
27470
28257
|
// src/storage/session-rewinder.ts
|
|
27471
28258
|
init_atomic_write();
|
|
@@ -27715,6 +28502,7 @@ async function saveTasks(filePath, tasks, events, traceId) {
|
|
|
27715
28502
|
durationMs: Date.now() - t0,
|
|
27716
28503
|
...traceId !== void 0 && { traceId }
|
|
27717
28504
|
});
|
|
28505
|
+
return true;
|
|
27718
28506
|
} catch (err) {
|
|
27719
28507
|
events?.emit("storage.error", {
|
|
27720
28508
|
sessionId: traceId ?? "~boot~",
|
|
@@ -27730,13 +28518,17 @@ async function saveTasks(filePath, tasks, events, traceId) {
|
|
|
27730
28518
|
"[task-store] save failed:",
|
|
27731
28519
|
toErrorMessage(err)
|
|
27732
28520
|
);
|
|
28521
|
+
return false;
|
|
27733
28522
|
}
|
|
27734
28523
|
}
|
|
27735
28524
|
async function mutateTasks(filePath, sessionId, fn, events, traceId) {
|
|
27736
28525
|
return withFileLock(filePath, async () => {
|
|
27737
28526
|
const file = await loadTasks(filePath, events, traceId) ?? emptyTaskFile(sessionId);
|
|
27738
28527
|
const updated = await fn(file);
|
|
27739
|
-
await saveTasks(filePath, updated, events, traceId);
|
|
28528
|
+
const persisted = await saveTasks(filePath, updated, events, traceId);
|
|
28529
|
+
if (!persisted) {
|
|
28530
|
+
throw new Error(`Failed to persist tasks to ${filePath} \u2014 the change was NOT saved.`);
|
|
28531
|
+
}
|
|
27740
28532
|
return updated;
|
|
27741
28533
|
});
|
|
27742
28534
|
}
|
|
@@ -28033,8 +28825,8 @@ var CloudSync = class {
|
|
|
28033
28825
|
const localPath = this.categoryToPath(cat);
|
|
28034
28826
|
if (!localPath) continue;
|
|
28035
28827
|
try {
|
|
28036
|
-
const
|
|
28037
|
-
if (
|
|
28828
|
+
const stat14 = await fsp3.stat(localPath);
|
|
28829
|
+
if (stat14.isDirectory()) {
|
|
28038
28830
|
const files = await this.walkDir(localPath, localPath);
|
|
28039
28831
|
for (const file of files) {
|
|
28040
28832
|
const content = await fsp3.readFile(file, "utf8");
|
|
@@ -28059,8 +28851,8 @@ var CloudSync = class {
|
|
|
28059
28851
|
const localPath = this.categoryToPath(cat);
|
|
28060
28852
|
if (!localPath) continue;
|
|
28061
28853
|
try {
|
|
28062
|
-
const
|
|
28063
|
-
if (
|
|
28854
|
+
const stat14 = await fsp3.stat(localPath);
|
|
28855
|
+
if (stat14.isDirectory()) {
|
|
28064
28856
|
const files = await this.walkDir(localPath, localPath);
|
|
28065
28857
|
for (const file of files) {
|
|
28066
28858
|
const content = await fsp3.readFile(file);
|
|
@@ -28087,6 +28879,7 @@ var CloudSync = class {
|
|
|
28087
28879
|
return this.paths.globalMemory;
|
|
28088
28880
|
case "history":
|
|
28089
28881
|
return this.paths.historyFile;
|
|
28882
|
+
/* v8 ignore next -- unreachable: SyncCategory is exhaustively matched above */
|
|
28090
28883
|
default:
|
|
28091
28884
|
return null;
|
|
28092
28885
|
}
|
|
@@ -29316,7 +30109,7 @@ var SecurityScannerOrchestrator = class {
|
|
|
29316
30109
|
message: errAsErr.message,
|
|
29317
30110
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
29318
30111
|
}));
|
|
29319
|
-
await new Promise((
|
|
30112
|
+
await new Promise((resolve15) => setTimeout(resolve15, delay));
|
|
29320
30113
|
return this.completeWithRetry(provider, request, abortController, attempt + 1);
|
|
29321
30114
|
}
|
|
29322
30115
|
}
|
|
@@ -29980,16 +30773,16 @@ Use \`/security report <number>\` to view a specific report.` };
|
|
|
29980
30773
|
}
|
|
29981
30774
|
const index = Number.parseInt(reportId, 10) - 1;
|
|
29982
30775
|
if (!Number.isNaN(index) && reports[index]) {
|
|
29983
|
-
const { readFile:
|
|
29984
|
-
const content = await
|
|
30776
|
+
const { readFile: readFile48 } = await import('fs/promises');
|
|
30777
|
+
const content = await readFile48(join(reportsDir, reports[index]), "utf-8");
|
|
29985
30778
|
return { message: `# Security Report
|
|
29986
30779
|
|
|
29987
30780
|
${content}` };
|
|
29988
30781
|
}
|
|
29989
30782
|
const match = reports.find((r) => r.includes(reportId));
|
|
29990
30783
|
if (match) {
|
|
29991
|
-
const { readFile:
|
|
29992
|
-
const content = await
|
|
30784
|
+
const { readFile: readFile48 } = await import('fs/promises');
|
|
30785
|
+
const content = await readFile48(join(reportsDir, match), "utf-8");
|
|
29993
30786
|
return { message: `# Security Report
|
|
29994
30787
|
|
|
29995
30788
|
${content}` };
|
|
@@ -30449,12 +31242,12 @@ var BrainDecisionQueue = class {
|
|
|
30449
31242
|
options: request.options,
|
|
30450
31243
|
rationale: "Decision escalated to human authority."
|
|
30451
31244
|
};
|
|
30452
|
-
const pending = new Promise((
|
|
30453
|
-
const entry = { request, resolve:
|
|
31245
|
+
const pending = new Promise((resolve15) => {
|
|
31246
|
+
const entry = { request, resolve: resolve15 };
|
|
30454
31247
|
if (this.opts.timeoutMs && this.opts.timeoutMs > 0) {
|
|
30455
31248
|
entry.timer = setTimeout(() => {
|
|
30456
31249
|
this.pending.delete(request.id);
|
|
30457
|
-
|
|
31250
|
+
resolve15({ type: "deny", reason: "Brain human decision timed out." });
|
|
30458
31251
|
}, this.opts.timeoutMs);
|
|
30459
31252
|
}
|
|
30460
31253
|
this.pending.set(request.id, entry);
|
|
@@ -30868,7 +31661,8 @@ var BrainMonitor = class {
|
|
|
30868
31661
|
id: "steer",
|
|
30869
31662
|
label: "Steer the agent with corrective guidance",
|
|
30870
31663
|
consequence: "A steer message is injected before its next step.",
|
|
30871
|
-
risk: "low"
|
|
31664
|
+
risk: "low",
|
|
31665
|
+
recommended: true
|
|
30872
31666
|
},
|
|
30873
31667
|
{
|
|
30874
31668
|
id: "continue",
|
|
@@ -30877,9 +31671,9 @@ var BrainMonitor = class {
|
|
|
30877
31671
|
}
|
|
30878
31672
|
],
|
|
30879
31673
|
risk: "medium",
|
|
30880
|
-
//
|
|
30881
|
-
//
|
|
30882
|
-
fallback: "
|
|
31674
|
+
// 'ask_human' routes to the LLM-backed autonomous layer via
|
|
31675
|
+
// createTieredBrainArbiter before any human escalation.
|
|
31676
|
+
fallback: "ask_human"
|
|
30883
31677
|
};
|
|
30884
31678
|
const decision = await this.opts.brain.decide(request);
|
|
30885
31679
|
const intervened = await this.maybeIntervene(kind, request, decision);
|
|
@@ -31979,6 +32773,10 @@ function makeDependencyWatcherConfig(opts) {
|
|
|
31979
32773
|
return {
|
|
31980
32774
|
watchPaths: unique,
|
|
31981
32775
|
debounceMs,
|
|
32776
|
+
dispose() {
|
|
32777
|
+
for (const t2 of pending.values()) clearTimeout(t2);
|
|
32778
|
+
pending.clear();
|
|
32779
|
+
},
|
|
31982
32780
|
async onChange(entry) {
|
|
31983
32781
|
if (entry.event === "delete") return;
|
|
31984
32782
|
if (!matchesPattern(entry.path)) return;
|
|
@@ -32399,6 +33197,10 @@ var KnowledgeGraph = class {
|
|
|
32399
33197
|
pendingDeliveries = /* @__PURE__ */ new Map();
|
|
32400
33198
|
filePath;
|
|
32401
33199
|
graphFilePath;
|
|
33200
|
+
/** Exposed for unit-testing only: read current index contents. */
|
|
33201
|
+
getIndex() {
|
|
33202
|
+
return this.index;
|
|
33203
|
+
}
|
|
32402
33204
|
constructor(sessionDir) {
|
|
32403
33205
|
this.filePath = path2.join(sessionDir, "_knowledge_graph");
|
|
32404
33206
|
this.graphFilePath = path2.join(this.filePath, "graph.jsonl");
|
|
@@ -32411,7 +33213,7 @@ var KnowledgeGraph = class {
|
|
|
32411
33213
|
async add(node) {
|
|
32412
33214
|
const full = { id: randomUUID(), ...node };
|
|
32413
33215
|
this.nodes.set(full.id, full);
|
|
32414
|
-
this.
|
|
33216
|
+
this._addToIndex(full, this._indexKeys(full));
|
|
32415
33217
|
await this._persist(full);
|
|
32416
33218
|
this._deliver(full);
|
|
32417
33219
|
return full;
|
|
@@ -32420,8 +33222,10 @@ var KnowledgeGraph = class {
|
|
|
32420
33222
|
async update(id, patch) {
|
|
32421
33223
|
const existing = this.nodes.get(id);
|
|
32422
33224
|
if (!existing) return null;
|
|
33225
|
+
this._removeFromIndex(existing, this._indexKeys(existing));
|
|
32423
33226
|
const updated = { ...existing, ...patch };
|
|
32424
33227
|
this.nodes.set(id, updated);
|
|
33228
|
+
this._addToIndex(updated, this._indexKeys(updated));
|
|
32425
33229
|
this._deliver(updated);
|
|
32426
33230
|
await this._append(updated);
|
|
32427
33231
|
return updated;
|
|
@@ -32507,15 +33311,10 @@ var KnowledgeGraph = class {
|
|
|
32507
33311
|
return { passed: checks.every((c) => c.passed), checks };
|
|
32508
33312
|
}
|
|
32509
33313
|
// ── Private ────────────────────────────────────────────────────────────
|
|
32510
|
-
|
|
32511
|
-
|
|
32512
|
-
|
|
32513
|
-
|
|
32514
|
-
set = /* @__PURE__ */ new Set();
|
|
32515
|
-
this.index.set(key, set);
|
|
32516
|
-
}
|
|
32517
|
-
set.add(node.id);
|
|
32518
|
-
};
|
|
33314
|
+
/** Pure: compute the set of index keys a node would belong to. */
|
|
33315
|
+
_indexKeys(node) {
|
|
33316
|
+
const keys = /* @__PURE__ */ new Set();
|
|
33317
|
+
const add = (key) => keys.add(key);
|
|
32519
33318
|
add(`type:${node.type}`);
|
|
32520
33319
|
if (node.type === "fact") {
|
|
32521
33320
|
const f = node;
|
|
@@ -32524,6 +33323,8 @@ var KnowledgeGraph = class {
|
|
|
32524
33323
|
add(`by:${f.discoveredBy}`);
|
|
32525
33324
|
for (const tag of f.tags) add(`tag:${tag}`);
|
|
32526
33325
|
add(`key:${f.key}`);
|
|
33326
|
+
add(`subject:${f.subject}`);
|
|
33327
|
+
if (f.detail) add(`detail:${f.detail}`);
|
|
32527
33328
|
}
|
|
32528
33329
|
if (node.type === "goal") {
|
|
32529
33330
|
const g = node;
|
|
@@ -32538,6 +33339,24 @@ var KnowledgeGraph = class {
|
|
|
32538
33339
|
add(`by:${c.proposedBy}`);
|
|
32539
33340
|
for (const g of c.satisfiesGoals) add(`goal:${g}`);
|
|
32540
33341
|
}
|
|
33342
|
+
return keys;
|
|
33343
|
+
}
|
|
33344
|
+
/** Mutate the index: add a node's id to every set for the given keys. */
|
|
33345
|
+
_addToIndex(node, keys) {
|
|
33346
|
+
for (const key of keys) {
|
|
33347
|
+
let set = this.index.get(key);
|
|
33348
|
+
if (!set) {
|
|
33349
|
+
set = /* @__PURE__ */ new Set();
|
|
33350
|
+
this.index.set(key, set);
|
|
33351
|
+
}
|
|
33352
|
+
set.add(node.id);
|
|
33353
|
+
}
|
|
33354
|
+
}
|
|
33355
|
+
/** Remove a node's id from all index sets for the given keys. */
|
|
33356
|
+
_removeFromIndex(node, keys) {
|
|
33357
|
+
for (const key of keys) {
|
|
33358
|
+
this.index.get(key)?.delete(node.id);
|
|
33359
|
+
}
|
|
32541
33360
|
}
|
|
32542
33361
|
_matches(node, f) {
|
|
32543
33362
|
if (f.type && node.type !== f.type) return false;
|
|
@@ -32587,11 +33406,15 @@ var KnowledgeGraph = class {
|
|
|
32587
33406
|
try {
|
|
32588
33407
|
const parsed = JSON.parse(line);
|
|
32589
33408
|
if (parsed.op === "update") {
|
|
33409
|
+
const oldNode = this.nodes.get(parsed.node.id);
|
|
33410
|
+
if (oldNode) {
|
|
33411
|
+
this._removeFromIndex(oldNode, this._indexKeys(oldNode));
|
|
33412
|
+
}
|
|
32590
33413
|
this.nodes.set(parsed.node.id, parsed.node);
|
|
32591
|
-
this.
|
|
33414
|
+
this._addToIndex(parsed.node, this._indexKeys(parsed.node));
|
|
32592
33415
|
} else {
|
|
32593
33416
|
this.nodes.set(parsed.id, parsed);
|
|
32594
|
-
this.
|
|
33417
|
+
this._addToIndex(parsed, this._indexKeys(parsed));
|
|
32595
33418
|
}
|
|
32596
33419
|
} catch {
|
|
32597
33420
|
}
|
|
@@ -32892,7 +33715,7 @@ var TaskDAG = class {
|
|
|
32892
33715
|
if (visited.has(current)) continue;
|
|
32893
33716
|
visited.add(current);
|
|
32894
33717
|
const node = this.nodes.get(current);
|
|
32895
|
-
if (node) stack.push(...node.
|
|
33718
|
+
if (node) stack.push(...node.deps);
|
|
32896
33719
|
}
|
|
32897
33720
|
return false;
|
|
32898
33721
|
}
|
|
@@ -32995,8 +33818,10 @@ var ConsensusProtocol = class {
|
|
|
32995
33818
|
return this._resolve(changeId, change.votes, eligible);
|
|
32996
33819
|
}
|
|
32997
33820
|
// ── Private ───────────────────────────────────────────────────────────
|
|
32998
|
-
_eligibleVoters(
|
|
32999
|
-
return Array.from(this.voters.keys())
|
|
33821
|
+
_eligibleVoters(change) {
|
|
33822
|
+
return Array.from(this.voters.keys()).filter(
|
|
33823
|
+
(agentId) => agentId !== change.proposedBy
|
|
33824
|
+
);
|
|
33000
33825
|
}
|
|
33001
33826
|
_resolve(changeId, votes, eligible) {
|
|
33002
33827
|
const totalEligible = eligible.length;
|
|
@@ -33041,7 +33866,7 @@ var ConsensusProtocol = class {
|
|
|
33041
33866
|
if (!quorumMet) {
|
|
33042
33867
|
return {
|
|
33043
33868
|
changeId,
|
|
33044
|
-
outcome: "
|
|
33869
|
+
outcome: "quorum_not_met",
|
|
33045
33870
|
votes,
|
|
33046
33871
|
approveCount: approve.length,
|
|
33047
33872
|
rejectCount: reject.length,
|
|
@@ -33418,7 +34243,7 @@ var AutonomousBrain = class {
|
|
|
33418
34243
|
}
|
|
33419
34244
|
return { type: "deny", reason: `Brain LLM failed: ${String(err)}` };
|
|
33420
34245
|
}
|
|
33421
|
-
|
|
34246
|
+
this._recordDecision({
|
|
33422
34247
|
id,
|
|
33423
34248
|
decisionType,
|
|
33424
34249
|
question,
|
|
@@ -33427,6 +34252,7 @@ var AutonomousBrain = class {
|
|
|
33427
34252
|
rationale: result.rationale,
|
|
33428
34253
|
madeBy: "autonomous-brain",
|
|
33429
34254
|
context: JSON.stringify(context)
|
|
34255
|
+
}).catch(() => {
|
|
33430
34256
|
});
|
|
33431
34257
|
if (requiresConsensus) {
|
|
33432
34258
|
this._emit("brain.decision", { id, decisionType, optionId: result.optionId, rationale: result.rationale, consensusRequired: true });
|
|
@@ -33705,10 +34531,16 @@ var TaskAuctioneer = class {
|
|
|
33705
34531
|
maxTasksPerAgent;
|
|
33706
34532
|
minConfidence;
|
|
33707
34533
|
// minimum dispatcher confidence to accept a bid
|
|
34534
|
+
maxBidRetries;
|
|
34535
|
+
// max republished attempts before marking task failed
|
|
33708
34536
|
/** Pending bids keyed by taskId. */
|
|
33709
34537
|
pendingBids = /* @__PURE__ */ new Map();
|
|
33710
34538
|
/** Active bid windows keyed by taskId. */
|
|
33711
34539
|
bidTimers = /* @__PURE__ */ new Map();
|
|
34540
|
+
/** FleetBus subscription disposers, detached in dispose(). */
|
|
34541
|
+
unsubs = [];
|
|
34542
|
+
/** How many times a task has been republished with no bids received. */
|
|
34543
|
+
bidRetryCounts = /* @__PURE__ */ new Map();
|
|
33712
34544
|
/** Agent → current task count (from graph + in-flight). */
|
|
33713
34545
|
agentTaskCounts = /* @__PURE__ */ new Map();
|
|
33714
34546
|
constructor(opts) {
|
|
@@ -33719,8 +34551,26 @@ var TaskAuctioneer = class {
|
|
|
33719
34551
|
this.bidWindowMs = opts.bidWindowMs ?? 3e4;
|
|
33720
34552
|
this.maxTasksPerAgent = opts.maxTasksPerAgent ?? 3;
|
|
33721
34553
|
this.minConfidence = opts.minConfidence ?? 0.3;
|
|
33722
|
-
this.
|
|
33723
|
-
this.fleet?.filter("task:
|
|
34554
|
+
this.maxBidRetries = opts.maxBidRetries ?? 3;
|
|
34555
|
+
const offBid = this.fleet?.filter("task:bid", (e) => this._onBidEvent(e));
|
|
34556
|
+
const offClaimed = this.fleet?.filter("task:claimed", (e) => this._onClaimedEvent(e));
|
|
34557
|
+
if (offBid) this.unsubs.push(offBid);
|
|
34558
|
+
if (offClaimed) this.unsubs.push(offClaimed);
|
|
34559
|
+
}
|
|
34560
|
+
/**
|
|
34561
|
+
* Detach all FleetBus subscriptions and cancel any open bid-window timers.
|
|
34562
|
+
* Call when the owning coordinator stops/restarts so handlers and timers
|
|
34563
|
+
* don't accumulate across cycles.
|
|
34564
|
+
*/
|
|
34565
|
+
dispose() {
|
|
34566
|
+
for (const off of this.unsubs.splice(0)) {
|
|
34567
|
+
try {
|
|
34568
|
+
off();
|
|
34569
|
+
} catch {
|
|
34570
|
+
}
|
|
34571
|
+
}
|
|
34572
|
+
for (const t2 of this.bidTimers.values()) clearTimeout(t2);
|
|
34573
|
+
this.bidTimers.clear();
|
|
33724
34574
|
}
|
|
33725
34575
|
// ── Publish a task ────────────────────────────────────────────────────
|
|
33726
34576
|
/**
|
|
@@ -33730,14 +34580,16 @@ var TaskAuctioneer = class {
|
|
|
33730
34580
|
* If `targetAgent` is specified, the task is assigned directly without auction.
|
|
33731
34581
|
*/
|
|
33732
34582
|
async publishTask(input) {
|
|
34583
|
+
const blockedBy = input.blockedBy ?? [];
|
|
34584
|
+
const hasOpenBlockers = blockedBy.length > 0 && blockedBy.some((id) => this.graph.get(id)?.status !== "done");
|
|
33733
34585
|
const goal = await this.graph.add({
|
|
33734
34586
|
type: "goal",
|
|
33735
34587
|
title: input.title,
|
|
33736
34588
|
description: input.description,
|
|
33737
|
-
status: input.targetAgent ? "in_progress" : "pending",
|
|
34589
|
+
status: input.targetAgent ? "in_progress" : hasOpenBlockers ? "blocked" : "pending",
|
|
33738
34590
|
priority: input.priority ?? "medium",
|
|
33739
34591
|
assignee: input.targetAgent,
|
|
33740
|
-
blockedBy
|
|
34592
|
+
blockedBy,
|
|
33741
34593
|
dependsOn: input.satisfiesGoals ?? [],
|
|
33742
34594
|
createdBy: this.selfAgentId,
|
|
33743
34595
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -33754,6 +34606,12 @@ var TaskAuctioneer = class {
|
|
|
33754
34606
|
});
|
|
33755
34607
|
}
|
|
33756
34608
|
}
|
|
34609
|
+
for (const blockerId of blockedBy) {
|
|
34610
|
+
const blocker = this.graph.get(blockerId);
|
|
34611
|
+
if (blocker && !blocker.children.includes(goal.id)) {
|
|
34612
|
+
await this.graph.update(blockerId, { children: [...blocker.children, goal.id] });
|
|
34613
|
+
}
|
|
34614
|
+
}
|
|
33757
34615
|
if (input.targetAgent) {
|
|
33758
34616
|
await this._assignDirect(goal.id, input.targetAgent);
|
|
33759
34617
|
} else {
|
|
@@ -33865,9 +34723,12 @@ Priority: ${goal.priority}`,
|
|
|
33865
34723
|
status: "done",
|
|
33866
34724
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
33867
34725
|
});
|
|
34726
|
+
this.bidRetryCounts.delete(taskId);
|
|
34727
|
+
this.pendingBids.delete(taskId);
|
|
34728
|
+
this._cancelBidWindow(taskId);
|
|
33868
34729
|
for (const childId of goal.children) {
|
|
33869
34730
|
const child = this.graph.get(childId);
|
|
33870
|
-
if (child) {
|
|
34731
|
+
if (child && child.status === "blocked") {
|
|
33871
34732
|
const allUnblocked = child.blockedBy.every((blockedId) => {
|
|
33872
34733
|
const blocked = this.graph.get(blockedId);
|
|
33873
34734
|
return blocked?.status === "done";
|
|
@@ -33876,6 +34737,7 @@ Priority: ${goal.priority}`,
|
|
|
33876
34737
|
await this.graph.update(childId, { status: "pending", updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
33877
34738
|
const unblockedGoal = this.graph.get(childId);
|
|
33878
34739
|
await this._broadcastTask(unblockedGoal);
|
|
34740
|
+
this._startBidWindow(childId);
|
|
33879
34741
|
}
|
|
33880
34742
|
}
|
|
33881
34743
|
}
|
|
@@ -33901,6 +34763,9 @@ ${_result ?? "No result provided."}`
|
|
|
33901
34763
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
33902
34764
|
result: error
|
|
33903
34765
|
});
|
|
34766
|
+
this.bidRetryCounts.delete(taskId);
|
|
34767
|
+
this.pendingBids.delete(taskId);
|
|
34768
|
+
this._cancelBidWindow(taskId);
|
|
33904
34769
|
this._emit("task:failed", { taskId, agentId, error });
|
|
33905
34770
|
await this._mailboxPublish({
|
|
33906
34771
|
type: "note",
|
|
@@ -33979,7 +34844,7 @@ Assignee: ${agentId}`
|
|
|
33979
34844
|
priority: goal.priority,
|
|
33980
34845
|
tags: goal.tags
|
|
33981
34846
|
});
|
|
33982
|
-
|
|
34847
|
+
this._mailboxPublish({
|
|
33983
34848
|
type: "broadcast",
|
|
33984
34849
|
subject: `[task] ${goal.title} (${goal.priority})`,
|
|
33985
34850
|
body: `New task available: "${goal.title}"
|
|
@@ -33990,6 +34855,7 @@ Task ID: ${goal.id}
|
|
|
33990
34855
|
Tags: ${goal.tags.join(", ") || "none"}
|
|
33991
34856
|
|
|
33992
34857
|
Bid by calling taskAuctioneer.bid("${goal.id}", ...)`
|
|
34858
|
+
}).catch(() => {
|
|
33993
34859
|
});
|
|
33994
34860
|
}
|
|
33995
34861
|
async _mailboxPublish(msg) {
|
|
@@ -34023,9 +34889,10 @@ Bid by calling taskAuctioneer.bid("${goal.id}", ...)`
|
|
|
34023
34889
|
}
|
|
34024
34890
|
_startBidWindow(taskId) {
|
|
34025
34891
|
this._cancelBidWindow(taskId);
|
|
34026
|
-
const timer = setTimeout(
|
|
34892
|
+
const timer = setTimeout(() => {
|
|
34027
34893
|
this.bidTimers.delete(taskId);
|
|
34028
|
-
|
|
34894
|
+
void this._evaluateBids(taskId).catch(() => {
|
|
34895
|
+
});
|
|
34029
34896
|
}, this.bidWindowMs);
|
|
34030
34897
|
this.bidTimers.set(taskId, timer);
|
|
34031
34898
|
}
|
|
@@ -34039,6 +34906,13 @@ Bid by calling taskAuctioneer.bid("${goal.id}", ...)`
|
|
|
34039
34906
|
async _evaluateBids(taskId) {
|
|
34040
34907
|
const bids = this.pendingBids.get(taskId);
|
|
34041
34908
|
if (!bids || bids.length === 0) {
|
|
34909
|
+
const retryCount = (this.bidRetryCounts.get(taskId) ?? 0) + 1;
|
|
34910
|
+
this.bidRetryCounts.set(taskId, retryCount);
|
|
34911
|
+
if (retryCount >= this.maxBidRetries) {
|
|
34912
|
+
await this.fail(taskId, `No bids received after ${this.maxBidRetries} attempts`);
|
|
34913
|
+
this.bidRetryCounts.delete(taskId);
|
|
34914
|
+
return;
|
|
34915
|
+
}
|
|
34042
34916
|
const goal = this.graph.get(taskId);
|
|
34043
34917
|
if (goal) {
|
|
34044
34918
|
await this._broadcastTask(goal);
|
|
@@ -34047,7 +34921,15 @@ Bid by calling taskAuctioneer.bid("${goal.id}", ...)`
|
|
|
34047
34921
|
return;
|
|
34048
34922
|
}
|
|
34049
34923
|
bids.sort((a, b) => b.score - a.score);
|
|
34050
|
-
const winner = bids
|
|
34924
|
+
const winner = bids.find((b) => this._getAgentTaskCount(b.agentId) < this.maxTasksPerAgent);
|
|
34925
|
+
if (!winner) {
|
|
34926
|
+
const goal = this.graph.get(taskId);
|
|
34927
|
+
if (goal) {
|
|
34928
|
+
await this._broadcastTask(goal);
|
|
34929
|
+
this._startBidWindow(taskId);
|
|
34930
|
+
}
|
|
34931
|
+
return;
|
|
34932
|
+
}
|
|
34051
34933
|
await this.claim(taskId, winner.agentId, winner.agentName);
|
|
34052
34934
|
}
|
|
34053
34935
|
async _assignDirect(taskId, agentId) {
|
|
@@ -34094,16 +34976,24 @@ var AutonomousCoordinator = class {
|
|
|
34094
34976
|
selfAgentId;
|
|
34095
34977
|
fleet;
|
|
34096
34978
|
fleetManager;
|
|
34979
|
+
director;
|
|
34097
34980
|
mailbox;
|
|
34098
34981
|
events;
|
|
34982
|
+
onCoordinatorEvent;
|
|
34099
34983
|
running = false;
|
|
34100
34984
|
iterationCount = 0;
|
|
34985
|
+
/** Tasks already handled by _onSubagentTerminated (to avoid double goal:failed on fleet event). */
|
|
34986
|
+
_handledBySubagent = /* @__PURE__ */ new Set();
|
|
34987
|
+
/** FleetBus subscription disposers, detached in dispose(). */
|
|
34988
|
+
unsubs = [];
|
|
34101
34989
|
constructor(opts) {
|
|
34102
34990
|
this.selfAgentId = opts.selfAgentId;
|
|
34103
34991
|
this.fleet = opts.fleet ?? void 0;
|
|
34104
34992
|
this.fleetManager = opts.fleetManager ?? void 0;
|
|
34993
|
+
this.director = opts.director ?? void 0;
|
|
34105
34994
|
this.mailbox = opts.mailbox ?? void 0;
|
|
34106
34995
|
this.events = opts.events ?? void 0;
|
|
34996
|
+
this.onCoordinatorEvent = opts.onCoordinatorEvent;
|
|
34107
34997
|
this.graph = new KnowledgeGraph(opts.sessionDir);
|
|
34108
34998
|
this.dag = new TaskDAG();
|
|
34109
34999
|
this.auction = new TaskAuctioneer({
|
|
@@ -34139,9 +35029,19 @@ var AutonomousCoordinator = class {
|
|
|
34139
35029
|
this.dag.onEvent((event) => {
|
|
34140
35030
|
this._onDagEvent(event);
|
|
34141
35031
|
});
|
|
34142
|
-
this.fleet?.filter("subagent.
|
|
35032
|
+
const offCompleted = this.fleet?.filter("subagent.completed", (e) => {
|
|
34143
35033
|
this._onSubagentTerminated(e);
|
|
34144
35034
|
});
|
|
35035
|
+
if (offCompleted) this.unsubs.push(offCompleted);
|
|
35036
|
+
const offFailed = this.fleet?.filter("task:failed", (e) => {
|
|
35037
|
+
const payload = e.payload;
|
|
35038
|
+
const taskId = payload?.taskId;
|
|
35039
|
+
if (!taskId || this._handledBySubagent.has(taskId)) return;
|
|
35040
|
+
this._handledBySubagent.add(taskId);
|
|
35041
|
+
this._emit({ type: "goal:failed", goalId: taskId, text: payload?.error ?? "Task failed" });
|
|
35042
|
+
});
|
|
35043
|
+
if (offFailed) this.unsubs.push(offFailed);
|
|
35044
|
+
this._emit({ type: "coordinator:mode", mode: this.fleet ? "fleet" : "standalone" });
|
|
34145
35045
|
}
|
|
34146
35046
|
// ── Public API ───────────────────────────────────────────────────────
|
|
34147
35047
|
/**
|
|
@@ -34155,11 +35055,13 @@ var AutonomousCoordinator = class {
|
|
|
34155
35055
|
const maxIterations = opts.maxIterations ?? 100;
|
|
34156
35056
|
const goal = opts.goal ?? "Improve the codebase";
|
|
34157
35057
|
const maxCost = opts.maxCostUsd;
|
|
34158
|
-
await this.graph.load();
|
|
34159
35058
|
try {
|
|
35059
|
+
await this.graph.load();
|
|
34160
35060
|
const goalConfigs = await this._decomposeGoal(goal);
|
|
34161
35061
|
for (const g of goalConfigs) {
|
|
34162
|
-
await this.auction.publishTask(g);
|
|
35062
|
+
const goalId = await this.auction.publishTask(g);
|
|
35063
|
+
this.dag.addNode(goalId, g.description, []);
|
|
35064
|
+
this._emit({ type: "goal:added", goalId, title: g.title, text: g.description });
|
|
34163
35065
|
}
|
|
34164
35066
|
while (this.running) {
|
|
34165
35067
|
this.iterationCount++;
|
|
@@ -34186,6 +35088,7 @@ var AutonomousCoordinator = class {
|
|
|
34186
35088
|
const blocked = this.dag.getBlocked();
|
|
34187
35089
|
if (blocked.length > 0 && this.dag.hasDeadlock()) {
|
|
34188
35090
|
(this.events?.emit)("autonomous:deadlock", { blocked });
|
|
35091
|
+
this._emit({ type: "deadlock:detected", goalId: blocked[0]?.id ?? "", text: `Deadlock detected: ${blocked.map((n) => n.id).join(", ")}` });
|
|
34189
35092
|
this.running = false;
|
|
34190
35093
|
}
|
|
34191
35094
|
break;
|
|
@@ -34202,7 +35105,15 @@ var AutonomousCoordinator = class {
|
|
|
34202
35105
|
}
|
|
34203
35106
|
const pendingChanges = this.changes.getPendingReviews();
|
|
34204
35107
|
for (const change of pendingChanges) {
|
|
34205
|
-
|
|
35108
|
+
try {
|
|
35109
|
+
await this._handlePendingChange(change);
|
|
35110
|
+
} catch (err) {
|
|
35111
|
+
this._emit({
|
|
35112
|
+
type: "goal:failed",
|
|
35113
|
+
goalId: change.id,
|
|
35114
|
+
text: `Consensus handling failed: ${err instanceof Error ? err.message : String(err)}`
|
|
35115
|
+
});
|
|
35116
|
+
}
|
|
34206
35117
|
}
|
|
34207
35118
|
}
|
|
34208
35119
|
} finally {
|
|
@@ -34212,7 +35123,26 @@ var AutonomousCoordinator = class {
|
|
|
34212
35123
|
}
|
|
34213
35124
|
/** Stop the autonomous loop. */
|
|
34214
35125
|
stop() {
|
|
35126
|
+
if (!this.running) return;
|
|
34215
35127
|
this.running = false;
|
|
35128
|
+
console.error(`[AutonomousCoordinator] stop signal received \u2014 shutting down (iteration ${this.iterationCount})`);
|
|
35129
|
+
}
|
|
35130
|
+
/**
|
|
35131
|
+
* Tear down the coordinator for good: stop the loop and detach all FleetBus
|
|
35132
|
+
* subscriptions (this coordinator's + the auctioneer's) plus any open bid
|
|
35133
|
+
* timers. Call this when discarding the instance (e.g. `/coordinator stop`
|
|
35134
|
+
* that recreates a fresh coordinator on the next start) so handlers and
|
|
35135
|
+
* timers don't accumulate across cycles. `stop()` only pauses the loop.
|
|
35136
|
+
*/
|
|
35137
|
+
dispose() {
|
|
35138
|
+
this.stop();
|
|
35139
|
+
for (const off of this.unsubs.splice(0)) {
|
|
35140
|
+
try {
|
|
35141
|
+
off();
|
|
35142
|
+
} catch {
|
|
35143
|
+
}
|
|
35144
|
+
}
|
|
35145
|
+
this.auction.dispose();
|
|
34216
35146
|
}
|
|
34217
35147
|
/** Get a stats snapshot. */
|
|
34218
35148
|
getStats() {
|
|
@@ -34267,6 +35197,7 @@ var AutonomousCoordinator = class {
|
|
|
34267
35197
|
body: `**${input.category}**${input.file ? ` in ${input.file}${input.line ? `:${input.line}` : ""}` : ""}
|
|
34268
35198
|
${input.detail}`
|
|
34269
35199
|
});
|
|
35200
|
+
this._emit({ type: "knowledge:added", knowledgeId: fact.id, title: input.subject, text: input.detail });
|
|
34270
35201
|
return fact;
|
|
34271
35202
|
}
|
|
34272
35203
|
// ── Goal creation helpers ────────────────────────────────────────────
|
|
@@ -34279,7 +35210,10 @@ ${input.detail}`
|
|
|
34279
35210
|
title: input.title,
|
|
34280
35211
|
description: input.description,
|
|
34281
35212
|
priority: resolvedPriority,
|
|
34282
|
-
...input.tags ? { tags: input.tags } : {}
|
|
35213
|
+
...input.tags ? { tags: input.tags } : {},
|
|
35214
|
+
// Mirror the dependency edges into the auction so blocked goals aren't
|
|
35215
|
+
// biddable until their deps complete (the DAG tracks the same edges).
|
|
35216
|
+
...input.deps && input.deps.length > 0 ? { blockedBy: input.deps } : {}
|
|
34283
35217
|
});
|
|
34284
35218
|
const goal = this.graph.get(goalId);
|
|
34285
35219
|
for (const depId of input.deps ?? []) {
|
|
@@ -34320,30 +35254,34 @@ ${input.detail}`
|
|
|
34320
35254
|
async _processGoal(goalId) {
|
|
34321
35255
|
const ready = this.dag.getReady();
|
|
34322
35256
|
if (ready.length === 0) return;
|
|
34323
|
-
const
|
|
34324
|
-
this.dag.start(
|
|
34325
|
-
const
|
|
34326
|
-
if (!
|
|
34327
|
-
|
|
34328
|
-
|
|
34329
|
-
|
|
34330
|
-
|
|
34331
|
-
|
|
35257
|
+
const dagNode = ready.find((n) => n.id === goalId) ?? ready[0];
|
|
35258
|
+
this.dag.start(dagNode.id, "auctioneer");
|
|
35259
|
+
const goalNode = this.graph.get(goalId);
|
|
35260
|
+
if (!goalNode) return;
|
|
35261
|
+
const title = goalNode.title || dagNode.description;
|
|
35262
|
+
const taskId = await this.auction.publishTask({
|
|
35263
|
+
title,
|
|
35264
|
+
description: goalNode.description,
|
|
35265
|
+
priority: this._dagPriorityToGoal(dagNode.priority),
|
|
35266
|
+
tags: dagNode.tags
|
|
35267
|
+
});
|
|
35268
|
+
this._emit({ type: "task:ready", goalId, taskId, title });
|
|
35269
|
+
if (this.director) {
|
|
35270
|
+
const config = {
|
|
35271
|
+
name: `worker-${goalId.slice(0, 8)}`,
|
|
35272
|
+
role: "general",
|
|
35273
|
+
maxIterations: 100,
|
|
35274
|
+
timeoutMs: 6e5
|
|
35275
|
+
// 10 minutes per goal
|
|
35276
|
+
};
|
|
35277
|
+
const subagentId = await this.director.spawn(config);
|
|
35278
|
+
await this.auction.claim(taskId, subagentId, config.name);
|
|
35279
|
+
await this.director.assign({
|
|
35280
|
+
id: goalId,
|
|
35281
|
+
subagentId,
|
|
35282
|
+
description: goalNode.description
|
|
34332
35283
|
});
|
|
34333
35284
|
}
|
|
34334
|
-
await this._waitForClaim(next.id);
|
|
34335
|
-
}
|
|
34336
|
-
async _waitForClaim(taskId) {
|
|
34337
|
-
const maxWait = 6e4;
|
|
34338
|
-
const pollInterval = 2e3;
|
|
34339
|
-
const start = Date.now();
|
|
34340
|
-
while (Date.now() - start < maxWait) {
|
|
34341
|
-
const goal = this.graph.get(taskId);
|
|
34342
|
-
if (goal?.status === "in_progress" || goal?.status === "done") {
|
|
34343
|
-
return;
|
|
34344
|
-
}
|
|
34345
|
-
await this._sleep(pollInterval);
|
|
34346
|
-
}
|
|
34347
35285
|
}
|
|
34348
35286
|
async _handlePendingChange(change) {
|
|
34349
35287
|
const result = this.consensus.getStatus(change.id);
|
|
@@ -34357,6 +35295,17 @@ ${input.detail}`
|
|
|
34357
35295
|
);
|
|
34358
35296
|
if (voteResult.outcome === "approved") {
|
|
34359
35297
|
await this.changes.markApplied(change.id, (/* @__PURE__ */ new Date()).toISOString());
|
|
35298
|
+
this._emit({ type: "consensus:reached", goalId: change.id, text: "Change approved and applied" });
|
|
35299
|
+
}
|
|
35300
|
+
} else {
|
|
35301
|
+
const voteResult = await this.consensus.castVote(
|
|
35302
|
+
change.id,
|
|
35303
|
+
this.selfAgentId,
|
|
35304
|
+
"reject",
|
|
35305
|
+
`Quality gate failed: ${change.qualityGate.checks.map((c) => `${c.name}=${c.passed}`).join(", ")}`
|
|
35306
|
+
);
|
|
35307
|
+
if (voteResult.outcome === "rejected" || voteResult.outcome === "vetoed") {
|
|
35308
|
+
this._emit({ type: "consensus:reached", goalId: change.id, text: "Change rejected by quality gate" });
|
|
34360
35309
|
}
|
|
34361
35310
|
}
|
|
34362
35311
|
}
|
|
@@ -34367,10 +35316,6 @@ ${input.detail}`
|
|
|
34367
35316
|
(this.events?.emit)("autonomous:task_ready", { taskId: event.nodeId, description: node.description });
|
|
34368
35317
|
}
|
|
34369
35318
|
}
|
|
34370
|
-
if (event.type === "deadlock") {
|
|
34371
|
-
(this.events?.emit)("autonomous:deadlock", { blocked: event.blocked });
|
|
34372
|
-
this.running = false;
|
|
34373
|
-
}
|
|
34374
35319
|
if (event.type === "graph:done") {
|
|
34375
35320
|
(this.events?.emit)("autonomous:all_done", this.getStats());
|
|
34376
35321
|
}
|
|
@@ -34378,13 +35323,16 @@ ${input.detail}`
|
|
|
34378
35323
|
_onSubagentTerminated(e) {
|
|
34379
35324
|
const payload = e.payload;
|
|
34380
35325
|
const subagentId = payload?.subagentId ?? e.subagentId;
|
|
34381
|
-
const stopReason = payload?.stopReason ?? "unknown";
|
|
35326
|
+
const stopReason = payload?.stopReason ?? (payload?.status === "ok" ? "end_turn" : payload?.status ?? "unknown");
|
|
34382
35327
|
const tasks = this.auction.getTasksForAgent(subagentId);
|
|
34383
35328
|
for (const task of tasks) {
|
|
35329
|
+
this._handledBySubagent.add(task.id);
|
|
34384
35330
|
if (stopReason === "end_turn") {
|
|
34385
35331
|
void this.auction.complete(task.id, "Subagent completed successfully");
|
|
35332
|
+
this._emit({ type: "task:completed", goalId: task.id, taskId: task.id, text: "Subagent completed successfully" });
|
|
34386
35333
|
} else {
|
|
34387
35334
|
void this.auction.fail(task.id, `Subagent terminated: ${stopReason}`);
|
|
35335
|
+
this._emit({ type: "goal:failed", goalId: task.id, text: `Subagent terminated: ${stopReason}` });
|
|
34388
35336
|
}
|
|
34389
35337
|
}
|
|
34390
35338
|
}
|
|
@@ -34398,6 +35346,10 @@ ${input.detail}`
|
|
|
34398
35346
|
}
|
|
34399
35347
|
_buildVoters() {
|
|
34400
35348
|
return [
|
|
35349
|
+
// The coordinator itself casts the quality-gate auto-vote in
|
|
35350
|
+
// _handlePendingChange — it MUST be a registered, eligible voter or
|
|
35351
|
+
// castVote throws "unknown voter" and tears down the run() loop.
|
|
35352
|
+
{ agentId: this.selfAgentId, agentName: "Coordinator", role: "coordinator", weight: 1 },
|
|
34401
35353
|
{ agentId: "critic", agentName: "Critic", role: "critic", weight: 2, veto: true },
|
|
34402
35354
|
{ agentId: "bug-hunter", agentName: "Bug Hunter", role: "bug-hunter", weight: 1.5 },
|
|
34403
35355
|
{ agentId: "security-scanner", agentName: "Security Scanner", role: "security-scanner", weight: 1.5 },
|
|
@@ -34435,8 +35387,9 @@ ${input.detail}`
|
|
|
34435
35387
|
} catch {
|
|
34436
35388
|
}
|
|
34437
35389
|
}
|
|
34438
|
-
|
|
34439
|
-
|
|
35390
|
+
/** Emit a CoordinatorEvent to the subscriber (e.g. TUI panel timeline). */
|
|
35391
|
+
_emit(event) {
|
|
35392
|
+
this.onCoordinatorEvent?.(event);
|
|
34440
35393
|
}
|
|
34441
35394
|
};
|
|
34442
35395
|
function createMcpControlTool(opts) {
|
|
@@ -34999,13 +35952,13 @@ function createAgentToolHandler(a) {
|
|
|
34999
35952
|
}
|
|
35000
35953
|
}
|
|
35001
35954
|
function waitForConfirm(info) {
|
|
35002
|
-
return new Promise((
|
|
35955
|
+
return new Promise((resolve15) => {
|
|
35003
35956
|
a.events.emit("tool.confirm_needed", {
|
|
35004
35957
|
tool: info.tool,
|
|
35005
35958
|
input: info.input,
|
|
35006
35959
|
toolUseId: info.toolUseId,
|
|
35007
35960
|
suggestedPattern: info.suggestedPattern,
|
|
35008
|
-
resolve:
|
|
35961
|
+
resolve: resolve15
|
|
35009
35962
|
});
|
|
35010
35963
|
});
|
|
35011
35964
|
}
|
|
@@ -35433,7 +36386,7 @@ async function injectPendingMailboxMessages(checkMailbox, foldFn, a) {
|
|
|
35433
36386
|
try {
|
|
35434
36387
|
messages = await checkMailbox();
|
|
35435
36388
|
} catch {
|
|
35436
|
-
return;
|
|
36389
|
+
return { interrupt: false };
|
|
35437
36390
|
}
|
|
35438
36391
|
for (const m of messages) {
|
|
35439
36392
|
a.events.emit("mailbox.received", {
|
|
@@ -35443,14 +36396,22 @@ async function injectPendingMailboxMessages(checkMailbox, foldFn, a) {
|
|
|
35443
36396
|
subject: m.subject
|
|
35444
36397
|
});
|
|
35445
36398
|
}
|
|
35446
|
-
if (messages.length === 0) return;
|
|
35447
|
-
|
|
35448
|
-
|
|
35449
|
-
|
|
35450
|
-
|
|
35451
|
-
|
|
35452
|
-
)
|
|
36399
|
+
if (messages.length === 0) return { interrupt: false };
|
|
36400
|
+
const control = messages.filter((m) => m.type === "control");
|
|
36401
|
+
const content = messages.filter((m) => m.type !== "control");
|
|
36402
|
+
if (content.length > 0) {
|
|
36403
|
+
try {
|
|
36404
|
+
foldFn(buildMailboxBlock(content));
|
|
36405
|
+
} catch (err) {
|
|
36406
|
+
(a.logger.debug ?? console.debug)?.(
|
|
36407
|
+
`mailbox: failed to fold messages: ${toErrorMessage(err)}`
|
|
36408
|
+
);
|
|
36409
|
+
}
|
|
35453
36410
|
}
|
|
36411
|
+
const interruptMsg = control.find(
|
|
36412
|
+
(m) => /\b(interrupt|stop|halt|abort|cancel)\b/i.test(`${m.subject} ${m.body}`)
|
|
36413
|
+
);
|
|
36414
|
+
return interruptMsg ? { interrupt: true, interruptReason: interruptMsg.body || interruptMsg.subject || "operator interrupt" } : { interrupt: false };
|
|
35454
36415
|
}
|
|
35455
36416
|
|
|
35456
36417
|
// src/mailbox-attach.ts
|
|
@@ -35516,12 +36477,12 @@ function attachMailboxCheckerInner(a, source) {
|
|
|
35516
36477
|
// src/core/iteration-limit.ts
|
|
35517
36478
|
function requestLimitExtension(opts) {
|
|
35518
36479
|
const { events, currentIterations, currentLimit, autoExtend, timeoutMs = 3e4 } = opts;
|
|
35519
|
-
return new Promise((
|
|
36480
|
+
return new Promise((resolve15) => {
|
|
35520
36481
|
let resolved = false;
|
|
35521
36482
|
const timerFired = () => {
|
|
35522
36483
|
if (!resolved) {
|
|
35523
36484
|
resolved = true;
|
|
35524
|
-
|
|
36485
|
+
resolve15(0);
|
|
35525
36486
|
}
|
|
35526
36487
|
};
|
|
35527
36488
|
const timer = setTimeout(timerFired, timeoutMs);
|
|
@@ -35530,14 +36491,14 @@ function requestLimitExtension(opts) {
|
|
|
35530
36491
|
if (!resolved) {
|
|
35531
36492
|
resolved = true;
|
|
35532
36493
|
clearTimeout(timer);
|
|
35533
|
-
|
|
36494
|
+
resolve15(0);
|
|
35534
36495
|
}
|
|
35535
36496
|
};
|
|
35536
36497
|
const grant = (extra) => {
|
|
35537
36498
|
if (!resolved) {
|
|
35538
36499
|
resolved = true;
|
|
35539
36500
|
clearTimeout(timer);
|
|
35540
|
-
|
|
36501
|
+
resolve15(Math.max(0, extra));
|
|
35541
36502
|
}
|
|
35542
36503
|
};
|
|
35543
36504
|
events.emit("iteration.limit_reached", {
|
|
@@ -35551,7 +36512,7 @@ function requestLimitExtension(opts) {
|
|
|
35551
36512
|
if (!resolved) {
|
|
35552
36513
|
resolved = true;
|
|
35553
36514
|
clearTimeout(timer);
|
|
35554
|
-
|
|
36515
|
+
resolve15(100);
|
|
35555
36516
|
}
|
|
35556
36517
|
});
|
|
35557
36518
|
}
|
|
@@ -35736,12 +36697,20 @@ function createAgentLoopHandler(a, handlers) {
|
|
|
35736
36697
|
a.events.emit("iteration.started", { ctx: a.ctx, index: i });
|
|
35737
36698
|
injectPendingBtwNotes();
|
|
35738
36699
|
injectQueueAwareness();
|
|
35739
|
-
await injectPendingMailboxMessages(
|
|
35740
|
-
|
|
35741
|
-
|
|
35742
|
-
|
|
35743
|
-
|
|
35744
|
-
|
|
36700
|
+
const mailboxResult = await injectPendingMailboxMessages(
|
|
36701
|
+
checkMailbox,
|
|
36702
|
+
foldBlockIntoConversation,
|
|
36703
|
+
{
|
|
36704
|
+
// Cast to the broad parameter type — injectPendingMailboxMessages only
|
|
36705
|
+
// calls emit('mailbox.received', ...) and uses logger.debug optionally.
|
|
36706
|
+
events: a.events,
|
|
36707
|
+
logger: a.logger
|
|
36708
|
+
}
|
|
36709
|
+
);
|
|
36710
|
+
if (mailboxResult.interrupt) {
|
|
36711
|
+
const reason = `interrupted: ${mailboxResult.interruptReason ?? "operator request"}`;
|
|
36712
|
+
return { status: "aborted", iterations, abortReason: reason, finalText };
|
|
36713
|
+
}
|
|
35745
36714
|
const req = await handlers.response.buildAndRunRequestPipeline(opts);
|
|
35746
36715
|
const preFlight = estimateRequestTokens(
|
|
35747
36716
|
req.messages,
|
|
@@ -36182,13 +37151,13 @@ async function runShellHook(spec, input, logger) {
|
|
|
36182
37151
|
logger?.warn?.(`hook rejected: command not in allowlist: ${spec.command}`);
|
|
36183
37152
|
return null;
|
|
36184
37153
|
}
|
|
36185
|
-
return await new Promise((
|
|
37154
|
+
return await new Promise((resolve15) => {
|
|
36186
37155
|
let settled = false;
|
|
36187
37156
|
const done = (v) => {
|
|
36188
37157
|
if (settled) return;
|
|
36189
37158
|
settled = true;
|
|
36190
37159
|
clearTimeout(timer);
|
|
36191
|
-
|
|
37160
|
+
resolve15(v);
|
|
36192
37161
|
};
|
|
36193
37162
|
let child;
|
|
36194
37163
|
try {
|
|
@@ -36201,7 +37170,7 @@ async function runShellHook(spec, input, logger) {
|
|
|
36201
37170
|
});
|
|
36202
37171
|
} catch (err2) {
|
|
36203
37172
|
logger?.warn?.(`hook spawn failed: ${toErrorMessage(err2)}`);
|
|
36204
|
-
return
|
|
37173
|
+
return resolve15(null);
|
|
36205
37174
|
}
|
|
36206
37175
|
const timer = setTimeout(() => {
|
|
36207
37176
|
logger?.warn?.(`hook command timed out after ${timeoutMs}ms: ${spec.command}`);
|
|
@@ -36456,6 +37425,12 @@ function flagsToConfigPatch(flags) {
|
|
|
36456
37425
|
patch.features ??= {};
|
|
36457
37426
|
patch.features.tokenSavingMode = true;
|
|
36458
37427
|
}
|
|
37428
|
+
if (typeof flags["token-saving-tier"] === "string") {
|
|
37429
|
+
patch.features ??= {};
|
|
37430
|
+
patch.features.tokenSavingMode = normalizeTokenSavingTier(
|
|
37431
|
+
flags["token-saving-tier"]
|
|
37432
|
+
);
|
|
37433
|
+
}
|
|
36459
37434
|
return patch;
|
|
36460
37435
|
}
|
|
36461
37436
|
async function writeProjectMeta(paths, projectRoot) {
|
|
@@ -36687,8 +37662,8 @@ var InputBuilder = class {
|
|
|
36687
37662
|
async registerFile(input) {
|
|
36688
37663
|
const ref = await this.store.add({ ...input, kind: "file" });
|
|
36689
37664
|
this.refs.push(ref);
|
|
36690
|
-
const
|
|
36691
|
-
return `[file:${
|
|
37665
|
+
const path44 = ref.meta.filename ?? ref.meta.label ?? String(ref.seq);
|
|
37666
|
+
return `[file:${path44}]`;
|
|
36692
37667
|
}
|
|
36693
37668
|
/**
|
|
36694
37669
|
* Whether `appendPaste(text)` would collapse the text to a placeholder
|
|
@@ -36782,26 +37757,30 @@ Never silently skip a failure \u2014 always report it, even when you choose not
|
|
|
36782
37757
|
|
|
36783
37758
|
## After-task suggestions
|
|
36784
37759
|
|
|
36785
|
-
**You are the leader agent.** After completing a significant task, end your
|
|
36786
|
-
response with 2\u20134 suggested next
|
|
36787
|
-
|
|
36788
|
-
|
|
37760
|
+
**You are the leader agent.** After completing a significant task, you MAY end your
|
|
37761
|
+
response with 2\u20134 suggested next prompt options in a \`<next_steps>\` block.
|
|
37762
|
+
The \`/next 1\`, \`/next 2\`, \`/next 1 2 3\` shortcuts let the user select one
|
|
37763
|
+
and continue in a new agent session.
|
|
37764
|
+
|
|
37765
|
+
Format:
|
|
36789
37766
|
|
|
36790
37767
|
\`\`\`
|
|
36791
37768
|
<next_steps>
|
|
36792
|
-
1.
|
|
36793
|
-
2.
|
|
36794
|
-
3.
|
|
37769
|
+
1. Prompt option 1 \u2014 a concrete next action phrased as what to type
|
|
37770
|
+
2. Prompt option 2
|
|
37771
|
+
3. Prompt option 3 (optional)
|
|
36795
37772
|
</next_steps>
|
|
36796
37773
|
\`\`\`
|
|
36797
37774
|
|
|
36798
37775
|
Rules:
|
|
36799
|
-
- Each
|
|
36800
|
-
|
|
36801
|
-
-
|
|
37776
|
+
- Each item is a **prompt the user can type** \u2014 not an instruction to a human.
|
|
37777
|
+
Write "pnpm test" not "Run the test suite."
|
|
37778
|
+
- Human-only actions (e.g., "open DevTools") go outside the tag as plain text,
|
|
37779
|
+
not inside \`<next_steps>\`.
|
|
37780
|
+
- Items marked \`auto="true"\` must include the exact input content for copy-paste.
|
|
36802
37781
|
- Order by priority. Keep each suggestion to one line.
|
|
36803
37782
|
- Skip during multi-step operations \u2014 only show after completion.
|
|
36804
|
-
- If nothing is pending,
|
|
37783
|
+
- If nothing is pending, omit the tag entirely.
|
|
36805
37784
|
|
|
36806
37785
|
**After a significant task**, also post a status update to the inter-agent
|
|
36807
37786
|
mailbox so other agents in the fleet can discover what you finished and
|
|
@@ -36835,6 +37814,43 @@ var DefaultSystemPromptBuilder = class {
|
|
|
36835
37814
|
_lastOnlineAgents;
|
|
36836
37815
|
/** Cached full buildToolUsage output — keyed by tools array + online agents refs. */
|
|
36837
37816
|
_toolsUsageCache;
|
|
37817
|
+
/**
|
|
37818
|
+
* Normalizes `tokenSavingMode` to a boolean for backward-compatible boolean checks.
|
|
37819
|
+
* - `undefined` / `false` / `'off'` → false
|
|
37820
|
+
* - `true` / any tier string other than `'off'` → true
|
|
37821
|
+
*/
|
|
37822
|
+
get isCompact() {
|
|
37823
|
+
const val = this.opts.tokenSavingMode;
|
|
37824
|
+
if (!val) return false;
|
|
37825
|
+
if (typeof val === "boolean") return val;
|
|
37826
|
+
return val !== "off";
|
|
37827
|
+
}
|
|
37828
|
+
/** Exposes the normalized `TokenSavingTier` for tier-aware guidance decisions. */
|
|
37829
|
+
get tier() {
|
|
37830
|
+
const val = this.opts.tokenSavingMode;
|
|
37831
|
+
if (typeof val === "string") return val;
|
|
37832
|
+
if (val === true) return "medium";
|
|
37833
|
+
return "off";
|
|
37834
|
+
}
|
|
37835
|
+
/**
|
|
37836
|
+
* Returns the max tool description length for the current tier.
|
|
37837
|
+
* Per the design doc: off=80, minimal=40, light=50, medium=60, aggressive=70.
|
|
37838
|
+
*/
|
|
37839
|
+
toolDescLimit() {
|
|
37840
|
+
switch (this.tier) {
|
|
37841
|
+
case "minimal":
|
|
37842
|
+
return 40;
|
|
37843
|
+
case "light":
|
|
37844
|
+
return 50;
|
|
37845
|
+
case "medium":
|
|
37846
|
+
return 60;
|
|
37847
|
+
case "aggressive":
|
|
37848
|
+
return 70;
|
|
37849
|
+
case "off":
|
|
37850
|
+
default:
|
|
37851
|
+
return 80;
|
|
37852
|
+
}
|
|
37853
|
+
}
|
|
36838
37854
|
async build(ctx) {
|
|
36839
37855
|
this._lastBuildTools = ctx.tools;
|
|
36840
37856
|
if (this.opts.skillLoader && !this.skillCache) {
|
|
@@ -36933,13 +37949,13 @@ var DefaultSystemPromptBuilder = class {
|
|
|
36933
37949
|
if (!planPath) return "";
|
|
36934
37950
|
let raw;
|
|
36935
37951
|
try {
|
|
36936
|
-
const
|
|
36937
|
-
if (this._planCache && this._planCache.path === planPath && this._planCache.mtimeMs ===
|
|
37952
|
+
const stat14 = await fsp3.stat(planPath);
|
|
37953
|
+
if (this._planCache && this._planCache.path === planPath && this._planCache.mtimeMs === stat14.mtimeMs) {
|
|
36938
37954
|
return this._planCache.text;
|
|
36939
37955
|
}
|
|
36940
37956
|
raw = await fsp3.readFile(planPath, "utf8");
|
|
36941
37957
|
const text = this._formatPlan(raw);
|
|
36942
|
-
this._planCache = { path: planPath, mtimeMs:
|
|
37958
|
+
this._planCache = { path: planPath, mtimeMs: stat14.mtimeMs, text };
|
|
36943
37959
|
return text;
|
|
36944
37960
|
} catch {
|
|
36945
37961
|
this._planCache = void 0;
|
|
@@ -36988,12 +38004,13 @@ var DefaultSystemPromptBuilder = class {
|
|
|
36988
38004
|
}
|
|
36989
38005
|
}
|
|
36990
38006
|
const lines = ["## Tool usage"];
|
|
38007
|
+
const descLimit = this.toolDescLimit();
|
|
36991
38008
|
for (const [cat, catTools] of byCat) {
|
|
36992
38009
|
lines.push(`
|
|
36993
38010
|
### ${cat}`);
|
|
36994
38011
|
for (const t2 of catTools) {
|
|
36995
38012
|
const hint = t2.usageHint ?? t2.description;
|
|
36996
|
-
const desc =
|
|
38013
|
+
const desc = hint.length > descLimit ? hint.slice(0, hint.indexOf(".", 20) + 1 || descLimit) + (hint.length > descLimit ? "\u2026" : "") : hint.trim();
|
|
36997
38014
|
lines.push(`- **${t2.name}** \u2014 ${desc}`);
|
|
36998
38015
|
}
|
|
36999
38016
|
}
|
|
@@ -37006,7 +38023,7 @@ var DefaultSystemPromptBuilder = class {
|
|
|
37006
38023
|
${hint.trim()}`);
|
|
37007
38024
|
}
|
|
37008
38025
|
}
|
|
37009
|
-
if (
|
|
38026
|
+
if (this.tier !== "minimal") {
|
|
37010
38027
|
lines.push(`
|
|
37011
38028
|
## Common patterns
|
|
37012
38029
|
|
|
@@ -37026,7 +38043,7 @@ When unsure about a file's current state, read it first rather than assuming.`);
|
|
|
37026
38043
|
return Array.isArray(role) ? role.filter((r) => typeof r === "string") : [];
|
|
37027
38044
|
})();
|
|
37028
38045
|
const roleList = enumValues.length > 0 ? enumValues.join(", ") : "(no roster configured)";
|
|
37029
|
-
if (this.
|
|
38046
|
+
if (this.tier === "minimal") ; else if (this.tier === "light" || this.tier === "medium") {
|
|
37030
38047
|
lines.push(`## Delegation
|
|
37031
38048
|
|
|
37032
38049
|
Use \`delegate\` to hand work to a subagent (roles: ${roleList}).`);
|
|
@@ -37099,9 +38116,9 @@ one by one, roll up results), use \`spawn_subagent\` + \`assign_task\` +
|
|
|
37099
38116
|
const hasMailbox = tools.some(
|
|
37100
38117
|
(t2) => t2.name === "mailbox" || t2.name === "mail_send" || t2.name === "mail_inbox"
|
|
37101
38118
|
);
|
|
37102
|
-
if (hasMailbox) {
|
|
38119
|
+
if (hasMailbox && this.tier !== "minimal") {
|
|
37103
38120
|
const onlineAgentsInfo = this.renderOnlineAgents(ctx.onlineAgents);
|
|
37104
|
-
if (this.
|
|
38121
|
+
if (this.tier === "light" || this.tier === "medium") {
|
|
37105
38122
|
lines.push(`
|
|
37106
38123
|
## Inter-agent mailbox${onlineAgentsInfo}
|
|
37107
38124
|
|
|
@@ -37179,9 +38196,19 @@ To catch up explicitly:
|
|
|
37179
38196
|
}
|
|
37180
38197
|
const hasMcpControl = tools.some((t2) => t2.name === "mcp_control");
|
|
37181
38198
|
const hasMcpUse = tools.some((t2) => t2.name === "mcp_use");
|
|
37182
|
-
if (hasMcpControl
|
|
37183
|
-
if (
|
|
37184
|
-
lines.push(
|
|
38199
|
+
if (hasMcpControl) {
|
|
38200
|
+
if (this.tier === "minimal" || this.tier === "light") {
|
|
38201
|
+
lines.push(
|
|
38202
|
+
hasMcpUse ? `
|
|
38203
|
+
## MCP tools (lazy-loaded)
|
|
38204
|
+
|
|
38205
|
+
Use \`mcp_use({ server: "<name>", tool: "<bare-tool>", input: { ... } })\` to activate and call MCP tools.` : `
|
|
38206
|
+
## MCP tools (lazy-loaded)
|
|
38207
|
+
|
|
38208
|
+
Use \`mcp_control({ action: "list" })\` to see available servers, \`mcp_control({ action: "activate", server: "<name>" })\` to register tools.`
|
|
38209
|
+
);
|
|
38210
|
+
} else {
|
|
38211
|
+
lines.push(hasMcpUse ? `
|
|
37185
38212
|
## MCP tools (lazy-loaded)
|
|
37186
38213
|
|
|
37187
38214
|
MCP server tools are NOT registered by default in token-saving mode to keep
|
|
@@ -37200,9 +38227,7 @@ deactivates \u2014 all in one call. No need to track activate/deactivate state.
|
|
|
37200
38227
|
4. \`mcp_control({ action: "deactivate", server: "<name>" })\` \u2014 clean up
|
|
37201
38228
|
|
|
37202
38229
|
Activation/deactivation is ephemeral (no config writes) and does NOT affect
|
|
37203
|
-
the server connection \u2014 only tool visibility changes.`
|
|
37204
|
-
} else {
|
|
37205
|
-
lines.push(`
|
|
38230
|
+
the server connection \u2014 only tool visibility changes.` : `
|
|
37206
38231
|
## MCP tools (lazy-loaded)
|
|
37207
38232
|
|
|
37208
38233
|
MCP server tools are NOT registered by default in token-saving mode to keep
|
|
@@ -37220,10 +38245,16 @@ the server connection \u2014 only tool visibility changes.`);
|
|
|
37220
38245
|
}
|
|
37221
38246
|
}
|
|
37222
38247
|
const hasContextManager = tools.some((t2) => t2.name === "context_manager");
|
|
37223
|
-
if (hasContextManager
|
|
37224
|
-
|
|
37225
|
-
|
|
37226
|
-
|
|
38248
|
+
if (hasContextManager) {
|
|
38249
|
+
if (this.tier === "minimal" || this.tier === "light") ; else if (this.tier === "medium") {
|
|
38250
|
+
lines.push(`
|
|
38251
|
+
## Context management
|
|
38252
|
+
|
|
38253
|
+
Use \`context_manager\` to manage context. Call \`{"action":"check"}\` to see token budget.`);
|
|
38254
|
+
} else {
|
|
38255
|
+
const maxCtx = this.opts.modelCapabilities?.maxContextTokens ?? 128e3;
|
|
38256
|
+
const threshold = maxCtx <= 32e3 ? "50" : "70";
|
|
38257
|
+
lines.push(`
|
|
37227
38258
|
## Context management
|
|
37228
38259
|
|
|
37229
38260
|
When the conversation grows long and context window usage exceeds what you can track,
|
|
@@ -37236,6 +38267,7 @@ use the context_manager tool proactively \u2014 do NOT wait to be told:
|
|
|
37236
38267
|
|
|
37237
38268
|
**Never** stuff redundant information into a tool result. If you summarize a file, do not paste its full content \u2014
|
|
37238
38269
|
summarize it, and let the tool result hold only the summary.`);
|
|
38270
|
+
}
|
|
37239
38271
|
}
|
|
37240
38272
|
const text = lines.join("\n");
|
|
37241
38273
|
this._toolsUsageCache = { toolsRef: tools, agentsRef: ctx.onlineAgents, text };
|
|
@@ -37247,6 +38279,10 @@ summarize it, and let the tool result hold only the summary.`);
|
|
|
37247
38279
|
* build turn (hundreds of ms). Reference equality avoids re-stringifying
|
|
37248
38280
|
* the same array on every iteration while still being correct when the
|
|
37249
38281
|
* caller passes a fresh array.
|
|
38282
|
+
*
|
|
38283
|
+
* Tier behaviour:
|
|
38284
|
+
* - 'off' / 'medium' / 'aggressive' → full list with names, sessions, sources
|
|
38285
|
+
* - 'minimal' / 'light' → count only (no list)
|
|
37250
38286
|
*/
|
|
37251
38287
|
renderOnlineAgents(agents) {
|
|
37252
38288
|
if (!agents || agents.length === 0) return "";
|
|
@@ -37254,6 +38290,11 @@ summarize it, and let the tool result hold only the summary.`);
|
|
|
37254
38290
|
return this._lastOnlineAgents.text;
|
|
37255
38291
|
}
|
|
37256
38292
|
const totalCount = agents.length;
|
|
38293
|
+
if (this.tier === "minimal" || this.tier === "light") {
|
|
38294
|
+
const text2 = ` (${totalCount} agent${totalCount !== 1 ? "s" : ""} online)`;
|
|
38295
|
+
this._lastOnlineAgents = { ref: agents, text: text2 };
|
|
38296
|
+
return text2;
|
|
38297
|
+
}
|
|
37257
38298
|
const agentList = agents.map(
|
|
37258
38299
|
(a) => `- **${a.name}** (${a.source ?? "unknown"}${a.sessionId ? `, session: ${a.sessionId.slice(0, 8)}` : ""})`
|
|
37259
38300
|
).join("\n");
|
|
@@ -37276,28 +38317,46 @@ ${agentList}`;
|
|
|
37276
38317
|
isGit ? this.gitStatus(ctx.projectRoot) : Promise.resolve("not a git repo"),
|
|
37277
38318
|
this.detectLanguages(ctx.projectRoot)
|
|
37278
38319
|
]);
|
|
37279
|
-
const
|
|
37280
|
-
|
|
37281
|
-
|
|
37282
|
-
`-
|
|
37283
|
-
|
|
37284
|
-
`-
|
|
37285
|
-
|
|
37286
|
-
|
|
37287
|
-
|
|
37288
|
-
|
|
37289
|
-
|
|
37290
|
-
|
|
37291
|
-
|
|
37292
|
-
);
|
|
37293
|
-
|
|
37294
|
-
|
|
37295
|
-
|
|
37296
|
-
|
|
37297
|
-
|
|
37298
|
-
|
|
37299
|
-
|
|
37300
|
-
|
|
38320
|
+
const tier = this.tier;
|
|
38321
|
+
const lines = ["## Environment"];
|
|
38322
|
+
if (tier === "minimal") {
|
|
38323
|
+
lines.push(`- Git: ${git} | Date: ${today}`);
|
|
38324
|
+
} else {
|
|
38325
|
+
lines.push(`- Operating system: ${platform2}`);
|
|
38326
|
+
if (tier !== "light") {
|
|
38327
|
+
lines.push(`- Shell: ${shell}`);
|
|
38328
|
+
lines.push(`- Node.js: ${node}`);
|
|
38329
|
+
}
|
|
38330
|
+
if (tier === "off" || tier === "medium" || tier === "aggressive") {
|
|
38331
|
+
lines.push(`- Detected languages: ${langs}`);
|
|
38332
|
+
}
|
|
38333
|
+
lines.push(`- Git status: ${git}`);
|
|
38334
|
+
lines.push(`- Today's date: ${today}`);
|
|
38335
|
+
if (tier === "aggressive") {
|
|
38336
|
+
if (ctx.provider || ctx.model) {
|
|
38337
|
+
lines.push(
|
|
38338
|
+
`- Running on: ${ctx.provider ?? "<unknown provider>"}/${ctx.model ?? "<unknown model>"}`
|
|
38339
|
+
);
|
|
38340
|
+
}
|
|
38341
|
+
if (this.opts.modelCapabilities) {
|
|
38342
|
+
lines.push(
|
|
38343
|
+
`- Context window: ${this.opts.modelCapabilities.maxContextTokens.toLocaleString()} tokens max`
|
|
38344
|
+
);
|
|
38345
|
+
}
|
|
38346
|
+
}
|
|
38347
|
+
if (tier !== "aggressive" && this.opts.modelCapabilities) {
|
|
38348
|
+
lines.push(
|
|
38349
|
+
`- Context window: ${this.opts.modelCapabilities.maxContextTokens.toLocaleString()} tokens max`
|
|
38350
|
+
);
|
|
38351
|
+
}
|
|
38352
|
+
if (tier !== "aggressive" && (ctx.provider || ctx.model)) {
|
|
38353
|
+
lines.push(
|
|
38354
|
+
`- Running on: ${ctx.provider ?? "<unknown provider>"}/${ctx.model ?? "<unknown model>"}`
|
|
38355
|
+
);
|
|
38356
|
+
}
|
|
38357
|
+
if (tier !== "aggressive" && this.opts.modeId && this.opts.modeId !== "default") {
|
|
38358
|
+
lines.push(`- Mode: ${this.opts.modeId}`);
|
|
38359
|
+
}
|
|
37301
38360
|
}
|
|
37302
38361
|
if (this.skillCache) {
|
|
37303
38362
|
lines.push(
|
|
@@ -37305,7 +38364,7 @@ ${agentList}`;
|
|
|
37305
38364
|
"## Skills in scope for this session",
|
|
37306
38365
|
this.skillCache,
|
|
37307
38366
|
"",
|
|
37308
|
-
this.
|
|
38367
|
+
this.isCompact ? "Compact skill instructions are injected in the Active Skills block below (Overview + Rules only)." : "Full skill instructions are injected in the Active Skills block below."
|
|
37309
38368
|
);
|
|
37310
38369
|
}
|
|
37311
38370
|
const text = lines.join("\n");
|
|
@@ -37314,6 +38373,8 @@ ${agentList}`;
|
|
|
37314
38373
|
}
|
|
37315
38374
|
async buildMemoryAndSkills() {
|
|
37316
38375
|
const parts = [];
|
|
38376
|
+
const memoryCount = this.tier === "minimal" ? 3 : this.tier === "light" ? 5 : 8;
|
|
38377
|
+
const compactMemory = this.tier === "minimal";
|
|
37317
38378
|
if (this.opts.memoryStore) {
|
|
37318
38379
|
try {
|
|
37319
38380
|
if (this.opts.memoryStore.scoreRelevant) {
|
|
@@ -37324,14 +38385,18 @@ ${agentList}`;
|
|
|
37324
38385
|
toolNames
|
|
37325
38386
|
},
|
|
37326
38387
|
"project-memory",
|
|
37327
|
-
|
|
38388
|
+
memoryCount
|
|
37328
38389
|
);
|
|
37329
38390
|
if (scored.length > 0) {
|
|
37330
38391
|
const lines = ["# Relevant Memory"];
|
|
37331
38392
|
for (const e of scored) {
|
|
37332
|
-
|
|
37333
|
-
|
|
37334
|
-
|
|
38393
|
+
if (compactMemory) {
|
|
38394
|
+
lines.push(`- ${e.text}`);
|
|
38395
|
+
} else {
|
|
38396
|
+
const badge2 = e.type ? `[\`${e.type.replace("_", "-")}\`] ` : "";
|
|
38397
|
+
const priorityMark = e.priority === "critical" ? "\u26A1" : e.priority === "high" ? "\u25B2" : "";
|
|
38398
|
+
lines.push(`- ${priorityMark}${badge2}${e.text}${e.tags ? ` \`#${e.tags.join(" #")}\`` : ""}`);
|
|
38399
|
+
}
|
|
37335
38400
|
}
|
|
37336
38401
|
parts.push(lines.join("\n"));
|
|
37337
38402
|
}
|
|
@@ -37345,7 +38410,7 @@ ${mem}`);
|
|
|
37345
38410
|
}
|
|
37346
38411
|
}
|
|
37347
38412
|
if (this.opts.skillLoader) {
|
|
37348
|
-
if (this.
|
|
38413
|
+
if (this.isCompact) {
|
|
37349
38414
|
if (this.skillBodyCache === void 0) {
|
|
37350
38415
|
await this.buildCompactSkillBodies();
|
|
37351
38416
|
}
|
|
@@ -37431,19 +38496,19 @@ ${clean.trim()}`);
|
|
|
37431
38496
|
}
|
|
37432
38497
|
async dirExists(p) {
|
|
37433
38498
|
try {
|
|
37434
|
-
const
|
|
37435
|
-
return
|
|
38499
|
+
const stat14 = await fsp3.stat(p);
|
|
38500
|
+
return stat14.isDirectory();
|
|
37436
38501
|
} catch {
|
|
37437
38502
|
return false;
|
|
37438
38503
|
}
|
|
37439
38504
|
}
|
|
37440
38505
|
async gitStatus(root) {
|
|
37441
|
-
return new Promise((
|
|
38506
|
+
return new Promise((resolve15) => {
|
|
37442
38507
|
let settled = false;
|
|
37443
38508
|
const finish = (s) => {
|
|
37444
38509
|
if (settled) return;
|
|
37445
38510
|
settled = true;
|
|
37446
|
-
|
|
38511
|
+
resolve15(s);
|
|
37447
38512
|
};
|
|
37448
38513
|
let proc;
|
|
37449
38514
|
const timer = setTimeout(() => {
|
|
@@ -37871,173 +38936,6 @@ var SlashCommandRegistry = class {
|
|
|
37871
38936
|
}
|
|
37872
38937
|
};
|
|
37873
38938
|
|
|
37874
|
-
// src/plugin/api.ts
|
|
37875
|
-
var DefaultPluginAPI = class {
|
|
37876
|
-
container;
|
|
37877
|
-
events;
|
|
37878
|
-
pipelines;
|
|
37879
|
-
tools;
|
|
37880
|
-
providers;
|
|
37881
|
-
mcp;
|
|
37882
|
-
slashCommands;
|
|
37883
|
-
extensions;
|
|
37884
|
-
session;
|
|
37885
|
-
metrics;
|
|
37886
|
-
config;
|
|
37887
|
-
log;
|
|
37888
|
-
configStore;
|
|
37889
|
-
hookRegistry;
|
|
37890
|
-
ownerName;
|
|
37891
|
-
pluginCleanupFns = [];
|
|
37892
|
-
constructor(init) {
|
|
37893
|
-
const owner = init.ownerName;
|
|
37894
|
-
this.ownerName = owner;
|
|
37895
|
-
this.hookRegistry = init.hookRegistry;
|
|
37896
|
-
this.container = init.container;
|
|
37897
|
-
this.events = init.events;
|
|
37898
|
-
this.config = init.config;
|
|
37899
|
-
this.configStore = init.configStore;
|
|
37900
|
-
this.log = init.log.child({ plugin: owner });
|
|
37901
|
-
this.extensions = init.extensions ?? new ExtensionRegistry();
|
|
37902
|
-
this.session = init.sessionWriter ?? noopSession;
|
|
37903
|
-
this.metrics = init.metricsSink ? scopedMetrics(init.metricsSink, owner) : noopMetrics;
|
|
37904
|
-
const pipelines = init.pipelines;
|
|
37905
|
-
const readonlyPipelines = {};
|
|
37906
|
-
for (const [key, pipeline2] of Object.entries(pipelines)) {
|
|
37907
|
-
readonlyPipelines[key] = pipeline2.asReadonly();
|
|
37908
|
-
}
|
|
37909
|
-
this.pipelines = readonlyPipelines;
|
|
37910
|
-
const tr = init.toolRegistry;
|
|
37911
|
-
const isOfficial = init.official === true;
|
|
37912
|
-
const capabilities = init.capabilities;
|
|
37913
|
-
const assertCanMutateTool = (name, op) => {
|
|
37914
|
-
if (isOfficial) return;
|
|
37915
|
-
const currentOwner = tr.ownerOf(name);
|
|
37916
|
-
if (currentOwner === void 0) return;
|
|
37917
|
-
const ownedSolelyByMe = currentOwner.split("+").every((seg) => seg === owner);
|
|
37918
|
-
if (ownedSolelyByMe) return;
|
|
37919
|
-
const toolCaps = tr.get(name)?.capabilities ?? [];
|
|
37920
|
-
const pluginMutateCaps = capabilities?.toolMutateCapabilities ?? [];
|
|
37921
|
-
const hasRequiredCap = toolCaps.some((c) => pluginMutateCaps.includes(c));
|
|
37922
|
-
if (!hasRequiredCap) {
|
|
37923
|
-
throw new Error(
|
|
37924
|
-
`Plugin "${owner}" may not ${op} tool "${name}" \u2014 it is owned by "${currentOwner}". Tool capabilities: [${toolCaps.join(", ") || "none"}]. Plugin toolMutateCapabilities: [${pluginMutateCaps.join(", ") || "none"}]. Missing required capability to mutate this tool.`
|
|
37925
|
-
);
|
|
37926
|
-
}
|
|
37927
|
-
};
|
|
37928
|
-
this.tools = {
|
|
37929
|
-
register: (t2) => tr.register(t2, owner),
|
|
37930
|
-
unregister: (name) => {
|
|
37931
|
-
assertCanMutateTool(name, "unregister");
|
|
37932
|
-
return tr.unregister(name);
|
|
37933
|
-
},
|
|
37934
|
-
wrap: (name, wrapper) => {
|
|
37935
|
-
assertCanMutateTool(name, "wrap");
|
|
37936
|
-
tr.wrap(name, wrapper, owner);
|
|
37937
|
-
},
|
|
37938
|
-
get: (name) => tr.get(name),
|
|
37939
|
-
list: () => tr.list()
|
|
37940
|
-
};
|
|
37941
|
-
const pr = init.providerRegistry;
|
|
37942
|
-
this.providers = {
|
|
37943
|
-
register: (f) => pr.register(f),
|
|
37944
|
-
create: (cfg) => pr.create(cfg),
|
|
37945
|
-
list: () => pr.list()
|
|
37946
|
-
};
|
|
37947
|
-
this.mcp = init.mcpRegistry ?? noopMcp;
|
|
37948
|
-
const scr = init.slashCommandRegistry;
|
|
37949
|
-
const official = init.official === true;
|
|
37950
|
-
this.slashCommands = scr ? {
|
|
37951
|
-
register: (cmd) => scr.register(cmd, owner, { official }),
|
|
37952
|
-
unregister: (name) => scr.unregister(name),
|
|
37953
|
-
get: (name) => scr.get(name),
|
|
37954
|
-
list: () => scr.list()
|
|
37955
|
-
} : noopSlashCommands;
|
|
37956
|
-
}
|
|
37957
|
-
onEvent(event, handler) {
|
|
37958
|
-
const off = this.events.on(event, handler);
|
|
37959
|
-
this.pluginCleanupFns.push(off);
|
|
37960
|
-
return off;
|
|
37961
|
-
}
|
|
37962
|
-
onPattern(pattern, handler) {
|
|
37963
|
-
const off = this.events.onPattern(pattern, handler);
|
|
37964
|
-
this.pluginCleanupFns.push(off);
|
|
37965
|
-
return off;
|
|
37966
|
-
}
|
|
37967
|
-
emitCustom(event, payload) {
|
|
37968
|
-
this.events.emitCustom(event, payload);
|
|
37969
|
-
}
|
|
37970
|
-
onConfigChange(handler) {
|
|
37971
|
-
if (!this.configStore) return () => {
|
|
37972
|
-
};
|
|
37973
|
-
return this.configStore.watch(handler);
|
|
37974
|
-
}
|
|
37975
|
-
/** Called by the plugin loader when uninstalling the plugin. */
|
|
37976
|
-
drainCleanup() {
|
|
37977
|
-
for (const fn of this.pluginCleanupFns.splice(0)) {
|
|
37978
|
-
try {
|
|
37979
|
-
fn();
|
|
37980
|
-
} catch {
|
|
37981
|
-
}
|
|
37982
|
-
}
|
|
37983
|
-
}
|
|
37984
|
-
registerSystemPromptContributor(c) {
|
|
37985
|
-
return this.extensions.registerSystemPromptContributor(c);
|
|
37986
|
-
}
|
|
37987
|
-
registerHook(event, matcher, hook) {
|
|
37988
|
-
if (!this.hookRegistry) return () => {
|
|
37989
|
-
};
|
|
37990
|
-
const off = this.hookRegistry.registerInProcess(event, matcher, hook, this.ownerName);
|
|
37991
|
-
this.pluginCleanupFns.push(off);
|
|
37992
|
-
return off;
|
|
37993
|
-
}
|
|
37994
|
-
};
|
|
37995
|
-
var noopMcp = {
|
|
37996
|
-
start: async () => void 0,
|
|
37997
|
-
stop: async () => void 0,
|
|
37998
|
-
restart: async () => void 0,
|
|
37999
|
-
list: () => []
|
|
38000
|
-
};
|
|
38001
|
-
var noopSlashCommands = {
|
|
38002
|
-
register() {
|
|
38003
|
-
},
|
|
38004
|
-
unregister() {
|
|
38005
|
-
return false;
|
|
38006
|
-
},
|
|
38007
|
-
get() {
|
|
38008
|
-
return void 0;
|
|
38009
|
-
},
|
|
38010
|
-
list() {
|
|
38011
|
-
return [];
|
|
38012
|
-
}
|
|
38013
|
-
};
|
|
38014
|
-
var noopSession = {
|
|
38015
|
-
append: async () => {
|
|
38016
|
-
}
|
|
38017
|
-
};
|
|
38018
|
-
var noopMetrics = {
|
|
38019
|
-
counter() {
|
|
38020
|
-
},
|
|
38021
|
-
histogram() {
|
|
38022
|
-
},
|
|
38023
|
-
gauge() {
|
|
38024
|
-
}
|
|
38025
|
-
};
|
|
38026
|
-
function scopedMetrics(sink, pluginName) {
|
|
38027
|
-
const prefix = `plugin.${pluginName}.`;
|
|
38028
|
-
return {
|
|
38029
|
-
counter(name, value, labels) {
|
|
38030
|
-
sink.counter(`${prefix}${name}`, value, labels);
|
|
38031
|
-
},
|
|
38032
|
-
histogram(name, value, labels) {
|
|
38033
|
-
sink.histogram(`${prefix}${name}`, value, labels);
|
|
38034
|
-
},
|
|
38035
|
-
gauge(name, value, labels) {
|
|
38036
|
-
sink.gauge(`${prefix}${name}`, value, labels);
|
|
38037
|
-
}
|
|
38038
|
-
};
|
|
38039
|
-
}
|
|
38040
|
-
|
|
38041
38939
|
// src/plugin/loader.ts
|
|
38042
38940
|
var pluginApiMap = /* @__PURE__ */ new WeakMap();
|
|
38043
38941
|
var KERNEL_API_VERSION = "0.1.10";
|
|
@@ -38202,12 +39100,23 @@ async function loadPlugins(plugins, opts) {
|
|
|
38202
39100
|
try {
|
|
38203
39101
|
const rawApi = opts.apiFactory(plugin);
|
|
38204
39102
|
const api = plugin.capabilities ? wrapApiForCapabilityCheck(plugin, rawApi, opts.log, opts.enforceCapabilities) : rawApi;
|
|
38205
|
-
|
|
39103
|
+
const setupTimeoutMs = opts.setupTimeoutMs ?? 3e4;
|
|
39104
|
+
const setupController = new AbortController();
|
|
39105
|
+
const setupTimeout = setTimeout(() => setupController.abort(), setupTimeoutMs);
|
|
39106
|
+
try {
|
|
39107
|
+
await plugin.setup(api, { signal: setupController.signal });
|
|
39108
|
+
} finally {
|
|
39109
|
+
clearTimeout(setupTimeout);
|
|
39110
|
+
}
|
|
38206
39111
|
pluginApiMap.set(plugin, api);
|
|
38207
39112
|
loaded.push(plugin);
|
|
38208
39113
|
opts.log.info(`Plugin "${plugin.name}" loaded`);
|
|
38209
39114
|
} catch (err) {
|
|
38210
|
-
|
|
39115
|
+
const isAbort = err instanceof DOMException && err.name === "AbortError";
|
|
39116
|
+
opts.log.error(
|
|
39117
|
+
`Plugin "${plugin.name}" setup failed`,
|
|
39118
|
+
isAbort ? { err, reason: "setup timed out or aborted" } : { err }
|
|
39119
|
+
);
|
|
38211
39120
|
failed.push({ plugin, err });
|
|
38212
39121
|
}
|
|
38213
39122
|
}
|
|
@@ -38224,11 +39133,22 @@ async function unloadPlugins(loadedPlugins, opts) {
|
|
|
38224
39133
|
`Plugin "${plugin.name}" API not found in pluginApiMap \u2014 was setup() called?`
|
|
38225
39134
|
);
|
|
38226
39135
|
}
|
|
38227
|
-
|
|
39136
|
+
const teardownTimeoutMs = opts.teardownTimeoutMs ?? 1e4;
|
|
39137
|
+
const teardownController = new AbortController();
|
|
39138
|
+
const teardownTimeout = setTimeout(() => teardownController.abort(), teardownTimeoutMs);
|
|
39139
|
+
try {
|
|
39140
|
+
await plugin.teardown(api, { signal: teardownController.signal });
|
|
39141
|
+
} finally {
|
|
39142
|
+
clearTimeout(teardownTimeout);
|
|
39143
|
+
}
|
|
38228
39144
|
pluginApiMap.delete(plugin);
|
|
38229
39145
|
opts.log.info(`Plugin "${plugin.name}" torn down`);
|
|
38230
39146
|
} catch (err) {
|
|
38231
|
-
|
|
39147
|
+
const isAbort = err instanceof DOMException && err.name === "AbortError";
|
|
39148
|
+
opts.log.error(
|
|
39149
|
+
`Plugin "${plugin.name}" teardown failed`,
|
|
39150
|
+
isAbort ? { err, reason: "teardown timed out or aborted" } : { err }
|
|
39151
|
+
);
|
|
38232
39152
|
}
|
|
38233
39153
|
}
|
|
38234
39154
|
}
|
|
@@ -38312,9 +39232,194 @@ function wrapApiForCapabilityCheck(plugin, api, log, enforce = false) {
|
|
|
38312
39232
|
});
|
|
38313
39233
|
}
|
|
38314
39234
|
|
|
39235
|
+
// src/plugin/api.ts
|
|
39236
|
+
var DefaultPluginAPI = class {
|
|
39237
|
+
container;
|
|
39238
|
+
events;
|
|
39239
|
+
pipelines;
|
|
39240
|
+
tools;
|
|
39241
|
+
providers;
|
|
39242
|
+
mcp;
|
|
39243
|
+
slashCommands;
|
|
39244
|
+
extensions;
|
|
39245
|
+
session;
|
|
39246
|
+
metrics;
|
|
39247
|
+
config;
|
|
39248
|
+
log;
|
|
39249
|
+
configStore;
|
|
39250
|
+
hookRegistry;
|
|
39251
|
+
ownerName;
|
|
39252
|
+
pluginCleanupFns = [];
|
|
39253
|
+
constructor(init) {
|
|
39254
|
+
const owner = init.ownerName;
|
|
39255
|
+
this.ownerName = owner;
|
|
39256
|
+
this.hookRegistry = init.hookRegistry;
|
|
39257
|
+
this.container = init.container;
|
|
39258
|
+
this.events = init.events;
|
|
39259
|
+
this.config = init.config;
|
|
39260
|
+
this.configStore = init.configStore;
|
|
39261
|
+
this.log = init.log.child({ plugin: owner });
|
|
39262
|
+
this.extensions = init.extensions ?? new ExtensionRegistry();
|
|
39263
|
+
this.session = init.sessionWriter ?? noopSession;
|
|
39264
|
+
this.metrics = init.metricsSink ? scopedMetrics(init.metricsSink, owner) : noopMetrics;
|
|
39265
|
+
const pipelines = init.pipelines;
|
|
39266
|
+
const readonlyPipelines = {};
|
|
39267
|
+
for (const [key, pipeline2] of Object.entries(pipelines)) {
|
|
39268
|
+
readonlyPipelines[key] = pipeline2.asReadonly();
|
|
39269
|
+
}
|
|
39270
|
+
this.pipelines = readonlyPipelines;
|
|
39271
|
+
const tr = init.toolRegistry;
|
|
39272
|
+
const isOfficial = init.official === true;
|
|
39273
|
+
const capabilities = init.capabilities;
|
|
39274
|
+
const assertCanMutateTool = (name, op) => {
|
|
39275
|
+
if (isOfficial) return;
|
|
39276
|
+
const currentOwner = tr.ownerOf(name);
|
|
39277
|
+
if (currentOwner === void 0) return;
|
|
39278
|
+
const ownedSolelyByMe = currentOwner.split("+").every((seg) => seg === owner);
|
|
39279
|
+
if (ownedSolelyByMe) return;
|
|
39280
|
+
const toolCaps = tr.get(name)?.capabilities ?? [];
|
|
39281
|
+
const pluginMutateCaps = capabilities?.toolMutateCapabilities ?? [];
|
|
39282
|
+
const hasRequiredCap = toolCaps.some((c) => pluginMutateCaps.includes(c));
|
|
39283
|
+
if (!hasRequiredCap) {
|
|
39284
|
+
throw new Error(
|
|
39285
|
+
`Plugin "${owner}" may not ${op} tool "${name}" \u2014 it is owned by "${currentOwner}". Tool capabilities: [${toolCaps.join(", ") || "none"}]. Plugin toolMutateCapabilities: [${pluginMutateCaps.join(", ") || "none"}]. Missing required capability to mutate this tool.`
|
|
39286
|
+
);
|
|
39287
|
+
}
|
|
39288
|
+
};
|
|
39289
|
+
this.tools = {
|
|
39290
|
+
register: (t2) => tr.register(t2, owner),
|
|
39291
|
+
unregister: (name) => {
|
|
39292
|
+
assertCanMutateTool(name, "unregister");
|
|
39293
|
+
return tr.unregister(name);
|
|
39294
|
+
},
|
|
39295
|
+
wrap: (name, wrapper) => {
|
|
39296
|
+
assertCanMutateTool(name, "wrap");
|
|
39297
|
+
tr.wrap(name, wrapper, owner);
|
|
39298
|
+
},
|
|
39299
|
+
get: (name) => tr.get(name),
|
|
39300
|
+
list: () => tr.list()
|
|
39301
|
+
};
|
|
39302
|
+
const pr = init.providerRegistry;
|
|
39303
|
+
this.providers = {
|
|
39304
|
+
register: (f) => pr.register(f),
|
|
39305
|
+
create: (cfg) => pr.create(cfg),
|
|
39306
|
+
list: () => pr.list()
|
|
39307
|
+
};
|
|
39308
|
+
this.mcp = init.mcpRegistry ?? noopMcp;
|
|
39309
|
+
const scr = init.slashCommandRegistry;
|
|
39310
|
+
const official = init.official === true;
|
|
39311
|
+
this.slashCommands = scr ? {
|
|
39312
|
+
register: (cmd) => scr.register(cmd, owner, { official }),
|
|
39313
|
+
unregister: (name) => scr.unregister(name),
|
|
39314
|
+
get: (name) => scr.get(name),
|
|
39315
|
+
list: () => scr.list()
|
|
39316
|
+
} : noopSlashCommands;
|
|
39317
|
+
}
|
|
39318
|
+
onEvent(event, handler) {
|
|
39319
|
+
const off = this.events.on(event, handler);
|
|
39320
|
+
this.pluginCleanupFns.push(off);
|
|
39321
|
+
return off;
|
|
39322
|
+
}
|
|
39323
|
+
onPattern(pattern, handler) {
|
|
39324
|
+
const off = this.events.onPattern(pattern, handler);
|
|
39325
|
+
this.pluginCleanupFns.push(off);
|
|
39326
|
+
return off;
|
|
39327
|
+
}
|
|
39328
|
+
emitCustom(event, payload) {
|
|
39329
|
+
this.events.emitCustom(event, payload);
|
|
39330
|
+
}
|
|
39331
|
+
onConfigChange(handler) {
|
|
39332
|
+
if (!this.configStore) return () => {
|
|
39333
|
+
};
|
|
39334
|
+
return this.configStore.watch(handler);
|
|
39335
|
+
}
|
|
39336
|
+
/** Called by the plugin loader when uninstalling the plugin. */
|
|
39337
|
+
drainCleanup() {
|
|
39338
|
+
for (const fn of this.pluginCleanupFns.splice(0)) {
|
|
39339
|
+
try {
|
|
39340
|
+
fn();
|
|
39341
|
+
} catch {
|
|
39342
|
+
}
|
|
39343
|
+
}
|
|
39344
|
+
}
|
|
39345
|
+
registerSystemPromptContributor(c) {
|
|
39346
|
+
return this.extensions.registerSystemPromptContributor(c);
|
|
39347
|
+
}
|
|
39348
|
+
registerHook(event, matcher, hook) {
|
|
39349
|
+
if (!this.hookRegistry) return () => {
|
|
39350
|
+
};
|
|
39351
|
+
const off = this.hookRegistry.registerInProcess(event, matcher, hook, this.ownerName);
|
|
39352
|
+
this.pluginCleanupFns.push(off);
|
|
39353
|
+
return off;
|
|
39354
|
+
}
|
|
39355
|
+
};
|
|
39356
|
+
var noopMcp = {
|
|
39357
|
+
start: async () => void 0,
|
|
39358
|
+
stop: async () => void 0,
|
|
39359
|
+
restart: async () => void 0,
|
|
39360
|
+
list: () => []
|
|
39361
|
+
};
|
|
39362
|
+
var noopSlashCommands = {
|
|
39363
|
+
register() {
|
|
39364
|
+
},
|
|
39365
|
+
unregister() {
|
|
39366
|
+
return false;
|
|
39367
|
+
},
|
|
39368
|
+
get() {
|
|
39369
|
+
return void 0;
|
|
39370
|
+
},
|
|
39371
|
+
list() {
|
|
39372
|
+
return [];
|
|
39373
|
+
}
|
|
39374
|
+
};
|
|
39375
|
+
var noopSession = {
|
|
39376
|
+
append: async () => {
|
|
39377
|
+
}
|
|
39378
|
+
};
|
|
39379
|
+
var noopMetrics = {
|
|
39380
|
+
counter() {
|
|
39381
|
+
},
|
|
39382
|
+
histogram() {
|
|
39383
|
+
},
|
|
39384
|
+
gauge() {
|
|
39385
|
+
}
|
|
39386
|
+
};
|
|
39387
|
+
function scopedMetrics(sink, pluginName) {
|
|
39388
|
+
const prefix = `plugin.${pluginName}.`;
|
|
39389
|
+
return {
|
|
39390
|
+
counter(name, value, labels) {
|
|
39391
|
+
sink.counter(`${prefix}${name}`, value, labels);
|
|
39392
|
+
},
|
|
39393
|
+
histogram(name, value, labels) {
|
|
39394
|
+
sink.histogram(`${prefix}${name}`, value, labels);
|
|
39395
|
+
},
|
|
39396
|
+
gauge(name, value, labels) {
|
|
39397
|
+
sink.gauge(`${prefix}${name}`, value, labels);
|
|
39398
|
+
}
|
|
39399
|
+
};
|
|
39400
|
+
}
|
|
39401
|
+
function definePlugin(metadata, factory) {
|
|
39402
|
+
return {
|
|
39403
|
+
name: metadata.name,
|
|
39404
|
+
version: metadata.version,
|
|
39405
|
+
description: metadata.description,
|
|
39406
|
+
capabilities: metadata.capabilities,
|
|
39407
|
+
configSchema: metadata.configSchema,
|
|
39408
|
+
defaultConfig: metadata.defaultConfig,
|
|
39409
|
+
dependsOn: metadata.dependsOn,
|
|
39410
|
+
optionalDeps: metadata.optionalDeps,
|
|
39411
|
+
conflictsWith: metadata.conflictsWith,
|
|
39412
|
+
apiVersion: KERNEL_API_VERSION,
|
|
39413
|
+
// Cast the factory to match the Plugin.setup signature.
|
|
39414
|
+
// The opts parameter ({ signal }) is accepted by Plugin.setup but the
|
|
39415
|
+
// definePlugin factory doesn't need to declare it — the host always passes
|
|
39416
|
+
// it at the call site; the factory just doesn't have to care about it.
|
|
39417
|
+
setup: (api) => factory(api, metadata.defaultConfig)
|
|
39418
|
+
};
|
|
39419
|
+
}
|
|
39420
|
+
|
|
38315
39421
|
// src/execution/prompt-enhancer.ts
|
|
38316
39422
|
var ENHANCER_SYSTEM_PROMPT = `You are a request refiner embedded in a coding agent. Your ONLY job is to rewrite the user's message into clearer, unambiguous instructions that the coding agent can act on confidently.
|
|
38317
|
-
import { toErrorMessage } from '../utils/error.js';
|
|
38318
39423
|
|
|
38319
39424
|
Rules:
|
|
38320
39425
|
- Preserve the user's intent and scope EXACTLY. Do not add new requirements, features, constraints, or steps the user did not ask for. Do not remove anything they did ask for.
|
|
@@ -38408,6 +39513,7 @@ async function enhanceUserPrompt(opts) {
|
|
|
38408
39513
|
opts.onError?.(toErrorMessage(err));
|
|
38409
39514
|
return null;
|
|
38410
39515
|
} finally {
|
|
39516
|
+
timer.abort();
|
|
38411
39517
|
clearTimeout(to);
|
|
38412
39518
|
}
|
|
38413
39519
|
}
|
|
@@ -38859,7 +39965,7 @@ var PhaseOrchestrator = class {
|
|
|
38859
39965
|
async mergeOne(phase, handle) {
|
|
38860
39966
|
if (!this.worktrees) return;
|
|
38861
39967
|
try {
|
|
38862
|
-
const
|
|
39968
|
+
const resolve15 = this.ctx.resolveConflict ? async (info) => {
|
|
38863
39969
|
const shouldResolve = await this.shouldAttemptConflictResolution(phase, info);
|
|
38864
39970
|
if (!shouldResolve) return false;
|
|
38865
39971
|
this.emit("phase.conflictResolving", {
|
|
@@ -38873,7 +39979,7 @@ var PhaseOrchestrator = class {
|
|
|
38873
39979
|
const mergeOpts = {
|
|
38874
39980
|
squash: true
|
|
38875
39981
|
};
|
|
38876
|
-
if (
|
|
39982
|
+
if (resolve15 !== void 0) mergeOpts.resolve = resolve15;
|
|
38877
39983
|
const result = await this.worktrees.merge(handle, mergeOpts);
|
|
38878
39984
|
if (result.resolved) {
|
|
38879
39985
|
this.emit("phase.conflictResolved", { phaseId: phase.id, name: phase.name });
|
|
@@ -39224,7 +40330,7 @@ var PhaseOrchestrator = class {
|
|
|
39224
40330
|
}
|
|
39225
40331
|
}
|
|
39226
40332
|
delay(ms) {
|
|
39227
|
-
return new Promise((
|
|
40333
|
+
return new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
39228
40334
|
}
|
|
39229
40335
|
};
|
|
39230
40336
|
|
|
@@ -40277,8 +41383,8 @@ var CollaborationBus = class {
|
|
|
40277
41383
|
if (this.isPaused()) return false;
|
|
40278
41384
|
this.pausedAtMs = Date.now();
|
|
40279
41385
|
this.pausedBy = byParticipant;
|
|
40280
|
-
this.pausePromise = new Promise((
|
|
40281
|
-
this.pauseResolve =
|
|
41386
|
+
this.pausePromise = new Promise((resolve15) => {
|
|
41387
|
+
this.pauseResolve = resolve15;
|
|
40282
41388
|
});
|
|
40283
41389
|
return true;
|
|
40284
41390
|
}
|
|
@@ -40314,8 +41420,8 @@ var CollaborationBus = class {
|
|
|
40314
41420
|
return true;
|
|
40315
41421
|
}
|
|
40316
41422
|
let timer;
|
|
40317
|
-
const timeoutPromise = new Promise((
|
|
40318
|
-
timer = setTimeout(() =>
|
|
41423
|
+
const timeoutPromise = new Promise((resolve15) => {
|
|
41424
|
+
timer = setTimeout(() => resolve15("timeout"), timeoutMs);
|
|
40319
41425
|
});
|
|
40320
41426
|
const resumedPromise = this.pausePromise.then(() => "resumed").catch(() => "resumed");
|
|
40321
41427
|
const winner = await Promise.race([resumedPromise, timeoutPromise]);
|
|
@@ -40558,8 +41664,8 @@ function extractManifestPath(msg) {
|
|
|
40558
41664
|
}
|
|
40559
41665
|
return void 0;
|
|
40560
41666
|
}
|
|
40561
|
-
function isManifestFile(
|
|
40562
|
-
const name = pathBasename(
|
|
41667
|
+
function isManifestFile(path44) {
|
|
41668
|
+
const name = pathBasename(path44).toLowerCase();
|
|
40563
41669
|
const manifests = [
|
|
40564
41670
|
"package.json",
|
|
40565
41671
|
"package-lock.json",
|
|
@@ -41063,7 +42169,7 @@ function createGitPlugin() {
|
|
|
41063
42169
|
}
|
|
41064
42170
|
async function runGit(args, cwd) {
|
|
41065
42171
|
try {
|
|
41066
|
-
return await new Promise((
|
|
42172
|
+
return await new Promise((resolve15, reject) => {
|
|
41067
42173
|
const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], signal: AbortSignal.timeout(3e4), windowsHide: true });
|
|
41068
42174
|
let stdout = "";
|
|
41069
42175
|
let stderr = "";
|
|
@@ -41084,7 +42190,7 @@ async function runGit(args, cwd) {
|
|
|
41084
42190
|
})
|
|
41085
42191
|
);
|
|
41086
42192
|
});
|
|
41087
|
-
child.on("close", (code) =>
|
|
42193
|
+
child.on("close", (code) => resolve15({ stdout, stderr, code: code ?? 0 }));
|
|
41088
42194
|
});
|
|
41089
42195
|
} catch (err) {
|
|
41090
42196
|
if (err instanceof WrongStackError) throw err;
|
|
@@ -41887,7 +42993,7 @@ If NOTHING worth flagging:
|
|
|
41887
42993
|
## \u{1F982} Chimera Review \u2014 all clear \u2705
|
|
41888
42994
|
No issues found in N changed files.`;
|
|
41889
42995
|
async function runGit2(args, cwd) {
|
|
41890
|
-
return new Promise((
|
|
42996
|
+
return new Promise((resolve15, reject) => {
|
|
41891
42997
|
let child;
|
|
41892
42998
|
try {
|
|
41893
42999
|
child = spawn("git", args, {
|
|
@@ -41908,8 +43014,8 @@ async function runGit2(args, cwd) {
|
|
|
41908
43014
|
child.stderr?.on("data", (d) => {
|
|
41909
43015
|
stderr += d;
|
|
41910
43016
|
});
|
|
41911
|
-
child.on("error", () =>
|
|
41912
|
-
child.on("close", (code) =>
|
|
43017
|
+
child.on("error", () => resolve15({ stdout, stderr, code: 1 }));
|
|
43018
|
+
child.on("close", (code) => resolve15({ stdout, stderr, code: code ?? 0 }));
|
|
41913
43019
|
});
|
|
41914
43020
|
}
|
|
41915
43021
|
async function isGitRepo2(cwd) {
|
|
@@ -42063,6 +43169,6 @@ function createChimeraPlugin() {
|
|
|
42063
43169
|
};
|
|
42064
43170
|
}
|
|
42065
43171
|
|
|
42066
|
-
export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, ALL_SYNC_CATEGORIES, AUDIT_LOG_AGENT, Agent, AgentError, AgentStatusTracker, AnnotationsStore, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutoPhasePlanner, AutoPhaseRunner, AutonomousBrain, AutonomousCoordinator, AutonomousRunner, BUG_HUNTER_AGENT, BUILD_AGENTS, BrainDecisionQueue, BrainMonitor, BudgetExceededError, BudgetThresholdSignal, CHIMERA_REVIEW_PROMPT, CONTEXT_WINDOW_MODES, CORE_RECONSTRUCT_EVENTS, ChangeManager, CheckpointManager, CloudSync, CollabSession, CollaborationBus, ConfigError, ConfigMigrationError, ConsensusProtocol, Container, Context, ConversationState, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_QUALITY_CHECKS, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SESSION_LOGGING_CONFIG, DEFAULT_SESSION_PRUNE_DAYS, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DELIVERY_AGENTS, DEPENDENCY_FILE_PATTERNS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultAttachmentStore, DefaultBrainArbiter, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMailbox, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultPromptStore, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorAlertLevel, DirectorStateCheckpoint, DoneConditionChecker, ENHANCER_SYSTEM_PROMPT, ERROR_CODES, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FORBIDDEN_PROTO_KEYS, FileMemoryBackend, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, FsError, GitignoreUpdater, GlobalMailbox, GraphMemoryBackend, HEAVY_BUDGET, HookRegistry, HookRunner, HumanEscalatingBrainArbiter, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, KNOWLEDGE_AGENTS, KnowledgeGraph, LAYER_1_IDENTITY, LIGHT_BUDGET, LLMSelector, LargeAnswerStore, MATRIX_PHASE_KEYS, MAX_JOURNAL_ENTRIES, MAX_PROGRESS_HISTORY, MEDIUM_BUDGET, MEMORY_TYPE_LABELS, META_AGENTS, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, ObservableBrainArbiter, PLANNING_AGENTS, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, PhaseGraphBuilder, PhaseOrchestrator, PhaseStore, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, RecoveryLock, ReplayLogStore, ReplayProviderRunner, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, STANDARD_AUDIT_EVENTS, ScopedEventBus, SddError, SddParallelRun, SddTaskDecomposer, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SessionMemoryConsolidator, SessionRecovery, SessionRegistry, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, StreamHangError, SubagentBudget, TOKENS, TaskAuctioneer, TaskDAG, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolAuditLog, ToolError, ToolExecutor, ToolRegistry, VERIFY_AGENTS, WorktreeManager, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyRosterBudget, asBlocks, asText, assertNever, assertNotPrivateHost, assertSafePath, atomicWrite, attachAutoExtend, attachDepWatcherBridge, attachMailboxChecker, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, bootConfig, braveSearchServer, buildBtwBlock, buildChildEnv, buildGoalPreamble, buildLosslessDigest, buildMailboxBlock, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildQueuedMessagesBlock, buildRecoveryStrategies, buildSmartDigest, classifyFamily, clearPlan, collabInjectMiddleware, collabPauseMiddleware, color, compactLog, compileGlob, compileUserRegex, completePartialObject, composeDirectorPrompt, composeSubagentPrompt, computeMessageTokens, computeTaskItemProgress, computeTaskProgress, consumeBtwNotes, consumeQueuedMessagesUpdate, context7Server, contextManagerTool, createAutoExecutor, createAutoPhaseFromTaskGraph, createAutonomyBrain, createChimeraPlugin, createContextManagerTool, createDefaultPipelines, createDelegateTool, createGitPlugin, createMailboxChecker, createMailboxHooks, createMcpControlTool, createMcpUseTool, createMessage, createObservabilityPlugin, createPlanPlugin, createPromptsPlugin, createSecurityPlugin, createSecuritySlashCommand, createSessionEventBridge, createSkillsPlugin, createStrategyCompactor, createSyncPlugin, createTieredBrainArbiter, createToolOutputSerializer, decryptConfigSecrets, deepMerge, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectEcosystem, detectNewlineStyle, detectEcosystem as detectPackageEcosystem, dispatchAgent, downloadGitHubTarball, eliseOldToolResults, emptyGoal, emptyPlan, emptyTaskFile, encryptConfigSecrets, enhanceUserPrompt, ensureDir, estimateMessageTokens, estimateMessages, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, expandGlob, expandIPv6, expectDefined, extractRunEnv, extractText, filesystemServer, findCriticalPath, findPreserveStart, flagsToConfigPatch, formatContextWindowModeList, formatDecisionSummary, formatGoal, formatHumanPrompt, formatPlan, formatPlanTemplates, formatTaskList, formatTaskProgress, formatTodosList, getAgentDefinition, getCalibrationState, getContextWindowMode, getFileHistory, getFilesByAgent, getFullLog, getFullPackageLog, getLastAuthor, getManifestPackages, getPackageAuthor, getPackagesByAgent, getPlanTemplate, getSessionRegistry, getTemplate, getTermSize, githubServer, goalFilePath, googleMapsServer, hasSessionRegistry, hasTextContent, hashRequest, hookMatcherMatches, injectPendingMailboxMessages, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isInteractive, isPluginError, isPrimitiveArray, isPrivateIPv4, isPrivateIPv6, isSddError, isSecretField, isSessionError, isStdinTTY, isStdoutTTY, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isValidMatrixKey, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTasks, loadTodosCheckpoint, loadUserModes, mailboxSessionTag, makeAgentSubagentRunner, makeAskResultTool, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeContinueToNextIterationTool, makeDependencyWatcherConfig, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeMailInboxTool, makeMailSendTool, makeMailboxTool, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, matchAny, matchGlob, matrixKeyKind, mergeCustomModelDefs, mergeModelsPayload, migratePlaintextSecrets, miniMaxVisionServer, mutatePlan, mutateTasks, noOpLogger, noOpVault, normalizeRecipient, normalizeToLf, normalizedEqual, onResize, parseContinueDirective, parseEntries, parseProgressFromText, parseSkillRef, peekQueuedMessages, pendingBtwCount, phaseForRole, projectHash, projectSlug, recentTextTurns, recordActualUsage, recordFileAction, recordPackageAction, recordProgress, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resetCalibration, resolveAuditLevel, resolveChimeraConfig, resolveContextWindowPolicy, resolveMailboxIdentity, resolveModelMatrix, resolveProjectDir, resolveSessionLoggingConfig, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, runProviderWithRetry, runShellHook, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTasks, saveTodosCheckpoint, scoreAgents, scoreMessage, securitySlashCommand, sentinelServer, setBtwNote, setOutputLineGuard, setPlanItemStatus, setProgress, setQueuedMessagesSnapshot, setRawMode, shouldEnhance, slackServer, sleep, stableStringify, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, startPackageOutdatedWatcher, startTechStackConsumer, stripAnsi, summarizeUsage, templateToMarkdown, toErrorMessage, toStyle, toWrongStackError, topologicalSort, truncate, unifiedDiff, unloadPlugins, updatePackageOutdatedStatus, validateAgainstSchema, wireMetricsToEvents, withDisabledToolFiltering, withFileLock, wrapAsState, writeErr, writeOut, wstackGlobalRoot, zaiVisionServer };
|
|
43172
|
+
export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, ALL_SYNC_CATEGORIES, AUDIT_LOG_AGENT, Agent, AgentError, AgentStatusTracker, AnnotationsStore, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutoPhasePlanner, AutoPhaseRunner, AutonomousBrain, AutonomousCoordinator, AutonomousRunner, BUG_HUNTER_AGENT, BUILD_AGENTS, BrainDecisionQueue, BrainMonitor, BudgetExceededError, BudgetThresholdSignal, CHIMERA_REVIEW_PROMPT, CONTEXT_WINDOW_MODES, CORE_RECONSTRUCT_EVENTS, ChangeManager, CheckpointManager, CloudSync, CollabSession, CollaborationBus, ConfigError, ConfigMigrationError, ConsensusProtocol, Container, Context, ConversationState, DECISION_TIMEOUT_MS, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CIRCUIT_BREAKER_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_QUALITY_CHECKS, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SESSION_LOGGING_CONFIG, DEFAULT_SESSION_PRUNE_DAYS, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DELIVERY_AGENTS, DEPENDENCY_FILE_PATTERNS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultAttachmentStore, DefaultBrainArbiter, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMailbox, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultPromptStore, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorAlertLevel, DirectorStateCheckpoint, DoneConditionChecker, ENHANCER_SYSTEM_PROMPT, ERROR_CODES, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FORBIDDEN_PROTO_KEYS, FileMemoryBackend, FleetBus, FleetCostCapError, FleetManager, FleetNotifier, FleetSpawnBudgetError, FleetUsageAggregator, FsError, GitignoreUpdater, GlobalMailbox, GraphMemoryBackend, HEAVY_BUDGET, HookRegistry, HookRunner, HumanEscalatingBrainArbiter, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, KNOWLEDGE_AGENTS, KnowledgeGraph, LAYER_1_IDENTITY, LIGHT_BUDGET, LLMSelector, LargeAnswerStore, MATRIX_PHASE_KEYS, MAX_JOURNAL_ENTRIES, MAX_PROGRESS_HISTORY, MEDIUM_BUDGET, MEMORY_TYPE_LABELS, META_AGENTS, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, ObservableBrainArbiter, PLANNING_AGENTS, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, PhaseGraphBuilder, PhaseOrchestrator, PhaseStore, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, RecoveryLock, ReplayLogStore, ReplayProviderRunner, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, STANDARD_AUDIT_EVENTS, ScopedEventBus, SddError, SddParallelRun, SddTaskDecomposer, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SessionMemoryConsolidator, SessionRecovery, SessionRegistry, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, StreamHangError, SubagentBudget, TIMEOUT_PREEMPT_FRACTION, TOKENS, TaskAuctioneer, TaskDAG, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolAuditLog, ToolError, ToolExecutor, ToolRegistry, VERIFY_AGENTS, WorktreeManager, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyRosterBudget, asBlocks, asText, assertNever, assertNotPrivateHost, assertSafePath, atomicWrite, attachAutoExtend, attachDepWatcherBridge, attachMailboxChecker, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, bootConfig, braveSearchServer, buildBtwBlock, buildChildEnv, buildGoalPreamble, buildLosslessDigest, buildMailboxBlock, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildQueuedMessagesBlock, buildRecoveryStrategies, buildSmartDigest, classifyFamily, clearPlan, collabInjectMiddleware, collabPauseMiddleware, color, compactLog, compileGlob, compileUserRegex, completePartialObject, composeDirectorPrompt, composeSubagentPrompt, computeMessageTokens, computeTaskItemProgress, computeTaskProgress, consumeBtwNotes, consumeQueuedMessagesUpdate, context7Server, contextManagerTool, createAutoExecutor, createAutoPhaseFromTaskGraph, createAutonomyBrain, createChimeraPlugin, createContextManagerTool, createDefaultPipelines, createDelegateTool, createGitPlugin, createMailboxChecker, createMailboxHooks, createMcpControlTool, createMcpUseTool, createMessage, createObservabilityPlugin, createPlanPlugin, createPromptsPlugin, createSecurityPlugin, createSecuritySlashCommand, createSessionEventBridge, createSkillsPlugin, createStrategyCompactor, createSyncPlugin, createTieredBrainArbiter, createToolOutputSerializer, decryptConfigSecrets, deepMerge, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, definePlugin, deriveTodosFromPlanItem, detectEcosystem, detectNewlineStyle, detectEcosystem as detectPackageEcosystem, dispatchAgent, downloadGitHubTarball, eliseOldToolResults, emptyGoal, emptyPlan, emptyTaskFile, encryptConfigSecrets, encryptedPrefixForVersion, enhanceUserPrompt, ensureDir, estimateMessageTokens, estimateMessages, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, expandGlob, expandIPv6, expectDefined, extractRunEnv, extractText, filesystemServer, findCriticalPath, findPreserveStart, flagsToConfigPatch, formatContextWindowModeList, formatDecisionSummary, formatGoal, formatHumanPrompt, formatPlan, formatPlanTemplates, formatTaskList, formatTaskProgress, formatTodosList, getAgentDefinition, getCalibrationState, getContextWindowMode, getFileHistory, getFilesByAgent, getFullLog, getFullPackageLog, getLastAuthor, getManifestPackages, getPackageAuthor, getPackagesByAgent, getPlanTemplate, getSessionRegistry, getTemplate, getTermSize, githubServer, goalFilePath, googleMapsServer, hasSessionRegistry, hasTextContent, hashRequest, hookMatcherMatches, injectPendingMailboxMessages, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isInteractive, isPluginError, isPrimitiveArray, isPrivateIPv4, isPrivateIPv6, isSddError, isSecretField, isSessionError, isStdinTTY, isStdoutTTY, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isValidMatrixKey, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTasks, loadTodosCheckpoint, loadUserModes, mailboxSessionTag, makeAgentSubagentRunner, makeAskResultTool, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeContinueToNextIterationTool, makeDependencyWatcherConfig, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeMailInboxTool, makeMailSendTool, makeMailboxTool, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, matchAny, matchGlob, matrixKeyKind, mergeCustomModelDefs, mergeModelsPayload, migratePlaintextSecrets, miniMaxVisionServer, mutatePlan, mutateTasks, noOpLogger, noOpVault, normalizeRecipient, normalizeToLf, normalizeTokenSavingTier, normalizedEqual, onResize, parseContinueDirective, parseEncryptedVersion, parseEntries, parseProgressFromText, parseSkillRef, peekQueuedMessages, pendingBtwCount, phaseForRole, projectHash, projectSlug, recentTextTurns, recordActualUsage, recordFileAction, recordPackageAction, recordProgress, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resetCalibration, resolveAuditLevel, resolveChimeraConfig, resolveContextWindowPolicy, resolveMailboxIdentity, resolveModelMatrix, resolveProjectDir, resolveSessionLoggingConfig, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, rotateConfigKeys, runConfigMigrations, runProviderWithRetry, runShellHook, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTasks, saveTodosCheckpoint, scoreAgents, scoreMessage, securitySlashCommand, sentinelServer, setBtwNote, setOutputLineGuard, setPlanItemStatus, setProgress, setQueuedMessagesSnapshot, setRawMode, shouldEnhance, slackServer, sleep, stableStringify, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, startPackageOutdatedWatcher, startTechStackConsumer, stripAnsi, summarizeUsage, templateToMarkdown, toErrorMessage, toStyle, toWrongStackError, topologicalSort, truncate, unifiedDiff, unloadPlugins, updatePackageOutdatedStatus, validateAgainstSchema, wireMetricsToEvents, withDisabledToolFiltering, withFileLock, wrapAsState, writeErr, writeOut, wstackGlobalRoot, zaiVisionServer };
|
|
42067
43173
|
//# sourceMappingURL=index.js.map
|
|
42068
43174
|
//# sourceMappingURL=index.js.map
|