feat(FN-4153): complete Step 4 — block ephemeral spawn when disabled
Fusion-Task-Id: FN-4153 Fusion-Task-Lineage: 6ec09467-c7a0-4a9d-9a09-16d5ef6f7a86
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
* lookup on creation, the on-disk fallback on completion, and the startup
|
||||
* sweep here close that gap.
|
||||
*/
|
||||
import type { AgentStore, AgentState, Agent, TaskStore, Task } from "@fusion/core";
|
||||
import type { AgentStore, AgentState, Agent, TaskStore, Task, Settings } from "@fusion/core";
|
||||
import { isEphemeralAgent } from "@fusion/core";
|
||||
|
||||
export interface TaskOwner {
|
||||
@@ -43,6 +43,7 @@ export interface EphemeralWorkerManagerOptions {
|
||||
* don't race the executor on the same agentId.
|
||||
*/
|
||||
isDeletionPendingExternal?: (agentId: string) => boolean;
|
||||
getSettings?: () => Promise<Pick<Settings, "ephemeralAgentsEnabled">>;
|
||||
}
|
||||
|
||||
const TERMINAL_TASK_COLUMNS = new Set<Task["column"]>(["done", "archived"]);
|
||||
@@ -52,6 +53,7 @@ export class EphemeralWorkerManager {
|
||||
private readonly taskStore: TaskStore;
|
||||
private readonly log: EphemeralWorkerLogger;
|
||||
private readonly isDeletionPendingExternal: (agentId: string) => boolean;
|
||||
private readonly getSettings: () => Promise<Pick<Settings, "ephemeralAgentsEnabled">>;
|
||||
|
||||
/** taskId → owner. In-memory only; on-disk fallback covers restart gaps. */
|
||||
private readonly taskAgentMap = new Map<string, TaskOwner>();
|
||||
@@ -65,6 +67,7 @@ export class EphemeralWorkerManager {
|
||||
this.taskStore = options.taskStore;
|
||||
this.log = options.logger;
|
||||
this.isDeletionPendingExternal = options.isDeletionPendingExternal ?? (() => false);
|
||||
this.getSettings = options.getSettings ?? (async () => ({ ephemeralAgentsEnabled: true }));
|
||||
}
|
||||
|
||||
// ── public surface ───────────────────────────────────────────────────────
|
||||
@@ -125,6 +128,14 @@ export class EphemeralWorkerManager {
|
||||
}
|
||||
}
|
||||
|
||||
const settings = await this.getSettings();
|
||||
if (settings.ephemeralAgentsEnabled === false) {
|
||||
this.log.warn(
|
||||
`Task ${task.id} has no permanent agent assignment; ephemeralAgentsEnabled=false — refusing to spawn ephemeral worker`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const agent = await this.agentStore.createAgent({
|
||||
name: `executor-${task.id}`,
|
||||
role: "executor",
|
||||
|
||||
@@ -754,6 +754,30 @@ describe("InProcessRuntime", () => {
|
||||
expect(assignTaskSpy.mock.invocationCallOrder[0]).toBeLessThan(updateStateSpy.mock.invocationCallOrder[0]);
|
||||
}, 30000);
|
||||
|
||||
it("does not spawn runtime task-worker agents when ephemeral agents are disabled", async () => {
|
||||
mockTaskStoreSettings.ephemeralAgentsEnabled = false;
|
||||
await runtime.start();
|
||||
|
||||
const store = getAgentStore(runtime);
|
||||
const createAgentSpy = vi.spyOn(store, "createAgent");
|
||||
const warnSpy = vi.spyOn(runtimeLog, "warn");
|
||||
|
||||
const executorOptions = mockExecutorCtor.mock.calls.at(-1)?.[0] as {
|
||||
onStart?: (task: Task, worktreePath: string) => void;
|
||||
};
|
||||
executorOptions.onStart?.({ id: "FN-1663" } as Task, join(testDir, "worktree-FN-1663"));
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Task FN-1663 has no permanent agent assignment; ephemeralAgentsEnabled=false"),
|
||||
);
|
||||
});
|
||||
|
||||
const agents = await store.listAgents({ includeEphemeral: true });
|
||||
expect(agents.some((agent: Agent) => agent.name === "executor-FN-1663")).toBe(false);
|
||||
expect(createAgentSpy).not.toHaveBeenCalledWith(expect.objectContaining({ name: "executor-FN-1663" }));
|
||||
}, 30000);
|
||||
|
||||
it("falls back to runtime task-worker when assignedAgentId points to ephemeral agent", async () => {
|
||||
await runtime.start();
|
||||
|
||||
|
||||
@@ -530,6 +530,10 @@ export class InProcessRuntime
|
||||
taskStore: this.taskStore,
|
||||
logger: runtimeLog,
|
||||
isDeletionPendingExternal: (agentId) => this.executor?.isEphemeralDeletionPending(agentId) ?? false,
|
||||
getSettings: async () => {
|
||||
const settings = await this.taskStore.getSettings();
|
||||
return { ephemeralAgentsEnabled: settings.ephemeralAgentsEnabled };
|
||||
},
|
||||
});
|
||||
}
|
||||
if (this.workerManager) {
|
||||
|
||||
Reference in New Issue
Block a user