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

Shell
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

JSON
"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

TypeScript
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

TypeScript
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 signal to 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().