@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.
Files changed (52) hide show
  1. package/README.md +67 -9
  2. package/package.json +3 -11
  3. package/src/aft/index.ts +40 -23
  4. package/src/aft/tools.ts +10 -9
  5. package/src/claude-code/files.ts +99 -54
  6. package/src/claude-code/glob.ts +8 -5
  7. package/src/claude-code/grep.ts +8 -5
  8. package/src/claude-code/index.ts +56 -10
  9. package/src/claude-code/search.ts +4 -3
  10. package/src/claude-code/session-tools.ts +4 -3
  11. package/src/claude-code/shell.ts +3 -8
  12. package/src/gh/index.ts +48 -25
  13. package/src/gh/tools/download-release-assets.ts +3 -3
  14. package/src/gh/tools/get-workflow-jobs.ts +3 -3
  15. package/src/gh/tools/list-issues.ts +3 -3
  16. package/src/gh/tools/list-prs.ts +3 -3
  17. package/src/gh/tools/list-releases.ts +3 -3
  18. package/src/gh/tools/list-workflow-runs.ts +3 -3
  19. package/src/gh/tools/read-ci-logs.ts +3 -4
  20. package/src/gh/tools/read-issue-comments.ts +3 -3
  21. package/src/gh/tools/read-issue.ts +3 -3
  22. package/src/gh/tools/read-pr-comments.ts +3 -3
  23. package/src/gh/tools/read-pr-diff.ts +3 -3
  24. package/src/gh/tools/read-pr-status.ts +3 -3
  25. package/src/gh/tools/read-pr.ts +3 -3
  26. package/src/gh/tools/read-release.ts +3 -3
  27. package/src/gh/tools/read-repo.ts +3 -3
  28. package/src/gh/tools/wait-commit-checks.ts +3 -3
  29. package/src/gh/tools/wait-pr-checks.ts +3 -3
  30. package/src/gh/tools/watch-run.ts +3 -3
  31. package/src/index.ts +120 -0
  32. package/src/lib/lsp/inspect-tool.ts +5 -5
  33. package/src/lib/lsp/rename-tool.ts +4 -7
  34. package/src/lib/tool-bus.ts +126 -0
  35. package/src/lib/tool-registration.ts +83 -0
  36. package/src/lib/tool-services.ts +81 -0
  37. package/src/lib/tool-units.ts +102 -0
  38. package/src/lib/tools-config.ts +277 -0
  39. package/src/opencode/bash.ts +7 -2
  40. package/src/opencode/files.ts +90 -43
  41. package/src/opencode/glob.ts +7 -1
  42. package/src/opencode/grep.ts +7 -1
  43. package/src/opencode/index.ts +66 -21
  44. package/src/opencode/question.ts +8 -2
  45. package/src/opencode/todo.ts +8 -2
  46. package/src/orphan-worker-cleaner.ts +38 -0
  47. package/src/spawn-agent.ts +160 -175
  48. package/src/talk/index.ts +92 -66
  49. package/src/vision-agent.ts +31 -7
  50. package/src/web/fetch.ts +89 -72
  51. package/src/web/search.ts +14 -2
  52. package/src/gh-readonly.ts +0 -10
@@ -8,12 +8,13 @@
8
8
 
9
9
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
10
10
 
11
+ import type { ToolBus } from "../lib/tool-bus.js";
11
12
  import { registerGlobTool } from "./glob.js";
12
13
  import { registerGrepTool } from "./grep.js";
13
14
 
