@namzu/sdk 58.1.1 → 60.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 (312) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/dist/account/binding.d.ts +20 -0
  3. package/dist/account/binding.d.ts.map +1 -0
  4. package/dist/account/binding.js +27 -0
  5. package/dist/account/binding.js.map +1 -0
  6. package/dist/account/index.d.ts +9 -0
  7. package/dist/account/index.d.ts.map +1 -0
  8. package/dist/account/index.js +5 -0
  9. package/dist/account/index.js.map +1 -0
  10. package/dist/account/lock.d.ts +33 -0
  11. package/dist/account/lock.d.ts.map +1 -0
  12. package/dist/account/lock.js +170 -0
  13. package/dist/account/lock.js.map +1 -0
  14. package/dist/account/registry.d.ts +21 -0
  15. package/dist/account/registry.d.ts.map +1 -0
  16. package/dist/account/registry.js +340 -0
  17. package/dist/account/registry.js.map +1 -0
  18. package/dist/account/types.d.ts +124 -0
  19. package/dist/account/types.d.ts.map +1 -0
  20. package/dist/account/types.js +35 -0
  21. package/dist/account/types.js.map +1 -0
  22. package/dist/compaction/context-window.d.ts +53 -0
  23. package/dist/compaction/context-window.d.ts.map +1 -1
  24. package/dist/compaction/context-window.js +141 -2
  25. package/dist/compaction/context-window.js.map +1 -1
  26. package/dist/execution/workflow/index.d.ts +3 -1
  27. package/dist/execution/workflow/index.d.ts.map +1 -1
  28. package/dist/execution/workflow/index.js +2 -1
  29. package/dist/execution/workflow/index.js.map +1 -1
  30. package/dist/execution/workflow/journal.d.ts +24 -4
  31. package/dist/execution/workflow/journal.d.ts.map +1 -1
  32. package/dist/execution/workflow/journal.js +131 -9
  33. package/dist/execution/workflow/journal.js.map +1 -1
  34. package/dist/execution/workflow/prelude.d.ts +2 -0
  35. package/dist/execution/workflow/prelude.d.ts.map +1 -1
  36. package/dist/execution/workflow/prelude.js +49 -8
  37. package/dist/execution/workflow/prelude.js.map +1 -1
  38. package/dist/execution/workflow/resume.d.ts +59 -0
  39. package/dist/execution/workflow/resume.d.ts.map +1 -0
  40. package/dist/execution/workflow/resume.js +273 -0
  41. package/dist/execution/workflow/resume.js.map +1 -0
  42. package/dist/execution/workflow/runtime.d.ts.map +1 -1
  43. package/dist/execution/workflow/runtime.js +112 -8
  44. package/dist/execution/workflow/runtime.js.map +1 -1
  45. package/dist/execution/workflow/types.d.ts +105 -1
  46. package/dist/execution/workflow/types.d.ts.map +1 -1
  47. package/dist/index.d.ts +3 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +4 -0
  50. package/dist/index.js.map +1 -1
  51. package/dist/pals/communication/ingress-schema.d.ts +38 -38
  52. package/dist/peers/record.d.ts +98 -0
  53. package/dist/peers/record.d.ts.map +1 -1
  54. package/dist/peers/record.js +17 -0
  55. package/dist/peers/record.js.map +1 -1
  56. package/dist/peers/registry.d.ts.map +1 -1
  57. package/dist/peers/registry.js +2 -2
  58. package/dist/peers/registry.js.map +1 -1
  59. package/dist/plugin/compat.d.ts +24 -0
  60. package/dist/plugin/compat.d.ts.map +1 -0
  61. package/dist/plugin/compat.js +149 -0
  62. package/dist/plugin/compat.js.map +1 -0
  63. package/dist/plugin/loader.d.ts +2 -1
  64. package/dist/plugin/loader.d.ts.map +1 -1
  65. package/dist/plugin/loader.js +6 -0
  66. package/dist/plugin/loader.js.map +1 -1
  67. package/dist/pricing/index.d.ts.map +1 -1
  68. package/dist/pricing/index.js +2 -1
  69. package/dist/pricing/index.js.map +1 -1
  70. package/dist/process/kill-tree.d.ts +44 -0
  71. package/dist/process/kill-tree.d.ts.map +1 -1
  72. package/dist/process/kill-tree.js +102 -4
  73. package/dist/process/kill-tree.js.map +1 -1
  74. package/dist/prompt/coding-agent-doctrine.d.ts +1 -1
  75. package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
  76. package/dist/prompt/coding-agent-doctrine.js +9 -1
  77. package/dist/prompt/coding-agent-doctrine.js.map +1 -1
  78. package/dist/provider/errors.d.ts.map +1 -1
  79. package/dist/provider/errors.js +25 -2
  80. package/dist/provider/errors.js.map +1 -1
  81. package/dist/provider/model-window-suffix.d.ts +18 -0
  82. package/dist/provider/model-window-suffix.d.ts.map +1 -0
  83. package/dist/provider/model-window-suffix.js +77 -0
  84. package/dist/provider/model-window-suffix.js.map +1 -0
  85. package/dist/provider/token-budget.d.ts.map +1 -1
  86. package/dist/provider/token-budget.js +9 -1
  87. package/dist/provider/token-budget.js.map +1 -1
  88. package/dist/public-runtime.d.ts +6 -3
  89. package/dist/public-runtime.d.ts.map +1 -1
  90. package/dist/public-runtime.js +6 -4
  91. package/dist/public-runtime.js.map +1 -1
  92. package/dist/public-types.d.ts +3 -0
  93. package/dist/public-types.d.ts.map +1 -1
  94. package/dist/registry/command/kernel-commands.d.ts +1 -4
  95. package/dist/registry/command/kernel-commands.d.ts.map +1 -1
  96. package/dist/registry/command/kernel-commands.js +0 -28
  97. package/dist/registry/command/kernel-commands.js.map +1 -1
  98. package/dist/runtime/jobs/awaited-jobs.d.ts +6 -0
  99. package/dist/runtime/jobs/awaited-jobs.d.ts.map +1 -1
  100. package/dist/runtime/jobs/awaited-jobs.js +11 -0
  101. package/dist/runtime/jobs/awaited-jobs.js.map +1 -1
  102. package/dist/runtime/jobs/host-exit-finalizer.d.ts +20 -0
  103. package/dist/runtime/jobs/host-exit-finalizer.d.ts.map +1 -0
  104. package/dist/runtime/jobs/host-exit-finalizer.js +57 -0
  105. package/dist/runtime/jobs/host-exit-finalizer.js.map +1 -0
  106. package/dist/runtime/jobs/liveness.d.ts +67 -0
  107. package/dist/runtime/jobs/liveness.d.ts.map +1 -0
  108. package/dist/runtime/jobs/liveness.js +94 -0
  109. package/dist/runtime/jobs/liveness.js.map +1 -0
  110. package/dist/runtime/jobs/registry.d.ts +21 -0
  111. package/dist/runtime/jobs/registry.d.ts.map +1 -1
  112. package/dist/runtime/jobs/registry.js +104 -2
  113. package/dist/runtime/jobs/registry.js.map +1 -1
  114. package/dist/runtime/jobs/wake-budget.d.ts +35 -0
  115. package/dist/runtime/jobs/wake-budget.d.ts.map +1 -0
  116. package/dist/runtime/jobs/wake-budget.js +48 -0
  117. package/dist/runtime/jobs/wake-budget.js.map +1 -0
  118. package/dist/runtime/query/detached-calls.d.ts +65 -0
  119. package/dist/runtime/query/detached-calls.d.ts.map +1 -0
  120. package/dist/runtime/query/detached-calls.js +116 -0
  121. package/dist/runtime/query/detached-calls.js.map +1 -0
  122. package/dist/runtime/query/executor.d.ts +11 -3
  123. package/dist/runtime/query/executor.d.ts.map +1 -1
  124. package/dist/runtime/query/executor.js +100 -8
  125. package/dist/runtime/query/executor.js.map +1 -1
  126. package/dist/runtime/query/index.d.ts +20 -0
  127. package/dist/runtime/query/index.d.ts.map +1 -1
  128. package/dist/runtime/query/index.js +31 -5
  129. package/dist/runtime/query/index.js.map +1 -1
  130. package/dist/runtime/query/iteration/outstanding-work.d.ts.map +1 -1
  131. package/dist/runtime/query/iteration/outstanding-work.js +11 -3
  132. package/dist/runtime/query/iteration/outstanding-work.js.map +1 -1
  133. package/dist/runtime/query/iteration/phases/context.d.ts +7 -0
  134. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  135. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  136. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  137. package/dist/runtime/query/iteration/phases/tool-review.js +63 -1
  138. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  139. package/dist/runtime/query/session-storage.d.ts.map +1 -1
  140. package/dist/runtime/query/session-storage.js +8 -3
  141. package/dist/runtime/query/session-storage.js.map +1 -1
  142. package/dist/runtime/query/steering.d.ts +29 -3
  143. package/dist/runtime/query/steering.d.ts.map +1 -1
  144. package/dist/runtime/query/steering.js +58 -3
  145. package/dist/runtime/query/steering.js.map +1 -1
  146. package/dist/runtime/query/tooling.d.ts +1 -0
  147. package/dist/runtime/query/tooling.d.ts.map +1 -1
  148. package/dist/runtime/query/tooling.js +1 -0
  149. package/dist/runtime/query/tooling.js.map +1 -1
  150. package/dist/runtime/system-events.d.ts +5 -0
  151. package/dist/runtime/system-events.d.ts.map +1 -1
  152. package/dist/runtime/system-events.js +7 -1
  153. package/dist/runtime/system-events.js.map +1 -1
  154. package/dist/sandbox/denial.d.ts +11 -0
  155. package/dist/sandbox/denial.d.ts.map +1 -0
  156. package/dist/sandbox/denial.js +61 -0
  157. package/dist/sandbox/denial.js.map +1 -0
  158. package/dist/sandbox/file-walk-program.d.ts.map +1 -1
  159. package/dist/sandbox/file-walk-program.js +31 -13
  160. package/dist/sandbox/file-walk-program.js.map +1 -1
  161. package/dist/sandbox/file-walk.d.ts +2 -0
  162. package/dist/sandbox/file-walk.d.ts.map +1 -1
  163. package/dist/sandbox/file-walk.js +64 -13
  164. package/dist/sandbox/file-walk.js.map +1 -1
  165. package/dist/sandbox/ignore-matcher.d.ts +30 -0
  166. package/dist/sandbox/ignore-matcher.d.ts.map +1 -0
  167. package/dist/sandbox/ignore-matcher.js +11 -0
  168. package/dist/sandbox/ignore-matcher.js.map +1 -0
  169. package/dist/sandbox/ignore-source.d.ts +26 -0
  170. package/dist/sandbox/ignore-source.d.ts.map +1 -0
  171. package/dist/sandbox/ignore-source.js +221 -0
  172. package/dist/sandbox/ignore-source.js.map +1 -0
  173. package/dist/sandbox/provider/local.d.ts.map +1 -1
  174. package/dist/sandbox/provider/local.js +11 -1
  175. package/dist/sandbox/provider/local.js.map +1 -1
  176. package/dist/scheduler/completion-inbox.d.ts.map +1 -1
  177. package/dist/scheduler/completion-inbox.js +1 -0
  178. package/dist/scheduler/completion-inbox.js.map +1 -1
  179. package/dist/session/log-hash.d.ts.map +1 -1
  180. package/dist/session/log-hash.js +18 -1
  181. package/dist/session/log-hash.js.map +1 -1
  182. package/dist/store/session-index/sqlite.d.ts.map +1 -1
  183. package/dist/store/session-index/sqlite.js +169 -16
  184. package/dist/store/session-index/sqlite.js.map +1 -1
  185. package/dist/tools/builtins/bash.d.ts.map +1 -1
  186. package/dist/tools/builtins/bash.js +10 -4
  187. package/dist/tools/builtins/bash.js.map +1 -1
  188. package/dist/tools/builtins/computer-use.d.ts +35 -0
  189. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  190. package/dist/tools/builtins/computer-use.js +512 -67
  191. package/dist/tools/builtins/computer-use.js.map +1 -1
  192. package/dist/tools/builtins/glob.d.ts +1 -0
  193. package/dist/tools/builtins/glob.d.ts.map +1 -1
  194. package/dist/tools/builtins/glob.js +65 -28
  195. package/dist/tools/builtins/glob.js.map +1 -1
  196. package/dist/tools/builtins/grep.d.ts +2 -0
  197. package/dist/tools/builtins/grep.d.ts.map +1 -1
  198. package/dist/tools/builtins/grep.js +94 -40
  199. package/dist/tools/builtins/grep.js.map +1 -1
  200. package/dist/tools/builtins/job.d.ts +1 -1
  201. package/dist/tools/builtins/job.d.ts.map +1 -1
  202. package/dist/tools/builtins/job.js +34 -7
  203. package/dist/tools/builtins/job.js.map +1 -1
  204. package/dist/tools/builtins/search-support.d.ts +44 -0
  205. package/dist/tools/builtins/search-support.d.ts.map +1 -0
  206. package/dist/tools/builtins/search-support.js +89 -0
  207. package/dist/tools/builtins/search-support.js.map +1 -0
  208. package/dist/tools/builtins/wait-for-job-bounds.d.ts +2 -0
  209. package/dist/tools/builtins/wait-for-job-bounds.d.ts.map +1 -1
  210. package/dist/tools/builtins/wait-for-job-bounds.js +3 -1
  211. package/dist/tools/builtins/wait-for-job-bounds.js.map +1 -1
  212. package/dist/tools/builtins/wait-for-job.d.ts +1 -1
  213. package/dist/tools/builtins/wait-for-job.d.ts.map +1 -1
  214. package/dist/tools/builtins/wait-for-job.js +13 -2
  215. package/dist/tools/builtins/wait-for-job.js.map +1 -1
  216. package/dist/tools/command-shell.d.ts.map +1 -1
  217. package/dist/tools/command-shell.js +14 -5
  218. package/dist/tools/command-shell.js.map +1 -1
  219. package/dist/types/computer-use/index.d.ts +129 -13
  220. package/dist/types/computer-use/index.d.ts.map +1 -1
  221. package/dist/types/computer-use/index.js.map +1 -1
  222. package/dist/types/errors/catalog.d.ts.map +1 -1
  223. package/dist/types/errors/catalog.js +6 -0
  224. package/dist/types/errors/catalog.js.map +1 -1
  225. package/dist/types/plugin/index.d.ts +24 -0
  226. package/dist/types/plugin/index.d.ts.map +1 -1
  227. package/dist/types/plugin/index.js +4 -0
  228. package/dist/types/plugin/index.js.map +1 -1
  229. package/dist/types/provider/errors.d.ts +7 -0
  230. package/dist/types/provider/errors.d.ts.map +1 -1
  231. package/dist/types/provider/errors.js +27 -3
  232. package/dist/types/provider/errors.js.map +1 -1
  233. package/dist/types/sandbox/index.d.ts +38 -1
  234. package/dist/types/sandbox/index.d.ts.map +1 -1
  235. package/dist/types/sandbox/index.js.map +1 -1
  236. package/dist/types/session/records.d.ts +479 -1
  237. package/dist/types/session/records.d.ts.map +1 -1
  238. package/dist/types/session/records.js +45 -1
  239. package/dist/types/session/records.js.map +1 -1
  240. package/dist/types/tool/index.d.ts +30 -0
  241. package/dist/types/tool/index.d.ts.map +1 -1
  242. package/dist/types/tool/index.js.map +1 -1
  243. package/dist/types/tool/presentation.d.ts +8 -0
  244. package/dist/types/tool/presentation.d.ts.map +1 -1
  245. package/package.json +1 -1
  246. package/src/account/binding.ts +43 -0
  247. package/src/account/index.ts +32 -0
  248. package/src/account/lock.ts +184 -0
  249. package/src/account/registry.ts +413 -0
  250. package/src/account/types.ts +137 -0
  251. package/src/compaction/context-window.ts +163 -2
  252. package/src/execution/workflow/index.ts +14 -1
  253. package/src/execution/workflow/journal.ts +161 -11
  254. package/src/execution/workflow/prelude.ts +51 -8
  255. package/src/execution/workflow/resume.ts +313 -0
  256. package/src/execution/workflow/runtime.ts +127 -9
  257. package/src/execution/workflow/types.ts +109 -1
  258. package/src/index.ts +17 -0
  259. package/src/peers/record.ts +18 -0
  260. package/src/peers/registry.ts +2 -2
  261. package/src/plugin/compat.ts +173 -0
  262. package/src/plugin/loader.ts +7 -1
  263. package/src/pricing/index.ts +2 -1
  264. package/src/process/kill-tree.ts +106 -4
  265. package/src/prompt/coding-agent-doctrine.ts +9 -1
  266. package/src/provider/errors.ts +27 -1
  267. package/src/provider/model-window-suffix.ts +84 -0
  268. package/src/provider/token-budget.ts +9 -3
  269. package/src/public-runtime.ts +38 -2
  270. package/src/public-types.ts +16 -0
  271. package/src/registry/command/kernel-commands.ts +1 -32
  272. package/src/runtime/jobs/awaited-jobs.ts +11 -0
  273. package/src/runtime/jobs/host-exit-finalizer.ts +67 -0
  274. package/src/runtime/jobs/liveness.ts +144 -0
  275. package/src/runtime/jobs/registry.ts +114 -2
  276. package/src/runtime/jobs/wake-budget.ts +55 -0
  277. package/src/runtime/query/detached-calls.ts +135 -0
  278. package/src/runtime/query/executor.ts +102 -12
  279. package/src/runtime/query/index.ts +50 -7
  280. package/src/runtime/query/iteration/outstanding-work.ts +12 -3
  281. package/src/runtime/query/iteration/phases/context.ts +7 -0
  282. package/src/runtime/query/iteration/phases/tool-review.ts +73 -8
  283. package/src/runtime/query/session-storage.ts +8 -3
  284. package/src/runtime/query/steering.ts +77 -6
  285. package/src/runtime/query/tooling.ts +2 -0
  286. package/src/runtime/system-events.ts +12 -1
  287. package/src/sandbox/denial.ts +79 -0
  288. package/src/sandbox/file-walk-program.ts +32 -13
  289. package/src/sandbox/file-walk.ts +75 -12
  290. package/src/sandbox/ignore-matcher.ts +46 -0
  291. package/src/sandbox/ignore-source.ts +220 -0
  292. package/src/sandbox/provider/local.ts +11 -1
  293. package/src/scheduler/completion-inbox.ts +1 -0
  294. package/src/session/log-hash.ts +21 -0
  295. package/src/store/session-index/sqlite.ts +171 -16
  296. package/src/tools/builtins/bash.ts +11 -4
  297. package/src/tools/builtins/computer-use.ts +593 -68
  298. package/src/tools/builtins/glob.ts +79 -32
  299. package/src/tools/builtins/grep.ts +107 -43
  300. package/src/tools/builtins/job.ts +42 -12
  301. package/src/tools/builtins/search-support.ts +107 -0
  302. package/src/tools/builtins/wait-for-job-bounds.ts +5 -1
  303. package/src/tools/builtins/wait-for-job.ts +14 -2
  304. package/src/tools/command-shell.ts +13 -5
  305. package/src/types/computer-use/index.ts +157 -15
  306. package/src/types/errors/catalog.ts +6 -0
  307. package/src/types/plugin/index.ts +8 -0
  308. package/src/types/provider/errors.ts +40 -3
  309. package/src/types/sandbox/index.ts +40 -1
  310. package/src/types/session/records.ts +52 -2
  311. package/src/types/tool/index.ts +30 -0
  312. package/src/types/tool/presentation.ts +8 -0
