@osolmaz/pi-workflows 0.13.1 → 0.13.3
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 +9 -7
- package/dist/builtins/autodoc.workflow.d.ts +5 -5
- package/dist/builtins/autoimplement.workflow.d.ts +69 -61
- package/dist/builtins/autoimplement.workflow.js +47 -8
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/autoplan.workflow.d.ts +11 -5
- package/dist/builtins/autoplan.workflow.js +68 -32
- package/dist/builtins/autoplan.workflow.js.map +1 -1
- package/dist/builtins/catalog.js +4 -4
- package/dist/builtins/change-verification.workflow.d.ts +1 -1
- package/dist/builtins/plain-summary.workflow.d.ts +1 -1
- package/dist/builtins/plain-summary.workflow.js +35 -24
- package/dist/builtins/plain-summary.workflow.js.map +1 -1
- package/dist/builtins/plan-approval.workflow.d.ts +1 -1
- package/dist/builtins/plan-change.workflow.d.ts +25 -23
- package/dist/builtins/sanity-check.workflow.d.ts +3 -3
- package/dist/builtins/sanity-check.workflow.js +19 -22
- package/dist/builtins/sanity-check.workflow.js.map +1 -1
- package/dist/builtins/workspace-preparation.workflow.d.ts +1 -1
- package/dist/controllers/index.d.ts +2 -2
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/sqlite.js +88 -61
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/controllers/types.d.ts +34 -0
- package/dist/controllers/workflow-engine-scheduler.d.ts +5 -1
- package/dist/controllers/workflow-engine-scheduler.js +83 -1
- package/dist/controllers/workflow-engine-scheduler.js.map +1 -1
- package/dist/controllers/workflows.d.ts +8 -1
- package/dist/controllers/workflows.js +36 -0
- package/dist/controllers/workflows.js.map +1 -1
- package/dist/extension/controller-host.d.ts +2 -1
- package/dist/extension/controller-host.js +1 -1
- package/dist/extension/controller-host.js.map +1 -1
- package/dist/extension/decision-channels.js +45 -7
- package/dist/extension/decision-channels.js.map +1 -1
- package/dist/extension/follow-up-coordinator.d.ts +27 -0
- package/dist/extension/follow-up-coordinator.js +131 -0
- package/dist/extension/follow-up-coordinator.js.map +1 -0
- package/dist/extension/index.d.ts +9 -0
- package/dist/extension/index.js +462 -76
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +1 -1
- package/dist/extension/recorder.js +8 -8
- package/dist/extension/recorder.js.map +1 -1
- package/dist/extension/session-events.d.ts +2 -3
- package/dist/extension/session-events.js +11 -10
- package/dist/extension/session-events.js.map +1 -1
- package/dist/extension/widget.js +9 -0
- package/dist/extension/widget.js.map +1 -1
- package/dist/state/database.d.ts +2 -1
- package/dist/state/database.js +11 -13
- package/dist/state/database.js.map +1 -1
- package/dist/state/json.js +6 -1
- package/dist/state/json.js.map +1 -1
- package/dist/state/mutation.d.ts +1 -1
- package/dist/state/mutation.js.map +1 -1
- package/dist/state/prune.d.ts +18 -0
- package/dist/state/prune.js +373 -0
- package/dist/state/prune.js.map +1 -0
- package/dist/state/schema.d.ts +1 -1
- package/dist/state/schema.js +130 -13
- package/dist/state/schema.js.map +1 -1
- package/dist/viewer/cli.d.ts +3 -1
- package/dist/viewer/cli.js +55 -4
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/render.js +19 -1
- package/dist/viewer/render.js.map +1 -1
- package/dist/viewer/session-reducer.d.ts +0 -1
- package/dist/viewer/session-reducer.js +2 -24
- package/dist/viewer/session-reducer.js.map +1 -1
- package/dist/workflows/composition.d.ts +2 -0
- package/dist/workflows/composition.js +15 -0
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/definition.d.ts +6 -3
- package/dist/workflows/definition.js +3 -0
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.d.ts +6 -1
- package/dist/workflows/engine.js +303 -36
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +1 -0
- package/dist/workflows/human-decision.js +25 -9
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +3 -1
- package/dist/workflows/index.js +2 -0
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/json-patch.d.ts +47 -0
- package/dist/workflows/json-patch.js +298 -0
- package/dist/workflows/json-patch.js.map +1 -0
- package/dist/workflows/schema.js +29 -1
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/settings.d.ts +128 -0
- package/dist/workflows/settings.js +199 -0
- package/dist/workflows/settings.js.map +1 -0
- package/dist/workflows/store.d.ts +81 -3
- package/dist/workflows/store.js +1563 -50
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +22 -0
- package/dist/workflows/tool-input.js +43 -0
- package/dist/workflows/tool-input.js.map +1 -1
- package/dist/workflows/types.d.ts +41 -6
- package/docs/2026-08-25-workflow-follow-ups.md +132 -0
- package/docs/2026-08-25-workflow-settings.md +169 -0
- package/docs/DEFERRED_TURNS.md +6 -0
- package/docs/DESIGN_PHILOSOPHY.md +2 -0
- package/docs/SQLITE_STATE.md +36 -17
- package/docs/WORKFLOW_COMPOSITION.md +15 -0
- package/docs/WORKFLOW_STEP_MESSAGES.md +4 -2
- package/docs/plans/2026-08-25-autoplan-user-intent-capture-plan.md +107 -0
- package/docs/plans/2026-08-25-live-workflow-settings-plan.md +532 -0
- package/docs/plans/2026-08-25-workflow-run-storage-plan.md +67 -0
- package/docs/session-event-journal.md +2 -3
- package/docs/tui-viewer.md +7 -6
- package/docs/workflows.md +58 -10
- package/examples/workflows/live-settings.workflow.ts +93 -0
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/src/builtins/autoimplement.workflow.ts +56 -8
- package/src/builtins/autoplan.workflow.ts +80 -32
- package/src/builtins/catalog.ts +4 -4
- package/src/builtins/plain-summary.workflow.ts +38 -40
- package/src/builtins/sanity-check.workflow.ts +19 -22
- package/src/controllers/index.ts +6 -0
- package/src/controllers/sqlite.ts +132 -72
- package/src/controllers/types.ts +40 -0
- package/src/controllers/workflow-engine-scheduler.ts +103 -1
- package/src/controllers/workflows.ts +68 -0
- package/src/extension/controller-host.ts +6 -1
- package/src/extension/decision-channels.ts +47 -7
- package/src/extension/follow-up-coordinator.ts +151 -0
- package/src/extension/index.ts +540 -89
- package/src/extension/recorder.ts +10 -8
- package/src/extension/session-events.ts +15 -13
- package/src/extension/widget.ts +8 -0
- package/src/state/database.ts +12 -12
- package/src/state/json.ts +6 -1
- package/src/state/mutation.ts +4 -1
- package/src/state/prune.ts +502 -0
- package/src/state/schema.ts +130 -13
- package/src/viewer/cli.ts +52 -5
- package/src/viewer/render.ts +43 -1
- package/src/viewer/session-reducer.ts +2 -29
- package/src/workflows/composition.ts +19 -0
- package/src/workflows/definition.ts +19 -5
- package/src/workflows/engine.ts +365 -36
- package/src/workflows/human-decision.ts +41 -11
- package/src/workflows/index.ts +43 -0
- package/src/workflows/json-patch.ts +375 -0
- package/src/workflows/schema.ts +31 -1
- package/src/workflows/settings.ts +430 -0
- package/src/workflows/store.ts +2268 -121
- package/src/workflows/tool-input.ts +58 -0
- package/src/workflows/types.ts +43 -5
package/dist/extension/index.js
CHANGED
|
@@ -2,9 +2,10 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
3
|
import { builtinWorkflowCatalog } from "../builtins/catalog.js";
|
|
4
4
|
import { SqliteControllerStore } from "../controllers/index.js";
|
|
5
|
+
import { canonicalJson } from "../state/json.js";
|
|
5
6
|
import { compositionMetadata } from "../workflows/composition.js";
|
|
6
7
|
import { humanDecisionChannelRequest } from "../workflows/decision-presentation.js";
|
|
7
|
-
import { WorkflowEngine } from "../workflows/engine.js";
|
|
8
|
+
import { WorkflowEngine, workflowIdentityMismatch } from "../workflows/engine.js";
|
|
8
9
|
import { BuiltinWorkflowRevisionChangedError, ClaimLostError, errorMessage, isClaimLostError, TimeoutError, } from "../workflows/errors.js";
|
|
9
10
|
import { HumanDecisionStore, createHumanDecisionAttemptId, validateHumanDecisionResponse, } from "../workflows/human-decision.js";
|
|
10
11
|
import { discoverWorkflows, resolveWorkflowRef } from "../workflows/loader.js";
|
|
@@ -15,6 +16,7 @@ import { PiDecisionChannel, TelegramDecisionChannel, audienceChannels, createTel
|
|
|
15
16
|
import { DeferredTurnCoordinator, } from "./deferred-turn-coordinator.js";
|
|
16
17
|
import { buildDeferredTurnContent, createDeferredTurnDescriptor, DEFERRED_TURN_MESSAGE_TYPE, deferredTurnMessageDetails, deferredTurnMessageId, deferredTurnSourceEventId, } from "./deferred-turn.js";
|
|
17
18
|
import { ConversationStepExecutor } from "./executor.js";
|
|
19
|
+
import { FollowUpCoordinator, findSettledPresentationEntries } from "./follow-up-coordinator.js";
|
|
18
20
|
import { HerdrWorkflowViewer, parseViewerPlacement, PIW_SHORTCUT, PIW_SHORTCUT_HINT, VIEWER_PLACEMENTS, } from "./herdr-viewer.js";
|
|
19
21
|
import { SessionRecorder } from "./recorder.js";
|
|
20
22
|
import { recoverAssistantStep, registerWorkflowAgentStepMessageRenderer, WORKFLOW_AGENT_STEP_MESSAGE_SCHEMA, WORKFLOW_AGENT_STEP_MESSAGE_TYPE, } from "./step-message.js";
|
|
@@ -58,7 +60,7 @@ function humanDecisionRequest(value) {
|
|
|
58
60
|
return value;
|
|
59
61
|
}
|
|
60
62
|
function definitionDigest(snapshot) {
|
|
61
|
-
return `sha256:${createHash("sha256").update(
|
|
63
|
+
return `sha256:${createHash("sha256").update(canonicalJson(snapshot)).digest("hex")}`;
|
|
62
64
|
}
|
|
63
65
|
function launchSourceIdentity(workflow, root) {
|
|
64
66
|
return {
|
|
@@ -121,6 +123,28 @@ export function parseWorkflowArgs(args) {
|
|
|
121
123
|
if (trimmed === "status") {
|
|
122
124
|
return { kind: "status" };
|
|
123
125
|
}
|
|
126
|
+
if (trimmed.startsWith("change-settings ")) {
|
|
127
|
+
const text = trimmed.slice("change-settings".length).trim();
|
|
128
|
+
try {
|
|
129
|
+
return { kind: "change-settings", patch: JSON.parse(text) };
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
throw new Error(`change-settings requires a JSON Patch array: ${error instanceof Error ? error.message : String(error)}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (trimmed.startsWith("queue-follow-up ")) {
|
|
136
|
+
const prompt = trimmed.slice("queue-follow-up".length).trim();
|
|
137
|
+
if (prompt.length === 0)
|
|
138
|
+
throw new Error("queue-follow-up requires a prompt");
|
|
139
|
+
return { kind: "queue-follow-up", prompt };
|
|
140
|
+
}
|
|
141
|
+
if (trimmed.startsWith("remove-follow-up ")) {
|
|
142
|
+
const followUpId = trimmed.slice("remove-follow-up".length).trim();
|
|
143
|
+
if (!/^follow-up-[a-f0-9]{40}$/u.test(followUpId)) {
|
|
144
|
+
throw new Error("remove-follow-up requires one valid follow-up id");
|
|
145
|
+
}
|
|
146
|
+
return { kind: "remove-follow-up", followUpId };
|
|
147
|
+
}
|
|
124
148
|
if (trimmed.startsWith("status ")) {
|
|
125
149
|
const runId = trimmed.slice("status".length).trim();
|
|
126
150
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/.test(runId)) {
|
|
@@ -176,7 +200,7 @@ export function parseWorkflowArgs(args) {
|
|
|
176
200
|
}
|
|
177
201
|
return { kind: "run", ref, input: rest.length > 0 ? { task: rest } : {} };
|
|
178
202
|
}
|
|
179
|
-
function workflowStateSummary(state) {
|
|
203
|
+
function workflowStateSummary(state, controls) {
|
|
180
204
|
const error = state.error === undefined
|
|
181
205
|
? undefined
|
|
182
206
|
: state.error.length <= MAX_STATUS_ERROR_CHARS
|
|
@@ -196,6 +220,32 @@ function workflowStateSummary(state) {
|
|
|
196
220
|
...(state.waitingOn !== undefined ? { waitingOn: state.waitingOn } : {}),
|
|
197
221
|
...(state.startedAt !== undefined ? { startedAt: state.startedAt } : {}),
|
|
198
222
|
...(state.finishedAt !== undefined ? { finishedAt: state.finishedAt } : {}),
|
|
223
|
+
...(controls !== undefined
|
|
224
|
+
? {
|
|
225
|
+
settingsScopes: (controls.settingsScopes ?? []).map((scope) => ({
|
|
226
|
+
scopeId: scope.scopeId,
|
|
227
|
+
mountPath: scope.mountPath,
|
|
228
|
+
invocation: scope.invocation,
|
|
229
|
+
changeNumber: scope.changeNumber,
|
|
230
|
+
settingsHash: scope.settingsHash,
|
|
231
|
+
activeRunId: scope.activeRunId,
|
|
232
|
+
})),
|
|
233
|
+
followUps: controls.followUpQueue === null || controls.followUpQueue === undefined
|
|
234
|
+
? null
|
|
235
|
+
: {
|
|
236
|
+
presentationState: controls.followUpQueue.presentationState,
|
|
237
|
+
items: controls.followUpQueue.followUps.map((followUp) => ({
|
|
238
|
+
followUpId: followUp.followUpId,
|
|
239
|
+
order: followUp.order,
|
|
240
|
+
state: followUp.state,
|
|
241
|
+
source: followUp.source,
|
|
242
|
+
...(followUp.sessionEntryId !== undefined
|
|
243
|
+
? { sessionEntryId: followUp.sessionEntryId }
|
|
244
|
+
: {}),
|
|
245
|
+
})),
|
|
246
|
+
},
|
|
247
|
+
}
|
|
248
|
+
: {}),
|
|
199
249
|
...(error !== undefined ? { error } : {}),
|
|
200
250
|
};
|
|
201
251
|
}
|
|
@@ -313,6 +363,7 @@ export default function piWorkflows(pi) {
|
|
|
313
363
|
}, { triggerTurn: true, deliverAs: "followUp" });
|
|
314
364
|
},
|
|
315
365
|
}, idle);
|
|
366
|
+
void followUpCoordinator?.synchronize(ctx, activeRun !== null).catch(() => undefined);
|
|
316
367
|
}
|
|
317
368
|
catch {
|
|
318
369
|
// Delivery retries on the next poll. It never affects workflow execution.
|
|
@@ -345,6 +396,8 @@ export default function piWorkflows(pi) {
|
|
|
345
396
|
let runGeneration = 0;
|
|
346
397
|
let presentationAbort = null;
|
|
347
398
|
let presentationPending = null;
|
|
399
|
+
let followUpStore = null;
|
|
400
|
+
let followUpCoordinator = null;
|
|
348
401
|
let controllerHost;
|
|
349
402
|
let controllerContext = null;
|
|
350
403
|
const branchIntentResolution = (intentId) => {
|
|
@@ -376,6 +429,38 @@ export default function piWorkflows(pi) {
|
|
|
376
429
|
branchResolution: branchIntentResolution,
|
|
377
430
|
leaseMs: TURN_INTENT_DELIVERY_LEASE_MS,
|
|
378
431
|
});
|
|
432
|
+
const ensureFollowUpDelivery = () => {
|
|
433
|
+
if (followUpStore !== null && followUpCoordinator !== null)
|
|
434
|
+
return;
|
|
435
|
+
followUpStore?.close();
|
|
436
|
+
followUpStore = new WorkflowRunStore();
|
|
437
|
+
followUpCoordinator = new FollowUpCoordinator(followUpStore, `follow-up-${runnerId}`, (text) => pi.sendUserMessage(text));
|
|
438
|
+
};
|
|
439
|
+
const settlePresentationFromBranch = (ctx, runId, unavailableReason) => {
|
|
440
|
+
if (followUpStore === null ||
|
|
441
|
+
followUpStore.readFollowUpQueue(runId)?.presentationState !== "pending") {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
const settled = findSettledPresentationEntries(ctx.sessionManager.getBranch(), runId);
|
|
445
|
+
if (settled !== undefined) {
|
|
446
|
+
followUpStore.settleFollowUpPresentation({
|
|
447
|
+
runId,
|
|
448
|
+
state: "settled",
|
|
449
|
+
presentationEntryId: settled.presentationEntryId,
|
|
450
|
+
assistantEntryId: settled.assistantEntryId,
|
|
451
|
+
});
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
if (unavailableReason !== undefined) {
|
|
455
|
+
followUpStore.settleFollowUpPresentation({
|
|
456
|
+
runId,
|
|
457
|
+
state: "unavailable",
|
|
458
|
+
reason: unavailableReason,
|
|
459
|
+
});
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
return false;
|
|
463
|
+
};
|
|
379
464
|
// UI updates are best-effort: a captured ctx becomes stale after session
|
|
380
465
|
// replacement or shutdown, and pi throws on any access (even `ctx.hasUI`).
|
|
381
466
|
// A workflow finishing right as the session goes away must not crash pi.
|
|
@@ -688,6 +773,7 @@ export default function piWorkflows(pi) {
|
|
|
688
773
|
return;
|
|
689
774
|
}
|
|
690
775
|
if (instructions === undefined || instructions.trim().length === 0) {
|
|
776
|
+
settlePresentationFromBranch(ctx, run.runId, "No result presentation was available");
|
|
691
777
|
if (run.abortProvenance !== undefined) {
|
|
692
778
|
makeRunTurnIntentEligible(ctx, run, state.status, "No result presentation was available");
|
|
693
779
|
}
|
|
@@ -698,13 +784,14 @@ export default function piWorkflows(pi) {
|
|
|
698
784
|
targetSessionId: originSessionId(ctx, run.runId),
|
|
699
785
|
resolution: "presentation",
|
|
700
786
|
send: (turnIntentId) => {
|
|
701
|
-
presentationPending = run.generation;
|
|
787
|
+
presentationPending = { generation: run.generation, runId: run.runId };
|
|
702
788
|
pi.sendMessage({
|
|
703
789
|
customType: PRESENTATION_MESSAGE_TYPE,
|
|
704
790
|
content: buildPresentationMessage(instructions, state),
|
|
705
791
|
display: false,
|
|
706
792
|
details: {
|
|
707
793
|
schema: PRESENTATION_MESSAGE_SCHEMA,
|
|
794
|
+
runId: run.runId,
|
|
708
795
|
...(turnIntentId === undefined ? {} : { turnIntentId }),
|
|
709
796
|
},
|
|
710
797
|
}, {
|
|
@@ -715,7 +802,7 @@ export default function piWorkflows(pi) {
|
|
|
715
802
|
}, ctx.isIdle() && systemTurnAbort === null);
|
|
716
803
|
}
|
|
717
804
|
catch (error) {
|
|
718
|
-
if (presentationPending === run.generation) {
|
|
805
|
+
if (presentationPending?.generation === run.generation) {
|
|
719
806
|
presentationPending = null;
|
|
720
807
|
}
|
|
721
808
|
if (error instanceof PresentationSupersededError ||
|
|
@@ -727,6 +814,7 @@ export default function piWorkflows(pi) {
|
|
|
727
814
|
? `timed out after ${PRESENTATION_TIMEOUT_MS}ms`
|
|
728
815
|
: errorMessage(error);
|
|
729
816
|
notify(ctx, `Could not present workflow result: ${message}`, "warning");
|
|
817
|
+
settlePresentationFromBranch(ctx, run.runId, message);
|
|
730
818
|
if (run.abortProvenance !== undefined) {
|
|
731
819
|
makeRunTurnIntentEligible(ctx, run, state.status, message);
|
|
732
820
|
}
|
|
@@ -875,6 +963,7 @@ export default function piWorkflows(pi) {
|
|
|
875
963
|
if (options.signal?.aborted) {
|
|
876
964
|
throw options.signal.reason ?? new Error("Workflow startup aborted");
|
|
877
965
|
}
|
|
966
|
+
ensureFollowUpDelivery();
|
|
878
967
|
if (activeRun) {
|
|
879
968
|
if (!options.quiet) {
|
|
880
969
|
notify(ctx, `A workflow is already running: ${activeRun.workflowName}. Use /workflow cancel first.`, "error");
|
|
@@ -1309,7 +1398,65 @@ export default function piWorkflows(pi) {
|
|
|
1309
1398
|
if (controllerHost !== undefined) {
|
|
1310
1399
|
return controllerHost;
|
|
1311
1400
|
}
|
|
1312
|
-
controllerHost = await PiControllerHost.create({
|
|
1401
|
+
controllerHost = await PiControllerHost.create({
|
|
1402
|
+
cwd: ctx.cwd,
|
|
1403
|
+
startChild,
|
|
1404
|
+
workflowControls: {
|
|
1405
|
+
changeSettings: async (request) => {
|
|
1406
|
+
const result = await changeWorkflowSettings(ctx, {
|
|
1407
|
+
runId: request.runId,
|
|
1408
|
+
...(request.scopeId !== undefined ? { scopeId: request.scopeId } : {}),
|
|
1409
|
+
...(request.expectedChangeNumber !== undefined
|
|
1410
|
+
? { expectedChangeNumber: request.expectedChangeNumber }
|
|
1411
|
+
: {}),
|
|
1412
|
+
patch: request.patch,
|
|
1413
|
+
requestId: request.actorRequestKey,
|
|
1414
|
+
actor: "controller",
|
|
1415
|
+
actorId: request.controllerResourceUid,
|
|
1416
|
+
source: "controller-request",
|
|
1417
|
+
});
|
|
1418
|
+
return {
|
|
1419
|
+
runId: request.runId,
|
|
1420
|
+
scopeId: String(result.details.scopeId),
|
|
1421
|
+
changeNumber: Number(result.details.changeNumber),
|
|
1422
|
+
adopted: result.details.adopted === true,
|
|
1423
|
+
};
|
|
1424
|
+
},
|
|
1425
|
+
queueFollowUp: async (request) => {
|
|
1426
|
+
const result = await queueWorkflowFollowUp(ctx, {
|
|
1427
|
+
runId: request.runId,
|
|
1428
|
+
prompt: request.prompt,
|
|
1429
|
+
requestId: request.actorRequestKey,
|
|
1430
|
+
actor: "controller",
|
|
1431
|
+
actorId: request.controllerResourceUid,
|
|
1432
|
+
source: "controller-request",
|
|
1433
|
+
});
|
|
1434
|
+
return {
|
|
1435
|
+
runId: request.runId,
|
|
1436
|
+
followUpId: String(result.details.followUpId),
|
|
1437
|
+
order: Number(result.details.order),
|
|
1438
|
+
state: "queued",
|
|
1439
|
+
adopted: result.details.adopted === true,
|
|
1440
|
+
};
|
|
1441
|
+
},
|
|
1442
|
+
removeFollowUp: async (request) => {
|
|
1443
|
+
const result = await removeWorkflowFollowUp(ctx, {
|
|
1444
|
+
runId: request.runId,
|
|
1445
|
+
followUpId: request.followUpId,
|
|
1446
|
+
actor: "controller",
|
|
1447
|
+
actorId: request.controllerResourceUid,
|
|
1448
|
+
source: "controller-request",
|
|
1449
|
+
});
|
|
1450
|
+
return {
|
|
1451
|
+
runId: request.runId,
|
|
1452
|
+
followUpId: String(result.details.followUpId),
|
|
1453
|
+
order: Number(result.details.order),
|
|
1454
|
+
state: String(result.details.state),
|
|
1455
|
+
adopted: false,
|
|
1456
|
+
};
|
|
1457
|
+
},
|
|
1458
|
+
},
|
|
1459
|
+
});
|
|
1313
1460
|
controllerHost?.start();
|
|
1314
1461
|
updateControllerStatus(ctx);
|
|
1315
1462
|
return controllerHost;
|
|
@@ -1573,7 +1720,7 @@ export default function piWorkflows(pi) {
|
|
|
1573
1720
|
const { state } = bundle;
|
|
1574
1721
|
return {
|
|
1575
1722
|
message: `Workflow ${state.workflowName} is ${state.status} (run ${state.runId}).`,
|
|
1576
|
-
details: workflowStateSummary(state),
|
|
1723
|
+
details: workflowStateSummary(state, bundle),
|
|
1577
1724
|
};
|
|
1578
1725
|
}
|
|
1579
1726
|
const state = activeRun?.lastState ?? widgetSource?.state;
|
|
@@ -1591,9 +1738,136 @@ export default function piWorkflows(pi) {
|
|
|
1591
1738
|
}
|
|
1592
1739
|
return {
|
|
1593
1740
|
message: `Workflow ${state.workflowName} is ${state.status} (run ${state.runId}).`,
|
|
1594
|
-
details: workflowStateSummary(state),
|
|
1741
|
+
details: workflowStateSummary(state, readWorkflowRun(state.runId) ?? undefined),
|
|
1595
1742
|
};
|
|
1596
1743
|
};
|
|
1744
|
+
const resolveMutableWorkflow = async (ctx, requestedRunId) => {
|
|
1745
|
+
const runId = requestedRunId ?? activeRun?.runId ?? lastWaitingRunId ?? undefined;
|
|
1746
|
+
if (runId === undefined) {
|
|
1747
|
+
throw new Error("No running, parked, or waiting workflow is available for changes.");
|
|
1748
|
+
}
|
|
1749
|
+
const loaded = readWorkflowRun(runId);
|
|
1750
|
+
if (loaded === null)
|
|
1751
|
+
throw new Error(`Workflow run not found: ${runId}`);
|
|
1752
|
+
if (originSessionId(ctx, runId) !== ctx.sessionManager.getSessionId()) {
|
|
1753
|
+
throw new Error("Workflow changes must come from the Pi session that owns the run.");
|
|
1754
|
+
}
|
|
1755
|
+
const source = loaded.state.workflowSource;
|
|
1756
|
+
if (source === undefined)
|
|
1757
|
+
throw new Error(`Workflow run ${runId} has no saved source`);
|
|
1758
|
+
const ref = source.kind === "builtin" ? `builtin:${source.id}` : source.path;
|
|
1759
|
+
const resolved = await resolveWorkflowRef(ref, { cwd: ctx.cwd }, builtinWorkflowCatalog);
|
|
1760
|
+
if (workflowIdentityMismatch(loaded.state, resolved.definition, resolved.source)) {
|
|
1761
|
+
throw new Error(`Workflow source changed since run ${runId} started`);
|
|
1762
|
+
}
|
|
1763
|
+
return { runId, workflow: resolved.definition, store: new WorkflowRunStore() };
|
|
1764
|
+
};
|
|
1765
|
+
const changeWorkflowSettings = async (ctx, options) => {
|
|
1766
|
+
const target = await resolveMutableWorkflow(ctx, options.runId);
|
|
1767
|
+
try {
|
|
1768
|
+
const scopes = target.store.listSettingsScopes(target.runId);
|
|
1769
|
+
const scopeId = options.scopeId ??
|
|
1770
|
+
(activeRun?.runId === target.runId ? activeRun.engine.activeSettingsScopeId : undefined) ??
|
|
1771
|
+
(scopes.length === 1 ? scopes[0]?.scopeId : undefined);
|
|
1772
|
+
if (scopeId === undefined) {
|
|
1773
|
+
throw new Error("Specify scopeId because this workflow has more than one settings scope.");
|
|
1774
|
+
}
|
|
1775
|
+
const scope = target.store.getSettingsScope(scopeId);
|
|
1776
|
+
if (scope === undefined || scope.activeRunId !== target.runId) {
|
|
1777
|
+
throw new Error(`Workflow settings scope not found for run ${target.runId}: ${scopeId}`);
|
|
1778
|
+
}
|
|
1779
|
+
const definition = scope.mountPath === ""
|
|
1780
|
+
? target.workflow.settings
|
|
1781
|
+
: compositionMetadata(target.workflow)?.scopes[scope.mountPath]?.settings;
|
|
1782
|
+
if (definition === undefined) {
|
|
1783
|
+
throw new Error(`Workflow scope ${scopeId} does not declare editable settings.`);
|
|
1784
|
+
}
|
|
1785
|
+
const result = await target.store.changeSettings(definition, {
|
|
1786
|
+
runId: target.runId,
|
|
1787
|
+
scopeId,
|
|
1788
|
+
requestId: options.requestId,
|
|
1789
|
+
...(options.expectedChangeNumber !== undefined
|
|
1790
|
+
? { expectedChangeNumber: options.expectedChangeNumber }
|
|
1791
|
+
: {}),
|
|
1792
|
+
actor: {
|
|
1793
|
+
type: options.actor,
|
|
1794
|
+
id: options.actorId ?? ctx.sessionManager.getSessionId(),
|
|
1795
|
+
},
|
|
1796
|
+
source: options.source,
|
|
1797
|
+
patch: options.patch,
|
|
1798
|
+
});
|
|
1799
|
+
renderWidget(ctx);
|
|
1800
|
+
return {
|
|
1801
|
+
message: `Workflow settings changed to number ${result.scope.changeNumber}.`,
|
|
1802
|
+
details: {
|
|
1803
|
+
action: "change-settings",
|
|
1804
|
+
runId: target.runId,
|
|
1805
|
+
scopeId,
|
|
1806
|
+
changeNumber: result.scope.changeNumber,
|
|
1807
|
+
adopted: result.adopted,
|
|
1808
|
+
},
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
finally {
|
|
1812
|
+
target.store.close();
|
|
1813
|
+
}
|
|
1814
|
+
};
|
|
1815
|
+
const queueWorkflowFollowUp = async (ctx, options) => {
|
|
1816
|
+
const target = await resolveMutableWorkflow(ctx, options.runId);
|
|
1817
|
+
try {
|
|
1818
|
+
const result = target.store.queueFollowUp({
|
|
1819
|
+
runId: target.runId,
|
|
1820
|
+
requestId: options.requestId,
|
|
1821
|
+
targetSessionId: ctx.sessionManager.getSessionId(),
|
|
1822
|
+
actor: {
|
|
1823
|
+
type: options.actor,
|
|
1824
|
+
id: options.actorId ?? ctx.sessionManager.getSessionId(),
|
|
1825
|
+
},
|
|
1826
|
+
source: options.source,
|
|
1827
|
+
prompt: options.prompt,
|
|
1828
|
+
});
|
|
1829
|
+
return {
|
|
1830
|
+
message: `Queued workflow follow-up ${result.followUp.order}.`,
|
|
1831
|
+
details: {
|
|
1832
|
+
action: "queue-follow-up",
|
|
1833
|
+
runId: target.runId,
|
|
1834
|
+
followUpId: result.followUp.followUpId,
|
|
1835
|
+
order: result.followUp.order,
|
|
1836
|
+
adopted: result.adopted,
|
|
1837
|
+
},
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
finally {
|
|
1841
|
+
target.store.close();
|
|
1842
|
+
}
|
|
1843
|
+
};
|
|
1844
|
+
const removeWorkflowFollowUp = async (ctx, options) => {
|
|
1845
|
+
const target = await resolveMutableWorkflow(ctx, options.runId);
|
|
1846
|
+
try {
|
|
1847
|
+
const followUp = target.store.removeFollowUp({
|
|
1848
|
+
runId: target.runId,
|
|
1849
|
+
followUpId: options.followUpId,
|
|
1850
|
+
actor: {
|
|
1851
|
+
type: options.actor,
|
|
1852
|
+
id: options.actorId ?? ctx.sessionManager.getSessionId(),
|
|
1853
|
+
},
|
|
1854
|
+
source: options.source,
|
|
1855
|
+
});
|
|
1856
|
+
return {
|
|
1857
|
+
message: `Removed workflow follow-up ${followUp.order}.`,
|
|
1858
|
+
details: {
|
|
1859
|
+
action: "remove-follow-up",
|
|
1860
|
+
runId: target.runId,
|
|
1861
|
+
followUpId: followUp.followUpId,
|
|
1862
|
+
order: followUp.order,
|
|
1863
|
+
state: followUp.state,
|
|
1864
|
+
},
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
finally {
|
|
1868
|
+
target.store.close();
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1597
1871
|
const resolveWaitingWorkflow = async (ctx, requestedRunId, allowOtherSession = false) => {
|
|
1598
1872
|
let parentRunId = requestedRunId ?? lastWaitingRunId;
|
|
1599
1873
|
if (parentRunId === null) {
|
|
@@ -2115,7 +2389,7 @@ export default function piWorkflows(pi) {
|
|
|
2115
2389
|
},
|
|
2116
2390
|
});
|
|
2117
2391
|
pi.registerCommand("workflow", {
|
|
2118
|
-
description: "Run or manage a workflow: /workflow <name-or-path> [task | --input-json {…}]; also: status, pause, resume, cancel, answer",
|
|
2392
|
+
description: "Run or manage a workflow: /workflow <name-or-path> [task | --input-json {…}]; also: status, pause, resume, cancel, answer, change-settings, queue-follow-up, remove-follow-up",
|
|
2119
2393
|
getArgumentCompletions: async (prefix) => {
|
|
2120
2394
|
const discovered = await discoverWorkflows({ cwd: process.cwd() }, builtinWorkflowCatalog);
|
|
2121
2395
|
const items = [
|
|
@@ -2125,6 +2399,9 @@ export default function piWorkflows(pi) {
|
|
|
2125
2399
|
{ value: "resume", label: "resume" },
|
|
2126
2400
|
{ value: "cancel", label: "cancel" },
|
|
2127
2401
|
{ value: "answer", label: "answer" },
|
|
2402
|
+
{ value: "change-settings", label: "change-settings" },
|
|
2403
|
+
{ value: "queue-follow-up", label: "queue-follow-up" },
|
|
2404
|
+
{ value: "remove-follow-up", label: "remove-follow-up" },
|
|
2128
2405
|
].filter((item) => item.value.startsWith(prefix));
|
|
2129
2406
|
return items.length > 0 ? items : null;
|
|
2130
2407
|
},
|
|
@@ -2166,6 +2443,50 @@ export default function piWorkflows(pi) {
|
|
|
2166
2443
|
}
|
|
2167
2444
|
return;
|
|
2168
2445
|
}
|
|
2446
|
+
if (parsed.kind === "change-settings") {
|
|
2447
|
+
try {
|
|
2448
|
+
const result = await changeWorkflowSettings(ctx, {
|
|
2449
|
+
patch: parsed.patch,
|
|
2450
|
+
requestId: `interactive-${randomUUID()}`,
|
|
2451
|
+
actor: "human",
|
|
2452
|
+
source: "interactive-command",
|
|
2453
|
+
});
|
|
2454
|
+
notify(ctx, result.message, result.level);
|
|
2455
|
+
}
|
|
2456
|
+
catch (error) {
|
|
2457
|
+
notify(ctx, errorMessage(error), "error");
|
|
2458
|
+
}
|
|
2459
|
+
return;
|
|
2460
|
+
}
|
|
2461
|
+
if (parsed.kind === "queue-follow-up") {
|
|
2462
|
+
try {
|
|
2463
|
+
const result = await queueWorkflowFollowUp(ctx, {
|
|
2464
|
+
prompt: parsed.prompt,
|
|
2465
|
+
requestId: `interactive-${randomUUID()}`,
|
|
2466
|
+
actor: "human",
|
|
2467
|
+
source: "interactive-command",
|
|
2468
|
+
});
|
|
2469
|
+
notify(ctx, result.message, result.level);
|
|
2470
|
+
}
|
|
2471
|
+
catch (error) {
|
|
2472
|
+
notify(ctx, errorMessage(error), "error");
|
|
2473
|
+
}
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
if (parsed.kind === "remove-follow-up") {
|
|
2477
|
+
try {
|
|
2478
|
+
const result = await removeWorkflowFollowUp(ctx, {
|
|
2479
|
+
followUpId: parsed.followUpId,
|
|
2480
|
+
actor: "human",
|
|
2481
|
+
source: "interactive-command",
|
|
2482
|
+
});
|
|
2483
|
+
notify(ctx, result.message, result.level);
|
|
2484
|
+
}
|
|
2485
|
+
catch (error) {
|
|
2486
|
+
notify(ctx, errorMessage(error), "error");
|
|
2487
|
+
}
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2169
2490
|
if (parsed.kind === "answer") {
|
|
2170
2491
|
try {
|
|
2171
2492
|
const result = await answerWorkflowControl(ctx, parsed.input, parsed.runId);
|
|
@@ -2315,11 +2636,26 @@ export default function piWorkflows(pi) {
|
|
|
2315
2636
|
}
|
|
2316
2637
|
},
|
|
2317
2638
|
});
|
|
2639
|
+
let workflowToolTail = Promise.resolve();
|
|
2640
|
+
const withWorkflowToolOrder = async (operation) => {
|
|
2641
|
+
const prior = workflowToolTail;
|
|
2642
|
+
let release;
|
|
2643
|
+
workflowToolTail = new Promise((resolve) => {
|
|
2644
|
+
release = resolve;
|
|
2645
|
+
});
|
|
2646
|
+
await prior;
|
|
2647
|
+
try {
|
|
2648
|
+
return await operation();
|
|
2649
|
+
}
|
|
2650
|
+
finally {
|
|
2651
|
+
release?.();
|
|
2652
|
+
}
|
|
2653
|
+
};
|
|
2318
2654
|
pi.registerTool({
|
|
2319
2655
|
name: "workflow",
|
|
2320
2656
|
label: "Workflow",
|
|
2321
2657
|
description: [
|
|
2322
|
-
"List, start, inspect, pause, resume, cancel, answer, update, or complete pi-workflows runs.",
|
|
2658
|
+
"List, start, inspect, change settings, queue or remove follow-ups, pause, resume, cancel, answer, update, or complete pi-workflows runs.",
|
|
2323
2659
|
"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
2660
|
"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.",
|
|
2325
2661
|
"Use update or submit only when a workflow step contract asks for it, and pass the exact step and attempt ids.",
|
|
@@ -2327,75 +2663,107 @@ export default function piWorkflows(pi) {
|
|
|
2327
2663
|
].join(" "),
|
|
2328
2664
|
parameters: WorkflowToolParameters,
|
|
2329
2665
|
async execute(toolCallId, rawParams, _signal, _onUpdate, ctx) {
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
control = await queueToolLaunch(ctx, waiting.workflowRef, params.input, {
|
|
2358
|
-
parentRunId: waiting.parentRunId,
|
|
2359
|
-
});
|
|
2360
|
-
break;
|
|
2361
|
-
}
|
|
2362
|
-
case "update": {
|
|
2363
|
-
if (!activeRun)
|
|
2364
|
-
throw new Error("No workflow step is active.");
|
|
2365
|
-
const receipt = await activeRun.engine.publishUpdate(params.step, params.attempt, params.update, toolCallId);
|
|
2366
|
-
return {
|
|
2367
|
-
content: [
|
|
2368
|
-
{ type: "text", text: "Workflow update published; the step remains active." },
|
|
2369
|
-
],
|
|
2370
|
-
details: { action: "update", ...receipt },
|
|
2371
|
-
};
|
|
2372
|
-
}
|
|
2373
|
-
case "submit": {
|
|
2374
|
-
if (!activeRun) {
|
|
2375
|
-
if (lastExpiredAttempt?.contract.attemptId === params.attempt &&
|
|
2376
|
-
lastExpiredAttempt.contract.nodeId === params.step) {
|
|
2377
|
-
throw new Error(`Workflow step ${JSON.stringify(params.step)} attempt ${JSON.stringify(params.attempt)} ${lastExpiredAttempt.reason}; its output is no longer accepted.`);
|
|
2666
|
+
return await withWorkflowToolOrder(async () => {
|
|
2667
|
+
const params = parseWorkflowToolInput(rawParams);
|
|
2668
|
+
let control;
|
|
2669
|
+
switch (params.action) {
|
|
2670
|
+
case "list":
|
|
2671
|
+
control = await listWorkflowControl(ctx, params.offset);
|
|
2672
|
+
break;
|
|
2673
|
+
case "start":
|
|
2674
|
+
control = await queueToolLaunch(ctx, params.workflow, params.input ?? {});
|
|
2675
|
+
break;
|
|
2676
|
+
case "status":
|
|
2677
|
+
control = await statusWorkflowControl(ctx, params.runId);
|
|
2678
|
+
break;
|
|
2679
|
+
case "pause":
|
|
2680
|
+
control = pauseWorkflowControl(ctx);
|
|
2681
|
+
break;
|
|
2682
|
+
case "resume":
|
|
2683
|
+
control = resumeWorkflowControl(ctx);
|
|
2684
|
+
break;
|
|
2685
|
+
case "cancel":
|
|
2686
|
+
control = await cancelWorkflowControl(ctx, "agent", params.runId);
|
|
2687
|
+
break;
|
|
2688
|
+
case "answer": {
|
|
2689
|
+
const waiting = await resolveWaitingWorkflow(ctx, params.runId);
|
|
2690
|
+
const parent = readWorkflowRun(waiting.parentRunId);
|
|
2691
|
+
if (parent !== null && humanDecisionRequest(parent.state.finalOutput) !== null) {
|
|
2692
|
+
throw new Error("This checkpoint requires a verified human answer from Pi UI or a configured decision channel.");
|
|
2378
2693
|
}
|
|
2379
|
-
|
|
2694
|
+
control = await queueToolLaunch(ctx, waiting.workflowRef, params.input, {
|
|
2695
|
+
parentRunId: waiting.parentRunId,
|
|
2696
|
+
});
|
|
2697
|
+
break;
|
|
2698
|
+
}
|
|
2699
|
+
case "change-settings":
|
|
2700
|
+
control = await changeWorkflowSettings(ctx, {
|
|
2701
|
+
...(params.runId !== undefined ? { runId: params.runId } : {}),
|
|
2702
|
+
...(params.scopeId !== undefined ? { scopeId: params.scopeId } : {}),
|
|
2703
|
+
...(params.expectedChangeNumber !== undefined
|
|
2704
|
+
? { expectedChangeNumber: params.expectedChangeNumber }
|
|
2705
|
+
: {}),
|
|
2706
|
+
patch: params.patch,
|
|
2707
|
+
requestId: `tool-${toolCallId}`,
|
|
2708
|
+
actor: "session",
|
|
2709
|
+
source: "workflow-tool",
|
|
2710
|
+
});
|
|
2711
|
+
break;
|
|
2712
|
+
case "queue-follow-up":
|
|
2713
|
+
control = await queueWorkflowFollowUp(ctx, {
|
|
2714
|
+
...(params.runId !== undefined ? { runId: params.runId } : {}),
|
|
2715
|
+
prompt: params.prompt,
|
|
2716
|
+
requestId: `tool-${toolCallId}`,
|
|
2717
|
+
actor: "session",
|
|
2718
|
+
source: "workflow-tool",
|
|
2719
|
+
});
|
|
2720
|
+
break;
|
|
2721
|
+
case "remove-follow-up":
|
|
2722
|
+
control = await removeWorkflowFollowUp(ctx, {
|
|
2723
|
+
...(params.runId !== undefined ? { runId: params.runId } : {}),
|
|
2724
|
+
followUpId: params.followUpId,
|
|
2725
|
+
actor: "session",
|
|
2726
|
+
source: "workflow-tool",
|
|
2727
|
+
});
|
|
2728
|
+
break;
|
|
2729
|
+
case "update": {
|
|
2730
|
+
if (!activeRun)
|
|
2731
|
+
throw new Error("No workflow step is active.");
|
|
2732
|
+
const receipt = await activeRun.engine.publishUpdate(params.step, params.attempt, params.update, toolCallId);
|
|
2733
|
+
return {
|
|
2734
|
+
content: [
|
|
2735
|
+
{ type: "text", text: "Workflow update published; the step remains active." },
|
|
2736
|
+
],
|
|
2737
|
+
details: { action: "update", ...receipt },
|
|
2738
|
+
};
|
|
2380
2739
|
}
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2740
|
+
case "submit": {
|
|
2741
|
+
if (!activeRun) {
|
|
2742
|
+
if (lastExpiredAttempt?.contract.attemptId === params.attempt &&
|
|
2743
|
+
lastExpiredAttempt.contract.nodeId === params.step) {
|
|
2744
|
+
throw new Error(`Workflow step ${JSON.stringify(params.step)} attempt ${JSON.stringify(params.attempt)} ${lastExpiredAttempt.reason}; its output is no longer accepted.`);
|
|
2745
|
+
}
|
|
2746
|
+
throw new Error("No workflow step is waiting for output.");
|
|
2747
|
+
}
|
|
2748
|
+
// Flush the conversation into SQLite before accepting, so the
|
|
2749
|
+
// attempt range includes the assistant message carrying this call.
|
|
2750
|
+
await activeRun.recorder?.record(ctx).catch(() => undefined);
|
|
2751
|
+
await activeRun.recorder?.synchronize(ctx).catch(() => undefined);
|
|
2752
|
+
const result = await activeRun.executor.submit(params.step, params.attempt, params.output);
|
|
2753
|
+
if (!result.accepted) {
|
|
2754
|
+
throw new Error(result.message);
|
|
2755
|
+
}
|
|
2756
|
+
return {
|
|
2757
|
+
content: [{ type: "text", text: result.message }],
|
|
2758
|
+
details: { action: "submit", step: params.step, accepted: true },
|
|
2759
|
+
};
|
|
2388
2760
|
}
|
|
2389
|
-
return {
|
|
2390
|
-
content: [{ type: "text", text: result.message }],
|
|
2391
|
-
details: { action: "submit", step: params.step, accepted: true },
|
|
2392
|
-
};
|
|
2393
2761
|
}
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
};
|
|
2762
|
+
return {
|
|
2763
|
+
content: [{ type: "text", text: control.message }],
|
|
2764
|
+
details: control.details,
|
|
2765
|
+
};
|
|
2766
|
+
});
|
|
2399
2767
|
},
|
|
2400
2768
|
});
|
|
2401
2769
|
if (herdrEnabled) {
|
|
@@ -2418,6 +2786,16 @@ export default function piWorkflows(pi) {
|
|
|
2418
2786
|
if (herdrEnabled)
|
|
2419
2787
|
void refreshHerdrCapability(ctx);
|
|
2420
2788
|
ensureRunQueueStore(ctx.cwd);
|
|
2789
|
+
ensureFollowUpDelivery();
|
|
2790
|
+
const sessionFollowUpStore = followUpStore;
|
|
2791
|
+
const sessionFollowUpCoordinator = followUpCoordinator;
|
|
2792
|
+
if (sessionFollowUpStore === null || sessionFollowUpCoordinator === null) {
|
|
2793
|
+
throw new Error("Workflow follow-up delivery did not initialize");
|
|
2794
|
+
}
|
|
2795
|
+
for (const runId of sessionFollowUpStore.listPendingPresentations(ctx.sessionManager.getSessionId())) {
|
|
2796
|
+
settlePresentationFromBranch(ctx, runId, "The final workflow response did not settle before the extension restarted");
|
|
2797
|
+
}
|
|
2798
|
+
await sessionFollowUpCoordinator.synchronize(ctx, false).catch(() => undefined);
|
|
2421
2799
|
try {
|
|
2422
2800
|
await reloadDecisionChannels(ctx);
|
|
2423
2801
|
}
|
|
@@ -2521,6 +2899,10 @@ export default function piWorkflows(pi) {
|
|
|
2521
2899
|
});
|
|
2522
2900
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
2523
2901
|
systemTurnAbort = null;
|
|
2902
|
+
if (presentationPending !== null) {
|
|
2903
|
+
settlePresentationFromBranch(ctx, presentationPending.runId, "The final workflow response settled without durable session evidence");
|
|
2904
|
+
presentationPending = null;
|
|
2905
|
+
}
|
|
2524
2906
|
if (activeRun === null) {
|
|
2525
2907
|
try {
|
|
2526
2908
|
const prepared = ensureRunQueueStore(ctx.cwd).findSessionReservation(ctx.sessionManager.getSessionId());
|
|
@@ -2537,7 +2919,7 @@ export default function piWorkflows(pi) {
|
|
|
2537
2919
|
const run = activeRun;
|
|
2538
2920
|
if (!run) {
|
|
2539
2921
|
turnCoordinator.flushNatural(ctx.isIdle() && !sessionClosed && !runHeld());
|
|
2540
|
-
|
|
2922
|
+
await followUpCoordinator?.synchronize(ctx, false).catch(() => undefined);
|
|
2541
2923
|
runSyncPass(ctx);
|
|
2542
2924
|
return;
|
|
2543
2925
|
}
|
|
@@ -2593,6 +2975,10 @@ export default function piWorkflows(pi) {
|
|
|
2593
2975
|
controllerContext = null;
|
|
2594
2976
|
runQueueStore?.close();
|
|
2595
2977
|
runQueueStore = null;
|
|
2978
|
+
followUpCoordinator?.clear();
|
|
2979
|
+
followUpCoordinator = null;
|
|
2980
|
+
followUpStore?.close();
|
|
2981
|
+
followUpStore = null;
|
|
2596
2982
|
presentationPending = null;
|
|
2597
2983
|
clearWidgetTimer();
|
|
2598
2984
|
stopWidgetTicker();
|