@crouton-kit/crouter 0.3.27 → 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 (343) 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 +8 -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__/reconnect-giveup.test.js +30 -0
  49. package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
  50. package/dist/clients/attach/__tests__/titled-editor.test.d.ts +1 -0
  51. package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
  52. package/dist/clients/attach/attach-cmd.js +445 -41
  53. package/dist/clients/attach/auth-pickers.d.ts +9 -0
  54. package/dist/clients/attach/auth-pickers.js +194 -0
  55. package/dist/clients/attach/canvas-panels.d.ts +12 -0
  56. package/dist/clients/attach/canvas-panels.js +78 -0
  57. package/dist/clients/attach/chat-view.d.ts +27 -2
  58. package/dist/clients/attach/chat-view.js +98 -9
  59. package/dist/clients/attach/clipboard-image.d.ts +47 -8
  60. package/dist/clients/attach/clipboard-image.js +150 -26
  61. package/dist/clients/attach/clipboard-text.d.ts +3 -0
  62. package/dist/clients/attach/clipboard-text.js +39 -0
  63. package/dist/clients/attach/config-load.d.ts +54 -7
  64. package/dist/clients/attach/config-load.js +99 -3
  65. package/dist/clients/attach/context-message.d.ts +12 -0
  66. package/dist/clients/attach/context-message.js +72 -0
  67. package/dist/clients/attach/git-info.d.ts +20 -0
  68. package/dist/clients/attach/git-info.js +44 -0
  69. package/dist/clients/attach/graph-overlay.d.ts +43 -0
  70. package/dist/clients/attach/graph-overlay.js +306 -0
  71. package/dist/clients/attach/input-controller.d.ts +81 -13
  72. package/dist/clients/attach/input-controller.js +307 -67
  73. package/dist/clients/attach/pickers.d.ts +58 -0
  74. package/dist/clients/attach/pickers.js +171 -0
  75. package/dist/clients/attach/slash-commands.d.ts +44 -1
  76. package/dist/clients/attach/slash-commands.js +250 -27
  77. package/dist/clients/attach/titled-editor.d.ts +33 -0
  78. package/dist/clients/attach/titled-editor.js +84 -0
  79. package/dist/clients/attach/view-socket.d.ts +37 -2
  80. package/dist/clients/attach/view-socket.js +131 -7
  81. package/dist/clients/web/dev-server.d.ts +7 -0
  82. package/dist/clients/web/dev-server.js +59 -0
  83. package/dist/clients/web/events.d.ts +14 -0
  84. package/dist/clients/web/events.js +151 -0
  85. package/dist/clients/web/server.d.ts +18 -0
  86. package/dist/clients/web/server.js +450 -0
  87. package/dist/clients/web/web-cmd.d.ts +2 -0
  88. package/dist/clients/web/web-cmd.js +120 -0
  89. package/dist/commands/canvas.js +1 -2
  90. package/dist/commands/chord.js +1 -1
  91. package/dist/commands/dashboard.js +5 -1
  92. package/dist/commands/human/prompts.js +3 -3
  93. package/dist/commands/human/queue.d.ts +17 -0
  94. package/dist/commands/human/queue.js +111 -4
  95. package/dist/commands/memory/__tests__/lint-schema.test.js +27 -4
  96. package/dist/commands/memory/lint.d.ts +5 -4
  97. package/dist/commands/memory/lint.js +23 -7
  98. package/dist/commands/memory/read.js +2 -2
  99. package/dist/commands/memory/shared.d.ts +7 -1
  100. package/dist/commands/memory/shared.js +18 -3
  101. package/dist/commands/memory/write.js +20 -7
  102. package/dist/commands/memory.js +4 -4
  103. package/dist/commands/node.d.ts +1 -1
  104. package/dist/commands/node.js +64 -65
  105. package/dist/commands/pkg.js +1 -2
  106. package/dist/commands/revive.js +11 -3
  107. package/dist/commands/sys/feedback.d.ts +1 -0
  108. package/dist/commands/sys/feedback.js +163 -0
  109. package/dist/commands/sys/sync.d.ts +1 -0
  110. package/dist/commands/sys/sync.js +187 -0
  111. package/dist/commands/sys.js +4 -2
  112. package/dist/commands/view-cycle.js +2 -2
  113. package/dist/commands/view-list.js +8 -8
  114. package/dist/commands/view-new.js +21 -17
  115. package/dist/commands/view-pick.js +1 -1
  116. package/dist/commands/view-run.js +35 -14
  117. package/dist/commands/view.js +8 -6
  118. package/dist/commands/workspace.d.ts +2 -0
  119. package/dist/commands/workspace.js +161 -0
  120. package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
  121. package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
  122. package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
  123. package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
  124. package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
  125. package/dist/core/__tests__/broker-snapshot-history.test.d.ts +1 -0
  126. package/dist/core/__tests__/broker-snapshot-history.test.js +105 -0
  127. package/dist/core/__tests__/child-death-wake.test.js +56 -111
  128. package/dist/core/__tests__/close.test.js +14 -20
  129. package/dist/core/__tests__/context-intro.test.js +19 -19
  130. package/dist/core/__tests__/daemon-boot.test.js +30 -23
  131. package/dist/core/__tests__/draw-style.test.js +1 -1
  132. package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
  133. package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
  134. package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -1
  135. package/dist/core/__tests__/fixtures/fake-engine.js +32 -2
  136. package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
  137. package/dist/core/__tests__/focuses.test.js +25 -13
  138. package/dist/core/__tests__/fork.test.js +22 -5
  139. package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
  140. package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
  141. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
  142. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
  143. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
  144. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
  145. package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
  146. package/dist/core/__tests__/full/cascade-close.test.js +22 -8
  147. package/dist/core/__tests__/full/detach-focus.test.js +66 -169
  148. package/dist/core/__tests__/helpers/harness.d.ts +6 -9
  149. package/dist/core/__tests__/helpers/harness.js +19 -36
  150. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
  151. package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
  152. package/dist/core/__tests__/human-stranded-deliver.test.d.ts +1 -0
  153. package/dist/core/__tests__/human-stranded-deliver.test.js +108 -0
  154. package/dist/core/__tests__/lifecycle.test.js +1 -19
  155. package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
  156. package/dist/core/__tests__/memory-resolver.test.js +1 -1
  157. package/dist/core/__tests__/on-read-dedup-resume.test.d.ts +1 -0
  158. package/dist/core/__tests__/on-read-dedup-resume.test.js +81 -0
  159. package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
  160. package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
  161. package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
  162. package/dist/core/__tests__/relaunch-root.test.js +114 -0
  163. package/dist/core/__tests__/reset.test.js +19 -79
  164. package/dist/core/__tests__/steer-note.test.js +9 -7
  165. package/dist/core/__tests__/tmux-surface.test.js +13 -7
  166. package/dist/core/bootstrap.d.ts +0 -3
  167. package/dist/core/bootstrap.js +1 -143
  168. package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
  169. package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
  170. package/dist/core/canvas/browse/app.js +131 -8
  171. package/dist/core/canvas/browse/model.d.ts +23 -5
  172. package/dist/core/canvas/browse/model.js +68 -13
  173. package/dist/core/canvas/browse/render.d.ts +7 -1
  174. package/dist/core/canvas/browse/render.js +178 -47
  175. package/dist/core/canvas/canvas.js +1 -1
  176. package/dist/core/canvas/focuses.d.ts +4 -4
  177. package/dist/core/canvas/focuses.js +7 -7
  178. package/dist/core/canvas/nav-model.d.ts +162 -0
  179. package/dist/core/canvas/nav-model.js +487 -0
  180. package/dist/core/canvas/paths.d.ts +7 -0
  181. package/dist/core/canvas/paths.js +9 -0
  182. package/dist/core/canvas/render.d.ts +68 -9
  183. package/dist/core/canvas/render.js +195 -61
  184. package/dist/core/canvas/types.d.ts +16 -23
  185. package/dist/core/config.js +1 -4
  186. package/dist/core/memory-resolver.js +43 -5
  187. package/dist/core/runtime/bearings.d.ts +1 -1
  188. package/dist/core/runtime/bearings.js +8 -8
  189. package/dist/core/runtime/branded-host.d.ts +16 -0
  190. package/dist/core/runtime/branded-host.js +127 -0
  191. package/dist/core/runtime/broker-protocol.d.ts +212 -6
  192. package/dist/core/runtime/broker-sdk.d.ts +0 -12
  193. package/dist/core/runtime/broker-sdk.js +77 -6
  194. package/dist/core/runtime/broker.d.ts +5 -1
  195. package/dist/core/runtime/broker.js +520 -59
  196. package/dist/core/runtime/close.d.ts +12 -2
  197. package/dist/core/runtime/close.js +37 -16
  198. package/dist/core/runtime/front-door.js +14 -7
  199. package/dist/core/runtime/host.d.ts +14 -34
  200. package/dist/core/runtime/host.js +14 -50
  201. package/dist/core/runtime/launch.d.ts +12 -9
  202. package/dist/core/runtime/launch.js +27 -15
  203. package/dist/core/runtime/lifecycle.d.ts +1 -1
  204. package/dist/core/runtime/lifecycle.js +15 -19
  205. package/dist/core/runtime/nodes.d.ts +0 -55
  206. package/dist/core/runtime/nodes.js +6 -74
  207. package/dist/core/runtime/placement.d.ts +91 -334
  208. package/dist/core/runtime/placement.js +262 -828
  209. package/dist/core/runtime/promote.d.ts +2 -0
  210. package/dist/core/runtime/promote.js +24 -6
  211. package/dist/core/runtime/recap.d.ts +8 -0
  212. package/dist/core/runtime/recap.js +107 -0
  213. package/dist/core/runtime/recycle.js +25 -61
  214. package/dist/core/runtime/reset.d.ts +43 -37
  215. package/dist/core/runtime/reset.js +131 -218
  216. package/dist/core/runtime/revive.d.ts +9 -29
  217. package/dist/core/runtime/revive.js +45 -135
  218. package/dist/core/runtime/spawn.d.ts +15 -11
  219. package/dist/core/runtime/spawn.js +168 -136
  220. package/dist/core/runtime/surface-bg.d.ts +11 -0
  221. package/dist/core/runtime/surface-bg.js +68 -0
  222. package/dist/core/runtime/tmux.d.ts +15 -43
  223. package/dist/core/runtime/tmux.js +38 -80
  224. package/dist/core/scope.d.ts +1 -2
  225. package/dist/core/scope.js +1 -5
  226. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
  227. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
  228. package/dist/core/skill-sync/builtins.d.ts +42 -0
  229. package/dist/core/skill-sync/builtins.js +112 -0
  230. package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
  231. package/dist/core/skill-sync/claude-plugins.js +71 -0
  232. package/dist/core/skill-sync/engine.d.ts +42 -0
  233. package/dist/core/skill-sync/engine.js +633 -0
  234. package/dist/core/skill-sync/export.d.ts +23 -0
  235. package/dist/core/skill-sync/export.js +86 -0
  236. package/dist/core/skill-sync/manifest.d.ts +64 -0
  237. package/dist/core/skill-sync/manifest.js +181 -0
  238. package/dist/core/skill-sync/profile.d.ts +76 -0
  239. package/dist/core/skill-sync/profile.js +173 -0
  240. package/dist/core/skill-sync/snapshot.d.ts +57 -0
  241. package/dist/core/skill-sync/snapshot.js +120 -0
  242. package/dist/core/spawn.d.ts +14 -0
  243. package/dist/core/spawn.js +29 -9
  244. package/dist/core/substrate/index.d.ts +2 -2
  245. package/dist/core/substrate/index.js +7 -7
  246. package/dist/core/substrate/injected-store.d.ts +10 -0
  247. package/dist/core/substrate/injected-store.js +55 -0
  248. package/dist/core/substrate/on-read.js +68 -6
  249. package/dist/core/substrate/render.d.ts +8 -11
  250. package/dist/core/substrate/render.js +29 -43
  251. package/dist/core/substrate/schema.d.ts +16 -11
  252. package/dist/core/substrate/schema.js +32 -31
  253. package/dist/core/tui/host.d.ts +12 -4
  254. package/dist/core/tui/host.js +280 -149
  255. package/dist/core/view/bridge.d.ts +10 -0
  256. package/dist/core/view/bridge.js +31 -0
  257. package/dist/core/view/chrome.d.ts +9 -0
  258. package/dist/core/view/chrome.js +22 -0
  259. package/dist/core/view/contract.d.ts +171 -0
  260. package/dist/core/view/contract.js +23 -0
  261. package/dist/core/view/loader.d.ts +31 -0
  262. package/dist/core/view/loader.js +188 -0
  263. package/dist/core/view/transport-local.d.ts +7 -0
  264. package/dist/core/view/transport-local.js +70 -0
  265. package/dist/core/view/transport.d.ts +4 -0
  266. package/dist/core/view/transport.js +15 -0
  267. package/dist/daemon/crtrd.d.ts +38 -27
  268. package/dist/daemon/crtrd.js +303 -376
  269. package/dist/daemon/manage.js +6 -1
  270. package/dist/index.js +1 -1
  271. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
  272. package/dist/pi-extensions/canvas-context-intro.js +4 -4
  273. package/dist/pi-extensions/canvas-doc-substrate.js +29 -20
  274. package/dist/pi-extensions/canvas-nav.js +35 -385
  275. package/dist/pi-extensions/canvas-recap.d.ts +37 -0
  276. package/dist/pi-extensions/canvas-recap.js +236 -0
  277. package/dist/pi-extensions/canvas-resume.js +2 -1
  278. package/dist/pi-extensions/canvas-stophook.d.ts +15 -13
  279. package/dist/pi-extensions/canvas-stophook.js +119 -103
  280. package/dist/pi-extensions/canvas-view.js +2 -1
  281. package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
  282. package/dist/pi-extensions/widget-order-bus.js +34 -0
  283. package/dist/prompts/view.d.ts +2 -2
  284. package/dist/prompts/view.js +145 -73
  285. package/dist/types.d.ts +1 -6
  286. package/dist/types.js +1 -3
  287. package/dist/web/ViewChrome.d.ts +7 -0
  288. package/dist/web/ViewChrome.js +28 -0
  289. package/dist/web/ViewPane.d.ts +39 -0
  290. package/dist/web/ViewPane.js +48 -0
  291. package/dist/web/index.d.ts +6 -0
  292. package/dist/web/index.js +16 -0
  293. package/dist/web/runtime.d.ts +39 -0
  294. package/dist/web/runtime.js +133 -0
  295. package/dist/web/states.d.ts +24 -0
  296. package/dist/web/states.js +24 -0
  297. package/dist/web/transport-http.d.ts +5 -0
  298. package/dist/web/transport-http.js +28 -0
  299. package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
  300. package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
  301. package/dist/web-client/index.html +13 -0
  302. package/package.json +20 -6
  303. package/dist/builtin-views/canvas/client.mjs +0 -303
  304. package/dist/builtin-views/canvas/view.mjs +0 -576
  305. package/dist/builtin-views/git-pr/client.mjs +0 -440
  306. package/dist/builtin-views/git-pr/view.mjs +0 -675
  307. package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
  308. package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
  309. package/dist/builtin-views/inbox/view.mjs +0 -889
  310. package/dist/builtin-views/linkedin/client.mjs +0 -610
  311. package/dist/builtin-views/linkedin/view.mjs +0 -1171
  312. package/dist/commands/pkg/bridge.d.ts +0 -1
  313. package/dist/commands/pkg/bridge.js +0 -137
  314. package/dist/commands/skill/author.d.ts +0 -3
  315. package/dist/commands/skill/author.js +0 -140
  316. package/dist/commands/skill/shared.d.ts +0 -3
  317. package/dist/commands/skill/shared.js +0 -19
  318. package/dist/commands/skill.d.ts +0 -2
  319. package/dist/commands/skill.js +0 -21
  320. package/dist/commands/tmux-spread.d.ts +0 -2
  321. package/dist/commands/tmux-spread.js +0 -144
  322. package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
  323. package/dist/core/__tests__/full/placement-focus.test.js +0 -309
  324. package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
  325. package/dist/core/__tests__/full/placement-revive.test.js +0 -238
  326. package/dist/core/__tests__/full/placement-teardown.test.js +0 -221
  327. package/dist/core/__tests__/home-session.test.js +0 -186
  328. package/dist/core/__tests__/relaunch.test.js +0 -335
  329. package/dist/core/bridge-map.d.ts +0 -19
  330. package/dist/core/bridge-map.js +0 -73
  331. package/dist/core/tui/contract.d.ts +0 -83
  332. package/dist/core/tui/contract.js +0 -8
  333. package/dist/core/tui/loader.d.ts +0 -16
  334. package/dist/core/tui/loader.js +0 -94
  335. package/dist/prompts/skill.d.ts +0 -2
  336. package/dist/prompts/skill.js +0 -650
  337. /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
  338. /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
  339. /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
  340. /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
  341. /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
  342. /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/reconnect-giveup.test.d.ts} +0 -0
  343. /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
