@agentdocumentformat/cli 0.7.8 → 0.8.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.
Files changed (100) hide show
  1. package/README.md +1 -1
  2. package/dist/adf.mjs +59 -8
  3. package/dist/chunks/{APEv2Parser-2CNNHMXV.mjs → APEv2Parser-WIXRJLHB.mjs} +2 -2
  4. package/dist/chunks/{AiffParser-7BR4RL6G.mjs → AiffParser-ZC4QFOVJ.mjs} +5 -5
  5. package/dist/chunks/{App-7RIIOHJJ.mjs → App-A5GUFCFJ.mjs} +279 -293
  6. package/dist/chunks/{DsdiffParser-CMD4AX7A.mjs → DsdiffParser-UAPAJHFX.mjs} +5 -5
  7. package/dist/chunks/{DsfParser-UOGZDS2M.mjs → DsfParser-NNYZK2QX.mjs} +6 -6
  8. package/dist/chunks/{FlacParser-HB3T3CD4.mjs → FlacParser-MW2W4LO3.mjs} +6 -6
  9. package/dist/chunks/{MP4Parser-66HBRUSX.mjs → MP4Parser-Z57SYOOQ.mjs} +4 -4
  10. package/dist/chunks/{MpegParser-YW6LIUEW.mjs → MpegParser-XIIPAZVM.mjs} +7 -7
  11. package/dist/chunks/{MusepackParser-PAUSXP7D.mjs → MusepackParser-QWNDEYHJ.mjs} +6 -6
  12. package/dist/chunks/{OggParser-NUBV3GWC.mjs → OggParser-WRX53F77.mjs} +9 -9
  13. package/dist/chunks/{WavPackParser-WQAGT7TC.mjs → WavPackParser-MJ7WCSJ5.mjs} +2 -2
  14. package/dist/chunks/{WaveParser-EWLQVDX4.mjs → WaveParser-RAVLHZ5S.mjs} +7 -7
  15. package/dist/chunks/{adf-schema-US4NFHMR.mjs → adf-schema-YJSGLMA4.mjs} +2 -2
  16. package/dist/chunks/auth-manager-AJ32FZWS.mjs +19 -0
  17. package/dist/chunks/auth-manager-C5OBEQK2.mjs +18 -0
  18. package/dist/chunks/auth-manager-TBNKCJ45.mjs +17 -0
  19. package/dist/chunks/{chatgpt-subscription-E4MXTIAT.mjs → chatgpt-subscription-MMGNW5KJ.mjs} +5 -2
  20. package/dist/chunks/chunk-3UGZFWIF.mjs +26 -0
  21. package/dist/chunks/{chunk-5PO5PKRV.mjs → chunk-4OGZFUKA.mjs} +126 -67
  22. package/dist/chunks/{chunk-VN7UH7I5.mjs → chunk-6XQZGYMK.mjs} +246 -67
  23. package/dist/chunks/{chunk-XNPZBLYL.mjs → chunk-75DC5EWN.mjs} +13 -2
  24. package/dist/chunks/chunk-AZYOXXVL.mjs +865 -0
  25. package/dist/chunks/{chunk-6VQEXLE2.mjs → chunk-BHY7QDCM.mjs} +41 -3
  26. package/dist/chunks/chunk-C2S64Y6F.mjs +203 -0
  27. package/dist/chunks/chunk-C4GA5SI4.mjs +12799 -0
  28. package/dist/chunks/chunk-DDTTSKGG.mjs +2443 -0
  29. package/dist/chunks/{chunk-VAUOM3IN.mjs → chunk-DLWCKKIO.mjs} +1 -1
  30. package/dist/chunks/{chunk-QPLEAE6V.mjs → chunk-G6NBFYG5.mjs} +152 -824
  31. package/dist/chunks/{chunk-KXEF4F4E.mjs → chunk-GAZO6UMI.mjs} +1 -1
  32. package/dist/chunks/{chunk-XYTWGHFS.mjs → chunk-H3XLRWVE.mjs} +1 -1
  33. package/dist/chunks/{chunk-7T2YMTWL.mjs → chunk-H4C65MTP.mjs} +4 -4
  34. package/dist/chunks/chunk-HBOFZRUS.mjs +738 -0
  35. package/dist/chunks/chunk-JVPZIMXW.mjs +976 -0
  36. package/dist/chunks/{chunk-XYV3VCIX.mjs → chunk-JZFONJSL.mjs} +79 -20
  37. package/dist/chunks/chunk-KDDVQKCG.mjs +65 -0
  38. package/dist/chunks/chunk-KRPKWOMB.mjs +290 -0
  39. package/dist/chunks/{chunk-G6PH7RPW.mjs → chunk-LCMANRYY.mjs} +3 -3
  40. package/dist/chunks/{chunk-ZESQMP66.mjs → chunk-LGL6ML37.mjs} +3 -3
  41. package/dist/chunks/chunk-LKFHERRV.mjs +145 -0
  42. package/dist/chunks/chunk-LTX74NU6.mjs +29 -0
  43. package/dist/chunks/chunk-M2F7C2UJ.mjs +425 -0
  44. package/dist/chunks/{chunk-62NE4LG4.mjs → chunk-MQAMI5BF.mjs} +5 -5
  45. package/dist/chunks/chunk-MZRXFWPQ.mjs +278 -0
  46. package/dist/chunks/{chunk-PRMGZZM5.mjs → chunk-NZLEPJ5M.mjs} +2 -0
  47. package/dist/chunks/{chunk-CD3ATSXE.mjs → chunk-NZO5O4VN.mjs} +1 -1
  48. package/dist/chunks/{chunk-I3UYSITV.mjs → chunk-O7XKQ6CH.mjs} +1 -1
  49. package/dist/chunks/chunk-P3RG5LQA.mjs +244 -0
  50. package/dist/chunks/chunk-PAK5JCO2.mjs +48 -0
  51. package/dist/chunks/chunk-PC63YFB7.mjs +34 -0
  52. package/dist/chunks/{daemon-control-HKFVSQTQ.mjs → chunk-PRPTKMQW.mjs} +67 -26
  53. package/dist/chunks/{chunk-LFMB4PAA.mjs → chunk-TKXIH27X.mjs} +974 -113
  54. package/dist/chunks/{chunk-5W7KES6P.mjs → chunk-UZPDETEU.mjs} +1 -1
  55. package/dist/chunks/chunk-V2T6NZX2.mjs +517 -0
  56. package/dist/chunks/chunk-V3MM6PCH.mjs +366 -0
  57. package/dist/chunks/chunk-WJGPNHGX.mjs +67 -0
  58. package/dist/chunks/chunk-WVTPPOKK.mjs +290 -0
  59. package/dist/chunks/{chunk-J74STFKE.mjs → chunk-WX6QCUWI.mjs} +272 -51
  60. package/dist/chunks/{chunk-Y5ZLKFLE.mjs → chunk-XNNJQCZP.mjs} +196 -165
  61. package/dist/chunks/{chunk-HDBPBORT.mjs → chunk-XT2BJUFC.mjs} +15 -6
  62. package/dist/chunks/{chunk-HSBV6YFU.mjs → chunk-YRMKSHW3.mjs} +614 -92
  63. package/dist/chunks/{chunk-BNXUAOO4.mjs → chunk-ZGXUOAWZ.mjs} +3186 -959
  64. package/dist/chunks/chunk-ZPB3HYM7.mjs +45 -0
  65. package/dist/chunks/claude-subscription-33BMZB6I.mjs +29 -0
  66. package/dist/chunks/{cli-NMFIAR7Z.mjs → cli-I47IFDHJ.mjs} +797 -56
  67. package/dist/chunks/{commands-KPJWFRUE.mjs → commands-E4EQUHE5.mjs} +4 -3
  68. package/dist/chunks/{daemon-YDIK3AAM.mjs → daemon-FQ5BV3AP.mjs} +82058 -63958
  69. package/dist/chunks/daemon-control-L3CLG63P.mjs +59 -0
  70. package/dist/chunks/{daemon-token-WRSULP7G.mjs → daemon-token-L5QKTHU5.mjs} +3 -1
  71. package/dist/chunks/{discord-DSP4HUWI.mjs → discord-IR62565M.mjs} +3 -52
  72. package/dist/chunks/{email-BRG7KUO2.mjs → email-KHHTJBDW.mjs} +4 -2
  73. package/dist/chunks/{grok-subscription-4PYX7TBF.mjs → grok-subscription-E74FS5EG.mjs} +5 -2
  74. package/dist/chunks/{lib-N7FCZWKH.mjs → lib-L27L5PXM.mjs} +22 -22
  75. package/dist/chunks/{open-VAU7PXT6.mjs → open-TLGS2Z3Q.mjs} +4 -4
  76. package/dist/chunks/{parser-JVY2PWSM.mjs → parser-7GARNT5L.mjs} +1 -1
  77. package/dist/chunks/{pipeline-executor-O77NTGLP.mjs → pipeline-executor-FD4FBWQA.mjs} +6 -5
  78. package/dist/chunks/prompt-RRHZX4YT.mjs +13 -0
  79. package/dist/chunks/remote-access-cli-XXAPQWH6.mjs +268 -0
  80. package/dist/chunks/remote-hello-UFWUBDQI.mjs +37 -0
  81. package/dist/chunks/sdk-5FD2ZP7N.mjs +28422 -0
  82. package/dist/chunks/{slack-OCNNF4DX.mjs → slack-IYC5RAB5.mjs} +3 -4
  83. package/dist/chunks/{store-HUVDJPYN.mjs → store-ZRN2WCFB.mjs} +6 -3
  84. package/dist/chunks/tailscale-WLJATFMB.mjs +25 -0
  85. package/dist/chunks/{theme-SFE3JMA2.mjs → theme-HLBDTF6F.mjs} +1 -1
  86. package/dist/chunks/{themes-YLXLOIJF.mjs → themes-BQ72P446.mjs} +2 -2
  87. package/dist/chunks/{token-6G3HCMNX.mjs → token-6D22WECM.mjs} +20 -16
  88. package/dist/chunks/{token-util-UXYSDQIW.mjs → token-util-YF3ZWBZ4.mjs} +1 -2
  89. package/dist/chunks/{tui-UQJLK7XI.mjs → tui-SPSPDADM.mjs} +17 -14
  90. package/dist/chunks/{welcome-XHXFV7SH.mjs → welcome-J5JZZAM5.mjs} +1 -1
  91. package/dist/chunks/{whatsapp-AJMRQBT7.mjs → whatsapp-YJFFVN4Q.mjs} +9 -9
  92. package/package.json +1 -1
  93. package/dist/chunks/chunk-GKKCMXIZ.mjs +0 -94
  94. package/dist/chunks/chunk-IHDDCZBS.mjs +0 -112
  95. package/dist/chunks/chunk-KWFOS5WC.mjs +0 -41
  96. package/dist/chunks/chunk-UEZR2CNS.mjs +0 -65
  97. package/dist/chunks/chunk-VMHTYDH4.mjs +0 -172
  98. package/dist/chunks/chunk-Y2QA53AN.mjs +0 -259
  99. package/dist/chunks/{chunk-NBTUWMGP.mjs → chunk-2MTE22CR.mjs} +3 -3
  100. package/dist/chunks/{telegram-2EFGKIVK.mjs → telegram-JYY2VDTI.mjs} +5 -5
