@theoremjs/react 0.3.0 → 0.3.1

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 (258) hide show
  1. package/README.md +728 -202
  2. package/client/composer-hints.js +1 -1
  3. package/client/decision-images.d.ts +76 -0
  4. package/client/decision-images.js +139 -0
  5. package/client/decision-transport.d.ts +26 -4
  6. package/client/decision-transport.js +79 -6
  7. package/client/embedding-input.d.ts +84 -0
  8. package/client/embedding-input.js +194 -0
  9. package/client/embedding-space.d.ts +82 -0
  10. package/client/embedding-space.js +389 -0
  11. package/client/embedding-transport.d.ts +40 -0
  12. package/client/embedding-transport.js +71 -0
  13. package/client/failure.d.ts +7 -10
  14. package/client/failure.js +9 -9
  15. package/client/host-call.d.ts +1 -1
  16. package/client/host-call.js +7 -2
  17. package/client/host-transport.d.ts +2 -0
  18. package/client/host-transport.js +3 -2
  19. package/client/index.d.ts +19 -11
  20. package/client/index.js +14 -10
  21. package/client/ink-waveform.d.ts +1 -1
  22. package/client/ink-waveform.js +1 -2
  23. package/client/live/apply-live-turn-tool-event.js +1 -1
  24. package/client/live/live-captions.d.ts +30 -0
  25. package/client/live/live-captions.js +105 -1
  26. package/client/live/live-mic-forward.d.ts +1 -0
  27. package/client/live/live-mic-forward.js +11 -2
  28. package/client/live/live-page-tool.d.ts +34 -0
  29. package/client/live/live-page-tool.js +11 -0
  30. package/client/live/live-state.d.ts +4 -2
  31. package/client/live/live-state.js +2 -0
  32. package/client/live/live-video.js +1 -1
  33. package/client/live/run-live-tool-call.d.ts +7 -2
  34. package/client/live/run-live-tool-call.js +48 -10
  35. package/client/live/use-live-caption-log.d.ts +17 -0
  36. package/client/live/use-live-caption-log.js +51 -0
  37. package/client/live-client.d.ts +52 -4
  38. package/client/live-client.js +250 -59
  39. package/client/live-messages.d.ts +51 -13
  40. package/client/live-messages.js +42 -9
  41. package/client/page-dispatch.d.ts +15 -0
  42. package/client/page-dispatch.js +63 -0
  43. package/client/run-commit.d.ts +16 -43
  44. package/client/run-commit.js +26 -101
  45. package/client/run-session.d.ts +40 -41
  46. package/client/run-session.js +104 -112
  47. package/client/schema-fields.js +27 -6
  48. package/client/shaped-data.js +37 -8
  49. package/client/source-citations.js +1 -1
  50. package/client/space-geometry.d.ts +39 -0
  51. package/client/space-geometry.js +183 -0
  52. package/client/state-fields.js +1 -1
  53. package/client/tool-result.js +30 -12
  54. package/client/tool-resume.d.ts +18 -1
  55. package/client/tool-resume.js +4 -1
  56. package/client/trace-feed.d.ts +1 -1
  57. package/client/trace-story.d.ts +2 -2
  58. package/client/trace-story.js +43 -19
  59. package/client/trace-view.d.ts +2 -2
  60. package/client/trace-view.js +56 -16
  61. package/client/transcript-groups.d.ts +28 -2
  62. package/client/transcript-groups.js +63 -13
  63. package/client/transcript-schema.d.ts +10 -0
  64. package/client/transcript-schema.js +120 -0
  65. package/client/transport.d.ts +114 -26
  66. package/client/transport.js +106 -32
  67. package/client/turn-client.d.ts +17 -8
  68. package/client/turn-client.js +26 -31
  69. package/client/voice-recorder.d.ts +1 -1
  70. package/client/voice-recorder.js +2 -5
  71. package/client/wire-line.js +5 -5
  72. package/client/worklets/mic-capture.js +9 -9
  73. package/components/InkWaveform.d.ts +12 -1
  74. package/components/InkWaveform.js +65 -42
  75. package/components/live/use-live-runner-controls.d.ts +3 -4
  76. package/components/live/use-live-runner-controls.js +6 -10
  77. package/components/live/use-live-runner-model.d.ts +10 -6
  78. package/components/live/use-live-runner-model.js +29 -14
  79. package/components/live/use-live-runner-ui.d.ts +10 -8
  80. package/components/live/use-live-runner-ui.js +19 -11
  81. package/components/live/use-live-session-client.d.ts +4 -4
  82. package/components/live/use-live-session-client.js +11 -8
  83. package/components/use-composer-voice.d.ts +4 -4
  84. package/components/use-composer-voice.js +8 -8
  85. package/components/use-editor-selection.js +4 -1
  86. package/components/use-voice-playback.d.ts +2 -1
  87. package/components/use-voice-playback.js +4 -4
  88. package/hooks/use-described.d.ts +5 -1
  89. package/hooks/use-described.js +11 -9
  90. package/hooks/use-embedding-index.d.ts +27 -0
  91. package/hooks/use-embedding-index.js +43 -0
  92. package/hooks/use-one-call.d.ts +31 -0
  93. package/hooks/use-one-call.js +76 -0
  94. package/hooks/use-page-tool-warnings.d.ts +6 -0
  95. package/hooks/use-page-tool-warnings.js +25 -0
  96. package/hooks/use-second-ticker.d.ts +2 -0
  97. package/hooks/use-second-ticker.js +13 -0
  98. package/hooks/use-theorem-chat-actions.d.ts +25 -21
  99. package/hooks/use-theorem-chat-actions.js +67 -45
  100. package/hooks/use-theorem-chat-state.d.ts +6 -6
  101. package/hooks/use-theorem-chat-state.js +13 -9
  102. package/hooks/use-theorem-chat.d.ts +48 -14
  103. package/hooks/use-theorem-chat.js +329 -141
  104. package/hooks/use-theorem-decision.d.ts +5 -10
  105. package/hooks/use-theorem-decision.js +4 -67
  106. package/hooks/use-theorem-embedding.d.ts +18 -0
  107. package/hooks/use-theorem-embedding.js +12 -0
  108. package/hooks/use-theorem-host.d.ts +5 -4
  109. package/hooks/use-theorem-host.js +26 -8
  110. package/hooks/use-theorem-interface.d.ts +6 -3
  111. package/hooks/use-theorem-interface.js +12 -22
  112. package/index.d.ts +6 -3
  113. package/index.js +5 -2
  114. package/live.d.ts +1 -0
  115. package/live.js +1 -1
  116. package/package.json +6 -5
  117. package/server/checkpoint-ownership.d.ts +6 -0
  118. package/server/checkpoint-ownership.js +42 -0
  119. package/server/conversation-store.d.ts +92 -0
  120. package/server/conversation-store.js +131 -0
  121. package/server/conversation.d.ts +193 -0
  122. package/server/conversation.js +552 -0
  123. package/server/decision-handler.d.ts +12 -4
  124. package/server/decision-handler.js +29 -38
  125. package/server/embedding-handler.d.ts +33 -0
  126. package/server/embedding-handler.js +45 -0
  127. package/server/handler.d.ts +58 -20
  128. package/server/handler.js +441 -238
  129. package/server/mod.d.ts +15 -6
  130. package/server/mod.js +13 -5
  131. package/server/page-dispatch.d.ts +22 -0
  132. package/server/page-dispatch.js +127 -0
  133. package/server/request-body.d.ts +2 -0
  134. package/server/request-body.js +42 -0
  135. package/server/request-check.d.ts +6 -1
  136. package/server/request-check.js +27 -12
  137. package/server/session-authority.d.ts +8 -0
  138. package/server/session-authority.js +78 -0
  139. package/server/session-boundary.d.ts +26 -0
  140. package/server/session-boundary.js +64 -0
  141. package/server/session-identity.d.ts +16 -0
  142. package/server/session-identity.js +41 -0
  143. package/server/session-limits.d.ts +12 -0
  144. package/server/session-limits.js +59 -0
  145. package/server/session-store.d.ts +69 -20
  146. package/server/session-store.js +220 -5
  147. package/server/single-action.d.ts +21 -0
  148. package/server/single-action.js +30 -0
  149. package/server/steer-inbox.js +1 -1
  150. package/server/tool-operations.d.ts +6 -0
  151. package/server/tool-operations.js +132 -0
  152. package/server/turn-input.d.ts +10 -0
  153. package/server/turn-input.js +29 -0
  154. package/styles/ink-controls.css +78 -78
  155. package/ui/CardWall.d.ts +10 -0
  156. package/ui/CardWall.js +61 -0
  157. package/ui/ChatComposerBar.d.ts +28 -5
  158. package/ui/ChatComposerBar.js +85 -30
  159. package/ui/ChatTranscript.d.ts +14 -1
  160. package/ui/ChatTranscript.js +110 -57
  161. package/ui/ConsoleFrame.d.ts +61 -4
  162. package/ui/ConsoleFrame.js +109 -9
  163. package/ui/DecisionAnswers.d.ts +40 -2
  164. package/ui/DecisionAnswers.js +166 -35
  165. package/ui/DecisionImages.d.ts +16 -0
  166. package/ui/DecisionImages.js +46 -0
  167. package/ui/EmbeddingDocuments.d.ts +78 -0
  168. package/ui/EmbeddingDocuments.js +278 -0
  169. package/ui/EmbeddingQuery.d.ts +27 -0
  170. package/ui/EmbeddingQuery.js +95 -0
  171. package/ui/EmbeddingResults.d.ts +41 -0
  172. package/ui/EmbeddingResults.js +165 -0
  173. package/ui/EmbeddingSpace.d.ts +26 -0
  174. package/ui/EmbeddingSpace.js +106 -0
  175. package/ui/LiveCaptionsPanel.d.ts +22 -0
  176. package/ui/LiveCaptionsPanel.js +27 -0
  177. package/ui/LiveRunner.d.ts +6 -4
  178. package/ui/LiveRunner.js +42 -55
  179. package/ui/PaneState.d.ts +33 -0
  180. package/ui/PaneState.js +50 -0
  181. package/ui/SchemaFields.d.ts +1 -1
  182. package/ui/SchemaFields.js +54 -26
  183. package/ui/ShapedData.js +73 -28
  184. package/ui/SidePanel.d.ts +74 -5
  185. package/ui/SidePanel.js +82 -9
  186. package/ui/StateFields.d.ts +10 -2
  187. package/ui/StateFields.js +56 -13
  188. package/ui/TheoremChat.d.ts +65 -14
  189. package/ui/TheoremChat.js +66 -78
  190. package/ui/TheoremDecision.d.ts +12 -20
  191. package/ui/TheoremDecision.js +78 -35
  192. package/ui/TheoremEmbedding.d.ts +27 -0
  193. package/ui/TheoremEmbedding.js +122 -0
  194. package/ui/TheoremHost.d.ts +22 -5
  195. package/ui/TheoremHost.js +127 -37
  196. package/ui/ToolGateCard.d.ts +5 -1
  197. package/ui/ToolGateCard.js +30 -23
  198. package/ui/ToolResult.js +12 -10
  199. package/ui/ToolResultCharts.js +29 -12
  200. package/ui/TraceCharts.d.ts +2 -2
  201. package/ui/TraceCharts.js +48 -17
  202. package/ui/TraceGuardrails.d.ts +3 -2
  203. package/ui/TraceGuardrails.js +24 -6
  204. package/ui/TraceInspector.d.ts +11 -1
  205. package/ui/TraceInspector.js +45 -28
  206. package/ui/TraceInspectorPanel.d.ts +31 -9
  207. package/ui/TraceInspectorPanel.js +77 -39
  208. package/ui/TraceOverview.d.ts +7 -3
  209. package/ui/TraceOverview.js +44 -14
  210. package/ui/TraceSpanDetail.js +24 -22
  211. package/ui/TraceStory.js +9 -8
  212. package/ui/TraceValues.js +19 -9
  213. package/ui/TraceWaterfall.js +12 -9
  214. package/ui/VoiceNote.js +4 -4
  215. package/ui/approval-heading.d.ts +4 -0
  216. package/ui/approval-heading.js +9 -0
  217. package/ui/arrive.d.ts +18 -0
  218. package/ui/arrive.js +46 -0
  219. package/ui/built/theme.css +86 -68
  220. package/ui/built/theme.css.d.ts +9 -0
  221. package/ui/built/theorem.d.ts +3 -3
  222. package/ui/built/theorem.js +34 -5
  223. package/ui/built/theorem.variants.d.ts +8 -2
  224. package/ui/code-language.d.ts +12 -0
  225. package/ui/code-language.js +50 -0
  226. package/ui/disclosure-motion.js +53 -9
  227. package/ui/embedding-space-gl.d.ts +37 -0
  228. package/ui/embedding-space-gl.js +663 -0
  229. package/ui/embedding-space-view.d.ts +8 -0
  230. package/ui/embedding-space-view.js +8 -0
  231. package/ui/glide.d.ts +7 -0
  232. package/ui/glide.js +44 -0
  233. package/ui/icons.js +2 -1
  234. package/ui/index.d.ts +20 -9
  235. package/ui/index.js +20 -10
  236. package/ui/labels-provider.js +5 -2
  237. package/ui/labels.d.ts +374 -85
  238. package/ui/labels.js +1028 -188
  239. package/ui/markdown-plugins.d.ts +9 -0
  240. package/ui/markdown-plugins.js +13 -0
  241. package/ui/mcp-icon.d.ts +6 -0
  242. package/ui/mcp-icon.js +8 -0
  243. package/ui/row-keys.d.ts +19 -0
  244. package/ui/row-keys.js +33 -0
  245. package/ui/theme.d.ts +1 -1
  246. package/ui/theme.js +2 -2
  247. package/ui/theorem-theme.js +32 -10
  248. package/ui/trace-palette.d.ts +2 -16
  249. package/ui/trace-palette.js +2 -16
  250. package/ui/trace-patterns.d.ts +11 -3
  251. package/ui/trace-patterns.js +16 -3
  252. package/ui/transcript-copy-text.js +1 -0
  253. package/ui/wall-columns.d.ts +8 -0
  254. package/ui/wall-columns.js +19 -0
  255. package/client/walk-away.d.ts +0 -34
  256. package/client/walk-away.js +0 -48
  257. package/server/walk-away.d.ts +0 -22
  258. package/server/walk-away.js +0 -41
