wu-framework 1.1.6 → 1.1.8
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 +511 -977
- package/dist/wu-framework.cjs.js +3 -1
- package/dist/wu-framework.cjs.js.map +1 -0
- package/dist/wu-framework.dev.js +7533 -2761
- package/dist/wu-framework.dev.js.map +1 -1
- package/dist/wu-framework.esm.js +3 -0
- package/dist/wu-framework.esm.js.map +1 -0
- package/dist/wu-framework.umd.js +3 -1
- package/dist/wu-framework.umd.js.map +1 -0
- package/integrations/astro/README.md +127 -0
- package/integrations/astro/WuApp.astro +63 -0
- package/integrations/astro/WuShell.astro +39 -0
- package/integrations/astro/index.js +68 -0
- package/integrations/astro/package.json +38 -0
- package/integrations/astro/types.d.ts +53 -0
- package/package.json +94 -74
- package/src/adapters/angular/ai.js +30 -0
- package/src/adapters/angular/index.d.ts +154 -0
- package/src/adapters/angular/index.js +932 -0
- package/src/adapters/angular.d.ts +3 -154
- package/src/adapters/angular.js +3 -813
- package/src/adapters/index.js +35 -24
- package/src/adapters/lit/ai.js +20 -0
- package/src/adapters/lit/index.d.ts +120 -0
- package/src/adapters/lit/index.js +721 -0
- package/src/adapters/lit.d.ts +3 -120
- package/src/adapters/lit.js +3 -726
- package/src/adapters/preact/ai.js +33 -0
- package/src/adapters/preact/index.d.ts +108 -0
- package/src/adapters/preact/index.js +661 -0
- package/src/adapters/preact.d.ts +3 -108
- package/src/adapters/preact.js +3 -665
- package/src/adapters/react/ai.js +135 -0
- package/src/adapters/react/index.d.ts +246 -0
- package/src/adapters/react/index.js +689 -0
- package/src/adapters/react.d.ts +3 -212
- package/src/adapters/react.js +3 -513
- package/src/adapters/shared.js +64 -0
- package/src/adapters/solid/ai.js +32 -0
- package/src/adapters/solid/index.d.ts +101 -0
- package/src/adapters/solid/index.js +586 -0
- package/src/adapters/solid.d.ts +3 -101
- package/src/adapters/solid.js +3 -591
- package/src/adapters/svelte/ai.js +31 -0
- package/src/adapters/svelte/index.d.ts +166 -0
- package/src/adapters/svelte/index.js +798 -0
- package/src/adapters/svelte.d.ts +3 -166
- package/src/adapters/svelte.js +3 -803
- package/src/adapters/vanilla/ai.js +30 -0
- package/src/adapters/vanilla/index.d.ts +179 -0
- package/src/adapters/vanilla/index.js +785 -0
- package/src/adapters/vanilla.d.ts +3 -179
- package/src/adapters/vanilla.js +3 -791
- package/src/adapters/vue/ai.js +52 -0
- package/src/adapters/vue/index.d.ts +299 -0
- package/src/adapters/vue/index.js +608 -0
- package/src/adapters/vue.d.ts +3 -299
- package/src/adapters/vue.js +3 -611
- package/src/ai/wu-ai-actions.js +261 -0
- package/src/ai/wu-ai-browser.js +663 -0
- package/src/ai/wu-ai-context.js +332 -0
- package/src/ai/wu-ai-conversation.js +554 -0
- package/src/ai/wu-ai-permissions.js +381 -0
- package/src/ai/wu-ai-provider.js +605 -0
- package/src/ai/wu-ai-schema.js +225 -0
- package/src/ai/wu-ai-triggers.js +396 -0
- package/src/ai/wu-ai.js +474 -0
- package/src/core/wu-app.js +50 -8
- package/src/core/wu-cache.js +1 -1
- package/src/core/wu-core.js +645 -677
- package/src/core/wu-html-parser.js +121 -211
- package/src/core/wu-iframe-sandbox.js +328 -0
- package/src/core/wu-mcp-bridge.js +647 -0
- package/src/core/wu-overrides.js +510 -0
- package/src/core/wu-prefetch.js +414 -0
- package/src/core/wu-proxy-sandbox.js +398 -75
- package/src/core/wu-sandbox.js +86 -268
- package/src/core/wu-script-executor.js +79 -182
- package/src/core/wu-snapshot-sandbox.js +149 -106
- package/src/core/wu-strategies.js +13 -0
- package/src/core/wu-style-bridge.js +0 -2
- package/src/index.js +139 -665
- package/dist/wu-framework.hex.js +0 -23
- package/dist/wu-framework.min.js +0 -1
- package/dist/wu-framework.obf.js +0 -1
- package/scripts/build-protected.js +0 -366
- package/scripts/build.js +0 -212
- package/scripts/rollup-plugin-hex.js +0 -143
- package/src/core/wu-registry.js +0 -60
- package/src/core/wu-sandbox-pool.js +0 -390
package/src/ai/wu-ai.js
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
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
|
|
10
|
+
* ├── WuAIPermissions → Security, rate limiting, circuit breaker
|
|
11
|
+
* ├── WuAIContext → Auto context collection for LLM
|
|
12
|
+
* ├── WuAIActions → Tool/action registry and execution
|
|
13
|
+
* ├── WuAIConversation → Multi-turn conversation manager
|
|
14
|
+
* └── WuAITriggers → Event-to-AI bridge (reactive AI)
|
|
15
|
+
*
|
|
16
|
+
* Public API (accessible via wu.ai):
|
|
17
|
+
* wu.ai.provider(name, config) → Register LLM provider
|
|
18
|
+
* wu.ai.send(message, opts) → Send message (non-streaming)
|
|
19
|
+
* wu.ai.stream(message, opts) → Send message (streaming)
|
|
20
|
+
* wu.ai.action(name, config) → Register an action/tool
|
|
21
|
+
* wu.ai.trigger(name, config) → Register an event trigger
|
|
22
|
+
* wu.ai.context.configure(...) → Configure context collection
|
|
23
|
+
* wu.ai.abort(namespace?) → Abort active request
|
|
24
|
+
*
|
|
25
|
+
* Paradigm 2 (External agent access):
|
|
26
|
+
* wu.ai.tools() → Get all registered tools (for CDP/WebMCP)
|
|
27
|
+
* wu.ai.execute(name, params) → Execute action directly (for external agents)
|
|
28
|
+
* wu.ai.expose() → Register tools via WebMCP (navigator.modelContext)
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { logger } from '../core/wu-logger.js';
|
|
32
|
+
import { WuAIProvider } from './wu-ai-provider.js';
|
|
33
|
+
import { WuAIPermissions } from './wu-ai-permissions.js';
|
|
34
|
+
import { WuAIContext } from './wu-ai-context.js';
|
|
35
|
+
import { WuAIActions } from './wu-ai-actions.js';
|
|
36
|
+
import { WuAIConversation } from './wu-ai-conversation.js';
|
|
37
|
+
import { WuAITriggers } from './wu-ai-triggers.js';
|
|
38
|
+
import { registerBrowserActions } from './wu-ai-browser.js';
|
|
39
|
+
|
|
40
|
+
export class WuAI {
|
|
41
|
+
/**
|
|
42
|
+
* @param {object} deps - Injected from WuCore
|
|
43
|
+
* @param {object} deps.eventBus - WuEventBus instance
|
|
44
|
+
* @param {object} deps.store - WuStore instance
|
|
45
|
+
* @param {object} deps.core - WuCore instance (for mounted apps)
|
|
46
|
+
*/
|
|
47
|
+
constructor({ eventBus, store, core }) {
|
|
48
|
+
this._eventBus = eventBus;
|
|
49
|
+
this._store = store;
|
|
50
|
+
this._core = core;
|
|
51
|
+
this._initialized = false;
|
|
52
|
+
this._modules = {};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─── Lazy Initialization ───────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Initialize all sub-modules. Called automatically on first use,
|
|
59
|
+
* or can be called explicitly with configuration.
|
|
60
|
+
*
|
|
61
|
+
* @param {object} [config]
|
|
62
|
+
* @param {object} [config.permissions] - Permission overrides
|
|
63
|
+
* @param {object} [config.rateLimit] - Rate limit config
|
|
64
|
+
* @param {object} [config.circuitBreaker] - Circuit breaker config
|
|
65
|
+
* @param {object} [config.loopProtection] - Loop protection config
|
|
66
|
+
* @param {object} [config.context] - Context collection config
|
|
67
|
+
* @param {object} [config.conversation] - Conversation defaults
|
|
68
|
+
* @param {object} [config.triggers] - Trigger system config
|
|
69
|
+
*/
|
|
70
|
+
init(config = {}) {
|
|
71
|
+
if (this._initialized) {
|
|
72
|
+
// Reconfigure if already initialized
|
|
73
|
+
this._reconfigure(config);
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 1. Permissions (independent — no deps)
|
|
78
|
+
this._modules.permissions = new WuAIPermissions({
|
|
79
|
+
permissions: config.permissions,
|
|
80
|
+
rateLimit: config.rateLimit,
|
|
81
|
+
circuitBreaker: config.circuitBreaker,
|
|
82
|
+
loopProtection: config.loopProtection,
|
|
83
|
+
allowedDomains: config.allowedDomains,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// 2. Provider (independent — no deps)
|
|
87
|
+
this._modules.provider = new WuAIProvider();
|
|
88
|
+
|
|
89
|
+
// 3. Context (depends on store, eventBus, core)
|
|
90
|
+
this._modules.context = new WuAIContext({
|
|
91
|
+
store: this._store,
|
|
92
|
+
eventBus: this._eventBus,
|
|
93
|
+
core: this._core,
|
|
94
|
+
});
|
|
95
|
+
if (config.context) {
|
|
96
|
+
this._modules.context.configure(config.context);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 4. Actions (depends on eventBus, store, permissions)
|
|
100
|
+
this._modules.actions = new WuAIActions({
|
|
101
|
+
eventBus: this._eventBus,
|
|
102
|
+
store: this._store,
|
|
103
|
+
permissions: this._modules.permissions,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// 5. Conversation (depends on provider, actions, context, permissions, eventBus)
|
|
107
|
+
this._modules.conversation = new WuAIConversation({
|
|
108
|
+
provider: this._modules.provider,
|
|
109
|
+
actions: this._modules.actions,
|
|
110
|
+
context: this._modules.context,
|
|
111
|
+
permissions: this._modules.permissions,
|
|
112
|
+
eventBus: this._eventBus,
|
|
113
|
+
});
|
|
114
|
+
if (config.conversation) {
|
|
115
|
+
this._modules.conversation.configure(config.conversation);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 6. Triggers (depends on eventBus, conversation, permissions)
|
|
119
|
+
this._modules.triggers = new WuAITriggers({
|
|
120
|
+
eventBus: this._eventBus,
|
|
121
|
+
conversation: this._modules.conversation,
|
|
122
|
+
permissions: this._modules.permissions,
|
|
123
|
+
});
|
|
124
|
+
if (config.triggers) {
|
|
125
|
+
this._modules.triggers.configure(config.triggers);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this._initialized = true;
|
|
129
|
+
logger.wuInfo('[wu-ai] Initialized');
|
|
130
|
+
|
|
131
|
+
// 7. Browser automation actions (screenshot, click, type, network, etc.)
|
|
132
|
+
// Must be AFTER _initialized = true to prevent recursive init loop
|
|
133
|
+
if (typeof window !== 'undefined') {
|
|
134
|
+
registerBrowserActions(this, this._core);
|
|
135
|
+
logger.wuInfo('[wu-ai] Browser actions registered (10 tools)');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this._eventBus.emit('ai:initialized', {}, { appName: 'wu-ai' });
|
|
139
|
+
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─── Provider Management ───────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Register an LLM provider.
|
|
147
|
+
*
|
|
148
|
+
* @param {string} name - Provider name ('openai', 'anthropic', 'ollama', or custom)
|
|
149
|
+
* @param {object} config - { endpoint, apiKey?, model?, adapter?, send?, stream? }
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* // OpenAI via proxy (recommended)
|
|
153
|
+
* wu.ai.provider('openai', { endpoint: '/api/ai/chat', model: 'gpt-4o' });
|
|
154
|
+
*
|
|
155
|
+
* // Anthropic direct (development only)
|
|
156
|
+
* wu.ai.provider('anthropic', {
|
|
157
|
+
* endpoint: 'https://api.anthropic.com/v1/messages',
|
|
158
|
+
* apiKey: 'sk-...',
|
|
159
|
+
* model: 'claude-sonnet-4-5-20250929',
|
|
160
|
+
* });
|
|
161
|
+
*
|
|
162
|
+
* // Local Ollama
|
|
163
|
+
* wu.ai.provider('ollama', { endpoint: 'http://localhost:11434/api/chat', model: 'llama3' });
|
|
164
|
+
*
|
|
165
|
+
* // Custom provider
|
|
166
|
+
* wu.ai.provider('my-llm', { send: async (messages, opts) => ({ content: '...' }) });
|
|
167
|
+
*/
|
|
168
|
+
provider(name, config) {
|
|
169
|
+
this._ensureInit();
|
|
170
|
+
this._modules.provider.register(name, config);
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ─── Paradigm 1: App → LLM (Conversation) ─────────────────────
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Send a message to the LLM and get a complete response.
|
|
178
|
+
*
|
|
179
|
+
* @param {string} message - User message
|
|
180
|
+
* @param {object} [options] - { namespace, systemPrompt, templateVars, temperature, maxTokens, signal }
|
|
181
|
+
* @returns {Promise<{ content: string, tool_results?: Array, usage?: object, namespace: string }>}
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* const response = await wu.ai.send('What items are in the cart?');
|
|
185
|
+
* console.log(response.content);
|
|
186
|
+
*
|
|
187
|
+
* // With namespace for separate conversation
|
|
188
|
+
* const response = await wu.ai.send('Analyze this chart', { namespace: 'analytics' });
|
|
189
|
+
*/
|
|
190
|
+
async send(message, options = {}) {
|
|
191
|
+
this._ensureInit();
|
|
192
|
+
return this._modules.conversation.send(message, options);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Send a message and stream the response.
|
|
197
|
+
*
|
|
198
|
+
* @param {string} message - User message
|
|
199
|
+
* @param {object} [options] - Same as send()
|
|
200
|
+
* @yields {{ type: 'text'|'tool_result'|'done'|'error', content?: string }}
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* for await (const chunk of wu.ai.stream('Tell me about this page')) {
|
|
204
|
+
* if (chunk.type === 'text') outputEl.textContent += chunk.content;
|
|
205
|
+
* if (chunk.type === 'done') console.log('Done!');
|
|
206
|
+
* }
|
|
207
|
+
*/
|
|
208
|
+
async *stream(message, options = {}) {
|
|
209
|
+
this._ensureInit();
|
|
210
|
+
yield* this._modules.conversation.stream(message, options);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Abort active request(s).
|
|
215
|
+
*
|
|
216
|
+
* @param {string} [namespace] - Specific namespace, or all if omitted
|
|
217
|
+
*/
|
|
218
|
+
abort(namespace) {
|
|
219
|
+
if (!this._initialized) return;
|
|
220
|
+
if (namespace) {
|
|
221
|
+
this._modules.conversation.abort(namespace);
|
|
222
|
+
} else {
|
|
223
|
+
this._modules.conversation.abortAll();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ─── Actions / Tools ───────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Register an action that the LLM can call.
|
|
231
|
+
*
|
|
232
|
+
* @param {string} name - Action name (used in tool_call)
|
|
233
|
+
* @param {object} config - { description, parameters, handler, confirm?, permissions?, dangerous? }
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* wu.ai.action('addToCart', {
|
|
237
|
+
* description: 'Add an item to the shopping cart',
|
|
238
|
+
* parameters: {
|
|
239
|
+
* productId: { type: 'string', required: true },
|
|
240
|
+
* quantity: { type: 'number' },
|
|
241
|
+
* },
|
|
242
|
+
* handler: async (params, api) => {
|
|
243
|
+
* api.setState('cart.items', [...api.getState('cart.items'), params]);
|
|
244
|
+
* api.emit('cart:updated', params);
|
|
245
|
+
* return { added: params.productId };
|
|
246
|
+
* },
|
|
247
|
+
* confirm: true, // require user confirmation
|
|
248
|
+
* });
|
|
249
|
+
*/
|
|
250
|
+
action(name, config) {
|
|
251
|
+
this._ensureInit();
|
|
252
|
+
this._modules.actions.register(name, config);
|
|
253
|
+
return this;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Execute an action directly (used by external agents via CDP/WebMCP).
|
|
258
|
+
*
|
|
259
|
+
* @param {string} name - Action name
|
|
260
|
+
* @param {object} params - Parameters
|
|
261
|
+
* @returns {Promise<{ success: boolean, result?: any, reason?: string }>}
|
|
262
|
+
*/
|
|
263
|
+
async execute(name, params) {
|
|
264
|
+
this._ensureInit();
|
|
265
|
+
const traceId = this._modules.permissions.loopProtection.createTraceId();
|
|
266
|
+
return this._modules.actions.execute(name, params, { traceId, depth: 0 });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ─── Triggers (Reactive AI) ────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Register a trigger that automatically sends messages to the LLM
|
|
273
|
+
* when specific events occur.
|
|
274
|
+
*
|
|
275
|
+
* @param {string} name - Trigger name
|
|
276
|
+
* @param {object} config - { pattern, prompt, condition?, debounce?, priority?, onResult? }
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* wu.ai.trigger('cartAnalysis', {
|
|
280
|
+
* pattern: 'cart:updated',
|
|
281
|
+
* prompt: 'The cart was updated: {{data}}. Suggest complementary products.',
|
|
282
|
+
* debounce: 3000,
|
|
283
|
+
* priority: 'low',
|
|
284
|
+
* onResult: (result) => {
|
|
285
|
+
* wu.emit('ai:suggestions', { suggestions: result.content });
|
|
286
|
+
* },
|
|
287
|
+
* });
|
|
288
|
+
*/
|
|
289
|
+
trigger(name, config) {
|
|
290
|
+
this._ensureInit();
|
|
291
|
+
this._modules.triggers.register(name, config);
|
|
292
|
+
return this;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Fire a trigger manually.
|
|
297
|
+
*/
|
|
298
|
+
async fireTrigger(name, eventData) {
|
|
299
|
+
this._ensureInit();
|
|
300
|
+
return this._modules.triggers.fire(name, eventData);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ─── Context ───────────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Context configuration sub-API.
|
|
307
|
+
* Access via wu.ai.context
|
|
308
|
+
*/
|
|
309
|
+
get context() {
|
|
310
|
+
this._ensureInit();
|
|
311
|
+
return {
|
|
312
|
+
configure: (config) => this._modules.context.configure(config),
|
|
313
|
+
register: (name, config) => this._modules.context.register(name, config),
|
|
314
|
+
collect: () => this._modules.context.collect(),
|
|
315
|
+
getSnapshot: () => this._modules.context.getSnapshot(),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ─── Conversation Management ───────────────────────────────────
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Conversation sub-API for direct history management.
|
|
323
|
+
*/
|
|
324
|
+
get conversation() {
|
|
325
|
+
this._ensureInit();
|
|
326
|
+
return {
|
|
327
|
+
getHistory: (ns) => this._modules.conversation.getHistory(ns),
|
|
328
|
+
clear: (ns) => this._modules.conversation.clear(ns),
|
|
329
|
+
clearAll: () => this._modules.conversation.clearAll(),
|
|
330
|
+
inject: (role, content, opts) => this._modules.conversation.inject(role, content, opts),
|
|
331
|
+
getNamespaces: () => this._modules.conversation.getNamespaces(),
|
|
332
|
+
deleteNamespace: (ns) => this._modules.conversation.deleteNamespace(ns),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ─── Permissions ───────────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Permissions sub-API.
|
|
340
|
+
*/
|
|
341
|
+
get permissions() {
|
|
342
|
+
this._ensureInit();
|
|
343
|
+
return {
|
|
344
|
+
configure: (config) => this._modules.permissions.configure(config),
|
|
345
|
+
check: (perm) => this._modules.permissions.check(perm),
|
|
346
|
+
getPermissions: () => this._modules.permissions.getPermissions(),
|
|
347
|
+
setAllowedDomains: (domains) => this._modules.permissions.setAllowedDomains(domains),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ─── Paradigm 2: LLM → App (External Agent Access) ────────────
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Get all registered tools (for external agents).
|
|
355
|
+
* An agent connected via CDP can call: window.wu.ai.tools()
|
|
356
|
+
*
|
|
357
|
+
* @returns {Array<{ name, description, parameters }>}
|
|
358
|
+
*/
|
|
359
|
+
tools() {
|
|
360
|
+
this._ensureInit();
|
|
361
|
+
return this._modules.actions.getToolSchemas();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Expose tools via WebMCP (Chrome 146+ / W3C proposal).
|
|
366
|
+
* Registers all actions with navigator.modelContext.registerTool()
|
|
367
|
+
*
|
|
368
|
+
* @returns {boolean} Whether WebMCP is available
|
|
369
|
+
*/
|
|
370
|
+
expose() {
|
|
371
|
+
this._ensureInit();
|
|
372
|
+
|
|
373
|
+
if (typeof navigator === 'undefined' || !navigator.modelContext) {
|
|
374
|
+
logger.wuDebug('[wu-ai] WebMCP not available (navigator.modelContext missing)');
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const tools = this._modules.actions.getToolSchemas();
|
|
379
|
+
const actionNames = this._modules.actions.getNames();
|
|
380
|
+
|
|
381
|
+
for (let i = 0; i < tools.length; i++) {
|
|
382
|
+
const tool = tools[i];
|
|
383
|
+
const actionName = actionNames[i];
|
|
384
|
+
|
|
385
|
+
try {
|
|
386
|
+
navigator.modelContext.registerTool({
|
|
387
|
+
name: tool.name,
|
|
388
|
+
description: tool.description,
|
|
389
|
+
inputSchema: tool.parameters,
|
|
390
|
+
handler: async (params) => {
|
|
391
|
+
const result = await this.execute(actionName, params);
|
|
392
|
+
return result.success ? result.result : { error: result.reason };
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
} catch (err) {
|
|
396
|
+
logger.wuDebug(`[wu-ai] WebMCP register failed for '${tool.name}': ${err.message}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
logger.wuInfo(`[wu-ai] Exposed ${tools.length} tools via WebMCP`);
|
|
401
|
+
|
|
402
|
+
this._eventBus.emit('ai:webmcp:exposed', {
|
|
403
|
+
toolCount: tools.length,
|
|
404
|
+
tools: tools.map(t => t.name),
|
|
405
|
+
}, { appName: 'wu-ai' });
|
|
406
|
+
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Confirm a pending tool call (for UI integration).
|
|
412
|
+
*/
|
|
413
|
+
confirmTool(callId) {
|
|
414
|
+
if (!this._initialized) return;
|
|
415
|
+
this._modules.actions.confirmTool(callId);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Reject a pending tool call.
|
|
420
|
+
*/
|
|
421
|
+
rejectTool(callId) {
|
|
422
|
+
if (!this._initialized) return;
|
|
423
|
+
this._modules.actions.rejectTool(callId);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ─── Stats & Debug ─────────────────────────────────────────────
|
|
427
|
+
|
|
428
|
+
getStats() {
|
|
429
|
+
if (!this._initialized) return { initialized: false };
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
initialized: true,
|
|
433
|
+
provider: this._modules.provider.getStats(),
|
|
434
|
+
permissions: this._modules.permissions.getStats(),
|
|
435
|
+
context: this._modules.context.getStats(),
|
|
436
|
+
actions: this._modules.actions.getStats(),
|
|
437
|
+
conversation: this._modules.conversation.getStats(),
|
|
438
|
+
triggers: this._modules.triggers.getStats(),
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Destroy the AI system and clean up all resources.
|
|
444
|
+
*/
|
|
445
|
+
destroy() {
|
|
446
|
+
if (!this._initialized) return;
|
|
447
|
+
|
|
448
|
+
this._modules.conversation.abortAll();
|
|
449
|
+
this._modules.triggers.destroy();
|
|
450
|
+
this._modules = {};
|
|
451
|
+
this._initialized = false;
|
|
452
|
+
|
|
453
|
+
logger.wuInfo('[wu-ai] Destroyed');
|
|
454
|
+
this._eventBus.emit('ai:destroyed', {}, { appName: 'wu-ai' });
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ─── Private ───────────────────────────────────────────────────
|
|
458
|
+
|
|
459
|
+
_ensureInit() {
|
|
460
|
+
if (!this._initialized) {
|
|
461
|
+
this.init();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
_reconfigure(config) {
|
|
466
|
+
if (config.permissions) this._modules.permissions.configure(config.permissions);
|
|
467
|
+
if (config.rateLimit) this._modules.permissions.rateLimiter.configure(config.rateLimit);
|
|
468
|
+
if (config.circuitBreaker) this._modules.permissions.circuitBreaker.configure(config.circuitBreaker);
|
|
469
|
+
if (config.loopProtection) this._modules.permissions.loopProtection.configure(config.loopProtection);
|
|
470
|
+
if (config.context) this._modules.context.configure(config.context);
|
|
471
|
+
if (config.conversation) this._modules.conversation.configure(config.conversation);
|
|
472
|
+
if (config.triggers) this._modules.triggers.configure(config.triggers);
|
|
473
|
+
}
|
|
474
|
+
}
|
package/src/core/wu-app.js
CHANGED
|
@@ -17,6 +17,7 @@ export class WuApp {
|
|
|
17
17
|
this.name = name
|
|
18
18
|
this.url = config.url
|
|
19
19
|
this.container = config.container
|
|
20
|
+
this.keepAlive = config.keepAlive || false
|
|
20
21
|
this._wu = wu
|
|
21
22
|
this._mounted = false
|
|
22
23
|
this._autoInit = config.autoInit !== false // Default true
|
|
@@ -33,10 +34,10 @@ export class WuApp {
|
|
|
33
34
|
*/
|
|
34
35
|
_registerApp() {
|
|
35
36
|
if (!this._wu.apps.has(this.name)) {
|
|
36
|
-
// Usar el método interno de wu-framework para registrar
|
|
37
37
|
this._wu.apps.set(this.name, {
|
|
38
38
|
name: this.name,
|
|
39
39
|
url: this.url,
|
|
40
|
+
keepAlive: this.keepAlive,
|
|
40
41
|
status: 'registered'
|
|
41
42
|
})
|
|
42
43
|
console.log(`📦 App registered: ${this.name} at ${this.url}`)
|
|
@@ -56,7 +57,7 @@ export class WuApp {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
// Asegurar que wu-framework está inicializado
|
|
59
|
-
if (!this._wu.
|
|
60
|
+
if (!this._wu.isInitialized) {
|
|
60
61
|
await this._wu.init({
|
|
61
62
|
apps: [{ name: this.name, url: this.url }]
|
|
62
63
|
})
|
|
@@ -70,18 +71,51 @@ export class WuApp {
|
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
/**
|
|
73
|
-
* Desmontar la app
|
|
74
|
-
*
|
|
74
|
+
* Desmontar la app.
|
|
75
|
+
* If keepAlive is configured, hides instead of destroying.
|
|
76
|
+
*
|
|
77
|
+
* @param {Object} [options] - { keepAlive, force }
|
|
78
|
+
* @returns {Promise<WuApp>}
|
|
75
79
|
*/
|
|
76
|
-
async unmount() {
|
|
80
|
+
async unmount(options = {}) {
|
|
81
|
+
if (!this._mounted && !this._wu.isHidden(this.name)) {
|
|
82
|
+
console.warn(`⚠️ App ${this.name} is not mounted`)
|
|
83
|
+
return this
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await this._wu.unmount(this.name, options)
|
|
87
|
+
this._mounted = !this._wu.isHidden(this.name)
|
|
88
|
+
|
|
89
|
+
return this
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Hide the app (keep-alive). Preserves all state for instant re-show.
|
|
94
|
+
* @returns {Promise<WuApp>}
|
|
95
|
+
*/
|
|
96
|
+
async hide() {
|
|
77
97
|
if (!this._mounted) {
|
|
78
98
|
console.warn(`⚠️ App ${this.name} is not mounted`)
|
|
79
99
|
return this
|
|
80
100
|
}
|
|
81
101
|
|
|
82
|
-
await this._wu.
|
|
102
|
+
await this._wu.hide(this.name)
|
|
83
103
|
this._mounted = false
|
|
104
|
+
return this
|
|
105
|
+
}
|
|
84
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Show a hidden (keep-alive) app instantly.
|
|
109
|
+
* @returns {Promise<WuApp>}
|
|
110
|
+
*/
|
|
111
|
+
async show() {
|
|
112
|
+
if (!this._wu.isHidden(this.name)) {
|
|
113
|
+
console.warn(`⚠️ App ${this.name} is not in keep-alive state`)
|
|
114
|
+
return this
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await this._wu.show(this.name)
|
|
118
|
+
this._mounted = true
|
|
85
119
|
return this
|
|
86
120
|
}
|
|
87
121
|
|
|
@@ -91,7 +125,7 @@ export class WuApp {
|
|
|
91
125
|
* @returns {Promise<void>}
|
|
92
126
|
*/
|
|
93
127
|
async remount(container) {
|
|
94
|
-
await this.unmount()
|
|
128
|
+
await this.unmount({ force: true })
|
|
95
129
|
await this.mount(container)
|
|
96
130
|
return this
|
|
97
131
|
}
|
|
@@ -104,6 +138,14 @@ export class WuApp {
|
|
|
104
138
|
return this._mounted && this._wu.mounted?.has(this.name)
|
|
105
139
|
}
|
|
106
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Check if the app is in keep-alive (hidden) state
|
|
143
|
+
* @returns {boolean}
|
|
144
|
+
*/
|
|
145
|
+
get isHidden() {
|
|
146
|
+
return this._wu.isHidden(this.name)
|
|
147
|
+
}
|
|
148
|
+
|
|
107
149
|
/**
|
|
108
150
|
* Obtener información de la app
|
|
109
151
|
* @returns {Object}
|
|
@@ -184,7 +226,7 @@ export class WuApp {
|
|
|
184
226
|
* Destruir la app completamente
|
|
185
227
|
*/
|
|
186
228
|
async destroy() {
|
|
187
|
-
await this.unmount()
|
|
229
|
+
await this.unmount({ force: true })
|
|
188
230
|
this._wu.apps.delete(this.name)
|
|
189
231
|
this._mounted = false
|
|
190
232
|
console.log(`🗑️ App destroyed: ${this.name}`)
|
package/src/core/wu-cache.js
CHANGED
|
@@ -346,8 +346,8 @@ export class WuCache {
|
|
|
346
346
|
* @param {Object} entry - Entrada
|
|
347
347
|
*/
|
|
348
348
|
saveToStorage(key, entry) {
|
|
349
|
+
const storage = this.getStorage();
|
|
349
350
|
try {
|
|
350
|
-
const storage = this.getStorage();
|
|
351
351
|
storage.setItem(`wu_cache_${key}`, JSON.stringify(entry));
|
|
352
352
|
} catch (error) {
|
|
353
353
|
// Storage lleno, limpiar entradas antiguas
|