@lota-sdk/core 0.1.11 → 0.1.13

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 (95) hide show
  1. package/package.json +2 -87
  2. package/src/ai/index.ts +3 -0
  3. package/src/bifrost/index.ts +1 -0
  4. package/src/config/agent-defaults.ts +30 -7
  5. package/src/config/constants.ts +0 -9
  6. package/src/config/debug-logger.ts +43 -0
  7. package/src/config/index.ts +5 -0
  8. package/src/config/model-constants.ts +0 -3
  9. package/src/config/workstream-defaults.ts +4 -0
  10. package/src/db/cursor-pagination.ts +2 -2
  11. package/src/db/index.ts +10 -0
  12. package/src/db/memory.ts +9 -15
  13. package/src/document/index.ts +2 -0
  14. package/src/document/parsing.ts +0 -25
  15. package/src/embeddings/provider.ts +17 -8
  16. package/src/index.ts +15 -505
  17. package/src/queues/index.ts +10 -0
  18. package/src/redis/connection-accessor.ts +26 -0
  19. package/src/redis/connection.ts +1 -1
  20. package/src/redis/index.ts +9 -25
  21. package/src/redis/org-memory-lock.ts +1 -1
  22. package/src/redis/redis-lease-lock.ts +1 -1
  23. package/src/redis/stream-context.ts +12 -2
  24. package/src/runtime/agent-runtime-policy.ts +9 -5
  25. package/src/runtime/agent-stream-helpers.ts +6 -3
  26. package/src/runtime/agent-types.ts +1 -5
  27. package/src/runtime/approval-continuation.ts +9 -1
  28. package/src/runtime/chat-attachments.ts +1 -1
  29. package/src/runtime/chat-request-routing.ts +1 -1
  30. package/src/runtime/context-compaction-runtime.ts +2 -2
  31. package/src/runtime/context-compaction.ts +1 -1
  32. package/src/runtime/execution-plan.ts +1 -1
  33. package/src/runtime/index.ts +26 -0
  34. package/src/runtime/indexed-repositories-policy.ts +10 -10
  35. package/src/runtime/memory-pipeline.ts +0 -2
  36. package/src/runtime/runtime-config.ts +238 -0
  37. package/src/runtime/runtime-extensions.ts +3 -2
  38. package/src/runtime/runtime-worker-registry.ts +47 -0
  39. package/src/runtime/team-consultation-orchestrator.ts +9 -6
  40. package/src/runtime/team-consultation-prompts.ts +3 -2
  41. package/src/runtime/turn-lifecycle.ts +1 -1
  42. package/src/runtime/workstream-chat-helpers.ts +0 -54
  43. package/src/runtime/workstream-routing-policy.ts +3 -7
  44. package/src/runtime.ts +387 -0
  45. package/src/services/chat-attachments.service.ts +1 -1
  46. package/src/services/context-compaction.service.ts +1 -1
  47. package/src/services/execution-plan.service.ts +14 -16
  48. package/src/services/index.ts +14 -0
  49. package/src/services/learned-skill.service.ts +80 -37
  50. package/src/services/memory.service.ts +5 -4
  51. package/src/services/mutating-approval.service.ts +1 -1
  52. package/src/services/organization-member.service.ts +1 -1
  53. package/src/services/organization.service.ts +1 -1
  54. package/src/services/plan-approval.service.ts +2 -2
  55. package/src/services/plan-artifact.service.ts +2 -3
  56. package/src/services/plan-builder.service.ts +1 -1
  57. package/src/services/plan-checkpoint.service.ts +2 -2
  58. package/src/services/plan-compiler.service.ts +2 -2
  59. package/src/services/plan-executor.service.ts +10 -9
  60. package/src/services/plan-run.service.ts +2 -2
  61. package/src/services/plan-validator.service.ts +4 -4
  62. package/src/services/recent-activity-title.service.ts +1 -1
  63. package/src/services/recent-activity.service.ts +14 -16
  64. package/src/services/user.service.ts +2 -2
  65. package/src/services/workstream-message.service.ts +2 -3
  66. package/src/services/workstream-title.service.ts +1 -1
  67. package/src/services/workstream-turn-preparation.ts +105 -50
  68. package/src/services/workstream-turn.ts +14 -1
  69. package/src/services/workstream.service.ts +9 -9
  70. package/src/storage/attachment-parser.ts +1 -1
  71. package/src/storage/attachment-storage.service.ts +11 -10
  72. package/src/storage/generated-document-storage.service.ts +7 -6
  73. package/src/storage/index.ts +10 -0
  74. package/src/system-agents/delegated-agent-factory.ts +78 -29
  75. package/src/system-agents/index.ts +4 -0
  76. package/src/system-agents/recent-activity-title-refiner.agent.ts +38 -3
  77. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  78. package/src/system-agents/skill-extractor.agent.ts +1 -1
  79. package/src/system-agents/skill-manager.agent.ts +2 -4
  80. package/src/tools/execution-plan.tool.ts +2 -2
  81. package/src/tools/firecrawl-client.ts +2 -2
  82. package/src/tools/index.ts +12 -0
  83. package/src/tools/research-topic.tool.ts +1 -1
  84. package/src/tools/team-think.tool.ts +1 -1
  85. package/src/tools/user-questions.tool.ts +2 -2
  86. package/src/utils/index.ts +6 -0
  87. package/src/workers/bootstrap.ts +8 -16
  88. package/src/workers/index.ts +7 -0
  89. package/src/workers/regular-chat-memory-digest.runner.ts +1 -1
  90. package/src/workers/skill-extraction.runner.ts +1 -1
  91. package/src/workers/utils/{repo-indexer-chunker.ts → file-section-chunker.ts} +23 -52
  92. package/src/workers/utils/repo-structure-extractor.ts +2 -5
  93. package/src/workers/utils/repomix-file-sections.ts +42 -0
  94. package/src/config/env-shapes.ts +0 -121
  95. package/src/runtime/agent-contract.ts +0 -1
