Files
my-pi/install.sh
T
云服务部-叶林立 dec48dab6d feat: 新增系统提示词封面,并修正工具结果投影与常驻工具
系统提示词封面(新增能力):

- extensions/system-prompt-cover.ts:读取 ~/.pi/agent/SYSTEM_COVER.md,
  在 before_agent_start 返回 { systemPrompt },用文本拼接替换 Pi 默认
  系统提示词的开头,同时保留 tools / rules / docs / skills /
  project_context / cwd 全部分区及其原始顺序。
- 必须走 forceSystemPrompt 投影路径。customPrompt 选项会连带抑制
  tools / rules / docs 三个分区,而回填只能追加到末尾,无法恢复原顺序;
  且 forceSystemPrompt 优先级更高,二者同时存在时 customPrompt 会被
  静默忽略,因此 handler 不得写 customPrompt。
- 分割边界要求独占一行的裸标签:<project_instructions path="..."> 嵌套在
  <project_context> 内,只有裸标签才是分区边界。
- 注册在 tool-routing 之前,否则提示词已被压平,改 systemPromptOptions
  不再影响本轮请求。
- scripts/deploy-system-prompt-cover.mjs:同目录临时文件 + rename 原子
  覆盖,不保留备份;install.sh 从脚本目录、update.sh 从升级后组合包
  定位 helper,禁止回退到可能陈旧的脚本目录。
- 封面缺失、不可读或空白时静默 no-op,Pi 行为保持不变;封面每轮重新
  读取,修改后在下一轮生效,不影响已结束的历史对话。

pi-compaction 投影修正:

- 仅在「摘要 + Source 行」严格短于原文时才替换工具结果。此前命中即
  无条件替换,小结果反而被包壳放大;未替换的结果完整保留原始 content
  parts(含非文本部分)。
- deterministic.maxStubTokens 由 256 提升到 1024。

pi-tool-search 常驻工具:

- alwaysEnabled 增加 context_tree_query:恢复被投影的工具证据不应依赖
  一次 tool_search 激活,常驻同时保持请求前缀稳定。
- BUNDLE_TOOL_SEARCH_DEFAULTS_VERSION 由 3 提升到 4;
  scripts/migrate-tool-search-settings.mjs 新增 version-3 快照常量,
  使其与 version-2 一样可被完整精确匹配并迁移。
- 权限策略为 context_tree_query 显式 allow:它只读扩展自己的 segment
  索引、不触碰工作区,常驻后不应每轮触发 AutoReview 往返。该种子仅在
  全新安装时部署,已存在的用户 policy.jsonc 不会被覆盖。

文档与测试同步:

- AGENTS.md:新增 system-prompt-cover 开发约定,修正加载顺序、
  bundleDefaultsVersion、可迁移旧快照集合与常驻工具范围的描述。
- pi-tool-search 的 README / CHANGELOG 同步 version 4 与新增常驻工具。
- tests/system-prompt-cover.test.ts:覆盖顺序保持、尾部逐字节不变、
  嵌套 project_instructions 不误判、部署幂等与覆盖、缺源时非零退出。
- 修正 pi-tool-search/test/config.test.ts 与
  tests/tool-search-settings-migration.test.ts 中写死的 version 3 与
  工具列表断言。
- update-git-clean-recovery / tool-search-settings-migration 的 mock
  组合包补拷 scripts/,与真实发布包结构一致,避免 helper 定位失败。

测试:根 61/61、pi-compaction 36/36 + tsc、pi-tool-search 16/16 + tsc、
pi-permission-system 2577/2577;sh -n 与 git diff --check 通过。
2026-10-15 18:02:03 -07:00