14
- export function registerSearchTools(pi: ExtensionAPI): void {
15
- registerGlobTool(pi);
16
- registerGrepTool(pi);
15
+ export function registerSearchTools(bus: ToolBus, pi: ExtensionAPI): void {
16
+ registerGlobTool(bus);
17
+ registerGrepTool(bus, pi);
17
18
  }
18
19
 
19
20
  // Re-exported for tests that import pure functions from "./search.js"; the
@@ -3,6 +3,7 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
3
3
  import { type Static, Type } from "typebox";
4
4
  import { Value } from "typebox/value";
5
5
 
6
+ import type { ToolBus } from "../lib/tool-bus.js";
6
7
  import { selectMultiple, selectWithOptionalInput } from "../lib/ui.js";
7
8
 
8
9
  const TODO_STATUSES = ["pending", "in_progress", "completed"] as const;
@@ -121,7 +122,7 @@ async function askMultiple(
121
122
  return selected.length > 0 ? selected.join(", ") : "Unanswered";
122
123
  }
123
124
 
124
- export function registerSessionTools(pi: ExtensionAPI): void {
125
+ export function registerSessionTools(bus: ToolBus, pi: ExtensionAPI): void {
125
126
  // TodoWrite 的列表随工具结果 details 持久化(跟随会话分支),但 widget 是
126
127
  // 纯 TUI 状态,进程重启后丢失。session 恢复时从当前分支取最后一个 TodoWrite
127
128
  // 的列表重新渲染(完整列表替换语义,后出现的覆盖前面的)。
@@ -139,7 +140,7 @@ export function registerSessionTools(pi: ExtensionAPI): void {
139
140
  }
140
141
  });
141
142
 
142
- pi.registerTool({
143
+ bus.register({
143
144
  name: "TodoWrite",
144
145
  label: "Todo Write",
145
146
  description: [
@@ -177,7 +178,7 @@ export function registerSessionTools(pi: ExtensionAPI): void {
177
178
  },
178
179
  });
179
180
 
180
- pi.registerTool({
181
+ bus.register({
181
182
  name: "AskUserQuestion",
182
183
  label: "Ask User Question",
183
184
  description: [
@@ -13,12 +13,11 @@ import { Type } from "typebox";
13
13
  import {
14
14
  BashInterruptedError,
15
15
  type BwrapRuntime,
16
- createBwrapRuntime,
17
16
  formatElapsedSeconds,
18
17
  sandboxHintBlock,
19
18
  } from "../bwrap/runtime.js";
20
19
  import { resolveWorkdir } from "../lib/path.js";
21
- import { createRequestPolicy } from "../lib/request-policy.js";
20
+ import type { ToolBus } from "../lib/tool-bus.js";
22
21
 
23
22
  const DEFAULT_TIMEOUT_MS = 120_000;
24
23
  const MAX_TIMEOUT_MS = 7_200_000;
@@ -88,12 +87,8 @@ function formatBashSuccess(result: Awaited<ReturnType<BwrapRuntime["execute"]>>)
88
87
  * runtime 由调用方注入:扩展工厂持有一个实例(不依赖模块级全局状态),
89
88
  * 测试可注入预置模式的实例。状态随扩展实例生命周期,session 切换重建即重置。
90
89
  */
91
- export function registerShellTools(
92
- pi: ExtensionAPI,
93
- runtime: BwrapRuntime = createBwrapRuntime(createRequestPolicy(pi.events)),
94
- ): void {
95
- runtime.setup(pi);
96
- pi.registerTool({
90
+ export function registerShellTools(bus: ToolBus, pi: ExtensionAPI, runtime: BwrapRuntime): void {
91
+ bus.register({
97
92
  name: "Bash",
98
93
  promptSnippet: "execute command",
99
94
  promptGuidelines: [BASH_PROMPT],
package/src/gh/index.ts CHANGED
@@ -36,6 +36,8 @@
36
36
 
37
37
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
38
38
 
39
+ import type { ToolBus } from "../lib/tool-bus.js";
40
+ import { registerToolsOnSessionStart } from "../lib/tool-registration.js";
39
41
  import { GhClient, isGhAvailable } from "./base.js";
40
42
  import { addDownloadReleaseAssetsTool } from "./tools/download-release-assets.js";
41
43
  import { addGetWorkflowJobsTool } from "./tools/get-workflow-jobs.js";
@@ -62,20 +64,58 @@ export * from "./tools/download-release-assets.js";
62
64
  export * from "./tools/read-ci-logs.js";
63
65
  export * from "./tools/read-pr-status.js";
64
66
 
65
- export default function ghReadonlyTools(pi: ExtensionAPI) {
67
+ /** 本工具集的工具:入口从它取注册函数(gh 不可用时返回 undefined)。 */
68
+ export function createGithubTools(): { register(bus: ToolBus): void } | undefined {
66
69
  // Windows 上禁用:gh 可执行文件的探测(无扩展名 + POSIX 路径)与进程
67
70
  // 管理(SIGTERM 信号语义)都是 POSIX 假设,不做 Windows 适配。
68
71
  if (process.platform === "win32") {
69
- pi.on("session_start", (_event, ctx) => {
70
- ctx.ui.notify("gh-readonly tools are disabled on Windows.", "warning");
71
- });
72
- return;
72
+ return undefined;
73
73
  }
74
74
 
75
75
  // Fail fast: the `gh` CLI is the only backend for these tools. Without it the
76
76
  // extension registers nothing and reports the problem at session start, so
77
77
  // the user gets one clear error instead of a dozen failing tool calls.
78
78
  if (!isGhAvailable()) {
79
+ return undefined;
80
+ }
81
+
82
+ const gh = new GhClient();
83
+ return {
84
+ register(bus) {
85
+ addReadIssueTool(gh, bus);
86
+ addListIssuesTool(gh, bus);
87
+ addReadIssueCommentsTool(gh, bus);
88
+ addReadPrTool(gh, bus);
89
+ addListPrsTool(gh, bus);
90
+ addReadPrDiffTool(gh, bus);
91
+ addReadPrStatusTool(gh, bus);
92
+ addReadPrCommentsTool(gh, bus);
93
+ addReadCiLogsTool(gh, bus);
94
+ addListWorkflowRunsTool(gh, bus);
95
+ addGetWorkflowJobsTool(gh, bus);
96
+ addReadRepoTool(gh, bus);
97
+ addListReleasesTool(gh, bus);
98
+ addReadReleaseTool(gh, bus);
99
+ addDownloadReleaseAssetsTool(gh, bus);
100
+ addWaitPrChecksTool(gh, bus);
101
+ addWaitCommitChecksTool(gh, bus);
102
+ addWatchRunTool(gh, bus);
103
+ },
104
+ };
105
+ }
106
+
107
+ export default function ghReadonlyTools(pi: ExtensionAPI): void {
108
+ // Windows 上禁用:gh 可执行文件的探测与进程管理都是 POSIX 假设。
109
+ if (process.platform === "win32") {
110
+ pi.on("session_start", (_event, ctx) => {
111
+ ctx.ui.notify("gh-readonly tools are disabled on Windows.", "warning");
112
+ });
113
+ return;
114
+ }
115
+
116
+ // gh 不可用时一个工具都不注册,只在会话启动时报告一次问题。
117
+ const tools = createGithubTools();
118
+ if (!tools) {
79
119
  pi.on("session_start", (_event, ctx) => {
80
120
  ctx.ui.notify(
81
121
  "gh CLI not found in PATH: GitHub read-only tools are disabled. Install GitHub CLI (https://cli.github.com/) and reload the session.",
@@ -85,24 +125,7 @@ export default function ghReadonlyTools(pi: ExtensionAPI) {
85
125
  return;
86
126
  }
87
127
 
88
- const gh = new GhClient();
89
-
90
- addReadIssueTool(gh, pi);
91
- addListIssuesTool(gh, pi);
92
- addReadIssueCommentsTool(gh, pi);
93
- addReadPrTool(gh, pi);
94
- addListPrsTool(gh, pi);
95
- addReadPrDiffTool(gh, pi);
96
- addReadPrStatusTool(gh, pi);
97
- addReadPrCommentsTool(gh, pi);
98
- addReadCiLogsTool(gh, pi);
99
- addListWorkflowRunsTool(gh, pi);
100
- addGetWorkflowJobsTool(gh, pi);
101
- addReadRepoTool(gh, pi);
102
- addListReleasesTool(gh, pi);
103
- addReadReleaseTool(gh, pi);
104
- addDownloadReleaseAssetsTool(gh, pi);
105
- addWaitPrChecksTool(gh, pi);
106
- addWaitCommitChecksTool(gh, pi);
107
- addWatchRunTool(gh, pi);
128
+ registerToolsOnSessionStart(pi, (bus) => {
129
+ tools.register(bus);
130
+ });
108
131
  }
@@ -2,11 +2,11 @@ import { mkdir, readdir, stat } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
 
5
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
5
  import { Type } from "typebox";
7
6
  import { Value } from "typebox/value";
8
7
 
9
8
  import { type ToolPendant } from "../../lib/pendant.js";
9
+ import type { ToolBus } from "../../lib/tool-bus.js";
10
10
  import {
11
11
  type GhClient,
12
12
  GhError,
@@ -203,8 +203,8 @@ export async function downloadReleaseAssets(
203
203
  };
204
204
  }
205
205
 
206
- export function addDownloadReleaseAssetsTool(_gh: GhClient, pi: ExtensionAPI) {
207
- pi.registerTool({
206
+ export function addDownloadReleaseAssetsTool(_gh: GhClient, bus: ToolBus) {
207
+ bus.register({
208
208
  name: "download-github-release-assets",
209
209
  label: "GitHub Release Download",
210
210
  description:
@@ -1,6 +1,6 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
 
3
+ import type { ToolBus } from "../../lib/tool-bus.js";
4
4
  import {
5
5
  type GhClient,
6
6
  resolveRepo,
@@ -30,8 +30,8 @@ async function workflowJobs(gh: GhClient, call: ToolCall<RunIdParams>): Promise<
30
30
  return result;
31
31
  }
32
32
 
33
- export function addGetWorkflowJobsTool(gh: GhClient, pi: ExtensionAPI) {
34
- pi.registerTool({
33
+ export function addGetWorkflowJobsTool(gh: GhClient, bus: ToolBus) {
34
+ bus.register({
35
35
  name: "get-github-workflow-jobs",
36
36
  label: "GitHub Workflow Jobs",
37
37
  description:
@@ -1,6 +1,6 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
 
3
+ import type { ToolBus } from "../../lib/tool-bus.js";
4
4
  import {
5
5
  type GhClient,
6
6
  type ListFilters,
@@ -24,8 +24,8 @@ async function listIssues(gh: GhClient, call: ToolCall<ListFilters>): Promise<To
24
24
  return result;
25
25
  }
26
26
 
27
- export function addListIssuesTool(gh: GhClient, pi: ExtensionAPI) {
28
- pi.registerTool({
27
+ export function addListIssuesTool(gh: GhClient, bus: ToolBus) {
28
+ bus.register({
29
29
  name: "list-github-issues",
30
30
  label: "GitHub Issues List",
31
31
  description:
@@ -1,6 +1,6 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
 
3
+ import type { ToolBus } from "../../lib/tool-bus.js";
4
4
  import {
5
5
  type GhClient,
6
6
  type ListFilters,
@@ -24,8 +24,8 @@ async function listPrs(gh: GhClient, call: ToolCall<ListFilters>): Promise<ToolR
24
24
  return result;
25
25
  }
26
26
 
27
- export function addListPrsTool(gh: GhClient, pi: ExtensionAPI) {
28
- pi.registerTool({
27
+ export function addListPrsTool(gh: GhClient, bus: ToolBus) {
28
+ bus.register({
29
29
  name: "list-github-prs",
30
30
  label: "GitHub PRs List",
31
31
  description:
@@ -1,10 +1,10 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
 
3
+ import type { ToolBus } from "../../lib/tool-bus.js";
4
4
  import { type GhClient, ghExec, repoArgs, subtitlePendant, toToolResult } from "../base.js";
5
5
 
6
- export function addListReleasesTool(_gh: GhClient, pi: ExtensionAPI) {
7
- pi.registerTool({
6
+ export function addListReleasesTool(_gh: GhClient, bus: ToolBus) {
7
+ bus.register({
8
8
  name: "list-github-releases",
9
9
  label: "GitHub Releases List",
10
10
  description: "List GitHub releases.",
@@ -1,10 +1,10 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
 
3
+ import type { ToolBus } from "../../lib/tool-bus.js";
4
4
  import { type GhClient, ghExec, repoArgs, subtitlePendant, toToolResult } from "../base.js";
5
5
 
6
- export function addListWorkflowRunsTool(_gh: GhClient, pi: ExtensionAPI) {
7
- pi.registerTool({
6
+ export function addListWorkflowRunsTool(_gh: GhClient, bus: ToolBus) {
7
+ bus.register({
8
8
  name: "list-github-workflow-runs",
9
9
  label: "GitHub Workflow Runs",
10
10
  description: "List GitHub Actions workflow runs.",
@@ -3,16 +3,15 @@
3
3
  * each executed step's block inside it, so the model can read the exact line
4
4
  * range of the step it needs.
5
5
  */
6
-
7
6
  import { mkdir, readFile, writeFile } from "node:fs/promises";
8
7
  import { homedir } from "node:os";
9
8
  import { dirname, join } from "node:path";
10
9
 
11
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
12
10
  import { Type } from "typebox";
13
11
 
14
12
  import type { RunJob } from "../../lib/github.js";
15
13
  import { createSeqState } from "../../lib/seq-state.js";
14
+ import type { ToolBus } from "../../lib/tool-bus.js";
16
15
  import {
17
16
  type GhClient,
18
17
  ghExec,
@@ -422,8 +421,8 @@ async function ciLogs(gh: GhClient, call: ToolCall<JobIdParams>): Promise<ToolRe
422
421
  };
423
422
  }
424
423
 
425
- export function addReadCiLogsTool(gh: GhClient, pi: ExtensionAPI) {
426
- pi.registerTool({
424
+ export function addReadCiLogsTool(gh: GhClient, bus: ToolBus) {
425
+ bus.register({
427
426
  name: "read-github-ci-logs",
428
427
  label: "GitHub CI Logs",
429
428
  description:
@@ -1,10 +1,10 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
 
3
+ import type { ToolBus } from "../../lib/tool-bus.js";
4
4
  import { type GhClient, ghExec, repoArgs, subtitlePendant, toToolResultJson } from "../base.js";
5
5
 
6
- export function addReadIssueCommentsTool(_gh: GhClient, pi: ExtensionAPI) {
7
- pi.registerTool({
6
+ export function addReadIssueCommentsTool(_gh: GhClient, bus: ToolBus) {
7
+ bus.register({
8
8
  name: "read-github-issue-comments",
9
9
  label: "GitHub Issue Comments",
10
10
  description: "Get comments on a GitHub issue.",
@@ -1,10 +1,10 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
 
3
+ import type { ToolBus } from "../../lib/tool-bus.js";
4
4
  import { type GhClient, ghExec, repoArgs, subtitlePendant, toToolResultJson } from "../base.js";
5
5
 
6
- export function addReadIssueTool(_gh: GhClient, pi: ExtensionAPI) {
7
- pi.registerTool({
6
+ export function addReadIssueTool(_gh: GhClient, bus: ToolBus) {
7
+ bus.register({
8
8
  name: "read-github-issue",
9
9
  label: "GitHub Issue",
10
10
  description: "Get details of a GitHub issue by number.",
@@ -1,6 +1,6 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
 
3
+ import type { ToolBus } from "../../lib/tool-bus.js";
4
4
  import {
5
5
  ghApiList,
6
6
  type GhClient,
@@ -11,8 +11,8 @@ import {
11
11
  toToolResultJson,
12
12
  } from "../base.js";
13
13
 
14
- export function addReadPrCommentsTool(_gh: GhClient, pi: ExtensionAPI) {
15
- pi.registerTool({
14
+ export function addReadPrCommentsTool(_gh: GhClient, bus: ToolBus) {
15
+ bus.register({
16
16
  name: "read-github-pr-comments",
17
17
  label: "GitHub PR Comments",
18
18
  description:
@@ -1,10 +1,10 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
 
3
+ import type { ToolBus } from "../../lib/tool-bus.js";
4
4
  import { type GhClient, ghExec, repoArgs, subtitlePendant, toToolResult } from "../base.js";
5
5
 
6
- export function addReadPrDiffTool(_gh: GhClient, pi: ExtensionAPI) {
7
- pi.registerTool({
6
+ export function addReadPrDiffTool(_gh: GhClient, bus: ToolBus) {
7
+ bus.register({
8
8
  name: "read-github-pr-diff",
9
9
  label: "GitHub PR Diff",
10
10
  description: "Get the diff of a GitHub pull request.",
@@ -1,6 +1,6 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
 
3
+ import type { ToolBus } from "../../lib/tool-bus.js";
4
4
  import {
5
5
  type GhClient,
6
6
  mergeChecks,
@@ -52,8 +52,8 @@ export async function prStatus(gh: GhClient, call: ToolCall<PrStatusParams>): Pr
52
52
  };
53
53
  }
54
54
 
55
- export function addReadPrStatusTool(gh: GhClient, pi: ExtensionAPI) {
56
- pi.registerTool({
55
+ export function addReadPrStatusTool(gh: GhClient, bus: ToolBus) {
56
+ bus.register({
57
57
  name: "read-github-pr-status",
58
58
  label: "GitHub PR Status",
59
59
  description:
@@ -1,10 +1,10 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
 
3
+ import type { ToolBus } from "../../lib/tool-bus.js";
4
4
  import { type GhClient, ghExec, repoArgs, subtitlePendant, toToolResultJson } from "../base.js";
5
5
 
6
- export function addReadPrTool(_gh: GhClient, pi: ExtensionAPI) {
7
- pi.registerTool({
6
+ export function addReadPrTool(_gh: GhClient, bus: ToolBus) {
7
+ bus.register({
8
8
  name: "read-github-pr",
9
9
  label: "GitHub PR",
10
10
  description: "Get details of a GitHub pull request by number.",
@@ -1,10 +1,10 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
 
3
+ import type { ToolBus } from "../../lib/tool-bus.js";
4
4
  import { type GhClient, ghExec, repoArgs, subtitlePendant, toToolResult } from "../base.js";
5
5
 
6
- export function addReadReleaseTool(_gh: GhClient, pi: ExtensionAPI) {
7
- pi.registerTool({
6
+ export function addReadReleaseTool(_gh: GhClient, bus: ToolBus) {
7
+ bus.register({
8
8
  name: "read-github-release",
9
9
  label: "GitHub Release",
10
10
  description: "Get details of a specific GitHub release by tag.",
@@ -1,10 +1,10 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
 
3
+ import type { ToolBus } from "../../lib/tool-bus.js";
4
4
  import { type GhClient, ghExec, subtitlePendant, toToolResult } from "../base.js";
5
5
 
6
- export function addReadRepoTool(_gh: GhClient, pi: ExtensionAPI) {
7
- pi.registerTool({
6
+ export function addReadRepoTool(_gh: GhClient, bus: ToolBus) {
7
+ bus.register({
8
8
  name: "read-github-repo",
9
9
  label: "GitHub Repo",
10
10
  description: "Get repository information.",
@@ -1,6 +1,6 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
 
3
+ import type { ToolBus } from "../../lib/tool-bus.js";
4
4
  import {
5
5
  type GhClient,
6
6
  resolveRepo,
@@ -53,8 +53,8 @@ async function waitCommitChecks(
53
53
  });
54
54
  }
55
55
 
56
- export function addWaitCommitChecksTool(gh: GhClient, pi: ExtensionAPI) {
57
- pi.registerTool({
56
+ export function addWaitCommitChecksTool(gh: GhClient, bus: ToolBus) {
57
+ bus.register({
58
58
  name: "wait-github-commit-checks",
59
59
  label: "Watch GitHub Commit Checks",
60
60
  description:
@@ -1,7 +1,7 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
  import { Value } from "typebox/value";
4
3
 
4
+ import type { ToolBus } from "../../lib/tool-bus.js";
5
5
  import {
6
6
  type GhClient,
7
7
  ghExec,
@@ -55,8 +55,8 @@ async function waitPrChecks(gh: GhClient, call: ToolCall<PrChecksWaitParams>): P
55
55
  });
56
56
  }
57
57
 
58
- export function addWaitPrChecksTool(gh: GhClient, pi: ExtensionAPI) {
59
- pi.registerTool({
58
+ export function addWaitPrChecksTool(gh: GhClient, bus: ToolBus) {
59
+ bus.register({
60
60
  name: "wait-github-pr-checks",
61
61
  label: "Watch GitHub PR Checks",
62
62
  description:
@@ -1,11 +1,11 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { Type } from "typebox";
3
2
 
4
3
  import { runGh } from "../../lib/gh-process.js";
4
+ import type { ToolBus } from "../../lib/tool-bus.js";
5
5
  import { type GhClient, repoArgs, subtitlePendant } from "../base.js";
6
6
 
7
- export function addWatchRunTool(_gh: GhClient, pi: ExtensionAPI) {
8
- pi.registerTool({
7
+ export function addWatchRunTool(_gh: GhClient, bus: ToolBus) {
8
+ bus.register({
9
9
  name: "watch-github-run",
10
10
  label: "Watch GitHub Workflow Run",
11
11
  description:
package/src/index.ts ADDED
@@ -0,0 +1,120 @@
1
+ /**
2
+ * 本仓库的统一扩展入口:所有工具都在这里注册。
3
+ *
4
+ * 为什么是单一入口:pi 给每个扩展入口单独的模块图,模块级状态不跨入口共享。
5
+ * 工具集中在一处之后,同一入口内的模块(codemode 等)才能看到并直接调用
6
+ * 全部工具;`personalExtensions` 配置(`fileIo` / `fileIoByModel` /
7
+ * `disabledTools` / `enabledTools`)也才有唯一的求值点。
8
+ *
9
+ * 注册时机:pi 在每次会话启动(启动、/new、resume、fork、/reload)时重建扩展,
10
+ * 所以在 `session_start` 里注册即「每个会话按自己的模型判定一次」。模型在扩展
11
+ * 加载期读不到(`ctx.model` 只在事件里),这是注册放在 session_start 的原因。
12
+ *
13
+ * 模块划分:两套文件 IO 工具集(claude-code / opencode)由 `ToolUnit` 表描述,
14
+ * 其余工具模块暴露 `createXxx(pi)`;各模块的默认导出保留给「按路径单独加载」
15
+ * (手工 `-e`),本入口直接调用 createXxx,因此一个实例里只有一份模块状态。
16
+ */
17
+
18
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
19
+
20
+ import { createAftTools } from "./aft/index.js";
21
+ import { createClaudeCodeFileTools } from "./claude-code/files.js";
22
+ import { createGithubTools } from "./gh/index.js";
23
+ import type { ToolBus } from "./lib/tool-bus.js";
24
+ import { createToolRegistration } from "./lib/tool-registration.js";
25
+ import { createToolServices } from "./lib/tool-services.js";
26
+ import { TOOL_UNITS, type ToolUnitDeps } from "./lib/tool-units.js";
27
+ import { resolveFileIoToolset } from "./lib/tools-config.js";
28
+ import { createOpencodeFileTools } from "./opencode/files.js";
29
+ import { createSpawnAgentTool } from "./spawn-agent.js";
30
+ import { createTalkTools } from "./talk/index.js";
31
+ import { createVisionTools } from "./vision-agent.js";
32
+ import { createWebFetch } from "./web/fetch.js";
33
+ import { createWebSearchTool } from "./web/search.js";
34
+
35
+ /** 只做工具注册、以及自己那些与会话无关的钩子的模块。 */
36
+ interface ToolModule {
37
+ register(bus: ToolBus): void;
38
+ }
39
+
40
+ export default function personalExtensions(pi: ExtensionAPI): void {
41
+ const registration = createToolRegistration(pi);
42
+ // 共享服务要在 registration 之后创建:LSP manager 的 session_start handler 得
43
+ // 排在「记录本会话模型」的 handler 之后。
44
+ const services = createToolServices(pi);
45
+
46
+ // 与文件工具集无关的模块(各自在 create 里注册自己的会话钩子与提示)。
47
+ const modules: { name: string; module: ToolModule }[] = [];
48
+ const addModule = (name: string, module: ToolModule | undefined): void => {
49
+ if (module) {
50
+ modules.push({ name, module });
51
+ }
52
+ };
53
+ addModule("github", createGithubTools());
54
+ addModule("vision", createVisionTools());
55
+ addModule("talk", createTalkTools(pi));
56
+ addModule("spawn-agent", createSpawnAgentTool());
57
+ addModule("web_search", createWebSearchTool());
58
+ addModule("web_fetch", createWebFetch(pi));
59
+ const aft = createAftTools(pi);
60
+
61
+ // 注册期的失败攒起来,在会话启动时一次性上报(那里才有 UI)。
62
+ const warnings: string[] = [];
63
+
64
+ /** 单个模块/单元注册失败不影响其余:记下失败,继续注册。 */
65
+ function runModule<T>(name: string, run: () => T): T | undefined {
66
+ try {
67
+ return run();
68
+ } catch (error) {
69
+ warnings.push(`${name}: ${error instanceof Error ? error.message : String(error)}`);
70
+ return undefined;
71
+ }
72
+ }
73
+
74
+ registration.onSessionStart(async (bus, ctx) => {
75
+ // 配置解析期的警告是稳定的,注册期的失败每个会话重新收集(避免跨会话重复上报)。
76
+ warnings.length = 0;
77
+ warnings.push(...registration.config.warnings);
78
+
79
+ // 文件工具集跟着本会话的模型走(会话内切换模型不重建扩展,因此不重判)。
80
+ const kind = resolveFileIoToolset(registration.config, ctx.model);
81
+ const fileToolset =
82
+ kind === "opencode"
83
+ ? createOpencodeFileTools(pi, {
84
+ policy: services.policy,
85
+ bus,
86
+ manager: services.manager,
87
+ })
88
+ : createClaudeCodeFileTools(pi, {
89
+ policy: services.policy,
90
+ bus,
91
+ manager: services.manager,
92
+ });
93
+ services.setLspEnabledHandler((service) => fileToolset.onLspEnabled(bus, service));
94
+ runModule(kind, () => fileToolset.restoreReads(ctx));
95
+
96
+ const deps: ToolUnitDeps = {
97
+ pi,
98
+ bus,
99
+ policy: services.policy,
100
+ runtime: services.runtime,
101
+ fileToolset,
102
+ };
103
+ for (const unit of TOOL_UNITS[kind]) {
104
+ runModule(unit.tools.join("/"), () => unit.register(deps));
105
+ }
106
+
107
+ // aft 的二进制探测、bridge 状态与工具注册绑在一起(找不到二进制就不注册)。
108
+ await runModule("aft", () => aft.registerForSession(bus, ctx));
109
+ for (const { name, module } of modules) {
110
+ runModule(name, () => module.register(bus));
111
+ }
112
+
113
+ for (const warning of warnings) {
114
+ ctx.ui.notify(warning, "warning");
115
+ }
116
+ for (const { field, pattern } of registration.unmatchedPatterns()) {
117
+ ctx.ui.notify(`personalExtensions.${field}: pattern "${pattern}" matches no tool`, "warning");
118
+ }
119
+ });
120
+ }