@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
@@ -1,31 +1,65 @@
1
1
  import type { SandboxedJob } from 'bullmq'
2
+ import { Effect } from 'effect'
3
+ import type { Context } from 'effect'
2
4
 
3
5
  import { serverLogger } from '../config/logger'
6
+ import { effectTryPromise } from '../effect/helpers'
7
+ import { DatabaseServiceTag, RuntimeAdaptersServiceTag, RuntimeConfigServiceTag } from '../effect/services'
4
8
  import type { OrganizationLearningJob } from '../queues/organization-learning.queue'
9
+ import { LearnedSkillServiceTag } from '../services/learned-skill.service'
10
+ import { MemoryServiceTag } from '../services/memory/memory.service'
11
+ import { SocialChatHistoryServiceTag } from '../services/social-chat-history.service'
5
12
  import { initializeSandboxedWorkerRuntime } from './bootstrap'
6
13
  import { runRegularChatMemoryDigest } from './regular-chat-memory-digest.runner'
14
+ import type { RegularChatDigestServices } from './regular-chat-memory-digest.runner'
7
15
  import { runSkillExtraction } from './skill-extraction.runner'
16
+ import type { SkillExtractionServices } from './skill-extraction.runner'
8
17
  import { toSandboxedWorkerError } from './utils/sandbox-error'
9
18
  import { createTracedWorkerProcessor } from './worker-utils'
10
19
 
11
- await initializeSandboxedWorkerRuntime()
20
+ const runtime = await initializeSandboxedWorkerRuntime()
21
+ const resolve = <I, T>(tag: Context.Key<I, T>): Promise<T> => runtime.runPromise(Effect.service(tag))
22
+ const regularChatDigestServices: RegularChatDigestServices = {
23
+ databaseService: await resolve(DatabaseServiceTag),
24
+ memoryService: await resolve(MemoryServiceTag),
25
+ socialChatHistoryService: await resolve(SocialChatHistoryServiceTag),
26
+ runtimeAdapters: await resolve(RuntimeAdaptersServiceTag),
27
+ }
28
+ const workerRuntimeConfig = await resolve(RuntimeConfigServiceTag)
29
+ const skillExtractionServices: SkillExtractionServices = {
30
+ databaseService: await resolve(DatabaseServiceTag),
31
+ learnedSkillService: await resolve(LearnedSkillServiceTag),
32
+ socialChatHistoryService: await resolve(SocialChatHistoryServiceTag),
33
+ runtimeAdapters: await resolve(RuntimeAdaptersServiceTag),
34
+ embeddingModel: workerRuntimeConfig.aiGateway.embeddingModel,
35
+ openRouterApiKey: workerRuntimeConfig.aiGateway.openRouterApiKey,
36
+ }
12
37
 
13
38
  // One sandboxed worker handles both organization-learning branches so the
14
39
  // queue can dispatch digest and skill jobs without separate worker images.
15
- const handler = async (job: SandboxedJob<OrganizationLearningJob>) => {
16
- try {
17
- if (job.data.kind === 'regular-chat-memory-digest') {
18
- await runRegularChatMemoryDigest(job.data)
19
- return
20
- }
21
- await runSkillExtraction(job.data)
22
- } catch (error) {
23
- const message =
24
- job.data.kind === 'regular-chat-memory-digest' ? 'Regular chat memory digest failed' : 'Skill extraction failed'
25
- const serialized = toSandboxedWorkerError(error, message)
26
- serverLogger.error`${serialized.message}`
27
- throw serialized
28
- }
29
- }
40
+ const handler = (job: SandboxedJob<OrganizationLearningJob>) =>
41
+ Effect.runPromise(
42
+ Effect.gen(function* () {
43
+ if (job.data.kind === 'regular-chat-memory-digest') {
44
+ const digestJob = job.data
45
+ return yield* effectTryPromise(() => runRegularChatMemoryDigest(digestJob, regularChatDigestServices))
46
+ }
47
+
48
+ const skillJob = job.data
49
+ return yield* effectTryPromise(() => runSkillExtraction(skillJob, skillExtractionServices))
50
+ }).pipe(
51
+ Effect.catch((error) =>
52
+ Effect.gen(function* () {
53
+ const message =
54
+ job.data.kind === 'regular-chat-memory-digest'
55
+ ? 'Regular chat memory digest failed'
56
+ : 'Skill extraction failed'
57
+ const serialized = toSandboxedWorkerError(error, message)
58
+ serverLogger.error`${serialized.message}`
59
+ return yield* Effect.fail(serialized)
60
+ }),
61
+ ),
62
+ ),
63
+ )
30
64
 
