@lota-sdk/core 0.4.8 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +94 -22
  3. package/src/ai-gateway/ai-gateway.ts +738 -223
  4. package/src/config/agent-defaults.ts +176 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/constants.ts +8 -2
  7. package/src/config/logger.ts +286 -19
  8. package/src/config/thread-defaults.ts +33 -21
  9. package/src/create-runtime.ts +725 -387
  10. package/src/db/base.service.ts +52 -28
  11. package/src/db/cursor-pagination.ts +71 -30
  12. package/src/db/memory-store.helpers.ts +4 -7
  13. package/src/db/memory-store.ts +856 -598
  14. package/src/db/memory.ts +398 -275
  15. package/src/db/record-id.ts +32 -10
  16. package/src/db/schema-fingerprint.ts +30 -12
  17. package/src/db/service-normalization.ts +255 -0
  18. package/src/db/service.ts +726 -761
  19. package/src/db/startup.ts +140 -66
  20. package/src/db/transaction-conflict.ts +15 -0
  21. package/src/effect/awaitable-effect.ts +87 -0
  22. package/src/effect/errors.ts +121 -0
  23. package/src/effect/helpers.ts +98 -0
  24. package/src/effect/index.ts +22 -0
  25. package/src/effect/layers.ts +228 -0
  26. package/src/effect/runtime-ref.ts +25 -0
  27. package/src/effect/runtime.ts +31 -0
  28. package/src/effect/services.ts +57 -0
  29. package/src/effect/zod.ts +43 -0
  30. package/src/embeddings/provider.ts +122 -76
  31. package/src/index.ts +46 -1
  32. package/src/openrouter/direct-provider.ts +11 -35
  33. package/src/queues/autonomous-job.queue.ts +130 -74
  34. package/src/queues/context-compaction.queue.ts +60 -15
  35. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  36. package/src/queues/document-processor.queue.ts +52 -77
  37. package/src/queues/memory-consolidation.queue.ts +47 -32
  38. package/src/queues/organization-learning.queue.ts +13 -4
  39. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  40. package/src/queues/plan-scheduler.queue.ts +107 -31
  41. package/src/queues/post-chat-memory.queue.ts +66 -24
  42. package/src/queues/queue-factory.ts +142 -52
  43. package/src/queues/standalone-worker.ts +39 -0
  44. package/src/queues/title-generation.queue.ts +54 -9
  45. package/src/redis/connection.ts +84 -32
  46. package/src/redis/index.ts +6 -8
  47. package/src/redis/org-memory-lock.ts +60 -27
  48. package/src/redis/redis-lease-lock.ts +200 -121
  49. package/src/redis/runtime-connection.ts +10 -0
  50. package/src/redis/stream-context.ts +84 -46
  51. package/src/runtime/agent-identity-overrides.ts +2 -2
  52. package/src/runtime/agent-runtime-policy.ts +4 -1
  53. package/src/runtime/agent-stream-helpers.ts +20 -9
  54. package/src/runtime/chat-run-orchestration.ts +102 -19
  55. package/src/runtime/chat-run-registry.ts +36 -2
  56. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  57. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  58. package/src/runtime/execution-plan-visibility.ts +2 -2
  59. package/src/runtime/execution-plan.ts +42 -15
  60. package/src/runtime/graph-designer.ts +11 -7
  61. package/src/runtime/helper-model.ts +135 -48
  62. package/src/runtime/index.ts +7 -7
  63. package/src/runtime/indexed-repositories-policy.ts +3 -3
  64. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  65. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  66. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  67. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  68. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  69. package/src/runtime/plugin-resolution.ts +144 -24
  70. package/src/runtime/plugin-types.ts +9 -1
  71. package/src/runtime/post-turn-side-effects.ts +197 -130
  72. package/src/runtime/retrieval-adapters.ts +38 -4
  73. package/src/runtime/runtime-config.ts +150 -61
  74. package/src/runtime/runtime-extensions.ts +21 -34
  75. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  76. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  77. package/src/runtime/social-chat/social-chat.ts +594 -0
  78. package/src/runtime/specialist-runner.ts +36 -10
  79. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  80. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  81. package/src/runtime/thread-chat-helpers.ts +2 -2
  82. package/src/runtime/thread-plan-turn.ts +2 -1
  83. package/src/runtime/thread-turn-context.ts +172 -94
  84. package/src/runtime/turn-lifecycle.ts +93 -27
  85. package/src/services/agent-activity.service.ts +287 -203
  86. package/src/services/agent-executor.service.ts +329 -217
  87. package/src/services/artifact.service.ts +225 -148
  88. package/src/services/attachment.service.ts +137 -115
  89. package/src/services/autonomous-job.service.ts +888 -491
  90. package/src/services/chat-run-registry.service.ts +11 -1
  91. package/src/services/context-compaction.service.ts +136 -86
  92. package/src/services/document-chunk.service.ts +162 -90
  93. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  94. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  95. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  96. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  97. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  98. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  99. package/src/services/feedback-loop.service.ts +132 -76
  100. package/src/services/global-orchestrator.service.ts +80 -170
  101. package/src/services/graph-full-routing.ts +182 -0
  102. package/src/services/index.ts +18 -21
  103. package/src/services/institutional-memory.service.ts +220 -123
  104. package/src/services/learned-skill.service.ts +364 -259
  105. package/src/services/memory/memory-conversation.ts +95 -0
  106. package/src/services/memory/memory-org-memory.ts +39 -0
  107. package/src/services/memory/memory-preseeded.ts +80 -0
  108. package/src/services/memory/memory-rerank.ts +297 -0
  109. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  110. package/src/services/memory/memory.service.ts +692 -0
  111. package/src/services/memory/rerank.service.ts +209 -0
  112. package/src/services/monitoring-window.service.ts +92 -70
  113. package/src/services/mutating-approval.service.ts +62 -53
  114. package/src/services/node-workspace.service.ts +141 -98
  115. package/src/services/notification.service.ts +17 -16
  116. package/src/services/organization-member.service.ts +120 -66
  117. package/src/services/organization.service.ts +144 -51
  118. package/src/services/ownership-dispatcher.service.ts +415 -264
  119. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  120. package/src/services/plan/plan-agent-query.service.ts +322 -0
  121. package/src/services/plan/plan-approval.service.ts +102 -0
  122. package/src/services/plan/plan-artifact.service.ts +60 -0
  123. package/src/services/plan/plan-builder.service.ts +76 -0
  124. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  125. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  126. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  127. package/src/services/plan/plan-coordination.service.ts +181 -0
  128. package/src/services/plan/plan-cycle.service.ts +398 -0
  129. package/src/services/plan/plan-deadline.service.ts +547 -0
  130. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  131. package/src/services/plan/plan-executor-context.ts +35 -0
  132. package/src/services/plan/plan-executor-graph.ts +475 -0
  133. package/src/services/plan/plan-executor-helpers.ts +322 -0
  134. package/src/services/plan/plan-executor-persistence.ts +209 -0
  135. package/src/services/plan/plan-executor.service.ts +1654 -0
  136. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  137. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  138. package/src/services/plan/plan-run-serialization.ts +15 -0
  139. package/src/services/plan/plan-run.service.ts +644 -0
  140. package/src/services/plan/plan-scheduler.service.ts +385 -0
  141. package/src/services/plan/plan-template.service.ts +224 -0
  142. package/src/services/plan/plan-transaction-events.ts +33 -0
  143. package/src/services/plan/plan-validator.service.ts +907 -0
  144. package/src/services/plan/plan-workspace.service.ts +125 -0
  145. package/src/services/plugin-executor.service.ts +97 -68
  146. package/src/services/quality-metrics.service.ts +112 -94
  147. package/src/services/queue-job.service.ts +296 -230
  148. package/src/services/recent-activity-title.service.ts +65 -36
  149. package/src/services/recent-activity.service.ts +274 -259
  150. package/src/services/skill-resolver.service.ts +38 -12
  151. package/src/services/social-chat-history.service.ts +176 -125
  152. package/src/services/system-executor.service.ts +91 -61
  153. package/src/services/thread/thread-active-run.ts +203 -0
  154. package/src/services/thread/thread-bootstrap.ts +369 -0
  155. package/src/services/thread/thread-listing.ts +198 -0
  156. package/src/services/thread/thread-memory-block.ts +117 -0
  157. package/src/services/thread/thread-message.service.ts +363 -0
  158. package/src/services/thread/thread-record-store.ts +155 -0
  159. package/src/services/thread/thread-title.service.ts +74 -0
  160. package/src/services/thread/thread-turn-execution.ts +280 -0
  161. package/src/services/thread/thread-turn-message-context.ts +73 -0
  162. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  163. package/src/services/thread/thread-turn-streaming.ts +402 -0
  164. package/src/services/thread/thread-turn-tracing.ts +35 -0
  165. package/src/services/thread/thread-turn.ts +343 -0
  166. package/src/services/thread/thread.service.ts +335 -0
  167. package/src/services/user.service.ts +82 -32
  168. package/src/services/write-intent-validator.service.ts +63 -51
  169. package/src/storage/attachment-parser.ts +69 -27
  170. package/src/storage/attachment-storage.service.ts +331 -275
  171. package/src/storage/generated-document-storage.service.ts +66 -34
  172. package/src/system-agents/agent-result.ts +3 -1
  173. package/src/system-agents/context-compaction.agent.ts +2 -2
  174. package/src/system-agents/delegated-agent-factory.ts +159 -90
  175. package/src/system-agents/memory-reranker.agent.ts +2 -2
  176. package/src/system-agents/memory.agent.ts +2 -2
  177. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  178. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  179. package/src/system-agents/skill-extractor.agent.ts +2 -2
  180. package/src/system-agents/skill-manager.agent.ts +2 -2
  181. package/src/system-agents/thread-router.agent.ts +157 -113
  182. package/src/system-agents/title-generator.agent.ts +2 -2
  183. package/src/tools/execution-plan.tool.ts +220 -161
  184. package/src/tools/fetch-webpage.tool.ts +21 -17
  185. package/src/tools/firecrawl-client.ts +16 -6
  186. package/src/tools/index.ts +1 -0
  187. package/src/tools/memory-block.tool.ts +14 -6
  188. package/src/tools/plan-approval.tool.ts +49 -47
  189. package/src/tools/read-file-parts.tool.ts +44 -33
  190. package/src/tools/remember-memory.tool.ts +65 -45
  191. package/src/tools/search-web.tool.ts +26 -22
  192. package/src/tools/search.tool.ts +41 -29
  193. package/src/tools/team-think.tool.ts +124 -83
  194. package/src/tools/user-questions.tool.ts +4 -3
  195. package/src/tools/web-tool-shared.ts +6 -0
  196. package/src/utils/async.ts +17 -23
  197. package/src/utils/crypto.ts +21 -0
  198. package/src/utils/date-time.ts +40 -1
  199. package/src/utils/errors.ts +95 -16
  200. package/src/utils/hono-error-handler.ts +24 -39
  201. package/src/utils/index.ts +2 -1
  202. package/src/utils/null-proto-record.ts +41 -0
  203. package/src/utils/sse-keepalive.ts +124 -21
  204. package/src/workers/bootstrap.ts +186 -51
  205. package/src/workers/memory-consolidation.worker.ts +325 -237
  206. package/src/workers/organization-learning.worker.ts +50 -16
  207. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  208. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  209. package/src/workers/skill-extraction.runner.ts +176 -93
  210. package/src/workers/utils/file-section-chunker.ts +8 -10
  211. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  212. package/src/workers/utils/repomix-file-sections.ts +2 -2
  213. package/src/workers/utils/thread-message-query.ts +97 -38
  214. package/src/workers/worker-utils.ts +56 -31
  215. package/src/config/debug-logger.ts +0 -47
  216. package/src/redis/connection-accessor.ts +0 -26
  217. package/src/runtime/context-compaction-runtime.ts +0 -87
  218. package/src/runtime/social-chat-agent-runner.ts +0 -118
  219. package/src/runtime/social-chat.ts +0 -516
  220. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  221. package/src/services/adaptive-playbook.service.ts +0 -152
  222. package/src/services/artifact-provenance.service.ts +0 -172
  223. package/src/services/chat-attachments.service.ts +0 -17
  224. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  225. package/src/services/execution-plan.service.ts +0 -1118
  226. package/src/services/memory.service.ts +0 -914
  227. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  228. package/src/services/plan-agent-query.service.ts +0 -267
  229. package/src/services/plan-approval.service.ts +0 -83
  230. package/src/services/plan-artifact.service.ts +0 -50
  231. package/src/services/plan-builder.service.ts +0 -67
  232. package/src/services/plan-checkpoint.service.ts +0 -81
  233. package/src/services/plan-completion-side-effects.ts +0 -80
  234. package/src/services/plan-coordination.service.ts +0 -157
  235. package/src/services/plan-cycle.service.ts +0 -284
  236. package/src/services/plan-deadline.service.ts +0 -430
  237. package/src/services/plan-event-delivery.service.ts +0 -166
  238. package/src/services/plan-executor.service.ts +0 -1950
  239. package/src/services/plan-run.service.ts +0 -515
  240. package/src/services/plan-scheduler.service.ts +0 -240
  241. package/src/services/plan-template.service.ts +0 -177
  242. package/src/services/plan-validator.service.ts +0 -818
  243. package/src/services/plan-workspace.service.ts +0 -83
  244. package/src/services/rerank.service.ts +0 -156
  245. package/src/services/thread-message.service.ts +0 -275
  246. package/src/services/thread-plan-registry.service.ts +0 -22
  247. package/src/services/thread-title.service.ts +0 -39
  248. package/src/services/thread-turn-preparation.service.ts +0 -1147
  249. package/src/services/thread-turn.ts +0 -172
  250. package/src/services/thread.service.ts +0 -869
  251. package/src/utils/env.ts +0 -8
  252. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  253. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  254. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  255. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  256. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  257. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  258. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  259. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -1,430 +0,0 @@
