@namzu/cli 29.0.1 → 30.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 (413) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/README.md +20 -0
  3. package/dist/browser/commands.d.ts +29 -0
  4. package/dist/browser/commands.d.ts.map +1 -0
  5. package/dist/browser/commands.js +361 -0
  6. package/dist/browser/commands.js.map +1 -0
  7. package/dist/browser/control.d.ts +79 -0
  8. package/dist/browser/control.d.ts.map +1 -0
  9. package/dist/browser/control.js +140 -0
  10. package/dist/browser/control.js.map +1 -0
  11. package/dist/browser/describe.d.ts +12 -0
  12. package/dist/browser/describe.d.ts.map +1 -0
  13. package/dist/browser/describe.js +27 -0
  14. package/dist/browser/describe.js.map +1 -0
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +29 -2
  17. package/dist/cli.js.map +1 -1
  18. package/dist/commands/acp.d.ts.map +1 -1
  19. package/dist/commands/acp.js +1 -0
  20. package/dist/commands/acp.js.map +1 -1
  21. package/dist/commands/browser.d.ts +7 -0
  22. package/dist/commands/browser.d.ts.map +1 -0
  23. package/dist/commands/browser.js +16 -0
  24. package/dist/commands/browser.js.map +1 -0
  25. package/dist/commands/drain.d.ts +2 -1
  26. package/dist/commands/drain.d.ts.map +1 -1
  27. package/dist/commands/drain.js +6 -3
  28. package/dist/commands/drain.js.map +1 -1
  29. package/dist/commands/exec-flags.d.ts +2 -1
  30. package/dist/commands/exec-flags.d.ts.map +1 -1
  31. package/dist/commands/exec-flags.js +6 -3
  32. package/dist/commands/exec-flags.js.map +1 -1
  33. package/dist/commands/exec-json.d.ts.map +1 -1
  34. package/dist/commands/exec-json.js +2 -1
  35. package/dist/commands/exec-json.js.map +1 -1
  36. package/dist/commands/exec.d.ts.map +1 -1
  37. package/dist/commands/exec.js +5 -1
  38. package/dist/commands/exec.js.map +1 -1
  39. package/dist/commands/host-queries.d.ts.map +1 -1
  40. package/dist/commands/host-queries.js +10 -3
  41. package/dist/commands/host-queries.js.map +1 -1
  42. package/dist/commands/schedule.d.ts +9 -0
  43. package/dist/commands/schedule.d.ts.map +1 -0
  44. package/dist/commands/schedule.js +126 -0
  45. package/dist/commands/schedule.js.map +1 -0
  46. package/dist/commands/serve.d.ts +3 -1
  47. package/dist/commands/serve.d.ts.map +1 -1
  48. package/dist/commands/serve.js +5 -3
  49. package/dist/commands/serve.js.map +1 -1
  50. package/dist/commands/skills.d.ts +10 -0
  51. package/dist/commands/skills.d.ts.map +1 -1
  52. package/dist/commands/skills.js +23 -7
  53. package/dist/commands/skills.js.map +1 -1
  54. package/dist/commands/upgrade.d.ts +5 -0
  55. package/dist/commands/upgrade.d.ts.map +1 -1
  56. package/dist/commands/upgrade.js +11 -0
  57. package/dist/commands/upgrade.js.map +1 -1
  58. package/dist/config/load.d.ts +39 -0
  59. package/dist/config/load.d.ts.map +1 -1
  60. package/dist/config/load.js +232 -1
  61. package/dist/config/load.js.map +1 -1
  62. package/dist/config/schema.d.ts +78 -0
  63. package/dist/config/schema.d.ts.map +1 -1
  64. package/dist/config/schema.js.map +1 -1
  65. package/dist/config/user-config.d.ts +23 -0
  66. package/dist/config/user-config.d.ts.map +1 -0
  67. package/dist/config/user-config.js +48 -0
  68. package/dist/config/user-config.js.map +1 -0
  69. package/dist/context/capabilities.d.ts +1 -1
  70. package/dist/context/capabilities.d.ts.map +1 -1
  71. package/dist/context/capabilities.js +1 -0
  72. package/dist/context/capabilities.js.map +1 -1
  73. package/dist/context/environment.d.ts +26 -3
  74. package/dist/context/environment.d.ts.map +1 -1
  75. package/dist/context/environment.js +68 -9
  76. package/dist/context/environment.js.map +1 -1
  77. package/dist/doctor/checks/browser.d.ts +15 -0
  78. package/dist/doctor/checks/browser.d.ts.map +1 -0
  79. package/dist/doctor/checks/browser.js +63 -0
  80. package/dist/doctor/checks/browser.js.map +1 -0
  81. package/dist/doctor/checks/index.d.ts +4 -1
  82. package/dist/doctor/checks/index.d.ts.map +1 -1
  83. package/dist/doctor/checks/index.js +8 -1
  84. package/dist/doctor/checks/index.js.map +1 -1
  85. package/dist/doctor/checks/scheduler.d.ts +10 -0
  86. package/dist/doctor/checks/scheduler.d.ts.map +1 -0
  87. package/dist/doctor/checks/scheduler.js +74 -0
  88. package/dist/doctor/checks/scheduler.js.map +1 -0
  89. package/dist/index.d.ts +1 -1
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +1 -1
  92. package/dist/index.js.map +1 -1
  93. package/dist/integrations/notifications/desktop/freedesktop.d.ts +10 -0
  94. package/dist/integrations/notifications/desktop/freedesktop.d.ts.map +1 -0
  95. package/dist/integrations/notifications/desktop/freedesktop.js +33 -0
  96. package/dist/integrations/notifications/desktop/freedesktop.js.map +1 -0
  97. package/dist/integrations/notifications/desktop/macos.d.ts +6 -0
  98. package/dist/integrations/notifications/desktop/macos.d.ts.map +1 -0
  99. package/dist/integrations/notifications/desktop/macos.js +17 -0
  100. package/dist/integrations/notifications/desktop/macos.js.map +1 -0
  101. package/dist/integrations/notifications/desktop/sanitize.d.ts +16 -0
  102. package/dist/integrations/notifications/desktop/sanitize.d.ts.map +1 -0
  103. package/dist/integrations/notifications/desktop/sanitize.js +49 -0
  104. package/dist/integrations/notifications/desktop/sanitize.js.map +1 -0
  105. package/dist/integrations/notifications/desktop/throttle.d.ts +10 -0
  106. package/dist/integrations/notifications/desktop/throttle.d.ts.map +1 -0
  107. package/dist/integrations/notifications/desktop/throttle.js +39 -0
  108. package/dist/integrations/notifications/desktop/throttle.js.map +1 -0
  109. package/dist/integrations/notifications/desktop/windows-toast.d.ts +23 -0
  110. package/dist/integrations/notifications/desktop/windows-toast.d.ts.map +1 -0
  111. package/dist/integrations/notifications/desktop/windows-toast.js +56 -0
  112. package/dist/integrations/notifications/desktop/windows-toast.js.map +1 -0
  113. package/dist/integrations/notifications/desktop.d.ts +84 -0
  114. package/dist/integrations/notifications/desktop.d.ts.map +1 -0
  115. package/dist/integrations/notifications/desktop.js +199 -0
  116. package/dist/integrations/notifications/desktop.js.map +1 -0
  117. package/dist/integrations/plugins/runtime.d.ts +8 -2
  118. package/dist/integrations/plugins/runtime.d.ts.map +1 -1
  119. package/dist/integrations/plugins/runtime.js +14 -8
  120. package/dist/integrations/plugins/runtime.js.map +1 -1
  121. package/dist/integrations/providers/harness-credentials.d.ts +9 -4
  122. package/dist/integrations/providers/harness-credentials.d.ts.map +1 -1
  123. package/dist/integrations/providers/harness-credentials.js +16 -8
  124. package/dist/integrations/providers/harness-credentials.js.map +1 -1
  125. package/dist/integrations/resident/session-step.d.ts.map +1 -1
  126. package/dist/integrations/resident/session-step.js +1 -0
  127. package/dist/integrations/resident/session-step.js.map +1 -1
  128. package/dist/integrations/subagents/runtime.d.ts +6 -0
  129. package/dist/integrations/subagents/runtime.d.ts.map +1 -1
  130. package/dist/integrations/subagents/runtime.js +14 -5
  131. package/dist/integrations/subagents/runtime.js.map +1 -1
  132. package/dist/integrations/web/open-url.d.ts +24 -0
  133. package/dist/integrations/web/open-url.d.ts.map +1 -0
  134. package/dist/integrations/web/open-url.js +73 -0
  135. package/dist/integrations/web/open-url.js.map +1 -0
  136. package/dist/permissions/browser-sites.d.ts +95 -0
  137. package/dist/permissions/browser-sites.d.ts.map +1 -0
  138. package/dist/permissions/browser-sites.js +270 -0
  139. package/dist/permissions/browser-sites.js.map +1 -0
  140. package/dist/schedule/build.d.ts +86 -0
  141. package/dist/schedule/build.d.ts.map +1 -0
  142. package/dist/schedule/build.js +267 -0
  143. package/dist/schedule/build.js.map +1 -0
  144. package/dist/schedule/changes.d.ts +28 -0
  145. package/dist/schedule/changes.d.ts.map +1 -0
  146. package/dist/schedule/changes.js +65 -0
  147. package/dist/schedule/changes.js.map +1 -0
  148. package/dist/schedule/commands/add.d.ts +24 -0
  149. package/dist/schedule/commands/add.d.ts.map +1 -0
  150. package/dist/schedule/commands/add.js +447 -0
  151. package/dist/schedule/commands/add.js.map +1 -0
  152. package/dist/schedule/commands/args.d.ts +22 -0
  153. package/dist/schedule/commands/args.d.ts.map +1 -0
  154. package/dist/schedule/commands/args.js +80 -0
  155. package/dist/schedule/commands/args.js.map +1 -0
  156. package/dist/schedule/commands/confirm-prompt.d.ts +6 -0
  157. package/dist/schedule/commands/confirm-prompt.d.ts.map +1 -0
  158. package/dist/schedule/commands/confirm-prompt.js +21 -0
  159. package/dist/schedule/commands/confirm-prompt.js.map +1 -0
  160. package/dist/schedule/commands/lifecycle.d.ts +39 -0
  161. package/dist/schedule/commands/lifecycle.d.ts.map +1 -0
  162. package/dist/schedule/commands/lifecycle.js +586 -0
  163. package/dist/schedule/commands/lifecycle.js.map +1 -0
  164. package/dist/schedule/commands/list.d.ts +33 -0
  165. package/dist/schedule/commands/list.d.ts.map +1 -0
  166. package/dist/schedule/commands/list.js +206 -0
  167. package/dist/schedule/commands/list.js.map +1 -0
  168. package/dist/schedule/commands/logs.d.ts +7 -0
  169. package/dist/schedule/commands/logs.d.ts.map +1 -0
  170. package/dist/schedule/commands/logs.js +106 -0
  171. package/dist/schedule/commands/logs.js.map +1 -0
  172. package/dist/schedule/commands/service.d.ts +16 -0
  173. package/dist/schedule/commands/service.d.ts.map +1 -0
  174. package/dist/schedule/commands/service.js +458 -0
  175. package/dist/schedule/commands/service.js.map +1 -0
  176. package/dist/schedule/daemon/daemon.d.ts +156 -0
  177. package/dist/schedule/daemon/daemon.d.ts.map +1 -0
  178. package/dist/schedule/daemon/daemon.js +1253 -0
  179. package/dist/schedule/daemon/daemon.js.map +1 -0
  180. package/dist/schedule/daemon/endpoint.d.ts +38 -0
  181. package/dist/schedule/daemon/endpoint.d.ts.map +1 -0
  182. package/dist/schedule/daemon/endpoint.js +158 -0
  183. package/dist/schedule/daemon/endpoint.js.map +1 -0
  184. package/dist/schedule/daemon/exit.d.ts +8 -0
  185. package/dist/schedule/daemon/exit.d.ts.map +1 -0
  186. package/dist/schedule/daemon/exit.js +8 -0
  187. package/dist/schedule/daemon/exit.js.map +1 -0
  188. package/dist/schedule/daemon/log.d.ts +12 -0
  189. package/dist/schedule/daemon/log.d.ts.map +1 -0
  190. package/dist/schedule/daemon/log.js +53 -0
  191. package/dist/schedule/daemon/log.js.map +1 -0
  192. package/dist/schedule/daemon/notify.d.ts +38 -0
  193. package/dist/schedule/daemon/notify.d.ts.map +1 -0
  194. package/dist/schedule/daemon/notify.js +115 -0
  195. package/dist/schedule/daemon/notify.js.map +1 -0
  196. package/dist/schedule/daemon/retention.d.ts +16 -0
  197. package/dist/schedule/daemon/retention.d.ts.map +1 -0
  198. package/dist/schedule/daemon/retention.js +46 -0
  199. package/dist/schedule/daemon/retention.js.map +1 -0
  200. package/dist/schedule/daemon/sessions.d.ts +23 -0
  201. package/dist/schedule/daemon/sessions.d.ts.map +1 -0
  202. package/dist/schedule/daemon/sessions.js +52 -0
  203. package/dist/schedule/daemon/sessions.js.map +1 -0
  204. package/dist/schedule/env.d.ts +32 -0
  205. package/dist/schedule/env.d.ts.map +1 -0
  206. package/dist/schedule/env.js +106 -0
  207. package/dist/schedule/env.js.map +1 -0
  208. package/dist/schedule/fire/browser-preflight.d.ts +41 -0
  209. package/dist/schedule/fire/browser-preflight.d.ts.map +1 -0
  210. package/dist/schedule/fire/browser-preflight.js +73 -0
  211. package/dist/schedule/fire/browser-preflight.js.map +1 -0
  212. package/dist/schedule/fire/calls.d.ts +49 -0
  213. package/dist/schedule/fire/calls.d.ts.map +1 -0
  214. package/dist/schedule/fire/calls.js +114 -0
  215. package/dist/schedule/fire/calls.js.map +1 -0
  216. package/dist/schedule/fire/fire.d.ts +78 -0
  217. package/dist/schedule/fire/fire.d.ts.map +1 -0
  218. package/dist/schedule/fire/fire.js +490 -0
  219. package/dist/schedule/fire/fire.js.map +1 -0
  220. package/dist/schedule/fire/result.d.ts +13 -0
  221. package/dist/schedule/fire/result.d.ts.map +1 -0
  222. package/dist/schedule/fire/result.js +18 -0
  223. package/dist/schedule/fire/result.js.map +1 -0
  224. package/dist/schedule/fire/unattended-note.d.ts +22 -0
  225. package/dist/schedule/fire/unattended-note.d.ts.map +1 -0
  226. package/dist/schedule/fire/unattended-note.js +46 -0
  227. package/dist/schedule/fire/unattended-note.js.map +1 -0
  228. package/dist/schedule/floor.d.ts +136 -0
  229. package/dist/schedule/floor.d.ts.map +1 -0
  230. package/dist/schedule/floor.js +1289 -0
  231. package/dist/schedule/floor.js.map +1 -0
  232. package/dist/schedule/folder.d.ts +34 -0
  233. package/dist/schedule/folder.d.ts.map +1 -0
  234. package/dist/schedule/folder.js +84 -0
  235. package/dist/schedule/folder.js.map +1 -0
  236. package/dist/schedule/paths.d.ts +49 -0
  237. package/dist/schedule/paths.d.ts.map +1 -0
  238. package/dist/schedule/paths.js +53 -0
  239. package/dist/schedule/paths.js.map +1 -0
  240. package/dist/schedule/policy.d.ts +141 -0
  241. package/dist/schedule/policy.d.ts.map +1 -0
  242. package/dist/schedule/policy.js +378 -0
  243. package/dist/schedule/policy.js.map +1 -0
  244. package/dist/schedule/resume-command.d.ts +36 -0
  245. package/dist/schedule/resume-command.d.ts.map +1 -0
  246. package/dist/schedule/resume-command.js +72 -0
  247. package/dist/schedule/resume-command.js.map +1 -0
  248. package/dist/schedule/service/detect.d.ts +22 -0
  249. package/dist/schedule/service/detect.d.ts.map +1 -0
  250. package/dist/schedule/service/detect.js +29 -0
  251. package/dist/schedule/service/detect.js.map +1 -0
  252. package/dist/schedule/service/index.d.ts +48 -0
  253. package/dist/schedule/service/index.d.ts.map +1 -0
  254. package/dist/schedule/service/index.js +361 -0
  255. package/dist/schedule/service/index.js.map +1 -0
  256. package/dist/schedule/service/launchd.d.ts +24 -0
  257. package/dist/schedule/service/launchd.d.ts.map +1 -0
  258. package/dist/schedule/service/launchd.js +84 -0
  259. package/dist/schedule/service/launchd.js.map +1 -0
  260. package/dist/schedule/service/manifest.d.ts +44 -0
  261. package/dist/schedule/service/manifest.d.ts.map +1 -0
  262. package/dist/schedule/service/manifest.js +16 -0
  263. package/dist/schedule/service/manifest.js.map +1 -0
  264. package/dist/schedule/service/names.d.ts +9 -0
  265. package/dist/schedule/service/names.d.ts.map +1 -0
  266. package/dist/schedule/service/names.js +22 -0
  267. package/dist/schedule/service/names.js.map +1 -0
  268. package/dist/schedule/service/quote.d.ts +24 -0
  269. package/dist/schedule/service/quote.d.ts.map +1 -0
  270. package/dist/schedule/service/quote.js +64 -0
  271. package/dist/schedule/service/quote.js.map +1 -0
  272. package/dist/schedule/service/runner.d.ts +17 -0
  273. package/dist/schedule/service/runner.d.ts.map +1 -0
  274. package/dist/schedule/service/runner.js +31 -0
  275. package/dist/schedule/service/runner.js.map +1 -0
  276. package/dist/schedule/service/systemd.d.ts +37 -0
  277. package/dist/schedule/service/systemd.d.ts.map +1 -0
  278. package/dist/schedule/service/systemd.js +96 -0
  279. package/dist/schedule/service/systemd.js.map +1 -0
  280. package/dist/schedule/service/windows-task.d.ts +58 -0
  281. package/dist/schedule/service/windows-task.d.ts.map +1 -0
  282. package/dist/schedule/service/windows-task.js +199 -0
  283. package/dist/schedule/service/windows-task.js.map +1 -0
  284. package/dist/schedule/service/wsl.d.ts +69 -0
  285. package/dist/schedule/service/wsl.d.ts.map +1 -0
  286. package/dist/schedule/service/wsl.js +121 -0
  287. package/dist/schedule/service/wsl.js.map +1 -0
  288. package/dist/schedule/store/atomic.d.ts +43 -0
  289. package/dist/schedule/store/atomic.d.ts.map +1 -0
  290. package/dist/schedule/store/atomic.js +135 -0
  291. package/dist/schedule/store/atomic.js.map +1 -0
  292. package/dist/schedule/store/claims.d.ts +26 -0
  293. package/dist/schedule/store/claims.d.ts.map +1 -0
  294. package/dist/schedule/store/claims.js +57 -0
  295. package/dist/schedule/store/claims.js.map +1 -0
  296. package/dist/schedule/store/digest.d.ts +18 -0
  297. package/dist/schedule/store/digest.d.ts.map +1 -0
  298. package/dist/schedule/store/digest.js +139 -0
  299. package/dist/schedule/store/digest.js.map +1 -0
  300. package/dist/schedule/store/history.d.ts +21 -0
  301. package/dist/schedule/store/history.d.ts.map +1 -0
  302. package/dist/schedule/store/history.js +114 -0
  303. package/dist/schedule/store/history.js.map +1 -0
  304. package/dist/schedule/store/jobs.d.ts +61 -0
  305. package/dist/schedule/store/jobs.d.ts.map +1 -0
  306. package/dist/schedule/store/jobs.js +210 -0
  307. package/dist/schedule/store/jobs.js.map +1 -0
  308. package/dist/schedule/store/state.d.ts +19 -0
  309. package/dist/schedule/store/state.d.ts.map +1 -0
  310. package/dist/schedule/store/state.js +38 -0
  311. package/dist/schedule/store/state.js.map +1 -0
  312. package/dist/schedule/types.d.ts +283 -0
  313. package/dist/schedule/types.d.ts.map +1 -0
  314. package/dist/schedule/types.js +9 -0
  315. package/dist/schedule/types.js.map +1 -0
  316. package/dist/skills/catalog.d.ts +96 -0
  317. package/dist/skills/catalog.d.ts.map +1 -0
  318. package/dist/skills/catalog.js +226 -0
  319. package/dist/skills/catalog.js.map +1 -0
  320. package/dist/skills/directory.d.ts +38 -0
  321. package/dist/skills/directory.d.ts.map +1 -0
  322. package/dist/skills/directory.js +51 -0
  323. package/dist/skills/directory.js.map +1 -0
  324. package/dist/skills/save.d.ts +148 -0
  325. package/dist/skills/save.d.ts.map +1 -0
  326. package/dist/skills/save.js +344 -0
  327. package/dist/skills/save.js.map +1 -0
  328. package/dist/skills/store.d.ts +103 -16
  329. package/dist/skills/store.d.ts.map +1 -1
  330. package/dist/skills/store.js +227 -30
  331. package/dist/skills/store.js.map +1 -1
  332. package/dist/tui/App.d.ts.map +1 -1
  333. package/dist/tui/App.js +609 -74
  334. package/dist/tui/App.js.map +1 -1
  335. package/dist/tui/LiveActivity.d.ts +8 -1
  336. package/dist/tui/LiveActivity.d.ts.map +1 -1
  337. package/dist/tui/LiveActivity.js +20 -4
  338. package/dist/tui/LiveActivity.js.map +1 -1
  339. package/dist/tui/PermissionOverlay.d.ts +36 -4
  340. package/dist/tui/PermissionOverlay.d.ts.map +1 -1
  341. package/dist/tui/PermissionOverlay.js +135 -15
  342. package/dist/tui/PermissionOverlay.js.map +1 -1
  343. package/dist/tui/SaveSkillOverlay.d.ts +33 -0
  344. package/dist/tui/SaveSkillOverlay.d.ts.map +1 -0
  345. package/dist/tui/SaveSkillOverlay.js +118 -0
  346. package/dist/tui/SaveSkillOverlay.js.map +1 -0
  347. package/dist/tui/agent.d.ts +208 -6
  348. package/dist/tui/agent.d.ts.map +1 -1
  349. package/dist/tui/agent.js +371 -58
  350. package/dist/tui/agent.js.map +1 -1
  351. package/dist/tui/browser-notices.d.ts +33 -0
  352. package/dist/tui/browser-notices.d.ts.map +1 -0
  353. package/dist/tui/browser-notices.js +102 -0
  354. package/dist/tui/browser-notices.js.map +1 -0
  355. package/dist/tui/open-browser.d.ts +29 -1
  356. package/dist/tui/open-browser.d.ts.map +1 -1
  357. package/dist/tui/open-browser.js +83 -11
  358. package/dist/tui/open-browser.js.map +1 -1
  359. package/dist/tui/permission-review.d.ts +10 -1
  360. package/dist/tui/permission-review.d.ts.map +1 -1
  361. package/dist/tui/permission-review.js +228 -22
  362. package/dist/tui/permission-review.js.map +1 -1
  363. package/dist/tui/schedule/host-commands.d.ts +34 -0
  364. package/dist/tui/schedule/host-commands.d.ts.map +1 -0
  365. package/dist/tui/schedule/host-commands.js +310 -0
  366. package/dist/tui/schedule/host-commands.js.map +1 -0
  367. package/dist/tui/schedule/integration.d.ts +62 -0
  368. package/dist/tui/schedule/integration.d.ts.map +1 -0
  369. package/dist/tui/schedule/integration.js +145 -0
  370. package/dist/tui/schedule/integration.js.map +1 -0
  371. package/dist/tui/schedule/loop-host.d.ts +42 -0
  372. package/dist/tui/schedule/loop-host.d.ts.map +1 -0
  373. package/dist/tui/schedule/loop-host.js +153 -0
  374. package/dist/tui/schedule/loop-host.js.map +1 -0
  375. package/dist/tui/schedule/resume.d.ts +122 -0
  376. package/dist/tui/schedule/resume.d.ts.map +1 -0
  377. package/dist/tui/schedule/resume.js +324 -0
  378. package/dist/tui/schedule/resume.js.map +1 -0
  379. package/dist/tui/schedule/startup.d.ts +11 -0
  380. package/dist/tui/schedule/startup.d.ts.map +1 -0
  381. package/dist/tui/schedule/startup.js +121 -0
  382. package/dist/tui/schedule/startup.js.map +1 -0
  383. package/dist/tui/schedule/tool-host.d.ts +58 -0
  384. package/dist/tui/schedule/tool-host.d.ts.map +1 -0
  385. package/dist/tui/schedule/tool-host.js +479 -0
  386. package/dist/tui/schedule/tool-host.js.map +1 -0
  387. package/dist/tui/skills/learning.d.ts +74 -0
  388. package/dist/tui/skills/learning.d.ts.map +1 -0
  389. package/dist/tui/skills/learning.js +131 -0
  390. package/dist/tui/skills/learning.js.map +1 -0
  391. package/dist/tui/skills/suggest-setting.d.ts +22 -0
  392. package/dist/tui/skills/suggest-setting.d.ts.map +1 -0
  393. package/dist/tui/skills/suggest-setting.js +55 -0
  394. package/dist/tui/skills/suggest-setting.js.map +1 -0
  395. package/dist/tui/skills/suggestion-ledger.d.ts +35 -0
  396. package/dist/tui/skills/suggestion-ledger.d.ts.map +1 -0
  397. package/dist/tui/skills/suggestion-ledger.js +52 -0
  398. package/dist/tui/skills/suggestion-ledger.js.map +1 -0
  399. package/dist/tui/slashCommands.d.ts +20 -0
  400. package/dist/tui/slashCommands.d.ts.map +1 -1
  401. package/dist/tui/slashCommands.js +76 -2
  402. package/dist/tui/slashCommands.js.map +1 -1
  403. package/dist/tui/turn-interruption.d.ts +20 -1
  404. package/dist/tui/turn-interruption.d.ts.map +1 -1
  405. package/dist/tui/turn-interruption.js +43 -0
  406. package/dist/tui/turn-interruption.js.map +1 -1
  407. package/dist/tui/types.d.ts +9 -1
  408. package/dist/tui/types.d.ts.map +1 -1
  409. package/package.json +7 -5
  410. package/skills/README.md +20 -0
  411. package/skills/browser-automation/SKILL.md +84 -0
  412. package/skills/schedule-task/SKILL.md +146 -0
  413. package/skills/skill-creator/SKILL.md +101 -0
