@north-light/crouter 0.3.192 → 0.3.194

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 (190) hide show
  1. package/dist/api/client.d.ts +31 -10
  2. package/dist/api/client.js +83 -24
  3. package/dist/api/dto/bash-jobs.d.ts +24 -0
  4. package/dist/api/dto/bash-jobs.js +9 -0
  5. package/dist/api/dto/human.d.ts +3 -4
  6. package/dist/api/dto/inbox.d.ts +106 -65
  7. package/dist/api/dto/inbox.js +2 -8
  8. package/dist/api/index.d.ts +1 -0
  9. package/dist/api/index.js +1 -0
  10. package/dist/api/routes.d.ts +7 -0
  11. package/dist/api/routes.js +7 -0
  12. package/dist/builtin-memory/02-lifecycle/01-resident.md +1 -1
  13. package/dist/builtin-memory/internal/examples/imessage-assistant.md +1 -1
  14. package/dist/builtin-memory/internal/nodes-and-canvas.md +2 -2
  15. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  16. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/serial/provider-rotation.test.ts +101 -3
  17. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +6 -2
  18. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +68 -14
  19. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +87 -13
  20. package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +2 -2
  21. package/dist/clients/attach/__tests__/group-activity.test.js +2 -2
  22. package/dist/clients/attach/chrome/canvas-panels.d.ts +1 -1
  23. package/dist/clients/attach/chrome/canvas-panels.js +1 -1
  24. package/dist/clients/attach/input/overlay-owner.js +1 -1
  25. package/dist/clients/attach/overlays/auth.js +34 -8
  26. package/dist/clients/attach/overlays/pickers.d.ts +5 -8
  27. package/dist/clients/attach/overlays/pickers.js +37 -43
  28. package/dist/clients/attach/viewer.js +643 -620
  29. package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +38 -128
  30. package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +72 -148
  31. package/dist/clients/inbox/controller.d.ts +2 -3
  32. package/dist/clients/inbox/controller.js +41 -42
  33. package/dist/clients/inbox/page-adapter.d.ts +33 -0
  34. package/dist/clients/inbox/page-adapter.js +54 -0
  35. package/dist/clients/inbox/resolve.d.ts +2 -2
  36. package/dist/clients/inbox/resolve.js +1 -1
  37. package/dist/clients/inbox/review-visibility.d.ts +1 -1
  38. package/dist/clients/inbox/review-visibility.js +1 -1
  39. package/dist/clients/inbox/tui/ansi.d.ts +1 -1
  40. package/dist/clients/inbox/tui/ansi.js +2 -2
  41. package/dist/clients/inbox/tui/input.d.ts +1 -3
  42. package/dist/clients/inbox/tui/input.js +242 -553
  43. package/dist/clients/inbox/tui/panel.d.ts +3 -1
  44. package/dist/clients/inbox/tui/panel.js +59 -187
  45. package/dist/clients/inbox/tui/render.d.ts +0 -25
  46. package/dist/clients/inbox/tui/render.js +165 -416
  47. package/dist/clients/inbox/tui/slots.d.ts +32 -0
  48. package/dist/clients/inbox/tui/slots.js +156 -0
  49. package/dist/clients/inbox/tui/types.d.ts +25 -13
  50. package/dist/clients/inbox/tui.js +5 -3
  51. package/dist/commands/attention.js +2 -2
  52. package/dist/commands/human/doc.js +66 -20
  53. package/dist/commands/human/prompts.d.ts +3 -3
  54. package/dist/commands/human/prompts.js +158 -112
  55. package/dist/commands/human/queue.d.ts +0 -1
  56. package/dist/commands/human/queue.js +34 -94
  57. package/dist/commands/human/shared.d.ts +10 -4
  58. package/dist/commands/human/shared.js +62 -13
  59. package/dist/commands/human.js +8 -4
  60. package/dist/commands/memory/read.js +13 -5
  61. package/dist/commands/node/bash.js +18 -30
  62. package/dist/commands/node/lifecycle.js +2 -2
  63. package/dist/commands/surface/node/focus.js +1 -1
  64. package/dist/core/__tests__/human-deliver.test.js +40 -46
  65. package/dist/core/__tests__/seam/broker-attach-stream.test.js +7 -1
  66. package/dist/core/__tests__/seam/dormancy-release.test.js +64 -10
  67. package/dist/core/__tests__/serial/human-deliver-e2e.test.js +40 -31
  68. package/dist/core/__tests__/serial/spawn-root.test.js +3 -1
  69. package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +3 -1
  70. package/dist/core/auth-interaction.d.ts +2 -2
  71. package/dist/core/bash-jobs.d.ts +43 -0
  72. package/dist/core/bash-jobs.js +96 -2
  73. package/dist/core/canvas/__tests__/attention.test.js +16 -10
  74. package/dist/core/canvas/__tests__/render-remote.test.js +9 -3
  75. package/dist/core/canvas/attention.js +1 -1
  76. package/dist/core/canvas/browse/app.js +1 -1
  77. package/dist/core/canvas/nav-model.d.ts +1 -1
  78. package/dist/core/canvas/nav-model.js +1 -1
  79. package/dist/core/canvas/remote-canvas-source.js +1 -1
  80. package/dist/core/canvas/types.d.ts +2 -1
  81. package/dist/core/config.js +4 -1
  82. package/dist/core/human/__tests__/page-render.test.d.ts +1 -0
  83. package/dist/core/human/__tests__/page-render.test.js +49 -0
  84. package/dist/core/human/__tests__/page-tickets.test.d.ts +1 -0
  85. package/dist/core/human/__tests__/page-tickets.test.js +112 -0
  86. package/dist/core/human/__tests__/page.test.d.ts +1 -0
  87. package/dist/core/human/__tests__/page.test.js +139 -0
  88. package/dist/core/human/__tests__/serial/inbox-core.test.js +42 -32
  89. package/dist/core/human/claim.js +2 -2
  90. package/dist/core/human/convention.d.ts +4 -4
  91. package/dist/core/human/convention.js +6 -13
  92. package/dist/core/human/markdown-html.d.ts +8 -0
  93. package/dist/core/human/markdown-html.js +581 -0
  94. package/dist/core/human/page-catalog.d.ts +6 -0
  95. package/dist/core/human/page-catalog.js +43 -0
  96. package/dist/core/human/page-render.d.ts +3 -0
  97. package/dist/core/human/page-render.js +131 -0
  98. package/dist/core/human/page-schema.d.ts +312 -0
  99. package/dist/core/human/page-schema.js +291 -0
  100. package/dist/core/human/page-synth.d.ts +9 -0
  101. package/dist/core/human/page-synth.js +17 -0
  102. package/dist/core/human/page.d.ts +22 -0
  103. package/dist/core/human/page.js +427 -0
  104. package/dist/core/human/review-schema.d.ts +19 -0
  105. package/dist/core/human/review-schema.js +34 -0
  106. package/dist/core/human/scan.d.ts +12 -3
  107. package/dist/core/human/scan.js +69 -26
  108. package/dist/core/human/summary.d.ts +5 -6
  109. package/dist/core/human/summary.js +54 -37
  110. package/dist/core/human/tickets.d.ts +61 -11
  111. package/dist/core/human/tickets.js +100 -57
  112. package/dist/core/human/types.d.ts +21 -66
  113. package/dist/core/human/types.js +1 -5
  114. package/dist/core/inspector/model.d.ts +5 -0
  115. package/dist/core/inspector/model.js +5 -0
  116. package/dist/core/inspector/text.js +2 -2
  117. package/dist/core/inspector/tui.js +10 -3
  118. package/dist/core/keybindings/catalog.d.ts +1 -1
  119. package/dist/core/keybindings/catalog.js +4 -2
  120. package/dist/core/preview-registry.js +0 -1
  121. package/dist/core/review/ticket-filter.d.ts +1 -1
  122. package/dist/core/review/ticket-filter.js +2 -8
  123. package/dist/core/runtime/broker/read-ops.js +3 -0
  124. package/dist/core/runtime/broker.js +19 -10
  125. package/dist/core/runtime/package-health.js +1 -1
  126. package/dist/core/runtime/persona.js +2 -1
  127. package/dist/core/runtime/tmux-bindings.js +8 -1
  128. package/dist/core/termrender/termrender.d.ts +1 -1
  129. package/dist/core/termrender/termrender.js +2 -2
  130. package/dist/core/termrender/version.d.ts +1 -1
  131. package/dist/core/termrender/version.js +1 -1
  132. package/dist/core/tui/page-host.d.ts +1 -1
  133. package/dist/core/tui/page-host.js +2 -2
  134. package/dist/core/user-settings.d.ts +19 -0
  135. package/dist/core/user-settings.js +22 -0
  136. package/dist/daemon/api/handlers/bash-jobs.d.ts +2 -0
  137. package/dist/daemon/api/handlers/bash-jobs.js +90 -0
  138. package/dist/daemon/api/handlers/human.js +16 -14
  139. package/dist/daemon/api/handlers/inbox.js +311 -227
  140. package/dist/daemon/api/router.d.ts +5 -1
  141. package/dist/daemon/api/router.js +5 -0
  142. package/dist/daemon/api/server.js +2 -0
  143. package/dist/daemon/cron-run.js +23 -10
  144. package/dist/daemon/human/finish.d.ts +8 -3
  145. package/dist/daemon/human/finish.js +35 -15
  146. package/dist/daemon/reconcilers/broker-supervision.js +7 -1
  147. package/dist/daemon/reconcilers/dormant-inbox.js +8 -0
  148. package/dist/daemon/reconcilers/live-obligation.d.ts +2 -0
  149. package/dist/daemon/reconcilers/live-obligation.js +11 -0
  150. package/dist/pages/bundle.css +1 -0
  151. package/dist/pages/bundle.js +1347 -0
  152. package/dist/pages/comments.d.ts +34 -0
  153. package/dist/pages/comments.js +72 -0
  154. package/dist/pages/elements/cards.d.ts +17 -0
  155. package/dist/pages/elements/cards.js +785 -0
  156. package/dist/pages/elements/chart.d.ts +1 -0
  157. package/dist/pages/elements/chart.js +648 -0
  158. package/dist/pages/elements/options.d.ts +21 -0
  159. package/dist/pages/elements/options.js +604 -0
  160. package/dist/pages/elements/pages.d.ts +24 -0
  161. package/dist/pages/elements/pages.js +355 -0
  162. package/dist/pages/elements/slot.d.ts +21 -0
  163. package/dist/pages/elements/slot.js +105 -0
  164. package/dist/pages/elements/table.d.ts +19 -0
  165. package/dist/pages/elements/table.js +906 -0
  166. package/dist/pages/elements/text.d.ts +25 -0
  167. package/dist/pages/elements/text.js +951 -0
  168. package/dist/pages/entry.d.ts +8 -0
  169. package/dist/pages/entry.js +8 -0
  170. package/dist/pages/host.d.ts +132 -0
  171. package/dist/pages/host.js +149 -0
  172. package/dist/pages/register.d.ts +2 -0
  173. package/dist/pages/register.js +3 -0
  174. package/dist/pages/slot-config.d.ts +36 -0
  175. package/dist/pages/slot-config.js +50 -0
  176. package/dist/pages/types.d.ts +98 -0
  177. package/dist/pages/types.js +23 -0
  178. package/dist/pi-extensions/canvas-bash-valve.d.ts +4 -2
  179. package/dist/pi-extensions/canvas-bash-valve.js +25 -7
  180. package/dist/types.d.ts +4 -0
  181. package/dist/types.js +2 -0
  182. package/package.json +8 -8
  183. package/runtime.lock.json +156 -101
  184. package/scripts/install-runtime.mjs +172 -5
  185. package/dist/clients/inbox/deck-adapter.d.ts +0 -29
  186. package/dist/clients/inbox/deck-adapter.js +0 -63
  187. package/dist/core/human/deck-factories.d.ts +0 -8
  188. package/dist/core/human/deck-factories.js +0 -15
  189. package/dist/core/human/deck-schema.d.ts +0 -70
  190. package/dist/core/human/deck-schema.js +0 -92