@@ -1,6 +1,6 @@
1
1
  import { S3Client } from 'bun'
2
2
 
3
- import { env } from '../config/env-shapes'
3
+ import { getRuntimeConfig } from '../runtime/runtime-config'
4
4
 
5
5
  function toSafeSegment(value: string): string {
6
6
  return value
@@ -27,12 +27,13 @@ class GeneratedDocumentStorageService {
27
27
 
28
28
  private get client(): InstanceType<typeof S3Client> {
29
29
  if (!this._client) {
30
+ const config = getRuntimeConfig()
30
31
  this._client = new S3Client({
31
- accessKeyId: env.S3_ACCESS_KEY_ID,
32
- secretAccessKey: env.S3_SECRET_ACCESS_KEY,
33
- bucket: env.S3_BUCKET,
34
- endpoint: env.S3_ENDPOINT,
35
- region: env.S3_REGION,
32
+ accessKeyId: config.s3.accessKeyId,
33
+ secretAccessKey: config.s3.secretAccessKey,
34
+ bucket: config.s3.bucket,
35
+ endpoint: config.s3.endpoint,
36
+ region: config.s3.region,
36
37
  })
37
38
  }
38
39
  return this._client
@@ -0,0 +1,10 @@
1
+ export * from './attachment-parser'
2
+ export * from './attachment-storage.service'
3
+ export * from './generated-document-storage.service'
4
+ export type { MessagePartLike, ReadableUploadPageMode, ReadableUploadPagePart } from './attachments.types'
5
+ export {
6
+ buildOrganizationDocumentStorageKey,
7
+ buildUploadStorageKey,
8
+ buildUploadStoragePrefix,
9
+ readNonNegativeInteger,
10
+ } from './attachments.utils'
@@ -7,11 +7,12 @@ import { isRecord } from '../utils/string'
7
7
  import { assertSubstantiveAgentResult } from './agent-result'
8
8
 
9
9
  type AgentProviderOptions = ToolLoopAgentSettings['providerOptions']
10
+ type AgentModel = LanguageModel | (() => LanguageModel)
10
11
 
11
12
  interface DelegatedAgentDefinition {
12
13
  id: string
13
14
  description: string
14
- model: LanguageModel
15
+ model: AgentModel
15
16
  providerOptions?: AgentProviderOptions
16
17
  instructions: string
17
18
  tools?: ToolSet
@@ -24,6 +25,10 @@ interface DelegatedAgentDefinitionWithContext<TContext> extends Omit<DelegatedAg
24
25
  createTools: (context: TContext) => ToolSet
25
26
  }
26
27
 
28
+ function resolveAgentModel(model: AgentModel): LanguageModel {
29
+ return typeof model === 'function' ? model() : model
30
+ }
31
+
27
32
  function buildCurrentDateContext(now = new Date()): string {
28
33
  const isoDate = now.toISOString().slice(0, 10)
29
34
  const humanDate = new Intl.DateTimeFormat('en-US', {
@@ -75,6 +80,9 @@ When your analysis is complete, return your final answer directly as markdown te
75
80
  </termination>`
76
81
 
77
82
  const MAX_RETAINED_AGENT_MESSAGES = 10
83
+ const MAX_NON_SUBSTANTIVE_AGENT_RESULT_ATTEMPTS = 2
84
+ const NON_SUBSTANTIVE_AGENT_RESULT_RETRY_PROMPT =
85
+ 'Return a complete substantive markdown answer. Do not reply with an empty result, placeholder, or tool-only outcome.'
78
86
 
79
87
  export function retainCriticalAgentMessages(messages: ModelMessage[]): ModelMessage[] {
80
88
  const systemMessages = messages.filter((message) => message.role === 'system')
@@ -112,6 +120,31 @@ function resolveDelegatedAgentTemperature(definition: {
112
120
  return definition.temperature ?? 0.2
113
121
  }
114
122
 
123
+ async function generateSubstantiveDelegatedAgentResult(params: {
124
+ label: string
125
+ task: string
126
+ abortSignal?: AbortSignal
127
+ createAgent: () => ToolLoopAgent<never, ToolSet>
128
+ }): Promise<string> {
129
+ let lastError: unknown
130
+
131
+ for (let attempt = 0; attempt < MAX_NON_SUBSTANTIVE_AGENT_RESULT_ATTEMPTS; attempt += 1) {
132
+ const prompt = attempt === 0 ? params.task : `${params.task}\n\n${NON_SUBSTANTIVE_AGENT_RESULT_RETRY_PROMPT}`
133
+ const result = await params.createAgent().generate({ prompt, abortSignal: params.abortSignal })
134
+
135
+ try {
136
+ return assertSubstantiveAgentResult(result.text, params.label)
137
+ } catch (error) {
138
+ lastError = error
139
+ if (params.abortSignal?.aborted) {
140
+ throw error
141
+ }
142
+ }
143
+ }
144
+
145
+ throw lastError ?? new Error(`${params.label} must contain substantive text.`)
146
+ }
147
+
115
148
  export function createDelegatedAgentTool(definition: DelegatedAgentDefinition): ToolDefinition<void> {
116
149
  const maxSteps = definition.maxSteps ?? 10
117
150
  const temperature = resolveDelegatedAgentTemperature(definition)
@@ -124,20 +157,28 @@ export function createDelegatedAgentTool(definition: DelegatedAgentDefinition):
124
157
  inputSchema: z.object({ task: z.string().min(1) }),
125
158
  execute: async ({ task }: { task: string }, { abortSignal }) => {
126
159
  const agentTools = definition.tools
127
- const agent = new ToolLoopAgent({
128
- id: definition.id,
129
- model: definition.model,
130
- ...(definition.providerOptions ? { providerOptions: definition.providerOptions } : {}),
131
- instructions: buildDelegatedAgentInstructions(definition.instructions, agentTools),
132
- tools: agentTools,
133
- maxOutputTokens: definition.maxOutputTokens ?? 4096,
134
- ...(typeof temperature === 'number' ? { temperature } : {}),
135
- stopWhen: [stepCountIs(maxSteps)],
136
- prepareStep: async ({ messages }) => ({ messages: retainCriticalAgentMessages(messages) }),
137
- })
138
-
139
- const result = await agent.generate({ prompt: task, abortSignal })
140
- return { task, result: assertSubstantiveAgentResult(result.text, `${definition.id} result`) }
160
+ const createAgent = () =>
161
+ new ToolLoopAgent({
162
+ id: definition.id,
163
+ model: resolveAgentModel(definition.model),
164
+ ...(definition.providerOptions ? { providerOptions: definition.providerOptions } : {}),
165
+ instructions: buildDelegatedAgentInstructions(definition.instructions, agentTools),
166
+ tools: agentTools,
167
+ maxOutputTokens: definition.maxOutputTokens ?? 4096,
168
+ ...(typeof temperature === 'number' ? { temperature } : {}),
169
+ stopWhen: [stepCountIs(maxSteps)],
170
+ prepareStep: async ({ messages }) => ({ messages: retainCriticalAgentMessages(messages) }),
171
+ })
172
+
173
+ return {
174
+ task,
175
+ result: await generateSubstantiveDelegatedAgentResult({
176
+ label: `${definition.id} result`,
177
+ task,
178
+ abortSignal,
179
+ createAgent,
180
+ }),
181
+ }
141
182
  },
142
183
  }),
143
184
  } as const satisfies ToolDefinition<void>
@@ -157,20 +198,28 @@ export function createDelegatedAgentToolWithContext<TContext>(
157
198
  inputSchema: z.object({ task: z.string().min(1) }),
158
199
  execute: async ({ task }: { task: string }, { abortSignal }) => {
159
200
  const agentTools = definition.createTools(context)
160
- const agent = new ToolLoopAgent({
161
- id: definition.id,
162
- model: definition.model,
163
- ...(definition.providerOptions ? { providerOptions: definition.providerOptions } : {}),
164
- instructions: buildDelegatedAgentInstructions(definition.instructions, agentTools),
165
- tools: agentTools,
166
- maxOutputTokens: definition.maxOutputTokens ?? 4096,
167
- ...(typeof temperature === 'number' ? { temperature } : {}),
168
- stopWhen: [stepCountIs(maxSteps)],
169
- prepareStep: async ({ messages }) => ({ messages: retainCriticalAgentMessages(messages) }),
170
- })
171
-
172
- const result = await agent.generate({ prompt: task, abortSignal })
173
- return { task, result: assertSubstantiveAgentResult(result.text, `${definition.id} result`) }
201
+ const createAgent = () =>
202
+ new ToolLoopAgent({
203
+ id: definition.id,
204
+ model: resolveAgentModel(definition.model),
205
+ ...(definition.providerOptions ? { providerOptions: definition.providerOptions } : {}),
206
+ instructions: buildDelegatedAgentInstructions(definition.instructions, agentTools),
207
+ tools: agentTools,
208
+ maxOutputTokens: definition.maxOutputTokens ?? 4096,
209
+ ...(typeof temperature === 'number' ? { temperature } : {}),
210
+ stopWhen: [stepCountIs(maxSteps)],
211
+ prepareStep: async ({ messages }) => ({ messages: retainCriticalAgentMessages(messages) }),
212
+ })
213
+
214
+ return {
215
+ task,
216
+ result: await generateSubstantiveDelegatedAgentResult({
217
+ label: `${definition.id} result`,
218
+ task,
219
+ abortSignal,
220
+ createAgent,
221
+ }),
222
+ }
174
223
  },
175
224
  }),
176
225
  } as const satisfies ToolDefinition<TContext>
@@ -0,0 +1,4 @@
1
+ export * from './agent-result'
2
+ export * from './delegated-agent-factory'
3
+ export * from './helper-agent-options'
4
+ export * from './recent-activity-title-refiner.agent'
@@ -1,6 +1,7 @@
1
1
  import { ToolLoopAgent } from 'ai'
2
2
 
3
3
  import { bifrostModel } from '../bifrost/bifrost'
4
+ import { getLeadAgentDisplayName } from '../config/agent-defaults'
4
5
  import {
5
6
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
6
7
  OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
@@ -10,8 +11,42 @@ import { resolveHelperAgentOptions } from './helper-agent-options'
10
11
 
11
12
  const RECENT_ACTIVITY_TITLE_MAX_TOKENS = 256
12
13
 
14
+ export function buildRecentActivityTitleRefinerPrompt(): string {
15
+ const leadAgentDisplayName = getLeadAgentDisplayName() || 'the lead agent'
16
+
17
+ return `<agent-instructions>
18
+ You are ${leadAgentDisplayName} writing the visible title for a recent activity item.
19
+
20
+ <goal>
21
+ Turn recent activity context into a short label that helps the user quickly recognize and reopen the task.
22
+ </goal>
23
+
24
+ <rules>
25
+ - Write 4-10 words when possible.
26
+ - Prefer concrete task language over generic labels.
27
+ - Mention the agent only when it adds useful context.
28
+ - Focus on what the user was trying to accomplish.
29
+ - Avoid generic phrases like "Chat", "Conversation", "Recent activity", "Task", or "Workstream update".
30
+ - Avoid punctuation at the end.
31
+ - Do not invent details that are not present in the input.
32
+ - If the input is too weak for improvement, return the current system title.
33
+ </rules>
34
+
35
+ <examples>
36
+ - "Review pending decisions with ${leadAgentDisplayName}"
37
+ - "Extract website intelligence findings"
38
+ - "Discuss onboarding repository indexing"
39
+ - "Review PRD from GitHub indexing"
40
+ </examples>
41
+
42
+ <output>
43
+ Return only the title text. No quotes, labels, JSON, markdown, or explanation.
44
+ </output>
45
+ </agent-instructions>`
46
+ }
47
+
13
48
  export const recentActivityTitleRefinerPrompt = `<agent-instructions>
14
- You are the Chief of Staff writing the visible title for a recent activity item.
49
+ You are the lead agent writing the visible title for a recent activity item.
15
50
 
16
51
  <goal>
17
52
  Turn recent activity context into a short label that helps the user quickly recognize and reopen the task.
@@ -29,7 +64,7 @@ Turn recent activity context into a short label that helps the user quickly reco
29
64
  </rules>
30
65
 
31
66
  <examples>
32
- - "Review pending decisions with Chief"
67
+ - "Review pending decisions with the lead agent"
33
68
  - "Extract website intelligence findings"
34
69
  - "Discuss onboarding repository indexing"
35
70
  - "Review PRD from GitHub indexing"
@@ -46,7 +81,7 @@ export function createRecentActivityTitleRefinerAgent(options: CreateHelperToolL
46
81
  model: bifrostModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
47
82
  providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
48
83
  ...resolveHelperAgentOptions(options, {
49
- instructions: recentActivityTitleRefinerPrompt,
84
+ instructions: buildRecentActivityTitleRefinerPrompt(),
50
85
  maxOutputTokens: RECENT_ACTIVITY_TITLE_MAX_TOKENS,
51
86
  }),
52
87
  })
@@ -10,7 +10,7 @@ import { resolveHelperAgentOptions } from './helper-agent-options'
10
10
 
11
11
  const REGULAR_CHAT_MEMORY_DIGEST_MAX_TOKENS = 8_192
12
12
 
13
- export const regularChatMemoryDigestPrompt = `<agent-instructions>
13
+ const regularChatMemoryDigestPrompt = `<agent-instructions>
14
14
  You are the regular-chat memory digest synthesizer.
15
15
 
16
16
  <goal>
@@ -11,7 +11,7 @@ import { resolveHelperAgentOptions } from './helper-agent-options'
11
11
 
12
12
  const SKILL_EXTRACTOR_MAX_TOKENS = 8_192
13
13
 
14
- export const skillExtractorPrompt = `<agent-instructions>
14
+ const skillExtractorPrompt = `<agent-instructions>
15
15
  You are the skill extractor.
16
16
 
17
17
  <goal>
@@ -11,7 +11,7 @@ import { resolveHelperAgentOptions } from './helper-agent-options'
11
11
 
12
12
  const SKILL_MANAGER_MAX_TOKENS = 4_096
13
13
 
14
- export const skillManagerPrompt = `<agent-instructions>
14
+ const skillManagerPrompt = `<agent-instructions>
15
15
  You are the skill manager.
16
16
 
17
17
  <goal>
@@ -49,7 +49,7 @@ Return a JSON object with:
49
49
  </output-contract>
50
50
  </agent-instructions>`
51
51
 
52
- export const MergedSkillSchema = z.object({
52
+ const MergedSkillSchema = z.object({
53
53
  name: z.string(),
54
54
  description: z.string(),
55
55
  instructions: z.string(),
@@ -65,8 +65,6 @@ export const SkillManagerOutputSchema = z.object({
65
65
  mergedSkill: MergedSkillSchema.optional(),
66
66
  })
67
67
 
68
- export type SkillManagerOutput = z.infer<typeof SkillManagerOutputSchema>
69
-
70
68
  export function createSkillManagerAgent(options: CreateHelperToolLoopAgentOptions) {
71
69
  return new ToolLoopAgent({
72
70
  id: 'skill-manager',
@@ -4,8 +4,8 @@ import {
4
4
  ResumeExecutionPlanRunArgsSchema,
5
5
  ReplaceExecutionPlanArgsSchema,
6
6
  SubmitExecutionNodeResultArgsSchema,
7
- } from '@lota-sdk/shared/schemas/tools'
8
- import type { ExecutionPlanToolResultData } from '@lota-sdk/shared/schemas/tools'
7
+ } from '@lota-sdk/shared'
8
+ import type { ExecutionPlanToolResultData } from '@lota-sdk/shared'
9
9
  import { tool } from 'ai'
10
10
 
11
11
  import type { RecordIdRef } from '../db/record-id'
@@ -1,12 +1,12 @@
1
1
  import Firecrawl from '@mendable/firecrawl-js'
2
2
 
3
- import { env } from '../config/env-shapes'
3
+ import { getRuntimeConfig } from '../runtime/runtime-config'
4
4
 
5
5
  let _firecrawlClient: Firecrawl | undefined
6
6
 
7
7
  export function getFirecrawlClient(): Firecrawl {
8
8
  if (!_firecrawlClient) {
9
- _firecrawlClient = new Firecrawl({ apiKey: env.FIRECRAWL_API_KEY })
9
+ _firecrawlClient = new Firecrawl({ apiKey: getRuntimeConfig().firecrawl.apiKey })
10
10
  }
11
11
  return _firecrawlClient
12
12
  }
@@ -0,0 +1,12 @@
1
+ export * from './execution-plan.tool'
2
+ export * from './fetch-webpage.tool'
3
+ export * from './log-hello-world.tool'
4
+ export * from './memory-block.tool'
5
+ export * from './read-file-parts.tool'
6
+ export * from './remember-memory.tool'
7
+ export * from './research-topic.tool'
8
+ export * from './search-tools'
9
+ export * from './search-web.tool'
10
+ export * from './team-think.tool'
11
+ export * from './tool-contract'
12
+ export * from './user-questions.tool'
@@ -12,7 +12,7 @@ export const researchTopicTool = createDelegatedAgentTool({
12
12
  id: 'researchTopic',
13
13
  description:
14
14
  'Delegate a research task to a dedicated research agent that searches the web, fetches pages, and returns a synthesized markdown report. Call multiple instances in parallel for broad research across different topics.',
15
- model: bifrostChatModel(OPENROUTER_WEB_RESEARCH_MODEL_ID),
15
+ model: () => bifrostChatModel(OPENROUTER_WEB_RESEARCH_MODEL_ID),
16
16
  providerOptions: OPENROUTER_MEDIUM_REASONING_PROVIDER_OPTIONS,
17
17
  instructions: RESEARCHER_PROMPT,
18
18
  tools: { searchWeb: searchWebTool.create(), fetchWebpage: fetchWebpageTool.create() },
@@ -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
  import { stepCountIs } from 'ai'
3
3
  import type { ToolSet } from 'ai'
4
4
 
@@ -1,5 +1,5 @@
1
- import { USER_QUESTIONS_TOOL_NAME, UserQuestionsArgsSchema } from '@lota-sdk/shared/schemas/tools'
2
- import type { UserQuestionsArgs } from '@lota-sdk/shared/schemas/tools'
1
+ import { USER_QUESTIONS_TOOL_NAME, UserQuestionsArgsSchema } from '@lota-sdk/shared'
2
+ import type { UserQuestionsArgs } from '@lota-sdk/shared'
3
3
  import { tool } from 'ai'
4
4
 
5
5
  import type { ToolDefinition } from '../ai/definitions'
@@ -0,0 +1,6 @@
1
+ export * from './async'
2
+ export * from './date-time'
3
+ export * from './error'
4
+ export * from './errors'
5
+ export * from './hono-error-handler'
6
+ export * from './string'
@@ -2,26 +2,17 @@ import { configureLogger, serverLogger } from '../config/logger'
2
2
  import { LOTA_SDK_DATABASE_NAME } from '../db/sdk-database'
3
3
  import { SurrealDBService, databaseService, setDatabaseService } from '../db/service'
4
4
  import { connectWithStartupRetry, waitForDatabaseBootstrap } from '../db/startup'
5
+ import { parseWorkerBootstrapEnv } from '../runtime/runtime-config'
5
6
  import { getConfiguredPluginDatabaseConnector } from '../runtime/runtime-extensions'
6
7
 
7
- /**
8
- * Sandboxed BullMQ workers run in separate child processes where createLotaRuntime
9
- * was never called, so the global databaseService proxy has no backing instance.
10
- * Create one from env vars so the proxy resolves.
11
- */
12
- function getRequiredEnv(key: string): string {
13
- const value = process.env[key]
14
- if (!value) throw new Error(`Missing required env var: ${key}`)
15
- return value
16
- }
17
-
18
8
  function ensureDatabaseServiceConfigured(): void {
9
+ const env = parseWorkerBootstrapEnv(process.env)
19
10
  const db = new SurrealDBService({
20
- url: getRequiredEnv('SURREALDB_URL'),
21
- namespace: getRequiredEnv('SURREALDB_NAMESPACE'),
11
+ url: env.SURREALDB_URL,
12
+ namespace: env.SURREALDB_NAMESPACE,
22
13
  database: LOTA_SDK_DATABASE_NAME,
23
- username: process.env.SURREALDB_USER,
24
- password: process.env.SURREALDB_PASSWORD,
14
+ username: env.SURREALDB_USER,
15
+ password: env.SURREALDB_PASSWORD,
25
16
  })
26
17
  setDatabaseService(db)
27
18
  }
@@ -35,6 +26,7 @@ export async function initializeSandboxedWorkerRuntime(): Promise<void> {
35
26
  }
36
27
 
37
28
  sandboxedWorkerRuntimePromise = (async () => {
29
+ const env = parseWorkerBootstrapEnv(process.env)
38
30
  await configureLogger()
39
31
 
40
32
  ensureDatabaseServiceConfigured()
@@ -58,7 +50,7 @@ export async function initializeSandboxedWorkerRuntime(): Promise<void> {
58
50
 
59
51
  await waitForDatabaseBootstrap({
60
52
  databaseService,
61
- expectedFingerprint: process.env.DB_SCHEMA_FINGERPRINT,
53
+ expectedFingerprint: env.DB_SCHEMA_FINGERPRINT,
62
54
  label: 'sandboxed worker runtime',
63
55
  logger: serverLogger,
64
56
  connect: () => databaseService.connect(),
@@ -0,0 +1,7 @@
1
+ export * from './bootstrap'
2
+ export * from './regular-chat-memory-digest.helpers'
3
+ export * from './worker-utils'
4
+ export * from './utils/file-section-chunker'
5
+ export * from './utils/repomix-file-sections'
6
+ export * from './utils/repo-structure-extractor'
7
+ export * from './utils/repomix-process-concurrency'
@@ -1,4 +1,4 @@
1
- import { toTimestamp } from '@lota-sdk/shared/runtime/chat-message-metadata'
1
+ import { toTimestamp } from '@lota-sdk/shared'
2
2
  import { BoundQuery } from 'surrealdb'
3
3
  import { z } from 'zod'
4
4
 
@@ -1,4 +1,4 @@
1
- import { toTimestamp } from '@lota-sdk/shared/runtime/chat-message-metadata'
1
+ import { toTimestamp } from '@lota-sdk/shared'
2
2
  import { BoundQuery } from 'surrealdb'
3
3
  import { z } from 'zod'
4
4
 
@@ -1,16 +1,15 @@
1
- export const DEFAULT_REPOMIX_CHUNK_MAX_CHARS = 250_000
2
- const MIN_REPOMIX_CHUNK_MAX_CHARS = 4_000
3
- export const DEFAULT_REPOMIX_CHUNK_MIN_CHARS = 10_000
1
+ export const DEFAULT_FILE_SECTION_CHUNK_MAX_CHARS = 250_000
2
+ const MIN_FILE_SECTION_CHUNK_MAX_CHARS = 4_000
3
+ export const DEFAULT_FILE_SECTION_CHUNK_MIN_CHARS = 10_000
4
4
  const SECTION_SEPARATOR_LENGTH = 2
5
- const FILE_SECTION_HEADER_SOURCE = '^## File:\\s+(.+)$'
6
5
 
7
- interface RepomixSection {
6
+ export interface FileSection {
8
7
  kind: 'preamble' | 'file'
9
8
  content: string
10
9
  filePath?: string
11
10
  }
12
11
 
13
- export interface RepomixContextChunk {
12
+ export interface FileSectionChunk {
14
13
  index: number
15
14
  totalChunks: number
16
15
  content: string
@@ -22,7 +21,7 @@ export interface RepomixContextChunk {
22
21
  lastFilePath: string | null
23
22
  }
24
23
 
25
- interface RepomixChunkOptions {
24
+ export interface FileSectionChunkOptions {
26
25
  maxChars?: number
27
26
  minChunkChars?: number
28
27
  preserveCodeFenceIntegrity?: boolean
@@ -35,14 +34,14 @@ function estimateTokenCountFromChars(text: string): number {
35
34
 
36
35
  function normalizeMaxChars(value?: number): number {
37
36
  if (typeof value !== 'number' || !Number.isFinite(value)) {
38
- return DEFAULT_REPOMIX_CHUNK_MAX_CHARS
37
+ return DEFAULT_FILE_SECTION_CHUNK_MAX_CHARS
39
38
  }
40
- return Math.max(MIN_REPOMIX_CHUNK_MAX_CHARS, Math.floor(value))
39
+ return Math.max(MIN_FILE_SECTION_CHUNK_MAX_CHARS, Math.floor(value))
41
40
  }
42
41
 
43
42
  function normalizeMinChunkChars(value: number | undefined, maxChars: number): number {
44
43
  if (typeof value !== 'number' || !Number.isFinite(value)) {
45
- return Math.min(DEFAULT_REPOMIX_CHUNK_MIN_CHARS, Math.floor(maxChars * 0.35))
44
+ return Math.min(DEFAULT_FILE_SECTION_CHUNK_MIN_CHARS, Math.floor(maxChars * 0.35))
46
45
  }
47
46
  const normalized = Math.max(512, Math.floor(value))
48
47
  return Math.min(normalized, Math.floor(maxChars * 0.6))
@@ -156,9 +155,9 @@ function mergeTinyTailParts(parts: string[], options: { minChunkChars: number; m
156
155
  }
157
156
 
158
157
  async function splitOversizedSection(
159
- section: RepomixSection,
158
+ section: FileSection,
160
159
  options: { maxChars: number; minChunkChars: number; preserveCodeFenceIntegrity: boolean },
161
- ): Promise<RepomixSection[]> {
160
+ ): Promise<FileSection[]> {
162
161
  if (section.content.length <= options.maxChars) {
163
162
  return [section]
164
163
  }
@@ -188,43 +187,12 @@ async function splitOversizedSection(
188
187
  }))
189
188
  }
190
189
 
191
- function parseRepomixSections(repomixOutput: string): RepomixSection[] {
192
- const source = repomixOutput.trim()
193
- if (!source) return []
194
-
195
- const matches = Array.from(source.matchAll(new RegExp(FILE_SECTION_HEADER_SOURCE, 'gm')))
196
- if (matches.length === 0) {
197
- return [{ kind: 'preamble', content: source }]
198
- }
199
-
200
- const sections: RepomixSection[] = []
201
- const firstMatch = matches.at(0)
202
- const firstIndex = firstMatch ? firstMatch.index : 0
203
- if (firstIndex > 0) {
204
- const preamble = source.slice(0, firstIndex).trim()
205
- if (preamble) {
206
- sections.push({ kind: 'preamble', content: preamble })
207
- }
208
- }
209
-
210
- for (const [index, match] of matches.entries()) {
211
- const start = match.index
212
- const nextStart = matches[index + 1]?.index ?? source.length
213
- const content = source.slice(start, nextStart).trim()
214
- if (!content) continue
215
- const filePath = (match[1] ?? '').trim()
216
- sections.push({ kind: 'file', content, filePath: filePath || undefined })
217
- }
218
-
219
- return sections
220
- }
221
-
222
190
  function mergeTinyChunks(
223
- chunks: Omit<RepomixContextChunk, 'index' | 'totalChunks'>[],
191
+ chunks: Omit<FileSectionChunk, 'index' | 'totalChunks'>[],
224
192
  options: { minChunkChars: number; maxChars: number },
225
- ): Omit<RepomixContextChunk, 'index' | 'totalChunks'>[] {
193
+ ): Omit<FileSectionChunk, 'index' | 'totalChunks'>[] {
226
194
  if (chunks.length <= 1) return chunks
227
- const merged: Omit<RepomixContextChunk, 'index' | 'totalChunks'>[] = []
195
+ const merged: Omit<FileSectionChunk, 'index' | 'totalChunks'>[] = []
228
196
 
229
197
  for (const chunk of chunks) {
230
198
  const previous = merged.at(-1)
@@ -252,15 +220,18 @@ function mergeTinyChunks(
252
220
  return merged
253
221
  }
254
222
 
255
- export async function chunkRepomixOutput(
256
- repomixOutput: string,
257
- options: RepomixChunkOptions = {},
258
- ): Promise<RepomixContextChunk[]> {
223
+ export async function chunkFileSections(
224
+ fileSections: readonly FileSection[],
225
+ options: FileSectionChunkOptions = {},
226
+ ): Promise<FileSectionChunk[]> {
259
227
  const maxChars = normalizeMaxChars(options.maxChars)
260
228
  const minChunkChars = normalizeMinChunkChars(options.minChunkChars, maxChars)
261
229
  const preserveCodeFenceIntegrity = options.preserveCodeFenceIntegrity ?? true
262
230
 
263
- const rawSections = parseRepomixSections(repomixOutput)
231
+ const rawSections = fileSections
232
+ .map((section) => ({ kind: section.kind, content: section.content.trim(), filePath: section.filePath }))
233
+ .filter((section) => section.content.length > 0)
234
+
264
235
  const splitSections = await Promise.all(
265
236
  rawSections.map(
266
237
  async (section) => await splitOversizedSection(section, { maxChars, minChunkChars, preserveCodeFenceIntegrity }),
@@ -270,7 +241,7 @@ export async function chunkRepomixOutput(
270
241
 
271
242
  if (sections.length === 0) return []
272
243
 
273
- const chunks: Omit<RepomixContextChunk, 'index' | 'totalChunks'>[] = []
244
+ const chunks: Omit<FileSectionChunk, 'index' | 'totalChunks'>[] = []
274
245
  let currentParts: string[] = []
275
246
  let currentCharLength = 0
276
247
  let currentSectionCount = 0
@@ -1,17 +1,14 @@
1
1
  import { readdir, readFile } from 'node:fs/promises'
2
2
  import path from 'node:path'
3
3
 
4
- import {
5
- RepositoryStructureArtifactSchema,
6
- RepositoryStructureSummarySchema,
7
- } from '@lota-sdk/shared/schemas/repository-structure'
4
+ import { RepositoryStructureArtifactSchema, RepositoryStructureSummarySchema } from '@lota-sdk/shared'
8
5
  import type {
9
6
  RepositoryStructureArtifact,
10
7
  RepositoryStructureComponent,
11
8
  RepositoryStructureShape,
12
9
  RepositoryStructureSignal,
13
10
  RepositoryStructureSummary,
14
- } from '@lota-sdk/shared/schemas/repository-structure'
11
+ } from '@lota-sdk/shared'
15
12
 
16
13
  const EXTRACTOR_VERSION = 'repository-structure-extractor.v1'
17
14
  const IGNORED_DIR_NAMES = new Set([