@crouton-kit/crouter 0.3.28 → 0.3.29

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 (322) hide show
  1. package/dist/build-root.js +4 -2
  2. package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
  3. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
  4. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
  5. package/dist/builtin-memory/crouter-development/personas.md +1 -1
  6. package/dist/builtin-memory/crouter-development/plugins.md +20 -18
  7. package/dist/builtin-memory/design.md +1 -1
  8. package/dist/builtin-memory/development.md +1 -1
  9. package/dist/builtin-memory/internal/INDEX.md +1 -1
  10. package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
  11. package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
  12. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  13. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  14. package/dist/builtin-memory/planning.md +1 -1
  15. package/dist/builtin-memory/spec.md +6 -4
  16. package/dist/builtin-personas/orchestration-kernel.md +4 -5
  17. package/dist/builtin-personas/runtime-base.md +5 -4
  18. package/dist/builtin-personas/spec/PERSONA.md +8 -4
  19. package/dist/builtin-personas/spec/orchestrator.md +5 -3
  20. package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
  21. package/dist/builtin-views/canvas/core.mjs +586 -0
  22. package/dist/builtin-views/canvas/text.mjs +58 -0
  23. package/dist/builtin-views/canvas/tui.mjs +165 -0
  24. package/dist/builtin-views/canvas/web.jsx +120 -0
  25. package/dist/builtin-views/git-pr/core.mjs +673 -0
  26. package/dist/builtin-views/git-pr/text.mjs +84 -0
  27. package/dist/builtin-views/git-pr/tui.mjs +302 -0
  28. package/dist/builtin-views/git-pr/web.jsx +216 -0
  29. package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
  30. package/dist/builtin-views/inbox/core.mjs +1273 -0
  31. package/dist/builtin-views/inbox/text.mjs +73 -0
  32. package/dist/builtin-views/inbox/tui.mjs +312 -0
  33. package/dist/builtin-views/inbox/web.jsx +188 -0
  34. package/dist/builtin-views/linkedin/core.mjs +906 -0
  35. package/dist/builtin-views/linkedin/text.mjs +69 -0
  36. package/dist/builtin-views/linkedin/tui.mjs +427 -0
  37. package/dist/builtin-views/linkedin/web.jsx +206 -0
  38. package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
  39. package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
  40. package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
  41. package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
  42. package/dist/cli.js +3 -3
  43. package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
  44. package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
  45. package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
  46. package/dist/clients/attach/__tests__/git-info.test.js +31 -0
  47. package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
  48. package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
  49. package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
  50. package/dist/clients/attach/attach-cmd.js +282 -46
  51. package/dist/clients/attach/auth-pickers.d.ts +9 -0
  52. package/dist/clients/attach/auth-pickers.js +194 -0
  53. package/dist/clients/attach/canvas-panels.d.ts +4 -2
  54. package/dist/clients/attach/canvas-panels.js +45 -17
  55. package/dist/clients/attach/chat-view.d.ts +27 -2
  56. package/dist/clients/attach/chat-view.js +98 -9
  57. package/dist/clients/attach/clipboard-image.d.ts +47 -8
  58. package/dist/clients/attach/clipboard-image.js +150 -26
  59. package/dist/clients/attach/clipboard-text.d.ts +3 -0
  60. package/dist/clients/attach/clipboard-text.js +39 -0
  61. package/dist/clients/attach/config-load.d.ts +54 -7
  62. package/dist/clients/attach/config-load.js +99 -3
  63. package/dist/clients/attach/context-message.d.ts +12 -0
  64. package/dist/clients/attach/context-message.js +72 -0
  65. package/dist/clients/attach/git-info.d.ts +20 -0
  66. package/dist/clients/attach/git-info.js +44 -0
  67. package/dist/clients/attach/graph-overlay.d.ts +13 -4
  68. package/dist/clients/attach/graph-overlay.js +59 -19
  69. package/dist/clients/attach/input-controller.d.ts +75 -13
  70. package/dist/clients/attach/input-controller.js +305 -67
  71. package/dist/clients/attach/pickers.d.ts +58 -0
  72. package/dist/clients/attach/pickers.js +171 -0
  73. package/dist/clients/attach/slash-commands.d.ts +22 -0
  74. package/dist/clients/attach/slash-commands.js +91 -25
  75. package/dist/clients/attach/titled-editor.d.ts +33 -0
  76. package/dist/clients/attach/titled-editor.js +84 -0
  77. package/dist/clients/attach/view-socket.d.ts +18 -1
  78. package/dist/clients/attach/view-socket.js +70 -1
  79. package/dist/clients/web/dev-server.d.ts +7 -0
  80. package/dist/clients/web/dev-server.js +59 -0
  81. package/dist/clients/web/events.d.ts +14 -0
  82. package/dist/clients/web/events.js +151 -0
  83. package/dist/clients/web/server.d.ts +18 -0
  84. package/dist/clients/web/server.js +450 -0
  85. package/dist/clients/web/web-cmd.d.ts +2 -0
  86. package/dist/clients/web/web-cmd.js +120 -0
  87. package/dist/commands/canvas.js +1 -2
  88. package/dist/commands/chord.js +1 -1
  89. package/dist/commands/dashboard.js +5 -1
  90. package/dist/commands/memory/__tests__/lint-schema.test.js +6 -6
  91. package/dist/commands/memory/lint.js +14 -2
  92. package/dist/commands/memory/read.js +2 -2
  93. package/dist/commands/memory/shared.d.ts +7 -1
  94. package/dist/commands/memory/shared.js +18 -3
  95. package/dist/commands/memory/write.js +8 -4
  96. package/dist/commands/memory.js +4 -4
  97. package/dist/commands/node.d.ts +1 -1
  98. package/dist/commands/node.js +64 -65
  99. package/dist/commands/pkg.js +1 -2
  100. package/dist/commands/revive.js +11 -3
  101. package/dist/commands/sys/sync.d.ts +1 -0
  102. package/dist/commands/sys/sync.js +187 -0
  103. package/dist/commands/sys.js +3 -2
  104. package/dist/commands/view-cycle.js +2 -2
  105. package/dist/commands/view-list.js +8 -8
  106. package/dist/commands/view-new.js +21 -17
  107. package/dist/commands/view-pick.js +1 -1
  108. package/dist/commands/view-run.js +35 -14
  109. package/dist/commands/view.js +8 -6
  110. package/dist/commands/workspace.d.ts +2 -0
  111. package/dist/commands/workspace.js +161 -0
  112. package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
  113. package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
  114. package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
  115. package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
  116. package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
  117. package/dist/core/__tests__/child-death-wake.test.js +56 -111
  118. package/dist/core/__tests__/close.test.js +14 -20
  119. package/dist/core/__tests__/context-intro.test.js +19 -19
  120. package/dist/core/__tests__/daemon-boot.test.js +30 -23
  121. package/dist/core/__tests__/draw-style.test.js +1 -1
  122. package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
  123. package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
  124. package/dist/core/__tests__/fixtures/fake-engine.d.ts +17 -1
  125. package/dist/core/__tests__/fixtures/fake-engine.js +22 -2
  126. package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
  127. package/dist/core/__tests__/focuses.test.js +25 -13
  128. package/dist/core/__tests__/fork.test.js +22 -5
  129. package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
  130. package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
  131. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
  132. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
  133. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
  134. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
  135. package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
  136. package/dist/core/__tests__/full/cascade-close.test.js +22 -8
  137. package/dist/core/__tests__/full/detach-focus.test.js +66 -169
  138. package/dist/core/__tests__/helpers/harness.d.ts +6 -9
  139. package/dist/core/__tests__/helpers/harness.js +19 -36
  140. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
  141. package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
  142. package/dist/core/__tests__/lifecycle.test.js +1 -19
  143. package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
  144. package/dist/core/__tests__/memory-resolver.test.js +1 -1
  145. package/dist/core/__tests__/on-read-dedup-resume.test.js +2 -2
  146. package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
  147. package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
  148. package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
  149. package/dist/core/__tests__/relaunch-root.test.js +114 -0
  150. package/dist/core/__tests__/reset.test.js +19 -79
  151. package/dist/core/__tests__/steer-note.test.js +9 -7
  152. package/dist/core/__tests__/tmux-surface.test.js +13 -7
  153. package/dist/core/bootstrap.d.ts +0 -3
  154. package/dist/core/bootstrap.js +1 -143
  155. package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
  156. package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
  157. package/dist/core/canvas/browse/app.js +131 -8
  158. package/dist/core/canvas/browse/model.d.ts +23 -5
  159. package/dist/core/canvas/browse/model.js +68 -13
  160. package/dist/core/canvas/browse/render.d.ts +7 -1
  161. package/dist/core/canvas/browse/render.js +178 -47
  162. package/dist/core/canvas/canvas.js +1 -1
  163. package/dist/core/canvas/focuses.d.ts +4 -4
  164. package/dist/core/canvas/focuses.js +7 -7
  165. package/dist/core/canvas/nav-model.js +8 -7
  166. package/dist/core/canvas/render.d.ts +68 -9
  167. package/dist/core/canvas/render.js +195 -61
  168. package/dist/core/canvas/types.d.ts +16 -23
  169. package/dist/core/config.js +1 -4
  170. package/dist/core/memory-resolver.js +43 -5
  171. package/dist/core/runtime/bearings.d.ts +1 -1
  172. package/dist/core/runtime/bearings.js +8 -8
  173. package/dist/core/runtime/branded-host.d.ts +16 -0
  174. package/dist/core/runtime/branded-host.js +127 -0
  175. package/dist/core/runtime/broker-protocol.d.ts +212 -6
  176. package/dist/core/runtime/broker.d.ts +4 -1
  177. package/dist/core/runtime/broker.js +494 -58
  178. package/dist/core/runtime/close.d.ts +12 -2
  179. package/dist/core/runtime/close.js +37 -16
  180. package/dist/core/runtime/front-door.js +6 -14
  181. package/dist/core/runtime/host.d.ts +14 -34
  182. package/dist/core/runtime/host.js +14 -50
  183. package/dist/core/runtime/launch.d.ts +12 -9
  184. package/dist/core/runtime/launch.js +27 -15
  185. package/dist/core/runtime/lifecycle.d.ts +1 -1
  186. package/dist/core/runtime/lifecycle.js +15 -19
  187. package/dist/core/runtime/nodes.d.ts +0 -55
  188. package/dist/core/runtime/nodes.js +6 -74
  189. package/dist/core/runtime/placement.d.ts +91 -335
  190. package/dist/core/runtime/placement.js +262 -840
  191. package/dist/core/runtime/promote.d.ts +2 -0
  192. package/dist/core/runtime/promote.js +24 -6
  193. package/dist/core/runtime/recap.d.ts +8 -0
  194. package/dist/core/runtime/recap.js +107 -0
  195. package/dist/core/runtime/recycle.js +25 -61
  196. package/dist/core/runtime/reset.d.ts +43 -37
  197. package/dist/core/runtime/reset.js +131 -218
  198. package/dist/core/runtime/revive.d.ts +9 -29
  199. package/dist/core/runtime/revive.js +40 -139
  200. package/dist/core/runtime/spawn.d.ts +15 -16
  201. package/dist/core/runtime/spawn.js +165 -194
  202. package/dist/core/runtime/surface-bg.d.ts +11 -0
  203. package/dist/core/runtime/surface-bg.js +68 -0
  204. package/dist/core/runtime/tmux.d.ts +13 -50
  205. package/dist/core/runtime/tmux.js +33 -87
  206. package/dist/core/scope.d.ts +1 -2
  207. package/dist/core/scope.js +1 -5
  208. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
  209. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
  210. package/dist/core/skill-sync/builtins.d.ts +42 -0
  211. package/dist/core/skill-sync/builtins.js +112 -0
  212. package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
  213. package/dist/core/skill-sync/claude-plugins.js +71 -0
  214. package/dist/core/skill-sync/engine.d.ts +42 -0
  215. package/dist/core/skill-sync/engine.js +633 -0
  216. package/dist/core/skill-sync/export.d.ts +23 -0
  217. package/dist/core/skill-sync/export.js +86 -0
  218. package/dist/core/skill-sync/manifest.d.ts +64 -0
  219. package/dist/core/skill-sync/manifest.js +181 -0
  220. package/dist/core/skill-sync/profile.d.ts +76 -0
  221. package/dist/core/skill-sync/profile.js +173 -0
  222. package/dist/core/skill-sync/snapshot.d.ts +57 -0
  223. package/dist/core/skill-sync/snapshot.js +120 -0
  224. package/dist/core/substrate/index.d.ts +1 -1
  225. package/dist/core/substrate/index.js +1 -1
  226. package/dist/core/substrate/injected-store.js +3 -3
  227. package/dist/core/substrate/on-read.js +68 -6
  228. package/dist/core/substrate/render.d.ts +8 -11
  229. package/dist/core/substrate/render.js +29 -43
  230. package/dist/core/substrate/schema.d.ts +10 -3
  231. package/dist/core/substrate/schema.js +6 -3
  232. package/dist/core/tui/host.d.ts +12 -4
  233. package/dist/core/tui/host.js +280 -149
  234. package/dist/core/view/bridge.d.ts +10 -0
  235. package/dist/core/view/bridge.js +31 -0
  236. package/dist/core/view/chrome.d.ts +9 -0
  237. package/dist/core/view/chrome.js +22 -0
  238. package/dist/core/view/contract.d.ts +171 -0
  239. package/dist/core/view/contract.js +23 -0
  240. package/dist/core/view/loader.d.ts +31 -0
  241. package/dist/core/view/loader.js +188 -0
  242. package/dist/core/view/transport-local.d.ts +7 -0
  243. package/dist/core/view/transport-local.js +70 -0
  244. package/dist/core/view/transport.d.ts +4 -0
  245. package/dist/core/view/transport.js +15 -0
  246. package/dist/daemon/crtrd.d.ts +38 -27
  247. package/dist/daemon/crtrd.js +303 -376
  248. package/dist/daemon/manage.js +6 -1
  249. package/dist/index.js +1 -1
  250. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
  251. package/dist/pi-extensions/canvas-context-intro.js +4 -4
  252. package/dist/pi-extensions/canvas-doc-substrate.js +16 -16
  253. package/dist/pi-extensions/canvas-nav.js +5 -0
  254. package/dist/pi-extensions/canvas-recap.d.ts +37 -0
  255. package/dist/pi-extensions/canvas-recap.js +236 -0
  256. package/dist/pi-extensions/canvas-resume.js +2 -1
  257. package/dist/pi-extensions/canvas-stophook.d.ts +14 -12
  258. package/dist/pi-extensions/canvas-stophook.js +87 -101
  259. package/dist/pi-extensions/canvas-view.js +2 -1
  260. package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
  261. package/dist/pi-extensions/widget-order-bus.js +34 -0
  262. package/dist/prompts/view.d.ts +2 -2
  263. package/dist/prompts/view.js +145 -73
  264. package/dist/types.d.ts +1 -6
  265. package/dist/types.js +1 -3
  266. package/dist/web/ViewChrome.d.ts +7 -0
  267. package/dist/web/ViewChrome.js +28 -0
  268. package/dist/web/ViewPane.d.ts +39 -0
  269. package/dist/web/ViewPane.js +48 -0
  270. package/dist/web/index.d.ts +6 -0
  271. package/dist/web/index.js +16 -0
  272. package/dist/web/runtime.d.ts +39 -0
  273. package/dist/web/runtime.js +133 -0
  274. package/dist/web/states.d.ts +24 -0
  275. package/dist/web/states.js +24 -0
  276. package/dist/web/transport-http.d.ts +5 -0
  277. package/dist/web/transport-http.js +28 -0
  278. package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
  279. package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
  280. package/dist/web-client/index.html +13 -0
  281. package/package.json +20 -6
  282. package/dist/builtin-views/canvas/client.mjs +0 -303
  283. package/dist/builtin-views/canvas/view.mjs +0 -576
  284. package/dist/builtin-views/git-pr/client.mjs +0 -440
  285. package/dist/builtin-views/git-pr/view.mjs +0 -675
  286. package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
  287. package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
  288. package/dist/builtin-views/inbox/view.mjs +0 -889
  289. package/dist/builtin-views/linkedin/client.mjs +0 -610
  290. package/dist/builtin-views/linkedin/view.mjs +0 -1171
  291. package/dist/commands/pkg/bridge.d.ts +0 -1
  292. package/dist/commands/pkg/bridge.js +0 -137
  293. package/dist/commands/skill/author.d.ts +0 -3
  294. package/dist/commands/skill/author.js +0 -140
  295. package/dist/commands/skill/shared.d.ts +0 -3
  296. package/dist/commands/skill/shared.js +0 -19
  297. package/dist/commands/skill.d.ts +0 -2
  298. package/dist/commands/skill.js +0 -21
  299. package/dist/commands/tmux-spread.d.ts +0 -2
  300. package/dist/commands/tmux-spread.js +0 -144
  301. package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
  302. package/dist/core/__tests__/full/placement-focus.test.js +0 -309
  303. package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
  304. package/dist/core/__tests__/full/placement-revive.test.js +0 -238
  305. package/dist/core/__tests__/full/placement-teardown.test.js +0 -297
  306. package/dist/core/__tests__/home-session.test.js +0 -186
  307. package/dist/core/__tests__/relaunch.test.js +0 -335
  308. package/dist/core/bridge-map.d.ts +0 -19
  309. package/dist/core/bridge-map.js +0 -73
  310. package/dist/core/tui/contract.d.ts +0 -83
  311. package/dist/core/tui/contract.js +0 -8
  312. package/dist/core/tui/loader.d.ts +0 -16
  313. package/dist/core/tui/loader.js +0 -94
  314. package/dist/prompts/skill.d.ts +0 -2
  315. package/dist/prompts/skill.js +0 -650
  316. /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
  317. /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
  318. /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
  319. /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
  320. /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
  321. /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
  322. /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/titled-editor.test.d.ts} +0 -0
