eigent/test/unit/components/ChatBox/runControlArbitration.test.ts

209 lines
5.9 KiB
TypeScript

// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
import {
selectActionableInterruptedRun,
selectEventNativeActiveRunId,
} from '@/components/ChatBox/runControlArbitration';
import { createProjectViewState, type ProjectedRun } from '@/lib/projector';
import {
createChatProjectionState,
type ChatProjectionNode,
} from '@/lib/projector/chat';
import {
createHumanControlProjectionState,
type HumanControlInteraction,
} from '@/lib/projector/control';
import type { ProjectEventStoreSnapshot } from '@/store/projectEventStore';
import { describe, expect, it } from 'vitest';
function run(
runId: string,
status: ProjectedRun['status'],
overrides: Partial<ProjectedRun> = {}
): ProjectedRun {
return {
runId,
status,
lastSequence: 2,
runVersion: 2,
updatedAt: '2026-08-12T10:00:00.000Z',
origin: 'local',
resumeBlockedReason: null,
...overrides,
};
}
function canonicalNode(runId: string, eventType = 'run.attempt_started') {
return {
id: `${runId}:${eventType}`,
eventId: `${runId}:${eventType}`,
projectId: 'project-1',
runId,
createdAt: '2026-08-12T10:00:00.000Z',
runSequence: 1,
cloudCursor: null,
eventType,
legacyStep: null,
kind: 'run_status',
status: 'running',
} as ChatProjectionNode;
}
function request(
interactionId: string,
runId: string,
requestEventType = 'interaction.requested'
) {
return {
interactionId,
runId,
status: 'requested',
requestSource: 'canonical',
requestEventId: `${runId}:${requestEventType}`,
requestEventType,
} as HumanControlInteraction;
}
function snapshot({
runs,
nodes = [],
controls = [],
needsResync = false,
eventsTruncated = false,
overflowed = false,
}: {
runs: ProjectedRun[];
nodes?: ChatProjectionNode[];
controls?: HumanControlInteraction[];
needsResync?: boolean;
eventsTruncated?: boolean;
overflowed?: boolean;
}): ProjectEventStoreSnapshot {
const view = createProjectViewState('project-1', 'live');
const chat = createChatProjectionState('project-1');
const control = createHumanControlProjectionState('project-1');
return {
view: {
...view,
needsResync,
eventsTruncated,
runs: Object.fromEntries(runs.map((item) => [item.runId, item])),
},
chat: {
...chat,
nodes,
nodeById: Object.fromEntries(nodes.map((node) => [node.eventId, node])),
},
control: {
...control,
orderedInteractionIds: controls.map((item) => item.interactionId),
interactionById: Object.fromEntries(
controls.map((item) => [item.interactionId, item])
),
},
revision: 1,
hasHydratedSnapshot: true,
overflowed,
lastEffects: [],
};
}
describe('event-native Run-control arbitration', () => {
it('lets a typed pending control outrank the legacy-owned live Run', () => {
const state = snapshot({
runs: [run('legacy-live', 'running'), run('input', 'waiting_for_user')],
nodes: [canonicalNode('legacy-live')],
controls: [request('question-1', 'input')],
});
expect(selectEventNativeActiveRunId(state, 'legacy-live')).toBe('input');
});
it('does not give aggregate-only or orphan historical Runs controls', () => {
expect(
selectEventNativeActiveRunId(
snapshot({ runs: [run('past', 'running')] }),
'past'
)
).toBeNull();
expect(
selectEventNativeActiveRunId(
snapshot({
runs: [run('past', 'running')],
nodes: [canonicalNode('past')],
}),
null
)
).toBeNull();
});
it('fails closed for truncated, resyncing, overflowed, and gapped state', () => {
const pending = run('input', 'waiting_for_user');
const controls = [request('question-1', 'input')];
expect(
selectEventNativeActiveRunId(
snapshot({ runs: [pending], controls, eventsTruncated: true }),
null
)
).toBeNull();
expect(
selectEventNativeActiveRunId(
snapshot({ runs: [pending], controls, needsResync: true }),
null
)
).toBeNull();
expect(
selectEventNativeActiveRunId(
snapshot({ runs: [pending], controls, overflowed: true }),
null
)
).toBeNull();
expect(
selectEventNativeActiveRunId(
snapshot({
runs: [run('input', 'waiting_for_user', { lastSequence: 1 })],
controls,
}),
null
)
).toBeNull();
});
it('does not treat a canonical-envelope legacy ASK as typed authority', () => {
const state = snapshot({
runs: [run('input', 'waiting_for_user')],
controls: [request('question-1', 'input', 'legacy.step')],
});
expect(selectEventNativeActiveRunId(state, null)).toBeNull();
});
it('allows only evidenced, actionable local interruptions', () => {
const local = run('local', 'interrupted');
const blocked = run('blocked', 'interrupted', {
resumeBlockedReason: 'local_workspace_missing',
});
const state = snapshot({
runs: [local, blocked],
nodes: [
canonicalNode(local.runId, 'run.interrupted'),
canonicalNode(blocked.runId, 'run.interrupted'),
],
});
expect(selectActionableInterruptedRun(state, local.runId)).toBe(local);
expect(selectActionableInterruptedRun(state, blocked.runId)).toBeNull();
});
});