@crouton-kit/crouter 0.3.32 → 0.3.33

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 (440) hide show
  1. package/README.md +8 -0
  2. package/dist/build-root.js +1 -0
  3. package/dist/builtin-memory/crouter-development/marketplaces.md +2 -2
  4. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +2 -2
  5. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +5 -5
  6. package/dist/builtin-memory/crouter-development/personas.md +3 -4
  7. package/dist/builtin-memory/crouter-development/plugins.md +18 -18
  8. package/dist/builtin-memory/design.md +4 -1
  9. package/dist/builtin-memory/development.md +4 -1
  10. package/dist/builtin-memory/internal/INDEX.md +1 -1
  11. package/dist/builtin-memory/internal/nodes-and-canvas.md +6 -6
  12. package/dist/builtin-memory/internal/storage-tiers.md +5 -5
  13. package/dist/builtin-memory/planning.md +4 -1
  14. package/dist/builtin-memory/product.md +80 -0
  15. package/dist/builtin-memory/spec.md +4 -1
  16. package/dist/builtin-personas/advisor/PERSONA.md +10 -0
  17. package/dist/builtin-personas/design/PERSONA.md +2 -2
  18. package/dist/builtin-personas/design/orchestrator.md +3 -3
  19. package/dist/builtin-personas/developer/PERSONA.md +1 -1
  20. package/dist/builtin-personas/developer/orchestrator.md +2 -2
  21. package/dist/builtin-personas/explore/PERSONA.md +3 -3
  22. package/dist/builtin-personas/explore/orchestrator.md +4 -2
  23. package/dist/builtin-personas/general/PERSONA.md +1 -1
  24. package/dist/builtin-personas/general/orchestrator.md +1 -1
  25. package/dist/builtin-personas/orchestration-kernel.md +7 -14
  26. package/dist/builtin-personas/plan/PERSONA.md +1 -1
  27. package/dist/builtin-personas/plan/orchestrator.md +2 -2
  28. package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -1
  29. package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -1
  30. package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -1
  31. package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -1
  32. package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -1
  33. package/dist/builtin-personas/product/PERSONA.md +18 -0
  34. package/dist/builtin-personas/product/orchestrator.md +14 -0
  35. package/dist/builtin-personas/product/teardown/PERSONA.md +13 -0
  36. package/dist/builtin-personas/review/PERSONA.md +1 -1
  37. package/dist/builtin-personas/review/orchestrator.md +1 -1
  38. package/dist/builtin-personas/runtime-base.md +5 -0
  39. package/dist/builtin-personas/spec/PERSONA.md +1 -1
  40. package/dist/builtin-personas/spec/orchestrator.md +2 -2
  41. package/dist/builtin-personas/spec/requirements/PERSONA.md +1 -1
  42. package/dist/builtin-views/canvas/core.mjs +82 -1
  43. package/dist/builtin-views/canvas/tui.mjs +9 -5
  44. package/dist/builtin-views/canvas/web.jsx +3 -2
  45. package/dist/builtin-views/chat/core.mjs +725 -0
  46. package/dist/builtin-views/chat/text.mjs +101 -0
  47. package/dist/builtin-views/chat/tui.mjs +368 -0
  48. package/dist/builtin-views/chat/web.jsx +367 -0
  49. package/dist/builtin-views/prompt-review/core.mjs +863 -0
  50. package/dist/builtin-views/prompt-review/text.mjs +15 -0
  51. package/dist/builtin-views/prompt-review/tui.mjs +196 -0
  52. package/dist/builtin-views/prompt-review/web.jsx +484 -0
  53. package/dist/builtin-views/settings/core.mjs +397 -0
  54. package/dist/builtin-views/settings/text.mjs +40 -0
  55. package/dist/builtin-views/settings/tui.mjs +95 -0
  56. package/dist/builtin-views/settings/web.jsx +167 -0
  57. package/dist/cli.js +16 -3
  58. package/dist/clients/attach/__tests__/autocomplete-and-bash-mode.test.js +96 -0
  59. package/dist/clients/attach/__tests__/bash-bang-routing.test.js +58 -0
  60. package/dist/clients/attach/__tests__/bundle-pi-tui-dedup.test.d.ts +1 -0
  61. package/dist/clients/attach/__tests__/bundle-pi-tui-dedup.test.js +56 -0
  62. package/dist/clients/attach/__tests__/titled-editor.test.js +22 -1
  63. package/dist/clients/attach/attach-cmd.d.ts +18 -0
  64. package/dist/clients/attach/attach-cmd.js +1723 -795
  65. package/dist/clients/attach/canvas-panels.js +2 -2
  66. package/dist/clients/attach/chat-view.d.ts +10 -0
  67. package/dist/clients/attach/chat-view.js +23 -0
  68. package/dist/clients/attach/config-load.d.ts +8 -4
  69. package/dist/clients/attach/config-load.js +2 -0
  70. package/dist/clients/attach/extension-dialogs.d.ts +2 -3
  71. package/dist/clients/attach/extension-dialogs.js +7 -9
  72. package/dist/clients/attach/graph-overlay.js +3 -2
  73. package/dist/clients/attach/input-controller.d.ts +4 -3
  74. package/dist/clients/attach/input-controller.js +24 -3
  75. package/dist/clients/attach/slash-commands.d.ts +7 -7
  76. package/dist/clients/attach/slash-commands.js +38 -14
  77. package/dist/clients/attach/titled-editor.js +18 -14
  78. package/dist/clients/attach/view-socket.d.ts +2 -1
  79. package/dist/clients/attach/view-socket.js +27 -8
  80. package/dist/clients/web/__tests__/source-cache.test.d.ts +1 -0
  81. package/dist/clients/web/__tests__/source-cache.test.js +32 -0
  82. package/dist/clients/web/dev-server.js +1 -0
  83. package/dist/clients/web/events.js +9 -11
  84. package/dist/clients/web/server.d.ts +4 -3
  85. package/dist/clients/web/server.js +138 -31
  86. package/dist/clients/web/source-cache.d.ts +10 -0
  87. package/dist/clients/web/source-cache.js +57 -0
  88. package/dist/clients/web/web-cmd.js +28 -9
  89. package/dist/commands/__tests__/human.test.js +30 -28
  90. package/dist/commands/canvas-history/search.js +1 -1
  91. package/dist/commands/canvas-history.js +5 -8
  92. package/dist/commands/canvas-issue.d.ts +2 -0
  93. package/dist/commands/canvas-issue.js +148 -0
  94. package/dist/commands/canvas-prune.js +19 -0
  95. package/dist/commands/canvas-rebuild-index.d.ts +2 -0
  96. package/dist/commands/canvas-rebuild-index.js +57 -0
  97. package/dist/commands/canvas-snapshot.d.ts +2 -0
  98. package/dist/commands/canvas-snapshot.js +48 -0
  99. package/dist/commands/canvas-tmux-spread.d.ts +2 -0
  100. package/dist/commands/canvas-tmux-spread.js +188 -0
  101. package/dist/commands/canvas.d.ts +1 -0
  102. package/dist/commands/canvas.js +16 -1
  103. package/dist/commands/chord.js +143 -48
  104. package/dist/commands/daemon.js +3 -3
  105. package/dist/commands/human/prompts.d.ts +0 -1
  106. package/dist/commands/human/prompts.js +39 -54
  107. package/dist/commands/human/queue.d.ts +12 -1
  108. package/dist/commands/human/queue.js +468 -73
  109. package/dist/commands/human/shared.d.ts +3 -4
  110. package/dist/commands/human/shared.js +3 -3
  111. package/dist/commands/human.js +11 -10
  112. package/dist/commands/memory/find.js +3 -2
  113. package/dist/commands/memory/list.js +4 -3
  114. package/dist/commands/memory/read.js +3 -3
  115. package/dist/commands/memory/shared.d.ts +1 -4
  116. package/dist/commands/memory/shared.js +6 -9
  117. package/dist/commands/memory/write.js +3 -3
  118. package/dist/commands/memory.js +4 -7
  119. package/dist/commands/node-context.d.ts +2 -0
  120. package/dist/commands/node-context.js +172 -0
  121. package/dist/commands/node-snapshot.d.ts +2 -0
  122. package/dist/commands/node-snapshot.js +123 -0
  123. package/dist/commands/node.js +228 -52
  124. package/dist/commands/pkg/plugin-inspect.js +6 -6
  125. package/dist/commands/pkg/plugin-manage.js +1 -1
  126. package/dist/commands/pkg/plugin.js +2 -2
  127. package/dist/commands/pkg.js +2 -2
  128. package/dist/commands/push.d.ts +2 -0
  129. package/dist/commands/push.js +79 -11
  130. package/dist/commands/revive.js +74 -1
  131. package/dist/commands/search/answer.d.ts +1 -0
  132. package/dist/commands/search/answer.js +50 -0
  133. package/dist/commands/search/contents.d.ts +1 -0
  134. package/dist/commands/search/contents.js +96 -0
  135. package/dist/commands/search/exa.d.ts +53 -0
  136. package/dist/commands/search/exa.js +155 -0
  137. package/dist/commands/search/puremd.d.ts +11 -0
  138. package/dist/commands/search/puremd.js +53 -0
  139. package/dist/commands/search/web.d.ts +1 -0
  140. package/dist/commands/search/web.js +65 -0
  141. package/dist/commands/search.d.ts +2 -0
  142. package/dist/commands/search.js +24 -0
  143. package/dist/commands/sys/__tests__/sync-import.test.d.ts +1 -0
  144. package/dist/commands/sys/__tests__/sync-import.test.js +72 -0
  145. package/dist/commands/sys/config.js +59 -10
  146. package/dist/commands/sys/doctor.js +56 -4
  147. package/dist/commands/sys/prompt-review.d.ts +1 -0
  148. package/dist/commands/sys/prompt-review.js +178 -0
  149. package/dist/commands/sys/promptstudio.d.ts +2 -0
  150. package/dist/commands/sys/promptstudio.js +65 -0
  151. package/dist/commands/sys/settings.d.ts +2 -0
  152. package/dist/commands/sys/settings.js +16 -0
  153. package/dist/commands/sys/sync.js +311 -159
  154. package/dist/commands/sys/sysprompt.d.ts +1 -0
  155. package/dist/commands/sys/sysprompt.js +86 -0
  156. package/dist/commands/sys.js +9 -5
  157. package/dist/commands/view-new.js +2 -2
  158. package/dist/core/__tests__/artifact-paths.test.d.ts +1 -0
  159. package/dist/core/__tests__/artifact-paths.test.js +44 -0
  160. package/dist/core/__tests__/broker-preflight.test.d.ts +1 -0
  161. package/dist/core/__tests__/broker-preflight.test.js +85 -0
  162. package/dist/core/__tests__/broker-sdk-wiring.test.js +15 -0
  163. package/dist/core/__tests__/canvas.test.js +37 -1
  164. package/dist/core/__tests__/chat-view-reconnect.test.d.ts +1 -0
  165. package/dist/core/__tests__/chat-view-reconnect.test.js +70 -0
  166. package/dist/core/__tests__/child-death-wake.test.js +11 -2
  167. package/dist/core/__tests__/close.test.js +39 -1
  168. package/dist/core/__tests__/connection-reconnect-resume.test.d.ts +1 -0
  169. package/dist/core/__tests__/connection-reconnect-resume.test.js +37 -0
  170. package/dist/core/__tests__/context-intro.test.js +48 -14
  171. package/dist/core/__tests__/daemon-boot.test.js +182 -9
  172. package/dist/core/__tests__/editor-label.test.d.ts +1 -0
  173. package/dist/core/__tests__/editor-label.test.js +26 -0
  174. package/dist/core/__tests__/fault-marker.test.d.ts +1 -0
  175. package/dist/core/__tests__/fault-marker.test.js +112 -0
  176. package/dist/core/__tests__/fault-retry-rewind.test.d.ts +1 -0
  177. package/dist/core/__tests__/fault-retry-rewind.test.js +123 -0
  178. package/dist/core/__tests__/fixtures/fake-engine.d.ts +30 -4
  179. package/dist/core/__tests__/fixtures/fake-engine.js +156 -17
  180. package/dist/core/__tests__/fixtures/fake-pi-host.js +10 -1
  181. package/dist/core/__tests__/full/human-new-window-regression.test.js +1 -1
  182. package/dist/core/__tests__/helpers/harness.d.ts +1 -1
  183. package/dist/core/__tests__/helpers/harness.js +2 -2
  184. package/dist/core/__tests__/human-stranded-deliver.test.js +38 -1
  185. package/dist/core/__tests__/human-surface-target.test.js +1 -1
  186. package/dist/core/__tests__/kickoff.test.js +18 -19
  187. package/dist/core/__tests__/memory-resolver.test.js +12 -1
  188. package/dist/core/__tests__/migration.test.js +49 -14
  189. package/dist/core/__tests__/model-ladders.test.d.ts +1 -0
  190. package/dist/core/__tests__/model-ladders.test.js +160 -0
  191. package/dist/core/__tests__/node-env.test.d.ts +1 -0
  192. package/dist/core/__tests__/node-env.test.js +26 -0
  193. package/dist/core/__tests__/push-final-guard.test.js +7 -1
  194. package/dist/core/__tests__/relaunch-root.test.js +9 -9
  195. package/dist/core/__tests__/revive.test.js +36 -6
  196. package/dist/core/__tests__/spawn-root.test.js +24 -1
  197. package/dist/core/__tests__/stop-guard.test.js +10 -0
  198. package/dist/core/__tests__/stranded-relaunch.test.d.ts +1 -0
  199. package/dist/core/__tests__/stranded-relaunch.test.js +72 -0
  200. package/dist/core/__tests__/tmux-surface.test.js +5 -1
  201. package/dist/core/__tests__/unknown-path.test.js +19 -19
  202. package/dist/core/__tests__/yield-ensures-daemon.test.d.ts +1 -0
  203. package/dist/core/__tests__/yield-ensures-daemon.test.js +54 -0
  204. package/dist/core/artifact.d.ts +2 -33
  205. package/dist/core/artifact.js +27 -87
  206. package/dist/core/bootstrap.js +2 -0
  207. package/dist/core/canvas/browse/__tests__/model.test.js +35 -1
  208. package/dist/core/canvas/browse/app.js +87 -17
  209. package/dist/core/canvas/browse/model.d.ts +10 -1
  210. package/dist/core/canvas/browse/model.js +37 -1
  211. package/dist/core/canvas/browse/render.js +35 -33
  212. package/dist/core/canvas/canvas.d.ts +7 -0
  213. package/dist/core/canvas/canvas.js +54 -5
  214. package/dist/core/canvas/db.js +35 -8
  215. package/dist/core/canvas/labels.d.ts +6 -8
  216. package/dist/core/canvas/labels.js +8 -11
  217. package/dist/core/canvas/nav-model.d.ts +5 -4
  218. package/dist/core/canvas/nav-model.js +31 -17
  219. package/dist/core/canvas/paths.d.ts +18 -1
  220. package/dist/core/canvas/paths.js +31 -2
  221. package/dist/core/canvas/render.d.ts +25 -0
  222. package/dist/core/canvas/render.js +96 -13
  223. package/dist/core/canvas/status-glyph.d.ts +35 -0
  224. package/dist/core/canvas/status-glyph.js +104 -0
  225. package/dist/core/canvas/types.d.ts +2 -2
  226. package/dist/core/config.js +114 -16
  227. package/dist/core/fault-classifier.d.ts +41 -0
  228. package/dist/core/fault-classifier.js +110 -0
  229. package/dist/core/feed/inbox.d.ts +15 -0
  230. package/dist/core/feed/inbox.js +40 -14
  231. package/dist/core/frontmatter.d.ts +1 -11
  232. package/dist/core/frontmatter.js +1 -55
  233. package/dist/core/hearth/config.d.ts +2 -0
  234. package/dist/core/hearth/config.js +75 -0
  235. package/dist/core/hearth/guest-env.d.ts +2 -0
  236. package/dist/core/hearth/guest-env.js +11 -0
  237. package/dist/core/hearth/index.d.ts +5 -0
  238. package/dist/core/hearth/index.js +5 -0
  239. package/dist/core/hearth/provider.d.ts +21 -0
  240. package/dist/core/hearth/provider.js +10 -0
  241. package/dist/core/hearth/providers/blaxel-bootstrap.d.ts +10 -0
  242. package/dist/core/hearth/providers/blaxel-bootstrap.js +39 -0
  243. package/dist/core/hearth/providers/blaxel-home.d.ts +11 -0
  244. package/dist/core/hearth/providers/blaxel-home.js +126 -0
  245. package/dist/core/hearth/providers/blaxel.d.ts +26 -0
  246. package/dist/core/hearth/providers/blaxel.js +208 -0
  247. package/dist/core/hearth/providers/types.d.ts +72 -0
  248. package/dist/core/hearth/providers/types.js +1 -0
  249. package/dist/core/hearth/registry.d.ts +15 -0
  250. package/dist/core/hearth/registry.js +179 -0
  251. package/dist/core/hearth/types.d.ts +121 -0
  252. package/dist/core/hearth/types.js +1 -0
  253. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.d.ts +1 -0
  254. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +79 -0
  255. package/dist/core/{skill-sync → host-exports}/builtins.d.ts +21 -13
  256. package/dist/core/host-exports/builtins.js +68 -0
  257. package/dist/core/{skill-sync → host-exports}/export.d.ts +9 -6
  258. package/dist/core/{skill-sync → host-exports}/export.js +37 -14
  259. package/dist/core/io.js +1 -1
  260. package/dist/core/log.d.ts +9 -0
  261. package/dist/core/log.js +113 -0
  262. package/dist/core/memory-resolver.d.ts +8 -8
  263. package/dist/core/memory-resolver.js +15 -28
  264. package/dist/core/personas/index.d.ts +4 -4
  265. package/dist/core/personas/index.js +2 -2
  266. package/dist/core/personas/loader.d.ts +51 -0
  267. package/dist/core/personas/loader.js +54 -50
  268. package/dist/core/personas/resolve.d.ts +43 -27
  269. package/dist/core/personas/resolve.js +336 -94
  270. package/dist/core/runtime/auth-reload.d.ts +7 -0
  271. package/dist/core/runtime/auth-reload.js +69 -0
  272. package/dist/core/runtime/bearings.d.ts +35 -15
  273. package/dist/core/runtime/bearings.js +305 -44
  274. package/dist/core/runtime/branded-host.d.ts +4 -2
  275. package/dist/core/runtime/branded-host.js +66 -4
  276. package/dist/core/runtime/broker-protocol.d.ts +38 -3
  277. package/dist/core/runtime/broker.js +343 -18
  278. package/dist/core/runtime/close.js +37 -29
  279. package/dist/core/runtime/connectivity.d.ts +12 -0
  280. package/dist/core/runtime/connectivity.js +73 -0
  281. package/dist/core/runtime/fault-recovery-nudge.d.ts +2 -0
  282. package/dist/core/runtime/fault-recovery-nudge.js +2 -0
  283. package/dist/core/runtime/fault-recovery.d.ts +23 -0
  284. package/dist/core/runtime/fault-recovery.js +74 -0
  285. package/dist/core/runtime/fault.d.ts +25 -0
  286. package/dist/core/runtime/fault.js +176 -0
  287. package/dist/core/runtime/front-door.d.ts +1 -1
  288. package/dist/core/runtime/front-door.js +2 -2
  289. package/dist/core/runtime/host.d.ts +10 -0
  290. package/dist/core/runtime/host.js +71 -9
  291. package/dist/core/runtime/kickoff.js +31 -45
  292. package/dist/core/runtime/launch.d.ts +32 -18
  293. package/dist/core/runtime/launch.js +148 -43
  294. package/dist/core/runtime/model-swap.d.ts +18 -0
  295. package/dist/core/runtime/model-swap.js +95 -0
  296. package/dist/core/runtime/naming.d.ts +7 -0
  297. package/dist/core/runtime/naming.js +61 -9
  298. package/dist/core/runtime/nodes.js +6 -1
  299. package/dist/core/runtime/persona.js +16 -17
  300. package/dist/core/runtime/placement.d.ts +36 -31
  301. package/dist/core/runtime/placement.js +149 -64
  302. package/dist/core/runtime/promote.d.ts +2 -0
  303. package/dist/core/runtime/promote.js +23 -3
  304. package/dist/core/runtime/recycle.js +6 -4
  305. package/dist/core/runtime/reset.d.ts +5 -2
  306. package/dist/core/runtime/reset.js +2 -2
  307. package/dist/core/runtime/revive.d.ts +4 -0
  308. package/dist/core/runtime/revive.js +27 -9
  309. package/dist/core/runtime/roadmap.d.ts +1 -1
  310. package/dist/core/runtime/roadmap.js +1 -1
  311. package/dist/core/runtime/spawn.d.ts +2 -2
  312. package/dist/core/runtime/spawn.js +30 -6
  313. package/dist/core/runtime/stop-guard.js +6 -1
  314. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  315. package/dist/core/runtime/tmux-chrome.js +1 -1
  316. package/dist/core/runtime/tmux.d.ts +29 -6
  317. package/dist/core/runtime/tmux.js +134 -80
  318. package/dist/core/runtime/view-socket-client.d.ts +46 -0
  319. package/dist/core/runtime/view-socket-client.js +203 -0
  320. package/dist/core/substrate/index.d.ts +1 -1
  321. package/dist/core/substrate/index.js +1 -1
  322. package/dist/core/substrate/on-read.js +16 -4
  323. package/dist/core/substrate/render.d.ts +10 -13
  324. package/dist/core/substrate/render.js +56 -51
  325. package/dist/core/tui/host.js +83 -18
  326. package/dist/core/view/__tests__/transport-cache.test.d.ts +1 -0
  327. package/dist/core/view/__tests__/transport-cache.test.js +62 -0
  328. package/dist/core/view/contract.d.ts +20 -0
  329. package/dist/core/view/stream-local.d.ts +3 -0
  330. package/dist/core/view/stream-local.js +184 -0
  331. package/dist/core/view/transport-cache.d.ts +8 -0
  332. package/dist/core/view/transport-cache.js +38 -0
  333. package/dist/core/view/transport-local.js +4 -0
  334. package/dist/core/view/transport.d.ts +7 -1
  335. package/dist/daemon/crtrd.d.ts +3 -19
  336. package/dist/daemon/crtrd.js +139 -178
  337. package/dist/daemon/manage.d.ts +18 -1
  338. package/dist/daemon/manage.js +54 -9
  339. package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.d.ts +1 -0
  340. package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.js +289 -0
  341. package/dist/hearth/wake-proxy/__tests__/config-timeout.test.d.ts +1 -0
  342. package/dist/hearth/wake-proxy/__tests__/config-timeout.test.js +34 -0
  343. package/dist/hearth/wake-proxy/__tests__/guest-source.test.d.ts +1 -0
  344. package/dist/hearth/wake-proxy/__tests__/guest-source.test.js +203 -0
  345. package/dist/hearth/wake-proxy/__tests__/hardening.test.d.ts +1 -0
  346. package/dist/hearth/wake-proxy/__tests__/hardening.test.js +59 -0
  347. package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.d.ts +1 -0
  348. package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.js +372 -0
  349. package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.d.ts +1 -0
  350. package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.js +258 -0
  351. package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.d.ts +1 -0
  352. package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.js +437 -0
  353. package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.d.ts +1 -0
  354. package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.js +455 -0
  355. package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.d.ts +1 -0
  356. package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.js +15 -0
  357. package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.d.ts +1 -0
  358. package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.js +141 -0
  359. package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.d.ts +1 -0
  360. package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.js +143 -0
  361. package/dist/hearth/wake-proxy/anthropic-oauth.d.ts +58 -0
  362. package/dist/hearth/wake-proxy/anthropic-oauth.js +189 -0
  363. package/dist/hearth/wake-proxy/auth.d.ts +22 -0
  364. package/dist/hearth/wake-proxy/auth.js +128 -0
  365. package/dist/hearth/wake-proxy/config.d.ts +2 -0
  366. package/dist/hearth/wake-proxy/config.js +151 -0
  367. package/dist/hearth/wake-proxy/guest-source.d.ts +14 -0
  368. package/dist/hearth/wake-proxy/guest-source.js +130 -0
  369. package/dist/hearth/wake-proxy/hearth-status.d.ts +44 -0
  370. package/dist/hearth/wake-proxy/hearth-status.js +267 -0
  371. package/dist/hearth/wake-proxy/home.d.ts +67 -0
  372. package/dist/hearth/wake-proxy/home.js +297 -0
  373. package/dist/hearth/wake-proxy/main.d.ts +1 -0
  374. package/dist/hearth/wake-proxy/main.js +134 -0
  375. package/dist/hearth/wake-proxy/model-auth.d.ts +13 -0
  376. package/dist/hearth/wake-proxy/model-auth.js +345 -0
  377. package/dist/hearth/wake-proxy/proxy.d.ts +35 -0
  378. package/dist/hearth/wake-proxy/proxy.js +716 -0
  379. package/dist/hearth/wake-proxy/public-source-gate.d.ts +9 -0
  380. package/dist/hearth/wake-proxy/public-source-gate.js +409 -0
  381. package/dist/hearth/wake-proxy/redact.d.ts +1 -0
  382. package/dist/hearth/wake-proxy/redact.js +17 -0
  383. package/dist/hearth/wake-proxy/server.d.ts +11 -0
  384. package/dist/hearth/wake-proxy/server.js +142 -0
  385. package/dist/hearth/wake-proxy/state.d.ts +18 -0
  386. package/dist/hearth/wake-proxy/state.js +342 -0
  387. package/dist/hearth/wake-proxy/types.d.ts +76 -0
  388. package/dist/hearth/wake-proxy/types.js +1 -0
  389. package/dist/index.js +3 -2
  390. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +6 -5
  391. package/dist/pi-extensions/canvas-context-intro.js +7 -7
  392. package/dist/pi-extensions/canvas-doc-substrate.js +19 -22
  393. package/dist/pi-extensions/canvas-nav.js +15 -8
  394. package/dist/pi-extensions/canvas-stophook.d.ts +1 -1
  395. package/dist/pi-extensions/canvas-stophook.js +78 -11
  396. package/dist/types.d.ts +39 -20
  397. package/dist/types.js +38 -13
  398. package/dist/web/runtime.js +141 -42
  399. package/dist/web/transport-http.js +7 -5
  400. package/dist/web/transport-stream.d.ts +3 -0
  401. package/dist/web/transport-stream.js +204 -0
  402. package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
  403. package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
  404. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
  405. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
  406. package/dist/web-client/assets/fraunces-latin-ext-wght-normal-Ca2vKHc0.woff2 +0 -0
  407. package/dist/web-client/assets/fraunces-latin-wght-normal-ukD16Tqj.woff2 +0 -0
  408. package/dist/web-client/assets/fraunces-vietnamese-wght-normal-CnvboYUG.woff2 +0 -0
  409. package/dist/web-client/assets/index-BZUxTkv5.css +2 -0
  410. package/dist/web-client/assets/index-D36PNBj4.js +80 -0
  411. package/dist/web-client/assets/instrument-sans-latin-ext-wght-normal-B5bTHO_g.woff2 +0 -0
  412. package/dist/web-client/assets/instrument-sans-latin-wght-normal-BbzFLZTg.woff2 +0 -0
  413. package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
  414. package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
  415. package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
  416. package/dist/web-client/index.html +3 -2
  417. package/package.json +32 -10
  418. package/dist/core/__tests__/error-stall-recycle.test.js +0 -141
  419. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +0 -57
  420. package/dist/core/skill-sync/builtins.js +0 -112
  421. package/dist/core/skill-sync/claude-plugins.d.ts +0 -23
  422. package/dist/core/skill-sync/claude-plugins.js +0 -71
  423. package/dist/core/skill-sync/engine.d.ts +0 -42
  424. package/dist/core/skill-sync/engine.js +0 -633
  425. package/dist/core/skill-sync/manifest.d.ts +0 -64
  426. package/dist/core/skill-sync/manifest.js +0 -181
  427. package/dist/core/skill-sync/profile.d.ts +0 -76
  428. package/dist/core/skill-sync/profile.js +0 -173
  429. package/dist/core/skill-sync/snapshot.d.ts +0 -57
  430. package/dist/core/skill-sync/snapshot.js +0 -120
  431. package/dist/pi-extensions/canvas-commands.d.ts +0 -37
  432. package/dist/pi-extensions/canvas-commands.js +0 -113
  433. package/dist/pi-extensions/canvas-resume.d.ts +0 -21
  434. package/dist/pi-extensions/canvas-resume.js +0 -83
  435. package/dist/pi-extensions/canvas-view.d.ts +0 -21
  436. package/dist/pi-extensions/canvas-view.js +0 -76
  437. package/dist/web-client/assets/index-BUvQb4hR.css +0 -2
  438. package/dist/web-client/assets/index-ClLQXYAE.js +0 -10
  439. /package/dist/{core/__tests__/error-stall-recycle.test.d.ts → clients/attach/__tests__/autocomplete-and-bash-mode.test.d.ts} +0 -0
  440. /package/dist/{core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts → clients/attach/__tests__/bash-bang-routing.test.d.ts} +0 -0
