@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
@@ -34,9 +34,9 @@ export function parseRepomixFileSections(repomixOutput: string): FileSection[] {
34
34
  return sections
35
35
  }
36
36
 
37
- export async function chunkRepomixFileSections(
37
+ export function chunkRepomixFileSections(
38
38
  repomixOutput: string,
39
39
  options: FileSectionChunkOptions = {},
40
- ): Promise<FileSectionChunk[]> {
40
+ ): FileSectionChunk[] {
41
41
  return chunkFileSections(parseRepomixFileSections(repomixOutput), options)
42
42
  }
@@ -1,93 +1,152 @@
1
- import { requireTimestamp } from '@lota-sdk/shared'
1
+ import { parseRowMetadata, requireTimestamp } from '@lota-sdk/shared'
2
+ import type { ChatMessage } from '@lota-sdk/shared'
3
+ import { Schema, Effect } from 'effect'
2
4
  import { BoundQuery } from 'surrealdb'
3
5
  import { z } from 'zod'
4
6
 
5
7
  import { ensureRecordId, recordIdToString } from '../../db/record-id'
6
8
  import type { RecordIdRef } from '../../db/record-id'
7
- import { databaseService } from '../../db/service'
9
+ import type { SurrealDBService } from '../../db/service'
8
10
  import { TABLES } from '../../db/tables'
9
11
  import { ThreadMessageRowSchema } from '../../db/thread-message-row'
10
12
  import type { ThreadMessageRow } from '../../db/thread-message-row'
11
13
  import { normalizeTextBody } from '../../document/parsing'
14
+ import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
12
15
  import type { LotaRuntimeBackgroundCursor } from '../../runtime/runtime-extensions'
16
+ import type { SocialChatHistoryMessage } from '../../services/social-chat-history.service'
17
+ import { unsafeDateFrom } from '../../utils/date-time'
13
18
 
14
- export type DigestCursor = LotaRuntimeBackgroundCursor
15
-
16
- export interface DigestMessage {
17
- source: 'thread' | 'social'
19
+ export interface ThreadDigestMessage {
20
+ source: 'thread'
18
21
  sourceId: string
19
22
  role: 'system' | 'user' | 'assistant'
20
- parts: Array<Record<string, unknown>>
21
- metadata?: Record<string, unknown>
22
- cursor: DigestCursor
23
+ parts: ChatMessage['parts']
24
+ metadata?: ChatMessage['metadata']
25
+ cursor: LotaRuntimeBackgroundCursor
23
26
  }
24
27
 
25
- function mapThreadRow(row: ThreadMessageRow): DigestMessage {
28
+ export type SocialDigestMessage = Pick<
29
+ SocialChatHistoryMessage,
30
+ 'source' | 'sourceId' | 'role' | 'parts' | 'metadata' | 'cursor'
31
+ >
32
+ export type DigestMessage = ThreadDigestMessage | SocialDigestMessage
33
+
34
+ function normalizeMessageValue(value: unknown): unknown {
35
+ if (value instanceof Date) {
36
+ return value.toISOString()
37
+ }
38
+
39
+ if (Array.isArray(value)) {
40
+ return value.map((item) => normalizeMessageValue(item))
41
+ }
42
+
43
+ if (value && typeof value === 'object') {
44
+ return Object.fromEntries(
45
+ Object.entries(value as Record<string, unknown>).map(([key, entry]) => [key, normalizeMessageValue(entry)]),
46
+ )
47
+ }
48
+
49
+ return value
50
+ }
51
+
52
+ function readPersistedMessageParts(parts: ThreadMessageRow['parts']): ChatMessage['parts'] {
53
+ return (Array.isArray(parts) ? normalizeMessageValue(parts) : []) as ChatMessage['parts']
54
+ }
55
+
56
+ class ThreadMessageQueryError extends Schema.TaggedErrorClass<ThreadMessageQueryError>()('ThreadMessageQueryError', {
57
+ message: Schema.String,
58
+ cause: Schema.Defect,
59
+ }) {}
60
+
61
+ function mapThreadRow(row: ThreadMessageRow): ThreadDigestMessage {
26
62
  return {
27
63
  source: 'thread',
28
64
  sourceId: recordIdToString(row.threadId, TABLES.THREAD),
29
65
  role: row.role,
30
- parts: row.parts as Array<Record<string, unknown>>,
31
- metadata: row.metadata ?? undefined,
66
+ parts: readPersistedMessageParts(row.parts),
67
+ metadata: parseRowMetadata(row.metadata),
32
68
  cursor: {
33
- createdAt: new Date(requireTimestamp(row.createdAt)),
69
+ createdAt: unsafeDateFrom(requireTimestamp(row.createdAt)),
34
70
  id: recordIdToString(row.id, TABLES.THREAD_MESSAGE),
35
71
  },
36
72
  }
37
73
  }
38
74
 
75
+ const effectTryPromise = makeEffectTryPromiseWithMessage(
76
+ (message, cause) => new ThreadMessageQueryError({ message, cause }),
77
+ )
78
+
39
79
  export function compareDigestMessageOrder(left: DigestMessage, right: DigestMessage): number {
40
80
  const timeDiff = left.cursor.createdAt.getTime() - right.cursor.createdAt.getTime()
41
81
  if (timeDiff !== 0) return timeDiff
42
82
  return left.cursor.id.localeCompare(right.cursor.id)
43
83
  }
44
84
 
45
- export async function listThreadIdsForOrg(orgRef: RecordIdRef): Promise<RecordIdRef[]> {
85
+ export function listThreadIdsForOrg(db: SurrealDBService, orgRef: RecordIdRef): Promise<RecordIdRef[]> {
46
86
  const EntityIdRowSchema = z.string().trim().min(1)
47
- const ids = await databaseService.query<unknown>(
48
- new BoundQuery(
49
- `SELECT VALUE type::string(id) FROM ${TABLES.THREAD}
87
+ return Effect.runPromise(
88
+ effectTryPromise(
89
+ () =>
90
+ db.query<unknown>(
91
+ new BoundQuery(
92
+ `SELECT VALUE type::string(id) FROM ${TABLES.THREAD}
50
93
  WHERE organizationId = $organizationId`,
51
- { organizationId: orgRef },
94
+ { organizationId: orgRef },
95
+ ),
96
+ ),
97
+ 'Failed to list thread ids for org digest.',
98
+ ).pipe(
99
+ Effect.map((ids) => ids.map((value: unknown) => ensureRecordId(EntityIdRowSchema.parse(value), TABLES.THREAD))),
52
100
  ),
53
101
  )
54
- return ids.map((value) => ensureRecordId(EntityIdRowSchema.parse(value), TABLES.THREAD))
55
102
  }
56
103
 
57
- export async function listEligibleThreadMessages(params: {
104
+ export function listEligibleThreadMessages(params: {
105
+ db: SurrealDBService
58
106
  threadIds: RecordIdRef[]
59
- cursor: DigestCursor | null
107
+ cursor: LotaRuntimeBackgroundCursor | null
60
108
  onboardingCutoff: Date | null
61
109
  }): Promise<DigestMessage[]> {
62
- if (params.threadIds.length === 0) return []
110
+ if (params.threadIds.length === 0) return Promise.resolve([])
63
111
 
64
- let query: BoundQuery | null = null
65
- if (params.cursor) {
66
- const cursorRowId = ensureRecordId(params.cursor.id, TABLES.THREAD_MESSAGE)
67
- query = new BoundQuery(
68
- `SELECT type::string(id) AS id, type::string(threadId) AS threadId, role, parts, metadata, createdAt FROM ${TABLES.THREAD_MESSAGE}
112
+ return Effect.runPromise(
113
+ Effect.gen(function* () {
114
+ const query = params.cursor
115
+ ? new BoundQuery(
116
+ `SELECT type::string(id) AS id, type::string(threadId) AS threadId, role, parts, metadata, createdAt FROM ${TABLES.THREAD_MESSAGE}
69
117
  WHERE threadId IN $threadIds
70
118
  AND (
71
119
  createdAt > $cursorCreatedAt
72
120
  OR (createdAt = $cursorCreatedAt AND id > $cursorRowId)
73
121
  )
74
122
  ORDER BY createdAt ASC, id ASC`,
75
- { threadIds: params.threadIds, cursorCreatedAt: params.cursor.createdAt, cursorRowId },
76
- )
77
- } else if (params.onboardingCutoff) {
78
- query = new BoundQuery(
79
- `SELECT type::string(id) AS id, type::string(threadId) AS threadId, role, parts, metadata, createdAt FROM ${TABLES.THREAD_MESSAGE}
123
+ {
124
+ threadIds: params.threadIds,
125
+ cursorCreatedAt: params.cursor.createdAt,
126
+ cursorRowId: ensureRecordId(params.cursor.id, TABLES.THREAD_MESSAGE),
127
+ },
128
+ )
129
+ : params.onboardingCutoff
130
+ ? new BoundQuery(
131
+ `SELECT type::string(id) AS id, type::string(threadId) AS threadId, role, parts, metadata, createdAt FROM ${TABLES.THREAD_MESSAGE}
80
132
  WHERE threadId IN $threadIds
81
133
  AND createdAt > $onboardingCutoff
82
134
  ORDER BY createdAt ASC, id ASC`,
83
- { threadIds: params.threadIds, onboardingCutoff: params.onboardingCutoff },
84
- )
85
- }
135
+ { threadIds: params.threadIds, onboardingCutoff: params.onboardingCutoff },
136
+ )
137
+ : null
86
138
 
87
- if (!query) return []
139
+ if (!query) {
140
+ return []
141
+ }
88
142
 
89
- const rows = await databaseService.query<unknown>(query)
90
- return rows.map((row) => mapThreadRow(ThreadMessageRowSchema.parse(row)))
143
+ const rows = yield* effectTryPromise(
144
+ () => params.db.query<unknown>(query),
145
+ 'Failed to list eligible thread messages.',
146
+ )
147
+ return rows.map((row: unknown) => mapThreadRow(ThreadMessageRowSchema.parse(row)))
148
+ }),
149
+ )
91
150
  }
92
151
 
93
152
  export function normalizeBlock(value: string): string {
@@ -1,10 +1,17 @@
1
- import path from 'node:path'
2
1
  import { fileURLToPath } from 'node:url'
3
2
 
4
3
  import type { Job, Worker } from 'bullmq'
4
+ import { Effect } from 'effect'
5
+ import type { Context } from 'effect'
5
6
 
6
7
  import { chatLogger } from '../config/logger'
7
- import { queueJobService } from '../services/queue-job.service'
8
+ import { getCurrentRuntime } from '../effect/runtime-ref'
9
+ import { QueueJobServiceTag } from '../services/queue-job.service'
10
+
11
+ export function getQueueJobService(): Context.Service.Shape<typeof QueueJobServiceTag> {
12
+ return getCurrentRuntime().runSync(Effect.service(QueueJobServiceTag))
13
+ }
14
+
8
15
  export const DEFAULT_JOB_RETENTION = { removeOnComplete: true, removeOnFail: { age: 24 * 60 * 60, count: 200 } }
9
16
  export const LOW_JOB_RETENTION = { removeOnComplete: true, removeOnFail: { age: 6 * 60 * 60, count: 50 } }
10
17
  export const LONG_JOB_LOCK_DURATION_MS = 600_000
@@ -12,7 +19,7 @@ export const LONG_JOB_LOCK_DURATION_MS = 600_000
12
19
  const DEFAULT_SHUTDOWN_TIMEOUT_MS = 10_000
13
20
 
14
21
  export function getWorkerPath(workerName: string): string {
15
- return fileURLToPath(new URL(path.join('.', workerName), import.meta.url))
22
+ return fileURLToPath(new URL(workerName, import.meta.url))
16
23
  }
17
24
 
18
25
  export interface WorkerHandle {
@@ -72,9 +79,9 @@ export const attachWorkerEvents = (worker: Worker, name: string, logger: typeof
72
79
  }
73
80
 
74
81
  export const createWorkerShutdown = (worker: Worker, name: string, logger: typeof chatLogger = chatLogger) => {
75
- return async () => {
82
+ return () => {
76
83
  logger.info`Shutting down ${name} worker`
77
- await worker.close()
84
+ return Effect.runPromise(Effect.asVoid(Effect.tryPromise(() => worker.close())))
78
85
  }
79
86
  }
80
87
 
@@ -82,7 +89,7 @@ export function createTracedWorkerProcessor<TJob extends TracedWorkerJobLike, TR
82
89
  queueName: string,
83
90
  processor: (job: TJob) => Promise<TResult>,
84
91
  ): (job: TJob) => Promise<TResult> {
85
- return async (job: TJob) => {
92
+ return (job: TJob) => {
86
93
  const trackedJob = {
87
94
  queueName,
88
95
  id: typeof job.id === 'string' || typeof job.id === 'number' ? job.id : undefined,
@@ -93,31 +100,44 @@ export function createTracedWorkerProcessor<TJob extends TracedWorkerJobLike, TR
93
100
  timestamp: job.timestamp,
94
101
  }
95
102
 
96
- try {
97
- await queueJobService.markAttemptStarted(trackedJob)
98
- } catch (error) {
99
- chatLogger.error`Failed to persist queue job start (queue=${queueName}, job=${job.id}): ${error}`
103
+ const logPersistenceError = (phase: string, error: unknown) => {
104
+ chatLogger.error`Failed to persist queue job ${phase} (queue=${queueName}, job=${job.id}): ${error}`
100
105
  }
101
106
 
102
- try {
103
- const result = await processor(job)
104
- try {
105
- await queueJobService.markAttemptCompleted(trackedJob, result)
106
- } catch (error) {
107
- chatLogger.error`Failed to persist queue job completion (queue=${queueName}, job=${job.id}): ${error}`
108
- }
109
- return result
110
- } catch (error) {
111
- try {
112
- await queueJobService.markAttemptFailed(trackedJob, error)
113
- } catch (persistenceError) {
114
- chatLogger.error`Failed to persist queue job failure (queue=${queueName}, job=${job.id}): ${persistenceError}`
115
- }
116
- throw error
117
- }
107
+ return Effect.runPromise(
108
+ Effect.catch(
109
+ Effect.gen(function* () {
110
+ yield* Effect.catch(getQueueJobService().markAttemptStarted(trackedJob), (error) =>
111
+ Effect.sync(() => {
112
+ logPersistenceError('start', error)
113
+ }),
114
+ )
115
+
116
+ const result = yield* Effect.tryPromise(() => processor(job))
117
+
118
+ yield* Effect.catch(getQueueJobService().markAttemptCompleted(trackedJob, result), (error) =>
119
+ Effect.sync(() => {
120
+ logPersistenceError('completion', error)
121
+ }),
122
+ )
123
+
124
+ return result
125
+ }),
126
+ (error) =>
127
+ Effect.gen(function* () {
128
+ yield* Effect.catch(getQueueJobService().markAttemptFailed(trackedJob, error), (persistenceError) =>
129
+ Effect.sync(() => {
130
+ logPersistenceError('failure', persistenceError)
131
+ }),
132
+ )
133
+ return yield* error
134
+ }),
135
+ ),
136
+ )
118
137
  }
119
138
  }
120
139
 
140
+ /** Registers process signal handlers for sandboxed workers that run outside an Effect scope. */
121
141
  export const registerShutdownSignals = ({
122
142
  name,
123
143
  shutdown,
@@ -130,21 +150,26 @@ export const registerShutdownSignals = ({
130
150
  timeoutMs?: number
131
151
  }) => {
132
152
  let shuttingDown = false
153
+ let forcedExitArmed = true
133
154
 
134
155
  const handleSignal = () => {
135
156
  if (shuttingDown) return
136
157
  shuttingDown = true
137
- const timeout = setTimeout(() => {
138
- logger.warn`Forced shutdown after ${timeoutMs}ms`
139
- process.exit(0)
140
- }, timeoutMs)
158
+ void Effect.runFork(
159
+ Effect.gen(function* () {
160
+ yield* Effect.tryPromise(() => Bun.sleep(timeoutMs))
161
+ if (!forcedExitArmed) return
162
+ logger.warn`Forced shutdown after ${timeoutMs}ms`
163
+ process.exit(0)
164
+ }),
165
+ )
141
166
 
142
167
  shutdown()
143
168
  .catch((err: unknown) => {
144
169
  logger.error`Failed to shutdown ${name} worker: ${err}`
145
170
  })
146
171
  .finally(() => {
147
- clearTimeout(timeout)
172
+ forcedExitArmed = false
148
173
  process.exit(0)
149
174
  })
150
175
  }
@@ -1,47 +0,0 @@
1
- import { chatLogger } from './logger'
2
-
3
- const isDebug = (): boolean => {
4
- const level = process.env.LOG_LEVEL
5
- const flag = process.env.LOTA_DEBUG
6
- return level === 'debug' || flag === '1'
7
- }
8
-
9
- interface DebugTimer {
10
- step(name: string): void
11
- elapsed(): number
12
- }
13
-
14
- const NOOP_TIMER: DebugTimer = { step() {}, elapsed: () => 0 }
15
-
16
- function createTimer(label: string): DebugTimer {
17
- const start = performance.now()
18
- let lastStep = start
19
-
20
- return {
21
- step(name: string) {
22
- const now = performance.now()
23
- const stepMs = now - lastStep
24
- const totalMs = now - start
25
- chatLogger.debug`[ttft:${label}] ${name}: ${stepMs.toFixed(1)}ms (elapsed: ${totalMs.toFixed(1)}ms)`
26
- lastStep = now
27
- },
28
- elapsed() {
29
- return performance.now() - start
30
- },
31
- }
32
- }
33
-
34
- export const lotaDebugLogger = {
35
- get enabled() {
36
- return isDebug()
37
- },
38
-
39
- timer(label: string): DebugTimer {
40
- return isDebug() ? createTimer(label) : NOOP_TIMER
41
- },
42
-
43
- step(name: string) {
44
- if (!isDebug()) return
45
- chatLogger.debug`[ttft] ${name}`
46
- },
47
- }
@@ -1,26 +0,0 @@
1
- import type IORedis from 'ioredis'
2
-
3
- export interface RedisConnectionAccessor {
4
- getConnection(): IORedis
5
- getConnectionForBullMQ(): IORedis
6
- }
7
-
8
- let redisManager: RedisConnectionAccessor | undefined
9
-
10
- export function setRedisConnectionManager(manager: RedisConnectionAccessor): void {
11
- redisManager = manager
12
- }
13
-
14
- export function getRedisConnection(): IORedis {
15
- if (!redisManager) {
16
- throw new Error('Redis connection manager not configured. Call setRedisConnectionManager() first.')
17
- }
18
- return redisManager.getConnection()
19
- }
20
-
21
- export function getRedisConnectionForBullMQ(): IORedis {
22
- if (!redisManager) {
23
- throw new Error('Redis connection manager not configured. Call setRedisConnectionManager() first.')
24
- }
25
- return redisManager.getConnectionForBullMQ()
26
- }
@@ -1,87 +0,0 @@
1
- import { createContextCompactionAgent } from '../system-agents/context-compaction.agent'
2
- import {
3
- buildContextCompactionPrompt,
4
- buildMemoryBlockCompactionPrompt,
5
- ContextCompactionOutputSchema,
6
- createContextCompactionRuntime,
7
- parseCompactionOutput,
8
- } from './context-compaction'
9
- import type { ContextCompactionRunnerParams } from './context-compaction'
10
- import {
11
- COMPACTION_CHUNK_MAX_CHARS,
12
- CONTEXT_COMPACTION_INCLUDED_TOOL_NAMES,
13
- CONTEXT_COMPACTION_INCLUDED_TOOL_PREFIXES,
14
- CONTEXT_COMPACTION_THRESHOLD_RATIO,
15
- CONTEXT_OUTPUT_RESERVE_TOKENS,
16
- CONTEXT_SAFETY_MARGIN_TOKENS,
17
- SUMMARY_ROLLUP_MAX_TOKENS,
18
- } from './context-compaction-constants'
19
- import type { GenerateHelperStructuredParams, GenerateHelperTextParams } from './helper-model'
20
-
21
- const CONTEXT_COMPACTION_MAX_OUTPUT_TOKENS = 512
22
-
23
- interface HelperModelRuntime {
24
- generateHelperStructured<T>(params: GenerateHelperStructuredParams<T>): Promise<T>
25
- generateHelperText(params: GenerateHelperTextParams): Promise<string>
26
- }
27
-
28
- interface CreateContextCompactionRuntimeDeps {
29
- helperModelRuntime: HelperModelRuntime
30
- now?: () => number
31
- randomId?: () => string
32
- }
33
-
34
- async function runContextCompacter(helperModelRuntime: HelperModelRuntime, params: ContextCompactionRunnerParams) {
35
- const output = await helperModelRuntime.generateHelperStructured({
36
- tag: 'context-compaction',
37
- createAgent: createContextCompactionAgent,
38
- messages: [
39
- {
40
- role: 'user',
41
- content: buildContextCompactionPrompt({
42
- previousSummary: params.previousSummary,
43
- transcript: params.transcript,
44
- }),
45
- },
46
- ],
47
- schema: ContextCompactionOutputSchema,
48
- maxOutputTokens: 8_000,
49
- })
50
-
51
- return parseCompactionOutput(output)
52
- }
53
-
54
- export function createWiredContextCompactionRuntime(deps: CreateContextCompactionRuntimeDeps) {
55
- const { helperModelRuntime } = deps
56
-
57
- const runtime = createContextCompactionRuntime({
58
- runCompacter: (params) => runContextCompacter(helperModelRuntime, params),
59
- now: deps.now,
60
- randomId: deps.randomId,
61
- thresholdRatio: CONTEXT_COMPACTION_THRESHOLD_RATIO,
62
- outputReserveTokens: CONTEXT_OUTPUT_RESERVE_TOKENS,
63
- safetyMarginTokens: CONTEXT_SAFETY_MARGIN_TOKENS,
64
- compactionChunkMaxChars: COMPACTION_CHUNK_MAX_CHARS,
65
- summaryRollupMaxTokens: SUMMARY_ROLLUP_MAX_TOKENS,
66
- includedToolNames: CONTEXT_COMPACTION_INCLUDED_TOOL_NAMES,
67
- includedToolPrefixes: CONTEXT_COMPACTION_INCLUDED_TOOL_PREFIXES,
68
- })
69
-
70
- async function compactMemoryBlockSummary(params: {
71
- previousSummary: string
72
- newEntriesText: string
73
- }): Promise<string> {
74
- const previousSummary = params.previousSummary.trim()
75
- const newEntriesText = params.newEntriesText.trim()
76
- if (!previousSummary && !newEntriesText) return ''
77
-
78
- return helperModelRuntime.generateHelperText({
79
- tag: 'memory-block-compaction',
80
- createAgent: createContextCompactionAgent,
81
- messages: [{ role: 'user', content: buildMemoryBlockCompactionPrompt({ previousSummary, newEntriesText }) }],
82
- maxOutputTokens: CONTEXT_COMPACTION_MAX_OUTPUT_TOKENS,
83
- })
84
- }
85
-
86
- return { ...runtime, compactMemoryBlockSummary }
87
- }
@@ -1,118 +0,0 @@
1
- import { stepCountIs } from 'ai'
2
- import type { ToolLoopAgent, ToolSet } from 'ai'
3
-
4
- import { createAgent, getAgentRuntimeConfig } from '../config/agent-defaults'
5
- import { aiLogger } from '../config/logger'
6
-
7
- type SocialChatAgent = ToolLoopAgent<never, ToolSet>
8
-
9
- interface ExecutableToolDefinition {
10
- execute: (...args: unknown[]) => Promise<unknown>
11
- }
12
-
13
- const SOCIAL_CHAT_RETRIEVAL_TOOL_NAMES = [
14
- 'memorySearch',
15
- 'conversationSearch',
16
- 'queryKnowledge',
17
- 'researchTopic',
18
- 'searchWeb',
19
- 'fetchWebpage',
20
- 'inspectWebsite',
21
- ] as const
22
-
23
- export function withLoggedSocialToolSet(
24
- tools: ToolSet,
25
- params: { agentId: string; channelId: string; threadId: string; executedToolNames: string[] },
26
- ): ToolSet {
27
- return Object.fromEntries(
28
- Object.entries(tools).map(([toolName, toolDefinition]) => {
29
- if (
30
- typeof toolDefinition !== 'object' ||
31
- !('execute' in toolDefinition) ||
32
- typeof toolDefinition.execute !== 'function'
33
- ) {
34
- return [toolName, toolDefinition]
35
- }
36
-
37
- const executableTool = toolDefinition as typeof toolDefinition & ExecutableToolDefinition
38
- return [
39
- toolName,
40
- {
41
- ...toolDefinition,
42
- execute: async (...args: unknown[]): Promise<unknown> => {
43
- aiLogger.info`Slack social-chat tool start: agentId=${params.agentId}, tool=${toolName}, channelId=${params.channelId}, threadId=${params.threadId}`
44
- try {
45
- const result: unknown = await executableTool.execute(...args)
46
- params.executedToolNames.push(toolName)
47
- aiLogger.info`Slack social-chat tool finish: agentId=${params.agentId}, tool=${toolName}, channelId=${params.channelId}, threadId=${params.threadId}`
48
- return result
49
- } catch (error) {
50
- aiLogger.warn`Slack social-chat tool failed: agentId=${params.agentId}, tool=${toolName}, channelId=${params.channelId}, threadId=${params.threadId}, error=${error}`
51
- throw error
52
- }
53
- },
54
- },
55
- ]
56
- }),
57
- )
58
- }
59
-
60
- export async function runSocialAgentTurn(params: {
61
- agentId: string
62
- mode: 'fixedThreadMode' | 'threadMode'
63
- threadType: 'group'
64
- onboardingActive: boolean
65
- linearInstalled: boolean
66
- systemWorkspaceDetails?: string
67
- preSeededMemoriesSection?: string
68
- retrievedKnowledgeSection?: string
69
- learnedSkillsSection?: string
70
- userMessageText?: string
71
- additionalInstructionSections?: string[]
72
- prompt: string
73
- tools: ToolSet
74
- abortSignal: AbortSignal
75
- }): Promise<{ text: string; displayName: string }> {
76
- const runtimeConfig = getAgentRuntimeConfig({
77
- agentId: params.agentId,
78
- threadType: params.threadType,
79
- mode: params.mode,
80
- onboardingActive: params.onboardingActive,
81
- linearInstalled: params.linearInstalled,
82
- systemWorkspaceDetails: params.systemWorkspaceDetails,
83
- preSeededMemoriesSection: params.preSeededMemoriesSection,
84
- retrievedKnowledgeSection: params.retrievedKnowledgeSection,
85
- learnedSkillsSection: params.learnedSkillsSection,
86
- userMessageText: params.userMessageText,
87
- ruleOptions: {
88
- includeMemr3Rule: SOCIAL_CHAT_RETRIEVAL_TOOL_NAMES.some((toolName) => toolName in params.tools),
89
- includeDomainReasoningFallbackRule: params.agentId === 'cpo' || params.agentId === 'mentor',
90
- },
91
- additionalInstructionSections: params.additionalInstructionSections,
92
- }) as Record<string, unknown>
93
-
94
- const agentFactory = createAgent[runtimeConfig.id as string]
95
- if (typeof agentFactory !== 'function') {
96
- throw new Error(`Social chat agent factory is not configured for ${String(runtimeConfig.id)}`)
97
- }
98
-
99
- const agent = agentFactory({
100
- mode: params.mode,
101
- tools: params.tools,
102
- extraInstructions: runtimeConfig.extraInstructions,
103
- stopWhen: [stepCountIs(runtimeConfig.maxSteps as number)],
104
- }) as SocialChatAgent
105
- const response = await agent.generate({ prompt: params.prompt, abortSignal: params.abortSignal })
106
- const text = response.text.trim()
107
- if (!text) {
108
- throw new Error(`Social chat agent ${params.agentId} returned an empty response.`)
109
- }
110
-
111
- return {
112
- text,
113
- displayName:
114
- typeof runtimeConfig.displayName === 'string' && runtimeConfig.displayName.trim()
115
- ? runtimeConfig.displayName.trim()
116
- : params.agentId,
117
- }
118
- }