@@ -0,0 +1,8 @@
1
+ import './bundle.css';
2
+ import './elements/options.js';
3
+ import './elements/text.js';
4
+ import './elements/table.js';
5
+ import './elements/cards.js';
6
+ import './elements/chart.js';
7
+ import './elements/pages.js';
8
+ import './elements/slot.js';
@@ -0,0 +1,8 @@
1
+ import './bundle.css';
2
+ import './elements/options.js';
3
+ import './elements/text.js';
4
+ import './elements/table.js';
5
+ import './elements/cards.js';
6
+ import './elements/chart.js';
7
+ import './elements/pages.js';
8
+ import './elements/slot.js';
@@ -0,0 +1,132 @@
1
+ /**
2
+ * `window.crtr` — the one door out of a page.
3
+ *
4
+ * An element renders inside a host (Northlight's srcdoc host today, a crtrd-served
5
+ * page later) that installs this bridge before the bundle loads. Everything an element
6
+ * needs from the outside world arrives through it: there is no `fetch`, no `XMLHttpRequest`,
7
+ * no direct network access anywhere under `src/pages/`. An element that cannot get what it
8
+ * needs renders a named unavailable state; it never falls back to reaching out itself.
9
+ *
10
+ * Two things the bridge deliberately does NOT have:
11
+ *
12
+ * - **No `save` and no `dismiss`.** Both are host chrome — a frame around the page, not
13
+ * markup inside it — so they are the host's own UI actions and are never advertised to
14
+ * page code.
15
+ * - **No per-element resolution.** `respond()` is a local-state write plus a request that
16
+ * the host autosave the partial map; it does not resolve the ticket. Only the pager's
17
+ * submit affordance calls `submit()`, which posts the complete map once.
18
+ *
19
+ * The host is optional at the type level (`window.crtr?`) because a page can be opened
20
+ * bare — dropped in a browser, or rendered by a host that installs nothing. Elements read
21
+ * the bridge through the accessors below rather than touching `window.crtr` directly, so
22
+ * that absence is typed and total instead of an `as any` at each call site.
23
+ */
24
+ import type { SlotResponse } from './types.js';
25
+ /** Live host theme tokens, as `--crtr-*` custom-property names mapped to values. */
26
+ export interface CrtrThemeBridge {
27
+ current(): Record<string, string>;
28
+ /** Registers `listener` for later theme changes; returns its unsubscribe. */
29
+ subscribe(listener: (tokens: Record<string, string>) => void): () => void;
30
+ }
31
+ /** Data behind a `source`-bearing config (table, cards, chart). */
32
+ export interface CrtrArtifactBridge {
33
+ /** Resolves the already-parsed data for `source`, or rejects when it is unreachable. */
34
+ data(source: string): Promise<unknown>;
35
+ }
36
+ /** What `mountWorkspaceComponent` can answer: the host rendered it, or it has no renderer. */
37
+ export type WorkspaceMountOutcome = 'mounted' | 'unavailable';
38
+ /** The bridge a host installs at `window.crtr`. */
39
+ export interface CrtrHost {
40
+ /**
41
+ * Records this slot's complete current response and asks the host to autosave the
42
+ * partial map. Called on every user change. NOT ticket resolution.
43
+ */
44
+ respond(slotId: string, response: SlotResponse): void;
45
+ /** Posts the complete response map once. Only the pager's submit affordance calls it. */
46
+ submit(): Promise<void>;
47
+ /** Flushes the pending autosave of the partial map. */
48
+ progress(): Promise<void>;
49
+ readonly theme: CrtrThemeBridge;
50
+ readonly artifact: CrtrArtifactBridge;
51
+ /**
52
+ * Asks the host to render a product-registered (`unvalidated`) slot into `element`.
53
+ * A host with no renderer for `kind` answers `'unavailable'`.
54
+ */
55
+ mountWorkspaceComponent(element: HTMLElement, kind: string, config: Record<string, unknown>): Promise<WorkspaceMountOutcome>;
56
+ }
57
+ declare global {
58
+ interface Window {
59
+ /**
60
+ * Installed by the host before the bundle loads. Optional: a page opened bare has
61
+ * no bridge, and every element must still render.
62
+ */
63
+ crtr?: CrtrHost;
64
+ }
65
+ }
66
+ /**
67
+ * The outcome of a bridge call. `unavailable` means the door is not there — no host, or a
68
+ * host that does not implement this method. `failed` means the host was asked and said no,
69
+ * and `reason` is what it said. Elements distinguish them because "this page is not hosted"
70
+ * and "the server rejected your answer" are different things to show a person.
71
+ */
72
+ export type HostCall = {
73
+ status: 'ok';
74
+ } | {
75
+ status: 'unavailable';
76
+ reason: string;
77
+ } | {
78
+ status: 'failed';
79
+ reason: string;
80
+ };
81
+ /** The outcome of an artifact read, with the same three-way split as `HostCall`. */
82
+ export type ArtifactData = {
83
+ status: 'ok';
84
+ data: unknown;
85
+ } | {
86
+ status: 'unavailable';
87
+ reason: string;
88
+ } | {
89
+ status: 'failed';
90
+ reason: string;
91
+ };
92
+ /** The installed bridge, or `undefined` on a bare page. */
93
+ export declare function host(): CrtrHost | undefined;
94
+ /**
95
+ * Writes this slot's complete current response to the host's in-frame map and requests an
96
+ * autosave. Elements call this on every user change with the whole response object, never a
97
+ * delta. Safe on a bare page: the element's own local state is authoritative for rendering,
98
+ * so an unavailable host only means the answer is not being persisted.
99
+ */
100
+ export declare function respond(slotId: string, response: SlotResponse): HostCall;
101
+ /**
102
+ * Posts the complete response map once, resolving the ticket. Only the pager's submit
103
+ * affordance calls this.
104
+ */
105
+ export declare function submit(): Promise<HostCall>;
106
+ /** Flushes the host's pending autosave of the partial map. Never resolves the ticket. */
107
+ export declare function progress(): Promise<HostCall>;
108
+ /**
109
+ * The host's current theme token values. Empty on a bare page — which is correct and needs
110
+ * no handling, because `bundle.css` declares every `--crtr-*` token at zero specificity and
111
+ * elements style through those custom properties rather than through this map. Read it only
112
+ * when a value must reach somewhere CSS cannot, such as an SVG attribute.
113
+ */
114
+ export declare function themeTokens(): Record<string, string>;
115
+ /**
116
+ * Subscribes to later theme changes. Always returns an unsubscribe, so a caller can wire it
117
+ * to `disconnectedCallback` without checking whether a host was there.
118
+ */
119
+ export declare function subscribeTheme(listener: (tokens: Record<string, string>) => void): () => void;
120
+ /**
121
+ * Reads the data behind a `source`-bearing config. The host resolves the reference and hands
122
+ * back parsed data; an element never resolves a reference itself and never fetches a URL.
123
+ * Anything other than `'ok'` is a named unavailable state to render, not a reason to retry
124
+ * some other way.
125
+ */
126
+ export declare function artifactData(source: string): Promise<ArtifactData>;
127
+ /**
128
+ * Asks the host to render a product-registered slot into `element`. A bare page, a host with
129
+ * no registry, and a host whose mount threw all answer `'unavailable'` — the escape element's
130
+ * named unavailable state is the only other outcome.
131
+ */
132
+ export declare function mountWorkspaceComponent(element: HTMLElement, kind: string, config: Record<string, unknown>): Promise<WorkspaceMountOutcome>;
@@ -0,0 +1,149 @@
1
+ /**
2
+ * `window.crtr` — the one door out of a page.
3
+ *
4
+ * An element renders inside a host (Northlight's srcdoc host today, a crtrd-served
5
+ * page later) that installs this bridge before the bundle loads. Everything an element
6
+ * needs from the outside world arrives through it: there is no `fetch`, no `XMLHttpRequest`,
7
+ * no direct network access anywhere under `src/pages/`. An element that cannot get what it
8
+ * needs renders a named unavailable state; it never falls back to reaching out itself.
9
+ *
10
+ * Two things the bridge deliberately does NOT have:
11
+ *
12
+ * - **No `save` and no `dismiss`.** Both are host chrome — a frame around the page, not
13
+ * markup inside it — so they are the host's own UI actions and are never advertised to
14
+ * page code.
15
+ * - **No per-element resolution.** `respond()` is a local-state write plus a request that
16
+ * the host autosave the partial map; it does not resolve the ticket. Only the pager's
17
+ * submit affordance calls `submit()`, which posts the complete map once.
18
+ *
19
+ * The host is optional at the type level (`window.crtr?`) because a page can be opened
20
+ * bare — dropped in a browser, or rendered by a host that installs nothing. Elements read
21
+ * the bridge through the accessors below rather than touching `window.crtr` directly, so
22
+ * that absence is typed and total instead of an `as any` at each call site.
23
+ */
24
+ /** The installed bridge, or `undefined` on a bare page. */
25
+ export function host() {
26
+ return typeof window === 'undefined' ? undefined : window.crtr;
27
+ }
28
+ function reasonOf(error) {
29
+ if (error instanceof Error && error.message !== '')
30
+ return error.message;
31
+ const text = String(error);
32
+ return text === '' ? 'unknown error' : text;
33
+ }
34
+ function missing(method) {
35
+ return { status: 'unavailable', reason: `host bridge does not provide ${method}()` };
36
+ }
37
+ /**
38
+ * Writes this slot's complete current response to the host's in-frame map and requests an
39
+ * autosave. Elements call this on every user change with the whole response object, never a
40
+ * delta. Safe on a bare page: the element's own local state is authoritative for rendering,
41
+ * so an unavailable host only means the answer is not being persisted.
42
+ */
43
+ export function respond(slotId, response) {
44
+ const bridge = host();
45
+ if (typeof bridge?.respond !== 'function')
46
+ return missing('respond');
47
+ try {
48
+ bridge.respond(slotId, response);
49
+ return { status: 'ok' };
50
+ }
51
+ catch (error) {
52
+ return { status: 'failed', reason: reasonOf(error) };
53
+ }
54
+ }
55
+ /**
56
+ * Posts the complete response map once, resolving the ticket. Only the pager's submit
57
+ * affordance calls this.
58
+ */
59
+ export async function submit() {
60
+ const bridge = host();
61
+ if (typeof bridge?.submit !== 'function')
62
+ return missing('submit');
63
+ try {
64
+ await bridge.submit();
65
+ return { status: 'ok' };
66
+ }
67
+ catch (error) {
68
+ return { status: 'failed', reason: reasonOf(error) };
69
+ }
70
+ }
71
+ /** Flushes the host's pending autosave of the partial map. Never resolves the ticket. */
72
+ export async function progress() {
73
+ const bridge = host();
74
+ if (typeof bridge?.progress !== 'function')
75
+ return missing('progress');
76
+ try {
77
+ await bridge.progress();
78
+ return { status: 'ok' };
79
+ }
80
+ catch (error) {
81
+ return { status: 'failed', reason: reasonOf(error) };
82
+ }
83
+ }
84
+ /**
85
+ * The host's current theme token values. Empty on a bare page — which is correct and needs
86
+ * no handling, because `bundle.css` declares every `--crtr-*` token at zero specificity and
87
+ * elements style through those custom properties rather than through this map. Read it only
88
+ * when a value must reach somewhere CSS cannot, such as an SVG attribute.
89
+ */
90
+ export function themeTokens() {
91
+ const bridge = host();
92
+ if (typeof bridge?.theme?.current !== 'function')
93
+ return {};
94
+ try {
95
+ return bridge.theme.current();
96
+ }
97
+ catch {
98
+ return {};
99
+ }
100
+ }
101
+ /**
102
+ * Subscribes to later theme changes. Always returns an unsubscribe, so a caller can wire it
103
+ * to `disconnectedCallback` without checking whether a host was there.
104
+ */
105
+ export function subscribeTheme(listener) {
106
+ const bridge = host();
107
+ if (typeof bridge?.theme?.subscribe !== 'function')
108
+ return () => { };
109
+ try {
110
+ const unsubscribe = bridge.theme.subscribe(listener);
111
+ return typeof unsubscribe === 'function' ? unsubscribe : () => { };
112
+ }
113
+ catch {
114
+ return () => { };
115
+ }
116
+ }
117
+ /**
118
+ * Reads the data behind a `source`-bearing config. The host resolves the reference and hands
119
+ * back parsed data; an element never resolves a reference itself and never fetches a URL.
120
+ * Anything other than `'ok'` is a named unavailable state to render, not a reason to retry
121
+ * some other way.
122
+ */
123
+ export async function artifactData(source) {
124
+ const bridge = host();
125
+ if (typeof bridge?.artifact?.data !== 'function')
126
+ return missing('artifact.data');
127
+ try {
128
+ return { status: 'ok', data: await bridge.artifact.data(source) };
129
+ }
130
+ catch (error) {
131
+ return { status: 'failed', reason: reasonOf(error) };
132
+ }
133
+ }
134
+ /**
135
+ * Asks the host to render a product-registered slot into `element`. A bare page, a host with
136
+ * no registry, and a host whose mount threw all answer `'unavailable'` — the escape element's
137
+ * named unavailable state is the only other outcome.
138
+ */
139
+ export async function mountWorkspaceComponent(element, kind, config) {
140
+ const bridge = host();
141
+ if (typeof bridge?.mountWorkspaceComponent !== 'function')
142
+ return 'unavailable';
143
+ try {
144
+ return await bridge.mountWorkspaceComponent(element, kind, config);
145
+ }
146
+ catch {
147
+ return 'unavailable';
148
+ }
149
+ }
@@ -0,0 +1,2 @@
1
+ export type CrtrElementName = `crtr-${string}`;
2
+ export declare function registerElement(name: CrtrElementName, element: CustomElementConstructor): void;
@@ -0,0 +1,3 @@
1
+ export function registerElement(name, element) {
2
+ customElements.define(name, element);
3
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Reading a slot's config out of the DOM.
3
+ *
4
+ * The host renders each slot as
5
+ * `<crtr-KIND slot-id="ID"><script type="application/json">CONFIG_JSON</script></crtr-KIND>`,
6
+ * and every element does the same three things in `connectedCallback`: find that script child,
7
+ * parse it, and render a named error state instead of throwing when it is missing or malformed.
8
+ * That is one behaviour, so it lives here once.
9
+ *
10
+ * **What is and is not checked.** The JSON's *shape* was already validated by
11
+ * `validatePageManifest` before the manifest was stored, and the renderer emits from that
12
+ * validated manifest — so an element may trust the parsed object against its own config type.
13
+ * What is not guaranteed is that the script child is there at all and that its text parses: a
14
+ * page can be hand-written, truncated, or rendered by something else. Those two failures are
15
+ * what this module reports.
16
+ */
17
+ /** A parsed config, or the reason the element should render an error state instead. */
18
+ export type SlotConfigResult<T> = {
19
+ status: 'ok';
20
+ config: T;
21
+ } | {
22
+ status: 'invalid';
23
+ reason: string;
24
+ };
25
+ /**
26
+ * Reads and parses the JSON script child of `element`.
27
+ *
28
+ * `T` is the element's own mirrored config type and is asserted, not verified — see the note
29
+ * above on why that is sound. Never throws.
30
+ */
31
+ export declare function readSlotConfig<T = Record<string, unknown>>(element: HTMLElement): SlotConfigResult<T>;
32
+ /**
33
+ * The slot id the host addressed this element by — the key `respond()` takes. Absent on a
34
+ * display-only slot, which has no id and produces no response.
35
+ */
36
+ export declare function readSlotId(element: HTMLElement): string | undefined;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Reading a slot's config out of the DOM.
3
+ *
4
+ * The host renders each slot as
5
+ * `<crtr-KIND slot-id="ID"><script type="application/json">CONFIG_JSON</script></crtr-KIND>`,
6
+ * and every element does the same three things in `connectedCallback`: find that script child,
7
+ * parse it, and render a named error state instead of throwing when it is missing or malformed.
8
+ * That is one behaviour, so it lives here once.
9
+ *
10
+ * **What is and is not checked.** The JSON's *shape* was already validated by
11
+ * `validatePageManifest` before the manifest was stored, and the renderer emits from that
12
+ * validated manifest — so an element may trust the parsed object against its own config type.
13
+ * What is not guaranteed is that the script child is there at all and that its text parses: a
14
+ * page can be hand-written, truncated, or rendered by something else. Those two failures are
15
+ * what this module reports.
16
+ */
17
+ /**
18
+ * Reads and parses the JSON script child of `element`.
19
+ *
20
+ * `T` is the element's own mirrored config type and is asserted, not verified — see the note
21
+ * above on why that is sound. Never throws.
22
+ */
23
+ export function readSlotConfig(element) {
24
+ const script = element.querySelector('script[type="application/json"]');
25
+ if (script === null)
26
+ return { status: 'invalid', reason: 'slot has no application/json config' };
27
+ const text = script.textContent ?? '';
28
+ if (text.trim() === '')
29
+ return { status: 'invalid', reason: 'slot config is empty' };
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(text);
33
+ }
34
+ catch (error) {
35
+ const detail = error instanceof Error && error.message !== '' ? error.message : 'invalid JSON';
36
+ return { status: 'invalid', reason: `slot config is not valid JSON: ${detail}` };
37
+ }
38
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
39
+ return { status: 'invalid', reason: 'slot config must be a JSON object' };
40
+ }
41
+ return { status: 'ok', config: parsed };
42
+ }
43
+ /**
44
+ * The slot id the host addressed this element by — the key `respond()` takes. Absent on a
45
+ * display-only slot, which has no id and produces no response.
46
+ */
47
+ export function readSlotId(element) {
48
+ const id = element.getAttribute('slot-id')?.trim();
49
+ return id === undefined || id === '' ? undefined : id;
50
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The response contract the page bundle produces, as plain browser-safe types.
3
+ *
4
+ * These mirror `src/core/human/page-schema.ts` field-for-field — `commentSchema`,
5
+ * `commentAnchorSchema`, and the four `*ResponseSchema`s — and are deliberately a
6
+ * separate declaration rather than an import: nothing under `src/pages/` may reach
7
+ * a runtime package (zod) or a module outside this directory, because the bundle is
8
+ * a browser IIFE. The schemas remain the source of truth; a change there is a change
9
+ * that must land here in the same commit.
10
+ *
11
+ * Two rules the shapes are built around:
12
+ *
13
+ * 1. **One comment shape everywhere.** Every response-bearing element carries
14
+ * comments, and every comment is `{ id, anchor, text }`. A comment on an option,
15
+ * a table row, a card, or a range of prose differs only in its `anchor`.
16
+ * 2. **Selection is ids, never indexes or values.** The agent authored the ids it is
17
+ * asking about, so the response keys straight off them.
18
+ *
19
+ * Config types are NOT here: a config is one element's private input, mirrored in that
20
+ * element's own module. A response is what crosses `window.crtr.respond`, so it is
21
+ * shared.
22
+ */
23
+ /** What a comment is attached to. One union across every element. */
24
+ export type CommentAnchor =
25
+ /** The presented content as a whole. */
26
+ {
27
+ kind: 'whole';
28
+ }
29
+ /** One option of an `options` element. */
30
+ | {
31
+ kind: 'option';
32
+ optionId: string;
33
+ }
34
+ /** One row of a `table`. */
35
+ | {
36
+ kind: 'row';
37
+ rowId: string;
38
+ }
39
+ /** One column of a `table`. */
40
+ | {
41
+ kind: 'column';
42
+ columnId: string;
43
+ }
44
+ /** One card of a `cards` grid. */
45
+ | {
46
+ kind: 'card';
47
+ cardId: string;
48
+ }
49
+ /**
50
+ * A character range of a `text` element's final text, review-style. `quote` is the
51
+ * text as it read when the comment was left, so the comment survives (readably) an
52
+ * edit that moves the offsets.
53
+ */
54
+ | {
55
+ kind: 'range';
56
+ start: number;
57
+ end: number;
58
+ quote: string;
59
+ };
60
+ /** A user comment. The same shape in every element's response. */
61
+ export type Comment = {
62
+ /** Stable per comment; frame-local, minted by `newCommentId()`. */
63
+ id: string;
64
+ anchor: CommentAnchor;
65
+ text: string;
66
+ };
67
+ /** `options` — single or multi select, per-option comments, optional freetext. */
68
+ export type OptionsResponse = {
69
+ /** Always an array; a single-select answer is a one-element array. */
70
+ selectedOptionIds: string[];
71
+ comments: Comment[];
72
+ /** Present only when the config allows freetext and the user typed something. */
73
+ freetext?: string;
74
+ };
75
+ /** `text` — an edited draft and/or anchored comments on presented prose. */
76
+ export type TextResponse = {
77
+ /** The text as the user is leaving it. */
78
+ text: string;
79
+ /** True once `text` differs from what the agent presented. */
80
+ edited: boolean;
81
+ comments: Comment[];
82
+ };
83
+ /** `table` — row and/or column selection with anchored comments. */
84
+ export type TableResponse = {
85
+ selectedRowIds: string[];
86
+ selectedColumnIds: string[];
87
+ comments: Comment[];
88
+ };
89
+ /** `cards` — card selection with per-card comments. */
90
+ export type CardsResponse = {
91
+ selectedCardIds: string[];
92
+ comments: Comment[];
93
+ };
94
+ /**
95
+ * Any slot response. The open member covers a product-registered `unvalidated` slot,
96
+ * whose shape only the product renderer knows.
97
+ */
98
+ export type SlotResponse = OptionsResponse | TextResponse | TableResponse | CardsResponse | Record<string, unknown>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The response contract the page bundle produces, as plain browser-safe types.
3
+ *
4
+ * These mirror `src/core/human/page-schema.ts` field-for-field — `commentSchema`,
5
+ * `commentAnchorSchema`, and the four `*ResponseSchema`s — and are deliberately a
6
+ * separate declaration rather than an import: nothing under `src/pages/` may reach
7
+ * a runtime package (zod) or a module outside this directory, because the bundle is
8
+ * a browser IIFE. The schemas remain the source of truth; a change there is a change
9
+ * that must land here in the same commit.
10
+ *
11
+ * Two rules the shapes are built around:
12
+ *
13
+ * 1. **One comment shape everywhere.** Every response-bearing element carries
14
+ * comments, and every comment is `{ id, anchor, text }`. A comment on an option,
15
+ * a table row, a card, or a range of prose differs only in its `anchor`.
16
+ * 2. **Selection is ids, never indexes or values.** The agent authored the ids it is
17
+ * asking about, so the response keys straight off them.
18
+ *
19
+ * Config types are NOT here: a config is one element's private input, mirrored in that
20
+ * element's own module. A response is what crosses `window.crtr.respond`, so it is
21
+ * shared.
22
+ */
23
+ export {};
@@ -3,6 +3,8 @@ 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). */
5
5
  export declare function leadingSleepSeconds(command: string): number | null;
