@namzu/sdk 59.0.0 → 60.1.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 (370) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/dist/agents/QueryAgent.d.ts.map +1 -1
  3. package/dist/agents/QueryAgent.js +1 -0
  4. package/dist/agents/QueryAgent.js.map +1 -1
  5. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  6. package/dist/bridge/a2a/mapper.js +16 -0
  7. package/dist/bridge/a2a/mapper.js.map +1 -1
  8. package/dist/bridge/acp/update.d.ts.map +1 -1
  9. package/dist/bridge/acp/update.js +1 -0
  10. package/dist/bridge/acp/update.js.map +1 -1
  11. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  12. package/dist/bridge/sse/mapper.js +12 -0
  13. package/dist/bridge/sse/mapper.js.map +1 -1
  14. package/dist/compaction/context-window.d.ts +53 -0
  15. package/dist/compaction/context-window.d.ts.map +1 -1
  16. package/dist/compaction/context-window.js +141 -2
  17. package/dist/compaction/context-window.js.map +1 -1
  18. package/dist/constants/continuation.d.ts +9 -0
  19. package/dist/constants/continuation.d.ts.map +1 -1
  20. package/dist/constants/continuation.js +9 -0
  21. package/dist/constants/continuation.js.map +1 -1
  22. package/dist/contracts/session/api.d.ts +7 -1
  23. package/dist/contracts/session/api.d.ts.map +1 -1
  24. package/dist/execution/workflow/index.d.ts +3 -1
  25. package/dist/execution/workflow/index.d.ts.map +1 -1
  26. package/dist/execution/workflow/index.js +2 -1
  27. package/dist/execution/workflow/index.js.map +1 -1
  28. package/dist/execution/workflow/journal.d.ts +24 -4
  29. package/dist/execution/workflow/journal.d.ts.map +1 -1
  30. package/dist/execution/workflow/journal.js +131 -9
  31. package/dist/execution/workflow/journal.js.map +1 -1
  32. package/dist/execution/workflow/prelude.d.ts +2 -0
  33. package/dist/execution/workflow/prelude.d.ts.map +1 -1
  34. package/dist/execution/workflow/prelude.js +49 -8
  35. package/dist/execution/workflow/prelude.js.map +1 -1
  36. package/dist/execution/workflow/resume.d.ts +59 -0
  37. package/dist/execution/workflow/resume.d.ts.map +1 -0
  38. package/dist/execution/workflow/resume.js +273 -0
  39. package/dist/execution/workflow/resume.js.map +1 -0
  40. package/dist/execution/workflow/runtime.d.ts.map +1 -1
  41. package/dist/execution/workflow/runtime.js +112 -8
  42. package/dist/execution/workflow/runtime.js.map +1 -1
  43. package/dist/execution/workflow/types.d.ts +105 -1
  44. package/dist/execution/workflow/types.d.ts.map +1 -1
  45. package/dist/index.d.ts +3 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +4 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/manager/session/turn-recorder.d.ts.map +1 -1
  50. package/dist/manager/session/turn-recorder.js +1 -0
  51. package/dist/manager/session/turn-recorder.js.map +1 -1
  52. package/dist/plugin/compat.d.ts +24 -0
  53. package/dist/plugin/compat.d.ts.map +1 -0
  54. package/dist/plugin/compat.js +149 -0
  55. package/dist/plugin/compat.js.map +1 -0
  56. package/dist/plugin/loader.d.ts +2 -1
  57. package/dist/plugin/loader.d.ts.map +1 -1
  58. package/dist/plugin/loader.js +6 -0
  59. package/dist/plugin/loader.js.map +1 -1
  60. package/dist/pricing/index.d.ts.map +1 -1
  61. package/dist/pricing/index.js +2 -1
  62. package/dist/pricing/index.js.map +1 -1
  63. package/dist/process/kill-tree.d.ts +44 -0
  64. package/dist/process/kill-tree.d.ts.map +1 -1
  65. package/dist/process/kill-tree.js +102 -4
  66. package/dist/process/kill-tree.js.map +1 -1
  67. package/dist/prompt/coding-agent-doctrine.d.ts +1 -1
  68. package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
  69. package/dist/prompt/coding-agent-doctrine.js +9 -1
  70. package/dist/prompt/coding-agent-doctrine.js.map +1 -1
  71. package/dist/provider/collect-chat-completion.d.ts.map +1 -1
  72. package/dist/provider/collect-chat-completion.js +4 -0
  73. package/dist/provider/collect-chat-completion.js.map +1 -1
  74. package/dist/provider/fallback.d.ts.map +1 -1
  75. package/dist/provider/fallback.js +11 -0
  76. package/dist/provider/fallback.js.map +1 -1
  77. package/dist/provider/idle-timeout.d.ts.map +1 -1
  78. package/dist/provider/idle-timeout.js +5 -0
  79. package/dist/provider/idle-timeout.js.map +1 -1
  80. package/dist/provider/mock.d.ts +2 -0
  81. package/dist/provider/mock.d.ts.map +1 -1
  82. package/dist/provider/mock.js +16 -8
  83. package/dist/provider/mock.js.map +1 -1
  84. package/dist/provider/model-window-suffix.d.ts +18 -0
  85. package/dist/provider/model-window-suffix.d.ts.map +1 -0
  86. package/dist/provider/model-window-suffix.js +78 -0
  87. package/dist/provider/model-window-suffix.js.map +1 -0
  88. package/dist/provider/retry.d.ts.map +1 -1
  89. package/dist/provider/retry.js +5 -0
  90. package/dist/provider/retry.js.map +1 -1
  91. package/dist/public-runtime.d.ts +5 -3
  92. package/dist/public-runtime.d.ts.map +1 -1
  93. package/dist/public-runtime.js +5 -4
  94. package/dist/public-runtime.js.map +1 -1
  95. package/dist/public-types.d.ts +1 -0
  96. package/dist/public-types.d.ts.map +1 -1
  97. package/dist/registry/command/kernel-commands.d.ts +1 -4
  98. package/dist/registry/command/kernel-commands.d.ts.map +1 -1
  99. package/dist/registry/command/kernel-commands.js +0 -28
  100. package/dist/registry/command/kernel-commands.js.map +1 -1
  101. package/dist/runtime/jobs/awaited-jobs.d.ts +6 -0
  102. package/dist/runtime/jobs/awaited-jobs.d.ts.map +1 -1
  103. package/dist/runtime/jobs/awaited-jobs.js +11 -0
  104. package/dist/runtime/jobs/awaited-jobs.js.map +1 -1
  105. package/dist/runtime/jobs/host-exit-finalizer.d.ts +20 -0
  106. package/dist/runtime/jobs/host-exit-finalizer.d.ts.map +1 -0
  107. package/dist/runtime/jobs/host-exit-finalizer.js +57 -0
  108. package/dist/runtime/jobs/host-exit-finalizer.js.map +1 -0
  109. package/dist/runtime/jobs/liveness.d.ts +67 -0
  110. package/dist/runtime/jobs/liveness.d.ts.map +1 -0
  111. package/dist/runtime/jobs/liveness.js +94 -0
  112. package/dist/runtime/jobs/liveness.js.map +1 -0
  113. package/dist/runtime/jobs/registry.d.ts +21 -0
  114. package/dist/runtime/jobs/registry.d.ts.map +1 -1
  115. package/dist/runtime/jobs/registry.js +104 -2
  116. package/dist/runtime/jobs/registry.js.map +1 -1
  117. package/dist/runtime/jobs/wake-budget.d.ts +35 -0
  118. package/dist/runtime/jobs/wake-budget.d.ts.map +1 -0
  119. package/dist/runtime/jobs/wake-budget.js +48 -0
  120. package/dist/runtime/jobs/wake-budget.js.map +1 -0
  121. package/dist/runtime/query/detached-calls.d.ts +65 -0
  122. package/dist/runtime/query/detached-calls.d.ts.map +1 -0
  123. package/dist/runtime/query/detached-calls.js +116 -0
  124. package/dist/runtime/query/detached-calls.js.map +1 -0
  125. package/dist/runtime/query/executor.d.ts +4 -1
  126. package/dist/runtime/query/executor.d.ts.map +1 -1
  127. package/dist/runtime/query/executor.js +90 -6
  128. package/dist/runtime/query/executor.js.map +1 -1
  129. package/dist/runtime/query/index.d.ts +34 -0
  130. package/dist/runtime/query/index.d.ts.map +1 -1
  131. package/dist/runtime/query/index.js +32 -5
  132. package/dist/runtime/query/index.js.map +1 -1
  133. package/dist/runtime/query/iteration/effort-clamp.d.ts +13 -0
  134. package/dist/runtime/query/iteration/effort-clamp.d.ts.map +1 -0
  135. package/dist/runtime/query/iteration/effort-clamp.js +42 -0
  136. package/dist/runtime/query/iteration/effort-clamp.js.map +1 -0
  137. package/dist/runtime/query/iteration/index.d.ts +34 -0
  138. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  139. package/dist/runtime/query/iteration/index.js +199 -6
  140. package/dist/runtime/query/iteration/index.js.map +1 -1
  141. package/dist/runtime/query/iteration/outstanding-work.d.ts.map +1 -1
  142. package/dist/runtime/query/iteration/outstanding-work.js +11 -3
  143. package/dist/runtime/query/iteration/outstanding-work.js.map +1 -1
  144. package/dist/runtime/query/iteration/phases/context.d.ts +9 -0
  145. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  146. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  147. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  148. package/dist/runtime/query/iteration/phases/tool-review.js +63 -1
  149. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  150. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  151. package/dist/runtime/query/iteration/stream-turn.js +6 -0
  152. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  153. package/dist/runtime/query/session-storage.d.ts.map +1 -1
  154. package/dist/runtime/query/session-storage.js +8 -3
  155. package/dist/runtime/query/session-storage.js.map +1 -1
  156. package/dist/runtime/query/steering.d.ts +29 -3
  157. package/dist/runtime/query/steering.d.ts.map +1 -1
  158. package/dist/runtime/query/steering.js +58 -3
  159. package/dist/runtime/query/steering.js.map +1 -1
  160. package/dist/runtime/query/tooling.d.ts +1 -0
  161. package/dist/runtime/query/tooling.d.ts.map +1 -1
  162. package/dist/runtime/query/tooling.js +1 -0
  163. package/dist/runtime/query/tooling.js.map +1 -1
  164. package/dist/runtime/system-events.d.ts +5 -0
  165. package/dist/runtime/system-events.d.ts.map +1 -1
  166. package/dist/runtime/system-events.js +7 -1
  167. package/dist/runtime/system-events.js.map +1 -1
  168. package/dist/sandbox/denial.d.ts +11 -0
  169. package/dist/sandbox/denial.d.ts.map +1 -0
  170. package/dist/sandbox/denial.js +61 -0
  171. package/dist/sandbox/denial.js.map +1 -0
  172. package/dist/sandbox/file-walk-program.d.ts.map +1 -1
  173. package/dist/sandbox/file-walk-program.js +31 -13
  174. package/dist/sandbox/file-walk-program.js.map +1 -1
  175. package/dist/sandbox/file-walk.d.ts +2 -0
  176. package/dist/sandbox/file-walk.d.ts.map +1 -1
  177. package/dist/sandbox/file-walk.js +64 -13
  178. package/dist/sandbox/file-walk.js.map +1 -1
  179. package/dist/sandbox/ignore-matcher.d.ts +30 -0
  180. package/dist/sandbox/ignore-matcher.d.ts.map +1 -0
  181. package/dist/sandbox/ignore-matcher.js +11 -0
  182. package/dist/sandbox/ignore-matcher.js.map +1 -0
  183. package/dist/sandbox/ignore-source.d.ts +26 -0
  184. package/dist/sandbox/ignore-source.d.ts.map +1 -0
  185. package/dist/sandbox/ignore-source.js +221 -0
  186. package/dist/sandbox/ignore-source.js.map +1 -0
  187. package/dist/sandbox/provider/local.d.ts.map +1 -1
  188. package/dist/sandbox/provider/local.js +11 -1
  189. package/dist/sandbox/provider/local.js.map +1 -1
  190. package/dist/scheduler/completion-inbox.d.ts.map +1 -1
  191. package/dist/scheduler/completion-inbox.js +1 -0
  192. package/dist/scheduler/completion-inbox.js.map +1 -1
  193. package/dist/session/log-hash.d.ts.map +1 -1
  194. package/dist/session/log-hash.js +18 -1
  195. package/dist/session/log-hash.js.map +1 -1
  196. package/dist/store/memory/disk.d.ts.map +1 -1
  197. package/dist/store/memory/disk.js +1 -1
  198. package/dist/store/memory/disk.js.map +1 -1
  199. package/dist/store/memory/markdown.d.ts.map +1 -1
  200. package/dist/store/memory/markdown.js +1 -1
  201. package/dist/store/memory/markdown.js.map +1 -1
  202. package/dist/store/memory/operation-lock.d.ts +20 -4
  203. package/dist/store/memory/operation-lock.d.ts.map +1 -1
  204. package/dist/store/memory/operation-lock.js +235 -58
  205. package/dist/store/memory/operation-lock.js.map +1 -1
  206. package/dist/store/session-index/sqlite.d.ts.map +1 -1
  207. package/dist/store/session-index/sqlite.js +169 -16
  208. package/dist/store/session-index/sqlite.js.map +1 -1
  209. package/dist/tools/builtins/bash.d.ts.map +1 -1
  210. package/dist/tools/builtins/bash.js +10 -4
  211. package/dist/tools/builtins/bash.js.map +1 -1
  212. package/dist/tools/builtins/computer-use.d.ts +35 -0
  213. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  214. package/dist/tools/builtins/computer-use.js +512 -67
  215. package/dist/tools/builtins/computer-use.js.map +1 -1
  216. package/dist/tools/builtins/glob.d.ts +1 -0
  217. package/dist/tools/builtins/glob.d.ts.map +1 -1
  218. package/dist/tools/builtins/glob.js +65 -28
  219. package/dist/tools/builtins/glob.js.map +1 -1
  220. package/dist/tools/builtins/grep.d.ts +2 -0
  221. package/dist/tools/builtins/grep.d.ts.map +1 -1
  222. package/dist/tools/builtins/grep.js +94 -40
  223. package/dist/tools/builtins/grep.js.map +1 -1
  224. package/dist/tools/builtins/job.d.ts +1 -1
  225. package/dist/tools/builtins/job.d.ts.map +1 -1
  226. package/dist/tools/builtins/job.js +34 -7
  227. package/dist/tools/builtins/job.js.map +1 -1
  228. package/dist/tools/builtins/search-support.d.ts +44 -0
  229. package/dist/tools/builtins/search-support.d.ts.map +1 -0
  230. package/dist/tools/builtins/search-support.js +89 -0
  231. package/dist/tools/builtins/search-support.js.map +1 -0
  232. package/dist/tools/builtins/wait-for-job-bounds.d.ts +2 -0
  233. package/dist/tools/builtins/wait-for-job-bounds.d.ts.map +1 -1
  234. package/dist/tools/builtins/wait-for-job-bounds.js +3 -1
  235. package/dist/tools/builtins/wait-for-job-bounds.js.map +1 -1
  236. package/dist/tools/builtins/wait-for-job.d.ts +1 -1
  237. package/dist/tools/builtins/wait-for-job.d.ts.map +1 -1
  238. package/dist/tools/builtins/wait-for-job.js +13 -2
  239. package/dist/tools/builtins/wait-for-job.js.map +1 -1
  240. package/dist/tools/command-shell.d.ts.map +1 -1
  241. package/dist/tools/command-shell.js +14 -5
  242. package/dist/tools/command-shell.js.map +1 -1
  243. package/dist/turn/reporter.d.ts.map +1 -1
  244. package/dist/turn/reporter.js +11 -0
  245. package/dist/turn/reporter.js.map +1 -1
  246. package/dist/types/agent/query.d.ts +2 -0
  247. package/dist/types/agent/query.d.ts.map +1 -1
  248. package/dist/types/computer-use/index.d.ts +129 -13
  249. package/dist/types/computer-use/index.d.ts.map +1 -1
  250. package/dist/types/computer-use/index.js.map +1 -1
  251. package/dist/types/message/index.d.ts +1 -1
  252. package/dist/types/message/index.d.ts.map +1 -1
  253. package/dist/types/message/index.js +1 -0
  254. package/dist/types/message/index.js.map +1 -1
  255. package/dist/types/plugin/index.d.ts +24 -0
  256. package/dist/types/plugin/index.d.ts.map +1 -1
  257. package/dist/types/plugin/index.js +4 -0
  258. package/dist/types/plugin/index.js.map +1 -1
  259. package/dist/types/provider/chat.d.ts +19 -0
  260. package/dist/types/provider/chat.d.ts.map +1 -1
  261. package/dist/types/provider/config.d.ts +7 -0
  262. package/dist/types/provider/config.d.ts.map +1 -1
  263. package/dist/types/provider/index.d.ts +1 -1
  264. package/dist/types/provider/index.d.ts.map +1 -1
  265. package/dist/types/provider/interface.d.ts +15 -0
  266. package/dist/types/provider/interface.d.ts.map +1 -1
  267. package/dist/types/provider/stream.d.ts +6 -0
  268. package/dist/types/provider/stream.d.ts.map +1 -1
  269. package/dist/types/sandbox/index.d.ts +38 -1
  270. package/dist/types/sandbox/index.d.ts.map +1 -1
  271. package/dist/types/sandbox/index.js.map +1 -1
  272. package/dist/types/session/events.d.ts +34 -0
  273. package/dist/types/session/events.d.ts.map +1 -1
  274. package/dist/types/session/events.js.map +1 -1
  275. package/dist/types/session/records.d.ts +298 -34
  276. package/dist/types/session/records.d.ts.map +1 -1
  277. package/dist/types/session/records.js +35 -1
  278. package/dist/types/session/records.js.map +1 -1
  279. package/dist/types/session/stop-reason.d.ts +11 -0
  280. package/dist/types/session/stop-reason.d.ts.map +1 -1
  281. package/dist/types/tool/index.d.ts +30 -0
  282. package/dist/types/tool/index.d.ts.map +1 -1
  283. package/dist/types/tool/index.js.map +1 -1
  284. package/dist/types/tool/presentation.d.ts +8 -0
  285. package/dist/types/tool/presentation.d.ts.map +1 -1
  286. package/package.json +1 -1
  287. package/src/agents/QueryAgent.ts +1 -0
  288. package/src/bridge/a2a/mapper.ts +19 -0
  289. package/src/bridge/acp/update.ts +6 -1
  290. package/src/bridge/sse/mapper.ts +13 -0
  291. package/src/compaction/context-window.ts +163 -2
  292. package/src/constants/continuation.ts +13 -0
  293. package/src/contracts/session/api.ts +6 -0
  294. package/src/execution/workflow/index.ts +14 -1
  295. package/src/execution/workflow/journal.ts +161 -11
  296. package/src/execution/workflow/prelude.ts +51 -8
  297. package/src/execution/workflow/resume.ts +313 -0
  298. package/src/execution/workflow/runtime.ts +127 -9
  299. package/src/execution/workflow/types.ts +109 -1
  300. package/src/index.ts +17 -0
  301. package/src/manager/session/turn-recorder.ts +1 -0
  302. package/src/plugin/compat.ts +173 -0
  303. package/src/plugin/loader.ts +7 -1
  304. package/src/pricing/index.ts +2 -1
  305. package/src/process/kill-tree.ts +106 -4
  306. package/src/prompt/coding-agent-doctrine.ts +9 -1
  307. package/src/provider/collect-chat-completion.ts +3 -0
  308. package/src/provider/fallback.ts +17 -0
  309. package/src/provider/idle-timeout.ts +9 -0
  310. package/src/provider/mock.ts +19 -9
  311. package/src/provider/model-window-suffix.ts +85 -0
  312. package/src/provider/retry.ts +9 -0
  313. package/src/public-runtime.ts +21 -2
  314. package/src/public-types.ts +1 -0
  315. package/src/registry/command/kernel-commands.ts +1 -32
  316. package/src/runtime/jobs/awaited-jobs.ts +11 -0
  317. package/src/runtime/jobs/host-exit-finalizer.ts +67 -0
  318. package/src/runtime/jobs/liveness.ts +144 -0
  319. package/src/runtime/jobs/registry.ts +114 -2
  320. package/src/runtime/jobs/wake-budget.ts +55 -0
  321. package/src/runtime/query/detached-calls.ts +135 -0
  322. package/src/runtime/query/executor.ts +92 -10
  323. package/src/runtime/query/index.ts +65 -7
  324. package/src/runtime/query/iteration/effort-clamp.ts +46 -0
  325. package/src/runtime/query/iteration/index.ts +260 -6
  326. package/src/runtime/query/iteration/outstanding-work.ts +12 -3
  327. package/src/runtime/query/iteration/phases/context.ts +9 -0
  328. package/src/runtime/query/iteration/phases/tool-review.ts +73 -8
  329. package/src/runtime/query/iteration/stream-turn.ts +5 -0
  330. package/src/runtime/query/session-storage.ts +8 -3
  331. package/src/runtime/query/steering.ts +77 -6
  332. package/src/runtime/query/tooling.ts +2 -0
  333. package/src/runtime/system-events.ts +12 -1
  334. package/src/sandbox/denial.ts +79 -0
  335. package/src/sandbox/file-walk-program.ts +32 -13
  336. package/src/sandbox/file-walk.ts +75 -12
  337. package/src/sandbox/ignore-matcher.ts +46 -0
  338. package/src/sandbox/ignore-source.ts +220 -0
  339. package/src/sandbox/provider/local.ts +11 -1
  340. package/src/scheduler/completion-inbox.ts +1 -0
  341. package/src/session/log-hash.ts +21 -0
  342. package/src/store/memory/disk.ts +1 -0
  343. package/src/store/memory/markdown.ts +1 -0
  344. package/src/store/memory/operation-lock.ts +288 -57
  345. package/src/store/session-index/sqlite.ts +171 -16
  346. package/src/tools/builtins/bash.ts +11 -4
  347. package/src/tools/builtins/computer-use.ts +593 -68
  348. package/src/tools/builtins/glob.ts +79 -32
  349. package/src/tools/builtins/grep.ts +107 -43
  350. package/src/tools/builtins/job.ts +42 -12
  351. package/src/tools/builtins/search-support.ts +107 -0
  352. package/src/tools/builtins/wait-for-job-bounds.ts +5 -1
  353. package/src/tools/builtins/wait-for-job.ts +14 -2
  354. package/src/tools/command-shell.ts +13 -5
  355. package/src/turn/reporter.ts +12 -0
  356. package/src/types/agent/query.ts +2 -0
  357. package/src/types/computer-use/index.ts +157 -15
  358. package/src/types/message/index.ts +5 -3
  359. package/src/types/plugin/index.ts +8 -0
  360. package/src/types/provider/chat.ts +20 -0
  361. package/src/types/provider/config.ts +7 -0
  362. package/src/types/provider/index.ts +1 -0
  363. package/src/types/provider/interface.ts +20 -0
  364. package/src/types/provider/stream.ts +6 -0
  365. package/src/types/sandbox/index.ts +40 -1
  366. package/src/types/session/events.ts +65 -5
  367. package/src/types/session/records.ts +39 -2
  368. package/src/types/session/stop-reason.ts +11 -0
  369. package/src/types/tool/index.ts +30 -0
  370. package/src/types/tool/presentation.ts +8 -0
