@north-light/crouter 0.3.191 → 0.3.193

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 (169) 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 +22 -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/internal/storage-tiers.md +1 -1
  13. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/serial/provider-rotation.test.ts +101 -3
  14. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +6 -2
  15. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +68 -14
  16. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +87 -13
  17. package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +2 -2
  18. package/dist/clients/attach/__tests__/group-activity.test.js +2 -2
  19. package/dist/clients/attach/chrome/canvas-panels.d.ts +1 -1
  20. package/dist/clients/attach/chrome/canvas-panels.js +1 -1
  21. package/dist/clients/attach/input/overlay-owner.js +1 -1
  22. package/dist/clients/attach/overlays/auth.js +34 -8
  23. package/dist/clients/attach/overlays/pickers.d.ts +5 -8
  24. package/dist/clients/attach/overlays/pickers.js +37 -43
  25. package/dist/clients/attach/viewer.js +643 -620
  26. package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +38 -128
  27. package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +72 -148
  28. package/dist/clients/inbox/controller.d.ts +2 -3
  29. package/dist/clients/inbox/controller.js +41 -42
  30. package/dist/clients/inbox/page-adapter.d.ts +33 -0
  31. package/dist/clients/inbox/page-adapter.js +54 -0
  32. package/dist/clients/inbox/resolve.d.ts +2 -2
  33. package/dist/clients/inbox/resolve.js +1 -1
  34. package/dist/clients/inbox/review-visibility.d.ts +1 -1
  35. package/dist/clients/inbox/review-visibility.js +1 -1
  36. package/dist/clients/inbox/tui/ansi.d.ts +1 -1
  37. package/dist/clients/inbox/tui/ansi.js +2 -2
  38. package/dist/clients/inbox/tui/input.d.ts +1 -3
  39. package/dist/clients/inbox/tui/input.js +242 -553
  40. package/dist/clients/inbox/tui/panel.d.ts +3 -1
  41. package/dist/clients/inbox/tui/panel.js +59 -187
  42. package/dist/clients/inbox/tui/render.d.ts +0 -25
  43. package/dist/clients/inbox/tui/render.js +165 -416
  44. package/dist/clients/inbox/tui/slots.d.ts +32 -0
  45. package/dist/clients/inbox/tui/slots.js +156 -0
  46. package/dist/clients/inbox/tui/types.d.ts +25 -13
  47. package/dist/clients/inbox/tui.js +5 -3
  48. package/dist/commands/attention.js +2 -2
  49. package/dist/commands/human/doc.js +66 -20
  50. package/dist/commands/human/prompts.d.ts +3 -3
  51. package/dist/commands/human/prompts.js +158 -112
  52. package/dist/commands/human/queue.d.ts +0 -1
  53. package/dist/commands/human/queue.js +34 -94
  54. package/dist/commands/human/shared.d.ts +10 -4
  55. package/dist/commands/human/shared.js +62 -13
  56. package/dist/commands/human.js +8 -4
  57. package/dist/commands/memory/read.js +13 -5
  58. package/dist/commands/node/bash.js +15 -28
  59. package/dist/commands/surface/node/focus.js +1 -1
  60. package/dist/core/__tests__/human-deliver.test.js +40 -46
  61. package/dist/core/__tests__/seam/broker-attach-stream.test.js +7 -1
  62. package/dist/core/__tests__/serial/human-deliver-e2e.test.js +40 -31
  63. package/dist/core/__tests__/serial/spawn-root.test.js +3 -1
  64. package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +3 -1
  65. package/dist/core/auth-interaction.d.ts +2 -2
  66. package/dist/core/bash-jobs.d.ts +32 -0
  67. package/dist/core/bash-jobs.js +72 -1
  68. package/dist/core/canvas/__tests__/attention.test.js +16 -10
  69. package/dist/core/canvas/__tests__/render-remote.test.js +9 -3
  70. package/dist/core/canvas/attention.js +1 -1
  71. package/dist/core/canvas/browse/app.js +1 -1
  72. package/dist/core/canvas/nav-model.d.ts +1 -1
  73. package/dist/core/canvas/nav-model.js +1 -1
  74. package/dist/core/canvas/remote-canvas-source.js +1 -1
  75. package/dist/core/config.js +4 -1
  76. package/dist/core/human/__tests__/page-render.test.d.ts +1 -0
  77. package/dist/core/human/__tests__/page-render.test.js +49 -0
  78. package/dist/core/human/__tests__/page-tickets.test.d.ts +1 -0
  79. package/dist/core/human/__tests__/page-tickets.test.js +112 -0
  80. package/dist/core/human/__tests__/page.test.d.ts +1 -0
  81. package/dist/core/human/__tests__/page.test.js +139 -0
  82. package/dist/core/human/__tests__/serial/inbox-core.test.js +42 -32
  83. package/dist/core/human/claim.js +2 -2
  84. package/dist/core/human/convention.d.ts +4 -4
  85. package/dist/core/human/convention.js +6 -13
  86. package/dist/core/human/markdown-html.d.ts +8 -0
  87. package/dist/core/human/markdown-html.js +581 -0
  88. package/dist/core/human/page-catalog.d.ts +6 -0
  89. package/dist/core/human/page-catalog.js +43 -0
  90. package/dist/core/human/page-render.d.ts +3 -0
  91. package/dist/core/human/page-render.js +131 -0
  92. package/dist/core/human/page-schema.d.ts +312 -0
  93. package/dist/core/human/page-schema.js +291 -0
  94. package/dist/core/human/page-synth.d.ts +9 -0
  95. package/dist/core/human/page-synth.js +17 -0
  96. package/dist/core/human/page.d.ts +22 -0
  97. package/dist/core/human/page.js +427 -0
  98. package/dist/core/human/review-schema.d.ts +19 -0
  99. package/dist/core/human/review-schema.js +34 -0
  100. package/dist/core/human/scan.d.ts +12 -3
  101. package/dist/core/human/scan.js +69 -26
  102. package/dist/core/human/summary.d.ts +5 -6
  103. package/dist/core/human/summary.js +54 -37
  104. package/dist/core/human/tickets.d.ts +61 -11
  105. package/dist/core/human/tickets.js +100 -57
  106. package/dist/core/human/types.d.ts +21 -66
  107. package/dist/core/human/types.js +1 -5
  108. package/dist/core/preview-registry.js +0 -1
  109. package/dist/core/review/ticket-filter.d.ts +1 -1
  110. package/dist/core/review/ticket-filter.js +2 -8
  111. package/dist/core/runtime/broker/read-ops.js +3 -0
  112. package/dist/core/runtime/broker.js +19 -10
  113. package/dist/core/runtime/package-health.js +1 -1
  114. package/dist/core/termrender/termrender.d.ts +1 -1
  115. package/dist/core/termrender/termrender.js +2 -2
  116. package/dist/core/tui/page-host.d.ts +1 -1
  117. package/dist/core/tui/page-host.js +2 -2
  118. package/dist/core/user-settings.d.ts +19 -0
  119. package/dist/core/user-settings.js +22 -0
  120. package/dist/daemon/api/handlers/bash-jobs.d.ts +2 -0
  121. package/dist/daemon/api/handlers/bash-jobs.js +89 -0
  122. package/dist/daemon/api/handlers/human.js +16 -14
  123. package/dist/daemon/api/handlers/inbox.js +311 -227
  124. package/dist/daemon/api/router.d.ts +5 -1
  125. package/dist/daemon/api/router.js +5 -0
  126. package/dist/daemon/api/server.js +2 -0
  127. package/dist/daemon/cron-run.js +23 -10
  128. package/dist/daemon/human/finish.d.ts +8 -3
  129. package/dist/daemon/human/finish.js +35 -15
  130. package/dist/pages/bundle.css +1 -0
  131. package/dist/pages/bundle.js +1347 -0
  132. package/dist/pages/comments.d.ts +34 -0
  133. package/dist/pages/comments.js +72 -0
  134. package/dist/pages/elements/cards.d.ts +17 -0
  135. package/dist/pages/elements/cards.js +785 -0
  136. package/dist/pages/elements/chart.d.ts +1 -0
  137. package/dist/pages/elements/chart.js +648 -0
  138. package/dist/pages/elements/options.d.ts +21 -0
  139. package/dist/pages/elements/options.js +604 -0
  140. package/dist/pages/elements/pages.d.ts +24 -0
  141. package/dist/pages/elements/pages.js +355 -0
  142. package/dist/pages/elements/slot.d.ts +21 -0
  143. package/dist/pages/elements/slot.js +105 -0
  144. package/dist/pages/elements/table.d.ts +19 -0
  145. package/dist/pages/elements/table.js +906 -0
  146. package/dist/pages/elements/text.d.ts +25 -0
  147. package/dist/pages/elements/text.js +951 -0
  148. package/dist/pages/entry.d.ts +8 -0
  149. package/dist/pages/entry.js +8 -0
  150. package/dist/pages/host.d.ts +132 -0
  151. package/dist/pages/host.js +149 -0
  152. package/dist/pages/register.d.ts +2 -0
  153. package/dist/pages/register.js +3 -0
  154. package/dist/pages/slot-config.d.ts +36 -0
  155. package/dist/pages/slot-config.js +50 -0
  156. package/dist/pages/types.d.ts +98 -0
  157. package/dist/pages/types.js +23 -0
  158. package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +13 -5
  159. package/dist/pi-extensions/canvas-structured-output.js +7 -3
  160. package/dist/types.d.ts +4 -0
  161. package/dist/types.js +2 -0
  162. package/package.json +8 -8
  163. package/runtime.lock.json +156 -101
  164. package/dist/clients/inbox/deck-adapter.d.ts +0 -29
  165. package/dist/clients/inbox/deck-adapter.js +0 -63
  166. package/dist/core/human/deck-factories.d.ts +0 -8
  167. package/dist/core/human/deck-factories.js +0 -15
  168. package/dist/core/human/deck-schema.d.ts +0 -70
  169. 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 {};
