mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-13 09:42:02 +08:00
The remote environment could start OpenCode runs under /opt/claudecodeui. That happened even when the selected project path was correct. The integration relied on child-process cwd alone. OpenCode run resolves its workspace through the explicit --dir contract. Pass --dir with the resolved working directory. Assert in the CLI test that launch args include the workspace dir.
115 lines
4.1 KiB
JavaScript
115 lines
4.1 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { chmod, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
|
|
import { spawnOpenCode } from './opencode-cli.js';
|
|
|
|
const findEnvKey = (name) =>
|
|
Object.keys(process.env).find((key) => key.toLowerCase() === name.toLowerCase()) || name;
|
|
|
|
async function createFakeOpenCodeExecutable(binDir) {
|
|
const scriptPath = path.join(binDir, 'opencode.js');
|
|
await writeFile(scriptPath, `
|
|
const capturePath = process.env.OPENCODE_ARGS_CAPTURE;
|
|
if (capturePath) {
|
|
require('node:fs').writeFileSync(capturePath, JSON.stringify(process.argv.slice(2)));
|
|
}
|
|
|
|
const events = [
|
|
{ type: 'text', sessionID: 'open-live-1', text: 'assistant response' },
|
|
{ type: 'step_finish', sessionID: 'open-live-1' },
|
|
];
|
|
|
|
for (const event of events) {
|
|
console.log(JSON.stringify(event));
|
|
}
|
|
`, 'utf8');
|
|
|
|
if (process.platform === 'win32') {
|
|
const commandPath = path.join(binDir, 'opencode.cmd');
|
|
await writeFile(commandPath, '@echo off\r\nnode "%~dp0opencode.js" %*\r\n', 'utf8');
|
|
return;
|
|
}
|
|
|
|
const commandPath = path.join(binDir, 'opencode');
|
|
await writeFile(commandPath, '#!/bin/sh\nnode "$(dirname "$0")/opencode.js" "$@"\n', 'utf8');
|
|
await chmod(commandPath, 0o755);
|
|
}
|
|
|
|
test('spawnOpenCode emits session_created before normalized live messages for new sessions', async () => {
|
|
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'opencode-cli-live-'));
|
|
const argsCapturePath = path.join(tempRoot, 'opencode-args.json');
|
|
const pathKey = findEnvKey('PATH');
|
|
const pathExtKey = findEnvKey('PATHEXT');
|
|
const previousPath = process.env[pathKey];
|
|
const previousPathExt = process.env[pathExtKey];
|
|
const previousArgsCapture = process.env.OPENCODE_ARGS_CAPTURE;
|
|
const messages = [];
|
|
const writer = {
|
|
userId: null,
|
|
sessionId: null,
|
|
send(message) {
|
|
messages.push(message);
|
|
},
|
|
setSessionId(sessionId) {
|
|
this.sessionId = sessionId;
|
|
},
|
|
};
|
|
|
|
try {
|
|
await createFakeOpenCodeExecutable(tempRoot);
|
|
process.env[pathKey] = `${tempRoot}${path.delimiter}${previousPath || ''}`;
|
|
process.env.OPENCODE_ARGS_CAPTURE = argsCapturePath;
|
|
if (process.platform === 'win32') {
|
|
process.env[pathExtKey] = previousPathExt?.toUpperCase().includes('.CMD')
|
|
? previousPathExt
|
|
: `.COM;.EXE;.BAT;.CMD${previousPathExt ? `;${previousPathExt}` : ''}`;
|
|
}
|
|
|
|
await spawnOpenCode('Hi', { cwd: tempRoot }, writer);
|
|
|
|
const sessionCreatedIndex = messages.findIndex((message) => message.kind === 'session_created');
|
|
const assistantDeltaIndex = messages.findIndex((message) =>
|
|
message.kind === 'stream_delta' && message.content === 'assistant response',
|
|
);
|
|
const streamEnd = messages.find((message) => message.kind === 'stream_end');
|
|
const complete = messages.find((message) => message.kind === 'complete');
|
|
|
|
assert.notEqual(sessionCreatedIndex, -1);
|
|
assert.notEqual(assistantDeltaIndex, -1);
|
|
assert.ok(sessionCreatedIndex < assistantDeltaIndex);
|
|
assert.equal(messages[sessionCreatedIndex].newSessionId, 'open-live-1');
|
|
assert.equal(writer.sessionId, 'open-live-1');
|
|
assert.equal(streamEnd?.sessionId, 'open-live-1');
|
|
assert.equal(complete?.sessionId, 'open-live-1');
|
|
assert.equal(messages.some((message) => message.kind === 'error'), false);
|
|
|
|
const launchedArgs = JSON.parse(await readFile(argsCapturePath, 'utf8'));
|
|
assert.ok(Array.isArray(launchedArgs));
|
|
assert.deepEqual(launchedArgs.slice(0, 4), ['run', '--format', 'json', '--dir']);
|
|
assert.equal(launchedArgs[4], tempRoot);
|
|
} finally {
|
|
if (previousPath === undefined) {
|
|
delete process.env[pathKey];
|
|
} else {
|
|
process.env[pathKey] = previousPath;
|
|
}
|
|
|
|
if (previousPathExt === undefined) {
|
|
delete process.env[pathExtKey];
|
|
} else {
|
|
process.env[pathExtKey] = previousPathExt;
|
|
}
|
|
|
|
if (previousArgsCapture === undefined) {
|
|
delete process.env.OPENCODE_ARGS_CAPTURE;
|
|
} else {
|
|
process.env.OPENCODE_ARGS_CAPTURE = previousArgsCapture;
|
|
}
|
|
|
|
await rm(tempRoot, { recursive: true, force: true });
|
|
}
|
|
});
|