Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions cli/__tests__/adapters.opencode.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1031,8 +1031,8 @@ describe('opencode external provider config', () => {
});

describe('opencode adapter — spawn()', () => {
test('records both smoke-tested OpenCode versions', () => {
expect(TESTED_OPENCODE_VERSIONS).toEqual(['1.18.35', '1.19.0']);
test('records every smoke-tested OpenCode version', () => {
expect(TESTED_OPENCODE_VERSIONS).toEqual(['1.18.35', '1.19.0', '1.19.4']);
});

test('a seat with no public sandbox declaration runs as trusted', async () => {
Expand Down Expand Up @@ -1077,7 +1077,7 @@ describe('opencode adapter — spawn()', () => {
});
});

test.each(['1.18.35', '1.19.0'])('a public seat accepts smoke-tested OpenCode %s', async (version) => {
test.each(['1.18.35', '1.19.0', '1.19.4'])('a public seat accepts smoke-tested OpenCode %s', async (version) => {
await withProcessEnv(ALLOW_UNTESTED_OPENCODE_VERSION_ENV, undefined, async () => {
await withTemp(async (root) => {
const workspace = join(root, 'workspace');
Expand Down Expand Up @@ -1137,7 +1137,7 @@ describe('opencode adapter — spawn()', () => {
});
expect(spawn.calls).toHaveLength(1);
expect(warning).toHaveBeenCalledWith(
'[opencode] installed OpenCode 1.20.0 is newer than smoke-tested versions 1.18.35, 1.19.0; trusted seat continues',
'[opencode] installed OpenCode 1.20.0 is newer than smoke-tested versions 1.18.35, 1.19.0, 1.19.4; trusted seat continues',
);
} finally {
warning.mockRestore();
Expand All @@ -1150,30 +1150,35 @@ describe('opencode adapter — spawn()', () => {
{
description: 'older',
result: { status: 0, stdout: 'OpenCode 1.18.0\n' },
message: /installed OpenCode 1\.18\.0 is older than smoke-tested versions 1\.18\.35, 1\.19\.0.*COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1/,
message: /installed OpenCode 1\.18\.0 is older than smoke-tested versions 1\.18\.35, 1\.19\.0, 1\.19\.4.*COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1/,
},
{
description: 'between smoke-tested versions',
result: { status: 0, stdout: 'OpenCode 1.18.40\n' },
message: /installed OpenCode 1\.18\.40 differs from smoke-tested versions 1\.18\.35, 1\.19\.0.*COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1/,
message: /installed OpenCode 1\.18\.40 differs from smoke-tested versions 1\.18\.35, 1\.19\.0, 1\.19\.4.*COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1/,
},
{
description: 'untested next-patch',
result: { status: 0, stdout: 'OpenCode 1.19.5\n' },
message: /installed OpenCode 1\.19\.5 is newer than smoke-tested versions 1\.18\.35, 1\.19\.0, 1\.19\.4.*COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1/,
},
{
description: 'newer',
result: { status: 0, stdout: 'OpenCode 1.20.0\n' },
message: /installed OpenCode 1\.20\.0 is newer than smoke-tested versions 1\.18\.35, 1\.19\.0.*COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1/,
message: /installed OpenCode 1\.20\.0 is newer than smoke-tested versions 1\.18\.35, 1\.19\.0, 1\.19\.4.*COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1/,
},
{
description: 'unavailable',
result: { status: 1, stdout: '', stderr: 'version command failed' },
message: /installed OpenCode version is unknown; smoke-tested versions: 1\.18\.35, 1\.19\.0.*COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1/,
message: /installed OpenCode version is unknown; smoke-tested versions: 1\.18\.35, 1\.19\.0, 1\.19\.4.*COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1/,
},
{
description: 'update announcement containing multiple versions',
result: {
status: 0,
stdout: 'A new release is available: 1.18.35 -> 1.19.0\n',
},
message: /installed OpenCode version is unknown; smoke-tested versions: 1\.18\.35, 1\.19\.0.*COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1/,
message: /installed OpenCode version is unknown; smoke-tested versions: 1\.18\.35, 1\.19\.0, 1\.19\.4.*COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1/,
},
])('a public seat refuses an $description version before creating state or spawning', async ({
result,
Expand Down Expand Up @@ -1241,7 +1246,7 @@ describe('opencode adapter — spawn()', () => {
_binaryPath: '/usr/local/bin/opencode',
_platform: 'darwin',
_spawnImpl: spawn.impl,
})).rejects.toThrow(/refuse to run.*1\.20\.0.*1\.18\.35, 1\.19\.0.*COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1/);
})).rejects.toThrow(/refuse to run.*1\.20\.0.*1\.18\.35, 1\.19\.0, 1\.19\.4.*COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1/);
expect(spawn.calls).toHaveLength(0);
expect(validateEnvironmentSpec({
version: 1, [ALLOW_UNTESTED_OPENCODE_VERSION_ENV]: '1',
Expand Down Expand Up @@ -1292,11 +1297,11 @@ describe('opencode adapter — spawn()', () => {
await opencode.spawn('public turn', spawnOptions);
expect(spawn.calls).toHaveLength(1);
expect(warning).toHaveBeenCalledWith(
'[opencode] installed OpenCode 1.20.0 is newer than smoke-tested versions 1.18.35, 1.19.0; continuing because the host process set COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1',
'[opencode] installed OpenCode 1.20.0 is newer than smoke-tested versions 1.18.35, 1.19.0, 1.19.4; continuing because the host process set COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1',
);
} else {
await expect(opencode.spawn('public turn', spawnOptions)).rejects.toThrow(
/refuse to run.*1\.20\.0.*1\.18\.35, 1\.19\.0.*COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1/,
/refuse to run.*1\.20\.0.*1\.18\.35, 1\.19\.0, 1\.19\.4.*COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION=1/,
);
expect(spawn.calls).toHaveLength(0);
expect(warning).not.toHaveBeenCalled();
Expand Down
28 changes: 25 additions & 3 deletions cli/__tests__/adapters.qwen.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,34 @@ test('public launch isolates HOME and config, strips shadows, and never serializ
});
});

test('public version drift refuses before launching a provider proxy or child', async () => {
test('records every smoke-tested Qwen version', () => {
expect(TESTED_QWEN_VERSIONS).toEqual(['0.25.1', '0.26.2']);
});

test.each(['0.25.1', '0.26.2'])('a public seat accepts smoke-tested Qwen %s without the override', async (version) => {
await withSeat(async (ctx) => {
spawnSyncMock.mockImplementation(() => ({ status: 0, stdout: '0.26.0\n' }));
spawnSyncMock.mockImplementation(() => ({ status: 0, stdout: `${version}\n` }));
const previous = process.env[ALLOW_UNTESTED_QWEN_VERSION_ENV];
const warning = jest.spyOn(console, 'warn').mockImplementation(() => {});
try {
delete process.env[ALLOW_UNTESTED_QWEN_VERSION_ENV];
await expect(qwen.spawn('hello', { environment: { model: 'test-model' }, ...ctx, _spawnImpl: () => fakeChild() }))
.resolves.toMatchObject({ text: 'Qwen reply' });
expect(warning).not.toHaveBeenCalled();
} finally {
warning.mockRestore();
if (previous === undefined) delete process.env[ALLOW_UNTESTED_QWEN_VERSION_ENV];
else process.env[ALLOW_UNTESTED_QWEN_VERSION_ENV] = previous;
}
});
});

test.each(['0.26.0', '0.26.3'])('public version drift to %s refuses before launching a provider proxy or child', async (version) => {
await withSeat(async (ctx) => {
spawnSyncMock.mockImplementation(() => ({ status: 0, stdout: `${version}\n` }));
const spawn = jest.fn();
await expect(qwen.spawn('hello', { environment: { model: 'test-model' }, ...ctx, _spawnImpl: spawn }))
.rejects.toThrow(/Public Qwen seats refuse version 0.26.0/);
.rejects.toThrow(`Public Qwen seats refuse version ${version}; smoke-tested: 0.25.1, 0.26.2.`);
expect(spawn).not.toHaveBeenCalled();
});
});
Expand Down
2 changes: 1 addition & 1 deletion cli/src/lib/adapters/opencode.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const ISOLATED_INSTANCE_PORT = 8766;
const LOOPBACK_BRIDGE = fileURLToPath(new URL('../sandbox/opencode-loopback.mjs', import.meta.url));
const TERMINATION_GRACE_MS = 5000;
// Bump only with a passing real sandbox-exec smoke against the new version.
const TESTED_OPENCODE_VERSIONS = Object.freeze(['1.18.35', '1.19.0']);
const TESTED_OPENCODE_VERSIONS = Object.freeze(['1.18.35', '1.19.0', '1.19.4']);
const ALLOW_UNTESTED_OPENCODE_VERSION_ENV = 'COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION';
const VERSION_CHECK_TIMEOUT_MS = 5000;
const OPENCODE_CONFIG_GITIGNORE = [
Expand Down
4 changes: 2 additions & 2 deletions cli/src/lib/adapters/qwen.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** Qwen Code 0.25.1 wrapper. Public turns have fresh HOME, config and keys. */
/** Qwen Code 0.25.1 and 0.26.2 wrapper. Public turns have fresh HOME, config and keys. */
import { requireSeatModel } from './seat-model.js';
import { spawn as childSpawn, spawnSync } from 'child_process';
import { lstatSync, realpathSync } from 'fs';
Expand All @@ -17,7 +17,7 @@ import { resolveProviderKeyFile, startProviderProxy } from '../provider-proxy.js
import { adapterFailure, spawnCredentials } from '../upstream-refusal.js';

// Add a version only with a passing real clean-box sandbox smoke that names it.
const TESTED_QWEN_VERSIONS = Object.freeze(['0.25.1']);
const TESTED_QWEN_VERSIONS = Object.freeze(['0.25.1', '0.26.2']);
const ALLOW_UNTESTED_QWEN_VERSION_ENV = 'COMMONLY_QWEN_ALLOW_UNTESTED_VERSION';
const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
const TERMINATION_GRACE_MS = 5000;
Expand Down
Loading