@osolmaz/pi-workflows 0.16.4 → 0.16.6
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 +5 -5
- package/dist/builtins/autoimplement.workflow.d.ts +5 -1
- package/dist/builtins/autoimplement.workflow.js +29 -10
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/client/client.js +3 -1
- package/dist/client/client.js.map +1 -1
- package/dist/client/protocol.d.ts +1 -1
- package/dist/client/protocol.js +18 -1
- package/dist/client/protocol.js.map +1 -1
- package/dist/client/view.d.ts +7 -5
- package/dist/extension/index.d.ts +2 -1
- package/dist/extension/index.js +115 -165
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +1 -1
- package/dist/extension/recorder.js +2 -2
- package/dist/extension/recorder.js.map +1 -1
- package/dist/extension/step-message.d.ts +1 -1
- package/dist/extension/step-message.js +4 -4
- package/dist/extension/step-message.js.map +1 -1
- package/dist/extension/workflow-message-coordinator.d.ts +12 -1
- package/dist/extension/workflow-message-coordinator.js +65 -54
- package/dist/extension/workflow-message-coordinator.js.map +1 -1
- package/dist/resource-managers/index.d.ts +1 -2
- package/dist/resource-managers/index.js +1 -2
- package/dist/resource-managers/index.js.map +1 -1
- package/dist/resource-managers/sqlite.d.ts +2 -295
- package/dist/resource-managers/sqlite.js +71 -1528
- package/dist/resource-managers/sqlite.js.map +1 -1
- package/dist/resource-managers/types.d.ts +2 -7
- package/dist/server/resource-runner-entry.js +2 -6
- package/dist/server/resource-runner-entry.js.map +1 -1
- package/dist/server/rpc-bridge.js +1 -1
- package/dist/server/rpc-bridge.js.map +1 -1
- package/dist/server/rpc-executor.js +2 -4
- package/dist/server/rpc-executor.js.map +1 -1
- package/dist/server/server-entry.js +3 -0
- package/dist/server/server-entry.js.map +1 -1
- package/dist/server/server.d.ts +22 -8
- package/dist/server/server.js +498 -543
- package/dist/server/server.js.map +1 -1
- package/dist/server/state.d.ts +16 -29
- package/dist/server/state.js +105 -319
- package/dist/server/state.js.map +1 -1
- package/dist/server/view.d.ts +4 -5
- package/dist/server/view.js +51 -51
- package/dist/server/view.js.map +1 -1
- package/dist/server/workflow-runner-entry.d.ts +1 -1
- package/dist/server/workflow-runner-entry.js +1 -49
- package/dist/server/workflow-runner-entry.js.map +1 -1
- package/dist/server/workflow-runner-protocol.d.ts +2 -7
- package/dist/server/workflow-runner-protocol.js +5 -10
- package/dist/server/workflow-runner-protocol.js.map +1 -1
- package/dist/server/workflow-runner-store.d.ts +5 -6
- package/dist/server/workflow-runner-store.js +7 -14
- package/dist/server/workflow-runner-store.js.map +1 -1
- package/dist/state/attempt-time.d.ts +21 -0
- package/dist/state/attempt-time.js +95 -0
- package/dist/state/attempt-time.js.map +1 -0
- package/dist/state/json.d.ts +2 -1
- package/dist/state/json.js.map +1 -1
- package/dist/state/project-store.d.ts +39 -0
- package/dist/state/project-store.js +154 -0
- package/dist/state/project-store.js.map +1 -0
- package/dist/state/prune.d.ts +28 -0
- package/dist/state/prune.js +278 -83
- package/dist/state/prune.js.map +1 -1
- package/dist/state/schema.js +23 -23
- package/dist/state/workflow-messages.d.ts +1 -1
- package/dist/state/workflow-messages.js +3 -1
- package/dist/state/workflow-messages.js.map +1 -1
- package/dist/viewer/backup.js +5 -0
- package/dist/viewer/backup.js.map +1 -1
- package/dist/workflows/diagnostics.d.ts +8 -0
- package/dist/workflows/diagnostics.js +61 -0
- package/dist/workflows/diagnostics.js.map +1 -0
- package/dist/workflows/engine.d.ts +2 -15
- package/dist/workflows/engine.js +125 -245
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +3 -7
- package/dist/workflows/human-decision.js +2 -56
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +2 -1
- package/dist/workflows/index.js +1 -0
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/queue.d.ts +267 -0
- package/dist/workflows/queue.js +1248 -0
- package/dist/workflows/queue.js.map +1 -0
- package/dist/workflows/requests.d.ts +47 -0
- package/dist/workflows/requests.js +158 -0
- package/dist/workflows/requests.js.map +1 -0
- package/dist/workflows/schema.js +2 -4
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/store.d.ts +11 -16
- package/dist/workflows/store.js +174 -169
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +6 -9
- package/dist/workflows/tool-input.js +15 -12
- package/dist/workflows/tool-input.js.map +1 -1
- package/dist/workflows/transitions.d.ts +38 -0
- package/dist/workflows/transitions.js +276 -0
- package/dist/workflows/transitions.js.map +1 -0
- package/dist/workflows/types.d.ts +9 -42
- package/dist/workflows/workflow-message-content.d.ts +7 -1
- package/dist/workflows/workflow-message-content.js +22 -4
- package/dist/workflows/workflow-message-content.js.map +1 -1
- package/docs/2026-08-25-workflow-follow-ups.md +5 -5
- package/docs/2026-09-02-unify-workflow-messages-plan.md +2 -0
- package/docs/2026-09-04-workflow-run-state-plan.md +2 -0
- package/docs/2026-09-06-durable-execution-plan.md +176 -0
- package/docs/DEFERRED_TURNS.md +84 -105
- package/docs/HUMAN_DECISIONS.md +17 -19
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +1 -1
- package/docs/SQLITE_STATE.md +87 -30
- package/docs/WORKFLOW_SERVER.md +50 -11
- package/docs/WORKFLOW_STEP_MESSAGES.md +72 -54
- package/docs/plans/2026-08-04-controller-runtime-plan.md +2 -0
- package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +2 -0
- package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +2 -0
- package/docs/plans/2026-09-04-automatic-state-retention-plan.md +283 -0
- package/docs/workflows.md +109 -44
- package/examples/workflows/autoresearch.workflow.ts +0 -2
- package/examples/workflows/branch.workflow.ts +0 -4
- package/examples/workflows/echo.workflow.ts +0 -2
- package/examples/workflows/live-settings.workflow.ts +0 -2
- package/examples/workflows/two-turn.workflow.ts +0 -2
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/protocol/fixtures/client-v1.json +1 -1
- package/skills/pi-workflows/SKILL.md +8 -7
- package/src/builtins/autoimplement.workflow.ts +31 -10
- package/src/client/client.ts +3 -1
- package/src/client/protocol.ts +21 -1
- package/src/client/view.ts +9 -5
- package/src/extension/index.ts +117 -189
- package/src/extension/recorder.ts +2 -2
- package/src/extension/step-message.ts +4 -4
- package/src/extension/workflow-message-coordinator.ts +78 -50
- package/src/resource-managers/index.ts +1 -9
- package/src/resource-managers/sqlite.ts +330 -2655
- package/src/resource-managers/types.ts +2 -3
- package/src/server/resource-runner-entry.ts +2 -7
- package/src/server/rpc-bridge.ts +1 -1
- package/src/server/rpc-executor.ts +4 -9
- package/src/server/server-entry.ts +3 -0
- package/src/server/server.ts +568 -644
- package/src/server/state.ts +122 -466
- package/src/server/view.ts +56 -60
- package/src/server/workflow-runner-entry.ts +2 -55
- package/src/server/workflow-runner-protocol.ts +5 -15
- package/src/server/workflow-runner-store.ts +12 -27
- package/src/state/attempt-time.ts +122 -0
- package/src/state/json.ts +2 -1
- package/src/state/project-store.ts +218 -0
- package/src/state/prune.ts +408 -95
- package/src/state/schema.ts +23 -23
- package/src/state/workflow-messages.ts +4 -2
- package/src/viewer/backup.ts +7 -0
- package/src/workflows/diagnostics.ts +78 -0
- package/src/workflows/engine.ts +133 -291
- package/src/workflows/human-decision.ts +3 -81
- package/src/workflows/index.ts +8 -2
- package/src/workflows/queue.ts +2091 -0
- package/src/workflows/requests.ts +283 -0
- package/src/workflows/schema.ts +2 -6
- package/src/workflows/store.ts +223 -231
- package/src/workflows/tool-input.ts +20 -12
- package/src/workflows/transitions.ts +330 -0
- package/src/workflows/types.ts +9 -46
- package/src/workflows/workflow-message-content.ts +28 -4
- package/dist/resource-managers/runtime.d.ts +0 -59
- package/dist/resource-managers/runtime.js +0 -423
- package/dist/resource-managers/runtime.js.map +0 -1
- package/src/resource-managers/runtime.ts +0 -576
package/src/extension/index.ts
CHANGED
|
@@ -67,7 +67,7 @@ export type ParsedWorkflowArgs =
|
|
|
67
67
|
| { kind: "resume" }
|
|
68
68
|
| { kind: "status"; runId?: string }
|
|
69
69
|
| { kind: "clear"; runId?: string }
|
|
70
|
-
| { kind: "restart"; runId?: string }
|
|
70
|
+
| { kind: "restart"; runId?: string; expectedRevision?: number }
|
|
71
71
|
| {
|
|
72
72
|
kind: "change-settings";
|
|
73
73
|
patch: unknown;
|
|
@@ -77,7 +77,7 @@ export type ParsedWorkflowArgs =
|
|
|
77
77
|
}
|
|
78
78
|
| { kind: "queue-follow-up"; prompt: string; runId?: string }
|
|
79
79
|
| { kind: "remove-follow-up"; followUpId: string; runId?: string }
|
|
80
|
-
| { kind: "answer";
|
|
80
|
+
| { kind: "answer"; requestId: string; input: unknown }
|
|
81
81
|
| { kind: "run"; ref: string; input: unknown };
|
|
82
82
|
|
|
83
83
|
/** Parse `/workflow` arguments. Exported for tests. */
|
|
@@ -131,35 +131,22 @@ export function parseWorkflowArgs(args: string): ParsedWorkflowArgs {
|
|
|
131
131
|
return { kind: "status", runId };
|
|
132
132
|
}
|
|
133
133
|
if (trimmed === "answer" || trimmed.startsWith("answer ")) {
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
const rest = trimmed === "answer" ? "" : trimmed.slice("answer".length).trim();
|
|
135
|
+
const firstSpace = rest.search(/\s/);
|
|
136
|
+
if (firstSpace <= 0 || rest.slice(firstSpace).trim().length === 0) {
|
|
136
137
|
throw new Error(
|
|
137
|
-
'answer requires
|
|
138
|
+
'answer requires an exact request id and a response: /workflow answer REQUEST_ID {"choice":"approve"}',
|
|
138
139
|
);
|
|
139
140
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const remainder = rest.slice(firstSpace).trim();
|
|
145
|
-
if (
|
|
146
|
-
validRunId(candidate) &&
|
|
147
|
-
remainder.length > 0 &&
|
|
148
|
-
(remainder.startsWith("{") || remainder.startsWith("["))
|
|
149
|
-
) {
|
|
150
|
-
runId = candidate;
|
|
151
|
-
rest = remainder;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
141
|
+
const requestId = rest.slice(0, firstSpace);
|
|
142
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(requestId))
|
|
143
|
+
throw new Error("answer requires a valid request id");
|
|
144
|
+
const text = rest.slice(firstSpace).trim();
|
|
154
145
|
try {
|
|
155
|
-
return {
|
|
156
|
-
kind: "answer",
|
|
157
|
-
input: JSON.parse(rest) as unknown,
|
|
158
|
-
...(runId === undefined ? {} : { runId }),
|
|
159
|
-
};
|
|
146
|
+
return { kind: "answer", requestId, input: JSON.parse(text) as unknown };
|
|
160
147
|
} catch {
|
|
161
|
-
if (
|
|
162
|
-
return { kind: "answer", input: { answer:
|
|
148
|
+
if (/^[{["]/.test(text)) throw new Error("answer contains invalid JSON");
|
|
149
|
+
return { kind: "answer", requestId, input: { answer: text } };
|
|
163
150
|
}
|
|
164
151
|
}
|
|
165
152
|
const spaceIndex = trimmed.search(/\s/);
|
|
@@ -190,6 +177,7 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
190
177
|
const sessionView = new SessionWorkflowView();
|
|
191
178
|
const sessionRecorders = new Map<string, SessionRecorder>();
|
|
192
179
|
let agentRunning = false;
|
|
180
|
+
let lastStopReason: ReturnType<typeof workflowTurnStopReason> = "completed";
|
|
193
181
|
let activeRecorder: SessionRecorder | null = null;
|
|
194
182
|
let activeRecorderMessageId: string | null = null;
|
|
195
183
|
|
|
@@ -216,7 +204,7 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
216
204
|
const message = workflowMessages.activeTurnMessage();
|
|
217
205
|
if (message === undefined || activeRecorderMessageId === message.workflowMessageId) return;
|
|
218
206
|
const contract = agentContractForWorkflowMessage(message);
|
|
219
|
-
if (contract === undefined && message.kind !== "
|
|
207
|
+
if (contract === undefined && message.kind !== "followUp") {
|
|
220
208
|
return;
|
|
221
209
|
}
|
|
222
210
|
const recorder = await ensureRecorder(message, ctx);
|
|
@@ -227,10 +215,7 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
227
215
|
return;
|
|
228
216
|
}
|
|
229
217
|
if (contract === undefined) {
|
|
230
|
-
recorder.
|
|
231
|
-
message.workflowMessageId,
|
|
232
|
-
message.kind as "terminal" | "followUp",
|
|
233
|
-
);
|
|
218
|
+
recorder.beginFollowUp(message.workflowMessageId);
|
|
234
219
|
} else {
|
|
235
220
|
recorder.beginAttempt(contract);
|
|
236
221
|
}
|
|
@@ -246,7 +231,28 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
246
231
|
});
|
|
247
232
|
await prior;
|
|
248
233
|
try {
|
|
249
|
-
|
|
234
|
+
const finishRecording = async (message: WorkflowMessage): Promise<void> => {
|
|
235
|
+
const recorder = sessionRecorders.get(message.runId);
|
|
236
|
+
if (recorder === undefined) return;
|
|
237
|
+
await recorder.record(ctx).catch((error) => {
|
|
238
|
+
ctx.ui.notify(
|
|
239
|
+
`Workflow conversation recording failed: ${errorMessage(error)}`,
|
|
240
|
+
"warning",
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
await recorder.finish();
|
|
244
|
+
sessionRecorders.delete(message.runId);
|
|
245
|
+
if (activeRecorder === recorder) activeRecorder = null;
|
|
246
|
+
};
|
|
247
|
+
await workflowMessages.synchronize(pi, client, ctx, {
|
|
248
|
+
beforeTurnEnd: async (message, end) => {
|
|
249
|
+
if (end.stopReason === "completed") {
|
|
250
|
+
await submitVisibleAssistantResponse(client, ctx, message, end.responseSessionEntryId);
|
|
251
|
+
}
|
|
252
|
+
if (message.kind === "followUp") await finishRecording(message);
|
|
253
|
+
},
|
|
254
|
+
terminalDelivered: finishRecording,
|
|
255
|
+
});
|
|
250
256
|
await activateRecorder(ctx);
|
|
251
257
|
} finally {
|
|
252
258
|
sessionView.refresh(ctx);
|
|
@@ -495,7 +501,7 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
495
501
|
"List, start, restart, inspect, change settings, queue or remove follow-ups, pause, resume, cancel, answer ordinary checkpoints, update, or complete hosted workflow runs.",
|
|
496
502
|
"Protected human decisions cannot be answered with this model-facing tool.",
|
|
497
503
|
"When the user asks to continue or resume the active workflow, call workflow resume immediately.",
|
|
498
|
-
"Use update or submit only when a workflow step contract asks for it, and pass
|
|
504
|
+
"Use update or submit only when a workflow step contract asks for it, and pass its exact requestId.",
|
|
499
505
|
"Do not start repeated work without the user's request.",
|
|
500
506
|
].join(" "),
|
|
501
507
|
parameters: WorkflowToolParameters,
|
|
@@ -503,27 +509,16 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
503
509
|
return await runToolInOrder(async () => {
|
|
504
510
|
const params = parseWorkflowToolInput(rawParams);
|
|
505
511
|
if (params.action === "update" || params.action === "submit") {
|
|
506
|
-
const interaction = pendingInteractionForSession(ctx.sessionManager.getSessionId());
|
|
507
|
-
if (interaction === undefined) throw new Error("No workflow step is waiting for output");
|
|
508
|
-
const contract = agentContract(interaction);
|
|
509
|
-
if (contract === undefined)
|
|
510
|
-
throw new Error("The pending interaction is not an agent step");
|
|
511
|
-
if (contract.nodeId !== params.step || contract.attemptId !== params.attempt) {
|
|
512
|
-
throw new Error("Workflow step or attempt does not match the durable request");
|
|
513
|
-
}
|
|
514
512
|
let response: ClientResponse;
|
|
515
513
|
try {
|
|
516
514
|
response = await requestAccepted(client, {
|
|
517
515
|
operation: params.action === "update" ? "interaction.update" : "interaction.submit",
|
|
518
516
|
requestId: `${params.action}-${toolCallId}-${randomUUID()}`,
|
|
519
517
|
idempotencyKey: toolCallId,
|
|
520
|
-
runId: interaction.runId,
|
|
521
|
-
expectedRevision: interaction.revision,
|
|
522
518
|
payload: jsonValue({
|
|
523
|
-
|
|
519
|
+
...sessionCommandPayload(ctx),
|
|
520
|
+
requestId: params.requestId,
|
|
524
521
|
submissionId: toolCallId,
|
|
525
|
-
step: params.step,
|
|
526
|
-
attempt: params.attempt,
|
|
527
522
|
value:
|
|
528
523
|
params.action === "update"
|
|
529
524
|
? { update: params.update }
|
|
@@ -692,40 +687,17 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
692
687
|
|
|
693
688
|
pi.on("agent_start", async (_event, ctx) => {
|
|
694
689
|
agentRunning = true;
|
|
690
|
+
lastStopReason = "completed";
|
|
695
691
|
activeRecorder = null;
|
|
696
692
|
activeRecorderMessageId = null;
|
|
697
693
|
workflowMessages.startTurn();
|
|
698
694
|
await presentInOrder(ctx).catch(() => undefined);
|
|
699
695
|
});
|
|
700
696
|
|
|
701
|
-
pi.on("agent_end",
|
|
702
|
-
try {
|
|
703
|
-
await submitVisibleAssistantResponse(client, ctx);
|
|
704
|
-
} catch (error) {
|
|
705
|
-
ctx.ui.notify(`Workflow response was rejected: ${errorMessage(error)}`, "error");
|
|
706
|
-
}
|
|
707
|
-
const finishedMessage = workflowMessages.activeTurnMessage();
|
|
697
|
+
pi.on("agent_end", (event, ctx) => {
|
|
708
698
|
agentRunning = false;
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
workflowTurnStopReason(event.messages, ctx.signal?.aborted === true),
|
|
712
|
-
responseEntryId(ctx.sessionManager.getBranch()),
|
|
713
|
-
);
|
|
714
|
-
if (
|
|
715
|
-
activeRecorder !== null &&
|
|
716
|
-
finishedMessage !== undefined &&
|
|
717
|
-
(finishedMessage.kind === "terminal" || finishedMessage.kind === "followUp")
|
|
718
|
-
) {
|
|
719
|
-
const finishedRecorder = activeRecorder;
|
|
720
|
-
activeRecorder = null;
|
|
721
|
-
await finishedRecorder.finish();
|
|
722
|
-
if (sessionRecorders.get(finishedMessage.runId) === finishedRecorder) {
|
|
723
|
-
sessionRecorders.delete(finishedMessage.runId);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
await presentInOrder(ctx).catch((error) => {
|
|
727
|
-
ctx.ui.notify(`Could not record workflow model activity: ${errorMessage(error)}`, "warning");
|
|
728
|
-
});
|
|
699
|
+
lastStopReason = workflowTurnStopReason(event.messages, ctx.signal?.aborted === true);
|
|
700
|
+
// Pi can retry after agent_end. Only agent_settled completes the owned turn.
|
|
729
701
|
});
|
|
730
702
|
|
|
731
703
|
pi.on("turn_start", (event) => {
|
|
@@ -762,12 +734,11 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
762
734
|
|
|
763
735
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
764
736
|
activeRecorder?.settleAttempt();
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
ctx.ui.notify(`
|
|
769
|
-
}
|
|
770
|
-
await presentInOrder(ctx).catch(() => undefined);
|
|
737
|
+
workflowMessages.endTurn(lastStopReason, responseEntryId(ctx.sessionManager.getBranch()));
|
|
738
|
+
activeRecorderMessageId = null;
|
|
739
|
+
await presentInOrder(ctx).catch((error) => {
|
|
740
|
+
ctx.ui.notify(`Could not record workflow model activity: ${errorMessage(error)}`, "warning");
|
|
741
|
+
});
|
|
771
742
|
});
|
|
772
743
|
|
|
773
744
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
@@ -920,17 +891,16 @@ async function executeCommand(
|
|
|
920
891
|
if (runId === undefined)
|
|
921
892
|
throw new Error("No workflow terminal result is available to restart");
|
|
922
893
|
const session = sessionCommandPayload(ctx);
|
|
923
|
-
const
|
|
894
|
+
const expectedRevision =
|
|
895
|
+
command.expectedRevision ?? (await client.getRun(runId))?.runRevision;
|
|
896
|
+
if (expectedRevision === undefined) throw new Error("Workflow run revision is unavailable");
|
|
924
897
|
const response = await requestAccepted(client, {
|
|
925
898
|
operation: "run.restart",
|
|
926
899
|
requestId: `restart-${idempotencyKey}`,
|
|
927
900
|
idempotencyKey,
|
|
928
901
|
runId,
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
workflowMessageId: marker.workflowMessageId,
|
|
932
|
-
...(marker.workflowTurnId === undefined ? {} : { workflowTurnId: marker.workflowTurnId }),
|
|
933
|
-
},
|
|
902
|
+
expectedRevision,
|
|
903
|
+
payload: session,
|
|
934
904
|
});
|
|
935
905
|
return {
|
|
936
906
|
message: `Restarted workflow ${runId}.`,
|
|
@@ -1033,11 +1003,30 @@ async function executeCommand(
|
|
|
1033
1003
|
};
|
|
1034
1004
|
}
|
|
1035
1005
|
case "answer": {
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1006
|
+
if (authority === "model") {
|
|
1007
|
+
const response = await requestAccepted(client, {
|
|
1008
|
+
operation: "checkpoint.answer",
|
|
1009
|
+
requestId: `checkpoint-${idempotencyKey}`,
|
|
1010
|
+
idempotencyKey,
|
|
1011
|
+
payload: {
|
|
1012
|
+
...sessionCommandPayload(ctx),
|
|
1013
|
+
requestId: command.requestId,
|
|
1014
|
+
submissionId: idempotencyKey,
|
|
1015
|
+
input: jsonValue(command.input),
|
|
1016
|
+
},
|
|
1017
|
+
});
|
|
1018
|
+
return {
|
|
1019
|
+
message: `Answered checkpoint ${command.requestId}.`,
|
|
1020
|
+
details: { action: "answer", response: response.receipt ?? null },
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
const interaction = sessionSnapshots
|
|
1024
|
+
.get(ctx.sessionManager.getSessionId())
|
|
1025
|
+
?.pendingInteractions.map(parseInteractiveRequest)
|
|
1026
|
+
.find((request) => request?.requestId === command.requestId);
|
|
1027
|
+
if (interaction === undefined)
|
|
1028
|
+
throw new Error("No matching checkpoint request is waiting in this session");
|
|
1029
|
+
if (interaction.kind === "decision") {
|
|
1041
1030
|
const response = await requestAccepted(client, {
|
|
1042
1031
|
operation: "decision.answer",
|
|
1043
1032
|
requestId: `answer-${idempotencyKey}`,
|
|
@@ -1045,6 +1034,7 @@ async function executeCommand(
|
|
|
1045
1034
|
runId: interaction.runId,
|
|
1046
1035
|
expectedRevision: interaction.revision,
|
|
1047
1036
|
payload: {
|
|
1037
|
+
...sessionCommandPayload(ctx),
|
|
1048
1038
|
requestId: interaction.requestId,
|
|
1049
1039
|
submissionId: idempotencyKey,
|
|
1050
1040
|
response: decisionResponse(command.input),
|
|
@@ -1059,33 +1049,26 @@ async function executeCommand(
|
|
|
1059
1049
|
},
|
|
1060
1050
|
};
|
|
1061
1051
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
const continuationRunId = createRunId(parent.workflowName);
|
|
1052
|
+
if (interaction.kind !== "checkpoint")
|
|
1053
|
+
throw new Error("Only an ordinary checkpoint accepts an answer");
|
|
1065
1054
|
const response = await requestAccepted(client, {
|
|
1066
1055
|
operation: "checkpoint.answer",
|
|
1067
1056
|
requestId: `checkpoint-${idempotencyKey}`,
|
|
1068
1057
|
idempotencyKey,
|
|
1069
|
-
runId:
|
|
1058
|
+
runId: interaction.runId,
|
|
1059
|
+
expectedRevision: interaction.revision,
|
|
1070
1060
|
payload: {
|
|
1071
|
-
|
|
1061
|
+
...sessionCommandPayload(ctx),
|
|
1062
|
+
requestId: interaction.requestId,
|
|
1063
|
+
submissionId: idempotencyKey,
|
|
1072
1064
|
input: jsonValue(command.input),
|
|
1073
1065
|
},
|
|
1074
1066
|
});
|
|
1075
|
-
const receipt = isRecord(response.receipt) ? response.receipt : undefined;
|
|
1076
|
-
const actualRunId =
|
|
1077
|
-
receipt !== undefined && typeof receipt.runId === "string"
|
|
1078
|
-
? receipt.runId
|
|
1079
|
-
: continuationRunId;
|
|
1080
1067
|
return {
|
|
1081
|
-
message:
|
|
1082
|
-
receipt?.alreadyAnswered === true
|
|
1083
|
-
? `Checkpoint ${parent.runId} was already answered; continuation ${actualRunId} exists.`
|
|
1084
|
-
: `Answered checkpoint ${parent.runId}; continuation ${actualRunId} started.`,
|
|
1068
|
+
message: `Answered checkpoint ${interaction.requestId}; run ${interaction.runId} can continue.`,
|
|
1085
1069
|
details: {
|
|
1086
1070
|
action: "answer",
|
|
1087
|
-
|
|
1088
|
-
runId: actualRunId,
|
|
1071
|
+
runId: interaction.runId,
|
|
1089
1072
|
response: response.receipt ?? null,
|
|
1090
1073
|
},
|
|
1091
1074
|
};
|
|
@@ -1162,54 +1145,33 @@ async function executeResourceManagerCommand(
|
|
|
1162
1145
|
async function submitVisibleAssistantResponse(
|
|
1163
1146
|
client: WorkflowClient,
|
|
1164
1147
|
ctx: ExtensionContext,
|
|
1148
|
+
message: WorkflowMessage,
|
|
1149
|
+
settledResponseEntryId: string | null,
|
|
1165
1150
|
): Promise<void> {
|
|
1166
|
-
const
|
|
1167
|
-
if (
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
)
|
|
1172
|
-
|
|
1173
|
-
}
|
|
1174
|
-
const contract = agentContract(interaction);
|
|
1175
|
-
if (contract === undefined) return;
|
|
1176
|
-
const submission = recoverAssistantStep(ctx.sessionManager.getBranch(), contract);
|
|
1151
|
+
const contract = agentContractForWorkflowMessage(message);
|
|
1152
|
+
if (contract?.completion !== "assistant" || workflowRunPaused(message.runId)) return;
|
|
1153
|
+
if (settledResponseEntryId === null) return;
|
|
1154
|
+
const branch = ctx.sessionManager.getBranch();
|
|
1155
|
+
const responseIndex = branch.findIndex((entry) => entry.id === settledResponseEntryId);
|
|
1156
|
+
if (responseIndex < 0) return;
|
|
1157
|
+
const submission = recoverAssistantStep(branch.slice(0, responseIndex + 1), contract);
|
|
1177
1158
|
if (submission === undefined) return;
|
|
1178
1159
|
const responseId = submission.conversation?.lastEntryId ?? submission.assistantMessage?.entryId;
|
|
1179
|
-
if (responseId
|
|
1180
|
-
await
|
|
1181
|
-
operation: "interaction.
|
|
1182
|
-
requestId: `assistant-${
|
|
1183
|
-
idempotencyKey: `assistant-${
|
|
1184
|
-
runId: interaction.runId,
|
|
1185
|
-
expectedRevision: interaction.revision,
|
|
1160
|
+
if (responseId !== settledResponseEntryId) return;
|
|
1161
|
+
const response = await client.request({
|
|
1162
|
+
operation: "interaction.assistant",
|
|
1163
|
+
requestId: `assistant-${message.sourceId}-${responseId}`,
|
|
1164
|
+
idempotencyKey: `assistant-${message.sourceId}-${responseId}`,
|
|
1186
1165
|
payload: {
|
|
1187
|
-
|
|
1166
|
+
...sessionCommandPayload(ctx),
|
|
1167
|
+
requestId: message.sourceId,
|
|
1188
1168
|
submissionId: `assistant-${responseId}`,
|
|
1189
|
-
step: contract.nodeId,
|
|
1190
|
-
attempt: contract.attemptId,
|
|
1191
1169
|
value: submission as unknown as JsonValue,
|
|
1192
1170
|
},
|
|
1193
1171
|
});
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
return sessionSnapshots
|
|
1198
|
-
.get(sessionId)
|
|
1199
|
-
?.pendingInteractions.map(parseInteractiveRequest)
|
|
1200
|
-
.find((request): request is ClientInteractiveRequest => request !== undefined);
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
function pendingDecision(sessionId: string, runId?: string): ClientInteractiveRequest | undefined {
|
|
1204
|
-
return sessionSnapshots
|
|
1205
|
-
.get(sessionId)
|
|
1206
|
-
?.pendingInteractions.map(parseInteractiveRequest)
|
|
1207
|
-
.find(
|
|
1208
|
-
(request): request is ClientInteractiveRequest =>
|
|
1209
|
-
request !== undefined &&
|
|
1210
|
-
request.kind === "decision" &&
|
|
1211
|
-
(runId === undefined || request.runId === runId),
|
|
1212
|
-
);
|
|
1172
|
+
if (response.outcome !== "accepted" && response.outcome !== "adopted") {
|
|
1173
|
+
ctx.ui.notify(`Workflow response was rejected: ${response.error ?? response.outcome}`, "error");
|
|
1174
|
+
}
|
|
1213
1175
|
}
|
|
1214
1176
|
|
|
1215
1177
|
function workflowRunPaused(runId: string): boolean {
|
|
@@ -1240,25 +1202,6 @@ function sessionCommandPayload(ctx: ExtensionContext): {
|
|
|
1240
1202
|
return { targetSessionId, coordinatorEpoch: session.coordinatorEpoch };
|
|
1241
1203
|
}
|
|
1242
1204
|
|
|
1243
|
-
function terminalMessageForSession(
|
|
1244
|
-
ctx: ExtensionContext,
|
|
1245
|
-
runId: string,
|
|
1246
|
-
): { workflowMessageId: string; workflowTurnId?: string } {
|
|
1247
|
-
const session = sessionSnapshots.get(ctx.sessionManager.getSessionId());
|
|
1248
|
-
const message = session?.workflowMessages
|
|
1249
|
-
.filter((item) => item.kind === "terminal" && item.runId === runId && item.status === "sent")
|
|
1250
|
-
.sort((left, right) => right.order - left.order)[0];
|
|
1251
|
-
if (message === undefined)
|
|
1252
|
-
throw new Error(`Workflow run ${runId} has no current terminal result`);
|
|
1253
|
-
const turn = session?.openWorkflowTurn;
|
|
1254
|
-
return {
|
|
1255
|
-
workflowMessageId: message.workflowMessageId,
|
|
1256
|
-
...(turn?.workflowMessageId === message.workflowMessageId
|
|
1257
|
-
? { workflowTurnId: turn.workflowTurnId }
|
|
1258
|
-
: {}),
|
|
1259
|
-
};
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
1205
|
function sessionRun(ctx: ExtensionContext, runId?: string): WorkflowRunQueueView | undefined {
|
|
1263
1206
|
const session = sessionSnapshots.get(ctx.sessionManager.getSessionId());
|
|
1264
1207
|
if (session?.run === null || session?.run === undefined || !isRecord(session.run.queue)) {
|
|
@@ -1288,7 +1231,7 @@ function toolInputToCommand(params: ReturnType<typeof parseWorkflowToolInput>):
|
|
|
1288
1231
|
case "start":
|
|
1289
1232
|
return { kind: "run", ref: params.workflow, input: params.input ?? {} };
|
|
1290
1233
|
case "restart":
|
|
1291
|
-
return { kind: "restart", runId: params.runId };
|
|
1234
|
+
return { kind: "restart", runId: params.runId, expectedRevision: params.expectedRevision };
|
|
1292
1235
|
case "change-settings":
|
|
1293
1236
|
return {
|
|
1294
1237
|
kind: "change-settings",
|
|
@@ -1324,8 +1267,8 @@ function toolInputToCommand(params: ReturnType<typeof parseWorkflowToolInput>):
|
|
|
1324
1267
|
case "answer":
|
|
1325
1268
|
return {
|
|
1326
1269
|
kind: "answer",
|
|
1327
|
-
|
|
1328
|
-
|
|
1270
|
+
requestId: params.requestId,
|
|
1271
|
+
input: params.input,
|
|
1329
1272
|
};
|
|
1330
1273
|
case "update":
|
|
1331
1274
|
case "submit":
|
|
@@ -1348,31 +1291,13 @@ async function requestAccepted(
|
|
|
1348
1291
|
return response;
|
|
1349
1292
|
}
|
|
1350
1293
|
|
|
1351
|
-
function interactionContract(interaction: ClientInteractiveRequest): Record<string, unknown> {
|
|
1352
|
-
if (!isRecord(interaction.contract)) throw new Error("Stored interaction contract is invalid");
|
|
1353
|
-
return interaction.contract;
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
function agentContract(interaction: ClientInteractiveRequest): AgentStepContract | undefined {
|
|
1357
|
-
const value = interactionContract(interaction).contract;
|
|
1358
|
-
if (
|
|
1359
|
-
!isRecord(value) ||
|
|
1360
|
-
typeof value.runId !== "string" ||
|
|
1361
|
-
typeof value.workflowName !== "string" ||
|
|
1362
|
-
typeof value.nodeId !== "string" ||
|
|
1363
|
-
typeof value.attemptId !== "string" ||
|
|
1364
|
-
(value.completion !== "submit" && value.completion !== "assistant")
|
|
1365
|
-
) {
|
|
1366
|
-
return undefined;
|
|
1367
|
-
}
|
|
1368
|
-
return value as unknown as AgentStepContract;
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
1294
|
function agentContractForWorkflowMessage(message: WorkflowMessage): AgentStepContract | undefined {
|
|
1372
1295
|
if (message.kind !== "step" || !isRecord(message.content.details)) return undefined;
|
|
1373
1296
|
const value = message.content.details.contract;
|
|
1374
1297
|
if (
|
|
1375
1298
|
!isRecord(value) ||
|
|
1299
|
+
value.requestId !== message.sourceId ||
|
|
1300
|
+
value.runId !== message.runId ||
|
|
1376
1301
|
typeof value.runId !== "string" ||
|
|
1377
1302
|
typeof value.workflowName !== "string" ||
|
|
1378
1303
|
typeof value.nodeId !== "string" ||
|
|
@@ -1484,7 +1409,10 @@ function parseInteractiveRequest(value: unknown): ClientInteractiveRequest | und
|
|
|
1484
1409
|
typeof value.runId !== "string" ||
|
|
1485
1410
|
typeof value.targetSessionId !== "string" ||
|
|
1486
1411
|
typeof value.revision !== "number" ||
|
|
1487
|
-
(value.kind !== "agent" &&
|
|
1412
|
+
(value.kind !== "agent" &&
|
|
1413
|
+
value.kind !== "assistant" &&
|
|
1414
|
+
value.kind !== "checkpoint" &&
|
|
1415
|
+
value.kind !== "decision")
|
|
1488
1416
|
) {
|
|
1489
1417
|
return undefined;
|
|
1490
1418
|
}
|
|
@@ -190,8 +190,8 @@ export class SessionRecorder {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
/** Record a workflow-owned turn that is not tied to one engine node attempt. */
|
|
193
|
-
|
|
194
|
-
this.currentAttempt = { nodeId:
|
|
193
|
+
beginFollowUp(workflowMessageId: string): void {
|
|
194
|
+
this.currentAttempt = { nodeId: "followUp", attemptId: workflowMessageId };
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
/** Release ownership of the turn that Pi has fully settled. */
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
export const WORKFLOW_AGENT_STEP_MESSAGE_TYPE = WORKFLOW_STEP_MESSAGE_TYPE;
|
|
20
20
|
export const WORKFLOW_AGENT_STEP_MESSAGE_SCHEMA = "pi-workflows.agent-step-message.v1";
|
|
21
21
|
|
|
22
|
-
type PromptDeliveryReason = "initial" | "
|
|
22
|
+
type PromptDeliveryReason = "initial" | "resumed";
|
|
23
23
|
|
|
24
24
|
export type WorkflowAgentStepMessageDetails = {
|
|
25
25
|
schema: typeof WORKFLOW_AGENT_STEP_MESSAGE_SCHEMA;
|
|
@@ -152,6 +152,7 @@ export function recoverAssistantStep(
|
|
|
152
152
|
|
|
153
153
|
function sameAttempt(left: AgentStepContract, right: AgentStepContract): boolean {
|
|
154
154
|
return (
|
|
155
|
+
left.requestId === right.requestId &&
|
|
155
156
|
left.runId === right.runId &&
|
|
156
157
|
left.workflowName === right.workflowName &&
|
|
157
158
|
left.nodeId === right.nodeId &&
|
|
@@ -225,9 +226,7 @@ function parseDetails(value: unknown): WorkflowAgentStepMessageDetails | undefin
|
|
|
225
226
|
if (candidate.schema !== WORKFLOW_AGENT_STEP_MESSAGE_SCHEMA) return undefined;
|
|
226
227
|
if (
|
|
227
228
|
candidate.kind !== "step" ||
|
|
228
|
-
(candidate.reason !== "initial" &&
|
|
229
|
-
candidate.reason !== "reminder" &&
|
|
230
|
-
candidate.reason !== "resumed") ||
|
|
229
|
+
(candidate.reason !== "initial" && candidate.reason !== "resumed") ||
|
|
231
230
|
typeof candidate.requestId !== "string" ||
|
|
232
231
|
typeof candidate.workflowMessageId !== "string"
|
|
233
232
|
) {
|
|
@@ -237,6 +236,7 @@ function parseDetails(value: unknown): WorkflowAgentStepMessageDetails | undefin
|
|
|
237
236
|
if (
|
|
238
237
|
contract === null ||
|
|
239
238
|
typeof contract !== "object" ||
|
|
239
|
+
typeof contract.requestId !== "string" ||
|
|
240
240
|
typeof contract.runId !== "string" ||
|
|
241
241
|
typeof contract.workflowName !== "string" ||
|
|
242
242
|
typeof contract.nodeId !== "string" ||
|