@@ -9,6 +9,7 @@ import { preview, sha256 } from './journal.js'
9
9
  import { splitWorkflowScript } from './meta.js'
10
10
  import { type PhaseAgentView, PhaseRegistry, summarizeWorkflowPhases } from './phases.js'
11
11
  import { buildPrelude, preludeLineCount } from './prelude.js'
12
+ import { workflowCallContent, workflowCallKey } from './resume.js'
12
13
  import { parseJsonText, validateAgainstSchema } from './schema.js'
13
14
  import type {
14
15
  WorkflowAgentEvent,
@@ -83,7 +84,9 @@ export function resolveWorkflowLimits(limits: WorkflowLimits = {}): WorkflowReso
83
84
  16 * 1024 * 1024,
84
85
  ),
85
86
  ...(limits.tokenBudget !== undefined
86
- ? { tokenBudget: integer('tokenBudget', limits.tokenBudget, 1, Number.MAX_SAFE_INTEGER) }
87
+ ? {
88
+ tokenBudget: integer('tokenBudget', limits.tokenBudget, 1, Number.MAX_SAFE_INTEGER),
89
+ }
87
90
  : {}),
88
91
  maxDurationMs: integer(
89
92
  'maxDurationMs',
@@ -198,6 +201,14 @@ interface AgentRecord {
198
201
  error?: string
199
202
  skipped: boolean
200
203
  userSkipped: boolean
204
+ key?: string
205
+ /** Set when the result came from an earlier pass instead of a run. */
206
+ cached?: {
207
+ readonly pass: number
208
+ readonly index: number
209
+ readonly childSessionId?: string
210
+ }
211
+ childSessionId?: string
201
212
  }
202
213
 
203
214
  interface Waiter {
@@ -283,6 +294,10 @@ export class WorkflowRuntime {
283
294
  const runId =
284
295
  options.runId ?? `w${[...randomBytes(8)].map((b) => (b % 36).toString(36)).join('')}`
285
296
  const startedAt = now()
297
+ const pass = options.resume?.pass ?? 1
298
+ const resume = options.resume
299
+ let latch = false
300
+ const seenContent = new Map<string, number>()
286
301
  const phases = new PhaseRegistry()
287
302
  const agents = new Map<number, AgentRecord>()
288
303
  const inFlight = new Set<Promise<unknown>>()
@@ -335,7 +350,13 @@ export class WorkflowRuntime {
335
350
  declared,
336
351
  at,
337
352
  })
338
- journal({ type: 'phase', at, index: phase.index, title: phase.title, declared })
353
+ journal({
354
+ type: 'phase',
355
+ at,
356
+ index: phase.index,
357
+ title: phase.title,
358
+ declared,
359
+ })
339
360
  }
340
361
  return phase
341
362
  }
