@lota-sdk/core 0.4.8 → 0.4.10

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 (272) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +96 -22
  3. package/src/ai-gateway/ai-gateway.ts +766 -223
  4. package/src/config/agent-defaults.ts +189 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/constants.ts +8 -2
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +299 -19
  10. package/src/config/thread-defaults.ts +40 -20
  11. package/src/create-runtime.ts +200 -449
  12. package/src/db/base.service.ts +52 -28
  13. package/src/db/cursor-pagination.ts +71 -30
  14. package/src/db/memory-query-builder.ts +2 -1
  15. package/src/db/memory-store.helpers.ts +4 -7
  16. package/src/db/memory-store.ts +868 -601
  17. package/src/db/memory.ts +396 -280
  18. package/src/db/record-id.ts +32 -10
  19. package/src/db/schema-fingerprint.ts +30 -12
  20. package/src/db/service-normalization.ts +288 -0
  21. package/src/db/service.ts +912 -779
  22. package/src/db/startup.ts +153 -68
  23. package/src/db/transaction-conflict.ts +15 -0
  24. package/src/effect/awaitable-effect.ts +96 -0
  25. package/src/effect/errors.ts +121 -0
  26. package/src/effect/helpers.ts +123 -0
  27. package/src/effect/index.ts +24 -0
  28. package/src/effect/layers.ts +238 -0
  29. package/src/effect/runtime-ref.ts +25 -0
  30. package/src/effect/runtime.ts +46 -0
  31. package/src/effect/services.ts +61 -0
  32. package/src/effect/zod.ts +43 -0
  33. package/src/embeddings/provider.ts +128 -83
  34. package/src/index.ts +48 -1
  35. package/src/openrouter/direct-provider.ts +11 -35
  36. package/src/queues/autonomous-job.queue.ts +117 -73
  37. package/src/queues/context-compaction.queue.ts +50 -17
  38. package/src/queues/delayed-node-promotion.queue.ts +46 -17
  39. package/src/queues/document-processor.queue.ts +52 -77
  40. package/src/queues/memory-consolidation.queue.ts +47 -32
  41. package/src/queues/organization-learning.queue.ts +26 -4
  42. package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
  43. package/src/queues/plan-scheduler.queue.ts +97 -33
  44. package/src/queues/post-chat-memory.queue.ts +56 -26
  45. package/src/queues/queue-factory.ts +227 -59
  46. package/src/queues/standalone-worker.ts +39 -0
  47. package/src/queues/title-generation.queue.ts +45 -11
  48. package/src/redis/connection.ts +182 -113
  49. package/src/redis/index.ts +6 -8
  50. package/src/redis/org-memory-lock.ts +60 -27
  51. package/src/redis/redis-lease-lock.ts +200 -121
  52. package/src/redis/runtime-connection.ts +20 -0
  53. package/src/redis/stream-context.ts +92 -46
  54. package/src/runtime/agent-identity-overrides.ts +2 -2
  55. package/src/runtime/agent-runtime-policy.ts +5 -2
  56. package/src/runtime/agent-stream-helpers.ts +24 -9
  57. package/src/runtime/chat-run-orchestration.ts +102 -19
  58. package/src/runtime/chat-run-registry.ts +36 -2
  59. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  60. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +161 -94
  61. package/src/runtime/domain-layer.ts +192 -0
  62. package/src/runtime/execution-plan-visibility.ts +2 -2
  63. package/src/runtime/execution-plan.ts +42 -15
  64. package/src/runtime/graph-designer.ts +16 -4
  65. package/src/runtime/helper-model.ts +139 -48
  66. package/src/runtime/index.ts +7 -8
  67. package/src/runtime/indexed-repositories-policy.ts +3 -3
  68. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -36
  69. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  70. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +54 -67
  71. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  72. package/src/runtime/memory/memory-scope.ts +53 -0
  73. package/src/runtime/plugin-resolution.ts +124 -25
  74. package/src/runtime/plugin-types.ts +9 -1
  75. package/src/runtime/post-turn-side-effects.ts +177 -130
  76. package/src/runtime/retrieval-adapters.ts +40 -6
  77. package/src/runtime/runtime-accessors.ts +92 -0
  78. package/src/runtime/runtime-config.ts +150 -61
  79. package/src/runtime/runtime-extensions.ts +23 -25
  80. package/src/runtime/runtime-lifecycle.ts +124 -0
  81. package/src/runtime/runtime-services.ts +386 -0
  82. package/src/runtime/runtime-token.ts +47 -0
  83. package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
  84. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
  85. package/src/runtime/social-chat/social-chat.ts +630 -0
  86. package/src/runtime/specialist-runner.ts +36 -10
  87. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -0
  88. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  89. package/src/runtime/thread-chat-helpers.ts +2 -2
  90. package/src/runtime/thread-plan-turn.ts +2 -1
  91. package/src/runtime/thread-turn-context.ts +183 -111
  92. package/src/runtime/turn-lifecycle.ts +93 -27
  93. package/src/services/agent-activity.service.ts +287 -203
  94. package/src/services/agent-executor.service.ts +253 -149
  95. package/src/services/artifact.service.ts +231 -149
  96. package/src/services/attachment.service.ts +171 -115
  97. package/src/services/autonomous-job.service.ts +890 -491
  98. package/src/services/background-work.service.ts +54 -0
  99. package/src/services/chat-run-registry.service.ts +13 -1
  100. package/src/services/context-compaction.service.ts +136 -86
  101. package/src/services/document-chunk.service.ts +151 -88
  102. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  103. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  104. package/src/services/execution-plan/execution-plan-graph.ts +278 -0
  105. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  106. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  107. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  108. package/src/services/feedback-loop.service.ts +132 -76
  109. package/src/services/global-orchestrator.service.ts +101 -168
  110. package/src/services/graph-full-routing.ts +193 -0
  111. package/src/services/index.ts +19 -21
  112. package/src/services/institutional-memory.service.ts +213 -125
  113. package/src/services/learned-skill.service.ts +368 -260
  114. package/src/services/memory/memory-conversation.ts +95 -0
  115. package/src/services/memory/memory-errors.ts +27 -0
  116. package/src/services/memory/memory-org-memory.ts +50 -0
  117. package/src/services/memory/memory-preseeded.ts +86 -0
  118. package/src/services/memory/memory-rerank.ts +297 -0
  119. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
  120. package/src/services/memory/memory.service.ts +674 -0
  121. package/src/services/memory/rerank.service.ts +201 -0
  122. package/src/services/monitoring-window.service.ts +92 -70
  123. package/src/services/mutating-approval.service.ts +62 -53
  124. package/src/services/node-workspace.service.ts +141 -98
  125. package/src/services/notification.service.ts +29 -16
  126. package/src/services/organization-member.service.ts +120 -66
  127. package/src/services/organization.service.ts +153 -77
  128. package/src/services/ownership-dispatcher.service.ts +456 -263
  129. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  130. package/src/services/plan/plan-agent-query.service.ts +322 -0
  131. package/src/services/{plan-approval.service.ts → plan/plan-approval.service.ts} +45 -22
  132. package/src/services/plan/plan-artifact.service.ts +60 -0
  133. package/src/services/plan/plan-builder.service.ts +76 -0
  134. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  135. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  136. package/src/services/plan/plan-completion-side-effects.ts +169 -0
  137. package/src/services/plan/plan-coordination.service.ts +181 -0
  138. package/src/services/plan/plan-cycle.service.ts +405 -0
  139. package/src/services/plan/plan-deadline.service.ts +533 -0
  140. package/src/services/plan/plan-event-delivery.service.ts +266 -0
  141. package/src/services/plan/plan-executor-context.ts +35 -0
  142. package/src/services/plan/plan-executor-graph.ts +522 -0
  143. package/src/services/plan/plan-executor-helpers.ts +307 -0
  144. package/src/services/plan/plan-executor-persistence.ts +209 -0
  145. package/src/services/plan/plan-executor.service.ts +1737 -0
  146. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  147. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  148. package/src/services/plan/plan-run-serialization.ts +15 -0
  149. package/src/services/plan/plan-run.service.ts +637 -0
  150. package/src/services/plan/plan-scheduler.service.ts +379 -0
  151. package/src/services/plan/plan-template.service.ts +224 -0
  152. package/src/services/plan/plan-transaction-events.ts +36 -0
  153. package/src/services/plan/plan-validator.service.ts +907 -0
  154. package/src/services/plan/plan-workspace.service.ts +131 -0
  155. package/src/services/plugin-executor.service.ts +102 -68
  156. package/src/services/quality-metrics.service.ts +112 -94
  157. package/src/services/queue-job.service.ts +288 -231
  158. package/src/services/recent-activity-title.service.ts +73 -36
  159. package/src/services/recent-activity.service.ts +274 -259
  160. package/src/services/skill-resolver.service.ts +38 -12
  161. package/src/services/social-chat-history.service.ts +190 -122
  162. package/src/services/system-executor.service.ts +96 -61
  163. package/src/services/thread/thread-active-run.ts +203 -0
  164. package/src/services/thread/thread-bootstrap.ts +385 -0
  165. package/src/services/thread/thread-listing.ts +199 -0
  166. package/src/services/thread/thread-memory-block.ts +130 -0
  167. package/src/services/thread/thread-message.service.ts +379 -0
  168. package/src/services/thread/thread-record-store.ts +155 -0
  169. package/src/services/thread/thread-title.service.ts +74 -0
  170. package/src/services/thread/thread-turn-execution.ts +280 -0
  171. package/src/services/thread/thread-turn-message-context.ts +73 -0
  172. package/src/services/thread/thread-turn-preparation.service.ts +1148 -0
  173. package/src/services/thread/thread-turn-streaming.ts +403 -0
  174. package/src/services/thread/thread-turn-tracing.ts +35 -0
  175. package/src/services/thread/thread-turn.ts +376 -0
  176. package/src/services/thread/thread.service.ts +344 -0
  177. package/src/services/user.service.ts +82 -32
  178. package/src/services/write-intent-validator.service.ts +63 -51
  179. package/src/storage/attachment-parser.ts +69 -27
  180. package/src/storage/attachment-storage.service.ts +334 -275
  181. package/src/storage/generated-document-storage.service.ts +66 -34
  182. package/src/system-agents/agent-result.ts +3 -1
  183. package/src/system-agents/context-compaction.agent.ts +3 -3
  184. package/src/system-agents/delegated-agent-factory.ts +159 -90
  185. package/src/system-agents/helper-agent-options.ts +1 -1
  186. package/src/system-agents/memory-reranker.agent.ts +3 -3
  187. package/src/system-agents/memory.agent.ts +3 -3
  188. package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
  189. package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
  190. package/src/system-agents/skill-extractor.agent.ts +3 -3
  191. package/src/system-agents/skill-manager.agent.ts +3 -3
  192. package/src/system-agents/thread-router.agent.ts +157 -113
  193. package/src/system-agents/title-generator.agent.ts +3 -3
  194. package/src/tools/execution-plan.tool.ts +241 -171
  195. package/src/tools/fetch-webpage.tool.ts +29 -18
  196. package/src/tools/firecrawl-client.ts +26 -6
  197. package/src/tools/index.ts +1 -0
  198. package/src/tools/memory-block.tool.ts +14 -6
  199. package/src/tools/plan-approval.tool.ts +57 -47
  200. package/src/tools/read-file-parts.tool.ts +44 -33
  201. package/src/tools/remember-memory.tool.ts +65 -45
  202. package/src/tools/search-web.tool.ts +33 -22
  203. package/src/tools/search.tool.ts +41 -29
  204. package/src/tools/team-think.tool.ts +125 -84
  205. package/src/tools/user-questions.tool.ts +4 -3
  206. package/src/tools/web-tool-shared.ts +6 -0
  207. package/src/utils/async.ts +25 -22
  208. package/src/utils/crypto.ts +21 -0
  209. package/src/utils/date-time.ts +40 -1
  210. package/src/utils/errors.ts +111 -20
  211. package/src/utils/hono-error-handler.ts +24 -39
  212. package/src/utils/index.ts +2 -1
  213. package/src/utils/null-proto-record.ts +41 -0
  214. package/src/utils/sse-keepalive.ts +124 -21
  215. package/src/workers/bootstrap.ts +164 -52
  216. package/src/workers/memory-consolidation.worker.ts +325 -237
  217. package/src/workers/organization-learning.worker.ts +50 -16
  218. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  219. package/src/workers/regular-chat-memory-digest.runner.ts +185 -114
  220. package/src/workers/skill-extraction.runner.ts +176 -93
  221. package/src/workers/utils/file-section-chunker.ts +8 -10
  222. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  223. package/src/workers/utils/repomix-file-sections.ts +2 -2
  224. package/src/workers/utils/thread-message-query.ts +97 -38
  225. package/src/workers/worker-utils.ts +74 -31
  226. package/src/config/debug-logger.ts +0 -47
  227. package/src/config/search.ts +0 -3
  228. package/src/redis/connection-accessor.ts +0 -26
  229. package/src/runtime/agent-types.ts +0 -1
  230. package/src/runtime/context-compaction-runtime.ts +0 -87
  231. package/src/runtime/memory-scope.ts +0 -43
  232. package/src/runtime/social-chat-agent-runner.ts +0 -118
  233. package/src/runtime/social-chat.ts +0 -516
  234. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  235. package/src/services/adaptive-playbook.service.ts +0 -152
  236. package/src/services/artifact-provenance.service.ts +0 -172
  237. package/src/services/chat-attachments.service.ts +0 -17
  238. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  239. package/src/services/execution-plan.service.ts +0 -1118
  240. package/src/services/memory.service.ts +0 -914
  241. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  242. package/src/services/plan-agent-query.service.ts +0 -267
  243. package/src/services/plan-artifact.service.ts +0 -50
  244. package/src/services/plan-builder.service.ts +0 -67
  245. package/src/services/plan-checkpoint.service.ts +0 -81
  246. package/src/services/plan-completion-side-effects.ts +0 -80
  247. package/src/services/plan-coordination.service.ts +0 -157
  248. package/src/services/plan-cycle.service.ts +0 -284
  249. package/src/services/plan-deadline.service.ts +0 -430
  250. package/src/services/plan-event-delivery.service.ts +0 -166
  251. package/src/services/plan-executor.service.ts +0 -1950
  252. package/src/services/plan-run.service.ts +0 -515
  253. package/src/services/plan-scheduler.service.ts +0 -240
  254. package/src/services/plan-template.service.ts +0 -177
  255. package/src/services/plan-validator.service.ts +0 -818
  256. package/src/services/plan-workspace.service.ts +0 -83
  257. package/src/services/rerank.service.ts +0 -156
  258. package/src/services/thread-message.service.ts +0 -275
  259. package/src/services/thread-plan-registry.service.ts +0 -22
  260. package/src/services/thread-title.service.ts +0 -39
  261. package/src/services/thread-turn-preparation.service.ts +0 -1147
  262. package/src/services/thread-turn.ts +0 -172
  263. package/src/services/thread.service.ts +0 -869
  264. package/src/utils/env.ts +0 -8
  265. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  266. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  267. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  268. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  269. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  270. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  271. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  272. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -0,0 +1,522 @@
