@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
@@ -40,11 +40,40 @@ export interface MCPStdioTransportConfig extends MCPTransportConfigBase {
40
40
  cwd?: string
41
41
  }
42
42
 
43
+ /**
44
+ * Anything that answers like `fetch`, restricted to the request shape the
45
+ * MCP HTTP transports actually send: a URL string, an optional
46
+ * method/headers/body, `redirect` (both transports pin this to `'manual'`
47
+ * so a caller cannot silently re-enable auto-following a redirect), and an
48
+ * abort signal.
49
+ *
50
+ * Structurally identical in spirit to `bridge/a2a/client.ts`'s `FetchLike`
51
+ * — the same injectable, socket-free function shape, so a test needs no
52
+ * socket — but the return type stays the real `Response` rather than that
53
+ * bridge's narrower `{ok, status, json(), text()}` duck type: both MCP
54
+ * transports already read `.headers` (content type, session id) and one of
55
+ * them reads `.body` as a stream (the SSE GET), neither of which the A2A
56
+ * bridge's version exposes. Re-declared here, rather than imported from the
57
+ * A2A bridge, so that bridge is not forced to grow fields it does not use.
58
+ */
59
+ export type MCPFetchLike = (
60
+ input: string,
61
+ init?: {
62
+ method?: string
63
+ headers?: Record<string, string>
64
+ body?: string
65
+ redirect?: 'manual' | 'follow' | 'error'
66
+ signal?: AbortSignal
67
+ },
68
+ ) => Promise<Response>
69
+
43
70
  export interface MCPHttpSseTransportConfig extends MCPTransportConfigBase {
44
71
  type: 'http-sse'
45
72
  url: string
46
73
  headers?: Record<string, string>
47
74
  timeoutMs?: number
75
+ /** Injected in place of the ambient global `fetch`. Defaults to it. */
76
+ fetch?: MCPFetchLike
48
77
  }
49
78
 
50
79
  export interface MCPStreamableHttpTransportConfig extends MCPTransportConfigBase {
@@ -52,6 +81,8 @@ export interface MCPStreamableHttpTransportConfig extends MCPTransportConfigBase
52
81
  url: string
53
82
  headers?: Record<string, string>
54
83
  timeoutMs?: number
84
+ /** Injected in place of the ambient global `fetch`. Defaults to it. */
85
+ fetch?: MCPFetchLike
55
86
  }
56
87
 
57
88
  export type MCPTransportUnion =
@@ -65,6 +96,79 @@ export interface MCPJsonRpcError {
65
96
  data?: unknown
66
97
  }
67
98
 
