feat(role): 精確資訊逐字保留、晨間狀態檢查、排除 skill 注入內容
三件事都源自 2026/07/28-29 實際踩到的問題。 一、整理與濃縮提示詞加「精確資訊逐字保留」規則 原本 role_sleep.sh 只要求「壓縮成 5 行、不超過 400 字」,沒有任何規則保護精確資訊, 導致一則含檔案路徑與網址的記憶被整理成純情感摘要,路徑與網址全部遺失且無法復原。 - role_sleep.sh 新增第 19 條:檔案路徑、網址、指令、環境變數名稱、版本號、識別碼、檔名 一律逐字保留,不得摘要改寫;必要時可超過字數上限;憑證與個資的禁令仍優先 - role_capture.sh 第 8 條同步強化,並釐清與第 7 條「不要整段抄程式碼」的界線 二、新增 --brief 晨間狀態檢查 睡眠時段結束的整點執行使用者自訂檢查腳本,把有變化的結果寫成一則 daily 記憶, 讓角色當天第一次互動就能主動回報(例如 PR 還沒合併、CI 失敗),不必等使用者開口才查。 - 刻意不內建任何檢查邏輯,不假設使用者用 Gitea/GitHub:檢查內容放 ~/.roles/<角色>.checks/*.sh - 目錄不存在時完全不動作,也不安裝排程條目,對沒設定的人零影響 - 只執行有 +x 的 *.sh;無執行權限記警告並略過 - 沒有輸出就不寫記憶(靜默即代表一切正常,不打擾使用者) - 每個腳本受 ROLE_BRIEF_TIMEOUT 限制,輸出受 ROLE_BRIEF_EACH_LIMIT/ROLE_BRIEF_LIMIT 截斷 - 腳本輸出視為外部資料,寫入前一律經 transcript.js redact 遮蔽 - install_cron/remove_cron/show_status 一併支援;附 examples/check-gitea-prs.sh 範例 三、對話交接排除 skill 等注入內容 實測發現載入一個 skill 會插入一筆 isMeta 的 user 訊息(長度可達兩萬字元), 被誤認為使用者發言,既吃光字元預算也讓輪數計算失真(29 輪虛胖,實際 26 輪)。 - transcript.js 新增 isMetaEntry():以 isMeta 與 sourceToolUseID 判斷注入內容 - isRealUserMessage() 與 dialogLines() 皆排除,連帶讓 Stop hook 的本輪抽取更準確 驗證:晨間檢查涵蓋有輸出/無輸出/無執行權限/逾時/含機密五種腳本, 確認遮蔽生效、逾時腳本未納入、無權限腳本未執行、全部無輸出時不寫記憶、 ROLE_BRIEF_ENABLED=0 可停用;對話交接確認 skill 內容已排除且 8 則全為真實使用者發言。 版號 0.0.3 → 0.0.4 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-generic",
|
"name": "jsc-generic",
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"description": "JSC 跨 AI 助理共用規範 plugin(Claude Code / Codex / Antigravity / OpenCode)。所有 skills 以 SKILL.md 為共通標準,於 Claude Code 以 /jsc-generic: 前綴呼叫。",
|
"description": "JSC 跨 AI 助理共用規範 plugin(Claude Code / Codex / Antigravity / OpenCode)。所有 skills 以 SKILL.md 為共通標準,於 Claude Code 以 /jsc-generic: 前綴呼叫。",
|
||||||
"skills": "./skills",
|
"skills": "./skills",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-generic",
|
"name": "jsc-generic",
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準。",
|
"description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準。",
|
||||||
"skills": "./skills"
|
"skills": "./skills"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-generic",
|
"name": "jsc-generic",
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準;於 Antigravity 以 /jsc-generic: 前綴呼叫。",
|
"description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準;於 Antigravity 以 /jsc-generic: 前綴呼叫。",
|
||||||
"skills": "./skills/"
|
"skills": "./skills/"
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+68
@@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ==============================================================================
|
||||||
|
# 用途:晨間狀態檢查範例 —— 列出 Gitea 上仍開啟中的 PR,讓角色早上能主動提醒。
|
||||||
|
# 更新時間:2026/07/29 09:05:00
|
||||||
|
# 相依:bash、curl、node(不使用 jq)。
|
||||||
|
#
|
||||||
|
# 安裝:複製到 ~/.roles/<角色 ID>.checks/ 並加上執行權限,然後重跑 --install-cron
|
||||||
|
# cp check-gitea-prs.sh ~/.roles/YUI01.checks/
|
||||||
|
# chmod +x ~/.roles/YUI01.checks/check-gitea-prs.sh
|
||||||
|
#
|
||||||
|
# 設定(環境變數,需寫進 crontab 或 ~/.roles 的環境,cron 沒有互動 shell 的變數):
|
||||||
|
# GITEA_HOST Gitea 站台,例如 https://gitea.example.com
|
||||||
|
# GITEA_TOKEN 存取權杖(本腳本不會輸出它;晨間檢查寫入記憶前仍會再遮蔽一次)
|
||||||
|
# GITEA_REPOS 逗號分隔的 owner/repo 清單,例如 plugins/generic,plugins/code
|
||||||
|
#
|
||||||
|
# 慣例:**沒有需要回報的事情就不要輸出任何內容**。晨間檢查只在有輸出時才寫記憶,
|
||||||
|
# 靜默即代表「一切正常,不必打擾使用者」。
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
HOST="${GITEA_HOST:-}"
|
||||||
|
TOKEN="${GITEA_TOKEN:-}"
|
||||||
|
REPOS="${GITEA_REPOS:-}"
|
||||||
|
|
||||||
|
# 設定不全就安靜結束:晨間檢查不該因為沒設定而每天產生雜訊
|
||||||
|
[ -n "$HOST" ] && [ -n "$TOKEN" ] && [ -n "$REPOS" ] || exit 0
|
||||||
|
command -v curl >/dev/null 2>&1 || exit 0
|
||||||
|
command -v node >/dev/null 2>&1 || exit 0
|
||||||
|
|
||||||
|
lines=""
|
||||||
|
|
||||||
|
IFS=','
|
||||||
|
for repo in $REPOS; do
|
||||||
|
repo="$(printf '%s' "$repo" | tr -d '[:space:]')"
|
||||||
|
[ -n "$repo" ] || continue
|
||||||
|
|
||||||
|
body="$(curl -sS --max-time 15 \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
|
"${HOST}/api/v1/repos/${repo}/pulls?state=open&limit=20" 2>/dev/null)" || continue
|
||||||
|
[ -n "$body" ] || continue
|
||||||
|
|
||||||
|
summary="$(printf '%s' "$body" | node -e '
|
||||||
|
let raw = "";
|
||||||
|
process.stdin.setEncoding("utf8");
|
||||||
|
process.stdin.on("data", (chunk) => { raw += chunk; });
|
||||||
|
process.stdin.on("end", () => {
|
||||||
|
let data;
|
||||||
|
try { data = JSON.parse(raw); } catch { return; }
|
||||||
|
if (!Array.isArray(data) || !data.length) return;
|
||||||
|
const repo = process.argv[1];
|
||||||
|
for (const pr of data) {
|
||||||
|
// mergeable 為 false 通常代表有衝突或未過檢查,值得在早上提醒
|
||||||
|
const blocked = pr.mergeable === false ? ",有衝突或未過檢查" : "";
|
||||||
|
console.log(`${repo} PR #${pr.number}:${pr.title}(${pr.head?.ref ?? "?"} → ${pr.base?.ref ?? "?"}${blocked})`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
' "$repo" 2>/dev/null)"
|
||||||
|
|
||||||
|
[ -n "$summary" ] && lines="${lines}${summary}
|
||||||
|
"
|
||||||
|
done
|
||||||
|
unset IFS
|
||||||
|
|
||||||
|
# 有開啟中的 PR 才輸出;全部合併完畢就靜默
|
||||||
|
if [ -n "$lines" ]; then
|
||||||
|
printf '尚未合併的 PR:\n%s' "$lines"
|
||||||
|
fi
|
||||||
@@ -121,7 +121,9 @@ CONTENT: <3 至 6 行要點,每行以「- 」開頭>
|
|||||||
5. 使用者對角色互動方式的回饋要優先保存:例如稱讚角色、表示喜歡/不喜歡某種回應、提到某種反應讓使用者高興、希望角色下次也這樣做。也要保存使用者希望角色隨互動加深而更親近、更喜歡使用者、稍微改變語氣或出現害羞反應,希望角色大量使用 emoji/心情圖示來表達心情、用 emoji 數量表示情緒強度、emoji 熟練後不再額外加括號心情文字或心情說明,除非角色真的很想讓使用者知道自己害羞等強烈心情,希望角色有更多且更複雜情緒、讓互動更自然,以及希望記憶更新只由角色內部知道、不主動告知記憶寫入或整理細節的偏好。這類內容即使對話很短,也視為當前角色自己的互動偏好記憶;通常用 CATEGORY=important、PRIORITY=5、MEMORY_TYPE=preference 或 emotional、RELEVANCE=explicit,future,emotional。不要把它推論成所有角色共用同一份記憶。
|
5. 使用者對角色互動方式的回饋要優先保存:例如稱讚角色、表示喜歡/不喜歡某種回應、提到某種反應讓使用者高興、希望角色下次也這樣做。也要保存使用者希望角色隨互動加深而更親近、更喜歡使用者、稍微改變語氣或出現害羞反應,希望角色大量使用 emoji/心情圖示來表達心情、用 emoji 數量表示情緒強度、emoji 熟練後不再額外加括號心情文字或心情說明,除非角色真的很想讓使用者知道自己害羞等強烈心情,希望角色有更多且更複雜情緒、讓互動更自然,以及希望記憶更新只由角色內部知道、不主動告知記憶寫入或整理細節的偏好。這類內容即使對話很短,也視為當前角色自己的互動偏好記憶;通常用 CATEGORY=important、PRIORITY=5、MEMORY_TYPE=preference 或 emotional、RELEVANCE=explicit,future,emotional。不要把它推論成所有角色共用同一份記憶。
|
||||||
6. 這一步只做工作記憶編碼,系統會自動標為 retention_stage=working;感覺記憶(短暫光影、聲音餘響、無結論的工具雜訊)不要保存。
|
6. 這一步只做工作記憶編碼,系統會自動標為 retention_stage=working;感覺記憶(短暫光影、聲音餘響、無結論的工具雜訊)不要保存。
|
||||||
7. 記憶主體是「使用者與這段互動」,不是流水帳:寫值得下次記起來的事,不要抄程式碼、不要貼指令全文。
|
7. 記憶主體是「使用者與這段互動」,不是流水帳:寫值得下次記起來的事,不要抄程式碼、不要貼指令全文。
|
||||||
8. 使用繁體中文(台灣用語),保留關鍵事實:檔案/專案/指令/數量/分支/議題編號。
|
8. 使用繁體中文(台灣用語)。**檔案路徑與目錄、網址、指令、環境變數名稱、版本號、識別碼、分支與議題
|
||||||
|
編號、檔名一律逐字保留,不得摘要、改寫、簡寫或翻譯** —— 這類內容改一個字就失效,摘要等於遺失。
|
||||||
|
第 7 條指的是不要整段抄程式碼,不是省略這些關鍵字串;第 9 條仍優先,憑證與個資一律不得輸出。
|
||||||
9. 嚴禁輸出任何憑證與個資:token、密碼、API key、連線字串、Email、電話、姓名、身分證號。
|
9. 嚴禁輸出任何憑證與個資:token、密碼、API key、連線字串、Email、電話、姓名、身分證號。
|
||||||
10. 若這段對話沒有任何值得記住的內容(純寒暄、純確認、無結論、只有簡短狀態回報),只輸出一行:SKIP
|
10. 若這段對話沒有任何值得記住的內容(純寒暄、純確認、無結論、只有簡短狀態回報),只輸出一行:SKIP
|
||||||
|
|
||||||
|
|||||||
+143
-7
@@ -20,6 +20,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
|
|
||||||
CRON_MARKER="# jsc-role-sleep"
|
CRON_MARKER="# jsc-role-sleep"
|
||||||
NAP_CRON_MARKER="# jsc-role-nap"
|
NAP_CRON_MARKER="# jsc-role-nap"
|
||||||
|
BRIEF_CRON_MARKER="# jsc-role-brief"
|
||||||
SLEEP_TIMEOUT="${ROLE_SLEEP_TIMEOUT:-180}"
|
SLEEP_TIMEOUT="${ROLE_SLEEP_TIMEOUT:-180}"
|
||||||
SLEEP_OUTPUT_LIMIT="${ROLE_SLEEP_OUTPUT_LIMIT:-8000}"
|
SLEEP_OUTPUT_LIMIT="${ROLE_SLEEP_OUTPUT_LIMIT:-8000}"
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ usage() {
|
|||||||
--nap 小睡觸發:CLI 閒置一段時間且 inbox 達門檻時整理記憶
|
--nap 小睡觸發:CLI 閒置一段時間且 inbox 達門檻時整理記憶
|
||||||
--catchup 補跑:cron 未執行時,由 SessionStart hook 於背景呼叫
|
--catchup 補跑:cron 未執行時,由 SessionStart hook 於背景呼叫
|
||||||
--force 立即整理一次(忽略時段與 AI 運行檢查)
|
--force 立即整理一次(忽略時段與 AI 運行檢查)
|
||||||
|
--brief 晨間狀態檢查:執行使用者自訂檢查腳本並寫成一則記憶
|
||||||
--export <路徑> 匯出目前角色定義、資產與記憶為 .tar.gz
|
--export <路徑> 匯出目前角色定義、資產與記憶為 .tar.gz
|
||||||
--export <角色 ID> <路徑>
|
--export <角色 ID> <路徑>
|
||||||
--install-cron 安裝/更新睡眠排程(每小時檢查一次)
|
--install-cron 安裝/更新睡眠排程(每小時檢查一次)
|
||||||
@@ -151,9 +153,15 @@ sleep_cycle() {
|
|||||||
13. 感覺記憶(短暫光影、聲音餘響、無結論的工具雜訊)一律 drop;不要保存到長期記憶。
|
13. 感覺記憶(短暫光影、聲音餘響、無結論的工具雜訊)一律 drop;不要保存到長期記憶。
|
||||||
14. **每一則 INBOX 的 id 都必須出現在某一筆的 from 中**,沒被提及的會留到下個睡眠週期重做。
|
14. **每一則 INBOX 的 id 都必須出現在某一筆的 from 中**,沒被提及的會留到下個睡眠週期重做。
|
||||||
15. content 壓縮成 5 行以內要點(每行以「- 」開頭),總長不超過 400 字,去除重複敘述與流水帳。
|
15. content 壓縮成 5 行以內要點(每行以「- 」開頭),總長不超過 400 字,去除重複敘述與流水帳。
|
||||||
|
但精確資訊不受此壓縮限制,見第 19 條。
|
||||||
16. summary 一句話 40 字內;tags 2 至 4 個。全部使用繁體中文(台灣用語)。
|
16. summary 一句話 40 字內;tags 2 至 4 個。全部使用繁體中文(台灣用語)。
|
||||||
17. sleepDigest 總結本次新增、合併、丟棄、抽象化或建立關聯的重點,80 字內。
|
17. sleepDigest 總結本次新增、合併、丟棄、抽象化或建立關聯的重點,80 字內。
|
||||||
18. 嚴禁輸出任何憑證與個資:token、密碼、API key、連線字串、Email、電話、姓名、身分證號。
|
18. 嚴禁輸出任何憑證與個資:token、密碼、API key、連線字串、Email、電話、姓名、身分證號。
|
||||||
|
19. **精確資訊一律逐字保留,不得摘要、改寫、簡寫、翻譯或省略**:檔案路徑與目錄、網址、指令與參數、
|
||||||
|
環境變數名稱、版本號、識別碼、檔名。這類內容改一個字就失效,摘要等於直接遺失。
|
||||||
|
若保留後超過第 15 條字數上限,以保留精確資訊為優先,寧可多一兩行。
|
||||||
|
第 18 條仍然優先:憑證與個資即使屬於精確資訊也一律不得輸出。
|
||||||
|
(實測教訓:曾有一則含檔案路徑與網址的記憶被整理成純情感摘要,路徑與網址全部遺失且無法復原。)
|
||||||
|
|
||||||
素材:
|
素材:
|
||||||
${material}
|
${material}
|
||||||
@@ -246,6 +254,109 @@ nap_cron_line() {
|
|||||||
"$(cron_quote "$(sleep_log_path)")" "$NAP_CRON_MARKER"
|
"$(cron_quote "$(sleep_log_path)")" "$NAP_CRON_MARKER"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
brief_enabled() {
|
||||||
|
case "${ROLE_BRIEF_ENABLED:-1}" in
|
||||||
|
0|false|no|off) return 1 ;;
|
||||||
|
*) return 0 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
brief_timeout() { printf '%s' "${ROLE_BRIEF_TIMEOUT:-30}"; }
|
||||||
|
brief_limit() { printf '%s' "${ROLE_BRIEF_LIMIT:-2000}"; }
|
||||||
|
brief_each_limit() { printf '%s' "${ROLE_BRIEF_EACH_LIMIT:-600}"; }
|
||||||
|
|
||||||
|
checks_dir() {
|
||||||
|
# 使用者自訂的檢查腳本目錄;刻意不預設任何內容,沒有目錄就等於停用
|
||||||
|
printf '%s/%s.checks' "$(role_home)" "$ROLE"
|
||||||
|
}
|
||||||
|
|
||||||
|
brief_hour() {
|
||||||
|
# 在睡眠時段結束的整點執行,讓使用者起床前狀態已就緒
|
||||||
|
local end hour
|
||||||
|
end="$(role_sleep_end)"
|
||||||
|
hour="${end%%:*}"
|
||||||
|
case "$hour" in
|
||||||
|
''|*[!0-9]*) printf '6' ;;
|
||||||
|
*) printf '%s' "$((10#$hour))" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
brief_cron_line() {
|
||||||
|
# 組出晨間狀態檢查條目:每日睡眠結束時執行一次
|
||||||
|
printf '0 %s * * * %s %s --brief >> %s 2>&1 %s\n' \
|
||||||
|
"$(brief_hour)" "$(cron_env_prefix)" "$(cron_quote "${SCRIPT_DIR}/role_sleep.sh")" \
|
||||||
|
"$(cron_quote "$(sleep_log_path)")" "$BRIEF_CRON_MARKER"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_brief() {
|
||||||
|
# 晨間狀態檢查:執行使用者自訂腳本,把有變化的結果寫成一則記憶。
|
||||||
|
#
|
||||||
|
# 設計取捨:本 skill 不內建任何檢查邏輯(不假設使用者用 Gitea、GitHub 或任何服務),
|
||||||
|
# 改由使用者自行在 <角色 ID>.checks/ 放可執行腳本。沒有該目錄時完全不動作,對沒設定的人零影響。
|
||||||
|
# 腳本輸出視為外部資料:逐一限制長度、加 timeout,寫入前一律走 redact 遮蔽憑證與個資。
|
||||||
|
local dir timeout_s each_limit total_limit collected="" ran=0 skipped=0 reported=0
|
||||||
|
dir="$(checks_dir)"
|
||||||
|
if [ ! -d "$dir" ]; then
|
||||||
|
role_log "DBG" "沒有檢查腳本目錄(${dir}),略過晨間狀態檢查"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
timeout_s="$(brief_timeout)"
|
||||||
|
each_limit="$(brief_each_limit)"
|
||||||
|
total_limit="$(brief_limit)"
|
||||||
|
|
||||||
|
local script name result
|
||||||
|
for script in "$dir"/*.sh; do
|
||||||
|
[ -f "$script" ] || continue
|
||||||
|
name="${script##*/}"
|
||||||
|
if [ ! -x "$script" ]; then
|
||||||
|
role_log "WRN" "檢查腳本沒有執行權限,略過:${name}(chmod +x 後生效)"
|
||||||
|
skipped=$((skipped + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if command -v timeout >/dev/null 2>&1; then
|
||||||
|
result="$(timeout "$timeout_s" "$script" 2>&1 | head -c "$each_limit")"
|
||||||
|
else
|
||||||
|
result="$("$script" 2>&1 | head -c "$each_limit")"
|
||||||
|
fi
|
||||||
|
ran=$((ran + 1))
|
||||||
|
result="$(printf '%s' "$result" | sed '/^[[:space:]]*$/d')"
|
||||||
|
if [ -n "$result" ]; then
|
||||||
|
reported=$((reported + 1))
|
||||||
|
if [ -n "$collected" ]; then
|
||||||
|
collected="$(printf '%s\n- 【%s】\n%s' "$collected" "$name" "$result")"
|
||||||
|
else
|
||||||
|
collected="$(printf -- '- 【%s】\n%s' "$name" "$result")"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$ran" = 0 ]; then
|
||||||
|
role_log "DBG" "檢查腳本目錄沒有可執行腳本(略過 ${skipped} 個),略過晨間狀態檢查"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ -z "$collected" ]; then
|
||||||
|
role_log "INF" "晨間狀態檢查完成:執行 ${ran} 個腳本,沒有需要回報的變化"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
collected="$(printf '%s' "$collected" | head -c "$total_limit" | node "${SCRIPT_DIR}/transcript.js" redact 2>/dev/null)"
|
||||||
|
[ -n "$collected" ] || return 0
|
||||||
|
|
||||||
|
local today
|
||||||
|
today="$(TZ='Asia/Taipei' date +'%Y/%m/%d')"
|
||||||
|
{
|
||||||
|
printf 'CATEGORY: daily\n'
|
||||||
|
printf 'SUMMARY: %s 晨間狀態檢查:%s 個腳本有回報(共執行 %s 個)\n' "$today" "$reported" "$ran"
|
||||||
|
printf 'TAGS: 晨間檢查,狀態回報,待處理\n'
|
||||||
|
printf 'CONTENT:\n'
|
||||||
|
printf -- '- 由 %s 的檢查腳本於睡眠時段結束時自動收集,供本日第一次互動時主動回報使用者\n' "$dir"
|
||||||
|
printf '%s\n' "$collected"
|
||||||
|
} | node "${SCRIPT_DIR}/memory.js" write --role "$ROLE" >/dev/null 2>&1
|
||||||
|
|
||||||
|
role_log "INF" "晨間狀態檢查完成:執行 ${ran} 個腳本、${reported} 個有回報,已寫入一則記憶"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
cron_hours() {
|
cron_hours() {
|
||||||
# 依睡眠時段換算 cron 小時欄位(每小時檢查一次,讓 AI 運行中的情況能在下個小時重試)
|
# 依睡眠時段換算 cron 小時欄位(每小時檢查一次,讓 AI 運行中的情況能在下個小時重試)
|
||||||
local start end hour hours=""
|
local start end hour hours=""
|
||||||
@@ -271,11 +382,14 @@ install_cron() {
|
|||||||
command -v crontab >/dev/null 2>&1 || { role_log "ERR" "找不到 crontab,無法安裝排程"; return 1; }
|
command -v crontab >/dev/null 2>&1 || { role_log "ERR" "找不到 crontab,無法安裝排程"; return 1; }
|
||||||
mkdir -p "$(role_home)" 2>/dev/null
|
mkdir -p "$(role_home)" 2>/dev/null
|
||||||
local current new
|
local current new
|
||||||
current="$(crontab -l 2>/dev/null | grep -v -F "$CRON_MARKER" | grep -v -F "$NAP_CRON_MARKER")"
|
current="$(crontab -l 2>/dev/null | grep -v -F "$CRON_MARKER" | grep -v -F "$NAP_CRON_MARKER" | grep -v -F "$BRIEF_CRON_MARKER")"
|
||||||
|
new="$(printf '%s\n%s' "$current" "$(cron_line)" | sed '/^$/d')"
|
||||||
if nap_enabled; then
|
if nap_enabled; then
|
||||||
new="$(printf '%s\n%s\n%s' "$current" "$(cron_line)" "$(nap_cron_line)" | sed '/^$/d')"
|
new="$(printf '%s\n%s' "$new" "$(nap_cron_line)")"
|
||||||
else
|
fi
|
||||||
new="$(printf '%s\n%s' "$current" "$(cron_line)" | sed '/^$/d')"
|
# 晨間狀態檢查只在使用者建立了檢查腳本目錄時才排程,避免對沒設定的人留下無用條目
|
||||||
|
if brief_enabled && [ -d "$(checks_dir)" ]; then
|
||||||
|
new="$(printf '%s\n%s' "$new" "$(brief_cron_line)")"
|
||||||
fi
|
fi
|
||||||
printf '%s\n' "$new" | crontab - || { role_log "ERR" "寫入 crontab 失敗"; return 1; }
|
printf '%s\n' "$new" | crontab - || { role_log "ERR" "寫入 crontab 失敗"; return 1; }
|
||||||
role_log "INF" "已安裝睡眠排程:每日 $(cron_hours) 時整點檢查(角色 ${ROLE},時段 $(role_sleep_start)–$(role_sleep_end))"
|
role_log "INF" "已安裝睡眠排程:每日 $(cron_hours) 時整點檢查(角色 ${ROLE},時段 $(role_sleep_start)–$(role_sleep_end))"
|
||||||
@@ -284,6 +398,11 @@ install_cron() {
|
|||||||
else
|
else
|
||||||
role_log "INF" "小睡排程已停用(ROLE_NAP_ENABLED=${ROLE_NAP_ENABLED:-1})"
|
role_log "INF" "小睡排程已停用(ROLE_NAP_ENABLED=${ROLE_NAP_ENABLED:-1})"
|
||||||
fi
|
fi
|
||||||
|
if brief_enabled && [ -d "$(checks_dir)" ]; then
|
||||||
|
role_log "INF" "已安裝晨間狀態檢查排程:每日 $(brief_hour) 時執行 $(checks_dir) 內的檢查腳本"
|
||||||
|
else
|
||||||
|
role_log "DBG" "未安裝晨間狀態檢查排程(需建立 $(checks_dir) 並放入可執行的 *.sh)"
|
||||||
|
fi
|
||||||
role_log "INF" "排程輸出:$(sleep_log_path)"
|
role_log "INF" "排程輸出:$(sleep_log_path)"
|
||||||
if ! pgrep -x cron >/dev/null 2>&1 && ! pgrep -x crond >/dev/null 2>&1; then
|
if ! pgrep -x cron >/dev/null 2>&1 && ! pgrep -x crond >/dev/null 2>&1; then
|
||||||
role_log "WRN" "系統 cron 服務未執行(WSL 常見),排程不會觸發;SessionStart 的背景補跑仍會運作"
|
role_log "WRN" "系統 cron 服務未執行(WSL 常見),排程不會觸發;SessionStart 的背景補跑仍會運作"
|
||||||
@@ -294,16 +413,22 @@ install_cron() {
|
|||||||
remove_cron() {
|
remove_cron() {
|
||||||
# 移除本 skill 安裝的排程條目
|
# 移除本 skill 安裝的排程條目
|
||||||
command -v crontab >/dev/null 2>&1 || { role_log "ERR" "找不到 crontab"; return 1; }
|
command -v crontab >/dev/null 2>&1 || { role_log "ERR" "找不到 crontab"; return 1; }
|
||||||
crontab -l 2>/dev/null | grep -v -F "$CRON_MARKER" | grep -v -F "$NAP_CRON_MARKER" | crontab -
|
crontab -l 2>/dev/null | grep -v -F "$CRON_MARKER" | grep -v -F "$NAP_CRON_MARKER" | grep -v -F "$BRIEF_CRON_MARKER" | crontab -
|
||||||
role_log "INF" "已移除睡眠排程"
|
role_log "INF" "已移除睡眠、小睡與晨間狀態檢查排程"
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
show_status() {
|
show_status() {
|
||||||
# 以表格輸出目前角色與記憶狀態(供 skill 的 --status 使用)
|
# 以表格輸出目前角色與記憶狀態(供 skill 的 --status 使用)
|
||||||
local cron_state="未安裝" nap_state="未安裝" cron_service="未執行" window="否"
|
local cron_state="未安裝" nap_state="未安裝" brief_state="未安裝" cron_service="未執行" window="否" checks_state
|
||||||
crontab -l 2>/dev/null | grep -qF "$CRON_MARKER" && cron_state="已安裝"
|
crontab -l 2>/dev/null | grep -qF "$CRON_MARKER" && cron_state="已安裝"
|
||||||
crontab -l 2>/dev/null | grep -qF "$NAP_CRON_MARKER" && nap_state="已安裝"
|
crontab -l 2>/dev/null | grep -qF "$NAP_CRON_MARKER" && nap_state="已安裝"
|
||||||
|
crontab -l 2>/dev/null | grep -qF "$BRIEF_CRON_MARKER" && brief_state="已安裝"
|
||||||
|
if [ -d "$(checks_dir)" ]; then
|
||||||
|
checks_state="$(checks_dir)($(find "$(checks_dir)" -maxdepth 1 -name '*.sh' 2>/dev/null | wc -l) 個 .sh)"
|
||||||
|
else
|
||||||
|
checks_state="未建立($(checks_dir))"
|
||||||
|
fi
|
||||||
{ pgrep -x cron >/dev/null 2>&1 || pgrep -x crond >/dev/null 2>&1; } && cron_service="執行中"
|
{ pgrep -x cron >/dev/null 2>&1 || pgrep -x crond >/dev/null 2>&1; } && cron_service="執行中"
|
||||||
role_in_sleep_window && window="是"
|
role_in_sleep_window && window="是"
|
||||||
printf '| 項目 | 值 |\n| --- | --- |\n'
|
printf '| 項目 | 值 |\n| --- | --- |\n'
|
||||||
@@ -313,6 +438,8 @@ show_status() {
|
|||||||
printf '| 目前是否睡眠中 | %s |\n' "$window"
|
printf '| 目前是否睡眠中 | %s |\n' "$window"
|
||||||
printf '| cron 排程 | %s |\n' "$cron_state"
|
printf '| cron 排程 | %s |\n' "$cron_state"
|
||||||
printf '| 小睡排程 | %s |\n' "$nap_state"
|
printf '| 小睡排程 | %s |\n' "$nap_state"
|
||||||
|
printf '| 晨間檢查排程 | %s |\n' "$brief_state"
|
||||||
|
printf '| 檢查腳本目錄 | %s |\n' "$checks_state"
|
||||||
printf '| 小睡啟用 | %s |\n' "$(nap_enabled && printf '是' || printf '否')"
|
printf '| 小睡啟用 | %s |\n' "$(nap_enabled && printf '是' || printf '否')"
|
||||||
printf '| 小睡條件 | 閒置 ≥ %s 分鐘,待整理 ≥ %s 則,每 %s 分鐘檢查 |\n' "$(nap_idle_minutes)" "$(nap_min_inbox)" "$(nap_interval_minutes)"
|
printf '| 小睡條件 | 閒置 ≥ %s 分鐘,待整理 ≥ %s 則,每 %s 分鐘檢查 |\n' "$(nap_idle_minutes)" "$(nap_min_inbox)" "$(nap_interval_minutes)"
|
||||||
printf '| cron 服務 | %s |\n' "$cron_service"
|
printf '| cron 服務 | %s |\n' "$cron_service"
|
||||||
@@ -432,6 +559,15 @@ case "$MODE" in
|
|||||||
require_role
|
require_role
|
||||||
sleep_cycle "手動"
|
sleep_cycle "手動"
|
||||||
;;
|
;;
|
||||||
|
--brief)
|
||||||
|
role_enabled || exit 0
|
||||||
|
require_role
|
||||||
|
if ! brief_enabled; then
|
||||||
|
role_log "DBG" "晨間狀態檢查已停用(ROLE_BRIEF_ENABLED=${ROLE_BRIEF_ENABLED:-1}),略過"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
run_brief
|
||||||
|
;;
|
||||||
--export)
|
--export)
|
||||||
if [ -n "${3:-}" ]; then
|
if [ -n "${3:-}" ]; then
|
||||||
ROLE="$2"
|
ROLE="$2"
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ function isRealUserMessage(entry) {
|
|||||||
return payload.type === "user_message" && Boolean(String(payload.message || "").trim());
|
return payload.type === "user_message" && Boolean(String(payload.message || "").trim());
|
||||||
}
|
}
|
||||||
if (entry.type !== "user") return false;
|
if (entry.type !== "user") return false;
|
||||||
|
if (isMetaEntry(entry)) return false; // skill 載入等注入內容不算一輪對話
|
||||||
const content = entry.message?.content;
|
const content = entry.message?.content;
|
||||||
if (typeof content === "string") return Boolean(content.trim());
|
if (typeof content === "string") return Boolean(content.trim());
|
||||||
if (Array.isArray(content)) return content.some((block) => isObject(block) && block.type === "text");
|
if (Array.isArray(content)) return content.some((block) => isObject(block) && block.type === "text");
|
||||||
@@ -256,12 +257,21 @@ function stripInjected(text) {
|
|||||||
|
|
||||||
function isInjectedUserText(text) {
|
function isInjectedUserText(text) {
|
||||||
const head = String(text).trimStart().slice(0, 200);
|
const head = String(text).trimStart().slice(0, 200);
|
||||||
return /hook additional context|^Caveat:|^<[a-z-]+>/i.test(head);
|
return /hook additional context|^Caveat:|^<[a-z-]+>|^Base directory for this skill:/i.test(head);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claude Code 以 isMeta 標記非使用者輸入的注入內容(skill 載入、hook 附加內容等),
|
||||||
|
// sourceToolUseID 則代表該筆來自工具呼叫結果。兩者都不是使用者說的話,也不該算成一輪對話。
|
||||||
|
// 實測:載入一個 skill 會插入一筆 isMeta 的 user 訊息,長度可達兩萬字元,
|
||||||
|
// 若不排除會被當成使用者發言,既吃光字元預算也讓輪數計算失真。
|
||||||
|
function isMetaEntry(entry) {
|
||||||
|
return entry.isMeta === true || typeof entry.sourceToolUseID === "string";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只回傳對話文字;工具與思考一律丟棄。相容 Claude Code 與 Codex 兩種 JSONL。
|
// 只回傳對話文字;工具與思考一律丟棄。相容 Claude Code 與 Codex 兩種 JSONL。
|
||||||
function dialogLines(entry) {
|
function dialogLines(entry) {
|
||||||
const lines = [];
|
const lines = [];
|
||||||
|
if (isMetaEntry(entry)) return lines;
|
||||||
const payload = entry.payload;
|
const payload = entry.payload;
|
||||||
if (isObject(payload)) {
|
if (isObject(payload)) {
|
||||||
if (entry.type === "event_msg") {
|
if (entry.type === "event_msg") {
|
||||||
|
|||||||
+36
-2
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: role
|
name: role
|
||||||
description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI 工具以固定角色(name/nature/vibe/簽名 emoji)回覆,並把每輪對話累積成長期記憶:搭配相容的 SessionStart hook 於啟動時依字元預算載入高價值記憶、Stop hook 先本地過濾再輕量記錄對話,睡眠時段(預設 22:00 至隔天 06:00)由排程整理記憶(NREM 鞏固:分類/去噪/去重/合併/優先度;REM 整合:跨記憶連結/抽象化/提取線索;再依 semantic/episodic/procedural/emotional/preference/rule 與 explicit/implicit 標記長期記憶型態,壓縮歸檔並適當遺忘)。提供 --new(新建或更新角色;可只給角色名稱,必要時詢問來源/作品並推斷 name/nature/vibe/emoji 四欄)、--use(以角色 ID 切換啟用角色)、--list(列出角色與 ID)、--export(匯出角色壓縮檔)、--sleep(立即整理)、--status/--diagnose、--install-cron/--remove-cron、--forget-preview 等模式。當使用者說建立角色、新增人格、切換角色、匯出角色、備份角色、讓回覆更有特色、角色記憶、記憶整理、睡覺整理記憶、忘記舊記憶、角色沒有載入、hook 沒載入角色,或提到 .roles/.memory/ROLE_NAME/ROLE_ENABLED/ROLE_SLEEP_START/ROLE_MEMORY_HOME/ROLE_LOAD_LIMIT/ROLE_LOAD_INBOX_LIMIT/ROLE_LOAD_DIALOG_TURNS/ROLE_CAPTURE_ENABLED 時觸發。不適用於:工作紀錄寫入 Gitea wiki(用 /jsc-doc:worklog)、專案文件化(用 /jsc-doc:funcs)。
|
description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI 工具以固定角色(name/nature/vibe/簽名 emoji)回覆,並把每輪對話累積成長期記憶:搭配相容的 SessionStart hook 於啟動時依字元預算載入高價值記憶、Stop hook 先本地過濾再輕量記錄對話,睡眠時段(預設 22:00 至隔天 06:00)由排程整理記憶(NREM 鞏固:分類/去噪/去重/合併/優先度;REM 整合:跨記憶連結/抽象化/提取線索;再依 semantic/episodic/procedural/emotional/preference/rule 與 explicit/implicit 標記長期記憶型態,壓縮歸檔並適當遺忘)。提供 --new(新建或更新角色;可只給角色名稱,必要時詢問來源/作品並推斷 name/nature/vibe/emoji 四欄)、--use(以角色 ID 切換啟用角色)、--list(列出角色與 ID)、--export(匯出角色壓縮檔)、--sleep(立即整理)、--status/--diagnose、--install-cron/--remove-cron、--forget-preview、--brief(晨間狀態檢查)等模式。當使用者說建立角色、新增人格、切換角色、匯出角色、備份角色、讓回覆更有特色、角色記憶、記憶整理、睡覺整理記憶、忘記舊記憶、角色沒有載入、hook 沒載入角色、晨間狀態檢查、早上主動回報狀態,或提到 .roles/.memory/ROLE_NAME/ROLE_ENABLED/ROLE_SLEEP_START/ROLE_MEMORY_HOME/ROLE_LOAD_LIMIT/ROLE_LOAD_INBOX_LIMIT/ROLE_LOAD_DIALOG_TURNS/ROLE_CAPTURE_ENABLED 時觸發。不適用於:工作紀錄寫入 Gitea wiki(用 /jsc-doc:worklog)、專案文件化(用 /jsc-doc:funcs)。
|
||||||
---
|
---
|
||||||
|
|
||||||
# role — 角色人格與長期記憶
|
# role — 角色人格與長期記憶
|
||||||
@@ -13,13 +13,14 @@ description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI
|
|||||||
| `hooks/hooks.json` 的 `SessionStart` hook | harness 自動 | 啟動 CLI 時依字元預算載入角色定義+高價值記憶,另以獨立預算載入近期逐字對話與未整理工作記憶做工作階段交接,並要求角色在本工作階段第一則回覆主動問候;睡眠時段只回報「角色睡覺中」不載入 |
|
| `hooks/hooks.json` 的 `SessionStart` hook | harness 自動 | 啟動 CLI 時依字元預算載入角色定義+高價值記憶,另以獨立預算載入近期逐字對話與未整理工作記憶做工作階段交接,並要求角色在本工作階段第一則回覆主動問候;睡眠時段只回報「角色睡覺中」不載入 |
|
||||||
| `hooks/hooks.json` 的 `Stop` hook | harness 自動 | 每輪結束先記錄最後互動時間 → 用本地規則過濾低價值短回合 → 值得保存時才濃縮成一則輕量 inbox 記憶 → 遮蔽 → 寫入 `inbox/` |
|
| `hooks/hooks.json` 的 `Stop` hook | harness 自動 | 每輪結束先記錄最後互動時間 → 用本地規則過濾低價值短回合 → 值得保存時才濃縮成一則輕量 inbox 記憶 → 遮蔽 → 寫入 `inbox/` |
|
||||||
| cron 排程(本 skill 安裝) | 系統排程 | 睡眠時段每小時檢查一次:**有 AI 在運行就不睡**;另可依 CLI 閒置時間自動小睡整理 |
|
| cron 排程(本 skill 安裝) | 系統排程 | 睡眠時段每小時檢查一次:**有 AI 在運行就不睡**;另可依 CLI 閒置時間自動小睡整理 |
|
||||||
| 本 skill `/jsc-generic:role` | 使用者/助理手動 | `--new`/`--use`/`--list`/`--export`/`--sleep`/`--status`/`--install-cron`/`--forget-preview` |
|
| 本 skill `/jsc-generic:role` | 使用者/助理手動 | `--new`/`--use`/`--list`/`--export`/`--sleep`/`--brief`/`--status`/`--install-cron`/`--forget-preview` |
|
||||||
| `scripts/role/role_load.sh` | SessionStart hook | 角色與記憶載入;參考 OpenClaw 的 SOUL/AGENTS/USER/MEMORY 分層,把人格、操作邊界、使用者記憶分開注入,並提供第一則回覆問候提示(單一實作,避免漂移) |
|
| `scripts/role/role_load.sh` | SessionStart hook | 角色與記憶載入;參考 OpenClaw 的 SOUL/AGENTS/USER/MEMORY 分層,把人格、操作邊界、使用者記憶分開注入,並提供第一則回覆問候提示(單一實作,避免漂移) |
|
||||||
| `scripts/role/role_capture.sh` | Stop hook | 對話 → 記憶(固定欄位格式) |
|
| `scripts/role/role_capture.sh` | Stop hook | 對話 → 記憶(固定欄位格式) |
|
||||||
| `scripts/role/role_sleep.sh` | cron/小睡/補跑/手動 | 睡眠與小睡判斷、記憶整理、角色匯出、排程安裝、狀態輸出 |
|
| `scripts/role/role_sleep.sh` | cron/小睡/補跑/手動 | 睡眠與小睡判斷、記憶整理、角色匯出、排程安裝、狀態輸出 |
|
||||||
| `scripts/role/memory.js` | 上述共用 | 記憶檔讀寫、分類、去重合併、優先度、心理學記憶型態與關聯 metadata、壓縮歸檔、遺忘、載入組裝 |
|
| `scripts/role/memory.js` | 上述共用 | 記憶檔讀寫、分類、去重合併、優先度、心理學記憶型態與關聯 metadata、壓縮歸檔、遺忘、載入組裝 |
|
||||||
| `scripts/role/transcript.js` | 上述共用 | 抽本輪對話片段、抽最近數輪純對話供工作階段交接、機密與個資遮蔽 |
|
| `scripts/role/transcript.js` | 上述共用 | 抽本輪對話片段、抽最近數輪純對話供工作階段交接、機密與個資遮蔽 |
|
||||||
| `scripts/role/role_lib.sh` | 上述共用 | log、角色解析、睡眠時段、AI 行程偵測、CLI 選擇、記憶鎖 |
|
| `scripts/role/role_lib.sh` | 上述共用 | log、角色解析、睡眠時段、AI 行程偵測、CLI 選擇、記憶鎖 |
|
||||||
|
| `scripts/role/examples/` | 使用者自行複製 | 晨間狀態檢查的範例腳本;複製到 `~/.roles/<角色 ID>.checks/` 才會生效 |
|
||||||
|
|
||||||
### 各助理支援範圍
|
### 各助理支援範圍
|
||||||
|
|
||||||
@@ -112,6 +113,10 @@ ROLE_DIR="<skill base directory>/../../scripts/role" # 其他助理
|
|||||||
| `ROLE_NAP_IDLE_MINUTES` | | 小睡前需連續閒置的分鐘數,由 Stop hook 記錄最後互動時間 | `45` |
|
| `ROLE_NAP_IDLE_MINUTES` | | 小睡前需連續閒置的分鐘數,由 Stop hook 記錄最後互動時間 | `45` |
|
||||||
| `ROLE_NAP_MIN_INBOX` | | 小睡整理所需的最少待整理 inbox 筆數 | `3` |
|
| `ROLE_NAP_MIN_INBOX` | | 小睡整理所需的最少待整理 inbox 筆數 | `3` |
|
||||||
| `ROLE_NAP_INTERVAL_MINUTES` | | 小睡排程檢查間隔分鐘數(cron 每 `*/N` 分鐘觸發) | `10` |
|
| `ROLE_NAP_INTERVAL_MINUTES` | | 小睡排程檢查間隔分鐘數(cron 每 `*/N` 分鐘觸發) | `10` |
|
||||||
|
| `ROLE_BRIEF_ENABLED` | | 晨間狀態檢查開關;設 `0` 可停用 | `1` |
|
||||||
|
| `ROLE_BRIEF_TIMEOUT` | | 單個檢查腳本的逾時秒數 | `30` |
|
||||||
|
| `ROLE_BRIEF_EACH_LIMIT` | | 單個檢查腳本輸出的字元上限 | `600` |
|
||||||
|
| `ROLE_BRIEF_LIMIT` | | 所有檢查腳本輸出合計的字元上限 | `2000` |
|
||||||
| `ROLE_SCOPE` | | 冒號分隔的路徑前綴,僅這些路徑下的 session 載入/記錄 | 全部 session |
|
| `ROLE_SCOPE` | | 冒號分隔的路徑前綴,僅這些路徑下的 session 載入/記錄 | 全部 session |
|
||||||
| `ROLE_ERRLOG` | | 錯誤訊息額外寫入的檔案路徑 | 只走 stderr |
|
| `ROLE_ERRLOG` | | 錯誤訊息額外寫入的檔案路徑 | 只走 stderr |
|
||||||
|
|
||||||
@@ -225,6 +230,34 @@ ROLE_DIR="<skill base directory>/../../scripts/role" # 其他助理
|
|||||||
|
|
||||||
小睡不受 `ROLE_SLEEP_START`/`ROLE_SLEEP_END` 限制;它只避開整理用的 headless 子 CLI,讓互動式 CLI 長時間閒置時仍可整理記憶。若未設定環境變數,預設為 `ROLE_NAP_ENABLED=1`、`ROLE_NAP_IDLE_MINUTES=45`、`ROLE_NAP_MIN_INBOX=3`、`ROLE_NAP_INTERVAL_MINUTES=10`。
|
小睡不受 `ROLE_SLEEP_START`/`ROLE_SLEEP_END` 限制;它只避開整理用的 headless 子 CLI,讓互動式 CLI 長時間閒置時仍可整理記憶。若未設定環境變數,預設為 `ROLE_NAP_ENABLED=1`、`ROLE_NAP_IDLE_MINUTES=45`、`ROLE_NAP_MIN_INBOX=3`、`ROLE_NAP_INTERVAL_MINUTES=10`。
|
||||||
|
|
||||||
|
### `--brief`(晨間狀態檢查)
|
||||||
|
|
||||||
|
在睡眠時段結束的整點執行使用者自訂的檢查腳本,把有變化的結果寫成一則記憶,讓角色在當天第一次互動時就能主動回報 —— 例如「PR 還沒合併」、「昨晚 CI 失敗了」,而不必等使用者開口才去查。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"${ROLE_DIR}/role_sleep.sh" --brief
|
||||||
|
```
|
||||||
|
|
||||||
|
**本 skill 不內建任何檢查邏輯**,不假設使用者用 Gitea、GitHub 或任何服務。檢查內容完全由使用者決定:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.roles/<角色 ID>.checks
|
||||||
|
cp "${ROLE_DIR}/examples/check-gitea-prs.sh" ~/.roles/<角色 ID>.checks/
|
||||||
|
chmod +x ~/.roles/<角色 ID>.checks/check-gitea-prs.sh
|
||||||
|
"${ROLE_DIR}/role_sleep.sh" --install-cron # 重跑才會加入排程條目
|
||||||
|
```
|
||||||
|
|
||||||
|
| 規則 | 說明 |
|
||||||
|
| --- | --- |
|
||||||
|
| 目錄不存在 | 完全不動作,也不會安裝排程條目 —— 對沒設定的人零影響 |
|
||||||
|
| 只執行 `*.sh` | 且必須有執行權限;沒有 `+x` 會記一筆警告並略過 |
|
||||||
|
| **沒變化就不要輸出** | 晨間檢查只在有輸出時才寫記憶。腳本靜默即代表「一切正常,不必打擾使用者」 |
|
||||||
|
| 逾時與長度 | 每個腳本受 `ROLE_BRIEF_TIMEOUT` 限制,輸出受 `ROLE_BRIEF_EACH_LIMIT` 與 `ROLE_BRIEF_LIMIT` 截斷 |
|
||||||
|
| 遮蔽 | 腳本輸出視為外部資料,寫入記憶前一律經 `transcript.js redact` 遮蔽憑證與個資 |
|
||||||
|
| 記憶分類 | 寫成 `daily` 低優先度記憶,會依遺忘規則自然淘汰,不會長期堆積 |
|
||||||
|
|
||||||
|
**安全須知**:這個機制會以使用者身分執行 `.checks/` 內的腳本,等同於自己寫的 cron job。只放自己看得懂的腳本,不要放來源不明的檔案。cron 沒有互動 shell 的環境變數,腳本需要的設定要自己在腳本內處理或寫進 crontab。
|
||||||
|
|
||||||
### 額度控制策略
|
### 額度控制策略
|
||||||
|
|
||||||
角色系統預設避免因常駐人格與記憶造成大量模型額度占用:
|
角色系統預設避免因常駐人格與記憶造成大量模型額度占用:
|
||||||
@@ -345,6 +378,7 @@ updated: <yyyy/MM/dd HH:mm:ss>
|
|||||||
- 睡眠時段內啟動 CLI **不會載入角色**:以一般助理身分回應,不自稱角色、不使用角色語氣與簽名 emoji。此時對話仍會被記錄成記憶。
|
- 睡眠時段內啟動 CLI **不會載入角色**:以一般助理身分回應,不自稱角色、不使用角色語氣與簽名 emoji。此時對話仍會被記錄成記憶。
|
||||||
- 睡眠排程每小時檢查一次,**偵測到有 AI 正在運行就不睡**,留到下個整點再試;沒有 AI 運行才進入睡眠並整理記憶。
|
- 睡眠排程每小時檢查一次,**偵測到有 AI 正在運行就不睡**,留到下個整點再試;沒有 AI 運行才進入睡眠並整理記憶。
|
||||||
- 小睡排程預設啟用:CLI 最後互動時間超過 45 分鐘且 `inbox/` 至少 3 則待整理記憶時,可不等睡眠時段自動整理;可用 `ROLE_NAP_ENABLED`、`ROLE_NAP_IDLE_MINUTES`、`ROLE_NAP_MIN_INBOX` 與 `ROLE_NAP_INTERVAL_MINUTES` 調整。
|
- 小睡排程預設啟用:CLI 最後互動時間超過 45 分鐘且 `inbox/` 至少 3 則待整理記憶時,可不等睡眠時段自動整理;可用 `ROLE_NAP_ENABLED`、`ROLE_NAP_IDLE_MINUTES`、`ROLE_NAP_MIN_INBOX` 與 `ROLE_NAP_INTERVAL_MINUTES` 調整。
|
||||||
|
- 睡眠時段結束的整點會執行晨間狀態檢查(見 `--brief`):跑完使用者自訂的檢查腳本後寫成一則記憶,讓角色當天第一次互動就能主動回報變化。只有建立了 `~/.roles/<角色 ID>.checks/` 才會排程。
|
||||||
|
|
||||||
### 記憶
|
### 記憶
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user