@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
|
@@ -17,6 +17,8 @@ import type { SurrealDBService } from '../../db/service'
|
|
|
17
17
|
import { TABLES } from '../../db/tables'
|
|
18
18
|
import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
|
|
19
19
|
import { DatabaseServiceTag } from '../../effect/services'
|
|
20
|
+
import type { PlanSchedulerQueueRuntime } from '../../queues/plan-scheduler.queue'
|
|
21
|
+
import { LotaQueuesServiceTag } from '../../queues/queues.service'
|
|
20
22
|
import { nowDate, unsafeDateFrom } from '../../utils/date-time'
|
|
21
23
|
import type { makePlanEventDeliveryService } from './plan-event-delivery.service'
|
|
22
24
|
import { PlanEventDeliveryServiceTag } from './plan-event-delivery.service'
|
|
@@ -86,6 +88,7 @@ interface PlanDeadlineDeps {
|
|
|
86
88
|
planExecutorService: ReturnType<typeof makePlanExecutorService>
|
|
87
89
|
planEventDeliveryService: ReturnType<typeof makePlanEventDeliveryService>
|
|
88
90
|
planRunService: ReturnType<typeof makePlanRunService>
|
|
91
|
+
planSchedulerQueue: PlanSchedulerQueueRuntime
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
class PlanDeadlineError extends Schema.TaggedErrorClass<PlanDeadlineError>()('PlanDeadlineError', {
|
|
@@ -94,12 +97,12 @@ class PlanDeadlineError extends Schema.TaggedErrorClass<PlanDeadlineError>()('Pl
|
|
|
94
97
|
}) {}
|
|
95
98
|
|
|
96
99
|
export function makePlanDeadlineService(deps: PlanDeadlineDeps) {
|
|
97
|
-
const { db, planEventDeliveryService, planExecutorService, planRunService } = deps
|
|
100
|
+
const { db, planEventDeliveryService, planExecutorService, planRunService, planSchedulerQueue } = deps
|
|
98
101
|
const effectTryPromise = makeEffectTryPromiseWithMessage(
|
|
99
102
|
(message, cause) => new PlanDeadlineError({ message, cause }),
|
|
100
103
|
)
|
|
101
|
-
const loadPlanSchedulerQueue = () =>
|
|
102
|
-
|
|
104
|
+
const loadPlanSchedulerQueue = (): Effect.Effect<PlanSchedulerQueueRuntime, PlanDeadlineError> =>
|
|
105
|
+
Effect.succeed(planSchedulerQueue)
|
|
103
106
|
|
|
104
107
|
function loadNodeSpec(nodeRun: PlanNodeRunRecord): Effect.Effect<PlanNodeSpecRecord | null, PlanDeadlineError> {
|
|
105
108
|
return db
|
|
@@ -111,50 +114,48 @@ export function makePlanDeadlineService(deps: PlanDeadlineDeps) {
|
|
|
111
114
|
.pipe(Effect.mapError((cause) => new PlanDeadlineError({ message: 'Failed to load plan node spec.', cause })))
|
|
112
115
|
}
|
|
113
116
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
statuses: ['running', 'awaiting-human'],
|
|
121
|
-
}),
|
|
122
|
-
PlanNodeRunSchema,
|
|
123
|
-
),
|
|
124
|
-
'Failed to load active plan node runs for deadline sweep.',
|
|
125
|
-
)
|
|
126
|
-
const results = yield* Effect.forEach(
|
|
127
|
-
activeNodeRuns,
|
|
128
|
-
(nodeRun) =>
|
|
129
|
-
Effect.gen(function* () {
|
|
130
|
-
const nodeSpec = yield* loadNodeSpec(nodeRun).pipe(
|
|
131
|
-
Effect.mapError(
|
|
132
|
-
(cause) =>
|
|
133
|
-
new PlanDeadlineError({
|
|
134
|
-
message: `Failed to load node spec for ${nodeRun.nodeId}.`,
|
|
135
|
-
cause: cause.cause,
|
|
136
|
-
}),
|
|
137
|
-
),
|
|
138
|
-
)
|
|
139
|
-
if (!nodeSpec?.deadline) {
|
|
140
|
-
return null
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const evaluation = evaluateDeadline({
|
|
144
|
-
deadline: nodeSpec.deadline,
|
|
145
|
-
nodeStartedAt: unsafeDateFrom(nodeRun.startedAt ?? nodeRun.createdAt),
|
|
146
|
-
now,
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
return { nodeRun, nodeSpec, evaluation }
|
|
117
|
+
const collectDeadlineSweepEffect = Effect.fn('PlanDeadline.collectDeadlineSweep')(function* (now: Date) {
|
|
118
|
+
const activeNodeRuns = yield* effectTryPromise(
|
|
119
|
+
() =>
|
|
120
|
+
db.queryMany(
|
|
121
|
+
new BoundQuery(`SELECT * FROM ${TABLES.PLAN_NODE_RUN} WHERE status IN $statuses`, {
|
|
122
|
+
statuses: ['running', 'awaiting-human'],
|
|
150
123
|
}),
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
124
|
+
PlanNodeRunSchema,
|
|
125
|
+
),
|
|
126
|
+
'Failed to load active plan node runs for deadline sweep.',
|
|
127
|
+
).pipe(Effect.withSpan('PlanDeadline.loadActiveNodeRuns'))
|
|
128
|
+
const results = yield* Effect.forEach(
|
|
129
|
+
activeNodeRuns,
|
|
130
|
+
(nodeRun) =>
|
|
131
|
+
Effect.gen(function* () {
|
|
132
|
+
const nodeSpec = yield* loadNodeSpec(nodeRun).pipe(
|
|
133
|
+
Effect.mapError(
|
|
134
|
+
(cause) =>
|
|
135
|
+
new PlanDeadlineError({
|
|
136
|
+
message: `Failed to load node spec for ${nodeRun.nodeId}.`,
|
|
137
|
+
cause: cause.cause,
|
|
138
|
+
}),
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
if (!nodeSpec?.deadline) {
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
156
144
|
|
|
157
|
-
|
|
145
|
+
const evaluation = evaluateDeadline({
|
|
146
|
+
deadline: nodeSpec.deadline,
|
|
147
|
+
nodeStartedAt: unsafeDateFrom(nodeRun.startedAt ?? nodeRun.createdAt),
|
|
148
|
+
now,
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
return { nodeRun, nodeSpec, evaluation }
|
|
152
|
+
}),
|
|
153
|
+
{ concurrency: 10 },
|
|
154
|
+
).pipe(Effect.withSpan('PlanDeadline.evaluateNodeRuns'))
|
|
155
|
+
return { entries: results.filter((entry): entry is NonNullable<typeof entry> => entry !== null) }
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
const emitDeadlineEventEffect = Effect.fn('PlanDeadline.emitDeadlineEvent')(function* (params: {
|
|
158
159
|
run: PlanRunRecord
|
|
159
160
|
nodeRun: PlanNodeRunRecord
|
|
160
161
|
eventType: Extract<PlanEventType, 'deadline-warning' | 'deadline-missed' | 'escalation-triggered'>
|
|
@@ -167,68 +168,69 @@ export function makePlanDeadlineService(deps: PlanDeadlineDeps) {
|
|
|
167
168
|
? params.detail.dedupeKey.trim()
|
|
168
169
|
: null
|
|
169
170
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
`SELECT * FROM ${TABLES.PLAN_EVENT}
|
|
171
|
+
const existing =
|
|
172
|
+
dedupeKey === null
|
|
173
|
+
? []
|
|
174
|
+
: yield* effectTryPromise(
|
|
175
|
+
() =>
|
|
176
|
+
db.queryMany(
|
|
177
|
+
new BoundQuery(
|
|
178
|
+
`SELECT * FROM ${TABLES.PLAN_EVENT}
|
|
179
179
|
WHERE runId = $runId
|
|
180
180
|
AND nodeId = $nodeId
|
|
181
181
|
AND eventType = $eventType
|
|
182
182
|
AND detail.dedupeKey = $dedupeKey
|
|
183
183
|
LIMIT 1`,
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
),
|
|
191
|
-
PlanEventSchema,
|
|
184
|
+
{
|
|
185
|
+
runId: ensureRecordId(params.run.id, TABLES.PLAN_RUN),
|
|
186
|
+
nodeId: params.nodeRun.nodeId,
|
|
187
|
+
eventType: params.eventType,
|
|
188
|
+
dedupeKey,
|
|
189
|
+
},
|
|
192
190
|
),
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
191
|
+
PlanEventSchema,
|
|
192
|
+
),
|
|
193
|
+
'Failed to load existing deadline event.',
|
|
194
|
+
)
|
|
195
|
+
if (existing.length > 0) {
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
198
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
)
|
|
206
|
-
const event = yield* effectTryPromise(
|
|
207
|
-
() =>
|
|
208
|
-
db.create(
|
|
209
|
-
TABLES.PLAN_EVENT,
|
|
210
|
-
{
|
|
211
|
-
id: new RecordId(TABLES.PLAN_EVENT, Bun.randomUUIDv7()),
|
|
212
|
-
planSpecId: ensureRecordId(spec.id, TABLES.PLAN_SPEC),
|
|
213
|
-
runId: ensureRecordId(params.run.id, TABLES.PLAN_RUN),
|
|
214
|
-
nodeId: params.nodeRun.nodeId,
|
|
215
|
-
eventType: params.eventType,
|
|
216
|
-
message: params.message,
|
|
217
|
-
emittedBy: params.emittedBy,
|
|
218
|
-
detail: params.detail,
|
|
219
|
-
},
|
|
220
|
-
PlanEventSchema,
|
|
221
|
-
),
|
|
222
|
-
'Failed to create deadline event.',
|
|
199
|
+
const spec = yield* planRunService
|
|
200
|
+
.getPlanSpecById(params.run.planSpecId)
|
|
201
|
+
.pipe(
|
|
202
|
+
Effect.mapError(
|
|
203
|
+
(cause) => new PlanDeadlineError({ message: 'Failed to load plan spec for deadline event.', cause }),
|
|
204
|
+
),
|
|
223
205
|
)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
206
|
+
const event = yield* effectTryPromise(
|
|
207
|
+
() =>
|
|
208
|
+
db.create(
|
|
209
|
+
TABLES.PLAN_EVENT,
|
|
210
|
+
{
|
|
211
|
+
id: new RecordId(TABLES.PLAN_EVENT, Bun.randomUUIDv7()),
|
|
212
|
+
planSpecId: ensureRecordId(spec.id, TABLES.PLAN_SPEC),
|
|
213
|
+
runId: ensureRecordId(params.run.id, TABLES.PLAN_RUN),
|
|
214
|
+
nodeId: params.nodeRun.nodeId,
|
|
215
|
+
eventType: params.eventType,
|
|
216
|
+
message: params.message,
|
|
217
|
+
emittedBy: params.emittedBy,
|
|
218
|
+
detail: params.detail,
|
|
219
|
+
},
|
|
220
|
+
PlanEventSchema,
|
|
221
|
+
),
|
|
222
|
+
'Failed to create deadline event.',
|
|
223
|
+
)
|
|
224
|
+
yield* planEventDeliveryService
|
|
225
|
+
.dispatchEvent(PlanEventSchema.parse(event))
|
|
226
|
+
.pipe(
|
|
227
|
+
Effect.mapError(
|
|
228
|
+
(error) => new PlanDeadlineError({ message: 'Failed to dispatch deadline event.', cause: error }),
|
|
229
|
+
),
|
|
227
230
|
)
|
|
228
|
-
|
|
229
|
-
}
|
|
231
|
+
})
|
|
230
232
|
|
|
231
|
-
|
|
233
|
+
const applyDeadlineMissActionEffect = Effect.fn('PlanDeadline.applyDeadlineMissAction')(function* (params: {
|
|
232
234
|
runId: RecordIdInput
|
|
233
235
|
nodeId: string
|
|
234
236
|
threadId: string
|
|
@@ -237,105 +239,99 @@ export function makePlanDeadlineService(deps: PlanDeadlineDeps) {
|
|
|
237
239
|
emittedBy: string
|
|
238
240
|
}) {
|
|
239
241
|
const runIdStr = recordIdToString(params.runId, TABLES.PLAN_RUN)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
.
|
|
245
|
-
|
|
246
|
-
(cause) => new PlanDeadlineError({ message: 'Failed to load plan run for deadline action.', cause }),
|
|
247
|
-
),
|
|
242
|
+
const [run, nodeRun] = yield* Effect.all([
|
|
243
|
+
planRunService
|
|
244
|
+
.getRunById(params.runId)
|
|
245
|
+
.pipe(
|
|
246
|
+
Effect.mapError(
|
|
247
|
+
(cause) => new PlanDeadlineError({ message: 'Failed to load plan run for deadline action.', cause }),
|
|
248
248
|
),
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
),
|
|
249
|
+
),
|
|
250
|
+
planRunService
|
|
251
|
+
.getNodeRunByNodeId(params.runId, params.nodeId)
|
|
252
|
+
.pipe(
|
|
253
|
+
Effect.mapError(
|
|
254
|
+
(cause) => new PlanDeadlineError({ message: 'Failed to load plan node run for deadline action.', cause }),
|
|
255
255
|
),
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
256
|
+
),
|
|
257
|
+
])
|
|
258
|
+
const blockNode = (message: string): Effect.Effect<void, PlanDeadlineError> =>
|
|
259
|
+
planExecutorService
|
|
260
|
+
.blockNodeOnDispatchFailure({
|
|
261
|
+
threadId: params.threadId,
|
|
262
|
+
runId: runIdStr,
|
|
263
|
+
nodeId: params.nodeId,
|
|
264
|
+
emittedBy: params.emittedBy,
|
|
265
|
+
message,
|
|
266
|
+
failureClass: 'timeout_exceeded',
|
|
267
|
+
})
|
|
268
|
+
.pipe(
|
|
269
|
+
Effect.asVoid,
|
|
270
|
+
Effect.mapError((cause) => new PlanDeadlineError({ message: `Failed to ${message.toLowerCase()}.`, cause })),
|
|
269
271
|
)
|
|
270
272
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
273
|
+
switch (params.action) {
|
|
274
|
+
case 'notify':
|
|
275
|
+
yield* emitDeadlineEventEffect({
|
|
276
|
+
run,
|
|
277
|
+
nodeRun,
|
|
278
|
+
eventType: 'deadline-missed',
|
|
279
|
+
emittedBy: params.emittedBy,
|
|
280
|
+
message: `Node "${params.nodeId}" has missed its deadline.`,
|
|
281
|
+
detail: { title: 'Deadline missed', dedupeKey: `plan-deadline:${runIdStr}:${params.nodeId}:missed:notify` },
|
|
282
|
+
})
|
|
283
|
+
return
|
|
282
284
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
285
|
+
case 'escalate':
|
|
286
|
+
yield* emitDeadlineEventEffect({
|
|
287
|
+
run,
|
|
288
|
+
nodeRun,
|
|
289
|
+
eventType: 'escalation-triggered',
|
|
290
|
+
emittedBy: params.emittedBy,
|
|
291
|
+
message: `Node "${params.nodeId}" has missed its deadline and requires escalation.`,
|
|
292
|
+
detail: {
|
|
293
|
+
title: 'Deadline escalation',
|
|
294
|
+
dedupeKey: `plan-deadline:${runIdStr}:${params.nodeId}:missed:escalate`,
|
|
295
|
+
missedDeadline: true,
|
|
296
|
+
},
|
|
297
|
+
})
|
|
298
|
+
return
|
|
297
299
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
300
|
+
case 'block':
|
|
301
|
+
yield* emitDeadlineEventEffect({
|
|
302
|
+
run,
|
|
303
|
+
nodeRun,
|
|
304
|
+
eventType: 'deadline-missed',
|
|
305
|
+
emittedBy: params.emittedBy,
|
|
306
|
+
message: `Node "${params.nodeId}" has missed its deadline.`,
|
|
307
|
+
detail: { title: 'Deadline missed', dedupeKey: `plan-deadline:${runIdStr}:${params.nodeId}:missed:block` },
|
|
308
|
+
})
|
|
309
|
+
yield* blockNode('Deadline missed — node blocked')
|
|
310
|
+
return
|
|
309
311
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
312
|
+
case 'fail':
|
|
313
|
+
yield* emitDeadlineEventEffect({
|
|
314
|
+
run,
|
|
315
|
+
nodeRun,
|
|
316
|
+
eventType: 'deadline-missed',
|
|
317
|
+
emittedBy: params.emittedBy,
|
|
318
|
+
message: `Node "${params.nodeId}" has missed its deadline.`,
|
|
319
|
+
detail: { title: 'Deadline missed', dedupeKey: `plan-deadline:${runIdStr}:${params.nodeId}:missed:fail` },
|
|
320
|
+
})
|
|
321
|
+
yield* blockNode('Deadline missed — node failed')
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
})
|
|
324
325
|
|
|
325
|
-
|
|
326
|
-
run: PlanRunRecord
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
now: Date
|
|
330
|
-
}) {
|
|
331
|
-
const escalation = params.nodeSpec.escalation
|
|
332
|
-
if (!escalation) return Effect.void
|
|
326
|
+
const maybeEmitEscalationPolicyEventEffect = Effect.fn('PlanDeadline.maybeEmitEscalationPolicyEvent')(
|
|
327
|
+
function* (params: { run: PlanRunRecord; nodeRun: PlanNodeRunRecord; nodeSpec: PlanNodeSpecRecord; now: Date }) {
|
|
328
|
+
const escalation = params.nodeSpec.escalation
|
|
329
|
+
if (!escalation) return
|
|
333
330
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
331
|
+
const startedAt = unsafeDateFrom(params.nodeRun.startedAt ?? params.nodeRun.createdAt)
|
|
332
|
+
const runIdStr = recordIdToString(params.run.id, TABLES.PLAN_RUN)
|
|
333
|
+
const baseKey = `plan-escalation:${runIdStr}:${params.nodeRun.nodeId}`
|
|
337
334
|
|
|
338
|
-
return Effect.gen(function* () {
|
|
339
335
|
if (escalation.autoEscalateAfterMinutes) {
|
|
340
336
|
const thresholdMs = escalation.autoEscalateAfterMinutes * 60_000
|
|
341
337
|
if (params.now.getTime() - startedAt.getTime() >= thresholdMs) {
|
|
@@ -378,146 +374,140 @@ export function makePlanDeadlineService(deps: PlanDeadlineDeps) {
|
|
|
378
374
|
})
|
|
379
375
|
}
|
|
380
376
|
}
|
|
381
|
-
}
|
|
382
|
-
|
|
377
|
+
},
|
|
378
|
+
)
|
|
383
379
|
|
|
384
|
-
const enqueueDeadlineCheckEffect = (scheduledFor: Date)
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
})
|
|
380
|
+
const enqueueDeadlineCheckEffect = Effect.fn('PlanDeadline.enqueueDeadlineCheck')(function* (scheduledFor: Date) {
|
|
381
|
+
const { enqueueDeadlineCheck } = yield* loadPlanSchedulerQueue()
|
|
382
|
+
yield* effectTryPromise(() => enqueueDeadlineCheck(scheduledFor), 'Failed to enqueue deadline check.')
|
|
383
|
+
})
|
|
389
384
|
|
|
390
|
-
const checkDeadlinesEffect = (now?: Date)
|
|
385
|
+
const checkDeadlinesEffect = Effect.fn('PlanDeadline.checkDeadlines')(function* (now?: Date) {
|
|
391
386
|
const currentTime = now ?? nowDate()
|
|
392
387
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const runCache = new Map<string, PlanRunRecord>()
|
|
388
|
+
const sweep = yield* collectDeadlineSweepEffect(currentTime)
|
|
389
|
+
if (sweep.entries.length === 0) {
|
|
390
|
+
return
|
|
391
|
+
}
|
|
400
392
|
|
|
401
|
-
|
|
402
|
-
Effect.gen(function* () {
|
|
403
|
-
const deadline = entry.nodeSpec.deadline
|
|
404
|
-
if (!deadline) {
|
|
405
|
-
return
|
|
406
|
-
}
|
|
393
|
+
const runCache = new Map<string, PlanRunRecord>()
|
|
407
394
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
db.findOne(
|
|
415
|
-
TABLES.PLAN_RUN,
|
|
416
|
-
{ id: ensureRecordId(entry.nodeRun.runId, TABLES.PLAN_RUN) },
|
|
417
|
-
PlanRunSchema,
|
|
418
|
-
),
|
|
419
|
-
'Failed to load plan run during deadline check.',
|
|
420
|
-
))
|
|
421
|
-
if (!run) {
|
|
422
|
-
return
|
|
423
|
-
}
|
|
424
|
-
runCache.set(runIdStr, run)
|
|
395
|
+
const handleEntry = (entry: (typeof sweep.entries)[number]): Effect.Effect<void, PlanDeadlineError> =>
|
|
396
|
+
Effect.gen(function* () {
|
|
397
|
+
const deadline = entry.nodeSpec.deadline
|
|
398
|
+
if (!deadline) {
|
|
399
|
+
return
|
|
400
|
+
}
|
|
425
401
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
402
|
+
const runIdStr = recordIdToString(entry.nodeRun.runId, TABLES.PLAN_RUN)
|
|
403
|
+
const cachedRun = runCache.get(runIdStr)
|
|
404
|
+
const run =
|
|
405
|
+
cachedRun ??
|
|
406
|
+
(yield* effectTryPromise(
|
|
407
|
+
() =>
|
|
408
|
+
db.findOne(TABLES.PLAN_RUN, { id: ensureRecordId(entry.nodeRun.runId, TABLES.PLAN_RUN) }, PlanRunSchema),
|
|
409
|
+
'Failed to load plan run during deadline check.',
|
|
410
|
+
))
|
|
411
|
+
if (!run) {
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
runCache.set(runIdStr, run)
|
|
415
|
+
|
|
416
|
+
const dedupeKeyBase = `plan-deadline:${runIdStr}:${entry.nodeRun.nodeId}`
|
|
417
|
+
const actionEffect =
|
|
418
|
+
entry.evaluation.status === 'warning'
|
|
419
|
+
? emitDeadlineEventEffect({
|
|
420
|
+
run,
|
|
421
|
+
nodeRun: entry.nodeRun,
|
|
422
|
+
eventType: 'deadline-warning',
|
|
423
|
+
emittedBy: 'plan-deadline-checker',
|
|
424
|
+
message:
|
|
425
|
+
entry.evaluation.activeReminder?.message ??
|
|
426
|
+
`Node "${entry.nodeRun.nodeId}" is approaching its deadline.`,
|
|
427
|
+
detail: {
|
|
428
|
+
title: 'Deadline approaching',
|
|
429
|
+
reminderBeforeMs: entry.evaluation.activeReminder?.beforeMs ?? null,
|
|
430
|
+
dedupeKey: `${dedupeKeyBase}:warning:${entry.evaluation.activeReminder?.beforeMs ?? 'default'}`,
|
|
431
|
+
},
|
|
432
|
+
})
|
|
433
|
+
: entry.evaluation.status === 'escalated'
|
|
429
434
|
? emitDeadlineEventEffect({
|
|
430
435
|
run,
|
|
431
436
|
nodeRun: entry.nodeRun,
|
|
432
|
-
eventType: '
|
|
437
|
+
eventType: 'escalation-triggered',
|
|
433
438
|
emittedBy: 'plan-deadline-checker',
|
|
434
439
|
message:
|
|
435
440
|
entry.evaluation.activeReminder?.message ??
|
|
436
|
-
`Node "${entry.nodeRun.nodeId}"
|
|
441
|
+
`Node "${entry.nodeRun.nodeId}" deadline requires escalation.`,
|
|
437
442
|
detail: {
|
|
438
|
-
title: 'Deadline
|
|
443
|
+
title: 'Deadline escalation',
|
|
439
444
|
reminderBeforeMs: entry.evaluation.activeReminder?.beforeMs ?? null,
|
|
440
|
-
dedupeKey: `${dedupeKeyBase}:
|
|
445
|
+
dedupeKey: `${dedupeKeyBase}:escalated:${entry.evaluation.activeReminder?.beforeMs ?? 'default'}`,
|
|
441
446
|
},
|
|
442
447
|
})
|
|
443
|
-
:
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
`Node "${entry.nodeRun.nodeId}" deadline requires escalation.`,
|
|
452
|
-
detail: {
|
|
453
|
-
title: 'Deadline escalation',
|
|
454
|
-
reminderBeforeMs: entry.evaluation.activeReminder?.beforeMs ?? null,
|
|
455
|
-
dedupeKey: `${dedupeKeyBase}:escalated:${entry.evaluation.activeReminder?.beforeMs ?? 'default'}`,
|
|
456
|
-
},
|
|
457
|
-
})
|
|
458
|
-
: applyDeadlineMissActionEffect({
|
|
459
|
-
runId: entry.nodeRun.runId,
|
|
460
|
-
nodeId: entry.nodeRun.nodeId,
|
|
461
|
-
threadId: recordIdToString(run.threadId, TABLES.THREAD),
|
|
462
|
-
organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
|
|
463
|
-
action: deadline.missAction,
|
|
464
|
-
emittedBy: 'plan-deadline-checker',
|
|
465
|
-
})
|
|
466
|
-
|
|
467
|
-
yield* actionEffect
|
|
468
|
-
yield* maybeEmitEscalationPolicyEventEffect({
|
|
469
|
-
run,
|
|
470
|
-
nodeRun: entry.nodeRun,
|
|
471
|
-
nodeSpec: entry.nodeSpec,
|
|
472
|
-
now: currentTime,
|
|
473
|
-
})
|
|
474
|
-
})
|
|
475
|
-
|
|
476
|
-
yield* Effect.forEach(
|
|
477
|
-
sweep.entries.filter((entry) => entry.evaluation.status !== 'ok'),
|
|
478
|
-
handleEntry,
|
|
479
|
-
{ concurrency: 3, discard: true },
|
|
480
|
-
)
|
|
481
|
-
|
|
482
|
-
const nextTriggerAt =
|
|
483
|
-
sweep.entries
|
|
484
|
-
.map((entry) => entry.evaluation.nextTriggerAt)
|
|
485
|
-
.filter((value): value is Date => {
|
|
486
|
-
if (!value) return false
|
|
487
|
-
return value.getTime() > currentTime.getTime()
|
|
488
|
-
})
|
|
489
|
-
.sort((a, b) => a.getTime() - b.getTime())
|
|
490
|
-
.at(0) ?? null
|
|
448
|
+
: applyDeadlineMissActionEffect({
|
|
449
|
+
runId: entry.nodeRun.runId,
|
|
450
|
+
nodeId: entry.nodeRun.nodeId,
|
|
451
|
+
threadId: recordIdToString(run.threadId, TABLES.THREAD),
|
|
452
|
+
organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
|
|
453
|
+
action: deadline.missAction,
|
|
454
|
+
emittedBy: 'plan-deadline-checker',
|
|
455
|
+
})
|
|
491
456
|
|
|
492
|
-
|
|
493
|
-
yield*
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
457
|
+
yield* actionEffect
|
|
458
|
+
yield* maybeEmitEscalationPolicyEventEffect({
|
|
459
|
+
run,
|
|
460
|
+
nodeRun: entry.nodeRun,
|
|
461
|
+
nodeSpec: entry.nodeSpec,
|
|
462
|
+
now: currentTime,
|
|
463
|
+
})
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
yield* Effect.forEach(
|
|
467
|
+
sweep.entries.filter((entry) => entry.evaluation.status !== 'ok'),
|
|
468
|
+
handleEntry,
|
|
469
|
+
{ concurrency: 3, discard: true },
|
|
470
|
+
).pipe(Effect.withSpan('PlanDeadline.processDeadlineEntries'))
|
|
471
|
+
|
|
472
|
+
const nextTriggerAt =
|
|
473
|
+
sweep.entries
|
|
474
|
+
.map((entry) => entry.evaluation.nextTriggerAt)
|
|
475
|
+
.filter((value): value is Date => {
|
|
476
|
+
if (!value) return false
|
|
477
|
+
return value.getTime() > currentTime.getTime()
|
|
478
|
+
})
|
|
479
|
+
.sort((a, b) => a.getTime() - b.getTime())
|
|
480
|
+
.at(0) ?? null
|
|
497
481
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
482
|
+
if (nextTriggerAt) {
|
|
483
|
+
yield* enqueueDeadlineCheckEffect(nextTriggerAt)
|
|
484
|
+
}
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
const recoverDeadlineChecksEffect = Effect.fn('PlanDeadline.recoverDeadlineChecks')(function* (
|
|
488
|
+
now: Date = nowDate(),
|
|
489
|
+
) {
|
|
490
|
+
const sweep = yield* collectDeadlineSweepEffect(now)
|
|
491
|
+
const hasDueAction = sweep.entries.some((entry) => entry.evaluation.status !== 'ok')
|
|
492
|
+
if (hasDueAction) {
|
|
493
|
+
yield* enqueueDeadlineCheckEffect(now)
|
|
494
|
+
return
|
|
495
|
+
}
|
|
506
496
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
497
|
+
const nextTriggerAt =
|
|
498
|
+
sweep.entries
|
|
499
|
+
.map((entry) => entry.evaluation.nextTriggerAt)
|
|
500
|
+
.filter((value): value is Date => {
|
|
501
|
+
if (!value) return false
|
|
502
|
+
return value.getTime() > now.getTime()
|
|
503
|
+
})
|
|
504
|
+
.sort((a, b) => a.getTime() - b.getTime())
|
|
505
|
+
.at(0) ?? null
|
|
516
506
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
507
|
+
if (nextTriggerAt) {
|
|
508
|
+
yield* enqueueDeadlineCheckEffect(nextTriggerAt)
|
|
509
|
+
}
|
|
510
|
+
})
|
|
521
511
|
|
|
522
512
|
return {
|
|
523
513
|
evaluateDeadline,
|
|
@@ -533,7 +523,7 @@ export function makePlanDeadlineService(deps: PlanDeadlineDeps) {
|
|
|
533
523
|
export class PlanDeadlineServiceTag extends Context.Service<
|
|
534
524
|
PlanDeadlineServiceTag,
|
|
535
525
|
ReturnType<typeof makePlanDeadlineService>
|
|
536
|
-
>()('PlanDeadlineService') {}
|
|
526
|
+
>()('@lota-sdk/core/PlanDeadlineService') {}
|
|
537
527
|
|
|
538
528
|
export const PlanDeadlineServiceLive = Layer.effect(
|
|
539
529
|
PlanDeadlineServiceTag,
|
|
@@ -542,6 +532,13 @@ export const PlanDeadlineServiceLive = Layer.effect(
|
|
|
542
532
|
const planExecutorService = yield* PlanExecutorServiceTag
|
|
543
533
|
const planEventDeliveryService = yield* PlanEventDeliveryServiceTag
|
|
544
534
|
const planRunService = yield* PlanRunServiceTag
|
|
545
|
-
|
|
535
|
+
const queues = yield* LotaQueuesServiceTag
|
|
536
|
+
return makePlanDeadlineService({
|
|
537
|
+
db,
|
|
538
|
+
planExecutorService,
|
|
539
|
+
planEventDeliveryService,
|
|
540
|
+
planRunService,
|
|
541
|
+
planSchedulerQueue: queues.planScheduler,
|
|
542
|
+
})
|
|
546
543
|
}),
|
|
547
544
|
)
|