@namzu/sdk 39.0.0 → 41.0.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 (289) hide show
  1. package/CHANGELOG.md +328 -0
  2. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  3. package/dist/bridge/a2a/mapper.js +8 -0
  4. package/dist/bridge/a2a/mapper.js.map +1 -1
  5. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  6. package/dist/bridge/sse/mapper.js +11 -0
  7. package/dist/bridge/sse/mapper.js.map +1 -1
  8. package/dist/connector/index.d.ts +2 -2
  9. package/dist/connector/index.d.ts.map +1 -1
  10. package/dist/connector/index.js +1 -1
  11. package/dist/connector/index.js.map +1 -1
  12. package/dist/connector/mcp/adapter.d.ts.map +1 -1
  13. package/dist/connector/mcp/adapter.js +112 -10
  14. package/dist/connector/mcp/adapter.js.map +1 -1
  15. package/dist/connector/mcp/audio-admission.d.ts +17 -0
  16. package/dist/connector/mcp/audio-admission.d.ts.map +1 -0
  17. package/dist/connector/mcp/audio-admission.js +171 -0
  18. package/dist/connector/mcp/audio-admission.js.map +1 -0
  19. package/dist/connector/mcp/client.d.ts +252 -1
  20. package/dist/connector/mcp/client.d.ts.map +1 -1
  21. package/dist/connector/mcp/client.js +611 -39
  22. package/dist/connector/mcp/client.js.map +1 -1
  23. package/dist/connector/mcp/envelope.d.ts +91 -0
  24. package/dist/connector/mcp/envelope.d.ts.map +1 -0
  25. package/dist/connector/mcp/envelope.js +173 -0
  26. package/dist/connector/mcp/envelope.js.map +1 -0
  27. package/dist/connector/mcp/era.d.ts +130 -0
  28. package/dist/connector/mcp/era.d.ts.map +1 -0
  29. package/dist/connector/mcp/era.js +304 -0
  30. package/dist/connector/mcp/era.js.map +1 -0
  31. package/dist/connector/mcp/errors.d.ts +106 -0
  32. package/dist/connector/mcp/errors.d.ts.map +1 -0
  33. package/dist/connector/mcp/errors.js +154 -0
  34. package/dist/connector/mcp/errors.js.map +1 -0
  35. package/dist/connector/mcp/http-sse.d.ts +11 -0
  36. package/dist/connector/mcp/http-sse.d.ts.map +1 -1
  37. package/dist/connector/mcp/http-sse.js +21 -6
  38. package/dist/connector/mcp/http-sse.js.map +1 -1
  39. package/dist/connector/mcp/index.d.ts +7 -0
  40. package/dist/connector/mcp/index.d.ts.map +1 -1
  41. package/dist/connector/mcp/index.js +10 -0
  42. package/dist/connector/mcp/index.js.map +1 -1
  43. package/dist/connector/mcp/streamable-http.d.ts +83 -0
  44. package/dist/connector/mcp/streamable-http.d.ts.map +1 -1
  45. package/dist/connector/mcp/streamable-http.js +177 -11
  46. package/dist/connector/mcp/streamable-http.js.map +1 -1
  47. package/dist/connector/mcp/x-mcp-header.d.ts +56 -0
  48. package/dist/connector/mcp/x-mcp-header.d.ts.map +1 -0
  49. package/dist/connector/mcp/x-mcp-header.js +254 -0
  50. package/dist/connector/mcp/x-mcp-header.js.map +1 -0
  51. package/dist/constants/mcp/index.d.ts +123 -15
  52. package/dist/constants/mcp/index.d.ts.map +1 -1
  53. package/dist/constants/mcp/index.js +135 -16
  54. package/dist/constants/mcp/index.js.map +1 -1
  55. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  56. package/dist/manager/agent/lifecycle.js +23 -0
  57. package/dist/manager/agent/lifecycle.js.map +1 -1
  58. package/dist/manager/run/persistence.d.ts +8 -0
  59. package/dist/manager/run/persistence.d.ts.map +1 -1
  60. package/dist/manager/run/persistence.js +12 -0
  61. package/dist/manager/run/persistence.js.map +1 -1
  62. package/dist/prompt/coding-agent-doctrine.d.ts +20 -0
  63. package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
  64. package/dist/prompt/coding-agent-doctrine.js +19 -3
  65. package/dist/prompt/coding-agent-doctrine.js.map +1 -1
  66. package/dist/prompt/index.d.ts +1 -1
  67. package/dist/prompt/index.d.ts.map +1 -1
  68. package/dist/prompt/index.js +1 -1
  69. package/dist/prompt/index.js.map +1 -1
  70. package/dist/public-runtime.d.ts +6 -4
  71. package/dist/public-runtime.d.ts.map +1 -1
  72. package/dist/public-runtime.js +8 -4
  73. package/dist/public-runtime.js.map +1 -1
  74. package/dist/public-tools.d.ts +11 -0
  75. package/dist/public-tools.d.ts.map +1 -1
  76. package/dist/public-tools.js +14 -0
  77. package/dist/public-tools.js.map +1 -1
  78. package/dist/registry/tool/execute.d.ts.map +1 -1
  79. package/dist/registry/tool/execute.js +2 -3
  80. package/dist/registry/tool/execute.js.map +1 -1
  81. package/dist/registry/tool/portable.d.ts +65 -0
  82. package/dist/registry/tool/portable.d.ts.map +1 -0
  83. package/dist/registry/tool/portable.js +244 -0
  84. package/dist/registry/tool/portable.js.map +1 -0
  85. package/dist/registry/tool/schema.d.ts +32 -5
  86. package/dist/registry/tool/schema.d.ts.map +1 -1
  87. package/dist/registry/tool/schema.js +35 -9
  88. package/dist/registry/tool/schema.js.map +1 -1
  89. package/dist/registry/toolset/catalog.js +8 -8
  90. package/dist/registry/toolset/catalog.js.map +1 -1
  91. package/dist/runtime/jobs/awaited-jobs.d.ts +215 -0
  92. package/dist/runtime/jobs/awaited-jobs.d.ts.map +1 -0
  93. package/dist/runtime/jobs/awaited-jobs.js +259 -0
  94. package/dist/runtime/jobs/awaited-jobs.js.map +1 -0
  95. package/dist/runtime/jobs/registry.d.ts +33 -2
  96. package/dist/runtime/jobs/registry.d.ts.map +1 -1
  97. package/dist/runtime/jobs/registry.js +37 -0
  98. package/dist/runtime/jobs/registry.js.map +1 -1
  99. package/dist/runtime/query/executor.d.ts +28 -0
  100. package/dist/runtime/query/executor.d.ts.map +1 -1
  101. package/dist/runtime/query/executor.js +39 -1
  102. package/dist/runtime/query/executor.js.map +1 -1
  103. package/dist/runtime/query/file-evidence-context.d.ts.map +1 -1
  104. package/dist/runtime/query/file-evidence-context.js +159 -43
  105. package/dist/runtime/query/file-evidence-context.js.map +1 -1
  106. package/dist/runtime/query/file-evidence-replay.d.ts +260 -0
  107. package/dist/runtime/query/file-evidence-replay.d.ts.map +1 -0
  108. package/dist/runtime/query/file-evidence-replay.js +647 -0
  109. package/dist/runtime/query/file-evidence-replay.js.map +1 -0
  110. package/dist/runtime/query/file-evidence-seed.d.ts +50 -0
  111. package/dist/runtime/query/file-evidence-seed.d.ts.map +1 -0
  112. package/dist/runtime/query/file-evidence-seed.js +100 -0
  113. package/dist/runtime/query/file-evidence-seed.js.map +1 -0
  114. package/dist/runtime/query/index.d.ts.map +1 -1
  115. package/dist/runtime/query/index.js +94 -2
  116. package/dist/runtime/query/index.js.map +1 -1
  117. package/dist/runtime/query/iteration/index.d.ts +87 -9
  118. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  119. package/dist/runtime/query/iteration/index.js +193 -28
  120. package/dist/runtime/query/iteration/index.js.map +1 -1
  121. package/dist/runtime/query/iteration/phases/context.d.ts +10 -0
  122. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  123. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  124. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  125. package/dist/runtime/query/iteration/phases/tool-review.js +5 -1
  126. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  127. package/dist/runtime/query/plugin-hooks.d.ts +14 -0
  128. package/dist/runtime/query/plugin-hooks.d.ts.map +1 -1
  129. package/dist/runtime/query/plugin-hooks.js +18 -0
  130. package/dist/runtime/query/plugin-hooks.js.map +1 -1
  131. package/dist/runtime/query/repeat-call.d.ts +17 -4
  132. package/dist/runtime/query/repeat-call.d.ts.map +1 -1
  133. package/dist/runtime/query/repeat-call.js +26 -19
  134. package/dist/runtime/query/repeat-call.js.map +1 -1
  135. package/dist/runtime/query/steering.d.ts +11 -1
  136. package/dist/runtime/query/steering.d.ts.map +1 -1
  137. package/dist/runtime/query/steering.js +12 -1
  138. package/dist/runtime/query/steering.js.map +1 -1
  139. package/dist/runtime/query/tooling.d.ts +2 -0
  140. package/dist/runtime/query/tooling.d.ts.map +1 -1
  141. package/dist/runtime/query/tooling.js +1 -0
  142. package/dist/runtime/query/tooling.js.map +1 -1
  143. package/dist/sandbox/provider/local.d.ts.map +1 -1
  144. package/dist/sandbox/provider/local.js +46 -3
  145. package/dist/sandbox/provider/local.js.map +1 -1
  146. package/dist/scheduler/completion-inbox.d.ts +48 -2
  147. package/dist/scheduler/completion-inbox.d.ts.map +1 -1
  148. package/dist/scheduler/completion-inbox.js +102 -10
  149. package/dist/scheduler/completion-inbox.js.map +1 -1
  150. package/dist/scheduler/local.d.ts.map +1 -1
  151. package/dist/scheduler/local.js +8 -0
  152. package/dist/scheduler/local.js.map +1 -1
  153. package/dist/store/run/disk.d.ts +35 -1
  154. package/dist/store/run/disk.d.ts.map +1 -1
  155. package/dist/store/run/disk.js +100 -0
  156. package/dist/store/run/disk.js.map +1 -1
  157. package/dist/tools/builtins/bash.d.ts.map +1 -1
  158. package/dist/tools/builtins/bash.js +4 -10
  159. package/dist/tools/builtins/bash.js.map +1 -1
  160. package/dist/tools/builtins/edit-apply.d.ts +126 -0
  161. package/dist/tools/builtins/edit-apply.d.ts.map +1 -0
  162. package/dist/tools/builtins/edit-apply.js +360 -0
  163. package/dist/tools/builtins/edit-apply.js.map +1 -0
  164. package/dist/tools/builtins/edit.d.ts +143 -1
  165. package/dist/tools/builtins/edit.d.ts.map +1 -1
  166. package/dist/tools/builtins/edit.js +37 -219
  167. package/dist/tools/builtins/edit.js.map +1 -1
  168. package/dist/tools/builtins/index.d.ts +1 -0
  169. package/dist/tools/builtins/index.d.ts.map +1 -1
  170. package/dist/tools/builtins/index.js +9 -3
  171. package/dist/tools/builtins/index.js.map +1 -1
  172. package/dist/tools/builtins/job.js +1 -1
  173. package/dist/tools/builtins/job.js.map +1 -1
  174. package/dist/tools/builtins/read-file.d.ts +2 -2
  175. package/dist/tools/builtins/read-file.d.ts.map +1 -1
  176. package/dist/tools/builtins/read-file.js +50 -65
  177. package/dist/tools/builtins/read-file.js.map +1 -1
  178. package/dist/tools/builtins/read-render.d.ts +56 -0
  179. package/dist/tools/builtins/read-render.d.ts.map +1 -0
  180. package/dist/tools/builtins/read-render.js +73 -0
  181. package/dist/tools/builtins/read-render.js.map +1 -0
  182. package/dist/tools/builtins/wait-for-job-bounds.d.ts +67 -0
  183. package/dist/tools/builtins/wait-for-job-bounds.d.ts.map +1 -0
  184. package/dist/tools/builtins/wait-for-job-bounds.js +108 -0
  185. package/dist/tools/builtins/wait-for-job-bounds.js.map +1 -0
  186. package/dist/tools/builtins/wait-for-job.d.ts +6 -0
  187. package/dist/tools/builtins/wait-for-job.d.ts.map +1 -0
  188. package/dist/tools/builtins/wait-for-job.js +162 -0
  189. package/dist/tools/builtins/wait-for-job.js.map +1 -0
  190. package/dist/tools/builtins/write-file.js +5 -0
  191. package/dist/tools/builtins/write-file.js.map +1 -1
  192. package/dist/tools/coordinator/index.d.ts.map +1 -1
  193. package/dist/tools/coordinator/index.js +1 -7
  194. package/dist/tools/coordinator/index.js.map +1 -1
  195. package/dist/tools/file-read-tracker.d.ts.map +1 -1
  196. package/dist/tools/file-read-tracker.js +88 -10
  197. package/dist/tools/file-read-tracker.js.map +1 -1
  198. package/dist/types/agent/scheduler.d.ts +20 -0
  199. package/dist/types/agent/scheduler.d.ts.map +1 -1
  200. package/dist/types/agent/task.d.ts +20 -0
  201. package/dist/types/agent/task.d.ts.map +1 -1
  202. package/dist/types/connector/mcp.d.ts +205 -0
  203. package/dist/types/connector/mcp.d.ts.map +1 -1
  204. package/dist/types/message/index.d.ts +1 -1
  205. package/dist/types/message/index.d.ts.map +1 -1
  206. package/dist/types/message/index.js +2 -0
  207. package/dist/types/message/index.js.map +1 -1
  208. package/dist/types/run/entity.d.ts +13 -0
  209. package/dist/types/run/entity.d.ts.map +1 -1
  210. package/dist/types/run/events.d.ts +56 -0
  211. package/dist/types/run/events.d.ts.map +1 -1
  212. package/dist/types/run/events.js.map +1 -1
  213. package/dist/types/run/store.d.ts +41 -0
  214. package/dist/types/run/store.d.ts.map +1 -1
  215. package/dist/types/sandbox/index.d.ts +83 -15
  216. package/dist/types/sandbox/index.d.ts.map +1 -1
  217. package/dist/types/sandbox/index.js.map +1 -1
  218. package/dist/types/tool/index.d.ts +109 -0
  219. package/dist/types/tool/index.d.ts.map +1 -1
  220. package/dist/types/tool/index.js.map +1 -1
  221. package/dist/utils/env.d.ts +19 -0
  222. package/dist/utils/env.d.ts.map +1 -0
  223. package/dist/utils/env.js +25 -0
  224. package/dist/utils/env.js.map +1 -0
  225. package/package.json +1 -1
  226. package/src/bridge/a2a/mapper.ts +8 -0
  227. package/src/bridge/sse/mapper.ts +11 -0
  228. package/src/connector/index.ts +27 -0
  229. package/src/connector/mcp/adapter.ts +123 -10
  230. package/src/connector/mcp/audio-admission.ts +173 -0
  231. package/src/connector/mcp/client.ts +694 -45
  232. package/src/connector/mcp/envelope.ts +235 -0
  233. package/src/connector/mcp/era.ts +400 -0
  234. package/src/connector/mcp/errors.ts +171 -0
  235. package/src/connector/mcp/http-sse.ts +23 -6
  236. package/src/connector/mcp/index.ts +37 -0
  237. package/src/connector/mcp/streamable-http.ts +199 -11
  238. package/src/connector/mcp/x-mcp-header.ts +322 -0
  239. package/src/constants/mcp/index.ts +145 -16
  240. package/src/manager/agent/lifecycle.ts +29 -0
  241. package/src/manager/run/persistence.ts +12 -0
  242. package/src/prompt/coding-agent-doctrine.ts +31 -4
  243. package/src/prompt/index.ts +1 -0
  244. package/src/public-runtime.ts +37 -1
  245. package/src/public-tools.ts +18 -0
  246. package/src/registry/tool/execute.ts +2 -4
  247. package/src/registry/tool/portable.ts +264 -0
  248. package/src/registry/tool/schema.ts +38 -8
  249. package/src/registry/toolset/catalog.ts +8 -9
  250. package/src/runtime/jobs/awaited-jobs.ts +271 -0
  251. package/src/runtime/jobs/registry.ts +50 -0
  252. package/src/runtime/query/executor.ts +49 -1
  253. package/src/runtime/query/file-evidence-context.ts +190 -46
  254. package/src/runtime/query/file-evidence-replay.ts +776 -0
  255. package/src/runtime/query/file-evidence-seed.ts +126 -0
  256. package/src/runtime/query/index.ts +104 -2
  257. package/src/runtime/query/iteration/index.ts +202 -28
  258. package/src/runtime/query/iteration/phases/context.ts +10 -0
  259. package/src/runtime/query/iteration/phases/tool-review.ts +4 -0
  260. package/src/runtime/query/plugin-hooks.ts +20 -0
  261. package/src/runtime/query/repeat-call.ts +28 -18
  262. package/src/runtime/query/steering.ts +11 -0
  263. package/src/runtime/query/tooling.ts +3 -0
  264. package/src/sandbox/provider/local.ts +45 -2
  265. package/src/scheduler/completion-inbox.ts +105 -9
  266. package/src/scheduler/local.ts +8 -0
  267. package/src/store/run/disk.ts +108 -0
  268. package/src/tools/builtins/bash.ts +4 -10
  269. package/src/tools/builtins/edit-apply.ts +456 -0
  270. package/src/tools/builtins/edit.ts +39 -270
  271. package/src/tools/builtins/index.ts +9 -3
  272. package/src/tools/builtins/job.ts +1 -1
  273. package/src/tools/builtins/read-file.ts +56 -77
  274. package/src/tools/builtins/read-render.ts +104 -0
  275. package/src/tools/builtins/wait-for-job-bounds.ts +179 -0
  276. package/src/tools/builtins/wait-for-job.ts +184 -0
  277. package/src/tools/builtins/write-file.ts +5 -0
  278. package/src/tools/coordinator/index.ts +1 -7
  279. package/src/tools/file-read-tracker.ts +85 -7
  280. package/src/types/agent/scheduler.ts +21 -0
  281. package/src/types/agent/task.ts +21 -0
  282. package/src/types/connector/mcp.ts +205 -1
  283. package/src/types/message/index.ts +2 -0
  284. package/src/types/run/entity.ts +14 -0
  285. package/src/types/run/events.ts +56 -0
  286. package/src/types/run/store.ts +42 -0
  287. package/src/types/sandbox/index.ts +84 -15
  288. package/src/types/tool/index.ts +104 -0
  289. package/src/utils/env.ts +23 -0