@@ -7,11 +7,19 @@ const __dirname = __adfDirname(__filename);
7
7
  import {
8
8
  DEFAULT_AGENT_CONFIG,
9
9
  DOCS_GUIDES_URL
10
- } from "./chunk-G6PH7RPW.mjs";
10
+ } from "./chunk-LCMANRYY.mjs";
11
11
  import {
12
12
  nanoid
13
13
  } from "./chunk-3GNIDM53.mjs";
14
14
  import {
15
+ applyFileRefChanges,
16
+ isAudioInputMime,
17
+ isFileRefBlock,
18
+ isVideoInputMime,
19
+ isVisionMime
20
+ } from "./chunk-LKFHERRV.mjs";
21
+ import {
22
+ AUDIT_DEFAULTS,
15
23
  CODE_EXECUTION_DEFAULTS,
16
24
  DEFAULT_MIND_CONTENT,
17
25
  DEFAULT_MIND_LOG_CONTENT,
@@ -21,7 +29,7 @@ import {
21
29
  META_PROTECTION_LEVELS,
22
30
  SETTABLE_STATES,
23
31
  getDefaultDocumentContent
24
- } from "./chunk-CD3ATSXE.mjs";
32
+ } from "./chunk-NZO5O4VN.mjs";
25
33
  import {
26
34
  __esm,
27
35
  __export
@@ -211,38 +219,6 @@ var TEXT_MIME_PREFIXES = ["text/"], TEXT_MIME_EXACT = /* @__PURE__ */ new Set([
211
219
  function isTextMime(mime) {
212
220
  return mime ? TEXT_MIME_PREFIXES.some((p) => mime.startsWith(p)) || /\+(json|xml|yaml)$/.test(mime) ? !0 : TEXT_MIME_EXACT.has(mime) : !1;
213
221
  }
214
- var VISION_MIMES = /* @__PURE__ */ new Set([
215
- "image/png",
216
- "image/jpeg",
217
- "image/gif",
218
- "image/webp"
219
- ]);
220
- function isVisionMime(mime) {
221
- return mime ? VISION_MIMES.has(mime) : !1;
222
- }
223
- var AUDIO_INPUT_MIMES = /* @__PURE__ */ new Set([
224
- "audio/wav",
225
- "audio/mpeg",
226
- "audio/mp3",
227
- "audio/aiff",
228
- "audio/aac",
229
- "audio/ogg",
230
- "audio/flac",
231
- "audio/x-m4a",
232
- "audio/webm"
233
- ]);
234
- function isAudioInputMime(mime) {
235
- return mime ? AUDIO_INPUT_MIMES.has(mime) : !1;
236
- }
237
- var VIDEO_INPUT_MIMES = /* @__PURE__ */ new Set([
238
- "video/mp4",
239
- "video/mpeg",
240
- "video/quicktime",
241
- "video/webm"
242
- ]);
243
- function isVideoInputMime(mime) {
244
- return mime ? VIDEO_INPUT_MIMES.has(mime) : !1;
245
- }
246
222
  var AUDIO_FORMAT_MAP = {
247
223
  "audio/wav": "wav",
248
224
  "audio/mpeg": "mp3",
@@ -462,7 +438,7 @@ var catHandler = {
462
438
  async execute(ctx) {
463
439
  if (ctx.args.length < 2) return err("cp: usage: cp <src> <dst>");
464
440
  let src = vfsPath(ctx.args[0]), dst = vfsPath(ctx.args[1]), [content, readErr] = await shellReadFile(ctx.toolRegistry, ctx.workspace, src);
465
- if (readErr) return err(`cp: ${readErr}`);
441
+ if (readErr !== null) return err(`cp: ${readErr}`);
466
442
  let write = await ctx.toolRegistry.executeTool("fs_write", { mode: "write", path: dst, content }, ctx.workspace);
467
443
  return write.isError ? err(`cp: ${write.content}`) : ok("");
468
444
  }
@@ -717,7 +693,7 @@ var duHandler = {
717
693
  let bytes = parseInt(raw, 10), content = ctx.stdin;
718
694
  if (ctx.args.length > 0) {
719
695
  let [fileContent, error] = await shellReadFile(ctx.toolRegistry, ctx.workspace, vfsPath(ctx.args[0]));
720
- if (error) return err(`head: ${error}`);
696
+ if (error !== null) return err(`head: ${error}`);
721
697
  content = fileContent;
722
698
  }
723
699
  return content ? ok(Buffer.from(content, "utf8").subarray(0, bytes).toString("utf8")) : ok("");
@@ -729,7 +705,7 @@ var duHandler = {
729
705
  let text = ctx.stdin;
730
706
  if (ctx.args.length > 0) {
731
707
  let [content, error] = await shellReadFile(ctx.toolRegistry, ctx.workspace, vfsPath(ctx.args[0]), { end_line: n });
732
- return error ? err(`head: ${error}`) : ok(content);
708
+ return error !== null ? err(`head: ${error}`) : ok(content);
733
709
  }
734
710
  if (!text) return ok("");
735
711
  let lines = text.split(`
@@ -761,7 +737,7 @@ var duHandler = {
761
737
  let fromByte = raw.startsWith("+"), bytes = parseInt(raw, 10), content = ctx.stdin;
762
738
  if (ctx.args.length > 0) {
763
739
  let [fileContent, error] = await shellReadFile(ctx.toolRegistry, ctx.workspace, vfsPath(ctx.args[0]));
764
- if (error) return err(`tail: ${error}`);
740
+ if (error !== null) return err(`tail: ${error}`);
765
741
  content = fileContent;
766
742
  }
767
743
  if (!content) return ok("");
@@ -779,7 +755,7 @@ var duHandler = {
779
755
  let text = ctx.stdin;
780
756
  if (ctx.args.length > 0) {
781
757
  let [content, error] = await shellReadFile(ctx.toolRegistry, ctx.workspace, vfsPath(ctx.args[0]));
782
- if (error) return err(`tail: ${error}`);
758
+ if (error !== null) return err(`tail: ${error}`);
783
759
  text = content;
784
760
  }
785
761
  if (!text) return ok("");
@@ -1217,7 +1193,7 @@ var GREP_KNOWN_FLAGS = /* @__PURE__ */ new Set([
1217
1193
  for (let file of files) {
1218
1194
  if (file.mime_type && !isTextMime(file.mime_type)) continue;
1219
1195
  let [content, readErr] = await shellReadFile(ctx.toolRegistry, ctx.workspace, file.path);
1220
- if (readErr) continue;
1196
+ if (readErr !== null) continue;
1221
1197
  let lines2 = splitLines(content), sel2 = await select(lines2, content?.length ?? 0);
1222
1198
  if ("failure" in sel2) return sel2.failure;
1223
1199
  let fileMatches = sel2.idx.length;
@@ -1236,7 +1212,7 @@ var GREP_KNOWN_FLAGS = /* @__PURE__ */ new Set([
1236
1212
  let text = ctx.stdin;
1237
1213
  if (fileArgs.length === 1) {
1238
1214
  let [content, readErr] = await shellReadFile(ctx.toolRegistry, ctx.workspace, vfsPath2(fileArgs[0]));
1239
- if (readErr) return err(`grep: ${fileArgs[0]}: No such file or directory`);
1215
+ if (readErr !== null) return err(`grep: ${fileArgs[0]}: No such file or directory`);
1240
1216
  text = content;
1241
1217
  }
1242
1218
  if (!text)
@@ -1320,7 +1296,7 @@ var SED_KNOWN_FLAGS = /* @__PURE__ */ new Set(["i", "E", "r"]), sedHandler = {
1320
1296
  if (ctx.args.length > 1) {
1321
1297
  filePath = vfsPath2(ctx.args[1]);
1322
1298
  let [sedContent, sedErr] = await shellReadFile(ctx.toolRegistry, ctx.workspace, filePath);
1323
- if (sedErr) return err(`sed: ${filePath}: No such file or directory`);
1299
+ if (sedErr !== null) return err(`sed: ${filePath}: No such file or directory`);
1324
1300
  text = sedContent;
1325
1301
  } else if (!text)
1326
1302
  return ok("");
@@ -1585,7 +1561,7 @@ var xargsHandler = {
1585
1561
  batchSize = n;
1586
1562
  }
1587
1563
  if (!text) return ok("");
1588
- let { parse } = await import("./parser-JVY2PWSM.mjs"), { executeNode } = await import("./pipeline-executor-O77NTGLP.mjs"), subCtx = {
1564
+ let { parse } = await import("./parser-7GARNT5L.mjs"), { executeNode } = await import("./pipeline-executor-FD4FBWQA.mjs"), subCtx = {
1589
1565
  workspace: ctx.workspace,
1590
1566
  toolRegistry: ctx.toolRegistry,
1591
1567
  config: ctx.config,
@@ -2405,7 +2381,7 @@ async function executeShellScript(path, ctx) {
2405
2381
  authorized = ctx.workspace.isFileAuthorized(path);
2406
2382
  } catch {
2407
2383
  }
2408
- let gate = { ...ctx.gate ?? {}, authorized }, base = baseRegistry(ctx.toolRegistry), toolRegistry = gate.authorized ? authorizedRegistry(base) : base, { parse } = await import("./parser-JVY2PWSM.mjs"), { executeNode, MAX_SHELL_DEPTH } = await import("./pipeline-executor-O77NTGLP.mjs"), depth = (ctx.depth ?? 0) + 1;
2384
+ let gate = { ...ctx.gate ?? {}, authorized }, base = baseRegistry(ctx.toolRegistry), toolRegistry = gate.authorized ? authorizedRegistry(base) : base, { parse } = await import("./parser-7GARNT5L.mjs"), { executeNode, MAX_SHELL_DEPTH } = await import("./pipeline-executor-FD4FBWQA.mjs"), depth = (ctx.depth ?? 0) + 1;
2409
2385
  if (depth > MAX_SHELL_DEPTH)
2410
2386
  return err(`./${path}: script nesting too deep (${MAX_SHELL_DEPTH}) \u2014 possible infinite recursion`);
2411
2387
  try {
@@ -2621,9 +2597,105 @@ import { createPrivateKey as createPrivateKey3, createPublicKey as createPublicK
2621
2597
  // src/main/adf/adf-database.ts
2622
2598
  import Database from "better-sqlite3";
2623
2599
  import * as sqliteVec from "sqlite-vec";
2624
- import { existsSync as existsSync2, unlinkSync, renameSync, copyFileSync, readdirSync, readFileSync as readFileSync3, openSync, closeSync, lstatSync, fstatSync, promises as fsp } from "fs";
2600
+ import { existsSync as existsSync2, unlinkSync, renameSync, copyFileSync, readdirSync, readFileSync as readFileSync3, openSync, closeSync, lstatSync, fstatSync, fsyncSync, statSync, promises as fsp } from "fs";
2601
+ import { createHash } from "crypto";
2625
2602
  import { brotliDecompressSync } from "zlib";
2626
- import { join as join2, resolve, sep } from "path";
2603
+ import { basename, dirname as dirname2, join as join2, resolve, sep } from "path";
2604
+
2605
+ // src/main/adf/file-ref-registry.ts
2606
+ var MediaPayloadCache = class {
2607
+ constructor(maxChars) {
2608
+ this.maxChars = maxChars;
2609
+ }
2610
+ maxChars;
2611
+ entries = /* @__PURE__ */ new Map();
2612
+ chars = 0;
2613
+ get(path) {
2614
+ let entry = this.entries.get(path);
2615
+ if (entry)
2616
+ return this.entries.delete(path), this.entries.set(path, entry), entry;
2617
+ }
2618
+ set(path, entry) {
2619
+ if (this.delete(path), !(entry.payload.length > this.maxChars)) {
2620
+ this.entries.set(path, entry), this.chars += entry.payload.length;
2621
+ for (let [oldest, value] of this.entries) {
2622
+ if (this.chars <= this.maxChars) break;
2623
+ this.entries.delete(oldest), this.chars -= value.payload.length;
2624
+ }
2625
+ }
2626
+ }
2627
+ delete(path) {
2628
+ let entry = this.entries.get(path);
2629
+ entry && (this.entries.delete(path), this.chars -= entry.payload.length);
2630
+ }
2631
+ clear() {
2632
+ this.entries.clear(), this.chars = 0;
2633
+ }
2634
+ get size() {
2635
+ return this.entries.size;
2636
+ }
2637
+ }, MEDIA_CACHE_MAX_CHARS = 48 * 1024 * 1024, FileRefRegistry = class {
2638
+ /**
2639
+ * Paths some transcript row references. Null until first needed: built by
2640
+ * one scan of adf_loop, then kept current by every loop write. May hold
2641
+ * paths whose rows are gone (cleared, compacted); that only costs a scan.
2642
+ */
2643
+ referenced = null;
2644
+ media = new MediaPayloadCache(MEDIA_CACHE_MAX_CHARS);
2645
+ /** Bumped when a file a request has carried changes, so resolved requests rebuild. */
2646
+ generation = 0;
2647
+ /** Paths a request has carried. */
2648
+ resolved = /* @__PURE__ */ new Set();
2649
+ holders = /* @__PURE__ */ new Set();
2650
+ addHolder(holder) {
2651
+ this.holders.add(new WeakRef(holder));
2652
+ }
2653
+ /** Record the references in a loop row just written. */
2654
+ noteContent(content) {
2655
+ if (this.referenced)
2656
+ for (let block of content)
2657
+ isFileRefBlock(block) && !block.file_ref.deleted && this.referenced.add(block.file_ref.path);
2658
+ }
2659
+ /** Whether any of these changes names a referenced path (`referenced` must be built). */
2660
+ affects(changes) {
2661
+ let referenced = this.referenced;
2662
+ return referenced ? changes.some((change) => referenced.has(change.op === "rename" ? change.from : change.path)) : !0;
2663
+ }
2664
+ /** Keep `referenced` current after the rows were rewritten. */
2665
+ noteChanges(changes) {
2666
+ let referenced = this.referenced;
2667
+ if (referenced)
2668
+ for (let change of changes)
2669
+ change.op !== "rename" || !referenced.has(change.from) || (referenced.delete(change.from), referenced.add(change.to));
2670
+ }
2671
+ /** Rewrite the references every live session holds. */
2672
+ notifyHolders(changes) {
2673
+ for (let ref of this.holders) {
2674
+ let holder = ref.deref();
2675
+ if (!holder) {
2676
+ this.holders.delete(ref);
2677
+ continue;
2678
+ }
2679
+ try {
2680
+ holder.applyFileRefChanges(changes);
2681
+ } catch (error) {
2682
+ console.error("[FileRefRegistry] A session could not apply a file change:", error);
2683
+ }
2684
+ }
2685
+ }
2686
+ /** A file was written, renamed or deleted: its loaded bytes are stale. */
2687
+ invalidate(path) {
2688
+ this.media.delete(path), this.resolved.has(path) && this.generation++;
2689
+ }
2690
+ }, byFile = /* @__PURE__ */ new Map(), byDatabase = /* @__PURE__ */ new WeakMap();
2691
+ function fileRefRegistry(database, canonicalPath) {
2692
+ if (canonicalPath === null) {
2693
+ let registry4 = byDatabase.get(database);
2694
+ return registry4 || (registry4 = new FileRefRegistry(), byDatabase.set(database, registry4)), registry4;
2695
+ }
2696
+ let registry3 = byFile.get(canonicalPath);
2697
+ return registry3 || (registry3 = new FileRefRegistry(), byFile.set(canonicalPath, registry3)), registry3;
2698
+ }
2627
2699
 
2628
2700
  // src/shared/constants/agent-icons.ts
2629
2701
  var AGENT_ICON_POOL = [
@@ -2834,7 +2906,20 @@ function didToPublicKey(did) {
2834
2906
  }
2835
2907
 
2836
2908
  // src/main/adf/adf-database.ts
2837
- var nanoid2 = () => nanoid(10), LOOP_TOOLS_BACKFILL_META = "adf_loop_tools_backfilled", LEGACY_ESSENTIAL_LOOP_TOOLS = ["loop_send", "loop_list"], ADF_LATEST_SCHEMA_VERSION = 32, CLEAN_CLOSE_META_KEY = "adf_clean_close", SNAPSHOT_DEADLINE_MS = 3e4, OPFLAG_P2ISREG = 16;
2909
+ var nanoid2 = () => nanoid(10), LOOP_TOOLS_BACKFILL_META = "adf_loop_tools_backfilled", LEGACY_ESSENTIAL_LOOP_TOOLS = ["loop_send", "loop_list"], ADF_LATEST_SCHEMA_VERSION = 32, ADF_OLDEST_MIGRATABLE_SCHEMA_VERSION = 3, AdfSchemaTooOldError = class extends Error {
2910
+ constructor(filePath, schemaVersion, opts = {}) {
2911
+ let name = basename(filePath);
2912
+ super(
2913
+ (schemaVersion > 0 ? `${name} uses schema v${schemaVersion}, which this version cannot upgrade` : `${name} has no schema version and an older layout, which this version cannot upgrade`) + ` (it upgrades v${ADF_OLDEST_MIGRATABLE_SCHEMA_VERSION} and later; no released version upgrades older files).${opts.unchanged === !1 ? "" : " The file was not changed."} See "An agent file uses schema v2" in the ADF troubleshooting guide.`
2914
+ );
2915
+ this.filePath = filePath;
2916
+ this.schemaVersion = schemaVersion;
2917
+ this.name = "AdfSchemaTooOldError";
2918
+ }
2919
+ filePath;
2920
+ schemaVersion;
2921
+ code = "schema_too_old";
2922
+ }, CLEAN_CLOSE_META_KEY = "adf_clean_close", SNAPSHOT_DEADLINE_MS = 3e4, OPFLAG_P2ISREG = 16;
2838
2923
  function formatMetaNumber(n) {
2839
2924
  return Number.isInteger(n) ? String(n) : String(Number(n.toFixed(10)));
2840
2925
  }
@@ -3144,7 +3229,12 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3144
3229
  /** Page cap for getLogsAfterId — pollers carry a cursor, so pages catch up. */
3145
3230
  static MAX_LOGS_AFTER_ID = 5e3;
3146
3231
  constructor(db, filePath) {
3147
- this.db = db, this.filePath = filePath, _AdfDatabase.incrementOpen(filePath), this.applyPragmas(), this.prepareStatements();
3232
+ this.db = db, this.filePath = filePath, _AdfDatabase.incrementOpen(filePath);
3233
+ try {
3234
+ this.applyPragmas(), this.prepareStatements();
3235
+ } catch (err2) {
3236
+ throw _AdfDatabase.decrementOpen(filePath), err2;
3237
+ }
3148
3238
  }
3149
3239
  // ===========================================================================
3150
3240
  // Static Factory Methods
@@ -3154,6 +3244,36 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3154
3244
  let code = err2?.code;
3155
3245
  return typeof code == "string" && (code.startsWith("SQLITE_BUSY") || code === "SQLITE_LOCKED");
3156
3246
  }
3247
+ /**
3248
+ * The file's schema version: `adf_schema_version`, else the older
3249
+ * `schema_version`, else 0. Null when there is no adf_meta table. Reads only.
3250
+ */
3251
+ static readSchemaVersion(db) {
3252
+ if (!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='adf_meta'").get()) return null;
3253
+ let r = db.prepare("SELECT value FROM adf_meta WHERE key = 'adf_schema_version'").get(), parse = (v) => {
3254
+ let n = parseInt(v, 10);
3255
+ return Number.isFinite(n) ? n : 0;
3256
+ };
3257
+ if (r) return parse(r.value);
3258
+ let r2 = db.prepare("SELECT value FROM adf_meta WHERE key = 'schema_version'").get();
3259
+ return r2 ? parse(r2.value) : 0;
3260
+ }
3261
+ /**
3262
+ * The schema version open() refuses for this file, or null when it may be
3263
+ * opened (and migrated). 1 <= v < 3 is refused. No version at all (0, or
3264
+ * unparsable) is refused unless the file has a clone's layout: a clone
3265
+ * whose adf_meta was deselected (before clone-fixup restamped the version)
3266
+ * has `adf_audit`, `adf_loop.loop` and no `adf_archive`. Null too without
3267
+ * an adf_meta table (the v0.2 check reports that). Reads only.
3268
+ */
3269
+ static refusedSchemaVersion(db) {
3270
+ let sv = _AdfDatabase.readSchemaVersion(db);
3271
+ if (sv === null) return null;
3272
+ if (sv > 0 && sv < ADF_OLDEST_MIGRATABLE_SCHEMA_VERSION) return sv;
3273
+ if (sv > 0) return null;
3274
+ let tables = new Set(db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map((t) => t.name)), loopCols = tables.has("adf_loop") ? new Set(db.prepare("PRAGMA table_info(adf_loop)").all().map((c) => c.name)) : /* @__PURE__ */ new Set();
3275
+ return tables.has("adf_audit") && !tables.has("adf_archive") && loopCols.has("loop") ? null : 0;
3276
+ }
3157
3277
  /** Retryable lock-contention error — distinct from the corruption path. */
3158
3278
  static lockedError(filePath) {
3159
3279
  return new Error(
@@ -3224,18 +3344,146 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3224
3344
  }
3225
3345
  /** Synchronous backup — only for `open()`, which cannot await mid-migration.
3226
3346
  * A file copy is safe HERE and only here: open() runs before any writer of
3227
- * this connection exists, so nothing can checkpoint underneath it. */
3347
+ * this connection exists, so nothing can checkpoint underneath it.
3348
+ * `complete`: the checkpoint folded the whole WAL first, so the copy holds
3349
+ * every committed write (a failed open may restore from it).
3350
+ * `reused`: a `.bak` already on disk has the same size and SHA-256 as the
3351
+ * checkpointed file (a migration that failed before and rolled back), so
3352
+ * it is kept as is instead of being moved aside and copied again. The
3353
+ * copy is fsynced. */
3228
3354
  static backupBeforeDestructiveSync(db, filePath) {
3229
- let backupPath = filePath + ".bak";
3355
+ let backupPath = filePath + ".bak", complete = !1;
3356
+ try {
3357
+ let [ck] = db.pragma("wal_checkpoint(TRUNCATE)");
3358
+ complete = ck?.busy === 0;
3359
+ } catch {
3360
+ }
3361
+ if (_AdfDatabase.sameFileContent(filePath, backupPath)) return { backupPath, complete, reused: !0 };
3230
3362
  try {
3231
3363
  renameSync(backupPath, _AdfDatabase.staleBackupName(backupPath));
3232
3364
  } catch {
3233
3365
  }
3366
+ return copyFileSync(filePath, backupPath), _AdfDatabase.fsyncFileSync(backupPath), { backupPath, complete, reused: !1 };
3367
+ }
3368
+ /** Both files exist with the same size and SHA-256. */
3369
+ static sameFileContent(a, b) {
3234
3370
  try {
3235
- db.pragma("wal_checkpoint(TRUNCATE)");
3371
+ if (statSync(a).size !== statSync(b).size) return !1;
3372
+ let hash = (p) => createHash("sha256").update(readFileSync3(p)).digest("hex");
3373
+ return hash(a) === hash(b);
3236
3374
  } catch {
3375
+ return !1;
3237
3376
  }
3238
- return copyFileSync(filePath, backupPath), backupPath;
3377
+ }
3378
+ static fsyncFileSync(path) {
3379
+ let fd = openSync(path, "r+");
3380
+ try {
3381
+ fsyncSync(fd);
3382
+ } finally {
3383
+ closeSync(fd);
3384
+ }
3385
+ }
3386
+ /** POSIX: make a rename in `dir` durable. Windows cannot open a directory for fsync. */
3387
+ static fsyncDirSync(dir) {
3388
+ if (process.platform !== "win32")
3389
+ try {
3390
+ let fd = openSync(dir, "r");
3391
+ try {
3392
+ fsyncSync(fd);
3393
+ } finally {
3394
+ closeSync(fd);
3395
+ }
3396
+ } catch {
3397
+ }
3398
+ }
3399
+ /**
3400
+ * After a failure that followed a committed migration (the constructor):
3401
+ * put the file back as its pre-migration backup. The `.bak` is kept (it is
3402
+ * removed only after a fully successful open). Null when restored, else
3403
+ * why not (the caller says so in its error).
3404
+ *
3405
+ * Only when no other connection can hold the file: none in this process,
3406
+ * no -wal left beside it (SQLite removes it at the last close), and an
3407
+ * exclusive lock taken with busy_timeout 0. The copy is fsynced before the
3408
+ * rename. On POSIX the rename happens while that lock is held, so nothing
3409
+ * can open the old file in between (a connection that opened it earlier
3410
+ * would have held a lock). On Windows the lock's own handle would block the
3411
+ * rename, so the probe closes first: the rename itself then fails while any
3412
+ * other program has the file open.
3413
+ *
3414
+ * Known POSIX residual (tracker §6, W5-H): the probe closes after the
3415
+ * rename, and SQLite's close unlinks `<file>-wal` by path. A program that
3416
+ * opens the restored file and writes in that window can lose its WAL.
3417
+ * Closing first would drop the lock, and better-sqlite3 cannot keep the
3418
+ * WAL on close (no SQLITE_FCNTL_PERSIST_WAL), so the window is not closed.
3419
+ */
3420
+ static restoreBackupSync(filePath) {
3421
+ let backupPath = filePath + ".bak";
3422
+ if (!existsSync2(backupPath)) return "there is no backup";
3423
+ let busy = "another connection has the file open";
3424
+ if (_AdfDatabase.openCounts.has(_AdfDatabase.canonicalKey(filePath)) || existsSync2(`${filePath}-wal`)) return busy;
3425
+ let restorePath = `${filePath}.restore`, probe = null;
3426
+ try {
3427
+ copyFileSync(backupPath, restorePath), _AdfDatabase.fsyncFileSync(restorePath);
3428
+ try {
3429
+ probe = new Database(filePath, { fileMustExist: !0 }), probe.pragma("busy_timeout = 0"), probe.pragma("locking_mode = EXCLUSIVE"), probe.exec("BEGIN IMMEDIATE; COMMIT");
3430
+ } catch {
3431
+ try {
3432
+ probe?.close();
3433
+ } catch {
3434
+ }
3435
+ probe = null;
3436
+ try {
3437
+ unlinkSync(restorePath);
3438
+ } catch {
3439
+ }
3440
+ return busy;
3441
+ }
3442
+ process.platform === "win32" && (probe.close(), probe = null);
3443
+ try {
3444
+ unlinkSync(`${filePath}-shm`);
3445
+ } catch {
3446
+ }
3447
+ return renameSync(restorePath, filePath), _AdfDatabase.fsyncDirSync(dirname2(filePath)), null;
3448
+ } catch (err2) {
3449
+ try {
3450
+ unlinkSync(restorePath);
3451
+ } catch {
3452
+ }
3453
+ let code = err2?.code;
3454
+ return code === "EPERM" || code === "EBUSY" || code === "EACCES" ? busy : `the restore failed (${err2 instanceof Error ? err2.message : String(err2)})`;
3455
+ } finally {
3456
+ try {
3457
+ probe?.close();
3458
+ } catch {
3459
+ }
3460
+ }
3461
+ }
3462
+ /**
3463
+ * A failed open(): roll back is the caller's; here the connection is
3464
+ * closed, the file is put back from the backup when `restore` (the failure
3465
+ * followed a committed migration), and the sidecars this open created are
3466
+ * reaped. Returns the sentence to add to the error when the file could not
3467
+ * be restored, else null.
3468
+ */
3469
+ static abandonOpen(db, filePath, opts) {
3470
+ try {
3471
+ db.close();
3472
+ } catch {
3473
+ }
3474
+ let note = null;
3475
+ if (opts.backup && !opts.restore)
3476
+ console.error(`[AdfDatabase] Open failed; the migration was rolled back, the file is unchanged. Backup kept at: ${filePath}.bak`);
3477
+ else if (opts.restore) {
3478
+ let why = opts.backup ? opts.backup.complete ? _AdfDatabase.restoreBackupSync(filePath) : "the backup may lack writes another connection had not checkpointed" : "there is no backup";
3479
+ why === null ? console.error(`[AdfDatabase] Open failed after migrating; restored ${filePath} from its backup, kept at ${filePath}.bak`) : (note = `${basename(filePath)} was migrated and could not be put back from its backup because ${why}; the backup is kept at ${filePath}.bak`, console.error(`[AdfDatabase] ${note}`));
3480
+ }
3481
+ if (!opts.walExistedBeforeOpen)
3482
+ try {
3483
+ _AdfDatabase.reapSidecars(filePath);
3484
+ } catch {
3485
+ }
3486
+ return note;
3239
3487
  }
3240
3488
  /** Remove a transient .bak file after a successful destructive operation. */
3241
3489
  static async removeBackup(filePath) {
@@ -3290,6 +3538,29 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3290
3538
  * 'reaped' — WAL folded into the main file, sidecars removed
3291
3539
  * 'error' — open/checkpoint/unlink failed; sidecars left in place
3292
3540
  */
3541
+ /**
3542
+ * True when no other connection holds `filePath`'s write lock: a probe
3543
+ * connection takes it with BEGIN IMMEDIATE (waiting at most
3544
+ * `busyTimeoutMs`) and gives it back at once. False on SQLITE_BUSY. Lets a
3545
+ * background job (the boot envelope sweep) pass over a file another
3546
+ * connection is writing instead of blocking in AdfWorkspace.open, whose
3547
+ * maintenance writes wait the full busy_timeout. Other errors throw.
3548
+ */
3549
+ static writeLockFree(filePath, busyTimeoutMs) {
3550
+ let db = null;
3551
+ try {
3552
+ return db = new Database(filePath, { fileMustExist: !0 }), db.pragma(`busy_timeout = ${Math.max(0, Math.floor(busyTimeoutMs))}`), db.exec("BEGIN IMMEDIATE; ROLLBACK"), !0;
3553
+ } catch (err2) {
3554
+ let code = err2?.code;
3555
+ if (typeof code == "string" && code.startsWith("SQLITE_BUSY")) return !1;
3556
+ throw err2;
3557
+ } finally {
3558
+ try {
3559
+ db?.close();
3560
+ } catch {
3561
+ }
3562
+ }
3563
+ }
3293
3564
  static reapSidecars(filePath) {
3294
3565
  let walPath = `${filePath}-wal`, shmPath = `${filePath}-shm`;
3295
3566
  if (!existsSync2(walPath) && !existsSync2(shmPath)) return "no-sidecars";
@@ -3376,7 +3647,40 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3376
3647
  }
3377
3648
  }
3378
3649
  static open(filePath, options) {
3379
- let walExistedBeforeOpen = existsSync2(`${filePath}-wal`) || existsSync2(`${filePath}-shm`), db = new Database(filePath), needsMigration = !1, integrityMode = "full";
3650
+ let walExistedBeforeOpen = existsSync2(`${filePath}-wal`) || existsSync2(`${filePath}-shm`);
3651
+ if (walExistedBeforeOpen) {
3652
+ let ro = null, refused = null;
3653
+ try {
3654
+ ro = new Database(filePath, { readonly: !0, fileMustExist: !0 }), refused = _AdfDatabase.refusedSchemaVersion(ro);
3655
+ } catch (err2) {
3656
+ if (_AdfDatabase.isBusyError(err2)) throw _AdfDatabase.lockedError(filePath);
3657
+ } finally {
3658
+ try {
3659
+ ro?.close();
3660
+ } catch {
3661
+ }
3662
+ }
3663
+ if (refused !== null) throw new AdfSchemaTooOldError(filePath, refused);
3664
+ }
3665
+ let db = new Database(filePath), needsMigration = !1, backup = null, migrated = !1, repaired = !1;
3666
+ if (!walExistedBeforeOpen) {
3667
+ let refused = null;
3668
+ try {
3669
+ refused = _AdfDatabase.refusedSchemaVersion(db);
3670
+ } catch (err2) {
3671
+ if (_AdfDatabase.isBusyError(err2))
3672
+ throw db.close(), _AdfDatabase.lockedError(filePath);
3673
+ }
3674
+ if (refused !== null) {
3675
+ db.close();
3676
+ try {
3677
+ _AdfDatabase.reapSidecars(filePath);
3678
+ } catch {
3679
+ }
3680
+ throw new AdfSchemaTooOldError(filePath, refused);
3681
+ }
3682
+ }
3683
+ let integrityMode = "full";
3380
3684
  if (!options?.forceIntegrityCheck) {
3381
3685
  let closedCleanly = !1;
3382
3686
  try {
@@ -3392,7 +3696,7 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3392
3696
  let checkPragma = integrityMode === "quick" ? "quick_check" : "integrity_check", status = (integrityMode === "none" ? [{ [checkPragma]: "ok" }] : db.pragma(checkPragma))[0]?.[checkPragma];
3393
3697
  if (status !== "ok")
3394
3698
  if (console.warn(`[AdfDatabase] Integrity check failed: ${status}`), db.close(), _AdfDatabase.tryRepair(filePath)) {
3395
- if (db = new Database(filePath), db.pragma("integrity_check")[0]?.integrity_check !== "ok")
3699
+ if (db = new Database(filePath), repaired = !0, db.pragma("integrity_check")[0]?.integrity_check !== "ok")
3396
3700
  throw db.close(), new Error(
3397
3701
  `ADF file is corrupt and auto-repair failed. A backup was saved at: ${filePath}.corrupt`
3398
3702
  );
@@ -3412,7 +3716,7 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3412
3716
  throw _AdfDatabase.lockedError(filePath);
3413
3717
  }
3414
3718
  if (console.warn("[AdfDatabase] Integrity check threw:", error), db.close(), _AdfDatabase.tryRepair(filePath))
3415
- db = new Database(filePath);
3719
+ db = new Database(filePath), repaired = !0;
3416
3720
  else
3417
3721
  throw new Error(
3418
3722
  "ADF file is corrupt and could not be repaired. Please delete and recreate the file."
@@ -3425,16 +3729,22 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3425
3729
  throw db.close(), new Error(
3426
3730
  "This ADF file does not have the v0.2 schema. Please delete and recreate the file."
3427
3731
  );
3732
+ let refused;
3733
+ try {
3734
+ refused = _AdfDatabase.refusedSchemaVersion(db);
3735
+ } catch (err2) {
3736
+ throw _AdfDatabase.isBusyError(err2) ? _AdfDatabase.lockedError(filePath) : new Error(
3737
+ `Cannot read the schema version of ${basename(filePath)}: ${err2 instanceof Error ? err2.message : String(err2)}. The file was not migrated.`,
3738
+ { cause: err2 }
3739
+ );
3740
+ }
3741
+ if (refused !== null)
3742
+ throw new AdfSchemaTooOldError(filePath, refused, { unchanged: !walExistedBeforeOpen && !repaired });
3428
3743
  try {
3429
3744
  db.prepare("DELETE FROM adf_meta WHERE key = ?").run(CLEAN_CLOSE_META_KEY), db.pragma("wal_checkpoint(PASSIVE)");
3430
3745
  } catch {
3431
3746
  }
3432
- let sv = (() => {
3433
- let r = db.prepare("SELECT value FROM adf_meta WHERE key = 'adf_schema_version'").get();
3434
- if (r) return parseInt(r.value, 10);
3435
- let r2 = db.prepare("SELECT value FROM adf_meta WHERE key = 'schema_version'").get();
3436
- return r2 ? parseInt(r2.value, 10) : 0;
3437
- })();
3747
+ let sv = _AdfDatabase.readSchemaVersion(db) ?? 0;
3438
3748
  if (needsMigration = sv < ADF_LATEST_SCHEMA_VERSION, !needsMigration) {
3439
3749
  if (sv > ADF_LATEST_SCHEMA_VERSION) {
3440
3750
  let msg = `[AdfDatabase] Schema version ${sv} is newer than this runtime's v${ADF_LATEST_SCHEMA_VERSION} for ${filePath}. Opening without migration; writes from an older runtime may not maintain newer invariants.`;
@@ -3450,11 +3760,13 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3450
3760
  }
3451
3761
  if (needsMigration)
3452
3762
  try {
3453
- _AdfDatabase.backupBeforeDestructiveSync(db, filePath), console.log(`[AdfDatabase] Backup created before migration (v${sv} \u2192 v${ADF_LATEST_SCHEMA_VERSION}): ${filePath}.bak`);
3763
+ let made = _AdfDatabase.backupBeforeDestructiveSync(db, filePath);
3764
+ backup = made, console.log(made.reused ? `[AdfDatabase] Backup before migration (v${sv} \u2192 v${ADF_LATEST_SCHEMA_VERSION}) already on disk, unchanged: ${filePath}.bak` : `[AdfDatabase] Backup created before migration (v${sv} \u2192 v${ADF_LATEST_SCHEMA_VERSION}): ${filePath}.bak`);
3454
3765
  } catch (e) {
3455
3766
  console.warn("[AdfDatabase] Could not create pre-migration backup:", e);
3456
3767
  }
3457
- let cols = db.prepare("PRAGMA table_info(adf_loop)").all();
3768
+ db.exec("BEGIN IMMEDIATE");
3769
+ let unversionedClone = sv === 0, cols = db.prepare("PRAGMA table_info(adf_loop)").all();
3458
3770
  new Set(cols.map((c) => c.name)).has("created_at") || (db.exec("ALTER TABLE adf_loop ADD COLUMN model TEXT"), db.exec("ALTER TABLE adf_loop ADD COLUMN tokens INTEGER"), db.exec("ALTER TABLE adf_loop ADD COLUMN created_at INTEGER NOT NULL DEFAULT 0")), db.exec("DROP TABLE IF EXISTS adf_archive"), db.prepare(
3459
3771
  "SELECT name FROM sqlite_master WHERE type='table' AND name='adf_audit'"
3460
3772
  ).get() || db.exec(`
@@ -3972,11 +4284,24 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3972
4284
  }
3973
4285
  } catch {
3974
4286
  }
3975
- needsMigration && _AdfDatabase.removeBackupSync(filePath);
4287
+ if (!db.inTransaction) throw new Error(`The migration of ${basename(filePath)} was rolled back by SQLite.`);
4288
+ unversionedClone && (db.prepare("INSERT INTO adf_meta (key, value, protection) VALUES ('adf_schema_version', ?, 'readonly') ON CONFLICT(key) DO UPDATE SET value = excluded.value, protection = 'readonly'").run(String(ADF_LATEST_SCHEMA_VERSION)), console.log(`[AdfDatabase] Stamped schema v${ADF_LATEST_SCHEMA_VERSION} on an unversioned clone: ${filePath}`)), db.exec("COMMIT"), migrated = !0;
3976
4289
  } catch (error) {
3977
- throw needsMigration && console.error(`[AdfDatabase] Migration failed. Backup preserved at: ${filePath}.bak`), db.close(), error;
4290
+ if (db.inTransaction)
4291
+ try {
4292
+ db.exec("ROLLBACK");
4293
+ } catch {
4294
+ }
4295
+ throw _AdfDatabase.abandonOpen(db, filePath, { backup, restore: !1, walExistedBeforeOpen }), error;
3978
4296
  }
3979
- return new _AdfDatabase(db, filePath);
4297
+ let opened;
4298
+ try {
4299
+ opened = new _AdfDatabase(db, filePath);
4300
+ } catch (error) {
4301
+ let note = _AdfDatabase.abandonOpen(db, filePath, { backup, restore: migrated, walExistedBeforeOpen });
4302
+ throw note ? new Error(`${error instanceof Error ? error.message : String(error)}. ${note}`, { cause: error }) : error;
4303
+ }
4304
+ return backup && _AdfDatabase.removeBackupSync(filePath), opened;
3980
4305
  }
3981
4306
  /**
3982
4307
  * Create a new ADF without replacing an existing destination.
@@ -4048,7 +4373,7 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4048
4373
  let overrideMap = new Map(options.tools.map((t) => [t.name, t]));
4049
4374
  tools = tools.map((t) => overrideMap.has(t.name) ? { ...t, ...overrideMap.get(t.name) } : t);
4050
4375
  for (let t of options.tools)
4051
- tools.some((dt) => dt.name === t.name) || tools.push(t);
4376
+ tools.some((dt) => dt.name === t.name) || tools.push({ visible: !0, ...t });
4052
4377
  }
4053
4378
  let mergedTriggers = { ...base.triggers };
4054
4379
  if (options.triggers)
@@ -4075,7 +4400,7 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4075
4400
  context: {
4076
4401
  ...base.context,
4077
4402
  ...options.context,
4078
- audit: { ...base.context.audit, ...options.context?.audit },
4403
+ audit: { ...AUDIT_DEFAULTS, ...base.context.audit, ...options.context?.audit },
4079
4404
  dynamic_instructions: { ...base.context.dynamic_instructions, ...options.context?.dynamic_instructions }
4080
4405
  },
4081
4406
  tools,
@@ -4160,10 +4485,10 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4160
4485
  * crash leftovers owned by the sweeps — left untouched here. Errors from
4161
4486
  * `fn` (and the open) propagate to the caller's existing catch.
4162
4487
  */
4163
- static peekReadonly(filePath, fn) {
4488
+ static peekReadonly(filePath, fn, busyTimeoutMs) {
4164
4489
  let hadSidecars = existsSync2(`${filePath}-wal`) || existsSync2(`${filePath}-shm`), db = null;
4165
4490
  try {
4166
- return db = new Database(filePath, { readonly: !0 }), fn(db);
4491
+ return db = new Database(filePath, { readonly: !0 }), busyTimeoutMs !== void 0 && db.pragma(`busy_timeout = ${Math.max(0, Math.floor(busyTimeoutMs))}`), fn(db);
4167
4492
  } finally {
4168
4493
  try {
4169
4494
  db?.close();
@@ -4198,6 +4523,7 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4198
4523
  throw new Error(
4199
4524
  "Snapshot timed out: the agent is being written to continuously \u2014 stop it and try again"
4200
4525
  );
4526
+ return 100;
4201
4527
  }
4202
4528
  });
4203
4529
  } finally {
@@ -4363,6 +4689,23 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4363
4689
  return null;
4364
4690
  }
4365
4691
  }
4692
+ /**
4693
+ * What the boot envelope sweep decides on before opening a file, read
4694
+ * read-only: its schema version (null without an adf_meta table, 0 with no
4695
+ * version) and its agent id (null without a config). Errors propagate;
4696
+ * `busyTimeoutMs` bounds the wait for a lock (the sweep's 25 ms).
4697
+ */
4698
+ static peekSweepStatus(filePath, busyTimeoutMs) {
4699
+ return _AdfDatabase.peekReadonly(filePath, (db) => {
4700
+ let schemaVersion = _AdfDatabase.readSchemaVersion(db), row = !!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='adf_config'").get() ? db.prepare("SELECT config_json FROM adf_config WHERE id = 1").get() : void 0, agentId = null;
4701
+ try {
4702
+ let id = row ? JSON.parse(row.config_json).id : void 0;
4703
+ typeof id == "string" && id && (agentId = id);
4704
+ } catch {
4705
+ }
4706
+ return { schemaVersion, agentId };
4707
+ }, busyTimeoutMs);
4708
+ }
4366
4709
  static peekBootStatusDetailed(filePath) {
4367
4710
  try {
4368
4711
  return _AdfDatabase.peekReadonly(filePath, (db) => {
@@ -4528,8 +4871,8 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4528
4871
  static peekIdentityPurposes(filePath, prefix) {
4529
4872
  try {
4530
4873
  return _AdfDatabase.peekReadonly(filePath, (db) => db.prepare(
4531
- "SELECT purpose FROM adf_identity WHERE purpose LIKE ? || '%'"
4532
- ).all(prefix).map((r) => r.purpose));
4874
+ "SELECT purpose FROM adf_identity WHERE substr(purpose, 1, length(?)) = ?"
4875
+ ).all(prefix, prefix).map((r) => r.purpose));
4533
4876
  } catch {
4534
4877
  return [];
4535
4878
  }
@@ -4584,6 +4927,8 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4584
4927
  "INSERT INTO adf_loop (seq, role, content_json, model, tokens, created_at, ord, loop) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
4585
4928
  ), this.stmts.clearLoop = this.db.prepare("DELETE FROM adf_loop WHERE loop = ?"), this.stmts.getLoopCount = this.db.prepare("SELECT COUNT(*) as count FROM adf_loop WHERE loop = ?"), this.stmts.getLastAssistantTokens = this.db.prepare(
4586
4929
  "SELECT tokens FROM adf_loop WHERE loop = ? AND role = 'assistant' AND tokens IS NOT NULL ORDER BY COALESCE(ord, seq) DESC, seq DESC LIMIT 1"
4930
+ ), this.stmts.getAssistantTokenRows = this.db.prepare(
4931
+ "SELECT seq, tokens FROM adf_loop WHERE loop = ? AND role = 'assistant' AND tokens IS NOT NULL ORDER BY COALESCE(ord, seq), seq"
4587
4932
  ), this.stmts.getInboxMessages = this.db.prepare(
4588
4933
  "SELECT * FROM adf_inbox ORDER BY received_at DESC"
4589
4934
  ), this.stmts.getInboxByStatus = this.db.prepare(
@@ -4666,7 +5011,7 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4666
5011
  ), this.stmts.deleteIdentity = this.db.prepare(
4667
5012
  "DELETE FROM adf_identity WHERE purpose = ?"
4668
5013
  ), this.stmts.listIdentityByPrefix = this.db.prepare(
4669
- "SELECT purpose FROM adf_identity WHERE purpose LIKE ? || '%'"
5014
+ "SELECT purpose FROM adf_identity WHERE substr(purpose, 1, length(?)) = ?"
4670
5015
  ), this.stmts.getIdentityFull = this.db.prepare(
4671
5016
  "SELECT purpose, value, encryption_algo, salt, kdf_params, code_access FROM adf_identity WHERE purpose = ?"
4672
5017
  ), this.stmts.getAllIdentityFull = this.db.prepare(
@@ -4703,6 +5048,8 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4703
5048
  "UPDATE adf_tasks SET requires_authorization = 1 WHERE id = ?"
4704
5049
  ), this.stmts.setTaskExecutorManaged = this.db.prepare(
4705
5050
  "UPDATE adf_tasks SET executor_managed = 1 WHERE id = ?"
5051
+ ), this.stmts.setTaskApprovalMeta = this.db.prepare(
5052
+ "UPDATE adf_tasks SET approval_meta = ? WHERE id = ?"
4706
5053
  ), this.stmts.updateTaskArgs = this.db.prepare(
4707
5054
  "UPDATE adf_tasks SET args = ? WHERE id = ?"
4708
5055
  ), this.stmts.getTasksByStatus = this.db.prepare(
@@ -4781,11 +5128,16 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4781
5128
  deleteIdentity(purpose) {
4782
5129
  return this.stmts.deleteIdentity.run(purpose).changes > 0;
4783
5130
  }
5131
+ /**
5132
+ * Prefix matches are exact and case-sensitive (substr, not LIKE): with LIKE,
5133
+ * `_` and `%` in a server or adapter name were wildcards and ASCII case was
5134
+ * ignored, so detaching one server deleted another's credentials.
5135
+ */
4784
5136
  deleteIdentityByPrefix(prefix) {
4785
- return this.db.prepare("DELETE FROM adf_identity WHERE purpose LIKE ? || '%'").run(prefix).changes;
5137
+ return this.db.prepare("DELETE FROM adf_identity WHERE substr(purpose, 1, length(?)) = ?").run(prefix, prefix).changes;
4786
5138
  }
4787
5139
  listIdentityPurposes(prefix) {
4788
- return prefix ? this.stmts.listIdentityByPrefix.all(prefix).map((r) => r.purpose) : this.stmts.listAllIdentityPurposes.all().map((r) => r.purpose);
5140
+ return prefix ? this.stmts.listIdentityByPrefix.all(prefix, prefix).map((r) => r.purpose) : this.stmts.listAllIdentityPurposes.all().map((r) => r.purpose);
4789
5141
  }
4790
5142
  // --- Encrypted identity methods ---
4791
5143
  /**
@@ -4909,9 +5261,27 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4909
5261
  }
4910
5262
  return added && this.setConfig(config), config;
4911
5263
  }
5264
+ /**
5265
+ * setConfig calls through this connection: with dataVersion(), a cheap "did
5266
+ * the config change" check for callers that cache it (RuntimeService's
5267
+ * running-agent config).
5268
+ */
5269
+ configWrites = 0;
5270
+ dataVersionStmt = null;
5271
+ configWriteCount() {
5272
+ return this.configWrites;
5273
+ }
5274
+ /**
5275
+ * SQLite's `PRAGMA data_version`: changes when another connection (another
5276
+ * process, or another handle in this one) commits to the file; this
5277
+ * connection's own writes leave it unchanged (configWriteCount counts those).
5278
+ */
5279
+ dataVersion() {
5280
+ return this.dataVersionStmt ??= this.db.prepare("PRAGMA data_version").pluck(), this.dataVersionStmt.get();
5281
+ }
4912
5282
  setConfig(config) {
4913
5283
  let now = (/* @__PURE__ */ new Date()).toISOString();
4914
- config.metadata.updated_at = now, this.stmts.setConfig.run(JSON.stringify(config), now), this.setMeta("adf_name", config.name), config.handle && this.setMeta("adf_handle", config.handle), this.setMeta("adf_updated_at", now);
5284
+ config.metadata.updated_at = now, this.stmts.setConfig.run(JSON.stringify(config), now), this.configWrites++, this.setMeta("adf_name", config.name), config.handle && this.setMeta("adf_handle", config.handle), this.setMeta("adf_updated_at", now);
4915
5285
  }
4916
5286
  // ===========================================================================
4917
5287
  // Loop Table
@@ -4970,7 +5340,25 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4970
5340
  opts?.ord ?? null,
4971
5341
  loop
4972
5342
  );
4973
- return this.bumpLoopRevision(loop), Number(result.lastInsertRowid);
5343
+ return this.bumpLoopRevision(loop), content.some((block) => block.type === "file_ref") && this.getFileRefRegistry().noteContent(content), Number(result.lastInsertRowid);
5344
+ }
5345
+ /** File-reference state of this file, shared by every connection to it (see file-ref-registry.ts). */
5346
+ getFileRefRegistry() {
5347
+ return fileRefRegistry(this, this.filePath === ":memory:" ? null : _AdfDatabase.canonicalKey(this.filePath));
5348
+ }
5349
+ /**
5350
+ * Rows of every loop that hold a `file_ref` block (a prefilter: the caller
5351
+ * parses and checks). One scan of adf_loop; callers run it only when a
5352
+ * referenced file is renamed or deleted.
5353
+ */
5354
+ getLoopRowsWithFileRefs() {
5355
+ return this.db.prepare(
5356
+ `SELECT seq, loop, content_json FROM adf_loop WHERE instr(content_json, '"file_ref"') > 0`
5357
+ ).all();
5358
+ }
5359
+ /** Replace one row's content in place (file references rewritten on rename/delete). */
5360
+ updateLoopContent(loop, seq, content) {
5361
+ this.db.prepare("UPDATE adf_loop SET content_json = ? WHERE seq = ? AND loop = ?").run(JSON.stringify(content), seq, loop), this.bumpLoopRevision(loop);
4974
5362
  }
4975
5363
  /** Wipe ONE stream. The whole-table wipe is clearAllLoops(). */
4976
5364
  clearLoop(loop) {
@@ -4999,6 +5387,15 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4999
5387
  let row = this.stmts.getLastAssistantTokens.get(loop);
5000
5388
  return _AdfDatabase.parseLoopTokens(row?.tokens);
5001
5389
  }
5390
+ /** Every assistant row's usage record, in loop order (oldest first). */
5391
+ getAssistantTokenRows(loop) {
5392
+ let rows = this.stmts.getAssistantTokenRows.all(loop), out = [];
5393
+ for (let row of rows) {
5394
+ let tokens = _AdfDatabase.parseLoopTokens(row.tokens);
5395
+ tokens && out.push({ seq: row.seq, tokens });
5396
+ }
5397
+ return out;
5398
+ }
5002
5399
  /** Loop rows in display order: each row's seq plus its ordering key
5003
5400
  * (COALESCE(ord, seq)). Positional operations resolve boundaries against
5004
5401
  * ordKey, never raw seq — an ord'd summary sorts at its override position. */
@@ -5086,6 +5483,24 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
5086
5483
  transaction(fn) {
5087
5484
  return this.db.transaction(fn)();
5088
5485
  }
5486
+ /**
5487
+ * `fn` in a BEGIN IMMEDIATE transaction: the file's write lock is taken
5488
+ * before `fn` reads, so a check-then-write in it is atomic against every
5489
+ * other connection (another process provisioning the same file). Inside a
5490
+ * transaction already, a savepoint. `busyTimeoutMs`: how long BEGIN waits
5491
+ * for another connection's write lock before SQLITE_BUSY (the connection's
5492
+ * own busy_timeout, 5 s, otherwise).
5493
+ */
5494
+ immediateTransaction(fn, busyTimeoutMs) {
5495
+ if (busyTimeoutMs === void 0 || this.db.inTransaction) return this.db.transaction(fn).immediate();
5496
+ let before = this.db.pragma("busy_timeout", { simple: !0 });
5497
+ this.db.pragma(`busy_timeout = ${Math.max(0, Math.floor(busyTimeoutMs))}`);
5498
+ try {
5499
+ return this.db.transaction(fn).immediate();
5500
+ } finally {
5501
+ this.db.pragma(`busy_timeout = ${before}`);
5502
+ }
5503
+ }
5089
5504
  // ===========================================================================
5090
5505
  // Inbox
5091
5506
  // ===========================================================================
@@ -5482,6 +5897,9 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
5482
5897
  setTaskExecutorManaged(id, _value) {
5483
5898
  this.stmts.setTaskExecutorManaged.run(id);
5484
5899
  }
5900
+ setTaskApprovalMeta(id, approvalMeta) {
5901
+ this.stmts.setTaskApprovalMeta.run(approvalMeta, id);
5902
+ }
5485
5903
  updateTaskArgs(id, args) {
5486
5904
  this.stmts.updateTaskArgs.run(args, id);
5487
5905
  }
@@ -5624,7 +6042,9 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
5624
6042
  }
5625
6043
  close() {
5626
6044
  if (this.closed) return;
5627
- if (this.closed = !0, _AdfDatabase.decrementOpen(this.filePath) === 0)
6045
+ this.closed = !0;
6046
+ let remaining = _AdfDatabase.decrementOpen(this.filePath);
6047
+ if (remaining === 0 && this.getFileRefRegistry().media.clear(), remaining === 0)
5628
6048
  try {
5629
6049
  this.db.prepare(
5630
6050
  "INSERT INTO adf_meta (key, value, protection) VALUES (?, ?, 'readonly') ON CONFLICT(key) DO UPDATE SET value = excluded.value"
@@ -6145,6 +6565,14 @@ var CODE_FORBIDDEN_WRITE_RE = /^crypto:/;
6145
6565
  function isCodeForbiddenIdentityWrite(purpose) {
6146
6566
  return CODE_FORBIDDEN_WRITE_RE.test(purpose);
6147
6567
  }
6568
+ function parseLoopContent(json) {
6569
+ try {
6570
+ let parsed = JSON.parse(json);
6571
+ return Array.isArray(parsed) ? parsed : [];
6572
+ } catch {
6573
+ return [];
6574
+ }
6575
+ }
6148
6576
  var AdfWorkspace = class _AdfWorkspace {
6149
6577
  db;
6150
6578
  filePath;
@@ -6812,6 +7240,14 @@ var AdfWorkspace = class _AdfWorkspace {
6812
7240
  getAgentConfig() {
6813
7241
  return this.db.getConfig();
6814
7242
  }
7243
+ /** Config writes through this file's connection so far (AdfDatabase.configWriteCount); no SQLite read. */
7244
+ configWriteCount() {
7245
+ return this.db.configWriteCount();
7246
+ }
7247
+ /** Commits by other connections to this file (AdfDatabase.dataVersion); no table read. */
7248
+ dataVersion() {
7249
+ return this.db.dataVersion();
7250
+ }
6815
7251
  setAgentConfig(config) {
6816
7252
  let previous = null;
6817
7253
  try {
@@ -7151,6 +7587,10 @@ var AdfWorkspace = class _AdfWorkspace {
7151
7587
  getLastAssistantTokens() {
7152
7588
  return this.db.getLastAssistantTokens(this.boundLoop);
7153
7589
  }
7590
+ /** Every assistant row's usage record in loop order — see AdfDatabase.getAssistantTokenRows. */
7591
+ getAssistantTokenRows() {
7592
+ return this.db.getAssistantTokenRows(this.boundLoop);
7593
+ }
7154
7594
  /** Meta key of this loop's context baseline: bare for main, `:<loop>`-suffixed
7155
7595
  * for side loops (one baseline per cognition stream). */
7156
7596
  contextBaselineKey() {
@@ -7522,12 +7962,12 @@ var AdfWorkspace = class _AdfWorkspace {
7522
7962
  let deleted2 = !1;
7523
7963
  return this.db.transaction(() => {
7524
7964
  archive && this.db.insertAudit("file", { ref: relativePath, entryCount: 1, sizeBytes: archive.json.length, data: archive.data }), opts?.force && entry && this.db.setFileProtection(relativePath, "none"), deleted2 = this.db.deleteFile(relativePath);
7525
- }), deleted2 && (this.emitUmbilical("file.deleted", { path: relativePath }), this.emitFileChange(relativePath, "deleted", void 0, entry?.content, metadata)), deleted2;
7965
+ }), deleted2 && (this.emitUmbilical("file.deleted", { path: relativePath }), this.emitFileChange(relativePath, "deleted", void 0, entry?.content, metadata), this.rewriteFileRefs([{ op: "delete", path: relativePath }])), deleted2;
7526
7966
  }
7527
7967
  let previous = this.onFileChangeCallback ? this.db.readFile(relativePath)?.content : void 0;
7528
7968
  opts?.force && this.db.fileExists(relativePath) && this.db.setFileProtection(relativePath, "none");
7529
7969
  let deleted = this.db.deleteFile(relativePath);
7530
- return deleted && (this.emitUmbilical("file.deleted", { path: relativePath }), this.emitFileChange(relativePath, "deleted", void 0, previous, metadata)), deleted;
7970
+ return deleted && (this.emitUmbilical("file.deleted", { path: relativePath }), this.emitFileChange(relativePath, "deleted", void 0, previous, metadata), this.rewriteFileRefs([{ op: "delete", path: relativePath }])), deleted;
7531
7971
  }
7532
7972
  getFileMeta(path) {
7533
7973
  return this.db.getFileMeta(path);
@@ -7547,7 +7987,10 @@ var AdfWorkspace = class _AdfWorkspace {
7547
7987
  return this.db.fileExists(relativePath);
7548
7988
  }
7549
7989
  renameInternalFile(oldPath, newPath) {
7550
- let previous = this.onFileChangeCallback ? this.db.readFile(oldPath)?.content : void 0, metadata = this.fileChangeMeta(oldPath), renamed = this.db.renameFile(oldPath, newPath);
7990
+ let previous = this.onFileChangeCallback ? this.db.readFile(oldPath)?.content : void 0, metadata = this.fileChangeMeta(oldPath), renamed = this.db.transaction(() => {
7991
+ let done = this.db.renameFile(oldPath, newPath);
7992
+ return done && this.rewriteFileRefs([{ op: "rename", from: oldPath, to: newPath }]), done;
7993
+ });
7551
7994
  return renamed && (this.emitFileChange(oldPath, "deleted", void 0, previous, metadata), this.emitFileChange(newPath, "created", previous, void 0, this.fileChangeMeta(newPath))), renamed;
7552
7995
  }
7553
7996
  renameFolder(oldPrefix, newPrefix) {
@@ -7555,14 +7998,15 @@ var AdfWorkspace = class _AdfWorkspace {
7555
7998
  path: file.path,
7556
7999
  content: notify ? this.db.readFile(file.path)?.content : void 0,
7557
8000
  metadata: notify ? this.getFileMeta(file.path) : null
7558
- })), count = this.db.renameFolder(oldPrefix, newPrefix);
7559
- if (count > 0) {
7560
- let replacementPrefix = newPrefix.endsWith("/") ? newPrefix : `${newPrefix}/`;
8001
+ })), replacementPrefix = newPrefix.endsWith("/") ? newPrefix : `${newPrefix}/`, count = this.db.transaction(() => {
8002
+ let changed = this.db.renameFolder(oldPrefix, newPrefix);
8003
+ return changed > 0 && this.rewriteFileRefs(moved.map((file) => ({ op: "rename", from: file.path, to: replacementPrefix + file.path.slice(prefix.length) }))), changed;
8004
+ });
8005
+ if (count > 0)
7561
8006
  for (let file of moved) {
7562
8007
  let newPath = replacementPrefix + file.path.slice(prefix.length);
7563
8008
  this.emitFileChange(file.path, "deleted", void 0, file.content, file.metadata), this.emitFileChange(newPath, "created", file.content, void 0, this.fileChangeMeta(newPath));
7564
8009
  }
7565
- }
7566
8010
  return count;
7567
8011
  }
7568
8012
  // The sinks are per-agent, not per-loop: registering through a forLoop() view
@@ -7597,8 +8041,38 @@ var AdfWorkspace = class _AdfWorkspace {
7597
8041
  } catch {
7598
8042
  }
7599
8043
  }
8044
+ /**
8045
+ * Keep file references (`file_ref` blocks) pointing at the right file: a
8046
+ * rename or move rewrites the path, a delete leaves a tombstone. Rewrites
8047
+ * every loop row that names the file, then every live session's copy. Only
8048
+ * files some row references cost a scan; the set of referenced paths is
8049
+ * built once per process and kept current by loop writes.
8050
+ */
8051
+ rewriteFileRefs(changes) {
8052
+ if (changes.length === 0) return;
8053
+ let registry3 = this.db.getFileRefRegistry();
8054
+ try {
8055
+ if (!registry3.referenced) {
8056
+ let referenced = /* @__PURE__ */ new Set();
8057
+ for (let row of this.db.getLoopRowsWithFileRefs())
8058
+ for (let block of parseLoopContent(row.content_json))
8059
+ isFileRefBlock(block) && !block.file_ref.deleted && referenced.add(block.file_ref.path);
8060
+ registry3.referenced = referenced;
8061
+ }
8062
+ if (!registry3.affects(changes)) return;
8063
+ this.db.transaction(() => {
8064
+ for (let row of this.db.getLoopRowsWithFileRefs()) {
8065
+ let content = parseLoopContent(row.content_json), next = applyFileRefChanges(content, changes);
8066
+ next !== content && this.db.updateLoopContent(row.loop, row.seq, next);
8067
+ }
8068
+ }), registry3.noteChanges(changes);
8069
+ } catch (error) {
8070
+ console.error("[AdfWorkspace] Could not update file references:", error);
8071
+ }
8072
+ registry3.notifyHolders(changes);
8073
+ }
7600
8074
  emitFileChange(path, operation, content, previousContent, metadata) {
7601
- if (this.skillIndexer.notifyPath(path), !this.onFileChangeCallback) return;
8075
+ if (this.db.getFileRefRegistry().invalidate(path), this.skillIndexer.notifyPath(path), this.emitDataChange("files"), !this.onFileChangeCallback) return;
7602
8076
  let mimeType = metadata?.mime_type ?? this.getMimeType(path);
7603
8077
  this.onFileChangeCallback({
7604
8078
  path,
@@ -7738,6 +8212,9 @@ var AdfWorkspace = class _AdfWorkspace {
7738
8212
  setTaskExecutorManaged(id, value) {
7739
8213
  this.db.setTaskExecutorManaged(id, value);
7740
8214
  }
8215
+ setTaskApprovalMeta(id, approvalMeta) {
8216
+ this.db.setTaskApprovalMeta(id, approvalMeta);
8217
+ }
7741
8218
  updateTaskArgs(id, args) {
7742
8219
  this.db.updateTaskArgs(id, args);
7743
8220
  }
@@ -7884,8 +8361,39 @@ var AdfWorkspace = class _AdfWorkspace {
7884
8361
  transaction(fn) {
7885
8362
  return this.db.transaction(fn);
7886
8363
  }
8364
+ /** AdfDatabase.immediateTransaction: an atomic check-then-write against other connections to the file. */
8365
+ immediateTransaction(fn, busyTimeoutMs) {
8366
+ return this.db.immediateTransaction(fn, busyTimeoutMs);
8367
+ }
7887
8368
  };
7888
8369
 
8370
+ // src/main/runtime/loop-usage.ts
8371
+ function summarizeLoopUsage(entries) {
8372
+ let totals = createUsageTotals(), byModel = /* @__PURE__ */ new Map(), usageRows = 0, estimatedRows = 0;
8373
+ for (let entry of entries) {
8374
+ if (!entry.tokens) continue;
8375
+ usageRows++, hasReportedUsage(entry.tokens) || estimatedRows++, addUsage(totals, entry.tokens);
8376
+ let model = entry.model ?? "unknown", bucket = byModel.get(model);
8377
+ bucket || (bucket = { model, rows: 0, ...createUsageTotals() }, byModel.set(model, bucket)), bucket.rows++, addUsage(bucket, entry.tokens);
8378
+ }
8379
+ return {
8380
+ loopRows: entries.length,
8381
+ usageRows,
8382
+ estimatedRows,
8383
+ totals,
8384
+ byModel: Array.from(byModel.values()).sort((a, b) => b.total - a.total)
8385
+ };
8386
+ }
8387
+ function createUsageTotals() {
8388
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
8389
+ }
8390
+ function addUsage(target, usage) {
8391
+ target.input += usage.input ?? 0, target.output += usage.output ?? 0, target.cacheRead += usage.cache_read ?? 0, target.cacheWrite += usage.cache_write ?? 0, target.total = target.input + target.output;
8392
+ }
8393
+ function hasReportedUsage(t) {
8394
+ return t.estimated ? !1 : t.cache_read !== void 0 || t.cache_write !== void 0 || t.reasoning !== void 0 || t.cost_usd !== void 0;
8395
+ }
8396
+
7889
8397
  // src/main/tools/shell/commands/status.ts
7890
8398
  function staticArgValue3(arg) {
7891
8399
  if (!arg) return null;
@@ -8062,7 +8570,7 @@ async function configTools(ctx) {
8062
8570
  var statusHandler = {
8063
8571
  name: "status",
8064
8572
  summary: "Show agent status summary",
8065
- helpText: "status State, token usage, budget remaining",
8573
+ helpText: "status State, model, token usage in the current loop",
8066
8574
  category: "identity",
8067
8575
  resolvedTools: ["sys_get_config"],
8068
8576
  async execute(ctx) {
@@ -8072,7 +8580,7 @@ var statusHandler = {
8072
8580
  `Model: ${config.model?.provider ?? ""}/${config.model?.model_id ?? ""}`,
8073
8581
  `Autonomy: ${config.autonomous ? "enabled" : "disabled"}`
8074
8582
  ];
8075
- return config.limits?.token_budget && lines.push(`Budget: ${config.limits.token_budget} tokens`), ok(lines.join(`
8583
+ return lines.push(`Tokens: ${tokenUsageLine(ctx)}`), ok(lines.join(`
8076
8584
  `));
8077
8585
  }
8078
8586
  }, stateHandler = {
@@ -8231,6 +8739,17 @@ var statusHandler = {
8231
8739
  falseHandler,
8232
8740
  sleepHandler
8233
8741
  ];
8742
+ function tokenUsageLine(ctx) {
8743
+ let usage;
8744
+ try {
8745
+ usage = summarizeLoopUsage(ctx.workspace.getLoop());
8746
+ } catch {
8747
+ return "unavailable";
8748
+ }
8749
+ if (usage.usageRows === 0) return "none recorded in the current loop";
8750
+ let n = (v) => v.toLocaleString("en-US"), { input, output, cacheRead, total } = usage.totals, cached = cacheRead > 0 ? ` (${n(cacheRead)} cached)` : "", estimated = usage.estimatedRows > 0 ? ` (${usage.estimatedRows} estimated)` : "", calls = `${usage.usageRows} call${usage.usageRows === 1 ? "" : "s"}${estimated}`;
8751
+ return `${n(total)} total: ${n(input)} in${cached}, ${n(output)} out; ${calls} in the current loop`;
8752
+ }
8234
8753
 
8235
8754
  // src/main/tools/shell/commands/meta.ts
8236
8755
  var metaHandler = {
@@ -8426,7 +8945,7 @@ var CATEGORY_ORDER = [
8426
8945
  resolvedTools: [],
8427
8946
  async execute(ctx) {
8428
8947
  if (ctx.args.length > 0) {
8429
- let { getCommand: getCommand2 } = await import("./commands-KPJWFRUE.mjs"), handler = getCommand2(ctx.args[0]);
8948
+ let { getCommand: getCommand2 } = await import("./commands-E4EQUHE5.mjs"), handler = getCommand2(ctx.args[0]);
8430
8949
  return handler ? ok(handler.helpText) : ok(`Unknown command: ${ctx.args[0]}`);
8431
8950
  }
8432
8951
  let commands = getAllCommands(), grouped = /* @__PURE__ */ new Map();
@@ -8495,10 +9014,13 @@ export {
8495
9014
  verifyEd25519,
8496
9015
  rawPublicKeyToSpki,
8497
9016
  didToPublicKey,
9017
+ ADF_LATEST_SCHEMA_VERSION,
8498
9018
  AdfDatabase,
8499
9019
  withSource,
8500
9020
  withLoop,
8501
9021
  withTurn,
9022
+ currentTurnId,
9023
+ currentAgentId,
8502
9024
  currentSourceOrUnknown,
8503
9025
  init_execution_context,
8504
9026
  SKILLS_REGISTRY_PATH,
@@ -8509,6 +9031,7 @@ export {
8509
9031
  rawX25519PrivateToPkcs8,
8510
9032
  rawX25519PublicToSpki,
8511
9033
  extractRawX25519PublicKey,
9034
+ openKeySlot,
8512
9035
  sealWithDek,
8513
9036
  openWithDek,
8514
9037
  envelopeFromAlgo,
@@ -8532,12 +9055,11 @@ export {
8532
9055
  isCodeForbiddenIdentityWrite,
8533
9056
  AdfWorkspace,
8534
9057
  isTextMime,
8535
- isVisionMime,
8536
- isAudioInputMime,
8537
- isVideoInputMime,
8538
9058
  mimeToAudioFormat,
8539
9059
  formatSize,
8540
9060
  mimeToExt,
9061
+ summarizeLoopUsage,
9062
+ hasReportedUsage,
8541
9063
  EXIT,
8542
9064
  err,
8543
9065
  shellReadFile,