@osolmaz/pi-workflows 0.15.3 → 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 (207) hide show
  1. package/README.md +24 -8
  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/activity.d.ts +2 -0
  15. package/dist/client/activity.js +6 -0
  16. package/dist/client/activity.js.map +1 -0
  17. package/dist/client/client.d.ts +102 -0
  18. package/dist/client/client.js +737 -0
  19. package/dist/client/client.js.map +1 -0
  20. package/dist/client/index.d.ts +3 -0
  21. package/dist/client/index.js +3 -0
  22. package/dist/client/index.js.map +1 -0
  23. package/dist/client/materialize.d.ts +7 -0
  24. package/dist/client/materialize.js +177 -0
  25. package/dist/client/materialize.js.map +1 -0
  26. package/dist/client/protocol.d.ts +60 -0
  27. package/dist/client/protocol.js +275 -0
  28. package/dist/client/protocol.js.map +1 -0
  29. package/dist/client/resolver.d.ts +23 -0
  30. package/dist/client/resolver.js +2 -0
  31. package/dist/client/resolver.js.map +1 -0
  32. package/dist/client/view.d.ts +144 -0
  33. package/dist/client/view.js +3 -0
  34. package/dist/client/view.js.map +1 -0
  35. package/dist/controllers/index.d.ts +1 -1
  36. package/dist/controllers/index.js.map +1 -1
  37. package/dist/controllers/sqlite.d.ts +47 -157
  38. package/dist/controllers/sqlite.js +230 -406
  39. package/dist/controllers/sqlite.js.map +1 -1
  40. package/dist/extension/index.d.ts +23 -1
  41. package/dist/extension/index.js +734 -555
  42. package/dist/extension/index.js.map +1 -1
  43. package/dist/extension/recorder.d.ts +95 -0
  44. package/dist/extension/recorder.js +530 -0
  45. package/dist/extension/recorder.js.map +1 -0
  46. package/dist/extension/remote-recorder-store.d.ts +25 -0
  47. package/dist/extension/remote-recorder-store.js +81 -0
  48. package/dist/extension/remote-recorder-store.js.map +1 -0
  49. package/dist/extension/session-events.d.ts +134 -0
  50. package/dist/extension/session-events.js +60 -0
  51. package/dist/extension/session-events.js.map +1 -0
  52. package/dist/extension/session-view.d.ts +9 -2
  53. package/dist/extension/session-view.js +117 -51
  54. package/dist/extension/session-view.js.map +1 -1
  55. package/dist/extension/step-message.d.ts +6 -4
  56. package/dist/extension/step-message.js +12 -6
  57. package/dist/extension/step-message.js.map +1 -1
  58. package/dist/extension/widget.d.ts +3 -2
  59. package/dist/extension/widget.js +41 -24
  60. package/dist/extension/widget.js.map +1 -1
  61. package/dist/extension/workflow-message-coordinator.d.ts +27 -0
  62. package/dist/extension/workflow-message-coordinator.js +294 -0
  63. package/dist/extension/workflow-message-coordinator.js.map +1 -0
  64. package/dist/host/channel-effects.d.ts +72 -0
  65. package/dist/host/channel-effects.js +271 -0
  66. package/dist/host/channel-effects.js.map +1 -0
  67. package/dist/host/channel-supervisor.d.ts +21 -0
  68. package/dist/host/channel-supervisor.js +54 -0
  69. package/dist/host/channel-supervisor.js.map +1 -0
  70. package/dist/host/child-worker-supervisor.js +1 -1
  71. package/dist/host/child-worker-supervisor.js.map +1 -1
  72. package/dist/host/resolver-entry.d.ts +2 -23
  73. package/dist/host/resolver-entry.js +1 -1
  74. package/dist/host/resolver-entry.js.map +1 -1
  75. package/dist/host/runner.d.ts +54 -8
  76. package/dist/host/runner.js +1997 -305
  77. package/dist/host/runner.js.map +1 -1
  78. package/dist/host/state.d.ts +25 -19
  79. package/dist/host/state.js +303 -89
  80. package/dist/host/state.js.map +1 -1
  81. package/dist/host/view.d.ts +83 -0
  82. package/dist/host/view.js +975 -0
  83. package/dist/host/view.js.map +1 -0
  84. package/dist/host/worker-protocol.js +1 -1
  85. package/dist/host/worker-protocol.js.map +1 -1
  86. package/dist/state/database.d.ts +1 -0
  87. package/dist/state/database.js +15 -0
  88. package/dist/state/database.js.map +1 -1
  89. package/dist/state/prune.d.ts +3 -1
  90. package/dist/state/prune.js +9 -33
  91. package/dist/state/prune.js.map +1 -1
  92. package/dist/state/schema.js +88 -98
  93. package/dist/state/schema.js.map +1 -1
  94. package/dist/state/workflow-messages.d.ts +96 -0
  95. package/dist/state/workflow-messages.js +335 -0
  96. package/dist/state/workflow-messages.js.map +1 -0
  97. package/dist/viewer/backup.d.ts +2 -0
  98. package/dist/viewer/backup.js +28 -0
  99. package/dist/viewer/backup.js.map +1 -0
  100. package/dist/viewer/cli.d.ts +4 -0
  101. package/dist/viewer/cli.js +150 -170
  102. package/dist/viewer/cli.js.map +1 -1
  103. package/dist/viewer/render.js +1 -1
  104. package/dist/viewer/render.js.map +1 -1
  105. package/dist/viewer/tui.d.ts +5 -7
  106. package/dist/viewer/tui.js +245 -108
  107. package/dist/viewer/tui.js.map +1 -1
  108. package/dist/workflows/human-decision.d.ts +2 -0
  109. package/dist/workflows/human-decision.js +8 -2
  110. package/dist/workflows/human-decision.js.map +1 -1
  111. package/dist/workflows/index.d.ts +1 -1
  112. package/dist/workflows/index.js.map +1 -1
  113. package/dist/workflows/settings.d.ts +1 -8
  114. package/dist/workflows/settings.js.map +1 -1
  115. package/dist/workflows/store.d.ts +67 -20
  116. package/dist/workflows/store.js +451 -378
  117. package/dist/workflows/store.js.map +1 -1
  118. package/dist/workflows/tool-input.d.ts +21 -0
  119. package/dist/workflows/tool-input.js +23 -1
  120. package/dist/workflows/tool-input.js.map +1 -1
  121. package/dist/workflows/types.d.ts +5 -5
  122. package/dist/workflows/workflow-message-content.d.ts +38 -0
  123. package/dist/workflows/workflow-message-content.js +157 -0
  124. package/dist/workflows/workflow-message-content.js.map +1 -0
  125. package/docs/2026-08-18-herdr-piw-plan.md +2 -1
  126. package/docs/2026-08-20-durable-workflow-launch-plan.md +1 -1
  127. package/docs/2026-08-25-workflow-follow-ups.md +26 -50
  128. package/docs/2026-08-30-out-of-process-workflow-host-plan.md +3 -3
  129. package/docs/2026-09-01-restore-session-delivery-controls-plan.md +4 -0
  130. package/docs/2026-09-01-unified-workflow-client-plan.md +385 -0
  131. package/docs/2026-09-02-installed-live-e2e-plan.md +239 -0
  132. package/docs/2026-09-02-unify-workflow-messages-plan.md +562 -0
  133. package/docs/DEFERRED_TURNS.md +66 -294
  134. package/docs/HUMAN_DECISIONS.md +29 -30
  135. package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -11
  136. package/docs/SQLITE_STATE.md +31 -25
  137. package/docs/WORKFLOW_HOST.md +155 -77
  138. package/docs/WORKFLOW_STEP_MESSAGES.md +166 -75
  139. package/docs/development.md +2 -1
  140. package/docs/live-replay-protocol.md +74 -132
  141. package/docs/tui-viewer.md +12 -14
  142. package/docs/workflows.md +78 -28
  143. package/herdr-plugin.toml +1 -1
  144. package/package.json +9 -3
  145. package/plugins/herdr/viewer.mjs +14 -5
  146. package/protocol/client.v1.schema.json +137 -0
  147. package/protocol/fixtures/client-v1.json +23 -0
  148. package/src/channels/adapter-entry.ts +220 -0
  149. package/src/channels/config.ts +296 -0
  150. package/src/channels/protocol.ts +333 -0
  151. package/src/channels/telegram.ts +335 -0
  152. package/src/client/activity.ts +6 -0
  153. package/src/client/client.ts +939 -0
  154. package/src/client/index.ts +25 -0
  155. package/src/client/materialize.ts +228 -0
  156. package/src/client/protocol.ts +333 -0
  157. package/src/client/resolver.ts +26 -0
  158. package/src/client/view.ts +163 -0
  159. package/src/controllers/index.ts +0 -1
  160. package/src/controllers/sqlite.ts +339 -808
  161. package/src/extension/index.ts +853 -665
  162. package/src/extension/recorder.ts +687 -0
  163. package/src/extension/remote-recorder-store.ts +126 -0
  164. package/src/extension/session-events.ts +119 -0
  165. package/src/extension/session-view.ts +157 -51
  166. package/src/extension/step-message.ts +19 -9
  167. package/src/extension/widget.ts +53 -25
  168. package/src/extension/workflow-message-coordinator.ts +334 -0
  169. package/src/host/channel-effects.ts +465 -0
  170. package/src/host/channel-supervisor.ts +73 -0
  171. package/src/host/child-worker-supervisor.ts +1 -1
  172. package/src/host/resolver-entry.ts +11 -26
  173. package/src/host/runner.ts +2639 -450
  174. package/src/host/state.ts +404 -137
  175. package/src/host/view.ts +1212 -0
  176. package/src/host/worker-protocol.ts +1 -1
  177. package/src/state/database.ts +13 -0
  178. package/src/state/prune.ts +14 -42
  179. package/src/state/schema.ts +88 -98
  180. package/src/state/workflow-messages.ts +548 -0
  181. package/src/viewer/backup.ts +29 -0
  182. package/src/viewer/cli.ts +171 -185
  183. package/src/viewer/render.ts +1 -5
  184. package/src/viewer/tui.ts +261 -124
  185. package/src/workflows/human-decision.ts +18 -2
  186. package/src/workflows/index.ts +0 -1
  187. package/src/workflows/settings.ts +1 -20
  188. package/src/workflows/store.ts +650 -511
  189. package/src/workflows/tool-input.ts +36 -1
  190. package/src/workflows/types.ts +5 -5
  191. package/src/workflows/workflow-message-content.ts +197 -0
  192. package/dist/extension/decision-channels.d.ts +0 -134
  193. package/dist/extension/decision-channels.js +0 -1307
  194. package/dist/extension/decision-channels.js.map +0 -1
  195. package/dist/host/client.d.ts +0 -48
  196. package/dist/host/client.js +0 -216
  197. package/dist/host/client.js.map +0 -1
  198. package/dist/host/protocol.d.ts +0 -38
  199. package/dist/host/protocol.js +0 -156
  200. package/dist/host/protocol.js.map +0 -1
  201. package/dist/viewer/watch.d.ts +0 -6
  202. package/dist/viewer/watch.js +0 -46
  203. package/dist/viewer/watch.js.map +0 -1
  204. package/src/extension/decision-channels.ts +0 -1826
  205. package/src/host/client.ts +0 -293
  206. package/src/host/protocol.ts +0 -196
  207. package/src/viewer/watch.ts +0 -51