1
+ import type {
2
+ PlanEventRecord,
3
+ PlanNodeRunRecord,
4
+ PlanNodeSpecRecord,
5
+ PlanRunRecord,
6
+ PlanSpecRecord,
7
+ } from '@lota-sdk/shared'
8
+ import { PlanNodeRunSchema } from '@lota-sdk/shared'
9
+ import { Effect, Match, Schema } from 'effect'
10
+ import type { z } from 'zod'
11
+
12
+ import type { RecordIdInput } from '../../db/record-id'
13
+ import { ensureRecordId, recordIdToString } from '../../db/record-id'
14
+ import type { DatabaseTransaction } from '../../db/service'
15
+ import { TABLES } from '../../db/tables'
16
+ import { NotFoundError } from '../../effect/errors'
17
+ import { runPromise } from '../../effect/runtime'
18
+ import { nowDate } from '../../utils/date-time'
19
+ import type { PlanExecutorContext } from './plan-executor-context'
20
+ import {
21
+ buildNodeContext,
22
+ buildResolvedInput,
23
+ evaluateCondition,
24
+ isHumanNodeType,
25
+ isStructuralNodeType,
26
+ isSuccessfulTerminalStatus,
27
+ toNodeRunData,
28
+ } from './plan-executor-helpers'
29
+ import { emitEvent, replaceRun } from './plan-executor-persistence'
30
+
31
+ const delayedNodePromotionQueueModule = Effect.tryPromise(() => import('../../queues/delayed-node-promotion.queue'))
32
+
33
+ class PlanExecutorGraphError extends Schema.TaggedErrorClass<PlanExecutorGraphError>()('PlanExecutorGraphError', {
34
+ message: Schema.String,
35
+ cause: Schema.optional(Schema.Defect),
36
+ }) {}
37
+
38
+ function parseRowOrFail<T>(
39
+ schema: z.ZodType<T>,
40
+ value: unknown,
41
+ operation: string,
42
+ ): Effect.Effect<T, PlanExecutorGraphError> {
43
+ return Effect.try({
44
+ try: () => schema.parse(value),
45
+ catch: (cause) => new PlanExecutorGraphError({ message: `Failed to parse ${operation} row`, cause }),
46
+ })
47
+ }
48
+
49
+ export function syncRunGraph(
50
+ context: PlanExecutorContext,
51
+ params: {
52
+ tx: DatabaseTransaction
53
+ run: PlanRunRecord
54
+ spec: PlanSpecRecord
55
+ nodeSpecs: PlanNodeSpecRecord[]
56
+ nodeRuns: PlanNodeRunRecord[]
57
+ artifacts: Array<{
58
+ id: RecordIdInput
59
+ nodeId: string
60
+ name: string
61
+ kind: string
62
+ pointer: string
63
+ schemaRef?: string
64
+ payload?: unknown
65
+ }>
66
+ emittedBy: string
67
+ capturedEvents?: PlanEventRecord[]
68
+ },
69
+ ): Promise<{
70
+ run: PlanRunRecord
71
+ nodeRuns: PlanNodeRunRecord[]
72
+ artifacts: Array<{
73
+ id: RecordIdInput
74
+ nodeId: string
75
+ name: string
76
+ kind: string
77
+ pointer: string
78
+ schemaRef?: string
79
+ payload?: unknown
80
+ }>
81
+ }> {
82
+ return runPromise(
83
+ Effect.gen(function* () {
84
+ const { planApprovalService, planCoordinationService, planSchedulerService } = context
85
+ const currentTime = nowDate()
86
+ let currentRun = params.run
87
+ let currentNodeRuns = [...params.nodeRuns]
88
+ const currentArtifacts = [...params.artifacts]
89
+ const sortedNodeSpecs = [...params.nodeSpecs].sort((left, right) => left.position - right.position)
90
+ const dependencies = params.spec.dependencies
91
+
92
+ const updateNodeRun = (nodeRun: PlanNodeRunRecord, patch: Parameters<typeof toNodeRunData>[1]) =>
93
+ params.tx
94
+ .update(ensureRecordId(nodeRun.id, TABLES.PLAN_NODE_RUN))
95
+ .content(toNodeRunData(nodeRun, patch))
96
+ .output('after')
97
+
98
+ const replaceNodeRun = (nextNodeRun: PlanNodeRunRecord) => {
99
+ currentNodeRuns = currentNodeRuns.map((candidate) =>
100
+ candidate.nodeId === nextNodeRun.nodeId ? nextNodeRun : candidate,
101
+ )
102
+ }
103
+
104
+ const getNodeRunsById = () => new Map(currentNodeRuns.map((nodeRun) => [nodeRun.nodeId, nodeRun]))
105
+ const getArtifactsByNodeId = () =>
106
+ currentArtifacts.reduce((groups, artifact) => {
107
+ const list = groups.get(artifact.nodeId) ?? []
108
+ list.push(artifact)
109
+ groups.set(artifact.nodeId, list)
110
+ return groups
111
+ }, new Map<string, typeof currentArtifacts>())
112
+
113
+ if (dependencies && dependencies.length > 0) {
114
+ const { unresolved } = yield* planCoordinationService.resolveDependencies({
115
+ dependencies,
116
+ threadId: recordIdToString(params.spec.threadId, TABLES.THREAD),
117
+ })
118
+ if (unresolved.length > 0) {
119
+ currentRun = yield* replaceRun(params.tx, currentRun, { status: 'blocked', readyNodeIds: [] })
120
+ yield* emitEvent({
121
+ tx: params.tx,
122
+ run: currentRun,
123
+ spec: params.spec,
124
+ eventType: 'run-status-changed',
125
+ fromStatus: params.run.status,
126
+ toStatus: currentRun.status,
127
+ message: `Run blocked: unresolved cross-plan dependencies (${unresolved.map((d) => d.sourcePlanSpecId).join(', ')}).`,
128
+ emittedBy: params.emittedBy,
129
+ capturedEvents: params.capturedEvents,
130
+ })
131
+ return { run: currentRun, nodeRuns: currentNodeRuns, artifacts: currentArtifacts }
132
+ }
133
+ }
134
+
135
+ let changed = true
136
+ while (changed) {
137
+ changed = false
138
+ const nodeRunsById = getNodeRunsById()
139
+ const artifactsByNodeId = getArtifactsByNodeId()
140
+
141
+ for (const nodeSpec of sortedNodeSpecs) {
142
+ const nodeRun = nodeRunsById.get(nodeSpec.nodeId)
143
+ if (!nodeRun || nodeRun.status !== 'pending') continue
144
+
145
+ const upstreamRuns = nodeSpec.upstreamNodeIds
146
+ .map((nodeId) => nodeRunsById.get(nodeId))
147
+ .filter(Boolean) as PlanNodeRunRecord[]
148
+ if (
149
+ nodeSpec.upstreamNodeIds.length > 0 &&
150
+ !upstreamRuns.every((upstreamRun) => isSuccessfulTerminalStatus(upstreamRun.status))
151
+ ) {
152
+ continue
153
+ }
154
+
155
+ const activeIncomingEdges: typeof params.spec.edges = []
156
+ for (const edge of params.spec.edges) {
157
+ if (edge.target !== nodeSpec.nodeId) continue
158
+ const sourceRun = nodeRunsById.get(edge.source)
159
+ if (!sourceRun) continue
160
+ const context = buildNodeContext({
161
+ nodeRun: sourceRun,
162
+ artifacts: artifactsByNodeId.get(edge.source) ?? [],
163
+ })
164
+ if (yield* evaluateCondition(edge.when, context)) {
165
+ activeIncomingEdges.push(edge)
166
+ }
167
+ }
168
+
169
+ if (nodeSpec.upstreamNodeIds.length > 0 && activeIncomingEdges.length === 0) {
170
+ const skippedNodeRunRow = yield* updateNodeRun(nodeRun, {
171
+ status: 'skipped',
172
+ completedAt: currentTime,
173
+ blockedReason: null,
174
+ failureClass: null,
175
+ })
176
+ const skippedNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, skippedNodeRunRow, 'plan node run')
177
+ replaceNodeRun(skippedNodeRun)
178
+ yield* emitEvent({
179
+ tx: params.tx,
180
+ run: currentRun,
181
+ spec: params.spec,
182
+ nodeId: skippedNodeRun.nodeId,
183
+ eventType: 'node-skipped',
184
+ fromStatus: nodeRun.status,
185
+ toStatus: skippedNodeRun.status,
186
+ message: `Node "${nodeSpec.label}" was skipped because no inbound branch was activated.`,
187
+ emittedBy: params.emittedBy,
188
+ capturedEvents: params.capturedEvents,
189
+ })
190
+ changed = true
191
+ continue
192
+ }
193
+
194
+ const resolvedInput = yield* buildResolvedInput({
195
+ spec: params.spec,
196
+ nodeSpec,
197
+ nodeRunsById,
198
+ artifactsByNodeId,
199
+ })
200
+
201
+ const nodeSchedule = nodeSpec.schedule
202
+ const hasNonImmediateSchedule = nodeSchedule && nodeSchedule.type !== 'immediate'
203
+
204
+ if (hasNonImmediateSchedule) {
205
+ const scheduledNodeRunRow = yield* updateNodeRun(nodeRun, {
206
+ status: 'scheduled',
207
+ resolvedInput,
208
+ scheduledAt: currentTime,
209
+ })
210
+ const scheduledNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, scheduledNodeRunRow, 'plan node run')
211
+ replaceNodeRun(scheduledNodeRun)
212
+ yield* planSchedulerService.createSchedule({
213
+ organizationId: currentRun.organizationId,
214
+ threadId: currentRun.threadId,
215
+ planSpecId: params.spec.id,
216
+ runId: currentRun.id,
217
+ nodeId: nodeSpec.nodeId,
218
+ scheduleSpec: nodeSchedule,
219
+ })
220
+ yield* emitEvent({
221
+ tx: params.tx,
222
+ run: currentRun,
223
+ spec: params.spec,
224
+ nodeId: scheduledNodeRun.nodeId,
225
+ eventType: 'node-scheduled',
226
+ fromStatus: nodeRun.status,
227
+ toStatus: scheduledNodeRun.status,
228
+ message: `Node "${nodeSpec.label}" is scheduled (${nodeSchedule.type}).`,
229
+ emittedBy: params.emittedBy,
230
+ capturedEvents: params.capturedEvents,
231
+ })
232
+ changed = true
233
+ } else if (nodeSpec.delayAfterPredecessorMs) {
234
+ const delayAfterPredecessorMs = nodeSpec.delayAfterPredecessorMs
235
+ const { enqueueDelayedNodePromotion } = yield* delayedNodePromotionQueueModule
236
+ const scheduledNodeRunRow = yield* updateNodeRun(nodeRun, {
237
+ status: 'scheduled',
238
+ resolvedInput,
239
+ scheduledAt: currentTime,
240
+ })
241
+ const scheduledNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, scheduledNodeRunRow, 'plan node run')
242
+ replaceNodeRun(scheduledNodeRun)
243
+ yield* Effect.tryPromise(() =>
244
+ enqueueDelayedNodePromotion(
245
+ {
246
+ runId: recordIdToString(currentRun.id, TABLES.PLAN_RUN),
247
+ nodeId: nodeSpec.nodeId,
248
+ emittedBy: params.emittedBy,
249
+ },
250
+ delayAfterPredecessorMs,
251
+ ),
252
+ )
253
+ yield* emitEvent({
254
+ tx: params.tx,
255
+ run: currentRun,
256
+ spec: params.spec,
257
+ nodeId: scheduledNodeRun.nodeId,
258
+ eventType: 'node-scheduled',
259
+ fromStatus: nodeRun.status,
260
+ toStatus: scheduledNodeRun.status,
261
+ message: `Node "${nodeSpec.label}" is delayed by ${delayAfterPredecessorMs}ms after predecessor.`,
262
+ emittedBy: params.emittedBy,
263
+ capturedEvents: params.capturedEvents,
264
+ })
265
+ changed = true
266
+ } else {
267
+ const readyNodeRunRow = yield* updateNodeRun(nodeRun, {
268
+ status: 'ready',
269
+ resolvedInput,
270
+ readyAt: currentTime,
271
+ })
272
+ const readyNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, readyNodeRunRow, 'plan node run')
273
+ replaceNodeRun(readyNodeRun)
274
+ yield* emitEvent({
275
+ tx: params.tx,
276
+ run: currentRun,
277
+ spec: params.spec,
278
+ nodeId: readyNodeRun.nodeId,
279
+ eventType: 'node-ready',
280
+ fromStatus: nodeRun.status,
281
+ toStatus: readyNodeRun.status,
282
+ message: `Node "${nodeSpec.label}" is ready to execute.`,
283
+ emittedBy: params.emittedBy,
284
+ capturedEvents: params.capturedEvents,
285
+ })
286
+ changed = true
287
+ }
288
+ }
289
+
290
+ const readyStructuralNodes = sortedNodeSpecs.filter((nodeSpec) => {
291
+ const nodeRun = getNodeRunsById().get(nodeSpec.nodeId)
292
+ return nodeRun?.status === 'ready' && isStructuralNodeType(nodeSpec.type)
293
+ })
294
+
295
+ for (const nodeSpec of readyStructuralNodes) {
296
+ const nodeRun = getNodeRunsById().get(nodeSpec.nodeId)
297
+ if (!nodeRun) continue
298
+
299
+ const completedNodeRunRow = yield* updateNodeRun(nodeRun, {
300
+ status: 'completed',
301
+ startedAt: nodeRun.startedAt ?? currentTime,
302
+ completedAt: currentTime,
303
+ })
304
+ const completedNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, completedNodeRunRow, 'plan node run')
305
+ replaceNodeRun(completedNodeRun)
306
+ yield* emitEvent({
307
+ tx: params.tx,
308
+ run: currentRun,
309
+ spec: params.spec,
310
+ nodeId: completedNodeRun.nodeId,
311
+ eventType: 'node-auto-completed',
312
+ fromStatus: nodeRun.status,
313
+ toStatus: completedNodeRun.status,
314
+ message: `Structural node "${nodeSpec.label}" auto-completed.`,
315
+ emittedBy: params.emittedBy,
316
+ capturedEvents: params.capturedEvents,
317
+ })
318
+ changed = true
319
+ }
320
+ }
321
+
322
+ const nodeRunsById = getNodeRunsById()
323
+ const activeRunningNode = currentNodeRuns.find((nodeRun) => nodeRun.status === 'running')
324
+ const activeHumanNode = currentNodeRuns.find((nodeRun) => nodeRun.status === 'awaiting-human')
325
+ const activeMonitoringNode = currentNodeRuns.find((nodeRun) => nodeRun.status === 'monitoring')
326
+ const hasScheduledOrMonitoring = currentNodeRuns.some(
327
+ (nodeRun) => nodeRun.status === 'scheduled' || nodeRun.status === 'monitoring',
328
+ )
329
+
330
+ if (!activeRunningNode && !activeHumanNode && !activeMonitoringNode) {
331
+ const nextHumanNodeSpec = sortedNodeSpecs.find((nodeSpec) => {
332
+ const nodeRun = nodeRunsById.get(nodeSpec.nodeId)
333
+ return nodeRun?.status === 'ready' && isHumanNodeType(nodeSpec.type)
334
+ })
335
+
336
+ if (nextHumanNodeSpec) {
337
+ const nodeRun = nodeRunsById.get(nextHumanNodeSpec.nodeId)
338
+ if (!nodeRun) {
339
+ return yield* new NotFoundError({
340
+ resource: 'plan node run',
341
+ id: nextHumanNodeSpec.nodeId,
342
+ message: `Expected ready node run for "${nextHumanNodeSpec.nodeId}".`,
343
+ })
344
+ }
345
+ const awaitingHumanNodeRunRow = yield* updateNodeRun(nodeRun, {
346
+ status: 'awaiting-human',
347
+ startedAt: nodeRun.startedAt ?? currentTime,
348
+ })
349
+ const awaitingHumanNodeRun = yield* parseRowOrFail(
350
+ PlanNodeRunSchema,
351
+ awaitingHumanNodeRunRow,
352
+ 'plan node run',
353
+ )
354
+ replaceNodeRun(awaitingHumanNodeRun)
355
+
356
+ const approval = yield* planApprovalService.createPendingApproval({
357
+ tx: params.tx,
358
+ runId: currentRun.id,
359
+ nodeRunId: awaitingHumanNodeRun.id,
360
+ nodeId: awaitingHumanNodeRun.nodeId,
361
+ requestedBy: params.emittedBy,
362
+ presented: {
363
+ nodeId: nextHumanNodeSpec.nodeId,
364
+ label: nextHumanNodeSpec.label,
365
+ objective: nextHumanNodeSpec.objective,
366
+ instructions: nextHumanNodeSpec.instructions,
367
+ deliverables: nextHumanNodeSpec.deliverables,
368
+ successCriteria: nextHumanNodeSpec.successCriteria,
369
+ resolvedInput: awaitingHumanNodeRun.resolvedInput ?? {},
370
+ },
371
+ })
372
+
373
+ currentRun = yield* replaceRun(params.tx, currentRun, {
374
+ status: 'awaiting-human',
375
+ currentNodeId: awaitingHumanNodeRun.nodeId,
376
+ waitingNodeId: awaitingHumanNodeRun.nodeId,
377
+ readyNodeIds: currentNodeRuns
378
+ .filter((candidate) => candidate.status === 'ready' && candidate.nodeId !== awaitingHumanNodeRun.nodeId)
379
+ .map((candidate) => candidate.nodeId),
380
+ })
381
+
382
+ yield* emitEvent({
383
+ tx: params.tx,
384
+ run: currentRun,
385
+ spec: params.spec,
386
+ nodeId: awaitingHumanNodeRun.nodeId,
387
+ approvalId: approval.id,
388
+ eventType: 'approval-requested',
389
+ fromStatus: params.run.status,
390
+ toStatus: currentRun.status,
391
+ message: `Node "${nextHumanNodeSpec.label}" is awaiting human input.`,
392
+ emittedBy: params.emittedBy,
393
+ capturedEvents: params.capturedEvents,
394
+ })
395
+ } else {
396
+ const nextActionNodeSpec = sortedNodeSpecs.find((nodeSpec) => {
397
+ const nodeRun = nodeRunsById.get(nodeSpec.nodeId)
398
+ return nodeRun?.status === 'ready' && !isStructuralNodeType(nodeSpec.type)
399
+ })
400
+
401
+ if (nextActionNodeSpec) {
402
+ const nodeRun = nodeRunsById.get(nextActionNodeSpec.nodeId)
403
+ if (!nodeRun) {
404
+ return yield* new NotFoundError({
405
+ resource: 'plan node run',
406
+ id: nextActionNodeSpec.nodeId,
407
+ message: `Expected ready node run for "${nextActionNodeSpec.nodeId}".`,
408
+ })
409
+ }
410
+ const runningNodeRunRow = yield* updateNodeRun(nodeRun, {
411
+ status: 'running',
412
+ startedAt: nodeRun.startedAt ?? currentTime,
413
+ })
414
+ const runningNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, runningNodeRunRow, 'plan node run')
415
+ replaceNodeRun(runningNodeRun)
416
+
417
+ currentRun = yield* replaceRun(params.tx, currentRun, {
418
+ status: 'running',
419
+ currentNodeId: runningNodeRun.nodeId,
420
+ waitingNodeId: null,
421
+ readyNodeIds: currentNodeRuns
422
+ .filter((candidate) => candidate.status === 'ready' && candidate.nodeId !== runningNodeRun.nodeId)
423
+ .map((candidate) => candidate.nodeId),
424
+ })
425
+
426
+ yield* emitEvent({
427
+ tx: params.tx,
428
+ run: currentRun,
429
+ spec: params.spec,
430
+ nodeId: runningNodeRun.nodeId,
431
+ eventType: 'node-running',
432
+ fromStatus: nodeRun.status,
433
+ toStatus: runningNodeRun.status,
434
+ message: `Node "${nextActionNodeSpec.label}" is now running.`,
435
+ emittedBy: params.emittedBy,
436
+ capturedEvents: params.capturedEvents,
437
+ })
438
+ yield* emitEvent({
439
+ tx: params.tx,
440
+ run: currentRun,
441
+ spec: params.spec,
442
+ nodeId: runningNodeRun.nodeId,
443
+ eventType: 'ownership-transition',
444
+ message: `Execution ownership transitioned to "${nextActionNodeSpec.label}".`,
445
+ detail: {
446
+ owner: nextActionNodeSpec.owner,
447
+ fromNodeId: params.run.currentNodeId ?? null,
448
+ toNodeId: runningNodeRun.nodeId,
449
+ },
450
+ emittedBy: params.emittedBy,
451
+ capturedEvents: params.capturedEvents,
452
+ })
453
+ } else {
454
+ const allTerminalSuccess = currentNodeRuns.every((nodeRun) => isSuccessfulTerminalStatus(nodeRun.status))
455
+ const runStatus: 'completed' | 'running' | 'blocked' = allTerminalSuccess
456
+ ? 'completed'
457
+ : hasScheduledOrMonitoring
458
+ ? 'running'
459
+ : 'blocked'
460
+ const readyIds = currentNodeRuns
461
+ .filter((candidate) => candidate.status === 'ready')
462
+ .map((candidate) => candidate.nodeId)
463
+
464
+ currentRun = yield* Match.value(runStatus).pipe(
465
+ Match.when('completed', () =>
466
+ replaceRun(params.tx, currentRun, {
467
+ status: 'completed',
468
+ currentNodeId: null,
469
+ waitingNodeId: null,
470
+ readyNodeIds: [],
471
+ completedAt: currentTime,
472
+ }),
473
+ ),
474
+ Match.when('running', () =>
475
+ replaceRun(params.tx, currentRun, {
476
+ status: 'running',
477
+ currentNodeId: null,
478
+ waitingNodeId: null,
479
+ readyNodeIds: readyIds,
480
+ }),
481
+ ),
482
+ Match.when('blocked', () =>
483
+ replaceRun(params.tx, currentRun, {
484
+ status: 'blocked',
485
+ currentNodeId: null,
486
+ waitingNodeId: null,
487
+ readyNodeIds: readyIds,
488
+ }),
489
+ ),
490
+ Match.exhaustive,
491
+ )
492
+
493
+ if (runStatus === 'completed') {
494
+ yield* emitEvent({
495
+ tx: params.tx,
496
+ run: currentRun,
497
+ spec: params.spec,
498
+ eventType: 'run-status-changed',
499
+ fromStatus: params.run.status,
500
+ toStatus: currentRun.status,
501
+ message: `Run "${params.spec.title}" completed.`,
502
+ emittedBy: params.emittedBy,
503
+ capturedEvents: params.capturedEvents,
504
+ })
505
+ }
506
+ }
507
+ }
508
+ } else {
509
+ currentRun = yield* replaceRun(params.tx, currentRun, {
510
+ status: activeHumanNode ? 'awaiting-human' : 'running',
511
+ currentNodeId: activeHumanNode?.nodeId ?? activeMonitoringNode?.nodeId ?? activeRunningNode?.nodeId ?? null,
512
+ waitingNodeId: activeHumanNode?.nodeId ?? null,
513
+ readyNodeIds: currentNodeRuns
514
+ .filter((candidate) => candidate.status === 'ready')
515
+ .map((candidate) => candidate.nodeId),
516
+ })
517
+ }
518
+
519
+ return { run: currentRun, nodeRuns: currentNodeRuns, artifacts: currentArtifacts }
520
+ }),
521
+ )
522
+ }