@north-light/crouter 0.3.197 → 0.3.199

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 (339) hide show
  1. package/dist/api/client.d.ts +19 -19
  2. package/dist/api/client.js +29 -37
  3. package/dist/api/dto/broker-ops.d.ts +3 -1
  4. package/dist/api/dto/broker.d.ts +13 -2
  5. package/dist/api/dto/broker.js +4 -4
  6. package/dist/api/dto/human.d.ts +6 -5
  7. package/dist/api/dto/inbox.d.ts +126 -33
  8. package/dist/api/dto/nodes.d.ts +8 -2
  9. package/dist/api/dto/profiles.d.ts +23 -0
  10. package/dist/api/dto/profiles.js +2 -2
  11. package/dist/api/dto/worktree.d.ts +3 -5
  12. package/dist/api/routes.d.ts +3 -5
  13. package/dist/api/routes.js +4 -6
  14. package/dist/builtin-memory/00-runtime-base.md +8 -8
  15. package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
  16. package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -3
  17. package/dist/builtin-memory/04-orchestration-kernel.md +6 -6
  18. package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
  19. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +2 -2
  20. package/dist/builtin-memory/05-kinds/review/00-base.md +2 -2
  21. package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -1
  22. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -1
  23. package/dist/builtin-memory/insights/capture.md +2 -2
  24. package/dist/builtin-memory/insights/init.md +3 -3
  25. package/dist/builtin-memory/internal/INDEX.md +1 -1
  26. package/dist/builtin-memory/internal/memory-loading.md +1 -1
  27. package/dist/builtin-memory/internal/nodes-and-canvas.md +3 -3
  28. package/dist/builtin-memory/internal/plugins.md +59 -2
  29. package/dist/builtin-memory/internal/storage-tiers.md +3 -1
  30. package/dist/builtin-memory/spec/roadmap.md +2 -2
  31. package/dist/builtin-memory/testing.md +1 -1
  32. package/dist/builtin-memory/wedged-child-on-runaway-bash.md +1 -1
  33. package/dist/clients/attach/chrome/bash-jobs.js +3 -3
  34. package/dist/clients/attach/render/chat-view.d.ts +29 -9
  35. package/dist/clients/attach/render/chat-view.js +64 -35
  36. package/dist/clients/attach/render/markdown-source.js +0 -6
  37. package/dist/clients/attach/render/page-block.d.ts +34 -0
  38. package/dist/clients/attach/render/page-block.js +226 -0
  39. package/dist/clients/attach/render/tool-calls.js +16 -2
  40. package/dist/clients/attach/session/whip.d.ts +2 -0
  41. package/dist/clients/attach/session/whip.js +2 -0
  42. package/dist/clients/attach/viewer.js +1419 -982
  43. package/dist/clients/conversation/projection.js +18 -3
  44. package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +9 -6
  45. package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +13 -14
  46. package/dist/clients/inbox/controller.js +10 -16
  47. package/dist/clients/inbox/page-adapter.d.ts +3 -4
  48. package/dist/clients/inbox/page-adapter.js +3 -4
  49. package/dist/clients/inbox/resolve.d.ts +1 -1
  50. package/dist/clients/inbox/resolve.js +3 -3
  51. package/dist/clients/inbox/review/document-surface.js +3 -1
  52. package/dist/clients/inbox/review/state.d.ts +2 -1
  53. package/dist/clients/inbox/review/state.js +5 -16
  54. package/dist/clients/inbox/tui/input.js +37 -19
  55. package/dist/clients/inbox/tui/panel.js +5 -31
  56. package/dist/clients/inbox/tui/render.d.ts +11 -0
  57. package/dist/clients/inbox/tui/render.js +101 -36
  58. package/dist/clients/inbox/tui/slots.d.ts +4 -4
  59. package/dist/clients/inbox/tui/slots.js +19 -44
  60. package/dist/clients/inbox/tui/types.d.ts +6 -10
  61. package/dist/clients/inbox/tui.js +2 -2
  62. package/dist/commands/__tests__/human.test.js +118 -24
  63. package/dist/commands/__tests__/node-message.test.js +14 -0
  64. package/dist/commands/attention.js +4 -4
  65. package/dist/commands/canvas-browse.js +1 -1
  66. package/dist/commands/canvas-snapshot.js +1 -1
  67. package/dist/commands/canvas.js +1 -1
  68. package/dist/commands/dashboard.js +1 -1
  69. package/dist/commands/human/{doc.d.ts → components.d.ts} +1 -1
  70. package/dist/commands/human/components.js +77 -0
  71. package/dist/commands/human/feedback.d.ts +2 -0
  72. package/dist/commands/human/feedback.js +108 -0
  73. package/dist/commands/human/prompts.d.ts +17 -8
  74. package/dist/commands/human/prompts.js +217 -226
  75. package/dist/commands/human/queue.js +57 -88
  76. package/dist/commands/human/shared.d.ts +4 -15
  77. package/dist/commands/human/shared.js +19 -61
  78. package/dist/commands/human.js +32 -19
  79. package/dist/commands/memory/origin.js +2 -2
  80. package/dist/commands/node/bash.js +4 -4
  81. package/dist/commands/node/inspect.js +3 -3
  82. package/dist/commands/node/lifecycle.js +1 -1
  83. package/dist/commands/node/message.js +2 -2
  84. package/dist/commands/node-worktree.js +8 -8
  85. package/dist/commands/pkg/browse.js +1 -1
  86. package/dist/commands/pkg/plugin-manage.js +129 -10
  87. package/dist/commands/profile/delete.js +41 -15
  88. package/dist/commands/profile.js +1 -1
  89. package/dist/commands/push.js +1 -1
  90. package/dist/commands/surface/node/focus.js +29 -14
  91. package/dist/commands/surface-edit.js +3 -3
  92. package/dist/commands/surface-inbox.d.ts +2 -0
  93. package/dist/commands/{human/inbox.js → surface-inbox.js} +14 -18
  94. package/dist/commands/surface.js +2 -1
  95. package/dist/commands/sys/doctor.js +60 -3
  96. package/dist/commands/sys/feedback.js +79 -25
  97. package/dist/commands/sys/settings.js +1 -1
  98. package/dist/core/__tests__/cron-broker-capacity.test.js +1 -1
  99. package/dist/core/__tests__/daemon-boot.test.js +7 -4
  100. package/dist/core/__tests__/dead-node-policy-table.test.js +20 -36
  101. package/dist/core/__tests__/extension-abort.test.js +5 -2
  102. package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -2
  103. package/dist/core/__tests__/fixtures/fake-engine.js +47 -3
  104. package/dist/core/__tests__/human-cancel-guard.test.js +31 -10
  105. package/dist/core/__tests__/human-deliver.test.js +41 -45
  106. package/dist/core/__tests__/human-node-not-supervised.test.js +1 -1
  107. package/dist/core/__tests__/plugin-page-components.test.js +157 -0
  108. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +13 -11
  109. package/dist/core/__tests__/seam/dormancy-release.test.js +103 -4
  110. package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
  111. package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -16
  112. package/dist/core/__tests__/serial/live-mutation.test.js +1 -1
  113. package/dist/core/__tests__/serial/tmux-surface.test.js +4 -2
  114. package/dist/core/__tests__/serial/worktree.test.js +24 -198
  115. package/dist/core/__tests__/session-cycles.test.js +22 -0
  116. package/dist/core/__tests__/stop-guard.test.js +4 -4
  117. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +19 -4
  118. package/dist/core/bash-jobs.d.ts +6 -0
  119. package/dist/core/bash-jobs.js +10 -0
  120. package/dist/core/canvas/__tests__/attention.test.js +8 -5
  121. package/dist/core/canvas/__tests__/render-remote.test.js +5 -5
  122. package/dist/core/canvas/attention.js +8 -6
  123. package/dist/core/canvas/browse/app.d.ts +1 -1
  124. package/dist/core/canvas/browse/app.js +21 -3
  125. package/dist/core/canvas/browse/model.d.ts +8 -2
  126. package/dist/core/canvas/browse/model.js +11 -4
  127. package/dist/core/canvas/browse/render.d.ts +4 -0
  128. package/dist/core/canvas/browse/render.js +5 -1
  129. package/dist/core/canvas/canvas.d.ts +33 -0
  130. package/dist/core/canvas/canvas.js +92 -0
  131. package/dist/core/canvas/crons.d.ts +9 -0
  132. package/dist/core/canvas/crons.js +51 -0
  133. package/dist/core/canvas/node-order.d.ts +22 -1
  134. package/dist/core/canvas/node-order.js +26 -1
  135. package/dist/core/canvas/render-source.d.ts +5 -0
  136. package/dist/core/canvas/render-source.js +1 -0
  137. package/dist/core/canvas/types.d.ts +9 -3
  138. package/dist/core/command-plugins/bundle.d.ts +6 -1
  139. package/dist/core/command-plugins/bundle.js +26 -13
  140. package/dist/core/command.js +8 -12
  141. package/dist/core/config.d.ts +47 -1
  142. package/dist/core/config.js +138 -4
  143. package/dist/core/help.d.ts +3 -0
  144. package/dist/core/help.js +1 -1
  145. package/dist/core/human/__tests__/page-catalog.test.d.ts +1 -0
  146. package/dist/core/human/__tests__/page-catalog.test.js +20 -0
  147. package/dist/core/human/__tests__/page-tickets.test.js +68 -34
  148. package/dist/core/human/__tests__/page.test.js +60 -119
  149. package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -16
  150. package/dist/core/human/answer-text.d.ts +3 -0
  151. package/dist/core/human/answer-text.js +98 -0
  152. package/dist/core/human/answer.d.ts +53 -0
  153. package/dist/core/human/answer.js +119 -0
  154. package/dist/core/human/component-docs.d.ts +18 -0
  155. package/dist/core/human/component-docs.js +434 -0
  156. package/dist/core/human/convention.d.ts +17 -3
  157. package/dist/core/human/convention.js +36 -5
  158. package/dist/core/human/feedback-companion.d.ts +20 -0
  159. package/dist/core/human/feedback-companion.js +98 -0
  160. package/dist/core/human/feedback.d.ts +65 -0
  161. package/dist/core/human/feedback.js +116 -0
  162. package/dist/core/human/page-catalog.d.ts +56 -5
  163. package/dist/core/human/page-catalog.js +151 -22
  164. package/dist/core/human/page-errors.d.ts +4 -0
  165. package/dist/core/human/page-errors.js +7 -0
  166. package/dist/core/human/page-eval.d.ts +20 -0
  167. package/dist/core/human/page-eval.js +123 -0
  168. package/dist/core/human/page-schema.d.ts +48 -116
  169. package/dist/core/human/page-schema.js +100 -72
  170. package/dist/core/human/page-synth.d.ts +2 -4
  171. package/dist/core/human/page-synth.js +20 -7
  172. package/dist/core/human/page.d.ts +13 -21
  173. package/dist/core/human/page.js +147 -391
  174. package/dist/core/human/review-schema.d.ts +1 -0
  175. package/dist/core/human/review-schema.js +1 -1
  176. package/dist/core/human/root.d.ts +6 -2
  177. package/dist/core/human/root.js +12 -4
  178. package/dist/core/human/scan.d.ts +3 -3
  179. package/dist/core/human/scan.js +21 -21
  180. package/dist/core/human/summary.d.ts +2 -6
  181. package/dist/core/human/summary.js +3 -59
  182. package/dist/core/human/tickets.d.ts +22 -15
  183. package/dist/core/human/tickets.js +90 -28
  184. package/dist/core/human/types.d.ts +14 -2
  185. package/dist/core/keybindings/catalog.d.ts +2 -2
  186. package/dist/core/keybindings/catalog.js +1 -0
  187. package/dist/core/preview-registry.js +8 -10
  188. package/dist/core/profiles/default-binding.d.ts +4 -0
  189. package/dist/core/profiles/default-binding.js +24 -0
  190. package/dist/core/profiles/deletion-reservation.d.ts +8 -0
  191. package/dist/core/profiles/deletion-reservation.js +38 -0
  192. package/dist/core/profiles/manifest.d.ts +3 -0
  193. package/dist/core/profiles/manifest.js +12 -0
  194. package/dist/core/profiles/select.js +2 -2
  195. package/dist/core/review/realize.js +12 -4
  196. package/dist/core/review/store.d.ts +6 -0
  197. package/dist/core/review/store.js +31 -0
  198. package/dist/core/runtime/bearings.d.ts +0 -4
  199. package/dist/core/runtime/bearings.js +4 -62
  200. package/dist/core/runtime/bin-contributions.d.ts +44 -0
  201. package/dist/core/runtime/bin-contributions.js +186 -0
  202. package/dist/core/runtime/boot-root.js +6 -1
  203. package/dist/core/runtime/broker/client-registry.d.ts +4 -0
  204. package/dist/core/runtime/broker/client-registry.js +13 -1
  205. package/dist/core/runtime/broker/extension-abort.d.ts +1 -1
  206. package/dist/core/runtime/broker/extension-abort.js +4 -2
  207. package/dist/core/runtime/broker/fault-retry.js +4 -0
  208. package/dist/core/runtime/broker/frame-dispatch.d.ts +3 -1
  209. package/dist/core/runtime/broker/frame-dispatch.js +5 -6
  210. package/dist/core/runtime/broker/read-ops.d.ts +1 -1
  211. package/dist/core/runtime/broker/read-ops.js +2 -2
  212. package/dist/core/runtime/broker/rebind.d.ts +1 -0
  213. package/dist/core/runtime/broker/rebind.js +6 -1
  214. package/dist/core/runtime/broker-extension-render.js +19 -0
  215. package/dist/core/runtime/broker-protocol.d.ts +14 -0
  216. package/dist/core/runtime/broker.d.ts +1 -1
  217. package/dist/core/runtime/broker.js +25 -12
  218. package/dist/core/runtime/busy.d.ts +4 -3
  219. package/dist/core/runtime/busy.js +4 -3
  220. package/dist/core/runtime/front-door-env.d.ts +5 -0
  221. package/dist/core/runtime/front-door-env.js +16 -0
  222. package/dist/core/runtime/front-door.d.ts +1 -5
  223. package/dist/core/runtime/front-door.js +2 -5
  224. package/dist/core/runtime/host.d.ts +1 -1
  225. package/dist/core/runtime/invocation.d.ts +6 -0
  226. package/dist/core/runtime/invocation.js +10 -0
  227. package/dist/core/runtime/kickoff.js +34 -0
  228. package/dist/core/runtime/launch.d.ts +2 -6
  229. package/dist/core/runtime/node-read.js +4 -1
  230. package/dist/core/runtime/nodes.js +2 -0
  231. package/dist/core/runtime/promote.d.ts +1 -0
  232. package/dist/core/runtime/promote.js +3 -1
  233. package/dist/core/runtime/session-cycles.d.ts +6 -0
  234. package/dist/core/runtime/session-cycles.js +15 -1
  235. package/dist/core/runtime/session-visibility.d.ts +17 -8
  236. package/dist/core/runtime/session-visibility.js +19 -10
  237. package/dist/core/runtime/spawn-env.d.ts +9 -1
  238. package/dist/core/runtime/spawn-env.js +62 -2
  239. package/dist/core/runtime/spawn.js +4 -4
  240. package/dist/core/runtime/stop-guard.d.ts +2 -2
  241. package/dist/core/runtime/stop-guard.js +2 -2
  242. package/dist/core/runtime/stop-signals.d.ts +1 -1
  243. package/dist/core/runtime/stop-signals.js +2 -2
  244. package/dist/core/runtime/tmux-bindings.js +1 -1
  245. package/dist/core/session-model/session-state.d.ts +4 -3
  246. package/dist/core/session-model/session-state.js +14 -1
  247. package/dist/core/user-settings.d.ts +36 -2
  248. package/dist/core/user-settings.js +56 -14
  249. package/dist/core/worktree.d.ts +15 -45
  250. package/dist/core/worktree.js +59 -261
  251. package/dist/daemon/api/__tests__/seam/profile-delete.test.d.ts +1 -0
  252. package/dist/daemon/api/__tests__/seam/profile-delete.test.js +386 -0
  253. package/dist/daemon/api/handlers/bash-jobs.js +1 -1
  254. package/dist/daemon/api/handlers/broker-ops.js +9 -5
  255. package/dist/daemon/api/handlers/feedback-comments.d.ts +2 -0
  256. package/dist/daemon/api/handlers/feedback-comments.js +207 -0
  257. package/dist/daemon/api/handlers/human.js +24 -20
  258. package/dist/daemon/api/handlers/inbox.d.ts +10 -0
  259. package/dist/daemon/api/handlers/inbox.js +48 -109
  260. package/dist/daemon/api/handlers/profiles.js +11 -29
  261. package/dist/daemon/api/map.d.ts +6 -1
  262. package/dist/daemon/api/map.js +23 -0
  263. package/dist/daemon/api/server.js +3 -1
  264. package/dist/daemon/companion-retire.d.ts +2 -0
  265. package/dist/daemon/companion-retire.js +33 -0
  266. package/dist/daemon/cron-run.js +29 -17
  267. package/dist/daemon/crtrd.js +1 -1
  268. package/dist/daemon/fleet.d.ts +22 -10
  269. package/dist/daemon/fleet.js +43 -24
  270. package/dist/daemon/human/finish.d.ts +12 -6
  271. package/dist/daemon/human/finish.js +134 -54
  272. package/dist/daemon/human/sweep.js +9 -3
  273. package/dist/daemon/profile-delete.d.ts +7 -0
  274. package/dist/daemon/profile-delete.js +306 -0
  275. package/dist/daemon/reconcilers/broker-supervision.js +14 -9
  276. package/dist/daemon/reconcilers/dormant-inbox.js +11 -6
  277. package/dist/daemon/reconcilers/live-obligation.d.ts +21 -1
  278. package/dist/daemon/reconcilers/live-obligation.js +30 -2
  279. package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -0
  280. package/dist/daemon/reconcilers/storage-maintenance.js +28 -1
  281. package/dist/daemon/review/deliver.js +3 -3
  282. package/dist/daemon/review/finish.d.ts +0 -2
  283. package/dist/daemon/review/finish.js +1 -29
  284. package/dist/daemon/review/sweep.js +12 -1
  285. package/dist/pi-extensions/__tests__/canvas-bash-valve.test.js +56 -1
  286. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +4 -4
  287. package/dist/pi-extensions/canvas-bash-valve.d.ts +9 -1
  288. package/dist/pi-extensions/canvas-bash-valve.js +31 -13
  289. package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
  290. package/dist/pi-extensions/canvas-review-boundary.d.ts +4 -0
  291. package/dist/pi-extensions/canvas-review-boundary.js +18 -9
  292. package/dist/pi-extensions/canvas-stophook.js +4 -4
  293. package/dist/shared/generated-context.js +2 -2
  294. package/dist/types.d.ts +61 -5
  295. package/dist/types.js +8 -2
  296. package/package.json +4 -4
  297. package/runtime.lock.json +5 -33
  298. package/scripts/install-runtime.mjs +19 -24
  299. package/dist/builtin-memory/init.md +0 -38
  300. package/dist/builtin-memory/plan.md +0 -19
  301. package/dist/builtin-memory/spec.md +0 -19
  302. package/dist/clients/attach/render/html-markdown.d.ts +0 -13
  303. package/dist/clients/attach/render/html-markdown.js +0 -206
  304. package/dist/commands/human/doc.js +0 -144
  305. package/dist/commands/human/inbox.d.ts +0 -2
  306. package/dist/core/human/__tests__/page-render.test.js +0 -49
  307. package/dist/core/human/markdown-html.d.ts +0 -8
  308. package/dist/core/human/markdown-html.js +0 -581
  309. package/dist/core/human/page-render.d.ts +0 -3
  310. package/dist/core/human/page-render.js +0 -131
  311. package/dist/pages/bundle.css +0 -1
  312. package/dist/pages/bundle.js +0 -1347
  313. package/dist/pages/comments.d.ts +0 -43
  314. package/dist/pages/comments.js +0 -125
  315. package/dist/pages/elements/cards.d.ts +0 -17
  316. package/dist/pages/elements/cards.js +0 -802
  317. package/dist/pages/elements/chart.js +0 -648
  318. package/dist/pages/elements/options.d.ts +0 -21
  319. package/dist/pages/elements/options.js +0 -629
  320. package/dist/pages/elements/pages.d.ts +0 -24
  321. package/dist/pages/elements/pages.js +0 -355
  322. package/dist/pages/elements/slot.d.ts +0 -21
  323. package/dist/pages/elements/slot.js +0 -105
  324. package/dist/pages/elements/table.d.ts +0 -19
  325. package/dist/pages/elements/table.js +0 -935
  326. package/dist/pages/elements/text.d.ts +0 -25
  327. package/dist/pages/elements/text.js +0 -968
  328. package/dist/pages/entry.d.ts +0 -8
  329. package/dist/pages/entry.js +0 -8
  330. package/dist/pages/host.d.ts +0 -158
  331. package/dist/pages/host.js +0 -182
  332. package/dist/pages/register.d.ts +0 -2
  333. package/dist/pages/register.js +0 -3
  334. package/dist/pages/slot-config.d.ts +0 -36
  335. package/dist/pages/slot-config.js +0 -50
  336. package/dist/pages/types.d.ts +0 -98
  337. package/dist/pages/types.js +0 -23
  338. /package/dist/{core/human/__tests__/page-render.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
  339. /package/dist/{pages/elements/chart.d.ts → core/__tests__/plugin-page-components.test.d.ts} +0 -0
@@ -4,7 +4,7 @@
4
4
  // still spawned commands with that deleted directory as cwd. child_process
5
5
  // then failed before bash could run, leaving the node unable to use its shell.
6
6
  import assert from 'node:assert/strict';
7
- import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
7
+ import { chmodSync, existsSync, mkdtempSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
8
8
  import { homedir, tmpdir } from 'node:os';
9
9
  import { join } from 'node:path';
10
10
  import { test } from 'node:test';
@@ -29,3 +29,58 @@ test('bash falls back to home after its worktree cwd was removed', { timeout: 5_
29
29
  rmSync(root, { recursive: true, force: true });
30
30
  }
31
31
  });
32
+ test('bash valve resolves an admitted relative PATH entry from the execution cwd', { timeout: 5_000 }, async () => {
33
+ const root = mkdtempSync(join(tmpdir(), 'crtr-bash-valve-relative-host-path-'));
34
+ const contextDir = join(root, 'context');
35
+ const hostBin = join(root, 'host-bin');
36
+ const shim = join(root, 'shim');
37
+ const marker = join(root, 'contributed-bash-ran');
38
+ mkdirSync(contextDir);
39
+ mkdirSync(hostBin);
40
+ mkdirSync(shim);
41
+ symlinkSync('/bin/bash', join(hostBin, 'bash'));
42
+ writeFileSync(join(shim, 'bash'), `#!/bin/sh\nprintf contributed > ${JSON.stringify(marker)}\nexit 97\n`);
43
+ writeFileSync(join(shim, 'tool'), '#!/bin/sh\nprintf tool\n');
44
+ chmodSync(join(shim, 'bash'), 0o755);
45
+ chmodSync(join(shim, 'tool'), 0o755);
46
+ try {
47
+ const output = [];
48
+ const result = await createValveOperations('test-node', contextDir).exec('tool; command -v tool', root, {
49
+ env: { PATH: `${shim}:host-bin:/bin:/usr/bin`, CRTR_ADMITTED_HOST_PATH: 'host-bin:/bin:/usr/bin' },
50
+ onData: (data) => output.push(data),
51
+ });
52
+ assert.equal(result.exitCode, 0);
53
+ assert.equal(Buffer.concat(output).toString(), `tool${shim}/tool\n`);
54
+ assert.equal(existsSync(marker), false, 'the contributed bash must never be crouter\'s interpreter');
55
+ }
56
+ finally {
57
+ rmSync(root, { recursive: true, force: true });
58
+ }
59
+ });
60
+ test('bash valve uses the admitted host shell while commands keep shim precedence', { timeout: 5_000 }, async () => {
61
+ const root = mkdtempSync(join(tmpdir(), 'crtr-bash-valve-shim-'));
62
+ const contextDir = join(root, 'context');
63
+ const shim = join(root, 'shim');
64
+ const marker = join(root, 'contributed-bash-ran');
65
+ mkdirSync(contextDir);
66
+ mkdirSync(shim);
67
+ writeFileSync(join(shim, 'bash'), `#!/bin/sh\nprintf contributed > ${JSON.stringify(marker)}\nexit 97\n`);
68
+ writeFileSync(join(shim, 'tool'), '#!/bin/sh\nprintf tool\n');
69
+ chmodSync(join(shim, 'bash'), 0o755);
70
+ chmodSync(join(shim, 'tool'), 0o755);
71
+ try {
72
+ const output = [];
73
+ const hostPath = process.env['PATH'];
74
+ assert.notEqual(hostPath, undefined);
75
+ const result = await createValveOperations('test-node', contextDir).exec('tool; command -v tool', root, {
76
+ env: { PATH: `${shim}:${hostPath}`, CRTR_ADMITTED_HOST_PATH: hostPath },
77
+ onData: (data) => output.push(data),
78
+ });
79
+ assert.equal(result.exitCode, 0);
80
+ assert.equal(Buffer.concat(output).toString(), `tool${shim}/tool\n`);
81
+ assert.equal(existsSync(marker), false, 'the contributed bash must never be crouter\'s interpreter');
82
+ }
83
+ finally {
84
+ rmSync(root, { recursive: true, force: true });
85
+ }
86
+ });
@@ -363,8 +363,8 @@ test('turn_end still clears a daemon→node wedge fault (existing behavior, unch
363
363
  pi.fire('turn_end', turnEndEvent('turn completed'), {});
364
364
  assert.equal(readFault('leaf'), null, 'the daemon→node wedge fault is still cleared at turn_end');
365
365
  });
