@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.
Files changed (173) 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 +115 -165
  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/workflow-message-coordinator.d.ts +12 -1
  21. package/dist/extension/workflow-message-coordinator.js +65 -54
  22. package/dist/extension/workflow-message-coordinator.js.map +1 -1
  23. package/dist/resource-managers/index.d.ts +1 -2
  24. package/dist/resource-managers/index.js +1 -2
  25. package/dist/resource-managers/index.js.map +1 -1
  26. package/dist/resource-managers/sqlite.d.ts +2 -295
  27. package/dist/resource-managers/sqlite.js +71 -1528
  28. package/dist/resource-managers/sqlite.js.map +1 -1
  29. package/dist/resource-managers/types.d.ts +2 -7
  30. package/dist/server/resource-runner-entry.js +2 -6
  31. package/dist/server/resource-runner-entry.js.map +1 -1
  32. package/dist/server/rpc-bridge.js +1 -1
  33. package/dist/server/rpc-bridge.js.map +1 -1
  34. package/dist/server/rpc-executor.js +2 -4
  35. package/dist/server/rpc-executor.js.map +1 -1
  36. package/dist/server/server-entry.js +3 -0
  37. package/dist/server/server-entry.js.map +1 -1
  38. package/dist/server/server.d.ts +22 -8
  39. package/dist/server/server.js +498 -543
  40. package/dist/server/server.js.map +1 -1
  41. package/dist/server/state.d.ts +16 -29
  42. package/dist/server/state.js +105 -319
  43. package/dist/server/state.js.map +1 -1
  44. package/dist/server/view.d.ts +4 -5
  45. package/dist/server/view.js +51 -51
  46. package/dist/server/view.js.map +1 -1
  47. package/dist/server/workflow-runner-entry.d.ts +1 -1
  48. package/dist/server/workflow-runner-entry.js +1 -49
  49. package/dist/server/workflow-runner-entry.js.map +1 -1
  50. package/dist/server/workflow-runner-protocol.d.ts +2 -7
  51. package/dist/server/workflow-runner-protocol.js +5 -10
  52. package/dist/server/workflow-runner-protocol.js.map +1 -1
  53. package/dist/server/workflow-runner-store.d.ts +5 -6
  54. package/dist/server/workflow-runner-store.js +7 -14
  55. package/dist/server/workflow-runner-store.js.map +1 -1
  56. package/dist/state/attempt-time.d.ts +21 -0
  57. package/dist/state/attempt-time.js +95 -0
  58. package/dist/state/attempt-time.js.map +1 -0
  59. package/dist/state/json.d.ts +2 -1
  60. package/dist/state/json.js.map +1 -1
  61. package/dist/state/project-store.d.ts +39 -0
  62. package/dist/state/project-store.js +154 -0
  63. package/dist/state/project-store.js.map +1 -0
  64. package/dist/state/prune.d.ts +28 -0
  65. package/dist/state/prune.js +278 -83
  66. package/dist/state/prune.js.map +1 -1
  67. package/dist/state/schema.js +23 -23
  68. package/dist/state/workflow-messages.d.ts +1 -1
  69. package/dist/state/workflow-messages.js +3 -1
  70. package/dist/state/workflow-messages.js.map +1 -1
  71. package/dist/viewer/backup.js +5 -0
  72. package/dist/viewer/backup.js.map +1 -1
  73. package/dist/workflows/diagnostics.d.ts +8 -0
  74. package/dist/workflows/diagnostics.js +61 -0
  75. package/dist/workflows/diagnostics.js.map +1 -0
  76. package/dist/workflows/engine.d.ts +2 -15
  77. package/dist/workflows/engine.js +125 -245
  78. package/dist/workflows/engine.js.map +1 -1
  79. package/dist/workflows/human-decision.d.ts +3 -7
  80. package/dist/workflows/human-decision.js +2 -56
  81. package/dist/workflows/human-decision.js.map +1 -1
  82. package/dist/workflows/index.d.ts +2 -1
  83. package/dist/workflows/index.js +1 -0
  84. package/dist/workflows/index.js.map +1 -1
  85. package/dist/workflows/queue.d.ts +267 -0
  86. package/dist/workflows/queue.js +1248 -0
  87. package/dist/workflows/queue.js.map +1 -0
  88. package/dist/workflows/requests.d.ts +47 -0
  89. package/dist/workflows/requests.js +158 -0
  90. package/dist/workflows/requests.js.map +1 -0
  91. package/dist/workflows/schema.js +2 -4
  92. package/dist/workflows/schema.js.map +1 -1
  93. package/dist/workflows/store.d.ts +11 -16
  94. package/dist/workflows/store.js +174 -169
  95. package/dist/workflows/store.js.map +1 -1
  96. package/dist/workflows/tool-input.d.ts +6 -9
  97. package/dist/workflows/tool-input.js +15 -12
  98. package/dist/workflows/tool-input.js.map +1 -1
  99. package/dist/workflows/transitions.d.ts +38 -0
  100. package/dist/workflows/transitions.js +276 -0
  101. package/dist/workflows/transitions.js.map +1 -0
  102. package/dist/workflows/types.d.ts +9 -42
  103. package/dist/workflows/workflow-message-content.d.ts +7 -1
  104. package/dist/workflows/workflow-message-content.js +22 -4
  105. package/dist/workflows/workflow-message-content.js.map +1 -1
  106. package/docs/2026-08-25-workflow-follow-ups.md +5 -5
  107. package/docs/2026-09-02-unify-workflow-messages-plan.md +2 -0
  108. package/docs/2026-09-04-workflow-run-state-plan.md +2 -0
  109. package/docs/2026-09-06-durable-execution-plan.md +176 -0
  110. package/docs/DEFERRED_TURNS.md +84 -105
  111. package/docs/HUMAN_DECISIONS.md +17 -19
  112. package/docs/HUMAN_DECISION_PRESENTATIONS.md +1 -1
  113. package/docs/SQLITE_STATE.md +87 -30
  114. package/docs/WORKFLOW_SERVER.md +50 -11
  115. package/docs/WORKFLOW_STEP_MESSAGES.md +72 -54
  116. package/docs/plans/2026-08-04-controller-runtime-plan.md +2 -0
  117. package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +2 -0
  118. package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +2 -0
  119. package/docs/plans/2026-09-04-automatic-state-retention-plan.md +283 -0
  120. package/docs/workflows.md +109 -44
  121. package/examples/workflows/autoresearch.workflow.ts +0 -2
  122. package/examples/workflows/branch.workflow.ts +0 -4
  123. package/examples/workflows/echo.workflow.ts +0 -2
  124. package/examples/workflows/live-settings.workflow.ts +0 -2
  125. package/examples/workflows/two-turn.workflow.ts +0 -2
  126. package/herdr-plugin.toml +1 -1
  127. package/package.json +1 -1
  128. package/protocol/fixtures/client-v1.json +1 -1
  129. package/skills/pi-workflows/SKILL.md +8 -7
  130. package/src/builtins/autoimplement.workflow.ts +31 -10
  131. package/src/client/client.ts +3 -1
  132. package/src/client/protocol.ts +21 -1
  133. package/src/client/view.ts +9 -5
  134. package/src/extension/index.ts +117 -189
  135. package/src/extension/recorder.ts +2 -2
  136. package/src/extension/step-message.ts +4 -4
  137. package/src/extension/workflow-message-coordinator.ts +78 -50
  138. package/src/resource-managers/index.ts +1 -9
  139. package/src/resource-managers/sqlite.ts +330 -2655
  140. package/src/resource-managers/types.ts +2 -3
  141. package/src/server/resource-runner-entry.ts +2 -7
  142. package/src/server/rpc-bridge.ts +1 -1
  143. package/src/server/rpc-executor.ts +4 -9
  144. package/src/server/server-entry.ts +3 -0
  145. package/src/server/server.ts +568 -644
  146. package/src/server/state.ts +122 -466
  147. package/src/server/view.ts +56 -60
  148. package/src/server/workflow-runner-entry.ts +2 -55
  149. package/src/server/workflow-runner-protocol.ts +5 -15
  150. package/src/server/workflow-runner-store.ts +12 -27
  151. package/src/state/attempt-time.ts +122 -0
  152. package/src/state/json.ts +2 -1
  153. package/src/state/project-store.ts +218 -0
  154. package/src/state/prune.ts +408 -95
  155. package/src/state/schema.ts +23 -23
  156. package/src/state/workflow-messages.ts +4 -2
  157. package/src/viewer/backup.ts +7 -0
  158. package/src/workflows/diagnostics.ts +78 -0
  159. package/src/workflows/engine.ts +133 -291
  160. package/src/workflows/human-decision.ts +3 -81
  161. package/src/workflows/index.ts +8 -2
  162. package/src/workflows/queue.ts +2091 -0
  163. package/src/workflows/requests.ts +283 -0
  164. package/src/workflows/schema.ts +2 -6
  165. package/src/workflows/store.ts +223 -231
  166. package/src/workflows/tool-input.ts +20 -12
  167. package/src/workflows/transitions.ts +330 -0
  168. package/src/workflows/types.ts +9 -46
  169. package/src/workflows/workflow-message-content.ts +28 -4
  170. package/dist/resource-managers/runtime.d.ts +0 -59
  171. package/dist/resource-managers/runtime.js +0 -423
  172. package/dist/resource-managers/runtime.js.map +0 -1
  173. package/src/resource-managers/runtime.ts +0 -576
