@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.
Files changed (153) hide show
  1. package/infrastructure/schema/00_workstream.surql +55 -0
  2. package/infrastructure/schema/01_memory.surql +47 -0
  3. package/infrastructure/schema/02_execution_plan.surql +62 -0
  4. package/infrastructure/schema/03_learned_skill.surql +32 -0
  5. package/infrastructure/schema/04_runtime_bootstrap.surql +8 -0
  6. package/package.json +128 -0
  7. package/src/ai/definitions.ts +308 -0
  8. package/src/bifrost/bifrost.ts +256 -0
  9. package/src/config/agent-defaults.ts +99 -0
  10. package/src/config/constants.ts +33 -0
  11. package/src/config/env-shapes.ts +122 -0
  12. package/src/config/logger.ts +29 -0
  13. package/src/config/model-constants.ts +31 -0
  14. package/src/config/search.ts +17 -0
  15. package/src/config/workstream-defaults.ts +68 -0
  16. package/src/db/base.service.ts +55 -0
  17. package/src/db/cursor-pagination.ts +73 -0
  18. package/src/db/memory-query-builder.ts +207 -0
  19. package/src/db/memory-store.helpers.ts +118 -0
  20. package/src/db/memory-store.rows.ts +29 -0
  21. package/src/db/memory-store.ts +974 -0
  22. package/src/db/memory-types.ts +193 -0
  23. package/src/db/memory.ts +505 -0
  24. package/src/db/record-id.ts +78 -0
  25. package/src/db/service.ts +932 -0
  26. package/src/db/startup.ts +152 -0
  27. package/src/db/tables.ts +20 -0
  28. package/src/document/org-document-chunking.ts +224 -0
  29. package/src/document/parsing.ts +40 -0
  30. package/src/embeddings/provider.ts +76 -0
  31. package/src/index.ts +302 -0
  32. package/src/queues/context-compaction.queue.ts +82 -0
  33. package/src/queues/document-processor.queue.ts +118 -0
  34. package/src/queues/memory-consolidation.queue.ts +65 -0
  35. package/src/queues/post-chat-memory.queue.ts +128 -0
  36. package/src/queues/recent-activity-title-refinement.queue.ts +69 -0
  37. package/src/queues/regular-chat-memory-digest.config.ts +12 -0
  38. package/src/queues/regular-chat-memory-digest.queue.ts +73 -0
  39. package/src/queues/skill-extraction.config.ts +9 -0
  40. package/src/queues/skill-extraction.queue.ts +62 -0
  41. package/src/redis/connection.ts +176 -0
  42. package/src/redis/index.ts +30 -0
  43. package/src/redis/org-memory-lock.ts +43 -0
  44. package/src/redis/redis-lease-lock.ts +158 -0
  45. package/src/runtime/agent-contract.ts +1 -0
  46. package/src/runtime/agent-prompt-context.ts +119 -0
  47. package/src/runtime/agent-runtime-policy.ts +192 -0
  48. package/src/runtime/agent-stream-helpers.ts +117 -0
  49. package/src/runtime/agent-types.ts +22 -0
  50. package/src/runtime/approval-continuation.ts +16 -0
  51. package/src/runtime/chat-attachments.ts +46 -0
  52. package/src/runtime/chat-message.ts +10 -0
  53. package/src/runtime/chat-request-routing.ts +21 -0
  54. package/src/runtime/chat-run-orchestration.ts +25 -0
  55. package/src/runtime/chat-run-registry.ts +20 -0
  56. package/src/runtime/chat-types.ts +18 -0
  57. package/src/runtime/context-compaction-constants.ts +11 -0
  58. package/src/runtime/context-compaction-runtime.ts +86 -0
  59. package/src/runtime/context-compaction.ts +909 -0
  60. package/src/runtime/execution-plan.ts +59 -0
  61. package/src/runtime/helper-model.ts +405 -0
  62. package/src/runtime/indexed-repositories-policy.ts +28 -0
  63. package/src/runtime/instruction-sections.ts +8 -0
  64. package/src/runtime/llm-content.ts +71 -0
  65. package/src/runtime/memory-block.ts +264 -0
  66. package/src/runtime/memory-digest-policy.ts +14 -0
  67. package/src/runtime/memory-format.ts +8 -0
  68. package/src/runtime/memory-pipeline.ts +570 -0
  69. package/src/runtime/memory-prompts-fact.ts +47 -0
  70. package/src/runtime/memory-prompts-parse.ts +3 -0
  71. package/src/runtime/memory-prompts-update.ts +37 -0
  72. package/src/runtime/memory-scope.ts +43 -0
  73. package/src/runtime/plugin-types.ts +10 -0
  74. package/src/runtime/retrieval-adapters.ts +25 -0
  75. package/src/runtime/retrieval-pipeline.ts +3 -0
  76. package/src/runtime/runtime-extensions.ts +154 -0
  77. package/src/runtime/skill-extraction-policy.ts +3 -0
  78. package/src/runtime/team-consultation-orchestrator.ts +245 -0
  79. package/src/runtime/team-consultation-prompts.ts +32 -0
  80. package/src/runtime/title-helpers.ts +12 -0
  81. package/src/runtime/turn-lifecycle.ts +28 -0
  82. package/src/runtime/workstream-chat-helpers.ts +187 -0
  83. package/src/runtime/workstream-routing-policy.ts +301 -0
  84. package/src/runtime/workstream-state.ts +261 -0
  85. package/src/services/attachment.service.ts +159 -0
  86. package/src/services/chat-attachments.service.ts +17 -0
  87. package/src/services/chat-run-registry.service.ts +3 -0
  88. package/src/services/context-compaction-runtime.ts +13 -0
  89. package/src/services/context-compaction.service.ts +115 -0
  90. package/src/services/document-chunk.service.ts +141 -0
  91. package/src/services/execution-plan.service.ts +890 -0
  92. package/src/services/learned-skill.service.ts +328 -0
  93. package/src/services/memory-assessment.service.ts +43 -0
  94. package/src/services/memory.service.ts +807 -0
  95. package/src/services/memory.utils.ts +84 -0
  96. package/src/services/mutating-approval.service.ts +110 -0
  97. package/src/services/recent-activity-title.service.ts +74 -0
  98. package/src/services/recent-activity.service.ts +397 -0
  99. package/src/services/workstream-change-tracker.service.ts +313 -0
  100. package/src/services/workstream-message.service.ts +283 -0
  101. package/src/services/workstream-title.service.ts +58 -0
  102. package/src/services/workstream-turn-preparation.ts +1340 -0
  103. package/src/services/workstream-turn.ts +37 -0
  104. package/src/services/workstream.service.ts +854 -0
  105. package/src/services/workstream.types.ts +118 -0
  106. package/src/storage/attachment-parser.ts +101 -0
  107. package/src/storage/attachment-storage.service.ts +391 -0
  108. package/src/storage/attachments.types.ts +11 -0
  109. package/src/storage/attachments.utils.ts +58 -0
  110. package/src/storage/generated-document-storage.service.ts +55 -0
  111. package/src/system-agents/agent-result.ts +27 -0
  112. package/src/system-agents/context-compacter.agent.ts +46 -0
  113. package/src/system-agents/delegated-agent-factory.ts +177 -0
  114. package/src/system-agents/helper-agent-options.ts +20 -0
  115. package/src/system-agents/memory-reranker.agent.ts +38 -0
  116. package/src/system-agents/memory.agent.ts +58 -0
  117. package/src/system-agents/recent-activity-title-refiner.agent.ts +53 -0
  118. package/src/system-agents/regular-chat-memory-digest.agent.ts +75 -0
  119. package/src/system-agents/researcher.agent.ts +34 -0
  120. package/src/system-agents/skill-extractor.agent.ts +88 -0
  121. package/src/system-agents/skill-manager.agent.ts +80 -0
  122. package/src/system-agents/title-generator.agent.ts +42 -0
  123. package/src/system-agents/workstream-tracker.agent.ts +58 -0
  124. package/src/tools/execution-plan.tool.ts +163 -0
  125. package/src/tools/fetch-webpage.tool.ts +132 -0
  126. package/src/tools/firecrawl-client.ts +12 -0
  127. package/src/tools/memory-block.tool.ts +55 -0
  128. package/src/tools/read-file-parts.tool.ts +80 -0
  129. package/src/tools/remember-memory.tool.ts +85 -0
  130. package/src/tools/research-topic.tool.ts +15 -0
  131. package/src/tools/search-tools.ts +55 -0
  132. package/src/tools/search-web.tool.ts +175 -0
  133. package/src/tools/team-think.tool.ts +125 -0
  134. package/src/tools/tool-contract.ts +21 -0
  135. package/src/tools/user-questions.tool.ts +18 -0
  136. package/src/utils/async.ts +50 -0
  137. package/src/utils/date-time.ts +34 -0
  138. package/src/utils/error.ts +10 -0
  139. package/src/utils/errors.ts +28 -0
  140. package/src/utils/hono-error-handler.ts +71 -0
  141. package/src/utils/string.ts +51 -0
  142. package/src/workers/bootstrap.ts +44 -0
  143. package/src/workers/memory-consolidation.worker.ts +318 -0
  144. package/src/workers/regular-chat-memory-digest.helpers.ts +100 -0
  145. package/src/workers/regular-chat-memory-digest.runner.ts +363 -0
  146. package/src/workers/regular-chat-memory-digest.worker.ts +22 -0
  147. package/src/workers/skill-extraction.runner.ts +331 -0
  148. package/src/workers/skill-extraction.worker.ts +22 -0
  149. package/src/workers/utils/repo-indexer-chunker.ts +331 -0
  150. package/src/workers/utils/repo-structure-extractor.ts +645 -0
  151. package/src/workers/utils/repomix-process-concurrency.ts +65 -0
  152. package/src/workers/utils/sandbox-error.ts +5 -0
  153. package/src/workers/worker-utils.ts +182 -0
