@osolmaz/pi-workflows 0.16.0 → 0.16.2

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 (175) hide show
  1. package/README.md +18 -2
  2. package/dist/channels/adapter-entry.d.ts +3 -0
  3. package/dist/channels/adapter-entry.js +203 -0
  4. package/dist/channels/adapter-entry.js.map +1 -0
  5. package/dist/channels/config.d.ts +43 -0
  6. package/dist/channels/config.js +234 -0
  7. package/dist/channels/config.js.map +1 -0
  8. package/dist/channels/protocol.d.ts +94 -0
  9. package/dist/channels/protocol.js +219 -0
  10. package/dist/channels/protocol.js.map +1 -0
  11. package/dist/channels/telegram.d.ts +46 -0
  12. package/dist/channels/telegram.js +285 -0
  13. package/dist/channels/telegram.js.map +1 -0
  14. package/dist/client/client.d.ts +1 -0
  15. package/dist/client/client.js +5 -1
  16. package/dist/client/client.js.map +1 -1
  17. package/dist/client/index.d.ts +1 -1
  18. package/dist/client/protocol.d.ts +1 -1
  19. package/dist/client/protocol.js +11 -5
  20. package/dist/client/protocol.js.map +1 -1
  21. package/dist/client/view.d.ts +48 -16
  22. package/dist/client/view.js +1 -0
  23. package/dist/client/view.js.map +1 -1
  24. package/dist/controllers/index.d.ts +1 -1
  25. package/dist/controllers/index.js.map +1 -1
  26. package/dist/controllers/sqlite.d.ts +20 -165
  27. package/dist/controllers/sqlite.js +178 -436
  28. package/dist/controllers/sqlite.js.map +1 -1
  29. package/dist/extension/index.d.ts +23 -2
  30. package/dist/extension/index.js +637 -534
  31. package/dist/extension/index.js.map +1 -1
  32. package/dist/extension/recorder.d.ts +95 -0
  33. package/dist/extension/recorder.js +530 -0
  34. package/dist/extension/recorder.js.map +1 -0
  35. package/dist/extension/remote-recorder-store.d.ts +25 -0
  36. package/dist/extension/remote-recorder-store.js +81 -0
  37. package/dist/extension/remote-recorder-store.js.map +1 -0
  38. package/dist/extension/session-events.d.ts +134 -0
  39. package/dist/extension/session-events.js +60 -0
  40. package/dist/extension/session-events.js.map +1 -0
  41. package/dist/extension/session-view.d.ts +2 -0
  42. package/dist/extension/session-view.js +60 -2
  43. package/dist/extension/session-view.js.map +1 -1
  44. package/dist/extension/step-message.d.ts +6 -4
  45. package/dist/extension/step-message.js +12 -6
  46. package/dist/extension/step-message.js.map +1 -1
  47. package/dist/extension/widget.d.ts +2 -2
  48. package/dist/extension/widget.js +28 -18
  49. package/dist/extension/widget.js.map +1 -1
  50. package/dist/extension/workflow-message-coordinator.d.ts +28 -0
  51. package/dist/extension/workflow-message-coordinator.js +346 -0
  52. package/dist/extension/workflow-message-coordinator.js.map +1 -0
  53. package/dist/host/channel-effects.d.ts +72 -0
  54. package/dist/host/channel-effects.js +271 -0
  55. package/dist/host/channel-effects.js.map +1 -0
  56. package/dist/host/channel-supervisor.d.ts +21 -0
  57. package/dist/host/channel-supervisor.js +54 -0
  58. package/dist/host/channel-supervisor.js.map +1 -0
  59. package/dist/host/runner.d.ts +50 -8
  60. package/dist/host/runner.js +1632 -341
  61. package/dist/host/runner.js.map +1 -1
  62. package/dist/host/state.d.ts +19 -16
  63. package/dist/host/state.js +251 -63
  64. package/dist/host/state.js.map +1 -1
  65. package/dist/host/view.d.ts +19 -9
  66. package/dist/host/view.js +227 -119
  67. package/dist/host/view.js.map +1 -1
  68. package/dist/host/worker-entry.d.ts +4 -1
  69. package/dist/host/worker-entry.js +23 -24
  70. package/dist/host/worker-entry.js.map +1 -1
  71. package/dist/host/worker-protocol.d.ts +15 -0
  72. package/dist/host/worker-protocol.js.map +1 -1
  73. package/dist/state/prune.js +3 -25
  74. package/dist/state/prune.js.map +1 -1
  75. package/dist/state/schema.js +76 -97
  76. package/dist/state/schema.js.map +1 -1
  77. package/dist/state/workflow-messages.d.ts +98 -0
  78. package/dist/state/workflow-messages.js +366 -0
  79. package/dist/state/workflow-messages.js.map +1 -0
  80. package/dist/viewer/render.js +1 -1
  81. package/dist/viewer/render.js.map +1 -1
  82. package/dist/viewer/tui.d.ts +1 -1
  83. package/dist/viewer/tui.js +65 -8
  84. package/dist/viewer/tui.js.map +1 -1
  85. package/dist/workflows/composition.js +0 -4
  86. package/dist/workflows/composition.js.map +1 -1
  87. package/dist/workflows/definition.js +0 -8
  88. package/dist/workflows/definition.js.map +1 -1
  89. package/dist/workflows/engine.js +4 -5
  90. package/dist/workflows/engine.js.map +1 -1
  91. package/dist/workflows/human-decision.d.ts +2 -0
  92. package/dist/workflows/human-decision.js +8 -2
  93. package/dist/workflows/human-decision.js.map +1 -1
  94. package/dist/workflows/index.d.ts +1 -1
  95. package/dist/workflows/index.js.map +1 -1
  96. package/dist/workflows/schema.js +0 -4
  97. package/dist/workflows/schema.js.map +1 -1
  98. package/dist/workflows/settings.d.ts +1 -8
  99. package/dist/workflows/settings.js.map +1 -1
  100. package/dist/workflows/store.d.ts +20 -19
  101. package/dist/workflows/store.js +163 -354
  102. package/dist/workflows/store.js.map +1 -1
  103. package/dist/workflows/tool-input.d.ts +21 -0
  104. package/dist/workflows/tool-input.js +23 -1
  105. package/dist/workflows/tool-input.js.map +1 -1
  106. package/dist/workflows/types.d.ts +6 -8
  107. package/dist/workflows/workflow-message-content.d.ts +38 -0
  108. package/dist/workflows/workflow-message-content.js +157 -0
  109. package/dist/workflows/workflow-message-content.js.map +1 -0
  110. package/docs/2026-08-18-herdr-piw-plan.md +2 -1
  111. package/docs/2026-08-20-durable-workflow-launch-plan.md +1 -1
  112. package/docs/2026-08-25-workflow-follow-ups.md +26 -50
  113. package/docs/2026-08-30-out-of-process-workflow-host-plan.md +3 -3
  114. package/docs/2026-09-01-restore-session-delivery-controls-plan.md +4 -0
  115. package/docs/2026-09-01-unified-workflow-client-plan.md +5 -1
  116. package/docs/2026-09-02-installed-live-e2e-plan.md +15 -1
  117. package/docs/2026-09-02-unify-workflow-messages-plan.md +562 -0
  118. package/docs/2026-09-04-workflow-run-state-plan.md +363 -0
  119. package/docs/DEFERRED_TURNS.md +66 -294
  120. package/docs/HUMAN_DECISIONS.md +29 -30
  121. package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -11
  122. package/docs/SQLITE_STATE.md +25 -17
  123. package/docs/WORKFLOW_HOST.md +105 -31
  124. package/docs/WORKFLOW_STEP_MESSAGES.md +166 -75
  125. package/docs/live-replay-protocol.md +8 -4
  126. package/docs/tui-viewer.md +3 -1
  127. package/docs/workflows.md +44 -28
  128. package/herdr-plugin.toml +1 -1
  129. package/package.json +1 -1
  130. package/plugins/herdr/viewer.mjs +14 -5
  131. package/src/channels/adapter-entry.ts +220 -0
  132. package/src/channels/config.ts +296 -0
  133. package/src/channels/protocol.ts +333 -0
  134. package/src/channels/telegram.ts +335 -0
  135. package/src/client/client.ts +6 -2
  136. package/src/client/index.ts +2 -1
  137. package/src/client/protocol.ts +11 -5
  138. package/src/client/view.ts +51 -18
  139. package/src/controllers/index.ts +0 -1
  140. package/src/controllers/sqlite.ts +247 -845
  141. package/src/extension/index.ts +712 -647
  142. package/src/extension/recorder.ts +687 -0
  143. package/src/extension/remote-recorder-store.ts +126 -0
  144. package/src/extension/session-events.ts +119 -0
  145. package/src/extension/session-view.ts +86 -3
  146. package/src/extension/step-message.ts +19 -9
  147. package/src/extension/widget.ts +37 -17
  148. package/src/extension/workflow-message-coordinator.ts +398 -0
  149. package/src/host/channel-effects.ts +465 -0
  150. package/src/host/channel-supervisor.ts +73 -0
  151. package/src/host/runner.ts +2163 -525
  152. package/src/host/state.ts +333 -94
  153. package/src/host/view.ts +254 -122
  154. package/src/host/worker-entry.ts +38 -30
  155. package/src/host/worker-protocol.ts +11 -0
  156. package/src/state/prune.ts +3 -31
  157. package/src/state/schema.ts +76 -97
  158. package/src/state/workflow-messages.ts +599 -0
  159. package/src/viewer/render.ts +1 -5
  160. package/src/viewer/tui.ts +71 -6
  161. package/src/workflows/composition.ts +0 -5
  162. package/src/workflows/definition.ts +0 -8
  163. package/src/workflows/engine.ts +4 -6
  164. package/src/workflows/human-decision.ts +18 -2
  165. package/src/workflows/index.ts +0 -1
  166. package/src/workflows/schema.ts +0 -6
  167. package/src/workflows/settings.ts +1 -20
  168. package/src/workflows/store.ts +237 -489
  169. package/src/workflows/tool-input.ts +36 -1
  170. package/src/workflows/types.ts +6 -8
  171. package/src/workflows/workflow-message-content.ts +197 -0
  172. package/dist/extension/decision-channels.d.ts +0 -134
  173. package/dist/extension/decision-channels.js +0 -1307
  174. package/dist/extension/decision-channels.js.map +0 -1
  175. package/src/extension/decision-channels.ts +0 -1826
