Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
495 lines
14 KiB
TypeScript
495 lines
14 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import {
|
|
buildManagedAgentState,
|
|
getInitialManagedAgentPaths,
|
|
updateCodebuddyManagedEnv,
|
|
updateCodebuddyManagedOptions,
|
|
} from '../settings/tabs/ai/managedAgentState';
|
|
import type { ExternalAgentConfig } from '../../infrastructure/ai/types';
|
|
|
|
test('buildManagedAgentState removes stale managed agents when path detection fails', () => {
|
|
const agents: ExternalAgentConfig[] = [
|
|
{
|
|
id: 'discovered_codex',
|
|
name: 'Codex CLI',
|
|
command: '/usr/local/bin/codex',
|
|
enabled: true,
|
|
sdkBackend: 'codex',
|
|
},
|
|
{
|
|
id: 'custom-agent',
|
|
name: 'Custom Agent',
|
|
command: '/usr/local/bin/custom-agent',
|
|
enabled: true,
|
|
},
|
|
];
|
|
|
|
const state = buildManagedAgentState(
|
|
agents,
|
|
'discovered_codex',
|
|
'codex',
|
|
{ path: '/usr/local/bin/codex', version: null, available: false },
|
|
);
|
|
|
|
assert.deepEqual(
|
|
state.agents.map((agent) => agent.id),
|
|
['custom-agent'],
|
|
);
|
|
assert.equal(state.defaultAgentId, 'catty');
|
|
});
|
|
|
|
test('buildManagedAgentState keeps unrelated defaults when removing stale managed agents', () => {
|
|
const agents: ExternalAgentConfig[] = [
|
|
{
|
|
id: 'discovered_claude',
|
|
name: 'Claude Code',
|
|
command: '/usr/local/bin/claude',
|
|
enabled: true,
|
|
sdkBackend: 'claude',
|
|
},
|
|
{
|
|
id: 'custom-agent',
|
|
name: 'Custom Agent',
|
|
command: '/usr/local/bin/custom-agent',
|
|
enabled: true,
|
|
},
|
|
];
|
|
|
|
const state = buildManagedAgentState(
|
|
agents,
|
|
'custom-agent',
|
|
'claude',
|
|
{ path: '/usr/local/bin/claude', version: null, available: false },
|
|
);
|
|
|
|
assert.deepEqual(
|
|
state.agents.map((agent) => agent.id),
|
|
['custom-agent'],
|
|
);
|
|
assert.equal(state.defaultAgentId, 'custom-agent');
|
|
});
|
|
|
|
test('buildManagedAgentState stores the system Claude executable for SDK runs', () => {
|
|
const state = buildManagedAgentState(
|
|
[],
|
|
'catty',
|
|
'claude',
|
|
{ path: '/opt/homebrew/bin/claude', version: '2.1.145 (Claude Code)', available: true },
|
|
);
|
|
|
|
assert.equal(state.agents.length, 1);
|
|
assert.equal(state.agents[0].command, '/opt/homebrew/bin/claude');
|
|
assert.equal(state.agents[0].sdkBackend, 'claude');
|
|
assert.deepEqual(state.agents[0].env, {
|
|
CLAUDE_CODE_EXECUTABLE: '/opt/homebrew/bin/claude',
|
|
});
|
|
});
|
|
|
|
test('buildManagedAgentState stores SDK backend keys for discovered managed agents', () => {
|
|
const codexState = buildManagedAgentState(
|
|
[],
|
|
'catty',
|
|
'codex',
|
|
{ path: '/opt/homebrew/bin/codex', version: '1.0.0', available: true },
|
|
);
|
|
const copilotState = buildManagedAgentState(
|
|
[],
|
|
'catty',
|
|
'copilot',
|
|
{ path: '/opt/homebrew/bin/copilot', version: '1.0.0', available: true },
|
|
);
|
|
|
|
assert.equal(codexState.agents[0].sdkBackend, 'codex');
|
|
assert.equal(codexState.agents[0].cliVersion, '1.0.0');
|
|
assert.equal(copilotState.agents[0].sdkBackend, 'copilot');
|
|
assert.equal(copilotState.agents[0].acpArgs, undefined);
|
|
});
|
|
|
|
test('buildManagedAgentState preserves the experimental Codex runtime across path refreshes', () => {
|
|
const state = buildManagedAgentState(
|
|
[{
|
|
id: 'discovered_codex',
|
|
name: 'Codex CLI',
|
|
command: '/old/codex',
|
|
enabled: true,
|
|
sdkBackend: 'codex',
|
|
codexRuntime: 'app-server',
|
|
}],
|
|
'discovered_codex',
|
|
'codex',
|
|
{ path: '/new/codex', version: '0.144.3', available: true },
|
|
);
|
|
|
|
assert.equal(state.agents[0].codexRuntime, 'app-server');
|
|
assert.equal(state.agents[0].command, '/new/codex');
|
|
});
|
|
|
|
test('buildManagedAgentState preserves Grok runtime across path refreshes', () => {
|
|
const state = buildManagedAgentState(
|
|
[{
|
|
id: 'discovered_grok',
|
|
name: 'Grok Build',
|
|
command: '/old/grok',
|
|
enabled: true,
|
|
sdkBackend: 'grok',
|
|
grokRuntime: 'streaming-json',
|
|
}],
|
|
'discovered_grok',
|
|
'grok',
|
|
{ path: '/new/grok', version: '0.2.118', available: true },
|
|
);
|
|
|
|
assert.equal(state.agents[0].grokRuntime, 'streaming-json');
|
|
assert.equal(state.agents[0].command, '/new/grok');
|
|
assert.equal(state.agents[0].sdkBackend, 'grok');
|
|
});
|
|
|
|
test('getInitialManagedAgentPaths ignores auto-detected command paths', () => {
|
|
const state = buildManagedAgentState(
|
|
[],
|
|
'catty',
|
|
'codex',
|
|
{ path: '/opt/homebrew/bin/codex', version: '1.0.0', available: true },
|
|
'auto',
|
|
);
|
|
|
|
assert.equal(state.agents[0].commandSource, 'auto');
|
|
assert.equal(getInitialManagedAgentPaths(state.agents).codex, '');
|
|
});
|
|
|
|
test('getInitialManagedAgentPaths keeps manual and legacy command paths', () => {
|
|
const manualState = buildManagedAgentState(
|
|
[],
|
|
'catty',
|
|
'codex',
|
|
{ path: '/opt/homebrew/bin/codex', version: '1.0.0', available: true },
|
|
'manual',
|
|
);
|
|
|
|
assert.equal(getInitialManagedAgentPaths(manualState.agents).codex, '/opt/homebrew/bin/codex');
|
|
assert.equal(getInitialManagedAgentPaths([{
|
|
id: 'discovered_codex',
|
|
name: 'Codex CLI',
|
|
command: '/legacy/bin/codex',
|
|
enabled: true,
|
|
available: true,
|
|
sdkBackend: 'codex',
|
|
}]).codex, '/legacy/bin/codex');
|
|
});
|
|
|
|
test('buildManagedAgentState stores SDK backend key for discovered Cursor', () => {
|
|
const state = buildManagedAgentState(
|
|
[],
|
|
'catty',
|
|
'cursor',
|
|
{ path: 'cursor', version: 'Cursor SDK 1.0.18', available: true },
|
|
);
|
|
|
|
assert.equal(state.agents[0].id, 'discovered_cursor');
|
|
assert.equal(state.agents[0].name, 'Cursor');
|
|
assert.equal(state.agents[0].command, 'cursor');
|
|
assert.equal(state.agents[0].sdkBackend, 'cursor');
|
|
});
|
|
|
|
test('buildManagedAgentState preserves a saved Cursor API key when SDK is not ready', () => {
|
|
const agents: ExternalAgentConfig[] = [
|
|
{
|
|
id: 'discovered_cursor',
|
|
name: 'Cursor',
|
|
command: 'cursor',
|
|
enabled: true,
|
|
available: true,
|
|
sdkBackend: 'cursor',
|
|
apiKey: 'enc:v1:test',
|
|
},
|
|
];
|
|
|
|
const state = buildManagedAgentState(
|
|
agents,
|
|
'discovered_cursor',
|
|
'cursor',
|
|
{ path: 'cursor', version: 'Cursor SDK', available: false, installed: true },
|
|
);
|
|
|
|
assert.equal(state.agents[0].id, 'discovered_cursor');
|
|
assert.equal(state.agents[0].apiKey, 'enc:v1:test');
|
|
// Keep enabled so a later mode/path that becomes available is not sticky-disabled.
|
|
assert.equal(state.agents[0].enabled, true);
|
|
assert.equal(state.agents[0].available, false);
|
|
assert.equal(state.defaultAgentId, 'catty');
|
|
});
|
|
|
|
test('buildManagedAgentState preserves enabled when CLI login probe is temporarily unavailable', () => {
|
|
const agents: ExternalAgentConfig[] = [
|
|
{
|
|
id: 'discovered_cursor',
|
|
name: 'Cursor',
|
|
command: '/bin/cursor-agent',
|
|
enabled: true,
|
|
available: true,
|
|
sdkBackend: 'cursor',
|
|
cursorAuthMode: 'cli-login',
|
|
apiKey: 'enc:v1:test',
|
|
},
|
|
];
|
|
|
|
const unavailable = buildManagedAgentState(
|
|
agents,
|
|
'discovered_cursor',
|
|
'cursor',
|
|
{
|
|
path: 'cursor',
|
|
version: 'Cursor Agent CLI',
|
|
available: false,
|
|
installed: true,
|
|
cliLoginOk: false,
|
|
apiKeyOk: true,
|
|
sdkInstalled: true,
|
|
},
|
|
);
|
|
assert.equal(unavailable.agents[0].enabled, true);
|
|
assert.equal(unavailable.agents[0].available, false);
|
|
assert.equal(unavailable.agents[0].apiKey, 'enc:v1:test');
|
|
assert.equal(unavailable.agents[0].cursorAuthMode, 'cli-login');
|
|
|
|
// When CLI login returns, mode-aware available becomes true and enabled stays on.
|
|
const recovered = buildManagedAgentState(
|
|
unavailable.agents,
|
|
'catty',
|
|
'cursor',
|
|
{
|
|
path: '/bin/cursor-agent',
|
|
cliBinPath: '/bin/cursor-agent',
|
|
version: 'Cursor Agent CLI',
|
|
available: true,
|
|
installed: true,
|
|
cliLoginOk: true,
|
|
apiKeyOk: true,
|
|
sdkInstalled: true,
|
|
authSource: 'cli-login',
|
|
},
|
|
);
|
|
assert.equal(recovered.agents[0].enabled, true);
|
|
assert.equal(recovered.agents[0].available, true);
|
|
assert.equal(recovered.agents[0].cursorAuthMode, 'cli-login');
|
|
assert.equal(recovered.agents[0].command, '/bin/cursor-agent');
|
|
});
|
|
|
|
test('buildManagedAgentState keeps API key when Cursor stays on CLI login mode', () => {
|
|
const agents: ExternalAgentConfig[] = [
|
|
{
|
|
id: 'discovered_cursor',
|
|
name: 'Cursor',
|
|
command: '/bin/cursor-agent',
|
|
enabled: true,
|
|
available: true,
|
|
sdkBackend: 'cursor',
|
|
cursorAuthMode: 'cli-login',
|
|
apiKey: 'enc:v1:keep-me',
|
|
},
|
|
];
|
|
|
|
const state = buildManagedAgentState(
|
|
agents,
|
|
'discovered_cursor',
|
|
'cursor',
|
|
{
|
|
path: '/bin/cursor-agent',
|
|
cliBinPath: '/bin/cursor-agent',
|
|
version: 'Cursor Agent CLI',
|
|
available: true,
|
|
installed: true,
|
|
cliLoginOk: true,
|
|
apiKeyOk: true,
|
|
sdkInstalled: true,
|
|
authSource: 'cli-login',
|
|
},
|
|
);
|
|
|
|
assert.equal(state.agents[0].apiKey, 'enc:v1:keep-me');
|
|
assert.equal(state.agents[0].cursorAuthMode, 'cli-login');
|
|
});
|
|
|
|
test('buildManagedAgentState stores CODEBUDDY_CODE_PATH for codebuddy', () => {
|
|
const state = buildManagedAgentState(
|
|
[],
|
|
'catty',
|
|
'codebuddy',
|
|
{ path: '/opt/homebrew/bin/codebuddy', version: '0.1.0', available: true },
|
|
);
|
|
|
|
assert.equal(state.agents.length, 1);
|
|
assert.equal(state.agents[0].command, '/opt/homebrew/bin/codebuddy');
|
|
assert.equal(state.agents[0].sdkBackend, 'codebuddy');
|
|
assert.deepEqual(state.agents[0].env, {
|
|
CODEBUDDY_CODE_PATH: '/opt/homebrew/bin/codebuddy',
|
|
});
|
|
});
|
|
|
|
test('buildManagedAgentState stores OPENCODE_BIN for opencode', () => {
|
|
const state = buildManagedAgentState(
|
|
[],
|
|
'catty',
|
|
'opencode',
|
|
{ path: '/opt/homebrew/bin/opencode', version: '1.0.0', available: true },
|
|
);
|
|
|
|
assert.equal(state.agents.length, 1);
|
|
assert.equal(state.agents[0].id, 'discovered_opencode');
|
|
assert.equal(state.agents[0].command, '/opt/homebrew/bin/opencode');
|
|
assert.equal(state.agents[0].sdkBackend, 'opencode');
|
|
assert.deepEqual(state.agents[0].env, {
|
|
OPENCODE_BIN: '/opt/homebrew/bin/opencode',
|
|
});
|
|
});
|
|
|
|
test('updateCodebuddyManagedEnv creates a disabled managed entry before CLI detection', () => {
|
|
const state = updateCodebuddyManagedEnv([], 'internal', 'CODEBUDDY_API_KEY=secret');
|
|
|
|
assert.equal(state.length, 1);
|
|
assert.equal(state[0].id, 'discovered_codebuddy');
|
|
assert.equal(state[0].command, 'codebuddy');
|
|
assert.equal(state[0].enabled, false);
|
|
assert.deepEqual(state[0].env, {
|
|
CODEBUDDY_INTERNET_ENVIRONMENT: 'internal',
|
|
CODEBUDDY_API_KEY: 'secret',
|
|
});
|
|
});
|
|
|
|
test('buildManagedAgentState preserves disabled CodeBuddy config when path detection fails', () => {
|
|
const agents = updateCodebuddyManagedEnv([], 'ioa', 'CODEBUDDY_AUTH_TOKEN=token');
|
|
|
|
const state = buildManagedAgentState(
|
|
agents,
|
|
'discovered_codebuddy',
|
|
'codebuddy',
|
|
{ path: null, version: null, available: false },
|
|
);
|
|
|
|
assert.equal(state.defaultAgentId, 'catty');
|
|
assert.equal(state.agents.length, 1);
|
|
assert.equal(state.agents[0].id, 'discovered_codebuddy');
|
|
assert.equal(state.agents[0].enabled, false);
|
|
assert.deepEqual(state.agents[0].env, {
|
|
CODEBUDDY_INTERNET_ENVIRONMENT: 'ioa',
|
|
CODEBUDDY_AUTH_TOKEN: 'token',
|
|
});
|
|
});
|
|
|
|
test('buildManagedAgentState enables preconfigured CodeBuddy when path detection succeeds', () => {
|
|
const agents = updateCodebuddyManagedEnv([], 'internal', 'CODEBUDDY_API_KEY=secret');
|
|
|
|
const state = buildManagedAgentState(
|
|
agents,
|
|
'catty',
|
|
'codebuddy',
|
|
{ path: '/opt/homebrew/bin/codebuddy', version: '0.1.0', available: true },
|
|
);
|
|
|
|
assert.equal(state.agents.length, 1);
|
|
assert.equal(state.agents[0].enabled, true);
|
|
assert.equal(state.agents[0].command, '/opt/homebrew/bin/codebuddy');
|
|
assert.deepEqual(state.agents[0].env, {
|
|
CODEBUDDY_INTERNET_ENVIRONMENT: 'internal',
|
|
CODEBUDDY_API_KEY: 'secret',
|
|
CODEBUDDY_CODE_PATH: '/opt/homebrew/bin/codebuddy',
|
|
});
|
|
});
|
|
|
|
test('updateCodebuddyManagedEnv removes an empty pre-detection placeholder', () => {
|
|
const agents = updateCodebuddyManagedEnv([], 'internal', 'CODEBUDDY_API_KEY=secret');
|
|
const cleared = updateCodebuddyManagedEnv(agents, '', '');
|
|
|
|
assert.deepEqual(cleared, []);
|
|
});
|
|
|
|
test('updateCodebuddyManagedOptions persists settings before CLI detection', () => {
|
|
const state = updateCodebuddyManagedOptions([], {
|
|
effort: 'high',
|
|
enableFileCheckpointing: true,
|
|
});
|
|
|
|
assert.equal(state.length, 1);
|
|
assert.equal(state[0].id, 'discovered_codebuddy');
|
|
assert.equal(state[0].command, 'codebuddy');
|
|
assert.equal(state[0].enabled, false);
|
|
assert.deepEqual(state[0].codebuddyOptions, {
|
|
effort: 'high',
|
|
enableFileCheckpointing: true,
|
|
});
|
|
});
|
|
|
|
test('buildManagedAgentState preserves advanced CodeBuddy config when detection fails', () => {
|
|
const agents = updateCodebuddyManagedOptions([], { effort: 'high' });
|
|
|
|
const state = buildManagedAgentState(
|
|
agents,
|
|
'discovered_codebuddy',
|
|
'codebuddy',
|
|
{ path: null, version: null, available: false },
|
|
);
|
|
|
|
assert.equal(state.defaultAgentId, 'catty');
|
|
assert.equal(state.agents.length, 1);
|
|
assert.equal(state.agents[0].enabled, false);
|
|
assert.equal(state.agents[0].available, false);
|
|
assert.deepEqual(state.agents[0].codebuddyOptions, { effort: 'high' });
|
|
});
|
|
|
|
test('updateCodebuddyManagedOptions removes an empty pre-detection placeholder', () => {
|
|
const agents = updateCodebuddyManagedOptions([], { effort: 'high' });
|
|
const cleared = updateCodebuddyManagedOptions(agents, undefined);
|
|
|
|
assert.deepEqual(cleared, []);
|
|
});
|
|
|
|
test('buildManagedAgentState does not remove user-created matching agents', () => {
|
|
const agents: ExternalAgentConfig[] = [
|
|
{
|
|
id: 'my-claude-wrapper',
|
|
name: 'My Claude Wrapper',
|
|
command: '/usr/local/bin/claude',
|
|
enabled: true,
|
|
sdkBackend: 'claude',
|
|
},
|
|
];
|
|
|
|
const state = buildManagedAgentState(
|
|
agents,
|
|
'my-claude-wrapper',
|
|
'claude',
|
|
{ path: '/usr/local/bin/claude', version: null, available: false },
|
|
);
|
|
|
|
assert.deepEqual(state.agents, agents);
|
|
assert.equal(state.defaultAgentId, 'my-claude-wrapper');
|
|
});
|
|
|
|
test('buildManagedAgentState only rewrites settings-managed discovered agents', () => {
|
|
const agents: ExternalAgentConfig[] = [
|
|
{
|
|
id: 'my-codex-wrapper',
|
|
name: 'My Codex Wrapper',
|
|
command: '/usr/local/bin/codex',
|
|
enabled: true,
|
|
sdkBackend: 'codex',
|
|
},
|
|
];
|
|
|
|
const state = buildManagedAgentState(
|
|
agents,
|
|
'my-codex-wrapper',
|
|
'codex',
|
|
{ path: '/opt/netcatty/codex', version: 'Bundled legacy adapter', available: true },
|
|
);
|
|
|
|
assert.deepEqual(
|
|
state.agents.map((agent) => agent.id),
|
|
['my-codex-wrapper', 'discovered_codex'],
|
|
);
|
|
assert.equal(state.agents[0], agents[0]);
|
|
assert.equal(state.defaultAgentId, 'my-codex-wrapper');
|
|
});
|