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.
- package/README.md +52 -20
- package/dist/wu-framework.cjs.js +1 -1
- package/dist/wu-framework.cjs.js.map +1 -1
- package/dist/wu-framework.dev.js +15511 -15146
- package/dist/wu-framework.dev.js.map +1 -1
- package/dist/wu-framework.esm.js +1 -1
- package/dist/wu-framework.esm.js.map +1 -1
- package/dist/wu-framework.umd.js +1 -1
- package/dist/wu-framework.umd.js.map +1 -1
- package/package.json +166 -161
- package/src/adapters/angular/ai.js +30 -30
- package/src/adapters/angular/index.d.ts +154 -154
- package/src/adapters/angular/index.js +932 -932
- package/src/adapters/angular.d.ts +3 -3
- package/src/adapters/angular.js +3 -3
- package/src/adapters/index.js +168 -168
- package/src/adapters/lit/ai.js +20 -20
- package/src/adapters/lit/index.d.ts +120 -120
- package/src/adapters/lit/index.js +721 -721
- package/src/adapters/lit.d.ts +3 -3
- package/src/adapters/lit.js +3 -3
- package/src/adapters/preact/ai.js +33 -33
- package/src/adapters/preact/index.d.ts +108 -108
- package/src/adapters/preact/index.js +661 -661
- package/src/adapters/preact.d.ts +3 -3
- package/src/adapters/preact.js +3 -3
- package/src/adapters/react/index.js +48 -54
- package/src/adapters/react.d.ts +3 -3
- package/src/adapters/react.js +3 -3
- package/src/adapters/shared.js +64 -64
- package/src/adapters/solid/ai.js +32 -32
- package/src/adapters/solid/index.d.ts +101 -101
- package/src/adapters/solid/index.js +586 -586
- package/src/adapters/solid.d.ts +3 -3
- package/src/adapters/solid.js +3 -3
- package/src/adapters/svelte/ai.js +31 -31
- package/src/adapters/svelte/index.d.ts +166 -166
- package/src/adapters/svelte/index.js +798 -798
- package/src/adapters/svelte.d.ts +3 -3
- package/src/adapters/svelte.js +3 -3
- package/src/adapters/vanilla/ai.js +30 -30
- package/src/adapters/vanilla/index.d.ts +179 -179
- package/src/adapters/vanilla/index.js +785 -785
- package/src/adapters/vanilla.d.ts +3 -3
- package/src/adapters/vanilla.js +3 -3
- package/src/adapters/vue/ai.js +52 -52
- package/src/adapters/vue/index.d.ts +299 -299
- package/src/adapters/vue/index.js +610 -610
- package/src/adapters/vue.d.ts +3 -3
- package/src/adapters/vue.js +3 -3
- package/src/ai/wu-ai-actions.js +261 -261
- package/src/ai/wu-ai-agent.js +546 -546
- package/src/ai/wu-ai-browser-primitives.js +354 -354
- package/src/ai/wu-ai-browser.js +380 -380
- package/src/ai/wu-ai-context.js +332 -332
- package/src/ai/wu-ai-conversation.js +613 -613
- package/src/ai/wu-ai-orchestrate.js +1021 -1021
- package/src/ai/wu-ai-permissions.js +381 -381
- package/src/ai/wu-ai-provider.js +700 -700
- package/src/ai/wu-ai-schema.js +225 -225
- package/src/ai/wu-ai-triggers.js +396 -396
- package/src/ai/wu-ai.js +804 -804
- package/src/core/wu-app.js +236 -236
- package/src/core/wu-cache.js +498 -477
- package/src/core/wu-core.js +1412 -1398
- package/src/core/wu-error-boundary.js +396 -382
- package/src/core/wu-event-bus.js +390 -348
- package/src/core/wu-hooks.js +350 -350
- package/src/core/wu-html-parser.js +199 -190
- package/src/core/wu-iframe-sandbox.js +328 -328
- package/src/core/wu-loader.js +385 -273
- package/src/core/wu-logger.js +142 -134
- package/src/core/wu-manifest.js +532 -509
- package/src/core/wu-mcp-bridge.js +432 -432
- package/src/core/wu-overrides.js +510 -510
- package/src/core/wu-performance.js +228 -228
- package/src/core/wu-plugin.js +401 -348
- package/src/core/wu-prefetch.js +414 -414
- package/src/core/wu-proxy-sandbox.js +477 -476
- package/src/core/wu-sandbox.js +779 -779
- package/src/core/wu-script-executor.js +161 -113
- package/src/core/wu-snapshot-sandbox.js +227 -227
- package/src/core/wu-store.js +13 -3
- package/src/core/wu-strategies.js +256 -256
- package/src/core/wu-style-bridge.js +477 -477
- package/src/index.d.ts +317 -0
- package/src/index.js +234 -224
- 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
|
+
}
|