@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
package/dist/extension/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { projectControllerStorePath, SqliteControllerStore, } from "../controlle
|
|
|
6
6
|
import { compositionMetadata } from "../workflows/composition.js";
|
|
7
7
|
import { humanDecisionChannelRequest } from "../workflows/decision-presentation.js";
|
|
8
8
|
import { WorkflowEngine } from "../workflows/engine.js";
|
|
9
|
-
import { ClaimLostError, errorMessage, isClaimLostError, TimeoutError, } from "../workflows/errors.js";
|
|
9
|
+
import { BuiltinWorkflowRevisionChangedError, ClaimLostError, errorMessage, isClaimLostError, TimeoutError, } from "../workflows/errors.js";
|
|
10
10
|
import { HumanDecisionStore, createHumanDecisionAttemptId, validateHumanDecisionResponse, } from "../workflows/human-decision.js";
|
|
11
11
|
import { discoverWorkflows, resolveWorkflowRef } from "../workflows/loader.js";
|
|
12
12
|
import { migrateLegacyWorkflowSources } from "../workflows/migrate-sources.js";
|
|
@@ -14,6 +14,8 @@ import { appendProgressHistory, progressRecordsFromTrace } from "../workflows/pr
|
|
|
14
14
|
import { createRunId, listRunBundles, readLastTraceEvent, readRunBundle, WorkflowRunStore, createDefinitionSnapshot, } from "../workflows/store.js";
|
|
15
15
|
import { PiControllerHost, parseControllerArgs, } from "./controller-host.js";
|
|
16
16
|
import { PiDecisionChannel, TelegramDecisionChannel, audienceChannels, createTelegramChannels, loadDecisionChannelConfig, verifyTelegramTokenFile, writeDecisionChannelProfile, } from "./decision-channels.js";
|
|
17
|
+
import { DeferredTurnCoordinator, } from "./deferred-turn-coordinator.js";
|
|
18
|
+
import { buildDeferredTurnContent, createDeferredTurnDescriptor, DEFERRED_TURN_MESSAGE_TYPE, deferredTurnMessageDetails, deferredTurnMessageId, deferredTurnSourceEventId, } from "./deferred-turn.js";
|
|
17
19
|
import { ConversationStepExecutor } from "./executor.js";
|
|
18
20
|
import { HerdrWorkflowViewer, parseViewerPlacement, PIW_SHORTCUT, PIW_SHORTCUT_HINT, VIEWER_PLACEMENTS, } from "./herdr-viewer.js";
|
|
19
21
|
import { SessionRecorder } from "./recorder.js";
|
|
@@ -25,8 +27,10 @@ const RUN_CLAIM_LEASE_MS = 30_000;
|
|
|
25
27
|
const RUN_CLAIM_RENEW_MS = 10_000;
|
|
26
28
|
const RUN_SYNC_POLL_MS = 3_000;
|
|
27
29
|
const NOTIFICATION_DELIVERY_LEASE_MS = 30_000;
|
|
30
|
+
const TURN_INTENT_DELIVERY_LEASE_MS = 30_000;
|
|
28
31
|
const WIDGET_KEY = "pi-workflows";
|
|
29
32
|
const PRESENTATION_MESSAGE_TYPE = "pi-workflows-presentation";
|
|
33
|
+
const PRESENTATION_MESSAGE_SCHEMA = "pi-workflows.presentation-message.v1";
|
|
30
34
|
const FINAL_WIDGET_TTL_MS = 60_000;
|
|
31
35
|
const WIDGET_SCROLL_STEP = 3;
|
|
32
36
|
const MAX_PRESENTATION_RESULT_CHARS = 50_000;
|
|
@@ -51,10 +55,8 @@ function humanDecisionRequest(value) {
|
|
|
51
55
|
const schema = value !== null && typeof value === "object"
|
|
52
56
|
? value.schema
|
|
53
57
|
: undefined;
|
|
54
|
-
if (schema !== "pi-workflows.human-decision-request.v1"
|
|
55
|
-
schema !== "pi-workflows.human-decision-request.v2") {
|
|
58
|
+
if (schema !== "pi-workflows.human-decision-request.v1")
|
|
56
59
|
return null;
|
|
57
|
-
}
|
|
58
60
|
return value;
|
|
59
61
|
}
|
|
60
62
|
function definitionDigest(snapshot) {
|
|
@@ -66,6 +68,20 @@ function launchSourceIdentity(workflow, root) {
|
|
|
66
68
|
mounted: compositionMetadata(workflow)?.sources ?? [],
|
|
67
69
|
};
|
|
68
70
|
}
|
|
71
|
+
function continuationSourceChangedError(runId, previous, current) {
|
|
72
|
+
if (previous.kind === "builtin" &&
|
|
73
|
+
current.kind === "builtin" &&
|
|
74
|
+
previous.id === current.id &&
|
|
75
|
+
previous.revision !== current.revision) {
|
|
76
|
+
return new BuiltinWorkflowRevisionChangedError({
|
|
77
|
+
runId,
|
|
78
|
+
workflowId: current.id,
|
|
79
|
+
previousRevision: previous.revision,
|
|
80
|
+
currentRevision: current.revision,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return new Error(`Workflow source changed since run ${runId} started; revert the edit to answer its checkpoint`);
|
|
84
|
+
}
|
|
69
85
|
function preparedLaunchOptions(options) {
|
|
70
86
|
return {
|
|
71
87
|
...(options.presentation !== undefined ? { presentation: options.presentation } : {}),
|
|
@@ -220,6 +236,7 @@ export default function piWorkflows(pi) {
|
|
|
220
236
|
let activationRecovery;
|
|
221
237
|
let decisionRecoveryTimer = null;
|
|
222
238
|
let decisionRecoveryActive = false;
|
|
239
|
+
let turnCoordinator;
|
|
223
240
|
const recordRunEvent = (event) => {
|
|
224
241
|
try {
|
|
225
242
|
runQueueStore?.recordRunEvent({ ...event, runnerId });
|
|
@@ -266,9 +283,7 @@ export default function piWorkflows(pi) {
|
|
|
266
283
|
runId: notification.runId,
|
|
267
284
|
kind: notification.kind,
|
|
268
285
|
},
|
|
269
|
-
},
|
|
270
|
-
? { triggerTurn: true, deliverAs: "followUp" }
|
|
271
|
-
: { triggerTurn: false });
|
|
286
|
+
}, { triggerTurn: false });
|
|
272
287
|
alreadyDelivered.add(notification.notificationId);
|
|
273
288
|
}
|
|
274
289
|
runQueueStore.markWorkflowNotificationDelivered({
|
|
@@ -277,6 +292,19 @@ export default function piWorkflows(pi) {
|
|
|
277
292
|
claimToken,
|
|
278
293
|
});
|
|
279
294
|
}
|
|
295
|
+
const idle = ctx.isIdle() && systemTurnAbort === null && !sessionClosed && !runHeld();
|
|
296
|
+
turnCoordinator.flushNatural(idle);
|
|
297
|
+
turnCoordinator.deliverFallbacks({
|
|
298
|
+
targetSessionId: sessionId,
|
|
299
|
+
send: (intent) => {
|
|
300
|
+
pi.sendMessage({
|
|
301
|
+
customType: DEFERRED_TURN_MESSAGE_TYPE,
|
|
302
|
+
content: buildDeferredTurnContent(intent),
|
|
303
|
+
display: true,
|
|
304
|
+
details: deferredTurnMessageDetails(intent),
|
|
305
|
+
}, { triggerTurn: true, deliverAs: "followUp" });
|
|
306
|
+
},
|
|
307
|
+
}, idle);
|
|
280
308
|
}
|
|
281
309
|
catch {
|
|
282
310
|
// Delivery retries on the next poll. It never affects workflow execution.
|
|
@@ -312,6 +340,35 @@ export default function piWorkflows(pi) {
|
|
|
312
340
|
let presentationPending = null;
|
|
313
341
|
let controllerHost;
|
|
314
342
|
let controllerContext = null;
|
|
343
|
+
const branchIntentResolution = (intentId) => {
|
|
344
|
+
if (controllerContext === null)
|
|
345
|
+
return null;
|
|
346
|
+
for (const entry of controllerContext.sessionManager.getBranch()) {
|
|
347
|
+
if (entry.type !== "custom_message")
|
|
348
|
+
continue;
|
|
349
|
+
const details = entry.details;
|
|
350
|
+
if (details === null || typeof details !== "object" || Array.isArray(details))
|
|
351
|
+
continue;
|
|
352
|
+
if (details.turnIntentId !== intentId)
|
|
353
|
+
continue;
|
|
354
|
+
let resolution = null;
|
|
355
|
+
if (entry.customType === WORKFLOW_AGENT_STEP_MESSAGE_TYPE)
|
|
356
|
+
resolution = "workflowPrompt";
|
|
357
|
+
if (entry.customType === PRESENTATION_MESSAGE_TYPE)
|
|
358
|
+
resolution = "presentation";
|
|
359
|
+
if (entry.customType === DEFERRED_TURN_MESSAGE_TYPE)
|
|
360
|
+
resolution = "fallback";
|
|
361
|
+
if (resolution !== null) {
|
|
362
|
+
return { resolution, messageId: deferredTurnMessageId(intentId, resolution) };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
};
|
|
367
|
+
turnCoordinator = new DeferredTurnCoordinator({
|
|
368
|
+
store: () => runQueueStore,
|
|
369
|
+
branchResolution: branchIntentResolution,
|
|
370
|
+
leaseMs: TURN_INTENT_DELIVERY_LEASE_MS,
|
|
371
|
+
});
|
|
315
372
|
// UI updates are best-effort: a captured ctx becomes stale after session
|
|
316
373
|
// replacement or shutdown, and pi throws on any access (even `ctx.hasUI`).
|
|
317
374
|
// A workflow finishing right as the session goes away must not crash pi.
|
|
@@ -352,6 +409,115 @@ export default function piWorkflows(pi) {
|
|
|
352
409
|
};
|
|
353
410
|
/** True when the run is held for the user (escape or /workflow pause). */
|
|
354
411
|
const runHeld = () => activeRun !== null && (activeRun.engine.pauseRequested || activeRun.executor.held);
|
|
412
|
+
const originSessionId = (ctx, runId) => ensureRunQueueStore(ctx.cwd).getWorkflowRun(runId)?.originSessionId ??
|
|
413
|
+
ctx.sessionManager.getSessionId();
|
|
414
|
+
const storeTurnDescriptor = (ctx, descriptor, eligible) => {
|
|
415
|
+
ensureRunQueueStore(ctx.cwd).ensureWorkflowTurnIntent({ ...descriptor, eligible });
|
|
416
|
+
};
|
|
417
|
+
const ensureAbortTurnIntent = (ctx, run, cause, contract, reason) => {
|
|
418
|
+
if (run.abortProvenance?.descriptor !== undefined) {
|
|
419
|
+
return run.abortProvenance.descriptor;
|
|
420
|
+
}
|
|
421
|
+
const descriptor = createDeferredTurnDescriptor({
|
|
422
|
+
runId: run.runId,
|
|
423
|
+
workflowName: run.workflowName,
|
|
424
|
+
targetSessionId: originSessionId(ctx, run.runId),
|
|
425
|
+
cause,
|
|
426
|
+
sourceEventId: deferredTurnSourceEventId({
|
|
427
|
+
runId: run.runId,
|
|
428
|
+
cause,
|
|
429
|
+
nodeId: contract.nodeId,
|
|
430
|
+
attemptId: contract.attemptId,
|
|
431
|
+
source: "agent-step-abort",
|
|
432
|
+
}),
|
|
433
|
+
observedState: cause === "claimLost" ? "handedOff" : "interrupted",
|
|
434
|
+
nodeId: contract.nodeId,
|
|
435
|
+
attemptId: contract.attemptId,
|
|
436
|
+
reason: errorMessage(reason),
|
|
437
|
+
handoff: cause === "claimLost",
|
|
438
|
+
});
|
|
439
|
+
run.abortProvenance = { cause, descriptor };
|
|
440
|
+
try {
|
|
441
|
+
storeTurnDescriptor(ctx, descriptor, false);
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
run.abortProvenance.storageError = errorMessage(error);
|
|
445
|
+
}
|
|
446
|
+
return descriptor;
|
|
447
|
+
};
|
|
448
|
+
const makeRunTurnIntentEligible = (ctx, run, observedState, reason) => {
|
|
449
|
+
if (sessionClosed ||
|
|
450
|
+
run.suppressTurnIntent === true ||
|
|
451
|
+
run.abortProvenance?.cause === "claimLost") {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const targetSessionId = originSessionId(ctx, run.runId);
|
|
455
|
+
const pending = ensureRunQueueStore(ctx.cwd).findPendingWorkflowTurnIntent({
|
|
456
|
+
runId: run.runId,
|
|
457
|
+
targetSessionId,
|
|
458
|
+
});
|
|
459
|
+
if (run.childKey !== undefined &&
|
|
460
|
+
run.abortProvenance === undefined &&
|
|
461
|
+
pending?.cause !== "claimLost") {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const cause = pending?.cause ??
|
|
465
|
+
run.abortProvenance?.cause ??
|
|
466
|
+
(observedState === "timed_out" ? "timedOut" : "failed");
|
|
467
|
+
if (observedState === "cancelled" &&
|
|
468
|
+
run.abortProvenance === undefined &&
|
|
469
|
+
pending === undefined) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const previous = run.abortProvenance?.descriptor;
|
|
473
|
+
const sourceEventId = pending?.sourceEventId ??
|
|
474
|
+
previous?.sourceEventId ??
|
|
475
|
+
deferredTurnSourceEventId({
|
|
476
|
+
runId: run.runId,
|
|
477
|
+
cause,
|
|
478
|
+
nodeId: "$terminal",
|
|
479
|
+
source: "terminal",
|
|
480
|
+
});
|
|
481
|
+
const descriptor = createDeferredTurnDescriptor({
|
|
482
|
+
runId: run.runId,
|
|
483
|
+
workflowName: pending?.workflowRef ?? run.workflowName,
|
|
484
|
+
targetSessionId,
|
|
485
|
+
cause,
|
|
486
|
+
sourceEventId,
|
|
487
|
+
observedState,
|
|
488
|
+
nodeId: pending?.nodeId ?? previous?.nodeId ?? "$terminal",
|
|
489
|
+
attemptId: pending?.attemptId ?? previous?.attemptId ?? null,
|
|
490
|
+
reason: reason ?? null,
|
|
491
|
+
handoff: false,
|
|
492
|
+
});
|
|
493
|
+
run.abortProvenance = {
|
|
494
|
+
cause,
|
|
495
|
+
descriptor,
|
|
496
|
+
...(run.abortProvenance?.storageError === undefined
|
|
497
|
+
? {}
|
|
498
|
+
: { storageError: run.abortProvenance.storageError }),
|
|
499
|
+
};
|
|
500
|
+
try {
|
|
501
|
+
storeTurnDescriptor(ctx, descriptor, false);
|
|
502
|
+
ensureRunQueueStore(ctx.cwd).makeWorkflowTurnIntentEligible({
|
|
503
|
+
intentId: descriptor.intentId,
|
|
504
|
+
fallbackFacts: descriptor.fallbackFacts,
|
|
505
|
+
});
|
|
506
|
+
delete run.abortProvenance.storageError;
|
|
507
|
+
syncArmed = true;
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
const message = errorMessage(error);
|
|
511
|
+
run.abortProvenance.storageError = message;
|
|
512
|
+
recordRunEvent({
|
|
513
|
+
runId: run.runId,
|
|
514
|
+
workflowRef: run.workflowName,
|
|
515
|
+
type: "turn_intent_failed",
|
|
516
|
+
payload: { error: message },
|
|
517
|
+
});
|
|
518
|
+
notify(ctx, `Workflow ${run.workflowName} ended, but Pi Workflows could not preserve its successor turn: ${message}`, "warning");
|
|
519
|
+
}
|
|
520
|
+
};
|
|
355
521
|
const footerStatus = (state) => {
|
|
356
522
|
const label = runHeld() || state.paused ? "paused" : state.status;
|
|
357
523
|
const node = state.currentNode ?? state.waitingOn;
|
|
@@ -512,20 +678,36 @@ export default function piWorkflows(pi) {
|
|
|
512
678
|
const instructions = typeof run.presentationPrompt === "function"
|
|
513
679
|
? await resolvePresentationPrompt(run.presentationPrompt, state, abort.signal)
|
|
514
680
|
: run.presentationPrompt;
|
|
515
|
-
if (sessionClosed ||
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
681
|
+
if (sessionClosed || run.generation !== runGeneration || abort.signal.aborted) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
if (instructions === undefined || instructions.trim().length === 0) {
|
|
685
|
+
if (run.abortProvenance !== undefined) {
|
|
686
|
+
makeRunTurnIntentEligible(ctx, run, state.status, "No result presentation was available");
|
|
687
|
+
}
|
|
520
688
|
return;
|
|
521
689
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
690
|
+
turnCoordinator.sendNatural({
|
|
691
|
+
runId: run.runId,
|
|
692
|
+
targetSessionId: originSessionId(ctx, run.runId),
|
|
693
|
+
resolution: "presentation",
|
|
694
|
+
send: (turnIntentId) => {
|
|
695
|
+
presentationPending = run.generation;
|
|
696
|
+
suppressWorkflowAssistantTail = false;
|
|
697
|
+
pi.sendMessage({
|
|
698
|
+
customType: PRESENTATION_MESSAGE_TYPE,
|
|
699
|
+
content: buildPresentationMessage(instructions, state),
|
|
700
|
+
display: false,
|
|
701
|
+
details: {
|
|
702
|
+
schema: PRESENTATION_MESSAGE_SCHEMA,
|
|
703
|
+
...(turnIntentId === undefined ? {} : { turnIntentId }),
|
|
704
|
+
},
|
|
705
|
+
}, {
|
|
706
|
+
deliverAs: turnIntentId === undefined ? "steer" : "followUp",
|
|
707
|
+
triggerTurn: true,
|
|
708
|
+
});
|
|
709
|
+
},
|
|
710
|
+
}, ctx.isIdle() && systemTurnAbort === null);
|
|
529
711
|
}
|
|
530
712
|
catch (error) {
|
|
531
713
|
if (presentationPending === run.generation) {
|
|
@@ -540,6 +722,9 @@ export default function piWorkflows(pi) {
|
|
|
540
722
|
? `timed out after ${PRESENTATION_TIMEOUT_MS}ms`
|
|
541
723
|
: errorMessage(error);
|
|
542
724
|
notify(ctx, `Could not present workflow result: ${message}`, "warning");
|
|
725
|
+
if (run.abortProvenance !== undefined) {
|
|
726
|
+
makeRunTurnIntentEligible(ctx, run, state.status, message);
|
|
727
|
+
}
|
|
543
728
|
}
|
|
544
729
|
finally {
|
|
545
730
|
clearTimeout(timer);
|
|
@@ -590,6 +775,19 @@ export default function piWorkflows(pi) {
|
|
|
590
775
|
clearWidget(ctx);
|
|
591
776
|
return;
|
|
592
777
|
}
|
|
778
|
+
if (run.abortProvenance?.cause === "claimLost") {
|
|
779
|
+
await run.recorder?.stop().catch(() => undefined);
|
|
780
|
+
releaseClaim(run, "lost");
|
|
781
|
+
recordRunEvent({
|
|
782
|
+
runId: run.runId,
|
|
783
|
+
workflowRef: run.workflowName,
|
|
784
|
+
type: "claim_lost",
|
|
785
|
+
});
|
|
786
|
+
stopWidgetTicker();
|
|
787
|
+
clearWidget(ctx);
|
|
788
|
+
notify(ctx, `Workflow ${run.workflowName} continues under another runner (run ${run.runId}).`);
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
593
791
|
releaseClaim(run, "done");
|
|
594
792
|
recordRunEvent({
|
|
595
793
|
runId: run.runId,
|
|
@@ -638,6 +836,14 @@ export default function piWorkflows(pi) {
|
|
|
638
836
|
catch (error) {
|
|
639
837
|
notify(ctx, `Could not record child workflow completion: ${errorMessage(error)}`, "warning");
|
|
640
838
|
}
|
|
839
|
+
if (state.status === "failed" || state.status === "timed_out" || state.status === "cancelled") {
|
|
840
|
+
makeRunTurnIntentEligible(ctx, run, state.status, state.error);
|
|
841
|
+
}
|
|
842
|
+
else if (run.abortProvenance !== undefined &&
|
|
843
|
+
run.presentationPrompt === undefined &&
|
|
844
|
+
(state.status === "completed" || state.status === "waiting")) {
|
|
845
|
+
makeRunTurnIntentEligible(ctx, run, state.status, state.error);
|
|
846
|
+
}
|
|
641
847
|
void presentRun(ctx, run, state);
|
|
642
848
|
if (pendingDecision !== null && state.status === "waiting") {
|
|
643
849
|
const channels = audienceChannels(decisionChannelConfig, pendingDecision.audience);
|
|
@@ -694,7 +900,7 @@ export default function piWorkflows(pi) {
|
|
|
694
900
|
}
|
|
695
901
|
if (parent.state.workflowSource !== undefined &&
|
|
696
902
|
!isDeepStrictEqual(parent.state.workflowSource, workflowSource)) {
|
|
697
|
-
throw
|
|
903
|
+
throw continuationSourceChangedError(options.parentRunId, parent.state.workflowSource, workflowSource);
|
|
698
904
|
}
|
|
699
905
|
}
|
|
700
906
|
// Interactive runs are queued and claimed atomically, so this session
|
|
@@ -744,21 +950,29 @@ export default function piWorkflows(pi) {
|
|
|
744
950
|
}
|
|
745
951
|
const executor = new ConversationStepExecutor({
|
|
746
952
|
sendPrompt: ({ prompt, contract, presentation, kind, streaming }) => {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
953
|
+
turnCoordinator.sendNatural({
|
|
954
|
+
runId,
|
|
955
|
+
targetSessionId: originSessionId(ctx, runId),
|
|
956
|
+
resolution: "workflowPrompt",
|
|
957
|
+
send: (turnIntentId) => {
|
|
958
|
+
const details = {
|
|
959
|
+
schema: WORKFLOW_AGENT_STEP_MESSAGE_SCHEMA,
|
|
960
|
+
kind,
|
|
961
|
+
contract,
|
|
962
|
+
...(presentation !== undefined ? { presentation } : {}),
|
|
963
|
+
...(turnIntentId === undefined ? {} : { turnIntentId }),
|
|
964
|
+
};
|
|
965
|
+
pi.sendMessage({
|
|
966
|
+
customType: WORKFLOW_AGENT_STEP_MESSAGE_TYPE,
|
|
967
|
+
content: prompt,
|
|
968
|
+
display: true,
|
|
969
|
+
details,
|
|
970
|
+
}, {
|
|
971
|
+
triggerTurn: true,
|
|
972
|
+
deliverAs: streaming ? "steer" : "followUp",
|
|
973
|
+
});
|
|
974
|
+
},
|
|
975
|
+
}, ctx.isIdle() && systemTurnAbort === null);
|
|
762
976
|
},
|
|
763
977
|
onAbort: (contract, reason) => {
|
|
764
978
|
lastExpiredAttempt = {
|
|
@@ -766,7 +980,15 @@ export default function piWorkflows(pi) {
|
|
|
766
980
|
reason: reason instanceof TimeoutError ? "timed out" : `ended: ${errorMessage(reason)}`,
|
|
767
981
|
};
|
|
768
982
|
if (!executor.held && !ctx.isIdle()) {
|
|
769
|
-
|
|
983
|
+
const cause = reason instanceof TimeoutError ? "timedOut" : run.pendingAbortCause;
|
|
984
|
+
const descriptor = cause === undefined
|
|
985
|
+
? undefined
|
|
986
|
+
: ensureAbortTurnIntent(ctx, run, cause, contract, reason);
|
|
987
|
+
run.pendingAbortCause = undefined;
|
|
988
|
+
systemTurnAbort = {
|
|
989
|
+
contract,
|
|
990
|
+
...(descriptor === undefined ? {} : { intentId: descriptor.intentId }),
|
|
991
|
+
};
|
|
770
992
|
ctx.abort();
|
|
771
993
|
}
|
|
772
994
|
},
|
|
@@ -859,6 +1081,7 @@ export default function piWorkflows(pi) {
|
|
|
859
1081
|
run.renewTimer = setInterval(() => {
|
|
860
1082
|
try {
|
|
861
1083
|
if (!store.renewWorkflowRunClaim({ runId, claimToken: token, leaseMs: RUN_CLAIM_LEASE_MS })) {
|
|
1084
|
+
run.pendingAbortCause = "claimLost";
|
|
862
1085
|
run.engine.cancel();
|
|
863
1086
|
}
|
|
864
1087
|
}
|
|
@@ -952,6 +1175,11 @@ export default function piWorkflows(pi) {
|
|
|
952
1175
|
}
|
|
953
1176
|
if (terminalStatus !== undefined) {
|
|
954
1177
|
recordRunEvent({ runId, workflowRef: workflow.name, type: terminalStatus, payload: {} });
|
|
1178
|
+
if (terminalStatus === "failed" ||
|
|
1179
|
+
terminalStatus === "timed_out" ||
|
|
1180
|
+
terminalStatus === "cancelled") {
|
|
1181
|
+
makeRunTurnIntentEligible(ctx, run, terminalStatus, message);
|
|
1182
|
+
}
|
|
955
1183
|
notify(ctx, `Workflow ${workflow.name} ${terminalStatus} (run ${runId}).`);
|
|
956
1184
|
return;
|
|
957
1185
|
}
|
|
@@ -961,6 +1189,7 @@ export default function piWorkflows(pi) {
|
|
|
961
1189
|
type: "failed",
|
|
962
1190
|
payload: { error: message },
|
|
963
1191
|
});
|
|
1192
|
+
makeRunTurnIntentEligible(ctx, run, "failed", message);
|
|
964
1193
|
try {
|
|
965
1194
|
run.onFinish?.({ state: "failed", runId, error: message });
|
|
966
1195
|
}
|
|
@@ -1155,10 +1384,12 @@ export default function piWorkflows(pi) {
|
|
|
1155
1384
|
}
|
|
1156
1385
|
return null;
|
|
1157
1386
|
};
|
|
1158
|
-
const cancelWorkflowControl = async (ctx, requestedRunId) => {
|
|
1387
|
+
const cancelWorkflowControl = async (ctx, origin, requestedRunId) => {
|
|
1159
1388
|
if (activeRun && (requestedRunId === undefined || requestedRunId === activeRun.runId)) {
|
|
1160
1389
|
const workflowName = activeRun.workflowName;
|
|
1161
1390
|
const runId = activeRun.runId;
|
|
1391
|
+
activeRun.pendingAbortCause = origin === "agent" ? "agentCancelled" : undefined;
|
|
1392
|
+
activeRun.suppressTurnIntent = origin === "user";
|
|
1162
1393
|
activeRun.engine.cancel();
|
|
1163
1394
|
return {
|
|
1164
1395
|
message: `Cancelling workflow ${workflowName}…`,
|
|
@@ -1250,6 +1481,7 @@ export default function piWorkflows(pi) {
|
|
|
1250
1481
|
},
|
|
1251
1482
|
};
|
|
1252
1483
|
}
|
|
1484
|
+
activeRun.suppressTurnIntent = true;
|
|
1253
1485
|
activeRun.engine.pause();
|
|
1254
1486
|
renderWidget(ctx);
|
|
1255
1487
|
return {
|
|
@@ -1282,6 +1514,7 @@ export default function piWorkflows(pi) {
|
|
|
1282
1514
|
level: "warning",
|
|
1283
1515
|
};
|
|
1284
1516
|
}
|
|
1517
|
+
activeRun.suppressTurnIntent = false;
|
|
1285
1518
|
activeRun.engine.resume();
|
|
1286
1519
|
activeRun.executor.release();
|
|
1287
1520
|
renderWidget(ctx);
|
|
@@ -1412,7 +1645,7 @@ export default function piWorkflows(pi) {
|
|
|
1412
1645
|
};
|
|
1413
1646
|
const store = new HumanDecisionStore(new WorkflowRunStore().outputRoot);
|
|
1414
1647
|
const acceptance = await store.accept(request, submission);
|
|
1415
|
-
if (acceptance.status === "conflict") {
|
|
1648
|
+
if (acceptance.status === "conflict" || acceptance.decision.provenance !== "human") {
|
|
1416
1649
|
throw new Error("That human decision was already answered differently.");
|
|
1417
1650
|
}
|
|
1418
1651
|
accepted = acceptance.decision;
|
|
@@ -1442,6 +1675,7 @@ export default function piWorkflows(pi) {
|
|
|
1442
1675
|
schema: "pi-workflows.human-decision-continuation.v1",
|
|
1443
1676
|
decisionId: request.decisionId,
|
|
1444
1677
|
requestDigest: request.requestDigest,
|
|
1678
|
+
provenance: accepted.provenance,
|
|
1445
1679
|
parentRunId: waiting.parentRunId,
|
|
1446
1680
|
runId: continuationRunId,
|
|
1447
1681
|
createdAt: accepted.acceptedAt,
|
|
@@ -1554,60 +1788,76 @@ export default function piWorkflows(pi) {
|
|
|
1554
1788
|
const ownedBySession = queueRecord !== undefined &&
|
|
1555
1789
|
(queueRecord.originSessionId === null ||
|
|
1556
1790
|
queueRecord.originSessionId === ctx.sessionManager.getSessionId());
|
|
1557
|
-
|
|
1558
|
-
if (
|
|
1791
|
+
let resolved = await store.readResolved(request.decisionId);
|
|
1792
|
+
if (resolved === null) {
|
|
1559
1793
|
let cancellation = await store.readCancellation(request.decisionId);
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1794
|
+
const expired = request.expiresAt !== undefined && Date.parse(request.expiresAt) <= Date.now();
|
|
1795
|
+
if (cancellation === null && expired) {
|
|
1796
|
+
if (request.defaultResponse === undefined) {
|
|
1797
|
+
await store.cancel(request, "expired");
|
|
1798
|
+
cancellation = await store.readCancellation(request.decisionId);
|
|
1799
|
+
}
|
|
1800
|
+
else {
|
|
1801
|
+
try {
|
|
1802
|
+
resolved = (await store.resolveTimeout(request)).decision;
|
|
1803
|
+
}
|
|
1804
|
+
catch {
|
|
1805
|
+
cancellation = await store.readCancellation(request.decisionId);
|
|
1806
|
+
resolved = await store.readResolved(request.decisionId);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1565
1809
|
}
|
|
1566
1810
|
if (cancellation !== null) {
|
|
1567
1811
|
await settleHumanDecisionChannels(cancellation);
|
|
1568
1812
|
continue;
|
|
1569
1813
|
}
|
|
1570
|
-
if (
|
|
1814
|
+
if (resolved === null) {
|
|
1815
|
+
if (!deliverPending || !ownedBySession)
|
|
1816
|
+
continue;
|
|
1817
|
+
const channels = audienceChannels(decisionChannelConfig, request.audience);
|
|
1818
|
+
for (const channelId of channels) {
|
|
1819
|
+
const channel = telegramDecisionChannels.get(channelId);
|
|
1820
|
+
if (channel !== undefined)
|
|
1821
|
+
await channel.deliver(humanDecisionChannelRequest(request));
|
|
1822
|
+
}
|
|
1823
|
+
if (ctx.mode === "tui" && channels.includes("pi") && lastWaitingRunId === null) {
|
|
1824
|
+
lastWaitingRunId = request.runId;
|
|
1825
|
+
queueMicrotask(() => void promptHumanDecision(ctx, request));
|
|
1826
|
+
}
|
|
1571
1827
|
continue;
|
|
1572
|
-
const channels = audienceChannels(decisionChannelConfig, request.audience);
|
|
1573
|
-
for (const channelId of channels) {
|
|
1574
|
-
const channel = telegramDecisionChannels.get(channelId);
|
|
1575
|
-
if (channel !== undefined)
|
|
1576
|
-
await channel.deliver(humanDecisionChannelRequest(request));
|
|
1577
|
-
}
|
|
1578
|
-
if (ctx.mode === "tui" && channels.includes("pi") && lastWaitingRunId === null) {
|
|
1579
|
-
lastWaitingRunId = request.runId;
|
|
1580
|
-
queueMicrotask(() => void promptHumanDecision(ctx, request));
|
|
1581
1828
|
}
|
|
1582
|
-
continue;
|
|
1583
1829
|
}
|
|
1584
1830
|
if (!ownedBySession)
|
|
1585
1831
|
continue;
|
|
1832
|
+
const currentParent = await readRunBundle(runStore.runDirFor(request.runId));
|
|
1833
|
+
if (currentParent === null || currentParent.state.status !== "waiting")
|
|
1834
|
+
continue;
|
|
1586
1835
|
const runId = `continuation-${request.decisionId.slice("decision-".length)}`;
|
|
1587
1836
|
const continuation = (await store.readContinuation(request.decisionId)) ?? {
|
|
1588
1837
|
schema: "pi-workflows.human-decision-continuation.v1",
|
|
1589
1838
|
decisionId: request.decisionId,
|
|
1590
1839
|
requestDigest: request.requestDigest,
|
|
1840
|
+
provenance: resolved.provenance,
|
|
1591
1841
|
parentRunId: request.runId,
|
|
1592
1842
|
runId,
|
|
1593
|
-
createdAt:
|
|
1843
|
+
createdAt: resolved.acceptedAt,
|
|
1594
1844
|
};
|
|
1595
1845
|
await store.recordContinuation(request.decisionId, continuation);
|
|
1596
1846
|
const existing = await readRunBundle(runStore.runDirFor(continuation.runId));
|
|
1597
1847
|
if (existing === null && activeRun === null) {
|
|
1598
|
-
if (
|
|
1848
|
+
if (currentParent.state.workflowSource === undefined)
|
|
1599
1849
|
continue;
|
|
1600
|
-
const workflowRef =
|
|
1601
|
-
? `builtin:${
|
|
1602
|
-
:
|
|
1603
|
-
await startRun(ctx, workflowRef,
|
|
1850
|
+
const workflowRef = currentParent.state.workflowSource.kind === "builtin"
|
|
1851
|
+
? `builtin:${currentParent.state.workflowSource.id}`
|
|
1852
|
+
: currentParent.state.workflowSource.path;
|
|
1853
|
+
await startRun(ctx, workflowRef, currentParent.state.input, {
|
|
1604
1854
|
parentRunId: request.runId,
|
|
1605
|
-
humanDecision:
|
|
1855
|
+
humanDecision: resolved,
|
|
1606
1856
|
runId: continuation.runId,
|
|
1607
1857
|
quiet: true,
|
|
1608
1858
|
});
|
|
1609
1859
|
}
|
|
1610
|
-
await settleHumanDecisionChannels(
|
|
1860
|
+
await settleHumanDecisionChannels(resolved);
|
|
1611
1861
|
if (activeRun !== null)
|
|
1612
1862
|
break;
|
|
1613
1863
|
}
|
|
@@ -1669,7 +1919,7 @@ export default function piWorkflows(pi) {
|
|
|
1669
1919
|
}
|
|
1670
1920
|
if (parent.state.workflowSource !== undefined &&
|
|
1671
1921
|
!isDeepStrictEqual(parent.state.workflowSource, workflowSource)) {
|
|
1672
|
-
throw
|
|
1922
|
+
throw continuationSourceChangedError(options.parentRunId, parent.state.workflowSource, workflowSource);
|
|
1673
1923
|
}
|
|
1674
1924
|
}
|
|
1675
1925
|
const snapshot = createDefinitionSnapshot(workflow);
|
|
@@ -1758,20 +2008,28 @@ export default function piWorkflows(pi) {
|
|
|
1758
2008
|
payload: { errorCode: safe.code, error: safe.message },
|
|
1759
2009
|
});
|
|
1760
2010
|
const content = `Workflow ${claimed.workflowName} failed to start (run ${claimed.runId}): ${safe.message}. Inspect the error and call workflow start again only after you correct the cause.`;
|
|
1761
|
-
|
|
1762
|
-
|
|
2011
|
+
const cause = "launchFailed";
|
|
2012
|
+
const descriptor = createDeferredTurnDescriptor({
|
|
2013
|
+
runId: claimed.runId,
|
|
2014
|
+
workflowName: claimed.workflowName,
|
|
2015
|
+
targetSessionId: claimed.originSessionId ?? ctx.sessionManager.getSessionId(),
|
|
2016
|
+
cause,
|
|
2017
|
+
sourceEventId: deferredTurnSourceEventId({
|
|
1763
2018
|
runId: claimed.runId,
|
|
2019
|
+
cause,
|
|
1764
2020
|
nodeId: "$launch",
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
2021
|
+
source: "queued-launch",
|
|
2022
|
+
}),
|
|
2023
|
+
observedState: "failedToStart",
|
|
2024
|
+
nodeId: "$launch",
|
|
2025
|
+
reason: safe.message,
|
|
2026
|
+
});
|
|
2027
|
+
try {
|
|
2028
|
+
storeTurnDescriptor(ctx, descriptor, true);
|
|
2029
|
+
syncArmed = true;
|
|
1772
2030
|
}
|
|
1773
|
-
catch {
|
|
1774
|
-
|
|
2031
|
+
catch (intentError) {
|
|
2032
|
+
notify(ctx, `Workflow ${claimed.workflowName} failed to start, but Pi Workflows could not preserve its successor turn: ${errorMessage(intentError)}`, "warning");
|
|
1775
2033
|
}
|
|
1776
2034
|
notify(ctx, content, "error");
|
|
1777
2035
|
runSyncPass(ctx);
|
|
@@ -1830,7 +2088,7 @@ export default function piWorkflows(pi) {
|
|
|
1830
2088
|
return;
|
|
1831
2089
|
}
|
|
1832
2090
|
if (parsed.kind === "cancel") {
|
|
1833
|
-
const result = await cancelWorkflowControl(ctx);
|
|
2091
|
+
const result = await cancelWorkflowControl(ctx, "user");
|
|
1834
2092
|
notify(ctx, result.message, result.level);
|
|
1835
2093
|
return;
|
|
1836
2094
|
}
|
|
@@ -1990,6 +2248,7 @@ export default function piWorkflows(pi) {
|
|
|
1990
2248
|
case "stop":
|
|
1991
2249
|
if (activeRun?.childKey !== undefined) {
|
|
1992
2250
|
activeRun.interruptionRequested = true;
|
|
2251
|
+
activeRun.pendingAbortCause = "controllerInterrupted";
|
|
1993
2252
|
activeRun.engine.cancel();
|
|
1994
2253
|
}
|
|
1995
2254
|
await host.stop();
|
|
@@ -2006,7 +2265,7 @@ export default function piWorkflows(pi) {
|
|
|
2006
2265
|
name: "workflow",
|
|
2007
2266
|
label: "Workflow",
|
|
2008
2267
|
description: [
|
|
2009
|
-
"List, start, inspect, pause, resume, cancel, answer, update, or complete
|
|
2268
|
+
"List, start, inspect, pause, resume, cancel, answer, update, or complete pi-workflows runs.",
|
|
2010
2269
|
"When the user asks to monitor, watch, poll, or check something repeatedly, start the built-in monitor workflow with input keys task, everyMinutes, stopWhen, and optional maxChecks.",
|
|
2011
2270
|
"Use update or submit only when a workflow step contract asks for it, and pass the exact step and attempt ids.",
|
|
2012
2271
|
"Do not start repeated work without the user's request, and keep monitoring observation-only unless the user authorizes mutations.",
|
|
@@ -2032,7 +2291,7 @@ export default function piWorkflows(pi) {
|
|
|
2032
2291
|
control = resumeWorkflowControl(ctx);
|
|
2033
2292
|
break;
|
|
2034
2293
|
case "cancel":
|
|
2035
|
-
control = await cancelWorkflowControl(ctx, params.runId);
|
|
2294
|
+
control = await cancelWorkflowControl(ctx, "agent", params.runId);
|
|
2036
2295
|
break;
|
|
2037
2296
|
case "answer": {
|
|
2038
2297
|
const waiting = await resolveWaitingWorkflow(ctx, params.runId);
|
|
@@ -2190,6 +2449,7 @@ export default function piWorkflows(pi) {
|
|
|
2190
2449
|
if (!aborted || runHeld()) {
|
|
2191
2450
|
return;
|
|
2192
2451
|
}
|
|
2452
|
+
run.suppressTurnIntent = true;
|
|
2193
2453
|
run.engine.pause();
|
|
2194
2454
|
run.executor.hold();
|
|
2195
2455
|
renderWidget(ctx);
|
|
@@ -2228,6 +2488,7 @@ export default function piWorkflows(pi) {
|
|
|
2228
2488
|
activeRun?.recorder?.handleToolEnd(event);
|
|
2229
2489
|
});
|
|
2230
2490
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
2491
|
+
systemTurnAbort = null;
|
|
2231
2492
|
if (activeRun === null) {
|
|
2232
2493
|
try {
|
|
2233
2494
|
const prepared = ensureRunQueueStore(ctx.cwd).findSessionReservation(ctx.sessionManager.getSessionId());
|
|
@@ -2241,23 +2502,30 @@ export default function piWorkflows(pi) {
|
|
|
2241
2502
|
return;
|
|
2242
2503
|
}
|
|
2243
2504
|
}
|
|
2505
|
+
const flushedNatural = turnCoordinator.flushNatural(ctx.isIdle() && !sessionClosed && !runHeld());
|
|
2244
2506
|
const run = activeRun;
|
|
2245
2507
|
if (!run) {
|
|
2246
2508
|
presentationPending = null;
|
|
2509
|
+
runSyncPass(ctx);
|
|
2247
2510
|
return;
|
|
2248
2511
|
}
|
|
2249
2512
|
await run.recorder?.synchronize(ctx).catch(() => undefined);
|
|
2250
2513
|
run.recorder?.settleAttempt();
|
|
2251
2514
|
run.executor.setStreaming(false);
|
|
2252
|
-
|
|
2515
|
+
if (flushedNatural === 0)
|
|
2516
|
+
run.executor.handleAgentSettled();
|
|
2517
|
+
runSyncPass(ctx);
|
|
2253
2518
|
});
|
|
2254
2519
|
pi.on("session_shutdown", async () => {
|
|
2255
2520
|
sessionClosed = true;
|
|
2256
2521
|
herdrProbeGeneration += 1;
|
|
2257
2522
|
systemTurnAbort = null;
|
|
2258
2523
|
suppressWorkflowAssistantTail = false;
|
|
2524
|
+
turnCoordinator.clearDeferred();
|
|
2259
2525
|
supersedePresentation();
|
|
2260
2526
|
const run = activeRun;
|
|
2527
|
+
if (run !== null)
|
|
2528
|
+
run.suppressTurnIntent = true;
|
|
2261
2529
|
if (run !== null && run.claimToken !== undefined) {
|
|
2262
2530
|
// Queued interactive runs park: no terminal event, no recorded partial
|
|
2263
2531
|
// attempt, and the claim releases so another runner can resume.
|