@@ -1,5 +1,5 @@
1
+ import { MAX_PROTOCOL_MESSAGE_BYTES } from "../client/protocol.js";
1
2
  import { canonicalJson, parseJson, type JsonValue } from "../state/json.js";
2
- import { MAX_PROTOCOL_MESSAGE_BYTES } from "./protocol.js";
3
3
 
4
4
  export const WORKER_MESSAGE_SCHEMA = "pi-workflows.worker-message.v1" as const;
5
5
  export const WORKER_RESPONSE_SCHEMA = "pi-workflows.worker-response.v1" as const;
@@ -110,6 +110,19 @@ export class StateDatabase {
110
110
  this.connection.close();
111
111
  }
112
112
 
113
+ readTransaction<T>(operation: () => T): T {
114
+ if (this.connection.inTransaction) return operation();
115
+ this.connection.exec("BEGIN");
116
+ try {
117
+ const result = operation();
118
+ this.connection.exec("COMMIT");
119
+ return result;
120
+ } catch (error) {
121
+ if (this.connection.inTransaction) this.connection.exec("ROLLBACK");
122
+ throw error;
123
+ }
124
+ }
125
+
113
126
  transaction<T>(operation: () => T): T {
114
127
  if (this.mode === "read-only") {
115
128
  throw new Error("Cannot mutate a read-only Pi Workflows database");
@@ -34,25 +34,26 @@ export type StatePruneReport = {
34
34
  applied: boolean;
35
35
  };
36
36
 
37
- export async function pruneState(
38
- databasePath: string,
39
- options: StatePruneOptions,
40
- ): Promise<StatePruneReport> {
41
- const cutoff = parseCutoff(options.before);
37
+ export function validateStatePruneOptions(options: StatePruneOptions): void {
38
+ parseCutoff(options.before);
42
39
  if (options.apply && options.backupPath === undefined) {
43
40
  throw new Error("state prune --apply requires --backup <absolute-path>");
44
41
  }
45
42
  if (options.backupPath !== undefined && !path.isAbsolute(options.backupPath)) {
46
43
  throw new Error("state prune backup path must be absolute");
47
44
  }
45
+ }
46
+
47
+ export async function pruneState(
48
+ state: StateDatabase,
49
+ databasePath: string,
50
+ options: StatePruneOptions,
51
+ ): Promise<StatePruneReport> {
52
+ validateStatePruneOptions(options);
53
+ const cutoff = parseCutoff(options.before);
48
54
  const lockPath = `${databasePath}.maintenance.lock`;
49
55
  const lock = options.apply ? acquireMaintenanceLock(lockPath) : undefined;
50
- let state: StateDatabase | undefined;
51
56
  try {
52
- state = new StateDatabase({
53
- filePath: databasePath,
54
- mode: options.apply ? "read-write" : "read-only",
55
- });
56
57
  const selection = selectRunTrees(state, cutoff);
57
58
  const sizeBefore = databaseBytes(databasePath);
58
59
  const base = {
@@ -136,7 +137,6 @@ export async function pruneState(
136
137
  applied: true,
137
138
  };
138
139
  } finally {
139
- state?.close();
140
140
  if (lock !== undefined) releaseMaintenanceLock(lockPath, lock);
141
141
  }
142
142
  }
@@ -215,13 +215,7 @@ function treeHasBlocker(database: Database.Database, runIds: string[]): boolean
215
215
  hasRow(
216
216
  database,
217
217
  `SELECT 1 FROM workflow_follow_ups
218
- WHERE run_id IN (${values}) AND status IN ('queued', 'pending_presentation', 'ready')`,
219
- runIds,
220
- ) ||
221
- hasRow(
222
- database,
223
- `SELECT 1 FROM workflow_follow_up_queues
224
- WHERE run_id IN (${values}) AND presentation_state = 'pending'`,
218
+ WHERE run_id IN (${values}) AND status = 'queued'`,
225
219
  runIds,
226
220
  ) ||
227
221
  hasRow(
@@ -294,15 +288,8 @@ function deleteRunAggregates(database: Database.Database, runIds: string[]): voi
294
288
  const effectIds = effectRows.map((row) => row.effectId);
295
289
  if (effectIds.length !== 0) {
296
290
  const effectValues = placeholders(effectIds);
297
- database
298
- .prepare(`DELETE FROM notifications WHERE effect_id IN (${effectValues})`)
299
- .run(...effectIds);
300
- database
301
- .prepare(`DELETE FROM turn_intents WHERE effect_id IN (${effectValues})`)
302
- .run(...effectIds);
303
291
  database.prepare(`DELETE FROM effects WHERE effect_id IN (${effectValues})`).run(...effectIds);
304
292
  }
305
- database.prepare(`DELETE FROM turn_intents WHERE run_id IN (${values})`).run(...runIds);
306
293
  database
307
294
  .prepare(
308
295
  `DELETE FROM continuations
@@ -331,13 +318,11 @@ function relatedResourceIds(database: Database.Database, runIds: string[]): stri
331
318
  `SELECT resource_id AS resourceId FROM runs WHERE run_id IN (${values})
332
319
  UNION SELECT resource_id FROM session_segments WHERE run_id IN (${values})
333
320
  UNION SELECT resource_id FROM human_decisions WHERE run_id IN (${values})
334
- UNION SELECT resource_id FROM turn_intents WHERE run_id IN (${values})
335
321
  UNION SELECT resource_id FROM workflow_settings
336
322
  WHERE origin_run_id IN (${values}) OR active_run_id IN (${values})
337
- UNION SELECT resource_id FROM workflow_follow_up_queues WHERE run_id IN (${values})
338
323
  UNION SELECT resource_id FROM workflow_follow_ups WHERE run_id IN (${values})`,
339
324
  )
340
- .all(...runIds, ...runIds, ...runIds, ...runIds, ...runIds, ...runIds, ...runIds, ...runIds)
325
+ .all(...runIds, ...runIds, ...runIds, ...runIds, ...runIds, ...runIds)
341
326
  .filter(isResourceRow);
342
327
  const resources = rows.map((row) => row.resourceId);
343
328
  if (resources.length === 0) return [];
@@ -349,20 +334,7 @@ function relatedResourceIds(database: Database.Database, runIds: string[]): stri
349
334
  .all(...resources)
350
335
  .filter(isResourceRow)
351
336
  .map((row) => row.resourceId);
352
- const notificationResources = database
353
- .prepare(
354
- `SELECT resource.resource_id AS resourceId
355
- FROM notifications notification
356
- JOIN effects effect ON effect.effect_id = notification.effect_id
357
- JOIN resources resource
358
- ON resource.resource_type = 'notification'
359
- AND resource.aggregate_key = notification.notification_id
360
- WHERE effect.source_resource_id IN (${placeholders(resources)})`,
361
- )
362
- .all(...resources)
363
- .filter(isResourceRow)
364
- .map((row) => row.resourceId);
365
- return [...new Set([...resources, ...effectResources, ...notificationResources])];
337
+ return [...new Set([...resources, ...effectResources])];
366
338
  }
367
339
 
368
340
  function selectionSignature(database: Database.Database, runIds: string[]): string {
@@ -56,8 +56,7 @@ CREATE TABLE blobs (
56
56
  CREATE TABLE resources (
57
57
  resource_id TEXT PRIMARY KEY,
58
58
  resource_type TEXT NOT NULL CHECK (resource_type IN (
59
- 'run', 'session', 'decision', 'controller', 'effect', 'channel', 'notification', 'turn_intent',
60
- 'settings', 'follow_up_queue', 'follow_up'
59
+ 'run', 'session', 'decision', 'controller', 'effect', 'channel', 'settings', 'follow_up'
61
60
  )),
62
61
  aggregate_key TEXT NOT NULL,
63
62
  revision INTEGER NOT NULL DEFAULT 0 CHECK (revision >= 0),
@@ -115,6 +114,12 @@ CREATE TABLE runs (
115
114
  resource_id TEXT NOT NULL UNIQUE REFERENCES resources(resource_id) ON DELETE CASCADE,
116
115
  project_id TEXT REFERENCES projects(project_id),
117
116
  parent_run_id TEXT REFERENCES runs(run_id),
117
+ root_run_id TEXT NOT NULL REFERENCES runs(run_id),
118
+ lineage_kind TEXT CHECK (lineage_kind IS NULL OR lineage_kind IN ('continuation', 'restart')),
119
+ restart_number INTEGER NOT NULL DEFAULT 0 CHECK (restart_number >= 0),
120
+ parent_terminal_fingerprint BLOB CHECK (
121
+ parent_terminal_fingerprint IS NULL OR length(parent_terminal_fingerprint) = 32
122
+ ),
118
123
  definition_digest BLOB NOT NULL REFERENCES workflow_definitions(definition_digest),
119
124
  workflow_ref TEXT NOT NULL,
120
125
  launch_options_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
@@ -127,15 +132,35 @@ CREATE TABLE runs (
127
132
  input_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
128
133
  final_output_hash BLOB REFERENCES blobs(blob_hash),
129
134
  error_hash BLOB REFERENCES blobs(blob_hash),
135
+ presentation_prompt_hash BLOB REFERENCES blobs(blob_hash),
130
136
  created_at INTEGER NOT NULL,
131
137
  updated_at INTEGER NOT NULL,
132
138
  finished_at INTEGER,
133
- CHECK ((status IN ('waiting', 'completed', 'failed', 'timed_out', 'cancelled')) = (finished_at IS NOT NULL))
139
+ CHECK ((status IN ('waiting', 'completed', 'failed', 'timed_out', 'cancelled')) = (finished_at IS NOT NULL)),
140
+ CHECK ((parent_run_id IS NULL) = (lineage_kind IS NULL)),
141
+ CHECK (
142
+ (lineage_kind = 'restart' AND parent_terminal_fingerprint IS NOT NULL) OR
143
+ (lineage_kind IS NOT 'restart' AND parent_terminal_fingerprint IS NULL)
144
+ ),
145
+ CHECK (parent_run_id IS NOT NULL OR restart_number = 0)
134
146
  ) STRICT;
135
147
 
136
148
  CREATE INDEX runs_project_idx ON runs(project_id, created_at DESC);
137
149
  CREATE INDEX runs_status_idx ON runs(status, updated_at DESC);
138
150
  CREATE INDEX runs_parent_idx ON runs(parent_run_id);
151
+ CREATE UNIQUE INDEX runs_restart_parent_idx ON runs(parent_run_id)
152
+ WHERE lineage_kind = 'restart';
153
+
154
+ CREATE TABLE run_view_content (
155
+ run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
156
+ content_hash BLOB NOT NULL,
157
+ media_type TEXT NOT NULL CHECK (media_type IN ('application/json', 'text/plain')),
158
+ byte_length INTEGER NOT NULL CHECK (byte_length >= 0),
159
+ content BLOB NOT NULL,
160
+ created_at INTEGER NOT NULL,
161
+ PRIMARY KEY (run_id, content_hash, media_type),
162
+ CHECK (byte_length = length(content))
163
+ ) STRICT;
139
164
 
140
165
  CREATE TABLE viewer_runs (
141
166
  run_id TEXT PRIMARY KEY REFERENCES runs(run_id) ON DELETE CASCADE,
@@ -222,33 +247,10 @@ CREATE TABLE workflow_setting_changes (
222
247
  UNIQUE (scope_id, change_number)
223
248
  ) STRICT;
224
249
 
225
- CREATE TABLE workflow_follow_up_queues (
226
- run_id TEXT PRIMARY KEY REFERENCES runs(run_id) ON DELETE CASCADE,
227
- resource_id TEXT NOT NULL UNIQUE REFERENCES resources(resource_id) ON DELETE CASCADE,
228
- origin_session_id TEXT,
229
- presentation_state TEXT NOT NULL DEFAULT 'none' CHECK (presentation_state IN (
230
- 'none', 'not-needed', 'pending', 'settled', 'unavailable'
231
- )),
232
- presentation_entry_id TEXT,
233
- presentation_assistant_entry_id TEXT,
234
- presentation_reason_hash BLOB REFERENCES blobs(blob_hash),
235
- presentation_updated_at INTEGER,
236
- created_at INTEGER NOT NULL,
237
- updated_at INTEGER NOT NULL,
238
- CHECK (
239
- (presentation_state IN ('none', 'not-needed', 'pending') AND presentation_assistant_entry_id IS NULL AND presentation_reason_hash IS NULL)
240
- OR
241
- (presentation_state = 'unavailable' AND presentation_assistant_entry_id IS NULL AND presentation_reason_hash IS NOT NULL)
242
- OR
243
- (presentation_state = 'settled' AND presentation_entry_id IS NOT NULL AND presentation_assistant_entry_id IS NOT NULL AND presentation_reason_hash IS NULL)
244
- )
245
- ) STRICT;
246
-
247
250
  CREATE TABLE workflow_follow_ups (
248
251
  follow_up_id TEXT PRIMARY KEY,
249
252
  resource_id TEXT NOT NULL UNIQUE REFERENCES resources(resource_id) ON DELETE CASCADE,
250
- queue_resource_id TEXT NOT NULL REFERENCES resources(resource_id),
251
- run_id TEXT NOT NULL REFERENCES workflow_follow_up_queues(run_id) ON DELETE CASCADE,
253
+ run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
252
254
  request_id TEXT NOT NULL,
253
255
  order_number INTEGER NOT NULL CHECK (order_number > 0),
254
256
  target_session_id TEXT NOT NULL,
@@ -258,21 +260,17 @@ CREATE TABLE workflow_follow_ups (
258
260
  actor_id TEXT,
259
261
  source_type TEXT NOT NULL,
260
262
  prompt_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
261
- status TEXT NOT NULL CHECK (status IN (
262
- 'queued', 'pending_presentation', 'ready', 'sent', 'removed', 'cancelled'
263
- )),
264
- session_entry_id TEXT,
263
+ status TEXT NOT NULL CHECK (status IN ('queued', 'removed', 'cancelled')),
265
264
  reason_hash BLOB REFERENCES blobs(blob_hash),
266
265
  created_at INTEGER NOT NULL,
267
266
  updated_at INTEGER NOT NULL,
268
- sent_at INTEGER,
269
267
  UNIQUE (run_id, request_id),
270
268
  UNIQUE (run_id, order_number),
271
- CHECK ((status = 'sent') = (session_entry_id IS NOT NULL AND sent_at IS NOT NULL)),
272
269
  CHECK ((status IN ('removed', 'cancelled')) = (reason_hash IS NOT NULL))
273
270
  ) STRICT;
274
271
 
275
- CREATE INDEX workflow_follow_ups_delivery_idx ON workflow_follow_ups(target_session_id, status, order_number);
272
+ CREATE INDEX workflow_follow_ups_order_idx
273
+ ON workflow_follow_ups(target_session_id, run_id, order_number);
276
274
 
277
275
  CREATE TABLE run_queue (
278
276
  run_id TEXT PRIMARY KEY REFERENCES runs(run_id) ON DELETE CASCADE,
@@ -301,9 +299,11 @@ CREATE TABLE host_commands (
301
299
  operation TEXT NOT NULL CHECK (operation IN (
302
300
  'run.start', 'run.pause', 'run.resume', 'run.cancel', 'run.status', 'run.list',
303
301
  'checkpoint.answer', 'decision.answer', 'interaction.submit', 'interaction.update',
304
- 'notification.claim', 'notification.deliver', 'turn.claim', 'turn.resolve',
302
+ 'workflowMessage.reportBranch', 'workflowTurn.report', 'sessionView.clearTerminal',
303
+ 'run.restart', 'run.changeSettings', 'followUp.queue', 'followUp.remove', 'session.record',
304
+ 'channel.reload', 'channel.recover',
305
305
  'controller.list', 'controller.get', 'controller.apply', 'controller.reconcile', 'controller.delete',
306
- 'host.status', 'host.stop'
306
+ 'host.status', 'host.stop', 'state.backup', 'state.prune'
307
307
  )),
308
308
  idempotency_key TEXT NOT NULL,
309
309
  request_fingerprint BLOB NOT NULL CHECK (length(request_fingerprint) = 32),
@@ -507,24 +507,70 @@ CREATE TABLE interactive_requests (
507
507
  kind TEXT NOT NULL CHECK (kind IN ('agent', 'assistant', 'decision')),
508
508
  contract_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
509
509
  revision INTEGER NOT NULL DEFAULT 1 CHECK (revision > 0),
510
- status TEXT NOT NULL CHECK (status IN ('pending', 'presenting', 'settled', 'cancelled')),
511
- presenter_id TEXT,
512
- presentation_claim_expires_at INTEGER,
513
- presentation_session_entry_id TEXT,
510
+ status TEXT NOT NULL CHECK (status IN ('pending', 'settled', 'cancelled')),
511
+ unproductive_turn_ends INTEGER NOT NULL DEFAULT 0 CHECK (unproductive_turn_ends >= 0),
514
512
  accepted_submission_id TEXT,
515
513
  created_at INTEGER NOT NULL,
516
514
  updated_at INTEGER NOT NULL,
517
515
  settled_at INTEGER,
518
516
  consumed_at INTEGER,
519
517
  CHECK ((status = 'settled') = (accepted_submission_id IS NOT NULL AND settled_at IS NOT NULL)),
520
- CHECK ((presenter_id IS NULL) = (presentation_claim_expires_at IS NULL)),
521
- CHECK (presenter_id IS NULL OR status = 'presenting'),
522
518
  CHECK (consumed_at IS NULL OR status = 'settled')
523
519
  ) STRICT;
524
520
 
525
521
  CREATE INDEX interactive_requests_session_idx
526
522
  ON interactive_requests(target_session_id, status, created_at);
527
523
 
524
+ CREATE TABLE workflow_messages (
525
+ workflow_message_id TEXT PRIMARY KEY,
526
+ run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
527
+ target_session_id TEXT NOT NULL,
528
+ kind TEXT NOT NULL CHECK (kind IN ('step', 'decision', 'notification', 'terminal', 'followUp')),
529
+ source_id TEXT NOT NULL,
530
+ content_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
531
+ order_number INTEGER NOT NULL CHECK (order_number > 0),
532
+ status TEXT NOT NULL CHECK (status IN ('pending', 'sent', 'cancelled')),
533
+ pi_session_entry_id TEXT,
534
+ created_at INTEGER NOT NULL,
535
+ updated_at INTEGER NOT NULL,
536
+ UNIQUE (target_session_id, order_number),
537
+ CHECK ((status = 'sent') = (pi_session_entry_id IS NOT NULL))
538
+ ) STRICT;
539
+
540
+ CREATE INDEX workflow_messages_session_idx
541
+ ON workflow_messages(target_session_id, status, order_number);
542
+ CREATE INDEX workflow_messages_run_idx ON workflow_messages(run_id, order_number);
543
+ CREATE INDEX workflow_messages_source_idx ON workflow_messages(kind, source_id, order_number);
544
+ CREATE UNIQUE INDEX workflow_messages_pending_step_source_idx
545
+ ON workflow_messages(source_id) WHERE kind = 'step' AND status = 'pending';
546
+
547
+ CREATE TABLE workflow_turns (
548
+ workflow_turn_id TEXT PRIMARY KEY,
549
+ workflow_message_id TEXT NOT NULL REFERENCES workflow_messages(workflow_message_id) ON DELETE CASCADE,
550
+ run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
551
+ target_session_id TEXT NOT NULL,
552
+ state TEXT NOT NULL CHECK (state IN ('started', 'ended')),
553
+ stop_reason TEXT CHECK (stop_reason IS NULL OR stop_reason IN ('completed', 'aborted', 'error', 'lost')),
554
+ response_session_entry_id TEXT,
555
+ started_at INTEGER NOT NULL,
556
+ ended_at INTEGER,
557
+ CHECK (
558
+ (state = 'started' AND stop_reason IS NULL AND ended_at IS NULL)
559
+ OR
560
+ (state = 'ended' AND stop_reason IS NOT NULL AND ended_at IS NOT NULL)
561
+ )
562
+ ) STRICT;
563
+
564
+ CREATE UNIQUE INDEX workflow_turns_open_message_idx
565
+ ON workflow_turns(workflow_message_id) WHERE state = 'started';
566
+ CREATE INDEX workflow_turns_session_idx ON workflow_turns(target_session_id, state, started_at);
567
+
568
+ CREATE TABLE session_terminal_views (
569
+ target_session_id TEXT PRIMARY KEY,
570
+ cleared_run_id TEXT REFERENCES runs(run_id) ON DELETE SET NULL,
571
+ cleared_at INTEGER NOT NULL
572
+ ) STRICT;
573
+
528
574
  CREATE TABLE interactive_submissions (
529
575
  submission_id TEXT PRIMARY KEY,
530
576
  request_id TEXT NOT NULL REFERENCES interactive_requests(request_id) ON DELETE CASCADE,
@@ -670,39 +716,6 @@ CREATE TABLE effect_attempts (
670
716
  PRIMARY KEY (effect_id, attempt_number)
671
717
  ) STRICT;
672
718
 
673
- CREATE TABLE notifications (
674
- notification_id TEXT PRIMARY KEY,
675
- effect_id TEXT NOT NULL UNIQUE REFERENCES effects(effect_id),
676
- run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
677
- attempt_id TEXT NOT NULL REFERENCES node_attempts(attempt_id),
678
- notification_index INTEGER NOT NULL CHECK (notification_index >= 0),
679
- target_session_id TEXT NOT NULL,
680
- notification_type TEXT NOT NULL CHECK (notification_type IN ('progress', 'final')),
681
- content_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
682
- created_at INTEGER NOT NULL,
683
- UNIQUE (run_id, attempt_id, notification_index)
684
- ) STRICT;
685
-
686
- CREATE TABLE turn_intents (
687
- turn_intent_id TEXT PRIMARY KEY,
688
- resource_id TEXT NOT NULL UNIQUE REFERENCES resources(resource_id),
689
- effect_id TEXT NOT NULL UNIQUE REFERENCES effects(effect_id),
690
- source_event_id TEXT NOT NULL UNIQUE,
691
- run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
692
- workflow_ref TEXT NOT NULL,
693
- target_session_id TEXT NOT NULL,
694
- cause TEXT NOT NULL,
695
- node_id TEXT,
696
- attempt_id TEXT,
697
- resolution_type TEXT,
698
- resolution_message_id TEXT,
699
- facts_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
700
- requested_at INTEGER NOT NULL,
701
- eligible_at INTEGER,
702
- resolved_at INTEGER,
703
- created_at INTEGER NOT NULL
704
- ) STRICT;
705
-
706
719
  CREATE TABLE channels (
707
720
  channel_id TEXT PRIMARY KEY,
708
721
  resource_id TEXT NOT NULL UNIQUE REFERENCES resources(resource_id),
@@ -720,41 +733,18 @@ CREATE TABLE channel_cursors (
720
733
  PRIMARY KEY (channel_id, cursor_key)
721
734
  ) STRICT;
722
735
 
723
- CREATE TABLE channel_inbox (
724
- channel_id TEXT NOT NULL REFERENCES channels(channel_id) ON DELETE CASCADE,
725
- external_event_id TEXT NOT NULL,
726
- event_type TEXT NOT NULL CHECK (event_type IN ('callback', 'reply')),
727
- payload_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
728
- received_at INTEGER NOT NULL,
729
- processed_at INTEGER,
730
- result_hash BLOB REFERENCES blobs(blob_hash),
731
- PRIMARY KEY (channel_id, external_event_id)
732
- ) STRICT;
733
-
734
736
  CREATE TABLE channel_messages (
735
737
  message_id TEXT PRIMARY KEY,
736
738
  channel_id TEXT NOT NULL REFERENCES channels(channel_id) ON DELETE CASCADE,
737
739
  decision_id TEXT REFERENCES human_decisions(decision_id),
738
740
  purpose TEXT NOT NULL CHECK (purpose IN ('delivery', 'settlement')),
739
741
  content_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
740
- external_conversation_ref TEXT,
741
- external_message_ref TEXT,
742
742
  status TEXT NOT NULL CHECK (status IN ('pending', 'confirmed', 'failed', 'ambiguous')),
743
743
  created_at INTEGER NOT NULL,
744
744
  updated_at INTEGER NOT NULL
745
745
  ) STRICT;
746
746
 
747
747
  CREATE INDEX channel_messages_decision_idx ON channel_messages(decision_id, purpose, created_at);
748
-
749
- CREATE TABLE channel_message_parts (
750
- message_id TEXT NOT NULL REFERENCES channel_messages(message_id) ON DELETE CASCADE,
751
- recipient_index INTEGER NOT NULL CHECK (recipient_index >= 0),
752
- part_index INTEGER NOT NULL CHECK (part_index >= 0),
753
- content_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
754
- external_conversation_ref TEXT NOT NULL,
755
- external_message_ref TEXT NOT NULL,
756
- PRIMARY KEY (message_id, recipient_index, part_index)
757
- ) STRICT;
758
748
  `;
759
749
 
760
750
  export const STATE_SCHEMA_DIGEST = createHash("sha256").update(STATE_SCHEMA_SQL).digest();