JavaScript SDK
JavaScript Workers share npm package @vibex/plugin-sdk, protocol 1.1, and the same handler rules as TypeScript. Template node-worker writes runtime/main.mjs and "type": "module" in package.json. Engine fields, digest, and generation match the TypeScript package.
Type imports may be omitted. Runtime is ESM. Node >=20.
Worker
import { definePluginWorker } from '@vibex/plugin-sdk/worker';
export default definePluginWorker((registrar, environment) => {
registrar.handle('hello', async (input, env) => {
env.log.info('hello', { input });
return { message: 'Hello from VibeX', input };
});
});definePluginWorker, registrar.handle, onDispose, environment.host.call, log, and signal follow TypeScript SDK. Error codes match: handler_duplicate, handler_not_found, worker_disposed, sdk_incompatible.
stdio entry
The Host runs node --max-old-space-size=128 dist/worker.mjs. Recommended split:
// runtime/worker.mjs — handler definition
export default definePluginWorker((registrar) => {
registrar.handle('hello', async (input) => ({ message: 'Hello from VibeX', input }));
});// runtime/main.mjs — Host entry
import { runStdioPluginWorker } from '@vibex/plugin-sdk/stdio';
import definition from './worker.mjs';
await runStdioPluginWorker(definition);vibex-plugin build emits runtime/main.mjs to dist/worker.mjs. Manifest:
"entrypoints": {
"worker": {
"path": "dist/worker.mjs",
"runtime": "node",
"protocol": "1.1"
}
}init --template node-worker writes the definition in runtime/main.mjs. Add runStdioPluginWorker there, or split into worker.mjs and main.mjs. Official Office uses the split.
Tests import runtime/worker.mjs. Importing dist/worker.mjs starts the stdio loop.
App
JavaScript Apps use the same definePluginApp. full and file-tab templates emit runtime/app.mjs, app.html, and app.css. bridge.invoke reaches only Worker handlers registered in this generation. Call bridge.ready() after the first paint is mounted.
Without a TypeScript compiler, keep .mjs sources. For types, use ts-worker or add .d.ts in the same package; runtime remains Node ESM.
The file-tab template App mounts on plugin.detail.panel. An editable file tab uses slot: artifact.editor. Declaration steps: Contribution model.
Testing
import test from 'node:test';
import assert from 'node:assert/strict';
import definition from '../runtime/worker.mjs';
import { createWorkerHarness } from '@vibex/plugin-sdk/testing';
test('registers hello', async () => {
const worker = await createWorkerHarness(definition);
assert.deepEqual(worker.handlers, ['hello']);
await assert.rejects(() => worker.invoke('undeclared', null), /not registered/);
await worker.dispose();
});init --template node-worker generates a test that imports ../dist/worker.mjs. After splitting sources, change the import to ../runtime/worker.mjs. vibex-plugin test runs build first. The host.service template also uses a JavaScript Worker plus intervalSeconds.
Using TypeScript in the same package
JavaScript templates fit a pure Worker with ESM sources. ts-worker writes the definition in runtime/main.ts. One product package may contain a .mjs Worker and a TypeScript App when build output paths match the manifest.

