@osolmaz/pi-workflows 0.16.0 → 0.16.1

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 (154) 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 +42 -16
  22. package/dist/client/view.js.map +1 -1
  23. package/dist/controllers/index.d.ts +1 -1
  24. package/dist/controllers/index.js.map +1 -1
  25. package/dist/controllers/sqlite.d.ts +12 -165
  26. package/dist/controllers/sqlite.js +124 -435
  27. package/dist/controllers/sqlite.js.map +1 -1
  28. package/dist/extension/index.d.ts +23 -2
  29. package/dist/extension/index.js +619 -535
  30. package/dist/extension/index.js.map +1 -1
  31. package/dist/extension/recorder.d.ts +95 -0
  32. package/dist/extension/recorder.js +530 -0
  33. package/dist/extension/recorder.js.map +1 -0
  34. package/dist/extension/remote-recorder-store.d.ts +25 -0
  35. package/dist/extension/remote-recorder-store.js +81 -0
  36. package/dist/extension/remote-recorder-store.js.map +1 -0
  37. package/dist/extension/session-events.d.ts +134 -0
  38. package/dist/extension/session-events.js +60 -0
  39. package/dist/extension/session-events.js.map +1 -0
  40. package/dist/extension/session-view.d.ts +2 -0
  41. package/dist/extension/session-view.js +60 -2
  42. package/dist/extension/session-view.js.map +1 -1
  43. package/dist/extension/step-message.d.ts +6 -4
  44. package/dist/extension/step-message.js +12 -6
  45. package/dist/extension/step-message.js.map +1 -1
  46. package/dist/extension/widget.d.ts +2 -2
  47. package/dist/extension/widget.js +28 -18
  48. package/dist/extension/widget.js.map +1 -1
  49. package/dist/extension/workflow-message-coordinator.d.ts +27 -0
  50. package/dist/extension/workflow-message-coordinator.js +294 -0
  51. package/dist/extension/workflow-message-coordinator.js.map +1 -0
  52. package/dist/host/channel-effects.d.ts +72 -0
  53. package/dist/host/channel-effects.js +271 -0
  54. package/dist/host/channel-effects.js.map +1 -0
  55. package/dist/host/channel-supervisor.d.ts +21 -0
  56. package/dist/host/channel-supervisor.js +54 -0
  57. package/dist/host/channel-supervisor.js.map +1 -0
  58. package/dist/host/runner.d.ts +43 -8
  59. package/dist/host/runner.js +1567 -331
  60. package/dist/host/runner.js.map +1 -1
  61. package/dist/host/state.d.ts +19 -16
  62. package/dist/host/state.js +251 -63
  63. package/dist/host/state.js.map +1 -1
  64. package/dist/host/view.d.ts +19 -9
  65. package/dist/host/view.js +222 -118
  66. package/dist/host/view.js.map +1 -1
  67. package/dist/state/prune.js +3 -25
  68. package/dist/state/prune.js.map +1 -1
  69. package/dist/state/schema.js +76 -97
  70. package/dist/state/schema.js.map +1 -1
  71. package/dist/state/workflow-messages.d.ts +96 -0
  72. package/dist/state/workflow-messages.js +335 -0
  73. package/dist/state/workflow-messages.js.map +1 -0
  74. package/dist/viewer/render.js +1 -1
  75. package/dist/viewer/render.js.map +1 -1
  76. package/dist/viewer/tui.d.ts +1 -1
  77. package/dist/viewer/tui.js +65 -8
  78. package/dist/viewer/tui.js.map +1 -1
  79. package/dist/workflows/human-decision.d.ts +2 -0
  80. package/dist/workflows/human-decision.js +8 -2
  81. package/dist/workflows/human-decision.js.map +1 -1
  82. package/dist/workflows/index.d.ts +1 -1
  83. package/dist/workflows/index.js.map +1 -1
  84. package/dist/workflows/settings.d.ts +1 -8
  85. package/dist/workflows/settings.js.map +1 -1
  86. package/dist/workflows/store.d.ts +5 -19
  87. package/dist/workflows/store.js +121 -354
  88. package/dist/workflows/store.js.map +1 -1
  89. package/dist/workflows/tool-input.d.ts +21 -0
  90. package/dist/workflows/tool-input.js +23 -1
  91. package/dist/workflows/tool-input.js.map +1 -1
  92. package/dist/workflows/types.d.ts +5 -5
  93. package/dist/workflows/workflow-message-content.d.ts +38 -0
  94. package/dist/workflows/workflow-message-content.js +157 -0
  95. package/dist/workflows/workflow-message-content.js.map +1 -0
  96. package/docs/2026-08-18-herdr-piw-plan.md +2 -1
  97. package/docs/2026-08-20-durable-workflow-launch-plan.md +1 -1
  98. package/docs/2026-08-25-workflow-follow-ups.md +26 -50
  99. package/docs/2026-08-30-out-of-process-workflow-host-plan.md +3 -3
  100. package/docs/2026-09-01-restore-session-delivery-controls-plan.md +4 -0
  101. package/docs/2026-09-01-unified-workflow-client-plan.md +5 -1
  102. package/docs/2026-09-02-installed-live-e2e-plan.md +15 -1
  103. package/docs/2026-09-02-unify-workflow-messages-plan.md +562 -0
  104. package/docs/DEFERRED_TURNS.md +66 -294
  105. package/docs/HUMAN_DECISIONS.md +29 -30
  106. package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -11
  107. package/docs/SQLITE_STATE.md +19 -15
  108. package/docs/WORKFLOW_HOST.md +93 -28
  109. package/docs/WORKFLOW_STEP_MESSAGES.md +166 -75
  110. package/docs/live-replay-protocol.md +8 -4
  111. package/docs/tui-viewer.md +3 -1
  112. package/docs/workflows.md +34 -27
  113. package/herdr-plugin.toml +1 -1
  114. package/package.json +1 -1
  115. package/plugins/herdr/viewer.mjs +14 -5
  116. package/src/channels/adapter-entry.ts +220 -0
  117. package/src/channels/config.ts +296 -0
  118. package/src/channels/protocol.ts +333 -0
  119. package/src/channels/telegram.ts +335 -0
  120. package/src/client/client.ts +6 -2
  121. package/src/client/index.ts +2 -1
  122. package/src/client/protocol.ts +11 -5
  123. package/src/client/view.ts +43 -18
  124. package/src/controllers/index.ts +0 -1
  125. package/src/controllers/sqlite.ts +164 -844
  126. package/src/extension/index.ts +693 -646
  127. package/src/extension/recorder.ts +687 -0
  128. package/src/extension/remote-recorder-store.ts +126 -0
  129. package/src/extension/session-events.ts +119 -0
  130. package/src/extension/session-view.ts +86 -3
  131. package/src/extension/step-message.ts +19 -9
  132. package/src/extension/widget.ts +37 -17
  133. package/src/extension/workflow-message-coordinator.ts +334 -0
  134. package/src/host/channel-effects.ts +465 -0
  135. package/src/host/channel-supervisor.ts +73 -0
  136. package/src/host/runner.ts +2120 -531
  137. package/src/host/state.ts +333 -94
  138. package/src/host/view.ts +249 -121
  139. package/src/state/prune.ts +3 -31
  140. package/src/state/schema.ts +76 -97
  141. package/src/state/workflow-messages.ts +548 -0
  142. package/src/viewer/render.ts +1 -5
  143. package/src/viewer/tui.ts +71 -6
  144. package/src/workflows/human-decision.ts +18 -2
  145. package/src/workflows/index.ts +0 -1
  146. package/src/workflows/settings.ts +1 -20
  147. package/src/workflows/store.ts +173 -489
  148. package/src/workflows/tool-input.ts +36 -1
  149. package/src/workflows/types.ts +5 -5
  150. package/src/workflows/workflow-message-content.ts +197 -0
  151. package/dist/extension/decision-channels.d.ts +0 -134
  152. package/dist/extension/decision-channels.js +0 -1307
  153. package/dist/extension/decision-channels.js.map +0 -1
  154. package/src/extension/decision-channels.ts +0 -1826
