@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
@@ -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
+ }