@crouton-kit/crouter 0.3.17 → 0.3.19

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 (283) hide show
  1. package/dist/build-root.js +6 -0
  2. package/dist/builtin-memory/crouter-development/marketplaces.md +164 -0
  3. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +58 -0
  4. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +60 -0
  5. package/dist/builtin-memory/crouter-development/personas.md +117 -0
  6. package/dist/builtin-memory/crouter-development/plugins.md +163 -0
  7. package/dist/builtin-memory/design.md +60 -0
  8. package/dist/builtin-memory/development.md +116 -0
  9. package/dist/builtin-memory/planning.md +66 -0
  10. package/dist/builtin-memory/spec.md +99 -0
  11. package/dist/builtin-personas/design/{base.md → PERSONA.md} +4 -0
  12. package/dist/builtin-personas/design/orchestrator.md +1 -1
  13. package/dist/builtin-personas/developer/{base.md → PERSONA.md} +4 -0
  14. package/dist/builtin-personas/developer/orchestrator.md +1 -1
  15. package/dist/builtin-personas/explore/{base.md → PERSONA.md} +4 -0
  16. package/dist/builtin-personas/general/{base.md → PERSONA.md} +4 -0
  17. package/dist/builtin-personas/lifecycle/resident.md +1 -1
  18. package/dist/builtin-personas/lifecycle/terminal.md +5 -2
  19. package/dist/builtin-personas/orchestration-kernel.md +15 -24
  20. package/dist/builtin-personas/plan/{base.md → PERSONA.md} +4 -0
  21. package/dist/builtin-personas/plan/orchestrator.md +1 -1
  22. package/dist/builtin-personas/plan/reviewers/architecture-fit/{base.md → PERSONA.md} +1 -1
  23. package/dist/builtin-personas/plan/reviewers/code-smells/{base.md → PERSONA.md} +1 -1
  24. package/dist/builtin-personas/plan/reviewers/pattern-consistency/{base.md → PERSONA.md} +1 -1
  25. package/dist/builtin-personas/plan/reviewers/requirements-coverage/{base.md → PERSONA.md} +1 -1
  26. package/dist/builtin-personas/plan/reviewers/security/{base.md → PERSONA.md} +1 -1
  27. package/dist/builtin-personas/review/{base.md → PERSONA.md} +4 -0
  28. package/dist/builtin-personas/runtime-base.md +6 -3
  29. package/dist/builtin-personas/spec/{base.md → PERSONA.md} +4 -0
  30. package/dist/builtin-personas/spec/orchestrator.md +1 -1
  31. package/dist/builtin-personas/waiting.md +8 -0
  32. package/dist/builtin-skills/skills/crouter-development/personas/SKILL.md +24 -14
  33. package/dist/builtin-skills/skills/crouter-development/personas/base-prompt/SKILL.md +4 -4
  34. package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +1 -1
  35. package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +2 -2
  36. package/dist/builtin-views/_lib/states.mjs +161 -0
  37. package/dist/builtin-views/canvas/client.mjs +303 -0
  38. package/dist/builtin-views/canvas/view.mjs +576 -0
  39. package/dist/builtin-views/git-pr/client.mjs +440 -0
  40. package/dist/builtin-views/git-pr/view.mjs +675 -0
  41. package/dist/builtin-views/inbox/_lib/render.mjs +177 -0
  42. package/dist/builtin-views/inbox/sources/gmail.mjs +965 -0
  43. package/dist/builtin-views/inbox/sources/linkedin.mjs +427 -0
  44. package/dist/builtin-views/inbox/view.mjs +889 -0
  45. package/dist/builtin-views/linkedin/client.mjs +610 -0
  46. package/dist/builtin-views/linkedin/view.mjs +1171 -0
  47. package/dist/clients/attach/attach-cmd.d.ts +2 -0
  48. package/dist/clients/attach/attach-cmd.js +354 -0
  49. package/dist/clients/attach/chat-view.d.ts +77 -0
  50. package/dist/clients/attach/chat-view.js +450 -0
  51. package/dist/clients/attach/clipboard-image.d.ts +16 -0
  52. package/dist/clients/attach/clipboard-image.js +113 -0
  53. package/dist/clients/attach/config-load.d.ts +31 -0
  54. package/dist/clients/attach/config-load.js +113 -0
  55. package/dist/clients/attach/extension-dialogs.d.ts +29 -0
  56. package/dist/clients/attach/extension-dialogs.js +101 -0
  57. package/dist/clients/attach/input-controller.d.ts +54 -0
  58. package/dist/clients/attach/input-controller.js +204 -0
  59. package/dist/clients/attach/slash-commands.d.ts +36 -0
  60. package/dist/clients/attach/slash-commands.js +200 -0
  61. package/dist/clients/attach/view-socket.d.ts +48 -0
  62. package/dist/clients/attach/view-socket.js +126 -0
  63. package/dist/commands/attention.js +3 -3
  64. package/dist/commands/canvas-prune.js +1 -1
  65. package/dist/commands/daemon.js +4 -3
  66. package/dist/commands/human/prompts.js +4 -10
  67. package/dist/commands/human/queue.js +43 -8
  68. package/dist/commands/human/shared.d.ts +28 -1
  69. package/dist/commands/human/shared.js +48 -10
  70. package/dist/commands/memory/find.d.ts +1 -0
  71. package/dist/commands/memory/find.js +180 -0
  72. package/dist/commands/memory/lint.d.ts +1 -0
  73. package/dist/commands/memory/lint.js +140 -0
  74. package/dist/commands/memory/list.d.ts +1 -0
  75. package/dist/commands/memory/list.js +79 -0
  76. package/dist/commands/memory/read.js +103 -0
  77. package/dist/commands/memory/shared.d.ts +30 -0
  78. package/dist/commands/memory/shared.js +122 -0
  79. package/dist/commands/memory/write.d.ts +1 -0
  80. package/dist/commands/memory/write.js +85 -0
  81. package/dist/commands/memory.d.ts +2 -0
  82. package/dist/commands/memory.js +27 -0
  83. package/dist/commands/node.d.ts +3 -2
  84. package/dist/commands/node.js +660 -65
  85. package/dist/commands/pkg/market-manage.js +1 -1
  86. package/dist/commands/push.js +6 -6
  87. package/dist/commands/revive.js +1 -1
  88. package/dist/commands/skill/author.js +1 -1
  89. package/dist/commands/skill/shared.d.ts +1 -8
  90. package/dist/commands/skill/shared.js +2 -55
  91. package/dist/commands/skill.js +9 -14
  92. package/dist/commands/sys/doctor.js +1 -1
  93. package/dist/commands/sys/update.js +1 -1
  94. package/dist/commands/view-cycle.d.ts +2 -0
  95. package/dist/commands/view-cycle.js +125 -0
  96. package/dist/commands/view-list.d.ts +2 -0
  97. package/dist/commands/view-list.js +66 -0
  98. package/dist/commands/view-new.d.ts +2 -0
  99. package/dist/commands/view-new.js +70 -0
  100. package/dist/commands/view-pick.d.ts +2 -0
  101. package/dist/commands/view-pick.js +119 -0
  102. package/dist/commands/view-run.d.ts +2 -0
  103. package/dist/commands/view-run.js +191 -0
  104. package/dist/commands/view.d.ts +2 -0
  105. package/dist/commands/view.js +29 -0
  106. package/dist/core/__tests__/broker-lifecycle.test.d.ts +1 -0
  107. package/dist/core/__tests__/broker-lifecycle.test.js +677 -0
  108. package/dist/core/__tests__/broker-sdk-wiring.test.d.ts +1 -0
  109. package/dist/core/__tests__/broker-sdk-wiring.test.js +166 -0
  110. package/dist/core/__tests__/cascade-close.test.js +12 -2
  111. package/dist/core/__tests__/child-death-wake.test.d.ts +1 -0
  112. package/dist/core/__tests__/child-death-wake.test.js +245 -0
  113. package/dist/core/__tests__/context-intro.test.js +76 -62
  114. package/dist/core/__tests__/daemon-boot.test.js +14 -5
  115. package/dist/core/__tests__/daemon-liveness.test.js +34 -9
  116. package/dist/core/__tests__/detach-focus.test.d.ts +1 -0
  117. package/dist/core/__tests__/detach-focus.test.js +206 -0
  118. package/dist/core/__tests__/draw-style.test.d.ts +1 -0
  119. package/dist/core/__tests__/draw-style.test.js +258 -0
  120. package/dist/core/__tests__/fixtures/c3-custom-provider-ext.d.ts +2 -0
  121. package/dist/core/__tests__/fixtures/c3-custom-provider-ext.js +18 -0
  122. package/dist/core/__tests__/fixtures/fake-engine.d.ts +138 -0
  123. package/dist/core/__tests__/fixtures/fake-engine.js +614 -0
  124. package/dist/core/__tests__/fixtures/fake-pi-host.js +1 -0
  125. package/dist/core/__tests__/flagship-lifecycle.test.js +7 -1
  126. package/dist/core/__tests__/frame-decoder-perf.test.d.ts +1 -0
  127. package/dist/core/__tests__/frame-decoder-perf.test.js +198 -0
  128. package/dist/core/__tests__/helpers/harness.d.ts +9 -0
  129. package/dist/core/__tests__/helpers/harness.js +111 -1
  130. package/dist/core/__tests__/home-session.test.js +41 -8
  131. package/dist/core/__tests__/human-new-window-regression.test.d.ts +1 -0
  132. package/dist/core/__tests__/human-new-window-regression.test.js +101 -0
  133. package/dist/core/__tests__/human-surface-target.test.d.ts +1 -0
  134. package/dist/core/__tests__/human-surface-target.test.js +98 -0
  135. package/dist/core/__tests__/kickoff.test.js +37 -3
  136. package/dist/core/__tests__/live-mutation.test.js +50 -33
  137. package/dist/core/__tests__/memory.test.js +23 -115
  138. package/dist/core/__tests__/persona-subkind.test.js +18 -15
  139. package/dist/core/__tests__/placement-focus.test.js +5 -0
  140. package/dist/core/__tests__/placement-teardown.test.js +54 -11
  141. package/dist/core/__tests__/relaunch.test.js +4 -3
  142. package/dist/core/__tests__/review-render-pane-regression.test.d.ts +1 -0
  143. package/dist/core/__tests__/review-render-pane-regression.test.js +133 -0
  144. package/dist/core/__tests__/spawn-root.test.js +10 -0
  145. package/dist/core/__tests__/spike-harness.test.js +1 -0
  146. package/dist/core/__tests__/wake-bearings.test.d.ts +1 -0
  147. package/dist/core/__tests__/wake-bearings.test.js +156 -0
  148. package/dist/core/__tests__/wake-origin.test.d.ts +1 -0
  149. package/dist/core/__tests__/wake-origin.test.js +110 -0
  150. package/dist/core/bootstrap.js +1 -1
  151. package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
  152. package/dist/core/canvas/browse/app.js +24 -2
  153. package/dist/core/canvas/browse/model.d.ts +38 -2
  154. package/dist/core/canvas/browse/model.js +134 -10
  155. package/dist/core/canvas/browse/render.d.ts +6 -12
  156. package/dist/core/canvas/browse/render.js +72 -104
  157. package/dist/core/canvas/canvas.js +15 -22
  158. package/dist/core/canvas/db.js +46 -0
  159. package/dist/core/canvas/index.d.ts +1 -0
  160. package/dist/core/canvas/index.js +1 -0
  161. package/dist/core/canvas/paths.d.ts +4 -1
  162. package/dist/core/canvas/paths.js +10 -4
  163. package/dist/core/canvas/pid.d.ts +4 -0
  164. package/dist/core/canvas/pid.js +23 -0
  165. package/dist/core/canvas/render.d.ts +11 -2
  166. package/dist/core/canvas/render.js +69 -0
  167. package/dist/core/canvas/types.d.ts +85 -3
  168. package/dist/core/canvas/types.js +2 -2
  169. package/dist/core/canvas/wakeups.d.ts +76 -0
  170. package/dist/core/canvas/wakeups.js +185 -0
  171. package/dist/core/config.js +4 -1
  172. package/dist/core/frontmatter.js +37 -124
  173. package/dist/core/help.d.ts +6 -0
  174. package/dist/core/help.js +7 -0
  175. package/dist/core/memory-resolver.d.ts +49 -0
  176. package/dist/core/memory-resolver.js +141 -0
  177. package/dist/core/personas/index.d.ts +4 -3
  178. package/dist/core/personas/index.js +3 -2
  179. package/dist/core/personas/loader.d.ts +41 -16
  180. package/dist/core/personas/loader.js +133 -29
  181. package/dist/core/personas/resolve.d.ts +4 -4
  182. package/dist/core/personas/resolve.js +28 -16
  183. package/dist/core/predicate.d.ts +63 -0
  184. package/dist/core/predicate.js +189 -0
  185. package/dist/core/resolver.js +26 -5
  186. package/dist/core/runtime/bearings.d.ts +53 -12
  187. package/dist/core/runtime/bearings.js +132 -59
  188. package/dist/core/runtime/broker-cli.d.ts +1 -0
  189. package/dist/core/runtime/broker-cli.js +46 -0
  190. package/dist/core/runtime/broker-protocol.d.ts +332 -0
  191. package/dist/core/runtime/broker-protocol.js +153 -0
  192. package/dist/core/runtime/broker-sdk.d.ts +48 -0
  193. package/dist/core/runtime/broker-sdk.js +72 -0
  194. package/dist/core/runtime/broker.d.ts +55 -0
  195. package/dist/core/runtime/broker.js +1128 -0
  196. package/dist/core/runtime/close.js +35 -6
  197. package/dist/core/runtime/host.d.ts +53 -0
  198. package/dist/core/runtime/host.js +186 -0
  199. package/dist/core/runtime/kickoff.d.ts +2 -1
  200. package/dist/core/runtime/kickoff.js +91 -5
  201. package/dist/core/runtime/launch.d.ts +45 -2
  202. package/dist/core/runtime/launch.js +65 -2
  203. package/dist/core/runtime/lifecycle.js +23 -6
  204. package/dist/core/runtime/memory.d.ts +2 -42
  205. package/dist/core/runtime/memory.js +11 -162
  206. package/dist/core/runtime/nodes.d.ts +33 -0
  207. package/dist/core/runtime/nodes.js +59 -1
  208. package/dist/core/runtime/persona.js +21 -11
  209. package/dist/core/runtime/pi-vendored.d.ts +18 -0
  210. package/dist/core/runtime/pi-vendored.js +49 -0
  211. package/dist/core/runtime/placement.d.ts +42 -14
  212. package/dist/core/runtime/placement.js +228 -38
  213. package/dist/core/runtime/promote.d.ts +0 -6
  214. package/dist/core/runtime/promote.js +1 -12
  215. package/dist/core/runtime/{demote.d.ts → recycle.d.ts} +5 -5
  216. package/dist/core/runtime/{demote.js → recycle.js} +27 -11
  217. package/dist/core/runtime/reset.js +8 -6
  218. package/dist/core/runtime/revive.d.ts +2 -0
  219. package/dist/core/runtime/revive.js +34 -34
  220. package/dist/core/runtime/spawn.d.ts +19 -0
  221. package/dist/core/runtime/spawn.js +75 -22
  222. package/dist/core/runtime/stop-guard.d.ts +1 -1
  223. package/dist/core/runtime/stop-guard.js +6 -1
  224. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  225. package/dist/core/runtime/tmux-chrome.js +1 -1
  226. package/dist/core/runtime/tmux.d.ts +28 -0
  227. package/dist/core/runtime/tmux.js +80 -6
  228. package/dist/core/scope.d.ts +11 -0
  229. package/dist/core/scope.js +39 -0
  230. package/dist/core/spawn.d.ts +25 -1
  231. package/dist/core/spawn.js +72 -7
  232. package/dist/core/substrate/gate.d.ts +13 -0
  233. package/dist/core/substrate/gate.js +21 -0
  234. package/dist/core/substrate/index.d.ts +7 -0
  235. package/dist/core/substrate/index.js +18 -0
  236. package/dist/core/substrate/on-read.d.ts +14 -0
  237. package/dist/core/substrate/on-read.js +292 -0
  238. package/dist/core/substrate/render.d.ts +25 -0
  239. package/dist/core/substrate/render.js +256 -0
  240. package/dist/core/substrate/schema.d.ts +76 -0
  241. package/dist/core/substrate/schema.js +124 -0
  242. package/dist/core/substrate/session-cache.d.ts +30 -0
  243. package/dist/core/substrate/session-cache.js +77 -0
  244. package/dist/core/substrate/subject.d.ts +41 -0
  245. package/dist/core/substrate/subject.js +54 -0
  246. package/dist/core/tui/contract.d.ts +83 -0
  247. package/dist/core/tui/contract.js +8 -0
  248. package/dist/core/tui/draw.d.ts +96 -0
  249. package/dist/core/tui/draw.js +339 -0
  250. package/dist/core/tui/host.d.ts +29 -0
  251. package/dist/core/tui/host.js +379 -0
  252. package/dist/core/tui/loader.d.ts +16 -0
  253. package/dist/core/tui/loader.js +94 -0
  254. package/dist/core/{canvas/browse → tui}/terminal.js +7 -6
  255. package/dist/core/wake.d.ts +86 -0
  256. package/dist/core/wake.js +308 -0
  257. package/dist/daemon/crtrd.d.ts +29 -4
  258. package/dist/daemon/crtrd.js +662 -46
  259. package/dist/pi-extensions/__tests__/canvas-context-intro.test.d.ts +1 -0
  260. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +171 -0
  261. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +19 -12
  262. package/dist/pi-extensions/canvas-commands.d.ts +3 -0
  263. package/dist/pi-extensions/canvas-commands.js +10 -0
  264. package/dist/pi-extensions/canvas-context-intro.d.ts +17 -0
  265. package/dist/pi-extensions/canvas-context-intro.js +55 -15
  266. package/dist/pi-extensions/canvas-doc-substrate.d.ts +44 -0
  267. package/dist/pi-extensions/canvas-doc-substrate.js +112 -0
  268. package/dist/pi-extensions/canvas-nav.d.ts +3 -0
  269. package/dist/pi-extensions/canvas-nav.js +145 -34
  270. package/dist/pi-extensions/canvas-stophook.js +17 -8
  271. package/dist/pi-extensions/canvas-view.d.ts +21 -0
  272. package/dist/pi-extensions/canvas-view.js +75 -0
  273. package/dist/prompts/skill.js +19 -26
  274. package/dist/prompts/view.d.ts +7 -0
  275. package/dist/prompts/view.js +101 -0
  276. package/dist/types.d.ts +4 -0
  277. package/dist/types.js +1 -0
  278. package/package.json +9 -4
  279. package/dist/commands/skill/find.d.ts +0 -4
  280. package/dist/commands/skill/find.js +0 -257
  281. package/dist/commands/skill/read.js +0 -91
  282. /package/dist/commands/{skill → memory}/read.d.ts +0 -0
  283. /package/dist/core/{canvas/browse → tui}/terminal.d.ts +0 -0
