@lota-sdk/core 0.1.9 → 0.1.12

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 (105) hide show
  1. package/infrastructure/schema/00_workstream.surql +1 -0
  2. package/infrastructure/schema/02_execution_plan.surql +202 -52
  3. package/package.json +4 -87
  4. package/src/ai/index.ts +3 -0
  5. package/src/bifrost/bifrost.ts +94 -25
  6. package/src/bifrost/index.ts +1 -0
  7. package/src/config/agent-defaults.ts +30 -7
  8. package/src/config/constants.ts +0 -9
  9. package/src/config/debug-logger.ts +43 -0
  10. package/src/config/index.ts +5 -0
  11. package/src/config/model-constants.ts +8 -9
  12. package/src/config/workstream-defaults.ts +4 -0
  13. package/src/db/cursor-pagination.ts +2 -2
  14. package/src/db/index.ts +10 -0
  15. package/src/db/memory-store.ts +3 -71
  16. package/src/db/memory.ts +9 -15
  17. package/src/db/service.ts +42 -2
  18. package/src/db/tables.ts +9 -2
  19. package/src/document/index.ts +2 -0
  20. package/src/document/parsing.ts +0 -25
  21. package/src/embeddings/provider.ts +102 -22
  22. package/src/index.ts +15 -499
  23. package/src/queues/index.ts +10 -0
  24. package/src/redis/connection-accessor.ts +26 -0
  25. package/src/redis/connection.ts +1 -1
  26. package/src/redis/index.ts +9 -25
  27. package/src/redis/org-memory-lock.ts +1 -1
  28. package/src/redis/redis-lease-lock.ts +1 -1
  29. package/src/redis/stream-context.ts +54 -0
  30. package/src/runtime/agent-runtime-policy.ts +9 -5
  31. package/src/runtime/agent-stream-helpers.ts +6 -3
  32. package/src/runtime/agent-types.ts +1 -5
  33. package/src/runtime/approval-continuation.ts +68 -1
  34. package/src/runtime/chat-attachments.ts +1 -1
  35. package/src/runtime/chat-request-routing.ts +6 -2
  36. package/src/runtime/context-compaction-runtime.ts +2 -2
  37. package/src/runtime/context-compaction.ts +1 -1
  38. package/src/runtime/execution-plan.ts +22 -15
  39. package/src/runtime/index.ts +26 -0
  40. package/src/runtime/indexed-repositories-policy.ts +10 -10
  41. package/src/runtime/memory-pipeline.ts +0 -2
  42. package/src/runtime/runtime-config.ts +238 -0
  43. package/src/runtime/runtime-extensions.ts +3 -2
  44. package/src/runtime/runtime-worker-registry.ts +47 -0
  45. package/src/runtime/team-consultation-orchestrator.ts +9 -6
  46. package/src/runtime/team-consultation-prompts.ts +3 -2
  47. package/src/runtime/turn-lifecycle.ts +13 -5
  48. package/src/runtime/workstream-chat-helpers.ts +0 -54
  49. package/src/runtime/workstream-routing-policy.ts +3 -7
  50. package/src/runtime.ts +387 -0
  51. package/src/services/chat-attachments.service.ts +1 -1
  52. package/src/services/context-compaction.service.ts +1 -1
  53. package/src/services/document-chunk.service.ts +2 -2
  54. package/src/services/execution-plan.service.ts +584 -793
  55. package/src/services/index.ts +14 -0
  56. package/src/services/learned-skill.service.ts +82 -39
  57. package/src/services/memory.service.ts +5 -4
  58. package/src/services/mutating-approval.service.ts +1 -1
  59. package/src/services/organization-member.service.ts +1 -1
  60. package/src/services/organization.service.ts +1 -1
  61. package/src/services/plan-approval.service.ts +83 -0
  62. package/src/services/plan-artifact.service.ts +44 -0
  63. package/src/services/plan-builder.service.ts +61 -0
  64. package/src/services/plan-checkpoint.service.ts +53 -0
  65. package/src/services/plan-compiler.service.ts +81 -0
  66. package/src/services/plan-executor.service.ts +1624 -0
  67. package/src/services/plan-run.service.ts +422 -0
  68. package/src/services/plan-validator.service.ts +760 -0
  69. package/src/services/recent-activity-title.service.ts +1 -1
  70. package/src/services/recent-activity.service.ts +14 -16
  71. package/src/services/user.service.ts +2 -2
  72. package/src/services/workstream-message.service.ts +2 -3
  73. package/src/services/workstream-title.service.ts +1 -1
  74. package/src/services/workstream-turn-preparation.ts +156 -59
  75. package/src/services/workstream-turn.ts +26 -1
  76. package/src/services/workstream.service.ts +35 -9
  77. package/src/services/workstream.types.ts +1 -0
  78. package/src/storage/attachment-parser.ts +1 -1
  79. package/src/storage/attachment-storage.service.ts +11 -10
  80. package/src/storage/generated-document-storage.service.ts +7 -6
  81. package/src/storage/index.ts +10 -0
  82. package/src/system-agents/delegated-agent-factory.ts +78 -29
  83. package/src/system-agents/index.ts +4 -0
  84. package/src/system-agents/recent-activity-title-refiner.agent.ts +38 -3
  85. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  86. package/src/system-agents/skill-extractor.agent.ts +1 -1
  87. package/src/system-agents/skill-manager.agent.ts +2 -4
  88. package/src/system-agents/title-generator.agent.ts +2 -2
  89. package/src/tools/execution-plan.tool.ts +22 -48
  90. package/src/tools/firecrawl-client.ts +2 -2
  91. package/src/tools/index.ts +12 -0
  92. package/src/tools/log-hello-world.tool.ts +17 -0
  93. package/src/tools/research-topic.tool.ts +1 -1
  94. package/src/tools/team-think.tool.ts +1 -1
  95. package/src/tools/user-questions.tool.ts +2 -2
  96. package/src/utils/index.ts +6 -0
  97. package/src/workers/bootstrap.ts +8 -16
  98. package/src/workers/index.ts +7 -0
  99. package/src/workers/regular-chat-memory-digest.runner.ts +1 -1
  100. package/src/workers/skill-extraction.runner.ts +3 -3
  101. package/src/workers/utils/{repo-indexer-chunker.ts → file-section-chunker.ts} +23 -52
  102. package/src/workers/utils/repo-structure-extractor.ts +2 -5
  103. package/src/workers/utils/repomix-file-sections.ts +42 -0
  104. package/src/config/env-shapes.ts +0 -121
  105. package/src/runtime/agent-contract.ts +0 -1
