@agentdocumentformat/cli 0.7.8 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) 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-4AZZJNN2.mjs} +917 -826
  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-AIPBQTR2.mjs} +9 -3
  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-2Z7X3UUR.mjs +678 -0
  21. package/dist/chunks/chunk-3UGZFWIF.mjs +26 -0
  22. package/dist/chunks/{chunk-XNPZBLYL.mjs → chunk-75DC5EWN.mjs} +13 -2
  23. package/dist/chunks/{chunk-5PO5PKRV.mjs → chunk-7JZXMDDF.mjs} +158 -67
  24. package/dist/chunks/chunk-AEAJDCVK.mjs +517 -0
  25. package/dist/chunks/{chunk-6VQEXLE2.mjs → chunk-BHY7QDCM.mjs} +41 -3
  26. package/dist/chunks/{chunk-CD3ATSXE.mjs → chunk-BR3AD5QZ.mjs} +8 -2
  27. package/dist/chunks/chunk-BS3USVEA.mjs +301 -0
  28. package/dist/chunks/chunk-C2S64Y6F.mjs +203 -0
  29. package/dist/chunks/chunk-D7A77SNL.mjs +976 -0
  30. package/dist/chunks/chunk-DDTTSKGG.mjs +2443 -0
  31. package/dist/chunks/{chunk-VAUOM3IN.mjs → chunk-DLWCKKIO.mjs} +1 -1
  32. package/dist/chunks/{chunk-G6PH7RPW.mjs → chunk-E7LC6SHP.mjs} +3 -3
  33. package/dist/chunks/{chunk-ZESQMP66.mjs → chunk-F3WSCAJD.mjs} +3 -3
  34. package/dist/chunks/{chunk-KXEF4F4E.mjs → chunk-GAZO6UMI.mjs} +1 -1
  35. package/dist/chunks/{chunk-XYTWGHFS.mjs → chunk-H3XLRWVE.mjs} +1 -1
  36. package/dist/chunks/{chunk-7T2YMTWL.mjs → chunk-H4C65MTP.mjs} +4 -4
  37. package/dist/chunks/chunk-HBOFZRUS.mjs +738 -0
  38. package/dist/chunks/{chunk-VN7UH7I5.mjs → chunk-IMVMSU3A.mjs} +251 -68
  39. package/dist/chunks/{chunk-XYV3VCIX.mjs → chunk-JZFONJSL.mjs} +79 -20
  40. package/dist/chunks/chunk-KCIRFUZH.mjs +12799 -0
  41. package/dist/chunks/chunk-KDDVQKCG.mjs +65 -0
  42. package/dist/chunks/chunk-KRPKWOMB.mjs +290 -0
  43. package/dist/chunks/chunk-LKFHERRV.mjs +145 -0
  44. package/dist/chunks/chunk-LTX74NU6.mjs +29 -0
  45. package/dist/chunks/{chunk-HSBV6YFU.mjs → chunk-LVF2DRBR.mjs} +634 -99
  46. package/dist/chunks/chunk-M2F7C2UJ.mjs +425 -0
  47. package/dist/chunks/{chunk-62NE4LG4.mjs → chunk-MQAMI5BF.mjs} +5 -5
  48. package/dist/chunks/chunk-MZRXFWPQ.mjs +278 -0
  49. package/dist/chunks/{chunk-PRMGZZM5.mjs → chunk-NZLEPJ5M.mjs} +2 -0
  50. package/dist/chunks/{chunk-I3UYSITV.mjs → chunk-O7XKQ6CH.mjs} +1 -1
  51. package/dist/chunks/chunk-P3RG5LQA.mjs +244 -0
  52. package/dist/chunks/chunk-PAK5JCO2.mjs +48 -0
  53. package/dist/chunks/chunk-PC63YFB7.mjs +34 -0
  54. package/dist/chunks/{daemon-control-HKFVSQTQ.mjs → chunk-RHBGCOS3.mjs} +67 -26
  55. package/dist/chunks/{chunk-J74STFKE.mjs → chunk-RJDOVMM4.mjs} +362 -58
  56. package/dist/chunks/{chunk-QPLEAE6V.mjs → chunk-SOSPQPEN.mjs} +167 -650
  57. package/dist/chunks/{chunk-LFMB4PAA.mjs → chunk-TKXIH27X.mjs} +974 -113
  58. package/dist/chunks/{chunk-5W7KES6P.mjs → chunk-UZPDETEU.mjs} +1 -1
  59. package/dist/chunks/chunk-V3MM6PCH.mjs +366 -0
  60. package/dist/chunks/chunk-VENQBMOA.mjs +62 -0
  61. package/dist/chunks/chunk-WJGPNHGX.mjs +67 -0
  62. package/dist/chunks/{chunk-Y5ZLKFLE.mjs → chunk-XNNJQCZP.mjs} +196 -165
  63. package/dist/chunks/{chunk-HDBPBORT.mjs → chunk-YRBFIDLB.mjs} +46 -10
  64. package/dist/chunks/{chunk-BNXUAOO4.mjs → chunk-ZGXUOAWZ.mjs} +3186 -959
  65. package/dist/chunks/chunk-ZPB3HYM7.mjs +45 -0
  66. package/dist/chunks/claude-subscription-EB4LAAVV.mjs +29 -0
  67. package/dist/chunks/{cli-NMFIAR7Z.mjs → cli-PCVFB6B4.mjs} +797 -56
  68. package/dist/chunks/{commands-KPJWFRUE.mjs → commands-KOMPXJZS.mjs} +4 -3
  69. package/dist/chunks/{daemon-YDIK3AAM.mjs → daemon-BVDB565V.mjs} +83142 -64424
  70. package/dist/chunks/daemon-control-MKN7G5M3.mjs +59 -0
  71. package/dist/chunks/{daemon-token-WRSULP7G.mjs → daemon-token-L5QKTHU5.mjs} +3 -1
  72. package/dist/chunks/{discord-DSP4HUWI.mjs → discord-IR62565M.mjs} +3 -52
  73. package/dist/chunks/{email-BRG7KUO2.mjs → email-KHHTJBDW.mjs} +4 -2
  74. package/dist/chunks/{grok-subscription-4PYX7TBF.mjs → grok-subscription-E74FS5EG.mjs} +5 -2
  75. package/dist/chunks/{layout-EPJQNFAI.mjs → layout-ECWGJF6M.mjs} +2 -2
  76. package/dist/chunks/{lib-N7FCZWKH.mjs → lib-L27L5PXM.mjs} +22 -22
  77. package/dist/chunks/{open-VAU7PXT6.mjs → open-3OR6WT4T.mjs} +6 -6
  78. package/dist/chunks/{parser-JVY2PWSM.mjs → parser-7GARNT5L.mjs} +1 -1
  79. package/dist/chunks/{pipeline-executor-O77NTGLP.mjs → pipeline-executor-DRYEEFDR.mjs} +6 -5
  80. package/dist/chunks/prompt-RRHZX4YT.mjs +13 -0
  81. package/dist/chunks/remote-access-cli-GLEEP7DI.mjs +268 -0
  82. package/dist/chunks/remote-hello-UFWUBDQI.mjs +37 -0
  83. package/dist/chunks/sdk-OJOAF4DP.mjs +30014 -0
  84. package/dist/chunks/{slack-OCNNF4DX.mjs → slack-IYC5RAB5.mjs} +3 -4
  85. package/dist/chunks/{store-HUVDJPYN.mjs → store-55CQSAOX.mjs} +7 -3
  86. package/dist/chunks/tailscale-WLJATFMB.mjs +25 -0
  87. package/dist/chunks/{theme-SFE3JMA2.mjs → theme-HLBDTF6F.mjs} +1 -1
  88. package/dist/chunks/{themes-YLXLOIJF.mjs → themes-BQ72P446.mjs} +2 -2
  89. package/dist/chunks/{token-6G3HCMNX.mjs → token-6D22WECM.mjs} +20 -16
  90. package/dist/chunks/{token-util-UXYSDQIW.mjs → token-util-YF3ZWBZ4.mjs} +1 -2
  91. package/dist/chunks/{tui-UQJLK7XI.mjs → tui-RAUVFOYY.mjs} +21 -18
  92. package/dist/chunks/{welcome-XHXFV7SH.mjs → welcome-J5JZZAM5.mjs} +1 -1
  93. package/dist/chunks/{whatsapp-AJMRQBT7.mjs → whatsapp-YJFFVN4Q.mjs} +9 -9
  94. package/package.json +1 -1
  95. package/dist/chunks/chunk-GKKCMXIZ.mjs +0 -94
  96. package/dist/chunks/chunk-IHDDCZBS.mjs +0 -112
  97. package/dist/chunks/chunk-KWFOS5WC.mjs +0 -41
  98. package/dist/chunks/chunk-UEZR2CNS.mjs +0 -65
  99. package/dist/chunks/chunk-VMHTYDH4.mjs +0 -172
  100. package/dist/chunks/chunk-Y2QA53AN.mjs +0 -259
  101. package/dist/chunks/{chunk-NBTUWMGP.mjs → chunk-2MTE22CR.mjs} +3 -3
  102. package/dist/chunks/{chunk-APRU6PHC.mjs → chunk-JZ7AQNPM.mjs} +3 -3
  103. 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-E7LC6SHP.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,
