agent-ecosystem/test/main/services/team/TeamRuntimeLivenessResolver.test.ts
2026-05-31 07:47:16 +03:00

298 lines
9.8 KiB
TypeScript

import {
commandArgEquals,
extractCliArgValues,
resolveTeamMemberRuntimeLiveness,
sanitizeProcessCommandForDiagnostics,
} from '@main/services/team/TeamRuntimeLivenessResolver';
import { describe, expect, it } from 'vitest';
const NOW = '2026-04-24T12:00:00.000Z';
describe('resolveTeamMemberRuntimeLiveness', () => {
it('classifies tmux shell panes as weak shell-only evidence', () => {
const result = resolveTeamMemberRuntimeLiveness({
teamName: 'demo',
memberName: 'bob',
agentId: 'agent-bob',
backendType: 'tmux',
tmuxPaneId: '%1',
pane: { paneId: '%1', panePid: 100, currentCommand: 'zsh' },
processRows: [{ pid: 100, ppid: 1, command: 'zsh' }],
processTableAvailable: true,
nowIso: NOW,
});
expect(result.alive).toBe(false);
expect(result.livenessKind).toBe('shell_only');
expect(result.pidSource).toBe('tmux_pane');
expect(result.pid).toBe(100);
});
it('promotes a verified team and agent process to strong runtime evidence', () => {
const result = resolveTeamMemberRuntimeLiveness({
teamName: 'demo',
memberName: 'alice',
agentId: 'agent-alice',
backendType: 'tmux',
processRows: [
{
pid: 222,
ppid: 1,
command: 'node runtime --team-name demo --agent-id agent-alice',
},
],
processTableAvailable: true,
nowIso: NOW,
});
expect(result.alive).toBe(true);
expect(result.livenessKind).toBe('runtime_process');
expect(result.pidSource).toBe('agent_process_table');
expect(result.pid).toBe(222);
});
it('uses the newest verified team and agent process without requiring sorted rows', () => {
const result = resolveTeamMemberRuntimeLiveness({
teamName: 'demo',
memberName: 'alice',
agentId: 'agent-alice',
backendType: 'tmux',
processRows: [
{
pid: 222,
ppid: 1,
command: 'node runtime --team-name demo --agent-id agent-alice',
},
{
pid: 111,
ppid: 1,
command: 'node runtime --team-name demo --agent-id agent-alice',
},
{
pid: 333,
ppid: 1,
command: 'node runtime --team-name other --agent-id agent-alice',
},
],
processTableAvailable: true,
nowIso: NOW,
});
expect(result.alive).toBe(true);
expect(result.livenessKind).toBe('runtime_process');
expect(result.pidSource).toBe('agent_process_table');
expect(result.pid).toBe(222);
});
it('keeps a verified process pid visible after bootstrap is confirmed', () => {
const result = resolveTeamMemberRuntimeLiveness({
teamName: 'demo',
memberName: 'alice',
agentId: 'agent-alice',
backendType: 'tmux',
trackedSpawnStatus: {
status: 'online',
launchState: 'confirmed_alive',
agentToolAccepted: true,
runtimeAlive: true,
bootstrapConfirmed: true,
hardFailure: false,
updatedAt: NOW,
},
processRows: [
{
pid: 222,
ppid: 1,
command: 'node runtime --team-name demo --agent-id agent-alice',
},
],
processTableAvailable: true,
nowIso: NOW,
});
expect(result.alive).toBe(true);
expect(result.livenessKind).toBe('runtime_process');
expect(result.pidSource).toBe('agent_process_table');
expect(result.pid).toBe(222);
});
it('keeps a non-shell tmux descendant without identity as a candidate', () => {
const result = resolveTeamMemberRuntimeLiveness({
teamName: 'demo',
memberName: 'jack',
agentId: 'agent-jack',
backendType: 'tmux',
tmuxPaneId: '%2',
pane: { paneId: '%2', panePid: 300, currentCommand: 'zsh' },
processRows: [
{ pid: 300, ppid: 1, command: 'zsh' },
{ pid: 301, ppid: 300, command: 'node helper.js' },
],
processTableAvailable: true,
nowIso: NOW,
});
expect(result.alive).toBe(false);
expect(result.livenessKind).toBe('runtime_process_candidate');
expect(result.pidSource).toBe('tmux_child');
expect(result.pid).toBe(301);
});
it('keeps a live OpenCode runtime pid as candidate until bootstrap is confirmed', () => {
const result = resolveTeamMemberRuntimeLiveness({
teamName: 'demo',
memberName: 'bob',
providerId: 'opencode',
persistedRuntimePid: 404,
persistedRuntimeSessionId: 'session-bob',
processRows: [{ pid: 404, ppid: 1, command: 'opencode runtime host' }],
processTableAvailable: true,
nowIso: NOW,
});
expect(result.alive).toBe(false);
expect(result.livenessKind).toBe('runtime_process_candidate');
expect(result.pidSource).toBe('opencode_bridge');
expect(result.pid).toBe(404);
expect(result.runtimeDiagnostic).toBe(
'OpenCode runtime process detected, but teammate bootstrap is not confirmed'
);
});
it('promotes a live OpenCode runtime pid after bootstrap confirmation', () => {
const result = resolveTeamMemberRuntimeLiveness({
teamName: 'demo',
memberName: 'bob',
providerId: 'opencode',
persistedRuntimePid: 404,
persistedRuntimeSessionId: 'session-bob',
trackedSpawnStatus: {
status: 'online',
launchState: 'confirmed_alive',
agentToolAccepted: true,
runtimeAlive: true,
bootstrapConfirmed: true,
hardFailure: false,
updatedAt: NOW,
},
processRows: [{ pid: 404, ppid: 1, command: 'opencode runtime host' }],
processTableAvailable: true,
nowIso: NOW,
});
expect(result.alive).toBe(true);
expect(result.livenessKind).toBe('runtime_process');
expect(result.pidSource).toBe('opencode_bridge');
expect(result.pid).toBe(404);
});
it('does not trust an OpenCode runtime pid reused by an unrelated process', () => {
const result = resolveTeamMemberRuntimeLiveness({
teamName: 'demo',
memberName: 'bob',
providerId: 'opencode',
persistedRuntimePid: 404,
persistedRuntimeSessionId: 'session-bob',
processRows: [{ pid: 404, ppid: 1, command: 'node unrelated-worker.js' }],
processTableAvailable: true,
nowIso: NOW,
});
expect(result.alive).toBe(false);
expect(result.livenessKind).toBe('runtime_process_candidate');
expect(result.pidSource).toBe('opencode_bridge');
expect(result.runtimeDiagnostic).toBe(
'OpenCode runtime pid is alive, but process identity is unverified'
);
});
it('does not let a reused OpenCode runtime pid downgrade committed bootstrap evidence', () => {
const result = resolveTeamMemberRuntimeLiveness({
teamName: 'demo',
memberName: 'bob',
providerId: 'opencode',
persistedRuntimePid: 404,
persistedRuntimeSessionId: 'session-bob',
trackedSpawnStatus: {
status: 'online',
launchState: 'confirmed_alive',
agentToolAccepted: true,
runtimeAlive: true,
bootstrapConfirmed: true,
hardFailure: false,
updatedAt: NOW,
},
processRows: [{ pid: 404, ppid: 1, command: 'node unrelated-worker.js' }],
processTableAvailable: true,
nowIso: NOW,
});
expect(result.alive).toBe(true);
expect(result.livenessKind).toBe('confirmed_bootstrap');
expect(result.pidSource).toBe('runtime_bootstrap');
expect(result.pid).toBeUndefined();
expect(result.diagnostics).toContain(
'bootstrap confirmed despite runtime pid identity mismatch'
);
});
it('does not trust a stale persisted pid without current process identity', () => {
const result = resolveTeamMemberRuntimeLiveness({
teamName: 'demo',
memberName: 'tom',
persistedRuntimePid: 444,
processRows: [{ pid: 555, ppid: 1, command: 'node other.js' }],
processTableAvailable: true,
nowIso: NOW,
});
expect(result.alive).toBe(false);
expect(result.livenessKind).toBe('stale_metadata');
expect(result.pidSource).toBe('persisted_metadata');
});
it('does not treat a persisted pid as stale when the process table is unavailable', () => {
const result = resolveTeamMemberRuntimeLiveness({
teamName: 'demo',
memberName: 'tom',
persistedRuntimePid: 444,
processRows: [],
processTableAvailable: false,
nowIso: NOW,
});
expect(result.alive).toBe(false);
expect(result.livenessKind).toBe('registered_only');
expect(result.pidSource).toBe('persisted_metadata');
expect(result.diagnostics).toContain('process table unavailable');
});
it('redacts common secret flags in diagnostics commands', () => {
expect(
sanitizeProcessCommandForDiagnostics('node runtime --api-key sk-123 --token=abc --safe ok')
).toBe('node runtime --api-key [redacted] --token=[redacted] --safe ok');
});
it('keeps cached CLI arg extraction immutable for callers', () => {
const command =
'node runtime --team-name demo --agent-id "agent alice" --agent-id agent-bob';
const first = extractCliArgValues(command, '--agent-id');
first.push('mutated');
expect(extractCliArgValues(command, '--agent-id')).toEqual(['agent alice', 'agent-bob']);
expect(extractCliArgValues(command, '--team-name')).toEqual(['demo']);
});
it('returns no CLI arg values when the flag is absent', () => {
expect(extractCliArgValues('node runtime --other value', '--agent-id')).toEqual([]);
});
it('matches CLI arg values repeatedly without changing extraction results', () => {
const command = 'node runtime --team-name demo --agent-id "agent alice"';
expect(commandArgEquals(command, '--agent-id', 'agent alice')).toBe(true);
expect(commandArgEquals(command, '--agent-id', 'agent-bob')).toBe(false);
expect(commandArgEquals(command, '--agent-id', 'agent alice')).toBe(true);
expect(extractCliArgValues(command, '--agent-id')).toEqual(['agent alice']);
});
});