@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
@@ -16,24 +16,28 @@ import {
16
16
 
17
17
  const WORKFLOW_MESSAGE_ID_FIELD = "workflowMessageId";
18
18
 
19
- type PendingTurn = {
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
+
26
+ type OwnedTurn = {
20
27
  workflowTurnId: string;
21
- workflowMessageId: string | null;
22
- runId: string | null;
23
- message: WorkflowMessage | null;
28
+ message: WorkflowMessage;
24
29
  startedReported: boolean;
25
- end: { stopReason: WorkflowTurnStopReason; responseSessionEntryId: string | null } | null;
26
- };
30
+ } & ({ phase: "delivering" | "running" } | { phase: "settled"; end: SettledTurn });
27
31
 
28
32
  /** Adds every server-owned workflow message to one Pi session through one public API path. */
29
33
  export class WorkflowMessageCoordinator {
30
34
  private readonly queued = new Set<string>();
31
35
  private readonly closedTurnMessages = new Set<string>();
36
+ private readonly finalizedTerminals = new Set<string>();
32
37
  private synchronizing = false;
33
38
  private lastBranchEpoch: string | null = null;
34
39
  private view: WorkflowSessionView | null = null;
35
- private awaitingTurnMessage: WorkflowMessage | null = null;
36
- private turn: PendingTurn | null = null;
40
+ private turn: OwnedTurn | null = null;
37
41
 
38
42
  updateView(view: WorkflowSessionView): void {
39
43
  this.view = view;
@@ -41,19 +45,14 @@ export class WorkflowMessageCoordinator {
41
45
  for (const messageId of this.closedTurnMessages) {
42
46
  if (!visibleIds.has(messageId)) this.closedTurnMessages.delete(messageId);
43
47
  }
48
+ for (const messageId of this.finalizedTerminals) {
49
+ if (!visibleIds.has(messageId)) this.finalizedTerminals.delete(messageId);
50
+ }
44
51
  for (const message of view.workflowMessages) {
45
52
  if (message.status === "sent" || message.status === "cancelled") {
46
53
  this.queued.delete(message.workflowMessageId);
47
54
  }
48
55
  }
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
56
  }
58
57
 
59
58
  branchChanged(): void {
@@ -61,23 +60,15 @@ export class WorkflowMessageCoordinator {
61
60
  }
62
61
 
63
62
  startTurn(): void {
64
- const awaited = this.awaitingTurnMessage;
65
- this.awaitingTurnMessage = null;
66
- this.turn = {
67
- workflowTurnId: `workflow-turn-${randomUUID()}`,
68
- workflowMessageId: null,
69
- runId: null,
70
- message: null,
71
- startedReported: false,
72
- end: null,
73
- };
74
- const candidate = awaited ?? this.turnCandidate();
75
- if (candidate !== undefined) this.bindTurn(candidate);
63
+ // Ordinary chat owns no workflow turn. Automatic Pi retries retain the
64
+ // existing turn, including a settled result whose acknowledgment is pending.
65
+ if (this.turn?.phase === "delivering") this.turn = { ...this.turn, phase: "running" };
76
66
  }
77
67
 
78
68
  endTurn(stopReason: WorkflowTurnStopReason, responseSessionEntryId: string | null): void {
79
- if (this.turn === null) this.startTurn();
80
- if (this.turn !== null) this.turn.end = { stopReason, responseSessionEntryId };
69
+ if (this.turn?.phase === "running") {
70
+ this.turn = { ...this.turn, phase: "settled", end: { stopReason, responseSessionEntryId } };
71
+ }
81
72
  }
82
73
 
83
74
  activeTurnMessage(): WorkflowMessage | undefined {
@@ -89,21 +80,32 @@ export class WorkflowMessageCoordinator {
89
80
  pi: ExtensionAPI,
90
81
  client: WorkflowClient,
91
82
  ctx: Pick<ExtensionContext, "hasPendingMessages" | "isIdle" | "sessionManager">,
83
+ callbacks: DeliveryCallbacks = {},
92
84
  ): Promise<void> {
93
85
  if (this.synchronizing || this.view === null) return;
94
86
  this.synchronizing = true;
95
87
  try {
96
88
  const view = this.view;
97
89
  if (!view.coordinatorActive || view.coordinatorEpoch === null) return;
98
- if (this.turn === null && !ctx.isIdle() && view.openWorkflowTurn !== null) {
99
- this.turn = {
100
- workflowTurnId: view.openWorkflowTurn.workflowTurnId,
101
- workflowMessageId: view.openWorkflowTurn.workflowMessageId,
102
- runId: view.openWorkflowTurn.runId,
103
- message: messageById(view, view.openWorkflowTurn.workflowMessageId) ?? null,
104
- startedReported: true,
105
- end: null,
106
- };
90
+ const branchEntries = branchWorkflowEntries(ctx.sessionManager.getBranch());
91
+ if (this.turn === null && !ctx.isIdle()) {
92
+ const candidate = this.turnCandidate();
93
+ const open = view.openWorkflowTurn;
94
+ if (
95
+ candidate !== undefined &&
96
+ open?.state === "started" &&
97
+ open.workflowMessageId === candidate.workflowMessageId &&
98
+ open.runId === candidate.runId &&
99
+ open.targetSessionId === view.sessionId &&
100
+ latestTurnInputIsWorkflow(ctx.sessionManager.getBranch(), candidate.workflowMessageId)
101
+ ) {
102
+ this.turn = {
103
+ workflowTurnId: open.workflowTurnId,
104
+ message: candidate,
105
+ startedReported: true,
106
+ phase: "running",
107
+ };
108
+ }
107
109
  }
108
110
  if (
109
111
  view.branchReportRequired ||
@@ -112,7 +114,19 @@ export class WorkflowMessageCoordinator {
112
114
  ) {
113
115
  await this.reportBranch(client, ctx, view);
114
116
  }
115
- await this.flushTurn(client, view);
117
+ await this.flushTurn(client, view, callbacks.beforeTurnEnd);
118
+ if (this.turn !== null || !ctx.isIdle() || ctx.hasPendingMessages()) return;
119
+ for (const message of view.workflowMessages) {
120
+ if (
121
+ message.kind === "terminal" &&
122
+ message.status === "sent" &&
123
+ branchEntries.has(message.workflowMessageId) &&
124
+ !this.finalizedTerminals.has(message.workflowMessageId)
125
+ ) {
126
+ await callbacks.terminalDelivered?.(message);
127
+ this.finalizedTerminals.add(message.workflowMessageId);
128
+ }
129
+ }
116
130
  const messageId = view.nextWorkflowMessageId;
117
131
  if (messageId === null || this.queued.has(messageId)) return;
118
132
  const message = messageById(view, messageId);
@@ -140,8 +154,13 @@ export class WorkflowMessageCoordinator {
140
154
  this.queued.delete(messageId);
141
155
  return;
142
156
  }
143
- if (message.content.triggerTurn && messageStartsTurn(message)) {
144
- this.awaitingTurnMessage = message;
157
+ if (messageStartsTurn(message)) {
158
+ this.turn = {
159
+ workflowTurnId: `workflow-turn-${randomUUID()}`,
160
+ message,
161
+ startedReported: false,
162
+ phase: "delivering",
163
+ };
145
164
  }
146
165
  try {
147
166
  pi.sendMessage(
@@ -155,13 +174,16 @@ export class WorkflowMessageCoordinator {
155
174
  );
156
175
  } catch (error) {
157
176
  this.queued.delete(messageId);
158
- if (this.awaitingTurnMessage?.workflowMessageId === messageId) {
159
- this.awaitingTurnMessage = null;
177
+ if (
178
+ this.turn?.phase === "delivering" &&
179
+ this.turn.message.workflowMessageId === messageId
180
+ ) {
181
+ this.turn = null;
160
182
  }
161
183
  throw error;
162
184
  }
163
185
  await this.reportBranch(client, ctx, view);
164
- await this.flushTurn(client, view);
186
+ await this.flushTurn(client, view, callbacks.beforeTurnEnd);
165
187
  } finally {
166
188
  this.synchronizing = false;
167
189
  }
@@ -170,8 +192,8 @@ export class WorkflowMessageCoordinator {
170
192
  clear(): void {
171
193
  this.queued.clear();
172
194
  this.closedTurnMessages.clear();
195
+ this.finalizedTerminals.clear();
173
196
  this.view = null;
174
- this.awaitingTurnMessage = null;
175
197
  this.turn = null;
176
198
  this.lastBranchEpoch = null;
177
199
  this.synchronizing = false;
@@ -197,19 +219,15 @@ export class WorkflowMessageCoordinator {
197
219
  return undefined;
198
220
  }
199
221
 
200
- private bindTurn(message: WorkflowMessage): void {
201
- if (this.turn === null) return;
202
- this.turn.workflowMessageId = message.workflowMessageId;
203
- this.turn.runId = message.runId;
204
- this.turn.message = message;
205
- }
206
-
207
- private async flushTurn(client: WorkflowClient, view: WorkflowSessionView): Promise<void> {
208
- const pending = this.turn;
222
+ private async flushTurn(
223
+ client: WorkflowClient,
224
+ view: WorkflowSessionView,
225
+ beforeTurnEnd?: BeforeTurnEnd,
226
+ ): Promise<void> {
227
+ let pending = this.turn;
209
228
  if (
210
229
  pending === null ||
211
- pending.workflowMessageId === null ||
212
- pending.runId === null ||
230
+ pending.phase === "delivering" ||
213
231
  view.coordinatorEpoch === null ||
214
232
  this.lastBranchEpoch !== view.coordinatorEpoch
215
233
  ) {
@@ -217,54 +235,53 @@ export class WorkflowMessageCoordinator {
217
235
  }
218
236
  let message = pending.message;
219
237
  if (!pending.startedReported) {
220
- message = messageById(view, pending.workflowMessageId) ?? null;
221
- 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",
246
- workflowMessageId: pending.workflowMessageId,
238
+ const confirmed = messageById(view, message.workflowMessageId);
239
+ if (confirmed?.status !== "sent") return;
240
+ message = confirmed;
241
+ const receipt = await reportTurn(client, {
242
+ state: "started",
243
+ workflowMessageId: message.workflowMessageId,
247
244
  workflowTurnId: pending.workflowTurnId,
248
- runId: pending.runId,
245
+ runId: message.runId,
249
246
  targetSessionId: view.sessionId,
250
247
  coordinatorEpoch: view.coordinatorEpoch,
251
- stopReason: pending.end.stopReason,
252
- responseSessionEntryId: pending.end.responseSessionEntryId,
253
248
  });
254
- } catch (error) {
255
- this.turn = null;
256
- throw error;
249
+ // Pi can settle while the start report is in flight. Keep that exact
250
+ // response and do not restore a turn cleared by session shutdown.
251
+ if (this.turn?.workflowTurnId !== pending.workflowTurnId) return;
252
+ pending = this.turn;
253
+ if (
254
+ receipt.ownership !== "active" &&
255
+ !(receipt.ownership === "settled" && pending.phase === "settled")
256
+ ) {
257
+ this.turn = null;
258
+ return;
259
+ }
260
+ pending.message = message;
261
+ pending.startedReported = true;
257
262
  }
258
- if (message?.kind === "terminal" || message?.kind === "followUp") {
259
- this.closedTurnMessages.add(pending.workflowMessageId);
263
+ if (pending.phase !== "settled") return;
264
+ await beforeTurnEnd?.(message, pending.end);
265
+ await reportTurn(client, {
266
+ state: "ended",
267
+ workflowMessageId: message.workflowMessageId,
268
+ workflowTurnId: pending.workflowTurnId,
269
+ runId: message.runId,
270
+ targetSessionId: view.sessionId,
271
+ coordinatorEpoch: view.coordinatorEpoch,
272
+ stopReason: pending.end.stopReason,
273
+ responseSessionEntryId: pending.end.responseSessionEntryId,
274
+ });
275
+ if (message.kind === "followUp") {
276
+ this.closedTurnMessages.add(message.workflowMessageId);
260
277
  }
261
278
  for (const current of new Set([view, this.view])) {
262
279
  if (current?.openWorkflowTurn?.workflowTurnId === pending.workflowTurnId) {
263
280
  current.openWorkflowTurn = null;
264
281
  }
265
282
  if (
266
- current?.openWorkflowMessageId === pending.workflowMessageId &&
267
- (message?.kind === "terminal" || message?.kind === "followUp")
283
+ current?.openWorkflowMessageId === message.workflowMessageId &&
284
+ message.kind === "followUp"
268
285
  ) {
269
286
  current.openWorkflowMessageId = null;
270
287
  }
@@ -332,6 +349,27 @@ export function branchWorkflowEntries(entries: readonly unknown[]): Map<string,
332
349
  return found;
333
350
  }
334
351
 
352
+ function latestTurnInputIsWorkflow(
353
+ entries: readonly unknown[],
354
+ workflowMessageId: string,
355
+ ): boolean {
356
+ for (const value of [...entries].reverse()) {
357
+ if (!isRecord(value)) continue;
358
+ if (
359
+ value.role === "user" ||
360
+ (value.type === "message" && isRecord(value.message) && value.message.role === "user")
361
+ ) {
362
+ return false;
363
+ }
364
+ if (value.type === "custom_message" || value.role === "custom") {
365
+ return (
366
+ isRecord(value.details) && value.details[WORKFLOW_MESSAGE_ID_FIELD] === workflowMessageId
367
+ );
368
+ }
369
+ }
370
+ return false;
371
+ }
372
+
335
373
  export function responseEntryId(entries: readonly unknown[]): string | null {
336
374
  for (const value of [...entries].reverse()) {
337
375
  if (!isRecord(value) || typeof value.id !== "string") continue;
@@ -351,7 +389,7 @@ function messageById(
351
389
  }
352
390
 
353
391
  function messageStartsTurn(message: WorkflowMessage): boolean {
354
- return message.kind === "step" || message.kind === "terminal" || message.kind === "followUp";
392
+ return message.content.triggerTurn;
355
393
  }
356
394
 
357
395
  async function reportTurn(
@@ -48,7 +48,7 @@ const STATUS_GLYPHS: Record<NodeStatus, string> = {
48
48
  timed_out: "×",
49
49
  active: "◐",
50
50
  replay_focus: "◆",
51
- waiting: "",
51
+ waiting: "",
52
52
  cancelled: "~",
53
53
  queued: "·",
54
54
  };
@@ -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,