package/dist/tui/agent.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createCurrentCredentialReader } from '../integrations/providers/current-credential.js';
2
+ import { createOpenUrlTool } from '../integrations/web/open-url.js';
2
3
  import { createWebSearchTool, resolveWebSearch, webSearchLabel, } from '../integrations/web/search.js';
3
4
  import { countListedResults, webActivityFromInput, webActivityKind, webCallTitle, } from './web-activity.js';
4
5
  /**
@@ -24,10 +25,11 @@ import { countListedResults, webActivityFromInput, webActivityKind, webCallTitle
24
25
  * `emptySession()` whose `send()` yields a single error event so the UI
25
26
  * renders an actionable hint rather than crashing.
26
27
  */
27
- import { BOOT_EVENT_NAMES, BackgroundJobRegistry, DiskSessionCheckpointStore, DiskSessionLog, DiskTaskStore, EVENT_NAME_ATTRIBUTE, GuardedFetchProvider, InMemorySessionLog, MarkdownMemoryStore, PromptContributionRegistry, ProviderRegistry, SESSION_GOAL_TOOL_NAMES, SessionPaths, ToolRegistry, WebFetchTool, abandonTurn, batchNeedsReview, buildAskUserQuestionTool, buildMemoryTools, buildResidentHistoryTools, buildResidentToolEvidenceTools, buildSessionGoalTools, compactNow, compactSession, createComputerUseTool, createFileReadTracker, createMemoryPromoter, createMemoryRecallStep, createResidentStepContext, createResidentStepContributions, createReviewHandler, createToolPresenter, ensureProject, generateSessionId, generateTenantId, generateTopicId, generateTurnId, getBuiltinTools, isReviewExempt, isTurnInProgressError, query, resumeSession, seedObservationLedger, webGuidanceContribution, withProviderFallback, } from '@namzu/sdk';
28
+ import { BOOT_EVENT_NAMES, BackgroundJobRegistry, DiskSessionCheckpointStore, DiskSessionLog, DiskTaskStore, EVENT_NAME_ATTRIBUTE, GuardedFetchProvider, InMemorySessionLog, MarkdownMemoryStore, PromptContributionRegistry, ProviderRegistry, SCHEDULE_TOOL_NAME, SESSION_GOAL_TOOL_NAMES, SessionPaths, ToolRegistry, WebFetchTool, abandonTurn, batchNeedsReview, buildAskUserQuestionTool, buildMemoryTools, buildResidentHistoryTools, buildResidentToolEvidenceTools, buildSessionGoalTools, compactNow, compactSession, computerUseUnavailableReason, createBrowserTools, createComputerUseTool, createFileReadTracker, createMemoryPromoter, createMemoryRecallStep, createResidentStepContext, createResidentStepContributions, createReviewHandler, createSkillTool, createToolPresenter, ensureProject, generateSessionId, generateTenantId, generateTopicId, generateTurnId, getBuiltinTools, isReviewExempt, isTurnInProgressError, query, resolveContextWindow, resumeSession, seedObservationLedger, webGuidanceContribution, withProviderFallback, } from '@namzu/sdk';
28
29
  import { SubprocessComputerUseHost } from '@namzu/computer-use';