@@ -0,0 +1,183 @@
1
+ /**
2
+ * What the embedding space draws, as flat arrays a GPU takes as they are:
3
+ * the axes' three circles, each document's dot, a line for each link, heavier
4
+ * the further its cosine similarity passes the threshold, and a query's white
5
+ * dot with a line to each match that passes its own.
6
+ * Every other line joins two real points and stands for their cosine
7
+ * similarity; nothing is drawn for show.
8
+ *
9
+ * @module
10
+ */
11
+ /**
12
+ * Floats per line segment: from (3), to (3), colour (3), alpha, width in CSS
13
+ * pixels, the two slots it reads its state from, where it sits along its line
14
+ * (0 at the start, 1 at the end), and the gap in CSS pixels it leaves before
15
+ * its start and after its end, where a dot sits.
16
+ */
17
+ export const SEGMENT_FLOATS = 16;
18
+ /** Floats per dot: position (3), colour (3), alpha, radius in CSS pixels, its slot, and where it sits along the line that reaches it. */
19
+ export const DOT_FLOATS = 10;
20
+ /** Stands for the query where a slot would go. */
21
+ export const QUERY_SLOT = -1;
22
+ /** Stands for the axes' circles, which no document's state reaches. */
23
+ export const FRAME_SLOT = -2;
24
+ /** Every cluster's colour has this lightness and chroma, so none reads dirtier or louder than another. */
25
+ const LIGHTNESS = 0.72;
26
+ const CHROMA = 0.085;
27
+ const QUERY_COLOUR = [0.97, 0.98, 1.0];
28
+ /**
29
+ * Every line is a faint hairline in its documents' colour: it hints at a
30
+ * connection, and the dots carry the picture. Its width in CSS pixels and its
31
+ * opacity, at the threshold and at identical.
32
+ */
33
+ const LINE_WIDTH = [0.3, 0.5];
34
+ const LINE_ALPHA = [0.18, 0.4];
35
+ function weighted([at, to], strength) {
36
+ return at + (to - at) * strength;
37
+ }
38
+ function gamma(linear) {
39
+ return linear <= 0.0031308 ? 12.92 * linear : 1.055 * linear ** (1 / 2.4) - 0.055;
40
+ }
41
+ /** An OKLCH colour as sRGB from 0 to 1, or `null` when the screen cannot show it. */
42
+ function oklch(l, c, hue) {
43
+ const a = c * Math.cos(hue);
44
+ const b = c * Math.sin(hue);
45
+ const l_ = (l + 0.3963377774 * a + 0.2158037573 * b) ** 3;
46
+ const m_ = (l - 0.1055613458 * a - 0.0638541728 * b) ** 3;
47
+ const s_ = (l - 0.0894841775 * a - 1.291485548 * b) ** 3;
48
+ const rgb = [
49
+ 4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_,
50
+ -1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_,
51
+ -0.0041960863 * l_ - 0.7034186147 * m_ + 1.707614701 * s_,
52
+ ];
53
+ if (rgb.some((v) => v < -1e-4 || v > 1 + 1e-4))
54
+ return null;
55
+ return rgb.map((v) => gamma(Math.min(1, Math.max(0, v))));
56
+ }
57
+ /** A cluster's colour from its tone, a hue from 0 to 1, at the palette's one lightness; chroma gives way only where the screen runs out. */
58
+ function colourOf(tone) {
59
+ const hue = tone * 2 * Math.PI;
60
+ for (let c = CHROMA; c > 0; c -= 0.005) {
61
+ const colour = oklch(LIGHTNESS, c, hue);
62
+ if (colour)
63
+ return colour;
64
+ }
65
+ return oklch(LIGHTNESS, 0, hue) ?? [0.8, 0.8, 0.8];
66
+ }
67
+ function mixed(a, b, u) {
68
+ return [a[0] + (b[0] - a[0]) * u, a[1] + (b[1] - a[1]) * u, a[2] + (b[2] - a[2]) * u];
69
+ }
70
+ /** The longest piece a line is cut into, so depth fades along it, not only at its ends. */
71
+ const PIECE = 0.1;
72
+ /** Points along the straight line from one point to another, through the ball. */
73
+ function pieces(a, b) {
74
+ const steps = Math.max(1, Math.ceil(Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]) / PIECE));
75
+ return Array.from({ length: steps + 1 }, (_, s) => mixed(a, b, s / steps));
76
+ }
77
+ class Strokes {
78
+ segments = [];
79
+ dots = [];
80
+ line({ from, to, colour, alpha, width, slots, gaps }) {
81
+ const points = pieces(from, to);
82
+ const last = points.length - 1;
83
+ for (let i = 1; i <= last; i += 1) {
84
+ const u = i / last;
85
+ this.segments.push(...points[i - 1], ...points[i], ...colour((i - 0.5) / last), alpha, width, slots[0], slots[1], u, i === 1 ? gaps[0] : 0, i === last ? gaps[1] : 0);
86
+ }
87
+ }
88
+ /** One segment no document's state reaches, always shown, with no gaps. */
89
+ segment(from, to, colour, alpha, width) {
90
+ this.segments.push(...from, ...to, ...colour, alpha, width, FRAME_SLOT, FRAME_SLOT, 0, 0, 0);
91
+ }
92
+ dot({ at, colour, alpha, radius, slot, along }) {
93
+ this.dots.push(...at, ...colour, alpha, radius, slot, along);
94
+ }
95
+ done() {
96
+ return { segments: new Float32Array(this.segments), dots: new Float32Array(this.dots) };
97
+ }
98
+ }
99
+ /** A document's dot, and the query's, a little larger, in CSS pixels. */
100
+ const DOCUMENT_RADIUS = 2.6;
101
+ const QUERY_RADIUS = 3.6;
102
+ /** The clear space between a line's end and the dot it reaches. */
103
+ const GAP = 1.75;
104
+ /** How far a link passes the threshold, from 0 at it to 1 at identical. */
105
+ function strengthOf(space, similarity) {
106
+ if (space.threshold >= 1)
107
+ return 1;
108
+ return Math.min(Math.max((similarity - space.threshold) / (1 - space.threshold), 0), 1);
109
+ }
110
+ /** The axes' circles: a hairline round the rim in each plane two of the three axes span. */
111
+ const FRAME_COLOUR = [0.88, 0.9, 0.94];
112
+ /** At the ball's front; the renderer fades a circle with the square of its depth, toward a quarter of this at the back. */
113
+ const FRAME_ALPHA = 0.29;
114
+ const FRAME_WIDTH = 0.7;
115
+ const FRAME_STEPS = 96;
116
+ function frame(strokes) {
117
+ const planes = [
118
+ (a) => [Math.cos(a), Math.sin(a), 0],
119
+ (a) => [Math.cos(a), 0, Math.sin(a)],
120
+ (a) => [0, Math.cos(a), Math.sin(a)],
121
+ ];
122
+ for (const at of planes)
123
+ for (let i = 0; i < FRAME_STEPS; i += 1)
124
+ strokes.segment(at((i / FRAME_STEPS) * 2 * Math.PI), at(((i + 1) / FRAME_STEPS) * 2 * Math.PI), FRAME_COLOUR, FRAME_ALPHA, FRAME_WIDTH);
125
+ }
126
+ /** Everything a space draws before any query: the axes' circles, a line for each link, then every document's dot. */
127
+ export function spaceGeometry(space) {
128
+ const strokes = new Strokes();
129
+ frame(strokes);
130
+ const colourOfDocument = (index) => colourOf(space.clusters[space.documents[index].cluster].tone);
131
+ for (const link of space.links) {
132
+ const strength = strengthOf(space, link.similarity);
133
+ // invariant: a link's two documents are in one cluster, so it takes their colour.
134
+ const colour = colourOfDocument(link.a);
135
+ strokes.line({
136
+ from: space.documents[link.a].position,
137
+ to: space.documents[link.b].position,
138
+ colour: () => colour,
139
+ alpha: weighted(LINE_ALPHA, strength),
140
+ width: weighted(LINE_WIDTH, strength),
141
+ slots: [link.a, link.b],
142
+ gaps: [DOCUMENT_RADIUS + GAP, DOCUMENT_RADIUS + GAP],
143
+ });
144
+ }
145
+ for (const document of space.documents)
146
+ strokes.dot({
147
+ at: document.position,
148
+ colour: colourOfDocument(document.index),
149
+ alpha: 1,
150
+ radius: DOCUMENT_RADIUS,
151
+ slot: document.index,
152
+ along: 0,
153
+ });
154
+ return strokes.done();
155
+ }
156
+ /** A query's white dot where it landed, and a line from it to each match, weighted as a link is. */
157
+ export function queryGeometry(space, at, matches) {
158
+ const strokes = new Strokes();
159
+ for (const match of matches) {
160
+ const document = space.documents[match.index];
161
+ if (!document)
162
+ continue;
163
+ const colour = colourOf(space.clusters[document.cluster].tone);
164
+ strokes.line({
165
+ from: at,
166
+ to: document.position,
167
+ colour: (u) => mixed(QUERY_COLOUR, colour, u),
168
+ alpha: weighted(LINE_ALPHA, match.closeness),
169
+ width: weighted(LINE_WIDTH, match.closeness),
170
+ slots: [QUERY_SLOT, match.index],
171
+ gaps: [QUERY_RADIUS + GAP, DOCUMENT_RADIUS + GAP],
172
+ });
173
+ }
174
+ strokes.dot({
175
+ at,
176
+ colour: QUERY_COLOUR,
177
+ alpha: 1,
178
+ radius: QUERY_RADIUS,
179
+ slot: QUERY_SLOT,
180
+ along: 0,
181
+ });
182
+ return strokes.done();
183
+ }
@@ -35,7 +35,7 @@ export function tableColumns(value) {
35
35
  Object.values(row).every((cell) => cell === null || typeof cell !== 'object'));