@@ -1,7 +1,5 @@
1
- import { withMessageCreatedAt } from '@lota-sdk/shared/runtime/chat-message-metadata'
2
- import type { ChatMessage } from '@lota-sdk/shared/schemas/chat-message'
3
- import { ConsultTeamArgsSchema } from '@lota-sdk/shared/schemas/tools'
4
- import type { ConsultTeamResultData } from '@lota-sdk/shared/schemas/tools'
1
+ import { ConsultTeamArgsSchema, withMessageCreatedAt } from '@lota-sdk/shared'
2
+ import type { ChatMessage, ConsultTeamResultData } from '@lota-sdk/shared'
5
3
  import { convertToModelMessages, readUIMessageStream, tool as createTool } from 'ai'
6
4
 
7
5
  import { agentDisplayNames, teamConsultParticipants } from '../config/agent-defaults'
@@ -79,9 +77,14 @@ export interface CreateConsultTeamToolParams {
79
77
  }
80
78
 
81
79
  export function createConsultTeamTool(params: CreateConsultTeamToolParams) {
80
+ const participantNames = teamConsultParticipants
81
+ .map((agentId) => agentDisplayNames[agentId] ?? agentId)
82
+ .filter((value) => value.trim().length > 0)
83
+ const participantSummary =
84
+ participantNames.length > 0 ? participantNames.join(', ') : 'the configured specialist participants'
85
+
82
86
  return createTool({
83
- description:
84
- 'Consult the specialist team in parallel before replying. Use this when the answer benefits from structured executive input across product, engineering, finance, marketing, strategy, and mentorship.',
87
+ description: `Consult the specialist team in parallel before replying. Use this when the answer benefits from structured input across ${participantSummary}.`,
85
88
  inputSchema: ConsultTeamArgsSchema,
86
89
  execute: async function* ({ task }) {
87
90
  const uploadMetadataText = buildReadableUploadMetadataText(params.availableUploads)
@@ -1,10 +1,11 @@
1
- import { agentDisplayNames } from '../config/agent-defaults'
1
+ import { agentDisplayNames, getLeadAgentDisplayName } from '../config/agent-defaults'
2
2
 
3
3
  export function buildTeamConsultationResponseGuard(params: { agentId: string; task: string }) {
4
4
  const agentName = agentDisplayNames[params.agentId] ?? params.agentId
5
+ const leadAgentDisplayName = getLeadAgentDisplayName()
5
6
  return [
6
7
  '<team-consultation-agent-protocol>',
7
- '- You are participating in a structured internal team consultation led by Chief of Staff.',
8
+ `- You are participating in a structured internal team consultation led by ${leadAgentDisplayName}.`,
8
9
  `- Your role for this response is ${agentName}.`,
9
10
  '- Use markdown when it helps clarity.',
10
11
  '- Make the recommendation, explain key tradeoffs, and note the next decision.',
@@ -1,4 +1,4 @@
1
- import type { ChatMessage } from '@lota-sdk/shared/schemas/chat-message'
1
+ import type { ChatMessage } from '@lota-sdk/shared'
2
2
 
3
3
  export async function finalizeTurnRun(params: {
4
4
  serverRunId: string
@@ -9,7 +9,17 @@ export async function finalizeTurnRun(params: {
9
9
  unregisterRun: (runId: string) => void
10
10
  clearActiveRunId: (runId: string) => Promise<void>
11
11
  disposeAbort: () => void
12
+ activeStreamId?: string
13
+ clearActiveStreamId?: (streamId: string) => Promise<void>
12
14
  }): Promise<void> {
15
+ // Clear the active run immediately so new messages are not blocked
16
+ params.unregisterRun(params.serverRunId)
17
+ await params.clearActiveRunId(params.serverRunId)
18
+ if (params.activeStreamId && params.clearActiveStreamId) {
19
+ await params.clearActiveStreamId(params.activeStreamId)
20
+ }
21
+ params.disposeAbort()
22
+
13
23
  try {
14
24
  const entity = await params.getEntity()
15
25
  const cursor = typeof entity.lastCompactedMessageId === 'string' ? entity.lastCompactedMessageId : undefined
@@ -20,9 +30,7 @@ export async function finalizeTurnRun(params: {
20
30
  if (shouldCompact) {
21
31
  await params.enqueueCompaction()
22
32
  }
23
- } finally {
24
- params.unregisterRun(params.serverRunId)
25
- await params.clearActiveRunId(params.serverRunId)
26
- params.disposeAbort()
33
+ } catch {
34
+ // compaction assessment errors should not surface to callers
27
35
  }
28
36
  }
@@ -16,49 +16,6 @@ function getAgentName(message: ChatMessageLike): string | undefined {
16
16
  return resolvedAgentName ? (agentDisplayNames[resolvedAgentName] ?? value.trim()) : value.trim()
17
17
  }
18
18
 
19
- function truncateTrackerText(value: string, maxChars: number): string {
20
- return value.length <= maxChars ? value : `${value.slice(0, maxChars).trimEnd()}...`
21
- }
22
-
23
- function toTrackerToolLine(part: ChatMessageLike['parts'][number]): string | null {
24
- if (typeof part !== 'object') return null
25
- const record = part as Record<string, unknown>
26
- const type = typeof record.type === 'string' ? record.type : null
27
- if (!type?.startsWith('tool-')) return null
28
-
29
- const toolName = type.slice('tool-'.length) || 'unknown'
30
- const state = typeof record.state === 'string' ? record.state : null
31
- const input = record.input
32
- const output = record.output
33
-
34
- const inputTask =
35
- input && typeof input === 'object' && typeof (input as Record<string, unknown>).task === 'string'
36
- ? truncateTrackerText(((input as Record<string, unknown>).task as string).trim(), 220)
37
- : null
38
- const outputSummary =
39
- output && typeof output === 'object' && typeof (output as Record<string, unknown>).summary === 'string'
40
- ? truncateTrackerText(((output as Record<string, unknown>).summary as string).trim(), 220)
41
- : null
42
- const outputResult =
43
- output && typeof output === 'object' && typeof (output as Record<string, unknown>).result === 'string'
44
- ? truncateTrackerText(((output as Record<string, unknown>).result as string).trim(), 220)
45
- : null
46
- const errorText = typeof record.errorText === 'string' ? truncateTrackerText(record.errorText.trim(), 220) : null
47
-
48
- if (state === 'output-error') {
49
- return errorText ? `Tool ${toolName} failed: ${errorText}` : `Tool ${toolName} failed.`
50
- }
51
-
52
- if (state !== 'output-available') {
53
- return inputTask ? `Tool ${toolName} ran for: ${inputTask}` : `Tool ${toolName} ran.`
54
- }
55
-
56
- if (outputSummary) return `Tool ${toolName} completed: ${outputSummary}`
57
- if (inputTask) return `Tool ${toolName} completed for: ${inputTask}`
58
- if (outputResult) return `Tool ${toolName} completed: ${outputResult}`
59
- return `Tool ${toolName} completed.`
60
- }
61
-
62
19
  export function extractMessageText(message: ChatMessageLike): string {
63
20
  return message.parts
64
21
  .flatMap((part) => (part.type === 'text' && typeof part.text === 'string' ? [part.text] : []))
@@ -66,17 +23,6 @@ export function extractMessageText(message: ChatMessageLike): string {
66
23
  .trim()
67
24
  }
68
25
 
69
- export function extractTrackerMessageText(message: ChatMessageLike): string {
70
- const textParts = message.parts.flatMap((part) =>
71
- part.type === 'text' && typeof part.text === 'string' ? [part.text] : [],
72
- )
73
- const toolParts = message.parts
74
- .map((part) => toTrackerToolLine(part))
75
- .filter((value): value is string => Boolean(value))
76
-
77
- return [...textParts, ...toolParts].join('\n').trim()
78
- }
79
-
80
26
  export function toHistoryMessages(messages: ChatMessageLike[], maxItems = 24): WorkstreamHistoryMessage[] {
81
27
  return messages
82
28
  .map((message): WorkstreamHistoryMessage | null => {
@@ -1,4 +1,4 @@
1
- import { extractAgentMentions } from '../config/agent-defaults'
1
+ import { extractAgentMentions, getLeadAgentId } from '../config/agent-defaults'
2
2
 
3
3
  export type MessageRoute =
4
4
  | { type: 'direct'; agents: [string] }
@@ -285,7 +285,7 @@ export function resolveMessageRoute(params: {
285
285
  message: string
286
286
  }): MessageRoute {
287
287
  if (params.workstreamMode === 'direct') {
288
- return { type: 'direct', agents: [params.workstreamAgentId ?? 'chief'] }
288
+ return { type: 'direct', agents: [params.workstreamAgentId ?? getLeadAgentId()] }
289
289
  }
290
290
 
291
291
  const mentions = uniqueMentionOrder(params.message)
@@ -293,9 +293,5 @@ export function resolveMessageRoute(params: {
293
293
  return { type: 'mentions', agents: mentions }
294
294
  }
295
295
 
296
- if (isGtmIntentMessage(params.message)) {
297
- return { type: 'group-default', agents: ['cmo'] }
298
- }
299
-
300
- return { type: 'group-default', agents: ['chief'] }
296
+ return { type: 'group-default', agents: [getLeadAgentId()] }
301
297
  }
package/src/runtime.ts ADDED
@@ -0,0 +1,387 @@
1
+ import { configureEmbeddingCache } from './ai/embedding-cache'
2
+ import { configureAgentFactory, configureAgents } from './config/agent-defaults'
3
+ import { configureBackgroundProcessing } from './config/background-processing'
4
+ import { configureLogger } from './config/logger'
5
+ import { configureWorkstreams } from './config/workstream-defaults'
6
+ import { ensureRecordId } from './db/record-id'
7
+ import { computeSchemaFingerprint } from './db/schema-fingerprint'
8
+ import { LOTA_SDK_DATABASE_NAME } from './db/sdk-database'
9
+ import type { SurrealDBService } from './db/service'
10
+ import { SurrealDBService as SurrealDBServiceClass, setDatabaseService } from './db/service'
11
+ import { publishDatabaseBootstrap } from './db/startup'
12
+ import { TABLES } from './db/tables'
13
+ import type { RedisConnectionManager } from './redis/connection'
14
+ import { createRedisConnectionManager } from './redis/connection'
15
+ import { setRedisConnectionManager } from './redis/index'
16
+ import type { isApprovalContinuationRequest } from './runtime/approval-continuation'
17
+ import { routeWorkstreamChatMessages } from './runtime/chat-request-routing'
18
+ import type { LotaPlugin } from './runtime/plugin-types'
19
+ import { configureRuntimeConfig, LOTA_RUNTIME_ENV_KEYS, parseLotaRuntimeConfig } from './runtime/runtime-config'
20
+ import type { LotaRuntimeConfig, ResolvedLotaRuntimeConfig } from './runtime/runtime-config'
21
+ import { configureRuntimeExtensions } from './runtime/runtime-extensions'
22
+ import type { LotaRuntimeWorkers } from './runtime/runtime-worker-registry'
23
+ import { buildRuntimeWorkerRegistry } from './runtime/runtime-worker-registry'
24
+ import type { attachmentService } from './services/attachment.service'
25
+ import { attachmentService as attachmentServiceSingleton } from './services/attachment.service'
26
+ import type { documentChunkService } from './services/document-chunk.service'
27
+ import { documentChunkService as documentChunkServiceSingleton } from './services/document-chunk.service'
28
+ import type { executionPlanService } from './services/execution-plan.service'
29
+ import { executionPlanService as executionPlanServiceSingleton } from './services/execution-plan.service'
30
+ import type { memoryService } from './services/memory.service'
31
+ import { memoryService as memoryServiceSingleton } from './services/memory.service'
32
+ import type { verifyMutatingApproval } from './services/mutating-approval.service'
33
+ import { verifyMutatingApproval as verifyMutatingApprovalSingleton } from './services/mutating-approval.service'
34
+ import type { organizationMemberService } from './services/organization-member.service'
35
+ import { organizationMemberService as organizationMemberServiceSingleton } from './services/organization-member.service'
36
+ import type { organizationService } from './services/organization.service'
37
+ import { organizationService as organizationServiceSingleton } from './services/organization.service'
38
+ import type { recentActivityTitleService } from './services/recent-activity-title.service'
39
+ import { recentActivityTitleService as recentActivityTitleServiceSingleton } from './services/recent-activity-title.service'
40
+ import type { recentActivityService } from './services/recent-activity.service'
41
+ import { recentActivityService as recentActivityServiceSingleton } from './services/recent-activity.service'
42
+ import type { userService } from './services/user.service'
43
+ import { userService as userServiceSingleton } from './services/user.service'
44
+ import type { workstreamMessageService } from './services/workstream-message.service'
45
+ import { workstreamMessageService as workstreamMessageServiceSingleton } from './services/workstream-message.service'
46
+ import type { workstreamTitleService } from './services/workstream-title.service'
47
+ import { workstreamTitleService as workstreamTitleServiceSingleton } from './services/workstream-title.service'
48
+ import type {
49
+ createWorkstreamApprovalContinuationStream,
50
+ createWorkstreamNativeToolApprovalStream,
51
+ createWorkstreamTurnStream,
52
+ runWorkstreamTurnInBackground,
53
+ } from './services/workstream-turn'
54
+ import {
55
+ createWorkstreamApprovalContinuationStream as createWorkstreamApprovalContinuationStreamSingleton,
56
+ createWorkstreamNativeToolApprovalStream as createWorkstreamNativeToolApprovalStreamSingleton,
57
+ createWorkstreamTurnStream as createWorkstreamTurnStreamSingleton,
58
+ isApprovalContinuationRequest as isApprovalContinuationRequestSingleton,
59
+ runWorkstreamTurnInBackground as runWorkstreamTurnInBackgroundSingleton,
60
+ } from './services/workstream-turn'
61
+ import type { workstreamService } from './services/workstream.service'
62
+ import { workstreamService as workstreamServiceSingleton } from './services/workstream.service'
63
+ import type { generatedDocumentStorageService } from './storage/generated-document-storage.service'
64
+ import { generatedDocumentStorageService as generatedDocumentStorageServiceSingleton } from './storage/generated-document-storage.service'
65
+
66
+ type ArchiveSdkWorkstream = (
67
+ workstreamId: Parameters<typeof workstreamServiceSingleton.updateStatus>[0],
68
+ status?: 'archived',
69
+ ) => ReturnType<typeof workstreamServiceSingleton.updateStatus>
70
+
71
+ type UnarchiveSdkWorkstream = (
72
+ workstreamId: Parameters<typeof workstreamServiceSingleton.updateStatus>[0],
73
+ status?: 'regular',
74
+ ) => ReturnType<typeof workstreamServiceSingleton.updateStatus>
75
+
76
+ export interface LotaRuntime {
77
+ services: {
78
+ database: SurrealDBService
79
+ databaseService: SurrealDBService
80
+ redis: RedisConnectionManager
81
+ closeRedisConnection: () => Promise<void>
82
+ attachmentService: typeof attachmentService
83
+ documentChunkService: typeof documentChunkService
84
+ generatedDocumentStorageService: typeof generatedDocumentStorageService
85
+ memoryService: typeof memoryService
86
+ verifyMutatingApproval: typeof verifyMutatingApproval
87
+ organizationService: typeof organizationService
88
+ organizationMemberService: typeof organizationMemberService
89
+ userService: typeof userService
90
+ recentActivityService: typeof recentActivityService
91
+ recentActivityTitleService: typeof recentActivityTitleService
92
+ executionPlanService: typeof executionPlanService
93
+ workstreamMessageService: typeof workstreamMessageService
94
+ workstreamService: typeof workstreamService
95
+ workstreamTitleService: typeof workstreamTitleService
96
+ createWorkstreamApprovalContinuationStream: typeof createWorkstreamApprovalContinuationStream
97
+ createWorkstreamNativeToolApprovalStream: typeof createWorkstreamNativeToolApprovalStream
98
+ createWorkstreamTurnStream: typeof createWorkstreamTurnStream
99
+ isApprovalContinuationRequest: typeof isApprovalContinuationRequest
100
+ runWorkstreamTurnInBackground: typeof runWorkstreamTurnInBackground
101
+ }
102
+ lota: {
103
+ organizations: {
104
+ create: typeof organizationServiceSingleton.createOrganization
105
+ upsert: typeof organizationServiceSingleton.upsertOrganization
106
+ get: typeof organizationServiceSingleton.getOrganization
107
+ list: typeof organizationServiceSingleton.listOrganizations
108
+ update: typeof organizationServiceSingleton.updateOrganization
109
+ delete: typeof organizationServiceSingleton.deleteOrganization
110
+ }
111
+ users: {
112
+ upsert: typeof userServiceSingleton.upsertUser
113
+ get: typeof userServiceSingleton.getUser
114
+ list: typeof userServiceSingleton.listUsers
115
+ update: typeof userServiceSingleton.updateUser
116
+ delete: typeof userServiceSingleton.deleteUser
117
+ }
118
+ memberships: {
119
+ add: typeof organizationMemberServiceSingleton.addMembership
120
+ listForOrganization: typeof organizationMemberServiceSingleton.listMembershipsForOrganization
121
+ listForUser: typeof organizationMemberServiceSingleton.listMembershipsForUser
122
+ remove: typeof organizationMemberServiceSingleton.removeMembership
123
+ isMember: typeof organizationMemberServiceSingleton.isMember
124
+ }
125
+ workstreams: {
126
+ create: typeof workstreamServiceSingleton.createWorkstream
127
+ list: typeof workstreamServiceSingleton.listWorkstreams
128
+ get: typeof workstreamServiceSingleton.getWorkstream
129
+ update: typeof workstreamServiceSingleton.updateTitle
130
+ archive: ArchiveSdkWorkstream
131
+ unarchive: UnarchiveSdkWorkstream
132
+ delete: typeof workstreamServiceSingleton.deleteWorkstream
133
+ stop: typeof workstreamServiceSingleton.stopActiveRun
134
+ listMessages: typeof workstreamMessageServiceSingleton.listMessageHistoryPage
135
+ getMessage: (params: { workstreamId: string; messageId: string }) => Promise<unknown>
136
+ sendMessage: (params: {
137
+ workstreamId: string
138
+ organizationId: string
139
+ userId: string
140
+ userName: string
141
+ messages: Parameters<typeof routeWorkstreamChatMessages>[0]
142
+ }) => Promise<Awaited<ReturnType<typeof createWorkstreamTurnStream>>>
143
+ continueApproval: (params: {
144
+ workstreamId: string
145
+ organizationId: string
146
+ userId: string
147
+ userName: string
148
+ messages: Parameters<typeof routeWorkstreamChatMessages>[0]
149
+ }) => Promise<Awaited<ReturnType<typeof createWorkstreamApprovalContinuationStream>>>
150
+ uploadAttachment: typeof attachmentServiceSingleton.uploadWorkstreamAttachment
151
+ }
152
+ }
153
+ redis: {
154
+ manager: RedisConnectionManager
155
+ getConnection: () => ReturnType<RedisConnectionManager['getConnection']>
156
+ getConnectionForBullMQ: () => ReturnType<RedisConnectionManager['getConnectionForBullMQ']>
157
+ closeConnection: () => Promise<void>
158
+ }
159
+ workers: LotaRuntimeWorkers
160
+ schemaFiles: Array<string | URL>
161
+ contributions: { envKeys: readonly string[]; schemaFiles: Array<string | URL> }
162
+ config: ResolvedLotaRuntimeConfig
163
+ plugins: Record<string, LotaPlugin>
164
+ connectPluginDatabases(): Promise<void>
165
+ connect(): Promise<void>
166
+ disconnect(): Promise<void>
167
+ }
168
+
169
+ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<LotaRuntime> {
170
+ const resolvedConfig = parseLotaRuntimeConfig(config)
171
+ configureRuntimeConfig(resolvedConfig)
172
+
173
+ await configureLogger(resolvedConfig.logging.level)
174
+
175
+ const db = new SurrealDBServiceClass({
176
+ url: resolvedConfig.database.url,
177
+ namespace: resolvedConfig.database.namespace,
178
+ database: LOTA_SDK_DATABASE_NAME,
179
+ username: resolvedConfig.database.username,
180
+ password: resolvedConfig.database.password,
181
+ })
182
+ setDatabaseService(db)
183
+
184
+ const redisManager = createRedisConnectionManager({ url: resolvedConfig.redis.url })
185
+ setRedisConnectionManager(redisManager)
186
+ configureEmbeddingCache(redisManager.getConnection(), resolvedConfig.memory.embeddingCacheTtlSeconds)
187
+ configureBackgroundProcessing(resolvedConfig.backgroundProcessing)
188
+
189
+ configureAgents({
190
+ roster: resolvedConfig.agents.roster,
191
+ leadAgentId: resolvedConfig.agents.leadAgentId,
192
+ displayNames: resolvedConfig.agents.displayNames,
193
+ shortDisplayNames: resolvedConfig.agents.shortDisplayNames,
194
+ teamConsultParticipants: resolvedConfig.agents.teamConsultParticipants,
195
+ getCoreWorkstreamProfile: resolvedConfig.agents.getCoreWorkstreamProfile,
196
+ })
197
+ configureAgentFactory({
198
+ createAgent: resolvedConfig.agents.createAgent,
199
+ buildAgentTools: resolvedConfig.agents.buildAgentTools,
200
+ getAgentRuntimeConfig: resolvedConfig.agents.getAgentRuntimeConfig,
201
+ pluginRuntime: resolvedConfig.pluginRuntime,
202
+ })
203
+ configureWorkstreams({ agentRoster: resolvedConfig.agents.roster, config: resolvedConfig.workstreams })
204
+ configureRuntimeExtensions({
205
+ adapters: resolvedConfig.runtimeAdapters,
206
+ turnHooks: resolvedConfig.turnHooks,
207
+ toolProviders: (resolvedConfig.toolProviders ?? {}) as never,
208
+ extraWorkers: resolvedConfig.extraWorkers,
209
+ })
210
+
211
+ const pluginRuntime = resolvedConfig.pluginRuntime ?? {}
212
+ const pluginContributions = Object.values(pluginRuntime).map((plugin) => plugin.contributions)
213
+ const schemaFiles = [...getBuiltInSchemaFiles(), ...(resolvedConfig.extraSchemaFiles ?? [])]
214
+ const hostContributionSchemaFiles = pluginContributions.flatMap((plugin) => plugin.schemaFiles)
215
+ const contributionEnvKeys = [...LOTA_RUNTIME_ENV_KEYS, ...pluginContributions.flatMap((plugin) => plugin.envKeys)]
216
+ const connectPluginDatabases = createPluginDatabaseConnector(pluginRuntime)
217
+ const workers = buildRuntimeWorkerRegistry(resolvedConfig.extraWorkers)
218
+
219
+ const lota = {
220
+ organizations: {
221
+ create: organizationServiceSingleton.createOrganization.bind(organizationServiceSingleton),
222
+ upsert: organizationServiceSingleton.upsertOrganization.bind(organizationServiceSingleton),
223
+ get: organizationServiceSingleton.getOrganization.bind(organizationServiceSingleton),
224
+ list: organizationServiceSingleton.listOrganizations.bind(organizationServiceSingleton),
225
+ update: organizationServiceSingleton.updateOrganization.bind(organizationServiceSingleton),
226
+ delete: organizationServiceSingleton.deleteOrganization.bind(organizationServiceSingleton),
227
+ },
228
+ users: {
229
+ upsert: userServiceSingleton.upsertUser.bind(userServiceSingleton),
230
+ get: userServiceSingleton.getUser.bind(userServiceSingleton),
231
+ list: userServiceSingleton.listUsers.bind(userServiceSingleton),
232
+ update: userServiceSingleton.updateUser.bind(userServiceSingleton),
233
+ delete: userServiceSingleton.deleteUser.bind(userServiceSingleton),
234
+ },
235
+ memberships: {
236
+ add: organizationMemberServiceSingleton.addMembership.bind(organizationMemberServiceSingleton),
237
+ listForOrganization: organizationMemberServiceSingleton.listMembershipsForOrganization.bind(
238
+ organizationMemberServiceSingleton,
239
+ ),
240
+ listForUser: organizationMemberServiceSingleton.listMembershipsForUser.bind(organizationMemberServiceSingleton),
241
+ remove: organizationMemberServiceSingleton.removeMembership.bind(organizationMemberServiceSingleton),
242
+ isMember: organizationMemberServiceSingleton.isMember.bind(organizationMemberServiceSingleton),
243
+ },
244
+ workstreams: {
245
+ create: workstreamServiceSingleton.createWorkstream.bind(workstreamServiceSingleton),
246
+ list: workstreamServiceSingleton.listWorkstreams.bind(workstreamServiceSingleton),
247
+ get: workstreamServiceSingleton.getWorkstream.bind(workstreamServiceSingleton),
248
+ update: workstreamServiceSingleton.updateTitle.bind(workstreamServiceSingleton),
249
+ archive: async (workstreamId, status = 'archived') =>
250
+ await workstreamServiceSingleton.updateStatus(workstreamId, status),
251
+ unarchive: async (workstreamId, status = 'regular') =>
252
+ await workstreamServiceSingleton.updateStatus(workstreamId, status),
253
+ delete: workstreamServiceSingleton.deleteWorkstream.bind(workstreamServiceSingleton),
254
+ stop: workstreamServiceSingleton.stopActiveRun.bind(workstreamServiceSingleton),
255
+ listMessages: workstreamMessageServiceSingleton.listMessageHistoryPage.bind(workstreamMessageServiceSingleton),
256
+ getMessage: async ({ workstreamId, messageId }) => {
257
+ const messages = await workstreamMessageServiceSingleton.listMessages(
258
+ ensureRecordId(workstreamId, TABLES.WORKSTREAM),
259
+ )
260
+ const message = messages.find((candidate) => candidate.id === messageId)
261
+ if (!message) {
262
+ throw new Error(`Workstream message not found: ${messageId}`)
263
+ }
264
+ return message
265
+ },
266
+ sendMessage: async ({ workstreamId, organizationId, userId, userName, messages }) => {
267
+ const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
268
+ const workstream = await workstreamServiceSingleton.getWorkstream(workstreamRef)
269
+ const routed = routeWorkstreamChatMessages(messages)
270
+ if (routed.kind !== 'turn') {
271
+ throw new Error(routed.kind === 'invalid' ? routed.message : 'Expected a user turn payload.')
272
+ }
273
+
274
+ return await createWorkstreamTurnStreamSingleton({
275
+ workstream,
276
+ workstreamRef,
277
+ orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
278
+ userRef: ensureRecordId(userId, TABLES.USER),
279
+ userName,
280
+ inputMessage: routed.inputMessage,
281
+ })
282
+ },
283
+ continueApproval: async ({ workstreamId, organizationId, userId, userName, messages }) => {
284
+ const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
285
+ const workstream = await workstreamServiceSingleton.getWorkstream(workstreamRef)
286
+ const routed = routeWorkstreamChatMessages(messages)
287
+ if (routed.kind !== 'approval-continuation') {
288
+ throw new Error(
289
+ routed.kind === 'invalid' ? routed.message : 'Expected approval continuation messages payload.',
290
+ )
291
+ }
292
+
293
+ return await createWorkstreamApprovalContinuationStreamSingleton({
294
+ workstream,
295
+ workstreamRef,
296
+ orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
297
+ userRef: ensureRecordId(userId, TABLES.USER),
298
+ userName,
299
+ approvalMessages: routed.approvalMessages,
300
+ })
301
+ },
302
+ uploadAttachment: attachmentServiceSingleton.uploadWorkstreamAttachment.bind(attachmentServiceSingleton),
303
+ },
304
+ } satisfies LotaRuntime['lota']
305
+
306
+ return {
307
+ services: {
308
+ database: db,
309
+ databaseService: db,
310
+ redis: redisManager,
311
+ closeRedisConnection: async () => await redisManager.closeConnection(),
312
+ attachmentService: attachmentServiceSingleton,
313
+ documentChunkService: documentChunkServiceSingleton,
314
+ generatedDocumentStorageService: generatedDocumentStorageServiceSingleton,
315
+ memoryService: memoryServiceSingleton,
316
+ verifyMutatingApproval: verifyMutatingApprovalSingleton,
317
+ organizationService: organizationServiceSingleton,
318
+ organizationMemberService: organizationMemberServiceSingleton,
319
+ userService: userServiceSingleton,
320
+ recentActivityService: recentActivityServiceSingleton,
321
+ recentActivityTitleService: recentActivityTitleServiceSingleton,
322
+ executionPlanService: executionPlanServiceSingleton,
323
+ workstreamMessageService: workstreamMessageServiceSingleton,
324
+ workstreamService: workstreamServiceSingleton,
325
+ workstreamTitleService: workstreamTitleServiceSingleton,
326
+ createWorkstreamApprovalContinuationStream: createWorkstreamApprovalContinuationStreamSingleton,
327
+ createWorkstreamNativeToolApprovalStream: createWorkstreamNativeToolApprovalStreamSingleton,
328
+ createWorkstreamTurnStream: createWorkstreamTurnStreamSingleton,
329
+ isApprovalContinuationRequest: isApprovalContinuationRequestSingleton,
330
+ runWorkstreamTurnInBackground: runWorkstreamTurnInBackgroundSingleton,
331
+ },
332
+ lota,
333
+ redis: {
334
+ manager: redisManager,
335
+ getConnection: () => redisManager.getConnection(),
336
+ getConnectionForBullMQ: () => redisManager.getConnectionForBullMQ(),
337
+ closeConnection: async () => await redisManager.closeConnection(),
338
+ },
339
+ workers,
340
+ schemaFiles,
341
+ contributions: { envKeys: [...new Set(contributionEnvKeys)], schemaFiles: hostContributionSchemaFiles },
342
+ config: resolvedConfig,
343
+ plugins: pluginRuntime,
344
+ async connectPluginDatabases() {
345
+ await connectPluginDatabases()
346
+ },
347
+ async connect() {
348
+ await db.connect()
349
+ const bunFiles = schemaFiles.map((schemaFile) =>
350
+ schemaFile instanceof URL ? Bun.file(schemaFile.pathname) : Bun.file(schemaFile),
351
+ )
352
+ await db.applySchemaAndMigrations(bunFiles)
353
+ const schemaFingerprint = await computeSchemaFingerprint(schemaFiles)
354
+ await publishDatabaseBootstrap({ databaseService: db, schemaFingerprint })
355
+ },
356
+ async disconnect() {
357
+ await db.disconnect()
358
+ await redisManager.closeConnection()
359
+ },
360
+ }
361
+ }
362
+
363
+ function getBuiltInSchemaFiles(): URL[] {
364
+ return [
365
+ new URL('../infrastructure/schema/00_identity.surql', import.meta.url),
366
+ new URL('../infrastructure/schema/00_workstream.surql', import.meta.url),
367
+ new URL('../infrastructure/schema/01_memory.surql', import.meta.url),
368
+ new URL('../infrastructure/schema/02_execution_plan.surql', import.meta.url),
369
+ new URL('../infrastructure/schema/03_learned_skill.surql', import.meta.url),
370
+ new URL('../infrastructure/schema/05_recent_activity.surql', import.meta.url),
371
+ new URL('../infrastructure/schema/04_runtime_bootstrap.surql', import.meta.url),
372
+ ]
373
+ }
374
+
375
+ function createPluginDatabaseConnector(pluginRuntime: Record<string, LotaPlugin>): () => Promise<void> {
376
+ return async () => {
377
+ for (const plugin of Object.values(pluginRuntime)) {
378
+ const services = plugin.services as Record<string, unknown>
379
+ const connectDatabase = services.connectDatabase
380
+ if (typeof connectDatabase !== 'function') {
381
+ continue
382
+ }
383
+
384
+ await Reflect.apply(connectDatabase, services, [])
385
+ }
386
+ }
387
+ }
@@ -1,4 +1,4 @@
1
- import type { ChatMessage } from '@lota-sdk/shared/schemas/chat-message'
1
+ import type { ChatMessage } from '@lota-sdk/shared'
2
2
 
3
3
  import type { RecordIdRef } from '../db/record-id'
4
4
  import { attachmentService } from './attachment.service'
@@ -1,4 +1,4 @@
1
- import type { ChatMessage } from '@lota-sdk/shared/schemas/chat-message'
1
+ import type { ChatMessage } from '@lota-sdk/shared'
2
2
 
3
3
  import { chatLogger } from '../config/logger'
4
4
  import type { RecordIdRef } from '../db/record-id'
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto'
2
2
 
3
3
  import { chunkMarkdownDocument, chunkPagedDocument, chunkPlainTextDocument } from '../document/org-document-chunking'
4
4
  import type { ParsedDocumentChunk } from '../document/org-document-chunking'
5
- import { createDefaultEmbeddings } from '../embeddings/provider'
5
+ import { getDefaultEmbeddings } from '../embeddings/provider'
6
6
 
7
7
  type DocumentChunkEmbeddings = {
8
8
  embedDocuments(documents: string[]): Promise<number[][]>
@@ -10,7 +10,7 @@ type DocumentChunkEmbeddings = {
10
10
  }
11
11
 
12
12
  function createDocumentChunkEmbeddings(): DocumentChunkEmbeddings {
13
- const embeddings = createDefaultEmbeddings()
13
+ const embeddings = getDefaultEmbeddings()
14
14
 
15
15
  return {
16
16
  embedDocuments: async (documents) => await embeddings.embedDocuments(documents),