6
- /** The valve's BashOperations backend. */
7
- export declare function createValveOperations(nodeId: string, contextDir: string): BashOperations;
6
+ /** The valve's BashOperations backend. Each tool execution closes over its own
7
+ * `takePurpose`, so concurrent calls cannot exchange labels. It is consumed even
8
+ * when the command is invalid or refused, leaving no state to leak later. */
9
+ export declare function createValveOperations(nodeId: string, contextDir: string, takePurpose?: () => string | null): BashOperations;
8
10
  export default function (pi: ExtensionAPI): void;
@@ -27,7 +27,7 @@
27
27
  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
- import { backgroundBashJob, bashJobPaths, formatBashElapsed, newBashJobId } from '../core/bash-jobs.js';
30
+ import { backgroundBashJob, bashJobPaths, formatBashElapsed, newBashJobId, normalizeBashJobPurpose } from '../core/bash-jobs.js';
31
31
  import { Type } from 'typebox';
32
32
  import { createBashToolDefinition } from '@earendil-works/pi-coding-agent';
33
33
  // ---------------------------------------------------------------------------
@@ -203,11 +203,14 @@ function resolveExecutionCwd(cwd) {
203
203
  warning: `[working directory no longer exists: ${cwd}; running from ${fallback}]\n`,
204
204
  };