@@ -0,0 +1,200 @@
1
+ // slash-commands.ts — builtin slash-command parse + dispatch for `crtr attach` (C6).
2
+ //
3
+ // Builtin slash commands are NOT engine-interpreted: `session.prompt('/model')`
4
+ // ships "/model" to the LLM as literal text. So the viewer recognizes the 21
5
+ // vendored `BUILTIN_SLASH_COMMANDS` itself and dispatches the matching
6
+ // `ClientToBroker` command frame; only EXTENSION commands (anything the viewer
7
+ // doesn't recognize) fall through to be sent as a `prompt` for the engine's
8
+ // extension runner to handle.
9
+ //
10
+ // SELECTORS — the Phase-4 reality. The plan called for `/model`, `/resume`,
11
+ // `/fork`, `/tree`, `/settings` to open pi's exported `*SelectorComponent`s. The
12
+ // authoritative 0.78.1 .d.ts shows those components require ENGINE-side state a
13
+ // socket-only viewer does not have (`ModelSelectorComponent` needs the
14
+ // `ModelRegistry` + `SettingsManager`; `SessionSelectorComponent` needs session
15
+ // loaders; `TreeSelectorComponent` needs the `SessionTreeNode[]` tree), and the
16
+ // §1.2 command-frame floor set exposes NO read op to fetch any of it. So in
17
+ // Phase 4 these resolve via a TEXT ARGUMENT — `/model <id>`, `/resume <path>`,
18
+ // `/fork <entryId>`, `/tree <entryId>` — which sends the SAME resolved value the
19
+ // selector would have, i.e. zero engine capability is lost; only the interactive
20
+ // picker UI waits for a future read op. A no-arg invocation shows a one-line
21
+ // notice with the arg form. (This protocol gap is reported up to the parent.)
22
+ //
23
+ // SCOPED OUT (review m2 — no engine method): `/login`, `/logout`, `/share`,
24
+ // `/trust` — a brief "not supported in attach" notice, never a frame.
25
+ import { BUILTIN_SLASH_COMMANDS } from '../../core/runtime/pi-vendored.js';
26
+ /** Builtins with no Phase-4 engine method — scoped out (review m2). */
27
+ const SCOPED_OUT = new Set(['login', 'logout', 'share', 'trust']);
28
+ /** Valid `/settings thinking` levels — the `SetThinkingLevelFrame['level']`
29
+ * union (= AgentSession['thinkingLevel'], pi-agent-core ThinkingLevel),
30
+ * mirrored as a runtime set so an invalid arg is rejected with the options
31
+ * rather than shipped as a frame the engine would throw on. Built from a
32
+ * `Record<level, true>` so the build fails BOTH ways if the union drifts: a
33
+ * missing key (union GAINED a level) or an extra key (union dropped one). */
34
+ const THINKING_LEVELS = new Set(Object.keys({
35
+ off: true,
36
+ minimal: true,
37
+ low: true,
38
+ medium: true,
39
+ high: true,
40
+ xhigh: true,
41
+ }));
42
+ const BUILTIN_NAMES = new Set(BUILTIN_SLASH_COMMANDS.map((c) => c.name));
43
+ /** True if `text` is a leading-slash command (vs. a normal prompt). */
44
+ export function isSlashCommand(text) {
45
+ return text.trimStart().startsWith('/');
46
+ }
47
+ /**
48
+ * Parse + dispatch a leading-slash command. Returns `true` if it was handled
49
+ * (a recognized builtin or a scoped-out notice) — the caller then clears the
50
+ * editor and sends nothing else. Returns `false` for an UNRECOGNIZED command,
51
+ * which the caller forwards to the engine as a `prompt` (extension command).
52
+ */
53
+ export function dispatchSlashCommand(text, ctx) {
54
+ const m = /^\/(\S+)\s*([\s\S]*)$/.exec(text.trim());
55
+ if (!m)
56
+ return false;
57
+ const name = m[1];
58
+ const arg = m[2].trim();
59
+ if (SCOPED_OUT.has(name)) {
60
+ ctx.notify(`/${name} is not supported in attach (Phase 4)`);
61
+ return true;
62
+ }
63
+ // Unknown to us AND not a builtin → let the engine's extension runner have it.
64
+ if (!BUILTIN_NAMES.has(name))
65
+ return false;
66
+ switch (name) {
67
+ // --- direct frames -----------------------------------------------------
68
+ case 'new':
69
+ ctx.send({ type: 'new_session' });
70
+ return true;
71
+ case 'reload':
72
+ ctx.send({ type: 'reload' });
73
+ return true;
74
+ case 'compact':
75
+ ctx.send({ type: 'compact', instructions: arg || undefined });
76
+ return true;
77
+ case 'quit':
78
+ // A viewer "quit" DETACHES — the shared engine keeps running (one-writer).
79
+ ctx.send({ type: 'bye' });
80
+ ctx.notify('Detaching — the engine keeps running');
81
+ return true;
82
+ // --- frames needing one argument --------------------------------------
83
+ case 'name':
84
+ if (!arg)
85
+ return usage(ctx, '/name <display name>');
86
+ ctx.send({ type: 'set_session_name', name: arg });
87
+ return true;
88
+ case 'export': {
89
+ const { path, format } = parseExport(arg, ctx.cwd ?? process.cwd());
90
+ ctx.send({ type: 'export', path, format });
91
+ ctx.notify(`Exporting session → ${path}`);
92
+ return true;
93
+ }
94
+ case 'import':
95
+ // `/import` resumes a session built from a JSONL file → switch_session.
96
+ if (!arg)
97
+ return usage(ctx, '/import <path-to-session.jsonl>');
98
+ ctx.send({ type: 'switch_session', path: arg });
99
+ return true;
100
+ // --- value-picker builtins: arg form (selector deferred, see header) ---
101
+ case 'model':
102
+ if (!arg)
103
+ return pickerHint(ctx, 'model', '/model <id>');
104
+ ctx.send({ type: 'set_model', model: arg });
105
+ return true;
106
+ case 'resume':
107
+ if (!arg)
108
+ return pickerHint(ctx, 'session', '/resume <session-path>');
109
+ ctx.send({ type: 'switch_session', path: arg });
110
+ return true;
111
+ case 'fork':
112
+ if (!arg)
113
+ return pickerHint(ctx, 'fork point', '/fork <entryId>');
114
+ ctx.send({ type: 'fork', entryId: arg });
115
+ return true;
116
+ case 'tree':
117
+ if (!arg)
118
+ return pickerHint(ctx, 'tree', '/tree <entryId>');
119
+ ctx.send({ type: 'navigate_tree', targetId: arg });
120
+ return true;
121
+ // --- settings (sub-command form; interactive menu needs read ops) ------
122
+ case 'settings':
123
+ return handleSettings(arg, ctx);
124
+ // --- read-only / informational ----------------------------------------
125
+ case 'session':
126
+ ctx.notify(describeSession(ctx.state));
127
+ return true;
128
+ // --- recognized builtins with no Phase-4 wire path --------------------
129
+ case 'copy':
130
+ case 'changelog':
131
+ case 'hotkeys':
132
+ case 'scoped-models':
133
+ case 'clone':
134
+ ctx.notify(`/${name} is not available in attach (Phase 4)`);
135
+ return true;
136
+ default:
137
+ // A builtin we listed but didn't special-case: treat as unsupported
138
+ // rather than leak it to the LLM as literal text.
139
+ ctx.notify(`/${name} is not available in attach (Phase 4)`);
140
+ return true;
141
+ }
142
+ }
143
+ /** Build slash-command autocomplete entries from the merged command list (the
144
+ * broker's `get_commands` result, which T7 may inject) — defaults to the
145
+ * vendored builtins. Shaped for pi-tui's `CombinedAutocompleteProvider`. */
146
+ export function slashCommandList(commands = BUILTIN_SLASH_COMMANDS) {
147
+ return commands.map((c) => ({ name: c.name, description: c.description }));
148
+ }
149
+ /** Same list as flat autocomplete items (value/label/description). */
150
+ export function commandAutocompleteItems(commands = BUILTIN_SLASH_COMMANDS) {
151
+ return commands.map((c) => ({ value: `/${c.name}`, label: `/${c.name}`, description: c.description }));
152
+ }
153
+ // ---------------------------------------------------------------------------
154
+ function usage(ctx, form) {
155
+ ctx.notify(`Usage: ${form}`);
156
+ return true;
157
+ }
158
+ function pickerHint(ctx, what, form) {
159
+ ctx.notify(`Interactive ${what} picker isn't available over view.sock yet — use ${form}`);
160
+ return true;
161
+ }
162
+ function handleSettings(arg, ctx) {
163
+ const [sub, ...rest] = arg.split(/\s+/);
164
+ const value = rest.join(' ').trim();
165
+ switch (sub) {
166
+ case 'thinking':
167
+ if (!value)
168
+ return usage(ctx, '/settings thinking <level>');
169
+ if (!THINKING_LEVELS.has(value)) {
170
+ ctx.notify(`Invalid thinking level "${value}" — choose one of: ${[...THINKING_LEVELS].join(', ')}`);
171
+ return true;
172
+ }
173
+ ctx.send({ type: 'set_thinking_level', level: value });
174
+ return true;
175
+ case 'auto-retry':
176
+ ctx.send({ type: 'set_auto_retry', enabled: parseBool(value) });
177
+ return true;
178
+ case 'auto-compaction':
179
+ ctx.send({ type: 'set_auto_compaction', enabled: parseBool(value) });
180
+ return true;
181
+ default:
182
+ ctx.notify('Settings: /settings thinking <level> | auto-retry on|off | auto-compaction on|off ' +
183
+ '(the interactive settings menu needs data not exposed over view.sock in Phase 4)');
184
+ return true;
185
+ }
186
+ }
187
+ function parseBool(value) {
188
+ return /^(on|true|yes|1|enabled?)$/i.test(value.trim());
189
+ }
190
+ function parseExport(arg, cwd) {
191
+ const path = arg || `${cwd}/pi-export-${Date.now()}.html`;
192
+ const format = path.endsWith('.jsonl') ? 'jsonl' : 'html';
193
+ return { path, format };
194
+ }
195
+ function describeSession(state) {
196
+ if (!state)
197
+ return 'Session info unavailable';
198
+ const name = state.sessionName ? `"${state.sessionName}" ` : '';
199
+ return `Session ${name}${state.sessionId} · model ${state.model ?? 'unknown'} · thinking ${state.thinkingLevel}`;
200
+ }
@@ -0,0 +1,48 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { type BrokerToClient, type ClientToBroker } from '../../core/runtime/broker-protocol.js';
3
+ /** Surfaced when the node has no reachable broker at connect time (no socket
4
+ * file, or a stale socket with nothing listening). The command catches this to
5
+ * exit non-zero with a focus/revive hint. */
6
+ export declare class BrokerUnavailableError extends Error {
7
+ readonly nodeId: string;
8
+ constructor(nodeId: string);
9
+ }
10
+ /** The plan-fixed interface (Wave 3): `connect()`, `on('frame', …)`,
11
+ * `send(frame)`, `on('close', …)`, plus `connect`/`error` events. */
12
+ export interface ViewSocketClient {
13
+ on(event: 'connect', listener: () => void): this;
14
+ on(event: 'frame', listener: (frame: BrokerToClient) => void): this;
15
+ on(event: 'close', listener: () => void): this;
16
+ on(event: 'error', listener: (err: Error) => void): this;
17
+ once(event: 'connect', listener: () => void): this;
18
+ once(event: 'frame', listener: (frame: BrokerToClient) => void): this;
19
+ once(event: 'close', listener: () => void): this;
20
+ once(event: 'error', listener: (err: Error) => void): this;
21
+ off(event: 'connect', listener: () => void): this;
22
+ off(event: 'error', listener: (err: Error) => void): this;
23
+ }
24
+ export declare class ViewSocketClient extends EventEmitter {
25
+ private readonly nodeId;
26
+ private socket;
27
+ private readonly decoder;
28
+ private closeEmitted;
29
+ constructor(nodeId: string);
30
+ /** The broker binds `join(nodeDir(id), 'view.sock')` — resolve it the same way. */
31
+ get socketPath(): string;
32
+ /** Open the connection. Wire up listeners; emits `connect` on success or
33
+ * `error` (a {@link BrokerUnavailableError} for ECONNREFUSED/ENOENT) on
34
+ * failure. Idempotent guard is the caller's job — call once. */
35
+ connect(): void;
36
+ /** Encode + write one client→broker frame. No-op on a dead/absent socket
37
+ * (a `close` event drives teardown); never throws. */
38
+ send(frame: ClientToBroker): void;
39
+ /** Detach: destroy the socket. `close` fires → the caller tears down. */
40
+ close(): void;
41
+ private onData;
42
+ private onError;
43
+ private onClose;
44
+ private destroy;
45
+ /** Emit `error` only when a listener exists — a bare EventEmitter `error`
46
+ * with no listener throws, and this client must never throw uncaught. */
47
+ private emitError;
48
+ }
@@ -0,0 +1,126 @@
1
+ // view-socket.ts — the socket client half of `crtr attach` (Phase 4, T7).
2
+ //
3
+ // The §0 ONE-WRITER INVARIANT lives in this directory: a viewer has ONLY a
4
+ // socket. ViewSocketClient connects to a node's ALREADY-running headless broker
5
+ // over its unix socket (`nodeDir(id)/view.sock`) and speaks the LOCAL
6
+ // broker-protocol codec (`node:net` + the bounded `FrameDecoder`). It NEVER
7
+ // spawns pi, NEVER opens the session `.jsonl`, NEVER constructs an engine — if
8
+ // the broker is not running, connect fails and `attach-cmd` exits (focus/T9 is
9
+ // what keeps a broker alive, not attach). It also never uses pi's `RpcClient`
10
+ // (which would spawn its own engine).
11
+ //
12
+ // No reconnect: a dropped broker means the engine's turn-cycle settled and the
13
+ // broker disposed+exited; a fresh focus re-runs attach. On EOF/close → emit
14
+ // `close` (the caller renders "broker gone" + exits). On a connect-time
15
+ // ECONNREFUSED/ENOENT → a `BrokerUnavailableError` so the command exits non-zero
16
+ // with a clear message. Decode is bounded by `CLIENT_READ_CAPS`; a broker that
17
+ // somehow sends an oversized frame surfaces as a clean error+exit, not a crash.
18
+ import { EventEmitter } from 'node:events';
19
+ import { createConnection } from 'node:net';
20
+ import { join } from 'node:path';
21
+ import { nodeDir } from '../../core/canvas/paths.js';
22
+ import { CLIENT_READ_CAPS, encodeFrame, FrameDecoder, FrameOverflowError, } from '../../core/runtime/broker-protocol.js';
23
+ /** Surfaced when the node has no reachable broker at connect time (no socket
24
+ * file, or a stale socket with nothing listening). The command catches this to
25
+ * exit non-zero with a focus/revive hint. */
26
+ export class BrokerUnavailableError extends Error {
27
+ nodeId;
28
+ constructor(nodeId) {
29
+ super(`node ${nodeId} has no running broker — focus or revive it first`);
30
+ this.nodeId = nodeId;
31
+ this.name = 'BrokerUnavailableError';
32
+ }
33
+ }
34
+ export class ViewSocketClient extends EventEmitter {
35
+ nodeId;
36
+ socket;
37
+ decoder = new FrameDecoder(CLIENT_READ_CAPS);
38
+ closeEmitted = false;
39
+ constructor(nodeId) {
40
+ super();
41
+ this.nodeId = nodeId;
42
+ }
43
+ /** The broker binds `join(nodeDir(id), 'view.sock')` — resolve it the same way. */
44
+ get socketPath() {
45
+ return join(nodeDir(this.nodeId), 'view.sock');
46
+ }
47
+ /** Open the connection. Wire up listeners; emits `connect` on success or
48
+ * `error` (a {@link BrokerUnavailableError} for ECONNREFUSED/ENOENT) on
49
+ * failure. Idempotent guard is the caller's job — call once. */
50
+ connect() {
51
+ const socket = createConnection(this.socketPath);
52
+ this.socket = socket;
53
+ socket.on('connect', () => this.emit('connect'));
54
+ socket.on('data', (chunk) => this.onData(chunk));
55
+ socket.on('error', (err) => this.onError(err));
56
+ socket.on('close', () => this.onClose());
57
+ }
58
+ /** Encode + write one client→broker frame. No-op on a dead/absent socket
59
+ * (a `close` event drives teardown); never throws. */
60
+ send(frame) {
61
+ const sock = this.socket;
62
+ if (sock === undefined || sock.destroyed)
63
+ return;
64
+ try {
65
+ sock.write(encodeFrame(frame));
66
+ }
67
+ catch {
68
+ /* dead socket — 'close' will fire and drive teardown */
69
+ }
70
+ }
71
+ /** Detach: destroy the socket. `close` fires → the caller tears down. */
72
+ close() {
73
+ this.destroy();
74
+ }
75
+ onData(chunk) {
76
+ let frames;
77
+ try {
78
+ frames = this.decoder.push(chunk);
79
+ }
80
+ catch (err) {
81
+ // A broker that somehow emits an oversized frame (or a corrupt stream):
82
+ // clean error + drop, never a crash.
83
+ const msg = err instanceof FrameOverflowError
84
+ ? `broker sent an oversized frame (${err.message}) — disconnecting`
85
+ : `failed to decode a broker frame: ${String(err)}`;
86
+ this.emitError(new Error(msg));
87
+ this.destroy();
88
+ return;
89
+ }
90
+ for (const raw of frames) {
91
+ this.emit('frame', raw);
92
+ }
93
+ }
94
+ onError(err) {
95
+ if (err.code === 'ECONNREFUSED' || err.code === 'ENOENT') {
96
+ this.emitError(new BrokerUnavailableError(this.nodeId));
97
+ }
98
+ else {
99
+ this.emitError(new Error(`view socket error: ${err.message}`));
100
+ }
101
+ // 'close' typically follows 'error'; teardown converges there.
102
+ }
103
+ onClose() {
104
+ if (this.closeEmitted)
105
+ return;
106
+ this.closeEmitted = true;
107
+ this.emit('close');
108
+ }
109
+ destroy() {
110
+ const sock = this.socket;
111
+ if (sock !== undefined && !sock.destroyed) {
112
+ try {
113
+ sock.destroy();
114
+ }
115
+ catch {
116
+ /* ignore */
117
+ }
118
+ }
119
+ }
120
+ /** Emit `error` only when a listener exists — a bare EventEmitter `error`
121
+ * with no listener throws, and this client must never throw uncaught. */
122
+ emitError(err) {
123
+ if (this.listenerCount('error') > 0)
124
+ this.emit('error', err);
125
+ }
126
+ }
@@ -21,7 +21,7 @@ import { countAsks, pendingAsksForView, asksAcrossCanvas, asksForNodes, } from '
21
21
  const attentionCount = defineLeaf({
22
22
  name: 'count',
23
23
  description: 'total pending ask count (machine-parseable stdout.count)',
24
- whenToUse: 'getting a single pending-ask count for a script or nav chrome to read (stdout.count is machine-parseable); scope with --node or --view, or default to canvas-wide',
24
+ whenToUse: 'getting a single pending-ask count for a script or nav chrome to read; scope it to a node or a sub-DAG, or default to canvas-wide',
25
25
  help: {
26
26
  name: 'canvas attention count',
27
27
  // stdout.count is parsed directly by the nav chrome — keep the contract.
@@ -93,7 +93,7 @@ const attentionCount = defineLeaf({
93
93
  const attentionList = defineLeaf({
94
94
  name: 'list',
95
95
  description: 'itemised list of cwds with pending asks',
96
- whenToUse: 'finding which agents are blocked waiting on a human — an itemised list of the cwds with pending asks, oldest first, so you know where to go answer. Scope to a sub-DAG with --view or list canvas-wide. Use `canvas attention count` instead when a script just needs the number, or `canvas attention map` for per-node counts to label a UI',
96
+ whenToUse: 'finding which agents are blocked waiting on a human — an itemised list of the cwds with pending asks, oldest first, so you know where to go answer. Scope it to a sub-DAG or list canvas-wide. Use `canvas attention count` instead when a script just needs the number, or `canvas attention map` for per-node counts to label a UI',
97
97
  help: {
98
98
  name: 'canvas attention list',
99
99
  summary: 'list nodes with pending human asks, grouped by cwd, oldest first',
@@ -211,7 +211,7 @@ const attentionMap = defineLeaf({
211
211
  export const attentionBranch = defineBranch({
212
212
  name: 'attention',
213
213
  description: 'count/list pending human asks across the graph',
214
- whenToUse: 'checking whether any agent on the canvas is blocked waiting on a human, and where: count the pending asks, list the cwds that have them, or map per-node counts. Scope with --node or --view, or go canvas-wide. Use `canvas dashboard` instead for the graph SHAPE, or `node inspect list` for a plain node roster',
214
+ whenToUse: 'checking whether any agent on the canvas is blocked waiting on a human, and where: count the pending asks, list the cwds that have them, or map per-node counts. Scope it to a node or a sub-DAG, or go canvas-wide. Use `canvas dashboard` instead for the graph SHAPE, or `node inspect list` for a plain node roster',
215
215
  help: {
216
216
  name: 'canvas attention',
217
217
  summary: 'aggregate pending human asks across the canvas',
@@ -13,7 +13,7 @@ const DEFAULT_TTL_DAYS = 14;
13
13
  export const canvasPruneLeaf = defineLeaf({
14
14
  name: 'prune',
15
15
  description: 'remove terminal nodes (dead/done/canceled) older than a TTL',
16
- whenToUse: 'you want to bound the canvas\u2019s on-disk growth \u2014 sweep away nodes that are finished (done), crashed (dead), or closed (canceled) and older than a retention window, reclaiming their rows, edges (cascade-deleted by the schema), and `nodes/<id>/` dirs. Run it as an operator/cron chore; live nodes (active/idle) are never touched. Pass `--dry-run` first to see exactly what would go, `--ttl <days>` to widen or tighten the window',
16
+ whenToUse: 'you want to bound the canvas\u2019s on-disk growth \u2014 sweep away nodes that are finished (done), crashed (dead), or closed (canceled) and older than a retention window, reclaiming their rows, edges (cascade-deleted by the schema), and `nodes/<id>/` dirs. Run it as an operator/cron chore; live nodes (active/idle) are never touched, and you can preview the sweep before it deletes anything',
17
17
  help: {
18
18
  name: 'canvas prune',
19
19
  summary: 'delete terminal nodes older than a TTL (edges cascade, dirs removed); --dry-run to preview',
@@ -1,7 +1,8 @@
1
1
  // `crtr canvas daemon` — thin supervisor daemon management.
2
2
  //
3
- // The daemon (crtrd) polls active+idle nodes and handles window exit:
4
- // crash (window gone, no intent) mark 'dead'
3
+ // The daemon (crtrd) polls active+idle nodes and handles engine-container exit
4
+ // (a tmux pane or a headless broker process):
5
+ // • crash (container gone, no intent) → mark 'dead'
5
6
  // • refresh-yield (intent=refresh) → fresh respawn
6
7
  //
7
8
  // This subtree starts, checks, and stops the daemon process.
@@ -93,7 +94,7 @@ const daemonStop = defineLeaf({
93
94
  throw new InputError({
94
95
  error: 'kill_failed',
95
96
  message: `failed to signal pid ${pid}: ${err.message}`,
96
- next: 'The pidfile may be stale; remove ~/.crtr/crtrd.pid manually.',
97
+ next: 'The pidfile may be stale; remove ~/.crouter/canvas/crtrd.pid manually.',
97
98
  });
98
99
  }
99
100
  },
@@ -2,12 +2,12 @@ import { defineLeaf } from '../../core/command.js';
2
2
  import { InputError } from '../../core/io.js';
3
3
  import { spawnNode } from '../../core/runtime/nodes.js';
4
4
  import { interactionDir } from '../../core/artifact.js';
5
- import { isInTmux, spawnAndDetach } from '../../core/spawn.js';
5
+ import { isInTmux } from '../../core/spawn.js';
6
6
  import { mkdirSync, existsSync } from 'node:fs';
7
7
  import { join, resolve } from 'node:path';
8
8
  import { randomBytes } from 'node:crypto';
9
9
  import { validateDeck, approveDeck, notifyDeck, atomicWriteJson, deckPath, display, } from '@crouton-kit/humanloop';
10
- import { DECK_SCHEMA_HINT, followUpReview, spawnHumanJob, pickPlacement, runCmd, resolveMaxPanes, } from './shared.js';
10
+ import { DECK_SCHEMA_HINT, followUpReview, spawnHumanJob, detachHumanTui, resolveMaxPanes, } from './shared.js';
11
11
  /** The asking node's id, or null when run from a bare shell (no parent to route to). */
12
12
  function askingNode() {
13
13
  return process.env['CRTR_NODE_ID'] ?? null;
@@ -132,7 +132,7 @@ export const humanReview = defineLeaf({
132
132
  outputKind: 'object',
133
133
  effects: [
134
134
  'Creates a kind:"human" node under you and writes run.json to the interaction dir.',
135
- 'Spawns a live, read-only review session (in a detached tmux pane when in tmux) that tracks the file and re-renders on save.',
135
+ 'Spawns a live review surface (detached tmux panes when in tmux): the raw source in a read-only editor for anchoring comments, beside a termrender-rendered view of the doc; both track the file and re-render on save.',
136
136
  'Returns instantly; the anchored comments fan into your inbox when the human submits (off-tmux, drain via `crtr human inbox`).',
137
137
  ],
138
138
  },
@@ -212,13 +212,7 @@ export const humanNotify = defineLeaf({
212
212
  atomicWriteJson(join(idir, 'run.json'), rc);
213
213
  let shown = false;
214
214
  if (isInTmux()) {
215
- const spawn = spawnAndDetach({
216
- command: runCmd(idir),
217
- cwd,
218
- placement: pickPlacement(),
219
- killAfterSeconds: 0,
220
- });
221
- shown = spawn.status === 'spawned';
215
+ shown = detachHumanTui(idir, cwd).status === 'spawned';
222
216
  }
223
217
  return { shown, dir: idir };
224
218
  },
@@ -8,7 +8,7 @@ import { transition } from '../../core/runtime/lifecycle.js';
8
8
  import { appendInbox } from '../../core/feed/inbox.js';
9
9
  import { existsSync } from 'node:fs';
10
10
  import { join } from 'node:path';
11
- import { inbox, scanInbox, parseDeck, deckPath, responsePath, isResolved, atomicWriteJson, ask, launchReview, readJson, } from '@crouton-kit/humanloop';
11
+ import { inbox, scanInbox, parseDeck, deckPath, responsePath, isResolved, atomicWriteJson, ask, launchReview, readJson, display, } from '@crouton-kit/humanloop';
12
12
  import { killPane } from './shared.js';
13
13
  // ---------------------------------------------------------------------------
14
14
  // inbox (human-invoked, blocking)
@@ -136,6 +136,13 @@ export const humanCancel = defineLeaf({
136
136
  const rc = readJson(join(idir, 'run.json'));
137
137
  if (rc?.pane_id !== undefined && rc.pane_id !== '')
138
138
  killPane(rc.pane_id, idir);
139
+ // A review also opened a live termrender render pane beside the editor —
140
+ // kill it too or it outlives the canceled job. Verified against the
141
+ // reviewed file path, which termrender bakes into the pane's start command
142
+ // (`termrender doc watch ... <file>`), so this can only ever hit our pane.
143
+ if (rc?.render_pane_id !== undefined && rc.render_pane_id !== '' && typeof rc.file === 'string' && rc.file !== '') {
144
+ killPane(rc.render_pane_id, rc.file);
145
+ }
139
146
  // (2) Drop it from the human queue: a response.json marks the dir resolved,
140
147
  // so scanInbox (human list/inbox) skips it.
141
148
  if (existsSync(idir)) {
@@ -218,13 +225,41 @@ export const humanRun = defineLeaf({
218
225
  else if (rc.mode === 'review') {
219
226
  // The _run worker is already its own dedicated tmux pane with a TTY, so
220
227
  // run nvim directly in it (noTmux) instead of letting launchReview
221
- // split off a SECOND pane and sit polling. This matches how ask/approve
222
- // render in-place and avoids the redundant side pane.
223
- const res = await launchReview(rc.file, {
224
- output: rc.output,
225
- noTmux: true,
226
- });
227
- await pushFinal(rc.job_id, JSON.stringify(res));
228
+ // split off a SECOND pane and sit polling.
229
+ //
230
+ // BUG REGRESSION (raw-markdown review): the nvim buffer must stay the
231
+ // RAW source — anchored comments hang off source line numbers — so the
232
+ // termrender render the help promises (panels/callouts/mermaid) lives
233
+ // in its OWN live pane opened beside this worker. `display` spawns the
234
+ // managed termrender binary in watch mode, so it re-renders on every
235
+ // save exactly like the nvim buffer reloads. Best-effort: off-tmux or
236
+ // renderer-unavailable degrades to editor-only. The pane id is merged
237
+ // into run.json so `human cancel` can kill it; the finally clears it on
238
+ // every exit of the editor (submit, quit, or failure).
239
+ let renderPane;
240
+ try {
241
+ renderPane = display(rc.file, { window: 'split' }).paneId;
242
+ }
243
+ catch {
244
+ /* render pane is best-effort; the review itself must not die */
245
+ }
246
+ if (renderPane !== undefined) {
247
+ const rcPath = join(dir, 'run.json');
248
+ const cur = readJson(rcPath);
249
+ if (cur !== null)
250
+ atomicWriteJson(rcPath, { ...cur, render_pane_id: renderPane });
251
+ }
252
+ try {
253
+ const res = await launchReview(rc.file, {
254
+ output: rc.output,
255
+ noTmux: true,
256
+ });
257
+ await pushFinal(rc.job_id, JSON.stringify(res));
258
+ }
259
+ finally {
260
+ if (renderPane !== undefined)
261
+ killPane(renderPane, rc.file);
262
+ }
228
263
  }
229
264
  }
230
265
  catch (e) {
@@ -1,3 +1,4 @@
1
+ import { type DetachResult } from '../../core/spawn.js';
1
2
  export declare const DECK_SCHEMA_HINT: string;
2
3
  export interface RunRecord {
3
4
  mode: 'ask' | 'approve' | 'notify' | 'review';
@@ -7,9 +8,35 @@ export interface RunRecord {
7
8
  output?: string;
8
9
  /** tmux pane id of the detached TUI, recorded so `human cancel` can kill it. */
9
10
  pane_id?: string;
11
+ /** tmux pane id of a review's live termrender render pane, so cancel/finish can clear it. */
12
+ render_pane_id?: string;
10
13
  }
11
14
  export declare function resolveMaxPanes(): number;
12
- export declare function pickPlacement(): 'split-h' | 'new-window';
15
+ export declare function pickPlacement(targetPane?: string): 'split-h' | 'new-window';
16
+ /**
17
+ * The tmux pane the humanloop TUI should open BESIDE so a PERSON actually sees
18
+ * it. A prompt raised by a canvas node must NOT land in the backstage `crtr`
19
+ * session (the holding ground for un-watched node panes) — it lands in the
20
+ * session the user is watching that node's graph in:
21
+ * 1. node prompt (CRTR_NODE_ID set) → the HIGHEST FOCUSED node of its graph
22
+ * (`graphSurfaceTarget`, the focused node closest to the graph root): split
23
+ * beside it, in the session/window the user already has it open in.
24
+ * 2. nothing in the graph on screen (or a dead focus pane), or a bare-shell
25
+ * prompt → the user's currently-attached pane (`attachedClientPane`).
26
+ * 3. neither resolvable → undefined (tmux falls back to the caller pane).
27
+ * The TUI lands in the right place but NEVER switches the user's session/window
28
+ * (no switch-client / select-window; new-window opens `-d`). They see it when
29
+ * they look at the node they are already watching.
30
+ */
31
+ export declare function resolveHumanTarget(): string | undefined;
32
+ /**
33
+ * Open the detached humanloop `_run` pane for an interaction dir, ROUTED to the
34
+ * session the user is watching (`resolveHumanTarget`). The single spawn path
35
+ * behind ask/approve/review (`spawnHumanJob`) and notify. `detached: true` keeps
36
+ * the user's view put — the prompt lands beside the watched node without
37
+ * jumping their session/window. `jobId`, when given, is injected as CRTR_JOB_ID.
38
+ */
39
+ export declare function detachHumanTui(idir: string, cwd: string, jobId?: string): DetachResult;
13
40
  export declare function runCmd(dir: string): string;
14
41
  export declare function followUpResult(_jobId: string): string;
15
42
  export declare function followUpDrain(_jobId: string): string;