@osolmaz/pi-workflows 0.16.5 → 0.16.7

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.
Files changed (181) hide show
  1. package/README.md +5 -5
  2. package/dist/builtins/autoimplement.workflow.d.ts +5 -1
  3. package/dist/builtins/autoimplement.workflow.js +29 -10
  4. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  5. package/dist/client/client.js +3 -1
  6. package/dist/client/client.js.map +1 -1
  7. package/dist/client/protocol.d.ts +1 -1
  8. package/dist/client/protocol.js +18 -1
  9. package/dist/client/protocol.js.map +1 -1
  10. package/dist/client/view.d.ts +7 -5
  11. package/dist/extension/index.d.ts +2 -1
  12. package/dist/extension/index.js +116 -166
  13. package/dist/extension/index.js.map +1 -1
  14. package/dist/extension/recorder.d.ts +1 -1
  15. package/dist/extension/recorder.js +2 -2
  16. package/dist/extension/recorder.js.map +1 -1
  17. package/dist/extension/step-message.d.ts +1 -1
  18. package/dist/extension/step-message.js +4 -4
  19. package/dist/extension/step-message.js.map +1 -1
  20. package/dist/extension/widget.js +1 -1
  21. package/dist/extension/widget.js.map +1 -1
  22. package/dist/extension/workflow-message-coordinator.d.ts +12 -3
  23. package/dist/extension/workflow-message-coordinator.js +108 -94
  24. package/dist/extension/workflow-message-coordinator.js.map +1 -1
  25. package/dist/render/graph-render.js +1 -1
  26. package/dist/resource-managers/index.d.ts +1 -2
  27. package/dist/resource-managers/index.js +1 -2
  28. package/dist/resource-managers/index.js.map +1 -1
  29. package/dist/resource-managers/sqlite.d.ts +2 -295
  30. package/dist/resource-managers/sqlite.js +71 -1528
  31. package/dist/resource-managers/sqlite.js.map +1 -1
  32. package/dist/resource-managers/types.d.ts +2 -7
  33. package/dist/server/resource-runner-entry.js +2 -6
  34. package/dist/server/resource-runner-entry.js.map +1 -1
  35. package/dist/server/rpc-bridge.js +1 -1
  36. package/dist/server/rpc-bridge.js.map +1 -1
  37. package/dist/server/rpc-executor.js +2 -4
  38. package/dist/server/rpc-executor.js.map +1 -1
  39. package/dist/server/server-entry.js +3 -0
  40. package/dist/server/server-entry.js.map +1 -1
  41. package/dist/server/server.d.ts +9 -8
  42. package/dist/server/server.js +364 -541
  43. package/dist/server/server.js.map +1 -1
  44. package/dist/server/state.d.ts +16 -29
  45. package/dist/server/state.js +105 -319
  46. package/dist/server/state.js.map +1 -1
  47. package/dist/server/view.d.ts +6 -5
  48. package/dist/server/view.js +68 -51
  49. package/dist/server/view.js.map +1 -1
  50. package/dist/server/workflow-runner-entry.d.ts +1 -1
  51. package/dist/server/workflow-runner-entry.js +1 -49
  52. package/dist/server/workflow-runner-entry.js.map +1 -1
  53. package/dist/server/workflow-runner-protocol.d.ts +2 -7
  54. package/dist/server/workflow-runner-protocol.js +5 -10
  55. package/dist/server/workflow-runner-protocol.js.map +1 -1
  56. package/dist/server/workflow-runner-store.d.ts +5 -6
  57. package/dist/server/workflow-runner-store.js +7 -14
  58. package/dist/server/workflow-runner-store.js.map +1 -1
  59. package/dist/state/attempt-time.d.ts +21 -0
  60. package/dist/state/attempt-time.js +95 -0
  61. package/dist/state/attempt-time.js.map +1 -0
  62. package/dist/state/json.d.ts +2 -1
  63. package/dist/state/json.js.map +1 -1
  64. package/dist/state/project-store.d.ts +39 -0
  65. package/dist/state/project-store.js +154 -0
  66. package/dist/state/project-store.js.map +1 -0
  67. package/dist/state/prune.js +0 -7
  68. package/dist/state/prune.js.map +1 -1
  69. package/dist/state/schema.js +23 -23
  70. package/dist/state/workflow-messages.d.ts +1 -1
  71. package/dist/state/workflow-messages.js +3 -1
  72. package/dist/state/workflow-messages.js.map +1 -1
  73. package/dist/viewer/backup.js +5 -0
  74. package/dist/viewer/backup.js.map +1 -1
  75. package/dist/workflows/diagnostics.d.ts +8 -0
  76. package/dist/workflows/diagnostics.js +61 -0
  77. package/dist/workflows/diagnostics.js.map +1 -0
  78. package/dist/workflows/engine.d.ts +2 -16
  79. package/dist/workflows/engine.js +128 -252
  80. package/dist/workflows/engine.js.map +1 -1
  81. package/dist/workflows/errors.d.ts +1 -1
  82. package/dist/workflows/errors.js +2 -2
  83. package/dist/workflows/errors.js.map +1 -1
  84. package/dist/workflows/human-decision.d.ts +3 -7
  85. package/dist/workflows/human-decision.js +2 -56
  86. package/dist/workflows/human-decision.js.map +1 -1
  87. package/dist/workflows/index.d.ts +2 -1
  88. package/dist/workflows/index.js +1 -0
  89. package/dist/workflows/index.js.map +1 -1
  90. package/dist/workflows/queue.d.ts +267 -0
  91. package/dist/workflows/queue.js +1248 -0
  92. package/dist/workflows/queue.js.map +1 -0
  93. package/dist/workflows/requests.d.ts +47 -0
  94. package/dist/workflows/requests.js +158 -0
  95. package/dist/workflows/requests.js.map +1 -0
  96. package/dist/workflows/schema.js +2 -4
  97. package/dist/workflows/schema.js.map +1 -1
  98. package/dist/workflows/store.d.ts +11 -16
  99. package/dist/workflows/store.js +177 -174
  100. package/dist/workflows/store.js.map +1 -1
  101. package/dist/workflows/tool-input.d.ts +6 -9
  102. package/dist/workflows/tool-input.js +15 -12
  103. package/dist/workflows/tool-input.js.map +1 -1
  104. package/dist/workflows/transitions.d.ts +38 -0
  105. package/dist/workflows/transitions.js +276 -0
  106. package/dist/workflows/transitions.js.map +1 -0
  107. package/dist/workflows/types.d.ts +9 -42
  108. package/dist/workflows/workflow-message-content.d.ts +7 -1
  109. package/dist/workflows/workflow-message-content.js +22 -4
  110. package/dist/workflows/workflow-message-content.js.map +1 -1
  111. package/docs/2026-08-25-workflow-follow-ups.md +5 -5
  112. package/docs/2026-09-02-unify-workflow-messages-plan.md +2 -0
  113. package/docs/2026-09-04-workflow-run-state-plan.md +2 -0
  114. package/docs/2026-09-06-durable-execution-plan.md +176 -0
  115. package/docs/2026-09-07-workflow-handoff-plan.md +84 -0
  116. package/docs/DEFERRED_TURNS.md +84 -105
  117. package/docs/HUMAN_DECISIONS.md +17 -19
  118. package/docs/HUMAN_DECISION_PRESENTATIONS.md +1 -1
  119. package/docs/SQLITE_STATE.md +68 -29
  120. package/docs/WORKFLOW_SERVER.md +21 -10
  121. package/docs/WORKFLOW_STEP_MESSAGES.md +83 -54
  122. package/docs/plans/2026-08-04-controller-runtime-plan.md +2 -0
  123. package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +2 -0
  124. package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +2 -0
  125. package/docs/workflows.md +111 -45
  126. package/examples/workflows/autoresearch.workflow.ts +0 -2
  127. package/examples/workflows/branch.workflow.ts +0 -4
  128. package/examples/workflows/echo.workflow.ts +0 -2
  129. package/examples/workflows/live-settings.workflow.ts +0 -2
  130. package/examples/workflows/two-turn.workflow.ts +0 -2
  131. package/herdr-plugin.toml +1 -1
  132. package/package.json +1 -1
  133. package/protocol/fixtures/client-v1.json +1 -1
  134. package/skills/pi-workflows/SKILL.md +8 -7
  135. package/src/builtins/autoimplement.workflow.ts +31 -10
  136. package/src/client/client.ts +3 -1
  137. package/src/client/protocol.ts +21 -1
  138. package/src/client/view.ts +9 -5
  139. package/src/extension/index.ts +118 -190
  140. package/src/extension/recorder.ts +2 -2
  141. package/src/extension/step-message.ts +4 -4
  142. package/src/extension/widget.ts +1 -1
  143. package/src/extension/workflow-message-coordinator.ts +133 -95
  144. package/src/render/graph-render.ts +1 -1
  145. package/src/resource-managers/index.ts +1 -9
  146. package/src/resource-managers/sqlite.ts +330 -2655
  147. package/src/resource-managers/types.ts +2 -3
  148. package/src/server/resource-runner-entry.ts +2 -7
  149. package/src/server/rpc-bridge.ts +1 -1
  150. package/src/server/rpc-executor.ts +4 -9
  151. package/src/server/server-entry.ts +3 -0
  152. package/src/server/server.ts +416 -641
  153. package/src/server/state.ts +122 -466
  154. package/src/server/view.ts +73 -58
  155. package/src/server/workflow-runner-entry.ts +2 -55
  156. package/src/server/workflow-runner-protocol.ts +5 -15
  157. package/src/server/workflow-runner-store.ts +12 -27
  158. package/src/state/attempt-time.ts +122 -0
  159. package/src/state/json.ts +2 -1
  160. package/src/state/project-store.ts +218 -0
  161. package/src/state/prune.ts +0 -13
  162. package/src/state/schema.ts +23 -23
  163. package/src/state/workflow-messages.ts +4 -2
  164. package/src/viewer/backup.ts +7 -0
  165. package/src/workflows/diagnostics.ts +78 -0
  166. package/src/workflows/engine.ts +136 -299
  167. package/src/workflows/errors.ts +4 -2
  168. package/src/workflows/human-decision.ts +3 -81
  169. package/src/workflows/index.ts +8 -2
  170. package/src/workflows/queue.ts +2091 -0
  171. package/src/workflows/requests.ts +283 -0
  172. package/src/workflows/schema.ts +2 -6
  173. package/src/workflows/store.ts +226 -236
  174. package/src/workflows/tool-input.ts +20 -12
  175. package/src/workflows/transitions.ts +330 -0
  176. package/src/workflows/types.ts +9 -46
  177. package/src/workflows/workflow-message-content.ts +28 -4
  178. package/dist/resource-managers/runtime.d.ts +0 -59
  179. package/dist/resource-managers/runtime.js +0 -423
  180. package/dist/resource-managers/runtime.js.map +0 -1
  181. package/src/resource-managers/runtime.ts +0 -576