488 lines
14 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
set -u
SCRIPT_DIR=$(CDPATH=; cd "$(dirname "$0")" && pwd) || exit 1
MANAGED_CONFIG_HELPERS=$SCRIPT_DIR/scripts/managed-config.sh
if [ ! -f "$MANAGED_CONFIG_HELPERS" ]; then
printf '错误: 找不到受管配置辅助脚本:%s\n' "$MANAGED_CONFIG_HELPERS" >&2
exit 1
fi
# shellcheck source=scripts/managed-config.sh
. "$MANAGED_CONFIG_HELPERS"
PACKAGE_SOURCE=${PI_PACKAGE_SOURCE:-git:git@bitbucket.org:siakitem/my-pi.git}
POWERLEVEL10K_PRESET_FILE=$SCRIPT_DIR/config/p10k.zsh
CODEGRAPH_INSTALL_URL=https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh
KITTY_INSTALL_URL=https://sw.kovidgoyal.net/kitty/installer.sh
OH_MY_ZSH_INSTALL_URL=https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
POWERLEVEL10K_REPOSITORY=https://github.com/romkatv/powerlevel10k.git
ZSH_AUTOSUGGESTIONS_REPOSITORY=https://github.com/zsh-users/zsh-autosuggestions.git
ZSH_SYNTAX_HIGHLIGHTING_REPOSITORY=https://github.com/zsh-users/zsh-syntax-highlighting.git
install_failed=0
say() {
printf '%s\n' "$*"
}
warn() {
printf '警告: %s\n' "$*" >&2
}
ask_yes_no() {
prompt=$1
printf '%s [y/N] ' "$prompt"
if ! IFS= read -r answer; then
answer=n
fi
case $answer in
y|Y|yes|YES|Yes) return 0 ;;
*) return 1 ;;
esac
}
ask_to_install() {
ask_yes_no "未检测到 $1。是否现在安装?"
}
ask_to_enable() {
ask_yes_no "尚未启用 $1。是否现在安装所需文件并启用?"
}
require_brew() {
if command -v brew >/dev/null 2>&1; then
return 0
fi
warn "自动安装该依赖需要 Homebrew,但当前 PATH 中没有 brew。"
warn "请先安装 Homebrew(https://brew.sh/),然后重新运行本脚本。"
return 1
}
record_install_failure() {
warn "$1 安装失败;其余依赖仍会继续检查。"
install_failed=1
}
resolve_agent_dir() {
if [ -n "${PI_CODING_AGENT_DIR:-}" ]; then
case $PI_CODING_AGENT_DIR in
~/*) printf '%s\n' "$HOME/${PI_CODING_AGENT_DIR#~/}" ;;
*) printf '%s\n' "$PI_CODING_AGENT_DIR" ;;
esac
return 0
fi
printf '%s\n' "$HOME/.pi/agent"
}
# 部署组合包权威的系统提示词封面;脚本目录内的组合包副本即为来源。
deploy_system_prompt_cover() {
cover_helper=$SCRIPT_DIR/scripts/deploy-system-prompt-cover.mjs
if [ ! -f "$cover_helper" ]; then
warn "找不到 SYSTEM_COVER.md 部署辅助脚本,已跳过封面部署。"
return 1
fi
if ! command -v node >/dev/null 2>&1; then
warn "找不到 node,无法部署 SYSTEM_COVER.md;Pi 将使用默认系统提示词封面。"
return 1
fi
cover_agent_dir=$(resolve_agent_dir) || return 1
cover_status=$(node "$cover_helper" "$cover_agent_dir" 2>&1) || {
warn "SYSTEM_COVER.md 部署失败:$cover_status"
return 1
}
case $cover_status in
created) say "✓ 已部署系统提示词封面:$cover_agent_dir/SYSTEM_COVER.md" ;;
updated) say "✓ 已同步系统提示词封面到组合包当前版本。" ;;
current) say "✓ 系统提示词封面已是当前组合包版本。" ;;
*)
warn "SYSTEM_COVER.md 部署返回未知状态:$cover_status"
return 1
;;
esac
}
java_major_version() {
java_version=$(java -version 2>&1 | awk -F '"' 'NR == 1 { print $2 }')
case $java_version in
1.*) java_version=${java_version#1.} ;;
esac
java_major=${java_version%%.*}
case $java_major in
''|*[!0-9]*) return 1 ;;
*) printf '%s\n' "$java_major" ;;
esac
}
install_codegraph() {
if ! command -v curl >/dev/null 2>&1; then
warn "安装 CodeGraph 需要 curl。"
return 1
fi
curl -fsSL "$CODEGRAPH_INSTALL_URL" | sh || return 1
# 官方安装器默认使用 ~/.local/bin;让本次检查立即看到新命令。
if [ -d "$HOME/.local/bin" ]; then
PATH="$HOME/.local/bin:$PATH"
export PATH
fi
command -v codegraph >/dev/null 2>&1
}
install_kotlin_lsp() {
require_brew || return 1
brew install JetBrains/utils/kotlin-lsp || return 1
command -v kotlin-lsp >/dev/null 2>&1
}
install_java_21() {
require_brew || return 1
brew install openjdk@21 || return 1
java_home=$(brew --prefix openjdk@21 2>/dev/null) || return 1
PATH="$java_home/bin:$PATH"
export PATH
installed_java_major=$(java_major_version || true)
if [ -z "$installed_java_major" ] || [ "$installed_java_major" -lt 21 ]; then
return 1
fi
say "已在本次安装流程中使用 $java_home/bin。若新终端仍找不到 Java,请把该目录加入 PATH。"
}
install_jdtls() {
require_brew || return 1
brew install jdtls || return 1
command -v jdtls >/dev/null 2>&1
}
detect_kitty() {
kitty_bin=
if command -v kitty >/dev/null 2>&1; then
kitty_bin=$(command -v kitty)
return 0
fi
for candidate in /Applications/kitty.app/Contents/MacOS/kitty "$HOME/.local/kitty.app/bin/kitty"; do
if [ -x "$candidate" ]; then
kitty_bin=$candidate
return 0
fi
done
return 1
}
install_kitty() {
case $(uname -s) in
Darwin)
require_brew || return 1
brew install --cask kitty || return 1
;;
*)
if ! command -v curl >/dev/null 2>&1; then
warn "安装 Kitty 需要 curl。"
return 1
fi
curl -L "$KITTY_INSTALL_URL" | sh /dev/stdin || return 1
;;
esac
detect_kitty
}
resolve_kitty_config_dir() {
kitty_config_dir=${KITTY_CONFIG_DIRECTORY:-${XDG_CONFIG_HOME:-$HOME/.config}/kitty}
}
kitty_solarized_dark_configured() {
resolve_kitty_config_dir
[ -f "$kitty_config_dir/current-theme.conf" ] || return 1
[ -f "$kitty_config_dir/kitty.conf" ] || return 1
grep -F "Solarized Dark" "$kitty_config_dir/current-theme.conf" >/dev/null 2>&1 || return 1
grep -E '^[[:space:]]*include[[:space:]]+([^#[:space:]]*/)?current-theme\.conf([[:space:]]*(#.*)?)?$' \
"$kitty_config_dir/kitty.conf" >/dev/null 2>&1
}
configure_kitty_solarized_dark() {
"$kitty_bin" +kitten themes --reload-in=none "Solarized Dark"
}
resolve_zsh_paths() {
oh_my_zsh_dir=${ZSH:-$HOME/.oh-my-zsh}
zsh_custom_dir=${ZSH_CUSTOM:-$oh_my_zsh_dir/custom}
zshrc_file=${ZDOTDIR:-$HOME}/.zshrc
my_pi_config_dir=${XDG_CONFIG_HOME:-$HOME/.config}/my-pi
p10k_managed_config_file=$my_pi_config_dir/p10k.zsh
}
oh_my_zsh_installed() {
resolve_zsh_paths
[ -f "$oh_my_zsh_dir/oh-my-zsh.sh" ]
}
install_oh_my_zsh() {
if ! command -v curl >/dev/null 2>&1; then
warn "安装 Oh My Zsh 需要 curl。"
return 1
fi
if ! command -v git >/dev/null 2>&1; then
warn "安装 Oh My Zsh 需要 git。"
return 1
fi
resolve_zsh_paths
oh_my_zsh_installer=$(curl -fsSL "$OH_MY_ZSH_INSTALL_URL") || return 1
RUNZSH=no CHSH=no KEEP_ZSHRC=yes ZSH="$oh_my_zsh_dir" \
sh -c "$oh_my_zsh_installer" "" --unattended --keep-zshrc || return 1
oh_my_zsh_installed
}
prepare_zshrc() {
resolve_zsh_paths
zshrc_dir=${zshrc_file%/*}
[ "$zshrc_dir" = "$zshrc_file" ] && zshrc_dir=.
mkdir -p "$zshrc_dir" || return 1
[ -f "$zshrc_file" ] || : > "$zshrc_file" || return 1
}
replace_zsh_managed_block() {
managed_id=$1
managed_placement=$2
managed_anchor=$3
shift 3
managed_content=$(mktemp "${TMPDIR:-/tmp}/my-pi-zsh-block.XXXXXX") || return 1
printf '%s\n' "$@" > "$managed_content"
my_pi_replace_managed_block "$zshrc_file" "$managed_id" "$managed_content" \
"$managed_placement" "$managed_anchor"
managed_status=$?
rm -f "$managed_content"
return "$managed_status"
}
zshrc_has_oh_my_zsh_loader() {
[ -f "$zshrc_file" ] || return 1
grep -E '^[[:space:]]*(source|\.)[[:space:]].*oh-my-zsh\.sh' "$zshrc_file" >/dev/null 2>&1
}
ensure_oh_my_zsh_loader() {
prepare_zshrc || return 1
if zshrc_has_oh_my_zsh_loader; then
return 0
fi
replace_zsh_managed_block "oh-my-zsh-loader" "append" "" \
"export ZSH=\"$oh_my_zsh_dir\"" \
"source \"\$ZSH/oh-my-zsh.sh\""
}
set_powerlevel10k_theme() {
prepare_zshrc || return 1
replace_zsh_managed_block "powerlevel10k-theme" "before" \
'^(# >>> my-pi:oh-my-zsh-loader >>>|[[:space:]]*(source|\.)[[:space:]].*oh-my-zsh\.sh)' \
'ZSH_THEME="powerlevel10k/powerlevel10k"'
}
p10k_config_sourced() {
my_pi_managed_block_exists "$zshrc_file" "powerlevel10k-config"
}
ensure_p10k_config_source() {
prepare_zshrc || return 1
replace_zsh_managed_block "powerlevel10k-config" "append" "" \
"[[ ! -f \"$p10k_managed_config_file\" ]] || source \"$p10k_managed_config_file\""
}
configure_powerlevel10k() {
resolve_zsh_paths
powerlevel10k_dir=$zsh_custom_dir/themes/powerlevel10k
if [ ! -f "$powerlevel10k_dir/powerlevel10k.zsh-theme" ]; then
if ! command -v git >/dev/null 2>&1; then
warn "安装 Powerlevel10k 需要 git。"
return 1
fi
mkdir -p "$zsh_custom_dir/themes" || return 1
git clone --depth=1 "$POWERLEVEL10K_REPOSITORY" "$powerlevel10k_dir" || return 1
fi
my_pi_sync_managed_file "$POWERLEVEL10K_PRESET_FILE" "$p10k_managed_config_file" || return 1
set_powerlevel10k_theme || return 1
ensure_oh_my_zsh_loader || return 1
ensure_p10k_config_source || return 1
say "已应用仓库内置的 Powerlevel10k Rainbow/ASCII 受管配置。"
}
zsh_plugin_enabled() {
plugin=$1
[ -f "$zshrc_file" ] || return 1
awk -v target="$plugin" '
{
line = $0
sub(/[[:space:]]*#.*/, "", line)
if (line ~ /^[[:space:]]*plugins[[:space:]]*[+]?=/) active = 1
if (active) {
gsub(/[()=+]/, " ", line)
count = split(line, fields, /[[:space:]]+/)
for (i = 1; i <= count; i++) if (fields[i] == target) found = 1
if ($0 ~ /\)/) active = 0
}
}
END { exit(found ? 0 : 1) }
' "$zshrc_file"
}
add_zsh_plugin() {
plugin=$1
prepare_zshrc || return 1
ensure_oh_my_zsh_loader || return 1
replace_zsh_managed_block "zsh-plugin-$plugin" "before" \
'^(# >>> my-pi:oh-my-zsh-loader >>>|[[:space:]]*(source|\.)[[:space:]].*oh-my-zsh\.sh)' \
"plugins+=($plugin)"
}
install_and_enable_zsh_plugin() {
plugin=$1
repository=${2:-}
if [ -n "$repository" ] && [ ! -d "$zsh_custom_dir/plugins/$plugin" ]; then
if ! command -v git >/dev/null 2>&1; then
warn "安装 $plugin 需要 git。"
return 1
fi
mkdir -p "$zsh_custom_dir/plugins" || return 1
git clone --depth=1 "$repository" "$zsh_custom_dir/plugins/$plugin" || return 1
fi
add_zsh_plugin "$plugin"
}
check_zsh_plugin() {
plugin=$1
description=$2
repository=${3:-}
if zsh_plugin_enabled "$plugin"; then
say "✓ $plugin 已启用"
elif ask_to_enable "$description"; then
install_and_enable_zsh_plugin "$plugin" "$repository" || record_install_failure "$plugin"
else
say "- 已跳过 ${plugin}。"
fi
}
if ! command -v pi >/dev/null 2>&1; then
warn "找不到 pi,无法安装组合包。请先安装 Pi 并确保 pi 位于 PATH。"
exit 127
fi
say "正在安装 Pi 组合包:$PACKAGE_SOURCE"
if ! pi install "$PACKAGE_SOURCE"; then
warn "pi install 执行失败,已停止后续依赖检查。"
exit 1
fi
say "Pi 组合包安装完成,开始部署受管配置。"
deploy_system_prompt_cover || record_install_failure "SYSTEM_COVER.md"
say "开始检查终端环境和机器级 CLI 依赖。"
kitty_ok=0
if detect_kitty; then
kitty_ok=1
say "✓ Kitty 已安装(${kitty_bin})"
elif ask_to_install "Kitty 终端"; then
if install_kitty; then
kitty_ok=1
say "✓ Kitty 安装完成"
else
record_install_failure "Kitty"
fi
else
say "- 已跳过 Kitty;不会检查 Solarized Dark 主题。"
fi
if [ "$kitty_ok" -eq 1 ]; then
if kitty_solarized_dark_configured; then
say "✓ Kitty 已配置 Solarized Dark"
elif ask_yes_no "Kitty 尚未配置 Solarized Dark。是否现在配置?"; then
configure_kitty_solarized_dark || record_install_failure "Kitty Solarized Dark 主题"
else
say "- 已跳过 Kitty Solarized Dark 主题。"
fi
fi
oh_my_zsh_ok=0
if oh_my_zsh_installed; then
oh_my_zsh_ok=1
say "✓ Oh My Zsh 已安装(${oh_my_zsh_dir})"
elif ask_to_install "Oh My Zsh"; then
if install_oh_my_zsh; then
oh_my_zsh_ok=1
say "✓ Oh My Zsh 安装完成"
else
record_install_failure "Oh My Zsh"
fi
else
say "- 已跳过 Oh My Zsh;不会检查 Powerlevel10k 和 Zsh 插件。"
fi
if [ "$oh_my_zsh_ok" -eq 1 ]; then
resolve_zsh_paths
powerlevel10k_dir=$zsh_custom_dir/themes/powerlevel10k
if [ -f "$powerlevel10k_dir/powerlevel10k.zsh-theme" ] && \
my_pi_managed_block_exists "$zshrc_file" "powerlevel10k-theme" && \
[ -f "$p10k_managed_config_file" ] && \
cmp -s "$POWERLEVEL10K_PRESET_FILE" "$p10k_managed_config_file" && p10k_config_sourced; then
say "✓ Powerlevel10k 已安装并配置"
elif ask_yes_no "Powerlevel10k 尚未完整配置。是否安装并应用仓库内置的 Rainbow/ASCII 配置?"; then
configure_powerlevel10k || record_install_failure "Powerlevel10k"
else
say "- 已跳过 Powerlevel10k。"
fi
check_zsh_plugin "git" "git 插件" ""
check_zsh_plugin "zsh-autosuggestions" "zsh-autosuggestions(命令自动建议)" \
"$ZSH_AUTOSUGGESTIONS_REPOSITORY"
check_zsh_plugin "zsh-syntax-highlighting" "zsh-syntax-highlighting(语法高亮)" \
"$ZSH_SYNTAX_HIGHLIGHTING_REPOSITORY"
fi
if command -v codegraph >/dev/null 2>&1; then
say "✓ codegraph 已安装"
elif ask_to_install "CodeGraph(提供 codegraph_explore)"; then
install_codegraph || record_install_failure "CodeGraph"
else
say "- 已跳过 CodeGraph;codegraph_explore 将不可用。"
fi
if command -v kotlin-lsp >/dev/null 2>&1; then
say "✓ kotlin-lsp 已安装"
elif ask_to_install "kotlin-lsp(Kotlin LSP)"; then
install_kotlin_lsp || record_install_failure "kotlin-lsp"
else
say "- 已跳过 kotlin-lsp;Kotlin LSP 功能将不可用。"
fi
java_ok=0
if command -v java >/dev/null 2>&1; then
java_major=$(java_major_version || true)
if [ -n "$java_major" ] && [ "$java_major" -ge 21 ]; then
java_ok=1
say "✓ Java $java_major 已安装(满足 Java 21+)"
fi
fi
if [ "$java_ok" -eq 0 ]; then
if ask_to_install "Java 21+(JDT LS 运行时)"; then
if install_java_21; then
java_ok=1
else
record_install_failure "Java 21+"
fi
else
say "- 已跳过 Java 21+;JDT LS 可能无法启动。"
fi
fi
if command -v jdtls >/dev/null 2>&1; then
say "✓ jdtls 已安装"
elif ask_to_install "jdtls(Java LSP)"; then
install_jdtls || record_install_failure "jdtls"
else
say "- 已跳过 jdtls;Java LSP 功能将不可用。"
fi
say "终端环境与依赖检查完成。RTK 命令改写默认关闭,因此未检查可选的 rtk CLI。"
exit "$install_failed"