@trim21/personal-pi-extensions 0.1.664 → 0.1.669

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trim21/personal-pi-extensions",
3
- "version": "0.1.664",
3
+ "version": "0.1.669",
4
4
  "type": "module",
5
5
  "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
6
  "keywords": [
@@ -88,7 +88,7 @@
88
88
  "node": ">=24.14"
89
89
  },
90
90
  "dependencies": {
91
- "@cortexkit/aft-bridge": "0.58.0",
91
+ "@cortexkit/aft-bridge": "0.58.2",
92
92
  "@mozilla/readability": "^0.6.0",
93
93
  "@parcel/watcher": "^2.6.0",
94
94
  "@vscode/tree-sitter-wasm": "^0.3.1",
@@ -105,6 +105,6 @@
105
105
  "web-tree-sitter": "^0.27.0"
106
106
  },
107
107
  "optionalDependencies": {
108
- "@cortexkit/aft-linux-x64": "0.58.0"
108
+ "@cortexkit/aft-linux-x64": "0.58.2"
109
109
  }
110
110
  }
package/src/aft/index.ts CHANGED
@@ -8,6 +8,11 @@
8
8
  * openai_compatible / ollama 且有 base_url)时注册;aft 默认的本地 ONNX
9
9
  * fastembed 后端不使用。
10
10
  *
11
+ * 项目根是**会话工作目录**(`session_start` 的 `ctx.cwd`),不是 pi 进程的启动
12
+ * 目录:引擎按项目根建索引与调用图存储,用错会把用户家目录当项目根并自动关闭这
13
+ * 些能力。扩展自己只读用户级 aft.jsonc(路径与会话目录无关),项目级 aft.jsonc
14
+ * 由 bridge 按项目根自行读取。
15
+ *
11
16
  * bridge 状态(日志 + 常驻 aft 子进程)的生命周期跟 session 走:session_start
12
17
  * 时先解析 aft 二进制(含 GitHub release auto-download 兜底)——找不到就
13
18
  * notify warning 且不注册任何 aft 工具,避免模型看到只会抛 "not initialized"
@@ -19,7 +24,7 @@
19
24
  * pi -e ./aft/index.ts
20
25
  */
21
26
 
22
- import { resolveCortexKitConfigPaths } from "@cortexkit/aft-bridge";
27
+ import { resolveCortexKitUserConfigPath } from "@cortexkit/aft-bridge";
23
28
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
24
29
 
25
30
  import type { ToolBus } from "../lib/tool-bus.js";
@@ -27,6 +32,7 @@ import { registerToolsOnSessionStart } from "../lib/tool-registration.js";
27
32
  import { createAftState, findBinary, resolveSessionId, shutdownAftPool } from "./bridge.js";
28
33
  import { loadAftConfig } from "./config.js";