205
205
  }
206
- /** The valve's BashOperations backend. */
207
- export function createValveOperations(nodeId, contextDir) {
206
+ /** The valve's BashOperations backend. Each tool execution closes over its own
207
+ * `takePurpose`, so concurrent calls cannot exchange labels. It is consumed even
208
+ * when the command is invalid or refused, leaving no state to leak later. */
209
+ export function createValveOperations(nodeId, contextDir, takePurpose = () => null) {
208
210
  return {
209
211
  exec: (command, cwd, { onData, signal, timeout, env }) => {
210
212
  return new Promise((resolve, reject) => {
213
+ const purpose = takePurpose();
211
214
  if (signal?.aborted) {
212
215
  reject(new Error('aborted'));
213
216
  return;
@@ -222,6 +225,8 @@ export function createValveOperations(nodeId, contextDir) {
222
225
  writeFileSync(paths.cmdSh, command);
223
226
  writeFileSync(paths.jobLog, '');
224
227
  writeFileSync(paths.jobRun, '');
228
+ if (purpose !== null)
229
+ writeFileSync(paths.jobPurpose, purpose);
225
230
  let offset = 0;
226
231
  let settled = false;
227
232
  let pgid;
@@ -357,8 +362,10 @@ export function createValveOperations(nodeId, contextDir) {
357
362
  // parsing partial JSON can show the label while the command is still being
358
363
  // written, rather than after the call is complete and about to finish.
359
364
  //
360
- // The execute path ignores it entirely — this is a labelling channel, not an
361
- // input. Optional, so a model that omits it behaves exactly as before.
365
+ // The command execution path ignores it as shell input — this remains a
366
+ // labelling channel, not an execution parameter. The valve only persists the
367
+ // label beside a background job. Optional, so a model that omits it behaves
368
+ // exactly as before.
362
369
  //
363
370
  // Upstream ask: pi's own bash tool should carry this (Claude Code's bash tool
364
371
  // has shipped an equivalent `description` field for years). Until it does, the
@@ -386,6 +393,18 @@ function withPurpose(definition) {
386
393
  }),
387
394
  };
388
395
  }
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) }));
401
+ const execute = (toolCallId, params, signal, onUpdate, ctx) => {
402
+ const purpose = normalizeBashJobPurpose(params['purpose']);
403
+ const definition = createBashToolDefinition(cwd, { operations: createValveOperations(nodeId, contextDir, () => purpose) });
404
+ return definition.execute(toolCallId, params, signal, onUpdate, ctx);
405
+ };
406
+ return { ...schemaDefinition, execute };
407
+ }
389
408
  export default function (pi) {
390
409
  const nodeId = process.env['CRTR_NODE_ID'];
391
410
  if (nodeId === undefined || nodeId.trim() === '')
@@ -399,7 +418,6 @@ export default function (pi) {
399
418
  // is built against. registerTool replaces the builtin by name; re-firing on
400
419
  // every session_start is idempotent (last registration wins).
401
420
  pi.on('session_start', (_event, ctx) => {
402
- const operations = createValveOperations(nodeId, contextDir);
403
- pi.registerTool(withPurpose(createBashToolDefinition(ctx.cwd, { operations })));
421
+ pi.registerTool(createPurposeValveToolDefinition(nodeId, contextDir, ctx.cwd));
404
422
  });
405
423
  }
package/dist/types.d.ts CHANGED
@@ -185,6 +185,10 @@ export interface ScopeConfig {
185
185
  /** Play the whip header animation whenever the human sends a prompt, and once
186
186
  * when an automatically opened managed-child viewer starts. Default false. */
187
187
  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[];
188
192
  /** Playful urgency messages the attach-viewer whip action sends to an agent. Missing, malformed, or empty lists fall back to the built-in rotation. */
189
193
  whip_messages: string[];
190
194
  /** Initial mouse wheel scrolling mode for each attach viewer. `tmux` follows
package/dist/types.js CHANGED
@@ -78,6 +78,8 @@ export function defaultScopeConfig() {
78
78
  completion_bell: true,
79
79
  working_gerunds: [...DEFAULT_WORKING_GERUNDS],
80
80
  whip_mode: false,
81
+ html_pages: false,
82
+ page_components: [],
81
83
  whip_messages: [...DEFAULT_WHIP_MESSAGES],
82
84
  mouse_mode_default: 'tmux',
83
85
  live_cycles: DEFAULT_LIVE_CYCLES,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.192",
3
+ "version": "0.3.194",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -86,9 +86,9 @@
86
86
  "test:seam": "npm run build && node --test --test-concurrency=1 --test-timeout=180000 'dist/**/__tests__/seam/*.test.js'"
87
87
  },
88
88
  "overrides": {
89
- "@earendil-works/pi-ai": "0.83.0",
90
- "@earendil-works/pi-agent-core": "0.83.0",
91
- "@earendil-works/pi-tui": "0.83.0"
89
+ "@earendil-works/pi-ai": "0.84.1",
90
+ "@earendil-works/pi-agent-core": "0.84.1",
91
+ "@earendil-works/pi-tui": "0.84.1"
92
92
  },
93
93
  "repository": {
94
94
  "type": "git",
@@ -99,10 +99,10 @@
99
99
  },
100
100
  "license": "MIT",
101
101
  "dependencies": {
102
- "@earendil-works/pi-agent-core": "0.83.0",
103
- "@earendil-works/pi-ai": "0.83.0",
104
- "@earendil-works/pi-coding-agent": "0.83.0",
105
- "@earendil-works/pi-tui": "0.83.0",
102
+ "@earendil-works/pi-agent-core": "0.84.1",
103
+ "@earendil-works/pi-ai": "0.84.1",
104
+ "@earendil-works/pi-coding-agent": "0.84.1",
105
+ "@earendil-works/pi-tui": "0.84.1",
106
106
  "cron-parser": "^5.6.0",
107
107
  "proper-lockfile": "4.1.2",
108
108
  "string-width": "^7.0.0",