Replace OpenCode with SmartAgent runtime

This commit is contained in:
2026-05-14 13:15:48 +00:00
parent 08ed394737
commit 73cccc1fc2
28 changed files with 1636 additions and 1840 deletions
+55
View File
@@ -0,0 +1,55 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as fs from 'node:fs/promises';
import * as os from 'node:os';
import * as path from 'node:path';
import { GitZoneAgentRuntime, type IAgentEventEnvelope, type IAgentProjectContext } from '../applications/electron-shell/ts/agent-runtime.js';
const createProjectContext = (instanceId: string): IAgentProjectContext => ({
instanceId,
title: 'Persisted Project',
path: '/srv/work/persisted-project',
runtimeRoot: '/tmp/gitzone-ide-runtime-test',
target: {
id: 'dev-box',
hostAlias: 'dev-box',
hostName: 'dev.example.com',
user: 'deploy',
port: 2222,
},
batchMode: true,
});
tap.test('should persist agent sessions by remote project context', async () => {
const persistenceRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'gitzone-agent-runtime-'));
try {
const events: IAgentEventEnvelope[] = [];
const firstRuntime = new GitZoneAgentRuntime(
(payload) => events.push(payload),
(instanceId) => createProjectContext(instanceId),
persistenceRoot,
);
const createdSession = await firstRuntime.createSession({
instanceId: 'first-instance',
title: 'Persisted Chat',
});
firstRuntime.dispose();
const secondRuntime = new GitZoneAgentRuntime(
() => undefined,
(instanceId) => createProjectContext(instanceId),
persistenceRoot,
);
const sessions = await secondRuntime.listSessions({ instanceId: 'second-instance' });
secondRuntime.dispose();
expect(createdSession.title).toEqual('Persisted Chat');
expect(events.some((payload) => payload.event.type === 'session.created')).toEqual(true);
expect(sessions).toHaveLength(1);
expect(sessions[0]!.id).toEqual(createdSession.id);
expect(sessions[0]!.title).toEqual('Persisted Chat');
} finally {
await fs.rm(persistenceRoot, { recursive: true, force: true });
}
});
export default tap.start();
-72
View File
@@ -1,18 +1,12 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as childProcess from 'node:child_process';
import * as fs from 'node:fs/promises';
import * as os from 'node:os';
import * as path from 'node:path';
import {
createRemoteEphemeralBootstrapCommand,
createRemoteEphemeralReadinessCommand,
createRemoteEphemeralPortAllocationCommand,
createRemoteEphemeralRuntimeCacheCheckCommand,
createRemoteEphemeralRuntimeMarkCommand,
createRemoteOpenCodeToolCommand,
createRemoteProjectListCommand,
createRemoteProjectUpsertCommand,
remoteOpenCodeToolScript,
createRemoteBootstrapCommand,
createRemoteInstallCommand,
createRemoteServerInstallPlan,
@@ -52,15 +46,10 @@ tap.test('should render install and bootstrap commands', async () => {
serverVersion: '0.1.0',
workspacePath: '/srv/work/project',
theiaPort: 33990,
opencodePort: 4096,
opencodeUsername: 'opencode',
opencodePassword: 'secret',
});
expect(installCommand).toInclude('GITZONE_IDE_MANIFEST');
expect(installCommand).toInclude('"$HOME"/\'.git.zone/ide/server/0.1.0\'');
expect(bootstrapCommand).toInclude('GITZONE_IDE_OPENCODE_PORT');
expect(bootstrapCommand).toInclude('GITZONE_IDE_DISABLE_OPENCODE_AUTOSTART');
expect(bootstrapCommand).toInclude('pnpm --dir');
});
@@ -69,9 +58,6 @@ tap.test('should render remote home paths as expandable shell paths', async () =
serverVersion: '0.1.0',
workspacePath: '$HOME',
theiaPort: 33990,
opencodePort: 4096,
opencodeUsername: 'opencode',
opencodePassword: 'secret',
});
expect(bootstrapCommand).toInclude('test -d "$HOME"');
@@ -85,9 +71,6 @@ tap.test('should render ephemeral runtime bootstrap without remote pnpm', async
runtimeRoot: '/tmp/gitzone-ide-runtime-test',
workspacePath: '$HOME',
theiaPort: 33990,
opencodePort: 4096,
opencodeUsername: 'opencode',
opencodePassword: 'secret',
});
expect(bootstrapCommand).toInclude('/tmp/gitzone-ide-runtime-test/node/bin/node');
@@ -95,7 +78,6 @@ tap.test('should render ephemeral runtime bootstrap without remote pnpm', async
expect(bootstrapCommand).not.toInclude('pnpm');
expect(bootstrapCommand).toInclude('LD_LIBRARY_PATH');
expect(bootstrapCommand).toInclude('THEIA_CONFIG_DIR="$HOME"/\'.git.zone/ide/theia\'');
expect(bootstrapCommand).toInclude('GITZONE_IDE_DISABLE_OPENCODE_AUTOSTART=\'1\'');
expect(bootstrapCommand).toInclude('GITZONE_IDE_THEIA_COLOR_THEME=\'dark\'');
expect(bootstrapCommand).toInclude("settings['workbench.colorTheme'] = colorTheme");
expect(bootstrapCommand).toInclude('"$HOME"/\'.git.zone/ide/logs\'');
@@ -150,43 +132,6 @@ tap.test('should render remote port allocation command', async () => {
expect(portCommand).toInclude('LD_LIBRARY_PATH');
});
tap.test('should render remote OpenCode tool bridge command', async () => {
const command = createRemoteOpenCodeToolCommand({
runtimeRoot: '/tmp/gitzone-ide-runtime-test',
workspacePath: '$HOME/project',
toolName: 'read',
});
expect(command).toInclude('/tmp/gitzone-ide-runtime-test/node/bin/node');
expect(command).toInclude('GITZONE_IDE_TOOL_NAME=\'read\'');
expect(command).toInclude('GITZONE_IDE_WORKSPACE="$HOME"/\'project\'');
expect(command).toInclude('GITZONE_IDE_RG_PATH');
expect(command).toInclude('fs.readFileSync(0');
});
tap.test('should execute remote OpenCode tool script with stdin payloads', async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gitzone-opencode-tool-'));
const filePath = path.join(tempDir, 'sample.txt');
await fs.writeFile(filePath, 'hello\nworld\n');
const readResult = runRemoteOpenCodeToolScript('read', tempDir, { filePath: 'sample.txt' });
expect(readResult.output).toInclude('1: hello');
const editResult = runRemoteOpenCodeToolScript('edit', tempDir, {
filePath: 'sample.txt',
oldString: 'world',
newString: 'remote',
});
expect(editResult.output).toInclude('Edit applied successfully');
expect(await fs.readFile(filePath, 'utf8')).toEqual('hello\nremote\n');
const patchResult = runRemoteOpenCodeToolScript('apply_patch', tempDir, {
patchText: '*** Begin Patch\n*** Add File: nested/new.txt\n+created remotely\n*** End Patch',
});
expect(patchResult.output).toInclude('A nested/new.txt');
expect(await fs.readFile(path.join(tempDir, 'nested', 'new.txt'), 'utf8')).toEqual('created remotely\n');
});
tap.test('should render remote project registry commands', async () => {
const listCommand = createRemoteProjectListCommand({
runtimeRoot: '/tmp/gitzone-ide-runtime-test',
@@ -204,21 +149,4 @@ tap.test('should render remote project registry commands', async () => {
expect(upsertCommand).toInclude('crypto.createHash');
});
const runRemoteOpenCodeToolScript = (toolName: string, workspacePath: string, args: Record<string, unknown>) => {
const result = childProcess.spawnSync(process.execPath, ['-e', remoteOpenCodeToolScript], {
input: JSON.stringify({ args }),
encoding: 'utf8',
env: {
...process.env,
GITZONE_IDE_TOOL_NAME: toolName,
GITZONE_IDE_WORKSPACE: workspacePath,
GITZONE_IDE_RG_PATH: '/not-found/rg',
},
});
if (result.status !== 0) {
throw new Error(result.stderr || `tool script failed with ${result.status}`);
}
return JSON.parse(result.stdout) as { output: string; metadata?: Record<string, unknown> };
};
export default tap.start();
-39
View File
@@ -1,39 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import {
openCodeBridgeToolNames,
renderOpenCodeBridgeConfigContent,
renderOpenCodeBridgeToolFile,
renderOpenCodeBridgeToolFiles,
} from '../packages/opencode-bridge/ts/index.js';
tap.test('should render managed OpenCode bridge config', async () => {
const config = JSON.parse(renderOpenCodeBridgeConfigContent());
expect(config.snapshot).toEqual(false);
expect(config.autoupdate).toEqual(false);
expect(config.permission.lsp).toEqual('deny');
expect(config.permission.skill).toEqual('deny');
});
tap.test('should render tool overrides for remote bridge', async () => {
const files = renderOpenCodeBridgeToolFiles();
for (const toolName of openCodeBridgeToolNames) {
expect(files[`tools/${toolName}.js`]).toInclude(`forwardTool(${JSON.stringify(toolName)}`);
}
expect(files['tools/bash.js']).toInclude('GITZONE_IDE_TOOL_BRIDGE_URL');
expect(files['tools/apply_patch.js']).toInclude('patchText');
});
tap.test('should allow custom bridge environment names', async () => {
const toolFile = renderOpenCodeBridgeToolFile('read', {
bridgeUrlEnvName: 'CUSTOM_BRIDGE_URL',
bridgeTokenEnvName: 'CUSTOM_BRIDGE_TOKEN',
});
expect(toolFile).toInclude('CUSTOM_BRIDGE_URL');
expect(toolFile).toInclude('CUSTOM_BRIDGE_TOKEN');
expect(toolFile).toInclude('filePath');
});
export default tap.start();
-40
View File
@@ -1,40 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as fs from 'node:fs/promises';
import { parseServerSentEvent, sanitizeOpenCodeEventForRenderer } from '../packages/opencode-bridge/ts/index.js';
tap.test('should parse named opencode sse events', async () => {
const event = parseServerSentEvent('id: 1\nevent: server.connected\ndata: {"type":"server.connected"}\n');
expect(event!.id).toEqual('1');
expect(event!.type).toEqual('server.connected');
expect(event!.data).toEqual({ type: 'server.connected' });
});
tap.test('should infer opencode event type from json data', async () => {
const event = parseServerSentEvent('data: {"type":"session.updated","properties":{"id":"abc"}}\n');
expect(event!.type).toEqual('session.updated');
expect(event!.data).toEqual({ type: 'session.updated', properties: { id: 'abc' } });
});
tap.test('should sanitize opencode events for renderer delivery', async () => {
const event = parseServerSentEvent('id: 2\nretry: 1000\nevent: permission.asked\ndata: {"permissionID":"perm-1"}\n')!;
const sanitized = sanitizeOpenCodeEventForRenderer(event);
expect(sanitized).toEqual({
type: 'permission.asked',
id: '2',
retry: 1000,
data: { permissionID: 'perm-1' },
});
expect(Object.prototype.hasOwnProperty.call(sanitized, 'raw')).toEqual(false);
});
tap.test('should keep electron shell opencode resolution IDE-local', async () => {
const source = await fs.readFile(new URL('../applications/electron-shell/ts/main.ts', import.meta.url), 'utf8');
expect(source.includes('process.env.OPENCODE_BINARY')).toEqual(false);
expect(source.includes("'.opencode', 'bin', 'opencode'")).toEqual(false);
expect(source.includes('/usr/local/bin/opencode')).toEqual(false);
expect(source.includes('/usr/bin/opencode')).toEqual(false);
});
export default tap.start();