@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.
- package/package.json +11 -12
- package/src/ai/embedding-cache.ts +96 -22
- package/src/ai-gateway/ai-gateway.ts +766 -223
- package/src/config/agent-defaults.ts +189 -75
- package/src/config/agent-types.ts +54 -4
- package/src/config/background-processing.ts +1 -1
- package/src/config/constants.ts +8 -2
- package/src/config/index.ts +0 -1
- package/src/config/logger.ts +299 -19
- package/src/config/thread-defaults.ts +40 -20
- package/src/create-runtime.ts +200 -449
- package/src/db/base.service.ts +52 -28
- package/src/db/cursor-pagination.ts +71 -30
- package/src/db/memory-query-builder.ts +2 -1
- package/src/db/memory-store.helpers.ts +4 -7
- package/src/db/memory-store.ts +868 -601
- package/src/db/memory.ts +396 -280
- package/src/db/record-id.ts +32 -10
- package/src/db/schema-fingerprint.ts +30 -12
- package/src/db/service-normalization.ts +288 -0
- package/src/db/service.ts +912 -779
- package/src/db/startup.ts +153 -68
- package/src/db/transaction-conflict.ts +15 -0
- package/src/effect/awaitable-effect.ts +96 -0
- package/src/effect/errors.ts +121 -0
- package/src/effect/helpers.ts +123 -0
- package/src/effect/index.ts +24 -0
- package/src/effect/layers.ts +238 -0
- package/src/effect/runtime-ref.ts +25 -0
- package/src/effect/runtime.ts +46 -0
- package/src/effect/services.ts +61 -0
- package/src/effect/zod.ts +43 -0
- package/src/embeddings/provider.ts +128 -83
- package/src/index.ts +48 -1
- package/src/openrouter/direct-provider.ts +11 -35
- package/src/queues/autonomous-job.queue.ts +117 -73
- package/src/queues/context-compaction.queue.ts +50 -17
- package/src/queues/delayed-node-promotion.queue.ts +46 -17
- package/src/queues/document-processor.queue.ts +52 -77
- package/src/queues/memory-consolidation.queue.ts +47 -32
- package/src/queues/organization-learning.queue.ts +26 -4
- package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
- package/src/queues/plan-scheduler.queue.ts +97 -33
- package/src/queues/post-chat-memory.queue.ts +56 -26
- package/src/queues/queue-factory.ts +227 -59
- package/src/queues/standalone-worker.ts +39 -0
- package/src/queues/title-generation.queue.ts +45 -11
- package/src/redis/connection.ts +182 -113
- package/src/redis/index.ts +6 -8
- package/src/redis/org-memory-lock.ts +60 -27
- package/src/redis/redis-lease-lock.ts +200 -121
- package/src/redis/runtime-connection.ts +20 -0
- package/src/redis/stream-context.ts +92 -46
- package/src/runtime/agent-identity-overrides.ts +2 -2
- package/src/runtime/agent-runtime-policy.ts +5 -2
- package/src/runtime/agent-stream-helpers.ts +24 -9
- package/src/runtime/chat-run-orchestration.ts +102 -19
- package/src/runtime/chat-run-registry.ts +36 -2
- package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
- package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +161 -94
- package/src/runtime/domain-layer.ts +192 -0
- package/src/runtime/execution-plan-visibility.ts +2 -2
- package/src/runtime/execution-plan.ts +42 -15
- package/src/runtime/graph-designer.ts +16 -4
- package/src/runtime/helper-model.ts +139 -48
- package/src/runtime/index.ts +7 -8
- package/src/runtime/indexed-repositories-policy.ts +3 -3
- package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -36
- package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
- package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +54 -67
- package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
- package/src/runtime/memory/memory-scope.ts +53 -0
- package/src/runtime/plugin-resolution.ts +124 -25
- package/src/runtime/plugin-types.ts +9 -1
- package/src/runtime/post-turn-side-effects.ts +177 -130
- package/src/runtime/retrieval-adapters.ts +40 -6
- package/src/runtime/runtime-accessors.ts +92 -0
- package/src/runtime/runtime-config.ts +150 -61
- package/src/runtime/runtime-extensions.ts +23 -25
- package/src/runtime/runtime-lifecycle.ts +124 -0
- package/src/runtime/runtime-services.ts +386 -0
- package/src/runtime/runtime-token.ts +47 -0
- package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
- package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
- package/src/runtime/social-chat/social-chat.ts +630 -0
- package/src/runtime/specialist-runner.ts +36 -10
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -0
- package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
- package/src/runtime/thread-chat-helpers.ts +2 -2
- package/src/runtime/thread-plan-turn.ts +2 -1
- package/src/runtime/thread-turn-context.ts +183 -111
- package/src/runtime/turn-lifecycle.ts +93 -27
- package/src/services/agent-activity.service.ts +287 -203
- package/src/services/agent-executor.service.ts +253 -149
- package/src/services/artifact.service.ts +231 -149
- package/src/services/attachment.service.ts +171 -115
- package/src/services/autonomous-job.service.ts +890 -491
- package/src/services/background-work.service.ts +54 -0
- package/src/services/chat-run-registry.service.ts +13 -1
- package/src/services/context-compaction.service.ts +136 -86
- package/src/services/document-chunk.service.ts +151 -88
- package/src/services/execution-plan/execution-plan-approval.ts +26 -0
- package/src/services/execution-plan/execution-plan-context.ts +29 -0
- package/src/services/execution-plan/execution-plan-graph.ts +278 -0
- package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
- package/src/services/execution-plan/execution-plan-spec.ts +75 -0
- package/src/services/execution-plan/execution-plan.service.ts +1041 -0
- package/src/services/feedback-loop.service.ts +132 -76
- package/src/services/global-orchestrator.service.ts +101 -168
- package/src/services/graph-full-routing.ts +193 -0
- package/src/services/index.ts +19 -21
- package/src/services/institutional-memory.service.ts +213 -125
- package/src/services/learned-skill.service.ts +368 -260
- package/src/services/memory/memory-conversation.ts +95 -0
- package/src/services/memory/memory-errors.ts +27 -0
- package/src/services/memory/memory-org-memory.ts +50 -0
- package/src/services/memory/memory-preseeded.ts +86 -0
- package/src/services/memory/memory-rerank.ts +297 -0
- package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
- package/src/services/memory/memory.service.ts +674 -0
- package/src/services/memory/rerank.service.ts +201 -0
- package/src/services/monitoring-window.service.ts +92 -70
- package/src/services/mutating-approval.service.ts +62 -53
- package/src/services/node-workspace.service.ts +141 -98
- package/src/services/notification.service.ts +29 -16
- package/src/services/organization-member.service.ts +120 -66
- package/src/services/organization.service.ts +153 -77
- package/src/services/ownership-dispatcher.service.ts +456 -263
- package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
- package/src/services/plan/plan-agent-query.service.ts +322 -0
- package/src/services/{plan-approval.service.ts → plan/plan-approval.service.ts} +45 -22
- package/src/services/plan/plan-artifact.service.ts +60 -0
- package/src/services/plan/plan-builder.service.ts +76 -0
- package/src/services/plan/plan-checkpoint.service.ts +103 -0
- package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
- package/src/services/plan/plan-completion-side-effects.ts +169 -0
- package/src/services/plan/plan-coordination.service.ts +181 -0
- package/src/services/plan/plan-cycle.service.ts +405 -0
- package/src/services/plan/plan-deadline.service.ts +533 -0
- package/src/services/plan/plan-event-delivery.service.ts +266 -0
- package/src/services/plan/plan-executor-context.ts +35 -0
- package/src/services/plan/plan-executor-graph.ts +522 -0
- package/src/services/plan/plan-executor-helpers.ts +307 -0
- package/src/services/plan/plan-executor-persistence.ts +209 -0
- package/src/services/plan/plan-executor.service.ts +1737 -0
- package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
- package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
- package/src/services/plan/plan-run-serialization.ts +15 -0
- package/src/services/plan/plan-run.service.ts +637 -0
- package/src/services/plan/plan-scheduler.service.ts +379 -0
- package/src/services/plan/plan-template.service.ts +224 -0
- package/src/services/plan/plan-transaction-events.ts +36 -0
- package/src/services/plan/plan-validator.service.ts +907 -0
- package/src/services/plan/plan-workspace.service.ts +131 -0
- package/src/services/plugin-executor.service.ts +102 -68
- package/src/services/quality-metrics.service.ts +112 -94
- package/src/services/queue-job.service.ts +288 -231
- package/src/services/recent-activity-title.service.ts +73 -36
- package/src/services/recent-activity.service.ts +274 -259
- package/src/services/skill-resolver.service.ts +38 -12
- package/src/services/social-chat-history.service.ts +190 -122
- package/src/services/system-executor.service.ts +96 -61
- package/src/services/thread/thread-active-run.ts +203 -0
- package/src/services/thread/thread-bootstrap.ts +385 -0
- package/src/services/thread/thread-listing.ts +199 -0
- package/src/services/thread/thread-memory-block.ts +130 -0
- package/src/services/thread/thread-message.service.ts +379 -0
- package/src/services/thread/thread-record-store.ts +155 -0
- package/src/services/thread/thread-title.service.ts +74 -0
- package/src/services/thread/thread-turn-execution.ts +280 -0
- package/src/services/thread/thread-turn-message-context.ts +73 -0
- package/src/services/thread/thread-turn-preparation.service.ts +1148 -0
- package/src/services/thread/thread-turn-streaming.ts +403 -0
- package/src/services/thread/thread-turn-tracing.ts +35 -0
- package/src/services/thread/thread-turn.ts +376 -0
- package/src/services/thread/thread.service.ts +344 -0
- package/src/services/user.service.ts +82 -32
- package/src/services/write-intent-validator.service.ts +63 -51
- package/src/storage/attachment-parser.ts +69 -27
- package/src/storage/attachment-storage.service.ts +334 -275
- package/src/storage/generated-document-storage.service.ts +66 -34
- package/src/system-agents/agent-result.ts +3 -1
- package/src/system-agents/context-compaction.agent.ts +3 -3
- package/src/system-agents/delegated-agent-factory.ts +159 -90
- package/src/system-agents/helper-agent-options.ts +1 -1
- package/src/system-agents/memory-reranker.agent.ts +3 -3
- package/src/system-agents/memory.agent.ts +3 -3
- package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
- package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
- package/src/system-agents/skill-extractor.agent.ts +3 -3
- package/src/system-agents/skill-manager.agent.ts +3 -3
- package/src/system-agents/thread-router.agent.ts +157 -113
- package/src/system-agents/title-generator.agent.ts +3 -3
- package/src/tools/execution-plan.tool.ts +241 -171
- package/src/tools/fetch-webpage.tool.ts +29 -18
- package/src/tools/firecrawl-client.ts +26 -6
- package/src/tools/index.ts +1 -0
- package/src/tools/memory-block.tool.ts +14 -6
- package/src/tools/plan-approval.tool.ts +57 -47
- package/src/tools/read-file-parts.tool.ts +44 -33
- package/src/tools/remember-memory.tool.ts +65 -45
- package/src/tools/search-web.tool.ts +33 -22
- package/src/tools/search.tool.ts +41 -29
- package/src/tools/team-think.tool.ts +125 -84
- package/src/tools/user-questions.tool.ts +4 -3
- package/src/tools/web-tool-shared.ts +6 -0
- package/src/utils/async.ts +25 -22
- package/src/utils/crypto.ts +21 -0
- package/src/utils/date-time.ts +40 -1
- package/src/utils/errors.ts +111 -20
- package/src/utils/hono-error-handler.ts +24 -39
- package/src/utils/index.ts +2 -1
- package/src/utils/null-proto-record.ts +41 -0
- package/src/utils/sse-keepalive.ts +124 -21
- package/src/workers/bootstrap.ts +164 -52
- package/src/workers/memory-consolidation.worker.ts +325 -237
- package/src/workers/organization-learning.worker.ts +50 -16
- package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
- package/src/workers/regular-chat-memory-digest.runner.ts +185 -114
- package/src/workers/skill-extraction.runner.ts +176 -93
- package/src/workers/utils/file-section-chunker.ts +8 -10
- package/src/workers/utils/repo-structure-extractor.ts +349 -260
- package/src/workers/utils/repomix-file-sections.ts +2 -2
- package/src/workers/utils/thread-message-query.ts +97 -38
- package/src/workers/worker-utils.ts +74 -31
- package/src/config/debug-logger.ts +0 -47
- package/src/config/search.ts +0 -3
- package/src/redis/connection-accessor.ts +0 -26
- package/src/runtime/agent-types.ts +0 -1
- package/src/runtime/context-compaction-runtime.ts +0 -87
- package/src/runtime/memory-scope.ts +0 -43
- package/src/runtime/social-chat-agent-runner.ts +0 -118
- package/src/runtime/social-chat.ts +0 -516
- package/src/runtime/team-consultation-orchestrator.ts +0 -272
- package/src/services/adaptive-playbook.service.ts +0 -152
- package/src/services/artifact-provenance.service.ts +0 -172
- package/src/services/chat-attachments.service.ts +0 -17
- package/src/services/context-compaction-runtime.singleton.ts +0 -13
- package/src/services/execution-plan.service.ts +0 -1118
- package/src/services/memory.service.ts +0 -914
- package/src/services/plan-agent-heartbeat.service.ts +0 -136
- package/src/services/plan-agent-query.service.ts +0 -267
- package/src/services/plan-artifact.service.ts +0 -50
- package/src/services/plan-builder.service.ts +0 -67
- package/src/services/plan-checkpoint.service.ts +0 -81
- package/src/services/plan-completion-side-effects.ts +0 -80
- package/src/services/plan-coordination.service.ts +0 -157
- package/src/services/plan-cycle.service.ts +0 -284
- package/src/services/plan-deadline.service.ts +0 -430
- package/src/services/plan-event-delivery.service.ts +0 -166
- package/src/services/plan-executor.service.ts +0 -1950
- package/src/services/plan-run.service.ts +0 -515
- package/src/services/plan-scheduler.service.ts +0 -240
- package/src/services/plan-template.service.ts +0 -177
- package/src/services/plan-validator.service.ts +0 -818
- package/src/services/plan-workspace.service.ts +0 -83
- package/src/services/rerank.service.ts +0 -156
- package/src/services/thread-message.service.ts +0 -275
- package/src/services/thread-plan-registry.service.ts +0 -22
- package/src/services/thread-title.service.ts +0 -39
- package/src/services/thread-turn-preparation.service.ts +0 -1147
- package/src/services/thread-turn.ts +0 -172
- package/src/services/thread.service.ts +0 -869
- package/src/utils/env.ts +0 -8
- /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
- /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
- /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
- /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
- /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
- /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
- /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
- /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
|
@@ -11,28 +11,62 @@ import type {
|
|
|
11
11
|
PlanRunRecord,
|
|
12
12
|
PlanSchemaRegistry,
|
|
13
13
|
PlanSpecRecord,
|
|
14
|
+
PlanNodeOwner,
|
|
14
15
|
PlanDraft,
|
|
15
|
-
SerializableExecutionPlan,
|
|
16
16
|
UpstreamHandoff,
|
|
17
17
|
} from '@lota-sdk/shared'
|
|
18
|
+
import { Cause, Context, Effect, Exit, Layer, Match } from 'effect'
|
|
18
19
|
|
|
19
|
-
import {
|
|
20
|
+
import type { ResolvedAgentConfig } from '../config/agent-defaults'
|
|
20
21
|
import type { RecordIdInput } from '../db/record-id'
|
|
21
22
|
import { ensureRecordId, recordIdToString } from '../db/record-id'
|
|
22
|
-
import {
|
|
23
|
+
import type { SurrealDBService } from '../db/service'
|
|
23
24
|
import { TABLES } from '../db/tables'
|
|
25
|
+
import { BadRequestError, ConfigurationError, DatabaseError, ServiceError } from '../effect/errors'
|
|
26
|
+
import { isPromiseLike, makeEffectTryPromiseWithMessage } from '../effect/helpers'
|
|
27
|
+
import { AgentConfigServiceTag, DatabaseServiceTag, RuntimeAdaptersServiceTag } from '../effect/services'
|
|
24
28
|
import { resolvePlanNodeExecutionVisibility, shouldPlanNodeUseVisibleTurn } from '../runtime/execution-plan-visibility'
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
29
|
+
import type { LotaRuntimeAdapters } from '../runtime/runtime-extensions'
|
|
30
|
+
import type { makeAgentExecutorService } from './agent-executor.service'
|
|
31
|
+
import { AgentExecutorServiceTag } from './agent-executor.service'
|
|
32
|
+
import { routeGraphFullEffect } from './graph-full-routing'
|
|
33
|
+
import type { makeMonitoringWindowService } from './monitoring-window.service'
|
|
34
|
+
import { MonitoringWindowServiceTag } from './monitoring-window.service'
|
|
35
|
+
import type { makePlanExecutorService } from './plan/plan-executor.service'
|
|
36
|
+
import { PlanExecutorServiceTag } from './plan/plan-executor.service'
|
|
37
|
+
import { serializeRunFull } from './plan/plan-run-serialization'
|
|
38
|
+
import type { makePlanRunService } from './plan/plan-run.service'
|
|
39
|
+
import { PlanRunServiceTag } from './plan/plan-run.service'
|
|
40
|
+
import type { PlanValidationIssueInput } from './plan/plan-validator.service'
|
|
41
|
+
import type { makePluginExecutorService } from './plugin-executor.service'
|
|
42
|
+
import { PluginExecutorServiceTag } from './plugin-executor.service'
|
|
43
|
+
import type { SkillResolverService } from './skill-resolver.service'
|
|
44
|
+
import { SkillResolverServiceTag } from './skill-resolver.service'
|
|
45
|
+
import type { makeSystemExecutorService } from './system-executor.service'
|
|
46
|
+
import { SystemExecutorServiceTag } from './system-executor.service'
|
|
47
|
+
import { ThreadSchema } from './thread/thread.types'
|
|
48
|
+
import type { makeUserService } from './user.service'
|
|
49
|
+
import { UserServiceTag } from './user.service'
|
|
50
|
+
|
|
51
|
+
interface OwnershipDispatcherDeps {
|
|
52
|
+
db: SurrealDBService
|
|
53
|
+
agentConfig: ResolvedAgentConfig
|
|
54
|
+
runtimeAdapters: LotaRuntimeAdapters
|
|
55
|
+
agentExecutor: NoContextService<Pick<ReturnType<typeof makeAgentExecutorService>, 'executeNode'>>
|
|
56
|
+
monitoringWindow: NoContextService<Pick<ReturnType<typeof makeMonitoringWindowService>, 'startMonitoringWindow'>>
|
|
57
|
+
planExecutor: ReturnType<typeof makePlanExecutorService>
|
|
58
|
+
planRun: ReturnType<typeof makePlanRunService>
|
|
59
|
+
pluginExecutor: NoContextService<Pick<ReturnType<typeof makePluginExecutorService>, 'executeNode' | 'validateOwner'>>
|
|
60
|
+
skillResolver: NoContextService<Pick<SkillResolverService, 'resolve'>>
|
|
61
|
+
systemExecutor: NoContextService<Pick<ReturnType<typeof makeSystemExecutorService>, 'executeNode' | 'validateOwner'>>
|
|
62
|
+
user: NoContextService<Pick<ReturnType<typeof makeUserService>, 'getUser'>>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type NoContextService<TService> = {
|
|
66
|
+
[K in keyof TService]: TService[K] extends (...args: infer TArgs) => Effect.Effect<infer A, infer E, unknown>
|
|
67
|
+
? (...args: TArgs) => Effect.Effect<A, E, never>
|
|
68
|
+
: TService[K]
|
|
69
|
+
}
|
|
36
70
|
|
|
37
71
|
const STABLE_RUN_STATUSES = new Set(['pending-approval', 'awaiting-human', 'blocked', 'failed', 'completed', 'aborted'])
|
|
38
72
|
const MAX_DISPATCH_ITERATIONS = 64
|
|
@@ -101,203 +135,93 @@ function formatDispatchError(error: unknown): string {
|
|
|
101
135
|
return error instanceof Error ? error.message : String(error)
|
|
102
136
|
}
|
|
103
137
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
for (const node of draft.nodes) {
|
|
109
|
-
if (node.owner.executorType === 'agent') {
|
|
110
|
-
if (!agentRoster.includes(node.owner.ref)) {
|
|
111
|
-
issues.push({
|
|
112
|
-
severity: 'blocking',
|
|
113
|
-
code: 'agent_executor_missing',
|
|
114
|
-
message: `Node "${node.label}" references unknown agent executor "${node.owner.ref}".`,
|
|
115
|
-
nodeId: node.id,
|
|
116
|
-
detail: { agentId: node.owner.ref },
|
|
117
|
-
})
|
|
118
|
-
}
|
|
119
|
-
continue
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (node.owner.executorType === 'plugin') {
|
|
123
|
-
issues.push(...pluginExecutorService.validateOwner(node.owner, node.id))
|
|
124
|
-
continue
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (node.owner.executorType === 'system') {
|
|
128
|
-
issues.push(...systemExecutorService.validateOwner(node.owner, node.id))
|
|
129
|
-
continue
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (node.owner.executorType === 'skill') {
|
|
133
|
-
// Skill owners are validated at execution time via skillResolverService.
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return issues
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async dispatchRunToStableBoundary(params: {
|
|
141
|
-
runId: RecordIdInput
|
|
142
|
-
emittedBy: string
|
|
143
|
-
}): Promise<SerializableExecutionPlan> {
|
|
144
|
-
const initialRun = await planRunService.getRunById(params.runId)
|
|
145
|
-
const autoDispatchEnabled = await this.shouldAutoDispatch(initialRun)
|
|
146
|
-
if (!autoDispatchEnabled) {
|
|
147
|
-
return this.serializeRun(initialRun.id)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
let iteration = 0
|
|
151
|
-
while (iteration < MAX_DISPATCH_ITERATIONS) {
|
|
152
|
-
const run = await planRunService.getRunById(params.runId)
|
|
153
|
-
if (STABLE_RUN_STATUSES.has(run.status) || run.status !== 'running' || !run.currentNodeId) {
|
|
154
|
-
return this.serializeRun(run.id)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const spec = await planRunService.getPlanSpecById(run.planSpecId)
|
|
158
|
-
|
|
159
|
-
if (spec.executionMode === 'graph-full') {
|
|
160
|
-
const { globalOrchestratorService } = await import('./global-orchestrator.service')
|
|
161
|
-
await globalOrchestratorService.routeGraphFull({
|
|
162
|
-
threadId: recordIdToString(run.threadId, TABLES.THREAD),
|
|
163
|
-
runId: recordIdToString(run.id, TABLES.PLAN_RUN),
|
|
164
|
-
})
|
|
165
|
-
return this.serializeRun(run.id)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const nodeSpecRecord = await planRunService.getNodeSpecByNodeId(spec.id, run.currentNodeId)
|
|
169
|
-
const planNode = toPlanNodeSpec(nodeSpecRecord)
|
|
170
|
-
if (planNode.owner.executorType === 'user') {
|
|
171
|
-
return this.serializeRun(run.id)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const nodeRun = await planRunService.getNodeRunByNodeId(run.id, nodeSpecRecord.nodeId)
|
|
175
|
-
if (nodeRun.status === 'monitoring') {
|
|
176
|
-
// Monitoring nodes are managed by the scheduler — treat as stable
|
|
177
|
-
return this.serializeRun(run.id)
|
|
178
|
-
}
|
|
179
|
-
if (nodeRun.status !== 'running') {
|
|
180
|
-
return this.serializeRun(run.id)
|
|
181
|
-
}
|
|
182
|
-
if (shouldPlanNodeUseVisibleTurn(spec, nodeSpecRecord)) {
|
|
183
|
-
return this.serializeRun(run.id)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const [artifacts, dispatchContext] = await Promise.all([
|
|
187
|
-
planRunService.listArtifacts(run.id),
|
|
188
|
-
this.buildDispatchContext(run, spec, nodeSpecRecord),
|
|
189
|
-
])
|
|
190
|
-
const inputArtifacts = artifacts
|
|
191
|
-
.filter((artifact) => nodeSpecRecord.upstreamNodeIds.includes(artifact.nodeId))
|
|
192
|
-
.map((artifact) => toArtifactSubmission(artifact))
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
const result = await this.dispatchNode({
|
|
196
|
-
nodeSpec: planNode,
|
|
197
|
-
resolvedInput: nodeRun.resolvedInput ?? {},
|
|
198
|
-
inputArtifacts,
|
|
199
|
-
context: { ...dispatchContext, nodeId: planNode.id },
|
|
200
|
-
executionMode: spec.executionMode,
|
|
201
|
-
schemaRegistry: spec.schemaRegistry,
|
|
202
|
-
})
|
|
138
|
+
function toDispatchDatabaseError(message: string, cause: unknown) {
|
|
139
|
+
return new DatabaseError({ message, cause })
|
|
140
|
+
}
|
|
203
141
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
142
|
+
const tryDispatchPromise = makeEffectTryPromiseWithMessage((message, cause) => new ServiceError({ message, cause }))
|
|
143
|
+
|
|
144
|
+
const matchDraftExecutor = (deps: OwnershipDispatcherDeps, node: { id: string; label: string }) =>
|
|
145
|
+
Match.type<PlanNodeOwner>().pipe(
|
|
146
|
+
Match.discriminator('executorType')('agent', (owner): PlanValidationIssueInput[] =>
|
|
147
|
+
deps.agentConfig.roster.includes(owner.ref)
|
|
148
|
+
? []
|
|
149
|
+
: [
|
|
150
|
+
{
|
|
151
|
+
severity: 'blocking',
|
|
152
|
+
code: 'agent_executor_missing',
|
|
153
|
+
message: `Node "${node.label}" references unknown agent executor "${owner.ref}".`,
|
|
154
|
+
nodeId: node.id,
|
|
155
|
+
detail: { agentId: owner.ref },
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
),
|
|
159
|
+
Match.discriminator('executorType')('plugin', (owner): PlanValidationIssueInput[] =>
|
|
160
|
+
deps.pluginExecutor.validateOwner(owner, node.id),
|
|
161
|
+
),
|
|
162
|
+
Match.discriminator('executorType')('system', (owner): PlanValidationIssueInput[] =>
|
|
163
|
+
deps.systemExecutor.validateOwner(owner, node.id),
|
|
164
|
+
),
|
|
165
|
+
Match.discriminator('executorType')('skill', (): PlanValidationIssueInput[] => []),
|
|
166
|
+
Match.discriminator('executorType')('user', (): PlanValidationIssueInput[] => []),
|
|
167
|
+
Match.exhaustive,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
function validateDraftExecutors(deps: OwnershipDispatcherDeps, draft: PlanDraft): PlanValidationIssueInput[] {
|
|
171
|
+
return draft.nodes.flatMap((node) => matchDraftExecutor(deps, node)(node.owner))
|
|
172
|
+
}
|
|
222
173
|
|
|
223
|
-
|
|
174
|
+
const shouldAutoDispatchEffect = (deps: OwnershipDispatcherDeps, run: PlanRunRecord) =>
|
|
175
|
+
Effect.gen(function* () {
|
|
176
|
+
const workspaceProvider = deps.runtimeAdapters.workspaceProvider
|
|
177
|
+
if (!workspaceProvider) {
|
|
178
|
+
return true
|
|
224
179
|
}
|
|
225
180
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
TABLES.PLAN_RUN,
|
|
230
|
-
)}.`,
|
|
181
|
+
const workspace = yield* tryDispatchPromise(
|
|
182
|
+
() => workspaceProvider.getWorkspace(ensureRecordId(run.organizationId, TABLES.ORGANIZATION)),
|
|
183
|
+
'Failed to load workspace for dispatch eligibility.',
|
|
231
184
|
)
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
async dispatchReadyNode(params: {
|
|
235
|
-
run: PlanRunRecord
|
|
236
|
-
nodeSpecRecord: PlanNodeSpecRecord
|
|
237
|
-
nodeRun: PlanNodeRunRecord
|
|
238
|
-
spec: PlanSpecRecord
|
|
239
|
-
executionModeOverride?: ExecutionMode
|
|
240
|
-
}): Promise<PlanNodeResultSubmission> {
|
|
241
|
-
if (shouldPlanNodeUseVisibleTurn(params.spec, params.nodeSpecRecord)) {
|
|
242
|
-
throw new Error(
|
|
243
|
-
`Node "${params.nodeSpecRecord.nodeId}" requires a visible plan turn and cannot be silently dispatched.`,
|
|
244
|
-
)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const planNode = toPlanNodeSpec(params.nodeSpecRecord)
|
|
248
|
-
const [artifacts, dispatchContext] = await Promise.all([
|
|
249
|
-
planRunService.listArtifacts(params.run.id),
|
|
250
|
-
this.buildDispatchContext(params.run, params.spec, params.nodeSpecRecord),
|
|
251
|
-
])
|
|
252
|
-
const inputArtifacts = artifacts
|
|
253
|
-
.filter((artifact) => params.nodeSpecRecord.upstreamNodeIds.includes(artifact.nodeId))
|
|
254
|
-
.map((artifact) => toArtifactSubmission(artifact))
|
|
255
|
-
|
|
256
|
-
return this.dispatchNode({
|
|
257
|
-
nodeSpec: planNode,
|
|
258
|
-
resolvedInput: params.nodeRun.resolvedInput ?? {},
|
|
259
|
-
inputArtifacts,
|
|
260
|
-
context: { ...dispatchContext, nodeId: planNode.id },
|
|
261
|
-
executionMode: params.spec.executionMode,
|
|
262
|
-
executionModeOverride: params.executionModeOverride,
|
|
263
|
-
schemaRegistry: params.spec.schemaRegistry,
|
|
264
|
-
})
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
private async shouldAutoDispatch(run: PlanRunRecord): Promise<boolean> {
|
|
268
|
-
const workspaceProvider = getRuntimeAdapters().workspaceProvider
|
|
269
|
-
if (!workspaceProvider) {
|
|
185
|
+
if (!workspaceProvider.getLifecycleState) {
|
|
270
186
|
return true
|
|
271
187
|
}
|
|
272
188
|
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
resolveExecutionVisibility(params: { spec: PlanSpecRecord; nodeSpecRecord: PlanNodeSpecRecord }) {
|
|
279
|
-
return resolvePlanNodeExecutionVisibility(params.spec, params.nodeSpecRecord)
|
|
280
|
-
}
|
|
189
|
+
const lifecycleState = yield* Effect.gen(function* () {
|
|
190
|
+
const result = workspaceProvider.getLifecycleState?.call(workspaceProvider, workspace)
|
|
191
|
+
if (isPromiseLike(result)) {
|
|
192
|
+
return yield* tryDispatchPromise(() => result, 'Failed to read workspace lifecycle state.')
|
|
193
|
+
}
|
|
281
194
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
195
|
+
return result
|
|
196
|
+
})
|
|
197
|
+
return lifecycleState ? lifecycleState.bootstrapActive !== true : true
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
const buildDispatchContextEffect = (
|
|
201
|
+
deps: OwnershipDispatcherDeps,
|
|
202
|
+
run: PlanRunRecord,
|
|
203
|
+
spec: PlanSpecRecord,
|
|
204
|
+
nodeSpecRecord: PlanNodeSpecRecord,
|
|
205
|
+
) =>
|
|
206
|
+
Effect.gen(function* () {
|
|
287
207
|
const organizationId = recordIdToString(run.organizationId, TABLES.ORGANIZATION)
|
|
288
208
|
const threadId = recordIdToString(run.threadId, TABLES.THREAD)
|
|
289
209
|
const planId = recordIdToString(run.id, TABLES.PLAN_RUN)
|
|
290
|
-
const [thread, nodeSpecs, nodeRuns] =
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
210
|
+
const [thread, nodeSpecs, nodeRuns] = yield* Effect.all([
|
|
211
|
+
deps.db
|
|
212
|
+
.findOne(TABLES.THREAD, { id: ensureRecordId(run.threadId, TABLES.THREAD) }, ThreadSchema)
|
|
213
|
+
.pipe(Effect.mapError((cause) => toDispatchDatabaseError('Failed to load thread context.', cause))),
|
|
214
|
+
deps.planRun
|
|
215
|
+
.listNodeSpecs(spec.id)
|
|
216
|
+
.pipe(Effect.mapError((cause) => toDispatchDatabaseError('Failed to load plan node specs.', cause))),
|
|
217
|
+
deps.planRun
|
|
218
|
+
.listNodeRuns(run.id)
|
|
219
|
+
.pipe(Effect.mapError((cause) => toDispatchDatabaseError('Failed to load plan node runs.', cause))),
|
|
294
220
|
])
|
|
295
221
|
const userId = thread?.userId ? recordIdToString(thread.userId, TABLES.USER) : undefined
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
.then((user) => user.name)
|
|
300
|
-
.catch(() => undefined)
|
|
222
|
+
const userResult = userId ? yield* Effect.exit(deps.user.getUser(userId)) : undefined
|
|
223
|
+
const userName = userResult
|
|
224
|
+
? Exit.match(userResult, { onSuccess: (user) => user.name, onFailure: () => undefined })
|
|
301
225
|
: undefined
|
|
302
226
|
const nodeSpecsById = new Map(nodeSpecs.map((candidate) => [candidate.nodeId, candidate]))
|
|
303
227
|
const upstreamHandoffs: UpstreamHandoff[] = nodeRuns
|
|
@@ -324,19 +248,17 @@ class OwnershipDispatcherService {
|
|
|
324
248
|
...(userName ? { userName } : {}),
|
|
325
249
|
...(upstreamHandoffs.length > 0 ? { upstreamHandoffs } : {}),
|
|
326
250
|
}
|
|
327
|
-
}
|
|
251
|
+
})
|
|
328
252
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
includeCheckpoints: true,
|
|
335
|
-
includeValidationIssues: true,
|
|
336
|
-
})
|
|
337
|
-
}
|
|
253
|
+
const serializeRunEffect = (deps: OwnershipDispatcherDeps, runId: RecordIdInput) =>
|
|
254
|
+
Effect.gen(function* () {
|
|
255
|
+
const run = yield* deps.planRun.getRunById(runId)
|
|
256
|
+
return yield* serializeRunFull(deps.planRun, run)
|
|
257
|
+
})
|
|
338
258
|
|
|
339
|
-
|
|
259
|
+
const dispatchNodeEffect = (
|
|
260
|
+
deps: OwnershipDispatcherDeps,
|
|
261
|
+
params: {
|
|
340
262
|
nodeSpec: PlanNodeSpec
|
|
341
263
|
resolvedInput: Record<string, unknown>
|
|
342
264
|
inputArtifacts: PlanArtifactSubmission[]
|
|
@@ -344,83 +266,354 @@ class OwnershipDispatcherService {
|
|
|
344
266
|
executionMode?: ExecutionMode
|
|
345
267
|
executionModeOverride?: ExecutionMode
|
|
346
268
|
schemaRegistry?: PlanSchemaRegistry
|
|
347
|
-
}
|
|
269
|
+
},
|
|
270
|
+
) =>
|
|
271
|
+
Effect.gen(function* () {
|
|
348
272
|
const effectiveExecutionMode = params.executionModeOverride ?? params.executionMode
|
|
349
273
|
if (params.nodeSpec.type === 'monitoring' && params.nodeSpec.monitoringConfig) {
|
|
350
|
-
|
|
274
|
+
const monitoringConfig = params.nodeSpec.monitoringConfig
|
|
275
|
+
yield* deps.monitoringWindow.startMonitoringWindow({
|
|
351
276
|
runId: params.context.planId,
|
|
352
277
|
nodeId: params.nodeSpec.id,
|
|
353
|
-
config:
|
|
278
|
+
config: monitoringConfig,
|
|
354
279
|
organizationId: params.context.organizationId,
|
|
355
280
|
threadId: params.context.threadId,
|
|
356
281
|
})
|
|
357
282
|
return {
|
|
358
283
|
notes: `Monitoring window started for node "${params.nodeSpec.label}".`,
|
|
359
|
-
structuredOutput: { status: 'monitoring-started', config:
|
|
284
|
+
structuredOutput: { status: 'monitoring-started', config: monitoringConfig },
|
|
360
285
|
artifacts: [],
|
|
361
286
|
}
|
|
362
287
|
}
|
|
363
288
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
inputArtifacts: params.inputArtifacts,
|
|
369
|
-
context: params.context,
|
|
370
|
-
executionMode: effectiveExecutionMode,
|
|
371
|
-
schemaRegistry: params.schemaRegistry,
|
|
372
|
-
})
|
|
373
|
-
}
|
|
374
|
-
if (params.nodeSpec.owner.executorType === 'plugin') {
|
|
375
|
-
return pluginExecutorService.executeNode({
|
|
376
|
-
nodeSpec: params.nodeSpec,
|
|
377
|
-
resolvedInput: params.resolvedInput,
|
|
378
|
-
context: params.context,
|
|
379
|
-
})
|
|
380
|
-
}
|
|
381
|
-
if (params.nodeSpec.owner.executorType === 'system') {
|
|
382
|
-
return systemExecutorService.executeNode({
|
|
383
|
-
nodeSpec: params.nodeSpec,
|
|
384
|
-
resolvedInput: params.resolvedInput,
|
|
385
|
-
context: params.context,
|
|
386
|
-
})
|
|
387
|
-
}
|
|
388
|
-
if (params.nodeSpec.owner.executorType === 'skill') {
|
|
389
|
-
const resolved = await skillResolverService.resolve({
|
|
390
|
-
skillRef: params.nodeSpec.owner.ref,
|
|
391
|
-
organizationId: params.context.organizationId,
|
|
392
|
-
})
|
|
393
|
-
if (!resolved) {
|
|
394
|
-
throw new Error(`Skill "${params.nodeSpec.owner.ref}" could not be resolved. This is a configuration error.`)
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (resolved.executorType === 'agent') {
|
|
398
|
-
const skillNodeSpec = { ...params.nodeSpec, owner: { executorType: 'agent' as const, ref: resolved.ref } }
|
|
399
|
-
return agentExecutorService.executeNode({
|
|
400
|
-
nodeSpec: skillNodeSpec,
|
|
289
|
+
return yield* Match.value(params.nodeSpec.owner.executorType).pipe(
|
|
290
|
+
Match.when('agent', () =>
|
|
291
|
+
deps.agentExecutor.executeNode({
|
|
292
|
+
nodeSpec: params.nodeSpec,
|
|
401
293
|
resolvedInput: params.resolvedInput,
|
|
402
294
|
inputArtifacts: params.inputArtifacts,
|
|
403
295
|
context: params.context,
|
|
404
296
|
executionMode: effectiveExecutionMode,
|
|
405
297
|
schemaRegistry: params.schemaRegistry,
|
|
406
|
-
})
|
|
298
|
+
}),
|
|
299
|
+
),
|
|
300
|
+
Match.when('plugin', () =>
|
|
301
|
+
deps.pluginExecutor.executeNode({
|
|
302
|
+
nodeSpec: params.nodeSpec,
|
|
303
|
+
resolvedInput: params.resolvedInput,
|
|
304
|
+
context: params.context,
|
|
305
|
+
}),
|
|
306
|
+
),
|
|
307
|
+
Match.when('system', () =>
|
|
308
|
+
deps.systemExecutor.executeNode({
|
|
309
|
+
nodeSpec: params.nodeSpec,
|
|
310
|
+
resolvedInput: params.resolvedInput,
|
|
311
|
+
context: params.context,
|
|
312
|
+
}),
|
|
313
|
+
),
|
|
314
|
+
Match.when('skill', () =>
|
|
315
|
+
Effect.gen(function* () {
|
|
316
|
+
const resolved = yield* deps.skillResolver.resolve({
|
|
317
|
+
skillRef: params.nodeSpec.owner.ref,
|
|
318
|
+
organizationId: params.context.organizationId,
|
|
319
|
+
})
|
|
320
|
+
if (!resolved) {
|
|
321
|
+
return yield* new ConfigurationError({
|
|
322
|
+
message: `Skill "${params.nodeSpec.owner.ref}" could not be resolved. This is a configuration error.`,
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (resolved.executorType === 'agent') {
|
|
327
|
+
const skillNodeSpec = { ...params.nodeSpec, owner: { executorType: 'agent' as const, ref: resolved.ref } }
|
|
328
|
+
return yield* deps.agentExecutor.executeNode({
|
|
329
|
+
nodeSpec: skillNodeSpec,
|
|
330
|
+
resolvedInput: params.resolvedInput,
|
|
331
|
+
inputArtifacts: params.inputArtifacts,
|
|
332
|
+
context: params.context,
|
|
333
|
+
executionMode: effectiveExecutionMode,
|
|
334
|
+
schemaRegistry: params.schemaRegistry,
|
|
335
|
+
})
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return yield* deps.pluginExecutor.executeNode({
|
|
339
|
+
nodeSpec: {
|
|
340
|
+
...params.nodeSpec,
|
|
341
|
+
owner: {
|
|
342
|
+
executorType: 'plugin' as const,
|
|
343
|
+
ref: resolved.ref,
|
|
344
|
+
operation: resolved.operation ?? params.nodeSpec.owner.ref,
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
resolvedInput: params.resolvedInput,
|
|
348
|
+
context: params.context,
|
|
349
|
+
})
|
|
350
|
+
}),
|
|
351
|
+
),
|
|
352
|
+
Match.when(
|
|
353
|
+
'user',
|
|
354
|
+
() => new BadRequestError({ message: `User-owned node "${params.nodeSpec.id}" cannot be auto-dispatched.` }),
|
|
355
|
+
),
|
|
356
|
+
Match.exhaustive,
|
|
357
|
+
)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
type GraphFullDispatchReadyNode = (
|
|
361
|
+
params: Parameters<typeof dispatchReadyNodeEffect>[1],
|
|
362
|
+
) => Effect.Effect<PlanNodeResultSubmission, Effect.Error<ReturnType<typeof dispatchReadyNodeEffect>>, never>
|
|
363
|
+
|
|
364
|
+
const dispatchRunToStableBoundaryEffect = (
|
|
365
|
+
deps: OwnershipDispatcherDeps,
|
|
366
|
+
params: { runId: RecordIdInput; emittedBy: string },
|
|
367
|
+
) =>
|
|
368
|
+
Effect.gen(function* () {
|
|
369
|
+
const initialRun = yield* deps.planRun.getRunById(params.runId)
|
|
370
|
+
const autoDispatchEnabled = yield* shouldAutoDispatchEffect(deps, initialRun)
|
|
371
|
+
if (!autoDispatchEnabled) {
|
|
372
|
+
return yield* serializeRunEffect(deps, initialRun.id)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
for (let iteration = 0; iteration < MAX_DISPATCH_ITERATIONS; iteration += 1) {
|
|
376
|
+
const run = yield* deps.planRun.getRunById(params.runId)
|
|
377
|
+
if (STABLE_RUN_STATUSES.has(run.status) || run.status !== 'running' || !run.currentNodeId) {
|
|
378
|
+
return yield* serializeRunEffect(deps, run.id)
|
|
407
379
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
380
|
+
|
|
381
|
+
const spec = yield* deps.planRun.getPlanSpecById(run.planSpecId)
|
|
382
|
+
|
|
383
|
+
if (spec.executionMode === 'graph-full') {
|
|
384
|
+
const dispatchReadyNodeForGraphFull: GraphFullDispatchReadyNode = (dispatchParams) =>
|
|
385
|
+
dispatchReadyNodeEffect(deps, dispatchParams)
|
|
386
|
+
|
|
387
|
+
yield* routeGraphFullEffect(
|
|
388
|
+
{ threadId: recordIdToString(run.threadId, TABLES.THREAD), runId: recordIdToString(run.id, TABLES.PLAN_RUN) },
|
|
389
|
+
{
|
|
390
|
+
dispatchReadyNode: dispatchReadyNodeForGraphFull,
|
|
391
|
+
planExecutorService: deps.planExecutor,
|
|
392
|
+
planRunService: deps.planRun,
|
|
415
393
|
},
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
394
|
+
).pipe(
|
|
395
|
+
Effect.mapError(
|
|
396
|
+
() =>
|
|
397
|
+
new ConfigurationError({
|
|
398
|
+
message: `Failed to route graph-full execution for run ${recordIdToString(run.id, TABLES.PLAN_RUN)}.`,
|
|
399
|
+
}),
|
|
400
|
+
),
|
|
401
|
+
)
|
|
402
|
+
return yield* serializeRunEffect(deps, run.id)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const currentNodeId = run.currentNodeId
|
|
406
|
+
if (!currentNodeId) {
|
|
407
|
+
return yield* serializeRunEffect(deps, run.id)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const nodeSpecRecord = yield* deps.planRun.getNodeSpecByNodeId(spec.id, currentNodeId)
|
|
411
|
+
const planNode = toPlanNodeSpec(nodeSpecRecord)
|
|
412
|
+
if (planNode.owner.executorType === 'user') {
|
|
413
|
+
return yield* serializeRunEffect(deps, run.id)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const nodeId = nodeSpecRecord.nodeId
|
|
417
|
+
if (!nodeId) {
|
|
418
|
+
return yield* new ConfigurationError({
|
|
419
|
+
message: `Node spec "${nodeSpecRecord.label}" is missing an executable node id.`,
|
|
420
|
+
})
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const nodeRun = yield* deps.planRun.getNodeRunByNodeId(run.id, nodeId)
|
|
424
|
+
if (nodeRun.status === 'monitoring') {
|
|
425
|
+
return yield* serializeRunEffect(deps, run.id)
|
|
426
|
+
}
|
|
427
|
+
if (nodeRun.status !== 'running') {
|
|
428
|
+
return yield* serializeRunEffect(deps, run.id)
|
|
429
|
+
}
|
|
430
|
+
if (shouldPlanNodeUseVisibleTurn(spec, nodeSpecRecord)) {
|
|
431
|
+
return yield* serializeRunEffect(deps, run.id)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const [artifacts, dispatchContext] = yield* Effect.all([
|
|
435
|
+
deps.planRun
|
|
436
|
+
.listArtifacts(run.id)
|
|
437
|
+
.pipe(Effect.mapError((cause) => toDispatchDatabaseError('Failed to load run artifacts.', cause))),
|
|
438
|
+
buildDispatchContextEffect(deps, run, spec, nodeSpecRecord),
|
|
439
|
+
])
|
|
440
|
+
const inputArtifacts = artifacts
|
|
441
|
+
.filter((artifact) => nodeSpecRecord.upstreamNodeIds.includes(artifact.nodeId))
|
|
442
|
+
.map((artifact) => toArtifactSubmission(artifact))
|
|
443
|
+
|
|
444
|
+
const dispatchExit = yield* Effect.exit(
|
|
445
|
+
Effect.gen(function* () {
|
|
446
|
+
const result = yield* dispatchNodeEffect(deps, {
|
|
447
|
+
nodeSpec: planNode,
|
|
448
|
+
resolvedInput: nodeRun.resolvedInput ?? {},
|
|
449
|
+
inputArtifacts,
|
|
450
|
+
context: { ...dispatchContext, nodeId: planNode.id },
|
|
451
|
+
executionMode: spec.executionMode,
|
|
452
|
+
schemaRegistry: spec.schemaRegistry,
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
yield* tryDispatchPromise(
|
|
456
|
+
() =>
|
|
457
|
+
deps.planExecutor.submitNodeResult({
|
|
458
|
+
threadId: run.threadId,
|
|
459
|
+
runId: recordIdToString(run.id, TABLES.PLAN_RUN),
|
|
460
|
+
nodeId: planNode.id,
|
|
461
|
+
emittedBy: planNode.owner.ref,
|
|
462
|
+
result,
|
|
463
|
+
}),
|
|
464
|
+
'Failed to submit plan node result.',
|
|
465
|
+
)
|
|
466
|
+
}),
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
if (Exit.isFailure(dispatchExit)) {
|
|
470
|
+
const failure = Cause.squash(dispatchExit.cause)
|
|
471
|
+
yield* tryDispatchPromise(
|
|
472
|
+
() =>
|
|
473
|
+
deps.planExecutor.blockNodeOnDispatchFailure({
|
|
474
|
+
threadId: run.threadId,
|
|
475
|
+
runId: recordIdToString(run.id, TABLES.PLAN_RUN),
|
|
476
|
+
nodeId: planNode.id,
|
|
477
|
+
emittedBy: planNode.owner.ref,
|
|
478
|
+
message: formatDispatchError(failure),
|
|
479
|
+
failureClass: classifyDispatchFailure({ ownerType: planNode.owner.executorType, error: failure }),
|
|
480
|
+
}),
|
|
481
|
+
'Failed to block plan node on dispatch failure.',
|
|
482
|
+
)
|
|
483
|
+
return yield* serializeRunEffect(deps, run.id)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return yield* new ConfigurationError({
|
|
488
|
+
message: `Ownership dispatch exceeded ${MAX_DISPATCH_ITERATIONS} iterations for run ${recordIdToString(
|
|
489
|
+
ensureRecordId(params.runId, TABLES.PLAN_RUN),
|
|
490
|
+
TABLES.PLAN_RUN,
|
|
491
|
+
)}.`,
|
|
492
|
+
})
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
const dispatchReadyNodeEffect = (
|
|
496
|
+
deps: OwnershipDispatcherDeps,
|
|
497
|
+
params: {
|
|
498
|
+
run: PlanRunRecord
|
|
499
|
+
nodeSpecRecord: PlanNodeSpecRecord
|
|
500
|
+
nodeRun: PlanNodeRunRecord
|
|
501
|
+
spec: PlanSpecRecord
|
|
502
|
+
executionModeOverride?: ExecutionMode
|
|
503
|
+
},
|
|
504
|
+
) =>
|
|
505
|
+
Effect.gen(function* () {
|
|
506
|
+
if (shouldPlanNodeUseVisibleTurn(params.spec, params.nodeSpecRecord)) {
|
|
507
|
+
return yield* new BadRequestError({
|
|
508
|
+
message: `Node "${params.nodeSpecRecord.nodeId}" requires a visible plan turn and cannot be silently dispatched.`,
|
|
419
509
|
})
|
|
420
510
|
}
|
|
421
511
|
|
|
422
|
-
|
|
423
|
-
|
|
512
|
+
const planNode = toPlanNodeSpec(params.nodeSpecRecord)
|
|
513
|
+
const [artifacts, dispatchContext] = yield* Effect.all([
|
|
514
|
+
deps.planRun
|
|
515
|
+
.listArtifacts(params.run.id)
|
|
516
|
+
.pipe(Effect.mapError((cause) => toDispatchDatabaseError('Failed to load run artifacts.', cause))),
|
|
517
|
+
buildDispatchContextEffect(deps, params.run, params.spec, params.nodeSpecRecord),
|
|
518
|
+
])
|
|
519
|
+
const inputArtifacts = artifacts
|
|
520
|
+
.filter((artifact) => params.nodeSpecRecord.upstreamNodeIds.includes(artifact.nodeId))
|
|
521
|
+
.map((artifact) => toArtifactSubmission(artifact))
|
|
522
|
+
|
|
523
|
+
return yield* dispatchNodeEffect(deps, {
|
|
524
|
+
nodeSpec: planNode,
|
|
525
|
+
resolvedInput: params.nodeRun.resolvedInput ?? {},
|
|
526
|
+
inputArtifacts,
|
|
527
|
+
context: { ...dispatchContext, nodeId: planNode.id },
|
|
528
|
+
executionMode: params.spec.executionMode,
|
|
529
|
+
executionModeOverride: params.executionModeOverride,
|
|
530
|
+
schemaRegistry: params.spec.schemaRegistry,
|
|
531
|
+
})
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
function resolveExecutionVisibility(params: { spec: PlanSpecRecord; nodeSpecRecord: PlanNodeSpecRecord }) {
|
|
535
|
+
return resolvePlanNodeExecutionVisibility(params.spec, params.nodeSpecRecord)
|
|
424
536
|
}
|
|
425
537
|
|
|
426
|
-
export
|
|
538
|
+
export function makeOwnershipDispatcherService(deps: OwnershipDispatcherDeps) {
|
|
539
|
+
return {
|
|
540
|
+
validateDraftExecutors(draft: PlanDraft) {
|
|
541
|
+
return validateDraftExecutors(deps, draft)
|
|
542
|
+
},
|
|
543
|
+
dispatchRunToStableBoundary(params: { runId: RecordIdInput; emittedBy: string }) {
|
|
544
|
+
return dispatchRunToStableBoundaryEffect(deps, params)
|
|
545
|
+
},
|
|
546
|
+
dispatchReadyNode(params: {
|
|
547
|
+
run: PlanRunRecord
|
|
548
|
+
nodeSpecRecord: PlanNodeSpecRecord
|
|
549
|
+
nodeRun: PlanNodeRunRecord
|
|
550
|
+
spec: PlanSpecRecord
|
|
551
|
+
executionModeOverride?: ExecutionMode
|
|
552
|
+
}) {
|
|
553
|
+
return dispatchReadyNodeEffect(deps, params)
|
|
554
|
+
},
|
|
555
|
+
resolveExecutionVisibility(params: { spec: PlanSpecRecord; nodeSpecRecord: PlanNodeSpecRecord }) {
|
|
556
|
+
return resolveExecutionVisibility(params)
|
|
557
|
+
},
|
|
558
|
+
dispatchNode(params: {
|
|
559
|
+
nodeSpec: PlanNodeSpec
|
|
560
|
+
resolvedInput: Record<string, unknown>
|
|
561
|
+
inputArtifacts: PlanArtifactSubmission[]
|
|
562
|
+
context: OwnershipDispatchContext
|
|
563
|
+
executionMode?: ExecutionMode
|
|
564
|
+
executionModeOverride?: ExecutionMode
|
|
565
|
+
schemaRegistry?: PlanSchemaRegistry
|
|
566
|
+
}) {
|
|
567
|
+
return dispatchNodeEffect(deps, params)
|
|
568
|
+
},
|
|
569
|
+
} as const
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export interface OwnershipDispatcherService extends ReturnType<typeof makeOwnershipDispatcherService> {}
|
|
573
|
+
|
|
574
|
+
export class OwnershipDispatcherServiceTag extends Context.Service<
|
|
575
|
+
OwnershipDispatcherServiceTag,
|
|
576
|
+
OwnershipDispatcherService
|
|
577
|
+
>()('@lota-sdk/core/OwnershipDispatcherService') {}
|
|
578
|
+
|
|
579
|
+
export const OwnershipDispatcherServiceLive = Layer.effect(
|
|
580
|
+
OwnershipDispatcherServiceTag,
|
|
581
|
+
Effect.gen(function* () {
|
|
582
|
+
const currentContext = yield* Effect.context()
|
|
583
|
+
const provideCurrentContext = <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, never> =>
|
|
584
|
+
effect.pipe(Effect.provide(currentContext)) as Effect.Effect<A, E, never>
|
|
585
|
+
const db = yield* DatabaseServiceTag
|
|
586
|
+
const agentConfig = yield* AgentConfigServiceTag
|
|
587
|
+
const runtimeAdapters = yield* RuntimeAdaptersServiceTag
|
|
588
|
+
const planRun = yield* PlanRunServiceTag
|
|
589
|
+
const agentExecutor = yield* AgentExecutorServiceTag
|
|
590
|
+
const monitoringWindow = yield* MonitoringWindowServiceTag
|
|
591
|
+
const planExecutor = yield* PlanExecutorServiceTag
|
|
592
|
+
const pluginExecutor = yield* PluginExecutorServiceTag
|
|
593
|
+
const skillResolver = yield* SkillResolverServiceTag
|
|
594
|
+
const systemExecutor = yield* SystemExecutorServiceTag
|
|
595
|
+
const user = yield* UserServiceTag
|
|
596
|
+
const executeAgentNode = agentExecutor.executeNode as OwnershipDispatcherDeps['agentExecutor']['executeNode']
|
|
597
|
+
return makeOwnershipDispatcherService({
|
|
598
|
+
db,
|
|
599
|
+
agentConfig,
|
|
600
|
+
runtimeAdapters,
|
|
601
|
+
agentExecutor: { executeNode: executeAgentNode },
|
|
602
|
+
monitoringWindow: {
|
|
603
|
+
startMonitoringWindow: (params) => provideCurrentContext(monitoringWindow.startMonitoringWindow(params)),
|
|
604
|
+
},
|
|
605
|
+
planExecutor,
|
|
606
|
+
planRun,
|
|
607
|
+
pluginExecutor: {
|
|
608
|
+
validateOwner: (owner, nodeId) => pluginExecutor.validateOwner(owner, nodeId),
|
|
609
|
+
executeNode: (params) => provideCurrentContext(pluginExecutor.executeNode(params)),
|
|
610
|
+
},
|
|
611
|
+
skillResolver: { resolve: (params) => provideCurrentContext(skillResolver.resolve(params)) },
|
|
612
|
+
systemExecutor: {
|
|
613
|
+
validateOwner: (owner, nodeId) => systemExecutor.validateOwner(owner, nodeId),
|
|
614
|
+
executeNode: (params) => provideCurrentContext(systemExecutor.executeNode(params)),
|
|
615
|
+
},
|
|
616
|
+
user: { getUser: (userId) => provideCurrentContext(user.getUser(userId)) },
|
|
617
|
+
})
|
|
618
|
+
}),
|
|
619
|
+
)
|