Session source example
The reference package for an agent extension. It reports the sessions of a fictional Example Agent and registers the Terminay MCP server with it, using only the public SDK.
Package shape
examples/session-source/
├── package.json # manifest: one session source, one MCP install target
├── extension.js # activate(): registers both
├── example-agent.js # the two runtimes
└── test.mjs # tests through @terminay/extension-api/testing
Example Agent keeps one <pid>.json file per live process under
<home>/sessions/ and deletes it when the process exits. The home
directory comes from EXAMPLE_AGENT_HOME, which the manifest declares so the
extension process receives it.
Manifest
"terminay": {
"manifestVersion": 1,
"id": "com.example.agent",
"displayName": "Example Agent",
"description": "Shows Example Agent sessions in Terminay and registers the Terminay MCP server with it.",
"api": "^3.0.0",
"engines": { "terminay": ">=1.0.0", "node": ">=24" },
"entrypoint": "extension.js",
"platforms": ["darwin", "linux"],
"permissions": ["agent-observation", "mcp-registration"],
"contributes": {
"agentSessionSources": [{
"id": "com.example.agent/sessions",
"displayName": "Example Agent",
"harnesses": [{ "id": "example-agent", "displayName": "Example Agent" }],
"environmentVariables": ["EXAMPLE_AGENT_HOME"]
}],
"mcpInstallTargets": [{
"id": "com.example.agent/example-agent",
"displayName": "Example Agent"
}]
}
}Activation
import { defineExtension } from '@terminay/extension-api';
import {
exampleMcpInstallTarget,
exampleSessionSource,
} from './example-agent.js';
export default defineExtension({
activate(context) {
context.subscriptions.add(
context.agents.registerSessionSource(
'com.example.agent/sessions',
exampleSessionSource,
),
);
context.subscriptions.add(
context.mcp.registerInstallTarget(
'com.example.agent/example-agent',
exampleMcpInstallTarget,
),
);
},
});
Both registrations go into context.subscriptions, so disable, update, shutdown,
and host failure dispose them.
Session source
export const exampleSessionSource = defineSessionSource({
async start({ enabledHarnesses, publisher, signal, onEnabledHarnessesChanged }) {
const directory = join(exampleHome(), 'sessions');
await mkdir(directory, { recursive: true });
let enabled = enabledHarnesses.includes('example-agent');
const live = new Map(); // file name -> session id
async function resetAll() {
live.clear();
if (!enabled) return publisher.reset([]);
const sessions = [];
for (const name of await readdir(directory)) {
const session = await readSession(directory, name);
if (!session) continue;
live.set(name, session.id);
sessions.push(session);
}
if (!signal.aborted) publisher.reset(sessions);
}
// Watch, never poll. Each event re-reads only the file it names.
const watcher = watch(directory, { signal }, async (_event, name) => {
if (!enabled || typeof name !== 'string') return;
const session = await readSession(directory, name);
if (signal.aborted) return;
if (session) {
live.set(name, session.id);
publisher.upsert(session);
} else if (live.has(name)) {
publisher.remove(live.get(name));
live.delete(name);
}
});
watcher.on('error', () => {
publisher.diagnostic({
code: 'watch-failed',
message: 'Example Agent sessions can no longer be watched',
});
});
const subscription = onEnabledHarnessesChanged((next) => {
enabled = next.includes('example-agent');
void resetAll();
});
signal.addEventListener('abort', () => subscription.dispose(), { once: true });
await resetAll();
},
});- It sends a reset after starting and after every harness switch change.
- It watches the directory and publishes one upsert or removal per changed file.
- It passes
signalto the watch, so aborting releases it. - It reports a lost watch as a typed diagnostic with no path in the message.
MCP install target and tests
The install target writes the supplied command to the agent's mcp.json through
a temporary file and a rename. Its code is shown in the
MCP install targets reference.
The tests drive both runtimes through
createAgentExtensionHarness
and end with assertConformant().