@lota-sdk/core 0.4.9 → 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 +2 -2
- package/src/ai/embedding-cache.ts +3 -1
- package/src/ai-gateway/ai-gateway.ts +38 -10
- package/src/config/agent-defaults.ts +22 -9
- 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 +20 -7
- package/src/config/thread-defaults.ts +12 -4
- package/src/create-runtime.ts +69 -656
- 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 +706 -538
- package/src/db/startup.ts +30 -19
- package/src/effect/awaitable-effect.ts +46 -37
- package/src/effect/helpers.ts +30 -5
- package/src/effect/index.ts +7 -5
- package/src/effect/layers.ts +82 -72
- package/src/effect/runtime.ts +18 -3
- package/src/effect/services.ts +15 -11
- package/src/embeddings/provider.ts +65 -66
- package/src/index.ts +13 -11
- package/src/queues/autonomous-job.queue.ts +59 -71
- package/src/queues/context-compaction.queue.ts +6 -18
- package/src/queues/delayed-node-promotion.queue.ts +9 -17
- package/src/queues/organization-learning.queue.ts +17 -4
- package/src/queues/plan-agent-heartbeat.queue.ts +23 -20
- package/src/queues/plan-scheduler.queue.ts +6 -18
- package/src/queues/post-chat-memory.queue.ts +6 -18
- package/src/queues/queue-factory.ts +128 -50
- package/src/queues/title-generation.queue.ts +6 -17
- package/src/redis/connection.ts +181 -164
- package/src/redis/runtime-connection.ts +13 -3
- package/src/redis/stream-context.ts +17 -9
- package/src/runtime/agent-runtime-policy.ts +1 -1
- package/src/runtime/agent-stream-helpers.ts +15 -11
- package/src/runtime/chat-run-orchestration.ts +1 -1
- package/src/runtime/context-compaction/context-compaction-runtime.ts +1 -1
- package/src/runtime/context-compaction/context-compaction.ts +126 -82
- package/src/runtime/domain-layer.ts +192 -0
- package/src/runtime/graph-designer.ts +15 -7
- package/src/runtime/helper-model.ts +8 -4
- package/src/runtime/index.ts +0 -1
- 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 +33 -54
- package/src/runtime/post-turn-side-effects.ts +6 -26
- package/src/runtime/retrieval-adapters.ts +4 -4
- package/src/runtime/runtime-accessors.ts +92 -0
- package/src/runtime/runtime-config.ts +3 -3
- package/src/runtime/runtime-extensions.ts +20 -9
- 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 +7 -5
- package/src/runtime/social-chat/social-chat-history.ts +21 -12
- package/src/runtime/social-chat/social-chat.ts +401 -365
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +58 -52
- package/src/runtime/thread-turn-context.ts +21 -27
- package/src/services/agent-activity.service.ts +1 -1
- package/src/services/agent-executor.service.ts +179 -187
- package/src/services/artifact.service.ts +10 -5
- package/src/services/attachment.service.ts +35 -1
- package/src/services/autonomous-job.service.ts +58 -56
- 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 +1 -1
- package/src/services/document-chunk.service.ts +8 -17
- package/src/services/execution-plan/execution-plan-graph.ts +74 -52
- package/src/services/execution-plan/execution-plan.service.ts +1 -1
- package/src/services/feedback-loop.service.ts +1 -1
- package/src/services/global-orchestrator.service.ts +33 -10
- package/src/services/graph-full-routing.ts +44 -33
- 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-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 +26 -44
- 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 +132 -90
- package/src/services/plan/plan-agent-heartbeat.service.ts +1 -1
- package/src/services/plan/plan-agent-query.service.ts +1 -1
- 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 +18 -24
- package/src/services/plan/plan-coordination.service.ts +1 -1
- package/src/services/plan/plan-cycle.service.ts +171 -164
- package/src/services/plan/plan-deadline.service.ts +290 -304
- package/src/services/plan/plan-event-delivery.service.ts +44 -39
- package/src/services/plan/plan-executor-graph.ts +114 -67
- package/src/services/plan/plan-executor-helpers.ts +60 -75
- package/src/services/plan/plan-executor.service.ts +550 -467
- package/src/services/plan/plan-run.service.ts +12 -19
- package/src/services/plan/plan-scheduler.service.ts +27 -33
- 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 +17 -9
- 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 +26 -10
- 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 +24 -8
- package/src/services/thread/thread-title.service.ts +1 -1
- package/src/services/thread/thread-turn-execution.ts +1 -1
- package/src/services/thread/thread-turn-preparation.service.ts +18 -16
- package/src/services/thread/thread-turn-streaming.ts +12 -11
- package/src/services/thread/thread-turn.ts +43 -10
- package/src/services/thread/thread.service.ts +11 -2
- 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 +1 -1
- 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/title-generator.agent.ts +1 -1
- package/src/tools/execution-plan.tool.ts +28 -17
- package/src/tools/fetch-webpage.tool.ts +20 -13
- package/src/tools/firecrawl-client.ts +13 -3
- package/src/tools/plan-approval.tool.ts +9 -1
- package/src/tools/search-web.tool.ts +16 -9
- package/src/tools/team-think.tool.ts +2 -2
- package/src/utils/async.ts +15 -6
- package/src/utils/errors.ts +27 -15
- package/src/workers/bootstrap.ts +25 -48
- package/src/workers/organization-learning.worker.ts +1 -1
- package/src/workers/regular-chat-memory-digest.runner.ts +25 -15
- package/src/workers/worker-utils.ts +20 -2
- package/src/config/search.ts +0 -3
- package/src/runtime/agent-types.ts +0 -1
|
@@ -111,50 +111,48 @@ export function makePlanDeadlineService(deps: PlanDeadlineDeps) {
|
|
|
111
111
|
.pipe(Effect.mapError((cause) => new PlanDeadlineError({ message: 'Failed to load plan node spec.', cause })))
|
|
112
112
|
}
|
|
113
113
|
|
|
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 }
|
|
114
|
+
const collectDeadlineSweepEffect = Effect.fn('PlanDeadline.collectDeadlineSweep')(function* (now: Date) {
|
|
115
|
+
const activeNodeRuns = yield* effectTryPromise(
|
|
116
|
+
() =>
|
|
117
|
+
db.queryMany(
|
|
118
|
+
new BoundQuery(`SELECT * FROM ${TABLES.PLAN_NODE_RUN} WHERE status IN $statuses`, {
|
|
119
|
+
statuses: ['running', 'awaiting-human'],
|
|
150
120
|
}),
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
121
|
+
PlanNodeRunSchema,
|
|
122
|
+
),
|
|
123
|
+
'Failed to load active plan node runs for deadline sweep.',
|
|
124
|
+
).pipe(Effect.withSpan('PlanDeadline.loadActiveNodeRuns'))
|
|
125
|
+
const results = yield* Effect.forEach(
|
|
126
|
+
activeNodeRuns,
|
|
127
|
+
(nodeRun) =>
|
|
128
|
+
Effect.gen(function* () {
|
|
129
|
+
const nodeSpec = yield* loadNodeSpec(nodeRun).pipe(
|
|
130
|
+
Effect.mapError(
|
|
131
|
+
(cause) =>
|
|
132
|
+
new PlanDeadlineError({
|
|
133
|
+
message: `Failed to load node spec for ${nodeRun.nodeId}.`,
|
|
134
|
+
cause: cause.cause,
|
|
135
|
+
}),
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
if (!nodeSpec?.deadline) {
|
|
139
|
+
return null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const evaluation = evaluateDeadline({
|
|
143
|
+
deadline: nodeSpec.deadline,
|
|
144
|
+
nodeStartedAt: unsafeDateFrom(nodeRun.startedAt ?? nodeRun.createdAt),
|
|
145
|
+
now,
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
return { nodeRun, nodeSpec, evaluation }
|
|
149
|
+
}),
|
|
150
|
+
{ concurrency: 10 },
|
|
151
|
+
).pipe(Effect.withSpan('PlanDeadline.evaluateNodeRuns'))
|
|
152
|
+
return { entries: results.filter((entry): entry is NonNullable<typeof entry> => entry !== null) }
|
|
153
|
+
})
|
|
156
154
|
|
|
157
|
-
|
|
155
|
+
const emitDeadlineEventEffect = Effect.fn('PlanDeadline.emitDeadlineEvent')(function* (params: {
|
|
158
156
|
run: PlanRunRecord
|
|
159
157
|
nodeRun: PlanNodeRunRecord
|
|
160
158
|
eventType: Extract<PlanEventType, 'deadline-warning' | 'deadline-missed' | 'escalation-triggered'>
|
|
@@ -167,68 +165,69 @@ export function makePlanDeadlineService(deps: PlanDeadlineDeps) {
|
|
|
167
165
|
? params.detail.dedupeKey.trim()
|
|
168
166
|
: null
|
|
169
167
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
`SELECT * FROM ${TABLES.PLAN_EVENT}
|
|
168
|
+
const existing =
|
|
169
|
+
dedupeKey === null
|
|
170
|
+
? []
|
|
171
|
+
: yield* effectTryPromise(
|
|
172
|
+
() =>
|
|
173
|
+
db.queryMany(
|
|
174
|
+
new BoundQuery(
|
|
175
|
+
`SELECT * FROM ${TABLES.PLAN_EVENT}
|
|
179
176
|
WHERE runId = $runId
|
|
180
177
|
AND nodeId = $nodeId
|
|
181
178
|
AND eventType = $eventType
|
|
182
179
|
AND detail.dedupeKey = $dedupeKey
|
|
183
180
|
LIMIT 1`,
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
),
|
|
191
|
-
PlanEventSchema,
|
|
181
|
+
{
|
|
182
|
+
runId: ensureRecordId(params.run.id, TABLES.PLAN_RUN),
|
|
183
|
+
nodeId: params.nodeRun.nodeId,
|
|
184
|
+
eventType: params.eventType,
|
|
185
|
+
dedupeKey,
|
|
186
|
+
},
|
|
192
187
|
),
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
188
|
+
PlanEventSchema,
|
|
189
|
+
),
|
|
190
|
+
'Failed to load existing deadline event.',
|
|
191
|
+
)
|
|
192
|
+
if (existing.length > 0) {
|
|
193
|
+
return
|
|
194
|
+
}
|
|
198
195
|
|
|
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.',
|
|
196
|
+
const spec = yield* planRunService
|
|
197
|
+
.getPlanSpecById(params.run.planSpecId)
|
|
198
|
+
.pipe(
|
|
199
|
+
Effect.mapError(
|
|
200
|
+
(cause) => new PlanDeadlineError({ message: 'Failed to load plan spec for deadline event.', cause }),
|
|
201
|
+
),
|
|
223
202
|
)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
203
|
+
const event = yield* effectTryPromise(
|
|
204
|
+
() =>
|
|
205
|
+
db.create(
|
|
206
|
+
TABLES.PLAN_EVENT,
|
|
207
|
+
{
|
|
208
|
+
id: new RecordId(TABLES.PLAN_EVENT, Bun.randomUUIDv7()),
|
|
209
|
+
planSpecId: ensureRecordId(spec.id, TABLES.PLAN_SPEC),
|
|
210
|
+
runId: ensureRecordId(params.run.id, TABLES.PLAN_RUN),
|
|
211
|
+
nodeId: params.nodeRun.nodeId,
|
|
212
|
+
eventType: params.eventType,
|
|
213
|
+
message: params.message,
|
|
214
|
+
emittedBy: params.emittedBy,
|
|
215
|
+
detail: params.detail,
|
|
216
|
+
},
|
|
217
|
+
PlanEventSchema,
|
|
218
|
+
),
|
|
219
|
+
'Failed to create deadline event.',
|
|
220
|
+
)
|
|
221
|
+
yield* planEventDeliveryService
|
|
222
|
+
.dispatchEventEffect(PlanEventSchema.parse(event))
|
|
223
|
+
.pipe(
|
|
224
|
+
Effect.mapError(
|
|
225
|
+
(error) => new PlanDeadlineError({ message: 'Failed to dispatch deadline event.', cause: error }),
|
|
226
|
+
),
|
|
227
227
|
)
|
|
228
|
-
|
|
229
|
-
}
|
|
228
|
+
})
|
|
230
229
|
|
|
231
|
-
|
|
230
|
+
const applyDeadlineMissActionEffect = Effect.fn('PlanDeadline.applyDeadlineMissAction')(function* (params: {
|
|
232
231
|
runId: RecordIdInput
|
|
233
232
|
nodeId: string
|
|
234
233
|
threadId: string
|
|
@@ -237,105 +236,98 @@ export function makePlanDeadlineService(deps: PlanDeadlineDeps) {
|
|
|
237
236
|
emittedBy: string
|
|
238
237
|
}) {
|
|
239
238
|
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
|
-
),
|
|
239
|
+
const [run, nodeRun] = yield* Effect.all([
|
|
240
|
+
planRunService
|
|
241
|
+
.getRunById(params.runId)
|
|
242
|
+
.pipe(
|
|
243
|
+
Effect.mapError(
|
|
244
|
+
(cause) => new PlanDeadlineError({ message: 'Failed to load plan run for deadline action.', cause }),
|
|
248
245
|
),
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
),
|
|
246
|
+
),
|
|
247
|
+
planRunService
|
|
248
|
+
.getNodeRunByNodeId(params.runId, params.nodeId)
|
|
249
|
+
.pipe(
|
|
250
|
+
Effect.mapError(
|
|
251
|
+
(cause) => new PlanDeadlineError({ message: 'Failed to load plan node run for deadline action.', cause }),
|
|
255
252
|
),
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
message,
|
|
266
|
-
failureClass: 'timeout_exceeded',
|
|
267
|
-
}),
|
|
268
|
-
`Failed to ${message.toLowerCase()}.`,
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
switch (params.action) {
|
|
272
|
-
case 'notify':
|
|
273
|
-
yield* emitDeadlineEventEffect({
|
|
274
|
-
run,
|
|
275
|
-
nodeRun,
|
|
276
|
-
eventType: 'deadline-missed',
|
|
253
|
+
),
|
|
254
|
+
])
|
|
255
|
+
const blockNode = (message: string): Effect.Effect<void, PlanDeadlineError> =>
|
|
256
|
+
effectTryPromise(
|
|
257
|
+
() =>
|
|
258
|
+
planExecutorService.blockNodeOnDispatchFailure({
|
|
259
|
+
threadId: params.threadId,
|
|
260
|
+
runId: runIdStr,
|
|
261
|
+
nodeId: params.nodeId,
|
|
277
262
|
emittedBy: params.emittedBy,
|
|
278
|
-
message
|
|
279
|
-
|
|
280
|
-
})
|
|
281
|
-
|
|
263
|
+
message,
|
|
264
|
+
failureClass: 'timeout_exceeded',
|
|
265
|
+
}),
|
|
266
|
+
`Failed to ${message.toLowerCase()}.`,
|
|
267
|
+
)
|
|
282
268
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
},
|
|
295
|
-
})
|
|
296
|
-
return
|
|
269
|
+
switch (params.action) {
|
|
270
|
+
case 'notify':
|
|
271
|
+
yield* emitDeadlineEventEffect({
|
|
272
|
+
run,
|
|
273
|
+
nodeRun,
|
|
274
|
+
eventType: 'deadline-missed',
|
|
275
|
+
emittedBy: params.emittedBy,
|
|
276
|
+
message: `Node "${params.nodeId}" has missed its deadline.`,
|
|
277
|
+
detail: { title: 'Deadline missed', dedupeKey: `plan-deadline:${runIdStr}:${params.nodeId}:missed:notify` },
|
|
278
|
+
})
|
|
279
|
+
return
|
|
297
280
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
281
|
+
case 'escalate':
|
|
282
|
+
yield* emitDeadlineEventEffect({
|
|
283
|
+
run,
|
|
284
|
+
nodeRun,
|
|
285
|
+
eventType: 'escalation-triggered',
|
|
286
|
+
emittedBy: params.emittedBy,
|
|
287
|
+
message: `Node "${params.nodeId}" has missed its deadline and requires escalation.`,
|
|
288
|
+
detail: {
|
|
289
|
+
title: 'Deadline escalation',
|
|
290
|
+
dedupeKey: `plan-deadline:${runIdStr}:${params.nodeId}:missed:escalate`,
|
|
291
|
+
missedDeadline: true,
|
|
292
|
+
},
|
|
293
|
+
})
|
|
294
|
+
return
|
|
309
295
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
})
|
|
323
|
-
}
|
|
296
|
+
case 'block':
|
|
297
|
+
yield* emitDeadlineEventEffect({
|
|
298
|
+
run,
|
|
299
|
+
nodeRun,
|
|
300
|
+
eventType: 'deadline-missed',
|
|
301
|
+
emittedBy: params.emittedBy,
|
|
302
|
+
message: `Node "${params.nodeId}" has missed its deadline.`,
|
|
303
|
+
detail: { title: 'Deadline missed', dedupeKey: `plan-deadline:${runIdStr}:${params.nodeId}:missed:block` },
|
|
304
|
+
})
|
|
305
|
+
yield* blockNode('Deadline missed — node blocked')
|
|
306
|
+
return
|
|
324
307
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
308
|
+
case 'fail':
|
|
309
|
+
yield* emitDeadlineEventEffect({
|
|
310
|
+
run,
|
|
311
|
+
nodeRun,
|
|
312
|
+
eventType: 'deadline-missed',
|
|
313
|
+
emittedBy: params.emittedBy,
|
|
314
|
+
message: `Node "${params.nodeId}" has missed its deadline.`,
|
|
315
|
+
detail: { title: 'Deadline missed', dedupeKey: `plan-deadline:${runIdStr}:${params.nodeId}:missed:fail` },
|
|
316
|
+
})
|
|
317
|
+
yield* blockNode('Deadline missed — node failed')
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
})
|
|
333
321
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
322
|
+
const maybeEmitEscalationPolicyEventEffect = Effect.fn('PlanDeadline.maybeEmitEscalationPolicyEvent')(
|
|
323
|
+
function* (params: { run: PlanRunRecord; nodeRun: PlanNodeRunRecord; nodeSpec: PlanNodeSpecRecord; now: Date }) {
|
|
324
|
+
const escalation = params.nodeSpec.escalation
|
|
325
|
+
if (!escalation) return
|
|
326
|
+
|
|
327
|
+
const startedAt = unsafeDateFrom(params.nodeRun.startedAt ?? params.nodeRun.createdAt)
|
|
328
|
+
const runIdStr = recordIdToString(params.run.id, TABLES.PLAN_RUN)
|
|
329
|
+
const baseKey = `plan-escalation:${runIdStr}:${params.nodeRun.nodeId}`
|
|
337
330
|
|
|
338
|
-
return Effect.gen(function* () {
|
|
339
331
|
if (escalation.autoEscalateAfterMinutes) {
|
|
340
332
|
const thresholdMs = escalation.autoEscalateAfterMinutes * 60_000
|
|
341
333
|
if (params.now.getTime() - startedAt.getTime() >= thresholdMs) {
|
|
@@ -378,146 +370,140 @@ export function makePlanDeadlineService(deps: PlanDeadlineDeps) {
|
|
|
378
370
|
})
|
|
379
371
|
}
|
|
380
372
|
}
|
|
381
|
-
}
|
|
382
|
-
|
|
373
|
+
},
|
|
374
|
+
)
|
|
383
375
|
|
|
384
|
-
const enqueueDeadlineCheckEffect = (scheduledFor: Date)
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
})
|
|
376
|
+
const enqueueDeadlineCheckEffect = Effect.fn('PlanDeadline.enqueueDeadlineCheck')(function* (scheduledFor: Date) {
|
|
377
|
+
const { enqueueDeadlineCheck } = yield* loadPlanSchedulerQueue()
|
|
378
|
+
yield* effectTryPromise(() => enqueueDeadlineCheck(scheduledFor), 'Failed to enqueue deadline check.')
|
|
379
|
+
})
|
|
389
380
|
|
|
390
|
-
const checkDeadlinesEffect = (now?: Date)
|
|
381
|
+
const checkDeadlinesEffect = Effect.fn('PlanDeadline.checkDeadlines')(function* (now?: Date) {
|
|
391
382
|
const currentTime = now ?? nowDate()
|
|
392
383
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const runCache = new Map<string, PlanRunRecord>()
|
|
384
|
+
const sweep = yield* collectDeadlineSweepEffect(currentTime)
|
|
385
|
+
if (sweep.entries.length === 0) {
|
|
386
|
+
return
|
|
387
|
+
}
|
|
400
388
|
|
|
401
|
-
|
|
402
|
-
Effect.gen(function* () {
|
|
403
|
-
const deadline = entry.nodeSpec.deadline
|
|
404
|
-
if (!deadline) {
|
|
405
|
-
return
|
|
406
|
-
}
|
|
389
|
+
const runCache = new Map<string, PlanRunRecord>()
|
|
407
390
|
|
|
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)
|
|
391
|
+
const handleEntry = (entry: (typeof sweep.entries)[number]): Effect.Effect<void, PlanDeadlineError> =>
|
|
392
|
+
Effect.gen(function* () {
|
|
393
|
+
const deadline = entry.nodeSpec.deadline
|
|
394
|
+
if (!deadline) {
|
|
395
|
+
return
|
|
396
|
+
}
|
|
425
397
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
398
|
+
const runIdStr = recordIdToString(entry.nodeRun.runId, TABLES.PLAN_RUN)
|
|
399
|
+
const cachedRun = runCache.get(runIdStr)
|
|
400
|
+
const run =
|
|
401
|
+
cachedRun ??
|
|
402
|
+
(yield* effectTryPromise(
|
|
403
|
+
() =>
|
|
404
|
+
db.findOne(TABLES.PLAN_RUN, { id: ensureRecordId(entry.nodeRun.runId, TABLES.PLAN_RUN) }, PlanRunSchema),
|
|
405
|
+
'Failed to load plan run during deadline check.',
|
|
406
|
+
))
|
|
407
|
+
if (!run) {
|
|
408
|
+
return
|
|
409
|
+
}
|
|
410
|
+
runCache.set(runIdStr, run)
|
|
411
|
+
|
|
412
|
+
const dedupeKeyBase = `plan-deadline:${runIdStr}:${entry.nodeRun.nodeId}`
|
|
413
|
+
const actionEffect =
|
|
414
|
+
entry.evaluation.status === 'warning'
|
|
415
|
+
? emitDeadlineEventEffect({
|
|
416
|
+
run,
|
|
417
|
+
nodeRun: entry.nodeRun,
|
|
418
|
+
eventType: 'deadline-warning',
|
|
419
|
+
emittedBy: 'plan-deadline-checker',
|
|
420
|
+
message:
|
|
421
|
+
entry.evaluation.activeReminder?.message ??
|
|
422
|
+
`Node "${entry.nodeRun.nodeId}" is approaching its deadline.`,
|
|
423
|
+
detail: {
|
|
424
|
+
title: 'Deadline approaching',
|
|
425
|
+
reminderBeforeMs: entry.evaluation.activeReminder?.beforeMs ?? null,
|
|
426
|
+
dedupeKey: `${dedupeKeyBase}:warning:${entry.evaluation.activeReminder?.beforeMs ?? 'default'}`,
|
|
427
|
+
},
|
|
428
|
+
})
|
|
429
|
+
: entry.evaluation.status === 'escalated'
|
|
429
430
|
? emitDeadlineEventEffect({
|
|
430
431
|
run,
|
|
431
432
|
nodeRun: entry.nodeRun,
|
|
432
|
-
eventType: '
|
|
433
|
+
eventType: 'escalation-triggered',
|
|
433
434
|
emittedBy: 'plan-deadline-checker',
|
|
434
435
|
message:
|
|
435
436
|
entry.evaluation.activeReminder?.message ??
|
|
436
|
-
`Node "${entry.nodeRun.nodeId}"
|
|
437
|
+
`Node "${entry.nodeRun.nodeId}" deadline requires escalation.`,
|
|
437
438
|
detail: {
|
|
438
|
-
title: 'Deadline
|
|
439
|
+
title: 'Deadline escalation',
|
|
439
440
|
reminderBeforeMs: entry.evaluation.activeReminder?.beforeMs ?? null,
|
|
440
|
-
dedupeKey: `${dedupeKeyBase}:
|
|
441
|
+
dedupeKey: `${dedupeKeyBase}:escalated:${entry.evaluation.activeReminder?.beforeMs ?? 'default'}`,
|
|
441
442
|
},
|
|
442
443
|
})
|
|
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
|
|
444
|
+
: applyDeadlineMissActionEffect({
|
|
445
|
+
runId: entry.nodeRun.runId,
|
|
446
|
+
nodeId: entry.nodeRun.nodeId,
|
|
447
|
+
threadId: recordIdToString(run.threadId, TABLES.THREAD),
|
|
448
|
+
organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
|
|
449
|
+
action: deadline.missAction,
|
|
450
|
+
emittedBy: 'plan-deadline-checker',
|
|
451
|
+
})
|
|
491
452
|
|
|
492
|
-
|
|
493
|
-
yield*
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
453
|
+
yield* actionEffect
|
|
454
|
+
yield* maybeEmitEscalationPolicyEventEffect({
|
|
455
|
+
run,
|
|
456
|
+
nodeRun: entry.nodeRun,
|
|
457
|
+
nodeSpec: entry.nodeSpec,
|
|
458
|
+
now: currentTime,
|
|
459
|
+
})
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
yield* Effect.forEach(
|
|
463
|
+
sweep.entries.filter((entry) => entry.evaluation.status !== 'ok'),
|
|
464
|
+
handleEntry,
|
|
465
|
+
{ concurrency: 3, discard: true },
|
|
466
|
+
).pipe(Effect.withSpan('PlanDeadline.processDeadlineEntries'))
|
|
467
|
+
|
|
468
|
+
const nextTriggerAt =
|
|
469
|
+
sweep.entries
|
|
470
|
+
.map((entry) => entry.evaluation.nextTriggerAt)
|
|
471
|
+
.filter((value): value is Date => {
|
|
472
|
+
if (!value) return false
|
|
473
|
+
return value.getTime() > currentTime.getTime()
|
|
474
|
+
})
|
|
475
|
+
.sort((a, b) => a.getTime() - b.getTime())
|
|
476
|
+
.at(0) ?? null
|
|
497
477
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
478
|
+
if (nextTriggerAt) {
|
|
479
|
+
yield* enqueueDeadlineCheckEffect(nextTriggerAt)
|
|
480
|
+
}
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
const recoverDeadlineChecksEffect = Effect.fn('PlanDeadline.recoverDeadlineChecks')(function* (
|
|
484
|
+
now: Date = nowDate(),
|
|
485
|
+
) {
|
|
486
|
+
const sweep = yield* collectDeadlineSweepEffect(now)
|
|
487
|
+
const hasDueAction = sweep.entries.some((entry) => entry.evaluation.status !== 'ok')
|
|
488
|
+
if (hasDueAction) {
|
|
489
|
+
yield* enqueueDeadlineCheckEffect(now)
|
|
490
|
+
return
|
|
491
|
+
}
|
|
506
492
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
493
|
+
const nextTriggerAt =
|
|
494
|
+
sweep.entries
|
|
495
|
+
.map((entry) => entry.evaluation.nextTriggerAt)
|
|
496
|
+
.filter((value): value is Date => {
|
|
497
|
+
if (!value) return false
|
|
498
|
+
return value.getTime() > now.getTime()
|
|
499
|
+
})
|
|
500
|
+
.sort((a, b) => a.getTime() - b.getTime())
|
|
501
|
+
.at(0) ?? null
|
|
516
502
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
503
|
+
if (nextTriggerAt) {
|
|
504
|
+
yield* enqueueDeadlineCheckEffect(nextTriggerAt)
|
|
505
|
+
}
|
|
506
|
+
})
|
|
521
507
|
|
|
522
508
|
return {
|
|
523
509
|
evaluateDeadline,
|
|
@@ -533,7 +519,7 @@ export function makePlanDeadlineService(deps: PlanDeadlineDeps) {
|
|
|
533
519
|
export class PlanDeadlineServiceTag extends Context.Service<
|
|
534
520
|
PlanDeadlineServiceTag,
|
|
535
521
|
ReturnType<typeof makePlanDeadlineService>
|
|
536
|
-
>()('PlanDeadlineService') {}
|
|
522
|
+
>()('@lota-sdk/core/PlanDeadlineService') {}
|
|
537
523
|
|
|
538
524
|
export const PlanDeadlineServiceLive = Layer.effect(
|
|
539
525
|
PlanDeadlineServiceTag,
|