mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
feat(prompt): 重新注册 bash,把"不要用 bash 搜索"写进工具描述
真实会话里模型仍习惯性先调 bash grep,被拒后才改用 grep 工具;系统提示词里已有 4 处 提醒,但模型下调用决定时看的是工具 schema 描述,而 Pi 内置 bash 描述对此只字未提。 - 新增 extensions/bash-override.ts:用 Pi 的 createBashToolDefinition 重新注册 bash, 执行、输出结构与 codemode 说明完全沿用上游;shellPath/commandPrefix 按 ctx.cwd 经 SettingsManager 惰性解析,与内置一致 - tools.jsonc 新增 descriptionAppend 字段,由 prompt-guard 注册时拼接到上游描述后 - bash 增加命令→工具对照规则(grep -n / grep -rl / find -name),并提示 文件清单用 $(git ls-files) 传参、不要接 xargs(会触发权限审核) Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
2b21163a18
commit
87940da51a
@@ -25,6 +25,7 @@
|
||||
## 各 wrapper 约定
|
||||
|
||||
- `fff-override.ts`:强制 FFF 官方 `override` 模式,统一接管 `find`、`grep` 和 `@` 补全;`multi_grep` 按上游保持关闭(加载前删除 `PI_FFF_MULTIGREP` 环境变量)。FFF 独占本地字面搜索,远端服务器搜索由 `ssh_find` / `ssh_grep` 负责,RTK 不得接管搜索工具(见 `pi-rtk-optimizer/AGENTS.md`)。显式 CLI flag 仍遵循 FFF 官方优先级。
|
||||
- `bash-override.ts`:用 `createBashToolDefinition` 重新注册内置 `bash`,目的只是让 `tools.jsonc` 的 `descriptionAppend` 落到模型可见描述里(prompt-guard 在注册时拼接)。ExtensionContext 不暴露 session settings,因此 `shellPath`/`commandPrefix` 在首次 execute 时按 `ctx.cwd` 经 `SettingsManager.create(ctx.cwd, getAgentDir())` 惰性解析并按 cwd 缓存,metadata 来自 `process.cwd()` 的定义;执行、输出结构与 codemode 说明完全委托上游定义,不得改动上游执行逻辑,也不得用 prepareLoadout 或 provider-payload 改写实现同样效果。
|
||||
- `codegraph.ts` + `codegraph-client.ts`:注册原生 `codegraph_explore` 工具,按索引根惰性常驻 `codegraph serve --mcp -p <root>` 子进程,经组合包自有 stdio 逐行 JSON-RPC 客户端直连(不经 shell、不带 pi-mcp-adapter 或 MCP SDK);serve 进程内置文件监听自动同步索引,无结果缓存。子进程 10 分钟无调用自动关闭、会话结束统一关闭;调用中死亡换新客户端透明重试一次;其启动的上游 daemon(`.codegraph/daemon.sock`)退出后可多驻留 ≤5 分钟,属上游行为,组合包不管理。
|
||||
- CodeGraph 是机器级依赖,`install.sh`/`update.sh` 经官方安装器管理 `codegraph` CLI,组合包不内置其运行时。安装、升级和普通启动流程不得执行 `codegraph init`;只有 `codegraph_explore` 在未初始化项目上被调用、且用户在 `ctx.ui.confirm` 中明确确认后才可 `codegraph init` 当前目录,拒绝记入会话级 declined 不再重复询问;init 失败必须如实报告并让模型回退内置工具;不得改动 CodeGraph 更新或遥测设置。CLI 缺失、init 被拒或服务调用失败都不得抛出,必须以文本结果打回并引导模型改用 `find`/`grep`/`read`/LSP。已移除的 `plugin-init.ts`(原 `/plugin_init`)职责已并入本文件的惰性确认,不得恢复。
|
||||
- `hashline.ts` + `hashline-config.ts`:部署组合包权威 `config/pi-hashline-edit.json` 后加载 `pi-hashline-edit`;默认以 2 字符行哈希覆盖内置 `read` / `edit`,`grep` 与 `replaceText` 均关闭。它保证锚定编辑而不负责压缩,大文件上下文节省必须来自先定位/隔离分析、再做最小范围 `offset/limit` 读取。
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
createBashToolDefinition,
|
||||
type ExtensionAPI,
|
||||
getAgentDir,
|
||||
SettingsManager,
|
||||
} from "@earendil-works/pi-coding-agent";
|
||||
import { guardExtensionApi } from "./prompt-guard.ts";
|
||||
|
||||
type BashDefinition = ReturnType<typeof createBashToolDefinition>;
|
||||
|
||||
/** Re-register Pi's bash so tools.jsonc can append to its model-facing description; execution stays upstream. */
|
||||
export default function bashOverrideExtension(pi: ExtensionAPI): void {
|
||||
const sessionDefinitions = new Map<string, BashDefinition>();
|
||||
const sessionDefinition = (cwd: string): BashDefinition => {
|
||||
let definition = sessionDefinitions.get(cwd);
|
||||
if (definition === undefined) {
|
||||
const settings = SettingsManager.create(cwd, getAgentDir());
|
||||
definition = createBashToolDefinition(cwd, {
|
||||
shellPath: settings.getShellPath(),
|
||||
commandPrefix: settings.getShellCommandPrefix(),
|
||||
});
|
||||
sessionDefinitions.set(cwd, definition);
|
||||
}
|
||||
return definition;
|
||||
};
|
||||
|
||||
guardExtensionApi(pi).registerTool({
|
||||
...sessionDefinition(process.cwd()),
|
||||
execute: (toolCallId, params, signal, onUpdate, ctx) =>
|
||||
sessionDefinition(ctx?.cwd ?? process.cwd()).execute(
|
||||
toolCallId,
|
||||
params,
|
||||
signal,
|
||||
onUpdate,
|
||||
ctx,
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -1,11 +1,28 @@
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
import { loadToolCatalog, type ToolCatalog } from "./system-prompt-config.ts";
|
||||
import {
|
||||
loadToolCatalog,
|
||||
type ToolCatalog,
|
||||
type ToolCatalogEntry,
|
||||
} from "./system-prompt-config.ts";
|
||||
|
||||
type AnyMessage = { role?: string; content?: unknown } & Record<string, unknown>;
|
||||
|
||||
type AnyHandler = (event: never, ctx: never) => unknown;
|
||||
|
||||
/**
|
||||
* The description a tool registers with under its catalog entry: a `description`
|
||||
* override replaces the upstream text, a `descriptionAppend` is joined onto
|
||||
* whichever base wins (override first, upstream otherwise) with a blank line.
|
||||
*/
|
||||
export function catalogDescription(description: string, entry: ToolCatalogEntry): string {
|
||||
if ("inherit" in entry) return description;
|
||||
const base = typeof entry.description === "string" ? entry.description : description;
|
||||
return typeof entry.descriptionAppend === "string"
|
||||
? `${base.trimEnd()}\n\n${entry.descriptionAppend}`
|
||||
: base;
|
||||
}
|
||||
|
||||
export interface PromptGuardOptions {
|
||||
/**
|
||||
* Post-process the messages a wrapped `context` handler leaves behind.
|
||||
@@ -19,9 +36,11 @@ export interface PromptGuardOptions {
|
||||
* Proxy an `ExtensionAPI` for third-party extensions so they cannot bypass the
|
||||
* bundle's single system-prompt owner (`extensions/system-prompt.ts`):
|
||||
*
|
||||
* - `registerTool` applies the tool's `description` override from
|
||||
* `config/prompt/tools.jsonc` when the catalog carries one (lazily loaded;
|
||||
* a catalog error keeps the upstream description).
|
||||
* - `registerTool` applies the tool's `description` override and
|
||||
* `descriptionAppend` suffix from `config/prompt/tools.jsonc` when the
|
||||
* catalog carries them (lazily loaded; a catalog error keeps the upstream
|
||||
* description). An append composes onto the override when both are set, or
|
||||
* onto the upstream description when only the append is set.
|
||||
* - `before_agent_start` handlers keep every returned field except
|
||||
* `systemPrompt`, which is dropped.
|
||||
* - `context` handlers are post-processed through `opts.contextFilter`.
|
||||
@@ -42,10 +61,12 @@ export function guardExtensionApi(pi: ExtensionAPI, opts: PromptGuardOptions = {
|
||||
|
||||
const registerTool: ExtensionAPI["registerTool"] = (tool) => {
|
||||
const entry = catalog()?.tools[tool.name];
|
||||
if (entry !== undefined && !("inherit" in entry) && typeof entry.description === "string") {
|
||||
pi.registerTool({ ...tool, description: entry.description });
|
||||
} else {
|
||||
const description =
|
||||
entry === undefined ? tool.description : catalogDescription(tool.description, entry);
|
||||
if (description === tool.description) {
|
||||
pi.registerTool(tool);
|
||||
} else {
|
||||
pi.registerTool({ ...tool, description });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,12 @@ import { fileURLToPath } from "node:url";
|
||||
/** Entry in `config/prompt/tools.jsonc`. */
|
||||
export type ToolCatalogEntry =
|
||||
| { inherit: true }
|
||||
| { snippet?: string | null; guidelines?: string[]; description?: string };
|
||||
| {
|
||||
snippet?: string | null;
|
||||
guidelines?: string[];
|
||||
description?: string;
|
||||
descriptionAppend?: string;
|
||||
};
|
||||
|
||||
export interface ToolCatalog {
|
||||
version: 1;
|
||||
@@ -152,7 +157,7 @@ function isStringArray(value: unknown): value is string[] {
|
||||
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
||||
}
|
||||
|
||||
const TOOL_ENTRY_KEYS = new Set(["inherit", "snippet", "guidelines", "description"]);
|
||||
const TOOL_ENTRY_KEYS = new Set(["inherit", "snippet", "guidelines", "description", "descriptionAppend"]);
|
||||
|
||||
function validateToolEntry(name: string, raw: unknown): ToolCatalogEntry {
|
||||
if (!isPlainObject(raw)) {
|
||||
@@ -170,9 +175,9 @@ function validateToolEntry(name: string, raw: unknown): ToolCatalogEntry {
|
||||
return { inherit: true };
|
||||
}
|
||||
const hasField =
|
||||
"snippet" in raw || "guidelines" in raw || "description" in raw;
|
||||
"snippet" in raw || "guidelines" in raw || "description" in raw || "descriptionAppend" in raw;
|
||||
if (!hasField) {
|
||||
fail("tools.jsonc", `tool "${name}" needs "inherit": true or at least one of snippet/guidelines/description`);
|
||||
fail("tools.jsonc", `tool "${name}" needs "inherit": true or at least one of snippet/guidelines/description/descriptionAppend`);
|
||||
}
|
||||
if ("snippet" in raw && raw.snippet !== null && typeof raw.snippet !== "string") {
|
||||
fail("tools.jsonc", `tool "${name}".snippet must be a string or null`);
|
||||
@@ -183,10 +188,13 @@ function validateToolEntry(name: string, raw: unknown): ToolCatalogEntry {
|
||||
if ("description" in raw && typeof raw.description !== "string") {
|
||||
fail("tools.jsonc", `tool "${name}".description must be a string`);
|
||||
}
|
||||
if ("descriptionAppend" in raw && typeof raw.descriptionAppend !== "string") {
|
||||
fail("tools.jsonc", `tool "${name}".descriptionAppend must be a string`);
|
||||
}
|
||||
return raw as ToolCatalogEntry;
|
||||
}
|
||||
|
||||
function validateToolCatalog(raw: unknown): ToolCatalog {
|
||||
export function validateToolCatalog(raw: unknown): ToolCatalog {
|
||||
if (!isPlainObject(raw) || raw.version !== 1 || !isPlainObject(raw.tools)) {
|
||||
fail("tools.jsonc", 'expected { "version": 1, "tools": { ... } }');
|
||||
}
|
||||
@@ -321,8 +329,8 @@ export function isSubagentCustomPrompt(customPrompt: unknown): boolean {
|
||||
* Apply the tool catalog to the mutable prompt options: a `snippet` string
|
||||
* replaces the tool's `<tools>` line (`null` hides it), `guidelines` replaces
|
||||
* the tool's `<rules>` lines; `{inherit:true}` entries are left untouched.
|
||||
* `description` is not applied here — it is a registration-time concern handled
|
||||
* by `extensions/prompt-guard.ts`.
|
||||
* `description` and `descriptionAppend` are not applied here — they are
|
||||
* registration-time concerns handled by `extensions/prompt-guard.ts`.
|
||||
*
|
||||
* Returns the selected tool names that have no catalog entry at all.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user