@@ -3,6 +3,7 @@ import { StateDatabase, workflowStatePath } from "../state/database.js";
3
3
  import { canonicalJson, parseJson } from "../state/json.js";
4
4
  import { StateMutationStore, StaleResourceError, resourceIdFor, tokenHash, } from "../state/mutation.js";
5
5
  import { initializeViewerRun, recordViewerDeltas, viewerTailPatch, VIEWER_PAGE_SIZE, } from "../state/viewer.js";
6
+ import { WorkflowMessageStore, workflowMessageIdFor } from "../state/workflow-messages.js";
6
7
  import { compositionMetadata } from "./composition.js";
7
8
  import { ClaimLostError } from "./errors.js";
8
9
  import { HumanDecisionStore } from "./human-decision.js";
@@ -10,6 +11,7 @@ import { applyJsonPatch, validateJsonPatch } from "./json-patch.js";
10
11
  import { reduceSessionEvents, reduceSessionEventsFromCheckpoint, } from "./session-reducer.js";
11
12
  import { applyWorkflowSettingsPatch, workflowSettingsScopeId, } from "./settings.js";
12
13
  import { MAX_CURRENT_UPDATES, createUpdateId, updateProjection } from "./updates.js";
14
+ import { followUpWorkflowMessageContent } from "./workflow-message-content.js";
13
15
  export const RUN_STATE_SCHEMA = "pi-workflows.run-state.v1";
14
16
  export const DEFINITION_SNAPSHOT_SCHEMA = "pi-workflows.definition-snapshot.v1";
15
17
  export const SESSION_BINDING_SCHEMA = "pi-workflows.session-binding.v1";
@@ -47,14 +49,11 @@ const STEP_ROW_SELECT = `
47
49
  ON last_link.attempt_id = a.attempt_id AND last_link.role = 'last'`;
48
50
  const FOLLOW_UP_ROW_SELECT = `
49
51
  SELECT f.follow_up_id AS followUpId, f.resource_id AS resourceId,
50
- f.queue_resource_id AS queueResourceId, f.run_id AS runId,
51
- f.request_id AS requestId, f.order_number AS orderNumber,
52
+ f.run_id AS runId, f.request_id AS requestId, f.order_number AS orderNumber,
52
53
  f.target_session_id AS targetSessionId, f.actor_type AS actorType,
53
54
  f.actor_id AS actorId, f.source_type AS sourceType,
54
- f.prompt_hash AS promptHash, f.status,
55
- f.session_entry_id AS sessionEntryId, f.reason_hash AS reasonHash,
56
- f.created_at AS createdAt, f.updated_at AS updatedAt, f.sent_at AS sentAt,
57
- r.revision
55
+ f.prompt_hash AS promptHash, f.status, f.reason_hash AS reasonHash,
56
+ f.created_at AS createdAt, f.updated_at AS updatedAt, r.revision
58
57
  FROM workflow_follow_ups f
59
58
  JOIN resources r ON r.resource_id = f.resource_id`;
