@osolmaz/pi-workflows 0.10.0 → 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.
Files changed (125) hide show
  1. package/README.md +23 -12
  2. package/dist/builtins/autodoc.workflow.d.ts +58 -0
  3. package/dist/builtins/autodoc.workflow.js +266 -0
  4. package/dist/builtins/autodoc.workflow.js.map +1 -0
  5. package/dist/builtins/autoimplement.workflow.d.ts +210 -11
  6. package/dist/builtins/autoimplement.workflow.js +245 -13
  7. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  8. package/dist/builtins/{autodevise.workflow.d.ts → autoplan.workflow.d.ts} +9 -9
  9. package/dist/builtins/{autodevise.workflow.js → autoplan.workflow.js} +20 -20
  10. package/dist/builtins/autoplan.workflow.js.map +1 -0
  11. package/dist/builtins/catalog.js +8 -4
  12. package/dist/builtins/catalog.js.map +1 -1
  13. package/dist/builtins/index.d.ts +6 -3
  14. package/dist/builtins/index.js +5 -2
  15. package/dist/builtins/index.js.map +1 -1
  16. package/dist/builtins/monitor.workflow.d.ts +4 -0
  17. package/dist/builtins/monitor.workflow.js +134 -9
  18. package/dist/builtins/monitor.workflow.js.map +1 -1
  19. package/dist/builtins/plan-approval.workflow.d.ts +83 -0
  20. package/dist/builtins/plan-approval.workflow.js +148 -0
  21. package/dist/builtins/plan-approval.workflow.js.map +1 -0
  22. package/dist/builtins/plan-presentation.d.ts +7 -0
  23. package/dist/builtins/plan-presentation.js +44 -0
  24. package/dist/builtins/plan-presentation.js.map +1 -0
  25. package/dist/extension/decision-channels.d.ts +132 -0
  26. package/dist/extension/decision-channels.js +1082 -0
  27. package/dist/extension/decision-channels.js.map +1 -0
  28. package/dist/extension/index.d.ts +1 -0
  29. package/dist/extension/index.js +426 -11
  30. package/dist/extension/index.js.map +1 -1
  31. package/dist/extension/widget.js +26 -2
  32. package/dist/extension/widget.js.map +1 -1
  33. package/dist/extension/workflow-tool.d.ts +1 -37
  34. package/dist/extension/workflow-tool.js +1 -42
  35. package/dist/extension/workflow-tool.js.map +1 -1
  36. package/dist/host/rpc-bridge.js +4 -21
  37. package/dist/host/rpc-bridge.js.map +1 -1
  38. package/dist/render/graph-render.js +33 -8
  39. package/dist/render/graph-render.js.map +1 -1
  40. package/dist/viewer/render.js +36 -2
  41. package/dist/viewer/render.js.map +1 -1
  42. package/dist/workflows/composition.js +15 -1
  43. package/dist/workflows/composition.js.map +1 -1
  44. package/dist/workflows/decision-presentation.d.ts +18 -0
  45. package/dist/workflows/decision-presentation.js +417 -0
  46. package/dist/workflows/decision-presentation.js.map +1 -0
  47. package/dist/workflows/engine.d.ts +2 -1
  48. package/dist/workflows/engine.js +86 -4
  49. package/dist/workflows/engine.js.map +1 -1
  50. package/dist/workflows/human-decision.d.ts +87 -0
  51. package/dist/workflows/human-decision.js +583 -0
  52. package/dist/workflows/human-decision.js.map +1 -0
  53. package/dist/workflows/index.d.ts +3 -1
  54. package/dist/workflows/index.js +2 -0
  55. package/dist/workflows/index.js.map +1 -1
  56. package/dist/workflows/schema.js +15 -0
  57. package/dist/workflows/schema.js.map +1 -1
  58. package/dist/workflows/store.js +7 -0
  59. package/dist/workflows/store.js.map +1 -1
  60. package/dist/workflows/tool-input.d.ts +72 -0
  61. package/dist/workflows/tool-input.js +141 -0
  62. package/dist/workflows/tool-input.js.map +1 -0
  63. package/dist/workflows/types.d.ts +223 -0
  64. package/docs/HUMAN_DECISIONS.md +371 -0
  65. package/docs/HUMAN_DECISION_PRESENTATIONS.md +323 -0
  66. package/docs/MONITOR.md +17 -6
  67. package/docs/WORKFLOW_COMPOSITION.md +13 -6
  68. package/docs/plans/2026-08-19-human-decision-gates-plan.md +296 -0
  69. package/docs/plans/2026-08-19-human-decision-presentations-plan.md +172 -0
  70. package/docs/plans/2026-08-19-provider-compatible-workflow-tool-schema-plan.md +45 -0
  71. package/docs/plans/2026-08-19-workflow-composition-plan.md +25 -17
  72. package/docs/run-bundles.md +23 -2
  73. package/docs/workflows.md +43 -5
  74. package/examples/workflows/approved-plan.workflow.ts +58 -0
  75. package/examples/workflows/autoplan.workflow.ts +1 -0
  76. package/examples/workflows/human-decision.workflow.ts +62 -0
  77. package/herdr-plugin.toml +1 -1
  78. package/package.json +2 -1
  79. package/schemas/decision-presentation-v1.schema.json +83 -0
  80. package/schemas/human-decision-accepted-v1.schema.json +44 -0
  81. package/schemas/human-decision-accepted-v2.schema.json +50 -0
  82. package/schemas/human-decision-answer-attempt-v1.schema.json +37 -0
  83. package/schemas/human-decision-cancellation-v1.schema.json +15 -0
  84. package/schemas/human-decision-continuation-v1.schema.json +16 -0
  85. package/schemas/human-decision-delivery-v1.schema.json +28 -0
  86. package/schemas/human-decision-delivery-v2.schema.json +36 -0
  87. package/schemas/human-decision-receipt-v1.schema.json +33 -0
  88. package/schemas/human-decision-receipt-v2.schema.json +39 -0
  89. package/schemas/human-decision-request-v1.schema.json +59 -0
  90. package/schemas/human-decision-request-v2.schema.json +69 -0
  91. package/schemas/human-decision-resolution-v1.schema.json +27 -0
  92. package/schemas/human-decision-resolution-v2.schema.json +27 -0
  93. package/schemas/human-decision-settlement-v1.schema.json +28 -0
  94. package/skills/autodoc/SKILL.md +43 -0
  95. package/skills/autoimplement/SKILL.md +58 -0
  96. package/skills/autoimplement/agents/openai.yaml +4 -0
  97. package/skills/autoplan/SKILL.md +25 -0
  98. package/skills/monitor/SKILL.md +3 -1
  99. package/skills/pi-workflows/SKILL.md +3 -3
  100. package/src/builtins/autodoc.workflow.ts +325 -0
  101. package/src/builtins/autoimplement.workflow.ts +281 -14
  102. package/src/builtins/{autodevise.workflow.ts → autoplan.workflow.ts} +32 -32
  103. package/src/builtins/catalog.ts +8 -4
  104. package/src/builtins/index.ts +25 -6
  105. package/src/builtins/monitor.workflow.ts +162 -8
  106. package/src/builtins/plan-approval.workflow.ts +190 -0
  107. package/src/builtins/plan-presentation.ts +57 -0
  108. package/src/extension/decision-channels.ts +1533 -0
  109. package/src/extension/index.ts +499 -9
  110. package/src/extension/widget.ts +39 -2
  111. package/src/extension/workflow-tool.ts +5 -81
  112. package/src/host/rpc-bridge.ts +7 -30
  113. package/src/render/graph-render.ts +40 -3
  114. package/src/viewer/render.ts +56 -3
  115. package/src/workflows/composition.ts +16 -1
  116. package/src/workflows/decision-presentation.ts +501 -0
  117. package/src/workflows/engine.ts +108 -4
  118. package/src/workflows/human-decision.ts +819 -0
  119. package/src/workflows/index.ts +69 -0
  120. package/src/workflows/schema.ts +17 -0
  121. package/src/workflows/store.ts +8 -0
  122. package/src/workflows/tool-input.ts +202 -0
  123. package/src/workflows/types.ts +265 -0
  124. package/dist/builtins/autodevise.workflow.js.map +0 -1
  125. package/examples/workflows/autodevise.workflow.ts +0 -1
