@wrongstack/core 0.308.7 → 0.309.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/coordination/director/director-toolset.d.ts +2 -2
- package/dist/coordination/director-mutation-test-tool.d.ts +29 -0
- package/dist/coordination/director-tools.d.ts +2 -0
- package/dist/coordination/director.d.ts +16 -0
- package/dist/coordination/explore-companion.d.ts +9 -6
- package/dist/coordination/fleet.d.ts +12 -0
- package/dist/coordination/index.d.ts +1 -1
- package/dist/coordination/index.js +990 -61
- package/dist/coordination/mail-tools.d.ts +10 -6
- package/dist/coordination/mailbox-codecs.d.ts +31 -0
- package/dist/coordination/multi-agent-coordinator.d.ts +14 -0
- package/dist/coordination/multi-agent-timeout.d.ts +11 -1
- package/dist/coordination/mutation-engine.d.ts +76 -0
- package/dist/coordination/subagent-budget.d.ts +54 -0
- package/dist/coordination/subagent-finish.d.ts +78 -0
- package/dist/core/index.js +58 -13
- package/dist/defaults/index.js +1132 -106
- package/dist/execution/index.js +260 -20
- package/dist/hq/index.js +45 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1456 -263
- package/dist/infrastructure/index.js +22 -3
- package/dist/kernel/events/agent-events.d.ts +31 -2
- package/dist/models/index.js +11 -1
- package/dist/observability/index.js +1 -1
- package/dist/plugin/index.js +113 -12
- package/dist/prompts/index.js +360 -3
- package/dist/security/auto-approve-policy.d.ts +2 -2
- package/dist/security/index.js +177 -50
- package/dist/security/permission-helpers.d.ts +11 -0
- package/dist/security/permission-policy.d.ts +10 -1
- package/dist/security/yolo-risk.d.ts +17 -0
- package/dist/session-catalog/index.js +32 -2
- package/dist/session-catalog/project-server.js +32 -2
- package/dist/skills/index.js +39 -6
- package/dist/storage/index.js +44 -3
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +14 -0
- package/dist/types/multi-agent.d.ts +15 -0
- package/dist/types/provider.d.ts +29 -1
- package/dist/types/tool.d.ts +15 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +54 -4
- package/dist/utils/terminal-sanitize.d.ts +41 -0
- package/dist/utils/tool-subject.d.ts +1 -1
- package/instructions/agents/chaos-monkey.md +61 -0
- package/package.json +4 -4
|
@@ -285,6 +285,46 @@ function createMessage(type, from, payload, to) {
|
|
|
285
285
|
};
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
// src/core/btw.ts
|
|
289
|
+
var META_KEY = "_btwNotes";
|
|
290
|
+
var MAX_PENDING = 20;
|
|
291
|
+
function readQueue(ctx) {
|
|
292
|
+
const raw = ctx.meta[META_KEY];
|
|
293
|
+
return Array.isArray(raw) ? raw : [];
|
|
294
|
+
}
|
|
295
|
+
function setBtwNote(ctx, text) {
|
|
296
|
+
const trimmed = text.trim();
|
|
297
|
+
if (!trimmed) return readQueue(ctx).length;
|
|
298
|
+
const next = [...readQueue(ctx), trimmed].slice(-MAX_PENDING);
|
|
299
|
+
ctx.meta[META_KEY] = next;
|
|
300
|
+
return next.length;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/coordination/subagent-finish.ts
|
|
304
|
+
var SUBAGENT_FINISH_REQUESTED_EVENT = "subagent.finish_requested";
|
|
305
|
+
var DEFAULT_SUBAGENT_FINISH_GRACE_MS = 12e4;
|
|
306
|
+
function resolveGracefulFinish(config) {
|
|
307
|
+
const raw = config.gracefulFinish;
|
|
308
|
+
if (raw === void 0 || raw === false) return void 0;
|
|
309
|
+
if (raw === true) return { graceMs: DEFAULT_SUBAGENT_FINISH_GRACE_MS };
|
|
310
|
+
const graceMs = typeof raw.graceMs === "number" && Number.isFinite(raw.graceMs) && raw.graceMs > 0 ? Math.floor(raw.graceMs) : DEFAULT_SUBAGENT_FINISH_GRACE_MS;
|
|
311
|
+
return { graceMs };
|
|
312
|
+
}
|
|
313
|
+
function buildSubagentFinishNotice(input) {
|
|
314
|
+
const localTime = new Date(input.deadlineMs).toISOString();
|
|
315
|
+
const seconds = Math.max(1, Math.round(input.graceMs / 1e3));
|
|
316
|
+
const timeLeft = input.graceMs > 0 ? `You have roughly ${seconds} seconds (until ${localTime}) of legitimate working time left.` : `Your working-time window is already spent (deadline was ${localTime}) \u2014 finish now.`;
|
|
317
|
+
return [
|
|
318
|
+
"[SUBAGENT FINISH] The leader agent has finished its work.",
|
|
319
|
+
`Reason: ${input.reason}`,
|
|
320
|
+
timeLeft,
|
|
321
|
+
"Finish your task now, in this turn: complete the thought you are working on, stop",
|
|
322
|
+
"starting new tool calls unless one is strictly required to finish, and write your",
|
|
323
|
+
"final answer or report as your final output, then end your turn.",
|
|
324
|
+
"Do not restart the task and do not begin new work."
|
|
325
|
+
].join("\n");
|
|
326
|
+
}
|
|
327
|
+
|
|
288
328
|
// src/coordination/subagent-budget.ts
|
|
289
329
|
var TIMEOUT_PREEMPT_FRACTION = 0.85;
|
|
290
330
|
var DECISION_TIMEOUT_MS = 6e4;
|
|
@@ -342,6 +382,82 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
342
382
|
this.limits.idleTimeoutMs = ext.idleTimeoutMs;
|
|
343
383
|
}
|
|
344
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* Graceful-finish state (see coordination/subagent-finish.ts).
|
|
387
|
+
* `_finishNotified` guards the single in-band emission; `_grace` records a
|
|
388
|
+
* granted working-time extension past the original wall-clock deadline.
|
|
389
|
+
* They are separate because the two callers want different semantics:
|
|
390
|
+
* the watchdog grants grace at the deadline crossing (notify + extend),
|
|
391
|
+
* while an explicit leader-finished request only notifies — a subagent
|
|
392
|
+
* well inside its budget keeps its full legitimate working time and simply
|
|
393
|
+
* accelerates.
|
|
394
|
+
*/
|
|
395
|
+
_finishNotified = false;
|
|
396
|
+
_grace = null;
|
|
397
|
+
/** True once the in-band finish notification has been emitted. */
|
|
398
|
+
get finishNotified() {
|
|
399
|
+
return this._finishNotified;
|
|
400
|
+
}
|
|
401
|
+
/** True once a grace window has been granted past the original deadline. */
|
|
402
|
+
get graceGranted() {
|
|
403
|
+
return this._grace !== null;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Notify the subagent in-band to finish its task in its own turn:
|
|
407
|
+
* `subagent.finish_requested` is emitted on the wired EventBus and the
|
|
408
|
+
* agent loop folds the notice into the conversation between tool batches.
|
|
409
|
+
* Nothing aborts — this is a notification, never an interrupt.
|
|
410
|
+
*
|
|
411
|
+
* `opts.graceMs` additionally extends the wall-clock ceiling by that window
|
|
412
|
+
* (used by the watchdog at a deadline crossing, so the model gets working
|
|
413
|
+
* time instead of a kill). Omit it to notify without touching the budget —
|
|
414
|
+
* the subagent keeps its existing time budget and just accelerates.
|
|
415
|
+
*
|
|
416
|
+
* Returns `true` when this call did something (emitted the notification
|
|
417
|
+
* and/or granted grace); `false` when there was nothing to do (already
|
|
418
|
+
* notified, grace already granted, no EventBus wired, budget not started).
|
|
419
|
+
*/
|
|
420
|
+
notifyFinish(reason, opts, now = Date.now) {
|
|
421
|
+
if (!this._events) return false;
|
|
422
|
+
if (this.startTime === null) return false;
|
|
423
|
+
const shouldEmit = !this._finishNotified;
|
|
424
|
+
const rawGrace = opts?.graceMs;
|
|
425
|
+
const shouldGrant = rawGrace !== void 0 && this._grace === null;
|
|
426
|
+
if (!shouldEmit && !shouldGrant) return false;
|
|
427
|
+
let grantedGraceMs = 0;
|
|
428
|
+
let graceDeadlineMs;
|
|
429
|
+
if (shouldGrant && rawGrace !== void 0) {
|
|
430
|
+
grantedGraceMs = Number.isFinite(rawGrace) && rawGrace > 0 ? Math.floor(rawGrace) : DEFAULT_SUBAGENT_FINISH_GRACE_MS;
|
|
431
|
+
graceDeadlineMs = now() + grantedGraceMs;
|
|
432
|
+
this._grace = { deadlineMs: graceDeadlineMs, graceMs: grantedGraceMs };
|
|
433
|
+
this.patchLimits({ timeoutMs: graceDeadlineMs - this.startTime });
|
|
434
|
+
}
|
|
435
|
+
if (shouldEmit) {
|
|
436
|
+
this._finishNotified = true;
|
|
437
|
+
const effectiveDeadlineMs = graceDeadlineMs ?? (this.limits.timeoutMs !== void 0 ? this.startTime + this.limits.timeoutMs : now() + DEFAULT_SUBAGENT_FINISH_GRACE_MS);
|
|
438
|
+
const effectiveGraceMs = Math.max(0, effectiveDeadlineMs - now());
|
|
439
|
+
const subagentId = this._subagentId;
|
|
440
|
+
this._events.emit(SUBAGENT_FINISH_REQUESTED_EVENT, {
|
|
441
|
+
// Omitted entirely when the budget was built without an id — an
|
|
442
|
+
// empty string is an address that matches nothing.
|
|
443
|
+
...subagentId !== void 0 ? { subagentId } : {},
|
|
444
|
+
reason,
|
|
445
|
+
deadlineMs: effectiveDeadlineMs,
|
|
446
|
+
graceMs: effectiveGraceMs,
|
|
447
|
+
notice: buildSubagentFinishNotice({
|
|
448
|
+
reason,
|
|
449
|
+
deadlineMs: effectiveDeadlineMs,
|
|
450
|
+
graceMs: effectiveGraceMs
|
|
451
|
+
})
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
/** Epoch ms by which the subagent should have produced its final output,
|
|
457
|
+
* once a grace window was granted. Undefined before that. */
|
|
458
|
+
get finishDeadlineMs() {
|
|
459
|
+
return this._grace?.deadlineMs;
|
|
460
|
+
}
|
|
345
461
|
iterations = 0;
|
|
346
462
|
toolCalls = 0;
|
|
347
463
|
tokenInput = 0;
|
|
@@ -357,6 +473,10 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
357
473
|
lastActivityTime = null;
|
|
358
474
|
_onThreshold;
|
|
359
475
|
_sessionId;
|
|
476
|
+
/** Owning subagent id — used to address the graceful-finish event. */
|
|
477
|
+
_subagentId;
|
|
478
|
+
/** True when only the coordinator watchdog may enforce wall-clock limits. */
|
|
479
|
+
_wallClockWatchdogOwned;
|
|
360
480
|
/**
|
|
361
481
|
* Hard cap on how long `_negotiateExtension` waits for the coordinator to
|
|
362
482
|
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
@@ -428,6 +548,8 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
428
548
|
constructor(limits = {}, mode = "auto", options = {}) {
|
|
429
549
|
this._mode = mode;
|
|
430
550
|
this._sessionId = options.sessionId;
|
|
551
|
+
this._subagentId = options.subagentId;
|
|
552
|
+
this._wallClockWatchdogOwned = options.wallClockWatchdogOwned === true;
|
|
431
553
|
this.limits = { ...limits };
|
|
432
554
|
}
|
|
433
555
|
currentSessionId() {
|
|
@@ -506,7 +628,7 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
506
628
|
if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
|
|
507
629
|
exceeded.push({ kind: "idle_timeout", used: idle, limit: this.limits.idleTimeoutMs });
|
|
508
630
|
}
|
|
509
|
-
const wallOwnedByWatchdog = this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
|
|
631
|
+
const wallOwnedByWatchdog = this._wallClockWatchdogOwned || this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
|
|
510
632
|
if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs && !wallOwnedByWatchdog) {
|
|
511
633
|
exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
|
|
512
634
|
}
|
|
@@ -705,7 +827,7 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
705
827
|
if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
|
|
706
828
|
const elapsed = Date.now() - this.startTime;
|
|
707
829
|
const wallSkipped = this._onThreshold !== void 0 && this._watchdogActive !== void 0 && timeoutMs !== void 0 && this._watchdogActive === timeoutMs;
|
|
708
|
-
const wallTripped = wallSkipped ? false : timeoutMs !== void 0 && elapsed > timeoutMs;
|
|
830
|
+
const wallTripped = this._wallClockWatchdogOwned || wallSkipped ? false : timeoutMs !== void 0 && elapsed > timeoutMs;
|
|
709
831
|
const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
|
|
710
832
|
if (!wallTripped && !idleTripped) return;
|
|
711
833
|
void this.checkLimits(elapsed);
|
|
@@ -1165,6 +1287,14 @@ function makeAgentSubagentRunner(opts) {
|
|
|
1165
1287
|
);
|
|
1166
1288
|
const onParentAbort = () => aborter.abort();
|
|
1167
1289
|
ctx.signal.addEventListener("abort", onParentAbort);
|
|
1290
|
+
if (resolveGracefulFinish(ctx.config)) {
|
|
1291
|
+
unsub.push(
|
|
1292
|
+
events.on("subagent.finish_requested", (e) => {
|
|
1293
|
+
if (e.subagentId && e.subagentId !== ctx.subagentId) return;
|
|
1294
|
+
setBtwNote(agent.ctx, e.notice);
|
|
1295
|
+
})
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1168
1298
|
let result;
|
|
1169
1299
|
try {
|
|
1170
1300
|
result = await agent.run(format(task, ctx.config), { signal: aborter.signal });
|
|
@@ -8469,6 +8599,14 @@ var PATTERNS = [
|
|
|
8469
8599
|
anchor: "sk-ant-"
|
|
8470
8600
|
},
|
|
8471
8601
|
{ type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g, anchor: "sk-" },
|
|
8602
|
+
{
|
|
8603
|
+
// `xai` is a first-class provider in this codebase, but its key shape was
|
|
8604
|
+
// absent here — so the one credential format WrongStack itself hands users
|
|
8605
|
+
// was the one the scrubber could not recognize (audit 2026-08-20).
|
|
8606
|
+
type: "xai_key",
|
|
8607
|
+
regex: /(?<![A-Za-z0-9])xai-[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
|
|
8608
|
+
anchor: "xai-"
|
|
8609
|
+
},
|
|
8472
8610
|
{ type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g, anchor: "ghp_" },
|
|
8473
8611
|
{ type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g, anchor: "github_pat_" },
|
|
8474
8612
|
{ type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g, anchor: "AKIA" },
|
|
@@ -8559,8 +8697,8 @@ var PATTERNS = [
|
|
|
8559
8697
|
// replacement so the separator between adjacent secrets is preserved
|
|
8560
8698
|
// rather than collapsed. Capture groups are therefore: 1=leading
|
|
8561
8699
|
// delimiter, 2=key name, 3=value.
|
|
8562
|
-
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
8563
|
-
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
|
|
8700
|
+
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD|PASSPHRASE))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
8701
|
+
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD", "PASSPHRASE"]
|
|
8564
8702
|
},
|
|
8565
8703
|
{
|
|
8566
8704
|
type: "json_credential_key",
|
|
@@ -8677,6 +8815,27 @@ var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key
|
|
|
8677
8815
|
var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
|
|
8678
8816
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
8679
8817
|
var SCRUB_OVERLAP_BYTES = 1024;
|
|
8818
|
+
var PEM_PRIVATE_KEY_BEGIN_RE = /-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----/;
|
|
8819
|
+
var PEM_END_MARKER = "-----END";
|
|
8820
|
+
var MAX_PEM_BLOCK_BYTES = 64 * 1024;
|
|
8821
|
+
var PEM_END_LINE_TOLERANCE = 64;
|
|
8822
|
+
function extendChunkBoundaryPastPem(text, chunkStart, proposedEnd) {
|
|
8823
|
+
const head = text.slice(chunkStart, proposedEnd);
|
|
8824
|
+
const lastBegin = head.lastIndexOf("-----BEGIN ");
|
|
8825
|
+
if (lastBegin === -1) return proposedEnd;
|
|
8826
|
+
const fromBegin = text.slice(chunkStart + lastBegin);
|
|
8827
|
+
const marker = PEM_PRIVATE_KEY_BEGIN_RE.exec(fromBegin);
|
|
8828
|
+
if (!marker || marker.index !== 0) return proposedEnd;
|
|
8829
|
+
const bodyStart = marker[0].length;
|
|
8830
|
+
const cap = Math.min(text.length, chunkStart + lastBegin + MAX_PEM_BLOCK_BYTES);
|
|
8831
|
+
const closeIdx = fromBegin.indexOf(PEM_END_MARKER, bodyStart);
|
|
8832
|
+
if (closeIdx === -1 || chunkStart + lastBegin + closeIdx >= cap + PEM_END_LINE_TOLERANCE) {
|
|
8833
|
+
return proposedEnd;
|
|
8834
|
+
}
|
|
8835
|
+
const lineEnd = fromBegin.indexOf("\n", closeIdx);
|
|
8836
|
+
const end = lineEnd === -1 ? text.length : chunkStart + lastBegin + lineEnd + 1;
|
|
8837
|
+
return Math.max(proposedEnd, end);
|
|
8838
|
+
}
|
|
8680
8839
|
var PATTERN_ANCHORS = [
|
|
8681
8840
|
...new Set(
|
|
8682
8841
|
PATTERNS.flatMap(
|
|
@@ -8713,6 +8872,7 @@ var DefaultSecretScrubber = class {
|
|
|
8713
8872
|
}
|
|
8714
8873
|
}
|
|
8715
8874
|
end = safe === -1 ? end : safe + 1;
|
|
8875
|
+
end = extendChunkBoundaryPastPem(text, i, end);
|
|
8716
8876
|
}
|
|
8717
8877
|
out.push(this.scrubOne(text.slice(i, end)));
|
|
8718
8878
|
i = end;
|
|
@@ -10412,6 +10572,22 @@ var EXPLORE_COMPANION_AGENT = {
|
|
|
10412
10572
|
textStream: "silent",
|
|
10413
10573
|
toolStream: "silent"
|
|
10414
10574
|
};
|
|
10575
|
+
var CHAOS_MONKEY_AGENT = {
|
|
10576
|
+
...defineAgent("chaos-monkey", "Chaos Monkey"),
|
|
10577
|
+
tools: [...TOOLS.build],
|
|
10578
|
+
skillNames: ["testing", "typescript-strict"],
|
|
10579
|
+
spawnBudgetExempt: true,
|
|
10580
|
+
// Run in the live checkout: mutation targets are usually freshly
|
|
10581
|
+
// written and uncommitted — a worktree spawned from HEAD would not
|
|
10582
|
+
// contain them and every mutant would drift. The mutation_test tool
|
|
10583
|
+
// honors this value as its default; callers can still override per
|
|
10584
|
+
// call via its `chaosWorktree` input when targets are committed and
|
|
10585
|
+
// isolation is wanted.
|
|
10586
|
+
worktree: "off",
|
|
10587
|
+
// Report travels via submit_result + final text, not the leader's stream.
|
|
10588
|
+
textStream: "silent",
|
|
10589
|
+
toolStream: "silent"
|
|
10590
|
+
};
|
|
10415
10591
|
var CRITIC_AGENT = defineAgent("critic", "Critic");
|
|
10416
10592
|
var GENERIC_AGENT = defineAgent("generic", "Generic Project Agent");
|
|
10417
10593
|
function withDispatchMetadata(definition) {
|
|
@@ -10431,6 +10607,7 @@ var FLEET_ROSTER = {
|
|
|
10431
10607
|
generic: GENERIC_AGENT,
|
|
10432
10608
|
"shadow-agent": SHADOW_AGENT,
|
|
10433
10609
|
"explore-companion": EXPLORE_COMPANION_AGENT,
|
|
10610
|
+
"chaos-monkey": CHAOS_MONKEY_AGENT,
|
|
10434
10611
|
...Object.fromEntries(
|
|
10435
10612
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, withDispatchMetadata(d)])
|
|
10436
10613
|
)
|
|
@@ -10461,6 +10638,16 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
10461
10638
|
maxTokens: 96e3,
|
|
10462
10639
|
maxCostUsd: 0.5
|
|
10463
10640
|
},
|
|
10641
|
+
"chaos-monkey": {
|
|
10642
|
+
// A mutation pass is many short apply/run/restore cycles — per-mutant
|
|
10643
|
+
// work is tiny, but a large plan (25 mutants/file × N files) needs
|
|
10644
|
+
// headroom. Idle-based reaping covers a stalled pass.
|
|
10645
|
+
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
10646
|
+
maxIterations: 2e3,
|
|
10647
|
+
maxToolCalls: 6e3,
|
|
10648
|
+
maxTokens: 96e3,
|
|
10649
|
+
maxCostUsd: 0.5
|
|
10650
|
+
},
|
|
10464
10651
|
...Object.fromEntries(
|
|
10465
10652
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
|
|
10466
10653
|
)
|
|
@@ -11571,16 +11758,14 @@ var ExploreCompanion = class {
|
|
|
11571
11758
|
this.running = true;
|
|
11572
11759
|
this.unsubscribers.push(
|
|
11573
11760
|
this.opts.events.on("tool.executed", (e) => {
|
|
11574
|
-
|
|
11575
|
-
if (lsid && e.sessionId && e.sessionId !== lsid) return;
|
|
11761
|
+
if (e.sessionId !== this.resolveLeaderSessionId()) return;
|
|
11576
11762
|
this.trackToolExecuted(e);
|
|
11577
11763
|
})
|
|
11578
11764
|
);
|
|
11579
11765
|
if (this.cfg.signals.todoInProgress && this.resolveLeaderAgentId()) {
|
|
11580
11766
|
this.unsubscribers.push(
|
|
11581
11767
|
this.opts.events.on("session.agents_updated", (e) => {
|
|
11582
|
-
|
|
11583
|
-
if (lsid && e.sessionId && e.sessionId !== lsid) return;
|
|
11768
|
+
if (e.sessionId !== this.resolveLeaderSessionId()) return;
|
|
11584
11769
|
this.trackAgentTodos(e.agents);
|
|
11585
11770
|
})
|
|
11586
11771
|
);
|
|
@@ -11588,8 +11773,7 @@ var ExploreCompanion = class {
|
|
|
11588
11773
|
if (this.cfg.signals.errorSymbol) {
|
|
11589
11774
|
this.unsubscribers.push(
|
|
11590
11775
|
this.opts.events.on("error", (e) => {
|
|
11591
|
-
|
|
11592
|
-
if (lsid && e.sessionId && e.sessionId !== lsid) return;
|
|
11776
|
+
if (e.sessionId !== this.resolveLeaderSessionId()) return;
|
|
11593
11777
|
this.trackError(e.err);
|
|
11594
11778
|
})
|
|
11595
11779
|
);
|
|
@@ -11702,9 +11886,18 @@ var ExploreCompanion = class {
|
|
|
11702
11886
|
limit: 20
|
|
11703
11887
|
});
|
|
11704
11888
|
const lsid = this.resolveLeaderSessionId();
|
|
11889
|
+
const selfRecipients = new Set(
|
|
11890
|
+
[
|
|
11891
|
+
this.cfg.companionAgentId,
|
|
11892
|
+
mailboxIdentityBase(this.cfg.companionAgentId),
|
|
11893
|
+
...lsid != null ? [sessionRecipient(lsid)] : []
|
|
11894
|
+
].map((r) => r.toLowerCase())
|
|
11895
|
+
);
|
|
11705
11896
|
for (const msg of messages) {
|
|
11706
11897
|
if (msg.type !== "ask" && msg.type !== "assign") continue;
|
|
11707
|
-
const
|
|
11898
|
+
const to = msg.to.trim().toLowerCase();
|
|
11899
|
+
if (to !== "*" && !selfRecipients.has(to)) continue;
|
|
11900
|
+
const fromLeader = msg.senderSessionId === void 0 && isMailboxLeader(msg.from) || lsid != null && msg.senderSessionId === lsid;
|
|
11708
11901
|
if (!fromLeader) continue;
|
|
11709
11902
|
this.engage({
|
|
11710
11903
|
id: randomUUID6(),
|
|
@@ -11913,7 +12106,7 @@ function attachDepWatcherBridge(opts) {
|
|
|
11913
12106
|
}
|
|
11914
12107
|
|
|
11915
12108
|
// src/coordination/director.ts
|
|
11916
|
-
import { randomUUID as
|
|
12109
|
+
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
11917
12110
|
import * as fsp25 from "node:fs/promises";
|
|
11918
12111
|
|
|
11919
12112
|
// src/core/instruction-template.ts
|
|
@@ -12988,7 +13181,7 @@ ${JSON.stringify(result.result, null, 2)}
|
|
|
12988
13181
|
};
|
|
12989
13182
|
|
|
12990
13183
|
// src/coordination/director-tools.ts
|
|
12991
|
-
import { randomUUID as
|
|
13184
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
12992
13185
|
import {
|
|
12993
13186
|
completeKanbanDispatch,
|
|
12994
13187
|
failKanbanDispatch,
|
|
@@ -14188,6 +14381,626 @@ function excerpt(text, max) {
|
|
|
14188
14381
|
...(truncated)`;
|
|
14189
14382
|
}
|
|
14190
14383
|
|
|
14384
|
+
// src/coordination/director-mutation-test-tool.ts
|
|
14385
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
14386
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
14387
|
+
import { isAbsolute as isAbsolute3, join as join16 } from "node:path";
|
|
14388
|
+
|
|
14389
|
+
// src/coordination/mutation-engine.ts
|
|
14390
|
+
var TOKEN_PATTERNS = [
|
|
14391
|
+
{
|
|
14392
|
+
kind: "relax-boundary",
|
|
14393
|
+
// `>` not followed by `=` and not part of `=>` or `>>`; require code-ish
|
|
14394
|
+
// context on both sides so generic text (JSX, strings) is not touched.
|
|
14395
|
+
regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>>(?!=|>))/g,
|
|
14396
|
+
replace: () => ">="
|
|
14397
|
+
},
|
|
14398
|
+
{
|
|
14399
|
+
kind: "tighten-boundary",
|
|
14400
|
+
regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>>=)/g,
|
|
14401
|
+
replace: () => ">"
|
|
14402
|
+
},
|
|
14403
|
+
{
|
|
14404
|
+
kind: "arith-plus-to-minus",
|
|
14405
|
+
// `+` between operands (binary), not `++`, unary `+x`, or `+=`.
|
|
14406
|
+
regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>\+(?!\+|=))/g,
|
|
14407
|
+
replace: () => "-"
|
|
14408
|
+
},
|
|
14409
|
+
{
|
|
14410
|
+
kind: "arith-minus-to-plus",
|
|
14411
|
+
// Binary `-` between operands, not `--`, `-=` or negative-number literal.
|
|
14412
|
+
regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>-(?!-|=))/g,
|
|
14413
|
+
replace: () => "+"
|
|
14414
|
+
},
|
|
14415
|
+
{
|
|
14416
|
+
kind: "negate-boolean",
|
|
14417
|
+
// Standalone boolean literals used as values, not property names.
|
|
14418
|
+
regex: /(?<![.\w$])(?<op>true|false)(?![\w$])/g,
|
|
14419
|
+
replace: (m) => m === "true" ? "false" : "true"
|
|
14420
|
+
},
|
|
14421
|
+
{
|
|
14422
|
+
kind: "return-null",
|
|
14423
|
+
// `return <expr>;` where expr is not already null/undefined/void.
|
|
14424
|
+
regex: /(?<indent>\breturn\b)(?<expr>\s+[^;{}\n]+?)\s*;/g,
|
|
14425
|
+
replace: () => "return null;",
|
|
14426
|
+
endpointsInCode: true
|
|
14427
|
+
}
|
|
14428
|
+
];
|
|
14429
|
+
function planMutations(file, source, opts = {}) {
|
|
14430
|
+
const maxPerFile = opts.maxPerFile ?? 25;
|
|
14431
|
+
const out = [];
|
|
14432
|
+
const lines = source.split("\n");
|
|
14433
|
+
const masks = computeLineMasks(source);
|
|
14434
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
14435
|
+
const line = lines[lineIdx];
|
|
14436
|
+
const t = line.trim();
|
|
14437
|
+
if (t.startsWith("//")) continue;
|
|
14438
|
+
const codeRanges = masks[lineIdx];
|
|
14439
|
+
const inCode = (start) => codeRanges.some(([s, e]) => start >= s && start < e);
|
|
14440
|
+
for (const pattern of TOKEN_PATTERNS) {
|
|
14441
|
+
pattern.regex.lastIndex = 0;
|
|
14442
|
+
let m;
|
|
14443
|
+
while ((m = pattern.regex.exec(line)) !== null) {
|
|
14444
|
+
const token = m.groups?.["op"] ?? m[0];
|
|
14445
|
+
const tokenStart = m.index + m[0].indexOf(token);
|
|
14446
|
+
if (!inCode(tokenStart)) continue;
|
|
14447
|
+
if (pattern.endpointsInCode && !inCode(tokenStart + token.length - 1)) continue;
|
|
14448
|
+
const original = line.slice(tokenStart, tokenStart + token.length);
|
|
14449
|
+
const replacement = pattern.replace(token);
|
|
14450
|
+
if (replacement === original) continue;
|
|
14451
|
+
out.push({
|
|
14452
|
+
id: `${pattern.kind}#${lineIdx + 1}#${tokenStart + 1}`,
|
|
14453
|
+
kind: pattern.kind,
|
|
14454
|
+
file,
|
|
14455
|
+
line: lineIdx + 1,
|
|
14456
|
+
column: tokenStart + 1,
|
|
14457
|
+
original,
|
|
14458
|
+
replacement
|
|
14459
|
+
});
|
|
14460
|
+
}
|
|
14461
|
+
}
|
|
14462
|
+
if (out.length >= maxPerFile) break;
|
|
14463
|
+
}
|
|
14464
|
+
return out.slice(0, maxPerFile);
|
|
14465
|
+
}
|
|
14466
|
+
function computeLineMasks(source) {
|
|
14467
|
+
const lines = source.split("\n");
|
|
14468
|
+
const masks = lines.map(() => []);
|
|
14469
|
+
const stack = [{ kind: "code", depth: 0, parens: [] }];
|
|
14470
|
+
let inBlockComment = false;
|
|
14471
|
+
let lastToken = null;
|
|
14472
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
14473
|
+
const line = lines[lineIdx];
|
|
14474
|
+
const ranges = masks[lineIdx];
|
|
14475
|
+
let runStart = null;
|
|
14476
|
+
const closeRun = (end) => {
|
|
14477
|
+
if (runStart !== null && end > runStart) ranges.push([runStart, end]);
|
|
14478
|
+
runStart = null;
|
|
14479
|
+
};
|
|
14480
|
+
let i = 0;
|
|
14481
|
+
if (inBlockComment) {
|
|
14482
|
+
const close = line.indexOf("*/");
|
|
14483
|
+
if (close === -1) continue;
|
|
14484
|
+
inBlockComment = false;
|
|
14485
|
+
i = close + 2;
|
|
14486
|
+
}
|
|
14487
|
+
while (i < line.length) {
|
|
14488
|
+
const top = stack[stack.length - 1];
|
|
14489
|
+
const c = line[i];
|
|
14490
|
+
if (top.kind === "template") {
|
|
14491
|
+
if (c === "\\") {
|
|
14492
|
+
i += 2;
|
|
14493
|
+
continue;
|
|
14494
|
+
}
|
|
14495
|
+
if (c === "`") {
|
|
14496
|
+
stack.pop();
|
|
14497
|
+
lastToken = "`";
|
|
14498
|
+
i++;
|
|
14499
|
+
continue;
|
|
14500
|
+
}
|
|
14501
|
+
if (c === "$" && line[i + 1] === "{") {
|
|
14502
|
+
stack.push({ kind: "code", depth: 0, parens: [] });
|
|
14503
|
+
lastToken = "${";
|
|
14504
|
+
i += 2;
|
|
14505
|
+
continue;
|
|
14506
|
+
}
|
|
14507
|
+
i++;
|
|
14508
|
+
continue;
|
|
14509
|
+
}
|
|
14510
|
+
if (/[\w$]/.test(c)) {
|
|
14511
|
+
let j = i + 1;
|
|
14512
|
+
while (j < line.length && /[\w$]/.test(line[j])) j++;
|
|
14513
|
+
lastToken = line.slice(i, j);
|
|
14514
|
+
if (runStart === null) runStart = i;
|
|
14515
|
+
i = j;
|
|
14516
|
+
continue;
|
|
14517
|
+
}
|
|
14518
|
+
if (c === "'" || c === '"') {
|
|
14519
|
+
closeRun(i);
|
|
14520
|
+
i++;
|
|
14521
|
+
while (i < line.length && line[i] !== c) {
|
|
14522
|
+
if (line[i] === "\\") i++;
|
|
14523
|
+
i++;
|
|
14524
|
+
}
|
|
14525
|
+
i++;
|
|
14526
|
+
lastToken = c;
|
|
14527
|
+
continue;
|
|
14528
|
+
}
|
|
14529
|
+
if (c === "`") {
|
|
14530
|
+
closeRun(i);
|
|
14531
|
+
stack.push({ kind: "template", depth: 0, parens: [] });
|
|
14532
|
+
i++;
|
|
14533
|
+
continue;
|
|
14534
|
+
}
|
|
14535
|
+
if (c === "/" && line[i + 1] === "/") {
|
|
14536
|
+
closeRun(i);
|
|
14537
|
+
break;
|
|
14538
|
+
}
|
|
14539
|
+
if (c === "/" && line[i + 1] === "*") {
|
|
14540
|
+
closeRun(i);
|
|
14541
|
+
const close = line.indexOf("*/", i + 2);
|
|
14542
|
+
if (close === -1) {
|
|
14543
|
+
inBlockComment = true;
|
|
14544
|
+
break;
|
|
14545
|
+
}
|
|
14546
|
+
i = close + 2;
|
|
14547
|
+
continue;
|
|
14548
|
+
}
|
|
14549
|
+
if (c === "/") {
|
|
14550
|
+
if (!tokenCanEndOperand(lastToken)) {
|
|
14551
|
+
closeRun(i);
|
|
14552
|
+
const next = skipRegexLiteral(line, i);
|
|
14553
|
+
lastToken = next > i + 1 ? "regex" : "/";
|
|
14554
|
+
i = next;
|
|
14555
|
+
continue;
|
|
14556
|
+
}
|
|
14557
|
+
}
|
|
14558
|
+
if (c === "(") {
|
|
14559
|
+
top.parens.push(CONTROL_KEYWORDS.has(lastToken ?? "") ? "control" : "expr");
|
|
14560
|
+
lastToken = c;
|
|
14561
|
+
} else if (c === ")") {
|
|
14562
|
+
const kind = top.parens.pop() ?? "expr";
|
|
14563
|
+
lastToken = kind === "control" ? "control-paren-close" : ")";
|
|
14564
|
+
} else if (c === "{") {
|
|
14565
|
+
top.depth++;
|
|
14566
|
+
lastToken = c;
|
|
14567
|
+
} else if (c === "}") {
|
|
14568
|
+
if (top.depth > 0) {
|
|
14569
|
+
top.depth--;
|
|
14570
|
+
lastToken = c;
|
|
14571
|
+
} else if (stack.length > 1) {
|
|
14572
|
+
closeRun(i);
|
|
14573
|
+
stack.pop();
|
|
14574
|
+
i++;
|
|
14575
|
+
continue;
|
|
14576
|
+
} else {
|
|
14577
|
+
lastToken = c;
|
|
14578
|
+
}
|
|
14579
|
+
} else if (c !== " " && c !== " " && c !== "\r") {
|
|
14580
|
+
lastToken = c;
|
|
14581
|
+
}
|
|
14582
|
+
if (runStart === null) runStart = i;
|
|
14583
|
+
i++;
|
|
14584
|
+
}
|
|
14585
|
+
closeRun(line.length);
|
|
14586
|
+
}
|
|
14587
|
+
return masks;
|
|
14588
|
+
}
|
|
14589
|
+
var KEYWORDS_BEFORE_REGEX = /* @__PURE__ */ new Set([
|
|
14590
|
+
"return",
|
|
14591
|
+
"typeof",
|
|
14592
|
+
"instanceof",
|
|
14593
|
+
"in",
|
|
14594
|
+
"of",
|
|
14595
|
+
"new",
|
|
14596
|
+
"delete",
|
|
14597
|
+
"void",
|
|
14598
|
+
"throw",
|
|
14599
|
+
"case",
|
|
14600
|
+
"do",
|
|
14601
|
+
"else",
|
|
14602
|
+
"yield",
|
|
14603
|
+
"await"
|
|
14604
|
+
]);
|
|
14605
|
+
var CONTROL_KEYWORDS = /* @__PURE__ */ new Set(["if", "for", "while", "switch", "catch", "with", "await"]);
|
|
14606
|
+
function tokenCanEndOperand(token) {
|
|
14607
|
+
if (token === null) return false;
|
|
14608
|
+
if (/^[\w$]+$/.test(token)) return !KEYWORDS_BEFORE_REGEX.has(token);
|
|
14609
|
+
return token === ")" || token === "]" || token === "." || token === '"' || token === "'" || token === "`";
|
|
14610
|
+
}
|
|
14611
|
+
function skipRegexLiteral(line, start) {
|
|
14612
|
+
let i = start + 1;
|
|
14613
|
+
let inClass = false;
|
|
14614
|
+
while (i < line.length) {
|
|
14615
|
+
const ch = line[i];
|
|
14616
|
+
if (ch === "\\") {
|
|
14617
|
+
i += 2;
|
|
14618
|
+
continue;
|
|
14619
|
+
}
|
|
14620
|
+
if (inClass) {
|
|
14621
|
+
if (ch === "]") inClass = false;
|
|
14622
|
+
i++;
|
|
14623
|
+
continue;
|
|
14624
|
+
}
|
|
14625
|
+
if (ch === "[") {
|
|
14626
|
+
inClass = true;
|
|
14627
|
+
i++;
|
|
14628
|
+
continue;
|
|
14629
|
+
}
|
|
14630
|
+
if (ch === "/") {
|
|
14631
|
+
i++;
|
|
14632
|
+
break;
|
|
14633
|
+
}
|
|
14634
|
+
if (ch === "\n" || ch === "\r") return line.length;
|
|
14635
|
+
i++;
|
|
14636
|
+
}
|
|
14637
|
+
while (i < line.length && /[a-z]/.test(line[i])) i++;
|
|
14638
|
+
return i;
|
|
14639
|
+
}
|
|
14640
|
+
function parseMutationReport(text) {
|
|
14641
|
+
const candidates = [];
|
|
14642
|
+
const fence = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
14643
|
+
if (fence?.[1]) candidates.push(fence[1].trim());
|
|
14644
|
+
const firstBrace = text.indexOf("{");
|
|
14645
|
+
if (firstBrace >= 0) candidates.push(extractBalancedObject(text, firstBrace));
|
|
14646
|
+
for (const candidate of candidates) {
|
|
14647
|
+
if (!candidate) continue;
|
|
14648
|
+
try {
|
|
14649
|
+
const parsed = JSON.parse(candidate);
|
|
14650
|
+
if (!Array.isArray(parsed.mutants)) continue;
|
|
14651
|
+
return {
|
|
14652
|
+
mutants: parsed.mutants.map(normalizeMutantEntry).filter((x) => Boolean(x)),
|
|
14653
|
+
summary: typeof parsed.summary === "string" ? parsed.summary : void 0
|
|
14654
|
+
};
|
|
14655
|
+
} catch {
|
|
14656
|
+
}
|
|
14657
|
+
}
|
|
14658
|
+
return void 0;
|
|
14659
|
+
}
|
|
14660
|
+
function extractBalancedObject(text, start) {
|
|
14661
|
+
let depth = 0;
|
|
14662
|
+
let inString = false;
|
|
14663
|
+
let escaped = false;
|
|
14664
|
+
for (let i = start; i < text.length; i++) {
|
|
14665
|
+
const c = text[i];
|
|
14666
|
+
if (escaped) {
|
|
14667
|
+
escaped = false;
|
|
14668
|
+
continue;
|
|
14669
|
+
}
|
|
14670
|
+
if (c === "\\") {
|
|
14671
|
+
escaped = true;
|
|
14672
|
+
continue;
|
|
14673
|
+
}
|
|
14674
|
+
if (c === '"') inString = !inString;
|
|
14675
|
+
if (inString) continue;
|
|
14676
|
+
if (c === "{") depth++;
|
|
14677
|
+
else if (c === "}") {
|
|
14678
|
+
depth--;
|
|
14679
|
+
if (depth === 0) return text.slice(start, i + 1);
|
|
14680
|
+
}
|
|
14681
|
+
}
|
|
14682
|
+
return text.slice(start);
|
|
14683
|
+
}
|
|
14684
|
+
function normalizeMutantEntry(value) {
|
|
14685
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
14686
|
+
const rec = value;
|
|
14687
|
+
const id = typeof rec["id"] === "string" ? rec["id"] : void 0;
|
|
14688
|
+
const status = rec["status"];
|
|
14689
|
+
if (!id || status !== "killed" && status !== "survived" && status !== "skipped" && status !== "killed-by-hang") {
|
|
14690
|
+
return void 0;
|
|
14691
|
+
}
|
|
14692
|
+
return {
|
|
14693
|
+
id,
|
|
14694
|
+
file: typeof rec["file"] === "string" ? rec["file"] : "",
|
|
14695
|
+
line: typeof rec["line"] === "number" ? rec["line"] : 0,
|
|
14696
|
+
kind: typeof rec["kind"] === "string" ? rec["kind"] : "",
|
|
14697
|
+
status,
|
|
14698
|
+
evidence: typeof rec["evidence"] === "string" ? rec["evidence"] : void 0
|
|
14699
|
+
};
|
|
14700
|
+
}
|
|
14701
|
+
|
|
14702
|
+
// src/coordination/director-mutation-test-tool.ts
|
|
14703
|
+
var DEFAULT_MAX_PER_FILE = 10;
|
|
14704
|
+
var DEFAULT_MAX_STRENGTHEN_ATTEMPTS = 2;
|
|
14705
|
+
var CHAOS_ROLE = "chaos-monkey";
|
|
14706
|
+
function makeMutationTestTool(director, roster, opts = {}) {
|
|
14707
|
+
return {
|
|
14708
|
+
name: "mutation_test",
|
|
14709
|
+
description: "Chaos Monkey mutation testing: deterministically sabotage boundary conditions in the target code (> to >=, + to -, boolean flips, return null), re-run the tests per mutant, and report which mutants were killed. Surviving mutants mean the tests are weak \u2014 optionally loop a strengthen-tests repair until they die.",
|
|
14710
|
+
usageHint: "Use after writing new code AND its tests, before delivering. Pass targets (files) and testCommand. Provide repairSubagentId to auto-strengthen weak tests. Survivors that persist are reported as suspected-equivalent.",
|
|
14711
|
+
permission: "auto",
|
|
14712
|
+
mutating: false,
|
|
14713
|
+
capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
|
|
14714
|
+
inputSchema: {
|
|
14715
|
+
type: "object",
|
|
14716
|
+
properties: {
|
|
14717
|
+
targets: {
|
|
14718
|
+
type: "array",
|
|
14719
|
+
items: { type: "string" },
|
|
14720
|
+
description: "Project-relative (or absolute) source files to mutate. Keep to files changed by the current task."
|
|
14721
|
+
},
|
|
14722
|
+
testCommand: {
|
|
14723
|
+
type: "string",
|
|
14724
|
+
description: 'Exact command that runs the relevant tests, e.g. "pnpm exec vitest run packages/core/tests/coordination/mutation-engine.test.ts".'
|
|
14725
|
+
},
|
|
14726
|
+
cwd: { type: "string", description: "Working directory for the test command." },
|
|
14727
|
+
maxPerFile: {
|
|
14728
|
+
type: "number",
|
|
14729
|
+
minimum: 1,
|
|
14730
|
+
maximum: 25,
|
|
14731
|
+
description: "Mutant cap per file per pass. Default 10."
|
|
14732
|
+
},
|
|
14733
|
+
maxStrengthenAttempts: {
|
|
14734
|
+
type: "number",
|
|
14735
|
+
minimum: 0,
|
|
14736
|
+
maximum: 5,
|
|
14737
|
+
description: "Strengthen\u2192re-verify rounds. Default 2 when repairSubagentId is set, else 0."
|
|
14738
|
+
},
|
|
14739
|
+
repairSubagentId: {
|
|
14740
|
+
type: "string",
|
|
14741
|
+
description: "Subagent that owns the tests. When set and mutants survive, it receives a strengthen-tests task and the survivors are re-verified."
|
|
14742
|
+
},
|
|
14743
|
+
chaosWorktree: {
|
|
14744
|
+
anyOf: [{ type: "boolean" }, { type: "string", enum: ["auto", "required", "off"] }],
|
|
14745
|
+
description: "Worktree override for the chaos agent. Defaults to the roster policy for chaos-monkey ('off'), because mutation targets are usually freshly written and uncommitted \u2014 a worktree from HEAD would not contain them and every mutant would drift to skipped. Only pass 'auto' or 'required' when the targets are committed."
|
|
14746
|
+
},
|
|
14747
|
+
timeoutMs: { type: "number", minimum: 1, description: "Per-task timeout for chaos/strengthen/rerun tasks." },
|
|
14748
|
+
reportOnly: {
|
|
14749
|
+
type: "boolean",
|
|
14750
|
+
description: "Skip the strengthen loop even when survivors exist. Default false."
|
|
14751
|
+
}
|
|
14752
|
+
},
|
|
14753
|
+
required: ["targets", "testCommand"],
|
|
14754
|
+
additionalProperties: false
|
|
14755
|
+
},
|
|
14756
|
+
async execute(input, ctx) {
|
|
14757
|
+
const i = normalizeMutationTestInput(input);
|
|
14758
|
+
const root = opts.projectRoot ?? ctx.projectRoot;
|
|
14759
|
+
const plan = buildPlan(i, root);
|
|
14760
|
+
if (plan.length === 0) {
|
|
14761
|
+
return {
|
|
14762
|
+
verdict: "inconclusive",
|
|
14763
|
+
passed: false,
|
|
14764
|
+
error: "No mutable sites found in the given targets (after comment/string filtering)."
|
|
14765
|
+
};
|
|
14766
|
+
}
|
|
14767
|
+
const chaosBase = roster?.[CHAOS_ROLE];
|
|
14768
|
+
if (!chaosBase) {
|
|
14769
|
+
return {
|
|
14770
|
+
verdict: "inconclusive",
|
|
14771
|
+
passed: false,
|
|
14772
|
+
error: "chaos-monkey role missing from the roster \u2014 refusing to spawn a saboteur without its prompt/tools contract. Build the toolset with a roster that includes 'chaos-monkey' (FLEET_ROSTER does)."
|
|
14773
|
+
};
|
|
14774
|
+
}
|
|
14775
|
+
const chaosSubagentId = await director.spawn(
|
|
14776
|
+
makeChaosConfig(chaosBase, i.chaosWorktree ?? chaosBase.worktree ?? "off")
|
|
14777
|
+
);
|
|
14778
|
+
const chaosTaskId = await director.assign({
|
|
14779
|
+
id: randomUUID11(),
|
|
14780
|
+
subagentId: chaosSubagentId,
|
|
14781
|
+
description: buildChaosTask(plan, i, 1, []),
|
|
14782
|
+
timeoutMs: i.timeoutMs
|
|
14783
|
+
});
|
|
14784
|
+
const [chaosResult] = await director.awaitTasks([chaosTaskId]);
|
|
14785
|
+
const pass1 = collectOutcomes(chaosResult, plan);
|
|
14786
|
+
const survivors = pass1.filter((m) => m.status === "survived");
|
|
14787
|
+
const maxAttempts = clamp(
|
|
14788
|
+
i.maxStrengthenAttempts ?? (i.repairSubagentId && !i.reportOnly ? DEFAULT_MAX_STRENGTHEN_ATTEMPTS : 0),
|
|
14789
|
+
0,
|
|
14790
|
+
5
|
|
14791
|
+
);
|
|
14792
|
+
const attempts = [];
|
|
14793
|
+
let current = survivors;
|
|
14794
|
+
let rerunUnknowns = [];
|
|
14795
|
+
while (current.length > 0 && attempts.length < maxAttempts && i.repairSubagentId) {
|
|
14796
|
+
const attemptNo = attempts.length + 1;
|
|
14797
|
+
const strengthenTaskId = await director.assign({
|
|
14798
|
+
id: randomUUID11(),
|
|
14799
|
+
subagentId: i.repairSubagentId,
|
|
14800
|
+
description: buildStrengthenTask(current, i, attemptNo),
|
|
14801
|
+
timeoutMs: i.timeoutMs
|
|
14802
|
+
});
|
|
14803
|
+
const [strengthenResult] = await director.awaitTasks([strengthenTaskId]);
|
|
14804
|
+
if (strengthenResult?.status !== "success") {
|
|
14805
|
+
attempts.push({
|
|
14806
|
+
attempt: attemptNo,
|
|
14807
|
+
survivorsBefore: current,
|
|
14808
|
+
strengthenResult: strengthenResult ? { taskId: strengthenResult.taskId, status: strengthenResult.status } : void 0,
|
|
14809
|
+
survivorsAfter: current,
|
|
14810
|
+
suspectedEquivalent: []
|
|
14811
|
+
});
|
|
14812
|
+
break;
|
|
14813
|
+
}
|
|
14814
|
+
const survivorPlan = plan.filter((p) => current.some((s) => s.id === p.id));
|
|
14815
|
+
const rerunSubagentId = await director.spawn(
|
|
14816
|
+
makeChaosConfig(chaosBase, i.chaosWorktree ?? chaosBase.worktree ?? "off")
|
|
14817
|
+
);
|
|
14818
|
+
const rerunTaskId = await director.assign({
|
|
14819
|
+
id: randomUUID11(),
|
|
14820
|
+
subagentId: rerunSubagentId,
|
|
14821
|
+
description: buildChaosTask(survivorPlan, i, attemptNo + 1, current),
|
|
14822
|
+
timeoutMs: i.timeoutMs
|
|
14823
|
+
});
|
|
14824
|
+
const [rerunResult] = await director.awaitTasks([rerunTaskId]);
|
|
14825
|
+
const passN = collectOutcomes(rerunResult, survivorPlan);
|
|
14826
|
+
const stillSurviving = passN.filter((m) => !isKill(m.status));
|
|
14827
|
+
rerunUnknowns = passN.filter((m) => m.status === "skipped");
|
|
14828
|
+
attempts.push({
|
|
14829
|
+
attempt: attemptNo,
|
|
14830
|
+
survivorsBefore: current,
|
|
14831
|
+
strengthenResult: { taskId: strengthenResult.taskId, status: strengthenResult.status },
|
|
14832
|
+
rerunResult: { taskId: rerunTaskId, status: rerunResult?.status ?? "unknown" },
|
|
14833
|
+
survivorsAfter: stillSurviving,
|
|
14834
|
+
suspectedEquivalent: stillSurviving.filter((m) => m.status === "survived" && current.some((c) => c.id === m.id)).map((m) => m.id)
|
|
14835
|
+
});
|
|
14836
|
+
current = stillSurviving;
|
|
14837
|
+
}
|
|
14838
|
+
const finalSurvivors = current.filter((m) => m.status === "survived");
|
|
14839
|
+
const verifiedCount = pass1.filter((m) => m.status !== "skipped").length;
|
|
14840
|
+
const skippedCount = pass1.filter((m) => m.status === "skipped").length;
|
|
14841
|
+
const rerunUnknownCount = rerunUnknowns.length;
|
|
14842
|
+
const score = plan.length === 0 ? 0 : pass1.filter((m) => isKill(m.status)).length / plan.length;
|
|
14843
|
+
const verdict = verifiedCount === 0 ? "inconclusive" : finalSurvivors.length === 0 ? skippedCount > 0 || rerunUnknownCount > 0 ? "partial" : "pass" : score >= 0.8 ? "partial" : "fail";
|
|
14844
|
+
return {
|
|
14845
|
+
verdict,
|
|
14846
|
+
passed: verdict === "pass",
|
|
14847
|
+
mutationScore: Number.parseFloat(score.toFixed(3)),
|
|
14848
|
+
planned: plan.length,
|
|
14849
|
+
killed: pass1.filter((m) => isKill(m.status)).length,
|
|
14850
|
+
// Breakout of `killed`: how many kills were detected by the test
|
|
14851
|
+
// command hanging rather than by a failing assertion. A subset of
|
|
14852
|
+
// `killed`, surfaced so a director can distinguish a hang-heavy
|
|
14853
|
+
// suite (mutants breaking termination, not assertions) from an
|
|
14854
|
+
// assertion-strong one. hangHeavy = killedByHang === killed.
|
|
14855
|
+
killedByHang: pass1.filter((m) => m.status === "killed-by-hang").length,
|
|
14856
|
+
survived: pass1.filter((m) => m.status === "survived").length,
|
|
14857
|
+
skipped: pass1.filter((m) => m.status === "skipped").length,
|
|
14858
|
+
finalSurvivors: finalSurvivors.map((m) => ({ id: m.id, file: m.file, kind: m.kind })),
|
|
14859
|
+
suspectedEquivalent: attempts.flatMap((a) => a.suspectedEquivalent),
|
|
14860
|
+
strengthenAttempts: attempts.length,
|
|
14861
|
+
attempts,
|
|
14862
|
+
chaosTaskId,
|
|
14863
|
+
// Unverified leftovers from the strengthen loop: surfaced so the
|
|
14864
|
+
// caller can see WHICH mutants lack kill evidence, and counted by
|
|
14865
|
+
// the verdict gate above.
|
|
14866
|
+
unverifiedFromRerun: rerunUnknowns.map((m) => ({ id: m.id, file: m.file, kind: m.kind })),
|
|
14867
|
+
nextAction: finalSurvivors.length === 0 && rerunUnknownCount === 0 && skippedCount === 0 ? "accept" : attempts.length >= maxAttempts && i.repairSubagentId ? "manual_review_survivors" : "strengthen_tests"
|
|
14868
|
+
};
|
|
14869
|
+
}
|
|
14870
|
+
};
|
|
14871
|
+
}
|
|
14872
|
+
function normalizeMutationTestInput(input) {
|
|
14873
|
+
const raw = input ?? {};
|
|
14874
|
+
const targets = stringArray2(raw["targets"]) ?? [];
|
|
14875
|
+
const testCommand = typeof raw["testCommand"] === "string" ? raw["testCommand"].trim() : "";
|
|
14876
|
+
return {
|
|
14877
|
+
targets: targets.filter(Boolean),
|
|
14878
|
+
testCommand,
|
|
14879
|
+
cwd: typeof raw["cwd"] === "string" && raw["cwd"].trim() ? raw["cwd"].trim() : void 0,
|
|
14880
|
+
maxPerFile: typeof raw["maxPerFile"] === "number" ? raw["maxPerFile"] : void 0,
|
|
14881
|
+
maxStrengthenAttempts: typeof raw["maxStrengthenAttempts"] === "number" ? raw["maxStrengthenAttempts"] : void 0,
|
|
14882
|
+
repairSubagentId: typeof raw["repairSubagentId"] === "string" && raw["repairSubagentId"].trim() ? raw["repairSubagentId"].trim() : void 0,
|
|
14883
|
+
chaosWorktree: normalizeWorktreeOverride(raw["chaosWorktree"]),
|
|
14884
|
+
timeoutMs: typeof raw["timeoutMs"] === "number" ? raw["timeoutMs"] : void 0,
|
|
14885
|
+
reportOnly: raw["reportOnly"] === true
|
|
14886
|
+
};
|
|
14887
|
+
}
|
|
14888
|
+
function clamp(n, lo, hi) {
|
|
14889
|
+
return Math.min(hi, Math.max(lo, n));
|
|
14890
|
+
}
|
|
14891
|
+
function buildPlan(i, projectRoot) {
|
|
14892
|
+
const plan = [];
|
|
14893
|
+
for (const target of i.targets) {
|
|
14894
|
+
const abs = isAbsolute3(target) ? target : join16(projectRoot ?? process.cwd(), target);
|
|
14895
|
+
let source;
|
|
14896
|
+
try {
|
|
14897
|
+
source = readFileSync13(abs, "utf8");
|
|
14898
|
+
} catch {
|
|
14899
|
+
continue;
|
|
14900
|
+
}
|
|
14901
|
+
plan.push(...planMutations(target, source, { maxPerFile: i.maxPerFile ?? DEFAULT_MAX_PER_FILE }));
|
|
14902
|
+
}
|
|
14903
|
+
return plan;
|
|
14904
|
+
}
|
|
14905
|
+
function makeChaosConfig(base, worktree) {
|
|
14906
|
+
return { ...instantiateRosterConfig2(CHAOS_ROLE, base), worktree };
|
|
14907
|
+
}
|
|
14908
|
+
function buildChaosTask(plan, i, pass, priorSurvivors) {
|
|
14909
|
+
const mutants = plan.map(
|
|
14910
|
+
(m) => `- ${m.id} | ${m.file}:${m.line}:${m.column} | ${m.kind} | "${m.original}" -> "${m.replacement}"`
|
|
14911
|
+
).join("\n");
|
|
14912
|
+
const prior = priorSurvivors.length > 0 ? `
|
|
14913
|
+
These mutants survived a previous pass (pass ${pass - 1}) \u2014 re-verify them against the STRENGTHENED tests:
|
|
14914
|
+
${priorSurvivors.map((s) => `- ${s.id} (${s.kind} @ ${s.file}:${s.line})`).join("\n")}` : "";
|
|
14915
|
+
return [
|
|
14916
|
+
"Execute this deterministic mutation plan against the current checkout.",
|
|
14917
|
+
"",
|
|
14918
|
+
"For each mutant, in order:",
|
|
14919
|
+
"1. Apply ONLY that mutation at its exact (file, line, column).",
|
|
14920
|
+
`2. Run the test command: ${i.testCommand}${i.cwd ? ` (cwd: ${i.cwd})` : ""}`,
|
|
14921
|
+
"3. Record killed (tests failed \u2014 quote first failing assertion), survived (suite green), or killed-by-hang (the test command timed out or was aborted \u2014 the mutation broke the suite by non-termination; record the timeout as evidence, do NOT report it as survived).",
|
|
14922
|
+
"4. Restore the file byte-for-byte before the next mutant.",
|
|
14923
|
+
"",
|
|
14924
|
+
"Mutants:",
|
|
14925
|
+
mutants,
|
|
14926
|
+
prior,
|
|
14927
|
+
"",
|
|
14928
|
+
"Rules: one mutation at a time; never stack; if the anchored token no longer matches, mark skipped with the drift as evidence; do not fix or refactor anything; stay inside the plan.",
|
|
14929
|
+
"Finish with submit_result, then repeat the same JSON as your final text."
|
|
14930
|
+
].join("\n");
|
|
14931
|
+
}
|
|
14932
|
+
function buildStrengthenTask(survivors, i, attempt) {
|
|
14933
|
+
const confirmed = survivors.filter((s) => s.status === "survived");
|
|
14934
|
+
const unverified = survivors.filter((s) => s.status === "skipped");
|
|
14935
|
+
const row = (s) => `- ${s.id} | ${s.file}:${s.line} | ${s.kind}${s.evidence ? ` | ${s.evidence}` : ""}`;
|
|
14936
|
+
return [
|
|
14937
|
+
`Strengthen the tests so the mutants below die (attempt ${attempt}).`,
|
|
14938
|
+
"",
|
|
14939
|
+
...confirmed.length > 0 ? [
|
|
14940
|
+
"CONFIRMED SURVIVORS \u2014 each was a deliberate sabotage of production code that the current suite did NOT catch:",
|
|
14941
|
+
...confirmed.map(row),
|
|
14942
|
+
""
|
|
14943
|
+
] : [],
|
|
14944
|
+
...unverified.length > 0 ? [
|
|
14945
|
+
"UNVERIFIED \u2014 these mutations were never actually re-tested (the re-verify pass skipped or did not report them). Do NOT assume the suite misses them: first apply each mutation, run the tests, and confirm it really survives; if the tests already fail, report that instead of writing new assertions.",
|
|
14946
|
+
...unverified.map(row),
|
|
14947
|
+
""
|
|
14948
|
+
] : [],
|
|
14949
|
+
`Test command that must fail under each CONFIRMED mutant: ${i.testCommand}`,
|
|
14950
|
+
"",
|
|
14951
|
+
"For each CONFIRMED survivor add or tighten exactly one assertion that pins the sabotaged boundary/behavior. Do not change production code. Do not weaken other tests. Run the suite green on clean code before finishing."
|
|
14952
|
+
].join("\n");
|
|
14953
|
+
}
|
|
14954
|
+
function collectOutcomes(result, plan) {
|
|
14955
|
+
const fromText = parseTextOutcomes(result);
|
|
14956
|
+
if (fromText.length > 0) {
|
|
14957
|
+
const remaining = [...plan];
|
|
14958
|
+
const matched = [];
|
|
14959
|
+
for (const m of fromText) {
|
|
14960
|
+
const idx = remaining.findIndex((p) => p.id === m.id);
|
|
14961
|
+
if (idx === -1) continue;
|
|
14962
|
+
remaining.splice(idx, 1);
|
|
14963
|
+
matched.push(m);
|
|
14964
|
+
}
|
|
14965
|
+
if (matched.length > 0) {
|
|
14966
|
+
const missing = remaining.map((p) => ({
|
|
14967
|
+
id: p.id,
|
|
14968
|
+
file: p.file,
|
|
14969
|
+
line: p.line,
|
|
14970
|
+
kind: p.kind,
|
|
14971
|
+
status: "skipped",
|
|
14972
|
+
evidence: "not reported by chaos task"
|
|
14973
|
+
}));
|
|
14974
|
+
return [...matched, ...missing];
|
|
14975
|
+
}
|
|
14976
|
+
}
|
|
14977
|
+
return plan.map((p) => ({
|
|
14978
|
+
id: p.id,
|
|
14979
|
+
file: p.file,
|
|
14980
|
+
line: p.line,
|
|
14981
|
+
kind: p.kind,
|
|
14982
|
+
status: "skipped",
|
|
14983
|
+
evidence: result ? `chaos task ended ${result.status}` : "chaos task produced no result"
|
|
14984
|
+
}));
|
|
14985
|
+
}
|
|
14986
|
+
function isKill(status) {
|
|
14987
|
+
return status === "killed" || status === "killed-by-hang";
|
|
14988
|
+
}
|
|
14989
|
+
function parseTextOutcomes(result) {
|
|
14990
|
+
const text = typeof result?.result === "string" ? result.result : void 0;
|
|
14991
|
+
if (!text) return [];
|
|
14992
|
+
const parsed = parseMutationReport(text);
|
|
14993
|
+
if (!parsed) return [];
|
|
14994
|
+
return parsed.mutants.map((m) => ({
|
|
14995
|
+
id: m.id,
|
|
14996
|
+
file: m.file,
|
|
14997
|
+
line: m.line,
|
|
14998
|
+
kind: m.kind,
|
|
14999
|
+
status: m.status,
|
|
15000
|
+
evidence: m.evidence
|
|
15001
|
+
}));
|
|
15002
|
+
}
|
|
15003
|
+
|
|
14191
15004
|
// src/coordination/director-tools.ts
|
|
14192
15005
|
function makeSpawnTool(director, roster) {
|
|
14193
15006
|
const dispatchCatalog = () => {
|
|
@@ -14480,7 +15293,7 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
14480
15293
|
try {
|
|
14481
15294
|
const config = buildKanbanSubagentConfig(claim.task, i, roster, instantiateRosterConfig2);
|
|
14482
15295
|
subagentId = await director.spawn(config);
|
|
14483
|
-
const dispatchTaskId =
|
|
15296
|
+
const dispatchTaskId = randomUUID12();
|
|
14484
15297
|
const taskSpec = {
|
|
14485
15298
|
id: dispatchTaskId,
|
|
14486
15299
|
subagentId,
|
|
@@ -14756,6 +15569,7 @@ function buildDirectorToolset(director, roster) {
|
|
|
14756
15569
|
makeAskResultTool(director),
|
|
14757
15570
|
makeRollUpTool(director),
|
|
14758
15571
|
makeQualityGateTool(director, roster),
|
|
15572
|
+
makeMutationTestTool(director, roster),
|
|
14759
15573
|
makeTerminateTool(director),
|
|
14760
15574
|
makeTerminateAllTool(director),
|
|
14761
15575
|
makeFleetTool(director),
|
|
@@ -14842,7 +15656,7 @@ import * as fsp24 from "node:fs/promises";
|
|
|
14842
15656
|
import * as path32 from "node:path";
|
|
14843
15657
|
|
|
14844
15658
|
// src/storage/session-store.ts
|
|
14845
|
-
import { randomUUID as
|
|
15659
|
+
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
14846
15660
|
import * as fsp23 from "node:fs/promises";
|
|
14847
15661
|
import * as path31 from "node:path";
|
|
14848
15662
|
|
|
@@ -16566,7 +17380,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
16566
17380
|
|
|
16567
17381
|
// src/storage/session-checkpoint-cas.ts
|
|
16568
17382
|
import { spawn as spawn3 } from "node:child_process";
|
|
16569
|
-
import { createHash as createHash3, randomUUID as
|
|
17383
|
+
import { createHash as createHash3, randomUUID as randomUUID13 } from "node:crypto";
|
|
16570
17384
|
import * as fsp10 from "node:fs/promises";
|
|
16571
17385
|
import * as path23 from "node:path";
|
|
16572
17386
|
|
|
@@ -16824,7 +17638,7 @@ var SessionCheckpointCas = class {
|
|
|
16824
17638
|
}
|
|
16825
17639
|
const temp = path23.join(
|
|
16826
17640
|
path23.dirname(target),
|
|
16827
|
-
`.${path23.basename(target)}.${process.pid}.${
|
|
17641
|
+
`.${path23.basename(target)}.${process.pid}.${randomUUID13()}.tmp`
|
|
16828
17642
|
);
|
|
16829
17643
|
let handle;
|
|
16830
17644
|
try {
|
|
@@ -18620,7 +19434,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
18620
19434
|
onAppend;
|
|
18621
19435
|
onAppendBatch;
|
|
18622
19436
|
catalogClient;
|
|
18623
|
-
maintenanceHolderId =
|
|
19437
|
+
maintenanceHolderId = randomUUID14();
|
|
18624
19438
|
_loadCache = /* @__PURE__ */ new Map();
|
|
18625
19439
|
loadCache = new SessionLoadCache(this._loadCache);
|
|
18626
19440
|
_indexCache = null;
|
|
@@ -20827,7 +21641,7 @@ function hashStr(s) {
|
|
|
20827
21641
|
}
|
|
20828
21642
|
|
|
20829
21643
|
// src/coordination/multi-agent-coordinator.ts
|
|
20830
|
-
import { randomUUID as
|
|
21644
|
+
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
20831
21645
|
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
20832
21646
|
|
|
20833
21647
|
// src/coordination/coordinator/error-classifier.ts
|
|
@@ -20918,7 +21732,8 @@ async function executeSubagentWithTimeout({
|
|
|
20918
21732
|
budget,
|
|
20919
21733
|
preemptFraction = TIMEOUT_PREEMPT_FRACTION,
|
|
20920
21734
|
abortSubagent,
|
|
20921
|
-
currentSessionId
|
|
21735
|
+
currentSessionId,
|
|
21736
|
+
gracefulFinish
|
|
20922
21737
|
}) {
|
|
20923
21738
|
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
20924
21739
|
const idleLimitMs = budget.limits.idleTimeoutMs;
|
|
@@ -20949,9 +21764,17 @@ async function executeSubagentWithTimeout({
|
|
|
20949
21764
|
const scheduleNext = () => {
|
|
20950
21765
|
const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
20951
21766
|
const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
|
|
20952
|
-
const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
20953
|
-
const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
|
|
20954
|
-
|
|
21767
|
+
const idleRemaining = idleLimitMs === void 0 || gracefulFinish !== void 0 && initialTimeoutMs !== void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
21768
|
+
const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit || gracefulFinish !== void 0 ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
|
|
21769
|
+
const next = Math.min(wallRemaining, idleRemaining, preemptRemaining);
|
|
21770
|
+
if (!Number.isFinite(next)) {
|
|
21771
|
+
if (timer) {
|
|
21772
|
+
clearTimeout(timer);
|
|
21773
|
+
timer = null;
|
|
21774
|
+
}
|
|
21775
|
+
return;
|
|
21776
|
+
}
|
|
21777
|
+
armFor(Math.max(25, next));
|
|
20955
21778
|
};
|
|
20956
21779
|
const negotiateTimeout = async (used, limit) => {
|
|
20957
21780
|
const handler = budget.onThreshold;
|
|
@@ -21000,6 +21823,10 @@ async function executeSubagentWithTimeout({
|
|
|
21000
21823
|
const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
|
|
21001
21824
|
const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
|
|
21002
21825
|
if (idleExceeded && !wallExceeded) {
|
|
21826
|
+
if (gracefulFinish !== void 0 && initialTimeoutMs !== void 0) {
|
|
21827
|
+
scheduleNext();
|
|
21828
|
+
return;
|
|
21829
|
+
}
|
|
21003
21830
|
const sessionId = currentSessionId();
|
|
21004
21831
|
budget._events?.emit("budget.threshold_reached", {
|
|
21005
21832
|
...sessionId ? { sessionId } : {},
|
|
@@ -21016,7 +21843,7 @@ async function executeSubagentWithTimeout({
|
|
|
21016
21843
|
reject(new BudgetExceededError("idle_timeout", idleLimit ?? 0, budget.idleMs()));
|
|
21017
21844
|
return;
|
|
21018
21845
|
}
|
|
21019
|
-
if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed >= wallLimit * preemptFraction) {
|
|
21846
|
+
if (wallLimit !== void 0 && !wallExceeded && gracefulFinish === void 0 && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed >= wallLimit * preemptFraction) {
|
|
21020
21847
|
const activityTs = Date.now() - budget.idleMs();
|
|
21021
21848
|
if (activityTs <= lastGrantActivityTs) {
|
|
21022
21849
|
preemptState = "locked" /* LOCKED */;
|
|
@@ -21050,6 +21877,22 @@ async function executeSubagentWithTimeout({
|
|
|
21050
21877
|
return;
|
|
21051
21878
|
}
|
|
21052
21879
|
const limit = wallLimit ?? 0;
|
|
21880
|
+
if (gracefulFinish !== void 0) {
|
|
21881
|
+
if (!budget.graceGranted) {
|
|
21882
|
+
const reason = `wall-clock budget of ${Math.round(limit / 1e3)}s reached`;
|
|
21883
|
+
if (budget.notifyFinish(reason, { graceMs: gracefulFinish.graceMs })) {
|
|
21884
|
+
scheduleNext();
|
|
21885
|
+
return;
|
|
21886
|
+
}
|
|
21887
|
+
abortSubagent(ctx.subagentId);
|
|
21888
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
21889
|
+
return;
|
|
21890
|
+
} else {
|
|
21891
|
+
abortSubagent(ctx.subagentId);
|
|
21892
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
21893
|
+
return;
|
|
21894
|
+
}
|
|
21895
|
+
}
|
|
21053
21896
|
if (!budget.onThreshold) {
|
|
21054
21897
|
abortSubagent(ctx.subagentId);
|
|
21055
21898
|
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
@@ -21197,7 +22040,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
21197
22040
|
return { ...subagent, name: display };
|
|
21198
22041
|
}
|
|
21199
22042
|
async spawn(subagent) {
|
|
21200
|
-
const id = subagent.id ||
|
|
22043
|
+
const id = subagent.id || randomUUID15();
|
|
21201
22044
|
const cfg = this.withNickname(subagent, id);
|
|
21202
22045
|
if (this.subagents.has(id)) {
|
|
21203
22046
|
throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
|
|
@@ -21434,6 +22277,32 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
21434
22277
|
completeTask(result) {
|
|
21435
22278
|
this.recordCompletion(result);
|
|
21436
22279
|
}
|
|
22280
|
+
/**
|
|
22281
|
+
* Ask every RUNNING subagent that opted into `gracefulFinish` to finish its
|
|
22282
|
+
* task in its own turn (see coordination/subagent-finish.ts). This is the
|
|
22283
|
+
* leader-side entry point for "the leader agent has finished": it delivers
|
|
22284
|
+
* an in-band notification between tool batches — never an interrupt, never
|
|
22285
|
+
* an abort. Each notified subagent keeps its existing time budget and
|
|
22286
|
+
* accelerates; the watchdog still bounds the maximum lifetime.
|
|
22287
|
+
*
|
|
22288
|
+
* Subagents without the policy opted in are deliberately untouched — their
|
|
22289
|
+
* lifecycle remains the legacy watchdog contract.
|
|
22290
|
+
*
|
|
22291
|
+
* Returns the number of subagents actually notified.
|
|
22292
|
+
*/
|
|
22293
|
+
requestFinish(reason) {
|
|
22294
|
+
let notified = 0;
|
|
22295
|
+
for (const subagent of this.subagents.values()) {
|
|
22296
|
+
if (subagent.status !== "running") continue;
|
|
22297
|
+
if (!resolveGracefulFinish(subagent.config)) continue;
|
|
22298
|
+
const budget = subagent.activeBudget;
|
|
22299
|
+
if (!budget) continue;
|
|
22300
|
+
const usage = budget.usage();
|
|
22301
|
+
if (usage.iterations === 0 && usage.toolCalls === 0) continue;
|
|
22302
|
+
if (budget.notifyFinish(reason)) notified++;
|
|
22303
|
+
}
|
|
22304
|
+
return notified;
|
|
22305
|
+
}
|
|
21437
22306
|
// --- internal dispatching ---------------------------------------------
|
|
21438
22307
|
tryDispatchNext() {
|
|
21439
22308
|
while (this.canDispatch()) {
|
|
@@ -21607,7 +22476,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
21607
22476
|
idleTimeoutMs: rawIdleTimeoutMs ?? this.config.defaultBudget?.idleTimeoutMs ?? configWithRosterDefaults.idleTimeoutMs
|
|
21608
22477
|
},
|
|
21609
22478
|
"auto",
|
|
21610
|
-
{
|
|
22479
|
+
{
|
|
22480
|
+
sessionId: () => this.currentSessionId(),
|
|
22481
|
+
subagentId,
|
|
22482
|
+
// Graceful-finish runs own wall-clock enforcement to the watchdog so
|
|
22483
|
+
// the notify-then-bound lifecycle cannot be raced by tool.progress
|
|
22484
|
+
// heartbeats calling checkTimeout() (see subagent-budget.ts).
|
|
22485
|
+
...resolveGracefulFinish(subagent.config) ? { wallClockWatchdogOwned: true } : {}
|
|
22486
|
+
}
|
|
21611
22487
|
);
|
|
21612
22488
|
subagent.activeBudget = budget;
|
|
21613
22489
|
if (!this.runner) {
|
|
@@ -21640,7 +22516,8 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
21640
22516
|
task,
|
|
21641
22517
|
runCtx,
|
|
21642
22518
|
budget,
|
|
21643
|
-
subagent.config.preemptFraction
|
|
22519
|
+
subagent.config.preemptFraction,
|
|
22520
|
+
resolveGracefulFinish(subagent.config)
|
|
21644
22521
|
);
|
|
21645
22522
|
result = {
|
|
21646
22523
|
subagentId,
|
|
@@ -21670,13 +22547,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
21670
22547
|
}
|
|
21671
22548
|
this.recordCompletion(result);
|
|
21672
22549
|
}
|
|
21673
|
-
async executeWithTimeout(runner, task, ctx, budget, preemptFraction) {
|
|
22550
|
+
async executeWithTimeout(runner, task, ctx, budget, preemptFraction, gracefulFinish) {
|
|
21674
22551
|
return executeSubagentWithTimeout({
|
|
21675
22552
|
runner,
|
|
21676
22553
|
task,
|
|
21677
22554
|
ctx,
|
|
21678
22555
|
budget,
|
|
21679
22556
|
preemptFraction,
|
|
22557
|
+
gracefulFinish,
|
|
21680
22558
|
abortSubagent: (subagentId) => this.subagents.get(subagentId)?.abortController.abort(),
|
|
21681
22559
|
currentSessionId: () => this.currentSessionId()
|
|
21682
22560
|
});
|
|
@@ -21985,6 +22863,7 @@ function worktreeOwnerLabel(task, config) {
|
|
|
21985
22863
|
}
|
|
21986
22864
|
|
|
21987
22865
|
// src/coordination/director.ts
|
|
22866
|
+
var BUSY_REARM_FLOOR_MS = 1e3;
|
|
21988
22867
|
var Director = class _Director {
|
|
21989
22868
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars — just a cast helper */
|
|
21990
22869
|
static _asManifestEntry(v) {
|
|
@@ -22050,6 +22929,13 @@ var Director = class _Director {
|
|
|
22050
22929
|
subagentIdleTimeoutMs;
|
|
22051
22930
|
retireSubagentOnTaskComplete;
|
|
22052
22931
|
subagentIdleTimers = /* @__PURE__ */ new Map();
|
|
22932
|
+
/**
|
|
22933
|
+
* Effective idle window per subagent (spawn-time `idleTimeoutMs` override
|
|
22934
|
+
* or the Director-wide default; undefined = no window). Internal-task
|
|
22935
|
+
* completion re-arms with THIS value, not the Director-wide default, so
|
|
22936
|
+
* a subagent-configured window survives its first internal probe.
|
|
22937
|
+
*/
|
|
22938
|
+
subagentIdleDelayMs = /* @__PURE__ */ new Map();
|
|
22053
22939
|
sharedScratchpadPath;
|
|
22054
22940
|
maxSpawns;
|
|
22055
22941
|
maxSpawnDepth;
|
|
@@ -22082,7 +22968,7 @@ var Director = class _Director {
|
|
|
22082
22968
|
sessionProvider;
|
|
22083
22969
|
sessionModel;
|
|
22084
22970
|
constructor(opts) {
|
|
22085
|
-
this.id = opts.config.coordinatorId ||
|
|
22971
|
+
this.id = opts.config.coordinatorId || randomUUID16();
|
|
22086
22972
|
this.manifestPath = opts.manifestPath;
|
|
22087
22973
|
this.roster = opts.roster;
|
|
22088
22974
|
this.directorPreamble = opts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
|
|
@@ -22206,7 +23092,13 @@ var Director = class _Director {
|
|
|
22206
23092
|
handleTaskCompleted(payload) {
|
|
22207
23093
|
const r = payload.result;
|
|
22208
23094
|
const settled = this.tasks.settle(r);
|
|
22209
|
-
if (settled.internal)
|
|
23095
|
+
if (settled.internal) {
|
|
23096
|
+
this.armSubagentIdleRetirement(
|
|
23097
|
+
r.subagentId,
|
|
23098
|
+
this.subagentIdleDelayMs.get(r.subagentId) ?? this.subagentIdleTimeoutMs
|
|
23099
|
+
);
|
|
23100
|
+
return;
|
|
23101
|
+
}
|
|
22210
23102
|
const title = this.tasks.descriptionFor(r.taskId, payload.task.description ?? r.taskId);
|
|
22211
23103
|
if (!settled.consumedInBand && this.taskResultNotifier) {
|
|
22212
23104
|
const resultText = typeof r.result === "string" ? r.result : r.result !== void 0 ? safeStringify(r.result) : void 0;
|
|
@@ -22271,7 +23163,7 @@ var Director = class _Director {
|
|
|
22271
23163
|
}
|
|
22272
23164
|
this.armSubagentIdleRetirement(
|
|
22273
23165
|
r.subagentId,
|
|
22274
|
-
this.retireSubagentOnTaskComplete ? 0 : this.subagentIdleTimeoutMs
|
|
23166
|
+
this.retireSubagentOnTaskComplete ? 0 : this.subagentIdleDelayMs.get(r.subagentId) ?? this.subagentIdleTimeoutMs
|
|
22275
23167
|
);
|
|
22276
23168
|
}
|
|
22277
23169
|
extensionsFor(subagentId) {
|
|
@@ -22289,6 +23181,17 @@ var Director = class _Director {
|
|
|
22289
23181
|
isWorkComplete() {
|
|
22290
23182
|
return this.workCompleteFlag;
|
|
22291
23183
|
}
|
|
23184
|
+
/**
|
|
23185
|
+
* Ask every running background subagent that opted into `gracefulFinish`
|
|
23186
|
+
* to finish its task in its own turn. In-band notification between tool
|
|
23187
|
+
* batches — no interrupt, no abort; each subagent keeps its time budget and
|
|
23188
|
+
* accelerates. Session shutdown calls this before draining Chimera work so
|
|
23189
|
+
* the post-session reviewer is nudged to complete rather than killed.
|
|
23190
|
+
* Returns the number of subagents notified.
|
|
23191
|
+
*/
|
|
23192
|
+
requestFinish(reason) {
|
|
23193
|
+
return this.coordinator.requestFinish(reason);
|
|
23194
|
+
}
|
|
22292
23195
|
setLeaderBtwNote(note) {
|
|
22293
23196
|
return this.btwNotes.add(note);
|
|
22294
23197
|
}
|
|
@@ -22346,6 +23249,7 @@ var Director = class _Director {
|
|
|
22346
23249
|
this.resolveSpawnModel(config);
|
|
22347
23250
|
const subagentId = await spawn4(this, config, priceLookup);
|
|
22348
23251
|
const perSubagentIdleMs = typeof config.idleTimeoutMs === "number" && Number.isFinite(config.idleTimeoutMs) && config.idleTimeoutMs >= 0 ? config.idleTimeoutMs : this.subagentIdleTimeoutMs;
|
|
23252
|
+
this.subagentIdleDelayMs.set(subagentId, perSubagentIdleMs);
|
|
22349
23253
|
this.armSubagentIdleRetirement(subagentId, perSubagentIdleMs);
|
|
22350
23254
|
return subagentId;
|
|
22351
23255
|
}
|
|
@@ -22365,7 +23269,7 @@ var Director = class _Director {
|
|
|
22365
23269
|
);
|
|
22366
23270
|
}
|
|
22367
23271
|
const msg = {
|
|
22368
|
-
id:
|
|
23272
|
+
id: randomUUID16(),
|
|
22369
23273
|
type: "task",
|
|
22370
23274
|
from: this.id,
|
|
22371
23275
|
to: subagentId,
|
|
@@ -22399,6 +23303,7 @@ var Director = class _Director {
|
|
|
22399
23303
|
this.budgetPolicy.dispose();
|
|
22400
23304
|
for (const timer of this.subagentIdleTimers.values()) clearTimeout(timer);
|
|
22401
23305
|
this.subagentIdleTimers.clear();
|
|
23306
|
+
this.subagentIdleDelayMs.clear();
|
|
22402
23307
|
await this.coordinator.stopAll();
|
|
22403
23308
|
this.tasks.resolveWaitersOnShutdown();
|
|
22404
23309
|
for (const b of this.subagentBridges.values()) {
|
|
@@ -22457,6 +23362,7 @@ var Director = class _Director {
|
|
|
22457
23362
|
}
|
|
22458
23363
|
async remove(subagentId) {
|
|
22459
23364
|
this.clearSubagentIdleRetirement(subagentId);
|
|
23365
|
+
this.subagentIdleDelayMs.delete(subagentId);
|
|
22460
23366
|
void this.appendSessionEvent({
|
|
22461
23367
|
type: "agent_stopped",
|
|
22462
23368
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -22509,9 +23415,13 @@ var Director = class _Director {
|
|
|
22509
23415
|
const timer = setTimeout(() => {
|
|
22510
23416
|
this.subagentIdleTimers.delete(subagentId);
|
|
22511
23417
|
const entry = this.coordinator.getStatus().subagents.find((a) => a.id === subagentId);
|
|
22512
|
-
if (entry
|
|
23418
|
+
if (entry === void 0) return;
|
|
23419
|
+
if (entry.status !== "idle") {
|
|
23420
|
+
this.armSubagentIdleRetirement(subagentId, Math.max(delayMs, BUSY_REARM_FLOOR_MS));
|
|
23421
|
+
return;
|
|
23422
|
+
}
|
|
22513
23423
|
if (this.coordinator.listPendingTasks().some((task) => task.subagentId === subagentId)) {
|
|
22514
|
-
this.armSubagentIdleRetirement(subagentId,
|
|
23424
|
+
this.armSubagentIdleRetirement(subagentId, Math.max(delayMs, BUSY_REARM_FLOOR_MS));
|
|
22515
23425
|
return;
|
|
22516
23426
|
}
|
|
22517
23427
|
void this.remove(subagentId).catch(
|
|
@@ -22619,7 +23529,7 @@ var Director = class _Director {
|
|
|
22619
23529
|
};
|
|
22620
23530
|
|
|
22621
23531
|
// src/coordination/fleet-manager.ts
|
|
22622
|
-
import { randomUUID as
|
|
23532
|
+
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
22623
23533
|
import * as fsp26 from "node:fs/promises";
|
|
22624
23534
|
import * as path33 from "node:path";
|
|
22625
23535
|
var FleetManager = class {
|
|
@@ -22685,7 +23595,7 @@ var FleetManager = class {
|
|
|
22685
23595
|
maxContext;
|
|
22686
23596
|
constructor(opts = {}) {
|
|
22687
23597
|
this.manifestPath = opts.manifestPath;
|
|
22688
|
-
this.directorRunId = opts.directorRunId ??
|
|
23598
|
+
this.directorRunId = opts.directorRunId ?? randomUUID17();
|
|
22689
23599
|
this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
|
|
22690
23600
|
this.maxSpawnDepth = resolveMaxSpawnDepth(opts.maxSpawnDepth);
|
|
22691
23601
|
this.spawnDepth = opts.spawnDepth ?? 0;
|
|
@@ -24490,7 +25400,7 @@ function makeFleetStatusTool(opts = {}) {
|
|
|
24490
25400
|
}
|
|
24491
25401
|
|
|
24492
25402
|
// src/coordination/fleet-supervisor.ts
|
|
24493
|
-
import { randomUUID as
|
|
25403
|
+
import { randomUUID as randomUUID18 } from "node:crypto";
|
|
24494
25404
|
var COLLAB_ID_PREFIXES2 = ["bug-hunter-", "refactor-planner-", "critic-"];
|
|
24495
25405
|
var DEFAULTS = {
|
|
24496
25406
|
intervalMs: 2e4,
|
|
@@ -24768,7 +25678,7 @@ var FleetSupervisor = class {
|
|
|
24768
25678
|
*/
|
|
24769
25679
|
async decide(question, context, options, risk) {
|
|
24770
25680
|
const request = {
|
|
24771
|
-
id: `fleetsup-${
|
|
25681
|
+
id: `fleetsup-${randomUUID18()}`,
|
|
24772
25682
|
sessionId: this.opts.sessionId?.(),
|
|
24773
25683
|
source: "system",
|
|
24774
25684
|
question,
|
|
@@ -25128,6 +26038,22 @@ var SEND_ALLOWED_FIELDS = /* @__PURE__ */ new Set([
|
|
|
25128
26038
|
// receiver trusts the sender-asserted `sessionId`; the boundary must
|
|
25129
26039
|
// refuse the field entirely.
|
|
25130
26040
|
]);
|
|
26041
|
+
var SEND_FORBIDDEN_FIELDS = /* @__PURE__ */ new Set([
|
|
26042
|
+
"from",
|
|
26043
|
+
"sessionAffinity"
|
|
26044
|
+
]);
|
|
26045
|
+
function filterMailboxSendPayload(input) {
|
|
26046
|
+
const payload = {};
|
|
26047
|
+
const stripped = [];
|
|
26048
|
+
for (const key of Object.keys(input)) {
|
|
26049
|
+
if (SEND_ALLOWED_FIELDS.has(key) || SEND_FORBIDDEN_FIELDS.has(key)) {
|
|
26050
|
+
payload[key] = input[key];
|
|
26051
|
+
} else {
|
|
26052
|
+
stripped.push(key);
|
|
26053
|
+
}
|
|
26054
|
+
}
|
|
26055
|
+
return { payload, stripped };
|
|
26056
|
+
}
|
|
25131
26057
|
var ACK_ALLOWED_FIELDS = /* @__PURE__ */ new Set([
|
|
25132
26058
|
"messageId",
|
|
25133
26059
|
"read",
|
|
@@ -25365,7 +26291,9 @@ function makeMailSendTool(opts = {}) {
|
|
|
25365
26291
|
required: ["to", "subject", "body"]
|
|
25366
26292
|
},
|
|
25367
26293
|
async execute(input, ctx) {
|
|
25368
|
-
const i
|
|
26294
|
+
const { payload: i, stripped } = filterMailboxSendPayload(
|
|
26295
|
+
input ?? {}
|
|
26296
|
+
);
|
|
25369
26297
|
const rawTo = i.to;
|
|
25370
26298
|
const subject = i.subject;
|
|
25371
26299
|
const body = i.body;
|
|
@@ -25388,15 +26316,13 @@ function makeMailSendTool(opts = {}) {
|
|
|
25388
26316
|
recipientAliases: /* @__PURE__ */ new Set([codecIdentity.baseId]),
|
|
25389
26317
|
sessionId: codecIdentity.sessionId
|
|
25390
26318
|
};
|
|
26319
|
+
let parsed;
|
|
25391
26320
|
try {
|
|
25392
|
-
parseMailboxSendInput(i, codecActor);
|
|
26321
|
+
parsed = parseMailboxSendInput(i, codecActor);
|
|
25393
26322
|
} catch (err) {
|
|
25394
26323
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
25395
26324
|
}
|
|
25396
|
-
const audience =
|
|
25397
|
-
if (audience !== void 0 && audience !== "all" && audience !== "leaders") {
|
|
25398
|
-
return { ok: false, error: '"audience" must be "all" or "leaders".' };
|
|
25399
|
-
}
|
|
26325
|
+
const audience = parsed.audience;
|
|
25400
26326
|
const mb = resolveMailbox(ctx);
|
|
25401
26327
|
const identity = await register(mb, ctx);
|
|
25402
26328
|
const requestedTo = normalizeRecipient(rawTo, identity.sessionId);
|
|
@@ -25410,10 +26336,10 @@ function makeMailSendTool(opts = {}) {
|
|
|
25410
26336
|
to: delivery.to,
|
|
25411
26337
|
type: resolvedType,
|
|
25412
26338
|
audience: delivery.audience,
|
|
25413
|
-
subject,
|
|
25414
|
-
body,
|
|
25415
|
-
priority:
|
|
25416
|
-
replyTo:
|
|
26339
|
+
subject: parsed.subject,
|
|
26340
|
+
body: parsed.body,
|
|
26341
|
+
priority: parsed.priority,
|
|
26342
|
+
replyTo: parsed.replyTo,
|
|
25417
26343
|
senderSessionId: identity.sessionId
|
|
25418
26344
|
});
|
|
25419
26345
|
return {
|
|
@@ -25421,7 +26347,9 @@ function makeMailSendTool(opts = {}) {
|
|
|
25421
26347
|
messageId: msg.id,
|
|
25422
26348
|
from: identity.callerId,
|
|
25423
26349
|
to: msg.to,
|
|
25424
|
-
|
|
26350
|
+
// Surfacing what was stripped keeps the send auditable without
|
|
26351
|
+
// re-introducing the clutter into the payload itself.
|
|
26352
|
+
...stripped.length > 0 ? { strippedFields: stripped, summary: `Mail sent to ${msg.to === "*" ? "all agents" : msg.to} as ${identity.callerId}. Ignored ${stripped.length} unrecognized field(s): ${stripped.join(", ")}.` } : { summary: `Mail sent to ${msg.to === "*" ? "all agents" : msg.to} as ${identity.callerId}.` }
|
|
25425
26353
|
};
|
|
25426
26354
|
}
|
|
25427
26355
|
};
|
|
@@ -28943,7 +29871,7 @@ function createAgentMonitorService(opts) {
|
|
|
28943
29871
|
}
|
|
28944
29872
|
|
|
28945
29873
|
// src/coordination/autonomous-brain.ts
|
|
28946
|
-
import { randomUUID as
|
|
29874
|
+
import { randomUUID as randomUUID19 } from "node:crypto";
|
|
28947
29875
|
var AutonomousBrain = class {
|
|
28948
29876
|
graph;
|
|
28949
29877
|
// Fleet bus for emitting decisions — null-safe, no-op if not provided
|
|
@@ -29049,7 +29977,7 @@ var AutonomousBrain = class {
|
|
|
29049
29977
|
consequence: i === 0 ? `Spawn the most appropriate agent for: ${taskDescription.slice(0, 80)}` : `Spawn an alternative agent for the same task`
|
|
29050
29978
|
}));
|
|
29051
29979
|
return this.decideAuto({
|
|
29052
|
-
id:
|
|
29980
|
+
id: randomUUID19(),
|
|
29053
29981
|
source,
|
|
29054
29982
|
decisionType: "spawn",
|
|
29055
29983
|
question: `Should we spawn a subagent for this task?`,
|
|
@@ -29092,7 +30020,7 @@ var AutonomousBrain = class {
|
|
|
29092
30020
|
}
|
|
29093
30021
|
];
|
|
29094
30022
|
return this.decideAuto({
|
|
29095
|
-
id:
|
|
30023
|
+
id: randomUUID19(),
|
|
29096
30024
|
source,
|
|
29097
30025
|
decisionType: "approve_change",
|
|
29098
30026
|
question: `Should we approve the change "${change.title}"?`,
|
|
@@ -29151,7 +30079,7 @@ var AutonomousBrain = class {
|
|
|
29151
30079
|
consequence: "Break the task into smaller sub-tasks"
|
|
29152
30080
|
});
|
|
29153
30081
|
return this.decideAuto({
|
|
29154
|
-
id:
|
|
30082
|
+
id: randomUUID19(),
|
|
29155
30083
|
source,
|
|
29156
30084
|
decisionType: "escalate_task",
|
|
29157
30085
|
question: `Task failed: ${error.slice(0, 100)}. How should we proceed?`,
|
|
@@ -29285,10 +30213,10 @@ ${ctx.error}`);
|
|
|
29285
30213
|
};
|
|
29286
30214
|
|
|
29287
30215
|
// src/coordination/autonomous-coordinator.ts
|
|
29288
|
-
import { randomUUID as
|
|
30216
|
+
import { randomUUID as randomUUID22 } from "node:crypto";
|
|
29289
30217
|
|
|
29290
30218
|
// src/coordination/knowledge-graph.ts
|
|
29291
|
-
import { randomUUID as
|
|
30219
|
+
import { randomUUID as randomUUID20 } from "node:crypto";
|
|
29292
30220
|
import * as fsp28 from "node:fs/promises";
|
|
29293
30221
|
import * as path41 from "node:path";
|
|
29294
30222
|
var DEFAULT_MAX_NODES = 2e3;
|
|
@@ -29336,7 +30264,7 @@ var KnowledgeGraph = class _KnowledgeGraph {
|
|
|
29336
30264
|
* Returns the node with its assigned id.
|
|
29337
30265
|
*/
|
|
29338
30266
|
async add(node) {
|
|
29339
|
-
const full = { id:
|
|
30267
|
+
const full = { id: randomUUID20(), ...node };
|
|
29340
30268
|
this.nodes.set(full.id, full);
|
|
29341
30269
|
this._trackSeq(full.id);
|
|
29342
30270
|
this._addToIndex(full, this._indexKeys(full));
|
|
@@ -29465,8 +30393,8 @@ var KnowledgeGraph = class _KnowledgeGraph {
|
|
|
29465
30393
|
if (this.subs.size >= MAX_SUBSCRIPTIONS) {
|
|
29466
30394
|
throw new Error(`Knowledge graph subscription limit reached (${MAX_SUBSCRIPTIONS})`);
|
|
29467
30395
|
}
|
|
29468
|
-
const channel =
|
|
29469
|
-
const sub = { id:
|
|
30396
|
+
const channel = randomUUID20();
|
|
30397
|
+
const sub = { id: randomUUID20(), agentId, filter, channel };
|
|
29470
30398
|
this.subs.set(channel, sub);
|
|
29471
30399
|
this.pendingDeliveries.set(channel, []);
|
|
29472
30400
|
return channel;
|
|
@@ -29947,7 +30875,7 @@ var TaskDAG = class {
|
|
|
29947
30875
|
};
|
|
29948
30876
|
|
|
29949
30877
|
// src/coordination/task-auctioneer.ts
|
|
29950
|
-
import { randomUUID as
|
|
30878
|
+
import { randomUUID as randomUUID21 } from "node:crypto";
|
|
29951
30879
|
function isTerminalGoalStatus(status) {
|
|
29952
30880
|
return status === "done" || status === "failed";
|
|
29953
30881
|
}
|
|
@@ -30070,7 +30998,7 @@ var TaskAuctioneer = class {
|
|
|
30070
30998
|
const score = dispatchResult.confidence * (dispatchResult.role === agent.agentRole ? 1.2 : 1);
|
|
30071
30999
|
if (score < this.minConfidence) return false;
|
|
30072
31000
|
const bid = {
|
|
30073
|
-
id:
|
|
31001
|
+
id: randomUUID21(),
|
|
30074
31002
|
taskId,
|
|
30075
31003
|
agentId: agent.agentId,
|
|
30076
31004
|
agentName: agent.agentName,
|
|
@@ -31007,7 +31935,7 @@ var AutonomousCoordinator = class _AutonomousCoordinator {
|
|
|
31007
31935
|
break;
|
|
31008
31936
|
}
|
|
31009
31937
|
const decision = await this.brain.decideAuto({
|
|
31010
|
-
id:
|
|
31938
|
+
id: randomUUID22(),
|
|
31011
31939
|
source: "system",
|
|
31012
31940
|
decisionType: "prioritize_goals",
|
|
31013
31941
|
question: `What should we work on next? Open goals: ${dispatchable.map((g) => g.title).join(", ")}`,
|
|
@@ -31903,6 +32831,7 @@ export {
|
|
|
31903
32831
|
makeMailInboxTool,
|
|
31904
32832
|
makeMailSendTool,
|
|
31905
32833
|
makeMailboxTool,
|
|
32834
|
+
makeMutationTestTool,
|
|
31906
32835
|
makeQualityGateTool,
|
|
31907
32836
|
makeRollUpTool,
|
|
31908
32837
|
makeSpawnTool,
|