@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.
- package/package.json +2 -87
- package/src/ai/index.ts +3 -0
- package/src/bifrost/index.ts +1 -0
- package/src/config/agent-defaults.ts +30 -7
- package/src/config/constants.ts +0 -9
- package/src/config/debug-logger.ts +43 -0
- package/src/config/index.ts +5 -0
- package/src/config/model-constants.ts +0 -3
- package/src/config/workstream-defaults.ts +4 -0
- package/src/db/cursor-pagination.ts +2 -2
- package/src/db/index.ts +10 -0
- package/src/db/memory.ts +9 -15
- package/src/document/index.ts +2 -0
- package/src/document/parsing.ts +0 -25
- package/src/embeddings/provider.ts +17 -8
- package/src/index.ts +15 -505
- package/src/queues/index.ts +10 -0
- package/src/redis/connection-accessor.ts +26 -0
- package/src/redis/connection.ts +1 -1
- package/src/redis/index.ts +9 -25
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +1 -1
- package/src/redis/stream-context.ts +12 -2
- package/src/runtime/agent-runtime-policy.ts +9 -5
- package/src/runtime/agent-stream-helpers.ts +6 -3
- package/src/runtime/agent-types.ts +1 -5
- package/src/runtime/approval-continuation.ts +9 -1
- package/src/runtime/chat-attachments.ts +1 -1
- package/src/runtime/chat-request-routing.ts +1 -1
- package/src/runtime/context-compaction-runtime.ts +2 -2
- package/src/runtime/context-compaction.ts +1 -1
- package/src/runtime/execution-plan.ts +1 -1
- package/src/runtime/index.ts +26 -0
- package/src/runtime/indexed-repositories-policy.ts +10 -10
- package/src/runtime/memory-pipeline.ts +0 -2
- package/src/runtime/runtime-config.ts +238 -0
- package/src/runtime/runtime-extensions.ts +3 -2
- package/src/runtime/runtime-worker-registry.ts +47 -0
- package/src/runtime/team-consultation-orchestrator.ts +9 -6
- package/src/runtime/team-consultation-prompts.ts +3 -2
- package/src/runtime/turn-lifecycle.ts +1 -1
- package/src/runtime/workstream-chat-helpers.ts +0 -54
- package/src/runtime/workstream-routing-policy.ts +3 -7
- package/src/runtime.ts +387 -0
- package/src/services/chat-attachments.service.ts +1 -1
- package/src/services/context-compaction.service.ts +1 -1
- package/src/services/execution-plan.service.ts +14 -16
- package/src/services/index.ts +14 -0
- package/src/services/learned-skill.service.ts +80 -37
- package/src/services/memory.service.ts +5 -4
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/organization.service.ts +1 -1
- package/src/services/plan-approval.service.ts +2 -2
- package/src/services/plan-artifact.service.ts +2 -3
- package/src/services/plan-builder.service.ts +1 -1
- package/src/services/plan-checkpoint.service.ts +2 -2
- package/src/services/plan-compiler.service.ts +2 -2
- package/src/services/plan-executor.service.ts +10 -9
- package/src/services/plan-run.service.ts +2 -2
- package/src/services/plan-validator.service.ts +4 -4
- package/src/services/recent-activity-title.service.ts +1 -1
- package/src/services/recent-activity.service.ts +14 -16
- package/src/services/user.service.ts +2 -2
- package/src/services/workstream-message.service.ts +2 -3
- package/src/services/workstream-title.service.ts +1 -1
- package/src/services/workstream-turn-preparation.ts +105 -50
- package/src/services/workstream-turn.ts +14 -1
- package/src/services/workstream.service.ts +9 -9
- package/src/storage/attachment-parser.ts +1 -1
- package/src/storage/attachment-storage.service.ts +11 -10
- package/src/storage/generated-document-storage.service.ts +7 -6
- package/src/storage/index.ts +10 -0
- package/src/system-agents/delegated-agent-factory.ts +78 -29
- package/src/system-agents/index.ts +4 -0
- package/src/system-agents/recent-activity-title-refiner.agent.ts +38 -3
- package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
- package/src/system-agents/skill-extractor.agent.ts +1 -1
- package/src/system-agents/skill-manager.agent.ts +2 -4
- package/src/tools/execution-plan.tool.ts +2 -2
- package/src/tools/firecrawl-client.ts +2 -2
- package/src/tools/index.ts +12 -0
- package/src/tools/research-topic.tool.ts +1 -1
- package/src/tools/team-think.tool.ts +1 -1
- package/src/tools/user-questions.tool.ts +2 -2
- package/src/utils/index.ts +6 -0
- package/src/workers/bootstrap.ts +8 -16
- package/src/workers/index.ts +7 -0
- package/src/workers/regular-chat-memory-digest.runner.ts +1 -1
- package/src/workers/skill-extraction.runner.ts +1 -1
- package/src/workers/utils/{repo-indexer-chunker.ts → file-section-chunker.ts} +23 -52
- package/src/workers/utils/repo-structure-extractor.ts +2 -5
- package/src/workers/utils/repomix-file-sections.ts +42 -0
- package/src/config/env-shapes.ts +0 -121
- package/src/runtime/agent-contract.ts +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { S3Client } from 'bun'
|
|
2
2
|
|
|
3
|
-
import {
|
|
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:
|
|
32
|
-
secretAccessKey:
|
|
33
|
-
bucket:
|
|
34
|
-
endpoint:
|
|
35
|
-
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:
|
|
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
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return {
|
|
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
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return {
|
|
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>
|
|
@@ -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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
8
|
-
import type { ExecutionPlanToolResultData } from '@lota-sdk/shared
|
|
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 {
|
|
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:
|
|
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,5 +1,5 @@
|
|
|
1
|
-
import { USER_QUESTIONS_TOOL_NAME, UserQuestionsArgsSchema } from '@lota-sdk/shared
|
|
2
|
-
import type { UserQuestionsArgs } from '@lota-sdk/shared
|
|
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'
|
package/src/workers/bootstrap.ts
CHANGED
|
@@ -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:
|
|
21
|
-
namespace:
|
|
11
|
+
url: env.SURREALDB_URL,
|
|
12
|
+
namespace: env.SURREALDB_NAMESPACE,
|
|
22
13
|
database: LOTA_SDK_DATABASE_NAME,
|
|
23
|
-
username:
|
|
24
|
-
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:
|
|
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,16 +1,15 @@
|
|
|
1
|
-
export const
|
|
2
|
-
const
|
|
3
|
-
export const
|
|
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
|
|
6
|
+
export interface FileSection {
|
|
8
7
|
kind: 'preamble' | 'file'
|
|
9
8
|
content: string
|
|
10
9
|
filePath?: string
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
export interface
|
|
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
|
|
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
|
|
37
|
+
return DEFAULT_FILE_SECTION_CHUNK_MAX_CHARS
|
|
39
38
|
}
|
|
40
|
-
return Math.max(
|
|
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(
|
|
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:
|
|
158
|
+
section: FileSection,
|
|
160
159
|
options: { maxChars: number; minChunkChars: number; preserveCodeFenceIntegrity: boolean },
|
|
161
|
-
): Promise<
|
|
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<
|
|
191
|
+
chunks: Omit<FileSectionChunk, 'index' | 'totalChunks'>[],
|
|
224
192
|
options: { minChunkChars: number; maxChars: number },
|
|
225
|
-
): Omit<
|
|
193
|
+
): Omit<FileSectionChunk, 'index' | 'totalChunks'>[] {
|
|
226
194
|
if (chunks.length <= 1) return chunks
|
|
227
|
-
const merged: Omit<
|
|
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
|
|
256
|
-
|
|
257
|
-
options:
|
|
258
|
-
): Promise<
|
|
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 =
|
|
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<
|
|
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
|
|
11
|
+
} from '@lota-sdk/shared'
|
|
15
12
|
|
|
16
13
|
const EXTRACTOR_VERSION = 'repository-structure-extractor.v1'
|
|
17
14
|
const IGNORED_DIR_NAMES = new Set([
|