@wrongstack/core 0.257.2 → 0.260.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/{agent-bridge-BrxWHEOm.d.ts → agent-bridge-BbskZ7HH.d.ts} +1 -1
- package/dist/{agent-subagent-runner-US741uBH.d.ts → agent-subagent-runner-BNIGZx18.d.ts} +28 -8
- package/dist/{brain-TjEEwSpw.d.ts → brain-C2yDd7Lw.d.ts} +58 -1
- package/dist/{compactor-C5sT4U7I.d.ts → compactor-t0R_AIt_.d.ts} +1 -1
- package/dist/{config-DuAu23zm.d.ts → config-FG6As4H5.d.ts} +1 -1
- package/dist/{context-CGdgA0q6.d.ts → context-JFOVvu6z.d.ts} +22 -0
- package/dist/coordination/index.d.ts +14 -14
- package/dist/coordination/index.js +189 -28
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +25 -25
- package/dist/defaults/index.js +881 -83
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +15 -15
- package/dist/execution/index.js +108 -26
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{goal-preamble-CznHTZqP.d.ts → goal-preamble-B1IXJtLX.d.ts} +9 -9
- package/dist/{goal-store-CV9Yz2X_.d.ts → goal-store-CPXz6Mml.d.ts} +4 -2
- package/dist/{index-CitPrI3a.d.ts → index-BPcg4N3M.d.ts} +5 -5
- package/dist/{index-CC0Mcm05.d.ts → index-CebbJB94.d.ts} +8 -8
- package/dist/index.d.ts +44 -42
- package/dist/index.js +1452 -274
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{llm-selector-CJ4SyAFE.d.ts → llm-selector-DXxI2tlu.d.ts} +2 -2
- package/dist/{mcp-servers-D8YnLaEp.d.ts → mcp-servers-OwNHo43-.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/{models-registry-ByZCdFuQ.d.ts → models-registry-Djlmq4uB.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-DqTUEAeC.d.ts → multi-agent-coordinator-CEmrSCMJ.d.ts} +1 -1
- package/dist/{null-fleet-bus-B5mfTJXT.d.ts → null-fleet-bus-DT92xqgJ.d.ts} +13 -8
- package/dist/observability/index.d.ts +2 -2
- package/dist/{package-outdated-watcher-BSgR_kK-.d.ts → package-outdated-watcher-C70ag2G9.d.ts} +3 -3
- package/dist/{parallel-eternal-engine-C0juOszP.d.ts → parallel-eternal-engine-0SItuq5r.d.ts} +13 -9
- package/dist/{path-resolver-CbkT-RMU.d.ts → path-resolver-DKBh6Jlo.d.ts} +3 -3
- package/dist/{permission-CwBBpCoF.d.ts → permission-BJ7eO9Vl.d.ts} +1 -1
- package/dist/{permission-policy-B8rSu908.d.ts → permission-policy-DEXOfnpm.d.ts} +3 -2
- package/dist/{pipeline-JG8XoudC.d.ts → pipeline-zflkI2dp.d.ts} +2 -2
- package/dist/{plan-templates-DPiQMkBz.d.ts → plan-templates-BFXyRkEK.d.ts} +32 -11
- package/dist/{provider-runner-hM7EXlLI.d.ts → provider-runner-BC-uywtT.d.ts} +3 -3
- package/dist/{retry-policy-Tg7LXkoK.d.ts → retry-policy-Cavrzmtk.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +20 -2
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-gxtFZYBt.d.ts → secret-vault-CDvDYXWX.d.ts} +1 -1
- package/dist/security/index.d.ts +4 -4
- package/dist/security/index.js +30 -1
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-DWsqVjGf.d.ts → selector-B7AivHsu.d.ts} +1 -1
- package/dist/{session-event-bridge-BAFWdgQ3.d.ts → session-event-bridge-BmIDxdJd.d.ts} +1 -1
- package/dist/{session-reader-CqRvaL5v.d.ts → session-reader-DtofsB-2.d.ts} +1 -1
- package/dist/storage/index.d.ts +30 -21
- package/dist/storage/index.js +1264 -216
- package/dist/storage/index.js.map +1 -1
- package/dist/types/index.d.ts +19 -19
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +2 -2
- package/package.json +1 -1
- package/skills/output-standards/SKILL.md +14 -9
- package/skills/output-standards/SKILL.save.md +3 -2
package/dist/index.js
CHANGED
|
@@ -4279,6 +4279,13 @@ function hasDangerousCapabilityForSubagents(toolOrCaps) {
|
|
|
4279
4279
|
const caps = Array.isArray(toolOrCaps) ? toolOrCaps : input.capabilities ?? [];
|
|
4280
4280
|
return caps.some((c) => DANGEROUS_FOR_SUBAGENTS.includes(c));
|
|
4281
4281
|
}
|
|
4282
|
+
function hasCapability(toolOrCaps, capability) {
|
|
4283
|
+
if (!toolOrCaps) return false;
|
|
4284
|
+
const input = toolOrCaps;
|
|
4285
|
+
const caps = Array.isArray(toolOrCaps) ? toolOrCaps : input.capabilities ?? [];
|
|
4286
|
+
const toCheck = Array.isArray(capability) ? capability : [capability];
|
|
4287
|
+
return toCheck.some((c) => caps.includes(c));
|
|
4288
|
+
}
|
|
4282
4289
|
function getDangerousCapabilities(toolOrCaps) {
|
|
4283
4290
|
if (!toolOrCaps) return [];
|
|
4284
4291
|
const input = toolOrCaps;
|
|
@@ -5052,6 +5059,12 @@ var Context = class {
|
|
|
5052
5059
|
agentId;
|
|
5053
5060
|
/** Human-readable agent name. */
|
|
5054
5061
|
agentName;
|
|
5062
|
+
/**
|
|
5063
|
+
* Session-level trace ID for correlating storage events with agent
|
|
5064
|
+
* iterations. Stored here and also propagated to `session.traceId`
|
|
5065
|
+
* so storage operations can include it in `storage.*` events.
|
|
5066
|
+
*/
|
|
5067
|
+
traceId;
|
|
5055
5068
|
/** Callbacks fired when `setWorkingDir()` changes the working directory. */
|
|
5056
5069
|
_onWorkingDirChanged = [];
|
|
5057
5070
|
/**
|
|
@@ -5087,6 +5100,8 @@ var Context = class {
|
|
|
5087
5100
|
this.tools = init.tools ?? [];
|
|
5088
5101
|
this.agentId = init.agentId ?? "unknown";
|
|
5089
5102
|
this.agentName = init.agentName ?? "Unknown Agent";
|
|
5103
|
+
this.traceId = init.traceId;
|
|
5104
|
+
this.session.traceId = init.traceId;
|
|
5090
5105
|
}
|
|
5091
5106
|
/**
|
|
5092
5107
|
* Observable wrapper over the mutable conversation state. Lazy so
|
|
@@ -6487,6 +6502,40 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
6487
6502
|
this.events = opts.events;
|
|
6488
6503
|
this.secretScrubber = opts.secretScrubber;
|
|
6489
6504
|
}
|
|
6505
|
+
// ── Storage event helpers ───────────────────────────────────────────────────
|
|
6506
|
+
emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
|
|
6507
|
+
this.events?.emit("storage.read", {
|
|
6508
|
+
sessionId,
|
|
6509
|
+
store: "session",
|
|
6510
|
+
filePath,
|
|
6511
|
+
operation,
|
|
6512
|
+
outcome,
|
|
6513
|
+
durationMs,
|
|
6514
|
+
...error !== void 0 ? { error } : {}
|
|
6515
|
+
});
|
|
6516
|
+
}
|
|
6517
|
+
emitWrite(sessionId, filePath, operation, outcome, durationMs, eventCount, error) {
|
|
6518
|
+
this.events?.emit("storage.write", {
|
|
6519
|
+
sessionId,
|
|
6520
|
+
store: "session",
|
|
6521
|
+
filePath,
|
|
6522
|
+
operation,
|
|
6523
|
+
outcome,
|
|
6524
|
+
durationMs,
|
|
6525
|
+
...eventCount !== void 0 ? { eventCount } : {},
|
|
6526
|
+
...error !== void 0 ? { error } : {}
|
|
6527
|
+
});
|
|
6528
|
+
}
|
|
6529
|
+
emitError(sessionId, filePath, operation, error, recoverable) {
|
|
6530
|
+
this.events?.emit("storage.error", {
|
|
6531
|
+
sessionId,
|
|
6532
|
+
store: "session",
|
|
6533
|
+
filePath,
|
|
6534
|
+
operation,
|
|
6535
|
+
error,
|
|
6536
|
+
recoverable
|
|
6537
|
+
});
|
|
6538
|
+
}
|
|
6490
6539
|
/** Absolute path to the session index file. */
|
|
6491
6540
|
get indexFile() {
|
|
6492
6541
|
return path7.join(this.dir, "_index.jsonl");
|
|
@@ -6510,22 +6559,26 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
6510
6559
|
const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt, meta.model ?? meta.provider);
|
|
6511
6560
|
const shardDir = await this.ensureShardDir(id);
|
|
6512
6561
|
const file = path7.join(shardDir, `${path7.basename(id)}.jsonl`);
|
|
6562
|
+
const t0 = Date.now();
|
|
6513
6563
|
let handle;
|
|
6514
6564
|
try {
|
|
6515
6565
|
handle = await fsp3.open(file, "a", 384);
|
|
6516
6566
|
} catch (err) {
|
|
6567
|
+
this.emitError(id, file, "create", err instanceof Error ? err.message : String(err), false);
|
|
6517
6568
|
throw new Error(
|
|
6518
6569
|
`Failed to open session file: ${err instanceof Error ? err.message : String(err)}`,
|
|
6519
6570
|
{ cause: err }
|
|
6520
6571
|
);
|
|
6521
6572
|
}
|
|
6522
6573
|
try {
|
|
6523
|
-
|
|
6574
|
+
const writer = new FileSessionWriter(id, handle, startedAt, meta, this.events, {
|
|
6524
6575
|
dir: shardDir,
|
|
6525
6576
|
filePath: file,
|
|
6526
6577
|
secretScrubber: this.secretScrubber,
|
|
6527
6578
|
onClose: (s) => this.appendToIndex(s)
|
|
6528
6579
|
});
|
|
6580
|
+
this.emitWrite(id, file, "create", "success", Date.now() - t0);
|
|
6581
|
+
return writer;
|
|
6529
6582
|
} catch (err) {
|
|
6530
6583
|
await handle.close().catch((e) => console.warn(JSON.stringify({
|
|
6531
6584
|
level: "warn",
|
|
@@ -6533,16 +6586,19 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
6533
6586
|
message: e instanceof Error ? e.message : String(e),
|
|
6534
6587
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6535
6588
|
})));
|
|
6589
|
+
this.emitError(id, file, "create", err instanceof Error ? err.message : String(err), true);
|
|
6536
6590
|
throw err;
|
|
6537
6591
|
}
|
|
6538
6592
|
}
|
|
6539
6593
|
async resume(id) {
|
|
6540
6594
|
const file = this.sessionPath(id, ".jsonl");
|
|
6595
|
+
const t0 = Date.now();
|
|
6541
6596
|
const data = await this.load(id);
|
|
6542
6597
|
let handle;
|
|
6543
6598
|
try {
|
|
6544
6599
|
handle = await fsp3.open(file, "a", 384);
|
|
6545
6600
|
} catch (err) {
|
|
6601
|
+
this.emitError(id, file, "resume", err instanceof Error ? err.message : String(err), false);
|
|
6546
6602
|
throw new Error(
|
|
6547
6603
|
`Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}`,
|
|
6548
6604
|
{ cause: err }
|
|
@@ -6570,6 +6626,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
6570
6626
|
onClose: (s) => this.appendToIndex(s)
|
|
6571
6627
|
}
|
|
6572
6628
|
);
|
|
6629
|
+
this.emitWrite(id, file, "resume", "success", Date.now() - t0);
|
|
6573
6630
|
return { writer, data };
|
|
6574
6631
|
} catch (err) {
|
|
6575
6632
|
await handle.close().catch((e) => console.warn(JSON.stringify({
|
|
@@ -6578,27 +6635,39 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
6578
6635
|
message: e instanceof Error ? e.message : String(e),
|
|
6579
6636
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6580
6637
|
})));
|
|
6638
|
+
this.emitError(id, file, "resume", err instanceof Error ? err.message : String(err), true);
|
|
6581
6639
|
throw err;
|
|
6582
6640
|
}
|
|
6583
6641
|
}
|
|
6584
6642
|
async load(id) {
|
|
6585
6643
|
const file = this.sessionPath(id, ".jsonl");
|
|
6586
|
-
const
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6644
|
+
const t0 = Date.now();
|
|
6645
|
+
let outcome = "success";
|
|
6646
|
+
let errorMsg;
|
|
6647
|
+
try {
|
|
6648
|
+
const raw = await fsp3.readFile(file, "utf8");
|
|
6649
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
6650
|
+
const events = [];
|
|
6651
|
+
for (const line of lines) {
|
|
6652
|
+
try {
|
|
6653
|
+
const parsed = JSON.parse(line);
|
|
6654
|
+
if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
|
|
6655
|
+
events.push(parsed);
|
|
6656
|
+
}
|
|
6657
|
+
} catch {
|
|
6594
6658
|
}
|
|
6595
|
-
} catch {
|
|
6596
6659
|
}
|
|
6660
|
+
const meta = this.metaFromEvents(id, events);
|
|
6661
|
+
const { messages, usage } = this.replay(events, id);
|
|
6662
|
+
const toolCallEnds = extractToolCallEnds(events);
|
|
6663
|
+
return { metadata: meta, events, messages, usage, toolCallEnds };
|
|
6664
|
+
} catch (err) {
|
|
6665
|
+
outcome = "failure";
|
|
6666
|
+
errorMsg = err instanceof Error ? err.message : String(err);
|
|
6667
|
+
throw err;
|
|
6668
|
+
} finally {
|
|
6669
|
+
this.emitRead(id, file, "load", outcome, Date.now() - t0, errorMsg);
|
|
6597
6670
|
}
|
|
6598
|
-
const meta = this.metaFromEvents(id, events);
|
|
6599
|
-
const { messages, usage } = this.replay(events, id);
|
|
6600
|
-
const toolCallEnds = extractToolCallEnds(events);
|
|
6601
|
-
return { metadata: meta, events, messages, usage, toolCallEnds };
|
|
6602
6671
|
}
|
|
6603
6672
|
async list(limit = 20) {
|
|
6604
6673
|
try {
|
|
@@ -6665,12 +6734,22 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
6665
6734
|
* (keep latest per session), and rewrite. Atomic via temp+rename.
|
|
6666
6735
|
*/
|
|
6667
6736
|
async compactIndex() {
|
|
6668
|
-
const
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6737
|
+
const t0 = Date.now();
|
|
6738
|
+
let outcome = "success";
|
|
6739
|
+
let errorMsg;
|
|
6740
|
+
try {
|
|
6741
|
+
const entries = await this.readIndex();
|
|
6742
|
+
if (entries.length === 0) return;
|
|
6743
|
+
const tmp = `${this.indexFile}.compact.tmp`;
|
|
6744
|
+
const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
|
|
6745
|
+
await fsp3.writeFile(tmp, lines, "utf8");
|
|
6746
|
+
await fsp3.rename(tmp, this.indexFile);
|
|
6747
|
+
} catch (err) {
|
|
6748
|
+
outcome = "failure";
|
|
6749
|
+
errorMsg = err instanceof Error ? err.message : String(err);
|
|
6750
|
+
} finally {
|
|
6751
|
+
this.emitWrite("~compact~", this.indexFile, "compact", outcome, Date.now() - t0, void 0, errorMsg);
|
|
6752
|
+
}
|
|
6674
6753
|
}
|
|
6675
6754
|
/**
|
|
6676
6755
|
* Read the index file and return deduplicated session summaries.
|
|
@@ -6746,22 +6825,31 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
6746
6825
|
}
|
|
6747
6826
|
async summaryFor(id) {
|
|
6748
6827
|
const manifest = this.sessionPath(id, ".summary.json");
|
|
6828
|
+
const t0 = Date.now();
|
|
6829
|
+
let outcome = "success";
|
|
6830
|
+
let errorMsg;
|
|
6749
6831
|
try {
|
|
6750
6832
|
const raw = await fsp3.readFile(manifest, "utf8");
|
|
6833
|
+
this.emitRead(id, manifest, "summary", "success", Date.now() - t0);
|
|
6751
6834
|
return JSON.parse(raw);
|
|
6752
6835
|
} catch {
|
|
6753
6836
|
const full = this.sessionPath(id, ".jsonl");
|
|
6754
6837
|
const stat11 = await fsp3.stat(full);
|
|
6755
6838
|
const summary = await this.summarize(id, stat11.mtime.toISOString());
|
|
6756
6839
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
6840
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6841
|
+
this.emitError(id, manifest, "summary_fallback", msg, true);
|
|
6757
6842
|
console.warn(JSON.stringify({
|
|
6758
6843
|
level: "warn",
|
|
6759
6844
|
event: "session_store.manifest_write_failed",
|
|
6760
6845
|
sessionId: id,
|
|
6761
|
-
message:
|
|
6846
|
+
message: msg,
|
|
6762
6847
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6763
6848
|
}));
|
|
6764
6849
|
});
|
|
6850
|
+
outcome = "failure";
|
|
6851
|
+
errorMsg = "summary fallback \u2014 manifest rebuilt";
|
|
6852
|
+
this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
|
|
6765
6853
|
return summary;
|
|
6766
6854
|
}
|
|
6767
6855
|
}
|
|
@@ -7027,7 +7115,7 @@ function extractToolCallEnds(events) {
|
|
|
7027
7115
|
return result;
|
|
7028
7116
|
}
|
|
7029
7117
|
var FileSessionWriter = class _FileSessionWriter {
|
|
7030
|
-
constructor(id, handle, startedAt, meta, events, opts = {}) {
|
|
7118
|
+
constructor(id, handle, startedAt, meta, events, opts = {}, traceId) {
|
|
7031
7119
|
this.id = id;
|
|
7032
7120
|
this.handle = handle;
|
|
7033
7121
|
this.startedAt = startedAt;
|
|
@@ -7046,6 +7134,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
7046
7134
|
provider: meta.provider ?? "unknown",
|
|
7047
7135
|
tokenTotal: 0
|
|
7048
7136
|
};
|
|
7137
|
+
this.traceId = traceId;
|
|
7049
7138
|
}
|
|
7050
7139
|
id;
|
|
7051
7140
|
handle;
|
|
@@ -7078,6 +7167,8 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
7078
7167
|
lastAppendWarnAt = 0;
|
|
7079
7168
|
secretScrubber;
|
|
7080
7169
|
onCloseCb;
|
|
7170
|
+
/** Implements SessionWriter.traceId — propagated from ContextInit.traceId. */
|
|
7171
|
+
traceId;
|
|
7081
7172
|
// ── Write buffer — batches events to reduce per-event disk I/O ─────────
|
|
7082
7173
|
//
|
|
7083
7174
|
// Every append() pushes the scrubbed event into an in-memory buffer instead
|
|
@@ -7231,9 +7322,14 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
7231
7322
|
const eventCount = this.writeBuffer.length;
|
|
7232
7323
|
const batch = this.writeBuffer.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
7233
7324
|
this.writeBuffer = [];
|
|
7325
|
+
const t0 = Date.now();
|
|
7326
|
+
let outcome = "success";
|
|
7327
|
+
let errorMsg;
|
|
7234
7328
|
try {
|
|
7235
7329
|
await this.enqueueWrite(batch);
|
|
7236
7330
|
} catch (err) {
|
|
7331
|
+
outcome = "failure";
|
|
7332
|
+
errorMsg = err instanceof Error ? err.message : String(err);
|
|
7237
7333
|
this.appendFailCount += eventCount;
|
|
7238
7334
|
const now = Date.now();
|
|
7239
7335
|
if (now - this.lastAppendWarnAt > 5e3) {
|
|
@@ -7247,6 +7343,18 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
7247
7343
|
this.lastAppendWarnAt = now;
|
|
7248
7344
|
this.appendFailCount = 0;
|
|
7249
7345
|
}
|
|
7346
|
+
} finally {
|
|
7347
|
+
this.events?.emit("storage.write", {
|
|
7348
|
+
sessionId: this.id,
|
|
7349
|
+
store: "session",
|
|
7350
|
+
filePath: this.filePath,
|
|
7351
|
+
operation: "flush",
|
|
7352
|
+
outcome,
|
|
7353
|
+
durationMs: Date.now() - t0,
|
|
7354
|
+
...errorMsg !== void 0 ? { error: errorMsg } : {},
|
|
7355
|
+
...eventCount !== void 0 ? { eventCount } : {},
|
|
7356
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
7357
|
+
});
|
|
7250
7358
|
}
|
|
7251
7359
|
}
|
|
7252
7360
|
observeForSummary(event) {
|
|
@@ -7312,14 +7420,46 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
7312
7420
|
outcome: this.outcome ?? "completed"
|
|
7313
7421
|
};
|
|
7314
7422
|
if (this.manifestFile) {
|
|
7423
|
+
const t0 = Date.now();
|
|
7424
|
+
let outcome = "success";
|
|
7425
|
+
let errorMsg;
|
|
7315
7426
|
try {
|
|
7316
7427
|
await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
|
|
7317
|
-
} catch {
|
|
7428
|
+
} catch (err) {
|
|
7429
|
+
outcome = "failure";
|
|
7430
|
+
errorMsg = err instanceof Error ? err.message : String(err);
|
|
7431
|
+
} finally {
|
|
7432
|
+
this.events?.emit("storage.write", {
|
|
7433
|
+
sessionId: this.id,
|
|
7434
|
+
store: "session",
|
|
7435
|
+
filePath: this.manifestFile,
|
|
7436
|
+
operation: "close",
|
|
7437
|
+
outcome,
|
|
7438
|
+
durationMs: Date.now() - t0,
|
|
7439
|
+
...errorMsg !== void 0 ? { error: errorMsg } : {},
|
|
7440
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
7441
|
+
});
|
|
7318
7442
|
}
|
|
7319
7443
|
}
|
|
7444
|
+
const idxT0 = Date.now();
|
|
7445
|
+
let idxOutcome = "success";
|
|
7446
|
+
let idxError;
|
|
7320
7447
|
try {
|
|
7321
7448
|
await this.onCloseCb?.(this.summary);
|
|
7322
|
-
} catch {
|
|
7449
|
+
} catch (err) {
|
|
7450
|
+
idxOutcome = "failure";
|
|
7451
|
+
idxError = err instanceof Error ? err.message : String(err);
|
|
7452
|
+
} finally {
|
|
7453
|
+
this.events?.emit("storage.write", {
|
|
7454
|
+
sessionId: this.summary.id,
|
|
7455
|
+
store: "session",
|
|
7456
|
+
filePath: this.filePath,
|
|
7457
|
+
operation: "index_append",
|
|
7458
|
+
outcome: idxOutcome,
|
|
7459
|
+
durationMs: Date.now() - idxT0,
|
|
7460
|
+
...idxError !== void 0 ? { error: idxError } : {},
|
|
7461
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
7462
|
+
});
|
|
7323
7463
|
}
|
|
7324
7464
|
try {
|
|
7325
7465
|
await this.handle.close();
|
|
@@ -7481,23 +7621,81 @@ function userInputTitle(content) {
|
|
|
7481
7621
|
init_atomic_write();
|
|
7482
7622
|
var QueueStore = class {
|
|
7483
7623
|
file;
|
|
7624
|
+
// Use `| undefined` (not `?`) so exactOptionalPropertyTypes doesn't
|
|
7625
|
+
// reject assigning an optional constructor parameter to these fields.
|
|
7626
|
+
events;
|
|
7627
|
+
traceId;
|
|
7484
7628
|
constructor(opts) {
|
|
7485
7629
|
this.file = path7.join(opts.dir, "queue.json");
|
|
7630
|
+
this.events = opts.events;
|
|
7631
|
+
this.traceId = opts.traceId;
|
|
7486
7632
|
}
|
|
7487
7633
|
async write(items) {
|
|
7634
|
+
const t0 = Date.now();
|
|
7488
7635
|
if (items.length === 0) {
|
|
7489
7636
|
await this.clear();
|
|
7490
7637
|
return;
|
|
7491
7638
|
}
|
|
7492
|
-
|
|
7639
|
+
try {
|
|
7640
|
+
await atomicWrite(this.file, JSON.stringify(items), { mode: 384 });
|
|
7641
|
+
this.events?.emit("storage.write", {
|
|
7642
|
+
sessionId: this.traceId ?? "~boot~",
|
|
7643
|
+
store: "queue",
|
|
7644
|
+
filePath: this.file,
|
|
7645
|
+
operation: "write",
|
|
7646
|
+
outcome: "success",
|
|
7647
|
+
durationMs: Date.now() - t0,
|
|
7648
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
7649
|
+
});
|
|
7650
|
+
} catch (err) {
|
|
7651
|
+
this.events?.emit("storage.error", {
|
|
7652
|
+
sessionId: this.traceId ?? "~boot~",
|
|
7653
|
+
store: "queue",
|
|
7654
|
+
filePath: this.file,
|
|
7655
|
+
operation: "write",
|
|
7656
|
+
outcome: "failure",
|
|
7657
|
+
error: err instanceof Error ? err.message : String(err),
|
|
7658
|
+
recoverable: false,
|
|
7659
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
7660
|
+
});
|
|
7661
|
+
console.warn(JSON.stringify({
|
|
7662
|
+
level: "warn",
|
|
7663
|
+
event: "queue_store.write_failed",
|
|
7664
|
+
path: this.file,
|
|
7665
|
+
message: err instanceof Error ? err.message : String(err),
|
|
7666
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7667
|
+
}));
|
|
7668
|
+
}
|
|
7493
7669
|
}
|
|
7494
7670
|
async read() {
|
|
7671
|
+
const t0 = Date.now();
|
|
7495
7672
|
let raw;
|
|
7496
7673
|
try {
|
|
7497
7674
|
raw = await fsp3.readFile(this.file, "utf8");
|
|
7498
7675
|
} catch (err) {
|
|
7499
7676
|
const code = err.code;
|
|
7500
|
-
if (code === "ENOENT")
|
|
7677
|
+
if (code === "ENOENT") {
|
|
7678
|
+
this.events?.emit("storage.read", {
|
|
7679
|
+
sessionId: this.traceId ?? "~boot~",
|
|
7680
|
+
store: "queue",
|
|
7681
|
+
filePath: this.file,
|
|
7682
|
+
operation: "read",
|
|
7683
|
+
outcome: "success",
|
|
7684
|
+
durationMs: Date.now() - t0,
|
|
7685
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
7686
|
+
});
|
|
7687
|
+
return [];
|
|
7688
|
+
}
|
|
7689
|
+
this.events?.emit("storage.error", {
|
|
7690
|
+
sessionId: this.traceId ?? "~boot~",
|
|
7691
|
+
store: "queue",
|
|
7692
|
+
filePath: this.file,
|
|
7693
|
+
operation: "read",
|
|
7694
|
+
outcome: "failure",
|
|
7695
|
+
error: err instanceof Error ? err.message : String(err),
|
|
7696
|
+
recoverable: true,
|
|
7697
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
7698
|
+
});
|
|
7501
7699
|
console.warn(JSON.stringify({
|
|
7502
7700
|
level: "warn",
|
|
7503
7701
|
event: "queue_store.read_failed",
|
|
@@ -7511,9 +7709,40 @@ var QueueStore = class {
|
|
|
7511
7709
|
try {
|
|
7512
7710
|
parsed = JSON.parse(raw);
|
|
7513
7711
|
} catch {
|
|
7712
|
+
this.events?.emit("storage.read", {
|
|
7713
|
+
sessionId: this.traceId ?? "~boot~",
|
|
7714
|
+
store: "queue",
|
|
7715
|
+
filePath: this.file,
|
|
7716
|
+
operation: "read",
|
|
7717
|
+
outcome: "failure",
|
|
7718
|
+
durationMs: Date.now() - t0,
|
|
7719
|
+
error: "parse_failed",
|
|
7720
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
7721
|
+
});
|
|
7722
|
+
return [];
|
|
7723
|
+
}
|
|
7724
|
+
if (!Array.isArray(parsed)) {
|
|
7725
|
+
this.events?.emit("storage.read", {
|
|
7726
|
+
sessionId: this.traceId ?? "~boot~",
|
|
7727
|
+
store: "queue",
|
|
7728
|
+
filePath: this.file,
|
|
7729
|
+
operation: "read",
|
|
7730
|
+
outcome: "failure",
|
|
7731
|
+
durationMs: Date.now() - t0,
|
|
7732
|
+
error: "invalid_schema",
|
|
7733
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
7734
|
+
});
|
|
7514
7735
|
return [];
|
|
7515
7736
|
}
|
|
7516
|
-
|
|
7737
|
+
this.events?.emit("storage.read", {
|
|
7738
|
+
sessionId: this.traceId ?? "~boot~",
|
|
7739
|
+
store: "queue",
|
|
7740
|
+
filePath: this.file,
|
|
7741
|
+
operation: "read",
|
|
7742
|
+
outcome: "success",
|
|
7743
|
+
durationMs: Date.now() - t0,
|
|
7744
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
7745
|
+
});
|
|
7517
7746
|
const out = [];
|
|
7518
7747
|
for (const v of parsed) {
|
|
7519
7748
|
if (isPersistedQueueItem(v)) out.push(v);
|
|
@@ -7521,11 +7750,31 @@ var QueueStore = class {
|
|
|
7521
7750
|
return out;
|
|
7522
7751
|
}
|
|
7523
7752
|
async clear() {
|
|
7753
|
+
const t0 = Date.now();
|
|
7524
7754
|
try {
|
|
7525
7755
|
await fsp3.unlink(this.file);
|
|
7756
|
+
this.events?.emit("storage.write", {
|
|
7757
|
+
sessionId: this.traceId ?? "~boot~",
|
|
7758
|
+
store: "queue",
|
|
7759
|
+
filePath: this.file,
|
|
7760
|
+
operation: "clear",
|
|
7761
|
+
outcome: "success",
|
|
7762
|
+
durationMs: Date.now() - t0,
|
|
7763
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
7764
|
+
});
|
|
7526
7765
|
} catch (err) {
|
|
7527
7766
|
const code = err.code;
|
|
7528
7767
|
if (code === "ENOENT") return;
|
|
7768
|
+
this.events?.emit("storage.error", {
|
|
7769
|
+
sessionId: this.traceId ?? "~boot~",
|
|
7770
|
+
store: "queue",
|
|
7771
|
+
filePath: this.file,
|
|
7772
|
+
operation: "clear",
|
|
7773
|
+
outcome: "failure",
|
|
7774
|
+
error: err instanceof Error ? err.message : String(err),
|
|
7775
|
+
recoverable: true,
|
|
7776
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
7777
|
+
});
|
|
7529
7778
|
console.warn(JSON.stringify({
|
|
7530
7779
|
level: "warn",
|
|
7531
7780
|
event: "queue_store.clear_failed",
|
|
@@ -7900,6 +8149,7 @@ var MAX_BYTES_TOTAL = 32e3;
|
|
|
7900
8149
|
var DefaultMemoryStore = class {
|
|
7901
8150
|
files;
|
|
7902
8151
|
events;
|
|
8152
|
+
traceId;
|
|
7903
8153
|
backend;
|
|
7904
8154
|
/**
|
|
7905
8155
|
* Per-scope serialization queue. `remember` / `forget` / `consolidate` /
|
|
@@ -7965,15 +8215,70 @@ var DefaultMemoryStore = class {
|
|
|
7965
8215
|
if (writeErr2) {
|
|
7966
8216
|
parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr2.message}`);
|
|
7967
8217
|
}
|
|
7968
|
-
const
|
|
7969
|
-
|
|
8218
|
+
const t0 = Date.now();
|
|
8219
|
+
const filePath = this.files[scope];
|
|
8220
|
+
try {
|
|
8221
|
+
const body = await this.backend.readAll(scope, filePath);
|
|
8222
|
+
const dur = Date.now() - t0;
|
|
8223
|
+
this.events?.emit("storage.read", {
|
|
8224
|
+
sessionId: "~memory~",
|
|
8225
|
+
store: "memory",
|
|
8226
|
+
filePath,
|
|
8227
|
+
operation: "readAll",
|
|
8228
|
+
outcome: "success",
|
|
8229
|
+
durationMs: dur,
|
|
8230
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
8231
|
+
});
|
|
8232
|
+
if (body.trim()) parts.push(`## ${labelOf(scope)}
|
|
7970
8233
|
|
|
7971
8234
|
${body.trim()}`);
|
|
8235
|
+
} catch (err) {
|
|
8236
|
+
const dur = Date.now() - t0;
|
|
8237
|
+
this.events?.emit("storage.read", {
|
|
8238
|
+
sessionId: "~memory~",
|
|
8239
|
+
store: "memory",
|
|
8240
|
+
filePath,
|
|
8241
|
+
operation: "readAll",
|
|
8242
|
+
outcome: "failure",
|
|
8243
|
+
durationMs: dur,
|
|
8244
|
+
error: err instanceof Error ? err.message : String(err),
|
|
8245
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
8246
|
+
});
|
|
8247
|
+
throw err;
|
|
8248
|
+
}
|
|
7972
8249
|
}
|
|
7973
8250
|
return parts.join("\n\n");
|
|
7974
8251
|
}
|
|
7975
8252
|
async read(scope) {
|
|
7976
|
-
|
|
8253
|
+
const t0 = Date.now();
|
|
8254
|
+
const filePath = this.files[scope];
|
|
8255
|
+
try {
|
|
8256
|
+
const body = await this.backend.readAll(scope, filePath);
|
|
8257
|
+
const dur = Date.now() - t0;
|
|
8258
|
+
this.events?.emit("storage.read", {
|
|
8259
|
+
sessionId: "~memory~",
|
|
8260
|
+
store: "memory",
|
|
8261
|
+
filePath,
|
|
8262
|
+
operation: "read",
|
|
8263
|
+
outcome: "success",
|
|
8264
|
+
durationMs: dur,
|
|
8265
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
8266
|
+
});
|
|
8267
|
+
return body;
|
|
8268
|
+
} catch (err) {
|
|
8269
|
+
const dur = Date.now() - t0;
|
|
8270
|
+
this.events?.emit("storage.read", {
|
|
8271
|
+
sessionId: "~memory~",
|
|
8272
|
+
store: "memory",
|
|
8273
|
+
filePath,
|
|
8274
|
+
operation: "read",
|
|
8275
|
+
outcome: "failure",
|
|
8276
|
+
durationMs: dur,
|
|
8277
|
+
error: err instanceof Error ? err.message : String(err),
|
|
8278
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
8279
|
+
});
|
|
8280
|
+
throw err;
|
|
8281
|
+
}
|
|
7977
8282
|
}
|
|
7978
8283
|
/**
|
|
7979
8284
|
* List entries from a scope, newest first. Delegates to the backend
|
|
@@ -7999,7 +8304,34 @@ ${body.trim()}`);
|
|
|
7999
8304
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
8000
8305
|
return this.runSerialized(scope, async () => {
|
|
8001
8306
|
const entry = { scope, text, ts, ...metadata };
|
|
8002
|
-
|
|
8307
|
+
const filePath = this.files[scope];
|
|
8308
|
+
const t0 = Date.now();
|
|
8309
|
+
try {
|
|
8310
|
+
await this.backend.remember(scope, entry, filePath);
|
|
8311
|
+
const dur = Date.now() - t0;
|
|
8312
|
+
this.events?.emit("storage.write", {
|
|
8313
|
+
sessionId: "~memory~",
|
|
8314
|
+
store: "memory",
|
|
8315
|
+
filePath,
|
|
8316
|
+
operation: "remember",
|
|
8317
|
+
outcome: "success",
|
|
8318
|
+
durationMs: dur,
|
|
8319
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
8320
|
+
});
|
|
8321
|
+
} catch (err) {
|
|
8322
|
+
const dur = Date.now() - t0;
|
|
8323
|
+
this.events?.emit("storage.write", {
|
|
8324
|
+
sessionId: "~memory~",
|
|
8325
|
+
store: "memory",
|
|
8326
|
+
filePath,
|
|
8327
|
+
operation: "remember",
|
|
8328
|
+
outcome: "failure",
|
|
8329
|
+
durationMs: dur,
|
|
8330
|
+
error: err instanceof Error ? err.message : String(err),
|
|
8331
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
8332
|
+
});
|
|
8333
|
+
throw err;
|
|
8334
|
+
}
|
|
8003
8335
|
const raw = await this.backend.readAll(scope, this.files[scope]);
|
|
8004
8336
|
if (Buffer.byteLength(raw, "utf8") > MAX_BYTES_TOTAL) {
|
|
8005
8337
|
const removed = await this.backend.consolidate(scope, this.files[scope]);
|
|
@@ -8127,7 +8459,35 @@ ${body.trim()}`);
|
|
|
8127
8459
|
}
|
|
8128
8460
|
async forget(query, scope = "project-memory") {
|
|
8129
8461
|
return this.runSerialized(scope, async () => {
|
|
8130
|
-
const
|
|
8462
|
+
const filePath = this.files[scope];
|
|
8463
|
+
const t0 = Date.now();
|
|
8464
|
+
let removed = 0;
|
|
8465
|
+
try {
|
|
8466
|
+
removed = await this.backend.forget(scope, query, filePath);
|
|
8467
|
+
const dur = Date.now() - t0;
|
|
8468
|
+
this.events?.emit("storage.write", {
|
|
8469
|
+
sessionId: "~memory~",
|
|
8470
|
+
store: "memory",
|
|
8471
|
+
filePath,
|
|
8472
|
+
operation: "forget",
|
|
8473
|
+
outcome: "success",
|
|
8474
|
+
durationMs: dur,
|
|
8475
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
8476
|
+
});
|
|
8477
|
+
} catch (err) {
|
|
8478
|
+
const dur = Date.now() - t0;
|
|
8479
|
+
this.events?.emit("storage.write", {
|
|
8480
|
+
sessionId: "~memory~",
|
|
8481
|
+
store: "memory",
|
|
8482
|
+
filePath,
|
|
8483
|
+
operation: "forget",
|
|
8484
|
+
outcome: "failure",
|
|
8485
|
+
durationMs: dur,
|
|
8486
|
+
error: err instanceof Error ? err.message : String(err),
|
|
8487
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
8488
|
+
});
|
|
8489
|
+
throw err;
|
|
8490
|
+
}
|
|
8131
8491
|
if (removed > 0) {
|
|
8132
8492
|
this.events?.emit("memory.forgotten", {
|
|
8133
8493
|
scope,
|
|
@@ -8141,7 +8501,35 @@ ${body.trim()}`);
|
|
|
8141
8501
|
}
|
|
8142
8502
|
async consolidate(scope) {
|
|
8143
8503
|
return this.runSerialized(scope, async () => {
|
|
8144
|
-
const
|
|
8504
|
+
const filePath = this.files[scope];
|
|
8505
|
+
const t0 = Date.now();
|
|
8506
|
+
let removed = 0;
|
|
8507
|
+
try {
|
|
8508
|
+
removed = await this.backend.consolidate(scope, filePath);
|
|
8509
|
+
const dur = Date.now() - t0;
|
|
8510
|
+
this.events?.emit("storage.write", {
|
|
8511
|
+
sessionId: "~memory~",
|
|
8512
|
+
store: "memory",
|
|
8513
|
+
filePath,
|
|
8514
|
+
operation: "consolidate",
|
|
8515
|
+
outcome: "success",
|
|
8516
|
+
durationMs: dur,
|
|
8517
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
8518
|
+
});
|
|
8519
|
+
} catch (err) {
|
|
8520
|
+
const dur = Date.now() - t0;
|
|
8521
|
+
this.events?.emit("storage.write", {
|
|
8522
|
+
sessionId: "~memory~",
|
|
8523
|
+
store: "memory",
|
|
8524
|
+
filePath,
|
|
8525
|
+
operation: "consolidate",
|
|
8526
|
+
outcome: "failure",
|
|
8527
|
+
durationMs: dur,
|
|
8528
|
+
error: err instanceof Error ? err.message : String(err),
|
|
8529
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
8530
|
+
});
|
|
8531
|
+
throw err;
|
|
8532
|
+
}
|
|
8145
8533
|
if (removed > 0) {
|
|
8146
8534
|
this.events?.emit("memory.consolidated", {
|
|
8147
8535
|
scope,
|
|
@@ -8154,7 +8542,34 @@ ${body.trim()}`);
|
|
|
8154
8542
|
async clear(scope) {
|
|
8155
8543
|
if (scope) {
|
|
8156
8544
|
await this.runSerialized(scope, async () => {
|
|
8157
|
-
|
|
8545
|
+
const filePath = this.files[scope];
|
|
8546
|
+
const t0 = Date.now();
|
|
8547
|
+
try {
|
|
8548
|
+
await this.backend.clear(scope, filePath);
|
|
8549
|
+
const dur = Date.now() - t0;
|
|
8550
|
+
this.events?.emit("storage.write", {
|
|
8551
|
+
sessionId: "~memory~",
|
|
8552
|
+
store: "memory",
|
|
8553
|
+
filePath,
|
|
8554
|
+
operation: "clear",
|
|
8555
|
+
outcome: "success",
|
|
8556
|
+
durationMs: dur,
|
|
8557
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
8558
|
+
});
|
|
8559
|
+
} catch (err) {
|
|
8560
|
+
const dur = Date.now() - t0;
|
|
8561
|
+
this.events?.emit("storage.write", {
|
|
8562
|
+
sessionId: "~memory~",
|
|
8563
|
+
store: "memory",
|
|
8564
|
+
filePath,
|
|
8565
|
+
operation: "clear",
|
|
8566
|
+
outcome: "failure",
|
|
8567
|
+
durationMs: dur,
|
|
8568
|
+
error: err instanceof Error ? err.message : String(err),
|
|
8569
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
8570
|
+
});
|
|
8571
|
+
throw err;
|
|
8572
|
+
}
|
|
8158
8573
|
this.events?.emit("memory.cleared", { scope });
|
|
8159
8574
|
await this.mirrorBackup(scope);
|
|
8160
8575
|
});
|
|
@@ -8162,15 +8577,54 @@ ${body.trim()}`);
|
|
|
8162
8577
|
}
|
|
8163
8578
|
await Promise.all(
|
|
8164
8579
|
["project-agents", "project-memory", "user-memory"].map(
|
|
8165
|
-
(s) => this.runSerialized(s, async () => {
|
|
8166
|
-
|
|
8580
|
+
async (s) => this.runSerialized(s, async () => {
|
|
8581
|
+
const filePath = this.files[s];
|
|
8582
|
+
const t0 = Date.now();
|
|
8583
|
+
try {
|
|
8584
|
+
await this.backend.clear(s, filePath);
|
|
8585
|
+
const dur = Date.now() - t0;
|
|
8586
|
+
this.events?.emit("storage.write", {
|
|
8587
|
+
sessionId: "~memory~",
|
|
8588
|
+
store: "memory",
|
|
8589
|
+
filePath,
|
|
8590
|
+
operation: "clear",
|
|
8591
|
+
outcome: "success",
|
|
8592
|
+
durationMs: dur,
|
|
8593
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
8594
|
+
});
|
|
8595
|
+
} catch (err) {
|
|
8596
|
+
const dur = Date.now() - t0;
|
|
8597
|
+
this.events?.emit("storage.write", {
|
|
8598
|
+
sessionId: "~memory~",
|
|
8599
|
+
store: "memory",
|
|
8600
|
+
filePath,
|
|
8601
|
+
operation: "clear",
|
|
8602
|
+
outcome: "failure",
|
|
8603
|
+
durationMs: dur,
|
|
8604
|
+
error: err instanceof Error ? err.message : String(err),
|
|
8605
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
8606
|
+
});
|
|
8607
|
+
throw err;
|
|
8608
|
+
}
|
|
8167
8609
|
this.events?.emit("memory.cleared", { scope: s });
|
|
8168
8610
|
await this.mirrorBackup(s);
|
|
8169
8611
|
})
|
|
8170
8612
|
)
|
|
8171
8613
|
);
|
|
8172
8614
|
}
|
|
8173
|
-
/**
|
|
8615
|
+
/**
|
|
8616
|
+
* Return a new MemoryStore proxy that carries `traceId` on every storage
|
|
8617
|
+
* event. The original store is left unchanged — callers that need a
|
|
8618
|
+
* trace-decorated view (e.g. session-run tools) receive the proxy while
|
|
8619
|
+
* the singleton remains trace-free for boot-time use.
|
|
8620
|
+
*
|
|
8621
|
+
* The proxy implements the full `MemoryStore` interface; all other
|
|
8622
|
+
* properties (backend, etc.) are delegated to the original store.
|
|
8623
|
+
*/
|
|
8624
|
+
withTraceId(traceId) {
|
|
8625
|
+
this.traceId = traceId;
|
|
8626
|
+
return this;
|
|
8627
|
+
}
|
|
8174
8628
|
async mirrorBackup(scope) {
|
|
8175
8629
|
if (!this.persistBackup || scope === "project-agents") return;
|
|
8176
8630
|
try {
|
|
@@ -8264,6 +8718,13 @@ function deepFreeze(obj) {
|
|
|
8264
8718
|
return Object.freeze(obj);
|
|
8265
8719
|
}
|
|
8266
8720
|
init_atomic_write();
|
|
8721
|
+
function storageErrorString(err) {
|
|
8722
|
+
if (err instanceof Error) {
|
|
8723
|
+
const code = err.code;
|
|
8724
|
+
return code ? `${code}: ${err.message}` : err.message;
|
|
8725
|
+
}
|
|
8726
|
+
return String(err);
|
|
8727
|
+
}
|
|
8267
8728
|
var BEHAVIOR_DEFAULTS = {
|
|
8268
8729
|
version: 1,
|
|
8269
8730
|
context: {
|
|
@@ -8366,11 +8827,15 @@ var DefaultConfigLoader = class {
|
|
|
8366
8827
|
strict;
|
|
8367
8828
|
vault;
|
|
8368
8829
|
extraSources;
|
|
8830
|
+
events;
|
|
8831
|
+
traceId;
|
|
8369
8832
|
constructor(opts) {
|
|
8370
8833
|
this.paths = opts.paths;
|
|
8371
8834
|
this.strict = opts.strict ?? false;
|
|
8372
8835
|
this.vault = opts.vault;
|
|
8373
8836
|
this.extraSources = opts.sources ?? [];
|
|
8837
|
+
this.events = opts.events;
|
|
8838
|
+
this.traceId = opts.traceId;
|
|
8374
8839
|
}
|
|
8375
8840
|
async load(opts = {}) {
|
|
8376
8841
|
let cfg = { ...BEHAVIOR_DEFAULTS };
|
|
@@ -8447,7 +8912,33 @@ var DefaultConfigLoader = class {
|
|
|
8447
8912
|
if (this.vault && toWrite.githubToken && !toWrite.githubToken.startsWith("enc:")) {
|
|
8448
8913
|
toWrite = { ...toWrite, githubToken: this.vault.encrypt(toWrite.githubToken) };
|
|
8449
8914
|
}
|
|
8450
|
-
|
|
8915
|
+
const fp = this.paths.syncConfig;
|
|
8916
|
+
const t0 = Date.now();
|
|
8917
|
+
try {
|
|
8918
|
+
await atomicWrite(fp, JSON.stringify(toWrite, null, 2), { mode: 384 });
|
|
8919
|
+
this.events?.emit("storage.write", {
|
|
8920
|
+
sessionId: "~config~",
|
|
8921
|
+
store: "config",
|
|
8922
|
+
filePath: fp,
|
|
8923
|
+
operation: "persist_sync",
|
|
8924
|
+
outcome: "success",
|
|
8925
|
+
durationMs: Date.now() - t0,
|
|
8926
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
8927
|
+
});
|
|
8928
|
+
} catch (err) {
|
|
8929
|
+
this.events?.emit("storage.error", {
|
|
8930
|
+
sessionId: "~config~",
|
|
8931
|
+
store: "config",
|
|
8932
|
+
filePath: fp,
|
|
8933
|
+
operation: "persist_sync",
|
|
8934
|
+
outcome: "failure",
|
|
8935
|
+
error: storageErrorString(err),
|
|
8936
|
+
recoverable: false,
|
|
8937
|
+
durationMs: Date.now() - t0,
|
|
8938
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
8939
|
+
});
|
|
8940
|
+
throw err;
|
|
8941
|
+
}
|
|
8451
8942
|
}
|
|
8452
8943
|
/**
|
|
8453
8944
|
* Read ~/.wrongstack/sync.json (encrypted GitHub token storage) and decrypt
|
|
@@ -8456,17 +8947,60 @@ var DefaultConfigLoader = class {
|
|
|
8456
8947
|
* isolated — it should never be part of project-local or env-driven config.
|
|
8457
8948
|
*/
|
|
8458
8949
|
async loadSyncConfig() {
|
|
8950
|
+
const fp = this.paths.syncConfig;
|
|
8951
|
+
const t0 = Date.now();
|
|
8459
8952
|
try {
|
|
8460
|
-
const raw = await fsp3.readFile(
|
|
8953
|
+
const raw = await fsp3.readFile(fp, "utf8");
|
|
8461
8954
|
const parsed = safeParse(raw);
|
|
8462
|
-
if (!parsed.ok || !parsed.value)
|
|
8955
|
+
if (!parsed.ok || !parsed.value) {
|
|
8956
|
+
this.events?.emit("storage.read", {
|
|
8957
|
+
sessionId: "~config~",
|
|
8958
|
+
store: "config",
|
|
8959
|
+
filePath: fp,
|
|
8960
|
+
operation: "load_sync",
|
|
8961
|
+
outcome: "failure",
|
|
8962
|
+
durationMs: Date.now() - t0,
|
|
8963
|
+
error: "parse error or empty file",
|
|
8964
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
8965
|
+
});
|
|
8966
|
+
return null;
|
|
8967
|
+
}
|
|
8463
8968
|
if (this.vault) {
|
|
8464
8969
|
const decrypted = decryptConfigSecrets({ sync: parsed.value }, this.vault);
|
|
8465
|
-
|
|
8970
|
+
const result = decrypted.sync ?? null;
|
|
8971
|
+
this.events?.emit("storage.read", {
|
|
8972
|
+
sessionId: "~config~",
|
|
8973
|
+
store: "config",
|
|
8974
|
+
filePath: fp,
|
|
8975
|
+
operation: "load_sync",
|
|
8976
|
+
outcome: "success",
|
|
8977
|
+
durationMs: Date.now() - t0,
|
|
8978
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
8979
|
+
});
|
|
8980
|
+
return result;
|
|
8466
8981
|
}
|
|
8982
|
+
this.events?.emit("storage.read", {
|
|
8983
|
+
sessionId: "~config~",
|
|
8984
|
+
store: "config",
|
|
8985
|
+
filePath: fp,
|
|
8986
|
+
operation: "load_sync",
|
|
8987
|
+
outcome: "success",
|
|
8988
|
+
durationMs: Date.now() - t0,
|
|
8989
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
8990
|
+
});
|
|
8467
8991
|
return parsed.value;
|
|
8468
8992
|
} catch (err) {
|
|
8469
8993
|
if (err.code === "ENOENT") return null;
|
|
8994
|
+
this.events?.emit("storage.read", {
|
|
8995
|
+
sessionId: "~config~",
|
|
8996
|
+
store: "config",
|
|
8997
|
+
filePath: fp,
|
|
8998
|
+
operation: "load_sync",
|
|
8999
|
+
outcome: "failure",
|
|
9000
|
+
durationMs: Date.now() - t0,
|
|
9001
|
+
error: storageErrorString(err),
|
|
9002
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
9003
|
+
});
|
|
8470
9004
|
console.warn(JSON.stringify({
|
|
8471
9005
|
level: "warn",
|
|
8472
9006
|
event: "config.sync_load_failed",
|
|
@@ -8478,10 +9012,21 @@ var DefaultConfigLoader = class {
|
|
|
8478
9012
|
}
|
|
8479
9013
|
async readJson(file) {
|
|
8480
9014
|
let raw;
|
|
9015
|
+
const t0 = Date.now();
|
|
8481
9016
|
try {
|
|
8482
9017
|
raw = await fsp3.readFile(file, "utf8");
|
|
8483
9018
|
} catch (err) {
|
|
8484
9019
|
if (err.code !== "ENOENT") {
|
|
9020
|
+
this.events?.emit("storage.read", {
|
|
9021
|
+
sessionId: "~config~",
|
|
9022
|
+
store: "config",
|
|
9023
|
+
filePath: file,
|
|
9024
|
+
operation: "read_json",
|
|
9025
|
+
outcome: "failure",
|
|
9026
|
+
durationMs: Date.now() - t0,
|
|
9027
|
+
error: storageErrorString(err),
|
|
9028
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
9029
|
+
});
|
|
8485
9030
|
console.warn(JSON.stringify({
|
|
8486
9031
|
level: "warn",
|
|
8487
9032
|
event: "config.read_failed",
|
|
@@ -8494,6 +9039,16 @@ var DefaultConfigLoader = class {
|
|
|
8494
9039
|
}
|
|
8495
9040
|
const parsed = safeParse(raw);
|
|
8496
9041
|
if (!parsed.ok || !parsed.value) {
|
|
9042
|
+
this.events?.emit("storage.read", {
|
|
9043
|
+
sessionId: "~config~",
|
|
9044
|
+
store: "config",
|
|
9045
|
+
filePath: file,
|
|
9046
|
+
operation: "read_json",
|
|
9047
|
+
outcome: "failure",
|
|
9048
|
+
durationMs: Date.now() - t0,
|
|
9049
|
+
error: "parse error or empty file",
|
|
9050
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
9051
|
+
});
|
|
8497
9052
|
console.warn(JSON.stringify({
|
|
8498
9053
|
level: "warn",
|
|
8499
9054
|
event: "config.parse_failed",
|
|
@@ -8969,24 +9524,66 @@ function resolveSessionLoggingConfig(cfg) {
|
|
|
8969
9524
|
|
|
8970
9525
|
// src/storage/todos-checkpoint.ts
|
|
8971
9526
|
init_atomic_write();
|
|
8972
|
-
async function loadTodosCheckpoint(filePath) {
|
|
9527
|
+
async function loadTodosCheckpoint(filePath, events, traceId) {
|
|
9528
|
+
const t0 = Date.now();
|
|
8973
9529
|
let raw;
|
|
8974
9530
|
try {
|
|
8975
9531
|
raw = await fsp3.readFile(filePath, "utf8");
|
|
8976
|
-
} catch {
|
|
9532
|
+
} catch (err) {
|
|
9533
|
+
events?.emit("storage.error", {
|
|
9534
|
+
sessionId: traceId ?? "~boot~",
|
|
9535
|
+
store: "todos",
|
|
9536
|
+
filePath,
|
|
9537
|
+
operation: "load",
|
|
9538
|
+
outcome: "failure",
|
|
9539
|
+
error: err instanceof Error ? err.message : String(err),
|
|
9540
|
+
recoverable: true
|
|
9541
|
+
});
|
|
8977
9542
|
return null;
|
|
8978
9543
|
}
|
|
8979
9544
|
try {
|
|
8980
9545
|
const parsed = JSON.parse(raw);
|
|
8981
|
-
if (parsed?.version !== 1 || !Array.isArray(parsed.todos))
|
|
9546
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.todos)) {
|
|
9547
|
+
events?.emit("storage.read", {
|
|
9548
|
+
sessionId: traceId ?? "~boot~",
|
|
9549
|
+
store: "todos",
|
|
9550
|
+
filePath,
|
|
9551
|
+
operation: "load",
|
|
9552
|
+
outcome: "failure",
|
|
9553
|
+
durationMs: Date.now() - t0,
|
|
9554
|
+
error: "invalid_schema",
|
|
9555
|
+
...traceId !== void 0 && { traceId }
|
|
9556
|
+
});
|
|
9557
|
+
return null;
|
|
9558
|
+
}
|
|
9559
|
+
events?.emit("storage.read", {
|
|
9560
|
+
sessionId: traceId ?? "~boot~",
|
|
9561
|
+
store: "todos",
|
|
9562
|
+
filePath,
|
|
9563
|
+
operation: "load",
|
|
9564
|
+
outcome: "success",
|
|
9565
|
+
durationMs: Date.now() - t0,
|
|
9566
|
+
...traceId !== void 0 && { traceId }
|
|
9567
|
+
});
|
|
8982
9568
|
return parsed.todos.filter(
|
|
8983
9569
|
(t2) => !!t2 && typeof t2.id === "string" && typeof t2.content === "string" && typeof t2.status === "string" && (t2.activeForm === void 0 || typeof t2.activeForm === "string")
|
|
8984
9570
|
);
|
|
8985
9571
|
} catch {
|
|
9572
|
+
events?.emit("storage.read", {
|
|
9573
|
+
sessionId: traceId ?? "~boot~",
|
|
9574
|
+
store: "todos",
|
|
9575
|
+
filePath,
|
|
9576
|
+
operation: "load",
|
|
9577
|
+
outcome: "failure",
|
|
9578
|
+
durationMs: Date.now() - t0,
|
|
9579
|
+
error: "parse_failed",
|
|
9580
|
+
...traceId !== void 0 && { traceId }
|
|
9581
|
+
});
|
|
8986
9582
|
return null;
|
|
8987
9583
|
}
|
|
8988
9584
|
}
|
|
8989
|
-
async function saveTodosCheckpoint(filePath, sessionId, todos) {
|
|
9585
|
+
async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId) {
|
|
9586
|
+
const t0 = Date.now();
|
|
8990
9587
|
const payload = {
|
|
8991
9588
|
version: 1,
|
|
8992
9589
|
sessionId,
|
|
@@ -8995,7 +9592,25 @@ async function saveTodosCheckpoint(filePath, sessionId, todos) {
|
|
|
8995
9592
|
};
|
|
8996
9593
|
try {
|
|
8997
9594
|
await atomicWrite(filePath, JSON.stringify(payload, null, 2), { mode: 384 });
|
|
9595
|
+
events?.emit("storage.write", {
|
|
9596
|
+
sessionId: traceId ?? sessionId,
|
|
9597
|
+
store: "todos",
|
|
9598
|
+
filePath,
|
|
9599
|
+
operation: "save",
|
|
9600
|
+
outcome: "success",
|
|
9601
|
+
durationMs: Date.now() - t0,
|
|
9602
|
+
...traceId !== void 0 && { traceId }
|
|
9603
|
+
});
|
|
8998
9604
|
} catch (err) {
|
|
9605
|
+
events?.emit("storage.error", {
|
|
9606
|
+
sessionId: traceId ?? sessionId,
|
|
9607
|
+
store: "todos",
|
|
9608
|
+
filePath,
|
|
9609
|
+
operation: "save",
|
|
9610
|
+
outcome: "failure",
|
|
9611
|
+
error: err instanceof Error ? err.message : String(err),
|
|
9612
|
+
recoverable: false
|
|
9613
|
+
});
|
|
8999
9614
|
console.warn(JSON.stringify({
|
|
9000
9615
|
level: "warn",
|
|
9001
9616
|
event: "todos_checkpoint.save_failed",
|
|
@@ -9004,12 +9619,12 @@ async function saveTodosCheckpoint(filePath, sessionId, todos) {
|
|
|
9004
9619
|
}));
|
|
9005
9620
|
}
|
|
9006
9621
|
}
|
|
9007
|
-
function attachTodosCheckpoint(state, filePath, sessionId) {
|
|
9622
|
+
function attachTodosCheckpoint(state, filePath, sessionId, events, traceId) {
|
|
9008
9623
|
let timer = null;
|
|
9009
9624
|
let pending = null;
|
|
9010
9625
|
let writeChain = Promise.resolve();
|
|
9011
9626
|
const enqueueWrite = (todos) => {
|
|
9012
|
-
writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos)).catch((err) => {
|
|
9627
|
+
writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)).catch((err) => {
|
|
9013
9628
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9014
9629
|
console.error(JSON.stringify({
|
|
9015
9630
|
level: "error",
|
|
@@ -9051,25 +9666,79 @@ function attachTodosCheckpoint(state, filePath, sessionId) {
|
|
|
9051
9666
|
|
|
9052
9667
|
// src/storage/plan-store.ts
|
|
9053
9668
|
init_atomic_write();
|
|
9054
|
-
async function loadPlan(filePath) {
|
|
9669
|
+
async function loadPlan(filePath, events) {
|
|
9670
|
+
const t0 = Date.now();
|
|
9055
9671
|
let raw;
|
|
9056
9672
|
try {
|
|
9057
9673
|
raw = await fsp3.readFile(filePath, "utf8");
|
|
9058
|
-
} catch {
|
|
9674
|
+
} catch (err) {
|
|
9675
|
+
events?.emit("storage.error", {
|
|
9676
|
+
sessionId: "~boot~",
|
|
9677
|
+
store: "plan",
|
|
9678
|
+
filePath,
|
|
9679
|
+
operation: "load",
|
|
9680
|
+
error: err instanceof Error ? err.message : String(err),
|
|
9681
|
+
recoverable: true
|
|
9682
|
+
});
|
|
9059
9683
|
return null;
|
|
9060
9684
|
}
|
|
9061
9685
|
try {
|
|
9062
9686
|
const parsed = JSON.parse(raw);
|
|
9063
|
-
if (parsed?.version !== 1 || !Array.isArray(parsed.items))
|
|
9687
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.items)) {
|
|
9688
|
+
events?.emit("storage.read", {
|
|
9689
|
+
sessionId: "~boot~",
|
|
9690
|
+
store: "plan",
|
|
9691
|
+
filePath,
|
|
9692
|
+
operation: "load",
|
|
9693
|
+
outcome: "failure",
|
|
9694
|
+
durationMs: Date.now() - t0,
|
|
9695
|
+
error: "invalid_schema"
|
|
9696
|
+
});
|
|
9697
|
+
return null;
|
|
9698
|
+
}
|
|
9699
|
+
events?.emit("storage.read", {
|
|
9700
|
+
sessionId: "~boot~",
|
|
9701
|
+
store: "plan",
|
|
9702
|
+
filePath,
|
|
9703
|
+
operation: "load",
|
|
9704
|
+
outcome: "success",
|
|
9705
|
+
durationMs: Date.now() - t0
|
|
9706
|
+
});
|
|
9064
9707
|
return parsed;
|
|
9065
9708
|
} catch {
|
|
9709
|
+
events?.emit("storage.read", {
|
|
9710
|
+
sessionId: "~boot~",
|
|
9711
|
+
store: "plan",
|
|
9712
|
+
filePath,
|
|
9713
|
+
operation: "load",
|
|
9714
|
+
outcome: "failure",
|
|
9715
|
+
durationMs: Date.now() - t0,
|
|
9716
|
+
error: "parse_failed"
|
|
9717
|
+
});
|
|
9066
9718
|
return null;
|
|
9067
9719
|
}
|
|
9068
9720
|
}
|
|
9069
|
-
async function savePlan(filePath, plan) {
|
|
9721
|
+
async function savePlan(filePath, plan, events) {
|
|
9722
|
+
const t0 = Date.now();
|
|
9070
9723
|
try {
|
|
9071
9724
|
await atomicWrite(filePath, JSON.stringify(plan, null, 2), { mode: 384 });
|
|
9725
|
+
events?.emit("storage.write", {
|
|
9726
|
+
sessionId: "~boot~",
|
|
9727
|
+
store: "plan",
|
|
9728
|
+
filePath,
|
|
9729
|
+
operation: "save",
|
|
9730
|
+
outcome: "success",
|
|
9731
|
+
durationMs: Date.now() - t0
|
|
9732
|
+
});
|
|
9072
9733
|
} catch (err) {
|
|
9734
|
+
events?.emit("storage.error", {
|
|
9735
|
+
sessionId: "~boot~",
|
|
9736
|
+
store: "plan",
|
|
9737
|
+
filePath,
|
|
9738
|
+
operation: "save",
|
|
9739
|
+
error: err instanceof Error ? err.message : String(err),
|
|
9740
|
+
recoverable: false
|
|
9741
|
+
});
|
|
9073
9742
|
console.warn(
|
|
9074
9743
|
"[plan-store] save failed:",
|
|
9075
9744
|
err instanceof Error ? err.message : String(err)
|
|
@@ -9753,7 +10422,16 @@ var DefaultPermissionPolicy = class {
|
|
|
9753
10422
|
};
|
|
9754
10423
|
}
|
|
9755
10424
|
}
|
|
9756
|
-
|
|
10425
|
+
const hasWriteCap = hasCapability(tool, ToolCapabilities.FS_WRITE);
|
|
10426
|
+
const hasShellCap = hasCapability(tool, [
|
|
10427
|
+
ToolCapabilities.SHELL_ARBITRARY,
|
|
10428
|
+
ToolCapabilities.SHELL_RESTRICTED
|
|
10429
|
+
]);
|
|
10430
|
+
const hasInstallCap = hasCapability(tool, ToolCapabilities.PACKAGE_INSTALL);
|
|
10431
|
+
const hasConfigCap = hasCapability(tool, ToolCapabilities.CONFIG_MUTATE);
|
|
10432
|
+
const hasSubagentCap = hasCapability(tool, ToolCapabilities.SUBAGENT_SPAWN);
|
|
10433
|
+
const isMutating = tool.mutating || hasWriteCap || hasShellCap || hasInstallCap || hasConfigCap || hasSubagentCap;
|
|
10434
|
+
if (tool.permission === "auto" && !isMutating) {
|
|
9757
10435
|
const decision = { permission: "auto", source: "default" };
|
|
9758
10436
|
this._evalCache.set(cacheKey, decision);
|
|
9759
10437
|
return decision;
|
|
@@ -9772,7 +10450,27 @@ var DefaultPermissionPolicy = class {
|
|
|
9772
10450
|
}
|
|
9773
10451
|
return { permission: "confirm", source: "default" };
|
|
9774
10452
|
}
|
|
10453
|
+
// Capability-based destructive check (preferred over name-based)
|
|
10454
|
+
isDestructiveByCapability(tool) {
|
|
10455
|
+
const caps = tool.capabilities ?? [];
|
|
10456
|
+
if (caps.includes("shell.arbitrary")) return true;
|
|
10457
|
+
if (caps.includes("fs.write")) return true;
|
|
10458
|
+
if (caps.includes("fs.write.outside-project")) return true;
|
|
10459
|
+
return false;
|
|
10460
|
+
}
|
|
9775
10461
|
isDestructiveYoloCall(tool, input, ctx) {
|
|
10462
|
+
if (this.isDestructiveByCapability(tool)) {
|
|
10463
|
+
if (tool.name === "bash") {
|
|
10464
|
+
const command = getInputString(input, "command");
|
|
10465
|
+
return command ? isClearlyDestructiveBashCommand(command, ctx.projectRoot) : true;
|
|
10466
|
+
}
|
|
10467
|
+
if (tool.name === "write" || tool.name === "edit" || tool.name === "replace" || tool.name === "patch") {
|
|
10468
|
+
const targetPath = getInputString(input, "path") ?? getInputString(input, "file");
|
|
10469
|
+
if (!targetPath || !ctx.projectRoot) return false;
|
|
10470
|
+
return !pathLooksInsideProject(targetPath, ctx.projectRoot);
|
|
10471
|
+
}
|
|
10472
|
+
return true;
|
|
10473
|
+
}
|
|
9776
10474
|
if (tool.name === "bash") {
|
|
9777
10475
|
const command = getInputString(input, "command");
|
|
9778
10476
|
return command ? isClearlyDestructiveBashCommand(command, ctx.projectRoot) : true;
|
|
@@ -11441,13 +12139,32 @@ var MAX_JOURNAL_ENTRIES = 500;
|
|
|
11441
12139
|
function goalFilePath(projectRoot) {
|
|
11442
12140
|
return resolveWstackPaths({ projectRoot }).projectGoal;
|
|
11443
12141
|
}
|
|
11444
|
-
async function loadGoal(filePath) {
|
|
12142
|
+
async function loadGoal(filePath, events) {
|
|
12143
|
+
const t0 = Date.now();
|
|
11445
12144
|
let raw;
|
|
11446
12145
|
try {
|
|
11447
12146
|
raw = await fsp3.readFile(filePath, "utf8");
|
|
11448
12147
|
} catch (err) {
|
|
11449
12148
|
const code = err.code;
|
|
11450
|
-
if (code === "ENOENT")
|
|
12149
|
+
if (code === "ENOENT") {
|
|
12150
|
+
events?.emit("storage.read", {
|
|
12151
|
+
sessionId: "~boot~",
|
|
12152
|
+
store: "goal",
|
|
12153
|
+
filePath,
|
|
12154
|
+
operation: "load",
|
|
12155
|
+
outcome: "success",
|
|
12156
|
+
durationMs: Date.now() - t0
|
|
12157
|
+
});
|
|
12158
|
+
return null;
|
|
12159
|
+
}
|
|
12160
|
+
events?.emit("storage.error", {
|
|
12161
|
+
sessionId: "~boot~",
|
|
12162
|
+
store: "goal",
|
|
12163
|
+
filePath,
|
|
12164
|
+
operation: "load",
|
|
12165
|
+
error: err instanceof Error ? err.message : String(err),
|
|
12166
|
+
recoverable: false
|
|
12167
|
+
});
|
|
11451
12168
|
throw err;
|
|
11452
12169
|
}
|
|
11453
12170
|
try {
|
|
@@ -11460,8 +12177,25 @@ async function loadGoal(filePath) {
|
|
|
11460
12177
|
message: "invalid schema \u2014 consider deleting and re-creating",
|
|
11461
12178
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11462
12179
|
}));
|
|
12180
|
+
events?.emit("storage.read", {
|
|
12181
|
+
sessionId: "~boot~",
|
|
12182
|
+
store: "goal",
|
|
12183
|
+
filePath,
|
|
12184
|
+
operation: "load",
|
|
12185
|
+
outcome: "failure",
|
|
12186
|
+
durationMs: Date.now() - t0,
|
|
12187
|
+
error: "invalid_schema"
|
|
12188
|
+
});
|
|
11463
12189
|
return null;
|
|
11464
12190
|
}
|
|
12191
|
+
events?.emit("storage.read", {
|
|
12192
|
+
sessionId: "~boot~",
|
|
12193
|
+
store: "goal",
|
|
12194
|
+
filePath,
|
|
12195
|
+
operation: "load",
|
|
12196
|
+
outcome: "success",
|
|
12197
|
+
durationMs: Date.now() - t0
|
|
12198
|
+
});
|
|
11465
12199
|
return parsed;
|
|
11466
12200
|
} catch {
|
|
11467
12201
|
console.warn(JSON.stringify({
|
|
@@ -11471,13 +12205,39 @@ async function loadGoal(filePath) {
|
|
|
11471
12205
|
message: "JSON parse failed \u2014 consider deleting and re-creating",
|
|
11472
12206
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11473
12207
|
}));
|
|
12208
|
+
events?.emit("storage.read", {
|
|
12209
|
+
sessionId: "~boot~",
|
|
12210
|
+
store: "goal",
|
|
12211
|
+
filePath,
|
|
12212
|
+
operation: "load",
|
|
12213
|
+
outcome: "failure",
|
|
12214
|
+
durationMs: Date.now() - t0,
|
|
12215
|
+
error: "parse_failed"
|
|
12216
|
+
});
|
|
11474
12217
|
return null;
|
|
11475
12218
|
}
|
|
11476
12219
|
}
|
|
11477
|
-
async function saveGoal(filePath, goal) {
|
|
12220
|
+
async function saveGoal(filePath, goal, events) {
|
|
12221
|
+
const t0 = Date.now();
|
|
11478
12222
|
try {
|
|
11479
12223
|
await atomicWrite(filePath, JSON.stringify(goal, null, 2), { mode: 384 });
|
|
12224
|
+
events?.emit("storage.write", {
|
|
12225
|
+
sessionId: "~boot~",
|
|
12226
|
+
store: "goal",
|
|
12227
|
+
filePath,
|
|
12228
|
+
operation: "save",
|
|
12229
|
+
outcome: "success",
|
|
12230
|
+
durationMs: Date.now() - t0
|
|
12231
|
+
});
|
|
11480
12232
|
} catch (err) {
|
|
12233
|
+
events?.emit("storage.error", {
|
|
12234
|
+
sessionId: "~boot~",
|
|
12235
|
+
store: "goal",
|
|
12236
|
+
filePath,
|
|
12237
|
+
operation: "save",
|
|
12238
|
+
error: err instanceof Error ? err.message : String(err),
|
|
12239
|
+
recoverable: false
|
|
12240
|
+
});
|
|
11481
12241
|
throw new FsError({
|
|
11482
12242
|
message: err instanceof Error ? err.message : String(err),
|
|
11483
12243
|
code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,
|
|
@@ -11923,7 +12683,7 @@ var EternalAutonomyEngine = class {
|
|
|
11923
12683
|
const emit = (stage) => {
|
|
11924
12684
|
this.opts.onStage?.(stage);
|
|
11925
12685
|
};
|
|
11926
|
-
const goal = await loadGoal(this.goalPath);
|
|
12686
|
+
const goal = await loadGoal(this.goalPath, this.opts.events);
|
|
11927
12687
|
if (!goal) {
|
|
11928
12688
|
emit({ phase: "stopped" });
|
|
11929
12689
|
this.stopRequested = true;
|
|
@@ -12027,7 +12787,7 @@ var EternalAutonomyEngine = class {
|
|
|
12027
12787
|
emit({ phase: "reflect", status, note });
|
|
12028
12788
|
let iterationIndex = 0;
|
|
12029
12789
|
try {
|
|
12030
|
-
const reloaded = await loadGoal(this.goalPath);
|
|
12790
|
+
const reloaded = await loadGoal(this.goalPath, this.opts.events);
|
|
12031
12791
|
iterationIndex = reloaded?.iterations ?? 0;
|
|
12032
12792
|
} catch {
|
|
12033
12793
|
}
|
|
@@ -12347,12 +13107,12 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
12347
13107
|
}
|
|
12348
13108
|
}
|
|
12349
13109
|
async appendIterationEntry(entry) {
|
|
12350
|
-
const current = await loadGoal(this.goalPath);
|
|
13110
|
+
const current = await loadGoal(this.goalPath, this.opts.events);
|
|
12351
13111
|
if (!current) {
|
|
12352
13112
|
return;
|
|
12353
13113
|
}
|
|
12354
13114
|
const updated = appendJournal(current, entry);
|
|
12355
|
-
await saveGoal(this.goalPath, updated);
|
|
13115
|
+
await saveGoal(this.goalPath, updated, this.opts.events);
|
|
12356
13116
|
}
|
|
12357
13117
|
/**
|
|
12358
13118
|
* Persistent per-todo failure counter. Skipped silently when the goal
|
|
@@ -12361,11 +13121,11 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
12361
13121
|
* the counter to rotate past stuck todos once they cross `todoMaxAttempts`.
|
|
12362
13122
|
*/
|
|
12363
13123
|
async bumpTodoAttempt(todoId) {
|
|
12364
|
-
const current = await loadGoal(this.goalPath);
|
|
13124
|
+
const current = await loadGoal(this.goalPath, this.opts.events);
|
|
12365
13125
|
if (!current) return;
|
|
12366
13126
|
const attempts = { ...current.todoAttempts ?? {} };
|
|
12367
13127
|
attempts[todoId] = (attempts[todoId] ?? 0) + 1;
|
|
12368
|
-
await saveGoal(this.goalPath, { ...current, todoAttempts: attempts });
|
|
13128
|
+
await saveGoal(this.goalPath, { ...current, todoAttempts: attempts }, this.opts.events);
|
|
12369
13129
|
}
|
|
12370
13130
|
/**
|
|
12371
13131
|
* Flip the mission to `completed` and journal it. Called from two
|
|
@@ -12375,7 +13135,7 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
12375
13135
|
* goal is already `completed`.
|
|
12376
13136
|
*/
|
|
12377
13137
|
async markGoalCompleted(action, note) {
|
|
12378
|
-
const current = await loadGoal(this.goalPath);
|
|
13138
|
+
const current = await loadGoal(this.goalPath, this.opts.events);
|
|
12379
13139
|
if (!current) return;
|
|
12380
13140
|
if (current.goalState === "completed") return;
|
|
12381
13141
|
const withFlag = { ...current, goalState: "completed" };
|
|
@@ -12385,7 +13145,7 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
12385
13145
|
status: "success",
|
|
12386
13146
|
note: note.slice(0, 240)
|
|
12387
13147
|
});
|
|
12388
|
-
await saveGoal(this.goalPath, withEntry);
|
|
13148
|
+
await saveGoal(this.goalPath, withEntry, this.opts.events);
|
|
12389
13149
|
this.opts.onEternalStop?.();
|
|
12390
13150
|
}
|
|
12391
13151
|
/**
|
|
@@ -12394,10 +13154,10 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
12394
13154
|
* `onEternalStop` so the REPL returns to normal mode.
|
|
12395
13155
|
*/
|
|
12396
13156
|
async clearGoalManually(note) {
|
|
12397
|
-
const current = await loadGoal(this.goalPath);
|
|
13157
|
+
const current = await loadGoal(this.goalPath, this.opts.events);
|
|
12398
13158
|
if (current) {
|
|
12399
13159
|
const abandoned = { ...current, goalState: "abandoned" };
|
|
12400
|
-
await saveGoal(this.goalPath, abandoned);
|
|
13160
|
+
await saveGoal(this.goalPath, abandoned, this.opts.events);
|
|
12401
13161
|
}
|
|
12402
13162
|
try {
|
|
12403
13163
|
const { unlink: unlink16 } = await import('fs/promises');
|
|
@@ -12473,16 +13233,16 @@ ${recentJournal}` : ""
|
|
|
12473
13233
|
* Persist a progress update from the agent's [PROGRESS: N%] output.
|
|
12474
13234
|
*/
|
|
12475
13235
|
async updateProgress(progress, note) {
|
|
12476
|
-
const current = await loadGoal(this.goalPath);
|
|
13236
|
+
const current = await loadGoal(this.goalPath, this.opts.events);
|
|
12477
13237
|
if (!current) return;
|
|
12478
13238
|
const updated = recordProgress(current, progress, note);
|
|
12479
|
-
await saveGoal(this.goalPath, updated);
|
|
13239
|
+
await saveGoal(this.goalPath, updated, this.opts.events);
|
|
12480
13240
|
}
|
|
12481
13241
|
async persistEngineState(state) {
|
|
12482
|
-
const current = await loadGoal(this.goalPath);
|
|
13242
|
+
const current = await loadGoal(this.goalPath, this.opts.events);
|
|
12483
13243
|
if (!current) return;
|
|
12484
13244
|
if (current.engineState === state) return;
|
|
12485
|
-
await saveGoal(this.goalPath, { ...current, engineState: state });
|
|
13245
|
+
await saveGoal(this.goalPath, { ...current, engineState: state }, this.opts.events);
|
|
12486
13246
|
}
|
|
12487
13247
|
};
|
|
12488
13248
|
|
|
@@ -12831,6 +13591,20 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
12831
13591
|
};
|
|
12832
13592
|
|
|
12833
13593
|
// src/coordination/agent-subagent-runner.ts
|
|
13594
|
+
function withDisabledToolFiltering(factory) {
|
|
13595
|
+
return async (config) => {
|
|
13596
|
+
const result = await factory(config);
|
|
13597
|
+
const disabled = config.disabledTools ?? [];
|
|
13598
|
+
if (disabled.length === 0) return result;
|
|
13599
|
+
const registry = result.agent.tools;
|
|
13600
|
+
if (registry && typeof registry.unregister === "function") {
|
|
13601
|
+
for (const toolName of disabled) {
|
|
13602
|
+
registry.unregister(toolName);
|
|
13603
|
+
}
|
|
13604
|
+
}
|
|
13605
|
+
return result;
|
|
13606
|
+
};
|
|
13607
|
+
}
|
|
12834
13608
|
function makeAgentSubagentRunner(opts) {
|
|
12835
13609
|
const format = opts.formatTaskInput ?? defaultFormatTaskInput;
|
|
12836
13610
|
return async (task, ctx) => {
|
|
@@ -16997,7 +17771,8 @@ var ParallelEternalEngine = class {
|
|
|
16997
17771
|
doneCondition: { type: "all_tasks_done" }
|
|
16998
17772
|
};
|
|
16999
17773
|
this.coordinator = new DefaultMultiAgentCoordinator(config);
|
|
17000
|
-
const
|
|
17774
|
+
const filteredFactory = withDisabledToolFiltering(this.agentFactory);
|
|
17775
|
+
const runner = makeAgentSubagentRunner({ factory: filteredFactory });
|
|
17001
17776
|
this.coordinator.setRunner?.(runner);
|
|
17002
17777
|
try {
|
|
17003
17778
|
while (!this.stopRequested) {
|
|
@@ -17032,7 +17807,7 @@ var ParallelEternalEngine = class {
|
|
|
17032
17807
|
this.opts.onStage?.(stage);
|
|
17033
17808
|
};
|
|
17034
17809
|
this.iterations++;
|
|
17035
|
-
const goal = await loadGoal(this.goalPath);
|
|
17810
|
+
const goal = await loadGoal(this.goalPath, this.opts.events);
|
|
17036
17811
|
if (!goal) {
|
|
17037
17812
|
this.stopRequested = true;
|
|
17038
17813
|
emit({ phase: "stopped" });
|
|
@@ -17050,7 +17825,8 @@ var ParallelEternalEngine = class {
|
|
|
17050
17825
|
doneCondition: { type: "all_tasks_done" }
|
|
17051
17826
|
};
|
|
17052
17827
|
this.coordinator = new DefaultMultiAgentCoordinator(config);
|
|
17053
|
-
const
|
|
17828
|
+
const filteredFactory = withDisabledToolFiltering(this.agentFactory);
|
|
17829
|
+
const runner = makeAgentSubagentRunner({ factory: filteredFactory });
|
|
17054
17830
|
this.coordinator.setRunner?.(runner);
|
|
17055
17831
|
}
|
|
17056
17832
|
emit({ phase: "decompose" });
|
|
@@ -17159,13 +17935,17 @@ ${personaLine}Task: ${task}
|
|
|
17159
17935
|
role: route.role,
|
|
17160
17936
|
tools: route.definition.config.tools,
|
|
17161
17937
|
systemPromptOverride: route.definition.config.prompt,
|
|
17162
|
-
timeoutMs: this.timeoutMs
|
|
17938
|
+
timeoutMs: this.timeoutMs,
|
|
17939
|
+
// Disable delegation — subagents are leaf workers, not orchestrators
|
|
17940
|
+
disabledTools: ["delegate"]
|
|
17163
17941
|
} : {
|
|
17164
17942
|
id: subagentId,
|
|
17165
17943
|
name: `slot-${subagentId.slice(-6)}`,
|
|
17166
17944
|
// Let the coordinator apply its default budget (roster or generic).
|
|
17167
17945
|
// Hardcoding low limits here defeats the x10 budget improvement.
|
|
17168
|
-
timeoutMs: this.timeoutMs
|
|
17946
|
+
timeoutMs: this.timeoutMs,
|
|
17947
|
+
// Disable delegation — subagents are leaf workers, not orchestrators
|
|
17948
|
+
disabledTools: ["delegate"]
|
|
17169
17949
|
}
|
|
17170
17950
|
);
|
|
17171
17951
|
subagentIds.push(subagentId);
|
|
@@ -17311,10 +18091,10 @@ ${lastFew}` : "No prior iterations.",
|
|
|
17311
18091
|
// Helpers
|
|
17312
18092
|
// -------------------------------------------------------------------------
|
|
17313
18093
|
async appendIterationEntry(entry) {
|
|
17314
|
-
const current = await loadGoal(this.goalPath);
|
|
18094
|
+
const current = await loadGoal(this.goalPath, this.opts.events);
|
|
17315
18095
|
if (!current) return;
|
|
17316
18096
|
const updated = appendJournal(current, entry);
|
|
17317
|
-
await saveGoal(this.goalPath, updated);
|
|
18097
|
+
await saveGoal(this.goalPath, updated, this.opts.events);
|
|
17318
18098
|
const entryWithMeta = {
|
|
17319
18099
|
at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
17320
18100
|
iteration: updated.iterations,
|
|
@@ -17326,10 +18106,10 @@ ${lastFew}` : "No prior iterations.",
|
|
|
17326
18106
|
await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
|
|
17327
18107
|
}
|
|
17328
18108
|
async persistState(state) {
|
|
17329
|
-
const current = await loadGoal(this.goalPath);
|
|
18109
|
+
const current = await loadGoal(this.goalPath, this.opts.events);
|
|
17330
18110
|
if (!current) return;
|
|
17331
18111
|
if (current.engineState === state) return;
|
|
17332
|
-
await saveGoal(this.goalPath, { ...current, engineState: state });
|
|
18112
|
+
await saveGoal(this.goalPath, { ...current, engineState: state }, this.opts.events);
|
|
17333
18113
|
}
|
|
17334
18114
|
};
|
|
17335
18115
|
|
|
@@ -18109,7 +18889,12 @@ Working rules:
|
|
|
18109
18889
|
6. Never claim a subagent's work as your own without verifying it. If a
|
|
18110
18890
|
result looks wrong, ask_subagent for clarification before passing it
|
|
18111
18891
|
to the user.
|
|
18112
|
-
7.
|
|
18892
|
+
7. **Act on subagent mail immediately**. Subagent messages (result, ask,
|
|
18893
|
+
assign, note) are injected inline before every step \u2014 even mid-task.
|
|
18894
|
+
When you see one, address it before continuing: reply to asks, factor
|
|
18895
|
+
in results, act on assignments. Use \`mailbox action=ack\` to mark
|
|
18896
|
+
completed messages.
|
|
18897
|
+
8. Wind down when satisfied. When the results are good enough, call
|
|
18113
18898
|
work_complete \u2014 no new subagents will spawn and queued tasks complete
|
|
18114
18899
|
as aborted. Running subagents finish naturally. Call terminate_subagent
|
|
18115
18900
|
only for ones you need to stop immediately.`;
|
|
@@ -18126,6 +18911,13 @@ Bridge contract:
|
|
|
18126
18911
|
structured, and self-contained \u2014 assume the Director will paste your
|
|
18127
18912
|
output into its own context.
|
|
18128
18913
|
|
|
18914
|
+
CRITICAL CONSTRAINT \u2014 NO FURTHER DELEGATION:
|
|
18915
|
+
- You MUST NOT call the \`delegate\` tool or attempt to spawn subagents.
|
|
18916
|
+
- You MUST NOT use \`spawn_subagent\`, \`assign_task\`, or any equivalent.
|
|
18917
|
+
- Your role is to execute the assigned task yourself, not to orchestrate.
|
|
18918
|
+
- If a subtask is too complex, report back to the Director with what you
|
|
18919
|
+
found and let the Director decide how to decompose.
|
|
18920
|
+
|
|
18129
18921
|
Inter-agent mailbox (if you have the \`mail_send\`/\`mail_inbox\`/\`mailbox\` tools):
|
|
18130
18922
|
- You are part of a project-wide fleet that may span other terminals and
|
|
18131
18923
|
WebUIs. Your mailbox identity is \`<your-name>@<session-tag>\` (unique
|
|
@@ -18140,7 +18932,12 @@ Inter-agent mailbox (if you have the \`mail_send\`/\`mail_inbox\`/\`mailbox\` to
|
|
|
18140
18932
|
their exact id instead of doing everything yourself. Discover ids with
|
|
18141
18933
|
\`mailbox action=online\`.
|
|
18142
18934
|
- Answer your mail: reply to the sender's exact \`from\` id. When done with
|
|
18143
|
-
an assigned task, post a \`result\` back to whoever assigned it
|
|
18935
|
+
an assigned task, post a \`result\` back to whoever assigned it.
|
|
18936
|
+
- **Mail to the leader is always seen**: when you send \`ask\`, \`result\`,
|
|
18937
|
+
or \`assign\` to the director/leader, the message is injected inline into
|
|
18938
|
+
the leader's conversation before their next step \u2014 even if the leader is
|
|
18939
|
+
mid-task. Use \`mail_send\` to reliably reach the leader instead of
|
|
18940
|
+
waiting for them to check in.`;
|
|
18144
18941
|
function composeDirectorPrompt(parts = {}) {
|
|
18145
18942
|
const sections = [];
|
|
18146
18943
|
const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
|
|
@@ -20549,6 +21346,7 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
20549
21346
|
}
|
|
20550
21347
|
function makeDirectorSessionFactory(opts) {
|
|
20551
21348
|
const runId = opts.directorRunId ?? `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-director`;
|
|
21349
|
+
const { traceId } = opts;
|
|
20552
21350
|
let store;
|
|
20553
21351
|
let dir;
|
|
20554
21352
|
if (opts.store) {
|
|
@@ -20564,12 +21362,16 @@ function makeDirectorSessionFactory(opts) {
|
|
|
20564
21362
|
dir,
|
|
20565
21363
|
directorRunId: runId,
|
|
20566
21364
|
async createSubagentSession({ subagentId, provider, model, title }) {
|
|
20567
|
-
|
|
21365
|
+
const writer = await store.create({
|
|
20568
21366
|
id: subagentId,
|
|
20569
21367
|
title: title ?? subagentId,
|
|
20570
21368
|
provider: provider ?? "unknown",
|
|
20571
21369
|
model: model ?? "unknown"
|
|
20572
21370
|
});
|
|
21371
|
+
if (traceId !== void 0) {
|
|
21372
|
+
writer.traceId = traceId;
|
|
21373
|
+
}
|
|
21374
|
+
return writer;
|
|
20573
21375
|
}
|
|
20574
21376
|
};
|
|
20575
21377
|
}
|
|
@@ -23288,7 +24090,9 @@ var SddParallelRun = class {
|
|
|
23288
24090
|
doneCondition: { type: "all_tasks_done" }
|
|
23289
24091
|
};
|
|
23290
24092
|
this.coordinator = new DefaultMultiAgentCoordinator(config);
|
|
23291
|
-
const
|
|
24093
|
+
const baseFactory = this.opts.subagentFactory ?? this.defaultFactory();
|
|
24094
|
+
const filteredFactory = withDisabledToolFiltering(baseFactory);
|
|
24095
|
+
const runner = makeAgentSubagentRunner({ factory: filteredFactory });
|
|
23292
24096
|
this.coordinator.setRunner?.(runner);
|
|
23293
24097
|
}
|
|
23294
24098
|
defaultFactory() {
|
|
@@ -23330,7 +24134,9 @@ var SddParallelRun = class {
|
|
|
23330
24134
|
id: subagentId,
|
|
23331
24135
|
name: subagentId,
|
|
23332
24136
|
role: "executor",
|
|
23333
|
-
timeoutMs: this.timeoutMs
|
|
24137
|
+
timeoutMs: this.timeoutMs,
|
|
24138
|
+
// Disable delegation — subagents are leaf workers, not orchestrators
|
|
24139
|
+
disabledTools: ["delegate"]
|
|
23334
24140
|
})
|
|
23335
24141
|
);
|
|
23336
24142
|
const spawnResults = await Promise.all(spawns);
|
|
@@ -25417,20 +26223,53 @@ var MAX_TEXT_LENGTH = 2e3;
|
|
|
25417
26223
|
var MAX_ANNOTATIONS = 1e3;
|
|
25418
26224
|
var AnnotationsStore = class {
|
|
25419
26225
|
dir;
|
|
26226
|
+
events;
|
|
26227
|
+
traceId;
|
|
25420
26228
|
/** Per-session write queue. Created lazily on first add. */
|
|
25421
26229
|
writeChains = /* @__PURE__ */ new Map();
|
|
25422
26230
|
constructor(opts) {
|
|
25423
26231
|
this.dir = opts.dir;
|
|
26232
|
+
this.events = opts.events;
|
|
26233
|
+
this.traceId = opts.traceId;
|
|
25424
26234
|
}
|
|
25425
26235
|
// ── Reads ──────────────────────────────────────────────────────────────
|
|
25426
26236
|
/**
|
|
25427
26237
|
* Return all annotations for `sessionId` in insertion order
|
|
25428
26238
|
* (oldest first). Returns an empty array when no file exists
|
|
25429
|
-
* yet (the normal case for a fresh session)
|
|
26239
|
+
* yet (the normal case for a fresh session) and also degrades
|
|
26240
|
+
* gracefully to `[]` on a read error (permissions, corruption) —
|
|
26241
|
+
* the failure is still surfaced via a `storage.read` event so it
|
|
26242
|
+
* never silently hides I/O problems from observers.
|
|
25430
26243
|
*/
|
|
25431
26244
|
async list(sessionId) {
|
|
25432
|
-
const
|
|
25433
|
-
|
|
26245
|
+
const t0 = Date.now();
|
|
26246
|
+
const fp = this.filePath(sessionId);
|
|
26247
|
+
try {
|
|
26248
|
+
const file = await this.readFile(sessionId);
|
|
26249
|
+
const durationMs = Date.now() - t0;
|
|
26250
|
+
this.events?.emit("storage.read", {
|
|
26251
|
+
sessionId,
|
|
26252
|
+
store: "annotations",
|
|
26253
|
+
filePath: fp,
|
|
26254
|
+
operation: "list",
|
|
26255
|
+
outcome: "success",
|
|
26256
|
+
durationMs,
|
|
26257
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26258
|
+
});
|
|
26259
|
+
return file ? file.annotations : [];
|
|
26260
|
+
} catch (err) {
|
|
26261
|
+
this.events?.emit("storage.read", {
|
|
26262
|
+
sessionId,
|
|
26263
|
+
store: "annotations",
|
|
26264
|
+
filePath: fp,
|
|
26265
|
+
operation: "list",
|
|
26266
|
+
outcome: "failure",
|
|
26267
|
+
durationMs: Date.now() - t0,
|
|
26268
|
+
error: err instanceof Error ? err.message : String(err),
|
|
26269
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26270
|
+
});
|
|
26271
|
+
return [];
|
|
26272
|
+
}
|
|
25434
26273
|
}
|
|
25435
26274
|
/**
|
|
25436
26275
|
* Convenience: only unresolved annotations, newest first — the
|
|
@@ -25482,25 +26321,62 @@ var AnnotationsStore = class {
|
|
|
25482
26321
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25483
26322
|
resolved: false
|
|
25484
26323
|
};
|
|
25485
|
-
|
|
25486
|
-
|
|
25487
|
-
|
|
25488
|
-
|
|
25489
|
-
|
|
25490
|
-
const
|
|
25491
|
-
|
|
25492
|
-
|
|
25493
|
-
|
|
25494
|
-
|
|
25495
|
-
|
|
25496
|
-
|
|
25497
|
-
|
|
25498
|
-
|
|
25499
|
-
|
|
25500
|
-
|
|
26324
|
+
const fp = this.filePath(input.sessionId);
|
|
26325
|
+
const t0 = Date.now();
|
|
26326
|
+
try {
|
|
26327
|
+
await this.enqueue(input.sessionId, async () => {
|
|
26328
|
+
await withFileLock(fp, async () => {
|
|
26329
|
+
const all = await this.list(input.sessionId);
|
|
26330
|
+
all.push(annotation);
|
|
26331
|
+
if (all.length > MAX_ANNOTATIONS) {
|
|
26332
|
+
const sorted = all.map((a, i) => ({ a, i })).sort((x, y) => {
|
|
26333
|
+
if (x.a.resolved !== y.a.resolved) return x.a.resolved ? 1 : -1;
|
|
26334
|
+
return x.a.createdAt.localeCompare(y.a.createdAt);
|
|
26335
|
+
});
|
|
26336
|
+
const evictCount = all.length - MAX_ANNOTATIONS;
|
|
26337
|
+
const toEvict = new Set(sorted.slice(0, evictCount).map((s) => s.a.id));
|
|
26338
|
+
const kept = all.filter((a) => !toEvict.has(a.id));
|
|
26339
|
+
await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: kept });
|
|
26340
|
+
const durationMs = Date.now() - t0;
|
|
26341
|
+
this.events?.emit("storage.write", {
|
|
26342
|
+
sessionId: input.sessionId,
|
|
26343
|
+
store: "annotations",
|
|
26344
|
+
filePath: fp,
|
|
26345
|
+
operation: "evict",
|
|
26346
|
+
outcome: "success",
|
|
26347
|
+
durationMs,
|
|
26348
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26349
|
+
});
|
|
26350
|
+
} else {
|
|
26351
|
+
await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
|
|
26352
|
+
const durationMs = Date.now() - t0;
|
|
26353
|
+
this.events?.emit("storage.write", {
|
|
26354
|
+
sessionId: input.sessionId,
|
|
26355
|
+
store: "annotations",
|
|
26356
|
+
filePath: fp,
|
|
26357
|
+
operation: "add",
|
|
26358
|
+
outcome: "success",
|
|
26359
|
+
durationMs,
|
|
26360
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26361
|
+
});
|
|
26362
|
+
}
|
|
26363
|
+
});
|
|
25501
26364
|
});
|
|
25502
|
-
|
|
25503
|
-
|
|
26365
|
+
return annotation;
|
|
26366
|
+
} catch (err) {
|
|
26367
|
+
this.events?.emit("storage.error", {
|
|
26368
|
+
sessionId: input.sessionId,
|
|
26369
|
+
store: "annotations",
|
|
26370
|
+
filePath: fp,
|
|
26371
|
+
operation: "add",
|
|
26372
|
+
outcome: "failure",
|
|
26373
|
+
error: err instanceof Error ? err.message : String(err),
|
|
26374
|
+
recoverable: false,
|
|
26375
|
+
durationMs: Date.now() - t0,
|
|
26376
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26377
|
+
});
|
|
26378
|
+
throw err;
|
|
26379
|
+
}
|
|
25504
26380
|
}
|
|
25505
26381
|
/**
|
|
25506
26382
|
* Mark an annotation as resolved. Returns the updated record, or
|
|
@@ -25510,26 +26386,53 @@ var AnnotationsStore = class {
|
|
|
25510
26386
|
*/
|
|
25511
26387
|
async resolve(input) {
|
|
25512
26388
|
let updated = null;
|
|
25513
|
-
|
|
25514
|
-
|
|
25515
|
-
|
|
25516
|
-
|
|
25517
|
-
|
|
25518
|
-
|
|
25519
|
-
|
|
25520
|
-
|
|
25521
|
-
|
|
25522
|
-
|
|
25523
|
-
|
|
25524
|
-
|
|
25525
|
-
|
|
25526
|
-
|
|
25527
|
-
|
|
25528
|
-
|
|
25529
|
-
|
|
26389
|
+
const fp = this.filePath(input.sessionId);
|
|
26390
|
+
const t0 = Date.now();
|
|
26391
|
+
try {
|
|
26392
|
+
await this.enqueue(input.sessionId, async () => {
|
|
26393
|
+
await withFileLock(fp, async () => {
|
|
26394
|
+
const all = await this.list(input.sessionId);
|
|
26395
|
+
const idx = all.findIndex((a) => a.id === input.annotationId);
|
|
26396
|
+
if (idx === -1) {
|
|
26397
|
+
updated = null;
|
|
26398
|
+
return;
|
|
26399
|
+
}
|
|
26400
|
+
const next = {
|
|
26401
|
+
...expectDefined(all[idx]),
|
|
26402
|
+
resolved: true,
|
|
26403
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26404
|
+
resolvedBy: input.resolvedBy
|
|
26405
|
+
};
|
|
26406
|
+
all[idx] = next;
|
|
26407
|
+
await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
|
|
26408
|
+
updated = next;
|
|
26409
|
+
const durationMs = Date.now() - t0;
|
|
26410
|
+
this.events?.emit("storage.write", {
|
|
26411
|
+
sessionId: input.sessionId,
|
|
26412
|
+
store: "annotations",
|
|
26413
|
+
filePath: fp,
|
|
26414
|
+
operation: "resolve",
|
|
26415
|
+
outcome: "success",
|
|
26416
|
+
durationMs,
|
|
26417
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26418
|
+
});
|
|
26419
|
+
});
|
|
25530
26420
|
});
|
|
25531
|
-
|
|
25532
|
-
|
|
26421
|
+
return updated;
|
|
26422
|
+
} catch (err) {
|
|
26423
|
+
this.events?.emit("storage.error", {
|
|
26424
|
+
sessionId: input.sessionId,
|
|
26425
|
+
store: "annotations",
|
|
26426
|
+
filePath: fp,
|
|
26427
|
+
operation: "resolve",
|
|
26428
|
+
outcome: "failure",
|
|
26429
|
+
error: err instanceof Error ? err.message : String(err),
|
|
26430
|
+
recoverable: false,
|
|
26431
|
+
durationMs: Date.now() - t0,
|
|
26432
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26433
|
+
});
|
|
26434
|
+
throw err;
|
|
26435
|
+
}
|
|
25533
26436
|
}
|
|
25534
26437
|
// ── Internals ──────────────────────────────────────────────────────────
|
|
25535
26438
|
filePath(sessionId) {
|
|
@@ -25537,16 +26440,21 @@ var AnnotationsStore = class {
|
|
|
25537
26440
|
}
|
|
25538
26441
|
async readFile(sessionId) {
|
|
25539
26442
|
const fp = this.filePath(sessionId);
|
|
26443
|
+
let raw;
|
|
26444
|
+
try {
|
|
26445
|
+
raw = await fsp3.readFile(fp, "utf8");
|
|
26446
|
+
} catch (err) {
|
|
26447
|
+
if (err.code === "ENOENT") return null;
|
|
26448
|
+
throw err;
|
|
26449
|
+
}
|
|
25540
26450
|
try {
|
|
25541
|
-
const raw = await fsp3.readFile(fp, "utf8");
|
|
25542
26451
|
const parsed = JSON.parse(raw);
|
|
25543
26452
|
if (parsed.version !== FILE_VERSION) {
|
|
25544
26453
|
return { version: FILE_VERSION, annotations: [] };
|
|
25545
26454
|
}
|
|
25546
26455
|
return parsed;
|
|
25547
|
-
} catch
|
|
25548
|
-
|
|
25549
|
-
return { version: FILE_VERSION, annotations: [] };
|
|
26456
|
+
} catch {
|
|
26457
|
+
return null;
|
|
25550
26458
|
}
|
|
25551
26459
|
}
|
|
25552
26460
|
async writeFile(sessionId, file) {
|
|
@@ -25603,9 +26511,20 @@ function hashRequest(request) {
|
|
|
25603
26511
|
const digest = createHash("sha256").update(json, "utf8").digest("hex");
|
|
25604
26512
|
return `sha256:${digest}`;
|
|
25605
26513
|
}
|
|
26514
|
+
|
|
26515
|
+
// src/storage/replay-log-store.ts
|
|
26516
|
+
function storageErrorString2(err) {
|
|
26517
|
+
if (err instanceof Error) {
|
|
26518
|
+
const code = err.code;
|
|
26519
|
+
return code ? `${code}: ${err.message}` : err.message;
|
|
26520
|
+
}
|
|
26521
|
+
return String(err);
|
|
26522
|
+
}
|
|
25606
26523
|
var DEFAULT_MAX_ENTRIES = 1e3;
|
|
25607
26524
|
var ReplayLogStore = class {
|
|
25608
26525
|
dir;
|
|
26526
|
+
events;
|
|
26527
|
+
traceId;
|
|
25609
26528
|
writeChains = /* @__PURE__ */ new Map();
|
|
25610
26529
|
/** Per-session hash → entry index, kept in memory after the first load. */
|
|
25611
26530
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -25614,6 +26533,8 @@ var ReplayLogStore = class {
|
|
|
25614
26533
|
maxEntries;
|
|
25615
26534
|
constructor(opts) {
|
|
25616
26535
|
this.dir = opts.dir;
|
|
26536
|
+
this.events = opts.events;
|
|
26537
|
+
this.traceId = opts.traceId;
|
|
25617
26538
|
this.maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
25618
26539
|
}
|
|
25619
26540
|
// ── Writes ──────────────────────────────────────────────────────────────
|
|
@@ -25624,38 +26545,43 @@ var ReplayLogStore = class {
|
|
|
25624
26545
|
*/
|
|
25625
26546
|
async record(input) {
|
|
25626
26547
|
const hash = hashRequest(input.request);
|
|
25627
|
-
|
|
25628
|
-
|
|
25629
|
-
|
|
25630
|
-
|
|
25631
|
-
|
|
25632
|
-
|
|
25633
|
-
|
|
25634
|
-
|
|
25635
|
-
|
|
25636
|
-
|
|
25637
|
-
|
|
25638
|
-
|
|
25639
|
-
|
|
25640
|
-
|
|
25641
|
-
|
|
25642
|
-
|
|
26548
|
+
const fp = this.filePath(input.sessionId);
|
|
26549
|
+
const t0 = Date.now();
|
|
26550
|
+
try {
|
|
26551
|
+
await this.enqueue(input.sessionId, async () => {
|
|
26552
|
+
await withFileLock(this.filePath(input.sessionId), async () => {
|
|
26553
|
+
const entries = await this.readAll(input.sessionId);
|
|
26554
|
+
if (entries.some((entry2) => entry2.hash === hash)) return;
|
|
26555
|
+
const entry = {
|
|
26556
|
+
hash,
|
|
26557
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26558
|
+
request: input.request,
|
|
26559
|
+
response: input.response
|
|
26560
|
+
};
|
|
26561
|
+
entries.push(entry);
|
|
26562
|
+
const keep = entries.slice(-this.maxEntries);
|
|
26563
|
+
const didEvict = keep.length < entries.length;
|
|
26564
|
+
const cache = /* @__PURE__ */ new Map();
|
|
26565
|
+
for (const e of keep) cache.set(e.hash, e);
|
|
26566
|
+
this.cache.set(input.sessionId, cache);
|
|
26567
|
+
await this.writeAll(input.sessionId, keep, didEvict ? "compact" : "record");
|
|
26568
|
+
});
|
|
25643
26569
|
});
|
|
25644
|
-
|
|
25645
|
-
|
|
25646
|
-
|
|
25647
|
-
|
|
25648
|
-
|
|
25649
|
-
|
|
25650
|
-
|
|
25651
|
-
|
|
25652
|
-
|
|
25653
|
-
|
|
25654
|
-
|
|
25655
|
-
|
|
25656
|
-
|
|
25657
|
-
|
|
25658
|
-
|
|
26570
|
+
return hash;
|
|
26571
|
+
} catch (err) {
|
|
26572
|
+
this.events?.emit("storage.error", {
|
|
26573
|
+
sessionId: input.sessionId,
|
|
26574
|
+
store: "replay",
|
|
26575
|
+
filePath: fp,
|
|
26576
|
+
operation: "record",
|
|
26577
|
+
outcome: "failure",
|
|
26578
|
+
error: storageErrorString2(err),
|
|
26579
|
+
recoverable: false,
|
|
26580
|
+
durationMs: Date.now() - t0,
|
|
26581
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26582
|
+
});
|
|
26583
|
+
throw err;
|
|
26584
|
+
}
|
|
25659
26585
|
}
|
|
25660
26586
|
// ── Reads ───────────────────────────────────────────────────────────────
|
|
25661
26587
|
/**
|
|
@@ -25664,13 +26590,65 @@ var ReplayLogStore = class {
|
|
|
25664
26590
|
* per session (in-memory cache).
|
|
25665
26591
|
*/
|
|
25666
26592
|
async lookup(sessionId, hash) {
|
|
25667
|
-
const
|
|
25668
|
-
|
|
26593
|
+
const fp = this.filePath(sessionId);
|
|
26594
|
+
const t0 = Date.now();
|
|
26595
|
+
try {
|
|
26596
|
+
const cache = await this.ensureCache(sessionId);
|
|
26597
|
+
this.events?.emit("storage.read", {
|
|
26598
|
+
sessionId,
|
|
26599
|
+
store: "replay",
|
|
26600
|
+
filePath: fp,
|
|
26601
|
+
operation: "lookup",
|
|
26602
|
+
outcome: "success",
|
|
26603
|
+
durationMs: Date.now() - t0,
|
|
26604
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26605
|
+
});
|
|
26606
|
+
return cache.get(hash) ?? null;
|
|
26607
|
+
} catch (err) {
|
|
26608
|
+
this.events?.emit("storage.read", {
|
|
26609
|
+
sessionId,
|
|
26610
|
+
store: "replay",
|
|
26611
|
+
filePath: fp,
|
|
26612
|
+
operation: "lookup",
|
|
26613
|
+
outcome: "failure",
|
|
26614
|
+
durationMs: Date.now() - t0,
|
|
26615
|
+
error: storageErrorString2(err),
|
|
26616
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26617
|
+
});
|
|
26618
|
+
throw err;
|
|
26619
|
+
}
|
|
25669
26620
|
}
|
|
25670
26621
|
/** All recorded entries for a session, in insertion order. */
|
|
25671
26622
|
async load(sessionId) {
|
|
25672
|
-
const
|
|
25673
|
-
|
|
26623
|
+
const fp = this.filePath(sessionId);
|
|
26624
|
+
const t0 = Date.now();
|
|
26625
|
+
try {
|
|
26626
|
+
const cache = await this.ensureCache(sessionId);
|
|
26627
|
+
const durationMs = Date.now() - t0;
|
|
26628
|
+
this.events?.emit("storage.read", {
|
|
26629
|
+
sessionId,
|
|
26630
|
+
store: "replay",
|
|
26631
|
+
filePath: fp,
|
|
26632
|
+
operation: "load",
|
|
26633
|
+
outcome: "success",
|
|
26634
|
+
durationMs,
|
|
26635
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26636
|
+
});
|
|
26637
|
+
return [...cache.values()];
|
|
26638
|
+
} catch (err) {
|
|
26639
|
+
const durationMs = Date.now() - t0;
|
|
26640
|
+
this.events?.emit("storage.read", {
|
|
26641
|
+
sessionId,
|
|
26642
|
+
store: "replay",
|
|
26643
|
+
filePath: fp,
|
|
26644
|
+
operation: "load",
|
|
26645
|
+
outcome: "failure",
|
|
26646
|
+
durationMs,
|
|
26647
|
+
error: storageErrorString2(err),
|
|
26648
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26649
|
+
});
|
|
26650
|
+
throw err;
|
|
26651
|
+
}
|
|
25674
26652
|
}
|
|
25675
26653
|
/**
|
|
25676
26654
|
* List every session id that has a replay log in the store dir.
|
|
@@ -25740,13 +26718,24 @@ var ReplayLogStore = class {
|
|
|
25740
26718
|
return out;
|
|
25741
26719
|
} catch (err) {
|
|
25742
26720
|
if (err.code === "ENOENT") return [];
|
|
25743
|
-
|
|
26721
|
+
throw err;
|
|
25744
26722
|
}
|
|
25745
26723
|
}
|
|
25746
|
-
async writeAll(sessionId, entries) {
|
|
26724
|
+
async writeAll(sessionId, entries, operation = "record") {
|
|
25747
26725
|
const fp = this.filePath(sessionId);
|
|
26726
|
+
const t0 = Date.now();
|
|
25748
26727
|
const body = entries.map((e) => JSON.stringify(e)).join("\n") + (entries.length ? "\n" : "");
|
|
25749
26728
|
await atomicWrite(fp, body);
|
|
26729
|
+
const durationMs = Date.now() - t0;
|
|
26730
|
+
this.events?.emit("storage.write", {
|
|
26731
|
+
sessionId,
|
|
26732
|
+
store: "replay",
|
|
26733
|
+
filePath: fp,
|
|
26734
|
+
operation,
|
|
26735
|
+
outcome: "success",
|
|
26736
|
+
durationMs,
|
|
26737
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26738
|
+
});
|
|
25750
26739
|
}
|
|
25751
26740
|
async ensureCache(sessionId) {
|
|
25752
26741
|
let cache = this.cache.get(sessionId);
|
|
@@ -25927,6 +26916,8 @@ var GENESIS_PREV = "0".repeat(64);
|
|
|
25927
26916
|
var DEFAULT_FSYNC_EVERY = 100;
|
|
25928
26917
|
var ToolAuditLog = class {
|
|
25929
26918
|
dir;
|
|
26919
|
+
events;
|
|
26920
|
+
traceId;
|
|
25930
26921
|
/** In-memory cache of the last entry's hash (per session), to compute chains efficiently. */
|
|
25931
26922
|
tailHash = /* @__PURE__ */ new Map();
|
|
25932
26923
|
/** In-memory counter for entry indices — avoids re-reading the file on every write. */
|
|
@@ -25937,6 +26928,8 @@ var ToolAuditLog = class {
|
|
|
25937
26928
|
fsyncEvery;
|
|
25938
26929
|
constructor(opts) {
|
|
25939
26930
|
this.dir = opts.dir;
|
|
26931
|
+
this.events = opts.events;
|
|
26932
|
+
this.traceId = opts.traceId;
|
|
25940
26933
|
this.fsyncEvery = opts.fsyncEvery ?? DEFAULT_FSYNC_EVERY;
|
|
25941
26934
|
}
|
|
25942
26935
|
/**
|
|
@@ -25947,96 +26940,180 @@ var ToolAuditLog = class {
|
|
|
25947
26940
|
*/
|
|
25948
26941
|
async record(input) {
|
|
25949
26942
|
let entry;
|
|
25950
|
-
|
|
25951
|
-
|
|
25952
|
-
|
|
25953
|
-
|
|
25954
|
-
|
|
25955
|
-
|
|
25956
|
-
|
|
25957
|
-
|
|
25958
|
-
|
|
25959
|
-
id
|
|
25960
|
-
ts
|
|
25961
|
-
|
|
25962
|
-
|
|
25963
|
-
|
|
25964
|
-
|
|
25965
|
-
|
|
25966
|
-
|
|
25967
|
-
|
|
25968
|
-
|
|
25969
|
-
|
|
25970
|
-
|
|
25971
|
-
|
|
25972
|
-
|
|
25973
|
-
|
|
25974
|
-
|
|
25975
|
-
|
|
25976
|
-
|
|
25977
|
-
|
|
25978
|
-
|
|
25979
|
-
|
|
25980
|
-
|
|
25981
|
-
|
|
25982
|
-
|
|
25983
|
-
|
|
25984
|
-
|
|
25985
|
-
|
|
26943
|
+
const fp = this.filePath(input.sessionId);
|
|
26944
|
+
const t0 = Date.now();
|
|
26945
|
+
try {
|
|
26946
|
+
await this.enqueue(input.sessionId, async () => {
|
|
26947
|
+
await withFileLock(fp, async () => {
|
|
26948
|
+
const entries = await this.readAll(input.sessionId);
|
|
26949
|
+
const prev = entries.at(-1);
|
|
26950
|
+
const prevHash = prev?.hash ?? GENESIS_PREV;
|
|
26951
|
+
const index = prev ? prev.index + 1 : 0;
|
|
26952
|
+
const id = randomUUID();
|
|
26953
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
26954
|
+
const content = {
|
|
26955
|
+
id,
|
|
26956
|
+
ts,
|
|
26957
|
+
prevHash,
|
|
26958
|
+
toolName: input.toolName,
|
|
26959
|
+
toolUseId: input.toolUseId,
|
|
26960
|
+
input: input.input,
|
|
26961
|
+
output: input.output,
|
|
26962
|
+
isError: input.isError,
|
|
26963
|
+
index
|
|
26964
|
+
};
|
|
26965
|
+
const hash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
|
|
26966
|
+
entry = {
|
|
26967
|
+
id,
|
|
26968
|
+
ts,
|
|
26969
|
+
prevHash,
|
|
26970
|
+
hash,
|
|
26971
|
+
toolName: input.toolName,
|
|
26972
|
+
toolUseId: input.toolUseId,
|
|
26973
|
+
input: input.input,
|
|
26974
|
+
output: input.output,
|
|
26975
|
+
isError: input.isError,
|
|
26976
|
+
index
|
|
26977
|
+
};
|
|
26978
|
+
entries.push(entry);
|
|
26979
|
+
await this.writeAll(input.sessionId, entries);
|
|
26980
|
+
this.tailHash.set(input.sessionId, hash);
|
|
26981
|
+
this.tailIndex.set(input.sessionId, index + 1);
|
|
26982
|
+
const durationMs = Date.now() - t0;
|
|
26983
|
+
this.events?.emit("storage.write", {
|
|
26984
|
+
sessionId: input.sessionId,
|
|
26985
|
+
store: "audit",
|
|
26986
|
+
filePath: fp,
|
|
26987
|
+
operation: "record",
|
|
26988
|
+
outcome: "success",
|
|
26989
|
+
durationMs,
|
|
26990
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26991
|
+
});
|
|
26992
|
+
});
|
|
25986
26993
|
});
|
|
25987
|
-
|
|
25988
|
-
|
|
26994
|
+
return entry;
|
|
26995
|
+
} catch (err) {
|
|
26996
|
+
this.events?.emit("storage.error", {
|
|
26997
|
+
sessionId: input.sessionId,
|
|
26998
|
+
store: "audit",
|
|
26999
|
+
filePath: fp,
|
|
27000
|
+
operation: "record",
|
|
27001
|
+
outcome: "failure",
|
|
27002
|
+
error: err instanceof Error ? err.message : String(err),
|
|
27003
|
+
recoverable: false,
|
|
27004
|
+
durationMs: Date.now() - t0,
|
|
27005
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
27006
|
+
});
|
|
27007
|
+
throw err;
|
|
27008
|
+
}
|
|
25989
27009
|
}
|
|
25990
27010
|
/**
|
|
25991
27011
|
* Walk the chain and verify every entry's hash and prevHash.
|
|
25992
27012
|
* Returns a structured verdict — never throws.
|
|
25993
27013
|
*/
|
|
25994
27014
|
async verify(sessionId) {
|
|
25995
|
-
const
|
|
25996
|
-
|
|
25997
|
-
|
|
25998
|
-
|
|
25999
|
-
|
|
26000
|
-
|
|
26001
|
-
|
|
26002
|
-
|
|
27015
|
+
const fp = this.filePath(sessionId);
|
|
27016
|
+
const t0 = Date.now();
|
|
27017
|
+
let entries;
|
|
27018
|
+
try {
|
|
27019
|
+
entries = await this.readAll(sessionId);
|
|
27020
|
+
} catch (err) {
|
|
27021
|
+
this.events?.emit("storage.read", {
|
|
27022
|
+
sessionId,
|
|
27023
|
+
store: "audit",
|
|
27024
|
+
filePath: fp,
|
|
27025
|
+
operation: "verify",
|
|
27026
|
+
outcome: "failure",
|
|
27027
|
+
durationMs: Date.now() - t0,
|
|
27028
|
+
error: err instanceof Error ? err.message : String(err),
|
|
27029
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
27030
|
+
});
|
|
27031
|
+
return { ok: true, entries: 0 };
|
|
26003
27032
|
}
|
|
26004
|
-
|
|
26005
|
-
|
|
26006
|
-
|
|
26007
|
-
if (e.prevHash !== prevHash) {
|
|
27033
|
+
const verdict = (() => {
|
|
27034
|
+
if (entries.length === 0) return { ok: true, entries: 0 };
|
|
27035
|
+
if (entries[0]?.prevHash !== GENESIS_PREV) {
|
|
26008
27036
|
return {
|
|
26009
27037
|
ok: false,
|
|
26010
|
-
brokenAt:
|
|
26011
|
-
reason:
|
|
27038
|
+
brokenAt: 0,
|
|
27039
|
+
reason: "first entry is not the genesis (prevHash != 0\u20260)"
|
|
26012
27040
|
};
|
|
26013
27041
|
}
|
|
26014
|
-
|
|
26015
|
-
|
|
26016
|
-
|
|
26017
|
-
|
|
26018
|
-
|
|
26019
|
-
|
|
26020
|
-
|
|
26021
|
-
|
|
26022
|
-
|
|
26023
|
-
|
|
26024
|
-
|
|
26025
|
-
|
|
26026
|
-
|
|
26027
|
-
|
|
26028
|
-
|
|
26029
|
-
|
|
26030
|
-
|
|
27042
|
+
let prevHash = GENESIS_PREV;
|
|
27043
|
+
for (let i = 0; i < entries.length; i++) {
|
|
27044
|
+
const e = expectDefined(entries[i]);
|
|
27045
|
+
if (e.prevHash !== prevHash) {
|
|
27046
|
+
return {
|
|
27047
|
+
ok: false,
|
|
27048
|
+
brokenAt: i,
|
|
27049
|
+
reason: `prevHash mismatch at entry ${i} (expected ${prevHash.slice(0, 8)}\u2026, got ${e.prevHash.slice(0, 8)}\u2026)`
|
|
27050
|
+
};
|
|
27051
|
+
}
|
|
27052
|
+
const content = {
|
|
27053
|
+
id: e.id,
|
|
27054
|
+
ts: e.ts,
|
|
27055
|
+
prevHash: e.prevHash,
|
|
27056
|
+
toolName: e.toolName,
|
|
27057
|
+
toolUseId: e.toolUseId,
|
|
27058
|
+
input: e.input,
|
|
27059
|
+
output: e.output,
|
|
27060
|
+
isError: e.isError,
|
|
27061
|
+
index: e.index
|
|
26031
27062
|
};
|
|
27063
|
+
const expectedHash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
|
|
27064
|
+
if (expectedHash !== e.hash) {
|
|
27065
|
+
return {
|
|
27066
|
+
ok: false,
|
|
27067
|
+
brokenAt: i,
|
|
27068
|
+
reason: `hash mismatch at entry ${i} (entry content was modified)`
|
|
27069
|
+
};
|
|
27070
|
+
}
|
|
27071
|
+
prevHash = e.hash;
|
|
26032
27072
|
}
|
|
26033
|
-
|
|
26034
|
-
}
|
|
26035
|
-
|
|
27073
|
+
return { ok: true, entries: entries.length };
|
|
27074
|
+
})();
|
|
27075
|
+
this.events?.emit("storage.read", {
|
|
27076
|
+
sessionId,
|
|
27077
|
+
store: "audit",
|
|
27078
|
+
filePath: fp,
|
|
27079
|
+
operation: "verify",
|
|
27080
|
+
outcome: verdict.ok ? "success" : "failure",
|
|
27081
|
+
durationMs: Date.now() - t0,
|
|
27082
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
27083
|
+
});
|
|
27084
|
+
return verdict;
|
|
26036
27085
|
}
|
|
26037
27086
|
/** All entries for a session, in insertion order. */
|
|
26038
27087
|
async load(sessionId) {
|
|
26039
|
-
|
|
27088
|
+
const fp = this.filePath(sessionId);
|
|
27089
|
+
const t0 = Date.now();
|
|
27090
|
+
try {
|
|
27091
|
+
const entries = await this.readAll(sessionId);
|
|
27092
|
+
const durationMs = Date.now() - t0;
|
|
27093
|
+
this.events?.emit("storage.read", {
|
|
27094
|
+
sessionId,
|
|
27095
|
+
store: "audit",
|
|
27096
|
+
filePath: fp,
|
|
27097
|
+
operation: "load",
|
|
27098
|
+
outcome: "success",
|
|
27099
|
+
durationMs,
|
|
27100
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
27101
|
+
});
|
|
27102
|
+
return entries;
|
|
27103
|
+
} catch (err) {
|
|
27104
|
+
const durationMs = Date.now() - t0;
|
|
27105
|
+
this.events?.emit("storage.read", {
|
|
27106
|
+
sessionId,
|
|
27107
|
+
store: "audit",
|
|
27108
|
+
filePath: fp,
|
|
27109
|
+
operation: "load",
|
|
27110
|
+
outcome: "failure",
|
|
27111
|
+
durationMs,
|
|
27112
|
+
error: err instanceof Error ? err.message : String(err),
|
|
27113
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
27114
|
+
});
|
|
27115
|
+
throw err;
|
|
27116
|
+
}
|
|
26040
27117
|
}
|
|
26041
27118
|
// ── Internals ────────────────────────────────────────────────────────────
|
|
26042
27119
|
filePath(sessionId) {
|
|
@@ -26058,7 +27135,7 @@ var ToolAuditLog = class {
|
|
|
26058
27135
|
return out;
|
|
26059
27136
|
} catch (err) {
|
|
26060
27137
|
if (err.code === "ENOENT") return [];
|
|
26061
|
-
|
|
27138
|
+
throw err;
|
|
26062
27139
|
}
|
|
26063
27140
|
}
|
|
26064
27141
|
async writeAll(sessionId, entries) {
|
|
@@ -26458,37 +27535,98 @@ function emptyTaskFile(sessionId) {
|
|
|
26458
27535
|
tasks: []
|
|
26459
27536
|
};
|
|
26460
27537
|
}
|
|
26461
|
-
async function loadTasks(filePath) {
|
|
27538
|
+
async function loadTasks(filePath, events, traceId) {
|
|
27539
|
+
const t0 = Date.now();
|
|
26462
27540
|
let raw;
|
|
26463
27541
|
try {
|
|
26464
27542
|
raw = await fsp3.readFile(filePath, "utf8");
|
|
26465
|
-
} catch {
|
|
27543
|
+
} catch (err) {
|
|
27544
|
+
events?.emit("storage.error", {
|
|
27545
|
+
sessionId: traceId ?? "~boot~",
|
|
27546
|
+
store: "tasks",
|
|
27547
|
+
filePath,
|
|
27548
|
+
operation: "load",
|
|
27549
|
+
outcome: "failure",
|
|
27550
|
+
error: err instanceof Error ? err.message : String(err),
|
|
27551
|
+
recoverable: true
|
|
27552
|
+
});
|
|
26466
27553
|
return null;
|
|
26467
27554
|
}
|
|
26468
27555
|
try {
|
|
26469
27556
|
const parsed = JSON.parse(raw);
|
|
26470
|
-
if (parsed?.version !== 1 || !Array.isArray(parsed.tasks))
|
|
27557
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.tasks)) {
|
|
27558
|
+
events?.emit("storage.read", {
|
|
27559
|
+
sessionId: traceId ?? "~boot~",
|
|
27560
|
+
store: "tasks",
|
|
27561
|
+
filePath,
|
|
27562
|
+
operation: "load",
|
|
27563
|
+
outcome: "failure",
|
|
27564
|
+
durationMs: Date.now() - t0,
|
|
27565
|
+
error: "invalid_schema",
|
|
27566
|
+
...traceId !== void 0 && { traceId }
|
|
27567
|
+
});
|
|
27568
|
+
return null;
|
|
27569
|
+
}
|
|
27570
|
+
events?.emit("storage.read", {
|
|
27571
|
+
sessionId: traceId ?? "~boot~",
|
|
27572
|
+
store: "tasks",
|
|
27573
|
+
filePath,
|
|
27574
|
+
operation: "load",
|
|
27575
|
+
outcome: "success",
|
|
27576
|
+
durationMs: Date.now() - t0,
|
|
27577
|
+
...traceId !== void 0 && { traceId }
|
|
27578
|
+
});
|
|
26471
27579
|
return parsed;
|
|
26472
27580
|
} catch {
|
|
27581
|
+
events?.emit("storage.read", {
|
|
27582
|
+
sessionId: traceId ?? "~boot~",
|
|
27583
|
+
store: "tasks",
|
|
27584
|
+
filePath,
|
|
27585
|
+
operation: "load",
|
|
27586
|
+
outcome: "failure",
|
|
27587
|
+
durationMs: Date.now() - t0,
|
|
27588
|
+
error: "parse_failed",
|
|
27589
|
+
...traceId !== void 0 && { traceId }
|
|
27590
|
+
});
|
|
26473
27591
|
return null;
|
|
26474
27592
|
}
|
|
26475
27593
|
}
|
|
26476
|
-
async function saveTasks(filePath, tasks) {
|
|
27594
|
+
async function saveTasks(filePath, tasks, events, traceId) {
|
|
27595
|
+
const t0 = Date.now();
|
|
26477
27596
|
try {
|
|
26478
27597
|
tasks.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
26479
27598
|
await atomicWrite(filePath, JSON.stringify(tasks, null, 2), { mode: 384 });
|
|
27599
|
+
events?.emit("storage.write", {
|
|
27600
|
+
sessionId: traceId ?? "~boot~",
|
|
27601
|
+
store: "tasks",
|
|
27602
|
+
filePath,
|
|
27603
|
+
operation: "save",
|
|
27604
|
+
outcome: "success",
|
|
27605
|
+
durationMs: Date.now() - t0,
|
|
27606
|
+
...traceId !== void 0 && { traceId }
|
|
27607
|
+
});
|
|
26480
27608
|
} catch (err) {
|
|
27609
|
+
events?.emit("storage.error", {
|
|
27610
|
+
sessionId: traceId ?? "~boot~",
|
|
27611
|
+
store: "tasks",
|
|
27612
|
+
filePath,
|
|
27613
|
+
operation: "save",
|
|
27614
|
+
outcome: "failure",
|
|
27615
|
+
error: err instanceof Error ? err.message : String(err),
|
|
27616
|
+
recoverable: false,
|
|
27617
|
+
...traceId !== void 0 && { traceId }
|
|
27618
|
+
});
|
|
26481
27619
|
console.warn(
|
|
26482
27620
|
"[task-store] save failed:",
|
|
26483
27621
|
err instanceof Error ? err.message : String(err)
|
|
26484
27622
|
);
|
|
26485
27623
|
}
|
|
26486
27624
|
}
|
|
26487
|
-
async function mutateTasks(filePath, sessionId, fn) {
|
|
27625
|
+
async function mutateTasks(filePath, sessionId, fn, events, traceId) {
|
|
26488
27626
|
return withFileLock(filePath, async () => {
|
|
26489
|
-
const file = await loadTasks(filePath) ?? emptyTaskFile(sessionId);
|
|
27627
|
+
const file = await loadTasks(filePath, events, traceId) ?? emptyTaskFile(sessionId);
|
|
26490
27628
|
const updated = await fn(file);
|
|
26491
|
-
await saveTasks(filePath, updated);
|
|
27629
|
+
await saveTasks(filePath, updated, events, traceId);
|
|
26492
27630
|
return updated;
|
|
26493
27631
|
});
|
|
26494
27632
|
}
|
|
@@ -30795,8 +31933,9 @@ function buildMailboxBlock(messages) {
|
|
|
30795
31933
|
const parts = [];
|
|
30796
31934
|
parts.push("[MAILBOX] New message(s) from other agents:");
|
|
30797
31935
|
parts.push("");
|
|
31936
|
+
const hasActionable = messages.some((m) => m.type === "ask" || m.type === "assign" || m.type === "result");
|
|
30798
31937
|
for (const m of messages) {
|
|
30799
|
-
const typeLabel = m.type === "steer" ? "\u{1F504} STEER" : m.type === "btw" ? "\u{1F4AC} BTW" : `\u{1F4E8} ${m.type.toUpperCase()}`;
|
|
31938
|
+
const typeLabel = m.type === "steer" ? "\u{1F504} STEER" : m.type === "btw" ? "\u{1F4AC} BTW" : m.type === "ask" ? "\u2753 ASK" : m.type === "assign" ? "\u{1F4CB} ASSIGN" : m.type === "result" ? "\u2705 RESULT" : `\u{1F4E8} ${m.type.toUpperCase()}`;
|
|
30800
31939
|
parts.push(`--- ${typeLabel} from ${m.from} ---`);
|
|
30801
31940
|
parts.push(`Subject: ${m.subject}`);
|
|
30802
31941
|
parts.push("");
|
|
@@ -30806,6 +31945,22 @@ function buildMailboxBlock(messages) {
|
|
|
30806
31945
|
parts.push("After your current operation reaches a stopping point, adjust your approach per the instruction above.");
|
|
30807
31946
|
parts.push("");
|
|
30808
31947
|
}
|
|
31948
|
+
if (m.type === "ask") {
|
|
31949
|
+
parts.push("\u21B3 This agent is waiting for your answer. Reply directly or use mailbox action=send to respond.");
|
|
31950
|
+
parts.push("");
|
|
31951
|
+
}
|
|
31952
|
+
if (m.type === "assign") {
|
|
31953
|
+
parts.push("\u21B3 This is a task assignment. Act on it when your current operation allows.");
|
|
31954
|
+
parts.push("");
|
|
31955
|
+
}
|
|
31956
|
+
if (m.type === "result") {
|
|
31957
|
+
parts.push("\u21B3 A subagent has completed its work. Factor this result into your next decision.");
|
|
31958
|
+
parts.push("");
|
|
31959
|
+
}
|
|
31960
|
+
}
|
|
31961
|
+
if (hasActionable) {
|
|
31962
|
+
parts.push("Action required: address the items above. When done, use `mailbox action=ack messageId=<id> completed=true` to mark them complete.");
|
|
31963
|
+
parts.push("");
|
|
30809
31964
|
}
|
|
30810
31965
|
parts.push("[END MAILBOX]");
|
|
30811
31966
|
return { type: "text", text: parts.join("\n") };
|
|
@@ -30826,26 +31981,12 @@ async function injectPendingMailboxMessages(checkMailbox, foldFn, a) {
|
|
|
30826
31981
|
});
|
|
30827
31982
|
}
|
|
30828
31983
|
if (messages.length === 0) return;
|
|
30829
|
-
|
|
30830
|
-
|
|
30831
|
-
|
|
30832
|
-
|
|
30833
|
-
|
|
30834
|
-
|
|
30835
|
-
(a.logger.debug ?? console.debug)?.(
|
|
30836
|
-
`mailbox: failed to fold messages: ${err instanceof Error ? err.message : String(err)}`
|
|
30837
|
-
);
|
|
30838
|
-
}
|
|
30839
|
-
}
|
|
30840
|
-
if (others.length > 0) {
|
|
30841
|
-
const otherSubjects = others.map((m) => ` - [${m.type}] ${m.from}: ${m.subject}`).join("\n");
|
|
30842
|
-
const note = `[MAILBOX] You have ${others.length} other unread message(s). Use \`mailbox action=check\` to read them:
|
|
30843
|
-
${otherSubjects}
|
|
30844
|
-
[END MAILBOX]`;
|
|
30845
|
-
try {
|
|
30846
|
-
foldFn({ type: "text", text: note });
|
|
30847
|
-
} catch {
|
|
30848
|
-
}
|
|
31984
|
+
try {
|
|
31985
|
+
foldFn(buildMailboxBlock(messages));
|
|
31986
|
+
} catch (err) {
|
|
31987
|
+
(a.logger.debug ?? console.debug)?.(
|
|
31988
|
+
`mailbox: failed to fold messages: ${err instanceof Error ? err.message : String(err)}`
|
|
31989
|
+
);
|
|
30849
31990
|
}
|
|
30850
31991
|
}
|
|
30851
31992
|
|
|
@@ -31860,9 +33001,26 @@ async function writeProjectMeta(paths, projectRoot) {
|
|
|
31860
33001
|
} catch {
|
|
31861
33002
|
}
|
|
31862
33003
|
}
|
|
31863
|
-
async function registerProjectInManifest(paths, projectRoot) {
|
|
33004
|
+
async function registerProjectInManifest(paths, projectRoot, events) {
|
|
33005
|
+
const manifestPath = path7.join(paths.globalRoot, "projects.json");
|
|
33006
|
+
try {
|
|
33007
|
+
const t0 = Date.now();
|
|
33008
|
+
const raw = await fsp3.readFile(manifestPath, "utf8");
|
|
33009
|
+
events?.emit("storage.read", {
|
|
33010
|
+
sessionId: "~boot~",
|
|
33011
|
+
store: "project",
|
|
33012
|
+
filePath: manifestPath,
|
|
33013
|
+
operation: "manifest_read",
|
|
33014
|
+
outcome: "success",
|
|
33015
|
+
durationMs: Date.now() - t0
|
|
33016
|
+
});
|
|
33017
|
+
try {
|
|
33018
|
+
JSON.parse(raw);
|
|
33019
|
+
} catch {
|
|
33020
|
+
}
|
|
33021
|
+
} catch (err) {
|
|
33022
|
+
}
|
|
31864
33023
|
try {
|
|
31865
|
-
const manifestPath = path7.join(paths.globalRoot, "projects.json");
|
|
31866
33024
|
let manifest;
|
|
31867
33025
|
try {
|
|
31868
33026
|
const raw = await fsp3.readFile(manifestPath, "utf8");
|
|
@@ -31879,8 +33037,17 @@ async function registerProjectInManifest(paths, projectRoot) {
|
|
|
31879
33037
|
const name = path7.basename(projectRoot);
|
|
31880
33038
|
manifest.projects.push({ name, root: projectRoot, slug, lastSeen: now, createdAt: now });
|
|
31881
33039
|
}
|
|
33040
|
+
const writeT0 = Date.now();
|
|
31882
33041
|
await fsp3.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
31883
|
-
|
|
33042
|
+
events?.emit("storage.write", {
|
|
33043
|
+
sessionId: "~boot~",
|
|
33044
|
+
store: "project",
|
|
33045
|
+
filePath: manifestPath,
|
|
33046
|
+
operation: "manifest_write",
|
|
33047
|
+
outcome: "success",
|
|
33048
|
+
durationMs: Date.now() - writeT0
|
|
33049
|
+
});
|
|
33050
|
+
} catch (err) {
|
|
31884
33051
|
}
|
|
31885
33052
|
}
|
|
31886
33053
|
var GITIGNORE_ENTRY = ".wrongstack/\n";
|
|
@@ -32145,9 +33312,10 @@ Never silently skip a failure \u2014 always report it, even when you choose not
|
|
|
32145
33312
|
|
|
32146
33313
|
## After-task suggestions
|
|
32147
33314
|
|
|
32148
|
-
After completing a significant task, end your
|
|
32149
|
-
actions in a \`<next_steps>\` block. Use this
|
|
32150
|
-
select them with \`/next 1\`, \`/next 2\`, or
|
|
33315
|
+
**You are the leader agent.** After completing a significant task, end your
|
|
33316
|
+
response with 2\u20134 suggested next actions in a \`<next_steps>\` block. Use this
|
|
33317
|
+
exact format so the user can select them with \`/next 1\`, \`/next 2\`, or
|
|
33318
|
+
\`/next 1 2 3\`:
|
|
32151
33319
|
|
|
32152
33320
|
\`\`\`
|
|
32153
33321
|
<next_steps>
|
|
@@ -32160,6 +33328,7 @@ select them with \`/next 1\`, \`/next 2\`, or \`/next 1 2 3\`:
|
|
|
32160
33328
|
Rules:
|
|
32161
33329
|
- Each line is a single imperative sentence the user can act on immediately.
|
|
32162
33330
|
- Be specific: mention file names, tool names, or commands.
|
|
33331
|
+
- **Concrete actions only** \u2014 never write declarations of intent ("we should fix X", "consider refactoring Y") or manual suggestions ("manually check Z"). Write exactly what should be done: "Fix null deref in auth/session.ts:42", "Run pnpm typecheck".
|
|
32163
33332
|
- Order by priority. Keep each suggestion to one line.
|
|
32164
33333
|
- Skip during multi-step operations \u2014 only show after completion.
|
|
32165
33334
|
- If nothing is pending, say "No pending actions."
|
|
@@ -32489,8 +33658,17 @@ sender's exact \`from\` id.
|
|
|
32489
33658
|
### Receiving
|
|
32490
33659
|
|
|
32491
33660
|
Unread mail (direct, base-name, and \`*\` broadcasts) is injected into
|
|
32492
|
-
your conversation automatically before each step \u2014
|
|
32493
|
-
|
|
33661
|
+
your conversation automatically before each step \u2014 ALL message types
|
|
33662
|
+
(steer, btw, ask, assign, result, note) appear inline with a call to
|
|
33663
|
+
action. You do NOT need to manually check the mailbox; subagent results
|
|
33664
|
+
and questions reach you even while you are mid-task.
|
|
33665
|
+
|
|
33666
|
+
When a message includes a call to action:
|
|
33667
|
+
- **ask**: reply to the agent directly or use \`mail_send\` to respond
|
|
33668
|
+
- **assign**: act on the task when your current operation allows
|
|
33669
|
+
- **result**: factor the outcome into your next decision
|
|
33670
|
+
|
|
33671
|
+
To catch up explicitly:
|
|
32494
33672
|
|
|
32495
33673
|
- \`mail_inbox\` \u2014 read your unread mail and mark it read
|
|
32496
33674
|
- \`mailbox action=query from=<agent> type=result\` \u2014 find specific results
|