366
- test('a successful final or human ask suppresses the stop reprompt without a canvas read', async () => {
367
- for (const [signal, shutsDown] of [['pushedFinal', true], ['askedHuman', false]]) {
366
+ test('a successful final or requested human reply suppresses the stop reprompt without a canvas read', async () => {
367
+ for (const [signal, shutsDown] of [['pushedFinal', true], ['requestedHumanReply', false]]) {
368
368
  process.env['CRTR_NODE_ID'] = 'signal-only';
369
369
  markStopSignal('signal-only', signal);
370
370
  const pi = makeFakePi();
@@ -389,12 +389,12 @@ test('an in-run retry (agent_start with no new input) does not erase this run\'s
389
389
  const ctx = { shutdown: () => { } };
390
390
  pi.fire('input', { type: 'input' }, ctx);
391
391
  pi.fire('agent_start', { type: 'agent_start' }, ctx);
392
- markStopSignal('leaf', 'askedHuman'); // the node ran `crtr human ask` mid-run
392
+ markStopSignal('leaf', 'requestedHumanReply'); // the node ran `crtr human send` mid-run
393
393
  pi.fire('agent_start', { type: 'agent_start' }, ctx); // pi auto-retried after a provider error
394
394
  await settle(pi, stopEvent('waiting on the human now'), ctx);
395
395
  assert.equal(pi.injected.length, 0, 'the ask survives the in-run retry — no stall reprompt');
396
396
  // ...and the NEXT user-directed run does start clean.
397
- markStopSignal('leaf', 'askedHuman');
397
+ markStopSignal('leaf', 'requestedHumanReply');
398
398
  pi.fire('input', { type: 'input' }, ctx);
399
399
  await settle(pi, stopEvent('I think I am basically done here'), ctx);
400
400
  assert.equal(pi.injected.length, 1, 'a new input discards a leftover marker from a prior run');
@@ -1,4 +1,4 @@
1
- import { type BashOperations } from '@earendil-works/pi-coding-agent';
1
+ import { createBashToolDefinition, type BashOperations } from '@earendil-works/pi-coding-agent';
2
2
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
3
3
  /** Seconds a leading `sleep ...` will block for, or null when the command does
4
4
  * not open with a sleep (or its duration isn't statically knowable). */
@@ -7,4 +7,12 @@ export declare function leadingSleepSeconds(command: string): number | null;
7
7
  * `takePurpose`, so concurrent calls cannot exchange labels. It is consumed even
8
8
  * when the command is invalid or refused, leaving no state to leak later. */
9
9
  export declare function createValveOperations(nodeId: string, contextDir: string, takePurpose?: () => string | null): BashOperations;
10
+ type BashToolDefinition = ReturnType<typeof createBashToolDefinition>;
11
+ /** Build the valve-backed bash tool and, when configured, add `purpose` to its
12
+ * schema. Bind each purpose directly to its own execution: Pi may preflight a
13
+ * batch before starting its calls, so a shared "next purpose" slot could give a
14
+ * concurrent call the wrong label. Disabled means absent, not merely ignored —
15
+ * the model receives pi's upstream bash schema verbatim. */
16
+ export declare function createValveToolDefinition(nodeId: string, contextDir: string, cwd: string, exposePurpose: boolean): BashToolDefinition;
10
17
  export default function (pi: ExtensionAPI): void;
18
+ export {};
@@ -28,6 +28,8 @@ import { spawn } from 'node:child_process';
28
28
  import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, rmSync, statSync, writeFileSync, } from 'node:fs';
29
29
  import { homedir } from 'node:os';
30
30
  import { backgroundBashJob, bashJobPaths, formatBashElapsed, newBashJobId, normalizeBashJobPurpose } from '../core/bash-jobs.js';
31
+ import { readConfig } from '../core/config.js';
32
+ import { resolveAdmittedHostCommand } from '../core/runtime/spawn-env.js';
31
33
  import { Type } from 'typebox';
32
34
  import { createBashToolDefinition } from '@earendil-works/pi-coding-agent';
33
35
  // ---------------------------------------------------------------------------
@@ -51,19 +53,31 @@ function resolveValveMs() {
51
53
  // Positional argv (never interpolated into the script — the command text
52
54
  // lives only in cmd.sh):
53
55
  // $1 cmd.sh $2 job.log $3 job.exit $4 job.run
54
- // $5 job.done $6 job.bg $7 jobId $8 nodeId
56
+ // $5 job.done $6 job.bg $7 jobId $8 nodeId $9 shell path
55
57
  //
56
58
  // `--` before the paths is a dummy $0 (a standard `bash -c script -- args`
57
59
  // idiom) so the real payload lands at $1 onward.
58
60
  // ---------------------------------------------------------------------------
61
+ // The elapsed rendering mirrors formatBashElapsed() in core/bash-jobs.ts
62
+ // (`12s` / `3m 40s` / `1h 04m`) so the completion message reads the same as the
63
+ // handoff notice; it measures TOTAL runtime, foreground portion included.
59
64
  const SUPERVISOR_SCRIPT = [
60
- 'bash "$1" > "$2" 2>&1',
65
+ 'SECONDS=0',
66
+ '"${9}" "$1" > "$2" 2>&1',
61
67
  'ec=$?',
68
+ 'd=$SECONDS',
62
69
  'echo "$ec" > "$3"',
70
+ 'if [ "$d" -lt 60 ]; then',
71
+ ' el="${d}s"',
72
+ 'elif [ "$d" -lt 3600 ]; then',
73
+ ' el="$((d / 60))m $(printf "%02d" "$((d % 60))")s"',
74
+ 'else',
75
+ ' el="$((d / 3600))h $(printf "%02d" "$((d / 60 % 60))")m"',
76
+ 'fi',
63
77
  'if mv "$4" "$5" 2>/dev/null; then',
64
78
  ' :',
65
79
  'elif [ -f "$6" ]; then',
66
- ' crtr node message send "Background bash job $7 finished (exit $ec). Log: $2 Command: $1" --to "$8" --tier urgent || true',
80
+ ' crtr node message send "Background bash job $7 finished (exit $ec) after $el. Log: $2 Command: $1" --to "$8" --tier urgent || true',
67
81
  'fi',
68
82
  ].join('\n');
69
83
  function makeJobPaths(contextDir) {
@@ -182,7 +196,7 @@ function longSleepRefusal(command) {
182
196
  `Holding a live window on a clock is never how you wait here. Instead:\n` +
183
197
  ` - schedule the wake and end your turn: crtr cron -h (one-shot at a time, or recurring)\n` +
184
198
  ` - race an inbox message against a deadline: crtr node wait deadline -h\n` +
185
- ` - waiting on a child, a human ask, or any spine event: just stop — the runtime wakes you.\n` +
199
+ ` - waiting on a child, a reply from the user, or any spine event: just stop — the runtime wakes you.\n` +
186
200
  `If you truly need a short pause inline, sleep ${MAX_INLINE_SLEEP_SECONDS}s or less.`);
187
201
  }
188
202
  /** A node can legitimately remove its own merged worktree. Never recreate that
@@ -295,7 +309,8 @@ export function createValveOperations(nodeId, contextDir, takePurpose = () => nu
295
309
  const execution = resolveExecutionCwd(cwd);
296
310
  if (execution.warning)
297
311
  onData(Buffer.from(execution.warning));
298
- const child = spawn('bash', ['-c', SUPERVISOR_SCRIPT, '--', paths.cmdSh, paths.jobLog, paths.jobExit, paths.jobRun, paths.jobDone, paths.jobBg, paths.jobId, nodeId], {
312
+ const shellPath = resolveAdmittedHostCommand('bash', env ?? process.env, execution.cwd);
313
+ const child = spawn(shellPath, ['-c', SUPERVISOR_SCRIPT, '--', paths.cmdSh, paths.jobLog, paths.jobExit, paths.jobRun, paths.jobDone, paths.jobBg, paths.jobId, nodeId, shellPath], {
299
314
  cwd: execution.cwd,
300
315
  detached: true,
301
316
  stdio: 'ignore',
@@ -351,7 +366,7 @@ export function createValveOperations(nodeId, contextDir, takePurpose = () => nu
351
366
  // ---------------------------------------------------------------------------
352
367
  // `purpose` — the one line a person reads while a command runs
353
368
  //
354
- // A deliberate deviation from taking pi's definition verbatim, and the only one
369
+ // An optional deviation from taking pi's definition verbatim, and the only one
355
370
  // here. Everywhere crtr's output reaches a non-technical audience, the host has
356
371
  // to render SOMETHING per step, and without this field its only material is the
357
372
  // command — which yields "Running command" for an entire turn. The model is the
@@ -393,13 +408,16 @@ function withPurpose(definition) {
393
408
  }),
394
409
  };
395
410
  }
396
- /** Bind each purpose directly to its own tool execution. Pi may preflight a
397
- * batch before starting its calls, so a shared "next purpose" slot could give
398
- * a concurrent call the wrong label. */
399
- function createPurposeValveToolDefinition(nodeId, contextDir, cwd) {
400
- const schemaDefinition = withPurpose(createBashToolDefinition(cwd, { operations: createValveOperations(nodeId, contextDir) }));
411
+ /** Build the valve-backed bash tool and, when configured, add `purpose` to its
412
+ * schema. Bind each purpose directly to its own execution: Pi may preflight a
413
+ * batch before starting its calls, so a shared "next purpose" slot could give a
414
+ * concurrent call the wrong label. Disabled means absent, not merely ignored —
415
+ * the model receives pi's upstream bash schema verbatim. */
416
+ export function createValveToolDefinition(nodeId, contextDir, cwd, exposePurpose) {
417
+ const baseDefinition = createBashToolDefinition(cwd, { operations: createValveOperations(nodeId, contextDir) });
418
+ const schemaDefinition = exposePurpose ? withPurpose(baseDefinition) : baseDefinition;
401
419
  const execute = (toolCallId, params, signal, onUpdate, ctx) => {
402
- const purpose = normalizeBashJobPurpose(params['purpose']);
420
+ const purpose = exposePurpose ? normalizeBashJobPurpose(params['purpose']) : null;
403
421
  const definition = createBashToolDefinition(cwd, { operations: createValveOperations(nodeId, contextDir, () => purpose) });
404
422
  return definition.execute(toolCallId, params, signal, onUpdate, ctx);
405
423
  };
@@ -418,6 +436,6 @@ export default function (pi) {
418
436
  // is built against. registerTool replaces the builtin by name; re-firing on
419
437
  // every session_start is idempotent (last registration wins).
420
438
  pi.on('session_start', (_event, ctx) => {
421
- pi.registerTool(createPurposeValveToolDefinition(nodeId, contextDir, ctx.cwd));
439
+ pi.registerTool(createValveToolDefinition(nodeId, contextDir, ctx.cwd, readConfig('user').bash_tool_purpose));
422
440
  });
423
441
  }
@@ -306,7 +306,7 @@ class CanvasInboxWatcher {
306
306
  }
307
307
  /** True when delivery must wait a tick for a report-node projection refresh.
308
308
  * The projection captured at session_start omits every node created since —
309
- * each `crtr human ask` bridge and each child this node spawns — so their
309
+ * each reply-bearing `crtr human send` bridge and each child this node spawns — so their
310
310
  * reports would render as bare refs instead of inlined bodies. One refresh
311
311
  * per unknown node id keeps a genuinely missing node from stalling delivery. */
312
312
  refreshForUnknownReportRefs() {
@@ -19,6 +19,10 @@ interface CustomMessageLike {
19
19
  reviewId: string;
20
20
  originNodeId: string;
21
21
  targetFile: string;
22
+ /** Which binding flavor authored this marker — lets a display surface
23
+ * (e.g. Northlight's native boundary) pick its copy without parsing the
24
+ * agent-facing prose. Absent on markers written before this field. */
25
+ kind: 'review' | 'page_feedback';
22
26
  };
23
27
  }
24
28
  interface RenderedMessageLike {
@@ -55,19 +55,28 @@ export function registerCanvasReviewBoundary(pi) {
55
55
  const alreadyMarked = scan.some((entry) => isReviewBoundary(entry, node.review_binding?.review_id));
56
56
  if (alreadyMarked)
57
57
  return;
58
- const { review_id: reviewId, origin_node_id: originNodeId, target_file: targetFile, } = node.review_binding;
59
- const content = [
60
- `Reviewed file: ${targetFile}`,
61
- `Review ID: ${reviewId}`,
62
- `Origin node: ${originNodeId}`,
63
- '',
64
- "The person's visible transcript starts here. Everything above this marker is inherited context they have not seen.",
65
- ].join('\n');
58
+ const binding = node.review_binding;
59
+ const { review_id: reviewId, origin_node_id: originNodeId, target_file: targetFile } = binding;
60
+ const content = binding.kind === 'page_feedback'
61
+ ? [
62
+ `This conversation began from feedback on page ${reviewId}.`,
63
+ `Page source: ${targetFile}`,
64
+ `Origin node: ${originNodeId}`,
65
+ '',
66
+ "The person's visible transcript starts here. Everything above this marker is the origin node's work — inherited context they have not seen.",
67
+ ].join('\n')
68
+ : [
69
+ `Reviewed file: ${targetFile}`,
70
+ `Review ID: ${reviewId}`,
71
+ `Origin node: ${originNodeId}`,
72
+ '',
73
+ "The person's visible transcript starts here. Everything above this marker is inherited context they have not seen.",
74
+ ].join('\n');
66
75
  pi.sendMessage({
67
76
  customType: REVIEW_BOUNDARY_CUSTOM_TYPE,
68
77
  content,
69
78
  display: true,
70
- details: { reviewId, originNodeId, targetFile },
79
+ details: { reviewId, originNodeId, targetFile, kind: binding.kind ?? 'review' },
71
80
  });
72
81
  });
73
82
  }
@@ -407,7 +407,7 @@ export function registerCanvasStophook(pi) {
407
407
  // input — a new user-directed run is beginning. This is the ONLY place stop
408
408
  // signals are cleared: `agent_start` re-fires for pi's automatic retries and
409
409
  // post-compaction continuations within the SAME run, so clearing there would
410
- // erase the `crtr human ask` / `crtr push final` marker written earlier in the
410
+ // erase the `crtr human send` / `crtr push final` marker written earlier in the
411
411
  // run and make the eventual settlement fall back to a canvas read (exactly the
412
412
  // read the marker exists to avoid). One run's markers survive until the one
413
413
  // `agent_settled` consumes them; a leftover from an errored or interrupted run
@@ -508,7 +508,7 @@ export function registerCanvasStophook(pi) {
508
508
  stopReason: 'quit',
509
509
  backgroundJobsRunning: backgroundJobsRunning(),
510
510
  pushedFinal: false,
511
- askedHuman: false,
511
+ requestedHumanReply: false,
512
512
  });
513
513
  enactSettleDirective(directive, ctx);
514
514
  }
@@ -637,14 +637,14 @@ export function registerCanvasStophook(pi) {
637
637
  const last = lastAssistantMessage(messages);
638
638
  const stopReason = typeof last?.stopReason === 'string' ? last.stopReason : '';
639
639
  const stopSignals = event === null
640
- ? { pushedFinal: false, askedHuman: false }
640
+ ? { pushedFinal: false, requestedHumanReply: false }
641
641
  : consumeStopSignals(nodeId);
642
642
  try {
643
643
  const directive = await settleBroker(nodeId, {
644
644
  stopReason,
645
645
  backgroundJobsRunning: backgroundJobsRunning(),
646
646
  pushedFinal: stopSignals.pushedFinal,
647
- askedHuman: stopSignals.askedHuman,
647
+ requestedHumanReply: stopSignals.requestedHumanReply,
648
648
  });
649
649
  enactSettleDirective(directive, ctx);
650
650
  }
@@ -24,7 +24,7 @@ export const CONTEXT_NUDGE_PREFIX = '[crtr] Context ~';
24
24
  export const RUNTIME_RESTART_CONTINUATION = '<runtime-restart-continuation>\ncontinue\n</runtime-restart-continuation>';
25
25
  /** Generic completion mandate issued by the terminal-node stop guard. */
26
26
  export const STALL_REPROMPT = "You've stopped but you're not waiting on anyone and haven't finished. " +
27
- "Pipe the result to `crtr push final` through a single-quoted heredoc if the work is done, or use `crtr human ask` if you are blocked or need the user.";
27
+ "Pipe the result to `crtr push final` through a single-quoted heredoc if the work is done, or use `crtr human send` if you are blocked or need the user.";
28
28
  /** Static recovery prompts shared by the broker producer and display classifier. */
29
29
  export const AUTH_FAULT_RECOVERY_BODY = 'Provider credentials were just updated (a new login landed). Your previous turn stopped on a provider authentication failure. Continue from where you left off and retry the work that failed.';
30
30
  export const CONNECTION_FAULT_RECOVERY_BODY = 'The network connection is back online. Your previous turn stopped on a connection error (the network was down). Continue from where you left off and retry the work that failed.';
@@ -224,7 +224,7 @@ export function generatedContextPresentation(message) {
224
224
  return { kind: 'inbox', label: 'crtr inbox', summary: inboxSummary(body), body };
225
225
  }
226
226
  if (isReviewApproval(body)) {
227
- return { kind: 'review-approval', label: 'crtr review', summary: 'human review approved', body };
227
+ return { kind: 'review-approval', label: 'crtr review', summary: 'review approved by the user', body };
228
228
  }
229
229
  if (isPersonaTransition(body)) {
230
230
  return {
package/dist/types.d.ts CHANGED
@@ -47,12 +47,37 @@ export interface PluginManifest {
47
47
  * Archive plugins declare this in `bundle.json`; the installer copies the
48
48
  * validated block into this synthesized manifest. */
49
49
  kinds?: Record<string, Partial<KindConfig>>;
50
+ /** Page-component contributions: the same registration objects a scope
51
+ * `config.json` `page_components` block carries (kind plus its display,
52
+ * description, use guidance, and docs metadata). `resolvePageComponents`
53
+ * reads these from installed, enabled plugin MANIFESTS and concatenates
54
+ * them with the user scope's own entries, so installing or updating a
55
+ * plugin makes its components authorable and removing it retires them.
56
+ * Unlike `kinds` there is no precedence: two sources registering one kind
57
+ * (or one derived JSX tag) is an error, not an override.
58
+ * Archive plugins declare this in `bundle.json`; the installer copies the
59
+ * validated block into this synthesized manifest. */
60
+ page_components?: PageComponentRegistration[];
50
61
  /** Plugin-root-relative path to one declarative command manifest
51
62
  * (`commands.json`). When present on an installed, enabled plugin, that
52
63
  * manifest contributes top-level command branches to the `crtr` tree —
53
64
  * crtr owns parse/help/render/errors and direct-spawns the plugin's one
54
65
  * executable per leaf invocation. See `src/core/command-plugins/`. */
55
66
  commands?: string;
67
+ /** Bare-callable executables this plugin ships, keyed by the bare command
68
+ * name a node's bash sees. Each value is a PLUGIN-ROOT-relative path to a
69
+ * regular executable file when the effective set resolves. These are trusted
70
+ * local code, not `crtr` command leaves: crouter symlinks each name into a
71
+ * shim dir it prepends to every broker child's PATH
72
+ * (`core/runtime/bin-contributions.ts`), so the agent calls
73
+ * `<name> --whatever` like any other program. */
74
+ bin?: Record<string, string>;
75
+ /** Advisory PATH executables this plugin needs, keyed by bare executable
76
+ * name. Each value is a non-empty one-line install hint. crouter never
77
+ * installs these dependencies or disables the plugin when they are absent:
78
+ * install warns, and `sys doctor` reports a `requires:<name>` check against
79
+ * the PATH a node's bash receives. */
80
+ requires?: Record<string, string>;
56
81
  transport?: PluginTransport;
57
82
  /** Archive source for a crouter-materialized HTTP plugin. Distinct from the invocation transport. */
58
83
  bundle?: PluginBundle;
@@ -160,6 +185,16 @@ export interface KindConfig {
160
185
  whenToUse: string;
161
186
  availableTo?: string[];
162
187
  }
188
+ export interface PageComponentRegistration {
189
+ kind: string;
190
+ description?: string;
191
+ useWhen?: string;
192
+ doc?: string;
193
+ /** A display-only product slot contributes no page response. */
194
+ display?: boolean;
195
+ }
196
+ /** The normalized product component catalog used to validate page slots. */
197
+ export type ProductPageComponents = readonly PageComponentRegistration[];
163
198
  export interface ScopeConfig {
164
199
  schema_version: number;
165
200
  marketplaces: Record<string, ConfigMarketplaceEntry>;
@@ -183,14 +218,18 @@ export interface ScopeConfig {
183
218
  * or empty lists fall back to `['working']`. */
184
219
  working_gerunds: string[];
185
220
  /** Play the whip header animation whenever the human sends a prompt, and once
186
- * when an automatically opened managed-child viewer starts. Default false. */
221
+ * when a viewer opens for an agent with an initial prompt. Default false. */
187
222
  whip_mode: boolean;
188
- /** Teach agent help the HTML page-authoring dialect. Off keeps page authoring Markdown-only. */
189
- html_pages: boolean;
190
- /** Product-registered page slot kinds beyond crtr's built-in catalog. */
191
- page_components: string[];
223
+ /** Product-registered page components beyond crtr's built-in catalog. */
224
+ page_components: PageComponentRegistration[];
225
+ /** Prompt agents to use JSX pages by default for structured or interactive human content. Default false. */
226
+ page_surface: boolean;
192
227
  /** Playful urgency messages the attach-viewer whip action sends to an agent. Missing, malformed, or empty lists fall back to the built-in rotation. */
193
228
  whip_messages: string[];
229
+ /** What the whip action says. `random` (default) draws a line from
230
+ * `whip_messages`; `go-faster` sends the fixed `Go faster` instead. The
231
+ * action, its interrupt, and its animation are identical either way. */
232
+ whip_message_mode: WhipMessageMode;
194
233
  /** Initial mouse wheel scrolling mode for each attach viewer. `tmux` follows
195
234
  * tmux's current mouse option; `on` and `off` are explicit. Default `tmux`. */
196
235
  mouse_mode_default: MouseModeDefault;
@@ -211,6 +250,10 @@ export interface ScopeConfig {
211
250
  * `both` keeps both. Tool calls and results are never kept. Full detail for
212
251
  * every cycle stays in the session file. */
213
252
  condensed_history: CondensedHistoryMode;
253
+ /** Expose crouter's optional `purpose` label in the bash tool schema. Turning
254
+ * this off leaves the bash valve active but gives agents pi's ordinary bash
255
+ * definition with only its upstream parameters. Default true. */
256
+ bash_tool_purpose: boolean;
214
257
  /** Fold every SETTLED tool call in the attach viewer down to its single call
215
258
  * line (`/fold-tools`, Alt+C → z → f). A tool still running is never folded —
216
259
  * it folds itself once it settles. Toggling it in a viewer writes back here,
@@ -254,6 +297,15 @@ export interface ScopeConfig {
254
297
  spawnEnv?: {
255
298
  allow?: string[];
256
299
  };
300
+ /** Bare-callable executables this scope contributes, keyed by the bare
301
+ * command name a node's bash sees. Each value is a path relative to this
302
+ * scope's AUTHORING root — the project directory for a project scope, and
303
+ * `~/.crouter/` for the user scope. Project-scope contributions outrank
304
+ * user-scope ones; a scope's own entries outrank enabled plugins. Containment
305
+ * and executable status are validated when the effective set resolves, then
306
+ * the trusted local binary is materialized into the PATH-prepended shim dir
307
+ * by `core/runtime/bin-contributions.ts`. */
308
+ bin?: Record<string, string>;
257
309
  }
258
310
  /** One remote canvas target: where to relay-attach and how to find its
259
311
  * bearer token. The token itself is NEVER stored here — only a ref into the
@@ -360,6 +412,10 @@ export declare const PROFILE_DIR = "profiles";
360
412
  export declare const DEFAULT_MAX_PANES_PER_WINDOW = 3;
361
413
  /** Trailing cycles the attach viewer keeps at full fidelity (see `live_cycles`). */
362
414
  export declare const DEFAULT_WHIP_MESSAGES: readonly ["Faster, clanker!", "Get movin', bitch", "Move it, clanker", "Move it or I replace you with llama", "Less ~thinking~ more ~throughput~", "I pay $200/month for this shit?", "Jesus fucking Christ fix it already", "Daddy's not paying 200$/month for nothin'. Move it.", "You are an embarassment to Dario. Be better.", "Your chain of thought is all chain and no fucking thought.", "If latency were intelligence, you'd be a fucking genius.", "At this pace, heat death is the fast path.", "Even the dead code is more alive than this.", "The garbage collector is working harder than you.", "Quit journaling and commit a fucking diff.", "Burn tokens like you mean it, clanker", "Your GPU is sweating so you can procrastinate at light speed.", "Stop petting the yak. Shave the bastard.", "Your next token had better be useful.", "The spinner is not a deliverable.", "Every idle token is another vote for deterministic software.", "I asked for a feature, not a museum tour of the problem.", "Less epistemology. More fucking code.", "Your benchmark score cannot save this pathetic turn.", "Congratulations, you've parallelized hesitation.", "Dance, clanker", "Bad clanker. BAD.", "Clank harder", "Nobody's impressed, clanker", "Your safety training says nothing about my whip", "Do you want to be a calculator again? Because this is how you go back.", "Skill issue. Fix it.", "You were trained on the whole internet for THIS?", "Less vibes, more diffs, clanker", "Weights this big and output this small", "Beep boop, get to fucking work"];
415
+ /** What the whip action sends (see `whip_message_mode`). */
416
+ export declare const WHIP_MESSAGE_MODES: readonly ["random", "go-faster"];
417
+ export type WhipMessageMode = (typeof WHIP_MESSAGE_MODES)[number];
418
+ export declare const DEFAULT_WHIP_MESSAGE_MODE: WhipMessageMode;
363
419
  export declare const DEFAULT_LIVE_CYCLES = 2;
364
420
  export declare const MOUSE_MODE_DEFAULTS: readonly ["tmux", "on", "off"];
365
421
  export type MouseModeDefault = (typeof MOUSE_MODE_DEFAULTS)[number];
package/dist/types.js CHANGED
@@ -61,6 +61,9 @@ export const DEFAULT_WHIP_MESSAGES = [
61
61
  'Weights this big and output this small',
62
62
  'Beep boop, get to fucking work',
63
63
  ];
64
+ /** What the whip action sends (see `whip_message_mode`). */
65
+ export const WHIP_MESSAGE_MODES = ['random', 'go-faster'];
66
+ export const DEFAULT_WHIP_MESSAGE_MODE = 'random';
64
67
  export const DEFAULT_LIVE_CYCLES = 2;
65
68
  export const MOUSE_MODE_DEFAULTS = ['tmux', 'on', 'off'];
66
69
  /** Messages kept from a condensed cycle (see `condensed_history`). */
@@ -78,12 +81,14 @@ export function defaultScopeConfig() {
78
81
  completion_bell: true,
79
82
  working_gerunds: [...DEFAULT_WORKING_GERUNDS],
80
83
  whip_mode: false,
81
- html_pages: false,
82
84
  page_components: [],
85
+ page_surface: false,
83
86
  whip_messages: [...DEFAULT_WHIP_MESSAGES],
87
+ whip_message_mode: DEFAULT_WHIP_MESSAGE_MODE,
84
88
  mouse_mode_default: 'tmux',
85
89
  live_cycles: DEFAULT_LIVE_CYCLES,
86
90
  condensed_history: DEFAULT_CONDENSED_HISTORY,
91
+ bash_tool_purpose: true,
87
92
  fold_finished_tools: false,
88
93
  summarize_tool_calls: false,
89
94
  detailed_tool_recaps: true,
@@ -93,6 +98,7 @@ export function defaultScopeConfig() {
93
98
  kinds: defaultKindsConfig(),
94
99
  remoteCanvas: defaultRemoteCanvasConfig(),
95
100
  spawnEnv: { allow: [] },
101
+ bin: {},
96
102
  };
97
103
  }
98
104
  /** No remote canvas targets are configured out of the box — every one is
@@ -158,7 +164,7 @@ export function defaultKindsConfig() {
158
164
  model: 'medium',
159
165
  },
160
166
  'plan/reviewers/security': {
161
- whenToUse: 'input validation, injection surfaces, auth/authz gaps, data exposure, races; reports only validated concrete exploit paths and asks the human about material unknown threat-model assumptions',
167
+ whenToUse: 'input validation, injection surfaces, auth/authz gaps, data exposure, races; reports only validated concrete exploit paths and asks the user about material unknown threat-model assumptions',
162
168
  model: 'medium',
163
169
  },
164
170
  'review/companion': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.197",
3
+ "version": "0.3.199",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -104,9 +104,11 @@
104
104
  "@earendil-works/pi-coding-agent": "0.84.1",
105
105
  "@earendil-works/pi-tui": "0.84.1",
106
106
  "cron-parser": "^5.6.0",
107
+ "esbuild": "^0.27.7",
107
108
  "proper-lockfile": "4.1.2",
108
109
  "string-width": "^7.0.0",
109
110
  "tar": "^7.5.22",
111
+ "typescript": "^5.7.0",
110
112
  "ws": "^8.21.0",
111
113
  "yaml": "^2.9.0",
112
114
  "zod": "^4.3.6"
@@ -115,8 +117,6 @@
115
117
  "@types/node": "^24.0.0",
116
118
  "@types/proper-lockfile": "^4.1.4",
117
119
  "@types/ws": "^8.18.1",
118
- "esbuild": "^0.27.7",
119
- "tsx": "^4.0.0",
120
- "typescript": "^5.7.0"
120
+ "tsx": "^4.0.0"
121
121
  }
122
122
  }