@lota-sdk/core 0.4.8 → 0.4.10

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 (272) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +96 -22
  3. package/src/ai-gateway/ai-gateway.ts +766 -223
  4. package/src/config/agent-defaults.ts +189 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/constants.ts +8 -2
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +299 -19
  10. package/src/config/thread-defaults.ts +40 -20
  11. package/src/create-runtime.ts +200 -449
  12. package/src/db/base.service.ts +52 -28
  13. package/src/db/cursor-pagination.ts +71 -30
  14. package/src/db/memory-query-builder.ts +2 -1
  15. package/src/db/memory-store.helpers.ts +4 -7
  16. package/src/db/memory-store.ts +868 -601
  17. package/src/db/memory.ts +396 -280
  18. package/src/db/record-id.ts +32 -10
  19. package/src/db/schema-fingerprint.ts +30 -12
  20. package/src/db/service-normalization.ts +288 -0
  21. package/src/db/service.ts +912 -779
  22. package/src/db/startup.ts +153 -68
  23. package/src/db/transaction-conflict.ts +15 -0
  24. package/src/effect/awaitable-effect.ts +96 -0
  25. package/src/effect/errors.ts +121 -0
  26. package/src/effect/helpers.ts +123 -0
  27. package/src/effect/index.ts +24 -0
  28. package/src/effect/layers.ts +238 -0
  29. package/src/effect/runtime-ref.ts +25 -0
  30. package/src/effect/runtime.ts +46 -0
  31. package/src/effect/services.ts +61 -0
  32. package/src/effect/zod.ts +43 -0
  33. package/src/embeddings/provider.ts +128 -83
  34. package/src/index.ts +48 -1
  35. package/src/openrouter/direct-provider.ts +11 -35
  36. package/src/queues/autonomous-job.queue.ts +117 -73
  37. package/src/queues/context-compaction.queue.ts +50 -17
  38. package/src/queues/delayed-node-promotion.queue.ts +46 -17
  39. package/src/queues/document-processor.queue.ts +52 -77
  40. package/src/queues/memory-consolidation.queue.ts +47 -32
  41. package/src/queues/organization-learning.queue.ts +26 -4
  42. package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
  43. package/src/queues/plan-scheduler.queue.ts +97 -33
  44. package/src/queues/post-chat-memory.queue.ts +56 -26
  45. package/src/queues/queue-factory.ts +227 -59
  46. package/src/queues/standalone-worker.ts +39 -0
  47. package/src/queues/title-generation.queue.ts +45 -11
  48. package/src/redis/connection.ts +182 -113
  49. package/src/redis/index.ts +6 -8
  50. package/src/redis/org-memory-lock.ts +60 -27
  51. package/src/redis/redis-lease-lock.ts +200 -121
  52. package/src/redis/runtime-connection.ts +20 -0
  53. package/src/redis/stream-context.ts +92 -46
  54. package/src/runtime/agent-identity-overrides.ts +2 -2
  55. package/src/runtime/agent-runtime-policy.ts +5 -2
  56. package/src/runtime/agent-stream-helpers.ts +24 -9
  57. package/src/runtime/chat-run-orchestration.ts +102 -19
  58. package/src/runtime/chat-run-registry.ts +36 -2
  59. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  60. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +161 -94
  61. package/src/runtime/domain-layer.ts +192 -0
  62. package/src/runtime/execution-plan-visibility.ts +2 -2
  63. package/src/runtime/execution-plan.ts +42 -15
  64. package/src/runtime/graph-designer.ts +16 -4
  65. package/src/runtime/helper-model.ts +139 -48
  66. package/src/runtime/index.ts +7 -8
  67. package/src/runtime/indexed-repositories-policy.ts +3 -3
  68. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -36
  69. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  70. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +54 -67
  71. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  72. package/src/runtime/memory/memory-scope.ts +53 -0
  73. package/src/runtime/plugin-resolution.ts +124 -25
  74. package/src/runtime/plugin-types.ts +9 -1
  75. package/src/runtime/post-turn-side-effects.ts +177 -130
  76. package/src/runtime/retrieval-adapters.ts +40 -6
  77. package/src/runtime/runtime-accessors.ts +92 -0
  78. package/src/runtime/runtime-config.ts +150 -61
  79. package/src/runtime/runtime-extensions.ts +23 -25
  80. package/src/runtime/runtime-lifecycle.ts +124 -0
  81. package/src/runtime/runtime-services.ts +386 -0
  82. package/src/runtime/runtime-token.ts +47 -0
  83. package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
  84. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
  85. package/src/runtime/social-chat/social-chat.ts +630 -0
  86. package/src/runtime/specialist-runner.ts +36 -10
  87. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -0
  88. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  89. package/src/runtime/thread-chat-helpers.ts +2 -2
  90. package/src/runtime/thread-plan-turn.ts +2 -1
  91. package/src/runtime/thread-turn-context.ts +183 -111
  92. package/src/runtime/turn-lifecycle.ts +93 -27
  93. package/src/services/agent-activity.service.ts +287 -203
  94. package/src/services/agent-executor.service.ts +253 -149
  95. package/src/services/artifact.service.ts +231 -149
  96. package/src/services/attachment.service.ts +171 -115
  97. package/src/services/autonomous-job.service.ts +890 -491
  98. package/src/services/background-work.service.ts +54 -0
  99. package/src/services/chat-run-registry.service.ts +13 -1
  100. package/src/services/context-compaction.service.ts +136 -86
  101. package/src/services/document-chunk.service.ts +151 -88
  102. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  103. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  104. package/src/services/execution-plan/execution-plan-graph.ts +278 -0
  105. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  106. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  107. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  108. package/src/services/feedback-loop.service.ts +132 -76
  109. package/src/services/global-orchestrator.service.ts +101 -168
  110. package/src/services/graph-full-routing.ts +193 -0
  111. package/src/services/index.ts +19 -21
  112. package/src/services/institutional-memory.service.ts +213 -125
  113. package/src/services/learned-skill.service.ts +368 -260
  114. package/src/services/memory/memory-conversation.ts +95 -0
  115. package/src/services/memory/memory-errors.ts +27 -0
  116. package/src/services/memory/memory-org-memory.ts +50 -0
  117. package/src/services/memory/memory-preseeded.ts +86 -0
  118. package/src/services/memory/memory-rerank.ts +297 -0
  119. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
  120. package/src/services/memory/memory.service.ts +674 -0
  121. package/src/services/memory/rerank.service.ts +201 -0
  122. package/src/services/monitoring-window.service.ts +92 -70
  123. package/src/services/mutating-approval.service.ts +62 -53
  124. package/src/services/node-workspace.service.ts +141 -98
  125. package/src/services/notification.service.ts +29 -16
  126. package/src/services/organization-member.service.ts +120 -66
  127. package/src/services/organization.service.ts +153 -77
  128. package/src/services/ownership-dispatcher.service.ts +456 -263
  129. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  130. package/src/services/plan/plan-agent-query.service.ts +322 -0
  131. package/src/services/{plan-approval.service.ts → plan/plan-approval.service.ts} +45 -22
  132. package/src/services/plan/plan-artifact.service.ts +60 -0
  133. package/src/services/plan/plan-builder.service.ts +76 -0
  134. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  135. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  136. package/src/services/plan/plan-completion-side-effects.ts +169 -0
  137. package/src/services/plan/plan-coordination.service.ts +181 -0
  138. package/src/services/plan/plan-cycle.service.ts +405 -0
  139. package/src/services/plan/plan-deadline.service.ts +533 -0
  140. package/src/services/plan/plan-event-delivery.service.ts +266 -0
  141. package/src/services/plan/plan-executor-context.ts +35 -0
  142. package/src/services/plan/plan-executor-graph.ts +522 -0
  143. package/src/services/plan/plan-executor-helpers.ts +307 -0
  144. package/src/services/plan/plan-executor-persistence.ts +209 -0
  145. package/src/services/plan/plan-executor.service.ts +1737 -0
  146. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  147. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  148. package/src/services/plan/plan-run-serialization.ts +15 -0
  149. package/src/services/plan/plan-run.service.ts +637 -0
  150. package/src/services/plan/plan-scheduler.service.ts +379 -0
  151. package/src/services/plan/plan-template.service.ts +224 -0
  152. package/src/services/plan/plan-transaction-events.ts +36 -0
  153. package/src/services/plan/plan-validator.service.ts +907 -0
  154. package/src/services/plan/plan-workspace.service.ts +131 -0
  155. package/src/services/plugin-executor.service.ts +102 -68
  156. package/src/services/quality-metrics.service.ts +112 -94
  157. package/src/services/queue-job.service.ts +288 -231
  158. package/src/services/recent-activity-title.service.ts +73 -36
  159. package/src/services/recent-activity.service.ts +274 -259
  160. package/src/services/skill-resolver.service.ts +38 -12
  161. package/src/services/social-chat-history.service.ts +190 -122
  162. package/src/services/system-executor.service.ts +96 -61
  163. package/src/services/thread/thread-active-run.ts +203 -0
  164. package/src/services/thread/thread-bootstrap.ts +385 -0
  165. package/src/services/thread/thread-listing.ts +199 -0
  166. package/src/services/thread/thread-memory-block.ts +130 -0
  167. package/src/services/thread/thread-message.service.ts +379 -0
  168. package/src/services/thread/thread-record-store.ts +155 -0
  169. package/src/services/thread/thread-title.service.ts +74 -0
  170. package/src/services/thread/thread-turn-execution.ts +280 -0
  171. package/src/services/thread/thread-turn-message-context.ts +73 -0
  172. package/src/services/thread/thread-turn-preparation.service.ts +1148 -0
  173. package/src/services/thread/thread-turn-streaming.ts +403 -0
  174. package/src/services/thread/thread-turn-tracing.ts +35 -0
  175. package/src/services/thread/thread-turn.ts +376 -0
  176. package/src/services/thread/thread.service.ts +344 -0
  177. package/src/services/user.service.ts +82 -32
  178. package/src/services/write-intent-validator.service.ts +63 -51
  179. package/src/storage/attachment-parser.ts +69 -27
  180. package/src/storage/attachment-storage.service.ts +334 -275
  181. package/src/storage/generated-document-storage.service.ts +66 -34
  182. package/src/system-agents/agent-result.ts +3 -1
  183. package/src/system-agents/context-compaction.agent.ts +3 -3
  184. package/src/system-agents/delegated-agent-factory.ts +159 -90
  185. package/src/system-agents/helper-agent-options.ts +1 -1
  186. package/src/system-agents/memory-reranker.agent.ts +3 -3
  187. package/src/system-agents/memory.agent.ts +3 -3
  188. package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
  189. package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
  190. package/src/system-agents/skill-extractor.agent.ts +3 -3
  191. package/src/system-agents/skill-manager.agent.ts +3 -3
  192. package/src/system-agents/thread-router.agent.ts +157 -113
  193. package/src/system-agents/title-generator.agent.ts +3 -3
  194. package/src/tools/execution-plan.tool.ts +241 -171
  195. package/src/tools/fetch-webpage.tool.ts +29 -18
  196. package/src/tools/firecrawl-client.ts +26 -6
  197. package/src/tools/index.ts +1 -0
  198. package/src/tools/memory-block.tool.ts +14 -6
  199. package/src/tools/plan-approval.tool.ts +57 -47
  200. package/src/tools/read-file-parts.tool.ts +44 -33
  201. package/src/tools/remember-memory.tool.ts +65 -45
  202. package/src/tools/search-web.tool.ts +33 -22
  203. package/src/tools/search.tool.ts +41 -29
  204. package/src/tools/team-think.tool.ts +125 -84
  205. package/src/tools/user-questions.tool.ts +4 -3
  206. package/src/tools/web-tool-shared.ts +6 -0
  207. package/src/utils/async.ts +25 -22
  208. package/src/utils/crypto.ts +21 -0
  209. package/src/utils/date-time.ts +40 -1
  210. package/src/utils/errors.ts +111 -20
  211. package/src/utils/hono-error-handler.ts +24 -39
  212. package/src/utils/index.ts +2 -1
  213. package/src/utils/null-proto-record.ts +41 -0
  214. package/src/utils/sse-keepalive.ts +124 -21
  215. package/src/workers/bootstrap.ts +164 -52
  216. package/src/workers/memory-consolidation.worker.ts +325 -237
  217. package/src/workers/organization-learning.worker.ts +50 -16
  218. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  219. package/src/workers/regular-chat-memory-digest.runner.ts +185 -114
  220. package/src/workers/skill-extraction.runner.ts +176 -93
  221. package/src/workers/utils/file-section-chunker.ts +8 -10
  222. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  223. package/src/workers/utils/repomix-file-sections.ts +2 -2
  224. package/src/workers/utils/thread-message-query.ts +97 -38
  225. package/src/workers/worker-utils.ts +74 -31
  226. package/src/config/debug-logger.ts +0 -47
  227. package/src/config/search.ts +0 -3
  228. package/src/redis/connection-accessor.ts +0 -26
  229. package/src/runtime/agent-types.ts +0 -1
  230. package/src/runtime/context-compaction-runtime.ts +0 -87
  231. package/src/runtime/memory-scope.ts +0 -43
  232. package/src/runtime/social-chat-agent-runner.ts +0 -118
  233. package/src/runtime/social-chat.ts +0 -516
  234. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  235. package/src/services/adaptive-playbook.service.ts +0 -152
  236. package/src/services/artifact-provenance.service.ts +0 -172
  237. package/src/services/chat-attachments.service.ts +0 -17
  238. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  239. package/src/services/execution-plan.service.ts +0 -1118
  240. package/src/services/memory.service.ts +0 -914
  241. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  242. package/src/services/plan-agent-query.service.ts +0 -267
  243. package/src/services/plan-artifact.service.ts +0 -50
  244. package/src/services/plan-builder.service.ts +0 -67
  245. package/src/services/plan-checkpoint.service.ts +0 -81
  246. package/src/services/plan-completion-side-effects.ts +0 -80
  247. package/src/services/plan-coordination.service.ts +0 -157
  248. package/src/services/plan-cycle.service.ts +0 -284
  249. package/src/services/plan-deadline.service.ts +0 -430
  250. package/src/services/plan-event-delivery.service.ts +0 -166
  251. package/src/services/plan-executor.service.ts +0 -1950
  252. package/src/services/plan-run.service.ts +0 -515
  253. package/src/services/plan-scheduler.service.ts +0 -240
  254. package/src/services/plan-template.service.ts +0 -177
  255. package/src/services/plan-validator.service.ts +0 -818
  256. package/src/services/plan-workspace.service.ts +0 -83
  257. package/src/services/rerank.service.ts +0 -156
  258. package/src/services/thread-message.service.ts +0 -275
  259. package/src/services/thread-plan-registry.service.ts +0 -22
  260. package/src/services/thread-title.service.ts +0 -39
  261. package/src/services/thread-turn-preparation.service.ts +0 -1147
  262. package/src/services/thread-turn.ts +0 -172
  263. package/src/services/thread.service.ts +0 -869
  264. package/src/utils/env.ts +0 -8
  265. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  266. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  267. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  268. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  269. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  270. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  271. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  272. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -14,14 +14,16 @@ import type {
14
14
  RecentActivityEventInput,
15
15
  RecentActivitySource,
16
16
  } from '@lota-sdk/shared'
