@lota-sdk/core 0.4.8 → 0.4.9

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 (259) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +94 -22
  3. package/src/ai-gateway/ai-gateway.ts +738 -223
  4. package/src/config/agent-defaults.ts +176 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/constants.ts +8 -2
  7. package/src/config/logger.ts +286 -19
  8. package/src/config/thread-defaults.ts +33 -21
  9. package/src/create-runtime.ts +725 -387
  10. package/src/db/base.service.ts +52 -28
  11. package/src/db/cursor-pagination.ts +71 -30
  12. package/src/db/memory-store.helpers.ts +4 -7
  13. package/src/db/memory-store.ts +856 -598
  14. package/src/db/memory.ts +398 -275
  15. package/src/db/record-id.ts +32 -10
  16. package/src/db/schema-fingerprint.ts +30 -12
  17. package/src/db/service-normalization.ts +255 -0
  18. package/src/db/service.ts +726 -761
  19. package/src/db/startup.ts +140 -66
  20. package/src/db/transaction-conflict.ts +15 -0
  21. package/src/effect/awaitable-effect.ts +87 -0
  22. package/src/effect/errors.ts +121 -0
  23. package/src/effect/helpers.ts +98 -0
  24. package/src/effect/index.ts +22 -0
  25. package/src/effect/layers.ts +228 -0
  26. package/src/effect/runtime-ref.ts +25 -0
  27. package/src/effect/runtime.ts +31 -0
  28. package/src/effect/services.ts +57 -0
  29. package/src/effect/zod.ts +43 -0
  30. package/src/embeddings/provider.ts +122 -76
  31. package/src/index.ts +46 -1
  32. package/src/openrouter/direct-provider.ts +11 -35
  33. package/src/queues/autonomous-job.queue.ts +130 -74
  34. package/src/queues/context-compaction.queue.ts +60 -15
  35. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  36. package/src/queues/document-processor.queue.ts +52 -77
  37. package/src/queues/memory-consolidation.queue.ts +47 -32
  38. package/src/queues/organization-learning.queue.ts +13 -4
  39. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  40. package/src/queues/plan-scheduler.queue.ts +107 -31
  41. package/src/queues/post-chat-memory.queue.ts +66 -24
  42. package/src/queues/queue-factory.ts +142 -52
  43. package/src/queues/standalone-worker.ts +39 -0
  44. package/src/queues/title-generation.queue.ts +54 -9
  45. package/src/redis/connection.ts +84 -32
  46. package/src/redis/index.ts +6 -8
  47. package/src/redis/org-memory-lock.ts +60 -27
  48. package/src/redis/redis-lease-lock.ts +200 -121
  49. package/src/redis/runtime-connection.ts +10 -0
  50. package/src/redis/stream-context.ts +84 -46
  51. package/src/runtime/agent-identity-overrides.ts +2 -2
  52. package/src/runtime/agent-runtime-policy.ts +4 -1
  53. package/src/runtime/agent-stream-helpers.ts +20 -9
  54. package/src/runtime/chat-run-orchestration.ts +102 -19
  55. package/src/runtime/chat-run-registry.ts +36 -2
  56. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  57. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  58. package/src/runtime/execution-plan-visibility.ts +2 -2
  59. package/src/runtime/execution-plan.ts +42 -15
  60. package/src/runtime/graph-designer.ts +11 -7
  61. package/src/runtime/helper-model.ts +135 -48
  62. package/src/runtime/index.ts +7 -7
  63. package/src/runtime/indexed-repositories-policy.ts +3 -3
  64. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  65. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  66. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  67. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  68. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  69. package/src/runtime/plugin-resolution.ts +144 -24
  70. package/src/runtime/plugin-types.ts +9 -1
  71. package/src/runtime/post-turn-side-effects.ts +197 -130
  72. package/src/runtime/retrieval-adapters.ts +38 -4
  73. package/src/runtime/runtime-config.ts +150 -61
  74. package/src/runtime/runtime-extensions.ts +21 -34
  75. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  76. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  77. package/src/runtime/social-chat/social-chat.ts +594 -0
  78. package/src/runtime/specialist-runner.ts +36 -10
  79. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  80. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  81. package/src/runtime/thread-chat-helpers.ts +2 -2
  82. package/src/runtime/thread-plan-turn.ts +2 -1
  83. package/src/runtime/thread-turn-context.ts +172 -94
  84. package/src/runtime/turn-lifecycle.ts +93 -27
  85. package/src/services/agent-activity.service.ts +287 -203
  86. package/src/services/agent-executor.service.ts +329 -217
  87. package/src/services/artifact.service.ts +225 -148
  88. package/src/services/attachment.service.ts +137 -115
  89. package/src/services/autonomous-job.service.ts +888 -491
  90. package/src/services/chat-run-registry.service.ts +11 -1
  91. package/src/services/context-compaction.service.ts +136 -86
  92. package/src/services/document-chunk.service.ts +162 -90
  93. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  94. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  95. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  96. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  97. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  98. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  99. package/src/services/feedback-loop.service.ts +132 -76
  100. package/src/services/global-orchestrator.service.ts +80 -170
  101. package/src/services/graph-full-routing.ts +182 -0
  102. package/src/services/index.ts +18 -21
  103. package/src/services/institutional-memory.service.ts +220 -123
  104. package/src/services/learned-skill.service.ts +364 -259
  105. package/src/services/memory/memory-conversation.ts +95 -0
  106. package/src/services/memory/memory-org-memory.ts +39 -0
  107. package/src/services/memory/memory-preseeded.ts +80 -0
  108. package/src/services/memory/memory-rerank.ts +297 -0
  109. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  110. package/src/services/memory/memory.service.ts +692 -0
  111. package/src/services/memory/rerank.service.ts +209 -0
  112. package/src/services/monitoring-window.service.ts +92 -70
  113. package/src/services/mutating-approval.service.ts +62 -53
  114. package/src/services/node-workspace.service.ts +141 -98
  115. package/src/services/notification.service.ts +17 -16
  116. package/src/services/organization-member.service.ts +120 -66
  117. package/src/services/organization.service.ts +144 -51
  118. package/src/services/ownership-dispatcher.service.ts +415 -264
  119. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  120. package/src/services/plan/plan-agent-query.service.ts +322 -0
  121. package/src/services/plan/plan-approval.service.ts +102 -0
  122. package/src/services/plan/plan-artifact.service.ts +60 -0
  123. package/src/services/plan/plan-builder.service.ts +76 -0
  124. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  125. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  126. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  127. package/src/services/plan/plan-coordination.service.ts +181 -0
  128. package/src/services/plan/plan-cycle.service.ts +398 -0
  129. package/src/services/plan/plan-deadline.service.ts +547 -0
  130. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  131. package/src/services/plan/plan-executor-context.ts +35 -0
  132. package/src/services/plan/plan-executor-graph.ts +475 -0
  133. package/src/services/plan/plan-executor-helpers.ts +322 -0
  134. package/src/services/plan/plan-executor-persistence.ts +209 -0
  135. package/src/services/plan/plan-executor.service.ts +1654 -0
  136. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  137. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  138. package/src/services/plan/plan-run-serialization.ts +15 -0
  139. package/src/services/plan/plan-run.service.ts +644 -0
  140. package/src/services/plan/plan-scheduler.service.ts +385 -0
  141. package/src/services/plan/plan-template.service.ts +224 -0
  142. package/src/services/plan/plan-transaction-events.ts +33 -0
  143. package/src/services/plan/plan-validator.service.ts +907 -0
  144. package/src/services/plan/plan-workspace.service.ts +125 -0
  145. package/src/services/plugin-executor.service.ts +97 -68
  146. package/src/services/quality-metrics.service.ts +112 -94
  147. package/src/services/queue-job.service.ts +296 -230
  148. package/src/services/recent-activity-title.service.ts +65 -36
  149. package/src/services/recent-activity.service.ts +274 -259
  150. package/src/services/skill-resolver.service.ts +38 -12
  151. package/src/services/social-chat-history.service.ts +176 -125
  152. package/src/services/system-executor.service.ts +91 -61
  153. package/src/services/thread/thread-active-run.ts +203 -0
  154. package/src/services/thread/thread-bootstrap.ts +369 -0
  155. package/src/services/thread/thread-listing.ts +198 -0
  156. package/src/services/thread/thread-memory-block.ts +117 -0
  157. package/src/services/thread/thread-message.service.ts +363 -0
  158. package/src/services/thread/thread-record-store.ts +155 -0
  159. package/src/services/thread/thread-title.service.ts +74 -0
  160. package/src/services/thread/thread-turn-execution.ts +280 -0
  161. package/src/services/thread/thread-turn-message-context.ts +73 -0
  162. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  163. package/src/services/thread/thread-turn-streaming.ts +402 -0
  164. package/src/services/thread/thread-turn-tracing.ts +35 -0
  165. package/src/services/thread/thread-turn.ts +343 -0
  166. package/src/services/thread/thread.service.ts +335 -0
  167. package/src/services/user.service.ts +82 -32
  168. package/src/services/write-intent-validator.service.ts +63 -51
  169. package/src/storage/attachment-parser.ts +69 -27
  170. package/src/storage/attachment-storage.service.ts +331 -275
  171. package/src/storage/generated-document-storage.service.ts +66 -34
  172. package/src/system-agents/agent-result.ts +3 -1
  173. package/src/system-agents/context-compaction.agent.ts +2 -2
  174. package/src/system-agents/delegated-agent-factory.ts +159 -90
  175. package/src/system-agents/memory-reranker.agent.ts +2 -2
  176. package/src/system-agents/memory.agent.ts +2 -2
  177. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  178. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  179. package/src/system-agents/skill-extractor.agent.ts +2 -2
  180. package/src/system-agents/skill-manager.agent.ts +2 -2
  181. package/src/system-agents/thread-router.agent.ts +157 -113
  182. package/src/system-agents/title-generator.agent.ts +2 -2
  183. package/src/tools/execution-plan.tool.ts +220 -161
  184. package/src/tools/fetch-webpage.tool.ts +21 -17
  185. package/src/tools/firecrawl-client.ts +16 -6
  186. package/src/tools/index.ts +1 -0
  187. package/src/tools/memory-block.tool.ts +14 -6
  188. package/src/tools/plan-approval.tool.ts +49 -47
  189. package/src/tools/read-file-parts.tool.ts +44 -33
  190. package/src/tools/remember-memory.tool.ts +65 -45
  191. package/src/tools/search-web.tool.ts +26 -22
  192. package/src/tools/search.tool.ts +41 -29
  193. package/src/tools/team-think.tool.ts +124 -83
  194. package/src/tools/user-questions.tool.ts +4 -3
  195. package/src/tools/web-tool-shared.ts +6 -0
  196. package/src/utils/async.ts +17 -23
  197. package/src/utils/crypto.ts +21 -0
  198. package/src/utils/date-time.ts +40 -1
  199. package/src/utils/errors.ts +95 -16
  200. package/src/utils/hono-error-handler.ts +24 -39
  201. package/src/utils/index.ts +2 -1
  202. package/src/utils/null-proto-record.ts +41 -0
  203. package/src/utils/sse-keepalive.ts +124 -21
  204. package/src/workers/bootstrap.ts +186 -51
  205. package/src/workers/memory-consolidation.worker.ts +325 -237
  206. package/src/workers/organization-learning.worker.ts +50 -16
  207. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  208. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  209. package/src/workers/skill-extraction.runner.ts +176 -93
  210. package/src/workers/utils/file-section-chunker.ts +8 -10
  211. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  212. package/src/workers/utils/repomix-file-sections.ts +2 -2
  213. package/src/workers/utils/thread-message-query.ts +97 -38
  214. package/src/workers/worker-utils.ts +56 -31
  215. package/src/config/debug-logger.ts +0 -47
  216. package/src/redis/connection-accessor.ts +0 -26
  217. package/src/runtime/context-compaction-runtime.ts +0 -87
  218. package/src/runtime/social-chat-agent-runner.ts +0 -118
  219. package/src/runtime/social-chat.ts +0 -516
  220. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  221. package/src/services/adaptive-playbook.service.ts +0 -152
  222. package/src/services/artifact-provenance.service.ts +0 -172
  223. package/src/services/chat-attachments.service.ts +0 -17
  224. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  225. package/src/services/execution-plan.service.ts +0 -1118
  226. package/src/services/memory.service.ts +0 -914
  227. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  228. package/src/services/plan-agent-query.service.ts +0 -267
  229. package/src/services/plan-approval.service.ts +0 -83
  230. package/src/services/plan-artifact.service.ts +0 -50
  231. package/src/services/plan-builder.service.ts +0 -67
  232. package/src/services/plan-checkpoint.service.ts +0 -81
  233. package/src/services/plan-completion-side-effects.ts +0 -80
  234. package/src/services/plan-coordination.service.ts +0 -157
  235. package/src/services/plan-cycle.service.ts +0 -284
  236. package/src/services/plan-deadline.service.ts +0 -430
  237. package/src/services/plan-event-delivery.service.ts +0 -166
  238. package/src/services/plan-executor.service.ts +0 -1950
  239. package/src/services/plan-run.service.ts +0 -515
  240. package/src/services/plan-scheduler.service.ts +0 -240
  241. package/src/services/plan-template.service.ts +0 -177
  242. package/src/services/plan-validator.service.ts +0 -818
  243. package/src/services/plan-workspace.service.ts +0 -83
  244. package/src/services/rerank.service.ts +0 -156
  245. package/src/services/thread-message.service.ts +0 -275
  246. package/src/services/thread-plan-registry.service.ts +0 -22
  247. package/src/services/thread-title.service.ts +0 -39
  248. package/src/services/thread-turn-preparation.service.ts +0 -1147
  249. package/src/services/thread-turn.ts +0 -172
  250. package/src/services/thread.service.ts +0 -869
  251. package/src/utils/env.ts +0 -8
  252. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  253. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  254. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  255. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  256. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  257. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  258. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  259. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { isRecord } from '../utils/string'
