Files
fusion/packages/engine/src/__tests__/executor-assigned-agent-runtime-config.test.ts
gsxdsm 3fcab9fc1d fix: green full-suite after main product drift (#2275)
## Summary
Follow-up after #2266: full suite on latest `main` still failed on i18n
parity and engine product-drift tests (duplicate flagging, mission
validator signature, worktree path reservation, pi ModelRuntime,
assigned-agent PG layer).

## Changes
- **i18n**: add `listView/tasks.statusReplan` and mobile-nav settings
keys to es/fr/ko/zh-CN/zh-TW
- **triage**: opt-in `triageDuplicateResolution: "delete"` coverage +
default prompt/flag path; stub `recordActivity`
- **MCP PR response**: stub `getTask` for merger model resolution
- **pi-layers**: mock `ModelRuntime.create` (FN-8142)
- **assigned-agent**: stub `getAsyncLayer` for authoritative AgentStore
fallback
- **mission validation**: `startValidatorRun(..., taskId)` +
`runValidation` `{ result, inspection }` shape
- **worktree acquisition**: real temp roots for path reservation;
probe-aware worktrunk failure fixture
- **useBlockerFanout**: read `MAX_AUTO_MERGE_RETRIES` from
`self-healing-constants.ts` (wave-8 peel)

## Test plan
- [x] Targeted engine suites above (48 tests)
- [x] i18n parity + gate coverage
- [x] useBlockerFanout
- [x] `pnpm test:gate`
- [ ] Full Suite (non-blocking) on PR

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Localization**
* Added “Replan”/replanning status labels across Spanish, French,
Korean, Simplified Chinese, and Traditional Chinese.
* Completed settings mobile navigation translations, including primary
items controls and reordering options.

* **Tests**
* Expanded coverage for validation recovery and task forwarding
behavior.
* Improved duplicate-resolution scenarios (including activity recording
and lineage deletion behavior).
* Increased test reliability for worktree acquisition by using temporary
filesystem roots.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-17 17:27:04 -07:00

87 lines
4.0 KiB
TypeScript

import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { AgentStore } from "@fusion/core";
import { TaskExecutor } from "../executor.js";
import { resolveExecutorSessionModel } from "../agent-session-helpers.js";
function createStore() {
return {
on: vi.fn(),
getFusionDir: vi.fn(() => "/worktree/.fusion"),
/*
FNXC:EngineTests 2026-07-17-11:45:
getAuthoritativeAssignedAgent requires store.getAsyncLayer() so the fallback
AgentStore runs in PostgreSQL backend mode. A missing method throws, is
swallowed, and the lookup returns null — defeating the runtimeConfig spy.
*/
getAsyncLayer: vi.fn(() => ({ kind: "test-async-layer" })),
} as any;
}
describe("TaskExecutor assigned-agent runtimeConfig lookup", () => {
const roots: string[] = [];
afterEach(async () => {
vi.restoreAllMocks();
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
});
async function createHarness(runtimeConfig: Record<string, unknown> | undefined) {
const rootDir = await mkdtemp(join(tmpdir(), "fn-7787-"));
roots.push(rootDir);
/*
* FNXC:PostgresCutover 2026-07-10:
* Upstream seeded a real sqlite AgentStore; that runtime is removed on
* this branch (VAL-REMOVAL-005). The invariant under test is the
* EXECUTOR's fallback from an agents-less execution store to the
* authoritative project agent store, so spy the AgentStore prototype the
* executor constructs internally instead of persisting a real agent.
*/
const agent = {
id: `agent-${Math.random().toString(16).slice(2)}`,
name: `executor-${Math.random().toString(16).slice(2)}`,
role: "executor",
state: "idle",
...(runtimeConfig ? { runtimeConfig } : {}),
};
vi.spyOn(AgentStore.prototype, "init").mockResolvedValue(undefined);
vi.spyOn(AgentStore.prototype, "getAgent").mockImplementation(async (id: string) => (id === agent.id ? (agent as never) : null) as never);
const worktreeAgentStore = { getAgent: vi.fn().mockResolvedValue(null) };
const executor = new TaskExecutor(createStore(), rootDir, { agentStore: worktreeAgentStore } as any);
return { executor: executor as any, agent, worktreeAgentStore };
}
it.each([
[{ model: "anthropic/claude-fable-5", modelProvider: "ignored", modelId: "ignored" }, { provider: "anthropic", modelId: "claude-fable-5" }],
[{ modelProvider: "anthropic", modelId: "claude-fable-5" }, { provider: "anthropic", modelId: "claude-fable-5" }],
])("falls back from an agents-less execution store to the authoritative project agent runtimeConfig %#", async (runtimeConfig, expected) => {
const { executor, agent, worktreeAgentStore } = await createHarness(runtimeConfig);
const foundRuntimeConfig = await executor.getAssignedAgentRuntimeConfig(agent.id);
expect(worktreeAgentStore.getAgent).toHaveBeenCalledWith(agent.id);
expect(resolveExecutorSessionModel(undefined, undefined, {}, foundRuntimeConfig)).toEqual(expected);
});
it("returns undefined when the assigned agent is missing or has no complete runtime model", async () => {
const { executor, agent } = await createHarness(undefined);
expect(await executor.getAssignedAgentRuntimeConfig("missing-agent")).toBeUndefined();
const runtimeConfig = await executor.getAssignedAgentRuntimeConfig(agent.id);
expect(resolveExecutorSessionModel(undefined, undefined, {}, runtimeConfig)).toEqual({ provider: undefined, modelId: undefined });
});
it("keeps configured settings ahead of the assigned agent runtimeConfig", async () => {
const { executor, agent } = await createHarness({ model: "anthropic/claude-fable-5" });
const runtimeConfig = await executor.getAssignedAgentRuntimeConfig(agent.id);
expect(resolveExecutorSessionModel(undefined, undefined, {
executionProvider: "openai",
executionModelId: "gpt-4.1",
}, runtimeConfig)).toEqual({ provider: "openai", modelId: "gpt-4.1" });
});
});