1
- import type {
2
- DeadlineAction,
3
- DeadlineReminder,
4
- DeadlineSpec,
5
- PlanEventType,
6
- PlanNodeRunRecord,
7
- PlanNodeSpecRecord,
8
- PlanRunRecord,
9
- } from '@lota-sdk/shared'
10
- import { PlanEventSchema, PlanNodeRunSchema, PlanNodeSpecRecordSchema, PlanRunSchema } from '@lota-sdk/shared'
11
- import { BoundQuery, RecordId } from 'surrealdb'
12
-
13
- import type { RecordIdInput } from '../db/record-id'
14
- import { ensureRecordId, recordIdToString } from '../db/record-id'
15
- import { databaseService } from '../db/service'
16
- import { TABLES } from '../db/tables'
17
- import { planEventDeliveryService } from './plan-event-delivery.service'
18
- import { planRunService } from './plan-run.service'
19
-
20
- export type DeadlineEvaluationStatus = 'ok' | 'warning' | 'escalated' | 'missed'
21
-
22
- export interface DeadlineEvaluationResult {
23
- status: DeadlineEvaluationStatus
24
- activeReminder?: DeadlineReminder
25
- nextTriggerAt?: Date | null
26
- }
27
-
28
- class PlanDeadlineService {
29
- evaluateDeadline(params: { deadline: DeadlineSpec; nodeStartedAt: Date; now?: Date }): DeadlineEvaluationResult {
30
- const now = params.now ?? new Date()
31
- const deadlineTime = this.resolveDeadlineTime(params.deadline, params.nodeStartedAt)
32
-
33
- if (!deadlineTime) {
34
- return { status: 'ok', nextTriggerAt: null }
35
- }
36
-
37
- const reminderEntries = [...params.deadline.reminders]
38
- .map((reminder) => ({ reminder, triggerAt: new Date(deadlineTime.getTime() - reminder.beforeMs) }))
39
- .sort((a, b) => a.triggerAt.getTime() - b.triggerAt.getTime())
40
-
41
- let activeReminder: DeadlineReminder | undefined
42
- let nextTriggerAt: Date | null = null
43
-
44
- for (const entry of reminderEntries) {
45
- if (now.getTime() < entry.triggerAt.getTime()) {
46
- nextTriggerAt = entry.triggerAt
47
- break
48
- }
49
-
50
- activeReminder = entry.reminder
51
- }
52
-
53
- if (now.getTime() >= deadlineTime.getTime()) {
54
- return { status: 'missed', nextTriggerAt: null }
55
- }
56
-
57
- if (activeReminder) {
58
- const status: DeadlineEvaluationStatus = activeReminder.action === 'escalate' ? 'escalated' : 'warning'
59
- return { status, activeReminder, nextTriggerAt: nextTriggerAt ?? deadlineTime }
60
- }
61
-
62
- return { status: 'ok', nextTriggerAt: nextTriggerAt ?? deadlineTime }
63
- }
64
-
65
- async checkDeadlines(now?: Date): Promise<void> {
66
- const currentTime = now ?? new Date()
67
-
68
- const sweep = await this.collectDeadlineSweep(currentTime)
69
- if (sweep.entries.length === 0) {
70
- return
71
- }
72
-
73
- // Query parent runs lazily only for node runs that need notification or escalation handling.
74
- const runCache = new Map<string, PlanRunRecord>()
75
-
76
- for (const entry of sweep.entries) {
77
- if (entry.evaluation.status === 'ok') continue
78
-
79
- const deadline = entry.nodeSpec.deadline
80
- if (!deadline) continue
81
-
82
- const runIdStr = recordIdToString(entry.nodeRun.runId, TABLES.PLAN_RUN)
83
- let run = runCache.get(runIdStr)
84
- if (!run) {
85
- const found = await databaseService.findOne(
86
- TABLES.PLAN_RUN,
87
- { id: ensureRecordId(entry.nodeRun.runId, TABLES.PLAN_RUN) },
88
- PlanRunSchema,
89
- )
90
- if (!found) continue
91
- run = found
92
- runCache.set(runIdStr, run)
93
- }
94
-
95
- const dedupeKeyBase = `plan-deadline:${runIdStr}:${entry.nodeRun.nodeId}`
96
-
97
- if (entry.evaluation.status === 'warning') {
98
- await this.emitDeadlineEvent({
99
- run,
100
- nodeRun: entry.nodeRun,
101
- eventType: 'deadline-warning',
102
- emittedBy: 'plan-deadline-checker',
103
- message:
104
- entry.evaluation.activeReminder?.message ?? `Node "${entry.nodeRun.nodeId}" is approaching its deadline.`,
105
- detail: {
106
- title: 'Deadline approaching',
107
- reminderBeforeMs: entry.evaluation.activeReminder?.beforeMs ?? null,
108
- dedupeKey: `${dedupeKeyBase}:warning:${entry.evaluation.activeReminder?.beforeMs ?? 'default'}`,
109
- },
110
- })
111
- } else if (entry.evaluation.status === 'escalated') {
112
- await this.emitDeadlineEvent({
113
- run,
114
- nodeRun: entry.nodeRun,
115
- eventType: 'escalation-triggered',
116
- emittedBy: 'plan-deadline-checker',
117
- message:
118
- entry.evaluation.activeReminder?.message ?? `Node "${entry.nodeRun.nodeId}" deadline requires escalation.`,
119
- detail: {
120
- title: 'Deadline escalation',
121
- reminderBeforeMs: entry.evaluation.activeReminder?.beforeMs ?? null,
122
- dedupeKey: `${dedupeKeyBase}:escalated:${entry.evaluation.activeReminder?.beforeMs ?? 'default'}`,
123
- },
124
- })
125
- } else {
126
- await this.applyDeadlineMissAction({
127
- runId: entry.nodeRun.runId,
128
- nodeId: entry.nodeRun.nodeId,
129
- threadId: recordIdToString(run.threadId, TABLES.THREAD),
130
- organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
131
- action: deadline.missAction,
132
- emittedBy: 'plan-deadline-checker',
133
- })
134
- }
135
-
136
- await this.maybeEmitEscalationPolicyEvent({
137
- run,
138
- nodeRun: entry.nodeRun,
139
- nodeSpec: entry.nodeSpec,
140
- now: currentTime,
141
- })
142
- }
143
-
144
- const nextTriggerAt =
145
- sweep.entries
146
- .map((entry) => entry.evaluation.nextTriggerAt)
147
- .filter((value): value is Date => {
148
- if (!value) return false
149
- return value.getTime() > currentTime.getTime()
150
- })
151
- .sort((a, b) => a.getTime() - b.getTime())
152
- .at(0) ?? null
153
-
154
- if (nextTriggerAt) {
155
- await this.enqueueDeadlineCheck(nextTriggerAt)
156
- }
157
- }
158
-
159
- async recoverDeadlineChecks(now = new Date()): Promise<void> {
160
- const sweep = await this.collectDeadlineSweep(now)
161
- const hasDueAction = sweep.entries.some((entry) => entry.evaluation.status !== 'ok')
162
- if (hasDueAction) {
163
- await this.enqueueDeadlineCheck(now)
164
- return
165
- }
166
-
167
- const nextTriggerAt =
168
- sweep.entries
169
- .map((entry) => entry.evaluation.nextTriggerAt)
170
- .filter((value): value is Date => {
171
- if (!value) return false
172
- return value.getTime() > now.getTime()
173
- })
174
- .sort((a, b) => a.getTime() - b.getTime())
175
- .at(0) ?? null
176
-
177
- if (nextTriggerAt) {
178
- await this.enqueueDeadlineCheck(nextTriggerAt)
179
- }
180
- }
181
-
182
- private async collectDeadlineSweep(
183
- now: Date,
184
- ): Promise<{
185
- entries: Array<{ nodeRun: PlanNodeRunRecord; nodeSpec: PlanNodeSpecRecord; evaluation: DeadlineEvaluationResult }>
186
- }> {
187
- const activeNodeRuns = await databaseService.queryMany(
188
- new BoundQuery(`SELECT * FROM ${TABLES.PLAN_NODE_RUN} WHERE status IN $statuses`, {
189
- statuses: ['running', 'awaiting-human'],
190
- }),
191
- PlanNodeRunSchema,
192
- )
193
-
194
- const entries = []
195
-
196
- for (const nodeRun of activeNodeRuns) {
197
- const nodeSpec = await this.loadNodeSpec(nodeRun)
198
- if (!nodeSpec?.deadline) continue
199
-
200
- const evaluation = this.evaluateDeadline({
201
- deadline: nodeSpec.deadline,
202
- nodeStartedAt: new Date(nodeRun.startedAt ?? nodeRun.createdAt),
203
- now,
204
- })
205
-
206
- entries.push({ nodeRun, nodeSpec, evaluation })
207
- }
208
-
209
- return { entries }
210
- }
211
-
212
- private resolveDeadlineTime(deadline: DeadlineSpec, nodeStartedAt: Date): Date | null {
213
- return (
214
- (deadline.dueAt ? new Date(deadline.dueAt) : null) ??
215
- (deadline.durationMs !== undefined ? new Date(nodeStartedAt.getTime() + deadline.durationMs) : null)
216
- )
217
- }
218
-
219
- private async loadNodeSpec(nodeRun: PlanNodeRunRecord): Promise<PlanNodeSpecRecord | null> {
220
- return databaseService.findOne(
221
- TABLES.PLAN_NODE_SPEC,
222
- { planSpecId: ensureRecordId(nodeRun.planSpecId, TABLES.PLAN_SPEC), nodeId: nodeRun.nodeId },
223
- PlanNodeSpecRecordSchema,
224
- )
225
- }
226
-
227
- async applyDeadlineMissAction(params: {
228
- runId: RecordIdInput
229
- nodeId: string
230
- threadId: string
231
- organizationId: string
232
- action: DeadlineAction
233
- emittedBy: string
234
- }): Promise<void> {
235
- const runIdStr = recordIdToString(params.runId, TABLES.PLAN_RUN)
236
- const run = await planRunService.getRunById(params.runId)
237
- const nodeRun = await planRunService.getNodeRunByNodeId(params.runId, params.nodeId)
238
-
239
- switch (params.action) {
240
- case 'notify':
241
- await this.emitDeadlineEvent({
242
- run,
243
- nodeRun,
244
- eventType: 'deadline-missed',
245
- emittedBy: params.emittedBy,
246
- message: `Node "${params.nodeId}" has missed its deadline.`,
247
- detail: { title: 'Deadline missed', dedupeKey: `plan-deadline:${runIdStr}:${params.nodeId}:missed:notify` },
248
- })
249
- break
250
-
251
- case 'escalate':
252
- await this.emitDeadlineEvent({
253
- run,
254
- nodeRun,
255
- eventType: 'escalation-triggered',
256
- emittedBy: params.emittedBy,
257
- message: `Node "${params.nodeId}" has missed its deadline and requires escalation.`,
258
- detail: {
259
- title: 'Deadline escalation',
260
- dedupeKey: `plan-deadline:${runIdStr}:${params.nodeId}:missed:escalate`,
261
- missedDeadline: true,
262
- },
263
- })
264
- break
265
-
266
- case 'block': {
267
- await this.emitDeadlineEvent({
268
- run,
269
- nodeRun,
270
- eventType: 'deadline-missed',
271
- emittedBy: params.emittedBy,
272
- message: `Node "${params.nodeId}" has missed its deadline.`,
273
- detail: { title: 'Deadline missed', dedupeKey: `plan-deadline:${runIdStr}:${params.nodeId}:missed:block` },
274
- })
275
- const { planExecutorService } = await import('./plan-executor.service')
276
- await planExecutorService.blockNodeOnDispatchFailure({
277
- threadId: params.threadId,
278
- runId: runIdStr,
279
- nodeId: params.nodeId,
280
- emittedBy: params.emittedBy,
281
- message: 'Deadline missed — node blocked',
282
- failureClass: 'timeout_exceeded',
283
- })
284
- break
285
- }
286
-
287
- case 'fail': {
288
- await this.emitDeadlineEvent({
289
- run,
290
- nodeRun,
291
- eventType: 'deadline-missed',
292
- emittedBy: params.emittedBy,
293
- message: `Node "${params.nodeId}" has missed its deadline.`,
294
- detail: { title: 'Deadline missed', dedupeKey: `plan-deadline:${runIdStr}:${params.nodeId}:missed:fail` },
295
- })
296
- const { planExecutorService } = await import('./plan-executor.service')
297
- await planExecutorService.blockNodeOnDispatchFailure({
298
- threadId: params.threadId,
299
- runId: runIdStr,
300
- nodeId: params.nodeId,
301
- emittedBy: params.emittedBy,
302
- message: 'Deadline missed — node failed',
303
- failureClass: 'timeout_exceeded',
304
- })
305
- break
306
- }
307
- }
308
- }
309
-
310
- private async maybeEmitEscalationPolicyEvent(params: {
311
- run: PlanRunRecord
312
- nodeRun: PlanNodeRunRecord
313
- nodeSpec: PlanNodeSpecRecord
314
- now: Date
315
- }): Promise<void> {
316
- const escalation = params.nodeSpec.escalation
317
- if (!escalation) return
318
-
319
- const startedAt = new Date(params.nodeRun.startedAt ?? params.nodeRun.createdAt)
320
- const runIdStr = recordIdToString(params.run.id, TABLES.PLAN_RUN)
321
- const baseKey = `plan-escalation:${runIdStr}:${params.nodeRun.nodeId}`
322
-
323
- if (escalation.autoEscalateAfterMinutes) {
324
- const thresholdMs = escalation.autoEscalateAfterMinutes * 60_000
325
- if (params.now.getTime() - startedAt.getTime() >= thresholdMs) {
326
- await this.emitDeadlineEvent({
327
- run: params.run,
328
- nodeRun: params.nodeRun,
329
- eventType: 'escalation-triggered',
330
- emittedBy: 'plan-deadline-checker',
331
- message: `Node "${params.nodeRun.nodeId}" exceeded its auto-escalation threshold.`,
332
- detail: {
333
- title: 'Execution escalation',
334
- dedupeKey: `${baseKey}:auto:${escalation.autoEscalateAfterMinutes}`,
335
- autoEscalateAfterMinutes: escalation.autoEscalateAfterMinutes,
336
- ...(escalation.escalateToAgent ? { escalateToAgent: escalation.escalateToAgent } : {}),
337
- ...(escalation.escalateToUser ? { escalateToUser: escalation.escalateToUser } : {}),
338
- },
339
- })
340
- }
341
- }
342
-
343
- if (escalation.deadlineThresholdMinutes && params.nodeSpec.deadline) {
344
- const dueAt = this.resolveDeadlineTime(params.nodeSpec.deadline, startedAt)
345
- if (!dueAt) return
346
-
347
- const thresholdMs = escalation.deadlineThresholdMinutes * 60_000
348
- if (dueAt.getTime() - params.now.getTime() <= thresholdMs && dueAt.getTime() > params.now.getTime()) {
349
- await this.emitDeadlineEvent({
350
- run: params.run,
351
- nodeRun: params.nodeRun,
352
- eventType: 'escalation-triggered',
353
- emittedBy: 'plan-deadline-checker',
354
- message: `Node "${params.nodeRun.nodeId}" is inside its escalation threshold.`,
355
- detail: {
356
- title: 'Deadline escalation threshold',
357
- dedupeKey: `${baseKey}:threshold:${escalation.deadlineThresholdMinutes}`,
358
- deadlineThresholdMinutes: escalation.deadlineThresholdMinutes,
359
- ...(escalation.escalateToAgent ? { escalateToAgent: escalation.escalateToAgent } : {}),
360
- ...(escalation.escalateToUser ? { escalateToUser: escalation.escalateToUser } : {}),
361
- },
362
- })
363
- }
364
- }
365
- }
366
-
367
- private async emitDeadlineEvent(params: {
368
- run: PlanRunRecord
369
- nodeRun: PlanNodeRunRecord
370
- eventType: Extract<PlanEventType, 'deadline-warning' | 'deadline-missed' | 'escalation-triggered'>
371
- emittedBy: string
372
- message: string
373
- detail: Record<string, unknown>
374
- }): Promise<void> {
375
- const dedupeKey =
376
- typeof params.detail.dedupeKey === 'string' && params.detail.dedupeKey.trim().length > 0
377
- ? params.detail.dedupeKey.trim()
378
- : null
379
-
380
- if (dedupeKey) {
381
- const existing = await databaseService.queryMany(
382
- new BoundQuery(
383
- `SELECT * FROM ${TABLES.PLAN_EVENT}
384
- WHERE runId = $runId
385
- AND nodeId = $nodeId
386
- AND eventType = $eventType
387
- AND detail.dedupeKey = $dedupeKey
388
- LIMIT 1`,
389
- {
390
- runId: ensureRecordId(params.run.id, TABLES.PLAN_RUN),
391
- nodeId: params.nodeRun.nodeId,
392
- eventType: params.eventType,
393
- dedupeKey,
394
- },
395
- ),
396
- PlanEventSchema,
397
- )
398
- if (existing.length > 0) {
399
- return
400
- }
401
- }
402
-
403
- const spec = await planRunService.getPlanSpecById(params.run.planSpecId)
404
- const event = PlanEventSchema.parse(
405
- await databaseService.create(
406
- TABLES.PLAN_EVENT,
407
- {
408
- id: new RecordId(TABLES.PLAN_EVENT, Bun.randomUUIDv7()),
409
- planSpecId: ensureRecordId(spec.id, TABLES.PLAN_SPEC),
410
- runId: ensureRecordId(params.run.id, TABLES.PLAN_RUN),
411
- nodeId: params.nodeRun.nodeId,
412
- eventType: params.eventType,
413
- message: params.message,
414
- emittedBy: params.emittedBy,
415
- detail: params.detail,
416
- },
417
- PlanEventSchema,
418
- ),
419
- )
420
-
421
- await planEventDeliveryService.dispatchEvent(event)
422
- }
423
-
424
- private async enqueueDeadlineCheck(scheduledFor: Date): Promise<void> {
425
- const { enqueueDeadlineCheck } = await import('../queues/plan-scheduler.queue')
426
- await enqueueDeadlineCheck(scheduledFor)
427
- }
428
- }
429
-
430
- export const planDeadlineService = new PlanDeadlineService()
@@ -1,166 +0,0 @@
1
- import { PlanEventSchema } from '@lota-sdk/shared'
2
- import type { PlanEventRecord } from '@lota-sdk/shared'
3
-
4
- import { ensureRecordId, recordIdToString } from '../db/record-id'
5
- import { databaseService } from '../db/service'
6
- import { TABLES } from '../db/tables'
7
- import { getRedisConnection } from '../redis'
8
- import { resolvePlanNodeExecutionVisibility } from '../runtime/execution-plan-visibility'
9
- import { getRuntimeAdapters } from '../runtime/runtime-extensions'
10
- import type { LotaRuntimePlanEventEnvelope } from '../runtime/runtime-extensions'
11
- import { planRunService } from './plan-run.service'
12
- import { ThreadSchema } from './thread.types'
13
- import { userService } from './user.service'
14
-
15
- const PLAN_EVENT_DELIVERED_TTL_MS = 7 * 24 * 60 * 60 * 1000
16
-
17
- function buildDeliveredKey(eventId: string): string {
18
- return `plan-event:delivered:${eventId}`
19
- }
20
-
21
- class PlanEventDeliveryService {
22
- async dispatchEvents(events: PlanEventRecord[]): Promise<void> {
23
- const runIds = new Set(events.map((event) => recordIdToString(event.runId, TABLES.PLAN_RUN)))
24
- for (const runId of runIds) {
25
- await this.dispatchUndeliveredEvents(runId)
26
- }
27
- }
28
-
29
- async dispatchEvent(rawEvent: PlanEventRecord): Promise<void> {
30
- const event = PlanEventSchema.parse(rawEvent)
31
- const eventId = recordIdToString(event.id, TABLES.PLAN_EVENT)
32
- const deliveredKey = buildDeliveredKey(eventId)
33
- if (await getRedisConnection().exists(deliveredKey)) {
34
- return
35
- }
36
-
37
- await this.deliverEvent(event)
38
- await getRedisConnection().set(deliveredKey, '1', 'PX', PLAN_EVENT_DELIVERED_TTL_MS)
39
- }
40
-
41
- private async deliverEvent(rawEvent: PlanEventRecord): Promise<void> {
42
- const event = PlanEventSchema.parse(rawEvent)
43
- const run = await planRunService.getRunById(event.runId)
44
- const [spec, nodeSpecs, nodeRuns, thread] = await Promise.all([
45
- planRunService.getPlanSpecById(run.planSpecId),
46
- planRunService.listNodeSpecs(run.planSpecId),
47
- planRunService.listNodeRuns(run.id),
48
- databaseService.findOne(TABLES.THREAD, { id: ensureRecordId(run.threadId, TABLES.THREAD) }, ThreadSchema),
49
- ])
50
- const nodeSpecsById = new Map(nodeSpecs.map((nodeSpec) => [nodeSpec.nodeId, nodeSpec]))
51
- const nodeRunsById = new Map(nodeRuns.map((nodeRun) => [nodeRun.nodeId, nodeRun]))
52
- const organizationId = recordIdToString(run.organizationId, TABLES.ORGANIZATION)
53
- const threadId = recordIdToString(run.threadId, TABLES.THREAD)
54
- const runIdString = recordIdToString(run.id, TABLES.PLAN_RUN)
55
- const planSpecId = recordIdToString(spec.id, TABLES.PLAN_SPEC)
56
- const userId = thread?.userId
57
- ? recordIdToString(ensureRecordId(thread.userId, TABLES.USER), TABLES.USER)
58
- : undefined
59
- const userName =
60
- userId === undefined
61
- ? undefined
62
- : await userService
63
- .getUser(userId)
64
- .then((user) => user.name)
65
- .catch(() => undefined)
66
-
67
- const envelope: LotaRuntimePlanEventEnvelope = {
68
- event,
69
- spec,
70
- run,
71
- ...(event.nodeId ? { nodeSpec: nodeSpecsById.get(event.nodeId) } : {}),
72
- ...(event.nodeId ? { nodeRun: nodeRunsById.get(event.nodeId) } : {}),
73
- organizationId,
74
- threadId,
75
- runId: runIdString,
76
- planSpecId,
77
- ...(userId ? { userId } : {}),
78
- ...(userName ? { userName } : {}),
79
- }
80
-
81
- await this.dispatchAdapterEvent(envelope)
82
- await this.enqueueWakeIfNeeded(envelope, nodeSpecsById)
83
- }
84
-
85
- async dispatchUndeliveredEvents(runId: string, options?: { limit?: number }): Promise<void> {
86
- const run = await planRunService.getRunById(runId)
87
- const events = await planRunService.listEvents(run.id, options?.limit ?? 200)
88
-
89
- for (const rawEvent of events) {
90
- const event = PlanEventSchema.parse(rawEvent)
91
- const eventId = recordIdToString(event.id, TABLES.PLAN_EVENT)
92
- const deliveredKey = buildDeliveredKey(eventId)
93
- if (await getRedisConnection().exists(deliveredKey)) {
94
- continue
95
- }
96
-
97
- await this.dispatchEvent(event)
98
- }
99
- }
100
-
101
- private async dispatchAdapterEvent(envelope: LotaRuntimePlanEventEnvelope): Promise<void> {
102
- const adapter = getRuntimeAdapters().planEventAdapter
103
- if (!adapter) {
104
- return
105
- }
106
-
107
- await adapter.onPlanEvent(envelope)
108
- }
109
-
110
- private async enqueueWakeIfNeeded(
111
- envelope: LotaRuntimePlanEventEnvelope,
112
- nodeSpecsById: Map<string, NonNullable<LotaRuntimePlanEventEnvelope['nodeSpec']>>,
113
- ): Promise<void> {
114
- const wakeTarget = this.resolveWakeTarget(envelope, nodeSpecsById)
115
- if (!wakeTarget) {
116
- return
117
- }
118
-
119
- const { enqueuePlanAgentHeartbeatWake } = await import('../queues/plan-agent-heartbeat.queue')
120
- await enqueuePlanAgentHeartbeatWake({
121
- organizationId: envelope.organizationId,
122
- threadId: envelope.threadId,
123
- runId: envelope.runId,
124
- nodeId: wakeTarget.nodeId,
125
- agentId: wakeTarget.agentId,
126
- reason: wakeTarget.reason,
127
- })
128
- }
129
-
130
- private resolveWakeTarget(
131
- envelope: LotaRuntimePlanEventEnvelope,
132
- nodeSpecsById: Map<string, NonNullable<LotaRuntimePlanEventEnvelope['nodeSpec']>>,
133
- ): { nodeId: string; agentId: string; reason: string } | null {
134
- const currentNodeId = envelope.run.currentNodeId ?? envelope.event.nodeId
135
- if (!currentNodeId) {
136
- return null
137
- }
138
-
139
- const effectiveNodeSpec =
140
- (envelope.event.nodeId === currentNodeId ? envelope.nodeSpec : undefined) ?? nodeSpecsById.get(currentNodeId)
141
- if (!effectiveNodeSpec || effectiveNodeSpec.owner.executorType !== 'agent') {
142
- return null
143
- }
144
-
145
- const isVisible = resolvePlanNodeExecutionVisibility(envelope.spec, effectiveNodeSpec) === 'visible'
146
- if (!isVisible) {
147
- return null
148
- }
149
-
150
- const supportedEventTypes = new Set([
151
- 'node-ready',
152
- 'node-unblocked',
153
- 'approval-resolved',
154
- 'run-resumed',
155
- 'deadline-warning',
156
- 'escalation-triggered',
157
- ])
158
- if (!supportedEventTypes.has(envelope.event.eventType)) {
159
- return null
160
- }
161
-
162
- return { nodeId: currentNodeId, agentId: effectiveNodeSpec.owner.ref, reason: envelope.event.eventType }
163
- }
164
- }
165
-
166
- export const planEventDeliveryService = new PlanEventDeliveryService()