@nxisai/agent-harness 0.2.0

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 (126) hide show
  1. package/README.md +434 -0
  2. package/dist/a2ui/cards.d.ts +55 -0
  3. package/dist/a2ui/cards.d.ts.map +1 -0
  4. package/dist/a2ui/cards.js +133 -0
  5. package/dist/a2ui/cards.js.map +1 -0
  6. package/dist/a2ui/emit.d.ts +33 -0
  7. package/dist/a2ui/emit.d.ts.map +1 -0
  8. package/dist/a2ui/emit.js +116 -0
  9. package/dist/a2ui/emit.js.map +1 -0
  10. package/dist/a2ui/form.d.ts +20 -0
  11. package/dist/a2ui/form.d.ts.map +1 -0
  12. package/dist/a2ui/form.js +92 -0
  13. package/dist/a2ui/form.js.map +1 -0
  14. package/dist/a2ui/host.d.ts +34 -0
  15. package/dist/a2ui/host.d.ts.map +1 -0
  16. package/dist/a2ui/host.js +47 -0
  17. package/dist/a2ui/host.js.map +1 -0
  18. package/dist/a2ui/index.d.ts +23 -0
  19. package/dist/a2ui/index.d.ts.map +1 -0
  20. package/dist/a2ui/index.js +20 -0
  21. package/dist/a2ui/index.js.map +1 -0
  22. package/dist/a2ui/stepper.d.ts +26 -0
  23. package/dist/a2ui/stepper.d.ts.map +1 -0
  24. package/dist/a2ui/stepper.js +162 -0
  25. package/dist/a2ui/stepper.js.map +1 -0
  26. package/dist/a2ui/theme.d.ts +21 -0
  27. package/dist/a2ui/theme.d.ts.map +1 -0
  28. package/dist/a2ui/theme.js +134 -0
  29. package/dist/a2ui/theme.js.map +1 -0
  30. package/dist/a2ui/to-v09.d.ts +31 -0
  31. package/dist/a2ui/to-v09.d.ts.map +1 -0
  32. package/dist/a2ui/to-v09.js +104 -0
  33. package/dist/a2ui/to-v09.js.map +1 -0
  34. package/dist/engine.d.ts +209 -0
  35. package/dist/engine.d.ts.map +1 -0
  36. package/dist/engine.js +505 -0
  37. package/dist/engine.js.map +1 -0
  38. package/dist/harness/harness.d.ts +284 -0
  39. package/dist/harness/harness.d.ts.map +1 -0
  40. package/dist/harness/harness.js +568 -0
  41. package/dist/harness/harness.js.map +1 -0
  42. package/dist/harness/plan.d.ts +32 -0
  43. package/dist/harness/plan.d.ts.map +1 -0
  44. package/dist/harness/plan.js +68 -0
  45. package/dist/harness/plan.js.map +1 -0
  46. package/dist/harness/policy.d.ts +49 -0
  47. package/dist/harness/policy.d.ts.map +1 -0
  48. package/dist/harness/policy.js +34 -0
  49. package/dist/harness/policy.js.map +1 -0
  50. package/dist/harness/renderer.d.ts +163 -0
  51. package/dist/harness/renderer.d.ts.map +1 -0
  52. package/dist/harness/renderer.js +12 -0
  53. package/dist/harness/renderer.js.map +1 -0
  54. package/dist/harness/router/coordinator.d.ts +120 -0
  55. package/dist/harness/router/coordinator.d.ts.map +1 -0
  56. package/dist/harness/router/coordinator.js +145 -0
  57. package/dist/harness/router/coordinator.js.map +1 -0
  58. package/dist/harness/router/rules.d.ts +48 -0
  59. package/dist/harness/router/rules.d.ts.map +1 -0
  60. package/dist/harness/router/rules.js +38 -0
  61. package/dist/harness/router/rules.js.map +1 -0
  62. package/dist/harness/slots.d.ts +73 -0
  63. package/dist/harness/slots.d.ts.map +1 -0
  64. package/dist/harness/slots.js +68 -0
  65. package/dist/harness/slots.js.map +1 -0
  66. package/dist/harness/tools/bridge.d.ts +71 -0
  67. package/dist/harness/tools/bridge.d.ts.map +1 -0
  68. package/dist/harness/tools/bridge.js +271 -0
  69. package/dist/harness/tools/bridge.js.map +1 -0
  70. package/dist/harness/tools/client.d.ts +11 -0
  71. package/dist/harness/tools/client.d.ts.map +1 -0
  72. package/dist/harness/tools/client.js +21 -0
  73. package/dist/harness/tools/client.js.map +1 -0
  74. package/dist/harness/tools/discovery.d.ts +13 -0
  75. package/dist/harness/tools/discovery.d.ts.map +1 -0
  76. package/dist/harness/tools/discovery.js +43 -0
  77. package/dist/harness/tools/discovery.js.map +1 -0
  78. package/dist/harness/tools/local-context.d.ts +38 -0
  79. package/dist/harness/tools/local-context.d.ts.map +1 -0
  80. package/dist/harness/tools/local-context.js +55 -0
  81. package/dist/harness/tools/local-context.js.map +1 -0
  82. package/dist/harness/tools/schema.d.ts +52 -0
  83. package/dist/harness/tools/schema.d.ts.map +1 -0
  84. package/dist/harness/tools/schema.js +175 -0
  85. package/dist/harness/tools/schema.js.map +1 -0
  86. package/dist/harness/tools/source.d.ts +65 -0
  87. package/dist/harness/tools/source.d.ts.map +1 -0
  88. package/dist/harness/tools/source.js +89 -0
  89. package/dist/harness/tools/source.js.map +1 -0
  90. package/dist/harness/tools/webmcp.d.ts +50 -0
  91. package/dist/harness/tools/webmcp.d.ts.map +1 -0
  92. package/dist/harness/tools/webmcp.js +114 -0
  93. package/dist/harness/tools/webmcp.js.map +1 -0
  94. package/dist/index.d.ts +49 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +38 -0
  97. package/dist/index.js.map +1 -0
  98. package/dist/providers/types.d.ts +123 -0
  99. package/dist/providers/types.d.ts.map +1 -0
  100. package/dist/providers/types.js +2 -0
  101. package/dist/providers/types.js.map +1 -0
  102. package/dist/types.d.ts +223 -0
  103. package/dist/types.d.ts.map +1 -0
  104. package/dist/types.js +8 -0
  105. package/dist/types.js.map +1 -0
  106. package/dist/ui/agent-fullscreen.d.ts +41 -0
  107. package/dist/ui/agent-fullscreen.d.ts.map +1 -0
  108. package/dist/ui/agent-fullscreen.js +2821 -0
  109. package/dist/ui/agent-fullscreen.js.map +1 -0
  110. package/dist/ui/config.d.ts +88 -0
  111. package/dist/ui/config.d.ts.map +1 -0
  112. package/dist/ui/config.js +96 -0
  113. package/dist/ui/config.js.map +1 -0
  114. package/dist/ui/index.d.ts +22 -0
  115. package/dist/ui/index.d.ts.map +1 -0
  116. package/dist/ui/index.js +19 -0
  117. package/dist/ui/index.js.map +1 -0
  118. package/dist/ui/tailwind.generated.d.ts +2 -0
  119. package/dist/ui/tailwind.generated.d.ts.map +1 -0
  120. package/dist/ui/tailwind.generated.js +3 -0
  121. package/dist/ui/tailwind.generated.js.map +1 -0
  122. package/dist/webmcp-contract.d.ts +57 -0
  123. package/dist/webmcp-contract.d.ts.map +1 -0
  124. package/dist/webmcp-contract.js +11 -0
  125. package/dist/webmcp-contract.js.map +1 -0
  126. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,434 @@
