@lota-sdk/core 0.1.5

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 (153) hide show
  1. package/infrastructure/schema/00_workstream.surql +55 -0
  2. package/infrastructure/schema/01_memory.surql +47 -0
  3. package/infrastructure/schema/02_execution_plan.surql +62 -0
  4. package/infrastructure/schema/03_learned_skill.surql +32 -0
  5. package/infrastructure/schema/04_runtime_bootstrap.surql +8 -0
  6. package/package.json +128 -0
  7. package/src/ai/definitions.ts +308 -0
  8. package/src/bifrost/bifrost.ts +256 -0
  9. package/src/config/agent-defaults.ts +99 -0
  10. package/src/config/constants.ts +33 -0
  11. package/src/config/env-shapes.ts +122 -0
  12. package/src/config/logger.ts +29 -0
  13. package/src/config/model-constants.ts +31 -0
  14. package/src/config/search.ts +17 -0
  15. package/src/config/workstream-defaults.ts +68 -0
  16. package/src/db/base.service.ts +55 -0
  17. package/src/db/cursor-pagination.ts +73 -0
  18. package/src/db/memory-query-builder.ts +207 -0
  19. package/src/db/memory-store.helpers.ts +118 -0
  20. package/src/db/memory-store.rows.ts +29 -0
  21. package/src/db/memory-store.ts +974 -0
  22. package/src/db/memory-types.ts +193 -0
  23. package/src/db/memory.ts +505 -0
  24. package/src/db/record-id.ts +78 -0
  25. package/src/db/service.ts +932 -0
  26. package/src/db/startup.ts +152 -0
  27. package/src/db/tables.ts +20 -0
  28. package/src/document/org-document-chunking.ts +224 -0
  29. package/src/document/parsing.ts +40 -0
  30. package/src/embeddings/provider.ts +76 -0
  31. package/src/index.ts +302 -0
  32. package/src/queues/context-compaction.queue.ts +82 -0
  33. package/src/queues/document-processor.queue.ts +118 -0
  34. package/src/queues/memory-consolidation.queue.ts +65 -0
  35. package/src/queues/post-chat-memory.queue.ts +128 -0
  36. package/src/queues/recent-activity-title-refinement.queue.ts +69 -0
  37. package/src/queues/regular-chat-memory-digest.config.ts +12 -0
  38. package/src/queues/regular-chat-memory-digest.queue.ts +73 -0
  39. package/src/queues/skill-extraction.config.ts +9 -0
  40. package/src/queues/skill-extraction.queue.ts +62 -0
  41. package/src/redis/connection.ts +176 -0
  42. package/src/redis/index.ts +30 -0
  43. package/src/redis/org-memory-lock.ts +43 -0
  44. package/src/redis/redis-lease-lock.ts +158 -0
  45. package/src/runtime/agent-contract.ts +1 -0
  46. package/src/runtime/agent-prompt-context.ts +119 -0
  47. package/src/runtime/agent-runtime-policy.ts +192 -0
  48. package/src/runtime/agent-stream-helpers.ts +117 -0
  49. package/src/runtime/agent-types.ts +22 -0
  50. package/src/runtime/approval-continuation.ts +16 -0
  51. package/src/runtime/chat-attachments.ts +46 -0
  52. package/src/runtime/chat-message.ts +10 -0
  53. package/src/runtime/chat-request-routing.ts +21 -0
  54. package/src/runtime/chat-run-orchestration.ts +25 -0
  55. package/src/runtime/chat-run-registry.ts +20 -0
  56. package/src/runtime/chat-types.ts +18 -0
  57. package/src/runtime/context-compaction-constants.ts +11 -0
  58. package/src/runtime/context-compaction-runtime.ts +86 -0
  59. package/src/runtime/context-compaction.ts +909 -0
  60. package/src/runtime/execution-plan.ts +59 -0
  61. package/src/runtime/helper-model.ts +405 -0
  62. package/src/runtime/indexed-repositories-policy.ts +28 -0
  63. package/src/runtime/instruction-sections.ts +8 -0
  64. package/src/runtime/llm-content.ts +71 -0
  65. package/src/runtime/memory-block.ts +264 -0
  66. package/src/runtime/memory-digest-policy.ts +14 -0
  67. package/src/runtime/memory-format.ts +8 -0
  68. package/src/runtime/memory-pipeline.ts +570 -0
  69. package/src/runtime/memory-prompts-fact.ts +47 -0
  70. package/src/runtime/memory-prompts-parse.ts +3 -0
  71. package/src/runtime/memory-prompts-update.ts +37 -0
  72. package/src/runtime/memory-scope.ts +43 -0
  73. package/src/runtime/plugin-types.ts +10 -0
  74. package/src/runtime/retrieval-adapters.ts +25 -0
  75. package/src/runtime/retrieval-pipeline.ts +3 -0
  76. package/src/runtime/runtime-extensions.ts +154 -0
  77. package/src/runtime/skill-extraction-policy.ts +3 -0
  78. package/src/runtime/team-consultation-orchestrator.ts +245 -0
  79. package/src/runtime/team-consultation-prompts.ts +32 -0
  80. package/src/runtime/title-helpers.ts +12 -0
  81. package/src/runtime/turn-lifecycle.ts +28 -0
  82. package/src/runtime/workstream-chat-helpers.ts +187 -0
  83. package/src/runtime/workstream-routing-policy.ts +301 -0
  84. package/src/runtime/workstream-state.ts +261 -0
  85. package/src/services/attachment.service.ts +159 -0
  86. package/src/services/chat-attachments.service.ts +17 -0
  87. package/src/services/chat-run-registry.service.ts +3 -0
  88. package/src/services/context-compaction-runtime.ts +13 -0
  89. package/src/services/context-compaction.service.ts +115 -0
  90. package/src/services/document-chunk.service.ts +141 -0
  91. package/src/services/execution-plan.service.ts +890 -0
  92. package/src/services/learned-skill.service.ts +328 -0
  93. package/src/services/memory-assessment.service.ts +43 -0
  94. package/src/services/memory.service.ts +807 -0
  95. package/src/services/memory.utils.ts +84 -0
  96. package/src/services/mutating-approval.service.ts +110 -0
  97. package/src/services/recent-activity-title.service.ts +74 -0
  98. package/src/services/recent-activity.service.ts +397 -0
  99. package/src/services/workstream-change-tracker.service.ts +313 -0
  100. package/src/services/workstream-message.service.ts +283 -0
  101. package/src/services/workstream-title.service.ts +58 -0
  102. package/src/services/workstream-turn-preparation.ts +1340 -0
  103. package/src/services/workstream-turn.ts +37 -0
  104. package/src/services/workstream.service.ts +854 -0
  105. package/src/services/workstream.types.ts +118 -0
  106. package/src/storage/attachment-parser.ts +101 -0
  107. package/src/storage/attachment-storage.service.ts +391 -0
  108. package/src/storage/attachments.types.ts +11 -0
  109. package/src/storage/attachments.utils.ts +58 -0
  110. package/src/storage/generated-document-storage.service.ts +55 -0
  111. package/src/system-agents/agent-result.ts +27 -0
  112. package/src/system-agents/context-compacter.agent.ts +46 -0
  113. package/src/system-agents/delegated-agent-factory.ts +177 -0
  114. package/src/system-agents/helper-agent-options.ts +20 -0
  115. package/src/system-agents/memory-reranker.agent.ts +38 -0
  116. package/src/system-agents/memory.agent.ts +58 -0
  117. package/src/system-agents/recent-activity-title-refiner.agent.ts +53 -0
  118. package/src/system-agents/regular-chat-memory-digest.agent.ts +75 -0
  119. package/src/system-agents/researcher.agent.ts +34 -0
  120. package/src/system-agents/skill-extractor.agent.ts +88 -0
  121. package/src/system-agents/skill-manager.agent.ts +80 -0
  122. package/src/system-agents/title-generator.agent.ts +42 -0
  123. package/src/system-agents/workstream-tracker.agent.ts +58 -0
  124. package/src/tools/execution-plan.tool.ts +163 -0
  125. package/src/tools/fetch-webpage.tool.ts +132 -0
  126. package/src/tools/firecrawl-client.ts +12 -0
  127. package/src/tools/memory-block.tool.ts +55 -0
  128. package/src/tools/read-file-parts.tool.ts +80 -0
  129. package/src/tools/remember-memory.tool.ts +85 -0
  130. package/src/tools/research-topic.tool.ts +15 -0
  131. package/src/tools/search-tools.ts +55 -0
  132. package/src/tools/search-web.tool.ts +175 -0
  133. package/src/tools/team-think.tool.ts +125 -0
  134. package/src/tools/tool-contract.ts +21 -0
  135. package/src/tools/user-questions.tool.ts +18 -0
  136. package/src/utils/async.ts +50 -0
  137. package/src/utils/date-time.ts +34 -0
  138. package/src/utils/error.ts +10 -0
  139. package/src/utils/errors.ts +28 -0
  140. package/src/utils/hono-error-handler.ts +71 -0
  141. package/src/utils/string.ts +51 -0
  142. package/src/workers/bootstrap.ts +44 -0
  143. package/src/workers/memory-consolidation.worker.ts +318 -0
  144. package/src/workers/regular-chat-memory-digest.helpers.ts +100 -0
  145. package/src/workers/regular-chat-memory-digest.runner.ts +363 -0
  146. package/src/workers/regular-chat-memory-digest.worker.ts +22 -0
  147. package/src/workers/skill-extraction.runner.ts +331 -0
  148. package/src/workers/skill-extraction.worker.ts +22 -0
  149. package/src/workers/utils/repo-indexer-chunker.ts +331 -0
  150. package/src/workers/utils/repo-structure-extractor.ts +645 -0
  151. package/src/workers/utils/repomix-process-concurrency.ts +65 -0
  152. package/src/workers/utils/sandbox-error.ts +5 -0
  153. package/src/workers/worker-utils.ts +182 -0