31
65
  export default createTracedWorkerProcessor('organization-learning', handler)
@@ -1,40 +1,40 @@
1
1
  import { isAgentName } from '../config/agent-defaults'
2
- import { compactWhitespace } from '../utils/string'
2
+ import { compactWhitespace, readRecord, readString } from '../utils/string'
3
+ import type { DigestMessage } from './utils/thread-message-query'
3
4
 
4
- interface DigestMessageForTranscript {
5
- source: 'thread' | 'social'
6
- sourceId: string
7
- role: 'system' | 'user' | 'assistant'
8
- parts: Array<Record<string, unknown>>
9
- metadata?: Record<string, unknown>
10
- }
5
+ type DigestMessageForTranscript = Pick<DigestMessage, 'source' | 'sourceId' | 'role' | 'parts' | 'metadata'>
6
+ type DigestMessagePart = DigestMessageForTranscript['parts'][number]
11
7
 
12
- function normalizeFilePartMetadata(part: Record<string, unknown>): string | null {
8
+ function normalizeFilePartMetadata(part: DigestMessagePart): string | null {
13
9
  if (part.type !== 'file') return null
10
+ const partRecord = readRecord(part)
11
+ if (!partRecord) return null
14
12
 
15
- const filename = typeof part.filename === 'string' && part.filename.trim() ? part.filename.trim() : 'attachment'
16
- const mediaType = typeof part.mediaType === 'string' && part.mediaType.trim() ? part.mediaType.trim() : 'unknown'
17
- const storageKey = typeof part.storageKey === 'string' && part.storageKey.trim() ? part.storageKey.trim() : 'unknown'
13
+ const filename = readString(partRecord.filename) ?? 'attachment'
14
+ const mediaType = readString(partRecord.mediaType) ?? 'unknown'
15
+ const providerMetadata = readRecord(partRecord.providerMetadata)
16
+ const lotaMetadata = readRecord(providerMetadata?.lota)
17
+ const storageKey = readString(partRecord.storageKey) ?? readString(lotaMetadata?.attachmentStorageKey) ?? 'unknown'
18
18
  const sizeBytes =
19
- typeof part.sizeBytes === 'number' && Number.isFinite(part.sizeBytes) && part.sizeBytes >= 0
20
- ? Math.trunc(part.sizeBytes)
19
+ typeof partRecord.sizeBytes === 'number' && Number.isFinite(partRecord.sizeBytes) && partRecord.sizeBytes >= 0
20
+ ? Math.trunc(partRecord.sizeBytes)
21
21
  : null
22
22
 
23
23
  const sizeSegment = sizeBytes === null ? '' : `, sizeBytes=${sizeBytes}`
24
24
  return `${filename} (${mediaType}${sizeSegment}, storageKey=${storageKey})`
25
25
  }
26
26
 
27
- function extractAssistantLabel(message: DigestMessageForTranscript): string {
27
+ function extractAssistantLabel(
28
+ message: DigestMessageForTranscript,
29
+ isKnownAgentName: (value: string) => boolean = isAgentName,
30
+ ): string {
28
31
  const metadataAgentId =
29
- message.metadata && typeof message.metadata.agentId === 'string'
30
- ? message.metadata.agentId.trim().toLowerCase()
31
- : ''
32
- if (metadataAgentId && isAgentName(metadataAgentId)) {
32
+ typeof message.metadata?.agentId === 'string' ? message.metadata.agentId.trim().toLowerCase() : ''
33
+ if (metadataAgentId && isKnownAgentName(metadataAgentId)) {
33
34
  return metadataAgentId
34
35
  }
35
36
 
36
- const metadataAgentName =
37
- message.metadata && typeof message.metadata.agentName === 'string' ? message.metadata.agentName.trim() : ''
37
+ const metadataAgentName = typeof message.metadata?.agentName === 'string' ? message.metadata.agentName.trim() : ''
38
38
  if (metadataAgentName) {
39
39
  return metadataAgentName
40
40
  }
@@ -42,12 +42,13 @@ function extractAssistantLabel(message: DigestMessageForTranscript): string {
42
42
  return 'assistant'
43
43
  }
44
44
 
45
- export function buildDigestTranscript(params: { messages: DigestMessageForTranscript[] }): {
46
- transcript: string
47
- involvedAgentNames: string[]
48
- } {
45
+ export function buildDigestTranscript(params: {
46
+ messages: DigestMessageForTranscript[]
47
+ isKnownAgentName?: (value: string) => boolean
48
+ }): { transcript: string; involvedAgentNames: string[] } {
49
49
  const lines: string[] = []
50
50
  const involvedAgentNames = new Set<string>()
51
+ const isKnownAgentName = params.isKnownAgentName ?? isAgentName
51
52
 
52
53
  for (const message of params.messages) {
53
54
  if (message.role !== 'user' && message.role !== 'assistant') continue
@@ -70,8 +71,8 @@ export function buildDigestTranscript(params: { messages: DigestMessageForTransc
70
71
  continue
71
72
  }
72
73
 
73
- const assistantLabel = extractAssistantLabel(message)
74
- if (isAgentName(assistantLabel)) {
74
+ const assistantLabel = extractAssistantLabel(message, isKnownAgentName)
75
+ if (isKnownAgentName(assistantLabel)) {
75
76
  involvedAgentNames.add(assistantLabel)
76
77
  }
77
78
 
@@ -1,21 +1,25 @@
1
+ import { Effect, Schema } from 'effect'
2
+ import type { Context } from 'effect'
1
3
  import { BoundQuery } from 'surrealdb'
2
4
  import { z } from 'zod'
3
5
 
4
6
  import { serverLogger } from '../config/logger'
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'
11
+ import { effectTryPromise } from '../effect/helpers'
9
12
  import {
10
13
  clearRegularChatMemoryDigestDeduplicationKey,
11
14
  enqueueRegularChatMemoryDigest,
12
15
  } from '../queues/organization-learning.queue'
13
16
  import type { RegularChatMemoryDigestJob } from '../queues/organization-learning.queue'
14
17
  import { createHelperModelRuntime } from '../runtime/helper-model'
15
- import { getRuntimeAdapters, withConfiguredWorkspaceMemoryLock } from '../runtime/runtime-extensions'
16
- import { memoryService } from '../services/memory.service'
17
- import { socialChatHistoryService } from '../services/social-chat-history.service'
18
+ import type { LotaRuntimeAdapters, LotaRuntimeBackgroundCursor } from '../runtime/runtime-extensions'
19
+ import type { MemoryServiceTag } from '../services/memory/memory.service'
20
+ import type { SocialChatHistoryServiceTag } from '../services/social-chat-history.service'
18
21
  import { createRegularChatMemoryDigestAgent } from '../system-agents/regular-chat-memory-digest.agent'
22
+ import { nowIsoDateTimeString } from '../utils/date-time'
19
23
  import { compactWhitespace } from '../utils/string'
20
24
  import { buildDigestTranscript, resolveWorkspaceBootstrapCutoff } from './regular-chat-memory-digest.helpers'
21
25
  import {
@@ -24,15 +28,26 @@ import {
24
28
  listThreadIdsForOrg,
25
29
  normalizeBlock,
26
30
  } from './utils/thread-message-query'
27
- import type { DigestCursor, DigestMessage } from './utils/thread-message-query'
31
+ import type { DigestMessage } from './utils/thread-message-query'
28
32
 
29
33
  // Onboarding extracts memory immediately inside the turn flow. This delayed
30
34
  // runner handles the regular-chat path after onboarding so longer transcripts
31
35
  // can be digested into durable memory and profile projections in the background.
32
36
  const StructuredProfilePatchSchema = z.record(z.string(), z.unknown()).default({})
33
37
 
38
+ class MemoryDigestError extends Schema.TaggedErrorClass<MemoryDigestError>()('MemoryDigestError', {
39
+ message: Schema.String,
40
+ cause: Schema.Defect,
41
+ }) {}
42
+
34
43
  const REGULAR_CHAT_MEMORY_DIGEST_TIMEOUT_MS = 10 * 60 * 1000
35
44
  const WorkspaceMemoryRowSchema = z.object({ content: z.string() })
45
+ export interface RegularChatDigestServices {
46
+ databaseService: SurrealDBService
47
+ memoryService: Context.Service.Shape<typeof MemoryServiceTag>
48
+ socialChatHistoryService: Context.Service.Shape<typeof SocialChatHistoryServiceTag>
49
+ runtimeAdapters: LotaRuntimeAdapters
50
+ }
36
51
 
37
52
  const ExtractedFactSchema = z.object({
38
53
  content: z.string().trim().min(1),
@@ -96,109 +111,124 @@ function buildPrompt(params: {
96
111
  ].join('\n')
97
112
  }
98
113
 
99
- function getLastCursor(messages: DigestMessage[]): DigestCursor | null {
114
+ function getLastCursor(messages: DigestMessage[]): LotaRuntimeBackgroundCursor | null {
100
115
  return messages.length > 0 ? messages[messages.length - 1].cursor : null
101
116
  }
102
117
 
103
- async function hasNewEligibleThreadMessages(params: {
118
+ function hasNewEligibleThreadMessages(params: {
119
+ db: SurrealDBService
104
120
  threadIds: RecordIdRef[]
105
- cursor: DigestCursor | null
121
+ cursor: LotaRuntimeBackgroundCursor | null
106
122
  onboardingCutoff: Date | null
107
123
  }): Promise<boolean> {
108
- if (params.threadIds.length === 0) return false
109
-
110
- let query: BoundQuery | null = null
111
- if (params.cursor) {
112
- const cursorRowId = ensureRecordId(params.cursor.id, TABLES.THREAD_MESSAGE)
113
- query = new BoundQuery(
114
- `SELECT id, createdAt FROM ${TABLES.THREAD_MESSAGE}
115
- WHERE threadId IN $threadIds
116
- AND (
117
- createdAt > $cursorCreatedAt
118
- OR (createdAt = $cursorCreatedAt AND id > $cursorRowId)
119
- )
120
- ORDER BY createdAt ASC, id ASC
121
- LIMIT 1`,
122
- { threadIds: params.threadIds, cursorCreatedAt: params.cursor.createdAt, cursorRowId },
123
- )
124
- } else if (params.onboardingCutoff) {
125
- query = new BoundQuery(
126
- `SELECT id, createdAt FROM ${TABLES.THREAD_MESSAGE}
127
- WHERE threadId IN $threadIds
128
- AND createdAt > $onboardingCutoff
129
- ORDER BY createdAt ASC, id ASC
130
- LIMIT 1`,
131
- { threadIds: params.threadIds, onboardingCutoff: params.onboardingCutoff },
132
- )
133
- }
124
+ return Effect.runPromise(
125
+ Effect.gen(function* () {
126
+ if (params.threadIds.length === 0) return false
127
+
128
+ const query: BoundQuery | null = params.cursor
129
+ ? new BoundQuery(
130
+ `SELECT id, createdAt FROM ${TABLES.THREAD_MESSAGE}
131
+ WHERE threadId IN $threadIds
132
+ AND (
133
+ createdAt > $cursorCreatedAt
134
+ OR (createdAt = $cursorCreatedAt AND id > $cursorRowId)
135
+ )
136
+ ORDER BY createdAt ASC, id ASC
137
+ LIMIT 1`,
138
+ {
139
+ threadIds: params.threadIds,
140
+ cursorCreatedAt: params.cursor.createdAt,
141
+ cursorRowId: ensureRecordId(params.cursor.id, TABLES.THREAD_MESSAGE),
142
+ },
143
+ )
144
+ : params.onboardingCutoff
145
+ ? new BoundQuery(
146
+ `SELECT id, createdAt FROM ${TABLES.THREAD_MESSAGE}
147
+ WHERE threadId IN $threadIds
148
+ AND createdAt > $onboardingCutoff
149
+ ORDER BY createdAt ASC, id ASC
150
+ LIMIT 1`,
151
+ { threadIds: params.threadIds, onboardingCutoff: params.onboardingCutoff },
152
+ )
153
+ : null
134
154
 
135
- if (!query) return false
136
- const rows = await databaseService.query<unknown>(query)
137
- return rows.length > 0
155
+ if (!query) return false
156
+ const rows = yield* effectTryPromise(() => params.db.query<unknown>(query))
157
+ return rows.length > 0
158
+ }),
159
+ )
138
160
  }
139
161
 
140
- async function loadExistingOrganizationMemories(orgId: string): Promise<Array<{ content: string }>> {
141
- return databaseService.queryMany(
142
- new BoundQuery(
143
- `SELECT content, createdAt, id FROM ${TABLES.MEMORY}
144
- WHERE metadata.orgId = $orgId
145
- AND archivedAt IS NONE
146
- AND (validUntil IS NONE OR validUntil > time::now())
147
- ORDER BY createdAt DESC, id DESC
148
- LIMIT 250`,
149
- { orgId },
162
+ function loadExistingOrganizationMemories(db: SurrealDBService, orgId: string): Promise<Array<{ content: string }>> {
163
+ return Effect.runPromise(
164
+ effectTryPromise(() =>
165
+ db.queryMany(
166
+ new BoundQuery(
167
+ `SELECT content, createdAt, id FROM ${TABLES.MEMORY}
168
+ WHERE metadata.orgId = $orgId
169
+ AND archivedAt IS NONE
170
+ AND (validUntil IS NONE OR validUntil > time::now())
171
+ ORDER BY createdAt DESC, id DESC
172
+ LIMIT 250`,
173
+ { orgId },
174
+ ),
175
+ WorkspaceMemoryRowSchema,
176
+ ),
150
177
  ),
151
- WorkspaceMemoryRowSchema,
152
178
  )
153
179
  }
154
180
 
155
- export async function runRegularChatMemoryDigest(
156
- data: RegularChatMemoryDigestJob,
157
- ): Promise<RegularChatDigestRunResult> {
158
- const orgRef = ensureRecordId(data.orgId, TABLES.ORGANIZATION)
159
- const orgId = recordIdToString(orgRef, TABLES.ORGANIZATION)
160
- const workspaceProvider = getRuntimeAdapters().workspaceProvider
161
- if (!workspaceProvider) {
162
- serverLogger.info`Skipping regular chat memory digest for ${orgId}: workspaceProvider is not configured`
163
- return { skipped: true, processedThreadMessages: 0, processedSocialMessages: 0, followUpScheduled: false }
164
- }
165
-
166
- return withConfiguredWorkspaceMemoryLock(orgId, async () => {
167
- if (
168
- !workspaceProvider.getBackgroundCursor ||
169
- !workspaceProvider.setBackgroundCursor ||
170
- !workspaceProvider.applyProfileProjection
171
- ) {
181
+ function runRegularChatMemoryDigestEffect(
182
+ services: RegularChatDigestServices,
183
+ orgRef: RecordIdRef,
184
+ orgId: string,
185
+ workspaceProvider: NonNullable<LotaRuntimeAdapters['workspaceProvider']>,
186
+ ) {
187
+ return Effect.gen(function* () {
188
+ const getBackgroundCursor = workspaceProvider.getBackgroundCursor?.bind(workspaceProvider)
189
+ const setBackgroundCursor = workspaceProvider.setBackgroundCursor?.bind(workspaceProvider)
190
+ const applyProfileProjection = workspaceProvider.applyProfileProjection?.bind(workspaceProvider)
191
+ if (!getBackgroundCursor || !setBackgroundCursor || !applyProfileProjection) {
172
192
  serverLogger.info`Skipping regular chat memory digest for ${orgId}: workspaceProvider background/profile methods are incomplete`
173
193
  return { skipped: true, processedThreadMessages: 0, processedSocialMessages: 0, followUpScheduled: false }
174
194
  }
175
195
 
176
- const workspace = await workspaceProvider.getWorkspace(orgRef)
177
- const lifecycleState = await workspaceProvider.getLifecycleState?.(workspace)
196
+ const workspace = yield* effectTryPromise(() => workspaceProvider.getWorkspace(orgRef))
197
+ const lifecycleState = workspaceProvider.getLifecycleState
198
+ ? yield* effectTryPromise(() => Promise.resolve(workspaceProvider.getLifecycleState?.(workspace)))
199
+ : undefined
178
200
  if (lifecycleState?.bootstrapActive ?? false) {
179
201
  serverLogger.info`Skipping regular chat memory digest for ${orgId}: onboarding is not completed`
180
202
  return { skipped: true, processedThreadMessages: 0, processedSocialMessages: 0, followUpScheduled: false }
181
203
  }
182
- const projectionState = await workspaceProvider.readProfileProjectionState?.(workspace)
204
+ const projectionState = workspaceProvider.readProfileProjectionState
205
+ ? yield* effectTryPromise(() => Promise.resolve(workspaceProvider.readProfileProjectionState?.(workspace)))
206
+ : undefined
183
207
 
184
- const existingThreadCursor = await workspaceProvider.getBackgroundCursor('regular-chat-digest', orgRef)
208
+ const existingThreadCursor = yield* effectTryPromise(() => getBackgroundCursor('regular-chat-digest', orgRef))
185
209
  const threadOnboardingCutoff = resolveWorkspaceBootstrapCutoff({
186
210
  hasExistingCursor: existingThreadCursor !== null,
187
211
  bootstrapCompletedAt: lifecycleState?.bootstrapCompletedAt,
188
212
  })
189
213
 
190
- const threadIds = await listThreadIdsForOrg(orgRef)
191
- const threadMessages = await listEligibleThreadMessages({
192
- threadIds,
193
- cursor: existingThreadCursor,
194
- onboardingCutoff: threadOnboardingCutoff,
195
- })
196
- const existingSocialCursor = await socialChatHistoryService.getBackgroundCursor('regular-chat-digest', orgId)
214
+ const threadIds = yield* effectTryPromise(() => listThreadIdsForOrg(services.databaseService, orgRef))
215
+ const threadMessages = yield* effectTryPromise(() =>
216
+ listEligibleThreadMessages({
217
+ db: services.databaseService,
218
+ threadIds,
219
+ cursor: existingThreadCursor,
220
+ onboardingCutoff: threadOnboardingCutoff,
221
+ }),
222
+ )
223
+ const existingSocialCursor = yield* services.socialChatHistoryService.getBackgroundCursor(
224
+ 'regular-chat-digest',
225
+ orgId,
226
+ )
197
227
  const socialOnboardingCutoff = resolveWorkspaceBootstrapCutoff({
198
228
  hasExistingCursor: existingSocialCursor !== null,
199
229
  bootstrapCompletedAt: lifecycleState?.bootstrapCompletedAt,
200
230
  })
201
- const socialMessages = await socialChatHistoryService.listWorkspaceMessages({
231
+ const socialMessages = yield* services.socialChatHistoryService.listWorkspaceMessages({
202
232
  workspaceId: orgId,
203
233
  cursor: existingSocialCursor,
204
234
  onboardingCutoff: socialOnboardingCutoff,
@@ -211,38 +241,45 @@ export async function runRegularChatMemoryDigest(
211
241
 
212
242
  const combinedMessages = [...threadMessages, ...socialMessages].sort(compareDigestMessageOrder)
213
243
  const { transcript, involvedAgentNames } = buildDigestTranscript({ messages: combinedMessages })
214
- const existingMemories = await loadExistingOrganizationMemories(orgId)
215
-
216
- const synthesis = await helperModelRuntime.generateHelperStructured({
217
- tag: 'regular-chat-memory-digest',
218
- createAgent: createRegularChatMemoryDigestAgent,
219
- timeoutMs: REGULAR_CHAT_MEMORY_DIGEST_TIMEOUT_MS,
220
- messages: [
221
- {
222
- role: 'user',
223
- content: buildPrompt({
224
- workspaceName: projectionState?.workspaceName || 'Workspace',
225
- currentSummaryBlock: projectionState?.summaryBlock ?? '',
226
- currentStructuredProfile: JSON.stringify(projectionState?.structuredProfile ?? {}, null, 2),
227
- existingMemories: buildMemoryContext(existingMemories),
228
- transcript,
229
- }),
230
- },
231
- ],
232
- schema: RegularChatMemoryDigestOutputSchema,
233
- })
244
+ const existingMemories = yield* effectTryPromise(() =>
245
+ loadExistingOrganizationMemories(services.databaseService, orgId),
246
+ )
247
+
248
+ const synthesis = yield* effectTryPromise(() =>
249
+ helperModelRuntime.generateHelperStructured({
250
+ tag: 'regular-chat-memory-digest',
251
+ createAgent: createRegularChatMemoryDigestAgent,
252
+ timeoutMs: REGULAR_CHAT_MEMORY_DIGEST_TIMEOUT_MS,
253
+ messages: [
254
+ {
255
+ role: 'user',
256
+ content: buildPrompt({
257
+ workspaceName: projectionState?.workspaceName || 'Workspace',
258
+ currentSummaryBlock: projectionState?.summaryBlock ?? '',
259
+ currentStructuredProfile: JSON.stringify(projectionState?.structuredProfile ?? {}, null, 2),
260
+ existingMemories: buildMemoryContext(existingMemories),
261
+ transcript,
262
+ }),
263
+ },
264
+ ],
265
+ schema: RegularChatMemoryDigestOutputSchema,
266
+ }),
267
+ )
234
268
 
235
269
  const summaryBlock = normalizeBlock(synthesis.summaryBlock)
236
270
  if (!summaryBlock) {
237
- throw new Error('Regular chat memory digest returned an empty summaryBlock')
271
+ return yield* new MemoryDigestError({
272
+ message: 'Regular chat memory digest returned an empty summaryBlock',
273
+ cause: new Error('Regular chat memory digest returned an empty summaryBlock'),
274
+ })
238
275
  }
239
276
 
240
277
  const processedThreadCursor = getLastCursor(threadMessages)
241
278
  const processedSocialCursor = getLastCursor(socialMessages)
242
- const digestRunAt = new Date().toISOString()
279
+ const digestRunAt = nowIsoDateTimeString()
243
280
 
244
281
  if (synthesis.facts.length > 0) {
245
- await memoryService.addExtractedFactsToScopes({
282
+ yield* services.memoryService.addExtractedFactsToScopes({
246
283
  orgId,
247
284
  facts: synthesis.facts,
248
285
  source: 'regular_chat_digest',
@@ -266,25 +303,27 @@ export async function runRegularChatMemoryDigest(
266
303
  })
267
304
  }
268
305
 
269
- await workspaceProvider.applyProfileProjection(orgRef, {
270
- summaryBlock,
271
- structuredPatch: synthesis.structuredProfilePatch,
272
- })
306
+ yield* effectTryPromise(() =>
307
+ applyProfileProjection(orgRef, { summaryBlock, structuredPatch: synthesis.structuredProfilePatch }),
308
+ )
273
309
  if (processedThreadCursor) {
274
- await workspaceProvider.setBackgroundCursor('regular-chat-digest', orgRef, processedThreadCursor)
310
+ yield* effectTryPromise(() => setBackgroundCursor('regular-chat-digest', orgRef, processedThreadCursor))
275
311
  }
276
312
  if (processedSocialCursor) {
277
- await socialChatHistoryService.setBackgroundCursor('regular-chat-digest', orgId, processedSocialCursor)
313
+ yield* services.socialChatHistoryService.setBackgroundCursor('regular-chat-digest', orgId, processedSocialCursor)
278
314
  }
279
315
 
280
316
  const threadBoundaryCursor = processedThreadCursor ?? existingThreadCursor
281
- const hasMoreThreadMessages = await hasNewEligibleThreadMessages({
282
- threadIds,
283
- cursor: threadBoundaryCursor,
284
- onboardingCutoff: threadBoundaryCursor ? null : threadOnboardingCutoff,
285
- })
317
+ const hasMoreThreadMessages = yield* effectTryPromise(() =>
318
+ hasNewEligibleThreadMessages({
319
+ db: services.databaseService,
320
+ threadIds,
321
+ cursor: threadBoundaryCursor,
322
+ onboardingCutoff: threadBoundaryCursor ? null : threadOnboardingCutoff,
323
+ }),
324
+ )
286
325
  const socialBoundaryCursor = processedSocialCursor ?? existingSocialCursor
287
- const hasMoreSocialMessages = await socialChatHistoryService.hasWorkspaceMessages({
326
+ const hasMoreSocialMessages = yield* services.socialChatHistoryService.hasWorkspaceMessages({
288
327
  workspaceId: orgId,
289
328
  cursor: socialBoundaryCursor,
290
329
  onboardingCutoff: socialBoundaryCursor ? null : socialOnboardingCutoff,
@@ -292,8 +331,8 @@ export async function runRegularChatMemoryDigest(
292
331
 
293
332
  const followUpScheduled = hasMoreThreadMessages || hasMoreSocialMessages
294
333
  if (followUpScheduled) {
295
- await clearRegularChatMemoryDigestDeduplicationKey(orgId)
296
- await enqueueRegularChatMemoryDigest({ orgId })
334
+ yield* effectTryPromise(() => clearRegularChatMemoryDigestDeduplicationKey(orgId))
335
+ yield* effectTryPromise(() => enqueueRegularChatMemoryDigest({ orgId }))
297
336
  }
298
337
 
299
338
  serverLogger.info`Regular chat memory digest completed for ${orgId}: threadMessages=${threadMessages.length}, socialMessages=${socialMessages.length}, facts=${synthesis.facts.length}, followUpScheduled=${followUpScheduled}`
@@ -306,3 +345,35 @@ export async function runRegularChatMemoryDigest(
306
345
  }
307
346
  })
308
347
  }
348
+
349
+ export function runRegularChatMemoryDigest(
350
+ data: RegularChatMemoryDigestJob,
351
+ services: RegularChatDigestServices,
352
+ ): Promise<RegularChatDigestRunResult> {
353
+ const { databaseService, memoryService, socialChatHistoryService, runtimeAdapters } = services
354
+ const orgRef = ensureRecordId(data.orgId, TABLES.ORGANIZATION)
355
+ const orgId = recordIdToString(orgRef, TABLES.ORGANIZATION)
356
+ const workspaceProvider = runtimeAdapters.workspaceProvider
357
+ if (!workspaceProvider) {
358
+ serverLogger.info`Skipping regular chat memory digest for ${orgId}: workspaceProvider is not configured`
359
+ return Promise.resolve({
360
+ skipped: true,
361
+ processedThreadMessages: 0,
362
+ processedSocialMessages: 0,
363
+ followUpScheduled: false,
364
+ })
365
+ }
366
+
367
+ const withMemoryLock = runtimeAdapters.withWorkspaceMemoryLock
368
+ const runDigest = () =>
369
+ Effect.runPromise(
370
+ runRegularChatMemoryDigestEffect(
371
+ { databaseService, memoryService, socialChatHistoryService, runtimeAdapters },
372
+ orgRef,
373
+ orgId,
374
+ workspaceProvider,
375
+ ),
376
+ )
377
+
378
+ return withMemoryLock ? withMemoryLock(orgId, runDigest) : runDigest()
379
+ }