@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
@@ -0,0 +1,155 @@
1
+ import { Duration, Effect, Schedule } from 'effect'
2
+
3
+ import type { RecordIdInput, RecordIdRef } from '../../db/record-id'
4
+ import { ensureRecordId, isRecordIdInput, recordIdToString } from '../../db/record-id'
5
+ import type { SurrealDBService } from '../../db/service'
6
+ import { TABLES } from '../../db/tables'
7
+ import { BadRequestError, DatabaseError, NotFoundError } from '../../effect/errors'
8
+ import { ThreadSchema } from './thread.types'
9
+ import type { ThreadRecord } from './thread.types'
10
+
11
+ const THREAD_UNIQUE_LOOKUP_MAX_ATTEMPTS = 20
12
+ const THREAD_UNIQUE_LOOKUP_RETRY_DELAY_MS = 100
13
+
14
+ function requireThreadIdEffect(id: unknown): Effect.Effect<RecordIdInput, BadRequestError> {
15
+ return isRecordIdInput(id)
16
+ ? Effect.succeed(id)
17
+ : Effect.fail(new BadRequestError({ message: `Invalid record id for table ${TABLES.THREAD}` }))
18
+ }
19
+
20
+ type UniqueThreadLookupParams = {
21
+ type: 'default' | 'thread'
22
+ organizationId: RecordIdRef
23
+ userId: RecordIdRef
24
+ agentId?: string
25
+ threadType?: string
26
+ }
27
+
28
+ function buildExistingThreadLookupMessage(params: UniqueThreadLookupParams): string {
29
+ const scope = {
30
+ organizationId: recordIdToString(params.organizationId, TABLES.ORGANIZATION),
31
+ userId: recordIdToString(params.userId, TABLES.USER),
32
+ ...(params.agentId ? { agentId: params.agentId } : {}),
33
+ ...(params.threadType ? { threadType: params.threadType } : {}),
34
+ }
35
+ return `Thread lookup failed after duplicate ${params.type} thread create: ${JSON.stringify(scope)}`
36
+ }
37
+ type ThreadOrderKey = Extract<keyof ThreadRecord, string>
38
+
39
+ function mapThreadStoreError(message: string) {
40
+ return Effect.mapError((cause: unknown) => new DatabaseError({ message, cause }))
41
+ }
42
+
43
+ export interface ThreadRecordStore {
44
+ findById(id: unknown): Effect.Effect<ThreadRecord | null, DatabaseError | BadRequestError>
45
+ getById(id: unknown): Effect.Effect<ThreadRecord, DatabaseError | BadRequestError | NotFoundError>
46
+ findAll(
47
+ filter?: Record<string, unknown>,
48
+ options?: { limit?: number; offset?: number; orderBy?: ThreadOrderKey; orderDir?: 'ASC' | 'DESC' },
49
+ ): Effect.Effect<ThreadRecord[], DatabaseError>
50
+ create(data: Record<string, unknown>): Effect.Effect<ThreadRecord, DatabaseError>
51
+ update(id: unknown, data: Record<string, unknown>): Effect.Effect<ThreadRecord, DatabaseError | NotFoundError>
52
+ deleteById(id: unknown): Effect.Effect<void, DatabaseError | NotFoundError>
53
+ findThreadByUniqueLookup(params: UniqueThreadLookupParams): Effect.Effect<ThreadRecord | null, DatabaseError>
54
+ waitForExistingThread(params: UniqueThreadLookupParams): Effect.Effect<ThreadRecord, NotFoundError | DatabaseError>
55
+ }
56
+
57
+ export function createThreadRecordStore(deps: { db: SurrealDBService }): ThreadRecordStore {
58
+ const { db } = deps
59
+ function findById(id: unknown): Effect.Effect<ThreadRecord | null, DatabaseError | BadRequestError> {
60
+ return Effect.gen(function* () {
61
+ const threadId = yield* requireThreadIdEffect(id)
62
+ return yield* db
63
+ .findOne(TABLES.THREAD, { id: ensureRecordId(threadId, TABLES.THREAD) }, ThreadSchema)
64
+ .pipe(mapThreadStoreError('Failed to load thread record.'))
65
+ })
66
+ }
67
+
68
+ function getById(id: unknown): Effect.Effect<ThreadRecord, DatabaseError | BadRequestError | NotFoundError> {
69
+ return Effect.gen(function* () {
70
+ const record = yield* findById(id)
71
+ if (!record) {
72
+ return yield* new NotFoundError({ resource: TABLES.THREAD, message: `${TABLES.THREAD} record not found` })
73
+ }
74
+ return record
75
+ })
76
+ }
77
+
78
+ function findAll(
79
+ filter: Record<string, unknown> = {},
80
+ options?: { limit?: number; offset?: number; orderBy?: ThreadOrderKey; orderDir?: 'ASC' | 'DESC' },
81
+ ): Effect.Effect<ThreadRecord[], DatabaseError> {
82
+ return db
83
+ .findMany(TABLES.THREAD, filter, ThreadSchema, options)
84
+ .pipe(mapThreadStoreError('Failed to list threads.'))
85
+ }
86
+
87
+ function create(data: Record<string, unknown>): Effect.Effect<ThreadRecord, DatabaseError> {
88
+ return db.create(TABLES.THREAD, data, ThreadSchema).pipe(mapThreadStoreError('Failed to create thread record.'))
89
+ }
90
+
91
+ function update(
92
+ id: unknown,
93
+ data: Record<string, unknown>,
94
+ ): Effect.Effect<ThreadRecord, DatabaseError | NotFoundError> {
95
+ return Effect.gen(function* () {
96
+ const record = yield* db
97
+ .update(TABLES.THREAD, id, data, ThreadSchema)
98
+ .pipe(mapThreadStoreError('Failed to update thread record.'))
99
+ if (!record) {
100
+ return yield* new NotFoundError({ resource: TABLES.THREAD, message: `${TABLES.THREAD} record not found` })
101
+ }
102
+ return record
103
+ })
104
+ }
105
+
106
+ function deleteById(id: unknown): Effect.Effect<void, DatabaseError | NotFoundError> {
107
+ return Effect.gen(function* () {
108
+ const deleted = yield* db
109
+ .deleteById(TABLES.THREAD, id)
110
+ .pipe(mapThreadStoreError('Failed to delete thread record.'))
111
+ if (!deleted) {
112
+ return yield* new NotFoundError({ resource: TABLES.THREAD, message: `${TABLES.THREAD} record not found` })
113
+ }
114
+ })
115
+ }
116
+
117
+ function findThreadByUniqueLookup(
118
+ params: UniqueThreadLookupParams,
119
+ ): Effect.Effect<ThreadRecord | null, DatabaseError> {
120
+ return db
121
+ .findOne(
122
+ TABLES.THREAD,
123
+ {
124
+ type: params.type,
125
+ organizationId: params.organizationId,
126
+ userId: params.userId,
127
+ ...(params.agentId ? { agentId: params.agentId } : {}),
128
+ ...(params.threadType ? { threadType: params.threadType } : {}),
129
+ },
130
+ ThreadSchema,
131
+ )
132
+ .pipe(mapThreadStoreError('Failed to find thread by unique lookup.'))
133
+ }
134
+
135
+ function waitForExistingThread(
136
+ params: UniqueThreadLookupParams,
137
+ ): Effect.Effect<ThreadRecord, NotFoundError | DatabaseError> {
138
+ const poll = findThreadByUniqueLookup(params).pipe(
139
+ Effect.flatMap((result) => (result ? Effect.succeed(result) : Effect.fail('not_found' as const))),
140
+ )
141
+
142
+ return Effect.retry(poll, {
143
+ times: THREAD_UNIQUE_LOOKUP_MAX_ATTEMPTS - 1,
144
+ schedule: Schedule.fixed(Duration.millis(THREAD_UNIQUE_LOOKUP_RETRY_DELAY_MS)),
145
+ }).pipe(
146
+ Effect.mapError((error) =>
147
+ error === 'not_found'
148
+ ? new NotFoundError({ resource: TABLES.THREAD, message: buildExistingThreadLookupMessage(params) })
149
+ : error,
150
+ ),
151
+ )
152
+ }
153
+
154
+ return { findById, getById, findAll, create, update, deleteById, findThreadByUniqueLookup, waitForExistingThread }
155
+ }
@@ -0,0 +1,74 @@
1
+ import { THREAD } from '@lota-sdk/shared'
2
+ import { Context, Schema, Effect, Layer } from 'effect'
3
+
4
+ import { chatLogger } from '../../config/logger'
5
+ import type { RecordIdRef } from '../../db/record-id'
6
+ import type { HelperModelRuntime } from '../../runtime/helper-model'
7
+ import { HelperModelTag } from '../../runtime/helper-model'
8
+ import { deriveTitle, limitTitleWords, normalizeTitle } from '../../runtime/title-helpers'
9
+ import {
10
+ createThreadTitleGeneratorAgent,
11
+ THREAD_TITLE_GENERATOR_PROMPT,
12
+ } from '../../system-agents/title-generator.agent'
13
+ import type { makeThreadService } from './thread.service'
14
+ import { ThreadServiceTag } from './thread.service'
15
+
16
+ const THREAD_TITLE_TIMEOUT_MS = 30_000
17
+
18
+ class ThreadTitleError extends Schema.TaggedErrorClass<ThreadTitleError>()('ThreadTitleError', {
19
+ message: Schema.String,
20
+ cause: Schema.optional(Schema.Defect),
21
+ }) {}
22
+
23
+ export function makeThreadTitleService(
24
+ threadService: ReturnType<typeof makeThreadService>,
25
+ helperModelRuntime: HelperModelRuntime,
26
+ ) {
27
+ return {
28
+ generateAndPersistTitle(threadId: RecordIdRef, sourceText: string) {
29
+ return Effect.gen(function* () {
30
+ const generatedTitle = yield* Effect.tryPromise({
31
+ try: () =>
32
+ helperModelRuntime.generateHelperText({
33
+ tag: 'thread-title',
34
+ createAgent: createThreadTitleGeneratorAgent,
35
+ defaultSystemPrompt: THREAD_TITLE_GENERATOR_PROMPT,
36
+ timeoutMs: THREAD_TITLE_TIMEOUT_MS,
37
+ messages: [{ role: 'user', content: sourceText }],
38
+ }),
39
+ catch: (error) => new ThreadTitleError({ message: 'Failed to generate thread title.', cause: error }),
40
+ }).pipe(
41
+ Effect.map((title) => normalizeTitle(title)),
42
+ Effect.catch((error) =>
43
+ Effect.sync(() => {
44
+ chatLogger.warn`Failed to generate thread title via LLM (non-fatal): ${error}`
45
+ return ''
46
+ }),
47
+ ),
48
+ )
49
+ const title = generatedTitle || limitTitleWords(deriveTitle(sourceText || THREAD.DEFAULT_TITLE))
50
+ yield* threadService
51
+ .update(threadId, { title, nameGenerated: true })
52
+ .pipe(
53
+ Effect.mapError(
54
+ (error) => new ThreadTitleError({ message: 'Failed to persist generated thread title.', cause: error }),
55
+ ),
56
+ )
57
+ })
58
+ },
59
+ }
60
+ }
61
+
62
+ export class ThreadTitleServiceTag extends Context.Service<
63
+ ThreadTitleServiceTag,
64
+ ReturnType<typeof makeThreadTitleService>
65
+ >()('ThreadTitleService') {}
66
+
67
+ export const ThreadTitleServiceLive = Layer.effect(
68
+ ThreadTitleServiceTag,
69
+ Effect.gen(function* () {
70
+ const threadService = yield* ThreadServiceTag
71
+ const helperModelRuntime = yield* HelperModelTag
72
+ return makeThreadTitleService(threadService, helperModelRuntime)
73
+ }),
74
+ )
@@ -0,0 +1,280 @@
1
+ import type { ChatMessage, MessageMetadata, PlanNodeSpecRecord } from '@lota-sdk/shared'
2
+ import { SUBMIT_PLAN_TURN_RESULT_TOOL_NAME, toTimestamp, withMessageCreatedAt } from '@lota-sdk/shared'
3
+ import type { ToolSet, UIMessageStreamWriter } from 'ai'
4
+ import { Schema, Effect } from 'effect'
5
+
6
+ import { aiLogger } from '../../config/logger'
7
+ import type { RecordIdRef } from '../../db/record-id'
8
+ import { effectTryMaybeAsync, effectTryPromise as effectTryPromiseShared } from '../../effect/helpers'
9
+ import { runPromise } from '../../effect/runtime'
10
+ import type { readRuntimeAgentIdentityOverrides } from '../../runtime/agent-identity-overrides'
11
+ import { resolveRuntimeAgentDisplayName } from '../../runtime/agent-identity-overrides'
12
+ import { OWNERSHIP_DISPATCH_BLOCKED_TOOL_NAMES } from '../../runtime/agent-runtime-policy'
13
+ import { collectCompletedConsultTeamMessages, readOptionalString } from '../../runtime/thread-chat-helpers'
14
+ import { nowEpochMillis } from '../../utils/date-time'
15
+ import type { makeThreadMessageService } from './thread-message.service'
16
+ import type { ThreadTurnPreparationError } from './thread-turn-preparation.service'
17
+ import { streamAgentResponse } from './thread-turn-streaming'
18
+ import type { StreamAgentResponseContext } from './thread-turn-streaming'
19
+ import { buildThreadTurnSpanAttributes, compactSpanAttributes } from './thread-turn-tracing'
20
+
21
+ type ChatStreamChunk = Parameters<UIMessageStreamWriter<ChatMessage>['write']>[0]
22
+
23
+ export function writeMultiAgentEvent(
24
+ writer: UIMessageStreamWriter<ChatMessage> | undefined,
25
+ event: {
26
+ phase: 'routing' | 'waiting-for-agent' | 'agent-message-persisted' | 'complete'
27
+ agentId?: string
28
+ agentName?: string
29
+ messageId?: string
30
+ note?: string
31
+ },
32
+ ): void {
33
+ if (!writer) return
34
+
35
+ const chunk: ChatStreamChunk = {
36
+ type: 'data-multi-agent-event',
37
+ id: `multi-agent-${Bun.randomUUIDv7()}`,
38
+ data: event,
39
+ transient: true,
40
+ }
41
+ writer.write(chunk)
42
+ }
43
+
44
+ export function applyPlanTurnToolPolicy(tools: ToolSet, nodeSpec: PlanNodeSpecRecord): ToolSet {
45
+ const blockedToolNames = new Set([...OWNERSHIP_DISPATCH_BLOCKED_TOOL_NAMES, ...nodeSpec.toolPolicy.deny])
46
+ const allowList = nodeSpec.toolPolicy.allow.length > 0 ? new Set(nodeSpec.toolPolicy.allow) : null
47
+
48
+ return Object.fromEntries(
49
+ Object.entries(tools).filter(
50
+ ([toolName]) =>
51
+ !blockedToolNames.has(toolName) &&
52
+ (toolName === SUBMIT_PLAN_TURN_RESULT_TOOL_NAME || allowList === null || allowList.has(toolName)),
53
+ ),
54
+ )
55
+ }
56
+
57
+ interface VisibleAgentRunParams {
58
+ agentId: string
59
+ mode: 'direct' | 'fixedThreadMode' | 'threadMode'
60
+ skills?: string[]
61
+ additionalInstructionSections?: string[]
62
+ extraMessages?: ChatMessage[]
63
+ extraTools?: ToolSet
64
+ filterTools?: (tools: ToolSet) => ToolSet
65
+ includeExecutionPlanTools?: boolean
66
+ metadataPatch?: NonNullable<MessageMetadata>
67
+ }
68
+
69
+ interface VisibleAgentRunnerDeps<TBuildTurnToolParams> {
70
+ agentFactoryConfig: { buildAgentTools: (params: TBuildTurnToolParams) => Promise<ToolSet> | ToolSet }
71
+ buildTurnToolParams: (params: {
72
+ agentId: string
73
+ mode: 'direct' | 'fixedThreadMode' | 'threadMode'
74
+ memoryBlock: string
75
+ onAppendMemoryBlock: (value: string) => void
76
+ extraMessages?: ChatMessage[]
77
+ skills?: string[]
78
+ includeExecutionPlanTools: boolean
79
+ }) => TBuildTurnToolParams
80
+ threadTurnMessageContext: {
81
+ buildRunInputMessages: (extraMessages?: ChatMessage[]) => ChatMessage[]
82
+ appendMessages: (messages: ChatMessage[]) => void
83
+ }
84
+ threadMessageService: Pick<ReturnType<typeof makeThreadMessageService>, 'upsertMessagesEffect'>
85
+ threadRef: RecordIdRef
86
+ writer?: UIMessageStreamWriter<ChatMessage>
87
+ streamCtx: StreamAgentResponseContext
88
+ agentIdentityOverrides: ReturnType<typeof readRuntimeAgentIdentityOverrides>
89
+ getMemoryBlock: () => string
90
+ setMemoryBlock: (value: string) => void
91
+ onPersistedMessage: (message: ChatMessage) => void
92
+ failIfRunAborted: () => Effect.Effect<void, ThreadTurnPreparationError>
93
+ }
94
+
95
+ export function createThreadTurnVisibleAgentRunner<TBuildTurnToolParams>(
96
+ deps: VisibleAgentRunnerDeps<TBuildTurnToolParams>,
97
+ ) {
98
+ class ThreadTurnExecutionError extends Schema.TaggedErrorClass<ThreadTurnExecutionError>()(
99
+ 'ThreadTurnExecutionError',
100
+ { message: Schema.String, cause: Schema.optional(Schema.Defect) },
101
+ ) {}
102
+
103
+ const effectTryPromise = <A>(
104
+ evaluate: () => PromiseLike<A> | Effect.Effect<A, unknown>,
105
+ message: string,
106
+ ): Effect.Effect<A, ThreadTurnExecutionError> =>
107
+ effectTryPromiseShared(evaluate, (error) => new ThreadTurnExecutionError({ message, cause: error }))
108
+
109
+ function effectFromMaybePromise<A>(
110
+ evaluate: () => A | PromiseLike<A>,
111
+ message: string,
112
+ ): Effect.Effect<A, ThreadTurnExecutionError> {
113
+ return effectTryMaybeAsync(evaluate, (error) => new ThreadTurnExecutionError({ message, cause: error }))
114
+ }
115
+
116
+ const buildAgentMetadataPatch = (agentId: string, agentName: string): NonNullable<MessageMetadata> => ({
117
+ agentId,
118
+ agentName,
119
+ semanticTerminationReason: 'none',
120
+ })
121
+
122
+ const failIfRunAborted = () =>
123
+ deps
124
+ .failIfRunAborted()
125
+ .pipe(
126
+ Effect.mapError(
127
+ (error) =>
128
+ new ThreadTurnExecutionError({ message: 'Thread turn was aborted during agent execution.', cause: error }),
129
+ ),
130
+ )
131
+
132
+ const createObserver = (agentId: string) => ({
133
+ run: <T>(fn: () => T | Promise<T>) =>
134
+ runPromise(
135
+ effectTryMaybeAsync(
136
+ fn,
137
+ (error) =>
138
+ new ThreadTurnExecutionError({ message: `Agent observer run failed (agent=${agentId}).`, cause: error }),
139
+ ),
140
+ ),
141
+ recordError: (error: unknown) => {
142
+ aiLogger.error`Agent run failed (agent=${agentId}): ${error}`
143
+ },
144
+ recordAbort: (error: unknown) => {
145
+ aiLogger.info`Agent run aborted (agent=${agentId}): ${error instanceof Error ? error.message : String(error)}`
146
+ },
147
+ })
148
+
149
+ const commitAssistantResponse = (
150
+ response: ChatMessage,
151
+ agentId: string,
152
+ agentName: string,
153
+ metadataPatch?: NonNullable<MessageMetadata>,
154
+ ): Promise<ChatMessage> =>
155
+ runPromise(
156
+ Effect.gen(function* () {
157
+ yield* failIfRunAborted()
158
+
159
+ const toCommittedAssistantMessage = (
160
+ message: ChatMessage,
161
+ resolvedAgentId: string,
162
+ resolvedAgentName: string,
163
+ patch?: NonNullable<MessageMetadata>,
164
+ ) =>
165
+ withMessageCreatedAt(
166
+ {
167
+ ...message,
168
+ metadata: {
169
+ ...message.metadata,
170
+ ...buildAgentMetadataPatch(resolvedAgentId, resolvedAgentName),
171
+ ...patch,
172
+ },
173
+ },
174
+ toTimestamp(message.metadata?.createdAt) ?? nowEpochMillis(),
175
+ )
176
+
177
+ const committedConsultMessages = collectCompletedConsultTeamMessages({ responseMessage: response }).flatMap(
178
+ (consultMessage) => {
179
+ const consultAgentId = readOptionalString(consultMessage.metadata?.agentId)
180
+ const consultAgentName = readOptionalString(consultMessage.metadata?.agentName)
181
+ if (!consultAgentId || !consultAgentName) {
182
+ return []
183
+ }
184
+
185
+ return [toCommittedAssistantMessage(consultMessage, consultAgentId, consultAgentName)]
186
+ },
187
+ )
188
+
189
+ const committed = toCommittedAssistantMessage(response, agentId, agentName, metadataPatch)
190
+ const messagesToPersist = [...committedConsultMessages, committed]
191
+ yield* deps.threadMessageService.upsertMessagesEffect({ threadId: deps.threadRef, messages: messagesToPersist })
192
+
193
+ for (const persistedMessage of messagesToPersist) {
194
+ deps.threadTurnMessageContext.appendMessages([persistedMessage])
195
+ deps.onPersistedMessage(persistedMessage)
196
+ }
197
+ yield* failIfRunAborted()
198
+
199
+ return committed
200
+ }),
201
+ )
202
+
203
+ const runVisibleAgentEffect = Effect.fn('ThreadTurnExecution.runVisibleAgent')(function* (
204
+ runParams: VisibleAgentRunParams,
205
+ ) {
206
+ let runMemoryBlock = deps.getMemoryBlock()
207
+ const includeExecutionPlanTools = runParams.includeExecutionPlanTools ?? runParams.mode !== 'fixedThreadMode'
208
+ const builtTools = yield* effectFromMaybePromise(
209
+ () =>
210
+ deps.agentFactoryConfig.buildAgentTools(
211
+ deps.buildTurnToolParams({
212
+ agentId: runParams.agentId,
213
+ mode: runParams.mode,
214
+ skills: runParams.skills,
215
+ memoryBlock: runMemoryBlock,
216
+ onAppendMemoryBlock: (value: string) => {
217
+ runMemoryBlock = value
218
+ },
219
+ extraMessages: runParams.extraMessages,
220
+ includeExecutionPlanTools,
221
+ }),
222
+ ),
223
+ `Failed to build tools for ${runParams.agentId}.`,
224
+ ).pipe(Effect.withSpan('ThreadTurnExecution.buildAgentTools'))
225
+ const rawTools = { ...builtTools, ...runParams.extraTools }
226
+ const tools = runParams.filterTools ? runParams.filterTools(rawTools) : rawTools
227
+ deps.streamCtx.memoryBlock = runMemoryBlock
228
+ yield* failIfRunAborted()
229
+ const responseMessage = yield* effectTryPromise(
230
+ () =>
231
+ streamAgentResponse(deps.streamCtx, {
232
+ agentId: runParams.agentId,
233
+ mode: runParams.mode,
234
+ messages: deps.threadTurnMessageContext.buildRunInputMessages(runParams.extraMessages),
235
+ tools,
236
+ observer: createObserver(runParams.agentId),
237
+ skills: runParams.skills,
238
+ additionalInstructionSections: runParams.additionalInstructionSections,
239
+ includeExecutionPlanTools,
240
+ writer: deps.writer,
241
+ }),
242
+ `Failed to stream response for ${runParams.agentId}.`,
243
+ ).pipe(Effect.withSpan('ThreadTurnExecution.streamVisibleAgent'))
244
+ deps.setMemoryBlock(runMemoryBlock)
245
+
246
+ return yield* effectTryPromise(
247
+ () =>
248
+ commitAssistantResponse(
249
+ responseMessage,
250
+ runParams.agentId,
251
+ resolveRuntimeAgentDisplayName(deps.agentIdentityOverrides, runParams.agentId),
252
+ runParams.metadataPatch,
253
+ ),
254
+ `Failed to commit assistant response for ${runParams.agentId}.`,
255
+ ).pipe(Effect.withSpan('ThreadTurnExecution.commitAssistantResponse'))
256
+ })
257
+
258
+ const runVisibleAgent = (runParams: VisibleAgentRunParams): Promise<ChatMessage> =>
259
+ runPromise(
260
+ runVisibleAgentEffect(runParams).pipe(
261
+ Effect.annotateSpans(
262
+ compactSpanAttributes({
263
+ ...buildThreadTurnSpanAttributes({
264
+ threadRef: deps.threadRef,
265
+ orgRef: deps.streamCtx.orgRef,
266
+ userRef: deps.streamCtx.userRef,
267
+ agentId: runParams.agentId,
268
+ threadType: deps.streamCtx.thread.type,
269
+ mode: runParams.mode,
270
+ }),
271
+ includeExecutionPlanTools: runParams.includeExecutionPlanTools ?? runParams.mode !== 'fixedThreadMode',
272
+ extraMessageCount: runParams.extraMessages?.length ?? 0,
273
+ extraToolCount: runParams.extraTools ? Object.keys(runParams.extraTools).length : 0,
274
+ }),
275
+ ),
276
+ ),
277
+ )
278
+
279
+ return { runVisibleAgent }
280
+ }
@@ -0,0 +1,73 @@
1
+ import type { ChatMessage } from '@lota-sdk/shared'
2
+
3
+ import type { RecordIdRef } from '../../db/record-id'
4
+ import {
5
+ buildReadableUploadMetadataText,
6
+ buildModelInputMessagesWithUploadMetadata,
7
+ } from '../../runtime/chat-attachments'
8
+ import type { ReadableUploadMetadataLike } from '../../runtime/chat-types'
9
+ import type { ContextCompactionRuntime } from '../../runtime/context-compaction/context-compaction'
10
+
11
+ export function upsertChatHistoryMessage(messages: ChatMessage[], nextMessage: ChatMessage): ChatMessage[] {
12
+ const existingIndex = messages.findIndex((message) => message.id === nextMessage.id)
13
+ if (existingIndex === -1) {
14
+ return [...messages, nextMessage]
15
+ }
16
+
17
+ const nextMessages = [...messages]
18
+ nextMessages[existingIndex] = nextMessage
19
+ return nextMessages
20
+ }
21
+
22
+ interface ThreadTurnMessageContext {
23
+ readonly currentMessages: ChatMessage[]
24
+ appendMessages(messages: ChatMessage[]): void
25
+ listReadableUploads(extraMessages?: ChatMessage[]): ReadableUploadMetadataLike[]
26
+ buildRunInputMessages(extraMessages?: ChatMessage[]): ChatMessage[]
27
+ }
28
+
29
+ export function createThreadTurnMessageContext(params: {
30
+ contextCompactionRuntime: Pick<ContextCompactionRuntime, 'prependSummaryMessage'>
31
+ persistedCompactionSummary: string
32
+ messagesForContext: ChatMessage[]
33
+ orgRef: RecordIdRef
34
+ userRef: RecordIdRef
35
+ latestUserMessageId: string
36
+ listReadableUploadsFromMessages: (params: {
37
+ messages: ChatMessage[]
38
+ orgId: RecordIdRef
39
+ userId: RecordIdRef
40
+ }) => ReadableUploadMetadataLike[]
41
+ }): ThreadTurnMessageContext {
42
+ let currentMessages = params.contextCompactionRuntime.prependSummaryMessage(
43
+ params.persistedCompactionSummary,
44
+ params.messagesForContext,
45
+ )
46
+
47
+ const listReadableUploads = (extraMessages: ChatMessage[] = []) =>
48
+ params.listReadableUploadsFromMessages({
49
+ messages: [...currentMessages, ...extraMessages],
50
+ orgId: params.orgRef,
51
+ userId: params.userRef,
52
+ })
53
+
54
+ const buildRunInputMessages = (extraMessages: ChatMessage[] = []): ChatMessage[] =>
55
+ buildModelInputMessagesWithUploadMetadata({
56
+ messages: [...currentMessages, ...extraMessages],
57
+ latestUserMessageId: params.latestUserMessageId,
58
+ uploadMetadataText: buildReadableUploadMetadataText(listReadableUploads(extraMessages)),
59
+ })
60
+
61
+ return {
62
+ get currentMessages() {
63
+ return currentMessages
64
+ },
65
+ appendMessages(messages: ChatMessage[]) {
66
+ for (const message of messages) {
67
+ currentMessages = upsertChatHistoryMessage(currentMessages, message)
68
+ }
69
+ },
70
+ listReadableUploads,
71
+ buildRunInputMessages,
72
+ }
73
+ }