Hooks, plugins, timeline e IA
Extensões e subsistemas adiados do runtime com limites precisos de execução, registro e autoridade.
Middleware de lifecycle
Os hooks recebem (context, next). Chamar next() permite continuar; omiti-lo cancela a cadeia. Uma prioridade maior é executada antes. O registro retorna uma função de cancelamento da inscrição que você deve guardar se o middleware tiver um lifecycle mais curto que o shell. A declaração TypeScript de use indica void; o runtime fornece esse cleanup.
const remove = wu.hooks.use('beforeMount', async (context, next) => {
console.log('Montando', context.appName);
await next();
}, { name: 'mount-observer', priority: 10 });
// Después de observar el escenario:
remove();
As fases são beforeInit, afterInit, beforeLoad, afterLoad, beforeMount, afterMount, beforeUpdate, afterUpdate, beforeUnmount, afterUnmount, beforeDestroy e afterDestroy. O contexto depende da fase; nem todos os hooks recebem lifecycle, sandbox ou props.
Um middleware que lança um erro antes de next cancela por padrão; failOpen: true permite continuar diante dessa falha. Os helpers exportados createSimpleHook, createConditionalHook, createGuardHook, createTransformHook e createTimedHook constroem middleware. O helper timed registra seu erro e chama next: não é adequado como controle que deva fechar o acesso diante de um timeout.
Plugin com grants explícitos
let stop = () => {};
await wu.pluginSystem.use({
name: 'catalog-observer',
permissions: ['events'],
install(api) {
stop = api.on('catalog:selected', (event) => {
console.info('Selección observada', event.data);
});
},
uninstall() { stop(); },
}, { grants: ['events'] });
// Al retirarlo:
await wu.pluginSystem.uninstall('catalog-observer');
use aceita um objeto ou uma factory, aguarda a instalação e valida as permissões. ready() permite aguardar instalações pendentes; init já espera essa barreira. O timeout padrão dos hooks de plugins é 5000 ms.
As factories recebem opções, não o core. Os grants do host são copiados antes de chamar a factory para que ela não amplie suas opções e se autorize sozinha. Ainda assim, um plugin é código executado no host: a API filtrada não isola closures nem o acesso global que esse código já tem.
Timeline e tempo de carregamento
await wu.timelineReady();
wu.timeline.record({ snapshotEvery: 64, maxEntries: 5000 });
wu.store.set('demo.counter', 1);
wu.store.set('demo.counter', 2);
await wu.timeline.stepBack();
console.log(wu.store.get('demo.counter'));
await wu.timeline.live();
const recording = await wu.timeline.export();
wu.timeline.stop();
timelineReady() carrega o chunk antes de começar. Se você chamar record() pela primeira vez e gravar imediatamente, a gravação pode ocorrer antes de o registro adiado estar preparado. A facade mantém a intenção de record/stop enquanto o módulo chega; para capturar a próxima gravação sem ambiguidade, use ready primeiro.
O journal registra gravações no store, eventos e lifecycle/props que passam pelo WU. Tem um limite padrão de 5000 entradas e snapshots a cada 64. seek, stepBack, stepForward, live, export, import, entries e status permitem inspecionar esse registro.
Retroceder rematerializa projeções; não reverte timers internos das apps, solicitações HTTP nem infraestrutura. Quando você está no passado, novas gravações não são adicionadas ao journal, e voltar a live as descarta. Não existe branching do histórico nem auditoria durável implícita.
IA opcional
O import principal instala um proxy lazy. Métodos encadeáveis como provider, action, trigger, capability e workflow podem enfileirar configurações enquanto o chunk chega; operações de valor exigem aguardar o módulo real. aiReady() é a forma explícita de obtê-lo.
const ai = await wu.aiReady();
ai.init({ permissions: { executeActions: true } });
ai.action('sum_local', {
description: 'Suma dos valores de demostración sin efectos externos',
parameters: {
type: 'object',
properties: {
a: { type: 'number' },
b: { type: 'number' },
},
required: ['a', 'b'],
},
handler: async ({ a, b }) => ({ total: a + b }),
});
const result = await ai.execute('sum_local', { a: 2, b: 3 });
console.log(result);
Registrar e executar essa action local não exige enviar dados a um modelo. Para conversação, existem providers, send, stream, json, agent, triggers e workflows, com suas próprias permissões e configurações. Não use uma chave privada de serviço em um bundle público.
Actions validam parâmetros, verificam executeActions, consultam permissões adicionais/condicionais e podem pedir confirmação. dangerous: true exige confirmação no código atual. Os resultados de execução usam um envelope { success, result? , reason? }: uma rejeição de permissões pode ser um resultado negativo, não uma exceção.
Os padrões permitem readStore e emitEvents; writeStore, readDOM, modifyDOM, readDiagnostics, navigate, executeActions, network e allowDirectKey começam desativados. São flags do módulo IA, independentes dos grants da app e da autorização do servidor.
MCP e automação
wu.mcp.connect(url, options), disconnect() e isConnected() expõem a ponte cliente adiada. O pacote também inclui o binário wu-mcp-server e seu subpath. A ponte pode inspecionar e alterar o estado e a interface conforme os controles do módulo; não acrescenta, por si só, dry-run vinculante, idempotência, autorização de domínio, rollback ou um registro permanente.
Para um agente que opera recursos reais, use um serviço que receba comandos validados e retorne receipts. O store pode projetar seu progresso; a action não deve se tornar uma closure que exerce privilégios sem revalidação.
Referência de implementação
Hooks: src/core/wu-hooks.js; plugins: src/core/wu-plugin.js; facade da timeline: src/index.js; journal: src/core/wu-timeline.js; IA: src/ai/wu-ai.js, src/ai/wu-ai-actions.js, src/ai/wu-ai-actions.js; padrões: src/ai/wu-ai-permissions.js; MCP: src/index.js. Testes: tests/unit/plugin-security.test.js, tests/unit/timeline-facade.test.js, tests/unit/lazy-ai-proxy.test.js, tests/unit/ai-actions.test.js, tests/unit/mcp-bridge-security.test.js.