@trim21/personal-pi-extensions 0.1.651 → 0.1.653
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/README.md +67 -9
- package/package.json +3 -11
- package/src/aft/index.ts +40 -23
- package/src/aft/tools.ts +10 -9
- package/src/claude-code/files.ts +99 -54
- package/src/claude-code/glob.ts +8 -5
- package/src/claude-code/grep.ts +8 -5
- package/src/claude-code/index.ts +56 -10
- package/src/claude-code/search.ts +4 -3
- package/src/claude-code/session-tools.ts +4 -3
- package/src/claude-code/shell.ts +3 -8
- package/src/gh/index.ts +48 -25
- package/src/gh/tools/download-release-assets.ts +3 -3
- package/src/gh/tools/get-workflow-jobs.ts +3 -3
- package/src/gh/tools/list-issues.ts +3 -3
- package/src/gh/tools/list-prs.ts +3 -3
- package/src/gh/tools/list-releases.ts +3 -3
- package/src/gh/tools/list-workflow-runs.ts +3 -3
- package/src/gh/tools/read-ci-logs.ts +3 -4
- package/src/gh/tools/read-issue-comments.ts +3 -3
- package/src/gh/tools/read-issue.ts +3 -3
- package/src/gh/tools/read-pr-comments.ts +3 -3
- package/src/gh/tools/read-pr-diff.ts +3 -3
- package/src/gh/tools/read-pr-status.ts +3 -3
- package/src/gh/tools/read-pr.ts +3 -3
- package/src/gh/tools/read-release.ts +3 -3
- package/src/gh/tools/read-repo.ts +3 -3
- package/src/gh/tools/wait-commit-checks.ts +3 -3
- package/src/gh/tools/wait-pr-checks.ts +3 -3
- package/src/gh/tools/watch-run.ts +3 -3
- package/src/index.ts +120 -0
- package/src/lib/lsp/inspect-tool.ts +5 -5
- package/src/lib/lsp/rename-tool.ts +4 -7
- package/src/lib/tool-bus.ts +126 -0
- package/src/lib/tool-registration.ts +83 -0
- package/src/lib/tool-services.ts +81 -0
- package/src/lib/tool-units.ts +102 -0
- package/src/lib/tools-config.ts +277 -0
- package/src/opencode/bash.ts +7 -2
- package/src/opencode/files.ts +90 -43
- package/src/opencode/glob.ts +7 -1
- package/src/opencode/grep.ts +7 -1
- package/src/opencode/index.ts +66 -21
- package/src/opencode/question.ts +8 -2
- package/src/opencode/todo.ts +8 -2
- package/src/orphan-worker-cleaner.ts +38 -0
- package/src/spawn-agent.ts +160 -175
- package/src/talk/index.ts +92 -66
- package/src/vision-agent.ts +31 -7
- package/src/web/fetch.ts +89 -72
- package/src/web/search.ts +14 -2
- package/src/gh-readonly.ts +0 -10
|
@@ -14,11 +14,11 @@ import { readFileSync } from "node:fs";
|
|
|
14
14
|
import { readFile, stat } from "node:fs/promises";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
|
|
17
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
18
17
|
import { Type } from "typebox";
|
|
19
18
|
import type { Hover } from "vscode-languageserver-types";
|
|
20
19
|
|
|
21
20
|
import { resolvePathArg } from "../path.js";
|
|
21
|
+
import type { ToolBus } from "../tool-bus.js";
|
|
22
22
|
import type { InspectLocation } from "./client.js";
|
|
23
23
|
import type { LspService } from "./lsp.js";
|
|
24
24
|
import { type LspPosition, symbolCandidates } from "./rename.js";
|
|
@@ -255,8 +255,8 @@ async function probeSymbolCandidates(options: {
|
|
|
255
255
|
return first.output;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
export function registerLspInspectTools(
|
|
259
|
-
|
|
258
|
+
export function registerLspInspectTools(bus: ToolBus, service: LspService): void {
|
|
259
|
+
bus.register({
|
|
260
260
|
name: "lsp-find-definition",
|
|
261
261
|
label: "Lsp Find Definition",
|
|
262
262
|
description:
|
|
@@ -299,7 +299,7 @@ export function registerLspInspectTools(pi: ExtensionAPI, service: LspService):
|
|
|
299
299
|
},
|
|
300
300
|
});
|
|
301
301
|
|
|
302
|
-
|
|
302
|
+
bus.register({
|
|
303
303
|
name: "lsp-find-reference",
|
|
304
304
|
label: "Lsp Find Reference",
|
|
305
305
|
description:
|
|
@@ -342,7 +342,7 @@ export function registerLspInspectTools(pi: ExtensionAPI, service: LspService):
|
|
|
342
342
|
},
|
|
343
343
|
});
|
|
344
344
|
|
|
345
|
-
|
|
345
|
+
bus.register({
|
|
346
346
|
name: "lsp-inspect",
|
|
347
347
|
label: "Lsp Inspect",
|
|
348
348
|
description:
|
|
@@ -14,16 +14,13 @@ import { readFileSync } from "node:fs";
|
|
|
14
14
|
import { readFile, stat, writeFile } from "node:fs/promises";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
|
|
17
|
-
import {
|
|
18
|
-
type ExtensionAPI,
|
|
19
|
-
generateDiffString,
|
|
20
|
-
withFileMutationQueue,
|
|
21
|
-
} from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import { generateDiffString, withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
22
18
|
import { Type } from "typebox";
|
|
23
19
|
|
|
24
20
|
import { resolvePathArg } from "../path.js";
|
|
25
21
|
import type { ToolPendant } from "../pendant.ts";
|
|
26
22
|
import type { RequestPolicy } from "../request-policy.js";
|
|
23
|
+
import type { ToolBus } from "../tool-bus.js";
|
|
27
24
|
import { guardWriteAccess } from "../write-guard.js";
|
|
28
25
|
import { RenameNotPossibleError } from "./client.js";
|
|
29
26
|
import type { LspService } from "./lsp.js";
|
|
@@ -51,11 +48,11 @@ const LSP_RENAME_PROMPT = readFileSync(
|
|
|
51
48
|
).trim();
|
|
52
49
|
|
|
53
50
|
export function registerLspRenameTool(
|
|
54
|
-
|
|
51
|
+
bus: ToolBus,
|
|
55
52
|
service: LspService,
|
|
56
53
|
options: LspRenameOptions,
|
|
57
54
|
): void {
|
|
58
|
-
|
|
55
|
+
bus.register({
|
|
59
56
|
name: "lsp-rename",
|
|
60
57
|
label: "Lsp Rename",
|
|
61
58
|
description: "Rename a code symbol and update all references across the workspace via LSP",
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具总线:本仓库全部工具的唯一注册通道,也是同入口模块(codemode 等)直接调用
|
|
3
|
+
* 工具的通道。
|
|
4
|
+
*
|
|
5
|
+
* - `register`:按配置过滤(被禁用时跳过并返回 false)后交给 `pi.registerTool`,
|
|
6
|
+
* 并保留通过的定义供 `list` / `get` 查询。
|
|
7
|
+
* - `executeTool`:先用工具自身的参数 schema 校验参数,再调用定义的 `execute`;
|
|
8
|
+
* 参数校验失败与工具抛出的异常都归一化成错误结果返回,不向调用方抛异常。
|
|
9
|
+
*
|
|
10
|
+
* 状态由闭包持有(工厂 + 闭包),不使用模块级可变状态。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { AgentToolResult, AgentToolUpdateCallback } from "@earendil-works/pi-agent-core";
|
|
14
|
+
import type {
|
|
15
|
+
ExtensionAPI,
|
|
16
|
+
ExtensionContext,
|
|
17
|
+
ToolDefinition,
|
|
18
|
+
} from "@earendil-works/pi-coding-agent";
|
|
19
|
+
import type { Static, TSchema } from "typebox";
|
|
20
|
+
|
|
21
|
+
import { parseWithSchema } from "./parse-with-schema.js";
|
|
22
|
+
|
|
23
|
+
/** 一次工具执行的结果:`isError` 标出失败(工具不存在、参数不合法、工具抛错)。 */
|
|
24
|
+
export type ToolExecutionResult = AgentToolResult<unknown> & { isError: boolean };
|
|
25
|
+
|
|
26
|
+
export interface ExecuteToolOptions {
|
|
27
|
+
/** 工具 `execute` 需要的宿主上下文:调用方把自己拿到的那个 ctx 传进来。 */
|
|
28
|
+
ctx: ExtensionContext;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
onUpdate?: AgentToolUpdateCallback<unknown>;
|
|
31
|
+
/** 日志与临时文件命名用的调用 id;缺省由总线生成。 */
|
|
32
|
+
toolCallId?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ToolBus {
|
|
36
|
+
/** 注册一个工具;被配置禁用时跳过并返回 false。 */
|
|
37
|
+
register<TParams extends TSchema, TDetails>(
|
|
38
|
+
definition: ToolDefinition<TParams, TDetails, unknown>,
|
|
39
|
+
): boolean;
|
|
40
|
+
/** 本次实际注册的工具定义。 */
|
|
41
|
+
list(): readonly ToolDefinition[];
|
|
42
|
+
/** 所有尝试注册过的工具名(含被禁用而跳过的),用于校验配置里的模式是否有效。 */
|
|
43
|
+
declaredNames(): readonly string[];
|
|
44
|
+
get(name: string): ToolDefinition | undefined;
|
|
45
|
+
/** 按名执行一个已注册工具;任何失败都以 `isError` 结果返回,不抛异常。 */
|
|
46
|
+
executeTool(
|
|
47
|
+
name: string,
|
|
48
|
+
args: unknown,
|
|
49
|
+
options: ExecuteToolOptions,
|
|
50
|
+
): Promise<ToolExecutionResult>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ToolBusOptions {
|
|
54
|
+
/** 返回 true 表示该工具被禁用、不注册。 */
|
|
55
|
+
isDisabled?: (name: string) => boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type AnyToolDefinition = ToolDefinition<TSchema, unknown, unknown>;
|
|
59
|
+
|
|
60
|
+
function errorResult(text: string): ToolExecutionResult {
|
|
61
|
+
return { content: [{ type: "text", text }], details: { error: text }, isError: true };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function errorMessage(error: unknown): string {
|
|
65
|
+
return error instanceof Error ? error.message : String(error);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createToolBus(pi: ExtensionAPI, options: ToolBusOptions = {}): ToolBus {
|
|
69
|
+
const registered = new Map<string, AnyToolDefinition>();
|
|
70
|
+
const declared = new Set<string>();
|
|
71
|
+
let callCounter = 0;
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
register(definition) {
|
|
75
|
+
declared.add(definition.name);
|
|
76
|
+
if (options.isDisabled?.(definition.name) === true) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
const stored = definition as unknown as AnyToolDefinition;
|
|
80
|
+
pi.registerTool(stored);
|
|
81
|
+
registered.set(stored.name, stored);
|
|
82
|
+
return true;
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
list() {
|
|
86
|
+
return [...registered.values()];
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
declaredNames() {
|
|
90
|
+
return [...declared];
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
get(name) {
|
|
94
|
+
return registered.get(name);
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
async executeTool(name, args, options) {
|
|
98
|
+
const definition = registered.get(name);
|
|
99
|
+
if (!definition) {
|
|
100
|
+
return errorResult(`Tool "${name}" is not available.`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const toolCallId = options.toolCallId ?? `tool-bus-${++callCounter}`;
|
|
104
|
+
let params: Static<TSchema>;
|
|
105
|
+
try {
|
|
106
|
+
const prepared = definition.prepareArguments ? definition.prepareArguments(args) : args;
|
|
107
|
+
params = parseWithSchema(definition.parameters, prepared);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return errorResult(`Invalid arguments for tool "${name}": ${errorMessage(error)}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const result = await definition.execute(
|
|
114
|
+
toolCallId,
|
|
115
|
+
params,
|
|
116
|
+
options.signal,
|
|
117
|
+
options.onUpdate,
|
|
118
|
+
options.ctx,
|
|
119
|
+
);
|
|
120
|
+
return { ...result, isError: false };
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return errorResult(`Tool "${name}" failed: ${errorMessage(error)}`);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具注册上下文:把「配置 + 当前会话模型 → 总线 → 注册」这条链路收在一处。
|
|
3
|
+
*
|
|
4
|
+
* 约定:模块里的 registerXxx(bus, ...) 只收发工具的总线,需要宿主 API(pi.exec、
|
|
5
|
+
* pi.on、runtime.setup 等)时把 `pi` 作为后续参数显式传入;扩展入口/默认导出
|
|
6
|
+
* 用本文件的 createToolRegistration 把「当前会话模型」接上,并在 session_start
|
|
7
|
+
* 里调用这些 registerXxx。
|
|
8
|
+
*
|
|
9
|
+
* 为什么总线要先建、注册要晚于 `session_start`:
|
|
10
|
+
* - 禁用规则可以带 `models`,只有在 `session_start` 才能拿到本会话的模型,
|
|
11
|
+
* 因此注册发生在 `session_start` 里(总线在加载期先建好,过滤在注册时求值)。
|
|
12
|
+
* - LSP manager 在加载期创建、并在它自己的 `session_start` handler 里启用服务器
|
|
13
|
+
* 后回调注册 lsp 工具,所以它必须在加载期就能拿到同一个总线;模型由本文件
|
|
14
|
+
* 注册的 handler 更新,且它排在所有注册 handler 之前。
|
|
15
|
+
*
|
|
16
|
+
* 注册即启用:`pi.registerTool` 后工具默认进入 active 列表(pi 的 `defaultActive`
|
|
17
|
+
* 语义),本文件不碰 active 列表——工具可见性完全由「注册与否」决定,禁用工具就是
|
|
18
|
+
* 不注册。
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
22
|
+
|
|
23
|
+
import { createToolBus, type ToolBus } from "./tool-bus.js";
|
|
24
|
+
import {
|
|
25
|
+
type ModelIdentity,
|
|
26
|
+
readToolsConfig,
|
|
27
|
+
resolveToolAvailability,
|
|
28
|
+
type ToolsConfig,
|
|
29
|
+
type UnmatchedPattern,
|
|
30
|
+
} from "./tools-config.js";
|
|
31
|
+
|
|
32
|
+
export interface ToolRegistration {
|
|
33
|
+
/** 与同入口其他模块共享的总线。 */
|
|
34
|
+
readonly bus: ToolBus;
|
|
35
|
+
/** 本次配置解析结果(含解析期警告)。 */
|
|
36
|
+
readonly config: ToolsConfig;
|
|
37
|
+
/**
|
|
38
|
+
* 在每次会话启动时注册工具;注册时总线的过滤已按本会话模型生效。回调拿到
|
|
39
|
+
* 本会话的 ctx(reads 记账恢复等需要 sessionManager)。
|
|
40
|
+
*/
|
|
41
|
+
onSessionStart(register: (bus: ToolBus, ctx: ExtensionContext) => void | Promise<void>): void;
|
|
42
|
+
/** 哪些配置模式没命中过任何工具(在注册完成后调用)。 */
|
|
43
|
+
unmatchedPatterns(): UnmatchedPattern[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 独立入口(按路径加载的模块、或只有一组工具的入口)的注册接线:
|
|
48
|
+
* 每次会话启动时用本会话模型建好的总线注册工具。
|
|
49
|
+
*/
|
|
50
|
+
export function registerToolsOnSessionStart(
|
|
51
|
+
pi: ExtensionAPI,
|
|
52
|
+
register: (bus: ToolBus, ctx: ExtensionContext) => void | Promise<void>,
|
|
53
|
+
): void {
|
|
54
|
+
createToolRegistration(pi).onSessionStart(register);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createToolRegistration(pi: ExtensionAPI, settingsPath?: string): ToolRegistration {
|
|
58
|
+
const config = readToolsConfig(settingsPath);
|
|
59
|
+
let model: ModelIdentity | undefined;
|
|
60
|
+
|
|
61
|
+
const bus = createToolBus(pi, {
|
|
62
|
+
// 每次注册都按当前模型重新判定:工具在 session_start 里注册,模型由下面
|
|
63
|
+
// 的 handler 更新,二者在同一个会话内保持一致。
|
|
64
|
+
isDisabled: (name) => resolveToolAvailability(config, model).isDisabled(name),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
pi.on("session_start", (_event, ctx) => {
|
|
68
|
+
model = ctx.model;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
bus,
|
|
73
|
+
config,
|
|
74
|
+
onSessionStart(register) {
|
|
75
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
76
|
+
await register(bus, ctx);
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
unmatchedPatterns() {
|
|
80
|
+
return resolveToolAvailability(config, model).unmatchedPatterns(bus.declaredNames());
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 共享工具服务:同一个扩展实例里的所有工具共用一份请求策略、bwrap runtime 与
|
|
3
|
+
* LSP manager。
|
|
4
|
+
*
|
|
5
|
+
* 为什么必须共享:两套文件 IO 工具集各自创建 LSP manager 会重复注册 `/lsp-*`
|
|
6
|
+
* 命令(命令名冲突);各自创建 bwrap runtime 会重复注册 `/bwrap*` 命令。因此
|
|
7
|
+
* 由入口创建一份、注入给选中的工具集。
|
|
8
|
+
*
|
|
9
|
+
* LSP 专属工具(lsp-rename / inspect 族)由「当前会话选中的工具集」注册,
|
|
10
|
+
* 通过 `setLspEnabledHandler` 交给本模块——manager 在 session_start 之后才
|
|
11
|
+
* 触发该回调,那时入口已完成工具集选择。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
15
|
+
|
|
16
|
+
import type { BwrapConfig } from "../bwrap/core.js";
|
|
17
|
+
import { type BwrapRuntime, createBwrapRuntime } from "../bwrap/runtime.js";
|
|
18
|
+
import {
|
|
19
|
+
createLspManager,
|
|
20
|
+
type LspManager,
|
|
21
|
+
type LspService,
|
|
22
|
+
type LspServiceOptions,
|
|
23
|
+
} from "./lsp/lsp.js";
|
|
24
|
+
import { createRequestPolicy, type RequestPolicy } from "./request-policy.js";
|
|
25
|
+
|
|
26
|
+
export interface ToolServices {
|
|
27
|
+
policy: RequestPolicy;
|
|
28
|
+
runtime: BwrapRuntime;
|
|
29
|
+
manager: LspManager;
|
|
30
|
+
/** 由入口在选出本会话的工具集后设置。 */
|
|
31
|
+
setLspEnabledHandler(handler: ((service: LspService) => void) | undefined): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ToolServicesOptions extends LspServiceOptions {
|
|
35
|
+
/** 固定沙箱配置(子代理用);不传则跟随用户配置与 /bwrap-* 命令。 */
|
|
36
|
+
sandbox?: BwrapConfig;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createToolServices(pi: ExtensionAPI, options?: ToolServicesOptions): ToolServices {
|
|
40
|
+
const policy = createRequestPolicy(pi.events);
|
|
41
|
+
const runtime = createBwrapRuntime(policy, options?.sandbox);
|
|
42
|
+
// 注册 /bwrap* 命令、沙箱系统提示与 session 生命周期钩子;固定沙箱
|
|
43
|
+
// (子代理)不注册命令,见 BwrapRuntime.setup。
|
|
44
|
+
runtime.setup(pi);
|
|
45
|
+
|
|
46
|
+
let delegate: ((service: LspService) => void) | undefined;
|
|
47
|
+
// manager 的 session_start handler 注册在入口的注册回调之前(入口须先知道本会话
|
|
48
|
+
// 的模型才能选出工具集),所以它触发 onEnabled 时 delegate 往往还没就位。先缓存
|
|
49
|
+
// 服务,等 setLspEnabledHandler 调用时立即补发,注册不再依赖两者的先后顺序。
|
|
50
|
+
let pendingService: LspService | undefined;
|
|
51
|
+
const manager = createLspManager(
|
|
52
|
+
pi,
|
|
53
|
+
{
|
|
54
|
+
onEnabled: (_pi, service) => {
|
|
55
|
+
if (delegate) {
|
|
56
|
+
delegate(service);
|
|
57
|
+
} else {
|
|
58
|
+
pendingService = service;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
options,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
policy,
|
|
67
|
+
runtime,
|
|
68
|
+
manager,
|
|
69
|
+
setLspEnabledHandler(handler) {
|
|
70
|
+
delegate = handler;
|
|
71
|
+
if (!handler) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const pending = pendingService;
|
|
75
|
+
pendingService = undefined;
|
|
76
|
+
if (pending) {
|
|
77
|
+
handler(pending);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具单元表:本仓库工具按「一套文件 IO 工具集 + 若干单元」组织,主入口与
|
|
3
|
+
* spawn-agent 的子代理共用同一张表,因此两处的工具实现、参数与审批完全一致。
|
|
4
|
+
*
|
|
5
|
+
* - 主入口按配置选中的工具集注册全部单元(`fileIo` / `fileIoByModel`)。
|
|
6
|
+
* - 子代理按 frontmatter 声明的工具名注册覆盖到的单元;可见性再由
|
|
7
|
+
* `createAgentSession({ tools })` 的白名单收敛。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
|
|
12
|
+
import type { BwrapRuntime } from "../bwrap/runtime.js";
|
|
13
|
+
import type { FileToolset } from "../claude-code/files.js";
|
|
14
|
+
import { registerSearchTools } from "../claude-code/search.js";
|
|
15
|
+
import { registerSessionTools } from "../claude-code/session-tools.js";
|
|
16
|
+
import { registerShellTools } from "../claude-code/shell.js";
|
|
17
|
+
import { registerBashTool } from "../opencode/bash.js";
|
|
18
|
+
import type { OpencodeFileToolset } from "../opencode/files.js";
|
|
19
|
+
import { registerGlobTool } from "../opencode/glob.js";
|
|
20
|
+
import { registerGrepTool } from "../opencode/grep.js";
|
|
21
|
+
import { registerQuestionTool } from "../opencode/question.js";
|
|
22
|
+
import { registerTodoTool } from "../opencode/todo.js";
|
|
23
|
+
import type { RequestPolicy } from "./request-policy.js";
|
|
24
|
+
import type { ToolBus } from "./tool-bus.js";
|
|
25
|
+
import type { FileIoToolset } from "./tools-config.js";
|
|
26
|
+
|
|
27
|
+
export interface ToolUnitDeps {
|
|
28
|
+
pi: ExtensionAPI;
|
|
29
|
+
bus: ToolBus;
|
|
30
|
+
policy: RequestPolicy;
|
|
31
|
+
runtime: BwrapRuntime;
|
|
32
|
+
/** 选中工具集的文件工具(Read/Edit/Write 或 read/edit/write)。 */
|
|
33
|
+
fileToolset: FileToolset | OpencodeFileToolset;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ToolUnit {
|
|
37
|
+
/** 该单元提供的工具名,用于子代理按声明挑选单元。 */
|
|
38
|
+
tools: readonly string[];
|
|
39
|
+
register(deps: ToolUnitDeps): void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const TOOL_UNITS: Record<FileIoToolset, readonly ToolUnit[]> = {
|
|
43
|
+
"claude-code": [
|
|
44
|
+
{
|
|
45
|
+
tools: ["Read", "Edit", "Write"],
|
|
46
|
+
register: ({ bus, fileToolset }) => fileToolset.register(bus),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
tools: ["Glob", "Grep"],
|
|
50
|
+
register: ({ bus, pi }) => registerSearchTools(bus, pi),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
tools: ["Bash"],
|
|
54
|
+
register: ({ bus, pi, runtime }) => registerShellTools(bus, pi, runtime),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
tools: ["TodoWrite", "AskUserQuestion"],
|
|
58
|
+
register: ({ bus, pi }) => registerSessionTools(bus, pi),
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
opencode: [
|
|
62
|
+
{
|
|
63
|
+
tools: ["read", "edit", "write"],
|
|
64
|
+
register: ({ bus, fileToolset }) => fileToolset.register(bus),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
tools: ["glob", "grep"],
|
|
68
|
+
register: ({ bus }) => {
|
|
69
|
+
registerGlobTool(bus);
|
|
70
|
+
registerGrepTool(bus);
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
tools: ["bash"],
|
|
75
|
+
register: ({ bus, pi, runtime }) => registerBashTool(bus, pi, runtime),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
tools: ["todowrite", "question"],
|
|
79
|
+
register: ({ bus }) => {
|
|
80
|
+
registerTodoTool(bus);
|
|
81
|
+
registerQuestionTool(bus);
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** 子代理声明的工具名对应哪一套文件 IO 工具集:出现哪一套的名字就用哪一套。 */
|
|
88
|
+
export function toolsetsForToolNames(toolNames: readonly string[]): FileIoToolset | undefined {
|
|
89
|
+
const declared = new Set(toolNames);
|
|
90
|
+
for (const kind of ["claude-code", "opencode"] as const) {
|
|
91
|
+
if (TOOL_UNITS[kind].some((unit) => unit.tools.some((name) => declared.has(name)))) {
|
|
92
|
+
return kind;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** 挑出覆盖到声明工具名的单元。 */
|
|
99
|
+
export function unitsForToolNames(kind: FileIoToolset, toolNames: readonly string[]): ToolUnit[] {
|
|
100
|
+
const declared = new Set(toolNames);
|
|
101
|
+
return TOOL_UNITS[kind].filter((unit) => unit.tools.some((name) => declared.has(name)));
|
|
102
|
+
}
|