1
+ # @nxisai/agent-harness
2
+
3
+ An on-device agent for the browser. It runs a small model locally over WebGPU, discovers the tools your page exposes (WebMCP and remote MCP), and decides per turn whether to run a tool, ask for what is missing, or confirm before writing.
4
+
5
+ No server. No API key. The model runs in the tab.
6
+
7
+ ```bash
8
+ npm install @nxisai/agent-harness
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```js
14
+ import '@nxisai/agent-harness/ui';
15
+ ```
16
+
17
+ ```html
18
+ <nxis-agent-fullscreen></nxis-agent-fullscreen>
19
+ ```
20
+
21
+ The element registers itself, finds the page's tools, and renders a chat surface with an inline launcher. That is the whole integration for a single agent.
22
+
23
+ ## Single agent or multiple agents
24
+
25
+ The element supports two modes. The default is one agent that reads every tool on the page. The optional mode adds a coordinator and a set of specialists; each turn then loads one agent's instructions and one agent's tools instead of everyone's.
26
+
27
+ ![Single agent: every turn loads one system prompt and all ten tool definitions, 2,476 tokens. Specialists: a coordinator with no tools routes to one of four agents, each loading only its own prompt and tools, 506 to 611 tokens per turn.](docs/single-vs-multi-agent.svg)
28
+
29
+
30
+ Numbers above are from one site (six forms, four retrieval tools). Tool definitions are re-sent on every turn and are most of what a small model reads; the saving comes from what each agent does not load, and the accuracy comes from choosing between two tools instead of ten.
31
+
32
+ ### Single agent
33
+
34
+ Nothing to configure beyond the prompt:
35
+
36
+ ```js
37
+ const agent = document.querySelector('nxis-agent-fullscreen');
38
+ agent.systemPrompt = '…';
39
+ agent.suggestions = ['Find a plumber', 'Book a visit'];
40
+ ```
41
+
42
+ ### Multiple agents
43
+
44
+ Declare the specialists, a rule table, and a fallback. The element builds an `AgentHarness` per entry over its one engine and places an `AgentCoordinator` in front. Clear `agents` to return to single-agent mode.
45
+
46
+ ```js
47
+ agent.agents = {
48
+ retrieval: {
49
+ systemPrompt: RETRIEVAL_PROMPT,
50
+ tools: (t) => /^nxis_(query|get)$/.test(t.name),
51
+ description: 'questions about services, ratings and areas',
52
+ unavailable: 'I cannot search the site records from here right now.',
53
+ },
54
+ booking: {
55
+ systemPrompt: BOOKING_PROMPT,
56
+ tools: (t) => /book|appointment/i.test(t.name),
57
+ description: 'booking a visit',
58
+ unavailable: 'I cannot book a visit from here. The booking page can.',
59
+ },
60
+ };
61
+ agent.routes = [
62
+ { route: 'booking', match: /\b(book|schedule|appointment)\b/ },
63
+ { route: 'retrieval', match: /\b(hours|rated|cover|who|find)\b/ },
64
+ ];
65
+ agent.fallback = 'retrieval';
66
+ ```
67
+
68
+ Each `AgentDefinition` field:
69
+
70
+ | Field | Purpose |
71
+ |---|---|
72
+ | `systemPrompt` | This agent's instructions. Asserted at the head of the shared transcript on every turn. |
73
+ | `tools(tool)` | Which registered tools this agent may use. Match by name. |
74
+ | `description` | One line for the model classifier. Read only when the classifier runs. |
75
+ | `unavailable` | What the agent says when none of its tools are on the page. |
76
+
77
+ ### How a turn is routed
78
+
79
+ A turn checks a rule table first and routes with no model call. If the rules are unclear, a short classification pass runs. The chosen specialist keeps handling later turns without re-routing, and can hand back once if the model asks for a tool it does not hold.
80
+
81
+ ![A turn checks a rule table first and routes with no model call. If the rules are unclear, a short classification pass runs. The chosen specialist keeps handling later turns without re-routing, and can hand back once if the model asks for a tool it does not hold.](docs/turn-routing.svg)
82
+
83
+
84
+
85
+ Cheapest path first:
86
+
87
+ | Path | When | Cost |
88
+ |---|---|---|
89
+ | rules | a rule matches | none |
90
+ | sticky | rules abstain; the conversation is already with an agent | none |
91
+ | model | rules abstain; no history; an engine is supplied | one short generation (no tools, 12-token cap) |
92
+ | fallback | nothing decided | none |
93
+ | hand-back | the agent's model asks for a tool it does not hold | one extra turn, at most once |
94
+
95
+ Rules vote; `weight` breaks ties; a tie that nothing separates abstains rather than guessing. Stickiness is released when an agent reports `unavailable`, so a follow-up does not return to an agent that cannot act.
96
+
97
+ ## Models
98
+
99
+ Gemma 4 on WebGPU is the default and needs no configuration. To run the agent on a remote model, implement `ModelProvider`:
100
+
101
+ ```ts
102
+ interface ModelProvider {
103
+ readonly name: string;
104
+ readonly local: boolean; // false makes the header badge read "Remote"
105
+ readonly ready: boolean;
106
+ readonly loading: boolean;
107
+ load?(): Promise<void>; // omit for a provider with nothing to download
108
+ preload?(mode: 'eager' | 'idle'): void;
109
+ complete(req: CompletionRequest): Promise<Completion>;
110
+ countTokens?(text: string): Promise<number>;
111
+ }
112
+
113
+ interface Completion {
114
+ text: string; // rendered verbatim
115
+ call?: { name: string; args: Record<string, unknown> };
116
+ usage?: { promptTokens: number; outputTokens: number };
117
+ }
118
+ ```
119
+
120
+ ```js
121
+ agent.modelProvider = myProvider; // or: new AgentHarness({ provider, renderer })
122
+ ```
123
+
124
+ The boundary carries a structured completion rather than a string. Gemma announces tool calls in text, so `LocalGemmaProvider` parses its own markers; Claude, Gemini and Kimi return native tool-use blocks and map them onto `call`. The harness parses nothing either way.
125
+
126
+ **Put your model behind your own origin.** A vendor key in a browser is a key anyone can read from the page source, and a browser cannot rate-limit or audit itself. See [docs/remote-providers.md](docs/remote-providers.md) for a worked Claude example: a route handler that holds the key, and the provider that calls it.
127
+
128
+ Three optional members are how the UI adapts. Omit `load` and the whole download treatment is skipped (no charging ring, no byte counts). Omit `countTokens` and the token metrics panel is hidden rather than rendered as zeroes. `local: false` puts `Remote` in the header, because the package's claim is that nothing leaves the tab and the exception has to be visible.
129
+
130
+ ### The cache hint
131
+
132
+ `CompletionRequest.cache` carries `{ stableMessages, toolsStable }`: what the harness knows will repeat next turn. The harness cannot cache anything itself, and never depends on a hit. Acting on it is your endpoint's decision, and only where the vendor supports it.
133
+
134
+ Both flags go false at a specialist handoff, which swaps the system prompt and the tool slice. Sticky routing therefore keeps a prefix stable within a topic and invalidates it at the boundary.
135
+
136
+ ## Element API
137
+
138
+ ### Properties
139
+
140
+ | Property | Type | Default | Notes |
141
+ |---|---|---|---|
142
+ | `systemPrompt` | `string` | packaged prompt | See [Prompts](#prompts). |
143
+ | `suggestions` | `string[]` | two generic chips | Empty-state chips. `[]` shows none. |
144
+ | `emptyState` | `string` | describes the mechanism | Line above the chips. |
145
+ | `name` | `string` | `"Nxis Agent"` | Header, launcher tooltip, dialog accessible name. |
146
+ | `logo` | `string` | Nxis mark | Inline SVG markup, or an image URL. |
147
+ | `theme` | `AgentTheme` | NxisAI palette | See [Theming](#theming). |
148
+ | `agents` | `Record<string, AgentDefinition> \| null` | `null` | Enables multi-agent mode. |
149
+ | `routes` | `RouteRule[]` | `[]` | Ignored unless `agents` is set. |
150
+ | `fallback` | `string` | first agent declared | Ignored unless `agents` is set. |
151
+ | `modelProvider` | `ModelProvider \| null` | `null` | A remote model. Null means local Gemma. See [Models](#models). |
152
+ | `preload` | `'eager' \| 'idle' \| 'none'` | `'idle'` | When weights are fetched. Local provider only. Settable mid-session. |
153
+ | `debug` | `boolean` | `false` | Shows tool receipts and route labels. |
154
+ | `preferences` | `AgentPreferences` | session-scoped | See [Preferences](#preferences). |
155
+
156
+ ### Attributes
157
+
158
+ | Attribute | Values | Purpose |
159
+ |---|---|---|
160
+ | `preload` | `eager`, `idle`, `none` (`false` also means `none`) | Same as the property. |
161
+ | `debug` | present, or `"false"` | Same as the property. |
162
+ | `discover` | `"false"` | Skip remote MCP discovery. |
163
+ | `cache` | `"false"` | Do not store model files in Cache Storage. |
164
+ | `mcp-api-key` | string | Key for private remote tools. |
165
+ | `model-host` | URL | Fetch weights from another origin. |
166
+ | `model-path-template` | string | Path template under `model-host`. |
167
+ | `a2ui` | `"false"` | Use the legacy DOM renderers. |
168
+ | `auto-open` | present | Open on connect. |
169
+
170
+ ### Methods
171
+
172
+ ```js
173
+ agent.open();
174
+ ```
175
+
176
+ ### Preferences
177
+
178
+ Standing values used to fill forms. Session-scoped unless `persist` is set.
179
+
180
+ ```js
181
+ agent.preferences.set({ name: 'Jane Doe', email: 'jane@example.com' });
182
+ agent.preferences.set('phone', '555-0100');
183
+ agent.preferences.get(); // all
184
+ agent.preferences.get('email');
185
+ agent.preferences.keys(); // ['name', 'email', 'phone', 'address']
186
+ agent.preferences.clear('phone');
187
+ agent.preferences.clear();
188
+ agent.preferences.persist = true; // survive reloads; false deletes the stored copy
189
+ agent.addEventListener('nxis-preferences-change', (e) => e.detail.preferences);
190
+ ```
191
+
192
+ Keys map onto differently named schema fields (`name` satisfies `full_name`, `fullname`, `name`). Extend `PROFILE_ALIASES` to add one.
193
+
194
+ ### Theming
195
+
196
+ `theme` merges over the defaults. Each key writes two custom properties (the element's own CSS and its Tailwind utilities use different names), so set colors here rather than in CSS.
197
+
198
+ ```js
199
+ agent.theme = {
200
+ accent: '#B45309', accentStrong: '#92400E', accentTint: '#FDF3E7',
201
+ ink: '#0E1C2B', inkSoft: '#40566D', inkMuted: '#8A97AC',
202
+ bg: '#FFFFFF', surface: '#FBFAF8', border: '#E2DDD6',
203
+ radius: '14px',
204
+ fontDisplay: 'Manrope, sans-serif', fontSans: 'Inter, sans-serif', fontMono: 'JetBrains Mono, monospace',
205
+ };
206
+ ```
207
+
208
+ Green and red are not themeable. Fonts are not bundled; load them or point the font keys at faces you ship. The footer's "Powered by NxisAI" badge is not affected by `logo` or `theme`.
209
+
210
+ ### Prompts
211
+
212
+ Two lines of the packaged prompt do real work. Keep them if you rewrite it:
213
+
214
+ > Call the tool as soon as you know which one is right, even if some required fields are still unknown; include the fields you do have and omit the rest. Never ask the user to supply a missing field in prose.
215
+
216
+ The harness collects missing fields as fields (one question, a stepper, or a confirm card). A prompt that tells the model to ask in prose produces a paragraph of questions instead.
217
+
218
+ ## What a turn does
219
+
220
+ Given the tool the model picked and everything already known, the harness asks what is still unknown:
221
+
222
+ | Tool | Gaps | Result |
223
+ |---|---|---|
224
+ | read (`readOnlyHint`) | none | runs, no UI |
225
+ | read | some | asks only for those, then runs |
226
+ | write | none | confirm card, one Submit |
227
+ | write | one | asks for one field, then confirms |
228
+ | write | several | stepper, then confirms |
229
+
230
+ Values merge in this order, last wins: schema default, page context, saved preferences, model arguments.
231
+
232
+ Every turn leaves three things in the history: the model's words (synthesized from the chosen tool when the model emitted only a call), the rendered UI, and, with `debug` on, a collapsed receipt showing the call and the raw response.
233
+
234
+ ### Stopping a turn
235
+
236
+ `stop()` aborts wherever the turn currently is: tool discovery, the routing classifier, or the generation itself (the local provider interrupts Gemma mid-stream). The turn resolves as `'stopped'`.
237
+
238
+ A stopped turn keeps the question and discards the partial answer, from the rendered thread and from `messages` alike. An unfinished reply is something the person has to decide whether to trust, and a model that reads it back as history will continue from it or contradict it. The question staying is what keeps the thread and the transcript in step.
239
+
240
+ In the element, the send button becomes a stop button for the duration, Escape stops the turn (it does not close the panel), and every message the person sent carries a row of actions: copy, edit in place, and send again. Editing or resending discards everything below that message, because it answered a question that no longer exists.
241
+
242
+ ## Headless API
243
+
244
+ Import from `@nxisai/agent-harness` and bring your own renderer.
245
+
246
+ ```ts
247
+ import { AgentHarness, AgentCoordinator, LocalGemmaProvider, connectTools } from '@nxisai/agent-harness';
248
+
249
+ await connectTools(); // WebMCP + remote MCP
250
+
251
+ const provider = new LocalGemmaProvider(); // or your own; shared between agents
252
+
253
+ const retrieval = new AgentHarness({
254
+ provider, renderer,
255
+ name: 'retrieval',
256
+ systemPrompt: RETRIEVAL_PROMPT,
257
+ tools: (t) => /^nxis_/.test(t.name),
258
+ unavailable: 'I cannot search from here right now.',
259
+ });
260
+
261
+ const coordinator = new AgentCoordinator({
262
+ agents: { retrieval, booking },
263
+ rules: ROUTE_RULES,
264
+ fallback: 'retrieval',
265
+ provider, // optional: enables the model pass
266
+ descriptions: { booking: 'visits and appointments' },
267
+ onRoute: (r) => console.debug(r.agent, r.via),
268
+ });
269
+
270
+ await coordinator.turn(goal, messages); // same signature as harness.turn
271
+ ```
272
+
273
+ ### `LocalGemmaProvider`
274
+
275
+ ```ts
276
+ new LocalGemmaProvider({ systemPrompt?, modelHost?, modelPathTemplate?, useBrowserCache?, onStatus?, onProgress? })
277
+ provider.complete(req): Promise<Completion>
278
+ provider.load(): Promise<void>
279
+ provider.preload(mode?: 'eager' | 'idle')
280
+ provider.countTokens(text): Promise<number>
281
+ provider.ready: boolean
282
+ provider.loading: boolean
283
+ ```
284
+
285
+ `onProgress(pct, detail?)` reports `{ loaded, total }` in bytes. The percentage is per stage and unclamped. Exported as `AgentEngine` too, deprecated: it used to own tool discovery and schema reading, which now live on the harness.
286
+
287
+ ### `AgentHarness`
288
+
289
+ ```ts
290
+ new AgentHarness({ renderer, provider?, toolSource?, systemPrompt?, tools?, name?, unavailable?, profile?, context?, groupRelated?, onMessages? })
291
+ harness.turn(goal, messages): Promise<TurnResult>
292
+ harness.owns(toolName): boolean
293
+ harness.stop(): void // abandon the request in flight
294
+ harness.provider: ModelProvider
295
+ harness.toolSource: WebMcpTools
296
+ ```
297
+
298
+ `TurnResult.decision` is one of the policy decisions, or `'none'`, `'handback'` (with `wanted`, the tool name the model asked for), `'unavailable'`, or `'stopped'`.
299
+
300
+ `stop()` abandons the turn in flight and resolves it as `'stopped'`; it never rejects, so a cancellation is not reported to the person as a failure.
301
+
302
+ ### `HarnessRenderer`
303
+
304
+ ```ts
305
+ interface HarnessRenderer {
306
+ message(role, text): void;
307
+ streaming(): StreamingBubble; // thinking(label?), update(text), finalize(text)
308
+ status(text, isError?): void;
309
+ ask(req: AskRequest): void;
310
+ confirm(req: ConfirmRequest): void;
311
+ results(tool, res): void;
312
+ detail(tool, values, text): void;
313
+ toolError(tool, message): void;
314
+ trace?(entry: TraceEntry): void; // { tool, args, text, isError, count? }
315
+ dismissPrompt?(): void;
316
+ stoppable?(canStop: boolean): void; // false once a tool call has gone out
317
+ }
318
+ ```
319
+
320
+ `stoppable` is called with `true` when a turn starts and `false` when it ends, and also `false` the moment a tool is invoked: past that point the call may already have reached a server, so a control still offering to stop would be promising something it cannot deliver.
321
+
322
+ ### `AgentCoordinator`
323
+
324
+ ```ts
325
+ new AgentCoordinator({ agents, rules, fallback, provider?, descriptions?, onRoute? })
326
+ coordinator.turn(goal, messages): Promise<TurnResult>
327
+ coordinator.stop(): void // the classifier and the agent that took the turn
328
+ coordinator.turn(goal, messages): Promise<TurnResult>
329
+ coordinator.route(goal, messages?): RoutedTurn // decide without running
330
+ ```
331
+
332
+ `RoutedTurn` carries `agent`, `via` (`'rules' | 'sticky' | 'model' | 'fallback' | 'handback'`), `fellBack`, `scores`, and the matched rules.
333
+
334
+ ### Routing rules
335
+
336
+ ```ts
337
+ interface RouteRule {
338
+ route: string;
339
+ match: RegExp; // tested against the lower-cased goal
340
+ weight?: number; // default 1
341
+ because?: string; // surfaced in the decision
342
+ }
343
+ matchRoute(goal, rules): RouteDecision // { route | null, scores, matched }
344
+ ```
345
+
346
+ ### Tool helpers
347
+
348
+ ```ts
349
+ import { WebMcpTools, toolSchema, missingRequired, reconcileArgs, extractResult } from '@nxisai/agent-harness';
350
+
351
+ const tools = new WebMcpTools(); // document.modelContext, wrapped
352
+ await tools.discoverTools();
353
+ await tools.execTool(tool, name, args);
354
+ ```
355
+
356
+ These are model agnostic, so they sit with the harness rather than behind the provider interface: a remote provider has no business knowing about `document.modelContext`.
357
+
358
+ ### Decision helpers
359
+
360
+ ```ts
361
+ import { resolveSlots, decide, policyFor, buildPlan } from '@nxisai/agent-harness';
362
+ const resolution = resolveSlots(schema, { modelArgs, profile });
363
+ const action = decide(policyFor(tool), resolution.missing.length);
364
+ const plan = buildPlan(resolution.missing);
365
+ ```
366
+
367
+ ### Tools
368
+
369
+ ```ts
370
+ import { connectTools } from '@nxisai/agent-harness/tools';
371
+ const { modelContext, remoteTools, warning } = await connectTools(apiKey?, { timeout?, fallback?, accept? });
372
+ ```
373
+
374
+ Discovery reads `/.well-known/ai-catalog.json`, follows it to the server card, and bridges the server's tools into `document.modelContext`. When the page has no WebMCP provider, a local registry is installed so bridged tools still have a home (`fallback: false` disables this). `nxis_action` is not bridged by default; it is the server-side path for external MCP clients, and the page's own form tools cover the same actions. Pass `accept` to change which tools cross.
375
+
376
+ Bridged tools are marked read-only only when their server says so. The `nxis_query` schema is flattened for the model: `{ schema_type, text, filters, sort, limit }`, where `filters` is `{ "path": ">=4.5" }` (prefixes `>=`, `>`, `<=`, `<`; a list means any-of) and `sort` is a path with `-` for descending. String and nested forms of `filters` are accepted.
377
+
378
+ ### A2UI
379
+
380
+ ```ts
381
+ import { emitAsk, emitStep, emitConfirm, toV09 } from '@nxisai/agent-harness/a2ui';
382
+ ```
383
+
384
+ Messages are authored against A2UI v1.0 and downgraded with `toV09` for the published `@a2ui/lit` renderer.
385
+
386
+ ## Requirements
387
+
388
+ - WebGPU: Chrome or Edge 113+, or Safari 18+.
389
+ - Model weights: fetched on first use (gigabytes on a first run, then cached).
390
+ - Tools: `document.modelContext` from `nxis.js` or a browser extension, or a remote MCP server discoverable through `.well-known`.
391
+
392
+ ## Troubleshooting and gotchas
393
+
394
+ **`__turbopack_context__.x is not a function` when the agent opens.**
395
+ The engine loads Transformers.js from a CDN with a dynamic import that must not be bundled. The import carries `@vite-ignore`, `webpackIgnore`, and `turbopackIgnore`; all three are required. If you fork the engine, keep them.
396
+
397
+ **The tab dies with `RESULT_CODE_KILLED_BAD_MESSAGE` when the model loads.**
398
+ Chrome's Cache Storage can fail on entries this large and kill the renderer rather than report it. The engine records a load before it starts; if the page died mid-load, the next attempt runs without the browser cache and says so. Clearing the site's data usually fixes the cache; `cache="false"` skips it permanently.
399
+
400
+ **"No tools are registered on this page yet."**
401
+ `document.modelContext` is empty. On a local origin the site tag does not activate, so on-page form tools are absent; remote MCP tools still arrive through discovery if `.well-known/ai-catalog.json` is served. Check that `discover` is not `"false"`.
402
+
403
+ **An agent answers "I cannot do that from this page".**
404
+ That agent's `tools` filter matched nothing among the registered tools. The slice is not widened silently. Check the names your page registers against the filter, and write an `unavailable` line per agent.
405
+
406
+ **The model says it cannot filter, sort, or use a field.**
407
+ The model only knows what the tool schema shows it and will deny a capability the schema omits. Check the bridged schema (`modelContext.getTools()`), not the prompt.
408
+
409
+ **A filter is ignored and results come back unfiltered.**
410
+ The bridge accepts `filters` as an object, a JSON string, a JS-style string with bare keys, or `path >= value`. Anything unreadable is dropped, and the query still runs. Turn on `debug` to see the exact call in the receipt.
411
+
412
+ **The rule table routes poorly.**
413
+ Rules written alongside their fixtures will pass those fixtures. Measure on phrasings you did not write the rules for; people describe symptoms ("slow drain in the kitchen") where rules expect verbs ("book a visit"). Abstaining is the designed failure mode; a wrong route is not.
414
+
415
+ **Routing keeps sending follow-ups to the wrong agent.**
416
+ Stickiness holds when the rules abstain. Add a rule for the phrasing, or rely on the hand-back: when the agent's model names a tool it does not hold, the coordinator moves the turn once to the owner.
417
+
418
+
419
+ **Colors set in CSS only apply to part of the UI.**
420
+ The element carries two token systems (`--blue` and `--color-blue`, and so on). Use the `theme` property, which writes both.
421
+
422
+ **A remote provider shows a charging ring or an empty metrics panel.**
423
+ Omit `load` from the provider so the UI knows there is nothing to download, and omit `countTokens` so the token comparison is hidden rather than rendered as zeroes.
424
+
425
+ **Requests keep running after the panel closes.**
426
+ Pass `signal` from `CompletionRequest` into your `fetch`. The harness aborts on close and when a new turn starts, but only the provider can act on it.
427
+
428
+ **Only one tool runs per turn.**
429
+ By design: the decision table asks, confirms and runs one call. A provider that returns several tool-use blocks should send the first; the rest are ignored.
430
+
431
+
432
+ ## License
433
+
434
+ Apache-2.0
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Renders typed-retrieval results as A2UI cards.
3
+ *
4
+ * The component count is fixed regardless of how many results there are: one
5
+ * result and twelve emit the same components, differing only in the data at
6
+ * `/results`. A result arriving late is therefore a data-model patch rather
7
+ * than new components, which keeps rendering incremental.
8
+ *
9
+ * Conditionality lives in the data rather than the components, so an entity
10
+ * with no rating simply has fewer entries. The basic catalog has no conditional
11
+ * rendering and none is needed.
12
+ */
13
+ import type { A2uiMessage } from './emit.js';
14
+ /** One label and value line on a result card. */
15
+ export interface CardRow {
16
+ label: string;
17
+ value: string;
18
+ }
19
+ /** A short pill shown on a result card. */
20
+ export interface CardTag {
21
+ label: string;
22
+ }
23
+ /** One record, in the shape a result card renders. */
24
+ export interface ResultCardData {
25
+ title: string;
26
+ sub: string;
27
+ tags: CardTag[];
28
+ rows: CardRow[];
29
+ }
30
+ /**
31
+ * Normalise one schema.org entity into flat card data.
32
+ *
33
+ * Mirrors `_entityCard` in agent-fullscreen.ts, with the same
34
+ * title/type/rating rules and 4-row cap, so the A2UI surface and inline card show
35
+ * the same thing. If this ships, the two should share one implementation rather
36
+ * than drift; this copy exists to keep the sketch self-contained.
37
+ */
38
+ export declare function toCardData(entity: Record<string, unknown>): ResultCardData;
39
+ /**
40
+ * Build a surface from card data that has already been normalised.
41
+ *
42
+ * agent-fullscreen has its own `_entityCard`, and it is the one that decides
43
+ * what a card shows. Taking its output rather than re-deriving from entities
44
+ * keeps the A2UI surface and the legacy inline card identical by construction.
45
+ */
46
+ export declare function emitCardsSurface(heading: string, results: ResultCardData[], surfaceId?: string): A2uiMessage[];
47
+ /**
48
+ * Build the A2UI messages for a set of results.
49
+ *
50
+ * @returns The messages to render, as one surface.
51
+ */
52
+ export declare function emitResultsSurface(toolName: string, entities: Record<string, unknown>[], surfaceId?: string): A2uiMessage[];
53
+ /** A late-arriving result appends to the list; no components are re-sent. */
54
+ export declare function emitAppendResult(surfaceId: string, index: number, entity: Record<string, unknown>): A2uiMessage;
55
+ //# sourceMappingURL=cards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cards.d.ts","sourceRoot":"","sources":["../../src/a2ui/cards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAM7C,iDAAiD;AACjD,MAAM,WAAW,OAAO;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACzD,2CAA2C;AAC3C,MAAM,WAAW,OAAO;IAAG,KAAK,EAAE,MAAM,CAAA;CAAE;AAC1C,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB;AAQD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,cAAc,CAmD1E;AAiCD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,cAAc,EAAE,EACzB,SAAS,SAAY,GACpB,WAAW,EAAE,CASf;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACnC,SAAS,SAAY,GACpB,WAAW,EAAE,CAMf;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChE,WAAW,CAKb"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Renders typed-retrieval results as A2UI cards.
3
+ *
4
+ * The component count is fixed regardless of how many results there are: one
5
+ * result and twelve emit the same components, differing only in the data at
6
+ * `/results`. A result arriving late is therefore a data-model patch rather
7
+ * than new components, which keeps rendering incremental.
8
+ *
9
+ * Conditionality lives in the data rather than the components, so an entity
10
+ * with no rating simply has fewer entries. The basic catalog has no conditional
11
+ * rendering and none is needed.
12
+ */
13
+ const CATALOG = 'https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json';
14
+ const str = (v) => (v == null ? '' : String(Array.isArray(v) ? v[0] : v));
15
+ const humanize = (s) => s.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[_-]+/g, ' ')
16
+ .replace(/^\w/, c => c.toUpperCase());
17
+ /**
18
+ * Normalise one schema.org entity into flat card data.
19
+ *
20
+ * Mirrors `_entityCard` in agent-fullscreen.ts, with the same
21
+ * title/type/rating rules and 4-row cap, so the A2UI surface and inline card show
22
+ * the same thing. If this ships, the two should share one implementation rather
23
+ * than drift; this copy exists to keep the sketch self-contained.
24
+ */
25
+ export function toCardData(entity) {
26
+ const title = str(entity.name ?? entity.title ?? entity.headline ?? entity['@id'] ?? 'Result');
27
+ const tags = [];
28
+ const rawType = entity['@type'];
29
+ if (rawType)
30
+ tags.push({ label: humanize(str(rawType)) });
31
+ const ar = entity.aggregateRating;
32
+ if (ar && typeof ar === 'object' && ar.ratingValue != null) {
33
+ const stars = '★'.repeat(Math.max(1, Math.min(5, Math.round(Number(ar.ratingValue) || 0))));
34
+ tags.push({
35
+ label: ar.reviewCount != null
36
+ ? `${stars} ${str(ar.ratingValue)} · ${str(ar.reviewCount)}`
37
+ : `${stars} ${str(ar.ratingValue)}`,
38
+ });
39
+ }
40
+ // 24/7 is only ever implied by round-the-clock opening hours.
41
+ const hours = entity.openingHoursSpecification;
42
+ if (Array.isArray(hours) && hours.some(h => h && typeof h === 'object'
43
+ && String(h.opens) === '00:00'
44
+ && String(h.closes) === '23:59')) {
45
+ tags.push({ label: '24/7' });
46
+ }
47
+ const desc = str(entity.description).trim();
48
+ const sub = desc.length > 150 ? desc.slice(0, 147) + '…' : desc;
49
+ const rows = [];
50
+ const push = (label, value) => {
51
+ const v = str(value).trim();
52
+ if (v && rows.length < 4 && !rows.some(r => r.label === label)) {
53
+ rows.push({ label, value: v.length > 90 ? v.slice(0, 87) + '…' : v });
54
+ }
55
+ };
56
+ const addr = entity.address;
57
+ if (addr && typeof addr === 'object') {
58
+ const parts = [addr.streetAddress, addr.addressLocality, addr.addressRegion]
59
+ .filter(Boolean).map(str);
60
+ if (parts.length)
61
+ push('Address', parts.join(', '));
62
+ }
63
+ for (const key of ['telephone', 'email', 'url']) {
64
+ const v = entity[key];
65
+ if (typeof v === 'string' || typeof v === 'number') {
66
+ push(humanize(key === 'url' ? 'website' : key), v);
67
+ }
68
+ }
69
+ return { title, sub, tags, rows };
70
+ }
71
+ /** Fixed component set — independent of how many results there are. */
72
+ function cardComponents(heading) {
73
+ return [
74
+ { id: 'root', component: 'Column', children: ['heading', 'results'] },
75
+ { id: 'heading', component: 'Text', text: heading, variant: 'body' },
76
+ // Outer list: one Card per entry in /results
77
+ { id: 'results', component: 'List',
78
+ children: { path: '/results', componentId: 'card_tpl' }, direction: 'vertical' },
79
+ { id: 'card_tpl', component: 'Card', child: 'card_body' },
80
+ { id: 'card_body', component: 'Column', children: ['card_title', 'card_tags', 'card_sub', 'card_rows'] },
81
+ // No bold: `weight` in this catalog is a flex weight (number), and Text
82
+ // offers only variant caption/body. Title emphasis is not expressible in
83
+ // the basic catalog: it needs a custom catalog or renderer styling.
84
+ { id: 'card_title', component: 'Text', text: '${title}', variant: 'body' },
85
+ // Relative paths resolve inside the current item's scope.
86
+ { id: 'card_tags', component: 'List',
87
+ children: { path: 'tags', componentId: 'tag_tpl' }, direction: 'horizontal' },
88
+ { id: 'tag_tpl', component: 'Text', text: '${label}', variant: 'caption' },
89
+ { id: 'card_sub', component: 'Text', text: '${sub}', variant: 'caption' },
90
+ { id: 'card_rows', component: 'List',
91
+ children: { path: 'rows', componentId: 'row_tpl' }, direction: 'vertical' },
92
+ { id: 'row_tpl', component: 'Row', children: ['row_label', 'row_value'] },
93
+ { id: 'row_label', component: 'Text', text: '${label}', variant: 'caption' },
94
+ { id: 'row_value', component: 'Text', text: '${value}', variant: 'caption' },
95
+ ];
96
+ }
97
+ /**
98
+ * Build a surface from card data that has already been normalised.
99
+ *
100
+ * agent-fullscreen has its own `_entityCard`, and it is the one that decides
101
+ * what a card shows. Taking its output rather than re-deriving from entities
102
+ * keeps the A2UI surface and the legacy inline card identical by construction.
103
+ */
104
+ export function emitCardsSurface(heading, results, surfaceId = 'results') {
105
+ return [{
106
+ version: 'v1.0',
107
+ createSurface: {
108
+ surfaceId, catalogId: CATALOG,
109
+ components: cardComponents(heading),
110
+ dataModel: { results: results.slice(0, 12) },
111
+ },
112
+ }];
113
+ }
114
+ /**
115
+ * Build the A2UI messages for a set of results.
116
+ *
117
+ * @returns The messages to render, as one surface.
118
+ */
119
+ export function emitResultsSurface(toolName, entities, surfaceId = 'results') {
120
+ const results = entities.slice(0, 12).map(toCardData);
121
+ const heading = results.length
122
+ ? `${humanize(toolName)} — ${results.length} result${results.length === 1 ? '' : 's'}`
123
+ : `${humanize(toolName)} — no results`;
124
+ return emitCardsSurface(heading, results, surfaceId);
125
+ }
126
+ /** A late-arriving result appends to the list; no components are re-sent. */
127
+ export function emitAppendResult(surfaceId, index, entity) {
128
+ return {
129
+ version: 'v1.0',
130
+ updateDataModel: { surfaceId, path: `/results/${index}`, value: toCardData(entity) },
131
+ };
132
+ }
133
+ //# sourceMappingURL=cards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cards.js","sourceRoot":"","sources":["../../src/a2ui/cards.ts"],"names":[],"mappings":"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"}