@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.
- package/infrastructure/schema/00_workstream.surql +55 -0
- package/infrastructure/schema/01_memory.surql +47 -0
- package/infrastructure/schema/02_execution_plan.surql +62 -0
- package/infrastructure/schema/03_learned_skill.surql +32 -0
- package/infrastructure/schema/04_runtime_bootstrap.surql +8 -0
- package/package.json +128 -0
- package/src/ai/definitions.ts +308 -0
- package/src/bifrost/bifrost.ts +256 -0
- package/src/config/agent-defaults.ts +99 -0
- package/src/config/constants.ts +33 -0
- package/src/config/env-shapes.ts +122 -0
- package/src/config/logger.ts +29 -0
- package/src/config/model-constants.ts +31 -0
- package/src/config/search.ts +17 -0
- package/src/config/workstream-defaults.ts +68 -0
- package/src/db/base.service.ts +55 -0
- package/src/db/cursor-pagination.ts +73 -0
- package/src/db/memory-query-builder.ts +207 -0
- package/src/db/memory-store.helpers.ts +118 -0
- package/src/db/memory-store.rows.ts +29 -0
- package/src/db/memory-store.ts +974 -0
- package/src/db/memory-types.ts +193 -0
- package/src/db/memory.ts +505 -0
- package/src/db/record-id.ts +78 -0
- package/src/db/service.ts +932 -0
- package/src/db/startup.ts +152 -0
- package/src/db/tables.ts +20 -0
- package/src/document/org-document-chunking.ts +224 -0
- package/src/document/parsing.ts +40 -0
- package/src/embeddings/provider.ts +76 -0
- package/src/index.ts +302 -0
- package/src/queues/context-compaction.queue.ts +82 -0
- package/src/queues/document-processor.queue.ts +118 -0
- package/src/queues/memory-consolidation.queue.ts +65 -0
- package/src/queues/post-chat-memory.queue.ts +128 -0
- package/src/queues/recent-activity-title-refinement.queue.ts +69 -0
- package/src/queues/regular-chat-memory-digest.config.ts +12 -0
- package/src/queues/regular-chat-memory-digest.queue.ts +73 -0
- package/src/queues/skill-extraction.config.ts +9 -0
- package/src/queues/skill-extraction.queue.ts +62 -0
- package/src/redis/connection.ts +176 -0
- package/src/redis/index.ts +30 -0
- package/src/redis/org-memory-lock.ts +43 -0
- package/src/redis/redis-lease-lock.ts +158 -0
- package/src/runtime/agent-contract.ts +1 -0
- package/src/runtime/agent-prompt-context.ts +119 -0
- package/src/runtime/agent-runtime-policy.ts +192 -0
- package/src/runtime/agent-stream-helpers.ts +117 -0
- package/src/runtime/agent-types.ts +22 -0
- package/src/runtime/approval-continuation.ts +16 -0
- package/src/runtime/chat-attachments.ts +46 -0
- package/src/runtime/chat-message.ts +10 -0
- package/src/runtime/chat-request-routing.ts +21 -0
- package/src/runtime/chat-run-orchestration.ts +25 -0
- package/src/runtime/chat-run-registry.ts +20 -0
- package/src/runtime/chat-types.ts +18 -0
- package/src/runtime/context-compaction-constants.ts +11 -0
- package/src/runtime/context-compaction-runtime.ts +86 -0
- package/src/runtime/context-compaction.ts +909 -0
- package/src/runtime/execution-plan.ts +59 -0
- package/src/runtime/helper-model.ts +405 -0
- package/src/runtime/indexed-repositories-policy.ts +28 -0
- package/src/runtime/instruction-sections.ts +8 -0
- package/src/runtime/llm-content.ts +71 -0
- package/src/runtime/memory-block.ts +264 -0
- package/src/runtime/memory-digest-policy.ts +14 -0
- package/src/runtime/memory-format.ts +8 -0
- package/src/runtime/memory-pipeline.ts +570 -0
- package/src/runtime/memory-prompts-fact.ts +47 -0
- package/src/runtime/memory-prompts-parse.ts +3 -0
- package/src/runtime/memory-prompts-update.ts +37 -0
- package/src/runtime/memory-scope.ts +43 -0
- package/src/runtime/plugin-types.ts +10 -0
- package/src/runtime/retrieval-adapters.ts +25 -0
- package/src/runtime/retrieval-pipeline.ts +3 -0
- package/src/runtime/runtime-extensions.ts +154 -0
- package/src/runtime/skill-extraction-policy.ts +3 -0
- package/src/runtime/team-consultation-orchestrator.ts +245 -0
- package/src/runtime/team-consultation-prompts.ts +32 -0
- package/src/runtime/title-helpers.ts +12 -0
- package/src/runtime/turn-lifecycle.ts +28 -0
- package/src/runtime/workstream-chat-helpers.ts +187 -0
- package/src/runtime/workstream-routing-policy.ts +301 -0
- package/src/runtime/workstream-state.ts +261 -0
- package/src/services/attachment.service.ts +159 -0
- package/src/services/chat-attachments.service.ts +17 -0
- package/src/services/chat-run-registry.service.ts +3 -0
- package/src/services/context-compaction-runtime.ts +13 -0
- package/src/services/context-compaction.service.ts +115 -0
- package/src/services/document-chunk.service.ts +141 -0
- package/src/services/execution-plan.service.ts +890 -0
- package/src/services/learned-skill.service.ts +328 -0
- package/src/services/memory-assessment.service.ts +43 -0
- package/src/services/memory.service.ts +807 -0
- package/src/services/memory.utils.ts +84 -0
- package/src/services/mutating-approval.service.ts +110 -0
- package/src/services/recent-activity-title.service.ts +74 -0
- package/src/services/recent-activity.service.ts +397 -0
- package/src/services/workstream-change-tracker.service.ts +313 -0
- package/src/services/workstream-message.service.ts +283 -0
- package/src/services/workstream-title.service.ts +58 -0
- package/src/services/workstream-turn-preparation.ts +1340 -0
- package/src/services/workstream-turn.ts +37 -0
- package/src/services/workstream.service.ts +854 -0
- package/src/services/workstream.types.ts +118 -0
- package/src/storage/attachment-parser.ts +101 -0
- package/src/storage/attachment-storage.service.ts +391 -0
- package/src/storage/attachments.types.ts +11 -0
- package/src/storage/attachments.utils.ts +58 -0
- package/src/storage/generated-document-storage.service.ts +55 -0
- package/src/system-agents/agent-result.ts +27 -0
- package/src/system-agents/context-compacter.agent.ts +46 -0
- package/src/system-agents/delegated-agent-factory.ts +177 -0
- package/src/system-agents/helper-agent-options.ts +20 -0
- package/src/system-agents/memory-reranker.agent.ts +38 -0
- package/src/system-agents/memory.agent.ts +58 -0
- package/src/system-agents/recent-activity-title-refiner.agent.ts +53 -0
- package/src/system-agents/regular-chat-memory-digest.agent.ts +75 -0
- package/src/system-agents/researcher.agent.ts +34 -0
- package/src/system-agents/skill-extractor.agent.ts +88 -0
- package/src/system-agents/skill-manager.agent.ts +80 -0
- package/src/system-agents/title-generator.agent.ts +42 -0
- package/src/system-agents/workstream-tracker.agent.ts +58 -0
- package/src/tools/execution-plan.tool.ts +163 -0
- package/src/tools/fetch-webpage.tool.ts +132 -0
- package/src/tools/firecrawl-client.ts +12 -0
- package/src/tools/memory-block.tool.ts +55 -0
- package/src/tools/read-file-parts.tool.ts +80 -0
- package/src/tools/remember-memory.tool.ts +85 -0
- package/src/tools/research-topic.tool.ts +15 -0
- package/src/tools/search-tools.ts +55 -0
- package/src/tools/search-web.tool.ts +175 -0
- package/src/tools/team-think.tool.ts +125 -0
- package/src/tools/tool-contract.ts +21 -0
- package/src/tools/user-questions.tool.ts +18 -0
- package/src/utils/async.ts +50 -0
- package/src/utils/date-time.ts +34 -0
- package/src/utils/error.ts +10 -0
- package/src/utils/errors.ts +28 -0
- package/src/utils/hono-error-handler.ts +71 -0
- package/src/utils/string.ts +51 -0
- package/src/workers/bootstrap.ts +44 -0
- package/src/workers/memory-consolidation.worker.ts +318 -0
- package/src/workers/regular-chat-memory-digest.helpers.ts +100 -0
- package/src/workers/regular-chat-memory-digest.runner.ts +363 -0
- package/src/workers/regular-chat-memory-digest.worker.ts +22 -0
- package/src/workers/skill-extraction.runner.ts +331 -0
- package/src/workers/skill-extraction.worker.ts +22 -0
- package/src/workers/utils/repo-indexer-chunker.ts +331 -0
- package/src/workers/utils/repo-structure-extractor.ts +645 -0
- package/src/workers/utils/repomix-process-concurrency.ts +65 -0
- package/src/workers/utils/sandbox-error.ts +5 -0
- 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)
|