@lota-sdk/core 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -87
- package/src/ai/index.ts +3 -0
- package/src/bifrost/index.ts +1 -0
- package/src/config/agent-defaults.ts +30 -7
- package/src/config/constants.ts +0 -9
- package/src/config/debug-logger.ts +43 -0
- package/src/config/index.ts +5 -0
- package/src/config/model-constants.ts +0 -3
- package/src/config/workstream-defaults.ts +4 -0
- package/src/db/cursor-pagination.ts +2 -2
- package/src/db/index.ts +10 -0
- package/src/db/memory.ts +9 -15
- package/src/document/index.ts +2 -0
- package/src/document/parsing.ts +0 -25
- package/src/embeddings/provider.ts +17 -8
- package/src/index.ts +15 -505
- package/src/queues/index.ts +10 -0
- package/src/redis/connection-accessor.ts +26 -0
- package/src/redis/connection.ts +1 -1
- package/src/redis/index.ts +9 -25
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +1 -1
- package/src/redis/stream-context.ts +12 -2
- package/src/runtime/agent-runtime-policy.ts +9 -5
- package/src/runtime/agent-stream-helpers.ts +6 -3
- package/src/runtime/agent-types.ts +1 -5
- package/src/runtime/approval-continuation.ts +9 -1
- package/src/runtime/chat-attachments.ts +1 -1
- package/src/runtime/chat-request-routing.ts +1 -1
- package/src/runtime/context-compaction-runtime.ts +2 -2
- package/src/runtime/context-compaction.ts +1 -1
- package/src/runtime/execution-plan.ts +1 -1
- package/src/runtime/index.ts +26 -0
- package/src/runtime/indexed-repositories-policy.ts +10 -10
- package/src/runtime/memory-pipeline.ts +0 -2
- package/src/runtime/runtime-config.ts +238 -0
- package/src/runtime/runtime-extensions.ts +3 -2
- package/src/runtime/runtime-worker-registry.ts +47 -0
- package/src/runtime/team-consultation-orchestrator.ts +9 -6
- package/src/runtime/team-consultation-prompts.ts +3 -2
- package/src/runtime/turn-lifecycle.ts +1 -1
- package/src/runtime/workstream-chat-helpers.ts +0 -54
- package/src/runtime/workstream-routing-policy.ts +3 -7
- package/src/runtime.ts +387 -0
- package/src/services/chat-attachments.service.ts +1 -1
- package/src/services/context-compaction.service.ts +1 -1
- package/src/services/execution-plan.service.ts +14 -16
- package/src/services/index.ts +14 -0
- package/src/services/learned-skill.service.ts +80 -37
- package/src/services/memory.service.ts +5 -4
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/organization.service.ts +1 -1
- package/src/services/plan-approval.service.ts +2 -2
- package/src/services/plan-artifact.service.ts +2 -3
- package/src/services/plan-builder.service.ts +1 -1
- package/src/services/plan-checkpoint.service.ts +2 -2
- package/src/services/plan-compiler.service.ts +2 -2
- package/src/services/plan-executor.service.ts +10 -9
- package/src/services/plan-run.service.ts +2 -2
- package/src/services/plan-validator.service.ts +4 -4
- package/src/services/recent-activity-title.service.ts +1 -1
- package/src/services/recent-activity.service.ts +14 -16
- package/src/services/user.service.ts +2 -2
- package/src/services/workstream-message.service.ts +2 -3
- package/src/services/workstream-title.service.ts +1 -1
- package/src/services/workstream-turn-preparation.ts +105 -50
- package/src/services/workstream-turn.ts +14 -1
- package/src/services/workstream.service.ts +9 -9
- package/src/storage/attachment-parser.ts +1 -1
- package/src/storage/attachment-storage.service.ts +11 -10
- package/src/storage/generated-document-storage.service.ts +7 -6
- package/src/storage/index.ts +10 -0
- package/src/system-agents/delegated-agent-factory.ts +78 -29
- package/src/system-agents/index.ts +4 -0
- package/src/system-agents/recent-activity-title-refiner.agent.ts +38 -3
- package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
- package/src/system-agents/skill-extractor.agent.ts +1 -1
- package/src/system-agents/skill-manager.agent.ts +2 -4
- package/src/tools/execution-plan.tool.ts +2 -2
- package/src/tools/firecrawl-client.ts +2 -2
- package/src/tools/index.ts +12 -0
- package/src/tools/research-topic.tool.ts +1 -1
- package/src/tools/team-think.tool.ts +1 -1
- package/src/tools/user-questions.tool.ts +2 -2
- package/src/utils/index.ts +6 -0
- package/src/workers/bootstrap.ts +8 -16
- package/src/workers/index.ts +7 -0
- package/src/workers/regular-chat-memory-digest.runner.ts +1 -1
- package/src/workers/skill-extraction.runner.ts +1 -1
- package/src/workers/utils/{repo-indexer-chunker.ts → file-section-chunker.ts} +23 -52
- package/src/workers/utils/repo-structure-extractor.ts +2 -5
- package/src/workers/utils/repomix-file-sections.ts +42 -0
- package/src/config/env-shapes.ts +0 -121
- package/src/runtime/agent-contract.ts +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getLeadAgentId } from '../config/agent-defaults'
|
|
2
|
+
import { resolveOnboardingOwnerAgentId } from '../config/workstream-defaults'
|
|
1
3
|
import type { ChatMode } from './agent-types'
|
|
2
4
|
import { resolveReasoningProfile } from './workstream-routing-policy'
|
|
3
5
|
import type { ReasoningProfileName } from './workstream-routing-policy'
|
|
@@ -48,6 +50,7 @@ export function resolveActiveAgentSkills<TAgent extends string, TSkill extends P
|
|
|
48
50
|
getAgentSkills: (agentId: TAgent, mode: ChatMode) => TSkill[]
|
|
49
51
|
}): TSkill[] {
|
|
50
52
|
const mode = params.mode ?? toChatMode(params.workstreamMode)
|
|
53
|
+
const onboardingOwnerAgentId = resolveOnboardingOwnerAgentId(getLeadAgentId()) as TAgent
|
|
51
54
|
const baseSkills = params
|
|
52
55
|
.getAgentSkills(params.agentId, mode)
|
|
53
56
|
.filter((skill) => (params.linearInstalled ? true : skill !== ('linear' as TSkill)))
|
|
@@ -56,7 +59,7 @@ export function resolveActiveAgentSkills<TAgent extends string, TSkill extends P
|
|
|
56
59
|
return baseSkills
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
if (params.agentId !==
|
|
62
|
+
if (params.agentId !== onboardingOwnerAgentId) {
|
|
60
63
|
return []
|
|
61
64
|
}
|
|
62
65
|
|
|
@@ -130,6 +133,7 @@ export function buildWorkstreamAgentToolPolicy<TAgent extends string, TSkill ext
|
|
|
130
133
|
getAgentSkills: (agentId: TAgent, mode: ChatMode) => TSkill[]
|
|
131
134
|
}): AgentToolPolicy<TSkill> {
|
|
132
135
|
const resolvedMode = params.mode ?? toChatMode(params.workstreamMode)
|
|
136
|
+
const onboardingOwnerAgentId = resolveOnboardingOwnerAgentId(getLeadAgentId()) as TAgent
|
|
133
137
|
const skills = resolveActiveAgentSkills({
|
|
134
138
|
agentId: params.agentId,
|
|
135
139
|
workstreamMode: params.workstreamMode,
|
|
@@ -148,10 +152,10 @@ export function buildWorkstreamAgentToolPolicy<TAgent extends string, TSkill ext
|
|
|
148
152
|
includeOrgActionSearch: !params.onboardingActive,
|
|
149
153
|
includeMemoryBlockAppend: true,
|
|
150
154
|
includeReadFileParts: true,
|
|
151
|
-
includeInspectWebsite: params.onboardingActive && params.agentId ===
|
|
152
|
-
includeProceedInOnboarding: params.onboardingActive && params.agentId ===
|
|
153
|
-
includeGithubIntegration: params.onboardingActive && params.agentId ===
|
|
154
|
-
includeIndexRepositoryByURL: params.onboardingActive && params.agentId ===
|
|
155
|
+
includeInspectWebsite: params.onboardingActive && params.agentId === onboardingOwnerAgentId,
|
|
156
|
+
includeProceedInOnboarding: params.onboardingActive && params.agentId === onboardingOwnerAgentId,
|
|
157
|
+
includeGithubIntegration: params.onboardingActive && params.agentId === onboardingOwnerAgentId,
|
|
158
|
+
includeIndexRepositoryByURL: params.onboardingActive && params.agentId === onboardingOwnerAgentId,
|
|
155
159
|
includeIndexedRepository: params.githubInstalled && params.provideRepoTool,
|
|
156
160
|
}
|
|
157
161
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { ChatMessage } from '@lota-sdk/shared
|
|
1
|
+
import type { ChatMessage } from '@lota-sdk/shared'
|
|
2
2
|
import type { LanguageModelUsage, UIMessageStreamOptions } from 'ai'
|
|
3
3
|
|
|
4
|
-
import { agentDisplayNames } from '../config/agent-defaults'
|
|
4
|
+
import { agentDisplayNames, getLeadAgentDisplayName } from '../config/agent-defaults'
|
|
5
5
|
|
|
6
6
|
export function readFiniteNumber(value: unknown): number | undefined {
|
|
7
7
|
return typeof value === 'number' && Number.isFinite(value) ? value : undefined
|
|
@@ -108,10 +108,13 @@ export function createTimedAbortSignal(parentSignal: AbortSignal, timeoutMs: num
|
|
|
108
108
|
|
|
109
109
|
export function buildSpecialistTaskMessage(params: { agentId: string; task: string }): ChatMessage {
|
|
110
110
|
const displayName = agentDisplayNames[params.agentId] ?? params.agentId
|
|
111
|
+
const leadAgentDisplayName = getLeadAgentDisplayName()
|
|
111
112
|
return {
|
|
112
113
|
id: Bun.randomUUIDv7(),
|
|
113
114
|
role: 'user',
|
|
114
|
-
parts: [
|
|
115
|
+
parts: [
|
|
116
|
+
{ type: 'text', text: [`${leadAgentDisplayName} request for ${displayName}:`, params.task.trim()].join('\n') },
|
|
117
|
+
],
|
|
115
118
|
metadata: { createdAt: Date.now() },
|
|
116
119
|
}
|
|
117
120
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type { ChatMessage } from '@lota-sdk/shared
|
|
1
|
+
import type { ChatMessage } from '@lota-sdk/shared'
|
|
2
|
+
|
|
3
|
+
import { TABLES } from '../db/tables'
|
|
2
4
|
|
|
3
5
|
export function hasApprovalRespondedParts(message: ChatMessage): boolean {
|
|
4
6
|
return message.parts.some((part) => 'state' in part && (part as { state: string }).state === 'approval-responded')
|
|
@@ -61,12 +63,18 @@ const PLAN_TOOL_NAMES = new Set([
|
|
|
61
63
|
'resumeExecutionPlanRun',
|
|
62
64
|
])
|
|
63
65
|
|
|
66
|
+
function isPlanApprovalId(value: unknown): value is string {
|
|
67
|
+
return typeof value === 'string' && value.startsWith(`${TABLES.PLAN_APPROVAL}:`)
|
|
68
|
+
}
|
|
69
|
+
|
|
64
70
|
export function isNativeToolApprovalRequest(messages: ChatMessage[]): boolean {
|
|
65
71
|
const lastAssistant = [...messages].reverse().find((m) => m.role === 'assistant')
|
|
66
72
|
if (!lastAssistant) return false
|
|
67
73
|
|
|
68
74
|
return lastAssistant.parts.some((part) => {
|
|
69
75
|
if (!('state' in part) || (part as { state: string }).state !== 'approval-responded') return false
|
|
76
|
+
const approvalId = (part as { approval?: { id?: unknown } }).approval?.id
|
|
77
|
+
if (isPlanApprovalId(approvalId)) return false
|
|
70
78
|
const type = (part as { type?: string }).type
|
|
71
79
|
if (typeof type !== 'string' || !type.startsWith('tool-')) return false
|
|
72
80
|
const toolName = type.slice(5)
|
|
@@ -18,12 +18,12 @@ import {
|
|
|
18
18
|
import type { GenerateHelperStructuredParams, GenerateHelperTextParams } from './helper-model'
|
|
19
19
|
import { StructuredCompactionOutputSchema } from './workstream-state'
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
interface HelperModelRuntime {
|
|
22
22
|
generateHelperStructured<T>(params: GenerateHelperStructuredParams<T>): Promise<T>
|
|
23
23
|
generateHelperText(params: GenerateHelperTextParams): Promise<string>
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
interface CreateContextCompactionRuntimeDeps {
|
|
27
27
|
helperModelRuntime: HelperModelRuntime
|
|
28
28
|
now?: () => number
|
|
29
29
|
randomId?: () => string
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SerializableExecutionPlan } from '@lota-sdk/shared
|
|
1
|
+
import type { SerializableExecutionPlan } from '@lota-sdk/shared'
|
|
2
2
|
|
|
3
3
|
export const EXECUTION_PLAN_AGENT_PROTOCOL_PROMPT = `<execution-plan-protocol>
|
|
4
4
|
- Before doing multi-step work, create a contract-driven execution plan instead of tracking steps only in prose.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export * from './approval-continuation'
|
|
2
|
+
export * from './agent-runtime-policy'
|
|
3
|
+
export * from './agent-stream-helpers'
|
|
4
|
+
export * from './agent-types'
|
|
5
|
+
export * from './chat-request-routing'
|
|
6
|
+
export * from './chat-run-registry'
|
|
7
|
+
export * from './context-compaction'
|
|
8
|
+
export * from './execution-plan'
|
|
9
|
+
export * from './helper-model'
|
|
10
|
+
export * from './indexed-repositories-policy'
|
|
11
|
+
export * from './instruction-sections'
|
|
12
|
+
export * from './memory-block'
|
|
13
|
+
export * from './memory-digest-policy'
|
|
14
|
+
export * from './memory-scope'
|
|
15
|
+
export * from './llm-content'
|
|
16
|
+
export * from './plugin-types'
|
|
17
|
+
export * from './runtime-config'
|
|
18
|
+
export * from './runtime-extensions'
|
|
19
|
+
export * from './runtime-worker-registry'
|
|
20
|
+
export * from './skill-extraction-policy'
|
|
21
|
+
export * from './team-consultation-orchestrator'
|
|
22
|
+
export * from './team-consultation-prompts'
|
|
23
|
+
export * from './turn-lifecycle'
|
|
24
|
+
export * from './workstream-chat-helpers'
|
|
25
|
+
export * from './workstream-routing-policy'
|
|
26
|
+
export * from './workstream-state'
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { agentRoster } from '../config/agent-defaults'
|
|
2
|
+
|
|
1
3
|
export type IndexedRepoAgentName = string
|
|
2
4
|
|
|
3
5
|
export const REPO_SECTION_NAMES = [
|
|
@@ -13,16 +15,14 @@ export type RepoSectionName = (typeof REPO_SECTION_NAMES)[number]
|
|
|
13
15
|
|
|
14
16
|
const ALL_REPO_SECTIONS: RepoSectionName[] = [...REPO_SECTION_NAMES]
|
|
15
17
|
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
cpo: [...ALL_REPO_SECTIONS],
|
|
21
|
-
cmo: [...ALL_REPO_SECTIONS],
|
|
22
|
-
cfo: [...ALL_REPO_SECTIONS],
|
|
23
|
-
mentor: [...ALL_REPO_SECTIONS],
|
|
18
|
+
export function buildDefaultRepoSectionsByAgent(
|
|
19
|
+
roster: readonly IndexedRepoAgentName[] = agentRoster,
|
|
20
|
+
): Record<IndexedRepoAgentName, RepoSectionName[]> {
|
|
21
|
+
return Object.fromEntries(roster.map((agentId) => [agentId, [...ALL_REPO_SECTIONS]]))
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
export function emptyAgentContextMap(
|
|
27
|
-
|
|
24
|
+
export function emptyAgentContextMap(
|
|
25
|
+
roster: readonly IndexedRepoAgentName[] = agentRoster,
|
|
26
|
+
): Record<IndexedRepoAgentName, string> {
|
|
27
|
+
return Object.fromEntries(roster.map((agentId) => [agentId, '']))
|
|
28
28
|
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import type { CoreWorkstreamProfile } from '../config/agent-defaults'
|
|
4
|
+
import type { LotaWorkstreamConfig, WorkstreamBootstrapWelcomeConfig } from '../config/workstream-defaults'
|
|
5
|
+
import type { LotaPlugin } from './plugin-types'
|
|
6
|
+
import type { LotaRuntimeAdapters, LotaRuntimeTurnHooks } from './runtime-extensions'
|
|
7
|
+
import type { LotaRuntimeWorkerExtensions } from './runtime-worker-registry'
|
|
8
|
+
|
|
9
|
+
const logLevelValues = ['trace', 'debug', 'info', 'warning', 'error', 'fatal'] as const
|
|
10
|
+
|
|
11
|
+
type LotaAgentFactoryRegistry = Record<string, (...args: unknown[]) => unknown>
|
|
12
|
+
|
|
13
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null
|
|
14
|
+
|
|
15
|
+
function isStringOrUrl(value: unknown): value is string | URL {
|
|
16
|
+
return typeof value === 'string' || value instanceof URL
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isFunction(value: unknown): value is (...args: unknown[]) => unknown {
|
|
20
|
+
return typeof value === 'function'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isStringRecord(value: unknown): value is Record<string, string> {
|
|
24
|
+
return isRecord(value) && Object.values(value).every((entry) => typeof entry === 'string')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isAgentFactoryRegistry(value: unknown): value is LotaAgentFactoryRegistry {
|
|
28
|
+
return isRecord(value) && Object.values(value).every((entry) => typeof entry === 'function')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isPluginRuntimeRecord(value: unknown): value is Record<string, LotaPlugin> {
|
|
32
|
+
return isRecord(value)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isToolProviderRecord(value: unknown): value is Record<string, unknown> {
|
|
36
|
+
return isRecord(value)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isWorkerExtensionRecord(value: unknown): value is LotaRuntimeWorkerExtensions {
|
|
40
|
+
if (!isRecord(value)) return false
|
|
41
|
+
|
|
42
|
+
for (const key of ['start', 'schedule'] as const) {
|
|
43
|
+
const entry = value[key]
|
|
44
|
+
if (entry !== undefined && !isRecord(entry)) {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const workstreamBootstrapWelcomeConfigSchema = z.object({
|
|
53
|
+
directAgentId: z.string().trim().min(1),
|
|
54
|
+
buildMessageText: z.custom<WorkstreamBootstrapWelcomeConfig['buildMessageText']>(isFunction, {
|
|
55
|
+
error: 'onboardingWelcome.buildMessageText must be a function',
|
|
56
|
+
}),
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const workstreamConfigSchema = z.object({
|
|
60
|
+
bootstrap: z
|
|
61
|
+
.object({
|
|
62
|
+
onboardingDirectAgents: z.array(z.string().trim().min(1)).optional(),
|
|
63
|
+
completedDirectAgents: z.array(z.string().trim().min(1)).optional(),
|
|
64
|
+
coreTypesAfterOnboarding: z.array(z.string().trim().min(1)).optional(),
|
|
65
|
+
ensureDefaultGroupOnCompleted: z.boolean().optional(),
|
|
66
|
+
onboardingWelcome: workstreamBootstrapWelcomeConfigSchema.optional(),
|
|
67
|
+
})
|
|
68
|
+
.optional(),
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const agentsConfigSchema = z
|
|
72
|
+
.object({
|
|
73
|
+
roster: z.array(z.string().trim().min(1)).min(1),
|
|
74
|
+
leadAgentId: z.string().trim().min(1),
|
|
75
|
+
displayNames: z.custom<Record<string, string>>(isStringRecord, {
|
|
76
|
+
error: 'agents.displayNames must be a string record',
|
|
77
|
+
}),
|
|
78
|
+
shortDisplayNames: z
|
|
79
|
+
.custom<Record<string, string>>(isStringRecord, { error: 'agents.shortDisplayNames must be a string record' })
|
|
80
|
+
.optional(),
|
|
81
|
+
teamConsultParticipants: z.array(z.string().trim().min(1)),
|
|
82
|
+
getCoreWorkstreamProfile: z
|
|
83
|
+
.custom<(coreType: string) => CoreWorkstreamProfile>(isFunction, {
|
|
84
|
+
error: 'agents.getCoreWorkstreamProfile must be a function',
|
|
85
|
+
})
|
|
86
|
+
.optional(),
|
|
87
|
+
createAgent: z
|
|
88
|
+
.custom<LotaAgentFactoryRegistry>(isAgentFactoryRegistry, {
|
|
89
|
+
error: 'agents.createAgent must be a function registry',
|
|
90
|
+
})
|
|
91
|
+
.optional(),
|
|
92
|
+
buildAgentTools: z
|
|
93
|
+
.custom<(...args: unknown[]) => unknown>(isFunction, { error: 'agents.buildAgentTools must be a function' })
|
|
94
|
+
.optional(),
|
|
95
|
+
getAgentRuntimeConfig: z
|
|
96
|
+
.custom<(...args: unknown[]) => unknown>(isFunction, { error: 'agents.getAgentRuntimeConfig must be a function' })
|
|
97
|
+
.optional(),
|
|
98
|
+
})
|
|
99
|
+
.superRefine((value, ctx) => {
|
|
100
|
+
if (!value.roster.includes(value.leadAgentId)) {
|
|
101
|
+
ctx.addIssue({
|
|
102
|
+
code: 'custom',
|
|
103
|
+
path: ['leadAgentId'],
|
|
104
|
+
message: 'agents.leadAgentId must be present in agents.roster',
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (const agentId of value.roster) {
|
|
109
|
+
if (!value.displayNames[agentId]) {
|
|
110
|
+
ctx.addIssue({
|
|
111
|
+
code: 'custom',
|
|
112
|
+
path: ['displayNames', agentId],
|
|
113
|
+
message: `Missing display name for agent "${agentId}"`,
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
export const LotaRuntimeConfigSchema = z.object({
|
|
120
|
+
database: z.object({
|
|
121
|
+
url: z.string().trim().min(1),
|
|
122
|
+
namespace: z.string().trim().min(1),
|
|
123
|
+
username: z.string().trim().min(1),
|
|
124
|
+
password: z.string().trim().min(1),
|
|
125
|
+
}),
|
|
126
|
+
redis: z.object({ url: z.string().trim().min(1) }),
|
|
127
|
+
aiGateway: z.object({
|
|
128
|
+
url: z.string().trim().min(1),
|
|
129
|
+
key: z.string().trim().min(1),
|
|
130
|
+
admin: z.string().trim().min(1).optional(),
|
|
131
|
+
pass: z.string().trim().min(1).optional(),
|
|
132
|
+
embeddingModel: z.string().trim().min(1).default('openai/text-embedding-3-small'),
|
|
133
|
+
}),
|
|
134
|
+
s3: z.object({
|
|
135
|
+
endpoint: z.string().trim().min(1),
|
|
136
|
+
bucket: z.string().trim().min(1),
|
|
137
|
+
region: z.string().trim().min(1).default('garage'),
|
|
138
|
+
accessKeyId: z.string().trim().min(1),
|
|
139
|
+
secretAccessKey: z.string().trim().min(1),
|
|
140
|
+
attachmentUrlExpiresIn: z.coerce.number().positive().default(1800),
|
|
141
|
+
}),
|
|
142
|
+
firecrawl: z.object({
|
|
143
|
+
apiKey: z
|
|
144
|
+
.string()
|
|
145
|
+
.min(1, 'Firecrawl API key is required')
|
|
146
|
+
.refine((value) => value.startsWith('fc-'), 'Firecrawl API key must start with fc-')
|
|
147
|
+
.refine((value) => value !== 'dev-fire-key', 'Firecrawl API key placeholder is not allowed'),
|
|
148
|
+
apiBaseUrl: z.string().trim().url().optional(),
|
|
149
|
+
}),
|
|
150
|
+
logging: z.object({ level: z.enum(logLevelValues).default('info') }).default({ level: 'info' }),
|
|
151
|
+
memory: z
|
|
152
|
+
.object({
|
|
153
|
+
searchK: z.coerce.number().int().positive().default(6),
|
|
154
|
+
embeddingCacheTtlSeconds: z.coerce.number().int().positive().default(3600),
|
|
155
|
+
})
|
|
156
|
+
.default({ searchK: 6, embeddingCacheTtlSeconds: 3600 }),
|
|
157
|
+
workstreams: workstreamConfigSchema.default({}),
|
|
158
|
+
backgroundProcessing: z
|
|
159
|
+
.object({
|
|
160
|
+
memoryExtractionFrequency: z.coerce.number().int().positive().default(3),
|
|
161
|
+
skillExtractionFrequency: z.coerce.number().int().positive().default(5),
|
|
162
|
+
memoryDigestFrequency: z.coerce.number().int().positive().default(1),
|
|
163
|
+
memoryConsolidationFrequency: z.coerce.number().int().positive().default(10),
|
|
164
|
+
})
|
|
165
|
+
.default({
|
|
166
|
+
memoryExtractionFrequency: 3,
|
|
167
|
+
skillExtractionFrequency: 5,
|
|
168
|
+
memoryDigestFrequency: 1,
|
|
169
|
+
memoryConsolidationFrequency: 10,
|
|
170
|
+
}),
|
|
171
|
+
agents: agentsConfigSchema,
|
|
172
|
+
toolProviders: z.custom<Record<string, unknown>>(isToolProviderRecord).optional(),
|
|
173
|
+
extraSchemaFiles: z.array(z.custom<string | URL>(isStringOrUrl)).optional(),
|
|
174
|
+
extraWorkers: z.custom<LotaRuntimeWorkerExtensions>(isWorkerExtensionRecord).optional(),
|
|
175
|
+
pluginRuntime: z.custom<Record<string, LotaPlugin>>(isPluginRuntimeRecord).optional(),
|
|
176
|
+
runtimeAdapters: z.custom<LotaRuntimeAdapters>(isRecord).optional(),
|
|
177
|
+
turnHooks: z.custom<LotaRuntimeTurnHooks>(isRecord).optional(),
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
export type LotaRuntimeConfig = z.input<typeof LotaRuntimeConfigSchema>
|
|
181
|
+
export type ResolvedLotaRuntimeConfig = z.infer<typeof LotaRuntimeConfigSchema>
|
|
182
|
+
|
|
183
|
+
export const WORKER_BOOTSTRAP_ENV_SCHEMA = z.object({
|
|
184
|
+
SURREALDB_URL: z.string().trim().min(1),
|
|
185
|
+
SURREALDB_NAMESPACE: z.string().trim().min(1),
|
|
186
|
+
SURREALDB_USER: z.string().trim().min(1),
|
|
187
|
+
SURREALDB_PASSWORD: z.string().trim().min(1),
|
|
188
|
+
DB_SCHEMA_FINGERPRINT: z.string().trim().min(1).optional(),
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
export type WorkerBootstrapEnv = z.infer<typeof WORKER_BOOTSTRAP_ENV_SCHEMA>
|
|
192
|
+
|
|
193
|
+
export const LOTA_RUNTIME_ENV_KEYS = Object.freeze([
|
|
194
|
+
'SURREALDB_URL',
|
|
195
|
+
'SURREALDB_NAMESPACE',
|
|
196
|
+
'SURREALDB_USER',
|
|
197
|
+
'SURREALDB_PASSWORD',
|
|
198
|
+
'REDIS_URL',
|
|
199
|
+
'AI_GATEWAY_URL',
|
|
200
|
+
'AI_GATEWAY_KEY',
|
|
201
|
+
'AI_GATEWAY_ADMIN',
|
|
202
|
+
'AI_GATEWAY_PASS',
|
|
203
|
+
'AI_EMBEDDING_MODEL',
|
|
204
|
+
'S3_ENDPOINT',
|
|
205
|
+
'S3_BUCKET',
|
|
206
|
+
'S3_REGION',
|
|
207
|
+
'S3_ACCESS_KEY_ID',
|
|
208
|
+
'S3_SECRET_ACCESS_KEY',
|
|
209
|
+
'ATTACHMENT_URL_EXPIRES_IN',
|
|
210
|
+
'FIRECRAWL_API_KEY',
|
|
211
|
+
'FIRECRAWL_API_BASE_URL',
|
|
212
|
+
'LOG_LEVEL',
|
|
213
|
+
'MEMORY_SEARCH_K',
|
|
214
|
+
])
|
|
215
|
+
|
|
216
|
+
let runtimeConfig: ResolvedLotaRuntimeConfig | null = null
|
|
217
|
+
|
|
218
|
+
export function parseLotaRuntimeConfig(config: LotaRuntimeConfig): ResolvedLotaRuntimeConfig {
|
|
219
|
+
return LotaRuntimeConfigSchema.parse(config)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function configureRuntimeConfig(config: ResolvedLotaRuntimeConfig): void {
|
|
223
|
+
runtimeConfig = config
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function getRuntimeConfig(): ResolvedLotaRuntimeConfig {
|
|
227
|
+
if (!runtimeConfig) {
|
|
228
|
+
throw new Error('Lota runtime config not configured. Call createLotaRuntime() first.')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return runtimeConfig
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function parseWorkerBootstrapEnv(env: Record<string, string | undefined>): WorkerBootstrapEnv {
|
|
235
|
+
return WORKER_BOOTSTRAP_ENV_SCHEMA.parse(env)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export type { LotaAgentFactoryRegistry, LotaWorkstreamConfig }
|
|
@@ -2,6 +2,7 @@ import type { ToolSet } from 'ai'
|
|
|
2
2
|
|
|
3
3
|
import type { RecordIdRef } from '../db/record-id'
|
|
4
4
|
import type { ReadableUploadMetadata } from '../services/attachment.service'
|
|
5
|
+
import type { LotaRuntimeWorkerExtensions } from './runtime-worker-registry'
|
|
5
6
|
|
|
6
7
|
export interface LotaRuntimeBackgroundCursor {
|
|
7
8
|
createdAt: Date
|
|
@@ -102,7 +103,7 @@ interface RuntimeExtensionsState {
|
|
|
102
103
|
adapters: LotaRuntimeAdapters
|
|
103
104
|
turnHooks: LotaRuntimeTurnHooks
|
|
104
105
|
toolProviders: ToolSet
|
|
105
|
-
extraWorkers:
|
|
106
|
+
extraWorkers: LotaRuntimeWorkerExtensions
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
const EMPTY_TOOLS = {} as ToolSet
|
|
@@ -118,7 +119,7 @@ export function configureRuntimeExtensions(params?: {
|
|
|
118
119
|
adapters?: LotaRuntimeAdapters
|
|
119
120
|
turnHooks?: LotaRuntimeTurnHooks
|
|
120
121
|
toolProviders?: ToolSet
|
|
121
|
-
extraWorkers?:
|
|
122
|
+
extraWorkers?: LotaRuntimeWorkerExtensions
|
|
122
123
|
}): void {
|
|
123
124
|
runtimeExtensionsState = {
|
|
124
125
|
adapters: params?.adapters ?? {},
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { startContextCompactionWorker } from '../queues/context-compaction.queue'
|
|
2
|
+
import { scheduleRecurringConsolidation, startMemoryConsolidationWorker } from '../queues/memory-consolidation.queue'
|
|
3
|
+
import { startPostChatMemoryWorker } from '../queues/post-chat-memory.queue'
|
|
4
|
+
import { startRecentActivityTitleRefinementWorker } from '../queues/recent-activity-title-refinement.queue'
|
|
5
|
+
import { startRegularChatMemoryDigestWorker } from '../queues/regular-chat-memory-digest.queue'
|
|
6
|
+
import { startSkillExtractionWorker } from '../queues/skill-extraction.queue'
|
|
7
|
+
import { startWorkstreamTitleGenerationWorker } from '../queues/workstream-title-generation.queue'
|
|
8
|
+
|
|
9
|
+
export interface LotaRuntimeWorkerStartRegistry {
|
|
10
|
+
contextCompaction: typeof startContextCompactionWorker
|
|
11
|
+
memoryConsolidation: typeof startMemoryConsolidationWorker
|
|
12
|
+
postChatMemory: typeof startPostChatMemoryWorker
|
|
13
|
+
regularChatMemoryDigest: typeof startRegularChatMemoryDigestWorker
|
|
14
|
+
skillExtraction: typeof startSkillExtractionWorker
|
|
15
|
+
workstreamTitleGeneration: typeof startWorkstreamTitleGenerationWorker
|
|
16
|
+
recentActivityTitleRefinement: typeof startRecentActivityTitleRefinementWorker
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LotaRuntimeWorkerScheduleRegistry {
|
|
20
|
+
recurringConsolidation: typeof scheduleRecurringConsolidation
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LotaRuntimeWorkers {
|
|
24
|
+
start: LotaRuntimeWorkerStartRegistry & Record<string, unknown>
|
|
25
|
+
schedule: LotaRuntimeWorkerScheduleRegistry & Record<string, unknown>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface LotaRuntimeWorkerExtensions {
|
|
29
|
+
start?: Record<string, unknown>
|
|
30
|
+
schedule?: Record<string, unknown>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function buildRuntimeWorkerRegistry(extraWorkers?: LotaRuntimeWorkerExtensions): LotaRuntimeWorkers {
|
|
34
|
+
return {
|
|
35
|
+
start: {
|
|
36
|
+
contextCompaction: startContextCompactionWorker,
|
|
37
|
+
memoryConsolidation: startMemoryConsolidationWorker,
|
|
38
|
+
postChatMemory: startPostChatMemoryWorker,
|
|
39
|
+
regularChatMemoryDigest: startRegularChatMemoryDigestWorker,
|
|
40
|
+
skillExtraction: startSkillExtractionWorker,
|
|
41
|
+
workstreamTitleGeneration: startWorkstreamTitleGenerationWorker,
|
|
42
|
+
recentActivityTitleRefinement: startRecentActivityTitleRefinementWorker,
|
|
43
|
+
...extraWorkers?.start,
|
|
44
|
+
},
|
|
45
|
+
schedule: { recurringConsolidation: scheduleRecurringConsolidation, ...extraWorkers?.schedule },
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { withMessageCreatedAt } from '@lota-sdk/shared
|
|
2
|
-
import type { ChatMessage } from '@lota-sdk/shared
|
|
3
|
-
import { ConsultTeamArgsSchema } from '@lota-sdk/shared/schemas/tools'
|
|
4
|
-
import type { ConsultTeamResultData } from '@lota-sdk/shared/schemas/tools'
|
|
1
|
+
import { ConsultTeamArgsSchema, withMessageCreatedAt } from '@lota-sdk/shared'
|
|
2
|
+
import type { ChatMessage, ConsultTeamResultData } from '@lota-sdk/shared'
|
|
5
3
|
import { convertToModelMessages, readUIMessageStream, tool as createTool } from 'ai'
|
|
6
4
|
|
|
7
5
|
import { agentDisplayNames, teamConsultParticipants } from '../config/agent-defaults'
|
|
@@ -79,9 +77,14 @@ export interface CreateConsultTeamToolParams {
|
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
export function createConsultTeamTool(params: CreateConsultTeamToolParams) {
|
|
80
|
+
const participantNames = teamConsultParticipants
|
|
81
|
+
.map((agentId) => agentDisplayNames[agentId] ?? agentId)
|
|
82
|
+
.filter((value) => value.trim().length > 0)
|
|
83
|
+
const participantSummary =
|
|
84
|
+
participantNames.length > 0 ? participantNames.join(', ') : 'the configured specialist participants'
|
|
85
|
+
|
|
82
86
|
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.',
|
|
87
|
+
description: `Consult the specialist team in parallel before replying. Use this when the answer benefits from structured input across ${participantSummary}.`,
|
|
85
88
|
inputSchema: ConsultTeamArgsSchema,
|
|
86
89
|
execute: async function* ({ task }) {
|
|
87
90
|
const uploadMetadataText = buildReadableUploadMetadataText(params.availableUploads)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { agentDisplayNames } from '../config/agent-defaults'
|
|
1
|
+
import { agentDisplayNames, getLeadAgentDisplayName } from '../config/agent-defaults'
|
|
2
2
|
|
|
3
3
|
export function buildTeamConsultationResponseGuard(params: { agentId: string; task: string }) {
|
|
4
4
|
const agentName = agentDisplayNames[params.agentId] ?? params.agentId
|
|
5
|
+
const leadAgentDisplayName = getLeadAgentDisplayName()
|
|
5
6
|
return [
|
|
6
7
|
'<team-consultation-agent-protocol>',
|
|
7
|
-
|
|
8
|
+
`- You are participating in a structured internal team consultation led by ${leadAgentDisplayName}.`,
|
|
8
9
|
`- Your role for this response is ${agentName}.`,
|
|
9
10
|
'- Use markdown when it helps clarity.',
|
|
10
11
|
'- Make the recommendation, explain key tradeoffs, and note the next decision.',
|
|
@@ -16,49 +16,6 @@ function getAgentName(message: ChatMessageLike): string | undefined {
|
|
|
16
16
|
return resolvedAgentName ? (agentDisplayNames[resolvedAgentName] ?? value.trim()) : value.trim()
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function truncateTrackerText(value: string, maxChars: number): string {
|
|
20
|
-
return value.length <= maxChars ? value : `${value.slice(0, maxChars).trimEnd()}...`
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function toTrackerToolLine(part: ChatMessageLike['parts'][number]): string | null {
|
|
24
|
-
if (typeof part !== 'object') return null
|
|
25
|
-
const record = part as Record<string, unknown>
|
|
26
|
-
const type = typeof record.type === 'string' ? record.type : null
|
|
27
|
-
if (!type?.startsWith('tool-')) return null
|
|
28
|
-
|
|
29
|
-
const toolName = type.slice('tool-'.length) || 'unknown'
|
|
30
|
-
const state = typeof record.state === 'string' ? record.state : null
|
|
31
|
-
const input = record.input
|
|
32
|
-
const output = record.output
|
|
33
|
-
|
|
34
|
-
const inputTask =
|
|
35
|
-
input && typeof input === 'object' && typeof (input as Record<string, unknown>).task === 'string'
|
|
36
|
-
? truncateTrackerText(((input as Record<string, unknown>).task as string).trim(), 220)
|
|
37
|
-
: null
|
|
38
|
-
const outputSummary =
|
|
39
|
-
output && typeof output === 'object' && typeof (output as Record<string, unknown>).summary === 'string'
|
|
40
|
-
? truncateTrackerText(((output as Record<string, unknown>).summary as string).trim(), 220)
|
|
41
|
-
: null
|
|
42
|
-
const outputResult =
|
|
43
|
-
output && typeof output === 'object' && typeof (output as Record<string, unknown>).result === 'string'
|
|
44
|
-
? truncateTrackerText(((output as Record<string, unknown>).result as string).trim(), 220)
|
|
45
|
-
: null
|
|
46
|
-
const errorText = typeof record.errorText === 'string' ? truncateTrackerText(record.errorText.trim(), 220) : null
|
|
47
|
-
|
|
48
|
-
if (state === 'output-error') {
|
|
49
|
-
return errorText ? `Tool ${toolName} failed: ${errorText}` : `Tool ${toolName} failed.`
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (state !== 'output-available') {
|
|
53
|
-
return inputTask ? `Tool ${toolName} ran for: ${inputTask}` : `Tool ${toolName} ran.`
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (outputSummary) return `Tool ${toolName} completed: ${outputSummary}`
|
|
57
|
-
if (inputTask) return `Tool ${toolName} completed for: ${inputTask}`
|
|
58
|
-
if (outputResult) return `Tool ${toolName} completed: ${outputResult}`
|
|
59
|
-
return `Tool ${toolName} completed.`
|
|
60
|
-
}
|
|
61
|
-
|
|
62
19
|
export function extractMessageText(message: ChatMessageLike): string {
|
|
63
20
|
return message.parts
|
|
64
21
|
.flatMap((part) => (part.type === 'text' && typeof part.text === 'string' ? [part.text] : []))
|
|
@@ -66,17 +23,6 @@ export function extractMessageText(message: ChatMessageLike): string {
|
|
|
66
23
|
.trim()
|
|
67
24
|
}
|
|
68
25
|
|
|
69
|
-
export function extractTrackerMessageText(message: ChatMessageLike): string {
|
|
70
|
-
const textParts = message.parts.flatMap((part) =>
|
|
71
|
-
part.type === 'text' && typeof part.text === 'string' ? [part.text] : [],
|
|
72
|
-
)
|
|
73
|
-
const toolParts = message.parts
|
|
74
|
-
.map((part) => toTrackerToolLine(part))
|
|
75
|
-
.filter((value): value is string => Boolean(value))
|
|
76
|
-
|
|
77
|
-
return [...textParts, ...toolParts].join('\n').trim()
|
|
78
|
-
}
|
|
79
|
-
|
|
80
26
|
export function toHistoryMessages(messages: ChatMessageLike[], maxItems = 24): WorkstreamHistoryMessage[] {
|
|
81
27
|
return messages
|
|
82
28
|
.map((message): WorkstreamHistoryMessage | null => {
|