@@ -20,8 +28,9 @@ import {
20
28
  LOOP_PROHIBITED_TOOLS,
21
29
  META_PROTECTION_LEVELS,
22
30
  SETTABLE_STATES,
23
- getDefaultDocumentContent
24
- } from "./chunk-CD3ATSXE.mjs";
31
+ getDefaultDocumentContent,
32
+ hasLoopGoal
33
+ } from "./chunk-BR3AD5QZ.mjs";
25
34
  import {
26
35
  __esm,
27
36
  __export
@@ -211,38 +220,6 @@ var TEXT_MIME_PREFIXES = ["text/"], TEXT_MIME_EXACT = /* @__PURE__ */ new Set([
211
220
  function isTextMime(mime) {
212
221
  return mime ? TEXT_MIME_PREFIXES.some((p) => mime.startsWith(p)) || /\+(json|xml|yaml)$/.test(mime) ? !0 : TEXT_MIME_EXACT.has(mime) : !1;
213
222
  }
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
223
  var AUDIO_FORMAT_MAP = {
247
224
  "audio/wav": "wav",
248
225
  "audio/mpeg": "mp3",
@@ -462,7 +439,7 @@ var catHandler = {
462
439
  async execute(ctx) {
463
440
  if (ctx.args.length < 2) return err("cp: usage: cp <src> <dst>");
464
441
  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}`);
442
+ if (readErr !== null) return err(`cp: ${readErr}`);
466
443
  let write = await ctx.toolRegistry.executeTool("fs_write", { mode: "write", path: dst, content }, ctx.workspace);
467
444
  return write.isError ? err(`cp: ${write.content}`) : ok("");
468
445
  }
@@ -717,7 +694,7 @@ var duHandler = {
717
694
  let bytes = parseInt(raw, 10), content = ctx.stdin;
718
695
  if (ctx.args.length > 0) {
719
696
  let [fileContent, error] = await shellReadFile(ctx.toolRegistry, ctx.workspace, vfsPath(ctx.args[0]));
720
- if (error) return err(`head: ${error}`);
697
+ if (error !== null) return err(`head: ${error}`);
721
698
  content = fileContent;
722
699
  }
723
700
  return content ? ok(Buffer.from(content, "utf8").subarray(0, bytes).toString("utf8")) : ok("");
@@ -729,7 +706,7 @@ var duHandler = {
729
706
  let text = ctx.stdin;
730
707
  if (ctx.args.length > 0) {
731
708
  let [content, error] = await shellReadFile(ctx.toolRegistry, ctx.workspace, vfsPath(ctx.args[0]), { end_line: n });
732
- return error ? err(`head: ${error}`) : ok(content);
709
+ return error !== null ? err(`head: ${error}`) : ok(content);
733
710
  }
734
711
  if (!text) return ok("");
735
712
  let lines = text.split(`
@@ -761,7 +738,7 @@ var duHandler = {
761
738
  let fromByte = raw.startsWith("+"), bytes = parseInt(raw, 10), content = ctx.stdin;
762
739
  if (ctx.args.length > 0) {
763
740
  let [fileContent, error] = await shellReadFile(ctx.toolRegistry, ctx.workspace, vfsPath(ctx.args[0]));
764
- if (error) return err(`tail: ${error}`);
741
+ if (error !== null) return err(`tail: ${error}`);
765
742
  content = fileContent;
766
743
  }
767
744
  if (!content) return ok("");
@@ -779,7 +756,7 @@ var duHandler = {
779
756
  let text = ctx.stdin;
780
757
  if (ctx.args.length > 0) {
781
758
  let [content, error] = await shellReadFile(ctx.toolRegistry, ctx.workspace, vfsPath(ctx.args[0]));
782
- if (error) return err(`tail: ${error}`);
759
+ if (error !== null) return err(`tail: ${error}`);
783
760
  text = content;
784
761
  }
785
762
  if (!text) return ok("");
@@ -1217,7 +1194,7 @@ var GREP_KNOWN_FLAGS = /* @__PURE__ */ new Set([
1217
1194
  for (let file of files) {
1218
1195
  if (file.mime_type && !isTextMime(file.mime_type)) continue;
1219
1196
  let [content, readErr] = await shellReadFile(ctx.toolRegistry, ctx.workspace, file.path);
1220
- if (readErr) continue;
1197
+ if (readErr !== null) continue;
1221
1198
  let lines2 = splitLines(content), sel2 = await select(lines2, content?.length ?? 0);
1222
1199
  if ("failure" in sel2) return sel2.failure;
1223
1200
  let fileMatches = sel2.idx.length;
@@ -1236,7 +1213,7 @@ var GREP_KNOWN_FLAGS = /* @__PURE__ */ new Set([
1236
1213
  let text = ctx.stdin;
1237
1214
  if (fileArgs.length === 1) {
1238
1215
  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`);
1216
+ if (readErr !== null) return err(`grep: ${fileArgs[0]}: No such file or directory`);
1240
1217
  text = content;
1241
1218
  }
1242
1219
  if (!text)
@@ -1320,7 +1297,7 @@ var SED_KNOWN_FLAGS = /* @__PURE__ */ new Set(["i", "E", "r"]), sedHandler = {
1320
1297
  if (ctx.args.length > 1) {
1321
1298
  filePath = vfsPath2(ctx.args[1]);
1322
1299
  let [sedContent, sedErr] = await shellReadFile(ctx.toolRegistry, ctx.workspace, filePath);
1323
- if (sedErr) return err(`sed: ${filePath}: No such file or directory`);
1300
+ if (sedErr !== null) return err(`sed: ${filePath}: No such file or directory`);
1324
1301
  text = sedContent;
1325
1302
  } else if (!text)
1326
1303
  return ok("");
@@ -1585,7 +1562,7 @@ var xargsHandler = {
1585
1562
  batchSize = n;
1586
1563
  }
1587
1564
  if (!text) return ok("");
1588
- let { parse } = await import("./parser-JVY2PWSM.mjs"), { executeNode } = await import("./pipeline-executor-O77NTGLP.mjs"), subCtx = {
1565
+ let { parse } = await import("./parser-7GARNT5L.mjs"), { executeNode } = await import("./pipeline-executor-DRYEEFDR.mjs"), subCtx = {
1589
1566
  workspace: ctx.workspace,
1590
1567
  toolRegistry: ctx.toolRegistry,
1591
1568
  config: ctx.config,
@@ -2405,7 +2382,7 @@ async function executeShellScript(path, ctx) {
2405
2382
  authorized = ctx.workspace.isFileAuthorized(path);
2406
2383
  } catch {
2407
2384
  }
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;
2385
+ 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-DRYEEFDR.mjs"), depth = (ctx.depth ?? 0) + 1;
2409
2386
  if (depth > MAX_SHELL_DEPTH)
2410
2387
  return err(`./${path}: script nesting too deep (${MAX_SHELL_DEPTH}) \u2014 possible infinite recursion`);
2411
2388
  try {
@@ -2621,9 +2598,105 @@ import { createPrivateKey as createPrivateKey3, createPublicKey as createPublicK
2621
2598
  // src/main/adf/adf-database.ts
2622
2599
  import Database from "better-sqlite3";
2623
2600
  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";
2601
+ import { existsSync as existsSync2, unlinkSync, renameSync, copyFileSync, readdirSync, readFileSync as readFileSync3, openSync, closeSync, lstatSync, fstatSync, fsyncSync, statSync, promises as fsp } from "fs";
2602
+ import { createHash } from "crypto";
2625
2603
  import { brotliDecompressSync } from "zlib";
2626
- import { join as join2, resolve, sep } from "path";
2604
+ import { basename, dirname as dirname2, join as join2, resolve, sep } from "path";
2605
+
2606
+ // src/main/adf/file-ref-registry.ts
2607
+ var MediaPayloadCache = class {
2608
+ constructor(maxChars) {
2609
+ this.maxChars = maxChars;
2610
+ }
2611
+ maxChars;
2612
+ entries = /* @__PURE__ */ new Map();
2613
+ chars = 0;
2614
+ get(path) {
2615
+ let entry = this.entries.get(path);
2616
+ if (entry)
2617
+ return this.entries.delete(path), this.entries.set(path, entry), entry;
2618
+ }
2619
+ set(path, entry) {
2620
+ if (this.delete(path), !(entry.payload.length > this.maxChars)) {
2621
+ this.entries.set(path, entry), this.chars += entry.payload.length;
2622
+ for (let [oldest, value] of this.entries) {
2623
+ if (this.chars <= this.maxChars) break;
2624
+ this.entries.delete(oldest), this.chars -= value.payload.length;
2625
+ }
2626
+ }
2627
+ }
2628
+ delete(path) {
2629
+ let entry = this.entries.get(path);
2630
+ entry && (this.entries.delete(path), this.chars -= entry.payload.length);
2631
+ }
2632
+ clear() {
2633
+ this.entries.clear(), this.chars = 0;
2634
+ }
2635
+ get size() {
2636
+ return this.entries.size;
2637
+ }
2638
+ }, MEDIA_CACHE_MAX_CHARS = 48 * 1024 * 1024, FileRefRegistry = class {
2639
+ /**
2640
+ * Paths some transcript row references. Null until first needed: built by
2641
+ * one scan of adf_loop, then kept current by every loop write. May hold
2642
+ * paths whose rows are gone (cleared, compacted); that only costs a scan.
2643
+ */
2644
+ referenced = null;
2645
+ media = new MediaPayloadCache(MEDIA_CACHE_MAX_CHARS);
2646
+ /** Bumped when a file a request has carried changes, so resolved requests rebuild. */
2647
+ generation = 0;
2648
+ /** Paths a request has carried. */
2649
+ resolved = /* @__PURE__ */ new Set();
2650
+ holders = /* @__PURE__ */ new Set();
2651
+ addHolder(holder) {
2652
+ this.holders.add(new WeakRef(holder));
2653
+ }
2654
+ /** Record the references in a loop row just written. */
2655
+ noteContent(content) {
2656
+ if (this.referenced)
2657
+ for (let block of content)
2658
+ isFileRefBlock(block) && !block.file_ref.deleted && this.referenced.add(block.file_ref.path);
2659
+ }
2660
+ /** Whether any of these changes names a referenced path (`referenced` must be built). */
2661
+ affects(changes) {
2662
+ let referenced = this.referenced;
2663
+ return referenced ? changes.some((change) => referenced.has(change.op === "rename" ? change.from : change.path)) : !0;
2664
+ }
2665
+ /** Keep `referenced` current after the rows were rewritten. */
2666
+ noteChanges(changes) {
2667
+ let referenced = this.referenced;
2668
+ if (referenced)
2669
+ for (let change of changes)
2670
+ change.op !== "rename" || !referenced.has(change.from) || (referenced.delete(change.from), referenced.add(change.to));
2671
+ }
2672
+ /** Rewrite the references every live session holds. */
2673
+ notifyHolders(changes) {
2674
+ for (let ref of this.holders) {
2675
+ let holder = ref.deref();
2676
+ if (!holder) {
2677
+ this.holders.delete(ref);
2678
+ continue;
2679
+ }
2680
+ try {
2681
+ holder.applyFileRefChanges(changes);
2682
+ } catch (error) {
2683
+ console.error("[FileRefRegistry] A session could not apply a file change:", error);
2684
+ }
2685
+ }
2686
+ }
2687
+ /** A file was written, renamed or deleted: its loaded bytes are stale. */
2688
+ invalidate(path) {
2689
+ this.media.delete(path), this.resolved.has(path) && this.generation++;
2690
+ }
2691
+ }, byFile = /* @__PURE__ */ new Map(), byDatabase = /* @__PURE__ */ new WeakMap();
2692
+ function fileRefRegistry(database, canonicalPath) {
2693
+ if (canonicalPath === null) {
2694
+ let registry4 = byDatabase.get(database);
2695
+ return registry4 || (registry4 = new FileRefRegistry(), byDatabase.set(database, registry4)), registry4;
2696
+ }
2697
+ let registry3 = byFile.get(canonicalPath);
2698
+ return registry3 || (registry3 = new FileRefRegistry(), byFile.set(canonicalPath, registry3)), registry3;
2699
+ }
2627
2700
 
2628
2701
  // src/shared/constants/agent-icons.ts
2629
2702
  var AGENT_ICON_POOL = [
@@ -2834,7 +2907,20 @@ function didToPublicKey(did) {
2834
2907
  }
2835
2908
 
2836
2909
  // 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;
2910
+ 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 {
2911
+ constructor(filePath, schemaVersion, opts = {}) {
2912
+ let name = basename(filePath);
2913
+ super(
2914
+ (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.`
2915
+ );
2916
+ this.filePath = filePath;
2917
+ this.schemaVersion = schemaVersion;
2918
+ this.name = "AdfSchemaTooOldError";
2919
+ }
2920
+ filePath;
2921
+ schemaVersion;
2922
+ code = "schema_too_old";
2923
+ }, CLEAN_CLOSE_META_KEY = "adf_clean_close", SNAPSHOT_DEADLINE_MS = 3e4, OPFLAG_P2ISREG = 16;
2838
2924
  function formatMetaNumber(n) {
2839
2925
  return Number.isInteger(n) ? String(n) : String(Number(n.toFixed(10)));
2840
2926
  }
@@ -3144,7 +3230,12 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3144
3230
  /** Page cap for getLogsAfterId — pollers carry a cursor, so pages catch up. */
3145
3231
  static MAX_LOGS_AFTER_ID = 5e3;
3146
3232
  constructor(db, filePath) {
3147
- this.db = db, this.filePath = filePath, _AdfDatabase.incrementOpen(filePath), this.applyPragmas(), this.prepareStatements();
3233
+ this.db = db, this.filePath = filePath, _AdfDatabase.incrementOpen(filePath);
3234
+ try {
3235
+ this.applyPragmas(), this.prepareStatements();
3236
+ } catch (err2) {
3237
+ throw _AdfDatabase.decrementOpen(filePath), err2;
3238
+ }
3148
3239
  }
3149
3240
  // ===========================================================================
3150
3241
  // Static Factory Methods
@@ -3154,6 +3245,36 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3154
3245
  let code = err2?.code;
3155
3246
  return typeof code == "string" && (code.startsWith("SQLITE_BUSY") || code === "SQLITE_LOCKED");
3156
3247
  }
3248
+ /**
3249
+ * The file's schema version: `adf_schema_version`, else the older
3250
+ * `schema_version`, else 0. Null when there is no adf_meta table. Reads only.
3251
+ */
3252
+ static readSchemaVersion(db) {
3253
+ if (!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='adf_meta'").get()) return null;
3254
+ let r = db.prepare("SELECT value FROM adf_meta WHERE key = 'adf_schema_version'").get(), parse = (v) => {
3255
+ let n = parseInt(v, 10);
3256
+ return Number.isFinite(n) ? n : 0;
3257
+ };
3258
+ if (r) return parse(r.value);
3259
+ let r2 = db.prepare("SELECT value FROM adf_meta WHERE key = 'schema_version'").get();
3260
+ return r2 ? parse(r2.value) : 0;
3261
+ }
3262
+ /**
3263
+ * The schema version open() refuses for this file, or null when it may be
3264
+ * opened (and migrated). 1 <= v < 3 is refused. No version at all (0, or
3265
+ * unparsable) is refused unless the file has a clone's layout: a clone
3266
+ * whose adf_meta was deselected (before clone-fixup restamped the version)
3267
+ * has `adf_audit`, `adf_loop.loop` and no `adf_archive`. Null too without
3268
+ * an adf_meta table (the v0.2 check reports that). Reads only.
3269
+ */
3270
+ static refusedSchemaVersion(db) {
3271
+ let sv = _AdfDatabase.readSchemaVersion(db);
3272
+ if (sv === null) return null;
3273
+ if (sv > 0 && sv < ADF_OLDEST_MIGRATABLE_SCHEMA_VERSION) return sv;
3274
+ if (sv > 0) return null;
3275
+ 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();
3276
+ return tables.has("adf_audit") && !tables.has("adf_archive") && loopCols.has("loop") ? null : 0;
3277
+ }
3157
3278
  /** Retryable lock-contention error — distinct from the corruption path. */
3158
3279
  static lockedError(filePath) {
3159
3280
  return new Error(
@@ -3224,18 +3345,146 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3224
3345
  }
3225
3346
  /** Synchronous backup — only for `open()`, which cannot await mid-migration.
3226
3347
  * 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. */
3348
+ * this connection exists, so nothing can checkpoint underneath it.
3349
+ * `complete`: the checkpoint folded the whole WAL first, so the copy holds
3350
+ * every committed write (a failed open may restore from it).
3351
+ * `reused`: a `.bak` already on disk has the same size and SHA-256 as the
3352
+ * checkpointed file (a migration that failed before and rolled back), so
3353
+ * it is kept as is instead of being moved aside and copied again. The
3354
+ * copy is fsynced. */
3228
3355
  static backupBeforeDestructiveSync(db, filePath) {
3229
- let backupPath = filePath + ".bak";
3356
+ let backupPath = filePath + ".bak", complete = !1;
3357
+ try {
3358
+ let [ck] = db.pragma("wal_checkpoint(TRUNCATE)");
3359
+ complete = ck?.busy === 0;
3360
+ } catch {
3361
+ }
3362
+ if (_AdfDatabase.sameFileContent(filePath, backupPath)) return { backupPath, complete, reused: !0 };
3230
3363
  try {
3231
3364
  renameSync(backupPath, _AdfDatabase.staleBackupName(backupPath));
3232
3365
  } catch {
3233
3366
  }
3367
+ return copyFileSync(filePath, backupPath), _AdfDatabase.fsyncFileSync(backupPath), { backupPath, complete, reused: !1 };
3368
+ }
3369
+ /** Both files exist with the same size and SHA-256. */
3370
+ static sameFileContent(a, b) {
3234
3371
  try {
3235
- db.pragma("wal_checkpoint(TRUNCATE)");
3372
+ if (statSync(a).size !== statSync(b).size) return !1;
3373
+ let hash = (p) => createHash("sha256").update(readFileSync3(p)).digest("hex");
3374
+ return hash(a) === hash(b);
3236
3375
  } catch {
3376
+ return !1;
3237
3377
  }
3238
- return copyFileSync(filePath, backupPath), backupPath;
3378
+ }
3379
+ static fsyncFileSync(path) {
3380
+ let fd = openSync(path, "r+");
3381
+ try {
3382
+ fsyncSync(fd);
3383
+ } finally {
3384
+ closeSync(fd);
3385
+ }
3386
+ }
3387
+ /** POSIX: make a rename in `dir` durable. Windows cannot open a directory for fsync. */
3388
+ static fsyncDirSync(dir) {
3389
+ if (process.platform !== "win32")
3390
+ try {
3391
+ let fd = openSync(dir, "r");
3392
+ try {
3393
+ fsyncSync(fd);
3394
+ } finally {
3395
+ closeSync(fd);
3396
+ }
3397
+ } catch {
3398
+ }
3399
+ }
3400
+ /**
3401
+ * After a failure that followed a committed migration (the constructor):
3402
+ * put the file back as its pre-migration backup. The `.bak` is kept (it is
3403
+ * removed only after a fully successful open). Null when restored, else
3404
+ * why not (the caller says so in its error).
3405
+ *
3406
+ * Only when no other connection can hold the file: none in this process,
3407
+ * no -wal left beside it (SQLite removes it at the last close), and an
3408
+ * exclusive lock taken with busy_timeout 0. The copy is fsynced before the
3409
+ * rename. On POSIX the rename happens while that lock is held, so nothing
3410
+ * can open the old file in between (a connection that opened it earlier
3411
+ * would have held a lock). On Windows the lock's own handle would block the
3412
+ * rename, so the probe closes first: the rename itself then fails while any
3413
+ * other program has the file open.
3414
+ *
3415
+ * Known POSIX residual (tracker §6, W5-H): the probe closes after the
3416
+ * rename, and SQLite's close unlinks `<file>-wal` by path. A program that
3417
+ * opens the restored file and writes in that window can lose its WAL.
3418
+ * Closing first would drop the lock, and better-sqlite3 cannot keep the
3419
+ * WAL on close (no SQLITE_FCNTL_PERSIST_WAL), so the window is not closed.
3420
+ */
3421
+ static restoreBackupSync(filePath) {
3422
+ let backupPath = filePath + ".bak";
3423
+ if (!existsSync2(backupPath)) return "there is no backup";
3424
+ let busy = "another connection has the file open";
3425
+ if (_AdfDatabase.openCounts.has(_AdfDatabase.canonicalKey(filePath)) || existsSync2(`${filePath}-wal`)) return busy;
3426
+ let restorePath = `${filePath}.restore`, probe = null;
3427
+ try {
3428
+ copyFileSync(backupPath, restorePath), _AdfDatabase.fsyncFileSync(restorePath);
3429
+ try {
3430
+ probe = new Database(filePath, { fileMustExist: !0 }), probe.pragma("busy_timeout = 0"), probe.pragma("locking_mode = EXCLUSIVE"), probe.exec("BEGIN IMMEDIATE; COMMIT");
3431
+ } catch {
3432
+ try {
3433
+ probe?.close();
3434
+ } catch {
3435
+ }
3436
+ probe = null;
3437
+ try {
3438
+ unlinkSync(restorePath);
3439
+ } catch {
3440
+ }
3441
+ return busy;
3442
+ }
3443
+ process.platform === "win32" && (probe.close(), probe = null);
3444
+ try {
3445
+ unlinkSync(`${filePath}-shm`);
3446
+ } catch {
3447
+ }
3448
+ return renameSync(restorePath, filePath), _AdfDatabase.fsyncDirSync(dirname2(filePath)), null;
3449
+ } catch (err2) {
3450
+ try {
3451
+ unlinkSync(restorePath);
3452
+ } catch {
3453
+ }
3454
+ let code = err2?.code;
3455
+ return code === "EPERM" || code === "EBUSY" || code === "EACCES" ? busy : `the restore failed (${err2 instanceof Error ? err2.message : String(err2)})`;
3456
+ } finally {
3457
+ try {
3458
+ probe?.close();
3459
+ } catch {
3460
+ }
3461
+ }
3462
+ }
3463
+ /**
3464
+ * A failed open(): roll back is the caller's; here the connection is
3465
+ * closed, the file is put back from the backup when `restore` (the failure
3466
+ * followed a committed migration), and the sidecars this open created are
3467
+ * reaped. Returns the sentence to add to the error when the file could not
3468
+ * be restored, else null.
3469
+ */
3470
+ static abandonOpen(db, filePath, opts) {
3471
+ try {
3472
+ db.close();
3473
+ } catch {
3474
+ }
3475
+ let note = null;
3476
+ if (opts.backup && !opts.restore)
3477
+ console.error(`[AdfDatabase] Open failed; the migration was rolled back, the file is unchanged. Backup kept at: ${filePath}.bak`);
3478
+ else if (opts.restore) {
3479
+ let why = opts.backup ? opts.backup.complete ? _AdfDatabase.restoreBackupSync(filePath) : "the backup may lack writes another connection had not checkpointed" : "there is no backup";
3480
+ 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}`));
3481
+ }
3482
+ if (!opts.walExistedBeforeOpen)
3483
+ try {
3484
+ _AdfDatabase.reapSidecars(filePath);
3485
+ } catch {
3486
+ }
3487
+ return note;
3239
3488
  }
3240
3489
  /** Remove a transient .bak file after a successful destructive operation. */
3241
3490
  static async removeBackup(filePath) {
@@ -3290,6 +3539,29 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3290
3539
  * 'reaped' — WAL folded into the main file, sidecars removed
3291
3540
  * 'error' — open/checkpoint/unlink failed; sidecars left in place
3292
3541
  */
3542
+ /**
3543
+ * True when no other connection holds `filePath`'s write lock: a probe
3544
+ * connection takes it with BEGIN IMMEDIATE (waiting at most
3545
+ * `busyTimeoutMs`) and gives it back at once. False on SQLITE_BUSY. Lets a
3546
+ * background job (the boot envelope sweep) pass over a file another
3547
+ * connection is writing instead of blocking in AdfWorkspace.open, whose
3548
+ * maintenance writes wait the full busy_timeout. Other errors throw.
3549
+ */
3550
+ static writeLockFree(filePath, busyTimeoutMs) {
3551
+ let db = null;
3552
+ try {
3553
+ return db = new Database(filePath, { fileMustExist: !0 }), db.pragma(`busy_timeout = ${Math.max(0, Math.floor(busyTimeoutMs))}`), db.exec("BEGIN IMMEDIATE; ROLLBACK"), !0;
3554
+ } catch (err2) {
3555
+ let code = err2?.code;
3556
+ if (typeof code == "string" && code.startsWith("SQLITE_BUSY")) return !1;
3557
+ throw err2;
3558
+ } finally {
3559
+ try {
3560
+ db?.close();
3561
+ } catch {
3562
+ }
3563
+ }
3564
+ }
3293
3565
  static reapSidecars(filePath) {
3294
3566
  let walPath = `${filePath}-wal`, shmPath = `${filePath}-shm`;
3295
3567
  if (!existsSync2(walPath) && !existsSync2(shmPath)) return "no-sidecars";
@@ -3376,7 +3648,40 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3376
3648
  }
3377
3649
  }
3378
3650
  static open(filePath, options) {
3379
- let walExistedBeforeOpen = existsSync2(`${filePath}-wal`) || existsSync2(`${filePath}-shm`), db = new Database(filePath), needsMigration = !1, integrityMode = "full";
3651
+ let walExistedBeforeOpen = existsSync2(`${filePath}-wal`) || existsSync2(`${filePath}-shm`);
3652
+ if (walExistedBeforeOpen) {
3653
+ let ro = null, refused = null;
3654
+ try {
3655
+ ro = new Database(filePath, { readonly: !0, fileMustExist: !0 }), refused = _AdfDatabase.refusedSchemaVersion(ro);
3656
+ } catch (err2) {
3657
+ if (_AdfDatabase.isBusyError(err2)) throw _AdfDatabase.lockedError(filePath);
3658
+ } finally {
3659
+ try {
3660
+ ro?.close();
3661
+ } catch {
3662
+ }
3663
+ }
3664
+ if (refused !== null) throw new AdfSchemaTooOldError(filePath, refused);
3665
+ }
3666
+ let db = new Database(filePath), needsMigration = !1, backup = null, migrated = !1, repaired = !1;
3667
+ if (!walExistedBeforeOpen) {
3668
+ let refused = null;
3669
+ try {
3670
+ refused = _AdfDatabase.refusedSchemaVersion(db);
3671
+ } catch (err2) {
3672
+ if (_AdfDatabase.isBusyError(err2))
3673
+ throw db.close(), _AdfDatabase.lockedError(filePath);
3674
+ }
3675
+ if (refused !== null) {
3676
+ db.close();
3677
+ try {
3678
+ _AdfDatabase.reapSidecars(filePath);
3679
+ } catch {
3680
+ }
3681
+ throw new AdfSchemaTooOldError(filePath, refused);
3682
+ }
3683
+ }
3684
+ let integrityMode = "full";
3380
3685
  if (!options?.forceIntegrityCheck) {
3381
3686
  let closedCleanly = !1;
3382
3687
  try {
@@ -3392,7 +3697,7 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3392
3697
  let checkPragma = integrityMode === "quick" ? "quick_check" : "integrity_check", status = (integrityMode === "none" ? [{ [checkPragma]: "ok" }] : db.pragma(checkPragma))[0]?.[checkPragma];
3393
3698
  if (status !== "ok")
3394
3699
  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")
3700
+ if (db = new Database(filePath), repaired = !0, db.pragma("integrity_check")[0]?.integrity_check !== "ok")
3396
3701
  throw db.close(), new Error(
3397
3702
  `ADF file is corrupt and auto-repair failed. A backup was saved at: ${filePath}.corrupt`
3398
3703
  );
@@ -3412,7 +3717,7 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3412
3717
  throw _AdfDatabase.lockedError(filePath);
3413
3718
  }
3414
3719
  if (console.warn("[AdfDatabase] Integrity check threw:", error), db.close(), _AdfDatabase.tryRepair(filePath))
3415
- db = new Database(filePath);
3720
+ db = new Database(filePath), repaired = !0;
3416
3721
  else
3417
3722
  throw new Error(
3418
3723
  "ADF file is corrupt and could not be repaired. Please delete and recreate the file."
@@ -3425,16 +3730,22 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3425
3730
  throw db.close(), new Error(
3426
3731
  "This ADF file does not have the v0.2 schema. Please delete and recreate the file."
3427
3732
  );
3733
+ let refused;
3734
+ try {
3735
+ refused = _AdfDatabase.refusedSchemaVersion(db);
3736
+ } catch (err2) {
3737
+ throw _AdfDatabase.isBusyError(err2) ? _AdfDatabase.lockedError(filePath) : new Error(
3738
+ `Cannot read the schema version of ${basename(filePath)}: ${err2 instanceof Error ? err2.message : String(err2)}. The file was not migrated.`,
3739
+ { cause: err2 }
3740
+ );
3741
+ }
3742
+ if (refused !== null)
3743
+ throw new AdfSchemaTooOldError(filePath, refused, { unchanged: !walExistedBeforeOpen && !repaired });
3428
3744
  try {
3429
3745
  db.prepare("DELETE FROM adf_meta WHERE key = ?").run(CLEAN_CLOSE_META_KEY), db.pragma("wal_checkpoint(PASSIVE)");
3430
3746
  } catch {
3431
3747
  }
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
- })();
3748
+ let sv = _AdfDatabase.readSchemaVersion(db) ?? 0;
3438
3749
  if (needsMigration = sv < ADF_LATEST_SCHEMA_VERSION, !needsMigration) {
3439
3750
  if (sv > ADF_LATEST_SCHEMA_VERSION) {
3440
3751
  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 +3761,13 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3450
3761
  }
3451
3762
  if (needsMigration)
3452
3763
  try {
3453
- _AdfDatabase.backupBeforeDestructiveSync(db, filePath), console.log(`[AdfDatabase] Backup created before migration (v${sv} \u2192 v${ADF_LATEST_SCHEMA_VERSION}): ${filePath}.bak`);
3764
+ let made = _AdfDatabase.backupBeforeDestructiveSync(db, filePath);
3765
+ 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
3766
  } catch (e) {
3455
3767
  console.warn("[AdfDatabase] Could not create pre-migration backup:", e);
3456
3768
  }
3457
- let cols = db.prepare("PRAGMA table_info(adf_loop)").all();
3769
+ db.exec("BEGIN IMMEDIATE");
3770
+ let unversionedClone = sv === 0, cols = db.prepare("PRAGMA table_info(adf_loop)").all();
3458
3771
  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
3772
  "SELECT name FROM sqlite_master WHERE type='table' AND name='adf_audit'"
3460
3773
  ).get() || db.exec(`
@@ -3972,11 +4285,24 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
3972
4285
  }
3973
4286
  } catch {
3974
4287
  }
3975
- needsMigration && _AdfDatabase.removeBackupSync(filePath);
4288
+ if (!db.inTransaction) throw new Error(`The migration of ${basename(filePath)} was rolled back by SQLite.`);
4289
+ 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
4290
  } catch (error) {
3977
- throw needsMigration && console.error(`[AdfDatabase] Migration failed. Backup preserved at: ${filePath}.bak`), db.close(), error;
4291
+ if (db.inTransaction)
4292
+ try {
4293
+ db.exec("ROLLBACK");
4294
+ } catch {
4295
+ }
4296
+ throw _AdfDatabase.abandonOpen(db, filePath, { backup, restore: !1, walExistedBeforeOpen }), error;
3978
4297
  }
3979
- return new _AdfDatabase(db, filePath);
4298
+ let opened;
4299
+ try {
4300
+ opened = new _AdfDatabase(db, filePath);
4301
+ } catch (error) {
4302
+ let note = _AdfDatabase.abandonOpen(db, filePath, { backup, restore: migrated, walExistedBeforeOpen });
4303
+ throw note ? new Error(`${error instanceof Error ? error.message : String(error)}. ${note}`, { cause: error }) : error;
4304
+ }
4305
+ return backup && _AdfDatabase.removeBackupSync(filePath), opened;
3980
4306
  }
3981
4307
  /**
3982
4308
  * Create a new ADF without replacing an existing destination.
@@ -4048,7 +4374,7 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4048
4374
  let overrideMap = new Map(options.tools.map((t) => [t.name, t]));
4049
4375
  tools = tools.map((t) => overrideMap.has(t.name) ? { ...t, ...overrideMap.get(t.name) } : t);
4050
4376
  for (let t of options.tools)
4051
- tools.some((dt) => dt.name === t.name) || tools.push(t);
4377
+ tools.some((dt) => dt.name === t.name) || tools.push({ visible: !0, ...t });
4052
4378
  }
4053
4379
  let mergedTriggers = { ...base.triggers };
4054
4380
  if (options.triggers)
@@ -4075,7 +4401,7 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4075
4401
  context: {
4076
4402
  ...base.context,
4077
4403
  ...options.context,
4078
- audit: { ...base.context.audit, ...options.context?.audit },
4404
+ audit: { ...AUDIT_DEFAULTS, ...base.context.audit, ...options.context?.audit },
4079
4405
  dynamic_instructions: { ...base.context.dynamic_instructions, ...options.context?.dynamic_instructions }
4080
4406
  },
4081
4407
  tools,
@@ -4085,6 +4411,7 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4085
4411
  messaging: { ...base.messaging, ...options.messaging },
4086
4412
  audit: { ...base.audit, ...options.audit },
4087
4413
  pre_llm_hook: options.pre_llm_hook ?? base.pre_llm_hook,
4414
+ commands: options.commands ?? base.commands,
4088
4415
  code_execution: { ...base.code_execution, ...options.code_execution },
4089
4416
  compute: { ...base.compute },
4090
4417
  logging: { ...base.logging, ...options.logging },
@@ -4160,10 +4487,10 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4160
4487
  * crash leftovers owned by the sweeps — left untouched here. Errors from
4161
4488
  * `fn` (and the open) propagate to the caller's existing catch.
4162
4489
  */
4163
- static peekReadonly(filePath, fn) {
4490
+ static peekReadonly(filePath, fn, busyTimeoutMs) {
4164
4491
  let hadSidecars = existsSync2(`${filePath}-wal`) || existsSync2(`${filePath}-shm`), db = null;
4165
4492
  try {
4166
- return db = new Database(filePath, { readonly: !0 }), fn(db);
4493
+ return db = new Database(filePath, { readonly: !0 }), busyTimeoutMs !== void 0 && db.pragma(`busy_timeout = ${Math.max(0, Math.floor(busyTimeoutMs))}`), fn(db);
4167
4494
  } finally {
4168
4495
  try {
4169
4496
  db?.close();
@@ -4198,6 +4525,7 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4198
4525
  throw new Error(
4199
4526
  "Snapshot timed out: the agent is being written to continuously \u2014 stop it and try again"
4200
4527
  );
4528
+ return 100;
4201
4529
  }
4202
4530
  });
4203
4531
  } finally {
@@ -4363,6 +4691,23 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4363
4691
  return null;
4364
4692
  }
4365
4693
  }
4694
+ /**
4695
+ * What the boot envelope sweep decides on before opening a file, read
4696
+ * read-only: its schema version (null without an adf_meta table, 0 with no
4697
+ * version) and its agent id (null without a config). Errors propagate;
4698
+ * `busyTimeoutMs` bounds the wait for a lock (the sweep's 25 ms).
4699
+ */
4700
+ static peekSweepStatus(filePath, busyTimeoutMs) {
4701
+ return _AdfDatabase.peekReadonly(filePath, (db) => {
4702
+ 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;
4703
+ try {
4704
+ let id = row ? JSON.parse(row.config_json).id : void 0;
4705
+ typeof id == "string" && id && (agentId = id);
4706
+ } catch {
4707
+ }
4708
+ return { schemaVersion, agentId };
4709
+ }, busyTimeoutMs);
4710
+ }
4366
4711
  static peekBootStatusDetailed(filePath) {
4367
4712
  try {
4368
4713
  return _AdfDatabase.peekReadonly(filePath, (db) => {
@@ -4528,8 +4873,8 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4528
4873
  static peekIdentityPurposes(filePath, prefix) {
4529
4874
  try {
4530
4875
  return _AdfDatabase.peekReadonly(filePath, (db) => db.prepare(
4531
- "SELECT purpose FROM adf_identity WHERE purpose LIKE ? || '%'"
4532
- ).all(prefix).map((r) => r.purpose));
4876
+ "SELECT purpose FROM adf_identity WHERE substr(purpose, 1, length(?)) = ?"
4877
+ ).all(prefix, prefix).map((r) => r.purpose));
4533
4878
  } catch {
4534
4879
  return [];
4535
4880
  }
@@ -4584,6 +4929,8 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4584
4929
  "INSERT INTO adf_loop (seq, role, content_json, model, tokens, created_at, ord, loop) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
4585
4930
  ), 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
4931
  "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"
4932
+ ), this.stmts.getAssistantTokenRows = this.db.prepare(
4933
+ "SELECT seq, tokens FROM adf_loop WHERE loop = ? AND role = 'assistant' AND tokens IS NOT NULL ORDER BY COALESCE(ord, seq), seq"
4587
4934
  ), this.stmts.getInboxMessages = this.db.prepare(
4588
4935
  "SELECT * FROM adf_inbox ORDER BY received_at DESC"
4589
4936
  ), this.stmts.getInboxByStatus = this.db.prepare(
@@ -4666,7 +5013,7 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4666
5013
  ), this.stmts.deleteIdentity = this.db.prepare(
4667
5014
  "DELETE FROM adf_identity WHERE purpose = ?"
4668
5015
  ), this.stmts.listIdentityByPrefix = this.db.prepare(
4669
- "SELECT purpose FROM adf_identity WHERE purpose LIKE ? || '%'"
5016
+ "SELECT purpose FROM adf_identity WHERE substr(purpose, 1, length(?)) = ?"
4670
5017
  ), this.stmts.getIdentityFull = this.db.prepare(
4671
5018
  "SELECT purpose, value, encryption_algo, salt, kdf_params, code_access FROM adf_identity WHERE purpose = ?"
4672
5019
  ), this.stmts.getAllIdentityFull = this.db.prepare(
@@ -4703,6 +5050,8 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4703
5050
  "UPDATE adf_tasks SET requires_authorization = 1 WHERE id = ?"
4704
5051
  ), this.stmts.setTaskExecutorManaged = this.db.prepare(
4705
5052
  "UPDATE adf_tasks SET executor_managed = 1 WHERE id = ?"
5053
+ ), this.stmts.setTaskApprovalMeta = this.db.prepare(
5054
+ "UPDATE adf_tasks SET approval_meta = ? WHERE id = ?"
4706
5055
  ), this.stmts.updateTaskArgs = this.db.prepare(
4707
5056
  "UPDATE adf_tasks SET args = ? WHERE id = ?"
4708
5057
  ), this.stmts.getTasksByStatus = this.db.prepare(
@@ -4781,11 +5130,16 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4781
5130
  deleteIdentity(purpose) {
4782
5131
  return this.stmts.deleteIdentity.run(purpose).changes > 0;
4783
5132
  }
5133
+ /**
5134
+ * Prefix matches are exact and case-sensitive (substr, not LIKE): with LIKE,
5135
+ * `_` and `%` in a server or adapter name were wildcards and ASCII case was
5136
+ * ignored, so detaching one server deleted another's credentials.
5137
+ */
4784
5138
  deleteIdentityByPrefix(prefix) {
4785
- return this.db.prepare("DELETE FROM adf_identity WHERE purpose LIKE ? || '%'").run(prefix).changes;
5139
+ return this.db.prepare("DELETE FROM adf_identity WHERE substr(purpose, 1, length(?)) = ?").run(prefix, prefix).changes;
4786
5140
  }
4787
5141
  listIdentityPurposes(prefix) {
4788
- return prefix ? this.stmts.listIdentityByPrefix.all(prefix).map((r) => r.purpose) : this.stmts.listAllIdentityPurposes.all().map((r) => r.purpose);
5142
+ return prefix ? this.stmts.listIdentityByPrefix.all(prefix, prefix).map((r) => r.purpose) : this.stmts.listAllIdentityPurposes.all().map((r) => r.purpose);
4789
5143
  }
4790
5144
  // --- Encrypted identity methods ---
4791
5145
  /**
@@ -4879,6 +5233,17 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4879
5233
  deleteAllAttestations() {
4880
5234
  this.db.prepare("DELETE FROM adf_attestations").run();
4881
5235
  }
5236
+ /**
5237
+ * Re-sign rows in place: each row whose signature is `from` takes `to`
5238
+ * (same issuer, subject and role), keeping its id and place. A row changed
5239
+ * or removed meanwhile matches nothing and is left as it is. Returns the
5240
+ * rows updated.
5241
+ */
5242
+ resignAttestations(updates) {
5243
+ let stmt = this.db.prepare("UPDATE adf_attestations SET signature = ?, raw_json = ? WHERE signature = ?"), changed = 0;
5244
+ for (let { from, to } of updates) changed += stmt.run(to.signature, JSON.stringify(to), from).changes;
5245
+ return changed;
5246
+ }
4882
5247
  deleteAllIdentity() {
4883
5248
  this.db.prepare("DELETE FROM adf_identity").run();
4884
5249
  }
@@ -4909,9 +5274,27 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4909
5274
  }
4910
5275
  return added && this.setConfig(config), config;
4911
5276
  }
5277
+ /**
5278
+ * setConfig calls through this connection: with dataVersion(), a cheap "did
5279
+ * the config change" check for callers that cache it (RuntimeService's
5280
+ * running-agent config).
5281
+ */
5282
+ configWrites = 0;
5283
+ dataVersionStmt = null;
5284
+ configWriteCount() {
5285
+ return this.configWrites;
5286
+ }
5287
+ /**
5288
+ * SQLite's `PRAGMA data_version`: changes when another connection (another
5289
+ * process, or another handle in this one) commits to the file; this
5290
+ * connection's own writes leave it unchanged (configWriteCount counts those).
5291
+ */
5292
+ dataVersion() {
5293
+ return this.dataVersionStmt ??= this.db.prepare("PRAGMA data_version").pluck(), this.dataVersionStmt.get();
5294
+ }
4912
5295
  setConfig(config) {
4913
5296
  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);
5297
+ 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
5298
  }
4916
5299
  // ===========================================================================
4917
5300
  // Loop Table
@@ -4970,7 +5353,25 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4970
5353
  opts?.ord ?? null,
4971
5354
  loop
4972
5355
  );
4973
- return this.bumpLoopRevision(loop), Number(result.lastInsertRowid);
5356
+ return this.bumpLoopRevision(loop), content.some((block) => block.type === "file_ref") && this.getFileRefRegistry().noteContent(content), Number(result.lastInsertRowid);
5357
+ }
5358
+ /** File-reference state of this file, shared by every connection to it (see file-ref-registry.ts). */
5359
+ getFileRefRegistry() {
5360
+ return fileRefRegistry(this, this.filePath === ":memory:" ? null : _AdfDatabase.canonicalKey(this.filePath));
5361
+ }
5362
+ /**
5363
+ * Rows of every loop that hold a `file_ref` block (a prefilter: the caller
5364
+ * parses and checks). One scan of adf_loop; callers run it only when a
5365
+ * referenced file is renamed or deleted.
5366
+ */
5367
+ getLoopRowsWithFileRefs() {
5368
+ return this.db.prepare(
5369
+ `SELECT seq, loop, content_json FROM adf_loop WHERE instr(content_json, '"file_ref"') > 0`
5370
+ ).all();
5371
+ }
5372
+ /** Replace one row's content in place (file references rewritten on rename/delete). */
5373
+ updateLoopContent(loop, seq, content) {
5374
+ this.db.prepare("UPDATE adf_loop SET content_json = ? WHERE seq = ? AND loop = ?").run(JSON.stringify(content), seq, loop), this.bumpLoopRevision(loop);
4974
5375
  }
4975
5376
  /** Wipe ONE stream. The whole-table wipe is clearAllLoops(). */
4976
5377
  clearLoop(loop) {
@@ -4999,6 +5400,15 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
4999
5400
  let row = this.stmts.getLastAssistantTokens.get(loop);
5000
5401
  return _AdfDatabase.parseLoopTokens(row?.tokens);
5001
5402
  }
5403
+ /** Every assistant row's usage record, in loop order (oldest first). */
5404
+ getAssistantTokenRows(loop) {
5405
+ let rows = this.stmts.getAssistantTokenRows.all(loop), out = [];
5406
+ for (let row of rows) {
5407
+ let tokens = _AdfDatabase.parseLoopTokens(row.tokens);
5408
+ tokens && out.push({ seq: row.seq, tokens });
5409
+ }
5410
+ return out;
5411
+ }
5002
5412
  /** Loop rows in display order: each row's seq plus its ordering key
5003
5413
  * (COALESCE(ord, seq)). Positional operations resolve boundaries against
5004
5414
  * ordKey, never raw seq — an ord'd summary sorts at its override position. */
@@ -5086,6 +5496,24 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
5086
5496
  transaction(fn) {
5087
5497
  return this.db.transaction(fn)();
5088
5498
  }
5499
+ /**
5500
+ * `fn` in a BEGIN IMMEDIATE transaction: the file's write lock is taken
5501
+ * before `fn` reads, so a check-then-write in it is atomic against every
5502
+ * other connection (another process provisioning the same file). Inside a
5503
+ * transaction already, a savepoint. `busyTimeoutMs`: how long BEGIN waits
5504
+ * for another connection's write lock before SQLITE_BUSY (the connection's
5505
+ * own busy_timeout, 5 s, otherwise).
5506
+ */
5507
+ immediateTransaction(fn, busyTimeoutMs) {
5508
+ if (busyTimeoutMs === void 0 || this.db.inTransaction) return this.db.transaction(fn).immediate();
5509
+ let before = this.db.pragma("busy_timeout", { simple: !0 });
5510
+ this.db.pragma(`busy_timeout = ${Math.max(0, Math.floor(busyTimeoutMs))}`);
5511
+ try {
5512
+ return this.db.transaction(fn).immediate();
5513
+ } finally {
5514
+ this.db.pragma(`busy_timeout = ${before}`);
5515
+ }
5516
+ }
5089
5517
  // ===========================================================================
5090
5518
  // Inbox
5091
5519
  // ===========================================================================
@@ -5482,6 +5910,9 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
5482
5910
  setTaskExecutorManaged(id, _value) {
5483
5911
  this.stmts.setTaskExecutorManaged.run(id);
5484
5912
  }
5913
+ setTaskApprovalMeta(id, approvalMeta) {
5914
+ this.stmts.setTaskApprovalMeta.run(approvalMeta, id);
5915
+ }
5485
5916
  updateTaskArgs(id, args) {
5486
5917
  this.stmts.updateTaskArgs.run(args, id);
5487
5918
  }
@@ -5624,7 +6055,9 @@ CREATE INDEX IF NOT EXISTS idx_adf_logs_origin ON adf_logs(origin);
5624
6055
  }
5625
6056
  close() {
5626
6057
  if (this.closed) return;
5627
- if (this.closed = !0, _AdfDatabase.decrementOpen(this.filePath) === 0)
6058
+ this.closed = !0;
6059
+ let remaining = _AdfDatabase.decrementOpen(this.filePath);
6060
+ if (remaining === 0 && this.getFileRefRegistry().media.clear(), remaining === 0)
5628
6061
  try {
5629
6062
  this.db.prepare(
5630
6063
  "INSERT INTO adf_meta (key, value, protection) VALUES (?, ?, 'readonly') ON CONFLICT(key) DO UPDATE SET value = excluded.value"
@@ -5970,12 +6403,12 @@ function buildLoopPreamble(loopName, agentHandle, grantedTools = []) {
5970
6403
 
5971
6404
  ${reachParagraph}
5972
6405
 
5973
- A timer, a trigger, or a message from another loop woke you. Do that work, write tersely, and end your turn.`;
6406
+ A timer, a trigger, a message from another loop, or the human writing to you directly woke you. Do that work, write tersely, and end your turn.`;
5974
6407
  }
5975
6408
  var LOOP_GOAL_SUMMARY_CHARS = 160;
5976
6409
  function summarizeGoal(goal) {
5977
- let flat = goal.replace(/\s+/g, " ").trim();
5978
- return flat.length <= LOOP_GOAL_SUMMARY_CHARS ? flat : `${flat.slice(0, LOOP_GOAL_SUMMARY_CHARS - 1).trimEnd()}\u2026`;
6410
+ let flat = (goal ?? "").replace(/\s+/g, " ").trim();
6411
+ return flat ? flat.length <= LOOP_GOAL_SUMMARY_CHARS ? flat : `${flat.slice(0, LOOP_GOAL_SUMMARY_CHARS - 1).trimEnd()}\u2026` : "(no goal)";
5979
6412
  }
5980
6413
  var LOOP_ARCHETYPES = `Use an inner loop when a piece of thinking should run on its own stream: tending memory on a timer, a sub-task that needs your full context, reviewing a draft before you act, thinking while nothing external is happening. Name each one for what it does for you. Guide: ${DOCS_GUIDES_URL}/inner-loops.md`;
5981
6414
  function buildMainLoopsSection(loops, options) {
@@ -6081,12 +6514,12 @@ function deriveLoopConfig(parent, loop) {
6081
6514
  throw new Error("deriveLoopConfig: 'main' is the host loop \u2014 use the raw agent config");
6082
6515
  let derived = cloneJson(parent);
6083
6516
  derived.tools = deriveTools(parent, loop);
6084
- let grantedNames = derived.tools.filter((t) => t.enabled).map((t) => t.name);
6085
- return derived.instructions = `${buildLoopPreamble(loop.name, parent.handle || parent.name, grantedNames)}
6517
+ let grantedNames = derived.tools.filter((t) => t.enabled).map((t) => t.name), preamble = buildLoopPreamble(loop.name, parent.handle || parent.name, grantedNames);
6518
+ return derived.instructions = hasLoopGoal(loop) ? `${preamble}
6086
6519
 
6087
6520
  Your goal:
6088
6521
 
6089
- ${loop.goal}`, derived.model = cloneJson(loop.model ?? parent.model), derived.autonomous = loop.autonomous === !0, loop.compact_threshold != null && (derived.context = { ...derived.context, compact_threshold: loop.compact_threshold }), derived.triggers = deriveTriggers(parent, loop.name), derived.code_execution = {
6522
+ ${loop.goal}` : preamble, derived.model = cloneJson(loop.model ?? parent.model), derived.autonomous = loop.autonomous === !0, loop.compact_threshold != null && (derived.context = { ...derived.context, compact_threshold: loop.compact_threshold }), derived.triggers = deriveTriggers(parent, loop.name), derived.code_execution = {
6090
6523
  ...deriveLoopCodeExecutionFlags(parent),
6091
6524
  // No inherited packages. A pure-JS package is loaded in the sandbox worker
6092
6525
  // through a worker-scope `createRequire` with an UNRESTRICTED `require` —
@@ -6145,6 +6578,14 @@ var CODE_FORBIDDEN_WRITE_RE = /^crypto:/;
6145
6578
  function isCodeForbiddenIdentityWrite(purpose) {
6146
6579
  return CODE_FORBIDDEN_WRITE_RE.test(purpose);
6147
6580
  }
6581
+ function parseLoopContent(json) {
6582
+ try {
6583
+ let parsed = JSON.parse(json);
6584
+ return Array.isArray(parsed) ? parsed : [];
6585
+ } catch {
6586
+ return [];
6587
+ }
6588
+ }
6148
6589
  var AdfWorkspace = class _AdfWorkspace {
6149
6590
  db;
6150
6591
  filePath;
@@ -6812,6 +7253,14 @@ var AdfWorkspace = class _AdfWorkspace {
6812
7253
  getAgentConfig() {
6813
7254
  return this.db.getConfig();
6814
7255
  }
7256
+ /** Config writes through this file's connection so far (AdfDatabase.configWriteCount); no SQLite read. */
7257
+ configWriteCount() {
7258
+ return this.db.configWriteCount();
7259
+ }
7260
+ /** Commits by other connections to this file (AdfDatabase.dataVersion); no table read. */
7261
+ dataVersion() {
7262
+ return this.db.dataVersion();
7263
+ }
6815
7264
  setAgentConfig(config) {
6816
7265
  let previous = null;
6817
7266
  try {
@@ -7151,6 +7600,10 @@ var AdfWorkspace = class _AdfWorkspace {
7151
7600
  getLastAssistantTokens() {
7152
7601
  return this.db.getLastAssistantTokens(this.boundLoop);
7153
7602
  }
7603
+ /** Every assistant row's usage record in loop order — see AdfDatabase.getAssistantTokenRows. */
7604
+ getAssistantTokenRows() {
7605
+ return this.db.getAssistantTokenRows(this.boundLoop);
7606
+ }
7154
7607
  /** Meta key of this loop's context baseline: bare for main, `:<loop>`-suffixed
7155
7608
  * for side loops (one baseline per cognition stream). */
7156
7609
  contextBaselineKey() {
@@ -7522,12 +7975,12 @@ var AdfWorkspace = class _AdfWorkspace {
7522
7975
  let deleted2 = !1;
7523
7976
  return this.db.transaction(() => {
7524
7977
  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;
7978
+ }), deleted2 && (this.emitUmbilical("file.deleted", { path: relativePath }), this.emitFileChange(relativePath, "deleted", void 0, entry?.content, metadata), this.rewriteFileRefs([{ op: "delete", path: relativePath }])), deleted2;
7526
7979
  }
7527
7980
  let previous = this.onFileChangeCallback ? this.db.readFile(relativePath)?.content : void 0;
7528
7981
  opts?.force && this.db.fileExists(relativePath) && this.db.setFileProtection(relativePath, "none");
7529
7982
  let deleted = this.db.deleteFile(relativePath);
7530
- return deleted && (this.emitUmbilical("file.deleted", { path: relativePath }), this.emitFileChange(relativePath, "deleted", void 0, previous, metadata)), deleted;
7983
+ return deleted && (this.emitUmbilical("file.deleted", { path: relativePath }), this.emitFileChange(relativePath, "deleted", void 0, previous, metadata), this.rewriteFileRefs([{ op: "delete", path: relativePath }])), deleted;
7531
7984
  }
7532
7985
  getFileMeta(path) {
7533
7986
  return this.db.getFileMeta(path);
@@ -7547,7 +8000,10 @@ var AdfWorkspace = class _AdfWorkspace {
7547
8000
  return this.db.fileExists(relativePath);
7548
8001
  }
7549
8002
  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);
8003
+ let previous = this.onFileChangeCallback ? this.db.readFile(oldPath)?.content : void 0, metadata = this.fileChangeMeta(oldPath), renamed = this.db.transaction(() => {
8004
+ let done = this.db.renameFile(oldPath, newPath);
8005
+ return done && this.rewriteFileRefs([{ op: "rename", from: oldPath, to: newPath }]), done;
8006
+ });
7551
8007
  return renamed && (this.emitFileChange(oldPath, "deleted", void 0, previous, metadata), this.emitFileChange(newPath, "created", previous, void 0, this.fileChangeMeta(newPath))), renamed;
7552
8008
  }
7553
8009
  renameFolder(oldPrefix, newPrefix) {
@@ -7555,14 +8011,15 @@ var AdfWorkspace = class _AdfWorkspace {
7555
8011
  path: file.path,
7556
8012
  content: notify ? this.db.readFile(file.path)?.content : void 0,
7557
8013
  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}/`;
8014
+ })), replacementPrefix = newPrefix.endsWith("/") ? newPrefix : `${newPrefix}/`, count = this.db.transaction(() => {
8015
+ let changed = this.db.renameFolder(oldPrefix, newPrefix);
8016
+ return changed > 0 && this.rewriteFileRefs(moved.map((file) => ({ op: "rename", from: file.path, to: replacementPrefix + file.path.slice(prefix.length) }))), changed;
8017
+ });
8018
+ if (count > 0)
7561
8019
  for (let file of moved) {
7562
8020
  let newPath = replacementPrefix + file.path.slice(prefix.length);
7563
8021
  this.emitFileChange(file.path, "deleted", void 0, file.content, file.metadata), this.emitFileChange(newPath, "created", file.content, void 0, this.fileChangeMeta(newPath));
7564
8022
  }
7565
- }
7566
8023
  return count;
7567
8024
  }
7568
8025
  // The sinks are per-agent, not per-loop: registering through a forLoop() view
@@ -7597,8 +8054,38 @@ var AdfWorkspace = class _AdfWorkspace {
7597
8054
  } catch {
7598
8055
  }
7599
8056
  }
8057
+ /**
8058
+ * Keep file references (`file_ref` blocks) pointing at the right file: a
8059
+ * rename or move rewrites the path, a delete leaves a tombstone. Rewrites
8060
+ * every loop row that names the file, then every live session's copy. Only
8061
+ * files some row references cost a scan; the set of referenced paths is
8062
+ * built once per process and kept current by loop writes.
8063
+ */
8064
+ rewriteFileRefs(changes) {
8065
+ if (changes.length === 0) return;
8066
+ let registry3 = this.db.getFileRefRegistry();
8067
+ try {
8068
+ if (!registry3.referenced) {
8069
+ let referenced = /* @__PURE__ */ new Set();
8070
+ for (let row of this.db.getLoopRowsWithFileRefs())
8071
+ for (let block of parseLoopContent(row.content_json))
8072
+ isFileRefBlock(block) && !block.file_ref.deleted && referenced.add(block.file_ref.path);
8073
+ registry3.referenced = referenced;
8074
+ }
8075
+ if (!registry3.affects(changes)) return;
8076
+ this.db.transaction(() => {
8077
+ for (let row of this.db.getLoopRowsWithFileRefs()) {
8078
+ let content = parseLoopContent(row.content_json), next = applyFileRefChanges(content, changes);
8079
+ next !== content && this.db.updateLoopContent(row.loop, row.seq, next);
8080
+ }
8081
+ }), registry3.noteChanges(changes);
8082
+ } catch (error) {
8083
+ console.error("[AdfWorkspace] Could not update file references:", error);
8084
+ }
8085
+ registry3.notifyHolders(changes);
8086
+ }
7600
8087
  emitFileChange(path, operation, content, previousContent, metadata) {
7601
- if (this.skillIndexer.notifyPath(path), !this.onFileChangeCallback) return;
8088
+ if (this.db.getFileRefRegistry().invalidate(path), this.skillIndexer.notifyPath(path), this.emitDataChange("files"), !this.onFileChangeCallback) return;
7602
8089
  let mimeType = metadata?.mime_type ?? this.getMimeType(path);
7603
8090
  this.onFileChangeCallback({
7604
8091
  path,
@@ -7738,6 +8225,9 @@ var AdfWorkspace = class _AdfWorkspace {
7738
8225
  setTaskExecutorManaged(id, value) {
7739
8226
  this.db.setTaskExecutorManaged(id, value);
7740
8227
  }
8228
+ setTaskApprovalMeta(id, approvalMeta) {
8229
+ this.db.setTaskApprovalMeta(id, approvalMeta);
8230
+ }
7741
8231
  updateTaskArgs(id, args) {
7742
8232
  this.db.updateTaskArgs(id, args);
7743
8233
  }
@@ -7884,8 +8374,39 @@ var AdfWorkspace = class _AdfWorkspace {
7884
8374
  transaction(fn) {
7885
8375
  return this.db.transaction(fn);
7886
8376
  }
8377
+ /** AdfDatabase.immediateTransaction: an atomic check-then-write against other connections to the file. */
8378
+ immediateTransaction(fn, busyTimeoutMs) {
8379
+ return this.db.immediateTransaction(fn, busyTimeoutMs);
8380
+ }
7887
8381
  };
7888
8382
 
8383
+ // src/main/runtime/loop-usage.ts
8384
+ function summarizeLoopUsage(entries) {
8385
+ let totals = createUsageTotals(), byModel = /* @__PURE__ */ new Map(), usageRows = 0, estimatedRows = 0;
8386
+ for (let entry of entries) {
8387
+ if (!entry.tokens) continue;
8388
+ usageRows++, hasReportedUsage(entry.tokens) || estimatedRows++, addUsage(totals, entry.tokens);
8389
+ let model = entry.model ?? "unknown", bucket = byModel.get(model);
8390
+ bucket || (bucket = { model, rows: 0, ...createUsageTotals() }, byModel.set(model, bucket)), bucket.rows++, addUsage(bucket, entry.tokens);
8391
+ }
8392
+ return {
8393
+ loopRows: entries.length,
8394
+ usageRows,
8395
+ estimatedRows,
8396
+ totals,
8397
+ byModel: Array.from(byModel.values()).sort((a, b) => b.total - a.total)
8398
+ };
8399
+ }
8400
+ function createUsageTotals() {
8401
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
8402
+ }
8403
+ function addUsage(target, usage) {
8404
+ 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;
8405
+ }
8406
+ function hasReportedUsage(t) {
8407
+ return t.estimated ? !1 : t.cache_read !== void 0 || t.cache_write !== void 0 || t.reasoning !== void 0 || t.cost_usd !== void 0;
8408
+ }
8409
+
7889
8410
  // src/main/tools/shell/commands/status.ts
7890
8411
  function staticArgValue3(arg) {
7891
8412
  if (!arg) return null;
@@ -8062,7 +8583,7 @@ async function configTools(ctx) {
8062
8583
  var statusHandler = {
8063
8584
  name: "status",
8064
8585
  summary: "Show agent status summary",
8065
- helpText: "status State, token usage, budget remaining",
8586
+ helpText: "status State, model, token usage in the current loop",
8066
8587
  category: "identity",
8067
8588
  resolvedTools: ["sys_get_config"],
8068
8589
  async execute(ctx) {
@@ -8072,7 +8593,7 @@ var statusHandler = {
8072
8593
  `Model: ${config.model?.provider ?? ""}/${config.model?.model_id ?? ""}`,
8073
8594
  `Autonomy: ${config.autonomous ? "enabled" : "disabled"}`
8074
8595
  ];
8075
- return config.limits?.token_budget && lines.push(`Budget: ${config.limits.token_budget} tokens`), ok(lines.join(`
8596
+ return lines.push(`Tokens: ${tokenUsageLine(ctx)}`), ok(lines.join(`
8076
8597
  `));
8077
8598
  }
8078
8599
  }, stateHandler = {
@@ -8231,6 +8752,17 @@ var statusHandler = {
8231
8752
  falseHandler,
8232
8753
  sleepHandler
8233
8754
  ];
8755
+ function tokenUsageLine(ctx) {
8756
+ let usage;
8757
+ try {
8758
+ usage = summarizeLoopUsage(ctx.workspace.getLoop());
8759
+ } catch {
8760
+ return "unavailable";
8761
+ }
8762
+ if (usage.usageRows === 0) return "none recorded in the current loop";
8763
+ 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}`;
8764
+ return `${n(total)} total: ${n(input)} in${cached}, ${n(output)} out; ${calls} in the current loop`;
8765
+ }
8234
8766
 
8235
8767
  // src/main/tools/shell/commands/meta.ts
8236
8768
  var metaHandler = {
@@ -8426,7 +8958,7 @@ var CATEGORY_ORDER = [
8426
8958
  resolvedTools: [],
8427
8959
  async execute(ctx) {
8428
8960
  if (ctx.args.length > 0) {
8429
- let { getCommand: getCommand2 } = await import("./commands-KPJWFRUE.mjs"), handler = getCommand2(ctx.args[0]);
8961
+ let { getCommand: getCommand2 } = await import("./commands-KOMPXJZS.mjs"), handler = getCommand2(ctx.args[0]);
8430
8962
  return handler ? ok(handler.helpText) : ok(`Unknown command: ${ctx.args[0]}`);
8431
8963
  }
8432
8964
  let commands = getAllCommands(), grouped = /* @__PURE__ */ new Map();
@@ -8495,10 +9027,13 @@ export {
8495
9027
  verifyEd25519,
8496
9028
  rawPublicKeyToSpki,
8497
9029
  didToPublicKey,
9030
+ ADF_LATEST_SCHEMA_VERSION,
8498
9031
  AdfDatabase,
8499
9032
  withSource,
8500
9033
  withLoop,
8501
9034
  withTurn,
9035
+ currentTurnId,
9036
+ currentAgentId,
8502
9037
  currentSourceOrUnknown,
8503
9038
  init_execution_context,
8504
9039
  SKILLS_REGISTRY_PATH,
@@ -8509,6 +9044,7 @@ export {
8509
9044
  rawX25519PrivateToPkcs8,
8510
9045
  rawX25519PublicToSpki,
8511
9046
  extractRawX25519PublicKey,
9047
+ openKeySlot,
8512
9048
  sealWithDek,
8513
9049
  openWithDek,
8514
9050
  envelopeFromAlgo,
@@ -8532,12 +9068,11 @@ export {
8532
9068
  isCodeForbiddenIdentityWrite,
8533
9069
  AdfWorkspace,
8534
9070
  isTextMime,
8535
- isVisionMime,
8536
- isAudioInputMime,
8537
- isVideoInputMime,
8538
9071
  mimeToAudioFormat,
8539
9072
  formatSize,
8540
9073
  mimeToExt,
9074
+ summarizeLoopUsage,
9075
+ hasReportedUsage,
8541
9076
  EXIT,
8542
9077
  err,
8543
9078
  shellReadFile,