@osolmaz/pi-workflows 0.12.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -28
- package/dist/builtins/autodoc.workflow.d.ts +4 -4
- package/dist/builtins/autoimplement.workflow.d.ts +369 -73
- package/dist/builtins/autoplan.workflow.d.ts +126 -13
- package/dist/builtins/autoplan.workflow.js +200 -29
- package/dist/builtins/autoplan.workflow.js.map +1 -1
- package/dist/builtins/catalog.js +6 -4
- package/dist/builtins/catalog.js.map +1 -1
- package/dist/builtins/index.d.ts +2 -1
- package/dist/builtins/index.js +1 -0
- package/dist/builtins/index.js.map +1 -1
- package/dist/builtins/monitor.workflow.d.ts +56 -21
- package/dist/builtins/monitor.workflow.js +497 -216
- package/dist/builtins/monitor.workflow.js.map +1 -1
- package/dist/builtins/plain-summary.workflow.d.ts +32 -0
- package/dist/builtins/plain-summary.workflow.js +134 -0
- package/dist/builtins/plain-summary.workflow.js.map +1 -0
- package/dist/builtins/plan-change.workflow.d.ts +170 -22
- package/dist/builtins/sanity-check.workflow.d.ts +45 -3
- package/dist/builtins/sanity-check.workflow.js +45 -7
- package/dist/builtins/sanity-check.workflow.js.map +1 -1
- package/dist/controllers/effects.d.ts +3 -2
- package/dist/controllers/effects.js +8 -1
- package/dist/controllers/effects.js.map +1 -1
- package/dist/controllers/index.d.ts +1 -1
- package/dist/controllers/index.js +1 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/manager.d.ts +1 -0
- package/dist/controllers/manager.js +41 -17
- package/dist/controllers/manager.js.map +1 -1
- package/dist/controllers/sqlite.d.ts +107 -85
- package/dist/controllers/sqlite.js +1611 -1486
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/controllers/store.d.ts +9 -8
- package/dist/controllers/store.js +1 -31
- package/dist/controllers/store.js.map +1 -1
- package/dist/controllers/types.d.ts +3 -0
- package/dist/controllers/workflow-engine-scheduler.d.ts +1 -1
- package/dist/controllers/workflow-engine-scheduler.js +3 -5
- package/dist/controllers/workflow-engine-scheduler.js.map +1 -1
- package/dist/controllers/workflows.d.ts +2 -2
- package/dist/controllers/workflows.js +11 -10
- package/dist/controllers/workflows.js.map +1 -1
- package/dist/extension/controller-host.js +2 -2
- package/dist/extension/controller-host.js.map +1 -1
- package/dist/extension/decision-channels.d.ts +2 -0
- package/dist/extension/decision-channels.js +311 -118
- package/dist/extension/decision-channels.js.map +1 -1
- package/dist/extension/executor.d.ts +9 -2
- package/dist/extension/executor.js +89 -2
- package/dist/extension/executor.js.map +1 -1
- package/dist/extension/herdr-viewer.d.ts +0 -1
- package/dist/extension/herdr-viewer.js +0 -4
- package/dist/extension/herdr-viewer.js.map +1 -1
- package/dist/extension/index.js +156 -103
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +3 -10
- package/dist/extension/recorder.js +13 -22
- package/dist/extension/recorder.js.map +1 -1
- package/dist/extension/step-message.d.ts +7 -2
- package/dist/extension/step-message.js +94 -2
- package/dist/extension/step-message.js.map +1 -1
- package/dist/extension/widget.js +6 -0
- package/dist/extension/widget.js.map +1 -1
- package/dist/host/rpc-executor.d.ts +3 -0
- package/dist/host/rpc-executor.js +2 -0
- package/dist/host/rpc-executor.js.map +1 -1
- package/dist/host/runner.d.ts +3 -6
- package/dist/host/runner.js +83 -32
- package/dist/host/runner.js.map +1 -1
- package/dist/render/graph-render.js +14 -8
- package/dist/render/graph-render.js.map +1 -1
- package/dist/state/database.d.ts +35 -0
- package/dist/state/database.js +287 -0
- package/dist/state/database.js.map +1 -0
- package/dist/state/index.d.ts +4 -0
- package/dist/state/index.js +5 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/json.d.ts +6 -0
- package/dist/state/json.js +38 -0
- package/dist/state/json.js.map +1 -0
- package/dist/state/mutation.d.ts +57 -0
- package/dist/state/mutation.js +255 -0
- package/dist/state/mutation.js.map +1 -0
- package/dist/state/schema.d.ts +6 -0
- package/dist/state/schema.js +473 -0
- package/dist/state/schema.js.map +1 -0
- package/dist/viewer/cli.d.ts +2 -2
- package/dist/viewer/cli.js +142 -122
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/render.d.ts +4 -4
- package/dist/viewer/render.js +2 -17
- package/dist/viewer/render.js.map +1 -1
- package/dist/viewer/session-reducer.d.ts +1 -1
- package/dist/viewer/session-reducer.js +1 -1
- package/dist/viewer/tui.d.ts +3 -3
- package/dist/viewer/tui.js +15 -13
- package/dist/viewer/tui.js.map +1 -1
- package/dist/viewer/watch.d.ts +2 -5
- package/dist/viewer/watch.js +13 -13
- package/dist/viewer/watch.js.map +1 -1
- package/dist/workflows/composition.js +12 -2
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/definition.d.ts +6 -2
- package/dist/workflows/definition.js +17 -0
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.d.ts +6 -6
- package/dist/workflows/engine.js +183 -103
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/errors.d.ts +3 -3
- package/dist/workflows/errors.js +3 -3
- package/dist/workflows/human-decision.d.ts +29 -4
- package/dist/workflows/human-decision.js +547 -283
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +4 -5
- package/dist/workflows/index.js +3 -4
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/schema.js +17 -1
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/store.d.ts +83 -103
- package/dist/workflows/store.js +902 -951
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +55 -81
- package/docs/CONTROLLERS.md +10 -8
- package/docs/DEFERRED_TURNS.md +2 -2
- package/docs/DESIGN_PHILOSOPHY.md +4 -2
- package/docs/HUMAN_DECISIONS.md +13 -14
- package/docs/MONITOR.md +179 -231
- package/docs/SQLITE_STATE.md +219 -0
- package/docs/WORKFLOW_COMPOSITION.md +19 -19
- package/docs/WORKFLOW_STEP_MESSAGES.md +20 -18
- package/docs/WORKFLOW_UPDATES.md +10 -9
- package/docs/development.md +9 -9
- package/docs/live-replay-protocol.md +28 -37
- package/docs/plans/2026-08-16-workflow-updates-plan.md +1 -1
- package/docs/plans/2026-08-19-human-decision-gates-plan.md +1 -1
- package/docs/plans/2026-08-21-autoimplement-timeout-fallback-plan.md +1 -1
- package/docs/plans/2026-08-21-plan-change-approval-policy-plan.md +1 -1
- package/docs/plans/2026-08-21-sanity-check-plan.md +30 -16
- package/docs/plans/2026-08-22-goal-finishing-monitor-plan.md +200 -0
- package/docs/plans/2026-08-23-assistant-agent-completion-plan.md +701 -0
- package/docs/plans/2026-08-23-sqlite-state-plan.md +970 -0
- package/docs/plans/replayable-run-bundles-implementation-plan.md +2 -2
- package/docs/plans/session-event-replay-implementation-plan.md +1 -1
- package/docs/plans/tui-viewer-implementation-plan.md +1 -1
- package/docs/session-event-journal.md +63 -434
- package/docs/tui-viewer.md +13 -15
- package/docs/workflows.md +104 -50
- package/examples/workflows/plain-summary.workflow.ts +1 -0
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/plugins/herdr/viewer.mjs +1 -11
- package/skills/autoplan/SKILL.md +5 -4
- package/skills/monitor/SKILL.md +91 -163
- package/skills/pi-workflows/SKILL.md +7 -7
- package/skills/sanity-check/SKILL.md +1 -1
- package/src/builtins/autoplan.workflow.ts +290 -40
- package/src/builtins/catalog.ts +6 -4
- package/src/builtins/index.ts +7 -1
- package/src/builtins/monitor.workflow.ts +663 -249
- package/src/builtins/plain-summary.workflow.ts +185 -0
- package/src/builtins/sanity-check.workflow.ts +62 -7
- package/src/controllers/effects.ts +7 -0
- package/src/controllers/index.ts +0 -6
- package/src/controllers/manager.ts +72 -32
- package/src/controllers/sqlite.ts +2420 -1948
- package/src/controllers/store.ts +17 -38
- package/src/controllers/types.ts +3 -0
- package/src/controllers/workflow-engine-scheduler.ts +4 -8
- package/src/controllers/workflows.ts +45 -21
- package/src/extension/controller-host.ts +1 -2
- package/src/extension/decision-channels.ts +427 -144
- package/src/extension/executor.ts +105 -2
- package/src/extension/herdr-viewer.ts +0 -5
- package/src/extension/index.ts +180 -118
- package/src/extension/recorder.ts +13 -22
- package/src/extension/step-message.ts +106 -4
- package/src/extension/widget.ts +8 -0
- package/src/host/rpc-executor.ts +4 -0
- package/src/host/runner.ts +93 -41
- package/src/render/graph-render.ts +11 -4
- package/src/state/database.ts +375 -0
- package/src/state/index.ts +29 -0
- package/src/state/json.ts +42 -0
- package/src/state/mutation.ts +435 -0
- package/src/state/schema.ts +475 -0
- package/src/viewer/cli.ts +151 -144
- package/src/viewer/render.ts +12 -24
- package/src/viewer/session-reducer.ts +1 -1
- package/src/viewer/tui.ts +18 -17
- package/src/viewer/watch.ts +13 -17
- package/src/workflows/composition.ts +12 -3
- package/src/workflows/definition.ts +36 -3
- package/src/workflows/engine.ts +212 -116
- package/src/workflows/errors.ts +3 -3
- package/src/workflows/human-decision.ts +795 -340
- package/src/workflows/index.ts +15 -19
- package/src/workflows/schema.ts +19 -1
- package/src/workflows/store.ts +1295 -1163
- package/src/workflows/types.ts +61 -81
- package/dist/workflows/artifacts.d.ts +0 -40
- package/dist/workflows/artifacts.js +0 -155
- package/dist/workflows/artifacts.js.map +0 -1
- package/dist/workflows/migrate-sources.d.ts +0 -42
- package/dist/workflows/migrate-sources.js +0 -133
- package/dist/workflows/migrate-sources.js.map +0 -1
- package/docs/run-bundles.md +0 -481
- package/src/workflows/artifacts.ts +0 -188
- package/src/workflows/migrate-sources.ts +0 -178
package/dist/extension/index.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import path from "node:path";
|
|
3
2
|
import { isDeepStrictEqual } from "node:util";
|
|
4
3
|
import { builtinWorkflowCatalog } from "../builtins/catalog.js";
|
|
5
|
-
import {
|
|
4
|
+
import { SqliteControllerStore } from "../controllers/index.js";
|
|
6
5
|
import { compositionMetadata } from "../workflows/composition.js";
|
|
7
6
|
import { humanDecisionChannelRequest } from "../workflows/decision-presentation.js";
|
|
8
7
|
import { WorkflowEngine } from "../workflows/engine.js";
|
|
9
8
|
import { BuiltinWorkflowRevisionChangedError, ClaimLostError, errorMessage, isClaimLostError, TimeoutError, } from "../workflows/errors.js";
|
|
10
9
|
import { HumanDecisionStore, createHumanDecisionAttemptId, validateHumanDecisionResponse, } from "../workflows/human-decision.js";
|
|
11
10
|
import { discoverWorkflows, resolveWorkflowRef } from "../workflows/loader.js";
|
|
12
|
-
import { migrateLegacyWorkflowSources } from "../workflows/migrate-sources.js";
|
|
13
11
|
import { appendProgressHistory, progressRecordsFromTrace } from "../workflows/progress.js";
|
|
14
|
-
import { createRunId,
|
|
12
|
+
import { createRunId, listWorkflowRuns, readWorkflowRun, WorkflowRunStore, createDefinitionSnapshot, } from "../workflows/store.js";
|
|
15
13
|
import { PiControllerHost, parseControllerArgs, } from "./controller-host.js";
|
|
16
14
|
import { PiDecisionChannel, TelegramDecisionChannel, audienceChannels, createTelegramChannels, loadDecisionChannelConfig, verifyTelegramTokenFile, writeDecisionChannelProfile, } from "./decision-channels.js";
|
|
17
15
|
import { DeferredTurnCoordinator, } from "./deferred-turn-coordinator.js";
|
|
@@ -19,7 +17,7 @@ import { buildDeferredTurnContent, createDeferredTurnDescriptor, DEFERRED_TURN_M
|
|
|
19
17
|
import { ConversationStepExecutor } from "./executor.js";
|
|
20
18
|
import { HerdrWorkflowViewer, parseViewerPlacement, PIW_SHORTCUT, PIW_SHORTCUT_HINT, VIEWER_PLACEMENTS, } from "./herdr-viewer.js";
|
|
21
19
|
import { SessionRecorder } from "./recorder.js";
|
|
22
|
-
import { registerWorkflowAgentStepMessageRenderer, WORKFLOW_AGENT_STEP_MESSAGE_SCHEMA, WORKFLOW_AGENT_STEP_MESSAGE_TYPE, } from "./step-message.js";
|
|
20
|
+
import { recoverAssistantStep, registerWorkflowAgentStepMessageRenderer, WORKFLOW_AGENT_STEP_MESSAGE_SCHEMA, WORKFLOW_AGENT_STEP_MESSAGE_TYPE, } from "./step-message.js";
|
|
23
21
|
import { buildWidgetView } from "./widget.js";
|
|
24
22
|
import { parseWorkflowToolInput, WorkflowToolParameters } from "./workflow-tool.js";
|
|
25
23
|
export { PiDecisionChannel, TelegramDecisionChannel, audienceChannels, createTelegramChannels, decisionConfigDir, loadDecisionChannelConfig, verifyTelegramTokenFile, writeDecisionChannelProfile, } from "./decision-channels.js";
|
|
@@ -101,7 +99,7 @@ function safeLaunchError(error) {
|
|
|
101
99
|
.replace(/(token|api[_-]?key|secret|password)(\s*[:=]\s*)\S+/giu, "$1$2[redacted]")
|
|
102
100
|
.replaceAll("\n", " ")
|
|
103
101
|
.trim();
|
|
104
|
-
const code = /not found|cannot find|unknown workflow/iu.test(raw)
|
|
102
|
+
const code = /not found|cannot find|unknown workflow|no such file or directory/iu.test(raw)
|
|
105
103
|
? "workflow_not_found"
|
|
106
104
|
: /source changed|source mismatch/iu.test(raw)
|
|
107
105
|
? "source_changed"
|
|
@@ -224,11 +222,21 @@ export default function piWorkflows(pi) {
|
|
|
224
222
|
...[...telegramDecisionChannels.values()].map(async (channel) => channel.settle(decision)),
|
|
225
223
|
]);
|
|
226
224
|
};
|
|
227
|
-
const migrationBlockedRuns = new Set();
|
|
228
225
|
const ensureRunQueueStore = (cwd) => {
|
|
229
|
-
runQueueStore ??= new SqliteControllerStore(
|
|
226
|
+
runQueueStore ??= new SqliteControllerStore(undefined, { projectPath: cwd });
|
|
230
227
|
return runQueueStore;
|
|
231
228
|
};
|
|
229
|
+
const workflowStore = (cwd, options = {}) => {
|
|
230
|
+
const queue = ensureRunQueueStore(cwd);
|
|
231
|
+
return new WorkflowRunStore(queue.filePath, { ...options, state: queue.state });
|
|
232
|
+
};
|
|
233
|
+
const humanDecisionStore = (cwd, authorityProvider) => {
|
|
234
|
+
const queue = ensureRunQueueStore(cwd);
|
|
235
|
+
return new HumanDecisionStore(queue.filePath, {
|
|
236
|
+
state: queue.state,
|
|
237
|
+
...(authorityProvider === undefined ? {} : { authorityProvider }),
|
|
238
|
+
});
|
|
239
|
+
};
|
|
232
240
|
// Session-addressed delivery: each session polls only its durable outbox.
|
|
233
241
|
// Run events remain an audit feed and never enter a conversation.
|
|
234
242
|
let syncArmed = false;
|
|
@@ -569,7 +577,6 @@ export default function piWorkflows(pi) {
|
|
|
569
577
|
workflowViewTarget = {
|
|
570
578
|
runId: state.runId,
|
|
571
579
|
workflowName: state.workflowName,
|
|
572
|
-
runDir: path.resolve(new WorkflowRunStore().runDirFor(state.runId)),
|
|
573
580
|
};
|
|
574
581
|
renderWidget(ctx);
|
|
575
582
|
};
|
|
@@ -746,14 +753,16 @@ export default function piWorkflows(pi) {
|
|
|
746
753
|
}
|
|
747
754
|
try {
|
|
748
755
|
if (outcome === "park") {
|
|
749
|
-
runQueueStore.parkWorkflowRun({ runId: run.runId, claimToken: run.claimToken })
|
|
756
|
+
if (runQueueStore.parkWorkflowRun({ runId: run.runId, claimToken: run.claimToken })) {
|
|
757
|
+
runQueueStore.settleRunEffect(run.runId, "run.park_queue");
|
|
758
|
+
}
|
|
750
759
|
}
|
|
751
|
-
else {
|
|
752
|
-
runQueueStore.
|
|
760
|
+
else if (runQueueStore.completeWorkflowRun({ runId: run.runId, claimToken: run.claimToken })) {
|
|
761
|
+
runQueueStore.settleRunEffect(run.runId, "run.settle_queue");
|
|
753
762
|
}
|
|
754
763
|
}
|
|
755
764
|
catch {
|
|
756
|
-
// Queue bookkeeping is best-effort next to the durable
|
|
765
|
+
// Queue bookkeeping is best-effort next to the durable run fact.
|
|
757
766
|
}
|
|
758
767
|
finally {
|
|
759
768
|
run.claimToken = undefined;
|
|
@@ -764,7 +773,7 @@ export default function piWorkflows(pi) {
|
|
|
764
773
|
activeRun = null;
|
|
765
774
|
}
|
|
766
775
|
if (result.state.status === "running") {
|
|
767
|
-
// The run was parked for another runner; its
|
|
776
|
+
// The run was parked for another runner; its SQLite state stays resumable
|
|
768
777
|
// and its queue row becomes claimable. The recorder drains first: its
|
|
769
778
|
// finalization writes share the fenced store, so the claim must stay
|
|
770
779
|
// valid until capture is complete. Nothing else to present.
|
|
@@ -788,7 +797,7 @@ export default function piWorkflows(pi) {
|
|
|
788
797
|
notify(ctx, `Workflow ${run.workflowName} continues under another runner (run ${run.runId}).`);
|
|
789
798
|
return;
|
|
790
799
|
}
|
|
791
|
-
releaseClaim(run, "done");
|
|
800
|
+
releaseClaim(run, result.state.status === "waiting" ? "park" : "done");
|
|
792
801
|
recordRunEvent({
|
|
793
802
|
runId: run.runId,
|
|
794
803
|
workflowRef: run.workflowName,
|
|
@@ -894,7 +903,7 @@ export default function piWorkflows(pi) {
|
|
|
894
903
|
// refused continuation (edited source, missing parent) must not consume
|
|
895
904
|
// the parent's one-continuation slot.
|
|
896
905
|
if (options.parentRunId !== undefined) {
|
|
897
|
-
const parent =
|
|
906
|
+
const parent = readWorkflowRun(options.parentRunId);
|
|
898
907
|
if (parent === null || parent.state.status !== "waiting") {
|
|
899
908
|
throw new Error(`Workflow run ${options.parentRunId} is not waiting at a checkpoint`);
|
|
900
909
|
}
|
|
@@ -921,6 +930,7 @@ export default function piWorkflows(pi) {
|
|
|
921
930
|
: workflowSource.path,
|
|
922
931
|
workflowSource: launchSourceIdentity(workflow, workflowSource),
|
|
923
932
|
definitionDigest: definitionDigest(snapshot),
|
|
933
|
+
definitionSnapshot: snapshot,
|
|
924
934
|
input,
|
|
925
935
|
launchOptions: preparedLaunchOptions(options),
|
|
926
936
|
runnerId,
|
|
@@ -994,14 +1004,17 @@ export default function piWorkflows(pi) {
|
|
|
994
1004
|
},
|
|
995
1005
|
conversation: {
|
|
996
1006
|
beginAttempt: (contract) => run.recorder?.beginAttempt(contract),
|
|
1007
|
+
recoverAssistant: (contract) => recoverAssistantStep(ctx.sessionManager.getBranch(), contract),
|
|
997
1008
|
mark: () => run.recorder?.mark() ?? 0,
|
|
998
1009
|
rangeSince: (mark) => run.recorder?.rangeSince(mark),
|
|
999
1010
|
},
|
|
1000
1011
|
});
|
|
1001
|
-
// The
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1012
|
+
// The engine and session recorder share one transactional SQLite store.
|
|
1013
|
+
const store = workflowStore(ctx.cwd, queueStore === null || claimToken === undefined
|
|
1014
|
+
? {}
|
|
1015
|
+
: {
|
|
1016
|
+
authorityProvider: () => queueStore.workflowRunAuthority(runId, claimToken),
|
|
1017
|
+
});
|
|
1005
1018
|
const engine = new WorkflowEngine({
|
|
1006
1019
|
executor,
|
|
1007
1020
|
store,
|
|
@@ -1026,8 +1039,8 @@ export default function piWorkflows(pi) {
|
|
|
1026
1039
|
},
|
|
1027
1040
|
// Awaited by the engine after run_started is persisted, so the session
|
|
1028
1041
|
// binding and its trace event always precede node and terminal events.
|
|
1029
|
-
onRunStarted: async (
|
|
1030
|
-
const recorder = new SessionRecorder(store,
|
|
1042
|
+
onRunStarted: async (runId) => {
|
|
1043
|
+
const recorder = new SessionRecorder(store, runId);
|
|
1031
1044
|
try {
|
|
1032
1045
|
await recorder.bind(ctx);
|
|
1033
1046
|
run.recorder = recorder;
|
|
@@ -1119,31 +1132,30 @@ export default function piWorkflows(pi) {
|
|
|
1119
1132
|
// once the cause is fixed, and say so in the feed.
|
|
1120
1133
|
let resumeFailed = run.resume === true && !isClaimLostError(error);
|
|
1121
1134
|
let terminalStatus;
|
|
1122
|
-
// Re-parking only makes sense while the
|
|
1123
|
-
// A terminal or waiting
|
|
1124
|
-
//
|
|
1125
|
-
// the bundle's real state, not a bogus failure.
|
|
1135
|
+
// Re-parking only makes sense while the run is still resumable.
|
|
1136
|
+
// A terminal or waiting run closes the queue row instead, and the feed
|
|
1137
|
+
// reports the authoritative run state.
|
|
1126
1138
|
if (resumeFailed) {
|
|
1127
1139
|
try {
|
|
1128
|
-
const bundle =
|
|
1140
|
+
const bundle = readWorkflowRun(runId);
|
|
1129
1141
|
if (bundle === null || bundle.state.status !== "running") {
|
|
1130
1142
|
resumeFailed = false;
|
|
1131
1143
|
terminalStatus = bundle?.state.status;
|
|
1132
1144
|
}
|
|
1133
1145
|
}
|
|
1134
1146
|
catch {
|
|
1135
|
-
// Unreadable
|
|
1147
|
+
// Unreadable run state closes the row too.
|
|
1136
1148
|
resumeFailed = false;
|
|
1137
1149
|
}
|
|
1138
1150
|
}
|
|
1139
|
-
// A continuation that failed before its
|
|
1151
|
+
// A continuation that failed before its run row exists frees the
|
|
1140
1152
|
// parent's continuation slot instead of consuming it forever.
|
|
1141
1153
|
let continuationSlotFreed = false;
|
|
1142
1154
|
if (run.parentRunId !== undefined &&
|
|
1143
1155
|
run.claimToken !== undefined &&
|
|
1144
1156
|
!isClaimLostError(error)) {
|
|
1145
1157
|
try {
|
|
1146
|
-
const bundle =
|
|
1158
|
+
const bundle = readWorkflowRun(runId);
|
|
1147
1159
|
if (bundle === null) {
|
|
1148
1160
|
continuationSlotFreed =
|
|
1149
1161
|
runQueueStore?.deleteWorkflowRun({ runId, claimToken: run.claimToken }) === true;
|
|
@@ -1213,7 +1225,7 @@ export default function piWorkflows(pi) {
|
|
|
1213
1225
|
runnerId,
|
|
1214
1226
|
claimToken,
|
|
1215
1227
|
leaseMs: RUN_CLAIM_LEASE_MS,
|
|
1216
|
-
excludeRunIds: [
|
|
1228
|
+
excludeRunIds: [],
|
|
1217
1229
|
sessionId: ctx.sessionManager.getSessionId(),
|
|
1218
1230
|
});
|
|
1219
1231
|
if (claimed === undefined) {
|
|
@@ -1221,7 +1233,11 @@ export default function piWorkflows(pi) {
|
|
|
1221
1233
|
}
|
|
1222
1234
|
let started;
|
|
1223
1235
|
try {
|
|
1224
|
-
const bundle =
|
|
1236
|
+
const bundle = readWorkflowRun(claimed.runId);
|
|
1237
|
+
if (bundle?.state.status === "waiting") {
|
|
1238
|
+
queueStore.parkWorkflowRun({ runId: claimed.runId, claimToken });
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1225
1241
|
const sourceRef = bundle?.state.workflowSource === undefined
|
|
1226
1242
|
? claimed.workflowSourceRef
|
|
1227
1243
|
: bundle.state.workflowSource.kind === "builtin"
|
|
@@ -1230,7 +1246,7 @@ export default function piWorkflows(pi) {
|
|
|
1230
1246
|
const launchOptions = parsePreparedLaunchOptions(claimed.launchOptions);
|
|
1231
1247
|
started = await startRun(ctx, sourceRef, claimed.input, {
|
|
1232
1248
|
...launchOptions,
|
|
1233
|
-
resume:
|
|
1249
|
+
resume: claimed.initialized,
|
|
1234
1250
|
runId: claimed.runId,
|
|
1235
1251
|
claimToken,
|
|
1236
1252
|
});
|
|
@@ -1259,12 +1275,12 @@ export default function piWorkflows(pi) {
|
|
|
1259
1275
|
? { state: "running", runId: activeRun.runId }
|
|
1260
1276
|
: workflowSchedulerResult(activeRun.lastState);
|
|
1261
1277
|
}
|
|
1262
|
-
const store =
|
|
1263
|
-
const bundle =
|
|
1278
|
+
const store = workflowStore(controllerContext?.cwd ?? process.cwd());
|
|
1279
|
+
const bundle = store.readRun(request.runId);
|
|
1264
1280
|
if (bundle !== null) {
|
|
1265
1281
|
const recovered = bundle.state.status === "running" ? await store.markRunInterrupted(request.runId) : bundle;
|
|
1266
1282
|
if (recovered !== null) {
|
|
1267
|
-
const lastTraceEvent =
|
|
1283
|
+
const lastTraceEvent = recovered.traceEvents?.at(-1) ?? null;
|
|
1268
1284
|
return workflowSchedulerResult(recovered.state, lastTraceEvent?.type === "run_interrupted");
|
|
1269
1285
|
}
|
|
1270
1286
|
return { state: "pending" };
|
|
@@ -1272,7 +1288,6 @@ export default function piWorkflows(pi) {
|
|
|
1272
1288
|
if (controllerContext === null) {
|
|
1273
1289
|
return { state: "pending" };
|
|
1274
1290
|
}
|
|
1275
|
-
await store.quarantineIncompleteRun(request.runId);
|
|
1276
1291
|
const runId = await startRun(controllerContext, request.workflow, request.input, {
|
|
1277
1292
|
runId: request.runId,
|
|
1278
1293
|
childKey,
|
|
@@ -1356,12 +1371,14 @@ export default function piWorkflows(pi) {
|
|
|
1356
1371
|
const result = await listWorkflowControl(ctx);
|
|
1357
1372
|
notify(ctx, result.message, result.level);
|
|
1358
1373
|
};
|
|
1359
|
-
const cancelHumanDecision = async (request) => {
|
|
1360
|
-
const store =
|
|
1374
|
+
const cancelHumanDecision = async (ctx, request) => {
|
|
1375
|
+
const store = humanDecisionStore(ctx.cwd);
|
|
1361
1376
|
await store.cancel(request, "cancelled");
|
|
1362
1377
|
const cancellation = await store.readCancellation(request.decisionId);
|
|
1363
|
-
if (cancellation !== null)
|
|
1378
|
+
if (cancellation !== null) {
|
|
1364
1379
|
await settleHumanDecisionChannels(cancellation);
|
|
1380
|
+
store.markEffectApplied(request.decisionId, "decision.settle_presentations");
|
|
1381
|
+
}
|
|
1365
1382
|
lastWaitingRunId = null;
|
|
1366
1383
|
};
|
|
1367
1384
|
const findOwnedWaitingHumanDecision = async (ctx) => {
|
|
@@ -1371,7 +1388,7 @@ export default function piWorkflows(pi) {
|
|
|
1371
1388
|
.filter((row) => row.originSessionId === null || row.originSessionId === sessionId)
|
|
1372
1389
|
.map((row) => row.runId));
|
|
1373
1390
|
const continued = new Set(rows.map((row) => row.parentRunId).filter((parent) => parent !== null));
|
|
1374
|
-
const bundles =
|
|
1391
|
+
const bundles = listWorkflowRuns();
|
|
1375
1392
|
for (const bundle of bundles) {
|
|
1376
1393
|
if (bundle.state.status !== "waiting" ||
|
|
1377
1394
|
!owned.has(bundle.state.runId) ||
|
|
@@ -1426,7 +1443,7 @@ export default function piWorkflows(pi) {
|
|
|
1426
1443
|
const { state } = widgetSource;
|
|
1427
1444
|
const request = humanDecisionRequest(state.finalOutput);
|
|
1428
1445
|
if (state.status === "waiting" && request !== null) {
|
|
1429
|
-
await cancelHumanDecision(request);
|
|
1446
|
+
await cancelHumanDecision(ctx, request);
|
|
1430
1447
|
clearWidgetTimer();
|
|
1431
1448
|
clearWidget(ctx);
|
|
1432
1449
|
return {
|
|
@@ -1446,7 +1463,7 @@ export default function piWorkflows(pi) {
|
|
|
1446
1463
|
}
|
|
1447
1464
|
const recovered = await findOwnedWaitingHumanDecision(ctx);
|
|
1448
1465
|
if (recovered !== null) {
|
|
1449
|
-
await cancelHumanDecision(recovered.request);
|
|
1466
|
+
await cancelHumanDecision(ctx, recovered.request);
|
|
1450
1467
|
return {
|
|
1451
1468
|
message: `Cancelled the pending human decision for workflow ${recovered.state.workflowName}.`,
|
|
1452
1469
|
details: {
|
|
@@ -1544,9 +1561,13 @@ export default function piWorkflows(pi) {
|
|
|
1544
1561
|
});
|
|
1545
1562
|
const statusWorkflowControl = async (ctx, runId) => {
|
|
1546
1563
|
if (runId !== undefined) {
|
|
1547
|
-
const
|
|
1564
|
+
const launch = ensureRunQueueStore(ctx.cwd).getWorkflowRun(runId);
|
|
1565
|
+
if (launch !== undefined &&
|
|
1566
|
+
["queued", "starting", "failed", "cancelled"].includes(launch.status)) {
|
|
1567
|
+
return workflowLaunchStatus(launch);
|
|
1568
|
+
}
|
|
1569
|
+
const bundle = readWorkflowRun(runId);
|
|
1548
1570
|
if (bundle === null) {
|
|
1549
|
-
const launch = ensureRunQueueStore(ctx.cwd).getWorkflowRun(runId);
|
|
1550
1571
|
if (launch === undefined)
|
|
1551
1572
|
throw new Error(`Workflow run not found: ${runId}`);
|
|
1552
1573
|
return workflowLaunchStatus(launch);
|
|
@@ -1584,7 +1605,7 @@ export default function piWorkflows(pi) {
|
|
|
1584
1605
|
.filter((row) => row.originSessionId === null || row.originSessionId === sessionId)
|
|
1585
1606
|
.map((row) => row.runId));
|
|
1586
1607
|
const continued = new Set(rows.map((row) => row.parentRunId).filter((parent) => parent !== null));
|
|
1587
|
-
const bundles =
|
|
1608
|
+
const bundles = listWorkflowRuns();
|
|
1588
1609
|
parentRunId =
|
|
1589
1610
|
bundles.find((bundle) => bundle.state.status === "waiting" &&
|
|
1590
1611
|
known.has(bundle.state.runId) &&
|
|
@@ -1600,7 +1621,7 @@ export default function piWorkflows(pi) {
|
|
|
1600
1621
|
queueRecord.originSessionId !== ctx.sessionManager.getSessionId()) {
|
|
1601
1622
|
throw new Error(`Workflow run ${parentRunId} belongs to another Pi session.`);
|
|
1602
1623
|
}
|
|
1603
|
-
const parent =
|
|
1624
|
+
const parent = readWorkflowRun(parentRunId);
|
|
1604
1625
|
if (parent === null ||
|
|
1605
1626
|
parent.state.status !== "waiting" ||
|
|
1606
1627
|
parent.state.workflowSource === undefined) {
|
|
@@ -1618,7 +1639,7 @@ export default function piWorkflows(pi) {
|
|
|
1618
1639
|
};
|
|
1619
1640
|
const answerWorkflowControl = async (ctx, input, requestedRunId, verified) => {
|
|
1620
1641
|
const waiting = await resolveWaitingWorkflow(ctx, requestedRunId, verified !== undefined);
|
|
1621
|
-
const parent =
|
|
1642
|
+
const parent = readWorkflowRun(waiting.parentRunId);
|
|
1622
1643
|
if (parent === null)
|
|
1623
1644
|
throw new Error(`Workflow run ${waiting.parentRunId} is unreadable.`);
|
|
1624
1645
|
const request = humanDecisionRequest(parent.state.finalOutput);
|
|
@@ -1643,7 +1664,7 @@ export default function piWorkflows(pi) {
|
|
|
1643
1664
|
},
|
|
1644
1665
|
idempotencyKey: verified?.idempotencyKey ?? createHumanDecisionAttemptId(),
|
|
1645
1666
|
};
|
|
1646
|
-
const store =
|
|
1667
|
+
const store = humanDecisionStore(ctx.cwd);
|
|
1647
1668
|
const acceptance = await store.accept(request, submission);
|
|
1648
1669
|
if (acceptance.status === "conflict" || acceptance.decision.provenance !== "human") {
|
|
1649
1670
|
throw new Error("That human decision was already answered differently.");
|
|
@@ -1669,20 +1690,9 @@ export default function piWorkflows(pi) {
|
|
|
1669
1690
|
};
|
|
1670
1691
|
}
|
|
1671
1692
|
}
|
|
1672
|
-
const decisionStore =
|
|
1673
|
-
if (request !== null && continuationRunId !== undefined && accepted !== undefined) {
|
|
1674
|
-
await decisionStore.recordContinuation(request.decisionId, {
|
|
1675
|
-
schema: "pi-workflows.human-decision-continuation.v1",
|
|
1676
|
-
decisionId: request.decisionId,
|
|
1677
|
-
requestDigest: request.requestDigest,
|
|
1678
|
-
provenance: accepted.provenance,
|
|
1679
|
-
parentRunId: waiting.parentRunId,
|
|
1680
|
-
runId: continuationRunId,
|
|
1681
|
-
createdAt: accepted.acceptedAt,
|
|
1682
|
-
});
|
|
1683
|
-
}
|
|
1693
|
+
const decisionStore = humanDecisionStore(ctx.cwd);
|
|
1684
1694
|
if (continuationRunId !== undefined) {
|
|
1685
|
-
const existingContinuation =
|
|
1695
|
+
const existingContinuation = readWorkflowRun(continuationRunId);
|
|
1686
1696
|
if (existingContinuation !== null) {
|
|
1687
1697
|
if (accepted !== undefined)
|
|
1688
1698
|
await settleHumanDecisionChannels(accepted);
|
|
@@ -1707,7 +1717,18 @@ export default function piWorkflows(pi) {
|
|
|
1707
1717
|
throw new Error("Could not start the checkpoint continuation.");
|
|
1708
1718
|
}
|
|
1709
1719
|
if (request !== null && accepted !== undefined) {
|
|
1720
|
+
await decisionStore.recordContinuation(request.decisionId, {
|
|
1721
|
+
schema: "pi-workflows.human-decision-continuation.v1",
|
|
1722
|
+
decisionId: request.decisionId,
|
|
1723
|
+
requestDigest: request.requestDigest,
|
|
1724
|
+
provenance: accepted.provenance,
|
|
1725
|
+
parentRunId: waiting.parentRunId,
|
|
1726
|
+
runId: continued,
|
|
1727
|
+
createdAt: accepted.acceptedAt,
|
|
1728
|
+
});
|
|
1729
|
+
decisionStore.markEffectApplied(request.decisionId, "decision.continue");
|
|
1710
1730
|
await settleHumanDecisionChannels(accepted);
|
|
1731
|
+
decisionStore.markEffectApplied(request.decisionId, "decision.settle_presentations");
|
|
1711
1732
|
}
|
|
1712
1733
|
lastWaitingRunId = null;
|
|
1713
1734
|
return {
|
|
@@ -1725,7 +1746,7 @@ export default function piWorkflows(pi) {
|
|
|
1725
1746
|
const channel = new PiDecisionChannel({
|
|
1726
1747
|
actorId: ctx.sessionManager.getSessionId(),
|
|
1727
1748
|
ui: ctx.ui,
|
|
1728
|
-
store:
|
|
1749
|
+
store: humanDecisionStore(ctx.cwd),
|
|
1729
1750
|
onAnswer: async (answer) => {
|
|
1730
1751
|
const result = await answerWorkflowControl(ctx, answer.response, request.runId, answer);
|
|
1731
1752
|
notify(ctx, result.message, result.level);
|
|
@@ -1763,7 +1784,7 @@ export default function piWorkflows(pi) {
|
|
|
1763
1784
|
config: loaded.channels,
|
|
1764
1785
|
credentials: loaded.credentials,
|
|
1765
1786
|
configDir: loaded.configDir,
|
|
1766
|
-
store:
|
|
1787
|
+
store: humanDecisionStore(ctx.cwd),
|
|
1767
1788
|
onAnswer: async (answer) => {
|
|
1768
1789
|
await answerWorkflowControl(ctx, answer.response, answer.request.runId, answer);
|
|
1769
1790
|
},
|
|
@@ -1771,11 +1792,11 @@ export default function piWorkflows(pi) {
|
|
|
1771
1792
|
await Promise.all([...telegramDecisionChannels.values()].map(async (channel) => channel.start()));
|
|
1772
1793
|
};
|
|
1773
1794
|
const recoverHumanDecisions = async (ctx, deliverPending = true) => {
|
|
1774
|
-
const runStore =
|
|
1775
|
-
const store =
|
|
1795
|
+
const runStore = workflowStore(ctx.cwd);
|
|
1796
|
+
const store = humanDecisionStore(ctx.cwd);
|
|
1776
1797
|
const requests = (await store.listRequests()).sort((left, right) => left.createdAt.localeCompare(right.createdAt));
|
|
1777
1798
|
for (const request of requests) {
|
|
1778
|
-
const parent =
|
|
1799
|
+
const parent = runStore.readRun(request.runId);
|
|
1779
1800
|
if (parent === null || parent.state.status !== "waiting")
|
|
1780
1801
|
continue;
|
|
1781
1802
|
const currentRequest = humanDecisionRequest(parent.state.finalOutput);
|
|
@@ -1788,18 +1809,31 @@ export default function piWorkflows(pi) {
|
|
|
1788
1809
|
const ownedBySession = queueRecord !== undefined &&
|
|
1789
1810
|
(queueRecord.originSessionId === null ||
|
|
1790
1811
|
queueRecord.originSessionId === ctx.sessionManager.getSessionId());
|
|
1812
|
+
if (!ownedBySession)
|
|
1813
|
+
continue;
|
|
1814
|
+
const recoveryToken = randomUUID();
|
|
1815
|
+
const recoveryQueue = ensureRunQueueStore(ctx.cwd);
|
|
1816
|
+
const claimed = recoveryQueue.claimWorkflowRun({
|
|
1817
|
+
runId: request.runId,
|
|
1818
|
+
runnerId: ctx.sessionManager.getSessionId(),
|
|
1819
|
+
claimToken: recoveryToken,
|
|
1820
|
+
leaseMs: RUN_CLAIM_LEASE_MS,
|
|
1821
|
+
});
|
|
1822
|
+
if (claimed === undefined)
|
|
1823
|
+
continue;
|
|
1824
|
+
const ownerStore = humanDecisionStore(ctx.cwd, () => recoveryQueue.workflowRunAuthority(request.runId, recoveryToken));
|
|
1791
1825
|
let resolved = await store.readResolved(request.decisionId);
|
|
1792
1826
|
if (resolved === null) {
|
|
1793
1827
|
let cancellation = await store.readCancellation(request.decisionId);
|
|
1794
1828
|
const expired = request.expiresAt !== undefined && Date.parse(request.expiresAt) <= Date.now();
|
|
1795
1829
|
if (cancellation === null && expired) {
|
|
1796
1830
|
if (request.defaultResponse === undefined) {
|
|
1797
|
-
await
|
|
1798
|
-
cancellation = await
|
|
1831
|
+
await ownerStore.cancel(request, "expired");
|
|
1832
|
+
cancellation = await ownerStore.readCancellation(request.decisionId);
|
|
1799
1833
|
}
|
|
1800
1834
|
else {
|
|
1801
1835
|
try {
|
|
1802
|
-
resolved = (await
|
|
1836
|
+
resolved = (await ownerStore.resolveTimeout(request)).decision;
|
|
1803
1837
|
}
|
|
1804
1838
|
catch {
|
|
1805
1839
|
cancellation = await store.readCancellation(request.decisionId);
|
|
@@ -1808,12 +1842,19 @@ export default function piWorkflows(pi) {
|
|
|
1808
1842
|
}
|
|
1809
1843
|
}
|
|
1810
1844
|
if (cancellation !== null) {
|
|
1845
|
+
recoveryQueue.completeWorkflowRun({ runId: request.runId, claimToken: recoveryToken });
|
|
1846
|
+
ownerStore.markEffectApplied(request.decisionId, "decision.cancel_parent");
|
|
1811
1847
|
await settleHumanDecisionChannels(cancellation);
|
|
1848
|
+
ownerStore.markEffectApplied(request.decisionId, "decision.settle_presentations");
|
|
1849
|
+
ownerStore.close();
|
|
1812
1850
|
continue;
|
|
1813
1851
|
}
|
|
1814
1852
|
if (resolved === null) {
|
|
1815
|
-
if (!deliverPending
|
|
1853
|
+
if (!deliverPending) {
|
|
1854
|
+
recoveryQueue.parkWorkflowRun({ runId: request.runId, claimToken: recoveryToken });
|
|
1855
|
+
ownerStore.close();
|
|
1816
1856
|
continue;
|
|
1857
|
+
}
|
|
1817
1858
|
const channels = audienceChannels(decisionChannelConfig, request.audience);
|
|
1818
1859
|
for (const channelId of channels) {
|
|
1819
1860
|
const channel = telegramDecisionChannels.get(channelId);
|
|
@@ -1824,14 +1865,17 @@ export default function piWorkflows(pi) {
|
|
|
1824
1865
|
lastWaitingRunId = request.runId;
|
|
1825
1866
|
queueMicrotask(() => void promptHumanDecision(ctx, request));
|
|
1826
1867
|
}
|
|
1868
|
+
recoveryQueue.parkWorkflowRun({ runId: request.runId, claimToken: recoveryToken });
|
|
1869
|
+
ownerStore.close();
|
|
1827
1870
|
continue;
|
|
1828
1871
|
}
|
|
1829
1872
|
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1873
|
+
const currentParent = runStore.readRun(request.runId);
|
|
1874
|
+
if (currentParent === null || currentParent.state.status !== "waiting") {
|
|
1875
|
+
recoveryQueue.parkWorkflowRun({ runId: request.runId, claimToken: recoveryToken });
|
|
1876
|
+
ownerStore.close();
|
|
1834
1877
|
continue;
|
|
1878
|
+
}
|
|
1835
1879
|
const runId = `continuation-${request.decisionId.slice("decision-".length)}`;
|
|
1836
1880
|
const continuation = (await store.readContinuation(request.decisionId)) ?? {
|
|
1837
1881
|
schema: "pi-workflows.human-decision-continuation.v1",
|
|
@@ -1842,11 +1886,16 @@ export default function piWorkflows(pi) {
|
|
|
1842
1886
|
runId,
|
|
1843
1887
|
createdAt: resolved.acceptedAt,
|
|
1844
1888
|
};
|
|
1845
|
-
|
|
1846
|
-
|
|
1889
|
+
recoveryQueue.parkWorkflowRun({
|
|
1890
|
+
runId: request.runId,
|
|
1891
|
+
claimToken: recoveryToken,
|
|
1892
|
+
});
|
|
1893
|
+
const existing = runStore.readRun(continuation.runId);
|
|
1847
1894
|
if (existing === null && activeRun === null) {
|
|
1848
|
-
if (currentParent.state.workflowSource === undefined)
|
|
1895
|
+
if (currentParent.state.workflowSource === undefined) {
|
|
1896
|
+
ownerStore.close();
|
|
1849
1897
|
continue;
|
|
1898
|
+
}
|
|
1850
1899
|
const workflowRef = currentParent.state.workflowSource.kind === "builtin"
|
|
1851
1900
|
? `builtin:${currentParent.state.workflowSource.id}`
|
|
1852
1901
|
: currentParent.state.workflowSource.path;
|
|
@@ -1857,7 +1906,13 @@ export default function piWorkflows(pi) {
|
|
|
1857
1906
|
quiet: true,
|
|
1858
1907
|
});
|
|
1859
1908
|
}
|
|
1909
|
+
if (runStore.readRun(continuation.runId) !== null) {
|
|
1910
|
+
await ownerStore.recordContinuation(request.decisionId, continuation);
|
|
1911
|
+
ownerStore.markEffectApplied(request.decisionId, "decision.continue");
|
|
1912
|
+
}
|
|
1860
1913
|
await settleHumanDecisionChannels(resolved);
|
|
1914
|
+
ownerStore.markEffectApplied(request.decisionId, "decision.settle_presentations");
|
|
1915
|
+
ownerStore.close();
|
|
1861
1916
|
if (activeRun !== null)
|
|
1862
1917
|
break;
|
|
1863
1918
|
}
|
|
@@ -1903,7 +1958,7 @@ export default function piWorkflows(pi) {
|
|
|
1903
1958
|
}
|
|
1904
1959
|
const queue = ensureRunQueueStore(ctx.cwd);
|
|
1905
1960
|
const existing = queue.findSessionReservation(ctx.sessionManager.getSessionId());
|
|
1906
|
-
if (existing !== undefined) {
|
|
1961
|
+
if (existing !== undefined && existing.runId !== options.parentRunId) {
|
|
1907
1962
|
throw new Error(`Workflow ${existing.workflowName} is already ${existing.status} (run ${existing.runId}).`);
|
|
1908
1963
|
}
|
|
1909
1964
|
if (presentationPending !== null) {
|
|
@@ -1913,7 +1968,7 @@ export default function piWorkflows(pi) {
|
|
|
1913
1968
|
const workflow = resolved.definition;
|
|
1914
1969
|
const workflowSource = resolved.source;
|
|
1915
1970
|
if (options.parentRunId !== undefined) {
|
|
1916
|
-
const parent =
|
|
1971
|
+
const parent = readWorkflowRun(options.parentRunId);
|
|
1917
1972
|
if (parent === null || parent.state.status !== "waiting") {
|
|
1918
1973
|
throw new Error(`Workflow run ${options.parentRunId} is not waiting at a checkpoint`);
|
|
1919
1974
|
}
|
|
@@ -1931,6 +1986,7 @@ export default function piWorkflows(pi) {
|
|
|
1931
1986
|
workflowSourceRef: workflowSource.kind === "builtin" ? `builtin:${workflowSource.id}` : workflowSource.path,
|
|
1932
1987
|
workflowSource: launchSourceIdentity(workflow, workflowSource),
|
|
1933
1988
|
definitionDigest: definitionDigest(snapshot),
|
|
1989
|
+
definitionSnapshot: snapshot,
|
|
1934
1990
|
input,
|
|
1935
1991
|
launchOptions: preparedLaunchOptions(options),
|
|
1936
1992
|
runnerId,
|
|
@@ -2119,7 +2175,7 @@ export default function piWorkflows(pi) {
|
|
|
2119
2175
|
}
|
|
2120
2176
|
catch (error) {
|
|
2121
2177
|
const message = errorMessage(error);
|
|
2122
|
-
notify(ctx, /
|
|
2178
|
+
notify(ctx, /continuations\.parent_run_id/.test(message)
|
|
2123
2179
|
? "That checkpoint was already answered; see its continuation run."
|
|
2124
2180
|
: `Could not continue workflow: ${message}`, "error");
|
|
2125
2181
|
}
|
|
@@ -2266,9 +2322,10 @@ export default function piWorkflows(pi) {
|
|
|
2266
2322
|
label: "Workflow",
|
|
2267
2323
|
description: [
|
|
2268
2324
|
"List, start, inspect, pause, resume, cancel, answer, update, or complete pi-workflows runs.",
|
|
2269
|
-
"When the user asks to monitor, watch, poll, or check something repeatedly, start the built-in monitor workflow with input keys task,
|
|
2325
|
+
"When the user asks to monitor, watch, poll, or check something repeatedly, start the built-in monitor workflow with input keys task, stopWhen, everyMinutes, and optional maxChecks.",
|
|
2326
|
+
"Put the exact goal, authority, limits, and recovery rules in task; Monitor observes first, performs only safe authorized actions, verifies them immediately, and waits only while target work is moving or an external event is pending.",
|
|
2270
2327
|
"Use update or submit only when a workflow step contract asks for it, and pass the exact step and attempt ids.",
|
|
2271
|
-
"Do not start repeated work without the user's request
|
|
2328
|
+
"Do not start repeated work without the user's request.",
|
|
2272
2329
|
].join(" "),
|
|
2273
2330
|
parameters: WorkflowToolParameters,
|
|
2274
2331
|
async execute(toolCallId, rawParams, _signal, _onUpdate, ctx) {
|
|
@@ -2295,7 +2352,7 @@ export default function piWorkflows(pi) {
|
|
|
2295
2352
|
break;
|
|
2296
2353
|
case "answer": {
|
|
2297
2354
|
const waiting = await resolveWaitingWorkflow(ctx, params.runId);
|
|
2298
|
-
const parent =
|
|
2355
|
+
const parent = readWorkflowRun(waiting.parentRunId);
|
|
2299
2356
|
if (parent !== null && humanDecisionRequest(parent.state.finalOutput) !== null) {
|
|
2300
2357
|
throw new Error("This checkpoint requires a verified human answer from Pi UI or a configured decision channel.");
|
|
2301
2358
|
}
|
|
@@ -2323,7 +2380,7 @@ export default function piWorkflows(pi) {
|
|
|
2323
2380
|
}
|
|
2324
2381
|
throw new Error("No workflow step is waiting for output.");
|
|
2325
2382
|
}
|
|
2326
|
-
// Flush the conversation into
|
|
2383
|
+
// Flush the conversation into SQLite before accepting, so the
|
|
2327
2384
|
// attempt range includes the assistant message carrying this call.
|
|
2328
2385
|
await activeRun.recorder?.record(ctx).catch(() => undefined);
|
|
2329
2386
|
await activeRun.recorder?.synchronize(ctx).catch(() => undefined);
|
|
@@ -2363,22 +2420,7 @@ export default function piWorkflows(pi) {
|
|
|
2363
2420
|
controllerContext = ctx;
|
|
2364
2421
|
if (herdrEnabled)
|
|
2365
2422
|
void refreshHerdrCapability(ctx);
|
|
2366
|
-
|
|
2367
|
-
const queue = ensureRunQueueStore(ctx.cwd);
|
|
2368
|
-
const migration = await migrateLegacyWorkflowSources({
|
|
2369
|
-
catalog: builtinWorkflowCatalog,
|
|
2370
|
-
queue,
|
|
2371
|
-
});
|
|
2372
|
-
migrationBlockedRuns.clear();
|
|
2373
|
-
for (const blocked of migration.blocked)
|
|
2374
|
-
migrationBlockedRuns.add(blocked.runId);
|
|
2375
|
-
if (migration.blocked.length > 0) {
|
|
2376
|
-
notify(ctx, `Could not migrate ${migration.blocked.length} legacy workflow source(s).`, "warning");
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
catch (error) {
|
|
2380
|
-
notify(ctx, `Could not migrate legacy workflow sources: ${errorMessage(error)}`, "warning");
|
|
2381
|
-
}
|
|
2423
|
+
ensureRunQueueStore(ctx.cwd);
|
|
2382
2424
|
try {
|
|
2383
2425
|
await reloadDecisionChannels(ctx);
|
|
2384
2426
|
}
|
|
@@ -2460,6 +2502,9 @@ export default function piWorkflows(pi) {
|
|
|
2460
2502
|
});
|
|
2461
2503
|
pi.on("turn_end", async (event, ctx) => {
|
|
2462
2504
|
await activeRun?.recorder?.handleTurnEnd(event, ctx).catch(() => undefined);
|
|
2505
|
+
// turn_end observes the message after message_end replacement handlers,
|
|
2506
|
+
// so this is the exact assistant message that remains visible.
|
|
2507
|
+
activeRun?.executor.handleMessageEnd(event.message);
|
|
2463
2508
|
});
|
|
2464
2509
|
pi.on("message_start", async (event, ctx) => {
|
|
2465
2510
|
await activeRun?.recorder?.handleMessageStart(event, ctx).catch(() => undefined);
|
|
@@ -2502,9 +2547,9 @@ export default function piWorkflows(pi) {
|
|
|
2502
2547
|
return;
|
|
2503
2548
|
}
|
|
2504
2549
|
}
|
|
2505
|
-
const flushedNatural = turnCoordinator.flushNatural(ctx.isIdle() && !sessionClosed && !runHeld());
|
|
2506
2550
|
const run = activeRun;
|
|
2507
2551
|
if (!run) {
|
|
2552
|
+
turnCoordinator.flushNatural(ctx.isIdle() && !sessionClosed && !runHeld());
|
|
2508
2553
|
presentationPending = null;
|
|
2509
2554
|
runSyncPass(ctx);
|
|
2510
2555
|
return;
|
|
@@ -2512,8 +2557,16 @@ export default function piWorkflows(pi) {
|
|
|
2512
2557
|
await run.recorder?.synchronize(ctx).catch(() => undefined);
|
|
2513
2558
|
run.recorder?.settleAttempt();
|
|
2514
2559
|
run.executor.setStreaming(false);
|
|
2515
|
-
if (
|
|
2560
|
+
if (run.executor.pendingCompletion === "assistant") {
|
|
2561
|
+
// The visible response is the step output. Resolve it before any
|
|
2562
|
+
// unrelated deferred turn can start and replace the captured message.
|
|
2516
2563
|
run.executor.handleAgentSettled();
|
|
2564
|
+
}
|
|
2565
|
+
else {
|
|
2566
|
+
const flushedNatural = turnCoordinator.flushNatural(ctx.isIdle() && !sessionClosed && !runHeld());
|
|
2567
|
+
if (flushedNatural === 0)
|
|
2568
|
+
run.executor.handleAgentSettled();
|
|
2569
|
+
}
|
|
2517
2570
|
runSyncPass(ctx);
|
|
2518
2571
|
});
|
|
2519
2572
|
pi.on("session_shutdown", async () => {
|