@lota-sdk/core 0.1.5 → 0.1.7
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_identity.surql +26 -0
- package/infrastructure/schema/00_workstream.surql +8 -0
- package/infrastructure/schema/05_recent_activity.surql +48 -0
- package/package.json +4 -3
- package/src/ai/embedding-cache.ts +48 -0
- package/src/config/background-processing.ts +33 -0
- package/src/config/env-shapes.ts +0 -1
- package/src/config/model-constants.ts +4 -0
- package/src/db/memory-store.ts +110 -19
- package/src/db/memory-types.ts +11 -0
- package/src/db/memory.ts +11 -1
- package/src/db/schema-fingerprint.ts +21 -0
- package/src/db/sdk-database.ts +1 -0
- package/src/db/service.ts +0 -4
- package/src/db/tables.ts +1 -1
- package/src/index.ts +207 -10
- package/src/queues/memory-consolidation.queue.ts +6 -0
- package/src/queues/workstream-title-generation.queue.ts +69 -0
- package/src/runtime/agent-types.ts +5 -22
- package/src/runtime/helper-model.ts +9 -2
- package/src/runtime/memory-digest-policy.ts +30 -2
- package/src/runtime/skill-extraction-policy.ts +9 -2
- package/src/services/memory.service.ts +35 -0
- package/src/services/organization-member.service.ts +114 -0
- package/src/services/organization.service.ts +117 -0
- package/src/services/user.service.ts +56 -0
- package/src/services/workstream-title.service.ts +25 -35
- package/src/services/workstream-turn-preparation.ts +37 -10
- package/src/services/workstream-turn.ts +2 -0
- package/src/services/workstream.service.ts +61 -1
- package/src/services/workstream.types.ts +3 -0
- package/src/system-agents/title-generator.agent.ts +5 -5
- package/src/tools/research-topic.tool.ts +5 -1
- package/src/utils/sse-keepalive.ts +40 -0
- package/src/workers/bootstrap.ts +26 -1
- package/src/workers/memory-consolidation.worker.ts +1 -9
package/src/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { CoreWorkstreamProfile } from './config/agent-defaults'
|
|
2
2
|
import type { LotaWorkstreamConfig } from './config/workstream-defaults'
|
|
3
|
+
import { ensureRecordId } from './db/record-id'
|
|
3
4
|
import type { SurrealDBService } from './db/service'
|
|
5
|
+
import { TABLES } from './db/tables'
|
|
4
6
|
import type { startContextCompactionWorker } from './queues/context-compaction.queue'
|
|
5
7
|
import type {
|
|
6
8
|
scheduleRecurringConsolidation,
|
|
@@ -10,16 +12,22 @@ import type { startPostChatMemoryWorker } from './queues/post-chat-memory.queue'
|
|
|
10
12
|
import type { startRecentActivityTitleRefinementWorker } from './queues/recent-activity-title-refinement.queue'
|
|
11
13
|
import type { startRegularChatMemoryDigestWorker } from './queues/regular-chat-memory-digest.queue'
|
|
12
14
|
import type { startSkillExtractionWorker } from './queues/skill-extraction.queue'
|
|
15
|
+
import type { startWorkstreamTitleGenerationWorker } from './queues/workstream-title-generation.queue'
|
|
13
16
|
import type { RedisConnectionManager } from './redis/connection'
|
|
14
17
|
import type { isApprovalContinuationRequest } from './runtime/approval-continuation'
|
|
18
|
+
import type { routeWorkstreamChatMessages } from './runtime/chat-request-routing'
|
|
15
19
|
import type { LotaPlugin } from './runtime/plugin-types'
|
|
16
20
|
import type { LotaRuntimeAdapters, LotaRuntimeTurnHooks } from './runtime/runtime-extensions'
|
|
17
21
|
import type { attachmentService } from './services/attachment.service'
|
|
22
|
+
import type { documentChunkService } from './services/document-chunk.service'
|
|
18
23
|
import type { executionPlanService } from './services/execution-plan.service'
|
|
19
24
|
import type { memoryService } from './services/memory.service'
|
|
20
25
|
import type { verifyMutatingApproval } from './services/mutating-approval.service'
|
|
26
|
+
import type { organizationMemberService } from './services/organization-member.service'
|
|
27
|
+
import type { organizationService } from './services/organization.service'
|
|
21
28
|
import type { recentActivityTitleService } from './services/recent-activity-title.service'
|
|
22
29
|
import type { recentActivityService } from './services/recent-activity.service'
|
|
30
|
+
import type { userService } from './services/user.service'
|
|
23
31
|
import type { workstreamMessageService } from './services/workstream-message.service'
|
|
24
32
|
import type { workstreamTitleService } from './services/workstream-title.service'
|
|
25
33
|
import type {
|
|
@@ -39,11 +47,22 @@ interface LotaRuntimeBuiltInWorkers {
|
|
|
39
47
|
startRecentActivityTitleRefinementWorker: typeof startRecentActivityTitleRefinementWorker
|
|
40
48
|
startRegularChatMemoryDigestWorker: typeof startRegularChatMemoryDigestWorker
|
|
41
49
|
startSkillExtractionWorker: typeof startSkillExtractionWorker
|
|
50
|
+
startWorkstreamTitleGenerationWorker: typeof startWorkstreamTitleGenerationWorker
|
|
42
51
|
scheduleRecurringConsolidation: typeof scheduleRecurringConsolidation
|
|
43
52
|
}
|
|
44
53
|
|
|
54
|
+
type ArchiveSdkWorkstream = (
|
|
55
|
+
workstreamId: Parameters<typeof workstreamService.updateStatus>[0],
|
|
56
|
+
status?: 'archived',
|
|
57
|
+
) => ReturnType<typeof workstreamService.updateStatus>
|
|
58
|
+
|
|
59
|
+
type UnarchiveSdkWorkstream = (
|
|
60
|
+
workstreamId: Parameters<typeof workstreamService.updateStatus>[0],
|
|
61
|
+
status?: 'regular',
|
|
62
|
+
) => ReturnType<typeof workstreamService.updateStatus>
|
|
63
|
+
|
|
45
64
|
export interface LotaRuntimeConfig {
|
|
46
|
-
database: { url: string; namespace: string;
|
|
65
|
+
database: { url: string; namespace: string; username: string; password: string }
|
|
47
66
|
redis: { url: string }
|
|
48
67
|
aiGateway: { url: string; key: string; admin?: string; pass?: string; embeddingModel?: string }
|
|
49
68
|
s3: {
|
|
@@ -56,8 +75,14 @@ export interface LotaRuntimeConfig {
|
|
|
56
75
|
}
|
|
57
76
|
firecrawl: { apiKey: string; apiBaseUrl?: string }
|
|
58
77
|
logging?: { level?: 'trace' | 'debug' | 'info' | 'warning' | 'error' | 'fatal' }
|
|
59
|
-
memory?: { searchK?: number }
|
|
78
|
+
memory?: { searchK?: number; embeddingCacheTtlSeconds?: number }
|
|
60
79
|
workstreams?: LotaWorkstreamConfig
|
|
80
|
+
backgroundProcessing?: {
|
|
81
|
+
memoryExtractionFrequency?: number
|
|
82
|
+
skillExtractionFrequency?: number
|
|
83
|
+
memoryDigestFrequency?: number
|
|
84
|
+
memoryConsolidationFrequency?: number
|
|
85
|
+
}
|
|
61
86
|
|
|
62
87
|
agents: {
|
|
63
88
|
roster: readonly string[]
|
|
@@ -85,9 +110,13 @@ export interface LotaRuntime {
|
|
|
85
110
|
redis: RedisConnectionManager
|
|
86
111
|
closeRedisConnection: () => Promise<void>
|
|
87
112
|
attachmentService: typeof attachmentService
|
|
113
|
+
documentChunkService: typeof documentChunkService
|
|
88
114
|
generatedDocumentStorageService: typeof generatedDocumentStorageService
|
|
89
115
|
memoryService: typeof memoryService
|
|
90
116
|
verifyMutatingApproval: typeof verifyMutatingApproval
|
|
117
|
+
organizationService: typeof organizationService
|
|
118
|
+
organizationMemberService: typeof organizationMemberService
|
|
119
|
+
userService: typeof userService
|
|
91
120
|
recentActivityService: typeof recentActivityService
|
|
92
121
|
recentActivityTitleService: typeof recentActivityTitleService
|
|
93
122
|
executionPlanService: typeof executionPlanService
|
|
@@ -99,6 +128,57 @@ export interface LotaRuntime {
|
|
|
99
128
|
isApprovalContinuationRequest: typeof isApprovalContinuationRequest
|
|
100
129
|
runWorkstreamTurnInBackground: typeof runWorkstreamTurnInBackground
|
|
101
130
|
}
|
|
131
|
+
lota: {
|
|
132
|
+
organizations: {
|
|
133
|
+
create: typeof organizationService.createOrganization
|
|
134
|
+
upsert: typeof organizationService.upsertOrganization
|
|
135
|
+
get: typeof organizationService.getOrganization
|
|
136
|
+
list: typeof organizationService.listOrganizations
|
|
137
|
+
update: typeof organizationService.updateOrganization
|
|
138
|
+
delete: typeof organizationService.deleteOrganization
|
|
139
|
+
}
|
|
140
|
+
users: {
|
|
141
|
+
upsert: typeof userService.upsertUser
|
|
142
|
+
get: typeof userService.getUser
|
|
143
|
+
list: typeof userService.listUsers
|
|
144
|
+
update: typeof userService.updateUser
|
|
145
|
+
delete: typeof userService.deleteUser
|
|
146
|
+
}
|
|
147
|
+
memberships: {
|
|
148
|
+
add: typeof organizationMemberService.addMembership
|
|
149
|
+
listForOrganization: typeof organizationMemberService.listMembershipsForOrganization
|
|
150
|
+
listForUser: typeof organizationMemberService.listMembershipsForUser
|
|
151
|
+
remove: typeof organizationMemberService.removeMembership
|
|
152
|
+
isMember: typeof organizationMemberService.isMember
|
|
153
|
+
}
|
|
154
|
+
workstreams: {
|
|
155
|
+
create: typeof workstreamService.createWorkstream
|
|
156
|
+
list: typeof workstreamService.listWorkstreams
|
|
157
|
+
get: typeof workstreamService.getWorkstream
|
|
158
|
+
update: typeof workstreamService.updateTitle
|
|
159
|
+
archive: ArchiveSdkWorkstream
|
|
160
|
+
unarchive: UnarchiveSdkWorkstream
|
|
161
|
+
delete: typeof workstreamService.deleteWorkstream
|
|
162
|
+
stop: typeof workstreamService.stopActiveRun
|
|
163
|
+
listMessages: typeof workstreamMessageService.listMessageHistoryPage
|
|
164
|
+
getMessage: (params: { workstreamId: string; messageId: string }) => Promise<unknown>
|
|
165
|
+
sendMessage: (params: {
|
|
166
|
+
workstreamId: string
|
|
167
|
+
organizationId: string
|
|
168
|
+
userId: string
|
|
169
|
+
userName: string
|
|
170
|
+
messages: Parameters<typeof routeWorkstreamChatMessages>[0]
|
|
171
|
+
}) => Promise<Awaited<ReturnType<typeof createWorkstreamTurnStream>>>
|
|
172
|
+
continueApproval: (params: {
|
|
173
|
+
workstreamId: string
|
|
174
|
+
organizationId: string
|
|
175
|
+
userId: string
|
|
176
|
+
userName: string
|
|
177
|
+
messages: Parameters<typeof routeWorkstreamChatMessages>[0]
|
|
178
|
+
}) => Promise<Awaited<ReturnType<typeof createWorkstreamApprovalContinuationStream>>>
|
|
179
|
+
uploadAttachment: typeof attachmentService.uploadWorkstreamAttachment
|
|
180
|
+
}
|
|
181
|
+
}
|
|
102
182
|
redis: {
|
|
103
183
|
manager: RedisConnectionManager
|
|
104
184
|
getConnection: () => ReturnType<RedisConnectionManager['getConnection']>
|
|
@@ -118,12 +198,30 @@ export interface LotaRuntime {
|
|
|
118
198
|
export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<LotaRuntime> {
|
|
119
199
|
const { lotaSdkEnvKeys, setEnv } = await import('./config/env-shapes')
|
|
120
200
|
const { configureLogger } = await import('./config/logger')
|
|
201
|
+
const { publishDatabaseBootstrap } = await import('./db/startup')
|
|
202
|
+
const { computeSchemaFingerprint } = await import('./db/schema-fingerprint')
|
|
203
|
+
const { LOTA_SDK_DATABASE_NAME } = await import('./db/sdk-database')
|
|
121
204
|
const { SurrealDBService: SurrealDBServiceClass, setDatabaseService } = await import('./db/service')
|
|
122
205
|
const { createRedisConnectionManager } = await import('./redis/connection')
|
|
123
206
|
const { setRedisConnectionManager } = await import('./redis/index')
|
|
124
207
|
const { configureAgents, configureAgentFactory } = await import('./config/agent-defaults')
|
|
125
208
|
const { configureWorkstreams } = await import('./config/workstream-defaults')
|
|
126
209
|
const { configureRuntimeExtensions } = await import('./runtime/runtime-extensions')
|
|
210
|
+
const { routeWorkstreamChatMessages } = await import('./runtime/chat-request-routing')
|
|
211
|
+
const { configureBackgroundProcessing } = await import('./config/background-processing')
|
|
212
|
+
const { configureEmbeddingCache } = await import('./ai/embedding-cache')
|
|
213
|
+
|
|
214
|
+
// Resolve config defaults
|
|
215
|
+
const memory = {
|
|
216
|
+
searchK: config.memory?.searchK ?? 6,
|
|
217
|
+
embeddingCacheTtlSeconds: config.memory?.embeddingCacheTtlSeconds ?? 3600,
|
|
218
|
+
}
|
|
219
|
+
const backgroundProcessing = {
|
|
220
|
+
memoryExtractionFrequency: config.backgroundProcessing?.memoryExtractionFrequency ?? 3,
|
|
221
|
+
skillExtractionFrequency: config.backgroundProcessing?.skillExtractionFrequency ?? 5,
|
|
222
|
+
memoryDigestFrequency: config.backgroundProcessing?.memoryDigestFrequency ?? 1,
|
|
223
|
+
memoryConsolidationFrequency: config.backgroundProcessing?.memoryConsolidationFrequency ?? 10,
|
|
224
|
+
}
|
|
127
225
|
|
|
128
226
|
setEnv({
|
|
129
227
|
AI_GATEWAY_URL: config.aiGateway.url,
|
|
@@ -141,7 +239,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
141
239
|
ATTACHMENT_URL_EXPIRES_IN: config.s3.attachmentUrlExpiresIn ?? 1800,
|
|
142
240
|
FIRECRAWL_API_KEY: config.firecrawl.apiKey,
|
|
143
241
|
FIRECRAWL_API_BASE_URL: config.firecrawl.apiBaseUrl,
|
|
144
|
-
MEMORY_SEARCH_K:
|
|
242
|
+
MEMORY_SEARCH_K: memory.searchK,
|
|
145
243
|
})
|
|
146
244
|
|
|
147
245
|
await configureLogger(config.logging?.level ?? 'info')
|
|
@@ -149,7 +247,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
149
247
|
const db = new SurrealDBServiceClass({
|
|
150
248
|
url: config.database.url,
|
|
151
249
|
namespace: config.database.namespace,
|
|
152
|
-
database:
|
|
250
|
+
database: LOTA_SDK_DATABASE_NAME,
|
|
153
251
|
username: config.database.username,
|
|
154
252
|
password: config.database.password,
|
|
155
253
|
})
|
|
@@ -157,6 +255,8 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
157
255
|
|
|
158
256
|
const redisManager = createRedisConnectionManager({ url: config.redis.url })
|
|
159
257
|
setRedisConnectionManager(redisManager)
|
|
258
|
+
configureEmbeddingCache(redisManager.getConnection(), memory.embeddingCacheTtlSeconds)
|
|
259
|
+
configureBackgroundProcessing(backgroundProcessing)
|
|
160
260
|
|
|
161
261
|
configureAgents({
|
|
162
262
|
roster: config.agents.roster,
|
|
@@ -177,11 +277,15 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
177
277
|
}
|
|
178
278
|
|
|
179
279
|
const { attachmentService } = await import('./services/attachment.service')
|
|
280
|
+
const { documentChunkService } = await import('./services/document-chunk.service')
|
|
180
281
|
const { recentActivityService } = await import('./services/recent-activity.service')
|
|
181
282
|
const { recentActivityTitleService } = await import('./services/recent-activity-title.service')
|
|
182
283
|
const { executionPlanService } = await import('./services/execution-plan.service')
|
|
183
284
|
const { memoryService } = await import('./services/memory.service')
|
|
184
285
|
const { verifyMutatingApproval } = await import('./services/mutating-approval.service')
|
|
286
|
+
const { organizationMemberService } = await import('./services/organization-member.service')
|
|
287
|
+
const { organizationService } = await import('./services/organization.service')
|
|
288
|
+
const { userService } = await import('./services/user.service')
|
|
185
289
|
const { workstreamMessageService } = await import('./services/workstream-message.service')
|
|
186
290
|
const { workstreamService } = await import('./services/workstream.service')
|
|
187
291
|
const { workstreamTitleService } = await import('./services/workstream-title.service')
|
|
@@ -199,6 +303,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
199
303
|
const { startRecentActivityTitleRefinementWorker } = await import('./queues/recent-activity-title-refinement.queue')
|
|
200
304
|
const { startRegularChatMemoryDigestWorker } = await import('./queues/regular-chat-memory-digest.queue')
|
|
201
305
|
const { startSkillExtractionWorker } = await import('./queues/skill-extraction.queue')
|
|
306
|
+
const { startWorkstreamTitleGenerationWorker } = await import('./queues/workstream-title-generation.queue')
|
|
202
307
|
|
|
203
308
|
configureRuntimeExtensions({
|
|
204
309
|
adapters: config.runtimeAdapters,
|
|
@@ -209,11 +314,8 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
209
314
|
|
|
210
315
|
const pluginRuntime = config.pluginRuntime ?? {}
|
|
211
316
|
const pluginContributions = Object.values(pluginRuntime).map((plugin) => plugin.contributions)
|
|
212
|
-
const schemaFiles = [
|
|
213
|
-
|
|
214
|
-
...pluginContributions.flatMap((plugin) => plugin.schemaFiles),
|
|
215
|
-
...(config.extraSchemaFiles ?? []),
|
|
216
|
-
]
|
|
317
|
+
const schemaFiles = [...getBuiltInSchemaFiles(), ...(config.extraSchemaFiles ?? [])]
|
|
318
|
+
const hostContributionSchemaFiles = pluginContributions.flatMap((plugin) => plugin.schemaFiles)
|
|
217
319
|
const contributionEnvKeys = [...lotaSdkEnvKeys, ...pluginContributions.flatMap((plugin) => plugin.envKeys)]
|
|
218
320
|
const connectPluginDatabases = createPluginDatabaseConnector(pluginRuntime)
|
|
219
321
|
const builtInWorkers = {
|
|
@@ -223,9 +325,91 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
223
325
|
startRecentActivityTitleRefinementWorker,
|
|
224
326
|
startRegularChatMemoryDigestWorker,
|
|
225
327
|
startSkillExtractionWorker,
|
|
328
|
+
startWorkstreamTitleGenerationWorker,
|
|
226
329
|
scheduleRecurringConsolidation,
|
|
227
330
|
} satisfies LotaRuntimeBuiltInWorkers
|
|
228
331
|
|
|
332
|
+
const lota = {
|
|
333
|
+
organizations: {
|
|
334
|
+
create: organizationService.createOrganization.bind(organizationService),
|
|
335
|
+
upsert: organizationService.upsertOrganization.bind(organizationService),
|
|
336
|
+
get: organizationService.getOrganization.bind(organizationService),
|
|
337
|
+
list: organizationService.listOrganizations.bind(organizationService),
|
|
338
|
+
update: organizationService.updateOrganization.bind(organizationService),
|
|
339
|
+
delete: organizationService.deleteOrganization.bind(organizationService),
|
|
340
|
+
},
|
|
341
|
+
users: {
|
|
342
|
+
upsert: userService.upsertUser.bind(userService),
|
|
343
|
+
get: userService.getUser.bind(userService),
|
|
344
|
+
list: userService.listUsers.bind(userService),
|
|
345
|
+
update: userService.updateUser.bind(userService),
|
|
346
|
+
delete: userService.deleteUser.bind(userService),
|
|
347
|
+
},
|
|
348
|
+
memberships: {
|
|
349
|
+
add: organizationMemberService.addMembership.bind(organizationMemberService),
|
|
350
|
+
listForOrganization: organizationMemberService.listMembershipsForOrganization.bind(organizationMemberService),
|
|
351
|
+
listForUser: organizationMemberService.listMembershipsForUser.bind(organizationMemberService),
|
|
352
|
+
remove: organizationMemberService.removeMembership.bind(organizationMemberService),
|
|
353
|
+
isMember: organizationMemberService.isMember.bind(organizationMemberService),
|
|
354
|
+
},
|
|
355
|
+
workstreams: {
|
|
356
|
+
create: workstreamService.createWorkstream.bind(workstreamService),
|
|
357
|
+
list: workstreamService.listWorkstreams.bind(workstreamService),
|
|
358
|
+
get: workstreamService.getWorkstream.bind(workstreamService),
|
|
359
|
+
update: workstreamService.updateTitle.bind(workstreamService),
|
|
360
|
+
archive: async (workstreamId, status = 'archived') => await workstreamService.updateStatus(workstreamId, status),
|
|
361
|
+
unarchive: async (workstreamId, status = 'regular') => await workstreamService.updateStatus(workstreamId, status),
|
|
362
|
+
delete: workstreamService.deleteWorkstream.bind(workstreamService),
|
|
363
|
+
stop: workstreamService.stopActiveRun.bind(workstreamService),
|
|
364
|
+
listMessages: workstreamMessageService.listMessageHistoryPage.bind(workstreamMessageService),
|
|
365
|
+
getMessage: async ({ workstreamId, messageId }) => {
|
|
366
|
+
const messages = await workstreamMessageService.listMessages(ensureRecordId(workstreamId, TABLES.WORKSTREAM))
|
|
367
|
+
const message = messages.find((candidate) => candidate.id === messageId)
|
|
368
|
+
if (!message) {
|
|
369
|
+
throw new Error(`Workstream message not found: ${messageId}`)
|
|
370
|
+
}
|
|
371
|
+
return message
|
|
372
|
+
},
|
|
373
|
+
sendMessage: async ({ workstreamId, organizationId, userId, userName, messages }) => {
|
|
374
|
+
const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
|
|
375
|
+
const workstream = await workstreamService.getWorkstream(workstreamRef)
|
|
376
|
+
const routed = routeWorkstreamChatMessages(messages)
|
|
377
|
+
if (routed.kind !== 'turn') {
|
|
378
|
+
throw new Error(routed.kind === 'invalid' ? routed.message : 'Expected a user turn payload.')
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return await createWorkstreamTurnStream({
|
|
382
|
+
workstream,
|
|
383
|
+
workstreamRef,
|
|
384
|
+
orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
|
|
385
|
+
userRef: ensureRecordId(userId, TABLES.USER),
|
|
386
|
+
userName,
|
|
387
|
+
inputMessage: routed.inputMessage,
|
|
388
|
+
})
|
|
389
|
+
},
|
|
390
|
+
continueApproval: async ({ workstreamId, organizationId, userId, userName, messages }) => {
|
|
391
|
+
const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
|
|
392
|
+
const workstream = await workstreamService.getWorkstream(workstreamRef)
|
|
393
|
+
const routed = routeWorkstreamChatMessages(messages)
|
|
394
|
+
if (routed.kind !== 'approval-continuation') {
|
|
395
|
+
throw new Error(
|
|
396
|
+
routed.kind === 'invalid' ? routed.message : 'Expected approval continuation messages payload.',
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return await createWorkstreamApprovalContinuationStream({
|
|
401
|
+
workstream,
|
|
402
|
+
workstreamRef,
|
|
403
|
+
orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
|
|
404
|
+
userRef: ensureRecordId(userId, TABLES.USER),
|
|
405
|
+
userName,
|
|
406
|
+
approvalMessages: routed.approvalMessages,
|
|
407
|
+
})
|
|
408
|
+
},
|
|
409
|
+
uploadAttachment: attachmentService.uploadWorkstreamAttachment.bind(attachmentService),
|
|
410
|
+
},
|
|
411
|
+
} satisfies LotaRuntime['lota']
|
|
412
|
+
|
|
229
413
|
return {
|
|
230
414
|
services: {
|
|
231
415
|
database: db,
|
|
@@ -233,9 +417,13 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
233
417
|
redis: redisManager,
|
|
234
418
|
closeRedisConnection: async () => await redisManager.closeConnection(),
|
|
235
419
|
attachmentService,
|
|
420
|
+
documentChunkService,
|
|
236
421
|
generatedDocumentStorageService,
|
|
237
422
|
memoryService,
|
|
238
423
|
verifyMutatingApproval,
|
|
424
|
+
organizationService,
|
|
425
|
+
organizationMemberService,
|
|
426
|
+
userService,
|
|
239
427
|
recentActivityService,
|
|
240
428
|
recentActivityTitleService,
|
|
241
429
|
executionPlanService,
|
|
@@ -247,6 +435,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
247
435
|
isApprovalContinuationRequest,
|
|
248
436
|
runWorkstreamTurnInBackground,
|
|
249
437
|
},
|
|
438
|
+
lota,
|
|
250
439
|
redis: {
|
|
251
440
|
manager: redisManager,
|
|
252
441
|
getConnection: () => redisManager.getConnection(),
|
|
@@ -255,7 +444,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
255
444
|
},
|
|
256
445
|
workers: { ...builtInWorkers, ...config.extraWorkers },
|
|
257
446
|
schemaFiles,
|
|
258
|
-
contributions: { envKeys: [...new Set(contributionEnvKeys)], schemaFiles },
|
|
447
|
+
contributions: { envKeys: [...new Set(contributionEnvKeys)], schemaFiles: hostContributionSchemaFiles },
|
|
259
448
|
config,
|
|
260
449
|
plugins: pluginRuntime,
|
|
261
450
|
async connectPluginDatabases() {
|
|
@@ -263,6 +452,12 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
263
452
|
},
|
|
264
453
|
async connect() {
|
|
265
454
|
await db.connect()
|
|
455
|
+
const bunFiles = schemaFiles.map((schemaFile) =>
|
|
456
|
+
schemaFile instanceof URL ? Bun.file(schemaFile.pathname) : Bun.file(schemaFile),
|
|
457
|
+
)
|
|
458
|
+
await db.applySchemaAndMigrations(bunFiles)
|
|
459
|
+
const schemaFingerprint = await computeSchemaFingerprint(schemaFiles)
|
|
460
|
+
await publishDatabaseBootstrap({ databaseService: db, schemaFingerprint })
|
|
266
461
|
},
|
|
267
462
|
async disconnect() {
|
|
268
463
|
await db.disconnect()
|
|
@@ -273,10 +468,12 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
273
468
|
|
|
274
469
|
function getBuiltInSchemaFiles(): URL[] {
|
|
275
470
|
return [
|
|
471
|
+
new URL('../infrastructure/schema/00_identity.surql', import.meta.url),
|
|
276
472
|
new URL('../infrastructure/schema/00_workstream.surql', import.meta.url),
|
|
277
473
|
new URL('../infrastructure/schema/01_memory.surql', import.meta.url),
|
|
278
474
|
new URL('../infrastructure/schema/02_execution_plan.surql', import.meta.url),
|
|
279
475
|
new URL('../infrastructure/schema/03_learned_skill.surql', import.meta.url),
|
|
476
|
+
new URL('../infrastructure/schema/05_recent_activity.surql', import.meta.url),
|
|
280
477
|
new URL('../infrastructure/schema/04_runtime_bootstrap.surql', import.meta.url),
|
|
281
478
|
]
|
|
282
479
|
}
|
|
@@ -32,6 +32,12 @@ function getMemoryConsolidationQueue(): Queue<MemoryConsolidationJob> {
|
|
|
32
32
|
return _memoryConsolidationQueue
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export async function enqueueMemoryConsolidation(job: MemoryConsolidationJob = {}) {
|
|
36
|
+
await getMemoryConsolidationQueue().add('consolidate-turn', job, {
|
|
37
|
+
jobId: job.scopeId ? `consolidate-turn:${job.scopeId}` : undefined,
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
export async function scheduleRecurringConsolidation() {
|
|
36
42
|
await getMemoryConsolidationQueue().add(
|
|
37
43
|
'consolidate',
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Queue, Worker } from 'bullmq'
|
|
2
|
+
import type { Job } from 'bullmq'
|
|
3
|
+
|
|
4
|
+
import { serverLogger } from '../config/logger'
|
|
5
|
+
import { ensureRecordId } from '../db/record-id'
|
|
6
|
+
import { databaseService } from '../db/service'
|
|
7
|
+
import { getRedisConnectionForBullMQ } from '../redis'
|
|
8
|
+
import { workstreamTitleService } from '../services/workstream-title.service'
|
|
9
|
+
import {
|
|
10
|
+
attachWorkerEvents,
|
|
11
|
+
createTracedWorkerProcessor,
|
|
12
|
+
createWorkerShutdown,
|
|
13
|
+
registerShutdownSignals,
|
|
14
|
+
} from '../workers/worker-utils'
|
|
15
|
+
import type { WorkerHandle } from '../workers/worker-utils'
|
|
16
|
+
|
|
17
|
+
interface WorkstreamTitleGenerationJob {
|
|
18
|
+
workstreamId: string
|
|
19
|
+
sourceText: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const WORKSTREAM_TITLE_GENERATION_QUEUE = 'workstream-title-generation'
|
|
23
|
+
|
|
24
|
+
let _workstreamTitleGenerationQueue: Queue<WorkstreamTitleGenerationJob> | null = null
|
|
25
|
+
function getWorkstreamTitleGenerationQueue(): Queue<WorkstreamTitleGenerationJob> {
|
|
26
|
+
if (!_workstreamTitleGenerationQueue) {
|
|
27
|
+
_workstreamTitleGenerationQueue = new Queue<WorkstreamTitleGenerationJob>(WORKSTREAM_TITLE_GENERATION_QUEUE, {
|
|
28
|
+
connection: getRedisConnectionForBullMQ(),
|
|
29
|
+
defaultJobOptions: {
|
|
30
|
+
removeOnComplete: 200,
|
|
31
|
+
removeOnFail: 200,
|
|
32
|
+
attempts: 2,
|
|
33
|
+
backoff: { type: 'exponential', delay: 2_000 },
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
return _workstreamTitleGenerationQueue
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function enqueueWorkstreamTitleGeneration(job: WorkstreamTitleGenerationJob) {
|
|
41
|
+
return await getWorkstreamTitleGenerationQueue().add('generate-workstream-title', job, {
|
|
42
|
+
jobId: `workstream-title:${job.workstreamId}`,
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function processWorkstreamTitleGenerationJob(job: Job<WorkstreamTitleGenerationJob>): Promise<void> {
|
|
47
|
+
await databaseService.connect()
|
|
48
|
+
const workstreamRef = ensureRecordId(job.data.workstreamId)
|
|
49
|
+
await workstreamTitleService.generateAndPersistTitle(workstreamRef, job.data.sourceText)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function startWorkstreamTitleGenerationWorker(options: { registerSignals?: boolean } = {}): WorkerHandle {
|
|
53
|
+
const { registerSignals = import.meta.main } = options
|
|
54
|
+
const worker = new Worker(
|
|
55
|
+
WORKSTREAM_TITLE_GENERATION_QUEUE,
|
|
56
|
+
createTracedWorkerProcessor(WORKSTREAM_TITLE_GENERATION_QUEUE, processWorkstreamTitleGenerationJob),
|
|
57
|
+
{ connection: getRedisConnectionForBullMQ(), concurrency: 2, lockDuration: 60_000 },
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
attachWorkerEvents(worker, 'Workstream title generation', serverLogger)
|
|
61
|
+
|
|
62
|
+
const shutdown = createWorkerShutdown(worker, 'Workstream title generation', serverLogger)
|
|
63
|
+
|
|
64
|
+
if (registerSignals) {
|
|
65
|
+
registerShutdownSignals({ name: 'Workstream title generation', shutdown, logger: serverLogger })
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { worker, shutdown }
|
|
69
|
+
}
|
|
@@ -1,22 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
mode: ChatMode
|
|
7
|
-
tools: TTools
|
|
8
|
-
extraInstructions?: string
|
|
9
|
-
stopWhen?: StopCondition<TTools> | Array<StopCondition<TTools>>
|
|
10
|
-
prepareStep?: PrepareStepFunction<TTools>
|
|
11
|
-
maxRetries?: number
|
|
12
|
-
modelOverride?: { model: unknown; providerOptions?: Record<string, unknown> }
|
|
13
|
-
onFinish?: ToolLoopAgentOnFinishCallback<TTools>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface CreateHelperToolLoopAgentOptions {
|
|
17
|
-
instructions?: string
|
|
18
|
-
maxOutputTokens?: number
|
|
19
|
-
temperature?: number
|
|
20
|
-
output?: Output.Output
|
|
21
|
-
maxRetries?: number
|
|
22
|
-
}
|
|
1
|
+
export type {
|
|
2
|
+
ChatMode,
|
|
3
|
+
CreateHelperToolLoopAgentOptions,
|
|
4
|
+
CreateRoutedAgentOptions,
|
|
5
|
+
} from '@lota-sdk/shared/runtime/agent-types'
|
|
@@ -48,6 +48,7 @@ export interface GenerateHelperStructuredParams<T> extends Omit<GenerateHelperTe
|
|
|
48
48
|
tag: string
|
|
49
49
|
schema: ZodSchema<T>
|
|
50
50
|
textFallbackParser?: (text: string) => T | null
|
|
51
|
+
normalizeCandidate?: (candidate: unknown) => unknown
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
function isObject(value: unknown): value is Record<string, unknown> {
|
|
@@ -358,6 +359,14 @@ export function createHelperModelRuntime() {
|
|
|
358
359
|
return parsed.data
|
|
359
360
|
}
|
|
360
361
|
|
|
362
|
+
if (params.normalizeCandidate && isObject(result.output)) {
|
|
363
|
+
const normalized = params.normalizeCandidate(result.output)
|
|
364
|
+
const normalizedParsed = parseStructuredCandidate({ schema: params.schema, candidate: normalized })
|
|
365
|
+
if (normalizedParsed) {
|
|
366
|
+
return normalizedParsed.data
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
361
370
|
if (typeof result.text === 'string' && result.text.trim().length > 0) {
|
|
362
371
|
return parseStructuredTextWithFallbacks({
|
|
363
372
|
schema: params.schema,
|
|
@@ -401,5 +410,3 @@ export function createHelperModelRuntime() {
|
|
|
401
410
|
|
|
402
411
|
return { generateHelperText, generateHelperStructured }
|
|
403
412
|
}
|
|
404
|
-
|
|
405
|
-
export const llmHelperService = createHelperModelRuntime()
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getBackgroundProcessingConfig, shouldRunAtFrequency } from '../config/background-processing'
|
|
2
|
+
|
|
1
3
|
export function shouldEnqueueOnboardingPostChatMemory(params: {
|
|
2
4
|
onboardingActive: boolean
|
|
3
5
|
userMessageText: string
|
|
@@ -9,6 +11,32 @@ export function shouldEnqueueOnboardingPostChatMemory(params: {
|
|
|
9
11
|
return params.userMessageText.trim().length > 0 || params.hasAttachmentContext
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
export function shouldEnqueueRegularDigestForWorkstream(params: {
|
|
13
|
-
|
|
14
|
+
export function shouldEnqueueRegularDigestForWorkstream(params: {
|
|
15
|
+
onboardingActive: boolean
|
|
16
|
+
turnCount?: number
|
|
17
|
+
}): boolean {
|
|
18
|
+
if (params.onboardingActive) return false
|
|
19
|
+
const { memoryDigestFrequency } = getBackgroundProcessingConfig()
|
|
20
|
+
if (typeof params.turnCount === 'number') {
|
|
21
|
+
return shouldRunAtFrequency(params.turnCount, memoryDigestFrequency)
|
|
22
|
+
}
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function shouldEnqueueMemoryExtraction(params: { onboardingActive: boolean; turnCount?: number }): boolean {
|
|
27
|
+
if (params.onboardingActive) return true
|
|
28
|
+
const { memoryExtractionFrequency } = getBackgroundProcessingConfig()
|
|
29
|
+
if (typeof params.turnCount === 'number') {
|
|
30
|
+
return shouldRunAtFrequency(params.turnCount, memoryExtractionFrequency)
|
|
31
|
+
}
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function shouldEnqueueMemoryConsolidation(params: { onboardingActive: boolean; turnCount?: number }): boolean {
|
|
36
|
+
if (params.onboardingActive) return false
|
|
37
|
+
const { memoryConsolidationFrequency } = getBackgroundProcessingConfig()
|
|
38
|
+
if (typeof params.turnCount === 'number') {
|
|
39
|
+
return shouldRunAtFrequency(params.turnCount, memoryConsolidationFrequency)
|
|
40
|
+
}
|
|
41
|
+
return false
|
|
14
42
|
}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { getBackgroundProcessingConfig, shouldRunAtFrequency } from '../config/background-processing'
|
|
2
|
+
|
|
3
|
+
export function shouldEnqueueSkillExtraction(params: { onboardingActive: boolean; turnCount?: number }): boolean {
|
|
4
|
+
if (params.onboardingActive) return false
|
|
5
|
+
const { skillExtractionFrequency } = getBackgroundProcessingConfig()
|
|
6
|
+
if (typeof params.turnCount === 'number') {
|
|
7
|
+
return shouldRunAtFrequency(params.turnCount, skillExtractionFrequency)
|
|
8
|
+
}
|
|
9
|
+
return true
|
|
3
10
|
}
|
|
@@ -8,10 +8,12 @@ import { isUniqueIndexConflict } from '../db/memory-store.helpers'
|
|
|
8
8
|
import type {
|
|
9
9
|
AddOptions,
|
|
10
10
|
ExtractedFact,
|
|
11
|
+
MemoryListScalar,
|
|
11
12
|
MemoryRecord,
|
|
12
13
|
MemorySearchResult,
|
|
13
14
|
MemoryType,
|
|
14
15
|
Message,
|
|
16
|
+
RelationType,
|
|
15
17
|
} from '../db/memory-types'
|
|
16
18
|
import { withOrgMemoryLock } from '../redis/org-memory-lock'
|
|
17
19
|
import { createHelperModelRuntime } from '../runtime/helper-model'
|
|
@@ -413,6 +415,19 @@ class MemoryService {
|
|
|
413
415
|
return `Agent memory (${agentName}):\n${agentResult}\n\nGlobal org memory:\n${orgResult}`
|
|
414
416
|
}
|
|
415
417
|
|
|
418
|
+
async listOrganizationMemoryRecords(params: {
|
|
419
|
+
orgId: string
|
|
420
|
+
limit?: number
|
|
421
|
+
memoryType?: MemoryType
|
|
422
|
+
metadataEquals?: Record<string, MemoryListScalar>
|
|
423
|
+
metadataNotEquals?: Record<string, MemoryListScalar>
|
|
424
|
+
sort?: 'createdAtAsc' | 'createdAtDesc'
|
|
425
|
+
}): Promise<MemoryRecord[]> {
|
|
426
|
+
const { orgId, ...listOptions } = params
|
|
427
|
+
const orgMemory = this.getOrgMemory(orgId)
|
|
428
|
+
return await orgMemory.list({ scopeId: scopeId(ORG_SCOPE_PREFIX, orgId), ...listOptions })
|
|
429
|
+
}
|
|
430
|
+
|
|
416
431
|
async getTopMemories(params: { orgId: string; agentName?: string; limit?: number }): Promise<string | undefined> {
|
|
417
432
|
const orgMemory = this.getOrgMemory(params.orgId)
|
|
418
433
|
const orgScopeId = scopeId(ORG_SCOPE_PREFIX, params.orgId)
|
|
@@ -568,12 +583,14 @@ class MemoryService {
|
|
|
568
583
|
memoryType,
|
|
569
584
|
metadata,
|
|
570
585
|
importance,
|
|
586
|
+
durability,
|
|
571
587
|
}: {
|
|
572
588
|
orgId: string
|
|
573
589
|
content: string
|
|
574
590
|
memoryType: MemoryType
|
|
575
591
|
metadata?: Record<string, unknown>
|
|
576
592
|
importance?: number
|
|
593
|
+
durability?: MemoryRecord['durability']
|
|
577
594
|
}): Promise<string> {
|
|
578
595
|
const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
|
|
579
596
|
aiLogger.info`[MEMORY_DEBUG] createOrganizationMemory - orgId: "${orgId}", scopeId: "${orgScopeId}", content preview: "${content.slice(0, 50)}"`
|
|
@@ -583,6 +600,7 @@ class MemoryService {
|
|
|
583
600
|
scopeId: orgScopeId,
|
|
584
601
|
memoryType,
|
|
585
602
|
importance: importance ?? 1,
|
|
603
|
+
durability,
|
|
586
604
|
metadata: { orgId, ...metadata },
|
|
587
605
|
})
|
|
588
606
|
} catch (error) {
|
|
@@ -594,6 +612,23 @@ class MemoryService {
|
|
|
594
612
|
}
|
|
595
613
|
}
|
|
596
614
|
|
|
615
|
+
async addOrganizationMemoryRelation({
|
|
616
|
+
orgId,
|
|
617
|
+
fromMemoryId,
|
|
618
|
+
toMemoryId,
|
|
619
|
+
relationType,
|
|
620
|
+
confidence,
|
|
621
|
+
}: {
|
|
622
|
+
orgId: string
|
|
623
|
+
fromMemoryId: string
|
|
624
|
+
toMemoryId: string
|
|
625
|
+
relationType: RelationType
|
|
626
|
+
confidence?: number
|
|
627
|
+
}): Promise<void> {
|
|
628
|
+
const memory = this.getOrgMemory(orgId)
|
|
629
|
+
await memory.addRelation(fromMemoryId, toMemoryId, relationType, confidence)
|
|
630
|
+
}
|
|
631
|
+
|
|
597
632
|
async createAgentMemory({
|
|
598
633
|
orgId,
|
|
599
634
|
agentName,
|