@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
@@ -0,0 +1,171 @@
1
+ import type { Draw, Rect } from '../tui/draw.js';
2
+ export interface ViewManifest {
3
+ id: string;
4
+ title: string;
5
+ subtitle?: string;
6
+ description: string;
7
+ refreshMs?: number;
8
+ }
9
+ /** Severity of a {@link HostSignals.setBanner} banner — drives glyph + hue and
10
+ * the derived state chip (error→blocked, action→attention, info→neutral). */
11
+ export type BannerLevel = 'info' | 'action' | 'error';
12
+ export interface Banner {
13
+ msg: string;
14
+ level: BannerLevel;
15
+ }
16
+ /** Optional host context passed to the text presenter's dump on the piped path. */
17
+ export interface DumpContext {
18
+ banner: Banner | null;
19
+ }
20
+ /** Semantic chrome signals the core raises; each target renders them in its own
21
+ * idiom (TUI footer/banner/title-chip vs web status line/alert bar/pill). */
22
+ export interface HostSignals {
23
+ setStatus(msg: string | null): void;
24
+ setBanner(msg: string, level: BannerLevel): void;
25
+ clearBanner(): void;
26
+ setSubtitle(s: string | null): void;
27
+ /** Interaction-mode chip override (compose/react); null returns to derived. */
28
+ setMode(mode: string | null): void;
29
+ /** TUI: leave the pane; web: no-op/close tab. */
30
+ quit(): void;
31
+ }
32
+ /** The host-tracked chrome record both targets render. */
33
+ export interface ChromeState {
34
+ status: string | null;
35
+ banner: Banner | null;
36
+ subtitle: string | null;
37
+ mode: string | null;
38
+ busy: boolean;
39
+ loaded: boolean;
40
+ lastRefresh: number;
41
+ }
42
+ /** A transport-agnostic request descriptor the host fulfills. The core never
43
+ * executes anything itself — it describes WHAT to run/read/fetch and the
44
+ * host's Transport decides HOW (local exec today, a cloud endpoint later). */
45
+ export type SourceRequest = {
46
+ kind: 'exec';
47
+ bin: string;
48
+ args: string[];
49
+ cwd?: string;
50
+ stdin?: string;
51
+ } | {
52
+ kind: 'file';
53
+ path: string;
54
+ } | {
55
+ kind: 'http';
56
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE';
57
+ url: string;
58
+ headers?: Record<string, string>;
59
+ body?: string;
60
+ };
61
+ export interface RawResponse {
62
+ ok: boolean;
63
+ exitCode?: number;
64
+ status?: number;
65
+ stdout: string;
66
+ stderr: string;
67
+ }
68
+ /** Typed error with a render-ready display. Presenters render `display`
69
+ * VERBATIM and never branch on `kind` (the view-internal taxonomy). */
70
+ export interface SourceError {
71
+ kind: string;
72
+ display: {
73
+ headline: string;
74
+ explanation: string;
75
+ nextStep: string;
76
+ level: BannerLevel;
77
+ blocking: boolean;
78
+ };
79
+ }
80
+ export type Result<T> = {
81
+ ok: true;
82
+ data: T;
83
+ } | {
84
+ ok: false;
85
+ error: SourceError;
86
+ };
87
+ /** A READ: declarative descriptor + pure parse + optional cadence. Resolved by
88
+ * the host through its Transport via `ctx.resolve(source, args)`. */
89
+ export interface Source<T, A = void> {
90
+ id: string;
91
+ request(args: A): SourceRequest;
92
+ parse(raw: RawResponse): Result<T>;
93
+ refreshMs?: number;
94
+ }
95
+ /** A WRITE: same {request, parse} pair, no cadence — invoked by an intent via
96
+ * `ctx.execute(command, args)`. */
97
+ export interface Command<T, A = void> {
98
+ id: string;
99
+ request(args: A): SourceRequest;
100
+ parse(raw: RawResponse): Result<T>;
101
+ }
102
+ /** A semantic action. State updates are immutable via `ctx.set`; async effects
103
+ * (transport calls) live in the same handler — a thunk, not a pure reducer.
104
+ * Sync intents call `ctx.set` once; async intents `await ctx.resolve/execute`
105
+ * between `set`s. The host serializes async intents in its single-flight lane. */
106
+ export type Intent<S, P = void> = (ctx: IntentCtx<S>, payload: P) => void | Promise<void>;
107
+ export interface IntentCtx<S> {
108
+ /** Snapshot of state at read time. */
109
+ readonly state: S;
110
+ /** Immutable update → triggers a re-render. Value or (prev)=>next fn. */
111
+ set(next: S | ((prev: S) => S)): void;
112
+ /** Run a READ source through the host's transport → typed Result. */
113
+ resolve<T, A>(source: Source<T, A>, args?: A): Promise<Result<T>>;
114
+ /** Run a WRITE command through the host's transport → typed Result. */
115
+ execute<T, A>(command: Command<T, A>, args?: A): Promise<Result<T>>;
116
+ /** Semantic chrome signals (status/banner/subtitle/mode/quit). */
117
+ signal: HostSignals;
118
+ /** Chain another intent by name. */
119
+ dispatch(intent: string, payload?: unknown): Promise<void>;
120
+ }
121
+ export interface ViewCore<S = unknown> {
122
+ manifest: ViewManifest;
123
+ /** Cheap, synchronous initial state. No fetch, no screen. The host mounts,
124
+ * paints a loading frame, then dispatches the first 'refresh'. */
125
+ init(opts: Readonly<Record<string, string>>): S;
126
+ /** Declarative READ dependencies the host resolves through the transport. */
127
+ sources?: Record<string, Source<any, any>>;
128
+ /** WRITE descriptors invoked from intents. */
129
+ commands?: Record<string, Command<any, any>>;
130
+ /** Semantic actions. Both presenters emit these. */
131
+ intents: Record<string, Intent<S, any>>;
132
+ }
133
+ export interface KeyHint {
134
+ keys: string;
135
+ label: string;
136
+ }
137
+ export type KeyBinding<S> = {
138
+ keys: string[];
139
+ intent: string;
140
+ payload?: (state: S) => unknown;
141
+ when?: (state: S) => boolean;
142
+ hint?: KeyHint;
143
+ } | {
144
+ /** Text-capture binding: while `when(state)` is true the host runs a
145
+ * built-in line-edit buffer over raw printable/backspace keys and
146
+ * dispatches `capture` with the next draft value on each edit. */
147
+ capture: string;
148
+ when: (state: S) => boolean;
149
+ hint?: KeyHint;
150
+ };
151
+ export interface TuiPresenter<S = unknown> {
152
+ /** Pure read of state, paints via draw.*; never ANSI. */
153
+ render(state: S, draw: Draw, content: Rect): void;
154
+ /** Pure input→intent mapping. */
155
+ keymap: KeyBinding<S>[];
156
+ }
157
+ /** Props handed to web.jsx's default-export React component. The component is a
158
+ * pure function of state; DOM events call `dispatch`. (Typed loosely here so
159
+ * the core contract carries no React type dependency.) */
160
+ export interface ViewProps<S = unknown> {
161
+ state: S;
162
+ dispatch: (intent: string, payload?: unknown) => void;
163
+ chrome: ChromeState;
164
+ }
165
+ export type WebPresenter<S = unknown> = (props: ViewProps<S>) => unknown;
166
+ export interface TextPresenter<S = unknown> {
167
+ /** Static text for the non-TTY / piped path. Snapshot of current state. */
168
+ dump(state: S, ctx?: DumpContext): string;
169
+ }
170
+ export declare function ok<T>(data: T): Result<T>;
171
+ export declare function fail<T = never>(error: SourceError): Result<T>;
@@ -0,0 +1,23 @@
1
+ // contract.ts — the dual-target view contract (portable core + thin presenters).
2
+ //
3
+ // One view definition renders to two targets — the tmux TUI (`crtr view run`)
4
+ // and a React+Tailwind web page (`crtr view serve`) — from ONE portable core.
5
+ // On disk a view is a directory of up to four files, each loaded only by the
6
+ // runtime that can execute it:
7
+ //
8
+ // <view>/
9
+ // core.mjs REQUIRED manifest · init · sources · commands · intents.
10
+ // Runs in BOTH Node and the browser. Imports NOTHING —
11
+ // no `node:*`, no crtr. Pure JS + the injected ctx.
12
+ // tui.mjs optional render(state, draw, content) + keymap (Node only)
13
+ // web.jsx optional default React component (Tailwind) (browser only)
14
+ // text.mjs optional dump(state, ctx) for the piped path (Node only)
15
+ //
16
+ // The invariant: ALL state + ALL behavior live in the core; presenters are pure
17
+ // reads of state that emit named intents. It is enforced structurally — only
18
+ // `core.mjs` ever sees a mutating `IntentCtx`; presenters receive `state` plus a
19
+ // `dispatch`/`keymap` that names intents, so a presenter has no path to data or
20
+ // logic.
21
+ // ── Result helpers (re-exported for view authors' convenience in tests) ─────
22
+ export function ok(data) { return { ok: true, data }; }
23
+ export function fail(error) { return { ok: false, error }; }
@@ -0,0 +1,31 @@
1
+ import type { ViewCore, TuiPresenter, TextPresenter } from './contract.js';
2
+ export interface ResolvedView {
3
+ id: string;
4
+ dir: string;
5
+ scope: 'project' | 'user' | 'builtin';
6
+ core: string;
7
+ tui: string | null;
8
+ web: string | null;
9
+ text: string | null;
10
+ }
11
+ /** Scope search project→user→builtin; first hit wins. null if no scope holds a
12
+ * `<name>/core.mjs`. */
13
+ export declare function resolveView(name: string): ResolvedView | null;
14
+ /** Find a directory named `name` across scopes even when it holds no core.mjs —
15
+ * lets a caller distinguish "no such view" from "a dir that isn't a valid view"
16
+ * (e.g. a pre-contract `view.mjs`-only dir) and name the contract in the error. */
17
+ export declare function findViewDir(name: string): string | null;
18
+ /** Enumerate every resolvable core-shaped view across scopes (first scope wins
19
+ * per id). Targets per view are derivable from the tui/web/text fields. */
20
+ export declare function listViews(): ResolvedView[];
21
+ /** Import + validate the portable core. Throws a guided error if malformed. */
22
+ export declare function loadCore(r: ResolvedView): Promise<ViewCore>;
23
+ /** Import + validate the TUI presenter. Call requireTui first. */
24
+ export declare function loadTui(r: ResolvedView): Promise<TuiPresenter>;
25
+ /** Import the text presenter if present; null otherwise (the host synthesizes a
26
+ * generic one-line dump when a view ships no text.mjs). */
27
+ export declare function loadText(r: ResolvedView): Promise<TextPresenter | null>;
28
+ /** Per-host presenter validation: `view run` requires a TUI presenter. */
29
+ export declare function requireTui(r: ResolvedView): void;
30
+ /** Per-host presenter validation: `view serve` requires a web presenter. */
31
+ export declare function requireWeb(r: ResolvedView): void;
@@ -0,0 +1,188 @@
1
+ // loader.ts — resolve + load dual-target (core.mjs) views across scopes.
2
+ //
3
+ // A view = a directory containing `core.mjs` (the portable core). Up to three
4
+ // presenter files sit beside it: `tui.mjs` (Node), `web.jsx`/`web.tsx` (browser,
5
+ // via Vite — NEVER Node-imported), `text.mjs` (Node, piped path). Targets are
6
+ // derived from which presenter files exist — no manifest field.
7
+ //
8
+ // Resolution order: project → user → builtin, first hit wins (scope.ts
9
+ // viewsDir). Node-side loads are plain `import(pathToFileURL())` — no
10
+ // transpile, no bundle. A directory without a `core.mjs` is not a view.
11
+ import { existsSync, readdirSync, statSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { pathToFileURL } from 'node:url';
14
+ import { viewsDir } from '../scope.js';
15
+ import { InputError } from '../io.js';
16
+ const SCOPE_ORDER = ['project', 'user', 'builtin'];
17
+ function probe(dir, scope, id) {
18
+ const core = join(dir, 'core.mjs');
19
+ if (!existsSync(core))
20
+ return null;
21
+ const tui = join(dir, 'tui.mjs');
22
+ const webJsx = join(dir, 'web.jsx');
23
+ const webTsx = join(dir, 'web.tsx');
24
+ const text = join(dir, 'text.mjs');
25
+ return {
26
+ id, dir, scope, core,
27
+ tui: existsSync(tui) ? tui : null,
28
+ web: existsSync(webJsx) ? webJsx : existsSync(webTsx) ? webTsx : null,
29
+ text: existsSync(text) ? text : null,
30
+ };
31
+ }
32
+ /** Scope search project→user→builtin; first hit wins. null if no scope holds a
33
+ * `<name>/core.mjs`. */
34
+ export function resolveView(name) {
35
+ for (const scope of SCOPE_ORDER) {
36
+ const root = viewsDir(scope);
37
+ if (!root)
38
+ continue;
39
+ const r = probe(join(root, name), scope, name);
40
+ if (r)
41
+ return r;
42
+ }
43
+ return null;
44
+ }
45
+ /** Find a directory named `name` across scopes even when it holds no core.mjs —
46
+ * lets a caller distinguish "no such view" from "a dir that isn't a valid view"
47
+ * (e.g. a pre-contract `view.mjs`-only dir) and name the contract in the error. */
48
+ export function findViewDir(name) {
49
+ for (const scope of SCOPE_ORDER) {
50
+ const root = viewsDir(scope);
51
+ if (!root)
52
+ continue;
53
+ const dir = join(root, name);
54
+ try {
55
+ if (statSync(dir).isDirectory())
56
+ return dir;
57
+ }
58
+ catch { /* not here */ }
59
+ }
60
+ return null;
61
+ }
62
+ /** Enumerate every resolvable core-shaped view across scopes (first scope wins
63
+ * per id). Targets per view are derivable from the tui/web/text fields. */
64
+ export function listViews() {
65
+ const seen = new Set();
66
+ const out = [];
67
+ for (const scope of SCOPE_ORDER) {
68
+ const root = viewsDir(scope);
69
+ if (!root || !existsSync(root))
70
+ continue;
71
+ let names;
72
+ try {
73
+ names = readdirSync(root);
74
+ }
75
+ catch {
76
+ continue;
77
+ }
78
+ for (const name of names.sort()) {
79
+ if (seen.has(name))
80
+ continue; // first scope wins
81
+ const dir = join(root, name);
82
+ let isDir = false;
83
+ try {
84
+ isDir = statSync(dir).isDirectory();
85
+ }
86
+ catch {
87
+ isDir = false;
88
+ }
89
+ if (!isDir)
90
+ continue;
91
+ const r = probe(dir, scope, name);
92
+ if (!r)
93
+ continue;
94
+ seen.add(name);
95
+ out.push(r);
96
+ }
97
+ }
98
+ return out;
99
+ }
100
+ /** Import + validate the portable core. Throws a guided error if malformed. */
101
+ export async function loadCore(r) {
102
+ let mod;
103
+ try {
104
+ mod = (await import(pathToFileURL(r.core).href));
105
+ }
106
+ catch (e) {
107
+ throw guided(r, 'core.mjs', `failed to import: ${errText(e)}`);
108
+ }
109
+ const c = mod['default'];
110
+ if (!c || typeof c !== 'object')
111
+ throw guided(r, 'core.mjs', 'no default export that is a ViewCore object');
112
+ const m = c.manifest;
113
+ if (!m || typeof m.id !== 'string' || typeof m.title !== 'string') {
114
+ throw guided(r, 'core.mjs', 'manifest.id and manifest.title must be strings');
115
+ }
116
+ if (typeof c.init !== 'function')
117
+ throw guided(r, 'core.mjs', 'init() is required');
118
+ if (!c.intents || typeof c.intents !== 'object')
119
+ throw guided(r, 'core.mjs', 'intents must be an object of intent functions');
120
+ return c;
121
+ }
122
+ /** Import + validate the TUI presenter. Call requireTui first. */
123
+ export async function loadTui(r) {
124
+ if (!r.tui)
125
+ throw notTarget(r, 'tui');
126
+ let mod;
127
+ try {
128
+ mod = (await import(pathToFileURL(r.tui).href));
129
+ }
130
+ catch (e) {
131
+ throw guided(r, 'tui.mjs', `failed to import: ${errText(e)}`);
132
+ }
133
+ // Accept named exports { render, keymap } or a default-exported object.
134
+ const candidate = (typeof mod['render'] === 'function' ? mod : mod['default']);
135
+ if (!candidate || typeof candidate.render !== 'function')
136
+ throw guided(r, 'tui.mjs', 'render(state, draw, content) is required');
137
+ if (!Array.isArray(candidate.keymap))
138
+ throw guided(r, 'tui.mjs', 'keymap must be an array of key bindings');
139
+ return candidate;
140
+ }
141
+ /** Import the text presenter if present; null otherwise (the host synthesizes a
142
+ * generic one-line dump when a view ships no text.mjs). */
143
+ export async function loadText(r) {
144
+ if (!r.text)
145
+ return null;
146
+ let mod;
147
+ try {
148
+ mod = (await import(pathToFileURL(r.text).href));
149
+ }
150
+ catch (e) {
151
+ throw guided(r, 'text.mjs', `failed to import: ${errText(e)}`);
152
+ }
153
+ const candidate = (typeof mod['dump'] === 'function' ? mod : mod['default']);
154
+ if (!candidate || typeof candidate.dump !== 'function')
155
+ throw guided(r, 'text.mjs', 'dump(state, ctx) is required');
156
+ return candidate;
157
+ }
158
+ /** Per-host presenter validation: `view run` requires a TUI presenter. */
159
+ export function requireTui(r) {
160
+ if (!r.tui) {
161
+ throw new InputError({
162
+ error: 'view_web_only',
163
+ message: `'${r.id}' is web-only — it ships no tui.mjs presenter`,
164
+ next: `Run \`crtr view serve ${r.id}\` to open it in the browser.`,
165
+ });
166
+ }
167
+ }
168
+ /** Per-host presenter validation: `view serve` requires a web presenter. */
169
+ export function requireWeb(r) {
170
+ if (!r.web) {
171
+ throw new InputError({
172
+ error: 'view_tui_only',
173
+ message: `'${r.id}' is tui-only — it ships no web.jsx presenter`,
174
+ next: `Run \`crtr view run ${r.id}\` to open it in a tmux pane.`,
175
+ });
176
+ }
177
+ }
178
+ function notTarget(r, target) {
179
+ return new Error(`view "${r.id}" has no ${target} presenter`);
180
+ }
181
+ function guided(r, file, problem) {
182
+ return new Error(`invalid view "${r.id}" (${r.scope}) at ${join(r.dir, file)}: ${problem}.\n` +
183
+ 'A dual-target view dir holds core.mjs (default export: { manifest: { id, title }, init(), intents }) ' +
184
+ 'plus optional tui.mjs (render + keymap), web.jsx, text.mjs (dump).');
185
+ }
186
+ function errText(e) {
187
+ return e instanceof Error ? e.message : String(e);
188
+ }
@@ -0,0 +1,7 @@
1
+ import type { Transport } from './transport.js';
2
+ export interface LocalTransportOptions {
3
+ /** Working directory for `exec` requests that don't carry their own cwd.
4
+ * Defaults to process.cwd(). */
5
+ cwd?: string;
6
+ }
7
+ export declare function createLocalTransport(opts?: LocalTransportOptions): Transport;
@@ -0,0 +1,70 @@
1
+ // transport-local.ts — the BUILT local transport: fulfills SourceRequests on
2
+ // this machine. exec→execFile (cwd = the view's cwd unless the request says
3
+ // otherwise), file→readFile, http→fetch.
4
+ //
5
+ // Never throws. Transport-level failure (binary missing, file unreadable,
6
+ // network down) ⇒ { ok:false, stderr }. A spawned process exiting non-zero is
7
+ // transport-level SUCCESS (ok:true + exitCode) — classifying that is the
8
+ // source's parse() job.
9
+ import { execFile } from 'node:child_process';
10
+ import { readFile } from 'node:fs/promises';
11
+ const MAX_BUFFER = 10 * 1024 * 1024;
12
+ export function createLocalTransport(opts = {}) {
13
+ const baseCwd = opts.cwd ?? process.cwd();
14
+ return {
15
+ async send(req) {
16
+ switch (req.kind) {
17
+ case 'exec': return sendExec(req, baseCwd);
18
+ case 'file': return sendFile(req);
19
+ case 'http': return sendHttp(req);
20
+ }
21
+ },
22
+ };
23
+ }
24
+ function sendExec(req, baseCwd) {
25
+ return new Promise((resolve) => {
26
+ const child = execFile(req.bin, req.args, { cwd: req.cwd ?? baseCwd, maxBuffer: MAX_BUFFER, encoding: 'utf8' }, (err, stdout, stderr) => {
27
+ if (!err) {
28
+ resolve({ ok: true, exitCode: 0, stdout, stderr });
29
+ return;
30
+ }
31
+ const code = err.code;
32
+ if (typeof code === 'number') {
33
+ // Ran but exited non-zero: transport-level success; parse() classifies.
34
+ resolve({ ok: true, exitCode: code, stdout: stdout ?? '', stderr: stderr ?? '' });
35
+ return;
36
+ }
37
+ if (err.signal) {
38
+ // Killed by signal: it ran; surface as a non-zero exit.
39
+ resolve({ ok: true, exitCode: 1, stdout: stdout ?? '', stderr: stderr ?? '' });
40
+ return;
41
+ }
42
+ // Spawn-level failure (ENOENT/EACCES/maxBuffer): transport failure.
43
+ const msg = code === 'ENOENT' ? `${req.bin}: command not found` : err.message;
44
+ resolve({ ok: false, stdout: stdout ?? '', stderr: msg });
45
+ });
46
+ if (req.stdin != null && child.stdin) {
47
+ child.stdin.write(req.stdin);
48
+ child.stdin.end();
49
+ }
50
+ });
51
+ }
52
+ async function sendFile(req) {
53
+ try {
54
+ const text = await readFile(req.path, 'utf8');
55
+ return { ok: true, stdout: text, stderr: '' };
56
+ }
57
+ catch (e) {
58
+ return { ok: false, stdout: '', stderr: e instanceof Error ? e.message : String(e) };
59
+ }
60
+ }
61
+ async function sendHttp(req) {
62
+ try {
63
+ const res = await fetch(req.url, { method: req.method, headers: req.headers, body: req.body });
64
+ const body = await res.text();
65
+ return { ok: true, status: res.status, stdout: body, stderr: res.ok ? '' : res.statusText };
66
+ }
67
+ catch (e) {
68
+ return { ok: false, stdout: '', stderr: e instanceof Error ? e.message : String(e) };
69
+ }
70
+ }
@@ -0,0 +1,4 @@
1
+ import type { SourceRequest, RawResponse } from './contract.js';
2
+ export interface Transport {
3
+ send(req: SourceRequest): Promise<RawResponse>;
4
+ }
@@ -0,0 +1,15 @@
1
+ // transport.ts — the transport seam between a view's declarative SourceRequests
2
+ // and whatever fulfills them.
3
+ //
4
+ // The core is transport-blind: it only ever calls ctx.resolve(source, args) /
5
+ // ctx.execute(command, args); the host threads Transport.send → source.parse.
6
+ // Implementations:
7
+ // • transport-local.ts (BUILT) — Node-side: exec→execFile, file→readFile,
8
+ // http→fetch. Used directly by the TUI host and by the `view serve` bridge
9
+ // server on behalf of the browser bundle.
10
+ // • src/web/transport-http.ts (BUILT) — browser-side: POSTs the SourceRequest
11
+ // to the local bridge (`/__crtr/source`).
12
+ // • cloud (SEAM ONLY, not built) — same interface; send() POSTs the request
13
+ // to an endpoint that runs it server-side, or maps it to a real API call.
14
+ // Wiring a different Transport is the entire cloud migration.
15
+ export {};
@@ -1,36 +1,47 @@
1
1
  import { type NodeRow } from '../core/canvas/index.js';
2
2
  import { isPidAlive } from '../core/canvas/pid.js';
3
3
  export declare const YIELD_STALL_GRACE_MS: number;
4
- export declare const NEVER_LAUNCHED_GRACE_MS: number;
5
- /** Never-launched decision (audit 2026-06-09, Bug 4 node mq45b6ch-9ecc2f03):
6
- * a spawn that opened its window but whose pi never came up leaves a row with
7
- * ZERO cycles, NO session, NO pid and, when the window lingers (e.g. a
8
- * remain-on-exit corpse), the live-pane pass read "no pid to judge" and left it
9
- * forever; the parent waited on a node that was never going to run. Pure core:
10
- * a node that has ever booted (session recorded) or ever been revived (cycles
11
- * bumped) is NOT never-launched the cycles gate also bounds this to ONE
12
- * automatic retry (the relaunch bumps cycles), so a persistently failing
13
- * launch can't loop. Age below the grace is a normal in-flight boot. */
14
- export declare function neverLaunchedVerdict(piSessionId: string | null, cycles: number, ageMs: number): 'leave' | 'relaunch';
15
- /** Yield-stall decision (audit 2026-06-09, Bug 1 — node mq5v9hfa-74493d45 and
16
- * three sibling orchestrators): a `node yield` records intent='refresh' and
17
- * agent_end dispatches the in-place respawn, but at very large contexts the pi
18
- * process can HANG instead of exiting — leaving pi alive, the turn over, and
19
- * the intent pending forever. The daemon only revives DEAD pis, so without
20
- * this verdict the node is permanently stuck (no revive, no wake). Pure core,
21
- * mirroring livenessVerdict; the kill side effects live in handleYieldStall.
4
+ /** Refresh-stall decision (§H refresh-authority; audit 2026-06-09, Bug 1 — node
5
+ * mq5v9hfa-74493d45 and siblings; stuck-refreshparent mq7s5o93): a `node
6
+ * yield` / refresh records intent='refresh' and relies on the in-process
7
+ * stophook to call shutdown at stop, but the engine can be left ALIVE either
8
+ * it HANGS instead of exiting (very large contexts) or a STALE stophook (pi
9
+ * extensions never reload) ignores intent='refresh' entirely and never calls
10
+ * shutdown. Either way: engine alive, turn over, intent pending forever. The
11
+ * daemon only revives DEAD engines, so without this verdict the node is
12
+ * permanently stuck (no revive, no wake). Pure core, mirroring livenessVerdict;
13
+ * the kill side effects live in handleYieldStall.
22
14
  *
23
- * 'kill' ONLY when ALL of: pi alive, intent='refresh', the turn is over (busy
24
- * marker absent — a node may legitimately keep working for many minutes after
25
- * running `node yield` mid-turn, and a working pi must never be killed), and
26
- * the state has persisted past YIELD_STALL_GRACE_MS. */
15
+ * 'kill' ONLY when ALL of: engine alive, intent='refresh', the turn is over
16
+ * (busy marker absent — a node may legitimately keep working for many minutes
17
+ * after running `node yield` mid-turn, and a working engine must never be
18
+ * killed), and the state has persisted past YIELD_STALL_GRACE_MS. */
27
19
  export declare function yieldStallVerdict(piPidAlive: boolean | null, intent: NodeRow['intent'], busy: boolean, stalledFor: number | null): 'leave' | 'pending' | 'kill';
20
+ export declare const ERROR_STALL_QUIET_MS: number;
21
+ /** The §I wedge signature, from a session-jsonl tail: the LAST assistant
22
+ * message stopped with 'error' (retry budget exhausted — pi appends the error
23
+ * turn and parks). Returns its errorMessage for the daemon log (the "why it
24
+ * wedged" trace), or null when the last assistant turn ended any other way —
25
+ * 'stop'/'length' is a healthy park, 'aborted' is a human Esc, and trailing
26
+ * non-assistant lines (toolResults, an injected user message) are walked past.
27
+ * The first line of a mid-file tail cut fails JSON.parse and is skipped. */
28
+ export declare function trailingEngineError(tail: string | null): string | null;
29
+ /** Error-stall decision (§I; gh issue #4 — node mqaeqzdu-0746b3b7, a 9h zombie
30
+ * after an Anthropic outage). Pure, mirroring yieldStallVerdict; the kill side
31
+ * effects live in handleErrorStall.
32
+ *
33
+ * 'kill' ONLY when ALL of: engine alive, NO pending intent (refresh is §H's
34
+ * domain; idle-release chose dormancy), the turn is over (busy absent — a node
35
+ * wedged mid-bash must be recovered by killing the subprocess, never the node),
36
+ * the session file has been quiet past ERROR_STALL_QUIET_MS, and its last
37
+ * assistant message stopped with 'error'. */
38
+ export declare function errorStallVerdict(piPidAlive: boolean | null, intent: NodeRow['intent'], busy: boolean, quietForMs: number | null, trailingError: boolean): 'leave' | 'kill';
28
39
  export type LivenessVerdict = 'leave' | 'pending' | 'revive';
29
- /** Decide what to do with a node whose tmux pane is alive, from its pi
30
- * liveness and how long it's been dead. Pure — the time-and-tmux side effects
31
- * live in handleLiveWindow; this is the unit-testable core.
32
- * piPidAlive: true=alive, false=dead, null=no pid recorded (legacy node, or a
33
- * relaunch in flight) — leave those to the pane-gone pass.
40
+ /** Decide what to do with a node whose engine pid is DEAD, from how long it's
41
+ * been dead. Pure — the time/revive side effects live in handleNodeLiveness;
42
+ * this is the unit-testable core. (piPidAlive carries dead/alive/unknown, but
43
+ * only `false` ever reaches a revive decision here — alive and null are routed
44
+ * upstream by handleNodeLiveness.)
34
45
  * deadFor: ms since first observed dead, or null on the first observation. */
35
46
  export declare function livenessVerdict(piPidAlive: boolean | null, deadFor: number | null): LivenessVerdict;
36
47
  export declare const DEAD_REAP_GRACE_MS: number;