@lota-sdk/core 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/infrastructure/schema/00_workstream.surql +55 -0
- package/infrastructure/schema/01_memory.surql +47 -0
- package/infrastructure/schema/02_execution_plan.surql +62 -0
- package/infrastructure/schema/03_learned_skill.surql +32 -0
- package/infrastructure/schema/04_runtime_bootstrap.surql +8 -0
- package/package.json +128 -0
- package/src/ai/definitions.ts +308 -0
- package/src/bifrost/bifrost.ts +256 -0
- package/src/config/agent-defaults.ts +99 -0
- package/src/config/constants.ts +33 -0
- package/src/config/env-shapes.ts +122 -0
- package/src/config/logger.ts +29 -0
- package/src/config/model-constants.ts +31 -0
- package/src/config/search.ts +17 -0
- package/src/config/workstream-defaults.ts +68 -0
- package/src/db/base.service.ts +55 -0
- package/src/db/cursor-pagination.ts +73 -0
- package/src/db/memory-query-builder.ts +207 -0
- package/src/db/memory-store.helpers.ts +118 -0
- package/src/db/memory-store.rows.ts +29 -0
- package/src/db/memory-store.ts +974 -0
- package/src/db/memory-types.ts +193 -0
- package/src/db/memory.ts +505 -0
- package/src/db/record-id.ts +78 -0
- package/src/db/service.ts +932 -0
- package/src/db/startup.ts +152 -0
- package/src/db/tables.ts +20 -0
- package/src/document/org-document-chunking.ts +224 -0
- package/src/document/parsing.ts +40 -0
- package/src/embeddings/provider.ts +76 -0
- package/src/index.ts +302 -0
- package/src/queues/context-compaction.queue.ts +82 -0
- package/src/queues/document-processor.queue.ts +118 -0
- package/src/queues/memory-consolidation.queue.ts +65 -0
- package/src/queues/post-chat-memory.queue.ts +128 -0
- package/src/queues/recent-activity-title-refinement.queue.ts +69 -0
- package/src/queues/regular-chat-memory-digest.config.ts +12 -0
- package/src/queues/regular-chat-memory-digest.queue.ts +73 -0
- package/src/queues/skill-extraction.config.ts +9 -0
- package/src/queues/skill-extraction.queue.ts +62 -0
- package/src/redis/connection.ts +176 -0
- package/src/redis/index.ts +30 -0
- package/src/redis/org-memory-lock.ts +43 -0
- package/src/redis/redis-lease-lock.ts +158 -0
- package/src/runtime/agent-contract.ts +1 -0
- package/src/runtime/agent-prompt-context.ts +119 -0
- package/src/runtime/agent-runtime-policy.ts +192 -0
- package/src/runtime/agent-stream-helpers.ts +117 -0
- package/src/runtime/agent-types.ts +22 -0
- package/src/runtime/approval-continuation.ts +16 -0
- package/src/runtime/chat-attachments.ts +46 -0
- package/src/runtime/chat-message.ts +10 -0
- package/src/runtime/chat-request-routing.ts +21 -0
- package/src/runtime/chat-run-orchestration.ts +25 -0
- package/src/runtime/chat-run-registry.ts +20 -0
- package/src/runtime/chat-types.ts +18 -0
- package/src/runtime/context-compaction-constants.ts +11 -0
- package/src/runtime/context-compaction-runtime.ts +86 -0
- package/src/runtime/context-compaction.ts +909 -0
- package/src/runtime/execution-plan.ts +59 -0
- package/src/runtime/helper-model.ts +405 -0
- package/src/runtime/indexed-repositories-policy.ts +28 -0
- package/src/runtime/instruction-sections.ts +8 -0
- package/src/runtime/llm-content.ts +71 -0
- package/src/runtime/memory-block.ts +264 -0
- package/src/runtime/memory-digest-policy.ts +14 -0
- package/src/runtime/memory-format.ts +8 -0
- package/src/runtime/memory-pipeline.ts +570 -0
- package/src/runtime/memory-prompts-fact.ts +47 -0
- package/src/runtime/memory-prompts-parse.ts +3 -0
- package/src/runtime/memory-prompts-update.ts +37 -0
- package/src/runtime/memory-scope.ts +43 -0
- package/src/runtime/plugin-types.ts +10 -0
- package/src/runtime/retrieval-adapters.ts +25 -0
- package/src/runtime/retrieval-pipeline.ts +3 -0
- package/src/runtime/runtime-extensions.ts +154 -0
- package/src/runtime/skill-extraction-policy.ts +3 -0
- package/src/runtime/team-consultation-orchestrator.ts +245 -0
- package/src/runtime/team-consultation-prompts.ts +32 -0
- package/src/runtime/title-helpers.ts +12 -0
- package/src/runtime/turn-lifecycle.ts +28 -0
- package/src/runtime/workstream-chat-helpers.ts +187 -0
- package/src/runtime/workstream-routing-policy.ts +301 -0
- package/src/runtime/workstream-state.ts +261 -0
- package/src/services/attachment.service.ts +159 -0
- package/src/services/chat-attachments.service.ts +17 -0
- package/src/services/chat-run-registry.service.ts +3 -0
- package/src/services/context-compaction-runtime.ts +13 -0
- package/src/services/context-compaction.service.ts +115 -0
- package/src/services/document-chunk.service.ts +141 -0
- package/src/services/execution-plan.service.ts +890 -0
- package/src/services/learned-skill.service.ts +328 -0
- package/src/services/memory-assessment.service.ts +43 -0
- package/src/services/memory.service.ts +807 -0
- package/src/services/memory.utils.ts +84 -0
- package/src/services/mutating-approval.service.ts +110 -0
- package/src/services/recent-activity-title.service.ts +74 -0
- package/src/services/recent-activity.service.ts +397 -0
- package/src/services/workstream-change-tracker.service.ts +313 -0
- package/src/services/workstream-message.service.ts +283 -0
- package/src/services/workstream-title.service.ts +58 -0
- package/src/services/workstream-turn-preparation.ts +1340 -0
- package/src/services/workstream-turn.ts +37 -0
- package/src/services/workstream.service.ts +854 -0
- package/src/services/workstream.types.ts +118 -0
- package/src/storage/attachment-parser.ts +101 -0
- package/src/storage/attachment-storage.service.ts +391 -0
- package/src/storage/attachments.types.ts +11 -0
- package/src/storage/attachments.utils.ts +58 -0
- package/src/storage/generated-document-storage.service.ts +55 -0
- package/src/system-agents/agent-result.ts +27 -0
- package/src/system-agents/context-compacter.agent.ts +46 -0
- package/src/system-agents/delegated-agent-factory.ts +177 -0
- package/src/system-agents/helper-agent-options.ts +20 -0
- package/src/system-agents/memory-reranker.agent.ts +38 -0
- package/src/system-agents/memory.agent.ts +58 -0
- package/src/system-agents/recent-activity-title-refiner.agent.ts +53 -0
- package/src/system-agents/regular-chat-memory-digest.agent.ts +75 -0
- package/src/system-agents/researcher.agent.ts +34 -0
- package/src/system-agents/skill-extractor.agent.ts +88 -0
- package/src/system-agents/skill-manager.agent.ts +80 -0
- package/src/system-agents/title-generator.agent.ts +42 -0
- package/src/system-agents/workstream-tracker.agent.ts +58 -0
- package/src/tools/execution-plan.tool.ts +163 -0
- package/src/tools/fetch-webpage.tool.ts +132 -0
- package/src/tools/firecrawl-client.ts +12 -0
- package/src/tools/memory-block.tool.ts +55 -0
- package/src/tools/read-file-parts.tool.ts +80 -0
- package/src/tools/remember-memory.tool.ts +85 -0
- package/src/tools/research-topic.tool.ts +15 -0
- package/src/tools/search-tools.ts +55 -0
- package/src/tools/search-web.tool.ts +175 -0
- package/src/tools/team-think.tool.ts +125 -0
- package/src/tools/tool-contract.ts +21 -0
- package/src/tools/user-questions.tool.ts +18 -0
- package/src/utils/async.ts +50 -0
- package/src/utils/date-time.ts +34 -0
- package/src/utils/error.ts +10 -0
- package/src/utils/errors.ts +28 -0
- package/src/utils/hono-error-handler.ts +71 -0
- package/src/utils/string.ts +51 -0
- package/src/workers/bootstrap.ts +44 -0
- package/src/workers/memory-consolidation.worker.ts +318 -0
- package/src/workers/regular-chat-memory-digest.helpers.ts +100 -0
- package/src/workers/regular-chat-memory-digest.runner.ts +363 -0
- package/src/workers/regular-chat-memory-digest.worker.ts +22 -0
- package/src/workers/skill-extraction.runner.ts +331 -0
- package/src/workers/skill-extraction.worker.ts +22 -0
- package/src/workers/utils/repo-indexer-chunker.ts +331 -0
- package/src/workers/utils/repo-structure-extractor.ts +645 -0
- package/src/workers/utils/repomix-process-concurrency.ts +65 -0
- package/src/workers/utils/sandbox-error.ts +5 -0
- package/src/workers/worker-utils.ts +182 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { createOpenAI } from '@ai-sdk/openai'
|
|
2
|
+
import { wrapLanguageModel } from 'ai'
|
|
3
|
+
import type { LanguageModelMiddleware } from 'ai'
|
|
4
|
+
|
|
5
|
+
import { isRecord, readString } from '../utils/string'
|
|
6
|
+
|
|
7
|
+
type BifrostExtraParams = Record<string, unknown>
|
|
8
|
+
type BifrostChatResponse = { body?: unknown }
|
|
9
|
+
type BifrostTransformParamsOptions = Parameters<NonNullable<LanguageModelMiddleware['transformParams']>>[0]
|
|
10
|
+
type WrapStreamOptions = Parameters<NonNullable<LanguageModelMiddleware['wrapStream']>>[0]
|
|
11
|
+
type BifrostCallOptions = WrapStreamOptions['params']
|
|
12
|
+
type BifrostGenerateResult = Awaited<ReturnType<WrapStreamOptions['doGenerate']>>
|
|
13
|
+
type BifrostStreamResult = Awaited<ReturnType<WrapStreamOptions['doStream']>>
|
|
14
|
+
type BifrostGeneratedContent = BifrostGenerateResult['content'][number]
|
|
15
|
+
type BifrostStreamPart = BifrostStreamResult['stream'] extends ReadableStream<infer T> ? T : never
|
|
16
|
+
|
|
17
|
+
const OPENROUTER_RESPONSE_HEALING_EXTRA_PARAMS = {
|
|
18
|
+
plugins: [{ id: 'response-healing' }],
|
|
19
|
+
} as const satisfies BifrostExtraParams
|
|
20
|
+
|
|
21
|
+
function readRequiredGatewayEnv(name: 'AI_GATEWAY_KEY' | 'AI_GATEWAY_URL'): string {
|
|
22
|
+
const value = process.env[name]?.trim()
|
|
23
|
+
if (value) return value
|
|
24
|
+
throw new Error(`[bifrost] Missing ${name}.`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readReasoningDetailsText(value: unknown): string | null {
|
|
28
|
+
if (!Array.isArray(value)) return null
|
|
29
|
+
|
|
30
|
+
const textParts = value
|
|
31
|
+
.map((item) => (isRecord(item) ? readString(item.text) : null))
|
|
32
|
+
.filter((item): item is string => item !== null)
|
|
33
|
+
|
|
34
|
+
if (textParts.length === 0) return null
|
|
35
|
+
|
|
36
|
+
return textParts.join('\n\n')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readBifrostChatReasoningText(message: Record<string, unknown>): string | null {
|
|
40
|
+
return (
|
|
41
|
+
readString(message.reasoning) ??
|
|
42
|
+
readString(message.reasoning_content) ??
|
|
43
|
+
readReasoningDetailsText(message.reasoning_details)
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function extractBifrostChatReasoningText(responseBody: unknown): string | null {
|
|
48
|
+
if (!isRecord(responseBody) || !Array.isArray(responseBody.choices)) return null
|
|
49
|
+
|
|
50
|
+
for (const choice of responseBody.choices) {
|
|
51
|
+
if (!isRecord(choice) || !isRecord(choice.message)) continue
|
|
52
|
+
|
|
53
|
+
const reasoningText = readBifrostChatReasoningText(choice.message)
|
|
54
|
+
if (reasoningText) return reasoningText
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function extractBifrostChatReasoningDeltaText(rawChunk: unknown): string | null {
|
|
61
|
+
if (!isRecord(rawChunk) || !Array.isArray(rawChunk.choices)) return null
|
|
62
|
+
|
|
63
|
+
for (const choice of rawChunk.choices) {
|
|
64
|
+
if (!isRecord(choice) || !isRecord(choice.delta)) continue
|
|
65
|
+
|
|
66
|
+
const reasoningText = readBifrostChatReasoningText(choice.delta)
|
|
67
|
+
if (reasoningText) return reasoningText
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function injectBifrostChatReasoningContent(
|
|
74
|
+
content: readonly BifrostGeneratedContent[],
|
|
75
|
+
response?: BifrostChatResponse,
|
|
76
|
+
): BifrostGeneratedContent[] {
|
|
77
|
+
if (content.some((part) => part.type === 'reasoning')) {
|
|
78
|
+
return [...content]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const reasoningText = extractBifrostChatReasoningText(response?.body)
|
|
82
|
+
if (!reasoningText) return [...content]
|
|
83
|
+
|
|
84
|
+
return [{ type: 'reasoning', text: reasoningText }, ...content]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isReasoningEnabled(params: BifrostCallOptions): boolean {
|
|
88
|
+
if (!isRecord(params.providerOptions) || !isRecord(params.providerOptions.openai)) return false
|
|
89
|
+
|
|
90
|
+
const openaiOptions = params.providerOptions.openai
|
|
91
|
+
if (openaiOptions.forceReasoning === true) return true
|
|
92
|
+
if (typeof openaiOptions.reasoningSummary === 'string' && openaiOptions.reasoningSummary.length > 0) return true
|
|
93
|
+
return typeof openaiOptions.reasoningEffort === 'string' && openaiOptions.reasoningEffort !== 'none'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function shouldCloseInjectedReasoning(chunk: BifrostStreamPart): boolean {
|
|
97
|
+
return chunk.type !== 'stream-start' && chunk.type !== 'response-metadata' && chunk.type !== 'raw'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function injectBifrostChatReasoningStream(
|
|
101
|
+
stream: ReadableStream<BifrostStreamPart>,
|
|
102
|
+
): ReadableStream<BifrostStreamPart> {
|
|
103
|
+
const reasoningId = 'bifrost-reasoning-0'
|
|
104
|
+
let reasoningOpen = false
|
|
105
|
+
let reasoningClosed = false
|
|
106
|
+
|
|
107
|
+
return stream.pipeThrough(
|
|
108
|
+
new TransformStream<BifrostStreamPart, BifrostStreamPart>({
|
|
109
|
+
transform(chunk, controller) {
|
|
110
|
+
const closeReasoning = () => {
|
|
111
|
+
if (!reasoningOpen || reasoningClosed) return
|
|
112
|
+
|
|
113
|
+
controller.enqueue({ type: 'reasoning-end', id: reasoningId } as BifrostStreamPart)
|
|
114
|
+
reasoningClosed = true
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (chunk.type === 'raw') {
|
|
118
|
+
const reasoningDelta = reasoningClosed ? null : extractBifrostChatReasoningDeltaText(chunk.rawValue)
|
|
119
|
+
controller.enqueue(chunk)
|
|
120
|
+
|
|
121
|
+
if (reasoningDelta) {
|
|
122
|
+
if (!reasoningOpen) {
|
|
123
|
+
controller.enqueue({ type: 'reasoning-start', id: reasoningId } as BifrostStreamPart)
|
|
124
|
+
reasoningOpen = true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
controller.enqueue({ type: 'reasoning-delta', id: reasoningId, delta: reasoningDelta } as BifrostStreamPart)
|
|
128
|
+
}
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (shouldCloseInjectedReasoning(chunk)) {
|
|
133
|
+
closeReasoning()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
controller.enqueue(chunk)
|
|
137
|
+
},
|
|
138
|
+
flush(controller) {
|
|
139
|
+
if (!reasoningOpen || reasoningClosed) return
|
|
140
|
+
controller.enqueue({ type: 'reasoning-end', id: reasoningId } as BifrostStreamPart)
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function addBifrostReasoningRawChunks(
|
|
147
|
+
params: BifrostCallOptions,
|
|
148
|
+
type: BifrostTransformParamsOptions['type'],
|
|
149
|
+
): BifrostCallOptions {
|
|
150
|
+
if (type !== 'stream' || !isReasoningEnabled(params) || params.includeRawChunks === true) {
|
|
151
|
+
return params
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return { ...params, includeRawChunks: true }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function injectBifrostExtraParamsRequestBody(
|
|
158
|
+
body: BodyInit | null | undefined,
|
|
159
|
+
extraParams: BifrostExtraParams,
|
|
160
|
+
): BodyInit | null | undefined {
|
|
161
|
+
if (typeof body !== 'string') return body
|
|
162
|
+
|
|
163
|
+
let parsed: unknown
|
|
164
|
+
try {
|
|
165
|
+
parsed = JSON.parse(body)
|
|
166
|
+
} catch {
|
|
167
|
+
return body
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!isRecord(parsed)) return body
|
|
171
|
+
|
|
172
|
+
const mergedExtraParams = isRecord(parsed.extra_params)
|
|
173
|
+
? { ...parsed.extra_params, ...extraParams }
|
|
174
|
+
: { ...extraParams }
|
|
175
|
+
|
|
176
|
+
return JSON.stringify({ ...parsed, extra_params: mergedExtraParams })
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function createBifrostFetchWithExtraParams(extraParams: BifrostExtraParams): typeof fetch {
|
|
180
|
+
const fetchWithExtraParams = (input: RequestInfo | URL, init?: RequestInit | BunFetchRequestInit) =>
|
|
181
|
+
globalThis.fetch(input, { ...init, body: injectBifrostExtraParamsRequestBody(init?.body, extraParams) })
|
|
182
|
+
|
|
183
|
+
return Object.assign(fetchWithExtraParams, { preconnect: globalThis.fetch.preconnect.bind(globalThis.fetch) })
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function createBifrostProvider(extraParams?: BifrostExtraParams) {
|
|
187
|
+
const apiKey = readRequiredGatewayEnv('AI_GATEWAY_KEY')
|
|
188
|
+
if (!apiKey.startsWith('sk-bf-')) {
|
|
189
|
+
throw new Error('[bifrost] AI_GATEWAY_KEY must use the Bifrost virtual-key format (sk-bf-*).')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return createOpenAI({
|
|
193
|
+
baseURL: readRequiredGatewayEnv('AI_GATEWAY_URL'),
|
|
194
|
+
apiKey,
|
|
195
|
+
headers: { 'x-bf-vk': apiKey, ...(extraParams ? { 'x-bf-passthrough-extra-params': 'true' } : {}) },
|
|
196
|
+
...(extraParams ? { fetch: createBifrostFetchWithExtraParams(extraParams) } : {}),
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let provider: ReturnType<typeof createOpenAI> | null = null
|
|
201
|
+
let openRouterResponseHealingProvider: ReturnType<typeof createOpenAI> | null = null
|
|
202
|
+
|
|
203
|
+
export function getBifrostProvider() {
|
|
204
|
+
if (provider) return provider
|
|
205
|
+
|
|
206
|
+
provider = createBifrostProvider()
|
|
207
|
+
|
|
208
|
+
return provider
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function getBifrostOpenRouterResponseHealingProvider() {
|
|
212
|
+
if (openRouterResponseHealingProvider) return openRouterResponseHealingProvider
|
|
213
|
+
|
|
214
|
+
openRouterResponseHealingProvider = createBifrostProvider(OPENROUTER_RESPONSE_HEALING_EXTRA_PARAMS)
|
|
215
|
+
|
|
216
|
+
return openRouterResponseHealingProvider
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function bifrostModel(modelId: string) {
|
|
220
|
+
return getBifrostProvider()(modelId)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function bifrostOpenRouterResponseHealingModel(modelId: string) {
|
|
224
|
+
return getBifrostOpenRouterResponseHealingProvider()(modelId)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function bifrostChatModel(modelId: string) {
|
|
228
|
+
return wrapLanguageModel({
|
|
229
|
+
model: getBifrostProvider().chat(modelId),
|
|
230
|
+
middleware: {
|
|
231
|
+
specificationVersion: 'v3',
|
|
232
|
+
transformParams: async ({ params, type }) => addBifrostReasoningRawChunks(params, type),
|
|
233
|
+
wrapGenerate: async ({ doGenerate }) => {
|
|
234
|
+
const result = await doGenerate()
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
...result,
|
|
238
|
+
content: injectBifrostChatReasoningContent(
|
|
239
|
+
result.content,
|
|
240
|
+
result.response as BifrostChatResponse | undefined,
|
|
241
|
+
),
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
wrapStream: async ({ doStream, params }) => {
|
|
245
|
+
const result = await doStream()
|
|
246
|
+
if (!isReasoningEnabled(params)) return result
|
|
247
|
+
|
|
248
|
+
return { ...result, stream: injectBifrostChatReasoningStream(result.stream) }
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function bifrostEmbeddingModel(modelId: string) {
|
|
255
|
+
return getBifrostProvider().embeddingModel(modelId)
|
|
256
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
type LotaAgentFactoryRegistry = Record<string, (...args: unknown[]) => unknown>
|
|
2
|
+
|
|
3
|
+
// Agent configuration — these are defaults that consumers override via createLotaRuntime config
|
|
4
|
+
export let agentDisplayNames: Record<string, string> = {}
|
|
5
|
+
export let agentShortDisplayNames: Record<string, string> = {}
|
|
6
|
+
export let agentRoster: readonly string[] = []
|
|
7
|
+
export let teamConsultParticipants: readonly string[] = []
|
|
8
|
+
|
|
9
|
+
export interface CoreWorkstreamProfile {
|
|
10
|
+
config: { coreType: string; agentId: string; title: string }
|
|
11
|
+
tools: readonly string[]
|
|
12
|
+
skills: readonly string[]
|
|
13
|
+
instructions: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export let getCoreWorkstreamProfile: (coreType: string) => CoreWorkstreamProfile = (_coreType) => ({
|
|
17
|
+
config: { coreType: _coreType, agentId: '', title: '' },
|
|
18
|
+
tools: [],
|
|
19
|
+
skills: [],
|
|
20
|
+
instructions: '',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export function configureAgents(config: {
|
|
24
|
+
roster: readonly string[]
|
|
25
|
+
displayNames: Record<string, string>
|
|
26
|
+
shortDisplayNames?: Record<string, string>
|
|
27
|
+
teamConsultParticipants: readonly string[]
|
|
28
|
+
getCoreWorkstreamProfile?: (coreType: string) => CoreWorkstreamProfile
|
|
29
|
+
}): void {
|
|
30
|
+
agentRoster = config.roster
|
|
31
|
+
agentDisplayNames = config.displayNames
|
|
32
|
+
agentShortDisplayNames = config.shortDisplayNames ?? {}
|
|
33
|
+
teamConsultParticipants = config.teamConsultParticipants
|
|
34
|
+
if (config.getCoreWorkstreamProfile) {
|
|
35
|
+
getCoreWorkstreamProfile = config.getCoreWorkstreamProfile
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isAgentName(value: unknown): boolean {
|
|
40
|
+
return typeof value === 'string' && new Set(agentRoster).has(value)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function resolveAgentNameAlias(value: unknown): string | undefined {
|
|
44
|
+
if (typeof value !== 'string') return undefined
|
|
45
|
+
const lowered = value.trim().toLowerCase()
|
|
46
|
+
const aliasMap = new Map<string, string>()
|
|
47
|
+
for (const agent of agentRoster) {
|
|
48
|
+
aliasMap.set(agent.toLowerCase(), agent)
|
|
49
|
+
const displayName = agentDisplayNames[agent]
|
|
50
|
+
if (displayName) aliasMap.set(displayName.toLowerCase(), agent)
|
|
51
|
+
const shortName = agentShortDisplayNames[agent]
|
|
52
|
+
if (shortName) aliasMap.set(shortName.toLowerCase(), agent)
|
|
53
|
+
}
|
|
54
|
+
return aliasMap.get(lowered)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export let createAgent: LotaAgentFactoryRegistry = {}
|
|
58
|
+
|
|
59
|
+
export let buildAgentTools: (...args: unknown[]) => unknown = () => ({})
|
|
60
|
+
export let getAgentRuntimeConfig: (...args: unknown[]) => unknown = () => ({})
|
|
61
|
+
export let pluginRuntime: unknown = undefined
|
|
62
|
+
|
|
63
|
+
export function configureAgentFactory(config: {
|
|
64
|
+
createAgent: LotaAgentFactoryRegistry
|
|
65
|
+
buildAgentTools?: (...args: unknown[]) => unknown
|
|
66
|
+
getAgentRuntimeConfig?: (...args: unknown[]) => unknown
|
|
67
|
+
pluginRuntime?: unknown
|
|
68
|
+
}): void {
|
|
69
|
+
createAgent = config.createAgent
|
|
70
|
+
if (config.buildAgentTools) buildAgentTools = config.buildAgentTools
|
|
71
|
+
if (config.getAgentRuntimeConfig) getAgentRuntimeConfig = config.getAgentRuntimeConfig
|
|
72
|
+
if (config.pluginRuntime !== undefined) pluginRuntime = config.pluginRuntime
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const AGENT_MENTION_REGEX = /(^|[^\w])@([a-z][a-z0-9_-]*)\b/gi
|
|
76
|
+
|
|
77
|
+
export interface AgentMentionMatch {
|
|
78
|
+
agent: string
|
|
79
|
+
mention: string
|
|
80
|
+
index: number
|
|
81
|
+
length: number
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function extractAgentMentions(message: string): AgentMentionMatch[] {
|
|
85
|
+
const matches: AgentMentionMatch[] = []
|
|
86
|
+
if (!message.trim()) return matches
|
|
87
|
+
|
|
88
|
+
const regex = new RegExp(AGENT_MENTION_REGEX)
|
|
89
|
+
for (const rawMatch of message.matchAll(regex)) {
|
|
90
|
+
const prefix = rawMatch[1] ?? ''
|
|
91
|
+
const rawAgent = (rawMatch[2] ?? '').toLowerCase()
|
|
92
|
+
if (!isAgentName(rawAgent)) continue
|
|
93
|
+
|
|
94
|
+
const index = rawMatch.index + prefix.length
|
|
95
|
+
matches.push({ agent: rawAgent, mention: `@${rawAgent}`, index, length: rawAgent.length + 1 })
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return matches
|
|
99
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Memory System Constants
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
export const MEMORY = {
|
|
8
|
+
/** Default number of memory candidates to fetch */
|
|
9
|
+
DEFAULT_CANDIDATE_LIMIT: 12,
|
|
10
|
+
/** Maximum KNN limit for safety */
|
|
11
|
+
MAX_KNN_LIMIT: 100,
|
|
12
|
+
} as const
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Validation Helpers
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validates that a value is a safe integer for KNN queries
|
|
20
|
+
* Throws if validation fails
|
|
21
|
+
*/
|
|
22
|
+
export function validateKnnLimit(limit: unknown): number {
|
|
23
|
+
return z.number().int().positive().max(MEMORY.MAX_KNN_LIMIT).parse(limit)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a KNN query string with validated limit
|
|
28
|
+
* Example: createKnnQuery(10) returns "<|10|>"
|
|
29
|
+
*/
|
|
30
|
+
export function createKnnQuery(limit: unknown): string {
|
|
31
|
+
const validatedLimit = validateKnnLimit(limit)
|
|
32
|
+
return `<|${validatedLimit}|>`
|
|
33
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { ZodTypeAny } from 'zod'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
const logLevelValues = ['trace', 'debug', 'info', 'warning', 'error', 'fatal'] as const
|
|
5
|
+
|
|
6
|
+
export const nodeServerEnvShape = {
|
|
7
|
+
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
|
8
|
+
PORT: z.coerce.number().default(3000),
|
|
9
|
+
MAIN_CLIENT_URL: z.string().min(1).default('http://localhost:5173'),
|
|
10
|
+
} as const satisfies Record<string, ZodTypeAny>
|
|
11
|
+
|
|
12
|
+
export const surrealDbEnvShape = {
|
|
13
|
+
SURREALDB_URL: z.string().min(1, 'SurrealDB URL is required'),
|
|
14
|
+
SURREALDB_USER: z.string().min(1, 'SurrealDB user is required'),
|
|
15
|
+
SURREALDB_PASSWORD: z.string().min(1, 'SurrealDB password is required'),
|
|
16
|
+
SURREALDB_NAMESPACE: z.string().min(1, 'SurrealDB namespace is required'),
|
|
17
|
+
SURREALDB_DATABASE: z.string().min(1, 'SurrealDB database is required'),
|
|
18
|
+
} as const satisfies Record<string, ZodTypeAny>
|
|
19
|
+
|
|
20
|
+
export const betterAuthEnvShape = {
|
|
21
|
+
BETTER_AUTH_SECRET: z.string().min(32, 'Better Auth secret must be at least 32 characters long'),
|
|
22
|
+
} as const satisfies Record<string, ZodTypeAny>
|
|
23
|
+
|
|
24
|
+
export const serverUrlEnvShape = { SERVER_URL: z.string().min(1, 'Server URL is required') } as const satisfies Record<
|
|
25
|
+
string,
|
|
26
|
+
ZodTypeAny
|
|
27
|
+
>
|
|
28
|
+
|
|
29
|
+
export const aiGatewayEnvShape = {
|
|
30
|
+
AI_GATEWAY_URL: z.string().url('AI gateway URL is required'),
|
|
31
|
+
AI_GATEWAY_KEY: z.string().min(1, 'AI gateway key is required'),
|
|
32
|
+
AI_GATEWAY_ADMIN: z.string().min(1).optional(),
|
|
33
|
+
AI_GATEWAY_PASS: z.string().min(1).optional(),
|
|
34
|
+
AI_EMBEDDING_MODEL: z.string().default('openai/text-embedding-3-small'),
|
|
35
|
+
} as const satisfies Record<string, ZodTypeAny>
|
|
36
|
+
|
|
37
|
+
export const firecrawlEnvShape = {
|
|
38
|
+
FIRECRAWL_API_KEY: z
|
|
39
|
+
.string()
|
|
40
|
+
.min(1, 'Firecrawl API key is required')
|
|
41
|
+
.refine((value) => value.startsWith('fc-'), 'Firecrawl API key must start with fc-')
|
|
42
|
+
.refine((value) => value !== 'dev-fire-key', 'Firecrawl API key placeholder is not allowed'),
|
|
43
|
+
FIRECRAWL_API_BASE_URL: z.string().url().optional(),
|
|
44
|
+
} as const satisfies Record<string, ZodTypeAny>
|
|
45
|
+
|
|
46
|
+
export const memorySearchEnvShape = {
|
|
47
|
+
MEMORY_SEARCH_K: z.coerce.number().int().positive().default(6),
|
|
48
|
+
} as const satisfies Record<string, ZodTypeAny>
|
|
49
|
+
|
|
50
|
+
export const redisEnvShape = { REDIS_URL: z.string().min(1, 'Redis URL is required') } as const satisfies Record<
|
|
51
|
+
string,
|
|
52
|
+
ZodTypeAny
|
|
53
|
+
>
|
|
54
|
+
|
|
55
|
+
export const s3StorageEnvShape = {
|
|
56
|
+
S3_ENDPOINT: z.string().min(1, 'S3 endpoint is required'),
|
|
57
|
+
S3_BUCKET: z.string().min(1, 'S3 bucket is required'),
|
|
58
|
+
S3_REGION: z.string().default('garage'),
|
|
59
|
+
S3_ACCESS_KEY_ID: z.string().min(1, 'S3 access key is required'),
|
|
60
|
+
S3_SECRET_ACCESS_KEY: z.string().min(1, 'S3 secret access key is required'),
|
|
61
|
+
ATTACHMENT_URL_EXPIRES_IN: z.coerce.number().positive('Attachment URL expiry must be positive').default(1800),
|
|
62
|
+
} as const satisfies Record<string, ZodTypeAny>
|
|
63
|
+
|
|
64
|
+
export const loggingEnvShape = { LOG_LEVEL: z.enum(logLevelValues).default('info') } as const satisfies Record<
|
|
65
|
+
string,
|
|
66
|
+
ZodTypeAny
|
|
67
|
+
>
|
|
68
|
+
|
|
69
|
+
export const githubAppEnvShape = {
|
|
70
|
+
GITHUB_APP_ID: z.string().optional(),
|
|
71
|
+
GITHUB_PRIVATE_KEY: z.string().optional(),
|
|
72
|
+
GITHUB_APP_SLUG: z.string().optional(),
|
|
73
|
+
} as const satisfies Record<string, ZodTypeAny>
|
|
74
|
+
|
|
75
|
+
export const linearOauthEnvShape = {
|
|
76
|
+
LINEAR_CLIENT_ID: z.string().optional(),
|
|
77
|
+
LINEAR_CLIENT_SECRET: z.string().optional(),
|
|
78
|
+
LINEAR_REDIRECT_URI: z.string().optional(),
|
|
79
|
+
} as const satisfies Record<string, ZodTypeAny>
|
|
80
|
+
|
|
81
|
+
export const marketingStudioEnvShape = {
|
|
82
|
+
MARKETING_STUDIO_BASE_URL: z.string().url().optional(),
|
|
83
|
+
MARKETING_STUDIO_ADMIN_API_KEY: z.string().min(1).optional(),
|
|
84
|
+
MARKETING_STUDIO_WEBHOOK_SIGNING_SECRET: z.string().min(1).optional(),
|
|
85
|
+
} as const satisfies Record<string, ZodTypeAny>
|
|
86
|
+
|
|
87
|
+
export function envKeys<TShape extends Record<string, ZodTypeAny>>(shape: TShape) {
|
|
88
|
+
return Object.freeze(Object.keys(shape)) as readonly Extract<keyof TShape, string>[]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const lotaSdkEnvSchema = z.object({
|
|
92
|
+
...aiGatewayEnvShape,
|
|
93
|
+
...memorySearchEnvShape,
|
|
94
|
+
...redisEnvShape,
|
|
95
|
+
...loggingEnvShape,
|
|
96
|
+
...s3StorageEnvShape,
|
|
97
|
+
...firecrawlEnvShape,
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
export const lotaSdkEnvKeys = Object.freeze([
|
|
101
|
+
...envKeys(aiGatewayEnvShape),
|
|
102
|
+
...envKeys(memorySearchEnvShape),
|
|
103
|
+
...envKeys(redisEnvShape),
|
|
104
|
+
...envKeys(loggingEnvShape),
|
|
105
|
+
...envKeys(s3StorageEnvShape),
|
|
106
|
+
...envKeys(firecrawlEnvShape),
|
|
107
|
+
]) as readonly string[]
|
|
108
|
+
|
|
109
|
+
type LotaSdkEnv = z.infer<typeof lotaSdkEnvSchema>
|
|
110
|
+
|
|
111
|
+
let _env: LotaSdkEnv | undefined
|
|
112
|
+
|
|
113
|
+
export function setEnv(value: LotaSdkEnv): void {
|
|
114
|
+
_env = value
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const env: LotaSdkEnv = new Proxy({} as LotaSdkEnv, {
|
|
118
|
+
get(_target, prop: string) {
|
|
119
|
+
if (!_env) throw new Error(`lota-sdk env not configured. Call setEnv() before accessing env.${prop}`)
|
|
120
|
+
return (_env as Record<string, unknown>)[prop]
|
|
121
|
+
},
|
|
122
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { LogLevel } from '@logtape/logtape'
|
|
2
|
+
import { configure, getAnsiColorFormatter, getConsoleSink, getLogger as getLogTapeLogger } from '@logtape/logtape'
|
|
3
|
+
|
|
4
|
+
export async function configureLotaLogger(logLevel: LogLevel): Promise<void> {
|
|
5
|
+
const formatter = getAnsiColorFormatter({ level: 'FULL' })
|
|
6
|
+
|
|
7
|
+
await configure({
|
|
8
|
+
reset: true,
|
|
9
|
+
sinks: { console: getConsoleSink({ formatter }) },
|
|
10
|
+
loggers: [
|
|
11
|
+
{ category: ['logtape', 'meta'], lowestLevel: 'warning', sinks: ['console'] },
|
|
12
|
+
{ category: ['server'], lowestLevel: logLevel, sinks: ['console'] },
|
|
13
|
+
{ category: ['lota-sdk'], lowestLevel: logLevel, sinks: ['console'] },
|
|
14
|
+
{ category: ['hono'], lowestLevel: logLevel, sinks: ['console'] },
|
|
15
|
+
],
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getLogger(category: readonly string[]) {
|
|
20
|
+
return getLogTapeLogger([...category])
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function configureLogger(logLevel?: LogLevel): Promise<void> {
|
|
24
|
+
await configureLotaLogger(logLevel ?? 'info')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const serverLogger = getLogger(['lota-sdk'])
|
|
28
|
+
export const chatLogger = getLogger(['lota-sdk', 'chat'])
|
|
29
|
+
export const aiLogger = getLogger(['lota-sdk', 'ai'])
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const OPENAI_REASONING_MODEL_ID = 'openai/gpt-5.4' as const
|
|
2
|
+
|
|
3
|
+
export const OPENROUTER_TEAM_AGENT_MODEL_ID = 'openrouter/google/gemini-3.1-pro-preview' as const
|
|
4
|
+
export const OPENROUTER_STRUCTURED_HELPER_MODEL_ID = 'openrouter/google/gemini-3-flash-preview:exacto' as const
|
|
5
|
+
export const OPENROUTER_DELEGATED_REASONING_MODEL_ID = 'openrouter/google/gemini-3-flash-preview:exacto' as const
|
|
6
|
+
export const OPENROUTER_WEB_RESEARCH_MODEL_ID = 'openrouter/stepfun/step-3.5-flash' as const
|
|
7
|
+
export const OPENROUTER_ARTIFACT_GENERATOR_MODEL_ID = 'openrouter/qwen/qwen3.5-flash-02-23' as const
|
|
8
|
+
export const OPENROUTER_REPO_INDEXER_MODEL_ID = 'openrouter/qwen/qwen3.5-flash-02-23:nitro' as const
|
|
9
|
+
export const OPENROUTER_CODE_ANALYSIS_MODEL_ID = 'openrouter/xiaomi/mimo-v2-flash' as const
|
|
10
|
+
export const OPENROUTER_FAST_REASONING_MODEL_ID = 'openrouter/openai/gpt-oss-120b:nitro' as const
|
|
11
|
+
export const OPENROUTER_STRUCTURED_REASONING_MODEL_ID = 'openrouter/openai/gpt-oss-120b:exacto' as const
|
|
12
|
+
|
|
13
|
+
export const OPENAI_HIGH_REASONING_PROVIDER_OPTIONS = {
|
|
14
|
+
openai: { forceReasoning: true, reasoningEffort: 'high', reasoningSummary: 'auto' },
|
|
15
|
+
} as const
|
|
16
|
+
|
|
17
|
+
export const OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS = {
|
|
18
|
+
openai: { forceReasoning: true, reasoningEffort: 'high', reasoningSummary: 'auto' },
|
|
19
|
+
} as const
|
|
20
|
+
|
|
21
|
+
export const OPENROUTER_XHIGH_REASONING_PROVIDER_OPTIONS = {
|
|
22
|
+
openai: { forceReasoning: true, reasoningEffort: 'xhigh', reasoningSummary: 'auto' },
|
|
23
|
+
} as const
|
|
24
|
+
|
|
25
|
+
export const OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS = {
|
|
26
|
+
openai: { forceReasoning: true, reasoningEffort: 'low', reasoningSummary: 'auto' },
|
|
27
|
+
} as const
|
|
28
|
+
|
|
29
|
+
export const OPENROUTER_MINIMAL_REASONING_PROVIDER_OPTIONS = {
|
|
30
|
+
openai: { forceReasoning: true, reasoningEffort: 'minimal', reasoningSummary: 'auto' },
|
|
31
|
+
} as const
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search-related constants for vector search and hybrid search
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Multiplier for vector search fetch limit.
|
|
7
|
+
* We fetch more candidates than needed (limit * multiplier) to allow for:
|
|
8
|
+
* - RRF (Reciprocal Rank Fusion) re-ranking
|
|
9
|
+
* - Hybrid search combining vector + full-text results
|
|
10
|
+
* - Filtering out low-quality matches before returning final results
|
|
11
|
+
*/
|
|
12
|
+
export const VECTOR_SEARCH_OVERFETCH_MULTIPLIER = 2
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Default limit for memory search queries
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_MEMORY_SEARCH_LIMIT = 10
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface WorkstreamBootstrapWelcomeConfig {
|
|
2
|
+
directAgentId: string
|
|
3
|
+
buildMessageText: (params: { userName?: string | null }) => string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface LotaWorkstreamBootstrapConfig {
|
|
7
|
+
onboardingDirectAgents?: readonly string[]
|
|
8
|
+
completedDirectAgents?: readonly string[]
|
|
9
|
+
coreTypesAfterOnboarding?: readonly string[]
|
|
10
|
+
ensureDefaultGroupOnCompleted?: boolean
|
|
11
|
+
onboardingWelcome?: WorkstreamBootstrapWelcomeConfig
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface LotaWorkstreamConfig {
|
|
15
|
+
bootstrap?: LotaWorkstreamBootstrapConfig
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ResolvedWorkstreamBootstrapConfig {
|
|
19
|
+
onboardingDirectAgents: readonly string[]
|
|
20
|
+
completedDirectAgents: readonly string[]
|
|
21
|
+
coreTypesAfterOnboarding: readonly string[]
|
|
22
|
+
ensureDefaultGroupOnCompleted: boolean
|
|
23
|
+
onboardingWelcome?: WorkstreamBootstrapWelcomeConfig
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const DEFAULT_WORKSTREAM_BOOTSTRAP_CONFIG: ResolvedWorkstreamBootstrapConfig = {
|
|
27
|
+
onboardingDirectAgents: [],
|
|
28
|
+
completedDirectAgents: [],
|
|
29
|
+
coreTypesAfterOnboarding: [],
|
|
30
|
+
ensureDefaultGroupOnCompleted: true,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let resolvedWorkstreamBootstrapConfig: ResolvedWorkstreamBootstrapConfig = DEFAULT_WORKSTREAM_BOOTSTRAP_CONFIG
|
|
34
|
+
|
|
35
|
+
function withDedupedStrings(values: readonly string[]): string[] {
|
|
36
|
+
const seen = new Set<string>()
|
|
37
|
+
const deduped: string[] = []
|
|
38
|
+
|
|
39
|
+
for (const value of values) {
|
|
40
|
+
const normalized = value.trim()
|
|
41
|
+
if (!normalized || seen.has(normalized)) continue
|
|
42
|
+
seen.add(normalized)
|
|
43
|
+
deduped.push(normalized)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return deduped
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function configureWorkstreams(params: { agentRoster: readonly string[]; config?: LotaWorkstreamConfig }): void {
|
|
50
|
+
const bootstrap = params.config?.bootstrap
|
|
51
|
+
const onboardingWelcome = bootstrap?.onboardingWelcome
|
|
52
|
+
const onboardingDirectAgents = withDedupedStrings([
|
|
53
|
+
...(bootstrap?.onboardingDirectAgents ?? params.agentRoster),
|
|
54
|
+
...(onboardingWelcome ? [onboardingWelcome.directAgentId] : []),
|
|
55
|
+
])
|
|
56
|
+
|
|
57
|
+
resolvedWorkstreamBootstrapConfig = {
|
|
58
|
+
onboardingDirectAgents,
|
|
59
|
+
completedDirectAgents: withDedupedStrings(bootstrap?.completedDirectAgents ?? params.agentRoster),
|
|
60
|
+
coreTypesAfterOnboarding: withDedupedStrings(bootstrap?.coreTypesAfterOnboarding ?? []),
|
|
61
|
+
ensureDefaultGroupOnCompleted: bootstrap?.ensureDefaultGroupOnCompleted ?? true,
|
|
62
|
+
...(onboardingWelcome ? { onboardingWelcome } : {}),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getWorkstreamBootstrapConfig(): ResolvedWorkstreamBootstrapConfig {
|
|
67
|
+
return resolvedWorkstreamBootstrapConfig
|
|
68
|
+
}
|