29
30
  import { realpath, stat } from 'node:fs/promises';
30
31
  import { parse, resolve } from 'node:path';
32
+ import { createBrowserControl, } from '../browser/control.js';
31
33
  import { FileCheckpointStore } from '../checkpoints/store.js';
32
34
  import { CHECKPOINTED_TOOLS, withCheckpoints } from '../checkpoints/wrap.js';
33
35
  import { configuredPassthroughTools, resolveToolResultScreens, unmatchedPassthroughTools, } from '../config/tool-result-screens.js';
@@ -62,13 +64,16 @@ import { discoverAgentDefinitions } from '../integrations/subagents/definitions.
62
64
  import { prepareDelegatedEffort } from '../integrations/subagents/model-effort.js';
63
65
  import { resolveSubagentParent } from '../integrations/subagents/parent.js';
64
66
  import { replaySavedChildrenFor } from '../integrations/subagents/replay.js';
65
- import { createSubagentRuntime } from '../integrations/subagents/runtime.js';
67
+ import { createSubagentRuntime, } from '../integrations/subagents/runtime.js';
66
68
  import { createSavedAgentHistory, createSavedAgentsStep, } from '../integrations/subagents/saved-agents.js';
67
69
  import { cliLogger } from '../logging.js';
68
70
  import { formatMemoryDiagnostics } from '../memory/presentation.js';