package/src/viewer/tui.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { WorkflowClient } from "../client/client.js";
2
2
  import { materializeRunView } from "../client/materialize.js";
3
3
  import { sanitizeText } from "../render/ansi.js";
4
+ import { formatDuration } from "../render/format.js";
4
5
  import type { JsonValue } from "../state/json.js";
5
6
 
6
7
  const ALT_SCREEN_ON = "\u001b[?1049h\u001b[?25l";
@@ -17,6 +18,7 @@ export async function runViewer(options: ViewerOptions): Promise<void> {
17
18
  let value: JsonValue = options.runId === undefined ? [] : null;
18
19
  let selected = 0;
19
20
  let scroll = 0;
21
+ let selectedStepIndex: number | null = null;
20
22
  let mode: "runs" | "run" = options.runId === undefined ? "runs" : "run";
21
23
  let unsubscribe: (() => Promise<void>) | undefined;
22
24
  let runMaterializationGeneration = 0;
@@ -30,6 +32,8 @@ export async function runViewer(options: ViewerOptions): Promise<void> {
30
32
  height,
31
33
  mode === "runs" ? selected : undefined,
32
34
  scroll,
35
+ new Date(),
36
+ selectedStepIndex,
33
37
  );
34
38
  process.stdout.write(`${CLEAR}${lines.join("\n")}`);
35
39
  };
