@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.
- package/package.json +2 -2
- package/src/ai/embedding-cache.ts +3 -1
- package/src/ai-gateway/ai-gateway.ts +164 -82
- package/src/ai-gateway/index.ts +16 -1
- package/src/config/agent-defaults.ts +4 -107
- package/src/config/agent-types.ts +1 -1
- package/src/config/background-processing.ts +1 -1
- package/src/config/index.ts +0 -1
- package/src/config/logger.ts +22 -25
- package/src/config/thread-defaults.ts +1 -10
- package/src/create-runtime.ts +145 -670
- package/src/db/base.service.ts +30 -38
- package/src/db/memory-query-builder.ts +2 -1
- package/src/db/memory-store.ts +29 -20
- package/src/db/memory.ts +188 -195
- package/src/db/service-normalization.ts +97 -64
- package/src/db/service.ts +496 -384
- package/src/db/startup.ts +30 -19
- package/src/effect/helpers.ts +30 -5
- package/src/effect/index.ts +7 -7
- package/src/effect/layers.ts +75 -72
- package/src/effect/services.ts +15 -11
- package/src/embeddings/provider.ts +65 -71
- package/src/index.ts +13 -12
- package/src/queues/autonomous-job.queue.ts +177 -143
- package/src/queues/context-compaction.queue.ts +41 -39
- package/src/queues/delayed-node-promotion.queue.ts +61 -42
- package/src/queues/document-processor.queue.ts +5 -3
- package/src/queues/index.ts +1 -0
- package/src/queues/memory-consolidation.queue.ts +79 -53
- package/src/queues/organization-learning.queue.ts +70 -33
- package/src/queues/plan-agent-heartbeat.queue.ts +111 -83
- package/src/queues/plan-scheduler.queue.ts +101 -97
- package/src/queues/post-chat-memory.queue.ts +56 -46
- package/src/queues/queue-factory.ts +146 -69
- package/src/queues/queues.service.ts +61 -0
- package/src/queues/title-generation.queue.ts +44 -44
- package/src/redis/connection.ts +181 -164
- package/src/redis/org-memory-lock.ts +24 -9
- package/src/redis/redis-lease-lock.ts +8 -1
- package/src/redis/stream-context.ts +17 -9
- package/src/runtime/agent-identity-overrides.ts +7 -3
- package/src/runtime/agent-runtime-policy.ts +10 -5
- package/src/runtime/agent-stream-helpers.ts +24 -15
- package/src/runtime/chat-run-orchestration.ts +1 -1
- package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
- package/src/runtime/context-compaction/context-compaction.ts +131 -85
- package/src/runtime/domain-layer.ts +203 -0
- package/src/runtime/execution-plan-visibility.ts +5 -2
- package/src/runtime/graph-designer.ts +0 -14
- package/src/runtime/helper-model.ts +8 -4
- package/src/runtime/index.ts +1 -1
- package/src/runtime/indexed-repositories-policy.ts +2 -6
- package/src/runtime/memory/memory-block.ts +19 -9
- package/src/runtime/memory/memory-pipeline.ts +53 -66
- package/src/runtime/memory/memory-scope.ts +33 -29
- package/src/runtime/plugin-resolution.ts +58 -62
- package/src/runtime/post-turn-side-effects.ts +139 -161
- package/src/runtime/retrieval-adapters.ts +4 -4
- package/src/runtime/runtime-config.ts +3 -9
- package/src/runtime/runtime-extensions.ts +0 -43
- package/src/runtime/runtime-lifecycle.ts +124 -0
- package/src/runtime/runtime-services.ts +455 -0
- package/src/runtime/runtime-worker-registry.ts +113 -30
- package/src/runtime/social-chat/social-chat-agent-runner.ts +13 -8
- package/src/runtime/social-chat/social-chat-history.ts +24 -13
- package/src/runtime/social-chat/social-chat.ts +420 -369
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +64 -57
- package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
- package/src/runtime/thread-chat-helpers.ts +18 -9
- package/src/runtime/thread-turn-context.ts +28 -74
- package/src/runtime/turn-lifecycle.ts +6 -14
- package/src/services/agent-activity.service.ts +169 -176
- package/src/services/agent-executor.service.ts +207 -196
- package/src/services/artifact.service.ts +10 -5
- package/src/services/attachment.service.ts +16 -48
- package/src/services/autonomous-job.service.ts +81 -87
- package/src/services/background-work.service.ts +54 -0
- package/src/services/chat-run-registry.service.ts +3 -1
- package/src/services/context-compaction.service.ts +8 -10
- package/src/services/document-chunk.service.ts +8 -17
- package/src/services/execution-plan/execution-plan-graph.ts +122 -109
- package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
- package/src/services/execution-plan/execution-plan.service.ts +68 -51
- package/src/services/feedback-loop.service.ts +1 -1
- package/src/services/global-orchestrator.service.ts +49 -15
- package/src/services/graph-full-routing.ts +49 -37
- package/src/services/index.ts +1 -0
- package/src/services/institutional-memory.service.ts +8 -17
- package/src/services/learned-skill.service.ts +38 -35
- package/src/services/memory/memory-conversation.ts +10 -5
- package/src/services/memory/memory-errors.ts +27 -0
- package/src/services/memory/memory-org-memory.ts +14 -3
- package/src/services/memory/memory-preseeded.ts +10 -4
- package/src/services/memory/memory-utils.ts +2 -1
- package/src/services/memory/memory.service.ts +37 -52
- package/src/services/memory/rerank.service.ts +3 -11
- package/src/services/monitoring-window.service.ts +1 -1
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/node-workspace.service.ts +2 -2
- package/src/services/notification.service.ts +16 -4
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/organization.service.ts +34 -51
- package/src/services/ownership-dispatcher.service.ts +148 -95
- package/src/services/plan/plan-agent-heartbeat.service.ts +30 -16
- package/src/services/plan/plan-agent-query.service.ts +13 -9
- package/src/services/plan/plan-approval.service.ts +52 -48
- package/src/services/plan/plan-artifact.service.ts +2 -2
- package/src/services/plan/plan-builder.service.ts +2 -2
- package/src/services/plan/plan-checkpoint.service.ts +1 -1
- package/src/services/plan/plan-compiler.service.ts +1 -1
- package/src/services/plan/plan-completion-side-effects.ts +99 -113
- package/src/services/plan/plan-coordination.service.ts +1 -1
- package/src/services/plan/plan-cycle.service.ts +171 -202
- package/src/services/plan/plan-deadline.service.ts +304 -307
- package/src/services/plan/plan-event-delivery.service.ts +84 -72
- package/src/services/plan/plan-executor-context.ts +2 -0
- package/src/services/plan/plan-executor-graph.ts +375 -353
- package/src/services/plan/plan-executor-helpers.ts +60 -75
- package/src/services/plan/plan-executor.service.ts +494 -489
- package/src/services/plan/plan-run.service.ts +12 -19
- package/src/services/plan/plan-scheduler.service.ts +89 -82
- package/src/services/plan/plan-template.service.ts +1 -1
- package/src/services/plan/plan-transaction-events.ts +8 -5
- package/src/services/plan/plan-validator.service.ts +1 -1
- package/src/services/plan/plan-workspace.service.ts +17 -11
- package/src/services/plugin-executor.service.ts +26 -21
- package/src/services/quality-metrics.service.ts +1 -1
- package/src/services/queue-job.service.ts +8 -17
- package/src/services/recent-activity-title.service.ts +22 -10
- package/src/services/recent-activity.service.ts +1 -1
- package/src/services/skill-resolver.service.ts +1 -1
- package/src/services/social-chat-history.service.ts +37 -20
- package/src/services/system-executor.service.ts +25 -20
- package/src/services/thread/thread-bootstrap.ts +37 -19
- package/src/services/thread/thread-listing.ts +2 -1
- package/src/services/thread/thread-memory-block.ts +18 -5
- package/src/services/thread/thread-message.service.ts +30 -13
- package/src/services/thread/thread-title.service.ts +1 -1
- package/src/services/thread/thread-turn-execution.ts +87 -83
- package/src/services/thread/thread-turn-preparation.service.ts +65 -40
- package/src/services/thread/thread-turn-streaming.ts +32 -36
- package/src/services/thread/thread-turn.ts +43 -29
- package/src/services/thread/thread.service.ts +32 -8
- package/src/services/user.service.ts +1 -1
- package/src/services/write-intent-validator.service.ts +1 -1
- package/src/storage/attachment-storage.service.ts +7 -4
- package/src/storage/generated-document-storage.service.ts +1 -1
- package/src/system-agents/context-compaction.agent.ts +1 -1
- package/src/system-agents/helper-agent-options.ts +1 -1
- package/src/system-agents/memory-reranker.agent.ts +1 -1
- package/src/system-agents/memory.agent.ts +1 -1
- package/src/system-agents/recent-activity-title-refiner.agent.ts +9 -6
- package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
- package/src/system-agents/skill-extractor.agent.ts +1 -1
- package/src/system-agents/skill-manager.agent.ts +1 -1
- package/src/system-agents/thread-router.agent.ts +23 -20
- package/src/system-agents/title-generator.agent.ts +1 -1
- package/src/tools/execution-plan.tool.ts +36 -20
- package/src/tools/fetch-webpage.tool.ts +30 -22
- package/src/tools/firecrawl-client.ts +1 -6
- package/src/tools/plan-approval.tool.ts +9 -1
- package/src/tools/remember-memory.tool.ts +3 -6
- package/src/tools/research-topic.tool.ts +12 -3
- package/src/tools/search-web.tool.ts +26 -18
- package/src/tools/search.tool.ts +4 -5
- package/src/tools/team-think.tool.ts +139 -121
- package/src/utils/async.ts +15 -6
- package/src/utils/errors.ts +27 -15
- package/src/workers/bootstrap.ts +34 -58
- package/src/workers/memory-consolidation.worker.ts +4 -1
- package/src/workers/organization-learning.worker.ts +16 -3
- package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
- package/src/workers/regular-chat-memory-digest.runner.ts +46 -29
- package/src/workers/skill-extraction.runner.ts +13 -15
- package/src/workers/worker-utils.ts +14 -8
- package/src/config/search.ts +0 -3
- package/src/effect/awaitable-effect.ts +0 -87
- package/src/effect/runtime-ref.ts +0 -25
- package/src/effect/runtime.ts +0 -31
- package/src/redis/runtime-connection.ts +0 -10
- package/src/runtime/agent-types.ts +0 -1
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExecutionPlanToolResultData,
|
|
3
|
-
PlanFailureClass,
|
|
4
|
-
PlanNodeResultSubmission,
|
|
5
|
-
SerializableExecutionPlan,
|
|
6
|
-
} from '@lota-sdk/shared'
|
|
1
|
+
import type { PlanFailureClass, PlanNodeResultSubmission } from '@lota-sdk/shared'
|
|
7
2
|
import { PlanNodeAttemptSchema, PlanNodeRunSchema } from '@lota-sdk/shared'
|
|
8
3
|
import { Context, Schema, Effect, Layer } from 'effect'
|
|
4
|
+
import type { z } from 'zod'
|
|
9
5
|
|
|
10
6
|
import { aiLogger } from '../../config/logger'
|
|
11
7
|
import type { RecordIdInput } from '../../db/record-id'
|
|
@@ -14,12 +10,14 @@ import type { DatabaseTransaction } from '../../db/service'
|
|
|
14
10
|
import { TABLES } from '../../db/tables'
|
|
15
11
|
import { BadRequestError, NotFoundError } from '../../effect/errors'
|
|
16
12
|
import { effectTryPromise as effectTryPromiseShared } from '../../effect/helpers'
|
|
17
|
-
import { runPromise } from '../../effect/runtime'
|
|
18
13
|
import { DatabaseServiceTag } from '../../effect/services'
|
|
14
|
+
import type { DelayedNodePromotionQueueRuntime } from '../../queues/delayed-node-promotion.queue'
|
|
15
|
+
import { LotaQueuesServiceTag } from '../../queues/queues.service'
|
|
19
16
|
import { GeneratedDocumentStorageServiceTag } from '../../storage/generated-document-storage.service'
|
|
20
17
|
import { nowDate } from '../../utils/date-time'
|
|
21
18
|
import { toError } from '../../utils/errors'
|
|
22
19
|
import { ArtifactServiceTag } from '../artifact.service'
|
|
20
|
+
import { BackgroundWorkService } from '../background-work.service'
|
|
23
21
|
import { FeedbackLoopServiceTag } from '../feedback-loop.service'
|
|
24
22
|
import { InstitutionalMemoryServiceTag } from '../institutional-memory.service'
|
|
25
23
|
import { QualityMetricsServiceTag } from '../quality-metrics.service'
|
|
@@ -74,6 +72,7 @@ interface PlanExecutorDeps {
|
|
|
74
72
|
planSchedulerService: Context.Service.Shape<typeof PlanSchedulerServiceTag>
|
|
75
73
|
planValidatorService: Context.Service.Shape<typeof PlanValidatorServiceTag>
|
|
76
74
|
qualityMetricsService: Context.Service.Shape<typeof QualityMetricsServiceTag>
|
|
75
|
+
delayedNodePromotionQueue: DelayedNodePromotionQueueRuntime
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
type PlanExecutorService = ReturnType<typeof makePlanExecutorService>
|
|
@@ -97,6 +96,17 @@ function fromPromise<A>(thunk: () => PromiseLike<A> | Effect.Effect<A, unknown>)
|
|
|
97
96
|
)
|
|
98
97
|
}
|
|
99
98
|
|
|
99
|
+
function parseRowOrFail<T>(
|
|
100
|
+
schema: z.ZodType<T>,
|
|
101
|
+
value: unknown,
|
|
102
|
+
operation: string,
|
|
103
|
+
): Effect.Effect<T, PlanExecutorInternalError> {
|
|
104
|
+
return Effect.try({
|
|
105
|
+
try: () => schema.parse(value),
|
|
106
|
+
catch: (cause) => new PlanExecutorInternalError({ message: `Failed to parse ${operation} row`, cause }),
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
100
110
|
function withDatabaseTransactionEffect<A, E, R>(
|
|
101
111
|
databaseService: Context.Service.Shape<typeof DatabaseServiceTag>,
|
|
102
112
|
run: (tx: DatabaseTransaction) => Effect.Effect<A, E, R>,
|
|
@@ -310,7 +320,7 @@ function persistNodeResultAttemptEffect(
|
|
|
310
320
|
issues: [...submission.validation.blocking, ...submission.validation.warnings],
|
|
311
321
|
})
|
|
312
322
|
|
|
313
|
-
|
|
323
|
+
const updatedAttemptRow = yield* fromPromise(() =>
|
|
314
324
|
tx
|
|
315
325
|
.update(ensureRecordId(attempt.id, TABLES.PLAN_NODE_ATTEMPT))
|
|
316
326
|
.content({
|
|
@@ -327,7 +337,8 @@ function persistNodeResultAttemptEffect(
|
|
|
327
337
|
...(attempt.failureClass ? { failureClass: attempt.failureClass } : {}),
|
|
328
338
|
})
|
|
329
339
|
.output('after'),
|
|
330
|
-
)
|
|
340
|
+
)
|
|
341
|
+
void (yield* parseRowOrFail(PlanNodeAttemptSchema, updatedAttemptRow, 'plan node attempt'))
|
|
331
342
|
|
|
332
343
|
const publishedArtifacts =
|
|
333
344
|
submission.validation.blocking.length > 0
|
|
@@ -351,21 +362,20 @@ function persistNodeResultAttemptEffect(
|
|
|
351
362
|
artifacts: publishedArtifacts,
|
|
352
363
|
})
|
|
353
364
|
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
.
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
.output('after'),
|
|
367
|
-
),
|
|
365
|
+
const nextNodeRunRow = yield* fromPromise(() =>
|
|
366
|
+
tx
|
|
367
|
+
.update(ensureRecordId(submission.nodeRun.id, TABLES.PLAN_NODE_RUN))
|
|
368
|
+
.content(
|
|
369
|
+
toNodeRunData(submission.nodeRun, {
|
|
370
|
+
attemptCount: submission.nodeRun.attemptCount + 1,
|
|
371
|
+
latestAttemptId: attempt.id,
|
|
372
|
+
latestStructuredOutput: result.structuredOutput ?? null,
|
|
373
|
+
latestNotes: result.notes,
|
|
374
|
+
}),
|
|
375
|
+
)
|
|
376
|
+
.output('after'),
|
|
368
377
|
)
|
|
378
|
+
const nextNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, nextNodeRunRow, 'plan node run')
|
|
369
379
|
|
|
370
380
|
const nodeRuns = yield* context.planRunService.listNodeRuns(submission.run.id)
|
|
371
381
|
|
|
@@ -391,24 +401,23 @@ function handleRetryNodeResultEffect(
|
|
|
391
401
|
const { tx, emittedEvents, submission, persisted, emittedBy } = params
|
|
392
402
|
|
|
393
403
|
return Effect.gen(function* () {
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
.
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
.output('after'),
|
|
410
|
-
),
|
|
404
|
+
const retryNodeRunRow = yield* fromPromise(() =>
|
|
405
|
+
tx
|
|
406
|
+
.update(ensureRecordId(persisted.nextNodeRun.id, TABLES.PLAN_NODE_RUN))
|
|
407
|
+
.content(
|
|
408
|
+
toNodeRunData(persisted.nextNodeRun, {
|
|
409
|
+
status: 'ready',
|
|
410
|
+
retryCount: persisted.nextNodeRun.retryCount + 1,
|
|
411
|
+
failureClass: submission.validation.failureClass,
|
|
412
|
+
blockedReason: submission.validation.blocking[0]?.message ?? null,
|
|
413
|
+
readyAt: nowDate(),
|
|
414
|
+
startedAt: null,
|
|
415
|
+
completedAt: null,
|
|
416
|
+
}),
|
|
417
|
+
)
|
|
418
|
+
.output('after'),
|
|
411
419
|
)
|
|
420
|
+
const retryNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, retryNodeRunRow, 'plan node run')
|
|
412
421
|
|
|
413
422
|
yield* emitEvent({
|
|
414
423
|
tx,
|
|
@@ -477,22 +486,21 @@ function handleHumanReviewNodeResultEffect(
|
|
|
477
486
|
const { tx, emittedEvents, submission, persisted, emittedBy } = params
|
|
478
487
|
|
|
479
488
|
return Effect.gen(function* () {
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
.
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
.output('after'),
|
|
494
|
-
),
|
|
489
|
+
const awaitingHumanNodeRunRow = yield* fromPromise(() =>
|
|
490
|
+
tx
|
|
491
|
+
.update(ensureRecordId(persisted.nextNodeRun.id, TABLES.PLAN_NODE_RUN))
|
|
492
|
+
.content(
|
|
493
|
+
toNodeRunData(persisted.nextNodeRun, {
|
|
494
|
+
status: 'awaiting-human',
|
|
495
|
+
retryCount: persisted.nextNodeRun.retryCount + 1,
|
|
496
|
+
failureClass: submission.validation.failureClass,
|
|
497
|
+
blockedReason: submission.validation.blocking[0]?.message ?? null,
|
|
498
|
+
startedAt: persisted.nextNodeRun.startedAt ?? nowDate(),
|
|
499
|
+
}),
|
|
500
|
+
)
|
|
501
|
+
.output('after'),
|
|
495
502
|
)
|
|
503
|
+
const awaitingHumanNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, awaitingHumanNodeRunRow, 'plan node run')
|
|
496
504
|
|
|
497
505
|
const approval = yield* context.planApprovalService
|
|
498
506
|
.createPendingApproval({
|
|
@@ -561,21 +569,20 @@ function handleReplanNodeResultEffect(
|
|
|
561
569
|
const { tx, emittedEvents, submission, persisted, emittedBy } = params
|
|
562
570
|
|
|
563
571
|
return Effect.gen(function* () {
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
.
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
.output('after'),
|
|
577
|
-
),
|
|
572
|
+
const blockedNodeRunRow = yield* fromPromise(() =>
|
|
573
|
+
tx
|
|
574
|
+
.update(ensureRecordId(persisted.nextNodeRun.id, TABLES.PLAN_NODE_RUN))
|
|
575
|
+
.content(
|
|
576
|
+
toNodeRunData(persisted.nextNodeRun, {
|
|
577
|
+
status: 'blocked',
|
|
578
|
+
retryCount: persisted.nextNodeRun.retryCount + 1,
|
|
579
|
+
failureClass: submission.validation.failureClass,
|
|
580
|
+
blockedReason: submission.validation.blocking[0]?.message ?? null,
|
|
581
|
+
}),
|
|
582
|
+
)
|
|
583
|
+
.output('after'),
|
|
578
584
|
)
|
|
585
|
+
const blockedNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, blockedNodeRunRow, 'plan node run')
|
|
579
586
|
|
|
580
587
|
const blockedRun = yield* replaceRun(tx, submission.run, {
|
|
581
588
|
status: 'blocked',
|
|
@@ -629,22 +636,21 @@ function handleFailedNodeResultEffect(
|
|
|
629
636
|
const { tx, emittedEvents, submission, persisted, emittedBy } = params
|
|
630
637
|
|
|
631
638
|
return Effect.gen(function* () {
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
.
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
.output('after'),
|
|
646
|
-
),
|
|
639
|
+
const failedNodeRunRow = yield* fromPromise(() =>
|
|
640
|
+
tx
|
|
641
|
+
.update(ensureRecordId(persisted.nextNodeRun.id, TABLES.PLAN_NODE_RUN))
|
|
642
|
+
.content(
|
|
643
|
+
toNodeRunData(persisted.nextNodeRun, {
|
|
644
|
+
status: 'failed',
|
|
645
|
+
retryCount: persisted.nextNodeRun.retryCount + 1,
|
|
646
|
+
failureClass: submission.validation.failureClass,
|
|
647
|
+
blockedReason: submission.validation.blocking[0]?.message ?? null,
|
|
648
|
+
completedAt: nowDate(),
|
|
649
|
+
}),
|
|
650
|
+
)
|
|
651
|
+
.output('after'),
|
|
647
652
|
)
|
|
653
|
+
const failedNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, failedNodeRunRow, 'plan node run')
|
|
648
654
|
|
|
649
655
|
const failedRun = yield* replaceRun(tx, submission.run, {
|
|
650
656
|
status: 'failed',
|
|
@@ -735,24 +741,23 @@ function handleSuccessfulNodeResultEffect(
|
|
|
735
741
|
const { tx, emittedEvents, submission, persisted, emittedBy, result } = params
|
|
736
742
|
|
|
737
743
|
return Effect.gen(function* () {
|
|
738
|
-
const
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
.
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
.output('after'),
|
|
754
|
-
),
|
|
744
|
+
const completedNodeRunRow = yield* fromPromise(() =>
|
|
745
|
+
tx
|
|
746
|
+
.update(ensureRecordId(persisted.nextNodeRun.id, TABLES.PLAN_NODE_RUN))
|
|
747
|
+
.content(
|
|
748
|
+
toNodeRunData(persisted.nextNodeRun, {
|
|
749
|
+
status: submission.validation.warnings.length > 0 ? 'partial' : 'completed',
|
|
750
|
+
latestAttemptId: persisted.attemptId,
|
|
751
|
+
latestStructuredOutput: result.structuredOutput ?? null,
|
|
752
|
+
latestNotes: result.notes,
|
|
753
|
+
blockedReason: null,
|
|
754
|
+
failureClass: null,
|
|
755
|
+
completedAt: nowDate(),
|
|
756
|
+
}),
|
|
757
|
+
)
|
|
758
|
+
.output('after'),
|
|
755
759
|
)
|
|
760
|
+
const completedNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, completedNodeRunRow, 'plan node run')
|
|
756
761
|
|
|
757
762
|
yield* emitEvent({
|
|
758
763
|
tx,
|
|
@@ -949,7 +954,7 @@ function persistHumanNodeResponseAttemptEffect(
|
|
|
949
954
|
issues: [...submission.validation.blocking, ...submission.validation.warnings],
|
|
950
955
|
})
|
|
951
956
|
|
|
952
|
-
|
|
957
|
+
const updatedHumanAttemptRow = yield* fromPromise(() =>
|
|
953
958
|
tx
|
|
954
959
|
.update(ensureRecordId(attempt.id, TABLES.PLAN_NODE_ATTEMPT))
|
|
955
960
|
.content({
|
|
@@ -966,27 +971,27 @@ function persistHumanNodeResponseAttemptEffect(
|
|
|
966
971
|
...(attempt.failureClass ? { failureClass: attempt.failureClass } : {}),
|
|
967
972
|
})
|
|
968
973
|
.output('after'),
|
|
969
|
-
)
|
|
974
|
+
)
|
|
975
|
+
void (yield* parseRowOrFail(PlanNodeAttemptSchema, updatedHumanAttemptRow, 'plan node attempt'))
|
|
970
976
|
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
.
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
.output('after'),
|
|
988
|
-
),
|
|
977
|
+
const nextNodeRunRow = yield* fromPromise(() =>
|
|
978
|
+
tx
|
|
979
|
+
.update(ensureRecordId(submission.nodeRun.id, TABLES.PLAN_NODE_RUN))
|
|
980
|
+
.content(
|
|
981
|
+
toNodeRunData(submission.nodeRun, {
|
|
982
|
+
status: submission.validation.blocking.length > 0 ? 'blocked' : 'completed',
|
|
983
|
+
attemptCount: submission.nodeRun.attemptCount + 1,
|
|
984
|
+
latestAttemptId: attempt.id,
|
|
985
|
+
latestStructuredOutput: response,
|
|
986
|
+
latestNotes: submission.responseComments ?? null,
|
|
987
|
+
blockedReason: submission.validation.blocking[0]?.message ?? null,
|
|
988
|
+
failureClass: submission.validation.blocking.length > 0 ? submission.validation.failureClass : null,
|
|
989
|
+
...(submission.validation.blocking.length > 0 ? {} : { completedAt: nowDate() }),
|
|
990
|
+
}),
|
|
991
|
+
)
|
|
992
|
+
.output('after'),
|
|
989
993
|
)
|
|
994
|
+
const nextNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, nextNodeRunRow, 'plan node run')
|
|
990
995
|
|
|
991
996
|
const nodeRuns = replaceNodeRunInList(yield* context.planRunService.listNodeRuns(submission.run.id), nextNodeRun)
|
|
992
997
|
|
|
@@ -1105,8 +1110,7 @@ function handleAcceptedHumanNodeResponseEffect(
|
|
|
1105
1110
|
})
|
|
1106
1111
|
}
|
|
1107
1112
|
|
|
1108
|
-
|
|
1109
|
-
function submitNodeResult(
|
|
1113
|
+
const submitNodeResult = Effect.fn('PlanExecutor.submitNodeResult')(function* (
|
|
1110
1114
|
context: PlanExecutorContext,
|
|
1111
1115
|
params: {
|
|
1112
1116
|
threadId: RecordIdInput
|
|
@@ -1115,7 +1119,7 @@ function submitNodeResult(
|
|
|
1115
1119
|
emittedBy: string
|
|
1116
1120
|
result: PlanNodeResultSubmission
|
|
1117
1121
|
},
|
|
1118
|
-
)
|
|
1122
|
+
) {
|
|
1119
1123
|
const {
|
|
1120
1124
|
databaseService,
|
|
1121
1125
|
generatedDocumentStorageService,
|
|
@@ -1124,55 +1128,58 @@ function submitNodeResult(
|
|
|
1124
1128
|
planRunService,
|
|
1125
1129
|
} = context
|
|
1126
1130
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
})
|
|
1131
|
+
const submission = yield* loadNodeResultSubmissionContextEffect(context, params)
|
|
1132
|
+
const publishedArtifactStorageKeys: string[] = []
|
|
1133
|
+
|
|
1134
|
+
yield* withTransactionAndEventsEffect({
|
|
1135
|
+
db: databaseService,
|
|
1136
|
+
planEventDeliveryService,
|
|
1137
|
+
run: (tx, emittedEvents) =>
|
|
1138
|
+
Effect.gen(function* () {
|
|
1139
|
+
const persisted = yield* persistNodeResultAttemptEffect(context, {
|
|
1140
|
+
tx,
|
|
1141
|
+
submission,
|
|
1142
|
+
nodeId: params.nodeId,
|
|
1143
|
+
emittedBy: params.emittedBy,
|
|
1144
|
+
result: params.result,
|
|
1145
|
+
publishedArtifactStorageKeys,
|
|
1146
|
+
})
|
|
1144
1147
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
tx,
|
|
1148
|
-
emittedEvents,
|
|
1149
|
-
submission,
|
|
1150
|
-
persisted,
|
|
1151
|
-
emittedBy: params.emittedBy,
|
|
1152
|
-
})
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
return yield* handleSuccessfulNodeResultEffect(context, {
|
|
1148
|
+
if (submission.validation.blocking.length > 0) {
|
|
1149
|
+
return yield* handleBlockedNodeResultEffect(context, {
|
|
1156
1150
|
tx,
|
|
1157
1151
|
emittedEvents,
|
|
1158
1152
|
submission,
|
|
1159
1153
|
persisted,
|
|
1160
1154
|
emittedBy: params.emittedBy,
|
|
1161
|
-
result: params.result,
|
|
1162
1155
|
})
|
|
1163
|
-
}
|
|
1164
|
-
}).pipe(
|
|
1165
|
-
Effect.tapError(() =>
|
|
1166
|
-
Effect.forEach(publishedArtifactStorageKeys, (storageKey) =>
|
|
1167
|
-
generatedDocumentStorageService.deleteTextArtifact(storageKey).pipe(Effect.ignore),
|
|
1168
|
-
).pipe(Effect.asVoid),
|
|
1169
|
-
),
|
|
1170
|
-
)
|
|
1156
|
+
}
|
|
1171
1157
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1158
|
+
return yield* handleSuccessfulNodeResultEffect(context, {
|
|
1159
|
+
tx,
|
|
1160
|
+
emittedEvents,
|
|
1161
|
+
submission,
|
|
1162
|
+
persisted,
|
|
1163
|
+
emittedBy: params.emittedBy,
|
|
1164
|
+
result: params.result,
|
|
1165
|
+
})
|
|
1166
|
+
}),
|
|
1167
|
+
}).pipe(
|
|
1168
|
+
Effect.withSpan('PlanExecutor.submitNodeResult.persistTransaction'),
|
|
1169
|
+
Effect.tapError(() =>
|
|
1170
|
+
Effect.forEach(publishedArtifactStorageKeys, (storageKey) =>
|
|
1171
|
+
generatedDocumentStorageService.deleteTextArtifact(storageKey).pipe(Effect.ignore),
|
|
1172
|
+
).pipe(Effect.asVoid),
|
|
1173
|
+
),
|
|
1174
|
+
)
|
|
1174
1175
|
|
|
1175
|
-
|
|
1176
|
+
const orgId = recordIdToString(submission.run.organizationId, TABLES.ORGANIZATION)
|
|
1177
|
+
const runIdStr = recordIdToString(submission.run.id, TABLES.PLAN_RUN)
|
|
1178
|
+
|
|
1179
|
+
const background = yield* BackgroundWorkService
|
|
1180
|
+
|
|
1181
|
+
yield* background.run(
|
|
1182
|
+
fromPromise(() =>
|
|
1176
1183
|
planCompletionSideEffects.runPlanNodeCompletionSideEffects({
|
|
1177
1184
|
runId: runIdStr,
|
|
1178
1185
|
organizationId: orgId,
|
|
@@ -1192,31 +1199,36 @@ function submitNodeResult(
|
|
|
1192
1199
|
aiLogger.warn`Failed to record node completion metrics for run ${runIdStr} node ${params.nodeId}: ${error.message}`
|
|
1193
1200
|
}),
|
|
1194
1201
|
),
|
|
1195
|
-
|
|
1196
|
-
|
|
1202
|
+
),
|
|
1203
|
+
'plan-executor.recordNodeCompletionMetrics',
|
|
1204
|
+
)
|
|
1197
1205
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1206
|
+
const updatedRun = yield* planRunService.getRunById(submission.run.id)
|
|
1207
|
+
if (updatedRun.status === 'completed') {
|
|
1208
|
+
yield* background.run(
|
|
1209
|
+
fromPromise(() =>
|
|
1201
1210
|
planCompletionSideEffects.runPlanCompletionSideEffectsSafely({ runId: runIdStr, organizationId: orgId }),
|
|
1202
1211
|
).pipe(
|
|
1203
|
-
Effect.catchTag('PlanExecutorInternalError', () =>
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1212
|
+
Effect.catchTag('PlanExecutorInternalError', (error) =>
|
|
1213
|
+
Effect.sync(() => {
|
|
1214
|
+
aiLogger.warn`Plan completion side-effects failed for run ${runIdStr}: ${error.message}`
|
|
1215
|
+
}),
|
|
1216
|
+
),
|
|
1217
|
+
),
|
|
1218
|
+
'plan-executor.runPlanCompletionSideEffects',
|
|
1219
|
+
)
|
|
1220
|
+
}
|
|
1207
1221
|
|
|
1208
|
-
|
|
1222
|
+
const snapshot = yield* serializeRunFull(planRunService, updatedRun)
|
|
1209
1223
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
}
|
|
1224
|
+
return buildExecutionPlanToolResult({
|
|
1225
|
+
action: 'node-result-submitted',
|
|
1226
|
+
plan: snapshot,
|
|
1227
|
+
message: `Submitted result for node "${submission.nodeSpec.label}".`,
|
|
1228
|
+
})
|
|
1229
|
+
})
|
|
1217
1230
|
|
|
1218
|
-
|
|
1219
|
-
function submitHumanNodeResponse(
|
|
1231
|
+
const submitHumanNodeResponse = Effect.fn('PlanExecutor.submitHumanNodeResponse')(function* (
|
|
1220
1232
|
context: PlanExecutorContext,
|
|
1221
1233
|
params: {
|
|
1222
1234
|
threadId: RecordIdInput
|
|
@@ -1225,195 +1237,186 @@ function submitHumanNodeResponse(
|
|
|
1225
1237
|
response: HumanNodeResponsePayload
|
|
1226
1238
|
approvalMessageId?: string
|
|
1227
1239
|
},
|
|
1228
|
-
)
|
|
1240
|
+
) {
|
|
1229
1241
|
const { databaseService, planEventDeliveryService } = context
|
|
1230
1242
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
}
|
|
1243
|
+
const submission = yield* loadHumanNodeResponseContextEffect(context, params)
|
|
1244
|
+
if (!submission) {
|
|
1245
|
+
return null
|
|
1246
|
+
}
|
|
1236
1247
|
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1248
|
+
return yield* withTransactionAndEventsEffect({
|
|
1249
|
+
db: databaseService,
|
|
1250
|
+
planEventDeliveryService,
|
|
1251
|
+
run: (tx, emittedEvents) =>
|
|
1252
|
+
Effect.gen(function* () {
|
|
1253
|
+
const persisted = yield* persistHumanNodeResponseAttemptEffect(context, {
|
|
1254
|
+
tx,
|
|
1255
|
+
submission,
|
|
1256
|
+
respondedBy: params.respondedBy,
|
|
1257
|
+
approvalMessageId: params.approvalMessageId,
|
|
1258
|
+
response: params.response,
|
|
1259
|
+
})
|
|
1249
1260
|
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
tx,
|
|
1253
|
-
emittedEvents,
|
|
1254
|
-
submission,
|
|
1255
|
-
persisted,
|
|
1256
|
-
respondedBy: params.respondedBy,
|
|
1257
|
-
})
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
return yield* handleAcceptedHumanNodeResponseEffect(context, {
|
|
1261
|
+
if (submission.validation.blocking.length > 0) {
|
|
1262
|
+
return yield* handleBlockedHumanNodeResponseEffect(context, {
|
|
1261
1263
|
tx,
|
|
1262
1264
|
emittedEvents,
|
|
1263
1265
|
submission,
|
|
1264
1266
|
persisted,
|
|
1265
1267
|
respondedBy: params.respondedBy,
|
|
1266
1268
|
})
|
|
1267
|
-
}
|
|
1268
|
-
})
|
|
1269
|
-
}).pipe(runPromise)
|
|
1270
|
-
}
|
|
1269
|
+
}
|
|
1271
1270
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1271
|
+
return yield* handleAcceptedHumanNodeResponseEffect(context, {
|
|
1272
|
+
tx,
|
|
1273
|
+
emittedEvents,
|
|
1274
|
+
submission,
|
|
1275
|
+
persisted,
|
|
1276
|
+
respondedBy: params.respondedBy,
|
|
1277
|
+
})
|
|
1278
|
+
}),
|
|
1279
|
+
}).pipe(Effect.withSpan('PlanExecutor.submitHumanNodeResponse.persistTransaction'))
|
|
1280
|
+
})
|
|
1281
|
+
|
|
1282
|
+
const resumeRun = Effect.fn('PlanExecutor.resumeRun')(function* (
|
|
1274
1283
|
context: PlanExecutorContext,
|
|
1275
1284
|
params: { threadId: RecordIdInput; runId: string; emittedBy: string },
|
|
1276
|
-
)
|
|
1285
|
+
) {
|
|
1277
1286
|
const { databaseService, planEventDeliveryService, planRunService } = context
|
|
1278
1287
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1288
|
+
const run = yield* planRunService.getRunById(params.runId)
|
|
1289
|
+
const spec = yield* planRunService.getPlanSpecById(run.planSpecId)
|
|
1290
|
+
const [nodeSpecs, nodeRuns, artifacts, latestCheckpoint, nextCheckpointSequence] = yield* Effect.all([
|
|
1291
|
+
planRunService.listNodeSpecs(spec.id),
|
|
1292
|
+
planRunService.listNodeRuns(run.id),
|
|
1293
|
+
planRunService.listArtifacts(run.id),
|
|
1294
|
+
planRunService.getLatestCheckpoint(run.id),
|
|
1295
|
+
planRunService.getNextCheckpointSequence(run.id),
|
|
1296
|
+
]).pipe(Effect.withSpan('PlanExecutor.resumeRun.loadRunGraph'))
|
|
1297
|
+
|
|
1298
|
+
const snapshot = yield* withTransactionAndEventsEffect({
|
|
1299
|
+
db: databaseService,
|
|
1300
|
+
planEventDeliveryService,
|
|
1301
|
+
run: (tx, emittedEvents) =>
|
|
1302
|
+
Effect.gen(function* () {
|
|
1303
|
+
let currentNodeRuns = [...nodeRuns]
|
|
1304
|
+
for (const currentNodeRun of currentNodeRuns.filter((candidate) => candidate.status === 'running')) {
|
|
1305
|
+
const resetNodeRunRow = yield* fromPromise(() =>
|
|
1306
|
+
tx
|
|
1307
|
+
.update(ensureRecordId(currentNodeRun.id, TABLES.PLAN_NODE_RUN))
|
|
1308
|
+
.content(
|
|
1309
|
+
toNodeRunData(currentNodeRun, {
|
|
1310
|
+
status: 'ready',
|
|
1311
|
+
readyAt: nowDate(),
|
|
1312
|
+
startedAt: currentNodeRun.startedAt ?? nowDate(),
|
|
1313
|
+
}),
|
|
1314
|
+
)
|
|
1315
|
+
.output('after'),
|
|
1316
|
+
)
|
|
1317
|
+
const resetNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, resetNodeRunRow, 'plan node run')
|
|
1318
|
+
currentNodeRuns = currentNodeRuns.map((candidate) =>
|
|
1319
|
+
candidate.nodeId === resetNodeRun.nodeId ? resetNodeRun : candidate,
|
|
1320
|
+
)
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
const resetRun = yield* replaceRun(tx, run, {
|
|
1324
|
+
status: run.status === 'awaiting-human' ? 'awaiting-human' : 'running',
|
|
1325
|
+
currentNodeId: run.status === 'awaiting-human' ? (run.currentNodeId ?? null) : null,
|
|
1326
|
+
waitingNodeId: run.status === 'awaiting-human' ? (run.waitingNodeId ?? null) : null,
|
|
1327
|
+
readyNodeIds: currentNodeRuns
|
|
1328
|
+
.filter((candidate) => candidate.status === 'ready')
|
|
1329
|
+
.map((candidate) => candidate.nodeId),
|
|
1330
|
+
})
|
|
1322
1331
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1332
|
+
yield* emitEvent({
|
|
1333
|
+
tx,
|
|
1334
|
+
run: resetRun,
|
|
1335
|
+
spec,
|
|
1336
|
+
eventType: 'run-resumed',
|
|
1337
|
+
fromStatus: run.status,
|
|
1338
|
+
toStatus: resetRun.status,
|
|
1339
|
+
message: `Run "${spec.title}" resumed from the latest checkpoint.`,
|
|
1340
|
+
detail: latestCheckpoint
|
|
1341
|
+
? { checkpointId: recordIdToString(latestCheckpoint.id, TABLES.PLAN_CHECKPOINT) }
|
|
1342
|
+
: {},
|
|
1343
|
+
emittedBy: params.emittedBy,
|
|
1344
|
+
capturedEvents: emittedEvents,
|
|
1345
|
+
})
|
|
1337
1346
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1347
|
+
const synced =
|
|
1348
|
+
resetRun.status === 'awaiting-human'
|
|
1349
|
+
? { run: resetRun, nodeRuns: currentNodeRuns, artifacts }
|
|
1350
|
+
: yield* fromPromise(() =>
|
|
1351
|
+
syncRunGraph(context, {
|
|
1352
|
+
tx,
|
|
1353
|
+
run: resetRun,
|
|
1354
|
+
spec,
|
|
1355
|
+
nodeSpecs,
|
|
1356
|
+
nodeRuns: currentNodeRuns,
|
|
1357
|
+
artifacts,
|
|
1358
|
+
emittedBy: params.emittedBy,
|
|
1359
|
+
capturedEvents: emittedEvents,
|
|
1360
|
+
}),
|
|
1361
|
+
)
|
|
1362
|
+
|
|
1363
|
+
const checkpoint = yield* saveCheckpointWithContext(context, {
|
|
1364
|
+
tx,
|
|
1365
|
+
run: synced.run,
|
|
1366
|
+
spec,
|
|
1367
|
+
nodeRuns: synced.nodeRuns,
|
|
1368
|
+
artifacts: synced.artifacts,
|
|
1369
|
+
sequence: nextCheckpointSequence,
|
|
1370
|
+
reason: 'run-resumed',
|
|
1371
|
+
capturedEvents: emittedEvents,
|
|
1372
|
+
})
|
|
1373
|
+
yield* attachCheckpoint(tx, synced.run, checkpoint)
|
|
1374
|
+
const latestRun = yield* planRunService.getRunById(run.id)
|
|
1375
|
+
return yield* serializeRunFull(planRunService, latestRun)
|
|
1376
|
+
}),
|
|
1377
|
+
}).pipe(Effect.withSpan('PlanExecutor.resumeRun.persistTransaction'))
|
|
1369
1378
|
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
}
|
|
1379
|
+
return buildExecutionPlanToolResult({
|
|
1380
|
+
action: 'run-resumed',
|
|
1381
|
+
plan: snapshot,
|
|
1382
|
+
message: `Resumed execution run "${snapshot.title}".`,
|
|
1383
|
+
})
|
|
1384
|
+
})
|
|
1377
1385
|
|
|
1378
|
-
|
|
1379
|
-
function transitionNodeToRunning(
|
|
1386
|
+
const transitionNodeToRunning = Effect.fn('PlanExecutor.transitionNodeToRunning')(function* (
|
|
1380
1387
|
context: PlanExecutorContext,
|
|
1381
1388
|
params: { runId: string; nodeId: string },
|
|
1382
|
-
)
|
|
1389
|
+
) {
|
|
1383
1390
|
const { databaseService, planRunService } = context
|
|
1384
1391
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
if (nodeRun.status !== 'ready') return
|
|
1392
|
+
const run = yield* planRunService.getRunById(params.runId)
|
|
1393
|
+
const nodeRun = yield* planRunService.getNodeRunByNodeId(run.id, params.nodeId)
|
|
1394
|
+
if (nodeRun.status !== 'ready') return
|
|
1389
1395
|
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
}).pipe(runPromise)
|
|
1413
|
-
}
|
|
1396
|
+
yield* withDatabaseTransactionEffect(databaseService, (tx) =>
|
|
1397
|
+
Effect.gen(function* () {
|
|
1398
|
+
const runningNodeRunRow = yield* fromPromise(() =>
|
|
1399
|
+
tx
|
|
1400
|
+
.update(ensureRecordId(nodeRun.id, TABLES.PLAN_NODE_RUN))
|
|
1401
|
+
.content(toNodeRunData(nodeRun, { status: 'running', startedAt: nodeRun.startedAt ?? nowDate() }))
|
|
1402
|
+
.output('after'),
|
|
1403
|
+
)
|
|
1404
|
+
const runningNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, runningNodeRunRow, 'plan node run')
|
|
1405
|
+
|
|
1406
|
+
const nodeRuns = yield* planRunService.listNodeRuns(run.id)
|
|
1407
|
+
yield* replaceRun(tx, run, {
|
|
1408
|
+
status: 'running',
|
|
1409
|
+
currentNodeId: runningNodeRun.nodeId,
|
|
1410
|
+
waitingNodeId: null,
|
|
1411
|
+
readyNodeIds: nodeRuns
|
|
1412
|
+
.filter((candidate) => candidate.status === 'ready' && candidate.nodeId !== runningNodeRun.nodeId)
|
|
1413
|
+
.map((candidate) => candidate.nodeId),
|
|
1414
|
+
})
|
|
1415
|
+
}),
|
|
1416
|
+
).pipe(Effect.withSpan('PlanExecutor.transitionNodeToRunning.persistTransaction'))
|
|
1417
|
+
})
|
|
1414
1418
|
|
|
1415
|
-
|
|
1416
|
-
function blockNodeOnDispatchFailure(
|
|
1419
|
+
const blockNodeOnDispatchFailure = Effect.fn('PlanExecutor.blockNodeOnDispatchFailure')(function* (
|
|
1417
1420
|
context: PlanExecutorContext,
|
|
1418
1421
|
params: {
|
|
1419
1422
|
threadId: RecordIdInput
|
|
@@ -1423,165 +1426,164 @@ function blockNodeOnDispatchFailure(
|
|
|
1423
1426
|
message: string
|
|
1424
1427
|
failureClass: PlanFailureClass
|
|
1425
1428
|
},
|
|
1426
|
-
)
|
|
1429
|
+
) {
|
|
1427
1430
|
const { databaseService, planEventDeliveryService, planRunService } = context
|
|
1428
1431
|
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
status: 'blocked',
|
|
1462
|
-
currentNodeId: blockedNodeRun.nodeId,
|
|
1463
|
-
waitingNodeId: null,
|
|
1464
|
-
readyNodeIds: [],
|
|
1465
|
-
failureCount: run.failureCount + 1,
|
|
1466
|
-
})
|
|
1432
|
+
const run = yield* planRunService.getRunById(params.runId)
|
|
1433
|
+
const spec = yield* planRunService.getPlanSpecById(run.planSpecId)
|
|
1434
|
+
const [nodeSpec, nodeRun, artifacts, nextCheckpointSequence] = yield* Effect.all([
|
|
1435
|
+
planRunService.getNodeSpecByNodeId(spec.id, params.nodeId),
|
|
1436
|
+
planRunService.getNodeRunByNodeId(run.id, params.nodeId),
|
|
1437
|
+
planRunService.listArtifacts(run.id),
|
|
1438
|
+
planRunService.getNextCheckpointSequence(run.id),
|
|
1439
|
+
]).pipe(Effect.withSpan('PlanExecutor.blockNodeOnDispatchFailure.loadNodeContext'))
|
|
1440
|
+
|
|
1441
|
+
return yield* withTransactionAndEventsEffect({
|
|
1442
|
+
db: databaseService,
|
|
1443
|
+
planEventDeliveryService,
|
|
1444
|
+
run: (tx, emittedEvents) =>
|
|
1445
|
+
Effect.gen(function* () {
|
|
1446
|
+
let blockedNodeRun: PlanNodeRunRecord
|
|
1447
|
+
if (nodeRun.status === 'blocked') {
|
|
1448
|
+
blockedNodeRun = nodeRun
|
|
1449
|
+
} else {
|
|
1450
|
+
const blockedNodeRunRow = yield* fromPromise(() =>
|
|
1451
|
+
tx
|
|
1452
|
+
.update(ensureRecordId(nodeRun.id, TABLES.PLAN_NODE_RUN))
|
|
1453
|
+
.content(
|
|
1454
|
+
toNodeRunData(nodeRun, {
|
|
1455
|
+
status: 'blocked',
|
|
1456
|
+
blockedReason: params.message,
|
|
1457
|
+
failureClass: params.failureClass,
|
|
1458
|
+
}),
|
|
1459
|
+
)
|
|
1460
|
+
.output('after'),
|
|
1461
|
+
)
|
|
1462
|
+
blockedNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, blockedNodeRunRow, 'plan node run')
|
|
1463
|
+
}
|
|
1467
1464
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
toStatus: blockedNodeRun.status,
|
|
1476
|
-
message: `Node "${nodeSpec.label}" failed during owner dispatch.`,
|
|
1477
|
-
detail: { failureClass: params.failureClass, phase: 'dispatch', error: params.message },
|
|
1478
|
-
emittedBy: params.emittedBy,
|
|
1479
|
-
capturedEvents: emittedEvents,
|
|
1480
|
-
})
|
|
1465
|
+
const blockedRun = yield* replaceRun(tx, run, {
|
|
1466
|
+
status: 'blocked',
|
|
1467
|
+
currentNodeId: blockedNodeRun.nodeId,
|
|
1468
|
+
waitingNodeId: null,
|
|
1469
|
+
readyNodeIds: [],
|
|
1470
|
+
failureCount: run.failureCount + 1,
|
|
1471
|
+
})
|
|
1481
1472
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1473
|
+
yield* emitEvent({
|
|
1474
|
+
tx,
|
|
1475
|
+
run: blockedRun,
|
|
1476
|
+
spec,
|
|
1477
|
+
nodeId: blockedNodeRun.nodeId,
|
|
1478
|
+
eventType: 'node-blocked',
|
|
1479
|
+
fromStatus: nodeRun.status,
|
|
1480
|
+
toStatus: blockedNodeRun.status,
|
|
1481
|
+
message: `Node "${nodeSpec.label}" failed during owner dispatch.`,
|
|
1482
|
+
detail: { failureClass: params.failureClass, phase: 'dispatch', error: params.message },
|
|
1483
|
+
emittedBy: params.emittedBy,
|
|
1484
|
+
capturedEvents: emittedEvents,
|
|
1485
|
+
})
|
|
1486
|
+
|
|
1487
|
+
const checkpointNodeRuns = (yield* planRunService.listNodeRuns(run.id)).map((candidate) =>
|
|
1488
|
+
candidate.nodeId === blockedNodeRun.nodeId ? blockedNodeRun : candidate,
|
|
1489
|
+
)
|
|
1490
|
+
const checkpoint = yield* saveCheckpointWithContext(context, {
|
|
1491
|
+
tx,
|
|
1492
|
+
run: blockedRun,
|
|
1493
|
+
spec,
|
|
1494
|
+
nodeRuns: checkpointNodeRuns,
|
|
1495
|
+
artifacts,
|
|
1496
|
+
sequence: nextCheckpointSequence,
|
|
1497
|
+
reason: 'owner-dispatch-failed',
|
|
1498
|
+
capturedEvents: emittedEvents,
|
|
1499
|
+
})
|
|
1500
|
+
yield* attachCheckpoint(tx, blockedRun, checkpoint)
|
|
1501
|
+
const latestRun = yield* planRunService.getRunById(run.id)
|
|
1502
|
+
return yield* serializeRunFull(planRunService, latestRun)
|
|
1503
|
+
}),
|
|
1504
|
+
}).pipe(Effect.withSpan('PlanExecutor.blockNodeOnDispatchFailure.persistTransaction'))
|
|
1505
|
+
})
|
|
1502
1506
|
|
|
1503
|
-
|
|
1504
|
-
function promoteDelayedNode(
|
|
1507
|
+
const promoteDelayedNode = Effect.fn('PlanExecutor.promoteDelayedNode')(function* (
|
|
1505
1508
|
context: PlanExecutorContext,
|
|
1506
1509
|
params: { runId: string; nodeId: string; emittedBy: string },
|
|
1507
|
-
)
|
|
1510
|
+
) {
|
|
1508
1511
|
const { databaseService, planEventDeliveryService, planRunService } = context
|
|
1509
1512
|
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
}
|
|
1513
|
+
const run = yield* planRunService.getRunById(params.runId)
|
|
1514
|
+
if (run.status === 'completed' || run.status === 'failed' || run.status === 'aborted') {
|
|
1515
|
+
return
|
|
1516
|
+
}
|
|
1515
1517
|
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
const nodeRuns = yield* planRunService.listNodeRuns(run.id)
|
|
1522
|
-
const artifacts = yield* planRunService.listArtifacts(run.id)
|
|
1523
|
-
const nextCheckpointSequence = yield* planRunService.getNextCheckpointSequence(run.id)
|
|
1524
|
-
|
|
1525
|
-
yield* withTransactionAndEventsEffect({
|
|
1526
|
-
db: databaseService,
|
|
1527
|
-
planEventDeliveryService,
|
|
1528
|
-
run: (tx, emittedEvents) =>
|
|
1529
|
-
Effect.gen(function* () {
|
|
1530
|
-
const readyNodeRun = PlanNodeRunSchema.parse(
|
|
1531
|
-
yield* fromPromise(() =>
|
|
1532
|
-
tx
|
|
1533
|
-
.update(ensureRecordId(nodeRun.id, TABLES.PLAN_NODE_RUN))
|
|
1534
|
-
.content(toNodeRunData(nodeRun, { status: 'ready', readyAt: nowDate() }))
|
|
1535
|
-
.output('after'),
|
|
1536
|
-
),
|
|
1537
|
-
)
|
|
1518
|
+
const spec = yield* planRunService.getPlanSpecById(run.planSpecId)
|
|
1519
|
+
const nodeSpecs = yield* planRunService.listNodeSpecs(spec.id)
|
|
1520
|
+
const nodeRun = yield* planRunService.getNodeRunByNodeId(run.id, params.nodeId)
|
|
1521
|
+
if (nodeRun.status !== 'scheduled') return
|
|
1538
1522
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1523
|
+
const [nodeRuns, artifacts, nextCheckpointSequence] = yield* Effect.all([
|
|
1524
|
+
planRunService.listNodeRuns(run.id),
|
|
1525
|
+
planRunService.listArtifacts(run.id),
|
|
1526
|
+
planRunService.getNextCheckpointSequence(run.id),
|
|
1527
|
+
]).pipe(Effect.withSpan('PlanExecutor.promoteDelayedNode.loadRunContext'))
|
|
1528
|
+
|
|
1529
|
+
yield* withTransactionAndEventsEffect({
|
|
1530
|
+
db: databaseService,
|
|
1531
|
+
planEventDeliveryService,
|
|
1532
|
+
run: (tx, emittedEvents) =>
|
|
1533
|
+
Effect.gen(function* () {
|
|
1534
|
+
const readyNodeRunRow = yield* fromPromise(() =>
|
|
1535
|
+
tx
|
|
1536
|
+
.update(ensureRecordId(nodeRun.id, TABLES.PLAN_NODE_RUN))
|
|
1537
|
+
.content(toNodeRunData(nodeRun, { status: 'ready', readyAt: nowDate() }))
|
|
1538
|
+
.output('after'),
|
|
1539
|
+
)
|
|
1540
|
+
const readyNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, readyNodeRunRow, 'plan node run')
|
|
1542
1541
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1542
|
+
const updatedNodeRuns = nodeRuns.map((candidate) =>
|
|
1543
|
+
candidate.nodeId === readyNodeRun.nodeId ? readyNodeRun : candidate,
|
|
1544
|
+
)
|
|
1545
|
+
|
|
1546
|
+
const currentNodeSpec = nodeSpecs.find((s) => s.nodeId === params.nodeId)
|
|
1547
|
+
yield* emitEvent({
|
|
1548
|
+
tx,
|
|
1549
|
+
run,
|
|
1550
|
+
spec,
|
|
1551
|
+
nodeId: readyNodeRun.nodeId,
|
|
1552
|
+
eventType: 'node-ready',
|
|
1553
|
+
fromStatus: nodeRun.status,
|
|
1554
|
+
toStatus: readyNodeRun.status,
|
|
1555
|
+
message: `Node "${currentNodeSpec?.label ?? params.nodeId}" promoted to ready after delay.`,
|
|
1556
|
+
emittedBy: params.emittedBy,
|
|
1557
|
+
capturedEvents: emittedEvents,
|
|
1558
|
+
})
|
|
1559
|
+
|
|
1560
|
+
const synced = yield* fromPromise(() =>
|
|
1561
|
+
syncRunGraph(context, {
|
|
1545
1562
|
tx,
|
|
1546
1563
|
run,
|
|
1547
1564
|
spec,
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
toStatus: readyNodeRun.status,
|
|
1552
|
-
message: `Node "${currentNodeSpec?.label ?? params.nodeId}" promoted to ready after delay.`,
|
|
1565
|
+
nodeSpecs,
|
|
1566
|
+
nodeRuns: updatedNodeRuns,
|
|
1567
|
+
artifacts,
|
|
1553
1568
|
emittedBy: params.emittedBy,
|
|
1554
1569
|
capturedEvents: emittedEvents,
|
|
1555
|
-
})
|
|
1556
|
-
|
|
1557
|
-
const synced = yield* fromPromise(() =>
|
|
1558
|
-
syncRunGraph(context, {
|
|
1559
|
-
tx,
|
|
1560
|
-
run,
|
|
1561
|
-
spec,
|
|
1562
|
-
nodeSpecs,
|
|
1563
|
-
nodeRuns: updatedNodeRuns,
|
|
1564
|
-
artifacts,
|
|
1565
|
-
emittedBy: params.emittedBy,
|
|
1566
|
-
capturedEvents: emittedEvents,
|
|
1567
|
-
}),
|
|
1568
|
-
)
|
|
1570
|
+
}),
|
|
1571
|
+
)
|
|
1569
1572
|
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
}
|
|
1573
|
+
const checkpoint = yield* saveCheckpointWithContext(context, {
|
|
1574
|
+
tx,
|
|
1575
|
+
run: synced.run,
|
|
1576
|
+
spec,
|
|
1577
|
+
nodeRuns: synced.nodeRuns,
|
|
1578
|
+
artifacts: synced.artifacts,
|
|
1579
|
+
sequence: nextCheckpointSequence,
|
|
1580
|
+
reason: 'delayed-node-promoted',
|
|
1581
|
+
capturedEvents: emittedEvents,
|
|
1582
|
+
})
|
|
1583
|
+
yield* attachCheckpoint(tx, synced.run, checkpoint)
|
|
1584
|
+
}),
|
|
1585
|
+
}).pipe(Effect.withSpan('PlanExecutor.promoteDelayedNode.persistTransaction'))
|
|
1586
|
+
})
|
|
1585
1587
|
|
|
1586
1588
|
export function makePlanExecutorService(deps: PlanExecutorDeps) {
|
|
1587
1589
|
const context: PlanExecutorContext = {
|
|
@@ -1599,6 +1601,7 @@ export function makePlanExecutorService(deps: PlanExecutorDeps) {
|
|
|
1599
1601
|
planSchedulerService: deps.planSchedulerService,
|
|
1600
1602
|
planValidatorService: deps.planValidatorService,
|
|
1601
1603
|
qualityMetricsService: deps.qualityMetricsService,
|
|
1604
|
+
delayedNodePromotionQueue: deps.delayedNodePromotionQueue,
|
|
1602
1605
|
planCompletionSideEffects: makePlanCompletionSideEffects({
|
|
1603
1606
|
databaseService: deps.db,
|
|
1604
1607
|
feedbackLoopService: deps.feedbackLoopService,
|
|
@@ -1626,7 +1629,7 @@ export function makePlanExecutorService(deps: PlanExecutorDeps) {
|
|
|
1626
1629
|
}
|
|
1627
1630
|
|
|
1628
1631
|
export class PlanExecutorServiceTag extends Context.Service<PlanExecutorServiceTag, PlanExecutorService>()(
|
|
1629
|
-
'PlanExecutorService',
|
|
1632
|
+
'@lota-sdk/core/PlanExecutorService',
|
|
1630
1633
|
) {}
|
|
1631
1634
|
|
|
1632
1635
|
export const PlanExecutorServiceLive = Layer.effect(
|
|
@@ -1634,6 +1637,7 @@ export const PlanExecutorServiceLive = Layer.effect(
|
|
|
1634
1637
|
Effect.gen(function* () {
|
|
1635
1638
|
const db = yield* DatabaseServiceTag
|
|
1636
1639
|
const storage = yield* GeneratedDocumentStorageServiceTag
|
|
1640
|
+
const queues = yield* LotaQueuesServiceTag
|
|
1637
1641
|
return makePlanExecutorService({
|
|
1638
1642
|
db,
|
|
1639
1643
|
storage,
|
|
@@ -1649,6 +1653,7 @@ export const PlanExecutorServiceLive = Layer.effect(
|
|
|
1649
1653
|
planSchedulerService: yield* PlanSchedulerServiceTag,
|
|
1650
1654
|
planValidatorService: yield* PlanValidatorServiceTag,
|
|
1651
1655
|
qualityMetricsService: yield* QualityMetricsServiceTag,
|
|
1656
|
+
delayedNodePromotionQueue: queues.delayedNodePromotion,
|
|
1652
1657
|
})
|
|
1653
1658
|
}),
|
|
1654
1659
|
)
|