@@ -1,6 +1,6 @@
1
1
  // Run with: node --import tsx/esm --test src/pi-extensions/__tests__/canvas-structured-output.test.ts
2
2
  //
3
- // Regression: Codex strict function schemas require every property in `required`.
3
+ // Regression: strict function schemas require all properties and closed object shapes.
4
4
  import { after, test } from 'node:test';
5
5
  import assert from 'node:assert/strict';
6
6
  import { mkdtempSync, rmSync } from 'node:fs';
@@ -37,9 +37,15 @@ test('optional submit fields remain optional through a strict provider tool sche
37
37
  commit: { type: 'string' },
38
38
  verification: { type: 'string' },
39
39
  blocker: { type: 'string' },
40
+ details: {
41
+ type: 'object',
42
+ properties: {
43
+ summary: { type: 'string' },
44
+ },
45
+ required: ['summary'],
46
+ },
40
47
  },
41
- required: ['status', 'verification'],
42
- additionalProperties: false,
48
+ required: ['status', 'verification', 'details'],
43
49
  };
44
50
  writeOutputSchema('structured-output-test', 'oneoff', schema);
45
51
  let definition;
@@ -51,10 +57,12 @@ test('optional submit fields remain optional through a strict provider tool sche
51
57
  });
52
58
  assert.ok(definition, 'the pending request registers submit');