@@ -0,0 +1,283 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { StateDatabase } from "../state/database.js";
3
+ import { canonicalJson, type JsonValue } from "../state/json.js";
4
+ import { WorkflowMessageStore } from "../state/workflow-messages.js";
5
+ import type { HumanDecisionReceipt } from "./types.js";
6
+
7
+ export type WorkflowRequestKind = "agent" | "assistant" | "checkpoint" | "decision";
8
+
9
+ export type WorkflowCheckpointState = {
10
+ request: InteractiveRequestRecord;
11
+ output?: JsonValue;
12
+ humanDecision?: HumanDecisionReceipt;
13
+ };
14
+
15
+ export type InteractiveRequestRecord = {
16
+ requestId: string;
17
+ runId: string;
18
+ attemptId: string;
19
+ targetSessionId: string;
20
+ kind: WorkflowRequestKind;
21
+ contract: JsonValue;
22
+ revision: number;
23
+ status: "pending" | "settled" | "cancelled";
24
+ acceptedSubmissionId: string | null;
25
+ createdAt: string;
26
+ updatedAt: string;
27
+ settledAt: string | null;
28
+ consumedAt: string | null;
29
+ };
30
+
31
+ export function workflowRequestId(runId: string, attemptId: string): string {
32
+ return `request-${createHash("sha256")
33
+ .update(JSON.stringify([runId, attemptId]))
34
+ .digest("hex")}`;
35
+ }
36
+
37
+ /** Immutable identity and payload; an accepted request is never reopened. */
38
+ export function ensureWorkflowRequest(
39
+ state: StateDatabase,
40
+ options: Pick<
41
+ InteractiveRequestRecord,
42
+ "requestId" | "runId" | "attemptId" | "targetSessionId" | "kind" | "contract"
43
+ >,
44
+ now = Date.now(),
45
+ ): InteractiveRequestRecord {
46
+ return state.transaction(() => {
47
+ const existing = readWorkflowRequest(state, options.requestId);
48
+ if (existing !== undefined) {
49
+ if (
50
+ existing.runId !== options.runId ||
51
+ existing.attemptId !== options.attemptId ||
52
+ existing.targetSessionId !== options.targetSessionId ||
53
+ existing.kind !== options.kind ||
54
+ canonicalJson(existing.contract) !== canonicalJson(options.contract)
55
+ )
56
+ throw new Error(`Interactive request conflicts: ${options.requestId}`);
57
+ return existing;
58
+ }
59
+ const attempt = state.connection
60
+ .prepare("SELECT run_id AS runId FROM node_attempts WHERE attempt_id = ?")
61
+ .get(options.attemptId) as { runId: string } | undefined;
62
+ if (attempt?.runId !== options.runId)
63
+ throw new Error("Request does not match its run and attempt");
64
+ state.connection
65
+ .prepare(
66
+ `INSERT INTO interactive_requests(
67
+ request_id, run_id, attempt_id, target_session_id, kind, contract_hash,
68
+ revision, status, created_at, updated_at
69
+ ) VALUES (?, ?, ?, ?, ?, ?, 1, 'pending', ?, ?)`,
70
+ )
71
+ .run(
72
+ options.requestId,
73
+ options.runId,
74
+ options.attemptId,
75
+ options.targetSessionId,
76
+ options.kind,
77
+ state.putJson(options.contract, now),
78
+ now,
79
+ now,
80
+ );
81
+ const created = readWorkflowRequest(state, options.requestId);
82
+ if (created === undefined) throw new Error("Workflow request was not saved");
83
+ return created;
84
+ });
85
+ }
86
+
87
+ type RequestRow = {
88
+ requestId: string;
89
+ runId: string;
90
+ attemptId: string;
91
+ targetSessionId: string;
92
+ kind: WorkflowRequestKind;
93
+ contractHash: Buffer;
94
+ revision: number;
95
+ status: InteractiveRequestRecord["status"];
96
+ acceptedSubmissionId: string | null;
97
+ createdAt: number;
98
+ updatedAt: number;
99
+ settledAt: number | null;
100
+ consumedAt: number | null;
101
+ };
102
+
103
+ export function readWorkflowRequest(
104
+ state: StateDatabase,
105
+ requestId: string,
106
+ ): InteractiveRequestRecord | undefined {
107
+ const row = state.connection
108
+ .prepare(
109
+ `SELECT request_id AS requestId, run_id AS runId, attempt_id AS attemptId,
110
+ target_session_id AS targetSessionId, kind, contract_hash AS contractHash,
111
+ revision, status,
112
+ accepted_submission_id AS acceptedSubmissionId, created_at AS createdAt,
113
+ updated_at AS updatedAt, settled_at AS settledAt, consumed_at AS consumedAt
114
+ FROM interactive_requests WHERE request_id = ?`,
115
+ )
116
+ .get(requestId) as RequestRow | undefined;
117
+ if (row === undefined) return undefined;
118
+ const { contractHash, createdAt, updatedAt, settledAt, consumedAt, ...request } = row;
119
+ return {
120
+ ...request,
121
+ contract: state.readJson(contractHash),
122
+ createdAt: new Date(createdAt).toISOString(),
123
+ updatedAt: new Date(updatedAt).toISOString(),
124
+ settledAt: settledAt === null ? null : new Date(settledAt).toISOString(),
125
+ consumedAt: consumedAt === null ? null : new Date(consumedAt).toISOString(),
126
+ };
127
+ }
128
+
129
+ export function requestForAttempt(
130
+ state: StateDatabase,
131
+ runId: string,
132
+ attemptId: string,
133
+ ): InteractiveRequestRecord | undefined {
134
+ const row = state.connection
135
+ .prepare(
136
+ "SELECT request_id AS requestId FROM interactive_requests WHERE run_id = ? AND attempt_id = ?",
137
+ )
138
+ .get(runId, attemptId) as { requestId: string } | undefined;
139
+ return row === undefined ? undefined : readWorkflowRequest(state, row.requestId);
140
+ }
141
+
142
+ export function acceptedRequestOutput(
143
+ state: StateDatabase,
144
+ request: InteractiveRequestRecord,
145
+ ): JsonValue {
146
+ if (request.status !== "settled" || request.acceptedSubmissionId === null) {
147
+ throw new Error("Workflow request has no accepted output");
148
+ }
149
+ const row = state.connection
150
+ .prepare(
151
+ `SELECT payload_hash AS payloadHash FROM interactive_submissions
152
+ WHERE request_id = ? AND submission_id = ? AND outcome = 'accepted'`,
153
+ )
154
+ .get(request.requestId, request.acceptedSubmissionId) as { payloadHash: Buffer } | undefined;
155
+ if (row === undefined) throw new Error("Accepted workflow response is missing");
156
+ return state.readJson(row.payloadHash);
157
+ }
158
+
159
+ export type WorkflowSubmissionOptions = {
160
+ requestId: string;
161
+ submissionId: string;
162
+ idempotencyKey: string;
163
+ expectedRevision: number;
164
+ payload: JsonValue;
165
+ outcome: "validating" | "accepted" | "rejected" | "adopted";
166
+ settle: boolean;
167
+ receipt?: JsonValue;
168
+ };
169
+
170
+ /** Record one exact response candidate or winner under the request revision. */
171
+ export function recordWorkflowSubmission(
172
+ state: StateDatabase,
173
+ options: WorkflowSubmissionOptions,
174
+ ): {
175
+ interaction: InteractiveRequestRecord;
176
+ submissionId: string;
177
+ outcome: "accepted" | "adopted";
178
+ receipt: JsonValue;
179
+ } {
180
+ return state.transaction(() => {
181
+ const request = readWorkflowRequest(state, options.requestId);
182
+ if (request === undefined) throw new Error("Workflow request is missing");
183
+ const payloadHash = createHash("sha256").update(canonicalJson(options.payload)).digest();
184
+ const existing = state.connection
185
+ .prepare(
186
+ `SELECT submission_id AS submissionId, payload_hash AS payloadHash, receipt_hash AS receiptHash
187
+ FROM interactive_submissions WHERE request_id = ? AND idempotency_key = ?`,
188
+ )
189
+ .get(options.requestId, options.idempotencyKey) as
190
+ | { submissionId: string; payloadHash: Buffer; receiptHash: Buffer | null }
191
+ | undefined;
192
+ if (existing !== undefined) {
193
+ if (!existing.payloadHash.equals(payloadHash))
194
+ throw new Error("Interactive submission idempotency key conflicts");
195
+ return {
196
+ interaction: request,
197
+ submissionId: existing.submissionId,
198
+ outcome: "adopted",
199
+ receipt:
200
+ existing.receiptHash === null
201
+ ? { requestId: request.requestId, submissionId: existing.submissionId }
202
+ : state.readJson(existing.receiptHash),
203
+ };
204
+ }
205
+ if (request.revision !== options.expectedRevision || request.status !== "pending") {
206
+ throw new Error("Interactive request revision conflict");
207
+ }
208
+ if (
209
+ options.outcome === "validating" &&
210
+ state.connection
211
+ .prepare(
212
+ "SELECT 1 FROM interactive_submissions WHERE request_id = ? AND outcome = 'validating'",
213
+ )
214
+ .get(options.requestId) !== undefined
215
+ )
216
+ throw new Error("Interactive submission validation is already active");
217
+ if (options.settle && options.outcome !== "accepted")
218
+ throw new Error("Only accepted output can settle a request");
219
+ if (request.kind === "decision" && options.settle) {
220
+ const resolution = state.connection
221
+ .prepare(
222
+ `SELECT r.response_hash AS responseHash FROM human_decision_resolutions r
223
+ JOIN human_decisions d ON d.decision_id = r.decision_id
224
+ WHERE d.decision_id = ? AND d.run_id = ? AND d.attempt_id = ? AND r.outcome = 'accepted'`,
225
+ )
226
+ .get(request.requestId, request.runId, request.attemptId) as
227
+ | { responseHash: Buffer }
228
+ | undefined;
229
+ const decision =
230
+ resolution === undefined
231
+ ? undefined
232
+ : (state.readJson(resolution.responseHash) as { response?: JsonValue });
233
+ if (
234
+ decision?.response === undefined ||
235
+ canonicalJson(decision.response) !== canonicalJson(options.payload)
236
+ ) {
237
+ throw new Error("Checkpoint response does not match a verified human decision");
238
+ }
239
+ }
240
+ const now = Date.now();
241
+ state.connection
242
+ .prepare(
243
+ `INSERT INTO interactive_submissions(
244
+ submission_id, request_id, idempotency_key, request_revision,
245
+ payload_hash, outcome, receipt_hash, submitted_at
246
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
247
+ )
248
+ .run(
249
+ options.submissionId,
250
+ request.requestId,
251
+ options.idempotencyKey,
252
+ options.expectedRevision,
253
+ state.putJson(options.payload, now),
254
+ options.outcome,
255
+ options.receipt === undefined ? null : state.putJson(options.receipt, now),
256
+ now,
257
+ );
258
+ if (options.settle) {
259
+ const changed = state.connection
260
+ .prepare(
261
+ `UPDATE interactive_requests SET status = 'settled', accepted_submission_id = ?,
262
+ revision = revision + 1, updated_at = ?, settled_at = ?
263
+ WHERE request_id = ? AND revision = ? AND status = 'pending'`,
264
+ )
265
+ .run(options.submissionId, now, now, request.requestId, options.expectedRevision);
266
+ if (changed.changes !== 1) throw new Error("Workflow response lost its request revision");
267
+ const messages = new WorkflowMessageStore(state);
268
+ messages.cancelPendingForSource(request.requestId, "step", now);
269
+ messages.cancelPendingForSource(request.requestId, "decision", now);
270
+ }
271
+ const interaction = readWorkflowRequest(state, request.requestId);
272
+ if (interaction === undefined) throw new Error("Workflow request disappeared after acceptance");
273
+ return {
274
+ interaction,
275
+ submissionId: options.submissionId,
276
+ outcome: "accepted",
277
+ receipt: options.receipt ?? {
278
+ requestId: request.requestId,
279
+ submissionId: options.submissionId,
280
+ },
281
+ };
282
+ });
283
+ }
@@ -317,12 +317,8 @@ export function assertValidWorkflowDefinitionShape(
317
317
  ) {
318
318
  fail("workflow title must be a string or function");
319
319
  }
320
- if (
321
- definition.presentationPrompt !== undefined &&
322
- typeof definition.presentationPrompt !== "string" &&
323
- typeof definition.presentationPrompt !== "function"
324
- ) {
325
- fail("workflow presentationPrompt must be a string or function");
320
+ if ("presentationPrompt" in definition) {
321
+ fail("presentationPrompt is not supported; use an explicit assistant-response node");
326
322
  }
327
323
  if (typeof definition.startAt !== "string" || definition.startAt.length === 0) {
328
324
  fail("workflow requires startAt");