Testing and conformance
@terminay/extension-api ships a manifest conformance command and in-memory
harnesses under @terminay/extension-api/testing. They apply the host's rules
without Terminay Server.
Manifest conformance
npx terminay-extension-conformance package.json
# Valid Terminay extension: com.example.agent
The command reads package.json (or the path you pass), validates the
terminay manifest, and checks that the package has a name, a version, and an
exports entry for the entrypoint. Each issue is printed as
path [code] message and the command exits non-zero.
Agent extensions
createAgentExtensionHarness(extension, { manifest }) activates the extension
and drives its session sources and MCP install targets.
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
import {
createAgentExtensionHarness,
fixtureMcpServerCommand,
} from '@terminay/extension-api/testing';
import extension from './extension.js';
const manifest = JSON.parse(await readFile('package.json', 'utf8')).terminay;
test('source reports live sessions', async (t) => {
const harness = await createAgentExtensionHarness(extension, { manifest });
t.after(() => harness.dispose());
await harness.start();
await harness.waitFor((h) => h.sessions().length === 1);
harness.setEnabledHarnesses([]);
await harness.waitFor(
(h) =>
h.publications().at(-1)?.kind === 'reset' &&
h.publications().at(-1).sessions.length === 0,
);
harness.assertConformant();
});
test('MCP target installs and removes the terminay entry', async (t) => {
const harness = await createAgentExtensionHarness(extension, { manifest });
t.after(() => harness.dispose());
const target = 'com.example.agent/example-agent';
assert.equal((await harness.mcpStatus(target)).state, 'not-installed');
assert.deepEqual(await harness.mcpInstall(target), { ok: true, installed: true });
assert.equal(
(await harness.mcpStatus(target, { ...fixtureMcpServerCommand, args: ['other.js'] })).state,
'changed',
);
assert.deepEqual(await harness.mcpUninstall(target), { ok: true, installed: false });
});| Method | Use |
|---|---|
registeredSourceIds(), registeredTargetIds() | What the extension registered, in order. |
start(sourceId?, options?) | Start a source (the first when omitted). options.enabledHarnesses defaults to every declared harness. |
setEnabledHarnesses(ids, sourceId?) | Deliver a harness switch change, as the host does. |
stop(sourceId?) | Abort the source's signal. |
sessions(), publications(), diagnostics() | The live sessions the host would hold, every publisher call in order, and recorded diagnostics. |
violations(), assertConformant() | Contract breaches the host would refuse. assertConformant() throws when there are any. |
waitFor(predicate, timeoutMs?) | Resolve once the predicate holds, re-checked after every publication. |
mcpStatus(), mcpInstall(), mcpUninstall() | Call an install target with fixtureMcpServerCommand or a command you pass. |
release(reason), dispose() | End the activation as disabled, updated, shutdown, or extension-host-failure. |
The harness checks manifest and registration agreement, snapshot bounds, declared and enabled harnesses, reset, upsert, and removal validity, cancellation, and privacy exclusions.
Language server extensions
createLanguageServerExtensionHarness applies the registration rules and returns
the launch. openLanguageServerSession starts that launch and speaks LSP to it
over stdio, so a package can prove its launch against a real server.
import {
createLanguageServerExtensionHarness,
openLanguageServerSession,
} from '@terminay/extension-api/testing';
import extension from './dist/extension.js';
const harness = await createLanguageServerExtensionHarness(extension, { manifest });
const launch = await harness.launch({
languageServerId: 'fixture-language',
projectRoot,
});
const session = await openLanguageServerSession(launch, { projectRoot });
await session.initialize();
session.didOpen({ path, languageId: 'fixturelang', text });
const diagnostics = await session.waitForDiagnostics(path);
const hover = await session.hover(path, { line: 0, character: 0 });
await session.dispose();
await harness.dispose();
The session offers initialize(), didOpen(),
completion(), hover(), definition(),
waitForDiagnostics(), raw request() and notify(), and
dispose(). Document paths are absolute.
Worktree insight sources
There is no dedicated harness. Check what you publish with
validateWorktreeProperties() and validateWorktreeSignInRequest()
from the main entry point, and call your runtime's start() with test doubles for
the publisher, credentials, and signal.
Fixtures
The package also exports validManifestFixture,
validAgentSessionSourceContributionFixture,
validMcpInstallTargetContributionFixture, and
validLanguageServerContributionFixture as known-valid manifest data.