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

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:
mesalogo
2026-09-02 00:50:54 +08:00
parent d36889d9f6
commit b36d688900
49 changed files with 4791 additions and 1191 deletions
+50
View File
@@ -4,6 +4,7 @@ import {
builtInDefaultProjectSeedDescription,
builtInDefaultProjectSeedName,
conversationBranchInputSchema,
conversationSubagentActivitySchema,
conversationSnapshotSchema,
isUntouchedBuiltInDefaultProject,
localConversationSaveBatchSchema,
@@ -12,6 +13,7 @@ import {
projectExecutionSpaceSchema,
projectUpdateSchema
} from './assistant-contracts'
import { subagentEventSchema } from './contracts'
const untouchedProject: AssistantProject = {
id: '00000000-0000-4000-8000-000000000101',
@@ -287,4 +289,52 @@ describe('conversation persistence contracts', () => {
expect(localConversationSaveBatchSchema.parse(batch)).toEqual(batch)
})
it('keeps legacy expert activities', () => {
const legacy = {
childTaskId: '00000000-0000-4000-8000-000000000321',
expertId: '00000000-0000-4000-8000-000000000322',
expertName: '代码专家',
routingMode: 'manual',
state: 'completed'
}
expect(conversationSubagentActivitySchema.parse(legacy))
.toEqual(legacy)
})
it('stores a direct-model child run in the existing child task slot', () => {
const directModel = {
childTaskId: '00000000-0000-4000-8000-000000000325',
actor: {
kind: 'direct-model',
label: '编程 Subagent'
},
routingMode: 'native',
workMode: 'execute',
state: 'completed',
reason: '修复并验证聚焦变更',
output: '验证通过'
}
expect(
conversationSubagentActivitySchema.parse(directModel)
).toEqual(directModel)
expect(
conversationSubagentActivitySchema.safeParse({
...directModel,
childRunId: directModel.childTaskId
}).success
).toBe(false)
expect(
subagentEventSchema.parse({
requestId: '00000000-0000-4000-8000-000000000326',
type: 'subagent',
...directModel
})
).toEqual({
requestId: '00000000-0000-4000-8000-000000000326',
type: 'subagent',
...directModel
})
})
})
+42 -16
View File
@@ -196,26 +196,52 @@ export type ConversationMessageBlock = z.infer<
export const maximumConversationToolActivities = 100
export const maximumConversationSubagentActivities = 100
export const conversationSubagentActivitySchema = z
export const subagentActorSchema = z
.object({
childTaskId: assistantIdSchema,
expertId: assistantIdSchema,
expertName: z.string().trim().min(1).max(80),
routingMode: z.enum(['manual', 'smart', 'native']),
runtimeCallId: z.string().trim().min(1).max(256).optional(),
state: z.enum([
'queued',
'running',
'completed',
'failed',
'cancelled'
]),
reason: z.string().trim().min(1).max(240).optional(),
output: z.string().optional(),
error: z.string().trim().min(1).max(1_000).optional()
kind: z.literal('direct-model'),
label: z.literal('编程 Subagent')
})
.strict()
export type SubagentActor = z.infer<typeof subagentActorSchema>
const conversationSubagentActivityBaseSchema = z.object({
childTaskId: assistantIdSchema,
routingMode: z.enum(['manual', 'smart', 'native']),
runtimeCallId: z.string().trim().min(1).max(256).optional(),
workMode: workModeSchema.optional(),
state: z.enum([
'queued',
'running',
'completed',
'failed',
'cancelled'
]),
reason: z.string().trim().min(1).max(240).optional(),
output: z.string().optional(),
error: z.string().trim().min(1).max(1_000).optional()
})
export const legacyConversationSubagentActivitySchema =
conversationSubagentActivityBaseSchema
.extend({
expertId: assistantIdSchema,
expertName: z.string().trim().min(1).max(80)
})
.strict()
export const actorConversationSubagentActivitySchema =
conversationSubagentActivityBaseSchema
.extend({
actor: subagentActorSchema
})
.strict()
export const conversationSubagentActivitySchema = z.union([
legacyConversationSubagentActivitySchema,
actorConversationSubagentActivitySchema
])
export type ConversationSubagentActivity = z.infer<
typeof conversationSubagentActivitySchema
>
+46
View File
@@ -0,0 +1,46 @@
import { describe, expect, it } from 'vitest'
import {
builtinModelToolGroups,
builtinModelTools
} from './builtin-model-tools'
describe('built-in model tool catalog', () => {
it('uses unique tool names', () => {
const names = builtinModelTools.map((tool) => tool.name)
expect(new Set(names).size).toBe(names.length)
})
it('places every tool in exactly its declared group', () => {
const groupedNames = builtinModelToolGroups.flatMap((group) =>
group.tools.map((tool) => tool.name)
)
expect(groupedNames).toHaveLength(builtinModelTools.length)
expect(new Set(groupedNames).size).toBe(builtinModelTools.length)
for (const group of builtinModelToolGroups) {
expect(group.tools.every((tool) => tool.group === group.id)).toBe(true)
}
expect(
builtinModelToolGroups
.find((group) => group.id === 'programming')
?.tools.map((tool) => tool.name)
).toEqual(['process_execute', 'subagent_delegate'])
})
it('classifies programming tool access for mode filtering', () => {
const programmingAccess = Object.fromEntries(
builtinModelTools
.filter((tool) => tool.group === 'programming')
.map((tool) => [tool.name, tool.access])
)
expect(programmingAccess).toEqual({
process_execute: 'write',
subagent_delegate: 'read'
})
})
})
+24 -1
View File
@@ -3,7 +3,7 @@ export type BuiltinModelToolSummary = {
displayName: string
description: string
access: 'read' | 'write'
group: 'filesystem' | 'browser' | 'web'
group: 'filesystem' | 'browser' | 'web' | 'programming'
}
export const builtinModelTools = [
@@ -93,6 +93,22 @@ export const builtinModelTools = [
'通过 Exa 托管 MCP 读取公开 HTTP 或 HTTPS 网页的有界正文。',
access: 'read',
group: 'web'
},
{
name: 'process_execute',
displayName: '进程执行',
description:
'使用当前工作区和当前用户权限运行 PowerShell、Bash 或 Sh 命令。',
access: 'write',
group: 'programming'
},
{
name: 'subagent_delegate',
displayName: '编程 Subagent',
description:
'将聚焦任务委派给同一模型连接;继承当前工作模式和工作区。',
access: 'read',
group: 'programming'
}
] as const satisfies readonly BuiltinModelToolSummary[]
@@ -117,5 +133,12 @@ export const builtinModelToolGroups = [
description:
'启用后,直连模型可在 Ask 和 Execute 模式搜索并读取公开网页。',
tools: builtinModelTools.filter((tool) => tool.group === 'web')
},
{
id: 'programming',
name: '编程能力',
description:
'直连模型可运行项目命令,或将聚焦任务委派给继承当前模式的 Subagent。',
tools: builtinModelTools.filter((tool) => tool.group === 'programming')
}
] as const
+15 -8
View File
@@ -21,7 +21,8 @@ import {
conversationAttachmentSchema,
conversationHistoryMessageSchema,
conversationContextCompressionStateSchema,
conversationSubagentActivitySchema,
actorConversationSubagentActivitySchema,
legacyConversationSubagentActivitySchema,
legacyWorkModeSchema,
maximumConversationHistoryCharacters,
maximumConversationHistoryMessages,
@@ -1191,13 +1192,19 @@ export const approvalDecisionSchema = z.enum([
export type ApprovalDecision = z.infer<typeof approvalDecisionSchema>
export const subagentEventSchema =
conversationSubagentActivitySchema
.extend({
requestId: z.string().uuid(),
type: z.literal('subagent')
})
.strict()
const subagentEventFields = {
requestId: z.string().uuid(),
type: z.literal('subagent')
} as const
export const subagentEventSchema = z.union([
legacyConversationSubagentActivitySchema
.extend(subagentEventFields)
.strict(),
actorConversationSubagentActivitySchema
.extend(subagentEventFields)
.strict()
])
export type SubagentEvent = z.infer<typeof subagentEventSchema>