mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 18:55:22 +00:00
- 组合包拥有、可随时重建的配置、日志与缓存统一放在 <agentDir>/my-pi/:permission-system/(policy.jsonc、hard-guards.jsonc、 logs/)、rtk-optimizer/config.json、tool-search/(config.json、 catalog-v1.json)与 system-prompt-cover.md。hashline.json、lsp.json 由外部扩展读取固定路径,是唯一例外。 - 新增 config/bundle-manifest.json 与 scripts/rebuild-bundle-config.mjs: 先校验全部来源与目标,在临时目录构建完整树,再整体删除旧 my-pi/ (符号链接只删链接)并原子替换,不备份。install.sh / update.sh 每次 运行都调用它;扩展自身不再播种或迁移配置。 - update.sh 删除 pi update 失败后的 git clean 恢复逻辑:Pi 1.3.0 会在 下一次 pi update 时依据中断标记自行恢复。同时删除 Tool Search settings.json 快照迁移与封面部署脚本。 - Tool Search 改为读取 my-pi/tool-search/config.json(源为 config/tool-search.json,须与 BUNDLE_TOOL_SEARCH_DEFAULTS 一致), 不再读写 settings.json 的 toolSearch 段,移除 bundleDefaultsVersion。 - 默认权限策略开启 debugLog。 - 旧位置的残留文件不迁移、不清理;AGENTS.md / README 同步新约束。 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
58 lines
2.1 KiB
TypeScript
58 lines
2.1 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
/** File name of the bundle-managed system prompt cover inside `<agentDir>/my-pi/`. */
|
|
export const COVER_FILE_NAME = "system-prompt-cover.md";
|
|
|
|
/**
|
|
* Matches the newline-delimited boundary before the first top-level prompt section.
|
|
*
|
|
* Pi renders the system prompt as `<preamble>` followed by sections wrapped in tags of
|
|
* the same name (for example `<tools>`). Nested markup such as
|
|
* `<project_instructions path="...">` carries attributes and therefore never matches a
|
|
* bare tag line, so the first match is the end of the preamble.
|
|
*/
|
|
const FIRST_SECTION_BOUNDARY = /\n<[a-z][a-z0-9_-]*>\n/;
|
|
|
|
/** Resolve the cover path inside a given agent directory's bundle-owned `my-pi/` tree. */
|
|
export function resolveCoverPath(agentDir: string): string {
|
|
return join(agentDir, "my-pi", COVER_FILE_NAME);
|
|
}
|
|
|
|
/**
|
|
* Read the bundle-managed cover file.
|
|
*
|
|
* Returns `undefined` when the file is absent, unreadable, or blank so the caller can
|
|
* leave Pi's own default prompt untouched instead of forcing an empty preamble.
|
|
*/
|
|
export function readCoverFile(coverPath: string): string | undefined {
|
|
let raw: string;
|
|
try {
|
|
raw = readFileSync(coverPath, "utf8");
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
const trimmed = raw.replace(/\s+$/, "");
|
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
}
|
|
|
|
/**
|
|
* Replace the rendered prompt's preamble with `cover`, keeping everything after the first
|
|
* top-level section byte-for-byte identical.
|
|
*
|
|
* Pi builds the preamble at the top of the rendered prompt, so swapping that prefix is
|
|
* what preserves section order: options-based replacement (`customPrompt`) would drop
|
|
* `tools`/`rules`/`docs` and rebuild them at the end, reordering the prompt. Splicing the
|
|
* rendered text keeps the existing order untouched.
|
|
*
|
|
* A rendered prompt without any top-level section degenerates to the cover alone.
|
|
*/
|
|
export function spliceCover(rendered: string, cover: string): string {
|
|
const trimmed = cover.replace(/\s+$/, "");
|
|
const boundary = rendered.search(FIRST_SECTION_BOUNDARY);
|
|
if (boundary < 0) {
|
|
return trimmed;
|
|
}
|
|
return trimmed + rendered.slice(boundary);
|
|
}
|