@@ -361,6 +382,7 @@ export class WorkflowRuntime {
361
382
  ...(agent.resultPreview !== undefined ? { resultPreview: agent.resultPreview } : {}),
362
383
  ...(agent.error !== undefined ? { error: agent.error } : {}),
363
384
  ...(agent.skipped ? { skipped: true } : {}),
385
+ ...(agent.cached !== undefined ? { cached: true } : {}),
364
386
  })
365
387
 
366
388
  // FIFO admission: an agent starts when fewer than maxConcurrency run.
@@ -411,7 +433,13 @@ export class WorkflowRuntime {
411
433
 
412
434
  const settle = (
413
435
  agent: AgentRecord,
414
- outcome: { state: 'done' | 'error'; skipped?: boolean; value?: unknown; error?: string },
436
+ outcome: {
437
+ state: 'done' | 'error'
438
+ skipped?: boolean
439
+ value?: unknown
440
+ error?: string
441
+ interrupted?: boolean
442
+ },
415
443
  ): HostCallResult => {
416
444
  const at = now()
417
445
  agent.state = outcome.state
@@ -434,11 +462,25 @@ export class WorkflowRuntime {
434
462
  value = null
435
463
  }
436
464
  emit(agentEvent(agent))
465
+ const finalState = agent.state === 'error' ? 'error' : agent.skipped ? 'skipped' : 'done'
466
+ let saved: { result: unknown } | { unsaved: true } | undefined
467
+ if (finalState === 'done' && value !== null && agent.cached === undefined) {
468
+ try {
469
+ saved = { result: JSON.parse(JSON.stringify(value)) as unknown }
470
+ } catch {
471
+ saved = { unsaved: true }
472
+ journal({
473
+ type: 'log',
474
+ at,
475
+ message: "An agent's result could not be saved, so a resume will run it again.",
476
+ })
477
+ }
478
+ }
437
479
  journal({
438
480
  type: 'agent_outcome',
439
481
  at,
440
482
  index: agent.index,
441
- state: agent.state === 'error' ? 'error' : agent.skipped ? 'skipped' : 'done',
483
+ state: finalState,
442
484
  tokens: agent.tokens,
443
485
  toolCalls: agent.toolCalls,
444
486
  queuedAt: agent.queuedAt,
@@ -446,6 +488,22 @@ export class WorkflowRuntime {
446
488
  finishedAt: at,
447
489
  ...(agent.resultPreview !== undefined ? { resultPreview: agent.resultPreview } : {}),
448
490
  ...(agent.error !== undefined ? { error: agent.error } : {}),
491
+ ...(agent.key !== undefined ? { key: agent.key } : {}),
492
+ pass,
493
+ ...(agent.cached !== undefined
494
+ ? {
495
+ cached: true as const,
496
+ resultFrom: {
497
+ pass: agent.cached.pass,
498
+ index: agent.cached.index,
499
+ },
500
+ }
501
+ : {}),
502
+ ...(saved ?? {}),
503
+ ...(outcome.interrupted === true && finalState === 'error'
504
+ ? { interrupted: true as const }
505
+ : {}),
506
+ ...(agent.childSessionId !== undefined ? { childSessionId: agent.childSessionId } : {}),
449
507
  })
450
508
  return { ok: true, value: { v: value, t: totalTokens() } }
451
509
  }
@@ -457,6 +515,7 @@ export class WorkflowRuntime {
457
515
  ): HostCallResult => {
458
516
  agent.tokens = count(outcome.tokens)
459
517
  agent.toolCalls = count(outcome.toolCalls)
518
+ if (outcome.sessionId !== undefined) agent.childSessionId = outcome.sessionId
460
519
  if (agent.userSkipped || outcome.state === 'skipped') {
461
520
  return settle(agent, { state: 'done', skipped: true })
462
521
  }
@@ -464,10 +523,14 @@ export class WorkflowRuntime {
464
523
  return settle(agent, {
465
524
  state: 'error',
466
525
  error: 'The workflow was stopped before this agent finished.',
526
+ interrupted: true,
467
527
  })
468
528
  }
469
529
  if (outcome.state === 'error') {
470
- return settle(agent, { state: 'error', error: outcome.error ?? 'The agent failed.' })
530
+ return settle(agent, {
531
+ state: 'error',
532
+ error: outcome.error ?? 'The agent failed.',
533
+ })
471
534
  }
472
535
  if (call.schema === undefined) {
473
536
  return settle(agent, { state: 'done', value: outcome.text ?? '' })
@@ -503,6 +566,18 @@ export class WorkflowRuntime {
503
566
  return { ok: false, error: reason }
504
567
  }
505
568
  const index = ++requested
569
+ const contentId = workflowCallContent(call.prompt, call)
570
+ const ordinal = seenContent.get(contentId) ?? 0
571
+ seenContent.set(contentId, ordinal + 1)
572
+ const key = workflowCallKey(call.prompt, call, ordinal)
573
+ const hit = latch ? undefined : resume?.results.get(key)
574
+ if (
575
+ resume !== undefined &&
576
+ hit === undefined &&
577
+ !(resume.seen.has(key) && !resume.failed.has(key) && !resume.demoted.has(key))
578
+ ) {
579
+ latch = true
580
+ }
506
581
  const phaseTitle = call.phase ?? call.cursor
507
582
  const phase = phaseTitle?.trim() ? announce(phaseTitle, false) : undefined
508
583
  const queuedAt = now()
@@ -521,13 +596,26 @@ export class WorkflowRuntime {
521
596
  toolCalls: 0,
522
597
  skipped: false,
523
598
  userSkipped: false,
599
+ key,
524
600
  }
525
601
  agents.set(index, agent)
602
+ if (hit !== undefined) {
603
+ agent.cached = {
604
+ pass: hit.pass,
605
+ index: hit.index,
606
+ ...(hit.childSessionId !== undefined ? { childSessionId: hit.childSessionId } : {}),
607
+ }
608
+ if (hit.childSessionId !== undefined) agent.childSessionId = hit.childSessionId
609
+ agent.startedAt = queuedAt
610
+ }
526
611
  emit(agentEvent(agent))
527
612
  journal({
528
613
  type: 'agent_request',
529
614
  at: queuedAt,
530
615
  index,
616
+ key,
617
+ pass,
618
+ ...(hit !== undefined ? { cached: true as const } : {}),
531
619
  label: agent.label,
532
620
  ...(phase ? { phaseIndex: phase.index, phaseTitle: phase.title } : {}),
533
621
  promptPreview: preview(call.prompt),
@@ -540,6 +628,12 @@ export class WorkflowRuntime {
540
628
  },
541
629
  })
542
630
 
631
+ if (hit !== undefined) {
632
+ return settle(agent, {
633
+ state: 'done',
634
+ value: structuredClone(hit.value),
635
+ })
636
+ }
543
637
  const admitted = await acquire(agent)
544
638
  if (!admitted) {
545
639
  return agent.userSkipped
@@ -547,6 +641,7 @@ export class WorkflowRuntime {
547
641
  : settle(agent, {
548
642
  state: 'error',
549
643
  error: 'The workflow was stopped before this agent started.',
644
+ interrupted: true,
550
645
  })
551
646
  }
552
647
  try {
@@ -554,6 +649,7 @@ export class WorkflowRuntime {
554
649
  return settle(agent, {
555
650
  state: 'error',
556
651
  error: `Not started: the token budget for this workflow (${limits.tokenBudget} tokens) is spent.`,
652
+ interrupted: true,
557
653
  })
558
654
  }
559
655
  if (controller.signal.aborted || agent.userSkipped) {
@@ -562,6 +658,7 @@ export class WorkflowRuntime {
562
658
  : settle(agent, {
563
659
  state: 'error',
564
660
  error: 'The workflow was stopped before this agent started.',
661
+ interrupted: true,
565
662
  })
566
663
  }
567
664
  agent.startedAt = now()
@@ -630,7 +727,7 @@ export class WorkflowRuntime {
630
727
  // --- start ---
631
728
  if (options.journal?.writeScript) {
632
729
  try {
633
- await options.journal.writeScript(options.script, sha256(options.script))
730
+ await options.journal.writeScript(options.script, sha256(options.script), pass)
634
731
  } catch (error) {
635
732
  journalError = error instanceof Error ? error.message : String(error)
636
733
  }
@@ -644,6 +741,17 @@ export class WorkflowRuntime {
644
741
  meta,
645
742
  args,
646
743
  limits,
744
+ ...(resume !== undefined
745
+ ? {
746
+ resume: {
747
+ pass,
748
+ previousFinish: resume.previousFinish,
749
+ scriptSha256Changed:
750
+ resume.scriptSha256 !== undefined && resume.scriptSha256 !== sha256(options.script),
751
+ },
752
+ }
753
+ : {}),
754
+ ...(options.scriptPath !== undefined ? { scriptPath: options.scriptPath } : {}),
647
755
  })
648
756
  emit({
649
757
  type: 'workflow_started',
@@ -665,7 +773,11 @@ export class WorkflowRuntime {
665
773
  maxPendingHostCalls: limits.maxAgents + 100,
666
774
  })
667
775
 
668
- const preludeConfig = { args, tokenLimit: limits.tokenBudget ?? null }
776
+ const preludeConfig = {
777
+ args,
778
+ tokenLimit: limits.tokenBudget ?? null,
779
+ deterministic: options.deterministic === true,
780
+ }
669
781
  const runResult = await codeRuntime.run({
670
782
  source: `${buildPrelude(preludeConfig)}\n${body}`,
671
783
  allowedCalls: [WORKFLOW_HOST_CALL],
@@ -676,7 +788,10 @@ export class WorkflowRuntime {
676
788
  onOutput,
677
789
  onHostCall: (request) => {
678
790
  if (request.name !== WORKFLOW_HOST_CALL) {
679
- return Promise.resolve({ ok: false, error: `Unknown host call "${request.name}".` })
791
+ return Promise.resolve({
792
+ ok: false,
793
+ error: `Unknown host call "${request.name}".`,
794
+ })
680
795
  }
681
796
  let call: AgentCall
682
797
  try {
@@ -738,7 +853,7 @@ export class WorkflowRuntime {
738
853
  const progress =
739
854
  begun.length === 0
740
855
  ? 'No agent had started.'
741
- : `Before it failed: ${begun.length} agent${begun.length === 1 ? '' : 's'} started, ${finished} finished, ${failedAgents} failed. A rerun starts the whole script again, so finished agents run again.`
856
+ : `Before it failed: ${begun.length} agent${begun.length === 1 ? '' : 's'} started, ${finished} finished, ${failedAgents} failed. ${options.canResume === true ? 'Resume the run to continue: agents whose prompt and options are unchanged return their saved results.' : 'A rerun starts the whole script again, so finished agents run again.'}`
742
857
  error = `${describeScriptFailure(outcome, body, preludeLineCount(preludeConfig))}\n${progress}${scriptPhase !== undefined ? ` Last phase: "${scriptPhase}".` : ''}`
743
858
  }
744
859
 
@@ -751,6 +866,9 @@ export class WorkflowRuntime {
751
866
  tokens: all.reduce((sum, a) => sum + a.tokens, 0),
752
867
  toolCalls: all.reduce((sum, a) => sum + a.toolCalls, 0),
753
868
  elapsedMs: Math.max(0, now() - startedAt),
869
+ ...(all.some((a) => a.cached !== undefined)
870
+ ? { cachedAgents: all.filter((a) => a.cached !== undefined).length }
871
+ : {}),
754
872
  }
755
873
  const at = now()
756
874
  emit({
@@ -53,6 +53,8 @@ export interface WorkflowAgentOutcome {
53
53
  readonly tokens: number
54
54
  readonly toolCalls: number
55
55
  readonly error?: string
56
+ /** The id of the child session the host ran this agent in, recorded in the journal. */
57
+ readonly sessionId?: string
56
58
  }
57
59
 
58
60
  /**
@@ -113,6 +115,8 @@ export interface WorkflowAgentEvent {
113
115
  readonly resultPreview?: string
114
116
  readonly error?: string
115
117
  readonly skipped?: boolean
118
+ /** True when the agent did not run: a resumed run returned the result it saved earlier. */
119
+ readonly cached?: boolean
116
120
  }
117
121
 
118
122
  export interface WorkflowLogEvent {
@@ -133,6 +137,8 @@ export interface WorkflowUsage {
133
137
  readonly tokens: number
134
138
  readonly toolCalls: number
135
139
  readonly elapsedMs: number
140
+ /** Agents that returned a result saved by an earlier pass instead of running (a subset of `agentsDone`). */
141
+ readonly cachedAgents?: number
136
142
  }
137
143
 
138
144
  export interface WorkflowFinishedEvent {
@@ -177,6 +183,14 @@ export type WorkflowJournalRecord =
177
183
  readonly meta: WorkflowMeta
178
184
  readonly args: unknown
179
185
  readonly limits: WorkflowResolvedLimits
186
+ /** Present on every pass after the first; a resumed run keeps its run id and journal. */
187
+ readonly resume?: {
188
+ readonly pass: number
189
+ readonly previousFinish: WorkflowFinishStatus | 'unfinished'
190
+ readonly scriptSha256Changed: boolean
191
+ }
192
+ /** Where the script came from when the host read it from a file. */
193
+ readonly scriptPath?: string
180
194
  }
181
195
  | {
182
196
  readonly type: 'phase'
@@ -200,6 +214,12 @@ export type WorkflowJournalRecord =
200
214
  readonly effort?: string
201
215
  readonly schema: boolean
202
216
  }
217
+ /** Identifies the call across passes; see `workflowCallKey`. */
218
+ readonly key?: string
219
+ /** 1 for the first pass. A record from an older pass than the latest `run_started` is stale. */
220
+ readonly pass?: number
221
+ /** The agent did not run; its result came from an earlier pass. */
222
+ readonly cached?: true
203
223
  }
204
224
  | {
205
225
  readonly type: 'agent_outcome'
@@ -213,6 +233,18 @@ export type WorkflowJournalRecord =
213
233
  readonly finishedAt: number
214
234
  readonly resultPreview?: string
215
235
  readonly error?: string
236
+ readonly key?: string
237
+ readonly pass?: number
238
+ readonly cached?: true
239
+ /** The agent's full value (after schema validation); present only for a finished, non-null result. */
240
+ readonly result?: unknown
241
+ /** A value existed but could not be saved; a resume runs this agent again. */
242
+ readonly unsaved?: true
243
+ /** The run or the agent was stopped before the agent finished; a resume runs it again and keeps its saved results valid. */
244
+ readonly interrupted?: true
245
+ /** Where a cached result was first recorded. */
246
+ readonly resultFrom?: { readonly pass: number; readonly index: number }
247
+ readonly childSessionId?: string
216
248
  }
217
249
  | { readonly type: 'log'; readonly at: number; readonly message: string }
218
250
  | {
@@ -222,12 +254,14 @@ export type WorkflowJournalRecord =
222
254
  readonly result?: unknown
223
255
  readonly error?: string
224
256
  readonly usage: WorkflowUsage
257
+ /** Written by a later session for a run whose process ended without finishing it. */
258
+ readonly recovered?: true
225
259
  }
226
260
 
227
261
  /** Where a run's audit trail goes. Injected so the SDK stays host-agnostic. */
228
262
  export interface WorkflowJournal {
229
263
  /** Called once, before the first record, with the exact script text. */
230
- writeScript?(source: string, sha256: string): Promise<void> | void
264
+ writeScript?(source: string, sha256: string, pass?: number): Promise<void> | void
231
265
  append(record: WorkflowJournalRecord): Promise<void> | void
232
266
  }
233
267
 
@@ -283,6 +317,80 @@ export interface WorkflowRunOptions {
283
317
  readonly onControl?: (control: WorkflowControl) => void
284
318
  /** Default: a fresh `w` + 8 base36 id. */
285
319
  readonly runId?: string
320
+ /**
321
+ * Continue an earlier run: agents whose prompt and options are unchanged
322
+ * return the result saved in the journal instead of running. Build it with
323
+ * `loadWorkflowResume`, and pass the same `runId`.
324
+ */
325
+ readonly resume?: WorkflowResumeState
326
+ /**
327
+ * Make the script repeatable, which resuming needs: `Date.now()`,
328
+ * `new Date()` with no argument and `Math.random()` throw, and `eval` is
329
+ * removed. Default false.
330
+ */
331
+ readonly deterministic?: boolean
332
+ /** Say in a failure that the run can be resumed. Default false. */
333
+ readonly canResume?: boolean
334
+ /** Recorded in `run_started` when the host read the script from a file. */
335
+ readonly scriptPath?: string
336
+ }
337
+
338
+ /** Everything a resumed pass needs from the earlier passes of one run. */
339
+ export interface WorkflowResumeState {
340
+ /** The number of the pass about to run (2 for the first resume). */
341
+ readonly pass: number
342
+ readonly results: ReadonlyMap<
343
+ string,
344
+ {
345
+ readonly value: unknown
346
+ readonly pass: number
347
+ readonly index: number
348
+ readonly childSessionId?: string
349
+ }
350
+ >
351
+ /** Keys the earlier passes asked for, finished or not. */
352
+ readonly seen: ReadonlySet<string>
353
+ /** Keys whose last outcome was a failure or a skip. */
354
+ readonly failed: ReadonlySet<string>
355
+ /** Keys that were asked for again after a result was saved and never finished since. */
356
+ readonly demoted: ReadonlySet<string>
357
+ readonly scriptSha256?: string
358
+ readonly previousFinish: WorkflowFinishStatus | 'unfinished'
359
+ /** Plain-language findings about the journal (changed, cut short, saved before resuming existed). */
360
+ readonly problems: readonly string[]
361
+ readonly resumable: boolean
362
+ /** First line (1-based) the integrity check could not trust, when any. */
363
+ readonly brokenAt?: number
364
+ }
365
+
366
+ /** A journal that can be read back, with its integrity checked. */
367
+ export interface WorkflowJournalReader {
368
+ read(): Promise<CheckedWorkflowJournal>
369
+ }
370
+
371
+ /** A journal record as written by `FileWorkflowJournal`: numbered and chained to the line before it. */
372
+ export type ChainedWorkflowJournalRecord = WorkflowJournalRecord & {
373
+ readonly seq: number
374
+ readonly prev: string
375
+ }
376
+
377
+ /** What reading a journal found. Only `records` can be trusted. */
378
+ export interface CheckedWorkflowJournal {
379
+ /** Records that passed the integrity check, in order. */
380
+ readonly records: readonly ChainedWorkflowJournalRecord[]
381
+ /** The journal was written before records were chained; it can be shown but not resumed. */
382
+ readonly legacy: boolean
383
+ /** The last write was cut off mid-line and was ignored. */
384
+ readonly tornTail: boolean
385
+ /** First line (1-based) that failed the check, when lines were changed, removed or cut short. */
386
+ readonly brokenAt?: number
387
+ /** Sequence number and hash the next record must chain from. */
388
+ readonly lastSeq: number
389
+ readonly lastHash: string
390
+ /** The file ends in a newline (false after a torn write). */
391
+ readonly endsWithNewline: boolean
392
+ /** Records of a legacy journal, for display only. */
393
+ readonly untrusted: readonly WorkflowJournalRecord[]
286
394
  }
287
395
 
288
396
  export interface WorkflowRunResult {
package/src/index.ts CHANGED
@@ -41,10 +41,27 @@ export type {
41
41
  MonitorStreamOptions,
42
42
  MonitorStreamStats,
43
43
  } from './runtime/jobs/monitor-stream.js'
44
+ export { WakeBudget, wakeBudgetFromEnv } from './runtime/jobs/wake-budget.js'
44
45
  export {
45
46
  createBackgroundJobId,
46
47
  isBackgroundJobId,
47
48
  } from './runtime/jobs/job-id.js'
49
+ // Three facts about a running item (where it is, when it last progressed,
50
+ // whether it is alive) and the one verdict derived from them.
51
+ export {
52
+ LIVENESS_QUIET_MS,
53
+ LIVENESS_STALLED_MS,
54
+ formatLivenessAge,
55
+ formatLivenessLine,
56
+ judgeLiveness,
57
+ } from './runtime/jobs/liveness.js'
58
+ export type {
59
+ Liveness,
60
+ LivenessInput,
61
+ LivenessKind,
62
+ LivenessThresholds,
63
+ LivenessVerdict,
64
+ } from './runtime/jobs/liveness.js'
48
65
  export type {
49
66
  BackgroundJob,
50
67
  ForegroundCommand,
@@ -130,6 +130,7 @@ const TURN_BOUND_EVENT_TYPES: ReadonlySet<SessionEventType> = new Set<SessionEve
130
130
  'hosted_tool',
131
131
  'provider_retry',
132
132
  'provider_fallback',
133
+ 'model_refusal',
133
134
  'tool_completed',
134
135
  'user_question_asked',
135
136
  'user_question_answered',
@@ -0,0 +1,173 @@
1
+ import type { PluginManifest } from '../types/plugin/index.js'
2
+
3
+ /**
4
+ * Whether a plugin can run on this machine and this runtime, decided from what
5
+ * its manifest declares. A manifest that declares nothing is compatible with
6
+ * everything, so every plugin written before these fields existed loads as it did.
7
+ */
8
+
9
+ type Triple = readonly [number, number, number]
10
+
11
+ interface Parsed {
12
+ readonly triple: Triple
13
+ /** The prerelease tag (`rc.1`), or undefined for a release. */
14
+ readonly pre: string | undefined
15
+ /** How many numeric parts were written: `1` is 1, `1.4` is 2, `1.4.2` is 3. */
16
+ readonly written: number
17
+ }
18
+
19
+ const VERSION_PATTERN =
20
+ /^v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/i
21
+
22
+ function parseVersion(text: string): Parsed | undefined {
23
+ const match = VERSION_PATTERN.exec(text.trim())
24
+ if (!match) return undefined
25
+ const part = (value: string | undefined): number =>
26
+ value === undefined || value === 'x' || value === '*' ? 0 : Number(value)
27
+ const written = [match[1], match[2], match[3]].filter(
28
+ (p) => p !== undefined && p !== 'x' && p !== '*',
29
+ ).length
30
+ return {
31
+ triple: [Number(match[1]), part(match[2]), part(match[3])],
32
+ pre: match[4],
33
+ written,
34
+ }
35
+ }
36
+
37
+ function compareTriples(a: Triple, b: Triple): number {
38
+ for (let i = 0; i < 3; i++) {
39
+ const diff = (a[i] as number) - (b[i] as number)
40
+ if (diff !== 0) return diff
41
+ }
42
+ return 0
43
+ }
44
+
45
+ /** Full precedence: a release is above any prerelease of the same numbers. */
46
+ function compare(a: Parsed, b: Parsed): number {
47
+ const order = compareTriples(a.triple, b.triple)
48
+ if (order !== 0) return order
49
+ if (a.pre === b.pre) return 0
50
+ if (a.pre === undefined) return 1
51
+ if (b.pre === undefined) return -1
52
+ return a.pre < b.pre ? -1 : 1
53
+ }
54
+
55
+ /**
56
+ * The first version a `^` or `~` range excludes, from how many parts were
57
+ * written. `^1` and `~1` stop below 2.0.0; `~1.4` and `~1.4.2` below 1.5.0;
58
+ * `^0` below 1.0.0, `^0.2` below 0.3.0, `^0.0.3` below 0.0.4.
59
+ */
60
+ function upperBound(operator: '^' | '~', target: Parsed): Triple {
61
+ const [major, minor, patch] = target.triple
62
+ if (operator === '~') return target.written <= 1 ? [major + 1, 0, 0] : [major, minor + 1, 0]
63
+ if (major > 0 || target.written <= 1) return [major + 1, 0, 0]
64
+ if (minor > 0 || target.written === 2) return [0, minor + 1, 0]
65
+ return [0, 0, patch + 1]
66
+ }
67
+
68
+ /** One comparator such as `>=1.2.0`, `^2`, `~1.4` or `3.1.0`; undefined when unreadable. */
69
+ function satisfiesComparator(version: Parsed, token: string): boolean | undefined {
70
+ const op = /^(>=|<=|>|<|=|\^|~)?\s*(.+)$/.exec(token)
71
+ if (!op) return undefined
72
+ const operator = op[1] ?? '='
73
+ const target = parseVersion(op[2] as string)
74
+ if (!target) return undefined
75
+ const order = compare(version, target)
76
+ switch (operator) {
77
+ case '>=':
78
+ return order >= 0
79
+ case '<=':
80
+ return order <= 0
81
+ case '>':
82
+ return order > 0
83
+ case '<':
84
+ return order < 0
85
+ case '^':
86
+ case '~':
87
+ // The upper bound is compared on the numbers alone, so a prerelease of the
88
+ // next major (`2.0.0-rc.1`) is not inside `^1`.
89
+ return order >= 0 && compareTriples(version.triple, upperBound(operator, target)) < 0
90
+ default: {
91
+ // A bare `1` or `1.4` names a whole major or minor.
92
+ if (version.pre !== target.pre) return false
93
+ const written = (op[2] as string).replace(/^v/i, '').split(/[-+]/)[0] as string
94
+ const parts = written.split('.').filter((p) => p !== 'x' && p !== '*')
95
+ return parts.every((p, i) => Number(p) === version.triple[i])
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Whether `version` satisfies `range`. Supports space-separated comparators
102
+ * that must all hold, `||` between alternatives, and the operators `>=`, `>`,
103
+ * `<=`, `<`, `=`, `^` and `~`. Returns undefined when the range cannot be read.
104
+ */
105
+ export function versionSatisfies(versionText: string, range: string): boolean | undefined {
106
+ const version = parseVersion(versionText)
107
+ if (!version) return undefined
108
+ let readable = true
109
+ let any = false
110
+ for (const alternative of range.split('||')) {
111
+ // Join an operator written apart from its number (">= 1.2") first.
112
+ const tokens = alternative
113
+ .trim()
114
+ .replace(/(>=|<=|>|<|=|\^|~)\s+/g, '$1')
115
+ .split(/\s+/)
116
+ .filter(Boolean)
117
+ if (tokens.length === 0) {
118
+ readable = false
119
+ continue
120
+ }
121
+ let all = true
122
+ for (const token of tokens) {
123
+ const ok = satisfiesComparator(version, token)
124
+ if (ok === undefined) {
125
+ readable = false
126
+ all = false
127
+ break
128
+ }
129
+ if (!ok) all = false
130
+ }
131
+ if (all) any = true
132
+ }
133
+ if (any) return true
134
+ return readable ? false : undefined
135
+ }
136
+
137
+ export interface PluginHostFacts {
138
+ /** The operating system, as `process.platform` names it. Default: this process. */
139
+ readonly platform?: string
140
+ /** The Namzu runtime version. Default: this package's own version. */
141
+ readonly namzuVersion?: string
142
+ }
143
+
144
+ /**
145
+ * Refuse a plugin that declares it does not support this platform or this
146
+ * Namzu version. The reason names the plugin, what it declares and what this
147
+ * machine is, so a person can act on it without reading the manifest.
148
+ *
149
+ * A runtime whose own version is unknown (`0.0.0`, the placeholder a bundle
150
+ * reports when it cannot read its package file) is not held against the range:
151
+ * refusing every plugin on a guess would be worse than admitting one.
152
+ */
153
+ export function assertCompatible(manifest: PluginManifest, host: Required<PluginHostFacts>): void {
154
+ const platforms = manifest.platforms
155
+ if (platforms && platforms.length > 0 && !platforms.includes(host.platform)) {
156
+ throw new Error(
157
+ `Plugin "${manifest.name}" supports only ${platforms.join(', ')}, and this machine runs ${host.platform}, so it was not loaded.`,
158
+ )
159
+ }
160
+ const range = manifest.engines?.namzu
161
+ if (range === undefined || host.namzuVersion === '0.0.0') return
162
+ const ok = versionSatisfies(host.namzuVersion, range)
163
+ if (ok === undefined) {
164
+ throw new Error(
165
+ `Plugin "${manifest.name}" declares the Namzu version range "${range}", which cannot be read, so it was not loaded. Write it like ">=60.0.0" or "^60.1.0".`,
166
+ )
167
+ }
168
+ if (!ok) {
169
+ throw new Error(
170
+ `Plugin "${manifest.name}" needs Namzu ${range}, and this is Namzu ${host.namzuVersion}, so it was not loaded. Update Namzu or use a plugin version made for this one.`,
171
+ )
172
+ }
173
+ }