Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion scripts/language-policy-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
"src/ui/design-system/sidebar-motion.js",
"src/ui/tui/api.js",
"src/ui/tui/blocks.js",
"src/ui/tui/golden-scenarios/initial-scenarios.js",
"src/ui/tui/golden-scenarios/planned-change-workflow.js",
"src/ui/tui/golden-scenarios/project-workflow.js",
"src/ui/tui/testing/child-protocol.js",
Expand Down
2 changes: 1 addition & 1 deletion src/ui/tui/golden-scenarios/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { concurrentWorkflowScenarios } from "./concurrent-workflow.ts";
import { initialGoldenScenarios } from "./initial-scenarios.js";
import { initialGoldenScenarios } from "./initial-scenarios.ts";
import { loadPlanWorkflowScenarios } from "./load-plan-workflow.ts";
import { loadPlanEpicWorkflowScenarios } from "./load-plan-epic-workflow.ts";
import { plannedChangeWorkflowScenarios } from "./planned-change-workflow.js";
Expand Down
12 changes: 6 additions & 6 deletions src/ui/tui/golden-scenarios/initial-scenarios.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
runtimeInteractionContractScenario,
startupNoProvidersOpensLoginScenario,
startupProviderWithoutModelsOpensModelScenario,
} from "./initial-scenarios.js";
} from "./initial-scenarios.ts";
import { GoldenScenarioActor, runGoldenScenario } from "../testing/mod.ts";
import { NO_OPEN_BROWSER_PORT } from "../../../shared/browser-port.ts";

