feat: connect prepared run and refine presenter notes
This commit is contained in:
@@ -80,15 +80,16 @@ export const GuidedProductMoment = ({
|
||||
const facts = projectDemoRunFacts(demo);
|
||||
const runResume = demo.state.events.find((event) => event.stage === "run_resume");
|
||||
const revisionRequested = approvalActions?.state === "revision_requested";
|
||||
const isReplay = demo.state.mode === "replay";
|
||||
// Replay revision evidence has a separate recording identity; do not let
|
||||
// the normal submitted-branch lens imply continuity for that branch.
|
||||
const eyebrow = revisionRequested
|
||||
? demo.state.mode === "replay"
|
||||
? isReplay
|
||||
? "Prepared branch"
|
||||
: "Live branch"
|
||||
: lens.eyebrow;
|
||||
const headline = revisionRequested
|
||||
? demo.state.mode === "replay"
|
||||
? isReplay
|
||||
? "Separate prepared revision recording"
|
||||
: "Live revision branch resumes the run"
|
||||
: lens.headline;
|
||||
@@ -106,7 +107,7 @@ export const GuidedProductMoment = ({
|
||||
<header className="guided-product-moment__header">
|
||||
<span>{eyebrow}</span>
|
||||
<strong>{headline}</strong>
|
||||
<p>{statusCopy(moment, approvalActions, demo.state.mode === "replay")}</p>
|
||||
<p>{statusCopy(moment, approvalActions, isReplay)}</p>
|
||||
</header>
|
||||
|
||||
<div className="guided-product-moment__primary">
|
||||
|
||||
@@ -268,7 +268,8 @@ describe("PresentationRoute", () => {
|
||||
.toHaveAttribute("data-phase", "deployment");
|
||||
});
|
||||
|
||||
it("records a Deployment run request locally without an RPC", async () => {
|
||||
it("starts the shared prepared workflow action without advancing the Deployment beat", async () => {
|
||||
window.sessionStorage.setItem("lda.workflowConsole.target", "http://127.0.0.1:8765/rpc");
|
||||
window.location.hash = "#scene/prepared-lifecycle/deployment";
|
||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||
render(<PresentationRoute />);
|
||||
@@ -277,11 +278,16 @@ describe("PresentationRoute", () => {
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: /send message/i }));
|
||||
|
||||
expect(await screen.findByRole("status")).toHaveTextContent(
|
||||
"Run request prepared for the next execution slice.",
|
||||
);
|
||||
expect(mockedCallOperation.mock.calls.some(([operation]) => operation === "workflow.runs.start"))
|
||||
.toBe(false);
|
||||
expect(await screen.findByText("Prepared run receipt added. Continue when you are ready."))
|
||||
.toBeInTheDocument();
|
||||
expect(window.location.hash).toBe("#scene/prepared-lifecycle/deployment");
|
||||
expect(screen.getByText(/three proposed issues are ready for review/i)).toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(mockedCallOperation.mock.calls.some(([operation]) => operation === "workflow.deployments.inspect"))
|
||||
.toBe(true);
|
||||
expect(mockedCallOperation.mock.calls.some(([operation]) => operation === "workflow.runs.start"))
|
||||
.toBe(true);
|
||||
}, { timeout: 3000 });
|
||||
});
|
||||
|
||||
it("keeps Diagnose submission on the current beat", async () => {
|
||||
|
||||
@@ -109,6 +109,11 @@ export const PresentationStage = ({
|
||||
motionDisabled={state.motionDisabled}
|
||||
approvalActions={approvalActions}
|
||||
onPreparedLifecycleAdvance={onPreparedLifecycleAdvance}
|
||||
onRunPreparedWorkflow={
|
||||
demoRail.kind === "action" && timelineAgent
|
||||
? () => timelineAgent.runPreparedWorkflow(demoRail.mode)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
|
||||
@@ -30,15 +30,16 @@ describe("RunInputFileBrowser", () => {
|
||||
|
||||
const files = within(browser).getByRole("list", { name: /included in prepared run/i });
|
||||
expect(within(files).getAllByRole("listitem")).toHaveLength(2);
|
||||
expect(within(files).getAllByText("selected")).toHaveLength(2);
|
||||
expect(within(files).getAllByText("selected")).toHaveLength(1);
|
||||
expect(within(files).getAllByRole("button")).toHaveLength(2);
|
||||
expect(within(files).queryAllByRole("link")).toHaveLength(0);
|
||||
for (const path of ["docs/project-brief.md", "docs/architecture-notes.md"]) {
|
||||
const row = within(files).getByText(path).closest("li");
|
||||
expect(row).not.toBeNull();
|
||||
expect(row).toHaveAttribute("data-file-path", path);
|
||||
expect(row).toHaveTextContent(/selected/i);
|
||||
}
|
||||
expect(within(files).getByText("docs/project-brief.md").closest("li")).toHaveTextContent(/selected/i);
|
||||
expect(within(files).getByText("docs/architecture-notes.md").closest("li")).not.toHaveTextContent(/selected/i);
|
||||
|
||||
const destination = within(browser).getByRole("group", { name: /workflow output/i });
|
||||
expect(destination).toHaveTextContent("artifacts/issue-board.json");
|
||||
@@ -57,6 +58,11 @@ describe("RunInputFileBrowser", () => {
|
||||
expect(preview).toHaveTextContent(/architecture-notes\.md/i);
|
||||
expect(preview).toHaveTextContent(/fixture preview/i);
|
||||
expect(preview).toHaveTextContent(/not execution evidence/i);
|
||||
expect(screen.getByRole("button", { name: /architecture-notes\.md/i })).toHaveAttribute(
|
||||
"aria-pressed",
|
||||
"true",
|
||||
);
|
||||
expect(screen.getByRole("button", { name: /project-brief\.md/i })).not.toHaveTextContent(/selected/i);
|
||||
});
|
||||
|
||||
it("shows an honest empty preview for files absent from the fixture catalog", () => {
|
||||
|
||||
@@ -34,7 +34,9 @@ export const RunInputFileBrowser = ({
|
||||
>
|
||||
<span className="run-input-file-browser__icon" aria-hidden="true">md</span>
|
||||
<code>{path}</code>
|
||||
<span className="run-input-file-browser__marker">selected</span>
|
||||
{path === visibleSelectedPath ? (
|
||||
<span className="run-input-file-browser__marker">selected</span>
|
||||
) : null}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -33,6 +33,7 @@ type SceneBodyProps = {
|
||||
readonly motionDisabled: boolean;
|
||||
readonly approvalActions?: DemoApprovalActions | undefined;
|
||||
readonly onPreparedLifecycleAdvance?: (() => void) | undefined;
|
||||
readonly onRunPreparedWorkflow?: (() => Promise<void>) | undefined;
|
||||
};
|
||||
|
||||
const workflowDemoSceneIds = new Set([
|
||||
@@ -290,7 +291,7 @@ const assertNever = (value: never): never => {
|
||||
throw new Error(`Unexpected view: ${value}`);
|
||||
};
|
||||
|
||||
export const SceneBody = ({ location, demo, selectedNodeId, selectNode, openEvidence, openDiscussion, onFocusPathChange, motionDisabled, approvalActions, onPreparedLifecycleAdvance }: SceneBodyProps) => {
|
||||
export const SceneBody = ({ location, demo, selectedNodeId, selectNode, openEvidence, openDiscussion, onFocusPathChange, motionDisabled, approvalActions, onPreparedLifecycleAdvance, onRunPreparedWorkflow }: SceneBodyProps) => {
|
||||
const sceneId = location.kind === "main" ? location.sceneId : "positioning";
|
||||
const beatId = location.kind === "main" ? location.beatId : "landscape";
|
||||
const scene = findScene(sceneId) ?? findScene("thesis")!;
|
||||
@@ -331,6 +332,7 @@ export const SceneBody = ({ location, demo, selectedNodeId, selectNode, openEvid
|
||||
scene={scene}
|
||||
beat={beat}
|
||||
onAdvance={onPreparedLifecycleAdvance}
|
||||
onRunPreparedWorkflow={onRunPreparedWorkflow}
|
||||
discussionRail={scene.id === "prepared-lifecycle" ? discussionLinks : undefined}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AssistantOperatorThread } from "../chat/AssistantOperatorThread.js";
|
||||
import {
|
||||
authoringToolGroupId,
|
||||
projectPreparedAuthoringThread,
|
||||
projectPreparedRunRequestExchange,
|
||||
type AuthoringPhaseId,
|
||||
} from "./authoring-recording.js";
|
||||
import {
|
||||
@@ -17,6 +18,7 @@ type AuthoringConversationProps = {
|
||||
readonly requestOverride?: string | undefined;
|
||||
readonly requestOverrides?: PreparedLifecycleSubmittedOverrides | undefined;
|
||||
readonly scrollMode?: "active" | "start" | undefined;
|
||||
readonly runRequested?: string | null | undefined;
|
||||
};
|
||||
|
||||
/** Renders the same prepared conversation at full-stage or compact-dock scale. */
|
||||
@@ -27,17 +29,25 @@ export const AuthoringConversation = ({
|
||||
requestOverride,
|
||||
requestOverrides,
|
||||
scrollMode,
|
||||
}: AuthoringConversationProps) => (
|
||||
<AssistantOperatorThread
|
||||
mode={surface === "stage" ? "full" : "dock"}
|
||||
surface={surface}
|
||||
messages={projectPreparedAuthoringThread(
|
||||
runRequested,
|
||||
}: AuthoringConversationProps) => {
|
||||
const messages = [
|
||||
...projectPreparedAuthoringThread(
|
||||
recordingPhaseForStep(throughPhase),
|
||||
requestOverride,
|
||||
requestOverrides,
|
||||
)}
|
||||
activeToolGroupId={authoringToolGroupId(recordingPhaseForStep(activePhase))}
|
||||
scrollMode={scrollMode}
|
||||
ariaLabel="prepared authoring conversation"
|
||||
/>
|
||||
);
|
||||
),
|
||||
...(runRequested ? projectPreparedRunRequestExchange(runRequested) : []),
|
||||
];
|
||||
|
||||
return (
|
||||
<AssistantOperatorThread
|
||||
mode={surface === "stage" ? "full" : "dock"}
|
||||
surface={surface}
|
||||
messages={messages}
|
||||
activeToolGroupId={authoringToolGroupId(recordingPhaseForStep(activePhase))}
|
||||
scrollMode={runRequested ? "end" : scrollMode}
|
||||
ariaLabel="prepared authoring conversation"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
+22
-1
@@ -7,7 +7,12 @@ import { PreparedAuthoringLifecycleScene } from "./PreparedAuthoringLifecycleSce
|
||||
|
||||
afterEach(() => cleanup());
|
||||
|
||||
const renderBeat = (beatId: string, onAdvance?: () => void, discussionRail?: ReactNode) => {
|
||||
const renderBeat = (
|
||||
beatId: string,
|
||||
onAdvance?: () => void,
|
||||
discussionRail?: ReactNode,
|
||||
onRunPreparedWorkflow?: () => Promise<void>,
|
||||
) => {
|
||||
const scene = findScene("prepared-lifecycle");
|
||||
const beat = findBeat("prepared-lifecycle", beatId);
|
||||
if (!scene || !beat) throw new Error(`missing prepared-lifecycle/${beatId}`);
|
||||
@@ -16,6 +21,7 @@ const renderBeat = (beatId: string, onAdvance?: () => void, discussionRail?: Rea
|
||||
scene={scene}
|
||||
beat={beat}
|
||||
onAdvance={onAdvance}
|
||||
onRunPreparedWorkflow={onRunPreparedWorkflow}
|
||||
discussionRail={discussionRail}
|
||||
/>,
|
||||
);
|
||||
@@ -173,6 +179,21 @@ describe("PreparedAuthoringLifecycleScene", () => {
|
||||
expect(onAdvance).toHaveBeenCalledTimes(advances ? 1 : 0);
|
||||
});
|
||||
|
||||
it("answers the terminal run request without advancing the presentation", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onAdvance = vi.fn();
|
||||
const onRunPreparedWorkflow = vi.fn().mockResolvedValue(undefined);
|
||||
renderBeat("deployment", onAdvance, undefined, onRunPreparedWorkflow);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /send message/i }));
|
||||
|
||||
expect(screen.getByRole("textbox", { name: /message to authoring assistant/i })).toHaveValue("");
|
||||
expect(screen.getByRole("button", { name: /run.*1 tool call/i })).toBeInTheDocument();
|
||||
expect(screen.getByText(/three proposed issues are ready for review/i)).toBeInTheDocument();
|
||||
expect(onRunPreparedWorkflow).toHaveBeenCalledOnce();
|
||||
expect(onAdvance).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["discover", "Discover"],
|
||||
["draft", "Draft"],
|
||||
|
||||
@@ -18,6 +18,7 @@ type PreparedAuthoringLifecycleSceneProps = {
|
||||
readonly scene: SceneDefinition;
|
||||
readonly beat: SceneBeatDefinition;
|
||||
readonly onAdvance?: (() => void) | undefined;
|
||||
readonly onRunPreparedWorkflow?: (() => Promise<void>) | undefined;
|
||||
readonly discussionRail?: ReactNode;
|
||||
};
|
||||
|
||||
@@ -40,7 +41,7 @@ const steps = [
|
||||
* Each beat shows a persistent prepared assistant beside one dominant phase
|
||||
* projection sourced from the prepared authoring recording.
|
||||
*/
|
||||
export const PreparedAuthoringLifecycleScene = ({ scene, beat, onAdvance, discussionRail }: PreparedAuthoringLifecycleSceneProps) => {
|
||||
export const PreparedAuthoringLifecycleScene = ({ scene, beat, onAdvance, onRunPreparedWorkflow, discussionRail }: PreparedAuthoringLifecycleSceneProps) => {
|
||||
const [messageState, dispatch] = useReducer(
|
||||
preparedLifecycleMessageReducer,
|
||||
initialPreparedLifecycleMessageState,
|
||||
@@ -81,7 +82,12 @@ export const PreparedAuthoringLifecycleScene = ({ scene, beat, onAdvance, discus
|
||||
dispatch({ type: "artifact_submitted" });
|
||||
onAdvance?.();
|
||||
}
|
||||
if (step === "deployment") dispatch({ type: "run_requested" });
|
||||
if (step === "deployment") {
|
||||
dispatch({ type: "run_requested" });
|
||||
// This is the same mode-aware action as the footer control; the
|
||||
// presentation remains on this beat so the presenter advances it.
|
||||
void onRunPreparedWorkflow?.();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="prepared-lifecycle-scene__presentation">
|
||||
|
||||
@@ -41,7 +41,8 @@ describe("PresentationAssistantPane", () => {
|
||||
);
|
||||
expect(screen.getByRole("heading", { name: /authoring assistant/i })).toBeInTheDocument();
|
||||
expect(screen.getByText(/current phase: validate/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/prepared replay only/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/prepared assistant transcript/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/final run request uses the configured demo target/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("exposes an explicit support role when composed in the lifecycle scene", () => {
|
||||
@@ -129,6 +130,15 @@ describe("PresentationAssistantPane", () => {
|
||||
cleanup();
|
||||
renderPane("deployment", { runRequested: "Run this deployment" });
|
||||
expect(screen.getByRole("button", { name: /send message/i })).toBeDisabled();
|
||||
expect(screen.getByText("Run this deployment")).toBeInTheDocument();
|
||||
expect(screen.getByText(/starting lda_report_case_study\.default with the prepared input/i))
|
||||
.toBeInTheDocument();
|
||||
const runTools = screen.getByRole("button", { name: /run.*1 tool call/i });
|
||||
expect(runTools).toHaveAttribute("aria-expanded", "false");
|
||||
await user.click(runTools);
|
||||
await user.click(screen.getByRole("button", { name: /startRun/i }));
|
||||
expect(screen.getByText(/run_recorded_lda_report/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/paused at the typed issue-review boundary/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders submitted overrides while keeping the replay tools canonical", () => {
|
||||
|
||||
@@ -58,7 +58,10 @@ export const PresentationAssistantPane = ({
|
||||
};
|
||||
const { submit, handleKeyDown } = usePreparedComposerSubmit(
|
||||
canSubmit,
|
||||
() => onSubmit(draft),
|
||||
() => {
|
||||
onSubmit(draft);
|
||||
setDraft("");
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -74,7 +77,7 @@ export const PresentationAssistantPane = ({
|
||||
<h2>Authoring assistant</h2>
|
||||
<p>Current phase: {phaseLabels[phase]}</p>
|
||||
<p className="presentation-assistant-pane__disclosure">
|
||||
Prepared replay only. No live actions or RPC calls.
|
||||
Prepared assistant transcript. The final run request uses the configured demo target.
|
||||
</p>
|
||||
</header>
|
||||
<div className="presentation-assistant-pane__conversation">
|
||||
@@ -83,6 +86,7 @@ export const PresentationAssistantPane = ({
|
||||
activePhase={phase}
|
||||
surface="stage"
|
||||
requestOverrides={submittedOverrides}
|
||||
runRequested={runRequested}
|
||||
/>
|
||||
</div>
|
||||
<form className="presentation-assistant-pane__composer" onSubmit={submit}>
|
||||
@@ -106,7 +110,7 @@ export const PresentationAssistantPane = ({
|
||||
</p>
|
||||
{runRequested !== null ? (
|
||||
<p role="status" className="presentation-assistant-pane__run-status">
|
||||
Run request prepared for the next execution slice.
|
||||
Prepared run receipt added. Continue when you are ready.
|
||||
</p>
|
||||
) : null}
|
||||
</form>
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
authoringToolGroupId,
|
||||
projectPreparedAuthoring,
|
||||
projectPreparedAuthoringThread,
|
||||
projectPreparedRunRequestExchange,
|
||||
type AuthoringPhaseId,
|
||||
} from "./authoring-recording.js";
|
||||
|
||||
@@ -225,3 +226,27 @@ describe("projectPreparedAuthoringThread", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("projectPreparedRunRequestExchange", () => {
|
||||
it("projects the prepared start receipt and typed interrupt without navigation actions", () => {
|
||||
const messages = projectPreparedRunRequestExchange("Run the saved deployment.");
|
||||
const parts = messages.flatMap((message) => message.parts);
|
||||
|
||||
expect(messages[0]).toMatchObject({ role: "user", parts: [{ text: "Run the saved deployment." }] });
|
||||
expect(parts).toContainEqual(expect.objectContaining({
|
||||
type: "tool-call",
|
||||
call: expect.objectContaining({ name: "startRun" }),
|
||||
}));
|
||||
expect(parts).toContainEqual(expect.objectContaining({
|
||||
type: "tool-result",
|
||||
result: expect.objectContaining({
|
||||
output: expect.objectContaining({
|
||||
run_id: "run_recorded_lda_report",
|
||||
status: "interrupted",
|
||||
resume_readiness: "ready",
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
expect(parts.some((part) => part.type === "presentation-action")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -354,5 +354,62 @@ export const projectPreparedAuthoringThread = (
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Projects the terminal prepared request into the same chat vocabulary as a
|
||||
* live run, while retaining the stable replay run identity used by later scenes.
|
||||
*/
|
||||
export const projectPreparedRunRequestExchange = (
|
||||
request: string,
|
||||
): readonly AgentMessage[] => {
|
||||
const callId = "authoring-run-request-call";
|
||||
return [
|
||||
agentTextMessage("authoring-run-request-user", "user", request),
|
||||
agentTextMessage(
|
||||
"authoring-run-request-starting",
|
||||
"assistant",
|
||||
"Starting lda_report_case_study.default with the prepared input.",
|
||||
),
|
||||
{
|
||||
// The authoring prefix reuses the compact phase tool-group treatment;
|
||||
// presenters can expand the real-shaped receipt only when useful.
|
||||
id: "authoring-run-tools",
|
||||
role: "assistant",
|
||||
parts: [
|
||||
agentToolCallPart(callId, "startRun", {
|
||||
operation: "workflow.runs.start",
|
||||
deployment_id: "lda_report_case_study.default",
|
||||
input_file: "examples/lda_report_workflow/run-input.json",
|
||||
equivalent_cli:
|
||||
"uv run wf --url http://127.0.0.1:8765/rpc run start lda_report_case_study.default --input-file examples/lda_report_workflow/run-input.json",
|
||||
}),
|
||||
agentToolResultPart(callId, "startRun", "success", {
|
||||
artifact_id: "lda_report_case_study",
|
||||
artifact_version: 1,
|
||||
deployment_id: "lda_report_case_study.default",
|
||||
diagnostics: [],
|
||||
interrupt: {
|
||||
id: "interrupt:review_issues",
|
||||
kind: "issue_review",
|
||||
node_id: "review_issues",
|
||||
outcomes: ["submitted", "cancelled"],
|
||||
proposed_issue_count: 3,
|
||||
resumable: true,
|
||||
typed: true,
|
||||
},
|
||||
resume_readiness: "ready",
|
||||
run_id: "run_recorded_lda_report",
|
||||
status: "interrupted",
|
||||
trace_count: 6,
|
||||
}),
|
||||
],
|
||||
},
|
||||
agentTextMessage(
|
||||
"authoring-run-request-paused",
|
||||
"assistant",
|
||||
"The run started and paused at the typed issue-review boundary. Three proposed issues are ready for review.",
|
||||
),
|
||||
];
|
||||
};
|
||||
|
||||
/** Returns the full prepared authoring recording. */
|
||||
export const projectPreparedAuthoring = (): readonly PreparedAuthoringPhase[] => recording;
|
||||
|
||||
+5
-4
@@ -43,14 +43,14 @@ describe("prepared lifecycle staged message state", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves edited draft text exactly when submitting draft", () => {
|
||||
it("preserves submitted text exactly and clears the composer draft", () => {
|
||||
const edited = preparedLifecycleMessageReducer(initialPreparedLifecycleMessageState, {
|
||||
type: "draft_edited",
|
||||
draft: " Check only the report binding. ",
|
||||
});
|
||||
|
||||
expect(preparedLifecycleMessageReducer(edited, { type: "draft_submitted" })).toEqual({
|
||||
draft: edited.draft,
|
||||
draft: "",
|
||||
submittedOverrides: { validate: edited.draft },
|
||||
runRequested: null,
|
||||
});
|
||||
@@ -63,7 +63,7 @@ describe("prepared lifecycle staged message state", () => {
|
||||
});
|
||||
|
||||
expect(preparedLifecycleMessageReducer(state, { type: "artifact_submitted" })).toEqual({
|
||||
draft: state.draft,
|
||||
draft: "",
|
||||
submittedOverrides: { deployment: state.draft },
|
||||
runRequested: null,
|
||||
});
|
||||
@@ -76,7 +76,7 @@ describe("prepared lifecycle staged message state", () => {
|
||||
});
|
||||
|
||||
expect(preparedLifecycleMessageReducer(state, { type: "discover_submitted" })).toEqual({
|
||||
draft: state.draft,
|
||||
draft: "",
|
||||
submittedOverrides: { discover: state.draft },
|
||||
runRequested: null,
|
||||
});
|
||||
@@ -106,6 +106,7 @@ describe("prepared lifecycle staged message state", () => {
|
||||
const requested = preparedLifecycleMessageReducer(state, { type: "run_requested" });
|
||||
|
||||
expect(requested.runRequested).toBe("Run this deployment");
|
||||
expect(requested.draft).toBe("");
|
||||
expect(requested.submittedOverrides).toEqual({});
|
||||
expect(preparedLifecycleMessageReducer(requested, { type: "run_requested" })).toBe(requested);
|
||||
});
|
||||
|
||||
@@ -57,6 +57,9 @@ const submitOverride = (
|
||||
|
||||
return {
|
||||
...state,
|
||||
// The submitted text is preserved in the transcript override; leaving it
|
||||
// in the composer would leak the previous phase's request into the next.
|
||||
draft: "",
|
||||
submittedOverrides: {
|
||||
...state.submittedOverrides,
|
||||
[destination]: state.draft,
|
||||
@@ -79,7 +82,7 @@ export const preparedLifecycleMessageReducer = (
|
||||
return submitOverride(state, "deployment");
|
||||
case "run_requested":
|
||||
return state.runRequested === null && hasText(state.draft)
|
||||
? { ...state, runRequested: state.draft }
|
||||
? { ...state, draft: "", runRequested: state.draft }
|
||||
: state;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ type PreparedComposerSubmitHandlers = {
|
||||
};
|
||||
|
||||
export const PREPARED_COMPOSER_HELP =
|
||||
"Shift+Enter adds a new line. This is a deterministic prepared replay, not a live model request.";
|
||||
"Shift+Enter adds a new line. Responses are prepared; the final run request may use the configured workflow target.";
|
||||
|
||||
/** Shares the prepared-replay composer keyboard and form submission contract. */
|
||||
export const usePreparedComposerSubmit = (
|
||||
|
||||
@@ -232,7 +232,7 @@ describe("AssistantOperatorThread", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("can show the latest response in a static comparison transcript", async () => {
|
||||
it("can show the latest response even when an authoring group is synchronized", async () => {
|
||||
const setScrollTop = vi.fn();
|
||||
const descriptors = {
|
||||
scrollTop: Object.getOwnPropertyDescriptor(HTMLDivElement.prototype, "scrollTop"),
|
||||
@@ -246,7 +246,14 @@ describe("AssistantOperatorThread", () => {
|
||||
scrollHeight: { configurable: true, get: () => 240 },
|
||||
clientHeight: { configurable: true, get: () => 80 },
|
||||
});
|
||||
render(<AssistantOperatorThread mode="dock" messages={[]} scrollMode="end" />);
|
||||
render(
|
||||
<AssistantOperatorThread
|
||||
mode="dock"
|
||||
messages={[]}
|
||||
activeToolGroupId="authoring-deployment"
|
||||
scrollMode="end"
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => expect(setScrollTop).toHaveBeenCalledWith(160));
|
||||
} finally {
|
||||
|
||||
@@ -276,9 +276,9 @@ export const AssistantOperatorThread = ({
|
||||
const viewport = viewportRef.current;
|
||||
if (!viewport) return;
|
||||
|
||||
// Static comparison transcripts should show their final answer; live
|
||||
// presentation docks instead keep the beat-owned tool group in view.
|
||||
if (!activeToolGroupId && scrollMode === "end") {
|
||||
// A terminal exchange must override phase anchoring so its result and
|
||||
// final assistant response remain visible after the composer submits.
|
||||
if (scrollMode === "end") {
|
||||
viewport.scrollTop = Math.max(0, viewport.scrollHeight - viewport.clientHeight);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ describe("PresenterNote", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("region", { name: "Beat goal" })).toHaveTextContent(note.goal);
|
||||
const goal = screen.getByRole("region", { name: "Beat goal" });
|
||||
expect(goal).toHaveTextContent(note.goal.replaceAll("**", ""));
|
||||
expect(within(goal).getByText("one public operation").tagName).toBe("STRONG");
|
||||
|
||||
const anchors = screen.getByRole("region", { name: "Anchor terms" });
|
||||
const anchorList = within(anchors).getByRole("list");
|
||||
|
||||
@@ -28,7 +28,7 @@ export const PresenterNote = ({ note, cumulativeSeconds, next, covered, onCovere
|
||||
|
||||
<section className="presenter-note__goal" aria-labelledby="presenter-goal">
|
||||
<span id="presenter-goal">Beat goal</span>
|
||||
<p>{note.goal}</p>
|
||||
<div className="presenter-note__goal-copy"><ReactMarkdown>{note.goal}</ReactMarkdown></div>
|
||||
</section>
|
||||
|
||||
<section className="presenter-note__anchors" aria-labelledby="presenter-anchors">
|
||||
|
||||
@@ -59,7 +59,7 @@ export const presenterNotes = [
|
||||
"thesis",
|
||||
"title",
|
||||
15,
|
||||
"Separate the AI-agent ambition from the implemented contribution.",
|
||||
"Separate the **AI-agent ambition** from the implemented contribution.",
|
||||
["AI-agent goal", "platform underneath"],
|
||||
"The title describes the original goal: an AI agent for workspace automation. My contribution is the platform underneath that agent.",
|
||||
["Thesis Abstract and Introduction"],
|
||||
@@ -72,7 +72,7 @@ export const presenterNotes = [
|
||||
"thesis",
|
||||
"substrate",
|
||||
15,
|
||||
"State what the platform lets its users do.",
|
||||
"State **what the platform lets its users do**.",
|
||||
["agents and humans", "build, run, inspect"],
|
||||
"It lets agents and humans build workflows, run them, and inspect what happened.",
|
||||
["Thesis Abstract and Introduction", "Thesis Contributions"],
|
||||
@@ -82,7 +82,7 @@ export const presenterNotes = [
|
||||
"problem",
|
||||
"direct-actions",
|
||||
15,
|
||||
"Show why one successful chat is not yet automation.",
|
||||
"Show why **one successful chat is not yet automation**.",
|
||||
["tool calls", "not reusable"],
|
||||
"Like the chat example, an agent can call tools and finish one task. But that conversation is not yet a reusable workflow.",
|
||||
["Thesis Problem Statement and Requirements"],
|
||||
@@ -92,7 +92,7 @@ export const presenterNotes = [
|
||||
"problem",
|
||||
"missing-contracts",
|
||||
15,
|
||||
"Name the minimum durable properties reusable automation needs.",
|
||||
"Name the **minimum durable properties** reusable automation needs.",
|
||||
["saved definition", "validation", "execution records"],
|
||||
"Reusable automation needs a saved definition, validation, execution records, and a clear way to pause and continue.",
|
||||
["Thesis Problem Statement and Requirements"],
|
||||
@@ -102,7 +102,7 @@ export const presenterNotes = [
|
||||
"positioning",
|
||||
"landscape",
|
||||
18,
|
||||
"Place the work beside familiar adjacent systems.",
|
||||
"Place the work beside **familiar adjacent systems**.",
|
||||
["Python / n8n / Zapier", "LangGraph", "MCP"],
|
||||
"Existing systems solve different parts of this problem: Python scripts, n8n, Zapier, LangGraph, and MCP.",
|
||||
["Thesis Positioning and Related Systems"],
|
||||
@@ -112,7 +112,7 @@ export const presenterNotes = [
|
||||
"positioning",
|
||||
"lda-position",
|
||||
17,
|
||||
"State the platform's narrow position without a superiority claim.",
|
||||
"State the platform's **narrow position**, without a superiority claim.",
|
||||
["provider-neutral", "workflow layer", "not a replacement"],
|
||||
"My platform does not replace them. It provides a provider-neutral workflow layer that agents and humans can operate.",
|
||||
["Thesis Positioning and Related Systems", "Thesis Source Model"],
|
||||
@@ -122,7 +122,7 @@ export const presenterNotes = [
|
||||
"planner-runtime",
|
||||
"planner",
|
||||
12,
|
||||
"Assign workflow decisions to an external planner.",
|
||||
"Assign workflow decisions to an **external planner**.",
|
||||
["human or AI planner"],
|
||||
"A human or AI planner decides what workflow to build.",
|
||||
["Thesis Architecture Overview"],
|
||||
@@ -132,7 +132,7 @@ export const presenterNotes = [
|
||||
"planner-runtime",
|
||||
"runtime",
|
||||
16,
|
||||
"Assign execution and recording to the runtime.",
|
||||
"Assign **execution and recording** to the runtime.",
|
||||
["validation", "step-by-step execution", "state and traces"],
|
||||
"The runtime validates the graph, executes it step by step, records state and traces, and pauses at declared boundaries.",
|
||||
["Thesis Workflow Core", "Thesis Architecture Overview"],
|
||||
@@ -146,7 +146,7 @@ export const presenterNotes = [
|
||||
"planner-runtime",
|
||||
"boundary",
|
||||
12,
|
||||
"Introduce the public seam between clients and runtime.",
|
||||
"Introduce the **public seam** between clients and runtime.",
|
||||
["Workflow API", "CLI", "JSON-RPC"],
|
||||
"Both sides communicate through the Workflow API. Today, clients reach it through the CLI or JSON-RPC without accessing runtime internals directly.",
|
||||
["Thesis Architecture Overview", "docs/source_architecture.md"],
|
||||
@@ -156,7 +156,7 @@ export const presenterNotes = [
|
||||
"lifecycle",
|
||||
"draft",
|
||||
9,
|
||||
"Introduce the editable lifecycle state.",
|
||||
"Introduce the **editable lifecycle state**.",
|
||||
["Draft", "being built"],
|
||||
"A workflow moves through four lifecycle stages. Draft means the workflow is still being built.",
|
||||
["Thesis Workflow Lifecycle"],
|
||||
@@ -166,7 +166,7 @@ export const presenterNotes = [
|
||||
"lifecycle",
|
||||
"artifact",
|
||||
9,
|
||||
"Introduce the immutable saved definition.",
|
||||
"Introduce the **immutable saved definition**.",
|
||||
["Artifact", "immutable version"],
|
||||
"Artifact is a saved, immutable version.",
|
||||
["Thesis Workflow Lifecycle"],
|
||||
@@ -175,7 +175,7 @@ export const presenterNotes = [
|
||||
"lifecycle",
|
||||
"deployment",
|
||||
9,
|
||||
"Connect a saved definition to a runnable environment.",
|
||||
"Connect a saved definition to a **runnable environment**.",
|
||||
["Deployment", "sources", "ready"],
|
||||
"Deployment connects that version to the sources it needs and checks whether it is ready.",
|
||||
["Thesis Workflow Lifecycle"],
|
||||
@@ -184,7 +184,7 @@ export const presenterNotes = [
|
||||
"lifecycle",
|
||||
"run",
|
||||
9,
|
||||
"Introduce one persisted execution record.",
|
||||
"Introduce one **persisted execution record**.",
|
||||
["Run", "status", "output and trace"],
|
||||
"Run is one recorded execution, including its status, output, and trace.",
|
||||
["Thesis Workflow Lifecycle"],
|
||||
@@ -194,27 +194,27 @@ export const presenterNotes = [
|
||||
"architecture",
|
||||
"overview",
|
||||
6,
|
||||
"Show how the implementation realizes the earlier concepts.",
|
||||
"Show how the **implementation realizes the earlier concepts**.",
|
||||
["architecture spine"],
|
||||
"This is how those concepts are organized in the implementation.",
|
||||
"Now I will show how those concepts map to the implementation.",
|
||||
["Thesis System Architecture", "docs/project_map.md"],
|
||||
),
|
||||
beatNote(
|
||||
"architecture",
|
||||
"client",
|
||||
8,
|
||||
"Show that humans and agents share one public surface.",
|
||||
"Show that humans and agents share **one public surface**.",
|
||||
["shared operations"],
|
||||
"Humans and agents use the same public workflow operations.",
|
||||
"At the top, humans and agents use the same public workflow operations.",
|
||||
["Thesis System Architecture", "docs/project_map.md"],
|
||||
),
|
||||
beatNote(
|
||||
"architecture",
|
||||
"api",
|
||||
9,
|
||||
"Identify the system's public front door.",
|
||||
"Identify the system's **public front door**.",
|
||||
["Workflow API", "public boundary"],
|
||||
"The Workflow API is the front door. It exposes lifecycle operations without exposing runtime internals.",
|
||||
"Those operations enter through the Workflow API, the public boundary that keeps clients out of runtime internals.",
|
||||
["Thesis System Architecture", "docs/source_architecture.md"],
|
||||
{ qnaBranchIds: ["not-just-cli"] },
|
||||
),
|
||||
@@ -222,9 +222,9 @@ export const presenterNotes = [
|
||||
"architecture",
|
||||
"runtime",
|
||||
9,
|
||||
"Explain what the server composes behind the API.",
|
||||
"Explain what the server composes **behind the API**.",
|
||||
["WorkflowServer", "records and capabilities", "execution core"],
|
||||
"Behind it, the workflow server brings together stored records, available capabilities, and the execution core.",
|
||||
"Behind that boundary, the workflow server brings together stored records, available capabilities, and the execution core.",
|
||||
["Thesis System Architecture", "docs/source_architecture.md"],
|
||||
{ qnaBranchIds: ["provider-security"] },
|
||||
),
|
||||
@@ -232,7 +232,7 @@ export const presenterNotes = [
|
||||
"agent-handoff",
|
||||
"request",
|
||||
12,
|
||||
"Disclose the prepared demonstration before it begins.",
|
||||
"Disclose the **prepared demonstration** before it begins.",
|
||||
["prepared example", "not an autonomous planner"],
|
||||
"This is a prepared example, not a live autonomous AI agent. It shows how an agent could use the platform to build and run a workflow.",
|
||||
["Constrained demo agent and prepared replay recipe"],
|
||||
@@ -245,7 +245,7 @@ export const presenterNotes = [
|
||||
"prepared-lifecycle",
|
||||
"discover",
|
||||
7,
|
||||
"Show that authoring starts with interface discovery.",
|
||||
"Show that authoring starts with **interface discovery**.",
|
||||
["sources", "capabilities"],
|
||||
"First, the agent checks which sources and operations are available.",
|
||||
["examples/lda_report_workflow", "deployment inspect replay evidence"],
|
||||
@@ -255,7 +255,7 @@ export const presenterNotes = [
|
||||
"prepared-lifecycle",
|
||||
"draft",
|
||||
7,
|
||||
"Show mutable workflow authoring.",
|
||||
"Show **mutable workflow authoring**.",
|
||||
["Draft", "editable workflow"],
|
||||
"Then it builds an editable workflow draft.",
|
||||
["examples/lda_report_workflow", "deployment inspect replay evidence", "CLI documentation", "Draft authoring API"],
|
||||
@@ -265,7 +265,7 @@ export const presenterNotes = [
|
||||
"prepared-lifecycle",
|
||||
"diagnose",
|
||||
7,
|
||||
"Show a concrete structured validation failure.",
|
||||
"Show a **concrete structured validation failure**.",
|
||||
["validation", "missing_outcome_edge"],
|
||||
"Validation finds that the analyze step has no route for its ok outcome.",
|
||||
["examples/lda_report_workflow", "deployment inspect replay evidence"],
|
||||
@@ -275,7 +275,7 @@ export const presenterNotes = [
|
||||
"prepared-lifecycle",
|
||||
"repair",
|
||||
7,
|
||||
"Show the exact focused correction and revalidation.",
|
||||
"Show the **focused correction and revalidation**.",
|
||||
["set-route", "validation passes"],
|
||||
"The agent adds that route, and validation passes.",
|
||||
["Validation and diagnostics", "Challenge UX findings"],
|
||||
@@ -285,7 +285,7 @@ export const presenterNotes = [
|
||||
"prepared-lifecycle",
|
||||
"artifact",
|
||||
7,
|
||||
"Show the transition to an immutable saved version.",
|
||||
"Show the transition to an **immutable saved version**.",
|
||||
["Artifact", "immutable"],
|
||||
"The valid workflow is saved as an immutable artifact.",
|
||||
["examples/lda_report_workflow", "deployment inspect replay evidence"],
|
||||
@@ -294,7 +294,7 @@ export const presenterNotes = [
|
||||
"prepared-lifecycle",
|
||||
"deployment",
|
||||
7,
|
||||
"Show source binding and readiness before execution.",
|
||||
"Show **source binding and readiness** before execution.",
|
||||
["Deployment", "three local sources"],
|
||||
"Finally, a deployment connects it to the three local sources it needs.",
|
||||
["examples/lda_report_workflow", "deployment inspect replay evidence", "Thesis deterministic report case study"],
|
||||
@@ -304,7 +304,7 @@ export const presenterNotes = [
|
||||
"run-from-deployment",
|
||||
"input",
|
||||
11,
|
||||
"Show the concrete inputs supplied before execution.",
|
||||
"Show the **concrete inputs** supplied before execution.",
|
||||
["run input", "selected documents"],
|
||||
"The deployment receives selected local documents and an issue-board path.",
|
||||
["workflow.runs.start replay evidence"],
|
||||
@@ -313,7 +313,7 @@ export const presenterNotes = [
|
||||
"run-from-deployment",
|
||||
"operation",
|
||||
12,
|
||||
"Show that one public operation creates a persisted execution.",
|
||||
"Show that **one public operation** creates a persisted execution.",
|
||||
["workflow.runs.start", "persisted Run"],
|
||||
"The public **workflow.runs.start** operation validates the deployment and input, creates a **persisted Run**, and begins the reusable graph.",
|
||||
["workflow.runs.start replay evidence"],
|
||||
@@ -323,7 +323,7 @@ export const presenterNotes = [
|
||||
"run-from-deployment",
|
||||
"graph",
|
||||
12,
|
||||
"Show the reusable workflow executing beyond the chat conversation.",
|
||||
"Show the **reusable workflow** executing beyond the chat conversation.",
|
||||
["workflow graph", "declared interrupt"],
|
||||
"The graph reads documents, analyzes them, builds a report, drafts proposed issues, and pauses at a declared review interrupt before issue-board changes.",
|
||||
["workflow.runs.start replay evidence", "examples/lda_report_workflow"],
|
||||
@@ -333,7 +333,7 @@ export const presenterNotes = [
|
||||
"typed-human-boundary",
|
||||
"interrupt",
|
||||
15,
|
||||
"Show what the paused workflow asks from the operator.",
|
||||
"Show what the **paused workflow asks** from the operator.",
|
||||
["issue_review", "interrupt payload", "resume schema"],
|
||||
"Execution pauses at a **typed issue_review interrupt** exposing request data, allowed outcomes, request schema, and resume schema.",
|
||||
["Typed interrupt payload and resume contract"],
|
||||
@@ -343,9 +343,9 @@ export const presenterNotes = [
|
||||
"typed-human-boundary",
|
||||
"approval",
|
||||
15,
|
||||
"Show that the operator chooses a declared continuation.",
|
||||
"Show that the operator chooses a **declared continuation**.",
|
||||
["submitted", "revision-requested", "typed resume"],
|
||||
"The operator chooses submitted or revision-requested; this is a typed interrupt and resume contract, not a production approval gate, role system, or policy engine.",
|
||||
"The operator can Submit or Request revision. Both continue through declared workflow branches; this typed resume contract is not a production approval system.",
|
||||
["Typed interrupt payload and resume contract"],
|
||||
{ warning: "Both outcomes resume through declared workflow branches; this is not production approval governance.", qnaBranchIds: ["typed-interrupts", "security-production-boundary"] },
|
||||
),
|
||||
@@ -353,7 +353,7 @@ export const presenterNotes = [
|
||||
"resume-output-evidence",
|
||||
"resume",
|
||||
16,
|
||||
"Show continuation of the same recorded run.",
|
||||
"Show continuation of the **same recorded run**.",
|
||||
["workflow.runs.resume", "same Run"],
|
||||
"On the submitted path, **workflow.runs.resume continues the recorded interrupted Run**.",
|
||||
["workflow.runs.resume replay evidence", "Revision replay identity"],
|
||||
@@ -363,7 +363,7 @@ export const presenterNotes = [
|
||||
"resume-output-evidence",
|
||||
"output",
|
||||
16,
|
||||
"Show the persisted terminal results of the submitted path.",
|
||||
"Show the **persisted terminal results** of the submitted path.",
|
||||
["report output", "issue-board changes"],
|
||||
"The workflow creates the report and issue-board changes, then records terminal output.",
|
||||
["workflow.runs.resume replay evidence", "examples/lda_report_workflow"],
|
||||
@@ -373,19 +373,19 @@ export const presenterNotes = [
|
||||
"resume-output-evidence",
|
||||
"trace",
|
||||
18,
|
||||
"Show that execution evidence remains inspectable after completion.",
|
||||
"Show that **execution evidence remains inspectable** after completion.",
|
||||
["trace frames", "protocol evidence"],
|
||||
"Trace frames and protocol evidence remain inspectable; this is declared-boundary resumability, not arbitrary crash recovery or exactly-once execution. The revision replay is a separate prepared recording.",
|
||||
["workflow.runs.resume replay evidence", "Revision replay identity"],
|
||||
{ warning: "Never claim run-ID continuity for the prepared revision recording.", qnaBranchIds: ["replay-provenance", "demo-reliability"] },
|
||||
"Trace frames and protocol evidence remain inspectable; this is declared-boundary resumability, not arbitrary crash recovery or exactly-once execution.",
|
||||
["workflow.runs.resume replay evidence"],
|
||||
{ qnaBranchIds: ["replay-provenance", "demo-reliability"] },
|
||||
),
|
||||
beatNote(
|
||||
"evaluation",
|
||||
"cohort",
|
||||
40,
|
||||
"Describe the external-agent evaluation design.",
|
||||
"Describe the **external-agent evaluation design**.",
|
||||
["36 trials", "two challenges", "three profiles"],
|
||||
"The evaluation combines conformance tests, deterministic case studies, and a **manually audited external-agent campaign**: 36 trials across two challenges, two hosted models, three instruction profiles, and three waves, with three attempts per cell.",
|
||||
"The evaluation combines conformance tests, deterministic case studies, and a **manually audited external-agent campaign**. Each model, under each instruction profile, attempted each challenge three times, for 36 trials in total.",
|
||||
["Thesis Evaluation and Appendix C"],
|
||||
{ qnaBranchIds: ["evaluation-validity"] },
|
||||
),
|
||||
@@ -393,9 +393,9 @@ export const presenterNotes = [
|
||||
"evaluation",
|
||||
"validity",
|
||||
40,
|
||||
"Separate audited valid evidence from contaminated samples.",
|
||||
"Separate **audited valid evidence** from contaminated samples.",
|
||||
["27 pass", "8 invalid", "1 fail"],
|
||||
"The author audit classified 27 trials as clean product-path passes, eight as invalid samples, and one as a failure. Invalid samples included contamination such as reading implementation files, prior artifacts, adjacent attempts, or evaluator state.",
|
||||
"The author audit then classified 27 trials as clean product-path passes, eight as invalid samples, and one as a failure. Invalid samples included contamination such as reading implementation files, prior artifacts, adjacent attempts, or evaluator state.",
|
||||
["Thesis Evaluation and Appendix C", "Author audit"],
|
||||
{ qnaBranchIds: ["evaluation-validity"] },
|
||||
),
|
||||
@@ -403,9 +403,9 @@ export const presenterNotes = [
|
||||
"evaluation",
|
||||
"findings",
|
||||
40,
|
||||
"State what the evaluation supports and what it cannot prove.",
|
||||
["longitudinal evidence", "not a benchmark"],
|
||||
"Because prompts, product snapshots, and hosted conditions changed across waves, these results are **longitudinal engineering evidence**. They expose authoring and diagnostic gaps, **not a benchmark** of model success, token reduction, retry reduction, or superiority.",
|
||||
"State **what the evaluation supports** and what it cannot prove.",
|
||||
["product evolved", "not a benchmark"],
|
||||
"But the prompts, product, and hosted conditions changed during the campaign. These results show what we learned while improving the product. They expose authoring and diagnostic gaps; they are **not a benchmark** of model success, token reduction, retry reduction, or superiority.",
|
||||
["Thesis Evaluation and Appendix C", "Thesis Threats to Validity"],
|
||||
{ warning: "Use non-benchmark wording; do not report the counts as general model performance.", qnaBranchIds: ["evaluation-validity"] },
|
||||
),
|
||||
@@ -413,7 +413,7 @@ export const presenterNotes = [
|
||||
"conclusion",
|
||||
"limits",
|
||||
18,
|
||||
"Bound the prototype claims before the final contribution statement.",
|
||||
"Bound the **prototype claims** before the final contribution statement.",
|
||||
["prototype", "not production security"],
|
||||
"The prototype uses trusted in-process Python and file-backed stores; it does not provide production authentication, RBAC, sandboxing, scheduling, arbitrary crash recovery, or a bundled autonomous planner.",
|
||||
["Thesis Limitations"],
|
||||
@@ -423,7 +423,7 @@ export const presenterNotes = [
|
||||
"conclusion",
|
||||
"future",
|
||||
18,
|
||||
"Name the surrounding layers left as future work.",
|
||||
"Name the surrounding layers left as **future work**.",
|
||||
["live agent", "scheduling", "controlled evaluation"],
|
||||
"A live agent interface, transactional storage, richer debugging, security hardening, scheduling, and controlled comparative evaluation remain future work.",
|
||||
["Thesis Future Work"],
|
||||
@@ -433,7 +433,7 @@ export const presenterNotes = [
|
||||
"conclusion",
|
||||
"conclusion",
|
||||
20,
|
||||
"Restate the implemented contribution and planner-runtime boundary.",
|
||||
"Restate the **implemented contribution** and planner-runtime boundary.",
|
||||
["planner proposes", "platform executes"],
|
||||
"The contribution is **architectural and implemented**: external planners can propose workflows while a typed platform **validates, binds, executes, persists, interrupts, resumes, and inspects** them through public operations.",
|
||||
["Thesis Contributions", "Thesis Conclusion"],
|
||||
@@ -443,7 +443,7 @@ export const presenterNotes = [
|
||||
"conclusion",
|
||||
"questions",
|
||||
19,
|
||||
"Open structured examiner discussion without introducing new claims.",
|
||||
"Open **structured examiner discussion** without introducing new claims.",
|
||||
["defense questions", "evidence"],
|
||||
"That boundary is the claim I will defend: reusable agent-operated automation is inspectable because planning and execution have explicit contracts. I welcome questions.",
|
||||
["Thesis Conclusion", "Defense Q&A index"],
|
||||
|
||||
@@ -114,7 +114,11 @@ describe("defense storyboard catalog", () => {
|
||||
|
||||
it("makes the evaluation beat carry the audited counts and validity boundary", () => {
|
||||
expect(findBeat("evaluation", "cohort")?.caption).toMatch(/36|two challenges|two hosted models|three waves/i);
|
||||
expect(findBeat("evaluation", "validity")?.caption).toMatch(/27|8|1|audit/i);
|
||||
const validityCaption = findBeat("evaluation", "validity")?.caption ?? "";
|
||||
expect(validityCaption).toMatch(/\b27\b/);
|
||||
expect(validityCaption).toMatch(/\b8\b/);
|
||||
expect(validityCaption).toMatch(/\b1\b/);
|
||||
expect(validityCaption).toMatch(/audit/i);
|
||||
expect(findBeat("evaluation", "findings")?.caption).toMatch(/changing|longitudinal|benchmark/i);
|
||||
});
|
||||
|
||||
|
||||
@@ -37,12 +37,23 @@ describe("presentationWorkflowPlan", () => {
|
||||
expect(node.capability, node.id).toBeTruthy();
|
||||
expect(node.inputSummary, node.id).toBeTruthy();
|
||||
expect(node.outputSummary, node.id).toBeTruthy();
|
||||
expect(node.outcomes, node.id).not.toHaveLength(0);
|
||||
expect(node.evidencePointer, node.id).toBeTruthy();
|
||||
|
||||
if (node.type !== "end") {
|
||||
expect(node.outcomes, node.id).toEqual(
|
||||
presentationWorkflowPlan.edges
|
||||
.filter((edge) => edge.from === node.id)
|
||||
.map((edge) => edge.outcome),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const interrupt = presentationWorkflowPlan.nodes.find((node) => node.id === "review_issues");
|
||||
expect(interrupt?.schemaSummary).toMatch(/request: issue proposals/i);
|
||||
expect(interrupt?.outcomes).toEqual(["submitted", "cancelled"]);
|
||||
expect(presentationWorkflowPlan.nodes.find((node) => node.id === "finalise")?.outcomes).toEqual([
|
||||
"completed",
|
||||
]);
|
||||
expect(presentationWorkflowPlan.nodes.find((node) => node.id === "revision_requested")?.outcomes).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -99,7 +99,7 @@ export const presentationWorkflowPlan = {
|
||||
capability: "State output",
|
||||
inputSummary: "Markdown report and issue-board state",
|
||||
outputSummary: "Completed run output",
|
||||
outcomes: ["ok"],
|
||||
outcomes: ["completed"],
|
||||
evidencePointer: "local.lda_report.finalise_report",
|
||||
},
|
||||
{
|
||||
@@ -111,7 +111,7 @@ export const presentationWorkflowPlan = {
|
||||
capability: "Revision record",
|
||||
inputSummary: "Issue-review decision",
|
||||
outputSummary: "Recorded revision request",
|
||||
outcomes: ["cancelled"],
|
||||
outcomes: [],
|
||||
evidencePointer: "local.lda_report.record_revision_request",
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user