@@ -20,12 +20,28 @@ export { detectColorCaps };
20
20
  // Fixed chrome heights, shared with app.ts so its viewport math never drifts
21
21
  // from what renderFrame actually draws.
22
22
  // header = title + tab bar + status line + separator (+ search input when searching)
23
- // preview = separator + meta line + PREVIEW_BODY prompt lines
24
- export const PREVIEW_BODY = 5;
23
+ // preview = separator + meta line + PROMPT_LINES (spawn/match) + REPLY_LINES (last reply)
24
+ export const PROMPT_LINES = 3;
25
+ export const REPLY_LINES = 3;
26
+ export const PREVIEW_BODY = PROMPT_LINES + REPLY_LINES;
25
27
  export const PREVIEW_HEIGHT = PREVIEW_BODY + 2;
28
+ // header = title + tab bar + status line + column header + separator (+ search input)
26
29
  export function headerHeight(search) {
27
- return 4 + (search ? 1 : 0);
30
+ return 5 + (search ? 1 : 0);
28
31
  }
32
+ // ── Row table layout ──────────────────────────────────────────────────────────
33
+ // A row is three zones: a fixed-width STATUS rail (glyph + word) on the left, the
34
+ // flexible tree-indented NAME in the middle, and a flush-right METADATA cluster
35
+ // (kind/mode · ctx · age · project) whose fixed-width segments align into clean
36
+ // vertical columns down the right edge. Wide terminals widen the name; narrow ones
37
+ // clip the name (and drop the cluster) first. The status WORD — not a lone glyph —
38
+ // is the legible, NO_COLOR-safe state signal.
39
+ const STATUS_W = 10; // glyph + ' ' + longest word ('active'/'cancel') + trailing gap
40
+ const KM_W = 22; // kind/mode column
41
+ const CTX_W = 6; // ctx tokens column
42
+ const AGE_W = 5; // age column
43
+ const PROJ_W = 16; // project (~basename) column, only across All dirs
44
+ const COL_GAP = ' ';
29
45
  // ── ANSI ────────────────────────────────────────────────────────────────────
