diff --git a/harness/package.json b/harness/package.json index e46a2123..25665ee5 100644 --- a/harness/package.json +++ b/harness/package.json @@ -33,8 +33,10 @@ "dev:provider-openai": "tsx src/provider-openai/main.ts", "dev:provider-kimi": "tsx src/provider-kimi/main.ts", "dev:provider-lmstudio": "tsx src/provider-lmstudio/main.ts", + "dev:provider-llamacpp": "tsx src/provider-llamacpp/main.ts", "dev:context-compaction": "tsx src/context-compaction/main.ts", - "dev:provider-config": "tsx src/provider-config/main.ts" + "dev:provider-config": "tsx src/provider-config/main.ts", + "dev:web": "tsx src/web/main.ts" }, "bin": { "harness": "./dist/index.js", @@ -50,8 +52,10 @@ "iii-provider-openai": "./dist/provider-openai/main.js", "iii-provider-kimi": "./dist/provider-kimi/main.js", "iii-provider-lmstudio": "./dist/provider-lmstudio/main.js", + "iii-provider-llamacpp": "./dist/provider-llamacpp/main.js", "iii-context-compaction": "./dist/context-compaction/main.js", - "iii-provider-config": "./dist/provider-config/main.js" + "iii-provider-config": "./dist/provider-config/main.js", + "iii-web": "./dist/web/main.js" }, "dependencies": { "@opentelemetry/api": "^1.9.0", diff --git a/harness/src/index.ts b/harness/src/index.ts index b14cd79e..e54422d7 100644 --- a/harness/src/index.ts +++ b/harness/src/index.ts @@ -33,6 +33,7 @@ import { } from './runtime/worker.js'; import { register as registerSession } from './session/register.js'; import { register as registerTurnOrchestrator } from './turn-orchestrator/register.js'; +import { register as registerWeb } from './web/register.js'; const WORKERS: readonly WorkerDefinition[] = [ { @@ -125,6 +126,11 @@ const WORKERS: readonly WorkerDefinition[] = [ 'Out-of-band session-history compactor. Subscribes to agent::events::TurnEnd and writes a session-tree Compaction entry when the running token count crosses the configured threshold.', register: (iii) => registerContextCompaction(iii), }, + { + name: 'web', + description: 'Outbound HTTP client on the iii bus (web::fetch).', + register: (iii, ctx) => registerWeb(iii, ctx), + }, ]; async function main(): Promise { diff --git a/harness/tests/composite-manifest.test.ts b/harness/tests/composite-manifest.test.ts new file mode 100644 index 00000000..f6c9f260 --- /dev/null +++ b/harness/tests/composite-manifest.test.ts @@ -0,0 +1,52 @@ +import { execFileSync } from 'node:child_process'; +import { existsSync, readFileSync, readdirSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; + +const here = dirname(fileURLToPath(import.meta.url)); +const repoRoot = join(here, '..'); +const srcDir = join(repoRoot, 'src'); +const indexEntry = join(srcDir, 'index.ts'); +const tsxBin = join(repoRoot, 'node_modules', '.bin', 'tsx'); + +const pkg = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')) as { + scripts: Record; + bin: Record; +}; + +/** + * Every folder under `src/` that ships a `main.ts` is a worker meant to run + * standalone. The composite entry-point (`src/index.ts`, what `dev:all` and + * `start:all` run) must register every one of them — otherwise a worker + * silently never starts in the all-in-one process. Regression guard for #202, + * where the `web` worker was added but never wired into `index.ts`. + */ +function runnableWorkerFolders(): string[] { + return readdirSync(srcDir, { withFileTypes: true }) + .filter((e) => e.isDirectory() && existsSync(join(srcDir, e.name, 'main.ts'))) + .map((e) => e.name) + .sort(); +} + +function compositeManifestNames(): string[] { + const out = execFileSync(tsxBin, [indexEntry, '--manifest'], { encoding: 'utf8' }); + const manifest = JSON.parse(out) as Array<{ name: string }>; + return manifest.map((w) => w.name).sort(); +} + +describe('composite entry-point worker registration', () => { + it('registers every runnable worker folder in the WORKERS array', () => { + expect(compositeManifestNames()).toEqual(runnableWorkerFolders()); + }); + + it('exposes a dev: script for every runnable worker', () => { + const missing = runnableWorkerFolders().filter((name) => !pkg.scripts[`dev:${name}`]); + expect(missing).toEqual([]); + }); + + it('exposes an iii- bin for every runnable worker', () => { + const missing = runnableWorkerFolders().filter((name) => !pkg.bin[`iii-${name}`]); + expect(missing).toEqual([]); + }); +});