@contractspec/example.agent-console 3.7.7 → 3.8.2

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 (186) hide show
  1. package/.turbo/turbo-build.log +126 -105
  2. package/AGENTS.md +3 -1
  3. package/CHANGELOG.md +29 -0
  4. package/README.md +46 -9
  5. package/dist/agent/agent.handler.d.ts +3 -0
  6. package/dist/agent/agent.handler.js +730 -1
  7. package/dist/agent/index.js +73 -72
  8. package/dist/agent.feature.js +179 -0
  9. package/dist/browser/agent/agent.handler.js +730 -1
  10. package/dist/browser/agent/index.js +73 -72
  11. package/dist/browser/agent.feature.js +179 -0
  12. package/dist/browser/docs/agent-console.docblock.js +11 -8
  13. package/dist/browser/docs/index.js +11 -8
  14. package/dist/browser/example.js +2 -3
  15. package/dist/browser/handlers/agent.handlers.js +1883 -2
  16. package/dist/browser/handlers/index.js +2142 -8
  17. package/dist/browser/index.js +3347 -2433
  18. package/dist/browser/presentations/index.js +49 -49
  19. package/dist/browser/run/index.js +818 -812
  20. package/dist/browser/run/run.handler.js +666 -1
  21. package/dist/browser/shared/index.js +293 -1
  22. package/dist/browser/shared/mock-runs.js +5 -0
  23. package/dist/browser/tool/index.js +331 -331
  24. package/dist/browser/tool/tool.handler.js +479 -3
  25. package/dist/browser/ui/AgentDashboard.js +1204 -319
  26. package/dist/browser/ui/AgentDashboard.visualizations.js +217 -0
  27. package/dist/browser/ui/AgentRunList.js +359 -127
  28. package/dist/browser/ui/hooks/index.js +468 -18
  29. package/dist/browser/ui/hooks/useAgentMutations.js +443 -8
  30. package/dist/browser/ui/hooks/useRunList.js +25 -10
  31. package/dist/browser/ui/index.js +1293 -390
  32. package/dist/browser/ui/renderers/agent-list.markdown.js +14 -5
  33. package/dist/browser/ui/renderers/dashboard.markdown.js +207 -36
  34. package/dist/browser/ui/renderers/index.js +245 -49
  35. package/dist/browser/ui/renderers/run-list.markdown.js +9 -4
  36. package/dist/browser/ui/renderers/tool-registry.markdown.js +15 -4
  37. package/dist/browser/ui/views/RunDataTable.js +326 -0
  38. package/dist/browser/ui/views/RunListView.js +359 -127
  39. package/dist/browser/ui/views/index.js +406 -174
  40. package/dist/browser/ui/views/run-data-table.columns.js +271 -0
  41. package/dist/browser/ui/views/run-list.shared.js +177 -0
  42. package/dist/browser/visualizations/catalog.js +134 -0
  43. package/dist/browser/visualizations/index.js +187 -0
  44. package/dist/browser/visualizations/selectors.js +181 -0
  45. package/dist/docs/agent-console.docblock.js +11 -8
  46. package/dist/docs/index.js +11 -8
  47. package/dist/example.js +2 -3
  48. package/dist/example.test.d.ts +1 -0
  49. package/dist/handlers/agent.handlers.d.ts +2 -0
  50. package/dist/handlers/agent.handlers.js +1883 -2
  51. package/dist/handlers/index.d.ts +1 -3
  52. package/dist/handlers/index.js +2142 -8
  53. package/dist/handlers/mock-handlers.test.d.ts +1 -0
  54. package/dist/index.d.ts +2 -0
  55. package/dist/index.js +3347 -2433
  56. package/dist/node/agent/agent.handler.js +730 -1
  57. package/dist/node/agent/index.js +73 -72
  58. package/dist/node/agent.feature.js +179 -0
  59. package/dist/node/docs/agent-console.docblock.js +11 -8
  60. package/dist/node/docs/index.js +11 -8
  61. package/dist/node/example.js +2 -3
  62. package/dist/node/handlers/agent.handlers.js +1883 -2
  63. package/dist/node/handlers/index.js +2142 -8
  64. package/dist/node/index.js +3347 -2433
  65. package/dist/node/presentations/index.js +49 -49
  66. package/dist/node/run/index.js +818 -812
  67. package/dist/node/run/run.handler.js +666 -1
  68. package/dist/node/shared/index.js +293 -1
  69. package/dist/node/shared/mock-runs.js +5 -0
  70. package/dist/node/tool/index.js +331 -331
  71. package/dist/node/tool/tool.handler.js +479 -3
  72. package/dist/node/ui/AgentDashboard.js +1204 -319
  73. package/dist/node/ui/AgentDashboard.visualizations.js +217 -0
  74. package/dist/node/ui/AgentRunList.js +359 -127
  75. package/dist/node/ui/hooks/index.js +468 -18
  76. package/dist/node/ui/hooks/useAgentMutations.js +443 -8
  77. package/dist/node/ui/hooks/useRunList.js +25 -10
  78. package/dist/node/ui/index.js +1293 -390
  79. package/dist/node/ui/renderers/agent-list.markdown.js +14 -5
  80. package/dist/node/ui/renderers/dashboard.markdown.js +207 -36
  81. package/dist/node/ui/renderers/index.js +245 -49
  82. package/dist/node/ui/renderers/run-list.markdown.js +9 -4
  83. package/dist/node/ui/renderers/tool-registry.markdown.js +15 -4
  84. package/dist/node/ui/views/RunDataTable.js +326 -0
  85. package/dist/node/ui/views/RunListView.js +359 -127
  86. package/dist/node/ui/views/index.js +406 -174
  87. package/dist/node/ui/views/run-data-table.columns.js +271 -0
  88. package/dist/node/ui/views/run-list.shared.js +177 -0
  89. package/dist/node/visualizations/catalog.js +134 -0
  90. package/dist/node/visualizations/index.js +187 -0
  91. package/dist/node/visualizations/selectors.js +181 -0
  92. package/dist/presentations/index.js +49 -49
  93. package/dist/proof/index.d.ts +2 -0
  94. package/dist/proof/meetup-proof.d.ts +10 -0
  95. package/dist/proof/meetup-proof.runtime.d.ts +22 -0
  96. package/dist/proof/meetup-proof.scenario.d.ts +2 -0
  97. package/dist/proof/meetup-proof.suite.d.ts +1 -0
  98. package/dist/proof/meetup-proof.test.d.ts +1 -0
  99. package/dist/run/index.js +818 -812
  100. package/dist/run/run.handler.d.ts +4 -0
  101. package/dist/run/run.handler.js +666 -1
  102. package/dist/shared/demo-dashboard-data.d.ts +16 -0
  103. package/dist/shared/demo-runtime-seed.d.ts +17 -0
  104. package/dist/shared/demo-runtime.d.ts +8 -0
  105. package/dist/shared/demo-runtime.test.d.ts +1 -0
  106. package/dist/shared/index.d.ts +3 -0
  107. package/dist/shared/index.js +293 -1
  108. package/dist/shared/mock-runs.d.ts +4 -0
  109. package/dist/shared/mock-runs.js +5 -0
  110. package/dist/tool/index.js +331 -331
  111. package/dist/tool/tool.handler.d.ts +4 -1
  112. package/dist/tool/tool.handler.js +479 -3
  113. package/dist/ui/AgentDashboard.js +1204 -319
  114. package/dist/ui/AgentDashboard.sandbox.test.d.ts +1 -0
  115. package/dist/ui/AgentDashboard.visualizations.d.ts +4 -0
  116. package/dist/ui/AgentDashboard.visualizations.js +218 -0
  117. package/dist/ui/AgentRunList.js +359 -127
  118. package/dist/ui/hooks/index.js +468 -18
  119. package/dist/ui/hooks/useAgentMutations.js +443 -8
  120. package/dist/ui/hooks/useRunList.d.ts +8 -2
  121. package/dist/ui/hooks/useRunList.js +25 -10
  122. package/dist/ui/index.js +1293 -390
  123. package/dist/ui/renderers/agent-list.markdown.js +14 -5
  124. package/dist/ui/renderers/dashboard.markdown.js +207 -36
  125. package/dist/ui/renderers/index.js +245 -49
  126. package/dist/ui/renderers/run-list.markdown.js +9 -4
  127. package/dist/ui/renderers/tool-registry.markdown.d.ts +1 -1
  128. package/dist/ui/renderers/tool-registry.markdown.js +15 -4
  129. package/dist/ui/views/RunDataTable.d.ts +18 -0
  130. package/dist/ui/views/RunDataTable.js +327 -0
  131. package/dist/ui/views/RunListView.js +359 -127
  132. package/dist/ui/views/index.js +406 -174
  133. package/dist/ui/views/run-data-table.columns.d.ts +3 -0
  134. package/dist/ui/views/run-data-table.columns.js +272 -0
  135. package/dist/ui/views/run-list.shared.d.ts +14 -0
  136. package/dist/ui/views/run-list.shared.js +178 -0
  137. package/dist/visualizations/catalog.d.ts +10 -0
  138. package/dist/visualizations/catalog.js +135 -0
  139. package/dist/visualizations/index.d.ts +2 -0
  140. package/dist/visualizations/index.js +188 -0
  141. package/dist/visualizations/selectors.d.ts +3 -0
  142. package/dist/visualizations/selectors.js +182 -0
  143. package/dist/visualizations/selectors.test.d.ts +1 -0
  144. package/package.json +112 -10
  145. package/proofs/agent-console-meetup.replay.json +220 -0
  146. package/src/agent/agent.handler.ts +18 -1
  147. package/src/agent.feature.ts +3 -0
  148. package/src/docs/agent-console.docblock.ts +11 -8
  149. package/src/example.test.ts +75 -0
  150. package/src/example.ts +2 -3
  151. package/src/handlers/agent.handlers.ts +55 -2
  152. package/src/handlers/index.ts +18 -2
  153. package/src/handlers/mock-handlers.test.ts +77 -0
  154. package/src/index.ts +2 -0
  155. package/src/proof/index.ts +2 -0
  156. package/src/proof/meetup-proof.runtime.ts +196 -0
  157. package/src/proof/meetup-proof.scenario.ts +99 -0
  158. package/src/proof/meetup-proof.suite.ts +29 -0
  159. package/src/proof/meetup-proof.test.ts +28 -0
  160. package/src/proof/meetup-proof.ts +130 -0
  161. package/src/run/run.handler.ts +17 -1
  162. package/src/shared/demo-dashboard-data.ts +58 -0
  163. package/src/shared/demo-runtime-seed.ts +139 -0
  164. package/src/shared/demo-runtime.test.ts +169 -0
  165. package/src/shared/demo-runtime.ts +260 -0
  166. package/src/shared/index.ts +11 -0
  167. package/src/shared/mock-runs.ts +5 -0
  168. package/src/tool/tool.handler.ts +21 -4
  169. package/src/ui/AgentDashboard.sandbox.test.tsx +312 -0
  170. package/src/ui/AgentDashboard.tsx +4 -1
  171. package/src/ui/AgentDashboard.visualizations.tsx +35 -0
  172. package/src/ui/hooks/useAgentMutations.ts +19 -11
  173. package/src/ui/hooks/useRunList.ts +41 -9
  174. package/src/ui/renderers/agent-list.markdown.ts +31 -12
  175. package/src/ui/renderers/dashboard.markdown.ts +37 -42
  176. package/src/ui/renderers/run-list.markdown.ts +16 -8
  177. package/src/ui/renderers/tool-registry.markdown.ts +21 -9
  178. package/src/ui/views/RunDataTable.tsx +74 -0
  179. package/src/ui/views/RunListView.tsx +37 -111
  180. package/src/ui/views/run-data-table.columns.tsx +102 -0
  181. package/src/ui/views/run-list.shared.tsx +139 -0
  182. package/src/visualizations/catalog.ts +132 -0
  183. package/src/visualizations/index.ts +2 -0
  184. package/src/visualizations/selectors.test.ts +12 -0
  185. package/src/visualizations/selectors.ts +70 -0
  186. package/tsdown.config.js +17 -0
