feat: add direct model programming agents
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build Linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build Linux x64 Agent (push) Canceled after 0s
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build Linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build Linux x64 Agent (push) Canceled after 0s
Direct model conversations can now execute bounded platform-native processes and delegate one level of programming work without duplicating tools in Agent Runtimes. Process and Subagent activity follows existing workspace, mode, approval, cancellation, and lifecycle boundaries, with chat attribution and complete feature documentation. Remove the static Renderer bundle baseline gate and its build-only manifests; production builds continue to compile all processes and verify the control-plane installer. Also include the pending documentation link normalization. Release note: 直连模型现在可在 Execute 模式运行本机命令,并可委派编程 Subagent 完成修改、测试和复核;构建流程不再因静态 Renderer 包体基线阻塞。
This commit is contained in:
@@ -9236,6 +9236,74 @@ describe('App', () => {
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('records direct-model child runs without adding Assistant Tasks', async () => {
|
||||
render(<App />)
|
||||
fireEvent.change(await screen.findByLabelText('向 GoodBuddy 提问'), {
|
||||
target: { value: '修复并验证聚焦变更' }
|
||||
})
|
||||
fireEvent.click(screen.getByLabelText('发送'))
|
||||
await waitFor(() => expect(run).toHaveBeenCalledOnce())
|
||||
const request = run.mock.calls[0]?.[0]
|
||||
if (!request) {
|
||||
throw new Error('Missing request')
|
||||
}
|
||||
const childRunId =
|
||||
'00000000-0000-4000-8000-000000000613'
|
||||
|
||||
act(() => {
|
||||
agentListener?.({
|
||||
requestId: request.requestId,
|
||||
type: 'subagent',
|
||||
childTaskId: childRunId,
|
||||
actor: {
|
||||
kind: 'direct-model',
|
||||
label: '编程 Subagent'
|
||||
},
|
||||
routingMode: 'native',
|
||||
workMode: 'execute',
|
||||
state: 'completed',
|
||||
reason: '修复并验证聚焦变更',
|
||||
output: '验证通过'
|
||||
})
|
||||
})
|
||||
|
||||
const region = await screen.findByLabelText('子代理状态')
|
||||
expect(
|
||||
within(region).getByText('编程 Subagent', {
|
||||
selector: 'strong'
|
||||
})
|
||||
).toBeInTheDocument()
|
||||
expect(within(region).getByText('直连模型 · Execute'))
|
||||
.toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
const persistedActivities = vi
|
||||
.mocked(api.conversations.saveLocal)
|
||||
.mock.calls.flatMap(([batch]) =>
|
||||
batch.flatMap((conversation) =>
|
||||
conversation.messages.flatMap(
|
||||
(message) => message.subagents ?? []
|
||||
)
|
||||
)
|
||||
)
|
||||
expect(persistedActivities).toContainEqual(
|
||||
expect.objectContaining({
|
||||
childTaskId: childRunId,
|
||||
actor: {
|
||||
kind: 'direct-model',
|
||||
label: '编程 Subagent'
|
||||
},
|
||||
workMode: 'execute',
|
||||
output: '验证通过'
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByLabelText('切换助手工作栏'))
|
||||
expect(
|
||||
await screen.findByText('明确创建的任务会显示在这里。')
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('fails an unterminated subagent when the parent reports done', async () => {
|
||||
render(<App />)
|
||||
fireEvent.change(await screen.findByLabelText('向 GoodBuddy 提问'), {
|
||||
|
||||
+77
-49
@@ -4506,6 +4506,18 @@ function App(): React.JSX.Element {
|
||||
}
|
||||
})
|
||||
} else if (event.type === 'subagent') {
|
||||
const actor =
|
||||
'actor' in event
|
||||
? event.actor
|
||||
: {
|
||||
kind: 'expert' as const,
|
||||
expertId: event.expertId,
|
||||
expertName: event.expertName
|
||||
}
|
||||
const actorLabel =
|
||||
actor.kind === 'direct-model'
|
||||
? tRef.current('chat.subagents.directModelLabel')
|
||||
: actor.expertName
|
||||
const childStatus = event.state
|
||||
const completedAt =
|
||||
event.state === 'completed' ||
|
||||
@@ -4513,46 +4525,48 @@ function App(): React.JSX.Element {
|
||||
event.state === 'cancelled'
|
||||
? new Date().toISOString()
|
||||
: undefined
|
||||
setAssistantTasks((current) => {
|
||||
const existing = current.find(
|
||||
(task) => task.id === event.childTaskId
|
||||
)
|
||||
const childTask: AssistantTask = {
|
||||
id: event.childTaskId,
|
||||
projectId: run.projectId,
|
||||
conversationId: run.conversationId,
|
||||
parentTaskId: event.requestId,
|
||||
expertId: event.expertId,
|
||||
routingMode:
|
||||
event.routingMode === 'native'
|
||||
? undefined
|
||||
: event.routingMode,
|
||||
title: event.expertName,
|
||||
instructions:
|
||||
event.reason ??
|
||||
tRef.current('chat.subagents.fallbackTask', {
|
||||
name: event.expertName
|
||||
}),
|
||||
origin: 'subagent',
|
||||
status: childStatus,
|
||||
createdAt:
|
||||
existing?.createdAt ?? new Date().toISOString(),
|
||||
startedAt:
|
||||
event.state === 'running'
|
||||
? existing?.startedAt ?? new Date().toISOString()
|
||||
: existing?.startedAt,
|
||||
completedAt: completedAt ?? existing?.completedAt,
|
||||
error: event.error
|
||||
}
|
||||
return existing
|
||||
? current.map((task) =>
|
||||
task.id === event.childTaskId ? childTask : task
|
||||
)
|
||||
: [...current, childTask].slice(
|
||||
0,
|
||||
maximumConversationSubagentActivities
|
||||
)
|
||||
})
|
||||
if (actor.kind === 'expert') {
|
||||
setAssistantTasks((current) => {
|
||||
const existing = current.find(
|
||||
(task) => task.id === event.childTaskId
|
||||
)
|
||||
const childTask: AssistantTask = {
|
||||
id: event.childTaskId,
|
||||
projectId: run.projectId,
|
||||
conversationId: run.conversationId,
|
||||
parentTaskId: event.requestId,
|
||||
expertId: actor.expertId,
|
||||
routingMode:
|
||||
event.routingMode === 'native'
|
||||
? undefined
|
||||
: event.routingMode,
|
||||
title: actor.expertName,
|
||||
instructions:
|
||||
event.reason ??
|
||||
tRef.current('chat.subagents.fallbackTask', {
|
||||
name: actor.expertName
|
||||
}),
|
||||
origin: 'subagent',
|
||||
status: childStatus,
|
||||
createdAt:
|
||||
existing?.createdAt ?? new Date().toISOString(),
|
||||
startedAt:
|
||||
event.state === 'running'
|
||||
? existing?.startedAt ?? new Date().toISOString()
|
||||
: existing?.startedAt,
|
||||
completedAt: completedAt ?? existing?.completedAt,
|
||||
error: event.error
|
||||
}
|
||||
return existing
|
||||
? current.map((task) =>
|
||||
task.id === event.childTaskId ? childTask : task
|
||||
)
|
||||
: [...current, childTask].slice(
|
||||
0,
|
||||
maximumConversationSubagentActivities
|
||||
)
|
||||
})
|
||||
}
|
||||
if (event.runtimeCallId) {
|
||||
setActivityRecords((current) =>
|
||||
current.filter(
|
||||
@@ -4570,13 +4584,20 @@ function App(): React.JSX.Element {
|
||||
requestId: event.requestId,
|
||||
callId: event.childTaskId,
|
||||
kind: 'subagent',
|
||||
title: event.expertName,
|
||||
title: actorLabel,
|
||||
detail: [
|
||||
event.routingMode === 'smart'
|
||||
? tRef.current('chat.subagents.smart')
|
||||
: event.routingMode === 'native'
|
||||
? tRef.current('chat.subagents.native')
|
||||
: tRef.current('chat.subagents.manual'),
|
||||
actor.kind === 'direct-model'
|
||||
? tRef.current('chat.subagents.directModel')
|
||||
: event.routingMode === 'smart'
|
||||
? tRef.current('chat.subagents.smart')
|
||||
: event.routingMode === 'native'
|
||||
? tRef.current('chat.subagents.native')
|
||||
: tRef.current('chat.subagents.manual'),
|
||||
actor.kind === 'direct-model' && event.workMode
|
||||
? event.workMode === 'execute'
|
||||
? 'Execute'
|
||||
: 'Ask'
|
||||
: undefined,
|
||||
event.reason,
|
||||
event.error
|
||||
]
|
||||
@@ -4594,17 +4615,24 @@ function App(): React.JSX.Element {
|
||||
(subagent) =>
|
||||
subagent.childTaskId === event.childTaskId
|
||||
)
|
||||
const subagent: SubagentActivity = {
|
||||
const commonSubagent = {
|
||||
childTaskId: event.childTaskId,
|
||||
expertId: event.expertId,
|
||||
expertName: event.expertName,
|
||||
routingMode: event.routingMode,
|
||||
runtimeCallId: event.runtimeCallId,
|
||||
workMode: event.workMode,
|
||||
state: event.state,
|
||||
reason: event.reason,
|
||||
output: event.output,
|
||||
error: event.error
|
||||
}
|
||||
const subagent: SubagentActivity =
|
||||
'actor' in event
|
||||
? { ...commonSubagent, actor: event.actor }
|
||||
: {
|
||||
...commonSubagent,
|
||||
expertId: event.expertId,
|
||||
expertName: event.expertName
|
||||
}
|
||||
if (index >= 0) {
|
||||
subagents[index] = subagent
|
||||
} else if (
|
||||
|
||||
@@ -353,6 +353,58 @@ describe('ChatTimeline', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('labels direct-model subagents with their inherited work mode', () => {
|
||||
const messages: Message[] = [
|
||||
{
|
||||
id: 'assistant-message',
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
createdAt: 1_775_000_000_000,
|
||||
state: 'streaming',
|
||||
subagents: [
|
||||
{
|
||||
childTaskId: '00000000-0000-4000-8000-000000000131',
|
||||
actor: {
|
||||
kind: 'direct-model',
|
||||
label: '编程 Subagent'
|
||||
},
|
||||
routingMode: 'native',
|
||||
workMode: 'execute',
|
||||
state: 'running',
|
||||
reason: '修复并验证聚焦变更'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
render(
|
||||
<ChatTimeline
|
||||
artifactById={new Map()}
|
||||
conversationId="conversation-1"
|
||||
hiddenMessageCount={0}
|
||||
isUnusedConversation={false}
|
||||
locale="zh-CN"
|
||||
messageStartIndex={0}
|
||||
messages={messages}
|
||||
{...callbacks}
|
||||
retryContent=""
|
||||
totalMessageCount={messages.length}
|
||||
/>
|
||||
)
|
||||
|
||||
const region = screen.getByLabelText('子代理状态')
|
||||
expect(
|
||||
within(region).getByText('编程 Subagent', {
|
||||
selector: 'strong'
|
||||
})
|
||||
).toBeInTheDocument()
|
||||
expect(within(region).getByText('直连模型 · Execute'))
|
||||
.toBeInTheDocument()
|
||||
expect(
|
||||
within(region).getByText('修复并验证聚焦变更')
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('uses one live source per event without announcing historical tool rows', () => {
|
||||
const messages: Message[] = [
|
||||
{
|
||||
|
||||
@@ -264,12 +264,30 @@ const SubagentStatusCard = memo(function SubagentStatusCard({
|
||||
: subagent.state === 'queued'
|
||||
? Clock3
|
||||
: XCircle
|
||||
const actor =
|
||||
'actor' in subagent
|
||||
? subagent.actor
|
||||
: {
|
||||
kind: 'expert' as const,
|
||||
expertId: subagent.expertId,
|
||||
expertName: subagent.expertName
|
||||
}
|
||||
const actorLabel =
|
||||
actor.kind === 'direct-model'
|
||||
? t('chat.subagents.directModelLabel')
|
||||
: actor.expertName
|
||||
const source =
|
||||
subagent.routingMode === 'smart'
|
||||
actor.kind === 'direct-model'
|
||||
? t('chat.subagents.directModel')
|
||||
: subagent.routingMode === 'smart'
|
||||
? t('chat.subagents.smart')
|
||||
: subagent.routingMode === 'native'
|
||||
? t('chat.subagents.native')
|
||||
: t('chat.subagents.manual')
|
||||
const sourceAndMode =
|
||||
actor.kind === 'direct-model' && subagent.workMode
|
||||
? `${source} · ${subagent.workMode === 'execute' ? 'Execute' : 'Ask'}`
|
||||
: source
|
||||
|
||||
return (
|
||||
<details
|
||||
@@ -283,7 +301,7 @@ const SubagentStatusCard = memo(function SubagentStatusCard({
|
||||
<Bot aria-hidden="true" size={13} />
|
||||
{t('chat.subagents.badge')}
|
||||
</span>
|
||||
<strong>{subagent.expertName}</strong>
|
||||
<strong>{actorLabel}</strong>
|
||||
</span>
|
||||
{subagent.reason && (
|
||||
<span className="subagent-status-card__task">
|
||||
@@ -292,7 +310,7 @@ const SubagentStatusCard = memo(function SubagentStatusCard({
|
||||
</span>
|
||||
)}
|
||||
<small className="subagent-status-card__source">
|
||||
{source}
|
||||
{sourceAndMode}
|
||||
</small>
|
||||
</span>
|
||||
<span
|
||||
|
||||
@@ -4977,6 +4977,17 @@ describe('SettingsPanel runtime files', () => {
|
||||
expect(await screen.findByText('文件系统操作')).toBeInTheDocument()
|
||||
expect(screen.getByText('内置浏览器')).toBeInTheDocument()
|
||||
expect(screen.getByText('联网搜索')).toBeInTheDocument()
|
||||
expect(screen.getByText('编程能力')).toBeInTheDocument()
|
||||
const programmingToggle = screen.getByRole('button', {
|
||||
name: '展开工具组 编程能力'
|
||||
})
|
||||
fireEvent.click(programmingToggle)
|
||||
expect(screen.getByText('进程执行')).toBeInTheDocument()
|
||||
expect(screen.getByText('编程 Subagent')).toBeInTheDocument()
|
||||
expect(
|
||||
within(programmingToggle.closest('article')!)
|
||||
.queryByRole('switch')
|
||||
).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('switch', {
|
||||
name: '启用直连模型内置浏览器'
|
||||
|
||||
@@ -364,6 +364,8 @@ export const app = {
|
||||
subagents: {
|
||||
region: 'Subagent status',
|
||||
badge: 'Subagent',
|
||||
directModelLabel: 'Programming Subagent',
|
||||
directModel: 'Direct model',
|
||||
smart: 'GoodBuddy smart routing',
|
||||
manual: 'GoodBuddy selected expert',
|
||||
native: 'Native OpenCode',
|
||||
|
||||
@@ -347,6 +347,8 @@ export const app = {
|
||||
subagents: {
|
||||
region: '子代理状态',
|
||||
badge: '子代理',
|
||||
directModelLabel: '编程 Subagent',
|
||||
directModel: '直连模型',
|
||||
smart: 'GoodBuddy 智能路由',
|
||||
manual: 'GoodBuddy 指定专家',
|
||||
native: 'OpenCode 原生',
|
||||
|
||||
Reference in New Issue
Block a user