@@ -0,0 +1,10 @@
1
+ export interface LotaPluginContributions {
2
+ envKeys: readonly string[]
3
+ schemaFiles: readonly (string | URL)[]
4
+ }
5
+
6
+ export interface LotaPlugin<TServices = unknown, TTools = unknown> {
7
+ services: TServices
8
+ tools?: TTools
9
+ contributions: LotaPluginContributions
10
+ }
@@ -0,0 +1,25 @@
1
+ interface ScopedRetrievalTask<TCandidate> {
2
+ scopeTag: string
3
+ retrieve: () => Promise<TCandidate[]>
4
+ }
5
+
6
+ interface ScopedRetrievalResult<TCandidate> {
7
+ scopeTag: string
8
+ candidates: TCandidate[]
9
+ }
10
+
11
+ export async function executeScopedRetrieval<TCandidate>(
12
+ tasks: ScopedRetrievalTask<TCandidate>[],
13
+ ): Promise<ScopedRetrievalResult<TCandidate>[]> {
14
+ return await Promise.all(tasks.map(async (task) => ({ scopeTag: task.scopeTag, candidates: await task.retrieve() })))
15
+ }
16
+
17
+ export function countScopedRetrievalCandidates<TCandidate>(scoped: ScopedRetrievalResult<TCandidate>[]): number {
18
+ return scoped.reduce((sum, entry) => sum + entry.candidates.length, 0)
19
+ }
20
+
21
+ export function scopedRetrievalToMap<TCandidate>(
22
+ scoped: ScopedRetrievalResult<TCandidate>[],
23
+ ): Map<string, TCandidate[]> {
24
+ return new Map(scoped.map((entry) => [entry.scopeTag, entry.candidates]))
25
+ }
@@ -0,0 +1,3 @@
1
+ export function resolveCandidateLimit(params: { limit: number; multiplier: number; minimum: number }): number {
2
+ return Math.max(params.limit * params.multiplier, params.minimum)
3
+ }
@@ -0,0 +1,154 @@
1
+ import type { ToolSet } from 'ai'
2
+
3
+ import type { RecordIdRef } from '../db/record-id'
4
+ import type { ReadableUploadMetadata } from '../services/attachment.service'
5
+
6
+ export interface LotaRuntimeBackgroundCursor {
7
+ createdAt: Date
8
+ id: string
9
+ }
10
+
11
+ export type LotaRuntimeBackgroundCursorKind = 'regular-chat-digest' | 'skill-extraction'
12
+
13
+ export interface LotaRuntimeWorkspaceLifecycleState {
14
+ bootstrapActive: boolean
15
+ bootstrapCompletedAt?: Date | null
16
+ }
17
+
18
+ export interface LotaRuntimeWorkspaceProjectionState {
19
+ workspaceName: string
20
+ summaryBlock?: string
21
+ structuredProfile?: Record<string, unknown>
22
+ }
23
+
24
+ export interface LotaRuntimeProfileProjection {
25
+ summaryBlock?: string
26
+ structuredPatch?: Record<string, unknown>
27
+ }
28
+
29
+ export interface LotaRuntimeWorkspaceProvider {
30
+ getWorkspace(workspaceId: RecordIdRef): Promise<Record<string, unknown>>
31
+ getLifecycleState?(
32
+ workspace: Record<string, unknown>,
33
+ ): Promise<LotaRuntimeWorkspaceLifecycleState> | LotaRuntimeWorkspaceLifecycleState
34
+ readProfileProjectionState?(
35
+ workspace: Record<string, unknown>,
36
+ ): Promise<LotaRuntimeWorkspaceProjectionState | void> | LotaRuntimeWorkspaceProjectionState | void
37
+ buildPromptSummary?(workspaceId: RecordIdRef): Promise<string | undefined>
38
+ listRecentDomainEvents?(workspaceId: RecordIdRef, limit?: number): Promise<Array<Record<string, unknown>>>
39
+ hasActiveKnowledgeSources?(workspaceId: string): Promise<boolean>
40
+ buildRetrievedKnowledgeSection?(params: {
41
+ workspaceId: string
42
+ userId: string
43
+ query: string
44
+ }): Promise<string | undefined>
45
+ applyProfileProjection?(workspaceId: RecordIdRef, projection: LotaRuntimeProfileProjection): Promise<void>
46
+ getBackgroundCursor?(
47
+ kind: LotaRuntimeBackgroundCursorKind,
48
+ workspaceId: RecordIdRef,
49
+ ): Promise<LotaRuntimeBackgroundCursor | null>
50
+ setBackgroundCursor?(
51
+ kind: LotaRuntimeBackgroundCursorKind,
52
+ workspaceId: RecordIdRef,
53
+ cursor: LotaRuntimeBackgroundCursor,
54
+ ): Promise<void>
55
+ }
56
+
57
+ export interface LotaRuntimeIndexedRepositoriesContext {
58
+ provideRepoTool: boolean
59
+ defaultSectionsByAgent: Record<string, string[]>
60
+ dataSourceContextByAgent?: Record<string, string>
61
+ context?: string
62
+ }
63
+
64
+ export interface LotaRuntimeTeamThinkToolsParams {
65
+ agentId: string
66
+ workspaceId: RecordIdRef
67
+ userId: RecordIdRef
68
+ workspaceIdString: string
69
+ workstreamId: RecordIdRef
70
+ githubInstalled: boolean
71
+ provideRepoTool: boolean
72
+ availableUploads: ReadableUploadMetadata[]
73
+ defaultRepoSections?: string[]
74
+ context?: unknown
75
+ toolProviders?: ToolSet
76
+ }
77
+
78
+ export interface LotaRuntimeTurnHooks {
79
+ buildContext?: (params: Record<string, unknown>) => Promise<Record<string, unknown> | void>
80
+ afterTurn?: (params: Record<string, unknown>) => Promise<void>
81
+ resolveAgent?: (params: Record<string, unknown>) => Promise<Record<string, unknown> | void>
82
+ buildExtraInstructionSections?: (params: Record<string, unknown>) => Promise<string[] | void>
83
+ }
84
+
85
+ export interface LotaRuntimeAdapters {
86
+ services?: { workspaceProvider?: LotaRuntimeWorkspaceProvider }
87
+ workstream?: {
88
+ buildIndexedRepositoriesContext?: (workspaceId: string) => Promise<LotaRuntimeIndexedRepositoriesContext>
89
+ buildTeamThinkAgentTools?: (params: LotaRuntimeTeamThinkToolsParams) => Promise<{ tools: ToolSet }>
90
+ }
91
+ queues?: {
92
+ enqueuePostChatOrgAction?: (job: Record<string, unknown>) => Promise<void> | void
93
+ enqueueOnboardingRepoIndexFollowUp?: (job: Record<string, unknown>) => Promise<void> | void
94
+ }
95
+ workers?: {
96
+ connectPluginDatabases?: () => Promise<void>
97
+ withWorkspaceMemoryLock?: <T>(workspaceId: string, fn: () => Promise<T>) => Promise<T>
98
+ }
99
+ }
100
+
101
+ interface RuntimeExtensionsState {
102
+ adapters: LotaRuntimeAdapters
103
+ turnHooks: LotaRuntimeTurnHooks
104
+ toolProviders: ToolSet
105
+ extraWorkers: Record<string, unknown>
106
+ }
107
+
108
+ const EMPTY_TOOLS = {} as ToolSet
109
+
110
+ let runtimeExtensionsState: RuntimeExtensionsState = {
111
+ adapters: {},
112
+ turnHooks: {},
113
+ toolProviders: EMPTY_TOOLS,
114
+ extraWorkers: {},
115
+ }
116
+
117
+ export function configureRuntimeExtensions(params?: {
118
+ adapters?: LotaRuntimeAdapters
119
+ turnHooks?: LotaRuntimeTurnHooks
120
+ toolProviders?: ToolSet
121
+ extraWorkers?: Record<string, unknown>
122
+ }): void {
123
+ runtimeExtensionsState = {
124
+ adapters: params?.adapters ?? {},
125
+ turnHooks: params?.turnHooks ?? {},
126
+ toolProviders: params?.toolProviders ?? EMPTY_TOOLS,
127
+ extraWorkers: params?.extraWorkers ?? {},
128
+ }
129
+ }
130
+
131
+ export function getRuntimeAdapters(): LotaRuntimeAdapters {
132
+ return runtimeExtensionsState.adapters
133
+ }
134
+
135
+ export function getTurnHooks(): LotaRuntimeTurnHooks {
136
+ return runtimeExtensionsState.turnHooks
137
+ }
138
+
139
+ export function getToolProviders(): ToolSet {
140
+ return runtimeExtensionsState.toolProviders
141
+ }
142
+
143
+ export function getConfiguredPluginDatabaseConnector(): (() => Promise<void>) | undefined {
144
+ return runtimeExtensionsState.adapters.workers?.connectPluginDatabases
145
+ }
146
+
147
+ export async function withConfiguredWorkspaceMemoryLock<T>(workspaceId: string, fn: () => Promise<T>): Promise<T> {
148
+ const adapter = runtimeExtensionsState.adapters.workers?.withWorkspaceMemoryLock
149
+ if (!adapter) {
150
+ return await fn()
151
+ }
152
+
153
+ return await adapter(workspaceId, fn)
154
+ }
@@ -0,0 +1,3 @@
1
+ export function shouldEnqueueSkillExtraction(params: { onboardingActive: boolean }): boolean {
2
+ return !params.onboardingActive
3
+ }
@@ -0,0 +1,245 @@
1
+ import { withMessageCreatedAt } from '@lota-sdk/shared/runtime/chat-message-metadata'
2
+ import type { ChatMessage } from '@lota-sdk/shared/schemas/chat-message'
3
+ import { ConsultTeamArgsSchema } from '@lota-sdk/shared/schemas/tools'
4
+ import type { ConsultTeamResultData } from '@lota-sdk/shared/schemas/tools'
5
+ import { convertToModelMessages, readUIMessageStream, tool as createTool } from 'ai'
6
+
7
+ import { agentDisplayNames, teamConsultParticipants } from '../config/agent-defaults'
8
+ import { createTimedAbortSignal } from './agent-stream-helpers'
9
+ import { buildModelInputMessagesWithUploadMetadata, buildReadableUploadMetadataText } from './chat-attachments'
10
+ import type { ReadableUploadMetadataLike } from './chat-attachments'
11
+ import type { RepoSectionName } from './indexed-repositories-policy'
12
+ import { buildTeamConsultationFailureMessage } from './team-consultation-prompts'
13
+ import { extractMessageText } from './workstream-chat-helpers'
14
+ import type { ReasoningProfileName } from './workstream-routing-policy'
15
+
16
+ export type DefaultRepoSections = RepoSectionName[]
17
+ const TEAM_CONSULTATION_TIMEOUT_MS = 90_000
18
+
19
+ function buildSnapshot(responses: ConsultTeamResultData['responses']): ConsultTeamResultData {
20
+ return { responses: responses.map((response) => ({ ...response })) }
21
+ }
22
+
23
+ function getConsultTeamOutput(output: unknown): ConsultTeamResultData | undefined {
24
+ if (output && typeof output === 'object' && Array.isArray((output as { responses?: unknown }).responses)) {
25
+ return output as ConsultTeamResultData
26
+ }
27
+
28
+ if (Array.isArray(output)) {
29
+ for (let index = output.length - 1; index >= 0; index -= 1) {
30
+ const candidate = getConsultTeamOutput(output[index])
31
+ if (candidate) return candidate
32
+ }
33
+ }
34
+
35
+ return undefined
36
+ }
37
+
38
+ interface ParticipantObserver {
39
+ run<T>(fn: () => T | Promise<T>): Promise<T>
40
+ recordError?: (error: unknown) => void
41
+ recordAbort?: (error: unknown) => void
42
+ }
43
+
44
+ export interface TeamConsultationParticipantRunner {
45
+ buildParticipantAgent(
46
+ agentId: string,
47
+ params: {
48
+ task: string
49
+ reasoningProfile: ReasoningProfileName
50
+ systemWorkspaceDetails?: string
51
+ preSeededMemoriesSection?: string
52
+ retrievedKnowledgeSection?: string
53
+ },
54
+ ): Promise<{
55
+ agent: {
56
+ stream(
57
+ params: Record<string, unknown>,
58
+ ): Promise<{
59
+ consumeStream(): unknown
60
+ toUIMessageStream(options: Record<string, unknown>): ReadableStream<unknown>
61
+ }>
62
+ }
63
+ observer: ParticipantObserver
64
+ }>
65
+ }
66
+
67
+ export interface CreateConsultTeamToolParams {
68
+ historyMessages: ChatMessage[]
69
+ latestUserMessageId: string
70
+ availableUploads: ReadableUploadMetadataLike[]
71
+ defaultRepoSectionsByAgent: Record<string, DefaultRepoSections | undefined>
72
+ reasoningProfile: 'fast' | 'standard' | 'deep'
73
+ systemWorkspaceDetails?: string
74
+ getPreSeededMemoriesSection: (agentId: string) => Promise<string | undefined>
75
+ retrievedKnowledgeSection?: string
76
+ abortSignal: AbortSignal
77
+ participantRunner: TeamConsultationParticipantRunner
78
+ onReadError?: (agentId: string, error: unknown) => void
79
+ }
80
+
81
+ export function createConsultTeamTool(params: CreateConsultTeamToolParams) {
82
+ return createTool({
83
+ description:
84
+ 'Consult the specialist team in parallel before replying. Use this when the answer benefits from structured executive input across product, engineering, finance, marketing, strategy, and mentorship.',
85
+ inputSchema: ConsultTeamArgsSchema,
86
+ execute: async function* ({ task }) {
87
+ const uploadMetadataText = buildReadableUploadMetadataText(params.availableUploads)
88
+ const responses: ConsultTeamResultData['responses'] = teamConsultParticipants.map((agentId) => ({
89
+ agentId,
90
+ agentName: agentDisplayNames[agentId] ?? agentId,
91
+ status: 'running',
92
+ }))
93
+ const queue: ConsultTeamResultData[] = []
94
+ let wake: (() => void) | undefined
95
+ let completedWorkers = 0
96
+ let fatalError: unknown
97
+
98
+ const pushSnapshot = () => {
99
+ queue.push(buildSnapshot(responses))
100
+ wake?.()
101
+ wake = undefined
102
+ }
103
+
104
+ const workerPromises = teamConsultParticipants.map(async (agentId, index) => {
105
+ const agentName = agentDisplayNames[agentId] ?? agentId
106
+ const timedAbort = createTimedAbortSignal(params.abortSignal, TEAM_CONSULTATION_TIMEOUT_MS)
107
+ let latestMessage: ChatMessage | null = null
108
+
109
+ try {
110
+ const { agent, observer } = await params.participantRunner.buildParticipantAgent(agentId, {
111
+ task,
112
+ reasoningProfile: params.reasoningProfile,
113
+ systemWorkspaceDetails: params.systemWorkspaceDetails,
114
+ preSeededMemoriesSection: await params.getPreSeededMemoriesSection(agentId),
115
+ retrievedKnowledgeSection: params.retrievedKnowledgeSection,
116
+ })
117
+ const modelMessages = await convertToModelMessages(
118
+ buildModelInputMessagesWithUploadMetadata({
119
+ messages: params.historyMessages,
120
+ latestUserMessageId: params.latestUserMessageId,
121
+ uploadMetadataText,
122
+ }),
123
+ { ignoreIncompleteToolCalls: true },
124
+ )
125
+
126
+ let result: Awaited<ReturnType<typeof agent.stream>>
127
+ try {
128
+ result = await observer.run(() => agent.stream({ messages: modelMessages, abortSignal: timedAbort.signal }))
129
+ } catch (error) {
130
+ if (params.abortSignal.aborted || timedAbort.signal.aborted) {
131
+ observer.recordAbort?.(error)
132
+ } else {
133
+ observer.recordError?.(error)
134
+ }
135
+ throw error
136
+ }
137
+
138
+ for await (const message of readUIMessageStream<ChatMessage>({
139
+ stream: result.toUIMessageStream({
140
+ generateMessageId: () => Bun.randomUUIDv7(),
141
+ sendReasoning: true,
142
+ sendSources: true,
143
+ sendStart: false,
144
+ sendFinish: false,
145
+ }) as ReadableStream<never>,
146
+ onError: (error) => {
147
+ params.onReadError?.(agentId, error)
148
+ },
149
+ })) {
150
+ latestMessage = withMessageCreatedAt(message)
151
+ responses[index] = {
152
+ agentId,
153
+ agentName,
154
+ status: 'running',
155
+ summary: extractMessageText(latestMessage).trim() || undefined,
156
+ message: latestMessage,
157
+ }
158
+ pushSnapshot()
159
+ }
160
+
161
+ if (!latestMessage) {
162
+ throw new Error(`Team participant ${agentId} did not produce a response.`)
163
+ }
164
+
165
+ responses[index] = {
166
+ agentId,
167
+ agentName,
168
+ status: 'complete',
169
+ summary: extractMessageText(latestMessage).trim() || undefined,
170
+ message: latestMessage,
171
+ }
172
+ pushSnapshot()
173
+ } catch (error) {
174
+ if (params.abortSignal.aborted) {
175
+ fatalError = error
176
+ wake?.()
177
+ wake = undefined
178
+ return
179
+ }
180
+
181
+ responses[index] = {
182
+ agentId,
183
+ agentName,
184
+ status: 'error',
185
+ summary: latestMessage ? extractMessageText(latestMessage).trim() || undefined : undefined,
186
+ error: buildTeamConsultationFailureMessage(
187
+ agentName,
188
+ TEAM_CONSULTATION_TIMEOUT_MS,
189
+ timedAbort.didTimeout(),
190
+ error,
191
+ ),
192
+ ...(latestMessage ? { message: latestMessage } : {}),
193
+ }
194
+ pushSnapshot()
195
+ } finally {
196
+ timedAbort.dispose()
197
+ completedWorkers += 1
198
+ wake?.()
199
+ wake = undefined
200
+ }
201
+ })
202
+
203
+ pushSnapshot()
204
+
205
+ while (queue.length > 0 || completedWorkers < teamConsultParticipants.length) {
206
+ if (fatalError) {
207
+ await Promise.allSettled(workerPromises)
208
+ throw fatalError
209
+ }
210
+
211
+ if (queue.length === 0) {
212
+ await new Promise<void>((resolve) => {
213
+ wake = resolve
214
+ })
215
+ continue
216
+ }
217
+
218
+ const snapshot = queue.shift()
219
+ if (snapshot) {
220
+ yield snapshot
221
+ }
222
+ }
223
+
224
+ await Promise.all(workerPromises)
225
+ return buildSnapshot(responses)
226
+ },
227
+ toModelOutput: ({ output }) => {
228
+ const result = getConsultTeamOutput(output)
229
+ const summaryLines =
230
+ result?.responses
231
+ .map((response) => {
232
+ const summary =
233
+ typeof response.error === 'string' && response.error.trim().length > 0
234
+ ? response.error.trim()
235
+ : typeof response.summary === 'string' && response.summary.trim().length > 0
236
+ ? response.summary.trim()
237
+ : 'No response.'
238
+ return `${response.agentName}: ${summary}`
239
+ })
240
+ .join('\n') ?? 'Team consultation completed.'
241
+
242
+ return { type: 'text', value: summaryLines }
243
+ },
244
+ })
245
+ }
@@ -0,0 +1,32 @@
1
+ import { agentDisplayNames } from '../config/agent-defaults'
2
+
3
+ export function buildTeamConsultationResponseGuard(params: { agentId: string; task: string }) {
4
+ const agentName = agentDisplayNames[params.agentId] ?? params.agentId
5
+ return [
6
+ '<team-consultation-agent-protocol>',
7
+ '- You are participating in a structured internal team consultation led by Chief of Staff.',
8
+ `- Your role for this response is ${agentName}.`,
9
+ '- Use markdown when it helps clarity.',
10
+ '- Make the recommendation, explain key tradeoffs, and note the next decision.',
11
+ '',
12
+ '<team-consultation-task>',
13
+ params.task.trim(),
14
+ '</team-consultation-task>',
15
+ '</team-consultation-agent-protocol>',
16
+ ].join('\n')
17
+ }
18
+
19
+ export function buildTeamConsultationFailureMessage(
20
+ agentName: string,
21
+ timeoutMs: number,
22
+ didTimeout: boolean,
23
+ error: unknown,
24
+ ): string {
25
+ if (didTimeout) {
26
+ return `${agentName} timed out after ${Math.ceil(timeoutMs / 1000)} seconds.`
27
+ }
28
+ if (error instanceof Error && error.message.trim().length > 0) {
29
+ return `${agentName} failed: ${error.message.trim()}`
30
+ }
31
+ return `${agentName} failed to complete the team consultation response.`
32
+ }
@@ -0,0 +1,12 @@
1
+ const TITLE_WORD_LIMIT = 5
2
+
3
+ export function limitTitleWords(text: string): string {
4
+ const words = text.replace(/\s+/g, ' ').trim().split(' ').filter(Boolean)
5
+ return words.slice(0, TITLE_WORD_LIMIT).join(' ')
6
+ }
7
+
8
+ export function deriveTitle(text: string): string {
9
+ const trimmed = text.replace(/\s+/g, ' ').trim()
10
+ if (trimmed.length <= 60) return trimmed
11
+ return `${trimmed.slice(0, 57)}...`
12
+ }
@@ -0,0 +1,28 @@
1
+ import type { ChatMessage } from '@lota-sdk/shared/schemas/chat-message'
2
+
3
+ export async function finalizeTurnRun(params: {
4
+ serverRunId: string
5
+ getEntity: () => Promise<{ lastCompactedMessageId?: string | null; chatSummary?: string | null }>
6
+ getUncompactedMessages: (cursor?: string) => Promise<ChatMessage[]>
7
+ assessCompaction: (summaryText: string, messages: ChatMessage[]) => { shouldCompact: boolean }
8
+ enqueueCompaction: () => Promise<void>
9
+ unregisterRun: (runId: string) => void
10
+ clearActiveRunId: (runId: string) => Promise<void>
11
+ disposeAbort: () => void
12
+ }): Promise<void> {
13
+ try {
14
+ const entity = await params.getEntity()
15
+ const cursor = typeof entity.lastCompactedMessageId === 'string' ? entity.lastCompactedMessageId : undefined
16
+ const uncompactedMessages = await params.getUncompactedMessages(cursor)
17
+ const summaryText = typeof entity.chatSummary === 'string' ? entity.chatSummary : ''
18
+ const { shouldCompact } = params.assessCompaction(summaryText, uncompactedMessages)
19
+
20
+ if (shouldCompact) {
21
+ await params.enqueueCompaction()
22
+ }
23
+ } finally {
24
+ params.unregisterRun(params.serverRunId)
25
+ await params.clearActiveRunId(params.serverRunId)
26
+ params.disposeAbort()
27
+ }
28
+ }