@lota-sdk/core 0.4.7 → 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/model-constants.ts +1 -0
  9. package/src/config/thread-defaults.ts +33 -21
  10. package/src/create-runtime.ts +725 -383
  11. package/src/db/base.service.ts +52 -28
  12. package/src/db/cursor-pagination.ts +71 -30
  13. package/src/db/memory-store.helpers.ts +4 -7
  14. package/src/db/memory-store.ts +856 -598
  15. package/src/db/memory.ts +398 -275
  16. package/src/db/record-id.ts +32 -10
  17. package/src/db/schema-fingerprint.ts +30 -12
  18. package/src/db/service-normalization.ts +255 -0
  19. package/src/db/service.ts +726 -761
  20. package/src/db/startup.ts +140 -66
  21. package/src/db/transaction-conflict.ts +15 -0
  22. package/src/effect/awaitable-effect.ts +87 -0
  23. package/src/effect/errors.ts +121 -0
  24. package/src/effect/helpers.ts +98 -0
  25. package/src/effect/index.ts +22 -0
  26. package/src/effect/layers.ts +228 -0
  27. package/src/effect/runtime-ref.ts +25 -0
  28. package/src/effect/runtime.ts +31 -0
  29. package/src/effect/services.ts +57 -0
  30. package/src/effect/zod.ts +43 -0
  31. package/src/embeddings/provider.ts +122 -71
  32. package/src/index.ts +46 -1
  33. package/src/openrouter/direct-provider.ts +29 -0
  34. package/src/queues/autonomous-job.queue.ts +130 -74
  35. package/src/queues/context-compaction.queue.ts +60 -15
  36. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  37. package/src/queues/document-processor.queue.ts +52 -77
  38. package/src/queues/memory-consolidation.queue.ts +47 -32
  39. package/src/queues/organization-learning.queue.ts +13 -4
  40. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  41. package/src/queues/plan-scheduler.queue.ts +107 -31
  42. package/src/queues/post-chat-memory.queue.ts +66 -24
  43. package/src/queues/queue-factory.ts +142 -52
  44. package/src/queues/standalone-worker.ts +39 -0
  45. package/src/queues/title-generation.queue.ts +54 -9
  46. package/src/redis/connection.ts +84 -32
  47. package/src/redis/index.ts +6 -8
  48. package/src/redis/org-memory-lock.ts +60 -27
  49. package/src/redis/redis-lease-lock.ts +200 -121
  50. package/src/redis/runtime-connection.ts +10 -0
  51. package/src/redis/stream-context.ts +84 -46
  52. package/src/runtime/agent-identity-overrides.ts +2 -2
  53. package/src/runtime/agent-runtime-policy.ts +4 -1
  54. package/src/runtime/agent-stream-helpers.ts +20 -9
  55. package/src/runtime/chat-run-orchestration.ts +102 -19
  56. package/src/runtime/chat-run-registry.ts +36 -2
  57. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  58. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  59. package/src/runtime/execution-plan-visibility.ts +2 -2
  60. package/src/runtime/execution-plan.ts +42 -15
  61. package/src/runtime/graph-designer.ts +11 -7
  62. package/src/runtime/helper-model.ts +135 -48
  63. package/src/runtime/index.ts +7 -7
  64. package/src/runtime/indexed-repositories-policy.ts +3 -3
  65. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  66. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  67. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  68. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  69. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  70. package/src/runtime/plugin-resolution.ts +144 -24
  71. package/src/runtime/plugin-types.ts +9 -1
  72. package/src/runtime/post-turn-side-effects.ts +197 -130
  73. package/src/runtime/retrieval-adapters.ts +38 -4
  74. package/src/runtime/runtime-config.ts +165 -61
  75. package/src/runtime/runtime-extensions.ts +21 -34
  76. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  77. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  78. package/src/runtime/social-chat/social-chat.ts +594 -0
  79. package/src/runtime/specialist-runner.ts +36 -10
  80. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  81. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  82. package/src/runtime/thread-chat-helpers.ts +2 -2
  83. package/src/runtime/thread-plan-turn.ts +2 -1
  84. package/src/runtime/thread-turn-context.ts +172 -94
  85. package/src/runtime/turn-lifecycle.ts +93 -27
  86. package/src/services/agent-activity.service.ts +287 -203
  87. package/src/services/agent-executor.service.ts +329 -217
  88. package/src/services/artifact.service.ts +225 -148
  89. package/src/services/attachment.service.ts +137 -115
  90. package/src/services/autonomous-job.service.ts +888 -491
  91. package/src/services/chat-run-registry.service.ts +11 -1
  92. package/src/services/context-compaction.service.ts +136 -86
  93. package/src/services/document-chunk.service.ts +162 -90
  94. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  95. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  96. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  97. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  98. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  99. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  100. package/src/services/feedback-loop.service.ts +132 -76
  101. package/src/services/global-orchestrator.service.ts +80 -170
  102. package/src/services/graph-full-routing.ts +182 -0
  103. package/src/services/index.ts +18 -20
  104. package/src/services/institutional-memory.service.ts +220 -123
  105. package/src/services/learned-skill.service.ts +364 -259
  106. package/src/services/memory/memory-conversation.ts +95 -0
  107. package/src/services/memory/memory-org-memory.ts +39 -0
  108. package/src/services/memory/memory-preseeded.ts +80 -0
  109. package/src/services/memory/memory-rerank.ts +297 -0
  110. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  111. package/src/services/memory/memory.service.ts +692 -0
  112. package/src/services/memory/rerank.service.ts +209 -0
  113. package/src/services/monitoring-window.service.ts +92 -70
  114. package/src/services/mutating-approval.service.ts +62 -53
  115. package/src/services/node-workspace.service.ts +141 -98
  116. package/src/services/notification.service.ts +17 -16
  117. package/src/services/organization-member.service.ts +120 -66
  118. package/src/services/organization.service.ts +144 -51
  119. package/src/services/ownership-dispatcher.service.ts +415 -264
  120. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  121. package/src/services/plan/plan-agent-query.service.ts +322 -0
  122. package/src/services/plan/plan-approval.service.ts +102 -0
  123. package/src/services/plan/plan-artifact.service.ts +60 -0
  124. package/src/services/plan/plan-builder.service.ts +76 -0
  125. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  126. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  127. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  128. package/src/services/plan/plan-coordination.service.ts +181 -0
  129. package/src/services/plan/plan-cycle.service.ts +398 -0
  130. package/src/services/plan/plan-deadline.service.ts +547 -0
  131. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  132. package/src/services/plan/plan-executor-context.ts +35 -0
  133. package/src/services/plan/plan-executor-graph.ts +475 -0
  134. package/src/services/plan/plan-executor-helpers.ts +322 -0
  135. package/src/services/plan/plan-executor-persistence.ts +209 -0
  136. package/src/services/plan/plan-executor.service.ts +1654 -0
  137. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  138. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  139. package/src/services/plan/plan-run-serialization.ts +15 -0
  140. package/src/services/plan/plan-run.service.ts +644 -0
  141. package/src/services/plan/plan-scheduler.service.ts +385 -0
  142. package/src/services/plan/plan-template.service.ts +224 -0
  143. package/src/services/plan/plan-transaction-events.ts +33 -0
  144. package/src/services/plan/plan-validator.service.ts +907 -0
  145. package/src/services/plan/plan-workspace.service.ts +125 -0
  146. package/src/services/plugin-executor.service.ts +97 -68
  147. package/src/services/quality-metrics.service.ts +112 -94
  148. package/src/services/queue-job.service.ts +296 -230
  149. package/src/services/recent-activity-title.service.ts +65 -36
  150. package/src/services/recent-activity.service.ts +274 -259
  151. package/src/services/skill-resolver.service.ts +38 -12
  152. package/src/services/social-chat-history.service.ts +176 -125
  153. package/src/services/system-executor.service.ts +91 -61
  154. package/src/services/thread/thread-active-run.ts +203 -0
  155. package/src/services/thread/thread-bootstrap.ts +369 -0
  156. package/src/services/thread/thread-listing.ts +198 -0
  157. package/src/services/thread/thread-memory-block.ts +117 -0
  158. package/src/services/thread/thread-message.service.ts +363 -0
  159. package/src/services/thread/thread-record-store.ts +155 -0
  160. package/src/services/thread/thread-title.service.ts +74 -0
  161. package/src/services/thread/thread-turn-execution.ts +280 -0
  162. package/src/services/thread/thread-turn-message-context.ts +73 -0
  163. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  164. package/src/services/thread/thread-turn-streaming.ts +402 -0
  165. package/src/services/thread/thread-turn-tracing.ts +35 -0
  166. package/src/services/thread/thread-turn.ts +343 -0
  167. package/src/services/thread/thread.service.ts +335 -0
  168. package/src/services/user.service.ts +82 -32
  169. package/src/services/write-intent-validator.service.ts +63 -51
  170. package/src/storage/attachment-parser.ts +69 -27
  171. package/src/storage/attachment-storage.service.ts +331 -275
  172. package/src/storage/generated-document-storage.service.ts +66 -34
  173. package/src/system-agents/agent-result.ts +3 -1
  174. package/src/system-agents/context-compaction.agent.ts +2 -2
  175. package/src/system-agents/delegated-agent-factory.ts +159 -90
  176. package/src/system-agents/memory-reranker.agent.ts +2 -2
  177. package/src/system-agents/memory.agent.ts +2 -2
  178. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  179. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  180. package/src/system-agents/skill-extractor.agent.ts +2 -2
  181. package/src/system-agents/skill-manager.agent.ts +2 -2
  182. package/src/system-agents/thread-router.agent.ts +157 -113
  183. package/src/system-agents/title-generator.agent.ts +2 -2
  184. package/src/tools/execution-plan.tool.ts +220 -161
  185. package/src/tools/fetch-webpage.tool.ts +21 -17
  186. package/src/tools/firecrawl-client.ts +16 -6
  187. package/src/tools/index.ts +1 -0
  188. package/src/tools/memory-block.tool.ts +14 -6
  189. package/src/tools/plan-approval.tool.ts +49 -47
  190. package/src/tools/read-file-parts.tool.ts +44 -33
  191. package/src/tools/remember-memory.tool.ts +65 -45
  192. package/src/tools/search-web.tool.ts +26 -22
  193. package/src/tools/search.tool.ts +41 -29
  194. package/src/tools/team-think.tool.ts +124 -83
  195. package/src/tools/user-questions.tool.ts +4 -3
  196. package/src/tools/web-tool-shared.ts +6 -0
  197. package/src/utils/async.ts +17 -23
  198. package/src/utils/crypto.ts +21 -0
  199. package/src/utils/date-time.ts +40 -1
  200. package/src/utils/errors.ts +95 -16
  201. package/src/utils/hono-error-handler.ts +24 -39
  202. package/src/utils/index.ts +2 -1
  203. package/src/utils/null-proto-record.ts +41 -0
  204. package/src/utils/sse-keepalive.ts +124 -21
  205. package/src/workers/bootstrap.ts +186 -51
  206. package/src/workers/memory-consolidation.worker.ts +325 -237
  207. package/src/workers/organization-learning.worker.ts +50 -16
  208. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  209. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  210. package/src/workers/skill-extraction.runner.ts +176 -93
  211. package/src/workers/utils/file-section-chunker.ts +8 -10
  212. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  213. package/src/workers/utils/repomix-file-sections.ts +2 -2
  214. package/src/workers/utils/thread-message-query.ts +97 -38
  215. package/src/workers/worker-utils.ts +56 -31
  216. package/src/config/debug-logger.ts +0 -47
  217. package/src/redis/connection-accessor.ts +0 -26
  218. package/src/runtime/context-compaction-runtime.ts +0 -87
  219. package/src/runtime/social-chat-agent-runner.ts +0 -118
  220. package/src/runtime/social-chat.ts +0 -516
  221. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  222. package/src/services/adaptive-playbook.service.ts +0 -152
  223. package/src/services/artifact-provenance.service.ts +0 -172
  224. package/src/services/chat-attachments.service.ts +0 -17
  225. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  226. package/src/services/execution-plan.service.ts +0 -1118
  227. package/src/services/memory.service.ts +0 -844
  228. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  229. package/src/services/plan-agent-query.service.ts +0 -267
  230. package/src/services/plan-approval.service.ts +0 -83
  231. package/src/services/plan-artifact.service.ts +0 -50
  232. package/src/services/plan-builder.service.ts +0 -67
  233. package/src/services/plan-checkpoint.service.ts +0 -81
  234. package/src/services/plan-completion-side-effects.ts +0 -80
  235. package/src/services/plan-coordination.service.ts +0 -157
  236. package/src/services/plan-cycle.service.ts +0 -284
  237. package/src/services/plan-deadline.service.ts +0 -430
  238. package/src/services/plan-event-delivery.service.ts +0 -166
  239. package/src/services/plan-executor.service.ts +0 -1950
  240. package/src/services/plan-run.service.ts +0 -515
  241. package/src/services/plan-scheduler.service.ts +0 -240
  242. package/src/services/plan-template.service.ts +0 -177
  243. package/src/services/plan-validator.service.ts +0 -818
  244. package/src/services/plan-workspace.service.ts +0 -83
  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()