@osolmaz/pi-workflows 0.12.0 → 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 +56 -31
- package/dist/builtins/autodoc.workflow.d.ts +4 -4
- package/dist/builtins/autoimplement.workflow.d.ts +797 -142
- package/dist/builtins/autoimplement.workflow.js +17 -105
- 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/index.d.ts +4 -2
- package/dist/builtins/index.js +3 -1
- package/dist/builtins/index.js.map +1 -1
- package/dist/builtins/monitor.workflow.d.ts +56 -23
- package/dist/builtins/monitor.workflow.js +508 -329
- package/dist/builtins/monitor.workflow.js.map +1 -1
- package/dist/builtins/pi-agent-group.d.ts +72 -0
- package/dist/builtins/pi-agent-group.js +1087 -0
- package/dist/builtins/pi-agent-group.js.map +1 -0
- package/dist/builtins/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-approval.workflow.d.ts +39 -5
- package/dist/builtins/plan-approval.workflow.js +92 -14
- package/dist/builtins/plan-approval.workflow.js.map +1 -1
- package/dist/builtins/plan-change.workflow.d.ts +449 -0
- package/dist/builtins/plan-change.workflow.js +256 -0
- package/dist/builtins/plan-change.workflow.js.map +1 -0
- package/dist/builtins/plan-presentation.js +2 -2
- package/dist/builtins/plan-presentation.js.map +1 -1
- package/dist/builtins/sanity-check.workflow.d.ts +50 -6
- package/dist/builtins/sanity-check.workflow.js +150 -28
- 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 +4 -2
- package/dist/extension/decision-channels.js +333 -146
- 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 +209 -127
- 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/session-events.d.ts +2 -2
- 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 +29 -3
- 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 +15 -10
- 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 +9 -23
- 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/catalog.js +7 -2
- package/dist/workflows/catalog.js.map +1 -1
- package/dist/workflows/composition.js +20 -2
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/decision-presentation.d.ts +1 -1
- package/dist/workflows/decision-presentation.js +51 -38
- package/dist/workflows/decision-presentation.js.map +1 -1
- package/dist/workflows/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 +7 -7
- package/dist/workflows/engine.js +196 -115
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/errors.d.ts +16 -3
- package/dist/workflows/errors.js +18 -3
- package/dist/workflows/errors.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +45 -8
- package/dist/workflows/human-decision.js +641 -274
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +5 -6
- package/dist/workflows/index.js +4 -5
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/progress.d.ts +1 -0
- package/dist/workflows/progress.js +15 -3
- package/dist/workflows/progress.js.map +1 -1
- package/dist/workflows/schema.js +27 -1
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/store.d.ts +83 -103
- package/dist/workflows/store.js +907 -951
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +88 -126
- 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 +36 -47
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +14 -24
- package/docs/MONITOR.md +179 -237
- package/docs/SQLITE_STATE.md +219 -0
- package/docs/WORKFLOW_COMPOSITION.md +27 -26
- 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 +322 -0
- package/docs/plans/2026-08-21-sanity-check-plan.md +217 -95
- 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 +127 -53
- package/examples/workflows/approved-plan.workflow.ts +19 -46
- package/examples/workflows/plain-summary.workflow.ts +1 -0
- package/herdr-plugin.toml +1 -1
- package/package.json +7 -7
- package/plugins/herdr/viewer.mjs +1 -11
- package/schemas/human-decision-accepted-v1.schema.json +15 -3
- package/schemas/human-decision-continuation-v1.schema.json +10 -1
- package/schemas/human-decision-delivery-v1.schema.json +8 -0
- package/schemas/human-decision-receipt-v1.schema.json +8 -0
- package/schemas/human-decision-request-v1.schema.json +24 -4
- package/skills/autoimplement/SKILL.md +27 -0
- package/skills/autoplan/SKILL.md +5 -4
- package/skills/monitor/SKILL.md +91 -135
- package/skills/pi-workflows/SKILL.md +9 -8
- package/skills/sanity-check/SKILL.md +44 -0
- package/src/builtins/autoimplement.workflow.ts +19 -118
- package/src/builtins/autoplan.workflow.ts +290 -40
- package/src/builtins/catalog.ts +7 -5
- package/src/builtins/index.ts +18 -1
- package/src/builtins/monitor.workflow.ts +673 -382
- package/src/builtins/pi-agent-group.ts +1407 -0
- package/src/builtins/plain-summary.workflow.ts +185 -0
- package/src/builtins/plan-approval.workflow.ts +157 -24
- package/src/builtins/plan-change.workflow.ts +321 -0
- package/src/builtins/plan-presentation.ts +2 -2
- package/src/builtins/sanity-check.workflow.ts +248 -48
- 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 +456 -203
- package/src/extension/executor.ts +105 -2
- package/src/extension/herdr-viewer.ts +0 -5
- package/src/extension/index.ts +251 -151
- package/src/extension/recorder.ts +13 -22
- package/src/extension/session-events.ts +2 -2
- package/src/extension/step-message.ts +106 -4
- package/src/extension/widget.ts +32 -5
- package/src/host/rpc-executor.ts +4 -0
- package/src/host/runner.ts +93 -41
- package/src/render/graph-render.ts +12 -6
- 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 +19 -30
- 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/catalog.ts +7 -2
- package/src/workflows/composition.ts +21 -3
- package/src/workflows/decision-presentation.ts +56 -43
- package/src/workflows/definition.ts +36 -3
- package/src/workflows/engine.ts +228 -130
- package/src/workflows/errors.ts +27 -3
- package/src/workflows/human-decision.ts +921 -349
- package/src/workflows/index.ts +20 -30
- package/src/workflows/progress.ts +18 -3
- package/src/workflows/schema.ts +36 -1
- package/src/workflows/store.ts +1300 -1163
- package/src/workflows/types.ts +100 -137
- package/dist/builtins/sanity-check-session.d.ts +0 -17
- package/dist/builtins/sanity-check-session.js +0 -168
- package/dist/builtins/sanity-check-session.js.map +0 -1
- 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/schemas/human-decision-accepted-v2.schema.json +0 -50
- package/schemas/human-decision-delivery-v2.schema.json +0 -36
- package/schemas/human-decision-receipt-v2.schema.json +0 -39
- package/schemas/human-decision-request-v2.schema.json +0 -69
- package/schemas/human-decision-resolution-v2.schema.json +0 -27
- package/src/builtins/sanity-check-session.ts +0 -205
- package/src/workflows/artifacts.ts +0 -188
- package/src/workflows/migrate-sources.ts +0 -178
package/src/extension/index.ts
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import path from "node:path";
|
|
3
2
|
import { isDeepStrictEqual } from "node:util";
|
|
4
3
|
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
5
4
|
import { builtinWorkflowCatalog } from "../builtins/catalog.js";
|
|
6
|
-
import {
|
|
7
|
-
projectControllerStorePath,
|
|
8
|
-
SqliteControllerStore,
|
|
9
|
-
type WorkflowRunQueueRecord,
|
|
10
|
-
} from "../controllers/index.js";
|
|
5
|
+
import { SqliteControllerStore, type WorkflowRunQueueRecord } from "../controllers/index.js";
|
|
11
6
|
import type {
|
|
12
7
|
WorkflowTurnIntentCause,
|
|
13
8
|
WorkflowTurnIntentResolution,
|
|
@@ -18,6 +13,7 @@ import { compositionMetadata } from "../workflows/composition.js";
|
|
|
18
13
|
import { humanDecisionChannelRequest } from "../workflows/decision-presentation.js";
|
|
19
14
|
import { WorkflowEngine } from "../workflows/engine.js";
|
|
20
15
|
import {
|
|
16
|
+
BuiltinWorkflowRevisionChangedError,
|
|
21
17
|
ClaimLostError,
|
|
22
18
|
errorMessage,
|
|
23
19
|
isClaimLostError,
|
|
@@ -29,18 +25,17 @@ import {
|
|
|
29
25
|
validateHumanDecisionResponse,
|
|
30
26
|
} from "../workflows/human-decision.js";
|
|
31
27
|
import { discoverWorkflows, resolveWorkflowRef } from "../workflows/loader.js";
|
|
32
|
-
import { migrateLegacyWorkflowSources } from "../workflows/migrate-sources.js";
|
|
33
28
|
import { appendProgressHistory, progressRecordsFromTrace } from "../workflows/progress.js";
|
|
34
29
|
import {
|
|
35
30
|
createRunId,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
readRunBundle,
|
|
31
|
+
listWorkflowRuns,
|
|
32
|
+
readWorkflowRun,
|
|
39
33
|
WorkflowRunStore,
|
|
40
34
|
createDefinitionSnapshot,
|
|
41
35
|
} from "../workflows/store.js";
|
|
42
36
|
import type {
|
|
43
37
|
AcceptedHumanDecision,
|
|
38
|
+
ResolvedHumanDecision,
|
|
44
39
|
AgentStepContract,
|
|
45
40
|
HumanDecisionRequest,
|
|
46
41
|
HumanDecisionSubmission,
|
|
@@ -48,6 +43,7 @@ import type {
|
|
|
48
43
|
WorkflowDefinitionSnapshot,
|
|
49
44
|
WorkflowRunResult,
|
|
50
45
|
WorkflowRunState,
|
|
46
|
+
WorkflowSource,
|
|
51
47
|
WorkflowUpdateRecord,
|
|
52
48
|
} from "../workflows/types.js";
|
|
53
49
|
import {
|
|
@@ -93,6 +89,7 @@ import {
|
|
|
93
89
|
} from "./herdr-viewer.js";
|
|
94
90
|
import { SessionRecorder } from "./recorder.js";
|
|
95
91
|
import {
|
|
92
|
+
recoverAssistantStep,
|
|
96
93
|
registerWorkflowAgentStepMessageRenderer,
|
|
97
94
|
WORKFLOW_AGENT_STEP_MESSAGE_SCHEMA,
|
|
98
95
|
WORKFLOW_AGENT_STEP_MESSAGE_TYPE,
|
|
@@ -194,19 +191,14 @@ function humanDecisionRequest(value: unknown): HumanDecisionRequest | null {
|
|
|
194
191
|
value !== null && typeof value === "object"
|
|
195
192
|
? (value as { schema?: unknown }).schema
|
|
196
193
|
: undefined;
|
|
197
|
-
if (
|
|
198
|
-
schema !== "pi-workflows.human-decision-request.v1" &&
|
|
199
|
-
schema !== "pi-workflows.human-decision-request.v2"
|
|
200
|
-
) {
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
194
|
+
if (schema !== "pi-workflows.human-decision-request.v1") return null;
|
|
203
195
|
return value as HumanDecisionRequest;
|
|
204
196
|
}
|
|
205
197
|
|
|
206
198
|
type PreparedLaunchOptions = {
|
|
207
199
|
presentation?: boolean;
|
|
208
200
|
parentRunId?: string;
|
|
209
|
-
humanDecision?:
|
|
201
|
+
humanDecision?: ResolvedHumanDecision;
|
|
210
202
|
};
|
|
211
203
|
|
|
212
204
|
type StartRunOptions = {
|
|
@@ -218,8 +210,8 @@ type StartRunOptions = {
|
|
|
218
210
|
signal?: AbortSignal;
|
|
219
211
|
/** Continue a checkpointed run: input becomes the legacy answer payload. */
|
|
220
212
|
parentRunId?: string;
|
|
221
|
-
/**
|
|
222
|
-
humanDecision?:
|
|
213
|
+
/** Durable human or timeout response for a protected decision continuation. */
|
|
214
|
+
humanDecision?: ResolvedHumanDecision;
|
|
223
215
|
/** Resume a parked run at its stopped node instead of starting fresh. */
|
|
224
216
|
resume?: boolean;
|
|
225
217
|
/** An existing queue claim token, when the caller already claimed the run. */
|
|
@@ -237,6 +229,29 @@ function launchSourceIdentity(workflow: WorkflowDefinition, root: unknown): unkn
|
|
|
237
229
|
};
|
|
238
230
|
}
|
|
239
231
|
|
|
232
|
+
function continuationSourceChangedError(
|
|
233
|
+
runId: string,
|
|
234
|
+
previous: WorkflowSource,
|
|
235
|
+
current: WorkflowSource,
|
|
236
|
+
): Error {
|
|
237
|
+
if (
|
|
238
|
+
previous.kind === "builtin" &&
|
|
239
|
+
current.kind === "builtin" &&
|
|
240
|
+
previous.id === current.id &&
|
|
241
|
+
previous.revision !== current.revision
|
|
242
|
+
) {
|
|
243
|
+
return new BuiltinWorkflowRevisionChangedError({
|
|
244
|
+
runId,
|
|
245
|
+
workflowId: current.id,
|
|
246
|
+
previousRevision: previous.revision,
|
|
247
|
+
currentRevision: current.revision,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return new Error(
|
|
251
|
+
`Workflow source changed since run ${runId} started; revert the edit to answer its checkpoint`,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
240
255
|
function preparedLaunchOptions(options: StartRunOptions): PreparedLaunchOptions {
|
|
241
256
|
return {
|
|
242
257
|
...(options.presentation !== undefined ? { presentation: options.presentation } : {}),
|
|
@@ -258,7 +273,7 @@ function safeLaunchError(error: unknown): { code: string; message: string } {
|
|
|
258
273
|
.replace(/(token|api[_-]?key|secret|password)(\s*[:=]\s*)\S+/giu, "$1$2[redacted]")
|
|
259
274
|
.replaceAll("\n", " ")
|
|
260
275
|
.trim();
|
|
261
|
-
const code = /not found|cannot find|unknown workflow/iu.test(raw)
|
|
276
|
+
const code = /not found|cannot find|unknown workflow|no such file or directory/iu.test(raw)
|
|
262
277
|
? "workflow_not_found"
|
|
263
278
|
: /source changed|source mismatch/iu.test(raw)
|
|
264
279
|
? "source_changed"
|
|
@@ -415,11 +430,29 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
415
430
|
...[...telegramDecisionChannels.values()].map(async (channel) => channel.settle(decision)),
|
|
416
431
|
]);
|
|
417
432
|
};
|
|
418
|
-
const migrationBlockedRuns = new Set<string>();
|
|
419
433
|
const ensureRunQueueStore = (cwd: string): SqliteControllerStore => {
|
|
420
|
-
runQueueStore ??= new SqliteControllerStore(
|
|
434
|
+
runQueueStore ??= new SqliteControllerStore(undefined, { projectPath: cwd });
|
|
421
435
|
return runQueueStore;
|
|
422
436
|
};
|
|
437
|
+
const workflowStore = (
|
|
438
|
+
cwd: string,
|
|
439
|
+
options: Omit<ConstructorParameters<typeof WorkflowRunStore>[1], "state"> = {},
|
|
440
|
+
): WorkflowRunStore => {
|
|
441
|
+
const queue = ensureRunQueueStore(cwd);
|
|
442
|
+
return new WorkflowRunStore(queue.filePath, { ...options, state: queue.state });
|
|
443
|
+
};
|
|
444
|
+
const humanDecisionStore = (
|
|
445
|
+
cwd: string,
|
|
446
|
+
authorityProvider?: NonNullable<
|
|
447
|
+
ConstructorParameters<typeof HumanDecisionStore>[1]
|
|
448
|
+
>["authorityProvider"],
|
|
449
|
+
): HumanDecisionStore => {
|
|
450
|
+
const queue = ensureRunQueueStore(cwd);
|
|
451
|
+
return new HumanDecisionStore(queue.filePath, {
|
|
452
|
+
state: queue.state,
|
|
453
|
+
...(authorityProvider === undefined ? {} : { authorityProvider }),
|
|
454
|
+
});
|
|
455
|
+
};
|
|
423
456
|
|
|
424
457
|
// Session-addressed delivery: each session polls only its durable outbox.
|
|
425
458
|
// Run events remain an audit feed and never enter a conversation.
|
|
@@ -817,7 +850,6 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
817
850
|
workflowViewTarget = {
|
|
818
851
|
runId: state.runId,
|
|
819
852
|
workflowName: state.workflowName,
|
|
820
|
-
runDir: path.resolve(new WorkflowRunStore().runDirFor(state.runId)),
|
|
821
853
|
};
|
|
822
854
|
renderWidget(ctx);
|
|
823
855
|
};
|
|
@@ -1027,12 +1059,16 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1027
1059
|
}
|
|
1028
1060
|
try {
|
|
1029
1061
|
if (outcome === "park") {
|
|
1030
|
-
runQueueStore.parkWorkflowRun({ runId: run.runId, claimToken: run.claimToken })
|
|
1031
|
-
|
|
1032
|
-
|
|
1062
|
+
if (runQueueStore.parkWorkflowRun({ runId: run.runId, claimToken: run.claimToken })) {
|
|
1063
|
+
runQueueStore.settleRunEffect(run.runId, "run.park_queue");
|
|
1064
|
+
}
|
|
1065
|
+
} else if (
|
|
1066
|
+
runQueueStore.completeWorkflowRun({ runId: run.runId, claimToken: run.claimToken })
|
|
1067
|
+
) {
|
|
1068
|
+
runQueueStore.settleRunEffect(run.runId, "run.settle_queue");
|
|
1033
1069
|
}
|
|
1034
1070
|
} catch {
|
|
1035
|
-
// Queue bookkeeping is best-effort next to the durable
|
|
1071
|
+
// Queue bookkeeping is best-effort next to the durable run fact.
|
|
1036
1072
|
} finally {
|
|
1037
1073
|
run.claimToken = undefined;
|
|
1038
1074
|
}
|
|
@@ -1047,7 +1083,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1047
1083
|
activeRun = null;
|
|
1048
1084
|
}
|
|
1049
1085
|
if (result.state.status === "running") {
|
|
1050
|
-
// The run was parked for another runner; its
|
|
1086
|
+
// The run was parked for another runner; its SQLite state stays resumable
|
|
1051
1087
|
// and its queue row becomes claimable. The recorder drains first: its
|
|
1052
1088
|
// finalization writes share the fenced store, so the claim must stay
|
|
1053
1089
|
// valid until capture is complete. Nothing else to present.
|
|
@@ -1074,7 +1110,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1074
1110
|
);
|
|
1075
1111
|
return;
|
|
1076
1112
|
}
|
|
1077
|
-
releaseClaim(run, "done");
|
|
1113
|
+
releaseClaim(run, result.state.status === "waiting" ? "park" : "done");
|
|
1078
1114
|
recordRunEvent({
|
|
1079
1115
|
runId: run.runId,
|
|
1080
1116
|
workflowRef: run.workflowName,
|
|
@@ -1202,7 +1238,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1202
1238
|
// refused continuation (edited source, missing parent) must not consume
|
|
1203
1239
|
// the parent's one-continuation slot.
|
|
1204
1240
|
if (options.parentRunId !== undefined) {
|
|
1205
|
-
const parent =
|
|
1241
|
+
const parent = readWorkflowRun(options.parentRunId);
|
|
1206
1242
|
if (parent === null || parent.state.status !== "waiting") {
|
|
1207
1243
|
throw new Error(`Workflow run ${options.parentRunId} is not waiting at a checkpoint`);
|
|
1208
1244
|
}
|
|
@@ -1210,8 +1246,10 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1210
1246
|
parent.state.workflowSource !== undefined &&
|
|
1211
1247
|
!isDeepStrictEqual(parent.state.workflowSource, workflowSource)
|
|
1212
1248
|
) {
|
|
1213
|
-
throw
|
|
1214
|
-
|
|
1249
|
+
throw continuationSourceChangedError(
|
|
1250
|
+
options.parentRunId,
|
|
1251
|
+
parent.state.workflowSource,
|
|
1252
|
+
workflowSource,
|
|
1215
1253
|
);
|
|
1216
1254
|
}
|
|
1217
1255
|
}
|
|
@@ -1235,6 +1273,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1235
1273
|
: workflowSource.path,
|
|
1236
1274
|
workflowSource: launchSourceIdentity(workflow, workflowSource),
|
|
1237
1275
|
definitionDigest: definitionDigest(snapshot),
|
|
1276
|
+
definitionSnapshot: snapshot,
|
|
1238
1277
|
input,
|
|
1239
1278
|
launchOptions: preparedLaunchOptions(options),
|
|
1240
1279
|
runnerId,
|
|
@@ -1316,16 +1355,20 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1316
1355
|
},
|
|
1317
1356
|
conversation: {
|
|
1318
1357
|
beginAttempt: (contract) => run.recorder?.beginAttempt(contract),
|
|
1358
|
+
recoverAssistant: (contract) =>
|
|
1359
|
+
recoverAssistantStep(ctx.sessionManager.getBranch(), contract),
|
|
1319
1360
|
mark: () => run.recorder?.mark() ?? 0,
|
|
1320
1361
|
rangeSince: (mark) => run.recorder?.rangeSince(mark),
|
|
1321
1362
|
},
|
|
1322
1363
|
});
|
|
1323
|
-
// The
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1364
|
+
// The engine and session recorder share one transactional SQLite store.
|
|
1365
|
+
const store = workflowStore(
|
|
1366
|
+
ctx.cwd,
|
|
1367
|
+
queueStore === null || claimToken === undefined
|
|
1368
|
+
? {}
|
|
1369
|
+
: {
|
|
1370
|
+
authorityProvider: () => queueStore.workflowRunAuthority(runId, claimToken),
|
|
1371
|
+
},
|
|
1329
1372
|
);
|
|
1330
1373
|
const engine = new WorkflowEngine({
|
|
1331
1374
|
executor,
|
|
@@ -1350,8 +1393,8 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1350
1393
|
},
|
|
1351
1394
|
// Awaited by the engine after run_started is persisted, so the session
|
|
1352
1395
|
// binding and its trace event always precede node and terminal events.
|
|
1353
|
-
onRunStarted: async (
|
|
1354
|
-
const recorder = new SessionRecorder(store,
|
|
1396
|
+
onRunStarted: async (runId) => {
|
|
1397
|
+
const recorder = new SessionRecorder(store, runId);
|
|
1355
1398
|
try {
|
|
1356
1399
|
await recorder.bind(ctx);
|
|
1357
1400
|
run.recorder = recorder;
|
|
@@ -1451,23 +1494,22 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1451
1494
|
// once the cause is fixed, and say so in the feed.
|
|
1452
1495
|
let resumeFailed = run.resume === true && !isClaimLostError(error);
|
|
1453
1496
|
let terminalStatus: string | undefined;
|
|
1454
|
-
// Re-parking only makes sense while the
|
|
1455
|
-
// A terminal or waiting
|
|
1456
|
-
//
|
|
1457
|
-
// the bundle's real state, not a bogus failure.
|
|
1497
|
+
// Re-parking only makes sense while the run is still resumable.
|
|
1498
|
+
// A terminal or waiting run closes the queue row instead, and the feed
|
|
1499
|
+
// reports the authoritative run state.
|
|
1458
1500
|
if (resumeFailed) {
|
|
1459
1501
|
try {
|
|
1460
|
-
const bundle =
|
|
1502
|
+
const bundle = readWorkflowRun(runId);
|
|
1461
1503
|
if (bundle === null || bundle.state.status !== "running") {
|
|
1462
1504
|
resumeFailed = false;
|
|
1463
1505
|
terminalStatus = bundle?.state.status;
|
|
1464
1506
|
}
|
|
1465
1507
|
} catch {
|
|
1466
|
-
// Unreadable
|
|
1508
|
+
// Unreadable run state closes the row too.
|
|
1467
1509
|
resumeFailed = false;
|
|
1468
1510
|
}
|
|
1469
1511
|
}
|
|
1470
|
-
// A continuation that failed before its
|
|
1512
|
+
// A continuation that failed before its run row exists frees the
|
|
1471
1513
|
// parent's continuation slot instead of consuming it forever.
|
|
1472
1514
|
let continuationSlotFreed = false;
|
|
1473
1515
|
if (
|
|
@@ -1476,7 +1518,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1476
1518
|
!isClaimLostError(error)
|
|
1477
1519
|
) {
|
|
1478
1520
|
try {
|
|
1479
|
-
const bundle =
|
|
1521
|
+
const bundle = readWorkflowRun(runId);
|
|
1480
1522
|
if (bundle === null) {
|
|
1481
1523
|
continuationSlotFreed =
|
|
1482
1524
|
runQueueStore?.deleteWorkflowRun({ runId, claimToken: run.claimToken }) === true;
|
|
@@ -1554,7 +1596,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1554
1596
|
runnerId,
|
|
1555
1597
|
claimToken,
|
|
1556
1598
|
leaseMs: RUN_CLAIM_LEASE_MS,
|
|
1557
|
-
excludeRunIds: [
|
|
1599
|
+
excludeRunIds: [],
|
|
1558
1600
|
sessionId: ctx.sessionManager.getSessionId(),
|
|
1559
1601
|
});
|
|
1560
1602
|
if (claimed === undefined) {
|
|
@@ -1562,7 +1604,11 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1562
1604
|
}
|
|
1563
1605
|
let started: string | undefined;
|
|
1564
1606
|
try {
|
|
1565
|
-
const bundle =
|
|
1607
|
+
const bundle = readWorkflowRun(claimed.runId);
|
|
1608
|
+
if (bundle?.state.status === "waiting") {
|
|
1609
|
+
queueStore.parkWorkflowRun({ runId: claimed.runId, claimToken });
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1566
1612
|
const sourceRef =
|
|
1567
1613
|
bundle?.state.workflowSource === undefined
|
|
1568
1614
|
? claimed.workflowSourceRef
|
|
@@ -1572,7 +1618,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1572
1618
|
const launchOptions = parsePreparedLaunchOptions(claimed.launchOptions);
|
|
1573
1619
|
started = await startRun(ctx, sourceRef, claimed.input, {
|
|
1574
1620
|
...launchOptions,
|
|
1575
|
-
resume:
|
|
1621
|
+
resume: claimed.initialized,
|
|
1576
1622
|
runId: claimed.runId,
|
|
1577
1623
|
claimToken,
|
|
1578
1624
|
});
|
|
@@ -1600,16 +1646,13 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1600
1646
|
? { state: "running", runId: activeRun.runId }
|
|
1601
1647
|
: workflowSchedulerResult(activeRun.lastState);
|
|
1602
1648
|
}
|
|
1603
|
-
const store =
|
|
1604
|
-
const bundle =
|
|
1649
|
+
const store = workflowStore(controllerContext?.cwd ?? process.cwd());
|
|
1650
|
+
const bundle = store.readRun(request.runId);
|
|
1605
1651
|
if (bundle !== null) {
|
|
1606
1652
|
const recovered =
|
|
1607
1653
|
bundle.state.status === "running" ? await store.markRunInterrupted(request.runId) : bundle;
|
|
1608
1654
|
if (recovered !== null) {
|
|
1609
|
-
const lastTraceEvent =
|
|
1610
|
-
recovered.runDir,
|
|
1611
|
-
recovered.manifest.paths.trace,
|
|
1612
|
-
);
|
|
1655
|
+
const lastTraceEvent = recovered.traceEvents?.at(-1) ?? null;
|
|
1613
1656
|
return workflowSchedulerResult(recovered.state, lastTraceEvent?.type === "run_interrupted");
|
|
1614
1657
|
}
|
|
1615
1658
|
return { state: "pending" };
|
|
@@ -1617,7 +1660,6 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1617
1660
|
if (controllerContext === null) {
|
|
1618
1661
|
return { state: "pending" };
|
|
1619
1662
|
}
|
|
1620
|
-
await store.quarantineIncompleteRun(request.runId);
|
|
1621
1663
|
const runId = await startRun(controllerContext, request.workflow, request.input, {
|
|
1622
1664
|
runId: request.runId,
|
|
1623
1665
|
childKey,
|
|
@@ -1723,11 +1765,17 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1723
1765
|
notify(ctx, result.message, result.level);
|
|
1724
1766
|
};
|
|
1725
1767
|
|
|
1726
|
-
const cancelHumanDecision = async (
|
|
1727
|
-
|
|
1768
|
+
const cancelHumanDecision = async (
|
|
1769
|
+
ctx: ExtensionContext,
|
|
1770
|
+
request: HumanDecisionRequest,
|
|
1771
|
+
): Promise<void> => {
|
|
1772
|
+
const store = humanDecisionStore(ctx.cwd);
|
|
1728
1773
|
await store.cancel(request, "cancelled");
|
|
1729
1774
|
const cancellation = await store.readCancellation(request.decisionId);
|
|
1730
|
-
if (cancellation !== null)
|
|
1775
|
+
if (cancellation !== null) {
|
|
1776
|
+
await settleHumanDecisionChannels(cancellation);
|
|
1777
|
+
store.markEffectApplied(request.decisionId, "decision.settle_presentations");
|
|
1778
|
+
}
|
|
1731
1779
|
lastWaitingRunId = null;
|
|
1732
1780
|
};
|
|
1733
1781
|
|
|
@@ -1744,7 +1792,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1744
1792
|
const continued = new Set(
|
|
1745
1793
|
rows.map((row) => row.parentRunId).filter((parent): parent is string => parent !== null),
|
|
1746
1794
|
);
|
|
1747
|
-
const bundles =
|
|
1795
|
+
const bundles = listWorkflowRuns();
|
|
1748
1796
|
for (const bundle of bundles) {
|
|
1749
1797
|
if (
|
|
1750
1798
|
bundle.state.status !== "waiting" ||
|
|
@@ -1810,7 +1858,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1810
1858
|
const { state } = widgetSource;
|
|
1811
1859
|
const request = humanDecisionRequest(state.finalOutput);
|
|
1812
1860
|
if (state.status === "waiting" && request !== null) {
|
|
1813
|
-
await cancelHumanDecision(request);
|
|
1861
|
+
await cancelHumanDecision(ctx, request);
|
|
1814
1862
|
clearWidgetTimer();
|
|
1815
1863
|
clearWidget(ctx);
|
|
1816
1864
|
return {
|
|
@@ -1831,7 +1879,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1831
1879
|
}
|
|
1832
1880
|
const recovered = await findOwnedWaitingHumanDecision(ctx);
|
|
1833
1881
|
if (recovered !== null) {
|
|
1834
|
-
await cancelHumanDecision(recovered.request);
|
|
1882
|
+
await cancelHumanDecision(ctx, recovered.request);
|
|
1835
1883
|
return {
|
|
1836
1884
|
message: `Cancelled the pending human decision for workflow ${recovered.state.workflowName}.`,
|
|
1837
1885
|
details: {
|
|
@@ -1936,9 +1984,15 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1936
1984
|
runId?: string,
|
|
1937
1985
|
): Promise<WorkflowControlResult> => {
|
|
1938
1986
|
if (runId !== undefined) {
|
|
1939
|
-
const
|
|
1987
|
+
const launch = ensureRunQueueStore(ctx.cwd).getWorkflowRun(runId);
|
|
1988
|
+
if (
|
|
1989
|
+
launch !== undefined &&
|
|
1990
|
+
["queued", "starting", "failed", "cancelled"].includes(launch.status)
|
|
1991
|
+
) {
|
|
1992
|
+
return workflowLaunchStatus(launch);
|
|
1993
|
+
}
|
|
1994
|
+
const bundle = readWorkflowRun(runId);
|
|
1940
1995
|
if (bundle === null) {
|
|
1941
|
-
const launch = ensureRunQueueStore(ctx.cwd).getWorkflowRun(runId);
|
|
1942
1996
|
if (launch === undefined) throw new Error(`Workflow run not found: ${runId}`);
|
|
1943
1997
|
return workflowLaunchStatus(launch);
|
|
1944
1998
|
}
|
|
@@ -1985,7 +2039,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
1985
2039
|
const continued = new Set(
|
|
1986
2040
|
rows.map((row) => row.parentRunId).filter((parent): parent is string => parent !== null),
|
|
1987
2041
|
);
|
|
1988
|
-
const bundles =
|
|
2042
|
+
const bundles = listWorkflowRuns();
|
|
1989
2043
|
parentRunId =
|
|
1990
2044
|
bundles.find(
|
|
1991
2045
|
(bundle) =>
|
|
@@ -2006,7 +2060,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2006
2060
|
) {
|
|
2007
2061
|
throw new Error(`Workflow run ${parentRunId} belongs to another Pi session.`);
|
|
2008
2062
|
}
|
|
2009
|
-
const parent =
|
|
2063
|
+
const parent = readWorkflowRun(parentRunId);
|
|
2010
2064
|
if (
|
|
2011
2065
|
parent === null ||
|
|
2012
2066
|
parent.state.status !== "waiting" ||
|
|
@@ -2033,7 +2087,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2033
2087
|
verified?: HumanDecisionChannelAnswer,
|
|
2034
2088
|
): Promise<WorkflowControlResult> => {
|
|
2035
2089
|
const waiting = await resolveWaitingWorkflow(ctx, requestedRunId, verified !== undefined);
|
|
2036
|
-
const parent =
|
|
2090
|
+
const parent = readWorkflowRun(waiting.parentRunId);
|
|
2037
2091
|
if (parent === null) throw new Error(`Workflow run ${waiting.parentRunId} is unreadable.`);
|
|
2038
2092
|
const request = humanDecisionRequest(parent.state.finalOutput);
|
|
2039
2093
|
let accepted: AcceptedHumanDecision | undefined;
|
|
@@ -2059,9 +2113,9 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2059
2113
|
},
|
|
2060
2114
|
idempotencyKey: verified?.idempotencyKey ?? createHumanDecisionAttemptId(),
|
|
2061
2115
|
};
|
|
2062
|
-
const store =
|
|
2116
|
+
const store = humanDecisionStore(ctx.cwd);
|
|
2063
2117
|
const acceptance = await store.accept(request, submission);
|
|
2064
|
-
if (acceptance.status === "conflict") {
|
|
2118
|
+
if (acceptance.status === "conflict" || acceptance.decision.provenance !== "human") {
|
|
2065
2119
|
throw new Error("That human decision was already answered differently.");
|
|
2066
2120
|
}
|
|
2067
2121
|
accepted = acceptance.decision;
|
|
@@ -2087,21 +2141,9 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2087
2141
|
};
|
|
2088
2142
|
}
|
|
2089
2143
|
}
|
|
2090
|
-
const decisionStore =
|
|
2091
|
-
if (request !== null && continuationRunId !== undefined && accepted !== undefined) {
|
|
2092
|
-
await decisionStore.recordContinuation(request.decisionId, {
|
|
2093
|
-
schema: "pi-workflows.human-decision-continuation.v1",
|
|
2094
|
-
decisionId: request.decisionId,
|
|
2095
|
-
requestDigest: request.requestDigest,
|
|
2096
|
-
parentRunId: waiting.parentRunId,
|
|
2097
|
-
runId: continuationRunId,
|
|
2098
|
-
createdAt: accepted.acceptedAt,
|
|
2099
|
-
});
|
|
2100
|
-
}
|
|
2144
|
+
const decisionStore = humanDecisionStore(ctx.cwd);
|
|
2101
2145
|
if (continuationRunId !== undefined) {
|
|
2102
|
-
const existingContinuation =
|
|
2103
|
-
new WorkflowRunStore().runDirFor(continuationRunId),
|
|
2104
|
-
);
|
|
2146
|
+
const existingContinuation = readWorkflowRun(continuationRunId);
|
|
2105
2147
|
if (existingContinuation !== null) {
|
|
2106
2148
|
if (accepted !== undefined) await settleHumanDecisionChannels(accepted);
|
|
2107
2149
|
lastWaitingRunId = null;
|
|
@@ -2125,7 +2167,18 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2125
2167
|
throw new Error("Could not start the checkpoint continuation.");
|
|
2126
2168
|
}
|
|
2127
2169
|
if (request !== null && accepted !== undefined) {
|
|
2170
|
+
await decisionStore.recordContinuation(request.decisionId, {
|
|
2171
|
+
schema: "pi-workflows.human-decision-continuation.v1",
|
|
2172
|
+
decisionId: request.decisionId,
|
|
2173
|
+
requestDigest: request.requestDigest,
|
|
2174
|
+
provenance: accepted.provenance,
|
|
2175
|
+
parentRunId: waiting.parentRunId,
|
|
2176
|
+
runId: continued,
|
|
2177
|
+
createdAt: accepted.acceptedAt,
|
|
2178
|
+
});
|
|
2179
|
+
decisionStore.markEffectApplied(request.decisionId, "decision.continue");
|
|
2128
2180
|
await settleHumanDecisionChannels(accepted);
|
|
2181
|
+
decisionStore.markEffectApplied(request.decisionId, "decision.settle_presentations");
|
|
2129
2182
|
}
|
|
2130
2183
|
lastWaitingRunId = null;
|
|
2131
2184
|
return {
|
|
@@ -2146,7 +2199,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2146
2199
|
const channel = new PiDecisionChannel({
|
|
2147
2200
|
actorId: ctx.sessionManager.getSessionId(),
|
|
2148
2201
|
ui: ctx.ui,
|
|
2149
|
-
store:
|
|
2202
|
+
store: humanDecisionStore(ctx.cwd),
|
|
2150
2203
|
onAnswer: async (answer) => {
|
|
2151
2204
|
const result = await answerWorkflowControl(ctx, answer.response, request.runId, answer);
|
|
2152
2205
|
notify(ctx, result.message, result.level);
|
|
@@ -2183,7 +2236,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2183
2236
|
config: loaded.channels,
|
|
2184
2237
|
credentials: loaded.credentials,
|
|
2185
2238
|
configDir: loaded.configDir,
|
|
2186
|
-
store:
|
|
2239
|
+
store: humanDecisionStore(ctx.cwd),
|
|
2187
2240
|
onAnswer: async (answer) => {
|
|
2188
2241
|
await answerWorkflowControl(ctx, answer.response, answer.request.runId, answer);
|
|
2189
2242
|
},
|
|
@@ -2197,13 +2250,13 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2197
2250
|
ctx: ExtensionContext,
|
|
2198
2251
|
deliverPending = true,
|
|
2199
2252
|
): Promise<void> => {
|
|
2200
|
-
const runStore =
|
|
2201
|
-
const store =
|
|
2253
|
+
const runStore = workflowStore(ctx.cwd);
|
|
2254
|
+
const store = humanDecisionStore(ctx.cwd);
|
|
2202
2255
|
const requests = (await store.listRequests()).sort((left, right) =>
|
|
2203
2256
|
left.createdAt.localeCompare(right.createdAt),
|
|
2204
2257
|
);
|
|
2205
2258
|
for (const request of requests) {
|
|
2206
|
-
const parent =
|
|
2259
|
+
const parent = runStore.readRun(request.runId);
|
|
2207
2260
|
if (parent === null || parent.state.status !== "waiting") continue;
|
|
2208
2261
|
const currentRequest = humanDecisionRequest(parent.state.finalOutput);
|
|
2209
2262
|
if (
|
|
@@ -2218,60 +2271,110 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2218
2271
|
queueRecord !== undefined &&
|
|
2219
2272
|
(queueRecord.originSessionId === null ||
|
|
2220
2273
|
queueRecord.originSessionId === ctx.sessionManager.getSessionId());
|
|
2221
|
-
|
|
2222
|
-
|
|
2274
|
+
if (!ownedBySession) continue;
|
|
2275
|
+
const recoveryToken = randomUUID();
|
|
2276
|
+
const recoveryQueue = ensureRunQueueStore(ctx.cwd);
|
|
2277
|
+
const claimed = recoveryQueue.claimWorkflowRun({
|
|
2278
|
+
runId: request.runId,
|
|
2279
|
+
runnerId: ctx.sessionManager.getSessionId(),
|
|
2280
|
+
claimToken: recoveryToken,
|
|
2281
|
+
leaseMs: RUN_CLAIM_LEASE_MS,
|
|
2282
|
+
});
|
|
2283
|
+
if (claimed === undefined) continue;
|
|
2284
|
+
const ownerStore = humanDecisionStore(ctx.cwd, () =>
|
|
2285
|
+
recoveryQueue.workflowRunAuthority(request.runId, recoveryToken),
|
|
2286
|
+
);
|
|
2287
|
+
let resolved = await store.readResolved(request.decisionId);
|
|
2288
|
+
if (resolved === null) {
|
|
2223
2289
|
let cancellation = await store.readCancellation(request.decisionId);
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2290
|
+
const expired =
|
|
2291
|
+
request.expiresAt !== undefined && Date.parse(request.expiresAt) <= Date.now();
|
|
2292
|
+
if (cancellation === null && expired) {
|
|
2293
|
+
if (request.defaultResponse === undefined) {
|
|
2294
|
+
await ownerStore.cancel(request, "expired");
|
|
2295
|
+
cancellation = await ownerStore.readCancellation(request.decisionId);
|
|
2296
|
+
} else {
|
|
2297
|
+
try {
|
|
2298
|
+
resolved = (await ownerStore.resolveTimeout(request)).decision;
|
|
2299
|
+
} catch {
|
|
2300
|
+
cancellation = await store.readCancellation(request.decisionId);
|
|
2301
|
+
resolved = await store.readResolved(request.decisionId);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2231
2304
|
}
|
|
2232
2305
|
if (cancellation !== null) {
|
|
2306
|
+
recoveryQueue.completeWorkflowRun({ runId: request.runId, claimToken: recoveryToken });
|
|
2307
|
+
ownerStore.markEffectApplied(request.decisionId, "decision.cancel_parent");
|
|
2233
2308
|
await settleHumanDecisionChannels(cancellation);
|
|
2309
|
+
ownerStore.markEffectApplied(request.decisionId, "decision.settle_presentations");
|
|
2310
|
+
ownerStore.close();
|
|
2234
2311
|
continue;
|
|
2235
2312
|
}
|
|
2236
|
-
if (
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2313
|
+
if (resolved === null) {
|
|
2314
|
+
if (!deliverPending) {
|
|
2315
|
+
recoveryQueue.parkWorkflowRun({ runId: request.runId, claimToken: recoveryToken });
|
|
2316
|
+
ownerStore.close();
|
|
2317
|
+
continue;
|
|
2318
|
+
}
|
|
2319
|
+
const channels = audienceChannels(decisionChannelConfig, request.audience);
|
|
2320
|
+
for (const channelId of channels) {
|
|
2321
|
+
const channel = telegramDecisionChannels.get(channelId);
|
|
2322
|
+
if (channel !== undefined) await channel.deliver(humanDecisionChannelRequest(request));
|
|
2323
|
+
}
|
|
2324
|
+
if (ctx.mode === "tui" && channels.includes("pi") && lastWaitingRunId === null) {
|
|
2325
|
+
lastWaitingRunId = request.runId;
|
|
2326
|
+
queueMicrotask(() => void promptHumanDecision(ctx, request));
|
|
2327
|
+
}
|
|
2328
|
+
recoveryQueue.parkWorkflowRun({ runId: request.runId, claimToken: recoveryToken });
|
|
2329
|
+
ownerStore.close();
|
|
2330
|
+
continue;
|
|
2245
2331
|
}
|
|
2246
|
-
continue;
|
|
2247
2332
|
}
|
|
2248
2333
|
|
|
2249
|
-
|
|
2334
|
+
const currentParent = runStore.readRun(request.runId);
|
|
2335
|
+
if (currentParent === null || currentParent.state.status !== "waiting") {
|
|
2336
|
+
recoveryQueue.parkWorkflowRun({ runId: request.runId, claimToken: recoveryToken });
|
|
2337
|
+
ownerStore.close();
|
|
2338
|
+
continue;
|
|
2339
|
+
}
|
|
2250
2340
|
const runId = `continuation-${request.decisionId.slice("decision-".length)}`;
|
|
2251
2341
|
const continuation = (await store.readContinuation(request.decisionId)) ?? {
|
|
2252
2342
|
schema: "pi-workflows.human-decision-continuation.v1" as const,
|
|
2253
2343
|
decisionId: request.decisionId,
|
|
2254
2344
|
requestDigest: request.requestDigest,
|
|
2345
|
+
provenance: resolved.provenance,
|
|
2255
2346
|
parentRunId: request.runId,
|
|
2256
2347
|
runId,
|
|
2257
|
-
createdAt:
|
|
2348
|
+
createdAt: resolved.acceptedAt,
|
|
2258
2349
|
};
|
|
2259
|
-
|
|
2260
|
-
|
|
2350
|
+
recoveryQueue.parkWorkflowRun({
|
|
2351
|
+
runId: request.runId,
|
|
2352
|
+
claimToken: recoveryToken,
|
|
2353
|
+
});
|
|
2354
|
+
const existing = runStore.readRun(continuation.runId);
|
|
2261
2355
|
if (existing === null && activeRun === null) {
|
|
2262
|
-
if (
|
|
2356
|
+
if (currentParent.state.workflowSource === undefined) {
|
|
2357
|
+
ownerStore.close();
|
|
2358
|
+
continue;
|
|
2359
|
+
}
|
|
2263
2360
|
const workflowRef =
|
|
2264
|
-
|
|
2265
|
-
? `builtin:${
|
|
2266
|
-
:
|
|
2267
|
-
await startRun(ctx, workflowRef,
|
|
2361
|
+
currentParent.state.workflowSource.kind === "builtin"
|
|
2362
|
+
? `builtin:${currentParent.state.workflowSource.id}`
|
|
2363
|
+
: currentParent.state.workflowSource.path;
|
|
2364
|
+
await startRun(ctx, workflowRef, currentParent.state.input, {
|
|
2268
2365
|
parentRunId: request.runId,
|
|
2269
|
-
humanDecision:
|
|
2366
|
+
humanDecision: resolved,
|
|
2270
2367
|
runId: continuation.runId,
|
|
2271
2368
|
quiet: true,
|
|
2272
2369
|
});
|
|
2273
2370
|
}
|
|
2274
|
-
|
|
2371
|
+
if (runStore.readRun(continuation.runId) !== null) {
|
|
2372
|
+
await ownerStore.recordContinuation(request.decisionId, continuation);
|
|
2373
|
+
ownerStore.markEffectApplied(request.decisionId, "decision.continue");
|
|
2374
|
+
}
|
|
2375
|
+
await settleHumanDecisionChannels(resolved);
|
|
2376
|
+
ownerStore.markEffectApplied(request.decisionId, "decision.settle_presentations");
|
|
2377
|
+
ownerStore.close();
|
|
2275
2378
|
if (activeRun !== null) break;
|
|
2276
2379
|
}
|
|
2277
2380
|
};
|
|
@@ -2332,7 +2435,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2332
2435
|
}
|
|
2333
2436
|
const queue = ensureRunQueueStore(ctx.cwd);
|
|
2334
2437
|
const existing = queue.findSessionReservation(ctx.sessionManager.getSessionId());
|
|
2335
|
-
if (existing !== undefined) {
|
|
2438
|
+
if (existing !== undefined && existing.runId !== options.parentRunId) {
|
|
2336
2439
|
throw new Error(
|
|
2337
2440
|
`Workflow ${existing.workflowName} is already ${existing.status} (run ${existing.runId}).`,
|
|
2338
2441
|
);
|
|
@@ -2344,7 +2447,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2344
2447
|
const workflow = resolved.definition;
|
|
2345
2448
|
const workflowSource = resolved.source;
|
|
2346
2449
|
if (options.parentRunId !== undefined) {
|
|
2347
|
-
const parent =
|
|
2450
|
+
const parent = readWorkflowRun(options.parentRunId);
|
|
2348
2451
|
if (parent === null || parent.state.status !== "waiting") {
|
|
2349
2452
|
throw new Error(`Workflow run ${options.parentRunId} is not waiting at a checkpoint`);
|
|
2350
2453
|
}
|
|
@@ -2352,8 +2455,10 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2352
2455
|
parent.state.workflowSource !== undefined &&
|
|
2353
2456
|
!isDeepStrictEqual(parent.state.workflowSource, workflowSource)
|
|
2354
2457
|
) {
|
|
2355
|
-
throw
|
|
2356
|
-
|
|
2458
|
+
throw continuationSourceChangedError(
|
|
2459
|
+
options.parentRunId,
|
|
2460
|
+
parent.state.workflowSource,
|
|
2461
|
+
workflowSource,
|
|
2357
2462
|
);
|
|
2358
2463
|
}
|
|
2359
2464
|
}
|
|
@@ -2367,6 +2472,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2367
2472
|
workflowSource.kind === "builtin" ? `builtin:${workflowSource.id}` : workflowSource.path,
|
|
2368
2473
|
workflowSource: launchSourceIdentity(workflow, workflowSource),
|
|
2369
2474
|
definitionDigest: definitionDigest(snapshot),
|
|
2475
|
+
definitionSnapshot: snapshot,
|
|
2370
2476
|
input,
|
|
2371
2477
|
launchOptions: preparedLaunchOptions(options),
|
|
2372
2478
|
runnerId,
|
|
@@ -2575,7 +2681,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2575
2681
|
const message = errorMessage(error);
|
|
2576
2682
|
notify(
|
|
2577
2683
|
ctx,
|
|
2578
|
-
/
|
|
2684
|
+
/continuations\.parent_run_id/.test(message)
|
|
2579
2685
|
? "That checkpoint was already answered; see its continuation run."
|
|
2580
2686
|
: `Could not continue workflow: ${message}`,
|
|
2581
2687
|
"error",
|
|
@@ -2730,9 +2836,10 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2730
2836
|
label: "Workflow",
|
|
2731
2837
|
description: [
|
|
2732
2838
|
"List, start, inspect, pause, resume, cancel, answer, update, or complete pi-workflows runs.",
|
|
2733
|
-
"When the user asks to monitor, watch, poll, or check something repeatedly, start the built-in monitor workflow with input keys task,
|
|
2839
|
+
"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.",
|
|
2840
|
+
"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.",
|
|
2734
2841
|
"Use update or submit only when a workflow step contract asks for it, and pass the exact step and attempt ids.",
|
|
2735
|
-
"Do not start repeated work without the user's request
|
|
2842
|
+
"Do not start repeated work without the user's request.",
|
|
2736
2843
|
].join(" "),
|
|
2737
2844
|
parameters: WorkflowToolParameters,
|
|
2738
2845
|
async execute(toolCallId, rawParams, _signal, _onUpdate, ctx) {
|
|
@@ -2759,7 +2866,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2759
2866
|
break;
|
|
2760
2867
|
case "answer": {
|
|
2761
2868
|
const waiting = await resolveWaitingWorkflow(ctx, params.runId);
|
|
2762
|
-
const parent =
|
|
2869
|
+
const parent = readWorkflowRun(waiting.parentRunId);
|
|
2763
2870
|
if (parent !== null && humanDecisionRequest(parent.state.finalOutput) !== null) {
|
|
2764
2871
|
throw new Error(
|
|
2765
2872
|
"This checkpoint requires a verified human answer from Pi UI or a configured decision channel.",
|
|
@@ -2797,7 +2904,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2797
2904
|
}
|
|
2798
2905
|
throw new Error("No workflow step is waiting for output.");
|
|
2799
2906
|
}
|
|
2800
|
-
// Flush the conversation into
|
|
2907
|
+
// Flush the conversation into SQLite before accepting, so the
|
|
2801
2908
|
// attempt range includes the assistant message carrying this call.
|
|
2802
2909
|
await activeRun.recorder?.record(ctx).catch(() => undefined);
|
|
2803
2910
|
await activeRun.recorder?.synchronize(ctx).catch(() => undefined);
|
|
@@ -2844,24 +2951,7 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2844
2951
|
sessionClosed = false;
|
|
2845
2952
|
controllerContext = ctx;
|
|
2846
2953
|
if (herdrEnabled) void refreshHerdrCapability(ctx);
|
|
2847
|
-
|
|
2848
|
-
const queue = ensureRunQueueStore(ctx.cwd);
|
|
2849
|
-
const migration = await migrateLegacyWorkflowSources({
|
|
2850
|
-
catalog: builtinWorkflowCatalog,
|
|
2851
|
-
queue,
|
|
2852
|
-
});
|
|
2853
|
-
migrationBlockedRuns.clear();
|
|
2854
|
-
for (const blocked of migration.blocked) migrationBlockedRuns.add(blocked.runId);
|
|
2855
|
-
if (migration.blocked.length > 0) {
|
|
2856
|
-
notify(
|
|
2857
|
-
ctx,
|
|
2858
|
-
`Could not migrate ${migration.blocked.length} legacy workflow source(s).`,
|
|
2859
|
-
"warning",
|
|
2860
|
-
);
|
|
2861
|
-
}
|
|
2862
|
-
} catch (error) {
|
|
2863
|
-
notify(ctx, `Could not migrate legacy workflow sources: ${errorMessage(error)}`, "warning");
|
|
2864
|
-
}
|
|
2954
|
+
ensureRunQueueStore(ctx.cwd);
|
|
2865
2955
|
try {
|
|
2866
2956
|
await reloadDecisionChannels(ctx);
|
|
2867
2957
|
} catch {
|
|
@@ -2953,6 +3043,9 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
2953
3043
|
|
|
2954
3044
|
pi.on("turn_end", async (event, ctx) => {
|
|
2955
3045
|
await activeRun?.recorder?.handleTurnEnd(event, ctx).catch(() => undefined);
|
|
3046
|
+
// turn_end observes the message after message_end replacement handlers,
|
|
3047
|
+
// so this is the exact assistant message that remains visible.
|
|
3048
|
+
activeRun?.executor.handleMessageEnd(event.message);
|
|
2956
3049
|
});
|
|
2957
3050
|
|
|
2958
3051
|
pi.on("message_start", async (event, ctx) => {
|
|
@@ -3007,11 +3100,9 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
3007
3100
|
return;
|
|
3008
3101
|
}
|
|
3009
3102
|
}
|
|
3010
|
-
const flushedNatural = turnCoordinator.flushNatural(
|
|
3011
|
-
ctx.isIdle() && !sessionClosed && !runHeld(),
|
|
3012
|
-
);
|
|
3013
3103
|
const run = activeRun;
|
|
3014
3104
|
if (!run) {
|
|
3105
|
+
turnCoordinator.flushNatural(ctx.isIdle() && !sessionClosed && !runHeld());
|
|
3015
3106
|
presentationPending = null;
|
|
3016
3107
|
runSyncPass(ctx);
|
|
3017
3108
|
return;
|
|
@@ -3019,7 +3110,16 @@ export default function piWorkflows(pi: ExtensionAPI) {
|
|
|
3019
3110
|
await run.recorder?.synchronize(ctx).catch(() => undefined);
|
|
3020
3111
|
run.recorder?.settleAttempt();
|
|
3021
3112
|
run.executor.setStreaming(false);
|
|
3022
|
-
if (
|
|
3113
|
+
if (run.executor.pendingCompletion === "assistant") {
|
|
3114
|
+
// The visible response is the step output. Resolve it before any
|
|
3115
|
+
// unrelated deferred turn can start and replace the captured message.
|
|
3116
|
+
run.executor.handleAgentSettled();
|
|
3117
|
+
} else {
|
|
3118
|
+
const flushedNatural = turnCoordinator.flushNatural(
|
|
3119
|
+
ctx.isIdle() && !sessionClosed && !runHeld(),
|
|
3120
|
+
);
|
|
3121
|
+
if (flushedNatural === 0) run.executor.handleAgentSettled();
|
|
3122
|
+
}
|
|
3023
3123
|
runSyncPass(ctx);
|
|
3024
3124
|
});
|
|
3025
3125
|
|