@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,136 +0,0 @@
1
- import { serverLogger } from '../config/logger'
2
- import { ensureRecordId } from '../db/record-id'
3
- import { TABLES } from '../db/tables'
4
- import { getRedisConnection, withRedisLeaseLock } from '../redis'
5
- import { resolvePlanNodeExecutionVisibility } from '../runtime/execution-plan-visibility'
6
- import { planAgentQueryService } from './plan-agent-query.service'
7
- import { planExecutorService } from './plan-executor.service'
8
- import { planRunService } from './plan-run.service'
9
- import { threadService } from './thread.service'
10
-
11
- const PLAN_AGENT_HEARTBEAT_LOCK_TTL_MS = 300_000
12
- const PLAN_AGENT_HEARTBEAT_LOCK_REFRESH_MS = 20_000
13
-
14
- function buildHeartbeatLockKey(runId: string, nodeId: string): string {
15
- return `plan-agent-heartbeat:${runId}:${nodeId}`
16
- }
17
-
18
- function buildWakeDedupeKey(params: {
19
- organizationId: string
20
- threadId: string
21
- runId: string
22
- nodeId: string
23
- agentId: string
24
- }) {
25
- return `${params.organizationId}:${params.threadId}:${params.runId}:${params.nodeId}:${params.agentId}`
26
- }
27
-
28
- class PlanAgentHeartbeatService {
29
- async wakeNode(params: {
30
- organizationId: string
31
- threadId: string
32
- runId: string
33
- nodeId: string
34
- agentId: string
35
- reason: string
36
- }): Promise<boolean> {
37
- const threadRef = ensureRecordId(params.threadId, TABLES.THREAD)
38
- await threadService.clearStaleActiveRunIfMissingFromRegistry(threadRef)
39
- if (await threadService.getActiveRunId(threadRef)) {
40
- return false
41
- }
42
-
43
- return withRedisLeaseLock(
44
- {
45
- redis: getRedisConnection(),
46
- lockKey: buildHeartbeatLockKey(params.runId, params.nodeId),
47
- lockTtlMs: PLAN_AGENT_HEARTBEAT_LOCK_TTL_MS,
48
- refreshIntervalMs: PLAN_AGENT_HEARTBEAT_LOCK_REFRESH_MS,
49
- label: 'plan agent heartbeat',
50
- maxWaitMs: 5_000,
51
- logger: serverLogger,
52
- },
53
- async () => {
54
- await threadService.clearStaleActiveRunIfMissingFromRegistry(threadRef)
55
- if (await threadService.hasActiveRunLease(threadRef)) {
56
- return false
57
- }
58
- if (await threadService.getActiveRunId(threadRef)) {
59
- return false
60
- }
61
-
62
- const run = await planRunService.getRunById(params.runId)
63
- const spec = await planRunService.getPlanSpecById(run.planSpecId)
64
- const [nodeSpec, nodeRun] = await Promise.all([
65
- planRunService.getNodeSpecByNodeId(spec.id, params.nodeId),
66
- planRunService.getNodeRunByNodeId(run.id, params.nodeId),
67
- ])
68
-
69
- if (nodeSpec.owner.executorType !== 'agent' || nodeSpec.owner.ref !== params.agentId) {
70
- return false
71
- }
72
-
73
- const visibility = resolvePlanNodeExecutionVisibility(spec, nodeSpec)
74
- if (visibility !== 'visible') {
75
- return false
76
- }
77
-
78
- if (nodeRun.status !== 'running' && nodeRun.status !== 'ready') {
79
- return false
80
- }
81
-
82
- if (nodeRun.status === 'ready' && run.currentNodeId === params.nodeId) {
83
- await planExecutorService.transitionNodeToRunning({ runId: params.runId, nodeId: params.nodeId })
84
- }
85
-
86
- const { triggerPlanNodeTurn } = await import('./thread-turn')
87
-
88
- await triggerPlanNodeTurn({ runId: params.runId, nodeId: params.nodeId })
89
- return true
90
- },
91
- )
92
- }
93
-
94
- async sweep(params?: { organizationId?: string }): Promise<void> {
95
- const { enqueuePlanAgentHeartbeatWake } = await import('../queues/plan-agent-heartbeat.queue')
96
- const [actionable, recentlyUnblocked, approachingDeadlines] = await Promise.all([
97
- planAgentQueryService.getActionableNodesForAgent({ organizationId: params?.organizationId }),
98
- planAgentQueryService.getRecentlyUnblockedNodes({ organizationId: params?.organizationId }),
99
- planAgentQueryService.getApproachingDeadlines({ organizationId: params?.organizationId, withinMinutes: 60 }),
100
- ])
101
-
102
- const wakeTargets = new Map<
103
- string,
104
- { organizationId: string; threadId: string; runId: string; nodeId: string; agentId: string; reason: string }
105
- >()
106
-
107
- for (const node of actionable) {
108
- wakeTargets.set(buildWakeDedupeKey(node), { ...node, reason: 'heartbeat-actionable' })
109
- }
110
-
111
- for (const node of recentlyUnblocked) {
112
- wakeTargets.set(buildWakeDedupeKey(node), { ...node, reason: node.sourceEventType })
113
- }
114
-
115
- for (const node of approachingDeadlines) {
116
- if (!node.agentId || node.visibility !== 'visible') {
117
- continue
118
- }
119
- const wakeTarget = {
120
- organizationId: node.organizationId,
121
- threadId: node.threadId,
122
- runId: node.runId,
123
- nodeId: node.nodeId,
124
- agentId: node.agentId,
125
- reason: `deadline-${node.status}`,
126
- }
127
- wakeTargets.set(buildWakeDedupeKey(wakeTarget), wakeTarget)
128
- }
129
-
130
- for (const target of wakeTargets.values()) {
131
- await enqueuePlanAgentHeartbeatWake(target)
132
- }
133
- }
134
- }
135
-
136
- export const planAgentHeartbeatService = new PlanAgentHeartbeatService()
@@ -1,267 +0,0 @@
1
- import type { PlanExecutionVisibility, PlanNodeSpecRecord, PlanRunRecord, PlanSpecRecord } from '@lota-sdk/shared'
2
- import { PlanRunSchema } from '@lota-sdk/shared'
3
- import { BoundQuery } from 'surrealdb'
4
-
5
- import type { RecordIdInput } from '../db/record-id'
6
- import { ensureRecordId, recordIdToString } from '../db/record-id'
7
- import { databaseService } from '../db/service'
8
- import { TABLES } from '../db/tables'
9
- import { resolvePlanNodeExecutionVisibility } from '../runtime/execution-plan-visibility'
10
- import { planDeadlineService } from './plan-deadline.service'
11
- import { planRunService } from './plan-run.service'
12
-
13
- const ACTIVE_PLAN_RUN_STATUSES = ['running', 'awaiting-human', 'blocked'] as const
14
- const ACTIONABLE_NODE_STATUSES = new Set(['ready', 'running'])
15
- const DEADLINE_TRACKED_NODE_STATUSES = new Set(['ready', 'running', 'awaiting-human'])
16
-
17
- export interface ActionablePlanAgentNode {
18
- organizationId: string
19
- threadId: string
20
- runId: string
21
- nodeId: string
22
- agentId: string
23
- status: 'ready' | 'running'
24
- visibility: PlanExecutionVisibility
25
- }
26
-
27
- export interface ApproachingDeadlineNode {
28
- organizationId: string
29
- threadId: string
30
- runId: string
31
- nodeId: string
32
- agentId?: string
33
- visibility?: PlanExecutionVisibility
34
- dueAt?: string
35
- status: 'warning' | 'escalated' | 'missed'
36
- nextTriggerAt?: string | null
37
- }
38
-
39
- export interface RecentlyUnblockedNode {
40
- organizationId: string
41
- threadId: string
42
- runId: string
43
- nodeId: string
44
- agentId: string
45
- visibility: PlanExecutionVisibility
46
- unblockedAt: string
47
- sourceEventType: 'node-unblocked' | 'approval-resolved'
48
- }
49
-
50
- function isVisibleAgentNode(params: {
51
- nodeSpec: PlanNodeSpecRecord
52
- spec: PlanSpecRecord
53
- }): { agentId: string; visibility: PlanExecutionVisibility } | null {
54
- if (params.nodeSpec.owner.executorType !== 'agent') {
55
- return null
56
- }
57
-
58
- const visibility = resolvePlanNodeExecutionVisibility(params.spec, params.nodeSpec)
59
- const isVisible = visibility === 'visible'
60
- if (!isVisible) {
61
- return null
62
- }
63
-
64
- return { agentId: params.nodeSpec.owner.ref, visibility }
65
- }
66
-
67
- class PlanAgentQueryService {
68
- async getActionableNodesForAgent(params: {
69
- agentId?: string
70
- organizationId?: RecordIdInput
71
- }): Promise<ActionablePlanAgentNode[]> {
72
- const runs = await this.listActiveRuns(params.organizationId)
73
- const actionable: ActionablePlanAgentNode[] = []
74
-
75
- for (const run of runs) {
76
- const spec = await planRunService.getPlanSpecById(run.planSpecId)
77
-
78
- // Graph-full plans can have multiple visible agent nodes running in parallel
79
- if (spec.executionMode === 'graph-full') {
80
- const [nodeSpecs, nodeRuns] = await Promise.all([
81
- planRunService.listNodeSpecs(spec.id),
82
- planRunService.listNodeRuns(run.id),
83
- ])
84
- for (const nodeRun of nodeRuns) {
85
- if (!ACTIONABLE_NODE_STATUSES.has(nodeRun.status)) continue
86
- const nodeSpec = nodeSpecs.find((ns) => ns.nodeId === nodeRun.nodeId)
87
- if (!nodeSpec) continue
88
- const visibleTarget = isVisibleAgentNode({ nodeSpec, spec })
89
- if (!visibleTarget) continue
90
- if (params.agentId && params.agentId !== visibleTarget.agentId) continue
91
- actionable.push({
92
- organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
93
- threadId: recordIdToString(run.threadId, TABLES.THREAD),
94
- runId: recordIdToString(run.id, TABLES.PLAN_RUN),
95
- nodeId: nodeSpec.nodeId,
96
- agentId: visibleTarget.agentId,
97
- status: nodeRun.status as 'ready' | 'running',
98
- visibility: visibleTarget.visibility,
99
- })
100
- }
101
- continue
102
- }
103
-
104
- // Linear plans: only the current node is actionable
105
- const currentNodeId = run.currentNodeId
106
- if (!currentNodeId) {
107
- continue
108
- }
109
-
110
- const [nodeSpec, nodeRun] = await Promise.all([
111
- planRunService.getNodeSpecByNodeId(spec.id, currentNodeId),
112
- planRunService.getNodeRunByNodeId(run.id, currentNodeId),
113
- ])
114
- if (!ACTIONABLE_NODE_STATUSES.has(nodeRun.status)) {
115
- continue
116
- }
117
-
118
- const visibleTarget = isVisibleAgentNode({ nodeSpec, spec })
119
- if (!visibleTarget) {
120
- continue
121
- }
122
- if (params.agentId && params.agentId !== visibleTarget.agentId) {
123
- continue
124
- }
125
-
126
- actionable.push({
127
- organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
128
- threadId: recordIdToString(run.threadId, TABLES.THREAD),
129
- runId: recordIdToString(run.id, TABLES.PLAN_RUN),
130
- nodeId: nodeSpec.nodeId,
131
- agentId: visibleTarget.agentId,
132
- status: nodeRun.status as 'ready' | 'running',
133
- visibility: visibleTarget.visibility,
134
- })
135
- }
136
-
137
- return actionable
138
- }
139
-
140
- async getApproachingDeadlines(params?: {
141
- organizationId?: RecordIdInput
142
- withinMinutes?: number
143
- }): Promise<ApproachingDeadlineNode[]> {
144
- const now = new Date()
145
- const maxWindowMs = (params?.withinMinutes ?? 60) * 60_000
146
- const runs = await this.listActiveRuns(params?.organizationId)
147
- const matches: ApproachingDeadlineNode[] = []
148
-
149
- for (const run of runs) {
150
- const spec = await planRunService.getPlanSpecById(run.planSpecId)
151
- const [nodeSpecs, nodeRuns] = await Promise.all([
152
- planRunService.listNodeSpecs(spec.id),
153
- planRunService.listNodeRuns(run.id),
154
- ])
155
- const nodeRunsById = new Map(nodeRuns.map((nodeRun) => [nodeRun.nodeId, nodeRun]))
156
-
157
- for (const nodeSpec of nodeSpecs) {
158
- const nodeRun = nodeRunsById.get(nodeSpec.nodeId)
159
- if (!nodeRun || !nodeSpec.deadline || !DEADLINE_TRACKED_NODE_STATUSES.has(nodeRun.status)) {
160
- continue
161
- }
162
-
163
- const evaluation = planDeadlineService.evaluateDeadline({
164
- deadline: nodeSpec.deadline,
165
- nodeStartedAt: new Date(nodeRun.startedAt ?? nodeRun.createdAt),
166
- now,
167
- })
168
- if (evaluation.status === 'ok') {
169
- continue
170
- }
171
-
172
- const nextTriggerTime = evaluation.nextTriggerAt?.getTime()
173
- if (nextTriggerTime && nextTriggerTime - now.getTime() > maxWindowMs && evaluation.status !== 'missed') {
174
- continue
175
- }
176
-
177
- const visibleTarget = isVisibleAgentNode({ nodeSpec, spec })
178
- matches.push({
179
- organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
180
- threadId: recordIdToString(run.threadId, TABLES.THREAD),
181
- runId: recordIdToString(run.id, TABLES.PLAN_RUN),
182
- nodeId: nodeSpec.nodeId,
183
- ...(visibleTarget ? { agentId: visibleTarget.agentId, visibility: visibleTarget.visibility } : {}),
184
- ...(nodeSpec.deadline.dueAt ? { dueAt: nodeSpec.deadline.dueAt } : {}),
185
- status: evaluation.status,
186
- nextTriggerAt: evaluation.nextTriggerAt?.toISOString() ?? null,
187
- })
188
- }
189
- }
190
-
191
- return matches
192
- }
193
-
194
- async getRecentlyUnblockedNodes(params?: {
195
- organizationId?: RecordIdInput
196
- sinceMinutes?: number
197
- agentId?: string
198
- }): Promise<RecentlyUnblockedNode[]> {
199
- const since = new Date(Date.now() - (params?.sinceMinutes ?? 30) * 60_000)
200
- const runs = await this.listActiveRuns(params?.organizationId)
201
- const matches: RecentlyUnblockedNode[] = []
202
-
203
- for (const run of runs) {
204
- const spec = await planRunService.getPlanSpecById(run.planSpecId)
205
- const [nodeSpecs, events] = await Promise.all([
206
- planRunService.listNodeSpecs(spec.id),
207
- planRunService.listEvents(run.id, 200),
208
- ])
209
- const nodeSpecsById = new Map(nodeSpecs.map((nodeSpec) => [nodeSpec.nodeId, nodeSpec]))
210
-
211
- for (const event of events) {
212
- if (
213
- (event.eventType !== 'node-unblocked' && event.eventType !== 'approval-resolved') ||
214
- !event.nodeId ||
215
- new Date(event.createdAt).getTime() < since.getTime()
216
- ) {
217
- continue
218
- }
219
-
220
- const currentNodeId = run.currentNodeId ?? event.nodeId
221
- const nodeSpec = nodeSpecsById.get(currentNodeId)
222
- if (!nodeSpec) {
223
- continue
224
- }
225
-
226
- const visibleTarget = isVisibleAgentNode({ nodeSpec, spec })
227
- if (!visibleTarget) {
228
- continue
229
- }
230
- if (params?.agentId && params.agentId !== visibleTarget.agentId) {
231
- continue
232
- }
233
-
234
- matches.push({
235
- organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
236
- threadId: recordIdToString(run.threadId, TABLES.THREAD),
237
- runId: recordIdToString(run.id, TABLES.PLAN_RUN),
238
- nodeId: currentNodeId,
239
- agentId: visibleTarget.agentId,
240
- visibility: visibleTarget.visibility,
241
- unblockedAt: new Date(event.createdAt).toISOString(),
242
- sourceEventType: event.eventType,
243
- })
244
- }
245
- }
246
-
247
- return matches
248
- }
249
-
250
- private async listActiveRuns(organizationId?: RecordIdInput): Promise<PlanRunRecord[]> {
251
- const bindings = {
252
- statuses: [...ACTIVE_PLAN_RUN_STATUSES],
253
- ...(organizationId ? { organizationId: ensureRecordId(organizationId, TABLES.ORGANIZATION) } : {}),
254
- }
255
-
256
- const whereOrganization = organizationId ? ' AND organizationId = $organizationId' : ''
257
- return databaseService.queryMany(
258
- new BoundQuery(
259
- `SELECT * FROM ${TABLES.PLAN_RUN} WHERE status INSIDE $statuses${whereOrganization} ORDER BY updatedAt DESC`,
260
- bindings,
261
- ),
262
- PlanRunSchema,
263
- )
264
- }
265
- }
266
-
267
- export const planAgentQueryService = new PlanAgentQueryService()
@@ -1,83 +0,0 @@
1
- import { PlanApprovalSchema } from '@lota-sdk/shared'
2
- import type { PlanApprovalRecord, PlanApprovalStatus } from '@lota-sdk/shared'
3
- import { RecordId } from 'surrealdb'
4
-
5
- import type { RecordIdInput } from '../db/record-id'
6
- import { ensureRecordId } from '../db/record-id'
7
- import { databaseService } from '../db/service'
8
- import type { DatabaseTransaction } from '../db/service'
9
- import { TABLES } from '../db/tables'
10
-
11
- class PlanApprovalService {
12
- async createPendingApproval(params: {
13
- tx: DatabaseTransaction
14
- runId: RecordIdInput
15
- nodeRunId: RecordIdInput
16
- nodeId: string
17
- requestedBy: string
18
- presented: Record<string, unknown>
19
- }): Promise<PlanApprovalRecord> {
20
- const approvalId = new RecordId(TABLES.PLAN_APPROVAL, Bun.randomUUIDv7())
21
- const created = await params.tx
22
- .create(approvalId)
23
- .content({
24
- runId: ensureRecordId(params.runId, TABLES.PLAN_RUN),
25
- nodeRunId: ensureRecordId(params.nodeRunId, TABLES.PLAN_NODE_RUN),
26
- nodeId: params.nodeId,
27
- status: 'pending',
28
- requestedBy: params.requestedBy,
29
- presented: params.presented,
30
- requiredEdits: [],
31
- })
32
- .output('after')
33
-
34
- return PlanApprovalSchema.parse(created)
35
- }
36
-
37
- async getApprovalById(approvalId: RecordIdInput): Promise<PlanApprovalRecord | null> {
38
- return databaseService.findOne(
39
- TABLES.PLAN_APPROVAL,
40
- { id: ensureRecordId(approvalId, TABLES.PLAN_APPROVAL) },
41
- PlanApprovalSchema,
42
- )
43
- }
44
-
45
- async getPendingApprovalForNodeRun(nodeRunId: RecordIdInput): Promise<PlanApprovalRecord | null> {
46
- const approvals = await databaseService.findMany(
47
- TABLES.PLAN_APPROVAL,
48
- { nodeRunId: ensureRecordId(nodeRunId, TABLES.PLAN_NODE_RUN), status: 'pending' },
49
- PlanApprovalSchema,
50
- { orderBy: 'createdAt', orderDir: 'DESC', limit: 1 },
51
- )
52
-
53
- return approvals.at(0) ?? null
54
- }
55
-
56
- async updateApprovalResponse(params: {
57
- tx: DatabaseTransaction
58
- approval: PlanApprovalRecord
59
- status: PlanApprovalStatus
60
- response: Record<string, unknown>
61
- respondedBy: string
62
- approvalMessageId?: string
63
- comments?: string
64
- requiredEdits?: string[]
65
- }): Promise<PlanApprovalRecord> {
66
- const updated = await params.tx
67
- .update(ensureRecordId(params.approval.id, TABLES.PLAN_APPROVAL))
68
- .merge({
69
- status: params.status,
70
- response: params.response,
71
- respondedBy: params.respondedBy,
72
- ...(params.approvalMessageId ? { approvalMessageId: params.approvalMessageId } : {}),
73
- ...(params.comments ? { comments: params.comments } : {}),
74
- ...(params.requiredEdits ? { requiredEdits: params.requiredEdits } : {}),
75
- respondedAt: new Date(),
76
- })
77
- .output('after')
78
-
79
- return PlanApprovalSchema.parse(updated)
80
- }
81
- }
82
-
83
- export const planApprovalService = new PlanApprovalService()
@@ -1,50 +0,0 @@
1
- import { PlanArtifactSchema } from '@lota-sdk/shared'
2
- import type { PlanArtifactRecord, PlanArtifactSubmission } from '@lota-sdk/shared'
3
- import { RecordId } from 'surrealdb'
4
-
5
- import type { RecordIdInput } from '../db/record-id'
6
- import { ensureRecordId } from '../db/record-id'
7
- import type { DatabaseTransaction } from '../db/service'
8
- import { TABLES } from '../db/tables'
9
-
10
- class PlanArtifactService {
11
- async persistArtifacts(params: {
12
- tx: DatabaseTransaction
13
- runId: RecordIdInput
14
- attemptId: RecordIdInput
15
- nodeId: string
16
- artifacts: PlanArtifactSubmission[]
17
- }): Promise<PlanArtifactRecord[]> {
18
- const records: PlanArtifactRecord[] = []
19
-
20
- // Sequential: SurrealDB transactions require ordered operations
21
- for (const artifact of params.artifacts) {
22
- const artifactId = new RecordId(TABLES.PLAN_ARTIFACT, Bun.randomUUIDv7())
23
- const created = await params.tx
24
- .create(artifactId)
25
- .content({
26
- runId: ensureRecordId(params.runId, TABLES.PLAN_RUN),
27
- attemptId: ensureRecordId(params.attemptId, TABLES.PLAN_NODE_ATTEMPT),
28
- nodeId: params.nodeId,
29
- name: artifact.name,
30
- kind: artifact.kind,
31
- pointer: artifact.publishedArtifactId
32
- ? `venturos://artifact/${artifact.publishedArtifactId}`
33
- : `artifact://${params.nodeId}/${artifact.name}`,
34
- ...(artifact.description ? { description: artifact.description } : {}),
35
- ...(artifact.content ? { content: artifact.content } : {}),
36
- ...(artifact.payload ? { payload: artifact.payload } : {}),
37
- ...(artifact.publishedArtifactId
38
- ? { publishedArtifactId: ensureRecordId(artifact.publishedArtifactId, TABLES.ARTIFACT) }
39
- : {}),
40
- })
41
- .output('after')
42
-
43
- records.push(PlanArtifactSchema.parse(created))
44
- }
45
-
46
- return records
47
- }
48
- }
49
-
50
- export const planArtifactService = new PlanArtifactService()
@@ -1,67 +0,0 @@
1
- import type { PlanDraft } from '@lota-sdk/shared'
2
-
3
- import { isExecutableConditionExpression } from './plan-helpers'
4
-
5
- function buildImplicitLinearEdges(draft: PlanDraft) {
6
- if (draft.edges.length > 0 || draft.nodes.length <= 1) {
7
- return draft.edges
8
- }
9
-
10
- return draft.nodes
11
- .slice(0, -1)
12
- .map((node, index) => ({
13
- id: `edge_${node.id}_to_${draft.nodes[index + 1]?.id ?? index + 1}`,
14
- source: node.id,
15
- target: draft.nodes[index + 1].id,
16
- map: {},
17
- }))
18
- }
19
-
20
- class PlanBuilderService {
21
- structureDesign(draft: PlanDraft): PlanDraft {
22
- return {
23
- ...draft,
24
- executionMode: draft.executionMode ?? 'linear',
25
- edges: buildImplicitLinearEdges(draft),
26
- entryNodeIds: draft.entryNodeIds && draft.entryNodeIds.length > 0 ? draft.entryNodeIds : [draft.nodes[0].id],
27
- }
28
- }
29
-
30
- semanticCompletion(draft: PlanDraft): PlanDraft {
31
- return {
32
- ...draft,
33
- nodes: draft.nodes.map((node) => ({
34
- ...node,
35
- deliverables: [...node.deliverables],
36
- successCriteria: [...node.successCriteria],
37
- completionChecks: [...node.completionChecks],
38
- failurePolicy: [...node.failurePolicy],
39
- retryPolicy: { ...node.retryPolicy, retryOn: [...node.retryPolicy.retryOn] },
40
- toolPolicy: { allow: [...node.toolPolicy.allow], deny: [...node.toolPolicy.deny] },
41
- contextPolicy: {
42
- retrievalScopes: [...node.contextPolicy.retrievalScopes],
43
- attachmentPolicy: node.contextPolicy.attachmentPolicy,
44
- webPolicy: node.contextPolicy.webPolicy,
45
- },
46
- })),
47
- edges: draft.edges.map((edge) => {
48
- const normalizedWhen = edge.when?.trim()
49
- const { when: _when, ...edgeWithoutWhen } = edge
50
- return {
51
- ...edgeWithoutWhen,
52
- ...(normalizedWhen && isExecutableConditionExpression(normalizedWhen) ? { when: normalizedWhen } : {}),
53
- map: { ...edge.map },
54
- }
55
- }),
56
- schemas: structuredClone(draft.schemas),
57
- entryNodeIds: [...(draft.entryNodeIds ?? [])],
58
- }
59
- }
60
-
61
- prepareDraft(draft: PlanDraft): PlanDraft {
62
- const withStructure = this.structureDesign(draft)
63
- return this.semanticCompletion(withStructure)
64
- }
65
- }
66
-
67
- export const planBuilderService = new PlanBuilderService()
@@ -1,81 +0,0 @@
1
- import { PlanCheckpointSchema } from '@lota-sdk/shared'
2
- import type { PlanCheckpointRecord, PlanRunStatus } from '@lota-sdk/shared'
3
- import { RecordId } from 'surrealdb'
4
-
5
- import { serverLogger } from '../config/logger'
6
- import type { RecordIdInput } from '../db/record-id'
7
- import { ensureRecordId, recordIdToString } from '../db/record-id'
8
- import { databaseService } from '../db/service'
9
- import type { DatabaseTransaction } from '../db/service'
10
- import { TABLES } from '../db/tables'
11
- import { planWorkspaceService } from './plan-workspace.service'
12
-
13
- class PlanCheckpointService {
14
- async createCheckpoint(params: {
15
- tx: DatabaseTransaction
16
- runId: RecordIdInput
17
- sequence: number
18
- runStatus: PlanRunStatus
19
- readyNodeIds: string[]
20
- activeNodeIds: string[]
21
- artifactIds: RecordIdInput[]
22
- lastCompletedNodeIds: string[]
23
- snapshot: Record<string, unknown>
24
- includeWorkspace?: boolean
25
- }): Promise<PlanCheckpointRecord> {
26
- const snapshotData = { ...params.snapshot }
27
-
28
- if (params.includeWorkspace) {
29
- const runIdStr = recordIdToString(params.runId, TABLES.PLAN_RUN)
30
- const workspaceSnapshot = await planWorkspaceService
31
- .currentRead({ runId: runIdStr })
32
- .then((all) => {
33
- // Filter to entries at or before the checkpoint sequence
34
- const filtered: Record<string, unknown> = {}
35
- for (const [key, entry] of Object.entries(all)) {
36
- if (entry.writeSequence <= params.sequence) {
37
- filtered[key] = entry
38
- }
39
- }
40
- return filtered
41
- })
42
- .catch((error) => {
43
- serverLogger.warn`Workspace snapshot failed for run ${runIdStr}: ${error}`
44
- return {}
45
- })
46
- if (Object.keys(workspaceSnapshot).length > 0) {
47
- snapshotData.workspaceSnapshot = workspaceSnapshot
48
- }
49
- }
50
-
51
- const checkpointId = new RecordId(TABLES.PLAN_CHECKPOINT, Bun.randomUUIDv7())
52
- const created = await params.tx
53
- .create(checkpointId)
54
- .content({
55
- runId: ensureRecordId(params.runId, TABLES.PLAN_RUN),
56
- sequence: params.sequence,
57
- runStatus: params.runStatus,
58
- readyNodeIds: [...params.readyNodeIds],
59
- activeNodeIds: [...params.activeNodeIds],
60
- artifactIds: params.artifactIds.map((artifactId) => ensureRecordId(artifactId, TABLES.PLAN_ARTIFACT)),
61
- lastCompletedNodeIds: [...params.lastCompletedNodeIds],
62
- snapshot: snapshotData,
63
- })
64
- .output('after')
65
-
66
- return PlanCheckpointSchema.parse(created)
67
- }
68
-
69
- async loadLatestForRun(runId: RecordIdInput): Promise<PlanCheckpointRecord | null> {
70
- const checkpoints = await databaseService.findMany(
71
- TABLES.PLAN_CHECKPOINT,
72
- { runId: ensureRecordId(runId, TABLES.PLAN_RUN) },
73
- PlanCheckpointSchema,
74
- { orderBy: 'sequence', orderDir: 'DESC', limit: 1 },
75
- )
76
-
77
- return checkpoints.at(0) ?? null
78
- }
79
- }
80
-
81
- export const planCheckpointService = new PlanCheckpointService()