Skip to content

Commit 5035c95

Browse files
authored
feat(chat): add deepchat tool controls (#1353)
1 parent b47e5e0 commit 5035c95

37 files changed

Lines changed: 1866 additions & 98 deletions

File tree

src/main/lib/agentRuntime/systemEnvPromptBuilder.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ export interface BuildSystemEnvPromptOptions {
1212
agentsFilePath?: string
1313
}
1414

15+
export interface RuntimeCapabilitiesPromptOptions {
16+
hasYoBrowser?: boolean
17+
hasExec?: boolean
18+
hasProcess?: boolean
19+
}
20+
1521
function resolveModelDisplayName(providerId: string, modelId: string): string | undefined {
1622
try {
1723
const models = presenter.configPresenter?.getProviderModels?.(providerId) || []
@@ -86,14 +92,33 @@ async function readAgentsInstructions(sourcePath: string): Promise<string> {
8692
}
8793
}
8894

89-
export function buildRuntimeCapabilitiesPrompt(): string {
90-
return [
91-
'## Runtime Capabilities',
92-
'- YoBrowser tools are available for browser automation when needed.',
93-
'- Use exec(background: true) to start long-running terminal commands.',
94-
'- Use process(list|poll|log|write|kill|remove) to manage background terminal sessions.',
95-
'- Before launching another long-running command, prefer process action "list" to inspect existing sessions.'
96-
].join('\n')
95+
export function buildRuntimeCapabilitiesPrompt(
96+
options: RuntimeCapabilitiesPromptOptions = {
97+
hasYoBrowser: true,
98+
hasExec: true,
99+
hasProcess: true
100+
}
101+
): string {
102+
const lines = ['## Runtime Capabilities']
103+
104+
if (options.hasYoBrowser) {
105+
lines.push('- YoBrowser tools are available for browser automation when needed.')
106+
}
107+
if (options.hasExec) {
108+
lines.push('- Use exec(background: true) to start long-running terminal commands.')
109+
}
110+
if (options.hasProcess) {
111+
lines.push(
112+
'- Use process(list|poll|log|write|kill|remove) to manage background terminal sessions.'
113+
)
114+
}
115+
if (options.hasExec && options.hasProcess) {
116+
lines.push(
117+
'- Before launching another long-running command, prefer process action "list" to inspect existing sessions.'
118+
)
119+
}
120+
121+
return lines.length > 1 ? lines.join('\n') : ''
97122
}
98123

99124
export async function buildSystemEnvPrompt(

src/main/presenter/agentPresenter/tool/toolCallCenter.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77

88
export type ToolCallContext = {
99
enabledMcpTools?: string[]
10+
disabledAgentTools?: string[]
1011
chatMode?: 'agent' | 'acp agent'
1112
supportsVision?: boolean
1213
agentWorkspacePath?: string | null
@@ -24,7 +25,10 @@ export class ToolCallCenter {
2425
return this.toolPresenter.callTool(request)
2526
}
2627

27-
buildToolSystemPrompt(context: { conversationId?: string }): string {
28+
buildToolSystemPrompt(context: {
29+
conversationId?: string
30+
toolDefinitions?: MCPToolDefinition[]
31+
}): string {
2832
return this.toolPresenter.buildToolSystemPrompt(context)
2933
}
3034
}

src/main/presenter/deepchatAgentPresenter/index.ts

Lines changed: 110 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -245,9 +245,11 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
245245
try {
246246
const generationSettings = await this.getEffectiveSessionGenerationSettings(sessionId)
247247
const maxTokens = generationSettings.maxTokens
248+
const tools = await this.loadToolDefinitionsForSession(sessionId, projectDir)
248249
const baseSystemPrompt = await this.buildSystemPromptWithSkills(
249250
sessionId,
250-
generationSettings.systemPrompt
251+
generationSettings.systemPrompt,
252+
tools
251253
)
252254
const historyRecords = this.messageStore
253255
.getMessages(sessionId)
@@ -343,7 +345,8 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
343345
messageId: assistantMessageId,
344346
messages,
345347
projectDir,
346-
promptPreview: normalizedInput.text
348+
promptPreview: normalizedInput.text,
349+
tools
347350
})
348351
this.applyProcessResultStatus(sessionId, result)
349352
} catch (err) {
@@ -1190,9 +1193,12 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
11901193
this.setSessionStatus(sessionId, 'generating')
11911194
const generationSettings = await this.getEffectiveSessionGenerationSettings(sessionId)
11921195
const maxTokens = generationSettings.maxTokens
1196+
const projectDir = this.resolveProjectDir(sessionId)
1197+
const tools = await this.loadToolDefinitionsForSession(sessionId, projectDir)
11931198
const baseSystemPrompt = await this.buildSystemPromptWithSkills(
11941199
sessionId,
1195-
generationSettings.systemPrompt
1200+
generationSettings.systemPrompt,
1201+
tools
11961202
)
11971203
const summaryState = await this.resolveCompactionStateForResumeTurn({
11981204
sessionId,
@@ -1218,9 +1224,6 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
12181224
fallbackProtectedTurnCount: 1
12191225
}
12201226
)
1221-
const projectDir = this.resolveProjectDir(sessionId)
1222-
const tools = await this.loadToolDefinitionsForSession(sessionId, projectDir)
1223-
12241227
if (budgetToolCall?.id && budgetToolCall.name) {
12251228
const resumeBudget = this.fitResumeBudgetForToolCall({
12261229
resumeContext,
@@ -1278,7 +1281,8 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
12781281

12791282
private async buildSystemPromptWithSkills(
12801283
sessionId: string,
1281-
basePrompt: string
1284+
basePrompt: string,
1285+
toolDefinitions: MCPToolDefinition[]
12821286
): Promise<string> {
12831287
const normalizedBase = basePrompt?.trim() ?? ''
12841288
const state = this.runtimeState.get(sessionId)
@@ -1331,14 +1335,16 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
13311335

13321336
const normalizedAvailableSkills = this.normalizeSkillNames(availableSkillNames)
13331337
const normalizedActiveSkills = this.normalizeSkillNames(activeSkillNames)
1338+
const agentToolNames = this.getAgentToolNames(toolDefinitions)
13341339
const fingerprint = this.buildSystemPromptFingerprint({
13351340
providerId,
13361341
modelId,
13371342
workdir,
13381343
basePrompt: normalizedBase,
13391344
skillsEnabled,
13401345
availableSkillNames: normalizedAvailableSkills,
1341-
activeSkillNames: normalizedActiveSkills
1346+
activeSkillNames: normalizedActiveSkills,
1347+
toolSignature: this.buildToolSignature(toolDefinitions)
13421348
})
13431349

13441350
const cachedPrompt = this.systemPromptCache.get(sessionId)
@@ -1350,9 +1356,19 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
13501356
return cachedPrompt.prompt
13511357
}
13521358

1353-
const runtimePrompt = buildRuntimeCapabilitiesPrompt()
1359+
const runtimePrompt = buildRuntimeCapabilitiesPrompt({
1360+
hasYoBrowser: toolDefinitions.some(
1361+
(tool) => tool.source === 'agent' && tool.server.name === 'yobrowser'
1362+
),
1363+
hasExec: agentToolNames.has('exec'),
1364+
hasProcess: agentToolNames.has('process')
1365+
})
13541366
const skillsMetadataPrompt = skillsEnabled
1355-
? this.buildSkillsMetadataPrompt(normalizedAvailableSkills)
1367+
? this.buildSkillsMetadataPrompt(normalizedAvailableSkills, {
1368+
canListSkills: agentToolNames.has('skill_list'),
1369+
canControlSkills: agentToolNames.has('skill_control'),
1370+
canRunSkillScripts: agentToolNames.has('skill_run')
1371+
})
13561372
: ''
13571373

13581374
let skillsPrompt = ''
@@ -1390,7 +1406,10 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
13901406
let toolingPrompt = ''
13911407
if (this.toolPresenter) {
13921408
try {
1393-
toolingPrompt = this.toolPresenter.buildToolSystemPrompt({ conversationId: sessionId })
1409+
toolingPrompt = this.toolPresenter.buildToolSystemPrompt({
1410+
conversationId: sessionId,
1411+
toolDefinitions
1412+
})
13941413
} catch (error) {
13951414
console.warn(
13961415
`[DeepChatAgent] Failed to build tooling prompt for session ${sessionId}:`,
@@ -1424,21 +1443,53 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
14241443
.join('\n\n')
14251444
}
14261445

1427-
private buildSkillsMetadataPrompt(availableSkillNames: string[]): string {
1428-
const lines = [
1429-
'## Skills',
1430-
'If you may need specialized guidance, call `skill_list` first to inspect available skills and activation status.',
1431-
'After identifying a matching skill, call `skill_control` to activate or deactivate it before proceeding.'
1432-
]
1446+
private buildSkillsMetadataPrompt(
1447+
availableSkillNames: string[],
1448+
capabilities: {
1449+
canListSkills: boolean
1450+
canControlSkills: boolean
1451+
canRunSkillScripts: boolean
1452+
}
1453+
): string {
1454+
if (
1455+
!capabilities.canListSkills &&
1456+
!capabilities.canControlSkills &&
1457+
!capabilities.canRunSkillScripts
1458+
) {
1459+
return ''
1460+
}
1461+
1462+
const lines = ['## Skills']
1463+
let hasContent = false
1464+
1465+
if (capabilities.canListSkills) {
1466+
lines.push(
1467+
'If you may need specialized guidance, call `skill_list` to inspect available skills and activation status.'
1468+
)
1469+
hasContent = true
1470+
}
1471+
if (capabilities.canControlSkills) {
1472+
lines.push(
1473+
'After identifying a matching skill, call `skill_control` to activate or deactivate it before proceeding.'
1474+
)
1475+
hasContent = true
1476+
}
1477+
if (capabilities.canRunSkillScripts) {
1478+
lines.push(
1479+
'Use `skill_run` to execute bundled helper scripts from active skills when a skill provides them.'
1480+
)
1481+
hasContent = true
1482+
}
14331483

14341484
if (availableSkillNames.length > 0) {
14351485
lines.push('Installed skill names:')
14361486
lines.push(...availableSkillNames.map((name) => `- ${name}`))
1437-
} else {
1487+
hasContent = true
1488+
} else if (hasContent) {
14381489
lines.push('Installed skill names: (none)')
14391490
}
14401491

1441-
return lines.join('\n')
1492+
return hasContent ? lines.join('\n') : ''
14421493
}
14431494

14441495
private buildActiveSkillsPrompt(skillSections: string[]): string {
@@ -1467,6 +1518,7 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
14671518
skillsEnabled: boolean
14681519
availableSkillNames: string[]
14691520
activeSkillNames: string[]
1521+
toolSignature: string[]
14701522
}): string {
14711523
return JSON.stringify({
14721524
providerId: params.providerId,
@@ -1475,17 +1527,35 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
14751527
basePrompt: params.basePrompt,
14761528
skillsEnabled: params.skillsEnabled,
14771529
availableSkillNames: params.availableSkillNames,
1478-
activeSkillNames: params.activeSkillNames
1530+
activeSkillNames: params.activeSkillNames,
1531+
toolSignature: params.toolSignature
14791532
})
14801533
}
14811534

1535+
private getAgentToolNames(toolDefinitions: MCPToolDefinition[]): Set<string> {
1536+
return new Set(
1537+
toolDefinitions.filter((tool) => tool.source === 'agent').map((tool) => tool.function.name)
1538+
)
1539+
}
1540+
1541+
private buildToolSignature(toolDefinitions: MCPToolDefinition[]): string[] {
1542+
return toolDefinitions
1543+
.filter((tool) => tool.source === 'agent')
1544+
.map((tool) => `${tool.server.name}:${tool.function.name}`)
1545+
.sort((left, right) => left.localeCompare(right))
1546+
}
1547+
14821548
private buildLocalDayKey(now: Date): string {
14831549
const year = now.getFullYear()
14841550
const month = String(now.getMonth() + 1).padStart(2, '0')
14851551
const day = String(now.getDate()).padStart(2, '0')
14861552
return `${year}-${month}-${day}`
14871553
}
14881554

1555+
public invalidateSessionSystemPromptCache(sessionId: string): void {
1556+
this.invalidateSystemPromptCache(sessionId)
1557+
}
1558+
14891559
private invalidateSystemPromptCache(sessionId: string): void {
14901560
this.systemPromptCache.delete(sessionId)
14911561
}
@@ -2191,6 +2261,21 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
21912261
return true
21922262
})
21932263

2264+
if (!toolDefinition) {
2265+
const disabledAgentTools = this.getDisabledAgentTools(sessionId)
2266+
if (disabledAgentTools.includes(toolName)) {
2267+
return {
2268+
responseText: `Tool '${toolName}' is disabled for the current session.`,
2269+
isError: true
2270+
}
2271+
}
2272+
2273+
return {
2274+
responseText: `Tool '${toolName}' is no longer available in the current session.`,
2275+
isError: true
2276+
}
2277+
}
2278+
21942279
const request: MCPToolCall = {
21952280
id: toolCall.id || '',
21962281
type: 'function',
@@ -2250,6 +2335,7 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
22502335

22512336
try {
22522337
return await this.toolPresenter.getAllToolDefinitions({
2338+
disabledAgentTools: this.getDisabledAgentTools(sessionId),
22532339
chatMode: 'agent',
22542340
conversationId: sessionId,
22552341
agentWorkspacePath: projectDir
@@ -2260,6 +2346,10 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
22602346
}
22612347
}
22622348

2349+
private getDisabledAgentTools(sessionId: string): string[] {
2350+
return this.sqlitePresenter.newSessionsTable?.getDisabledAgentTools(sessionId) ?? []
2351+
}
2352+
22632353
private fitResumeBudgetForToolCall(params: {
22642354
resumeContext: ChatMessage[]
22652355
toolDefinitions: MCPToolDefinition[]

0 commit comments

Comments
 (0)