29
34
  import {
35
+ type AftToolContext,
30
36
  registerCallgraphTool,
31
37
  registerOutlineTool,
32
38
  registerSearchTool,
@@ -35,14 +41,15 @@ import {
35
41
 
36
42
  /**
37
43
  * aft 工具集:入口用它。`registerForSession` 必须在会话启动时调用——二进制
38
- * 探测、semantic_search 的提示、bridge 状态的创建都在里面;找不到二进制就不
39
- * 注册任何工具(避免模型看到只会抛 "not initialized" 的死工具)。
44
+ * 探测、semantic_search 的提示、bridge 状态(项目根 = 本会话的 `ctx.cwd`)的
45
+ * 创建都在里面;找不到二进制就不注册任何工具(避免模型看到只会抛
46
+ * "not initialized" 的死工具)。
40
47
  */
41
48
  export function createAftTools(pi: ExtensionAPI): {
42
49
  registerForSession(bus: ToolBus, ctx: ExtensionContext): Promise<void>;
43
50
  } {
44
- const cwd = process.cwd();
45
- const cfg = loadAftConfig(resolveCortexKitConfigPaths(cwd).userConfigPath);
51
+ // 扩展自己的门控只读用户级配置:项目级 aft.jsonc 由 bridge 按会话工作目录读。
52
+ const cfg = loadAftConfig(resolveCortexKitUserConfigPath());
46
53
  // bridge 状态跟 session 生命周期走,作用域就是本工厂闭包,不落到模块级。
47
54
  let state: Awaited<ReturnType<typeof createAftState>> | null = null;
48
55
 
@@ -55,7 +62,7 @@ export function createAftTools(pi: ExtensionAPI): {
55
62
  return state;
56
63
  };
57
64
 
58
- const toolCtx = { cwd, getState };
65
+ const toolCtx: AftToolContext = { getState };
59
66
 
60
67
  async function registerForSession(bus: ToolBus, ctx: ExtensionContext): Promise<void> {
61
68
  if (!cfg.enabled) {
@@ -91,7 +98,7 @@ export function createAftTools(pi: ExtensionAPI): {
91
98
  registerSearchTool(bus, toolCtx);
92
99
  }
93
100
 
94
- state = await createAftState(cwd, resolveSessionId(ctx), binaryPath, cfg.semanticRemote);
101
+ state = await createAftState(ctx.cwd, resolveSessionId(ctx), binaryPath, cfg.semanticRemote);
95
102
  }
96
103
 
97
104
  // 释放当前 session 的 bridge 状态。session_shutdown 是 pi 的正常生命周期;
package/src/aft/tools.ts CHANGED
@@ -57,13 +57,13 @@ const AFT_GUIDELINES = readFileSync(
57
57
  ).trim();
58
58
 
59
59
  export interface AftToolContext {
60
- cwd: string;
61
60
  /** 当前 session 的 bridge 状态;session 未初始化时抛错。 */
62
61
  getState(): AftState;
63
62
  }
64
63
 
65
64
  function bridgeFor(ctx: AftToolContext): AftProjectTransport {
66
- return ctx.getState().pool.pool.getBridge(ctx.cwd);
65
+ const { pool } = ctx.getState();
66
+ return pool.pool.getBridge(pool.projectRoot);
67
67
  }
68
68
 
69
69
  /**
@@ -152,7 +152,7 @@ export function registerOutlineTool(bus: ToolBus, ctx: AftToolContext): void {
152
152
  filesMode = stats?.isDirectory() ?? false;
153
153
  }
154
154
  const rawArgs = compactArgs({
155
- target: filesMode ? target : resolved,
155
+ target: resolved,
156
156
  files: filesMode || undefined,
157
157
  includeTests: params.includeTests,
158
158
  });
@@ -3,8 +3,9 @@
3
3
  * 的能力是调用 `tools.*`——每个嵌套调用都由主线程经本仓库的工具总线执行,因此工具实现
4
4
  * 内部的审批(工作区外写入、Bash 沙箱提权等)照常生效;codemode 不再加自己的确认层。
5
5
  *
6
- * 可调用集合:总线上实际注册的工具减去 codemode 自身,执行时再与 active 列表求交——
7
- * pi 自己的 `defaultTools` / `--tools` / 子代理白名单的排除因此同样生效。
6
+ * 可调用集合:总线上实际注册的工具减去 codemode 自身与 spawn-agent,执行时再与 active
7
+ * 列表求交——pi 自己的 `defaultTools` / `--tools` / 子代理白名单的排除因此同样生效。
8
+ * spawn-agent 被排除是因为它启动一个新的隔离会话、成本与运行时长都不适合放进脚本编排。
8
9
  *
9
10
  * wasm 在注册这个工具时编译一次(`createCodemodeSandbox`),worker 复用编译结果。
10
11
  */
@@ -26,6 +27,9 @@ import { CODEMODE_SOURCE_GRAMMAR, DEFAULT_OUTPUT_TOKENS, parseCodemodeSource } f
26
27
 
27
28
  export const CODEMODE_TOOL_NAME = "codemode";
28
29
 
30
+ /** 不暴露给脚本的工具:codemode 自身(防递归)与 spawn-agent(见文件头注释)。 */
31
+ const EXCLUDED_TOOL_NAMES: ReadonlySet<string> = new Set([CODEMODE_TOOL_NAME, "spawn-agent"]);
32
+
29
33
  /** 估计 token 用的字符数(与 pi 一致)。 */
30
34
  const CHARS_PER_TOKEN = 4;
31
35
 
@@ -56,7 +60,7 @@ function allowedToolNames(pi: ExtensionAPI): Set<string> | undefined {
56
60
  function collectTools(bus: ToolBus, allowed: Set<string> | undefined): CallableTool[] {
57
61
  return bus
58
62
  .list()
59
- .filter((definition) => definition.name !== CODEMODE_TOOL_NAME)
63
+ .filter((definition) => !EXCLUDED_TOOL_NAMES.has(definition.name))
60
64
  .filter((definition) => allowed === undefined || allowed.has(definition.name))
61
65
  .map((definition) => ({
62
66
  name: definition.name,
@@ -131,7 +131,10 @@ const spawnAgentSchema = Type.Object({
131
131
  description:
132
132
  "Name of the subagent type to invoke. Choose one of the available subagent types listed in your system prompt.",
133
133
  }),
134
- task: Type.String({ description: "Task to delegate to the subagent" }),
134
+ task: Type.String({
135
+ description:
136
+ "Self-contained task for the subagent, which sees none of this conversation: state the repository path, the exact question, and what the answer must contain (file paths with line numbers).",
137
+ }),
135
138
  });
136
139
 
137
140
  // ── result types ─────────────────────────────────────────────────────────────
@@ -499,9 +502,19 @@ export async function runAgent(
499
502
  export function formatAgentListSection(agents: AgentConfig[]): string {
500
503
  const lines = agents.map((a) => `- \`${a.name}\`: ${a.description}`);
501
504
  return [
505
+ "### Delegating to subagents",
506
+ "",
507
+ "Delegate a focused, read-only research task when the answer is a summary rather than source you must quote or edit:",
508
+ "",
509
+ "- locating code whose position is unknown, or answering a question that spans several files;",
510
+ "- several independent lookups at once — issue one `spawn-agent` call per lookup in the same message and they run concurrently;",
511
+ "- keeping the searching and reading out of your own context window.",
512
+ "",
513
+ "Do not delegate work you are about to do yourself — a single file you will edit, a change whose evidence must be the exact source text, or anything that writes. A subagent sees none of this conversation, so its task must be self-contained. Treat its answer as a pointer and read the location it names before acting on it.",
514
+ "",
502
515
  "### Available subagents",
503
516
  "",
504
- "You can delegate tasks to the following subagent types by calling the `spawn-agent` tool with their name in the `agent` parameter:",
517
+ "Pass one of these names as the `agent` parameter:",
505
518
  "",
506
519
  ...lines,
507
520
  ].join("\n");
@@ -526,9 +539,11 @@ export function createSpawnAgentTool(): { register(bus: ToolBus): void } {
526
539
  bus.register<typeof spawnAgentSchema, SubagentDetails>({
527
540
  name: "spawn-agent",
528
541
  label: "spawn-agent",
542
+ promptSnippet: "Delegate focused research to a subagent with its own context window",
529
543
  description: [
530
- "Delegate a task to a subagent that runs in an isolated session with its own context window, inside this pi process rather than a separate one.",
531
- "The call blocks until the subagent finishes its turn; its final output comes back as the tool result.",
544
+ "Delegate a self-contained research task to a subagent that runs in an isolated session with its own context window, inside this pi process rather than a separate one.",
545
+ "The call blocks until the subagent finishes its turn and returns only its final answer: its intermediate tool calls are not visible to you.",
546
+ "Several `spawn-agent` calls issued in the same message run concurrently.",
532
547
  "The `agent` parameter must be one of the available subagent types listed in the system prompt.",
533
548
  `Subagents run read-only (${DEFAULT_TOOLS.join(", ")}) unless the agent declares an explicit toolset.`,
534
549
  ].join(" "),