@elie-laloum/outpost 7.0.0 → 8.0.0

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 (230) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +2 -0
  3. package/dist/adapters/agents/antigravity-adapter.js +3 -0
  4. package/dist/adapters/agents/antigravity-adapter.js.map +1 -1
  5. package/dist/adapters/agents/claude-adapter.js +3 -0
  6. package/dist/adapters/agents/claude-adapter.js.map +1 -1
  7. package/dist/adapters/agents/claude-events.js +25 -1
  8. package/dist/adapters/agents/claude-events.js.map +1 -1
  9. package/dist/adapters/agents/codex-adapter.js +3 -0
  10. package/dist/adapters/agents/codex-adapter.js.map +1 -1
  11. package/dist/adapters/agents/copilot-adapter.js +3 -0
  12. package/dist/adapters/agents/copilot-adapter.js.map +1 -1
  13. package/dist/adapters/agents/copilot-events.js +7 -3
  14. package/dist/adapters/agents/copilot-events.js.map +1 -1
  15. package/dist/adapters/agents/kimi-adapter.js +3 -0
  16. package/dist/adapters/agents/kimi-adapter.js.map +1 -1
  17. package/dist/adapters/agents/quota.constants.d.ts +7 -0
  18. package/dist/adapters/agents/quota.constants.js +31 -0
  19. package/dist/adapters/agents/quota.constants.js.map +1 -0
  20. package/dist/adapters/agents/quota.d.ts +1 -0
  21. package/dist/adapters/agents/quota.js +4 -0
  22. package/dist/adapters/agents/quota.js.map +1 -0
  23. package/dist/adapters/models/http-provider.js +8 -2
  24. package/dist/adapters/models/http-provider.js.map +1 -1
  25. package/dist/adapters/models/model.constants.d.ts +2 -0
  26. package/dist/adapters/models/model.constants.js +6 -0
  27. package/dist/adapters/models/model.constants.js.map +1 -1
  28. package/dist/adapters/models/stream-events.js +11 -7
  29. package/dist/adapters/models/stream-events.js.map +1 -1
  30. package/dist/application/agent-output.js +10 -1
  31. package/dist/application/agent-output.js.map +1 -1
  32. package/dist/application/agent-quota.d.ts +7 -0
  33. package/dist/application/agent-quota.js +33 -0
  34. package/dist/application/agent-quota.js.map +1 -0
  35. package/dist/application/agent-turn.js +25 -9
  36. package/dist/application/agent-turn.js.map +1 -1
  37. package/dist/application/dispatch-observation.js +10 -1
  38. package/dist/application/dispatch-observation.js.map +1 -1
  39. package/dist/application/execution.types.d.ts +2 -0
  40. package/dist/application/execution.types.js.map +1 -1
  41. package/dist/application/interactive-task-execution.d.ts +3 -0
  42. package/dist/application/interactive-task-execution.js +76 -0
  43. package/dist/application/interactive-task-execution.js.map +1 -0
  44. package/dist/application/interactive-task-protocol.d.ts +4 -0
  45. package/dist/application/interactive-task-protocol.js +56 -0
  46. package/dist/application/interactive-task-protocol.js.map +1 -0
  47. package/dist/application/interactive-task.constants.d.ts +4 -0
  48. package/dist/application/interactive-task.constants.js +7 -0
  49. package/dist/application/interactive-task.constants.js.map +1 -0
  50. package/dist/application/interactive-task.d.ts +3 -0
  51. package/dist/application/interactive-task.js +84 -0
  52. package/dist/application/interactive-task.js.map +1 -0
  53. package/dist/application/interactive-task.types.d.ts +44 -0
  54. package/dist/application/interactive-task.types.js +2 -0
  55. package/dist/application/interactive-task.types.js.map +1 -0
  56. package/dist/application/queue-worker.js +19 -1
  57. package/dist/application/queue-worker.js.map +1 -1
  58. package/dist/application/queued-task.js +17 -2
  59. package/dist/application/queued-task.js.map +1 -1
  60. package/dist/application/quota-resume.constants.d.ts +1 -0
  61. package/dist/application/quota-resume.constants.js +2 -0
  62. package/dist/application/quota-resume.constants.js.map +1 -0
  63. package/dist/application/quota-resume.d.ts +9 -0
  64. package/dist/application/quota-resume.js +34 -0
  65. package/dist/application/quota-resume.js.map +1 -0
  66. package/dist/application/quota-resume.types.d.ts +1 -0
  67. package/dist/application/quota-resume.types.js +2 -0
  68. package/dist/application/quota-resume.types.js.map +1 -0
  69. package/dist/application/recovery-retention-policy.js +1 -1
  70. package/dist/application/recovery-retention-policy.js.map +1 -1
  71. package/dist/application/recovery-retention.js +3 -3
  72. package/dist/application/recovery-retention.js.map +1 -1
  73. package/dist/application/recovery-retention.types.d.ts +1 -1
  74. package/dist/application/recovery-retention.types.js.map +1 -1
  75. package/dist/application/remote-workspace.js +2 -1
  76. package/dist/application/remote-workspace.js.map +1 -1
  77. package/dist/application/replay-recording.d.ts +2 -0
  78. package/dist/application/replay-recording.js +22 -0
  79. package/dist/application/replay-recording.js.map +1 -0
  80. package/dist/application/replay-turn.d.ts +4 -0
  81. package/dist/application/replay-turn.js +47 -0
  82. package/dist/application/replay-turn.js.map +1 -0
  83. package/dist/application/replay-workspace.d.ts +4 -0
  84. package/dist/application/replay-workspace.js +71 -0
  85. package/dist/application/replay-workspace.js.map +1 -0
  86. package/dist/application/replay-workspace.types.d.ts +6 -0
  87. package/dist/application/replay-workspace.types.js +2 -0
  88. package/dist/application/replay-workspace.types.js.map +1 -0
  89. package/dist/application/repository-fingerprint.d.ts +1 -0
  90. package/dist/application/repository-fingerprint.js +18 -0
  91. package/dist/application/repository-fingerprint.js.map +1 -0
  92. package/dist/application/sandbox-dispatch.js +2 -0
  93. package/dist/application/sandbox-dispatch.js.map +1 -1
  94. package/dist/application/speculation-checkpoint.js +19 -2
  95. package/dist/application/speculation-checkpoint.js.map +1 -1
  96. package/dist/application/speculation-checkpoint.types.d.ts +1 -1
  97. package/dist/application/speculation-checkpoint.types.js.map +1 -1
  98. package/dist/application/speculation-resume.js +21 -0
  99. package/dist/application/speculation-resume.js.map +1 -1
  100. package/dist/application/speculation-run.js +18 -1
  101. package/dist/application/speculation-run.js.map +1 -1
  102. package/dist/application/speculation.types.d.ts +7 -2
  103. package/dist/application/speculation.types.js.map +1 -1
  104. package/dist/application/tasks.d.ts +2 -2
  105. package/dist/application/tasks.js +11 -4
  106. package/dist/application/tasks.js.map +1 -1
  107. package/dist/application/tasks.types.d.ts +5 -0
  108. package/dist/application/tasks.types.js.map +1 -1
  109. package/dist/application/transport-inspection.constants.js +1 -0
  110. package/dist/application/transport-inspection.constants.js.map +1 -1
  111. package/dist/application/transport-retention.js +33 -1
  112. package/dist/application/transport-retention.js.map +1 -1
  113. package/dist/domain/agent-observation.js +1 -0
  114. package/dist/domain/agent-observation.js.map +1 -1
  115. package/dist/domain/agent.types.d.ts +8 -1
  116. package/dist/domain/agent.types.js.map +1 -1
  117. package/dist/domain/errors.types.d.ts +1 -1
  118. package/dist/domain/errors.types.js.map +1 -1
  119. package/dist/domain/observation.types.d.ts +6 -1
  120. package/dist/domain/observation.types.js.map +1 -1
  121. package/dist/domain/quota.constants.d.ts +1 -0
  122. package/dist/domain/quota.constants.js +2 -0
  123. package/dist/domain/quota.constants.js.map +1 -0
  124. package/dist/domain/quota.d.ts +4 -0
  125. package/dist/domain/quota.js +28 -0
  126. package/dist/domain/quota.js.map +1 -0
  127. package/dist/domain/quota.types.d.ts +7 -0
  128. package/dist/domain/quota.types.js +2 -0
  129. package/dist/domain/quota.types.js.map +1 -0
  130. package/dist/domain/replay.constants.d.ts +11 -0
  131. package/dist/domain/replay.constants.js +42 -0
  132. package/dist/domain/replay.constants.js.map +1 -0
  133. package/dist/domain/replay.d.ts +11 -0
  134. package/dist/domain/replay.js +231 -0
  135. package/dist/domain/replay.js.map +1 -0
  136. package/dist/domain/replay.types.d.ts +84 -0
  137. package/dist/domain/replay.types.js +2 -0
  138. package/dist/domain/replay.types.js.map +1 -0
  139. package/dist/domain/task-queue.js +18 -0
  140. package/dist/domain/task-queue.js.map +1 -1
  141. package/dist/domain/task-queue.types.d.ts +9 -0
  142. package/dist/domain/task-queue.types.js.map +1 -1
  143. package/dist/domain/workflow/checkpoint-validation.js +14 -1
  144. package/dist/domain/workflow/checkpoint-validation.js.map +1 -1
  145. package/dist/domain/workflow/checkpoint.js +13 -2
  146. package/dist/domain/workflow/checkpoint.js.map +1 -1
  147. package/dist/domain/workflow/gate-validation.js +2 -1
  148. package/dist/domain/workflow/gate-validation.js.map +1 -1
  149. package/dist/domain/workflow/input-validation.d.ts +7 -0
  150. package/dist/domain/workflow/input-validation.js +82 -0
  151. package/dist/domain/workflow/input-validation.js.map +1 -0
  152. package/dist/domain/workflow/input.d.ts +10 -0
  153. package/dist/domain/workflow/input.js +93 -0
  154. package/dist/domain/workflow/input.js.map +1 -0
  155. package/dist/domain/workflow/input.types.d.ts +37 -0
  156. package/dist/domain/workflow/input.types.js +2 -0
  157. package/dist/domain/workflow/input.types.js.map +1 -0
  158. package/dist/domain/workflow/loop-runner.d.ts +3 -0
  159. package/dist/domain/workflow/loop-runner.js +91 -0
  160. package/dist/domain/workflow/loop-runner.js.map +1 -0
  161. package/dist/domain/workflow/loop-task.d.ts +10 -0
  162. package/dist/domain/workflow/loop-task.js +39 -0
  163. package/dist/domain/workflow/loop-task.js.map +1 -0
  164. package/dist/domain/workflow/loop-task.types.d.ts +28 -0
  165. package/dist/domain/workflow/loop-task.types.js +2 -0
  166. package/dist/domain/workflow/loop-task.types.js.map +1 -0
  167. package/dist/domain/workflow/loop-validation.d.ts +4 -0
  168. package/dist/domain/workflow/loop-validation.js +62 -0
  169. package/dist/domain/workflow/loop-validation.js.map +1 -0
  170. package/dist/domain/workflow/quota-pause.d.ts +7 -0
  171. package/dist/domain/workflow/quota-pause.js +83 -0
  172. package/dist/domain/workflow/quota-pause.js.map +1 -0
  173. package/dist/domain/workflow/quota-pause.types.d.ts +14 -0
  174. package/dist/domain/workflow/quota-pause.types.js +2 -0
  175. package/dist/domain/workflow/quota-pause.types.js.map +1 -0
  176. package/dist/domain/workflow/quota-validation.d.ts +2 -0
  177. package/dist/domain/workflow/quota-validation.js +30 -0
  178. package/dist/domain/workflow/quota-validation.js.map +1 -0
  179. package/dist/domain/workflow/task-cache-entry.d.ts +3 -0
  180. package/dist/domain/workflow/task-cache-entry.js +57 -0
  181. package/dist/domain/workflow/task-cache-entry.js.map +1 -0
  182. package/dist/domain/workflow/task-cache.constants.d.ts +3 -0
  183. package/dist/domain/workflow/task-cache.constants.js +3 -0
  184. package/dist/domain/workflow/task-cache.constants.js.map +1 -0
  185. package/dist/domain/workflow/task-cache.d.ts +6 -0
  186. package/dist/domain/workflow/task-cache.js +92 -0
  187. package/dist/domain/workflow/task-cache.js.map +1 -0
  188. package/dist/domain/workflow/task-cache.types.d.ts +35 -0
  189. package/dist/domain/workflow/task-cache.types.js +2 -0
  190. package/dist/domain/workflow/task-cache.types.js.map +1 -0
  191. package/dist/domain/workflow/task-runner.js +100 -34
  192. package/dist/domain/workflow/task-runner.js.map +1 -1
  193. package/dist/domain/workflow/task.js +21 -0
  194. package/dist/domain/workflow/task.js.map +1 -1
  195. package/dist/domain/workflow/validation.js +13 -2
  196. package/dist/domain/workflow/validation.js.map +1 -1
  197. package/dist/domain/workflow/workflow-scheduler.js +27 -2
  198. package/dist/domain/workflow/workflow-scheduler.js.map +1 -1
  199. package/dist/domain/workflow/workflow-state.js +20 -3
  200. package/dist/domain/workflow/workflow-state.js.map +1 -1
  201. package/dist/domain/workflow.types.d.ts +26 -3
  202. package/dist/domain/workflow.types.js.map +1 -1
  203. package/dist/index.d.ts +16 -1
  204. package/dist/index.js +6 -0
  205. package/dist/index.js.map +1 -1
  206. package/dist/infrastructure/git/command.d.ts +2 -1
  207. package/dist/infrastructure/git/command.js +2 -2
  208. package/dist/infrastructure/git/command.js.map +1 -1
  209. package/dist/infrastructure/git/replay-commits.constants.d.ts +5 -0
  210. package/dist/infrastructure/git/replay-commits.constants.js +6 -0
  211. package/dist/infrastructure/git/replay-commits.constants.js.map +1 -0
  212. package/dist/infrastructure/git/replay-commits.d.ts +2 -0
  213. package/dist/infrastructure/git/replay-commits.js +92 -0
  214. package/dist/infrastructure/git/replay-commits.js.map +1 -0
  215. package/dist/infrastructure/journal.types.d.ts +1 -0
  216. package/dist/infrastructure/journal.types.js.map +1 -1
  217. package/dist/infrastructure/reporter.js +3 -0
  218. package/dist/infrastructure/reporter.js.map +1 -1
  219. package/dist/infrastructure/storage-inventory.types.d.ts +1 -1
  220. package/dist/infrastructure/storage-inventory.types.js.map +1 -1
  221. package/dist/infrastructure/transport-task-cache.constants.d.ts +2 -0
  222. package/dist/infrastructure/transport-task-cache.constants.js +3 -0
  223. package/dist/infrastructure/transport-task-cache.constants.js.map +1 -0
  224. package/dist/infrastructure/transport-task-cache.d.ts +3 -0
  225. package/dist/infrastructure/transport-task-cache.js +56 -0
  226. package/dist/infrastructure/transport-task-cache.js.map +1 -0
  227. package/dist/infrastructure/transport-task-cache.types.d.ts +4 -0
  228. package/dist/infrastructure/transport-task-cache.types.js +2 -0
  229. package/dist/infrastructure/transport-task-cache.types.js.map +1 -0
  230. package/package.json +1 -1