@@ -297,8 +297,36 @@ function nodeRuntimeSegments(
297
297
  }
298
298
 
299
299
  if (state.waitingOn === nodeId) {
300
- const summary = snapshot.nodes[nodeId]?.summary;
301
- segments.push(summary ? sanitizeText(summary) : "waiting");
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
- import { StringEnum } from "@earendil-works/pi-ai";
2
- import { Type, type TSchema } from "typebox";
3
-
4
- const noExtraProperties = { additionalProperties: false } as const;
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";
@@ -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 { Type } from "typebox";
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: Type.Union([
23
- Type.Object(
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,14 +305,51 @@ 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
- atLatestStep && state.currentNode === nodeId && state.statusDetail
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
  );
@@ -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 { WorkflowRunStatus, WorkflowStepRecord } from "../workflows/types.js";
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
- suffix = ansi.yellow(" waiting");
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
- suffix = ansi.dim(` ${formatDuration(result.durationMs)}`);
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`
@@ -477,7 +477,9 @@ function wrapNode(
477
477
  message: (context) => (node as NotifyNodeDefinition).message(project(context)),
478
478
  ...(node.kind !== undefined ? { kind: node.kind } : {}),
479
479
  };
480
- case "checkpoint":
480
+ case "checkpoint": {
481
+ const human = node.humanDecision;
482
+ const audience = human?.audience;
481
483
  return {
482
484
  ...common,
483
485
  nodeType: "checkpoint",
@@ -485,7 +487,20 @@ function wrapNode(
485
487
  ...(node.run !== undefined
486
488
  ? { run: (context: WorkflowNodeContext) => node.run?.(project(context)) }
487
489
  : {}),
490
+ ...(human !== undefined
491
+ ? {
492
+ humanDecision: {
493
+ audience:
494
+ typeof audience === "function"
495
+ ? (context: WorkflowNodeContext) => audience(project(context))
496
+ : audience!,
497
+ choices: human.choices,
498
+ request: (context: WorkflowNodeContext) => human.request(project(context)),
499
+ },
500
+ }
501
+ : {}),
488
502
  } as CheckpointNodeDefinition;
503
+ }
489
504
  case "action":
490
505
  if ("exec" in node) {
491
506
  const shell = node as ShellActionNodeDefinition;