@osolmaz/pi-workflows 0.9.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -19
- package/dist/builtins/autodoc.workflow.d.ts +58 -0
- package/dist/builtins/autodoc.workflow.js +266 -0
- package/dist/builtins/autodoc.workflow.js.map +1 -0
- package/dist/builtins/autoimplement.workflow.d.ts +353 -0
- package/dist/builtins/autoimplement.workflow.js +961 -0
- package/dist/builtins/autoimplement.workflow.js.map +1 -0
- package/dist/builtins/autoplan.workflow.d.ts +58 -0
- package/dist/builtins/autoplan.workflow.js +190 -0
- package/dist/builtins/autoplan.workflow.js.map +1 -0
- package/dist/builtins/catalog.js +9 -1
- package/dist/builtins/catalog.js.map +1 -1
- package/dist/builtins/index.d.ts +6 -0
- package/dist/builtins/index.js +7 -0
- package/dist/builtins/index.js.map +1 -0
- package/dist/builtins/monitor.workflow.d.ts +29 -3
- package/dist/builtins/monitor.workflow.js +325 -13
- package/dist/builtins/monitor.workflow.js.map +1 -1
- package/dist/builtins/plan-approval.workflow.d.ts +83 -0
- package/dist/builtins/plan-approval.workflow.js +148 -0
- package/dist/builtins/plan-approval.workflow.js.map +1 -0
- package/dist/builtins/plan-presentation.d.ts +7 -0
- package/dist/builtins/plan-presentation.js +44 -0
- package/dist/builtins/plan-presentation.js.map +1 -0
- package/dist/extension/decision-channels.d.ts +132 -0
- package/dist/extension/decision-channels.js +1082 -0
- package/dist/extension/decision-channels.js.map +1 -0
- package/dist/extension/index.d.ts +1 -0
- package/dist/extension/index.js +426 -11
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/widget.js +26 -2
- package/dist/extension/widget.js.map +1 -1
- package/dist/extension/workflow-tool.d.ts +1 -37
- package/dist/extension/workflow-tool.js +1 -42
- package/dist/extension/workflow-tool.js.map +1 -1
- package/dist/host/rpc-bridge.js +4 -21
- package/dist/host/rpc-bridge.js.map +1 -1
- package/dist/render/graph-render.js +46 -10
- package/dist/render/graph-render.js.map +1 -1
- package/dist/viewer/render.js +36 -2
- package/dist/viewer/render.js.map +1 -1
- package/dist/workflows/catalog.d.ts +1 -0
- package/dist/workflows/catalog.js +6 -0
- package/dist/workflows/catalog.js.map +1 -1
- package/dist/workflows/composition.d.ts +45 -0
- package/dist/workflows/composition.js +485 -0
- package/dist/workflows/composition.js.map +1 -0
- package/dist/workflows/decision-presentation.d.ts +18 -0
- package/dist/workflows/decision-presentation.js +417 -0
- package/dist/workflows/decision-presentation.js.map +1 -0
- package/dist/workflows/decision.d.ts +11 -5
- package/dist/workflows/decision.js.map +1 -1
- package/dist/workflows/definition.d.ts +22 -3
- package/dist/workflows/definition.js +46 -3
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.d.ts +2 -1
- package/dist/workflows/engine.js +199 -18
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/graph.js +8 -6
- package/dist/workflows/graph.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +87 -0
- package/dist/workflows/human-decision.js +583 -0
- package/dist/workflows/human-decision.js.map +1 -0
- package/dist/workflows/index.d.ts +5 -2
- package/dist/workflows/index.js +4 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/loader.d.ts +5 -4
- package/dist/workflows/loader.js +118 -18
- package/dist/workflows/loader.js.map +1 -1
- package/dist/workflows/schema.d.ts +3 -1
- package/dist/workflows/schema.js +64 -2
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/store.js +39 -2
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +72 -0
- package/dist/workflows/tool-input.js +141 -0
- package/dist/workflows/tool-input.js.map +1 -0
- package/dist/workflows/types.d.ts +300 -2
- package/docs/CONTROLLERS.md +1 -1
- package/docs/DESIGN_PHILOSOPHY.md +1 -1
- package/docs/HUMAN_DECISIONS.md +371 -0
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +323 -0
- package/docs/MONITOR.md +46 -18
- package/docs/WORKFLOW_COMPOSITION.md +333 -0
- package/docs/plans/2026-08-19-human-decision-gates-plan.md +296 -0
- package/docs/plans/2026-08-19-human-decision-presentations-plan.md +172 -0
- package/docs/plans/2026-08-19-provider-compatible-workflow-tool-schema-plan.md +45 -0
- package/docs/plans/2026-08-19-workflow-composition-plan.md +308 -0
- package/docs/run-bundles.md +45 -10
- package/docs/workflows.md +106 -15
- package/examples/workflows/approved-plan.workflow.ts +58 -0
- package/examples/workflows/autoimplement.workflow.ts +1 -92
- package/examples/workflows/autoplan.workflow.ts +1 -0
- package/examples/workflows/human-decision.workflow.ts +62 -0
- package/herdr-plugin.toml +1 -1
- package/package.json +6 -1
- package/schemas/decision-presentation-v1.schema.json +83 -0
- package/schemas/human-decision-accepted-v1.schema.json +44 -0
- package/schemas/human-decision-accepted-v2.schema.json +50 -0
- package/schemas/human-decision-answer-attempt-v1.schema.json +37 -0
- package/schemas/human-decision-cancellation-v1.schema.json +15 -0
- package/schemas/human-decision-continuation-v1.schema.json +16 -0
- package/schemas/human-decision-delivery-v1.schema.json +28 -0
- package/schemas/human-decision-delivery-v2.schema.json +36 -0
- package/schemas/human-decision-receipt-v1.schema.json +33 -0
- package/schemas/human-decision-receipt-v2.schema.json +39 -0
- package/schemas/human-decision-request-v1.schema.json +59 -0
- package/schemas/human-decision-request-v2.schema.json +69 -0
- package/schemas/human-decision-resolution-v1.schema.json +27 -0
- package/schemas/human-decision-resolution-v2.schema.json +27 -0
- package/schemas/human-decision-settlement-v1.schema.json +28 -0
- package/skills/autodoc/SKILL.md +43 -0
- package/skills/autoimplement/SKILL.md +58 -0
- package/skills/autoimplement/agents/openai.yaml +4 -0
- package/skills/autoplan/SKILL.md +25 -0
- package/skills/monitor/SKILL.md +8 -1
- package/skills/pi-workflows/SKILL.md +5 -3
- package/src/builtins/autodoc.workflow.ts +325 -0
- package/src/builtins/autoimplement.workflow.ts +1123 -0
- package/src/builtins/autoplan.workflow.ts +231 -0
- package/src/builtins/catalog.ts +9 -1
- package/src/builtins/index.ts +32 -0
- package/src/builtins/monitor.workflow.ts +396 -15
- package/src/builtins/plan-approval.workflow.ts +190 -0
- package/src/builtins/plan-presentation.ts +57 -0
- package/src/extension/decision-channels.ts +1533 -0
- package/src/extension/index.ts +499 -9
- package/src/extension/widget.ts +39 -2
- package/src/extension/workflow-tool.ts +5 -81
- package/src/host/rpc-bridge.ts +7 -30
- package/src/render/graph-render.ts +54 -5
- package/src/viewer/render.ts +56 -3
- package/src/workflows/catalog.ts +7 -0
- package/src/workflows/composition.ts +642 -0
- package/src/workflows/decision-presentation.ts +501 -0
- package/src/workflows/decision.ts +12 -5
- package/src/workflows/definition.ts +118 -8
- package/src/workflows/engine.ts +257 -20
- package/src/workflows/graph.ts +8 -6
- package/src/workflows/human-decision.ts +819 -0
- package/src/workflows/index.ts +89 -0
- package/src/workflows/loader.ts +186 -18
- package/src/workflows/schema.ts +79 -2
- package/src/workflows/store.ts +45 -2
- package/src/workflows/tool-input.ts +202 -0
- package/src/workflows/types.ts +374 -2
- package/examples/workflows/elegant-solution.workflow.ts +0 -95
package/src/extension/widget.ts
CHANGED
|
@@ -297,8 +297,36 @@ function nodeRuntimeSegments(
|
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
if (state.waitingOn === nodeId) {
|
|
300
|
-
const
|
|
301
|
-
|
|
300
|
+
const human = snapshot.nodes[nodeId]?.humanDecision;
|
|
301
|
+
if (human !== undefined) {
|
|
302
|
+
const request =
|
|
303
|
+
state.finalOutput !== null && typeof state.finalOutput === "object"
|
|
304
|
+
? (state.finalOutput as {
|
|
305
|
+
schema?: unknown;
|
|
306
|
+
audience?: unknown;
|
|
307
|
+
presentationDigest?: unknown;
|
|
308
|
+
presentation?: { summary?: unknown };
|
|
309
|
+
})
|
|
310
|
+
: undefined;
|
|
311
|
+
const requestAudience =
|
|
312
|
+
(request?.schema === "pi-workflows.human-decision-request.v1" ||
|
|
313
|
+
request?.schema === "pi-workflows.human-decision-request.v2") &&
|
|
314
|
+
typeof request.audience === "string"
|
|
315
|
+
? request.audience
|
|
316
|
+
: human.audience;
|
|
317
|
+
segments.push(
|
|
318
|
+
`human decision · ${sanitizeText(requestAudience)}${typeof request?.presentation?.summary === "string" ? ` · ${sanitizeText(request.presentation.summary)}` : ""} · ${Object.values(
|
|
319
|
+
human.choices,
|
|
320
|
+
)
|
|
321
|
+
.map((choice) => sanitizeText(choice.label))
|
|
322
|
+
.join(
|
|
323
|
+
" / ",
|
|
324
|
+
)}${typeof request?.presentationDigest === "string" ? ` · ${request.presentationDigest.slice(7, 19)}` : ""}`,
|
|
325
|
+
);
|
|
326
|
+
} else {
|
|
327
|
+
const summary = snapshot.nodes[nodeId]?.summary;
|
|
328
|
+
segments.push(summary ? sanitizeText(summary) : "waiting");
|
|
329
|
+
}
|
|
302
330
|
return segments;
|
|
303
331
|
}
|
|
304
332
|
|
|
@@ -313,6 +341,15 @@ function nodeRuntimeSegments(
|
|
|
313
341
|
);
|
|
314
342
|
return segments;
|
|
315
343
|
}
|
|
344
|
+
const human = snapshot.nodes[nodeId]?.humanDecision;
|
|
345
|
+
if (
|
|
346
|
+
human !== undefined &&
|
|
347
|
+
state.humanDecision !== undefined &&
|
|
348
|
+
state.humanDecision.nodeId === nodeId
|
|
349
|
+
) {
|
|
350
|
+
const selected = human.choices[state.humanDecision.response.choice];
|
|
351
|
+
if (selected !== undefined) segments.push(`human: ${sanitizeText(selected.label)}`);
|
|
352
|
+
}
|
|
316
353
|
if (Number.isFinite(result.durationMs)) {
|
|
317
354
|
segments.push(formatDuration(result.durationMs));
|
|
318
355
|
}
|
|
@@ -1,81 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export const WorkflowToolParameters: TSchema = Type.Union([
|
|
7
|
-
Type.Object(
|
|
8
|
-
{
|
|
9
|
-
action: StringEnum(["list"] as const),
|
|
10
|
-
offset: Type.Optional(Type.Integer({ minimum: 0, description: "Workflow list offset" })),
|
|
11
|
-
},
|
|
12
|
-
noExtraProperties,
|
|
13
|
-
),
|
|
14
|
-
Type.Object(
|
|
15
|
-
{
|
|
16
|
-
action: StringEnum(["start"] as const),
|
|
17
|
-
workflow: Type.String({ description: "Discovered workflow name or workflow file path" }),
|
|
18
|
-
input: Type.Optional(Type.Unknown({ description: "Structured workflow input" })),
|
|
19
|
-
},
|
|
20
|
-
noExtraProperties,
|
|
21
|
-
),
|
|
22
|
-
Type.Object(
|
|
23
|
-
{
|
|
24
|
-
action: StringEnum(["status"] as const),
|
|
25
|
-
runId: Type.Optional(Type.String({ description: "Run id; omit for the active run" })),
|
|
26
|
-
},
|
|
27
|
-
noExtraProperties,
|
|
28
|
-
),
|
|
29
|
-
Type.Object({ action: StringEnum(["pause"] as const) }, noExtraProperties),
|
|
30
|
-
Type.Object({ action: StringEnum(["resume"] as const) }, noExtraProperties),
|
|
31
|
-
Type.Object({ action: StringEnum(["cancel"] as const) }, noExtraProperties),
|
|
32
|
-
Type.Object(
|
|
33
|
-
{
|
|
34
|
-
action: StringEnum(["answer"] as const),
|
|
35
|
-
input: Type.Unknown({ description: "Checkpoint answer" }),
|
|
36
|
-
runId: Type.Optional(Type.String({ description: "Waiting run id" })),
|
|
37
|
-
},
|
|
38
|
-
noExtraProperties,
|
|
39
|
-
),
|
|
40
|
-
Type.Object(
|
|
41
|
-
{
|
|
42
|
-
action: StringEnum(["update"] as const),
|
|
43
|
-
step: Type.String({ description: "Active step id" }),
|
|
44
|
-
attempt: Type.String({ description: "Active attempt id" }),
|
|
45
|
-
update: Type.Object(
|
|
46
|
-
{
|
|
47
|
-
type: Type.String(),
|
|
48
|
-
key: Type.String(),
|
|
49
|
-
data: Type.Record(Type.String(), Type.Unknown()),
|
|
50
|
-
},
|
|
51
|
-
noExtraProperties,
|
|
52
|
-
),
|
|
53
|
-
},
|
|
54
|
-
noExtraProperties,
|
|
55
|
-
),
|
|
56
|
-
Type.Object(
|
|
57
|
-
{
|
|
58
|
-
action: StringEnum(["submit"] as const),
|
|
59
|
-
step: Type.String({ description: "Step id from the workflow step contract" }),
|
|
60
|
-
attempt: Type.String({ description: "Attempt id from the workflow step contract" }),
|
|
61
|
-
output: Type.Unknown({ description: "Step output matching the expected shape" }),
|
|
62
|
-
},
|
|
63
|
-
noExtraProperties,
|
|
64
|
-
),
|
|
65
|
-
]);
|
|
66
|
-
|
|
67
|
-
export type WorkflowToolInput =
|
|
68
|
-
| { action: "list"; offset?: number }
|
|
69
|
-
| { action: "start"; workflow: string; input?: unknown }
|
|
70
|
-
| { action: "status"; runId?: string }
|
|
71
|
-
| { action: "pause" }
|
|
72
|
-
| { action: "resume" }
|
|
73
|
-
| { action: "cancel" }
|
|
74
|
-
| { action: "answer"; input: unknown; runId?: string }
|
|
75
|
-
| {
|
|
76
|
-
action: "update";
|
|
77
|
-
step: string;
|
|
78
|
-
attempt: string;
|
|
79
|
-
update: { type: string; key: string; data: Record<string, unknown> };
|
|
80
|
-
}
|
|
81
|
-
| { action: "submit"; step: string; attempt: string; output: unknown };
|
|
1
|
+
export {
|
|
2
|
+
parseWorkflowToolInput,
|
|
3
|
+
WorkflowToolParameters,
|
|
4
|
+
type WorkflowToolInput,
|
|
5
|
+
} from "../workflows/tool-input.js";
|
package/src/host/rpc-bridge.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
parseWorkflowSubmissionInput,
|
|
4
|
+
WorkflowSubmissionToolParameters,
|
|
5
|
+
} from "../workflows/tool-input.js";
|
|
4
6
|
|
|
5
7
|
export const RPC_SUBMISSION_PREFIX = "PI_WORKFLOWS_STEP_SUBMISSION ";
|
|
6
8
|
|
|
@@ -19,34 +21,9 @@ export default function piWorkflowsRpcBridge(pi: ExtensionAPI) {
|
|
|
19
21
|
"Only call this tool when a workflow step contract in the conversation asks you to.",
|
|
20
22
|
"Pass the exact step and attempt ids from the contract.",
|
|
21
23
|
].join(" "),
|
|
22
|
-
parameters:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
action: StringEnum(["update"] as const),
|
|
26
|
-
step: Type.String(),
|
|
27
|
-
attempt: Type.String(),
|
|
28
|
-
update: Type.Object(
|
|
29
|
-
{
|
|
30
|
-
type: Type.String(),
|
|
31
|
-
key: Type.String(),
|
|
32
|
-
data: Type.Record(Type.String(), Type.Unknown()),
|
|
33
|
-
},
|
|
34
|
-
{ additionalProperties: false },
|
|
35
|
-
),
|
|
36
|
-
},
|
|
37
|
-
{ additionalProperties: false },
|
|
38
|
-
),
|
|
39
|
-
Type.Object(
|
|
40
|
-
{
|
|
41
|
-
action: StringEnum(["submit"] as const),
|
|
42
|
-
step: Type.String({ description: "Step id from the workflow step contract" }),
|
|
43
|
-
attempt: Type.String({ description: "Attempt id from the workflow step contract" }),
|
|
44
|
-
output: Type.Unknown({ description: "Step output matching the expected shape" }),
|
|
45
|
-
},
|
|
46
|
-
{ additionalProperties: false },
|
|
47
|
-
),
|
|
48
|
-
]),
|
|
49
|
-
async execute(toolCallId, params) {
|
|
24
|
+
parameters: WorkflowSubmissionToolParameters,
|
|
25
|
+
async execute(toolCallId, rawParams) {
|
|
26
|
+
const params = parseWorkflowSubmissionInput(rawParams);
|
|
50
27
|
process.stderr.write(
|
|
51
28
|
`${RPC_SUBMISSION_PREFIX}${JSON.stringify({ ...params, idempotencyKey: toolCallId })}\n`,
|
|
52
29
|
);
|
|
@@ -216,7 +216,7 @@ function cardMetrics(view: GraphView): CardMetrics {
|
|
|
216
216
|
measure(`${nodeTypeBadge("checkpoint")} ${STATUS_GLYPHS[status]} ${STATUS_LABELS[status]}`);
|
|
217
217
|
}
|
|
218
218
|
for (const [nodeId, node] of Object.entries(snapshot.nodes)) {
|
|
219
|
-
measure(sanitizeText(nodeId));
|
|
219
|
+
measure(sanitizeText(hierarchicalNodeLabel(nodeId, node)));
|
|
220
220
|
measure(nodeTypeBadge(node.nodeType, node.actionExecution));
|
|
221
221
|
const labels = nodeBranchLabels(view, nodeId);
|
|
222
222
|
branchRows = Math.max(branchRows, labels.length);
|
|
@@ -305,25 +305,63 @@ function renderCellText(
|
|
|
305
305
|
const durationMs = Date.parse(attempt.finishedAt) - Date.parse(attempt.startedAt);
|
|
306
306
|
elapsed = formatDuration(durationMs);
|
|
307
307
|
}
|
|
308
|
+
const human = node?.humanDecision;
|
|
309
|
+
const waitingSchema =
|
|
310
|
+
state.waitingOn === nodeId &&
|
|
311
|
+
state.finalOutput !== null &&
|
|
312
|
+
typeof state.finalOutput === "object"
|
|
313
|
+
? (state.finalOutput as { schema?: unknown }).schema
|
|
314
|
+
: undefined;
|
|
315
|
+
const waitingRequest =
|
|
316
|
+
waitingSchema === "pi-workflows.human-decision-request.v1" ||
|
|
317
|
+
waitingSchema === "pi-workflows.human-decision-request.v2"
|
|
318
|
+
? (state.finalOutput as {
|
|
319
|
+
audience?: unknown;
|
|
320
|
+
presentationDigest?: unknown;
|
|
321
|
+
presentation?: { summary?: unknown };
|
|
322
|
+
})
|
|
323
|
+
: undefined;
|
|
324
|
+
const selected =
|
|
325
|
+
human !== undefined &&
|
|
326
|
+
state.humanDecision !== undefined &&
|
|
327
|
+
state.humanDecision.nodeId === nodeId
|
|
328
|
+
? human.choices[state.humanDecision.response.choice]
|
|
329
|
+
: undefined;
|
|
330
|
+
const humanDetail =
|
|
331
|
+
human === undefined
|
|
332
|
+
? undefined
|
|
333
|
+
: state.waitingOn === nodeId
|
|
334
|
+
? `human decision · ${sanitizeText(typeof waitingRequest?.audience === "string" ? waitingRequest.audience : human.audience)}${typeof waitingRequest?.presentation?.summary === "string" ? ` · ${sanitizeText(waitingRequest.presentation.summary)}` : ""} · ${Object.values(
|
|
335
|
+
human.choices,
|
|
336
|
+
)
|
|
337
|
+
.map((choice) => sanitizeText(choice.label))
|
|
338
|
+
.join(
|
|
339
|
+
" / ",
|
|
340
|
+
)}${typeof waitingRequest?.presentationDigest === "string" ? ` · ${waitingRequest.presentationDigest.slice(7, 19)}` : ""}`
|
|
341
|
+
: selected !== undefined
|
|
342
|
+
? `human: ${sanitizeText(selected.label)}`
|
|
343
|
+
: undefined;
|
|
308
344
|
const detail =
|
|
309
|
-
|
|
345
|
+
humanDetail ??
|
|
346
|
+
(atLatestStep && state.currentNode === nodeId && state.statusDetail
|
|
310
347
|
? sanitizeText(state.statusDetail)
|
|
311
348
|
: node?.statusDetail
|
|
312
349
|
? sanitizeText(node.statusDetail)
|
|
313
350
|
: node?.summary
|
|
314
351
|
? sanitizeText(node.summary)
|
|
315
|
-
: "";
|
|
352
|
+
: "");
|
|
316
353
|
const branchLines = Array.from({ length: metrics.branchRows }, (_, index) =>
|
|
317
354
|
labels[index] ? `◇ ${labels[index]}` : "",
|
|
318
355
|
);
|
|
319
356
|
const count = atLatestStep && state.currentNode === nodeId ? Math.max(attempts, 1) : attempts;
|
|
320
357
|
const timing =
|
|
321
358
|
attempt || count > 0 ? `${count} attempt${count === 1 ? "" : "s"} · ${elapsed}` : "not visited";
|
|
322
|
-
const
|
|
359
|
+
const displayNodeId = hierarchicalNodeLabel(nodeId, node);
|
|
360
|
+
const text = `${displayNodeId} [${nodeType}] ${timing}`;
|
|
323
361
|
return {
|
|
324
362
|
cell,
|
|
325
363
|
text,
|
|
326
|
-
nodeId: sanitizeText(
|
|
364
|
+
nodeId: sanitizeText(displayNodeId),
|
|
327
365
|
nodeType,
|
|
328
366
|
typeBadge: node ? nodeTypeBadge(node.nodeType, node.actionExecution) : "? unknown",
|
|
329
367
|
status,
|
|
@@ -337,6 +375,17 @@ function renderCellText(
|
|
|
337
375
|
};
|
|
338
376
|
}
|
|
339
377
|
|
|
378
|
+
function hierarchicalNodeLabel(
|
|
379
|
+
nodeId: string,
|
|
380
|
+
node: WorkflowDefinitionSnapshot["nodes"][string] | undefined,
|
|
381
|
+
): string {
|
|
382
|
+
if (node?.mountPath === undefined || node.localNodeId === undefined) return nodeId;
|
|
383
|
+
const path = node.mountPath.join(" › ");
|
|
384
|
+
if (node.includeTransition === "entry") return `${path} · enter`;
|
|
385
|
+
if (node.includeTransition === "exit") return `${path} · ${node.localNodeId} exit`;
|
|
386
|
+
return `${path} › ${node.localNodeId}`;
|
|
387
|
+
}
|
|
388
|
+
|
|
340
389
|
type RankGeometry = {
|
|
341
390
|
cells: RenderedCell[];
|
|
342
391
|
centers: number[];
|
package/src/viewer/render.ts
CHANGED
|
@@ -2,13 +2,22 @@ import { ansi, fitWidth, sanitizeText } from "../render/ansi.js";
|
|
|
2
2
|
import { formatDuration, runElapsedMs } from "../render/format.js";
|
|
3
3
|
import { renderGraphLines } from "../render/graph-render.js";
|
|
4
4
|
import { decodeValueWith } from "../workflows/artifacts.js";
|
|
5
|
+
import {
|
|
6
|
+
decisionDocumentSegments,
|
|
7
|
+
decisionPresentationFingerprint,
|
|
8
|
+
humanDecisionChannelRequest,
|
|
9
|
+
} from "../workflows/decision-presentation.js";
|
|
5
10
|
import {
|
|
6
11
|
formatProgressLine,
|
|
7
12
|
progressRecordsFromTrace,
|
|
8
13
|
progressTracksFromRecords,
|
|
9
14
|
} from "../workflows/progress.js";
|
|
10
15
|
import type { LoadedRunBundle } from "../workflows/store.js";
|
|
11
|
-
import type {
|
|
16
|
+
import type {
|
|
17
|
+
HumanDecisionRequest,
|
|
18
|
+
WorkflowRunStatus,
|
|
19
|
+
WorkflowStepRecord,
|
|
20
|
+
} from "../workflows/types.js";
|
|
12
21
|
|
|
13
22
|
export { formatDuration, runElapsedMs };
|
|
14
23
|
|
|
@@ -132,10 +141,31 @@ function nodeStatusLine(bundle: LoadedRunBundle, nodeId: string, width: number,
|
|
|
132
141
|
suffix = ansi.cyan(` running ${formatDuration(now.getTime() - startedAt)}${detail}`);
|
|
133
142
|
} else if (state.waitingOn === nodeId) {
|
|
134
143
|
glyph = ansi.yellow("⏸");
|
|
135
|
-
|
|
144
|
+
const human = bundle.snapshot?.nodes[nodeId]?.humanDecision;
|
|
145
|
+
const request = humanDecisionRequest(state.finalOutput);
|
|
146
|
+
const requestAudience = request?.audience ?? human?.audience;
|
|
147
|
+
suffix = ansi.yellow(
|
|
148
|
+
human === undefined
|
|
149
|
+
? " waiting"
|
|
150
|
+
: ` waiting for human · ${sanitizeText(requestAudience ?? "operator")}${request?.schema === "pi-workflows.human-decision-request.v2" ? ` · ${sanitizeText(request.presentation.summary)}` : ""} · ${Object.values(
|
|
151
|
+
human.choices,
|
|
152
|
+
)
|
|
153
|
+
.map((choice) => sanitizeText(choice.label))
|
|
154
|
+
.join(
|
|
155
|
+
" / ",
|
|
156
|
+
)}${request?.schema === "pi-workflows.human-decision-request.v2" ? ` · ${request.presentationDigest.slice(7, 19)}` : ""}`,
|
|
157
|
+
);
|
|
136
158
|
} else if (result) {
|
|
137
159
|
glyph = result.outcome === "ok" ? ansi.green("✓") : ansi.red("✗");
|
|
138
|
-
|
|
160
|
+
const human = bundle.snapshot?.nodes[nodeId]?.humanDecision;
|
|
161
|
+
const accepted = state.humanDecision;
|
|
162
|
+
const selected =
|
|
163
|
+
human !== undefined && accepted !== undefined && accepted.nodeId === nodeId
|
|
164
|
+
? human.choices[accepted.response.choice]
|
|
165
|
+
: undefined;
|
|
166
|
+
suffix = ansi.dim(
|
|
167
|
+
` ${formatDuration(result.durationMs)}${selected === undefined ? "" : ` · human: ${sanitizeText(selected.label)}`}`,
|
|
168
|
+
);
|
|
139
169
|
}
|
|
140
170
|
return fitWidth(` ${glyph} ${nodeId} ${ansi.dim(`[${nodeType}]`)}${suffix}`, width);
|
|
141
171
|
}
|
|
@@ -143,6 +173,20 @@ function nodeStatusLine(bundle: LoadedRunBundle, nodeId: string, width: number,
|
|
|
143
173
|
/** Pretty-printed JSON body of the selected step for the inspector pane. */
|
|
144
174
|
function inspectorLines(step: WorkflowStepRecord, width: number): string[] {
|
|
145
175
|
const lines: string[] = [];
|
|
176
|
+
const request = step.error === undefined ? humanDecisionRequest(step.output) : null;
|
|
177
|
+
if (request !== null) {
|
|
178
|
+
const channelRequest = humanDecisionChannelRequest(request);
|
|
179
|
+
for (const segment of decisionDocumentSegments(channelRequest)) {
|
|
180
|
+
for (const raw of segment.text.split("\n")) {
|
|
181
|
+
lines.push(fitWidth(` ${sanitizeText(raw)}`, width));
|
|
182
|
+
}
|
|
183
|
+
lines.push("");
|
|
184
|
+
}
|
|
185
|
+
lines.push(
|
|
186
|
+
fitWidth(ansi.dim(` decision ${decisionPresentationFingerprint(channelRequest)}`), width),
|
|
187
|
+
);
|
|
188
|
+
return lines;
|
|
189
|
+
}
|
|
146
190
|
const body = step.error !== undefined ? step.error : withArtifactPlaceholders(step.output);
|
|
147
191
|
const rendered =
|
|
148
192
|
typeof body === "string" && step.error !== undefined ? body : JSON.stringify(body, null, 2);
|
|
@@ -163,6 +207,15 @@ function inspectorLines(step: WorkflowStepRecord, width: number): string[] {
|
|
|
163
207
|
return lines;
|
|
164
208
|
}
|
|
165
209
|
|
|
210
|
+
function humanDecisionRequest(value: unknown): HumanDecisionRequest | null {
|
|
211
|
+
if (value === null || typeof value !== "object") return null;
|
|
212
|
+
const schema = (value as { schema?: unknown }).schema;
|
|
213
|
+
return schema === "pi-workflows.human-decision-request.v1" ||
|
|
214
|
+
schema === "pi-workflows.human-decision-request.v2"
|
|
215
|
+
? (value as HumanDecisionRequest)
|
|
216
|
+
: null;
|
|
217
|
+
}
|
|
218
|
+
|
|
166
219
|
/**
|
|
167
220
|
* Full-run detail view: header, graph pane, step timeline, inspector.
|
|
168
221
|
* `scroll` shifts the viewport down over the full body; `selectedStepIndex`
|
package/src/workflows/catalog.ts
CHANGED
|
@@ -83,6 +83,13 @@ export class BuiltinWorkflowCatalog {
|
|
|
83
83
|
return this.byName.get(name);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
sourceForDefinition(definition: WorkflowDefinition): WorkflowSource | undefined {
|
|
87
|
+
const entry = this.list().find((candidate) => candidate.definition === definition);
|
|
88
|
+
return entry === undefined
|
|
89
|
+
? undefined
|
|
90
|
+
: { kind: "builtin", id: entry.id, revision: entry.revision };
|
|
91
|
+
}
|
|
92
|
+
|
|
86
93
|
resolve(
|
|
87
94
|
source: WorkflowSource,
|
|
88
95
|
runId = `builtin:${source.kind === "builtin" ? source.id : "unknown"}`,
|