99
+ /**
100
+ * A protocol revision this client can still negotiate DOWN to when a server
101
+ * does not speak the current spec, newest first.
102
+ *
103
+ * Kept as a literal union (rather than just `string`) so a caller pattern
104
+ * matching on `McpEra` gets real exhaustiveness checking; the runtime array
105
+ * of the same values lives in `constants/mcp` and is typed against this.
106
+ */
107
+ export type McpLegacyVersion = '2025-11-25' | '2025-06-18' | '2025-03-26' | '2024-11-05'
108
+
109
+ /**
110
+ * A protocol revision this client speaks WITHOUT the `initialize`
111
+ * handshake.
112
+ *
113
+ * A modern connection is stateless: there is no handshake, no session id,
114
+ * and every request carries its own protocol version, client capabilities
115
+ * and client info in `_meta`. `connect()` probes for one before it offers
116
+ * the legacy handshake.
117
+ */
118
+ export type McpModernVersion = '2026-07-28'
119
+
120
+ /**
121
+ * Which family of the wire protocol a connection resolved to, and which
122
+ * exact revision within it.
123
+ *
124
+ * `kind` alone tells a caller which rules apply — whether `_meta` and the
125
+ * stateless per-request shape are in play, or the `initialize` handshake
126
+ * and (for 2025-06-18 and later) the `MCP-Protocol-Version` header — without
127
+ * re-deriving it from the version string on every read.
128
+ *
129
+ * `MCPClient.connect()` resolves this by probing for a modern peer first
130
+ * and falling back to the legacy `initialize` handshake, so which arm a
131
+ * given connection lands on is the server's answer, not a configuration.
132
+ */
133
+ export type McpEra =
134
+ | { readonly kind: 'modern'; readonly version: McpModernVersion }
135
+ | { readonly kind: 'legacy'; readonly version: McpLegacyVersion }
136
+
137
+ /**
138
+ * What a modern server answers `server/discover` with.
139
+ *
140
+ * The modern era's replacement for the `initialize` result: it names the
141
+ * revisions the server speaks, what it can do, and — under the reserved
142
+ * `_meta` key — who it is. Every field is optional on the wire as far as
143
+ * this client is concerned, because the one thing it MUST be able to do
144
+ * with a malformed answer is decline to treat it as proof of a modern peer.
145
+ */
146
+ export interface MCPDiscoverResult {
147
+ /** Newest first is conventional but not required; this client sorts. */
148
+ supportedVersions?: readonly string[]
149
+ capabilities?: MCPServerCapabilities
150
+ _meta?: Record<string, unknown>
151
+ }
152
+
153
+ /**
154
+ * Where a resolved {@link McpEra} is remembered between connections.
155
+ *
156
+ * The spec's own guidance: a client SHOULD cache the era for the lifetime
157
+ * of the server process (stdio) or the origin (HTTP) and re-probe if the
158
+ * cached assumption later fails. Without it every connection to a legacy
159
+ * server pays a wasted probe round trip.
160
+ *
161
+ * An interface rather than a module-level `Map` because a process-global
162
+ * cache leaks between tests and would make a conformance suite depend on
163
+ * the order its cases happen to run in. `MCPClientConfig.eraCache` injects
164
+ * one; omitting it uses a process-wide default.
165
+ */
166
+ export interface MCPEraCache {
167
+ get(key: string): McpEra | undefined
168
+ set(key: string, era: McpEra): void
169
+ delete(key: string): void
170
+ }
171
+
68
172
  export interface MCPJsonRpcMessage {
69
173
  jsonrpc: '2.0'
70
174
  id?: string | number
@@ -83,12 +187,48 @@ export interface MCPRequestOptions {
83
187
  * waiting, not that an already-started remote side effect was rolled back.
84
188
  */
85
189
  readonly signal?: AbortSignal
190
+ /**
191
+ * Extra headers for this one request, merged over the transport's static
192
+ * config headers (a collision resolves to this value) and under this same
193
+ * call's `bearerToken`, if both are given.
194
+ *
195
+ * The protocol's own headers are the exception: `MCP-Protocol-Version`,
196
+ * `Mcp-Method` and `Mcp-Name` mirror values inside the request this call
197
+ * is sending, and a server rejects a header that disagrees with the body
198
+ * it mirrors. A value given here under one of those names — matched
199
+ * without regard to case — is refused and warn-logged rather than put on
200
+ * the wire.
201
+ *
202
+ * A transport with no header concept (stdio) receives the field and does
203
+ * nothing with it.
204
+ */
205
+ readonly headers?: Readonly<Record<string, string>>
206
+ /**
207
+ * Sent as `Authorization: Bearer <bearerToken>` on this one request.
208
+ *
209
+ * Overrides a configured `Authorization` header — static or supplied via
210
+ * `headers` above — for this request only; it never touches a
211
+ * differently-named header such as a static `X-API-Key`. Omit it and a
212
+ * configured `Authorization` header is left exactly as configured.
213
+ */
214
+ readonly bearerToken?: string
86
215
  }
87
216
 
88
217
  /** Authority for one transport write and any response body it consumes. */
89
218
  export interface MCPTransportSendOptions {
90
219
  /** A pre-aborted signal starts no transport work. */
91
220
  readonly signal?: AbortSignal
221
+ /**
222
+ * Extra headers for this one send.
223
+ *
224
+ * An HTTP-speaking transport merges these over its static config
225
+ * headers; a transport with no header concept (stdio) receives the
226
+ * field and does nothing with it. Introduced so the client — which
227
+ * alone knows the negotiated era — can ask for `MCP-Protocol-Version`
228
+ * on a post-initialize request without the transport having to know
229
+ * what a protocol version is.
230
+ */
231
+ readonly headers?: Readonly<Record<string, string>>
92
232
  }
93
233
 
94
234
  export interface MCPTransport {
@@ -138,10 +278,38 @@ export interface MCPToolDefinition {
138
278
  annotations?: MCPToolAnnotations
139
279
  }
140
280
 
281
+ /**
282
+ * Audience/priority/freshness hints a server may attach to a content block,
283
+ * part of the schema since 2025-06-18. Advisory only: namzu does not act on
284
+ * any of these fields today, but drops none of them either — they survive
285
+ * into `ToolResult.data` for a host that wants to read them.
286
+ *
287
+ * Distinct from {@link MCPToolAnnotations}, which describes a TOOL
288
+ * (read-only, destructive, …); this describes one piece of CONTENT.
289
+ */
290
+ export interface MCPContentAnnotations {
291
+ audience?: Array<'user' | 'assistant'>
292
+ priority?: number
293
+ lastModified?: string
294
+ }
295
+
141
296
  export type MCPContentBlock =
142
297
  | { type: 'text'; text: string }
143
298
  | { type: 'image'; data: string; mimeType: string }
144
- | { type: 'resource'; resource: { uri: string; mimeType?: string; text?: string } }
299
+ | {
300
+ type: 'resource'
301
+ resource: { uri: string; mimeType?: string; text?: string; blob?: string }
302
+ annotations?: MCPContentAnnotations
303
+ }
304
+ /** Since 2025-03-26. Raw audio bytes, base64-encoded like `image`. */
305
+ | { type: 'audio'; data: string; mimeType: string }
306
+ /**
307
+ * Since 2025-06-18. A pointer to a resource the server has NOT embedded
308
+ * inline — unlike `resource`, which always carries `text` or `blob`.
309
+ * Because this block carries no content at all, the adapter names it
310
+ * for the model rather than fabricating text the server never sent.
311
+ */
312
+ | { type: 'resource_link'; uri: string; name: string; description?: string; mimeType?: string }
145
313
 
146
314
  export interface MCPToolResult {
147
315
  content: MCPContentBlock[]
@@ -160,6 +328,23 @@ export interface MCPToolResult {
160
328
  _meta?: Record<string, unknown>
161
329
  }
162
330
 
331
+ /**
332
+ * One thing the client would have to do that it never declared it could —
333
+ * elicit input, sample a message, list roots, or something a later spec
334
+ * revision defines. Only `method` is read by this client; every other field
335
+ * is carried opaquely so a shape it does not understand still names itself.
336
+ *
337
+ * namzu declares `clientCapabilities: {}` in every era, so MRTR rule 7 — a
338
+ * server MUST NOT send an `inputRequests` entry for a capability the client
339
+ * did not declare — means a CONFORMING server never produces one of these.
340
+ * The type exists for the defensive path: a non-conforming server's demand
341
+ * is named and refused rather than silently misread as an ordinary result.
342
+ */
343
+ export interface MCPInputRequest {
344
+ readonly method: string
345
+ readonly [key: string]: unknown
346
+ }
347
+
163
348
  export interface MCPResource {
164
349
  uri: string
165
350
  name: string
@@ -242,6 +427,25 @@ export interface MCPClientConfig {
242
427
  * forever — no error, no failure, just a run that stopped.
243
428
  */
244
429
  requestTimeoutMs?: number
430
+ /**
431
+ * How long `connect()`'s era probe waits for an answer before deciding
432
+ * the peer speaks a legacy revision. Defaults to
433
+ * `DEFAULT_MCP_ERA_PROBE_TIMEOUT_MS`.
434
+ *
435
+ * Never longer than `requestTimeoutMs`: a probe is a request, and a
436
+ * probe that outlived the deadline every other request is held to would
437
+ * be a connect that hangs past its own timeout.
438
+ */
439
+ eraProbeTimeoutMs?: number
440
+ /**
441
+ * Where this client reads and records the resolved era.
442
+ *
443
+ * Defaults to a process-wide cache shared by every `MCPClient`, which is
444
+ * the point — two clients reaching the same origin should not each pay a
445
+ * probe. Inject a fresh one to isolate a test, or a longer-lived one to
446
+ * scope the memory to a host rather than the process.
447
+ */
448
+ eraCache?: MCPEraCache
245
449
  /**
246
450
  * A pre-built logger. Threaded into the transport `MCPClient` constructs
247
451
  * internally (`createTransport`), so a caller that supplies this gets a
@@ -246,7 +246,9 @@ export const RUNTIME_CONTEXT_MESSAGE_KINDS = [
246
246
  'advisory',
247
247
  'answer-review',
248
248
  'auto-continuation',
249
+ 'job-exit',
249
250
  'limit-finalization',
251
+ 'repeat-call',
250
252
  'steering',
251
253
  'step-context',
252
254
  'structured-output',
@@ -123,6 +123,20 @@ export interface Run {
123
123
  */
124
124
  abandonedTaskIds?: readonly string[]
125
125
 
126
+ /**
127
+ * Background jobs the model was waiting on that were still running when
128
+ * this run ended.
129
+ *
130
+ * Only jobs `wait_for_job` named: a dev server nobody awaited is not work
131
+ * this run walked away from, it is work it deliberately left behind. The
132
+ * run holds itself open for these, bounded, and names the ones the bound
133
+ * ran out on — the same honesty `abandonedTaskIds` owes for a delegated
134
+ * worker, and with the same limit: naming a job is not stopping it. A
135
+ * run-owned job is still stopped by the run's own teardown; one bound to
136
+ * the host's session keeps running, which is what it was started for.
137
+ */
138
+ abandonedJobIds?: readonly string[]
139
+
126
140
  parentRunId?: RunId
127
141
 
128
142
  depth?: number
@@ -812,6 +812,62 @@ type CoreRunEvent =
812
812
  /** Approved plan edge carried while the blocking tool is still live. */
813
813
  planId?: string
814
814
  planStepId?: string
815
+ /**
816
+ * How the host that delegated this child wants it GROUPED on screen —
817
+ * a shared label over a set of related delegations, typically one
818
+ * operator-visible piece of work several children are doing together.
819
+ *
820
+ * These fields are display annotations only; they do not create
821
+ * dependencies, barriers, or serial execution. Nothing in the kernel
822
+ * reads them: admission, ordering and concurrency come from the
823
+ * scheduler and from {@link planId}/{@link planStepId}, which is the
824
+ * field pair that DOES carry correlation a host may act on. A reader
825
+ * who infers execution structure from a label here has inferred it
826
+ * from a caption.
827
+ *
828
+ * Absent unless the delegating host supplied them, which is the
829
+ * normal case — a host that groups nothing sends nothing, and a
830
+ * consumer written before these existed reads the same event it
831
+ * always did.
832
+ *
833
+ * They ride this event rather than staying in the delegating
834
+ * process's memory for REACH: a consumer watching from outside
835
+ * that process — another listener, or an SSE client — can rebuild
836
+ * the same picture instead of seeing an undifferentiated list of
837
+ * children.
838
+ *
839
+ * Reach is not durability, and this event buys only the first.
840
+ * Like every delegation lifecycle event, it is handed straight to
841
+ * a host's listener and never enters a run's log — which is what
842
+ * the absent `seq` on this variant says, and what the `seq` doc
843
+ * above spells out. A label here is therefore written nowhere by
844
+ * the kernel and does not survive a restart of the host that chose
845
+ * it; a host wanting the grouping to outlive its process records it
846
+ * from the listener.
847
+ */
848
+ workflow?: string
849
+ /**
850
+ * Display group WITHIN {@link workflow} — a stage of that work, as
851
+ * the delegating host labelled it. Display-only on the same terms as
852
+ * {@link workflow}: it creates no dependencies, barriers or serial
853
+ * execution, and two children naming the same phase are not thereby
854
+ * sequenced or synchronised.
855
+ */
856
+ phase?: string
857
+ /**
858
+ * Longer text explaining {@link phase}, for a surface that has room
859
+ * to show it. Display-only on the same terms as {@link workflow}.
860
+ */
861
+ phaseDetail?: string
862
+ /**
863
+ * Where {@link phase} sits in the host's intended DISPLAY order,
864
+ * zero-based. Display-only on the same terms as {@link workflow}: it
865
+ * orders a list on a screen and orders nothing that runs. Children in
866
+ * one phase are expected to carry the same value; a consumer that
867
+ * sees two disagree should keep the first rather than resequence,
868
+ * because nothing here is authoritative enough to arbitrate.
869
+ */
870
+ phaseOrder?: number
815
871
  }
816
872
  | {
817
873
  type: 'agent_completed'
@@ -28,6 +28,7 @@ import type { RunEvidenceScope, RunTextEvidenceSource } from '../../store/eviden
28
28
  * re-keyed per call, it happens once, deliberately, as its own change.
29
29
  */
30
30
 
31
+ import type { RunExecutionStatus } from '../common/index.js'
31
32
  import type { Message } from '../message/index.js'
32
33
  import type { AuditEvent } from './audit.js'
33
34
  import type { Run } from './entity.js'
@@ -103,6 +104,47 @@ export type ToolExecutionRecord =
103
104
  | (CompletedToolRecord & { readonly status: 'completed' })
104
105
  | { readonly toolUseId: string; readonly toolName: string; readonly status: 'started' }
105
106
 
107
+ /**
108
+ * One delegated child run found on disk under its parent's `children/`
109
+ * directory, as {@link import('../../store/run/disk.js').RunDiskStore.listChildren}
110
+ * reports it.
111
+ *
112
+ * A DISCOVERY record, not the child's evidence: every field here comes from
113
+ * the child's `run.json`, and the transcript, message snapshot and report
114
+ * beside it stay on disk until something asks for them. {@link dir} is what
115
+ * that something reads from.
116
+ *
117
+ * Everything the file supplies is optional, because a `run.json` is written
118
+ * by the child's own terminal path and a process killed before it got there
119
+ * leaves a directory whose other evidence is still worth opening. An absent
120
+ * field is "this file did not say", never a zero or an empty string.
121
+ */
122
+ export interface DelegatedChildRun {
123
+ /**
124
+ * The child's run id, taken from the directory name.
125
+ *
126
+ * The location is the fact: `initRun` names the directory after the run
127
+ * it binds, so a `run.json` whose `id` disagrees with its own directory
128
+ * was moved or hand-edited, and the directory is the half that decides
129
+ * where the evidence actually is.
130
+ */
131
+ readonly id: string
132
+ /** The parent run whose `children/` directory holds this one. */
133
+ readonly parentRunId: string
134
+ /** Absolute path to the child's evidence directory. */
135
+ readonly dir: string
136
+ readonly agentId?: string
137
+ readonly agentName?: string
138
+ /** `metadata.config.model` — the model the child was configured with. */
139
+ readonly model?: string
140
+ readonly status?: RunExecutionStatus
141
+ readonly startedAt?: number
142
+ readonly endedAt?: number
143
+ /** `tokenUsage.totalTokens` — this child's own cumulative spend. */
144
+ readonly totalTokens?: number
145
+ readonly depth?: number
146
+ }
147
+
106
148
  /** Absence proves no recorded start only when the whole selected log is complete. */
107
149
  export interface ToolExecutionSnapshot {
108
150
  readonly complete: boolean
@@ -261,6 +261,35 @@ export interface SandboxTcpConnectOptions {
261
261
  readonly host?: '127.0.0.1' | '::1'
262
262
  }
263
263
 
264
+ /**
265
+ * What to read, and how to stop reading it. Every field is optional, so
266
+ * `readFile(path)` and `readFile(path, {})` mean the same thing: the whole
267
+ * file.
268
+ */
269
+ export interface SandboxReadFileOptions {
270
+ /** First byte to read. Defaults to 0. */
271
+ readonly offset?: number
272
+ /**
273
+ * How many bytes to read. Defaults to the rest of the file. A range
274
+ * that runs past the end returns the bytes that exist, not an error —
275
+ * a caller resuming from a remembered offset must be able to ask
276
+ * without knowing the answer first.
277
+ *
278
+ * **A backend may cap how large a single range it will serve**, and
279
+ * one past that cap is REFUSED rather than shortened — a caller that
280
+ * asked for 4 MiB, got 1 MiB and was told nothing would read the short
281
+ * answer as the end of its range. The agent-backed backends cap it at
282
+ * `NAMZU_AGENT_READ_FILE_RANGE_BYTES` (1 MiB by default), because one
283
+ * range is one wire frame there; a provider reading from local disk has
284
+ * no such ceiling. A caller that wants more than a frame's worth in one
285
+ * call iterates {@link Sandbox.readFileStream} instead, which is not
286
+ * capped.
287
+ */
288
+ readonly length?: number
289
+ /** Aborts the read. */
290
+ readonly signal?: AbortSignal
291
+ }
292
+
264
293
  export interface Sandbox {
265
294
  readonly id: SandboxId
266
295
  readonly status: SandboxStatus
@@ -296,22 +325,23 @@ export interface Sandbox {
296
325
  * Open a real pseudo-terminal whose complete process tree is confined to
297
326
  * and owned by this sandbox.
298
327
  *
299
- * Optional, and a backend that cannot provide one must **throw** rather
300
- * than hand back a pipe — the same rule {@link Sandbox.setNetworkPolicy}
301
- * states one line up, and for a sharper reason. A pipe would appear to
302
- * work: bytes would flow, and every program that calls `isatty` would
303
- * take its non-interactive branch. The prompt never appears, the REPL
304
- * exits immediately, the progress bar prints ten thousand lines, and
305
- * nothing says why.
328
+ * Optional, and a backend that cannot provide one must **omit this
329
+ * method** rather than hand back a pipe — the same skip-if-unavailable
330
+ * rule {@link Sandbox.openTcpConnection} states below, and for a sharper
331
+ * reason. A pipe would appear to work: bytes would flow, and every
332
+ * program that calls `isatty` would take its non-interactive branch. The
333
+ * prompt never appears, the REPL exits immediately, the progress bar
334
+ * prints ten thousand lines, and nothing says why.
306
335
  *
307
- * A backend that implements this method MUST make {@link destroy} kill and
308
- * await every terminal it returned. Merely starting a host pseudo-terminal
309
- * with `rootDir` as its working directory does not satisfy either the
310
- * confinement or the ownership contract.
336
+ * A backend that DOES implement this method MUST make {@link destroy}
337
+ * kill and await every terminal it returned. Merely starting a host
338
+ * pseudo-terminal with `rootDir` as its working directory does not
339
+ * satisfy either the confinement or the ownership contract.
311
340
  *
312
- * The Firecracker backend satisfies both guarantees by owning the PTY in the
313
- * guest and awaiting its exit before the microVM is released. Backends that
314
- * cannot provide that boundary omit the capability.
341
+ * The Firecracker backend satisfies both guarantees by owning the PTY in
342
+ * the guest and awaiting its exit before the microVM is released.
343
+ * Backends that cannot provide that boundary omit the capability, as
344
+ * stated above.
315
345
  */
316
346
  openTerminal?(options: OpenTerminalOptions): Promise<TerminalSession>
317
347
  /**
@@ -322,7 +352,46 @@ export interface Sandbox {
322
352
  */
323
353
  openTcpConnection?(options: SandboxTcpConnectOptions): Promise<SandboxTcpConnection>
324
354
  writeFile(path: string, content: string | Buffer): Promise<void>
325
- readFile(path: string): Promise<Buffer>
355
+ /**
356
+ * Read a file out of the sandbox.
357
+ *
358
+ * `options` is optional in both directions, which is what keeps this
359
+ * source-compatible: a caller may go on writing `readFile(path)`, and a
360
+ * backend may go on declaring the one-parameter form and still satisfy
361
+ * this signature. A backend that accepts the parameter and IGNORES
362
+ * `offset`/`length` does not — returning the whole file where a slice
363
+ * was asked for is a wrong answer, not a degraded one, so such a
364
+ * backend must reject instead.
365
+ *
366
+ * @param options.offset First byte to read. Defaults to 0.
367
+ * @param options.length How many bytes to read. Defaults to the rest of
368
+ * the file. A range that runs past the end returns the bytes that
369
+ * exist rather than failing.
370
+ * @param options.signal Aborts the read.
371
+ */
372
+ readFile(path: string, options?: SandboxReadFileOptions): Promise<Buffer>
373
+ /**
374
+ * Read a file as a stream of chunks, so neither the sandbox nor this
375
+ * process ever holds the whole of it.
376
+ *
377
+ * Optional, in the same way {@link Sandbox.openTerminal} is: a backend
378
+ * that cannot read a file incrementally must OMIT this rather than
379
+ * implement it by reading the file whole and chopping the result up,
380
+ * which would give a caller the bounded-memory behaviour it asked for in
381
+ * name only. A caller that needs the bound therefore refuses an absent
382
+ * method rather than falling back to {@link Sandbox.readFile}.
383
+ *
384
+ * Chunk boundaries are not part of the contract — only the order and
385
+ * the concatenation are. Aborting `options.signal`, or leaving the loop
386
+ * early, must stop the transfer and release whatever the sandbox opened
387
+ * for it.
388
+ *
389
+ * Hosts that drain agent-produced output files before {@link destroy}
390
+ * (see {@link listFiles}) are the reason this exists: those files are
391
+ * routinely tens to hundreds of megabytes, and a whole-file read of one
392
+ * of them costs several times its size in the sandbox.
393
+ */
394
+ readFileStream?(path: string, options?: SandboxReadFileOptions): AsyncIterable<Buffer>
326
395
  /**
327
396
  * Recursively enumerate regular files under `rootPath`. Directories,
328
397
  * symlinks, sockets, and other non-regular entries are skipped.
@@ -113,6 +113,33 @@ export interface BackgroundJobRegistryRef {
113
113
  }
114
114
  kill(id: string): Promise<{ id: string; status: string }>
115
115
  list(): readonly { id: string; command: string; status: string }[]
116
+ /**
117
+ * Await this job's exit instead of reading it in a loop; resolves at
118
+ * once for a job that has already stopped. Optional, and added after
119
+ * the rest of this surface: a host implementing this interface directly
120
+ * rather than through `bindOwner` may not have it yet, so `wait_for_job`
121
+ * checks for it and says so rather than assuming every host can wait.
122
+ */
123
+ waitForExit?(
124
+ id: string,
125
+ opts?: { signal?: AbortSignal },
126
+ ): Promise<{
127
+ id: string
128
+ status: string
129
+ exitCode?: number
130
+ }>
131
+ /**
132
+ * Say that the model is waiting on this job, so the run stays open for it
133
+ * when the model stops calling tools.
134
+ *
135
+ * Called by `wait_for_job` and nothing else. The kernel holds a finishing
136
+ * run open — bounded, and for no model tokens — only for a job marked
137
+ * here; a job nobody marked never delays a run, which is what a dev server
138
+ * or a watcher needs. Optional for the reason `waitForExit` is: a host
139
+ * implementing this interface directly may have nowhere to record the
140
+ * intent, and then there is simply no hold.
141
+ */
142
+ markAwaited?(id: string): void
116
143
  }
117
144
 
118
145
  /**
@@ -138,6 +165,83 @@ export interface FileReadTracker {
138
165
  * preserves it; different or unknown content clears it. Never infer from a name.
139
166
  */
140
167
  writeCallId?(key: string): string | undefined
168
+ /**
169
+ * Optional chain of calls whose bodies compose the current content: the
170
+ * full-body write that started it, then the successful edits applied on
171
+ * top of it, in order.
172
+ *
173
+ * Defined only while at least one edit sits on a witnessed write — exactly
174
+ * when `writeCallId` is not. An edited file's body is no longer the write
175
+ * call's body, and a consumer that knows only `writeCallId` has to keep
176
+ * seeing nothing there rather than a claim that has quietly stopped being
177
+ * true. The chain names INPUTS: reconstructing the body means replaying
178
+ * those calls' visible arguments and checking the result against
179
+ * `fingerprint(key)`, never asserting it.
180
+ */
181
+ editChain?(key: string): { rootWriteCallId: string; editCallIds: readonly string[] } | undefined
182
+ /**
183
+ * Optional record of a successful edit's resulting content, with the call
184
+ * that produced it.
185
+ *
186
+ * Does everything `recordRead(key, content)` does, and additionally extends
187
+ * the chain when this edit ran against content the ledger already had a
188
+ * fingerprint for and a witnessed write underneath it — the two facts that
189
+ * make the replay reproducible. Missing either, it is an ordinary
190
+ * observation of a body nobody can replay: the fingerprint advances and the
191
+ * chain is cleared. Pass ToolContext.toolUseId only after the edit succeeded.
192
+ */
193
+ recordEdit?(key: string, content: string, callId: string): void
194
+ /**
195
+ * Optional witness of a read that returned the file WHOLE, with the
196
+ * fingerprint of the rendering it returned.
197
+ *
198
+ * Does everything `recordRead(key, content)` does, and additionally records
199
+ * that the body is visible in `callId`'s receipt — not as text this ledger
200
+ * holds, but as the exact rendering `callId` emitted, so a consumer can
201
+ * check the receipt it can see against `renderedFingerprint` before
202
+ * referencing it. A partial read never calls this: a window proves nothing
203
+ * about the rest of the file. Pass `ToolContext.toolUseId` and the
204
+ * fingerprint of the tool's own output string.
205
+ *
206
+ * The witness is recorded only where no write witness or chain survives the
207
+ * observation, because a write-rooted body is one the model composed and
208
+ * can be replayed through. It is cleared by exactly what clears a write
209
+ * witness — different or unknown content — and additionally by any
210
+ * `recordEdit`, because a read roots no chain: the body it witnesses exists
211
+ * only as a rendering, and nothing may be replayed on top of it.
212
+ */
213
+ recordFullRead?(key: string, content: string, callId: string, renderedFingerprint: string): void
214
+ /**
215
+ * Optional witness of the full read above: the call whose receipt holds the
216
+ * body, and the fingerprint of what that call emitted.
217
+ *
218
+ * `renderedFingerprint` is deliberately not the body's fingerprint —
219
+ * `fingerprint(key)` is that. It describes the RECEIPT, so a consumer
220
+ * admits the reference only while the receipt it can see is byte-for-byte
221
+ * what the tool returned; an elided, spilled or cleared one is not.
222
+ *
223
+ * The derived work context asks a tracker for `writeCallId` and
224
+ * `fingerprint` before it reads any witness at all — a name alone does not
225
+ * say the ledger is the one execution wrote — so a tracker implementing
226
+ * this pair and neither of those still establishes nothing.
227
+ */
228
+ readWitness?(key: string): { callId: string; renderedFingerprint: string } | undefined
229
+ /**
230
+ * Optional note that a built-in mutation has just refused this path because
231
+ * the body on disk differs from the fingerprint above.
232
+ *
233
+ * Not an observation. The refusing tool read the disk, but all it reports
234
+ * here is the disagreement — recording the body it found would re-baseline
235
+ * the drift check and admit the very mutation that was refused. An
236
+ * implementation must therefore leave `fingerprint`, `hasRead`,
237
+ * `writeCallId`, `editChain` and `readWitness` exactly as they were, and any
238
+ * later content observation clears the flag. It exists so a consumer that
239
+ * cannot read the filesystem — the derived work context — can stop
240
+ * referencing a body it has been told is stale, without a check of its own.
241
+ */
242
+ recordDriftObserved?(key: string): void
243
+ /** Whether a refused mutation has reported this path stale since the last observation. */
244
+ driftObserved?(key: string): boolean
141
245
  /**
142
246
  * Fingerprint of the body captured at the last read, when one was.
143
247
  *
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A positive whole number of milliseconds from the environment, or the
3
+ * caller's own default.
4
+ *
5
+ * Every bound that can be tuned wants the same three answers, and the third
6
+ * is the one worth sharing: a variable set to `soon`, to `-1` or to nothing
7
+ * at all falls back rather than parsing, because a run must not wait, hold or
8
+ * time out for `NaN`.
9
+ *
10
+ * `process.env` is read on every call, so where it is called decides when the
11
+ * value is sampled — `wait_for_job` fixes its bounds at module load and names
12
+ * them in a tool schema, and the kernel's hold ceiling asks per hold. That
13
+ * choice is why this lives here rather than being imported from the tool: the
14
+ * kernel reaching into `wait_for_job` for the parse would pull the tool's
15
+ * module-load sampling into the runtime's own graph, and move when the
16
+ * `NAMZU_JOB_WAIT_*` bounds are read.
17
+ */
18
+ export function readPositiveIntEnv(key: string, fallback: number): number {
19
+ const value = process.env[key]?.trim()
20
+ if (!value) return fallback
21
+ const parsed = Number(value)
22
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback
23
+ }