36
36
  if (!alike)
37
37
  return null;
38
- // Wider columns for longer values: a role stays narrow beside its message.
38
+ // why: Wider columns for longer values: a role stays narrow beside its message.
39
39
  return keys.map((key) => {
40
40
  const lengths = value.map((row) => String(row[key] ?? '').length);
41
41
  const longest = Math.max(key.length, ...lengths);
@@ -57,8 +57,10 @@ function figuresOf(row, units) {
57
57
  if (reading)
58
58
  figures.push({ key, value, ...reading });
59
59
  }
60
- // Numbers with a unit say more at a glance: they lead.
61
- return figures.sort((a, b) => Number(b.unit !== undefined) - Number(a.unit !== undefined)).slice(0, MAX_FIGURES);
60
+ // why: Numbers with a unit say more at a glance: they lead.
61
+ return figures
62
+ .sort((a, b) => Number(b.unit !== undefined) - Number(a.unit !== undefined))
63
+ .slice(0, MAX_FIGURES);
62
64
  }
63
65
  /**
64
66
  * The figures: the numbers of the first nested object of plain fields (the
@@ -87,7 +89,9 @@ function distinct(rows, key) {
87
89
  }
88
90
  /** Fields every row holds as a number, that measure something and vary. */
89
91
  function numericSeries(rows, skip, units) {
90
- const keys = [...new Set(rows.flatMap((row) => Object.keys(row)))].filter((key) => key !== skip && rows.every((row) => typeof row[key] === 'number' || row[key] === null) && distinct(rows, key) > 1);
92
+ const keys = [...new Set(rows.flatMap((row) => Object.keys(row)))].filter((key) => key !== skip &&
93
+ rows.every((row) => typeof row[key] === 'number' || row[key] === null) &&
94
+ distinct(rows, key) > 1);
91
95
  return keys.flatMap((key) => {
92
96
  const reading = measured(key, units);
93
97
  return reading ? [{ key, ...reading }] : [];
@@ -105,7 +109,8 @@ function sharedWords(series) {
105
109
  const words = series.map((one) => one.label.split(' '));
106
110
  const [first = []] = words;
107
111
  let shared = 0;
108
- while (shared < first.length && words.every((one) => one.length > shared + 1 && one[shared] === first[shared]))
112
+ while (shared < first.length &&
113
+ words.every((one) => one.length > shared + 1 && one[shared] === first[shared]))
109
114
  shared++;
110
115
  if (!shared)
111
116
  return undefined;
@@ -131,7 +136,9 @@ function timeCharts(path, title, rows, x, units) {
131
136
  return {
132
137
  kind: 'time',
133
138
  key: `${path}:${shown.map((series) => series.key).join('+')}`,
134
- title: shown.length === 1 ? (shown[0]?.label ?? title) : (shared?.title ?? (title || shown.map((series) => series.label).join(' · '))),
139
+ title: shown.length === 1
140
+ ? (shown[0]?.label ?? title)
141
+ : (shared?.title ?? (title || shown.map((series) => series.label).join(' · '))),
135
142
  x,
136
143
  series: shared?.series ?? shown,
137
144
  rows,
@@ -142,7 +149,9 @@ function timeCharts(path, title, rows, x, units) {
142
149
  function labelKey(rows) {
143
150
  const [first] = rows;
144
151
  const title = first ? titleKey(first) : undefined;
145
- if (!first || !title || !rows.every((row) => typeof row[title] === 'string' || typeof row[title] === 'number'))
152
+ if (!first ||
153
+ !title ||
154
+ !rows.every((row) => typeof row[title] === 'string' || typeof row[title] === 'number'))
146
155
  return undefined;
147
156
  if (distinct(rows, title) === rows.length)
148
157
  return title;
@@ -193,7 +202,6 @@ function chartsIn(value, path, title, units, depth) {
193
202
  const entries = shownEntries(value);
194
203
  const numbers = entries.filter(([key, entry]) => typeof entry === 'number' && isMeasure(key));
195
204
  if (depth > 0 && numbers.length >= MIN_POINTS && numbers.length === entries.length) {
196
- // An object of numbers (`rates`): each key a bar.
197
205
  const rows = numbers.map(([key, entry]) => ({ label: keyLabel(key), value: entry }));
198
206
  return [
199
207
  {
@@ -247,13 +255,18 @@ export function returnedMedia(layout, parts, imageAlt) {
247
255
  return src ? [{ type: part.type, src, mimeType: part.mimeType }] : [];
248
256
  });
249
257
  return {
250
- images: [...sourced.filter((part) => part.type === 'image').map(({ src }) => ({ src, alt: imageAlt })), ...layout.images],
251
- audio: sourced.filter((part) => part.type === 'audio').map(({ src, mimeType }) => ({ src, ...(mimeType ? { mimeType } : {}) })),
258
+ images: [
259
+ ...sourced.filter((part) => part.type === 'image').map(({ src }) => ({ src, alt: imageAlt })),
260
+ ...layout.images,
261
+ ],
262
+ audio: sourced
263
+ .filter((part) => part.type === 'audio')
264
+ .map(({ src, mimeType }) => ({ src, ...(mimeType ? { mimeType } : {}) })),
252
265
  };
253
266
  }
254
267
  /** Whether the result has anything to lead with above its data. */
255
268
  export function hasLead(layout, media) {
256
- return layout.figures.length + layout.charts.length + media.images.length + media.audio.length > 0;
269
+ return (layout.figures.length + layout.charts.length + media.images.length + media.audio.length > 0);
257
270
  }
258
271
  const DAY_MS = 86_400_000;
259
272
  /** Days a time chart names by weekday; longer runs read as dates. */
@@ -266,10 +279,15 @@ const MAX_WEEKDAYS = 10;
266
279
  export function timeFormats(dates) {
267
280
  if (dates.some((date) => date.includes('T'))) {
268
281
  const span = Math.abs(Date.parse(dates.at(-1) ?? '') - Date.parse(dates[0] ?? ''));
269
- return { tick: span <= DAY_MS ? { hour: 'numeric' } : { month: 'short', day: 'numeric' }, when: { dateStyle: 'medium', timeStyle: 'short' } };
282
+ return {
283
+ tick: span <= DAY_MS ? { hour: 'numeric' } : { month: 'short', day: 'numeric' },
284
+ when: { dateStyle: 'medium', timeStyle: 'short' },
285
+ };
270
286
  }
271
287
  return {
272
- tick: dates.length <= MAX_WEEKDAYS ? { weekday: 'short', timeZone: 'UTC' } : { month: 'short', day: 'numeric', timeZone: 'UTC' },
288
+ tick: dates.length <= MAX_WEEKDAYS
289
+ ? { weekday: 'short', timeZone: 'UTC' }
290
+ : { month: 'short', day: 'numeric', timeZone: 'UTC' },
273
291
  when: { dateStyle: 'medium', timeZone: 'UTC' },
274
292
  };
275
293
  }
@@ -7,12 +7,28 @@ export type ToolGateResolution = {
7
7
  | {
8
8
  action: 'auth';
9
9
  secret?: string;
10
+ }
11
+ /** The page answered a `page` gate: no person decides it. */
12
+ | {
13
+ action: 'page';
14
+ page: PagePart;
15
+ };
16
+ /** What the page sends for a call it was asked to answer: its output, or that nothing here answers the tool. */
17
+ export type PagePart = {
18
+ output: unknown;
19
+ } | {
20
+ unanswered: true;
10
21
  };
11
22
  /** An answer on its way to the gate on `callId`; the gate shows it until the answer settles or fails. */
12
23
  export type AnsweringGate = {
13
24
  callId: string;
14
- action: ToolGateResolution['action'];
25
+ operationId: string;
26
+ action: PersonGateResolution['action'];
15
27
  };
28
+ /** An answer a person gives: every resolution but the page's own. */
29
+ export type PersonGateResolution = Exclude<ToolGateResolution, {
30
+ action: 'page';
31
+ }>;
16
32
  /**
17
33
  * What the browser sends for the user's answer to a gate. The host settles a
18
34
  * refusal; an approval carries the permissions the session holds after it,
@@ -23,6 +39,7 @@ export type GatedToolContinue = {
23
39
  } | {
24
40
  decision: 'approve';
25
41
  secret?: string;
42
+ page?: PagePart;
26
43
  sessionPermissions: string[];
27
44
  };
28
45
  export declare function continueGatedToolInvocation(args: {
@@ -5,7 +5,10 @@ export function continueGatedToolInvocation(args) {
5
5
  const { resolution } = args;
6
6
  return {
7
7
  decision: 'approve',
8
- ...(resolution.action === 'auth' && resolution.secret !== undefined ? { secret: resolution.secret } : {}),
8
+ ...(resolution.action === 'auth' && resolution.secret !== undefined
9
+ ? { secret: resolution.secret }
10
+ : {}),
11
+ ...(resolution.action === 'page' ? { page: resolution.page } : {}),
9
12
  sessionPermissions: sessionPermissionsAfterApproval(args.sessionPermissions, args.toolName, args.gate.permission),
10
13
  };
11
14
  }
@@ -1,7 +1,7 @@
1
1
  import type { TraceRecord } from '@theoremjs/agents';
2
2
  /**
3
3
  * Trace records a host sends back to the client, in arrival order. Only a host
4
- * that delivers its traces (the playground) has one; `useTraceRecords` reads it.
4
+ * that delivers its traces (the studio) has one; `useTraceRecords` reads it.
5
5
  */
6
6
  export interface TraceFeed {
7
7
  push(record: TraceRecord): void;
@@ -61,7 +61,7 @@ export type TraceStep = {
61
61
  /** The last message's text from `role`, read from a span's input or output messages. */
62
62
  export declare function messageText(node: TraceNode, key: 'gen_ai.input.messages' | 'gen_ai.output.messages', role: string): string;
63
63
  /**
64
- * A turn as it happened: what the user asked, each model call and the tool
64
+ * A turn as it happened: what the user asked, each model call, and the tool
65
65
  * calls it asked for (nested under it), the events that changed the course,
66
66
  * and how it ended. HTTP tries stay in the timeline; they are how a call
67
67
  * travelled, not a step of the story.
@@ -71,7 +71,7 @@ export declare function traceStory(root: TraceNode): TraceStep[];
71
71
  export declare function toolArguments(node: TraceNode): [string, string][];
72
72
  /** A stored JSON or text value, parsed when it is JSON. */
73
73
  export declare function storedValue(node: TraceNode, key: string): unknown;
74
- /** How a span ended when it did not simply succeed: a tool denied, paused or failed, or any span's error. */
74
+ /** How a span ended when it did not simply succeed: a tool denied, paused, or failed, or any span's error. */
75
75
  export interface TraceOutcome {
76
76
  label: string;
77
77
  doc?: string;
@@ -1,6 +1,6 @@
1
- import { inlineContent, traceAttributeMeta, traceEventAttributeMeta, traceEventMeta } from '@theoremjs/agents';
1
+ import { inlineContent, traceAttributeMeta, traceEventAttributeMeta, traceEventMeta, } from '@theoremjs/agents';
2
2
  import { isRecord } from '@theoremjs/agents/kernel';
3
- import { nanosToMs, traceSpans, traceTotals } from "./trace-view.js";
3
+ import { nanosToMs, traceSpans, traceTotals, } from "./trace-view.js";
4
4
  export function traceTurns(tree) {
5
5
  return tree.map((node) => ({
6
6
  node,
@@ -44,7 +44,7 @@ export function traceActor(node) {
44
44
  return 'theorem';
45
45
  }
46
46
  }
47
- /** Events worth a line of their own: something acted, retried, waited or warned. */
47
+ /** Events worth a line of their own: something acted, retried, waited, or warned. */
48
48
  const STORY_EVENTS = new Set([
49
49
  'theorem.guardrail',
50
50
  'theorem.attempt.retry',
@@ -116,7 +116,7 @@ function storyEvents(node, turnStart) {
116
116
  });
117
117
  }
118
118
  /**
119
- * A turn as it happened: what the user asked, each model call and the tool
119
+ * A turn as it happened: what the user asked, each model call, and the tool
120
120
  * calls it asked for (nested under it), the events that changed the course,
121
121
  * and how it ended. HTTP tries stay in the timeline; they are how a call
122
122
  * travelled, not a step of the story.
@@ -185,7 +185,9 @@ export function toolArguments(node) {
185
185
  }
186
186
  if (!isRecord(value))
187
187
  return [];
188
- return Object.entries(value).flatMap(([key, item]) => typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean' ? [[key, String(item)]] : []);
188
+ return Object.entries(value).flatMap(([key, item]) => typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean'
189
+ ? [[key, String(item)]]
190
+ : []);
189
191
  }
190
192
  /** A stored JSON or text value, parsed when it is JSON. */
191
193
  export function storedValue(node, key) {
@@ -209,14 +211,22 @@ export function traceOutcome(node) {
209
211
  const codePart = typeof code === 'string' ? { code } : {};
210
212
  const tool = outcome !== 'ok' ? optionOf('theorem.tool.outcome', outcome) : undefined;
211
213
  if (tool)
212
- return { ...tool, ...codePart, tone: status.code === 'ERROR' || outcome === 'error' ? 'error' : 'warning' };
214
+ return {
215
+ ...tool,
216
+ ...codePart,
217
+ tone: status.code === 'ERROR' || outcome === 'error' ? 'error' : 'warning',
218
+ };
213
219
  if (status.code !== 'ERROR')
214
220
  return undefined;
215
221
  const type = attributes['error.type'];
216
222
  const kind = optionOf('error.type', type);
217
223
  if (kind)
218
224
  return { ...kind, ...codePart, tone: 'error' };
219
- return { label: typeof type === 'string' ? type : (status.message ?? ''), ...codePart, tone: 'error' };
225
+ return {
226
+ label: typeof type === 'string' ? type : (status.message ?? ''),
227
+ ...codePart,
228
+ tone: 'error',
229
+ };
220
230
  }
221
231
  function guardrailHits(value) {
222
232
  if (!Array.isArray(value))
@@ -226,7 +236,7 @@ function guardrailHits(value) {
226
236
  if (typeof hit.rule !== 'string')
227
237
  return [];
228
238
  const severity = typeof hit.severity === 'string' ? hit.severity : '';
229
- // A host names its own rules on the hit; Theorem's are named in the catalog.
239
+ // why: A host names its own rules on the hit; Theorem's are named in the catalog.
230
240
  const rule = fields?.rule?.options?.[hit.rule];
231
241
  const ruleDoc = typeof hit.doc === 'string' ? hit.doc : rule?.doc;
232
242
  return [
@@ -268,7 +278,9 @@ export function traceGuardrails(root) {
268
278
  action,
269
279
  actionLabel: actionMeta?.label ?? action,
270
280
  ...(actionMeta && { actionDoc: actionMeta.doc }),
271
- ...(typeof attributes.duration_ms === 'number' && { durationMs: attributes.duration_ms }),
281
+ ...(typeof attributes.duration_ms === 'number' && {
282
+ durationMs: attributes.duration_ms,
283
+ }),
272
284
  hits: guardrailHits(attributes.hits),
273
285
  ...(typeof provenance?.tool === 'string' && { tool: provenance.tool }),
274
286
  ...(typeof reason === 'string' && reason && { reason }),
@@ -290,7 +302,9 @@ function union(intervals) {
290
302
  return total;
291
303
  }
292
304
  function intervalsOf(nodes, types) {
293
- return nodes.filter((node) => types.has(node.meta.type)).map((node) => [node.startMs, node.startMs + node.durationMs]);
305
+ return nodes
306
+ .filter((node) => types.has(node.meta.type))
307
+ .map((node) => [node.startMs, node.startMs + node.durationMs]);
294
308
  }
295
309
  const MODEL_TYPES = new Set(['call', 'response', 'decision']);
296
310
  const TOOL_TYPES = new Set(['tool']);
@@ -311,8 +325,10 @@ function eventsMs(nodes, name, key) {
311
325
  * their own share, so the model and tools keep only their own work.
312
326
  */
313
327
  function traceTimeSplit(root) {
314
- // A host's call is its own root: the tool span is the whole trace.
315
- const spans = TOOL_TYPES.has(root.meta.type) ? [root, ...traceSpans(root.children)] : traceSpans(root.children);
328
+ // why: A host's call is its own root: the tool span is the whole trace.
329
+ const spans = TOOL_TYPES.has(root.meta.type)
330
+ ? [root, ...traceSpans(root.children)]
331
+ : traceSpans(root.children);
316
332
  const model = intervalsOf(spans, MODEL_TYPES);
317
333
  const tools = intervalsOf(spans, TOOL_TYPES);
318
334
  const toolNodes = spans.filter((node) => TOOL_TYPES.has(node.meta.type));
@@ -321,10 +337,12 @@ function traceTimeSplit(root) {
321
337
  return total + (typeof value === 'number' ? value : 0);
322
338
  }, 0);
323
339
  const modelMs = union(model);
324
- const toolsMs = union(tools);
340
+ const covered = union([...model, ...tools]);
341
+ // why: A tool that runs an agent holds that agent's model calls: their time is the model's, not the tool's too.
342
+ const toolsMs = covered - modelMs;
325
343
  const toolGuard = Math.min(toolsMs, eventsMs(toolNodes, 'theorem.guardrail', 'duration_ms'));
326
344
  const toolHooks = Math.min(toolsMs - toolGuard, eventsMs(toolNodes, 'theorem.stage', 'hook_ms'));
327
- const between = Math.max(0, root.durationMs - union([...model, ...tools]));
345
+ const between = Math.max(0, root.durationMs - covered);
328
346
  const turnGuard = Math.min(between, eventMs(root, 'theorem.guardrail', 'duration_ms'));
329
347
  const turnHooks = Math.min(between - turnGuard, eventMs(root, 'theorem.stage', 'hook_ms'));
330
348
  return {
@@ -344,7 +362,6 @@ export function traceLatency(root) {
344
362
  const spans = traceSpans([root]);
345
363
  const calls = spans.filter((node) => MODEL_TYPES.has(node.meta.type));
346
364
  const firstTextMs = secondsAttribute(root, 'theorem.turn.time_to_first_text');
347
- // When the model first wrote, measured from the turn's start.
348
365
  const written = calls
349
366
  .map((node) => {
350
367
  const at = secondsAttribute(node, 'theorem.response.time_to_first_text');
@@ -379,14 +396,15 @@ export function traceLatency(root) {
379
396
  if (typeof event.attributes.runs === 'number')
380
397
  streamChecksMs += took;
381
398
  }
382
- // A call's stream checks carry their own time; the call's total adds only what they do not (a trace from before they did).
399
+ // why: A call's stream checks carry their own time; the call's total adds only what they do not (a trace from before they did).
383
400
  const stream = node.span.attributes['theorem.guardrail.stream_ms'];
384
401
  if (typeof stream === 'number')
385
402
  guardMs += Math.max(0, stream - streamChecksMs);
386
403
  }
387
404
  return {
388
405
  ...(firstTextMs !== undefined && { firstTextMs }),
389
- ...(firstTextMs !== undefined && Number.isFinite(firstWritten) && { heldMs: Math.max(0, firstTextMs - firstWritten) }),
406
+ ...(firstTextMs !== undefined &&
407
+ Number.isFinite(firstWritten) && { heldMs: Math.max(0, firstTextMs - firstWritten) }),
390
408
  ...(writingMs > 0 && { tokensPerSecond: tokens / (writingMs / MS_PER_S) }),
391
409
  ...(checks > 0 && { guardrails: { checks, ms: guardMs, flagged } }),
392
410
  };
@@ -408,9 +426,15 @@ export function traceActivity(root, buckets) {
408
426
  const model = intervalsOf(spans, MODEL_TYPES);
409
427
  const tools = intervalsOf(spans, TOOL_TYPES);
410
428
  const width = Math.max(root.durationMs, 1) / buckets;
411
- const share = (intervals, from, to) => Math.min(1, union(intervals.map(([a, b]) => [Math.max(a, from), Math.min(b, to)]).filter(([a, b]) => b > a)) / width);
429
+ const share = (intervals, from, to) => Math.min(1, union(intervals
430
+ .map(([a, b]) => [Math.max(a, from), Math.min(b, to)])
431
+ .filter(([a, b]) => b > a)) / width);
412
432
  return Array.from({ length: buckets + 1 }, (_, index) => {
413
433
  const from = root.startMs + index * width;
414
- return { at: index * width, model: share(model, from, from + width), tools: share(tools, from, from + width) };
434
+ return {
435
+ at: index * width,
436
+ model: share(model, from, from + width),
437
+ tools: share(tools, from, from + width),
438
+ };
415
439
  });
416
440
  }
@@ -95,9 +95,9 @@ export interface TraceFilter {
95
95
  };
96
96
  }
97
97
  /**
98
- * The fields a search over `nodes` offers: type, status, duration and events,
98
+ * The fields a search over `nodes` offers: type, status, duration, and events,
99
99
  * then every attribute the spans recorded that compares as a value. Stored
100
- * text, JSON and structured values are reached by the text field.
100
+ * text, JSON, and structured values are reached by the text field.
101
101
  */
102
102
  export declare function traceSearchFields(nodes: readonly TraceNode[]): TraceSearchField[];
103
103
  /**