@@ -30,6 +30,111 @@
30
30
  * only ever as fresh as the day someone checked it.
31
31
  */
32
32
 
33
+ /** The window an id declares with the `[1m]` suffix, and the ceiling `NAMZU_AUTO_COMPACT_WINDOW` accepts. */
34
+ export const ONE_MILLION_WINDOW_TOKENS = 1_000_000
35
+ /** The window a model is held to when `NAMZU_DISABLE_1M_CONTEXT` is on. */
36
+ export const DISABLED_ONE_MILLION_WINDOW_CAP_TOKENS = 200_000
37
+
38
+ const WINDOW_SUFFIX = /\[1m\]$/i
39
+
40
+ /**
41
+ * A model id may end in `[1m]` (any case) to declare a one-million-token
42
+ * window, whether or not the id is recognised. The suffix is a marker for
43
+ * Namzu, never part of the vendor's name: strip it with this before an id
44
+ * reaches a driver or a wire.
45
+ */
46
+ export function splitModelWindowSuffix(model: string): {
47
+ readonly model: string
48
+ readonly declaresOneMillion: boolean
49
+ } {
50
+ const match = WINDOW_SUFFIX.exec(model)
51
+ if (!match) return { model, declaresOneMillion: false }
52
+ return { model: model.slice(0, match.index), declaresOneMillion: true }
53
+ }
54
+
55
+ /** The id with any `[1m]` suffix removed. */
56
+ export function stripModelWindowSuffix(model: string): string {
57
+ return splitModelWindowSuffix(model).model
58
+ }
59
+
60
+ /**
61
+ * `NAMZU_DISABLE_1M_CONTEXT` (1, true, yes or on): the suffix is still
62
+ * stripped but declares nothing, and no window above 200,000 tokens is
63
+ * assumed for a model whose window comes from a driver or the table.
64
+ */
65
+ export function oneMillionWindowDisabled(env: NodeJS.ProcessEnv = process.env): boolean {
66
+ const value = env.NAMZU_DISABLE_1M_CONTEXT?.trim().toLowerCase()
67
+ return value === '1' || value === 'true' || value === 'yes' || value === 'on'
68
+ }
69
+
70
+ /** Smallest and largest window `NAMZU_AUTO_COMPACT_WINDOW` can name. */
71
+ export const AUTO_COMPACT_WINDOW_MIN_TOKENS = 100_000
72
+ export const AUTO_COMPACT_WINDOW_MAX_TOKENS = ONE_MILLION_WINDOW_TOKENS
73
+
74
+ export type AutoCompactWindowParse =
75
+ | { readonly kind: 'in_range'; readonly tokens: number }
76
+ | { readonly kind: 'below_minimum'; readonly tokens: number }
77
+ | { readonly kind: 'above_maximum' }
78
+ | { readonly kind: 'unusable' }
79
+
80
+ /**
81
+ * Read an automatic-summary window the way people write one: a plain number
82
+ * (`200000`) or one with a `k` or `m` suffix (`786k`, `1m`, `1.5m`). Anything
83
+ * under 100,000 is `below_minimum`, over 1,000,000 `above_maximum`, and
84
+ * something that is not a size at all `unusable`.
85
+ */
86
+ export function parseAutoCompactWindow(raw: string): AutoCompactWindowParse {
87
+ const text = raw.trim()
88
+ const last = text.slice(-1).toLowerCase()
89
+ const scale = last === 'm' ? 1_000_000 : last === 'k' ? 1_000 : undefined
90
+ const digits = scale === undefined ? text : text.slice(0, -1)
91
+ const valid = scale === undefined ? /^\+?\d+$/ : /^\+?\d+(\.\d+)?$/
92
+ const value = valid.test(digits) ? Number(digits) * (scale ?? 1) : Number.NaN
93
+ if (Number.isNaN(value) || value < 1) return { kind: 'unusable' }
94
+ if (value < AUTO_COMPACT_WINDOW_MIN_TOKENS)
95
+ return {
96
+ kind: 'below_minimum',
97
+ tokens: Math.min(AUTO_COMPACT_WINDOW_MIN_TOKENS - 1, Math.round(value)),
98
+ }
99
+ if (value > AUTO_COMPACT_WINDOW_MAX_TOKENS) return { kind: 'above_maximum' }
100
+ return { kind: 'in_range', tokens: Math.round(value) }
101
+ }
102
+
103
+ /**
104
+ * The window to use for a written size, and a one-line warning when what was
105
+ * written had to be changed: below 100,000 reads as 100,000, above 1,000,000
106
+ * as 1,000,000, and a non-size is ignored (`tokens` undefined). `source`
107
+ * names where it was written, e.g. `NAMZU_AUTO_COMPACT_WINDOW`.
108
+ */
109
+ export function resolveAutoCompactWindowSetting(
110
+ raw: string | number,
111
+ source: string,
112
+ ): { readonly tokens: number | undefined; readonly warning?: string } {
113
+ const text = String(raw)
114
+ const parsed = parseAutoCompactWindow(text)
115
+ const shown = `${source}="${text.slice(0, 32).replace(/[^\w.,+ $%{}=;-]+/g, '?')}${text.length > 32 ? '\u2026' : ''}"`
116
+ const advice = 'Set it to a size from 100k to 1M, such as 200k or 200000, or unset it.'
117
+ switch (parsed.kind) {
118
+ case 'in_range':
119
+ return { tokens: parsed.tokens }
120
+ case 'below_minimum':
121
+ return {
122
+ tokens: AUTO_COMPACT_WINDOW_MIN_TOKENS,
123
+ warning: `${shown} reads as ${parsed.tokens.toLocaleString('en-US')} tokens, so Namzu treats it as the 100k minimum. ${advice}`,
124
+ }
125
+ case 'above_maximum':
126
+ return {
127
+ tokens: AUTO_COMPACT_WINDOW_MAX_TOKENS,
128
+ warning: `${shown} is too large, so Namzu treats it as the 1M maximum. ${advice}`,
129
+ }
130
+ case 'unusable':
131
+ return {
132
+ tokens: undefined,
133
+ warning: `${shown} isn't a usable size, so Namzu ignores it. ${advice}`,
134
+ }
135
+ }
136
+ }
137
+
33
138
  /**
34
139
  * Conservative default for a model we do not recognise.
35
140
  *
@@ -85,9 +190,32 @@ const WINDOWS: ReadonlyArray<readonly [prefix: string, tokens: number]> = [
85
190
  ['qwen', 32_768],
86
191
  ['mistral-large', 128_000],
87
192
  ['mixtral', 32_768],
193
+ // The current DeepSeek models document a 1M window
194
+ // (https://api-docs.deepseek.com/quick_start/pricing), listed per id: a
195
+ // gateway can serve a derivative under a name that merely starts like one
196
+ // (a fine-tune, a distill), and nothing says it keeps the parent's window.
197
+ // Over-estimating ends a turn on a provider rejection; under-estimating
198
+ // costs a summary. So an id not named here falls to the bare `deepseek`
199
+ // row below, the 64k the older ids (chat, reasoner, R1 distills) had. The
200
+ // three ids are matched whole (see `DEEPSEEK_ONE_MILLION_IDS`), never by
201
+ // substring.
88
202
  ['deepseek', 64_000],
89
203
  ]
90
204
 
205
+ /** DeepSeek ids documented at a 1M window; matched exactly, not by substring. */
206
+ const DEEPSEEK_ONE_MILLION_IDS: ReadonlySet<string> = new Set([
207
+ 'deepseek-v4-flash',
208
+ 'deepseek-v4-pro',
209
+ 'deepseek-flash',
210
+ ])
211
+
212
+ /** The id with a gateway prefix (`deepseek/`) and a `:tag` (`:free`) removed. */
213
+ function bareModelName(id: string): string {
214
+ const afterSlash = id.slice(id.lastIndexOf('/') + 1)
215
+ const colon = afterSlash.indexOf(':')
216
+ return colon === -1 ? afterSlash : afterSlash.slice(0, colon)
217
+ }
218
+
91
219
  const SORTED = [...WINDOWS].sort((a, b) => b[0].length - a[0].length)
