@wrongstack/core 0.10.2 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-DBBGP5O4.d.ts → agent-bridge-D_XcS2HL.d.ts} +3 -1
- package/dist/{agent-subagent-runner-KwufTWwn.d.ts → agent-subagent-runner-DuRtZmhl.d.ts} +13 -3
- package/dist/coordination/index.d.ts +8 -8
- package/dist/coordination/index.js +83 -21
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +11 -11
- package/dist/defaults/index.js +89 -23
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-Bw1K5E6K.d.ts → events-BrQiweXN.d.ts} +1 -1
- package/dist/execution/index.d.ts +6 -6
- package/dist/execution/index.js +32 -10
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +2 -2
- package/dist/{index-DgQWnZxP.d.ts → index-DAHEM_II.d.ts} +1 -1
- package/dist/{index-DIt5TIaF.d.ts → index-DnBHrz2y.d.ts} +2 -2
- package/dist/index.d.ts +17 -17
- package/dist/index.js +226 -101
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +2 -2
- package/dist/kernel/index.d.ts +3 -3
- package/dist/kernel/index.js.map +1 -1
- package/dist/{multi-agent-C1O8EFzb.d.ts → multi-agent-C8Z1i__e.d.ts} +2 -2
- package/dist/{multi-agent-coordinator-DoVSBFNx.d.ts → multi-agent-coordinator-CjG-FZKj.d.ts} +8 -3
- package/dist/{null-fleet-bus-T-s3JaOt.d.ts → null-fleet-bus-B3WdVY9i.d.ts} +28 -23
- package/dist/observability/index.d.ts +1 -1
- package/dist/{path-resolver-BfyK-I7w.d.ts → path-resolver-DumKAi0n.d.ts} +1 -1
- package/dist/{plan-templates-BWs6Je7j.d.ts → plan-templates-BUP4o-ds.d.ts} +2 -2
- package/dist/{provider-runner-BmPe2RuU.d.ts → provider-runner-Dlv8Fvw9.d.ts} +1 -1
- package/dist/sdd/index.d.ts +4 -4
- package/dist/sdd/index.js +30 -8
- package/dist/sdd/index.js.map +1 -1
- package/dist/security/index.js +3 -3
- package/dist/security/index.js.map +1 -1
- package/dist/{session-reader-CCSBfQmd.d.ts → session-reader-bfgsy2a0.d.ts} +7 -3
- package/dist/storage/index.d.ts +29 -10
- package/dist/storage/index.js +131 -65
- package/dist/storage/index.js.map +1 -1
- package/dist/{tool-executor-D7RxLtg4.d.ts → tool-executor-D87-_5Of.d.ts} +1 -1
- package/dist/types/index.d.ts +8 -8
- package/dist/types/index.js +25 -5
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -56,8 +56,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
56
56
|
}
|
|
57
57
|
let mode;
|
|
58
58
|
try {
|
|
59
|
-
const
|
|
60
|
-
mode =
|
|
59
|
+
const stat9 = await fsp2.stat(targetPath);
|
|
60
|
+
mode = stat9.mode & 511;
|
|
61
61
|
} catch {
|
|
62
62
|
mode = opts.mode;
|
|
63
63
|
}
|
|
@@ -1302,7 +1302,7 @@ var isColorTty = () => {
|
|
|
1302
1302
|
return Boolean(process.stdout?.isTTY);
|
|
1303
1303
|
};
|
|
1304
1304
|
var COLOR = isColorTty();
|
|
1305
|
-
var wrap = (
|
|
1305
|
+
var wrap = (open5, close) => (s) => COLOR ? `\x1B[${open5}m${s}\x1B[${close}m` : s;
|
|
1306
1306
|
var color = {
|
|
1307
1307
|
reset: wrap("0", "0"),
|
|
1308
1308
|
bold: wrap("1", "22"),
|
|
@@ -2879,12 +2879,14 @@ var InMemoryAgentBridge = class {
|
|
|
2879
2879
|
timeoutMs;
|
|
2880
2880
|
/** Guards request() so concurrent calls on the same id can't silently overwrite. */
|
|
2881
2881
|
inflightGuards = /* @__PURE__ */ new Set();
|
|
2882
|
+
/** Stores the transport unsubscribe function so it can be called on stop(). */
|
|
2883
|
+
_transportUnsubscribe;
|
|
2882
2884
|
constructor(config, transport) {
|
|
2883
2885
|
this.agentId = config.agentId;
|
|
2884
2886
|
this.coordinatorId = config.coordinatorId;
|
|
2885
2887
|
this.transport = transport;
|
|
2886
2888
|
this.timeoutMs = config.timeoutMs ?? 3e4;
|
|
2887
|
-
this.transport.subscribe(this.agentId, (msg) => {
|
|
2889
|
+
this._transportUnsubscribe = this.transport.subscribe(this.agentId, (msg) => {
|
|
2888
2890
|
if (msg.type === "heartbeat") return;
|
|
2889
2891
|
const pending = this.pendingRequests.get(msg.id);
|
|
2890
2892
|
if (pending) {
|
|
@@ -2959,6 +2961,8 @@ var InMemoryAgentBridge = class {
|
|
|
2959
2961
|
this.pendingRequests.clear();
|
|
2960
2962
|
this.inflightGuards.clear();
|
|
2961
2963
|
this.subscriptions.clear();
|
|
2964
|
+
this._transportUnsubscribe?.();
|
|
2965
|
+
this._transportUnsubscribe = void 0;
|
|
2962
2966
|
await this.transport.close(this.agentId);
|
|
2963
2967
|
}
|
|
2964
2968
|
};
|
|
@@ -3110,13 +3114,13 @@ var DANGEROUS_FOR_SUBAGENTS = [
|
|
|
3110
3114
|
function hasDangerousCapabilityForSubagents(toolOrCaps) {
|
|
3111
3115
|
if (!toolOrCaps) return false;
|
|
3112
3116
|
const input = toolOrCaps;
|
|
3113
|
-
const caps = Array.isArray(
|
|
3117
|
+
const caps = Array.isArray(toolOrCaps) ? toolOrCaps : input.capabilities ?? [];
|
|
3114
3118
|
return caps.some((c) => DANGEROUS_FOR_SUBAGENTS.includes(c));
|
|
3115
3119
|
}
|
|
3116
3120
|
function getDangerousCapabilities(toolOrCaps) {
|
|
3117
3121
|
if (!toolOrCaps) return [];
|
|
3118
3122
|
const input = toolOrCaps;
|
|
3119
|
-
const caps = Array.isArray(
|
|
3123
|
+
const caps = Array.isArray(toolOrCaps) ? toolOrCaps : input.capabilities ?? [];
|
|
3120
3124
|
return caps.filter(
|
|
3121
3125
|
(c) => DANGEROUS_FOR_SUBAGENTS.includes(c)
|
|
3122
3126
|
);
|
|
@@ -3683,11 +3687,27 @@ var DefaultSessionReader = class {
|
|
|
3683
3687
|
const data = await this.store.load(sessionId);
|
|
3684
3688
|
for (const e of data.events) yield e;
|
|
3685
3689
|
}
|
|
3686
|
-
async search(q, sessionId) {
|
|
3690
|
+
async search(q, sessionId, sessionQuery) {
|
|
3687
3691
|
const limit = q.limit ?? 100;
|
|
3688
3692
|
const matcher = buildMatcher(q);
|
|
3689
3693
|
const allowedTypes = q.types ? new Set(q.types) : null;
|
|
3690
|
-
|
|
3694
|
+
let ids;
|
|
3695
|
+
if (sessionId) {
|
|
3696
|
+
ids = [sessionId];
|
|
3697
|
+
} else {
|
|
3698
|
+
const sessions = await this.store.list(1e3);
|
|
3699
|
+
const titleNeedle = sessionQuery?.titleContains?.toLowerCase();
|
|
3700
|
+
const filtered = sessions.filter((s) => {
|
|
3701
|
+
if (sessionQuery?.since && s.startedAt < sessionQuery.since) return false;
|
|
3702
|
+
if (sessionQuery?.until && s.startedAt > sessionQuery.until) return false;
|
|
3703
|
+
if (sessionQuery?.provider && s.provider !== sessionQuery.provider) return false;
|
|
3704
|
+
if (sessionQuery?.model && s.model !== sessionQuery.model) return false;
|
|
3705
|
+
if (sessionQuery?.minTokens !== void 0 && s.tokenTotal < sessionQuery.minTokens) return false;
|
|
3706
|
+
if (titleNeedle && !s.title.toLowerCase().includes(titleNeedle)) return false;
|
|
3707
|
+
return true;
|
|
3708
|
+
});
|
|
3709
|
+
ids = filtered.map((s) => s.id);
|
|
3710
|
+
}
|
|
3691
3711
|
const hits = [];
|
|
3692
3712
|
for (const id of ids) {
|
|
3693
3713
|
let data;
|
|
@@ -4643,8 +4663,8 @@ var DefaultSessionStore = class {
|
|
|
4643
4663
|
return JSON.parse(raw);
|
|
4644
4664
|
} catch {
|
|
4645
4665
|
const full = this.sessionPath(id, ".jsonl");
|
|
4646
|
-
const
|
|
4647
|
-
const summary = await this.summarize(id,
|
|
4666
|
+
const stat9 = await fsp2.stat(full);
|
|
4667
|
+
const summary = await this.summarize(id, stat9.mtime.toISOString());
|
|
4648
4668
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
4649
4669
|
console.warn(
|
|
4650
4670
|
`[session-store] Failed to write manifest for "${id}":`,
|
|
@@ -9204,8 +9224,9 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
9204
9224
|
if (elapsedMs !== void 0) {
|
|
9205
9225
|
const idle = this.idleMs();
|
|
9206
9226
|
if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
|
|
9207
|
-
exceeded.push({ kind: "
|
|
9208
|
-
}
|
|
9227
|
+
exceeded.push({ kind: "idle_timeout", used: idle, limit: this.limits.idleTimeoutMs });
|
|
9228
|
+
}
|
|
9229
|
+
if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
|
|
9209
9230
|
exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
|
|
9210
9231
|
}
|
|
9211
9232
|
}
|
|
@@ -12418,10 +12439,6 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
12418
12439
|
this.drainPendingAsAborted("Coordinator stopAll() drained the pending queue");
|
|
12419
12440
|
await Promise.allSettled([...this.subagents.keys()].map((id) => this.stop(id)));
|
|
12420
12441
|
}
|
|
12421
|
-
async remove(subagentId) {
|
|
12422
|
-
await this.stop(subagentId);
|
|
12423
|
-
this.subagents.delete(subagentId);
|
|
12424
|
-
}
|
|
12425
12442
|
/**
|
|
12426
12443
|
* Get current coordinator stats for monitoring/debugging.
|
|
12427
12444
|
*/
|
|
@@ -12687,7 +12704,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
12687
12704
|
durationMs: Date.now() - startTime
|
|
12688
12705
|
};
|
|
12689
12706
|
} catch (err) {
|
|
12690
|
-
const status = err instanceof BudgetExceededError && err.kind === "timeout" ? "timeout" : subagent.abortController.signal.aborted ? "stopped" : "failed";
|
|
12707
|
+
const status = err instanceof BudgetExceededError && (err.kind === "timeout" || err.kind === "idle_timeout") ? "timeout" : subagent.abortController.signal.aborted ? "stopped" : "failed";
|
|
12691
12708
|
const usage = budget.usage();
|
|
12692
12709
|
result = {
|
|
12693
12710
|
subagentId,
|
|
@@ -12841,6 +12858,30 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
12841
12858
|
});
|
|
12842
12859
|
}
|
|
12843
12860
|
}
|
|
12861
|
+
/**
|
|
12862
|
+
* Stop a subagent and remove it from the coordinator. Releases all
|
|
12863
|
+
* associated resources (AbortController, context, budget state).
|
|
12864
|
+
* The subagent entry is deleted so the id can be reused in a future spawn.
|
|
12865
|
+
*/
|
|
12866
|
+
async remove(subagentId) {
|
|
12867
|
+
const subagent = this.subagents.get(subagentId);
|
|
12868
|
+
if (!subagent) return;
|
|
12869
|
+
if (subagent.status === "running" || subagent.status === "idle") {
|
|
12870
|
+
this.terminating.add(subagentId);
|
|
12871
|
+
subagent.abortController.abort();
|
|
12872
|
+
subagent.status = "stopped";
|
|
12873
|
+
}
|
|
12874
|
+
this.subagents.delete(subagentId);
|
|
12875
|
+
this.terminating.delete(subagentId);
|
|
12876
|
+
this.pendingTasks = this.pendingTasks.filter((t2) => t2.subagentId !== subagentId);
|
|
12877
|
+
this.fleetBus?.emit({
|
|
12878
|
+
subagentId,
|
|
12879
|
+
ts: Date.now(),
|
|
12880
|
+
type: "subagent.removed",
|
|
12881
|
+
payload: { subagentId }
|
|
12882
|
+
});
|
|
12883
|
+
this.emitCoordinatorStats();
|
|
12884
|
+
}
|
|
12844
12885
|
isDone() {
|
|
12845
12886
|
if (this.config.doneCondition.type === "all_tasks_done") {
|
|
12846
12887
|
return this.pendingTasks.length === 0 && this.inFlight === 0;
|
|
@@ -12864,7 +12905,8 @@ function classifySubagentError(err, hints = {}) {
|
|
|
12864
12905
|
tool_calls: "budget_tool_calls",
|
|
12865
12906
|
tokens: "budget_tokens",
|
|
12866
12907
|
cost: "budget_cost",
|
|
12867
|
-
timeout: "budget_timeout"
|
|
12908
|
+
timeout: "budget_timeout",
|
|
12909
|
+
idle_timeout: "budget_timeout"
|
|
12868
12910
|
};
|
|
12869
12911
|
return {
|
|
12870
12912
|
kind: map[err.kind],
|
|
@@ -13626,14 +13668,36 @@ var FleetUsageAggregator = class {
|
|
|
13626
13668
|
constructor(bus, priceLookup, metaLookup) {
|
|
13627
13669
|
this.priceLookup = priceLookup;
|
|
13628
13670
|
this.metaLookup = metaLookup;
|
|
13629
|
-
bus.filter("provider.response", (e) => this.onProviderResponse(e));
|
|
13630
|
-
bus.filter("tool.executed", (e) => this.onToolExecuted(e));
|
|
13631
|
-
bus.filter("iteration.started", (e) => this.onIterationStarted(e));
|
|
13671
|
+
this.unsub.push(bus.filter("provider.response", (e) => this.onProviderResponse(e)));
|
|
13672
|
+
this.unsub.push(bus.filter("tool.executed", (e) => this.onToolExecuted(e)));
|
|
13673
|
+
this.unsub.push(bus.filter("iteration.started", (e) => this.onIterationStarted(e)));
|
|
13632
13674
|
}
|
|
13633
13675
|
priceLookup;
|
|
13634
13676
|
metaLookup;
|
|
13635
13677
|
perSubagent = /* @__PURE__ */ new Map();
|
|
13636
13678
|
total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
13679
|
+
unsub = new Array();
|
|
13680
|
+
/**
|
|
13681
|
+
* Remove a terminated subagent's data from the aggregator and subtract its
|
|
13682
|
+
* contribution from the running totals. Call this when a subagent is removed
|
|
13683
|
+
* from the fleet so the aggregator doesn't accumulate unbounded data for
|
|
13684
|
+
* entities that will never emit events again.
|
|
13685
|
+
*/
|
|
13686
|
+
removeSubagent(subagentId) {
|
|
13687
|
+
const snap = this.perSubagent.get(subagentId);
|
|
13688
|
+
if (!snap) return;
|
|
13689
|
+
this.perSubagent.delete(subagentId);
|
|
13690
|
+
this.total.input -= snap.input;
|
|
13691
|
+
this.total.output -= snap.output;
|
|
13692
|
+
this.total.cacheRead -= snap.cacheRead;
|
|
13693
|
+
this.total.cacheWrite -= snap.cacheWrite;
|
|
13694
|
+
this.total.cost -= snap.cost;
|
|
13695
|
+
}
|
|
13696
|
+
/** Disposes all fleet-bus subscriptions. Call when the aggregator is no longer needed. */
|
|
13697
|
+
dispose() {
|
|
13698
|
+
for (const off of this.unsub) off();
|
|
13699
|
+
this.unsub.length = 0;
|
|
13700
|
+
}
|
|
13637
13701
|
/** Live snapshot — safe to call from a tool's execute() body. */
|
|
13638
13702
|
snapshot() {
|
|
13639
13703
|
return {
|
|
@@ -14386,7 +14450,7 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
14386
14450
|
this.cancel(`Director cancelled: ${role} ${payload.kind} threshold`);
|
|
14387
14451
|
return;
|
|
14388
14452
|
}
|
|
14389
|
-
if (payload.kind === "timeout") {
|
|
14453
|
+
if (payload.kind === "timeout" || payload.kind === "idle_timeout") {
|
|
14390
14454
|
const progress = this.progressBySubagent.get(e.subagentId) ?? 0;
|
|
14391
14455
|
const lastProgress = this.lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
14392
14456
|
if (progress <= lastProgress) {
|
|
@@ -15124,7 +15188,7 @@ var Director = class {
|
|
|
15124
15188
|
if (needsNickname) {
|
|
15125
15189
|
const role = config.role ?? "subagent";
|
|
15126
15190
|
if (this.fleetManager) {
|
|
15127
|
-
this.fleetManager.assignNicknameAndRecord(
|
|
15191
|
+
this.fleetManager.assignNicknameAndRecord(config);
|
|
15128
15192
|
} else {
|
|
15129
15193
|
config.name = assignNickname(role, this._usedNicknames);
|
|
15130
15194
|
this._usedNicknames.add(config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-"));
|
|
@@ -15132,9 +15196,7 @@ var Director = class {
|
|
|
15132
15196
|
}
|
|
15133
15197
|
result = await this.coordinator.spawn(config);
|
|
15134
15198
|
if (this.fleetManager) {
|
|
15135
|
-
|
|
15136
|
-
this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
|
|
15137
|
-
}
|
|
15199
|
+
this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
|
|
15138
15200
|
} else {
|
|
15139
15201
|
this.spawnCount += 1;
|
|
15140
15202
|
this.subagentMeta.set(result.subagentId, {
|
|
@@ -15422,6 +15484,7 @@ var Director = class {
|
|
|
15422
15484
|
}
|
|
15423
15485
|
async remove(subagentId) {
|
|
15424
15486
|
await this.coordinator.remove(subagentId);
|
|
15487
|
+
this.usage.removeSubagent(subagentId);
|
|
15425
15488
|
}
|
|
15426
15489
|
status() {
|
|
15427
15490
|
const base = this.coordinator.getStatus();
|
|
@@ -15757,6 +15820,7 @@ function createDelegateTool(opts) {
|
|
|
15757
15820
|
if (timer) clearTimeout(timer);
|
|
15758
15821
|
offTool();
|
|
15759
15822
|
offIter();
|
|
15823
|
+
offProgress();
|
|
15760
15824
|
resolve9(value);
|
|
15761
15825
|
};
|
|
15762
15826
|
const arm = () => {
|
|
@@ -15768,6 +15832,7 @@ function createDelegateTool(opts) {
|
|
|
15768
15832
|
};
|
|
15769
15833
|
const offTool = dir.fleet.filter("tool.executed", bump);
|
|
15770
15834
|
const offIter = dir.fleet.filter("iteration.started", bump);
|
|
15835
|
+
const offProgress = dir.fleet.filter("tool.progress", bump);
|
|
15771
15836
|
arm();
|
|
15772
15837
|
dir.awaitTasks([taskId]).then((r) => finish(r[0] ?? { __timeout: true })).catch(() => finish({ __timeout: true }));
|
|
15773
15838
|
});
|
|
@@ -15987,7 +16052,8 @@ var FIELD_BY_KIND = {
|
|
|
15987
16052
|
tool_calls: "maxToolCalls",
|
|
15988
16053
|
tokens: "maxTokens",
|
|
15989
16054
|
cost: "maxCostUsd",
|
|
15990
|
-
timeout: "timeoutMs"
|
|
16055
|
+
timeout: "timeoutMs",
|
|
16056
|
+
idle_timeout: "timeoutMs"
|
|
15991
16057
|
};
|
|
15992
16058
|
function attachAutoExtend(events, policy = {}) {
|
|
15993
16059
|
const factor = policy.factor ?? 0.5;
|
|
@@ -16005,7 +16071,7 @@ function attachAutoExtend(events, policy = {}) {
|
|
|
16005
16071
|
}),
|
|
16006
16072
|
events.on("budget.threshold_reached", (e) => {
|
|
16007
16073
|
const { kind, limit, extend, deny } = e;
|
|
16008
|
-
if (kind === "timeout") {
|
|
16074
|
+
if (kind === "timeout" || kind === "idle_timeout") {
|
|
16009
16075
|
if (progress > lastTimeoutProgress) {
|
|
16010
16076
|
lastTimeoutProgress = progress;
|
|
16011
16077
|
const next2 = Math.min(Math.ceil(limit * (1 + factor)), ceiling.timeoutMs);
|
|
@@ -16110,8 +16176,8 @@ async function loadProjectModes(modesDir) {
|
|
|
16110
16176
|
for (const entry of entries) {
|
|
16111
16177
|
if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
|
|
16112
16178
|
const filePath = path6.join(modesDir, entry);
|
|
16113
|
-
const
|
|
16114
|
-
if (!
|
|
16179
|
+
const stat9 = await fsp2.stat(filePath);
|
|
16180
|
+
if (!stat9.isFile()) continue;
|
|
16115
16181
|
const content = await fsp2.readFile(filePath, "utf8");
|
|
16116
16182
|
const id = path6.basename(entry, path6.extname(entry));
|
|
16117
16183
|
modes.push({
|
|
@@ -20040,10 +20106,10 @@ var SkillInstaller = class {
|
|
|
20040
20106
|
if (!resolved.startsWith(path6.resolve(destDir))) {
|
|
20041
20107
|
throw new Error(`Path traversal detected in skill file: ${file}`);
|
|
20042
20108
|
}
|
|
20043
|
-
const
|
|
20044
|
-
if (
|
|
20109
|
+
const stat9 = await fsp2.stat(srcPath);
|
|
20110
|
+
if (stat9.size > MAX_SKILL_FILE_SIZE) {
|
|
20045
20111
|
throw new Error(
|
|
20046
|
-
`Skill file "${file}" is too large (${(
|
|
20112
|
+
`Skill file "${file}" is too large (${(stat9.size / 1024).toFixed(1)}KB). Max: ${MAX_SKILL_FILE_SIZE / 1024}KB`
|
|
20047
20113
|
);
|
|
20048
20114
|
}
|
|
20049
20115
|
await fsp2.mkdir(path6.dirname(destPath), { recursive: true });
|
|
@@ -20473,6 +20539,8 @@ var ReplayLogStore = class {
|
|
|
20473
20539
|
writeChains = /* @__PURE__ */ new Map();
|
|
20474
20540
|
/** Per-session hash → entry index, kept in memory after the first load. */
|
|
20475
20541
|
cache = /* @__PURE__ */ new Map();
|
|
20542
|
+
/** Per-session entry count on disk, to detect when compaction is needed. */
|
|
20543
|
+
diskCount = /* @__PURE__ */ new Map();
|
|
20476
20544
|
maxEntries;
|
|
20477
20545
|
constructor(opts) {
|
|
20478
20546
|
this.dir = opts.dir;
|
|
@@ -20495,17 +20563,30 @@ var ReplayLogStore = class {
|
|
|
20495
20563
|
request: input.request,
|
|
20496
20564
|
response: input.response
|
|
20497
20565
|
};
|
|
20498
|
-
|
|
20499
|
-
|
|
20500
|
-
|
|
20501
|
-
|
|
20502
|
-
}
|
|
20503
|
-
|
|
20504
|
-
|
|
20505
|
-
|
|
20566
|
+
cache.set(hash, entry);
|
|
20567
|
+
const count = (this.diskCount.get(input.sessionId) ?? 0) + 1;
|
|
20568
|
+
if (count > this.maxEntries) {
|
|
20569
|
+
await this.compact(input.sessionId, cache);
|
|
20570
|
+
} else {
|
|
20571
|
+
await fsp2.appendFile(this.filePath(input.sessionId), JSON.stringify(entry) + "\n", "utf8");
|
|
20572
|
+
this.diskCount.set(input.sessionId, count);
|
|
20573
|
+
}
|
|
20506
20574
|
});
|
|
20507
20575
|
return hash;
|
|
20508
20576
|
}
|
|
20577
|
+
/**
|
|
20578
|
+
* Compact the replay log to keep only the most recent maxEntries.
|
|
20579
|
+
* Called when entry count exceeds the cap. Rewrites the entire file
|
|
20580
|
+
* but only happens O(n / maxEntries) times per session.
|
|
20581
|
+
*/
|
|
20582
|
+
async compact(sessionId, cache) {
|
|
20583
|
+
const all = [...cache.values()];
|
|
20584
|
+
const keep = all.slice(-this.maxEntries);
|
|
20585
|
+
await this.writeAll(sessionId, keep);
|
|
20586
|
+
cache.clear();
|
|
20587
|
+
for (const e of keep) cache.set(e.hash, e);
|
|
20588
|
+
this.diskCount.set(sessionId, keep.length);
|
|
20589
|
+
}
|
|
20509
20590
|
// ── Reads ───────────────────────────────────────────────────────────────
|
|
20510
20591
|
/**
|
|
20511
20592
|
* Look up an entry by hash. Returns `null` when the request has
|
|
@@ -20589,6 +20670,7 @@ var ReplayLogStore = class {
|
|
|
20589
20670
|
cache = /* @__PURE__ */ new Map();
|
|
20590
20671
|
for (const e of all) cache.set(e.hash, e);
|
|
20591
20672
|
this.cache.set(sessionId, cache);
|
|
20673
|
+
this.diskCount.set(sessionId, all.length);
|
|
20592
20674
|
return cache;
|
|
20593
20675
|
}
|
|
20594
20676
|
enqueue(sessionId, fn) {
|
|
@@ -20618,14 +20700,50 @@ var SessionRecovery = class {
|
|
|
20618
20700
|
*/
|
|
20619
20701
|
async detectStale(sessionId) {
|
|
20620
20702
|
const fp = this.filePath(sessionId);
|
|
20621
|
-
|
|
20703
|
+
const TAIL_SIZE = 8192;
|
|
20704
|
+
let stat9;
|
|
20622
20705
|
try {
|
|
20623
|
-
|
|
20706
|
+
stat9 = await fsp2.stat(fp);
|
|
20624
20707
|
} catch (err) {
|
|
20625
20708
|
if (err.code === "ENOENT") return null;
|
|
20626
20709
|
return null;
|
|
20627
20710
|
}
|
|
20628
|
-
|
|
20711
|
+
if (stat9.size === 0) return null;
|
|
20712
|
+
const position = Math.max(0, stat9.size - TAIL_SIZE);
|
|
20713
|
+
const buf = Buffer.alloc(TAIL_SIZE);
|
|
20714
|
+
let fh;
|
|
20715
|
+
try {
|
|
20716
|
+
fh = await fsp2.open(fp, "r");
|
|
20717
|
+
const { bytesRead } = await fh.read(buf, 0, TAIL_SIZE, position);
|
|
20718
|
+
let eventCount = 0;
|
|
20719
|
+
const raw = buf.subarray(0, bytesRead).toString("utf8");
|
|
20720
|
+
for (const line of raw.split("\n")) {
|
|
20721
|
+
if (line.trim()) eventCount++;
|
|
20722
|
+
}
|
|
20723
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
20724
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
20725
|
+
try {
|
|
20726
|
+
const ev = JSON.parse(lines[i]);
|
|
20727
|
+
if (ev.type === "in_flight_start") {
|
|
20728
|
+
return {
|
|
20729
|
+
sessionId,
|
|
20730
|
+
path: fp,
|
|
20731
|
+
lastEventTs: ev.ts,
|
|
20732
|
+
context: ev.context,
|
|
20733
|
+
eventCount
|
|
20734
|
+
};
|
|
20735
|
+
}
|
|
20736
|
+
return null;
|
|
20737
|
+
} catch {
|
|
20738
|
+
continue;
|
|
20739
|
+
}
|
|
20740
|
+
}
|
|
20741
|
+
return null;
|
|
20742
|
+
} catch {
|
|
20743
|
+
return null;
|
|
20744
|
+
} finally {
|
|
20745
|
+
if (fh) await fh.close();
|
|
20746
|
+
}
|
|
20629
20747
|
}
|
|
20630
20748
|
/**
|
|
20631
20749
|
* Generate a recovery plan for a session. The plan describes
|
|
@@ -20706,49 +20824,22 @@ var SessionRecovery = class {
|
|
|
20706
20824
|
}
|
|
20707
20825
|
return path6.join(this.dir, `${sessionId}.jsonl`);
|
|
20708
20826
|
}
|
|
20709
|
-
/**
|
|
20710
|
-
* Stream-parse the last few lines of a JSONL log. We do NOT load
|
|
20711
|
-
* the whole file into memory — for long-running sessions the log
|
|
20712
|
-
* can be megabytes. Instead we read tail-ward and find the last
|
|
20713
|
-
* `in_flight_start` / `in_flight_end` pair.
|
|
20714
|
-
*/
|
|
20715
|
-
async parseForStale(sessionId, fp, raw) {
|
|
20716
|
-
const lines = raw.split("\n");
|
|
20717
|
-
let lastEvent = null;
|
|
20718
|
-
let eventCount = 0;
|
|
20719
|
-
for (const line of lines) {
|
|
20720
|
-
if (!line.trim()) continue;
|
|
20721
|
-
try {
|
|
20722
|
-
const ev = JSON.parse(line);
|
|
20723
|
-
lastEvent = ev;
|
|
20724
|
-
eventCount++;
|
|
20725
|
-
} catch {
|
|
20726
|
-
}
|
|
20727
|
-
}
|
|
20728
|
-
if (!lastEvent) return null;
|
|
20729
|
-
if (lastEvent.type === "in_flight_start") {
|
|
20730
|
-
return {
|
|
20731
|
-
sessionId,
|
|
20732
|
-
path: fp,
|
|
20733
|
-
lastEventTs: lastEvent.ts,
|
|
20734
|
-
context: lastEvent.context,
|
|
20735
|
-
eventCount
|
|
20736
|
-
};
|
|
20737
|
-
}
|
|
20738
|
-
return null;
|
|
20739
|
-
}
|
|
20740
20827
|
};
|
|
20741
|
-
|
|
20742
|
-
// src/storage/tool-audit-log.ts
|
|
20743
|
-
init_atomic_write();
|
|
20744
20828
|
var GENESIS_PREV = "0".repeat(64);
|
|
20829
|
+
var DEFAULT_FSYNC_EVERY = 100;
|
|
20745
20830
|
var ToolAuditLog = class {
|
|
20746
20831
|
dir;
|
|
20747
20832
|
/** In-memory cache of the last entry's hash (per session), to compute chains efficiently. */
|
|
20748
20833
|
tailHash = /* @__PURE__ */ new Map();
|
|
20834
|
+
/** In-memory counter for entry indices — avoids re-reading the file on every write. */
|
|
20835
|
+
tailIndex = /* @__PURE__ */ new Map();
|
|
20836
|
+
/** Tracks writes since last fsync, per session. */
|
|
20837
|
+
unSyncedWrites = /* @__PURE__ */ new Map();
|
|
20749
20838
|
writeChains = /* @__PURE__ */ new Map();
|
|
20839
|
+
fsyncEvery;
|
|
20750
20840
|
constructor(opts) {
|
|
20751
20841
|
this.dir = opts.dir;
|
|
20842
|
+
this.fsyncEvery = opts.fsyncEvery ?? DEFAULT_FSYNC_EVERY;
|
|
20752
20843
|
}
|
|
20753
20844
|
/**
|
|
20754
20845
|
* Append a tool call/result pair to the chain. Returns the
|
|
@@ -20760,6 +20851,7 @@ var ToolAuditLog = class {
|
|
|
20760
20851
|
let entry = null;
|
|
20761
20852
|
await this.enqueue(input.sessionId, async () => {
|
|
20762
20853
|
const prevHash = this.tailHash.get(input.sessionId) ?? GENESIS_PREV;
|
|
20854
|
+
const index = this.tailIndex.get(input.sessionId) ?? 0;
|
|
20763
20855
|
const id = randomUUID();
|
|
20764
20856
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
20765
20857
|
const content = {
|
|
@@ -20770,12 +20862,11 @@ var ToolAuditLog = class {
|
|
|
20770
20862
|
toolUseId: input.toolUseId,
|
|
20771
20863
|
input: input.input,
|
|
20772
20864
|
output: input.output,
|
|
20773
|
-
isError: input.isError
|
|
20865
|
+
isError: input.isError,
|
|
20866
|
+
index
|
|
20774
20867
|
};
|
|
20775
20868
|
const hash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
|
|
20776
20869
|
entry = {
|
|
20777
|
-
index: this.tailHash.has(input.sessionId) ? -1 : 0,
|
|
20778
|
-
// placeholder; recomputed below
|
|
20779
20870
|
id,
|
|
20780
20871
|
ts,
|
|
20781
20872
|
prevHash,
|
|
@@ -20784,15 +20875,12 @@ var ToolAuditLog = class {
|
|
|
20784
20875
|
toolUseId: input.toolUseId,
|
|
20785
20876
|
input: input.input,
|
|
20786
20877
|
output: input.output,
|
|
20787
|
-
isError: input.isError
|
|
20878
|
+
isError: input.isError,
|
|
20879
|
+
index
|
|
20788
20880
|
};
|
|
20789
|
-
const existing = await this.readAll(input.sessionId);
|
|
20790
|
-
const index = existing.length;
|
|
20791
|
-
const finalContent = { ...content, index };
|
|
20792
|
-
const finalHash = createHash("sha256").update(stableStringify2(finalContent), "utf8").digest("hex");
|
|
20793
|
-
entry = { ...entry, index, hash: finalHash };
|
|
20794
20881
|
await this.appendLine(input.sessionId, entry);
|
|
20795
|
-
this.tailHash.set(input.sessionId,
|
|
20882
|
+
this.tailHash.set(input.sessionId, hash);
|
|
20883
|
+
this.tailIndex.set(input.sessionId, index + 1);
|
|
20796
20884
|
});
|
|
20797
20885
|
return entry;
|
|
20798
20886
|
}
|
|
@@ -20874,9 +20962,34 @@ var ToolAuditLog = class {
|
|
|
20874
20962
|
}
|
|
20875
20963
|
async appendLine(sessionId, entry) {
|
|
20876
20964
|
const fp = this.filePath(sessionId);
|
|
20877
|
-
const
|
|
20878
|
-
|
|
20879
|
-
|
|
20965
|
+
const line = JSON.stringify(entry) + "\n";
|
|
20966
|
+
await fsp2.appendFile(fp, line, "utf8");
|
|
20967
|
+
const count = (this.unSyncedWrites.get(sessionId) ?? 0) + 1;
|
|
20968
|
+
this.unSyncedWrites.set(sessionId, count);
|
|
20969
|
+
if (this.fsyncEvery !== Infinity && count % this.fsyncEvery === 0) {
|
|
20970
|
+
await this.sync(sessionId, fp);
|
|
20971
|
+
}
|
|
20972
|
+
}
|
|
20973
|
+
/**
|
|
20974
|
+
* Explicitly sync the file to disk. Called automatically every
|
|
20975
|
+
* `fsyncEvery` writes, and available for callers who want to
|
|
20976
|
+
* force a sync before closing or during graceful shutdown.
|
|
20977
|
+
*/
|
|
20978
|
+
async flush(sessionId) {
|
|
20979
|
+
await this.sync(sessionId, this.filePath(sessionId));
|
|
20980
|
+
}
|
|
20981
|
+
async sync(sessionId, fp) {
|
|
20982
|
+
try {
|
|
20983
|
+
const fh = await fsp2.open(fp, "r+");
|
|
20984
|
+
try {
|
|
20985
|
+
await fh.sync();
|
|
20986
|
+
} finally {
|
|
20987
|
+
await fh.close();
|
|
20988
|
+
}
|
|
20989
|
+
} catch {
|
|
20990
|
+
} finally {
|
|
20991
|
+
this.unSyncedWrites.set(sessionId, 0);
|
|
20992
|
+
}
|
|
20880
20993
|
}
|
|
20881
20994
|
enqueue(sessionId, fn) {
|
|
20882
20995
|
const prev = this.writeChains.get(sessionId) ?? Promise.resolve();
|
|
@@ -21353,8 +21466,8 @@ var CloudSync = class {
|
|
|
21353
21466
|
const localPath = this.categoryToPath(cat);
|
|
21354
21467
|
if (!localPath) continue;
|
|
21355
21468
|
try {
|
|
21356
|
-
const
|
|
21357
|
-
if (
|
|
21469
|
+
const stat9 = await fsp2.stat(localPath);
|
|
21470
|
+
if (stat9.isDirectory()) {
|
|
21358
21471
|
const files = await this.walkDir(localPath, localPath);
|
|
21359
21472
|
for (const file of files) {
|
|
21360
21473
|
const content = await fsp2.readFile(file, "utf8");
|
|
@@ -21379,8 +21492,8 @@ var CloudSync = class {
|
|
|
21379
21492
|
const localPath = this.categoryToPath(cat);
|
|
21380
21493
|
if (!localPath) continue;
|
|
21381
21494
|
try {
|
|
21382
|
-
const
|
|
21383
|
-
if (
|
|
21495
|
+
const stat9 = await fsp2.stat(localPath);
|
|
21496
|
+
if (stat9.isDirectory()) {
|
|
21384
21497
|
const files = await this.walkDir(localPath, localPath);
|
|
21385
21498
|
for (const file of files) {
|
|
21386
21499
|
const content = await fsp2.readFile(file);
|
|
@@ -23434,14 +23547,18 @@ var FleetManager = class {
|
|
|
23434
23547
|
* Call this INSTEAD of `recordSpawn` when you want automatic nicknames.
|
|
23435
23548
|
* The nickname is written back to `config.name` BEFORE the coordinator
|
|
23436
23549
|
* sees the config, so the manifest, logs, and fleet UI all show it.
|
|
23550
|
+
*
|
|
23551
|
+
* NOTE: This method ONLY assigns the nickname and marks it used.
|
|
23552
|
+
* The caller MUST call `recordSpawn(subagentId, config, priceLookup)` AFTER
|
|
23553
|
+
* `coordinator.spawn()` returns with the real subagentId. This is because
|
|
23554
|
+
* the subagentId is not known until after the coordinator creates the subagent.
|
|
23437
23555
|
*/
|
|
23438
|
-
assignNicknameAndRecord(
|
|
23556
|
+
assignNicknameAndRecord(config) {
|
|
23439
23557
|
const role = config.role ?? "subagent";
|
|
23440
23558
|
const nickname = assignNickname(role, this._usedNicknames);
|
|
23441
23559
|
const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
23442
23560
|
this._usedNicknames.add(baseKey);
|
|
23443
23561
|
config.name = nickname;
|
|
23444
|
-
this.recordSpawn(subagentId, config, priceLookup);
|
|
23445
23562
|
return nickname;
|
|
23446
23563
|
}
|
|
23447
23564
|
/**
|
|
@@ -23619,6 +23736,14 @@ var FleetManager = class {
|
|
|
23619
23736
|
}));
|
|
23620
23737
|
return { pending, live: [] };
|
|
23621
23738
|
}
|
|
23739
|
+
/** Release all resources: clear the manifest debounce timer and dispose the usage aggregator. */
|
|
23740
|
+
dispose() {
|
|
23741
|
+
if (this.manifestTimer) {
|
|
23742
|
+
clearTimeout(this.manifestTimer);
|
|
23743
|
+
this.manifestTimer = null;
|
|
23744
|
+
}
|
|
23745
|
+
this.usage.dispose();
|
|
23746
|
+
}
|
|
23622
23747
|
};
|
|
23623
23748
|
function createMcpControlTool(opts) {
|
|
23624
23749
|
const { getConfig, configPath, registry } = opts;
|
|
@@ -25311,8 +25436,8 @@ var DefaultSystemPromptBuilder = class {
|
|
|
25311
25436
|
return "";
|
|
25312
25437
|
}
|
|
25313
25438
|
if (!Array.isArray(parsed.items) || parsed.items.length === 0) return "";
|
|
25314
|
-
const
|
|
25315
|
-
if (
|
|
25439
|
+
const open5 = parsed.items.filter((i) => i?.status !== "done");
|
|
25440
|
+
if (open5.length === 0) return "";
|
|
25316
25441
|
const lines = ["## Active plan"];
|
|
25317
25442
|
if (parsed.title) lines.push(`*${parsed.title}*`, "");
|
|
25318
25443
|
parsed.items.forEach((it, idx) => {
|
|
@@ -25528,8 +25653,8 @@ ${mem}`);
|
|
|
25528
25653
|
}
|
|
25529
25654
|
async dirExists(p) {
|
|
25530
25655
|
try {
|
|
25531
|
-
const
|
|
25532
|
-
return
|
|
25656
|
+
const stat9 = await fsp2.stat(p);
|
|
25657
|
+
return stat9.isDirectory();
|
|
25533
25658
|
} catch {
|
|
25534
25659
|
return false;
|
|
25535
25660
|
}
|