feat: apply selected experts as role prompts on the conversation runtime
This commit is contained in:
+41
-121
@@ -12083,16 +12083,26 @@ describe('registerIpcHandlers agent terminal state', () => {
|
|||||||
await harness.dispose()
|
await harness.dispose()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('routes eligible requests through the persisted smart expert service and publishes child events', async () => {
|
it.each([
|
||||||
|
{ route: 'manual', consumesTrustedInstructions: true },
|
||||||
|
{ route: 'smart', consumesTrustedInstructions: true },
|
||||||
|
{ route: 'manual', consumesTrustedInstructions: false }
|
||||||
|
] as const)('applies a $route expert as a role prompt on the conversation runtime (system channel: $consumesTrustedInstructions)', async ({ route, consumesTrustedInstructions }) => {
|
||||||
|
const received: Array<{ requestId: string; prompt: string; trustedInstructions?: string }> = []
|
||||||
const runtime = {
|
const runtime = {
|
||||||
|
runtimeId: 'opencode',
|
||||||
capability: 'chat',
|
capability: 'chat',
|
||||||
requiresToolApproval: false,
|
requiresToolApproval: false,
|
||||||
supportsToolExecution: true,
|
supportsToolExecution: true,
|
||||||
|
consumesTrustedInstructions,
|
||||||
getStatus: vi.fn(),
|
getStatus: vi.fn(),
|
||||||
dispose: vi.fn(),
|
dispose: vi.fn(),
|
||||||
run: vi.fn()
|
async *run(request: { requestId: string; prompt: string; trustedInstructions?: string }) {
|
||||||
|
received.push(request)
|
||||||
|
yield { requestId: request.requestId, type: 'text', delta: '角色回答' }
|
||||||
|
yield { requestId: request.requestId, type: 'done' }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const childTaskId = '00000000-0000-4000-8000-000000000099'
|
|
||||||
const expert = {
|
const expert = {
|
||||||
id: '00000000-0000-4000-8000-000000000001',
|
id: '00000000-0000-4000-8000-000000000001',
|
||||||
name: '研究专家',
|
name: '研究专家',
|
||||||
@@ -12104,69 +12114,49 @@ describe('registerIpcHandlers agent terminal state', () => {
|
|||||||
updatedAt: '2026-01-01T00:00:00.000Z'
|
updatedAt: '2026-01-01T00:00:00.000Z'
|
||||||
}
|
}
|
||||||
const subagentService = {
|
const subagentService = {
|
||||||
run: vi.fn(async (input: {
|
run: vi.fn(),
|
||||||
parentRequest: { requestId: string }
|
|
||||||
onEvent: (event: Record<string, unknown>) => void
|
|
||||||
}) => {
|
|
||||||
for (const state of ['queued', 'running', 'completed']) {
|
|
||||||
input.onEvent({
|
|
||||||
requestId: input.parentRequest.requestId,
|
|
||||||
type: 'subagent',
|
|
||||||
childTaskId,
|
|
||||||
expertId: expert.id,
|
|
||||||
expertName: expert.name,
|
|
||||||
routingMode: 'smart',
|
|
||||||
state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return { childTaskId, output: '专家结果' }
|
|
||||||
}),
|
|
||||||
cancelAll: vi.fn(),
|
cancelAll: vi.fn(),
|
||||||
dispose: vi.fn(async () => undefined)
|
dispose: vi.fn(async () => undefined)
|
||||||
}
|
}
|
||||||
const harness = createHarness(
|
const harness = createHarness(runtime, undefined, subagentService, true)
|
||||||
runtime,
|
vi.mocked(harness.assistantDatabase.listExperts).mockReturnValue([expert])
|
||||||
undefined,
|
vi.mocked(harness.assistantDatabase.getExpert).mockReturnValue(expert)
|
||||||
subagentService,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
vi.mocked(harness.assistantDatabase.listExperts).mockReturnValue([
|
|
||||||
expert
|
|
||||||
])
|
|
||||||
const requestId = '3f496642-f47d-4e0a-8944-a32c77b0d6ef'
|
const requestId = '3f496642-f47d-4e0a-8944-a32c77b0d6ef'
|
||||||
|
|
||||||
harness.handler?.(trustedEvent(harness.webContents), {
|
harness.handler?.(trustedEvent(harness.webContents), {
|
||||||
requestId,
|
requestId,
|
||||||
conversationId: 'conversation-smart',
|
conversationId: 'conversation-role',
|
||||||
prompt: '请做资料分析',
|
prompt: '请做资料分析',
|
||||||
smartRouting: true
|
...(route === 'manual' ? { expertId: expert.id } : { smartRouting: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
await vi.waitFor(() =>
|
await vi.waitFor(() =>
|
||||||
expect(harness.assistantDatabase.updateTaskStatus).toHaveBeenCalledWith(
|
expect(harness.assistantDatabase.updateTaskStatus).toHaveBeenCalledWith(requestId, 'completed')
|
||||||
requestId,
|
|
||||||
'completed'
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
expect(runtime.run).not.toHaveBeenCalled()
|
expect(received).toHaveLength(1)
|
||||||
expect(harness.getPolicySettings).toHaveBeenCalledOnce()
|
const request = received[0]!
|
||||||
expect(harness.getResolvedSettings).not.toHaveBeenCalled()
|
expect(request.trustedInstructions).toContain('enabled capabilities')
|
||||||
expect(subagentService.run).toHaveBeenCalledWith(
|
expect(request.trustedInstructions).toContain('"研究专家"')
|
||||||
expect.objectContaining({ expert, routingMode: 'smart' })
|
expect(request.trustedInstructions).toContain('Analyze evidence.')
|
||||||
)
|
if (consumesTrustedInstructions) {
|
||||||
expect(harness.assistantDatabase.appendTaskEvent).toHaveBeenCalledWith(
|
expect(request.prompt).toBe('请做资料分析')
|
||||||
|
} else {
|
||||||
|
expect(request.prompt).toMatch(/enabled capabilities[\s\S]*Analyze evidence\.\n\n请做资料分析$/u)
|
||||||
|
}
|
||||||
|
expect(subagentService.run).not.toHaveBeenCalled()
|
||||||
|
expect(harness.assistantDatabase.appendTaskEvent).not.toHaveBeenCalledWith(
|
||||||
requestId,
|
requestId,
|
||||||
'subagent',
|
'subagent',
|
||||||
expect.objectContaining({ childTaskId, state: 'queued' })
|
expect.anything()
|
||||||
)
|
)
|
||||||
expect(harness.webContents.send).toHaveBeenCalledWith(
|
expect(harness.webContents.send).toHaveBeenCalledWith(
|
||||||
ipcChannels.agentEvent,
|
ipcChannels.agentEvent,
|
||||||
expect.objectContaining({ type: 'subagent', state: 'completed' })
|
expect.objectContaining({ type: 'text', delta: '角色回答' })
|
||||||
)
|
)
|
||||||
await harness.dispose()
|
await harness.dispose()
|
||||||
})
|
})
|
||||||
|
|
||||||
it.each(['manual', 'team', 'smart'] as const)('binds %s expert writes to each request project instead of the global workspace', async (route) => {
|
it('binds expert team writes to each request project instead of the global workspace', async () => {
|
||||||
const actual = await vi.importActual<typeof import('./agent/create-runtime')>('./agent/create-runtime')
|
const actual = await vi.importActual<typeof import('./agent/create-runtime')>('./agent/create-runtime')
|
||||||
const directory = await mkdtemp(join(tmpdir(), 'expert-projects-'))
|
const directory = await mkdtemp(join(tmpdir(), 'expert-projects-'))
|
||||||
const globalRoot = join(directory, 'global')
|
const globalRoot = join(directory, 'global')
|
||||||
@@ -12215,11 +12205,10 @@ describe('registerIpcHandlers agent terminal state', () => {
|
|||||||
for (const [index, projectId] of projectIds.entries()) {
|
for (const [index, projectId] of projectIds.entries()) {
|
||||||
const requestId = crypto.randomUUID()
|
const requestId = crypto.randomUUID()
|
||||||
await harness.handler!(trustedEvent(harness.webContents), { requestId, conversationId: `project-${index}`, projectId,
|
await harness.handler!(trustedEvent(harness.webContents), { requestId, conversationId: `project-${index}`, projectId,
|
||||||
prompt: `write ${index === 0 ? 'alpha' : 'beta'}`, expertId: route === 'manual' ? expert.id : undefined,
|
prompt: `write ${index === 0 ? 'alpha' : 'beta'}`, teamMode: true })
|
||||||
teamMode: route === 'team', smartRouting: route === 'smart' })
|
|
||||||
await vi.waitFor(() => expect(harness.assistantDatabase.updateTaskStatus).toHaveBeenCalledWith(requestId, 'completed'))
|
await vi.waitFor(() => expect(harness.assistantDatabase.updateTaskStatus).toHaveBeenCalledWith(requestId, 'completed'))
|
||||||
const files = await readdir(roots[index]!)
|
const files = await readdir(roots[index]!)
|
||||||
expect(files).toHaveLength(route === 'team' ? 2 : 1)
|
expect(files).toHaveLength(2)
|
||||||
for (const file of files) expect(await readFile(join(roots[index]!, file), 'utf8')).toBe(index === 0 ? 'alpha' : 'beta')
|
for (const file of files) expect(await readFile(join(roots[index]!, file), 'utf8')).toBe(index === 0 ? 'alpha' : 'beta')
|
||||||
for (const input of requests.filter(input => input.parentRequest.requestId === requestId)) {
|
for (const input of requests.filter(input => input.parentRequest.requestId === requestId)) {
|
||||||
expect(input.executionSpace).toBe(getRuntime.mock.calls[index]?.[1])
|
expect(input.executionSpace).toBe(getRuntime.mock.calls[index]?.[1])
|
||||||
@@ -12236,9 +12225,7 @@ describe('registerIpcHandlers agent terminal state', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it.each((['manual', 'team', 'smart'] as const).flatMap(route =>
|
it.each(['opencode', 'continue'] as const)('uses managed %s for SSH expert team members with their profile and parent bindings', async (provider) => {
|
||||||
(['opencode', 'continue'] as const).map(provider => ({ route, provider }))
|
|
||||||
))('uses managed $provider for $route SSH experts with their profile and parent bindings', async ({ route, provider }) => {
|
|
||||||
const actual = await vi.importActual<typeof import('./agent/create-runtime')>('./agent/create-runtime')
|
const actual = await vi.importActual<typeof import('./agent/create-runtime')>('./agent/create-runtime')
|
||||||
const globalRoot = await mkdtemp(join(tmpdir(), 'expert-ssh-'))
|
const globalRoot = await mkdtemp(join(tmpdir(), 'expert-ssh-'))
|
||||||
const projectId = crypto.randomUUID()
|
const projectId = crypto.randomUUID()
|
||||||
@@ -12297,14 +12284,13 @@ describe('registerIpcHandlers agent terminal state', () => {
|
|||||||
try {
|
try {
|
||||||
const requestId = crypto.randomUUID()
|
const requestId = crypto.randomUUID()
|
||||||
await harness.handler!(trustedEvent(harness.webContents), { requestId, conversationId: 'remote-expert', projectId,
|
await harness.handler!(trustedEvent(harness.webContents), { requestId, conversationId: 'remote-expert', projectId,
|
||||||
prompt: 'write a file', expertId: route === 'manual' ? expert.id : undefined,
|
prompt: 'write a file', teamMode: true })
|
||||||
teamMode: route === 'team', smartRouting: route === 'smart' })
|
|
||||||
await vi.waitFor(() => expect(harness.assistantDatabase.updateTaskStatus).toHaveBeenCalledWith(requestId, 'completed'))
|
await vi.waitFor(() => expect(harness.assistantDatabase.updateTaskStatus).toHaveBeenCalledWith(requestId, 'completed'))
|
||||||
expect(factory).toHaveBeenCalledTimes(route === 'team' ? 2 : 1)
|
expect(factory).toHaveBeenCalledTimes(2)
|
||||||
expect(createRemote).toHaveBeenCalledWith({ provider, profileId: expert.modelProfileId }, executionSpace)
|
expect(createRemote).toHaveBeenCalledWith({ provider, profileId: expert.modelProfileId }, executionSpace)
|
||||||
expect(createLocal).not.toHaveBeenCalled()
|
expect(createLocal).not.toHaveBeenCalled()
|
||||||
expect(binding.save).not.toHaveBeenCalled()
|
expect(binding.save).not.toHaveBeenCalled()
|
||||||
expect(fetcher).toHaveBeenCalledTimes(route === 'team' ? 1 : 0)
|
expect(fetcher).toHaveBeenCalledTimes(1)
|
||||||
expect(ordinary.run).not.toHaveBeenCalled()
|
expect(ordinary.run).not.toHaveBeenCalled()
|
||||||
expect(await readdir(globalRoot)).toEqual([])
|
expect(await readdir(globalRoot)).toEqual([])
|
||||||
} finally {
|
} finally {
|
||||||
@@ -12375,72 +12361,6 @@ describe('registerIpcHandlers agent terminal state', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
it('does not fall back to the ordinary runtime after smart subagent cancellation', async () => {
|
|
||||||
const runtime = {
|
|
||||||
capability: 'chat',
|
|
||||||
requiresToolApproval: false,
|
|
||||||
supportsToolExecution: true,
|
|
||||||
getStatus: vi.fn(),
|
|
||||||
dispose: vi.fn(),
|
|
||||||
run: vi.fn()
|
|
||||||
}
|
|
||||||
let markStarted!: () => void
|
|
||||||
const started = new Promise<void>((resolve) => {
|
|
||||||
markStarted = resolve
|
|
||||||
})
|
|
||||||
const subagentService = {
|
|
||||||
run: vi.fn((input: { signal: AbortSignal }) => {
|
|
||||||
markStarted()
|
|
||||||
return new Promise((_resolve, reject) => {
|
|
||||||
input.signal.addEventListener(
|
|
||||||
'abort',
|
|
||||||
() => reject(input.signal.reason),
|
|
||||||
{ once: true }
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
cancelAll: vi.fn(),
|
|
||||||
dispose: vi.fn(async () => undefined)
|
|
||||||
}
|
|
||||||
const harness = createHarness(
|
|
||||||
runtime,
|
|
||||||
undefined,
|
|
||||||
subagentService,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
vi.mocked(harness.assistantDatabase.listExperts).mockReturnValue([
|
|
||||||
{
|
|
||||||
id: '00000000-0000-4000-8000-000000000001',
|
|
||||||
name: '研究专家',
|
|
||||||
description: '',
|
|
||||||
systemInstructions: 'Analyze.',
|
|
||||||
routingKeywords: ['资料分析'],
|
|
||||||
enabled: true,
|
|
||||||
createdAt: '2026-01-01T00:00:00.000Z',
|
|
||||||
updatedAt: '2026-01-01T00:00:00.000Z'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
const requestId = '3f496642-f47d-4e0a-8944-a32c77b0d6ef'
|
|
||||||
harness.handler?.(trustedEvent(harness.webContents), {
|
|
||||||
requestId,
|
|
||||||
conversationId: 'conversation-cancel-smart',
|
|
||||||
prompt: '请做资料分析',
|
|
||||||
smartRouting: true
|
|
||||||
})
|
|
||||||
await started
|
|
||||||
harness.cancelHandler?.(trustedEvent(harness.webContents), requestId)
|
|
||||||
|
|
||||||
await vi.waitFor(() =>
|
|
||||||
expect(harness.assistantDatabase.updateTaskStatus).toHaveBeenCalledWith(
|
|
||||||
requestId,
|
|
||||||
'cancelled',
|
|
||||||
'请求已取消'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
expect(runtime.run).not.toHaveBeenCalled()
|
|
||||||
await harness.dispose()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('allows ordinary text requests on runtimes without tool execution', async () => {
|
it('allows ordinary text requests on runtimes without tool execution', async () => {
|
||||||
const runtime = {
|
const runtime = {
|
||||||
capability: 'chat',
|
capability: 'chat',
|
||||||
|
|||||||
+46
-83
@@ -282,10 +282,7 @@ import {
|
|||||||
supervisionKnowledgeCommitRequestSchema
|
supervisionKnowledgeCommitRequestSchema
|
||||||
} from '../shared/supervision-contracts'
|
} from '../shared/supervision-contracts'
|
||||||
import { showDesktopNotificationWhenUnfocused } from './desktop-notification'
|
import { showDesktopNotificationWhenUnfocused } from './desktop-notification'
|
||||||
import {
|
import type { SubagentService } from './assistant/subagent-service'
|
||||||
SubagentRunError,
|
|
||||||
type SubagentService
|
|
||||||
} from './assistant/subagent-service'
|
|
||||||
import { routeSubagent } from './assistant/subagent-router'
|
import { routeSubagent } from './assistant/subagent-router'
|
||||||
import {
|
import {
|
||||||
startEnvironmentChannels
|
startEnvironmentChannels
|
||||||
@@ -2699,37 +2696,20 @@ export function registerIpcHandlers(
|
|||||||
yield { requestId: request.requestId, type: 'done' }
|
yield { requestId: request.requestId, type: 'done' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const runSingleExpert = async function* (
|
/**
|
||||||
request: AgentExecutionRequest,
|
* A selected or smart-routed expert is a role prompt for the conversation's
|
||||||
expert: Awaited<ReturnType<AssistantStoragePort['getExpert']>>,
|
* own runtime, not a delegated child task: it joins the trusted
|
||||||
routingMode: 'manual' | 'smart',
|
* instructions so streaming, tools and the selected runtime stay unchanged.
|
||||||
signal: AbortSignal,
|
*/
|
||||||
executionSpace: ExecutionSpaceDescriptor | undefined,
|
const expertRolePrompt = (
|
||||||
reason?: string
|
expert: Awaited<ReturnType<AssistantStoragePort['getExpert']>>
|
||||||
): AsyncGenerator<RuntimeEvent, void, void> {
|
): string =>
|
||||||
if (!subagentService) {
|
[
|
||||||
throw new Error('专家子任务服务不可用')
|
`Role: act as the specialist "${expert.name}" for this request.`,
|
||||||
}
|
expert.systemInstructions
|
||||||
const result = await subagentService.run({
|
]
|
||||||
parentRequest: request,
|
.filter(Boolean)
|
||||||
executionSpace,
|
.join('\n\n')
|
||||||
expert,
|
|
||||||
routingMode,
|
|
||||||
reason,
|
|
||||||
signal,
|
|
||||||
onEvent: async (event) =>
|
|
||||||
(await publishSubagentEvent(request.requestId, event)),
|
|
||||||
onModelUsage: persistModelUsage
|
|
||||||
})
|
|
||||||
if (result.output) {
|
|
||||||
yield {
|
|
||||||
requestId: request.requestId,
|
|
||||||
type: 'text',
|
|
||||||
delta: result.output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
yield { requestId: request.requestId, type: 'done' }
|
|
||||||
}
|
|
||||||
|
|
||||||
let scheduleQueueTickRunning = false
|
let scheduleQueueTickRunning = false
|
||||||
const queueDueSchedules = async (): Promise<void> => {
|
const queueDueSchedules = async (): Promise<void> => {
|
||||||
@@ -4192,6 +4172,15 @@ export function registerIpcHandlers(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// A selected or smart-routed expert is a role prompt for the
|
||||||
|
// conversation's own runtime, not a delegated child task.
|
||||||
|
const roleExpert =
|
||||||
|
executionRequest.teamMode || imageGeneration
|
||||||
|
? undefined
|
||||||
|
: executionRequest.expertId
|
||||||
|
? await assistantDatabase.getExpert(executionRequest.expertId)
|
||||||
|
: smartRoute?.expert
|
||||||
|
const rolePrompt = roleExpert ? expertRolePrompt(roleExpert) : ''
|
||||||
const ordinaryStream = (): AsyncGenerator<
|
const ordinaryStream = (): AsyncGenerator<
|
||||||
RuntimeEvent,
|
RuntimeEvent,
|
||||||
void,
|
void,
|
||||||
@@ -4202,66 +4191,40 @@ export function registerIpcHandlers(
|
|||||||
managedSshOperationAccepted = true
|
managedSshOperationAccepted = true
|
||||||
markManagedSshAccepted()
|
markManagedSshAccepted()
|
||||||
}
|
}
|
||||||
|
const runtimeRequest: AgentExecutionRequest = rolePrompt
|
||||||
|
? {
|
||||||
|
...executionRequest,
|
||||||
|
trustedInstructions: [
|
||||||
|
executionRequest.trustedInstructions,
|
||||||
|
rolePrompt
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n\n')
|
||||||
|
}
|
||||||
|
: executionRequest
|
||||||
|
// Runtimes without a system channel get the capability and role
|
||||||
|
// instructions ahead of the prompt.
|
||||||
|
const promptPrefix =
|
||||||
|
selectedRuntime.consumesTrustedInstructions !== true
|
||||||
|
? [capabilityInstruction, rolePrompt].filter(Boolean).join('\n\n')
|
||||||
|
: ''
|
||||||
return selectedRuntime.run(
|
return selectedRuntime.run(
|
||||||
capabilityInstruction && selectedRuntime.consumesTrustedInstructions !== true
|
promptPrefix
|
||||||
? {
|
? {
|
||||||
...executionRequest,
|
...runtimeRequest,
|
||||||
prompt: `${capabilityInstruction}\n\n${executionRequest.prompt}`
|
prompt: `${promptPrefix}\n\n${runtimeRequest.prompt}`
|
||||||
}
|
}
|
||||||
: executionRequest,
|
: runtimeRequest,
|
||||||
controller.signal
|
controller.signal
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const runSmartRoute = async function* (): AsyncGenerator<
|
|
||||||
RuntimeEvent,
|
|
||||||
void,
|
|
||||||
void
|
|
||||||
> {
|
|
||||||
if (!smartRoute) {
|
|
||||||
yield* ordinaryStream()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
yield* runSingleExpert(
|
|
||||||
executionRequest,
|
|
||||||
smartRoute.expert,
|
|
||||||
'smart',
|
|
||||||
controller.signal,
|
|
||||||
requestExecutionSpace,
|
|
||||||
`匹配 ${smartRoute.matches} 个关键词,得分 ${smartRoute.score}`
|
|
||||||
)
|
|
||||||
} catch (error) {
|
|
||||||
if (controller.signal.aborted) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
if (error instanceof SubagentRunError && error.output) {
|
|
||||||
yield {
|
|
||||||
requestId: request.requestId,
|
|
||||||
type: 'text',
|
|
||||||
delta: error.output
|
|
||||||
}
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
yield* ordinaryStream()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const eventStream = executionRequest.teamMode
|
const eventStream = executionRequest.teamMode
|
||||||
? runExpertTeam(
|
? runExpertTeam(
|
||||||
executionRequest,
|
executionRequest,
|
||||||
controller.signal,
|
controller.signal,
|
||||||
requestExecutionSpace
|
requestExecutionSpace
|
||||||
)
|
)
|
||||||
: executionRequest.expertId && !imageGeneration
|
: ordinaryStream()
|
||||||
? runSingleExpert(
|
|
||||||
executionRequest,
|
|
||||||
await assistantDatabase.getExpert(
|
|
||||||
executionRequest.expertId
|
|
||||||
),
|
|
||||||
'manual',
|
|
||||||
controller.signal,
|
|
||||||
requestExecutionSpace
|
|
||||||
)
|
|
||||||
: runSmartRoute()
|
|
||||||
for await (const agentEvent of splitTaggedReasoning(eventStream)) {
|
for await (const agentEvent of splitTaggedReasoning(eventStream)) {
|
||||||
if (agentEvent.type === 'done' || agentEvent.type === 'error') activeRequestLease.settling = true
|
if (agentEvent.type === 'done' || agentEvent.type === 'error') activeRequestLease.settling = true
|
||||||
const provenance = remoteSemanticProvenance(agentEvent)
|
const provenance = remoteSemanticProvenance(agentEvent)
|
||||||
|
|||||||
@@ -3035,7 +3035,7 @@ describe('SettingsPanel runtime files', () => {
|
|||||||
'自动选择 1 位专家'
|
'自动选择 1 位专家'
|
||||||
)
|
)
|
||||||
expect(screen.getByText(/未显式选择专家或团队时/)).toHaveTextContent(
|
expect(screen.getByText(/未显式选择专家或团队时/)).toHaveTextContent(
|
||||||
'子专家使用默认文本模型和已启用工具'
|
'专家提示词作为角色提示词,由当前会话的 Runtime 直接执行'
|
||||||
)
|
)
|
||||||
|
|
||||||
fireEvent.click(smartRouting)
|
fireEvent.click(smartRouting)
|
||||||
|
|||||||
@@ -867,7 +867,7 @@ export const settings = {
|
|||||||
'Automatically choose the expert role that best matches the question',
|
'Automatically choose the expert role that best matches the question',
|
||||||
enabled: 'Enable Smart Subagent routing',
|
enabled: 'Enable Smart Subagent routing',
|
||||||
help:
|
help:
|
||||||
'Off by default. When no expert or team is explicitly selected, GoodBuddy chooses one expert. The Subagent uses the default text model and enabled tools.'
|
'Off by default. When no expert or team is explicitly selected, GoodBuddy chooses one expert. Its prompt is applied as a role prompt on the conversation\'s current Runtime.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
appearance: {
|
appearance: {
|
||||||
|
|||||||
@@ -802,7 +802,7 @@ export const settings = {
|
|||||||
description: '按问题内容自动选择最匹配的专家角色',
|
description: '按问题内容自动选择最匹配的专家角色',
|
||||||
enabled: '启用 Subagent 智能路由',
|
enabled: '启用 Subagent 智能路由',
|
||||||
help:
|
help:
|
||||||
'默认关闭。未显式选择专家或团队时,自动选择 1 位专家;子专家使用默认文本模型和已启用工具。'
|
'默认关闭。未显式选择专家或团队时,自动选择 1 位专家;专家提示词作为角色提示词,由当前会话的 Runtime 直接执行。'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
appearance: {
|
appearance: {
|
||||||
|
|||||||
Reference in New Issue
Block a user