69
71
  import { composeMemoryPrompt, readMemory } from '../memory/store.js';
70
72
  import { composeStoredMemoryPrompt, describeCuratedNotesImport, describeMemoryMigration, importCuratedNotes, migrateMemoryOnce, saveTypedNote, } from '../memory/typed.js';
71
73
  import { createLiveModeControl, permissionChangeReason, } from '../permissions/live-mode.js';
74
+ import { createSessionSkillCatalog } from '../skills/catalog.js';
75
+ import { createSkillDirectoryResolver } from '../skills/directory.js';
76
+ import { SAVE_SKILL_TOOL_NAME } from '../skills/save.js';
72
77
  import { projectTurnConversation } from './conversation-history.js';
73
78
  import { buildSwitchModelTool } from './model-switch-tool.js';
74
79
  import { resolveModelSwitch } from './model-switch.js';
@@ -287,7 +292,7 @@ async function drainIterator(iterator) {
287
292
  * Read preferences + run discovery once. Returned context drives the
288
293
  * App's lifecycle decision: ready / picker / unhealthy.
289
294
  */
290
- export async function probeAgentSession() {
295
+ export async function probeAgentSession(options = {}) {
291
296
  const read = readPreferences();
292
297
  // Bracketed in the log because this is where a boot has stalled without
293
298
  // a record on either side: it reads credential files, and on WSL it asks
@@ -295,7 +300,7 @@ export async function probeAgentSession() {
295
300
  // and nothing after is this step.
296
301
  const discoveryStartedAt = Date.now();
297
302
  cliLogger().debug('discovering provider credentials');
298
- const detected = await discoverProviders();
303
+ const detected = await discoverProviders(options.env ? { env: options.env } : {});
299
304
  cliLogger().debug('provider credentials discovered', {
300
305
  'namzu.boot.discovery_ms': Date.now() - discoveryStartedAt,
301
306
  'namzu.boot.detected_count': detected.length,
@@ -376,6 +381,9 @@ const EAGER_TOOLS_WHEN_DEFERRED = [
376
381
  'web_fetch',
377
382
  'search_conversation',
378
383
  'read_conversation',
384
+ // The skills manifest tells the model to load a skill through this tool;
385
+ // deferring it would make every skill a two-step search first.
386
+ 'skill',
379
387
  'search_resident_history',
380
388
  'read_resident_history',
381
389
  'search_resident_tools',
@@ -483,13 +491,6 @@ function buildToolRegistry(paths, backgroundJobs, checkpoints, screens) {
483
491
  // after runtime tools are registered. Ordinary CLI task tools are active.
484
492
  return { registry, memoryStore, memoryDirectory: directory };
485
493
  }
486
- /** Refresh plugin skill metadata before each provider operation. */
487
- async function currentPluginSkills(registry) {
488
- const names = registry.list().map((skill) => skill.metadata.name);
489
- for (const name of names)
490
- await registry.load(name, 'metadata');
491
- return registry.list();
492
- }
493
494
  export async function createAgentSession(prefs, detected, options = {}) {
494
495
  const fileObservations = new Map();
495
496
  /**
@@ -906,7 +907,12 @@ export async function createAgentSession(prefs, detected, options = {}) {
906
907
  let lastSendInteractive = false;
907
908
  const boundaryFor = (interactive) => ({
908
909
  ...(sandbox.provider && sandbox.environment
909
- ? { sandbox: { environment: sandbox.environment, enforced: sandbox.enforced } }
910
+ ? {
911
+ sandbox: {
912
+ environment: sandbox.environment,
913
+ enforced: sandbox.enforced,
914
+ },
915
+ }
910
916
  : {}),
911
917
  escape: sandboxEscape === 'refuse'
912
918
  ? 'refused'
@@ -962,7 +968,9 @@ export async function createAgentSession(prefs, detected, options = {}) {
962
968
  */
963
969
  const storedMemoryPrompt = async () => {
964
970
  try {
965
- return { prompt: composeStoredMemoryPrompt(await memoryStore.readIndex()) };
971
+ return {
972
+ prompt: composeStoredMemoryPrompt(await memoryStore.readIndex()),
973
+ };
966
974
  }
967
975
  catch (error) {
968
976
  return {
@@ -979,12 +987,27 @@ export async function createAgentSession(prefs, detected, options = {}) {
979
987
  const capabilities = await probeCapabilities();
980
988
  const computerUsePackage = capabilities.find((probe) => probe.specifier === '@namzu/computer-use');
981
989
  let computerUseHost;
990
+ let computerUseTool;
982
991
  let computerUseError;
983
- if (options.enableComputerUse === true && computerUsePackage?.state === 'present') {
992
+ // The model sees the desktop only as an image in a tool result. A driver
993
+ // that declares it cannot carry one would hand the model a line of text
994
+ // for every screenshot while each click reported success — the model
995
+ // acting on a screen it never saw. Mounted as a diagnostic that says so,
996
+ // without starting the desktop host at all.
997
+ const computerUseProviderRefusal = options.enableComputerUse === true && computerUsePackage?.state === 'present'
998
+ ? computerUseUnavailableReason(provider)
999
+ : undefined;
1000
+ if (computerUseProviderRefusal !== undefined) {
1001
+ registry.register(createComputerUseTool(new SubprocessComputerUseHost(), {
1002
+ unavailableReason: computerUseProviderRefusal,
1003
+ }));
1004
+ }
1005
+ else if (options.enableComputerUse === true && computerUsePackage?.state === 'present') {
984
1006
  const candidate = new SubprocessComputerUseHost();
985
1007
  try {
986
1008
  await candidate.initialize();
987
- registry.register(createComputerUseTool(candidate));
1009
+ computerUseTool = createComputerUseTool(candidate);
1010
+ registry.register(computerUseTool);
988
1011
  computerUseHost = candidate;
989
1012
  }
990
1013
  catch (error) {
@@ -1014,6 +1037,26 @@ export async function createAgentSession(prefs, detected, options = {}) {
1014
1037
  }));
1015
1038
  }
1016
1039
  }
1040
+ // The browser: mounted for a surface that asked for it, never launched
1041
+ // here. The host detects the engine now and starts the browser on the
1042
+ // model's first call. Parent session only — sub-agents build their own
1043
+ // registries below and never receive it.
1044
+ const browserPackage = capabilities.find((probe) => probe.specifier === '@namzu/browser');
1045
+ let browserControl;
1046
+ let browserError;
1047
+ if (options.browser && browserPackage?.state === 'present') {
1048
+ try {
1049
+ const { PlaywrightBrowserHost } = await import('@namzu/browser');
1050
+ browserControl = createBrowserControl(PlaywrightBrowserHost, options.browser);
1051
+ registry.register(createBrowserTools(browserControl.host));
1052
+ }
1053
+ catch (error) {
1054
+ browserError = error instanceof Error ? error : new Error(String(error));
1055
+ }
1056
+ }
1057
+ const browserUnavailable = browserError !== undefined
1058
+ ? describeError(browserError)
1059
+ : browserControl?.status().unavailableReason;
1017
1060
  // Registered only on the main session path. Sub-agents call
1018
1061
  // `buildToolRegistry` directly below, so they never receive these tools.
1019
1062
  // Per-send denial further keeps the schemas out of ordinary human turns.
@@ -1069,6 +1112,9 @@ export async function createAgentSession(prefs, detected, options = {}) {
1069
1112
  sandboxReady: sandbox.provider !== undefined,
1070
1113
  computerUseReady: computerUseHost !== undefined,
1071
1114
  ...(computerUseError ? { computerUseError } : {}),
1115
+ browserReady: browserControl !== undefined && browserUnavailable === undefined,
1116
+ ...(browserControl ? { browser: browserControl.status() } : {}),
1117
+ ...(browserUnavailable !== undefined ? { browserUnavailable } : {}),
1072
1118
  });
1073
1119
  // URL fetching is separately opt-in and parent-only: children do not
1074
1120
  // carry its guarded provider. Independent search is shared below because
@@ -1096,6 +1142,9 @@ export async function createAgentSession(prefs, detected, options = {}) {
1096
1142
  // Best-effort — if the runtime can't stand up, the chat still works.
1097
1143
  const delegationScopes = new Map();
1098
1144
  const delegationLimits = new Map();
1145
+ // A resumed turn that runs on another model than the session's (a
1146
+ // scheduled job's): its children inherit that model, not the session's.
1147
+ const delegationModels = new Map();
1099
1148
  const delegatedInputWaiters = new Map();
1100
1149
  if (options.residentHistory) {
1101
1150
  const history = options.residentHistory;
@@ -1193,6 +1242,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
1193
1242
  tokenBudget: options.limits?.tokenBudget,
1194
1243
  maxIterations: options.limits?.maxIterations,
1195
1244
  resolveLimits: (turnId) => delegationLimits.get(turnId),
1245
+ resolveTurnModel: (turnId) => delegationModels.get(turnId),
1196
1246
  timeoutMs: options.limits?.timeoutMs,
1197
1247
  definitions: discovered.definitions,
1198
1248
  // Children log under the parent's `<session-id>/subagents/`; an
@@ -1382,6 +1432,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
1382
1432
  const remaining = await Promise.allSettled([
1383
1433
  mcp.close(),
1384
1434
  computerUseHost?.dispose(),
1435
+ browserControl?.dispose(),
1385
1436
  jobRegistry?.killOwner(jobOwner),
1386
1437
  checkpoints.close(),
1387
1438
  ]);
@@ -1448,6 +1499,12 @@ export async function createAgentSession(prefs, detected, options = {}) {
1448
1499
  // park recorder is supplied.
1449
1500
  registry.register(buildAskUserQuestionTool({ resumeHandler: parkQuestion }));
1450
1501
  }
1502
+ // The host's own additions, to this registry only: `buildTools` above
1503
+ // builds a child's roster separately, so none of these reach a sub-agent.
1504
+ for (const tool of options.extraTools ?? [])
1505
+ registry.register(tool);
1506
+ if (options.openUrl)
1507
+ registry.register(createOpenUrlTool());
1451
1508
  // Task store → query registers task_create / task_update / task_list and
1452
1509
  // emits task_created/task_updated, so the agent can track a plan. Tasks
1453
1510
  // belong to the session (`<session-id>/tasks/`) and record the turn that
@@ -1501,6 +1558,15 @@ export async function createAgentSession(prefs, detected, options = {}) {
1501
1558
  // Persists across turns: once the user picks "approve all", later tool
1502
1559
  // batches in this session run without prompting.
1503
1560
  const approval = { all: false };
1561
+ // The sessions whose operator let the model see the screen, asked once per
1562
+ // session before the first screenshot. Kept across mode switches and
1563
+ // turns, unlike "approve all"; a new session id is asked again.
1564
+ const screenConsent = { sessions: new Set() };
1565
+ // The registry is read at each decision: a turn may swap in its own.
1566
+ const screenPolicyFor = (tools) => ({
1567
+ consent: screenConsent,
1568
+ capturesScreen: (name, input) => tools().get(name)?.capturesScreen?.(input) === true,
1569
+ });
1504
1570
  // The turns running now, each deciding under a mode the operator may change
1505
1571
  // mid-turn, and the mode each conversation's log last recorded.
1506
1572
  const liveModeControls = new Set();
@@ -1514,14 +1580,43 @@ export async function createAgentSession(prefs, detected, options = {}) {
1514
1580
  // refusal closes the MCP processes already opened for this candidate before
1515
1581
  // returning an inert session. Sub-agents were built above from their own
1516
1582
  // registries, so executable plugins remain a top-level-session capability.
1583
+ // File skills (built-in, ~/.agents, ~/.namzu, and the project's) reach the
1584
+ // model through the kernel's manifest and `skill` tool. Registered BEFORE
1585
+ // the plugin runtime: a runtime that finds the tool already there never
1586
+ // owns it, so disabling the last plugin skill cannot take away the tool
1587
+ // the file skills load through.
1588
+ const skillCatalog = await createSessionSkillCatalog({
1589
+ cwd,
1590
+ ...(options.skills ? { config: options.skills } : {}),
1591
+ log: cliLogger(),
1592
+ });
1593
+ // One `skill` tool for file and plugin skills alike, told which directory
1594
+ // the model can open for each: the real one on the host, the mounted one
1595
+ // inside the sandbox, none when the sandbox does not mount it.
1596
+ const skillTool = createSkillTool({
1597
+ resolveModelDirectory: createSkillDirectoryResolver({
1598
+ sandboxMounts: () => (sandboxWorkspace === 'working-directory' ? directories : []),
1599
+ }),
1600
+ });
1601
+ // A session that can save a skill loads it next turn through this tool,
1602
+ // even when it started with none.
1603
+ if ((skillCatalog.hasFileSkills || registry.has(SAVE_SKILL_TOOL_NAME)) &&
1604
+ !registry.has(skillTool.name))
1605
+ registry.register(skillTool);
1517
1606
  let pluginRuntime;
1518
1607
  try {
1519
- pluginRuntime = await createCliPluginRuntime(options.plugins, registry, cwd, options.hooks);
1608
+ pluginRuntime = await createCliPluginRuntime(options.plugins, registry, cwd, options.hooks, skillTool);
1520
1609
  }
1521
1610
  catch (error) {
1522
- await Promise.allSettled([mcp.close(), computerUseHost?.dispose()]);
1611
+ await Promise.allSettled([mcp.close(), computerUseHost?.dispose(), browserControl?.dispose()]);
1523
1612
  return emptySession(describeError(error));
1524
1613
  }
1614
+ // Everything is registered by now but the deferred task tools, which no
1615
+ // caller withholds.
1616
+ for (const name of options.withheldTools ?? []) {
1617
+ if (registry.get(name))
1618
+ registry.unregister(name);
1619
+ }
1525
1620
  // The session's own lifecycle, for hooks that set up or tear down
1526
1621
  // something per session rather than per turn. These two calls belong to no
1527
1622
  // turn, so they carry no turn id — nothing is minted to fill the field.
@@ -1530,6 +1625,14 @@ export async function createAgentSession(prefs, detected, options = {}) {
1530
1625
  // is replaced when the conversation is first made durable — and a hook
1531
1626
  // given the provisional id could never match it to a turn.
1532
1627
  const sessionPlugins = pluginRuntime;
1628
+ // What one turn's prompt manifest and `skill` tool see: the file skills
1629
+ // gated against the tools registered now, merged with the plugins' own.
1630
+ const turnSkillsFor = (turnModel) => skillCatalog.forTurn({
1631
+ toolNames: registry.listNames(),
1632
+ contextWindowTokens: resolveContextWindow(options.compaction?.contextWindowTokens, turnModel)
1633
+ .tokens,
1634
+ ...(sessionPlugins ? { pluginSkills: sessionPlugins.skills } : {}),
1635
+ });
1533
1636
  let sessionStarted = false;
1534
1637
  const announceSessionStart = async () => {
1535
1638
  if (!sessionPlugins || sessionStarted)
@@ -1591,6 +1694,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
1591
1694
  : undefined,
1592
1695
  mcp.close(),
1593
1696
  computerUseHost?.dispose(),
1697
+ browserControl?.dispose(),
1594
1698
  jobRegistry?.killOwner(jobOwner),
1595
1699
  checkpoints.close(),
1596
1700
  ]);
@@ -1647,13 +1751,35 @@ export async function createAgentSession(prefs, detected, options = {}) {
1647
1751
  reasoningEffortDefault = undefined;
1648
1752
  effortNotice = `Reasoning effort levels could not be established for this session: ${describeError(error)}`;
1649
1753
  }
1754
+ /**
1755
+ * The provider a resumed turn runs on when its caller pinned one (a
1756
+ * scheduled job's model): built from this session's credential for that
1757
+ * provider, with no fallback chain, since the job's own run has none.
1758
+ * `undefined` without a pin.
1759
+ */
1760
+ const pinnedResumeRoute = async (pin, sessionId, signal) => {
1761
+ if (!pin)
1762
+ return undefined;
1763
+ const providerId = pin.provider;
1764
+ const found = findDetected(detected, providerId);
1765
+ if (!found)
1766
+ throw new Error(`the turn runs on ${pin.provider}${pin.model ? `/${pin.model}` : ''} and this session has no credential for ${pin.provider}`);
1767
+ const pinnedModel = pin.model ?? found.entry.defaultModel;
1768
+ await ensureRegistered(providerId);
1769
+ const credential = await currentCredentialFor(providerId, signal);
1770
+ const pinnedProvider = constructProvider(providerId, credential, pinnedModel, { sessionId });
1771
+ const effort = pin.effort;
1772
+ if (effort !== undefined)
1773
+ await prepareDelegatedEffort(pinnedProvider, pinnedModel, signal);
1774
+ return { provider: pinnedProvider, model: pinnedModel, ...(effort ? { effort } : {}) };
1775
+ };
1650
1776
  /**
1651
1777
  * The kernel's resume with this session's half of the turn attached: the
1652
1778
  * provider, the tools, the working directory, the doctrine — the part a
1653
1779
  * checkpoint cannot carry. `resumeDurable` and `resumePaused` differ only
1654
1780
  * in where the log and its lease come from.
1655
1781
  */
1656
- const kernelResume = ({ entry, sessionLog, checkpointStore, lease, signal, checkpointId, listener, permissionMode, currentPermissionMode, }) => operations.promise(signal, async (ownedSignal) => {
1782
+ const kernelResume = ({ entry, sessionLog, checkpointStore, lease, signal, checkpointId, listener, permissionMode, currentPermissionMode, pendingDecision, onPermission, rules, reviewHold, model: pinned, systemNote, }) => operations.promise(signal, async (ownedSignal) => {
1657
1783
  const selectTaskStore = beginTaskStoreReadout();
1658
1784
  // The turn's own limits, as its `turn_started` recorded them.
1659
1785
  const resumedLimits = (await readStoredTurnGuards(sessionLog, entry.turnId)) ?? resolveTurnGuards(options.limits);
@@ -1662,9 +1788,8 @@ export async function createAgentSession(prefs, detected, options = {}) {
1662
1788
  // fallback chain has to be built AFTER that so its members do not
1663
1789
  // hold a client the refresh just replaced.
1664
1790
  await prepareProviderCredential(ownedSignal);
1665
- const pluginSkills = pluginRuntime
1666
- ? await currentPluginSkills(pluginRuntime.skills)
1667
- : undefined;
1791
+ const route = await pinnedResumeRoute(pinned, entry.sessionId, ownedSignal);
1792
+ const turnSkills = await turnSkillsFor(route?.model ?? model);
1668
1793
  const curatedMemory = readMemory(undefined, cwd);
1669
1794
  for (const notice of formatMemoryDiagnostics(curatedMemory))
1670
1795
  cliLogger().warn(notice);
@@ -1687,6 +1812,8 @@ export async function createAgentSession(prefs, detected, options = {}) {
1687
1812
  options.conversationSessions ? CONVERSATION_EVIDENCE_GUIDANCE : undefined,
1688
1813
  environmentPrompt,
1689
1814
  memoryPrompt,
1815
+ systemNote,
1816
+ turnSkills.overflowNote,
1690
1817
  ]
1691
1818
  .filter((s) => Boolean(s))
1692
1819
  .join('\n\n') || undefined;
@@ -1708,7 +1835,13 @@ export async function createAgentSession(prefs, detected, options = {}) {
1708
1835
  ...(recordedModes.has(String(entry.sessionId))
1709
1836
  ? { recorded: recordedModes.get(String(entry.sessionId)) }
1710
1837
  : {}),
1711
- handlerFor: (mode) => makeResumeHandler(approval, undefined, mode, reviewExemptionFor(mode, registry, (input) => subagentRuntime?.launchesReadOnlyAgent(input) === true), { unattendedSandboxEscape }),
1838
+ handlerFor: (mode) => onPermission === undefined && reviewHold !== undefined
1839
+ ? makeHoldingResumeHandler(mode, reviewExemptionFor(mode, registry, (input) => subagentRuntime?.launchesReadOnlyAgent(input) === true), { unattendedSandboxEscape }, reviewHold.reason, screenPolicyFor(() => registry))
1840
+ : makeResumeHandler(
1841
+ // A caller that brings its own prompt (a scheduled turn answered in
1842
+ // the TUI) gets its own latch: the session's "approve all" is not
1843
+ // an answer about a turn another policy started.
1844
+ onPermission ? { all: false } : approval, onPermission, mode, reviewExemptionFor(mode, registry, (input) => subagentRuntime?.launchesReadOnlyAgent(input) === true), { unattendedSandboxEscape }, screenPolicyFor(() => registry)),
1712
1845
  });
1713
1846
  const resumeHandler = modeControl.handler;
1714
1847
  const reviewAllowedCalls = modeControl.reviewAllowedCalls;
@@ -1718,15 +1851,21 @@ export async function createAgentSession(prefs, detected, options = {}) {
1718
1851
  delegatedReviewAllowedCalls.set(entry.turnId, reviewAllowedCalls);
1719
1852
  delegationScopes.set(entry.turnId, turnScope);
1720
1853
  delegationLimits.set(entry.turnId, resumedLimits);
1854
+ if (route && pinned)
1855
+ delegationModels.set(entry.turnId, {
1856
+ provider: pinned.provider,
1857
+ model: route.model,
1858
+ ...(route.effort ? { effort: route.effort } : {}),
1859
+ });
1721
1860
  const turnTaskStore = selectTaskStore(turnScope);
1722
1861
  try {
1723
1862
  return await resumeSession({
1724
- provider: providerForSession(entry.sessionId),
1725
- fallbackProviders: fallbackPlan.build(currentToken, entry.sessionId),
1863
+ provider: route?.provider ?? providerForSession(entry.sessionId),
1864
+ fallbackProviders: route ? [] : fallbackPlan.build(currentToken, entry.sessionId),
1726
1865
  tools: registry,
1727
1866
  pluginManager: pluginRuntime?.manager,
1728
- skillRegistry: pluginRuntime?.skills,
1729
- skills: pluginSkills,
1867
+ ...(turnSkills.registry ? { skillRegistry: turnSkills.registry } : {}),
1868
+ ...(turnSkills.manifest ? { skills: turnSkills.manifest } : {}),
1730
1869
  taskStore: turnTaskStore,
1731
1870
  ...(webCapability ? { web: webCapability } : {}),
1732
1871
  // The same availability the original turn registered under.
@@ -1739,9 +1878,12 @@ export async function createAgentSession(prefs, detected, options = {}) {
1739
1878
  task_list: 'active',
1740
1879
  },
1741
1880
  ...(subagentRuntime
1742
- ? { taskScheduler: await subagentRuntime.gatewayForTurn(entry.turnId) }
1881
+ ? {
1882
+ taskScheduler: await subagentRuntime.gatewayForTurn(entry.turnId),
1883
+ }
1743
1884
  : {}),
1744
- authorizationGate: gateFor(options.rules),
1885
+ authorizationGate: gateFor(rules ?? options.rules),
1886
+ ...(pendingDecision ? { pendingDecision } : {}),
1745
1887
  compactionConfig: compactionConfigFor(options.compaction),
1746
1888
  retainedToolPreviewChars: options.conversationSessions
1747
1889
  ? (options.compaction?.retainedToolPreviewChars ?? 4_000)
@@ -1770,7 +1912,8 @@ export async function createAgentSession(prefs, detected, options = {}) {
1770
1912
  ? { sandboxTeardownTimeoutMs: options.sandbox.teardownTimeoutMs }
1771
1913
  : {}),
1772
1914
  turnConfig: {
1773
- model,
1915
+ model: route?.model ?? model,
1916
+ ...(route?.effort ? { effort: route.effort } : {}),
1774
1917
  ...(nativeWebSearch ? { webSearch: nativeWebSearch } : {}),
1775
1918
  ...(sandbox.provider ? { sandbox: { workspace: sandboxWorkspace } } : {}),
1776
1919
  ...resumedLimits,
@@ -1833,6 +1976,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
1833
1976
  delegatedReviewAllowedCalls.delete(entry.turnId);
1834
1977
  delegationScopes.delete(entry.turnId);
1835
1978
  delegationLimits.delete(entry.turnId);
1979
+ delegationModels.delete(entry.turnId);
1836
1980
  await subagentRuntime?.releaseTurn(entry.turnId);
1837
1981
  }
1838
1982
  }
@@ -1843,12 +1987,13 @@ export async function createAgentSession(prefs, detected, options = {}) {
1843
1987
  * headless caller renders a resumed turn exactly as it rendered the first
1844
1988
  * segment.
1845
1989
  */
1846
- const resumePausedStream = ({ turnId, checkpointId, signal, permissionMode, currentPermissionMode, }) => {
1990
+ const resumePausedStream = ({ turnId, checkpointId, signal, permissionMode, currentPermissionMode, pendingDecision, onPermission, rules, reviewHold, model: pinned, systemNote, }) => {
1847
1991
  const queue = [];
1848
1992
  let wake;
1849
1993
  let settled = false;
1850
1994
  let failure;
1851
1995
  const presenter = createToolPresenter(registry);
1996
+ const readsOnly = declaredReadOnly(registry);
1852
1997
  // The log the turn appends to, and its checkpoints beside it.
1853
1998
  const sessionLog = DiskSessionLog.at(paths, { sessionId: scope.sessionId });
1854
1999
  const outcome = kernelResume({
@@ -1863,6 +2008,12 @@ export async function createAgentSession(prefs, detected, options = {}) {
1863
2008
  ...(checkpointId !== undefined ? { checkpointId: checkpointId } : {}),
1864
2009
  ...(permissionMode ? { permissionMode } : {}),
1865
2010
  ...(currentPermissionMode ? { currentPermissionMode } : {}),
2011
+ ...(pendingDecision ? { pendingDecision } : {}),
2012
+ ...(onPermission ? { onPermission } : {}),
2013
+ ...(rules ? { rules } : {}),
2014
+ ...(reviewHold ? { reviewHold } : {}),
2015
+ ...(pinned ? { model: pinned } : {}),
2016
+ ...(systemNote ? { systemNote } : {}),
1866
2017
  listener: (event) => {
1867
2018
  queue.push(event);
1868
2019
  wake?.();
@@ -1888,7 +2039,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
1888
2039
  const next = queue.shift();
1889
2040
  if (!next)
1890
2041
  break;
1891
- const mapped = toAgentEvent(next, presenter);
2042
+ const mapped = toAgentEvent(next, presenter, readsOnly);
1892
2043
  if (mapped)
1893
2044
  yield mapped;
1894
2045
  }
@@ -2005,6 +2156,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
2005
2156
  return mcp.failed;
2006
2157
  },
2007
2158
  mcpStatus: () => mcp.current(),
2159
+ ...(browserControl ? { browser: browserControl } : {}),
2008
2160
  configNotices: [
2009
2161
  ...(capabilityNotice ? [capabilityNotice] : []),
2010
2162
  ...(effortNotice ? [effortNotice] : []),
@@ -2012,15 +2164,27 @@ export async function createAgentSession(prefs, detected, options = {}) {
2012
2164
  ...(computerUseError
2013
2165
  ? [`Computer use is unavailable on this device: ${describeError(computerUseError)}`]
2014
2166
  : []),
2167
+ ...(computerUseProviderRefusal !== undefined
2168
+ ? [`Computer use is unavailable in this session: ${computerUseProviderRefusal}`]
2169
+ : []),
2170
+ ...(browserUnavailable !== undefined
2171
+ ? [`The browser is unavailable: ${browserUnavailable}`]
2172
+ : []),
2015
2173
  ...unresolvedNotice.map((line) => `Provider chain: capabilities could not be established for ${line}.`),
2016
2174
  ...fallbackPlan.notices,
2017
2175
  ...memoryMigrationNotices,
2018
2176
  ],
2019
2177
  rememberNote: (text, type) => saveTypedNote(memoryStore, text, type),
2020
- importCuratedNotes: async () => describeCuratedNotesImport(await importCuratedNotes({ store: memoryStore, directory: memoryDirectory, cwd }), memoryDirectory),
2178
+ importCuratedNotes: async () => describeCuratedNotesImport(await importCuratedNotes({
2179
+ store: memoryStore,
2180
+ directory: memoryDirectory,
2181
+ cwd,
2182
+ }), memoryDirectory),
2021
2183
  storedMemoryIndex: async () => ({
2022
2184
  directory: memoryDirectory,
2023
- index: await memoryStore.readIndex({ maxLines: Number.POSITIVE_INFINITY }),
2185
+ index: await memoryStore.readIndex({
2186
+ maxLines: Number.POSITIVE_INFINITY,
2187
+ }),
2024
2188
  derived: await memoryStore.readIndex({
2025
2189
  maxLines: Number.POSITIVE_INFINITY,
2026
2190
  derived: true,
@@ -2039,6 +2203,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
2039
2203
  await Promise.all([...liveModeControls].map((control) => control.record(mode, reason ?? permissionChangeReason(mode, 'now'))));
2040
2204
  },
2041
2205
  promptExemptTools: () => promptExemptToolNames(registry).filter((name) => !goalToolNames.has(name)),
2206
+ describeComputerUseRef: (ref) => computerUseTool?.describeUiRef(ref),
2042
2207
  send: (messages, opts) => operations.stream(opts?.signal, (signal) => (async function* () {
2043
2208
  const selectTaskStore = beginTaskStoreReadout();
2044
2209
  const turnLimits = resolveTurnGuards(options.limits, opts?.limits);
@@ -2048,7 +2213,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
2048
2213
  const turnScope = { ...scope };
2049
2214
  const initialMode = opts?.permissionMode ??
2050
2215
  options.permissionMode ??
2051
- (opts?.onPermission ? 'prompt' : 'auto');
2216
+ (opts?.onPermission || opts?.reviewHold ? 'prompt' : 'auto');
2052
2217
  // The mode is read at every decision, so the operator can change it
2053
2218
  // while this turn runs (see permissions/live-mode.ts).
2054
2219
  const modeControl = createLiveModeControl({
@@ -2057,7 +2222,9 @@ export async function createAgentSession(prefs, detected, options = {}) {
2057
2222
  ...(recordedModes.has(String(turnScope.sessionId))
2058
2223
  ? { recorded: recordedModes.get(String(turnScope.sessionId)) }
2059
2224
  : {}),
2060
- handlerFor: (mode) => makeResumeHandler(approval, opts?.onPermission, mode, reviewExemptionFor(mode, runTools, (input) => subagentRuntime?.launchesReadOnlyAgent(input) === true), { unattendedSandboxEscape }),
2225
+ handlerFor: (mode) => opts?.reviewHold
2226
+ ? makeHoldingResumeHandler(mode, reviewExemptionFor(mode, runTools, (input) => subagentRuntime?.launchesReadOnlyAgent(input) === true), { unattendedSandboxEscape }, opts.reviewHold.reason, screenPolicyFor(() => runTools))
2227
+ : makeResumeHandler(approval, opts?.onPermission, mode, reviewExemptionFor(mode, runTools, (input) => subagentRuntime?.launchesReadOnlyAgent(input) === true), { unattendedSandboxEscape }, screenPolicyFor(() => runTools)),
2061
2228
  });
2062
2229
  const resumeHandler = modeControl.handler;
2063
2230
  liveModeControls.add(modeControl);
@@ -2106,9 +2273,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
2106
2273
  // runs — midnight passes, and the agent checks out a branch itself.
2107
2274
  // Its text only changes when a fact changes, so it costs a prompt-cache
2108
2275
  // miss exactly when a hit would have been a stale claim.
2109
- const pluginSkills = pluginRuntime
2110
- ? await currentPluginSkills(pluginRuntime.skills)
2111
- : undefined;
2276
+ const turnSkills = await turnSkillsFor(model);
2112
2277
  // One fork after plugin refresh, held through every iteration of
2113
2278
  // this send. Discovery cannot activate another send's schemas.
2114
2279
  if (options.toolLoading === 'deferred')
@@ -2121,7 +2286,11 @@ export async function createAgentSession(prefs, detected, options = {}) {
2121
2286
  }
2122
2287
  const storedMemory = await storedMemoryPrompt();
2123
2288
  if (storedMemory.notice) {
2124
- yield { kind: 'context', text: storedMemory.notice, shed: false };
2289
+ yield {
2290
+ kind: 'context',
2291
+ text: storedMemory.notice,
2292
+ shed: false,
2293
+ };
2125
2294
  }
2126
2295
  const memoryPrompt = [composeMemoryPrompt(curatedMemory), storedMemory.prompt]
2127
2296
  .filter((part) => Boolean(part))
@@ -2179,7 +2348,10 @@ export async function createAgentSession(prefs, detected, options = {}) {
2179
2348
  authorizeLearningRead: (context) => claimed.has(context.turnId) &&
2180
2349
  delegationScopes.get(context.turnId) === turnScope,
2181
2350
  })
2182
- : { contributions: createResidentStepContributions(contextOptions), tools: [] };
2351
+ : {
2352
+ contributions: createResidentStepContributions(contextOptions),
2353
+ tools: [],
2354
+ };
2183
2355
  if (bundle.tools.length) {
2184
2356
  // Per-send membership: neither another send nor delegated sessions inherit this tool.
2185
2357
  runTools = runTools.fork();
@@ -2201,7 +2373,12 @@ export async function createAgentSession(prefs, detected, options = {}) {
2201
2373
  const systemPrompt = [
2202
2374
  NAMZU_IDENTITY,
2203
2375
  residentContext ? undefined : NAMZU_WORKING_DOCTRINE,
2204
- residentContext ? undefined : NAMZU_DELEGATION_DOCTRINE,
2376
+ // Advice on delegating, for a session that has the Agent tool:
2377
+ // a scheduled run whose job withholds it would only be told how
2378
+ // to use a tool it was not sent.
2379
+ residentContext || options.withheldTools?.includes(AGENT_LAUNCH_TOOL)
2380
+ ? undefined
2381
+ : NAMZU_DELEGATION_DOCTRINE,
2205
2382
  !residentContext && opts?.orchestrate ? NAMZU_ORCHESTRATE_DOCTRINE : undefined,
2206
2383
  options.conversationSessions ? CONVERSATION_EVIDENCE_GUIDANCE : undefined,
2207
2384
  options.toolLoading === 'deferred' ? DEFERRED_TOOL_GUIDANCE : undefined,
@@ -2214,6 +2391,8 @@ export async function createAgentSession(prefs, detected, options = {}) {
2214
2391
  residentContext ? undefined : environmentPrompt,
2215
2392
  residentContext ? undefined : memoryPrompt,
2216
2393
  residentContext ? undefined : opts?.extraSystem,
2394
+ residentContext ? undefined : opts?.systemNote,
2395
+ turnSkills.overflowNote,
2217
2396
  ]
2218
2397
  .filter((s) => Boolean(s))
2219
2398
  .join('\n\n') || undefined;
@@ -2246,7 +2425,9 @@ export async function createAgentSession(prefs, detected, options = {}) {
2246
2425
  // when this session is the one bringing it into existence.
2247
2426
  let ephemeralLog;
2248
2427
  if (options.ephemeral) {
2249
- ephemeralLog = new InMemorySessionLog({ sessionId: turnScope.sessionId });
2428
+ ephemeralLog = new InMemorySessionLog({
2429
+ sessionId: turnScope.sessionId,
2430
+ });
2250
2431
  await ensureSessionStarted(ephemeralLog, {
2251
2432
  ...turnScope,
2252
2433
  cwd,
@@ -2288,8 +2469,8 @@ export async function createAgentSession(prefs, detected, options = {}) {
2288
2469
  model,
2289
2470
  tools: runTools,
2290
2471
  pluginManager: pluginRuntime?.manager,
2291
- skillRegistry: pluginRuntime?.skills,
2292
- skills: pluginSkills,
2472
+ skillRegistry: turnSkills.registry,
2473
+ skills: turnSkills.manifest,
2293
2474
  scope: turnScope,
2294
2475
  turnId,
2295
2476
  paths,
@@ -2841,6 +3022,7 @@ async function* runTurn({ retainedToolPreviewChars, fileReadTracker, provider, c
2841
3022
  // matching in the first place: `toAgentEvent` is pure over a `SessionEvent`
2842
3023
  // and could not ask a tool anything, so the host guessed from the name.
2843
3024
  const presenter = createToolPresenter(tools);
3025
+ const readsOnly = declaredReadOnly(tools);
2844
3026
  try {
2845
3027
  const events = query({
2846
3028
  ...(retainedToolPreviewChars !== undefined ? { retainedToolPreviewChars } : {}),
@@ -2959,7 +3141,7 @@ async function* runTurn({ retainedToolPreviewChars, fileReadTracker, provider, c
2959
3141
  // receipts and reasoning before the next user turn.
2960
3142
  continue;
2961
3143
  }
2962
- const mapped = toAgentEvent(event, presenter);
3144
+ const mapped = toAgentEvent(event, presenter, readsOnly);
2963
3145
  if (!mapped)
2964
3146
  continue;
2965
3147
  yield mapped;
@@ -3004,18 +3186,55 @@ async function* runTurn({ retainedToolPreviewChars, fileReadTracker, provider, c
3004
3186
  * (`createReviewHandler`); what this application adds is the person to ask
3005
3187
  * and the session's "approve all" box, which the screen also reads.
3006
3188
  */
3007
- export function makeResumeHandler(approval, onPermission, mode = onPermission ? 'prompt' : 'auto', exempt = () => false, escapePolicy = {}) {
3189
+ export function makeResumeHandler(approval, onPermission, mode = onPermission ? 'prompt' : 'auto', exempt = () => false, escapePolicy = {}, screen) {
3008
3190
  return createReviewHandler({
3009
3191
  mode,
3010
3192
  prompt: onPermission,
3011
3193
  exempt,
3012
3194
  remembered: approval,
3195
+ ...(screen ? { screenConsent: screen.consent, capturesScreen: screen.capturesScreen } : {}),
3013
3196
  // Refused unless the operator wrote `sandbox.allowUnattendedEscape`: a
3014
3197
  // session with nobody to ask has nobody to consent to leaving the
3015
3198
  // sandbox, and `auto` is not consent to a command it never showed.
3016
3199
  unattendedSandboxEscape: escapePolicy.unattendedSandboxEscape ?? 'refuse',
3017
3200
  });
3018
3201
  }
3202
+ /** Thrown by the holding prompt; never leaves {@link makeHoldingResumeHandler}. */
3203
+ class ReviewHoldSignal extends Error {
3204
+ name = 'ReviewHoldSignal';
3205
+ }
3206
+ /**
3207
+ * The kernel's review policy with a prompt that never answers: every batch
3208
+ * that would be put to a person parks the turn instead (`{ action: 'pause' }`),
3209
+ * which the kernel records durably for a person to answer later.
3210
+ *
3211
+ * The prompt throws and THIS wrapper catches it: a handler that rejects is
3212
+ * turned into `abort` by the kernel (`awaitDecisionOrAbort`), which would end
3213
+ * the turn rather than hold it. The latch is private and never set, so no
3214
+ * earlier answer can approve a later batch.
3215
+ */
3216
+ export function makeHoldingResumeHandler(mode, exempt, escapePolicy, reason, screen) {
3217
+ const inner = createReviewHandler({
3218
+ mode,
3219
+ prompt: async () => {
3220
+ throw new ReviewHoldSignal(reason);
3221
+ },
3222
+ exempt,
3223
+ remembered: { all: false },
3224
+ ...(screen ? { screenConsent: screen.consent, capturesScreen: screen.capturesScreen } : {}),
3225
+ unattendedSandboxEscape: escapePolicy.unattendedSandboxEscape ?? 'refuse',
3226
+ });
3227
+ return async (request) => {
3228
+ try {
3229
+ return await inner(request);
3230
+ }
3231
+ catch (error) {
3232
+ if (error instanceof ReviewHoldSignal)
3233
+ return { action: 'pause', reason };
3234
+ throw error;
3235
+ }
3236
+ };
3237
+ }
3019
3238
  /**
3020
3239
  * Whether a call runs without asking. The kernel's rule: a trusted read-only
3021
3240
  * declaration or a named bookkeeping write, never a fetch, never a tool the
@@ -3038,10 +3257,53 @@ export const AGENT_LAUNCH_TOOL = 'Agent';
3038
3257
  * An operator who wants every launch asked about writes
3039
3258
  * `permissions: { Agent: "ask" }`: an `ask` rule is an explicit review, which
3040
3259
  * no exemption skips.
3260
+ *
3261
+ * `save_skill` (the TUI's alone) is not asked about here either, outside
3262
+ * `strict` and `plan`: its own screen shows the whole file and where it goes,
3263
+ * and asks in every mode, `auto` included. A second question in front of it
3264
+ * would ask the same thing with less on the screen. `strict` and `plan` still
3265
+ * refuse it, and an `ask` or `deny` rule for it still applies. Neither is
3266
+ * the `schedule` tool's `create`, `update`, `resume` or `delete`, for the
3267
+ * same reason (see {@link confirmsItself}).
3041
3268
  */
3042
3269
  export function reviewExemptionFor(mode, registry, launchesReadOnlyAgent) {
3043
3270
  return (name, input) => isPromptExempt(registry, name, input) ||
3044
- (mode !== 'strict' && name === AGENT_LAUNCH_TOOL && launchesReadOnlyAgent(input));
3271
+ (mode !== 'strict' && name === AGENT_LAUNCH_TOOL && launchesReadOnlyAgent(input)) ||
3272
+ (mode !== 'strict' && mode !== 'plan' && confirmsItself(name, input)) ||
3273
+ (mode !== 'strict' &&
3274
+ mode !== 'plan' &&
3275
+ name === SAVE_SKILL_TOOL_NAME &&
3276
+ registry.has(SAVE_SKILL_TOOL_NAME));
3277
+ }
3278
+ /**
3279
+ * The `schedule` tool's `create`, `update`, `resume` and `delete`: each puts its own
3280
+ * confirmation in front of the operator, drawn from the host's computation,
3281
+ * and changes nothing unless they choose to. A review before it only asked
3282
+ * "Do you want to run schedule?" over the model's raw arguments, and then the
3283
+ * real question came. `pause` and anything else is reviewed as before; `plan`
3284
+ * and `strict` still refuse, and an `ask` or `deny` rule still decides, since
3285
+ * an explicit review is never exempted.
3286
+ */
3287
+ export function confirmsItself(name, input) {
3288
+ if (name !== SCHEDULE_TOOL_NAME || typeof input !== 'object' || input === null)
3289
+ return false;
3290
+ const action = input.action;
3291
+ return action === 'create' || action === 'update' || action === 'resume' || action === 'delete';
3292
+ }
3293
+ /**
3294
+ * A call's own read-only declaration, as the tool states it for this input.
3295
+ * Not a permission answer (that is `isPromptExempt`): it only says whether a
3296
+ * finished turn changed anything, for the TUI's skill suggestion.
3297
+ */
3298
+ function declaredReadOnly(registry) {
3299
+ return (toolName, input) => {
3300
+ try {
3301
+ return registry.get(toolName)?.isReadOnly?.(input) === true;
3302
+ }
3303
+ catch {
3304
+ return false;
3305
+ }
3306
+ };
3045
3307
  }
3046
3308
  /** The exempt roster, sorted, for the surface that has to NAME it. */
3047
3309
  export function promptExemptToolNames(registry) {
@@ -3058,7 +3320,9 @@ export const batchNeedsPrompt = batchNeedsReview;
3058
3320
  * `null` for events the chat surface doesn't render (iteration markers,
3059
3321
  * checkpoints, plan lifecycle, …). Pure — unit-tested.
3060
3322
  */
3061
- export function toAgentEvent(event, presenter) {
3323
+ export function toAgentEvent(event, presenter,
3324
+ /** Whether a call only reads, from the tool's own declaration; see `tool-start.readOnly`. */
3325
+ readsOnly) {
3062
3326
  switch (event.type) {
3063
3327
  case 'hosted_tool': {
3064
3328
  const common = {
@@ -3134,6 +3398,7 @@ export function toAgentEvent(event, presenter) {
3134
3398
  const web = webActivityFromInput(event.toolName, event.input);
3135
3399
  return web ? { web } : {};
3136
3400
  })(),
3401
+ ...(readsOnly?.(event.toolName, event.input) ? { readOnly: true } : {}),
3137
3402
  };
3138
3403
  case 'tool_progress':
3139
3404
  return {
@@ -3151,12 +3416,19 @@ export function toAgentEvent(event, presenter) {
3151
3416
  output: event.result,
3152
3417
  });
3153
3418
  const detail = viewToLines(view);
3154
- // Drop only an exact duplicate. A shortened summary cannot replace
3155
- // the first line's evidence in expanded or raw output.
3156
- const summary = view.kind === 'terminal' && detail && detail.length > 0
3157
- ? truncate(detail[0], 120)
3158
- : firstLine(event.result);
3159
- const withoutRepeatedSummary = view.kind === 'terminal' && detail?.[0] === summary ? detail.slice(1) : detail;
3419
+ // The first line is the summary row. It leaves the body only when
3420
+ // the row says all of it: a shortened summary cannot replace the
3421
+ // first line's evidence in expanded or raw output. A first line
3422
+ // up to a few rows long is shown whole, so a note like "A
3423
+ // navigation to … was blocked" is not printed twice, once cut off
3424
+ // on the summary row and again in full underneath.
3425
+ const first = view.kind === 'terminal' && view.output.trim().length > 0
3426
+ ? resultToLines(view.output)[0]
3427
+ : undefined;
3428
+ const summary = first !== undefined ? resultSummaryLine(first) : firstLine(event.result);
3429
+ const withoutRepeatedSummary = first !== undefined && detail?.[0] === first && summarySaysAll(first, summary)
3430
+ ? detail.slice(1)
3431
+ : detail;
3160
3432
  return {
3161
3433
  kind: 'tool-end',
3162
3434
  output: event.result,
@@ -3167,6 +3439,9 @@ export function toAgentEvent(event, presenter) {
3167
3439
  summary,
3168
3440
  ...(event.durationMs !== undefined ? { durationMs: event.durationMs } : {}),
3169
3441
  ...(view.kind === 'generic' && view.visibility === 'hidden' ? { hidden: true } : {}),
3442
+ ...(event.isError && view.kind === 'generic' && view.outcome === 'cancelled'
3443
+ ? { cancelled: true }
3444
+ : {}),
3170
3445
  ...(view.kind === 'generic' && view.label.length > 0 ? { resultLabel: view.label } : {}),
3171
3446
  ...(withoutRepeatedSummary && withoutRepeatedSummary.length > 0
3172
3447
  ? { detail: withoutRepeatedSummary }
@@ -3176,7 +3451,9 @@ export function toAgentEvent(event, presenter) {
3176
3451
  if (!kind)
3177
3452
  return {};
3178
3453
  const results = kind === 'search' && !event.isError ? countListedResults(event.result) : undefined;
3179
- return { web: { kind, ...(results !== undefined ? { results } : {}) } };
3454
+ return {
3455
+ web: { kind, ...(results !== undefined ? { results } : {}) },
3456
+ };
3180
3457
  })(),
3181
3458
  };
3182
3459
  }
@@ -3265,6 +3542,7 @@ export function toAgentEvent(event, presenter) {
3265
3542
  ...(event.failure ? { failure: event.failure } : {}),
3266
3543
  ...(event.providerError ? { providerError: event.providerError } : {}),
3267
3544
  ...(event.explanation ? { explanation: event.explanation } : {}),
3545
+ ...(event.handoff ? { handoff: event.handoff } : {}),
3268
3546
  };
3269
3547
  case 'turn_completed':
3270
3548
  // Carried through rather than dropped: `turn_failed` fires only from
@@ -3448,8 +3726,10 @@ export function viewToLines(view) {
3448
3726
  if (view.output.trim().length === 0)
3449
3727
  return undefined;
3450
3728
  const lines = resultToLines(view.output);
3451
- // A single short line is already the summary — no need to repeat it.
3452
- return lines.length === 1 && lines[0] === truncate(lines[0] ?? '', 120) ? undefined : lines;
3729
+ // A single line the summary row shows whole is not repeated under it.
3730
+ return lines.length === 1 && summarySaysAll(lines[0] ?? '', resultSummaryLine(lines[0] ?? ''))
3731
+ ? undefined
3732
+ : lines;
3453
3733
  }
3454
3734
  }
3455
3735
  }
@@ -3464,6 +3744,21 @@ export function viewToSummary(view) {
3464
3744
  return truncate(view.command ?? view.output.split('\n')[0] ?? '', 120);
3465
3745
  }
3466
3746
  }
3747
+ /**
3748
+ * The longest first line a result's summary row shows whole, about three
3749
+ * terminal rows. Longer, the row shows its first 120 characters and the body
3750
+ * keeps the line.
3751
+ */
3752
+ export const RESULT_SUMMARY_WHOLE_MAX = 300;
3753
+ /** A result's summary row for its first line: whole when short enough, else shortened. */
3754
+ export function resultSummaryLine(line) {
3755
+ const flat = line.replace(/\s+/g, ' ');
3756
+ return flat.length <= RESULT_SUMMARY_WHOLE_MAX ? flat : truncate(line, 120);
3757
+ }
3758
+ /** The summary row says everything `line` does (whitespace aside). */
3759
+ function summarySaysAll(line, summary) {
3760
+ return line === summary || line.replace(/\s+/g, ' ') === summary;
3761
+ }
3467
3762
  function truncate(value, max) {
3468
3763
  const oneLine = value.replace(/\s+/g, ' ');
3469
3764
  return oneLine.length > max ? `${oneLine.slice(0, max - 1)}…` : oneLine;
@@ -3573,7 +3868,9 @@ function logCapabilities(probes, runtime) {
3573
3868
  ? runtime.sandboxReady
3574
3869
  : p.specifier === '@namzu/computer-use'
3575
3870
  ? runtime.computerUseReady
3576
- : p.state === 'present';
3871
+ : p.specifier === '@namzu/browser'
3872
+ ? runtime.browserReady === true
3873
+ : p.state === 'present';
3577
3874
  return `${p.specifier.split('/').pop()} ${present ? 'yes' : 'no'}`;
3578
3875
  })
3579
3876
  .join(' · ');
@@ -3597,6 +3894,22 @@ function logCapabilities(probes, runtime) {
3597
3894
  ...(probe.state === 'present' ? { 'namzu.capability.version': probe.version } : {}),
3598
3895
  });
3599
3896
  }
3897
+ if (runtime.browser) {
3898
+ // The engine and profile the browser tools will use; nothing is running yet.
3899
+ log.info('Browser tools mounted', {
3900
+ [EVENT_NAME_ATTRIBUTE]: BOOT_EVENT_NAMES.CAPABILITY_DETECTED,
3901
+ 'namzu.capability.name': '@namzu/browser',
3902
+ 'namzu.capability.state': runtime.browserUnavailable === undefined ? 'ready' : 'unavailable',
3903
+ 'namzu.capability.present': runtime.browserReady === true,
3904
+ 'namzu.browser.engine': runtime.browser.engine,
3905
+ 'namzu.browser.profile': runtime.browser.profile,
3906
+ 'namzu.browser.headless': runtime.browser.headless,
3907
+ 'namzu.browser.site_count': Object.keys(runtime.browser.sites).length,
3908
+ ...(runtime.browserUnavailable !== undefined
3909
+ ? { 'namzu.browser.unavailable_reason': runtime.browserUnavailable }
3910
+ : {}),
3911
+ });
3912
+ }
3600
3913
  if (runtime.computerUseError) {
3601
3914
  log.warn('Computer use adapter is unavailable', {
3602
3915
  [EVENT_NAME_ATTRIBUTE]: BOOT_EVENT_NAMES.CAPABILITY_DETECTED,