17
- import { RecordId } from 'surrealdb'
17
+ import { Context, Effect, Layer } from 'effect'
18
18
  import { z } from 'zod'
19
19
 
20
20
  import { ensureRecordId, recordIdToString } from '../db/record-id'
21
21
  import type { RecordIdInput, RecordIdRef } from '../db/record-id'
22
- import { databaseService } from '../db/service'
22
+ import type { SurrealDBService } from '../db/service'
23
23
  import { TABLES } from '../db/tables'
24
- import { toIsoDateTimeString, toOptionalIsoDateTimeString } from '../utils/date-time'
24
+ import { DatabaseServiceTag } from '../effect/services'
25
+ import { createDeterministicRecordId } from '../utils/crypto'
26
+ import { nowDate, toIsoDateTimeString, toOptionalIsoDateTimeString, unsafeDateFrom } from '../utils/date-time'
25
27
  import { compactRecord, compactWhitespace, truncateText } from '../utils/string'
26
28
 
27
29
  const RecentActivityEventRowSchema = z.object({
@@ -67,11 +69,6 @@ const RecentActivityRowSchema = z.object({
67
69
  type RecentActivityEventRow = z.infer<typeof RecentActivityEventRowSchema>
68
70
  type RecentActivityRow = z.infer<typeof RecentActivityRowSchema>
69
71
 
70
- function buildDeterministicRecordId(table: string, key: string): RecordId {
71
- const digest = new Bun.CryptoHasher('sha256').update(key).digest('hex')
72
- return new RecordId(table, digest)
73
- }
74
-
75
72
  function shouldKeepExistingAgentTitle(existing: RecentActivityRow | null): boolean {
76
73
  return existing?.titleSource === 'agent' && compactWhitespace(existing.title).length > 0
77
74
  }
@@ -98,286 +95,304 @@ function buildRecentActivityAreaKey(
98
95
  return row.mergeKey
99
96
  }
100
97
 
101
- class RecentActivityService {
102
- private toPublicEvent(row: RecentActivityEventRow): RecentActivityEvent {
103
- return RecentActivityEventSchema.parse({
104
- id: recordIdToString(
105
- ensureRecordId(row.id as RecordIdInput, TABLES.RECENT_ACTIVITY_EVENT),
106
- TABLES.RECENT_ACTIVITY_EVENT,
107
- ),
108
- sourceEventId: row.sourceEventId,
109
- source: row.source,
110
- kind: row.kind,
111
- targetKind: row.targetKind,
112
- targetId: row.targetId,
113
- mergeKey: row.mergeKey,
114
- title: row.title,
115
- sourceLabel: row.sourceLabel,
116
- deepLink: row.deepLink,
117
- ...(row.metadata ? { metadata: row.metadata } : {}),
118
- occurredAt: toIsoDateTimeString(row.occurredAt),
119
- createdAt: toIsoDateTimeString(row.createdAt),
120
- })
121
- }
98
+ function toPublicEvent(row: RecentActivityEventRow): RecentActivityEvent {
99
+ return RecentActivityEventSchema.parse({
100
+ id: recordIdToString(
101
+ ensureRecordId(row.id as RecordIdInput, TABLES.RECENT_ACTIVITY_EVENT),
102
+ TABLES.RECENT_ACTIVITY_EVENT,
103
+ ),
104
+ sourceEventId: row.sourceEventId,
105
+ source: row.source,
106
+ kind: row.kind,
107
+ targetKind: row.targetKind,
108
+ targetId: row.targetId,
109
+ mergeKey: row.mergeKey,
110
+ title: row.title,
111
+ sourceLabel: row.sourceLabel,
112
+ deepLink: row.deepLink,
113
+ ...(row.metadata ? { metadata: row.metadata } : {}),
114
+ occurredAt: toIsoDateTimeString(row.occurredAt),
115
+ createdAt: toIsoDateTimeString(row.createdAt),
116
+ })
117
+ }
122
118
 
123
- private toPublicItem(row: RecentActivityRow): RecentActivity {
124
- return RecentActivitySchema.parse({
125
- id: recordIdToString(ensureRecordId(row.id as RecordIdInput, TABLES.RECENT_ACTIVITY), TABLES.RECENT_ACTIVITY),
126
- kind: row.kind,
127
- targetKind: row.targetKind,
128
- targetId: row.targetId,
129
- mergeKey: row.mergeKey,
130
- title: row.title,
131
- systemTitle: row.systemTitle,
132
- titleSource: row.titleSource,
133
- sourceLabel: row.sourceLabel,
134
- deepLink: row.deepLink,
135
- ...(row.metadata ? { metadata: row.metadata } : {}),
136
- latestEventAt: toIsoDateTimeString(row.latestEventAt),
137
- titleRefinedAt: toOptionalIsoDateTimeString(row.titleRefinedAt),
138
- createdAt: toIsoDateTimeString(row.createdAt),
139
- updatedAt: toIsoDateTimeString(row.updatedAt),
140
- })
141
- }
119
+ function toPublicItem(row: RecentActivityRow): RecentActivity {
120
+ return RecentActivitySchema.parse({
121
+ id: recordIdToString(ensureRecordId(row.id as RecordIdInput, TABLES.RECENT_ACTIVITY), TABLES.RECENT_ACTIVITY),
122
+ kind: row.kind,
123
+ targetKind: row.targetKind,
124
+ targetId: row.targetId,
125
+ mergeKey: row.mergeKey,
126
+ title: row.title,
127
+ systemTitle: row.systemTitle,
128
+ titleSource: row.titleSource,
129
+ sourceLabel: row.sourceLabel,
130
+ deepLink: row.deepLink,
131
+ ...(row.metadata ? { metadata: row.metadata } : {}),
132
+ latestEventAt: toIsoDateTimeString(row.latestEventAt),
133
+ titleRefinedAt: toOptionalIsoDateTimeString(row.titleRefinedAt),
134
+ createdAt: toIsoDateTimeString(row.createdAt),
135
+ updatedAt: toIsoDateTimeString(row.updatedAt),
136
+ })
137
+ }
142
138
 
143
- private sanitizeEvent(input: RecentActivityEventInput): RecentActivityEventInput {
144
- return {
145
- ...input,
146
- sourceEventId: truncateText(input.sourceEventId, 200),
147
- ...(input.targetId ? { targetId: truncateText(input.targetId, 200) } : {}),
148
- mergeKey: truncateText(input.mergeKey, 200),
149
- title: truncateText(input.title, 140),
150
- sourceLabel: truncateText(input.sourceLabel, 40),
151
- ...(input.metadata ? { metadata: RecentActivityMetadataSchema.parse(input.metadata) } : {}),
152
- }
139
+ function sanitizeEvent(input: RecentActivityEventInput): RecentActivityEventInput {
140
+ return {
141
+ ...input,
142
+ sourceEventId: truncateText(input.sourceEventId, 200),
143
+ ...(input.targetId ? { targetId: truncateText(input.targetId, 200) } : {}),
144
+ mergeKey: truncateText(input.mergeKey, 200),
145
+ title: truncateText(input.title, 140),
146
+ sourceLabel: truncateText(input.sourceLabel, 40),
147
+ ...(input.metadata ? { metadata: RecentActivityMetadataSchema.parse(input.metadata) } : {}),
153
148
  }
149
+ }
154
150
 
155
- async recordEvents(params: {
151
+ export function makeRecentActivityService(db: SurrealDBService) {
152
+ function recordEventBody(params: {
156
153
  orgId: RecordIdRef
157
154
  userId: RecordIdRef
158
155
  source: RecentActivitySource
159
- events: RecentActivityEventInput[]
160
- }): Promise<RecentActivity[]> {
161
- await databaseService.connect()
162
-
163
- const results = await Promise.all(
164
- params.events.map((candidate) =>
165
- this.recordEvent({ orgId: params.orgId, userId: params.userId, source: params.source, event: candidate }),
166
- ),
167
- )
168
- return results.map((r) => r.item)
156
+ event: RecentActivityEventInput
157
+ }) {
158
+ return Effect.gen(function* () {
159
+ const parsedEvent = sanitizeEvent(RecentActivityEventInputSchema.parse(params.event))
160
+ const orgIdString = recordIdToString(params.orgId, TABLES.ORGANIZATION)
161
+ const userIdString = recordIdToString(params.userId, TABLES.USER)
162
+ const eventRecordId = createDeterministicRecordId(
163
+ TABLES.RECENT_ACTIVITY_EVENT,
164
+ `${orgIdString}:${userIdString}:${parsedEvent.sourceEventId}`,
165
+ )
166
+ const recentRecordId = createDeterministicRecordId(
167
+ TABLES.RECENT_ACTIVITY,
168
+ `${orgIdString}:${userIdString}:${parsedEvent.mergeKey}`,
169
+ )
170
+ const occurredAt = parsedEvent.occurredAt ? unsafeDateFrom(parsedEvent.occurredAt) : nowDate()
171
+ const existingRecent = yield* db.findOne(TABLES.RECENT_ACTIVITY, { id: recentRecordId }, RecentActivityRowSchema)
172
+
173
+ const eventRow = yield* db.upsert(
174
+ TABLES.RECENT_ACTIVITY_EVENT,
175
+ eventRecordId,
176
+ compactRecord({
177
+ organizationId: params.orgId,
178
+ userId: params.userId,
179
+ sourceEventId: parsedEvent.sourceEventId,
180
+ source: params.source,
181
+ kind: parsedEvent.kind,
182
+ targetKind: parsedEvent.targetKind,
183
+ targetId: parsedEvent.targetId,
184
+ mergeKey: parsedEvent.mergeKey,
185
+ title: parsedEvent.title,
186
+ sourceLabel: parsedEvent.sourceLabel,
187
+ deepLink: parsedEvent.deepLink,
188
+ metadata: parsedEvent.metadata,
189
+ occurredAt,
190
+ }),
191
+ RecentActivityEventRowSchema,
192
+ { mutation: 'merge' },
193
+ )
194
+
195
+ const keepExistingAgentTitle = shouldKeepExistingAgentTitle(existingRecent)
196
+ const visibleTitle = keepExistingAgentTitle && existingRecent ? existingRecent.title : parsedEvent.title
197
+ const recentRow = yield* db.upsert(
198
+ TABLES.RECENT_ACTIVITY,
199
+ recentRecordId,
200
+ compactRecord({
201
+ organizationId: params.orgId,
202
+ userId: params.userId,
203
+ mergeKey: parsedEvent.mergeKey,
204
+ kind: parsedEvent.kind,
205
+ targetKind: parsedEvent.targetKind,
206
+ targetId: parsedEvent.targetId,
207
+ title: visibleTitle,
208
+ systemTitle: parsedEvent.title,
209
+ titleSource: keepExistingAgentTitle ? 'agent' : 'system',
210
+ sourceLabel: parsedEvent.sourceLabel,
211
+ deepLink: parsedEvent.deepLink,
212
+ metadata: parsedEvent.metadata,
213
+ latestEventId: eventRecordId,
214
+ latestSourceEventId: parsedEvent.sourceEventId,
215
+ latestEventAt: occurredAt,
216
+ }),
217
+ RecentActivityRowSchema,
218
+ { mutation: 'merge' },
219
+ )
220
+
221
+ return { event: toPublicEvent(eventRow), item: toPublicItem(recentRow) }
222
+ })
169
223
  }
170
224
 
171
- async recordEvent(params: {
225
+ function recordEvent(params: {
172
226
  orgId: RecordIdRef
173
227
  userId: RecordIdRef
174
228
  source: RecentActivitySource
175
229
  event: RecentActivityEventInput
176
- }): Promise<{ event: RecentActivityEvent; item: RecentActivity }> {
177
- await databaseService.connect()
178
-
179
- const parsedEvent = this.sanitizeEvent(RecentActivityEventInputSchema.parse(params.event))
180
- const orgIdString = recordIdToString(params.orgId, TABLES.ORGANIZATION)
181
- const userIdString = recordIdToString(params.userId, TABLES.USER)
182
- const eventRecordId = buildDeterministicRecordId(
183
- TABLES.RECENT_ACTIVITY_EVENT,
184
- `${orgIdString}:${userIdString}:${parsedEvent.sourceEventId}`,
185
- )
186
- const recentRecordId = buildDeterministicRecordId(
187
- TABLES.RECENT_ACTIVITY,
188
- `${orgIdString}:${userIdString}:${parsedEvent.mergeKey}`,
189
- )
190
- const occurredAt = parsedEvent.occurredAt ? new Date(parsedEvent.occurredAt) : new Date()
191
- const existingRecent = await databaseService.findOne(
192
- TABLES.RECENT_ACTIVITY,
193
- { id: recentRecordId },
194
- RecentActivityRowSchema,
195
- )
196
-
197
- const eventRow = await databaseService.upsert(
198
- TABLES.RECENT_ACTIVITY_EVENT,
199
- eventRecordId,
200
- compactRecord({
201
- organizationId: params.orgId,
202
- userId: params.userId,
203
- sourceEventId: parsedEvent.sourceEventId,
204
- source: params.source,
205
- kind: parsedEvent.kind,
206
- targetKind: parsedEvent.targetKind,
207
- targetId: parsedEvent.targetId,
208
- mergeKey: parsedEvent.mergeKey,
209
- title: parsedEvent.title,
210
- sourceLabel: parsedEvent.sourceLabel,
211
- deepLink: parsedEvent.deepLink,
212
- metadata: parsedEvent.metadata,
213
- occurredAt,
214
- }),
215
- RecentActivityEventRowSchema,
216
- { mutation: 'merge' },
217
- )
218
-
219
- const keepExistingAgentTitle = shouldKeepExistingAgentTitle(existingRecent)
220
- const visibleTitle = keepExistingAgentTitle && existingRecent ? existingRecent.title : parsedEvent.title
221
- const recentRow = await databaseService.upsert(
222
- TABLES.RECENT_ACTIVITY,
223
- recentRecordId,
224
- compactRecord({
225
- organizationId: params.orgId,
226
- userId: params.userId,
227
- mergeKey: parsedEvent.mergeKey,
228
- kind: parsedEvent.kind,
229
- targetKind: parsedEvent.targetKind,
230
- targetId: parsedEvent.targetId,
231
- title: visibleTitle,
232
- systemTitle: parsedEvent.title,
233
- titleSource: keepExistingAgentTitle ? 'agent' : 'system',
234
- sourceLabel: parsedEvent.sourceLabel,
235
- deepLink: parsedEvent.deepLink,
236
- metadata: parsedEvent.metadata,
237
- latestEventId: eventRecordId,
238
- latestSourceEventId: parsedEvent.sourceEventId,
239
- latestEventAt: occurredAt,
240
- }),
241
- RecentActivityRowSchema,
242
- { mutation: 'merge' },
243
- )
244
-
245
- return { event: this.toPublicEvent(eventRow), item: this.toPublicItem(recentRow) }
230
+ }) {
231
+ return Effect.gen(function* () {
232
+ yield* db.connect()
233
+ return yield* recordEventBody(params)
234
+ })
246
235
  }
247
236
 
248
- async listRecentActivities(params: {
237
+ function recordEvents(params: {
249
238
  orgId: RecordIdRef
250
239
  userId: RecordIdRef
251
- limit?: number
252
- }): Promise<RecentActivity[]> {
253
- await databaseService.connect()
254
- const limit = params.limit ?? 5
255
- const rows = await databaseService.findMany(
256
- TABLES.RECENT_ACTIVITY,
257
- { organizationId: params.orgId, userId: params.userId },
258
- RecentActivityRowSchema,
259
- { orderBy: 'latestEventAt', orderDir: 'DESC', limit: Math.max(limit * 5, 25) },
260
- )
261
-
262
- const items: RecentActivity[] = []
263
- const seenAreas = new Set<string>()
264
-
265
- for (const row of rows) {
266
- const areaKey = buildRecentActivityAreaKey(row)
267
- if (seenAreas.has(areaKey)) continue
268
-
269
- seenAreas.add(areaKey)
270
- items.push(this.toPublicItem(row))
271
-
272
- if (items.length === limit) break
273
- }
274
-
275
- return items
240
+ source: RecentActivitySource
241
+ events: RecentActivityEventInput[]
242
+ }) {
243
+ return Effect.gen(function* () {
244
+ yield* db.connect()
245
+ const results = yield* Effect.all(
246
+ params.events.map((candidate) =>
247
+ recordEventBody({ orgId: params.orgId, userId: params.userId, source: params.source, event: candidate }),
248
+ ),
249
+ )
250
+ return results.map((r) => r.item)
251
+ })
276
252
  }
277
253
 
278
- async listRecentEvents(params: {
279
- orgId: RecordIdRef
280
- userId: RecordIdRef
281
- limit?: number
282
- }): Promise<RecentActivityEvent[]> {
283
- await databaseService.connect()
284
- const rows = await databaseService.findMany(
285
- TABLES.RECENT_ACTIVITY_EVENT,
286
- { organizationId: params.orgId, userId: params.userId },
287
- RecentActivityEventRowSchema,
288
- { orderBy: 'occurredAt', orderDir: 'DESC', limit: params.limit ?? 25 },
289
- )
290
- return rows.map((row) => this.toPublicEvent(row))
291
- }
254
+ const refineTitle = (params: { activityId: string | RecordIdRef; title: string }) =>
255
+ Effect.gen(function* () {
256
+ yield* db.connect()
257
+ const activityRef = ensureRecordId(params.activityId, TABLES.RECENT_ACTIVITY)
258
+ const existing = yield* db.findOne(TABLES.RECENT_ACTIVITY, { id: activityRef }, RecentActivityRowSchema)
259
+ if (!existing) return null
260
+
261
+ const nextTitle = truncateText(params.title, 80)
262
+ if (!nextTitle) return toPublicItem(existing)
263
+ if (compactWhitespace(nextTitle).toLowerCase() === compactWhitespace(existing.title).toLowerCase()) {
264
+ return toPublicItem(existing)
265
+ }
266
+
267
+ const updated = yield* db.update(
268
+ TABLES.RECENT_ACTIVITY,
269
+ activityRef,
270
+ { title: nextTitle, titleSource: 'agent', titleRefinedAt: nowDate() },
271
+ RecentActivityRowSchema,
272
+ { mutation: 'merge' },
273
+ )
274
+
275
+ return updated ? toPublicItem(updated) : null
276
+ })
292
277
 
293
- async getRecentActivityById(activityId: string | RecordIdRef): Promise<RecentActivity | null> {
294
- await databaseService.connect()
295
- const activityRef = ensureRecordId(activityId, TABLES.RECENT_ACTIVITY)
296
- const row = await databaseService.findOne(TABLES.RECENT_ACTIVITY, { id: activityRef }, RecentActivityRowSchema)
297
- return row ? this.toPublicItem(row) : null
298
- }
278
+ const getRefinementCandidate = (activityId: string | RecordIdRef) =>
279
+ Effect.gen(function* () {
280
+ yield* db.connect()
281
+ const activityRef = ensureRecordId(activityId, TABLES.RECENT_ACTIVITY)
282
+ const row = yield* db.findOne(TABLES.RECENT_ACTIVITY, { id: activityRef }, RecentActivityRowSchema)
283
+ if (!row) return null
284
+
285
+ return {
286
+ id: recordIdToString(activityRef, TABLES.RECENT_ACTIVITY),
287
+ title: row.title,
288
+ systemTitle: row.systemTitle,
289
+ sourceLabel: row.sourceLabel,
290
+ kind: row.kind,
291
+ metadata: RecentActivityMetadataSchema.parse(row.metadata),
292
+ }
293
+ })
299
294
 
300
- async refineTitle(params: { activityId: string | RecordIdRef; title: string }): Promise<RecentActivity | null> {
301
- await databaseService.connect()
302
- const activityRef = ensureRecordId(params.activityId, TABLES.RECENT_ACTIVITY)
303
- const existing = await databaseService.findOne(TABLES.RECENT_ACTIVITY, { id: activityRef }, RecentActivityRowSchema)
304
- if (!existing) return null
305
-
306
- const nextTitle = truncateText(params.title, 80)
307
- if (!nextTitle) return this.toPublicItem(existing)
308
- if (compactWhitespace(nextTitle).toLowerCase() === compactWhitespace(existing.title).toLowerCase()) {
309
- return this.toPublicItem(existing)
310
- }
311
-
312
- const updated = await databaseService.update(
313
- TABLES.RECENT_ACTIVITY,
314
- activityRef,
315
- { title: nextTitle, titleSource: 'agent', titleRefinedAt: new Date() },
316
- RecentActivityRowSchema,
317
- { mutation: 'merge' },
318
- )
319
-
320
- return updated ? this.toPublicItem(updated) : null
321
- }
295
+ const listRecentActivities = (params: { orgId: RecordIdRef; userId: RecordIdRef; limit?: number }) =>
296
+ Effect.gen(function* () {
297
+ yield* db.connect()
298
+ const limit = params.limit ?? 5
299
+ const rows = yield* db.findMany(
300
+ TABLES.RECENT_ACTIVITY,
301
+ { organizationId: params.orgId, userId: params.userId },
302
+ RecentActivityRowSchema,
303
+ { orderBy: 'latestEventAt', orderDir: 'DESC', limit: Math.max(limit * 5, 25) },
304
+ )
322
305
 
323
- async getRefinementCandidate(
324
- activityId: string | RecordIdRef,
325
- ): Promise<{
326
- id: string
327
- title: string
328
- systemTitle: string
329
- sourceLabel: string
330
- kind: RecentActivity['kind']
331
- metadata: z.infer<typeof RecentActivityMetadataSchema>
332
- } | null> {
333
- await databaseService.connect()
334
- const activityRef = ensureRecordId(activityId, TABLES.RECENT_ACTIVITY)
335
- const row = await databaseService.findOne(TABLES.RECENT_ACTIVITY, { id: activityRef }, RecentActivityRowSchema)
336
- if (!row) return null
337
-
338
- return {
339
- id: recordIdToString(activityRef, TABLES.RECENT_ACTIVITY),
340
- title: row.title,
341
- systemTitle: row.systemTitle,
342
- sourceLabel: row.sourceLabel,
343
- kind: row.kind,
344
- metadata: RecentActivityMetadataSchema.parse(row.metadata),
345
- }
346
- }
306
+ const items: RecentActivity[] = []
307
+ const seenAreas = new Set<string>()
347
308
 
348
- isMeaningfulRefinementCandidate(activity: RecentActivity | null): boolean {
349
- if (!activity) return false
350
- if (activity.kind !== 'chat.turn.completed') return false
351
- return activity.titleSource === 'system'
352
- }
309
+ for (const row of rows) {
310
+ const areaKey = buildRecentActivityAreaKey(row)
311
+ if (seenAreas.has(areaKey)) continue
353
312
 
354
- isAgentTitleUseful(params: { currentTitle: string; systemTitle: string; candidateTitle: string }): boolean {
355
- const candidate = compactWhitespace(params.candidateTitle)
356
- if (!candidate) return false
313
+ seenAreas.add(areaKey)
314
+ items.push(toPublicItem(row))
357
315
 
358
- const normalizedCandidate = candidate.toLowerCase()
359
- const normalizedCurrent = compactWhitespace(params.currentTitle).toLowerCase()
360
- const normalizedSystem = compactWhitespace(params.systemTitle).toLowerCase()
316
+ if (items.length === limit) break
317
+ }
361
318
 
362
- if (normalizedCandidate === normalizedCurrent || normalizedCandidate === normalizedSystem) {
363
- return false
364
- }
319
+ return items
320
+ })
365
321
 
366
- if (candidate.length < 8 || candidate.length > 80) {
367
- return false
368
- }
322
+ const listRecentEvents = (params: { orgId: RecordIdRef; userId: RecordIdRef; limit?: number }) =>
323
+ Effect.gen(function* () {
324
+ yield* db.connect()
325
+ const rows = yield* db.findMany(
326
+ TABLES.RECENT_ACTIVITY_EVENT,
327
+ { organizationId: params.orgId, userId: params.userId },
328
+ RecentActivityEventRowSchema,
329
+ { orderBy: 'occurredAt', orderDir: 'DESC', limit: params.limit ?? 25 },
330
+ )
331
+ return rows.map((row) => toPublicEvent(row))
332
+ })
369
333
 
370
- const bannedTitles = new Set([
371
- 'follow up',
372
- 'conversation',
373
- 'chat',
374
- 'agent task',
375
- 'recent activity',
376
- 'thread update',
377
- ])
334
+ const getRecentActivityById = (activityId: string | RecordIdRef) =>
335
+ Effect.gen(function* () {
336
+ yield* db.connect()
337
+ const activityRef = ensureRecordId(activityId, TABLES.RECENT_ACTIVITY)
338
+ const row = yield* db.findOne(TABLES.RECENT_ACTIVITY, { id: activityRef }, RecentActivityRowSchema)
339
+ return row ? toPublicItem(row) : null
340
+ })
378
341
 
379
- return !bannedTitles.has(normalizedCandidate)
342
+ return {
343
+ recordEvent,
344
+ recordEvents,
345
+ listRecentActivities,
346
+ listRecentEvents,
347
+ getRecentActivityById,
348
+ refineTitle,
349
+ getRefinementCandidate,
350
+
351
+ isMeaningfulRefinementCandidate(activity: RecentActivity | null): boolean {
352
+ if (!activity) return false
353
+ if (activity.kind !== 'chat.turn.completed') return false
354
+ return activity.titleSource === 'system'
355
+ },
356
+
357
+ isAgentTitleUseful(params: { currentTitle: string; systemTitle: string; candidateTitle: string }): boolean {
358
+ const candidate = compactWhitespace(params.candidateTitle)
359
+ if (!candidate) return false
360
+
361
+ const normalizedCandidate = candidate.toLowerCase()
362
+ const normalizedCurrent = compactWhitespace(params.currentTitle).toLowerCase()
363
+ const normalizedSystem = compactWhitespace(params.systemTitle).toLowerCase()
364
+
365
+ if (normalizedCandidate === normalizedCurrent || normalizedCandidate === normalizedSystem) {
366
+ return false
367
+ }
368
+
369
+ if (candidate.length < 8 || candidate.length > 80) {
370
+ return false
371
+ }
372
+
373
+ const bannedTitles = new Set([
374
+ 'follow up',
375
+ 'conversation',
376
+ 'chat',
377
+ 'agent task',
378
+ 'recent activity',
379
+ 'thread update',
380
+ ])
381
+
382
+ return !bannedTitles.has(normalizedCandidate)
383
+ },
380
384
  }
381
385
  }
382
386
 
383
- export const recentActivityService = new RecentActivityService()
387
+ export class RecentActivityServiceTag extends Context.Service<
388
+ RecentActivityServiceTag,
389
+ ReturnType<typeof makeRecentActivityService>
390
+ >()('@lota-sdk/core/RecentActivityService') {}
391
+
392
+ export const RecentActivityServiceLive = Layer.effect(
393
+ RecentActivityServiceTag,
394
+ Effect.gen(function* () {
395
+ const db = yield* DatabaseServiceTag
396
+ return makeRecentActivityService(db)
397
+ }),
398
+ )
@@ -1,19 +1,45 @@
1
- class SkillResolverService {
2
- async resolve(params: {
1
+ import { Context, Effect, Layer } from 'effect'
2
+
3
+ import { LearnedSkillServiceTag } from './learned-skill.service'
4
+
5
+ type LearnedSkillService = Context.Service.Shape<typeof LearnedSkillServiceTag>
6
+
7
+ export interface SkillResolverService {
8
+ resolve(params: {
3
9
  skillRef: string
4
10
  organizationId: string
5
- }): Promise<{ executorType: 'agent' | 'plugin'; ref: string; operation?: string } | null> {
6
- // Dynamic import to avoid circular dependencies and enable testability
7
- const { learnedSkillService } = await import('./learned-skill.service')
8
- const skill = await learnedSkillService.findByNameOrTag(params.organizationId, params.skillRef)
9
- if (!skill) return null
11
+ }): Effect.Effect<
12
+ { executorType: 'agent' | 'plugin'; ref: string; operation?: string } | null,
13
+ Effect.Error<ReturnType<LearnedSkillService['findByNameOrTag']>>
14
+ >
15
+ }
10
16
 
11
- if (skill.agentId) {
12
- return { executorType: 'agent', ref: skill.agentId }
13
- }
17
+ export function makeSkillResolverService(deps: {
18
+ learnedSkillService: Pick<LearnedSkillService, 'findByNameOrTag'>
19
+ }): SkillResolverService {
20
+ return {
21
+ resolve(params) {
22
+ return Effect.gen(function* () {
23
+ const skill = yield* deps.learnedSkillService.findByNameOrTag(params.organizationId, params.skillRef)
24
+ // eslint-disable-next-line typescript-eslint/no-unnecessary-condition -- skill can be null (findByNameOrTag returns T | null)
25
+ if (!skill || !skill.agentId) {
26
+ return null
27
+ }
14
28
 
15
- return null
29
+ return { executorType: 'agent' as const, ref: skill.agentId }
30
+ })
31
+ },
16
32
  }
17
33
  }
18
34
 
19
- export const skillResolverService = new SkillResolverService()
35
+ export class SkillResolverServiceTag extends Context.Service<SkillResolverServiceTag, SkillResolverService>()(
36
+ '@lota-sdk/core/SkillResolverService',
37
+ ) {}
38
+
39
+ export const SkillResolverServiceLive = Layer.effect(
40
+ SkillResolverServiceTag,
41
+ Effect.gen(function* () {
42
+ const learnedSkillService = yield* LearnedSkillServiceTag
43
+ return makeSkillResolverService({ learnedSkillService })
44
+ }),
45
+ )