@@ -0,0 +1,312 @@
1
+ import { afterEach, beforeAll, describe, expect, it } from 'bun:test';
2
+ import {
3
+ type TemplateDefinition,
4
+ TemplateRuntimeContext,
5
+ type TemplateRuntimeContextValue,
6
+ } from '@contractspec/lib.example-shared-ui';
7
+ import Window from 'happy-dom/lib/window/Window.js';
8
+ import { act } from 'react';
9
+ import { createRoot, type Root } from 'react-dom/client';
10
+ import type { AgentHandlers } from '../handlers/agent.handlers';
11
+ import {
12
+ AGENT_CONSOLE_DEMO_ORGANIZATION_ID,
13
+ createAgentConsoleDemoHandlers,
14
+ } from '../shared';
15
+ import { AgentDashboard } from './AgentDashboard';
16
+
17
+ const PROJECT_ID = 'agent-console-sandbox-smoke';
18
+ const TEMPLATE: TemplateDefinition = {
19
+ id: 'agent-console',
20
+ name: 'Agent Console',
21
+ description: 'Deterministic sandbox smoke test template.',
22
+ category: 'ai',
23
+ complexity: 'intermediate',
24
+ icon: '🤖',
25
+ features: ['agents', 'runs', 'tools', 'metrics'],
26
+ tags: ['sandbox', 'smoke'],
27
+ schema: { models: ['Agent', 'Run', 'Tool'], contracts: [] },
28
+ components: {
29
+ list: 'AgentListView',
30
+ detail: 'RunListView',
31
+ form: 'CreateAgentModal',
32
+ },
33
+ };
34
+
35
+ beforeAll(() => {
36
+ const windowInstance = new Window({
37
+ url: 'https://sandbox.contractspec.local/sandbox',
38
+ });
39
+ Object.defineProperty(windowInstance, 'SyntaxError', {
40
+ value: SyntaxError,
41
+ configurable: true,
42
+ });
43
+ Object.assign(globalThis, {
44
+ window: windowInstance,
45
+ document: windowInstance.document,
46
+ navigator: windowInstance.navigator,
47
+ HTMLElement: windowInstance.HTMLElement,
48
+ HTMLButtonElement: windowInstance.HTMLButtonElement,
49
+ HTMLInputElement: windowInstance.HTMLInputElement,
50
+ HTMLTextAreaElement: windowInstance.HTMLTextAreaElement,
51
+ Node: windowInstance.Node,
52
+ Event: windowInstance.Event,
53
+ MouseEvent: windowInstance.MouseEvent,
54
+ KeyboardEvent: windowInstance.KeyboardEvent,
55
+ MutationObserver: windowInstance.MutationObserver,
56
+ getComputedStyle: windowInstance.getComputedStyle.bind(windowInstance),
57
+ requestAnimationFrame: (callback: FrameRequestCallback) =>
58
+ setTimeout(() => callback(Date.now()), 0),
59
+ cancelAnimationFrame: (id: number) => clearTimeout(id),
60
+ IS_REACT_ACT_ENVIRONMENT: true,
61
+ });
62
+ });
63
+
64
+ afterEach(() => {
65
+ document.body.innerHTML = '';
66
+ });
67
+
68
+ function createContextValue(
69
+ handlers: AgentHandlers
70
+ ): TemplateRuntimeContextValue<{ agent: AgentHandlers }> {
71
+ return {
72
+ template: TEMPLATE,
73
+ runtime: {},
74
+ installer: {
75
+ install: async () => {},
76
+ saveToStudio: async () => ({ projectId: PROJECT_ID, status: 'saved' }),
77
+ },
78
+ client: {} as never,
79
+ templateId: TEMPLATE.id,
80
+ projectId: PROJECT_ID,
81
+ engine: {} as never,
82
+ fetchData: async () => ({ data: null }),
83
+ handlers: { agent: handlers },
84
+ };
85
+ }
86
+
87
+ async function renderDashboard() {
88
+ const container = document.createElement('div');
89
+ document.body.append(container);
90
+ const root: Root = createRoot(container);
91
+ const handlers = createAgentConsoleDemoHandlers({
92
+ projectId: PROJECT_ID,
93
+ organizationId: AGENT_CONSOLE_DEMO_ORGANIZATION_ID,
94
+ idFactory: (() => {
95
+ const counters = { agent: 0, run: 0 };
96
+ return (kind: 'agent' | 'run') => `${kind}-sandbox-${++counters[kind]}`;
97
+ })(),
98
+ });
99
+
100
+ await act(async () => {
101
+ root.render(
102
+ <TemplateRuntimeContext.Provider value={createContextValue(handlers)}>
103
+ <AgentDashboard />
104
+ </TemplateRuntimeContext.Provider>
105
+ );
106
+ });
107
+
108
+ return { container, root };
109
+ }
110
+
111
+ async function waitFor(assertion: () => boolean, timeoutMs = 3000) {
112
+ const startedAt = Date.now();
113
+ while (Date.now() - startedAt < timeoutMs) {
114
+ if (assertion()) {
115
+ return;
116
+ }
117
+ await act(async () => {
118
+ await new Promise((resolve) => setTimeout(resolve, 25));
119
+ });
120
+ }
121
+ throw new Error('Timed out waiting for dashboard state.');
122
+ }
123
+
124
+ function findButton(container: HTMLElement, label: string) {
125
+ return [...container.getElementsByTagName('button')].find((element) =>
126
+ element.textContent?.includes(label)
127
+ );
128
+ }
129
+
130
+ function findAgentCard(container: HTMLElement, name: string) {
131
+ return [...container.getElementsByTagName('*')].find(
132
+ (element) =>
133
+ element.getAttribute('role') === 'button' &&
134
+ element.textContent?.includes(name)
135
+ );
136
+ }
137
+
138
+ function getReactProp<T>(element: Element, propName: string): T | undefined {
139
+ const record = element as unknown as Record<string, unknown>;
140
+ const reactPropsKey = Object.keys(record).find((key) =>
141
+ key.startsWith('__reactProps$')
142
+ );
143
+ if (!reactPropsKey) {
144
+ return undefined;
145
+ }
146
+ const props = record[reactPropsKey];
147
+ if (typeof props !== 'object' || props === null || !(propName in props)) {
148
+ return undefined;
149
+ }
150
+ const value = (props as Record<string, unknown>)[propName];
151
+ return value as T | undefined;
152
+ }
153
+
154
+ async function click(element: Element | null | undefined) {
155
+ if (!element) {
156
+ throw new Error('Expected clickable element.');
157
+ }
158
+ const onPress = getReactProp<(() => void) | undefined>(element, 'onPress');
159
+ const onClick = getReactProp<((event: MouseEvent) => void) | undefined>(
160
+ element,
161
+ 'onClick'
162
+ );
163
+ await act(async () => {
164
+ if (typeof onPress === 'function') {
165
+ onPress();
166
+ return;
167
+ }
168
+ if (typeof onClick === 'function') {
169
+ onClick(new MouseEvent('click', { bubbles: true }));
170
+ return;
171
+ }
172
+ if ('click' in element && typeof element.click === 'function') {
173
+ element.click();
174
+ return;
175
+ }
176
+ element.dispatchEvent(new MouseEvent('click', { bubbles: true }));
177
+ });
178
+ }
179
+
180
+ async function fill(selector: string, value: string) {
181
+ const element = document.querySelector<
182
+ HTMLInputElement | HTMLTextAreaElement
183
+ >(selector);
184
+ if (!element) {
185
+ throw new Error(`Missing form field ${selector}.`);
186
+ }
187
+ const valueSetter = Object.getOwnPropertyDescriptor(
188
+ Object.getPrototypeOf(element),
189
+ 'value'
190
+ )?.set;
191
+ if (!valueSetter) {
192
+ throw new Error(`Missing value setter for ${selector}.`);
193
+ }
194
+ const onChange = getReactProp<
195
+ (event: {
196
+ target: { value: string };
197
+ currentTarget: { value: string };
198
+ }) => void
199
+ >(element, 'onChange');
200
+ await act(async () => {
201
+ valueSetter.call(element, value);
202
+ onChange?.({
203
+ target: { value },
204
+ currentTarget: { value },
205
+ });
206
+ if (!onChange) {
207
+ element.dispatchEvent(new Event('input', { bubbles: true }));
208
+ element.dispatchEvent(new Event('change', { bubbles: true }));
209
+ }
210
+ });
211
+ }
212
+
213
+ describe('AgentDashboard sandbox smoke', () => {
214
+ it('loads seeded state, renders tabs, creates an agent, and executes a run', async () => {
215
+ const { container, root } = await renderDashboard();
216
+ await waitFor(
217
+ () => container.textContent?.includes('AI Agent Console') === true
218
+ );
219
+ await waitFor(
220
+ () =>
221
+ container.textContent?.includes(
222
+ 'Affichage de 1 à 3 sur 5 résultats'
223
+ ) === true
224
+ );
225
+
226
+ expect(container.textContent).toContain('Runs');
227
+ expect(container.textContent).toContain('Agents');
228
+ expect(container.textContent).toContain('Tools');
229
+ expect(container.textContent).toContain('Metrics');
230
+ expect(container.textContent).toContain('Run History');
231
+
232
+ await click(findButton(container, 'Tools'));
233
+ await waitFor(
234
+ () => container.textContent?.includes('Total Tools') === true
235
+ );
236
+ await click(findButton(container, 'Metrics'));
237
+ await waitFor(
238
+ () => container.textContent?.includes('Usage Analytics') === true
239
+ );
240
+ await click(findButton(container, 'Agents'));
241
+ await waitFor(() => container.textContent?.includes('Total: 4') === true);
242
+
243
+ await click(findButton(container, 'New Agent'));
244
+ await waitFor(
245
+ () => document.body.textContent?.includes('Create New Agent') === true
246
+ );
247
+ await fill('#agent-name', 'Paris Meetup UI Agent');
248
+ await fill(
249
+ '#agent-description',
250
+ 'Smoke test agent for the sandbox walkthrough.'
251
+ );
252
+ await click(findButton(document.body, 'Create Agent'));
253
+ await click(findButton(container, 'Runs'));
254
+ await waitFor(
255
+ () =>
256
+ container.textContent?.includes(
257
+ 'Affichage de 1 à 3 sur 5 résultats'
258
+ ) === true
259
+ );
260
+ await click(findButton(container, 'Agents'));
261
+ await waitFor(
262
+ () => container.textContent?.includes('Paris Meetup UI Agent') === true
263
+ );
264
+ await waitFor(() => container.textContent?.includes('Total: 5') === true);
265
+
266
+ const agentCard = findAgentCard(container, 'Paris Meetup UI Agent');
267
+ await click(agentCard);
268
+ await waitFor(
269
+ () => document.body.textContent?.includes('Activate Agent') === true
270
+ );
271
+ await click(findButton(document.body, 'Activate Agent'));
272
+ await click(findButton(container, 'Runs'));
273
+ await waitFor(
274
+ () => container.textContent?.includes('Run History') === true
275
+ );
276
+ await click(findButton(container, 'Agents'));
277
+ await waitFor(
278
+ () =>
279
+ findAgentCard(
280
+ container,
281
+ 'Paris Meetup UI Agent'
282
+ )?.textContent?.includes('ACTIVE') === true
283
+ );
284
+
285
+ const activeAgentCard = findAgentCard(container, 'Paris Meetup UI Agent');
286
+ await click(activeAgentCard);
287
+ await waitFor(
288
+ () => document.body.textContent?.includes('Execute Agent') === true
289
+ );
290
+ await click(findButton(document.body, 'Execute Agent'));
291
+ await waitFor(
292
+ () => document.body.textContent?.includes('Message *') === true
293
+ );
294
+ await fill('#execute-message', 'Summarize the meetup smoke test.');
295
+ await click(findButton(document.body, 'Execute'));
296
+
297
+ await click(findButton(container, 'Runs'));
298
+ await waitFor(
299
+ () =>
300
+ container.textContent?.includes(
301
+ 'Affichage de 1 à 3 sur 6 résultats'
302
+ ) === true
303
+ );
304
+ await waitFor(
305
+ () => container.textContent?.includes('Paris Meetup UI Agent') === true
306
+ );
307
+
308
+ await act(async () => {
309
+ root.unmount();
310
+ });
311
+ });
312
+ });
@@ -17,6 +17,7 @@ import {
17
17
  * - ExecuteAgentCommand -> Execute agent via modal
18
18
  */
19
19
  import { useCallback, useMemo, useState } from 'react';
20
+ import { AgentVisualizationOverview } from './AgentDashboard.visualizations';
20
21
  import { type Agent, useAgentList } from './hooks/useAgentList';
21
22
  import { useAgentMutations } from './hooks/useAgentMutations';
22
23
  import { type RunMetrics, useRunList } from './hooks/useRunList';
@@ -34,7 +35,7 @@ export function AgentDashboard() {
34
35
  const [selectedAgent, setSelectedAgent] = useState<Agent | null>(null);
35
36
  const [isAgentActionsOpen, setIsAgentActionsOpen] = useState(false);
36
37
 
37
- const { metrics, refetch: refetchRuns } = useRunList();
38
+ const { data: runData, metrics, refetch: refetchRuns } = useRunList();
38
39
  const { refetch: refetchAgents } = useAgentList();
39
40
 
40
41
  const mutations = useAgentMutations({
@@ -115,6 +116,8 @@ export function AgentDashboard() {
115
116
  ))}
116
117
  </StatCardGroup>
117
118
 
119
+ <AgentVisualizationOverview runs={runData?.items ?? []} />
120
+
118
121
  {/* Navigation Tabs */}
119
122
  <nav className="flex gap-1 rounded-lg bg-muted p-1" role="tablist">
120
123
  {tabs.map((tab) => (
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+
3
+ import {
4
+ VisualizationCard,
5
+ VisualizationGrid,
6
+ } from '@contractspec/lib.design-system';
7
+ import { createAgentVisualizationItems } from '../visualizations';
8
+ import type { Run } from './hooks/useRunList';
9
+
10
+ export function AgentVisualizationOverview({ runs }: { runs: Run[] }) {
11
+ const items = createAgentVisualizationItems(runs);
12
+
13
+ return (
14
+ <section className="space-y-3">
15
+ <div>
16
+ <h3 className="font-semibold text-lg">Operational Visualizations</h3>
17
+ <p className="text-muted-foreground text-sm">
18
+ Contract-backed charts derived from recent run activity.
19
+ </p>
20
+ </div>
21
+ <VisualizationGrid>
22
+ {items.map((item) => (
23
+ <VisualizationCard
24
+ key={item.key}
25
+ data={item.data}
26
+ description={item.description}
27
+ height={item.height}
28
+ spec={item.spec}
29
+ title={item.title}
30
+ />
31
+ ))}
32
+ </VisualizationGrid>
33
+ </section>
34
+ );
35
+ }
@@ -15,6 +15,7 @@ import type {
15
15
  Run,
16
16
  UpdateAgentInput,
17
17
  } from '../../handlers/agent.handlers';
18
+ import { AGENT_CONSOLE_DEMO_ORGANIZATION_ID } from '../../shared/demo-runtime-seed';
18
19
 
19
20
  export interface MutationState<T> {
20
21
  loading: boolean;
@@ -27,6 +28,13 @@ export interface UseAgentMutationsOptions {
27
28
  onError?: (error: Error) => void;
28
29
  }
29
30
 
31
+ function normalizeMutationError(
32
+ error: unknown,
33
+ fallbackMessage: string
34
+ ): Error {
35
+ return error instanceof Error ? error : new Error(fallbackMessage);
36
+ }
37
+
30
38
  export function useAgentMutations(options: UseAgentMutationsOptions = {}) {
31
39
  const { handlers, projectId } = useTemplateRuntime<{
32
40
  agent: AgentHandlers;
@@ -60,17 +68,16 @@ export function useAgentMutations(options: UseAgentMutationsOptions = {}) {
60
68
  try {
61
69
  const result = await agent.createAgent(input, {
62
70
  projectId,
63
- organizationId: 'demo-org',
71
+ organizationId: AGENT_CONSOLE_DEMO_ORGANIZATION_ID,
64
72
  });
65
73
  setCreateState({ loading: false, error: null, data: result });
66
74
  options.onSuccess?.();
67
75
  return result;
68
76
  } catch (err) {
69
- const error =
70
- err instanceof Error ? err : new Error('Failed to create agent');
77
+ const error = normalizeMutationError(err, 'Failed to create agent');
71
78
  setCreateState({ loading: false, error, data: null });
72
79
  options.onError?.(error);
73
- return null;
80
+ throw error;
74
81
  }
75
82
  },
76
83
  [agent, projectId, options]
@@ -88,11 +95,10 @@ export function useAgentMutations(options: UseAgentMutationsOptions = {}) {
88
95
  options.onSuccess?.();
89
96
  return result;
90
97
  } catch (err) {
91
- const error =
92
- err instanceof Error ? err : new Error('Failed to update agent');
98
+ const error = normalizeMutationError(err, 'Failed to update agent');
93
99
  setUpdateState({ loading: false, error, data: null });
94
100
  options.onError?.(error);
95
- return null;
101
+ throw error;
96
102
  }
97
103
  },
98
104
  [agent, options]
@@ -141,17 +147,19 @@ export function useAgentMutations(options: UseAgentMutationsOptions = {}) {
141
147
  const result = await agent.executeAgent({
142
148
  agentId: input.agentId,
143
149
  message: input.message,
144
- context: { projectId, organizationId: 'demo-org' },
150
+ context: {
151
+ projectId,
152
+ organizationId: AGENT_CONSOLE_DEMO_ORGANIZATION_ID,
153
+ },
145
154
  });
146
155
  setExecuteState({ loading: false, error: null, data: result });
147
156
  options.onSuccess?.();
148
157
  return result;
149
158
  } catch (err) {
150
- const error =
151
- err instanceof Error ? err : new Error('Failed to execute agent');
159
+ const error = normalizeMutationError(err, 'Failed to execute agent');
152
160
  setExecuteState({ loading: false, error, data: null });
153
161
  options.onError?.(error);
154
- return null;
162
+ throw error;
155
163
  }
156
164
  },
157
165
  [agent, projectId, options]
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { useTemplateRuntime } from '@contractspec/lib.example-shared-ui';
8
+ import type { ContractTableSort } from '@contractspec/lib.presentation-runtime-core';
8
9
  import { useCallback, useEffect, useState } from 'react';
9
10
  import type {
10
11
  AgentHandlers,
@@ -22,6 +23,9 @@ export interface UseRunListOptions {
22
23
  agentId?: string;
23
24
  status?: Run['status'] | 'all';
24
25
  limit?: number;
26
+ pageIndex?: number;
27
+ pageSize?: number;
28
+ sorting?: ContractTableSort[];
25
29
  }
26
30
 
27
31
  export function useRunList(options: UseRunListOptions = {}) {
@@ -34,7 +38,11 @@ export function useRunList(options: UseRunListOptions = {}) {
34
38
  const [metrics, setMetrics] = useState<RunMetrics | null>(null);
35
39
  const [loading, setLoading] = useState(true);
36
40
  const [error, setError] = useState<Error | null>(null);
37
- const [page, setPage] = useState(1);
41
+ const [internalPageIndex, setInternalPageIndex] = useState(0);
42
+
43
+ const pageSize = options.pageSize ?? options.limit ?? 20;
44
+ const pageIndex = options.pageIndex ?? internalPageIndex;
45
+ const [sort] = options.sorting ?? [];
38
46
 
39
47
  const fetchData = useCallback(async () => {
40
48
  setLoading(true);
@@ -46,14 +54,21 @@ export function useRunList(options: UseRunListOptions = {}) {
46
54
  projectId,
47
55
  agentId: options.agentId,
48
56
  status: options.status === 'all' ? undefined : options.status,
49
- limit: options.limit ?? 20,
50
- offset: (page - 1) * (options.limit ?? 20),
57
+ sortBy: sort?.id as
58
+ | 'queuedAt'
59
+ | 'totalTokens'
60
+ | 'durationMs'
61
+ | 'estimatedCostUsd'
62
+ | 'status'
63
+ | 'agentName'
64
+ | undefined,
65
+ sortDirection: sort ? (sort.desc ? 'desc' : 'asc') : undefined,
66
+ limit: pageSize,
67
+ offset: pageIndex * pageSize,
51
68
  }),
52
69
  agent.getRunMetrics({
53
70
  projectId,
54
71
  agentId: options.agentId,
55
- startDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), // 30 days ago
56
- endDate: new Date(),
57
72
  }),
58
73
  ]);
59
74
  setData(runsResult);
@@ -63,20 +78,37 @@ export function useRunList(options: UseRunListOptions = {}) {
63
78
  } finally {
64
79
  setLoading(false);
65
80
  }
66
- }, [agent, projectId, options.agentId, options.status, options.limit, page]);
81
+ }, [
82
+ agent,
83
+ pageIndex,
84
+ pageSize,
85
+ projectId,
86
+ options.agentId,
87
+ options.status,
88
+ sort?.desc,
89
+ sort?.id,
90
+ ]);
67
91
 
68
92
  useEffect(() => {
69
93
  fetchData();
70
94
  }, [fetchData]);
71
95
 
96
+ const hasControlledPagination = options.pageIndex !== undefined;
97
+
72
98
  return {
73
99
  data,
74
100
  metrics,
75
101
  loading,
76
102
  error,
77
- page,
103
+ page: pageIndex + 1,
104
+ pageIndex,
105
+ pageSize,
78
106
  refetch: fetchData,
79
- nextPage: () => setPage((p) => p + 1),
80
- prevPage: () => page > 1 && setPage((p) => p - 1),
107
+ nextPage: hasControlledPagination
108
+ ? undefined
109
+ : () => setInternalPageIndex((current) => current + 1),
110
+ prevPage: hasControlledPagination
111
+ ? undefined
112
+ : () => setInternalPageIndex((current) => Math.max(0, current - 1)),
81
113
  };
82
114
  }
@@ -5,13 +5,20 @@
5
5
  */
6
6
 
7
7
  import {
8
- type AgentSummary,
9
- mockListAgentsHandler,
10
- } from '@contractspec/example.agent-console/handlers';
8
+ AGENT_CONSOLE_DEMO_ORGANIZATION_ID,
9
+ AGENT_CONSOLE_DEMO_PROJECT_ID,
10
+ createAgentConsoleDemoHandlers,
11
+ } from '@contractspec/example.agent-console/shared';
11
12
  import type { PresentationSpec } from '@contractspec/lib.contracts-spec/presentations';
12
13
  import type { PresentationRenderer } from '@contractspec/lib.contracts-spec/presentations/transform-engine';
13
14
 
14
- type Agent = AgentSummary;
15
+ interface AgentListItem {
16
+ name: string;
17
+ description?: string;
18
+ status: string;
19
+ modelProvider: string;
20
+ modelName: string;
21
+ }
15
22
 
16
23
  /**
17
24
  * Markdown renderer for agent-console.agent.list presentation
@@ -22,7 +29,7 @@ export const agentListMarkdownRenderer: PresentationRenderer<{
22
29
  body: string;
23
30
  }> = {
24
31
  target: 'markdown',
25
- render: async (desc: PresentationSpec) => {
32
+ render: async (desc: PresentationSpec, ctx) => {
26
33
  // Only handle AgentListView
27
34
  if (
28
35
  desc.source.type !== 'component' ||
@@ -31,12 +38,24 @@ export const agentListMarkdownRenderer: PresentationRenderer<{
31
38
  throw new Error('agentListMarkdownRenderer: not AgentListView');
32
39
  }
33
40
 
34
- // Fetch data using mock handler
35
- const data = await mockListAgentsHandler({
36
- organizationId: 'demo-org',
37
- limit: 50,
38
- offset: 0,
39
- });
41
+ const data: {
42
+ items: AgentListItem[];
43
+ total: number;
44
+ hasMore: boolean;
45
+ } = Array.isArray(ctx?.data)
46
+ ? {
47
+ items: ctx.data as AgentListItem[],
48
+ total: ctx.data.length,
49
+ hasMore: false,
50
+ }
51
+ : await createAgentConsoleDemoHandlers({
52
+ projectId: AGENT_CONSOLE_DEMO_PROJECT_ID,
53
+ }).listAgents({
54
+ projectId: AGENT_CONSOLE_DEMO_PROJECT_ID,
55
+ organizationId: AGENT_CONSOLE_DEMO_ORGANIZATION_ID,
56
+ limit: 50,
57
+ offset: 0,
58
+ });
40
59
 
41
60
  // Generate markdown
42
61
  const lines: string[] = [
@@ -51,7 +70,7 @@ export const agentListMarkdownRenderer: PresentationRenderer<{
51
70
  ];
52
71
 
53
72
  // Group by status
54
- const byStatus: Record<string, Agent[]> = {};
73
+ const byStatus: Record<string, AgentListItem[]> = {};
55
74
  for (const agent of data.items) {
56
75
  const status = agent.status;
57
76
  if (byStatus[status]) {