Files
Jesse Vincent 30916f8cad feat(harness): add Cursor (#113) and opencode (#117) support
Adds Cursor (live transcripts + opt-in state.vscdb backfill) and opencode (DB->JSONL export) as harnesses, following the existing Codex pattern. opencode summarization is fixed to route via transcript text instead of a doomed claude --resume, and hasConversationContent recognizes opencode_message lines. Harness union is 'claude'|'codex'|'cursor'|'opencode'. Also pins conversation timestamps to en-US/UTC in every renderer (#130) and restores valid YAML in the search agent frontmatter (#153).

Co-authored-by: vicnaum <vicnaum@users.noreply.github.com>

Co-authored-by: slandau3 <slandau3@users.noreply.github.com>

Co-authored-by: arimu1 <arimu1@users.noreply.github.com>

Claude-Session: https://claude.ai/code/session_0112vdwZphiWzfCYfaXMes4C
2026-09-08 18:47:47 +00:00

69 lines
2.1 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { existsSync, readFileSync } from 'fs';
import { join } from 'path';
import plugin, { server } from '../src/opencode-plugin.js';
const REPO_ROOT = join(import.meta.dirname, '..');
function readJson(relPath: string): any {
return JSON.parse(readFileSync(join(REPO_ROOT, relPath), 'utf-8'));
}
function createShellRecorder() {
const commands: string[] = [];
const shell = ((strings: TemplateStringsArray, ...expressions: unknown[]) => {
let command = '';
strings.forEach((part, index) => {
command += part;
if (index < expressions.length) command += String(expressions[index]);
});
commands.push(command.trim().replace(/\s+/g, ' '));
const promise = Promise.resolve({
exitCode: 0,
stdout: Buffer.from(''),
stderr: Buffer.from(''),
text: () => '',
}) as any;
promise.quiet = () => promise;
promise.nothrow = () => promise;
return promise;
}) as any;
return { shell, commands };
}
describe('opencode plugin packaging', () => {
it('exports a v1 opencode server plugin as the default export', () => {
expect(plugin).toMatchObject({
id: 'episodic-memory',
server,
});
});
it('runs opencode-only background sync when a session becomes idle', async () => {
const { shell, commands } = createShellRecorder();
const hooks = await server({ $, client: undefined } as any, { summaryLimit: 3 });
await hooks.event?.({ event: { type: 'session.idle' } as any });
await hooks.event?.({ event: { type: 'session.updated' } as any });
expect(commands).toEqual([
'episodic-memory sync --background --only opencode --summary-limit 3',
]);
function $() {
return shell.apply(null, arguments as any);
}
});
it('declares an opencode server export in package metadata', () => {
const pkg = readJson('package.json');
expect(pkg.exports['./server']).toBeDefined();
expect(pkg.exports['./server'].import).toBe('./dist/opencode-plugin.js');
});
it('builds an opencode documentation page', () => {
expect(existsSync(join(REPO_ROOT, 'docs/OPENCODE.md'))).toBe(true);
});
});