@osolmaz/pi-workflows 0.11.2 → 0.12.1
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 +15 -10
- package/dist/builtins/autoimplement-command-batches.d.ts +52 -0
- package/dist/builtins/autoimplement-command-batches.js +258 -0
- package/dist/builtins/autoimplement-command-batches.js.map +1 -0
- package/dist/builtins/autoimplement.workflow.d.ts +509 -124
- package/dist/builtins/autoimplement.workflow.js +884 -327
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/catalog.js +5 -3
- package/dist/builtins/catalog.js.map +1 -1
- package/dist/builtins/index.d.ts +4 -1
- package/dist/builtins/index.js +3 -1
- package/dist/builtins/index.js.map +1 -1
- package/dist/builtins/monitor.workflow.d.ts +2 -4
- package/dist/builtins/monitor.workflow.js +26 -128
- package/dist/builtins/monitor.workflow.js.map +1 -1
- package/dist/builtins/pi-agent-group.d.ts +72 -0
- package/dist/builtins/pi-agent-group.js +1087 -0
- package/dist/builtins/pi-agent-group.js.map +1 -0
- package/dist/builtins/plan-approval.workflow.d.ts +39 -5
- package/dist/builtins/plan-approval.workflow.js +92 -14
- package/dist/builtins/plan-approval.workflow.js.map +1 -1
- package/dist/builtins/plan-change.workflow.d.ts +301 -0
- package/dist/builtins/plan-change.workflow.js +256 -0
- package/dist/builtins/plan-change.workflow.js.map +1 -0
- package/dist/builtins/plan-presentation.js +2 -2
- package/dist/builtins/plan-presentation.js.map +1 -1
- package/dist/builtins/sanity-check.workflow.d.ts +85 -0
- package/dist/builtins/sanity-check.workflow.js +482 -0
- package/dist/builtins/sanity-check.workflow.js.map +1 -0
- package/dist/controllers/sqlite.d.ts +90 -2
- package/dist/controllers/sqlite.js +380 -5
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/decision-channels.d.ts +2 -2
- package/dist/extension/decision-channels.js +29 -35
- package/dist/extension/decision-channels.js.map +1 -1
- package/dist/extension/deferred-turn-coordinator.d.ts +32 -0
- package/dist/extension/deferred-turn-coordinator.js +143 -0
- package/dist/extension/deferred-turn-coordinator.js.map +1 -0
- package/dist/extension/deferred-turn.d.ts +44 -0
- package/dist/extension/deferred-turn.js +110 -0
- package/dist/extension/deferred-turn.js.map +1 -0
- package/dist/extension/index.js +348 -80
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/session-events.d.ts +2 -2
- package/dist/extension/step-message.d.ts +1 -0
- package/dist/extension/step-message.js.map +1 -1
- package/dist/extension/widget.js +23 -3
- package/dist/extension/widget.js.map +1 -1
- package/dist/herdr/setup.js +4 -4
- package/dist/render/graph-render.js +1 -2
- package/dist/render/graph-render.js.map +1 -1
- package/dist/viewer/render.js +7 -6
- package/dist/viewer/render.js.map +1 -1
- package/dist/workflows/catalog.js +7 -2
- package/dist/workflows/catalog.js.map +1 -1
- package/dist/workflows/command-batch.d.ts +38 -0
- package/dist/workflows/command-batch.js +176 -0
- package/dist/workflows/command-batch.js.map +1 -0
- package/dist/workflows/composition.js +8 -0
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/decision-presentation.d.ts +1 -1
- package/dist/workflows/decision-presentation.js +51 -38
- package/dist/workflows/decision-presentation.js.map +1 -1
- package/dist/workflows/engine.d.ts +2 -2
- package/dist/workflows/engine.js +22 -17
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/errors.d.ts +13 -0
- package/dist/workflows/errors.js +15 -0
- package/dist/workflows/errors.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +16 -4
- package/dist/workflows/human-decision.js +175 -72
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +3 -2
- package/dist/workflows/index.js +2 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/progress.d.ts +1 -0
- package/dist/workflows/progress.js +15 -3
- package/dist/workflows/progress.js.map +1 -1
- package/dist/workflows/schema.js +12 -1
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/shell.d.ts +4 -0
- package/dist/workflows/shell.js +6 -0
- package/dist/workflows/shell.js.map +1 -1
- package/dist/workflows/store.js +8 -1
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +37 -48
- package/docs/2026-08-18-herdr-piw-plan.md +1 -1
- package/docs/2026-08-20-durable-workflow-launch-plan.md +19 -23
- package/docs/CONTROLLERS.md +2 -2
- package/docs/DEFERRED_TURNS.md +298 -0
- package/docs/DESIGN_PHILOSOPHY.md +1 -1
- package/docs/HUMAN_DECISIONS.md +30 -40
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +17 -27
- package/docs/MONITOR.md +7 -13
- package/docs/WORKFLOW_COMPOSITION.md +10 -9
- package/docs/WORKFLOW_STEP_MESSAGES.md +5 -5
- package/docs/WORKFLOW_UPDATES.md +28 -5
- package/docs/plans/2026-08-04-controller-runtime-plan.md +1 -1
- package/docs/plans/2026-08-05-always-on-workflows-plan.md +1 -1
- package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +9 -9
- package/docs/plans/2026-08-13-built-in-workflow-catalog-plan.md +1 -1
- package/docs/plans/2026-08-13-session-addressed-workflow-notifications-plan.md +1 -1
- package/docs/plans/2026-08-16-workflow-updates-plan.md +3 -3
- package/docs/plans/2026-08-17-bundled-skills-plan.md +6 -6
- package/docs/plans/2026-08-19-human-decision-gates-plan.md +5 -5
- package/docs/plans/2026-08-19-human-decision-presentations-plan.md +1 -1
- package/docs/plans/2026-08-19-provider-compatible-workflow-tool-schema-plan.md +1 -1
- package/docs/plans/2026-08-19-workflow-composition-plan.md +6 -6
- package/docs/plans/2026-08-20-autoimplement-blocker-challenge-plan.md +3 -3
- package/docs/plans/2026-08-20-bounded-command-batches-plan.md +206 -0
- package/docs/plans/2026-08-20-herdr-plugin-sync-plan.md +9 -9
- package/docs/plans/2026-08-21-autoimplement-timeout-fallback-plan.md +242 -0
- package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +324 -0
- package/docs/plans/2026-08-21-plan-change-approval-policy-plan.md +322 -0
- package/docs/plans/2026-08-21-sanity-check-plan.md +283 -0
- package/docs/run-bundles.md +8 -6
- package/docs/workflows.md +85 -22
- package/examples/workflows/approved-plan.workflow.ts +19 -46
- package/examples/workflows/command-batch.workflow.ts +50 -0
- package/examples/workflows/sanity-check.workflow.ts +1 -0
- package/herdr-plugin.toml +3 -3
- package/package.json +7 -7
- package/schemas/decision-presentation-v1.schema.json +1 -1
- package/schemas/human-decision-accepted-v1.schema.json +16 -4
- package/schemas/human-decision-answer-attempt-v1.schema.json +1 -1
- package/schemas/human-decision-cancellation-v1.schema.json +1 -1
- package/schemas/human-decision-continuation-v1.schema.json +11 -2
- package/schemas/human-decision-delivery-v1.schema.json +9 -1
- package/schemas/human-decision-receipt-v1.schema.json +9 -1
- package/schemas/human-decision-request-v1.schema.json +25 -5
- package/schemas/human-decision-resolution-v1.schema.json +1 -1
- package/schemas/human-decision-settlement-v1.schema.json +1 -1
- package/skills/autodoc/SKILL.md +33 -2
- package/skills/autoimplement/SKILL.md +93 -12
- package/skills/autoplan/SKILL.md +26 -2
- package/skills/monitor/SKILL.md +66 -24
- package/skills/pi-workflows/SKILL.md +21 -6
- package/skills/sanity-check/SKILL.md +44 -0
- package/src/builtins/autoimplement-command-batches.ts +364 -0
- package/src/builtins/autoimplement.workflow.ts +1075 -370
- package/src/builtins/catalog.ts +5 -3
- package/src/builtins/index.ts +24 -0
- package/src/builtins/monitor.workflow.ts +27 -150
- package/src/builtins/pi-agent-group.ts +1407 -0
- package/src/builtins/plan-approval.workflow.ts +157 -24
- package/src/builtins/plan-change.workflow.ts +321 -0
- package/src/builtins/plan-presentation.ts +2 -2
- package/src/builtins/sanity-check.workflow.ts +769 -0
- package/src/controllers/sqlite.ts +580 -7
- package/src/extension/decision-channels.ts +36 -66
- package/src/extension/deferred-turn-coordinator.ts +171 -0
- package/src/extension/deferred-turn.ts +166 -0
- package/src/extension/index.ts +438 -90
- package/src/extension/session-events.ts +2 -2
- package/src/extension/step-message.ts +1 -0
- package/src/extension/widget.ts +24 -5
- package/src/herdr/setup.ts +4 -4
- package/src/render/graph-render.ts +1 -2
- package/src/viewer/render.ts +7 -6
- package/src/workflows/catalog.ts +7 -2
- package/src/workflows/command-batch.ts +254 -0
- package/src/workflows/composition.ts +9 -0
- package/src/workflows/decision-presentation.ts +56 -43
- package/src/workflows/engine.ts +25 -20
- package/src/workflows/errors.ts +24 -0
- package/src/workflows/human-decision.ts +218 -101
- package/src/workflows/index.ts +20 -11
- package/src/workflows/progress.ts +18 -3
- package/src/workflows/schema.ts +19 -1
- package/src/workflows/shell.ts +10 -0
- package/src/workflows/store.ts +8 -1
- package/src/workflows/types.ts +43 -59
- package/schemas/human-decision-accepted-v2.schema.json +0 -50
- package/schemas/human-decision-delivery-v2.schema.json +0 -36
- package/schemas/human-decision-receipt-v2.schema.json +0 -39
- package/schemas/human-decision-request-v2.schema.json +0 -69
- package/schemas/human-decision-resolution-v2.schema.json +0 -27
|
@@ -56,7 +56,18 @@ export function decisionPresentationDigest(presentation: DecisionPresentation):
|
|
|
56
56
|
export function validateHumanDecisionRequestIntegrity(
|
|
57
57
|
request: HumanDecisionRequest,
|
|
58
58
|
): HumanDecisionRequest {
|
|
59
|
-
if (
|
|
59
|
+
if (
|
|
60
|
+
request.schema !== "pi-workflows.human-decision-request.v1" ||
|
|
61
|
+
!Object.hasOwn(request, "subject") ||
|
|
62
|
+
!Object.hasOwn(request, "presentation") ||
|
|
63
|
+
!Object.hasOwn(request, "subjectDigest") ||
|
|
64
|
+
!Object.hasOwn(request, "presentationDigest") ||
|
|
65
|
+
!Object.hasOwn(request, "revision")
|
|
66
|
+
) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"Human decision state uses an incompatible alpha contract; reset the affected workflow run and decision state.",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
60
71
|
const presentation = normalizeDecisionPresentation(request.presentation);
|
|
61
72
|
const subjectDigest = digestCanonical(request.subject);
|
|
62
73
|
const presentationDigest = digestCanonical(presentation);
|
|
@@ -70,6 +81,7 @@ export function validateHumanDecisionRequestIntegrity(
|
|
|
70
81
|
title: request.title,
|
|
71
82
|
choices: request.choices,
|
|
72
83
|
...(request.expiresAt !== undefined ? { expiresAt: request.expiresAt } : {}),
|
|
84
|
+
...(request.defaultResponse !== undefined ? { defaultResponse: request.defaultResponse } : {}),
|
|
73
85
|
subject: request.subject,
|
|
74
86
|
presentation,
|
|
75
87
|
revision: request.revision,
|
|
@@ -90,10 +102,7 @@ export function humanDecisionChannelRequest(
|
|
|
90
102
|
request: HumanDecisionRequest,
|
|
91
103
|
): HumanDecisionChannelRequest {
|
|
92
104
|
validateHumanDecisionRequestIntegrity(request);
|
|
93
|
-
const presentation =
|
|
94
|
-
request.schema === "pi-workflows.human-decision-request.v2"
|
|
95
|
-
? normalizeDecisionPresentation(request.presentation)
|
|
96
|
-
: legacyDecisionPresentation(request.body);
|
|
105
|
+
const presentation = normalizeDecisionPresentation(request.presentation);
|
|
97
106
|
const presentationDigest = decisionPresentationDigest(presentation);
|
|
98
107
|
return {
|
|
99
108
|
schema: "pi-workflows.human-decision-channel-request.v1",
|
|
@@ -108,25 +117,26 @@ export function humanDecisionChannelRequest(
|
|
|
108
117
|
title: request.title,
|
|
109
118
|
presentation,
|
|
110
119
|
presentationDigest,
|
|
111
|
-
revision: request.
|
|
120
|
+
revision: request.revision,
|
|
112
121
|
choices: request.choices,
|
|
113
122
|
createdAt: request.createdAt,
|
|
114
123
|
...(request.expiresAt !== undefined ? { expiresAt: request.expiresAt } : {}),
|
|
124
|
+
...(request.defaultResponse !== undefined ? { defaultResponse: request.defaultResponse } : {}),
|
|
115
125
|
};
|
|
116
126
|
}
|
|
117
127
|
|
|
118
|
-
export function
|
|
128
|
+
export function valueDecisionPresentation(value: unknown): DecisionPresentation {
|
|
119
129
|
const blocks: DecisionPresentationBlock[] = [];
|
|
120
|
-
if (typeof
|
|
121
|
-
|
|
130
|
+
if (typeof value === "string") {
|
|
131
|
+
appendValueText(blocks, value, "Decision details");
|
|
122
132
|
} else {
|
|
123
|
-
|
|
133
|
+
appendValue(blocks, value, undefined);
|
|
124
134
|
}
|
|
125
135
|
const summary =
|
|
126
|
-
typeof
|
|
136
|
+
typeof value === "string"
|
|
127
137
|
? "Review the decision message below."
|
|
128
138
|
: "Review the decision details below.";
|
|
129
|
-
return normalizeDecisionPresentation(
|
|
139
|
+
return normalizeDecisionPresentation(boundValuePresentation(summary, blocks, value));
|
|
130
140
|
}
|
|
131
141
|
|
|
132
142
|
export function decisionDocumentSegments(
|
|
@@ -150,6 +160,13 @@ export function decisionDocumentSegments(
|
|
|
150
160
|
segments.push({ kind: block.kind, text: block.text });
|
|
151
161
|
}
|
|
152
162
|
}
|
|
163
|
+
if (request.expiresAt !== undefined && request.defaultResponse !== undefined) {
|
|
164
|
+
const selected = request.choices[request.defaultResponse.choice];
|
|
165
|
+
segments.push({
|
|
166
|
+
kind: "paragraph",
|
|
167
|
+
text: `If no answer is accepted by ${request.expiresAt}, this decision continues automatically with: ${selected?.label ?? request.defaultResponse.choice}.`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
153
170
|
const choices = Object.values(request.choices).map((choice) =>
|
|
154
171
|
choice.input === undefined
|
|
155
172
|
? `• ${choice.label}`
|
|
@@ -251,10 +268,10 @@ function normalizeString(value: unknown, label: string): string {
|
|
|
251
268
|
return text;
|
|
252
269
|
}
|
|
253
270
|
|
|
254
|
-
function
|
|
271
|
+
function boundValuePresentation(
|
|
255
272
|
summary: string,
|
|
256
273
|
blocks: DecisionPresentationBlock[],
|
|
257
|
-
|
|
274
|
+
value: unknown,
|
|
258
275
|
): DecisionPresentation {
|
|
259
276
|
const candidate: DecisionPresentation = {
|
|
260
277
|
schema: "pi-workflows.decision-presentation.v1",
|
|
@@ -263,13 +280,13 @@ function boundLegacyPresentation(
|
|
|
263
280
|
};
|
|
264
281
|
if (
|
|
265
282
|
blocks.length <= MAX_PRESENTATION_BLOCKS &&
|
|
266
|
-
blocks.every(
|
|
283
|
+
blocks.every(valueBlockFitsSchema) &&
|
|
267
284
|
presentationCodeUnits(candidate) <= MAX_PRESENTATION_CODE_UNITS
|
|
268
285
|
) {
|
|
269
286
|
return candidate;
|
|
270
287
|
}
|
|
271
|
-
const originalCodeUnits = canonicalJson(
|
|
272
|
-
const omission = `Some
|
|
288
|
+
const originalCodeUnits = canonicalJson(value).length;
|
|
289
|
+
const omission = `Some decision content is not shown because it exceeds the readable presentation limit. Inspect the canonical source value before answering. Value digest: ${digestCanonical(value)}. Original value: ${originalCodeUnits} UTF-16 code units. Generated blocks: ${blocks.length}.`;
|
|
273
290
|
const budget = MAX_PRESENTATION_CODE_UNITS - summary.length - omission.length;
|
|
274
291
|
const kept: DecisionPresentationBlock[] = [];
|
|
275
292
|
let used = 0;
|
|
@@ -278,7 +295,7 @@ function boundLegacyPresentation(
|
|
|
278
295
|
const units = presentationBlockCodeUnits(block);
|
|
279
296
|
if (
|
|
280
297
|
kept.length >= MAX_PRESENTATION_BLOCKS ||
|
|
281
|
-
!
|
|
298
|
+
!valueBlockFitsSchema(block) ||
|
|
282
299
|
used + units > budget
|
|
283
300
|
) {
|
|
284
301
|
omitted = true;
|
|
@@ -298,7 +315,7 @@ function boundLegacyPresentation(
|
|
|
298
315
|
};
|
|
299
316
|
}
|
|
300
317
|
|
|
301
|
-
function
|
|
318
|
+
function valueBlockFitsSchema(block: DecisionPresentationBlock): boolean {
|
|
302
319
|
if (block.kind === "paragraph" || block.kind === "preformatted") {
|
|
303
320
|
return block.text.length <= MAX_PRESENTATION_STRING_CODE_UNITS;
|
|
304
321
|
}
|
|
@@ -332,47 +349,43 @@ function presentationCodeUnits(presentation: DecisionPresentation): number {
|
|
|
332
349
|
return total;
|
|
333
350
|
}
|
|
334
351
|
|
|
335
|
-
function
|
|
352
|
+
function appendValue(
|
|
336
353
|
blocks: DecisionPresentationBlock[],
|
|
337
354
|
value: unknown,
|
|
338
355
|
label: string | undefined,
|
|
339
356
|
): void {
|
|
340
357
|
if (isScalar(value)) {
|
|
341
|
-
const scalar =
|
|
358
|
+
const scalar = valueScalar(value);
|
|
342
359
|
if (label === undefined || scalar.length > MAX_PRESENTATION_STRING_CODE_UNITS) {
|
|
343
|
-
|
|
344
|
-
blocks,
|
|
345
|
-
scalar,
|
|
346
|
-
label === undefined ? "Decision details" : legacyLabel(label),
|
|
347
|
-
);
|
|
360
|
+
appendValueText(blocks, scalar, label === undefined ? "Decision details" : valueLabel(label));
|
|
348
361
|
} else {
|
|
349
|
-
blocks.push({ kind: "fields", items: [{ label:
|
|
362
|
+
blocks.push({ kind: "fields", items: [{ label: valueLabel(label), value: scalar }] });
|
|
350
363
|
}
|
|
351
364
|
return;
|
|
352
365
|
}
|
|
353
366
|
if (Array.isArray(value)) {
|
|
354
|
-
if (label !== undefined) blocks.push({ kind: "section", title:
|
|
367
|
+
if (label !== undefined) blocks.push({ kind: "section", title: valueLabel(label) });
|
|
355
368
|
if (value.length === 0) {
|
|
356
369
|
blocks.push({ kind: "paragraph", text: "None" });
|
|
357
370
|
return;
|
|
358
371
|
}
|
|
359
372
|
if (
|
|
360
373
|
value.every(isScalar) &&
|
|
361
|
-
value.every((item) =>
|
|
374
|
+
value.every((item) => valueScalar(item).length <= MAX_PRESENTATION_STRING_CODE_UNITS)
|
|
362
375
|
) {
|
|
363
|
-
const items = value.map(
|
|
376
|
+
const items = value.map(valueScalar);
|
|
364
377
|
for (let index = 0; index < items.length; index += MAX_PRESENTATION_ITEMS) {
|
|
365
378
|
blocks.push({ kind: "bullets", items: items.slice(index, index + MAX_PRESENTATION_ITEMS) });
|
|
366
379
|
}
|
|
367
380
|
return;
|
|
368
381
|
}
|
|
369
|
-
value.forEach((item, index) =>
|
|
382
|
+
value.forEach((item, index) => appendValue(blocks, item, `Item ${index + 1}`));
|
|
370
383
|
return;
|
|
371
384
|
}
|
|
372
385
|
const entries = Object.entries(value as Record<string, unknown>).sort(([left], [right]) =>
|
|
373
386
|
compareKeys(left, right),
|
|
374
387
|
);
|
|
375
|
-
if (label !== undefined) blocks.push({ kind: "section", title:
|
|
388
|
+
if (label !== undefined) blocks.push({ kind: "section", title: valueLabel(label) });
|
|
376
389
|
if (entries.length === 0) {
|
|
377
390
|
blocks.push({ kind: "paragraph", text: "None" });
|
|
378
391
|
return;
|
|
@@ -380,34 +393,34 @@ function appendLegacyValue(
|
|
|
380
393
|
const fields = entries
|
|
381
394
|
.filter(
|
|
382
395
|
([, child]) =>
|
|
383
|
-
isScalar(child) &&
|
|
396
|
+
isScalar(child) && valueScalar(child).length <= MAX_PRESENTATION_STRING_CODE_UNITS,
|
|
384
397
|
)
|
|
385
|
-
.map(([key, child]) => ({ label:
|
|
398
|
+
.map(([key, child]) => ({ label: valueLabel(key), value: valueScalar(child) }));
|
|
386
399
|
for (let index = 0; index < fields.length; index += MAX_PRESENTATION_ITEMS) {
|
|
387
400
|
blocks.push({ kind: "fields", items: fields.slice(index, index + MAX_PRESENTATION_ITEMS) });
|
|
388
401
|
}
|
|
389
402
|
for (const [key, child] of entries.filter(
|
|
390
403
|
([, child]) =>
|
|
391
|
-
!isScalar(child) ||
|
|
404
|
+
!isScalar(child) || valueScalar(child).length > MAX_PRESENTATION_STRING_CODE_UNITS,
|
|
392
405
|
)) {
|
|
393
|
-
|
|
406
|
+
appendValue(blocks, child, key);
|
|
394
407
|
}
|
|
395
408
|
}
|
|
396
409
|
|
|
397
|
-
function
|
|
410
|
+
function appendValueText(
|
|
398
411
|
blocks: DecisionPresentationBlock[],
|
|
399
412
|
value: string,
|
|
400
413
|
section: string,
|
|
401
414
|
): void {
|
|
402
|
-
const text =
|
|
415
|
+
const text = sanitizeValueText(value);
|
|
403
416
|
blocks.push({ kind: "section", title: section });
|
|
404
417
|
for (const part of splitByCodeUnits(text, MAX_PRESENTATION_STRING_CODE_UNITS)) {
|
|
405
418
|
blocks.push({ kind: "paragraph", text: part.trim().length === 0 ? "None" : part });
|
|
406
419
|
}
|
|
407
420
|
}
|
|
408
421
|
|
|
409
|
-
function
|
|
410
|
-
const sanitized =
|
|
422
|
+
function valueLabel(value: string): string {
|
|
423
|
+
const sanitized = sanitizeValueText(value)
|
|
411
424
|
.replaceAll(/[_-]+/gu, " ")
|
|
412
425
|
.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2")
|
|
413
426
|
.replaceAll(/\s+/gu, " ")
|
|
@@ -416,13 +429,13 @@ function legacyLabel(value: string): string {
|
|
|
416
429
|
return `${sanitized[0]!.toUpperCase()}${sanitized.slice(1)}`;
|
|
417
430
|
}
|
|
418
431
|
|
|
419
|
-
function
|
|
432
|
+
function valueScalar(value: unknown): string {
|
|
420
433
|
if (value === null) return "None";
|
|
421
434
|
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
422
|
-
return
|
|
435
|
+
return sanitizeValueText(String(value));
|
|
423
436
|
}
|
|
424
437
|
|
|
425
|
-
function
|
|
438
|
+
function sanitizeValueText(value: string): string {
|
|
426
439
|
return value
|
|
427
440
|
.replaceAll("\r\n", "\n")
|
|
428
441
|
.replaceAll("\r", "\n")
|
package/src/workflows/engine.ts
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
readRunBundle,
|
|
33
33
|
} from "./store.js";
|
|
34
34
|
import type {
|
|
35
|
-
|
|
35
|
+
ResolvedHumanDecision,
|
|
36
36
|
AgentNodeDefinition,
|
|
37
37
|
AgentStepExecutor,
|
|
38
38
|
ActionNodeDefinition,
|
|
@@ -377,7 +377,7 @@ export class WorkflowEngine {
|
|
|
377
377
|
workflowSource?: WorkflowSource;
|
|
378
378
|
runId?: string;
|
|
379
379
|
force?: boolean;
|
|
380
|
-
humanDecision?:
|
|
380
|
+
humanDecision?: ResolvedHumanDecision;
|
|
381
381
|
} = {},
|
|
382
382
|
): Promise<WorkflowRunResult> {
|
|
383
383
|
workflow = isCompiledWorkflow(workflow) ? workflow : compileWorkflowDefinition(workflow);
|
|
@@ -412,14 +412,13 @@ export class WorkflowEngine {
|
|
|
412
412
|
}
|
|
413
413
|
const request = parent.state.finalOutput as HumanDecisionRequest;
|
|
414
414
|
if (
|
|
415
|
-
|
|
416
|
-
request?.schema !== "pi-workflows.human-decision-request.v2") ||
|
|
415
|
+
request?.schema !== "pi-workflows.human-decision-request.v1" ||
|
|
417
416
|
request.decisionId !== options.humanDecision.decisionId ||
|
|
418
417
|
request.requestDigest !== options.humanDecision.requestDigest
|
|
419
418
|
) {
|
|
420
419
|
throw new Error("Accepted human decision does not match the waiting request");
|
|
421
420
|
}
|
|
422
|
-
const durableDecision = await new HumanDecisionStore(this.store.outputRoot).
|
|
421
|
+
const durableDecision = await new HumanDecisionStore(this.store.outputRoot).readResolved(
|
|
423
422
|
request.decisionId,
|
|
424
423
|
);
|
|
425
424
|
if (durableDecision === null || !isDeepStrictEqual(durableDecision, options.humanDecision)) {
|
|
@@ -464,19 +463,17 @@ export class WorkflowEngine {
|
|
|
464
463
|
requestDigest: options.humanDecision.requestDigest,
|
|
465
464
|
nodeId: acceptedNodeId ?? waitingNodeId,
|
|
466
465
|
response: options.humanDecision.response,
|
|
466
|
+
provenance: options.humanDecision.provenance,
|
|
467
467
|
acceptedAt: options.humanDecision.acceptedAt,
|
|
468
468
|
answerDigest: options.humanDecision.answerDigest,
|
|
469
469
|
};
|
|
470
|
-
state.humanDecision =
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
revision: options.humanDecision.revision,
|
|
478
|
-
}
|
|
479
|
-
: { schema: "pi-workflows.human-decision-receipt.v1", ...receipt };
|
|
470
|
+
state.humanDecision = {
|
|
471
|
+
schema: "pi-workflows.human-decision-receipt.v1",
|
|
472
|
+
...receipt,
|
|
473
|
+
subjectDigest: options.humanDecision.subjectDigest,
|
|
474
|
+
presentationDigest: options.humanDecision.presentationDigest,
|
|
475
|
+
revision: options.humanDecision.revision,
|
|
476
|
+
};
|
|
480
477
|
state.outputs[waitingNodeId] = acceptedResponse;
|
|
481
478
|
const priorResult = state.results[waitingNodeId];
|
|
482
479
|
if (priorResult === undefined) {
|
|
@@ -983,9 +980,11 @@ export class WorkflowEngine {
|
|
|
983
980
|
if (abort.signal.aborted) {
|
|
984
981
|
throw abortError(abort.signal);
|
|
985
982
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
983
|
+
if (timeoutMs !== null) {
|
|
984
|
+
timer = setTimeout(() => {
|
|
985
|
+
abort.abort(new TimeoutError(timeoutMs));
|
|
986
|
+
}, timeoutMs);
|
|
987
|
+
}
|
|
989
988
|
this.activeAttempt = { runDir, state, nodeId, attemptId, signal: abort.signal };
|
|
990
989
|
const dispatched = this.dispatchNode(
|
|
991
990
|
workflow,
|
|
@@ -1047,8 +1046,9 @@ export class WorkflowEngine {
|
|
|
1047
1046
|
node: WorkflowNodeDefinition,
|
|
1048
1047
|
context: WorkflowNodeContext,
|
|
1049
1048
|
abort: AbortController,
|
|
1050
|
-
): Promise<number> {
|
|
1049
|
+
): Promise<number | null> {
|
|
1051
1050
|
const configured = node.timeoutMs;
|
|
1051
|
+
if (configured === null) return null;
|
|
1052
1052
|
if (typeof configured !== "function") {
|
|
1053
1053
|
return assertValidTimeout(configured ?? this.defaultNodeTimeoutMs);
|
|
1054
1054
|
}
|
|
@@ -1061,7 +1061,7 @@ export class WorkflowEngine {
|
|
|
1061
1061
|
Promise.resolve(configured(context)),
|
|
1062
1062
|
abortRejection(abort.signal),
|
|
1063
1063
|
]);
|
|
1064
|
-
return assertValidTimeout(resolved);
|
|
1064
|
+
return resolved === null ? null : assertValidTimeout(resolved);
|
|
1065
1065
|
} finally {
|
|
1066
1066
|
clearTimeout(timer);
|
|
1067
1067
|
}
|
|
@@ -1314,6 +1314,10 @@ async function runCheckpointNode(
|
|
|
1314
1314
|
typeof node.humanDecision.audience === "function"
|
|
1315
1315
|
? await node.humanDecision.audience(context)
|
|
1316
1316
|
: node.humanDecision.audience;
|
|
1317
|
+
const timeout =
|
|
1318
|
+
typeof node.humanDecision.onTimeout === "function"
|
|
1319
|
+
? await node.humanDecision.onTimeout(context)
|
|
1320
|
+
: node.humanDecision.onTimeout;
|
|
1317
1321
|
const request = createHumanDecisionRequest({
|
|
1318
1322
|
runId: context.state.runId,
|
|
1319
1323
|
workflowName: execution.workflowName,
|
|
@@ -1321,6 +1325,7 @@ async function runCheckpointNode(
|
|
|
1321
1325
|
attemptId: execution.attemptId,
|
|
1322
1326
|
contract: { audience, choices: node.humanDecision.choices },
|
|
1323
1327
|
prompt,
|
|
1328
|
+
...(timeout !== undefined ? { timeout } : {}),
|
|
1324
1329
|
});
|
|
1325
1330
|
await new HumanDecisionStore(execution.store.outputRoot).createRequest(request);
|
|
1326
1331
|
return { output: request, promptText: null };
|
package/src/workflows/errors.ts
CHANGED
|
@@ -56,6 +56,30 @@ export class WorkflowSourceChangedError extends Error {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/** A built-in revision is unavailable, so its incompatible run must restart. */
|
|
60
|
+
export class BuiltinWorkflowRevisionChangedError extends Error {
|
|
61
|
+
readonly runId: string;
|
|
62
|
+
readonly workflowId: string;
|
|
63
|
+
readonly previousRevision: string;
|
|
64
|
+
readonly currentRevision: string;
|
|
65
|
+
|
|
66
|
+
constructor(options: {
|
|
67
|
+
runId: string;
|
|
68
|
+
workflowId: string;
|
|
69
|
+
previousRevision: string;
|
|
70
|
+
currentRevision: string;
|
|
71
|
+
}) {
|
|
72
|
+
super(
|
|
73
|
+
`Built-in workflow ${options.workflowId} revision ${options.previousRevision} cannot resume run ${options.runId} with installed revision ${options.currentRevision}; cancel run ${options.runId}, then start ${options.workflowId} again`,
|
|
74
|
+
);
|
|
75
|
+
this.name = "BuiltinWorkflowRevisionChangedError";
|
|
76
|
+
this.runId = options.runId;
|
|
77
|
+
this.workflowId = options.workflowId;
|
|
78
|
+
this.previousRevision = options.previousRevision;
|
|
79
|
+
this.currentRevision = options.currentRevision;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
59
83
|
export function isClaimLostError(error: unknown): error is ClaimLostError {
|
|
60
84
|
return error instanceof ClaimLostError;
|
|
61
85
|
}
|