feat(pi-workflow): add human-readable run labels

This commit is contained in:
jb
2026-08-25 21:22:08 -04:00
parent a1d0b1415c
commit 54a7921227
17 changed files with 385 additions and 52 deletions
+22 -16
View File
@@ -15,10 +15,8 @@ import {
type WorkflowView,
} from "./engine.ts";
import { isValidContextContinuation } from "./continuation.ts";
import {
readVerifiedPlanArtifact,
type WorkflowStore,
} from "./store.ts";
import { deriveRunLabel, deriveRunLabels } from "./run-label.ts";
import { readVerifiedPlanArtifact, type WorkflowStore } from "./store.ts";
const MAX_HANDOFF_BYTES = 32 * 1024;
type NewSessionOptions = NonNullable<
@@ -74,7 +72,7 @@ async function boundedPlan(view: WorkflowView) {
function handoffMessage(view: WorkflowView, plan: string) {
return [
`Continue pi-workflow run ${view.runId}.`,
`Continue pi-workflow run ${deriveRunLabel(view)}.`,
`Worktree: ${view.workspace.root ?? "missing"}`,
`Plan revision: ${view.planArtifact?.revision ?? "missing"}`,
"The workflow state remains authoritative. Inspect it before taking action.",
@@ -133,7 +131,10 @@ async function consumeHandoff(
previousSessionFile: sourceSessionFile,
};
await current.engine.execute(command);
replacementCtx.ui.notify(`Workflow ${runId} context handoff is ready`, "info");
replacementCtx.ui.notify(
`Workflow ${deriveRunLabel(current.view)} context handoff is ready`,
"info",
);
}
async function continueFreshSession(
@@ -190,7 +191,7 @@ async function continueNormalCompaction(
};
const compacting = await engine.execute(command);
const instructions = [
`Prepare context for pi-workflow run ${view.runId}.`,
`Prepare context for pi-workflow run ${deriveRunLabel(view)}.`,
`Preserve the worktree path, accepted Decisions, and plan revision ${view.planArtifact?.revision}.`,
"Use this approved plan as the implementation focus:",
plan,
@@ -239,11 +240,6 @@ function isInteractive(ctx: ExtensionCommandContext) {
return ctx.hasUI && ctx.mode === "tui";
}
function runLabel(run: WorkflowRun) {
const name = run.featureName ?? run.request;
return `${name} · ${run.runId} · ${run.status}`;
}
function belongsToActiveParent(run: WorkflowRun, sessionId: string) {
return run.lease?.activeParentSessionId === sessionId;
}
@@ -263,7 +259,9 @@ async function selectRun(
title: string,
runs: WorkflowRun[],
) {
const labels = runs.map(runLabel);
const labels = deriveRunLabels(runs).map(
(label, index) => `${label} · ${runs[index]!.status}`,
);
const selected = await ctx.ui.select(title, labels);
return runs[labels.indexOf(selected ?? "")];
}
@@ -274,7 +272,13 @@ async function continueRun(
ctx: ExtensionCommandContext,
) {
await ctx.waitForIdle();
const { view } = await inspect(runtime, ctx, runId);
let view: WorkflowView;
try {
({ view } = await inspect(runtime, ctx, runId));
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
throw new Error(`Workflow Run ${runId} could not be loaded: ${reason}`);
}
const status = view.implementationContext?.status;
if (status === "handoff-pending") {
await continueFreshSession(runtime, ctx, view);
@@ -284,7 +288,9 @@ async function continueRun(
await continueNormalCompaction(runtime, ctx, view);
return;
}
throw new Error(`Workflow Run ${runId} has no pending context continuation`);
throw new Error(
`Workflow Run ${deriveRunLabel(view)} has no pending context continuation`,
);
}
async function handleContinue(
@@ -392,7 +398,7 @@ async function handleStop(
expectedRevision: current.revision,
idempotencyKey: `workflow-stop-${current.runId}-${current.revision}-${randomUUID()}`,
});
ctx.ui.notify(`Workflow ${current.runId} stopped.`, "info");
ctx.ui.notify(`Workflow ${deriveRunLabel(current)} stopped.`, "info");
}
export function executeHostDecision(