92
220
 
93
221
  /**
@@ -98,7 +226,19 @@ const SORTED = [...WINDOWS].sort((a, b) => b[0].length - a[0].length)
98
226
  */
99
227
  export function lookupContextWindow(model: string | undefined): number | undefined {
100
228
  if (!model) return undefined
101
- const id = model.toLowerCase()
229
+ const { declaresOneMillion } = splitModelWindowSuffix(model)
230
+ const known = lookupBareContextWindow(model)
231
+ if (declaresOneMillion && !oneMillionWindowDisabled()) {
232
+ return Math.max(known ?? 0, ONE_MILLION_WINDOW_TOKENS)
233
+ }
234
+ return known
235
+ }
236
+
237
+ /** The table's answer for an id with any `[1m]` suffix ignored. */
238
+ function lookupBareContextWindow(model: string | undefined): number | undefined {
239
+ if (!model) return undefined
240
+ const id = stripModelWindowSuffix(model).toLowerCase()
241
+ if (DEEPSEEK_ONE_MILLION_IDS.has(bareModelName(id))) return ONE_MILLION_WINDOW_TOKENS
102
242
  for (const [prefix, tokens] of SORTED) {
103
243
  if (id.includes(prefix)) return tokens
104
244
  }
@@ -191,13 +331,34 @@ export function resolveContextWindow(
191
331
  if (configured !== undefined && configured > 0) {
192
332
  return { tokens: configured, source: 'config' }
193
333
  }
334
+ const { declaresOneMillion } = splitModelWindowSuffix(model ?? '')
335
+ const disabled = oneMillionWindowDisabled()
336
+ const base = resolveUndeclaredWindow(model, providerReported)
337
+ // `[1m]` raises the window to at least a million (reported as 'model-table':
338
+ // the id itself is the fact), never lowers one a
339
+ // driver or the table already puts higher.
340
+ if (declaresOneMillion && !disabled && base.tokens < ONE_MILLION_WINDOW_TOKENS) {
341
+ return { tokens: ONE_MILLION_WINDOW_TOKENS, source: 'model-table' }
342
+ }
343
+ // With the million-token window switched off, nothing assumed or reported
344
+ // for a model counts for more than 200,000.
345
+ if (disabled && base.tokens > DISABLED_ONE_MILLION_WINDOW_CAP_TOKENS) {
346
+ return { tokens: DISABLED_ONE_MILLION_WINDOW_CAP_TOKENS, source: base.source }
347
+ }
348
+ return base
349
+ }
350
+
351
+ function resolveUndeclaredWindow(
352
+ model: string | undefined,
353
+ providerReported: number | undefined,
354
+ ): ResolvedContextWindow {
194
355
  if (providerReported !== undefined && providerReported > 0) {
195
356
  return { tokens: providerReported, source: 'provider' }
196
357
  }
197
358
  // A driver that resolved `undefined` falls through to HERE, not to the
198
359
  // default. "I asked and it does not know" leaves the table exactly as
199
360
  // authoritative as it was before anyone asked.
200
- const known = lookupContextWindow(model)
361
+ const known = lookupBareContextWindow(model)
201
362
  if (known !== undefined) {
202
363
  return { tokens: known, source: 'model-table' }
203
364
  }
@@ -1,8 +1,21 @@
1
1
  export { WorkflowScriptError } from './errors.js'
2
2
  export { FileWorkflowJournal } from './journal.js'
3
3
  export { parseWorkflowMeta } from './meta.js'
4
+ export {
5
+ formatWorkflowJournalLine,
6
+ loadWorkflowResume,
7
+ readWorkflowJournalChecked,
8
+ workflowCallKey,
9
+ workflowJournalLineHash,
10
+ workflowJournalSeed,
11
+ } from './resume.js'
12
+ export type { WorkflowCallIdentity } from './resume.js'
4
13
  export { summarizeWorkflowPhases } from './phases.js'
5
14
  export type { PhaseAgentView, PhaseRecord } from './phases.js'
6
- export { resolveWorkflowLimits, WORKFLOW_HOST_CALL, WorkflowRuntime } from './runtime.js'
15
+ export {
16
+ resolveWorkflowLimits,
17
+ WORKFLOW_HOST_CALL,
18
+ WorkflowRuntime,
19
+ } from './runtime.js'
7
20
  export type { WorkflowRuntimeOptions } from './runtime.js'
8
21
  export type * from './types.js'
@@ -1,8 +1,19 @@
1
1
  import { createHash } from 'node:crypto'
2
- import { appendFile, mkdir, writeFile } from 'node:fs/promises'
3
- import { join } from 'node:path'
2
+ import { mkdir, open, readFile, stat, writeFile } from 'node:fs/promises'
3
+ import { basename, dirname, join, resolve } from 'node:path'
4
4
 
5
- import type { WorkflowJournal, WorkflowJournalRecord } from './types.js'
5
+ import {
6
+ formatWorkflowJournalLine,
7
+ readWorkflowJournalChecked,
8
+ workflowJournalLineHash,
9
+ workflowJournalSeed,
10
+ } from './resume.js'
11
+ import type {
12
+ CheckedWorkflowJournal,
13
+ WorkflowJournal,
14
+ WorkflowJournalReader,
15
+ WorkflowJournalRecord,
16
+ } from './types.js'
6
17
 
7
18
  export function sha256(text: string): string {
8
19
  return createHash('sha256').update(text).digest('hex')
@@ -17,30 +28,169 @@ export function preview(text: string, max = 500): string {
17
28
  return `${text.slice(0, end)}…`
18
29
  }
19
30
 
31
+ /** The chain state of one journal file, shared by every writer in this process. */
32
+ interface ChainState {
33
+ queue: Promise<void>
34
+ loaded: boolean
35
+ seq: number
36
+ prev: string
37
+ needsNewline: boolean
38
+ finished: boolean
39
+ /** Bytes the file had after the last write this process made; a different size means someone else wrote. */
40
+ size: number
41
+ /** Appends queued or running; the state is dropped once a pass's end is written and none are left. */
42
+ pending: number
43
+ }
44
+
45
+ const chains = new Map<string, ChainState>()
46
+
47
+ /** Records after which a crash must not lose the line: a saved result, the end of a pass, the start of one. */
48
+ function mustSurviveCrash(record: WorkflowJournalRecord): boolean {
49
+ return (
50
+ record.type === 'run_finished' ||
51
+ record.type === 'run_started' ||
52
+ (record.type === 'agent_outcome' && Object.hasOwn(record, 'result'))
53
+ )
54
+ }
55
+
20
56
  /**
21
57
  * A journal in a directory: `script.js` (the exact script text, mode 0600)
22
58
  * and `journal.jsonl` (one JSON record per line, append-only). The caller
23
59
  * chooses the directory, normally `<session>/runs/<runId>/workflow/`.
60
+ *
61
+ * Every line carries `seq` and `prev` (the hash of the line before it, the
62
+ * first chaining from a seed bound to the run id), so a line that was edited,
63
+ * removed or cut short is found when the journal is read back. One writer per
64
+ * file serves the whole process: a resumed pass and a late agent of the pass
65
+ * before it append through the same queue and the same chain, and nothing is
66
+ * appended after a pass's `run_finished` until the next pass starts. This
67
+ * assumes one process writes the file at a time, which the session lease
68
+ * already guarantees. A chain proves the file was not damaged or edited by
69
+ * accident; someone who can rewrite the whole file can rewrite the chain too.
24
70
  */
25
- export class FileWorkflowJournal implements WorkflowJournal {
71
+ export class FileWorkflowJournal implements WorkflowJournal, WorkflowJournalReader {
26
72
  private ready: Promise<void> | undefined
73
+ private readonly file: string
27
74
 
28
- constructor(private readonly dir: string) {}
75
+ constructor(
76
+ private readonly dir: string,
77
+ private readonly options: {
78
+ readonly runId?: string
79
+ /** A secret that signs every line; see `readWorkflowJournalChecked`. */
80
+ readonly key?: string | Uint8Array
81
+ } = {},
82
+ ) {
83
+ this.file = resolve(dir, 'journal.jsonl')
84
+ }
29
85
 
30
86
  private ensure(): Promise<void> {
31
87
  this.ready ??= mkdir(this.dir, { recursive: true, mode: 0o700 }).then(() => undefined)
32
88
  return this.ready
33
89
  }
34
90
 
35
- async writeScript(source: string): Promise<void> {
36
- await this.ensure()
37
- await writeFile(join(this.dir, 'script.js'), source, { mode: 0o600 })
91
+ /** The run id of the file's first line, learned from the file or from the first `run_started` written. */
92
+ private learned: string | undefined
93
+
94
+ private runId(record?: WorkflowJournalRecord, text?: string): string {
95
+ if (this.options.runId !== undefined) return this.options.runId
96
+ if (record?.type === 'run_started') this.learned = record.runId
97
+ if (this.learned !== undefined) return this.learned
98
+ const first = text?.slice(0, text.indexOf('\n') < 0 ? undefined : text.indexOf('\n'))
99
+ if (first) {
100
+ try {
101
+ const parsed = JSON.parse(first) as { type?: unknown; runId?: unknown }
102
+ if (parsed.type === 'run_started' && typeof parsed.runId === 'string') {
103
+ this.learned = parsed.runId
104
+ return parsed.runId
105
+ }
106
+ } catch {
107
+ // Not a record: fall back to the folder name below.
108
+ }
109
+ }
110
+ return basename(dirname(resolve(this.dir)))
38
111
  }
39
112
 
40
- async append(record: WorkflowJournalRecord): Promise<void> {
113
+ async writeScript(source: string, _sha256?: string, pass = 1): Promise<void> {
41
114
  await this.ensure()
42
- await appendFile(join(this.dir, 'journal.jsonl'), `${JSON.stringify(record)}\n`, {
43
- mode: 0o600,
115
+ const name = pass <= 1 ? 'script.js' : `script.${pass}.js`
116
+ await writeFile(join(this.dir, name), source, { mode: 0o600 })
117
+ }
118
+
119
+ async read(): Promise<CheckedWorkflowJournal> {
120
+ let text: string
121
+ try {
122
+ text = await readFile(this.file, 'utf8')
123
+ } catch (error) {
124
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
125
+ text = ''
126
+ }
127
+ return readWorkflowJournalChecked(text, this.runId(undefined, text), {
128
+ ...(this.options.key !== undefined ? { key: this.options.key } : {}),
129
+ })
130
+ }
131
+
132
+ append(record: WorkflowJournalRecord): Promise<void> {
133
+ let state = chains.get(this.file)
134
+ if (state === undefined) {
135
+ state = {
136
+ queue: Promise.resolve(),
137
+ loaded: false,
138
+ seq: 0,
139
+ prev: '',
140
+ needsNewline: false,
141
+ finished: false,
142
+ size: 0,
143
+ pending: 0,
144
+ }
145
+ chains.set(this.file, state)
146
+ }
147
+ const chain = state
148
+ chain.pending++
149
+ const run = chain.queue.then(async () => {
150
+ await this.ensure()
151
+ const onDisk = await stat(this.file).then(
152
+ (info) => info.size,
153
+ () => 0,
154
+ )
155
+ if (!chain.loaded || onDisk !== chain.size) {
156
+ const checked = await this.read()
157
+ chain.seq = checked.lastSeq
158
+ chain.prev = checked.lastHash
159
+ chain.needsNewline = !checked.endsWithNewline
160
+ chain.finished = checked.records.at(-1)?.type === 'run_finished'
161
+ chain.loaded = true
162
+ chain.size = onDisk
163
+ }
164
+ if (record.type === 'run_started') chain.finished = false
165
+ // Nothing belongs after the pass's end; a late agent of that pass is dropped here.
166
+ else if (chain.finished) return
167
+ if (chain.seq === 0) chain.prev = workflowJournalSeed(this.runId(record))
168
+ const seq = chain.seq + 1
169
+ const line = formatWorkflowJournalLine(record, seq, chain.prev, this.options.key)
170
+ const handle = await open(this.file, 'a', 0o600)
171
+ try {
172
+ const text = `${chain.needsNewline ? '\n' : ''}${line}\n`
173
+ await handle.appendFile(text)
174
+ chain.size += Buffer.byteLength(text)
175
+ if (mustSurviveCrash(record)) await handle.datasync()
176
+ } finally {
177
+ await handle.close()
178
+ }
179
+ chain.needsNewline = false
180
+ chain.seq = seq
181
+ chain.prev = workflowJournalLineHash(line)
182
+ if (record.type === 'run_finished') chain.finished = true
44
183
  })
184
+ chain.queue = run
185
+ .catch(() => undefined)
186
+ .then(() => {
187
+ chain.pending--
188
+ // A finished run keeps no state: the next append reads the file again, and
189
+ // what it finds (a last `run_finished`) is the same thing the state held.
190
+ if (chain.pending === 0 && chain.finished && chains.get(this.file) === chain) {
191
+ chains.delete(this.file)
192
+ }
193
+ })
194
+ return run
45
195
  }
46
196
  }
@@ -28,7 +28,7 @@ const PRELUDE_SOURCE = String.raw`(function (config) {
28
28
  }
29
29
  function phase(title) {
30
30
  var t = clean(title, 120);
31
- if (!t) throw new TypeError('phase() needs a non-empty title.');
31
+ if (!t) throw misuse('phase() needs a non-empty title.');
32
32
  cursor = t;
33
33
  emit({ k: 'phase', title: t });
34
34
  }
@@ -53,13 +53,26 @@ const PRELUDE_SOURCE = String.raw`(function (config) {
53
53
  return e;
54
54
  }
55
55
  function isBudgetError(e) { return !!e && e.name === 'WorkflowBudgetExceededError'; }
56
+ function misuse(message) {
57
+ var e = new TypeError(message);
58
+ e.workflowMisuse = true;
59
+ return e;
60
+ }
61
+ function isMisuse(e) { return !!e && e.workflowMisuse === true; }
62
+ var optionNames = ['label', 'phase', 'schema', 'agentType', 'model', 'effort'];
56
63
  async function agent(prompt, opts) {
57
64
  if (typeof prompt !== 'string' || prompt.length === 0) {
58
- throw new TypeError('agent(prompt, options?): the prompt must be a non-empty string, got ' + kind(prompt));
65
+ throw misuse('agent(prompt, options?): the prompt must be a non-empty string, got ' + kind(prompt));
59
66
  }
60
67
  var o = opts == null ? {} : opts;
61
68
  if (typeof o !== 'object' || Array.isArray(o)) {
62
- throw new TypeError('agent(prompt, options?): options must be an object, got ' + kind(o));
69
+ throw misuse('agent(prompt, options?): options must be an object, got ' + kind(o));
70
+ }
71
+ var unknown = Object.keys(o).filter(function (k) { return optionNames.indexOf(k) < 0; });
72
+ if (unknown.length > 0) {
73
+ // Ignored, as it always was, but no longer silently: a saved script that
74
+ // misspells an option keeps running and the line says why it had no effect.
75
+ log('agent(prompt, options?): ignoring unknown option "' + unknown[0] + '"; the options are ' + optionNames.join(', ') + '.');
63
76
  }
64
77
  if (overBudget()) throw budgetError();
65
78
  var request = { prompt: prompt, cursor: cursor };
@@ -69,23 +82,32 @@ const PRELUDE_SOURCE = String.raw`(function (config) {
69
82
  if (o.agentType != null) request.agentType = clean(o.agentType, 120);
70
83
  if (o.model != null) request.model = clean(o.model, 120);
71
84
  if (o.effort != null) request.effort = clean(o.effort, 32);
72
- var reply = await rawCall('agent', request);
85
+ var reply;
86
+ try {
87
+ reply = await rawCall('agent', request);
88
+ } catch (e) {
89
+ // The host rejects a malformed request with an "agent(): ..." message;
90
+ // that is the script's mistake, not a failed child.
91
+ if (e && typeof e.message === 'string' && e.message.indexOf('agent(): ') === 0) throw misuse(e.message);
92
+ throw e;
93
+ }
73
94
  spent.tokensUsed = reply.t;
74
95
  return reply.v;
75
96
  }
76
97
  async function parallel(thunks) {
77
98
  if (!Array.isArray(thunks)) {
78
- throw new TypeError('parallel(thunks): the argument must be an array of functions, got ' + kind(thunks));
99
+ throw misuse('parallel(thunks): the argument must be an array of functions, got ' + kind(thunks));
79
100
  }
80
101
  for (var p = 0; p < thunks.length; p++) {
81
102
  if (typeof thunks[p] !== 'function') {
82
- throw new TypeError('parallel(thunks): item ' + (p + 1) + ' must be a function (wrap the work as () => agent(...)), got ' + kind(thunks[p]));
103
+ throw misuse('parallel(thunks): item ' + (p + 1) + ' must be a function (wrap the work as () => agent(...)), got ' + kind(thunks[p]));
83
104
  }
84
105
  }
85
106
  var dropped = 0;
86
107
  var results = await Promise.all(thunks.map(function (t, n) {
87
108
  return Promise.resolve().then(t).catch(function (e) {
88
109
  if (isBudgetError(e)) { dropped++; return null; }
110
+ if (isMisuse(e)) throw e;
89
111
  log('Step ' + (n + 1) + ' stopped: ' + clean(e && e.message ? e.message : e, 300));
90
112
  return null;
91
113
  });
@@ -95,12 +117,12 @@ const PRELUDE_SOURCE = String.raw`(function (config) {
95
117
  }
96
118
  async function pipeline(items) {
97
119
  if (!Array.isArray(items)) {
98
- throw new TypeError('pipeline(items, ...stages): the first argument must be an array, got ' + kind(items));
120
+ throw misuse('pipeline(items, ...stages): the first argument must be an array, got ' + kind(items));
99
121
  }
100
122
  var stages = Array.prototype.slice.call(arguments, 1);
101
123
  for (var s = 0; s < stages.length; s++) {
102
124
  if (typeof stages[s] !== 'function') {
103
- throw new TypeError('pipeline(items, ...stages): stage ' + (s + 1) + ' must be a function, got ' + kind(stages[s]));
125
+ throw misuse('pipeline(items, ...stages): stage ' + (s + 1) + ' must be a function, got ' + kind(stages[s]));
104
126
  }
105
127
  }
106
128
  if (items.length > 0 && overBudget()) throw budgetError();
@@ -112,6 +134,7 @@ const PRELUDE_SOURCE = String.raw`(function (config) {
112
134
  value = await stages[i](value, item, index);
113
135
  } catch (e) {
114
136
  if (isBudgetError(e)) { dropped++; return null; }
137
+ if (isMisuse(e)) throw e;
115
138
  log('Item ' + (index + 1) + ' stopped at step ' + (i + 1) + ': ' + clean(e && e.message ? e.message : e, 300));
116
139
  return null;
117
140
  }
@@ -141,6 +164,24 @@ const PRELUDE_SOURCE = String.raw`(function (config) {
141
164
  install('log', log);
142
165
  install('budget', budget);
143
166
  install('args', config.args);
167
+ if (config.deterministic) {
168
+ var RealDate = Date;
169
+ var clockMessage = 'Date.now() / new Date() are unavailable in workflow scripts (breaks resume). Stamp results after the workflow returns, or pass timestamps through args.';
170
+ var randomMessage = 'Math.random() is unavailable in workflow scripts (breaks resume). For N independent samples, include the index in the agent label or prompt.';
171
+ var ShimDate = function Date(a, b, c, d, e, f, g) {
172
+ if (!new.target || arguments.length === 0) throw new Error(clockMessage);
173
+ return Reflect.construct(RealDate, Array.prototype.slice.call(arguments), new.target === ShimDate ? RealDate : new.target);
174
+ };
175
+ Object.defineProperty(ShimDate, 'prototype', { value: RealDate.prototype, writable: false, configurable: false });
176
+ Object.defineProperty(ShimDate, 'now', { value: function now() { throw new Error(clockMessage); }, writable: false, configurable: false });
177
+ Object.defineProperty(ShimDate, 'parse', { value: RealDate.parse, writable: false, configurable: false });
178
+ Object.defineProperty(ShimDate, 'UTC', { value: RealDate.UTC, writable: false, configurable: false });
179
+ Object.defineProperty(RealDate.prototype, 'constructor', { value: ShimDate, writable: false, configurable: false });
180
+ Object.defineProperty(globalThis, 'Date', { value: ShimDate, writable: false, configurable: false, enumerable: false });
181
+ Object.defineProperty(Math, 'random', { value: function random() { throw new Error(randomMessage); }, writable: false, configurable: false });
182
+ try { delete globalThis.eval; } catch (ignored) {}
183
+ Object.freeze(RealDate);
184
+ }
144
185
  })`
145
186
 
146
187
  /**
@@ -151,6 +192,7 @@ const PRELUDE_SOURCE = String.raw`(function (config) {
151
192
  export function preludeLineCount(config: {
152
193
  args: unknown
153
194
  tokenLimit: number | null
195
+ deterministic?: boolean
154
196
  }): number {
155
197
  return buildPrelude(config).split('\n').length
156
198
  }
@@ -159,6 +201,7 @@ export function preludeLineCount(config: {
159
201
  export function buildPrelude(config: {
160
202
  args: unknown
161
203
  tokenLimit: number | null
204
+ deterministic?: boolean
162
205
  }): string {
163
206
  const literal = JSON.stringify(config)
164
207
  .split(String.fromCharCode(0x2028))