refactor(agents): share the agent-host tool set and keep connection errors English

- AGENT_HOST_TOOL_NAMES in tool-names.ts is now the single list that
  config.ts declares as coreTools and ToolRegistry.isToolDisabled lets
  through, so the declared and runnable sets cannot drift apart.
- The agent-host connection route's eleven error strings were Chinese
  and reached clients verbatim via { error: error.message }. They are
  now English, matching the convention #12854 set for wire reasons
  (review-bot residual 1).
This commit is contained in:
yiliang114 2026-10-01 21:39:16 +09:00
parent e2178a163a
commit 6e1ba96b18
4 changed files with 38 additions and 20 deletions

View file

@ -31,6 +31,7 @@ import {
type LspClient,
type ToolName,
type ToolInvocationGuard,
AGENT_HOST_TOOL_NAMES,
ToolNames,
NativeLspClient,
createDebugLogger,
@ -2873,11 +2874,7 @@ export async function loadCliConfig(
};
if (agentHostReadOnly) {
configParams.coreTools = [
ToolNames.READ_FILE,
ToolNames.GREP,
ToolNames.LS,
];
configParams.coreTools = [...AGENT_HOST_TOOL_NAMES];
configParams.disableAllHooks = true;
configParams.mcpServers = {};
configParams.topTierMcpServers = undefined;

View file

@ -5,7 +5,7 @@ import { isLoopbackBind } from '../loopback-binds.js';
import type { WorkspaceRuntime } from '../workspace-registry.js';
function serverUrl(value: unknown, allowHttp: boolean): string {
if (typeof value !== 'string') throw new Error('请填写服务地址。');
if (typeof value !== 'string') throw new Error('A server URL is required.');
const url = new URL(value);
if (
url.username ||
@ -19,7 +19,7 @@ function serverUrl(value: unknown, allowHttp: boolean): string {
))
) {
throw new Error(
'请使用 HTTPS;可信演示网络可显式允许 HTTP。地址不能包含凭证或查询参数。',
'Use HTTPS; HTTP is allowed only when explicitly enabled for a trusted network. The URL must not contain credentials, a query, or a fragment.',
);
}
return url.toString().replace(/\/+$/, '');
@ -82,7 +82,7 @@ export function registerAgentHostConnectionRoutes(
!input.enrollmentToken ||
input.provider !== 'qwen'
) {
throw new Error('接入参数不完整。');
throw new Error('Missing connection parameters.');
}
if (!isCurrent(req, res, runtime)) return;
const { startAgentHostConnection } = await import(
@ -106,7 +106,9 @@ export function registerAgentHostConnectionRoutes(
} catch (error) {
res
.status(400)
.json({ error: error instanceof Error ? error.message : '接入失败。' });
.json({
error: error instanceof Error ? error.message : 'Connection failed.',
});
}
});
@ -130,7 +132,9 @@ export function registerAgentHostConnectionRoutes(
!input.remoteToken.trim() ||
input.provider !== 'qwen'
)
throw new Error('请填写远程服务凭证、远程执行目录和执行程序。');
throw new Error(
'Remote server credential, remote workspace, and provider are required.',
);
const endpoint = `${remote}/workspaces/${encodeURIComponent(input.remoteCwd)}/agent/hosts`;
const request = async (path: string, body?: unknown) => {
const response = await fetch(`${endpoint}${path}`, {
@ -145,13 +149,15 @@ export function registerAgentHostConnectionRoutes(
});
if (response.status === 404)
throw new Error(
'远程服务不支持在线主机接入,或执行目录未注册。请升级并启用协作功能,确认远程工作区后重试。',
'The remote server does not support Agent Host enrollment, or the workspace is not registered. Upgrade it, enable agent collaboration, confirm the remote workspace, and retry.',
);
if (response.status === 401 || response.status === 403)
throw new Error('远程凭证无效,或远程项目尚未授权。');
throw new Error(
'The remote credential is invalid, or the remote workspace is not authorized.',
);
if (!response.ok)
throw new Error(
`远程接入失败(${response.status}),请检查远程服务日志及协调端回连地址。`,
`Remote connection failed (${response.status}). Check the remote server log and the coordinator callback URL.`,
);
return (await response.json()) as {
protocol?: number;
@ -164,7 +170,9 @@ export function registerAgentHostConnectionRoutes(
service.protocol !== 1 ||
!service.providers?.includes(input.provider)
)
throw new Error('远程服务不支持所选执行程序或接入协议。');
throw new Error(
'The remote server does not support the selected provider or connection protocol.',
);
if (!isCurrent(req, res, runtime)) return;
// The enrollment token below crosses both legs; mirror the Host's
// warning on this side, which is the one holding the secret.
@ -182,11 +190,15 @@ export function registerAgentHostConnectionRoutes(
allowHttp: input.allowHttp === true,
});
if (!isCurrent(req, res, runtime)) return;
if (!result.connected) throw new Error('远程服务未确认接入。');
if (!result.connected)
throw new Error('The remote server did not confirm the connection.');
res.json(result);
} catch (error) {
res.status(400).json({
error: error instanceof Error ? error.message : '无法连接远程服务。',
error:
error instanceof Error
? error.message
: 'Cannot reach the remote server.',
});
}
});

View file

@ -100,6 +100,17 @@ export const ToolNames = {
THREAD_READ: 'thread_read',
} as const;
/**
* The only tools an agent-host session declares and runs. The Host is
* read-only and auto-rejects permission prompts, so anything outside this
* set would be advertised to the model yet fail every invocation.
*/
export const AGENT_HOST_TOOL_NAMES: readonly string[] = [
ToolNames.READ_FILE,
ToolNames.GREP,
ToolNames.LS,
];
/**
* Tool display name constants to avoid circular dependencies.
* These constants are used across multiple files and should be kept in sync

View file

@ -22,7 +22,7 @@ import { McpClientManager } from './mcp-client-manager.js';
import { DiscoveredMCPTool } from './mcp-tool.js';
import { parse } from 'shell-quote';
import { ToolErrorType } from './tool-error.js';
import { ToolNames } from './tool-names.js';
import { AGENT_HOST_TOOL_NAMES, ToolNames } from './tool-names.js';
import { safeJsonStringify } from '../utils/safeJsonStringify.js';
import type { EventEmitter } from 'node:events';
import { createDebugLogger } from '../utils/debugLogger.js';
@ -325,9 +325,7 @@ export class ToolRegistry {
): boolean {
if (
this.config.getSessionSourceType?.() === 'agent-host' &&
name !== ToolNames.READ_FILE &&
name !== ToolNames.GREP &&
name !== ToolNames.LS
!AGENT_HOST_TOOL_NAMES.includes(name)
) {
return true;
}