feat: add lda report workflow demo panel
This commit is contained in:
@@ -21,6 +21,8 @@ export {
|
||||
WorkflowDeploymentsValidate,
|
||||
WorkflowRunsList,
|
||||
WorkflowRunsInspect,
|
||||
WorkflowRunsStart,
|
||||
WorkflowRunsResume,
|
||||
WorkflowRunsTrace,
|
||||
WorkflowRpcs,
|
||||
ArtifactRefSchema,
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
WorkflowRunsListResultSchema,
|
||||
WorkflowRunsInspectPayloadSchema,
|
||||
WorkflowRunsInspectResultSchema,
|
||||
WorkflowRunsStartPayloadSchema,
|
||||
WorkflowRunsResumePayloadSchema,
|
||||
WorkflowRunsTracePayloadSchema,
|
||||
WorkflowRunsTraceResultSchema,
|
||||
} from "./rpcs.js";
|
||||
@@ -24,7 +26,7 @@ export type OperationMeta = {
|
||||
readonly method: string;
|
||||
readonly label: string;
|
||||
readonly explanation: string;
|
||||
readonly idempotency: "read";
|
||||
readonly idempotency: "read" | "write";
|
||||
readonly equivalentCli: (params: unknown) => string;
|
||||
readonly interpret: (result: unknown) => unknown;
|
||||
};
|
||||
@@ -68,6 +70,37 @@ const interpretNextActions = (nextActions: {
|
||||
warnings: nextActions.warnings,
|
||||
});
|
||||
|
||||
/** Adapts a snake_case run detail from the server into camelCase for the browser. */
|
||||
const interpretRunDetail = (decoded: {
|
||||
readonly run_id: string;
|
||||
readonly deployment_id: string;
|
||||
readonly artifact_id: string;
|
||||
readonly artifact_version: number;
|
||||
readonly status: string;
|
||||
readonly resume_readiness: string;
|
||||
readonly interrupt: unknown;
|
||||
readonly outcome: string | null;
|
||||
readonly error: string | null;
|
||||
readonly output: Record<string, unknown> | null;
|
||||
readonly diagnostics: ReadonlyArray<unknown>;
|
||||
readonly trace_count: number;
|
||||
readonly next_actions: Parameters<typeof interpretNextActions>[0];
|
||||
}) => ({
|
||||
runId: decoded.run_id,
|
||||
deploymentId: decoded.deployment_id,
|
||||
artifactId: decoded.artifact_id,
|
||||
artifactVersion: decoded.artifact_version,
|
||||
status: decoded.status,
|
||||
resumeReadiness: decoded.resume_readiness,
|
||||
interrupt: decoded.interrupt,
|
||||
outcome: decoded.outcome,
|
||||
error: decoded.error,
|
||||
output: decoded.output,
|
||||
diagnostics: decoded.diagnostics,
|
||||
traceCount: decoded.trace_count,
|
||||
nextActions: interpretNextActions(decoded.next_actions),
|
||||
});
|
||||
|
||||
const operationEntries: ReadonlyArray<OperationMeta> = [
|
||||
{
|
||||
method: "workflow.health",
|
||||
@@ -324,6 +357,46 @@ const operationEntries: ReadonlyArray<OperationMeta> = [
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "workflow.runs.start",
|
||||
label: "Start run",
|
||||
explanation: "Start a workflow deployment run",
|
||||
idempotency: "write",
|
||||
equivalentCli: (params) => {
|
||||
const p = Schema.decodeUnknownSync(WorkflowRunsStartPayloadSchema)(
|
||||
params,
|
||||
{ onExcessProperty: "error" },
|
||||
);
|
||||
return `uv run wf run start ${p.deployment_id} --input '<json>'`;
|
||||
},
|
||||
interpret: (result) => {
|
||||
const decoded = Schema.decodeUnknownSync(WorkflowRunsInspectResultSchema)(
|
||||
result,
|
||||
{ onExcessProperty: "ignore" },
|
||||
);
|
||||
return interpretRunDetail(decoded);
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "workflow.runs.resume",
|
||||
label: "Resume run",
|
||||
explanation: "Resume an interrupted workflow run",
|
||||
idempotency: "write",
|
||||
equivalentCli: (params) => {
|
||||
const p = Schema.decodeUnknownSync(WorkflowRunsResumePayloadSchema)(
|
||||
params,
|
||||
{ onExcessProperty: "error" },
|
||||
);
|
||||
return `uv run wf run resume ${p.run_id} --payload '<json>'`;
|
||||
},
|
||||
interpret: (result) => {
|
||||
const decoded = Schema.decodeUnknownSync(WorkflowRunsInspectResultSchema)(
|
||||
result,
|
||||
{ onExcessProperty: "ignore" },
|
||||
);
|
||||
return interpretRunDetail(decoded);
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "workflow.runs.trace",
|
||||
label: "Read run trace",
|
||||
|
||||
@@ -242,6 +242,9 @@ const RunInterruptSchema = Schema.Struct({
|
||||
kind: Schema.String,
|
||||
payload: JsonObjectSchema,
|
||||
outcomes: Schema.Array(Schema.String),
|
||||
request_schema: Schema.optional(JsonObjectSchema),
|
||||
resume_schema: Schema.optional(JsonObjectSchema),
|
||||
typed: Schema.optional(Schema.Boolean),
|
||||
});
|
||||
|
||||
const RunNextActionsSchema = Schema.Struct({
|
||||
@@ -275,6 +278,31 @@ export const WorkflowRunsInspect = Rpc.make("workflow.runs.inspect", {
|
||||
error: Schema.Never,
|
||||
});
|
||||
|
||||
export const WorkflowRunsStartPayloadSchema = Schema.Struct({
|
||||
deployment_id: Schema.String,
|
||||
workflow_input: JsonObjectSchema,
|
||||
trace_range: Schema.optional(Schema.NullOr(TraceRangeSchema)),
|
||||
});
|
||||
|
||||
export const WorkflowRunsResumePayloadSchema = Schema.Struct({
|
||||
run_id: Schema.String,
|
||||
resume_payload: JsonObjectSchema,
|
||||
resume_outcome: Schema.optional(Schema.String),
|
||||
trace_range: Schema.optional(Schema.NullOr(TraceRangeSchema)),
|
||||
});
|
||||
|
||||
export const WorkflowRunsStart = Rpc.make("workflow.runs.start", {
|
||||
payload: WorkflowRunsStartPayloadSchema,
|
||||
success: WorkflowRunsInspectResultSchema,
|
||||
error: Schema.Never,
|
||||
});
|
||||
|
||||
export const WorkflowRunsResume = Rpc.make("workflow.runs.resume", {
|
||||
payload: WorkflowRunsResumePayloadSchema,
|
||||
success: WorkflowRunsInspectResultSchema,
|
||||
error: Schema.Never,
|
||||
});
|
||||
|
||||
export const WorkflowRunsTracePayloadSchema = Schema.Struct({
|
||||
run_id: Schema.String,
|
||||
trace_range: TraceRangeSchema,
|
||||
@@ -314,5 +342,7 @@ export const WorkflowRpcs = RpcGroup.make(
|
||||
WorkflowDeploymentsValidate,
|
||||
WorkflowRunsList,
|
||||
WorkflowRunsInspect,
|
||||
WorkflowRunsStart,
|
||||
WorkflowRunsResume,
|
||||
WorkflowRunsTrace,
|
||||
);
|
||||
|
||||
@@ -210,6 +210,115 @@ const lifecycleCases = [
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "workflow.runs.start" as const,
|
||||
params: {
|
||||
deployment_id: "lda_report_case_study.default",
|
||||
workflow_input: {
|
||||
selected_documents: [
|
||||
"project-brief.md",
|
||||
"architecture-notes.md",
|
||||
"evaluation-findings.md",
|
||||
"risk-register.md",
|
||||
"roadmap.md",
|
||||
],
|
||||
board_path: "issue-board.json",
|
||||
},
|
||||
trace_range: { start: 0, limit: 50 },
|
||||
},
|
||||
result: {
|
||||
run_id: "run_demo",
|
||||
deployment_id: "lda_report_case_study.default",
|
||||
artifact_id: "lda_report_case_study",
|
||||
artifact_version: 1,
|
||||
status: "interrupted",
|
||||
resume_readiness: "ready",
|
||||
interrupt: {
|
||||
kind: "issue_review",
|
||||
payload: {
|
||||
report_markdown: "# lda.chat Thesis And Project Readiness Report",
|
||||
proposed_issues: [
|
||||
{
|
||||
id: "demo-issue-1",
|
||||
title: "Prepare demo script",
|
||||
body: "Write the defense walkthrough.",
|
||||
severity: "medium",
|
||||
},
|
||||
],
|
||||
},
|
||||
outcomes: ["submitted", "cancelled"],
|
||||
request_schema: {
|
||||
type: "object",
|
||||
required: ["report_markdown", "proposed_issues"],
|
||||
},
|
||||
resume_schema: {
|
||||
type: "object",
|
||||
required: ["approved", "selected_issue_ids"],
|
||||
},
|
||||
typed: true,
|
||||
},
|
||||
outcome: null,
|
||||
error: null,
|
||||
output: null,
|
||||
diagnostics: [],
|
||||
trace_count: 1,
|
||||
next_actions: {
|
||||
can_continue: true,
|
||||
can_save_now: null,
|
||||
recommended_next_tool: "wf.workflow.resume_run",
|
||||
reason: "run is interrupted",
|
||||
patch_examples: [],
|
||||
warnings: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "workflow.runs.resume" as const,
|
||||
params: {
|
||||
run_id: "run_demo",
|
||||
resume_payload: {
|
||||
approved: true,
|
||||
selected_issue_ids: ["demo-issue-1"],
|
||||
comment: "Create selected issues.",
|
||||
},
|
||||
resume_outcome: "submitted",
|
||||
trace_range: { start: 0, limit: 50 },
|
||||
},
|
||||
result: {
|
||||
run_id: "run_demo",
|
||||
deployment_id: "lda_report_case_study.default",
|
||||
artifact_id: "lda_report_case_study",
|
||||
artifact_version: 1,
|
||||
status: "completed",
|
||||
resume_readiness: "not_applicable",
|
||||
interrupt: null,
|
||||
outcome: "completed",
|
||||
error: null,
|
||||
output: {
|
||||
approved: true,
|
||||
markdown: "# lda.chat Thesis And Project Readiness Report",
|
||||
created_issues: [
|
||||
{
|
||||
id: "ISSUE-001",
|
||||
title: "Prepare demo script",
|
||||
url: "local://issues/ISSUE-001",
|
||||
},
|
||||
],
|
||||
selected_issue_ids: ["demo-issue-1"],
|
||||
comment: "Create selected issues.",
|
||||
},
|
||||
diagnostics: [],
|
||||
trace_count: 4,
|
||||
next_actions: {
|
||||
can_continue: false,
|
||||
can_save_now: null,
|
||||
recommended_next_tool: null,
|
||||
reason: "Run completed.",
|
||||
patch_examples: [],
|
||||
warnings: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "workflow.runs.trace" as const,
|
||||
params: { run_id: "run_1", trace_range: { start: 0, limit: 50 } },
|
||||
@@ -472,4 +581,40 @@ describe("lifecycle operations", () => {
|
||||
});
|
||||
expect(exchange.interpreted).not.toHaveProperty("trace");
|
||||
});
|
||||
|
||||
it("interprets typed interrupt contracts from run start", async () => {
|
||||
const startCase = lifecycleCases.find(
|
||||
(testCase) => testCase.operation === "workflow.runs.start",
|
||||
);
|
||||
expect(startCase).toBeDefined();
|
||||
if (!startCase) return;
|
||||
|
||||
const fetch: typeof globalThis.fetch = async (input, init) => {
|
||||
const request = await requestBody(input, init);
|
||||
return jsonResponse({
|
||||
jsonrpc: "2.0",
|
||||
id: request.id,
|
||||
result: startCase.result,
|
||||
});
|
||||
};
|
||||
|
||||
const exchange = await runOperation(
|
||||
{ fetch },
|
||||
startCase.operation as "workflow.health" | "workflow.sources.list",
|
||||
startCase.params,
|
||||
);
|
||||
|
||||
expect(exchange.interpreted).toMatchObject({
|
||||
runId: "run_demo",
|
||||
status: "interrupted",
|
||||
interrupt: {
|
||||
kind: "issue_review",
|
||||
typed: true,
|
||||
outcomes: ["submitted", "cancelled"],
|
||||
},
|
||||
nextActions: {
|
||||
canContinue: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,6 +34,8 @@ import {
|
||||
WorkflowDeploymentsValidatePayloadSchema,
|
||||
WorkflowRunsListPayloadSchema,
|
||||
WorkflowRunsInspectPayloadSchema,
|
||||
WorkflowRunsStartPayloadSchema,
|
||||
WorkflowRunsResumePayloadSchema,
|
||||
WorkflowRunsTracePayloadSchema,
|
||||
} from "./rpcs.js";
|
||||
import { normalizeLoopbackTarget } from "./target-policy.js";
|
||||
@@ -51,6 +53,8 @@ export type OperationName =
|
||||
| "workflow.deployments.validate"
|
||||
| "workflow.runs.list"
|
||||
| "workflow.runs.inspect"
|
||||
| "workflow.runs.start"
|
||||
| "workflow.runs.resume"
|
||||
| "workflow.runs.trace";
|
||||
|
||||
export interface WorkflowRpcOptions {
|
||||
@@ -89,6 +93,8 @@ const isOperationName = (value: string): value is OperationName =>
|
||||
value === "workflow.deployments.validate" ||
|
||||
value === "workflow.runs.list" ||
|
||||
value === "workflow.runs.inspect" ||
|
||||
value === "workflow.runs.start" ||
|
||||
value === "workflow.runs.resume" ||
|
||||
value === "workflow.runs.trace";
|
||||
|
||||
const toExchange = (evidence: EvidenceRecord | null): RpcExchangeEvidence => ({
|
||||
@@ -338,6 +344,20 @@ const executeImpl =
|
||||
);
|
||||
return yield* client.workflow["runs.inspect"](payload);
|
||||
}
|
||||
case "workflow.runs.start": {
|
||||
const payload = yield* decodeParams(
|
||||
WorkflowRunsStartPayloadSchema,
|
||||
params,
|
||||
);
|
||||
return yield* client.workflow["runs.start"](payload);
|
||||
}
|
||||
case "workflow.runs.resume": {
|
||||
const payload = yield* decodeParams(
|
||||
WorkflowRunsResumePayloadSchema,
|
||||
params,
|
||||
);
|
||||
return yield* client.workflow["runs.resume"](payload);
|
||||
}
|
||||
case "workflow.runs.trace": {
|
||||
const payload = yield* decodeParams(
|
||||
WorkflowRunsTracePayloadSchema,
|
||||
|
||||
Reference in New Issue
Block a user