@lota-sdk/core 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,12 +1,14 @@
|
|
|
1
|
-
import { toTimestamp, withMessageCreatedAt } from '@lota-sdk/shared/runtime/chat-message-metadata'
|
|
2
|
-
import { baseChatMessageSchema } from '@lota-sdk/shared/schemas/chat-api'
|
|
3
|
-
import { messageMetadataSchema, dataPartsSchema } from '@lota-sdk/shared/schemas/chat-message'
|
|
4
|
-
import type { ChatMessage, MessageMetadata } from '@lota-sdk/shared/schemas/chat-message'
|
|
5
1
|
import {
|
|
6
|
-
|
|
2
|
+
baseChatMessageSchema,
|
|
7
3
|
CONSULT_SPECIALIST_TOOL_NAME,
|
|
4
|
+
CONSULT_TEAM_TOOL_NAME,
|
|
8
5
|
ConsultSpecialistArgsSchema,
|
|
9
|
-
|
|
6
|
+
dataPartsSchema,
|
|
7
|
+
messageMetadataSchema,
|
|
8
|
+
toTimestamp,
|
|
9
|
+
withMessageCreatedAt,
|
|
10
|
+
} from '@lota-sdk/shared'
|
|
11
|
+
import type { ChatMessage, MessageMetadata } from '@lota-sdk/shared'
|
|
10
12
|
import { convertToModelMessages, readUIMessageStream, stepCountIs, tool as createTool, validateUIMessages } from 'ai'
|
|
11
13
|
import type { PrepareStepFunction, StopCondition, ToolSet, UIMessageStreamWriter } from 'ai'
|
|
12
14
|
import type { z } from 'zod'
|
|
@@ -15,10 +17,12 @@ import {
|
|
|
15
17
|
agentDisplayNames,
|
|
16
18
|
buildAgentTools,
|
|
17
19
|
createAgent,
|
|
20
|
+
getLeadAgentId,
|
|
18
21
|
getCoreWorkstreamProfile,
|
|
19
22
|
getAgentRuntimeConfig,
|
|
20
23
|
pluginRuntime,
|
|
21
24
|
} from '../config/agent-defaults'
|
|
25
|
+
import { lotaDebugLogger } from '../config/debug-logger'
|
|
22
26
|
import { aiLogger } from '../config/logger'
|
|
23
27
|
import type { RecordIdRef } from '../db/record-id'
|
|
24
28
|
import { recordIdToString } from '../db/record-id'
|
|
@@ -93,7 +97,7 @@ interface UIMessageStreamResult {
|
|
|
93
97
|
toUIMessageStream(options: Record<string, unknown>): ReadableStream<unknown>
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
|
|
100
|
+
function hasUIMessageStream(value: unknown): value is UIMessageStreamResult {
|
|
97
101
|
return (
|
|
98
102
|
typeof value === 'object' &&
|
|
99
103
|
value !== null &&
|
|
@@ -101,7 +105,7 @@ export function hasUIMessageStream(value: unknown): value is UIMessageStreamResu
|
|
|
101
105
|
)
|
|
102
106
|
}
|
|
103
107
|
|
|
104
|
-
|
|
108
|
+
function getPluginService(path: string[]): ((...args: unknown[]) => unknown) | undefined {
|
|
105
109
|
let current: unknown = pluginRuntime
|
|
106
110
|
let owner: unknown = undefined
|
|
107
111
|
for (const key of path) {
|
|
@@ -118,7 +122,7 @@ export function getPluginService(path: string[]): ((...args: unknown[]) => unkno
|
|
|
118
122
|
: (current as (...args: unknown[]) => unknown)
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
|
|
125
|
+
async function buildIndexedRepositoriesContext(
|
|
122
126
|
workspaceId: string,
|
|
123
127
|
): Promise<{ provideRepoTool: boolean; defaultSectionsByAgent: Record<string, unknown>; context: string }> {
|
|
124
128
|
const buildContext = getRuntimeAdapters().workstream?.buildIndexedRepositoriesContext
|
|
@@ -136,11 +140,11 @@ export async function buildIndexedRepositoriesContext(
|
|
|
136
140
|
|
|
137
141
|
const PRESEEDED_MEMORY_LOOKUP_LIMIT = 3
|
|
138
142
|
|
|
139
|
-
|
|
143
|
+
function parsePersistedWorkstreamState(value: unknown): WorkstreamState | null {
|
|
140
144
|
return parseWorkstreamState(value)
|
|
141
145
|
}
|
|
142
146
|
|
|
143
|
-
|
|
147
|
+
function stripExecutionPlanFieldsFromWorkstreamState(
|
|
144
148
|
state: WorkstreamState | null | undefined,
|
|
145
149
|
hasExecutionPlan: boolean,
|
|
146
150
|
): WorkstreamState | null | undefined {
|
|
@@ -149,7 +153,7 @@ export function stripExecutionPlanFieldsFromWorkstreamState(
|
|
|
149
153
|
return { ...state, currentPlan: null, tasks: [], artifacts: [] }
|
|
150
154
|
}
|
|
151
155
|
|
|
152
|
-
|
|
156
|
+
async function waitForWorkstreamCompactionIfNeeded(workstreamId: RecordIdRef): Promise<WorkstreamRecord> {
|
|
153
157
|
return await waitForCompactionIfNeeded({
|
|
154
158
|
entityId: recordIdToString(workstreamId, TABLES.WORKSTREAM),
|
|
155
159
|
entityLabel: 'Workstream',
|
|
@@ -158,13 +162,13 @@ export async function waitForWorkstreamCompactionIfNeeded(workstreamId: RecordId
|
|
|
158
162
|
})
|
|
159
163
|
}
|
|
160
164
|
|
|
161
|
-
|
|
165
|
+
function parseChatMessageCandidate(value: unknown): ChatMessage | undefined {
|
|
162
166
|
const parsed = baseChatMessageSchema.safeParse(value)
|
|
163
167
|
if (!parsed.success) return undefined
|
|
164
168
|
return parsed.data as ChatMessage
|
|
165
169
|
}
|
|
166
170
|
|
|
167
|
-
|
|
171
|
+
function getChatMessageFromToolOutput(output: unknown): ChatMessage | undefined {
|
|
168
172
|
const directCandidate = parseChatMessageCandidate(output)
|
|
169
173
|
if (directCandidate) return directCandidate
|
|
170
174
|
|
|
@@ -182,7 +186,7 @@ export function getChatMessageFromToolOutput(output: unknown): ChatMessage | und
|
|
|
182
186
|
return undefined
|
|
183
187
|
}
|
|
184
188
|
|
|
185
|
-
|
|
189
|
+
class WorkstreamTurnError extends AppError {
|
|
186
190
|
constructor(
|
|
187
191
|
message: string,
|
|
188
192
|
readonly statusCode: 400 | 409,
|
|
@@ -192,19 +196,19 @@ export class WorkstreamTurnError extends AppError {
|
|
|
192
196
|
}
|
|
193
197
|
}
|
|
194
198
|
|
|
195
|
-
|
|
199
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
196
200
|
return value && typeof value === 'object' ? (value as Record<string, unknown>) : null
|
|
197
201
|
}
|
|
198
202
|
|
|
199
|
-
|
|
203
|
+
function readOptionalString(value: unknown): string | undefined {
|
|
200
204
|
return typeof value === 'string' && value.trim().length > 0 ? value : undefined
|
|
201
205
|
}
|
|
202
206
|
|
|
203
|
-
|
|
207
|
+
function readOptionalBoolean(value: unknown): boolean | undefined {
|
|
204
208
|
return typeof value === 'boolean' ? value : undefined
|
|
205
209
|
}
|
|
206
210
|
|
|
207
|
-
|
|
211
|
+
function readInstructionSections(value: unknown): string[] {
|
|
208
212
|
if (!Array.isArray(value)) {
|
|
209
213
|
return []
|
|
210
214
|
}
|
|
@@ -215,7 +219,7 @@ export function readInstructionSections(value: unknown): string[] {
|
|
|
215
219
|
.filter((section) => section.length > 0)
|
|
216
220
|
}
|
|
217
221
|
|
|
218
|
-
|
|
222
|
+
function optionalInstructionSection(value: unknown): string[] | undefined {
|
|
219
223
|
const section = readOptionalString(value)
|
|
220
224
|
return section ? [section] : undefined
|
|
221
225
|
}
|
|
@@ -243,7 +247,7 @@ export interface WorkstreamApprovalContinuationParams {
|
|
|
243
247
|
streamId?: string
|
|
244
248
|
}
|
|
245
249
|
|
|
246
|
-
|
|
250
|
+
type WorkstreamRunCoreParams = {
|
|
247
251
|
workstream: NormalizedWorkstream
|
|
248
252
|
workstreamRef: RecordIdRef
|
|
249
253
|
orgRef: RecordIdRef
|
|
@@ -257,12 +261,12 @@ export type WorkstreamRunCoreParams = {
|
|
|
257
261
|
| { kind: 'nativeToolApprovalTurn'; approvalMessages: ChatMessage[] }
|
|
258
262
|
)
|
|
259
263
|
|
|
260
|
-
|
|
264
|
+
interface PreparedWorkstreamTurn {
|
|
261
265
|
originalMessages: ChatMessage[]
|
|
262
266
|
run: (writer?: UIMessageStreamWriter<ChatMessage>) => Promise<void>
|
|
263
267
|
}
|
|
264
268
|
|
|
265
|
-
|
|
269
|
+
function upsertChatHistoryMessage(messages: ChatMessage[], nextMessage: ChatMessage): ChatMessage[] {
|
|
266
270
|
const existingIndex = messages.findIndex((message) => message.id === nextMessage.id)
|
|
267
271
|
if (existingIndex === -1) {
|
|
268
272
|
return [...messages, nextMessage]
|
|
@@ -273,22 +277,19 @@ export function upsertChatHistoryMessage(messages: ChatMessage[], nextMessage: C
|
|
|
273
277
|
return nextMessages
|
|
274
278
|
}
|
|
275
279
|
|
|
276
|
-
|
|
280
|
+
function buildRecentActivityChatDeepLink(params: {
|
|
277
281
|
workstream: NormalizedWorkstream
|
|
278
282
|
workstreamId: string
|
|
279
283
|
visibleAgentId: string
|
|
280
|
-
}): { route:
|
|
284
|
+
}): { route: string; search: Record<string, string> } {
|
|
281
285
|
if (params.workstream.mode === 'direct') {
|
|
282
|
-
return {
|
|
283
|
-
route: '/chat',
|
|
284
|
-
search: { agent: params.visibleAgentId, tab: params.visibleAgentId === 'chief' ? 'cos' : 'team' },
|
|
285
|
-
}
|
|
286
|
+
return { route: 'direct-workstream', search: { workstreamId: params.workstreamId, agentId: params.visibleAgentId } }
|
|
286
287
|
}
|
|
287
288
|
|
|
288
|
-
return { route: '
|
|
289
|
+
return { route: 'group-workstream', search: { workstreamId: params.workstreamId } }
|
|
289
290
|
}
|
|
290
291
|
|
|
291
|
-
|
|
292
|
+
function buildRecentActivityChatSystemTitle(params: {
|
|
292
293
|
workstream: NormalizedWorkstream
|
|
293
294
|
visibleAgentId: string
|
|
294
295
|
}): string {
|
|
@@ -335,7 +336,13 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
335
336
|
}
|
|
336
337
|
}
|
|
337
338
|
|
|
339
|
+
const timer = lotaDebugLogger.timer('prepare')
|
|
340
|
+
|
|
341
|
+
// Start workspace fetch early — does not depend on compaction gate
|
|
342
|
+
const workspacePromise = workspaceProvider ? workspaceProvider.getWorkspace(orgRef) : Promise.resolve({})
|
|
343
|
+
|
|
338
344
|
const workstreamRecord = await waitForWorkstreamCompactionIfNeeded(workstreamRef)
|
|
345
|
+
timer.step('compaction-gate')
|
|
339
346
|
if (toOptionalTrimmedString(workstreamRecord.activeRunId)) {
|
|
340
347
|
throw new WorkstreamTurnError('A chat run is already active.', 409)
|
|
341
348
|
}
|
|
@@ -348,9 +355,9 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
348
355
|
throw new WorkstreamTurnError('No approval-responded message found.', 400)
|
|
349
356
|
}
|
|
350
357
|
await workstreamMessageService.upsertMessages({ workstreamId: workstreamRef, messages: [approvedAssistantMessage] })
|
|
358
|
+
timer.step('persist-approval-message')
|
|
351
359
|
}
|
|
352
360
|
|
|
353
|
-
const workspacePromise = workspaceProvider ? workspaceProvider.getWorkspace(orgRef) : Promise.resolve({})
|
|
354
361
|
const initialWorkstreamState = parsePersistedWorkstreamState(workstreamRecord.state)
|
|
355
362
|
const persistedCompactionCursor = toOptionalTrimmedString(workstreamRecord.lastCompactedMessageId) ?? undefined
|
|
356
363
|
const persistedLiveHistoryPromise = workstreamMessageService.listMessagesAfterCursor(
|
|
@@ -375,10 +382,13 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
375
382
|
persistedLiveHistoryPromise,
|
|
376
383
|
persistedRecentHistoryPromise,
|
|
377
384
|
])
|
|
385
|
+
timer.step('fetch-workspace+history')
|
|
378
386
|
const workspaceLifecycleState = workspaceProvider ? await workspaceProvider.getLifecycleState?.(workspace) : undefined
|
|
387
|
+
timer.step('workspace-lifecycle-state')
|
|
379
388
|
const workspaceProfileState = workspaceProvider
|
|
380
389
|
? await workspaceProvider.readProfileProjectionState?.(workspace)
|
|
381
390
|
: undefined
|
|
391
|
+
timer.step('workspace-profile-state')
|
|
382
392
|
const [liveHistory, recentHistory] = await Promise.all([
|
|
383
393
|
persistedLiveHistory.length === 0
|
|
384
394
|
? Promise.resolve([] as ChatMessage[])
|
|
@@ -395,9 +405,11 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
395
405
|
dataSchemas: dataPartsSchema,
|
|
396
406
|
}).then((messages) => messages.map(hydrateMessageFileUrls)),
|
|
397
407
|
])
|
|
408
|
+
timer.step('validate+hydrate-history')
|
|
398
409
|
|
|
399
410
|
if (userMessage && shouldPersistInputMessage) {
|
|
400
411
|
await workstreamMessageService.upsertMessages({ workstreamId: workstreamRef, messages: [userMessage] })
|
|
412
|
+
timer.step('persist-user-message')
|
|
401
413
|
}
|
|
402
414
|
|
|
403
415
|
const originalMessages = userMessage ? upsertChatHistoryMessage(liveHistory, userMessage) : liveHistory
|
|
@@ -433,8 +445,9 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
433
445
|
skills?: string[]
|
|
434
446
|
})
|
|
435
447
|
: null
|
|
448
|
+
const defaultLeadAgentId = getLeadAgentId()
|
|
436
449
|
const visibleWorkstreamAgentId =
|
|
437
|
-
workstream.mode === 'direct' ? workstream.agentId : (coreWorkstreamProfile?.config.agentId ??
|
|
450
|
+
workstream.mode === 'direct' ? workstream.agentId : (coreWorkstreamProfile?.config.agentId ?? defaultLeadAgentId)
|
|
438
451
|
const coreInstructionSections = coreWorkstreamProfile ? [coreWorkstreamProfile.instructions] : undefined
|
|
439
452
|
const getLinearInstallationByOrgId = getPluginService([
|
|
440
453
|
'linear',
|
|
@@ -456,6 +469,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
456
469
|
forceDeep: highImpactAssessment.classes.length > 0 || policyAssessment.classes.length > 0,
|
|
457
470
|
explicitProfile: onboardingActive ? 'standard' : undefined,
|
|
458
471
|
})
|
|
472
|
+
timer.step('reasoning-classification')
|
|
459
473
|
|
|
460
474
|
const [linearInstallation, githubInstallation, indexedRepoContext, recentDomainEvents, promptSummary] =
|
|
461
475
|
await Promise.all([
|
|
@@ -469,6 +483,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
469
483
|
? workspaceProvider.buildPromptSummary(orgRef).catch(() => undefined)
|
|
470
484
|
: Promise.resolve(undefined),
|
|
471
485
|
])
|
|
486
|
+
timer.step('parallel-context-fetch(plugins+repos+events+summary)')
|
|
472
487
|
let linearInstalled = Boolean(linearInstallation)
|
|
473
488
|
let githubInstalled = Boolean(githubInstallation)
|
|
474
489
|
let promptContext = buildAgentPromptContext({
|
|
@@ -487,6 +502,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
487
502
|
userId: userIdString,
|
|
488
503
|
query: messageText,
|
|
489
504
|
})
|
|
505
|
+
timer.step('rag-knowledge-retrieval')
|
|
490
506
|
const buildContextResult = asRecord(
|
|
491
507
|
await turnHooks.buildContext?.({
|
|
492
508
|
workstream,
|
|
@@ -508,6 +524,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
508
524
|
retrievedKnowledgeSection,
|
|
509
525
|
}),
|
|
510
526
|
)
|
|
527
|
+
timer.step('hook:buildContext')
|
|
511
528
|
const buildContextPromptDetails = readOptionalString(buildContextResult?.systemWorkspaceDetails)
|
|
512
529
|
if (buildContextPromptDetails) {
|
|
513
530
|
promptContext = { systemWorkspaceDetails: buildContextPromptDetails }
|
|
@@ -546,6 +563,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
546
563
|
context: buildContextResult,
|
|
547
564
|
}),
|
|
548
565
|
)
|
|
566
|
+
timer.step('hook:buildExtraInstructionSections')
|
|
549
567
|
|
|
550
568
|
let memoryBlock = workstreamService.formatMemoryBlockForPrompt(workstreamRecord)
|
|
551
569
|
let workstreamState = initialWorkstreamState
|
|
@@ -585,6 +603,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
585
603
|
invalidateExecutionPlanInstructionSections()
|
|
586
604
|
}
|
|
587
605
|
}
|
|
606
|
+
timer.step('execution-plan-input')
|
|
588
607
|
const preSeededMemoriesByAgent = new Map<string, string | undefined>()
|
|
589
608
|
const getPreSeededMemoriesSection = async (agentId: string): Promise<string | undefined> => {
|
|
590
609
|
if (preSeededMemoriesByAgent.has(agentId)) {
|
|
@@ -635,17 +654,21 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
635
654
|
uploadMetadataText: buildReadableUploadMetadataText(listReadableUploads(extraMessages)),
|
|
636
655
|
})
|
|
637
656
|
|
|
657
|
+
timer.step('preparation-complete')
|
|
658
|
+
|
|
638
659
|
return {
|
|
639
660
|
originalMessages,
|
|
640
661
|
run: async (writer?: UIMessageStreamWriter<ChatMessage>) => {
|
|
641
662
|
const executeRun = async () => {
|
|
663
|
+
const runTimer = lotaDebugLogger.timer('run')
|
|
642
664
|
const serverRunId = Bun.randomUUIDv7()
|
|
643
665
|
const runAbort = createServerRunAbortController(params.abortSignal)
|
|
644
|
-
await
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
666
|
+
await Promise.all([
|
|
667
|
+
workstreamService.setActiveRunId(workstreamRef, serverRunId),
|
|
668
|
+
params.streamId ? workstreamService.setActiveStreamId(workstreamRef, params.streamId) : undefined,
|
|
669
|
+
])
|
|
648
670
|
chatRunRegistry.register(serverRunId, runAbort.controller)
|
|
671
|
+
runTimer.step('set-active-run+stream')
|
|
649
672
|
|
|
650
673
|
try {
|
|
651
674
|
const buildAgentMetadataPatch = (agentId: string, agentName: string): NonNullable<MessageMetadata> => ({
|
|
@@ -704,7 +727,9 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
704
727
|
prepareStep?: PrepareStepFunction<ToolSet>
|
|
705
728
|
abortSignal?: AbortSignal
|
|
706
729
|
}): Promise<ChatMessage> => {
|
|
730
|
+
const agentTimer = lotaDebugLogger.timer(`agent:${streamParams.agentId}`)
|
|
707
731
|
const executionPlanInstructionSections = await getExecutionPlanInstructionSections()
|
|
732
|
+
agentTimer.step('get-execution-plan')
|
|
708
733
|
const agentResolution = asRecord(
|
|
709
734
|
await turnHooks.resolveAgent?.({
|
|
710
735
|
agentId: streamParams.agentId,
|
|
@@ -723,7 +748,14 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
723
748
|
context: buildContextResult,
|
|
724
749
|
}),
|
|
725
750
|
)
|
|
751
|
+
agentTimer.step('hook:resolveAgent')
|
|
726
752
|
const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? streamParams.agentId
|
|
753
|
+
const [preSeededMemoriesSection, workstreamStateSection, learnedSkillsSection] = await Promise.all([
|
|
754
|
+
getPreSeededMemoriesSection(resolvedAgentId),
|
|
755
|
+
getWorkstreamStateSection(),
|
|
756
|
+
getLearnedSkillsSection(resolvedAgentId),
|
|
757
|
+
])
|
|
758
|
+
agentTimer.step('parallel-fetch(memories+state+skills)')
|
|
727
759
|
const config = getAgentRuntimeConfig({
|
|
728
760
|
agentId: resolvedAgentId,
|
|
729
761
|
workstreamMode: workstream.mode,
|
|
@@ -733,11 +765,11 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
733
765
|
linearInstalled,
|
|
734
766
|
reasoningProfile: reasoningProfile.name,
|
|
735
767
|
systemWorkspaceDetails: promptContext.systemWorkspaceDetails,
|
|
736
|
-
preSeededMemoriesSection
|
|
768
|
+
preSeededMemoriesSection,
|
|
737
769
|
retrievedKnowledgeSection,
|
|
738
770
|
workstreamMemoryBlock: memoryBlock,
|
|
739
|
-
workstreamStateSection
|
|
740
|
-
learnedSkillsSection
|
|
771
|
+
workstreamStateSection,
|
|
772
|
+
learnedSkillsSection,
|
|
741
773
|
additionalInstructionSections: mergeInstructionSections(
|
|
742
774
|
executionPlanInstructionSections,
|
|
743
775
|
streamParams.additionalInstructionSections,
|
|
@@ -747,9 +779,11 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
747
779
|
),
|
|
748
780
|
context: buildContextResult,
|
|
749
781
|
}) as AgentRuntimeConfig
|
|
782
|
+
agentTimer.step('build-agent-config')
|
|
750
783
|
const modelMessages = await convertToModelMessages(streamParams.messages, {
|
|
751
784
|
ignoreIncompleteToolCalls: true,
|
|
752
785
|
})
|
|
786
|
+
agentTimer.step('convert-model-messages')
|
|
753
787
|
const agent = (createAgent as unknown as AgentFactory)[config.id as string]({
|
|
754
788
|
mode: streamParams.mode,
|
|
755
789
|
tools: streamParams.tools,
|
|
@@ -763,12 +797,14 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
763
797
|
(agentResolution?.prepareStep as PrepareStepFunction<ToolSet> | undefined) ?? streamParams.prepareStep,
|
|
764
798
|
})
|
|
765
799
|
const agentAbortSignal = streamParams.abortSignal ?? runAbort.signal
|
|
800
|
+
agentTimer.step('agent-construction')
|
|
766
801
|
|
|
767
802
|
let result: unknown
|
|
768
803
|
try {
|
|
769
804
|
result = await streamParams.observer.run(() =>
|
|
770
805
|
agent.stream({ messages: modelMessages, abortSignal: agentAbortSignal }),
|
|
771
806
|
)
|
|
807
|
+
agentTimer.step('agent.stream()-resolved')
|
|
772
808
|
} catch (error) {
|
|
773
809
|
if (agentAbortSignal.aborted) {
|
|
774
810
|
streamParams.observer.recordAbort(error)
|
|
@@ -802,10 +838,15 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
802
838
|
},
|
|
803
839
|
}) as ReadableStream<ChatStreamChunk>
|
|
804
840
|
const reader = uiStream.getReader()
|
|
841
|
+
let firstChunkLogged = false
|
|
805
842
|
try {
|
|
806
843
|
for (;;) {
|
|
807
844
|
const { done, value } = await reader.read()
|
|
808
845
|
if (done) break
|
|
846
|
+
if (!firstChunkLogged) {
|
|
847
|
+
agentTimer.step('first-stream-chunk')
|
|
848
|
+
firstChunkLogged = true
|
|
849
|
+
}
|
|
809
850
|
if (streamParams.writer) {
|
|
810
851
|
streamParams.writer.write(value)
|
|
811
852
|
}
|
|
@@ -813,6 +854,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
813
854
|
} finally {
|
|
814
855
|
reader.releaseLock()
|
|
815
856
|
}
|
|
857
|
+
agentTimer.step('stream-complete')
|
|
816
858
|
|
|
817
859
|
const finalizedResponseMessage = await finishedStream.then(() => responseMessage)
|
|
818
860
|
if (finalizedResponseMessage === null) {
|
|
@@ -834,6 +876,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
834
876
|
extraMessages?: ChatMessage[]
|
|
835
877
|
extraTools?: ToolSet
|
|
836
878
|
}): Promise<ChatMessage> => {
|
|
879
|
+
const visibleTimer = lotaDebugLogger.timer(`visible:${runParams.agentId}`)
|
|
837
880
|
let runMemoryBlock = memoryBlock
|
|
838
881
|
const tools: ToolSet = {
|
|
839
882
|
...((await buildAgentTools({
|
|
@@ -863,6 +906,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
863
906
|
...toolProviders,
|
|
864
907
|
...runParams.extraTools,
|
|
865
908
|
}
|
|
909
|
+
visibleTimer.step('build-agent-tools')
|
|
866
910
|
const responseMessage = await streamAgentResponse({
|
|
867
911
|
agentId: runParams.agentId,
|
|
868
912
|
mode: runParams.mode,
|
|
@@ -874,6 +918,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
874
918
|
writer,
|
|
875
919
|
})
|
|
876
920
|
|
|
921
|
+
visibleTimer.step('stream-agent-response')
|
|
877
922
|
memoryBlock = runMemoryBlock
|
|
878
923
|
|
|
879
924
|
return await commitAssistantResponse(
|
|
@@ -921,7 +966,17 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
921
966
|
context: buildContextResult,
|
|
922
967
|
})
|
|
923
968
|
|
|
924
|
-
const
|
|
969
|
+
const [
|
|
970
|
+
specialistExecutionPlanInstructionSections,
|
|
971
|
+
specialistPreSeededMemories,
|
|
972
|
+
specialistWorkstreamState,
|
|
973
|
+
specialistLearnedSkills,
|
|
974
|
+
] = await Promise.all([
|
|
975
|
+
getExecutionPlanInstructionSections(),
|
|
976
|
+
getPreSeededMemoriesSection(agentId),
|
|
977
|
+
getWorkstreamStateSection(),
|
|
978
|
+
getLearnedSkillsSection(agentId),
|
|
979
|
+
])
|
|
925
980
|
const specialistConfig = getAgentRuntimeConfig({
|
|
926
981
|
agentId,
|
|
927
982
|
workstreamMode: workstream.mode,
|
|
@@ -930,11 +985,11 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
930
985
|
linearInstalled,
|
|
931
986
|
reasoningProfile: reasoningProfile.name,
|
|
932
987
|
systemWorkspaceDetails: promptContext.systemWorkspaceDetails,
|
|
933
|
-
preSeededMemoriesSection:
|
|
988
|
+
preSeededMemoriesSection: specialistPreSeededMemories,
|
|
934
989
|
retrievedKnowledgeSection,
|
|
935
990
|
workstreamMemoryBlock: specialistMemoryBlock,
|
|
936
|
-
workstreamStateSection:
|
|
937
|
-
learnedSkillsSection:
|
|
991
|
+
workstreamStateSection: specialistWorkstreamState,
|
|
992
|
+
learnedSkillsSection: specialistLearnedSkills,
|
|
938
993
|
additionalInstructionSections: mergeInstructionSections(
|
|
939
994
|
specialistExecutionPlanInstructionSections,
|
|
940
995
|
coreInstructionSections,
|
|
@@ -1047,7 +1102,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
1047
1102
|
await runVisibleAgent({ agentId: workstream.agentId, mode: 'direct' })
|
|
1048
1103
|
} else {
|
|
1049
1104
|
await runVisibleAgent({
|
|
1050
|
-
agentId: visibleWorkstreamAgentId ??
|
|
1105
|
+
agentId: visibleWorkstreamAgentId ?? defaultLeadAgentId,
|
|
1051
1106
|
mode: 'workstreamMode',
|
|
1052
1107
|
skills: coreWorkstreamProfile?.skills ? [...coreWorkstreamProfile.skills] : undefined,
|
|
1053
1108
|
additionalInstructionSections: mergeInstructionSections(coreInstructionSections, hookInstructionSections),
|
|
@@ -1145,17 +1200,17 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
1145
1200
|
mergeKey: `workstream:${workstreamIdString}`,
|
|
1146
1201
|
title: buildRecentActivityChatSystemTitle({
|
|
1147
1202
|
workstream,
|
|
1148
|
-
visibleAgentId: visibleWorkstreamAgentId ??
|
|
1203
|
+
visibleAgentId: visibleWorkstreamAgentId ?? defaultLeadAgentId,
|
|
1149
1204
|
}),
|
|
1150
|
-
sourceLabel: agentDisplayNames[visibleWorkstreamAgentId ??
|
|
1205
|
+
sourceLabel: agentDisplayNames[visibleWorkstreamAgentId ?? defaultLeadAgentId],
|
|
1151
1206
|
deepLink: buildRecentActivityChatDeepLink({
|
|
1152
1207
|
workstream,
|
|
1153
1208
|
workstreamId: workstreamIdString,
|
|
1154
|
-
visibleAgentId: visibleWorkstreamAgentId ??
|
|
1209
|
+
visibleAgentId: visibleWorkstreamAgentId ?? defaultLeadAgentId,
|
|
1155
1210
|
}),
|
|
1156
1211
|
metadata: {
|
|
1157
|
-
agentId: visibleWorkstreamAgentId ??
|
|
1158
|
-
agentName: agentDisplayNames[visibleWorkstreamAgentId ??
|
|
1212
|
+
agentId: visibleWorkstreamAgentId ?? defaultLeadAgentId,
|
|
1213
|
+
agentName: agentDisplayNames[visibleWorkstreamAgentId ?? defaultLeadAgentId],
|
|
1159
1214
|
workstreamId: workstreamIdString,
|
|
1160
1215
|
workstreamTitle: latestWorkstreamRecord.title ?? workstream.title,
|
|
1161
1216
|
workstreamMode: workstream.mode,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { ChatMessage } from '@lota-sdk/shared
|
|
1
|
+
import type { ChatMessage } from '@lota-sdk/shared'
|
|
2
2
|
import { createUIMessageStream } from 'ai'
|
|
3
3
|
|
|
4
|
+
import { lotaDebugLogger } from '../config/debug-logger'
|
|
4
5
|
import { hasApprovalRespondedParts, isApprovalContinuationRequest } from '../runtime/approval-continuation'
|
|
5
6
|
import { wrapResponseWithKeepalive } from '../utils/sse-keepalive'
|
|
6
7
|
import { prepareWorkstreamRunCore } from './workstream-turn-preparation'
|
|
@@ -10,42 +11,54 @@ export { hasApprovalRespondedParts, isApprovalContinuationRequest }
|
|
|
10
11
|
export { wrapResponseWithKeepalive }
|
|
11
12
|
|
|
12
13
|
export async function createWorkstreamApprovalContinuationStream(params: WorkstreamApprovalContinuationParams) {
|
|
14
|
+
const timer = lotaDebugLogger.timer('turn:approval-continuation')
|
|
13
15
|
const prepared = await prepareWorkstreamRunCore({ ...params, kind: 'approvalContinuation' })
|
|
16
|
+
timer.step('prepare')
|
|
14
17
|
|
|
15
18
|
return createUIMessageStream<ChatMessage>({
|
|
16
19
|
originalMessages: prepared.originalMessages,
|
|
17
20
|
onError: (error) => (error instanceof Error ? error.message : 'Approval continuation stream failed.'),
|
|
18
21
|
execute: async ({ writer }) => {
|
|
19
22
|
await prepared.run(writer)
|
|
23
|
+
timer.step('run')
|
|
20
24
|
},
|
|
21
25
|
})
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
export async function createWorkstreamNativeToolApprovalStream(params: WorkstreamApprovalContinuationParams) {
|
|
29
|
+
const timer = lotaDebugLogger.timer('turn:native-tool-approval')
|
|
25
30
|
const prepared = await prepareWorkstreamRunCore({ ...params, kind: 'nativeToolApprovalTurn' })
|
|
31
|
+
timer.step('prepare')
|
|
26
32
|
|
|
27
33
|
return createUIMessageStream<ChatMessage>({
|
|
28
34
|
originalMessages: prepared.originalMessages,
|
|
29
35
|
onError: (error) => (error instanceof Error ? error.message : 'Native tool approval stream failed.'),
|
|
30
36
|
execute: async ({ writer }) => {
|
|
31
37
|
await prepared.run(writer)
|
|
38
|
+
timer.step('run')
|
|
32
39
|
},
|
|
33
40
|
})
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
export async function createWorkstreamTurnStream(params: WorkstreamTurnParams) {
|
|
44
|
+
const timer = lotaDebugLogger.timer('turn:user-turn')
|
|
37
45
|
const prepared = await prepareWorkstreamRunCore({ ...params, kind: 'userTurn' })
|
|
46
|
+
timer.step('prepare')
|
|
38
47
|
|
|
39
48
|
return createUIMessageStream<ChatMessage>({
|
|
40
49
|
originalMessages: prepared.originalMessages,
|
|
41
50
|
onError: (error) => (error instanceof Error ? error.message : 'Chat stream failed.'),
|
|
42
51
|
execute: async ({ writer }) => {
|
|
43
52
|
await prepared.run(writer)
|
|
53
|
+
timer.step('run')
|
|
44
54
|
},
|
|
45
55
|
})
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
export async function runWorkstreamTurnInBackground(params: WorkstreamTurnParams): Promise<void> {
|
|
59
|
+
const timer = lotaDebugLogger.timer('turn:background')
|
|
49
60
|
const prepared = await prepareWorkstreamRunCore({ ...params, kind: 'userTurn' })
|
|
61
|
+
timer.step('prepare')
|
|
50
62
|
await prepared.run()
|
|
63
|
+
timer.step('run')
|
|
51
64
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WORKSTREAM } from '@lota-sdk/shared
|
|
1
|
+
import { WORKSTREAM } from '@lota-sdk/shared'
|
|
2
2
|
import { BoundQuery, RecordId, StringRecordId, surql } from 'surrealdb'
|
|
3
3
|
|
|
4
4
|
import { agentDisplayNames, getCoreWorkstreamProfile, isAgentName } from '../config/agent-defaults'
|
|
@@ -345,19 +345,19 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
345
345
|
|
|
346
346
|
const onboardingWelcome = bootstrapConfig.onboardingWelcome
|
|
347
347
|
if (!onboardingCompleted && onboardingWelcome) {
|
|
348
|
-
const
|
|
348
|
+
const createdOnboardingOwnerWorkstream = createdWorkstreams.find(
|
|
349
349
|
(workstream) => workstream.mode === 'direct' && workstream.agentId === onboardingWelcome.directAgentId,
|
|
350
350
|
)
|
|
351
|
-
const
|
|
351
|
+
const existingOnboardingOwnerWorkstream = directWorkstreamsByAgent.get(onboardingWelcome.directAgentId)
|
|
352
352
|
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
(
|
|
353
|
+
const onboardingOwnerWorkstreamId =
|
|
354
|
+
createdOnboardingOwnerWorkstream?.id ??
|
|
355
|
+
(existingOnboardingOwnerWorkstream ? this.normalizeWorkstreamId(existingOnboardingOwnerWorkstream.id) : null)
|
|
356
356
|
|
|
357
|
-
if (
|
|
358
|
-
const
|
|
357
|
+
if (onboardingOwnerWorkstreamId) {
|
|
358
|
+
const onboardingOwnerWorkstreamRef = ensureRecordId(onboardingOwnerWorkstreamId, TABLES.WORKSTREAM)
|
|
359
359
|
await workstreamMessageService.ensureBootstrapWelcomeMessage({
|
|
360
|
-
workstreamId:
|
|
360
|
+
workstreamId: onboardingOwnerWorkstreamRef,
|
|
361
361
|
agentId: onboardingWelcome.directAgentId,
|
|
362
362
|
text: onboardingWelcome.buildMessageText({ userName: options?.userName }),
|
|
363
363
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SUPPORTED_ATTACHMENT_MIME_TYPES, getAttachmentExtension } from '@lota-sdk/shared
|
|
1
|
+
import { SUPPORTED_ATTACHMENT_MIME_TYPES, getAttachmentExtension } from '@lota-sdk/shared'
|
|
2
2
|
import mammoth from 'mammoth'
|
|
3
3
|
import { PDFParse } from 'pdf-parse'
|
|
4
4
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { inferContentType } from '@lota-sdk/shared
|
|
1
|
+
import { inferContentType } from '@lota-sdk/shared'
|
|
2
2
|
import { S3Client } from 'bun'
|
|
3
3
|
|
|
4
|
-
import { env } from '../config/env-shapes'
|
|
5
4
|
import { serverLogger } from '../config/logger'
|
|
5
|
+
import { getRuntimeConfig } from '../runtime/runtime-config'
|
|
6
6
|
import { readString } from '../utils/string'
|
|
7
7
|
import {
|
|
8
8
|
extractAttachmentText,
|
|
@@ -41,17 +41,18 @@ export class AttachmentStorageService {
|
|
|
41
41
|
private readonly client: S3Client
|
|
42
42
|
|
|
43
43
|
constructor() {
|
|
44
|
+
const config = getRuntimeConfig()
|
|
44
45
|
this.client = new S3Client({
|
|
45
|
-
accessKeyId:
|
|
46
|
-
secretAccessKey:
|
|
47
|
-
bucket:
|
|
48
|
-
endpoint:
|
|
49
|
-
region:
|
|
46
|
+
accessKeyId: config.s3.accessKeyId,
|
|
47
|
+
secretAccessKey: config.s3.secretAccessKey,
|
|
48
|
+
bucket: config.s3.bucket,
|
|
49
|
+
endpoint: config.s3.endpoint,
|
|
50
|
+
region: config.s3.region,
|
|
50
51
|
})
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
getAttachmentUrl(storageKey: string): string {
|
|
54
|
-
return this.client.file(storageKey).presign({ expiresIn:
|
|
55
|
+
return this.client.file(storageKey).presign({ expiresIn: getRuntimeConfig().s3.attachmentUrlExpiresIn })
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
async writeOrganizationDocument({
|
|
@@ -102,7 +103,7 @@ export class AttachmentStorageService {
|
|
|
102
103
|
sizeBytes: file.size,
|
|
103
104
|
storageKey,
|
|
104
105
|
url: this.getAttachmentUrl(storageKey),
|
|
105
|
-
expiresInSeconds:
|
|
106
|
+
expiresInSeconds: getRuntimeConfig().s3.attachmentUrlExpiresIn,
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
|
|
@@ -128,7 +129,7 @@ export class AttachmentStorageService {
|
|
|
128
129
|
sizeBytes,
|
|
129
130
|
storageKey,
|
|
130
131
|
url: this.getAttachmentUrl(storageKey),
|
|
131
|
-
expiresInSeconds:
|
|
132
|
+
expiresInSeconds: getRuntimeConfig().s3.attachmentUrlExpiresIn,
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
|