chore(engine): remove workflow-step mock routing + stale FN-5482 docs
Drops the "workflow-step" MockSessionPurpose enum value and the workflowStepId / workflowStepTemplateId plumbing through agent-runtime, agent-session-helpers, mock-provider, executor, and merger. The seeded-workflow-prompts script loses its FN-5205 rationale comment + test (no longer applicable now that workflow steps run through the regular session purposes). Also strips the stale FN-5482 architecture-invariant bullet from AGENTS.md and the corresponding audit-event line from docs/architecture.md (the self-healing reclaim invariant they described no longer holds). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -126,26 +126,6 @@ describe("createResolvedAgentSession", () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards workflow step runtime context fields to mock sessions", async () => {
|
||||
const { createResolvedAgentSession } = await import("../agent-session-helpers.js");
|
||||
|
||||
const result = await createResolvedAgentSession({
|
||||
sessionPurpose: "executor",
|
||||
pluginRunner: undefined,
|
||||
cwd: "/tmp/project",
|
||||
systemPrompt: "system",
|
||||
defaultProvider: "mock",
|
||||
runtimeContext: {
|
||||
workflowStepId: "WS-004",
|
||||
workflowStepTemplateId: "browser-verification",
|
||||
},
|
||||
});
|
||||
|
||||
const mockMeta = (result.session as unknown as { __mock?: { workflowStepId?: string; workflowStepTemplateId?: string } }).__mock;
|
||||
expect(mockMeta?.workflowStepId).toBe("WS-004");
|
||||
expect(mockMeta?.workflowStepTemplateId).toBe("browser-verification");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveMergerSessionModel", () => {
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
clearMockScript,
|
||||
resetMockScripts,
|
||||
setMockScript,
|
||||
resolveMockScript,
|
||||
} from "../providers/mock-provider.js";
|
||||
|
||||
function createTool(name: string, execute = vi.fn().mockResolvedValue({ content: [], details: {} })): ToolDefinition {
|
||||
@@ -68,7 +67,6 @@ describe("MockAgentRuntime", () => {
|
||||
["merger", []],
|
||||
["heartbeat", []],
|
||||
["validation", []],
|
||||
["workflow-step", []],
|
||||
] as const)("runs the default %s script deterministically", async (sessionPurpose, expectedCalls) => {
|
||||
const runtime = new MockAgentRuntime();
|
||||
const { cwd, taskDir, taskId } = await createWorkspace();
|
||||
@@ -130,34 +128,6 @@ describe("MockAgentRuntime", () => {
|
||||
if (sessionPurpose === "reviewer" || sessionPurpose === "validation") {
|
||||
expect(onText).toHaveBeenCalledWith(expect.stringContaining("Verdict: APPROVE"));
|
||||
}
|
||||
if (sessionPurpose === "workflow-step") {
|
||||
expect(onText).toHaveBeenCalledWith(expect.stringContaining('{"verdict":"APPROVE","notes":""}'));
|
||||
}
|
||||
});
|
||||
|
||||
it("resolves mock script overrides by specificity precedence", async () => {
|
||||
const defaultScript = resolveMockScript({ sessionPurpose: "workflow-step" });
|
||||
const purposeOnly = { run: vi.fn(async () => undefined) };
|
||||
const templateOnly = { run: vi.fn(async () => undefined) };
|
||||
const taskOnly = { run: vi.fn(async () => undefined) };
|
||||
const taskAndTemplate = { run: vi.fn(async () => undefined) };
|
||||
|
||||
setMockScript({ sessionPurpose: "workflow-step" }, purposeOnly);
|
||||
setMockScript({ sessionPurpose: "workflow-step", workflowStepTemplateId: "browser-verification" }, templateOnly);
|
||||
setMockScript({ sessionPurpose: "workflow-step", taskId: "FN-1" }, taskOnly);
|
||||
setMockScript({ sessionPurpose: "workflow-step", taskId: "FN-1", workflowStepTemplateId: "browser-verification" }, taskAndTemplate);
|
||||
|
||||
expect(resolveMockScript({ sessionPurpose: "workflow-step", taskId: "FN-1", workflowStepTemplateId: "browser-verification" })).toBe(taskAndTemplate);
|
||||
expect(resolveMockScript({ sessionPurpose: "workflow-step", taskId: "FN-1", workflowStepTemplateId: "other-template" })).toBe(taskOnly);
|
||||
expect(resolveMockScript({ sessionPurpose: "workflow-step", taskId: "FN-2", workflowStepTemplateId: "browser-verification" })).toBe(templateOnly);
|
||||
expect(resolveMockScript({ sessionPurpose: "workflow-step", taskId: "FN-2", workflowStepTemplateId: "other-template" })).toBe(purposeOnly);
|
||||
expect(resolveMockScript({ sessionPurpose: "workflow-step" })).toBe(purposeOnly);
|
||||
|
||||
clearMockScript({ sessionPurpose: "workflow-step", taskId: "FN-1", workflowStepTemplateId: "browser-verification" });
|
||||
clearMockScript({ sessionPurpose: "workflow-step", taskId: "FN-1" });
|
||||
clearMockScript({ sessionPurpose: "workflow-step", workflowStepTemplateId: "browser-verification" });
|
||||
clearMockScript({ sessionPurpose: "workflow-step" });
|
||||
expect(resolveMockScript({ sessionPurpose: "workflow-step" })).toBe(defaultScript);
|
||||
});
|
||||
|
||||
it("prefers a task-scoped override over the default script", async () => {
|
||||
@@ -230,7 +200,7 @@ describe("MockAgentRuntime", () => {
|
||||
throw new Error("https.request should not be called");
|
||||
});
|
||||
|
||||
for (const sessionPurpose of ["executor", "triage", "reviewer", "merger", "heartbeat", "validation", "workflow-step"] as const) {
|
||||
for (const sessionPurpose of ["executor", "triage", "reviewer", "merger", "heartbeat", "validation"] as const) {
|
||||
const { cwd, taskId } = await createWorkspace(`FN-${sessionPurpose}`);
|
||||
const { session } = await runtime.createSession({
|
||||
cwd,
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import "./executor-test-helpers.js";
|
||||
import { TaskExecutor, parseWorkflowStepVerdict } from "../executor.js";
|
||||
import { mockedCreateFnAgent, createMockStore, resetExecutorMocks } from "./executor-test-helpers.js";
|
||||
|
||||
function buildTask() {
|
||||
return {
|
||||
id: "FN-5205",
|
||||
title: "Workflow test",
|
||||
description: "",
|
||||
column: "in-progress" as const,
|
||||
dependencies: [],
|
||||
steps: [{ name: "Preflight", status: "done" as const }],
|
||||
currentStep: 0,
|
||||
log: [],
|
||||
enabledWorkflowSteps: ["WS-004"],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
function buildStep() {
|
||||
return {
|
||||
id: "WS-004",
|
||||
templateId: "browser-verification",
|
||||
name: "Browser Verification",
|
||||
mode: "prompt",
|
||||
toolMode: "readonly",
|
||||
prompt: "verify",
|
||||
gateMode: "gate",
|
||||
enabled: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
function scriptedSession(output: string) {
|
||||
const subscribers: Array<(event: any) => void> = [];
|
||||
return {
|
||||
state: {},
|
||||
subscribe: (cb: (event: any) => void) => subscribers.push(cb),
|
||||
prompt: vi.fn(async () => {
|
||||
subscribers.forEach((cb) => cb({ type: "message_update", assistantMessageEvent: { type: "text_delta", delta: output } }));
|
||||
}),
|
||||
dispose: vi.fn(),
|
||||
getSessionStats: () => ({ tokens: { input: 100, output: 50, cacheRead: 0, cacheWrite: 0 } }),
|
||||
};
|
||||
}
|
||||
|
||||
describe("workflow-step test mode routing", () => {
|
||||
beforeEach(() => {
|
||||
resetExecutorMocks();
|
||||
});
|
||||
|
||||
it("FN-5205 test mode browser verification defaults to APPROVE", async () => {
|
||||
const store = createMockStore();
|
||||
const task = buildTask();
|
||||
store.getTask.mockResolvedValue(task as any);
|
||||
store.getWorkflowStep.mockResolvedValue(buildStep() as any);
|
||||
vi.spyOn(TaskExecutor.prototype as any, "captureModifiedFiles").mockResolvedValue([]);
|
||||
|
||||
mockedCreateFnAgent.mockResolvedValue({ session: scriptedSession('{"verdict":"APPROVE","notes":""}\n') as any, sessionFile: undefined } as any);
|
||||
|
||||
const executor = new TaskExecutor(store as any, "/tmp/test", {} as any);
|
||||
const result = await (executor as any).runWorkflowSteps(task as any, "/tmp/test", { testMode: true, defaultProvider: "anthropic", defaultModelId: "claude-sonnet-4-5" });
|
||||
|
||||
expect(result.allPassed).toBe(true);
|
||||
const args = mockedCreateFnAgent.mock.calls.at(-1)?.[0] as any;
|
||||
expect(args?.defaultProvider).toBe("mock");
|
||||
expect(args?.defaultModelId).toBe("scripted");
|
||||
expect(args?.runtimeContext).toEqual({ workflowStepId: "WS-004", workflowStepTemplateId: "browser-verification" });
|
||||
const parsed = parseWorkflowStepVerdict('{"verdict":"APPROVE","notes":""}');
|
||||
expect(parsed?.verdict).toBe("APPROVE");
|
||||
});
|
||||
|
||||
it("test mode per-template REVISE override blocks merge path", async () => {
|
||||
const store = createMockStore();
|
||||
const task = buildTask();
|
||||
store.getTask.mockResolvedValue(task as any);
|
||||
store.getWorkflowStep.mockResolvedValue(buildStep() as any);
|
||||
vi.spyOn(TaskExecutor.prototype as any, "captureModifiedFiles").mockResolvedValue([]);
|
||||
|
||||
mockedCreateFnAgent.mockResolvedValue({ session: scriptedSession('{"verdict":"REVISE","notes":"forced FAIL via FN-5205 override"}\n') as any, sessionFile: undefined } as any);
|
||||
|
||||
const executor = new TaskExecutor(store as any, "/tmp/test", {} as any);
|
||||
const result = await (executor as any).runWorkflowSteps(task as any, "/tmp/test", { testMode: true, defaultProvider: "anthropic", defaultModelId: "claude-sonnet-4-5" });
|
||||
|
||||
expect(result).toEqual(expect.objectContaining({ allPassed: false, revisionRequested: true, stepName: "Browser Verification" }));
|
||||
expect(String((result as any).feedback)).toContain("forced FAIL");
|
||||
});
|
||||
|
||||
it("test mode off preserves real-provider selection while forwarding template context", async () => {
|
||||
const store = createMockStore();
|
||||
const task = buildTask();
|
||||
store.getTask.mockResolvedValue(task as any);
|
||||
store.getWorkflowStep.mockResolvedValue(buildStep() as any);
|
||||
|
||||
const executor = new TaskExecutor(store as any, "/tmp/test", {} as any);
|
||||
mockedCreateFnAgent.mockResolvedValue({ session: scriptedSession('{"verdict":"APPROVE","notes":""}\n') as any, sessionFile: undefined } as any);
|
||||
|
||||
await (executor as any).executeWorkflowStep(task as any, buildStep() as any, "/tmp/test", {
|
||||
defaultProvider: "anthropic",
|
||||
defaultModelId: "claude-sonnet-4-5",
|
||||
}, undefined);
|
||||
|
||||
const args = mockedCreateFnAgent.mock.calls.at(-1)?.[0] as any;
|
||||
expect(args?.defaultProvider).toBe("anthropic");
|
||||
expect(args?.runtimeContext?.workflowStepTemplateId).toBe("browser-verification");
|
||||
});
|
||||
|
||||
it("FN-5205 mock workflow-step path does not spawn browser harness or bash tools", async () => {
|
||||
const store = createMockStore();
|
||||
const task = buildTask();
|
||||
store.getWorkflowStep.mockResolvedValue(buildStep() as any);
|
||||
mockedCreateFnAgent.mockResolvedValue({ session: scriptedSession('{"verdict":"APPROVE","notes":""}\n') as any, sessionFile: undefined } as any);
|
||||
|
||||
const executor = new TaskExecutor(store as any, "/tmp/test", {} as any);
|
||||
await (executor as any).executeWorkflowStep(task as any, buildStep() as any, "/tmp/test", { testMode: true, defaultProvider: "mock", defaultModelId: "scripted" }, undefined);
|
||||
|
||||
const args = mockedCreateFnAgent.mock.calls.at(-1)?.[0] as any;
|
||||
expect(args?.tools).toBe("readonly");
|
||||
expect((args?.customTools ?? []).some((tool: { name?: string }) => {
|
||||
const name = String(tool.name ?? "");
|
||||
return name === "bash" || name === "Bash" || name.startsWith("agent-browser");
|
||||
})).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -30,8 +30,6 @@ export interface AgentRuntimeContext {
|
||||
toolMode?: "coding" | "readonly";
|
||||
customToolNames?: string[];
|
||||
requestedSkillNames?: string[];
|
||||
workflowStepId?: string;
|
||||
workflowStepTemplateId?: string;
|
||||
}
|
||||
|
||||
export interface AgentRuntimeOptions {
|
||||
|
||||
@@ -280,8 +280,6 @@ export async function createResolvedAgentSession(
|
||||
runtimeContext: {
|
||||
...runtimeOptions.runtimeContext,
|
||||
sessionPurpose,
|
||||
workflowStepId: runtimeOptions.runtimeContext?.workflowStepId,
|
||||
workflowStepTemplateId: runtimeOptions.runtimeContext?.workflowStepTemplateId,
|
||||
},
|
||||
}
|
||||
: runtimeOptions;
|
||||
|
||||
@@ -84,7 +84,6 @@ import { computeRecoveryDecision, formatDelay, MAX_RECOVERY_RETRIES } from "./re
|
||||
import type { StuckTaskDetector, StuckTaskEvent } from "./stuck-task-detector.js";
|
||||
import type { PluginRunner } from "./plugin-runner.js";
|
||||
import { isContextLimitError } from "./context-limit-detector.js";
|
||||
import { isMockProviderId } from "./runtime-resolution.js";
|
||||
import { StepSessionExecutor } from "./step-session-executor.js";
|
||||
import { acquireTaskWorktree } from "./worktree-acquisition.js";
|
||||
import { installTaskWorktreeIdentityGuard } from "./worktree-hooks.js";
|
||||
@@ -7835,31 +7834,18 @@ Backward compat fallback: if JSON is unavailable, you may still begin output wit
|
||||
// fallback is the per-step override's missing-counterpart settings, then
|
||||
// the global validator/fallback pair, then the executor's `fallbackProvider`.
|
||||
const defaultModel = resolveProjectDefaultModel(settings);
|
||||
let primaryProvider = workflowStep.modelProvider || defaultModel.provider;
|
||||
let primaryModelId = workflowStep.modelId || defaultModel.modelId;
|
||||
let useOverride = !!(workflowStep.modelProvider && workflowStep.modelId);
|
||||
const testMode = (settings as { testMode?: boolean }).testMode === true
|
||||
|| isMockProviderId(primaryProvider)
|
||||
|| isMockProviderId(settings.defaultProvider);
|
||||
|
||||
if (testMode) {
|
||||
primaryProvider = "mock";
|
||||
primaryModelId = "scripted";
|
||||
useOverride = false;
|
||||
executorLog.log(`${task.id}: workflow step '${workflowStep.name}' using model: mock/scripted (test mode)`);
|
||||
await this.store.logEntry(task.id, `Workflow step '${workflowStep.name}' using model: mock/scripted (test mode)`);
|
||||
}
|
||||
const primaryProvider = workflowStep.modelProvider || defaultModel.provider;
|
||||
const primaryModelId = workflowStep.modelId || defaultModel.modelId;
|
||||
const useOverride = !!(workflowStep.modelProvider && workflowStep.modelId);
|
||||
|
||||
type ModelTuple = { provider?: string; modelId?: string };
|
||||
const fallbackCandidates: Array<ModelTuple & { label: string }> = [
|
||||
{ provider: settings.validatorFallbackProvider, modelId: settings.validatorFallbackModelId, label: "validatorFallback" },
|
||||
{ provider: settings.fallbackProvider, modelId: settings.fallbackModelId, label: "globalFallback" },
|
||||
];
|
||||
const fallback = testMode
|
||||
? undefined
|
||||
: fallbackCandidates.find(
|
||||
(c) => c.provider && c.modelId && (c.provider !== primaryProvider || c.modelId !== primaryModelId),
|
||||
);
|
||||
const fallback = fallbackCandidates.find(
|
||||
(c) => c.provider && c.modelId && (c.provider !== primaryProvider || c.modelId !== primaryModelId),
|
||||
);
|
||||
|
||||
const timeoutMs = Math.max(60_000, settings.workflowStepTimeoutMs ?? 360_000);
|
||||
|
||||
@@ -7895,10 +7881,6 @@ Backward compat fallback: if JSON is unavailable, you may still begin output wit
|
||||
);
|
||||
}
|
||||
|
||||
const workflowRuntimeContext = {
|
||||
workflowStepId: workflowStep.id,
|
||||
workflowStepTemplateId: workflowStep.templateId ?? workflowStep.id,
|
||||
};
|
||||
const { session } = await createResolvedAgentSession({
|
||||
sessionPurpose: "executor",
|
||||
runtimeHint: workflowRuntimeHint,
|
||||
@@ -7908,15 +7890,13 @@ Backward compat fallback: if JSON is unavailable, you may still begin output wit
|
||||
tools: toolMode,
|
||||
defaultProvider: provider,
|
||||
defaultModelId: modelId,
|
||||
fallbackProvider: testMode ? undefined : settings.fallbackProvider,
|
||||
fallbackModelId: testMode ? undefined : settings.fallbackModelId,
|
||||
fallbackProvider: settings.fallbackProvider,
|
||||
fallbackModelId: settings.fallbackModelId,
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
taskEnv,
|
||||
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
||||
...(skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
||||
...(readonlyCustomTools.allowed.length > 0 ? { customTools: readonlyCustomTools.allowed } : {}),
|
||||
// Test-mode routing (FN-5205): mock dispatcher keys on workflowStepTemplateId; real providers ignore.
|
||||
runtimeContext: workflowRuntimeContext,
|
||||
});
|
||||
|
||||
executorLog.log(`${task.id}: workflow step '${workflowStep.name}' using model ${describeModel(session)}${useOverride && attemptLabel === "primary" ? " (workflow step override)" : ""}${attemptLabel === "fallback" ? " (fallback after timeout)" : ""}`);
|
||||
|
||||
@@ -88,7 +88,6 @@ import { describeModel, promptWithFallback } from "./pi.js";
|
||||
import { accumulateSessionTokenUsage } from "./session-token-usage.js";
|
||||
import { createResolvedAgentSession, extractRuntimeHint, resolveMergerSessionModel } from "./agent-session-helpers.js";
|
||||
import { createFallbackModelObserver } from "./fallback-model-observer.js";
|
||||
import { isMockProviderId } from "./runtime-resolution.js";
|
||||
import { buildSessionSkillContext } from "./session-skill-context.js";
|
||||
import { classifyTaskWorktree, RemovalReason, removeWorktree, type WorktreePool } from "./worktree-pool.js";
|
||||
import { activeSessionRegistry } from "./active-session-registry.js";
|
||||
@@ -11461,20 +11460,9 @@ If issues are found that need attention, describe them clearly and include concr
|
||||
? await agentStoreWithGetAgent.getAgent(assignedAgentId).catch(() => null)
|
||||
: null;
|
||||
const mergerSessionModel = resolveMergerSessionModel(settings, assignedAgent?.runtimeConfig);
|
||||
let stepProvider = workflowStep.modelProvider || mergerSessionModel.provider;
|
||||
let stepModelId = workflowStep.modelId || mergerSessionModel.modelId;
|
||||
let useOverride = !!(workflowStep.modelProvider && workflowStep.modelId);
|
||||
const testMode = (settings as { testMode?: boolean }).testMode === true
|
||||
|| isMockProviderId(stepProvider)
|
||||
|| isMockProviderId(settings.defaultProvider);
|
||||
|
||||
if (testMode) {
|
||||
stepProvider = "mock";
|
||||
stepModelId = "scripted";
|
||||
useOverride = false;
|
||||
mergerLog.log(`${taskId}: [post-merge] workflow step '${workflowStep.name}' using model: mock/scripted (test mode)`);
|
||||
await store.logEntry(taskId, `[post-merge] Workflow step '${workflowStep.name}' using model: mock/scripted (test mode)`);
|
||||
}
|
||||
const stepProvider = workflowStep.modelProvider || mergerSessionModel.provider;
|
||||
const stepModelId = workflowStep.modelId || mergerSessionModel.modelId;
|
||||
const useOverride = !!(workflowStep.modelProvider && workflowStep.modelId);
|
||||
|
||||
// Post-merge step agents inherit merger instructions
|
||||
let postMergeInstructions = "";
|
||||
@@ -11503,10 +11491,6 @@ If issues are found that need attention, describe them clearly and include concr
|
||||
`[readonly-violation] Post-merge workflow step '${workflowStep.name}' dropped denied custom tools: ${readonlyCustomTools.denied.join(", ")}`,
|
||||
);
|
||||
}
|
||||
const workflowRuntimeContext = {
|
||||
workflowStepId: workflowStep.id,
|
||||
workflowStepTemplateId: workflowStep.templateId ?? workflowStep.id,
|
||||
};
|
||||
const { session } = await createResolvedAgentSession({
|
||||
sessionPurpose: "merger",
|
||||
runtimeHint: mergerRuntimeHint,
|
||||
@@ -11516,15 +11500,13 @@ If issues are found that need attention, describe them clearly and include concr
|
||||
tools: toolMode,
|
||||
defaultProvider: stepProvider,
|
||||
defaultModelId: stepModelId,
|
||||
fallbackProvider: testMode ? undefined : settings.fallbackProvider,
|
||||
fallbackModelId: testMode ? undefined : settings.fallbackModelId,
|
||||
fallbackProvider: settings.fallbackProvider,
|
||||
fallbackModelId: settings.fallbackModelId,
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
||||
...(postMergeSkillContext?.skillSelectionContext ? { skillSelection: postMergeSkillContext.skillSelectionContext } : {}),
|
||||
...(readonlyCustomTools.allowed.length > 0 ? { customTools: readonlyCustomTools.allowed } : {}),
|
||||
taskId,
|
||||
// Test-mode routing (FN-5205): mock dispatcher keys on workflowStepTemplateId; real providers ignore.
|
||||
runtimeContext: workflowRuntimeContext,
|
||||
onFallbackModelUsed: createFallbackModelObserver({
|
||||
agent: "merger",
|
||||
label: `post-merge workflow step '${workflowStep.name}'`,
|
||||
|
||||
@@ -45,8 +45,6 @@ export interface MockScriptContext {
|
||||
tools: ToolDefinition[];
|
||||
taskId?: string;
|
||||
taskTitle?: string;
|
||||
workflowStepId?: string;
|
||||
workflowStepTemplateId?: string;
|
||||
invokeTool(name: string, args: Record<string, unknown>): Promise<unknown>;
|
||||
}
|
||||
|
||||
@@ -57,11 +55,10 @@ export interface MockScript {
|
||||
interface MockScriptKey {
|
||||
sessionPurpose: MockSessionPurpose;
|
||||
taskId?: string;
|
||||
workflowStepTemplateId?: string;
|
||||
}
|
||||
|
||||
function registryKey({ sessionPurpose, taskId, workflowStepTemplateId }: MockScriptKey): string {
|
||||
return `${sessionPurpose}:${taskId ?? "*"}:${workflowStepTemplateId ?? "*"}`;
|
||||
function registryKey({ sessionPurpose, taskId }: MockScriptKey): string {
|
||||
return `${sessionPurpose}:${taskId ?? "*"}`;
|
||||
}
|
||||
|
||||
const overrides = new Map<string, MockScript>();
|
||||
@@ -78,8 +75,6 @@ export const mockScriptRegistry = {
|
||||
},
|
||||
resolveMockScript(key: MockScriptKey): MockScript {
|
||||
return overrides.get(registryKey(key))
|
||||
?? overrides.get(registryKey({ sessionPurpose: key.sessionPurpose, taskId: key.taskId }))
|
||||
?? overrides.get(registryKey({ sessionPurpose: key.sessionPurpose, workflowStepTemplateId: key.workflowStepTemplateId }))
|
||||
?? overrides.get(registryKey({ sessionPurpose: key.sessionPurpose }))
|
||||
?? DEFAULT_SCRIPTS[key.sessionPurpose];
|
||||
},
|
||||
@@ -95,8 +90,6 @@ let toolCallCounter = 0;
|
||||
interface MockAgentSessionState {
|
||||
sessionPurpose: MockSessionPurpose;
|
||||
options: AgentRuntimeOptions;
|
||||
workflowStepId?: string;
|
||||
workflowStepTemplateId?: string;
|
||||
}
|
||||
|
||||
interface MockToolCallResult {
|
||||
@@ -107,13 +100,8 @@ export class MockAgentSession {
|
||||
readonly __mock: MockAgentSessionState;
|
||||
readonly state: { errorMessage?: string; error?: string } = {};
|
||||
|
||||
constructor(
|
||||
options: AgentRuntimeOptions,
|
||||
sessionPurpose: MockSessionPurpose,
|
||||
workflowStepId?: string,
|
||||
workflowStepTemplateId?: string,
|
||||
) {
|
||||
this.__mock = { options, sessionPurpose, workflowStepId, workflowStepTemplateId };
|
||||
constructor(options: AgentRuntimeOptions, sessionPurpose: MockSessionPurpose) {
|
||||
this.__mock = { options, sessionPurpose };
|
||||
}
|
||||
|
||||
dispose(): void {}
|
||||
@@ -247,11 +235,6 @@ const DEFAULT_SCRIPTS: Record<MockSessionPurpose, MockScript> = {
|
||||
ctx.options.onText?.("Verdict: APPROVE\n\nSummary: Mock validation passed.\n");
|
||||
},
|
||||
},
|
||||
"workflow-step": {
|
||||
async run(ctx) {
|
||||
ctx.options.onText?.("Mock workflow-step approved scripted run.\n{\"verdict\":\"APPROVE\",\"notes\":\"\"}\n");
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export class MockAgentRuntime implements AgentRuntime {
|
||||
@@ -260,30 +243,20 @@ export class MockAgentRuntime implements AgentRuntime {
|
||||
|
||||
async createSession(options: AgentRuntimeOptions): Promise<AgentSessionResult> {
|
||||
await options.beforeSpawnSession?.();
|
||||
const runtimeContext = options.runtimeContext as
|
||||
| { sessionPurpose?: SessionPurpose; workflowStepId?: string; workflowStepTemplateId?: string }
|
||||
| undefined;
|
||||
const workflowStepId = runtimeContext?.workflowStepId;
|
||||
const workflowStepTemplateId = runtimeContext?.workflowStepTemplateId;
|
||||
let sessionPurpose: MockSessionPurpose = (runtimeContext?.sessionPurpose as SessionPurpose | undefined) ?? "executor";
|
||||
// FN-5205: workflow-step template routing overrides lane purpose for mock script dispatch.
|
||||
if (workflowStepTemplateId) {
|
||||
sessionPurpose = "workflow-step";
|
||||
}
|
||||
const sessionPurpose = (options.runtimeContext?.sessionPurpose as SessionPurpose | undefined) ?? "executor";
|
||||
return {
|
||||
session: new MockAgentSession(options, sessionPurpose, workflowStepId, workflowStepTemplateId) as unknown as AgentSession,
|
||||
session: new MockAgentSession(options, sessionPurpose) as unknown as AgentSession,
|
||||
sessionFile: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async promptWithFallback(session: AgentSession, prompt: string, _promptOptions?: unknown): Promise<void> {
|
||||
const mockSession = session as unknown as MockAgentSession;
|
||||
const { options, sessionPurpose, workflowStepId, workflowStepTemplateId } = mockSession.__mock;
|
||||
const { options, sessionPurpose } = mockSession.__mock;
|
||||
const tools = options.customTools ?? [];
|
||||
const script = mockScriptRegistry.resolveMockScript({
|
||||
sessionPurpose,
|
||||
taskId: options.taskId,
|
||||
workflowStepTemplateId,
|
||||
});
|
||||
await script.run({
|
||||
sessionPurpose,
|
||||
@@ -292,8 +265,6 @@ export class MockAgentRuntime implements AgentRuntime {
|
||||
tools,
|
||||
taskId: options.taskId,
|
||||
taskTitle: options.taskTitle,
|
||||
workflowStepId,
|
||||
workflowStepTemplateId,
|
||||
invokeTool: (name, args) => executeTool(tools, options, name, args),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user