wu-framework 1.1.15 → 1.1.17

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 (88) hide show
  1. package/README.md +52 -20
  2. package/dist/wu-framework.cjs.js +1 -1
  3. package/dist/wu-framework.cjs.js.map +1 -1
  4. package/dist/wu-framework.dev.js +15511 -15146
  5. package/dist/wu-framework.dev.js.map +1 -1
  6. package/dist/wu-framework.esm.js +1 -1
  7. package/dist/wu-framework.esm.js.map +1 -1
  8. package/dist/wu-framework.umd.js +1 -1
  9. package/dist/wu-framework.umd.js.map +1 -1
  10. package/package.json +166 -161
  11. package/src/adapters/angular/ai.js +30 -30
  12. package/src/adapters/angular/index.d.ts +154 -154
  13. package/src/adapters/angular/index.js +932 -932
  14. package/src/adapters/angular.d.ts +3 -3
  15. package/src/adapters/angular.js +3 -3
  16. package/src/adapters/index.js +168 -168
  17. package/src/adapters/lit/ai.js +20 -20
  18. package/src/adapters/lit/index.d.ts +120 -120
  19. package/src/adapters/lit/index.js +721 -721
  20. package/src/adapters/lit.d.ts +3 -3
  21. package/src/adapters/lit.js +3 -3
  22. package/src/adapters/preact/ai.js +33 -33
  23. package/src/adapters/preact/index.d.ts +108 -108
  24. package/src/adapters/preact/index.js +661 -661
  25. package/src/adapters/preact.d.ts +3 -3
  26. package/src/adapters/preact.js +3 -3
  27. package/src/adapters/react/index.js +48 -54
  28. package/src/adapters/react.d.ts +3 -3
  29. package/src/adapters/react.js +3 -3
  30. package/src/adapters/shared.js +64 -64
  31. package/src/adapters/solid/ai.js +32 -32
  32. package/src/adapters/solid/index.d.ts +101 -101
  33. package/src/adapters/solid/index.js +586 -586
  34. package/src/adapters/solid.d.ts +3 -3
  35. package/src/adapters/solid.js +3 -3
  36. package/src/adapters/svelte/ai.js +31 -31
  37. package/src/adapters/svelte/index.d.ts +166 -166
  38. package/src/adapters/svelte/index.js +798 -798
  39. package/src/adapters/svelte.d.ts +3 -3
  40. package/src/adapters/svelte.js +3 -3
  41. package/src/adapters/vanilla/ai.js +30 -30
  42. package/src/adapters/vanilla/index.d.ts +179 -179
  43. package/src/adapters/vanilla/index.js +785 -785
  44. package/src/adapters/vanilla.d.ts +3 -3
  45. package/src/adapters/vanilla.js +3 -3
  46. package/src/adapters/vue/ai.js +52 -52
  47. package/src/adapters/vue/index.d.ts +299 -299
  48. package/src/adapters/vue/index.js +610 -610
  49. package/src/adapters/vue.d.ts +3 -3
  50. package/src/adapters/vue.js +3 -3
  51. package/src/ai/wu-ai-actions.js +261 -261
  52. package/src/ai/wu-ai-agent.js +546 -546
  53. package/src/ai/wu-ai-browser-primitives.js +354 -354
  54. package/src/ai/wu-ai-browser.js +380 -380
  55. package/src/ai/wu-ai-context.js +332 -332
  56. package/src/ai/wu-ai-conversation.js +613 -613
  57. package/src/ai/wu-ai-orchestrate.js +1021 -1021
  58. package/src/ai/wu-ai-permissions.js +381 -381
  59. package/src/ai/wu-ai-provider.js +700 -700
  60. package/src/ai/wu-ai-schema.js +225 -225
  61. package/src/ai/wu-ai-triggers.js +396 -396
  62. package/src/ai/wu-ai.js +804 -804
  63. package/src/core/wu-app.js +236 -236
  64. package/src/core/wu-cache.js +498 -477
  65. package/src/core/wu-core.js +1412 -1398
  66. package/src/core/wu-error-boundary.js +396 -382
  67. package/src/core/wu-event-bus.js +390 -348
  68. package/src/core/wu-hooks.js +350 -350
  69. package/src/core/wu-html-parser.js +199 -190
  70. package/src/core/wu-iframe-sandbox.js +328 -328
  71. package/src/core/wu-loader.js +385 -273
  72. package/src/core/wu-logger.js +142 -134
  73. package/src/core/wu-manifest.js +532 -509
  74. package/src/core/wu-mcp-bridge.js +432 -432
  75. package/src/core/wu-overrides.js +510 -510
  76. package/src/core/wu-performance.js +228 -228
  77. package/src/core/wu-plugin.js +401 -348
  78. package/src/core/wu-prefetch.js +414 -414
  79. package/src/core/wu-proxy-sandbox.js +477 -476
  80. package/src/core/wu-sandbox.js +779 -779
  81. package/src/core/wu-script-executor.js +161 -113
  82. package/src/core/wu-snapshot-sandbox.js +227 -227
  83. package/src/core/wu-store.js +13 -3
  84. package/src/core/wu-strategies.js +256 -256
  85. package/src/core/wu-style-bridge.js +477 -477
  86. package/src/index.d.ts +317 -0
  87. package/src/index.js +234 -224
  88. package/src/utils/dependency-resolver.js +327 -327