@@ -39,6 +43,7 @@ export async function runViewer(options: ViewerOptions): Promise<void> {
39
43
  await unsubscribe?.();
40
44
  mode = "runs";
41
45
  scroll = 0;
46
+ selectedStepIndex = null;
42
47
  unsubscribe = await options.client.watchRuns((event) => {
43
48
  value = event.payload;
44
49
  selected = Math.min(selected, Math.max(0, arrayLength(value) - 1));
@@ -51,6 +56,7 @@ export async function runViewer(options: ViewerOptions): Promise<void> {
51
56
  await unsubscribe?.();
52
57
  mode = "run";
53
58
  scroll = 0;
59
+ selectedStepIndex = null;
54
60
  unsubscribe = await options.client.watchRun(runId, (event) => {
55
61
  const generation = ++runMaterializationGeneration;
56
62
  value = event.payload;
@@ -75,6 +81,8 @@ export async function runViewer(options: ViewerOptions): Promise<void> {
75
81
  draw();
76
82
  if (rawModeSupported) process.stdin.setRawMode(true);
77
83
  process.stdin.resume();
84
+ const redrawTimer = setInterval(draw, 1_000);
85
+ redrawTimer.unref?.();
78
86
  await new Promise<void>((resolve) => {
79
87
  const onKey = (data: Buffer) => {
80
88
  const key = data.toString("utf8");
@@ -101,6 +109,29 @@ export async function runViewer(options: ViewerOptions): Promise<void> {
101
109
  } else if (key === "\u001b[6~") {
102
110
  scroll += page;
103
111
  draw();
112
+ } else if (key === "[") {
113
+ const total = runStepCount(value);
114
+ if (total > 0) {
115
+ selectedStepIndex = Math.max(0, (selectedStepIndex ?? total - 1) - 1);
116
+ scroll = 0;
117
+ draw();
118
+ }
119
+ } else if (key === "]") {
120
+ const total = runStepCount(value);
121
+ if (total > 0) {
122
+ const next = (selectedStepIndex ?? total - 1) + 1;
123
+ selectedStepIndex = next >= total - 1 ? null : next;
124
+ scroll = 0;
125
+ draw();
126
+ }
127
+ } else if (key === "\u001b[F" || key === "G" || key === "L") {
128
+ selectedStepIndex = null;
129
+ scroll = 0;
130
+ draw();
131
+ } else if (key === "\u001b[H" || key === "g") {
132
+ selectedStepIndex = runStepCount(value) > 0 ? 0 : null;
133
+ scroll = 0;
134
+ draw();
104
135
  }
105
136
  } else if (mode === "runs") {
106
137
  if (key === "\u001b[A" || key === "k") {
@@ -117,6 +148,7 @@ export async function runViewer(options: ViewerOptions): Promise<void> {
117
148
  };
118
149
  process.stdin.on("data", onKey);
119
150
  });
151
+ clearInterval(redrawTimer);
120
152
  } finally {
121
153
  runMaterializationGeneration += 1;
122
154
  await unsubscribe?.();
@@ -132,6 +164,8 @@ export function renderClientView(
132
164
  height: number,
133
165
  selected: number | undefined,
134
166
  scroll: number,
167
+ now: Date = new Date(),
168
+ selectedStepIndex: number | null = null,
135
169
  ): string[] {
136
170
  if (Array.isArray(value)) {
137
171
  if (value.length === 0) return ["No workflow runs found."];
@@ -146,12 +180,12 @@ export function renderClientView(
146
180
  return truncate(`${prefix}${summary(item)}`, width);
147
181
  });
148
182
  }
149
- const lines = renderRun(value);
183
+ const lines = renderRun(value, now, selectedStepIndex);
150
184
  const start = Math.min(scroll, Math.max(0, lines.length - height));
151
185
  return lines.slice(start, start + height).map((line) => truncate(line, width));
152
186
  }
153
187
 
154
- function renderRun(value: JsonValue): string[] {
188
+ function renderRun(value: JsonValue, now: Date, selectedStepIndex: number | null): string[] {
155
189
  if (!isRecord(value) || value.schema !== "pi-workflows.run-view.v1") {
156
190
  return JSON.stringify(value, null, 2).split("\n");
157
191
  }
@@ -166,20 +200,46 @@ function renderRun(value: JsonValue): string[] {
166
200
  : "unknown";
167
201
  const runId = typeof value.runId === "string" ? sanitizeText(value.runId) : "unknown";
168
202
  const status = typeof display.status === "string" ? display.status : "unknown";
203
+ const steps = Array.isArray(state.steps) ? state.steps : [];
204
+ const selected =
205
+ steps.length === 0
206
+ ? -1
207
+ : Math.min(Math.max(0, selectedStepIndex ?? steps.length - 1), steps.length - 1);
208
+ const startedAt = typeof manifest.startedAt === "string" ? Date.parse(manifest.startedAt) : NaN;
209
+ const finishedAt =
210
+ typeof manifest.finishedAt === "string" ? Date.parse(manifest.finishedAt) : NaN;
211
+ const elapsed = Number.isFinite(startedAt)
212
+ ? ` · elapsed ${formatDuration(Math.max(0, (Number.isFinite(finishedAt) ? finishedAt : now.getTime()) - startedAt))}`
213
+ : "";
214
+ const position =
215
+ selectedStepIndex === null || selected < 0 ? "" : ` · step ${selected + 1}/${steps.length}`;
169
216
  const lines = [
170
217
  `workflow ${workflowName}`,
171
- `${statusGlyph(status)} ${sanitizeText(status)} · run ${runId}`,
218
+ `${statusGlyph(status)} ${sanitizeText(status)} · run ${runId}${elapsed}${position}`,
172
219
  ];
173
220
  if (typeof display.reason === "string") lines.push(`reason: ${sanitizeText(display.reason)}`);
174
221
 
175
- const steps = Array.isArray(state.steps) ? state.steps : [];
176
222
  if (steps.length > 0) {
177
223
  lines.push("", "steps");
178
- for (const step of steps) {
224
+ for (const [index, step] of steps.entries()) {
179
225
  if (!isRecord(step)) continue;
180
226
  const nodeId = typeof step.nodeId === "string" ? sanitizeText(step.nodeId) : "unknown";
181
227
  const outcome = typeof step.outcome === "string" ? step.outcome : "unknown";
182
- lines.push(` ${stepGlyph(outcome)} ${nodeId} · ${sanitizeText(outcome)}`);
228
+ lines.push(
229
+ `${index === selected ? ">" : " "} ${stepGlyph(outcome)} ${nodeId} · ${sanitizeText(outcome)}`,
230
+ );
231
+ }
232
+ const inspected = steps[selected];
233
+ if (isRecord(inspected)) {
234
+ const nodeId =
235
+ typeof inspected.nodeId === "string" ? sanitizeText(inspected.nodeId) : "unknown";
236
+ const output = Object.hasOwn(inspected, "error") ? inspected.error : inspected.output;
237
+ lines.push("", `step output — ${nodeId}`);
238
+ lines.push(
239
+ ...JSON.stringify(output ?? null, null, 2)
240
+ .split("\n")
241
+ .map((line) => ` ${sanitizeText(line)}`),
242
+ );
183
243
  }
184
244
  }
185
245
  if (status === "completed" && Object.hasOwn(state, "finalOutput")) {
@@ -218,6 +278,11 @@ function selectedRunId(value: JsonValue, index: number): string | undefined {
218
278
  return isRecord(item) && typeof item.runId === "string" ? item.runId : undefined;
219
279
  }
220
280
 
281
+ function runStepCount(value: JsonValue): number {
282
+ if (!isRecord(value) || !isRecord(value.state) || !Array.isArray(value.state.steps)) return 0;
283
+ return value.state.steps.length;
284
+ }
285
+
221
286
  function arrayLength(value: JsonValue): number {
222
287
  return Array.isArray(value) ? value.length : 0;
223
288
  }
@@ -468,6 +468,14 @@ export class HumanDecisionStore {
468
468
  channel: string,
469
469
  value: HumanDecisionDeliveryRecord,
470
470
  ): Promise<"created" | "adopted"> {
471
+ return this.recordDeliverySync(request, channel, value);
472
+ }
473
+
474
+ recordDeliverySync(
475
+ request: HumanDecisionRequest | HumanDecisionChannelRequest,
476
+ channel: string,
477
+ value: HumanDecisionDeliveryRecord,
478
+ ): "created" | "adopted" {
471
479
  return this.recordChannelMessage(request.decisionId, channel, "delivery", value);
472
480
  }
473
481
 
@@ -570,6 +578,14 @@ export class HumanDecisionStore {
570
578
  channel: string,
571
579
  value: HumanDecisionSettlementRecord,
572
580
  ): Promise<"created" | "adopted"> {
581
+ return this.recordSettlementSync(decisionId, channel, value);
582
+ }
583
+
584
+ recordSettlementSync(
585
+ decisionId: string,
586
+ channel: string,
587
+ value: HumanDecisionSettlementRecord,
588
+ ): "created" | "adopted" {
573
589
  return this.recordChannelMessage(decisionId, channel, "settlement", value);
574
590
  }
575
591
 
@@ -1066,7 +1082,7 @@ export class HumanDecisionStore {
1066
1082
  purpose: "delivery" | "settlement",
1067
1083
  value: T,
1068
1084
  ): "created" | "adopted" {
1069
- const safeChannel = requireSimpleId(channel, "Human decision channel");
1085
+ const safeChannel = requireChannelId(channel, "Human decision channel");
1070
1086
  assertId(value.attemptId, `${purpose} attempt id`);
1071
1087
  return this.state.transaction(() => {
1072
1088
  if (this.readRequestRow(decisionId) === undefined) {
@@ -1120,7 +1136,7 @@ export class HumanDecisionStore {
1120
1136
  channel: string,
1121
1137
  purpose: "delivery" | "settlement",
1122
1138
  ): T[] {
1123
- const channelId = channelIdFor(requireSimpleId(channel, "Human decision channel"));
1139
+ const channelId = channelIdFor(requireChannelId(channel, "Human decision channel"));
1124
1140
  const rows = this.state.connection
1125
1141
  .prepare(
1126
1142
  `SELECT content_hash AS contentHash
@@ -92,7 +92,6 @@ export {
92
92
  type WorkflowFollowUpQueueRecord,
93
93
  type WorkflowFollowUpRecord,
94
94
  type WorkflowFollowUpState,
95
- type WorkflowPresentationState,
96
95
  type WorkflowQueueFollowUpRequest,
97
96
  type WorkflowRemoveFollowUpRequest,
98
97
  type WorkflowSettingsChangeContext,
@@ -101,20 +101,7 @@ export type WorkflowSettingsChangeResult = {
101
101
  adopted: boolean;
102
102
  };
103
103
 
104
- export type WorkflowFollowUpState =
105
- | "queued"
106
- | "pending_presentation"
107
- | "ready"
108
- | "sent"
109
- | "removed"
110
- | "cancelled";
111
-
112
- export type WorkflowPresentationState =
113
- | "none"
114
- | "not-needed"
115
- | "pending"
116
- | "settled"
117
- | "unavailable";
104
+ export type WorkflowFollowUpState = "queued" | "removed" | "cancelled";
118
105
 
119
106
  export type WorkflowFollowUpRecord = {
120
107
  followUpId: string;
@@ -126,20 +113,14 @@ export type WorkflowFollowUpRecord = {
126
113
  source: string;
127
114
  prompt: string;
128
115
  state: WorkflowFollowUpState;
129
- sessionEntryId?: string;
130
116
  reason?: string;
131
117
  createdAt: string;
132
118
  updatedAt: string;
133
- sentAt?: string;
134
119
  };
135
120
 
136
121
  export type WorkflowFollowUpQueueRecord = {
137
122
  runId: string;
138
123
  originSessionId?: string;
139
- presentationState: WorkflowPresentationState;
140
- presentationEntryId?: string;
141
- presentationAssistantEntryId?: string;
142
- presentationReason?: string;
143
124
  followUps: WorkflowFollowUpRecord[];
144
125
  };
145
126