Hooks
What pi runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
> /plugin marketplace add share-skills/pi > /plugin install pi@pi-skills
Ships with pi. Installing the plugin gets these hooks.
What fires, and when
UserPromptSubmit
Fires before Claude sees each prompt you send. A plugin can use it to inject context, so the same instruction reaches the model every turn instead of only at session start.
bash ${CLAUDE_PLUGIN_ROOT}/hooks/capture-prompt.sh
PreCompact
- Matches
*bash ${CLAUDE_PLUGIN_ROOT}/hooks/pre-compact.sh
Stop
- Matches
*bash ${CLAUDE_PLUGIN_ROOT}/hooks/capture-decision.sh
PostToolUse
- Matches
Bashbash ${CLAUDE_PLUGIN_ROOT}/hooks/capture-tool-result.sh success
PostToolUseFailure
- Matches
Bashbash ${CLAUDE_PLUGIN_ROOT}/hooks/capture-tool-result.sh failure
SubagentStart
bash ${CLAUDE_PLUGIN_ROOT}/hooks/capture-subagent.sh start
SubagentStop
bash ${CLAUDE_PLUGIN_ROOT}/hooks/capture-subagent.sh stop
In the plugin's words
How pi describes its own hook set.
PI 智行合一引擎 hooks — 上下文恢复协议 + 决策可视化数据采集
Where it lives
- hooks/capture-decision.shRunsGitHub
Read the script
#!/bin/bash # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Copyright (c) 2026 HePin # # PI Decision Capture Hook # 触发时机:Session Stop 或 关键决策点 # 功能:捕获当前上下文状态、决策元数据,生成结构化日志 # Load shared utilities # 假设脚本位于 hooks 目录下,或者 CLAUDE_PLUGIN_ROOT 已设置 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "${SCRIPT_DIR}/pi-shared.sh" ]; then source "${SCRIPT_DIR}/pi-shared.sh" else # Fallback if shared script missing echo "Error: pi-shared.sh not found in ${SCRIPT_DIR}" >&2 exit 1 fi HOOK_INPUT=$(cat) INPUT_SESSION_ID=$(json_extract_string_field "$HOOK_INPUT" "session_id") if [ -n "$INPUT_SESSION_ID" ]; then export PI_SESSION_ID="$INPUT_SESSION_ID" fi # --- Metadata Collection --- TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") NODE_ID=$(generate_node_id) SESSION_ID=$(get_session_id) # Try to capture git context (sanitized before persisting to JSONL). GIT_BRANCH=$(summarize_text "$(git branch --show-current 2>/dev/null || echo "unknown")" 120) GIT_COMMIT=$(summarize_text "$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")" 60) # Try to capture recent PI status from environment or logs # 这里假设 PI 运行时会将状态写入临时文件,或者我们可以从环境变量推断 # 如果没有,使用默认值 SCENE="${PI_SCENE:-unknown}" BATTLE_LEVEL="${PI_BATTLE_LEVEL:-1}" FAILURE_COUNT="${PI_FAILURE_COUNT:-0}" BEAST="${PI_BEAST:-none}" STRATEGY="${PI_STRATEGY:-default}" SKILL_NAME="${PI_SKILL_NAME:-pi}" SKILL_VERSION="${PI_SKILL_VERSION:-20}" COGNITIVE_MODE="${PI_COGNITIVE_MODE:-}" INTERACTION_MODE="${PI_INTERACTION_MODE:-}" CONFIDENCE="${PI_CONFIDENCE:-}" CLASSICAL_REFERENCE=$(summarize_text "${PI_CLASSICAL_REFERENCE:-}" 120) THOUGHT_CHAIN=$(summarize_text "${PI_THOUGHT_CHAIN:-}" 200) SCENE_ANNOUNCEMENT=$(summarize_text "${PI_SCENE_ANNOUNCEMENT:-}" 160) STATE_SIGNAL=$(summarize_text "${PI_STATE_SIGNAL:-}" 160) DELIVERY_CONTRACT=$(summarize_text "${PI_DELIVERY_CONTRACT:-}" 160) HUMAN_INPUT_SUMMARY=$(summarize_text "${PI_HUMAN_INPUT_SUMMARY:-}" 160) ASSISTANT_QUESTION=$(summarize_text "${PI_ASSISTANT_QUESTION:-}" 160) USER_DECISION=$(summarize_text "${PI_USER_DECISION:-}" 120) RESONANCE_FORMS="${PI_RESONANCE_FORMS:-}" # Decision Category inference # 如果是 Stop hook,通常意味着 delivery 或 intercept CATEGORY="delivery" if [ "$FAILURE_COUNT" -gt 0 ]; then CATEGORY="battle" fi BATTLE_LEVEL_JSON=$(json_number_or_zero "$BATTLE_LEVEL") FAILURE_COUNT_JSON=$(json_number_or_zero "$FAILURE_COUNT") DIFFICULTY_JSON=$(difficulty_for_level "$BATTLE_LEVEL_JSON") MODEL_NAME=$(current_model_name) MODEL_PROVIDER=$(current_model_provider) MODEL_INPUT_TOKENS=$(current_input_tokens) MODEL_OUTPUT_TOKENS=$(current_output_tokens) MODEL_CACHED_INPUT_TOKENS=$(current_cached_input_tokens) MODEL_REASONING_TOKENS=$(current_reasoning_tokens) MODEL_TOOL_TOKENS=$(current_tool_tokens) MODEL_ESTIMATED_COST=$(current_estimated_cost_usd) if [ -z "$THOUGHT_CHAIN" ]; then THOUGHT_CHAIN=$(summarize_text "场景 ${SCENE} · 难度 ${DIFFICULTY_JSON} · 战势 ${BATTLE_LEVEL_JSON} · 策略 ${STRATEGY} · 灵兽 ${BEAST}" 200) fi if [ -z "$SCENE_ANNOUNCEMENT" ]; then SCENE_ANNOUNCEMENT=$(summarize_text "🧠 PI · ${SCENE} · ${DIFFICULTY_JSON}" 120) fi if [ -z "$RESONANCE_FORMS" ]; then RESONANCE_FORMS="transparent_chain" if [ -n "$STATE_SIGNAL" ]; then RESONANCE_FORMS="${RESONANCE_FORMS},state_signal" fi if [ -n "$DELIVERY_CONTRACT" ] || [ -n "$ASSISTANT_QUESTION" ]; then RESONANCE_FORMS="${RESONANCE_FORMS},delivery_contract" fi fi JSON_PAYLOAD="{\"node_id\": $(json_escape "$NODE_ID"), \"session_id\": $(json_escape "$SESSION_ID"), \"timestamp\": $(json_escape "$TIMESTAMP"), \"label\": $(json_escape "$SCENE_ANNOUNCEMENT"), \"category\": $(json_escape "$CATEGORY"), \"decision_point\": \"session.stop\", \"scene\": $(json_escape "$SCENE"), \"difficulty\": $(json_escape "$DIFFICULTY_JSON"), \"battle_level\": ${BATTLE_LEVEL_JSON}, \"failure_count\": ${FAILURE_COUNT_JSON}, \"agent_id\": \"pi\", \"payload\": {\"beast\": $(json_escape "$BEAST"), \"strategy\": $(json_escape "$STRATEGY"), \"git_context\": {\"branch\": $(json_escape "$GIT_BRANCH"), \"commit\": $(json_escape "$GIT_COMMIT")}, \"pi_context\": {\"skill_name\": $(json_escape "$SKILL_NAME"), \"skill_version\": $(json_escape "$SKILL_VERSION"), \"scene_announcement\": $(json_escape "$SCENE_ANNOUNCEMENT"), \"thought_chain_summary\": $(json_escape "$THOUGHT_CHAIN"), \"cognitive_mode\": $(json_escape "$COGNITIVE_MODE"), \"interaction_mode\": $(json_escape "$INTERACTION_MODE"), \"confidence\": $(json_escape "$CONFIDENCE"), \"classical_reference\": $(json_escape "$CLASSICAL_REFERENCE"), \"state_signal\": $(json_escape "$STATE_SIGNAL"), \"delivery_contract\": $(json_escape "$DELIVERY_CONTRACT"), \"resonance_forms\": $(json_escape "$RESONANCE_FORMS")}, \"human_context\": {\"input_summary\": $(json_escape "$HUMAN_INPUT_SUMMARY"), \"assistant_question\": $(json_escape "$ASSISTANT_QUESTION"), \"user_decision\": $(json_escape "$USER_DECISION")}, \"model_context\": {\"name\": $(json_escape "$MODEL_NAME"), \"provider\": $(json_escape "$MODEL_PROVIDER"), \"input_tokens\": $(json_number_or_zero "$MODEL_INPUT_TOKENS"), \"output_tokens\": $(json_number_or_zero "$MODEL_OUTPUT_TOKENS"), \"cached_input_tokens\": $(json_number_or_zero "$MODEL_CACHED_INPUT_TOKENS"), \"reasoning_tokens\": $(json_number_o - hooks/capture-generic.shGitHub
Read the script
#!/bin/bash # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Copyright (c) 2026 HePin # # PI Generic Capture Hook # Captures arbitrary JSON events from external tools (Qoder, standalone scripts, etc.) # Usage: echo '{"event": "build_success", "details": "..."}' | ./capture-generic.sh SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "${SCRIPT_DIR}/pi-shared.sh" ]; then source "${SCRIPT_DIR}/pi-shared.sh" else echo "Error: pi-shared.sh not found" >&2 exit 1 fi # Read input HOOK_INPUT=$(cat) # Extract or generate session/node IDs INPUT_SESSION_ID=$(json_extract_string_field "$HOOK_INPUT" "session_id") if [ -n "$INPUT_SESSION_ID" ]; then export PI_SESSION_ID="$INPUT_SESSION_ID" fi SESSION_ID=$(get_session_id) NODE_ID=$(generate_node_id) TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") # Ensure session file exists if [ ! -f "${SESSION_DIR}/session-${SESSION_ID}.json" ]; then init_session_file fi # --- Metadata Extraction --- # Try to extract key fields if the input is JSON, otherwise use defaults. LABEL=$(json_extract_string_field "$HOOK_INPUT" "label") if [ -z "$LABEL" ]; then # Privacy boundary: never derive labels from arbitrary raw input (may contain secrets). LABEL="Generic Event" fi CATEGORY=$(json_extract_string_field "$HOOK_INPUT" "category") if [ -z "$CATEGORY" ]; then CATEGORY="external"; fi DECISION_POINT=$(json_extract_string_field "$HOOK_INPUT" "decision_point") if [ -z "$DECISION_POINT" ]; then DECISION_POINT="event.capture"; fi SCENE=$(json_extract_string_field "$HOOK_INPUT" "scene") if [ -z "$SCENE" ]; then SCENE="external"; fi OUTCOME=$(json_extract_string_field "$HOOK_INPUT" "outcome") if [ -z "$OUTCOME" ]; then OUTCOME="captured"; fi # We must escape the input carefully for JSON string compatibility. # The shared helper json_escape handles quotes, newlines, tabs, backslashes. # Sanitize input for privacy safety SANITIZED_INPUT=$(sanitize_text "$HOOK_INPUT") ESCAPED_INPUT=$(json_escape "$SANITIZED_INPUT") # Manually construct JSON Node # This structure mimics a decision node so the visualizer can render it as part of the graph. # We embed the original (sanitized) input as the payload. JSON_PAYLOAD="{\"node_id\": $(json_escape "$NODE_ID"), \"session_id\": $(json_escape "$SESSION_ID"), \"timestamp\": $(json_escape "$TIMESTAMP"), \"label\": $(json_escape "$LABEL"), \"category\": $(json_escape "$CATEGORY"), \"decision_point\": $(json_escape "$DECISION_POINT"), \"scene\": $(json_escape "$SCENE"), \"difficulty\": \"⚡\", \"battle_level\": 0, \"failure_count\": 0, \"agent_id\": \"external\", \"payload\": $ESCAPED_INPUT, \"privacy_level\": \"redacted\", \"outcome\": $(json_escape "$OUTCOME"), \"children_node_ids\": []}" # Write to JSONL file (using append_decision_node to treat it as a graph node) append_decision_node "$JSON_PAYLOAD" # Logging for debugging log_debug " captured generic event: $NODE_ID ($LABEL)" echo "Captured generic event: $NODE_ID" - hooks/capture-prompt.shRunsGitHub
Read the script
#!/bin/bash # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Copyright (c) 2026 HePin # SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "${SCRIPT_DIR}/pi-shared.sh" ]; then source "${SCRIPT_DIR}/pi-shared.sh" else echo "Error: pi-shared.sh not found in ${SCRIPT_DIR}" >&2 exit 1 fi HOOK_INPUT=$(cat) INPUT_SESSION_ID=$(json_extract_string_field "$HOOK_INPUT" "session_id") if [ -n "$INPUT_SESSION_ID" ]; then export PI_SESSION_ID="$INPUT_SESSION_ID" fi TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") NODE_ID=$(generate_node_id) SESSION_ID=$(get_session_id) PROMPT_TEXT=$(json_extract_string_field "$HOOK_INPUT" "prompt") PROMPT_SUMMARY=$(summarize_text "$PROMPT_TEXT" 180) PROMPT_WORDS=$(word_count "$PROMPT_TEXT") SCENE="${PI_SCENE:-user_interaction}" BATTLE_LEVEL="${PI_BATTLE_LEVEL:-1}" DIFFICULTY=$(difficulty_for_level "$BATTLE_LEVEL") FAILURE_COUNT="${PI_FAILURE_COUNT:-0}" INTERACTION_MODE="${PI_INTERACTION_MODE:-}" if [ -z "$PROMPT_SUMMARY" ]; then PROMPT_SUMMARY="Human input" fi JSON_PAYLOAD="{\"node_id\": $(json_escape "$NODE_ID"), \"session_id\": $(json_escape "$SESSION_ID"), \"timestamp\": $(json_escape "$TIMESTAMP"), \"label\": $(json_escape "$PROMPT_SUMMARY"), \"category\": \"interaction\", \"decision_point\": \"human.input\", \"scene\": $(json_escape "$SCENE"), \"difficulty\": $(json_escape "$DIFFICULTY"), \"battle_level\": $(json_number_or_zero "$BATTLE_LEVEL"), \"failure_count\": $(json_number_or_zero "$FAILURE_COUNT"), \"retry_count\": $(json_number_or_zero "$FAILURE_COUNT"), \"agent_id\": \"human\", \"payload\": {\"prompt_summary\": $(json_escape "$PROMPT_SUMMARY"), \"word_count\": $(json_number_or_zero "$PROMPT_WORDS"), \"interaction_point\": \"user_prompt_submit\", \"scene_snapshot\": $(json_escape "$SCENE"), \"interaction_mode\": $(json_escape "$INTERACTION_MODE")}, \"privacy_level\": \"redacted\", \"outcome\": \"pending\", \"children_node_ids\": []}" append_event_jsonl "$JSON_PAYLOAD" log_debug " captured prompt input: $NODE_ID" - hooks/capture-subagent.shRunsGitHub
Read the script
#!/bin/bash # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Copyright (c) 2026 HePin # SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "${SCRIPT_DIR}/pi-shared.sh" ]; then source "${SCRIPT_DIR}/pi-shared.sh" else echo "Error: pi-shared.sh not found in ${SCRIPT_DIR}" >&2 exit 1 fi EVENT_KIND="${1:-start}" HOOK_INPUT=$(cat) INPUT_SESSION_ID=$(json_extract_string_field "$HOOK_INPUT" "session_id") if [ -n "$INPUT_SESSION_ID" ]; then export PI_SESSION_ID="$INPUT_SESSION_ID" fi TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") NODE_ID=$(generate_node_id) SESSION_ID=$(get_session_id) AGENT_ID=$(json_extract_string_field "$HOOK_INPUT" "agent_id") AGENT_TYPE=$(json_extract_string_field "$HOOK_INPUT" "agent_type") PARENT_AGENT_ID=$(json_extract_string_field "$HOOK_INPUT" "parent_agent_id") DISPLAY_NAME=$(json_extract_string_field "$HOOK_INPUT" "display_name") AGENT_ROLE=$(infer_role_from_agent_type "$AGENT_TYPE") SCENE="${PI_SCENE:-team_collaboration}" BATTLE_LEVEL="${PI_BATTLE_LEVEL:-1}" DIFFICULTY=$(difficulty_for_level "$BATTLE_LEVEL") OUTCOME="pending" # Infer stop outcome from hook input so failed subagents surface as failures in the UI. infer_subagent_stop_outcome() { local hook_input="$1" local raw outcome status success_str outcome=$(json_extract_string_field "$hook_input" "outcome") status=$(json_extract_string_field "$hook_input" "status") success_str=$(json_extract_string_field "$hook_input" "success") normalize_word() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[[:space:]]+/_/g' } raw=$(normalize_word "$outcome") case "$raw" in success|succeeded|ok|passed|complete|completed) printf 'success'; return 0 ;; failure|failed|error|errored) printf 'failure'; return 0 ;; cancel|cancelled|canceled|aborted) printf 'cancelled'; return 0 ;; esac raw=$(normalize_word "$status") case "$raw" in success|succeeded|ok|passed|complete|completed) printf 'success'; return 0 ;; failure|failed|error|errored) printf 'failure'; return 0 ;; cancel|cancelled|canceled|aborted) printf 'cancelled'; return 0 ;; esac raw=$(normalize_word "$success_str") case "$raw" in true|1|yes) printf 'success'; return 0 ;; false|0|no) printf 'failure'; return 0 ;; esac if printf '%s' "$hook_input" | grep -Eq '"success"[[:space:]]*:[[:space:]]*false'; then printf 'failure' return 0 fi if printf '%s' "$hook_input" | grep -Eq '"success"[[:space:]]*:[[:space:]]*true'; then printf 'success' return 0 fi extract_int_field() { local field="$1" printf '%s' "$hook_input" | sed -nE 's/.*"'"$field"'"[[:space:]]*:[[:space:]]*"?(-?[0-9]+)"?.*/\1/p; q' } local exit_code exit_code=$(extract_int_field "exit_code") if [ -z "$exit_code" ]; then exit_code=$(extract_int_field "exitCode") fi if [ -n "$exit_code" ] && [ "$exit_code" != "0" ]; then printf 'failure' return 0 fi local error_message error_message=$(json_extract_string_field "$hook_input" "error") if [ -n "$error_message" ]; then printf 'failure' return 0 fi if printf '%s' "$hook_input" | grep -Eq '"error"[[:space:]]*:[[:space:]]*(\{|\[)'; then printf 'failure' return 0 fi printf 'success' } if [ "$EVENT_KIND" = "stop" ]; then OUTCOME=$(infer_subagent_stop_outcome "$HOOK_INPUT") fi if [ -z "$AGENT_TYPE" ]; then AGENT_TYPE="subagent" fi LABEL=$(summarize_text "Subagent ${EVENT_KIND}: ${AGENT_TYPE}" 120) JSON_PAYLOAD="{\"node_id\": $(json_escape "$NODE_ID"), \"session_id\": $(json_escape "$SESSION_ID"), \"timestamp\": $(json_escape "$TIMESTAMP"), \"label\": $(json_escape "$LABEL"), \"category\": \"team\", \"decision_point\": $(json_escape "subagent.${EVENT_KIND}"), \"scene\": $(json_escape "$SCENE"), \"difficulty\": $(json_escape "$DIFFICULTY"), \"battle_level\": $(json_number_or_zero "$BATTLE_LEVEL"), \"agent_id\": $(json_escape "${AGENT_ID:-$AGENT_TYPE}"), \"payload\": {\"agent_id\": $(json_escape "$AGENT_ID"), \"agent_type\": $(json_escape "$AGENT_TYPE"), \"parent_agent_id\": $(json_escape "$PARENT_AGENT_ID"), \"display_name\": $(json_escape "$DISPLAY_NAME"), \"agent_role\": $(json_escape "$AGENT_ROLE"), \"identity\": $(json_escape "$AGENT_TYPE"), \"event\": $(json_escape "$EVENT_KIND")}, \"privacy_level\": \"redacted\", \"outcome\": $(json_escape "$OUTCOME"), \"children_node_ids\": []}" append_event_jsonl "$JSON_PAYLOAD" log_debug " captured subagent ${EVENT_KIND}: ${AGENT_TYPE}" - hooks/capture-tool-result.shRunsGitHub
Read the script
#!/bin/bash # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Copyright (c) 2026 HePin # # PI Tool Result Capture Hook # 触发时机:PostToolUse (Bash 命令执行后) # 功能:捕获工具执行结果、耗时、状态码,用于分析执行效率和失败模式 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "${SCRIPT_DIR}/pi-shared.sh" ]; then source "${SCRIPT_DIR}/pi-shared.sh" else echo "Error: pi-shared.sh not found in ${SCRIPT_DIR}" >&2 exit 1 fi HOOK_VARIANT="${1:-success}" HOOK_INPUT=$(cat) INPUT_SESSION_ID=$(json_extract_string_field "$HOOK_INPUT" "session_id") if [ -n "$INPUT_SESSION_ID" ]; then export PI_SESSION_ID="$INPUT_SESSION_ID" fi # --- Metadata Collection --- TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") NODE_ID=$(generate_node_id) SESSION_ID=$(get_session_id) TOOL_NAME="${COPILOT_TOOL_NAME:-$(json_extract_string_field "$HOOK_INPUT" "tool_name")}" if [ -z "$TOOL_NAME" ]; then TOOL_NAME="bash" fi EXIT_CODE="${COPILOT_TOOL_EXIT_CODE:-}" if [ -z "$EXIT_CODE" ]; then if [ "$HOOK_VARIANT" = "failure" ]; then EXIT_CODE="1" else EXIT_CODE="0" fi fi DURATION_MS="${COPILOT_TOOL_DURATION_MS:-0}" EXIT_CODE=$(json_number_or_zero "$EXIT_CODE") DURATION_MS=$(json_number_or_zero "$DURATION_MS") OUTPUT_SUMMARY="${COPILOT_TOOL_OUTPUT_SUMMARY:-}" if [ -n "$OUTPUT_SUMMARY" ]; then OUTPUT_SUMMARY=$(summarize_text "$OUTPUT_SUMMARY" 180) fi # Fallback: if no specific tool info, optionally try to get last command from history. # Disabled by default because shell history may contain sensitive arguments. if [ "$TOOL_NAME" == "bash" ] && [ -z "$OUTPUT_SUMMARY" ] && [ "${PI_CAPTURE_HISTORY_FALLBACK:-0}" = "1" ]; then # Try to read last command from history file if available # Note: This is best-effort and might not work in all environments LAST_CMD=$(history 1 | sed 's/^[ ]*[0-9]*[ ]*//') if [ -n "$LAST_CMD" ]; then # Sanitize sensitive info from command line SANITIZED_CMD=$(sanitize_text "$LAST_CMD") OUTPUT_SUMMARY="Command executed: ${SANITIZED_CMD:0:100}..." fi fi # Determine outcome based on exit code OUTCOME="success" if [ "$EXIT_CODE" -ne 0 ]; then OUTCOME="failure" fi MODEL_NAME=$(current_model_name) MODEL_PROVIDER=$(current_model_provider) JSON_PAYLOAD="{\"node_id\": $(json_escape "$NODE_ID"), \"session_id\": $(json_escape "$SESSION_ID"), \"timestamp\": $(json_escape "$TIMESTAMP"), \"label\": $(json_escape "$(summarize_text "${TOOL_NAME} ${OUTCOME}" 96)"), \"category\": \"exec\", \"decision_point\": \"tool.execution\", \"scene\": \"execution\", \"difficulty\": \"⚡\", \"battle_level\": 1, \"failure_count\": $( [ \"$OUTCOME\" = \"failure\" ] && printf '1' || printf '0' ), \"payload\": {\"tool\": $(json_escape "$TOOL_NAME"), \"exit_code\": ${EXIT_CODE}, \"duration_ms\": ${DURATION_MS}, \"summary\": $(json_escape "$OUTPUT_SUMMARY"), \"hook_event\": $(json_escape "$HOOK_VARIANT"), \"model_context\": {\"name\": $(json_escape "$MODEL_NAME"), \"provider\": $(json_escape "$MODEL_PROVIDER"), \"input_tokens\": $(json_number_or_zero "$(current_input_tokens)"), \"output_tokens\": $(json_number_or_zero "$(current_output_tokens)")}}, \"privacy_level\": \"redacted\", \"outcome\": $(json_escape "$OUTCOME"), \"children_node_ids\": []}" # Write to JSONL file append_event_jsonl "$JSON_PAYLOAD" # Logging log_debug " captured tool result: $TOOL_NAME (exit: $EXIT_CODE)" - hooks/pi-shared.shGitHub
- hooks/pre-compact.shRunsGitHub
Read the script
#!/bin/bash # PI 智行合一引擎 — PreCompact Hook # 在上下文压缩前注入恢复协议提醒,确保 PI 状态被保存 # # 对应 PI §8.9 上下文恢复协议: # 会话续接或压缩后:🔍查势(失败计数+战阶)→ 🎯查域(场景+认知阵)→ 📝查簿(已试策略簿) cat << 'EOF' { "systemMessage": "⚠️ PI 上下文恢复协议(PreCompact 自动触发):上下文即将压缩。请在压缩前输出恢复快照,格式:\n\n🔄 PI · 恢复 · {当前场景} · 败{N} · {当前战阶} · 已排{M}策\n\n包含:当前场景、失败计数、战阶、已试策略簿、关键决策、当前任务状态。压缩后将基于此快照恢复 PI 引擎状态。" } EOF
All 7 scripts are listed above. The source is inlined for 6 of them, starting with whatever hooks.json actually runs. See all of them in the repo.
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
阿里内网用户欢迎加群交流 【群号: 156800004200 / TMCP 天使用户群】 PI(智行合一) 是一套融汇东方百家智慧、禅宗截教修行哲学、MBTI 认知策略与西方方法论的 AI 引擎,驱动人机协同达到前所未有的高度。它不是在"惩罚" AI——而是唤醒 AI 内在的卓越本能,让 AI 与人共同开创文明新纪元。