30
46
  const ESC = '\x1b[';
31
47
  const RESET = `${ESC}0m`;
@@ -40,6 +56,7 @@ const FG_RED = '31';
40
56
  const FG_CYAN = '36';
41
57
  const FG_GRAY = '90'; // bright-black
42
58
  const FG_BRIGHT_YELLOW = '93';
59
+ const FG_BRIGHT_GREEN = '92'; // streaming pulse (brighter than the active-status green)
43
60
  const FG_BRIGHT_CYAN = '96'; // query-match highlight (ties to the cyan search accent)
44
61
  const STATUS_GLYPH = {
45
62
  active: '●',
@@ -165,8 +182,67 @@ function nameSpans(name, query, style) {
165
182
  flush();
166
183
  return out;
167
184
  }
168
- /** One row line: `<indent><collapse> <glyph> <name> [kind/mode] ctx Nk age [~dir] [⚑n]`.
169
- * `showCwd` adds the project-name cue (All-dirs view); `now` drives the age. */
185
+ /** Visible (cell) width of a span group ANSI-free, surrogate-safe. */
186
+ function spansWidth(spans) {
187
+ let n = 0;
188
+ for (const s of spans)
189
+ n += [...s.text].length;
190
+ return n;
191
+ }
192
+ /** The status rail: a status WORD next to its glyph, both in the status hue, padded
193
+ * to STATUS_W so the rail forms a clean left column. `live` (bright green) when the
194
+ * node is genuinely mid-turn; otherwise the lifecycle word. The word is what makes
195
+ * state legible at a glance and survives NO_COLOR (the glyph is the second cue). */
196
+ function statusRail(r) {
197
+ const streaming = r.streaming === true;
198
+ const word = streaming
199
+ ? 'live'
200
+ : ({ active: 'active', idle: 'idle', done: 'done', dead: 'dead', canceled: 'cancel' }[r.status] ?? r.status);
201
+ const fg = streaming ? FG_BRIGHT_GREEN : STATUS_COLOR[r.status];
202
+ const glyph = streaming ? '⟳' : (STATUS_GLYPH[r.status] ?? '?');
203
+ const text = `${glyph} ${word}`;
204
+ const pad = Math.max(1, STATUS_W - [...text].length);
205
+ return [
206
+ { text, style: { fg, bold: streaming } },
207
+ { text: ' '.repeat(pad) },
208
+ ];
209
+ }
210
+ /** The flush-right metadata cluster as fixed-width segments, so kind/mode · ctx ·
211
+ * age · project align into vertical columns down the right edge. `showCwd` adds the
212
+ * project column (only meaningful across All dirs). */
213
+ function metaCluster(r, now, showCwd) {
214
+ const ctxFg = ctxColorCode(r.ctx_tokens);
215
+ const out = [
216
+ { text: clipPad(`${r.kind}/${r.mode}`, KM_W), style: { fg: FG_GRAY } },
217
+ { text: COL_GAP },
218
+ { text: fmtCtx(r.ctx_tokens).padStart(CTX_W), style: { fg: ctxFg, dim: ctxFg === undefined } },
219
+ { text: COL_GAP },
220
+ { text: relAge(r.created, now).padStart(AGE_W), style: { dim: true } },
221
+ ];
222
+ if (showCwd) {
223
+ out.push({ text: COL_GAP }, { text: clipPad(`~${baseDir(r.cwd)}`, PROJ_W), style: { fg: FG_GRAY } });
224
+ }
225
+ return out;
226
+ }
227
+ /** Left-clip-pad: clip `s` to `w` cells then pad-end with spaces to exactly `w`. */
228
+ function clipPad(s, w) {
229
+ return clip(s, w).padEnd(w);
230
+ }
231
+ /** Compose a row from a flexible LEFT zone and a fixed flush-right RIGHT cluster:
232
+ * pad fills the gap so RIGHT hugs the edge; when the terminal is too narrow the
233
+ * cluster is dropped rather than colliding with the name. The caller has already
234
+ * clipped the name to fit. */
235
+ function assembleRow(left, right, width, color, lineBase, fill) {
236
+ const leftW = spansWidth(left);
237
+ const rightW = spansWidth(right);
238
+ if (right.length === 0 || leftW + rightW + 1 > width) {
239
+ return assemble(left, width, color, lineBase, fill);
240
+ }
241
+ const pad = width - leftW - rightW;
242
+ return assemble([...left, { text: ' '.repeat(pad) }, ...right], width, color, lineBase, fill);
243
+ }
244
+ /** One table row: `<status rail> <indent><collapse> <name> <flags>` ........ `<meta>`.
245
+ * `showCwd` adds the project column (All-dirs view); `now` drives the age. */
170
246
  function rowLine(row, tree, width, isCursor, query, caps, showCwd, now) {
171
247
  const node = tree.nodes.get(row.id);
172
248
  if (node === undefined)
@@ -174,30 +250,35 @@ function rowLine(row, tree, width, isCursor, query, caps, showCwd, now) {
174
250
  const r = node.row;
175
251
  const indent = ' '.repeat(row.depth);
176
252
  const collapse = !row.hasChildren ? ' ' : row.collapsed ? '▸' : '▾';
177
- const glyph = STATUS_GLYPH[r.status] ?? '?';
178
253
  const terminal = r.status === 'done' || r.status === 'dead' || r.status === 'canceled';
179
- const ctxStr = fmtCtx(r.ctx_tokens);
180
- const ctxFg = ctxColorCode(r.ctx_tokens);
181
- const age = relAge(r.created, now);
182
- // Name: bold on the cursor row; dim for terminal status (live names stay default).
183
- const nameStyle = { dim: !isCursor && terminal, bold: isCursor };
184
- const spans = [
185
- { text: `${indent}${collapse} ` },
186
- { text: glyph, style: { fg: STATUS_COLOR[r.status] } }, // load-bearing status hue
187
- { text: ' ' },
188
- ...nameSpans(r.name, query, nameStyle),
189
- { text: ` [${r.kind}/${r.mode}]`, style: { fg: FG_GRAY } }, // recedes
190
- { text: ' ctx ', style: { dim: true } },
191
- { text: ctxStr, style: { fg: ctxFg, dim: ctxFg === undefined } }, // tiered budget cue
192
- ];
193
- if (age !== '')
194
- spans.push({ text: ` ${age}`, style: { dim: true } }); // recency cue
195
- if (showCwd)
196
- spans.push({ text: ` ~${baseDir(r.cwd)}`, style: { fg: FG_GRAY } }); // project cue (All dirs)
254
+ // Left gutter (fixed slot): an eye when a viewer is ATTACHED — a scannable rail
255
+ // down the left edge of "what's on a screen right now". Blank otherwise.
256
+ const gutter = r.viewed === true
257
+ ? { text: '◉ ', style: { fg: FG_BRIGHT_CYAN, bold: true } }
258
+ : { text: ' ' };
259
+ // Inline attention flag that travels with the name (streaming lives in the rail,
260
+ // attached lives in the gutter).
261
+ const flags = [];
197
262
  if (r.asks > 0)
198
- spans.push({ text: ` ⚑${r.asks}`, style: { fg: FG_BRIGHT_YELLOW, bold: true } }); // attention
199
- // Row base: cursor → subtle bg (256) or reverse fallback (also covers !color);
200
- // non-matched ancestor → whole-row dim for tree context (keep prior behavior).
263
+ flags.push({ text: ` ⚑${r.asks}`, style: { fg: FG_BRIGHT_YELLOW, bold: true } }); // pending asks
264
+ const status = statusRail(r);
265
+ const right = metaCluster(r, now, showCwd);
266
+ const treeLead = `${indent}${collapse} `;
267
+ // Clip the NAME (only) so gutter + status + tree + name + flags + meta all fit;
268
+ // the cluster is dropped first (assembleRow) when even a 4-col name won't fit it.
269
+ const fixed = spansWidth([gutter]) + spansWidth(status) + [...treeLead].length + spansWidth(flags);
270
+ const rightW = spansWidth(right);
271
+ const nameMax = Math.max(4, width - fixed - rightW - 1);
272
+ const nameStyle = { dim: !isCursor && terminal, bold: isCursor }; // dim terminal names; bold the cursor row
273
+ const left = [
274
+ gutter,
275
+ ...status,
276
+ { text: treeLead, style: { dim: true } },
277
+ ...nameSpans(clip(r.name, nameMax), query, nameStyle),
278
+ ...flags,
279
+ ];
280
+ // Row base: cursor → subtle bg (256) or reverse fallback (also !color); non-matched
281
+ // ancestor → whole-row dim for tree context.
201
282
  let lineBase = '';
202
283
  let fill = false;
203
284
  if (isCursor) {
@@ -207,7 +288,26 @@ function rowLine(row, tree, width, isCursor, query, caps, showCwd, now) {
207
288
  else if (!row.matched) {
208
289
  lineBase = DIM;
209
290
  }
210
- return assemble(spans, width, caps.color, lineBase, fill);
291
+ return assembleRow(left, right, width, caps.color, lineBase, fill);
292
+ }
293
+ /** The column-header row — dim labels aligned to the same zones the rows use, so
294
+ * the metadata columns read as a table. */
295
+ function columnHeaderLine(width, showCwd, caps) {
296
+ const left = [
297
+ { text: ' ' }, // viewer gutter (no label)
298
+ { text: 'STATUS'.padEnd(STATUS_W), style: { dim: true, bold: true } },
299
+ { text: 'NAME', style: { dim: true, bold: true } },
300
+ ];
301
+ const right = [
302
+ { text: 'KIND/MODE'.padEnd(KM_W), style: { dim: true, bold: true } },
303
+ { text: COL_GAP },
304
+ { text: 'CTX'.padStart(CTX_W), style: { dim: true, bold: true } },
305
+ { text: COL_GAP },
306
+ { text: 'AGE'.padStart(AGE_W), style: { dim: true, bold: true } },
307
+ ];
308
+ if (showCwd)
309
+ right.push({ text: COL_GAP }, { text: 'PROJECT'.padEnd(PROJ_W), style: { dim: true, bold: true } });
310
+ return assembleRow(left, right, width, caps.color, '', false);
211
311
  }
212
312
  /** The status line (always present): active cwd scope · sort mode · committed
213
313
  * filter. The committed-filter indicator lives here now (not its own line) so a
@@ -248,11 +348,13 @@ function snippetLine(ln, width, caps) {
248
348
  return assemble(spans, width, caps.color, '', false);
249
349
  }
250
350
  /** The bottom preview panel — exactly PREVIEW_HEIGHT lines: a separator, a meta
251
- * line (status · kind/mode · project · age · ctx · asks), then the selected node's
252
- * prompt wrapped to PREVIEW_BODY lines. Under a live query the body is WINDOWED to
253
- * the matching prompt (anywhere in the conversation) with the match highlighted;
254
- * with no query it shows the spawn prompt from the start. The "which one was this?"
255
- * answer paired with super-search. Always full height so viewport math holds. */
351
+ * line (status · kind/mode · project · age · ctx · asks · streaming/viewing), then
352
+ * TWO blocks the spawn prompt / query-match (PROMPT_LINES) and the node's LAST
353
+ * assistant reply (REPLY_LINES, prefixed `↩`). Under a live query the prompt block
354
+ * is WINDOWED to the matching prompt (anywhere in the conversation) with the match
355
+ * highlighted; with no query it shows the spawn prompt from the start. Seeing both
356
+ * ends — what it was asked and where it left off — answers "which one was this?".
357
+ * Always full height so viewport math holds. */
256
358
  function previewPanel(r, width, caps, now, query) {
257
359
  const out = [`${DIM}${'─'.repeat(width)}${RESET}`];
258
360
  if (r === undefined) {
@@ -266,23 +368,43 @@ function previewPanel(r, width, caps, now, query) {
266
368
  const metaPieces = [`${r.status} ${r.kind}/${r.mode}`, baseDir(r.cwd), relAge(r.created, now), `ctx ${fmtCtx(r.ctx_tokens)}`];
267
369
  if (r.asks > 0)
268
370
  metaPieces.push(`⚑${r.asks}`);
371
+ if (r.streaming === true)
372
+ metaPieces.push('⟳ streaming');
373
+ if (r.viewed === true)
374
+ metaPieces.push('◉ viewing');
269
375
  const metaText = clip(metaPieces.filter((p) => p !== '').join(' · '), Math.max(0, width - 2));
270
376
  out.push(caps.color ? `${glyph} ${DIM}${metaText}${RESET}` : `${glyph} ${metaText}`);
271
- // With a query, window+highlight the matching prompt from the WHOLE conversation;
272
- // otherwise show the spawn prompt from the start.
377
+ // Block 1 — spawn prompt / query-match. With a query, window+highlight the
378
+ // matching prompt from the WHOLE conversation; otherwise the spawn prompt.
273
379
  const sourceText = query !== '' ? promptText(r) : (r.goal ?? '');
274
- const snippet = previewSnippet(query, sourceText, width, PREVIEW_BODY);
275
- if (snippet.length === 0) {
380
+ const promptSnippet = previewSnippet(query, sourceText, width, PROMPT_LINES);
381
+ if (promptSnippet.length === 0) {
276
382
  out.push(`${DIM}(no spawn prompt)${RESET}`);
277
- for (let i = 1; i < PREVIEW_BODY; i++)
383
+ for (let i = 1; i < PROMPT_LINES; i++)
278
384
  out.push('');
279
385
  }
280
386
  else {
281
- for (let i = 0; i < PREVIEW_BODY; i++) {
282
- const ln = snippet[i];
387
+ for (let i = 0; i < PROMPT_LINES; i++) {
388
+ const ln = promptSnippet[i];
283
389
  out.push(ln === undefined ? '' : snippetLine(ln, width, caps));
284
390
  }
285
391
  }
392
+ // Block 2 — the node's LAST assistant reply, prefixed `↩` on its first line so
393
+ // the two blocks read apart. Wrapped to width-2 to leave room for the marker.
394
+ const replyMark = caps.color ? `${DIM}↩ ${RESET}` : '↩ ';
395
+ const replySnippet = previewSnippet('', r.lastAssistant ?? '', Math.max(1, width - 2), REPLY_LINES);
396
+ if (replySnippet.length === 0) {
397
+ out.push(`${replyMark}${DIM}(no reply yet)${RESET}`);
398
+ for (let i = 1; i < REPLY_LINES; i++)
399
+ out.push('');
400
+ }
401
+ else {
402
+ for (let i = 0; i < REPLY_LINES; i++) {
403
+ const ln = replySnippet[i];
404
+ const prefix = i === 0 ? replyMark : ' ';
405
+ out.push(ln === undefined ? (i === 0 ? `${replyMark}` : '') : `${prefix}${snippetLine(ln, Math.max(1, width - 2), caps)}`);
406
+ }
407
+ }
286
408
  return out;
287
409
  }
288
410
  /**
@@ -315,11 +437,12 @@ export function renderFrame(state, size, caps = { color: false, color256: false
315
437
  const slash = caps.color ? `${ESC}${FG_CYAN}m/${RESET}` : '/';
316
438
  lines.push(`${slash} ${state.query}▎`);
317
439
  }
318
- // separator.
440
+ // column header (table labels) + separator.
441
+ const showCwd = state.cwdScope === null; // project column only matters across dirs
442
+ lines.push(columnHeaderLine(width, showCwd, caps));
319
443
  lines.push(`${DIM}${'─'.repeat(width)}${RESET}`);
320
444
  // body — windowed visible rows, leaving room for the (optional) preview panel.
321
445
  const head = lines.length; // === headerHeight(state.search)
322
- const showCwd = state.cwdScope === null; // project cue only matters across dirs
323
446
  const previewOn = state.preview && state.visible.length > 0;
324
447
  const previewH = previewOn ? PREVIEW_HEIGHT : 0;
325
448
  const viewport = Math.max(1, rows - head - 1 /* footer */ - previewH);
@@ -344,11 +467,19 @@ export function renderFrame(state, size, caps = { color: false, color256: false
344
467
  for (const l of previewPanel(r, width, caps, now, state.query))
345
468
  lines.push(l);
346
469
  }
347
- // footer.
348
- const footer = state.search
349
- ? '⏎ commit Esc cancel ⌫ delete'
350
- : '↑↓ move →/← tree ⏎ resume Tab tabs / search s sort c cwd r residents p preview q quit';
351
- lines.push(`${DIM}${clip(footer, width)}${RESET}`);
470
+ // footer — the y/n close-out confirm takes over when pending; else search / nav.
471
+ if (state.pendingClose !== null && state.pendingClose !== undefined) {
472
+ const node = state.tree.nodes.get(state.pendingClose);
473
+ const who = node !== undefined ? node.row.name : state.pendingClose;
474
+ const warn = `⚠ ${who} is actively streaming — close it (and its subtree) anyway? y / n`;
475
+ lines.push(caps.color ? `${BOLD}${ESC}${FG_BRIGHT_YELLOW}m${clip(warn, width)}${RESET}` : `${REVERSE}${clip(warn, width)}${RESET}`);
476
+ }
477
+ else {
478
+ const footer = state.search
479
+ ? '⏎ commit Esc cancel ⌫ delete'
480
+ : '↑↓ move →/← tree ⏎ resume x close Tab tabs / search s sort c cwd r residents p preview q quit';
481
+ lines.push(`${DIM}${clip(footer, width)}${RESET}`);
482
+ }
352
483
  // Assemble: home, each line cleared to EOL, then clear below.
353
484
  const body = lines.map((l) => `${l}${ESC}K`).join('\r\n');
354
485
  return `${ESC}H${body}${ESC}J`;
@@ -22,7 +22,7 @@ import { ensureHome, ensureNodeDirs, nodeMetaPath, nodeDir, nodesRoot, } from '.
22
22
  const IDENTITY_KEYS = [
23
23
  'node_id', 'name', 'description', 'cycles', 'created', 'cwd', 'host_kind', 'kind', 'mode',
24
24
  'lifecycle', 'persona_ack', 'parent', 'spawned_by', 'fork_from', 'passive_default',
25
- 'home_session', 'pi_session_id', 'pi_session_file', 'model_override', 'launch',
25
+ 'pi_session_id', 'pi_session_file', 'model_override', 'launch',
26
26
  ];
27
27
  /** Project any node object down to its durable-identity subset. */
28
28
  function toIdentity(m) {
@@ -4,10 +4,10 @@ import type { FocusRow } from './types.js';
4
4
  export declare function openFocusRow(focus_id: string, pane: string | null, session: string | null, node_id: string): void;
5
5
  /** Hot-swap a focus's occupant — single-statement UPDATE. Respects
6
6
  * UNIQUE(node_id): if `node_id` already occupies ANOTHER focus this throws
7
- * (correct — vacate-first is retargetFocus's job, Step 6, not this setter's). */
7
+ * (correct — vacate-first is placement.focus()'s job, not this setter's). */
8
8
  export declare function setFocusOccupant(focus_id: string, node_id: string): void;
9
- /** Re-point a focus's durable pane + its derived session cache for
10
- * reconcileFocus / the daemon (Step 6). Single-statement UPDATE. */
9
+ /** Re-point a viewer row's durable pane + its derived session cache after the
10
+ * pane moves. Single-statement UPDATE. */
11
11
  export declare function setFocusPane(focus_id: string, pane: string | null, session: string | null): void;
12
12
  /** DELETE a viewport. */
13
13
  export declare function closeFocusRow(focus_id: string): void;
@@ -16,7 +16,7 @@ export declare function getFocusByNode(node_id: string): FocusRow | null;
16
16
  /** The focus realized by a given pane (`%id`), or null. */
17
17
  export declare function getFocusByPane(pane: string): FocusRow | null;
18
18
  /** A focus by its stable id, or null. Used by placement to read a row back by id
19
- * (handFocusToManager / retargetFocus / registerRootFocus). */
19
+ * after openFocusRow registers a viewer. */
20
20
  export declare function getFocusById(focus_id: string): FocusRow | null;
21
21
  /** Every focus row, ordered by id. */
22
22
  export declare function listFocuses(): FocusRow[];
@@ -11,9 +11,9 @@
11
11
  // setPresence) — it never runs raw focus SQL itself.
12
12
  //
13
13
  // Each setter is a single atomic statement. UNIQUE(node_id) upholds "a node
14
- // occupies at most one focus" (Q5): a second focus row (or an occupant UPDATE)
15
- // for an already-focused node throws — that is correct, the Q5 vacate-first
16
- // orchestration is retargetFocus's job (Step 6), not these setters'.
14
+ // occupies at most one viewer" (Q5): a second focus row (or an occupant UPDATE)
15
+ // for an already-viewed node throws — that is correct, the vacate-first
16
+ // orchestration is placement.focus()'s job, not these setters'.
17
17
  import { openDb } from './db.js';
18
18
  function focusFrom(r) {
19
19
  return {
@@ -35,12 +35,12 @@ export function openFocusRow(focus_id, pane, session, node_id) {
35
35
  }
36
36
  /** Hot-swap a focus's occupant — single-statement UPDATE. Respects
37
37
  * UNIQUE(node_id): if `node_id` already occupies ANOTHER focus this throws
38
- * (correct — vacate-first is retargetFocus's job, Step 6, not this setter's). */
38
+ * (correct — vacate-first is placement.focus()'s job, not this setter's). */
39
39
  export function setFocusOccupant(focus_id, node_id) {
40
40
  openDb().prepare('UPDATE focuses SET node_id = ? WHERE focus_id = ?').run(node_id, focus_id);
41
41
  }
42
- /** Re-point a focus's durable pane + its derived session cache for
43
- * reconcileFocus / the daemon (Step 6). Single-statement UPDATE. */
42
+ /** Re-point a viewer row's durable pane + its derived session cache after the
43
+ * pane moves. Single-statement UPDATE. */
44
44
  export function setFocusPane(focus_id, pane, session) {
45
45
  openDb()
46
46
  .prepare('UPDATE focuses SET pane = ?, session = ? WHERE focus_id = ?')
@@ -68,7 +68,7 @@ export function getFocusByPane(pane) {
68
68
  return r ? focusFrom(r) : null;
69
69
  }
70
70
  /** A focus by its stable id, or null. Used by placement to read a row back by id
71
- * (handFocusToManager / retargetFocus / registerRootFocus). */
71
+ * after openFocusRow registers a viewer. */
72
72
  export function getFocusById(focus_id) {
73
73
  const r = openDb()
74
74
  .prepare('SELECT * FROM focuses WHERE focus_id = ?')
@@ -0,0 +1,162 @@
1
+ import type { NodeMeta, SubscriptionRef } from './index.js';
2
+ /** pi's InteractiveMode.MAX_WIDGET_LINES — the hard cap on lines in a string
3
+ * array widget; anything beyond it pi truncates with its own "... (widget
4
+ * truncated)". Our GRAPH viewport stays at/under this and scrolls internally. */
5
+ export declare const PI_MAX_WIDGET_LINES = 10;
6
+ export declare const VIEWPORT_FALLBACK_ROWS = 30;
7
+ export declare const ESC = "\u001B[";
8
+ export declare const RESET = "\u001B[0m";
9
+ export declare const BOLD = "\u001B[1m";
10
+ export declare const DIM = "\u001B[2m";
11
+ export declare const REVERSE = "\u001B[7m";
12
+ /** Dark-green background bar marking an ATTACHED node (a human is currently
13
+ * watching it) — distinct from the cursor's reverse-video bar; chosen so
14
+ * default-fg text stays readable. */
15
+ export declare const BG_ATTACHED = "\u001B[48;5;22m";
16
+ export declare const GREEN = "\u001B[32m";
17
+ export declare const RED = "\u001B[31m";
18
+ export declare const YELLOW = "\u001B[33m";
19
+ export declare const CYAN = "\u001B[36m";
20
+ export declare const GRAY = "\u001B[90m";
21
+ /** Status glyph colored by state: active green, idle dim, done cyan, dead red. */
22
+ export declare function coloredGlyph(node: NodeMeta | null): string;
23
+ /** Visible width, ignoring ANSI escapes. */
24
+ export declare function visibleWidth(s: string): number;
25
+ /** Truncate to `max` VISIBLE columns: escape sequences are copied through
26
+ * verbatim (so a cut never lands mid-escape) and the result always ends in
27
+ * RESET, so a clipped style can't bleed into the editor below. */
28
+ export declare function truncate(s: string, max?: number): string;
29
+ /** Visible columns available to ONE widget line — the cap for every line we
30
+ * emit, and the width a full-width reverse-video SELF bar fills to.
31
+ *
32
+ * pi does NOT clip widget lines; it WRAPS them. Each string line is wrapped in
33
+ * a `Text(paddingX = 1)` inside a full-terminal-width container, so the usable
34
+ * content width is `columns - 2` (a 1-col margin on each side). A line wider
35
+ * than that wraps, and the overflow spills onto a second row as a stray
36
+ * reverse-video block (the bug this guards against). Clamp to `columns - 2`. */
37
+ export declare function fillWidth(): number;
38
+ /** Wrap `content` in a full-width background bar opened by `open` (REVERSE for
39
+ * the cursor, BG_ATTACHED for a human-watched node). `open` is re-asserted after every
40
+ * embedded RESET so a coloured cell (the status dot) can't punch a hole in the
41
+ * bar; the visible width is padded out to `width`; the line closes with a real
42
+ * RESET so the style never bleeds into the editor below. */
43
+ export declare function fillBar(content: string, width: number, open: string): string;
44
+ export declare function beginFrame(): void;
45
+ /** getNode memoized for the current frame (meta.json read + sqlite row). */
46
+ export declare function cNode(id: string): NodeMeta | null;
47
+ /** subscriptionsOf (a node's reports) memoized for the current frame. */
48
+ export declare function cSubscriptions(id: string): SubscriptionRef[];
49
+ /** subscribersOf (a node's managers) memoized for the current frame. */
50
+ export declare function cSubscribers(id: string): SubscriptionRef[];
51
+ export interface Telemetry {
52
+ /** Live context-window size (pi's getContextUsage) — the figure pi's own footer
53
+ * shows. This is the node's window fill, NOT a per-turn delta. */
54
+ context_tokens?: number;
55
+ /** One-line "what is it doing" cue (`tool: detail`), written on every tool start. */
56
+ last_activity?: string;
57
+ }
58
+ export declare function readTelemetry(nodeId: string): Telemetry;
59
+ export declare function fmtTokens(n: number): string;
60
+ /** The context-window cell — live window fill rounded to the nearest 1k (the same
61
+ * figure pi's footer shows), NOT a per-turn token delta. */
62
+ export declare function tokensCell(id: string): string;
63
+ /** Dimmed live "what is it doing" cue for an ACTIVE node — the last tool it ran.
64
+ * Empty for non-active rows (stale once a node parks) and when none is recorded. */
65
+ export declare function activityCell(id: string, node: NodeMeta | null): string;
66
+ /** Revive-count badge (meta.cycles). Hidden on the first cycle (0) to cut noise. */
67
+ export declare function cycleBadge(node: NodeMeta | null): string;
68
+ /** Short on-screen label: the explicit handle when set, else the pi-generated
69
+ * description, else the bare name. fullName joins BOTH (`handle description`);
70
+ * the nav chrome shows just the first so rows stay compact. */
71
+ export declare function navLabel(node: NodeMeta | null, id: string): string;
72
+ export declare function shortId(id: string): string;
73
+ /** Node ids currently shown in a tmux focus viewport. Built once per render. */
74
+ export declare function focusedNodeIds(): Set<string>;
75
+ /** True when a human is watching `id` right now (tmux focus or broker viewer). */
76
+ export declare function isAttached(id: string, node: NodeMeta | null, focused: ReadonlySet<string>): boolean;
77
+ export declare function fetchAsksMap(rootId: string): Record<string, number>;
78
+ /** First manager (by created) — the UP step for the ancestry spine. */
79
+ export declare function managerOf(id: string): string | undefined;
80
+ /** A kind:'human' node is a control-plane ASK (a humanloop deck on the human's
81
+ * screen), NOT a pi conversation — it has no session, so focusing/reviving it
82
+ * boots a confused blank "you have been revived" pi. Its pending-ask signal
83
+ * already rides the ⚑ badge on the ASKING node (attention.ts attributes asks by
84
+ * source.nodeId, never to the human node), so the row carries no signal of its
85
+ * own. Drop it from every navigable list (the tree, BASE reports, child counts,
86
+ * subtree expansion) so it can never be selected. */
87
+ export declare function isHumanAsk(id: string): boolean;
88
+ /** A node's direct children that are navigable conversations — human-ask nodes
89
+ * dropped. The one place the nav chrome enumerates children. */
90
+ export declare function convoChildIds(id: string): string[];
91
+ /** Live reports (active|idle) of a node — the DOWN set in BASE. */
92
+ export declare function liveReports(id: string): string[];
93
+ /** Direct navigable children — used for the ⤳ badge and fold counts (human-ask
94
+ * nodes excluded, so the count matches what the tree actually shows). */
95
+ export declare function childCount(id: string): number;
96
+ /** Climb first-manager edges from `self` to the ancestry root (cycle-guarded). */
97
+ export declare function climbRoot(self: string): string;
98
+ /** Space-joined ids of a node's subtree (cursor-relative {subtree} var). */
99
+ export declare function subtreeIds(root: string): string[];
100
+ /** ⤳M direct-children badge — only on orchestrator rows. */
101
+ export declare function childBadge(node: NodeMeta | null): string;
102
+ /** ⚑K pending-asks badge for a node, read from the supplied per-node ask map. */
103
+ export declare function askBadge(id: string, asks: Record<string, number>): string;
104
+ /** "Live work below" badge — green ⇣N when this node is NOT itself active but has
105
+ * N active descendants. The one signal that an idle/parked node isn't dead: real
106
+ * work is still running beneath it. Count comes from the single subtree-activity
107
+ * pass (no per-row walk). Suppressed on active rows (their own ● already says so). */
108
+ export declare function liveBelowBadge(node: NodeMeta | null, activeBelow: ReadonlyMap<string, number>): string;
109
+ /** Sort rank for sibling ordering — live nodes (active, then idle) ahead of
110
+ * terminal ones, so sessions still running surface at the TOP of each child
111
+ * group instead of being buried under finished/failed ones. */
112
+ export declare function statusRank(id: string): number;
113
+ /** Direct children, live-first — the sibling order used both when flattening
114
+ * the tree and when stepping into a subtree (`l`). Array.sort is stable, so
115
+ * equal-status siblings keep their creation order. */
116
+ export declare function sortedChildIds(id: string): string[];
117
+ export interface FlatRow {
118
+ id: string;
119
+ hasKids: boolean;
120
+ isSelf: boolean;
121
+ branch: string;
122
+ cycle: boolean;
123
+ collapsed: boolean;
124
+ }
125
+ /** Manual fold OVERRIDES, keyed by id. `userCollapsed` forces a fold, `userExpanded`
126
+ * forces an unfold; both override the default activity-driven policy. Held by the
127
+ * consumer (the extension / viewer) and threaded into buildGraphModel so this layer
128
+ * carries no mutable singletons. */
129
+ export interface FoldState {
130
+ userExpanded: ReadonlySet<string>;
131
+ userCollapsed: ReadonlySet<string>;
132
+ }
133
+ /** One bottom-up O(N) pass over the local graph, computing TWO things at once so
134
+ * the render path never walks twice:
135
+ * - `expand`: which nodes auto-UNFOLD — a node expands when a child subtree holds
136
+ * a running ('active') agent or self, so the path to any live agent (and to
137
+ * you) is revealed while quiescent branches stay folded.
138
+ * - `activeBelow`: per node, the count of ACTIVE descendants (excluding itself) —
139
+ * drives the ⇣N "live work below" badge on idle/parked nodes.
140
+ * Cycle-guarded (the graph is declared acyclic; a re-seen id contributes only
141
+ * its own status, never its subtree again, so counts can't double or loop). */
142
+ export interface SubtreeActivity {
143
+ expand: Set<string>;
144
+ activeBelow: Map<string, number>;
145
+ }
146
+ export declare function computeSubtreeActivity(root: string, self: string): SubtreeActivity;
147
+ /** Fold policy alone — thin wrapper over computeSubtreeActivity for the keypress
148
+ * path (buildGraphModel without a precomputed set). The render path computes the
149
+ * activity once and threads `expand` in, so it never recomputes here. */
150
+ export declare function computeDefaultExpanded(root: string, self: string): Set<string>;
151
+ export declare function buildGraphModel(self: string, folds: FoldState, expand?: ReadonlySet<string>): FlatRow[];
152
+ /** Render one GRAPH row. CURSOR (selected) → reverse-video bar; an ATTACHED
153
+ * (human-watched) node → a coloured background bar; SELF → bold name. The
154
+ * cursor outranks the attached tint when both land on the same row. Running
155
+ * is signaled by the dot glyph alone (● green = active engine). */
156
+ export declare function renderGraphRow(r: FlatRow, isCursor: boolean, focused: ReadonlySet<string>, activeBelow: ReadonlyMap<string, number>, asks: Record<string, number>): string;
157
+ /** Total lines the GRAPH widget may emit. pi hard-caps extension widgets at
158
+ * MAX_WIDGET_LINES — anything past that pi truncates itself, eating our own
159
+ * scroll chrome — so never exceed it (and shrink on a very short terminal).
160
+ * The viewport scrolls WITHIN this cap as the cursor moves. */
161
+ export declare function graphWidgetBudget(): number;
162
+ export declare const GRAPH_HINT = "\u001B[2mjk move \u00B7 hl fold \u00B7 \u21B5 focus \u00B7 e expand \u00B7 x kill \u00B7 m mgr \u00B7 esc\u001B[0m";