@@ -3,13 +3,13 @@ import { InputError } from '../../core/io.js';
3
3
  import { pushFinal } from '../../core/feed/feed.js';
4
4
  import { interactionsRoot, interactionDir } from '../../core/artifact.js';
5
5
  import { paginate } from '../../core/pagination.js';
6
- import { getNode, subscribersOf } from '../../core/canvas/index.js';
6
+ import { getNode, listNodes, subscribersOf } from '../../core/canvas/index.js';
7
7
  import { transition } from '../../core/runtime/lifecycle.js';
8
8
  import { appendInbox } from '../../core/feed/inbox.js';
9
- import { existsSync, readdirSync } from 'node:fs';
10
- import { join } from 'node:path';
9
+ import { closeSync, existsSync, linkSync, openSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs';
10
+ import { basename, join } from 'node:path';
11
11
  import { spawnSync } from 'node:child_process';
12
- import { inbox, scanInbox, parseDeck, deckPath, responsePath, isResolved, atomicWriteJson, ask, launchReview, readJson, display, } from '@crouton-kit/humanloop';
12
+ import { inbox, scanInbox, parseDeck, deckPath, responsePath, progressPath, isResolved, atomicWriteJson, ask, launchReview, readJson, display, } from '@crouton-kit/humanloop';
13
13
  import { killPane } from './shared.js';
14
14
  // ---------------------------------------------------------------------------
15
15
  // stranded-answer healing
@@ -18,12 +18,26 @@ import { killPane } from './shared.js';
18
18
  * `<id>: <option>[ — <freetext>]` shape. Used when reconstructing a
19
19
  * ResolutionEnvelope from a bare on-disk response.json (which stores only
20
20
  * `{responses, completedAt}`), so a healed deliver-back reads like the live one. */
21
- function summarizeResponses(responses) {
21
+ function summarizeResponses(responses, deck) {
22
+ const interactions = new Map((deck?.interactions ?? []).map((it) => [it.id, it]));
22
23
  return responses
23
24
  .map((r) => {
24
- const opt = r.selectedOptionId ?? '';
25
+ const it = interactions.get(r.id);
26
+ let picked = '';
27
+ if (r.selectedOptionIds !== undefined) {
28
+ picked = r.selectedOptionIds
29
+ .map((id) => {
30
+ const label = it?.options.find((o) => o.id === id)?.label ?? id;
31
+ const note = r.optionComments?.[id];
32
+ return typeof note === 'string' && note.trim() !== '' ? `${label} ("${note.trim()}")` : label;
33
+ })
34
+ .join(', ');
35
+ }
36
+ else if (r.selectedOptionId !== undefined) {
37
+ picked = it?.options.find((o) => o.id === r.selectedOptionId)?.label ?? r.selectedOptionId;
38
+ }
25
39
  const ft = typeof r.freetext === 'string' && r.freetext.trim() !== '' ? ` — ${r.freetext.trim()}` : '';
26
- return `${r.id}: ${opt}${ft}`;
40
+ return `${it?.title ?? r.id}: ${picked}${ft}`;
27
41
  })
28
42
  .join('\n');
29
43
  }
@@ -39,47 +53,56 @@ function summarizeResponses(responses) {
39
53
  * flips status=done). Reconstructs the same pushFinal body `_run` would have
40
54
  * emitted, per mode; a canceled-on-disk response reaps the node without
41
55
  * delivering a result (mirrors `human cancel`). Returns true iff it acted. */
42
- export async function finalizeResolvedInteraction(jobId) {
43
- const node = getNode(jobId);
44
- if (node === null)
45
- return false;
46
- if (node.status !== 'active' && node.status !== 'idle')
56
+ export async function finalizeResolvedInteraction(jobId, claim) {
57
+ const firstNode = getNode(jobId);
58
+ if (firstNode === null)
47
59
  return false;
48
- const idir = interactionDir(jobId, node.cwd);
49
- if (!isResolved(idir))
60
+ const idir = interactionDir(jobId, firstNode.cwd);
61
+ const ownedClaim = claim ?? acquireInteractionClaim(idir, jobId, { allowResolved: true });
62
+ if (ownedClaim === 'already_resolved' || ownedClaim === 'claimed')
50
63
  return false;
51
- const rc = readJson(join(idir, 'run.json'));
52
- if (rc === null)
53
- return false;
54
- const resp = readJson(responsePath(idir));
55
- if (resp === null)
56
- return false;
57
- // Canceled out-of-band (a raw canceled response.json, not via `human cancel`):
58
- // there is no answer to deliver — just reap the node and tell waiting
59
- // subscribers no answer is coming, the same quiet deferred note `human cancel`
60
- // emits. (`finalize` is legal from active|idle; the status guard above holds.)
61
- if (resp['canceled'] === true) {
62
- transition(jobId, 'finalize');
63
- const note = typeof resp['reason'] === 'string' && resp['reason'] !== '' ? ` — ${resp['reason']}` : '';
64
- for (const sub of subscribersOf(jobId)) {
65
- appendInbox(sub.node_id, {
66
- from: jobId,
67
- tier: 'deferred',
68
- kind: 'message',
69
- label: `human interaction ${jobId} canceled no answer is coming${note}`,
70
- data: { body: `The human interaction ${jobId} was canceled${note}. No response will arrive.` },
71
- });
64
+ try {
65
+ const node = getNode(jobId);
66
+ if (node === null)
67
+ return false;
68
+ if (node.status !== 'active' && node.status !== 'idle')
69
+ return false;
70
+ if (!isResolved(idir))
71
+ return false;
72
+ const rc = readJson(join(idir, 'run.json'));
73
+ if (rc === null)
74
+ return false;
75
+ const resp = readJson(responsePath(idir));
76
+ if (resp === null)
77
+ return false;
78
+ // Canceled out-of-band (a raw canceled response.json, not via `human cancel`):
79
+ // there is no answer to deliver — just reap the node and tell waiting
80
+ // subscribers no answer is coming, the same quiet deferred note `human cancel`
81
+ // emits. (`finalize` is legal from active|idle; the status guard above holds.)
82
+ if (resp['canceled'] === true) {
83
+ transition(jobId, 'finalize');
84
+ const note = typeof resp['reason'] === 'string' && resp['reason'] !== '' ? ` — ${resp['reason']}` : '';
85
+ for (const sub of subscribersOf(jobId)) {
86
+ appendInbox(sub.node_id, {
87
+ from: jobId,
88
+ tier: 'deferred',
89
+ kind: 'message',
90
+ label: `human interaction ${jobId} canceled — no answer is coming${note}`,
91
+ data: { body: `The human interaction ${jobId} was canceled${note}. No response will arrive.` },
92
+ });
93
+ }
94
+ return true;
72
95
  }
73
- return true;
74
- }
75
- const responses = resp['responses'] ?? [];
76
- const completedAt = resp['completedAt'] ?? new Date().toISOString();
77
- const summary = summarizeResponses(responses);
78
- if (rc.mode === 'approve') {
79
- const sel = responses.find((r) => r.id === rc.approve_iid)?.selectedOptionId;
80
- await pushFinal(jobId, JSON.stringify({ approved: sel === 'yes', summary, responses, responsePath: responsePath(idir), completedAt }));
81
- }
82
- else {
96
+ const responses = resp['responses'] ?? [];
97
+ const completedAt = resp['completedAt'] ?? new Date().toISOString();
98
+ let deck;
99
+ try {
100
+ deck = parseDeck(deckPath(idir));
101
+ }
102
+ catch {
103
+ deck = undefined;
104
+ }
105
+ const summary = summarizeResponses(responses, deck);
83
106
  // ask (and any other answered deck): the full ResolutionEnvelope shape.
84
107
  const env = {
85
108
  summary,
@@ -89,8 +112,12 @@ export async function finalizeResolvedInteraction(jobId) {
89
112
  completedAt,
90
113
  };
91
114
  await pushFinal(jobId, JSON.stringify(env));
115
+ return true;
116
+ }
117
+ finally {
118
+ if (claim === undefined)
119
+ ownedClaim.release();
92
120
  }
93
- return true;
94
121
  }
95
122
  /** Sweep every interaction under the cwd's interactions root and deliver-back +
96
123
  * reap any that are answered-but-undelivered (see finalizeResolvedInteraction).
@@ -139,6 +166,76 @@ export const humanInbox = defineLeaf({
139
166
  return { drained: true, delivered };
140
167
  },
141
168
  });
169
+ function allInteractionRoots() {
170
+ const roots = new Set();
171
+ roots.add(interactionsRoot(process.cwd()));
172
+ for (const node of listNodes())
173
+ roots.add(interactionsRoot(node.cwd));
174
+ return [...roots];
175
+ }
176
+ function nodeRefs() {
177
+ return listNodes().map((n) => ({ node_id: n.node_id, name: n.name, cwd: n.cwd, parent: n.parent }));
178
+ }
179
+ function resolveConversation(asking, nodes) {
180
+ if (asking === undefined)
181
+ return undefined;
182
+ const byId = new Map(nodes.map((n) => [n.node_id, n]));
183
+ let cur = asking;
184
+ const seen = new Set();
185
+ while (cur.parent !== null && !seen.has(cur.node_id)) {
186
+ seen.add(cur.node_id);
187
+ const parent = byId.get(cur.parent);
188
+ if (parent === undefined)
189
+ break;
190
+ cur = parent;
191
+ }
192
+ return cur;
193
+ }
194
+ function askingNodeFor(jobId, deck, dir, nodes) {
195
+ const stamped = deck?.source?.nodeId;
196
+ if (stamped !== undefined && stamped !== '') {
197
+ const found = nodes.find((n) => n.node_id === stamped);
198
+ if (found !== undefined)
199
+ return found;
200
+ }
201
+ const bridge = getNode(jobId);
202
+ if (bridge?.parent !== undefined && bridge.parent !== null) {
203
+ const parent = nodes.find((n) => n.node_id === bridge.parent);
204
+ if (parent !== undefined)
205
+ return parent;
206
+ }
207
+ for (const n of nodes) {
208
+ const root = interactionsRoot(n.cwd);
209
+ if (dir === root || dir.startsWith(`${root}/`))
210
+ return n;
211
+ }
212
+ return bridge !== null ? { node_id: bridge.node_id, name: bridge.name, cwd: bridge.cwd, parent: bridge.parent ?? null } : undefined;
213
+ }
214
+ function summarizeDeckItem(item, deck, nodes) {
215
+ const jobId = basename(item.dir);
216
+ if (getNode(jobId) === null)
217
+ return null;
218
+ const asking = askingNodeFor(jobId, deck, item.dir, nodes);
219
+ const conversation = resolveConversation(asking, nodes);
220
+ return {
221
+ id: item.id,
222
+ job_id: jobId,
223
+ dir: item.dir,
224
+ title: item.title !== undefined ? item.title : deck?.title ?? null,
225
+ kind: item.kind !== undefined ? item.kind : deck?.interactions[0]?.kind ?? null,
226
+ blocked_since: item.blockedSince,
227
+ ...(asking !== undefined ? { asking_node_id: asking.node_id, asking_node_name: asking.name } : {}),
228
+ ...(conversation !== undefined ? { conversation_id: conversation.node_id, conversation_title: conversation.name } : {}),
229
+ ...(deck !== null ? { interaction_count: deck.interactions.length } : {}),
230
+ };
231
+ }
232
+ function interactionDirForJob(jobId) {
233
+ const node = getNode(jobId);
234
+ if (node === null) {
235
+ throw new InputError({ error: 'not_found', message: `no interaction node: ${jobId}`, field: 'job_id', next: 'Pass a job_id from `crtr human list`.' });
236
+ }
237
+ return { dir: interactionDir(jobId, node.cwd) };
238
+ }
142
239
  // ---------------------------------------------------------------------------
143
240
  // list (read-only, paginated)
144
241
  // ---------------------------------------------------------------------------
@@ -154,7 +251,7 @@ export const humanList = defineLeaf({
154
251
  { kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: "Opaque token from a previous response's next_cursor. Omit on first call." },
155
252
  ],
156
253
  output: [
157
- { name: 'items', type: 'object[]', required: true, constraint: 'Each: {id, dir, title, kind, blocked_since}. Oldest first.' },
254
+ { name: 'items', type: 'object[]', required: true, constraint: 'Each: {id, dir, title, kind, blocked_since}, enriched when derivable with asking_node_id/name, conversation_id/title, interaction_count. Oldest first.' },
158
255
  { name: 'next_cursor', type: 'string | null', required: true, constraint: 'Pass on the next call to continue. null means no more items.' },
159
256
  { name: 'total', type: 'integer | null', required: true, constraint: 'Total pending interactions.' },
160
257
  ],
@@ -165,15 +262,13 @@ export const humanList = defineLeaf({
165
262
  const limitRaw = input['limit'];
166
263
  const limit = Math.min(Math.max(1, limitRaw), 100);
167
264
  const cursor = input['cursor'];
168
- const raw = scanInbox([interactionsRoot(process.cwd())]);
265
+ const nodes = nodeRefs();
266
+ const raw = scanInbox(allInteractionRoots());
169
267
  const items = raw
170
- .map((i) => ({
171
- id: i.id,
172
- dir: i.dir,
173
- title: i.title !== undefined ? i.title : null,
174
- kind: i.kind !== undefined ? i.kind : null,
175
- blocked_since: i.blockedSince,
176
- }))
268
+ .flatMap((i) => {
269
+ const item = summarizeDeckItem(i, readJson(deckPath(i.dir)), nodes);
270
+ return item === null ? [] : [item];
271
+ })
177
272
  .sort((a, b) => {
178
273
  const ka = `${a.blocked_since}|${a.id}`;
179
274
  const kb = `${b.blocked_since}|${b.id}`;
@@ -189,18 +284,308 @@ export const humanList = defineLeaf({
189
284
  },
190
285
  });
191
286
  // ---------------------------------------------------------------------------
192
- // cancel retract a pending ask/approve/review
287
+ // deck detail (read-only)
288
+ // ---------------------------------------------------------------------------
289
+ export const humanDeck = defineLeaf({
290
+ name: 'deck',
291
+ description: 'read one pending humanloop deck',
292
+ whenToUse: 'you need the full questions/options for a pending human interaction',
293
+ help: {
294
+ name: 'human deck',
295
+ summary: 'read full pending deck detail for one human interaction job',
296
+ params: [{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: 'job_id from human ask/review/list.' }],
297
+ output: [
298
+ { name: 'id', type: 'string', required: true, constraint: 'Interaction job id.' },
299
+ { name: 'interactions', type: 'object[]', required: true, constraint: 'Deck interactions with options and freetext policy.' },
300
+ ],
301
+ outputKind: 'object',
302
+ effects: ['Read-only: reads deck.json from the interaction dir resolved through the canvas node cwd.'],
303
+ },
304
+ run: async (input) => {
305
+ const jobId = input['job_id'];
306
+ const { dir } = interactionDirForJob(jobId);
307
+ if (isResolved(dir)) {
308
+ throw new InputError({ error: 'already_resolved', message: `interaction already resolved: ${jobId}`, field: 'job_id', next: 'Use `crtr human list` for pending interactions.' });
309
+ }
310
+ let deck;
311
+ try {
312
+ deck = parseDeck(deckPath(dir));
313
+ }
314
+ catch {
315
+ throw new InputError({ error: 'not_found', message: `no deck for interaction: ${jobId}`, field: 'job_id', next: 'Pass a pending job_id from `crtr human list`.' });
316
+ }
317
+ const nodes = nodeRefs();
318
+ const asking = askingNodeFor(jobId, deck, dir, nodes);
319
+ const conversation = resolveConversation(asking, nodes);
320
+ const first = deck.interactions[0];
321
+ return {
322
+ id: jobId,
323
+ title: deck.title ?? first?.title ?? null,
324
+ kind: first?.kind ?? null,
325
+ blocked_since: deck.source?.blockedSince ?? null,
326
+ ...(asking !== undefined ? { asking_node_id: asking.node_id, asking_node_name: asking.name } : {}),
327
+ ...(conversation !== undefined ? { conversation_id: conversation.node_id, conversation_title: conversation.name } : {}),
328
+ interaction_count: deck.interactions.length,
329
+ interactions: deck.interactions.map((it) => ({
330
+ id: it.id,
331
+ kind: it.kind ?? null,
332
+ prompt: it.body ?? it.title,
333
+ options: it.options.map((o) => ({ id: o.id, label: o.label, ...(o.description !== undefined ? { description: o.description } : {}) })),
334
+ allow_freetext: it.allowFreetext ?? false,
335
+ ...(it.preAnswered?.selectedOptionId !== undefined ? { default_option_id: it.preAnswered.selectedOptionId } : {}),
336
+ })),
337
+ };
338
+ },
339
+ });
340
+ function isErrno(e, code) {
341
+ return typeof e === 'object' && e !== null && 'code' in e && e.code === code;
342
+ }
343
+ function pidAlive(pid) {
344
+ try {
345
+ process.kill(pid, 0);
346
+ return true;
347
+ }
348
+ catch (e) {
349
+ if (isErrno(e, 'ESRCH'))
350
+ return false;
351
+ return true;
352
+ }
353
+ }
354
+ function interactionLockPath(dir) {
355
+ return join(dir, 'resolve.lock');
356
+ }
357
+ function releaseClaim(dir, token) {
358
+ const lockFile = interactionLockPath(dir);
359
+ const cur = readJson(lockFile);
360
+ if (cur?.token === token) {
361
+ try {
362
+ unlinkSync(lockFile);
363
+ }
364
+ catch (e) {
365
+ if (!isErrno(e, 'ENOENT'))
366
+ throw e;
367
+ }
368
+ }
369
+ const progressFile = progressPath(dir);
370
+ const progress = readJson(progressFile);
371
+ if (progress?.claimToken !== token)
372
+ return;
373
+ try {
374
+ unlinkSync(progressFile);
375
+ }
376
+ catch (e) {
377
+ if (!isErrno(e, 'ENOENT'))
378
+ throw e;
379
+ }
380
+ }
381
+ export function acquireInteractionClaim(dir, jobId, opts = {}) {
382
+ const allowResolved = opts.allowResolved === true;
383
+ const markProgress = opts.markProgress === true;
384
+ const respectProgress = opts.respectProgress === true;
385
+ const lockFile = interactionLockPath(dir);
386
+ const progressFile = progressPath(dir);
387
+ const token = `${process.pid}:${Date.now()}:${Math.random().toString(36).slice(2)}`;
388
+ for (;;) {
389
+ if (!allowResolved && isResolved(dir))
390
+ return 'already_resolved';
391
+ if (!allowResolved && respectProgress && existsSync(progressFile) && !existsSync(lockFile)) {
392
+ const progress = readJson(progressFile);
393
+ if (progress?.claim === 'crtr human resolve') {
394
+ try {
395
+ unlinkSync(progressFile);
396
+ }
397
+ catch (e) {
398
+ if (!isErrno(e, 'ENOENT'))
399
+ return 'claimed';
400
+ }
401
+ }
402
+ else {
403
+ return 'claimed';
404
+ }
405
+ }
406
+ let fd = null;
407
+ try {
408
+ fd = openSync(lockFile, 'wx');
409
+ writeFileSync(fd, JSON.stringify({ pid: process.pid, token, jobId, claimedAt: new Date().toISOString() }, null, 2));
410
+ closeSync(fd);
411
+ fd = null;
412
+ if (markProgress) {
413
+ atomicWriteJson(progressFile, { partial: true, claim: 'crtr human resolve', claimToken: token, jobId, claimedAt: new Date().toISOString() });
414
+ }
415
+ if (!allowResolved && isResolved(dir)) {
416
+ releaseClaim(dir, token);
417
+ return 'already_resolved';
418
+ }
419
+ return { release: () => releaseClaim(dir, token) };
420
+ }
421
+ catch (e) {
422
+ if (fd !== null)
423
+ closeSync(fd);
424
+ if (isErrno(e, 'EEXIST')) {
425
+ const owner = readJson(lockFile);
426
+ if (typeof owner?.pid === 'number' && !pidAlive(owner.pid)) {
427
+ try {
428
+ unlinkSync(lockFile);
429
+ }
430
+ catch (unlinkErr) {
431
+ if (!isErrno(unlinkErr, 'ENOENT'))
432
+ return 'claimed';
433
+ }
434
+ continue;
435
+ }
436
+ return 'claimed';
437
+ }
438
+ throw e;
439
+ }
440
+ }
441
+ }
442
+ function writeResponseOnce(dir, responses, completedAt) {
443
+ const finalPath = responsePath(dir);
444
+ const tmpPath = `${finalPath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
445
+ writeFileSync(tmpPath, JSON.stringify({ responses, completedAt }, null, 2), { flag: 'wx' });
446
+ try {
447
+ linkSync(tmpPath, finalPath);
448
+ return 'written';
449
+ }
450
+ catch (e) {
451
+ if (isErrno(e, 'EEXIST'))
452
+ return 'already_resolved';
453
+ throw e;
454
+ }
455
+ finally {
456
+ try {
457
+ unlinkSync(tmpPath);
458
+ }
459
+ catch (e) {
460
+ if (!isErrno(e, 'ENOENT'))
461
+ throw e;
462
+ }
463
+ }
464
+ }
465
+ function validateResolveResponses(deck, responses) {
466
+ const byId = new Map(deck.interactions.map((it) => [it.id, it]));
467
+ for (const response of responses) {
468
+ const interaction = byId.get(response.id);
469
+ if (interaction === undefined) {
470
+ throw new InputError({ error: 'invalid_field', message: `unknown interaction_id: ${response.id}`, field: 'responses', next: 'Use interaction ids from `crtr human deck <job_id> --json`.' });
471
+ }
472
+ if (interaction.multiSelect === true) {
473
+ if (response.selectedOptionId !== undefined) {
474
+ throw new InputError({ error: 'invalid_field', message: `interaction ${response.id} is multi-select; use selected_option_ids`, field: 'responses', next: 'Use selected_option_ids for multi-select interactions.' });
475
+ }
476
+ }
477
+ else if (response.selectedOptionIds !== undefined && response.selectedOptionIds.length > 1) {
478
+ throw new InputError({ error: 'invalid_field', message: `interaction ${response.id} is single-select`, field: 'responses', next: 'Use selected_option_id or one selected_option_ids value for single-select interactions.' });
479
+ }
480
+ const selected = response.selectedOptionIds ?? (response.selectedOptionId !== undefined ? [response.selectedOptionId] : []);
481
+ const optionIds = new Set(interaction.options.map((o) => o.id));
482
+ for (const id of selected) {
483
+ if (!optionIds.has(id)) {
484
+ throw new InputError({ error: 'invalid_field', message: `unknown option id for ${response.id}: ${id}`, field: 'responses', next: 'Use option ids from `crtr human deck <job_id> --json`.' });
485
+ }
486
+ }
487
+ }
488
+ }
489
+ function parseResolveBody(raw) {
490
+ let parsed;
491
+ try {
492
+ parsed = JSON.parse(raw);
493
+ }
494
+ catch (e) {
495
+ throw new InputError({ error: 'invalid_json', message: `stdin is not valid JSON: ${String(e)}`, field: 'stdin', next: 'Pass {"responses":[{"interaction_id":"..."}]} on stdin.' });
496
+ }
497
+ if (!Array.isArray(parsed.responses)) {
498
+ throw new InputError({ error: 'invalid_field', message: 'responses must be an array', field: 'responses', next: 'Pass {"responses":[{"interaction_id":"...","selected_option_ids":["..."]}]}.' });
499
+ }
500
+ return {
501
+ responses: parsed.responses.map((r) => {
502
+ const id = r.interaction_id ?? r.id;
503
+ if (id === undefined || id === '') {
504
+ throw new InputError({ error: 'invalid_field', message: 'each response needs interaction_id', field: 'responses', next: 'Include interaction_id for every response.' });
505
+ }
506
+ const selectedOptionIds = r.selected_option_ids ?? r.selectedOptionIds;
507
+ const selectedOptionId = r.selected_option_id ?? r.selectedOptionId;
508
+ return {
509
+ id,
510
+ ...(selectedOptionIds !== undefined ? { selectedOptionIds } : {}),
511
+ ...(selectedOptionId !== undefined ? { selectedOptionId } : {}),
512
+ ...(r.freetext !== undefined ? { freetext: r.freetext } : {}),
513
+ ...(r.optionComments !== undefined ? { optionComments: r.optionComments } : {}),
514
+ };
515
+ }),
516
+ completedAt: parsed.completed_at ?? parsed.completedAt ?? new Date().toISOString(),
517
+ };
518
+ }
519
+ export const humanResolve = defineLeaf({
520
+ name: 'resolve',
521
+ description: 'resolve one humanloop deck from stdin answers',
522
+ whenToUse: 'a browser or local tool is submitting answers for a pending human interaction; this writes through the crouter/humanloop finalize path',
523
+ help: {
524
+ name: 'human resolve',
525
+ summary: 'write deck answers atomically and deliver them back to the asking node',
526
+ params: [
527
+ { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: 'job_id from human ask/review/list.' },
528
+ { kind: 'stdin', name: 'body', required: true, constraint: 'JSON {responses:[{interaction_id, selected_option_ids?, freetext?}], completed_at?}.' },
529
+ ],
530
+ output: [
531
+ { name: 'resolved', type: 'boolean', required: true, constraint: 'True when this call wrote and delivered the answer.' },
532
+ { name: 'job_id', type: 'string', required: true, constraint: 'Interaction job id.' },
533
+ { name: 'delivered', type: 'boolean', required: false, constraint: 'True when delivered through crouter push/finalize.' },
534
+ { name: 'reason', type: 'string', required: false, constraint: 'already_resolved or claimed when resolved is false.' },
535
+ ],
536
+ outputKind: 'object',
537
+ effects: ['Claims the interaction with a local resolve.lock (and progress.json so other humanloop inbox scans skip it), writes response.json with an exclusive no-clobber create, then calls crouter finalize/deliver-back. Does not write response.json from browser/server code.'],
538
+ },
539
+ run: async (input) => {
540
+ const jobId = input['job_id'];
541
+ const { dir } = interactionDirForJob(jobId);
542
+ const { responses, completedAt } = parseResolveBody(input['body']);
543
+ try {
544
+ parseDeck(deckPath(dir));
545
+ }
546
+ catch {
547
+ throw new InputError({ error: 'not_found', message: `no deck for interaction: ${jobId}`, field: 'job_id', next: 'Pass a pending job_id from `crtr human list`.' });
548
+ }
549
+ const claim = acquireInteractionClaim(dir, jobId, { markProgress: true, respectProgress: true });
550
+ if (claim === 'already_resolved')
551
+ return { resolved: false, job_id: jobId, reason: 'already_resolved' };
552
+ if (claim === 'claimed')
553
+ return { resolved: false, job_id: jobId, reason: 'claimed' };
554
+ try {
555
+ let deck;
556
+ try {
557
+ deck = parseDeck(deckPath(dir));
558
+ }
559
+ catch {
560
+ throw new InputError({ error: 'not_found', message: `no deck for interaction: ${jobId}`, field: 'job_id', next: 'Pass a pending job_id from `crtr human list`.' });
561
+ }
562
+ validateResolveResponses(deck, responses);
563
+ const writeResult = writeResponseOnce(dir, responses, completedAt);
564
+ if (writeResult === 'already_resolved')
565
+ return { resolved: false, job_id: jobId, reason: 'already_resolved' };
566
+ const delivered = await finalizeResolvedInteraction(jobId, claim);
567
+ if (!delivered)
568
+ return { resolved: false, job_id: jobId, reason: 'already_resolved' };
569
+ return { resolved: true, job_id: jobId, delivered };
570
+ }
571
+ finally {
572
+ claim.release();
573
+ }
574
+ },
575
+ });
576
+ // ---------------------------------------------------------------------------
577
+ // cancel — retract a pending ask/review
193
578
  // ---------------------------------------------------------------------------
194
579
  export const humanCancel = defineLeaf({
195
580
  name: 'cancel',
196
- description: 'retract a pending ask/approve/review',
581
+ description: 'retract a pending ask/review',
197
582
  whenToUse: 'a question went stale before the human answered',
198
583
  help: {
199
584
  name: 'human cancel',
200
- summary: 'retract a pending ask/approve/review you posed — kills its TUI pane, drops it from the human queue, and retires the node. Reach for this the moment a question goes stale (you answered it yourself, the situation changed) so a human is not left resolving a prompt whose answer no longer matters',
201
- guide: 'Pass the job_id returned by `human ask`/`approve`/`review`. Best-effort and idempotent: if the human already answered, or it was already canceled, it reports canceled:false with reason "already_resolved" and changes nothing. The agent that posed the deck is almost always the one canceling it, so the caller is never messaged — only OTHER subscribers (e.g. the asking node when a human dismisses the prompt) get a quiet deferred note that no answer is coming. Canceling a review kills its live on-screen pane and delivers no comments — the same quiet deferred note covers it.',
585
+ summary: 'retract a pending ask/review you posed — kills its TUI pane, drops it from the human queue, and retires the node. Reach for this the moment a question goes stale (you answered it yourself, the situation changed) so a human is not left resolving a prompt whose answer no longer matters',
586
+ guide: 'Pass the job_id returned by `human ask`/`review`. Best-effort and idempotent: if the human already answered, or it was already canceled, it reports canceled:false with reason "already_resolved" and changes nothing. The agent that posed the deck is almost always the one canceling it, so the caller is never messaged — only OTHER subscribers (e.g. the asking node when a human dismisses the prompt) get a quiet deferred note that no answer is coming. Canceling a review kills its live on-screen pane and delivers no comments — the same quiet deferred note covers it.',
202
587
  params: [
203
- { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: 'Node id of the interaction to cancel — the job_id returned by ask/approve/review.' },
588
+ { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: 'Node id of the interaction to cancel — the job_id returned by ask/review.' },
204
589
  { kind: 'flag', name: 'reason', type: 'string', required: false, constraint: 'Optional short note delivered to subscribers explaining why it was retracted.' },
205
590
  ],
206
591
  output: [
@@ -224,7 +609,7 @@ export const humanCancel = defineLeaf({
224
609
  error: 'not_found',
225
610
  message: `no interaction node: ${jobId}`,
226
611
  field: 'job_id',
227
- next: 'Pass the job_id from human ask/approve/review, or list pending with `crtr human list`.',
612
+ next: 'Pass the job_id from human ask/review, or list pending with `crtr human list`.',
228
613
  });
229
614
  }
230
615
  // Resolve the interaction dir from the node's RECORDED cwd: interaction dirs
@@ -312,21 +697,31 @@ export const humanRun = defineLeaf({
312
697
  process.exit(1);
313
698
  }
314
699
  try {
315
- if (rc.mode === 'ask' || rc.mode === 'approve' || rc.mode === 'notify') {
700
+ if (rc.mode === 'ask' || rc.mode === 'notify') {
316
701
  const deck = parseDeck(deckPath(dir));
317
- const env = await ask(deck, { dir });
318
702
  if (rc.mode === 'ask') {
319
- await pushFinal(rc.job_id, JSON.stringify(env));
703
+ const claim = acquireInteractionClaim(dir, rc.job_id);
704
+ if (claim === 'already_resolved') {
705
+ await finalizeResolvedInteraction(rc.job_id);
706
+ return;
707
+ }
708
+ if (claim === 'claimed')
709
+ return;
710
+ try {
711
+ await ask(deck, { dir });
712
+ // Pass the held claim: finalize re-acquires the same exclusive
713
+ // resolve.lock, which THIS worker still holds — without the claim it
714
+ // would see its own live lock, return 'claimed', and never deliver
715
+ // the answer (stranding the asker). The held claim makes finalize
716
+ // run under our lock; _run's finally still owns the release.
717
+ await finalizeResolvedInteraction(rc.job_id, claim);
718
+ }
719
+ finally {
720
+ claim.release();
721
+ }
320
722
  }
321
- else if (rc.mode === 'approve') {
322
- const sel = env.responses.find((r) => r.id === rc.approve_iid)?.selectedOptionId;
323
- await pushFinal(rc.job_id, JSON.stringify({
324
- approved: sel === 'yes',
325
- summary: env.summary,
326
- responses: env.responses,
327
- responsePath: env.responsePath,
328
- completedAt: env.completedAt,
329
- }));
723
+ else {
724
+ await ask(deck, { dir });
330
725
  }
331
726
  // notify: no job — nothing to write
332
727
  }
@@ -1,9 +1,8 @@
1
1
  import { type DetachResult } from '../../core/spawn.js';
2
2
  export declare const DECK_SCHEMA_HINT: string;
3
3
  export interface RunRecord {
4
- mode: 'ask' | 'approve' | 'notify' | 'review';
4
+ mode: 'ask' | 'notify' | 'review';
5
5
  job_id?: string;
6
- approve_iid?: string;
7
6
  file?: string;
8
7
  output?: string;
9
8
  /** tmux pane id of the detached TUI, recorded so `human cancel` can kill it. */
@@ -32,7 +31,7 @@ export declare function resolveHumanTarget(): string | undefined;
32
31
  /**
33
32
  * Open the detached humanloop `_run` pane for an interaction dir, ROUTED to the
34
33
  * session the user is watching (`resolveHumanTarget`). The single spawn path
35
- * behind ask/approve/review (`spawnHumanJob`) and notify. `detached: true` keeps
34
+ * behind ask/review (`spawnHumanJob`) and notify. `detached: true` keeps
36
35
  * the user's view put — the prompt lands beside the watched node without
37
36
  * jumping their session/window. `jobId`, when given, is injected as CRTR_JOB_ID.
38
37
  */
@@ -55,7 +54,7 @@ export declare function followUpReview(_jobId: string): string;
55
54
  *
56
55
  * Completion routing needs no bookkeeping here: the human node was created
57
56
  * under the asking node as its parent (spawnNode auto-subscribes the parent),
58
- * so the `pushFinal` the `_run` worker emits — for ask, approve, AND review —
57
+ * so the `pushFinal` the `_run` worker emits — for ask AND review —
59
58
  * fans the answer straight into the asking node's inbox. The pane id is recorded
60
59
  * on run.json (not returned) so `human cancel` can later kill the TUI.
61
60
  */