@osolmaz/pi-workflows 0.12.1 → 0.13.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 +59 -33
- package/dist/builtins/autodoc.workflow.d.ts +194 -7
- package/dist/builtins/autodoc.workflow.js +156 -30
- package/dist/builtins/autodoc.workflow.js.map +1 -1
- package/dist/builtins/autoimplement-command-batches.d.ts +1 -0
- package/dist/builtins/autoimplement-command-batches.js +29 -31
- package/dist/builtins/autoimplement-command-batches.js.map +1 -1
- package/dist/builtins/autoimplement.workflow.d.ts +1619 -97
- package/dist/builtins/autoimplement.workflow.js +416 -153
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- 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 +7 -5
- package/dist/builtins/catalog.js.map +1 -1
- package/dist/builtins/change-verification.workflow.d.ts +110 -0
- package/dist/builtins/change-verification.workflow.js +860 -0
- package/dist/builtins/change-verification.workflow.js.map +1 -0
- 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 +499 -217
- 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 +527 -26
- package/dist/builtins/plan-change.workflow.js +26 -0
- package/dist/builtins/plan-change.workflow.js.map +1 -1
- 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/builtins/workspace-preparation.workflow.d.ts +75 -0
- package/dist/builtins/workspace-preparation.workflow.js +498 -0
- package/dist/builtins/workspace-preparation.workflow.js.map +1 -0
- 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 +90 -6
- 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 -117
- 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 +27 -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/2026-08-24-change-scoped-verification-plan.md +419 -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 +111 -62
- 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/autodoc/SKILL.md +7 -0
- package/skills/autoimplement/SKILL.md +4 -0
- 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/autodoc.workflow.ts +184 -33
- package/src/builtins/autoimplement-command-batches.ts +39 -33
- package/src/builtins/autoimplement.workflow.ts +483 -175
- package/src/builtins/autoplan.workflow.ts +290 -40
- package/src/builtins/catalog.ts +7 -5
- package/src/builtins/change-verification.workflow.ts +1143 -0
- package/src/builtins/index.ts +7 -1
- package/src/builtins/monitor.workflow.ts +667 -250
- package/src/builtins/plain-summary.workflow.ts +185 -0
- package/src/builtins/plan-change.workflow.ts +35 -0
- package/src/builtins/sanity-check.workflow.ts +62 -7
- package/src/builtins/workspace-preparation.workflow.ts +668 -0
- 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 +106 -6
- package/src/extension/herdr-viewer.ts +0 -5
- package/src/extension/index.ts +180 -132
- 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;
|
|
@@ -321,7 +329,6 @@ export default function piWorkflows(pi) {
|
|
|
321
329
|
};
|
|
322
330
|
let activeRun = null;
|
|
323
331
|
let systemTurnAbort = null;
|
|
324
|
-
let suppressWorkflowAssistantTail = false;
|
|
325
332
|
let lastExpiredAttempt = null;
|
|
326
333
|
// The interactive run currently parked at a checkpoint, if any.
|
|
327
334
|
let lastWaitingRunId = null;
|
|
@@ -569,7 +576,6 @@ export default function piWorkflows(pi) {
|
|
|
569
576
|
workflowViewTarget = {
|
|
570
577
|
runId: state.runId,
|
|
571
578
|
workflowName: state.workflowName,
|
|
572
|
-
runDir: path.resolve(new WorkflowRunStore().runDirFor(state.runId)),
|
|
573
579
|
};
|
|
574
580
|
renderWidget(ctx);
|
|
575
581
|
};
|
|
@@ -693,7 +699,6 @@ export default function piWorkflows(pi) {
|
|
|
693
699
|
resolution: "presentation",
|
|
694
700
|
send: (turnIntentId) => {
|
|
695
701
|
presentationPending = run.generation;
|
|
696
|
-
suppressWorkflowAssistantTail = false;
|
|
697
702
|
pi.sendMessage({
|
|
698
703
|
customType: PRESENTATION_MESSAGE_TYPE,
|
|
699
704
|
content: buildPresentationMessage(instructions, state),
|
|
@@ -746,14 +751,16 @@ export default function piWorkflows(pi) {
|
|
|
746
751
|
}
|
|
747
752
|
try {
|
|
748
753
|
if (outcome === "park") {
|
|
749
|
-
runQueueStore.parkWorkflowRun({ runId: run.runId, claimToken: run.claimToken })
|
|
754
|
+
if (runQueueStore.parkWorkflowRun({ runId: run.runId, claimToken: run.claimToken })) {
|
|
755
|
+
runQueueStore.settleRunEffect(run.runId, "run.park_queue");
|
|
756
|
+
}
|
|
750
757
|
}
|
|
751
|
-
else {
|
|
752
|
-
runQueueStore.
|
|
758
|
+
else if (runQueueStore.completeWorkflowRun({ runId: run.runId, claimToken: run.claimToken })) {
|
|
759
|
+
runQueueStore.settleRunEffect(run.runId, "run.settle_queue");
|
|
753
760
|
}
|
|
754
761
|
}
|
|
755
762
|
catch {
|
|
756
|
-
// Queue bookkeeping is best-effort next to the durable
|
|
763
|
+
// Queue bookkeeping is best-effort next to the durable run fact.
|
|
757
764
|
}
|
|
758
765
|
finally {
|
|
759
766
|
run.claimToken = undefined;
|
|
@@ -764,7 +771,7 @@ export default function piWorkflows(pi) {
|
|
|
764
771
|
activeRun = null;
|
|
765
772
|
}
|
|
766
773
|
if (result.state.status === "running") {
|
|
767
|
-
// The run was parked for another runner; its
|
|
774
|
+
// The run was parked for another runner; its SQLite state stays resumable
|
|
768
775
|
// and its queue row becomes claimable. The recorder drains first: its
|
|
769
776
|
// finalization writes share the fenced store, so the claim must stay
|
|
770
777
|
// valid until capture is complete. Nothing else to present.
|
|
@@ -788,7 +795,7 @@ export default function piWorkflows(pi) {
|
|
|
788
795
|
notify(ctx, `Workflow ${run.workflowName} continues under another runner (run ${run.runId}).`);
|
|
789
796
|
return;
|
|
790
797
|
}
|
|
791
|
-
releaseClaim(run, "done");
|
|
798
|
+
releaseClaim(run, result.state.status === "waiting" ? "park" : "done");
|
|
792
799
|
recordRunEvent({
|
|
793
800
|
runId: run.runId,
|
|
794
801
|
workflowRef: run.workflowName,
|
|
@@ -894,7 +901,7 @@ export default function piWorkflows(pi) {
|
|
|
894
901
|
// refused continuation (edited source, missing parent) must not consume
|
|
895
902
|
// the parent's one-continuation slot.
|
|
896
903
|
if (options.parentRunId !== undefined) {
|
|
897
|
-
const parent =
|
|
904
|
+
const parent = readWorkflowRun(options.parentRunId);
|
|
898
905
|
if (parent === null || parent.state.status !== "waiting") {
|
|
899
906
|
throw new Error(`Workflow run ${options.parentRunId} is not waiting at a checkpoint`);
|
|
900
907
|
}
|
|
@@ -921,6 +928,7 @@ export default function piWorkflows(pi) {
|
|
|
921
928
|
: workflowSource.path,
|
|
922
929
|
workflowSource: launchSourceIdentity(workflow, workflowSource),
|
|
923
930
|
definitionDigest: definitionDigest(snapshot),
|
|
931
|
+
definitionSnapshot: snapshot,
|
|
924
932
|
input,
|
|
925
933
|
launchOptions: preparedLaunchOptions(options),
|
|
926
934
|
runnerId,
|
|
@@ -994,14 +1002,17 @@ export default function piWorkflows(pi) {
|
|
|
994
1002
|
},
|
|
995
1003
|
conversation: {
|
|
996
1004
|
beginAttempt: (contract) => run.recorder?.beginAttempt(contract),
|
|
1005
|
+
recoverAssistant: (contract) => recoverAssistantStep(ctx.sessionManager.getBranch(), contract),
|
|
997
1006
|
mark: () => run.recorder?.mark() ?? 0,
|
|
998
1007
|
rangeSince: (mark) => run.recorder?.rangeSince(mark),
|
|
999
1008
|
},
|
|
1000
1009
|
});
|
|
1001
|
-
// The
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1010
|
+
// The engine and session recorder share one transactional SQLite store.
|
|
1011
|
+
const store = workflowStore(ctx.cwd, queueStore === null || claimToken === undefined
|
|
1012
|
+
? {}
|
|
1013
|
+
: {
|
|
1014
|
+
authorityProvider: () => queueStore.workflowRunAuthority(runId, claimToken),
|
|
1015
|
+
});
|
|
1005
1016
|
const engine = new WorkflowEngine({
|
|
1006
1017
|
executor,
|
|
1007
1018
|
store,
|
|
@@ -1026,8 +1037,8 @@ export default function piWorkflows(pi) {
|
|
|
1026
1037
|
},
|
|
1027
1038
|
// Awaited by the engine after run_started is persisted, so the session
|
|
1028
1039
|
// binding and its trace event always precede node and terminal events.
|
|
1029
|
-
onRunStarted: async (
|
|
1030
|
-
const recorder = new SessionRecorder(store,
|
|
1040
|
+
onRunStarted: async (runId) => {
|
|
1041
|
+
const recorder = new SessionRecorder(store, runId);
|
|
1031
1042
|
try {
|
|
1032
1043
|
await recorder.bind(ctx);
|
|
1033
1044
|
run.recorder = recorder;
|
|
@@ -1119,31 +1130,30 @@ export default function piWorkflows(pi) {
|
|
|
1119
1130
|
// once the cause is fixed, and say so in the feed.
|
|
1120
1131
|
let resumeFailed = run.resume === true && !isClaimLostError(error);
|
|
1121
1132
|
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.
|
|
1133
|
+
// Re-parking only makes sense while the run is still resumable.
|
|
1134
|
+
// A terminal or waiting run closes the queue row instead, and the feed
|
|
1135
|
+
// reports the authoritative run state.
|
|
1126
1136
|
if (resumeFailed) {
|
|
1127
1137
|
try {
|
|
1128
|
-
const bundle =
|
|
1138
|
+
const bundle = readWorkflowRun(runId);
|
|
1129
1139
|
if (bundle === null || bundle.state.status !== "running") {
|
|
1130
1140
|
resumeFailed = false;
|
|
1131
1141
|
terminalStatus = bundle?.state.status;
|
|
1132
1142
|
}
|
|
1133
1143
|
}
|
|
1134
1144
|
catch {
|
|
1135
|
-
// Unreadable
|
|
1145
|
+
// Unreadable run state closes the row too.
|
|
1136
1146
|
resumeFailed = false;
|
|
1137
1147
|
}
|
|
1138
1148
|
}
|
|
1139
|
-
// A continuation that failed before its
|
|
1149
|
+
// A continuation that failed before its run row exists frees the
|
|
1140
1150
|
// parent's continuation slot instead of consuming it forever.
|
|
1141
1151
|
let continuationSlotFreed = false;
|
|
1142
1152
|
if (run.parentRunId !== undefined &&
|
|
1143
1153
|
run.claimToken !== undefined &&
|
|
1144
1154
|
!isClaimLostError(error)) {
|
|
1145
1155
|
try {
|
|
1146
|
-
const bundle =
|
|
1156
|
+
const bundle = readWorkflowRun(runId);
|
|
1147
1157
|
if (bundle === null) {
|
|
1148
1158
|
continuationSlotFreed =
|
|
1149
1159
|
runQueueStore?.deleteWorkflowRun({ runId, claimToken: run.claimToken }) === true;
|
|
@@ -1213,7 +1223,7 @@ export default function piWorkflows(pi) {
|
|
|
1213
1223
|
runnerId,
|
|
1214
1224
|
claimToken,
|
|
1215
1225
|
leaseMs: RUN_CLAIM_LEASE_MS,
|
|
1216
|
-
excludeRunIds: [
|
|
1226
|
+
excludeRunIds: [],
|
|
1217
1227
|
sessionId: ctx.sessionManager.getSessionId(),
|
|
1218
1228
|
});
|
|
1219
1229
|
if (claimed === undefined) {
|
|
@@ -1221,7 +1231,11 @@ export default function piWorkflows(pi) {
|
|
|
1221
1231
|
}
|
|
1222
1232
|
let started;
|
|
1223
1233
|
try {
|
|
1224
|
-
const bundle =
|
|
1234
|
+
const bundle = readWorkflowRun(claimed.runId);
|
|
1235
|
+
if (bundle?.state.status === "waiting") {
|
|
1236
|
+
queueStore.parkWorkflowRun({ runId: claimed.runId, claimToken });
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1225
1239
|
const sourceRef = bundle?.state.workflowSource === undefined
|
|
1226
1240
|
? claimed.workflowSourceRef
|
|
1227
1241
|
: bundle.state.workflowSource.kind === "builtin"
|
|
@@ -1230,7 +1244,7 @@ export default function piWorkflows(pi) {
|
|
|
1230
1244
|
const launchOptions = parsePreparedLaunchOptions(claimed.launchOptions);
|
|
1231
1245
|
started = await startRun(ctx, sourceRef, claimed.input, {
|
|
1232
1246
|
...launchOptions,
|
|
1233
|
-
resume:
|
|
1247
|
+
resume: claimed.initialized,
|
|
1234
1248
|
runId: claimed.runId,
|
|
1235
1249
|
claimToken,
|
|
1236
1250
|
});
|
|
@@ -1259,12 +1273,12 @@ export default function piWorkflows(pi) {
|
|
|
1259
1273
|
? { state: "running", runId: activeRun.runId }
|
|
1260
1274
|
: workflowSchedulerResult(activeRun.lastState);
|
|
1261
1275
|
}
|
|
1262
|
-
const store =
|
|
1263
|
-
const bundle =
|
|
1276
|
+
const store = workflowStore(controllerContext?.cwd ?? process.cwd());
|
|
1277
|
+
const bundle = store.readRun(request.runId);
|
|
1264
1278
|
if (bundle !== null) {
|
|
1265
1279
|
const recovered = bundle.state.status === "running" ? await store.markRunInterrupted(request.runId) : bundle;
|
|
1266
1280
|
if (recovered !== null) {
|
|
1267
|
-
const lastTraceEvent =
|
|
1281
|
+
const lastTraceEvent = recovered.traceEvents?.at(-1) ?? null;
|
|
1268
1282
|
return workflowSchedulerResult(recovered.state, lastTraceEvent?.type === "run_interrupted");
|
|
1269
1283
|
}
|
|
1270
1284
|
return { state: "pending" };
|
|
@@ -1272,7 +1286,6 @@ export default function piWorkflows(pi) {
|
|
|
1272
1286
|
if (controllerContext === null) {
|
|
1273
1287
|
return { state: "pending" };
|
|
1274
1288
|
}
|
|
1275
|
-
await store.quarantineIncompleteRun(request.runId);
|
|
1276
1289
|
const runId = await startRun(controllerContext, request.workflow, request.input, {
|
|
1277
1290
|
runId: request.runId,
|
|
1278
1291
|
childKey,
|
|
@@ -1356,12 +1369,14 @@ export default function piWorkflows(pi) {
|
|
|
1356
1369
|
const result = await listWorkflowControl(ctx);
|
|
1357
1370
|
notify(ctx, result.message, result.level);
|
|
1358
1371
|
};
|
|
1359
|
-
const cancelHumanDecision = async (request) => {
|
|
1360
|
-
const store =
|
|
1372
|
+
const cancelHumanDecision = async (ctx, request) => {
|
|
1373
|
+
const store = humanDecisionStore(ctx.cwd);
|
|
1361
1374
|
await store.cancel(request, "cancelled");
|
|
1362
1375
|
const cancellation = await store.readCancellation(request.decisionId);
|
|
1363
|
-
if (cancellation !== null)
|
|
1376
|
+
if (cancellation !== null) {
|
|
1364
1377
|
await settleHumanDecisionChannels(cancellation);
|
|
1378
|
+
store.markEffectApplied(request.decisionId, "decision.settle_presentations");
|
|
1379
|
+
}
|
|
1365
1380
|
lastWaitingRunId = null;
|
|
1366
1381
|
};
|
|
1367
1382
|
const findOwnedWaitingHumanDecision = async (ctx) => {
|
|
@@ -1371,7 +1386,7 @@ export default function piWorkflows(pi) {
|
|
|
1371
1386
|
.filter((row) => row.originSessionId === null || row.originSessionId === sessionId)
|
|
1372
1387
|
.map((row) => row.runId));
|
|
1373
1388
|
const continued = new Set(rows.map((row) => row.parentRunId).filter((parent) => parent !== null));
|
|
1374
|
-
const bundles =
|
|
1389
|
+
const bundles = listWorkflowRuns();
|
|
1375
1390
|
for (const bundle of bundles) {
|
|
1376
1391
|
if (bundle.state.status !== "waiting" ||
|
|
1377
1392
|
!owned.has(bundle.state.runId) ||
|
|
@@ -1426,7 +1441,7 @@ export default function piWorkflows(pi) {
|
|
|
1426
1441
|
const { state } = widgetSource;
|
|
1427
1442
|
const request = humanDecisionRequest(state.finalOutput);
|
|
1428
1443
|
if (state.status === "waiting" && request !== null) {
|
|
1429
|
-
await cancelHumanDecision(request);
|
|
1444
|
+
await cancelHumanDecision(ctx, request);
|
|
1430
1445
|
clearWidgetTimer();
|
|
1431
1446
|
clearWidget(ctx);
|
|
1432
1447
|
return {
|
|
@@ -1446,7 +1461,7 @@ export default function piWorkflows(pi) {
|
|
|
1446
1461
|
}
|
|
1447
1462
|
const recovered = await findOwnedWaitingHumanDecision(ctx);
|
|
1448
1463
|
if (recovered !== null) {
|
|
1449
|
-
await cancelHumanDecision(recovered.request);
|
|
1464
|
+
await cancelHumanDecision(ctx, recovered.request);
|
|
1450
1465
|
return {
|
|
1451
1466
|
message: `Cancelled the pending human decision for workflow ${recovered.state.workflowName}.`,
|
|
1452
1467
|
details: {
|
|
@@ -1544,9 +1559,13 @@ export default function piWorkflows(pi) {
|
|
|
1544
1559
|
});
|
|
1545
1560
|
const statusWorkflowControl = async (ctx, runId) => {
|
|
1546
1561
|
if (runId !== undefined) {
|
|
1547
|
-
const
|
|
1562
|
+
const launch = ensureRunQueueStore(ctx.cwd).getWorkflowRun(runId);
|
|
1563
|
+
if (launch !== undefined &&
|
|
1564
|
+
["queued", "starting", "failed", "cancelled"].includes(launch.status)) {
|
|
1565
|
+
return workflowLaunchStatus(launch);
|
|
1566
|
+
}
|
|
1567
|
+
const bundle = readWorkflowRun(runId);
|
|
1548
1568
|
if (bundle === null) {
|
|
1549
|
-
const launch = ensureRunQueueStore(ctx.cwd).getWorkflowRun(runId);
|
|
1550
1569
|
if (launch === undefined)
|
|
1551
1570
|
throw new Error(`Workflow run not found: ${runId}`);
|
|
1552
1571
|
return workflowLaunchStatus(launch);
|
|
@@ -1584,7 +1603,7 @@ export default function piWorkflows(pi) {
|
|
|
1584
1603
|
.filter((row) => row.originSessionId === null || row.originSessionId === sessionId)
|
|
1585
1604
|
.map((row) => row.runId));
|
|
1586
1605
|
const continued = new Set(rows.map((row) => row.parentRunId).filter((parent) => parent !== null));
|
|
1587
|
-
const bundles =
|
|
1606
|
+
const bundles = listWorkflowRuns();
|
|
1588
1607
|
parentRunId =
|
|
1589
1608
|
bundles.find((bundle) => bundle.state.status === "waiting" &&
|
|
1590
1609
|
known.has(bundle.state.runId) &&
|
|
@@ -1600,7 +1619,7 @@ export default function piWorkflows(pi) {
|
|
|
1600
1619
|
queueRecord.originSessionId !== ctx.sessionManager.getSessionId()) {
|
|
1601
1620
|
throw new Error(`Workflow run ${parentRunId} belongs to another Pi session.`);
|
|
1602
1621
|
}
|
|
1603
|
-
const parent =
|
|
1622
|
+
const parent = readWorkflowRun(parentRunId);
|
|
1604
1623
|
if (parent === null ||
|
|
1605
1624
|
parent.state.status !== "waiting" ||
|
|
1606
1625
|
parent.state.workflowSource === undefined) {
|
|
@@ -1618,7 +1637,7 @@ export default function piWorkflows(pi) {
|
|
|
1618
1637
|
};
|
|
1619
1638
|
const answerWorkflowControl = async (ctx, input, requestedRunId, verified) => {
|
|
1620
1639
|
const waiting = await resolveWaitingWorkflow(ctx, requestedRunId, verified !== undefined);
|
|
1621
|
-
const parent =
|
|
1640
|
+
const parent = readWorkflowRun(waiting.parentRunId);
|
|
1622
1641
|
if (parent === null)
|
|
1623
1642
|
throw new Error(`Workflow run ${waiting.parentRunId} is unreadable.`);
|
|
1624
1643
|
const request = humanDecisionRequest(parent.state.finalOutput);
|
|
@@ -1643,7 +1662,7 @@ export default function piWorkflows(pi) {
|
|
|
1643
1662
|
},
|
|
1644
1663
|
idempotencyKey: verified?.idempotencyKey ?? createHumanDecisionAttemptId(),
|
|
1645
1664
|
};
|
|
1646
|
-
const store =
|
|
1665
|
+
const store = humanDecisionStore(ctx.cwd);
|
|
1647
1666
|
const acceptance = await store.accept(request, submission);
|
|
1648
1667
|
if (acceptance.status === "conflict" || acceptance.decision.provenance !== "human") {
|
|
1649
1668
|
throw new Error("That human decision was already answered differently.");
|
|
@@ -1669,20 +1688,9 @@ export default function piWorkflows(pi) {
|
|
|
1669
1688
|
};
|
|
1670
1689
|
}
|
|
1671
1690
|
}
|
|
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
|
-
}
|
|
1691
|
+
const decisionStore = humanDecisionStore(ctx.cwd);
|
|
1684
1692
|
if (continuationRunId !== undefined) {
|
|
1685
|
-
const existingContinuation =
|
|
1693
|
+
const existingContinuation = readWorkflowRun(continuationRunId);
|
|
1686
1694
|
if (existingContinuation !== null) {
|
|
1687
1695
|
if (accepted !== undefined)
|
|
1688
1696
|
await settleHumanDecisionChannels(accepted);
|
|
@@ -1707,7 +1715,18 @@ export default function piWorkflows(pi) {
|
|
|
1707
1715
|
throw new Error("Could not start the checkpoint continuation.");
|
|
1708
1716
|
}
|
|
1709
1717
|
if (request !== null && accepted !== undefined) {
|
|
1718
|
+
await decisionStore.recordContinuation(request.decisionId, {
|
|
1719
|
+
schema: "pi-workflows.human-decision-continuation.v1",
|
|
1720
|
+
decisionId: request.decisionId,
|
|
1721
|
+
requestDigest: request.requestDigest,
|
|
1722
|
+
provenance: accepted.provenance,
|
|
1723
|
+
parentRunId: waiting.parentRunId,
|
|
1724
|
+
runId: continued,
|
|
1725
|
+
createdAt: accepted.acceptedAt,
|
|
1726
|
+
});
|
|
1727
|
+
decisionStore.markEffectApplied(request.decisionId, "decision.continue");
|
|
1710
1728
|
await settleHumanDecisionChannels(accepted);
|
|
1729
|
+
decisionStore.markEffectApplied(request.decisionId, "decision.settle_presentations");
|
|
1711
1730
|
}
|
|
1712
1731
|
lastWaitingRunId = null;
|
|
1713
1732
|
return {
|
|
@@ -1725,7 +1744,7 @@ export default function piWorkflows(pi) {
|
|
|
1725
1744
|
const channel = new PiDecisionChannel({
|
|
1726
1745
|
actorId: ctx.sessionManager.getSessionId(),
|
|
1727
1746
|
ui: ctx.ui,
|
|
1728
|
-
store:
|
|
1747
|
+
store: humanDecisionStore(ctx.cwd),
|
|
1729
1748
|
onAnswer: async (answer) => {
|
|
1730
1749
|
const result = await answerWorkflowControl(ctx, answer.response, request.runId, answer);
|
|
1731
1750
|
notify(ctx, result.message, result.level);
|
|
@@ -1763,7 +1782,7 @@ export default function piWorkflows(pi) {
|
|
|
1763
1782
|
config: loaded.channels,
|
|
1764
1783
|
credentials: loaded.credentials,
|
|
1765
1784
|
configDir: loaded.configDir,
|
|
1766
|
-
store:
|
|
1785
|
+
store: humanDecisionStore(ctx.cwd),
|
|
1767
1786
|
onAnswer: async (answer) => {
|
|
1768
1787
|
await answerWorkflowControl(ctx, answer.response, answer.request.runId, answer);
|
|
1769
1788
|
},
|
|
@@ -1771,11 +1790,11 @@ export default function piWorkflows(pi) {
|
|
|
1771
1790
|
await Promise.all([...telegramDecisionChannels.values()].map(async (channel) => channel.start()));
|
|
1772
1791
|
};
|
|
1773
1792
|
const recoverHumanDecisions = async (ctx, deliverPending = true) => {
|
|
1774
|
-
const runStore =
|
|
1775
|
-
const store =
|
|
1793
|
+
const runStore = workflowStore(ctx.cwd);
|
|
1794
|
+
const store = humanDecisionStore(ctx.cwd);
|
|
1776
1795
|
const requests = (await store.listRequests()).sort((left, right) => left.createdAt.localeCompare(right.createdAt));
|
|
1777
1796
|
for (const request of requests) {
|
|
1778
|
-
const parent =
|
|
1797
|
+
const parent = runStore.readRun(request.runId);
|
|
1779
1798
|
if (parent === null || parent.state.status !== "waiting")
|
|
1780
1799
|
continue;
|
|
1781
1800
|
const currentRequest = humanDecisionRequest(parent.state.finalOutput);
|
|
@@ -1788,18 +1807,31 @@ export default function piWorkflows(pi) {
|
|
|
1788
1807
|
const ownedBySession = queueRecord !== undefined &&
|
|
1789
1808
|
(queueRecord.originSessionId === null ||
|
|
1790
1809
|
queueRecord.originSessionId === ctx.sessionManager.getSessionId());
|
|
1810
|
+
if (!ownedBySession)
|
|
1811
|
+
continue;
|
|
1812
|
+
const recoveryToken = randomUUID();
|
|
1813
|
+
const recoveryQueue = ensureRunQueueStore(ctx.cwd);
|
|
1814
|
+
const claimed = recoveryQueue.claimWorkflowRun({
|
|
1815
|
+
runId: request.runId,
|
|
1816
|
+
runnerId: ctx.sessionManager.getSessionId(),
|
|
1817
|
+
claimToken: recoveryToken,
|
|
1818
|
+
leaseMs: RUN_CLAIM_LEASE_MS,
|
|
1819
|
+
});
|
|
1820
|
+
if (claimed === undefined)
|
|
1821
|
+
continue;
|
|
1822
|
+
const ownerStore = humanDecisionStore(ctx.cwd, () => recoveryQueue.workflowRunAuthority(request.runId, recoveryToken));
|
|
1791
1823
|
let resolved = await store.readResolved(request.decisionId);
|
|
1792
1824
|
if (resolved === null) {
|
|
1793
1825
|
let cancellation = await store.readCancellation(request.decisionId);
|
|
1794
1826
|
const expired = request.expiresAt !== undefined && Date.parse(request.expiresAt) <= Date.now();
|
|
1795
1827
|
if (cancellation === null && expired) {
|
|
1796
1828
|
if (request.defaultResponse === undefined) {
|
|
1797
|
-
await
|
|
1798
|
-
cancellation = await
|
|
1829
|
+
await ownerStore.cancel(request, "expired");
|
|
1830
|
+
cancellation = await ownerStore.readCancellation(request.decisionId);
|
|
1799
1831
|
}
|
|
1800
1832
|
else {
|
|
1801
1833
|
try {
|
|
1802
|
-
resolved = (await
|
|
1834
|
+
resolved = (await ownerStore.resolveTimeout(request)).decision;
|
|
1803
1835
|
}
|
|
1804
1836
|
catch {
|
|
1805
1837
|
cancellation = await store.readCancellation(request.decisionId);
|
|
@@ -1808,12 +1840,19 @@ export default function piWorkflows(pi) {
|
|
|
1808
1840
|
}
|
|
1809
1841
|
}
|
|
1810
1842
|
if (cancellation !== null) {
|
|
1843
|
+
recoveryQueue.completeWorkflowRun({ runId: request.runId, claimToken: recoveryToken });
|
|
1844
|
+
ownerStore.markEffectApplied(request.decisionId, "decision.cancel_parent");
|
|
1811
1845
|
await settleHumanDecisionChannels(cancellation);
|
|
1846
|
+
ownerStore.markEffectApplied(request.decisionId, "decision.settle_presentations");
|
|
1847
|
+
ownerStore.close();
|
|
1812
1848
|
continue;
|
|
1813
1849
|
}
|
|
1814
1850
|
if (resolved === null) {
|
|
1815
|
-
if (!deliverPending
|
|
1851
|
+
if (!deliverPending) {
|
|
1852
|
+
recoveryQueue.parkWorkflowRun({ runId: request.runId, claimToken: recoveryToken });
|
|
1853
|
+
ownerStore.close();
|
|
1816
1854
|
continue;
|
|
1855
|
+
}
|
|
1817
1856
|
const channels = audienceChannels(decisionChannelConfig, request.audience);
|
|
1818
1857
|
for (const channelId of channels) {
|
|
1819
1858
|
const channel = telegramDecisionChannels.get(channelId);
|
|
@@ -1824,14 +1863,17 @@ export default function piWorkflows(pi) {
|
|
|
1824
1863
|
lastWaitingRunId = request.runId;
|
|
1825
1864
|
queueMicrotask(() => void promptHumanDecision(ctx, request));
|
|
1826
1865
|
}
|
|
1866
|
+
recoveryQueue.parkWorkflowRun({ runId: request.runId, claimToken: recoveryToken });
|
|
1867
|
+
ownerStore.close();
|
|
1827
1868
|
continue;
|
|
1828
1869
|
}
|
|
1829
1870
|
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1871
|
+
const currentParent = runStore.readRun(request.runId);
|
|
1872
|
+
if (currentParent === null || currentParent.state.status !== "waiting") {
|
|
1873
|
+
recoveryQueue.parkWorkflowRun({ runId: request.runId, claimToken: recoveryToken });
|
|
1874
|
+
ownerStore.close();
|
|
1834
1875
|
continue;
|
|
1876
|
+
}
|
|
1835
1877
|
const runId = `continuation-${request.decisionId.slice("decision-".length)}`;
|
|
1836
1878
|
const continuation = (await store.readContinuation(request.decisionId)) ?? {
|
|
1837
1879
|
schema: "pi-workflows.human-decision-continuation.v1",
|
|
@@ -1842,11 +1884,16 @@ export default function piWorkflows(pi) {
|
|
|
1842
1884
|
runId,
|
|
1843
1885
|
createdAt: resolved.acceptedAt,
|
|
1844
1886
|
};
|
|
1845
|
-
|
|
1846
|
-
|
|
1887
|
+
recoveryQueue.parkWorkflowRun({
|
|
1888
|
+
runId: request.runId,
|
|
1889
|
+
claimToken: recoveryToken,
|
|
1890
|
+
});
|
|
1891
|
+
const existing = runStore.readRun(continuation.runId);
|
|
1847
1892
|
if (existing === null && activeRun === null) {
|
|
1848
|
-
if (currentParent.state.workflowSource === undefined)
|
|
1893
|
+
if (currentParent.state.workflowSource === undefined) {
|
|
1894
|
+
ownerStore.close();
|
|
1849
1895
|
continue;
|
|
1896
|
+
}
|
|
1850
1897
|
const workflowRef = currentParent.state.workflowSource.kind === "builtin"
|
|
1851
1898
|
? `builtin:${currentParent.state.workflowSource.id}`
|
|
1852
1899
|
: currentParent.state.workflowSource.path;
|
|
@@ -1857,7 +1904,13 @@ export default function piWorkflows(pi) {
|
|
|
1857
1904
|
quiet: true,
|
|
1858
1905
|
});
|
|
1859
1906
|
}
|
|
1907
|
+
if (runStore.readRun(continuation.runId) !== null) {
|
|
1908
|
+
await ownerStore.recordContinuation(request.decisionId, continuation);
|
|
1909
|
+
ownerStore.markEffectApplied(request.decisionId, "decision.continue");
|
|
1910
|
+
}
|
|
1860
1911
|
await settleHumanDecisionChannels(resolved);
|
|
1912
|
+
ownerStore.markEffectApplied(request.decisionId, "decision.settle_presentations");
|
|
1913
|
+
ownerStore.close();
|
|
1861
1914
|
if (activeRun !== null)
|
|
1862
1915
|
break;
|
|
1863
1916
|
}
|
|
@@ -1903,7 +1956,7 @@ export default function piWorkflows(pi) {
|
|
|
1903
1956
|
}
|
|
1904
1957
|
const queue = ensureRunQueueStore(ctx.cwd);
|
|
1905
1958
|
const existing = queue.findSessionReservation(ctx.sessionManager.getSessionId());
|
|
1906
|
-
if (existing !== undefined) {
|
|
1959
|
+
if (existing !== undefined && existing.runId !== options.parentRunId) {
|
|
1907
1960
|
throw new Error(`Workflow ${existing.workflowName} is already ${existing.status} (run ${existing.runId}).`);
|
|
1908
1961
|
}
|
|
1909
1962
|
if (presentationPending !== null) {
|
|
@@ -1913,7 +1966,7 @@ export default function piWorkflows(pi) {
|
|
|
1913
1966
|
const workflow = resolved.definition;
|
|
1914
1967
|
const workflowSource = resolved.source;
|
|
1915
1968
|
if (options.parentRunId !== undefined) {
|
|
1916
|
-
const parent =
|
|
1969
|
+
const parent = readWorkflowRun(options.parentRunId);
|
|
1917
1970
|
if (parent === null || parent.state.status !== "waiting") {
|
|
1918
1971
|
throw new Error(`Workflow run ${options.parentRunId} is not waiting at a checkpoint`);
|
|
1919
1972
|
}
|
|
@@ -1931,6 +1984,7 @@ export default function piWorkflows(pi) {
|
|
|
1931
1984
|
workflowSourceRef: workflowSource.kind === "builtin" ? `builtin:${workflowSource.id}` : workflowSource.path,
|
|
1932
1985
|
workflowSource: launchSourceIdentity(workflow, workflowSource),
|
|
1933
1986
|
definitionDigest: definitionDigest(snapshot),
|
|
1987
|
+
definitionSnapshot: snapshot,
|
|
1934
1988
|
input,
|
|
1935
1989
|
launchOptions: preparedLaunchOptions(options),
|
|
1936
1990
|
runnerId,
|
|
@@ -2119,7 +2173,7 @@ export default function piWorkflows(pi) {
|
|
|
2119
2173
|
}
|
|
2120
2174
|
catch (error) {
|
|
2121
2175
|
const message = errorMessage(error);
|
|
2122
|
-
notify(ctx, /
|
|
2176
|
+
notify(ctx, /continuations\.parent_run_id/.test(message)
|
|
2123
2177
|
? "That checkpoint was already answered; see its continuation run."
|
|
2124
2178
|
: `Could not continue workflow: ${message}`, "error");
|
|
2125
2179
|
}
|
|
@@ -2266,9 +2320,10 @@ export default function piWorkflows(pi) {
|
|
|
2266
2320
|
label: "Workflow",
|
|
2267
2321
|
description: [
|
|
2268
2322
|
"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,
|
|
2323
|
+
"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.",
|
|
2324
|
+
"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
2325
|
"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
|
|
2326
|
+
"Do not start repeated work without the user's request.",
|
|
2272
2327
|
].join(" "),
|
|
2273
2328
|
parameters: WorkflowToolParameters,
|
|
2274
2329
|
async execute(toolCallId, rawParams, _signal, _onUpdate, ctx) {
|
|
@@ -2295,7 +2350,7 @@ export default function piWorkflows(pi) {
|
|
|
2295
2350
|
break;
|
|
2296
2351
|
case "answer": {
|
|
2297
2352
|
const waiting = await resolveWaitingWorkflow(ctx, params.runId);
|
|
2298
|
-
const parent =
|
|
2353
|
+
const parent = readWorkflowRun(waiting.parentRunId);
|
|
2299
2354
|
if (parent !== null && humanDecisionRequest(parent.state.finalOutput) !== null) {
|
|
2300
2355
|
throw new Error("This checkpoint requires a verified human answer from Pi UI or a configured decision channel.");
|
|
2301
2356
|
}
|
|
@@ -2323,7 +2378,7 @@ export default function piWorkflows(pi) {
|
|
|
2323
2378
|
}
|
|
2324
2379
|
throw new Error("No workflow step is waiting for output.");
|
|
2325
2380
|
}
|
|
2326
|
-
// Flush the conversation into
|
|
2381
|
+
// Flush the conversation into SQLite before accepting, so the
|
|
2327
2382
|
// attempt range includes the assistant message carrying this call.
|
|
2328
2383
|
await activeRun.recorder?.record(ctx).catch(() => undefined);
|
|
2329
2384
|
await activeRun.recorder?.synchronize(ctx).catch(() => undefined);
|
|
@@ -2331,7 +2386,6 @@ export default function piWorkflows(pi) {
|
|
|
2331
2386
|
if (!result.accepted) {
|
|
2332
2387
|
throw new Error(result.message);
|
|
2333
2388
|
}
|
|
2334
|
-
suppressWorkflowAssistantTail = true;
|
|
2335
2389
|
return {
|
|
2336
2390
|
content: [{ type: "text", text: result.message }],
|
|
2337
2391
|
details: { action: "submit", step: params.step, accepted: true },
|
|
@@ -2363,22 +2417,7 @@ export default function piWorkflows(pi) {
|
|
|
2363
2417
|
controllerContext = ctx;
|
|
2364
2418
|
if (herdrEnabled)
|
|
2365
2419
|
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
|
-
}
|
|
2420
|
+
ensureRunQueueStore(ctx.cwd);
|
|
2382
2421
|
try {
|
|
2383
2422
|
await reloadDecisionChannels(ctx);
|
|
2384
2423
|
}
|
|
@@ -2420,7 +2459,6 @@ export default function piWorkflows(pi) {
|
|
|
2420
2459
|
}
|
|
2421
2460
|
});
|
|
2422
2461
|
pi.on("agent_start", () => {
|
|
2423
|
-
suppressWorkflowAssistantTail = false;
|
|
2424
2462
|
if (!activeRun && presentationPending === null && presentationAbort) {
|
|
2425
2463
|
// A normal user turn started while an async presentation prompt was
|
|
2426
2464
|
// still resolving. The user's new request supersedes that old result.
|
|
@@ -2430,7 +2468,6 @@ export default function piWorkflows(pi) {
|
|
|
2430
2468
|
activeRun?.executor.setStreaming(true);
|
|
2431
2469
|
});
|
|
2432
2470
|
pi.on("agent_end", (event, ctx) => {
|
|
2433
|
-
suppressWorkflowAssistantTail = false;
|
|
2434
2471
|
const aborted = event.messages.some((message) => typeof message === "object" &&
|
|
2435
2472
|
message !== null &&
|
|
2436
2473
|
"stopReason" in message &&
|
|
@@ -2460,6 +2497,9 @@ export default function piWorkflows(pi) {
|
|
|
2460
2497
|
});
|
|
2461
2498
|
pi.on("turn_end", async (event, ctx) => {
|
|
2462
2499
|
await activeRun?.recorder?.handleTurnEnd(event, ctx).catch(() => undefined);
|
|
2500
|
+
// turn_end observes the message after message_end replacement handlers,
|
|
2501
|
+
// so this is the exact assistant message that remains visible.
|
|
2502
|
+
activeRun?.executor.handleMessageEnd(event.message);
|
|
2463
2503
|
});
|
|
2464
2504
|
pi.on("message_start", async (event, ctx) => {
|
|
2465
2505
|
await activeRun?.recorder?.handleMessageStart(event, ctx).catch(() => undefined);
|
|
@@ -2468,14 +2508,6 @@ export default function piWorkflows(pi) {
|
|
|
2468
2508
|
activeRun?.recorder?.handleMessageUpdate(event);
|
|
2469
2509
|
});
|
|
2470
2510
|
pi.on("message_end", (event) => {
|
|
2471
|
-
if (suppressWorkflowAssistantTail && event.message.role === "assistant") {
|
|
2472
|
-
const message = {
|
|
2473
|
-
...event.message,
|
|
2474
|
-
content: event.message.content.filter((part) => part.type !== "text"),
|
|
2475
|
-
};
|
|
2476
|
-
activeRun?.recorder?.handleMessageEnd({ ...event, message });
|
|
2477
|
-
return { message };
|
|
2478
|
-
}
|
|
2479
2511
|
activeRun?.recorder?.handleMessageEnd(event);
|
|
2480
2512
|
});
|
|
2481
2513
|
pi.on("tool_execution_start", (event) => {
|
|
@@ -2502,9 +2534,9 @@ export default function piWorkflows(pi) {
|
|
|
2502
2534
|
return;
|
|
2503
2535
|
}
|
|
2504
2536
|
}
|
|
2505
|
-
const flushedNatural = turnCoordinator.flushNatural(ctx.isIdle() && !sessionClosed && !runHeld());
|
|
2506
2537
|
const run = activeRun;
|
|
2507
2538
|
if (!run) {
|
|
2539
|
+
turnCoordinator.flushNatural(ctx.isIdle() && !sessionClosed && !runHeld());
|
|
2508
2540
|
presentationPending = null;
|
|
2509
2541
|
runSyncPass(ctx);
|
|
2510
2542
|
return;
|
|
@@ -2512,15 +2544,22 @@ export default function piWorkflows(pi) {
|
|
|
2512
2544
|
await run.recorder?.synchronize(ctx).catch(() => undefined);
|
|
2513
2545
|
run.recorder?.settleAttempt();
|
|
2514
2546
|
run.executor.setStreaming(false);
|
|
2515
|
-
if (
|
|
2547
|
+
if (run.executor.pendingCompletion === "assistant") {
|
|
2548
|
+
// The visible response is the step output. Resolve it before any
|
|
2549
|
+
// unrelated deferred turn can start and replace the captured message.
|
|
2516
2550
|
run.executor.handleAgentSettled();
|
|
2551
|
+
}
|
|
2552
|
+
else {
|
|
2553
|
+
const flushedNatural = turnCoordinator.flushNatural(ctx.isIdle() && !sessionClosed && !runHeld());
|
|
2554
|
+
if (flushedNatural === 0)
|
|
2555
|
+
run.executor.handleAgentSettled();
|
|
2556
|
+
}
|
|
2517
2557
|
runSyncPass(ctx);
|
|
2518
2558
|
});
|
|
2519
2559
|
pi.on("session_shutdown", async () => {
|
|
2520
2560
|
sessionClosed = true;
|
|
2521
2561
|
herdrProbeGeneration += 1;
|
|
2522
2562
|
systemTurnAbort = null;
|
|
2523
|
-
suppressWorkflowAssistantTail = false;
|
|
2524
2563
|
turnCoordinator.clearDeferred();
|
|
2525
2564
|
supersedePresentation();
|
|
2526
2565
|
const run = activeRun;
|