@@ -0,0 +1,647 @@
1
+ import { resolve } from 'node:path';
2
+ import { isClearedToolResult } from '../../compaction/tool-result-editing.js';
3
+ import { replayEditCallWithin } from '../../tools/builtins/edit-apply.js';
4
+ import { EditTool } from '../../tools/builtins/edit.js';
5
+ import { ReadFileTool } from '../../tools/builtins/read-file.js';
6
+ import { renderNumberedRead, resolveReadWindow, } from '../../tools/builtins/read-render.js';
7
+ import { WriteFileTool } from '../../tools/builtins/write-file.js';
8
+ import { isSkippedToolResult } from './plugin-hooks.js';
9
+ /**
10
+ * Reconstructing a file's body from calls the conversation can still see.
11
+ *
12
+ * One module because two callers need the same answer and must not each have
13
+ * their own idea of it: the derived work context asks "is this ledger entry's
14
+ * body one the model can rebuild from what is in front of it?", and the resume
15
+ * seed asks "which of these ledger entries can be rebuilt at all?". A
16
+ * predicate that admitted a hop in one and refused it in the other would mean
17
+ * a path referenced as evidence that the ledger never witnessed, or the
18
+ * reverse. Everything below is a function of the messages, the ledger and the
19
+ * bounds — there is no filesystem access anywhere in this file.
20
+ */
21
+ /**
22
+ * Edit CALLS one chain may carry before the path is withheld.
23
+ *
24
+ * On calls rather than replacements because the bound is on replay work, and
25
+ * an `edits: [...]` batch is one pass over the content however many hunks it
26
+ * holds. Eight is generous for the shape this exists for — a file written and
27
+ * then refined within a turn — and short enough that the whole request's
28
+ * replay stays a rounding error beside the model call it rides on.
29
+ */
30
+ export const MAX_EDIT_CALLS = 8;
31
+ /**
32
+ * Content a replay may materialise across every path in one pass.
33
+ *
34
+ * Counted cumulatively in UTF-16 units — the measure the caps below and the
35
+ * step-context budget already use — and charged for the write body each chain
36
+ * starts from as well as every body an edit builds on top of it, because those
37
+ * are the strings actually built. Each operation is measured exactly against
38
+ * the content it is about to be applied to and refused before it is built, so
39
+ * a pass whose history is full of long chains it is going to refuse anyway
40
+ * cannot make itself slow proving that. A refusal is charged nothing: the body
41
+ * was never built, and the paths after it keep their room.
42
+ */
43
+ export const MAX_REPLAYED_UNITS = 256 * 1024;
44
+ /**
45
+ * Arguments longer than a real tool call carries; past this the call is not
46
+ * read AS EVIDENCE.
47
+ *
48
+ * A bound on what may be BELIEVED, and on nothing else. The path such a call
49
+ * declares is still read, under no bound at all — see {@link declaredPath} —
50
+ * because a write too long to quote back is still a write that happened, and a
51
+ * pass that could not say WHICH file it happened to would have to abandon every
52
+ * other path in the conversation to stay honest.
53
+ */
54
+ const MAX_ARGUMENT_UNITS = 32_000;
55
+ /** Call ids longer than any provider mints. */
56
+ const MAX_CALL_ID_UNITS = 256;
57
+ /**
58
+ * Path lengths a `write` entry may go out with.
59
+ *
60
+ * Exported because `file-evidence-context.ts` puts the same bound on the
61
+ * spelling a `read` entry emits — a read is the other thing that puts a path
62
+ * in the work-context message, and the constant belongs to this module.
63
+ */
64
+ export const MAX_PATH_UNITS = 512;
65
+ /**
66
+ * Arguments this module will not read as JSON at all, at any bound.
67
+ *
68
+ * {@link MAX_ARGUMENT_UNITS} governs what may be BELIEVED about a call, and
69
+ * deliberately leaves attribution unbounded so that one large `write` cannot
70
+ * cost a conversation every other witness in it. Unbounded is still not free:
71
+ * `JSON.parse` over a multi-megabyte body is real work, and it was being done
72
+ * up to three times for one call — once to collect the paths, once to key the
73
+ * mutation, once to read the call as evidence. Two of those are gone: the
74
+ * attribution answer is now memoised for the whole pass (see
75
+ * {@link PathAttributions}), and the evidence read never reaches a call past
76
+ * the far smaller bound above. What is left is this ceiling on the single
77
+ * remaining parse.
78
+ *
79
+ * About a megabyte, some thirty times the evidence bound, because the rule is
80
+ * that an oversize-but-ORDINARY write stays attributable — a generated file, a
81
+ * bundled config, a long document — while the pathological input is refused. A
82
+ * call past it is attributable to nothing, and the walk treats it exactly as
83
+ * it treats one that names no `path`: there is no key to withdraw, so the
84
+ * whole pass is abandoned and the conversation keeps the empty ledger a resume
85
+ * has always started from.
86
+ */
87
+ const MAX_ATTRIBUTION_UNITS = 1024 * 1024;
88
+ export function createPathAttributions() {
89
+ return new WeakMap();
90
+ }
91
+ /**
92
+ * A call's arguments as JSON, or `undefined` for arguments this module will
93
+ * not read.
94
+ *
95
+ * Every `JSON.parse` of a tool call in this file goes through here, so the
96
+ * ceiling is one rule rather than one rule per reader, and malformed input is
97
+ * a value the callers test rather than an exception they have to be wrapped
98
+ * against.
99
+ */
100
+ function parseArguments(call) {
101
+ if (call.function.arguments.length > MAX_ATTRIBUTION_UNITS)
102
+ return undefined;
103
+ try {
104
+ return JSON.parse(call.function.arguments);
105
+ }
106
+ catch {
107
+ return undefined;
108
+ }
109
+ }
110
+ /**
111
+ * The lexical key space: the path resolved against the working directory, or
112
+ * the path as written when the tools address a sandbox.
113
+ *
114
+ * What a reader of the ledger uses. Not what a writer of it may use: see
115
+ * {@link FileKeyResolver}.
116
+ */
117
+ export function lexicalFileKeys(workingDirectory, sandboxed) {
118
+ return (path) => (sandboxed ? path : resolve(workingDirectory, path));
119
+ }
120
+ /**
121
+ * Index a transcript once, so every lookup below is a map hit.
122
+ *
123
+ * An id claimed twice, or answered twice, indexes to `null` rather than to
124
+ * whichever message came last: two calls wearing one id is an ambiguity, and
125
+ * resolving it by position would let the second silently vouch for the first.
126
+ */
127
+ export function indexVisibleHistory(messages, keyOf) {
128
+ const calls = new Map();
129
+ const results = new Map();
130
+ for (const message of messages) {
131
+ if (message.role === 'assistant') {
132
+ for (const call of message.toolCalls ?? []) {
133
+ calls.set(call.id, calls.has(call.id) ? null : call);
134
+ }
135
+ }
136
+ else if (message.role === 'tool') {
137
+ results.set(message.toolCallId, results.has(message.toolCallId) ? null : message);
138
+ }
139
+ }
140
+ return { calls, results, keyOf };
141
+ }
142
+ /**
143
+ * Every path a replay of this history might have to key, in the order the
144
+ * calls arrived.
145
+ *
146
+ * For a caller that has to resolve them before the replay can run, and only
147
+ * for that: nothing here decides anything about a path. Empty when the history
148
+ * holds no `write` and no `edit` call at all, because a claim is only ever made
149
+ * by a mutation — a conversation that only read files reconstructs nothing, and
150
+ * resolving its reads would be filesystem work with no possible result.
151
+ *
152
+ * Every mutation the walk can ATTRIBUTE has to appear here, the ones it will
153
+ * refuse to reconstruct included. This used to drop a call whose arguments ran
154
+ * past the evidence bound, so the walk reached it holding no key for its path,
155
+ * read that as a mutation it could not attribute, and abandoned the seeding
156
+ * entirely: one thirty-kilobyte `write` anywhere in a conversation erased every
157
+ * witness in it. The bounds belong to what may be believed; the path is read
158
+ * here out of the same field the walk reads it from, under no bound at all.
159
+ */
160
+ export function collectObservedPaths(messages, attributions = createPathAttributions()) {
161
+ const paths = new Set();
162
+ let mutated = false;
163
+ for (const message of messages) {
164
+ if (message.role !== 'assistant')
165
+ continue;
166
+ for (const call of message.toolCalls ?? []) {
167
+ const name = call.function.name;
168
+ if (name !== 'write' && name !== 'edit' && name !== 'read')
169
+ continue;
170
+ if (name !== 'read')
171
+ mutated = true;
172
+ const path = declaredPath(call, attributions);
173
+ if (path !== undefined)
174
+ paths.add(path);
175
+ }
176
+ }
177
+ return mutated ? [...paths] : [];
178
+ }
179
+ /**
180
+ * The `path` argument a tool call declares, whatever else its input holds.
181
+ *
182
+ * The one attribution rule, used both to collect the paths a pass must resolve
183
+ * and to key each mutation as the walk reaches it — one function, so the two
184
+ * can never disagree about which file a call touched, which is the disagreement
185
+ * that turns an ordinary large write into a total loss.
186
+ *
187
+ * The recorded arguments and nothing else. A receipt names the path too, but it
188
+ * names it inside a sentence a path may itself contain, in a spelling that
189
+ * differs between the host and sandbox branches, and compaction clears receipts
190
+ * while it never clears a call. A call the provider stream cut off mid-JSON
191
+ * carries `{}` here: `function.arguments` is normalized to that and the raw
192
+ * buffer moves to `metadata.partialArguments`, which is what the model was
193
+ * saying rather than what ran — a `repairToolCall` hook may have rewritten the
194
+ * arguments before execution. So a truncated call is attributable to nothing,
195
+ * and the walk treats it as such. So is a call whose arguments run past
196
+ * {@link MAX_ATTRIBUTION_UNITS}, which this does not read at all.
197
+ *
198
+ * Answered once per pass and remembered. The path collection and the walk ask
199
+ * the same question of the same calls, and a `write` carries a whole file body
200
+ * in the string being parsed to answer it.
201
+ */
202
+ function declaredPath(call, attributions) {
203
+ const remembered = attributions.get(call);
204
+ if (remembered !== undefined)
205
+ return remembered ?? undefined;
206
+ const parsed = parseArguments(call);
207
+ const declared = typeof parsed === 'object' && parsed !== null ? parsed.path : undefined;
208
+ const path = typeof declared === 'string' && declared.length > 0 ? declared : null;
209
+ attributions.set(call, path);
210
+ return path ?? undefined;
211
+ }
212
+ /**
213
+ * The visibility every hop must pass, write or edit, root or tip.
214
+ *
215
+ * One function because a middle hop is not a lesser claim than the last one.
216
+ * A chain whose second edit was cleared by compaction, or came back an error,
217
+ * or arrived truncated, reconstructs nothing at all — so the whole path is
218
+ * withheld rather than emitted as the part still visible, which would name a
219
+ * body the model cannot rebuild.
220
+ *
221
+ * A call a `pre_tool_use` hook SKIPPED is the one refusal that does not arrive
222
+ * as an error. The hook declined the call; nothing failed, so the receipt is a
223
+ * plain success carrying the sentence `plugin-hooks.ts` writes for it, and
224
+ * reading that as a `write` would hand back a body the tool was never allowed
225
+ * to put on disk — a fingerprint for a file that still holds whatever it held
226
+ * before, and a spurious drift refusal on the next edit. Recognised through
227
+ * the same function that writes the sentence, so the two cannot drift apart.
228
+ */
229
+ export function visibleCall(history, id, name) {
230
+ const call = history.calls.get(id);
231
+ const receipt = history.results.get(id);
232
+ if (!call ||
233
+ call.function.name !== name ||
234
+ call.metadata?.inputTruncated ||
235
+ call.function.arguments.length > MAX_ARGUMENT_UNITS ||
236
+ id.length > MAX_CALL_ID_UNITS ||
237
+ !receipt ||
238
+ receipt.role !== 'tool' ||
239
+ receipt.isError ||
240
+ typeof receipt.content !== 'string' ||
241
+ isClearedToolResult(receipt.content) ||
242
+ isSkippedToolResult(name, receipt.content))
243
+ return undefined;
244
+ return call;
245
+ }
246
+ /** A full body that arrived whole in one visible call. */
247
+ export function visibleWrite(history, id) {
248
+ const call = visibleCall(history, id, 'write');
249
+ if (!call)
250
+ return;
251
+ const input = WriteFileTool.inputSchema.safeParse(parseArguments(call));
252
+ if (!input.success || input.data.path.length > MAX_PATH_UNITS)
253
+ return;
254
+ const body = input.data.content ?? input.data.newStr;
255
+ if (typeof body !== 'string')
256
+ return;
257
+ const key = history.keyOf(input.data.path);
258
+ if (key === undefined)
259
+ return;
260
+ return { path: input.data.path, key, body };
261
+ }
262
+ /**
263
+ * One visible edit call's arguments, ready to replay.
264
+ *
265
+ * No length bound on the path here, unlike the write above. A write's path is
266
+ * the spelling an entry goes out with; an edit is held to the KEY it resolves
267
+ * to, which its chain's root has already been bounded on.
268
+ */
269
+ export function visibleEdit(history, id) {
270
+ const call = visibleCall(history, id, 'edit');
271
+ if (!call)
272
+ return;
273
+ const input = EditTool.inputSchema.safeParse(parseArguments(call));
274
+ if (!input.success)
275
+ return;
276
+ const key = history.keyOf(input.data.path);
277
+ if (key === undefined)
278
+ return;
279
+ return { key, input: input.data };
280
+ }
281
+ export function createReplayBudget(units = MAX_REPLAYED_UNITS) {
282
+ return { remaining: units };
283
+ }
284
+ /**
285
+ * Charge one body against the pass's replay allowance.
286
+ *
287
+ * Checked before it is taken, so a refusal costs nothing. Deducting first and
288
+ * reporting afterwards left the allowance negative, and one oversized path
289
+ * then refused every admissible path behind it in the same pass.
290
+ */
291
+ export function spend(budget, units) {
292
+ if (units > budget.remaining)
293
+ return false;
294
+ budget.remaining -= units;
295
+ return true;
296
+ }
297
+ /**
298
+ * Replay one edit call onto the body in hand, within what the budget has left.
299
+ *
300
+ * `undefined` for a hop the budget turns away and for one that no longer
301
+ * applies, because both mean the same thing to every caller here: this path
302
+ * reconstructs nothing. The charge is settled either way — what the attempt
303
+ * built, it built — and for a hop refused at its first operation that charge
304
+ * is zero.
305
+ */
306
+ export function replayHop(content, input, budget) {
307
+ const replay = replayEditCallWithin(content, input, budget.remaining);
308
+ budget.remaining = Math.max(budget.remaining - replay.charged, 0);
309
+ return replay.outcome === 'replayed' ? replay.content : undefined;
310
+ }
311
+ /**
312
+ * Whether a refused mutation has reported this path stale since the ledger
313
+ * last observed it.
314
+ *
315
+ * That refusal read the real file to make its comparison, so the ledger knows
316
+ * its body is behind disk without a consumer here touching the filesystem.
317
+ * The reconstruction would still be a body the model can rebuild — but not the
318
+ * FILE's body, which is what an entry claims. Withheld until a real
319
+ * observation re-baselines the ledger, which is also what clears the flag.
320
+ */
321
+ export function knownStale(tracker, key) {
322
+ return tracker.driftObserved?.(key) === true;
323
+ }
324
+ /**
325
+ * Paths one pass may hold a reconstructed body for at a time.
326
+ *
327
+ * The projection emits at most this many entries and keeps the most recent, so
328
+ * a pass that tracked more would be doing work whose result is discarded.
329
+ */
330
+ export const MAX_WITNESSED_PATHS = 6;
331
+ /**
332
+ * Rebuild an observation ledger from a conversation's own history.
333
+ *
334
+ * The ledger is process state. A resumed conversation gets a fresh empty one,
335
+ * and until something reads a file again the runtime knows nothing about files
336
+ * this conversation wrote in full — so the projection admits nothing and the
337
+ * model re-reads a body already in front of it, every time a session is picked
338
+ * back up.
339
+ *
340
+ * What is reconstructable here is exactly what the projection would admit, by
341
+ * the same predicates and the same bounded replay, because both go through the
342
+ * functions above. A `write` whose call and successful receipt are both intact
343
+ * hands back its body and its witness; the edits on top of it are replayed
344
+ * hop by hop and extend the chain. Anything else about a path the conversation
345
+ * mutated — a receipt compaction cleared, a call the transcript never answered,
346
+ * a mutation it refused, one a `pre_tool_use` hook skipped before it ever ran,
347
+ * a hop that no longer applies, a body past the bounds, a call too long to
348
+ * quote back — withdraws whatever this pass was holding for that path and
349
+ * writes NOTHING for it.
350
+ *
351
+ * Content-backed observations only, and that is the whole of what a resume
352
+ * restores. A path in the ledger with no fingerprint is a path `write` lets
353
+ * through unread: the read-before-overwrite refusal is `hasRead`, and the drift
354
+ * comparison it guards needs a body to compare. Seeding membership alone would
355
+ * therefore admit a full overwrite of a file that changed while the session was
356
+ * closed, with nothing checked — weaker than the empty ledger a resume used to
357
+ * start from, which refuses that overwrite outright. So a path this pass cannot
358
+ * rebuild is left exactly as that empty ledger leaves it, and the first real
359
+ * read re-establishes it.
360
+ *
361
+ * Two things this deliberately does not do. It never reads the filesystem: a
362
+ * fingerprint restored here is a claim derived from history, and the built-in
363
+ * mutation checks still compare it against the real file before anything is
364
+ * written — so a file changed while the session was closed is refused exactly
365
+ * as it is today, and the refusal's drift flag withdraws the entry. And it
366
+ * never recovers a body from a `read` receipt by undoing the line numbering: a
367
+ * read's rendering is compared, whole, against a body this pass already holds,
368
+ * and a read that cannot be matched that way withdraws the body rather than
369
+ * supplying one.
370
+ *
371
+ * That first guarantee is the reason `keyOf` is a parameter. Everything written
372
+ * here has to land on the key the mutation tools will look it up under, and on
373
+ * a host those keys are canonical — `write` and `edit` follow every symlink
374
+ * before they touch the ledger. Keying a seed lexically instead writes entries
375
+ * into a key space the tools never read: the projection would then match a
376
+ * fingerprint this pass had just written to it, and the drift refusal that is
377
+ * supposed to withdraw the claim would land somewhere else and never reach it.
378
+ * So the caller resolves the paths the way the tools do — see
379
+ * `file-evidence-seed.ts` — and a path it could not resolve gets no key, which
380
+ * makes the mutation that named it unattributable and stops the pass.
381
+ */
382
+ export function replayObservationLedger(messages, tracker, keyOf, attributions = createPathAttributions()) {
383
+ const history = indexVisibleHistory(messages, keyOf);
384
+ const budget = createReplayBudget();
385
+ const claims = new Map();
386
+ // The keys currently holding a body, newest last — the projection's own
387
+ // order, so the paths this keeps are the paths it would emit.
388
+ const withBody = new Set();
389
+ for (const message of messages) {
390
+ if (message.role !== 'assistant')
391
+ continue;
392
+ for (const call of message.toolCalls ?? []) {
393
+ const name = call.function.name;
394
+ if (name !== 'write' && name !== 'edit' && name !== 'read')
395
+ continue;
396
+ const receipt = history.results.get(call.id);
397
+ // Two receipts wearing one call id hide which of them this call got.
398
+ // On a mutation that is the ambiguity below, arriving from the other
399
+ // side. On a READ it is no smaller: the receipt that was hidden could
400
+ // be the one showing a body this walk is holding to be something else,
401
+ // and skipping the read past would keep a claim that very observation
402
+ // withdrew. Both get the same answer.
403
+ if (receipt === null)
404
+ return commit(tracker, new Map(), budget);
405
+ const answered = receipt !== undefined && receipt.role === 'tool' && !receipt.isError;
406
+ if (name === 'read') {
407
+ // A read that never came back, or came back refused, saw no body: it
408
+ // can neither confirm what this pass holds nor contradict it. And a
409
+ // read changes no file, so a claim left standing across one is still a
410
+ // claim about the same bytes.
411
+ if (answered)
412
+ observeRead(history, call, claims, withBody, budget);
413
+ continue;
414
+ }
415
+ // One id claimed by two calls hides which of them ran. On a path this
416
+ // pass is not holding that is merely invisible, but a mutation could
417
+ // have replaced a body claimed somewhere else in this walk, and there
418
+ // is no way to tell where — so the pass establishes nothing at all
419
+ // rather than carry a body something unseen may have moved past.
420
+ if (history.calls.get(call.id) === null)
421
+ return commit(tracker, new Map(), budget);
422
+ // Attribution before outcome, for every mutation the walk reaches. A call
423
+ // the transcript never answered — the unknown-outcome result the kernel's
424
+ // own repair writes for one included — is precisely the call whose effect
425
+ // on the file nobody knows: the process may have died with the write half
426
+ // made. And a refusal is a tool's own report about this path, a drift
427
+ // refusal above all, which read the disk and found the body this ledger
428
+ // holds is not the body there. Carrying a claim through either would have
429
+ // the first resumed request tell the model a file holds a body the
430
+ // transcript itself says nobody can vouch for, so each withdraws the path
431
+ // it names — and only that path. A mutation attributable to no path at all
432
+ // is still the one thing that stops the pass, because then there is no
433
+ // path to withdraw.
434
+ const key = mutatedKey(history, call, attributions);
435
+ if (key === undefined)
436
+ return commit(tracker, new Map(), budget);
437
+ const claim = answered
438
+ ? mutate(history, call, name, key, claims.get(key), budget)
439
+ : { kind: 'seen' };
440
+ claims.set(key, claim);
441
+ withBody.delete(key);
442
+ if (claim.kind === 'content')
443
+ withBody.add(key);
444
+ // Oldest body first, matching what the projection keeps. An evicted
445
+ // path is one this pass stops vouching for: it was mutated here, and
446
+ // what it holds is past what the pass can carry, so it reaches the
447
+ // ledger as the nothing every unrebuilt path reaches it as.
448
+ if (withBody.size > MAX_WITNESSED_PATHS) {
449
+ const oldest = withBody.values().next().value;
450
+ withBody.delete(oldest);
451
+ // Setting an existing key leaves it where it is, so the order the
452
+ // ledger is written in stays the order the calls arrived in.
453
+ claims.set(oldest, { kind: 'seen' });
454
+ }
455
+ }
456
+ }
457
+ return commit(tracker, claims, budget);
458
+ }
459
+ /** Apply a walk's surviving claims to the ledger, in the order they were made. */
460
+ function commit(tracker, claims, budget) {
461
+ let pathsWitnessed = 0;
462
+ let pathsSeen = 0;
463
+ for (const [key, claim] of claims) {
464
+ if (claim.kind === 'seen') {
465
+ // Nothing. Not even membership: `hasRead` is the read-before-overwrite
466
+ // refusal, and granting it without a fingerprint would let a full
467
+ // overwrite of this path through with no body to compare — over a file
468
+ // that may have moved while the session was closed. The claim was
469
+ // carried through the walk so that no edit stacked onto a body
470
+ // something unseen had replaced; having done that, it is dropped.
471
+ pathsSeen++;
472
+ continue;
473
+ }
474
+ for (const step of claim.steps) {
475
+ if (step.kind === 'write') {
476
+ tracker.recordRead(key, step.body, step.callId);
477
+ }
478
+ else if (tracker.recordEdit) {
479
+ tracker.recordEdit(key, step.content, step.callId);
480
+ }
481
+ else {
482
+ // The same fallback the `edit` tool takes against a tracker without
483
+ // the method: the observation advances and no chain is built.
484
+ tracker.recordRead(key, step.content);
485
+ }
486
+ }
487
+ pathsWitnessed++;
488
+ }
489
+ return { pathsWitnessed, pathsSeen, unitsReplayed: MAX_REPLAYED_UNITS - budget.remaining };
490
+ }
491
+ /** Fold one successful mutation into the path's claim. */
492
+ function mutate(history, call, name, key, claim, budget) {
493
+ if (name === 'write') {
494
+ const written = visibleWrite(history, call.id);
495
+ // A write nothing may quote back is still a write that HAPPENED, and the
496
+ // path it happened to is known: arguments past the evidence bound, a call
497
+ // the stream truncated, a path longer than an entry goes out with, a key
498
+ // the call and the ledger disagree about. Each of those withdraws the
499
+ // body and keeps the walk going — what was on this path is now unknown,
500
+ // which is a statement about one path rather than a reason to stop. A
501
+ // call a hook SKIPPED lands here too, from the other direction: that one
502
+ // never ran, so the file holds whatever it held before and this pass
503
+ // cannot say what that was either.
504
+ if (!written || written.key !== key)
505
+ return { kind: 'seen' };
506
+ if (!spend(budget, written.body.length))
507
+ return { kind: 'seen' };
508
+ // A full body replaces everything under it, chain included.
509
+ return {
510
+ kind: 'content',
511
+ body: written.body,
512
+ steps: [{ kind: 'write', body: written.body, callId: call.id }],
513
+ };
514
+ }
515
+ // An edit onto a body this pass is not holding cannot be replayed onto
516
+ // anything, and one onto a chain already at its bound stops being followed.
517
+ if (!claim || claim.kind !== 'content')
518
+ return { kind: 'seen' };
519
+ if (claim.steps.length > MAX_EDIT_CALLS)
520
+ return { kind: 'seen' };
521
+ const hop = visibleEdit(history, call.id);
522
+ if (!hop || hop.key !== key)
523
+ return { kind: 'seen' };
524
+ const replayed = replayHop(claim.body, hop.input, budget);
525
+ if (replayed === undefined)
526
+ return { kind: 'seen' };
527
+ claim.body = replayed;
528
+ claim.steps.push({ kind: 'edit', content: replayed, callId: call.id });
529
+ return claim;
530
+ }
531
+ /**
532
+ * Confirm or withdraw a body this pass holds, from a `read` that observed it.
533
+ *
534
+ * A read changes no file, so on its own it establishes nothing: a partial read
535
+ * shows a window, and a body cannot be recovered from the rendering without
536
+ * stripping the line numbers back off — which would be inventing a file from
537
+ * text that was formatted for a reader, and is exactly what the ledger's
538
+ * contract forbids. What a read CAN do is settle whether a body already
539
+ * reconstructed here is the one the read saw. The rendering is produced
540
+ * forwards, from the body in hand through the read tool's own renderer, and
541
+ * compared whole.
542
+ *
543
+ * Equality is conclusive because of the numbering. Every line of a rendering
544
+ * goes out behind its own `${n}\t`, so one rendering belongs to exactly one
545
+ * body and window; and the partial-view notice carries no such prefix on its
546
+ * lines, so a rendering that covers a whole file cannot be equal to one that
547
+ * was cut short. A read this cannot match leaves the path known and its body
548
+ * withdrawn — the honest answer, and the one the drift check then re-derives
549
+ * from disk.
550
+ */
551
+ function observeRead(history, call, claims, withBody, budget) {
552
+ const read = readWindow(history, call);
553
+ if (!read)
554
+ return;
555
+ const claim = claims.get(read.key);
556
+ if (!claim || claim.kind !== 'content')
557
+ return;
558
+ const shown = history.results.get(call.id)?.content;
559
+ if (visibleCall(history, call.id, 'read') && typeof shown === 'string') {
560
+ // A whole-file rendering is the body plus a number on every line, so one
561
+ // shorter than the body it would render cannot be equal to it. The cheap
562
+ // half of the comparison, taken before anything is measured.
563
+ const units = shown.length < claim.body.length ? undefined : wholeFileRenderUnits(claim.body, read.window);
564
+ // The rendering is content this pass materialises, so it is charged like
565
+ // every other body — and charged for what it will actually come to,
566
+ // worked out from the body and the window while the string does not exist
567
+ // yet. Charging the RECEIPT's length was charging one number and building
568
+ // another: a receipt that had been rewritten short could have the pass
569
+ // build a rendering the ceiling had never been asked about.
570
+ if (units !== undefined && spend(budget, units)) {
571
+ const rendered = renderNumberedRead(claim.body, read.window);
572
+ if (!rendered.partial && rendered.output === shown)
573
+ return;
574
+ }
575
+ }
576
+ claims.set(read.key, { kind: 'seen' });
577
+ // Off the held list as well as out of the claim. A withdrawn path is one
578
+ // this pass is no longer carrying a body for, so counting it against the
579
+ // eviction bound would have a later mutation evict a path that IS still
580
+ // holding one — the pass would then write fewer witnesses than the
581
+ // projection can emit, and the one it dropped was admissible.
582
+ withBody.delete(read.key);
583
+ }
584
+ /**
585
+ * What `read` would return for this whole body, in UTF-16 units, without
586
+ * building it — and `undefined` for a window that would leave a line out.
587
+ *
588
+ * The rendering puts `${n}\t` in front of every line and joins them back with
589
+ * the newlines the body already carries, so its length is the body's, plus one
590
+ * tab per line, plus the digits of the numbers `1..lines`. A windowed read
591
+ * renumbers from its offset and carries the PARTIAL notice, and can never equal
592
+ * a whole body's rendering — so it is refused here rather than built and
593
+ * compared, which is also what makes the figure above exact.
594
+ */
595
+ function wholeFileRenderUnits(body, window) {
596
+ const lines = countLines(body);
597
+ const { start, end } = resolveReadWindow(window, lines);
598
+ if (start !== 0 || end < lines)
599
+ return undefined;
600
+ return body.length + lines + lineNumberUnits(lines);
601
+ }
602
+ /** Lines the way `String.split('\n')` counts them, without building the array. */
603
+ function countLines(body) {
604
+ let lines = 1;
605
+ for (let at = body.indexOf('\n'); at !== -1; at = body.indexOf('\n', at + 1))
606
+ lines++;
607
+ return lines;
608
+ }
609
+ /** Units the line numbers `1..lines` occupy, counted by decade rather than one by one. */
610
+ function lineNumberUnits(lines) {
611
+ let units = 0;
612
+ for (let width = 1, first = 1; first <= lines; width++, first *= 10)
613
+ units += (Math.min(lines, first * 10 - 1) - first + 1) * width;
614
+ return units;
615
+ }
616
+ /**
617
+ * The ledger key a mutation belongs to, whatever the transcript says came back
618
+ * — or `undefined` for one this pass cannot attribute to any file at all.
619
+ *
620
+ * Deliberately the declared path rather than the tool's own schema, and asked
621
+ * before the outcome is. A call that fails validation in some OTHER field, that
622
+ * is too long to read as evidence, that was never answered or that was refused
623
+ * still says which file it was aimed at, and a mutation that can be attributed
624
+ * can be honoured — as a content-changing observation nothing here can replay,
625
+ * which costs the path it names and no other. A key is exactly what withdrawing
626
+ * one path rather than the whole pass requires. `undefined` is kept for the two
627
+ * things that really are unattributable: a call naming no path, and a path this
628
+ * run may not reach, whose key the resolver therefore withheld. Those, and only
629
+ * those, stop the pass — a refused call included, because a refusal this pass
630
+ * cannot place is a refusal it cannot act on either.
631
+ */
632
+ function mutatedKey(history, call, attributions) {
633
+ const path = declaredPath(call, attributions);
634
+ return path === undefined ? undefined : history.keyOf(path);
635
+ }
636
+ /** The path and window one `read` call asked for. */
637
+ function readWindow(history, call) {
638
+ const input = ReadFileTool.inputSchema.safeParse(parseArguments(call));
639
+ if (!input.success)
640
+ return undefined;
641
+ const key = history.keyOf(input.data.path);
642
+ // A read this pass cannot key cannot contradict a claim either: the only
643
+ // claims it holds came from mutations, and a mutation whose path would
644
+ // not resolve stopped the pass before it made one.
645
+ return key === undefined ? undefined : { key, window: input.data };
646
+ }
647
+ //# sourceMappingURL=file-evidence-replay.js.map