@@ -0,0 +1,363 @@
1
+ import { toTimestamp } from '@lota-sdk/shared/runtime/chat-message-metadata'
2
+ import { BoundQuery } from 'surrealdb'
3
+ import { z } from 'zod'
4
+
5
+ import { serverLogger } from '../config/logger'
6
+ import { ensureRecordId, recordIdToString } from '../db/record-id'
7
+ import type { RecordIdRef } from '../db/record-id'
8
+ import { databaseService } from '../db/service'
9
+ import { TABLES } from '../db/tables'
10
+ import {
11
+ clearRegularChatMemoryDigestDeduplicationKey,
12
+ enqueueRegularChatMemoryDigest,
13
+ } from '../queues/regular-chat-memory-digest.queue'
14
+ import type { RegularChatMemoryDigestJob } from '../queues/regular-chat-memory-digest.queue'
15
+ import { createHelperModelRuntime } from '../runtime/helper-model'
16
+ import { getRuntimeAdapters, withConfiguredWorkspaceMemoryLock } from '../runtime/runtime-extensions'
17
+ import { memoryService } from '../services/memory.service'
18
+ import { createRegularChatMemoryDigestAgent } from '../system-agents/regular-chat-memory-digest.agent'
19
+ import { buildDigestTranscript, resolveWorkspaceBootstrapCutoff } from './regular-chat-memory-digest.helpers'
20
+
21
+ const StructuredProfilePatchSchema = z.record(z.string(), z.unknown()).default({})
22
+
23
+ const REGULAR_CHAT_MEMORY_DIGEST_TIMEOUT_MS = 10 * 60 * 1000
24
+ const WorkspaceMemoryRowSchema = z.object({ content: z.string() })
25
+ const EntityIdRowSchema = z.string().trim().min(1)
26
+ const RecordTimestampSchema = z.union([z.date(), z.string(), z.number()])
27
+ const MessageRoleSchema = z.enum(['system', 'user', 'assistant'])
28
+ const MessagePartSchema = z.record(z.string(), z.unknown())
29
+ const MessageMetadataSchema = z.record(z.string(), z.unknown()).nullish()
30
+
31
+ const WorkstreamDigestMessageRowSchema = z.object({
32
+ id: z.string(),
33
+ workstreamId: z.string(),
34
+ role: MessageRoleSchema,
35
+ parts: z.array(MessagePartSchema).optional(),
36
+ metadata: MessageMetadataSchema,
37
+ createdAt: RecordTimestampSchema,
38
+ })
39
+
40
+ const ExtractedFactSchema = z.object({
41
+ content: z.string().trim().min(1),
42
+ type: z.enum(['fact', 'preference', 'decision']),
43
+ confidence: z.number().min(0).max(1),
44
+ durability: z.enum(['core', 'standard', 'ephemeral']),
45
+ })
46
+
47
+ const RegularChatMemoryDigestOutputSchema = z.object({
48
+ summaryBlock: z.string().trim().min(1),
49
+ structuredProfilePatch: StructuredProfilePatchSchema,
50
+ facts: z.array(ExtractedFactSchema).default([]),
51
+ })
52
+
53
+ const helperModelRuntime = createHelperModelRuntime()
54
+
55
+ interface DigestCursor {
56
+ createdAt: Date
57
+ id: string
58
+ }
59
+
60
+ interface DigestMessage {
61
+ source: 'workstream'
62
+ sourceId: string
63
+ role: 'system' | 'user' | 'assistant'
64
+ parts: Array<Record<string, unknown>>
65
+ metadata?: Record<string, unknown>
66
+ cursor: DigestCursor
67
+ }
68
+
69
+ interface RegularChatDigestRunResult {
70
+ skipped: boolean
71
+ processedWorkstreamMessages: number
72
+ followUpScheduled: boolean
73
+ }
74
+
75
+ function normalizeWhitespace(value: string): string {
76
+ return value.replace(/\s+/g, ' ').trim()
77
+ }
78
+
79
+ function normalizeBlock(value: string): string {
80
+ return value.replaceAll(String.fromCharCode(0), '').replace(/\r/g, '').trim()
81
+ }
82
+
83
+ function buildMemoryContext(memories: Array<{ content: string }>): string {
84
+ if (memories.length === 0) return 'No existing memories.'
85
+
86
+ return memories
87
+ .map((memory, index) => {
88
+ const content = normalizeWhitespace(memory.content)
89
+ if (!content) return ''
90
+ return `${index + 1}. ${content}`
91
+ })
92
+ .filter((line) => line.length > 0)
93
+ .join('\n')
94
+ }
95
+
96
+ function buildPrompt(params: {
97
+ workspaceName: string
98
+ currentSummaryBlock: string
99
+ currentStructuredProfile: string
100
+ existingMemories: string
101
+ transcript: string
102
+ }): string {
103
+ return [
104
+ `Workspace name: ${params.workspaceName}`,
105
+ '',
106
+ 'Current workspace profile summary block:',
107
+ params.currentSummaryBlock || 'No current workspace profile summary block.',
108
+ '',
109
+ 'Current structured workspace profile JSON:',
110
+ params.currentStructuredProfile,
111
+ '',
112
+ 'Existing durable memories:',
113
+ params.existingMemories,
114
+ '',
115
+ 'Newly added regular-chat transcript:',
116
+ params.transcript || 'No new transcript lines.',
117
+ ].join('\n')
118
+ }
119
+
120
+ function mapWorkstreamDigestRow(row: z.infer<typeof WorkstreamDigestMessageRowSchema>): DigestMessage {
121
+ return {
122
+ source: 'workstream',
123
+ sourceId: row.workstreamId,
124
+ role: row.role,
125
+ parts: row.parts ?? [],
126
+ metadata: row.metadata ?? undefined,
127
+ cursor: { createdAt: new Date(toTimestamp(row.createdAt)), id: row.id },
128
+ }
129
+ }
130
+
131
+ function compareDigestMessageOrder(left: DigestMessage, right: DigestMessage): number {
132
+ const timeDiff = left.cursor.createdAt.getTime() - right.cursor.createdAt.getTime()
133
+ if (timeDiff !== 0) return timeDiff
134
+ return left.cursor.id.localeCompare(right.cursor.id)
135
+ }
136
+
137
+ function getLastCursor(messages: DigestMessage[]): DigestCursor | null {
138
+ return messages.length > 0 ? messages[messages.length - 1].cursor : null
139
+ }
140
+
141
+ async function listWorkstreamIdsForOrg(orgRef: RecordIdRef): Promise<RecordIdRef[]> {
142
+ const ids = await databaseService.query<unknown>(
143
+ new BoundQuery(
144
+ `SELECT VALUE type::string(id) FROM ${TABLES.WORKSTREAM}
145
+ WHERE organizationId = $organizationId`,
146
+ { organizationId: orgRef },
147
+ ),
148
+ )
149
+
150
+ return ids.map((value) => ensureRecordId(EntityIdRowSchema.parse(value), TABLES.WORKSTREAM))
151
+ }
152
+
153
+ async function listEligibleWorkstreamMessages(params: {
154
+ workstreamIds: RecordIdRef[]
155
+ cursor: DigestCursor | null
156
+ onboardingCutoff: Date | null
157
+ }): Promise<DigestMessage[]> {
158
+ if (params.workstreamIds.length === 0) return []
159
+
160
+ let query: BoundQuery | null = null
161
+ if (params.cursor) {
162
+ const cursorRowId = ensureRecordId(params.cursor.id, TABLES.WORKSTREAM_MESSAGE)
163
+ query = new BoundQuery(
164
+ `SELECT type::string(id) AS id, type::string(workstreamId) AS workstreamId, role, parts, metadata, createdAt FROM ${TABLES.WORKSTREAM_MESSAGE}
165
+ WHERE workstreamId IN $workstreamIds
166
+ AND (
167
+ createdAt > $cursorCreatedAt
168
+ OR (createdAt = $cursorCreatedAt AND id > $cursorRowId)
169
+ )
170
+ ORDER BY createdAt ASC, id ASC`,
171
+ { workstreamIds: params.workstreamIds, cursorCreatedAt: params.cursor.createdAt, cursorRowId },
172
+ )
173
+ } else if (params.onboardingCutoff) {
174
+ query = new BoundQuery(
175
+ `SELECT type::string(id) AS id, type::string(workstreamId) AS workstreamId, role, parts, metadata, createdAt FROM ${TABLES.WORKSTREAM_MESSAGE}
176
+ WHERE workstreamId IN $workstreamIds
177
+ AND createdAt > $onboardingCutoff
178
+ ORDER BY createdAt ASC, id ASC`,
179
+ { workstreamIds: params.workstreamIds, onboardingCutoff: params.onboardingCutoff },
180
+ )
181
+ }
182
+
183
+ if (!query) return []
184
+
185
+ const rows = await databaseService.query<unknown>(query)
186
+ return rows.map((row) => mapWorkstreamDigestRow(WorkstreamDigestMessageRowSchema.parse(row)))
187
+ }
188
+
189
+ async function hasNewEligibleWorkstreamMessages(params: {
190
+ workstreamIds: RecordIdRef[]
191
+ cursor: DigestCursor | null
192
+ onboardingCutoff: Date | null
193
+ }): Promise<boolean> {
194
+ if (params.workstreamIds.length === 0) return false
195
+
196
+ let query: BoundQuery | null = null
197
+ if (params.cursor) {
198
+ const cursorRowId = ensureRecordId(params.cursor.id, TABLES.WORKSTREAM_MESSAGE)
199
+ query = new BoundQuery(
200
+ `SELECT id, createdAt FROM ${TABLES.WORKSTREAM_MESSAGE}
201
+ WHERE workstreamId IN $workstreamIds
202
+ AND (
203
+ createdAt > $cursorCreatedAt
204
+ OR (createdAt = $cursorCreatedAt AND id > $cursorRowId)
205
+ )
206
+ ORDER BY createdAt ASC, id ASC
207
+ LIMIT 1`,
208
+ { workstreamIds: params.workstreamIds, cursorCreatedAt: params.cursor.createdAt, cursorRowId },
209
+ )
210
+ } else if (params.onboardingCutoff) {
211
+ query = new BoundQuery(
212
+ `SELECT id, createdAt FROM ${TABLES.WORKSTREAM_MESSAGE}
213
+ WHERE workstreamId IN $workstreamIds
214
+ AND createdAt > $onboardingCutoff
215
+ ORDER BY createdAt ASC, id ASC
216
+ LIMIT 1`,
217
+ { workstreamIds: params.workstreamIds, onboardingCutoff: params.onboardingCutoff },
218
+ )
219
+ }
220
+
221
+ if (!query) return false
222
+ const rows = await databaseService.query<unknown>(query)
223
+ return rows.length > 0
224
+ }
225
+
226
+ async function loadExistingOrganizationMemories(orgId: string): Promise<Array<{ content: string }>> {
227
+ return await databaseService.queryMany(
228
+ new BoundQuery(
229
+ `SELECT content, createdAt, id FROM ${TABLES.MEMORY}
230
+ WHERE metadata.orgId = $orgId
231
+ AND archivedAt IS NONE
232
+ AND (validUntil IS NONE OR validUntil > time::now())
233
+ ORDER BY createdAt DESC, id DESC
234
+ LIMIT 250`,
235
+ { orgId },
236
+ ),
237
+ WorkspaceMemoryRowSchema,
238
+ )
239
+ }
240
+
241
+ export async function runRegularChatMemoryDigest(
242
+ data: RegularChatMemoryDigestJob,
243
+ ): Promise<RegularChatDigestRunResult> {
244
+ const orgRef = ensureRecordId(data.orgId, TABLES.ORGANIZATION)
245
+ const orgId = recordIdToString(orgRef, TABLES.ORGANIZATION)
246
+ const workspaceProvider = getRuntimeAdapters().services?.workspaceProvider
247
+ if (!workspaceProvider) {
248
+ serverLogger.info`Skipping regular chat memory digest for ${orgId}: workspaceProvider is not configured`
249
+ return { skipped: true, processedWorkstreamMessages: 0, followUpScheduled: false }
250
+ }
251
+
252
+ return await withConfiguredWorkspaceMemoryLock(orgId, async () => {
253
+ if (
254
+ !workspaceProvider.getBackgroundCursor ||
255
+ !workspaceProvider.setBackgroundCursor ||
256
+ !workspaceProvider.applyProfileProjection
257
+ ) {
258
+ serverLogger.info`Skipping regular chat memory digest for ${orgId}: workspaceProvider background/profile methods are incomplete`
259
+ return { skipped: true, processedWorkstreamMessages: 0, followUpScheduled: false }
260
+ }
261
+
262
+ const workspace = await workspaceProvider.getWorkspace(orgRef)
263
+ const lifecycleState = await workspaceProvider.getLifecycleState?.(workspace)
264
+ if (lifecycleState?.bootstrapActive ?? false) {
265
+ serverLogger.info`Skipping regular chat memory digest for ${orgId}: onboarding is not completed`
266
+ return { skipped: true, processedWorkstreamMessages: 0, followUpScheduled: false }
267
+ }
268
+ const projectionState = await workspaceProvider.readProfileProjectionState?.(workspace)
269
+
270
+ const existingWorkstreamCursor = await workspaceProvider.getBackgroundCursor('regular-chat-digest', orgRef)
271
+ const workstreamOnboardingCutoff = resolveWorkspaceBootstrapCutoff({
272
+ hasExistingCursor: existingWorkstreamCursor !== null,
273
+ bootstrapCompletedAt: lifecycleState?.bootstrapCompletedAt,
274
+ })
275
+
276
+ const workstreamIds = await listWorkstreamIdsForOrg(orgRef)
277
+ const workstreamMessages = await listEligibleWorkstreamMessages({
278
+ workstreamIds,
279
+ cursor: existingWorkstreamCursor,
280
+ onboardingCutoff: workstreamOnboardingCutoff,
281
+ })
282
+
283
+ if (workstreamMessages.length === 0) {
284
+ serverLogger.info`Skipping regular chat memory digest for ${orgId}: no eligible messages`
285
+ return { skipped: true, processedWorkstreamMessages: 0, followUpScheduled: false }
286
+ }
287
+
288
+ const combinedMessages = [...workstreamMessages].sort(compareDigestMessageOrder)
289
+ const { transcript, involvedAgentNames } = buildDigestTranscript({ messages: combinedMessages })
290
+ const existingMemories = await loadExistingOrganizationMemories(orgId)
291
+
292
+ const synthesis = await helperModelRuntime.generateHelperStructured({
293
+ tag: 'regular-chat-memory-digest',
294
+ createAgent: createRegularChatMemoryDigestAgent,
295
+ timeoutMs: REGULAR_CHAT_MEMORY_DIGEST_TIMEOUT_MS,
296
+ messages: [
297
+ {
298
+ role: 'user',
299
+ content: buildPrompt({
300
+ workspaceName: projectionState?.workspaceName || 'Workspace',
301
+ currentSummaryBlock: projectionState?.summaryBlock ?? '',
302
+ currentStructuredProfile: JSON.stringify(projectionState?.structuredProfile ?? {}, null, 2),
303
+ existingMemories: buildMemoryContext(existingMemories),
304
+ transcript,
305
+ }),
306
+ },
307
+ ],
308
+ schema: RegularChatMemoryDigestOutputSchema,
309
+ })
310
+
311
+ const summaryBlock = normalizeBlock(synthesis.summaryBlock)
312
+ if (!summaryBlock) {
313
+ throw new Error('Regular chat memory digest returned an empty summaryBlock')
314
+ }
315
+
316
+ const processedWorkstreamCursor = getLastCursor(combinedMessages)
317
+ const digestRunAt = new Date().toISOString()
318
+
319
+ if (synthesis.facts.length > 0) {
320
+ await memoryService.addExtractedFactsToScopes({
321
+ orgId,
322
+ facts: synthesis.facts,
323
+ source: 'regular_chat_digest',
324
+ sourceMetadata: {
325
+ digestRunAt,
326
+ ...(processedWorkstreamCursor
327
+ ? {
328
+ digestWorkstreamCursorCreatedAt: processedWorkstreamCursor.createdAt.toISOString(),
329
+ digestWorkstreamCursorId: processedWorkstreamCursor.id,
330
+ }
331
+ : {}),
332
+ },
333
+ agentNames: involvedAgentNames,
334
+ acquireLock: false,
335
+ })
336
+ }
337
+
338
+ await workspaceProvider.applyProfileProjection(orgRef, {
339
+ summaryBlock,
340
+ structuredPatch: synthesis.structuredProfilePatch,
341
+ })
342
+ if (processedWorkstreamCursor) {
343
+ await workspaceProvider.setBackgroundCursor('regular-chat-digest', orgRef, processedWorkstreamCursor)
344
+ }
345
+
346
+ const workstreamBoundaryCursor = processedWorkstreamCursor ?? existingWorkstreamCursor
347
+ const hasMoreWorkstreamMessages = await hasNewEligibleWorkstreamMessages({
348
+ workstreamIds,
349
+ cursor: workstreamBoundaryCursor,
350
+ onboardingCutoff: workstreamBoundaryCursor ? null : workstreamOnboardingCutoff,
351
+ })
352
+
353
+ const followUpScheduled = hasMoreWorkstreamMessages
354
+ if (followUpScheduled) {
355
+ await clearRegularChatMemoryDigestDeduplicationKey(orgId)
356
+ await enqueueRegularChatMemoryDigest({ orgId })
357
+ }
358
+
359
+ serverLogger.info`Regular chat memory digest completed for ${orgId}: workstreamMessages=${workstreamMessages.length}, facts=${synthesis.facts.length}, followUpScheduled=${followUpScheduled}`
360
+
361
+ return { skipped: false, processedWorkstreamMessages: workstreamMessages.length, followUpScheduled }
362
+ })
363
+ }
@@ -0,0 +1,22 @@
1
+ import type { SandboxedJob } from 'bullmq'
2
+
3
+ import { serverLogger } from '../config/logger'
4
+ import type { RegularChatMemoryDigestJob } from '../queues/regular-chat-memory-digest.queue'
5
+ import { initializeSandboxedWorkerRuntime } from './bootstrap'
6
+ import { runRegularChatMemoryDigest } from './regular-chat-memory-digest.runner'
7
+ import { toSandboxedWorkerError } from './utils/sandbox-error'
8
+ import { createTracedWorkerProcessor } from './worker-utils'
9
+
10
+ await initializeSandboxedWorkerRuntime()
11
+
12
+ const handler = async (job: SandboxedJob<RegularChatMemoryDigestJob>) => {
13
+ try {
14
+ await runRegularChatMemoryDigest(job.data)
15
+ } catch (error) {
16
+ const serialized = toSandboxedWorkerError(error, 'Regular chat memory digest failed')
17
+ serverLogger.error`${serialized.message}`
18
+ throw serialized
19
+ }
20
+ }
21
+
22
+ export default createTracedWorkerProcessor('regular-chat-memory-digest', handler)