60
59
  export function workflowStateDatabasePath(homeDir) {
@@ -69,12 +68,15 @@ export class WorkflowRunStore {
69
68
  state;
70
69
  authorityProvider;
71
70
  snapshotLifecycle;
71
+ allowHostSessionRecording;
72
72
  contexts = new Map();
73
73
  ownsState;
74
74
  mutations;
75
+ workflowMessages;
75
76
  constructor(databasePath = workflowStatePath(), options = {}) {
76
77
  this.authorityProvider = options.authorityProvider;
77
78
  this.snapshotLifecycle = options.snapshotLifecycle;
79
+ this.allowHostSessionRecording = options.allowHostSessionRecording === true;
78
80
  this.ownsState = options.state === undefined;
79
81
  this.state =
80
82
  options.state ??
@@ -85,6 +87,7 @@ export class WorkflowRunStore {
85
87
  });
86
88
  this.databasePath = this.state.filePath;
87
89
  this.mutations = new StateMutationStore(this.state);
90
+ this.workflowMessages = new WorkflowMessageStore(this.state);
88
91
  }
89
92
  close() {
90
93
  if (this.ownsState)
@@ -333,6 +336,15 @@ export class WorkflowRunStore {
333
336
  return;
334
337
  }
335
338
  const definitionHash = this.state.putJson(snapshot, now);
339
+ const parentLineage = state.parentRunId === undefined
340
+ ? undefined
341
+ : this.state.connection
342
+ .prepare(`SELECT root_run_id AS rootRunId, restart_number AS restartNumber
343
+ FROM runs WHERE run_id = ?`)
344
+ .get(state.parentRunId);
345
+ if (state.parentRunId !== undefined && !isRunLineageRow(parentLineage)) {
346
+ throw new Error(`Workflow parent run is missing: ${state.parentRunId}`);
347
+ }
336
348
  this.state.connection
337
349
  .prepare(`INSERT INTO workflow_definitions(
338
350
  definition_digest, workflow_name, definition_hash, created_at
@@ -355,12 +367,13 @@ export class WorkflowRunStore {
355
367
  const errorHash = state.error === undefined ? null : this.state.putText(state.error, now);
356
368
  this.state.connection
357
369
  .prepare(`INSERT INTO runs(
358
- run_id, resource_id, project_id, parent_run_id, definition_digest,
370
+ run_id, resource_id, project_id, parent_run_id, root_run_id, lineage_kind,
371
+ restart_number, parent_terminal_fingerprint, definition_digest,
359
372
  workflow_ref, launch_options_hash, title, status, paused,
360
373
  status_detail, input_hash, final_output_hash, error_hash,
361
374
  created_at, updated_at, finished_at
362
- ) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
363
- .run(state.runId, resourceId, state.parentRunId ?? null, definitionDigest, state.workflowName, launchOptionsHash, state.runTitle ?? null, state.status, state.paused === true ? 1 : 0, state.statusDetail ?? null, inputHash, finalOutputHash, errorHash, Date.parse(state.startedAt), now, state.finishedAt === undefined ? null : Date.parse(state.finishedAt));
375
+ ) VALUES (?, ?, NULL, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
376
+ .run(state.runId, resourceId, state.parentRunId ?? null, isRunLineageRow(parentLineage) ? parentLineage.rootRunId : state.runId, state.parentRunId === undefined ? null : "continuation", isRunLineageRow(parentLineage) ? parentLineage.restartNumber : 0, definitionDigest, state.workflowName, launchOptionsHash, state.runTitle ?? null, state.status, state.paused === true ? 1 : 0, state.statusDetail ?? null, inputHash, finalOutputHash, errorHash, Date.parse(state.startedAt), now, state.finishedAt === undefined ? null : Date.parse(state.finishedAt));
364
377
  initializeViewerRun(this.state, state.runId, now);
365
378
  this.insertRunSources(state.runId, source, state.workflowSources ?? []);
366
379
  this.syncContinuationIdentity(state);
@@ -585,59 +598,49 @@ export class WorkflowRunStore {
585
598
  if (Buffer.byteLength(request.prompt, "utf8") > 64 * 1024) {
586
599
  throw new Error("Workflow follow-up prompt cannot exceed 65536 bytes");
587
600
  }
588
- for (let attempt = 0; attempt < 8; attempt += 1) {
601
+ return this.state.transaction(() => {
589
602
  const prior = this.followUpRowByRequest(request.runId, request.requestId);
590
603
  if (prior !== undefined) {
591
604
  this.assertSameFollowUp(prior, request);
592
605
  return { followUp: this.followUpRecord(prior), adopted: true };
593
606
  }
594
607
  this.requireRunAcceptsSettings(request.runId, true);
595
- const queue = this.requireFollowUpQueueRow(request.runId);
596
- try {
597
- const result = this.mutations.mutate({
598
- resourceId: queue.resourceId,
599
- operation: "follow-up.queue",
600
- actor: request.actor,
601
- expectedRevision: queue.revision,
602
- }, "follow-up.queued", ({ database, now }) => {
603
- this.requireRunAcceptsSettings(request.runId, true);
604
- const currentQueue = this.requireFollowUpQueueRow(request.runId);
605
- if (currentQueue.originSessionId !== null &&
606
- currentQueue.originSessionId !== request.targetSessionId) {
607
- throw new Error("Workflow follow-ups must target the run's origin Pi session");
608
- }
609
- if (currentQueue.originSessionId === null) {
610
- database.connection
611
- .prepare(`UPDATE workflow_follow_up_queues
612
- SET origin_session_id = ?, updated_at = ? WHERE run_id = ?`)
613
- .run(request.targetSessionId, now, request.runId);
614
- }
615
- const order = this.nextFollowUpOrder(request.runId);
616
- const followUpId = followUpIdFor(request.runId, request.requestId);
617
- const resourceId = this.mutations.ensureResource("follow_up", followUpId, now);
618
- const promptHash = database.putText(request.prompt, now);
619
- database.connection
620
- .prepare(`INSERT INTO workflow_follow_ups(
621
- follow_up_id, resource_id, queue_resource_id, run_id,
622
- request_id, order_number, target_session_id,
623
- actor_type, actor_id, source_type, prompt_hash, status,
624
- created_at, updated_at
625
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'queued', ?, ?)`)
626
- .run(followUpId, resourceId, queue.resourceId, request.runId, request.requestId, order, request.targetSessionId, request.actor.type, request.actor.id ?? null, request.source, promptHash, now, now);
627
- recordViewerDeltas(this.state, request.runId, this.viewerInspectorTargets(request.runId), now);
628
- return followUpId;
629
- }, { payload: { runId: request.runId, requestId: request.requestId } });
630
- return {
631
- followUp: this.followUpRecord(this.requireFollowUpRow(result.value)),
632
- adopted: false,
633
- };
608
+ if (this.originSessionId(request.runId) !== request.targetSessionId) {
609
+ throw new Error("Workflow follow-ups must target the run's origin Pi session");
634
610
  }
635
- catch (error) {
636
- if (!(error instanceof StaleResourceError))
637
- throw error;
638
- }
639
- }
640
- throw new StaleResourceError("Workflow follow-up queue kept changing; retry the request");
611
+ const now = Date.now();
612
+ const order = this.nextFollowUpOrder(request.runId);
613
+ const followUpId = followUpIdFor(request.runId, request.requestId);
614
+ const resourceId = this.mutations.ensureResource("follow_up", followUpId, now);
615
+ const promptHash = this.state.putText(request.prompt, now);
616
+ this.state.connection
617
+ .prepare(`INSERT INTO workflow_follow_ups(
618
+ follow_up_id, resource_id, run_id, request_id, order_number,
619
+ target_session_id, actor_type, actor_id, source_type, prompt_hash,
620
+ status, created_at, updated_at
621
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'queued', ?, ?)`)
622
+ .run(followUpId, resourceId, request.runId, request.requestId, order, request.targetSessionId, request.actor.type, request.actor.id ?? null, request.source, promptHash, now, now);
623
+ const workflowMessageId = workflowMessageIdFor("followUp", followUpId, "initial");
624
+ this.workflowMessages.create({
625
+ workflowMessageId,
626
+ runId: request.runId,
627
+ targetSessionId: request.targetSessionId,
628
+ kind: "followUp",
629
+ sourceId: followUpId,
630
+ idempotencyKey: "initial",
631
+ content: followUpWorkflowMessageContent({
632
+ workflowMessageId,
633
+ followUpId,
634
+ runId: request.runId,
635
+ prompt: request.prompt,
636
+ }),
637
+ now,
638
+ });
639
+ this.bumpResource(resourceId, 0, now);
640
+ insertGenericEvent(this.state, resourceId, 1, "follow-up.queued", request.actor.type, request.actor.id ?? null, this.state.putJson({ runId: request.runId, requestId: request.requestId }, now), now);
641
+ recordViewerDeltas(this.state, request.runId, this.viewerInspectorTargets(request.runId), now);
642
+ return { followUp: this.followUpRecord(this.requireFollowUpRow(followUpId)), adopted: false };
643
+ });
641
644
  }
642
645
  removeFollowUp(request) {
643
646
  assertSourceType(request.source);
@@ -647,12 +650,13 @@ export class WorkflowRunStore {
647
650
  }
648
651
  if (row.status === "removed")
649
652
  return this.followUpRecord(row);
650
- if (row.status === "sent" || row.status === "cancelled") {
651
- throw new Error(`Workflow follow-up cannot be removed after it is ${row.status}`);
653
+ if (row.status === "cancelled") {
654
+ throw new Error("A cancelled workflow follow-up cannot be removed");
652
655
  }
653
656
  this.assertFollowUpRemovalAuthority(row, request.actor, request.source);
654
- if (this.hasLiveLease(row.resourceId)) {
655
- throw new Error("Workflow follow-up is being delivered and cannot be removed");
657
+ const message = this.workflowMessages.latestForSource("followUp", request.followUpId);
658
+ if (message?.status === "sent") {
659
+ throw new Error("A sent workflow follow-up cannot be removed");
656
660
  }
657
661
  const reason = "Removed before delivery";
658
662
  this.mutations.mutate({
@@ -665,21 +669,20 @@ export class WorkflowRunStore {
665
669
  const update = database.connection
666
670
  .prepare(`UPDATE workflow_follow_ups
667
671
  SET status = 'removed', reason_hash = ?, updated_at = ?
668
- WHERE follow_up_id = ? AND status IN ('queued', 'pending_presentation', 'ready')`)
672
+ WHERE follow_up_id = ? AND status = 'queued'`)
669
673
  .run(reasonHash, now, request.followUpId);
670
674
  if (update.changes !== 1) {
671
675
  throw new StaleResourceError("Workflow follow-up changed before removal");
672
676
  }
677
+ this.workflowMessages.cancelPendingForSource(request.followUpId, "followUp", now);
673
678
  recordViewerDeltas(this.state, request.runId, this.viewerInspectorTargets(request.runId), now);
674
679
  }, { payload: { followUpId: request.followUpId, source: request.source } });
675
680
  return this.followUpRecord(this.requireFollowUpRow(request.followUpId));
676
681
  }
677
682
  originSessionId(runId) {
678
683
  const row = this.state.connection
679
- .prepare(`SELECT COALESCE(q.origin_session_id, b.origin_session_id) AS originSessionId
680
- FROM runs r
681
- LEFT JOIN workflow_follow_up_queues q ON q.run_id = r.run_id
682
- LEFT JOIN run_bindings b ON b.run_id = r.run_id
684
+ .prepare(`SELECT b.origin_session_id AS originSessionId
685
+ FROM runs r LEFT JOIN run_bindings b ON b.run_id = r.run_id
683
686
  WHERE r.run_id = ?`)
684
687
  .get(runId);
685
688
  return isRecord(row) && typeof row.originSessionId === "string"
@@ -687,176 +690,26 @@ export class WorkflowRunStore {
687
690
  : undefined;
688
691
  }
689
692
  readFollowUpQueue(runId) {
690
- const queue = this.followUpQueueRow(runId);
691
- if (queue === undefined)
693
+ const originSessionId = this.originSessionId(runId);
694
+ if (originSessionId === undefined)
692
695
  return undefined;
693
696
  const followUps = this.state.connection
694
697
  .prepare(`${FOLLOW_UP_ROW_SELECT} WHERE f.run_id = ? ORDER BY f.order_number`)
695
698
  .all(runId)
696
699
  .filter(isFollowUpRow)
697
700
  .map((row) => this.followUpRecord(row));
698
- return {
699
- runId,
700
- ...(queue.originSessionId !== null ? { originSessionId: queue.originSessionId } : {}),
701
- presentationState: assertPresentationState(queue.presentationState),
702
- ...(queue.presentationEntryId !== null
703
- ? { presentationEntryId: queue.presentationEntryId }
704
- : {}),
705
- ...(queue.presentationAssistantEntryId !== null
706
- ? { presentationAssistantEntryId: queue.presentationAssistantEntryId }
707
- : {}),
708
- ...(queue.presentationReasonHash !== null
709
- ? { presentationReason: this.readText(queue.presentationReasonHash) }
710
- : {}),
711
- followUps,
712
- };
701
+ return { runId, originSessionId, followUps };
713
702
  }
714
703
  readFollowUpQueueRange(runId, range) {
715
- const queue = this.followUpQueueRow(runId);
716
- if (queue === undefined)
704
+ const originSessionId = this.originSessionId(runId);
705
+ if (originSessionId === undefined)
717
706
  return null;
718
707
  const followUps = this.state.connection
719
708
  .prepare(`${FOLLOW_UP_ROW_SELECT} WHERE f.run_id = ? ORDER BY f.order_number LIMIT ? OFFSET ?`)
720
709
  .all(runId, range.limit, range.start)
721
710
  .filter(isFollowUpRow)
722
711
  .map((row) => this.followUpRecord(row));
723
- return {
724
- runId,
725
- ...(queue.originSessionId !== null ? { originSessionId: queue.originSessionId } : {}),
726
- presentationState: assertPresentationState(queue.presentationState),
727
- ...(queue.presentationEntryId !== null
728
- ? { presentationEntryId: queue.presentationEntryId }
729
- : {}),
730
- ...(queue.presentationAssistantEntryId !== null
731
- ? { presentationAssistantEntryId: queue.presentationAssistantEntryId }
732
- : {}),
733
- ...(queue.presentationReasonHash !== null
734
- ? { presentationReason: this.readText(queue.presentationReasonHash) }
735
- : {}),
736
- followUps,
737
- };
738
- }
739
- listPendingPresentations(targetSessionId) {
740
- return this.state.connection
741
- .prepare(`SELECT run_id AS runId FROM workflow_follow_up_queues
742
- WHERE origin_session_id = ? AND presentation_state = 'pending'
743
- ORDER BY created_at`)
744
- .all(targetSessionId)
745
- .flatMap((row) => (isRecord(row) && typeof row.runId === "string" ? [row.runId] : []));
746
- }
747
- settleFollowUpPresentation(options) {
748
- const queue = this.requireFollowUpQueueRow(options.runId);
749
- if (queue.presentationState === options.state) {
750
- return this.readFollowUpQueue(options.runId);
751
- }
752
- if (queue.presentationState !== "pending") {
753
- throw new Error(`Workflow presentation is ${queue.presentationState}, not pending for ${options.runId}`);
754
- }
755
- if (options.state === "settled" &&
756
- (options.presentationEntryId === undefined || options.assistantEntryId === undefined)) {
757
- throw new Error("Settled workflow presentation requires both session entry IDs");
758
- }
759
- if (options.state === "unavailable" && !options.reason?.trim()) {
760
- throw new Error("Unavailable workflow presentation requires a reason");
761
- }
762
- try {
763
- this.mutations.mutate({
764
- resourceId: queue.resourceId,
765
- operation: "follow-up.presentation",
766
- actor: {
767
- type: "session",
768
- ...(queue.originSessionId !== null ? { id: queue.originSessionId } : {}),
769
- },
770
- expectedRevision: queue.revision,
771
- }, `follow-up.presentation-${options.state}`, ({ database, now }) => {
772
- const reasonHash = options.state === "unavailable"
773
- ? database.putText(options.reason, now)
774
- : null;
775
- database.connection
776
- .prepare(`UPDATE workflow_follow_up_queues
777
- SET presentation_state = ?, presentation_entry_id = ?,
778
- presentation_assistant_entry_id = ?, presentation_reason_hash = ?,
779
- presentation_updated_at = ?, updated_at = ?
780
- WHERE run_id = ? AND presentation_state = 'pending'`)
781
- .run(options.state, options.presentationEntryId ?? null, options.assistantEntryId ?? null, reasonHash, now, now, options.runId);
782
- database.connection
783
- .prepare(`UPDATE workflow_follow_ups SET status = 'ready', updated_at = ?
784
- WHERE run_id = ? AND status = 'pending_presentation'`)
785
- .run(now, options.runId);
786
- recordViewerDeltas(this.state, options.runId, this.viewerInspectorTargets(options.runId), now);
787
- }, { payload: { runId: options.runId, state: options.state } });
788
- }
789
- catch (error) {
790
- if (error instanceof StaleResourceError) {
791
- const current = this.requireFollowUpQueueRow(options.runId);
792
- if (current.presentationState === options.state) {
793
- return this.readFollowUpQueue(options.runId);
794
- }
795
- }
796
- throw error;
797
- }
798
- return this.readFollowUpQueue(options.runId);
799
- }
800
- claimNextFollowUp(targetSessionId, ownerId, leaseMs = 30_000) {
801
- const rows = this.state.connection
802
- .prepare(`${FOLLOW_UP_ROW_SELECT}
803
- WHERE f.target_session_id = ? AND f.status = 'ready'
804
- ORDER BY f.created_at, f.order_number LIMIT 16`)
805
- .all(targetSessionId)
806
- .filter(isFollowUpRow);
807
- for (const row of rows) {
808
- let claim;
809
- try {
810
- claim = this.mutations.claim({
811
- resourceId: row.resourceId,
812
- ownerType: "session",
813
- ownerId,
814
- expectedRevision: row.revision,
815
- leaseMs,
816
- });
817
- }
818
- catch (error) {
819
- if (error instanceof StaleResourceError)
820
- continue;
821
- throw error;
822
- }
823
- if (claim === undefined)
824
- continue;
825
- const current = this.requireFollowUpRow(row.followUpId);
826
- if (current.status !== "ready") {
827
- this.mutations.release(claim, claim.resourceRevision);
828
- continue;
829
- }
830
- return { followUp: this.followUpRecord(current), claim };
831
- }
832
- return undefined;
833
- }
834
- markFollowUpSent(followUpId, claim, sessionEntryId) {
835
- const row = this.requireFollowUpRow(followUpId);
836
- if (row.status === "sent")
837
- return this.followUpRecord(row);
838
- this.mutations.mutate({
839
- resourceId: row.resourceId,
840
- operation: "follow-up.sent",
841
- actor: { type: "session", id: claim.ownerId },
842
- expectedRevision: claim.resourceRevision,
843
- lease: claim,
844
- }, "follow-up.sent", ({ database, now }) => {
845
- const update = database.connection
846
- .prepare(`UPDATE workflow_follow_ups
847
- SET status = 'sent', session_entry_id = ?, sent_at = ?, updated_at = ?
848
- WHERE follow_up_id = ? AND status = 'ready'`)
849
- .run(sessionEntryId, now, now, followUpId);
850
- if (update.changes !== 1) {
851
- throw new StaleResourceError("Workflow follow-up changed before delivery completed");
852
- }
853
- recordViewerDeltas(this.state, row.runId, this.viewerInspectorTargets(row.runId), now);
854
- }, { payload: { followUpId, sessionEntryId } });
855
- return this.followUpRecord(this.requireFollowUpRow(followUpId));
856
- }
857
- releaseFollowUpClaim(claim) {
858
- const revision = this.requireResourceRevision(claim.resourceId);
859
- this.mutations.release(claim, revision);
712
+ return { runId, originSessionId, followUps };
860
713
  }
861
714
  async prepareRunResume(runId) {
862
715
  const loaded = this.readRun(runId, { includeTrace: true });
@@ -868,7 +721,6 @@ export class WorkflowRunStore {
868
721
  this.verifySettingsProjections(runId);
869
722
  const revision = this.resourceRevision(runId);
870
723
  this.contexts.set(runId, { revision, lock: Promise.resolve() });
871
- this.finalizeRecordingCaptures(runId, "Workflow host stopped before the run finished");
872
724
  this.state.transaction(() => {
873
725
  const row = this.requireRunRow(runId);
874
726
  const context = this.contextFor(runId);
@@ -1051,7 +903,7 @@ export class WorkflowRunStore {
1051
903
  this.state.transaction(() => {
1052
904
  const run = this.requireRunRow(runId);
1053
905
  const context = this.contextFor(runId);
1054
- this.assertWriteAuthority(run, context.revision);
906
+ this.assertSessionWriteAuthority(runId);
1055
907
  const now = Date.parse(binding.boundAt);
1056
908
  const resourceId = resourceIdFor("session", segmentId);
1057
909
  const bindingHash = this.state.putJson(binding, now);
@@ -1075,11 +927,6 @@ export class WorkflowRunStore {
1075
927
  VALUES (?, ?, 'interactive', ?)
1076
928
  ON CONFLICT(run_id) DO NOTHING`)
1077
929
  .run(runId, binding.piSessionId, now);
1078
- this.state.connection
1079
- .prepare(`UPDATE workflow_follow_up_queues
1080
- SET origin_session_id = COALESCE(origin_session_id, ?), updated_at = ?
1081
- WHERE run_id = ?`)
1082
- .run(binding.piSessionId, now, runId);
1083
930
  const revision = context.revision + 1;
1084
931
  const at = new Date(now).toISOString();
1085
932
  const traceEvent = {
@@ -1415,6 +1262,48 @@ export class WorkflowRunStore {
1415
1262
  lastEventSeq: segment?.eventCount ?? 0,
1416
1263
  };
1417
1264
  }
1265
+ readRunInput(runId) {
1266
+ const row = this.readRunRow(runId);
1267
+ return row === undefined ? null : this.state.readJson(row.inputHash);
1268
+ }
1269
+ readRunFinalOutput(runId) {
1270
+ const row = this.readRunRow(runId);
1271
+ return row?.finalOutputHash == null ? null : this.state.readJson(row.finalOutputHash);
1272
+ }
1273
+ readRunError(runId) {
1274
+ const row = this.readRunRow(runId);
1275
+ return row?.errorHash == null ? null : this.readText(row.errorHash);
1276
+ }
1277
+ readPresentationInstructions(runId) {
1278
+ const row = this.state.connection
1279
+ .prepare("SELECT presentation_prompt_hash AS hash FROM runs WHERE run_id = ?")
1280
+ .get(runId);
1281
+ if (row?.hash == null) {
1282
+ return "Explain the final workflow result to the user in a normal response.";
1283
+ }
1284
+ return this.readText(row.hash);
1285
+ }
1286
+ readTerminalData(runId) {
1287
+ const row = this.state.connection
1288
+ .prepare(`SELECT status, status_detail AS statusDetail, restart_number AS restartNumber
1289
+ FROM runs WHERE run_id = ?`)
1290
+ .get(runId);
1291
+ if (row === undefined ||
1292
+ !["completed", "failed", "timed_out", "cancelled"].includes(String(row.status)) ||
1293
+ typeof row.restartNumber !== "number") {
1294
+ return null;
1295
+ }
1296
+ return {
1297
+ runId,
1298
+ status: row.status,
1299
+ statusDetail: typeof row.statusDetail === "string" ? row.statusDetail : null,
1300
+ input: this.readRunInput(runId),
1301
+ finalOutput: this.readRunFinalOutput(runId),
1302
+ error: this.readRunError(runId),
1303
+ presentationInstructions: this.readPresentationInstructions(runId),
1304
+ restartNumber: row.restartNumber,
1305
+ };
1306
+ }
1418
1307
  readRun(runId, options = {}) {
1419
1308
  const row = this.readRunRow(runId);
1420
1309
  if (row === undefined)
@@ -1659,6 +1548,8 @@ export class WorkflowRunStore {
1659
1548
  }
1660
1549
  assertSessionWriteAuthority(runId) {
1661
1550
  const run = this.requireRunRow(runId);
1551
+ if (this.allowHostSessionRecording)
1552
+ return;
1662
1553
  this.assertWriteAuthority(run, this.contextFor(runId).revision);
1663
1554
  }
1664
1555
  resourceRevision(runId) {
@@ -1808,10 +1699,8 @@ export class WorkflowRunStore {
1808
1699
  order: row.orderNumber,
1809
1700
  state: row.status,
1810
1701
  source: row.sourceType,
1811
- sessionEntryId: row.sessionEntryId,
1812
1702
  }));
1813
1703
  const followUpStart = Math.max(0, followUps.length - VIEWER_PAGE_SIZE);
1814
- const queue = this.requireFollowUpQueueRow(runId);
1815
1704
  return [
1816
1705
  {
1817
1706
  targetType: "inspector",
@@ -1833,10 +1722,7 @@ export class WorkflowRunStore {
1833
1722
  {
1834
1723
  op: "replace",
1835
1724
  path: "/followUpQueue",
1836
- value: parseJson(canonicalJson({
1837
- presentationState: queue.presentationState,
1838
- items: followUps.slice(followUpStart),
1839
- })),
1725
+ value: parseJson(canonicalJson({ items: followUps.slice(followUpStart) })),
1840
1726
  },
1841
1727
  { op: "replace", path: "/followUpStart", value: followUpStart },
1842
1728
  { op: "replace", path: "/followUpTotal", value: followUps.length },
@@ -1845,21 +1731,7 @@ export class WorkflowRunStore {
1845
1731
  ];
1846
1732
  }
1847
1733
  initializeRunSettingsAndFollowUps(state, initialSettings, now) {
1848
- const childQueueResourceId = this.ensureFollowUpQueue(state.runId, now);
1849
1734
  if (state.parentRunId !== undefined) {
1850
- const parentQueue = this.followUpQueueRow(state.parentRunId);
1851
- if (parentQueue !== undefined) {
1852
- this.state.connection
1853
- .prepare(`UPDATE workflow_follow_up_queues
1854
- SET origin_session_id = COALESCE(origin_session_id, ?), updated_at = ?
1855
- WHERE run_id = ?`)
1856
- .run(parentQueue.originSessionId, now, state.runId);
1857
- this.state.connection
1858
- .prepare(`UPDATE workflow_follow_ups
1859
- SET run_id = ?, queue_resource_id = ?, updated_at = ?
1860
- WHERE run_id = ? AND status = 'queued'`)
1861
- .run(state.runId, childQueueResourceId, now, state.parentRunId);
1862
- }
1863
1735
  this.state.connection
1864
1736
  .prepare(`UPDATE workflow_settings SET active_run_id = ?, updated_at = ? WHERE active_run_id = ?`)
1865
1737
  .run(state.runId, now, state.parentRunId);
@@ -1884,16 +1756,6 @@ export class WorkflowRunStore {
1884
1756
  .run(scopeId, resourceId, originRunId, state.runId, scope.mountPath, scope.invocation, settingsHash, settingsHash, now, now);
1885
1757
  }
1886
1758
  }
1887
- ensureFollowUpQueue(runId, now) {
1888
- const resourceId = this.mutations.ensureResource("follow_up_queue", runId, now);
1889
- this.state.connection
1890
- .prepare(`INSERT INTO workflow_follow_up_queues(
1891
- run_id, resource_id, created_at, updated_at
1892
- ) VALUES (?, ?, ?, ?)
1893
- ON CONFLICT(run_id) DO NOTHING`)
1894
- .run(runId, resourceId, now, now);
1895
- return resourceId;
1896
- }
1897
1759
  logicalOriginRunId(runId) {
1898
1760
  let current = runId;
1899
1761
  for (let depth = 0; depth < 100; depth += 1) {
@@ -2026,27 +1888,6 @@ export class WorkflowRunStore {
2026
1888
  throw new Error(`Workflow settings request ID was reused with different content`);
2027
1889
  }
2028
1890
  }
2029
- followUpQueueRow(runId) {
2030
- const row = this.state.connection
2031
- .prepare(`SELECT q.run_id AS runId, q.resource_id AS resourceId,
2032
- q.origin_session_id AS originSessionId,
2033
- q.presentation_state AS presentationState,
2034
- q.presentation_entry_id AS presentationEntryId,
2035
- q.presentation_assistant_entry_id AS presentationAssistantEntryId,
2036
- q.presentation_reason_hash AS presentationReasonHash,
2037
- r.revision
2038
- FROM workflow_follow_up_queues q
2039
- JOIN resources r ON r.resource_id = q.resource_id
2040
- WHERE q.run_id = ?`)
2041
- .get(runId);
2042
- return isFollowUpQueueRow(row) ? row : undefined;
2043
- }
2044
- requireFollowUpQueueRow(runId) {
2045
- const row = this.followUpQueueRow(runId);
2046
- if (row === undefined)
2047
- throw new Error(`Workflow follow-up queue not found: ${runId}`);
2048
- return row;
2049
- }
2050
1891
  followUpRowByRequest(runId, requestId) {
2051
1892
  const row = this.state.connection
2052
1893
  .prepare(`${FOLLOW_UP_ROW_SELECT} WHERE f.run_id = ? AND f.request_id = ?`)
@@ -2079,11 +1920,9 @@ export class WorkflowRunStore {
2079
1920
  source: row.sourceType,
2080
1921
  prompt: this.readText(row.promptHash),
2081
1922
  state: assertFollowUpState(row.status),
2082
- ...(row.sessionEntryId !== null ? { sessionEntryId: row.sessionEntryId } : {}),
2083
1923
  ...(row.reasonHash !== null ? { reason: this.readText(row.reasonHash) } : {}),
2084
1924
  createdAt: new Date(row.createdAt).toISOString(),
2085
1925
  updatedAt: new Date(row.updatedAt).toISOString(),
2086
- ...(row.sentAt !== null ? { sentAt: new Date(row.sentAt).toISOString() } : {}),
2087
1926
  };
2088
1927
  }
2089
1928
  assertSameFollowUp(row, request) {
@@ -2107,12 +1946,6 @@ export class WorkflowRunStore {
2107
1946
  }
2108
1947
  throw new Error("Workflow follow-up can be removed only by its source or a verified human");
2109
1948
  }
2110
- hasLiveLease(resourceId) {
2111
- const row = this.state.connection
2112
- .prepare("SELECT expires_at AS expiresAt FROM leases WHERE resource_id = ?")
2113
- .get(resourceId);
2114
- return isLeaseExpiryRow(row) && row.expiresAt !== null && row.expiresAt > Date.now();
2115
- }
2116
1949
  nextFollowUpOrder(runId) {
2117
1950
  const row = this.state.connection
2118
1951
  .prepare(`SELECT COALESCE(MAX(order_number), 0) + 1 AS nextOrder
@@ -2137,37 +1970,37 @@ export class WorkflowRunStore {
2137
1970
  throw new StaleResourceError(`Settings route ${event.nodeId} used change ${changeNumber}, current change is ${scope.revision}`);
2138
1971
  }
2139
1972
  }
2140
- transitionFollowUpsForRunState(state, event, now) {
2141
- if (state.status === "running" || state.status === "waiting")
1973
+ transitionFollowUpsForRunState(state, _event, now) {
1974
+ if (state.status === "running" || state.status === "waiting" || state.status === "completed") {
2142
1975
  return;
2143
- const queue = this.followUpQueueRow(state.runId);
2144
- if (queue === undefined || queue.presentationState !== "none")
2145
- return;
2146
- let presentationState = "not-needed";
2147
- let followUpState = "cancelled";
2148
- let reasonHash = null;
2149
- if (state.status === "completed") {
2150
- const required = event.payload.presentationRequired === true;
2151
- presentationState = required ? "pending" : "not-needed";
2152
- followUpState = required ? "pending_presentation" : "ready";
2153
1976
  }
2154
- else {
2155
- reasonHash = this.state.putText(state.error ?? `Workflow ended with status ${state.status}`, now);
1977
+ const rows = this.state.connection
1978
+ .prepare(`${FOLLOW_UP_ROW_SELECT}
1979
+ WHERE f.run_id IN (
1980
+ WITH RECURSIVE ancestors(run_id, parent_run_id) AS (
1981
+ SELECT run_id, parent_run_id FROM runs WHERE run_id = ?
1982
+ UNION ALL
1983
+ SELECT parent.run_id, parent.parent_run_id
1984
+ FROM runs parent JOIN ancestors ON ancestors.parent_run_id = parent.run_id
1985
+ )
1986
+ SELECT run_id FROM ancestors
1987
+ ) AND f.status = 'queued'`)
1988
+ .all(state.runId)
1989
+ .filter(isFollowUpRow);
1990
+ for (const row of rows) {
1991
+ const reason = state.error ?? `Workflow ended with status ${state.status}`;
1992
+ const reasonHash = this.state.putText(reason, now);
1993
+ const update = this.state.connection
1994
+ .prepare(`UPDATE workflow_follow_ups
1995
+ SET status = 'cancelled', reason_hash = ?, updated_at = ?
1996
+ WHERE follow_up_id = ? AND status = 'queued'`)
1997
+ .run(reasonHash, now, row.followUpId);
1998
+ if (update.changes !== 1)
1999
+ continue;
2000
+ this.workflowMessages.cancelPendingForSource(row.followUpId, "followUp", now);
2001
+ this.bumpResource(row.resourceId, row.revision, now);
2002
+ insertGenericEvent(this.state, row.resourceId, row.revision + 1, "follow-up.cancelled", "system", null, this.state.putJson({ runId: state.runId, status: state.status, reason }, now), now);
2156
2003
  }
2157
- this.state.connection
2158
- .prepare(`UPDATE workflow_follow_up_queues
2159
- SET presentation_state = ?, presentation_updated_at = ?, updated_at = ?
2160
- WHERE run_id = ? AND presentation_state = 'none'`)
2161
- .run(presentationState, now, now, state.runId);
2162
- this.state.connection
2163
- .prepare(`UPDATE workflow_follow_ups
2164
- SET status = ?, reason_hash = ?, updated_at = ?
2165
- WHERE run_id = ? AND status IN ('queued', 'pending_presentation', 'ready')`)
2166
- .run(followUpState, reasonHash, now, state.runId);
2167
- const revision = this.requireResourceRevision(queue.resourceId);
2168
- this.bumpResource(queue.resourceId, revision, now);
2169
- const payloadHash = this.state.putJson({ runId: state.runId, state: presentationState, followUps: followUpState }, now);
2170
- insertGenericEvent(this.state, queue.resourceId, revision + 1, "follow-up.run-finished", "system", null, payloadHash, now);
2171
2004
  }
2172
2005
  syncNodeAttempts(state, snapshot, now) {
2173
2006
  for (const step of state.steps.slice(state.carriedStepCount ?? 0)) {
@@ -2300,7 +2133,7 @@ export class WorkflowRunStore {
2300
2133
  JOIN session_segments s ON s.segment_id = e.segment_id
2301
2134
  JOIN blobs b ON b.blob_hash = e.entry_hash
2302
2135
  WHERE s.run_id = ?
2303
- AND json_extract(CAST(b.content AS TEXT), '$.customType') = 'pi-workflows-agent-step'
2136
+ AND json_extract(CAST(b.content AS TEXT), '$.customType') = 'pi-workflows-step'
2304
2137
  AND json_extract(CAST(b.content AS TEXT), '$.details.contract.attemptId') = ?
2305
2138
  ORDER BY e.recorded_at DESC LIMIT 1`)
2306
2139
  .get(runId, attemptId);
@@ -2438,7 +2271,13 @@ export class WorkflowRunStore {
2438
2271
  buildRunState(row, snapshot, projectionSteps, visibleSteps, visibleUpdates) {
2439
2272
  const sources = this.readRunSources(row.runId, snapshot);
2440
2273
  const carriedStepCount = this.carriedStepCount(row.runId);
2441
- const activeAttempt = row.status === "running" ? this.readActiveAttempt(row.runId) : undefined;
2274
+ const activeAttempt = row.status === "running" || row.status === "waiting"
2275
+ ? this.readActiveAttempt(row.runId)
2276
+ : undefined;
2277
+ const runningAttempt = row.status === "running" ? activeAttempt : undefined;
2278
+ const waitingNode = row.status === "waiting"
2279
+ ? (activeAttempt?.nodeId ?? projectionSteps.at(-1)?.nodeId)
2280
+ : undefined;
2442
2281
  const humanDecision = this.readHumanDecisionReceipt(row.runId);
2443
2282
  const outputs = {};
2444
2283
  const results = {};
@@ -2481,7 +2320,7 @@ export class WorkflowRunStore {
2481
2320
  results,
2482
2321
  steps: visibleSteps,
2483
2322
  ...(visibleUpdates.length === 0 ? {} : { updates: visibleUpdates }),
2484
- ...(activeAttempt === undefined ? {} : { currentNode: activeAttempt.nodeId }),
2323
+ ...(runningAttempt === undefined ? {} : { currentNode: runningAttempt.nodeId }),
2485
2324
  ...(activeAttempt === undefined ? {} : { currentAttemptId: activeAttempt.attemptId }),
2486
2325
  ...(activeAttempt?.startedAt === null || activeAttempt === undefined
2487
2326
  ? {}
@@ -2497,9 +2336,7 @@ export class WorkflowRunStore {
2497
2336
  ...(row.statusDetail === null ? {} : { statusDetail: row.statusDetail }),
2498
2337
  ...(humanDecision === undefined ? {} : { humanDecision }),
2499
2338
  ...(row.paused === 0 ? {} : { paused: true }),
2500
- ...(row.status === "waiting" && projectionSteps.at(-1) !== undefined
2501
- ? { waitingOn: projectionSteps.at(-1)?.nodeId }
2502
- : {}),
2339
+ ...(waitingNode === undefined ? {} : { waitingOn: waitingNode }),
2503
2340
  ...(row.finalOutputHash === null
2504
2341
  ? {}
2505
2342
  : { finalOutput: this.state.readJson(row.finalOutputHash) }),
@@ -3334,26 +3171,11 @@ function assertActorType(value) {
3334
3171
  return value;
3335
3172
  }
3336
3173
  function assertFollowUpState(value) {
3337
- if (value !== "queued" &&
3338
- value !== "pending_presentation" &&
3339
- value !== "ready" &&
3340
- value !== "sent" &&
3341
- value !== "removed" &&
3342
- value !== "cancelled") {
3174
+ if (value !== "queued" && value !== "removed" && value !== "cancelled") {
3343
3175
  throw new Error(`Unknown workflow follow-up state: ${value}`);
3344
3176
  }
3345
3177
  return value;
3346
3178
  }
3347
- function assertPresentationState(value) {
3348
- if (value !== "none" &&
3349
- value !== "not-needed" &&
3350
- value !== "pending" &&
3351
- value !== "settled" &&
3352
- value !== "unavailable") {
3353
- throw new Error(`Unknown workflow presentation state: ${value}`);
3354
- }
3355
- return value;
3356
- }
3357
3179
  function isSettingsScopeRow(value) {
3358
3180
  return (isRecord(value) &&
3359
3181
  typeof value.scopeId === "string" &&
@@ -3395,23 +3217,10 @@ function isSettingsChangeRow(value) {
3395
3217
  Buffer.isBuffer(value.afterHash) &&
3396
3218
  typeof value.acceptedAt === "number");
3397
3219
  }
3398
- function isFollowUpQueueRow(value) {
3399
- return (isRecord(value) &&
3400
- typeof value.runId === "string" &&
3401
- typeof value.resourceId === "string" &&
3402
- (typeof value.originSessionId === "string" || value.originSessionId === null) &&
3403
- typeof value.presentationState === "string" &&
3404
- (typeof value.presentationEntryId === "string" || value.presentationEntryId === null) &&
3405
- (typeof value.presentationAssistantEntryId === "string" ||
3406
- value.presentationAssistantEntryId === null) &&
3407
- (Buffer.isBuffer(value.presentationReasonHash) || value.presentationReasonHash === null) &&
3408
- typeof value.revision === "number");
3409
- }
3410
3220
  function isFollowUpRow(value) {
3411
3221
  return (isRecord(value) &&
3412
3222
  typeof value.followUpId === "string" &&
3413
3223
  typeof value.resourceId === "string" &&
3414
- typeof value.queueResourceId === "string" &&
3415
3224
  typeof value.runId === "string" &&
3416
3225
  typeof value.requestId === "string" &&
3417
3226
  typeof value.orderNumber === "number" &&
@@ -3421,16 +3230,19 @@ function isFollowUpRow(value) {
3421
3230
  typeof value.sourceType === "string" &&
3422
3231
  Buffer.isBuffer(value.promptHash) &&
3423
3232
  typeof value.status === "string" &&
3424
- (typeof value.sessionEntryId === "string" || value.sessionEntryId === null) &&
3425
3233
  (Buffer.isBuffer(value.reasonHash) || value.reasonHash === null) &&
3426
3234
  typeof value.createdAt === "number" &&
3427
3235
  typeof value.updatedAt === "number" &&
3428
- (typeof value.sentAt === "number" || value.sentAt === null) &&
3429
3236
  typeof value.revision === "number");
3430
3237
  }
3431
3238
  function isParentRunRow(value) {
3432
3239
  return isRecord(value) && (typeof value.parentRunId === "string" || value.parentRunId === null);
3433
3240
  }
3241
+ function isRunLineageRow(value) {
3242
+ return (isRecord(value) &&
3243
+ typeof value.rootRunId === "string" &&
3244
+ typeof value.restartNumber === "number");
3245
+ }
3434
3246
  function isStatusRow(value) {
3435
3247
  return isRecord(value) && typeof value.status === "string";
3436
3248
  }
@@ -3440,9 +3252,6 @@ function isCountRow(value) {
3440
3252
  function isStepIndexRow(value) {
3441
3253
  return isRecord(value) && typeof value.stepIndex === "number";
3442
3254
  }
3443
- function isLeaseExpiryRow(value) {
3444
- return isRecord(value) && (typeof value.expiresAt === "number" || value.expiresAt === null);
3445
- }
3446
3255
  function isNextOrderRow(value) {
3447
3256
  return isRecord(value) && typeof value.nextOrder === "number";
3448
3257
  }