53
59
  const parameters = definition['parameters'];
54
- assert.deepEqual(parameters.required, ['status', 'commit', 'verification', 'blocker']);
60
+ assert.deepEqual(parameters.required, ['status', 'commit', 'verification', 'blocker', 'details']);
55
61
  assert.deepEqual(parameters.properties.commit, { anyOf: [{ type: 'string' }, { type: 'null' }] });
56
62
  assert.deepEqual(parameters.properties.blocker, { anyOf: [{ type: 'string' }, { type: 'null' }] });
57
- assert.deepEqual(schema.required, ['status', 'verification'], 'the stored request keeps its original optionality');
63
+ assert.equal(parameters.additionalProperties, false);
64
+ assert.equal(parameters.properties.details.additionalProperties, false);
65
+ assert.deepEqual(schema.required, ['status', 'verification', 'details'], 'the stored request keeps its original optionality');
58
66
  const [providerTool] = convertResponsesTools([definition], {
59
67
  strict: false,
60
68
  supportsStrictMode: true,
@@ -47,6 +47,10 @@ function strictToolSchema(schema) {
47
47
  }
48
48
  }
49
49
  const properties = transformed['properties'];
50
+ const isObjectSchema = transformed['type'] === 'object'
51
+ || (properties !== null && typeof properties === 'object' && !Array.isArray(properties));
52
+ if (isObjectSchema)
53
+ transformed['additionalProperties'] = false;
50
54
  if (properties === null || typeof properties !== 'object' || Array.isArray(properties))
51
55
  return transformed;
52
56
  const required = new Set(Array.isArray(source['required']) ? source['required'].filter((name) => typeof name === 'string') : []);
@@ -125,9 +129,9 @@ export function registerCanvasStructuredOutput(pi) {
125
129
  'Use submit to answer the pending one-off structured-output request before stopping or going dormant.',
126
130
  'After one-off submit succeeds, continue with any remaining work normally.',
127
131
  ],
128
- // OpenAI strict tools require every property to be required. Optional fields
129
- // become nullable in the provider-only schema and null is removed before the
130
- // original result is persisted.
132
+ // Strict provider tools require every property to be required and every
133
+ // object to reject undeclared properties. Optional fields become nullable
134
+ // in the provider-only schema and null is removed before persistence.
131
135
  parameters: strictToolSchema(request.schema),
132
136
  constrainedSampling: { type: 'json_schema', strict: 'prefer' },
133
137
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
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.191",
3
+ "version": "0.3.193",
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",