@@ -8,6 +8,7 @@ import { dirname, join } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import { mkdirSync } from 'node:fs';
10
10
  import { crtrHome } from '../core/canvas/paths.js';
11
+ import { hostExecPath } from '../core/runtime/branded-host.js';
11
12
  import { isDaemonRunning, readPidfile } from './crtrd.js';
12
13
  // ---------------------------------------------------------------------------
13
14
  // Entry point resolution
@@ -30,8 +31,12 @@ export function spawnDaemon() {
30
31
  }
31
32
  // Ensure the canvas home directory exists so the daemon can write its pidfile.
32
33
  mkdirSync(crtrHome(), { recursive: true });
34
+ // Launch from the crouter-branded host binary so the daemon shows "crouter"
35
+ // in macOS Full Disk Access, not "node" (see branded-host). NOTE: when crtrd
36
+ // is launchd-owned, the plist's ProgramArguments must point at the branded
37
+ // binary too — this path only covers a manual `crtr canvas daemon start`.
33
38
  const entry = resolveCrtrdEntry();
34
- const child = spawn(process.execPath, [entry], {
39
+ const child = spawn(hostExecPath(), [entry], {
35
40
  detached: true,
36
41
  stdio: 'ignore',
37
42
  });
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@
10
10
  // side effects and no circular imports — keep it that way.
11
11
  // ── Runtime control ──────────────────────────────────────────────────────
12
12
  // Sanctioned launchers/mutators. reviveNode is the ONLY sanctioned launcher of
13
- // `pi --session`; spawnChild's host is chosen via `hostKind: 'tmux' | 'broker'`.
13
+ // the broker engine; every node is broker-hosted (no host_kind choice).
14
14
  export { spawnChild } from './core/runtime/spawn.js';
15
15
  export { reviveNode } from './core/runtime/revive.js';
16
16
  export { closeNode } from './core/runtime/close.js';
@@ -109,37 +109,42 @@ test('natural stop while awaiting a live worker → idle-release with NO push (n
109
109
  // ---------------------------------------------------------------------------
110
110
  // §5.1 — the §1.7 agent_end branch map on the focuses table. Every assertion is
111
111
  // on the canvas focuses/runtime rows after firing agent_end (TMUX_PANE is
112
- // cleared in beforeEach, so the focus helpers in the handler are pure DB and the
113
- // '%pane' ids below are never read by tmux). status='done' is reached by setting
114
- // the runtime row directly (the branch reads getNode(nodeId).status).
112
+ // cleared in beforeEach). NOTE (broker-host cut): the placement read `focusOf`
113
+ // used by the handler now GC-prunes a viewer row whose pane is not a LIVE tmux
114
+ // pane (liveOrPrune paneExists), so a fabricated '%pane' id is pruned on the
115
+ // first read. To drive the handler's focus path deterministically in the
116
+ // tmux-free fast tier we register viewer rows with a NULL pane (a registered-
117
+ // but-not-yet-realized viewer, which liveOrPrune passes through). status='done'
118
+ // is reached by setting the runtime row directly (the branch reads
119
+ // getNode(nodeId).status).
115
120
  // ---------------------------------------------------------------------------
116
- test('§5.1.1 truly-done + focused + dormant idle-release manager-not-focused MANAGER TAKEOVER of the focus row', () => {
121
+ test('§5.1.1 truly-done + focused focus row CLOSED, NO manager takeover (broker cut deleted handFocusToManager)', () => {
122
+ // Regression guard for the broker-host cut: the engine is now a detached
123
+ // broker, not a pane, so there is no engine to hand to a manager. The old
124
+ // "dormant idle-release manager takes over the finished node's focus row"
125
+ // path (handFocusToManager) is DELETED — a truly-done focused node just
126
+ // tearDownNode's its own viewer and shuts down, EVEN when a dormant
127
+ // idle-release manager exists (the exact case that used to trigger takeover).
117
128
  createNode(node('root', { parent: null, lifecycle: 'resident' }));
118
- // mgr is dormant + idle-release — the ONLY dormant manager the daemon will
119
- // revive INTO the frozen pane, so the ONLY dormant case that is a real takeover.
120
129
  createNode(node('mgr', { parent: 'root', lifecycle: 'terminal', mode: 'orchestrator', status: 'idle', intent: 'idle-release' }));
121
- // M starts WITH a recorded LOCATION so the MINOR presence-null is observable.
130
+ // M starts WITH a recorded LOCATION so the done-path presence-null is observable.
122
131
  createNode(node('M', { parent: 'mgr', lifecycle: 'terminal', pane: '%m', tmux_session: 'Suser', window: '@wm' }));
123
132
  subscribe('mgr', 'M', true);
124
- openFocusRow('fM', '%m', 'Suser', 'M');
133
+ openFocusRow('fM', null, 'Suser', 'M'); // null pane → focusOf returns it → drives tearDownNode
125
134
  process.env['CRTR_NODE_ID'] = 'M';
126
135
  setStatus('M', 'done'); // pushed final this turn
127
136
  const pi = makeFakePi();
128
137
  registerCanvasStophook(pi);
129
138
  let shutdown = false;
130
139
  pi.fire('agent_end', stopEvent('done — pushed final'), { shutdown: () => { shutdown = true; } });
131
- // managerId = M.parent = 'mgr' (idle-release, not focused elsewhere, no live
132
- // pane here the DORMANT-takeover path) handFocusToManager repoints fM's
133
- // occupant M→mgr (true ONLY because mgr is idle+idle-release). The
134
- // daemon later revives mgr INTO M's frozen focus pane. Non-vacuous: a no-op (no
135
- // handoff) impl leaves M as occupant, so getFocusByNode('mgr') is null AND
136
- // getFocusByNode('M') still names fM — both asserts fail.
137
- assert.equal(getFocusByNode('mgr')?.focus_id, 'fM', 'focus row taken over by the manager');
140
+ // The done branch: focusOf('M') != null tearDownNode('M') CLOSES fM. The
141
+ // manager is NOT handed anything (takeover deleted). Non-vacuous: a takeover
142
+ // impl would leave getFocusByNode('mgr').focus_id === 'fM'.
143
+ assert.equal(getFocusByNode('mgr'), null, 'the manager is NOT handed the finished node\'s focus (takeover deleted)');
144
+ assert.equal(getFocusById('fM'), null, 'the finished node\'s viewer focus row is closed');
138
145
  assert.equal(getFocusByNode('M'), null, 'the finished node no longer occupies any focus');
139
- assert.equal(shutdown, true, 'pi shut down after the handoff');
140
- // MINOR: after a successful takeover M (done) owns no pane (Invariant P) — its
141
- // own presence is nulled so two rows never reference %m. Non-vacuous: an impl
142
- // that skips the done-path setPresence-null leaves getNode('M').pane === '%m'.
146
+ assert.equal(shutdown, true, 'pi shut down after teardown');
147
+ // The done node's own presence is nulled (setPresence(null) on the done path).
143
148
  assert.equal(getNode('M')?.pane ?? null, null, 'the finished node\'s own LOCATION pane is nulled');
144
149
  assert.equal(getNode('M')?.window ?? null, null, 'the finished node\'s window presence is nulled too');
145
150
  });
@@ -206,7 +211,10 @@ test('§5.1.5 awaiting + FOCUSED → STAY ALIVE: pi keeps running (no release),
206
211
  createNode(node('worker', { parent: 'mgr', lifecycle: 'terminal', status: 'active' }));
207
212
  subscribe('root', 'mgr', true);
208
213
  subscribe('mgr', 'worker', true); // mgr awaits a live worker → would idle-release if UNfocused
209
- openFocusRow('fMgr', '%g', 'Suser', 'mgr');
214
+ // null pane (broker cut): focusOf passes a null-pane viewer row through, so the
215
+ // handler reads mgr as FOCUSED in the tmux-free fast tier (a fabricated %pane
216
+ // would be GC-pruned by liveOrPrune and the node would wrongly release).
217
+ openFocusRow('fMgr', null, 'Suser', 'mgr');
210
218
  process.env['CRTR_NODE_ID'] = 'mgr';
211
219
  const pi = makeFakePi();
212
220
  registerCanvasStophook(pi);
@@ -15,11 +15,11 @@
15
15
  //
16
16
  // The block carries: the path to the node's own context dir and the framing for
17
17
  // what belongs there (a shared document store for the other nodes). EVERY node
18
- // also gets a `<references>` block rendered from the document substrate —
19
- // eligible `reference` docs at their system-prompt visibility rung, plus the
18
+ // also gets a `<knowledge>` block rendered from the document substrate —
19
+ // eligible `knowledge` docs at their system-prompt visibility rung, plus the
20
20
  // node's own node-local substrate docs — so the bearings name what to read on
21
- // demand. (Skills and preferences surface as their own `<skills>` /
22
- // `<preferences>` blocks of the system prompt, not in this block.) An
21
+ // demand. (Preferences surface as their own `<preferences>` block of the
22
+ // system prompt, not in this block.) An
23
23
  // orchestrator additionally gets the across-refresh-cycles framing (the one
24
24
  // thing a terminal worker's bearings drop). The prose lives in
25
25
  // core/runtime/bearings.ts (shared with the promotion guidance dump).
@@ -9,13 +9,13 @@
9
9
  // It owns the substrate's two new pi hooks:
10
10
  //
11
11
  // 1. before_agent_start — the BOOT system-prompt half. Splices the rendered
12
- // `<skills>` + `<preferences>` + `<memory-guidance>` blocks into
13
- // `event.systemPrompt`, right after pi's native "Available tools" list
14
- // (before the `\n\nGuidelines:`
15
- // anchor), so the substrate's skills/preferences sit in the tool-selection
16
- // frame the agent reads while choosing a capability — mirroring the
17
- // personal crouter-help.ts anchor logic. Falls back to appending when the
18
- // anchor is absent.
12
+ // `<preferences>` + `<memory-guidance>` blocks into `event.systemPrompt`,
13
+ // right after pi's native "Available tools" list (before the
14
+ // `\n\nGuidelines:` anchor), so the substrate's standing directives sit in
15
+ // the tool-selection frame the agent reads while choosing a capability —
16
+ // mirroring the personal crouter-help.ts anchor logic. Falls back to
17
+ // appending when the anchor is absent. (Knowledge rides the `<crtr-context>`
18
+ // bearings message instead — see core/substrate/render.ts.)
19
19
  //
20
20
  // 2. tool_result (gated on `read`) — the ON-READ half. For each non-error
21
21
  // `read`, surfaces the substrate docs that should appear alongside the file
@@ -34,7 +34,7 @@
34
34
  // with no dep on the pi packages. Mirrors canvas-context-intro.ts.
35
35
  import { homedir } from 'node:os';
36
36
  import { join, resolve } from 'node:path';
37
- import { renderMemoryGuidance, renderOnReadDocs, renderPreferencesSection, renderSkillsSection, } from '../core/substrate/index.js';
37
+ import { renderMemoryGuidance, renderOnReadDocs, renderPreferencesSection, } from '../core/substrate/index.js';
38
38
  import { clearSessionCache } from '../core/substrate/session-cache.js';
39
39
  import { loadInjectedDocs, saveInjectedDocs } from '../core/substrate/injected-store.js';
40
40
  // ---------------------------------------------------------------------------
@@ -61,9 +61,9 @@ export function registerCanvasDocSubstrate(pi) {
61
61
  // cycle. A resume reuses the same .jsonl transcript in a NEW pi process, so
62
62
  // the set is REHYDRATED from disk at process start (not started empty) and is
63
63
  // NOT cleared on session_start — a resume continues the transcript, so the
64
- // dedup must carry forward. The launch paths that begin a FRESH transcript
65
- // (revive resume=false, reviveInPlace, relaunchRootInPane) delete the file, so
66
- // a new conversation rehydrates empty here. See core/substrate/injected-store.ts.
64
+ // dedup must carry forward. The one launch path that begins a FRESH transcript
65
+ // (reviveNode with resume=false) deletes the file, so a new conversation
66
+ // rehydrates empty here. See core/substrate/injected-store.ts.
67
67
  const injectedDocs = loadInjectedDocs(nodeId);
68
68
  pi.on('session_start', () => {
69
69
  // Only the per-session substrate PARSE cache resets each session (so the
@@ -71,14 +71,14 @@ export function registerCanvasDocSubstrate(pi) {
71
71
  // transcript-scoped, not session-scoped — deliberately NOT cleared here.
72
72
  clearSessionCache();
73
73
  });
74
- // 1. BOOT system-prompt half — splice `<skills>` + `<preferences>` +
75
- // `<memory-guidance>`. The guidance block is always present for a canvas
76
- // node (the memory system always exists), so the splice is never empty even
77
- // when both trees are.
74
+ // 1. BOOT system-prompt half — splice `<preferences>` + `<memory-guidance>`.
75
+ // The guidance block is always present for a canvas node (the memory system
76
+ // always exists), so the splice is never empty even when the preferences
77
+ // tree is. (Knowledge no longer enters the system prompt — it rides the
78
+ // `<crtr-context>` bearings message via renderKnowledgeBlock.)
78
79
  pi.on('before_agent_start', (event) => {
79
80
  try {
80
81
  const sections = [
81
- renderSkillsSection(nodeId),
82
82
  renderPreferencesSection(nodeId),
83
83
  renderMemoryGuidance(),
84
84
  ].filter((s) => s !== '');
@@ -47,6 +47,7 @@ import { execFile } from 'node:child_process';
47
47
  import { fullName } from '../core/canvas/index.js';
48
48
  import { beginFrame, cNode, managerOf, liveReports, sortedChildIds, subtreeIds, climbRoot, computeSubtreeActivity, buildGraphModel, renderGraphRow, navLabel, coloredGlyph, truncate, tokensCell, cycleBadge, childBadge, liveBelowBadge, askBadge, activityCell, focusedNodeIds, graphWidgetBudget, fetchAsksMap, shortId, DIM, RESET, BOLD, YELLOW, GRAPH_HINT, } from '../core/canvas/nav-model.js';
49
49
  import { readConfig } from '../core/config.js';
50
+ import { onNavRerender } from './widget-order-bus.js';
50
51
  // ---------------------------------------------------------------------------
51
52
  // Module-level state — persists across /reload so guards don't stack and fold
52
53
  // state / current view survive a hot-swap.
@@ -317,6 +318,10 @@ export function registerCanvasNav(pi) {
317
318
  render();
318
319
  }, RENDER_DEBOUNCE_MS);
319
320
  };
321
+ // Let canvas-recap ask us to re-assert the manager line so its recap card
322
+ // stays the topmost aboveEditor chrome (pi's widget store is insertion-
323
+ // ordered; re-setting crtr-managers drops it below crtr-recap).
324
+ onNavRerender(scheduleRender);
320
325
  // -------------------------------------------------------------------------
321
326
  // Actions (all shell out; the extension stays tmux/revive-free)
322
327
  // -------------------------------------------------------------------------
@@ -0,0 +1,37 @@
1
+ interface TextContentLike {
2
+ type: string;
3
+ text?: string;
4
+ }
5
+ interface MessageLike {
6
+ role: string;
7
+ content: string | TextContentLike[];
8
+ }
9
+ interface SessionEntryLike {
10
+ type: string;
11
+ message?: MessageLike;
12
+ }
13
+ interface SessionManagerLike {
14
+ getEntries(): SessionEntryLike[];
15
+ }
16
+ interface UIContextLike {
17
+ setWidget(key: string, content: string[] | undefined, options?: {
18
+ placement?: 'aboveEditor' | 'belowEditor';
19
+ }): void;
20
+ }
21
+ interface ExtensionCtxLike {
22
+ ui?: UIContextLike;
23
+ mode?: string;
24
+ sessionManager?: SessionManagerLike;
25
+ }
26
+ type PiEvents = 'session_start' | 'input' | 'turn_end' | 'session_shutdown';
27
+ interface PiLike {
28
+ on(event: PiEvents, handler: (event: any, ctx: ExtensionCtxLike) => void): void;
29
+ }
30
+ /**
31
+ * Register the inactivity-recap chrome on `pi`.
32
+ *
33
+ * Returns immediately (inert) when CRTR_NODE_ID is absent — a plain pi session
34
+ * or legacy job agent loads it as a no-op.
35
+ */
36
+ export declare function registerCanvasRecap(pi: PiLike): void;
37
+ export default registerCanvasRecap;
@@ -0,0 +1,236 @@
1
+ // canvas-recap.ts — pi extension for pi-native canvas agent nodes.
2
+ //
3
+ // A per-node INACTIVITY RECAP, drawn as the topmost chrome above the editor.
4
+ // When a node has had NO new message (user or assistant) for IDLE_MS (300s by
5
+ // default), it runs Haiku over the literal conversation and pins a terse
6
+ // three-fragment card — goal / doing-now / next — above canvas-nav's manager
7
+ // line. The card disappears the instant the next message arrives, and the idle
8
+ // clock restarts.
9
+ //
10
+ // trigger/clear are keyed off MESSAGES, not turns-of-work:
11
+ // • a user `input` event OR an assistant `turn_end` resets the idle clock
12
+ // and clears any shown recap.
13
+ // • a single low-rate timer (no busy-loop) checks elapsed-since-last-message
14
+ // and, on crossing IDLE_MS, generates + shows the recap once.
15
+ //
16
+ // Haiku input = ALL user + assistant LITERAL text, most-recent within a char
17
+ // budget. Tool calls, tool results, thinking blocks, and extension-injected
18
+ // custom messages are excluded (only `message` entries with role user/assistant,
19
+ // and only their `text` content blocks).
20
+ //
21
+ // Placement: key `crtr-recap`, placement `aboveEditor`, ordered ABOVE
22
+ // `crtr-managers` via the widget-order bus (pi's widget store is insertion-
23
+ // ordered and re-setting moves a key to the bottom; after we paint the recap
24
+ // we ask canvas-nav to re-assert its manager line, which drops it below us).
25
+ //
26
+ // Surface gating: shows in BOTH the in-pane pi TUI (mode 'tui') and the
27
+ // headless broker → attach/web viewers (mode 'print', setWidget broadcasts to
28
+ // all surfaces). It does NOT gate on mode === 'tui' (that would kill the
29
+ // headless+attach path). It NO-OPs only when ui/setWidget is unavailable, and
30
+ // harmlessly in a true one-shot `pi -p` (the unref'd timer never fires before
31
+ // the process exits, and naming-style one-shots load with --no-extensions).
32
+ //
33
+ // INERT when CRTR_NODE_ID is absent (a plain pi session or legacy job agent).
34
+ //
35
+ // Plain TS-with-types — no imports from @earendil-works/* so this compiles
36
+ // inside crouter's own tsc build without a dep on the pi packages.
37
+ import { generateRecap } from '../core/runtime/recap.js';
38
+ import { requestNavRerender } from './widget-order-bus.js';
39
+ // ---------------------------------------------------------------------------
40
+ // Tuning constants
41
+ // ---------------------------------------------------------------------------
42
+ /** No-new-message window before a recap is shown. 300s by default; override with
43
+ * CRTR_RECAP_IDLE_MS (for fast runtime verification). */
44
+ function idleMs() {
45
+ const raw = process.env['CRTR_RECAP_IDLE_MS'];
46
+ const n = raw !== undefined ? parseInt(raw, 10) : NaN;
47
+ return Number.isFinite(n) && n > 0 ? n : 300_000;
48
+ }
49
+ /** How often the (single, low-rate) timer checks elapsed-since-last-message.
50
+ * Mirrors canvas-nav's ASK_POLL_MS — cheap and tmux/crtr-free. */
51
+ const POLL_MS = 7_000;
52
+ /** Char budget on the conversation fed to Haiku — the model only needs the gist,
53
+ * so we keep the MOST-RECENT text within this cap (a recap is about where we
54
+ * left off, not the whole history). */
55
+ const CONVO_CHAR_BUDGET = 12_000;
56
+ const WIDGET_KEY = 'crtr-recap';
57
+ // ---------------------------------------------------------------------------
58
+ // Module-level state — persists across /reload so the timer doesn't stack.
59
+ // ---------------------------------------------------------------------------
60
+ /** The one live poll timer. Cleared and replaced on every re-registration. */
61
+ let liveTimer;
62
+ // ---------------------------------------------------------------------------
63
+ // Conversation extraction — literal user+assistant text ONLY.
64
+ // ---------------------------------------------------------------------------
65
+ /** Pull the literal text out of a message's content (string or text blocks),
66
+ * dropping non-text blocks (thinking, toolCall, image). */
67
+ function messageText(msg) {
68
+ const c = msg.content;
69
+ if (typeof c === 'string')
70
+ return c.trim();
71
+ if (!Array.isArray(c))
72
+ return '';
73
+ return c
74
+ .filter((b) => b !== null && typeof b === 'object' && b.type === 'text' && typeof b.text === 'string')
75
+ .map((b) => (b.text ?? '').trim())
76
+ .filter((t) => t !== '')
77
+ .join('\n')
78
+ .trim();
79
+ }
80
+ /** Concatenate the conversation's user + assistant literal text into a single
81
+ * `User:`/`Agent:` transcript, keeping only the MOST-RECENT CONVO_CHAR_BUDGET
82
+ * characters. Excludes tool calls/results (role toolResult, plus toolCall/
83
+ * thinking content blocks dropped by messageText) and extension-injected
84
+ * custom messages (entry.type !== 'message'). Returns '' when there is nothing
85
+ * to summarize. */
86
+ function buildConversation(sm) {
87
+ let entries;
88
+ try {
89
+ entries = sm.getEntries();
90
+ }
91
+ catch {
92
+ return '';
93
+ }
94
+ const parts = [];
95
+ for (const e of entries) {
96
+ if (e.type !== 'message' || e.message === undefined)
97
+ continue;
98
+ const role = e.message.role;
99
+ if (role !== 'user' && role !== 'assistant')
100
+ continue; // drop toolResult etc.
101
+ const text = messageText(e.message);
102
+ if (text === '')
103
+ continue;
104
+ parts.push(`${role === 'user' ? 'User' : 'Agent'}: ${text}`);
105
+ }
106
+ if (parts.length === 0)
107
+ return '';
108
+ const full = parts.join('\n\n');
109
+ return full.length > CONVO_CHAR_BUDGET ? full.slice(full.length - CONVO_CHAR_BUDGET) : full;
110
+ }
111
+ // ---------------------------------------------------------------------------
112
+ // Extension
113
+ // ---------------------------------------------------------------------------
114
+ /**
115
+ * Register the inactivity-recap chrome on `pi`.
116
+ *
117
+ * Returns immediately (inert) when CRTR_NODE_ID is absent — a plain pi session
118
+ * or legacy job agent loads it as a no-op.
119
+ */
120
+ export function registerCanvasRecap(pi) {
121
+ const nodeId = process.env['CRTR_NODE_ID'];
122
+ if (nodeId === undefined || nodeId.trim() === '')
123
+ return; // not a canvas node
124
+ // Captured on session_start; used by the timer and the message handlers.
125
+ let ui;
126
+ let sessionManager;
127
+ /** Wall-clock of the most recent user/assistant message. The idle clock. */
128
+ let lastMessageAt = Date.now();
129
+ /** True while a recap is currently shown (so we generate at most once per
130
+ * idle window, and know whether a clear is needed on the next message). */
131
+ let recapShown = false;
132
+ /** True while a Haiku generation is in flight (so we don't fire a second). */
133
+ let generating = false;
134
+ const canPaint = () => ui !== undefined && typeof ui.setWidget === 'function';
135
+ const clearRecap = () => {
136
+ if (!canPaint())
137
+ return;
138
+ try {
139
+ ui.setWidget(WIDGET_KEY, undefined, { placement: 'aboveEditor' });
140
+ }
141
+ catch { /* best-effort */ }
142
+ };
143
+ /** Render the three fragments as the topmost above-editor card. */
144
+ const showRecap = (fragments) => {
145
+ if (!canPaint())
146
+ return;
147
+ const labels = ['◷ goal', '· now ', '→ next'];
148
+ const lines = fragments.slice(0, 3).map((f, i) => `${labels[i] ?? ' '} ${f}`);
149
+ if (lines.length === 0)
150
+ return;
151
+ try {
152
+ ui.setWidget(WIDGET_KEY, lines, { placement: 'aboveEditor' });
153
+ }
154
+ catch { /* best-effort */ }
155
+ // Re-assert the manager line BELOW us (pi's widget store is insertion-
156
+ // ordered; re-setting crtr-managers drops it under crtr-recap).
157
+ requestNavRerender();
158
+ };
159
+ /** A user or assistant message landed: reset the idle clock and clear any
160
+ * shown recap (the conversation has moved on). */
161
+ const onMessage = () => {
162
+ lastMessageAt = Date.now();
163
+ if (recapShown) {
164
+ recapShown = false;
165
+ clearRecap();
166
+ requestNavRerender();
167
+ }
168
+ };
169
+ // -------------------------------------------------------------------------
170
+ // Events
171
+ // -------------------------------------------------------------------------
172
+ pi.on('session_start', (_event, ctx) => {
173
+ ui = ctx.ui;
174
+ sessionManager = ctx.sessionManager;
175
+ // Fresh session / hot-swap: start the idle clock now and drop any stale
176
+ // recap that bled through a /reload.
177
+ lastMessageAt = Date.now();
178
+ recapShown = false;
179
+ generating = false;
180
+ clearRecap();
181
+ });
182
+ pi.on('input', (_event, _ctx) => {
183
+ // Every user message (any source) is activity — reset + clear.
184
+ onMessage();
185
+ });
186
+ pi.on('turn_end', (_event, _ctx) => {
187
+ // A completed assistant turn is activity — reset + clear.
188
+ onMessage();
189
+ });
190
+ // -------------------------------------------------------------------------
191
+ // The single low-rate idle poll — no busy-loop, mirrors canvas-nav's timer.
192
+ // -------------------------------------------------------------------------
193
+ if (liveTimer !== undefined)
194
+ clearInterval(liveTimer);
195
+ const timer = setInterval(() => {
196
+ try {
197
+ if (!canPaint() || sessionManager === undefined)
198
+ return;
199
+ if (recapShown || generating)
200
+ return;
201
+ if (Date.now() - lastMessageAt < idleMs())
202
+ return;
203
+ const convo = buildConversation(sessionManager);
204
+ if (convo === '')
205
+ return; // nothing to summarize yet
206
+ // Snapshot the clock: if a message arrives while Haiku runs, discard the
207
+ // (now-stale) result instead of painting over the live conversation.
208
+ const startedAt = lastMessageAt;
209
+ generating = true;
210
+ generateRecap(convo, (fragments) => {
211
+ generating = false;
212
+ if (lastMessageAt !== startedAt)
213
+ return; // a message landed mid-flight
214
+ if (!canPaint())
215
+ return;
216
+ recapShown = true;
217
+ showRecap(fragments);
218
+ });
219
+ // generateRecap is silent on failure (never calls back), so clear the
220
+ // in-flight flag after the timeout window even if no callback fires.
221
+ setTimeout(() => { generating = false; }, 30_000).unref?.();
222
+ }
223
+ catch {
224
+ /* timer is best-effort */
225
+ }
226
+ }, POLL_MS);
227
+ if (typeof timer.unref === 'function')
228
+ timer.unref();
229
+ liveTimer = timer;
230
+ pi.on('session_shutdown', () => {
231
+ clearInterval(timer);
232
+ if (liveTimer === timer)
233
+ liveTimer = undefined;
234
+ });
235
+ }
236
+ export default registerCanvasRecap;
@@ -27,6 +27,7 @@
27
27
  // inside crouter's own tsc build without a dep on the pi packages (mirrors
28
28
  // canvas-nav.ts / canvas-commands.ts).
29
29
  import { execFile } from 'node:child_process';
30
+ import { surfaceTmuxStyleArgs } from '../core/runtime/surface-bg.js';
30
31
  /** Single-quote a string for safe interpolation into a `sh -c` command line —
31
32
  * tmux runs the display-popup trailing string through the shell, so a cwd with
32
33
  * spaces or quotes must be escaped. */
@@ -66,7 +67,7 @@ export function registerCanvasResume(pi) {
66
67
  const cwd = shellQuote(process.cwd());
67
68
  const cmd = `crtr canvas browse --return-pane ${origPane} --cwd ${cwd}`;
68
69
  try {
69
- execFile('tmux', ['display-popup', '-E', '-w', '90%', '-h', '85%', cmd], () => { });
70
+ execFile('tmux', ['display-popup', '-E', '-w', '90%', '-h', '85%', ...surfaceTmuxStyleArgs(), cmd], () => { });
70
71
  }
71
72
  catch { /* best-effort */ }
72
73
  return;
@@ -6,20 +6,22 @@ interface PiLike {
6
6
  }) => void;
7
7
  }
8
8
  /** The nudge text for a crossed band, specialized to the node's (mode,
9
- * lifecycle) persona + how far along the escalation it is.
9
+ * lifecycle) persona + how far along the escalation it is. `crtr node yield` is
10
+ * the single "keep going past this window" verb for every persona: it refreshes
11
+ * the node into a clean window against a roadmap, and for a base node
12
+ * auto-promotes transparently — so no persona is ever steered at a bare `node
13
+ * promote` (which framed promotion as a heavy "become an orchestrator" step and
14
+ * bred yield-avoidance).
10
15
  *
11
- * - orchestrator (terminal OR resident): checkpoint its roadmap and yield
12
- * (gently firmly → pushy). It has a context/roadmap.md to yield against.
13
- * - resident/base (root conversation): never promoted, so NO roadmap exists
14
- * steer it to PROMOTE if the chat is growing into a multi-phase job (which
15
- * seeds a roadmap), else wrap up / start fresh. Never points at roadmap.md or
16
- * a bare `node yield`, which for a roadmap-less root just drops context.
17
- * - terminal/base (worker): PROMOTE itself — become an orchestrator — when
18
- * work remains, with an "ignore if nearly done, finish with push final" once
19
- * it's deeper in.
16
+ * - orchestrator (terminal OR resident): checkpoint context/roadmap.md, then
17
+ * yield (it already has a roadmap to bring current first).
18
+ * - resident/base (root conversation): yield to continue in a fresh window if
19
+ * the chat is growing into a job, else wrap up — a root can simply end.
20
+ * - terminal/base (worker): yield to continue if work remains, or `push final`
21
+ * when close.
20
22
  *
21
- * At/past 185k every persona goes PUSHY: the context is long enough that
22
- * drifting further risks an overflow. */
23
+ * The wording strengthens with depth: gentle < 150k firm < 185k → pushy at/
24
+ * past 185k, where drifting further risks an overflow. */
23
25
  export declare function steerNote(at: number, lifecycle: string, mode: string): string;
24
26
  /**
25
27
  * Register the canvas turn_end / agent_end handlers on `pi`.