@osolmaz/pi-workflows 0.14.0 → 0.15.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 (235) hide show
  1. package/README.md +78 -110
  2. package/dist/builtins/autoimplement.workflow.js +3 -1
  3. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  4. package/dist/builtins/change-verification.workflow.js +7 -2
  5. package/dist/builtins/change-verification.workflow.js.map +1 -1
  6. package/dist/builtins/metadata.d.ts +22 -0
  7. package/dist/builtins/metadata.js +10 -0
  8. package/dist/builtins/metadata.js.map +1 -0
  9. package/dist/builtins/monitor.workflow.js +4 -1
  10. package/dist/builtins/monitor.workflow.js.map +1 -1
  11. package/dist/builtins/sanity-check.workflow.js +4 -1
  12. package/dist/builtins/sanity-check.workflow.js.map +1 -1
  13. package/dist/builtins/workspace-preparation.workflow.js +3 -1
  14. package/dist/builtins/workspace-preparation.workflow.js.map +1 -1
  15. package/dist/controllers/index.d.ts +0 -1
  16. package/dist/controllers/index.js +0 -1
  17. package/dist/controllers/index.js.map +1 -1
  18. package/dist/controllers/sqlite.d.ts +51 -0
  19. package/dist/controllers/sqlite.js +433 -57
  20. package/dist/controllers/sqlite.js.map +1 -1
  21. package/dist/controllers/store.d.ts +1 -0
  22. package/dist/extension/controller-command.d.ts +22 -0
  23. package/dist/extension/controller-command.js +32 -0
  24. package/dist/extension/controller-command.js.map +1 -0
  25. package/dist/extension/index.d.ts +2 -9
  26. package/dist/extension/index.js +876 -3447
  27. package/dist/extension/index.js.map +1 -1
  28. package/dist/extension/message-card.d.ts +13 -0
  29. package/dist/extension/message-card.js +46 -0
  30. package/dist/extension/message-card.js.map +1 -0
  31. package/dist/extension/step-message.d.ts +5 -6
  32. package/dist/extension/step-message.js +41 -52
  33. package/dist/extension/step-message.js.map +1 -1
  34. package/dist/host/child-worker-supervisor.d.ts +50 -0
  35. package/dist/host/child-worker-supervisor.js +159 -0
  36. package/dist/host/child-worker-supervisor.js.map +1 -0
  37. package/dist/host/client.d.ts +48 -0
  38. package/dist/host/client.js +216 -0
  39. package/dist/host/client.js.map +1 -0
  40. package/dist/host/controller-worker-entry.d.ts +2 -0
  41. package/dist/host/controller-worker-entry.js +244 -0
  42. package/dist/host/controller-worker-entry.js.map +1 -0
  43. package/dist/host/controller-worker-protocol.d.ts +36 -0
  44. package/dist/host/controller-worker-protocol.js +49 -0
  45. package/dist/host/controller-worker-protocol.js.map +1 -0
  46. package/dist/host/controller-worker-supervisor.d.ts +21 -0
  47. package/dist/host/controller-worker-supervisor.js +54 -0
  48. package/dist/host/controller-worker-supervisor.js.map +1 -0
  49. package/dist/host/host-entry.d.ts +2 -0
  50. package/dist/host/host-entry.js +23 -0
  51. package/dist/host/host-entry.js.map +1 -0
  52. package/dist/host/processes.d.ts +17 -12
  53. package/dist/host/processes.js +154 -50
  54. package/dist/host/processes.js.map +1 -1
  55. package/dist/host/protocol.d.ts +38 -0
  56. package/dist/host/protocol.js +156 -0
  57. package/dist/host/protocol.js.map +1 -0
  58. package/dist/host/resolver-entry.d.ts +49 -0
  59. package/dist/host/resolver-entry.js +149 -0
  60. package/dist/host/resolver-entry.js.map +1 -0
  61. package/dist/host/rpc-executor.d.ts +14 -3
  62. package/dist/host/rpc-executor.js +63 -30
  63. package/dist/host/rpc-executor.js.map +1 -1
  64. package/dist/host/runner.d.ts +95 -29
  65. package/dist/host/runner.js +2311 -348
  66. package/dist/host/runner.js.map +1 -1
  67. package/dist/host/state.d.ts +174 -0
  68. package/dist/host/state.js +689 -0
  69. package/dist/host/state.js.map +1 -0
  70. package/dist/host/worker-entry.d.ts +10 -0
  71. package/dist/host/worker-entry.js +446 -0
  72. package/dist/host/worker-entry.js.map +1 -0
  73. package/dist/host/worker-protocol.d.ts +31 -0
  74. package/dist/host/worker-protocol.js +122 -0
  75. package/dist/host/worker-protocol.js.map +1 -0
  76. package/dist/host/worker-store.d.ts +80 -0
  77. package/dist/host/worker-store.js +91 -0
  78. package/dist/host/worker-store.js.map +1 -0
  79. package/dist/host/worker-supervisor.d.ts +22 -0
  80. package/dist/host/worker-supervisor.js +54 -0
  81. package/dist/host/worker-supervisor.js.map +1 -0
  82. package/dist/render/canvas.d.ts +2 -1
  83. package/dist/render/canvas.js +40 -15
  84. package/dist/render/canvas.js.map +1 -1
  85. package/dist/render/graph-render.d.ts +7 -3
  86. package/dist/render/graph-render.js +125 -74
  87. package/dist/render/graph-render.js.map +1 -1
  88. package/dist/state/database.js +3 -1
  89. package/dist/state/database.js.map +1 -1
  90. package/dist/state/index.d.ts +1 -0
  91. package/dist/state/index.js +1 -0
  92. package/dist/state/index.js.map +1 -1
  93. package/dist/state/schema.d.ts +1 -1
  94. package/dist/state/schema.js +171 -3
  95. package/dist/state/schema.js.map +1 -1
  96. package/dist/state/viewer.d.ts +46 -0
  97. package/dist/state/viewer.js +249 -0
  98. package/dist/state/viewer.js.map +1 -0
  99. package/dist/viewer/cli.d.ts +1 -1
  100. package/dist/viewer/cli.js +47 -18
  101. package/dist/viewer/cli.js.map +1 -1
  102. package/dist/workflows/composition.js +25 -2
  103. package/dist/workflows/composition.js.map +1 -1
  104. package/dist/workflows/definition.d.ts +3 -1
  105. package/dist/workflows/definition.js +25 -0
  106. package/dist/workflows/definition.js.map +1 -1
  107. package/dist/workflows/engine.d.ts +1 -0
  108. package/dist/workflows/engine.js +157 -42
  109. package/dist/workflows/engine.js.map +1 -1
  110. package/dist/workflows/errors.d.ts +3 -1
  111. package/dist/workflows/errors.js +4 -7
  112. package/dist/workflows/errors.js.map +1 -1
  113. package/dist/workflows/human-decision.d.ts +3 -0
  114. package/dist/workflows/human-decision.js +31 -0
  115. package/dist/workflows/human-decision.js.map +1 -1
  116. package/dist/workflows/index.d.ts +1 -1
  117. package/dist/workflows/index.js +1 -1
  118. package/dist/workflows/index.js.map +1 -1
  119. package/dist/workflows/schema.js +17 -6
  120. package/dist/workflows/schema.js.map +1 -1
  121. package/dist/{viewer → workflows}/session-reducer.d.ts +3 -1
  122. package/dist/{viewer → workflows}/session-reducer.js +4 -0
  123. package/dist/workflows/session-reducer.js.map +1 -0
  124. package/dist/workflows/store.d.ts +87 -1
  125. package/dist/workflows/store.js +892 -93
  126. package/dist/workflows/store.js.map +1 -1
  127. package/dist/workflows/tool-input.d.ts +0 -26
  128. package/dist/workflows/tool-input.js +2 -50
  129. package/dist/workflows/tool-input.js.map +1 -1
  130. package/dist/workflows/types.d.ts +36 -4
  131. package/docs/2026-08-30-out-of-process-workflow-host-plan.md +353 -0
  132. package/docs/CONTROLLERS.md +11 -11
  133. package/docs/DEFERRED_TURNS.md +23 -2
  134. package/docs/SQLITE_STATE.md +41 -31
  135. package/docs/WORKFLOW_HOST.md +452 -0
  136. package/docs/development.md +46 -30
  137. package/docs/live-replay-protocol.md +129 -100
  138. package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +42 -0
  139. package/docs/plans/2026-08-28-piw-incremental-viewer-plan.md +293 -0
  140. package/docs/plans/piw-viewer-experience-implementation-plan.md +11 -1
  141. package/docs/plans/tui-viewer-implementation-plan.md +5 -0
  142. package/docs/tui-viewer.md +18 -3
  143. package/docs/workflows.md +162 -210
  144. package/examples/workflows/command-batch.workflow.ts +2 -0
  145. package/examples/workflows/shell.workflow.ts +2 -1
  146. package/herdr-plugin.toml +1 -1
  147. package/package.json +1 -1
  148. package/src/builtins/autoimplement.workflow.ts +3 -0
  149. package/src/builtins/change-verification.workflow.ts +7 -2
  150. package/src/builtins/metadata.ts +9 -0
  151. package/src/builtins/monitor.workflow.ts +4 -0
  152. package/src/builtins/sanity-check.workflow.ts +4 -0
  153. package/src/builtins/workspace-preparation.workflow.ts +3 -1
  154. package/src/controllers/index.ts +0 -5
  155. package/src/controllers/sqlite.ts +709 -76
  156. package/src/controllers/store.ts +1 -0
  157. package/src/extension/controller-command.ts +45 -0
  158. package/src/extension/index.ts +944 -4163
  159. package/src/extension/message-card.ts +61 -0
  160. package/src/extension/step-message.ts +58 -63
  161. package/src/host/child-worker-supervisor.ts +183 -0
  162. package/src/host/client.ts +293 -0
  163. package/src/host/controller-worker-entry.ts +311 -0
  164. package/src/host/controller-worker-protocol.ts +104 -0
  165. package/src/host/controller-worker-supervisor.ts +79 -0
  166. package/src/host/host-entry.ts +23 -0
  167. package/src/host/processes.ts +171 -54
  168. package/src/host/protocol.ts +196 -0
  169. package/src/host/resolver-entry.ts +241 -0
  170. package/src/host/rpc-executor.ts +76 -34
  171. package/src/host/runner.ts +2813 -422
  172. package/src/host/state.ts +1160 -0
  173. package/src/host/worker-entry.ts +533 -0
  174. package/src/host/worker-protocol.ts +165 -0
  175. package/src/host/worker-store.ts +229 -0
  176. package/src/host/worker-supervisor.ts +74 -0
  177. package/src/render/canvas.ts +44 -10
  178. package/src/render/graph-render.ts +145 -90
  179. package/src/state/database.ts +2 -1
  180. package/src/state/index.ts +14 -0
  181. package/src/state/schema.ts +171 -3
  182. package/src/state/viewer.ts +356 -0
  183. package/src/viewer/cli.ts +49 -17
  184. package/src/workflows/composition.ts +36 -2
  185. package/src/workflows/definition.ts +32 -0
  186. package/src/workflows/engine.ts +157 -54
  187. package/src/workflows/errors.ts +11 -2
  188. package/src/workflows/human-decision.ts +49 -0
  189. package/src/workflows/index.ts +2 -0
  190. package/src/workflows/schema.ts +19 -6
  191. package/src/{viewer → workflows}/session-reducer.ts +11 -1
  192. package/src/workflows/store.ts +1316 -108
  193. package/src/workflows/tool-input.ts +3 -69
  194. package/src/workflows/types.ts +32 -4
  195. package/dist/controllers/workflow-engine-scheduler.d.ts +0 -29
  196. package/dist/controllers/workflow-engine-scheduler.js +0 -175
  197. package/dist/controllers/workflow-engine-scheduler.js.map +0 -1
  198. package/dist/extension/controller-host.d.ts +0 -48
  199. package/dist/extension/controller-host.js +0 -110
  200. package/dist/extension/controller-host.js.map +0 -1
  201. package/dist/extension/deferred-turn-coordinator.d.ts +0 -32
  202. package/dist/extension/deferred-turn-coordinator.js +0 -143
  203. package/dist/extension/deferred-turn-coordinator.js.map +0 -1
  204. package/dist/extension/deferred-turn.d.ts +0 -44
  205. package/dist/extension/deferred-turn.js +0 -110
  206. package/dist/extension/deferred-turn.js.map +0 -1
  207. package/dist/extension/executor.d.ts +0 -86
  208. package/dist/extension/executor.js +0 -311
  209. package/dist/extension/executor.js.map +0 -1
  210. package/dist/extension/follow-up-coordinator.d.ts +0 -27
  211. package/dist/extension/follow-up-coordinator.js +0 -131
  212. package/dist/extension/follow-up-coordinator.js.map +0 -1
  213. package/dist/extension/recorder.d.ts +0 -84
  214. package/dist/extension/recorder.js +0 -528
  215. package/dist/extension/recorder.js.map +0 -1
  216. package/dist/extension/restart-policy.d.ts +0 -38
  217. package/dist/extension/restart-policy.js +0 -116
  218. package/dist/extension/restart-policy.js.map +0 -1
  219. package/dist/extension/session-events.d.ts +0 -133
  220. package/dist/extension/session-events.js +0 -61
  221. package/dist/extension/session-events.js.map +0 -1
  222. package/dist/extension/terminal-decision.d.ts +0 -51
  223. package/dist/extension/terminal-decision.js +0 -110
  224. package/dist/extension/terminal-decision.js.map +0 -1
  225. package/dist/viewer/session-reducer.js.map +0 -1
  226. package/src/controllers/workflow-engine-scheduler.ts +0 -246
  227. package/src/extension/controller-host.ts +0 -167
  228. package/src/extension/deferred-turn-coordinator.ts +0 -171
  229. package/src/extension/deferred-turn.ts +0 -166
  230. package/src/extension/executor.ts +0 -411
  231. package/src/extension/follow-up-coordinator.ts +0 -151
  232. package/src/extension/recorder.ts +0 -655
  233. package/src/extension/restart-policy.ts +0 -163
  234. package/src/extension/session-events.ts +0 -121
  235. package/src/extension/terminal-decision.ts +0 -172
