@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,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,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,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
|
+
}
|