@lota-sdk/core 0.4.9 → 0.4.11

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