@wrongstack/core 0.308.6 → 0.309.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/coordination/agents/index.js +1 -0
- package/dist/coordination/agents/role-skills.d.ts +1 -0
- 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 +9 -0
- package/dist/coordination/explore-companion.d.ts +191 -0
- package/dist/coordination/fleet.d.ts +26 -0
- package/dist/coordination/index.d.ts +2 -1
- package/dist/coordination/index.js +1396 -370
- 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 +74 -0
- package/dist/coordination/subagent-budget.d.ts +54 -0
- package/dist/coordination/subagent-finish.d.ts +78 -0
- package/dist/core/index.js +19 -4
- package/dist/defaults/index.js +731 -52
- package/dist/execution/compaction-core.d.ts +1 -1
- package/dist/execution/compaction-elision.d.ts +0 -10
- package/dist/execution/index.js +269 -16
- package/dist/goal/index.js +54 -27
- package/dist/goal/phase-orchestrator.d.ts +7 -0
- package/dist/goal/types.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1280 -201
- package/dist/kernel/events/agent-events.d.ts +31 -2
- package/dist/models/index.js +11 -1
- package/dist/plugin/discovery.d.ts +73 -0
- package/dist/plugin/index.d.ts +2 -0
- package/dist/plugin/index.js +270 -29
- package/dist/plugin/loader.d.ts +5 -1
- package/dist/plugin/trust.d.ts +78 -0
- package/dist/tools/index.js +1 -0
- package/dist/types/config/mcp-features.d.ts +21 -0
- package/dist/types/config/skills-fleet-brain.d.ts +18 -0
- 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/instructions/agents/chaos-monkey.md +57 -0
- package/instructions/agents/explore-companion.md +35 -0
- package/package.json +3 -3
|
@@ -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 });
|
|
@@ -2862,6 +2992,7 @@ function inferRuntimeCapabilities(toolNames) {
|
|
|
2862
2992
|
var skillSet = (...names) => names;
|
|
2863
2993
|
var ROLE_SKILL_SETS = {
|
|
2864
2994
|
explore: skillSet("research-web", "node-modern", "typescript-strict"),
|
|
2995
|
+
"explore-companion": skillSet("node-modern", "typescript-strict"),
|
|
2865
2996
|
search: skillSet("bug-hunter", "typescript-strict", "research-web"),
|
|
2866
2997
|
research: skillSet("research-web", "tech-stack", "security-scanner", "api-design"),
|
|
2867
2998
|
analyst: skillSet("sdd", "api-design", "testing", "security-scanner"),
|
|
@@ -10389,6 +10520,42 @@ var SHADOW_AGENT = {
|
|
|
10389
10520
|
...defineAgent("shadow-agent", "Shadow"),
|
|
10390
10521
|
skillNames: [...SHADOW_AGENT_SKILLS]
|
|
10391
10522
|
};
|
|
10523
|
+
var EXPLORE_COMPANION_AGENT = {
|
|
10524
|
+
...defineAgent("explore-companion", "Explore Companion"),
|
|
10525
|
+
tools: [...TOOLS.read, ...TOOLS.index],
|
|
10526
|
+
// Read-only, triple-enforced: allowlist has no write/bash, and the
|
|
10527
|
+
// disabled list blocks the escape hatches explicitly.
|
|
10528
|
+
disabledTools: [
|
|
10529
|
+
"write",
|
|
10530
|
+
"edit",
|
|
10531
|
+
"replace",
|
|
10532
|
+
"patch",
|
|
10533
|
+
"bash",
|
|
10534
|
+
"exec",
|
|
10535
|
+
"delegate",
|
|
10536
|
+
"spawn_subagent",
|
|
10537
|
+
"assign_task"
|
|
10538
|
+
],
|
|
10539
|
+
skillNames: [...ROLE_SKILL_SETS["explore-companion"]],
|
|
10540
|
+
spawnBudgetExempt: true,
|
|
10541
|
+
// Findings travel via mailbox + submit_result, not the leader's stream.
|
|
10542
|
+
textStream: "silent",
|
|
10543
|
+
toolStream: "silent"
|
|
10544
|
+
};
|
|
10545
|
+
var CHAOS_MONKEY_AGENT = {
|
|
10546
|
+
...defineAgent("chaos-monkey", "Chaos Monkey"),
|
|
10547
|
+
tools: [...TOOLS.build],
|
|
10548
|
+
skillNames: ["testing", "typescript-strict"],
|
|
10549
|
+
spawnBudgetExempt: true,
|
|
10550
|
+
// Follow fleet worktree policy (NOT 'required'): mutation targets are
|
|
10551
|
+
// often freshly written and uncommitted — a worktree spawned from HEAD
|
|
10552
|
+
// would not contain them and every mutant would drift. Callers pass
|
|
10553
|
+
// `worktree: 'off'` in the mutation_test input for uncommitted targets.
|
|
10554
|
+
worktree: "auto",
|
|
10555
|
+
// Report travels via submit_result + final text, not the leader's stream.
|
|
10556
|
+
textStream: "silent",
|
|
10557
|
+
toolStream: "silent"
|
|
10558
|
+
};
|
|
10392
10559
|
var CRITIC_AGENT = defineAgent("critic", "Critic");
|
|
10393
10560
|
var GENERIC_AGENT = defineAgent("generic", "Generic Project Agent");
|
|
10394
10561
|
function withDispatchMetadata(definition) {
|
|
@@ -10407,6 +10574,8 @@ var FLEET_ROSTER = {
|
|
|
10407
10574
|
critic: CRITIC_AGENT,
|
|
10408
10575
|
generic: GENERIC_AGENT,
|
|
10409
10576
|
"shadow-agent": SHADOW_AGENT,
|
|
10577
|
+
"explore-companion": EXPLORE_COMPANION_AGENT,
|
|
10578
|
+
"chaos-monkey": CHAOS_MONKEY_AGENT,
|
|
10410
10579
|
...Object.fromEntries(
|
|
10411
10580
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, withDispatchMetadata(d)])
|
|
10412
10581
|
)
|
|
@@ -10430,6 +10599,23 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
10430
10599
|
maxTokens: 96e3,
|
|
10431
10600
|
maxCostUsd: 0.5
|
|
10432
10601
|
},
|
|
10602
|
+
"explore-companion": {
|
|
10603
|
+
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
10604
|
+
maxIterations: 3e3,
|
|
10605
|
+
maxToolCalls: 8e3,
|
|
10606
|
+
maxTokens: 96e3,
|
|
10607
|
+
maxCostUsd: 0.5
|
|
10608
|
+
},
|
|
10609
|
+
"chaos-monkey": {
|
|
10610
|
+
// A mutation pass is many short apply/run/restore cycles — per-mutant
|
|
10611
|
+
// work is tiny, but a large plan (25 mutants/file × N files) needs
|
|
10612
|
+
// headroom. Idle-based reaping covers a stalled pass.
|
|
10613
|
+
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
10614
|
+
maxIterations: 2e3,
|
|
10615
|
+
maxToolCalls: 6e3,
|
|
10616
|
+
maxTokens: 96e3,
|
|
10617
|
+
maxCostUsd: 0.5
|
|
10618
|
+
},
|
|
10433
10619
|
...Object.fromEntries(
|
|
10434
10620
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
|
|
10435
10621
|
)
|
|
@@ -11168,89 +11354,648 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
11168
11354
|
return void 0;
|
|
11169
11355
|
}
|
|
11170
11356
|
|
|
11171
|
-
// src/coordination/
|
|
11172
|
-
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
|
|
11186
|
-
|
|
11187
|
-
|
|
11188
|
-
|
|
11189
|
-
|
|
11190
|
-
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
|
|
11196
|
-
|
|
11197
|
-
|
|
11198
|
-
|
|
11199
|
-
|
|
11200
|
-
|
|
11201
|
-
|
|
11202
|
-
|
|
11203
|
-
|
|
11204
|
-
|
|
11205
|
-
|
|
11206
|
-
|
|
11207
|
-
|
|
11208
|
-
|
|
11209
|
-
|
|
11210
|
-
|
|
11211
|
-
|
|
11212
|
-
|
|
11213
|
-
|
|
11214
|
-
|
|
11215
|
-
|
|
11216
|
-
}
|
|
11217
|
-
|
|
11218
|
-
|
|
11219
|
-
|
|
11220
|
-
|
|
11221
|
-
|
|
11222
|
-
|
|
11357
|
+
// src/coordination/explore-companion.ts
|
|
11358
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
11359
|
+
|
|
11360
|
+
// src/coordination/mailbox-type-properties.ts
|
|
11361
|
+
var MAILBOX_TYPE_PROPERTIES = {
|
|
11362
|
+
note: {
|
|
11363
|
+
category: "informational",
|
|
11364
|
+
expectsReply: false,
|
|
11365
|
+
requiresAction: false,
|
|
11366
|
+
backgroundEligible: false,
|
|
11367
|
+
outOfBand: false,
|
|
11368
|
+
renderPriority: 20,
|
|
11369
|
+
recipientObligation: "Read for context; no reply needed.",
|
|
11370
|
+
senderGuidance: "General-purpose FYI. Use when no more specific type applies."
|
|
11371
|
+
},
|
|
11372
|
+
ask: {
|
|
11373
|
+
category: "actionable",
|
|
11374
|
+
expectsReply: true,
|
|
11375
|
+
requiresAction: true,
|
|
11376
|
+
backgroundEligible: true,
|
|
11377
|
+
outOfBand: false,
|
|
11378
|
+
renderPriority: 10,
|
|
11379
|
+
recipientObligation: "Answer as soon as possible \u2014 the sender is waiting.",
|
|
11380
|
+
senderGuidance: "Blocking question. Only use when you need an answer to proceed."
|
|
11381
|
+
},
|
|
11382
|
+
assign: {
|
|
11383
|
+
category: "actionable",
|
|
11384
|
+
expectsReply: false,
|
|
11385
|
+
requiresAction: true,
|
|
11386
|
+
backgroundEligible: true,
|
|
11387
|
+
outOfBand: false,
|
|
11388
|
+
renderPriority: 10,
|
|
11389
|
+
recipientObligation: "Accept or decline; act on it when current operation allows.",
|
|
11390
|
+
senderGuidance: 'Task delegation. Must be directed to a specific recipient (not "*").'
|
|
11391
|
+
},
|
|
11392
|
+
steer: {
|
|
11393
|
+
category: "actionable",
|
|
11394
|
+
expectsReply: false,
|
|
11395
|
+
requiresAction: true,
|
|
11396
|
+
backgroundEligible: true,
|
|
11397
|
+
outOfBand: false,
|
|
11398
|
+
renderPriority: 0,
|
|
11399
|
+
// Always rendered first
|
|
11400
|
+
recipientObligation: "Pause current approach, adjust per instruction, then resume.",
|
|
11401
|
+
senderGuidance: "Mid-task direction change. The recipient is already working on something."
|
|
11402
|
+
},
|
|
11403
|
+
btw: {
|
|
11404
|
+
category: "informational",
|
|
11405
|
+
expectsReply: false,
|
|
11406
|
+
requiresAction: false,
|
|
11407
|
+
backgroundEligible: false,
|
|
11408
|
+
outOfBand: false,
|
|
11409
|
+
renderPriority: 30,
|
|
11410
|
+
recipientObligation: "Absorb the information and stay on current task; no reply needed.",
|
|
11411
|
+
senderGuidance: "Low-priority aside. Non-urgent info that can wait."
|
|
11412
|
+
},
|
|
11413
|
+
broadcast: {
|
|
11414
|
+
category: "routing",
|
|
11415
|
+
expectsReply: false,
|
|
11416
|
+
requiresAction: false,
|
|
11417
|
+
backgroundEligible: false,
|
|
11418
|
+
outOfBand: false,
|
|
11419
|
+
renderPriority: 20,
|
|
11420
|
+
recipientObligation: 'Read if addressed to you (direct recipient, alias, or "*").',
|
|
11421
|
+
senderGuidance: 'Multi-recipient envelope. Auto-selected when to is "*" or "@session".'
|
|
11422
|
+
},
|
|
11423
|
+
status: {
|
|
11424
|
+
category: "informational",
|
|
11425
|
+
expectsReply: false,
|
|
11426
|
+
requiresAction: false,
|
|
11427
|
+
backgroundEligible: false,
|
|
11428
|
+
outOfBand: false,
|
|
11429
|
+
renderPriority: 40,
|
|
11430
|
+
recipientObligation: "Use to avoid redundant work; never act on as a task or question.",
|
|
11431
|
+
senderGuidance: "Agent/system status update. Machine-generated, not for human-originated messages."
|
|
11432
|
+
},
|
|
11433
|
+
result: {
|
|
11434
|
+
category: "informational",
|
|
11435
|
+
expectsReply: false,
|
|
11436
|
+
requiresAction: false,
|
|
11437
|
+
backgroundEligible: true,
|
|
11438
|
+
outOfBand: false,
|
|
11439
|
+
renderPriority: 10,
|
|
11440
|
+
recipientObligation: "Factor into next decision; treat as evidence, not a new task.",
|
|
11441
|
+
senderGuidance: "Task completion notice. Share the outcome of finished work."
|
|
11442
|
+
},
|
|
11443
|
+
review: {
|
|
11444
|
+
category: "actionable",
|
|
11445
|
+
expectsReply: false,
|
|
11446
|
+
requiresAction: true,
|
|
11447
|
+
backgroundEligible: true,
|
|
11448
|
+
outOfBand: false,
|
|
11449
|
+
renderPriority: 10,
|
|
11450
|
+
recipientObligation: "Inspect when convenient; no immediate reply required.",
|
|
11451
|
+
senderGuidance: "Passive review request (code/doc/PR). No reply required."
|
|
11452
|
+
},
|
|
11453
|
+
control: {
|
|
11454
|
+
category: "control_signal",
|
|
11455
|
+
expectsReply: false,
|
|
11456
|
+
requiresAction: false,
|
|
11457
|
+
backgroundEligible: false,
|
|
11458
|
+
outOfBand: true,
|
|
11459
|
+
renderPriority: 999,
|
|
11460
|
+
// Never rendered
|
|
11461
|
+
recipientObligation: 'Handled by the agent loop, NOT folded into conversation. "interrupt" causes cooperative halt.',
|
|
11462
|
+
senderGuidance: "RESERVED for runtime use. Agents must NOT send control messages."
|
|
11223
11463
|
}
|
|
11224
|
-
|
|
11225
|
-
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
|
|
11229
|
-
|
|
11230
|
-
|
|
11231
|
-
|
|
11232
|
-
|
|
11233
|
-
|
|
11234
|
-
|
|
11235
|
-
|
|
11236
|
-
|
|
11237
|
-
|
|
11238
|
-
|
|
11464
|
+
};
|
|
11465
|
+
|
|
11466
|
+
// src/coordination/mailbox-auth-types.ts
|
|
11467
|
+
var MAILBOX_CAPABILITY_IMPLICATIONS = {
|
|
11468
|
+
"mail.read.all": ["mail.read.self"],
|
|
11469
|
+
"mail.events.all": ["mail.events.self"],
|
|
11470
|
+
"mail.send.directive": ["mail.send.actionable", "mail.send.informational"],
|
|
11471
|
+
"mail.send.actionable": ["mail.send.informational"],
|
|
11472
|
+
// Leaf capabilities imply nothing further.
|
|
11473
|
+
"mail.send.informational": [],
|
|
11474
|
+
"mail.read.self": [],
|
|
11475
|
+
"mail.ack.self": [],
|
|
11476
|
+
"mail.events.self": [],
|
|
11477
|
+
"mail.presence.register.self": [],
|
|
11478
|
+
"mail.presence.heartbeat.self": [],
|
|
11479
|
+
"mail.presence.deregister.self": [],
|
|
11480
|
+
"mail.presence.read": [],
|
|
11481
|
+
"mail.retention.purge": [],
|
|
11482
|
+
"mail.retention.clear": [],
|
|
11483
|
+
"mail.admin.receipts": []
|
|
11484
|
+
};
|
|
11485
|
+
function expandMailboxCapabilities(caps) {
|
|
11486
|
+
const result = /* @__PURE__ */ new Set();
|
|
11487
|
+
const queue = [...caps];
|
|
11488
|
+
while (queue.length > 0) {
|
|
11489
|
+
const cap = queue.pop();
|
|
11490
|
+
if (result.has(cap)) continue;
|
|
11491
|
+
result.add(cap);
|
|
11492
|
+
const implied = MAILBOX_CAPABILITY_IMPLICATIONS[cap];
|
|
11493
|
+
if (implied) queue.push(...implied);
|
|
11239
11494
|
}
|
|
11240
|
-
|
|
11241
|
-
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
|
|
11246
|
-
|
|
11247
|
-
|
|
11248
|
-
|
|
11249
|
-
|
|
11250
|
-
|
|
11251
|
-
|
|
11252
|
-
|
|
11253
|
-
|
|
11495
|
+
return result;
|
|
11496
|
+
}
|
|
11497
|
+
function hasMailboxCapability(actor, cap) {
|
|
11498
|
+
if (actor.capabilities.has(cap)) return true;
|
|
11499
|
+
const expanded = expandMailboxCapabilities(actor.capabilities);
|
|
11500
|
+
return expanded.has(cap);
|
|
11501
|
+
}
|
|
11502
|
+
|
|
11503
|
+
// src/coordination/mailbox-predicates.ts
|
|
11504
|
+
function mailboxIdentityBase(agentId) {
|
|
11505
|
+
return agentId.split(/[@#]/, 1)[0].trim().toLowerCase();
|
|
11506
|
+
}
|
|
11507
|
+
function isMailboxLeader(agentId, role) {
|
|
11508
|
+
return mailboxIdentityBase(agentId) === "leader" || role?.trim().toLowerCase() === "leader";
|
|
11509
|
+
}
|
|
11510
|
+
function isMailboxSenderInFamily(senderId, family) {
|
|
11511
|
+
const base = mailboxIdentityBase(senderId);
|
|
11512
|
+
const normalizedFamily = family.trim().toLowerCase();
|
|
11513
|
+
if (normalizedFamily.length === 0) return false;
|
|
11514
|
+
return base === normalizedFamily || base.startsWith(`${normalizedFamily}-`);
|
|
11515
|
+
}
|
|
11516
|
+
function isMailboxMessageVisibleTo(message, agentId, role) {
|
|
11517
|
+
return message.audience !== "leaders" || isMailboxLeader(agentId, role);
|
|
11518
|
+
}
|
|
11519
|
+
function validateSendType(type, to) {
|
|
11520
|
+
if (type === "control") {
|
|
11521
|
+
throw new TypeError('Type "control" is reserved for runtime use and cannot be set by agents');
|
|
11522
|
+
}
|
|
11523
|
+
const isMultiRecipient = to === "*" || to.startsWith("@session:");
|
|
11524
|
+
if (type === "assign" && isMultiRecipient) {
|
|
11525
|
+
throw new TypeError(
|
|
11526
|
+
`Type "assign" requires a specific recipient \u2014 multi-recipient target "${to}" is ambiguous`
|
|
11527
|
+
);
|
|
11528
|
+
}
|
|
11529
|
+
if (type === "steer" && isMultiRecipient) {
|
|
11530
|
+
throw new TypeError(
|
|
11531
|
+
`Type "steer" requires a specific recipient \u2014 multi-recipient target "${to}" is ambiguous`
|
|
11532
|
+
);
|
|
11533
|
+
}
|
|
11534
|
+
}
|
|
11535
|
+
var SESSION_RECIPIENT_PREFIX = "@session:";
|
|
11536
|
+
function sessionRecipient(sessionId) {
|
|
11537
|
+
const normalizedSessionId = sessionId.trim();
|
|
11538
|
+
if (!normalizedSessionId) {
|
|
11539
|
+
throw new TypeError('sessionId is required for the "@session" recipient');
|
|
11540
|
+
}
|
|
11541
|
+
return `${SESSION_RECIPIENT_PREFIX}${normalizedSessionId}`;
|
|
11542
|
+
}
|
|
11543
|
+
function normalizeRecipient(to, sessionId) {
|
|
11544
|
+
const trimmed = to.trim();
|
|
11545
|
+
const normalized = trimmed.toLowerCase();
|
|
11546
|
+
if (normalized === "all") return "*";
|
|
11547
|
+
if (normalized === "@session") return sessionRecipient(sessionId ?? "");
|
|
11548
|
+
return trimmed;
|
|
11549
|
+
}
|
|
11550
|
+
function isActionRequiredForActor(message, projection) {
|
|
11551
|
+
if (projection.legacyGlobalCompletion) return false;
|
|
11552
|
+
if (message.deletedAt !== void 0) return false;
|
|
11553
|
+
if (projection.completedByMe) return false;
|
|
11554
|
+
return MAILBOX_TYPE_PROPERTIES[message.type]?.requiresAction === true;
|
|
11555
|
+
}
|
|
11556
|
+
|
|
11557
|
+
// src/coordination/mailbox-session-sync.ts
|
|
11558
|
+
function isAffectedBySessionAffinity(message) {
|
|
11559
|
+
return message.sessionAffinity !== void 0;
|
|
11560
|
+
}
|
|
11561
|
+
async function acceptMailboxMessageForSession(message, currentSessionId, ctx) {
|
|
11562
|
+
if (!isAffectedBySessionAffinity(message)) return true;
|
|
11563
|
+
const affinity = message.sessionAffinity;
|
|
11564
|
+
if (affinity === null || typeof affinity !== "object" || Array.isArray(affinity)) {
|
|
11565
|
+
return false;
|
|
11566
|
+
}
|
|
11567
|
+
if (affinity.sessionId !== void 0 && typeof affinity.sessionId !== "string" || affinity.reportId !== void 0 && typeof affinity.reportId !== "string") {
|
|
11568
|
+
return false;
|
|
11569
|
+
}
|
|
11570
|
+
if (!currentSessionId) {
|
|
11571
|
+
return ctx?.allowUnscoped === true;
|
|
11572
|
+
}
|
|
11573
|
+
if (typeof affinity.sessionId === "string" && affinity.sessionId.length > 0) {
|
|
11574
|
+
if (affinity.sessionId !== currentSessionId) return false;
|
|
11575
|
+
return true;
|
|
11576
|
+
}
|
|
11577
|
+
if (affinity.reportId && ctx?.resolveChimeraReportSessionId) {
|
|
11578
|
+
try {
|
|
11579
|
+
const resolved = await ctx.resolveChimeraReportSessionId(affinity.reportId);
|
|
11580
|
+
if (resolved === currentSessionId) return true;
|
|
11581
|
+
if (resolved !== void 0) return false;
|
|
11582
|
+
} catch {
|
|
11583
|
+
}
|
|
11584
|
+
}
|
|
11585
|
+
if (ctx?.allowUnscoped === true) return true;
|
|
11586
|
+
return false;
|
|
11587
|
+
}
|
|
11588
|
+
|
|
11589
|
+
// src/coordination/explore-companion.ts
|
|
11590
|
+
var DEFAULT_EXPLORE_COMPANION_AGENT_ID = "explore-companion";
|
|
11591
|
+
var DEFAULT_PROBE_COOLDOWN_MS = 12e4;
|
|
11592
|
+
var DEFAULT_MAX_PENDING_PROBES = 8;
|
|
11593
|
+
var DEFAULT_MAILBOX_POLL_INTERVAL_MS = 5e3;
|
|
11594
|
+
var DEFAULT_EXPLORE_EDIT_TOOLS = [
|
|
11595
|
+
"edit",
|
|
11596
|
+
"write",
|
|
11597
|
+
"patch",
|
|
11598
|
+
"multi_edit",
|
|
11599
|
+
"multiedit",
|
|
11600
|
+
"str_replace"
|
|
11601
|
+
];
|
|
11602
|
+
var DEFAULT_EXPLORE_SEARCH_TOOLS = [
|
|
11603
|
+
"search",
|
|
11604
|
+
"grep",
|
|
11605
|
+
"codebase-search"
|
|
11606
|
+
];
|
|
11607
|
+
function buildProbeTaskText(probe) {
|
|
11608
|
+
const payload = { probe: probe.probe };
|
|
11609
|
+
if (probe.hint) payload.hint = probe.hint;
|
|
11610
|
+
if (probe.context) payload.context = probe.context;
|
|
11611
|
+
return JSON.stringify(payload, null, 2);
|
|
11612
|
+
}
|
|
11613
|
+
function extractedPath(input) {
|
|
11614
|
+
if (!input || typeof input !== "object") return void 0;
|
|
11615
|
+
const rec = input;
|
|
11616
|
+
const candidate = typeof rec["path"] === "string" ? rec["path"] : typeof rec["file"] === "string" ? rec["file"] : void 0;
|
|
11617
|
+
return candidate && candidate.length > 0 ? candidate : void 0;
|
|
11618
|
+
}
|
|
11619
|
+
function looksEmpty(e) {
|
|
11620
|
+
if (typeof e.outputLines === "number" && e.outputLines === 0) return true;
|
|
11621
|
+
const out = e.output ?? "";
|
|
11622
|
+
return /(?:^|\n)(?:no |0 )(?:matches|results|files? found|occurrences)/i.test(out) || /total\s*:\s*0\b/i.test(out);
|
|
11623
|
+
}
|
|
11624
|
+
function extractSubjectTokens(text) {
|
|
11625
|
+
const out = [];
|
|
11626
|
+
const fileRe = /([\w@./-]+\.(?:[cm]?[jt]sx?|json|md|py|go|rs|ya?ml))\b/g;
|
|
11627
|
+
for (const m of text.matchAll(fileRe)) {
|
|
11628
|
+
const value = m[1];
|
|
11629
|
+
if (value) out.push({ kind: "file", value });
|
|
11630
|
+
}
|
|
11631
|
+
const symRe = /\b[A-Z][A-Za-z0-9_]{2,}\b/g;
|
|
11632
|
+
for (const m of text.matchAll(symRe)) {
|
|
11633
|
+
out.push({ kind: "symbol", value: m[0] });
|
|
11634
|
+
}
|
|
11635
|
+
return out;
|
|
11636
|
+
}
|
|
11637
|
+
var ExploreCompanion = class {
|
|
11638
|
+
constructor(opts) {
|
|
11639
|
+
this.opts = opts;
|
|
11640
|
+
this.cfg = this.resolveConfig(opts);
|
|
11641
|
+
}
|
|
11642
|
+
opts;
|
|
11643
|
+
unsubscribers = [];
|
|
11644
|
+
/** Paths the leader has read (readSet) — feeds edit-unread + unfamiliar-read. */
|
|
11645
|
+
readSet = /* @__PURE__ */ new Set();
|
|
11646
|
+
/** subject → last probe time; cooldown gate. Survives detach/reconfigure. */
|
|
11647
|
+
probedAt = /* @__PURE__ */ new Map();
|
|
11648
|
+
/** todo id → last observed status, per leader agent. */
|
|
11649
|
+
todoSeen = /* @__PURE__ */ new Map();
|
|
11650
|
+
pending = [];
|
|
11651
|
+
inFlight = false;
|
|
11652
|
+
pollTimer;
|
|
11653
|
+
running = false;
|
|
11654
|
+
hostStarted = false;
|
|
11655
|
+
cfg;
|
|
11656
|
+
resolveConfig(opts) {
|
|
11657
|
+
const signals = opts.signals ?? {};
|
|
11658
|
+
return {
|
|
11659
|
+
enabled: opts.enabled ?? true,
|
|
11660
|
+
cooldownMs: opts.cooldownMs ?? DEFAULT_PROBE_COOLDOWN_MS,
|
|
11661
|
+
maxPending: opts.maxPending ?? DEFAULT_MAX_PENDING_PROBES,
|
|
11662
|
+
pollIntervalMs: opts.pollIntervalMs ?? DEFAULT_MAILBOX_POLL_INTERVAL_MS,
|
|
11663
|
+
companionAgentId: opts.companionAgentId ?? DEFAULT_EXPLORE_COMPANION_AGENT_ID,
|
|
11664
|
+
signals: {
|
|
11665
|
+
editUnreadFile: signals.editUnreadFile ?? true,
|
|
11666
|
+
searchZeroHits: signals.searchZeroHits ?? true,
|
|
11667
|
+
unfamiliarRead: signals.unfamiliarRead ?? true,
|
|
11668
|
+
todoInProgress: signals.todoInProgress ?? true,
|
|
11669
|
+
errorSymbol: signals.errorSymbol ?? true,
|
|
11670
|
+
mailboxAsk: signals.mailboxAsk ?? true
|
|
11671
|
+
},
|
|
11672
|
+
fileEditTools: new Set(
|
|
11673
|
+
(opts.fileEditTools ?? DEFAULT_EXPLORE_EDIT_TOOLS).map((t) => t.toLowerCase())
|
|
11674
|
+
),
|
|
11675
|
+
searchTools: new Set(
|
|
11676
|
+
(opts.searchTools ?? DEFAULT_EXPLORE_SEARCH_TOOLS).map((t) => t.toLowerCase())
|
|
11677
|
+
)
|
|
11678
|
+
};
|
|
11679
|
+
}
|
|
11680
|
+
/** Resolve the leader's own session id for event filtering. */
|
|
11681
|
+
resolveLeaderSessionId() {
|
|
11682
|
+
const sid = this.opts.leaderSessionId;
|
|
11683
|
+
return typeof sid === "function" ? sid() : sid;
|
|
11684
|
+
}
|
|
11685
|
+
/** Resolve the leader's agent id for todo diffing (optional signal). */
|
|
11686
|
+
resolveLeaderAgentId() {
|
|
11687
|
+
const aid = this.opts.leaderAgentId;
|
|
11688
|
+
if (!aid) return void 0;
|
|
11689
|
+
return typeof aid === "function" ? aid() : aid;
|
|
11690
|
+
}
|
|
11691
|
+
/** Re-apply tunables to a (possibly running) companion. */
|
|
11692
|
+
reconfigure(next) {
|
|
11693
|
+
const merged = { ...this.opts, ...next };
|
|
11694
|
+
const nextCfg = this.resolveConfig(merged);
|
|
11695
|
+
const changed = nextCfg.enabled !== this.cfg.enabled || nextCfg.cooldownMs !== this.cfg.cooldownMs || nextCfg.maxPending !== this.cfg.maxPending || nextCfg.pollIntervalMs !== this.cfg.pollIntervalMs || nextCfg.companionAgentId !== this.cfg.companionAgentId || Object.keys(nextCfg.signals).some(
|
|
11696
|
+
(k) => nextCfg.signals[k] !== this.cfg.signals[k]
|
|
11697
|
+
);
|
|
11698
|
+
this.cfg = nextCfg;
|
|
11699
|
+
if (!changed) return false;
|
|
11700
|
+
if (this.hostStarted) {
|
|
11701
|
+
this.detach();
|
|
11702
|
+
this.attach();
|
|
11703
|
+
}
|
|
11704
|
+
return true;
|
|
11705
|
+
}
|
|
11706
|
+
/** Begin watching. Idempotent; a disabled companion records intent only. */
|
|
11707
|
+
start() {
|
|
11708
|
+
this.hostStarted = true;
|
|
11709
|
+
this.attach();
|
|
11710
|
+
}
|
|
11711
|
+
/** Stop watching and drop the host's intent to watch. */
|
|
11712
|
+
stop() {
|
|
11713
|
+
this.hostStarted = false;
|
|
11714
|
+
this.detach();
|
|
11715
|
+
}
|
|
11716
|
+
/** True while the watchers are attached. */
|
|
11717
|
+
isRunning() {
|
|
11718
|
+
return this.running;
|
|
11719
|
+
}
|
|
11720
|
+
/** Number of probes queued but not yet dispatched (for status surfaces). */
|
|
11721
|
+
pendingCount() {
|
|
11722
|
+
return this.pending.length;
|
|
11723
|
+
}
|
|
11724
|
+
attach() {
|
|
11725
|
+
if (!this.cfg.enabled || this.running) return;
|
|
11726
|
+
this.running = true;
|
|
11727
|
+
this.unsubscribers.push(
|
|
11728
|
+
this.opts.events.on("tool.executed", (e) => {
|
|
11729
|
+
const lsid = this.resolveLeaderSessionId();
|
|
11730
|
+
if (lsid && e.sessionId && e.sessionId !== lsid) return;
|
|
11731
|
+
this.trackToolExecuted(e);
|
|
11732
|
+
})
|
|
11733
|
+
);
|
|
11734
|
+
if (this.cfg.signals.todoInProgress && this.resolveLeaderAgentId()) {
|
|
11735
|
+
this.unsubscribers.push(
|
|
11736
|
+
this.opts.events.on("session.agents_updated", (e) => {
|
|
11737
|
+
const lsid = this.resolveLeaderSessionId();
|
|
11738
|
+
if (lsid && e.sessionId && e.sessionId !== lsid) return;
|
|
11739
|
+
this.trackAgentTodos(e.agents);
|
|
11740
|
+
})
|
|
11741
|
+
);
|
|
11742
|
+
}
|
|
11743
|
+
if (this.cfg.signals.errorSymbol) {
|
|
11744
|
+
this.unsubscribers.push(
|
|
11745
|
+
this.opts.events.on("error", (e) => {
|
|
11746
|
+
const lsid = this.resolveLeaderSessionId();
|
|
11747
|
+
if (lsid && e.sessionId && e.sessionId !== lsid) return;
|
|
11748
|
+
this.trackError(e.err);
|
|
11749
|
+
})
|
|
11750
|
+
);
|
|
11751
|
+
}
|
|
11752
|
+
if (this.cfg.signals.mailboxAsk) {
|
|
11753
|
+
this.pollTimer = setInterval(() => {
|
|
11754
|
+
void this.pollMailbox();
|
|
11755
|
+
}, this.cfg.pollIntervalMs);
|
|
11756
|
+
this.pollTimer.unref?.();
|
|
11757
|
+
}
|
|
11758
|
+
}
|
|
11759
|
+
/** Tear down watchers without touching host intent. Cooldowns survive. */
|
|
11760
|
+
detach() {
|
|
11761
|
+
for (const unsub of this.unsubscribers.splice(0)) unsub();
|
|
11762
|
+
if (this.pollTimer) {
|
|
11763
|
+
clearInterval(this.pollTimer);
|
|
11764
|
+
this.pollTimer = void 0;
|
|
11765
|
+
}
|
|
11766
|
+
this.running = false;
|
|
11767
|
+
}
|
|
11768
|
+
// ── signal handlers ──────────────────────────────────────────────────────
|
|
11769
|
+
trackToolExecuted(e) {
|
|
11770
|
+
const tool = e.name.toLowerCase();
|
|
11771
|
+
const path42 = extractedPath(e.input);
|
|
11772
|
+
if (e.ok && this.cfg.signals.editUnreadFile && this.cfg.fileEditTools.has(tool) && path42) {
|
|
11773
|
+
if (!this.readSet.has(path42)) {
|
|
11774
|
+
this.engage({
|
|
11775
|
+
id: randomUUID6(),
|
|
11776
|
+
probe: `Map file ${path42}: role, exports, dependencies, and callers \u2014 the leader is about to edit it.`,
|
|
11777
|
+
hint: { file: path42 },
|
|
11778
|
+
context: `Leader edited ${path42} without reading it first.`,
|
|
11779
|
+
source: "edit_unread_file",
|
|
11780
|
+
subject: `file:${path42}`,
|
|
11781
|
+
createdAt: this.now()
|
|
11782
|
+
});
|
|
11783
|
+
}
|
|
11784
|
+
return;
|
|
11785
|
+
}
|
|
11786
|
+
if (e.ok && this.cfg.signals.unfamiliarRead && tool === "read" && path42) {
|
|
11787
|
+
if (!this.readSet.has(path42)) {
|
|
11788
|
+
this.readSet.add(path42);
|
|
11789
|
+
this.engage({
|
|
11790
|
+
id: randomUUID6(),
|
|
11791
|
+
probe: `Skeleton + callers + dependents of ${path42}: what it exports, who imports it, and how it fits the feature flow.`,
|
|
11792
|
+
hint: { file: path42 },
|
|
11793
|
+
context: `Leader read unfamiliar file ${path42}.`,
|
|
11794
|
+
source: "unfamiliar_read",
|
|
11795
|
+
subject: `file:${path42}`,
|
|
11796
|
+
createdAt: this.now()
|
|
11797
|
+
});
|
|
11798
|
+
}
|
|
11799
|
+
return;
|
|
11800
|
+
}
|
|
11801
|
+
if (e.ok && this.cfg.signals.searchZeroHits && this.cfg.searchTools.has(tool) && looksEmpty(e)) {
|
|
11802
|
+
const input = e.input ?? {};
|
|
11803
|
+
const query = typeof input["query"] === "string" ? input["query"] : typeof input["pattern"] === "string" ? input["pattern"] : "";
|
|
11804
|
+
this.engage({
|
|
11805
|
+
id: randomUUID6(),
|
|
11806
|
+
probe: query ? `Locate "${query}" \u2014 the leader's ${e.name} returned no hits. Try synonyms, a refreshed index, and lexical fallbacks.` : `The leader's ${e.name} returned no results. Find where the concept actually lives.`,
|
|
11807
|
+
hint: query ? { symbol: query } : void 0,
|
|
11808
|
+
context: `${e.name} for "${query}" returned zero results.`,
|
|
11809
|
+
source: "search_zero_hits",
|
|
11810
|
+
subject: `search:${query}`,
|
|
11811
|
+
createdAt: this.now()
|
|
11812
|
+
});
|
|
11813
|
+
}
|
|
11814
|
+
}
|
|
11815
|
+
trackAgentTodos(agents) {
|
|
11816
|
+
const leaderId = this.resolveLeaderAgentId();
|
|
11817
|
+
if (!leaderId) return;
|
|
11818
|
+
const leader = agents.find((a) => a.id === leaderId);
|
|
11819
|
+
if (!leader?.todos) return;
|
|
11820
|
+
for (const todo of leader.todos) {
|
|
11821
|
+
const prev = this.todoSeen.get(todo.id);
|
|
11822
|
+
if (prev !== "in_progress" && todo.status === "in_progress") {
|
|
11823
|
+
const mentions = extractSubjectTokens(todo.content);
|
|
11824
|
+
const first = mentions[0];
|
|
11825
|
+
this.engage({
|
|
11826
|
+
id: randomUUID6(),
|
|
11827
|
+
probe: `Pre-map the files/symbols behind this in-progress todo: "${todo.content.slice(0, 160)}".`,
|
|
11828
|
+
hint: first ? { [first.kind]: first.value } : void 0,
|
|
11829
|
+
context: `Todo "${todo.content.slice(0, 120)}" flipped to in_progress.`,
|
|
11830
|
+
source: "todo_in_progress",
|
|
11831
|
+
subject: `todo:${todo.id}`,
|
|
11832
|
+
createdAt: this.now()
|
|
11833
|
+
});
|
|
11834
|
+
}
|
|
11835
|
+
this.todoSeen.set(todo.id, todo.status);
|
|
11836
|
+
}
|
|
11837
|
+
}
|
|
11838
|
+
trackError(err) {
|
|
11839
|
+
const tokens = extractSubjectTokens(err.message);
|
|
11840
|
+
for (const token of tokens.slice(0, 2)) {
|
|
11841
|
+
this.engage({
|
|
11842
|
+
id: randomUUID6(),
|
|
11843
|
+
probe: `What is ${token.value}, where does it live, and who uses it? The leader hit an error naming it.`,
|
|
11844
|
+
hint: { [token.kind]: token.value },
|
|
11845
|
+
context: `Error: ${err.message.slice(0, 300)}`,
|
|
11846
|
+
source: "error_symbol",
|
|
11847
|
+
subject: `token:${token.value}`,
|
|
11848
|
+
createdAt: this.now()
|
|
11849
|
+
});
|
|
11850
|
+
}
|
|
11851
|
+
}
|
|
11852
|
+
async pollMailbox() {
|
|
11853
|
+
if (!this.cfg.enabled || !this.cfg.signals.mailboxAsk) return;
|
|
11854
|
+
try {
|
|
11855
|
+
const messages = await this.opts.mailbox.query({
|
|
11856
|
+
unreadBy: this.cfg.companionAgentId,
|
|
11857
|
+
limit: 20
|
|
11858
|
+
});
|
|
11859
|
+
const lsid = this.resolveLeaderSessionId();
|
|
11860
|
+
for (const msg of messages) {
|
|
11861
|
+
if (msg.type !== "ask" && msg.type !== "assign") continue;
|
|
11862
|
+
const fromLeader = isMailboxLeader(msg.from) || lsid != null && msg.senderSessionId === lsid;
|
|
11863
|
+
if (!fromLeader) continue;
|
|
11864
|
+
this.engage({
|
|
11865
|
+
id: randomUUID6(),
|
|
11866
|
+
probe: msg.body.trim().slice(0, 2e3) || msg.subject,
|
|
11867
|
+
context: `Direct ask from ${msg.from}: ${msg.subject}`,
|
|
11868
|
+
source: "mailbox_ask",
|
|
11869
|
+
subject: `mail:${msg.id}`,
|
|
11870
|
+
createdAt: this.now()
|
|
11871
|
+
});
|
|
11872
|
+
await this.opts.mailbox.ack({
|
|
11873
|
+
messageId: msg.id,
|
|
11874
|
+
readerId: this.cfg.companionAgentId,
|
|
11875
|
+
read: true,
|
|
11876
|
+
completed: true
|
|
11877
|
+
}).catch(() => {
|
|
11878
|
+
});
|
|
11879
|
+
}
|
|
11880
|
+
} catch {
|
|
11881
|
+
}
|
|
11882
|
+
}
|
|
11883
|
+
// ── engagement ───────────────────────────────────────────────────────────
|
|
11884
|
+
cooldownOk(subject) {
|
|
11885
|
+
const last = this.probedAt.get(subject);
|
|
11886
|
+
return last === void 0 || this.now() - last >= this.cfg.cooldownMs;
|
|
11887
|
+
}
|
|
11888
|
+
now() {
|
|
11889
|
+
return this.opts.now ? this.opts.now() : Date.now();
|
|
11890
|
+
}
|
|
11891
|
+
engage(probe) {
|
|
11892
|
+
if (!this.cfg.enabled) return;
|
|
11893
|
+
if (!this.cooldownOk(probe.subject)) return;
|
|
11894
|
+
this.probedAt.set(probe.subject, this.now());
|
|
11895
|
+
if (this.pending.length >= this.cfg.maxPending) {
|
|
11896
|
+
this.pending.shift();
|
|
11897
|
+
}
|
|
11898
|
+
this.pending.push(probe);
|
|
11899
|
+
void this.drain();
|
|
11900
|
+
}
|
|
11901
|
+
async drain() {
|
|
11902
|
+
if (this.inFlight) return;
|
|
11903
|
+
const probe = this.pending.shift();
|
|
11904
|
+
if (!probe) return;
|
|
11905
|
+
this.inFlight = true;
|
|
11906
|
+
try {
|
|
11907
|
+
await this.opts.onProbe(probe);
|
|
11908
|
+
} catch {
|
|
11909
|
+
} finally {
|
|
11910
|
+
this.inFlight = false;
|
|
11911
|
+
if (this.pending.length > 0) void this.drain();
|
|
11912
|
+
}
|
|
11913
|
+
}
|
|
11914
|
+
};
|
|
11915
|
+
|
|
11916
|
+
// src/coordination/dep-watcher.ts
|
|
11917
|
+
var DEPENDENCY_FILE_PATTERNS = [
|
|
11918
|
+
"package.json",
|
|
11919
|
+
"tsconfig.json",
|
|
11920
|
+
"pnpm-lock.yaml",
|
|
11921
|
+
"yarn.lock",
|
|
11922
|
+
"package-lock.json",
|
|
11923
|
+
"go.mod",
|
|
11924
|
+
"go.sum",
|
|
11925
|
+
"Cargo.toml",
|
|
11926
|
+
"Cargo.lock",
|
|
11927
|
+
"pyproject.toml",
|
|
11928
|
+
"setup.py",
|
|
11929
|
+
"setup.cfg",
|
|
11930
|
+
"requirements.txt",
|
|
11931
|
+
"Pipfile",
|
|
11932
|
+
"Pipfile.lock",
|
|
11933
|
+
"Gemfile",
|
|
11934
|
+
"Gemfile.lock",
|
|
11935
|
+
"composer.json",
|
|
11936
|
+
"composer.lock",
|
|
11937
|
+
"mix.exs",
|
|
11938
|
+
"mix.lock",
|
|
11939
|
+
"pom.xml",
|
|
11940
|
+
"build.gradle",
|
|
11941
|
+
"build.gradle.kts",
|
|
11942
|
+
"settings.gradle",
|
|
11943
|
+
"settings.gradle.kts",
|
|
11944
|
+
"*.csproj",
|
|
11945
|
+
"packages.config",
|
|
11946
|
+
"pubspec.yaml",
|
|
11947
|
+
"pubspec.lock",
|
|
11948
|
+
"CMakeLists.txt",
|
|
11949
|
+
"conanfile.txt",
|
|
11950
|
+
"conanfile.py",
|
|
11951
|
+
"vcpkg.json"
|
|
11952
|
+
];
|
|
11953
|
+
function makeDependencyWatcherConfig(opts) {
|
|
11954
|
+
const {
|
|
11955
|
+
projectRoot,
|
|
11956
|
+
mailbox,
|
|
11957
|
+
targetAgent = "*",
|
|
11958
|
+
watcherAgentId = "dep-watcher",
|
|
11959
|
+
debounceMs = 3e3,
|
|
11960
|
+
patterns = DEPENDENCY_FILE_PATTERNS
|
|
11961
|
+
} = opts;
|
|
11962
|
+
const watchPaths = [];
|
|
11963
|
+
for (const p of patterns) {
|
|
11964
|
+
if (p.includes("*")) {
|
|
11965
|
+
continue;
|
|
11966
|
+
}
|
|
11967
|
+
watchPaths.push(`${projectRoot}/${p}`);
|
|
11968
|
+
}
|
|
11969
|
+
watchPaths.push(projectRoot);
|
|
11970
|
+
const unique = [...new Set(watchPaths)];
|
|
11971
|
+
const isMultiRecipient = targetAgent === "*" || targetAgent.startsWith("@session:");
|
|
11972
|
+
const globPatterns = patterns.filter((p) => p.includes("*"));
|
|
11973
|
+
const plainPatterns = patterns.filter((p) => !p.includes("*"));
|
|
11974
|
+
function matchesPattern(filePath) {
|
|
11975
|
+
const basename6 = filePath.split("/").pop()?.split("\\").pop() ?? "";
|
|
11976
|
+
if (plainPatterns.includes(basename6)) return true;
|
|
11977
|
+
for (const gp of globPatterns) {
|
|
11978
|
+
const regex = new RegExp(
|
|
11979
|
+
"^" + gp.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$"
|
|
11980
|
+
);
|
|
11981
|
+
if (regex.test(basename6)) return true;
|
|
11982
|
+
}
|
|
11983
|
+
return false;
|
|
11984
|
+
}
|
|
11985
|
+
const pending = /* @__PURE__ */ new Map();
|
|
11986
|
+
return {
|
|
11987
|
+
watchPaths: unique,
|
|
11988
|
+
debounceMs,
|
|
11989
|
+
dispose() {
|
|
11990
|
+
for (const t of pending.values()) clearTimeout(t);
|
|
11991
|
+
pending.clear();
|
|
11992
|
+
},
|
|
11993
|
+
async onChange(entry) {
|
|
11994
|
+
if (entry.event === "delete") return;
|
|
11995
|
+
if (!matchesPattern(entry.path)) return;
|
|
11996
|
+
const key = entry.path;
|
|
11997
|
+
const existing = pending.get(key);
|
|
11998
|
+
if (existing) clearTimeout(existing);
|
|
11254
11999
|
pending.set(
|
|
11255
12000
|
key,
|
|
11256
12001
|
setTimeout(async () => {
|
|
@@ -11323,7 +12068,7 @@ function attachDepWatcherBridge(opts) {
|
|
|
11323
12068
|
}
|
|
11324
12069
|
|
|
11325
12070
|
// src/coordination/director.ts
|
|
11326
|
-
import { randomUUID as
|
|
12071
|
+
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
11327
12072
|
import * as fsp25 from "node:fs/promises";
|
|
11328
12073
|
|
|
11329
12074
|
// src/core/instruction-template.ts
|
|
@@ -12128,7 +12873,7 @@ var FleetContextOverflowError = class extends Error {
|
|
|
12128
12873
|
};
|
|
12129
12874
|
|
|
12130
12875
|
// src/coordination/director/director-task-registry.ts
|
|
12131
|
-
import { randomUUID as
|
|
12876
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
12132
12877
|
var DirectorTaskRegistry = class _DirectorTaskRegistry {
|
|
12133
12878
|
constructor(deps) {
|
|
12134
12879
|
this.deps = deps;
|
|
@@ -12163,7 +12908,7 @@ var DirectorTaskRegistry = class _DirectorTaskRegistry {
|
|
|
12163
12908
|
return { internal, consumedInBand: waiter !== void 0 || anyConsumed };
|
|
12164
12909
|
}
|
|
12165
12910
|
async assign(task) {
|
|
12166
|
-
const taskWithId = task.id ? task : { ...task, id:
|
|
12911
|
+
const taskWithId = task.id ? task : { ...task, id: randomUUID7() };
|
|
12167
12912
|
if (this.deps.isWorkComplete()) {
|
|
12168
12913
|
const stopped = this.makeStoppedResult(
|
|
12169
12914
|
taskWithId.id,
|
|
@@ -12183,7 +12928,7 @@ var DirectorTaskRegistry = class _DirectorTaskRegistry {
|
|
|
12183
12928
|
return taskWithId.id;
|
|
12184
12929
|
}
|
|
12185
12930
|
async assignInternal(task) {
|
|
12186
|
-
const taskWithId = task.id ? task : { ...task, id:
|
|
12931
|
+
const taskWithId = task.id ? task : { ...task, id: randomUUID7() };
|
|
12187
12932
|
this.internalTaskIds.add(taskWithId.id);
|
|
12188
12933
|
try {
|
|
12189
12934
|
await this.deps.coordinator.assign(taskWithId);
|
|
@@ -12398,7 +13143,7 @@ ${JSON.stringify(result.result, null, 2)}
|
|
|
12398
13143
|
};
|
|
12399
13144
|
|
|
12400
13145
|
// src/coordination/director-tools.ts
|
|
12401
|
-
import { randomUUID as
|
|
13146
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
12402
13147
|
import {
|
|
12403
13148
|
completeKanbanDispatch,
|
|
12404
13149
|
failKanbanDispatch,
|
|
@@ -12411,7 +13156,7 @@ import {
|
|
|
12411
13156
|
} from "@wrongstack/kanban";
|
|
12412
13157
|
|
|
12413
13158
|
// src/coordination/director-input-helpers.ts
|
|
12414
|
-
import { randomUUID as
|
|
13159
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
12415
13160
|
function stringArray2(value) {
|
|
12416
13161
|
if (!Array.isArray(value)) return void 0;
|
|
12417
13162
|
const strings = value.filter((v) => typeof v === "string" && v.trim().length > 0);
|
|
@@ -12425,7 +13170,7 @@ function normalizeWorktreeOverride(value) {
|
|
|
12425
13170
|
function instantiateRosterConfig2(role, base) {
|
|
12426
13171
|
return {
|
|
12427
13172
|
...base,
|
|
12428
|
-
id: `${role}-${
|
|
13173
|
+
id: `${role}-${randomUUID8().slice(0, 8)}`
|
|
12429
13174
|
};
|
|
12430
13175
|
}
|
|
12431
13176
|
|
|
@@ -12492,7 +13237,7 @@ function buildKanbanFleetTaskPrompt(board, task, lease) {
|
|
|
12492
13237
|
const dependencyLines = (task.dependsOn ?? []).map((depId) => board.tasks.find((candidate) => candidate.id === depId)).filter((dep) => Boolean(dep)).map((dep) => `- ${dep.title} [${dep.status}] (${dep.id})`);
|
|
12493
13238
|
const checks = task.successCriteria?.map((check) => `- ${check.description}`).join("\n");
|
|
12494
13239
|
const metrics = task.goalMetrics?.map(
|
|
12495
|
-
(metric) => `- ${metric.name}: ${metric.current ?? "n/a"}${metric.target !== void 0 ? ` / ${metric.target}` : ""}${metric.unit ? ` ${metric.unit}` : ""} [${metric.status}]`
|
|
13240
|
+
(metric) => `- ${metric.name}: ${metric.current ?? "n/a"}${metric.target !== void 0 ? ` / ${metric.direction === "at_most" ? "\u2264" : "\u2265"} ${metric.target}` : ""}${metric.unit ? ` ${metric.unit}` : ""} [${metric.status}]`
|
|
12496
13241
|
).join("\n");
|
|
12497
13242
|
const chain = task.chain ? [
|
|
12498
13243
|
`chainId: ${task.chain.chainId}`,
|
|
@@ -12766,7 +13511,7 @@ function makeLLMClassifier(complete2) {
|
|
|
12766
13511
|
}
|
|
12767
13512
|
|
|
12768
13513
|
// src/coordination/director-basic-tools.ts
|
|
12769
|
-
import { randomUUID as
|
|
13514
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
12770
13515
|
function makeAssignTool(director) {
|
|
12771
13516
|
const inputSchema = {
|
|
12772
13517
|
type: "object",
|
|
@@ -12805,7 +13550,7 @@ function makeAssignTool(director) {
|
|
|
12805
13550
|
};
|
|
12806
13551
|
}
|
|
12807
13552
|
const task = {
|
|
12808
|
-
id:
|
|
13553
|
+
id: randomUUID9(),
|
|
12809
13554
|
description: composeBoundedTaskDescription(i.description, boundary.boundary),
|
|
12810
13555
|
subagentId: i.subagentId,
|
|
12811
13556
|
maxToolCalls: i.maxToolCalls,
|
|
@@ -13225,7 +13970,7 @@ function makeWorkCompleteTool(director) {
|
|
|
13225
13970
|
}
|
|
13226
13971
|
|
|
13227
13972
|
// src/coordination/director-quality-gate-tool.ts
|
|
13228
|
-
import { randomUUID as
|
|
13973
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
13229
13974
|
function makeQualityGateTool(director, roster) {
|
|
13230
13975
|
return {
|
|
13231
13976
|
name: "quality_gate",
|
|
@@ -13322,7 +14067,7 @@ function makeQualityGateTool(director, roster) {
|
|
|
13322
14067
|
makeQualityGateSubagentConfig("verifier", roster, i.verifierWorktree ?? "auto")
|
|
13323
14068
|
);
|
|
13324
14069
|
const taskId = await director.assign({
|
|
13325
|
-
id:
|
|
14070
|
+
id: randomUUID10(),
|
|
13326
14071
|
subagentId,
|
|
13327
14072
|
description: buildVerifierTask(i, {
|
|
13328
14073
|
attempt,
|
|
@@ -13340,7 +14085,7 @@ function makeQualityGateTool(director, roster) {
|
|
|
13340
14085
|
makeQualityGateSubagentConfig("reviewer", roster, i.reviewerWorktree ?? "off")
|
|
13341
14086
|
);
|
|
13342
14087
|
const taskId = await director.assign({
|
|
13343
|
-
id:
|
|
14088
|
+
id: randomUUID10(),
|
|
13344
14089
|
subagentId,
|
|
13345
14090
|
description: buildReviewerTask(i, {
|
|
13346
14091
|
attempt,
|
|
@@ -13368,7 +14113,7 @@ function makeQualityGateTool(director, roster) {
|
|
|
13368
14113
|
};
|
|
13369
14114
|
}
|
|
13370
14115
|
const repairTaskId = await director.assign({
|
|
13371
|
-
id:
|
|
14116
|
+
id: randomUUID10(),
|
|
13372
14117
|
subagentId: i.repairSubagentId,
|
|
13373
14118
|
description: buildRepairTask(i, attempts[attempts.length - 1], attempt),
|
|
13374
14119
|
timeoutMs: i.timeoutMs
|
|
@@ -13598,6 +14343,413 @@ function excerpt(text, max) {
|
|
|
13598
14343
|
...(truncated)`;
|
|
13599
14344
|
}
|
|
13600
14345
|
|
|
14346
|
+
// src/coordination/director-mutation-test-tool.ts
|
|
14347
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
14348
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
14349
|
+
import { isAbsolute as isAbsolute3, join as join16 } from "node:path";
|
|
14350
|
+
|
|
14351
|
+
// src/coordination/mutation-engine.ts
|
|
14352
|
+
var TOKEN_PATTERNS = [
|
|
14353
|
+
{
|
|
14354
|
+
kind: "relax-boundary",
|
|
14355
|
+
// `>` not followed by `=` and not part of `=>` or `>>`; require code-ish
|
|
14356
|
+
// context on both sides so generic text (JSX, strings) is not touched.
|
|
14357
|
+
regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>>(?!=|>))/g,
|
|
14358
|
+
replace: () => ">="
|
|
14359
|
+
},
|
|
14360
|
+
{
|
|
14361
|
+
kind: "tighten-boundary",
|
|
14362
|
+
regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>>=)/g,
|
|
14363
|
+
replace: () => ">"
|
|
14364
|
+
},
|
|
14365
|
+
{
|
|
14366
|
+
kind: "arith-plus-to-minus",
|
|
14367
|
+
// `+` between operands (binary), not `++`, unary `+x`, or `+=`.
|
|
14368
|
+
regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>\+(?!\+|=))/g,
|
|
14369
|
+
replace: () => "-"
|
|
14370
|
+
},
|
|
14371
|
+
{
|
|
14372
|
+
kind: "arith-minus-to-plus",
|
|
14373
|
+
// Binary `-` between operands, not `--`, `-=` or negative-number literal.
|
|
14374
|
+
regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>-(?!-|=))/g,
|
|
14375
|
+
replace: () => "+"
|
|
14376
|
+
},
|
|
14377
|
+
{
|
|
14378
|
+
kind: "negate-boolean",
|
|
14379
|
+
// Standalone boolean literals used as values, not property names.
|
|
14380
|
+
regex: /(?<![.\w$])(?<op>true|false)(?![\w$])/g,
|
|
14381
|
+
replace: (m) => m === "true" ? "false" : "true"
|
|
14382
|
+
},
|
|
14383
|
+
{
|
|
14384
|
+
kind: "return-null",
|
|
14385
|
+
// `return <expr>;` where expr is not already null/undefined/void.
|
|
14386
|
+
regex: /(?<indent>\breturn\b)(?<expr>\s+[^;{}\n]+?)\s*;/g,
|
|
14387
|
+
replace: () => "return null;"
|
|
14388
|
+
}
|
|
14389
|
+
];
|
|
14390
|
+
function planMutations(file, source, opts = {}) {
|
|
14391
|
+
const maxPerFile = opts.maxPerFile ?? 25;
|
|
14392
|
+
const out = [];
|
|
14393
|
+
const lines = source.split("\n");
|
|
14394
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
14395
|
+
const line = lines[lineIdx];
|
|
14396
|
+
const t = line.trim();
|
|
14397
|
+
if (t.startsWith("//") || t.startsWith("*") || t.startsWith("/*")) continue;
|
|
14398
|
+
for (const pattern of TOKEN_PATTERNS) {
|
|
14399
|
+
pattern.regex.lastIndex = 0;
|
|
14400
|
+
let m;
|
|
14401
|
+
while ((m = pattern.regex.exec(line)) !== null) {
|
|
14402
|
+
const token = m.groups?.["op"] ?? m[0];
|
|
14403
|
+
const tokenStart = m.index + m[0].indexOf(token);
|
|
14404
|
+
if (isMasked(line, tokenStart, token.length)) continue;
|
|
14405
|
+
const original = line.slice(tokenStart, tokenStart + token.length);
|
|
14406
|
+
const replacement = pattern.replace(token);
|
|
14407
|
+
if (replacement === original) continue;
|
|
14408
|
+
out.push({
|
|
14409
|
+
id: `${pattern.kind}#${lineIdx + 1}#${tokenStart + 1}`,
|
|
14410
|
+
kind: pattern.kind,
|
|
14411
|
+
file,
|
|
14412
|
+
line: lineIdx + 1,
|
|
14413
|
+
column: tokenStart + 1,
|
|
14414
|
+
original,
|
|
14415
|
+
replacement
|
|
14416
|
+
});
|
|
14417
|
+
}
|
|
14418
|
+
}
|
|
14419
|
+
if (out.length >= maxPerFile) break;
|
|
14420
|
+
}
|
|
14421
|
+
return out.slice(0, maxPerFile);
|
|
14422
|
+
}
|
|
14423
|
+
function isMasked(line, start, len) {
|
|
14424
|
+
let inSingle = false;
|
|
14425
|
+
let inDouble = false;
|
|
14426
|
+
for (let i = 0; i < start; i++) {
|
|
14427
|
+
const c = line[i];
|
|
14428
|
+
const prev = i > 0 ? line[i - 1] : void 0;
|
|
14429
|
+
if (c === "'" && prev !== "\\") inSingle = !inSingle;
|
|
14430
|
+
else if (c === '"' && prev !== "\\") inDouble = !inDouble;
|
|
14431
|
+
if (!inSingle && !inDouble && c === "/" && prev === "/") return true;
|
|
14432
|
+
}
|
|
14433
|
+
if (inSingle || inDouble) return true;
|
|
14434
|
+
const window = line.slice(start, start + len);
|
|
14435
|
+
return /['"]/.test(window);
|
|
14436
|
+
}
|
|
14437
|
+
function parseMutationReport(text) {
|
|
14438
|
+
const candidates = [];
|
|
14439
|
+
const fence = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
14440
|
+
if (fence?.[1]) candidates.push(fence[1].trim());
|
|
14441
|
+
const firstBrace = text.indexOf("{");
|
|
14442
|
+
if (firstBrace >= 0) candidates.push(extractBalancedObject(text, firstBrace));
|
|
14443
|
+
for (const candidate of candidates) {
|
|
14444
|
+
if (!candidate) continue;
|
|
14445
|
+
try {
|
|
14446
|
+
const parsed = JSON.parse(candidate);
|
|
14447
|
+
if (!Array.isArray(parsed.mutants)) continue;
|
|
14448
|
+
return {
|
|
14449
|
+
mutants: parsed.mutants.map(normalizeMutantEntry).filter((x) => Boolean(x)),
|
|
14450
|
+
summary: typeof parsed.summary === "string" ? parsed.summary : void 0
|
|
14451
|
+
};
|
|
14452
|
+
} catch {
|
|
14453
|
+
}
|
|
14454
|
+
}
|
|
14455
|
+
return void 0;
|
|
14456
|
+
}
|
|
14457
|
+
function extractBalancedObject(text, start) {
|
|
14458
|
+
let depth = 0;
|
|
14459
|
+
let inString = false;
|
|
14460
|
+
let escaped = false;
|
|
14461
|
+
for (let i = start; i < text.length; i++) {
|
|
14462
|
+
const c = text[i];
|
|
14463
|
+
if (escaped) {
|
|
14464
|
+
escaped = false;
|
|
14465
|
+
continue;
|
|
14466
|
+
}
|
|
14467
|
+
if (c === "\\") {
|
|
14468
|
+
escaped = true;
|
|
14469
|
+
continue;
|
|
14470
|
+
}
|
|
14471
|
+
if (c === '"') inString = !inString;
|
|
14472
|
+
if (inString) continue;
|
|
14473
|
+
if (c === "{") depth++;
|
|
14474
|
+
else if (c === "}") {
|
|
14475
|
+
depth--;
|
|
14476
|
+
if (depth === 0) return text.slice(start, i + 1);
|
|
14477
|
+
}
|
|
14478
|
+
}
|
|
14479
|
+
return text.slice(start);
|
|
14480
|
+
}
|
|
14481
|
+
function normalizeMutantEntry(value) {
|
|
14482
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
14483
|
+
const rec = value;
|
|
14484
|
+
const id = typeof rec["id"] === "string" ? rec["id"] : void 0;
|
|
14485
|
+
const status = rec["status"];
|
|
14486
|
+
if (!id || status !== "killed" && status !== "survived" && status !== "skipped") {
|
|
14487
|
+
return void 0;
|
|
14488
|
+
}
|
|
14489
|
+
return {
|
|
14490
|
+
id,
|
|
14491
|
+
file: typeof rec["file"] === "string" ? rec["file"] : "",
|
|
14492
|
+
line: typeof rec["line"] === "number" ? rec["line"] : 0,
|
|
14493
|
+
kind: typeof rec["kind"] === "string" ? rec["kind"] : "",
|
|
14494
|
+
status,
|
|
14495
|
+
evidence: typeof rec["evidence"] === "string" ? rec["evidence"] : void 0
|
|
14496
|
+
};
|
|
14497
|
+
}
|
|
14498
|
+
|
|
14499
|
+
// src/coordination/director-mutation-test-tool.ts
|
|
14500
|
+
var DEFAULT_MAX_PER_FILE = 10;
|
|
14501
|
+
var DEFAULT_MAX_STRENGTHEN_ATTEMPTS = 2;
|
|
14502
|
+
var CHAOS_ROLE = "chaos-monkey";
|
|
14503
|
+
function makeMutationTestTool(director, roster, opts = {}) {
|
|
14504
|
+
return {
|
|
14505
|
+
name: "mutation_test",
|
|
14506
|
+
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.",
|
|
14507
|
+
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.",
|
|
14508
|
+
permission: "auto",
|
|
14509
|
+
mutating: false,
|
|
14510
|
+
capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
|
|
14511
|
+
inputSchema: {
|
|
14512
|
+
type: "object",
|
|
14513
|
+
properties: {
|
|
14514
|
+
targets: {
|
|
14515
|
+
type: "array",
|
|
14516
|
+
items: { type: "string" },
|
|
14517
|
+
description: "Project-relative (or absolute) source files to mutate. Keep to files changed by the current task."
|
|
14518
|
+
},
|
|
14519
|
+
testCommand: {
|
|
14520
|
+
type: "string",
|
|
14521
|
+
description: 'Exact command that runs the relevant tests, e.g. "pnpm exec vitest run packages/core/tests/coordination/mutation-engine.test.ts".'
|
|
14522
|
+
},
|
|
14523
|
+
cwd: { type: "string", description: "Working directory for the test command." },
|
|
14524
|
+
maxPerFile: {
|
|
14525
|
+
type: "number",
|
|
14526
|
+
minimum: 1,
|
|
14527
|
+
maximum: 25,
|
|
14528
|
+
description: "Mutant cap per file per pass. Default 10."
|
|
14529
|
+
},
|
|
14530
|
+
maxStrengthenAttempts: {
|
|
14531
|
+
type: "number",
|
|
14532
|
+
minimum: 0,
|
|
14533
|
+
maximum: 5,
|
|
14534
|
+
description: "Strengthen\u2192re-verify rounds. Default 2 when repairSubagentId is set, else 0."
|
|
14535
|
+
},
|
|
14536
|
+
repairSubagentId: {
|
|
14537
|
+
type: "string",
|
|
14538
|
+
description: "Subagent that owns the tests. When set and mutants survive, it receives a strengthen-tests task and the survivors are re-verified."
|
|
14539
|
+
},
|
|
14540
|
+
chaosWorktree: {
|
|
14541
|
+
anyOf: [{ type: "boolean" }, { type: "string", enum: ["auto", "required", "off"] }],
|
|
14542
|
+
description: "Worktree override for the chaos agent. Use 'off' when targets are uncommitted \u2014 a worktree from HEAD would not contain them."
|
|
14543
|
+
},
|
|
14544
|
+
timeoutMs: { type: "number", minimum: 1, description: "Per-task timeout for chaos/strengthen/rerun tasks." },
|
|
14545
|
+
reportOnly: {
|
|
14546
|
+
type: "boolean",
|
|
14547
|
+
description: "Skip the strengthen loop even when survivors exist. Default false."
|
|
14548
|
+
}
|
|
14549
|
+
},
|
|
14550
|
+
required: ["targets", "testCommand"],
|
|
14551
|
+
additionalProperties: false
|
|
14552
|
+
},
|
|
14553
|
+
async execute(input, ctx) {
|
|
14554
|
+
const i = normalizeMutationTestInput(input);
|
|
14555
|
+
const root = opts.projectRoot ?? ctx.projectRoot;
|
|
14556
|
+
const plan = buildPlan(i, root);
|
|
14557
|
+
if (plan.length === 0) {
|
|
14558
|
+
return {
|
|
14559
|
+
verdict: "inconclusive",
|
|
14560
|
+
passed: false,
|
|
14561
|
+
error: "No mutable sites found in the given targets (after comment/string filtering)."
|
|
14562
|
+
};
|
|
14563
|
+
}
|
|
14564
|
+
const chaosSubagentId = await director.spawn(
|
|
14565
|
+
makeChaosConfig(roster, i.chaosWorktree ?? "off")
|
|
14566
|
+
);
|
|
14567
|
+
const chaosTaskId = await director.assign({
|
|
14568
|
+
id: randomUUID11(),
|
|
14569
|
+
subagentId: chaosSubagentId,
|
|
14570
|
+
description: buildChaosTask(plan, i, 1, []),
|
|
14571
|
+
timeoutMs: i.timeoutMs
|
|
14572
|
+
});
|
|
14573
|
+
const [chaosResult] = await director.awaitTasks([chaosTaskId]);
|
|
14574
|
+
const pass1 = collectOutcomes(chaosResult, plan);
|
|
14575
|
+
const survivors = pass1.filter((m) => m.status === "survived");
|
|
14576
|
+
const maxAttempts = clamp(
|
|
14577
|
+
i.maxStrengthenAttempts ?? (i.repairSubagentId && !i.reportOnly ? DEFAULT_MAX_STRENGTHEN_ATTEMPTS : 0),
|
|
14578
|
+
0,
|
|
14579
|
+
5
|
|
14580
|
+
);
|
|
14581
|
+
const attempts = [];
|
|
14582
|
+
let current = survivors;
|
|
14583
|
+
while (current.length > 0 && attempts.length < maxAttempts && i.repairSubagentId) {
|
|
14584
|
+
const attemptNo = attempts.length + 1;
|
|
14585
|
+
const strengthenTaskId = await director.assign({
|
|
14586
|
+
id: randomUUID11(),
|
|
14587
|
+
subagentId: i.repairSubagentId,
|
|
14588
|
+
description: buildStrengthenTask(current, i, attemptNo),
|
|
14589
|
+
timeoutMs: i.timeoutMs
|
|
14590
|
+
});
|
|
14591
|
+
const [strengthenResult] = await director.awaitTasks([strengthenTaskId]);
|
|
14592
|
+
if (strengthenResult?.status !== "success") {
|
|
14593
|
+
attempts.push({
|
|
14594
|
+
attempt: attemptNo,
|
|
14595
|
+
survivorsBefore: current,
|
|
14596
|
+
strengthenResult: strengthenResult ? { taskId: strengthenResult.taskId, status: strengthenResult.status } : void 0,
|
|
14597
|
+
survivorsAfter: current,
|
|
14598
|
+
suspectedEquivalent: []
|
|
14599
|
+
});
|
|
14600
|
+
break;
|
|
14601
|
+
}
|
|
14602
|
+
const survivorPlan = plan.filter((p) => current.some((s) => s.id === p.id));
|
|
14603
|
+
const rerunSubagentId = await director.spawn(
|
|
14604
|
+
makeChaosConfig(roster, i.chaosWorktree ?? "off")
|
|
14605
|
+
);
|
|
14606
|
+
const rerunTaskId = await director.assign({
|
|
14607
|
+
id: randomUUID11(),
|
|
14608
|
+
subagentId: rerunSubagentId,
|
|
14609
|
+
description: buildChaosTask(survivorPlan, i, attemptNo + 1, current),
|
|
14610
|
+
timeoutMs: i.timeoutMs
|
|
14611
|
+
});
|
|
14612
|
+
const [rerunResult] = await director.awaitTasks([rerunTaskId]);
|
|
14613
|
+
const passN = collectOutcomes(rerunResult, survivorPlan);
|
|
14614
|
+
const stillSurviving = passN.filter((m) => m.status === "survived" || m.status === "skipped");
|
|
14615
|
+
attempts.push({
|
|
14616
|
+
attempt: attemptNo,
|
|
14617
|
+
survivorsBefore: current,
|
|
14618
|
+
strengthenResult: { taskId: strengthenResult.taskId, status: strengthenResult.status },
|
|
14619
|
+
rerunResult: { taskId: rerunTaskId, status: rerunResult?.status ?? "unknown" },
|
|
14620
|
+
survivorsAfter: stillSurviving,
|
|
14621
|
+
suspectedEquivalent: stillSurviving.filter((m) => current.some((c) => c.id === m.id)).map((m) => m.id)
|
|
14622
|
+
});
|
|
14623
|
+
current = stillSurviving.filter((m) => m.status === "survived");
|
|
14624
|
+
if (passN.every((m) => m.status === "skipped")) break;
|
|
14625
|
+
}
|
|
14626
|
+
const finalSurvivors = current;
|
|
14627
|
+
const verifiedCount = pass1.filter((m) => m.status !== "skipped").length;
|
|
14628
|
+
const skippedCount = pass1.filter((m) => m.status === "skipped").length;
|
|
14629
|
+
const score = plan.length === 0 ? 0 : pass1.filter((m) => m.status === "killed").length / plan.length;
|
|
14630
|
+
const verdict = verifiedCount === 0 ? "inconclusive" : finalSurvivors.length === 0 ? skippedCount > 0 ? "partial" : "pass" : score >= 0.8 ? "partial" : "fail";
|
|
14631
|
+
return {
|
|
14632
|
+
verdict,
|
|
14633
|
+
passed: verdict === "pass",
|
|
14634
|
+
mutationScore: Number.parseFloat(score.toFixed(3)),
|
|
14635
|
+
planned: plan.length,
|
|
14636
|
+
killed: pass1.filter((m) => m.status === "killed").length,
|
|
14637
|
+
survived: pass1.filter((m) => m.status === "survived").length,
|
|
14638
|
+
skipped: pass1.filter((m) => m.status === "skipped").length,
|
|
14639
|
+
finalSurvivors: finalSurvivors.map((m) => ({ id: m.id, file: m.file, kind: m.kind })),
|
|
14640
|
+
suspectedEquivalent: attempts.flatMap((a) => a.suspectedEquivalent),
|
|
14641
|
+
strengthenAttempts: attempts.length,
|
|
14642
|
+
attempts,
|
|
14643
|
+
chaosTaskId,
|
|
14644
|
+
nextAction: finalSurvivors.length === 0 ? "accept" : attempts.length >= maxAttempts && i.repairSubagentId ? "manual_review_survivors" : "strengthen_tests"
|
|
14645
|
+
};
|
|
14646
|
+
}
|
|
14647
|
+
};
|
|
14648
|
+
}
|
|
14649
|
+
function normalizeMutationTestInput(input) {
|
|
14650
|
+
const raw = input ?? {};
|
|
14651
|
+
const targets = stringArray2(raw["targets"]) ?? [];
|
|
14652
|
+
const testCommand = typeof raw["testCommand"] === "string" ? raw["testCommand"].trim() : "";
|
|
14653
|
+
return {
|
|
14654
|
+
targets: targets.filter(Boolean),
|
|
14655
|
+
testCommand,
|
|
14656
|
+
cwd: typeof raw["cwd"] === "string" && raw["cwd"].trim() ? raw["cwd"].trim() : void 0,
|
|
14657
|
+
maxPerFile: typeof raw["maxPerFile"] === "number" ? raw["maxPerFile"] : void 0,
|
|
14658
|
+
maxStrengthenAttempts: typeof raw["maxStrengthenAttempts"] === "number" ? raw["maxStrengthenAttempts"] : void 0,
|
|
14659
|
+
repairSubagentId: typeof raw["repairSubagentId"] === "string" && raw["repairSubagentId"].trim() ? raw["repairSubagentId"].trim() : void 0,
|
|
14660
|
+
chaosWorktree: raw["chaosWorktree"] ?? void 0,
|
|
14661
|
+
timeoutMs: typeof raw["timeoutMs"] === "number" ? raw["timeoutMs"] : void 0,
|
|
14662
|
+
reportOnly: raw["reportOnly"] === true
|
|
14663
|
+
};
|
|
14664
|
+
}
|
|
14665
|
+
function clamp(n, lo, hi) {
|
|
14666
|
+
return Math.min(hi, Math.max(lo, n));
|
|
14667
|
+
}
|
|
14668
|
+
function buildPlan(i, projectRoot) {
|
|
14669
|
+
const plan = [];
|
|
14670
|
+
for (const target of i.targets) {
|
|
14671
|
+
const abs = isAbsolute3(target) ? target : join16(projectRoot ?? process.cwd(), target);
|
|
14672
|
+
let source;
|
|
14673
|
+
try {
|
|
14674
|
+
source = readFileSync13(abs, "utf8");
|
|
14675
|
+
} catch {
|
|
14676
|
+
continue;
|
|
14677
|
+
}
|
|
14678
|
+
plan.push(...planMutations(target, source, { maxPerFile: i.maxPerFile ?? DEFAULT_MAX_PER_FILE }));
|
|
14679
|
+
}
|
|
14680
|
+
return plan;
|
|
14681
|
+
}
|
|
14682
|
+
function makeChaosConfig(roster, worktree) {
|
|
14683
|
+
const base = roster?.[CHAOS_ROLE] ?? getAgentDefinition(CHAOS_ROLE)?.config ?? { name: "Chaos Monkey", role: CHAOS_ROLE };
|
|
14684
|
+
return { ...instantiateRosterConfig2(CHAOS_ROLE, base), worktree };
|
|
14685
|
+
}
|
|
14686
|
+
function buildChaosTask(plan, i, pass, priorSurvivors) {
|
|
14687
|
+
const mutants = plan.map(
|
|
14688
|
+
(m) => `- ${m.id} | ${m.file}:${m.line}:${m.column} | ${m.kind} | "${m.original}" -> "${m.replacement}"`
|
|
14689
|
+
).join("\n");
|
|
14690
|
+
const prior = priorSurvivors.length > 0 ? `
|
|
14691
|
+
These mutants survived a previous pass (pass ${pass - 1}) \u2014 re-verify them against the STRENGTHENED tests:
|
|
14692
|
+
${priorSurvivors.map((s) => `- ${s.id} (${s.kind} @ ${s.file}:${s.line})`).join("\n")}` : "";
|
|
14693
|
+
return [
|
|
14694
|
+
"Execute this deterministic mutation plan against the current checkout.",
|
|
14695
|
+
"",
|
|
14696
|
+
"For each mutant, in order:",
|
|
14697
|
+
"1. Apply ONLY that mutation at its exact (file, line, column).",
|
|
14698
|
+
`2. Run the test command: ${i.testCommand}${i.cwd ? ` (cwd: ${i.cwd})` : ""}`,
|
|
14699
|
+
"3. Record killed (tests failed \u2014 quote first failing assertion) or survived (suite green).",
|
|
14700
|
+
"4. Restore the file byte-for-byte before the next mutant.",
|
|
14701
|
+
"",
|
|
14702
|
+
"Mutants:",
|
|
14703
|
+
mutants,
|
|
14704
|
+
prior,
|
|
14705
|
+
"",
|
|
14706
|
+
"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.",
|
|
14707
|
+
"Finish with submit_result, then repeat the same JSON as your final text."
|
|
14708
|
+
].join("\n");
|
|
14709
|
+
}
|
|
14710
|
+
function buildStrengthenTask(survivors, i, attempt) {
|
|
14711
|
+
return [
|
|
14712
|
+
`Strengthen the tests so these SURVIVING mutants die (attempt ${attempt}).`,
|
|
14713
|
+
"",
|
|
14714
|
+
"Each survivor below was a deliberate sabotage of production code that the current suite did NOT catch:",
|
|
14715
|
+
...survivors.map((s) => `- ${s.id} | ${s.file}:${s.line} | ${s.kind}${s.evidence ? ` | ${s.evidence}` : ""}`),
|
|
14716
|
+
"",
|
|
14717
|
+
`Test command that must fail under each mutant: ${i.testCommand}`,
|
|
14718
|
+
"",
|
|
14719
|
+
"For each 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."
|
|
14720
|
+
].join("\n");
|
|
14721
|
+
}
|
|
14722
|
+
function collectOutcomes(result, plan) {
|
|
14723
|
+
const fromText = parseTextOutcomes(result);
|
|
14724
|
+
if (fromText.length > 0) {
|
|
14725
|
+
const planned = new Set(plan.map((p) => p.id));
|
|
14726
|
+
const matched = fromText.filter((m) => planned.has(m.id));
|
|
14727
|
+
if (matched.length > 0) return matched;
|
|
14728
|
+
}
|
|
14729
|
+
return plan.map((p) => ({
|
|
14730
|
+
id: p.id,
|
|
14731
|
+
file: p.file,
|
|
14732
|
+
line: p.line,
|
|
14733
|
+
kind: p.kind,
|
|
14734
|
+
status: "skipped",
|
|
14735
|
+
evidence: result ? `chaos task ended ${result.status}` : "chaos task produced no result"
|
|
14736
|
+
}));
|
|
14737
|
+
}
|
|
14738
|
+
function parseTextOutcomes(result) {
|
|
14739
|
+
const text = typeof result?.result === "string" ? result.result : void 0;
|
|
14740
|
+
if (!text) return [];
|
|
14741
|
+
const parsed = parseMutationReport(text);
|
|
14742
|
+
if (!parsed) return [];
|
|
14743
|
+
return parsed.mutants.map((m) => ({
|
|
14744
|
+
id: m.id,
|
|
14745
|
+
file: m.file,
|
|
14746
|
+
line: m.line,
|
|
14747
|
+
kind: m.kind,
|
|
14748
|
+
status: m.status,
|
|
14749
|
+
evidence: m.evidence
|
|
14750
|
+
}));
|
|
14751
|
+
}
|
|
14752
|
+
|
|
13601
14753
|
// src/coordination/director-tools.ts
|
|
13602
14754
|
function makeSpawnTool(director, roster) {
|
|
13603
14755
|
const dispatchCatalog = () => {
|
|
@@ -13890,7 +15042,7 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
13890
15042
|
try {
|
|
13891
15043
|
const config = buildKanbanSubagentConfig(claim.task, i, roster, instantiateRosterConfig2);
|
|
13892
15044
|
subagentId = await director.spawn(config);
|
|
13893
|
-
const dispatchTaskId =
|
|
15045
|
+
const dispatchTaskId = randomUUID12();
|
|
13894
15046
|
const taskSpec = {
|
|
13895
15047
|
id: dispatchTaskId,
|
|
13896
15048
|
subagentId,
|
|
@@ -14166,6 +15318,7 @@ function buildDirectorToolset(director, roster) {
|
|
|
14166
15318
|
makeAskResultTool(director),
|
|
14167
15319
|
makeRollUpTool(director),
|
|
14168
15320
|
makeQualityGateTool(director, roster),
|
|
15321
|
+
makeMutationTestTool(director, roster),
|
|
14169
15322
|
makeTerminateTool(director),
|
|
14170
15323
|
makeTerminateAllTool(director),
|
|
14171
15324
|
makeFleetTool(director),
|
|
@@ -14252,7 +15405,7 @@ import * as fsp24 from "node:fs/promises";
|
|
|
14252
15405
|
import * as path32 from "node:path";
|
|
14253
15406
|
|
|
14254
15407
|
// src/storage/session-store.ts
|
|
14255
|
-
import { randomUUID as
|
|
15408
|
+
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
14256
15409
|
import * as fsp23 from "node:fs/promises";
|
|
14257
15410
|
import * as path31 from "node:path";
|
|
14258
15411
|
|
|
@@ -15976,7 +17129,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
15976
17129
|
|
|
15977
17130
|
// src/storage/session-checkpoint-cas.ts
|
|
15978
17131
|
import { spawn as spawn3 } from "node:child_process";
|
|
15979
|
-
import { createHash as createHash3, randomUUID as
|
|
17132
|
+
import { createHash as createHash3, randomUUID as randomUUID13 } from "node:crypto";
|
|
15980
17133
|
import * as fsp10 from "node:fs/promises";
|
|
15981
17134
|
import * as path23 from "node:path";
|
|
15982
17135
|
|
|
@@ -16234,7 +17387,7 @@ var SessionCheckpointCas = class {
|
|
|
16234
17387
|
}
|
|
16235
17388
|
const temp = path23.join(
|
|
16236
17389
|
path23.dirname(target),
|
|
16237
|
-
`.${path23.basename(target)}.${process.pid}.${
|
|
17390
|
+
`.${path23.basename(target)}.${process.pid}.${randomUUID13()}.tmp`
|
|
16238
17391
|
);
|
|
16239
17392
|
let handle;
|
|
16240
17393
|
try {
|
|
@@ -18030,7 +19183,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
18030
19183
|
onAppend;
|
|
18031
19184
|
onAppendBatch;
|
|
18032
19185
|
catalogClient;
|
|
18033
|
-
maintenanceHolderId =
|
|
19186
|
+
maintenanceHolderId = randomUUID14();
|
|
18034
19187
|
_loadCache = /* @__PURE__ */ new Map();
|
|
18035
19188
|
loadCache = new SessionLoadCache(this._loadCache);
|
|
18036
19189
|
_indexCache = null;
|
|
@@ -20237,7 +21390,7 @@ function hashStr(s) {
|
|
|
20237
21390
|
}
|
|
20238
21391
|
|
|
20239
21392
|
// src/coordination/multi-agent-coordinator.ts
|
|
20240
|
-
import { randomUUID as
|
|
21393
|
+
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
20241
21394
|
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
20242
21395
|
|
|
20243
21396
|
// src/coordination/coordinator/error-classifier.ts
|
|
@@ -20328,7 +21481,8 @@ async function executeSubagentWithTimeout({
|
|
|
20328
21481
|
budget,
|
|
20329
21482
|
preemptFraction = TIMEOUT_PREEMPT_FRACTION,
|
|
20330
21483
|
abortSubagent,
|
|
20331
|
-
currentSessionId
|
|
21484
|
+
currentSessionId,
|
|
21485
|
+
gracefulFinish
|
|
20332
21486
|
}) {
|
|
20333
21487
|
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
20334
21488
|
const idleLimitMs = budget.limits.idleTimeoutMs;
|
|
@@ -20359,9 +21513,17 @@ async function executeSubagentWithTimeout({
|
|
|
20359
21513
|
const scheduleNext = () => {
|
|
20360
21514
|
const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
20361
21515
|
const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
|
|
20362
|
-
const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
20363
|
-
const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
|
|
20364
|
-
|
|
21516
|
+
const idleRemaining = idleLimitMs === void 0 || gracefulFinish !== void 0 && initialTimeoutMs !== void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
21517
|
+
const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit || gracefulFinish !== void 0 ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
|
|
21518
|
+
const next = Math.min(wallRemaining, idleRemaining, preemptRemaining);
|
|
21519
|
+
if (!Number.isFinite(next)) {
|
|
21520
|
+
if (timer) {
|
|
21521
|
+
clearTimeout(timer);
|
|
21522
|
+
timer = null;
|
|
21523
|
+
}
|
|
21524
|
+
return;
|
|
21525
|
+
}
|
|
21526
|
+
armFor(Math.max(25, next));
|
|
20365
21527
|
};
|
|
20366
21528
|
const negotiateTimeout = async (used, limit) => {
|
|
20367
21529
|
const handler = budget.onThreshold;
|
|
@@ -20410,6 +21572,10 @@ async function executeSubagentWithTimeout({
|
|
|
20410
21572
|
const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
|
|
20411
21573
|
const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
|
|
20412
21574
|
if (idleExceeded && !wallExceeded) {
|
|
21575
|
+
if (gracefulFinish !== void 0 && initialTimeoutMs !== void 0) {
|
|
21576
|
+
scheduleNext();
|
|
21577
|
+
return;
|
|
21578
|
+
}
|
|
20413
21579
|
const sessionId = currentSessionId();
|
|
20414
21580
|
budget._events?.emit("budget.threshold_reached", {
|
|
20415
21581
|
...sessionId ? { sessionId } : {},
|
|
@@ -20426,7 +21592,7 @@ async function executeSubagentWithTimeout({
|
|
|
20426
21592
|
reject(new BudgetExceededError("idle_timeout", idleLimit ?? 0, budget.idleMs()));
|
|
20427
21593
|
return;
|
|
20428
21594
|
}
|
|
20429
|
-
if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed >= wallLimit * preemptFraction) {
|
|
21595
|
+
if (wallLimit !== void 0 && !wallExceeded && gracefulFinish === void 0 && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed >= wallLimit * preemptFraction) {
|
|
20430
21596
|
const activityTs = Date.now() - budget.idleMs();
|
|
20431
21597
|
if (activityTs <= lastGrantActivityTs) {
|
|
20432
21598
|
preemptState = "locked" /* LOCKED */;
|
|
@@ -20460,6 +21626,22 @@ async function executeSubagentWithTimeout({
|
|
|
20460
21626
|
return;
|
|
20461
21627
|
}
|
|
20462
21628
|
const limit = wallLimit ?? 0;
|
|
21629
|
+
if (gracefulFinish !== void 0) {
|
|
21630
|
+
if (!budget.graceGranted) {
|
|
21631
|
+
const reason = `wall-clock budget of ${Math.round(limit / 1e3)}s reached`;
|
|
21632
|
+
if (budget.notifyFinish(reason, { graceMs: gracefulFinish.graceMs })) {
|
|
21633
|
+
scheduleNext();
|
|
21634
|
+
return;
|
|
21635
|
+
}
|
|
21636
|
+
abortSubagent(ctx.subagentId);
|
|
21637
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
21638
|
+
return;
|
|
21639
|
+
} else {
|
|
21640
|
+
abortSubagent(ctx.subagentId);
|
|
21641
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
21642
|
+
return;
|
|
21643
|
+
}
|
|
21644
|
+
}
|
|
20463
21645
|
if (!budget.onThreshold) {
|
|
20464
21646
|
abortSubagent(ctx.subagentId);
|
|
20465
21647
|
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
@@ -20607,7 +21789,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
20607
21789
|
return { ...subagent, name: display };
|
|
20608
21790
|
}
|
|
20609
21791
|
async spawn(subagent) {
|
|
20610
|
-
const id = subagent.id ||
|
|
21792
|
+
const id = subagent.id || randomUUID15();
|
|
20611
21793
|
const cfg = this.withNickname(subagent, id);
|
|
20612
21794
|
if (this.subagents.has(id)) {
|
|
20613
21795
|
throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
|
|
@@ -20844,6 +22026,32 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
20844
22026
|
completeTask(result) {
|
|
20845
22027
|
this.recordCompletion(result);
|
|
20846
22028
|
}
|
|
22029
|
+
/**
|
|
22030
|
+
* Ask every RUNNING subagent that opted into `gracefulFinish` to finish its
|
|
22031
|
+
* task in its own turn (see coordination/subagent-finish.ts). This is the
|
|
22032
|
+
* leader-side entry point for "the leader agent has finished": it delivers
|
|
22033
|
+
* an in-band notification between tool batches — never an interrupt, never
|
|
22034
|
+
* an abort. Each notified subagent keeps its existing time budget and
|
|
22035
|
+
* accelerates; the watchdog still bounds the maximum lifetime.
|
|
22036
|
+
*
|
|
22037
|
+
* Subagents without the policy opted in are deliberately untouched — their
|
|
22038
|
+
* lifecycle remains the legacy watchdog contract.
|
|
22039
|
+
*
|
|
22040
|
+
* Returns the number of subagents actually notified.
|
|
22041
|
+
*/
|
|
22042
|
+
requestFinish(reason) {
|
|
22043
|
+
let notified = 0;
|
|
22044
|
+
for (const subagent of this.subagents.values()) {
|
|
22045
|
+
if (subagent.status !== "running") continue;
|
|
22046
|
+
if (!resolveGracefulFinish(subagent.config)) continue;
|
|
22047
|
+
const budget = subagent.activeBudget;
|
|
22048
|
+
if (!budget) continue;
|
|
22049
|
+
const usage = budget.usage();
|
|
22050
|
+
if (usage.iterations === 0 && usage.toolCalls === 0) continue;
|
|
22051
|
+
if (budget.notifyFinish(reason)) notified++;
|
|
22052
|
+
}
|
|
22053
|
+
return notified;
|
|
22054
|
+
}
|
|
20847
22055
|
// --- internal dispatching ---------------------------------------------
|
|
20848
22056
|
tryDispatchNext() {
|
|
20849
22057
|
while (this.canDispatch()) {
|
|
@@ -21017,7 +22225,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
21017
22225
|
idleTimeoutMs: rawIdleTimeoutMs ?? this.config.defaultBudget?.idleTimeoutMs ?? configWithRosterDefaults.idleTimeoutMs
|
|
21018
22226
|
},
|
|
21019
22227
|
"auto",
|
|
21020
|
-
{
|
|
22228
|
+
{
|
|
22229
|
+
sessionId: () => this.currentSessionId(),
|
|
22230
|
+
subagentId,
|
|
22231
|
+
// Graceful-finish runs own wall-clock enforcement to the watchdog so
|
|
22232
|
+
// the notify-then-bound lifecycle cannot be raced by tool.progress
|
|
22233
|
+
// heartbeats calling checkTimeout() (see subagent-budget.ts).
|
|
22234
|
+
...resolveGracefulFinish(subagent.config) ? { wallClockWatchdogOwned: true } : {}
|
|
22235
|
+
}
|
|
21021
22236
|
);
|
|
21022
22237
|
subagent.activeBudget = budget;
|
|
21023
22238
|
if (!this.runner) {
|
|
@@ -21050,7 +22265,8 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
21050
22265
|
task,
|
|
21051
22266
|
runCtx,
|
|
21052
22267
|
budget,
|
|
21053
|
-
subagent.config.preemptFraction
|
|
22268
|
+
subagent.config.preemptFraction,
|
|
22269
|
+
resolveGracefulFinish(subagent.config)
|
|
21054
22270
|
);
|
|
21055
22271
|
result = {
|
|
21056
22272
|
subagentId,
|
|
@@ -21080,13 +22296,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
21080
22296
|
}
|
|
21081
22297
|
this.recordCompletion(result);
|
|
21082
22298
|
}
|
|
21083
|
-
async executeWithTimeout(runner, task, ctx, budget, preemptFraction) {
|
|
22299
|
+
async executeWithTimeout(runner, task, ctx, budget, preemptFraction, gracefulFinish) {
|
|
21084
22300
|
return executeSubagentWithTimeout({
|
|
21085
22301
|
runner,
|
|
21086
22302
|
task,
|
|
21087
22303
|
ctx,
|
|
21088
22304
|
budget,
|
|
21089
22305
|
preemptFraction,
|
|
22306
|
+
gracefulFinish,
|
|
21090
22307
|
abortSubagent: (subagentId) => this.subagents.get(subagentId)?.abortController.abort(),
|
|
21091
22308
|
currentSessionId: () => this.currentSessionId()
|
|
21092
22309
|
});
|
|
@@ -21492,7 +22709,7 @@ var Director = class _Director {
|
|
|
21492
22709
|
sessionProvider;
|
|
21493
22710
|
sessionModel;
|
|
21494
22711
|
constructor(opts) {
|
|
21495
|
-
this.id = opts.config.coordinatorId ||
|
|
22712
|
+
this.id = opts.config.coordinatorId || randomUUID16();
|
|
21496
22713
|
this.manifestPath = opts.manifestPath;
|
|
21497
22714
|
this.roster = opts.roster;
|
|
21498
22715
|
this.directorPreamble = opts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
|
|
@@ -21699,6 +22916,17 @@ var Director = class _Director {
|
|
|
21699
22916
|
isWorkComplete() {
|
|
21700
22917
|
return this.workCompleteFlag;
|
|
21701
22918
|
}
|
|
22919
|
+
/**
|
|
22920
|
+
* Ask every running background subagent that opted into `gracefulFinish`
|
|
22921
|
+
* to finish its task in its own turn. In-band notification between tool
|
|
22922
|
+
* batches — no interrupt, no abort; each subagent keeps its time budget and
|
|
22923
|
+
* accelerates. Session shutdown calls this before draining Chimera work so
|
|
22924
|
+
* the post-session reviewer is nudged to complete rather than killed.
|
|
22925
|
+
* Returns the number of subagents notified.
|
|
22926
|
+
*/
|
|
22927
|
+
requestFinish(reason) {
|
|
22928
|
+
return this.coordinator.requestFinish(reason);
|
|
22929
|
+
}
|
|
21702
22930
|
setLeaderBtwNote(note) {
|
|
21703
22931
|
return this.btwNotes.add(note);
|
|
21704
22932
|
}
|
|
@@ -21775,7 +23003,7 @@ var Director = class _Director {
|
|
|
21775
23003
|
);
|
|
21776
23004
|
}
|
|
21777
23005
|
const msg = {
|
|
21778
|
-
id:
|
|
23006
|
+
id: randomUUID16(),
|
|
21779
23007
|
type: "task",
|
|
21780
23008
|
from: this.id,
|
|
21781
23009
|
to: subagentId,
|
|
@@ -22029,7 +23257,7 @@ var Director = class _Director {
|
|
|
22029
23257
|
};
|
|
22030
23258
|
|
|
22031
23259
|
// src/coordination/fleet-manager.ts
|
|
22032
|
-
import { randomUUID as
|
|
23260
|
+
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
22033
23261
|
import * as fsp26 from "node:fs/promises";
|
|
22034
23262
|
import * as path33 from "node:path";
|
|
22035
23263
|
var FleetManager = class {
|
|
@@ -22095,7 +23323,7 @@ var FleetManager = class {
|
|
|
22095
23323
|
maxContext;
|
|
22096
23324
|
constructor(opts = {}) {
|
|
22097
23325
|
this.manifestPath = opts.manifestPath;
|
|
22098
|
-
this.directorRunId = opts.directorRunId ??
|
|
23326
|
+
this.directorRunId = opts.directorRunId ?? randomUUID17();
|
|
22099
23327
|
this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
|
|
22100
23328
|
this.maxSpawnDepth = resolveMaxSpawnDepth(opts.maxSpawnDepth);
|
|
22101
23329
|
this.spawnDepth = opts.spawnDepth ?? 0;
|
|
@@ -23004,235 +24232,6 @@ var MailboxProjectServerConnection = class {
|
|
|
23004
24232
|
}
|
|
23005
24233
|
};
|
|
23006
24234
|
|
|
23007
|
-
// src/coordination/mailbox-type-properties.ts
|
|
23008
|
-
var MAILBOX_TYPE_PROPERTIES = {
|
|
23009
|
-
note: {
|
|
23010
|
-
category: "informational",
|
|
23011
|
-
expectsReply: false,
|
|
23012
|
-
requiresAction: false,
|
|
23013
|
-
backgroundEligible: false,
|
|
23014
|
-
outOfBand: false,
|
|
23015
|
-
renderPriority: 20,
|
|
23016
|
-
recipientObligation: "Read for context; no reply needed.",
|
|
23017
|
-
senderGuidance: "General-purpose FYI. Use when no more specific type applies."
|
|
23018
|
-
},
|
|
23019
|
-
ask: {
|
|
23020
|
-
category: "actionable",
|
|
23021
|
-
expectsReply: true,
|
|
23022
|
-
requiresAction: true,
|
|
23023
|
-
backgroundEligible: true,
|
|
23024
|
-
outOfBand: false,
|
|
23025
|
-
renderPriority: 10,
|
|
23026
|
-
recipientObligation: "Answer as soon as possible \u2014 the sender is waiting.",
|
|
23027
|
-
senderGuidance: "Blocking question. Only use when you need an answer to proceed."
|
|
23028
|
-
},
|
|
23029
|
-
assign: {
|
|
23030
|
-
category: "actionable",
|
|
23031
|
-
expectsReply: false,
|
|
23032
|
-
requiresAction: true,
|
|
23033
|
-
backgroundEligible: true,
|
|
23034
|
-
outOfBand: false,
|
|
23035
|
-
renderPriority: 10,
|
|
23036
|
-
recipientObligation: "Accept or decline; act on it when current operation allows.",
|
|
23037
|
-
senderGuidance: 'Task delegation. Must be directed to a specific recipient (not "*").'
|
|
23038
|
-
},
|
|
23039
|
-
steer: {
|
|
23040
|
-
category: "actionable",
|
|
23041
|
-
expectsReply: false,
|
|
23042
|
-
requiresAction: true,
|
|
23043
|
-
backgroundEligible: true,
|
|
23044
|
-
outOfBand: false,
|
|
23045
|
-
renderPriority: 0,
|
|
23046
|
-
// Always rendered first
|
|
23047
|
-
recipientObligation: "Pause current approach, adjust per instruction, then resume.",
|
|
23048
|
-
senderGuidance: "Mid-task direction change. The recipient is already working on something."
|
|
23049
|
-
},
|
|
23050
|
-
btw: {
|
|
23051
|
-
category: "informational",
|
|
23052
|
-
expectsReply: false,
|
|
23053
|
-
requiresAction: false,
|
|
23054
|
-
backgroundEligible: false,
|
|
23055
|
-
outOfBand: false,
|
|
23056
|
-
renderPriority: 30,
|
|
23057
|
-
recipientObligation: "Absorb the information and stay on current task; no reply needed.",
|
|
23058
|
-
senderGuidance: "Low-priority aside. Non-urgent info that can wait."
|
|
23059
|
-
},
|
|
23060
|
-
broadcast: {
|
|
23061
|
-
category: "routing",
|
|
23062
|
-
expectsReply: false,
|
|
23063
|
-
requiresAction: false,
|
|
23064
|
-
backgroundEligible: false,
|
|
23065
|
-
outOfBand: false,
|
|
23066
|
-
renderPriority: 20,
|
|
23067
|
-
recipientObligation: 'Read if addressed to you (direct recipient, alias, or "*").',
|
|
23068
|
-
senderGuidance: 'Multi-recipient envelope. Auto-selected when to is "*" or "@session".'
|
|
23069
|
-
},
|
|
23070
|
-
status: {
|
|
23071
|
-
category: "informational",
|
|
23072
|
-
expectsReply: false,
|
|
23073
|
-
requiresAction: false,
|
|
23074
|
-
backgroundEligible: false,
|
|
23075
|
-
outOfBand: false,
|
|
23076
|
-
renderPriority: 40,
|
|
23077
|
-
recipientObligation: "Use to avoid redundant work; never act on as a task or question.",
|
|
23078
|
-
senderGuidance: "Agent/system status update. Machine-generated, not for human-originated messages."
|
|
23079
|
-
},
|
|
23080
|
-
result: {
|
|
23081
|
-
category: "informational",
|
|
23082
|
-
expectsReply: false,
|
|
23083
|
-
requiresAction: false,
|
|
23084
|
-
backgroundEligible: true,
|
|
23085
|
-
outOfBand: false,
|
|
23086
|
-
renderPriority: 10,
|
|
23087
|
-
recipientObligation: "Factor into next decision; treat as evidence, not a new task.",
|
|
23088
|
-
senderGuidance: "Task completion notice. Share the outcome of finished work."
|
|
23089
|
-
},
|
|
23090
|
-
review: {
|
|
23091
|
-
category: "actionable",
|
|
23092
|
-
expectsReply: false,
|
|
23093
|
-
requiresAction: true,
|
|
23094
|
-
backgroundEligible: true,
|
|
23095
|
-
outOfBand: false,
|
|
23096
|
-
renderPriority: 10,
|
|
23097
|
-
recipientObligation: "Inspect when convenient; no immediate reply required.",
|
|
23098
|
-
senderGuidance: "Passive review request (code/doc/PR). No reply required."
|
|
23099
|
-
},
|
|
23100
|
-
control: {
|
|
23101
|
-
category: "control_signal",
|
|
23102
|
-
expectsReply: false,
|
|
23103
|
-
requiresAction: false,
|
|
23104
|
-
backgroundEligible: false,
|
|
23105
|
-
outOfBand: true,
|
|
23106
|
-
renderPriority: 999,
|
|
23107
|
-
// Never rendered
|
|
23108
|
-
recipientObligation: 'Handled by the agent loop, NOT folded into conversation. "interrupt" causes cooperative halt.',
|
|
23109
|
-
senderGuidance: "RESERVED for runtime use. Agents must NOT send control messages."
|
|
23110
|
-
}
|
|
23111
|
-
};
|
|
23112
|
-
|
|
23113
|
-
// src/coordination/mailbox-auth-types.ts
|
|
23114
|
-
var MAILBOX_CAPABILITY_IMPLICATIONS = {
|
|
23115
|
-
"mail.read.all": ["mail.read.self"],
|
|
23116
|
-
"mail.events.all": ["mail.events.self"],
|
|
23117
|
-
"mail.send.directive": ["mail.send.actionable", "mail.send.informational"],
|
|
23118
|
-
"mail.send.actionable": ["mail.send.informational"],
|
|
23119
|
-
// Leaf capabilities imply nothing further.
|
|
23120
|
-
"mail.send.informational": [],
|
|
23121
|
-
"mail.read.self": [],
|
|
23122
|
-
"mail.ack.self": [],
|
|
23123
|
-
"mail.events.self": [],
|
|
23124
|
-
"mail.presence.register.self": [],
|
|
23125
|
-
"mail.presence.heartbeat.self": [],
|
|
23126
|
-
"mail.presence.deregister.self": [],
|
|
23127
|
-
"mail.presence.read": [],
|
|
23128
|
-
"mail.retention.purge": [],
|
|
23129
|
-
"mail.retention.clear": [],
|
|
23130
|
-
"mail.admin.receipts": []
|
|
23131
|
-
};
|
|
23132
|
-
function expandMailboxCapabilities(caps) {
|
|
23133
|
-
const result = /* @__PURE__ */ new Set();
|
|
23134
|
-
const queue = [...caps];
|
|
23135
|
-
while (queue.length > 0) {
|
|
23136
|
-
const cap = queue.pop();
|
|
23137
|
-
if (result.has(cap)) continue;
|
|
23138
|
-
result.add(cap);
|
|
23139
|
-
const implied = MAILBOX_CAPABILITY_IMPLICATIONS[cap];
|
|
23140
|
-
if (implied) queue.push(...implied);
|
|
23141
|
-
}
|
|
23142
|
-
return result;
|
|
23143
|
-
}
|
|
23144
|
-
function hasMailboxCapability(actor, cap) {
|
|
23145
|
-
if (actor.capabilities.has(cap)) return true;
|
|
23146
|
-
const expanded = expandMailboxCapabilities(actor.capabilities);
|
|
23147
|
-
return expanded.has(cap);
|
|
23148
|
-
}
|
|
23149
|
-
|
|
23150
|
-
// src/coordination/mailbox-predicates.ts
|
|
23151
|
-
function mailboxIdentityBase(agentId) {
|
|
23152
|
-
return agentId.split(/[@#]/, 1)[0].trim().toLowerCase();
|
|
23153
|
-
}
|
|
23154
|
-
function isMailboxLeader(agentId, role) {
|
|
23155
|
-
return mailboxIdentityBase(agentId) === "leader" || role?.trim().toLowerCase() === "leader";
|
|
23156
|
-
}
|
|
23157
|
-
function isMailboxSenderInFamily(senderId, family) {
|
|
23158
|
-
const base = mailboxIdentityBase(senderId);
|
|
23159
|
-
const normalizedFamily = family.trim().toLowerCase();
|
|
23160
|
-
if (normalizedFamily.length === 0) return false;
|
|
23161
|
-
return base === normalizedFamily || base.startsWith(`${normalizedFamily}-`);
|
|
23162
|
-
}
|
|
23163
|
-
function isMailboxMessageVisibleTo(message, agentId, role) {
|
|
23164
|
-
return message.audience !== "leaders" || isMailboxLeader(agentId, role);
|
|
23165
|
-
}
|
|
23166
|
-
function validateSendType(type, to) {
|
|
23167
|
-
if (type === "control") {
|
|
23168
|
-
throw new TypeError('Type "control" is reserved for runtime use and cannot be set by agents');
|
|
23169
|
-
}
|
|
23170
|
-
const isMultiRecipient = to === "*" || to.startsWith("@session:");
|
|
23171
|
-
if (type === "assign" && isMultiRecipient) {
|
|
23172
|
-
throw new TypeError(
|
|
23173
|
-
`Type "assign" requires a specific recipient \u2014 multi-recipient target "${to}" is ambiguous`
|
|
23174
|
-
);
|
|
23175
|
-
}
|
|
23176
|
-
if (type === "steer" && isMultiRecipient) {
|
|
23177
|
-
throw new TypeError(
|
|
23178
|
-
`Type "steer" requires a specific recipient \u2014 multi-recipient target "${to}" is ambiguous`
|
|
23179
|
-
);
|
|
23180
|
-
}
|
|
23181
|
-
}
|
|
23182
|
-
var SESSION_RECIPIENT_PREFIX = "@session:";
|
|
23183
|
-
function sessionRecipient(sessionId) {
|
|
23184
|
-
const normalizedSessionId = sessionId.trim();
|
|
23185
|
-
if (!normalizedSessionId) {
|
|
23186
|
-
throw new TypeError('sessionId is required for the "@session" recipient');
|
|
23187
|
-
}
|
|
23188
|
-
return `${SESSION_RECIPIENT_PREFIX}${normalizedSessionId}`;
|
|
23189
|
-
}
|
|
23190
|
-
function normalizeRecipient(to, sessionId) {
|
|
23191
|
-
const trimmed = to.trim();
|
|
23192
|
-
const normalized = trimmed.toLowerCase();
|
|
23193
|
-
if (normalized === "all") return "*";
|
|
23194
|
-
if (normalized === "@session") return sessionRecipient(sessionId ?? "");
|
|
23195
|
-
return trimmed;
|
|
23196
|
-
}
|
|
23197
|
-
function isActionRequiredForActor(message, projection) {
|
|
23198
|
-
if (projection.legacyGlobalCompletion) return false;
|
|
23199
|
-
if (message.deletedAt !== void 0) return false;
|
|
23200
|
-
if (projection.completedByMe) return false;
|
|
23201
|
-
return MAILBOX_TYPE_PROPERTIES[message.type]?.requiresAction === true;
|
|
23202
|
-
}
|
|
23203
|
-
|
|
23204
|
-
// src/coordination/mailbox-session-sync.ts
|
|
23205
|
-
function isAffectedBySessionAffinity(message) {
|
|
23206
|
-
return message.sessionAffinity !== void 0;
|
|
23207
|
-
}
|
|
23208
|
-
async function acceptMailboxMessageForSession(message, currentSessionId, ctx) {
|
|
23209
|
-
if (!isAffectedBySessionAffinity(message)) return true;
|
|
23210
|
-
const affinity = message.sessionAffinity;
|
|
23211
|
-
if (affinity === null || typeof affinity !== "object" || Array.isArray(affinity)) {
|
|
23212
|
-
return false;
|
|
23213
|
-
}
|
|
23214
|
-
if (affinity.sessionId !== void 0 && typeof affinity.sessionId !== "string" || affinity.reportId !== void 0 && typeof affinity.reportId !== "string") {
|
|
23215
|
-
return false;
|
|
23216
|
-
}
|
|
23217
|
-
if (!currentSessionId) {
|
|
23218
|
-
return ctx?.allowUnscoped === true;
|
|
23219
|
-
}
|
|
23220
|
-
if (typeof affinity.sessionId === "string" && affinity.sessionId.length > 0) {
|
|
23221
|
-
if (affinity.sessionId !== currentSessionId) return false;
|
|
23222
|
-
return true;
|
|
23223
|
-
}
|
|
23224
|
-
if (affinity.reportId && ctx?.resolveChimeraReportSessionId) {
|
|
23225
|
-
try {
|
|
23226
|
-
const resolved = await ctx.resolveChimeraReportSessionId(affinity.reportId);
|
|
23227
|
-
if (resolved === currentSessionId) return true;
|
|
23228
|
-
if (resolved !== void 0) return false;
|
|
23229
|
-
} catch {
|
|
23230
|
-
}
|
|
23231
|
-
}
|
|
23232
|
-
if (ctx?.allowUnscoped === true) return true;
|
|
23233
|
-
return false;
|
|
23234
|
-
}
|
|
23235
|
-
|
|
23236
24235
|
// src/coordination/mailbox-message-codec.ts
|
|
23237
24236
|
function resolveSendType(type, to) {
|
|
23238
24237
|
const normalizedTo = normalizeRecipient(to);
|
|
@@ -24129,7 +25128,7 @@ function makeFleetStatusTool(opts = {}) {
|
|
|
24129
25128
|
}
|
|
24130
25129
|
|
|
24131
25130
|
// src/coordination/fleet-supervisor.ts
|
|
24132
|
-
import { randomUUID as
|
|
25131
|
+
import { randomUUID as randomUUID18 } from "node:crypto";
|
|
24133
25132
|
var COLLAB_ID_PREFIXES2 = ["bug-hunter-", "refactor-planner-", "critic-"];
|
|
24134
25133
|
var DEFAULTS = {
|
|
24135
25134
|
intervalMs: 2e4,
|
|
@@ -24407,7 +25406,7 @@ var FleetSupervisor = class {
|
|
|
24407
25406
|
*/
|
|
24408
25407
|
async decide(question, context, options, risk) {
|
|
24409
25408
|
const request = {
|
|
24410
|
-
id: `fleetsup-${
|
|
25409
|
+
id: `fleetsup-${randomUUID18()}`,
|
|
24411
25410
|
sessionId: this.opts.sessionId?.(),
|
|
24412
25411
|
source: "system",
|
|
24413
25412
|
question,
|
|
@@ -24767,6 +25766,22 @@ var SEND_ALLOWED_FIELDS = /* @__PURE__ */ new Set([
|
|
|
24767
25766
|
// receiver trusts the sender-asserted `sessionId`; the boundary must
|
|
24768
25767
|
// refuse the field entirely.
|
|
24769
25768
|
]);
|
|
25769
|
+
var SEND_FORBIDDEN_FIELDS = /* @__PURE__ */ new Set([
|
|
25770
|
+
"from",
|
|
25771
|
+
"sessionAffinity"
|
|
25772
|
+
]);
|
|
25773
|
+
function filterMailboxSendPayload(input) {
|
|
25774
|
+
const payload = {};
|
|
25775
|
+
const stripped = [];
|
|
25776
|
+
for (const key of Object.keys(input)) {
|
|
25777
|
+
if (SEND_ALLOWED_FIELDS.has(key) || SEND_FORBIDDEN_FIELDS.has(key)) {
|
|
25778
|
+
payload[key] = input[key];
|
|
25779
|
+
} else {
|
|
25780
|
+
stripped.push(key);
|
|
25781
|
+
}
|
|
25782
|
+
}
|
|
25783
|
+
return { payload, stripped };
|
|
25784
|
+
}
|
|
24770
25785
|
var ACK_ALLOWED_FIELDS = /* @__PURE__ */ new Set([
|
|
24771
25786
|
"messageId",
|
|
24772
25787
|
"read",
|
|
@@ -25004,7 +26019,9 @@ function makeMailSendTool(opts = {}) {
|
|
|
25004
26019
|
required: ["to", "subject", "body"]
|
|
25005
26020
|
},
|
|
25006
26021
|
async execute(input, ctx) {
|
|
25007
|
-
const i
|
|
26022
|
+
const { payload: i, stripped } = filterMailboxSendPayload(
|
|
26023
|
+
input ?? {}
|
|
26024
|
+
);
|
|
25008
26025
|
const rawTo = i.to;
|
|
25009
26026
|
const subject = i.subject;
|
|
25010
26027
|
const body = i.body;
|
|
@@ -25027,15 +26044,13 @@ function makeMailSendTool(opts = {}) {
|
|
|
25027
26044
|
recipientAliases: /* @__PURE__ */ new Set([codecIdentity.baseId]),
|
|
25028
26045
|
sessionId: codecIdentity.sessionId
|
|
25029
26046
|
};
|
|
26047
|
+
let parsed;
|
|
25030
26048
|
try {
|
|
25031
|
-
parseMailboxSendInput(i, codecActor);
|
|
26049
|
+
parsed = parseMailboxSendInput(i, codecActor);
|
|
25032
26050
|
} catch (err) {
|
|
25033
26051
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
25034
26052
|
}
|
|
25035
|
-
const audience =
|
|
25036
|
-
if (audience !== void 0 && audience !== "all" && audience !== "leaders") {
|
|
25037
|
-
return { ok: false, error: '"audience" must be "all" or "leaders".' };
|
|
25038
|
-
}
|
|
26053
|
+
const audience = parsed.audience;
|
|
25039
26054
|
const mb = resolveMailbox(ctx);
|
|
25040
26055
|
const identity = await register(mb, ctx);
|
|
25041
26056
|
const requestedTo = normalizeRecipient(rawTo, identity.sessionId);
|
|
@@ -25049,10 +26064,10 @@ function makeMailSendTool(opts = {}) {
|
|
|
25049
26064
|
to: delivery.to,
|
|
25050
26065
|
type: resolvedType,
|
|
25051
26066
|
audience: delivery.audience,
|
|
25052
|
-
subject,
|
|
25053
|
-
body,
|
|
25054
|
-
priority:
|
|
25055
|
-
replyTo:
|
|
26067
|
+
subject: parsed.subject,
|
|
26068
|
+
body: parsed.body,
|
|
26069
|
+
priority: parsed.priority,
|
|
26070
|
+
replyTo: parsed.replyTo,
|
|
25056
26071
|
senderSessionId: identity.sessionId
|
|
25057
26072
|
});
|
|
25058
26073
|
return {
|
|
@@ -25060,7 +26075,9 @@ function makeMailSendTool(opts = {}) {
|
|
|
25060
26075
|
messageId: msg.id,
|
|
25061
26076
|
from: identity.callerId,
|
|
25062
26077
|
to: msg.to,
|
|
25063
|
-
|
|
26078
|
+
// Surfacing what was stripped keeps the send auditable without
|
|
26079
|
+
// re-introducing the clutter into the payload itself.
|
|
26080
|
+
...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}.` }
|
|
25064
26081
|
};
|
|
25065
26082
|
}
|
|
25066
26083
|
};
|
|
@@ -28582,7 +29599,7 @@ function createAgentMonitorService(opts) {
|
|
|
28582
29599
|
}
|
|
28583
29600
|
|
|
28584
29601
|
// src/coordination/autonomous-brain.ts
|
|
28585
|
-
import { randomUUID as
|
|
29602
|
+
import { randomUUID as randomUUID19 } from "node:crypto";
|
|
28586
29603
|
var AutonomousBrain = class {
|
|
28587
29604
|
graph;
|
|
28588
29605
|
// Fleet bus for emitting decisions — null-safe, no-op if not provided
|
|
@@ -28688,7 +29705,7 @@ var AutonomousBrain = class {
|
|
|
28688
29705
|
consequence: i === 0 ? `Spawn the most appropriate agent for: ${taskDescription.slice(0, 80)}` : `Spawn an alternative agent for the same task`
|
|
28689
29706
|
}));
|
|
28690
29707
|
return this.decideAuto({
|
|
28691
|
-
id:
|
|
29708
|
+
id: randomUUID19(),
|
|
28692
29709
|
source,
|
|
28693
29710
|
decisionType: "spawn",
|
|
28694
29711
|
question: `Should we spawn a subagent for this task?`,
|
|
@@ -28731,7 +29748,7 @@ var AutonomousBrain = class {
|
|
|
28731
29748
|
}
|
|
28732
29749
|
];
|
|
28733
29750
|
return this.decideAuto({
|
|
28734
|
-
id:
|
|
29751
|
+
id: randomUUID19(),
|
|
28735
29752
|
source,
|
|
28736
29753
|
decisionType: "approve_change",
|
|
28737
29754
|
question: `Should we approve the change "${change.title}"?`,
|
|
@@ -28790,7 +29807,7 @@ var AutonomousBrain = class {
|
|
|
28790
29807
|
consequence: "Break the task into smaller sub-tasks"
|
|
28791
29808
|
});
|
|
28792
29809
|
return this.decideAuto({
|
|
28793
|
-
id:
|
|
29810
|
+
id: randomUUID19(),
|
|
28794
29811
|
source,
|
|
28795
29812
|
decisionType: "escalate_task",
|
|
28796
29813
|
question: `Task failed: ${error.slice(0, 100)}. How should we proceed?`,
|
|
@@ -28924,10 +29941,10 @@ ${ctx.error}`);
|
|
|
28924
29941
|
};
|
|
28925
29942
|
|
|
28926
29943
|
// src/coordination/autonomous-coordinator.ts
|
|
28927
|
-
import { randomUUID as
|
|
29944
|
+
import { randomUUID as randomUUID22 } from "node:crypto";
|
|
28928
29945
|
|
|
28929
29946
|
// src/coordination/knowledge-graph.ts
|
|
28930
|
-
import { randomUUID as
|
|
29947
|
+
import { randomUUID as randomUUID20 } from "node:crypto";
|
|
28931
29948
|
import * as fsp28 from "node:fs/promises";
|
|
28932
29949
|
import * as path41 from "node:path";
|
|
28933
29950
|
var DEFAULT_MAX_NODES = 2e3;
|
|
@@ -28975,7 +29992,7 @@ var KnowledgeGraph = class _KnowledgeGraph {
|
|
|
28975
29992
|
* Returns the node with its assigned id.
|
|
28976
29993
|
*/
|
|
28977
29994
|
async add(node) {
|
|
28978
|
-
const full = { id:
|
|
29995
|
+
const full = { id: randomUUID20(), ...node };
|
|
28979
29996
|
this.nodes.set(full.id, full);
|
|
28980
29997
|
this._trackSeq(full.id);
|
|
28981
29998
|
this._addToIndex(full, this._indexKeys(full));
|
|
@@ -29104,8 +30121,8 @@ var KnowledgeGraph = class _KnowledgeGraph {
|
|
|
29104
30121
|
if (this.subs.size >= MAX_SUBSCRIPTIONS) {
|
|
29105
30122
|
throw new Error(`Knowledge graph subscription limit reached (${MAX_SUBSCRIPTIONS})`);
|
|
29106
30123
|
}
|
|
29107
|
-
const channel =
|
|
29108
|
-
const sub = { id:
|
|
30124
|
+
const channel = randomUUID20();
|
|
30125
|
+
const sub = { id: randomUUID20(), agentId, filter, channel };
|
|
29109
30126
|
this.subs.set(channel, sub);
|
|
29110
30127
|
this.pendingDeliveries.set(channel, []);
|
|
29111
30128
|
return channel;
|
|
@@ -29586,7 +30603,7 @@ var TaskDAG = class {
|
|
|
29586
30603
|
};
|
|
29587
30604
|
|
|
29588
30605
|
// src/coordination/task-auctioneer.ts
|
|
29589
|
-
import { randomUUID as
|
|
30606
|
+
import { randomUUID as randomUUID21 } from "node:crypto";
|
|
29590
30607
|
function isTerminalGoalStatus(status) {
|
|
29591
30608
|
return status === "done" || status === "failed";
|
|
29592
30609
|
}
|
|
@@ -29709,7 +30726,7 @@ var TaskAuctioneer = class {
|
|
|
29709
30726
|
const score = dispatchResult.confidence * (dispatchResult.role === agent.agentRole ? 1.2 : 1);
|
|
29710
30727
|
if (score < this.minConfidence) return false;
|
|
29711
30728
|
const bid = {
|
|
29712
|
-
id:
|
|
30729
|
+
id: randomUUID21(),
|
|
29713
30730
|
taskId,
|
|
29714
30731
|
agentId: agent.agentId,
|
|
29715
30732
|
agentName: agent.agentName,
|
|
@@ -30646,7 +31663,7 @@ var AutonomousCoordinator = class _AutonomousCoordinator {
|
|
|
30646
31663
|
break;
|
|
30647
31664
|
}
|
|
30648
31665
|
const decision = await this.brain.decideAuto({
|
|
30649
|
-
id:
|
|
31666
|
+
id: randomUUID22(),
|
|
30650
31667
|
source: "system",
|
|
30651
31668
|
decisionType: "prioritize_goals",
|
|
30652
31669
|
question: `What should we work on next? Open goals: ${dispatchable.map((g) => g.title).join(", ")}`,
|
|
@@ -31361,7 +32378,13 @@ export {
|
|
|
31361
32378
|
DEFAULT_DIRECTOR_PREAMBLE,
|
|
31362
32379
|
DEFAULT_DISPATCH_ROLE,
|
|
31363
32380
|
DEFAULT_EAGER_SKILL_LIMIT,
|
|
32381
|
+
DEFAULT_EXPLORE_COMPANION_AGENT_ID,
|
|
32382
|
+
DEFAULT_EXPLORE_EDIT_TOOLS,
|
|
32383
|
+
DEFAULT_EXPLORE_SEARCH_TOOLS,
|
|
32384
|
+
DEFAULT_MAILBOX_POLL_INTERVAL_MS,
|
|
31364
32385
|
DEFAULT_MAX_FLEET_SPAWNS,
|
|
32386
|
+
DEFAULT_MAX_PENDING_PROBES,
|
|
32387
|
+
DEFAULT_PROBE_COOLDOWN_MS,
|
|
31365
32388
|
DEFAULT_QUALITY_CHECKS,
|
|
31366
32389
|
DEFAULT_SUBAGENT_BASELINE,
|
|
31367
32390
|
DELIVERY_AGENTS,
|
|
@@ -31375,6 +32398,7 @@ export {
|
|
|
31375
32398
|
Director,
|
|
31376
32399
|
DirectorAlertLevel,
|
|
31377
32400
|
EscalationRoutingBrainArbiter,
|
|
32401
|
+
ExploreCompanion,
|
|
31378
32402
|
FLEET_ROSTER,
|
|
31379
32403
|
FLEET_ROSTER_BUDGETS,
|
|
31380
32404
|
FLEET_ROSTER_WITHACP,
|
|
@@ -31453,6 +32477,7 @@ export {
|
|
|
31453
32477
|
brainDecisionKey,
|
|
31454
32478
|
buildConsolidationInstruction,
|
|
31455
32479
|
buildDownAlert,
|
|
32480
|
+
buildProbeTaskText,
|
|
31456
32481
|
buildProjectContextualizedPrompt,
|
|
31457
32482
|
buildRecoveryAlert,
|
|
31458
32483
|
buildSkillDistillInstruction,
|
|
@@ -31534,6 +32559,7 @@ export {
|
|
|
31534
32559
|
makeMailInboxTool,
|
|
31535
32560
|
makeMailSendTool,
|
|
31536
32561
|
makeMailboxTool,
|
|
32562
|
+
makeMutationTestTool,
|
|
31537
32563
|
makeQualityGateTool,
|
|
31538
32564
|
makeRollUpTool,
|
|
31539
32565
|
makeSpawnTool,
|