@@ -0,0 +1,689 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { StateDatabase } from "../state/database.js";
3
+ import { canonicalJson } from "../state/json.js";
4
+ import { tokenHash } from "../state/mutation.js";
5
+ import { requestFingerprint } from "./protocol.js";
6
+ export class HostStateStore {
7
+ state;
8
+ ownsState;
9
+ constructor(databasePath, options = {}) {
10
+ this.ownsState = options.state === undefined;
11
+ this.state =
12
+ options.state ??
13
+ new StateDatabase({
14
+ filePath: databasePath,
15
+ mode: options.readOnly === true ? "read-only" : "read-write",
16
+ });
17
+ }
18
+ close() {
19
+ if (this.ownsState)
20
+ this.state.close();
21
+ }
22
+ acquireHost(options) {
23
+ const now = options.now ?? Date.now();
24
+ requireLeaseMs(options.leaseMs);
25
+ return this.state.transaction(() => {
26
+ const current = this.hostRow();
27
+ if (current.hostId !== null && current.expiresAt !== null && current.expiresAt > now) {
28
+ throw new Error(`A live Pi Workflows host already owns epoch ${current.epoch}`);
29
+ }
30
+ const token = randomBytes(32).toString("base64url");
31
+ const epoch = current.epoch + 1;
32
+ const expiresAt = now + options.leaseMs;
33
+ const changed = this.state.connection
34
+ .prepare(`UPDATE workflow_host_state
35
+ SET epoch = ?, host_id = ?, token_hash = ?, pid = ?, process_start_identity = ?,
36
+ started_at = ?, heartbeat_at = ?, expires_at = ?
37
+ WHERE id = 1 AND epoch = ? AND (host_id IS NULL OR expires_at IS NULL OR expires_at <= ?)`)
38
+ .run(epoch, options.hostId, tokenHash(token), options.pid, options.processStartIdentity, now, now, expiresAt, current.epoch, now);
39
+ if (changed.changes !== 1)
40
+ throw new Error("Pi Workflows host claim changed during startup");
41
+ return {
42
+ hostId: options.hostId,
43
+ token,
44
+ epoch,
45
+ pid: options.pid,
46
+ processStartIdentity: options.processStartIdentity,
47
+ expiresAt,
48
+ };
49
+ });
50
+ }
51
+ renewHost(claim, leaseMs, now = Date.now()) {
52
+ requireLeaseMs(leaseMs);
53
+ return this.state.transaction(() => {
54
+ const expiresAt = now + leaseMs;
55
+ const changed = this.state.connection
56
+ .prepare(`UPDATE workflow_host_state SET heartbeat_at = ?, expires_at = ?
57
+ WHERE id = 1 AND epoch = ? AND host_id = ? AND token_hash = ?
58
+ AND pid = ? AND process_start_identity = ? AND expires_at > ?`)
59
+ .run(now, expiresAt, claim.epoch, claim.hostId, tokenHash(claim.token), claim.pid, claim.processStartIdentity, now);
60
+ if (changed.changes !== 1)
61
+ throw new Error("Pi Workflows host claim lost");
62
+ return { ...claim, expiresAt };
63
+ });
64
+ }
65
+ releaseHost(claim, _now = Date.now()) {
66
+ return this.state.transaction(() => this.state.connection
67
+ .prepare(`UPDATE workflow_host_state
68
+ SET host_id = NULL, token_hash = NULL, pid = NULL, process_start_identity = NULL,
69
+ started_at = NULL, heartbeat_at = NULL, expires_at = NULL
70
+ WHERE id = 1 AND epoch = ? AND host_id = ? AND token_hash = ?
71
+ AND pid = ? AND process_start_identity = ?`)
72
+ .run(claim.epoch, claim.hostId, tokenHash(claim.token), claim.pid, claim.processStartIdentity).changes === 1);
73
+ }
74
+ hostStatus(now = Date.now()) {
75
+ const row = this.hostRow();
76
+ return {
77
+ epoch: row.epoch,
78
+ hostId: row.hostId,
79
+ pid: row.pid,
80
+ processStartIdentity: row.processStartIdentity,
81
+ startedAt: iso(row.startedAt),
82
+ heartbeatAt: iso(row.heartbeatAt),
83
+ expiresAt: iso(row.expiresAt),
84
+ live: row.hostId !== null && row.expiresAt !== null && row.expiresAt > now,
85
+ };
86
+ }
87
+ readCommand(request) {
88
+ const row = this.state.connection
89
+ .prepare(`SELECT request_fingerprint AS requestFingerprint, outcome, accepted_revision AS revision,
90
+ receipt_hash AS receiptHash, error_hash AS errorHash
91
+ FROM host_commands WHERE request_id = ?`)
92
+ .get(request.requestId);
93
+ if (!isCommandRow(row))
94
+ return undefined;
95
+ if (!row.requestFingerprint.equals(requestFingerprint(request))) {
96
+ return conflictResponse(request.requestId, "Request ID was reused with another payload");
97
+ }
98
+ return this.commandResponse(request.requestId, row);
99
+ }
100
+ executeCommand(request, hostEpoch, operation) {
101
+ const existing = this.readCommand(request);
102
+ if (existing !== undefined) {
103
+ return existing.outcome === "conflict" ? existing : { ...existing, outcome: "adopted" };
104
+ }
105
+ return this.state.transaction(() => {
106
+ const idempotent = this.state.connection
107
+ .prepare(`SELECT request_id AS requestId, request_fingerprint AS requestFingerprint,
108
+ outcome, accepted_revision AS revision, receipt_hash AS receiptHash,
109
+ error_hash AS errorHash
110
+ FROM host_commands WHERE client_id = ? AND idempotency_key = ?`)
111
+ .get(request.clientId, request.idempotencyKey);
112
+ if (isIdempotentCommandRow(idempotent)) {
113
+ if (!idempotent.requestFingerprint.equals(requestFingerprint(request))) {
114
+ return conflictResponse(request.requestId, "Idempotency key was reused with another payload");
115
+ }
116
+ const adopted = this.commandResponse(idempotent.requestId, idempotent);
117
+ return { ...adopted, requestId: request.requestId, outcome: "adopted" };
118
+ }
119
+ const result = operation();
120
+ const response = {
121
+ schema: "pi-workflows.host-response.v1",
122
+ requestId: request.requestId,
123
+ ...result,
124
+ };
125
+ const now = Date.now();
126
+ const receiptHash = response.receipt === undefined ? null : this.state.putJson(response.receipt, now);
127
+ const errorHash = response.error === undefined ? null : this.state.putText(response.error, now);
128
+ this.state.connection
129
+ .prepare(`INSERT INTO host_commands(
130
+ request_id, client_id, operation, idempotency_key, request_fingerprint,
131
+ run_id, accepted_revision, outcome, receipt_hash, error_hash,
132
+ host_epoch, created_at, completed_at
133
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
134
+ .run(request.requestId, request.clientId, request.operation, request.idempotencyKey, requestFingerprint(request), request.runId ?? null, response.revision ?? null, response.outcome, receiptHash, errorHash, hostEpoch, now, now);
135
+ return response;
136
+ });
137
+ }
138
+ recordWorkerStart(envelope, hostEpoch) {
139
+ const now = Date.now();
140
+ this.state.transaction(() => {
141
+ const launchHash = this.state.putJson(envelope, now);
142
+ this.state.connection
143
+ .prepare(`INSERT INTO run_workers(
144
+ worker_epoch, run_id, generation, host_epoch, launch_envelope_hash,
145
+ status, started_at
146
+ ) VALUES (?, ?, ?, ?, ?, 'starting', ?)`)
147
+ .run(envelope.workerEpoch, envelope.runId, envelope.generation, hostEpoch, launchHash, now);
148
+ });
149
+ }
150
+ attachWorkerProcess(workerEpoch, pid, processStartIdentity) {
151
+ const changed = this.state.connection
152
+ .prepare(`UPDATE run_workers SET pid = ?, process_start_identity = ?
153
+ WHERE worker_epoch = ? AND status = 'starting' AND pid IS NULL`)
154
+ .run(pid, processStartIdentity, workerEpoch);
155
+ if (changed.changes !== 1)
156
+ throw new Error(`Worker epoch is not starting: ${workerEpoch}`);
157
+ }
158
+ markWorkerReady(workerEpoch) {
159
+ const now = Date.now();
160
+ const changed = this.state.connection
161
+ .prepare(`UPDATE run_workers SET status = 'running', ready_at = ?
162
+ WHERE worker_epoch = ? AND status = 'starting'`)
163
+ .run(now, workerEpoch);
164
+ if (changed.changes !== 1)
165
+ throw new Error(`Worker epoch cannot become ready: ${workerEpoch}`);
166
+ }
167
+ finishWorker(options) {
168
+ const now = Date.now();
169
+ this.state.transaction(() => {
170
+ const diagnosticHash = options.diagnostic === undefined ? null : this.state.putText(options.diagnostic, now);
171
+ const changed = this.state.connection
172
+ .prepare(`UPDATE run_workers
173
+ SET status = ?, finished_at = ?, exit_code = ?, signal = ?, diagnostic_hash = ?
174
+ WHERE worker_epoch = ? AND status IN ('starting', 'ready', 'running')`)
175
+ .run(options.outcome, now, options.exitCode ?? null, options.signal ?? null, diagnosticHash, options.workerEpoch);
176
+ if (changed.changes !== 1)
177
+ throw new Error(`Worker epoch is not active: ${options.workerEpoch}`);
178
+ });
179
+ }
180
+ readWorkerMessage(message) {
181
+ const row = this.state.connection
182
+ .prepare(`SELECT request_fingerprint AS requestFingerprint, outcome,
183
+ accepted_revision AS revision, result_hash AS resultHash, error_hash AS errorHash
184
+ FROM worker_messages WHERE worker_epoch = ? AND message_id = ?`)
185
+ .get(message.workerEpoch, message.messageId);
186
+ if (!isWorkerMessageRow(row))
187
+ return undefined;
188
+ if (!row.requestFingerprint.equals(workerMessageFingerprint(message))) {
189
+ return {
190
+ schema: "pi-workflows.worker-response.v1",
191
+ messageId: message.messageId,
192
+ outcome: "rejected",
193
+ error: "Worker message ID was reused with another payload",
194
+ };
195
+ }
196
+ return workerMessageResponse(this.state, message.messageId, row);
197
+ }
198
+ recordWorkerMessage(message, response) {
199
+ return this.state.transaction(() => {
200
+ const existing = this.readWorkerMessage(message);
201
+ if (existing !== undefined)
202
+ return existing;
203
+ const now = Date.now();
204
+ const resultHash = response.result === undefined ? null : this.state.putJson(response.result, now);
205
+ const errorHash = response.error === undefined ? null : this.state.putText(response.error, now);
206
+ this.state.connection
207
+ .prepare(`INSERT INTO worker_messages(
208
+ worker_epoch, message_id, request_fingerprint, outcome, accepted_revision,
209
+ result_hash, error_hash, completed_at
210
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
211
+ .run(message.workerEpoch, message.messageId, workerMessageFingerprint(message), response.outcome, response.revision ?? null, resultHash, errorHash, now);
212
+ return response;
213
+ });
214
+ }
215
+ createInteractiveRequest(options) {
216
+ const now = Date.now();
217
+ return this.state.transaction(() => {
218
+ const existing = this.interactiveRequest(options.requestId);
219
+ if (existing !== undefined) {
220
+ if (existing.runId !== options.runId ||
221
+ existing.attemptId !== options.attemptId ||
222
+ canonicalJson(existing.contract) !== canonicalJson(options.contract)) {
223
+ throw new Error(`Interactive request conflicts: ${options.requestId}`);
224
+ }
225
+ if (existing.status === "settled" && existing.consumedAt === null) {
226
+ this.state.connection
227
+ .prepare(`UPDATE interactive_requests
228
+ SET status = 'pending', presenter_id = NULL,
229
+ presentation_claim_expires_at = NULL, presentation_session_entry_id = NULL,
230
+ accepted_submission_id = NULL, settled_at = NULL, consumed_at = NULL,
231
+ revision = revision + 1, updated_at = ?
232
+ WHERE request_id = ? AND status = 'settled' AND consumed_at IS NULL`)
233
+ .run(now, options.requestId);
234
+ return this.requireInteractiveRequest(options.requestId);
235
+ }
236
+ return existing;
237
+ }
238
+ const contractHash = this.state.putJson(options.contract, now);
239
+ this.state.connection
240
+ .prepare(`INSERT INTO interactive_requests(
241
+ request_id, run_id, attempt_id, target_session_id, kind, contract_hash,
242
+ revision, status, created_at, updated_at
243
+ ) VALUES (?, ?, ?, ?, ?, ?, 1, 'pending', ?, ?)`)
244
+ .run(options.requestId, options.runId, options.attemptId, options.targetSessionId, options.kind, contractHash, now, now);
245
+ return this.requireInteractiveRequest(options.requestId);
246
+ });
247
+ }
248
+ getInteraction(requestId) {
249
+ return this.interactiveRequest(requestId);
250
+ }
251
+ acceptedInteraction(runId) {
252
+ const row = this.state.connection
253
+ .prepare(`SELECT i.request_id AS requestId, i.attempt_id AS attemptId, a.node_id AS nodeId,
254
+ s.submission_id AS submissionId, s.payload_hash AS payloadHash
255
+ FROM interactive_requests i
256
+ JOIN node_attempts a ON a.attempt_id = i.attempt_id
257
+ JOIN interactive_submissions s ON s.submission_id = i.accepted_submission_id
258
+ WHERE i.run_id = ? AND i.status = 'settled' AND i.consumed_at IS NULL
259
+ ORDER BY i.settled_at DESC LIMIT 1`)
260
+ .get(runId);
261
+ if (!isAcceptedInteractionRow(row))
262
+ return undefined;
263
+ return {
264
+ requestId: row.requestId,
265
+ attemptId: row.attemptId,
266
+ nodeId: row.nodeId,
267
+ submissionId: row.submissionId,
268
+ payload: this.state.readJson(row.payloadHash),
269
+ };
270
+ }
271
+ validatingInteraction(runId) {
272
+ const row = this.state.connection
273
+ .prepare(`SELECT i.request_id AS requestId, i.attempt_id AS attemptId, a.node_id AS nodeId,
274
+ s.submission_id AS submissionId, s.payload_hash AS payloadHash
275
+ FROM interactive_requests i
276
+ JOIN node_attempts a ON a.attempt_id = i.attempt_id
277
+ JOIN interactive_submissions s ON s.request_id = i.request_id
278
+ WHERE i.run_id = ? AND i.status IN ('pending', 'presenting')
279
+ AND s.outcome = 'validating'
280
+ ORDER BY s.submitted_at DESC LIMIT 1`)
281
+ .get(runId);
282
+ if (!isAcceptedInteractionRow(row))
283
+ return undefined;
284
+ return {
285
+ requestId: row.requestId,
286
+ attemptId: row.attemptId,
287
+ nodeId: row.nodeId,
288
+ submissionId: row.submissionId,
289
+ payload: this.state.readJson(row.payloadHash),
290
+ };
291
+ }
292
+ validatingInteractionRunIds() {
293
+ return this.state.connection
294
+ .prepare(`SELECT i.run_id AS runId
295
+ FROM interactive_requests i
296
+ JOIN interactive_submissions s ON s.request_id = i.request_id
297
+ JOIN run_queue q ON q.run_id = i.run_id
298
+ WHERE i.status IN ('pending', 'presenting') AND s.outcome = 'validating'
299
+ AND q.status NOT IN ('done', 'failed', 'cancelled')
300
+ GROUP BY i.run_id
301
+ ORDER BY MIN(s.submitted_at), i.run_id`)
302
+ .all()
303
+ .flatMap((row) => (isRunIdRow(row) ? [row.runId] : []));
304
+ }
305
+ expiredInteractionRunIds(now = Date.now()) {
306
+ return this.state.connection
307
+ .prepare(`SELECT i.run_id AS runId
308
+ FROM interactive_requests i
309
+ JOIN node_attempts a ON a.attempt_id = i.attempt_id
310
+ JOIN run_queue q ON q.run_id = i.run_id
311
+ WHERE i.status IN ('pending', 'presenting')
312
+ AND a.deadline_at IS NOT NULL AND a.deadline_at <= ?
313
+ AND q.status NOT IN ('done', 'failed', 'cancelled')
314
+ GROUP BY i.run_id
315
+ ORDER BY MIN(a.deadline_at), i.run_id`)
316
+ .all(now)
317
+ .flatMap((row) => (isRunIdRow(row) ? [row.runId] : []));
318
+ }
319
+ timedOutInteraction(runId) {
320
+ const row = this.state.connection
321
+ .prepare(`SELECT i.request_id AS requestId, i.attempt_id AS attemptId, a.node_id AS nodeId
322
+ FROM interactive_requests i
323
+ JOIN node_attempts a ON a.attempt_id = i.attempt_id
324
+ JOIN runs r ON r.run_id = i.run_id
325
+ WHERE i.run_id = ? AND i.status = 'cancelled' AND r.status = 'running'
326
+ AND a.deadline_at IS NOT NULL AND a.deadline_at <= ?
327
+ AND a.status IN ('pending', 'running', 'waiting', 'interrupted')
328
+ ORDER BY a.deadline_at, i.request_id LIMIT 1`)
329
+ .get(runId, Date.now());
330
+ return isTimedOutInteractionRow(row) ? row : undefined;
331
+ }
332
+ interactionSubmission(requestId, submissionId) {
333
+ const row = this.state.connection
334
+ .prepare(`SELECT request_id AS requestId, submission_id AS submissionId,
335
+ idempotency_key AS idempotencyKey, outcome, payload_hash AS payloadHash,
336
+ receipt_hash AS receiptHash, submitted_at AS submittedAt
337
+ FROM interactive_submissions WHERE request_id = ? AND submission_id = ?`)
338
+ .get(requestId, submissionId);
339
+ if (!isSubmissionDetailRow(row))
340
+ return undefined;
341
+ return {
342
+ requestId: row.requestId,
343
+ submissionId: row.submissionId,
344
+ idempotencyKey: row.idempotencyKey,
345
+ outcome: row.outcome,
346
+ payload: this.state.readJson(row.payloadHash),
347
+ receipt: row.receiptHash === null ? null : this.state.readJson(row.receiptHash),
348
+ submittedAt: new Date(row.submittedAt).toISOString(),
349
+ };
350
+ }
351
+ finishInteractionValidation(options) {
352
+ const now = Date.now();
353
+ return this.state.transaction(() => {
354
+ const submission = this.interactionSubmission(options.requestId, options.submissionId);
355
+ const expectedOutcome = options.accepted ? "accepted" : "rejected";
356
+ if (submission?.outcome === expectedOutcome)
357
+ return submission;
358
+ if (submission === undefined || submission.outcome !== "validating") {
359
+ throw new Error("Interactive submission is not awaiting validation");
360
+ }
361
+ const request = this.requireInteractiveRequest(options.requestId);
362
+ const receiptHash = this.state.putJson(options.receipt, now);
363
+ this.state.connection
364
+ .prepare(`UPDATE interactive_submissions SET outcome = ?, receipt_hash = ?
365
+ WHERE request_id = ? AND submission_id = ? AND outcome = 'validating'`)
366
+ .run(options.accepted ? "accepted" : "rejected", receiptHash, options.requestId, options.submissionId);
367
+ if (options.accepted) {
368
+ const changed = this.state.connection
369
+ .prepare(`UPDATE interactive_requests
370
+ SET status = 'settled', presenter_id = NULL,
371
+ presentation_claim_expires_at = NULL, accepted_submission_id = ?,
372
+ revision = revision + 1, updated_at = ?, settled_at = ?
373
+ WHERE request_id = ? AND revision = ? AND status IN ('pending', 'presenting')`)
374
+ .run(options.submissionId, now, now, options.requestId, request.revision);
375
+ if (changed.changes !== 1)
376
+ throw new Error("Interactive request validation is stale");
377
+ }
378
+ const settled = this.interactionSubmission(options.requestId, options.submissionId);
379
+ if (settled === undefined)
380
+ throw new Error("Interactive submission result is missing");
381
+ return settled;
382
+ });
383
+ }
384
+ consumeAcceptedInteraction(runId, attemptId, now = Date.now()) {
385
+ const changed = this.state.connection
386
+ .prepare(`UPDATE interactive_requests SET consumed_at = ?, updated_at = ?
387
+ WHERE run_id = ? AND attempt_id = ? AND status = 'settled'
388
+ AND accepted_submission_id IS NOT NULL AND consumed_at IS NULL`)
389
+ .run(now, now, runId, attemptId);
390
+ return changed.changes === 1;
391
+ }
392
+ listPendingInteractions(sessionId) {
393
+ return this.state.connection
394
+ .prepare(`SELECT request_id AS requestId FROM interactive_requests
395
+ WHERE target_session_id = ? AND status IN ('pending', 'presenting')
396
+ ORDER BY created_at, request_id`)
397
+ .all(sessionId)
398
+ .flatMap((row) => isRequestIdRow(row) ? [this.requireInteractiveRequest(row.requestId)] : []);
399
+ }
400
+ claimInteractionPresentation(options) {
401
+ requireLeaseMs(options.leaseMs);
402
+ const now = Date.now();
403
+ const changed = this.state.connection
404
+ .prepare(`UPDATE interactive_requests
405
+ SET status = 'presenting', presenter_id = ?, presentation_claim_expires_at = ?,
406
+ revision = revision + 1, updated_at = ?
407
+ WHERE request_id = ? AND revision = ? AND presentation_session_entry_id IS NULL
408
+ AND (
409
+ status = 'pending'
410
+ OR (status = 'presenting' AND (presenter_id = ? OR presentation_claim_expires_at <= ?))
411
+ )`)
412
+ .run(options.presenterId, now + options.leaseMs, now, options.requestId, options.expectedRevision, options.presenterId, now);
413
+ if (changed.changes !== 1)
414
+ throw new Error("Interactive request presentation claim conflict");
415
+ return this.requireInteractiveRequest(options.requestId);
416
+ }
417
+ markInteractionPresented(options) {
418
+ const now = Date.now();
419
+ const changed = this.state.connection
420
+ .prepare(`UPDATE interactive_requests
421
+ SET status = 'presenting', presenter_id = NULL, presentation_claim_expires_at = NULL,
422
+ presentation_session_entry_id = ?, revision = revision + 1, updated_at = ?
423
+ WHERE request_id = ? AND revision = ? AND status IN ('pending', 'presenting')
424
+ AND (presentation_session_entry_id IS NULL OR presentation_session_entry_id = ?)`)
425
+ .run(options.sessionEntryId, now, options.requestId, options.expectedRevision, options.sessionEntryId);
426
+ if (changed.changes !== 1)
427
+ throw new Error("Interactive request presentation conflict");
428
+ return this.requireInteractiveRequest(options.requestId);
429
+ }
430
+ submitInteraction(options) {
431
+ return this.recordInteractionSubmission({
432
+ ...options,
433
+ outcome: options.accepted ? "accepted" : "rejected",
434
+ settle: options.accepted,
435
+ });
436
+ }
437
+ beginInteractionValidation(options) {
438
+ return this.recordInteractionSubmission({
439
+ ...options,
440
+ outcome: "validating",
441
+ settle: false,
442
+ });
443
+ }
444
+ recordInteractionSubmission(options) {
445
+ const now = Date.now();
446
+ return this.state.transaction(() => {
447
+ const existing = this.state.connection
448
+ .prepare(`SELECT submission_id AS submissionId, payload_hash AS payloadHash,
449
+ outcome, receipt_hash AS receiptHash
450
+ FROM interactive_submissions WHERE request_id = ? AND idempotency_key = ?`)
451
+ .get(options.requestId, options.idempotencyKey);
452
+ const payloadHash = createHash("sha256").update(canonicalJson(options.payload)).digest();
453
+ if (isSubmissionRow(existing)) {
454
+ if (!existing.payloadHash.equals(payloadHash)) {
455
+ throw new Error("Interactive submission idempotency key conflicts");
456
+ }
457
+ return {
458
+ interaction: this.requireInteractiveRequest(options.requestId),
459
+ outcome: "adopted",
460
+ receipt: existing.receiptHash === null
461
+ ? { requestId: options.requestId, submissionId: existing.submissionId }
462
+ : this.state.readJson(existing.receiptHash),
463
+ };
464
+ }
465
+ if (options.outcome === "validating") {
466
+ const active = this.state.connection
467
+ .prepare(`SELECT submission_id AS submissionId FROM interactive_submissions
468
+ WHERE request_id = ? AND outcome = 'validating' LIMIT 1`)
469
+ .get(options.requestId);
470
+ if (isSubmissionIdRow(active)) {
471
+ throw new Error("Interactive submission validation is already active");
472
+ }
473
+ }
474
+ const request = this.requireInteractiveRequest(options.requestId);
475
+ if (request.revision !== options.expectedRevision ||
476
+ !["pending", "presenting"].includes(request.status)) {
477
+ throw new Error("Interactive request revision conflict");
478
+ }
479
+ const savedPayloadHash = this.state.putJson(options.payload, now);
480
+ const receiptHash = options.receipt === undefined ? null : this.state.putJson(options.receipt, now);
481
+ this.state.connection
482
+ .prepare(`INSERT INTO interactive_submissions(
483
+ submission_id, request_id, idempotency_key, request_revision,
484
+ payload_hash, outcome, receipt_hash, submitted_at
485
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
486
+ .run(options.submissionId, options.requestId, options.idempotencyKey, options.expectedRevision, savedPayloadHash, options.outcome, receiptHash, now);
487
+ if (options.settle) {
488
+ this.state.connection
489
+ .prepare(`UPDATE interactive_requests
490
+ SET status = 'settled', presenter_id = NULL, presentation_claim_expires_at = NULL,
491
+ accepted_submission_id = ?, revision = revision + 1,
492
+ updated_at = ?, settled_at = ?
493
+ WHERE request_id = ? AND revision = ? AND status IN ('pending', 'presenting')`)
494
+ .run(options.submissionId, now, now, options.requestId, options.expectedRevision);
495
+ }
496
+ return {
497
+ interaction: this.requireInteractiveRequest(options.requestId),
498
+ outcome: "accepted",
499
+ receipt: options.receipt ?? {
500
+ requestId: options.requestId,
501
+ submissionId: options.submissionId,
502
+ },
503
+ };
504
+ });
505
+ }
506
+ hostRow() {
507
+ const row = this.state.connection
508
+ .prepare(`SELECT epoch, host_id AS hostId, token_hash AS tokenHash, pid,
509
+ process_start_identity AS processStartIdentity, started_at AS startedAt,
510
+ heartbeat_at AS heartbeatAt, expires_at AS expiresAt
511
+ FROM workflow_host_state WHERE id = 1`)
512
+ .get();
513
+ if (!isHostRow(row))
514
+ throw new Error("Pi Workflows host state is missing");
515
+ return row;
516
+ }
517
+ commandResponse(requestId, row) {
518
+ const receipt = row.receiptHash === null ? undefined : this.state.readJson(row.receiptHash);
519
+ const error = row.errorHash === null
520
+ ? undefined
521
+ : this.state.readBlob(row.errorHash)?.content.toString("utf8");
522
+ return {
523
+ schema: "pi-workflows.host-response.v1",
524
+ requestId,
525
+ outcome: row.outcome,
526
+ ...(row.revision === null ? {} : { revision: row.revision }),
527
+ ...(receipt === undefined ? {} : { receipt }),
528
+ ...(error === undefined ? {} : { error }),
529
+ };
530
+ }
531
+ interactiveRequest(requestId) {
532
+ const row = this.state.connection
533
+ .prepare(`SELECT request_id AS requestId, run_id AS runId, attempt_id AS attemptId,
534
+ target_session_id AS targetSessionId, kind, contract_hash AS contractHash,
535
+ revision, status, presentation_session_entry_id AS presentationSessionEntryId,
536
+ accepted_submission_id AS acceptedSubmissionId, created_at AS createdAt,
537
+ updated_at AS updatedAt, settled_at AS settledAt, consumed_at AS consumedAt
538
+ FROM interactive_requests WHERE request_id = ?`)
539
+ .get(requestId);
540
+ if (!isInteractiveRequestRow(row))
541
+ return undefined;
542
+ return {
543
+ requestId: row.requestId,
544
+ runId: row.runId,
545
+ attemptId: row.attemptId,
546
+ targetSessionId: row.targetSessionId,
547
+ kind: row.kind,
548
+ contract: this.state.readJson(row.contractHash),
549
+ revision: row.revision,
550
+ status: row.status,
551
+ presentationSessionEntryId: row.presentationSessionEntryId,
552
+ acceptedSubmissionId: row.acceptedSubmissionId,
553
+ createdAt: new Date(row.createdAt).toISOString(),
554
+ updatedAt: new Date(row.updatedAt).toISOString(),
555
+ settledAt: iso(row.settledAt),
556
+ consumedAt: iso(row.consumedAt),
557
+ };
558
+ }
559
+ requireInteractiveRequest(requestId) {
560
+ const request = this.interactiveRequest(requestId);
561
+ if (request === undefined)
562
+ throw new Error(`Interactive request not found: ${requestId}`);
563
+ return request;
564
+ }
565
+ }
566
+ function conflictResponse(requestId, error) {
567
+ return { schema: "pi-workflows.host-response.v1", requestId, outcome: "conflict", error };
568
+ }
569
+ function requireLeaseMs(value) {
570
+ if (!Number.isSafeInteger(value) || value <= 0)
571
+ throw new Error("Host lease duration must be positive");
572
+ }
573
+ function iso(value) {
574
+ return value === null ? null : new Date(value).toISOString();
575
+ }
576
+ function isHostRow(value) {
577
+ return (isRecord(value) &&
578
+ typeof value.epoch === "number" &&
579
+ nullableString(value.hostId) &&
580
+ (value.tokenHash === null || Buffer.isBuffer(value.tokenHash)) &&
581
+ (value.pid === null || typeof value.pid === "number") &&
582
+ nullableString(value.processStartIdentity) &&
583
+ nullableNumber(value.startedAt) &&
584
+ nullableNumber(value.heartbeatAt) &&
585
+ nullableNumber(value.expiresAt));
586
+ }
587
+ function isCommandRow(value) {
588
+ return (isRecord(value) &&
589
+ Buffer.isBuffer(value.requestFingerprint) &&
590
+ typeof value.outcome === "string" &&
591
+ nullableNumber(value.revision) &&
592
+ (value.receiptHash === null || Buffer.isBuffer(value.receiptHash)) &&
593
+ (value.errorHash === null || Buffer.isBuffer(value.errorHash)));
594
+ }
595
+ function isIdempotentCommandRow(value) {
596
+ return (isRecord(value) &&
597
+ isCommandRow(value) &&
598
+ typeof value.requestId === "string");
599
+ }
600
+ function isRequestIdRow(value) {
601
+ return isRecord(value) && typeof value.requestId === "string";
602
+ }
603
+ function isRunIdRow(value) {
604
+ return isRecord(value) && typeof value.runId === "string";
605
+ }
606
+ function isWorkerMessageRow(value) {
607
+ return (isRecord(value) &&
608
+ Buffer.isBuffer(value.requestFingerprint) &&
609
+ ["accepted", "adopted", "rejected", "claimLost"].includes(value.outcome) &&
610
+ nullableNumber(value.revision) &&
611
+ (value.resultHash === null || Buffer.isBuffer(value.resultHash)) &&
612
+ (value.errorHash === null || Buffer.isBuffer(value.errorHash)));
613
+ }
614
+ function isAcceptedInteractionRow(value) {
615
+ return (isRecord(value) &&
616
+ typeof value.requestId === "string" &&
617
+ typeof value.attemptId === "string" &&
618
+ typeof value.nodeId === "string" &&
619
+ typeof value.submissionId === "string" &&
620
+ Buffer.isBuffer(value.payloadHash));
621
+ }
622
+ function isTimedOutInteractionRow(value) {
623
+ return (isRecord(value) &&
624
+ typeof value.requestId === "string" &&
625
+ typeof value.attemptId === "string" &&
626
+ typeof value.nodeId === "string");
627
+ }
628
+ function isSubmissionRow(value) {
629
+ return (isRecord(value) &&
630
+ typeof value.submissionId === "string" &&
631
+ Buffer.isBuffer(value.payloadHash) &&
632
+ typeof value.outcome === "string" &&
633
+ (value.receiptHash === null || Buffer.isBuffer(value.receiptHash)));
634
+ }
635
+ function isSubmissionIdRow(value) {
636
+ return isRecord(value) && typeof value.submissionId === "string";
637
+ }
638
+ function isSubmissionDetailRow(value) {
639
+ return (isRecord(value) &&
640
+ typeof value.requestId === "string" &&
641
+ typeof value.submissionId === "string" &&
642
+ typeof value.idempotencyKey === "string" &&
643
+ ["validating", "accepted", "rejected", "adopted"].includes(value.outcome) &&
644
+ Buffer.isBuffer(value.payloadHash) &&
645
+ (value.receiptHash === null || Buffer.isBuffer(value.receiptHash)) &&
646
+ typeof value.submittedAt === "number");
647
+ }
648
+ function isInteractiveRequestRow(value) {
649
+ return (isRecord(value) &&
650
+ typeof value.requestId === "string" &&
651
+ typeof value.runId === "string" &&
652
+ typeof value.attemptId === "string" &&
653
+ typeof value.targetSessionId === "string" &&
654
+ ["agent", "assistant", "decision"].includes(value.kind) &&
655
+ Buffer.isBuffer(value.contractHash) &&
656
+ typeof value.revision === "number" &&
657
+ ["pending", "presenting", "settled", "cancelled"].includes(value.status) &&
658
+ nullableString(value.presentationSessionEntryId) &&
659
+ nullableString(value.acceptedSubmissionId) &&
660
+ typeof value.createdAt === "number" &&
661
+ typeof value.updatedAt === "number" &&
662
+ nullableNumber(value.settledAt) &&
663
+ nullableNumber(value.consumedAt));
664
+ }
665
+ function workerMessageFingerprint(message) {
666
+ return createHash("sha256").update(canonicalJson(message)).digest();
667
+ }
668
+ function workerMessageResponse(state, messageId, row) {
669
+ const result = row.resultHash === null ? undefined : state.readJson(row.resultHash);
670
+ const error = row.errorHash === null ? undefined : state.readBlob(row.errorHash)?.content.toString("utf8");
671
+ return {
672
+ schema: "pi-workflows.worker-response.v1",
673
+ messageId,
674
+ outcome: row.outcome,
675
+ ...(row.revision === null ? {} : { revision: row.revision }),
676
+ ...(result === undefined ? {} : { result }),
677
+ ...(error === undefined ? {} : { error }),
678
+ };
679
+ }
680
+ function nullableString(value) {
681
+ return value === null || typeof value === "string";
682
+ }
683
+ function nullableNumber(value) {
684
+ return value === null || typeof value === "number";
685
+ }
686
+ function isRecord(value) {
687
+ return typeof value === "object" && value !== null && !Array.isArray(value);
688
+ }
689
+ //# sourceMappingURL=state.js.map