agent-ecosystem/test/renderer/utils/memberLaunchDiagnostics.test.ts
777genius 6e4f8ff8c4 fix: stabilize opencode mcp transport refresh
Keep OpenCode app MCP transport evidence durable, refresh stale sessions without consuming normal delivery attempts, and keep recoverable runtime diagnostics out of member card errors.

Cover stable MCP restart/fallback, forced session refresh, resolved_behavior_changed recovery, and renderer diagnostics with regression and safe e2e tests.
2026-05-18 13:08:34 +03:00

241 lines
8.9 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
buildMemberLaunchDiagnosticsPayload,
formatMemberLaunchDiagnosticsPayload,
hasMemberLaunchDiagnosticsDetails,
getMemberLaunchDiagnosticsErrorMessage,
} from '@renderer/utils/memberLaunchDiagnostics';
describe('member launch diagnostics', () => {
it('builds a bounded copy payload from spawn and runtime evidence', () => {
const payload = buildMemberLaunchDiagnosticsPayload({
teamName: 'demo-team',
runId: 'run-42',
memberName: 'bob',
spawnEntry: {
status: 'waiting',
launchState: 'runtime_pending_bootstrap',
runtimeAlive: false,
bootstrapConfirmed: false,
hardFailure: false,
agentToolAccepted: true,
livenessKind: 'shell_only',
livenessSource: 'process',
runtimeDiagnostic: 'tmux pane foreground command is zsh',
runtimeDiagnosticSeverity: 'warning',
updatedAt: '2026-04-24T12:00:00.000Z',
},
runtimeEntry: {
memberName: 'bob',
alive: false,
restartable: true,
pid: 26676,
pidSource: 'tmux_pane',
paneId: '%42',
panePid: 26676,
paneCurrentCommand: 'zsh',
processCommand: 'node runtime --token super-secret --team-name demo-team',
diagnostics: ['tmux pane foreground command is zsh', 'no runtime child found'],
updatedAt: '2026-04-24T12:00:01.000Z',
},
});
expect(payload).toMatchObject({
teamName: 'demo-team',
runId: 'run-42',
memberName: 'bob',
launchState: 'runtime_pending_bootstrap',
spawnStatus: 'waiting',
livenessKind: 'shell_only',
pid: 26676,
pidSource: 'tmux_pane',
paneCurrentCommand: 'zsh',
runtimeDiagnostic: 'tmux pane foreground command is zsh',
runtimeDiagnosticSeverity: 'warning',
});
expect(payload.processCommand).toContain('--token [redacted]');
expect(payload.processCommand).not.toContain('super-secret');
expect(payload.diagnostics).toEqual([
'tmux pane foreground command is zsh',
'no runtime child found',
]);
expect(hasMemberLaunchDiagnosticsDetails(payload)).toBe(true);
expect(formatMemberLaunchDiagnosticsPayload(payload)).toContain('"livenessKind": "shell_only"');
});
it('includes the exact normalized member card error in copy diagnostics', () => {
const payload = buildMemberLaunchDiagnosticsPayload({
memberName: 'jack',
spawnEntry: {
status: 'error',
launchState: 'failed_to_start',
hardFailure: true,
hardFailureReason:
'Latest assistant message msg_123 failed with APIError - OpenCode quota exhausted. Visit https://openrouter.ai/settings/keys',
runtimeDiagnostic: 'persisted runtime pid is not alive',
runtimeDiagnosticSeverity: 'error',
updatedAt: '2026-05-08T12:00:00.000Z',
},
});
expect(payload.memberCardError).toBe(
'OpenCode quota exhausted. Visit https://openrouter.ai/settings/keys'
);
expect(payload.diagnostics?.[0]).toBe(
'OpenCode quota exhausted. Visit https://openrouter.ai/settings/keys'
);
expect(getMemberLaunchDiagnosticsErrorMessage(payload)).toBe(
'OpenCode quota exhausted. Visit https://openrouter.ai/settings/keys'
);
expect(formatMemberLaunchDiagnosticsPayload(payload)).toContain('"memberCardError"');
});
it('includes runtime advisory evidence in copy diagnostics', () => {
const payload = buildMemberLaunchDiagnosticsPayload({
memberName: 'alice',
runtimeAdvisoryLabel: 'OpenCode delivery error',
runtimeAdvisoryTitle: 'OpenCode accepted the prompt, but no assistant turn was recorded.',
runtimeAdvisory: {
kind: 'api_error',
observedAt: '2026-05-17T22:11:38.239Z',
reasonCode: 'backend_error',
message: 'OpenCode accepted the prompt, but no assistant turn was recorded.',
},
});
expect(payload.memberCardError).toBe(
'OpenCode accepted the prompt, but no assistant turn was recorded.'
);
expect(payload.runtimeAdvisoryKind).toBe('api_error');
expect(payload.runtimeAdvisoryReasonCode).toBe('backend_error');
expect(payload.diagnostics).toContain(
'OpenCode accepted the prompt, but no assistant turn was recorded.'
);
expect(hasMemberLaunchDiagnosticsDetails(payload)).toBe(true);
});
it('does not turn healthy info liveness diagnostics into member card errors', () => {
const payload = buildMemberLaunchDiagnosticsPayload({
teamName: 'atlas-hq-5',
runId: '5a9ee2e5-a8cb-4559-b624-0dbf13ee4d11',
memberName: 'atlas',
spawnEntry: {
status: 'online',
launchState: 'confirmed_alive',
runtimeAlive: true,
bootstrapConfirmed: true,
hardFailure: false,
agentToolAccepted: true,
livenessKind: 'runtime_process',
livenessSource: 'heartbeat',
runtimeDiagnostic: 'OpenCode runtime process detected after bootstrap confirmation',
runtimeDiagnosticSeverity: 'info',
updatedAt: '2026-05-18T08:13:23.902Z',
},
runtimeEntry: {
memberName: 'atlas',
providerId: 'opencode',
alive: true,
restartable: false,
livenessKind: 'runtime_process',
runtimeDiagnostic: 'OpenCode runtime process detected after bootstrap confirmation',
runtimeDiagnosticSeverity: 'info',
diagnostics: [
'OpenCode runtime process detected after bootstrap confirmation',
'matched OpenCode runtime pid and process identity',
'bootstrap confirmed',
],
updatedAt: '2026-05-18T08:34:47.845Z',
},
});
expect(payload.memberCardError).toBeUndefined();
expect(payload.runtimeDiagnostic).toBe(
'OpenCode runtime process detected after bootstrap confirmation'
);
expect(payload.runtimeDiagnosticSeverity).toBe('info');
expect(payload.diagnostics).toContain(
'OpenCode runtime process detected after bootstrap confirmation'
);
});
it('prefers advisory errors over healthy info liveness diagnostics', () => {
const payload = buildMemberLaunchDiagnosticsPayload({
memberName: 'atlas',
runtimeAdvisoryLabel: 'OpenCode delivery error',
runtimeAdvisoryTitle:
'OpenCode runtime delivery error. OpenCode accepted the prompt, but no assistant turn was recorded.',
spawnEntry: {
status: 'online',
launchState: 'confirmed_alive',
runtimeAlive: true,
bootstrapConfirmed: true,
hardFailure: false,
livenessKind: 'runtime_process',
runtimeDiagnostic: 'OpenCode runtime process detected after bootstrap confirmation',
runtimeDiagnosticSeverity: 'info',
updatedAt: '2026-05-18T08:13:23.902Z',
},
runtimeAdvisory: {
kind: 'api_error',
observedAt: '2026-05-18T08:31:46.075Z',
reasonCode: 'backend_error',
message: 'OpenCode accepted the prompt, but no assistant turn was recorded.',
},
});
expect(payload.memberCardError).toBe(
'OpenCode runtime delivery error. OpenCode accepted the prompt, but no assistant turn was recorded.'
);
expect(payload.memberCardError).not.toBe(
'OpenCode runtime process detected after bootstrap confirmation'
);
});
it('does not surface recoverable OpenCode session refresh advisory as card error', () => {
const payload = buildMemberLaunchDiagnosticsPayload({
memberName: 'tom',
runtimeAdvisoryLabel: 'OpenCode session refresh',
runtimeAdvisoryTitle: 'OpenCode session changed; refreshing the session before retry.',
spawnEntry: {
status: 'online',
launchState: 'confirmed_alive',
runtimeAlive: true,
bootstrapConfirmed: true,
hardFailure: false,
runtimeDiagnostic: 'OpenCode runtime process detected after bootstrap confirmation',
runtimeDiagnosticSeverity: 'info',
updatedAt: '2026-05-18T08:13:23.902Z',
},
runtimeAdvisory: {
kind: 'api_error',
observedAt: '2026-05-18T08:31:46.075Z',
reasonCode: 'backend_error',
message: 'OpenCode session changed; refreshing the session before retry.',
},
});
expect(payload.memberCardError).toBeUndefined();
expect(payload.diagnostics).toContain(
'OpenCode session changed; refreshing the session before retry.'
);
});
it('does not surface recoverable OpenCode transport refresh advisory as card error', () => {
const payload = buildMemberLaunchDiagnosticsPayload({
memberName: 'tom',
runtimeAdvisoryLabel: 'OpenCode session refresh',
runtimeAdvisoryTitle:
'OpenCode session changed; refreshing the session before retry.',
runtimeAdvisory: {
kind: 'api_error',
observedAt: '2026-05-18T08:31:46.075Z',
reasonCode: 'backend_error',
message: 'opencode_app_mcp_transport_changed:old->new',
},
});
expect(payload.memberCardError).toBeUndefined();
});
});