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:
@@ -8,6 +8,7 @@ import {
|
||||
} from './model-tool-provider'
|
||||
import { ModelAgentRuntime } from './model-runtime'
|
||||
import type { RuntimeEvent } from './runtime'
|
||||
import { SubagentScheduler } from '../assistant/subagent-scheduler'
|
||||
|
||||
const toolPng = Buffer.from([
|
||||
0x89, 0x50, 0x4e, 0x47,
|
||||
@@ -2009,10 +2010,10 @@ describe('ModelAgentRuntime', () => {
|
||||
|
||||
expect(fetcher).toHaveBeenCalledTimes(2)
|
||||
expect(toolProvider.listTools).toHaveBeenCalledWith(
|
||||
{
|
||||
expect.objectContaining({
|
||||
conversationId: 'conversation-tools',
|
||||
workMode: 'execute'
|
||||
},
|
||||
}),
|
||||
expect.any(AbortSignal)
|
||||
)
|
||||
const firstBody = JSON.parse(
|
||||
@@ -2058,25 +2059,26 @@ describe('ModelAgentRuntime', () => {
|
||||
expect(authorize).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
scopeKey: 'model:builtin:workspace_read_text'
|
||||
})
|
||||
}),
|
||||
expect.any(AbortSignal)
|
||||
)
|
||||
expect(toolProvider.getApproval).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ name: 'workspace_read_text' }),
|
||||
{ path: 'README.md' },
|
||||
expect.any(String),
|
||||
{
|
||||
expect.objectContaining({
|
||||
conversationId: 'conversation-tools',
|
||||
workMode: 'execute'
|
||||
}
|
||||
})
|
||||
)
|
||||
expect(toolProvider.callTool).toHaveBeenCalledWith(
|
||||
'workspace_read_text',
|
||||
{ path: 'README.md' },
|
||||
expect.any(AbortSignal),
|
||||
{
|
||||
expect.objectContaining({
|
||||
conversationId: 'conversation-tools',
|
||||
workMode: 'execute'
|
||||
}
|
||||
})
|
||||
)
|
||||
expect(
|
||||
events
|
||||
@@ -2124,6 +2126,150 @@ describe('ModelAgentRuntime', () => {
|
||||
expect(toolProvider.dispose).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('delegates one programming Subagent that can run a real project command without recursion', async () => {
|
||||
const command =
|
||||
process.platform === 'win32'
|
||||
? "[Console]::Write('child-ok')"
|
||||
: 'printf child-ok'
|
||||
const responses = [
|
||||
{
|
||||
model: 'qwen3',
|
||||
choices: [{
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: null,
|
||||
tool_calls: [{
|
||||
id: 'call-delegate',
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'subagent_delegate',
|
||||
arguments: JSON.stringify({
|
||||
task: '运行一个命令确认子代理执行能力'
|
||||
})
|
||||
}
|
||||
}]
|
||||
}
|
||||
}],
|
||||
usage: { prompt_tokens: 10, completion_tokens: 4 }
|
||||
},
|
||||
{
|
||||
model: 'qwen3',
|
||||
choices: [{
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: null,
|
||||
tool_calls: [{
|
||||
id: 'call-child-process',
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'process_execute',
|
||||
arguments: JSON.stringify({ command })
|
||||
}
|
||||
}]
|
||||
}
|
||||
}],
|
||||
usage: { prompt_tokens: 12, completion_tokens: 5 }
|
||||
},
|
||||
{
|
||||
model: 'qwen3',
|
||||
choices: [{
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: '子代理已验证 child-ok。'
|
||||
}
|
||||
}],
|
||||
usage: { prompt_tokens: 20, completion_tokens: 6 }
|
||||
},
|
||||
{
|
||||
model: 'qwen3',
|
||||
choices: [{
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: '父模型已综合子代理结果。'
|
||||
}
|
||||
}],
|
||||
usage: { prompt_tokens: 25, completion_tokens: 7 }
|
||||
}
|
||||
]
|
||||
const fetcher = vi.fn<typeof fetch>(async () =>
|
||||
Response.json(responses.shift())
|
||||
)
|
||||
const scheduler = new SubagentScheduler({
|
||||
concurrency: 3,
|
||||
queueLimit: 20,
|
||||
timeoutMs: 10_000
|
||||
})
|
||||
const runtime = new ModelAgentRuntime({
|
||||
baseUrl: 'http://127.0.0.1:11434/v1',
|
||||
model: 'qwen3',
|
||||
protocol: 'openai-chat-completions',
|
||||
authentication: 'none',
|
||||
fetcher,
|
||||
defaultWorkspace: process.cwd(),
|
||||
directModelSubagentScheduler: scheduler
|
||||
})
|
||||
const events: RuntimeEvent[] = []
|
||||
|
||||
for await (const event of runtime.run(
|
||||
{
|
||||
requestId: 'a431666e-5ec8-45e6-beb4-654132eed177',
|
||||
conversationId: 'conversation-direct-subagent',
|
||||
prompt: '委派子代理验证进程能力',
|
||||
workMode: 'execute'
|
||||
},
|
||||
new AbortController().signal,
|
||||
async () => 'once'
|
||||
)) {
|
||||
events.push(event)
|
||||
}
|
||||
|
||||
expect(fetcher).toHaveBeenCalledTimes(4)
|
||||
const parentTools = (
|
||||
JSON.parse(
|
||||
fetcher.mock.calls[0]?.[1]?.body as string
|
||||
) as {
|
||||
tools: Array<{ function: { name: string } }>
|
||||
}
|
||||
).tools.map((tool) => tool.function.name)
|
||||
const childTools = (
|
||||
JSON.parse(
|
||||
fetcher.mock.calls[1]?.[1]?.body as string
|
||||
) as {
|
||||
tools: Array<{ function: { name: string } }>
|
||||
}
|
||||
).tools.map((tool) => tool.function.name)
|
||||
expect(parentTools).toContain('subagent_delegate')
|
||||
expect(parentTools).toContain('process_execute')
|
||||
expect(childTools).toContain('process_execute')
|
||||
expect(childTools).not.toContain('subagent_delegate')
|
||||
expect(
|
||||
events
|
||||
.filter((event) => event.type === 'subagent')
|
||||
.map((event) => event.state)
|
||||
).toEqual(['queued', 'running', 'completed'])
|
||||
expect(
|
||||
events
|
||||
.flatMap(
|
||||
(event) =>
|
||||
event.type === 'tool' &&
|
||||
event.callId.includes('call-child-process')
|
||||
? [event.state]
|
||||
: []
|
||||
)
|
||||
).toEqual(['pending', 'running', 'completed'])
|
||||
expect(
|
||||
events.some(
|
||||
(event) =>
|
||||
event.type === 'text' &&
|
||||
event.delta.includes('父模型已综合')
|
||||
)
|
||||
).toBe(true)
|
||||
|
||||
await runtime.dispose()
|
||||
scheduler.dispose()
|
||||
await scheduler.waitForIdle()
|
||||
})
|
||||
|
||||
it('compresses complete earlier tool rounds before a later Agent model call', async () => {
|
||||
const firstToolResult = `first-result-${'旧'.repeat(5_000)}`
|
||||
const secondToolResult = `second-result-${'新'.repeat(5_000)}`
|
||||
@@ -3045,11 +3191,11 @@ describe('ModelAgentRuntime', () => {
|
||||
}
|
||||
|
||||
expect(toolProvider.listTools).toHaveBeenCalledWith(
|
||||
{
|
||||
expect.objectContaining({
|
||||
conversationId: 'conversation-knowledge-ask',
|
||||
workMode: 'ask',
|
||||
knowledgeCapabilityToken: 'main-only-token'
|
||||
},
|
||||
}),
|
||||
expect.any(AbortSignal)
|
||||
)
|
||||
expect(toolProvider.callTool).toHaveBeenCalledWith(
|
||||
|
||||
Reference in New Issue
Block a user