@steerable/agent-shell 0.6.51 → 0.6.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brand.d.ts +5 -0
- package/dist/goal-store.d.ts +43 -0
- package/dist/goal-store.js +155 -0
- package/dist/host/runtime.js +13 -3
- package/dist/host-tools.d.ts +1 -1
- package/dist/host-tools.js +6 -0
- package/dist/local-backend/router.js +17 -12
- package/dist/local-backend/subagent-profiles.js +4 -0
- package/dist/local-backend/task-service.d.ts +6 -0
- package/dist/local-backend/task-service.js +47 -2
- package/dist/local-executor.d.ts +14 -2
- package/dist/local-executor.js +53 -18
- package/dist/project-home.d.ts +1 -1
- package/dist/project-home.js +1 -1
- package/dist/project-registry.d.ts +4 -4
- package/dist/project-registry.js +2 -2
- package/dist/sidecar/boot.d.ts +5 -2
- package/dist/sidecar/boot.js +1 -0
- package/dist/sidecar/reverse-approval.d.ts +2 -0
- package/dist/sidecar/reverse-approval.js +2 -0
- package/dist/sidecar/reverse-tools.d.ts +6 -2
- package/dist/sidecar/reverse-tools.js +9 -1
- package/dist/sidecar/types.d.ts +10 -1
- package/dist/storage/write-lease-error.js +4 -1
- package/dist/tool-presentation.d.ts +23 -0
- package/dist/tool-presentation.js +266 -0
- package/dist/tool-router.d.ts +26 -8
- package/dist/tool-router.js +254 -18
- package/dist/workspace-search.d.ts +35 -0
- package/dist/workspace-search.js +186 -0
- package/package.json +4 -4
package/dist/project-home.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* 项目 = 名字 + 托管家目录 + 可选源文件夹。家目录默认建在
|
|
5
5
|
* `Documents/<应用名>/<项目名>/`(见 project-home.ts)。chat 通过
|
|
6
6
|
* `chat_sessions.project_id` 绑定项目;绑定后文件读写与命令执行被硬限制
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* 在家目录及其子目录内(见 tool-router.ts 的 ToolExecContext.projectRoot);
|
|
8
|
+
* 源文件夹及其子目录同样可写。
|
|
9
9
|
*
|
|
10
10
|
* 持久化在 userData/agent-projects.json。存储通过 {@link ProjectKvStore}
|
|
11
11
|
* 接口注入:main.ts 用 electron-store 实现,单测用内存实现——本模块不
|
|
@@ -18,8 +18,8 @@ export interface ProjectRecord {
|
|
|
18
18
|
/** 托管家目录(绝对路径)。默认 `Documents/<应用名>/<项目名>/`。 */
|
|
19
19
|
folderPath: string;
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* 附加源文件夹(已有代码目录)。与家目录一样可读写,范围含各自子目录。
|
|
22
|
+
* 不替代家目录。旧记录没有此字段,读取时按空列表处理。
|
|
23
23
|
*/
|
|
24
24
|
sourceFolders?: string[];
|
|
25
25
|
/**
|
package/dist/project-registry.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* 项目 = 名字 + 托管家目录 + 可选源文件夹。家目录默认建在
|
|
5
5
|
* `Documents/<应用名>/<项目名>/`(见 project-home.ts)。chat 通过
|
|
6
6
|
* `chat_sessions.project_id` 绑定项目;绑定后文件读写与命令执行被硬限制
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* 在家目录及其子目录内(见 tool-router.ts 的 ToolExecContext.projectRoot);
|
|
8
|
+
* 源文件夹及其子目录同样可写。
|
|
9
9
|
*
|
|
10
10
|
* 持久化在 userData/agent-projects.json。存储通过 {@link ProjectKvStore}
|
|
11
11
|
* 接口注入:main.ts 用 electron-store 实现,单测用内存实现——本模块不
|
package/dist/sidecar/boot.d.ts
CHANGED
|
@@ -9,10 +9,13 @@ export interface HostSidecarDeps {
|
|
|
9
9
|
/** W4-2 围栏:chatId → 项目家目录或无项目对话工作区。 */
|
|
10
10
|
resolveProjectRoot: (chatId: string) => Promise<string | null>;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* projectRoot 围栏约束;这些根只放宽 local_read_file 的读取范围。
|
|
12
|
+
* 额外只读根(会话附件目录、源文件夹)。只放宽 local_read_file,含子目录。
|
|
14
13
|
*/
|
|
15
14
|
resolveAdditionalReadRoots?: (chatId: string) => string[] | Promise<string[]>;
|
|
15
|
+
/**
|
|
16
|
+
* 额外可写根(项目源文件夹)。放宽写入、编辑和命令 cwd,含各自子目录。
|
|
17
|
+
*/
|
|
18
|
+
resolveAdditionalWriteRoots?: (chatId: string) => string[] | Promise<string[]>;
|
|
16
19
|
/** W4-1 审批反向通道处理器(宿主审批弹窗的应答入口)。 */
|
|
17
20
|
approvalHandler: ReturnType<typeof createApprovalBridge>['handler'];
|
|
18
21
|
/**
|
package/dist/sidecar/boot.js
CHANGED
|
@@ -214,6 +214,7 @@ export async function startHostSidecar(deps) {
|
|
|
214
214
|
// root so ToolRouter's cwd/path confinement applies.
|
|
215
215
|
resolveProjectRoot: deps.resolveProjectRoot,
|
|
216
216
|
resolveAdditionalReadRoots: deps.resolveAdditionalReadRoots,
|
|
217
|
+
resolveAdditionalWriteRoots: deps.resolveAdditionalWriteRoots,
|
|
217
218
|
}));
|
|
218
219
|
// W4-1: approval algebra reverse channel — the sidecar's
|
|
219
220
|
// ApprovalExecutor(HostApprover) asks here; the renderer modal answers
|
|
@@ -43,6 +43,7 @@ export function createApprovalBridge(deps) {
|
|
|
43
43
|
return deny('no renderer window available to approve the call');
|
|
44
44
|
}
|
|
45
45
|
const requestId = randomUUID();
|
|
46
|
+
const chatId = typeof p.chatId === 'string' && p.chatId ? p.chatId : undefined;
|
|
46
47
|
const prompt = {
|
|
47
48
|
requestId,
|
|
48
49
|
toolName,
|
|
@@ -52,6 +53,7 @@ export function createApprovalBridge(deps) {
|
|
|
52
53
|
mode: typeof p.mode === 'string' ? p.mode : 'other',
|
|
53
54
|
category: typeof p.category === 'string' ? p.category : toolName,
|
|
54
55
|
round: typeof p.round === 'number' ? p.round : 0,
|
|
56
|
+
...(chatId ? { chatId } : {}),
|
|
55
57
|
};
|
|
56
58
|
deps.onLog?.(`approval: prompting for ${toolName} (${prompt.mode}/${prompt.category})`);
|
|
57
59
|
return await new Promise((resolve) => {
|
|
@@ -21,9 +21,13 @@ export interface ReverseToolDeps {
|
|
|
21
21
|
*/
|
|
22
22
|
resolveProjectRoot?: (chatId: string) => Promise<string | null>;
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
* 这里只放宽 local_read_file 的读取范围。
|
|
24
|
+
* 额外只读根(会话附件目录、源文件夹)。只放宽读取,含各自子目录。
|
|
26
25
|
*/
|
|
27
26
|
resolveAdditionalReadRoots?: (chatId: string) => string[] | Promise<string[]>;
|
|
27
|
+
/**
|
|
28
|
+
* 额外可写根(项目源文件夹)。放宽写入、编辑和命令 cwd,含各自子目录。
|
|
29
|
+
* 附件目录不进这里。
|
|
30
|
+
*/
|
|
31
|
+
resolveAdditionalWriteRoots?: (chatId: string) => string[] | Promise<string[]>;
|
|
28
32
|
}
|
|
29
33
|
export declare function createToolInvokeHandler(deps: ReverseToolDeps): SidecarReverseHandler;
|
|
@@ -74,6 +74,9 @@ export function createToolInvokeHandler(deps) {
|
|
|
74
74
|
const additionalReadRoots = p.context?.chatId
|
|
75
75
|
? await Promise.resolve(deps.resolveAdditionalReadRoots?.(p.context.chatId) ?? [])
|
|
76
76
|
: [];
|
|
77
|
+
const additionalWriteRoots = p.context?.chatId
|
|
78
|
+
? await Promise.resolve(deps.resolveAdditionalWriteRoots?.(p.context.chatId) ?? [])
|
|
79
|
+
: [];
|
|
77
80
|
if (name === 'local_exec_shell') {
|
|
78
81
|
const classification = classifyShellCommand(String(toolArgs.command ?? ''));
|
|
79
82
|
if (classification.severity === 'critical') {
|
|
@@ -94,7 +97,11 @@ export function createToolInvokeHandler(deps) {
|
|
|
94
97
|
? normalizeToolPolicy(p.context.toolPolicy)
|
|
95
98
|
: null;
|
|
96
99
|
const toolPolicy = normalizedPolicy && normalizedPolicy.mode !== 'all' ? normalizedPolicy : null;
|
|
97
|
-
const execContext = projectRoot ||
|
|
100
|
+
const execContext = projectRoot ||
|
|
101
|
+
additionalReadRoots.length > 0 ||
|
|
102
|
+
additionalWriteRoots.length > 0 ||
|
|
103
|
+
p.context?.chatId ||
|
|
104
|
+
toolPolicy
|
|
98
105
|
? {
|
|
99
106
|
projectRoot,
|
|
100
107
|
chatId: p.context?.chatId,
|
|
@@ -102,6 +109,7 @@ export function createToolInvokeHandler(deps) {
|
|
|
102
109
|
// worktree_* 工具按它把任务绑定到来源对话。
|
|
103
110
|
...(toolPolicy ? { toolPolicy } : {}),
|
|
104
111
|
...(additionalReadRoots.length > 0 ? { additionalReadRoots } : {}),
|
|
112
|
+
...(additionalWriteRoots.length > 0 ? { additionalWriteRoots } : {}),
|
|
105
113
|
}
|
|
106
114
|
: undefined;
|
|
107
115
|
try {
|
package/dist/sidecar/types.d.ts
CHANGED
|
@@ -333,12 +333,15 @@ export interface SidecarChatStreamRequest {
|
|
|
333
333
|
* executor with the six-tool orchestration family (agent_spawn /
|
|
334
334
|
* agent_send / agent_wait / agent_close / agent_list / agent_interrupt).
|
|
335
335
|
* When delegation is also on (it is by default), both surfaces share one
|
|
336
|
-
* agent pool — one maxParallel budget, one lineage space.
|
|
336
|
+
* agent pool — one maxParallel budget, one lineage space.
|
|
337
|
+
* `childMaxRounds` / `childMaxToolErrors` default to the parent loop's
|
|
338
|
+
* limits. */
|
|
337
339
|
orchestration?: {
|
|
338
340
|
enabled?: boolean;
|
|
339
341
|
maxDepth?: number;
|
|
340
342
|
maxParallel?: number;
|
|
341
343
|
childMaxRounds?: number;
|
|
344
|
+
childMaxToolErrors?: number;
|
|
342
345
|
};
|
|
343
346
|
/**
|
|
344
347
|
* Sub-agent delegation (delegate-on-pool). ON BY DEFAULT on the sidecar —
|
|
@@ -451,6 +454,12 @@ export interface SidecarChatStreamRequest {
|
|
|
451
454
|
mode: 'host' | 'auto';
|
|
452
455
|
timeoutMs?: number;
|
|
453
456
|
storePath?: string;
|
|
457
|
+
/**
|
|
458
|
+
* Project home, source folders, and other configured write roots.
|
|
459
|
+
* File tools whose targets all sit inside these directories skip the
|
|
460
|
+
* host prompt. Independent of whether the OS exec sandbox is on.
|
|
461
|
+
*/
|
|
462
|
+
writableRoots?: string[];
|
|
454
463
|
};
|
|
455
464
|
}
|
|
456
465
|
/**
|
|
@@ -6,9 +6,12 @@ export function acquireWriteLeaseOrExit(acquire, loadElectron = () => import('el
|
|
|
6
6
|
}
|
|
7
7
|
catch (error) {
|
|
8
8
|
if (error instanceof StoreAlreadyOwnedError) {
|
|
9
|
+
console.error(`[bs] 本地数据库正在被另一个进程使用(${error.lockPath})。\n` +
|
|
10
|
+
'[bs] 再打开一次桌面应用会回到已经打开的窗口。\n' +
|
|
11
|
+
'[bs] 如果终端里的 pnpm dev:bs 占着数据库,请先在那个终端按 Ctrl+C 停掉,再重新打开。');
|
|
9
12
|
void loadElectron()
|
|
10
13
|
.then(({ app, dialog }) => {
|
|
11
|
-
dialog.showErrorBox('
|
|
14
|
+
dialog.showErrorBox('无法再开一个', '再打开一次应用时,会回到已经打开的窗口。\n\n这次没有回到那个窗口,是因为本地数据库正被另一个进程占用。如果终端里还在跑 pnpm dev:bs,先停掉它,再重新打开。');
|
|
12
15
|
app.exit(1);
|
|
13
16
|
})
|
|
14
17
|
.catch(() => exitProcess(1));
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一等工具的展示声明。
|
|
3
|
+
*
|
|
4
|
+
* 卡片种类和标题从这里按工具名取,不从名字前缀猜测。
|
|
5
|
+
* 未声明的名字(MCP、插件、场景包)落到 generic / other,`declared: false`。
|
|
6
|
+
* 宿主在 executed_actions 上盖 `view`,卡片读这份声明。
|
|
7
|
+
*/
|
|
8
|
+
export declare const TOOL_CARDS: readonly ["generic", "terminal", "diff", "search", "read", "web"];
|
|
9
|
+
export type ToolCard = (typeof TOOL_CARDS)[number];
|
|
10
|
+
export declare const TOOL_KINDS: readonly ["read", "edit", "delete", "move", "search", "execute", "fetch", "other"];
|
|
11
|
+
export type ToolKind = (typeof TOOL_KINDS)[number];
|
|
12
|
+
export interface ToolCallView {
|
|
13
|
+
card: ToolCard;
|
|
14
|
+
kind: ToolKind;
|
|
15
|
+
title: string;
|
|
16
|
+
/** 名字在声明表里。false 表示通用回退,不是从名字前缀推断出来的。 */
|
|
17
|
+
declared: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 取一次调用的展示。`result` 缺省时只根据参数出标题;结果到达后用同一声明补计数和状态。
|
|
21
|
+
*/
|
|
22
|
+
export declare function presentToolCall(name: string, args: unknown, result?: unknown): ToolCallView;
|
|
23
|
+
export declare function declaredToolNames(): readonly string[];
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一等工具的展示声明。
|
|
3
|
+
*
|
|
4
|
+
* 卡片种类和标题从这里按工具名取,不从名字前缀猜测。
|
|
5
|
+
* 未声明的名字(MCP、插件、场景包)落到 generic / other,`declared: false`。
|
|
6
|
+
* 宿主在 executed_actions 上盖 `view`,卡片读这份声明。
|
|
7
|
+
*/
|
|
8
|
+
export const TOOL_CARDS = ['generic', 'terminal', 'diff', 'search', 'read', 'web'];
|
|
9
|
+
export const TOOL_KINDS = [
|
|
10
|
+
'read',
|
|
11
|
+
'edit',
|
|
12
|
+
'delete',
|
|
13
|
+
'move',
|
|
14
|
+
'search',
|
|
15
|
+
'execute',
|
|
16
|
+
'fetch',
|
|
17
|
+
'other',
|
|
18
|
+
];
|
|
19
|
+
function clip(text, max = 60) {
|
|
20
|
+
const trimmed = text.replace(/\s+/g, ' ').trim();
|
|
21
|
+
if (!trimmed)
|
|
22
|
+
return '';
|
|
23
|
+
return trimmed.length > max ? `${trimmed.slice(0, max - 1)}…` : trimmed;
|
|
24
|
+
}
|
|
25
|
+
function str(args, key) {
|
|
26
|
+
const value = args[key];
|
|
27
|
+
return typeof value === 'string' ? value : '';
|
|
28
|
+
}
|
|
29
|
+
function dataOf(result) {
|
|
30
|
+
if (!result || typeof result !== 'object')
|
|
31
|
+
return null;
|
|
32
|
+
const data = result.data;
|
|
33
|
+
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
34
|
+
return data;
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
function failed(result) {
|
|
39
|
+
return Boolean(result && typeof result === 'object' && result.success === false);
|
|
40
|
+
}
|
|
41
|
+
function commandTitle(args) {
|
|
42
|
+
return clip(str(args, 'command')) || 'shell';
|
|
43
|
+
}
|
|
44
|
+
function pathTitle(args, fallback) {
|
|
45
|
+
return clip(str(args, 'path') || str(args, 'file') || str(args, 'target'), 72) || fallback;
|
|
46
|
+
}
|
|
47
|
+
function webSearchTitle(args, result) {
|
|
48
|
+
const query = str(args, 'query');
|
|
49
|
+
const head = `搜索“${clip(query, 40) || '…'}”`;
|
|
50
|
+
if (result === undefined || failed(result))
|
|
51
|
+
return head;
|
|
52
|
+
const data = dataOf(result);
|
|
53
|
+
const count = data && typeof data.result_count === 'number' ? data.result_count : null;
|
|
54
|
+
return count === null ? head : `${head} → ${count} 条结果`;
|
|
55
|
+
}
|
|
56
|
+
function shortUrl(raw) {
|
|
57
|
+
if (!raw)
|
|
58
|
+
return '未知地址';
|
|
59
|
+
try {
|
|
60
|
+
const url = new URL(raw);
|
|
61
|
+
const path = url.pathname === '/' ? '' : url.pathname;
|
|
62
|
+
return clip(`${url.host}${path}`, 48);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return clip(raw, 48);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function webFetchTitle(args, result) {
|
|
69
|
+
const head = `抓取 ${shortUrl(str(args, 'url'))}`;
|
|
70
|
+
if (result === undefined || failed(result))
|
|
71
|
+
return head;
|
|
72
|
+
const data = dataOf(result);
|
|
73
|
+
if (!data)
|
|
74
|
+
return head;
|
|
75
|
+
const parts = [];
|
|
76
|
+
if (typeof data.status === 'number')
|
|
77
|
+
parts.push(String(data.status));
|
|
78
|
+
if (typeof data.bytes === 'number' && Number.isFinite(data.bytes)) {
|
|
79
|
+
parts.push(data.bytes < 1024 ? `${data.bytes} B` : `${(data.bytes / 1024).toFixed(1)} KB`);
|
|
80
|
+
}
|
|
81
|
+
if (data.truncated === true)
|
|
82
|
+
parts.push('已截断');
|
|
83
|
+
return parts.length > 0 ? `${head} → ${parts.join(' · ')}` : head;
|
|
84
|
+
}
|
|
85
|
+
function taskRunTitle(args, result) {
|
|
86
|
+
const task = clip(str(args, 'task'));
|
|
87
|
+
if (!task)
|
|
88
|
+
return '后台任务';
|
|
89
|
+
const status = dataOf(result)?.status;
|
|
90
|
+
if (status === 'blocked')
|
|
91
|
+
return `${task} · 等待依赖`;
|
|
92
|
+
if (args.worktree === true)
|
|
93
|
+
return `${task} · 隔离工作区`;
|
|
94
|
+
return task;
|
|
95
|
+
}
|
|
96
|
+
function jobIdTitle(args, fallback) {
|
|
97
|
+
const id = str(args, 'job_id') || str(args, 'taskId');
|
|
98
|
+
return id ? id.slice(0, 8) : fallback;
|
|
99
|
+
}
|
|
100
|
+
function runCodeTitle(args, result) {
|
|
101
|
+
const description = clip(str(args, 'description'), 40) || '程序';
|
|
102
|
+
if (failed(result))
|
|
103
|
+
return `程序「${description}」失败`;
|
|
104
|
+
const calls = dataOf(result)?.calls;
|
|
105
|
+
const count = Array.isArray(calls) ? calls.length : 0;
|
|
106
|
+
return count > 0 ? `程序「${description}」· ${count} 个内层工具` : `程序「${description}」`;
|
|
107
|
+
}
|
|
108
|
+
function goalTitle(args, fallback) {
|
|
109
|
+
return clip(str(args, 'objective'), 72) || fallback;
|
|
110
|
+
}
|
|
111
|
+
const PRESENTERS = {
|
|
112
|
+
local_exec_shell: { card: 'terminal', kind: 'execute', title: commandTitle },
|
|
113
|
+
pwsh: { card: 'terminal', kind: 'execute', title: commandTitle },
|
|
114
|
+
bash: { card: 'terminal', kind: 'execute', title: commandTitle },
|
|
115
|
+
bash_session: { card: 'terminal', kind: 'execute', title: commandTitle },
|
|
116
|
+
write_stdin: {
|
|
117
|
+
card: 'terminal',
|
|
118
|
+
kind: 'execute',
|
|
119
|
+
title: (args) => clip(str(args, 'chars'), 40) || str(args, 'sessionId').slice(0, 8) || 'stdin',
|
|
120
|
+
},
|
|
121
|
+
local_run_snippet: {
|
|
122
|
+
card: 'terminal',
|
|
123
|
+
kind: 'execute',
|
|
124
|
+
title: (args) => clip(str(args, 'code'), 48) || str(args, 'language') || 'snippet',
|
|
125
|
+
},
|
|
126
|
+
local_run_script: {
|
|
127
|
+
card: 'terminal',
|
|
128
|
+
kind: 'execute',
|
|
129
|
+
title: (args) => str(args, 'scriptId') || 'script',
|
|
130
|
+
},
|
|
131
|
+
run_code: { card: 'generic', kind: 'execute', title: runCodeTitle },
|
|
132
|
+
run_js: { card: 'generic', kind: 'execute', title: runCodeTitle },
|
|
133
|
+
wait_js: {
|
|
134
|
+
card: 'generic',
|
|
135
|
+
kind: 'execute',
|
|
136
|
+
title: (args) => str(args, 'id').slice(0, 8) || 'wait_js',
|
|
137
|
+
},
|
|
138
|
+
local_read_file: { card: 'read', kind: 'read', title: (args) => pathTitle(args, 'read') },
|
|
139
|
+
read_file: { card: 'read', kind: 'read', title: (args) => pathTitle(args, 'read') },
|
|
140
|
+
view_image: { card: 'read', kind: 'read', title: (args) => pathTitle(args, 'image') },
|
|
141
|
+
capture_display: {
|
|
142
|
+
card: 'read',
|
|
143
|
+
kind: 'read',
|
|
144
|
+
title: (args) => clip(str(args, 'target'), 48) || 'display',
|
|
145
|
+
},
|
|
146
|
+
local_list_scripts: { card: 'read', kind: 'read', title: () => '已保存的脚本' },
|
|
147
|
+
local_write_file: { card: 'diff', kind: 'edit', title: (args) => pathTitle(args, 'write') },
|
|
148
|
+
write_file: { card: 'diff', kind: 'edit', title: (args) => pathTitle(args, 'write') },
|
|
149
|
+
local_edit_file: { card: 'diff', kind: 'edit', title: (args) => pathTitle(args, 'edit') },
|
|
150
|
+
edit_file: { card: 'diff', kind: 'edit', title: (args) => pathTitle(args, 'edit') },
|
|
151
|
+
apply_patch: { card: 'diff', kind: 'edit', title: () => 'apply_patch' },
|
|
152
|
+
grep: {
|
|
153
|
+
card: 'search',
|
|
154
|
+
kind: 'search',
|
|
155
|
+
title: (args) => clip(str(args, 'pattern')) || 'grep',
|
|
156
|
+
},
|
|
157
|
+
glob: {
|
|
158
|
+
card: 'search',
|
|
159
|
+
kind: 'search',
|
|
160
|
+
title: (args) => clip(str(args, 'pattern')) || 'glob',
|
|
161
|
+
},
|
|
162
|
+
tool_search: {
|
|
163
|
+
card: 'search',
|
|
164
|
+
kind: 'search',
|
|
165
|
+
title: (args) => clip(str(args, 'query')) || 'tool_search',
|
|
166
|
+
},
|
|
167
|
+
web_search: { card: 'web', kind: 'search', title: webSearchTitle },
|
|
168
|
+
web_fetch: { card: 'web', kind: 'fetch', title: webFetchTitle },
|
|
169
|
+
task_run: { card: 'generic', kind: 'other', title: taskRunTitle },
|
|
170
|
+
task_send: {
|
|
171
|
+
card: 'generic',
|
|
172
|
+
kind: 'other',
|
|
173
|
+
title: (args) => clip(str(args, 'message')) || '转达任务',
|
|
174
|
+
},
|
|
175
|
+
task_status: {
|
|
176
|
+
card: 'generic',
|
|
177
|
+
kind: 'read',
|
|
178
|
+
title: (args, result) => {
|
|
179
|
+
const id = str(args, 'taskId');
|
|
180
|
+
if (id)
|
|
181
|
+
return id.slice(0, 8);
|
|
182
|
+
const total = dataOf(result)?.total;
|
|
183
|
+
return typeof total === 'number' ? `${total} 个任务` : '本会话';
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
task_result: {
|
|
187
|
+
card: 'generic',
|
|
188
|
+
kind: 'read',
|
|
189
|
+
title: (args) => jobIdTitle(args, '收取结果'),
|
|
190
|
+
},
|
|
191
|
+
job_list: {
|
|
192
|
+
card: 'generic',
|
|
193
|
+
kind: 'read',
|
|
194
|
+
title: (_args, result) => {
|
|
195
|
+
const total = dataOf(result)?.total;
|
|
196
|
+
return typeof total === 'number' ? `${total} 个任务` : '后台任务';
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
job_output: { card: 'generic', kind: 'read', title: (args) => jobIdTitle(args, '任务输出') },
|
|
200
|
+
job_kill: { card: 'generic', kind: 'delete', title: (args) => jobIdTitle(args, '结束任务') },
|
|
201
|
+
worktree_create: {
|
|
202
|
+
card: 'generic',
|
|
203
|
+
kind: 'other',
|
|
204
|
+
title: (args) => str(args, 'name') || '新建工作区',
|
|
205
|
+
},
|
|
206
|
+
worktree_list: { card: 'read', kind: 'read', title: () => '工作区列表' },
|
|
207
|
+
worktree_remove: {
|
|
208
|
+
card: 'generic',
|
|
209
|
+
kind: 'delete',
|
|
210
|
+
title: (args) => str(args, 'name') || '移除工作区',
|
|
211
|
+
},
|
|
212
|
+
get_goal: { card: 'generic', kind: 'read', title: () => '当前目标' },
|
|
213
|
+
create_goal: { card: 'generic', kind: 'other', title: (args) => goalTitle(args, '新建目标') },
|
|
214
|
+
update_goal: {
|
|
215
|
+
card: 'generic',
|
|
216
|
+
kind: 'other',
|
|
217
|
+
title: (args) => {
|
|
218
|
+
const action = str(args, 'action');
|
|
219
|
+
const objective = clip(str(args, 'objective'), 48);
|
|
220
|
+
return objective ? `${action || 'update'} ${objective}` : action || '更新目标';
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
present_files: { card: 'generic', kind: 'other', title: () => '交付文件' },
|
|
224
|
+
local_open_path: {
|
|
225
|
+
card: 'generic',
|
|
226
|
+
kind: 'other',
|
|
227
|
+
title: (args) => pathTitle(args, 'open'),
|
|
228
|
+
},
|
|
229
|
+
todo_write: { card: 'generic', kind: 'other', title: () => '待办' },
|
|
230
|
+
ask_user: {
|
|
231
|
+
card: 'generic',
|
|
232
|
+
kind: 'other',
|
|
233
|
+
title: (args) => clip(str(args, 'intro') || str(args, 'question')) || '提问',
|
|
234
|
+
},
|
|
235
|
+
skill: { card: 'generic', kind: 'read', title: (args) => str(args, 'name') || 'skill' },
|
|
236
|
+
delegate_subagent: {
|
|
237
|
+
card: 'generic',
|
|
238
|
+
kind: 'other',
|
|
239
|
+
title: (args) => clip(str(args, 'task')) || str(args, 'subagent_type') || '子代理',
|
|
240
|
+
},
|
|
241
|
+
mcp_list_tools: { card: 'generic', kind: 'fetch', title: () => 'MCP 工具' },
|
|
242
|
+
mcp_tool_exec: {
|
|
243
|
+
card: 'generic',
|
|
244
|
+
kind: 'fetch',
|
|
245
|
+
title: (args) => str(args, 'toolName') || 'MCP',
|
|
246
|
+
},
|
|
247
|
+
plugin_list: { card: 'generic', kind: 'read', title: () => '插件' },
|
|
248
|
+
plugin_enable: { card: 'generic', kind: 'other', title: (args) => str(args, 'name') || '启用插件' },
|
|
249
|
+
plugin_disable: { card: 'generic', kind: 'delete', title: (args) => str(args, 'name') || '停用插件' },
|
|
250
|
+
plugin_reload: { card: 'generic', kind: 'other', title: (args) => str(args, 'name') || '重载插件' },
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* 取一次调用的展示。`result` 缺省时只根据参数出标题;结果到达后用同一声明补计数和状态。
|
|
254
|
+
*/
|
|
255
|
+
export function presentToolCall(name, args, result) {
|
|
256
|
+
const record = args && typeof args === 'object' && !Array.isArray(args) ? args : {};
|
|
257
|
+
const presenter = PRESENTERS[name];
|
|
258
|
+
if (!presenter) {
|
|
259
|
+
return { card: 'generic', kind: 'other', title: name, declared: false };
|
|
260
|
+
}
|
|
261
|
+
const title = presenter.title(record, result).trim() || name;
|
|
262
|
+
return { card: presenter.card, kind: presenter.kind, title, declared: true };
|
|
263
|
+
}
|
|
264
|
+
export function declaredToolNames() {
|
|
265
|
+
return Object.keys(PRESENTERS);
|
|
266
|
+
}
|
package/dist/tool-router.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { WorktreeService } from './local-backend/worktree-service.js';
|
|
|
7
7
|
import type { ToolContribution, ToolMode, ToolExposure } from './scenario/pack.js';
|
|
8
8
|
export type { ToolMode, ToolExposure } from './scenario/pack.js';
|
|
9
9
|
import { type AgentToolPolicy } from './local-backend/agent-capability.js';
|
|
10
|
+
import { GoalStore } from './goal-store.js';
|
|
10
11
|
/** 已注册 MCP 服务的动态工具名前缀:`mcp__<serverKey>__<toolName>`。 */
|
|
11
12
|
export declare const MCP_DYNAMIC_TOOL_PREFIX = "mcp__";
|
|
12
13
|
/**
|
|
@@ -27,17 +28,23 @@ export interface ToolCall {
|
|
|
27
28
|
* 单次工具执行的调用上下文(按 chat 解析,随每次 execute 传入)。
|
|
28
29
|
*
|
|
29
30
|
* `projectRoot` 是项目模式的硬沙箱根:非空时
|
|
30
|
-
* -
|
|
31
|
-
* -
|
|
31
|
+
* - 读路径必须落在根内,或落在 additionalReadRoots 内(含各自子目录)
|
|
32
|
+
* - 写路径必须落在根内,或落在 additionalWriteRoots 内(含各自子目录)
|
|
33
|
+
* - local_exec_shell 的 cwd 默认改为项目根;显式 cwd 必须落在上述可写根内
|
|
32
34
|
* 无项目对话(null/缺省)行为与之前完全一致。
|
|
33
35
|
*/
|
|
34
36
|
export interface ToolExecContext {
|
|
35
37
|
projectRoot?: string | null;
|
|
36
38
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
+
* 额外只读根(会话附件目录、源文件夹)。只放宽 local_read_file。
|
|
40
|
+
* 每个根包含其全部子目录。
|
|
39
41
|
*/
|
|
40
42
|
additionalReadRoots?: string[] | null;
|
|
43
|
+
/**
|
|
44
|
+
* 额外可写根(项目源文件夹)。放宽写入、编辑和命令 cwd。
|
|
45
|
+
* 每个根包含其全部子目录。附件目录不在这里。
|
|
46
|
+
*/
|
|
47
|
+
additionalWriteRoots?: string[] | null;
|
|
41
48
|
/**
|
|
42
49
|
* 4.6a:调用发生的 chat。task_run / task_status / task_result 需要它把
|
|
43
50
|
* 任务绑定到来源对话(任务表按 chatId 归组);reverse-tools 从
|
|
@@ -141,6 +148,10 @@ export declare class ToolRouter {
|
|
|
141
148
|
taskService: TaskService;
|
|
142
149
|
worktreeService: WorktreeService;
|
|
143
150
|
} | null): void;
|
|
151
|
+
/** 会话目标的持久化。缺省写到用户数据目录;测试注入临时文件。 */
|
|
152
|
+
private goalStore;
|
|
153
|
+
setGoalStore(store: GoalStore | null): void;
|
|
154
|
+
private goals;
|
|
144
155
|
setWebTools(delegate: WebToolDelegate | null, names?: readonly string[]): void;
|
|
145
156
|
setHostedWebSearch(handler: ((args: Record<string, unknown>) => Promise<unknown>) | null): void;
|
|
146
157
|
/**
|
|
@@ -168,6 +179,10 @@ export declare class ToolRouter {
|
|
|
168
179
|
* destructive,走宿主审批链。
|
|
169
180
|
*/
|
|
170
181
|
private listPluginToolSchemas;
|
|
182
|
+
/**
|
|
183
|
+
* 内容搜索、PowerShell、会话目标。后台任务的 job_* 跟 task 服务一起出场。
|
|
184
|
+
*/
|
|
185
|
+
private listParityToolSchemas;
|
|
171
186
|
/**
|
|
172
187
|
* 4.6a/4.6b 任务与 worktree 工具的 schema。task_run 立即返回
|
|
173
188
|
* (任务在后台独立流里跨 turn 跑完);worktree_* 操作绑定项目的
|
|
@@ -204,6 +219,8 @@ export declare class ToolRouter {
|
|
|
204
219
|
* 项目围栏按 chat 解析)。schema 出场 ⇒ 服务已接线,这里只缺 chatId 时
|
|
205
220
|
* 抛错(reverse-tools 总是透传;缺了就是接线 bug,响亮失败)。
|
|
206
221
|
*/
|
|
222
|
+
private executeGoalTool;
|
|
223
|
+
private executeJobTool;
|
|
207
224
|
private requireTaskContext;
|
|
208
225
|
/**
|
|
209
226
|
* Agent 写出 `…/skills/<name>/SKILL.md` 且该目录还不在已知 skill root 上时,
|
|
@@ -211,12 +228,13 @@ export declare class ToolRouter {
|
|
|
211
228
|
*/
|
|
212
229
|
private autoInstallSkillOnSuccess;
|
|
213
230
|
/**
|
|
214
|
-
* 项目模式硬沙箱(cwd
|
|
215
|
-
* 模型显式给的 cwd
|
|
216
|
-
*
|
|
231
|
+
* 项目模式硬沙箱(cwd 维度):有可写根时,cwd 默认收到项目家目录;
|
|
232
|
+
* 模型显式给的 cwd 必须落在家目录或额外可写根(源文件夹)内,含各自子目录。
|
|
233
|
+
* 相对 cwd 按家目录解析(而不是进程 cwd)。
|
|
217
234
|
*
|
|
218
235
|
* 注意边界:shell 命令文本里内嵌的绝对路径无法可靠解析,围栏只覆盖
|
|
219
|
-
* cwd
|
|
236
|
+
* cwd 与文件读写工具。命令实际写盘还受 exec 沙箱的 writableRoots 约束,
|
|
237
|
+
* 那些根同样包含子目录。
|
|
220
238
|
*
|
|
221
239
|
* local_exec_shell 与 local_run_snippet 共用本 helper。
|
|
222
240
|
*/
|