package/src/ai/wu-ai.js CHANGED
@@ -1,804 +1,804 @@
1
- /**
2
- * WU-AI: Central orchestrator for AI integration
3
- *
4
- * This is the main entry point for wu.ai — it wires together all sub-modules
5
- * and exposes the public API. Lazy-initialized on first use.
6
- *
7
- * Architecture:
8
- * WuAI (this file)
9
- * ├── WuAIProvider → BYOL provider management (OpenAI, Anthropic, Ollama, Custom)
10
- * ├── WuAIPermissions → 4-layer security (perms, rate limit, circuit breaker, loop guard)
11
- * ├── WuAIContext → Auto context collection with token budget
12
- * ├── WuAIActions → Tool/action registry and sandboxed execution
13
- * ├── WuAIConversation → Multi-turn conversation manager with namespaces
14
- * ├── WuAITriggers → Event-to-AI reactive bridge
15
- * ├── WuAIAgent → Autonomous agent loop (goal → steps → done)
16
- * ├── WuAIOrchestrate → Cross-micro-app AI coordination (capabilities + intents)
17
- * └── BrowserPrimitives → Shared screenshot, click, type, a11y tree, interceptors
18
- *
19
- * Four Paradigms:
20
- * 1. App → LLM send/stream/json → conversation with tool loops
21
- * 2. LLM → App tools/execute/expose → external agents call into the app
22
- * 3. AI Director agent(goal) → autonomous multi-step loop
23
- * 4. MF Glue capability/intent → cross-app coordination via AI
24
- *
25
- * Public API (accessible via wu.ai):
26
- * wu.ai.provider(name, config) → Register LLM provider
27
- * wu.ai.send(message, opts) → Send message (non-streaming)
28
- * wu.ai.stream(message, opts) → Send message (streaming)
29
- * wu.ai.json(message, schema?) → Send and get parsed JSON back
30
- * wu.ai.agent(goal, opts) → Run autonomous agent loop
31
- * wu.ai.action(name, config) → Register an action/tool
32
- * wu.ai.trigger(name, config) → Register an event trigger
33
- * wu.ai.capability(app, name, c) → Register app-scoped capability
34
- * wu.ai.intent(desc, opts) → Resolve cross-app intent
35
- * wu.ai.removeApp(appName) → Remove app capabilities (unmount)
36
- * wu.ai.workflow(name, config) → Register reusable AI workflow
37
- * wu.ai.runWorkflow(name, params)→ Execute workflow (async generator)
38
- * wu.ai.context.configure(...) → Configure context collection
39
- * wu.ai.abort(namespace?) → Abort active request
40
- *
41
- * Paradigm 2 (External agent access):
42
- * wu.ai.tools() → Get all registered tools (for CDP/WebMCP)
43
- * wu.ai.execute(name, params) → Execute action directly (for external agents)
44
- * wu.ai.expose() → Register tools via WebMCP (navigator.modelContext)
45
- */
46
-
47
- import { logger } from '../core/wu-logger.js';
48
- import { WuAIProvider } from './wu-ai-provider.js';
49
- import { WuAIPermissions } from './wu-ai-permissions.js';
50
- import { WuAIContext } from './wu-ai-context.js';
51
- import { WuAIActions } from './wu-ai-actions.js';
52
- import { WuAIConversation } from './wu-ai-conversation.js';
53
- import { WuAITriggers } from './wu-ai-triggers.js';
54
- import { WuAIAgent } from './wu-ai-agent.js';
55
- import { WuAIOrchestrate } from './wu-ai-orchestrate.js';
56
- import { registerBrowserActions } from './wu-ai-browser.js';
57
-
58
- export class WuAI {
59
- /**
60
- * @param {object} deps - Injected from WuCore
61
- * @param {object} deps.eventBus - WuEventBus instance
62
- * @param {object} deps.store - WuStore instance
63
- * @param {object} deps.core - WuCore instance (for mounted apps)
64
- */
65
- constructor({ eventBus, store, core }) {
66
- this._eventBus = eventBus;
67
- this._store = store;
68
- this._core = core;
69
- this._initialized = false;
70
- this._modules = {};
71
- }
72
-
73
- // ─── Lazy Initialization ───────────────────────────────────────
74
-
75
- /**
76
- * Initialize all sub-modules. Called automatically on first use,
77
- * or can be called explicitly with configuration.
78
- *
79
- * @param {object} [config]
80
- * @param {object} [config.permissions] - Permission overrides
81
- * @param {object} [config.rateLimit] - Rate limit config
82
- * @param {object} [config.circuitBreaker] - Circuit breaker config
83
- * @param {object} [config.loopProtection] - Loop protection config
84
- * @param {object} [config.context] - Context collection config
85
- * @param {object} [config.conversation] - Conversation defaults
86
- * @param {object} [config.triggers] - Trigger system config
87
- */
88
- init(config = {}) {
89
- if (this._initialized) {
90
- // Reconfigure if already initialized
91
- this._reconfigure(config);
92
- return this;
93
- }
94
-
95
- // 1. Permissions (independent — no deps)
96
- this._modules.permissions = new WuAIPermissions({
97
- permissions: config.permissions,
98
- rateLimit: config.rateLimit,
99
- circuitBreaker: config.circuitBreaker,
100
- loopProtection: config.loopProtection,
101
- allowedDomains: config.allowedDomains,
102
- });
103
-
104
- // 2. Provider (independent — no deps)
105
- this._modules.provider = new WuAIProvider();
106
-
107
- // 3. Context (depends on store, eventBus, core)
108
- this._modules.context = new WuAIContext({
109
- store: this._store,
110
- eventBus: this._eventBus,
111
- core: this._core,
112
- });
113
- if (config.context) {
114
- this._modules.context.configure(config.context);
115
- }
116
-
117
- // 4. Actions (depends on eventBus, store, permissions)
118
- this._modules.actions = new WuAIActions({
119
- eventBus: this._eventBus,
120
- store: this._store,
121
- permissions: this._modules.permissions,
122
- });
123
-
124
- // 5. Conversation (depends on provider, actions, context, permissions, eventBus)
125
- this._modules.conversation = new WuAIConversation({
126
- provider: this._modules.provider,
127
- actions: this._modules.actions,
128
- context: this._modules.context,
129
- permissions: this._modules.permissions,
130
- eventBus: this._eventBus,
131
- });
132
- if (config.conversation) {
133
- this._modules.conversation.configure(config.conversation);
134
- }
135
-
136
- // 6. Triggers (depends on eventBus, conversation, permissions)
137
- this._modules.triggers = new WuAITriggers({
138
- eventBus: this._eventBus,
139
- conversation: this._modules.conversation,
140
- permissions: this._modules.permissions,
141
- });
142
- if (config.triggers) {
143
- this._modules.triggers.configure(config.triggers);
144
- }
145
-
146
- // 7. Agent (depends on conversation, actions, context, permissions, eventBus)
147
- this._modules.agent = new WuAIAgent({
148
- conversation: this._modules.conversation,
149
- actions: this._modules.actions,
150
- context: this._modules.context,
151
- permissions: this._modules.permissions,
152
- eventBus: this._eventBus,
153
- });
154
- if (config.agent) {
155
- this._modules.agent.configure(config.agent);
156
- }
157
-
158
- // 8. Orchestrate — Paradigm 4: AI as microfrontend glue
159
- // Agent ref is passed so workflows can delegate to the agent loop
160
- // Store ref is passed for deterministic setState steps
161
- this._modules.orchestrate = new WuAIOrchestrate({
162
- actions: this._modules.actions,
163
- conversation: this._modules.conversation,
164
- context: this._modules.context,
165
- permissions: this._modules.permissions,
166
- eventBus: this._eventBus,
167
- agent: this._modules.agent,
168
- store: this._store,
169
- });
170
- if (config.orchestrate) {
171
- this._modules.orchestrate.configure(config.orchestrate);
172
- }
173
-
174
- this._initialized = true;
175
- logger.wuInfo('[wu-ai] Initialized');
176
-
177
- // 9. Browser automation actions (screenshot, click, type, network, etc.)
178
- // Must be AFTER _initialized = true to prevent recursive init loop
179
- if (typeof window !== 'undefined') {
180
- registerBrowserActions(this, this._core);
181
- logger.wuInfo('[wu-ai] Browser actions registered (10 tools)');
182
- }
183
-
184
- this._eventBus.emit('ai:initialized', {}, { appName: 'wu-ai' });
185
-
186
- return this;
187
- }
188
-
189
- // ─── Provider Management ───────────────────────────────────────
190
-
191
- /**
192
- * Register an LLM provider.
193
- *
194
- * @param {string} name - Provider name ('openai', 'anthropic', 'ollama', or custom)
195
- * @param {object} config - { endpoint, apiKey?, model?, adapter?, send?, stream? }
196
- *
197
- * @example
198
- * // OpenAI via proxy (recommended)
199
- * wu.ai.provider('openai', { endpoint: '/api/ai/chat', model: 'gpt-4o' });
200
- *
201
- * // Anthropic direct (development only)
202
- * wu.ai.provider('anthropic', {
203
- * endpoint: 'https://api.anthropic.com/v1/messages',
204
- * apiKey: 'sk-...',
205
- * model: 'claude-sonnet-4-5-20250929',
206
- * });
207
- *
208
- * // Local Ollama
209
- * wu.ai.provider('ollama', { endpoint: 'http://localhost:11434/api/chat', model: 'llama3' });
210
- *
211
- * // Custom provider
212
- * wu.ai.provider('my-llm', { send: async (messages, opts) => ({ content: '...' }) });
213
- */
214
- provider(name, config) {
215
- this._ensureInit();
216
- this._modules.provider.register(name, config);
217
- return this;
218
- }
219
-
220
- // ─── Paradigm 1: App → LLM (Conversation) ─────────────────────
221
-
222
- /**
223
- * Send a message to the LLM and get a complete response.
224
- *
225
- * @param {string} message - User message
226
- * @param {object} [options] - { namespace, systemPrompt, templateVars, temperature, maxTokens, provider, responseFormat, signal }
227
- * @param {string} [options.provider] - Use a specific registered provider (e.g., 'anthropic', 'openai')
228
- * @param {string|object} [options.responseFormat] - Request JSON output.
229
- * - `'json'` — simple JSON mode (OpenAI: json_object, Ollama: format:"json", Anthropic: prompt injection)
230
- * - `{ type: 'json_schema', schema: {...}, name?: string }` — structured output with JSON Schema
231
- * (OpenAI: native json_schema mode, Ollama: schema in format, Anthropic: schema in system prompt)
232
- * @returns {Promise<{ content: string, tool_results?: Array, usage?: object, namespace: string }>}
233
- *
234
- * @example
235
- * const response = await wu.ai.send('What items are in the cart?');
236
- * console.log(response.content);
237
- *
238
- * // With namespace for separate conversation
239
- * const response = await wu.ai.send('Analyze this chart', { namespace: 'analytics' });
240
- *
241
- * // Use a specific provider for this message
242
- * const response = await wu.ai.send('Translate this', { provider: 'anthropic' });
243
- *
244
- * // Simple JSON mode
245
- * const response = await wu.ai.send('List 5 colors', { responseFormat: 'json' });
246
- *
247
- * // Structured output with JSON Schema
248
- * const response = await wu.ai.send('List 5 colors', {
249
- * responseFormat: {
250
- * type: 'json_schema',
251
- * schema: { type: 'object', properties: { colors: { type: 'array', items: { type: 'string' } } } },
252
- * name: 'color_list',
253
- * },
254
- * });
255
- */
256
- async send(message, options = {}) {
257
- this._ensureInit();
258
- return this._modules.conversation.send(message, options);
259
- }
260
-
261
- /**
262
- * Send a message and stream the response.
263
- *
264
- * @param {string} message - User message
265
- * @param {object} [options] - Same as send()
266
- * @yields {{ type: 'text'|'tool_result'|'done'|'error', content?: string }}
267
- *
268
- * @example
269
- * for await (const chunk of wu.ai.stream('Tell me about this page')) {
270
- * if (chunk.type === 'text') outputEl.textContent += chunk.content;
271
- * if (chunk.type === 'done') console.log('Done!');
272
- * }
273
- */
274
- async *stream(message, options = {}) {
275
- this._ensureInit();
276
- yield* this._modules.conversation.stream(message, options);
277
- }
278
-
279
- /**
280
- * Send a message and get a parsed JSON response.
281
- * Shortcut for send() with responseFormat + automatic JSON.parse().
282
- *
283
- * @param {string} message - User message
284
- * @param {object} [options] - All send() options plus:
285
- * @param {object} [options.schema] - JSON Schema for structured output
286
- * @param {string} [options.schemaName='response'] - Schema name (required by OpenAI)
287
- * @returns {Promise<{ data: object|null, raw: string, error?: string, usage?: object, namespace: string }>}
288
- *
289
- * @example
290
- * // Simple JSON (no schema)
291
- * const { data } = await wu.ai.json('List 5 colors as a JSON array');
292
- * // data = ["red", "blue", ...]
293
- *
294
- * // With schema
295
- * const { data } = await wu.ai.json('List 5 colors', {
296
- * schema: { type: 'object', properties: { colors: { type: 'array', items: { type: 'string' } } } },
297
- * });
298
- * // data = { colors: ["red", "blue", ...] }
299
- *
300
- * // With schema + provider
301
- * const { data } = await wu.ai.json('List 5 colors', {
302
- * schema: mySchema,
303
- * provider: 'openai',
304
- * temperature: 0,
305
- * });
306
- */
307
- async json(message, options = {}) {
308
- this._ensureInit();
309
-
310
- const { schema, schemaName, ...rest } = options;
311
-
312
- let responseFormat;
313
- if (schema) {
314
- responseFormat = { type: 'json_schema', schema, name: schemaName || 'response' };
315
- } else {
316
- responseFormat = options.responseFormat || 'json';
317
- }
318
-
319
- const response = await this._modules.conversation.send(message, { ...rest, responseFormat });
320
-
321
- // The provider already attempts parse and sets response.parsed / response.parseError
322
- let data = null;
323
- let error;
324
-
325
- if (response.parsed !== undefined) {
326
- data = response.parsed;
327
- } else if (response.content) {
328
- try {
329
- data = JSON.parse(response.content);
330
- } catch {
331
- error = 'LLM response is not valid JSON';
332
- }
333
- }
334
-
335
- return {
336
- data,
337
- raw: response.content || '',
338
- error: error || response.parseError,
339
- usage: response.usage,
340
- namespace: response.namespace,
341
- };
342
- }
343
-
344
- /**
345
- * Abort active request(s).
346
- *
347
- * @param {string} [namespace] - Specific namespace, or all if omitted
348
- */
349
- abort(namespace) {
350
- if (!this._initialized) return;
351
- if (namespace) {
352
- this._modules.conversation.abort(namespace);
353
- } else {
354
- this._modules.conversation.abortAll();
355
- }
356
- }
357
-
358
- // ─── Actions / Tools ───────────────────────────────────────────
359
-
360
- /**
361
- * Register an action that the LLM can call.
362
- *
363
- * @param {string} name - Action name (used in tool_call)
364
- * @param {object} config - { description, parameters, handler, confirm?, permissions?, dangerous? }
365
- *
366
- * @example
367
- * wu.ai.action('addToCart', {
368
- * description: 'Add an item to the shopping cart',
369
- * parameters: {
370
- * productId: { type: 'string', required: true },
371
- * quantity: { type: 'number' },
372
- * },
373
- * handler: async (params, api) => {
374
- * api.setState('cart.items', [...api.getState('cart.items'), params]);
375
- * api.emit('cart:updated', params);
376
- * return { added: params.productId };
377
- * },
378
- * confirm: true, // require user confirmation
379
- * });
380
- */
381
- action(name, config) {
382
- this._ensureInit();
383
- this._modules.actions.register(name, config);
384
- return this;
385
- }
386
-
387
- /**
388
- * Execute an action directly (used by external agents via CDP/WebMCP).
389
- *
390
- * @param {string} name - Action name
391
- * @param {object} params - Parameters
392
- * @returns {Promise<{ success: boolean, result?: any, reason?: string }>}
393
- */
394
- async execute(name, params) {
395
- this._ensureInit();
396
- const traceId = this._modules.permissions.loopProtection.createTraceId();
397
- return this._modules.actions.execute(name, params, { traceId, depth: 0 });
398
- }
399
-
400
- // ─── Triggers (Reactive AI) ────────────────────────────────────
401
-
402
- /**
403
- * Register a trigger that automatically sends messages to the LLM
404
- * when specific events occur.
405
- *
406
- * @param {string} name - Trigger name
407
- * @param {object} config - { pattern, prompt, condition?, debounce?, priority?, onResult? }
408
- *
409
- * @example
410
- * wu.ai.trigger('cartAnalysis', {
411
- * pattern: 'cart:updated',
412
- * prompt: 'The cart was updated: {{data}}. Suggest complementary products.',
413
- * debounce: 3000,
414
- * priority: 'low',
415
- * onResult: (result) => {
416
- * wu.emit('ai:suggestions', { suggestions: result.content });
417
- * },
418
- * });
419
- */
420
- trigger(name, config) {
421
- this._ensureInit();
422
- this._modules.triggers.register(name, config);
423
- return this;
424
- }
425
-
426
- /**
427
- * Fire a trigger manually.
428
- */
429
- async fireTrigger(name, eventData) {
430
- this._ensureInit();
431
- return this._modules.triggers.fire(name, eventData);
432
- }
433
-
434
- // ─── Agent (Paradigm 3: Autonomous AI) ─────────────────────────
435
-
436
- /**
437
- * Run an autonomous agent that pursues a goal using available tools.
438
- * Returns an async generator that yields step-by-step results.
439
- *
440
- * @param {string} goal - What the agent should accomplish
441
- * @param {object} [options]
442
- * @param {number} [options.maxSteps=10] - Maximum autonomous steps
443
- * @param {string} [options.provider] - Which LLM provider to use
444
- * @param {string} [options.namespace] - Conversation namespace
445
- * @param {string} [options.systemPrompt] - Override system prompt
446
- * @param {Function} [options.onStep] - Callback per step: (stepResult) => void
447
- * @param {Function} [options.shouldContinue] - Human-in-the-loop: (stepResult) => boolean|Promise<boolean>
448
- * @param {AbortSignal} [options.signal] - Abort signal
449
- * @returns {AsyncGenerator<AgentStepResult>}
450
- *
451
- * @example
452
- * // Basic usage
453
- * for await (const step of wu.ai.agent('Find all orders above $100 and summarize them')) {
454
- * console.log(`Step ${step.step}: ${step.content?.slice(0, 100)}`);
455
- * if (step.done) console.log('Agent finished!');
456
- * }
457
- *
458
- * // With human-in-the-loop
459
- * for await (const step of wu.ai.agent('Reorganize the product catalog', {
460
- * shouldContinue: (step) => confirm(`Continue? Step ${step.step}: ${step.content?.slice(0, 50)}`),
461
- * })) {
462
- * updateUI(step);
463
- * }
464
- */
465
- async *agent(goal, options = {}) {
466
- this._ensureInit();
467
- yield* this._modules.agent.run(goal, options);
468
- }
469
-
470
- // ─── Paradigm 4: AI as Microfrontend Glue ─────────────────────
471
-
472
- /**
473
- * Register a capability scoped to a specific micro-app.
474
- *
475
- * Each micro-app calls this to declare what it can do. The AI uses
476
- * the capability map to resolve cross-app intents.
477
- *
478
- * @param {string} appName - The micro-app name (e.g., 'orders', 'dashboard')
479
- * @param {string} actionName - The capability name (e.g., 'getRecent', 'updateKPIs')
480
- * @param {object} config - Same as wu.ai.action() config:
481
- * { description, parameters, handler, confirm?, permissions?, dangerous? }
482
- *
483
- * @example
484
- * // In orders micro-app (React):
485
- * wu.ai.capability('orders', 'getRecent', {
486
- * description: 'Get the N most recent orders',
487
- * parameters: { limit: { type: 'number' } },
488
- * handler: async (params) => fetchOrders({ limit: params.limit || 10 }),
489
- * });
490
- *
491
- * // In dashboard micro-app (Svelte):
492
- * wu.ai.capability('dashboard', 'updateKPIs', {
493
- * description: 'Refresh the KPI cards with latest data',
494
- * handler: async () => { refreshKPIs(); return { updated: true }; },
495
- * });
496
- */
497
- capability(appName, actionName, config) {
498
- this._ensureInit();
499
- this._modules.orchestrate.register(appName, actionName, config);
500
- return this;
501
- }
502
-
503
- /**
504
- * Resolve a cross-app intent in a single conversation turn.
505
- *
506
- * The AI receives the full capability map (what each app can do),
507
- * current application state, and mounted apps. It resolves the
508
- * intent by calling the right capabilities across app boundaries.
509
- *
510
- * @param {string} description - Natural language intent
511
- * @param {object} [options]
512
- * @param {string[]} [options.plan] - Optional action sequence hint
513
- * @param {string} [options.provider] - LLM provider override
514
- * @param {number} [options.temperature] - Temperature override
515
- * @param {number} [options.maxTokens] - Max tokens override
516
- * @param {AbortSignal} [options.signal] - Abort signal
517
- * @param {string|object} [options.responseFormat] - Response format
518
- * @returns {Promise<{ content: string, tool_results: Array, usage: object|null, resolved: boolean, appsInvolved: string[] }>}
519
- *
520
- * @example
521
- * // Simple cross-app query
522
- * const result = await wu.ai.intent('Show me the top customer by order count');
523
- * // AI calls orders:getRecent → aggregates → returns answer
524
- *
525
- * // With plan hint
526
- * const result = await wu.ai.intent('Update all views after a new order', {
527
- * plan: ['orders:getRecent', 'dashboard:updateKPIs', 'analytics:refresh'],
528
- * });
529
- *
530
- * // With JSON response
531
- * const result = await wu.ai.intent('Get order stats by status', {
532
- * responseFormat: 'json',
533
- * });
534
- */
535
- async intent(description, options = {}) {
536
- this._ensureInit();
537
- return this._modules.orchestrate.resolve(description, options);
538
- }
539
-
540
- /**
541
- * Remove all capabilities for a micro-app.
542
- * Call this when a micro-app is unmounted to prevent stale
543
- * capabilities from appearing in the AI's capability map.
544
- *
545
- * @param {string} appName - The micro-app name
546
- *
547
- * @example
548
- * // In unmount lifecycle:
549
- * wu.ai.removeApp('orders');
550
- */
551
- removeApp(appName) {
552
- this._ensureInit();
553
- this._modules.orchestrate.removeApp(appName);
554
- return this;
555
- }
556
-
557
- /**
558
- * Register a reusable AI workflow — a named, step-by-step recipe
559
- * that the AI agent follows using browser automation.
560
- *
561
- * @param {string} name - Workflow name (e.g., 'register-user')
562
- * @param {object} config
563
- * @param {string} config.description - What this workflow does
564
- * @param {string[]} config.steps - Step-by-step instructions
565
- * Use {{paramName}} for parameter interpolation.
566
- * @param {object} [config.parameters] - Parameter definitions
567
- * @param {number} [config.maxSteps=15] - Max agent steps
568
- * @param {string} [config.provider] - LLM provider
569
- *
570
- * @example
571
- * wu.ai.workflow('register-user', {
572
- * description: 'Register a new user in the system',
573
- * steps: [
574
- * 'Navigate to the Customers section',
575
- * 'Click the "Add Customer" button',
576
- * 'Type "{{name}}" into the name field',
577
- * 'Type "{{email}}" into the email field',
578
- * 'Click Submit',
579
- * 'Verify the success message appears',
580
- * ],
581
- * parameters: {
582
- * name: { type: 'string', required: true },
583
- * email: { type: 'string', required: true },
584
- * },
585
- * });
586
- */
587
- workflow(name, config) {
588
- this._ensureInit();
589
- this._modules.orchestrate.registerWorkflow(name, config);
590
- return this;
591
- }
592
-
593
- /**
594
- * Execute a registered workflow. Returns an async generator
595
- * so you can observe each step in real time.
596
- *
597
- * @param {string} name - Workflow name
598
- * @param {object} [params={}] - Parameters to fill into the steps
599
- * @param {object} [options={}]
600
- * @param {Function} [options.onStep] - Callback per step
601
- * @param {Function} [options.shouldContinue] - Human-in-the-loop gate
602
- * @param {AbortSignal} [options.signal] - Abort signal
603
- * @returns {AsyncGenerator<AgentStepResult>}
604
- *
605
- * @example
606
- * // Run and watch every step
607
- * for await (const step of wu.ai.runWorkflow('register-user', {
608
- * name: 'Juan Pérez',
609
- * email: 'juan@test.com',
610
- * })) {
611
- * console.log(`Step ${step.step}: ${step.content}`);
612
- * if (step.type === 'done') console.log('Workflow complete!');
613
- * }
614
- *
615
- * // With human approval per step
616
- * for await (const step of wu.ai.runWorkflow('register-user', params, {
617
- * shouldContinue: (s) => confirm(`Continue? ${s.content?.slice(0, 60)}`),
618
- * })) {
619
- * renderStep(step);
620
- * }
621
- */
622
- async *runWorkflow(name, params = {}, options = {}) {
623
- this._ensureInit();
624
- yield* this._modules.orchestrate.executeWorkflow(name, params, options);
625
- }
626
-
627
- // ─── Context ───────────────────────────────────────────────────
628
-
629
- /**
630
- * Context configuration sub-API.
631
- * Access via wu.ai.context
632
- */
633
- get context() {
634
- this._ensureInit();
635
- return {
636
- configure: (config) => this._modules.context.configure(config),
637
- register: (name, config) => this._modules.context.register(name, config),
638
- collect: () => this._modules.context.collect(),
639
- getSnapshot: () => this._modules.context.getSnapshot(),
640
- };
641
- }
642
-
643
- // ─── Conversation Management ───────────────────────────────────
644
-
645
- /**
646
- * Conversation sub-API for direct history management.
647
- */
648
- get conversation() {
649
- this._ensureInit();
650
- return {
651
- getHistory: (ns) => this._modules.conversation.getHistory(ns),
652
- clear: (ns) => this._modules.conversation.clear(ns),
653
- clearAll: () => this._modules.conversation.clearAll(),
654
- inject: (role, content, opts) => this._modules.conversation.inject(role, content, opts),
655
- getNamespaces: () => this._modules.conversation.getNamespaces(),
656
- deleteNamespace: (ns) => this._modules.conversation.deleteNamespace(ns),
657
- };
658
- }
659
-
660
- // ─── Permissions ───────────────────────────────────────────────
661
-
662
- /**
663
- * Permissions sub-API.
664
- */
665
- get permissions() {
666
- this._ensureInit();
667
- return {
668
- configure: (config) => this._modules.permissions.configure(config),
669
- check: (perm) => this._modules.permissions.check(perm),
670
- getPermissions: () => this._modules.permissions.getPermissions(),
671
- setAllowedDomains: (domains) => this._modules.permissions.setAllowedDomains(domains),
672
- };
673
- }
674
-
675
- // ─── Paradigm 2: LLM → App (External Agent Access) ────────────
676
-
677
- /**
678
- * Get all registered tools (for external agents).
679
- * An agent connected via CDP can call: window.wu.ai.tools()
680
- *
681
- * @returns {Array<{ name, description, parameters }>}
682
- */
683
- tools() {
684
- this._ensureInit();
685
- return this._modules.actions.getToolSchemas();
686
- }
687
-
688
- /**
689
- * Expose tools via WebMCP (Chrome 146+ / W3C proposal).
690
- * Registers all actions with navigator.modelContext.registerTool()
691
- *
692
- * @returns {boolean} Whether WebMCP is available
693
- */
694
- expose() {
695
- this._ensureInit();
696
-
697
- if (typeof navigator === 'undefined' || !navigator.modelContext) {
698
- logger.wuDebug('[wu-ai] WebMCP not available (navigator.modelContext missing)');
699
- return false;
700
- }
701
-
702
- const tools = this._modules.actions.getToolSchemas();
703
- const actionNames = this._modules.actions.getNames();
704
-
705
- for (let i = 0; i < tools.length; i++) {
706
- const tool = tools[i];
707
- const actionName = actionNames[i];
708
-
709
- try {
710
- navigator.modelContext.registerTool({
711
- name: tool.name,
712
- description: tool.description,
713
- inputSchema: tool.parameters,
714
- handler: async (params) => {
715
- const result = await this.execute(actionName, params);
716
- return result.success ? result.result : { error: result.reason };
717
- },
718
- });
719
- } catch (err) {
720
- logger.wuDebug(`[wu-ai] WebMCP register failed for '${tool.name}': ${err.message}`);
721
- }
722
- }
723
-
724
- logger.wuInfo(`[wu-ai] Exposed ${tools.length} tools via WebMCP`);
725
-
726
- this._eventBus.emit('ai:webmcp:exposed', {
727
- toolCount: tools.length,
728
- tools: tools.map(t => t.name),
729
- }, { appName: 'wu-ai' });
730
-
731
- return true;
732
- }
733
-
734
- /**
735
- * Confirm a pending tool call (for UI integration).
736
- */
737
- confirmTool(callId) {
738
- if (!this._initialized) return;
739
- this._modules.actions.confirmTool(callId);
740
- }
741
-
742
- /**
743
- * Reject a pending tool call.
744
- */
745
- rejectTool(callId) {
746
- if (!this._initialized) return;
747
- this._modules.actions.rejectTool(callId);
748
- }
749
-
750
- // ─── Stats & Debug ─────────────────────────────────────────────
751
-
752
- getStats() {
753
- if (!this._initialized) return { initialized: false };
754
-
755
- return {
756
- initialized: true,
757
- provider: this._modules.provider.getStats(),
758
- permissions: this._modules.permissions.getStats(),
759
- context: this._modules.context.getStats(),
760
- actions: this._modules.actions.getStats(),
761
- conversation: this._modules.conversation.getStats(),
762
- triggers: this._modules.triggers.getStats(),
763
- agent: this._modules.agent.getStats(),
764
- orchestrate: this._modules.orchestrate.getStats(),
765
- };
766
- }
767
-
768
- /**
769
- * Destroy the AI system and clean up all resources.
770
- */
771
- destroy() {
772
- if (!this._initialized) return;
773
-
774
- this._modules.orchestrate.destroy();
775
- this._modules.agent.destroy();
776
- this._modules.conversation.abortAll();
777
- this._modules.triggers.destroy();
778
- this._modules = {};
779
- this._initialized = false;
780
-
781
- logger.wuInfo('[wu-ai] Destroyed');
782
- this._eventBus.emit('ai:destroyed', {}, { appName: 'wu-ai' });
783
- }
784
-
785
- // ─── Private ───────────────────────────────────────────────────
786
-
787
- _ensureInit() {
788
- if (!this._initialized) {
789
- this.init();
790
- }
791
- }
792
-
793
- _reconfigure(config) {
794
- if (config.permissions) this._modules.permissions.configure(config.permissions);
795
- if (config.rateLimit) this._modules.permissions.rateLimiter.configure(config.rateLimit);
796
- if (config.circuitBreaker) this._modules.permissions.circuitBreaker.configure(config.circuitBreaker);
797
- if (config.loopProtection) this._modules.permissions.loopProtection.configure(config.loopProtection);
798
- if (config.context) this._modules.context.configure(config.context);
799
- if (config.conversation) this._modules.conversation.configure(config.conversation);
800
- if (config.triggers) this._modules.triggers.configure(config.triggers);
801
- if (config.agent) this._modules.agent.configure(config.agent);
802
- if (config.orchestrate) this._modules.orchestrate.configure(config.orchestrate);
803
- }
804
- }
1
+ /**
2
+ * WU-AI: Central orchestrator for AI integration
3
+ *
4
+ * This is the main entry point for wu.ai — it wires together all sub-modules
5
+ * and exposes the public API. Lazy-initialized on first use.
6
+ *
7
+ * Architecture:
8
+ * WuAI (this file)
9
+ * ├── WuAIProvider → BYOL provider management (OpenAI, Anthropic, Ollama, Custom)
10
+ * ├── WuAIPermissions → 4-layer security (perms, rate limit, circuit breaker, loop guard)
11
+ * ├── WuAIContext → Auto context collection with token budget
12
+ * ├── WuAIActions → Tool/action registry and sandboxed execution
13
+ * ├── WuAIConversation → Multi-turn conversation manager with namespaces
14
+ * ├── WuAITriggers → Event-to-AI reactive bridge
15
+ * ├── WuAIAgent → Autonomous agent loop (goal → steps → done)
16
+ * ├── WuAIOrchestrate → Cross-micro-app AI coordination (capabilities + intents)
17
+ * └── BrowserPrimitives → Shared screenshot, click, type, a11y tree, interceptors
18
+ *
19
+ * Four Paradigms:
20
+ * 1. App → LLM send/stream/json → conversation with tool loops
21
+ * 2. LLM → App tools/execute/expose → external agents call into the app
22
+ * 3. AI Director agent(goal) → autonomous multi-step loop
23
+ * 4. MF Glue capability/intent → cross-app coordination via AI
24
+ *
25
+ * Public API (accessible via wu.ai):
26
+ * wu.ai.provider(name, config) → Register LLM provider
27
+ * wu.ai.send(message, opts) → Send message (non-streaming)
28
+ * wu.ai.stream(message, opts) → Send message (streaming)
29
+ * wu.ai.json(message, schema?) → Send and get parsed JSON back
30
+ * wu.ai.agent(goal, opts) → Run autonomous agent loop
31
+ * wu.ai.action(name, config) → Register an action/tool
32
+ * wu.ai.trigger(name, config) → Register an event trigger
33
+ * wu.ai.capability(app, name, c) → Register app-scoped capability
34
+ * wu.ai.intent(desc, opts) → Resolve cross-app intent
35
+ * wu.ai.removeApp(appName) → Remove app capabilities (unmount)
36
+ * wu.ai.workflow(name, config) → Register reusable AI workflow
37
+ * wu.ai.runWorkflow(name, params)→ Execute workflow (async generator)
38
+ * wu.ai.context.configure(...) → Configure context collection
39
+ * wu.ai.abort(namespace?) → Abort active request
40
+ *
41
+ * Paradigm 2 (External agent access):
42
+ * wu.ai.tools() → Get all registered tools (for CDP/WebMCP)
43
+ * wu.ai.execute(name, params) → Execute action directly (for external agents)
44
+ * wu.ai.expose() → Register tools via WebMCP (navigator.modelContext)
45
+ */
46
+
47
+ import { logger } from '../core/wu-logger.js';
48
+ import { WuAIProvider } from './wu-ai-provider.js';
49
+ import { WuAIPermissions } from './wu-ai-permissions.js';
50
+ import { WuAIContext } from './wu-ai-context.js';
51
+ import { WuAIActions } from './wu-ai-actions.js';
52
+ import { WuAIConversation } from './wu-ai-conversation.js';
53
+ import { WuAITriggers } from './wu-ai-triggers.js';
54
+ import { WuAIAgent } from './wu-ai-agent.js';
55
+ import { WuAIOrchestrate } from './wu-ai-orchestrate.js';
56
+ import { registerBrowserActions } from './wu-ai-browser.js';
57
+
58
+ export class WuAI {
59
+ /**
60
+ * @param {object} deps - Injected from WuCore
61
+ * @param {object} deps.eventBus - WuEventBus instance
62
+ * @param {object} deps.store - WuStore instance
63
+ * @param {object} deps.core - WuCore instance (for mounted apps)
64
+ */
65
+ constructor({ eventBus, store, core }) {
66
+ this._eventBus = eventBus;
67
+ this._store = store;
68
+ this._core = core;
69
+ this._initialized = false;
70
+ this._modules = {};
71
+ }
72
+
73
+ // ─── Lazy Initialization ───────────────────────────────────────
74
+
75
+ /**
76
+ * Initialize all sub-modules. Called automatically on first use,
77
+ * or can be called explicitly with configuration.
78
+ *
79
+ * @param {object} [config]
80
+ * @param {object} [config.permissions] - Permission overrides
81
+ * @param {object} [config.rateLimit] - Rate limit config
82
+ * @param {object} [config.circuitBreaker] - Circuit breaker config
83
+ * @param {object} [config.loopProtection] - Loop protection config
84
+ * @param {object} [config.context] - Context collection config
85
+ * @param {object} [config.conversation] - Conversation defaults
86
+ * @param {object} [config.triggers] - Trigger system config
87
+ */
88
+ init(config = {}) {
89
+ if (this._initialized) {
90
+ // Reconfigure if already initialized
91
+ this._reconfigure(config);
92
+ return this;
93
+ }
94
+
95
+ // 1. Permissions (independent — no deps)
96
+ this._modules.permissions = new WuAIPermissions({
97
+ permissions: config.permissions,
98
+ rateLimit: config.rateLimit,
99
+ circuitBreaker: config.circuitBreaker,
100
+ loopProtection: config.loopProtection,
101
+ allowedDomains: config.allowedDomains,
102
+ });
103
+
104
+ // 2. Provider (independent — no deps)
105
+ this._modules.provider = new WuAIProvider();
106
+
107
+ // 3. Context (depends on store, eventBus, core)
108
+ this._modules.context = new WuAIContext({
109
+ store: this._store,
110
+ eventBus: this._eventBus,
111
+ core: this._core,
112
+ });
113
+ if (config.context) {
114
+ this._modules.context.configure(config.context);
115
+ }
116
+
117
+ // 4. Actions (depends on eventBus, store, permissions)
118
+ this._modules.actions = new WuAIActions({
119
+ eventBus: this._eventBus,
120
+ store: this._store,
121
+ permissions: this._modules.permissions,
122
+ });
123
+
124
+ // 5. Conversation (depends on provider, actions, context, permissions, eventBus)
125
+ this._modules.conversation = new WuAIConversation({
126
+ provider: this._modules.provider,
127
+ actions: this._modules.actions,
128
+ context: this._modules.context,
129
+ permissions: this._modules.permissions,
130
+ eventBus: this._eventBus,
131
+ });
132
+ if (config.conversation) {
133
+ this._modules.conversation.configure(config.conversation);
134
+ }
135
+
136
+ // 6. Triggers (depends on eventBus, conversation, permissions)
137
+ this._modules.triggers = new WuAITriggers({
138
+ eventBus: this._eventBus,
139
+ conversation: this._modules.conversation,
140
+ permissions: this._modules.permissions,
141
+ });
142
+ if (config.triggers) {
143
+ this._modules.triggers.configure(config.triggers);
144
+ }
145
+
146
+ // 7. Agent (depends on conversation, actions, context, permissions, eventBus)
147
+ this._modules.agent = new WuAIAgent({
148
+ conversation: this._modules.conversation,
149
+ actions: this._modules.actions,
150
+ context: this._modules.context,
151
+ permissions: this._modules.permissions,
152
+ eventBus: this._eventBus,
153
+ });
154
+ if (config.agent) {
155
+ this._modules.agent.configure(config.agent);
156
+ }
157
+
158
+ // 8. Orchestrate — Paradigm 4: AI as microfrontend glue
159
+ // Agent ref is passed so workflows can delegate to the agent loop
160
+ // Store ref is passed for deterministic setState steps
161
+ this._modules.orchestrate = new WuAIOrchestrate({
162
+ actions: this._modules.actions,
163
+ conversation: this._modules.conversation,
164
+ context: this._modules.context,
165
+ permissions: this._modules.permissions,
166
+ eventBus: this._eventBus,
167
+ agent: this._modules.agent,
168
+ store: this._store,
169
+ });
170
+ if (config.orchestrate) {
171
+ this._modules.orchestrate.configure(config.orchestrate);
172
+ }
173
+
174
+ this._initialized = true;
175
+ logger.wuInfo('[wu-ai] Initialized');
176
+
177
+ // 9. Browser automation actions (screenshot, click, type, network, etc.)
178
+ // Must be AFTER _initialized = true to prevent recursive init loop
179
+ if (typeof window !== 'undefined') {
180
+ registerBrowserActions(this, this._core);
181
+ logger.wuInfo('[wu-ai] Browser actions registered (10 tools)');
182
+ }
183
+
184
+ this._eventBus.emit('ai:initialized', {}, { appName: 'wu-ai' });
185
+
186
+ return this;
187
+ }
188
+
189
+ // ─── Provider Management ───────────────────────────────────────
190
+
191
+ /**
192
+ * Register an LLM provider.
193
+ *
194
+ * @param {string} name - Provider name ('openai', 'anthropic', 'ollama', or custom)
195
+ * @param {object} config - { endpoint, apiKey?, model?, adapter?, send?, stream? }
196
+ *
197
+ * @example
198
+ * // OpenAI via proxy (recommended)
199
+ * wu.ai.provider('openai', { endpoint: '/api/ai/chat', model: 'gpt-4o' });
200
+ *
201
+ * // Anthropic direct (development only)
202
+ * wu.ai.provider('anthropic', {
203
+ * endpoint: 'https://api.anthropic.com/v1/messages',
204
+ * apiKey: 'sk-...',
205
+ * model: 'claude-sonnet-4-5-20250929',
206
+ * });
207
+ *
208
+ * // Local Ollama
209
+ * wu.ai.provider('ollama', { endpoint: 'http://localhost:11434/api/chat', model: 'llama3' });
210
+ *
211
+ * // Custom provider
212
+ * wu.ai.provider('my-llm', { send: async (messages, opts) => ({ content: '...' }) });
213
+ */
214
+ provider(name, config) {
215
+ this._ensureInit();
216
+ this._modules.provider.register(name, config);
217
+ return this;
218
+ }
219
+
220
+ // ─── Paradigm 1: App → LLM (Conversation) ─────────────────────
221
+
222
+ /**
223
+ * Send a message to the LLM and get a complete response.
224
+ *
225
+ * @param {string} message - User message
226
+ * @param {object} [options] - { namespace, systemPrompt, templateVars, temperature, maxTokens, provider, responseFormat, signal }
227
+ * @param {string} [options.provider] - Use a specific registered provider (e.g., 'anthropic', 'openai')
228
+ * @param {string|object} [options.responseFormat] - Request JSON output.
229
+ * - `'json'` — simple JSON mode (OpenAI: json_object, Ollama: format:"json", Anthropic: prompt injection)
230
+ * - `{ type: 'json_schema', schema: {...}, name?: string }` — structured output with JSON Schema
231
+ * (OpenAI: native json_schema mode, Ollama: schema in format, Anthropic: schema in system prompt)
232
+ * @returns {Promise<{ content: string, tool_results?: Array, usage?: object, namespace: string }>}
233
+ *
234
+ * @example
235
+ * const response = await wu.ai.send('What items are in the cart?');
236
+ * console.log(response.content);
237
+ *
238
+ * // With namespace for separate conversation
239
+ * const response = await wu.ai.send('Analyze this chart', { namespace: 'analytics' });
240
+ *
241
+ * // Use a specific provider for this message
242
+ * const response = await wu.ai.send('Translate this', { provider: 'anthropic' });
243
+ *
244
+ * // Simple JSON mode
245
+ * const response = await wu.ai.send('List 5 colors', { responseFormat: 'json' });
246
+ *
247
+ * // Structured output with JSON Schema
248
+ * const response = await wu.ai.send('List 5 colors', {
249
+ * responseFormat: {
250
+ * type: 'json_schema',
251
+ * schema: { type: 'object', properties: { colors: { type: 'array', items: { type: 'string' } } } },
252
+ * name: 'color_list',
253
+ * },
254
+ * });
255
+ */
256
+ async send(message, options = {}) {
257
+ this._ensureInit();
258
+ return this._modules.conversation.send(message, options);
259
+ }
260
+
261
+ /**
262
+ * Send a message and stream the response.
263
+ *
264
+ * @param {string} message - User message
265
+ * @param {object} [options] - Same as send()
266
+ * @yields {{ type: 'text'|'tool_result'|'done'|'error', content?: string }}
267
+ *
268
+ * @example
269
+ * for await (const chunk of wu.ai.stream('Tell me about this page')) {
270
+ * if (chunk.type === 'text') outputEl.textContent += chunk.content;
271
+ * if (chunk.type === 'done') console.log('Done!');
272
+ * }
273
+ */
274
+ async *stream(message, options = {}) {
275
+ this._ensureInit();
276
+ yield* this._modules.conversation.stream(message, options);
277
+ }
278
+
279
+ /**
280
+ * Send a message and get a parsed JSON response.
281
+ * Shortcut for send() with responseFormat + automatic JSON.parse().
282
+ *
283
+ * @param {string} message - User message
284
+ * @param {object} [options] - All send() options plus:
285
+ * @param {object} [options.schema] - JSON Schema for structured output
286
+ * @param {string} [options.schemaName='response'] - Schema name (required by OpenAI)
287
+ * @returns {Promise<{ data: object|null, raw: string, error?: string, usage?: object, namespace: string }>}
288
+ *
289
+ * @example
290
+ * // Simple JSON (no schema)
291
+ * const { data } = await wu.ai.json('List 5 colors as a JSON array');
292
+ * // data = ["red", "blue", ...]
293
+ *
294
+ * // With schema
295
+ * const { data } = await wu.ai.json('List 5 colors', {
296
+ * schema: { type: 'object', properties: { colors: { type: 'array', items: { type: 'string' } } } },
297
+ * });
298
+ * // data = { colors: ["red", "blue", ...] }
299
+ *
300
+ * // With schema + provider
301
+ * const { data } = await wu.ai.json('List 5 colors', {
302
+ * schema: mySchema,
303
+ * provider: 'openai',
304
+ * temperature: 0,
305
+ * });
306
+ */
307
+ async json(message, options = {}) {
308
+ this._ensureInit();
309
+
310
+ const { schema, schemaName, ...rest } = options;
311
+
312
+ let responseFormat;
313
+ if (schema) {
314
+ responseFormat = { type: 'json_schema', schema, name: schemaName || 'response' };
315
+ } else {
316
+ responseFormat = options.responseFormat || 'json';
317
+ }
318
+
319
+ const response = await this._modules.conversation.send(message, { ...rest, responseFormat });
320
+
321
+ // The provider already attempts parse and sets response.parsed / response.parseError
322
+ let data = null;
323
+ let error;
324
+
325
+ if (response.parsed !== undefined) {
326
+ data = response.parsed;
327
+ } else if (response.content) {
328
+ try {
329
+ data = JSON.parse(response.content);
330
+ } catch {
331
+ error = 'LLM response is not valid JSON';
332
+ }
333
+ }
334
+
335
+ return {
336
+ data,
337
+ raw: response.content || '',
338
+ error: error || response.parseError,
339
+ usage: response.usage,
340
+ namespace: response.namespace,
341
+ };
342
+ }
343
+
344
+ /**
345
+ * Abort active request(s).
346
+ *
347
+ * @param {string} [namespace] - Specific namespace, or all if omitted
348
+ */
349
+ abort(namespace) {
350
+ if (!this._initialized) return;
351
+ if (namespace) {
352
+ this._modules.conversation.abort(namespace);
353
+ } else {
354
+ this._modules.conversation.abortAll();
355
+ }
356
+ }
357
+
358
+ // ─── Actions / Tools ───────────────────────────────────────────
359
+
360
+ /**
361
+ * Register an action that the LLM can call.
362
+ *
363
+ * @param {string} name - Action name (used in tool_call)
364
+ * @param {object} config - { description, parameters, handler, confirm?, permissions?, dangerous? }
365
+ *
366
+ * @example
367
+ * wu.ai.action('addToCart', {
368
+ * description: 'Add an item to the shopping cart',
369
+ * parameters: {
370
+ * productId: { type: 'string', required: true },
371
+ * quantity: { type: 'number' },
372
+ * },
373
+ * handler: async (params, api) => {
374
+ * api.setState('cart.items', [...api.getState('cart.items'), params]);
375
+ * api.emit('cart:updated', params);
376
+ * return { added: params.productId };
377
+ * },
378
+ * confirm: true, // require user confirmation
379
+ * });
380
+ */
381
+ action(name, config) {
382
+ this._ensureInit();
383
+ this._modules.actions.register(name, config);
384
+ return this;
385
+ }
386
+
387
+ /**
388
+ * Execute an action directly (used by external agents via CDP/WebMCP).
389
+ *
390
+ * @param {string} name - Action name
391
+ * @param {object} params - Parameters
392
+ * @returns {Promise<{ success: boolean, result?: any, reason?: string }>}
393
+ */
394
+ async execute(name, params) {
395
+ this._ensureInit();
396
+ const traceId = this._modules.permissions.loopProtection.createTraceId();
397
+ return this._modules.actions.execute(name, params, { traceId, depth: 0 });
398
+ }
399
+
400
+ // ─── Triggers (Reactive AI) ────────────────────────────────────
401
+
402
+ /**
403
+ * Register a trigger that automatically sends messages to the LLM
404
+ * when specific events occur.
405
+ *
406
+ * @param {string} name - Trigger name
407
+ * @param {object} config - { pattern, prompt, condition?, debounce?, priority?, onResult? }
408
+ *
409
+ * @example
410
+ * wu.ai.trigger('cartAnalysis', {
411
+ * pattern: 'cart:updated',
412
+ * prompt: 'The cart was updated: {{data}}. Suggest complementary products.',
413
+ * debounce: 3000,
414
+ * priority: 'low',
415
+ * onResult: (result) => {
416
+ * wu.emit('ai:suggestions', { suggestions: result.content });
417
+ * },
418
+ * });
419
+ */
420
+ trigger(name, config) {
421
+ this._ensureInit();
422
+ this._modules.triggers.register(name, config);
423
+ return this;
424
+ }
425
+
426
+ /**
427
+ * Fire a trigger manually.
428
+ */
429
+ async fireTrigger(name, eventData) {
430
+ this._ensureInit();
431
+ return this._modules.triggers.fire(name, eventData);
432
+ }
433
+
434
+ // ─── Agent (Paradigm 3: Autonomous AI) ─────────────────────────
435
+
436
+ /**
437
+ * Run an autonomous agent that pursues a goal using available tools.
438
+ * Returns an async generator that yields step-by-step results.
439
+ *
440
+ * @param {string} goal - What the agent should accomplish
441
+ * @param {object} [options]
442
+ * @param {number} [options.maxSteps=10] - Maximum autonomous steps
443
+ * @param {string} [options.provider] - Which LLM provider to use
444
+ * @param {string} [options.namespace] - Conversation namespace
445
+ * @param {string} [options.systemPrompt] - Override system prompt
446
+ * @param {Function} [options.onStep] - Callback per step: (stepResult) => void
447
+ * @param {Function} [options.shouldContinue] - Human-in-the-loop: (stepResult) => boolean|Promise<boolean>
448
+ * @param {AbortSignal} [options.signal] - Abort signal
449
+ * @returns {AsyncGenerator<AgentStepResult>}
450
+ *
451
+ * @example
452
+ * // Basic usage
453
+ * for await (const step of wu.ai.agent('Find all orders above $100 and summarize them')) {
454
+ * console.log(`Step ${step.step}: ${step.content?.slice(0, 100)}`);
455
+ * if (step.done) console.log('Agent finished!');
456
+ * }
457
+ *
458
+ * // With human-in-the-loop
459
+ * for await (const step of wu.ai.agent('Reorganize the product catalog', {
460
+ * shouldContinue: (step) => confirm(`Continue? Step ${step.step}: ${step.content?.slice(0, 50)}`),
461
+ * })) {
462
+ * updateUI(step);
463
+ * }
464
+ */
465
+ async *agent(goal, options = {}) {
466
+ this._ensureInit();
467
+ yield* this._modules.agent.run(goal, options);
468
+ }
469
+
470
+ // ─── Paradigm 4: AI as Microfrontend Glue ─────────────────────
471
+
472
+ /**
473
+ * Register a capability scoped to a specific micro-app.
474
+ *
475
+ * Each micro-app calls this to declare what it can do. The AI uses
476
+ * the capability map to resolve cross-app intents.
477
+ *
478
+ * @param {string} appName - The micro-app name (e.g., 'orders', 'dashboard')
479
+ * @param {string} actionName - The capability name (e.g., 'getRecent', 'updateKPIs')
480
+ * @param {object} config - Same as wu.ai.action() config:
481
+ * { description, parameters, handler, confirm?, permissions?, dangerous? }
482
+ *
483
+ * @example
484
+ * // In orders micro-app (React):
485
+ * wu.ai.capability('orders', 'getRecent', {
486
+ * description: 'Get the N most recent orders',
487
+ * parameters: { limit: { type: 'number' } },
488
+ * handler: async (params) => fetchOrders({ limit: params.limit || 10 }),
489
+ * });
490
+ *
491
+ * // In dashboard micro-app (Svelte):
492
+ * wu.ai.capability('dashboard', 'updateKPIs', {
493
+ * description: 'Refresh the KPI cards with latest data',
494
+ * handler: async () => { refreshKPIs(); return { updated: true }; },
495
+ * });
496
+ */
497
+ capability(appName, actionName, config) {
498
+ this._ensureInit();
499
+ this._modules.orchestrate.register(appName, actionName, config);
500
+ return this;
501
+ }
502
+
503
+ /**
504
+ * Resolve a cross-app intent in a single conversation turn.
505
+ *
506
+ * The AI receives the full capability map (what each app can do),
507
+ * current application state, and mounted apps. It resolves the
508
+ * intent by calling the right capabilities across app boundaries.
509
+ *
510
+ * @param {string} description - Natural language intent
511
+ * @param {object} [options]
512
+ * @param {string[]} [options.plan] - Optional action sequence hint
513
+ * @param {string} [options.provider] - LLM provider override
514
+ * @param {number} [options.temperature] - Temperature override
515
+ * @param {number} [options.maxTokens] - Max tokens override
516
+ * @param {AbortSignal} [options.signal] - Abort signal
517
+ * @param {string|object} [options.responseFormat] - Response format
518
+ * @returns {Promise<{ content: string, tool_results: Array, usage: object|null, resolved: boolean, appsInvolved: string[] }>}
519
+ *
520
+ * @example
521
+ * // Simple cross-app query
522
+ * const result = await wu.ai.intent('Show me the top customer by order count');
523
+ * // AI calls orders:getRecent → aggregates → returns answer
524
+ *
525
+ * // With plan hint
526
+ * const result = await wu.ai.intent('Update all views after a new order', {
527
+ * plan: ['orders:getRecent', 'dashboard:updateKPIs', 'analytics:refresh'],
528
+ * });
529
+ *
530
+ * // With JSON response
531
+ * const result = await wu.ai.intent('Get order stats by status', {
532
+ * responseFormat: 'json',
533
+ * });
534
+ */
535
+ async intent(description, options = {}) {
536
+ this._ensureInit();
537
+ return this._modules.orchestrate.resolve(description, options);
538
+ }
539
+
540
+ /**
541
+ * Remove all capabilities for a micro-app.
542
+ * Call this when a micro-app is unmounted to prevent stale
543
+ * capabilities from appearing in the AI's capability map.
544
+ *
545
+ * @param {string} appName - The micro-app name
546
+ *
547
+ * @example
548
+ * // In unmount lifecycle:
549
+ * wu.ai.removeApp('orders');
550
+ */
551
+ removeApp(appName) {
552
+ this._ensureInit();
553
+ this._modules.orchestrate.removeApp(appName);
554
+ return this;
555
+ }
556
+
557
+ /**
558
+ * Register a reusable AI workflow — a named, step-by-step recipe
559
+ * that the AI agent follows using browser automation.
560
+ *
561
+ * @param {string} name - Workflow name (e.g., 'register-user')
562
+ * @param {object} config
563
+ * @param {string} config.description - What this workflow does
564
+ * @param {string[]} config.steps - Step-by-step instructions
565
+ * Use {{paramName}} for parameter interpolation.
566
+ * @param {object} [config.parameters] - Parameter definitions
567
+ * @param {number} [config.maxSteps=15] - Max agent steps
568
+ * @param {string} [config.provider] - LLM provider
569
+ *
570
+ * @example
571
+ * wu.ai.workflow('register-user', {
572
+ * description: 'Register a new user in the system',
573
+ * steps: [
574
+ * 'Navigate to the Customers section',
575
+ * 'Click the "Add Customer" button',
576
+ * 'Type "{{name}}" into the name field',
577
+ * 'Type "{{email}}" into the email field',
578
+ * 'Click Submit',
579
+ * 'Verify the success message appears',
580
+ * ],
581
+ * parameters: {
582
+ * name: { type: 'string', required: true },
583
+ * email: { type: 'string', required: true },
584
+ * },
585
+ * });
586
+ */
587
+ workflow(name, config) {
588
+ this._ensureInit();
589
+ this._modules.orchestrate.registerWorkflow(name, config);
590
+ return this;
591
+ }
592
+
593
+ /**
594
+ * Execute a registered workflow. Returns an async generator
595
+ * so you can observe each step in real time.
596
+ *
597
+ * @param {string} name - Workflow name
598
+ * @param {object} [params={}] - Parameters to fill into the steps
599
+ * @param {object} [options={}]
600
+ * @param {Function} [options.onStep] - Callback per step
601
+ * @param {Function} [options.shouldContinue] - Human-in-the-loop gate
602
+ * @param {AbortSignal} [options.signal] - Abort signal
603
+ * @returns {AsyncGenerator<AgentStepResult>}
604
+ *
605
+ * @example
606
+ * // Run and watch every step
607
+ * for await (const step of wu.ai.runWorkflow('register-user', {
608
+ * name: 'Juan Pérez',
609
+ * email: 'juan@test.com',
610
+ * })) {
611
+ * console.log(`Step ${step.step}: ${step.content}`);
612
+ * if (step.type === 'done') console.log('Workflow complete!');
613
+ * }
614
+ *
615
+ * // With human approval per step
616
+ * for await (const step of wu.ai.runWorkflow('register-user', params, {
617
+ * shouldContinue: (s) => confirm(`Continue? ${s.content?.slice(0, 60)}`),
618
+ * })) {
619
+ * renderStep(step);
620
+ * }
621
+ */
622
+ async *runWorkflow(name, params = {}, options = {}) {
623
+ this._ensureInit();
624
+ yield* this._modules.orchestrate.executeWorkflow(name, params, options);
625
+ }
626
+
627
+ // ─── Context ───────────────────────────────────────────────────
628
+
629
+ /**
630
+ * Context configuration sub-API.
631
+ * Access via wu.ai.context
632
+ */
633
+ get context() {
634
+ this._ensureInit();
635
+ return {
636
+ configure: (config) => this._modules.context.configure(config),
637
+ register: (name, config) => this._modules.context.register(name, config),
638
+ collect: () => this._modules.context.collect(),
639
+ getSnapshot: () => this._modules.context.getSnapshot(),
640
+ };
641
+ }
642
+
643
+ // ─── Conversation Management ───────────────────────────────────
644
+
645
+ /**
646
+ * Conversation sub-API for direct history management.
647
+ */
648
+ get conversation() {
649
+ this._ensureInit();
650
+ return {
651
+ getHistory: (ns) => this._modules.conversation.getHistory(ns),
652
+ clear: (ns) => this._modules.conversation.clear(ns),
653
+ clearAll: () => this._modules.conversation.clearAll(),
654
+ inject: (role, content, opts) => this._modules.conversation.inject(role, content, opts),
655
+ getNamespaces: () => this._modules.conversation.getNamespaces(),
656
+ deleteNamespace: (ns) => this._modules.conversation.deleteNamespace(ns),
657
+ };
658
+ }
659
+
660
+ // ─── Permissions ───────────────────────────────────────────────
661
+
662
+ /**
663
+ * Permissions sub-API.
664
+ */
665
+ get permissions() {
666
+ this._ensureInit();
667
+ return {
668
+ configure: (config) => this._modules.permissions.configure(config),
669
+ check: (perm) => this._modules.permissions.check(perm),
670
+ getPermissions: () => this._modules.permissions.getPermissions(),
671
+ setAllowedDomains: (domains) => this._modules.permissions.setAllowedDomains(domains),
672
+ };
673
+ }
674
+
675
+ // ─── Paradigm 2: LLM → App (External Agent Access) ────────────
676
+
677
+ /**
678
+ * Get all registered tools (for external agents).
679
+ * An agent connected via CDP can call: window.wu.ai.tools()
680
+ *
681
+ * @returns {Array<{ name, description, parameters }>}
682
+ */
683
+ tools() {
684
+ this._ensureInit();
685
+ return this._modules.actions.getToolSchemas();
686
+ }
687
+
688
+ /**
689
+ * Expose tools via WebMCP (Chrome 146+ / W3C proposal).
690
+ * Registers all actions with navigator.modelContext.registerTool()
691
+ *
692
+ * @returns {boolean} Whether WebMCP is available
693
+ */
694
+ expose() {
695
+ this._ensureInit();
696
+
697
+ if (typeof navigator === 'undefined' || !navigator.modelContext) {
698
+ logger.wuDebug('[wu-ai] WebMCP not available (navigator.modelContext missing)');
699
+ return false;
700
+ }
701
+
702
+ const tools = this._modules.actions.getToolSchemas();
703
+ const actionNames = this._modules.actions.getNames();
704
+
705
+ for (let i = 0; i < tools.length; i++) {
706
+ const tool = tools[i];
707
+ const actionName = actionNames[i];
708
+
709
+ try {
710
+ navigator.modelContext.registerTool({
711
+ name: tool.name,
712
+ description: tool.description,
713
+ inputSchema: tool.parameters,
714
+ handler: async (params) => {
715
+ const result = await this.execute(actionName, params);
716
+ return result.success ? result.result : { error: result.reason };
717
+ },
718
+ });
719
+ } catch (err) {
720
+ logger.wuDebug(`[wu-ai] WebMCP register failed for '${tool.name}': ${err.message}`);
721
+ }
722
+ }
723
+
724
+ logger.wuInfo(`[wu-ai] Exposed ${tools.length} tools via WebMCP`);
725
+
726
+ this._eventBus.emit('ai:webmcp:exposed', {
727
+ toolCount: tools.length,
728
+ tools: tools.map(t => t.name),
729
+ }, { appName: 'wu-ai' });
730
+
731
+ return true;
732
+ }
733
+
734
+ /**
735
+ * Confirm a pending tool call (for UI integration).
736
+ */
737
+ confirmTool(callId) {
738
+ if (!this._initialized) return;
739
+ this._modules.actions.confirmTool(callId);
740
+ }
741
+
742
+ /**
743
+ * Reject a pending tool call.
744
+ */
745
+ rejectTool(callId) {
746
+ if (!this._initialized) return;
747
+ this._modules.actions.rejectTool(callId);
748
+ }
749
+
750
+ // ─── Stats & Debug ─────────────────────────────────────────────
751
+
752
+ getStats() {
753
+ if (!this._initialized) return { initialized: false };
754
+
755
+ return {
756
+ initialized: true,
757
+ provider: this._modules.provider.getStats(),
758
+ permissions: this._modules.permissions.getStats(),
759
+ context: this._modules.context.getStats(),
760
+ actions: this._modules.actions.getStats(),
761
+ conversation: this._modules.conversation.getStats(),
762
+ triggers: this._modules.triggers.getStats(),
763
+ agent: this._modules.agent.getStats(),
764
+ orchestrate: this._modules.orchestrate.getStats(),
765
+ };
766
+ }
767
+
768
+ /**
769
+ * Destroy the AI system and clean up all resources.
770
+ */
771
+ destroy() {
772
+ if (!this._initialized) return;
773
+
774
+ this._modules.orchestrate.destroy();
775
+ this._modules.agent.destroy();
776
+ this._modules.conversation.abortAll();
777
+ this._modules.triggers.destroy();
778
+ this._modules = {};
779
+ this._initialized = false;
780
+
781
+ logger.wuInfo('[wu-ai] Destroyed');
782
+ this._eventBus.emit('ai:destroyed', {}, { appName: 'wu-ai' });
783
+ }
784
+
785
+ // ─── Private ───────────────────────────────────────────────────
786
+
787
+ _ensureInit() {
788
+ if (!this._initialized) {
789
+ this.init();
790
+ }
791
+ }
792
+
793
+ _reconfigure(config) {
794
+ if (config.permissions) this._modules.permissions.configure(config.permissions);
795
+ if (config.rateLimit) this._modules.permissions.rateLimiter.configure(config.rateLimit);
796
+ if (config.circuitBreaker) this._modules.permissions.circuitBreaker.configure(config.circuitBreaker);
797
+ if (config.loopProtection) this._modules.permissions.loopProtection.configure(config.loopProtection);
798
+ if (config.context) this._modules.context.configure(config.context);
799
+ if (config.conversation) this._modules.conversation.configure(config.conversation);
800
+ if (config.triggers) this._modules.triggers.configure(config.triggers);
801
+ if (config.agent) this._modules.agent.configure(config.agent);
802
+ if (config.orchestrate) this._modules.orchestrate.configure(config.orchestrate);
803
+ }
804
+ }