@lota-sdk/core 0.2.1 → 0.2.2
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 +7 -7
- package/src/ai/definitions.ts +4 -3
- package/src/runtime/agent-identity-overrides.ts +62 -0
- package/src/runtime/post-turn-side-effects.ts +17 -3
- package/src/runtime/team-consultation-orchestrator.ts +14 -3
- package/src/services/agent-executor.service.ts +38 -13
- package/src/services/workstream-turn-preparation.service.ts +21 -15
- package/src/system-agents/workstream-router.agent.ts +104 -25
- package/src/tools/execution-plan.tool.ts +20 -6
- package/src/tools/research-topic.tool.ts +2 -2
- package/src/tools/team-think.tool.ts +33 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lota-sdk/core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -28,18 +28,18 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@ai-sdk/devtools": "^0.0.15",
|
|
31
|
-
"@ai-sdk/openai": "^3.0.
|
|
31
|
+
"@ai-sdk/openai": "^3.0.50",
|
|
32
32
|
"@chat-adapter/slack": "^4.23.0",
|
|
33
33
|
"@chat-adapter/state-ioredis": "^4.23.0",
|
|
34
34
|
"@logtape/logtape": "^2.0.5",
|
|
35
|
-
"@lota-sdk/shared": "0.2.
|
|
36
|
-
"@mendable/firecrawl-js": "^4.18.
|
|
35
|
+
"@lota-sdk/shared": "0.2.2",
|
|
36
|
+
"@mendable/firecrawl-js": "^4.18.1",
|
|
37
37
|
"@surrealdb/node": "^3.0.3",
|
|
38
|
-
"ai": "^6.0.
|
|
39
|
-
"bullmq": "^5.
|
|
38
|
+
"ai": "^6.0.145",
|
|
39
|
+
"bullmq": "^5.73.0",
|
|
40
40
|
"chat": "^4.23.0",
|
|
41
41
|
"cron-parser": "^5.5.0",
|
|
42
|
-
"hono": "^4.12.
|
|
42
|
+
"hono": "^4.12.10",
|
|
43
43
|
"ioredis": "5.9.3",
|
|
44
44
|
"mammoth": "^1.12.0",
|
|
45
45
|
"pdf-parse": "^2.4.5",
|
package/src/ai/definitions.ts
CHANGED
|
@@ -91,7 +91,7 @@ Do not call additional tools in the same response.
|
|
|
91
91
|
Do not ask when reasonable defaults exist or information is retrievable.`,
|
|
92
92
|
})
|
|
93
93
|
|
|
94
|
-
const researchSkillTools = ['researchTopic', 'fetchWebpage', 'inspectWebsite'] as const
|
|
94
|
+
const researchSkillTools = ['searchWeb', 'researchTopic', 'fetchWebpage', 'inspectWebsite'] as const
|
|
95
95
|
|
|
96
96
|
export const researchSkill = defineSkill({
|
|
97
97
|
name: 'research',
|
|
@@ -103,8 +103,9 @@ export const researchSkill = defineSkill({
|
|
|
103
103
|
Use for external information: market research, competitive analysis, fact verification.
|
|
104
104
|
|
|
105
105
|
## Tools
|
|
106
|
-
- \`
|
|
107
|
-
- \`
|
|
106
|
+
- \`searchWeb\` — fastest path for narrow current lookups, finding candidate sources, and checking recency-sensitive facts. Prefer this first when a quick answer is enough.
|
|
107
|
+
- \`researchTopic\` — delegate to the research agent only when the task needs deeper multi-source synthesis or a structured research brief.
|
|
108
|
+
- \`fetchWebpage\` — use when the user shares a specific URL or \`searchWeb\` surfaces a page worth reading in full.
|
|
108
109
|
- \`inspectWebsite\` — structured analysis of a website. Pass URL in \`url\` field. Use \`forceRefresh: true\` to overwrite.
|
|
109
110
|
|
|
110
111
|
## Output
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { agentDisplayNames } from '../config/agent-defaults'
|
|
2
|
+
import { asRecord, readOptionalString } from './workstream-chat-helpers'
|
|
3
|
+
|
|
4
|
+
interface RuntimeAgentIdentityOverrides {
|
|
5
|
+
displayNamesById: Partial<Record<string, string>>
|
|
6
|
+
shortDisplayNamesById: Partial<Record<string, string>>
|
|
7
|
+
primaryLabelsById: Partial<Record<string, string>>
|
|
8
|
+
secondaryLabelsById: Partial<Record<string, string>>
|
|
9
|
+
templateRoleIdsByAgentId: Partial<Record<string, string>>
|
|
10
|
+
routingAliasesByAgentId: Partial<Record<string, string[]>>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function readStringRecord(value: unknown): Partial<Record<string, string>> {
|
|
14
|
+
const record = asRecord(value)
|
|
15
|
+
if (!record) return {}
|
|
16
|
+
|
|
17
|
+
return Object.fromEntries(
|
|
18
|
+
Object.entries(record)
|
|
19
|
+
.map(([key, entry]) => [key, readOptionalString(entry)] as const)
|
|
20
|
+
.filter((entry): entry is [string, string] => typeof entry[1] === 'string'),
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readStringArrayRecord(value: unknown): Partial<Record<string, string[]>> {
|
|
25
|
+
const record = asRecord(value)
|
|
26
|
+
if (!record) return {}
|
|
27
|
+
|
|
28
|
+
return Object.fromEntries(
|
|
29
|
+
Object.entries(record).map(([key, entry]) => [
|
|
30
|
+
key,
|
|
31
|
+
Array.isArray(entry)
|
|
32
|
+
? entry.map((item) => readOptionalString(item)).filter((item): item is string => typeof item === 'string')
|
|
33
|
+
: [],
|
|
34
|
+
]),
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readStringOverride(record: Partial<Record<string, string>>, key: string): string | undefined {
|
|
39
|
+
return Object.hasOwn(record, key) ? record[key] : undefined
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function readRuntimeAgentIdentityOverrides(
|
|
43
|
+
context: Record<string, unknown> | null | undefined,
|
|
44
|
+
): RuntimeAgentIdentityOverrides {
|
|
45
|
+
return {
|
|
46
|
+
displayNamesById: readStringRecord(context?.agentDisplayNamesById),
|
|
47
|
+
shortDisplayNamesById: readStringRecord(context?.agentShortDisplayNamesById),
|
|
48
|
+
primaryLabelsById: readStringRecord(context?.agentPrimaryLabelsById),
|
|
49
|
+
secondaryLabelsById: readStringRecord(context?.agentSecondaryLabelsById),
|
|
50
|
+
templateRoleIdsByAgentId: readStringRecord(context?.agentTemplateRoleIdsById),
|
|
51
|
+
routingAliasesByAgentId: readStringArrayRecord(context?.agentRoutingAliasesById),
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function resolveRuntimeAgentDisplayName(overrides: RuntimeAgentIdentityOverrides, agentId: string): string {
|
|
56
|
+
const override = readStringOverride(overrides.displayNamesById, agentId)
|
|
57
|
+
if (override !== undefined) {
|
|
58
|
+
return override
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return agentDisplayNames[agentId] ?? agentId
|
|
62
|
+
}
|
|
@@ -29,6 +29,17 @@ import type { NormalizedWorkstream, WorkstreamRecord } from '../services/workstr
|
|
|
29
29
|
import { safeEnqueue } from '../utils/async'
|
|
30
30
|
import { toIsoDateTimeString } from '../utils/date-time'
|
|
31
31
|
|
|
32
|
+
function resolveDisplayName(agentId: string, overrides?: Partial<Record<string, string>>): string {
|
|
33
|
+
if (overrides && Object.hasOwn(overrides, agentId)) {
|
|
34
|
+
const override = overrides[agentId]
|
|
35
|
+
if (override !== undefined) {
|
|
36
|
+
return override
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return agentDisplayNames[agentId] ?? agentId
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
function buildRecentActivityChatDeepLink(params: {
|
|
33
44
|
workstream: NormalizedWorkstream
|
|
34
45
|
workstreamId: string
|
|
@@ -44,9 +55,10 @@ function buildRecentActivityChatDeepLink(params: {
|
|
|
44
55
|
function buildRecentActivityChatSystemTitle(params: {
|
|
45
56
|
workstream: NormalizedWorkstream
|
|
46
57
|
visibleAgentId: string
|
|
58
|
+
agentDisplayNamesById?: Partial<Record<string, string>>
|
|
47
59
|
}): string {
|
|
48
60
|
if (params.workstream.mode === 'direct') {
|
|
49
|
-
return `Conversation with ${
|
|
61
|
+
return `Conversation with ${resolveDisplayName(params.visibleAgentId, params.agentDisplayNamesById)}`
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
return params.workstream.title.trim() || 'Workstream update'
|
|
@@ -77,6 +89,7 @@ interface PostTurnSideEffectsParams {
|
|
|
77
89
|
defaultLeadAgentId: string
|
|
78
90
|
latestWorkstreamRecord: WorkstreamRecord
|
|
79
91
|
isUserTurn: boolean
|
|
92
|
+
agentDisplayNamesById?: Partial<Record<string, string>>
|
|
80
93
|
}
|
|
81
94
|
|
|
82
95
|
export async function runPostTurnSideEffects(params: PostTurnSideEffectsParams): Promise<void> {
|
|
@@ -140,8 +153,9 @@ export async function runPostTurnSideEffects(params: PostTurnSideEffectsParams):
|
|
|
140
153
|
title: buildRecentActivityChatSystemTitle({
|
|
141
154
|
workstream: params.workstream,
|
|
142
155
|
visibleAgentId: effectiveAgentId,
|
|
156
|
+
agentDisplayNamesById: params.agentDisplayNamesById,
|
|
143
157
|
}),
|
|
144
|
-
sourceLabel:
|
|
158
|
+
sourceLabel: resolveDisplayName(effectiveAgentId, params.agentDisplayNamesById),
|
|
145
159
|
deepLink: buildRecentActivityChatDeepLink({
|
|
146
160
|
workstream: params.workstream,
|
|
147
161
|
workstreamId: params.workstreamIdString,
|
|
@@ -149,7 +163,7 @@ export async function runPostTurnSideEffects(params: PostTurnSideEffectsParams):
|
|
|
149
163
|
}),
|
|
150
164
|
metadata: {
|
|
151
165
|
agentId: effectiveAgentId,
|
|
152
|
-
agentName:
|
|
166
|
+
agentName: resolveDisplayName(effectiveAgentId, params.agentDisplayNamesById),
|
|
153
167
|
workstreamId: params.workstreamIdString,
|
|
154
168
|
workstreamTitle: params.latestWorkstreamRecord.title ?? params.workstream.title,
|
|
155
169
|
workstreamMode: params.workstream.mode,
|
|
@@ -91,14 +91,25 @@ export interface CreateConsultTeamToolParams {
|
|
|
91
91
|
systemWorkspaceDetails?: string
|
|
92
92
|
getPreSeededMemoriesSection: (agentId: string) => Promise<string | undefined>
|
|
93
93
|
retrievedKnowledgeSection?: string
|
|
94
|
+
displayNamesById?: Partial<Record<string, string>>
|
|
94
95
|
abortSignal: AbortSignal
|
|
95
96
|
participantRunner: TeamConsultationParticipantRunner
|
|
96
97
|
onReadError?: (agentId: string, error: unknown) => void
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
export function createConsultTeamTool(params: CreateConsultTeamToolParams) {
|
|
101
|
+
const resolveDisplayName = (agentId: string) => {
|
|
102
|
+
if (params.displayNamesById && Object.hasOwn(params.displayNamesById, agentId)) {
|
|
103
|
+
const override = params.displayNamesById[agentId]
|
|
104
|
+
if (override !== undefined) {
|
|
105
|
+
return override
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return agentDisplayNames[agentId] ?? agentId
|
|
110
|
+
}
|
|
100
111
|
const participantNames = teamConsultParticipants
|
|
101
|
-
.map((agentId) =>
|
|
112
|
+
.map((agentId) => resolveDisplayName(agentId))
|
|
102
113
|
.filter((value) => value.trim().length > 0)
|
|
103
114
|
const participantSummary =
|
|
104
115
|
participantNames.length > 0 ? participantNames.join(', ') : 'the configured specialist participants'
|
|
@@ -110,7 +121,7 @@ export function createConsultTeamTool(params: CreateConsultTeamToolParams) {
|
|
|
110
121
|
const uploadMetadataText = buildReadableUploadMetadataText(params.availableUploads)
|
|
111
122
|
const responses: ConsultTeamResultData['responses'] = teamConsultParticipants.map((agentId) => ({
|
|
112
123
|
agentId,
|
|
113
|
-
agentName:
|
|
124
|
+
agentName: resolveDisplayName(agentId),
|
|
114
125
|
status: 'running',
|
|
115
126
|
}))
|
|
116
127
|
const queue: ConsultTeamResultData[] = []
|
|
@@ -125,7 +136,7 @@ export function createConsultTeamTool(params: CreateConsultTeamToolParams) {
|
|
|
125
136
|
}
|
|
126
137
|
|
|
127
138
|
const workerPromises = teamConsultParticipants.map(async (agentId, index) => {
|
|
128
|
-
const agentName =
|
|
139
|
+
const agentName = resolveDisplayName(agentId)
|
|
129
140
|
const timedAbort = createTimedAbortSignal(params.abortSignal, TEAM_CONSULTATION_TIMEOUT_MS)
|
|
130
141
|
let latestMessage: ChatMessage | null = null
|
|
131
142
|
|
|
@@ -21,7 +21,10 @@ import {
|
|
|
21
21
|
buildOwnershipDispatchContextSection,
|
|
22
22
|
buildOwnershipDispatchResponseGuard,
|
|
23
23
|
} from '../runtime/agent-runtime-policy'
|
|
24
|
+
import { mergeInstructionSections } from '../runtime/instruction-sections'
|
|
24
25
|
import { buildIndexedRepositoriesContext, getPluginService } from '../runtime/plugin-resolution'
|
|
26
|
+
import { getTurnHooks } from '../runtime/runtime-extensions'
|
|
27
|
+
import { asRecord, readInstructionSections, readOptionalString } from '../runtime/workstream-chat-helpers'
|
|
25
28
|
import { nodeWorkspaceService } from './node-workspace.service'
|
|
26
29
|
import type { PlanValidationIssueInput } from './plan-validator.service'
|
|
27
30
|
import { WorkstreamSchema } from './workstream.types'
|
|
@@ -145,25 +148,47 @@ class AgentExecutorService {
|
|
|
145
148
|
const mode = params.executionMode ?? 'linear'
|
|
146
149
|
|
|
147
150
|
const dispatchMode = workstream.mode === 'group' ? 'fixedWorkstreamMode' : 'direct'
|
|
151
|
+
const dispatchInstructionSections = [
|
|
152
|
+
buildOwnershipDispatchContextSection({
|
|
153
|
+
node: params.nodeSpec,
|
|
154
|
+
resolvedInput: params.resolvedInput,
|
|
155
|
+
inputArtifacts: params.inputArtifacts,
|
|
156
|
+
upstreamHandoffs: params.context.upstreamHandoffs,
|
|
157
|
+
}),
|
|
158
|
+
]
|
|
159
|
+
const turnHooks = getTurnHooks()
|
|
160
|
+
const agentResolution = asRecord(
|
|
161
|
+
await turnHooks.resolveAgent?.({
|
|
162
|
+
agentId,
|
|
163
|
+
mode: dispatchMode,
|
|
164
|
+
workstream,
|
|
165
|
+
workstreamRef,
|
|
166
|
+
orgRef: organizationRef,
|
|
167
|
+
userRef,
|
|
168
|
+
userName,
|
|
169
|
+
onboardingActive: false,
|
|
170
|
+
linearInstalled: Boolean(linearInstallation),
|
|
171
|
+
githubInstalled: Boolean(githubInstallation),
|
|
172
|
+
additionalInstructionSections: dispatchInstructionSections,
|
|
173
|
+
context: null,
|
|
174
|
+
}),
|
|
175
|
+
)
|
|
176
|
+
const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
|
|
148
177
|
const runtimeConfig = getAgentRuntimeConfig({
|
|
149
|
-
agentId,
|
|
178
|
+
agentId: resolvedAgentId,
|
|
150
179
|
workstreamMode: workstream.mode,
|
|
151
180
|
mode: dispatchMode,
|
|
152
181
|
onboardingActive: false,
|
|
153
182
|
linearInstalled: Boolean(linearInstallation),
|
|
154
|
-
additionalInstructionSections:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
inputArtifacts: params.inputArtifacts,
|
|
159
|
-
upstreamHandoffs: params.context.upstreamHandoffs,
|
|
160
|
-
}),
|
|
161
|
-
],
|
|
183
|
+
additionalInstructionSections: mergeInstructionSections(
|
|
184
|
+
dispatchInstructionSections,
|
|
185
|
+
readInstructionSections(agentResolution?.additionalInstructionSections),
|
|
186
|
+
),
|
|
162
187
|
responseGuardSection: buildOwnershipDispatchResponseGuard({ node: params.nodeSpec, executionMode: mode }),
|
|
163
188
|
}) as Record<string, unknown>
|
|
164
189
|
|
|
165
190
|
const rawTools = (await buildAgentTools({
|
|
166
|
-
agentId,
|
|
191
|
+
agentId: resolvedAgentId,
|
|
167
192
|
orgId: organizationRef,
|
|
168
193
|
userId: userRef,
|
|
169
194
|
userName,
|
|
@@ -175,7 +200,7 @@ class AgentExecutorService {
|
|
|
175
200
|
onboardingActive: false,
|
|
176
201
|
githubInstalled: Boolean(githubInstallation),
|
|
177
202
|
provideRepoTool: indexedRepoContext.provideRepoTool,
|
|
178
|
-
defaultRepoSections: indexedRepoContext.defaultSectionsByAgent[
|
|
203
|
+
defaultRepoSections: indexedRepoContext.defaultSectionsByAgent[resolvedAgentId],
|
|
179
204
|
memoryBlock: '',
|
|
180
205
|
onAppendMemoryBlock: () => undefined,
|
|
181
206
|
availableUploads: [],
|
|
@@ -183,9 +208,9 @@ class AgentExecutorService {
|
|
|
183
208
|
})) as ToolSet
|
|
184
209
|
const tools = applyToolPolicy(rawTools, params.nodeSpec)
|
|
185
210
|
|
|
186
|
-
const agentFactory = createAgent[
|
|
211
|
+
const agentFactory = createAgent[resolvedAgentId] as ((...args: unknown[]) => unknown) | undefined
|
|
187
212
|
if (!agentFactory) {
|
|
188
|
-
throw new Error(`Agent factory "${
|
|
213
|
+
throw new Error(`Agent factory "${resolvedAgentId}" is not registered.`)
|
|
189
214
|
}
|
|
190
215
|
|
|
191
216
|
const maxSteps = typeof runtimeConfig.maxSteps === 'number' ? runtimeConfig.maxSteps : 8
|
|
@@ -13,7 +13,6 @@ import type { PrepareStepFunction, StopCondition, ToolLoopAgent, ToolSet, UIMess
|
|
|
13
13
|
|
|
14
14
|
import type { CoreWorkstreamProfile } from '../config/agent-defaults'
|
|
15
15
|
import {
|
|
16
|
-
agentDisplayNames,
|
|
17
16
|
agentRoster,
|
|
18
17
|
buildAgentTools,
|
|
19
18
|
createAgent,
|
|
@@ -28,6 +27,7 @@ import { recordIdToString } from '../db/record-id'
|
|
|
28
27
|
import { TABLES } from '../db/tables'
|
|
29
28
|
import { enqueueContextCompaction } from '../queues/context-compaction.queue'
|
|
30
29
|
import { enqueueWorkstreamTitleGeneration } from '../queues/title-generation.queue'
|
|
30
|
+
import { readRuntimeAgentIdentityOverrides, resolveRuntimeAgentDisplayName } from '../runtime/agent-identity-overrides'
|
|
31
31
|
import { OWNERSHIP_DISPATCH_BLOCKED_TOOL_NAMES } from '../runtime/agent-runtime-policy'
|
|
32
32
|
import { createAgentMessageMetadata, createServerRunAbortController } from '../runtime/agent-stream-helpers'
|
|
33
33
|
import { hasApprovalRespondedParts } from '../runtime/approval-continuation'
|
|
@@ -260,6 +260,7 @@ async function streamAgentResponse(
|
|
|
260
260
|
streamParams: StreamAgentResponseParams,
|
|
261
261
|
): Promise<ChatMessage> {
|
|
262
262
|
const agentTimer = lotaDebugLogger.timer(`agent:${streamParams.agentId}`)
|
|
263
|
+
const agentIdentityOverrides = readRuntimeAgentIdentityOverrides(ctx.buildContextResult)
|
|
263
264
|
// Skip full plan state during plan turns — the plan-turn sections already have the active node contract
|
|
264
265
|
const executionPlanInstructionSections =
|
|
265
266
|
streamParams.includeExecutionPlanTools === false ? undefined : await ctx.getExecutionPlanInstructionSections()
|
|
@@ -383,7 +384,10 @@ async function streamAgentResponse(
|
|
|
383
384
|
originalMessages: streamParams.messages,
|
|
384
385
|
sendReasoning: true,
|
|
385
386
|
sendSources: true,
|
|
386
|
-
messageMetadata: createAgentMessageMetadata({
|
|
387
|
+
messageMetadata: createAgentMessageMetadata({
|
|
388
|
+
agentId: resolvedAgentId,
|
|
389
|
+
agentName: resolveRuntimeAgentDisplayName(agentIdentityOverrides, resolvedAgentId),
|
|
390
|
+
}),
|
|
387
391
|
onFinish: ({ responseMessage: finishedResponseMessage }: { responseMessage: ChatMessage }) => {
|
|
388
392
|
responseMessage = withMessageCreatedAt(finishedResponseMessage, Date.now())
|
|
389
393
|
resolveFinishedStream()
|
|
@@ -735,6 +739,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
735
739
|
originalMessages,
|
|
736
740
|
run: async (writer?: UIMessageStreamWriter<ChatMessage>) => {
|
|
737
741
|
const executeRun = async (leaseAbortSignal?: AbortSignal): Promise<PreparedWorkstreamTurnResult | void> => {
|
|
742
|
+
const agentIdentityOverrides = readRuntimeAgentIdentityOverrides(buildContextResult)
|
|
738
743
|
const runTimer = lotaDebugLogger.timer('run')
|
|
739
744
|
const serverRunId = Bun.randomUUIDv7()
|
|
740
745
|
const runAbortSignals = leaseAbortSignal ? [params.abortSignal, leaseAbortSignal] : [params.abortSignal]
|
|
@@ -828,7 +833,6 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
828
833
|
filterTools?: (tools: ToolSet) => ToolSet
|
|
829
834
|
includeExecutionPlanTools?: boolean
|
|
830
835
|
metadataPatch?: NonNullable<MessageMetadata>
|
|
831
|
-
headless?: boolean
|
|
832
836
|
}): Promise<ChatMessage> => {
|
|
833
837
|
const visibleTimer = lotaDebugLogger.timer(`visible:${runParams.agentId}`)
|
|
834
838
|
let runMemoryBlock = memoryBlock
|
|
@@ -864,7 +868,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
864
868
|
skills: runParams.skills,
|
|
865
869
|
additionalInstructionSections: runParams.additionalInstructionSections,
|
|
866
870
|
includeExecutionPlanTools,
|
|
867
|
-
writer
|
|
871
|
+
writer,
|
|
868
872
|
})
|
|
869
873
|
|
|
870
874
|
visibleTimer.step('stream-agent-response')
|
|
@@ -873,7 +877,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
873
877
|
return commitAssistantResponse(
|
|
874
878
|
responseMessage,
|
|
875
879
|
runParams.agentId,
|
|
876
|
-
|
|
880
|
+
resolveRuntimeAgentDisplayName(agentIdentityOverrides, runParams.agentId),
|
|
877
881
|
runParams.metadataPatch,
|
|
878
882
|
)
|
|
879
883
|
}
|
|
@@ -927,13 +931,13 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
927
931
|
members,
|
|
928
932
|
messageText,
|
|
929
933
|
recentContext,
|
|
934
|
+
displayNamesById: agentIdentityOverrides.displayNamesById,
|
|
935
|
+
shortDisplayNamesById: agentIdentityOverrides.shortDisplayNamesById,
|
|
936
|
+
routingAliasesByAgentId: agentIdentityOverrides.routingAliasesByAgentId,
|
|
930
937
|
})
|
|
931
938
|
throwIfRunAborted()
|
|
932
939
|
|
|
933
|
-
const runGroupAgent = async (
|
|
934
|
-
agentId: string,
|
|
935
|
-
options?: { routingContext?: string; headless?: boolean },
|
|
936
|
-
) => {
|
|
940
|
+
const runGroupAgent = async (agentId: string, options?: { routingContext?: string }) => {
|
|
937
941
|
const additionalSections = [...(coreInstructionSections ?? []), ...hookInstructionSections]
|
|
938
942
|
if (options?.routingContext) {
|
|
939
943
|
additionalSections.push(`<routing-context>\n${options.routingContext}\n</routing-context>`)
|
|
@@ -948,7 +952,6 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
948
952
|
mode: 'workstreamMode',
|
|
949
953
|
skills: coreWorkstreamProfile?.skills ? [...coreWorkstreamProfile.skills] : undefined,
|
|
950
954
|
additionalInstructionSections: additionalSections,
|
|
951
|
-
headless: options?.headless,
|
|
952
955
|
})
|
|
953
956
|
}
|
|
954
957
|
|
|
@@ -965,8 +968,8 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
965
968
|
respondedAgents.push(triageResult.agentId)
|
|
966
969
|
throwIfRunAborted()
|
|
967
970
|
|
|
968
|
-
// Follow-up specialists
|
|
969
|
-
//
|
|
971
|
+
// Follow-up specialists stream visibly in order so the user can
|
|
972
|
+
// watch each specialist reply instead of waiting for a persisted refresh.
|
|
970
973
|
while (respondedAgents.length < 3) {
|
|
971
974
|
const lastResponseText = extractMessageText(lastResponse).slice(0, 500)
|
|
972
975
|
const checkResult = await checkForNextAgent({
|
|
@@ -975,6 +978,9 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
975
978
|
messageText,
|
|
976
979
|
respondedAgents,
|
|
977
980
|
lastResponseSummary: lastResponseText,
|
|
981
|
+
displayNamesById: agentIdentityOverrides.displayNamesById,
|
|
982
|
+
shortDisplayNamesById: agentIdentityOverrides.shortDisplayNamesById,
|
|
983
|
+
routingAliasesByAgentId: agentIdentityOverrides.routingAliasesByAgentId,
|
|
978
984
|
})
|
|
979
985
|
throwIfRunAborted()
|
|
980
986
|
|
|
@@ -983,7 +989,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
983
989
|
writeMultiAgentEvent(writer, {
|
|
984
990
|
phase: 'waiting-for-agent',
|
|
985
991
|
agentId: checkResult.agentId,
|
|
986
|
-
agentName:
|
|
992
|
+
agentName: resolveRuntimeAgentDisplayName(agentIdentityOverrides, checkResult.agentId),
|
|
987
993
|
note: checkResult.routingContext,
|
|
988
994
|
})
|
|
989
995
|
|
|
@@ -1009,14 +1015,13 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
1009
1015
|
|
|
1010
1016
|
lastResponse = await runGroupAgent(checkResult.agentId, {
|
|
1011
1017
|
routingContext: checkResult.routingContext,
|
|
1012
|
-
headless: true,
|
|
1013
1018
|
})
|
|
1014
1019
|
respondedAgents.push(checkResult.agentId)
|
|
1015
1020
|
throwIfRunAborted()
|
|
1016
1021
|
writeMultiAgentEvent(writer, {
|
|
1017
1022
|
phase: 'agent-message-persisted',
|
|
1018
1023
|
agentId: checkResult.agentId,
|
|
1019
|
-
agentName:
|
|
1024
|
+
agentName: resolveRuntimeAgentDisplayName(agentIdentityOverrides, checkResult.agentId),
|
|
1020
1025
|
messageId: lastResponse.id,
|
|
1021
1026
|
})
|
|
1022
1027
|
}
|
|
@@ -1083,6 +1088,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
1083
1088
|
defaultLeadAgentId,
|
|
1084
1089
|
latestWorkstreamRecord,
|
|
1085
1090
|
isUserTurn: params.kind === 'userTurn',
|
|
1091
|
+
agentDisplayNamesById: agentIdentityOverrides.displayNamesById,
|
|
1086
1092
|
})
|
|
1087
1093
|
}
|
|
1088
1094
|
|
|
@@ -3,13 +3,7 @@ import { z } from 'zod'
|
|
|
3
3
|
|
|
4
4
|
import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
|
|
5
5
|
import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
|
|
6
|
-
import {
|
|
7
|
-
agentDescriptions,
|
|
8
|
-
agentDisplayNames,
|
|
9
|
-
agentShortDisplayNames,
|
|
10
|
-
resolveAgentNameAlias,
|
|
11
|
-
routerModelId,
|
|
12
|
-
} from '../config/agent-defaults'
|
|
6
|
+
import { agentDescriptions, agentDisplayNames, agentShortDisplayNames, routerModelId } from '../config/agent-defaults'
|
|
13
7
|
|
|
14
8
|
// ---------------------------------------------------------------------------
|
|
15
9
|
// Schemas
|
|
@@ -23,6 +17,8 @@ const CheckResultSchema = z.object({
|
|
|
23
17
|
routingContext: z.string().optional(),
|
|
24
18
|
})
|
|
25
19
|
|
|
20
|
+
const ROUTER_OUTPUT_PREVIEW_CHARS = 300
|
|
21
|
+
|
|
26
22
|
export type RouterTriageResult = z.infer<typeof TriageResultSchema>
|
|
27
23
|
export type RouterCheckResult = z.infer<typeof CheckResultSchema>
|
|
28
24
|
|
|
@@ -30,10 +26,45 @@ export type RouterCheckResult = z.infer<typeof CheckResultSchema>
|
|
|
30
26
|
// Helpers
|
|
31
27
|
// ---------------------------------------------------------------------------
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
interface RouterDisplayOptions {
|
|
30
|
+
displayNamesById?: Partial<Record<string, string>>
|
|
31
|
+
shortDisplayNamesById?: Partial<Record<string, string>>
|
|
32
|
+
routingAliasesByAgentId?: Partial<Record<string, string[]>>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readStringOverride(record: Partial<Record<string, string>> | undefined, key: string): string | undefined {
|
|
36
|
+
return record && Object.hasOwn(record, key) ? record[key] : undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readStringArrayOverride(
|
|
40
|
+
record: Partial<Record<string, string[]>> | undefined,
|
|
41
|
+
key: string,
|
|
42
|
+
): string[] | undefined {
|
|
43
|
+
return record && Object.hasOwn(record, key) ? record[key] : undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readDisplayName(agentId: string, options?: RouterDisplayOptions): string {
|
|
47
|
+
const override = readStringOverride(options?.displayNamesById, agentId)
|
|
48
|
+
if (override !== undefined) {
|
|
49
|
+
return override
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return agentDisplayNames[agentId] ?? agentId
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readShortDisplayName(agentId: string, options?: RouterDisplayOptions): string {
|
|
56
|
+
const override = readStringOverride(options?.shortDisplayNamesById, agentId)
|
|
57
|
+
if (override !== undefined) {
|
|
58
|
+
return override
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return agentShortDisplayNames[agentId] ?? readDisplayName(agentId, options)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildMembersDescription(members: readonly string[], options?: RouterDisplayOptions): string {
|
|
34
65
|
return members
|
|
35
66
|
.map((id) => {
|
|
36
|
-
const display =
|
|
67
|
+
const display = readDisplayName(id, options)
|
|
37
68
|
const desc = agentDescriptions[id] ?? ''
|
|
38
69
|
return `- ${display} (id: ${id}): ${desc}`
|
|
39
70
|
})
|
|
@@ -44,12 +75,16 @@ function escapeRegex(value: string): string {
|
|
|
44
75
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
45
76
|
}
|
|
46
77
|
|
|
47
|
-
function buildExplicitAgentRoutingContext(agentId: string): string {
|
|
48
|
-
const displayName =
|
|
78
|
+
function buildExplicitAgentRoutingContext(agentId: string, options?: RouterDisplayOptions): string {
|
|
79
|
+
const displayName = readDisplayName(agentId, options)
|
|
49
80
|
return `Respond directly to the part of the user's request explicitly addressed to ${displayName}.`
|
|
50
81
|
}
|
|
51
82
|
|
|
52
|
-
function extractExplicitAgentTargets(
|
|
83
|
+
function extractExplicitAgentTargets(
|
|
84
|
+
messageText: string,
|
|
85
|
+
members: readonly string[],
|
|
86
|
+
options?: RouterDisplayOptions,
|
|
87
|
+
): string[] {
|
|
53
88
|
const normalizedMessage = messageText.trim()
|
|
54
89
|
if (!normalizedMessage) return []
|
|
55
90
|
|
|
@@ -57,7 +92,14 @@ function extractExplicitAgentTargets(messageText: string, members: readonly stri
|
|
|
57
92
|
const aliases = new Map<string, string>()
|
|
58
93
|
|
|
59
94
|
for (const member of members) {
|
|
60
|
-
for (const rawAlias of [
|
|
95
|
+
for (const rawAlias of [
|
|
96
|
+
member,
|
|
97
|
+
agentDisplayNames[member],
|
|
98
|
+
agentShortDisplayNames[member],
|
|
99
|
+
readDisplayName(member, options),
|
|
100
|
+
readShortDisplayName(member, options),
|
|
101
|
+
...(readStringArrayOverride(options?.routingAliasesByAgentId, member) ?? []),
|
|
102
|
+
]) {
|
|
61
103
|
if (typeof rawAlias !== 'string') continue
|
|
62
104
|
const alias = rawAlias.trim().toLowerCase()
|
|
63
105
|
if (!alias) continue
|
|
@@ -70,13 +112,13 @@ function extractExplicitAgentTargets(messageText: string, members: readonly stri
|
|
|
70
112
|
const orderedAliases = [...aliases.keys()].sort((left, right) => right.length - left.length)
|
|
71
113
|
const matches: Array<{ agentId: string; index: number }> = []
|
|
72
114
|
const directAddressRegex = new RegExp(
|
|
73
|
-
`(^|[
|
|
115
|
+
`(^|[\\s([{>,"'])(?<alias>${orderedAliases.map(escapeRegex).join('|')})(?=\\s*(?::|[—–-](?=\\s)))`,
|
|
74
116
|
'gi',
|
|
75
117
|
)
|
|
76
118
|
|
|
77
119
|
for (const match of normalizedMessage.matchAll(directAddressRegex)) {
|
|
78
120
|
const alias = match.groups?.alias
|
|
79
|
-
const agentId = alias ?
|
|
121
|
+
const agentId = alias ? aliases.get(alias.trim().toLowerCase()) : undefined
|
|
80
122
|
if (!agentId || !memberSet.has(agentId)) continue
|
|
81
123
|
|
|
82
124
|
const prefix = match[1]
|
|
@@ -125,6 +167,12 @@ function extractResultText(result: { text?: string; reasoning?: unknown }): stri
|
|
|
125
167
|
return ''
|
|
126
168
|
}
|
|
127
169
|
|
|
170
|
+
function logRouterRaw(label: 'triage' | 'check', text: string): void {
|
|
171
|
+
const preview = text.trim().slice(0, ROUTER_OUTPUT_PREVIEW_CHARS)
|
|
172
|
+
if (!preview) return
|
|
173
|
+
console.log(`[workstream-router] ${label} raw:`, preview)
|
|
174
|
+
}
|
|
175
|
+
|
|
128
176
|
// ---------------------------------------------------------------------------
|
|
129
177
|
// Prompts
|
|
130
178
|
// ---------------------------------------------------------------------------
|
|
@@ -166,7 +214,7 @@ function createRouterAgent(systemPrompt: string) {
|
|
|
166
214
|
id: 'workstream-router',
|
|
167
215
|
model: aiGatewayChatModel(modelId),
|
|
168
216
|
headers: buildAiGatewayDirectCacheHeaders('workstream-router'),
|
|
169
|
-
providerOptions: { openai: { reasoningEffort: '
|
|
217
|
+
providerOptions: { openai: { reasoningEffort: 'low' } },
|
|
170
218
|
instructions: systemPrompt,
|
|
171
219
|
maxOutputTokens: 256,
|
|
172
220
|
})
|
|
@@ -177,14 +225,25 @@ export async function triageWorkstreamMessage(params: {
|
|
|
177
225
|
members: readonly string[]
|
|
178
226
|
messageText: string
|
|
179
227
|
recentContext?: string
|
|
228
|
+
displayNamesById?: Partial<Record<string, string>>
|
|
229
|
+
shortDisplayNamesById?: Partial<Record<string, string>>
|
|
230
|
+
routingAliasesByAgentId?: Partial<Record<string, string[]>>
|
|
180
231
|
}): Promise<RouterTriageResult | null> {
|
|
181
|
-
const
|
|
232
|
+
const displayOptions: RouterDisplayOptions = {
|
|
233
|
+
displayNamesById: params.displayNamesById,
|
|
234
|
+
shortDisplayNamesById: params.shortDisplayNamesById,
|
|
235
|
+
routingAliasesByAgentId: params.routingAliasesByAgentId,
|
|
236
|
+
}
|
|
237
|
+
const explicitTargets = extractExplicitAgentTargets(params.messageText, params.members, displayOptions)
|
|
182
238
|
const firstExplicitTarget = explicitTargets[0]
|
|
183
239
|
if (firstExplicitTarget) {
|
|
184
|
-
return {
|
|
240
|
+
return {
|
|
241
|
+
agentId: firstExplicitTarget,
|
|
242
|
+
routingContext: buildExplicitAgentRoutingContext(firstExplicitTarget, displayOptions),
|
|
243
|
+
}
|
|
185
244
|
}
|
|
186
245
|
|
|
187
|
-
const membersDesc = buildMembersDescription(params.members)
|
|
246
|
+
const membersDesc = buildMembersDescription(params.members, displayOptions)
|
|
188
247
|
const prompt = [
|
|
189
248
|
`Workstream: "${params.workstreamTitle}"`,
|
|
190
249
|
`Members:\n${membersDesc}`,
|
|
@@ -204,8 +263,14 @@ export async function triageWorkstreamMessage(params: {
|
|
|
204
263
|
}
|
|
205
264
|
|
|
206
265
|
const effectiveText = extractResultText(result as { text?: string; reasoning?: unknown })
|
|
207
|
-
|
|
266
|
+
logRouterRaw('triage', effectiveText)
|
|
208
267
|
const json = extractJson(effectiveText)
|
|
268
|
+
if (json === null) {
|
|
269
|
+
if (effectiveText.trim()) {
|
|
270
|
+
console.log('[workstream-router] triage ignored non-json output')
|
|
271
|
+
}
|
|
272
|
+
return null
|
|
273
|
+
}
|
|
209
274
|
const parsed = TriageResultSchema.safeParse(json)
|
|
210
275
|
if (!parsed.success) {
|
|
211
276
|
console.log('[workstream-router] triage parse failed:', JSON.stringify(parsed.error.issues))
|
|
@@ -230,22 +295,30 @@ export async function checkForNextAgent(params: {
|
|
|
230
295
|
messageText: string
|
|
231
296
|
respondedAgents: string[]
|
|
232
297
|
lastResponseSummary: string
|
|
298
|
+
displayNamesById?: Partial<Record<string, string>>
|
|
299
|
+
shortDisplayNamesById?: Partial<Record<string, string>>
|
|
300
|
+
routingAliasesByAgentId?: Partial<Record<string, string[]>>
|
|
233
301
|
}): Promise<RouterCheckResult> {
|
|
234
|
-
const
|
|
302
|
+
const displayOptions: RouterDisplayOptions = {
|
|
303
|
+
displayNamesById: params.displayNamesById,
|
|
304
|
+
shortDisplayNamesById: params.shortDisplayNamesById,
|
|
305
|
+
routingAliasesByAgentId: params.routingAliasesByAgentId,
|
|
306
|
+
}
|
|
307
|
+
const explicitTargets = extractExplicitAgentTargets(params.messageText, params.members, displayOptions)
|
|
235
308
|
const nextExplicitTarget = explicitTargets.find((agentId) => !params.respondedAgents.includes(agentId))
|
|
236
309
|
if (nextExplicitTarget) {
|
|
237
310
|
return {
|
|
238
311
|
done: false,
|
|
239
312
|
agentId: nextExplicitTarget,
|
|
240
|
-
routingContext: buildExplicitAgentRoutingContext(nextExplicitTarget),
|
|
313
|
+
routingContext: buildExplicitAgentRoutingContext(nextExplicitTarget, displayOptions),
|
|
241
314
|
}
|
|
242
315
|
}
|
|
243
316
|
|
|
244
317
|
const remainingMembers = params.members.filter((id) => !params.respondedAgents.includes(id))
|
|
245
318
|
if (remainingMembers.length === 0) return { done: true }
|
|
246
319
|
|
|
247
|
-
const membersDesc = buildMembersDescription(remainingMembers)
|
|
248
|
-
const respondedList = params.respondedAgents.map((id) =>
|
|
320
|
+
const membersDesc = buildMembersDescription(remainingMembers, displayOptions)
|
|
321
|
+
const respondedList = params.respondedAgents.map((id) => readDisplayName(id, displayOptions)).join(', ')
|
|
249
322
|
|
|
250
323
|
const prompt = [
|
|
251
324
|
`Workstream: "${params.workstreamTitle}"`,
|
|
@@ -265,8 +338,14 @@ export async function checkForNextAgent(params: {
|
|
|
265
338
|
}
|
|
266
339
|
|
|
267
340
|
const effectiveText = extractResultText(result as { text?: string; reasoning?: unknown })
|
|
268
|
-
|
|
341
|
+
logRouterRaw('check', effectiveText)
|
|
269
342
|
const json = extractJson(effectiveText)
|
|
343
|
+
if (json === null) {
|
|
344
|
+
if (effectiveText.trim()) {
|
|
345
|
+
console.log('[workstream-router] check ignored non-json output')
|
|
346
|
+
}
|
|
347
|
+
return { done: true }
|
|
348
|
+
}
|
|
270
349
|
const parsed = CheckResultSchema.safeParse(json)
|
|
271
350
|
if (!parsed.success) {
|
|
272
351
|
console.log('[workstream-router] check parse failed:', JSON.stringify(parsed.error.issues))
|
|
@@ -68,10 +68,16 @@ export function createExecutionPlanTool(params: {
|
|
|
68
68
|
'create-project': async () => {
|
|
69
69
|
const targetWorkstream = targetWorkstreamId
|
|
70
70
|
? await resolvedWsService.getWorkstream(targetWorkstreamId)
|
|
71
|
-
: await
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
: await (() => {
|
|
72
|
+
if (!projectTitle) {
|
|
73
|
+
throw new Error('projectTitle is required when action is "create-project".')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return resolvedWsService.createWorkstream(params.userId, params.orgId, {
|
|
77
|
+
title: projectTitle,
|
|
78
|
+
mode: 'group',
|
|
79
|
+
})
|
|
80
|
+
})()
|
|
75
81
|
|
|
76
82
|
if (targetWorkstream.organizationId !== recordIdToString(params.orgId, TABLES.ORGANIZATION)) {
|
|
77
83
|
throw new Error('Target workstream belongs to a different organization.')
|
|
@@ -110,19 +116,27 @@ export function createExecutionPlanTool(params: {
|
|
|
110
116
|
},
|
|
111
117
|
|
|
112
118
|
replace: async () => {
|
|
119
|
+
if (!runId || !reason) {
|
|
120
|
+
throw new Error('runId and reason are required when action is "replace".')
|
|
121
|
+
}
|
|
122
|
+
|
|
113
123
|
return await resolvedEpService.replacePlan({
|
|
114
124
|
organizationId: params.orgId,
|
|
115
125
|
workstreamId: params.workstreamId,
|
|
116
126
|
leadAgentId: params.agentId,
|
|
117
|
-
input: { runId
|
|
127
|
+
input: { runId, reason, ...draft },
|
|
118
128
|
})
|
|
119
129
|
},
|
|
120
130
|
|
|
121
131
|
resume: async () => {
|
|
132
|
+
if (!runId) {
|
|
133
|
+
throw new Error('runId is required when action is "resume".')
|
|
134
|
+
}
|
|
135
|
+
|
|
122
136
|
return await resolvedEpService.resumeRun({
|
|
123
137
|
workstreamId: params.workstreamId,
|
|
124
138
|
emittedBy: params.agentId,
|
|
125
|
-
input: { runId
|
|
139
|
+
input: { runId },
|
|
126
140
|
})
|
|
127
141
|
},
|
|
128
142
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
|
|
2
2
|
import { buildAiGatewayStrictSemanticCacheHeaders } from '../ai-gateway/cache-headers'
|
|
3
3
|
import {
|
|
4
|
-
OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
|
|
5
4
|
OPENROUTER_FAST_REASONING_MODEL_ID,
|
|
5
|
+
OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
|
|
6
6
|
} from '../config/model-constants'
|
|
7
7
|
import { createDelegatedAgentTool } from '../system-agents/delegated-agent-factory'
|
|
8
8
|
import { RESEARCHER_PROMPT } from '../system-agents/researcher.agent'
|
|
@@ -14,7 +14,7 @@ export const researchTopicTool = createDelegatedAgentTool({
|
|
|
14
14
|
description:
|
|
15
15
|
'Delegate a research task to a dedicated research agent that searches the web, fetches pages, and returns a synthesized markdown report.',
|
|
16
16
|
model: () => aiGatewayChatModel(OPENROUTER_FAST_REASONING_MODEL_ID),
|
|
17
|
-
providerOptions:
|
|
17
|
+
providerOptions: OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
|
|
18
18
|
headers: buildAiGatewayStrictSemanticCacheHeaders('researchTopic'),
|
|
19
19
|
instructions: RESEARCHER_PROMPT,
|
|
20
20
|
tools: { searchWeb: searchWebTool.create(), fetchWebpage: fetchWebpageTool.create() },
|
|
@@ -7,12 +7,14 @@ import { aiLogger } from '../config/logger'
|
|
|
7
7
|
import type { RecordIdRef } from '../db/record-id'
|
|
8
8
|
import { recordIdToString } from '../db/record-id'
|
|
9
9
|
import { TABLES } from '../db/tables'
|
|
10
|
+
import { readRuntimeAgentIdentityOverrides } from '../runtime/agent-identity-overrides'
|
|
10
11
|
import { mergeInstructionSections } from '../runtime/instruction-sections'
|
|
11
|
-
import { getRuntimeAdapters } from '../runtime/runtime-extensions'
|
|
12
|
+
import { getRuntimeAdapters, getTurnHooks } from '../runtime/runtime-extensions'
|
|
12
13
|
import type { LotaRuntimeTeamThinkToolsParams } from '../runtime/runtime-extensions'
|
|
13
14
|
import { createConsultTeamTool as createConsultTeamToolSdk } from '../runtime/team-consultation-orchestrator'
|
|
14
15
|
import type { DefaultRepoSections, TeamConsultationParticipantRunner } from '../runtime/team-consultation-orchestrator'
|
|
15
16
|
import { buildTeamConsultationResponseGuard } from '../runtime/team-consultation-prompts'
|
|
17
|
+
import { asRecord, readInstructionSections, readOptionalString } from '../runtime/workstream-chat-helpers'
|
|
16
18
|
import type { ReadableUploadMetadata } from '../services/attachment.service'
|
|
17
19
|
|
|
18
20
|
async function buildTeamThinkAgentTools(
|
|
@@ -49,11 +51,33 @@ export function createTeamThinkTool(params: {
|
|
|
49
51
|
toolProviders?: ToolSet
|
|
50
52
|
abortSignal: AbortSignal
|
|
51
53
|
}) {
|
|
54
|
+
const agentIdentityOverrides = readRuntimeAgentIdentityOverrides(
|
|
55
|
+
(params.context as Record<string, unknown> | null | undefined) ?? null,
|
|
56
|
+
)
|
|
52
57
|
const participantRunner: TeamConsultationParticipantRunner = {
|
|
53
58
|
async buildParticipantAgent(agentId, runParams) {
|
|
54
59
|
const dynamicInstructionSections = await params.getAdditionalInstructionSections?.()
|
|
60
|
+
const agentResolution = asRecord(
|
|
61
|
+
await getTurnHooks().resolveAgent?.({
|
|
62
|
+
agentId,
|
|
63
|
+
mode: 'fixedWorkstreamMode',
|
|
64
|
+
workstream: null,
|
|
65
|
+
workstreamRef: params.workstreamId,
|
|
66
|
+
orgRef: params.orgId,
|
|
67
|
+
userRef: params.userId,
|
|
68
|
+
onboardingActive: false,
|
|
69
|
+
linearInstalled: false,
|
|
70
|
+
githubInstalled: params.githubInstalled,
|
|
71
|
+
additionalInstructionSections: mergeInstructionSections(
|
|
72
|
+
dynamicInstructionSections,
|
|
73
|
+
params.additionalInstructionSections,
|
|
74
|
+
),
|
|
75
|
+
context: (params.context as Record<string, unknown> | null | undefined) ?? null,
|
|
76
|
+
}),
|
|
77
|
+
)
|
|
78
|
+
const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
|
|
55
79
|
const config = getAgentRuntimeConfig({
|
|
56
|
-
agentId,
|
|
80
|
+
agentId: resolvedAgentId,
|
|
57
81
|
workstreamMode: 'group' as const,
|
|
58
82
|
mode: 'fixedWorkstreamMode',
|
|
59
83
|
onboardingActive: false,
|
|
@@ -64,24 +88,25 @@ export function createTeamThinkTool(params: {
|
|
|
64
88
|
additionalInstructionSections: mergeInstructionSections(
|
|
65
89
|
dynamicInstructionSections,
|
|
66
90
|
params.additionalInstructionSections,
|
|
91
|
+
readInstructionSections(agentResolution?.additionalInstructionSections),
|
|
67
92
|
),
|
|
68
|
-
responseGuardSection: buildTeamConsultationResponseGuard({ agentId, task: runParams.task }),
|
|
93
|
+
responseGuardSection: buildTeamConsultationResponseGuard({ agentId: resolvedAgentId, task: runParams.task }),
|
|
69
94
|
})
|
|
70
95
|
const { tools } = await buildTeamThinkAgentTools({
|
|
71
|
-
agentId,
|
|
96
|
+
agentId: resolvedAgentId,
|
|
72
97
|
workspaceId: params.orgId,
|
|
73
98
|
userId: params.userId,
|
|
74
99
|
workspaceIdString: recordIdToString(params.orgId, TABLES.ORGANIZATION),
|
|
75
100
|
workstreamId: params.workstreamId,
|
|
76
101
|
githubInstalled: params.githubInstalled,
|
|
77
|
-
provideRepoTool:
|
|
102
|
+
provideRepoTool: resolvedAgentId !== 'mentor' && params.provideRepoTool,
|
|
78
103
|
availableUploads: params.availableUploads,
|
|
79
|
-
defaultRepoSections: params.defaultRepoSectionsByAgent[
|
|
104
|
+
defaultRepoSections: params.defaultRepoSectionsByAgent[resolvedAgentId],
|
|
80
105
|
context: params.context,
|
|
81
106
|
toolProviders: params.toolProviders,
|
|
82
107
|
})
|
|
83
108
|
const agentConfig = config as Record<string, unknown>
|
|
84
|
-
const agentId_ = typeof agentConfig.id === 'string' ? agentConfig.id :
|
|
109
|
+
const agentId_ = typeof agentConfig.id === 'string' ? agentConfig.id : resolvedAgentId
|
|
85
110
|
const configuredMaxSteps = typeof agentConfig.maxSteps === 'number' ? agentConfig.maxSteps : 10
|
|
86
111
|
const maxSteps = Math.min(configuredMaxSteps, TEAM_THINK_AGENT_MAX_STEPS)
|
|
87
112
|
const agent = createAgent[agentId_]({
|
|
@@ -117,6 +142,7 @@ export function createTeamThinkTool(params: {
|
|
|
117
142
|
systemWorkspaceDetails: params.systemWorkspaceDetails,
|
|
118
143
|
getPreSeededMemoriesSection: params.getPreSeededMemoriesSection,
|
|
119
144
|
retrievedKnowledgeSection: params.retrievedKnowledgeSection,
|
|
145
|
+
displayNamesById: agentIdentityOverrides.displayNamesById,
|
|
120
146
|
abortSignal: params.abortSignal,
|
|
121
147
|
participantRunner,
|
|
122
148
|
onReadError: (agentId, error) => {
|