@wrongstack/core 0.308.7 → 0.309.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/coordination/director/director-toolset.d.ts +2 -2
- package/dist/coordination/director-mutation-test-tool.d.ts +29 -0
- package/dist/coordination/director-tools.d.ts +2 -0
- package/dist/coordination/director.d.ts +16 -0
- package/dist/coordination/explore-companion.d.ts +9 -6
- package/dist/coordination/fleet.d.ts +12 -0
- package/dist/coordination/index.d.ts +1 -1
- package/dist/coordination/index.js +990 -61
- package/dist/coordination/mail-tools.d.ts +10 -6
- package/dist/coordination/mailbox-codecs.d.ts +31 -0
- package/dist/coordination/multi-agent-coordinator.d.ts +14 -0
- package/dist/coordination/multi-agent-timeout.d.ts +11 -1
- package/dist/coordination/mutation-engine.d.ts +76 -0
- package/dist/coordination/subagent-budget.d.ts +54 -0
- package/dist/coordination/subagent-finish.d.ts +78 -0
- package/dist/core/index.js +58 -13
- package/dist/defaults/index.js +1132 -106
- package/dist/execution/index.js +260 -20
- package/dist/hq/index.js +45 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1456 -263
- package/dist/infrastructure/index.js +22 -3
- package/dist/kernel/events/agent-events.d.ts +31 -2
- package/dist/models/index.js +11 -1
- package/dist/observability/index.js +1 -1
- package/dist/plugin/index.js +113 -12
- package/dist/prompts/index.js +360 -3
- package/dist/security/auto-approve-policy.d.ts +2 -2
- package/dist/security/index.js +177 -50
- package/dist/security/permission-helpers.d.ts +11 -0
- package/dist/security/permission-policy.d.ts +10 -1
- package/dist/security/yolo-risk.d.ts +17 -0
- package/dist/session-catalog/index.js +32 -2
- package/dist/session-catalog/project-server.js +32 -2
- package/dist/skills/index.js +39 -6
- package/dist/storage/index.js +44 -3
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +14 -0
- package/dist/types/multi-agent.d.ts +15 -0
- package/dist/types/provider.d.ts +29 -1
- package/dist/types/tool.d.ts +15 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +54 -4
- package/dist/utils/terminal-sanitize.d.ts +41 -0
- package/dist/utils/tool-subject.d.ts +1 -1
- package/instructions/agents/chaos-monkey.md +61 -0
- package/package.json +4 -4
package/dist/execution/index.js
CHANGED
|
@@ -11592,7 +11592,7 @@ function resolveReasoningForRequest(settings, rc, warnings) {
|
|
|
11592
11592
|
const cfg = settings.reasoning;
|
|
11593
11593
|
if (!cfg) return void 0;
|
|
11594
11594
|
const capKnown = rc !== void 0;
|
|
11595
|
-
const supportsReasoning = rc ? rc.default !== "disabled" || rc.disableSupported || rc.effortSupported : false;
|
|
11595
|
+
const supportsReasoning = rc ? rc.default !== "disabled" || rc.disableSupported || rc.effortSupported !== false : false;
|
|
11596
11596
|
const out = {};
|
|
11597
11597
|
if (cfg.mode === "off") {
|
|
11598
11598
|
if (capKnown && rc?.disableSupported) {
|
|
@@ -11614,14 +11614,17 @@ function resolveReasoningForRequest(settings, rc, warnings) {
|
|
|
11614
11614
|
}
|
|
11615
11615
|
const effort = cfg.effort;
|
|
11616
11616
|
if (effort !== void 0) {
|
|
11617
|
-
if (capKnown
|
|
11618
|
-
|
|
11619
|
-
|
|
11617
|
+
if (!capKnown) {
|
|
11618
|
+
} else if (rc?.effortSupported === false) {
|
|
11619
|
+
warnings.push(
|
|
11620
|
+
`reasoning effort "${effort}" requested, but this model does not support effort control; the setting was omitted.`
|
|
11621
|
+
);
|
|
11622
|
+
} else if (rc?.effortSupported === true && rc.effortLevels.length > 0 && !rc.effortLevels.includes(effort)) {
|
|
11620
11623
|
warnings.push(
|
|
11621
11624
|
`reasoning effort "${effort}" not supported by this model (supported: ${rc.effortLevels.join(", ")}); the setting was omitted.`
|
|
11622
11625
|
);
|
|
11623
|
-
} else
|
|
11624
|
-
|
|
11626
|
+
} else {
|
|
11627
|
+
out.effort = effort;
|
|
11625
11628
|
}
|
|
11626
11629
|
}
|
|
11627
11630
|
if (cfg.preserve !== void 0) {
|
|
@@ -11973,6 +11976,46 @@ function asTextBlocks(system) {
|
|
|
11973
11976
|
// src/execution/parallel-eternal-engine.ts
|
|
11974
11977
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
11975
11978
|
|
|
11979
|
+
// src/core/btw.ts
|
|
11980
|
+
var META_KEY2 = "_btwNotes";
|
|
11981
|
+
var MAX_PENDING = 20;
|
|
11982
|
+
function readQueue(ctx) {
|
|
11983
|
+
const raw = ctx.meta[META_KEY2];
|
|
11984
|
+
return Array.isArray(raw) ? raw : [];
|
|
11985
|
+
}
|
|
11986
|
+
function setBtwNote(ctx, text) {
|
|
11987
|
+
const trimmed = text.trim();
|
|
11988
|
+
if (!trimmed) return readQueue(ctx).length;
|
|
11989
|
+
const next = [...readQueue(ctx), trimmed].slice(-MAX_PENDING);
|
|
11990
|
+
ctx.meta[META_KEY2] = next;
|
|
11991
|
+
return next.length;
|
|
11992
|
+
}
|
|
11993
|
+
|
|
11994
|
+
// src/coordination/subagent-finish.ts
|
|
11995
|
+
var SUBAGENT_FINISH_REQUESTED_EVENT = "subagent.finish_requested";
|
|
11996
|
+
var DEFAULT_SUBAGENT_FINISH_GRACE_MS = 12e4;
|
|
11997
|
+
function resolveGracefulFinish(config) {
|
|
11998
|
+
const raw = config.gracefulFinish;
|
|
11999
|
+
if (raw === void 0 || raw === false) return void 0;
|
|
12000
|
+
if (raw === true) return { graceMs: DEFAULT_SUBAGENT_FINISH_GRACE_MS };
|
|
12001
|
+
const graceMs = typeof raw.graceMs === "number" && Number.isFinite(raw.graceMs) && raw.graceMs > 0 ? Math.floor(raw.graceMs) : DEFAULT_SUBAGENT_FINISH_GRACE_MS;
|
|
12002
|
+
return { graceMs };
|
|
12003
|
+
}
|
|
12004
|
+
function buildSubagentFinishNotice(input) {
|
|
12005
|
+
const localTime = new Date(input.deadlineMs).toISOString();
|
|
12006
|
+
const seconds = Math.max(1, Math.round(input.graceMs / 1e3));
|
|
12007
|
+
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.`;
|
|
12008
|
+
return [
|
|
12009
|
+
"[SUBAGENT FINISH] The leader agent has finished its work.",
|
|
12010
|
+
`Reason: ${input.reason}`,
|
|
12011
|
+
timeLeft,
|
|
12012
|
+
"Finish your task now, in this turn: complete the thought you are working on, stop",
|
|
12013
|
+
"starting new tool calls unless one is strictly required to finish, and write your",
|
|
12014
|
+
"final answer or report as your final output, then end your turn.",
|
|
12015
|
+
"Do not restart the task and do not begin new work."
|
|
12016
|
+
].join("\n");
|
|
12017
|
+
}
|
|
12018
|
+
|
|
11976
12019
|
// src/coordination/subagent-budget.ts
|
|
11977
12020
|
var TIMEOUT_PREEMPT_FRACTION = 0.85;
|
|
11978
12021
|
var DECISION_TIMEOUT_MS = 6e4;
|
|
@@ -12030,6 +12073,82 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
12030
12073
|
this.limits.idleTimeoutMs = ext.idleTimeoutMs;
|
|
12031
12074
|
}
|
|
12032
12075
|
}
|
|
12076
|
+
/**
|
|
12077
|
+
* Graceful-finish state (see coordination/subagent-finish.ts).
|
|
12078
|
+
* `_finishNotified` guards the single in-band emission; `_grace` records a
|
|
12079
|
+
* granted working-time extension past the original wall-clock deadline.
|
|
12080
|
+
* They are separate because the two callers want different semantics:
|
|
12081
|
+
* the watchdog grants grace at the deadline crossing (notify + extend),
|
|
12082
|
+
* while an explicit leader-finished request only notifies — a subagent
|
|
12083
|
+
* well inside its budget keeps its full legitimate working time and simply
|
|
12084
|
+
* accelerates.
|
|
12085
|
+
*/
|
|
12086
|
+
_finishNotified = false;
|
|
12087
|
+
_grace = null;
|
|
12088
|
+
/** True once the in-band finish notification has been emitted. */
|
|
12089
|
+
get finishNotified() {
|
|
12090
|
+
return this._finishNotified;
|
|
12091
|
+
}
|
|
12092
|
+
/** True once a grace window has been granted past the original deadline. */
|
|
12093
|
+
get graceGranted() {
|
|
12094
|
+
return this._grace !== null;
|
|
12095
|
+
}
|
|
12096
|
+
/**
|
|
12097
|
+
* Notify the subagent in-band to finish its task in its own turn:
|
|
12098
|
+
* `subagent.finish_requested` is emitted on the wired EventBus and the
|
|
12099
|
+
* agent loop folds the notice into the conversation between tool batches.
|
|
12100
|
+
* Nothing aborts — this is a notification, never an interrupt.
|
|
12101
|
+
*
|
|
12102
|
+
* `opts.graceMs` additionally extends the wall-clock ceiling by that window
|
|
12103
|
+
* (used by the watchdog at a deadline crossing, so the model gets working
|
|
12104
|
+
* time instead of a kill). Omit it to notify without touching the budget —
|
|
12105
|
+
* the subagent keeps its existing time budget and just accelerates.
|
|
12106
|
+
*
|
|
12107
|
+
* Returns `true` when this call did something (emitted the notification
|
|
12108
|
+
* and/or granted grace); `false` when there was nothing to do (already
|
|
12109
|
+
* notified, grace already granted, no EventBus wired, budget not started).
|
|
12110
|
+
*/
|
|
12111
|
+
notifyFinish(reason, opts, now = Date.now) {
|
|
12112
|
+
if (!this._events) return false;
|
|
12113
|
+
if (this.startTime === null) return false;
|
|
12114
|
+
const shouldEmit = !this._finishNotified;
|
|
12115
|
+
const rawGrace = opts?.graceMs;
|
|
12116
|
+
const shouldGrant = rawGrace !== void 0 && this._grace === null;
|
|
12117
|
+
if (!shouldEmit && !shouldGrant) return false;
|
|
12118
|
+
let grantedGraceMs = 0;
|
|
12119
|
+
let graceDeadlineMs;
|
|
12120
|
+
if (shouldGrant && rawGrace !== void 0) {
|
|
12121
|
+
grantedGraceMs = Number.isFinite(rawGrace) && rawGrace > 0 ? Math.floor(rawGrace) : DEFAULT_SUBAGENT_FINISH_GRACE_MS;
|
|
12122
|
+
graceDeadlineMs = now() + grantedGraceMs;
|
|
12123
|
+
this._grace = { deadlineMs: graceDeadlineMs, graceMs: grantedGraceMs };
|
|
12124
|
+
this.patchLimits({ timeoutMs: graceDeadlineMs - this.startTime });
|
|
12125
|
+
}
|
|
12126
|
+
if (shouldEmit) {
|
|
12127
|
+
this._finishNotified = true;
|
|
12128
|
+
const effectiveDeadlineMs = graceDeadlineMs ?? (this.limits.timeoutMs !== void 0 ? this.startTime + this.limits.timeoutMs : now() + DEFAULT_SUBAGENT_FINISH_GRACE_MS);
|
|
12129
|
+
const effectiveGraceMs = Math.max(0, effectiveDeadlineMs - now());
|
|
12130
|
+
const subagentId = this._subagentId;
|
|
12131
|
+
this._events.emit(SUBAGENT_FINISH_REQUESTED_EVENT, {
|
|
12132
|
+
// Omitted entirely when the budget was built without an id — an
|
|
12133
|
+
// empty string is an address that matches nothing.
|
|
12134
|
+
...subagentId !== void 0 ? { subagentId } : {},
|
|
12135
|
+
reason,
|
|
12136
|
+
deadlineMs: effectiveDeadlineMs,
|
|
12137
|
+
graceMs: effectiveGraceMs,
|
|
12138
|
+
notice: buildSubagentFinishNotice({
|
|
12139
|
+
reason,
|
|
12140
|
+
deadlineMs: effectiveDeadlineMs,
|
|
12141
|
+
graceMs: effectiveGraceMs
|
|
12142
|
+
})
|
|
12143
|
+
});
|
|
12144
|
+
}
|
|
12145
|
+
return true;
|
|
12146
|
+
}
|
|
12147
|
+
/** Epoch ms by which the subagent should have produced its final output,
|
|
12148
|
+
* once a grace window was granted. Undefined before that. */
|
|
12149
|
+
get finishDeadlineMs() {
|
|
12150
|
+
return this._grace?.deadlineMs;
|
|
12151
|
+
}
|
|
12033
12152
|
iterations = 0;
|
|
12034
12153
|
toolCalls = 0;
|
|
12035
12154
|
tokenInput = 0;
|
|
@@ -12045,6 +12164,10 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
12045
12164
|
lastActivityTime = null;
|
|
12046
12165
|
_onThreshold;
|
|
12047
12166
|
_sessionId;
|
|
12167
|
+
/** Owning subagent id — used to address the graceful-finish event. */
|
|
12168
|
+
_subagentId;
|
|
12169
|
+
/** True when only the coordinator watchdog may enforce wall-clock limits. */
|
|
12170
|
+
_wallClockWatchdogOwned;
|
|
12048
12171
|
/**
|
|
12049
12172
|
* Hard cap on how long `_negotiateExtension` waits for the coordinator to
|
|
12050
12173
|
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
@@ -12116,6 +12239,8 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
12116
12239
|
constructor(limits = {}, mode = "auto", options = {}) {
|
|
12117
12240
|
this._mode = mode;
|
|
12118
12241
|
this._sessionId = options.sessionId;
|
|
12242
|
+
this._subagentId = options.subagentId;
|
|
12243
|
+
this._wallClockWatchdogOwned = options.wallClockWatchdogOwned === true;
|
|
12119
12244
|
this.limits = { ...limits };
|
|
12120
12245
|
}
|
|
12121
12246
|
currentSessionId() {
|
|
@@ -12194,7 +12319,7 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
12194
12319
|
if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
|
|
12195
12320
|
exceeded.push({ kind: "idle_timeout", used: idle, limit: this.limits.idleTimeoutMs });
|
|
12196
12321
|
}
|
|
12197
|
-
const wallOwnedByWatchdog = this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
|
|
12322
|
+
const wallOwnedByWatchdog = this._wallClockWatchdogOwned || this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
|
|
12198
12323
|
if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs && !wallOwnedByWatchdog) {
|
|
12199
12324
|
exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
|
|
12200
12325
|
}
|
|
@@ -12393,7 +12518,7 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
12393
12518
|
if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
|
|
12394
12519
|
const elapsed = Date.now() - this.startTime;
|
|
12395
12520
|
const wallSkipped = this._onThreshold !== void 0 && this._watchdogActive !== void 0 && timeoutMs !== void 0 && this._watchdogActive === timeoutMs;
|
|
12396
|
-
const wallTripped = wallSkipped ? false : timeoutMs !== void 0 && elapsed > timeoutMs;
|
|
12521
|
+
const wallTripped = this._wallClockWatchdogOwned || wallSkipped ? false : timeoutMs !== void 0 && elapsed > timeoutMs;
|
|
12397
12522
|
const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
|
|
12398
12523
|
if (!wallTripped && !idleTripped) return;
|
|
12399
12524
|
void this.checkLimits(elapsed);
|
|
@@ -12769,6 +12894,14 @@ function makeAgentSubagentRunner(opts) {
|
|
|
12769
12894
|
);
|
|
12770
12895
|
const onParentAbort = () => aborter.abort();
|
|
12771
12896
|
ctx.signal.addEventListener("abort", onParentAbort);
|
|
12897
|
+
if (resolveGracefulFinish(ctx.config)) {
|
|
12898
|
+
unsub.push(
|
|
12899
|
+
events.on("subagent.finish_requested", (e) => {
|
|
12900
|
+
if (e.subagentId && e.subagentId !== ctx.subagentId) return;
|
|
12901
|
+
setBtwNote(agent.ctx, e.notice);
|
|
12902
|
+
})
|
|
12903
|
+
);
|
|
12904
|
+
}
|
|
12772
12905
|
let result;
|
|
12773
12906
|
try {
|
|
12774
12907
|
result = await agent.run(format(task, ctx.config), { signal: aborter.signal });
|
|
@@ -16960,6 +17093,22 @@ var EXPLORE_COMPANION_AGENT = {
|
|
|
16960
17093
|
textStream: "silent",
|
|
16961
17094
|
toolStream: "silent"
|
|
16962
17095
|
};
|
|
17096
|
+
var CHAOS_MONKEY_AGENT = {
|
|
17097
|
+
...defineAgent("chaos-monkey", "Chaos Monkey"),
|
|
17098
|
+
tools: [...TOOLS.build],
|
|
17099
|
+
skillNames: ["testing", "typescript-strict"],
|
|
17100
|
+
spawnBudgetExempt: true,
|
|
17101
|
+
// Run in the live checkout: mutation targets are usually freshly
|
|
17102
|
+
// written and uncommitted — a worktree spawned from HEAD would not
|
|
17103
|
+
// contain them and every mutant would drift. The mutation_test tool
|
|
17104
|
+
// honors this value as its default; callers can still override per
|
|
17105
|
+
// call via its `chaosWorktree` input when targets are committed and
|
|
17106
|
+
// isolation is wanted.
|
|
17107
|
+
worktree: "off",
|
|
17108
|
+
// Report travels via submit_result + final text, not the leader's stream.
|
|
17109
|
+
textStream: "silent",
|
|
17110
|
+
toolStream: "silent"
|
|
17111
|
+
};
|
|
16963
17112
|
var CRITIC_AGENT = defineAgent("critic", "Critic");
|
|
16964
17113
|
var GENERIC_AGENT = defineAgent("generic", "Generic Project Agent");
|
|
16965
17114
|
function withDispatchMetadata(definition) {
|
|
@@ -16979,6 +17128,7 @@ var FLEET_ROSTER = {
|
|
|
16979
17128
|
generic: GENERIC_AGENT,
|
|
16980
17129
|
"shadow-agent": SHADOW_AGENT,
|
|
16981
17130
|
"explore-companion": EXPLORE_COMPANION_AGENT,
|
|
17131
|
+
"chaos-monkey": CHAOS_MONKEY_AGENT,
|
|
16982
17132
|
...Object.fromEntries(
|
|
16983
17133
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, withDispatchMetadata(d)])
|
|
16984
17134
|
)
|
|
@@ -17009,6 +17159,16 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
17009
17159
|
maxTokens: 96e3,
|
|
17010
17160
|
maxCostUsd: 0.5
|
|
17011
17161
|
},
|
|
17162
|
+
"chaos-monkey": {
|
|
17163
|
+
// A mutation pass is many short apply/run/restore cycles — per-mutant
|
|
17164
|
+
// work is tiny, but a large plan (25 mutants/file × N files) needs
|
|
17165
|
+
// headroom. Idle-based reaping covers a stalled pass.
|
|
17166
|
+
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
17167
|
+
maxIterations: 2e3,
|
|
17168
|
+
maxToolCalls: 6e3,
|
|
17169
|
+
maxTokens: 96e3,
|
|
17170
|
+
maxCostUsd: 0.5
|
|
17171
|
+
},
|
|
17012
17172
|
...Object.fromEntries(
|
|
17013
17173
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
|
|
17014
17174
|
)
|
|
@@ -17178,7 +17338,8 @@ async function executeSubagentWithTimeout({
|
|
|
17178
17338
|
budget,
|
|
17179
17339
|
preemptFraction = TIMEOUT_PREEMPT_FRACTION,
|
|
17180
17340
|
abortSubagent,
|
|
17181
|
-
currentSessionId
|
|
17341
|
+
currentSessionId,
|
|
17342
|
+
gracefulFinish
|
|
17182
17343
|
}) {
|
|
17183
17344
|
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
17184
17345
|
const idleLimitMs = budget.limits.idleTimeoutMs;
|
|
@@ -17209,9 +17370,17 @@ async function executeSubagentWithTimeout({
|
|
|
17209
17370
|
const scheduleNext = () => {
|
|
17210
17371
|
const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
17211
17372
|
const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
|
|
17212
|
-
const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
17213
|
-
const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
|
|
17214
|
-
|
|
17373
|
+
const idleRemaining = idleLimitMs === void 0 || gracefulFinish !== void 0 && initialTimeoutMs !== void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
17374
|
+
const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit || gracefulFinish !== void 0 ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
|
|
17375
|
+
const next = Math.min(wallRemaining, idleRemaining, preemptRemaining);
|
|
17376
|
+
if (!Number.isFinite(next)) {
|
|
17377
|
+
if (timer) {
|
|
17378
|
+
clearTimeout(timer);
|
|
17379
|
+
timer = null;
|
|
17380
|
+
}
|
|
17381
|
+
return;
|
|
17382
|
+
}
|
|
17383
|
+
armFor(Math.max(25, next));
|
|
17215
17384
|
};
|
|
17216
17385
|
const negotiateTimeout = async (used, limit) => {
|
|
17217
17386
|
const handler = budget.onThreshold;
|
|
@@ -17260,6 +17429,10 @@ async function executeSubagentWithTimeout({
|
|
|
17260
17429
|
const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
|
|
17261
17430
|
const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
|
|
17262
17431
|
if (idleExceeded && !wallExceeded) {
|
|
17432
|
+
if (gracefulFinish !== void 0 && initialTimeoutMs !== void 0) {
|
|
17433
|
+
scheduleNext();
|
|
17434
|
+
return;
|
|
17435
|
+
}
|
|
17263
17436
|
const sessionId = currentSessionId();
|
|
17264
17437
|
budget._events?.emit("budget.threshold_reached", {
|
|
17265
17438
|
...sessionId ? { sessionId } : {},
|
|
@@ -17276,7 +17449,7 @@ async function executeSubagentWithTimeout({
|
|
|
17276
17449
|
reject(new BudgetExceededError("idle_timeout", idleLimit ?? 0, budget.idleMs()));
|
|
17277
17450
|
return;
|
|
17278
17451
|
}
|
|
17279
|
-
if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed >= wallLimit * preemptFraction) {
|
|
17452
|
+
if (wallLimit !== void 0 && !wallExceeded && gracefulFinish === void 0 && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed >= wallLimit * preemptFraction) {
|
|
17280
17453
|
const activityTs = Date.now() - budget.idleMs();
|
|
17281
17454
|
if (activityTs <= lastGrantActivityTs) {
|
|
17282
17455
|
preemptState = "locked" /* LOCKED */;
|
|
@@ -17310,6 +17483,22 @@ async function executeSubagentWithTimeout({
|
|
|
17310
17483
|
return;
|
|
17311
17484
|
}
|
|
17312
17485
|
const limit = wallLimit ?? 0;
|
|
17486
|
+
if (gracefulFinish !== void 0) {
|
|
17487
|
+
if (!budget.graceGranted) {
|
|
17488
|
+
const reason = `wall-clock budget of ${Math.round(limit / 1e3)}s reached`;
|
|
17489
|
+
if (budget.notifyFinish(reason, { graceMs: gracefulFinish.graceMs })) {
|
|
17490
|
+
scheduleNext();
|
|
17491
|
+
return;
|
|
17492
|
+
}
|
|
17493
|
+
abortSubagent(ctx.subagentId);
|
|
17494
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
17495
|
+
return;
|
|
17496
|
+
} else {
|
|
17497
|
+
abortSubagent(ctx.subagentId);
|
|
17498
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
17499
|
+
return;
|
|
17500
|
+
}
|
|
17501
|
+
}
|
|
17313
17502
|
if (!budget.onThreshold) {
|
|
17314
17503
|
abortSubagent(ctx.subagentId);
|
|
17315
17504
|
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
@@ -17694,6 +17883,32 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
17694
17883
|
completeTask(result) {
|
|
17695
17884
|
this.recordCompletion(result);
|
|
17696
17885
|
}
|
|
17886
|
+
/**
|
|
17887
|
+
* Ask every RUNNING subagent that opted into `gracefulFinish` to finish its
|
|
17888
|
+
* task in its own turn (see coordination/subagent-finish.ts). This is the
|
|
17889
|
+
* leader-side entry point for "the leader agent has finished": it delivers
|
|
17890
|
+
* an in-band notification between tool batches — never an interrupt, never
|
|
17891
|
+
* an abort. Each notified subagent keeps its existing time budget and
|
|
17892
|
+
* accelerates; the watchdog still bounds the maximum lifetime.
|
|
17893
|
+
*
|
|
17894
|
+
* Subagents without the policy opted in are deliberately untouched — their
|
|
17895
|
+
* lifecycle remains the legacy watchdog contract.
|
|
17896
|
+
*
|
|
17897
|
+
* Returns the number of subagents actually notified.
|
|
17898
|
+
*/
|
|
17899
|
+
requestFinish(reason) {
|
|
17900
|
+
let notified = 0;
|
|
17901
|
+
for (const subagent of this.subagents.values()) {
|
|
17902
|
+
if (subagent.status !== "running") continue;
|
|
17903
|
+
if (!resolveGracefulFinish(subagent.config)) continue;
|
|
17904
|
+
const budget = subagent.activeBudget;
|
|
17905
|
+
if (!budget) continue;
|
|
17906
|
+
const usage = budget.usage();
|
|
17907
|
+
if (usage.iterations === 0 && usage.toolCalls === 0) continue;
|
|
17908
|
+
if (budget.notifyFinish(reason)) notified++;
|
|
17909
|
+
}
|
|
17910
|
+
return notified;
|
|
17911
|
+
}
|
|
17697
17912
|
// --- internal dispatching ---------------------------------------------
|
|
17698
17913
|
tryDispatchNext() {
|
|
17699
17914
|
while (this.canDispatch()) {
|
|
@@ -17867,7 +18082,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
17867
18082
|
idleTimeoutMs: rawIdleTimeoutMs ?? this.config.defaultBudget?.idleTimeoutMs ?? configWithRosterDefaults.idleTimeoutMs
|
|
17868
18083
|
},
|
|
17869
18084
|
"auto",
|
|
17870
|
-
{
|
|
18085
|
+
{
|
|
18086
|
+
sessionId: () => this.currentSessionId(),
|
|
18087
|
+
subagentId,
|
|
18088
|
+
// Graceful-finish runs own wall-clock enforcement to the watchdog so
|
|
18089
|
+
// the notify-then-bound lifecycle cannot be raced by tool.progress
|
|
18090
|
+
// heartbeats calling checkTimeout() (see subagent-budget.ts).
|
|
18091
|
+
...resolveGracefulFinish(subagent.config) ? { wallClockWatchdogOwned: true } : {}
|
|
18092
|
+
}
|
|
17871
18093
|
);
|
|
17872
18094
|
subagent.activeBudget = budget;
|
|
17873
18095
|
if (!this.runner) {
|
|
@@ -17900,7 +18122,8 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
17900
18122
|
task,
|
|
17901
18123
|
runCtx,
|
|
17902
18124
|
budget,
|
|
17903
|
-
subagent.config.preemptFraction
|
|
18125
|
+
subagent.config.preemptFraction,
|
|
18126
|
+
resolveGracefulFinish(subagent.config)
|
|
17904
18127
|
);
|
|
17905
18128
|
result = {
|
|
17906
18129
|
subagentId,
|
|
@@ -17930,13 +18153,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
17930
18153
|
}
|
|
17931
18154
|
this.recordCompletion(result);
|
|
17932
18155
|
}
|
|
17933
|
-
async executeWithTimeout(runner, task, ctx, budget, preemptFraction) {
|
|
18156
|
+
async executeWithTimeout(runner, task, ctx, budget, preemptFraction, gracefulFinish) {
|
|
17934
18157
|
return executeSubagentWithTimeout({
|
|
17935
18158
|
runner,
|
|
17936
18159
|
task,
|
|
17937
18160
|
ctx,
|
|
17938
18161
|
budget,
|
|
17939
18162
|
preemptFraction,
|
|
18163
|
+
gracefulFinish,
|
|
17940
18164
|
abortSubagent: (subagentId) => this.subagents.get(subagentId)?.abortController.abort(),
|
|
17941
18165
|
currentSessionId: () => this.currentSessionId()
|
|
17942
18166
|
});
|
|
@@ -20219,6 +20443,10 @@ var DefaultSkillLoader = class {
|
|
|
20219
20443
|
);
|
|
20220
20444
|
for (const e of entries) {
|
|
20221
20445
|
if (!await entryIsDirectory(dir, e)) continue;
|
|
20446
|
+
if (!isValidSkillNameFormat(e.name)) {
|
|
20447
|
+
this.skipped.push({ dir, entry: e.name, reason: "invalid-name-format" });
|
|
20448
|
+
continue;
|
|
20449
|
+
}
|
|
20222
20450
|
const skillFile = path15.join(dir, e.name, "SKILL.md");
|
|
20223
20451
|
let raw;
|
|
20224
20452
|
try {
|
|
@@ -21288,9 +21516,21 @@ function renderCommandLine(command, args) {
|
|
|
21288
21516
|
});
|
|
21289
21517
|
return [command, ...rendered].join(" ");
|
|
21290
21518
|
}
|
|
21291
|
-
function
|
|
21519
|
+
function renderSubjectFields(obj, fields) {
|
|
21520
|
+
const parts = [];
|
|
21521
|
+
for (const field of fields) {
|
|
21522
|
+
const value = obj[field];
|
|
21523
|
+
if (value === void 0 || value === null || value === "" || value === false) continue;
|
|
21524
|
+
const str = String(value);
|
|
21525
|
+
parts.push(`${field}=${/\s/.test(str) ? `"${str.replace(/"/g, '\\"')}"` : str}`);
|
|
21526
|
+
}
|
|
21527
|
+
return parts.join(" ");
|
|
21528
|
+
}
|
|
21529
|
+
function subjectForToolInput(toolName, input, subjectKey, subjectFields) {
|
|
21292
21530
|
if (!input || typeof input !== "object") return void 0;
|
|
21293
21531
|
const obj = input;
|
|
21532
|
+
const extra = subjectFields && subjectFields.length > 0 ? renderSubjectFields(obj, subjectFields) : "";
|
|
21533
|
+
const withExtra = (base) => extra ? `${base} ${extra}` : base;
|
|
21294
21534
|
if (subjectKey) {
|
|
21295
21535
|
const value = obj[subjectKey];
|
|
21296
21536
|
if (Array.isArray(value)) {
|
|
@@ -21306,9 +21546,9 @@ function subjectForToolInput(toolName, input, subjectKey) {
|
|
|
21306
21546
|
if (subjectKey === "command") {
|
|
21307
21547
|
const rendered = renderCommandLine(value, obj["args"]);
|
|
21308
21548
|
if (value === "commit" && obj["dry_run"] === true) {
|
|
21309
|
-
return `${escapeGlobSubject(rendered)}:dry-run`;
|
|
21549
|
+
return `${escapeGlobSubject(withExtra(rendered))}:dry-run`;
|
|
21310
21550
|
}
|
|
21311
|
-
return escapeGlobSubject(rendered);
|
|
21551
|
+
return escapeGlobSubject(withExtra(rendered));
|
|
21312
21552
|
}
|
|
21313
21553
|
if (subjectKey === "directory" && obj["dry_run"] === true) {
|
|
21314
21554
|
return `${escapeGlobSubject(value)}:dry-run`;
|
|
@@ -22559,7 +22799,7 @@ var ToolExecutor = class _ToolExecutor {
|
|
|
22559
22799
|
return { result, tool, durationMs: Date.now() - start };
|
|
22560
22800
|
}
|
|
22561
22801
|
if (effectivePermission === "confirm") {
|
|
22562
|
-
const suggestedPattern = boundary.decision === "confirm" ? `kanban-boundary:${boundary.path ?? tool.name}` : subjectForToolInput(tool.name, use.input, tool.subjectKey) ?? tool.name;
|
|
22802
|
+
const suggestedPattern = boundary.decision === "confirm" ? `kanban-boundary:${boundary.path ?? tool.name}` : subjectForToolInput(tool.name, use.input, tool.subjectKey, tool.subjectFields) ?? tool.name;
|
|
22563
22803
|
if (this.opts.confirmAwaiter) {
|
|
22564
22804
|
const awaiter = this.opts.confirmAwaiter;
|
|
22565
22805
|
const choice = await new Promise(
|
package/dist/hq/index.js
CHANGED
|
@@ -702,6 +702,14 @@ var PATTERNS = [
|
|
|
702
702
|
anchor: "sk-ant-"
|
|
703
703
|
},
|
|
704
704
|
{ type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g, anchor: "sk-" },
|
|
705
|
+
{
|
|
706
|
+
// `xai` is a first-class provider in this codebase, but its key shape was
|
|
707
|
+
// absent here — so the one credential format WrongStack itself hands users
|
|
708
|
+
// was the one the scrubber could not recognize (audit 2026-08-20).
|
|
709
|
+
type: "xai_key",
|
|
710
|
+
regex: /(?<![A-Za-z0-9])xai-[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
|
|
711
|
+
anchor: "xai-"
|
|
712
|
+
},
|
|
705
713
|
{ type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g, anchor: "ghp_" },
|
|
706
714
|
{ type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g, anchor: "github_pat_" },
|
|
707
715
|
{ type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g, anchor: "AKIA" },
|
|
@@ -792,8 +800,8 @@ var PATTERNS = [
|
|
|
792
800
|
// replacement so the separator between adjacent secrets is preserved
|
|
793
801
|
// rather than collapsed. Capture groups are therefore: 1=leading
|
|
794
802
|
// delimiter, 2=key name, 3=value.
|
|
795
|
-
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
796
|
-
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
|
|
803
|
+
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD|PASSPHRASE))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
804
|
+
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD", "PASSPHRASE"]
|
|
797
805
|
},
|
|
798
806
|
{
|
|
799
807
|
type: "json_credential_key",
|
|
@@ -910,6 +918,27 @@ var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key
|
|
|
910
918
|
var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
|
|
911
919
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
912
920
|
var SCRUB_OVERLAP_BYTES = 1024;
|
|
921
|
+
var PEM_PRIVATE_KEY_BEGIN_RE = /-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----/;
|
|
922
|
+
var PEM_END_MARKER = "-----END";
|
|
923
|
+
var MAX_PEM_BLOCK_BYTES = 64 * 1024;
|
|
924
|
+
var PEM_END_LINE_TOLERANCE = 64;
|
|
925
|
+
function extendChunkBoundaryPastPem(text, chunkStart, proposedEnd) {
|
|
926
|
+
const head = text.slice(chunkStart, proposedEnd);
|
|
927
|
+
const lastBegin = head.lastIndexOf("-----BEGIN ");
|
|
928
|
+
if (lastBegin === -1) return proposedEnd;
|
|
929
|
+
const fromBegin = text.slice(chunkStart + lastBegin);
|
|
930
|
+
const marker = PEM_PRIVATE_KEY_BEGIN_RE.exec(fromBegin);
|
|
931
|
+
if (!marker || marker.index !== 0) return proposedEnd;
|
|
932
|
+
const bodyStart = marker[0].length;
|
|
933
|
+
const cap = Math.min(text.length, chunkStart + lastBegin + MAX_PEM_BLOCK_BYTES);
|
|
934
|
+
const closeIdx = fromBegin.indexOf(PEM_END_MARKER, bodyStart);
|
|
935
|
+
if (closeIdx === -1 || chunkStart + lastBegin + closeIdx >= cap + PEM_END_LINE_TOLERANCE) {
|
|
936
|
+
return proposedEnd;
|
|
937
|
+
}
|
|
938
|
+
const lineEnd = fromBegin.indexOf("\n", closeIdx);
|
|
939
|
+
const end = lineEnd === -1 ? text.length : chunkStart + lastBegin + lineEnd + 1;
|
|
940
|
+
return Math.max(proposedEnd, end);
|
|
941
|
+
}
|
|
913
942
|
var PATTERN_ANCHORS = [
|
|
914
943
|
...new Set(
|
|
915
944
|
PATTERNS.flatMap(
|
|
@@ -946,6 +975,7 @@ var DefaultSecretScrubber = class {
|
|
|
946
975
|
}
|
|
947
976
|
}
|
|
948
977
|
end = safe === -1 ? end : safe + 1;
|
|
978
|
+
end = extendChunkBoundaryPastPem(text, i, end);
|
|
949
979
|
}
|
|
950
980
|
out.push(this.scrubOne(text.slice(i, end)));
|
|
951
981
|
i = end;
|
|
@@ -3080,6 +3110,15 @@ function deriveSessionStatus(agents) {
|
|
|
3080
3110
|
(a) => a.status === "running" || a.status === "streaming" || a.status === "waiting_user"
|
|
3081
3111
|
) ? "active" : "idle";
|
|
3082
3112
|
}
|
|
3113
|
+
function downgradeStaleAgentStatuses(agents, nowMs) {
|
|
3114
|
+
const cutoff = nowMs - HQ_STALE_SNAPSHOT_WINDOW_MS;
|
|
3115
|
+
return agents.map((agent) => {
|
|
3116
|
+
if (agent.status !== "running" && agent.status !== "streaming") return agent;
|
|
3117
|
+
const lastActivityAt = Date.parse(agent.lastActivityAt);
|
|
3118
|
+
if (!Number.isFinite(lastActivityAt) || lastActivityAt >= cutoff) return agent;
|
|
3119
|
+
return { ...agent, status: "idle" };
|
|
3120
|
+
});
|
|
3121
|
+
}
|
|
3083
3122
|
function startSessionTelemetryBridge(opts) {
|
|
3084
3123
|
const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
3085
3124
|
const publisher = opts.publisher;
|
|
@@ -3100,6 +3139,7 @@ function startSessionTelemetryBridge(opts) {
|
|
|
3100
3139
|
let lastPublishedAtMs = Date.now();
|
|
3101
3140
|
let disposed = false;
|
|
3102
3141
|
function buildSnapshot() {
|
|
3142
|
+
const effectiveAgents = downgradeStaleAgentStatuses(agents, Date.parse(now()));
|
|
3103
3143
|
return {
|
|
3104
3144
|
sessionId: opts.sessionId,
|
|
3105
3145
|
clientKind: identity.kind,
|
|
@@ -3107,11 +3147,11 @@ function startSessionTelemetryBridge(opts) {
|
|
|
3107
3147
|
projectId: project.projectId,
|
|
3108
3148
|
projectName: opts.projectName ?? project.projectName,
|
|
3109
3149
|
projectRoot: opts.projectRoot,
|
|
3110
|
-
status: deriveSessionStatus(
|
|
3150
|
+
status: deriveSessionStatus(effectiveAgents),
|
|
3111
3151
|
startedAt,
|
|
3112
3152
|
lastActivityAt,
|
|
3113
|
-
agentCount:
|
|
3114
|
-
agents,
|
|
3153
|
+
agentCount: effectiveAgents.length,
|
|
3154
|
+
agents: effectiveAgents,
|
|
3115
3155
|
...identity.hostname !== void 0 ? { hostname: identity.hostname } : {},
|
|
3116
3156
|
...identity.pid !== void 0 ? { pid: identity.pid } : {},
|
|
3117
3157
|
...opts.gitBranch !== void 0 ? { gitBranch: opts.gitBranch } : {}
|
package/dist/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export { DEPENDENCY_FILE_PATTERNS, type DependencyWatcherConfig, type DepWatchEn
|
|
|
16
16
|
export { attachDepWatcherBridge, type DepWatcherBridgeOptions, } from './coordination/dep-watcher-bridge.js';
|
|
17
17
|
export { Director, FleetCostCapError, FleetSpawnBudgetError, FleetTokenCapError, type TaskResultNotification, } from './coordination/director.js';
|
|
18
18
|
export { type DirectorSessionFactory, type DirectorSessionFactoryOptions, makeDirectorSessionFactory, } from './coordination/director-session.js';
|
|
19
|
-
export { makeAskTool, makeAssignTool, makeAwaitTasksTool, makeCollabDebugTool, makeFleetEmitTool, makeFleetTool, makeKanbanQueueTool, makeQualityGateTool, makeRollUpTool, makeSpawnTool, makeTerminateTool, } from './coordination/director-tools.js';
|
|
19
|
+
export { makeAskTool, makeAssignTool, makeAwaitTasksTool, makeCollabDebugTool, makeFleetEmitTool, makeFleetTool, makeKanbanQueueTool, makeMutationTestTool, makeQualityGateTool, makeRollUpTool, makeSpawnTool, makeTerminateTool, } from './coordination/director-tools.js';
|
|
20
20
|
export { DEFAULT_DISPATCH_ROLE, type DispatchCandidate, type DispatchClassifier, type DispatchMethod, type DispatchOptions, type DispatchResult, dispatchAgent, makeLLMClassifier, scoreAgents, } from './coordination/dispatcher.js';
|
|
21
21
|
export { compactLog, type FileAuthorEntry, type FileAuthorLog, type FileAuthorTrackerOptions, getFileHistory, getFilesByAgent, getFullLog, getLastAuthor, recordFileAction, } from './coordination/file-author-tracker.js';
|
|
22
22
|
export { ACP_AGENTS, ALL_FLEET_AGENTS, applyRosterBudget, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, type FleetRosterBudget, } from './coordination/fleet.js';
|