1
+ import { isRecord } from '../../utils/string'
2
2
 
3
3
  export function readPathValue(source: unknown, path: string): unknown {
4
4
  if (!path.trim()) return source
@@ -5,10 +5,10 @@ import type {
5
5
  SerializableExecutionPlan,
6
6
  } from '@lota-sdk/shared'
7
7
 
8
- import type { RecordIdInput } from '../db/record-id'
9
- import { ensureRecordId } from '../db/record-id'
10
- import { TABLES } from '../db/tables'
11
- import { toDatabaseDateTime } from '../utils/date-time'
8
+ import type { RecordIdInput } from '../../db/record-id'
9
+ import { ensureRecordId } from '../../db/record-id'
10
+ import { TABLES } from '../../db/tables'
11
+ import { toDatabaseDateTime } from '../../utils/date-time'
12
12
 
13
13
  export type PlanRunUpdate = Omit<
14
14
  Partial<PlanRunRecord>,
@@ -0,0 +1,15 @@
1
+ import type { PlanRunRecord } from '@lota-sdk/shared'
2
+
3
+ import type { makePlanRunService } from './plan-run.service'
4
+
5
+ const FULL_PLAN_SNAPSHOT_OPTIONS = {
6
+ includeEvents: true,
7
+ includeArtifacts: true,
8
+ includeApprovals: true,
9
+ includeCheckpoints: true,
10
+ includeValidationIssues: true,
11
+ } as const
12
+
13
+ export function serializeRunFull(planRunService: ReturnType<typeof makePlanRunService>, run: PlanRunRecord) {
14
+ return planRunService.toSerializablePlan(run, FULL_PLAN_SNAPSHOT_OPTIONS)
15
+ }
@@ -0,0 +1,644 @@
1
+ import {
2
+ PlanApprovalSchema,
3
+ PlanArtifactSchema,
4
+ PlanCheckpointSchema,
5
+ PlanEventSchema,
6
+ PlanNodeAttemptSchema,
7
+ PlanNodeRunSchema,
8
+ PlanNodeSpecRecordSchema,
9
+ PlanRunSchema,
10
+ PlanSpecSchema,
11
+ PlanValidationIssueSchema,
12
+ } from '@lota-sdk/shared'
13
+ import type {
14
+ PlanApprovalRecord,
15
+ PlanArtifactRecord,
16
+ PlanCheckpointRecord,
17
+ PlanEventRecord,
18
+ PlanNodeRunRecord,
19
+ PlanNodeRunStatus,
20
+ PlanRunRecord,
21
+ PlanValidationIssueRecord,
22
+ SerializablePlanApproval,
23
+ SerializablePlanArtifact,
24
+ SerializablePlanCheckpoint,
25
+ SerializablePlanEvent,
26
+ SerializablePlanNode,
27
+ SerializablePlanValidationIssue,
28
+ } from '@lota-sdk/shared'
29
+ import { Context, Schema, Effect, Layer } from 'effect'
30
+ import type { RecordId } from 'surrealdb'
31
+
32
+ import type { RecordIdInput } from '../../db/record-id'
33
+ import { ensureRecordId, recordIdToString } from '../../db/record-id'
34
+ import type { SurrealDBService } from '../../db/service'
35
+ import { TABLES } from '../../db/tables'
36
+ import { ServiceError } from '../../effect/errors'
37
+ import { DatabaseServiceTag } from '../../effect/services'
38
+ import { toIsoDateTimeString, toOptionalIsoDateTimeString } from '../../utils/date-time'
39
+
40
+ const ACTIVE_RUN_STATUSES = new Set(['pending-approval', 'running', 'awaiting-human', 'blocked'])
41
+
42
+ function buildProgress(nodeRuns: PlanNodeRunRecord[]) {
43
+ const counts = nodeRuns.reduce(
44
+ (summary, nodeRun) => {
45
+ summary[nodeRun.status] += 1
46
+ return summary
47
+ },
48
+ {
49
+ pending: 0,
50
+ ready: 0,
51
+ running: 0,
52
+ 'awaiting-human': 0,
53
+ completed: 0,
54
+ partial: 0,
55
+ blocked: 0,
56
+ failed: 0,
57
+ skipped: 0,
58
+ scheduled: 0,
59
+ monitoring: 0,
60
+ } satisfies Record<PlanNodeRunStatus, number>,
61
+ )
62
+
63
+ const total = nodeRuns.length
64
+ const completedWork = counts.completed + counts.partial + counts.skipped
65
+
66
+ return {
67
+ total,
68
+ pending: counts.pending,
69
+ ready: counts.ready,
70
+ running: counts.running,
71
+ awaitingHuman: counts['awaiting-human'],
72
+ completed: counts.completed,
73
+ partial: counts.partial,
74
+ blocked: counts.blocked,
75
+ failed: counts.failed,
76
+ skipped: counts.skipped,
77
+ scheduled: counts.scheduled,
78
+ monitoring: counts.monitoring,
79
+ completionRatio: total > 0 ? Number((completedWork / total).toFixed(4)) : undefined,
80
+ }
81
+ }
82
+
83
+ function serializeArtifact(artifact: PlanArtifactRecord): SerializablePlanArtifact {
84
+ return {
85
+ id: recordIdToString(artifact.id, TABLES.PLAN_ARTIFACT),
86
+ nodeId: artifact.nodeId,
87
+ attemptId: recordIdToString(artifact.attemptId, TABLES.PLAN_NODE_ATTEMPT),
88
+ name: artifact.name,
89
+ kind: artifact.kind,
90
+ pointer: artifact.pointer,
91
+ schemaRef: artifact.schemaRef,
92
+ description: artifact.description,
93
+ content: artifact.content,
94
+ payload: artifact.payload,
95
+ publishedArtifactId: artifact.publishedArtifactId
96
+ ? recordIdToString(artifact.publishedArtifactId, TABLES.ARTIFACT)
97
+ : undefined,
98
+ createdAt: toIsoDateTimeString(artifact.createdAt),
99
+ }
100
+ }
101
+
102
+ function serializeValidationIssue(issue: PlanValidationIssueRecord): SerializablePlanValidationIssue {
103
+ return {
104
+ id: recordIdToString(issue.id, TABLES.PLAN_VALIDATION_ISSUE),
105
+ nodeId: issue.nodeId,
106
+ attemptId: issue.attemptId ? recordIdToString(issue.attemptId, TABLES.PLAN_NODE_ATTEMPT) : undefined,
107
+ severity: issue.severity,
108
+ code: issue.code,
109
+ message: issue.message,
110
+ detail: issue.detail,
111
+ createdAt: toIsoDateTimeString(issue.createdAt),
112
+ }
113
+ }
114
+
115
+ function serializeApproval(approval: PlanApprovalRecord): SerializablePlanApproval {
116
+ return {
117
+ id: recordIdToString(approval.id, TABLES.PLAN_APPROVAL),
118
+ nodeId: approval.nodeId,
119
+ status: approval.status,
120
+ presented: approval.presented,
121
+ response: approval.response,
122
+ requestedBy: approval.requestedBy,
123
+ respondedBy: approval.respondedBy,
124
+ approvalMessageId: approval.approvalMessageId,
125
+ comments: approval.comments,
126
+ requiredEdits: [...approval.requiredEdits],
127
+ createdAt: toIsoDateTimeString(approval.createdAt),
128
+ respondedAt: toOptionalIsoDateTimeString(approval.respondedAt),
129
+ }
130
+ }
131
+
132
+ function serializeCheckpoint(checkpoint: PlanCheckpointRecord): SerializablePlanCheckpoint {
133
+ return {
134
+ id: recordIdToString(checkpoint.id, TABLES.PLAN_CHECKPOINT),
135
+ sequence: checkpoint.sequence,
136
+ runStatus: checkpoint.runStatus,
137
+ readyNodeIds: [...checkpoint.readyNodeIds],
138
+ activeNodeIds: [...checkpoint.activeNodeIds],
139
+ artifactIds: checkpoint.artifactIds.map((artifactId) => recordIdToString(artifactId, TABLES.PLAN_ARTIFACT)),
140
+ lastCompletedNodeIds: [...checkpoint.lastCompletedNodeIds],
141
+ snapshot: checkpoint.snapshot,
142
+ createdAt: toIsoDateTimeString(checkpoint.createdAt),
143
+ }
144
+ }
145
+
146
+ function serializeEvent(event: PlanEventRecord): SerializablePlanEvent {
147
+ return {
148
+ id: recordIdToString(event.id, TABLES.PLAN_EVENT),
149
+ nodeId: event.nodeId,
150
+ attemptId: event.attemptId ? recordIdToString(event.attemptId, TABLES.PLAN_NODE_ATTEMPT) : undefined,
151
+ approvalId: event.approvalId ? recordIdToString(event.approvalId, TABLES.PLAN_APPROVAL) : undefined,
152
+ eventType: event.eventType,
153
+ fromStatus: event.fromStatus,
154
+ toStatus: event.toStatus,
155
+ message: event.message,
156
+ detail: event.detail,
157
+ emittedBy: event.emittedBy,
158
+ createdAt: toIsoDateTimeString(event.createdAt),
159
+ }
160
+ }
161
+
162
+ type PlanRunServiceError = ServiceError
163
+
164
+ class PlanRunNotFoundError extends Schema.TaggedErrorClass<PlanRunNotFoundError>()('PlanRunNotFoundError', {
165
+ message: Schema.String,
166
+ entity: Schema.Literals(['plan spec', 'plan run', 'plan node spec', 'plan node run']),
167
+ id: Schema.String,
168
+ }) {}
169
+
170
+ class PlanRunSerializationError extends Schema.TaggedErrorClass<PlanRunSerializationError>()(
171
+ 'PlanRunSerializationError',
172
+ { message: Schema.String },
173
+ ) {}
174
+
175
+ function tryPlanRunPromise<A>(
176
+ message: string,
177
+ thunk: () => PromiseLike<A> | Effect.Effect<A, unknown>,
178
+ ): Effect.Effect<A, PlanRunServiceError> {
179
+ return Effect.suspend(() => {
180
+ try {
181
+ const value = thunk()
182
+ if (Effect.isEffect(value)) {
183
+ return value.pipe(Effect.mapError((cause) => new ServiceError({ message, cause })))
184
+ }
185
+
186
+ return Effect.tryPromise({
187
+ try: () => Promise.resolve(value),
188
+ catch: (cause) => new ServiceError({ message, cause }),
189
+ })
190
+ } catch (cause) {
191
+ return Effect.fail(new ServiceError({ message, cause }))
192
+ }
193
+ })
194
+ }
195
+
196
+ export function makePlanRunService(db: SurrealDBService) {
197
+ const getPlanSpecByIdEffect = (planSpecId: RecordIdInput) =>
198
+ Effect.gen(function* () {
199
+ const planSpecRecordId = ensureRecordId(planSpecId, TABLES.PLAN_SPEC)
200
+ const spec = yield* tryPlanRunPromise(
201
+ `Failed to load plan spec ${recordIdToString(planSpecId, TABLES.PLAN_SPEC)}`,
202
+ () => db.findOne(TABLES.PLAN_SPEC, { id: planSpecRecordId }, PlanSpecSchema),
203
+ )
204
+
205
+ if (!spec) {
206
+ return yield* new PlanRunNotFoundError({
207
+ message: `Plan spec not found: ${recordIdToString(planSpecId, TABLES.PLAN_SPEC)}`,
208
+ entity: 'plan spec',
209
+ id: recordIdToString(planSpecId, TABLES.PLAN_SPEC),
210
+ })
211
+ }
212
+
213
+ return spec
214
+ })
215
+
216
+ const listPlanSpecsByThreadEffect = (threadId: RecordIdInput) =>
217
+ tryPlanRunPromise(`Failed to load plan specs for thread ${recordIdToString(threadId, TABLES.THREAD)}`, () =>
218
+ db.findMany(TABLES.PLAN_SPEC, { threadId: ensureRecordId(threadId, TABLES.THREAD) }, PlanSpecSchema, {
219
+ orderBy: 'createdAt',
220
+ orderDir: 'DESC',
221
+ }),
222
+ )
223
+
224
+ const listRunsBySpecEffect = (planSpecId: RecordIdInput) =>
225
+ tryPlanRunPromise(`Failed to load plan runs for spec ${recordIdToString(planSpecId, TABLES.PLAN_SPEC)}`, () =>
226
+ db.findMany(TABLES.PLAN_RUN, { planSpecId: ensureRecordId(planSpecId, TABLES.PLAN_SPEC) }, PlanRunSchema, {
227
+ orderBy: 'createdAt',
228
+ orderDir: 'DESC',
229
+ }),
230
+ )
231
+
232
+ const listNodeSpecsEffect = (planSpecId: RecordIdInput) =>
233
+ tryPlanRunPromise(`Failed to load plan node specs for spec ${recordIdToString(planSpecId, TABLES.PLAN_SPEC)}`, () =>
234
+ db.findMany(
235
+ TABLES.PLAN_NODE_SPEC,
236
+ { planSpecId: ensureRecordId(planSpecId, TABLES.PLAN_SPEC) },
237
+ PlanNodeSpecRecordSchema,
238
+ { orderBy: 'position', orderDir: 'ASC' },
239
+ ),
240
+ )
241
+
242
+ const getNodeSpecByNodeIdEffect = (planSpecId: RecordIdInput, nodeId: string) =>
243
+ Effect.gen(function* () {
244
+ const nodeSpec = yield* tryPlanRunPromise(`Failed to load plan node spec "${nodeId}"`, () =>
245
+ db.findOne(
246
+ TABLES.PLAN_NODE_SPEC,
247
+ { planSpecId: ensureRecordId(planSpecId, TABLES.PLAN_SPEC), nodeId },
248
+ PlanNodeSpecRecordSchema,
249
+ ),
250
+ )
251
+
252
+ if (!nodeSpec) {
253
+ return yield* new PlanRunNotFoundError({
254
+ message: `Plan node spec "${nodeId}" not found.`,
255
+ entity: 'plan node spec',
256
+ id: nodeId,
257
+ })
258
+ }
259
+
260
+ return nodeSpec
261
+ })
262
+
263
+ const getRunByIdEffect = (runId: RecordIdInput) =>
264
+ Effect.gen(function* () {
265
+ const runRecordId = ensureRecordId(runId, TABLES.PLAN_RUN)
266
+ const run = yield* tryPlanRunPromise(`Failed to load plan run ${recordIdToString(runId, TABLES.PLAN_RUN)}`, () =>
267
+ db.findOne(TABLES.PLAN_RUN, { id: runRecordId }, PlanRunSchema),
268
+ )
269
+
270
+ if (!run) {
271
+ return yield* new PlanRunNotFoundError({
272
+ message: `Plan run not found: ${recordIdToString(runId, TABLES.PLAN_RUN)}`,
273
+ entity: 'plan run',
274
+ id: recordIdToString(runId, TABLES.PLAN_RUN),
275
+ })
276
+ }
277
+
278
+ return run
279
+ })
280
+
281
+ const getActiveRunRecordsEffect = (threadId: RecordIdInput) =>
282
+ Effect.gen(function* () {
283
+ const runs = yield* tryPlanRunPromise(
284
+ `Failed to load active plan runs for thread ${recordIdToString(threadId, TABLES.THREAD)}`,
285
+ () =>
286
+ db.findMany(TABLES.PLAN_RUN, { threadId: ensureRecordId(threadId, TABLES.THREAD) }, PlanRunSchema, {
287
+ orderBy: 'updatedAt',
288
+ orderDir: 'DESC',
289
+ }),
290
+ )
291
+ return runs.filter((run) => ACTIVE_RUN_STATUSES.has(run.status))
292
+ })
293
+
294
+ const getActiveRunRecordEffect = (threadId: RecordIdInput) =>
295
+ Effect.map(getActiveRunRecordsEffect(threadId), (runs) => (runs.length > 0 ? runs[0] : null))
296
+
297
+ const getRunsCreatedInContextEffect = (params: {
298
+ organizationId: RecordIdInput
299
+ sourceThreadId?: RecordIdInput
300
+ createdByAgentId?: string
301
+ statuses?: ReadonlyArray<PlanRunRecord['status']>
302
+ }) =>
303
+ Effect.gen(function* () {
304
+ const filter: Record<string, unknown> = {
305
+ organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
306
+ }
307
+ if (params.sourceThreadId) {
308
+ filter.sourceThreadId = ensureRecordId(params.sourceThreadId, TABLES.THREAD)
309
+ }
310
+ if (params.createdByAgentId) {
311
+ filter.createdByAgentId = params.createdByAgentId
312
+ }
313
+
314
+ const statuses = params.statuses ? new Set(params.statuses) : ACTIVE_RUN_STATUSES
315
+ const runs = yield* tryPlanRunPromise(
316
+ `Failed to load execution plans in context ${recordIdToString(params.organizationId, TABLES.ORGANIZATION)}`,
317
+ () => db.findMany(TABLES.PLAN_RUN, filter, PlanRunSchema, { orderBy: 'updatedAt', orderDir: 'DESC' }),
318
+ )
319
+ return runs.filter((run) => statuses.has(run.status))
320
+ })
321
+
322
+ const listNodeRunsEffect = (runId: RecordIdInput) =>
323
+ tryPlanRunPromise(`Failed to load plan node runs for run ${recordIdToString(runId, TABLES.PLAN_RUN)}`, () =>
324
+ db.findMany(TABLES.PLAN_NODE_RUN, { runId: ensureRecordId(runId, TABLES.PLAN_RUN) }, PlanNodeRunSchema, {
325
+ orderBy: 'nodeId',
326
+ orderDir: 'ASC',
327
+ }),
328
+ )
329
+
330
+ const getNodeRunByNodeIdEffect = (runId: RecordIdInput, nodeId: string) =>
331
+ Effect.gen(function* () {
332
+ const nodeRun = yield* tryPlanRunPromise(`Failed to load plan node run "${nodeId}"`, () =>
333
+ db.findOne(TABLES.PLAN_NODE_RUN, { runId: ensureRecordId(runId, TABLES.PLAN_RUN), nodeId }, PlanNodeRunSchema),
334
+ )
335
+
336
+ if (!nodeRun) {
337
+ return yield* new PlanRunNotFoundError({
338
+ message: `Plan node run "${nodeId}" not found.`,
339
+ entity: 'plan node run',
340
+ id: nodeId,
341
+ })
342
+ }
343
+
344
+ return nodeRun
345
+ })
346
+
347
+ const listArtifactsEffect = (runId: RecordIdInput) =>
348
+ tryPlanRunPromise(`Failed to load plan artifacts for run ${recordIdToString(runId, TABLES.PLAN_RUN)}`, () =>
349
+ db.findMany(TABLES.PLAN_ARTIFACT, { runId: ensureRecordId(runId, TABLES.PLAN_RUN) }, PlanArtifactSchema, {
350
+ orderBy: 'createdAt',
351
+ orderDir: 'ASC',
352
+ }),
353
+ )
354
+
355
+ const listAttemptsEffect = (runId: RecordIdInput) =>
356
+ tryPlanRunPromise(`Failed to load plan attempts for run ${recordIdToString(runId, TABLES.PLAN_RUN)}`, () =>
357
+ db.findMany(TABLES.PLAN_NODE_ATTEMPT, { runId: ensureRecordId(runId, TABLES.PLAN_RUN) }, PlanNodeAttemptSchema, {
358
+ orderBy: 'createdAt',
359
+ orderDir: 'ASC',
360
+ }),
361
+ )
362
+
363
+ const listValidationIssuesEffect = (params: {
364
+ runId?: RecordIdInput
365
+ planSpecId?: RecordIdInput
366
+ attemptId?: RecordIdInput
367
+ }) =>
368
+ Effect.gen(function* () {
369
+ const filter: Record<string, unknown> = {}
370
+ if (params.runId) filter.runId = ensureRecordId(params.runId, TABLES.PLAN_RUN)
371
+ if (params.planSpecId) filter.planSpecId = ensureRecordId(params.planSpecId, TABLES.PLAN_SPEC)
372
+ if (params.attemptId) filter.attemptId = ensureRecordId(params.attemptId, TABLES.PLAN_NODE_ATTEMPT)
373
+
374
+ return yield* tryPlanRunPromise('Failed to load plan validation issues', () =>
375
+ db.findMany(TABLES.PLAN_VALIDATION_ISSUE, filter, PlanValidationIssueSchema, {
376
+ orderBy: 'createdAt',
377
+ orderDir: 'ASC',
378
+ }),
379
+ )
380
+ })
381
+
382
+ const listApprovalsEffect = (runId: RecordIdInput) =>
383
+ tryPlanRunPromise(`Failed to load plan approvals for run ${recordIdToString(runId, TABLES.PLAN_RUN)}`, () =>
384
+ db.findMany(TABLES.PLAN_APPROVAL, { runId: ensureRecordId(runId, TABLES.PLAN_RUN) }, PlanApprovalSchema, {
385
+ orderBy: 'createdAt',
386
+ orderDir: 'ASC',
387
+ }),
388
+ )
389
+
390
+ const getLatestCheckpointEffect = (runId: RecordIdInput) =>
391
+ Effect.gen(function* () {
392
+ const checkpoints = yield* tryPlanRunPromise(
393
+ `Failed to load latest checkpoint for run ${recordIdToString(runId, TABLES.PLAN_RUN)}`,
394
+ () =>
395
+ db.findMany(TABLES.PLAN_CHECKPOINT, { runId: ensureRecordId(runId, TABLES.PLAN_RUN) }, PlanCheckpointSchema, {
396
+ orderBy: 'sequence',
397
+ orderDir: 'DESC',
398
+ limit: 1,
399
+ }),
400
+ )
401
+ return checkpoints.at(0) ?? null
402
+ })
403
+
404
+ const getNextCheckpointSequenceEffect = (runId: RecordIdInput) =>
405
+ Effect.map(getLatestCheckpointEffect(runId), (latestCheckpoint) =>
406
+ latestCheckpoint ? latestCheckpoint.sequence + 1 : 1,
407
+ )
408
+
409
+ const listEventsEffect = (runId: RecordIdInput, limit = 20) =>
410
+ Effect.gen(function* () {
411
+ const events = yield* tryPlanRunPromise(
412
+ `Failed to load plan events for run ${recordIdToString(runId, TABLES.PLAN_RUN)}`,
413
+ () =>
414
+ db.findMany(TABLES.PLAN_EVENT, { runId: ensureRecordId(runId, TABLES.PLAN_RUN) }, PlanEventSchema, {
415
+ orderBy: 'createdAt',
416
+ orderDir: 'DESC',
417
+ limit,
418
+ }),
419
+ )
420
+ return [...events].reverse()
421
+ })
422
+
423
+ const collectLineageArtifactsEffect = (run: PlanRunRecord) =>
424
+ Effect.gen(function* () {
425
+ const collect = (
426
+ currentRunId: RecordId | null,
427
+ depth: number,
428
+ ): Effect.Effect<SerializablePlanArtifact[], PlanRunServiceError | PlanRunNotFoundError> =>
429
+ Effect.gen(function* () {
430
+ if (!currentRunId || depth >= 5) {
431
+ return []
432
+ }
433
+
434
+ const previousRun = yield* tryPlanRunPromise(
435
+ `Failed to load lineage run ${recordIdToString(currentRunId, TABLES.PLAN_RUN)}`,
436
+ () => db.findOne(TABLES.PLAN_RUN, { id: currentRunId }, PlanRunSchema),
437
+ )
438
+ if (!previousRun) {
439
+ return []
440
+ }
441
+
442
+ const [olderArtifacts, artifacts] = yield* Effect.all([
443
+ collect(
444
+ previousRun.replacedRunId ? ensureRecordId(previousRun.replacedRunId, TABLES.PLAN_RUN) : null,
445
+ depth + 1,
446
+ ),
447
+ listArtifactsEffect(previousRun.id),
448
+ ])
449
+
450
+ return [...olderArtifacts, ...artifacts.map(serializeArtifact)]
451
+ })
452
+
453
+ return yield* collect(run.replacedRunId ? ensureRecordId(run.replacedRunId, TABLES.PLAN_RUN) : null, 0)
454
+ })
455
+
456
+ const toSerializablePlanEffect = (
457
+ run: PlanRunRecord,
458
+ options?: {
459
+ includeArtifacts?: boolean
460
+ includeApprovals?: boolean
461
+ includeCheckpoints?: boolean
462
+ includeEvents?: boolean
463
+ includeValidationIssues?: boolean
464
+ slim?: boolean
465
+ },
466
+ ) =>
467
+ Effect.gen(function* () {
468
+ const slim = options?.slim === true
469
+ const spec = yield* getPlanSpecByIdEffect(run.planSpecId)
470
+ const [
471
+ nodeSpecs,
472
+ nodeRuns,
473
+ artifacts,
474
+ lineageArtifacts,
475
+ approvals,
476
+ validationIssues,
477
+ latestCheckpoint,
478
+ recentEvents,
479
+ ] = yield* Effect.all([
480
+ listNodeSpecsEffect(spec.id),
481
+ listNodeRunsEffect(run.id),
482
+ options?.includeArtifacts === false ? Effect.succeed<PlanArtifactRecord[]>([]) : listArtifactsEffect(run.id),
483
+ options?.includeArtifacts === false || slim
484
+ ? Effect.succeed<SerializablePlanArtifact[]>([])
485
+ : collectLineageArtifactsEffect(run),
486
+ options?.includeApprovals === false || slim
487
+ ? Effect.succeed<PlanApprovalRecord[]>([])
488
+ : listApprovalsEffect(run.id),
489
+ options?.includeValidationIssues === false || slim
490
+ ? Effect.succeed<PlanValidationIssueRecord[]>([])
491
+ : listValidationIssuesEffect({ runId: run.id, planSpecId: spec.id }),
492
+ options?.includeCheckpoints
493
+ ? getLatestCheckpointEffect(run.id)
494
+ : Effect.succeed<PlanCheckpointRecord | null>(null),
495
+ options?.includeEvents === false
496
+ ? Effect.succeed<PlanEventRecord[]>([])
497
+ : listEventsEffect(run.id, slim ? 5 : 20),
498
+ ])
499
+
500
+ const nodeRunsById = new Map(nodeRuns.map((nodeRun) => [nodeRun.nodeId, nodeRun]))
501
+ const activeNodeIds = new Set(run.currentNodeId ? [run.currentNodeId] : [])
502
+ const readyNodeIds = new Set(run.readyNodeIds)
503
+
504
+ const nodes: SerializablePlanNode[] = []
505
+ for (const nodeSpec of nodeSpecs) {
506
+ const nodeRun = nodeRunsById.get(nodeSpec.nodeId)
507
+ if (!nodeRun) {
508
+ return yield* new PlanRunSerializationError({
509
+ message: `Plan run ${recordIdToString(run.id, TABLES.PLAN_RUN)} is missing node run "${nodeSpec.nodeId}".`,
510
+ })
511
+ }
512
+
513
+ const isActiveOrReady = activeNodeIds.has(nodeSpec.nodeId) || readyNodeIds.has(nodeSpec.nodeId)
514
+
515
+ if (slim && !isActiveOrReady) {
516
+ nodes.push({
517
+ id: nodeSpec.nodeId,
518
+ type: nodeSpec.type,
519
+ label: nodeSpec.label,
520
+ owner: { executorType: nodeSpec.owner.executorType, ref: nodeSpec.owner.ref },
521
+ objective: nodeSpec.objective,
522
+ status: nodeRun.status,
523
+ upstreamNodeIds: [...nodeSpec.upstreamNodeIds],
524
+ downstreamNodeIds: [...nodeSpec.downstreamNodeIds],
525
+ ...(nodeRun.completedAt ? { completedAt: toOptionalIsoDateTimeString(nodeRun.completedAt) } : {}),
526
+ } as SerializablePlanNode)
527
+ continue
528
+ }
529
+
530
+ nodes.push({
531
+ id: nodeSpec.nodeId,
532
+ type: nodeSpec.type,
533
+ label: nodeSpec.label,
534
+ owner: nodeSpec.owner,
535
+ objective: nodeSpec.objective,
536
+ instructions: nodeSpec.instructions,
537
+ inputSchemaRef: nodeSpec.inputSchemaRef,
538
+ outputSchemaRef: nodeSpec.outputSchemaRef,
539
+ deliverables: [...nodeSpec.deliverables],
540
+ successCriteria: [...nodeSpec.successCriteria],
541
+ completionChecks: [...nodeSpec.completionChecks],
542
+ retryPolicy: { ...nodeSpec.retryPolicy, retryOn: [...nodeSpec.retryPolicy.retryOn] },
543
+ failurePolicy: [...nodeSpec.failurePolicy],
544
+ timeoutMs: nodeSpec.timeoutMs,
545
+ toolPolicy: { allow: [...nodeSpec.toolPolicy.allow], deny: [...nodeSpec.toolPolicy.deny] },
546
+ contextPolicy: {
547
+ retrievalScopes: [...nodeSpec.contextPolicy.retrievalScopes],
548
+ attachmentPolicy: nodeSpec.contextPolicy.attachmentPolicy,
549
+ webPolicy: nodeSpec.contextPolicy.webPolicy,
550
+ },
551
+ executionVisibility: nodeSpec.executionVisibility,
552
+ schedule: nodeSpec.schedule,
553
+ deadline: nodeSpec.deadline,
554
+ escalation: nodeSpec.escalation,
555
+ monitoringConfig: nodeSpec.monitoringConfig,
556
+ delayAfterPredecessorMs: nodeSpec.delayAfterPredecessorMs,
557
+ deliberationConfig: nodeSpec.deliberationConfig,
558
+ status: nodeRun.status,
559
+ attemptCount: nodeRun.attemptCount,
560
+ retryCount: nodeRun.retryCount,
561
+ resolvedInput: nodeRun.resolvedInput,
562
+ latestStructuredOutput: nodeRun.latestStructuredOutput,
563
+ latestNotes: nodeRun.latestNotes,
564
+ blockedReason: nodeRun.blockedReason,
565
+ failureClass: nodeRun.failureClass,
566
+ upstreamNodeIds: [...nodeSpec.upstreamNodeIds],
567
+ downstreamNodeIds: [...nodeSpec.downstreamNodeIds],
568
+ readyAt: toOptionalIsoDateTimeString(nodeRun.readyAt),
569
+ startedAt: toOptionalIsoDateTimeString(nodeRun.startedAt),
570
+ completedAt: toOptionalIsoDateTimeString(nodeRun.completedAt),
571
+ })
572
+ }
573
+
574
+ return {
575
+ specId: recordIdToString(spec.id, TABLES.PLAN_SPEC),
576
+ runId: recordIdToString(run.id, TABLES.PLAN_RUN),
577
+ threadId: recordIdToString(run.threadId, TABLES.THREAD),
578
+ sourceThreadId: run.sourceThreadId ? recordIdToString(run.sourceThreadId, TABLES.THREAD) : undefined,
579
+ organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
580
+ title: spec.title,
581
+ objective: spec.objective,
582
+ version: spec.version,
583
+ status: run.status,
584
+ leadAgentId: run.leadAgentId,
585
+ createdByAgentId: run.createdByAgentId,
586
+ defaultExecutionVisibility: spec.defaultExecutionVisibility,
587
+ executionMode: spec.executionMode,
588
+ schemaRegistry: slim ? {} : structuredClone(spec.schemaRegistry),
589
+ entryNodeIds: [...spec.entryNodeIds],
590
+ edges: [...spec.edges],
591
+ schedule: spec.schedule,
592
+ activeNodeIds: run.currentNodeId ? [run.currentNodeId] : [],
593
+ readyNodeIds: [...run.readyNodeIds],
594
+ waitingNodeId: run.waitingNodeId,
595
+ replacedRunId: run.replacedRunId ? recordIdToString(run.replacedRunId, TABLES.PLAN_RUN) : undefined,
596
+ failureCount: run.failureCount,
597
+ startedAt: toOptionalIsoDateTimeString(run.startedAt),
598
+ completedAt: toOptionalIsoDateTimeString(run.completedAt),
599
+ progress: buildProgress(nodeRuns),
600
+ nodes,
601
+ artifacts: artifacts.map(serializeArtifact),
602
+ lineageArtifacts,
603
+ validationIssues: validationIssues.map(serializeValidationIssue),
604
+ approvals: approvals.map(serializeApproval),
605
+ latestCheckpoint: latestCheckpoint ? serializeCheckpoint(latestCheckpoint) : null,
606
+ recentEvents: recentEvents.map(serializeEvent),
607
+ }
608
+ })
609
+
610
+ return {
611
+ getPlanSpecById: getPlanSpecByIdEffect,
612
+ listPlanSpecsByThread: listPlanSpecsByThreadEffect,
613
+ listRunsBySpec: listRunsBySpecEffect,
614
+ listNodeSpecs: listNodeSpecsEffect,
615
+ getNodeSpecByNodeId: getNodeSpecByNodeIdEffect,
616
+ getRunById: getRunByIdEffect,
617
+ getActiveRunRecord: getActiveRunRecordEffect,
618
+ getActiveRunRecords: getActiveRunRecordsEffect,
619
+ getRunsCreatedInContext: getRunsCreatedInContextEffect,
620
+ listNodeRuns: listNodeRunsEffect,
621
+ getNodeRunByNodeId: getNodeRunByNodeIdEffect,
622
+ listArtifacts: listArtifactsEffect,
623
+ listAttempts: listAttemptsEffect,
624
+ listValidationIssues: listValidationIssuesEffect,
625
+ listApprovals: listApprovalsEffect,
626
+ getLatestCheckpoint: getLatestCheckpointEffect,
627
+ getNextCheckpointSequence: getNextCheckpointSequenceEffect,
628
+ listEvents: listEventsEffect,
629
+ toSerializablePlan: toSerializablePlanEffect,
630
+ collectLineageArtifacts: collectLineageArtifactsEffect,
631
+ }
632
+ }
633
+
634
+ export class PlanRunServiceTag extends Context.Service<PlanRunServiceTag, ReturnType<typeof makePlanRunService>>()(
635
+ 'PlanRunService',
636
+ ) {}
637
+
638
+ export const PlanRunServiceLive = Layer.effect(
639
+ PlanRunServiceTag,
640
+ Effect.gen(function* () {
641
+ const db = yield* DatabaseServiceTag
642
+ return makePlanRunService(db)
643
+ }),
644
+ )