@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
@@ -0,0 +1,144 @@
1
+ /**
2
+ * What a host can say about one running item, from three facts it reads
3
+ * itself: where it is, when it last made progress, and whether it is alive.
4
+ *
5
+ * Pure: no clock, no process probe. The caller passes `now` and the probe's
6
+ * answer, so the same table of cases runs under a fake clock. The kinds
7
+ * (background job, monitor, delegated agent, workflow run) share one verdict
8
+ * so the outstanding-work note and every status read print the same words.
9
+ */
10
+
11
+ /** Silence shorter than this still reads as progress. */
12
+ export const LIVENESS_QUIET_MS = 30_000
13
+
14
+ /**
15
+ * Silence from an item that is alive and has no known reason to wait. The
16
+ * same number `wait_for_job` uses for its idle bound, so there is one
17
+ * threshold to explain.
18
+ */
19
+ export const LIVENESS_STALLED_MS = 2 * 60 * 1000
20
+
21
+ export type LivenessKind = 'job' | 'monitor' | 'agent' | 'workflow'
22
+
23
+ export type LivenessVerdict = 'progressing' | 'waiting' | 'stalled' | 'dead' | 'finished'
24
+
25
+ export interface LivenessInput {
26
+ readonly kind: LivenessKind
27
+ readonly id: string
28
+ /** Short name the person would recognise: a command, a description, a workflow name. */
29
+ readonly label: string
30
+ /** Where it is: the last output line, the current tool, the phase. One line, already bounded. */
31
+ readonly where?: string
32
+ readonly startedAt: number
33
+ /** When it last made progress (epoch ms). Absent means it has not yet: the start counts. */
34
+ readonly lastProgressAt?: number
35
+ /** The probe's answer: is the process or run still there. */
36
+ readonly alive: boolean
37
+ /** A result or an end has arrived. */
38
+ readonly finished?: boolean
39
+ /** What it is known to be waiting on, when it is waiting for a reason (a tool, a free slot). */
40
+ readonly waitingOn?: string
41
+ readonly waitingSince?: number
42
+ }
43
+
44
+ export interface Liveness {
45
+ readonly kind: LivenessKind
46
+ readonly id: string
47
+ readonly label: string
48
+ readonly where?: string
49
+ readonly verdict: LivenessVerdict
50
+ /** Whole seconds since the last progress (or the start). */
51
+ readonly idleSeconds: number
52
+ /** Whole seconds since it started. */
53
+ readonly ageSeconds: number
54
+ /** Present when the verdict is `waiting` with a reason. */
55
+ readonly waitingOn?: string
56
+ readonly waitingSeconds?: number
57
+ }
58
+
59
+ export interface LivenessThresholds {
60
+ readonly quietMs?: number
61
+ readonly stalledMs?: number
62
+ }
63
+
64
+ const seconds = (ms: number): number => Math.max(0, Math.floor(ms / 1000))
65
+
66
+ /** Decide the verdict. Order matters: a result beats everything, then death, then the reason to wait. */
67
+ export function judgeLiveness(
68
+ input: LivenessInput,
69
+ now: number,
70
+ thresholds: LivenessThresholds = {},
71
+ ): Liveness {
72
+ const quietMs = thresholds.quietMs ?? LIVENESS_QUIET_MS
73
+ const stalledMs = thresholds.stalledMs ?? LIVENESS_STALLED_MS
74
+ const last = Math.max(input.startedAt, input.lastProgressAt ?? input.startedAt)
75
+ const idle = Math.max(0, now - last)
76
+ let verdict: LivenessVerdict
77
+ if (input.finished) verdict = 'finished'
78
+ else if (!input.alive) verdict = 'dead'
79
+ else if (input.waitingOn !== undefined) verdict = 'waiting'
80
+ else if (idle >= stalledMs) verdict = 'stalled'
81
+ else if (idle >= quietMs) verdict = 'waiting'
82
+ else verdict = 'progressing'
83
+ const waitingOn =
84
+ verdict === 'waiting' && input.waitingOn !== undefined ? input.waitingOn : undefined
85
+ const waitingSince = input.waitingSince ?? last
86
+ return {
87
+ kind: input.kind,
88
+ id: input.id,
89
+ label: input.label,
90
+ ...(input.where !== undefined ? { where: input.where } : {}),
91
+ verdict,
92
+ idleSeconds: seconds(idle),
93
+ ageSeconds: seconds(now - input.startedAt),
94
+ ...(waitingOn !== undefined ? { waitingOn, waitingSeconds: seconds(now - waitingSince) } : {}),
95
+ }
96
+ }
97
+
98
+ /** `45s`, `3m 05s`, `1h 02m`: short, for a line a person or the model reads. */
99
+ export function formatLivenessAge(totalSeconds: number): string {
100
+ const s = Math.max(0, Math.floor(totalSeconds))
101
+ if (s < 60) return `${s}s`
102
+ const m = Math.floor(s / 60)
103
+ if (m < 60) return `${m}m ${String(s % 60).padStart(2, '0')}s`
104
+ return `${Math.floor(m / 60)}h ${String(m % 60).padStart(2, '0')}m`
105
+ }
106
+
107
+ function oneLine(text: string, max: number): string {
108
+ const flat = text.replace(/\s+/g, ' ').trim()
109
+ return flat.length <= max ? flat : `${flat.slice(0, max - 1)}…`
110
+ }
111
+
112
+ const LABEL_MAX = 80
113
+ const WHERE_MAX = 120
114
+
115
+ /**
116
+ * One line per item, the same for the note and for the list reads:
117
+ * `job b1a2 "npm test" - progressing - last progress 4s ago - at: compiling`.
118
+ * Everything free-form is clipped and flattened; the line carries data, not
119
+ * instructions.
120
+ */
121
+ export function formatLivenessLine(item: Liveness): string {
122
+ const parts = [`${item.kind} ${item.id} "${oneLine(item.label, LABEL_MAX)}"`]
123
+ if (item.verdict === 'waiting' && item.waitingOn !== undefined) {
124
+ parts.push(
125
+ `waiting on ${oneLine(item.waitingOn, LABEL_MAX)} for ${formatLivenessAge(item.waitingSeconds ?? item.idleSeconds)}`,
126
+ )
127
+ } else if (item.verdict === 'finished') {
128
+ parts.push(`finished after ${formatLivenessAge(item.ageSeconds)}`)
129
+ } else if (item.verdict === 'dead') {
130
+ parts.push(
131
+ `dead: gone without a result after ${formatLivenessAge(item.ageSeconds)}, last progress ${formatLivenessAge(item.idleSeconds)} ago`,
132
+ )
133
+ } else {
134
+ parts.push(item.verdict)
135
+ parts.push(
136
+ item.idleSeconds === 0
137
+ ? 'last progress just now'
138
+ : `last progress ${formatLivenessAge(item.idleSeconds)} ago`,
139
+ )
140
+ }
141
+ if (item.where !== undefined && item.where.length > 0)
142
+ parts.push(`at: ${oneLine(item.where, WHERE_MAX)}`)
143
+ return parts.join(' - ')
144
+ }
@@ -1,7 +1,7 @@
1
1
  import type { spawn } from 'node:child_process'