Expand Down Expand Up @@ -72,7 +72,7 @@ for (const scenario of initialGoldenScenarios.filter((candidate) => candidate !=
test(`golden scenario: ${scenario.name}`, async () => {
const { runGoldenScenarioChildProcess } = await import("../testing/child-protocol.js");
const result = await runGoldenScenarioChildProcess({
scenarioModule: "src/ui/tui/golden-scenarios/initial-scenarios.js",
scenarioModule: "src/ui/tui/golden-scenarios/initial-scenarios.ts",
exportName: scenarioExportNames.get(scenario) || "",
timeoutMs: "timeoutMs" in scenario && typeof scenario.timeoutMs === "number" ? scenario.timeoutMs : 30000,
});
Expand Down Expand Up @@ -147,7 +147,7 @@ test("golden actor fails when required tools are unavailable", () => {
test("golden scenario child process runs with isolated environment before scenario import", async () => {
const { runGoldenScenarioChildProcess } = await import("../testing/child-protocol.js");
const result = await runGoldenScenarioChildProcess({
scenarioModule: "src/ui/tui/golden-scenarios/initial-scenarios.js",
scenarioModule: "src/ui/tui/golden-scenarios/initial-scenarios.ts",
exportName: "helpSlashCommandScenario",
timeoutMs: 30000,
});
Expand Down Expand Up @@ -206,7 +206,7 @@ test("golden scenario child process failure reports retained diagnostics artifac
const error = await assertRejects(
() =>
runGoldenScenarioChildProcess({
scenarioModule: "src/ui/tui/golden-scenarios/initial-scenarios.js",
scenarioModule: "src/ui/tui/golden-scenarios/initial-scenarios.ts",
exportName: "missingGoldenScenario",
timeoutMs: 3000,
}),
Expand All @@ -229,7 +229,7 @@ test("golden scenario timeout retains child heartbeat diagnostics", async () =>
const error = await assertRejects(
() =>
runGoldenScenarioChildProcess({
scenarioModule: "src/ui/tui/golden-scenarios/initial-scenarios.js",
scenarioModule: "src/ui/tui/golden-scenarios/initial-scenarios.ts",
exportName: "timeoutDiagnosticScenario",
timeoutMs: 2000,
}),
Expand Down Expand Up @@ -263,7 +263,7 @@ test("golden scenario child crash surfaces substantive scenario diagnostics", as
const error = await assertRejects(
() =>
runGoldenScenarioChildProcess({
scenarioModule: "src/ui/tui/golden-scenarios/initial-scenarios.js",
scenarioModule: "src/ui/tui/golden-scenarios/initial-scenarios.ts",
exportName: "diagnosticArtifactFailureScenario",
timeoutMs: 10000,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,33 @@
import { ok as assert } from "node:assert/strict";
import { assertEventIncludes, assertScreenIncludes } from "../testing/scenario-runner.js";

/** @typedef {import('../testing/scenario-runner.js').GoldenScenarioResult} GoldenScenarioResult */
import type {
ConsumedScriptedInteraction,
ConsumedScriptedReview,
GoldenScenario,
GoldenScenarioResult,
} from "../testing/mod.ts";

/** @param {GoldenScenarioResult} result */
function assertRuntimeGuideSwitch(result) {
interface PlanReviewLifecycleEvent {
event: string;
status?: string;
}
interface PlanReviewAttributes {
status: string;
}
interface ReviewConversationDocument {
before?: string;
after?: string;
}
interface PlanReviewEvidence {
attrs: PlanReviewAttributes;
lifecycleEvents: PlanReviewLifecycleEvent[];
consumed: ConsumedScriptedReview[];
conversationDocuments: ReviewConversationDocument[];
plan: string;
}

function assertRuntimeGuideSwitch(result: GoldenScenarioResult) {
assertEventIncludes(result, "runtime:agent:guide");
assert(
result.state.activeAgent === "guide",
Expand All @@ -21,43 +44,36 @@ function assertRuntimeGuideSwitch(result) {
assertEventIncludes(result, "runtime:assistant:text");
}

/** @param {GoldenScenarioResult} result */
function assertTerminalInputVisible(result) {
function assertTerminalInputVisible(result: GoldenScenarioResult) {
assertEventIncludes(result, "terminal:type:how does routing work?");
}

/** @param {GoldenScenarioResult} result */
function assertProjectClean(result) {
function assertProjectClean(result: GoldenScenarioResult) {
assert(
result.state.projectMutation === "clean",
`Expected clean Project mutation state; got ${result.state.projectMutation}`,
);
}

/** @param {GoldenScenarioResult} result */
function assertCancellationEvent(result) {
function assertCancellationEvent(result: GoldenScenarioResult) {
assertEventIncludes(result, "runtime:cancellation");
}

/** @param {GoldenScenarioResult} result */
function assertEditorReady(result) {
function assertEditorReady(result: GoldenScenarioResult) {
assert(result.state.editorUsable === true, "Expected editor to be usable after cancellation.");
assertEventIncludes(result, "terminal:type:benign follow-up after cancel");
assertEventIncludes(result, "runtime:assistant:text");
}

/** @param {GoldenScenarioResult} result */
function assertHelpSlashVisible(result) {
function assertHelpSlashVisible(result: GoldenScenarioResult) {
assertEventIncludes(result, "terminal:type:/help");
}

/** @param {GoldenScenarioResult} result */
function assertKeyboardHelpVisible(result) {
function assertKeyboardHelpVisible(result: GoldenScenarioResult) {
assertScreenIncludes(result, "Global flags:");
}

/** @param {GoldenScenarioResult} result */
function assertStartupLoginPromptOpened(result) {
function assertStartupLoginPromptOpened(result: GoldenScenarioResult) {
const startupScreen = String(result.state.startupScreen || "");
assert(
startupScreen.includes("Welcome to RunWield"),
Expand All @@ -69,8 +85,7 @@ function assertStartupLoginPromptOpened(result) {
);
}

/** @param {GoldenScenarioResult} result */
function assertStartupModelSelectorOpened(result) {
function assertStartupModelSelectorOpened(result: GoldenScenarioResult) {
const startupScreen = String(result.state.startupScreen || "");
assert(
startupScreen.includes("Only showing models from configured providers"),
Expand All @@ -82,24 +97,18 @@ function assertStartupModelSelectorOpened(result) {
);
}

/** @param {GoldenScenarioResult} result */
function assertReviewFeedbackEvent(result) {
function assertReviewFeedbackEvent(result: GoldenScenarioResult) {
assertEventIncludes(result, "interaction:PLAN_REVIEW:feedback");
assertEventIncludes(result, "review_feedback");
}

/** @param {GoldenScenarioResult} result */
function assertReviewApprovedEvent(result) {
function assertReviewApprovedEvent(result: GoldenScenarioResult) {
assertEventIncludes(result, "interaction:PLAN_REVIEW:approved");
assertEventIncludes(result, "review_approved");
}

/** @param {GoldenScenarioResult} result */
function assertPlanReviewLifecyclePersisted(result) {
const planReview =
/** @type {{ lifecycleEvents?: Array<{ event: string, status?: unknown }>, attrs?: Record<string, unknown>, consumed?: unknown[] } | undefined} */ (
result.state.planReview
);
function assertPlanReviewLifecyclePersisted(result: GoldenScenarioResult) {
const planReview = result.state.planReview as Partial<PlanReviewEvidence> | undefined;
assert(planReview, "Expected planReview state from production submitPlanForReview transaction.");
assert(
planReview.lifecycleEvents?.map((event) => `${event.event}:${event.status}`).join(",") ===
Expand All @@ -110,7 +119,7 @@ function assertPlanReviewLifecyclePersisted(result) {
);
assert(planReview.consumed?.length === 2, "Expected scripted review surface to be consumed twice.");
assert(
String(/** @type {{ plan?: unknown }} */ (planReview).plan || "").includes("Reviewed content persisted."),
String(planReview.plan || "").includes("Reviewed content persisted."),
"Expected reviewed Plan body to persist.",
);
}
Expand Down Expand Up @@ -163,7 +172,7 @@ export const routerToGuideInquiryScenario = {
{ type: "assertProjectUnchanged" },
],
assertions: [assertTerminalInputVisible, assertRuntimeGuideSwitch, assertProjectClean],
};
} satisfies GoldenScenario;

export const escapeCancellationScenario = {
name: "escape-cancellation-restores-editor",
Expand Down Expand Up @@ -203,15 +212,15 @@ export const escapeCancellationScenario = {
{ type: "waitForIdle" },
],
assertions: [assertCancellationEvent, assertEditorReady],
};
} satisfies GoldenScenario;

export const helpSlashCommandScenario = {
name: "help-slash-command",
composedTui: true,
terminal: { columns: 100, rows: 30 },
actions: [{ type: "type", text: "/help" }, { type: "enter" }, { type: "waitForIdle" }],
assertions: [assertHelpSlashVisible, assertKeyboardHelpVisible],
};
} satisfies GoldenScenario;

export const startupNoProvidersOpensLoginScenario = {
name: "startup-no-providers-opens-login",
Expand All @@ -227,7 +236,7 @@ export const startupNoProvidersOpensLoginScenario = {
actions: [],
assertions: [assertStartupLoginPromptOpened],
timeoutMs: 5000,
};
} satisfies GoldenScenario;

export const startupProviderWithoutModelsOpensModelScenario = {
name: "startup-provider-without-models-opens-model",
Expand All @@ -241,7 +250,7 @@ export const startupProviderWithoutModelsOpensModelScenario = {
actions: [],
assertions: [assertStartupModelSelectorOpened],
timeoutMs: 15000,
};
} satisfies GoldenScenario;

export const planReviewTransactionContractScenario = {
name: "plan-review-transaction-contract",
Expand Down Expand Up @@ -294,21 +303,17 @@ export const planReviewTransactionContractScenario = {
{ type: "waitForIdle" },
],
assertions: [assertReviewFeedbackEvent, assertReviewApprovedEvent, assertPlanReviewLifecyclePersisted],
};
} satisfies GoldenScenario;

/** @param {GoldenScenarioResult} result */
function assertReplyOnlyReviewApproval(result) {
function assertReplyOnlyReviewApproval(result: GoldenScenarioResult) {
assertEventIncludes(result, "interaction:PLAN_REVIEW:conversation");
assertEventIncludes(result, "interaction:PLAN_REVIEW:approved");
assertEventIncludes(result, "runtime:assistant:text");
assert(
result.events.filter((event) => event === "runtime:tool:start:plan_written").length === 1,
"The reply-only turn must not call plan_written again.",
);
const review =
/** @type {{ attrs: { status: string }, conversationDocuments: Array<{ before?: string, after?: string }>, lifecycleEvents: Array<{ event: string }>, consumed: Array<{ decision: { approved: boolean } }>, plan: string }} */ (
result.state.planReview
);
const review = result.state.planReview as PlanReviewEvidence;
assert(review.attrs.status === "ready_for_work", "Approval through the re-armed review must pass readiness.");
assert(review.consumed.length === 2, "The same live page must accept chat then approval.");
assert(
Expand Down Expand Up @@ -357,7 +362,7 @@ export const planReviewReplyOnlyConversationScenario = {
{ type: "waitForIdle" },
],
assertions: [assertReplyOnlyReviewApproval],
};
} satisfies GoldenScenario;

export const fauxProviderProtocolScenario = {
name: "faux-provider-protocol-contract",
Expand Down Expand Up @@ -392,19 +397,16 @@ export const fauxProviderProtocolScenario = {
],
actions: [{ type: "type", text: "protocol check" }, { type: "enter" }, { type: "waitForIdle" }],
assertions: [
(/** @type {GoldenScenarioResult} */ result) =>
assertEventIncludes(result, "model:faux-provider:router:triage"),
(/** @type {GoldenScenarioResult} */ result) =>
assertEventIncludes(result, "model:faux-provider:guide:inquiry"),
(result: GoldenScenarioResult) => assertEventIncludes(result, "model:faux-provider:router:triage"),
(result: GoldenScenarioResult) => assertEventIncludes(result, "model:faux-provider:guide:inquiry"),
],
};
} satisfies GoldenScenario;

/** @param {GoldenScenarioResult} result */
function assertScriptedRuntimeInteractions(result) {
function assertScriptedRuntimeInteractions(result: GoldenScenarioResult) {
assertEventIncludes(result, "interaction:select:selected");
assertEventIncludes(result, "interaction:text:text");
assertEventIncludes(result, "interaction:approval:accepted");
const interactions = /** @type {unknown[]} */ (result.state.scriptedInteractions || []);
const interactions = (result.state.scriptedInteractions || []) as ConsumedScriptedInteraction[];
assert(interactions.length === 3, `Expected three scripted Runtime interactions; got ${interactions.length}`);
}

Expand Down Expand Up @@ -446,7 +448,7 @@ export const runtimeInteractionContractScenario = {
},
],
assertions: [assertScriptedRuntimeInteractions],
};
} satisfies GoldenScenario;

// Deliberately hangs so the parent's scenario timeout fires mid-sleep. The sleep
// is far longer than that timeout so the kill window cannot be outrun.
Expand All @@ -456,15 +458,15 @@ export const timeoutDiagnosticScenario = {
terminal: { columns: 80, rows: 20 },
script: [{ id: "timeout-unused-turn", agent: "router", phase: "triage", text: "unused" }],
actions: [{ type: "sleep", ms: 120_000 }],
};
} satisfies GoldenScenario;

export const diagnosticArtifactFailureScenario = {
name: "diagnostic-artifact-failure",
composedTui: true,
terminal: { columns: 80, rows: 20 },
script: [{ id: "unused-router-turn", agent: "router", phase: "triage", text: "unused" }],
actions: [{ type: "unknown-composed-action" }],
};
} satisfies GoldenScenario;

export const initialGoldenScenarios = [
routerToGuideInquiryScenario,
Expand Down
Loading