@@ -16,19 +16,27 @@ import {
16
16
 
17
17
  const WORKFLOW_MESSAGE_ID_FIELD = "workflowMessageId";
18
18
 
19
+ type SettledTurn = { stopReason: WorkflowTurnStopReason; responseSessionEntryId: string | null };
20
+ type BeforeTurnEnd = (message: WorkflowMessage, end: SettledTurn) => Promise<void>;
21
+ type DeliveryCallbacks = {
22
+ beforeTurnEnd?: BeforeTurnEnd;
23
+ terminalDelivered?: (message: WorkflowMessage) => Promise<void>;
24
+ };
25
+
19
26
  type PendingTurn = {
20
27
  workflowTurnId: string;
21
28
  workflowMessageId: string | null;
22
29
  runId: string | null;
23
30
  message: WorkflowMessage | null;
24
31
  startedReported: boolean;
25
- end: { stopReason: WorkflowTurnStopReason; responseSessionEntryId: string | null } | null;
32
+ end: SettledTurn | null;
26
33
  };
27
34
 
28
35
  /** Adds every server-owned workflow message to one Pi session through one public API path. */
29
36
  export class WorkflowMessageCoordinator {
30
37
  private readonly queued = new Set<string>();
31
38
  private readonly closedTurnMessages = new Set<string>();
39
+ private readonly finalizedTerminals = new Set<string>();
32
40
  private synchronizing = false;
33
41
  private lastBranchEpoch: string | null = null;
34
42
  private view: WorkflowSessionView | null = null;
@@ -41,19 +49,14 @@ export class WorkflowMessageCoordinator {
41
49
  for (const messageId of this.closedTurnMessages) {
42
50
  if (!visibleIds.has(messageId)) this.closedTurnMessages.delete(messageId);
43
51
  }
52
+ for (const messageId of this.finalizedTerminals) {
53
+ if (!visibleIds.has(messageId)) this.finalizedTerminals.delete(messageId);
54
+ }
44
55
  for (const message of view.workflowMessages) {
45
56
  if (message.status === "sent" || message.status === "cancelled") {
46
57
  this.queued.delete(message.workflowMessageId);
47
58
  }
48
59
  }
49
- if (
50
- this.turn !== null &&
51
- this.turn.workflowMessageId === null &&
52
- view.openWorkflowMessageId !== null
53
- ) {
54
- const message = messageById(view, view.openWorkflowMessageId);
55
- if (message !== undefined && messageStartsTurn(message)) this.bindTurn(message);
56
- }
57
60
  }
58
61
 
59
62
  branchChanged(): void {
@@ -61,6 +64,8 @@ export class WorkflowMessageCoordinator {
61
64
  }
62
65
 
63
66
  startTurn(): void {
67
+ // Automatic Pi retries belong to the same unsettled workflow turn.
68
+ if (this.turn !== null) return;
64
69
  const awaited = this.awaitingTurnMessage;
65
70
  this.awaitingTurnMessage = null;
66
71
  this.turn = {
@@ -71,13 +76,12 @@ export class WorkflowMessageCoordinator {
71
76
  startedReported: false,
72
77
  end: null,
73
78
  };
74
- const candidate = awaited ?? this.turnCandidate();
75
- if (candidate !== undefined) this.bindTurn(candidate);
79
+ if (awaited !== null) this.bindTurn(awaited);
76
80
  }
77
81
 
78
82
  endTurn(stopReason: WorkflowTurnStopReason, responseSessionEntryId: string | null): void {
79
- if (this.turn === null) this.startTurn();
80
- if (this.turn !== null) this.turn.end = { stopReason, responseSessionEntryId };
83
+ if (this.turn !== null && this.turn.end === null)
84
+ this.turn.end = { stopReason, responseSessionEntryId };
81
85
  }
82
86
 
83
87
  activeTurnMessage(): WorkflowMessage | undefined {
@@ -89,13 +93,26 @@ export class WorkflowMessageCoordinator {
89
93
  pi: ExtensionAPI,
90
94
  client: WorkflowClient,
91
95
  ctx: Pick<ExtensionContext, "hasPendingMessages" | "isIdle" | "sessionManager">,
96
+ callbacks: DeliveryCallbacks = {},
92
97
  ): Promise<void> {
93
98
  if (this.synchronizing || this.view === null) return;
94
99
  this.synchronizing = true;
95
100
  try {
96
101
  const view = this.view;
97
102
  if (!view.coordinatorActive || view.coordinatorEpoch === null) return;
98
- if (this.turn === null && !ctx.isIdle() && view.openWorkflowTurn !== null) {
103
+ const branchEntries = branchWorkflowEntries(ctx.sessionManager.getBranch());
104
+ if (this.turn?.workflowMessageId === null) {
105
+ const candidate = this.turnCandidate();
106
+ if (candidate !== undefined && branchEntries.has(candidate.workflowMessageId)) {
107
+ this.bindTurn(candidate);
108
+ }
109
+ }
110
+ if (
111
+ this.turn === null &&
112
+ !ctx.isIdle() &&
113
+ view.openWorkflowTurn !== null &&
114
+ branchEntries.has(view.openWorkflowTurn.workflowMessageId)
115
+ ) {
99
116
  this.turn = {
100
117
  workflowTurnId: view.openWorkflowTurn.workflowTurnId,
101
118
  workflowMessageId: view.openWorkflowTurn.workflowMessageId,
@@ -112,7 +129,19 @@ export class WorkflowMessageCoordinator {
112
129
  ) {
113
130
  await this.reportBranch(client, ctx, view);
114
131
  }
115
- await this.flushTurn(client, view);
132
+ await this.flushTurn(client, view, callbacks.beforeTurnEnd);
133
+ if (this.turn !== null || !ctx.isIdle() || ctx.hasPendingMessages()) return;
134
+ for (const message of view.workflowMessages) {
135
+ if (
136
+ message.kind === "terminal" &&
137
+ message.status === "sent" &&
138
+ branchEntries.has(message.workflowMessageId) &&
139
+ !this.finalizedTerminals.has(message.workflowMessageId)
140
+ ) {
141
+ await callbacks.terminalDelivered?.(message);
142
+ this.finalizedTerminals.add(message.workflowMessageId);
143
+ }
144
+ }
116
145
  const messageId = view.nextWorkflowMessageId;
117
146
  if (messageId === null || this.queued.has(messageId)) return;
118
147
  const message = messageById(view, messageId);
@@ -140,7 +169,7 @@ export class WorkflowMessageCoordinator {
140
169
  this.queued.delete(messageId);
141
170
  return;
142
171
  }
143
- if (message.content.triggerTurn && messageStartsTurn(message)) {
172
+ if (messageStartsTurn(message)) {
144
173
  this.awaitingTurnMessage = message;
145
174
  }
146
175
  try {
@@ -161,7 +190,7 @@ export class WorkflowMessageCoordinator {
161
190
  throw error;
162
191
  }
163
192
  await this.reportBranch(client, ctx, view);
164
- await this.flushTurn(client, view);
193
+ await this.flushTurn(client, view, callbacks.beforeTurnEnd);
165
194
  } finally {
166
195
  this.synchronizing = false;
167
196
  }
@@ -170,6 +199,7 @@ export class WorkflowMessageCoordinator {
170
199
  clear(): void {
171
200
  this.queued.clear();
172
201
  this.closedTurnMessages.clear();
202
+ this.finalizedTerminals.clear();
173
203
  this.view = null;
174
204
  this.awaitingTurnMessage = null;
175
205
  this.turn = null;
@@ -204,7 +234,11 @@ export class WorkflowMessageCoordinator {
204
234
  this.turn.message = message;
205
235
  }
206
236
 
207
- private async flushTurn(client: WorkflowClient, view: WorkflowSessionView): Promise<void> {
237
+ private async flushTurn(
238
+ client: WorkflowClient,
239
+ view: WorkflowSessionView,
240
+ beforeTurnEnd?: BeforeTurnEnd,
241
+ ): Promise<void> {
208
242
  const pending = this.turn;
209
243
  if (
210
244
  pending === null ||
@@ -219,43 +253,37 @@ export class WorkflowMessageCoordinator {
219
253
  if (!pending.startedReported) {
220
254
  message = messageById(view, pending.workflowMessageId) ?? null;
221
255
  if (message?.status !== "sent") return;
222
- try {
223
- const receipt = await reportTurn(client, {
224
- state: "started",
225
- workflowMessageId: pending.workflowMessageId,
226
- workflowTurnId: pending.workflowTurnId,
227
- runId: pending.runId,
228
- targetSessionId: view.sessionId,
229
- coordinatorEpoch: view.coordinatorEpoch,
230
- });
231
- if (receipt.ownership !== "active") {
232
- this.turn = null;
233
- return;
234
- }
235
- pending.message = message;
236
- pending.startedReported = true;
237
- } catch (error) {
238
- this.turn = null;
239
- throw error;
240
- }
241
- }
242
- if (pending.end === null) return;
243
- try {
244
- await reportTurn(client, {
245
- state: "ended",
256
+ const receipt = await reportTurn(client, {
257
+ state: "started",
246
258
  workflowMessageId: pending.workflowMessageId,
247
259
  workflowTurnId: pending.workflowTurnId,
248
260
  runId: pending.runId,
249
261
  targetSessionId: view.sessionId,
250
262
  coordinatorEpoch: view.coordinatorEpoch,
251
- stopReason: pending.end.stopReason,
252
- responseSessionEntryId: pending.end.responseSessionEntryId,
253
263
  });
254
- } catch (error) {
255
- this.turn = null;
256
- throw error;
264
+ if (
265
+ receipt.ownership !== "active" &&
266
+ !(receipt.ownership === "settled" && pending.end !== null)
267
+ ) {
268
+ this.turn = null;
269
+ return;
270
+ }
271
+ pending.message = message;
272
+ pending.startedReported = true;
257
273
  }
258
- if (message?.kind === "terminal" || message?.kind === "followUp") {
274
+ if (pending.end === null) return;
275
+ if (message !== null) await beforeTurnEnd?.(message, pending.end);
276
+ await reportTurn(client, {
277
+ state: "ended",
278
+ workflowMessageId: pending.workflowMessageId,
279
+ workflowTurnId: pending.workflowTurnId,
280
+ runId: pending.runId,
281
+ targetSessionId: view.sessionId,
282
+ coordinatorEpoch: view.coordinatorEpoch,
283
+ stopReason: pending.end.stopReason,
284
+ responseSessionEntryId: pending.end.responseSessionEntryId,
285
+ });
286
+ if (message?.kind === "followUp") {
259
287
  this.closedTurnMessages.add(pending.workflowMessageId);
260
288
  }
261
289
  for (const current of new Set([view, this.view])) {
@@ -264,7 +292,7 @@ export class WorkflowMessageCoordinator {
264
292
  }
265
293
  if (
266
294
  current?.openWorkflowMessageId === pending.workflowMessageId &&
267
- (message?.kind === "terminal" || message?.kind === "followUp")
295
+ message?.kind === "followUp"
268
296
  ) {
269
297
  current.openWorkflowMessageId = null;
270
298
  }
@@ -351,7 +379,7 @@ function messageById(
351
379
  }
352
380
 
353
381
  function messageStartsTurn(message: WorkflowMessage): boolean {
354
- return message.kind === "step" || message.kind === "terminal" || message.kind === "followUp";
382
+ return message.content.triggerTurn;
355
383
  }
356
384
 
357
385
  async function reportTurn(
@@ -18,16 +18,8 @@ export {
18
18
  } from "./errors.js";
19
19
  export { ResourceManagerEffectService } from "./effects.js";
20
20
  export { canonicalJson, jsonFingerprint } from "./json.js";
21
- export { ResourceManagerRuntime, type ResourceManagerRuntimeOptions } from "./runtime.js";
22
21
  export { createResultHelpers, requeue, requeueAfter, settled } from "./results.js";
23
- export {
24
- SqliteResourceManagerStore,
25
- type RunEventRecord,
26
- type WorkflowRunClaimOptions,
27
- type WorkflowRunPreparationResult,
28
- type WorkflowRunQueueRecord,
29
- type WorkflowRunReservationOptions,
30
- } from "./sqlite.js";
22
+ export { SqliteResourceManagerStore } from "./sqlite.js";
31
23
  export {
32
24
  type ResourceManagerStore,
33
25
  type EffectReservation,