feat(FN-5544): emit runtime-resolved audit event across engine lanes
Adds a "session runtime resolved" audit event that flows through the engine's main execution lanes — triage, executor, reviewer, merger, heartbeat, step-session-executor, and mission-execution-loop — with runtime mutation support and test coverage, plus a compile-fix for the merger auditor wiring. Fusion-Task-Id: FN-5544 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> Fusion-Task-Id: FN-5544
This commit is contained in:
@@ -126,6 +126,97 @@ describe("createResolvedAgentSession", () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("emits session:runtime-resolved when runAuditor is provided", async () => {
|
||||
const mockSession = { prompt: vi.fn() } as any;
|
||||
const createSessionMock = vi.fn().mockResolvedValue({ session: mockSession });
|
||||
const auditDatabaseMock = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
const { createResolvedAgentSession } = await import("../agent-session-helpers.js");
|
||||
|
||||
await createResolvedAgentSession({
|
||||
sessionPurpose: "executor",
|
||||
cwd: "/tmp/project",
|
||||
systemPrompt: "system",
|
||||
defaultProvider: "mock",
|
||||
defaultModelId: "mock-default",
|
||||
runAuditor: { database: auditDatabaseMock } as any,
|
||||
settings: { testMode: true } as any,
|
||||
});
|
||||
|
||||
expect(createSessionMock).not.toHaveBeenCalled();
|
||||
expect(auditDatabaseMock).toHaveBeenCalledTimes(1);
|
||||
expect(auditDatabaseMock).toHaveBeenCalledWith({
|
||||
type: "session:runtime-resolved",
|
||||
target: "mock",
|
||||
metadata: {
|
||||
sessionPurpose: "executor",
|
||||
runtimeId: "mock",
|
||||
wasConfigured: true,
|
||||
provider: "mock",
|
||||
modelId: "mock-default",
|
||||
mockProviderActive: true,
|
||||
testModeActive: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("succeeds when runAuditor is omitted", async () => {
|
||||
const mockSession = { prompt: vi.fn() } as any;
|
||||
const createSessionMock = vi.fn().mockResolvedValue({
|
||||
session: mockSession,
|
||||
sessionFile: "session.json",
|
||||
});
|
||||
resolveRuntimeMock.mockResolvedValue({
|
||||
runtime: {
|
||||
id: "pi",
|
||||
name: "Default PI Runtime",
|
||||
createSession: createSessionMock,
|
||||
promptWithFallback: vi.fn(),
|
||||
describeModel: vi.fn(() => "mock/model"),
|
||||
},
|
||||
runtimeId: "pi",
|
||||
wasConfigured: false,
|
||||
});
|
||||
|
||||
const { createResolvedAgentSession } = await import("../agent-session-helpers.js");
|
||||
|
||||
await expect(createResolvedAgentSession({
|
||||
sessionPurpose: "executor",
|
||||
cwd: "/tmp/project",
|
||||
systemPrompt: "system",
|
||||
})).resolves.toMatchObject({ runtimeId: "pi", wasConfigured: false });
|
||||
});
|
||||
|
||||
it("warns and continues when runAuditor throws", async () => {
|
||||
const mockSession = { prompt: vi.fn() } as any;
|
||||
const createSessionMock = vi.fn().mockResolvedValue({ session: mockSession });
|
||||
resolveRuntimeMock.mockResolvedValue({
|
||||
runtime: {
|
||||
id: "pi",
|
||||
name: "Default PI Runtime",
|
||||
createSession: createSessionMock,
|
||||
promptWithFallback: vi.fn(),
|
||||
describeModel: vi.fn(() => "mock/model"),
|
||||
},
|
||||
runtimeId: "pi",
|
||||
wasConfigured: false,
|
||||
});
|
||||
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const { createResolvedAgentSession } = await import("../agent-session-helpers.js");
|
||||
|
||||
await expect(createResolvedAgentSession({
|
||||
sessionPurpose: "executor",
|
||||
cwd: "/tmp/project",
|
||||
systemPrompt: "system",
|
||||
runAuditor: {
|
||||
database: vi.fn().mockRejectedValue(new Error("audit down")),
|
||||
} as any,
|
||||
})).resolves.toMatchObject({ session: mockSession, runtimeId: "pi", wasConfigured: false });
|
||||
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveMergerSessionModel", () => {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { RunAuditEvent, RunAuditEventFilter, RunAuditEventInput, TaskStore } from "@fusion/core";
|
||||
import { createResolvedAgentSession } from "../agent-session-helpers.js";
|
||||
import { createRunAuditor } from "../run-audit.js";
|
||||
import { MOCK_PROVIDER_ID } from "../providers/mock-provider.js";
|
||||
|
||||
const { resolveRuntimeMock } = vi.hoisted(() => ({ resolveRuntimeMock: vi.fn() }));
|
||||
|
||||
vi.mock("../runtime-resolution.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../runtime-resolution.js")>("../runtime-resolution.js");
|
||||
return { ...actual, resolveRuntime: resolveRuntimeMock };
|
||||
});
|
||||
|
||||
/** SessionPurpose canonical set: executor | triage | reviewer | merger | heartbeat | validation */
|
||||
|
||||
describe("FN-5544 session:runtime-resolved audit event", () => {
|
||||
let recordedEvents: RunAuditEvent[] = [];
|
||||
let counter = 0;
|
||||
let store: TaskStore;
|
||||
|
||||
beforeEach(() => {
|
||||
recordedEvents = [];
|
||||
counter = 0;
|
||||
resolveRuntimeMock.mockReset().mockResolvedValue({
|
||||
runtime: {
|
||||
id: "pi",
|
||||
name: "pi",
|
||||
createSession: vi.fn().mockResolvedValue({ session: { prompt: vi.fn() } }),
|
||||
promptWithFallback: vi.fn(),
|
||||
describeModel: vi.fn(),
|
||||
},
|
||||
runtimeId: "pi",
|
||||
wasConfigured: false,
|
||||
});
|
||||
store = {
|
||||
recordRunAuditEvent: vi.fn(async (input: RunAuditEventInput) => {
|
||||
recordedEvents.push({ ...input, id: `audit-${++counter}`, timestamp: input.timestamp ?? new Date().toISOString() });
|
||||
}),
|
||||
getRunAuditEvents: vi.fn((filter?: RunAuditEventFilter) => {
|
||||
const filtered = recordedEvents.filter((event) => !filter?.mutationType || event.mutationType === filter.mutationType);
|
||||
return filter?.limit ? filtered.slice(0, filter.limit) : filtered;
|
||||
}),
|
||||
} as unknown as TaskStore;
|
||||
});
|
||||
|
||||
it("emits mock-provider runtime-resolved event", async () => {
|
||||
const auditor = createRunAuditor(store, { runId: "r1", agentId: "a1", taskId: "FN-5544", phase: "execute", source: "executor" });
|
||||
await createResolvedAgentSession({ sessionPurpose: "executor", cwd: "/tmp/project", systemPrompt: "system", defaultProvider: MOCK_PROVIDER_ID, defaultModelId: "scripted", runAuditor: auditor });
|
||||
const events = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0]?.target).toBe("mock");
|
||||
expect(events[0]?.metadata).toEqual(expect.objectContaining({ sessionPurpose: "executor", runtimeId: "mock", mockProviderActive: true }));
|
||||
});
|
||||
|
||||
it("emits non-mock provider metadata", async () => {
|
||||
const auditor = createRunAuditor(store, { runId: "r2", agentId: "a1", taskId: "FN-5544", phase: "review", source: "reviewer" });
|
||||
await createResolvedAgentSession({ sessionPurpose: "reviewer", cwd: "/tmp/project", systemPrompt: "system", defaultProvider: "openai", defaultModelId: "gpt-4.1", runAuditor: auditor, runtimeHint: "pi" });
|
||||
const events = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0]?.metadata).toEqual(expect.objectContaining({ sessionPurpose: "reviewer", provider: "openai", modelId: "gpt-4.1", mockProviderActive: false }));
|
||||
});
|
||||
|
||||
it("records no rows when runAuditor is omitted", async () => {
|
||||
await createResolvedAgentSession({ sessionPurpose: "validation", cwd: "/tmp/project", systemPrompt: "system", defaultProvider: MOCK_PROVIDER_ID, defaultModelId: "scripted" });
|
||||
const events = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
|
||||
expect(events).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("round-trips metadata through getRunAuditEvents", async () => {
|
||||
const auditor = createRunAuditor(store, { runId: "r4", agentId: "a1", taskId: "FN-5544", phase: "heartbeat", source: "heartbeat" });
|
||||
await createResolvedAgentSession({ sessionPurpose: "heartbeat", cwd: "/tmp/project", systemPrompt: "system", defaultProvider: "anthropic", defaultModelId: "claude-sonnet-4-5", runtimeHint: "hermes", runAuditor: auditor, settings: { testMode: true } as any });
|
||||
const events = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0]?.metadata).toEqual(expect.objectContaining({ sessionPurpose: "heartbeat", runtimeHint: "hermes", testModeActive: true }));
|
||||
});
|
||||
});
|
||||
@@ -2542,6 +2542,8 @@ export class HeartbeatMonitor {
|
||||
defaultModelId: heartbeatSessionModels.defaultModelId,
|
||||
fallbackProvider: heartbeatSessionModels.fallbackProvider,
|
||||
fallbackModelId: heartbeatSessionModels.fallbackModelId,
|
||||
runAuditor: audit,
|
||||
settings: heartbeatModelSettings,
|
||||
onText: (delta) => {
|
||||
outputLength += delta.length;
|
||||
appendStdoutExcerpt(delta);
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import { resolveRuntime, buildRuntimeResolutionContext, isMockProviderId, type SessionPurpose } from "./runtime-resolution.js";
|
||||
import { createLogger } from "./logger.js";
|
||||
import { promptWithFallback, describeModel } from "./pi.js";
|
||||
import type { RunAuditor } from "./run-audit.js";
|
||||
import { MockAgentRuntime } from "./providers/mock-provider.js";
|
||||
|
||||
/** Logger for agent session helpers */
|
||||
@@ -48,6 +49,18 @@ export interface ResolvedSessionOptions extends AgentRuntimeOptions {
|
||||
pluginRunner?: PluginRunner;
|
||||
/** Optional runtime hint from task/agent configuration */
|
||||
runtimeHint?: string;
|
||||
/**
|
||||
* Optional run-audit emitter; when provided, a `session:runtime-resolved`
|
||||
* database event is recorded at resolution time. No-ops when omitted to
|
||||
* preserve backward compatibility for callers that have not yet been wired
|
||||
* through.
|
||||
*/
|
||||
runAuditor?: RunAuditor;
|
||||
/**
|
||||
* Optional settings used only to capture `testModeActive` in
|
||||
* `session:runtime-resolved` metadata.
|
||||
*/
|
||||
settings?: Settings;
|
||||
/**
|
||||
* `beforeSpawnSession` and `taskEnv` are inherited from
|
||||
* {@link AgentRuntimeOptions}. Both are forwarded verbatim to
|
||||
@@ -261,7 +274,7 @@ export function resolveMergerSessionModel(
|
||||
export async function createResolvedAgentSession(
|
||||
options: ResolvedSessionOptions,
|
||||
): Promise<ResolvedSessionResult> {
|
||||
const { sessionPurpose, pluginRunner, runtimeHint, ...runtimeOptionsRaw } = options;
|
||||
const { sessionPurpose, pluginRunner, runtimeHint, runAuditor, settings, ...runtimeOptionsRaw } = options;
|
||||
|
||||
const skillNamesFromSelection = extractSkillNamesFromSelection(runtimeOptionsRaw.skillSelection);
|
||||
const mergedSkillNames = runtimeOptionsRaw.skills && runtimeOptionsRaw.skills.length > 0
|
||||
@@ -296,6 +309,25 @@ export async function createResolvedAgentSession(
|
||||
`[${sessionPurpose}] Using runtime "${resolved.runtimeId}" (configured=${resolved.wasConfigured})`,
|
||||
);
|
||||
|
||||
try {
|
||||
await runAuditor?.database({
|
||||
type: "session:runtime-resolved",
|
||||
target: resolved.runtimeId,
|
||||
metadata: {
|
||||
sessionPurpose,
|
||||
runtimeId: resolved.runtimeId,
|
||||
wasConfigured: resolved.wasConfigured,
|
||||
provider: runtimeOptions.defaultProvider ?? null,
|
||||
modelId: runtimeOptions.defaultModelId ?? null,
|
||||
mockProviderActive: isMockProviderId(runtimeOptions.defaultProvider),
|
||||
testModeActive: settings ? isTestModeActive(settings) : false,
|
||||
...(runtimeHint ? { runtimeHint } : {}),
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
sessionLog.warn(`[${sessionPurpose}] failed to record session:runtime-resolved audit: ${String(err)}`);
|
||||
}
|
||||
|
||||
// Forward `beforeSpawnSession` to the runtime so it fires at the true
|
||||
// latest sync point (just before LLM session instantiation) rather than
|
||||
// here, before the runtime's own awaited setup work runs. See
|
||||
|
||||
@@ -4005,6 +4005,8 @@ export class TaskExecutor {
|
||||
fallbackProvider: executorFallbackProvider,
|
||||
fallbackModelId: executorFallbackModelId,
|
||||
defaultThinkingLevel: executorThinkingLevel,
|
||||
runAuditor: audit,
|
||||
settings,
|
||||
sessionManager,
|
||||
taskEnv,
|
||||
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
||||
@@ -4421,6 +4423,8 @@ export class TaskExecutor {
|
||||
fallbackProvider: executorFallbackProvider,
|
||||
fallbackModelId: executorFallbackModelId,
|
||||
defaultThinkingLevel: executorThinkingLevel,
|
||||
runAuditor: audit,
|
||||
settings,
|
||||
sessionManager: SessionManager.create(worktreePath),
|
||||
taskEnv,
|
||||
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
||||
@@ -6872,6 +6876,8 @@ Do not refactor, rename broadly, or make opportunistic improvements.
|
||||
defaultProvider: executorProvider,
|
||||
defaultModelId: executorModelId,
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
runAuditor: createRunAuditor(this.store, this.getRunContextFor(task.id)),
|
||||
settings,
|
||||
taskEnv: extraEnv,
|
||||
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
||||
});
|
||||
@@ -7961,6 +7967,8 @@ Backward compat fallback: if JSON is unavailable, you may still begin output wit
|
||||
fallbackProvider: settings.fallbackProvider,
|
||||
fallbackModelId: settings.fallbackModelId,
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
runAuditor: createRunAuditor(this.store, this.getRunContextFor(task.id)),
|
||||
settings,
|
||||
taskEnv,
|
||||
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
||||
...(skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
||||
@@ -10412,6 +10420,8 @@ Child agent: ${agent.id} (${name})`;
|
||||
defaultModelId: childExecutorModelId,
|
||||
fallbackProvider: settings.fallbackProvider,
|
||||
fallbackModelId: settings.fallbackModelId,
|
||||
runAuditor: createRunAuditor(this.store, this.getRunContextFor(taskId)),
|
||||
settings,
|
||||
taskEnv,
|
||||
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
||||
...(skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
||||
|
||||
@@ -1664,6 +1664,14 @@ Do not refactor, rename broadly, or make opportunistic improvements.
|
||||
fallbackProvider: settings.fallbackProvider,
|
||||
fallbackModelId: settings.fallbackModelId,
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
runAuditor: createRunAuditor(store, {
|
||||
runId: mergeRunContext?.runId ?? generateSyntheticRunId("merge", taskId),
|
||||
agentId: mergeRunContext?.agentId ?? "merger",
|
||||
taskId,
|
||||
phase: "merge",
|
||||
source: "merger",
|
||||
}),
|
||||
settings,
|
||||
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
||||
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
||||
taskId,
|
||||
@@ -2848,6 +2856,14 @@ ${fileList}
|
||||
fallbackProvider: settings.fallbackProvider,
|
||||
fallbackModelId: settings.fallbackModelId,
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
runAuditor: createRunAuditor(store, {
|
||||
runId: generateSyntheticRunId("merge", taskId),
|
||||
agentId: "merger",
|
||||
taskId,
|
||||
phase: "merge",
|
||||
source: "merger",
|
||||
}),
|
||||
settings,
|
||||
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
||||
taskId,
|
||||
taskTitle: taskForSkillContext?.title,
|
||||
@@ -3251,6 +3267,14 @@ ${fileList}
|
||||
fallbackProvider: settings.fallbackProvider,
|
||||
fallbackModelId: settings.fallbackModelId,
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
runAuditor: createRunAuditor(store, {
|
||||
runId: generateSyntheticRunId("merge", taskId),
|
||||
agentId: "merger",
|
||||
taskId,
|
||||
phase: "merge",
|
||||
source: "merger",
|
||||
}),
|
||||
settings,
|
||||
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
||||
taskId,
|
||||
taskTitle: taskForSkillContext?.title,
|
||||
@@ -6521,6 +6545,14 @@ You are assisting with a paused \`git pull --rebase\`.
|
||||
fallbackProvider: settings.fallbackProvider,
|
||||
fallbackModelId: settings.fallbackModelId,
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
runAuditor: createRunAuditor(store, {
|
||||
runId: generateSyntheticRunId("merge", taskId),
|
||||
agentId: "merger",
|
||||
taskId,
|
||||
phase: "merge",
|
||||
source: "merger",
|
||||
}),
|
||||
settings,
|
||||
taskId,
|
||||
onFallbackModelUsed: createFallbackModelObserver({
|
||||
agent: "merger",
|
||||
@@ -10878,6 +10910,14 @@ async function runAiAgentForCommit(params: AiAgentParams): Promise<{ success: bo
|
||||
fallbackProvider: settings.fallbackProvider,
|
||||
fallbackModelId: settings.fallbackModelId,
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
runAuditor: createRunAuditor(store, {
|
||||
runId: generateSyntheticRunId("merge", taskId),
|
||||
agentId: "merger",
|
||||
taskId,
|
||||
phase: "merge",
|
||||
source: "merger",
|
||||
}),
|
||||
settings,
|
||||
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
||||
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
||||
taskId,
|
||||
@@ -11511,6 +11551,14 @@ If issues are found that need attention, describe them clearly and include concr
|
||||
fallbackProvider: settings.fallbackProvider,
|
||||
fallbackModelId: settings.fallbackModelId,
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
runAuditor: createRunAuditor(store, {
|
||||
runId: generateSyntheticRunId("merge", taskId),
|
||||
agentId: "merger",
|
||||
taskId,
|
||||
phase: "merge",
|
||||
source: "merger",
|
||||
}),
|
||||
settings,
|
||||
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
||||
...(postMergeSkillContext?.skillSelectionContext ? { skillSelection: postMergeSkillContext.skillSelectionContext } : {}),
|
||||
...(readonlyCustomTools.allowed.length > 0 ? { customTools: readonlyCustomTools.allowed } : {}),
|
||||
|
||||
@@ -24,6 +24,7 @@ import { createFnAgent, promptWithFallback, type AgentResult } from "./pi.js";
|
||||
import { createResolvedAgentSession, extractRuntimeHint } from "./agent-session-helpers.js";
|
||||
import { createLogger } from "./logger.js";
|
||||
import { createFallbackModelObserver } from "./fallback-model-observer.js";
|
||||
import { createRunAuditor, generateSyntheticRunId } from "./run-audit.js";
|
||||
|
||||
/** Logger for the mission execution loop subsystem. */
|
||||
export const loopLog = createLogger("mission-loop");
|
||||
@@ -308,6 +309,13 @@ export class MissionExecutionLoop extends EventEmitter {
|
||||
|
||||
try {
|
||||
// Create validation agent session
|
||||
const runAuditor = createRunAuditor(this.taskStore, {
|
||||
runId: generateSyntheticRunId("mission", feature.taskId ?? feature.id),
|
||||
agentId: "reviewer",
|
||||
taskId: task?.id,
|
||||
phase: "mission",
|
||||
source: "mission-execution-loop",
|
||||
});
|
||||
const sessionResult = await createResolvedAgentSession({
|
||||
sessionPurpose: "validation",
|
||||
runtimeHint: validationRuntimeHint,
|
||||
@@ -316,6 +324,7 @@ export class MissionExecutionLoop extends EventEmitter {
|
||||
systemPrompt: this.buildValidationSystemPrompt(feature, assertions, taskContext),
|
||||
tools: "readonly",
|
||||
defaultThinkingLevel: "medium",
|
||||
runAuditor,
|
||||
onText: (_delta) => {
|
||||
// Could stream this to a log entry if needed
|
||||
},
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
} from "./agent-instructions.js";
|
||||
import { buildPromptLayers, collapsePromptLayers } from "./prompt-layers.js";
|
||||
import { createFallbackModelObserver } from "./fallback-model-observer.js";
|
||||
import { createRunAuditor, generateSyntheticRunId } from "./run-audit.js";
|
||||
import { createMemoryGetTool, createMemorySearchTool, createWebFetchTool } from "./agent-tools.js";
|
||||
|
||||
export const REVIEWER_SYSTEM_PROMPT = `You are an independent code and plan reviewer.
|
||||
@@ -500,6 +501,15 @@ export async function reviewStep(
|
||||
const createReviewerSession = async (
|
||||
overrides?: { forceProvider?: string; forceModelId?: string },
|
||||
): Promise<import("@mariozechner/pi-coding-agent").AgentSession> => {
|
||||
const runAuditor = options.store
|
||||
? createRunAuditor(options.store, {
|
||||
runId: generateSyntheticRunId("reviewer", options.taskId ?? "review"),
|
||||
agentId: options.agentId ?? "reviewer",
|
||||
taskId: options.taskId,
|
||||
phase: "review",
|
||||
source: "reviewer",
|
||||
})
|
||||
: undefined;
|
||||
const { session } = await createResolvedAgentSession({
|
||||
sessionPurpose: "reviewer",
|
||||
runtimeHint: extractRuntimeHint(memoryAgent?.runtimeConfig),
|
||||
@@ -518,6 +528,8 @@ export async function reviewStep(
|
||||
fallbackProvider: validatorFallbackProvider,
|
||||
fallbackModelId: validatorFallbackModelId,
|
||||
defaultThinkingLevel: options.defaultThinkingLevel,
|
||||
runAuditor,
|
||||
settings: options.settings,
|
||||
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
||||
taskId: options.taskId,
|
||||
taskTitle: options.taskTitle,
|
||||
|
||||
@@ -428,6 +428,26 @@ export type DatabaseMutationType =
|
||||
| "task:auto-board-stall-unrecovered"
|
||||
/** Metadata: { errors: string[], lastCheckedAt: string | null, notificationDispatched: boolean } */
|
||||
| "task:auto-db-corruption-detected"
|
||||
/**
|
||||
* Per-lane runtime/provider/model selection telemetry, emitted once per
|
||||
* `createResolvedAgentSession` call. Target is the resolved runtime id
|
||||
* (e.g., `"pi"`, `"mock"`, `"hermes"`).
|
||||
*
|
||||
* Metadata shape:
|
||||
* ```ts
|
||||
* {
|
||||
* sessionPurpose: SessionPurpose; // canonical lane label
|
||||
* runtimeId: string; // resolved runtime id (same as target)
|
||||
* wasConfigured: boolean; // runtime was explicitly configured (vs default fallback)
|
||||
* provider: string | null; // resolved AI provider id (null when not yet set)
|
||||
* modelId: string | null; // resolved model id (null when not yet set)
|
||||
* mockProviderActive: boolean; // isMockProviderId(provider) — convenience flag for test-mode assertions
|
||||
* testModeActive: boolean; // isTestModeActive(settings) at resolution time
|
||||
* runtimeHint?: string; // raw runtime hint when present
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
| "session:runtime-resolved"
|
||||
| "task:in-review-stall-deadlock-disposed"
|
||||
| "task:finalize-unproven-blocked"
|
||||
| "task:integrity-reconcile-modified-files"
|
||||
|
||||
@@ -36,6 +36,7 @@ import { StuckTaskDetector } from "./stuck-task-detector.js";
|
||||
import { AgentLogger } from "./agent-logger.js";
|
||||
import { createLogger } from "./logger.js";
|
||||
import { createFallbackModelObserver } from "./fallback-model-observer.js";
|
||||
import { createRunAuditor, generateSyntheticRunId } from "./run-audit.js";
|
||||
import { isContextLimitError } from "./context-limit-detector.js";
|
||||
import { checkSessionError } from "./usage-limit-detector.js";
|
||||
import {
|
||||
@@ -1015,6 +1016,15 @@ Follow instructions precisely and avoid unrelated changes.`,
|
||||
fallbackProvider: settings.fallbackProvider,
|
||||
fallbackModelId: settings.fallbackModelId,
|
||||
defaultThinkingLevel: taskDetail.thinkingLevel ?? settings.defaultThinkingLevel,
|
||||
runAuditor: createRunAuditor(this.store, {
|
||||
runId: generateSyntheticRunId("workflow-step", taskDetail.id),
|
||||
agentId: taskDetail.assignedAgentId ?? "executor",
|
||||
taskId: taskDetail.id,
|
||||
taskLineageId: taskDetail.lineageId,
|
||||
phase: "execute",
|
||||
source: "step-session-executor",
|
||||
}),
|
||||
settings,
|
||||
customTools: [
|
||||
...pluginTools,
|
||||
...documentTools,
|
||||
|
||||
@@ -1210,6 +1210,14 @@ export class TriageProcessor {
|
||||
defaultProvider: planningModel.provider,
|
||||
defaultModelId: planningModel.modelId,
|
||||
};
|
||||
const runAuditor = createRunAuditor(this.store, {
|
||||
runId: generateSyntheticRunId("triage", task.id),
|
||||
agentId: assignedAgent?.id ?? "triage",
|
||||
taskId: task.id,
|
||||
taskLineageId: task.lineageId,
|
||||
phase: "plan",
|
||||
source: "triage",
|
||||
});
|
||||
|
||||
let { session } = await createResolvedAgentSession({
|
||||
sessionPurpose: "triage",
|
||||
@@ -1232,6 +1240,8 @@ export class TriageProcessor {
|
||||
? settings.planningFallbackModelId
|
||||
: settings.fallbackModelId,
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
runAuditor,
|
||||
settings,
|
||||
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
||||
...(skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
||||
taskId: task.id,
|
||||
@@ -1460,6 +1470,8 @@ export class TriageProcessor {
|
||||
defaultProvider: planningFallbackProvider,
|
||||
defaultModelId: planningFallbackModelId,
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
runAuditor,
|
||||
settings,
|
||||
...(skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
||||
taskId: task.id,
|
||||
taskTitle: task.title,
|
||||
|
||||
Reference in New Issue
Block a user