wu-framework 1.1.14 → 1.1.16

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 (90) hide show
  1. package/LICENSE +39 -39
  2. package/README.md +408 -408
  3. package/dist/wu-framework.cjs.js.map +1 -1
  4. package/dist/wu-framework.dev.js +15151 -15151
  5. package/dist/wu-framework.dev.js.map +1 -1
  6. package/dist/wu-framework.esm.js.map +1 -1
  7. package/dist/wu-framework.umd.js.map +1 -1
  8. package/integrations/astro/README.md +127 -127
  9. package/integrations/astro/WuApp.astro +63 -63
  10. package/integrations/astro/WuShell.astro +39 -39
  11. package/integrations/astro/index.js +68 -68
  12. package/integrations/astro/package.json +38 -38
  13. package/integrations/astro/types.d.ts +53 -53
  14. package/package.json +161 -161
  15. package/src/adapters/angular/ai.js +30 -30
  16. package/src/adapters/angular/index.d.ts +154 -154
  17. package/src/adapters/angular/index.js +932 -932
  18. package/src/adapters/angular.d.ts +3 -3
  19. package/src/adapters/angular.js +3 -3
  20. package/src/adapters/index.js +168 -168
  21. package/src/adapters/lit/ai.js +20 -20
  22. package/src/adapters/lit/index.d.ts +120 -120
  23. package/src/adapters/lit/index.js +721 -721
  24. package/src/adapters/lit.d.ts +3 -3
  25. package/src/adapters/lit.js +3 -3
  26. package/src/adapters/preact/ai.js +33 -33
  27. package/src/adapters/preact/index.d.ts +108 -108
  28. package/src/adapters/preact/index.js +661 -661
  29. package/src/adapters/preact.d.ts +3 -3
  30. package/src/adapters/preact.js +3 -3
  31. package/src/adapters/react/index.js +48 -54
  32. package/src/adapters/react.d.ts +3 -3
  33. package/src/adapters/react.js +3 -3
  34. package/src/adapters/shared.js +64 -64
  35. package/src/adapters/solid/ai.js +32 -32
  36. package/src/adapters/solid/index.d.ts +101 -101
  37. package/src/adapters/solid/index.js +586 -586
  38. package/src/adapters/solid.d.ts +3 -3
  39. package/src/adapters/solid.js +3 -3
  40. package/src/adapters/svelte/ai.js +31 -31
  41. package/src/adapters/svelte/index.d.ts +166 -166
  42. package/src/adapters/svelte/index.js +798 -798
  43. package/src/adapters/svelte.d.ts +3 -3
  44. package/src/adapters/svelte.js +3 -3
  45. package/src/adapters/vanilla/ai.js +30 -30
  46. package/src/adapters/vanilla/index.d.ts +179 -179
  47. package/src/adapters/vanilla/index.js +785 -785
  48. package/src/adapters/vanilla.d.ts +3 -3
  49. package/src/adapters/vanilla.js +3 -3
  50. package/src/adapters/vue/ai.js +52 -52
  51. package/src/adapters/vue/index.d.ts +299 -299
  52. package/src/adapters/vue/index.js +610 -610
  53. package/src/adapters/vue.d.ts +3 -3
  54. package/src/adapters/vue.js +3 -3
  55. package/src/ai/wu-ai-actions.js +261 -261
  56. package/src/ai/wu-ai-agent.js +546 -546
  57. package/src/ai/wu-ai-browser-primitives.js +354 -354
  58. package/src/ai/wu-ai-browser.js +380 -380
  59. package/src/ai/wu-ai-context.js +332 -332
  60. package/src/ai/wu-ai-conversation.js +613 -613
  61. package/src/ai/wu-ai-orchestrate.js +1021 -1021
  62. package/src/ai/wu-ai-permissions.js +381 -381
  63. package/src/ai/wu-ai-provider.js +700 -700
  64. package/src/ai/wu-ai-schema.js +225 -225
  65. package/src/ai/wu-ai-triggers.js +396 -396
  66. package/src/ai/wu-ai.js +804 -804
  67. package/src/core/wu-app.js +236 -236
  68. package/src/core/wu-cache.js +477 -477
  69. package/src/core/wu-core.js +1398 -1398
  70. package/src/core/wu-error-boundary.js +382 -382
  71. package/src/core/wu-event-bus.js +348 -348
  72. package/src/core/wu-hooks.js +350 -350
  73. package/src/core/wu-html-parser.js +190 -190
  74. package/src/core/wu-iframe-sandbox.js +328 -328
  75. package/src/core/wu-loader.js +272 -272
  76. package/src/core/wu-logger.js +134 -134
  77. package/src/core/wu-manifest.js +509 -509
  78. package/src/core/wu-mcp-bridge.js +432 -432
  79. package/src/core/wu-overrides.js +510 -510
  80. package/src/core/wu-performance.js +228 -228
  81. package/src/core/wu-plugin.js +348 -348
  82. package/src/core/wu-prefetch.js +414 -414
  83. package/src/core/wu-proxy-sandbox.js +476 -476
  84. package/src/core/wu-sandbox.js +779 -779
  85. package/src/core/wu-script-executor.js +113 -113
  86. package/src/core/wu-snapshot-sandbox.js +227 -227
  87. package/src/core/wu-strategies.js +256 -256
  88. package/src/core/wu-style-bridge.js +477 -477
  89. package/src/index.js +224 -224
  90. 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
+ }