2
2
 
3
3
  import { SANDBOX_KILL_GRACE_MS } from '../../constants/sandbox/index.js'
4
- import { killTree } from '../../process/kill-tree.js'
4
+ import { isSignalablePid, killTree } from '../../process/kill-tree.js'
5
5
  import { describeSpawnFailure, spawnHostShell } from '../../tools/command-shell.js'
6
6
  import { scrubInheritedEnv } from '../../tools/env-scrub.js'
7
7
  import { createHostOutputDecoder } from '../../tools/host-output.js'
@@ -12,6 +12,7 @@ import type {
12
12
  import type { MonitorWebSocketTarget } from '../../types/tool/index.js'
13
13
  import { subscribeToAbort } from '../../utils/abort.js'
14
14
  import { oneMetadataLine } from '../system-events.js'
15
+ import { stopAtHostExit } from './host-exit-finalizer.js'
15
16
  import { createBackgroundJobId } from './job-id.js'
16
17
  import {
17
18
  MONITOR_TIMEOUT_CAP_MS,
@@ -53,6 +54,10 @@ export interface BackgroundJob {
53
54
  readonly command: string
54
55
  readonly status: BackgroundJobStatus
55
56
  readonly startedAt: number
57
+ /** When the job last wrote output (epoch ms). Absent until it has written anything. */
58
+ readonly lastOutputAt?: number
59
+ /** The last non-empty output line, flattened and clipped to {@link LAST_OUTPUT_LINE_MAX}. */
60
+ readonly lastOutputLine?: string
56
61
  readonly exitedAt?: number
57
62
  readonly exitCode?: number
58
63
  readonly signal?: string
@@ -347,7 +352,7 @@ interface MonitorState {
347
352
 
348
353
  /** Whether any process of the group led by `pid` is still alive. */
349
354
  function groupAlive(pid: number | undefined): boolean {
350
- if (pid === undefined || process.platform === 'win32') return false
355
+ if (!isSignalablePid(pid) || process.platform === 'win32') return false
351
356
  try {
352
357
  process.kill(-pid, 0)
353
358
  return true
@@ -356,8 +361,67 @@ function groupAlive(pid: number | undefined): boolean {
356
361
  }
357
362
  }
358
363
 
364
+ /**
365
+ * The same probe for a status read: a group that exists but is not ours to
366
+ * signal (EPERM) is there. The close handler keeps {@link groupAlive}, which
367
+ * must not wait on a group it can never see end.
368
+ *
369
+ * Not proof the group is still the job's: between the group ending and the
370
+ * record closing, a recycled pid that leads a new group would read as alive.
371
+ * The close handler polls every {@link GROUP_POLL_MS}, so the window is
372
+ * that small.
373
+ */
374
+ function groupPresent(pid: number): boolean {
375
+ if (!isSignalablePid(pid)) return false
376
+ try {
377
+ process.kill(-pid, 0)
378
+ return true
379
+ } catch (error) {
380
+ return (error as NodeJS.ErrnoException).code === 'EPERM'
381
+ }
382
+ }
383
+
359
384
  const GROUP_POLL_MS = 250
360
385
 
386
+ /** Longest `lastOutputLine` kept on a job record. */
387
+ export const LAST_OUTPUT_LINE_MAX = 160
388
+
389
+ /**
390
+ * Drop terminal control sequences and control characters, keeping one space
391
+ * where a control character stood. Done by code point so no pattern has to
392
+ * spell a control character.
393
+ */
394
+ function plainLine(raw: string): string {
395
+ let out = ''
396
+ let i = 0
397
+ while (i < raw.length) {
398
+ const code = raw.charCodeAt(i)
399
+ if (code === 0x1b && raw[i + 1] === '[') {
400
+ // CSI: parameters and intermediates, then one final byte in @..~.
401
+ i += 2
402
+ while (i < raw.length && raw.charCodeAt(i) >= 0x20 && raw.charCodeAt(i) <= 0x3f) i += 1
403
+ i += 1
404
+ continue
405
+ }
406
+ out += code < 0x20 || code === 0x7f ? ' ' : (raw[i] ?? '')
407
+ i += 1
408
+ }
409
+ return out.replace(/ {2,}/g, ' ').trim()
410
+ }
411
+
412
+ /** The last non-empty line of `text`, flattened and clipped; `undefined` when it has none. */
413
+ function lastLineOf(text: string): string | undefined {
414
+ const lines = text.split('\n')
415
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
416
+ const flat = plainLine(lines[index] ?? '')
417
+ if (flat.length > 0)
418
+ return flat.length <= LAST_OUTPUT_LINE_MAX
419
+ ? flat
420
+ : `${flat.slice(0, LAST_OUTPUT_LINE_MAX - 1)}…`
421
+ }
422
+ return undefined
423
+ }
424
+
361
425
  /**
362
426
  * Why a command that was running in the foreground moved to the background:
363
427
  * the person asked (`user`), or the person sent a message that has to reach
@@ -708,6 +772,7 @@ export class BackgroundJobRegistry {
708
772
  const finalize = (code: number | null, signal: NodeJS.Signals | null): void => {
709
773
  if (finalized) return
710
774
  finalized = true
775
+ untrackAtHostExit()
711
776
  entry.record = {
712
777
  ...entry.record,
713
778
  // A job killed by this registry says `killed`, not
@@ -788,11 +853,26 @@ export class BackgroundJobRegistry {
788
853
  }),
789
854
  }
790
855
  entry.finalize = finalizeEntry
856
+ // A job that is not stopped before the host exits is an orphan: nothing
857
+ // owns its id any more. A remote job confirms its own termination and is
858
+ // not signalled from an exit listener.
859
+ const untrackAtHostExit = entry.terminateProcess
860
+ ? () => {}
861
+ : stopAtHostExit({
862
+ child,
863
+ ...(entry.killProcess ? { kill: entry.killProcess } : {}),
864
+ })
791
865
 
792
866
  const append = (text: string, stream: 'stdout' | 'stderr'): void => {
793
867
  const chunk: OutputChunk = { stream, offset: entry.produced, bytes: Buffer.from(text) }
794
868
  if (chunk.bytes.length === 0) return
795
869
  entry.produced += chunk.bytes.length
870
+ const line = lastLineOf(text)
871
+ entry.record = {
872
+ ...entry.record,
873
+ lastOutputAt: Date.now(),
874
+ ...(line !== undefined ? { lastOutputLine: line } : {}),
875
+ }
796
876
  entry.buffer += text
797
877
  entry.buffer = outputTail(entry.buffer, this.maxBytes).text
798
878
  entry.outputChunks.push(chunk)
@@ -898,6 +978,20 @@ export class BackgroundJobRegistry {
898
978
  return entry.record
899
979
  }
900
980
 
981
+ /**
982
+ * Whether the job's process group is still there, asked of the system rather
983
+ * than inferred from the record. A job with no process of its own to probe
984
+ * (a sandboxed or WebSocket source, or a platform without process groups)
985
+ * is alive exactly when the registry says it is running.
986
+ */
987
+ isAlive(id: string): boolean {
988
+ const entry = this.jobs.get(id)
989
+ if (!entry || entry.record.status !== 'running') return false
990
+ const pid = entry.child.pid
991
+ if (pid === undefined || process.platform === 'win32') return true
992
+ return groupPresent(pid)
993
+ }
994
+
901
995
  /**
902
996
  * Await one job's exit — the public counterpart to `onExit`, for a
903
997
  * caller that wants a single result rather than a standing
@@ -1253,6 +1347,12 @@ export function bindOwner(
1253
1347
  * than present and silently doing nothing.
1254
1348
  */
1255
1349
  readonly onAwaited?: (id: string) => void
1350
+ /**
1351
+ * Be told that a tool result has handed the model this job's exit, so the
1352
+ * notice announcing the same exit can be withdrawn. Like `onAwaited` it
1353
+ * belongs to the turn; absent, `markCollected` is absent from the ref.
1354
+ */
1355
+ readonly onCollected?: (id: string) => void
1256
1356
  /**
1257
1357
  * The longest a monitor started through this binding may live. A host
1258
1358
  * may lower it (a single-shot run has no tomorrow to watch for); the
@@ -1387,11 +1487,23 @@ export function bindOwner(
1387
1487
  },
1388
1488
  }
1389
1489
  : {}),
1490
+ ...(defaults.onCollected
1491
+ ? {
1492
+ markCollected: (id: string) => {
1493
+ mine(id)
1494
+ defaults.onCollected?.(id)
1495
+ },
1496
+ }
1497
+ : {}),
1390
1498
  kill: async (id: string) => {
1391
1499
  mine(id)
1392
1500
  return await registry.kill(id)
1393
1501
  },
1394
1502
  list: () => registry.list(owner),
1503
+ isAlive: (id: string) => {
1504
+ mine(id)
1505
+ return registry.isAlive(id)
1506
+ },
1395
1507
  }
1396
1508
  }
1397
1509
 
@@ -0,0 +1,55 @@
1
+ /**
2
+ * A bound on how many times in a row a session wakes the model on its own.
3
+ *
4
+ * A finished background job wakes an idle session, and the turn that wakes may
5
+ * start another job whose exit wakes it again, with nobody reading. For an
6
+ * owner that nobody is watching, that chain never has to end. The budget caps
7
+ * it: after `max` wakes in a row no further exit wakes the model, and the
8
+ * exits stay pending so the next message from a person carries them.
9
+ *
10
+ * **Off unless asked.** An interactive session has a person to stop it, and a
11
+ * cap there would only get in the way. `max` of `undefined` (or anything that
12
+ * is not a positive integer) never limits.
13
+ *
14
+ * **Only a person resets it.** A wake, a job notice or a tool result is not
15
+ * evidence anyone is looking; a human message is.
16
+ */
17
+ export class WakeBudget {
18
+ private woken = 0
19
+ private readonly max: number | undefined
20
+
21
+ constructor(max?: number) {
22
+ this.max = max !== undefined && Number.isInteger(max) && max > 0 ? max : undefined
23
+ }
24
+
25
+ /** Whether another automatic wake is allowed. */
26
+ get canWake(): boolean {
27
+ return this.max === undefined || this.woken < this.max
28
+ }
29
+
30
+ /** Count one automatic wake. Call when a wake begins a turn. */
31
+ recordWake(): void {
32
+ this.woken++
33
+ }
34
+
35
+ /** A person wrote: the chain is attended again. */
36
+ reset(): void {
37
+ this.woken = 0
38
+ }
39
+
40
+ /** Wakes since the last human message. */
41
+ get consecutiveWakes(): number {
42
+ return this.woken
43
+ }
44
+ }
45
+
46
+ /**
47
+ * The budget a host asked for through `NAMZU_MAX_CONSECUTIVE_WAKES`, or an
48
+ * unlimited one. Anything but a positive integer means no limit.
49
+ */
50
+ export function wakeBudgetFromEnv(
51
+ env: Record<string, string | undefined> = process.env,
52
+ ): WakeBudget {
53
+ const raw = env.NAMZU_MAX_CONSECUTIVE_WAKES?.trim()
54
+ return new WakeBudget(raw && /^\d+$/.test(raw) ? Number(raw) : undefined)
55
+ }
@@ -0,0 +1,135 @@
1
+ import { frameNotice } from '../../types/message/notice-frame.js'
2
+ import type { ToolCallOutcome } from './executor.js'
3
+
4
+ /**
5
+ * Tool calls that were still running when the operator wrote.
6
+ *
7
+ * A model cannot answer while a `tool_use` block has no `tool_result`, and a
8
+ * long call used to hold the operator's question until it finished: the person
9
+ * asked how things stand and heard nothing for as long as the call took. When
10
+ * the operator writes while a call has been running for a while, the batch
11
+ * answers the unfinished calls with a placeholder result, the model reads the
12
+ * message at once, and the call carries on untouched. Its real result reaches
13
+ * the model later as a message of its own that names the call.
14
+ *
15
+ * This holds the bookkeeping for one turn: which calls were handed back this
16
+ * way, and the outcomes that have arrived since.
17
+ */
18
+
19
+ /** How long a call must have been running before an operator message detaches it. */
20
+ export const DETACH_AFTER_MS = 2_000
21
+
22
+ /**
23
+ * What a model reads as the result of a call that is still running.
24
+ *
25
+ * It says the call goes on, what not to do about it, and what to tell the
26
+ * person; the model never sees these words as anything the tool printed.
27
+ */
28
+ export function stillRunningResult(toolName: string, started = true): string {
29
+ if (!started) {
30
+ return [
31
+ `[Waiting to start] The ${toolName} call has not started: it is queued behind an earlier call in this batch. The operator wrote a message while the batch ran, and that message follows so you can answer it now.`,
32
+ 'The call will start by itself when the earlier one ends, and its result will arrive in a later message that names this call id.',
33
+ 'Repeating the call would run the work twice, and waiting or polling for it would delay your answer, so do neither. If they ask, say it has not started yet.',
34
+ ].join('\n')
35
+ }
36
+ return [
37
+ `[Still running] The ${toolName} call has not finished. The operator wrote a message while it ran, and that message follows so you can answer it now.`,
38
+ 'The call was not interrupted. Its result will arrive on its own, in a later message that names this call id.',
39
+ 'Repeating the call would run the work twice, and waiting or polling for it would delay your answer, so do neither.',
40
+ 'You cannot stop it from here, so do not say you cancelled or dropped it. On the operator’s screen it still shows as in progress; if they ask, say it is still running.',
41
+ ].join('\n')
42
+ }
43
+
44
+ /** What a model reads when a call it was told was still running has finished. */
45
+ export function formatLateResults(outcomes: readonly ToolCallOutcome[]): string {
46
+ const parts = outcomes.map(
47
+ (outcome) =>
48
+ `The ${outcome.toolName} call with id ${outcome.toolCallId}, which you were told was still running, has finished${outcome.isError ? ' with an error' : ''}. This is what it returned:\n${outcome.output}`,
49
+ )
50
+ return frameNotice('job', '[Tool call finished]', parts.join('\n\n'))
51
+ }
52
+
53
+ export class DetachedCalls {
54
+ private readonly open = new Map<string, string>()
55
+ private arrived: ToolCallOutcome[] = []
56
+ private readonly waiters = new Set<() => void>()
57
+
58
+ /**
59
+ * `waitForOperator` resolves when the operator wrote and at no other wake: a
60
+ * child's notice or a peer's mail must not hand a call back, because the
61
+ * model would then get a turn with no question to answer.
62
+ */
63
+ constructor(readonly waitForOperator?: (signal: AbortSignal) => Promise<void>) {}
64
+
65
+ /** A call was answered with the placeholder and is still running. */
66
+ begin(toolCallId: string, toolName: string): void {
67
+ this.open.set(toolCallId, toolName)
68
+ }
69
+
70
+ /** The real outcome of a detached call. Ignored for a call that was never detached. */
71
+ settle(outcome: ToolCallOutcome): void {
72
+ if (!this.open.delete(outcome.toolCallId)) return
73
+ this.arrived.push(outcome)
74
+ this.wake()
75
+ }
76
+
77
+ /** True while a call is still running or its outcome has not been read. */
78
+ get hasWork(): boolean {
79
+ return this.open.size > 0 || this.arrived.length > 0
80
+ }
81
+
82
+ /** Calls still running. */
83
+ get running(): number {
84
+ return this.open.size
85
+ }
86
+
87
+ /** The outcomes that have arrived since the last read, in arrival order. */
88
+ take(): readonly ToolCallOutcome[] {
89
+ const taken = this.arrived
90
+ this.arrived = []
91
+ return taken
92
+ }
93
+
94
+ /**
95
+ * Resolves when an outcome is waiting, when nothing is running, after
96
+ * `graceMs`, or rejects when `signal` aborts. Resolving early is never
97
+ * delivery: the caller reads with {@link take}.
98
+ */
99
+ waitForArrival(graceMs: number, signal: AbortSignal): Promise<void> {
100
+ if (this.arrived.length > 0 || this.open.size === 0) return Promise.resolve()
101
+ if (signal.aborted) return Promise.reject(signal.reason)
102
+ return new Promise<void>((resolve, reject) => {
103
+ const done = (): void => {
104
+ clearTimeout(timer)
105
+ this.waiters.delete(wake)
106
+ signal.removeEventListener('abort', abort)
107
+ }
108
+ const wake = (): void => {
109
+ done()
110
+ resolve()
111
+ }
112
+ const abort = (): void => {
113
+ done()
114
+ reject(signal.reason)
115
+ }
116
+ const timer = setTimeout(wake, Math.max(0, graceMs))
117
+ this.waiters.add(wake)
118
+ signal.addEventListener('abort', abort, { once: true })
119
+ })
120
+ }
121
+
122
+ private wake(): void {
123
+ for (const wake of [...this.waiters]) wake()
124
+ }
125
+ }
126
+
127
+ /**
128
+ * What one batch needs to hand its unfinished calls back. `requested` settles
129
+ * once the operator has written and the batch has run for {@link DETACH_AFTER_MS};
130
+ * `calls` receives each call that was handed back and, later, its outcome.
131
+ */
132
+ export interface BatchDetach {
133
+ readonly requested: Promise<void>
134
+ readonly calls: DetachedCalls
135
+ }