@@ -2,6 +2,8 @@ import type { Usage } from "./agent.types.ts";
2
2
  import type { WorkflowJson } from "./workflow/checkpoint.types.ts";
3
3
  export interface QueueRequest {
4
4
  readonly id: string;
5
+ /** Stable effect key for handlers when the job id differs, such as after a quota pause. */
6
+ readonly idempotencyKey?: string;
5
7
  readonly handler: string;
6
8
  readonly input: WorkflowJson;
7
9
  readonly deadline?: number;
@@ -10,6 +12,13 @@ export interface QueueResult {
10
12
  readonly value: WorkflowJson;
11
13
  readonly usage?: Usage;
12
14
  readonly error?: string;
15
+ /** Present when the handler failed on a usage or rate limit. */
16
+ readonly quota?: QueueQuota;
17
+ }
18
+ export interface QueueQuota {
19
+ readonly resetAt?: string;
20
+ /** Captured conversation the handler can continue after the pause. */
21
+ readonly conversation?: string;
13
22
  }
14
23
  export interface QueueJob extends QueueRequest {
15
24
  readonly status: "pending" | "active" | "done" | "failed" | "cancelled";
@@ -1 +1 @@
1
- {"version":3,"file":"task-queue.types.js","sourceRoot":"","sources":["../../src/domain/task-queue.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Usage } from \"./agent.types.ts\";\nimport type { WorkflowJson } from \"./workflow/checkpoint.types.ts\";\n\nexport interface QueueRequest {\n readonly id: string;\n readonly handler: string;\n readonly input: WorkflowJson;\n readonly deadline?: number;\n}\nexport interface QueueResult {\n readonly value: WorkflowJson;\n readonly usage?: Usage;\n readonly error?: string;\n}\nexport interface QueueJob extends QueueRequest {\n readonly status: \"pending\" | \"active\" | \"done\" | \"failed\" | \"cancelled\";\n readonly fence: number;\n readonly worker?: string;\n readonly expires?: number;\n readonly result?: QueueResult;\n}\nexport interface QueueClaim {\n readonly worker: string;\n readonly handlers: readonly string[];\n readonly leaseMs: number;\n}\nexport interface QueueLease {\n readonly id: string;\n readonly worker: string;\n readonly fence: number;\n}\nexport interface TaskQueue {\n enqueue(request: QueueRequest): Promise<QueueJob>;\n get(id: string): Promise<QueueJob | undefined>;\n claim(request: QueueClaim): Promise<QueueJob | undefined>;\n renew(lease: QueueLease, leaseMs: number): Promise<QueueJob>;\n complete(lease: QueueLease, result: QueueResult): Promise<QueueJob>;\n cancel(id: string, fence: number): Promise<QueueJob>;\n}\nexport interface DurableTaskQueue extends TaskQueue {\n close(): void;\n}\n"]}
1
+ {"version":3,"file":"task-queue.types.js","sourceRoot":"","sources":["../../src/domain/task-queue.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Usage } from \"./agent.types.ts\";\nimport type { WorkflowJson } from \"./workflow/checkpoint.types.ts\";\n\nexport interface QueueRequest {\n readonly id: string;\n /** Stable effect key for handlers when the job id differs, such as after a quota pause. */\n readonly idempotencyKey?: string;\n readonly handler: string;\n readonly input: WorkflowJson;\n readonly deadline?: number;\n}\nexport interface QueueResult {\n readonly value: WorkflowJson;\n readonly usage?: Usage;\n readonly error?: string;\n /** Present when the handler failed on a usage or rate limit. */\n readonly quota?: QueueQuota;\n}\nexport interface QueueQuota {\n readonly resetAt?: string;\n /** Captured conversation the handler can continue after the pause. */\n readonly conversation?: string;\n}\nexport interface QueueJob extends QueueRequest {\n readonly status: \"pending\" | \"active\" | \"done\" | \"failed\" | \"cancelled\";\n readonly fence: number;\n readonly worker?: string;\n readonly expires?: number;\n readonly result?: QueueResult;\n}\nexport interface QueueClaim {\n readonly worker: string;\n readonly handlers: readonly string[];\n readonly leaseMs: number;\n}\nexport interface QueueLease {\n readonly id: string;\n readonly worker: string;\n readonly fence: number;\n}\nexport interface TaskQueue {\n enqueue(request: QueueRequest): Promise<QueueJob>;\n get(id: string): Promise<QueueJob | undefined>;\n claim(request: QueueClaim): Promise<QueueJob | undefined>;\n renew(lease: QueueLease, leaseMs: number): Promise<QueueJob>;\n complete(lease: QueueLease, result: QueueResult): Promise<QueueJob>;\n cancel(id: string, fence: number): Promise<QueueJob>;\n}\nexport interface DurableTaskQueue extends TaskQueue {\n close(): void;\n}\n"]}
@@ -1,3 +1,6 @@
1
+ import { validateLoopRecord } from "./loop-validation.js";
2
+ import { validateQuotaRecord } from "./quota-validation.js";
3
+ import { validateInteractionRecord } from "./input-validation.js";
1
4
  import { validateUsageReceipts } from "./usage-receipt.js";
2
5
  import { validateGateRecord } from "./gate-validation.js";
3
6
  import { checkpointValue } from "./checkpoint-value.js";
@@ -42,6 +45,7 @@ export function validateCheckpoint(value, identity, tasks) {
42
45
  !keys.delete(record.key) ||
43
46
  !integer(record.attempts) ||
44
47
  ![
48
+ "waiting-input",
45
49
  "waiting",
46
50
  "active",
47
51
  "done",
@@ -55,7 +59,16 @@ export function validateCheckpoint(value, identity, tasks) {
55
59
  for (const field of ["startedAt", "finishedAt", "error"])
56
60
  if (record[field] !== undefined && typeof record[field] !== "string")
57
61
  throw invalid();
62
+ if (record.cacheHit !== undefined &&
63
+ (record.cacheHit !== true ||
64
+ record.status !== "done" ||
65
+ record.attempts !== 0 ||
66
+ record.rounds !== undefined))
67
+ throw invalid();
58
68
  validateUsageReceipts(record.usageReceipts);
69
+ validateLoopRecord(record, tasks.find((item) => item.key === record.key));
70
+ validateQuotaRecord(record, tasks.find((item) => item.key === record.key));
71
+ validateInteractionRecord(record, tasks.find((item) => item.key === record.key), value.executionId);
59
72
  attempts += record.attempts;
60
73
  const output = Object.hasOwn(value.values, record.key)
61
74
  ? value.values[record.key]
@@ -80,7 +93,7 @@ export function validateCheckpoint(value, identity, tasks) {
80
93
  throw invalid();
81
94
  const records = new Map(value.records.map((entry) => [entry.key, entry]));
82
95
  for (const item of tasks)
83
- if (["done", "paused", "rejected"].includes(String(records.get(item.key)?.status)) &&
96
+ if (["done", "paused", "rejected", "waiting-input"].includes(String(records.get(item.key)?.status)) &&
84
97
  item.after.some((dependency) => records.get(dependency.key)?.status !== "done"))
85
98
  throw invalid();
86
99
  }
@@ -1 +1 @@
1
- {"version":3,"file":"checkpoint-validation.js","sourceRoot":"","sources":["../../../src/domain/workflow/checkpoint-validation.ts"],"names":[],"mappings":"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","sourcesContent":["import { validateUsageReceipts } from \"./usage-receipt.ts\";\nimport { validateGateRecord } from \"./gate-validation.ts\";\nimport type { WorkflowCheckpoint } from \"./checkpoint.types.ts\";\nimport type { Task } from \"../workflow.types.ts\";\nimport { checkpointValue } from \"./checkpoint-value.ts\";\n\nfunction object(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\nfunction integer(value: unknown): value is number {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0;\n}\n\nexport function validateCheckpoint(\n value: unknown,\n identity: string,\n tasks: readonly Task[],\n): asserts value is WorkflowCheckpoint {\n const invalid = () =>\n new Error(\"Invalid or incompatible workflow checkpoint\");\n if (\n !object(value) ||\n value.format !== 1 ||\n value.identity !== identity ||\n typeof value.executionId !== \"string\" ||\n !value.executionId ||\n !Array.isArray(value.records) ||\n !object(value.values) ||\n !object(value.usage)\n )\n throw invalid();\n if (!integer(value.usage.attempts) || !object(value.usage.tokens))\n throw invalid();\n for (const dimension of [\"input\", \"cached\", \"output\"])\n if (!integer(value.usage.tokens[dimension])) throw invalid();\n for (const [dimension, count] of Object.entries(value.usage.tokens)) {\n if (dimension === \"complete\") {\n if (typeof count !== \"boolean\") throw invalid();\n continue;\n }\n if (!integer(count)) throw invalid();\n }\n if (value.records.length !== tasks.length) throw invalid();\n const keys = new Set(tasks.map((item) => item.key));\n let attempts = 0;\n for (const record of value.records) {\n if (\n !object(record) ||\n typeof record.key !== \"string\" ||\n !keys.delete(record.key) ||\n !integer(record.attempts) ||\n ![\n \"waiting\",\n \"active\",\n \"done\",\n \"failed\",\n \"skipped\",\n \"cancelled\",\n \"paused\",\n \"rejected\",\n ].includes(String(record.status))\n )\n throw invalid();\n for (const field of [\"startedAt\", \"finishedAt\", \"error\"])\n if (record[field] !== undefined && typeof record[field] !== \"string\")\n throw invalid();\n validateUsageReceipts(record.usageReceipts);\n attempts += record.attempts;\n const output = Object.hasOwn(value.values, record.key)\n ? value.values[record.key]\n : undefined;\n validateGateRecord(\n record,\n tasks.find((item) => item.key === record.key)!,\n value.executionId,\n output,\n );\n if (record.status !== \"done\") {\n if (output !== undefined) throw invalid();\n continue;\n }\n if (!object(output) || ![\"undefined\", \"json\"].includes(String(output.kind)))\n throw invalid();\n if (output.kind === \"json\") {\n if (!Object.hasOwn(output, \"value\") || output.value === undefined)\n throw invalid();\n checkpointValue(output.value);\n }\n }\n if (attempts !== value.usage.attempts) throw invalid();\n if (\n Object.keys(value.values).some(\n (key) => !tasks.some((item) => item.key === key),\n )\n )\n throw invalid();\n const records = new Map(value.records.map((entry) => [entry.key, entry]));\n for (const item of tasks)\n if (\n [\"done\", \"paused\", \"rejected\"].includes(\n String(records.get(item.key)?.status),\n ) &&\n item.after.some(\n (dependency) => records.get(dependency.key)?.status !== \"done\",\n )\n )\n throw invalid();\n}\n"]}
1
+ {"version":3,"file":"checkpoint-validation.js","sourceRoot":"","sources":["../../../src/domain/workflow/checkpoint-validation.ts"],"names":[],"mappings":"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","sourcesContent":["import { validateLoopRecord } from \"./loop-validation.ts\";\nimport { validateQuotaRecord } from \"./quota-validation.ts\";\nimport { validateInteractionRecord } from \"./input-validation.ts\";\nimport { validateUsageReceipts } from \"./usage-receipt.ts\";\nimport { validateGateRecord } from \"./gate-validation.ts\";\nimport type { WorkflowCheckpoint } from \"./checkpoint.types.ts\";\nimport type { Task } from \"../workflow.types.ts\";\nimport { checkpointValue } from \"./checkpoint-value.ts\";\n\nfunction object(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\nfunction integer(value: unknown): value is number {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0;\n}\n\nexport function validateCheckpoint(\n value: unknown,\n identity: string,\n tasks: readonly Task[],\n): asserts value is WorkflowCheckpoint {\n const invalid = () =>\n new Error(\"Invalid or incompatible workflow checkpoint\");\n if (\n !object(value) ||\n value.format !== 1 ||\n value.identity !== identity ||\n typeof value.executionId !== \"string\" ||\n !value.executionId ||\n !Array.isArray(value.records) ||\n !object(value.values) ||\n !object(value.usage)\n )\n throw invalid();\n if (!integer(value.usage.attempts) || !object(value.usage.tokens))\n throw invalid();\n for (const dimension of [\"input\", \"cached\", \"output\"])\n if (!integer(value.usage.tokens[dimension])) throw invalid();\n for (const [dimension, count] of Object.entries(value.usage.tokens)) {\n if (dimension === \"complete\") {\n if (typeof count !== \"boolean\") throw invalid();\n continue;\n }\n if (!integer(count)) throw invalid();\n }\n if (value.records.length !== tasks.length) throw invalid();\n const keys = new Set(tasks.map((item) => item.key));\n let attempts = 0;\n for (const record of value.records) {\n if (\n !object(record) ||\n typeof record.key !== \"string\" ||\n !keys.delete(record.key) ||\n !integer(record.attempts) ||\n ![\n \"waiting-input\",\n \"waiting\",\n \"active\",\n \"done\",\n \"failed\",\n \"skipped\",\n \"cancelled\",\n \"paused\",\n \"rejected\",\n ].includes(String(record.status))\n )\n throw invalid();\n for (const field of [\"startedAt\", \"finishedAt\", \"error\"])\n if (record[field] !== undefined && typeof record[field] !== \"string\")\n throw invalid();\n if (\n record.cacheHit !== undefined &&\n (record.cacheHit !== true ||\n record.status !== \"done\" ||\n record.attempts !== 0 ||\n record.rounds !== undefined)\n )\n throw invalid();\n validateUsageReceipts(record.usageReceipts);\n validateLoopRecord(\n record,\n tasks.find((item) => item.key === record.key)!,\n );\n validateQuotaRecord(\n record,\n tasks.find((item) => item.key === record.key)!,\n );\n validateInteractionRecord(\n record,\n tasks.find((item) => item.key === record.key)!,\n value.executionId,\n );\n attempts += record.attempts;\n const output = Object.hasOwn(value.values, record.key)\n ? value.values[record.key]\n : undefined;\n validateGateRecord(\n record,\n tasks.find((item) => item.key === record.key)!,\n value.executionId,\n output,\n );\n if (record.status !== \"done\") {\n if (output !== undefined) throw invalid();\n continue;\n }\n if (!object(output) || ![\"undefined\", \"json\"].includes(String(output.kind)))\n throw invalid();\n if (output.kind === \"json\") {\n if (!Object.hasOwn(output, \"value\") || output.value === undefined)\n throw invalid();\n checkpointValue(output.value);\n }\n }\n if (attempts !== value.usage.attempts) throw invalid();\n if (\n Object.keys(value.values).some(\n (key) => !tasks.some((item) => item.key === key),\n )\n )\n throw invalid();\n const records = new Map(value.records.map((entry) => [entry.key, entry]));\n for (const item of tasks)\n if (\n [\"done\", \"paused\", \"rejected\", \"waiting-input\"].includes(\n String(records.get(item.key)?.status),\n ) &&\n item.after.some(\n (dependency) => records.get(dependency.key)?.status !== \"done\",\n )\n )\n throw invalid();\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import { loopDefinition } from "./loop-task.js";
1
2
  import { createHash } from "node:crypto";
2
3
  import { checkpointValue } from "./checkpoint-value.js";
3
4
  import { validateCheckpoint } from "./checkpoint-validation.js";
@@ -7,6 +8,10 @@ export async function openCheckpoint(name, tasks, options) {
7
8
  const graph = tasks
8
9
  .map((item) => ({
9
10
  key: item.key,
11
+ ...(item.interaction ? { interaction: item.interaction } : {}),
12
+ ...(loopDefinition(item)
13
+ ? { maxRounds: loopDefinition(item).maxRounds }
14
+ : {}),
10
15
  ...(item.gate ? { gate: item.gate } : {}),
11
16
  after: item.after.map((dependency) => dependency.key).sort(),
12
17
  timeoutMs: item.timeoutMs,
@@ -33,8 +38,14 @@ export async function openCheckpoint(name, tasks, options) {
33
38
  if (saved !== undefined) {
34
39
  validateCheckpoint(saved, identity, tasks);
35
40
  initial = saved;
36
- const settledGate = saved.records.some((record) => ["paused", "rejected"].includes(record.status));
37
- if (saved.records.some((record) => !["done", "skipped", "paused", "rejected"].includes(record.status) &&
41
+ const settledGate = saved.records.some((record) => ["paused", "rejected", "waiting-input"].includes(record.status));
42
+ if (saved.records.some((record) => ![
43
+ "done",
44
+ "skipped",
45
+ "paused",
46
+ "rejected",
47
+ "waiting-input",
48
+ ].includes(record.status) &&
38
49
  !(settledGate &&
39
50
  record.status === "waiting" &&
40
51
  record.attempts === 0)) &&
@@ -1 +1 @@
1
- {"version":3,"file":"checkpoint.js","sourceRoot":"","sources":["../../../src/domain/workflow/checkpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAOzC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAEhE,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAY,EACZ,KAAsB,EACtB,OAAkC;IAElC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE;QAClD,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,MAAM,KAAK,GAAG,KAAK;SAChB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACd,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE;QAC5D,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ;QAC3B,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO;QAC5B,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO;QAC5B,UAAU,EAAE,IAAI,CAAC,KAAK,EAAE,UAAU;QAClC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM;QAC1B,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS;QAC3B,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO;KAC/B,CAAC,CAAC;SACF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACb,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG;YAAE,OAAO,CAAC,CAAC;QAC9B,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IACL,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC;SAClC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;SACjE,MAAM,CAAC,KAAK,CAAC,CAAC;IACjB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACzD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,OAAuC,CAAC;QAC5C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,kBAAkB,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;YAC3C,OAAO,GAAG,KAAK,CAAC;YAChB,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAChD,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAC/C,CAAC;YACF,IACE,KAAK,CAAC,OAAO,CAAC,IAAI,CAChB,CAAC,MAAM,EAAE,EAAE,CACT,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,QAAQ,CACjD,MAAM,CAAC,MAAM,CACd;gBACD,CAAC,CACC,WAAW;oBACX,MAAM,CAAC,MAAM,KAAK,SAAS;oBAC3B,MAAM,CAAC,QAAQ,KAAK,CAAC,CACtB,CACJ;gBACD,OAAO,CAAC,MAAM,KAAK,kBAAkB;gBAErC,MAAM,IAAI,KAAK,CACb,0FAA0F,CAC3F,CAAC;QACN,CAAC;QACD,IAAI,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QAC/B,OAAO;YACL,OAAO;YACP,IAAI,CAAC,KAA6B;gBAChC,MAAM,QAAQ,GAAuB;oBACnC,MAAM,EAAE,CAAC;oBACT,QAAQ;oBACR,WAAW,EAAE,KAAK,CAAC,WAAW;oBAC9B,OAAO,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;oBACrE,MAAM,EAAE,MAAM,CAAC,WAAW,CACxB,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;wBACvC,IAAI,CAAC,GAAG;wBACR,eAAe,CAAC,KAAK,CAAC;qBACvB,CAAC,CACH;oBACD,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,QAAQ,EAAE;iBACnC,CAAC;gBACF,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAClD,OAAO,MAAM,CAAC;YAChB,CAAC;YACD,KAAK,CAAC,OAAO;gBACX,MAAM,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBAC7B,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;YACxB,CAAC;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;QACtB,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { Task, WorkflowExecutionState } from \"../workflow.types.ts\";\nimport type {\n WorkflowCheckpoint,\n WorkflowCheckpointOptions,\n WorkflowCheckpointSession,\n} from \"./checkpoint.types.ts\";\nimport { checkpointValue } from \"./checkpoint-value.ts\";\nimport { validateCheckpoint } from \"./checkpoint-validation.ts\";\n\nexport async function openCheckpoint(\n name: string,\n tasks: readonly Task[],\n options: WorkflowCheckpointOptions,\n): Promise<WorkflowCheckpointSession> {\n if (!options.runId.trim() || !options.version.trim())\n throw new Error(\"Checkpoint runId and version must not be empty\");\n const graph = tasks\n .map((item) => ({\n key: item.key,\n ...(item.gate ? { gate: item.gate } : {}),\n after: item.after.map((dependency) => dependency.key).sort(),\n timeoutMs: item.timeoutMs,\n retry: item.retry?.attempts,\n delayMs: item.retry?.delayMs,\n backoff: item.retry?.backoff,\n maxDelayMs: item.retry?.maxDelayMs,\n jitter: item.retry?.jitter,\n condition: !!item.condition,\n accepts: !!item.retry?.accepts,\n }))\n .sort((a, b) => {\n if (a.key === b.key) return 0;\n return a.key < b.key ? -1 : 1;\n });\n const identity = createHash(\"sha256\")\n .update(JSON.stringify({ name, version: options.version, graph }))\n .digest(\"hex\");\n const lease = await options.store.acquire(options.runId);\n try {\n const saved = await lease.read();\n let initial: WorkflowCheckpoint | undefined;\n if (saved !== undefined) {\n validateCheckpoint(saved, identity, tasks);\n initial = saved;\n const settledGate = saved.records.some((record) =>\n [\"paused\", \"rejected\"].includes(record.status),\n );\n if (\n saved.records.some(\n (record) =>\n ![\"done\", \"skipped\", \"paused\", \"rejected\"].includes(\n record.status,\n ) &&\n !(\n settledGate &&\n record.status === \"waiting\" &&\n record.attempts === 0\n ),\n ) &&\n options.resume !== \"retry-incomplete\"\n )\n throw new Error(\n \"Incomplete workflow checkpoint: resume retry-incomplete must explicitly authorize replay\",\n );\n }\n let writes = Promise.resolve();\n return {\n initial,\n save(state: WorkflowExecutionState) {\n const snapshot: WorkflowCheckpoint = {\n format: 1,\n identity,\n executionId: state.executionId,\n records: [...state.records.values()].map((record) => ({ ...record })),\n values: Object.fromEntries(\n [...state.values].map(([item, value]) => [\n item.key,\n checkpointValue(value),\n ]),\n ),\n usage: state.accounting.snapshot(),\n };\n writes = writes.then(() => lease.write(snapshot));\n return writes;\n },\n async release() {\n await writes.catch(() => {});\n await lease.release();\n },\n };\n } catch (error) {\n await lease.release();\n throw error;\n }\n}\n"]}
1
+ {"version":3,"file":"checkpoint.js","sourceRoot":"","sources":["../../../src/domain/workflow/checkpoint.ts"],"names":[],"mappings":"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","sourcesContent":["import { loopDefinition } from \"./loop-task.ts\";\nimport { createHash } from \"node:crypto\";\nimport type { Task, WorkflowExecutionState } from \"../workflow.types.ts\";\nimport type {\n WorkflowCheckpoint,\n WorkflowCheckpointOptions,\n WorkflowCheckpointSession,\n} from \"./checkpoint.types.ts\";\nimport { checkpointValue } from \"./checkpoint-value.ts\";\nimport { validateCheckpoint } from \"./checkpoint-validation.ts\";\n\nexport async function openCheckpoint(\n name: string,\n tasks: readonly Task[],\n options: WorkflowCheckpointOptions,\n): Promise<WorkflowCheckpointSession> {\n if (!options.runId.trim() || !options.version.trim())\n throw new Error(\"Checkpoint runId and version must not be empty\");\n const graph = tasks\n .map((item) => ({\n key: item.key,\n ...(item.interaction ? { interaction: item.interaction } : {}),\n ...(loopDefinition(item)\n ? { maxRounds: loopDefinition(item)!.maxRounds }\n : {}),\n ...(item.gate ? { gate: item.gate } : {}),\n after: item.after.map((dependency) => dependency.key).sort(),\n timeoutMs: item.timeoutMs,\n retry: item.retry?.attempts,\n delayMs: item.retry?.delayMs,\n backoff: item.retry?.backoff,\n maxDelayMs: item.retry?.maxDelayMs,\n jitter: item.retry?.jitter,\n condition: !!item.condition,\n accepts: !!item.retry?.accepts,\n }))\n .sort((a, b) => {\n if (a.key === b.key) return 0;\n return a.key < b.key ? -1 : 1;\n });\n const identity = createHash(\"sha256\")\n .update(JSON.stringify({ name, version: options.version, graph }))\n .digest(\"hex\");\n const lease = await options.store.acquire(options.runId);\n try {\n const saved = await lease.read();\n let initial: WorkflowCheckpoint | undefined;\n if (saved !== undefined) {\n validateCheckpoint(saved, identity, tasks);\n initial = saved;\n const settledGate = saved.records.some((record) =>\n [\"paused\", \"rejected\", \"waiting-input\"].includes(record.status),\n );\n if (\n saved.records.some(\n (record) =>\n ![\n \"done\",\n \"skipped\",\n \"paused\",\n \"rejected\",\n \"waiting-input\",\n ].includes(record.status) &&\n !(\n settledGate &&\n record.status === \"waiting\" &&\n record.attempts === 0\n ),\n ) &&\n options.resume !== \"retry-incomplete\"\n )\n throw new Error(\n \"Incomplete workflow checkpoint: resume retry-incomplete must explicitly authorize replay\",\n );\n }\n let writes = Promise.resolve();\n return {\n initial,\n save(state: WorkflowExecutionState) {\n const snapshot: WorkflowCheckpoint = {\n format: 1,\n identity,\n executionId: state.executionId,\n records: [...state.records.values()].map((record) => ({ ...record })),\n values: Object.fromEntries(\n [...state.values].map(([item, value]) => [\n item.key,\n checkpointValue(value),\n ]),\n ),\n usage: state.accounting.snapshot(),\n };\n writes = writes.then(() => lease.write(snapshot));\n return writes;\n },\n async release() {\n await writes.catch(() => {});\n await lease.release();\n },\n };\n } catch (error) {\n await lease.release();\n throw error;\n }\n}\n"]}
@@ -22,7 +22,8 @@ export function validateGateRecord(record, item, executionId, output) {
22
22
  if (!gate) {
23
23
  if (request !== undefined ||
24
24
  decision !== undefined ||
25
- ["paused", "rejected"].includes(String(record.status)))
25
+ record.status === "rejected" ||
26
+ (record.status === "paused" && record.quota === undefined))
26
27
  throw invalid();
27
28
  return;
28
29
  }
@@ -1 +1 @@
1
- {"version":3,"file":"gate-validation.js","sourceRoot":"","sources":["../../../src/domain/workflow/gate-validation.ts"],"names":[],"mappings":"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","sourcesContent":["import { gateActions } from \"./gates.constants.ts\";\nimport type { Task } from \"../workflow.types.ts\";\nimport type { WorkflowGate } from \"./gates.types.ts\";\n\nfunction object(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nexport function validateGate(gate: WorkflowGate): void {\n if (gate.authentication !== undefined && gate.authentication !== \"signed\")\n throw new Error(\"Invalid gate authentication policy\");\n if (![\"approval\", \"pause\"].includes(gate.kind))\n throw new Error(\"Invalid workflow gate kind\");\n if (!gate.prompt.trim()) throw new Error(\"Gate prompt cannot be empty\");\n if (\n !gate.actors.length ||\n gate.actors.some((actor) => !actor.trim()) ||\n new Set(gate.actors).size !== gate.actors.length\n )\n throw new Error(\"Gate actors must be nonempty and unique\");\n}\n\nexport function validateGateRecord(\n record: Record<string, unknown>,\n item: Task,\n executionId: string,\n output: unknown,\n): void {\n const invalid = () => new Error(\"Invalid workflow checkpoint gate record\");\n const gate = item.gate;\n const request = record.pause;\n const decision = record.decision;\n if (!gate) {\n if (\n request !== undefined ||\n decision !== undefined ||\n [\"paused\", \"rejected\"].includes(String(record.status))\n )\n throw invalid();\n return;\n }\n if (record.attempts !== 0) throw invalid();\n if (![\"paused\", \"done\", \"rejected\"].includes(String(record.status))) {\n if (\n ![\"waiting\", \"skipped\", \"cancelled\"].includes(String(record.status)) ||\n request !== undefined ||\n decision !== undefined\n )\n throw invalid();\n return;\n }\n if (\n !object(request) ||\n typeof request.id !== \"string\" ||\n !request.id.trim() ||\n typeof request.requestedAt !== \"string\" ||\n !Number.isFinite(Date.parse(request.requestedAt)) ||\n request.authentication !== gate.authentication ||\n request.kind !== gate.kind ||\n request.prompt !== gate.prompt ||\n JSON.stringify(request.actors) !== JSON.stringify(gate.actors)\n )\n throw invalid();\n if (record.status === \"paused\") {\n if (decision !== undefined) throw invalid();\n return;\n }\n const action =\n record.status === \"rejected\" ? \"reject\" : gateActions[gate.kind];\n if (\n !object(decision) ||\n decision.executionId !== executionId ||\n decision.key !== item.key ||\n decision.requestId !== request.id ||\n decision.action !== action ||\n typeof decision.actor !== \"string\" ||\n !gate.actors.includes(decision.actor) ||\n typeof decision.reason !== \"string\" ||\n !decision.reason.trim() ||\n typeof decision.decidedAt !== \"string\" ||\n !Number.isFinite(Date.parse(decision.decidedAt))\n )\n throw invalid();\n const verification = decision.verification;\n if (gate.authentication === \"signed\" || verification !== undefined) {\n if (\n !object(verification) ||\n typeof verification.keyId !== \"string\" ||\n !verification.keyId.trim() ||\n typeof verification.verifiedAt !== \"string\" ||\n !Number.isFinite(Date.parse(verification.verifiedAt))\n )\n throw invalid();\n }\n if (\n record.status === \"done\" &&\n (!object(output) ||\n output.kind !== \"json\" ||\n !object(output.value) ||\n Object.keys(output.value).length !== Object.keys(decision).length ||\n !Object.entries(decision).every(\n ([key, value]) =>\n object(output.value) &&\n JSON.stringify(output.value[key]) === JSON.stringify(value),\n ))\n )\n throw invalid();\n}\n"]}
1
+ {"version":3,"file":"gate-validation.js","sourceRoot":"","sources":["../../../src/domain/workflow/gate-validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAInD,SAAS,MAAM,CAAC,KAAc;IAC5B,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAkB;IAC7C,IAAI,IAAI,CAAC,cAAc,KAAK,SAAS,IAAI,IAAI,CAAC,cAAc,KAAK,QAAQ;QACvE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,IAAI,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACxE,IACE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM;QACnB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAC1C,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,MAAM;QAEhD,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,MAA+B,EAC/B,IAAU,EACV,WAAmB,EACnB,MAAe;IAEf,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC3E,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC;IAC7B,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IACjC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IACE,OAAO,KAAK,SAAS;YACrB,QAAQ,KAAK,SAAS;YACtB,MAAM,CAAC,MAAM,KAAK,UAAU;YAC5B,CAAC,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC;YAE1D,MAAM,OAAO,EAAE,CAAC;QAClB,OAAO;IACT,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC;QAAE,MAAM,OAAO,EAAE,CAAC;IAC3C,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QACpE,IACE,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACpE,OAAO,KAAK,SAAS;YACrB,QAAQ,KAAK,SAAS;YAEtB,MAAM,OAAO,EAAE,CAAC;QAClB,OAAO;IACT,CAAC;IACD,IACE,CAAC,MAAM,CAAC,OAAO,CAAC;QAChB,OAAO,OAAO,CAAC,EAAE,KAAK,QAAQ;QAC9B,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,OAAO,OAAO,CAAC,WAAW,KAAK,QAAQ;QACvC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QACjD,OAAO,CAAC,cAAc,KAAK,IAAI,CAAC,cAAc;QAC9C,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;QAC1B,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAC9B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;QAE9D,MAAM,OAAO,EAAE,CAAC;IAClB,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,IAAI,QAAQ,KAAK,SAAS;YAAE,MAAM,OAAO,EAAE,CAAC;QAC5C,OAAO;IACT,CAAC;IACD,MAAM,MAAM,GACV,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnE,IACE,CAAC,MAAM,CAAC,QAAQ,CAAC;QACjB,QAAQ,CAAC,WAAW,KAAK,WAAW;QACpC,QAAQ,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG;QACzB,QAAQ,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE;QACjC,QAAQ,CAAC,MAAM,KAAK,MAAM;QAC1B,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ;QAClC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC;QACrC,OAAO,QAAQ,CAAC,MAAM,KAAK,QAAQ;QACnC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE;QACvB,OAAO,QAAQ,CAAC,SAAS,KAAK,QAAQ;QACtC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAEhD,MAAM,OAAO,EAAE,CAAC;IAClB,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC;IAC3C,IAAI,IAAI,CAAC,cAAc,KAAK,QAAQ,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QACnE,IACE,CAAC,MAAM,CAAC,YAAY,CAAC;YACrB,OAAO,YAAY,CAAC,KAAK,KAAK,QAAQ;YACtC,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE;YAC1B,OAAO,YAAY,CAAC,UAAU,KAAK,QAAQ;YAC3C,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;YAErD,MAAM,OAAO,EAAE,CAAC;IACpB,CAAC;IACD,IACE,MAAM,CAAC,MAAM,KAAK,MAAM;QACxB,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;YACd,MAAM,CAAC,IAAI,KAAK,MAAM;YACtB,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;YACrB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM;YACjE,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,CAC7B,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CACf,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;gBACpB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAC9D,CAAC;QAEJ,MAAM,OAAO,EAAE,CAAC;AACpB,CAAC","sourcesContent":["import { gateActions } from \"./gates.constants.ts\";\nimport type { Task } from \"../workflow.types.ts\";\nimport type { WorkflowGate } from \"./gates.types.ts\";\n\nfunction object(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nexport function validateGate(gate: WorkflowGate): void {\n if (gate.authentication !== undefined && gate.authentication !== \"signed\")\n throw new Error(\"Invalid gate authentication policy\");\n if (![\"approval\", \"pause\"].includes(gate.kind))\n throw new Error(\"Invalid workflow gate kind\");\n if (!gate.prompt.trim()) throw new Error(\"Gate prompt cannot be empty\");\n if (\n !gate.actors.length ||\n gate.actors.some((actor) => !actor.trim()) ||\n new Set(gate.actors).size !== gate.actors.length\n )\n throw new Error(\"Gate actors must be nonempty and unique\");\n}\n\nexport function validateGateRecord(\n record: Record<string, unknown>,\n item: Task,\n executionId: string,\n output: unknown,\n): void {\n const invalid = () => new Error(\"Invalid workflow checkpoint gate record\");\n const gate = item.gate;\n const request = record.pause;\n const decision = record.decision;\n if (!gate) {\n if (\n request !== undefined ||\n decision !== undefined ||\n record.status === \"rejected\" ||\n (record.status === \"paused\" && record.quota === undefined)\n )\n throw invalid();\n return;\n }\n if (record.attempts !== 0) throw invalid();\n if (![\"paused\", \"done\", \"rejected\"].includes(String(record.status))) {\n if (\n ![\"waiting\", \"skipped\", \"cancelled\"].includes(String(record.status)) ||\n request !== undefined ||\n decision !== undefined\n )\n throw invalid();\n return;\n }\n if (\n !object(request) ||\n typeof request.id !== \"string\" ||\n !request.id.trim() ||\n typeof request.requestedAt !== \"string\" ||\n !Number.isFinite(Date.parse(request.requestedAt)) ||\n request.authentication !== gate.authentication ||\n request.kind !== gate.kind ||\n request.prompt !== gate.prompt ||\n JSON.stringify(request.actors) !== JSON.stringify(gate.actors)\n )\n throw invalid();\n if (record.status === \"paused\") {\n if (decision !== undefined) throw invalid();\n return;\n }\n const action =\n record.status === \"rejected\" ? \"reject\" : gateActions[gate.kind];\n if (\n !object(decision) ||\n decision.executionId !== executionId ||\n decision.key !== item.key ||\n decision.requestId !== request.id ||\n decision.action !== action ||\n typeof decision.actor !== \"string\" ||\n !gate.actors.includes(decision.actor) ||\n typeof decision.reason !== \"string\" ||\n !decision.reason.trim() ||\n typeof decision.decidedAt !== \"string\" ||\n !Number.isFinite(Date.parse(decision.decidedAt))\n )\n throw invalid();\n const verification = decision.verification;\n if (gate.authentication === \"signed\" || verification !== undefined) {\n if (\n !object(verification) ||\n typeof verification.keyId !== \"string\" ||\n !verification.keyId.trim() ||\n typeof verification.verifiedAt !== \"string\" ||\n !Number.isFinite(Date.parse(verification.verifiedAt))\n )\n throw invalid();\n }\n if (\n record.status === \"done\" &&\n (!object(output) ||\n output.kind !== \"json\" ||\n !object(output.value) ||\n Object.keys(output.value).length !== Object.keys(decision).length ||\n !Object.entries(decision).every(\n ([key, value]) =>\n object(output.value) &&\n JSON.stringify(output.value[key]) === JSON.stringify(value),\n ))\n )\n throw invalid();\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { Task } from "../workflow.types.ts";
2
+ import type { TaskInteraction, WorkflowAnswer, WorkflowInputQuestion } from "./input.types.ts";
3
+ export declare function inputObject(value: unknown): value is Record<string, unknown>;
4
+ export declare function validateInteraction(value: TaskInteraction): void;
5
+ export declare function validateInputQuestion(value: unknown): asserts value is WorkflowInputQuestion;
6
+ export declare function validateAnswer(value: unknown, request: Record<string, unknown>, definition: TaskInteraction, executionId: string, key: string): asserts value is WorkflowAnswer;
7
+ export declare function validateInteractionRecord(record: Record<string, unknown>, item: Task, executionId: string): void;
@@ -0,0 +1,82 @@
1
+ import { checkpointValue } from "./checkpoint-value.js";
2
+ export function inputObject(value) {
3
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4
+ }
5
+ function text(value) {
6
+ return typeof value === "string" && !!value.trim();
7
+ }
8
+ export function validateInteraction(value) {
9
+ if (!text(value.identity) ||
10
+ !Array.isArray(value.actors) ||
11
+ !value.actors.length ||
12
+ value.actors.some((actor) => !text(actor)) ||
13
+ new Set(value.actors).size !== value.actors.length)
14
+ throw new Error("Interaction identity and unique authorized actors are required");
15
+ }
16
+ export function validateInputQuestion(value) {
17
+ if (!inputObject(value) ||
18
+ !text(value.question) ||
19
+ (value.allowFreeText !== undefined &&
20
+ typeof value.allowFreeText !== "boolean") ||
21
+ (value.choices !== undefined &&
22
+ (!Array.isArray(value.choices) ||
23
+ !value.choices.length ||
24
+ value.choices.some((choice) => !text(choice)) ||
25
+ new Set(value.choices).size !== value.choices.length)) ||
26
+ (value.allowFreeText === false && value.choices === undefined))
27
+ throw new Error("Invalid workflow input question");
28
+ }
29
+ export function validateAnswer(value, request, definition, executionId, key) {
30
+ if (!inputObject(value) ||
31
+ value.executionId !== executionId ||
32
+ value.key !== key ||
33
+ value.requestId !== request.id ||
34
+ !text(value.actor) ||
35
+ !definition.actors.includes(value.actor) ||
36
+ !text(value.value) ||
37
+ (request.allowFreeText === false &&
38
+ (!Array.isArray(request.choices) ||
39
+ !request.choices.includes(value.value))))
40
+ throw new Error("Invalid or unauthorized workflow answer");
41
+ }
42
+ export function validateInteractionRecord(record, item, executionId) {
43
+ const value = record.interaction;
44
+ const invalid = () => new Error("Invalid workflow checkpoint interaction record");
45
+ if (value === undefined) {
46
+ if (record.status === "waiting-input")
47
+ throw invalid();
48
+ return;
49
+ }
50
+ if (!item.interaction || !inputObject(value))
51
+ throw invalid();
52
+ if (value.state !== undefined)
53
+ checkpointValue(value.state);
54
+ if (value.request === undefined) {
55
+ if (value.answer !== undefined || record.status === "waiting-input")
56
+ throw invalid();
57
+ return;
58
+ }
59
+ const request = value.request;
60
+ validateInputQuestion(request);
61
+ if (!inputObject(request) ||
62
+ !text(request.id) ||
63
+ request.executionId !== executionId ||
64
+ request.key !== item.key ||
65
+ !text(request.requestedAt) ||
66
+ !Number.isFinite(Date.parse(request.requestedAt)) ||
67
+ value.state === undefined)
68
+ throw invalid();
69
+ if (value.answer === undefined) {
70
+ if (record.status !== "waiting-input")
71
+ throw invalid();
72
+ return;
73
+ }
74
+ if (record.status === "waiting-input")
75
+ throw invalid();
76
+ validateAnswer(value.answer, request, item.interaction, executionId, item.key);
77
+ if (!inputObject(value.answer) ||
78
+ !text(value.answer.answeredAt) ||
79
+ !Number.isFinite(Date.parse(value.answer.answeredAt)))
80
+ throw invalid();
81
+ }
82
+ //# sourceMappingURL=input-validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-validation.js","sourceRoot":"","sources":["../../../src/domain/workflow/input-validation.ts"],"names":[],"mappings":"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","sourcesContent":["import { checkpointValue } from \"./checkpoint-value.ts\";\nimport type { Task } from \"../workflow.types.ts\";\nimport type {\n TaskInteraction,\n WorkflowAnswer,\n WorkflowInputQuestion,\n} from \"./input.types.ts\";\n\nexport function inputObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction text(value: unknown): value is string {\n return typeof value === \"string\" && !!value.trim();\n}\n\nexport function validateInteraction(value: TaskInteraction): void {\n if (\n !text(value.identity) ||\n !Array.isArray(value.actors) ||\n !value.actors.length ||\n value.actors.some((actor) => !text(actor)) ||\n new Set(value.actors).size !== value.actors.length\n )\n throw new Error(\n \"Interaction identity and unique authorized actors are required\",\n );\n}\n\nexport function validateInputQuestion(\n value: unknown,\n): asserts value is WorkflowInputQuestion {\n if (\n !inputObject(value) ||\n !text(value.question) ||\n (value.allowFreeText !== undefined &&\n typeof value.allowFreeText !== \"boolean\") ||\n (value.choices !== undefined &&\n (!Array.isArray(value.choices) ||\n !value.choices.length ||\n value.choices.some((choice) => !text(choice)) ||\n new Set(value.choices).size !== value.choices.length)) ||\n (value.allowFreeText === false && value.choices === undefined)\n )\n throw new Error(\"Invalid workflow input question\");\n}\n\nexport function validateAnswer(\n value: unknown,\n request: Record<string, unknown>,\n definition: TaskInteraction,\n executionId: string,\n key: string,\n): asserts value is WorkflowAnswer {\n if (\n !inputObject(value) ||\n value.executionId !== executionId ||\n value.key !== key ||\n value.requestId !== request.id ||\n !text(value.actor) ||\n !definition.actors.includes(value.actor) ||\n !text(value.value) ||\n (request.allowFreeText === false &&\n (!Array.isArray(request.choices) ||\n !request.choices.includes(value.value)))\n )\n throw new Error(\"Invalid or unauthorized workflow answer\");\n}\n\nexport function validateInteractionRecord(\n record: Record<string, unknown>,\n item: Task,\n executionId: string,\n): void {\n const value = record.interaction;\n const invalid = () =>\n new Error(\"Invalid workflow checkpoint interaction record\");\n if (value === undefined) {\n if (record.status === \"waiting-input\") throw invalid();\n return;\n }\n if (!item.interaction || !inputObject(value)) throw invalid();\n if (value.state !== undefined) checkpointValue(value.state);\n if (value.request === undefined) {\n if (value.answer !== undefined || record.status === \"waiting-input\")\n throw invalid();\n return;\n }\n const request = value.request;\n validateInputQuestion(request);\n if (\n !inputObject(request) ||\n !text(request.id) ||\n request.executionId !== executionId ||\n request.key !== item.key ||\n !text(request.requestedAt) ||\n !Number.isFinite(Date.parse(request.requestedAt)) ||\n value.state === undefined\n )\n throw invalid();\n if (value.answer === undefined) {\n if (record.status !== \"waiting-input\") throw invalid();\n return;\n }\n if (record.status === \"waiting-input\") throw invalid();\n validateAnswer(\n value.answer,\n request,\n item.interaction,\n executionId,\n item.key,\n );\n if (\n !inputObject(value.answer) ||\n !text(value.answer.answeredAt) ||\n !Number.isFinite(Date.parse(value.answer.answeredAt))\n )\n throw invalid();\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { Task, WorkflowExecutionState } from "../workflow.types.ts";
2
+ import type { TaskInteractionContext, TaskInteractionRecord, WorkflowAnswerRecord } from "./input.types.ts";
3
+ export declare class InputSuspension extends Error {
4
+ readonly interaction: TaskInteractionRecord;
5
+ constructor(interaction: TaskInteractionRecord);
6
+ }
7
+ export declare function immutableInput<T>(value: T): T;
8
+ export declare function interactionContext(item: Task, runtime: WorkflowExecutionState, attempt: number, signal: AbortSignal, isActive: () => boolean): TaskInteractionContext;
9
+ export declare function prepareAnswers(runtime: WorkflowExecutionState): readonly WorkflowAnswerRecord[];
10
+ export declare function applyAnswers(runtime: WorkflowExecutionState, answers: readonly WorkflowAnswerRecord[]): void;
@@ -0,0 +1,93 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { checkpointValue } from "./checkpoint-value.js";
3
+ import { validateAnswer, validateInputQuestion } from "./input-validation.js";
4
+ export class InputSuspension extends Error {
5
+ interaction;
6
+ constructor(interaction) {
7
+ super("Task is waiting for human input");
8
+ this.interaction = interaction;
9
+ }
10
+ }
11
+ export function immutableInput(value) {
12
+ checkpointValue(value);
13
+ const copy = structuredClone(value);
14
+ function freeze(item) {
15
+ if (item === null || typeof item !== "object")
16
+ return;
17
+ for (const child of Object.values(item))
18
+ freeze(child);
19
+ Object.freeze(item);
20
+ }
21
+ freeze(copy);
22
+ return copy;
23
+ }
24
+ export function interactionContext(item, runtime, attempt, signal, isActive) {
25
+ const record = runtime.record(item);
26
+ const current = () => {
27
+ signal.throwIfAborted();
28
+ if (!isActive() ||
29
+ !runtime.options.checkpoint ||
30
+ record.status !== "active" ||
31
+ record.attempts !== attempt ||
32
+ attempt === 0)
33
+ throw new Error("Interaction requires an active checkpointed task attempt");
34
+ };
35
+ return {
36
+ get state() {
37
+ return record.interaction?.state;
38
+ },
39
+ get answer() {
40
+ return record.interaction?.answer;
41
+ },
42
+ async save(state) {
43
+ current();
44
+ record.interaction = immutableInput({ ...record.interaction, state });
45
+ await runtime.persist();
46
+ },
47
+ suspend(question, state) {
48
+ current();
49
+ validateInputQuestion(question);
50
+ const pending = immutableInput({
51
+ state,
52
+ request: {
53
+ ...question,
54
+ id: randomUUID(),
55
+ executionId: runtime.executionId,
56
+ key: item.key,
57
+ requestedAt: new Date().toISOString(),
58
+ },
59
+ });
60
+ throw new InputSuspension(pending);
61
+ },
62
+ };
63
+ }
64
+ export function prepareAnswers(runtime) {
65
+ const answers = [];
66
+ const seen = new Set();
67
+ for (const value of runtime.options.answers ?? []) {
68
+ const item = [...runtime.records.keys()].find((task) => task.key === value.key);
69
+ const record = item && runtime.record(item);
70
+ const request = record?.interaction?.request;
71
+ if (!item?.interaction ||
72
+ !request ||
73
+ record?.status !== "waiting-input" ||
74
+ record.interaction?.answer ||
75
+ seen.has(item.key))
76
+ throw new Error("Stale or duplicate workflow answer");
77
+ validateAnswer(value, { ...request }, item.interaction, runtime.executionId, item.key);
78
+ seen.add(item.key);
79
+ answers.push(immutableInput({ ...value, answeredAt: new Date().toISOString() }));
80
+ }
81
+ return answers;
82
+ }
83
+ export function applyAnswers(runtime, answers) {
84
+ for (const answer of answers) {
85
+ const item = [...runtime.records.keys()].find((task) => task.key === answer.key);
86
+ const record = runtime.record(item);
87
+ record.interaction = immutableInput({ ...record.interaction, answer });
88
+ record.status = "waiting";
89
+ delete record.finishedAt;
90
+ runtime.emit({ type: "input-answer", key: item.key, status: "waiting" });
91
+ }
92
+ }
93
+ //# sourceMappingURL=input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.js","sourceRoot":"","sources":["../../../src/domain/workflow/input.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { checkpointValue } from \"./checkpoint-value.ts\";\nimport { validateAnswer, validateInputQuestion } from \"./input-validation.ts\";\nimport type { Task, WorkflowExecutionState } from \"../workflow.types.ts\";\nimport type { WorkflowJson } from \"./checkpoint.types.ts\";\nimport type {\n TaskInteractionContext,\n TaskInteractionRecord,\n WorkflowAnswerRecord,\n} from \"./input.types.ts\";\n\nexport class InputSuspension extends Error {\n readonly interaction: TaskInteractionRecord;\n constructor(interaction: TaskInteractionRecord) {\n super(\"Task is waiting for human input\");\n this.interaction = interaction;\n }\n}\n\nexport function immutableInput<T>(value: T): T {\n checkpointValue(value);\n const copy: T = structuredClone(value);\n function freeze(item: unknown): void {\n if (item === null || typeof item !== \"object\") return;\n for (const child of Object.values(item)) freeze(child);\n Object.freeze(item);\n }\n freeze(copy);\n return copy;\n}\n\nexport function interactionContext(\n item: Task,\n runtime: WorkflowExecutionState,\n attempt: number,\n signal: AbortSignal,\n isActive: () => boolean,\n): TaskInteractionContext {\n const record = runtime.record(item);\n const current = () => {\n signal.throwIfAborted();\n if (\n !isActive() ||\n !runtime.options.checkpoint ||\n record.status !== \"active\" ||\n record.attempts !== attempt ||\n attempt === 0\n )\n throw new Error(\n \"Interaction requires an active checkpointed task attempt\",\n );\n };\n return {\n get state() {\n return record.interaction?.state;\n },\n get answer() {\n return record.interaction?.answer;\n },\n async save(state: WorkflowJson) {\n current();\n record.interaction = immutableInput({ ...record.interaction, state });\n await runtime.persist();\n },\n suspend(question, state) {\n current();\n validateInputQuestion(question);\n const pending = immutableInput({\n state,\n request: {\n ...question,\n id: randomUUID(),\n executionId: runtime.executionId,\n key: item.key,\n requestedAt: new Date().toISOString(),\n },\n });\n throw new InputSuspension(pending);\n },\n };\n}\n\nexport function prepareAnswers(\n runtime: WorkflowExecutionState,\n): readonly WorkflowAnswerRecord[] {\n const answers: WorkflowAnswerRecord[] = [];\n const seen = new Set<string>();\n for (const value of runtime.options.answers ?? []) {\n const item = [...runtime.records.keys()].find(\n (task) => task.key === value.key,\n );\n const record = item && runtime.record(item);\n const request = record?.interaction?.request;\n if (\n !item?.interaction ||\n !request ||\n record?.status !== \"waiting-input\" ||\n record.interaction?.answer ||\n seen.has(item.key)\n )\n throw new Error(\"Stale or duplicate workflow answer\");\n validateAnswer(\n value,\n { ...request },\n item.interaction,\n runtime.executionId,\n item.key,\n );\n seen.add(item.key);\n answers.push(\n immutableInput({ ...value, answeredAt: new Date().toISOString() }),\n );\n }\n return answers;\n}\n\nexport function applyAnswers(\n runtime: WorkflowExecutionState,\n answers: readonly WorkflowAnswerRecord[],\n): void {\n for (const answer of answers) {\n const item = [...runtime.records.keys()].find(\n (task) => task.key === answer.key,\n )!;\n const record = runtime.record(item);\n record.interaction = immutableInput({ ...record.interaction, answer });\n record.status = \"waiting\";\n delete record.finishedAt;\n runtime.emit({ type: \"input-answer\", key: item.key, status: \"waiting\" });\n }\n}\n"]}
@@ -0,0 +1,37 @@
1
+ import type { WorkflowJson } from "./checkpoint.types.ts";
2
+ export interface WorkflowInputQuestion {
3
+ readonly question: string;
4
+ readonly choices?: readonly string[];
5
+ readonly allowFreeText?: boolean;
6
+ }
7
+ export interface WorkflowInputRequest extends WorkflowInputQuestion {
8
+ readonly id: string;
9
+ readonly executionId: string;
10
+ readonly key: string;
11
+ readonly requestedAt: string;
12
+ }
13
+ export interface WorkflowAnswer {
14
+ readonly executionId: string;
15
+ readonly key: string;
16
+ readonly requestId: string;
17
+ readonly actor: string;
18
+ readonly value: string;
19
+ }
20
+ export interface WorkflowAnswerRecord extends WorkflowAnswer {
21
+ readonly answeredAt: string;
22
+ }
23
+ export interface TaskInteraction {
24
+ readonly actors: readonly string[];
25
+ readonly identity: string;
26
+ }
27
+ export interface TaskInteractionRecord {
28
+ readonly state?: WorkflowJson;
29
+ readonly request?: WorkflowInputRequest;
30
+ readonly answer?: WorkflowAnswerRecord;
31
+ }
32
+ export interface TaskInteractionContext {
33
+ readonly state: WorkflowJson | undefined;
34
+ readonly answer: WorkflowAnswerRecord | undefined;
35
+ save(state: WorkflowJson): Promise<void>;
36
+ suspend(question: WorkflowInputQuestion, state: WorkflowJson): never;
37
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=input.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.types.js","sourceRoot":"","sources":["../../../src/domain/workflow/input.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { WorkflowJson } from \"./checkpoint.types.ts\";\n\nexport interface WorkflowInputQuestion {\n readonly question: string;\n readonly choices?: readonly string[];\n readonly allowFreeText?: boolean;\n}\n\nexport interface WorkflowInputRequest extends WorkflowInputQuestion {\n readonly id: string;\n readonly executionId: string;\n readonly key: string;\n readonly requestedAt: string;\n}\n\nexport interface WorkflowAnswer {\n readonly executionId: string;\n readonly key: string;\n readonly requestId: string;\n readonly actor: string;\n readonly value: string;\n}\n\nexport interface WorkflowAnswerRecord extends WorkflowAnswer {\n readonly answeredAt: string;\n}\n\nexport interface TaskInteraction {\n readonly actors: readonly string[];\n readonly identity: string;\n}\n\nexport interface TaskInteractionRecord {\n readonly state?: WorkflowJson;\n readonly request?: WorkflowInputRequest;\n readonly answer?: WorkflowAnswerRecord;\n}\n\nexport interface TaskInteractionContext {\n readonly state: WorkflowJson | undefined;\n readonly answer: WorkflowAnswerRecord | undefined;\n save(state: WorkflowJson): Promise<void>;\n suspend(question: WorkflowInputQuestion, state: WorkflowJson): never;\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { Task, WorkflowExecutionState } from "../workflow.types.ts";
2
+ import type { LoopDefinition } from "./loop-task.types.ts";
3
+ export declare function runLoopTask(item: Task, definition: LoopDefinition, runtime: WorkflowExecutionState): Promise<unknown>;
@@ -0,0 +1,91 @@
1
+ import { checkpointValue } from "./checkpoint-value.js";
2
+ import { LoopTaskExhausted } from "./loop-task.js";
3
+ import { validateLoopCheck } from "./loop-validation.js";
4
+ export async function runLoopTask(item, definition, runtime) {
5
+ const state = runtime.record(item);
6
+ let rounds = state.rounds ?? Object.freeze([]);
7
+ async function save(entry) {
8
+ rounds = Object.freeze([
9
+ ...rounds.filter((value) => value.round !== entry.round),
10
+ Object.freeze(entry),
11
+ ]);
12
+ state.rounds = rounds;
13
+ runtime.emit({
14
+ type: "loop",
15
+ key: item.key,
16
+ attempt: state.attempts,
17
+ round: entry.round,
18
+ phase: entry.phase,
19
+ });
20
+ await runtime.persist();
21
+ }
22
+ while (true) {
23
+ runtime.signal.throwIfAborted();
24
+ const last = rounds.at(-1);
25
+ if (last?.check?.done)
26
+ return last.output?.kind === "json" ? last.output.value : undefined;
27
+ if (last?.phase === "complete" && last.round === definition.maxRounds)
28
+ throw new LoopTaskExhausted(item.key, definition.maxRounds, last.check?.done === false ? last.check.feedback : "");
29
+ const entry = last && last.phase !== "complete"
30
+ ? last
31
+ : { round: rounds.length + 1, phase: "attempt" };
32
+ runtime.accounting.admit();
33
+ state.attempts++;
34
+ runtime.emit({ type: "attempt", key: item.key, attempt: state.attempts });
35
+ await save(entry);
36
+ const deadline = new AbortController();
37
+ const timer = item.timeoutMs === undefined
38
+ ? undefined
39
+ : setTimeout(() => deadline.abort(new Error(`${item.key} timed out`)), item.timeoutMs);
40
+ const signal = AbortSignal.any([runtime.signal, deadline.signal]);
41
+ const base = runtime.context(item, state.attempts, signal);
42
+ const context = (phase) => ({
43
+ ...base,
44
+ round: entry.round,
45
+ phase,
46
+ idempotencyKey: `${base.idempotencyKey}:round:${entry.round}:${phase}`,
47
+ });
48
+ try {
49
+ signal.throwIfAborted();
50
+ let result = entry.output?.kind === "json"
51
+ ? structuredClone(entry.output.value)
52
+ : undefined;
53
+ if (entry.phase === "attempt") {
54
+ const previous = rounds.at(-2)?.check;
55
+ result = await definition.attempt(context("attempt"), previous?.done === false ? previous.feedback : undefined);
56
+ signal.throwIfAborted();
57
+ const output = runtime.options.checkpoint
58
+ ? checkpointValue(result)
59
+ : undefined;
60
+ await save({
61
+ round: entry.round,
62
+ phase: "check",
63
+ ...(output ? { output } : {}),
64
+ });
65
+ }
66
+ signal.throwIfAborted();
67
+ const checked = await definition.check(context("check"), result);
68
+ signal.throwIfAborted();
69
+ validateLoopCheck(checked);
70
+ const check = Object.freeze(checked.done
71
+ ? { done: true }
72
+ : { done: false, feedback: checked.feedback });
73
+ await save({
74
+ ...rounds.at(-1),
75
+ phase: "complete",
76
+ check,
77
+ ...(runtime.options.checkpoint
78
+ ? { output: checkpointValue(result) }
79
+ : {}),
80
+ });
81
+ signal.throwIfAborted();
82
+ if (check.done)
83
+ return result;
84
+ }
85
+ finally {
86
+ runtime.closeAttempt(item);
87
+ clearTimeout(timer);
88
+ }
89
+ }
90
+ }
91
+ //# sourceMappingURL=loop-runner.js.map