@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
@@ -0,0 +1,863 @@
1
+ // @ts-check
2
+ /**
3
+ * Prompt Studio — portable core for the `prompt-review` builtin view.
4
+ *
5
+ * Core rule: imports NOTHING. All state + behavior live here; presenters are
6
+ * pure reads that emit named intents.
7
+ *
8
+ * @module prompt-review/core
9
+ */
10
+
11
+ /** @typedef {import('../../core/view/contract.js').SourceError} SourceError */
12
+ /** @typedef {import('../../core/view/contract.js').IntentCtx<PromptReviewState>} Ctx */
13
+
14
+ const DEFAULT_KIND = 'developer';
15
+ const DEFAULT_MODE = 'base';
16
+ const DEFAULT_LIFECYCLE = 'terminal';
17
+ const DEFAULT_HAS_MANAGER = true;
18
+ const DEFAULT_BUDGET = 200_000;
19
+
20
+ /** @template T @param {T} data @returns {{ok:true, data:T}} */
21
+ function ok(data) { return { ok: true, data }; }
22
+ /** @param {SourceError} error @returns {{ok:false, error:SourceError}} */
23
+ function fail(error) { return { ok: false, error }; }
24
+ /** @param {unknown} v @returns {string} */
25
+ function str(v) { return v == null ? '' : String(v); }
26
+ /** @param {unknown} v @returns {boolean} */
27
+ function bool(v) { return v === true || v === 'true' || v === 1 || v === '1'; }
28
+ /** @param {number} n @returns {number} */
29
+ function approxTokens(n) { return Math.ceil(Math.max(0, n) / 4); }
30
+ /** @param {string} s @returns {string} */
31
+ function firstLine(s) { return str(s).split(/\r?\n/).map((l) => l.trim()).find(Boolean) || ''; }
32
+ /** @param {string} msg @param {SourceError['display']['level']} [level='error'] @returns {SourceError} */
33
+ function studioError(msg, level = 'error') {
34
+ return {
35
+ kind: 'prompt-review',
36
+ display: {
37
+ headline: 'Prompt Studio unavailable',
38
+ explanation: msg,
39
+ nextStep: 'Press g to retry.',
40
+ level,
41
+ blocking: level === 'error',
42
+ },
43
+ };
44
+ }
45
+ /** @param {string} bin @param {string[]} args @param {string} [stdin] @returns {import('../../core/view/contract.js').SourceRequest} */
46
+ function execRequest(bin, args, stdin) {
47
+ const req = { kind: 'exec', bin, args };
48
+ return stdin === undefined ? req : { ...req, stdin };
49
+ }
50
+
51
+ /** @typedef {'protocol'|'persona'|'generated'|'substrate'} LayerGroup */
52
+ /** @typedef {'project'|'user'|'builtin'|'generated'|'runtime'} LayerScope */
53
+
54
+ /**
55
+ * @typedef {Object} PromptReviewConfig
56
+ * @property {string} kind
57
+ * @property {'base'|'orchestrator'} mode
58
+ * @property {'terminal'|'resident'} lifecycle
59
+ * @property {boolean} hasManager
60
+ * @property {string|null} node
61
+ */
62
+
63
+ /**
64
+ * @typedef {Object} PromptLayer
65
+ * @property {string} id
66
+ * @property {string} label
67
+ * @property {LayerGroup} group
68
+ * @property {string} source
69
+ * @property {string|null} sourcePath
70
+ * @property {LayerScope} scope
71
+ * @property {boolean} included
72
+ * @property {string|null} condition
73
+ * @property {string} text
74
+ * @property {number} tokens
75
+ * @property {number} chars
76
+ */
77
+
78
+ /**
79
+ * @typedef {Object} PromptSource
80
+ * @property {string} id
81
+ * @property {string} label
82
+ * @property {string} path
83
+ * @property {LayerScope} scope
84
+ */
85
+
86
+ /** @typedef {{kind:string, whenToUse:string}} KindInfo */
87
+
88
+ /**
89
+ * @typedef {Object} PromptReviewList
90
+ * @property {KindInfo[]} kinds
91
+ * @property {Array<'base'|'orchestrator'>} modes
92
+ * @property {Array<'terminal'|'resident'>} lifecycles
93
+ * @property {Record<string, KindInfo[]>} subPersonas
94
+ */
95
+
96
+ /**
97
+ * @typedef {Object} ReviewState
98
+ * @property {PromptReviewConfig} config
99
+ * @property {PromptReviewConfig|null} compareConfig
100
+ * @property {{kinds: KindInfo[], modes: Array<'base'|'orchestrator'>, lifecycles: Array<'terminal'|'resident'>, subPersonas: Record<string, KindInfo[]>} | null} list
101
+ * @property {Array<{node_id:string, name:string, kind:string, mode:string, lifecycle:string, status:string, cwd:string, parent:string|null, created:string, enterable:boolean}>} liveNodes
102
+ * @property {PromptLayer[]|null} review
103
+ * @property {PromptLayer[]|null} compareReview
104
+ * @property {PromptSource[]} sources
105
+ * @property {number} totalTokens
106
+ * @property {number} totalChars
107
+ * @property {string|null} assembled
108
+ * @property {string|null} compareAssembled
109
+ * @property {string|null} selectedLayerId
110
+ * @property {number} cursor
111
+ * @property {number} scroll
112
+ * @property {string|null} compareSelectedLayerId
113
+ * @property {number} compareCursor
114
+ * @property {Record<string, boolean>} expanded
115
+ * @property {'layered'|'raw'} viewMode
116
+ * @property {Array<{layerId:string,label:string,sourcePath:string|null,scope:LayerScope,anchor:string|null,body:string}>} comments
117
+ * @property {{layerId:string,label:string,sourcePath:string|null,scope:LayerScope,anchor:string|null,body:string}|null} draftComment
118
+ * @property {string} rawSelection
119
+ * @property {string|null} lastExportPath
120
+ * @property {boolean} compareOpen
121
+ * @property {number} lastFetch
122
+ * @property {SourceError|null} sourceError
123
+ * @property {SourceError|null} auxiliaryError
124
+ */
125
+
126
+ /** @param {PromptReviewConfig} config @returns {string} */
127
+ function configKey(config) {
128
+ return [config.kind, config.mode, config.lifecycle, config.hasManager ? '1' : '0', config.node || '-'].join('|');
129
+ }
130
+
131
+ /** @param {PromptReviewConfig} config @returns {PromptReviewConfig} */
132
+ function cloneConfig(config) {
133
+ return { kind: config.kind, mode: config.mode, lifecycle: config.lifecycle, hasManager: config.hasManager, node: config.node };
134
+ }
135
+
136
+ /** @param {PromptReviewConfig} config @returns {PromptReviewConfig} */
137
+ function makeDefaultCompare(config) {
138
+ return {
139
+ kind: config.kind,
140
+ mode: config.mode === 'base' ? 'orchestrator' : 'base',
141
+ lifecycle: config.lifecycle === 'terminal' ? 'resident' : 'terminal',
142
+ hasManager: config.hasManager,
143
+ node: config.node,
144
+ };
145
+ }
146
+
147
+ /** @param {PromptReviewConfig} config @returns {PromptReviewConfig} */
148
+ function normalizeConfig(config) {
149
+ return {
150
+ kind: str(config.kind) || DEFAULT_KIND,
151
+ mode: config.mode === 'orchestrator' ? 'orchestrator' : 'base',
152
+ lifecycle: config.lifecycle === 'resident' ? 'resident' : 'terminal',
153
+ hasManager: bool(config.hasManager),
154
+ node: config.node ? str(config.node) : null,
155
+ };
156
+ }
157
+
158
+ /** @param {PromptLayer[]} layers @returns {PromptSource[]} */
159
+ function dedupeSources(layers) {
160
+ /** @type {PromptSource[]} */
161
+ const out = [];
162
+ const seen = new Set();
163
+ for (const layer of layers) {
164
+ if (!layer.sourcePath || seen.has(layer.sourcePath)) continue;
165
+ seen.add(layer.sourcePath);
166
+ out.push({ id: layer.id, label: layer.label, path: layer.sourcePath, scope: layer.scope });
167
+ }
168
+ return out;
169
+ }
170
+
171
+ /** @param {string} s @returns {string} */
172
+ function normalizeText(s) {
173
+ return str(s).replace(/\r\n/g, '\n');
174
+ }
175
+
176
+ /** @param {string[]} pieces @returns {string} */
177
+ function joinPieces(pieces) {
178
+ return pieces.filter((p) => p != null && p !== '').join('\n\n');
179
+ }
180
+
181
+ /** @param {string} text @returns {number} */
182
+ function chars(text) { return normalizeText(text).length; }
183
+
184
+ /** @param {string} text @returns {number} */
185
+ function tokens(text) { return approxTokens(chars(text)); }
186
+
187
+ /** @param {string} id @param {string} label @param {LayerGroup} group @param {string} source @param {string|null} sourcePath @param {LayerScope} scope @param {boolean} included @param {string|null} condition @param {string} text @returns {PromptLayer} */
188
+ function makeLayer(id, label, group, source, sourcePath, scope, included, condition, text) {
189
+ const norm = normalizeText(text);
190
+ return { id, label, group, source, sourcePath, scope, included, condition, text: norm, tokens: tokens(norm), chars: chars(norm) };
191
+ }
192
+
193
+ /** @param {string} kind @returns {string} */
194
+ function kindLabel(kind) { return kind || DEFAULT_KIND; }
195
+
196
+ /** @param {PromptReviewConfig} config @returns {string} */
197
+ function configHeadline(config) {
198
+ return `${config.kind}/${config.mode} · ${config.lifecycle} · manager:${config.hasManager ? 'yes' : 'no'}${config.node ? ` · node:${config.node}` : ''}`;
199
+ }
200
+
201
+ /** @param {string} id @returns {string} */
202
+ function layerLabel(id) {
203
+ const map = {
204
+ 'runtime-base': 'Runtime base',
205
+ 'spine-has-manager': 'Spine (has manager)',
206
+ 'spine-no-manager': 'Spine (no manager)',
207
+ 'lifecycle-terminal': 'Lifecycle (terminal)',
208
+ 'lifecycle-resident': 'Lifecycle (resident)',
209
+ 'waiting': 'Waiting',
210
+ 'persona-body': 'Persona body',
211
+ 'orchestration-kernel': 'Orchestration kernel',
212
+ 'subpersona-menu': 'Sub-persona menu',
213
+ 'substrate-preferences': 'Preferences substrate',
214
+ 'substrate-memory-guidance': 'Memory guidance substrate',
215
+ 'substrate-knowledge': 'Context message — not system prompt',
216
+ };
217
+ return map[id] || id.replace(/[-_]/g, ' ');
218
+ }
219
+
220
+ /** @param {PromptReviewConfig} config @returns {PromptLayer[]} */
221
+ function buildReviewLayers(config) {
222
+ const c = normalizeConfig(config);
223
+ const base = 'System prompt composition and agent runtime rules.';
224
+ const spineHas = c.hasManager;
225
+ const spineNo = !c.hasManager;
226
+ const lifecycleTerminal = c.lifecycle === 'terminal';
227
+ const lifecycleResident = !lifecycleTerminal;
228
+ const baseText = joinPieces([
229
+ '# Runtime base',
230
+ base,
231
+ ]);
232
+ const spineHasText = joinPieces([
233
+ '# Spine with manager',
234
+ 'You have a manager. Report upward and keep the spine active.',
235
+ ]);
236
+ const spineNoText = joinPieces([
237
+ '# Spine without manager',
238
+ 'You have no manager. Work independently and do not assume an upstream reviewer.',
239
+ ]);
240
+ const lifecycleTerminalText = joinPieces([
241
+ '# Terminal lifecycle',
242
+ 'This node must finish with a final report when its work is done.',
243
+ ]);
244
+ const lifecycleResidentText = joinPieces([
245
+ '# Resident lifecycle',
246
+ 'This node stays resident and waits for inbound events instead of always owing a final.',
247
+ ]);
248
+ const waitingText = joinPieces([
249
+ '# Waiting',
250
+ 'When you are blocked on a future event, stop and wait instead of busy-looping.',
251
+ ]);
252
+ const personaBody = joinPieces([
253
+ `# ${kindLabel(c.kind)} persona`,
254
+ `Kind-specific prompt body for ${c.kind}.`,
255
+ ]);
256
+ const orchestrationKernel = joinPieces([
257
+ '# Orchestration kernel',
258
+ 'Kernel guidance for orchestrator mode.',
259
+ ]);
260
+
261
+ /** @type {PromptLayer[]} */
262
+ const layers = [
263
+ makeLayer('runtime-base', 'Runtime base', 'protocol', 'personas/runtime-base.md', '/abs/src/builtin-personas/runtime-base.md', 'builtin', true, null, baseText),
264
+ makeLayer('spine-has-manager', 'Spine (has manager)', 'protocol', 'personas/spine/has-manager.md', '/abs/src/builtin-personas/spine/has-manager.md', 'builtin', spineHas, spineHas ? null : 'hasManager == false', spineHasText),
265
+ makeLayer('spine-no-manager', 'Spine (no manager)', 'protocol', 'personas/spine/no-manager.md', '/abs/src/builtin-personas/spine/no-manager.md', 'builtin', spineNo, spineNo ? null : 'hasManager == true', spineNoText),
266
+ makeLayer('lifecycle-terminal', 'Lifecycle (terminal)', 'protocol', 'personas/lifecycle/terminal.md', '/abs/src/builtin-personas/lifecycle/terminal.md', 'builtin', lifecycleTerminal, lifecycleTerminal ? null : 'lifecycle == resident', lifecycleTerminalText),
267
+ makeLayer('lifecycle-resident', 'Lifecycle (resident)', 'protocol', 'personas/lifecycle/resident.md', '/abs/src/builtin-personas/lifecycle/resident.md', 'builtin', lifecycleResident, lifecycleResident ? null : 'lifecycle == terminal', lifecycleResidentText),
268
+ makeLayer('waiting', 'Waiting', 'protocol', 'personas/waiting.md', '/abs/src/builtin-personas/waiting.md', 'builtin', true, null, waitingText),
269
+ makeLayer('persona-body', 'Persona body', 'persona', c.mode === 'base' ? `personas/${c.kind}/PERSONA.md` : `personas/${c.kind}/orchestrator.md`, c.mode === 'base' ? `/abs/src/builtin-personas/${c.kind}/PERSONA.md` : `/abs/src/builtin-personas/${c.kind}/orchestrator.md`, 'builtin', true, null, c.mode === 'base' ? personaBody : joinPieces([personaBody, orchestrationKernel])),
270
+ makeLayer('subpersona-menu', 'Sub-persona menu', 'generated', `generated:${c.kind}:sub-personas`, null, 'generated', true, null, joinPieces([
271
+ `Generated sub-persona menu for ${c.kind}.`,
272
+ 'This layer is synthesized from the available sub-personas and their source files.',
273
+ ])),
274
+ ];
275
+
276
+ if (c.node) {
277
+ layers.push(
278
+ makeLayer('substrate-preferences', 'Preferences substrate', 'substrate', 'runtime:preferences', null, 'runtime', true, null, joinPieces([
279
+ '# Preferences substrate',
280
+ 'Node-scoped preferences are spliced into the system prompt here.',
281
+ ])),
282
+ makeLayer('substrate-memory-guidance', 'Memory guidance substrate', 'substrate', 'runtime:memory-guidance', null, 'runtime', true, null, joinPieces([
283
+ '# Memory guidance',
284
+ 'Runtime-spliced memory guidance shown here because a node was supplied.',
285
+ ])),
286
+ makeLayer('substrate-knowledge', 'Context message — not system prompt', 'substrate', 'runtime:knowledge', null, 'runtime', true, null, joinPieces([
287
+ '# Context message',
288
+ 'This node-specific knowledge block is not part of the system prompt.',
289
+ ])),
290
+ );
291
+ }
292
+
293
+ return layers;
294
+ }
295
+
296
+ /** @param {PromptReviewConfig} config @returns {string} */
297
+ function composeAssembled(config) {
298
+ const c = normalizeConfig(config);
299
+ const layers = buildReviewLayers(c);
300
+ const protocol = layers.filter((layer) => layer.group !== 'substrate');
301
+ const substrate = layers.filter((layer) => layer.group === 'substrate');
302
+ const protocolText = joinPieces(protocol.filter((layer) => layer.included).map((layer) => layer.text));
303
+ if (c.node && substrate.length) {
304
+ return joinPieces([protocolText, ...substrate.map((layer) => layer.text)]);
305
+ }
306
+ return protocolText;
307
+ }
308
+
309
+ /** @param {PromptReviewConfig} config @param {PromptLayer[]} layers @returns {PromptLayer[]} */
310
+ function maybeAddNodeLayers(config, layers) {
311
+ if (!config.node) return layers;
312
+ return layers.concat(buildReviewLayers(config).filter((layer) => layer.group === 'substrate'));
313
+ }
314
+
315
+ /** @param {PromptLayer} layer @returns {string} */
316
+ function sourceForLayer(layer) { return layer.sourcePath || layer.source; }
317
+
318
+ /** @param {PromptReviewConfig} config @param {PromptLayer[]} layers @returns {{config:PromptReviewConfig, layers:PromptLayer[], assembled:string, total:{tokens:number, chars:number}, sources:PromptSource[]}} */
319
+ function reviewPayload(config, layers) {
320
+ const assembled = composeAssembled(config);
321
+ const totalChars = chars(assembled);
322
+ const totalTokens = tokens(assembled);
323
+ return { config: normalizeConfig(config), layers, assembled, total: { tokens: totalTokens, chars: totalChars }, sources: dedupeSources(layers) };
324
+ }
325
+
326
+ /** @param {unknown} value @returns {value is PromptReviewConfig} */
327
+ function looksLikeConfig(value) {
328
+ return !!value && typeof value === 'object' && 'kind' in /** @type {any} */ (value) && 'mode' in /** @type {any} */ (value) && 'lifecycle' in /** @type {any} */ (value);
329
+ }
330
+
331
+ /** @param {PromptReviewConfig} config @returns {PromptReviewConfig} */
332
+ function withNormalizedCompare(config) {
333
+ return normalizeConfig(config);
334
+ }
335
+
336
+ /** @param {string} s @returns {string} */
337
+ function stripSnippet(s) { return str(s).replace(/\s+/g, ' ').trim(); }
338
+
339
+ /** @param {string} text @returns {string[]} */
340
+ function textLines(text) { return normalizeText(text).split('\n'); }
341
+
342
+ /** @param {string} a @param {string} b @returns {string} */
343
+ function diffSnippet(a, b) {
344
+ const left = textLines(a);
345
+ const right = textLines(b);
346
+ let start = 0;
347
+ while (start < left.length && start < right.length && left[start] === right[start]) start++;
348
+ let endLeft = left.length - 1;
349
+ let endRight = right.length - 1;
350
+ while (endLeft >= start && endRight >= start && left[endLeft] === right[endRight]) { endLeft--; endRight--; }
351
+ const out = [];
352
+ const prefix = left.slice(0, start);
353
+ const suffix = left.slice(endLeft + 1);
354
+ for (const line of prefix.slice(-2)) out.push(` ${line}`);
355
+ for (const line of left.slice(start, endLeft + 1)) out.push(`- ${line}`);
356
+ for (const line of right.slice(start, endRight + 1)) out.push(`+ ${line}`);
357
+ for (const line of suffix.slice(0, 2)) out.push(` ${line}`);
358
+ return out.join('\n').trim();
359
+ }
360
+
361
+ /** @param {PromptLayer|null} left @param {PromptLayer|null} right @returns {string} */
362
+ function compareLayerText(left, right) {
363
+ if (!left && !right) return '';
364
+ if (!left) return right ? `+ ${right.text}` : '';
365
+ if (!right) return `- ${left.text}`;
366
+ if (left.text === right.text) return left.text;
367
+ return diffSnippet(left.text, right.text);
368
+ }
369
+
370
+ /** @param {PromptLayer[]} left @param {PromptLayer[]} right @returns {Array<{id:string,label:string,kind:'added'|'removed'|'changed',left:PromptLayer|null,right:PromptLayer|null,summary:string,diffText:string}>} */
371
+ function diffReviews(left, right) {
372
+ const a = new Map(left.map((layer) => [layer.id, layer]));
373
+ const b = new Map(right.map((layer) => [layer.id, layer]));
374
+ const ids = [...new Set([...a.keys(), ...b.keys()])];
375
+ /** @type {Array<{id:string,label:string,kind:'added'|'removed'|'changed',left:PromptLayer|null,right:PromptLayer|null,summary:string,diffText:string}>} */
376
+ const out = [];
377
+ for (const id of ids) {
378
+ const l = a.get(id) || null;
379
+ const r = b.get(id) || null;
380
+ if (l && !r) {
381
+ out.push({ id, label: l.label, kind: 'removed', left: l, right: null, summary: `${l.label} removed in compare config`, diffText: `- ${stripSnippet(l.text)}` });
382
+ } else if (!l && r) {
383
+ out.push({ id, label: r.label, kind: 'added', left: null, right: r, summary: `${r.label} added in compare config`, diffText: `+ ${stripSnippet(r.text)}` });
384
+ } else if (l && r && (l.included !== r.included || l.text !== r.text || l.condition !== r.condition || l.sourcePath !== r.sourcePath || l.scope !== r.scope)) {
385
+ out.push({ id, label: l.label, kind: 'changed', left: l, right: r, summary: `${l.label} changed`, diffText: compareLayerText(l, r) });
386
+ }
387
+ }
388
+ return out;
389
+ }
390
+
391
+ /** @param {PromptReviewConfig} config @param {PromptLayer[]} layers @returns {{config:PromptReviewConfig, layers:PromptLayer[], assembled:string, total:{tokens:number, chars:number}, sources:PromptSource[]}} */
392
+ function makeReview(config, layers) {
393
+ return reviewPayload(config, layers);
394
+ }
395
+
396
+ /** @type {import('../../core/view/contract.js').Source<PromptReviewList>} */
397
+ export const listSource = {
398
+ id: 'prompt-review-list',
399
+ request: () => execRequest('crtr', ['sys', 'prompt-review', '--list', '--json']),
400
+ parse: (raw) => {
401
+ if (!raw.ok || raw.exitCode !== 0) return fail(studioError(firstLine(raw.stderr || raw.stdout || 'crtr sys prompt-review --list failed')));
402
+ let data;
403
+ try { data = JSON.parse(String(raw.stdout || '').trim() || '{}'); }
404
+ catch { return fail(studioError('could not parse crtr sys prompt-review --list output as JSON')); }
405
+ if (!data || typeof data !== 'object' || !Array.isArray(data.kinds) || !Array.isArray(data.modes) || !Array.isArray(data.lifecycles) || typeof data.subPersonas !== 'object') {
406
+ return fail(studioError('unexpected response from crtr sys prompt-review --list'));
407
+ }
408
+ return ok(/** @type {PromptReviewList} */ ({
409
+ kinds: data.kinds.map((item) => ({ kind: str(item.kind), whenToUse: str(item.whenToUse) })),
410
+ modes: data.modes.filter((m) => m === 'base' || m === 'orchestrator'),
411
+ lifecycles: data.lifecycles.filter((l) => l === 'terminal' || l === 'resident'),
412
+ subPersonas: Object.fromEntries(Object.entries(data.subPersonas).map(([k, items]) => [k, Array.isArray(items) ? items.map((item) => ({ kind: str(item.kind), whenToUse: str(item.whenToUse) })) : []])),
413
+ }));
414
+ },
415
+ };
416
+
417
+ /** @type {import('../../core/view/contract.js').Source<ReturnType<typeof makeReview>, PromptReviewConfig>} */
418
+ export const reviewSource = {
419
+ id: 'prompt-review-review',
420
+ request: (config) => {
421
+ const c = normalizeConfig(config || /** @type {PromptReviewConfig} */ ({ kind: DEFAULT_KIND, mode: DEFAULT_MODE, lifecycle: DEFAULT_LIFECYCLE, hasManager: DEFAULT_HAS_MANAGER, node: null }));
422
+ const args = ['sys', 'prompt-review', '--kind', c.kind, '--mode', c.mode, '--lifecycle', c.lifecycle, '--has-manager', c.hasManager ? 'true' : 'false', '--json'];
423
+ if (c.node) args.push('--node', c.node);
424
+ return execRequest('crtr', args);
425
+ },
426
+ parse: (raw) => {
427
+ if (!raw.ok || raw.exitCode !== 0) return fail(studioError(firstLine(raw.stderr || raw.stdout || 'crtr sys prompt-review failed')));
428
+ let data;
429
+ try { data = JSON.parse(String(raw.stdout || '').trim() || '{}'); }
430
+ catch { return fail(studioError('could not parse crtr sys prompt-review output as JSON')); }
431
+ if (!data || typeof data !== 'object' || !looksLikeConfig(data.config) || !Array.isArray(data.layers) || typeof data.assembled !== 'string' || !data.total || typeof data.total !== 'object' || !Array.isArray(data.sources)) {
432
+ return fail(studioError('unexpected response from crtr sys prompt-review'));
433
+ }
434
+ const layers = data.layers.map((layer) => ({
435
+ id: str(layer.id),
436
+ label: str(layer.label) || layerLabel(str(layer.id)),
437
+ group: layer.group === 'persona' || layer.group === 'generated' || layer.group === 'substrate' ? layer.group : 'protocol',
438
+ source: str(layer.source),
439
+ sourcePath: layer.sourcePath ? str(layer.sourcePath) : null,
440
+ scope: layer.scope === 'project' || layer.scope === 'user' || layer.scope === 'builtin' || layer.scope === 'generated' || layer.scope === 'runtime' ? layer.scope : 'builtin',
441
+ included: layer.included === true,
442
+ condition: layer.condition == null ? null : str(layer.condition),
443
+ text: normalizeText(layer.text),
444
+ tokens: typeof layer.tokens === 'number' ? layer.tokens : approxTokens(chars(layer.text)),
445
+ chars: typeof layer.chars === 'number' ? layer.chars : chars(layer.text),
446
+ }));
447
+ const config = normalizeConfig({
448
+ kind: str(data.config.kind) || DEFAULT_KIND,
449
+ mode: data.config.mode === 'orchestrator' ? 'orchestrator' : 'base',
450
+ lifecycle: data.config.lifecycle === 'resident' ? 'resident' : 'terminal',
451
+ hasManager: bool(data.config.hasManager),
452
+ node: data.config.node ? str(data.config.node) : null,
453
+ });
454
+ // Preserve the backend's authoritative assembled string, totals, and source list —
455
+ // only the layered UI iterates layers; Raw/Copy/export must reflect the real prompt.
456
+ const sources = data.sources.map((s) => ({
457
+ id: str(s.id),
458
+ label: str(s.label) || layerLabel(str(s.id)),
459
+ path: s.path ? str(s.path) : '',
460
+ scope: s.scope === 'project' || s.scope === 'user' || s.scope === 'builtin' || s.scope === 'generated' || s.scope === 'runtime' ? s.scope : 'builtin',
461
+ })).filter((s) => s.path);
462
+ const assembled = normalizeText(data.assembled);
463
+ const total = {
464
+ tokens: typeof data.total.tokens === 'number' ? data.total.tokens : approxTokens(chars(assembled)),
465
+ chars: typeof data.total.chars === 'number' ? data.total.chars : chars(assembled),
466
+ };
467
+ return ok(/** @type {ReturnType<typeof makeReview>} */ ({ config, layers, assembled, total, sources }));
468
+ },
469
+ };
470
+
471
+ /** @type {import('../../core/view/contract.js').Source<Array<{node_id:string, name:string, kind:string, mode:string, lifecycle:string, status:string, cwd:string, parent:string|null, created:string, enterable:boolean}>>} */
472
+ export const nodeListSource = {
473
+ id: 'prompt-review-nodes',
474
+ request: () => execRequest('crtr', ['node', 'inspect', 'list', '--json']),
475
+ parse: (raw) => {
476
+ if (!raw.ok || raw.exitCode !== 0) return fail(studioError(firstLine(raw.stderr || raw.stdout || 'crtr node inspect list failed'), 'action'));
477
+ let data;
478
+ try { data = JSON.parse(String(raw.stdout || '').trim() || '{}'); }
479
+ catch { return fail(studioError('could not parse crtr node inspect list output as JSON')); }
480
+ if (!data || typeof data !== 'object' || !Array.isArray(data.nodes)) return fail(studioError('unexpected response from crtr node inspect list'));
481
+ return ok(data.nodes.map((node) => ({
482
+ node_id: str(node.node_id),
483
+ name: str(node.name),
484
+ kind: str(node.kind),
485
+ mode: str(node.mode),
486
+ lifecycle: str(node.lifecycle),
487
+ status: str(node.status),
488
+ cwd: str(node.cwd),
489
+ parent: node.parent == null ? null : str(node.parent),
490
+ created: str(node.created),
491
+ enterable: node.enterable === true,
492
+ })).filter((node) => node.status === 'active' || node.status === 'idle'));
493
+ },
494
+ };
495
+
496
+ /** @type {import('../../core/view/contract.js').Command<{path:string}, {config:PromptReviewConfig, comments:Array<{layerId:string,label:string,sourcePath:string|null,scope:LayerScope,anchor:string|null,body:string}>, sources:PromptSource[]}>} */
497
+ export const exportCommand = {
498
+ id: 'prompt-review-export',
499
+ request: (payload) => execRequest('crtr', ['sys', 'prompt-review', 'export', '--json'], JSON.stringify(payload || {})),
500
+ parse: (raw) => {
501
+ if (!raw.ok || raw.exitCode !== 0) return fail(studioError(firstLine(raw.stderr || raw.stdout || 'crtr sys prompt-review export failed')));
502
+ let data;
503
+ try { data = JSON.parse(String(raw.stdout || '').trim() || '{}'); }
504
+ catch { return fail(studioError('could not parse crtr sys prompt-review export output as JSON')); }
505
+ if (!data || typeof data !== 'object' || typeof data.path !== 'string') return fail(studioError('unexpected response from crtr sys prompt-review export'));
506
+ return ok({ path: data.path });
507
+ },
508
+ };
509
+
510
+ /** @param {PromptReviewConfig} config @param {PromptReviewConfig|null} compareConfig @returns {Promise<{review: ReturnType<typeof makeReview>, compareReview: ReturnType<typeof makeReview> | null, list: PromptReviewList | null, liveNodes: Array<{node_id:string, name:string, kind:string, mode:string, lifecycle:string, status:string, cwd:string, parent:string|null, created:string, enterable:boolean}> | null}>} */
511
+ async function loadAll(ctx, config, compareConfig) {
512
+ const tasks = [ctx.resolve(listSource), ctx.resolve(reviewSource, config), ctx.resolve(nodeListSource)];
513
+ if (compareConfig) tasks.push(ctx.resolve(reviewSource, compareConfig));
514
+ const [listRes, reviewRes, nodesRes, compareRes] = await Promise.all(tasks);
515
+ if (!reviewRes.ok) throw reviewRes.error;
516
+ return {
517
+ list: listRes.ok ? listRes.data : null,
518
+ review: reviewRes.data,
519
+ liveNodes: nodesRes.ok ? nodesRes.data : null,
520
+ compareReview: compareConfig ? (compareRes && compareRes.ok ? compareRes.data : null) : null,
521
+ };
522
+ }
523
+
524
+ /** @param {ReviewState} state @returns {PromptLayer|undefined} */
525
+ function selectedLayer(state) {
526
+ const layers = state.review || [];
527
+ return layers.find((layer) => layer.id === state.selectedLayerId) || layers[state.cursor] || layers.find((layer) => layer.included) || layers[0];
528
+ }
529
+
530
+ /** @param {ReviewState} state @returns {PromptLayer|undefined} */
531
+ function compareSelectedLayer(state) {
532
+ const layers = state.compareReview || [];
533
+ return layers.find((layer) => layer.id === state.compareSelectedLayerId) || layers[state.compareCursor] || layers.find((layer) => layer.included) || layers[0];
534
+ }
535
+
536
+ /** @param {PromptReviewConfig} config @param {PromptLayer[]|null} review @returns {string} */
537
+ function selectedLayerIdOrFirst(config, review) {
538
+ if (!review || review.length === 0) return '';
539
+ return review.find((layer) => layer.included)?.id || review[0].id;
540
+ }
541
+
542
+ /** @param {PromptLayer[]|null} review @param {string|null} id @returns {number} */
543
+ function cursorFor(review, id) {
544
+ if (!review || review.length === 0) return 0;
545
+ const idx = id ? review.findIndex((layer) => layer.id === id) : -1;
546
+ return idx >= 0 ? idx : Math.max(0, review.findIndex((layer) => layer.included) >= 0 ? review.findIndex((layer) => layer.included) : 0);
547
+ }
548
+
549
+ /** @param {ReviewState} state @param {PromptReviewConfig} config @param {PromptReviewConfig|null} compareConfig @param {ReturnType<typeof makeReview>|null} reviewPayloadObj @param {ReturnType<typeof makeReview>|null} comparePayloadObj @returns {ReviewState} */
550
+ function materialize(state, config, compareConfig, reviewPayloadObj, comparePayloadObj) {
551
+ const review = reviewPayloadObj ? reviewPayloadObj.layers : null;
552
+ const compareReview = comparePayloadObj ? comparePayloadObj.layers : null;
553
+ const next = { ...state, config, compareConfig, review, compareReview };
554
+ if (review && review.length) {
555
+ const chosen = state.selectedLayerId && review.some((layer) => layer.id === state.selectedLayerId) ? state.selectedLayerId : selectedLayerIdOrFirst(config, review);
556
+ next.selectedLayerId = chosen;
557
+ next.cursor = cursorFor(review, chosen);
558
+ const expanded = { ...state.expanded };
559
+ if (!(chosen in expanded)) expanded[chosen] = true;
560
+ next.expanded = expanded;
561
+ }
562
+ if (compareReview && compareReview.length) {
563
+ next.compareSelectedLayerId = state.compareSelectedLayerId && compareReview.some((layer) => layer.id === state.compareSelectedLayerId)
564
+ ? state.compareSelectedLayerId
565
+ : selectedLayerIdOrFirst(compareConfig || config, compareReview);
566
+ next.compareCursor = cursorFor(compareReview, next.compareSelectedLayerId);
567
+ if (!(next.compareSelectedLayerId in next.expanded)) next.expanded = { ...next.expanded, [next.compareSelectedLayerId]: true };
568
+ }
569
+ next.sources = reviewPayloadObj ? reviewPayloadObj.sources : [];
570
+ next.assembled = reviewPayloadObj ? reviewPayloadObj.assembled : null;
571
+ next.totalTokens = reviewPayloadObj ? reviewPayloadObj.total.tokens : 0;
572
+ next.totalChars = reviewPayloadObj ? reviewPayloadObj.total.chars : 0;
573
+ next.compareAssembled = comparePayloadObj ? comparePayloadObj.assembled : null;
574
+ return next;
575
+ }
576
+
577
+ /** @type {import('../../core/view/contract.js').ViewCore<ReviewState>} */
578
+ const core = {
579
+ manifest: {
580
+ id: 'prompt-review',
581
+ title: 'Prompt Studio',
582
+ subtitle: 'prompt assembly review + export',
583
+ description: 'inspect assembled prompts, provenance, comments, and diffs',
584
+ },
585
+
586
+ init() {
587
+ const config = normalizeConfig({ kind: DEFAULT_KIND, mode: DEFAULT_MODE, lifecycle: DEFAULT_LIFECYCLE, hasManager: DEFAULT_HAS_MANAGER, node: null });
588
+ return {
589
+ config,
590
+ compareConfig: makeDefaultCompare(config),
591
+ list: null,
592
+ liveNodes: [],
593
+ review: null,
594
+ compareReview: null,
595
+ sources: [],
596
+ totalTokens: 0,
597
+ totalChars: 0,
598
+ assembled: null,
599
+ compareAssembled: null,
600
+ selectedLayerId: null,
601
+ cursor: 0,
602
+ scroll: 0,
603
+ compareSelectedLayerId: null,
604
+ compareCursor: 0,
605
+ expanded: {},
606
+ viewMode: 'layered',
607
+ comments: [],
608
+ draftComment: null,
609
+ rawSelection: '',
610
+ lastExportPath: null,
611
+ compareOpen: true,
612
+ lastFetch: 0,
613
+ sourceError: null,
614
+ auxiliaryError: null,
615
+ };
616
+ },
617
+
618
+ sources: {
619
+ list: listSource,
620
+ nodes: nodeListSource,
621
+ review: reviewSource,
622
+ },
623
+
624
+ commands: {
625
+ export: exportCommand,
626
+ },
627
+
628
+ intents: {
629
+ async refresh(ctx) {
630
+ ctx.signal.setStatus('Loading Prompt Studio…');
631
+ const config = normalizeConfig(ctx.state.config);
632
+ const compareConfig = ctx.state.compareOpen && ctx.state.compareConfig ? normalizeConfig(ctx.state.compareConfig) : null;
633
+ try {
634
+ const result = await loadAll(ctx, config, compareConfig);
635
+ ctx.set((s) => {
636
+ const next = materialize({ ...s }, config, compareConfig, result.review, result.compareReview);
637
+ next.list = result.list || s.list;
638
+ next.liveNodes = result.liveNodes || s.liveNodes;
639
+ next.sourceError = null;
640
+ next.auxiliaryError = null;
641
+ next.lastFetch = Date.now();
642
+ return next;
643
+ });
644
+ if (result.list && result.liveNodes) ctx.signal.clearBanner();
645
+ ctx.signal.setStatus(null);
646
+ } catch (err) {
647
+ const e = /** @type {SourceError} */ (err);
648
+ ctx.set((s) => {
649
+ const next = { ...s, lastFetch: Date.now() };
650
+ if (!s.review) next.sourceError = e;
651
+ else next.auxiliaryError = e;
652
+ return next;
653
+ });
654
+ if (ctx.state.review) ctx.signal.setBanner(e.display.explanation || e.display.headline, e.display.level);
655
+ ctx.signal.setStatus(null);
656
+ }
657
+ },
658
+
659
+ setKind(ctx, kind) {
660
+ ctx.set((s) => ({ ...s, config: { ...s.config, kind: str(kind) || DEFAULT_KIND } }));
661
+ return ctx.dispatch('refresh');
662
+ },
663
+
664
+ setMode(ctx, mode) {
665
+ ctx.set((s) => ({ ...s, config: { ...s.config, mode: mode === 'orchestrator' ? 'orchestrator' : 'base' } }));
666
+ return ctx.dispatch('refresh');
667
+ },
668
+
669
+ setLifecycle(ctx, lifecycle) {
670
+ ctx.set((s) => ({ ...s, config: { ...s.config, lifecycle: lifecycle === 'resident' ? 'resident' : 'terminal' } }));
671
+ return ctx.dispatch('refresh');
672
+ },
673
+
674
+ toggleHasManager(ctx) {
675
+ ctx.set((s) => ({ ...s, config: { ...s.config, hasManager: !s.config.hasManager } }));
676
+ return ctx.dispatch('refresh');
677
+ },
678
+
679
+ setNode(ctx, node) {
680
+ ctx.set((s) => ({ ...s, config: { ...s.config, node: node ? str(node) : null } }));
681
+ return ctx.dispatch('refresh');
682
+ },
683
+
684
+ setCompareKind(ctx, kind) {
685
+ ctx.set((s) => ({ ...s, compareConfig: { ...(s.compareConfig || makeDefaultCompare(s.config)), kind: str(kind) || DEFAULT_KIND } }));
686
+ return ctx.dispatch('refresh');
687
+ },
688
+
689
+ setCompareMode(ctx, mode) {
690
+ ctx.set((s) => ({ ...s, compareConfig: { ...(s.compareConfig || makeDefaultCompare(s.config)), mode: mode === 'orchestrator' ? 'orchestrator' : 'base' } }));
691
+ return ctx.dispatch('refresh');
692
+ },
693
+
694
+ setCompareLifecycle(ctx, lifecycle) {
695
+ ctx.set((s) => ({ ...s, compareConfig: { ...(s.compareConfig || makeDefaultCompare(s.config)), lifecycle: lifecycle === 'resident' ? 'resident' : 'terminal' } }));
696
+ return ctx.dispatch('refresh');
697
+ },
698
+
699
+ toggleCompareHasManager(ctx) {
700
+ ctx.set((s) => ({ ...s, compareConfig: { ...(s.compareConfig || makeDefaultCompare(s.config)), hasManager: !(s.compareConfig || makeDefaultCompare(s.config)).hasManager } }));
701
+ return ctx.dispatch('refresh');
702
+ },
703
+
704
+ setCompareNode(ctx, node) {
705
+ ctx.set((s) => ({ ...s, compareConfig: { ...(s.compareConfig || makeDefaultCompare(s.config)), node: node ? str(node) : null } }));
706
+ return ctx.dispatch('refresh');
707
+ },
708
+
709
+ toggleCompareOpen(ctx) {
710
+ ctx.set((s) => {
711
+ const compareOpen = !s.compareOpen;
712
+ // Reopening must seed a config — materialize nulls compareConfig on close, so without
713
+ // this the diff panel comes back empty until another compare control fires.
714
+ const compareConfig = compareOpen ? (s.compareConfig || makeDefaultCompare(s.config)) : s.compareConfig;
715
+ return { ...s, compareOpen, compareConfig };
716
+ });
717
+ return ctx.dispatch('refresh');
718
+ },
719
+
720
+ setViewMode(ctx, mode) {
721
+ ctx.set((s) => ({ ...s, viewMode: mode === 'raw' ? 'raw' : 'layered' }));
722
+ },
723
+
724
+ selectLayer(ctx, layerId) {
725
+ ctx.set((s) => {
726
+ const id = str(layerId) || s.selectedLayerId;
727
+ const cursor = s.review ? Math.max(0, s.review.findIndex((layer) => layer.id === id)) : s.cursor;
728
+ return { ...s, selectedLayerId: id, cursor };
729
+ });
730
+ },
731
+
732
+ cursorDown(ctx) {
733
+ ctx.set((s) => {
734
+ if (!s.review || s.review.length === 0) return s;
735
+ const cursor = Math.min(s.review.length - 1, s.cursor + 1);
736
+ const layer = s.review[cursor];
737
+ return { ...s, cursor, selectedLayerId: layer ? layer.id : s.selectedLayerId };
738
+ });
739
+ },
740
+
741
+ cursorUp(ctx) {
742
+ ctx.set((s) => {
743
+ if (!s.review || s.review.length === 0) return s;
744
+ const cursor = Math.max(0, s.cursor - 1);
745
+ const layer = s.review[cursor];
746
+ return { ...s, cursor, selectedLayerId: layer ? layer.id : s.selectedLayerId };
747
+ });
748
+ },
749
+
750
+ selectCompareLayer(ctx, layerId) {
751
+ ctx.set((s) => {
752
+ const id = str(layerId) || s.compareSelectedLayerId;
753
+ const compareCursor = s.compareReview ? Math.max(0, s.compareReview.findIndex((layer) => layer.id === id)) : s.compareCursor;
754
+ return { ...s, compareSelectedLayerId: id, compareCursor };
755
+ });
756
+ },
757
+
758
+ compareCursorDown(ctx) {
759
+ ctx.set((s) => {
760
+ if (!s.compareReview || s.compareReview.length === 0) return s;
761
+ const compareCursor = Math.min(s.compareReview.length - 1, s.compareCursor + 1);
762
+ const layer = s.compareReview[compareCursor];
763
+ return { ...s, compareCursor, compareSelectedLayerId: layer ? layer.id : s.compareSelectedLayerId };
764
+ });
765
+ },
766
+
767
+ compareCursorUp(ctx) {
768
+ ctx.set((s) => {
769
+ if (!s.compareReview || s.compareReview.length === 0) return s;
770
+ const compareCursor = Math.max(0, s.compareCursor - 1);
771
+ const layer = s.compareReview[compareCursor];
772
+ return { ...s, compareCursor, compareSelectedLayerId: layer ? layer.id : s.compareSelectedLayerId };
773
+ });
774
+ },
775
+
776
+ toggleLayerExpanded(ctx, layerId) {
777
+ ctx.set((s) => ({ ...s, expanded: { ...s.expanded, [str(layerId)]: !s.expanded[str(layerId)] } }));
778
+ },
779
+
780
+ captureRawSelection(ctx, text) {
781
+ ctx.set((s) => ({ ...s, rawSelection: stripSnippet(text) }));
782
+ },
783
+
784
+ beginComment(ctx) {
785
+ const layer = selectedLayer(ctx.state);
786
+ if (!layer) return;
787
+ const anchor = ctx.state.viewMode === 'raw' ? (ctx.state.rawSelection || null) : null;
788
+ ctx.set((s) => ({
789
+ ...s,
790
+ draftComment: {
791
+ layerId: layer.id,
792
+ label: layer.label,
793
+ sourcePath: layer.sourcePath,
794
+ scope: layer.scope,
795
+ anchor,
796
+ body: '',
797
+ },
798
+ }));
799
+ },
800
+
801
+ setCommentBody(ctx, body) {
802
+ ctx.set((s) => (s.draftComment ? { ...s, draftComment: { ...s.draftComment, body: str(body) } } : s));
803
+ },
804
+
805
+ cancelComment(ctx) {
806
+ ctx.set((s) => ({ ...s, draftComment: null }));
807
+ },
808
+
809
+ saveComment(ctx) {
810
+ const draft = ctx.state.draftComment;
811
+ if (!draft || !stripSnippet(draft.body)) return;
812
+ ctx.set((s) => ({
813
+ ...s,
814
+ comments: [...s.comments, { ...draft, body: stripSnippet(draft.body) }],
815
+ draftComment: null,
816
+ }));
817
+ },
818
+
819
+ removeComment(ctx, idx) {
820
+ const i = typeof idx === 'number' ? idx : Number(idx);
821
+ if (!Number.isFinite(i) || i < 0) return;
822
+ ctx.set((s) => ({ ...s, comments: s.comments.filter((_, n) => n !== i) }));
823
+ },
824
+
825
+ async exportComments(ctx) {
826
+ if (!ctx.state.review) return;
827
+ const payload = {
828
+ config: ctx.state.config,
829
+ comments: ctx.state.comments,
830
+ sources: ctx.state.sources,
831
+ };
832
+ ctx.signal.setStatus('Exporting review…');
833
+ const r = await ctx.execute(exportCommand, payload);
834
+ if (!r.ok) {
835
+ ctx.signal.setBanner(r.error.display.explanation || r.error.display.headline, r.error.display.level);
836
+ ctx.signal.setStatus(null);
837
+ return;
838
+ }
839
+ ctx.set((s) => ({ ...s, lastExportPath: r.data.path }));
840
+ ctx.signal.setStatus(`Review copied: ${r.data.path}`);
841
+ },
842
+
843
+ copyRaw(ctx) {
844
+ ctx.signal.setStatus('Use Copy raw in the web view; the TUI is read-only.');
845
+ },
846
+ },
847
+ };
848
+
849
+ export {
850
+ configKey,
851
+ configHeadline,
852
+ diffReviews,
853
+ layerLabel,
854
+ makeDefaultCompare,
855
+ reviewPayload,
856
+ selectedLayer,
857
+ compareSelectedLayer,
858
+ composeAssembled,
859
+ buildReviewLayers,
860
+ kindLabel,
861
+ };
862
+
863
+ export default core;