釋出助理心跳與運行閘門(閘門未接線) #69
@@ -28,6 +28,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
|
||||
| `hooks/deny.sh` | 不直接接線,由 `version-guard.sh` 與 `restart-gate.sh` 呼叫 | 產出各 CLI 認得的阻擋輸出,訊息從參數或標準輸入進。claude、codex、copilot 訊息寫 stderr 並回 exit 2;antigravity 印 stdout 的 `{"decision":"deny","reason":"..."}` 並固定回 0——那支 CLI 的結束碼語意兩邊文件都沒寫,靠結束碼會變成「判定擋下、CLI 照樣放行」的無聲失效,所以 stdout 只准有那一行;kiro 擋不下技能叫用,改印警告後回 0;認不得的代號走 stderr 加 2 這個保守預設 |
|
||||
| `hooks/version-guard.sh` | PreToolUse:claude matcher `Skill`、codex matcher `Bash`、copilot matcher `skill`、antigravity matcher `^view_file$` 加 `PreInvocation`;kiro `userPromptSubmit`(只注入警告) | 技能使用前的版本前置檢查,擋兩種情況,兩種都擋下該次呼叫並提示更新指令(更新指令依當前 CLI 給;技能名解析交給 `hooks/skill-name.sh`、阻擋輸出形態交給 `hooks/deny.sh`,兩支的規則見上面兩列,這裡不重寫第二套):一是本機**實際載入**版本落後遠端發佈版本,二是技能所屬 plugin 的 manifest 在 `jsc.requires` 宣告的相依 plugin 版本落後——相依那一項讀 `installPath` 底下那份 `plugin.json`,逐項比對相依 plugin 的本機實際載入版本,訊息講明哪一個 plugin、需要哪一版、目前哪一版、怎麼補。相依檢查排在遠端比對之前,全部讀本機檔案,離線也判得動;判定邏輯自己實作,不呼叫 `jsc-cli/tools/check-requires.sh`,免得 hook 散落到別的 domain,也免得跟已宣告相依 `jsc-hooks` 的 `jsc-cli` 做出循環相依。部署那端照樣更新、只回報,阻擋落在這支 hook。兩種都只擋確定落後:超前放行(開發技能組時本機本來就會超前),讀不到本機版本、推導不出站台、查不到遠端版本、解不出安裝路徑、讀不到 manifest、manifest 沒有 `jsc.requires`、讀不到相依 plugin 的本機載入版本也一律放行。遠端版本快取在 `$JSC_HOME/version-cache/{CLI 代號}/{domain}`,一支 CLI 一份;舊路徑 `$JSC_HOME/version-cache/{domain}` 會在第一次讀取時複製到新路徑。逃生門 `JSC_VERSION_GUARD=off`。豁免 `jsc-cli:deploy`、`jsc-hooks:hooks-install`、`jsc-hooks:repair`、`jsc-cli:models`、`jsc-meta:*`、`jsc-ask:ask`、`jsc-gitea:wiki`——共 7 項,兩種擋人情況共用同一份,相依落後不另立短清單;清單的唯一來源是 `hooks/version-guard.sh` 的檔頭,那裡一項一個理由 |
|
||||
| `hooks/restart-gate.sh` | PreToolUse:claude matcher `Skill`、codex matcher `Bash`、copilot matcher `skill`、antigravity matcher `^view_file$` 加 `PreInvocation`;kiro `userPromptSubmit`(只注入警告) | 部署後強制重啟閘門:`$JSC_HOME/restart-required.d/{CLI 代號}` 一支 CLI 一份,當前 CLI 那份存在時擋下 jsc 技能呼叫,並印出要重新啟動哪一支 CLI(技能名解析交給 `hooks/skill-name.sh`、阻擋輸出形態交給 `hooks/deny.sh`,兩支的規則見上面兩列);別支 CLI 那幾份不影響這一支。狀態檔由 `jsc-cli:deploy` 在 install 或 update 收尾時經 `restart-gate.sh require {install|update} [{domain}...]` 寫入當前 CLI 那一份,在下一個工作階段開始時由 `session-timer.sh` 呼叫 `restart-gate.sh clear` 只清除那一份。判定看檔案在不在:狀態檔讀不到、CLI 代號取不到、技能名取不到都放行(理由與 `version-guard.sh` 一致,只擋確定違規)。舊格式的單一檔案 `$JSC_HOME/restart-required` 存在時一律擋,`clear` 會一併刪掉它(過渡相容,詳見下面「部署後重啟狀態檔」)。豁免 `jsc-cli:deploy`、`jsc-hooks:hooks-install`、`jsc-hooks:repair`、`jsc-gitea:wiki`、`jsc-log:worklog`、`jsc-log:learn`、`jsc-meta:*`、`jsc-ask:ask`、`jsc-git:pr`、`jsc-git:commit`——部署後還要寫得完技能組異動報告與工作日誌,hook 壞掉也要修得回來,整批擋下去這些規則會互相打死。清單認技能名不認呼叫鏈,後三支是為了讓前七支走得完才補進來的:`deploy` 要問模式、報告寫完要開 PR。另有唯讀子指令 `report`,一支 CLI 一行印出每一份狀態檔的內容,看得出還有哪幾支沒重啟。逃生門 `JSC_RESTART_GATE=off` |
|
||||
| `hooks/heartbeat.sh` | 不接線,由 `jsc-assist` 的助理主體、系統排程與 `status` 技能呼叫 | 助理心跳檔 `$JSC_HOME/assistant/heartbeat` 的讀寫工具,純文字 key=value,欄位 `ts`、`pid`、`cli`、`session`。四個子命令:`write` 寫入四個欄位(目錄不存在就建,先寫暫存檔再改名,讀的那一端永遠讀到完整的一份)、`check` 判定新不新鮮(什麼都不印,結果只在結束碼)、`report` 印一行現況供 `status` 技能與擋人訊息取用、`clear` 刪除心跳檔(由助理的 `stop` 呼叫,檔案不存在也算成功)。新鮮的判準只有一條:心跳檔存在、而且 `ts` 距現在小於門檻秒數,門檻預設 300(心跳週期 60 秒的五倍,一次卡頓不會誤判),可用 `JSC_ASSISTANT_HEARTBEAT_TTL` 覆寫。**絕不看 pid 存活**:五支 CLI 與容器裡的行程互相看不到彼此的 pid,問了會把活著的判成停了,pid 又會被回收,反過來把停掉的判成還在跑,兩種誤判都不報錯;pid 只當擋人訊息的線索。`check` 的結束碼分四種讓呼叫端各自處置:0 新鮮、1 過期(跑過但停了)、3 心跳檔不存在(從沒啟動過)、4 檔案在但 `ts` 讀不出來(檔案壞了);`write` 與 `clear` 的檔案系統失敗回 5,用法錯誤回 6。四個子命令都不讀標準輸入——這支不是 hook,是被工具端呼叫的腳本,讀了會在管線沒人關閉時整支卡死。逐碼意義與 `report` 的欄位順序見腳本檔頭 |
|
||||
| `hooks/skill-usage.sh` | PostToolUse(Skill) | 記錄技能使用與呼叫鏈到 `$JSC_HOME/usage/*.jsonl`,供 `jsc-log:stats` 統計 |
|
||||
| `hooks/comment-scope.sh` | UserPromptSubmit、PostToolUse(Write、Edit、MultiEdit)、codex `notify`、kiro `userPromptSubmit`、`tools/jsc-wrap.sh` 收尾 | 程式碼註解不得夾帶文件相關資訊與審查流程痕跡,共三種模式。`prompt`:在每次提示注入規則摘要(禁止項與白名單各一行),五個 CLI 都接得到。無參數:寫檔後的逐檔掃描,從 stdin JSON 取 `file_path`(或環境變數 `JSC_CHANGED_FILE`),只有 claude 的 PostToolUse 接得上。`sweep [dir]`:掃整個 git 工作區這次改過的所有檔案,給沒有 post-tool hook 的四個 CLI 用,找不到 git 就安靜 exit 0。掃描時機每個 CLI 不同——claude 逐檔即時(PostToolUse)、codex 每輪結束(`notify`)、kiro 每輪提示送出時(`userPromptSubmit`,掃的是上一輪寫的檔)、copilot 與 antigravity 只有工作階段結束時由 `tools/jsc-wrap.sh` 收尾掃一次。兩種掃描模式都只看 `git diff HEAD` 的新增行、不翻舊帳,命中就把警告與最多三行證據送到 stderr 並以 exit 2 交回模型就地修正(不擋寫入,檔案已經寫好了)。markdown、純文字、資料檔與二進位檔一律跳過。只實作可用樣式判定的項目,專案代號、客戶名稱這類判不出來的交給 `/jsc-review:code-review`。規則正文的唯一來源在 `jsc-review` 的 `references/comment-scope.md`,本存取庫不留副本。逃生門 `JSC_COMMENT_SCOPE=off` |
|
||||
| `hooks/lang-guard.sh` | UserPromptSubmit、PostToolUse(Write、Edit、MultiEdit)、codex `notify`、kiro `userPromptSubmit`、`tools/jsc-wrap.sh` 收尾 | 所有非程式碼輸出一律繁體中文、UTF-8、無亂碼、無簡體字,共三種模式。`prompt`:在每次提示注入規則摘要(適用範圍與自我檢查各一行),五個 CLI 都接得到。無參數:寫檔後的逐檔掃描,從 stdin JSON 取 `file_path`(或環境變數 `JSC_CHANGED_FILE`),只有 claude 的 PostToolUse 接得上。`sweep [dir]`:掃整個 git 工作區這次改過的所有檔案,給沒有 post-tool hook 的四個 CLI 用,找不到 git 就安靜 exit 0。接線位置與掃描時機跟 `comment-scope.sh` 完全一樣,見下面那張表。偵測三項:簡體字(字表在 `hooks/simplified.txt`,讀不到就安靜跳過這一項)、亂碼(U+FFFD 替代字元與雙重編碼殘骸)、非 UTF-8 編碼(用 `iconv` 判定,沒有 `iconv` 就跳過)。三項都掃整個檔案、不只掃註解行,`.md` 與純文字檔照掃——那些正是「非程式碼輸出」的主場,這兩點跟 `comment-scope.sh` 刻意不同。掃描深度仍只看 `git diff HEAD` 的新增行、不翻舊帳,命中就把警告與最多三行證據送到 stderr 並以 exit 2 交回模型就地修正(不擋寫入)。二進位檔(只認 NUL 位元組)與 `*.lock`、`*.min.js`、`*.map` 這類產生檔跳過;`hooks/simplified.txt`、`hooks/ste100-guard.sh`、`hooks/lang-guard.sh` 也跳過,那三份檔案裡的簡體字與亂碼樣本是被討論的對象,不是被使用。規則正文的唯一來源在 `jsc-meta` 的 `references/ste100.md`。逃生門 `JSC_LANG_GUARD=off` |
|
||||
@@ -217,6 +218,7 @@ Claude 由 `hooks/hooks.json` 自動接線九支 hook;其他 CLI 用 `hooks-in
|
||||
| `JSC_LANG_GUARD` | 設 `off` 完全略過繁中與編碼檢查(`lang-guard.sh` 三種模式都直接結束) | 啟用檢查 |
|
||||
| `JSC_WRITE_GUARD` | 設 `off` 完全略過寫入與提交閘門(`write-guard.sh` 三種模式都直接結束) | 啟用閘門 |
|
||||
| `JSC_WRITE_GUARD_TTL` | `write-guard.sh review` 判定「稽核技能還在跑」的時效秒數 | 預設 900 |
|
||||
| `JSC_ASSISTANT_HEARTBEAT_TTL` | `hooks/heartbeat.sh check` 判定心跳新鮮的門檻秒數。值不是正整數就退回預設值 | 預設 300(心跳週期 60 秒的五倍) |
|
||||
| `JSC_READONLY` | 設 `1` 時 `tools/wire-cli.sh` 只准 `status` 與 `smoke`,`purge` 與接線一律拒絕並回 exit 6 | 四個用法都可執行 |
|
||||
| `JSC_CHANGED_FILE` | 非 Claude CLI 要掃描的檔案路徑,代替 stdin JSON 的 `file_path`,供 `comment-scope.sh` 與 `lang-guard.sh` 使用 | 安靜降級,不掃描 |
|
||||
| `JSC_TOOL_COMMAND` | 非 Claude CLI 要判定的 Bash 指令字串,代替 stdin JSON 的 `command`,供 `write-guard.sh commit` 使用 | 安靜降級,不判定 |
|
||||
|
||||
Executable
+176
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env sh
|
||||
# heartbeat.sh — 助理心跳檔的讀寫工具。
|
||||
#
|
||||
# 助理在背景跑,前景會話看不到它。心跳檔就是它還在跑的唯一證據:助理主體與系統排程每 60 秒
|
||||
# 寫一次,`jsc-assist:status` 與擋人訊息讀這一份,判斷助理在不在。
|
||||
#
|
||||
# 這支不是 hook,不接在任何事件上,只被工具端呼叫,所以它擋不到任何人。它只做判定,擋不擋
|
||||
# 由呼叫端自己決定——助理不參與閘門判定,只負責維持心跳。
|
||||
#
|
||||
# 用法(四個子命令都不讀標準輸入,理由見下方「不讀標準輸入」):
|
||||
# heartbeat.sh write 寫入心跳檔,四個欄位一次寫齊,目錄不存在就建。由助理主體與系統
|
||||
# 排程呼叫。
|
||||
# heartbeat.sh check 判定心跳新不新鮮。什麼都不印,結果只在結束碼;要細節請跑 report。
|
||||
# heartbeat.sh report 印一行心跳現況,格式見下方「report 輸出格式」。
|
||||
# heartbeat.sh clear 刪除心跳檔。由助理的 stop 呼叫。檔案不存在也算成功。
|
||||
#
|
||||
# 結束碼:
|
||||
# 0 check 判定新鮮(state=fresh);write 寫成功;clear 清完,檔案已經不在;report 印完
|
||||
# 1 check:心跳檔在、ts 也讀得到,但距現在已達門檻(state=stale)。助理跑過,現在停了。
|
||||
# 訊息要叫人去查助理為什麼停
|
||||
# 2 保留給「腳本沒跑起來」:`. lib.sh` 載入失敗時 sh 自己回這一碼(見最下方註)。判定路徑
|
||||
# 刻意不用 2,兩者才分得開
|
||||
# 3 check:心跳檔不存在(state=absent)。助理從沒啟動過。處置與 1 不同,訊息要叫人去啟動
|
||||
# 4 check:心跳檔在、ts 卻讀不出來(state=invalid,缺鍵、空值或不是數字)。檔案壞了,不是
|
||||
# 助理停了。呼叫端一律當成不新鮮處置,絕不可以退回當成新鮮
|
||||
# 5 檔案系統操作失敗:write 寫不進去(磁碟滿、權限壞、目錄建不起來),或 clear 刪不掉、
|
||||
# 檔案還在。這是嚴重狀況——助理沒有心跳就會被自己那道閘門擋掉,所以一定要吵出來,
|
||||
# 不能安靜當成成功
|
||||
# 6 用法錯誤:不認得的子命令,或一個子命令都沒給。刻意不與上面任何一種正常狀態共用碼,
|
||||
# 共用了呼叫端就分不出「助理沒在跑」與「這支腳本被叫錯」
|
||||
#
|
||||
# --- 只看 ts,絕不看 pid 存活 ---
|
||||
#
|
||||
# 新鮮的判準只有一條:心跳檔存在,而且 ts 距現在小於門檻秒數。pid 一律不拿來判定。
|
||||
# 一台機器上五支 CLI 各自是獨立行程,助理也可能跑在容器裡,彼此看不到對方的 pid:拿
|
||||
# `kill -0` 去問,看不到的行程一律回失敗,活著的助理會被判成停了;pid 還會被回收,別人的
|
||||
# 行程剛好接到同一個號碼,就反過來把停掉的助理判成還在跑。兩種誤判都不會報錯,查起來也沒有
|
||||
# 線索。pid 只寫進檔案當擋人訊息的線索,讓人自己去查那個行程。
|
||||
#
|
||||
# --- 門檻為什麼是 300 ---
|
||||
#
|
||||
# 心跳週期是 60 秒,門檻取五倍。一次網路或磁碟卡頓讓某一拍沒寫成,後面還有四拍補得回來,
|
||||
# 不會誤判成助理停了。門檻用環境變數 JSC_ASSISTANT_HEARTBEAT_TTL 覆寫,單位是秒;值不是
|
||||
# 正整數就退回 300——環境變數打錯字不該讓判定整個歪掉。
|
||||
#
|
||||
# --- 不讀標準輸入 ---
|
||||
#
|
||||
# 這支是被工具端呼叫的腳本,四個子命令一律不讀 stdin。理由與 restart-gate.sh 的子命令相同:
|
||||
# read_stdin 在標準輸入是管線又沒人關閉時會一直等,工具端呼叫就整支卡死。
|
||||
#
|
||||
# --- 狀態檔格式 ---
|
||||
#
|
||||
# $JSC_HOME/assistant/heartbeat(JSC_HOME 未設定時為 ~/.jsc),純文字 key=value,一行一欄位,
|
||||
# 順序不拘,不認得的鍵一律忽略:
|
||||
# ts={epoch 秒數} 寫入當下的時間。判定只看這一欄
|
||||
# pid={行程 id} 寫入者的行程 id。只當擋人訊息的線索
|
||||
# cli={CLI 代號} 寫入者是哪一支 CLI,取自 cli_name()
|
||||
# session={id} 寫入者的工作階段 id,取自 session_id()
|
||||
# 寫入走「先寫暫存檔、再改名」:改名是原子的,讀的那一端永遠讀到完整的一份。直接覆寫的話,
|
||||
# 剛好讀到寫一半的檔案會少掉 ts,判定就從 fresh 掉成 invalid,助理明明活著卻被說成壞了。
|
||||
#
|
||||
# --- report 輸出格式 ---
|
||||
#
|
||||
# 固定一行,鍵的順序固定,欄位以空白分隔。鍵一個都不會少,缺值就只留鍵名,呼叫端不必判斷
|
||||
# 有沒有這一欄。路徑擺最後,路徑含空白時才不會把後面的欄位吃掉:
|
||||
# state={fresh|stale|invalid|absent} ts={epoch} age={秒} ttl={秒} pid={} cli={} session={} file={路徑}
|
||||
# state 的四種值與 check 的結束碼一一對應:fresh=0、stale=1、absent=3、invalid=4。
|
||||
# 例(心跳新鮮):
|
||||
# state=fresh ts=1756684800 age=42 ttl=300 pid=31415 cli=claude session=a1b2c3 file=/root/.jsc/assistant/heartbeat
|
||||
# 例(心跳檔不存在):
|
||||
# state=absent ts= age= ttl=300 pid= cli= session= file=/root/.jsc/assistant/heartbeat
|
||||
# ts 不是數字時 ts 與 age 兩欄都印空的:那個值是垃圾,原樣印出來會夾帶空白把欄位切歪。
|
||||
#
|
||||
# 註:本檔以 `. "$HERE/lib.sh"` 載入共用函式,沒有接 `|| true`。載入失敗時 sh 會就地結束並回
|
||||
# 2。這一點的後果與 restart-gate.sh 不同:那支接在 PreToolUse 上,回 2 等於無聲擋下每一次
|
||||
# 技能呼叫;這支沒接任何 hook,回 2 只會讓呼叫端收到「心跳判不出來」,擋不到任何人。
|
||||
HERE=$(dirname "$0"); . "$HERE/lib.sh"
|
||||
|
||||
# 這支永遠不讀標準輸入,但 session_id() 會去看 STDIN_JSON。先設成空字串,讓它直接走環境
|
||||
# 變數那條路,不會因為變數沒定義而拿到不確定的值。
|
||||
STDIN_JSON=""
|
||||
|
||||
STATE_DIR="$JSC_HOME/assistant"
|
||||
STATE="$STATE_DIR/heartbeat"
|
||||
|
||||
DEFAULT_TTL=300
|
||||
|
||||
# 門檻秒數。環境變數不是正整數就退回預設值,理由見檔頭「門檻為什麼是 300」。
|
||||
ttl() {
|
||||
_t="${JSC_ASSISTANT_HEARTBEAT_TTL:-}"
|
||||
case "$_t" in
|
||||
''|*[!0-9]*) printf '%s' "$DEFAULT_TTL"; return 0 ;;
|
||||
esac
|
||||
if [ "$_t" -gt 0 ] 2>/dev/null; then printf '%s' "$_t"; else printf '%s' "$DEFAULT_TTL"; fi
|
||||
}
|
||||
|
||||
# 從心跳檔取一個欄位;檔案讀不到或欄位不存在就不輸出。
|
||||
field() { # $1=鍵名
|
||||
[ -f "$STATE" ] && [ -r "$STATE" ] || return 0
|
||||
sed -n "s/^$1=//p" "$STATE" 2>/dev/null | head -n1
|
||||
}
|
||||
|
||||
# 判定心跳狀態,印出「{state}<TAB>{ts}<TAB>{age}」,後兩欄在 absent 與 invalid 時留空。
|
||||
# check 與 report 共用這一份:兩邊各判一次就會漂移,狀態與訊息對不上。
|
||||
probe() {
|
||||
if [ ! -f "$STATE" ] || [ ! -r "$STATE" ]; then
|
||||
printf 'absent\t\t\n'; return 0
|
||||
fi
|
||||
_ts=$(field ts)
|
||||
case "$_ts" in
|
||||
''|*[!0-9]*) printf 'invalid\t\t\n'; return 0 ;;
|
||||
esac
|
||||
# 去掉開頭的 0:POSIX 算術把 08 當八進位,會直接報錯,錯完 age 是空的,判定就整條歪掉。
|
||||
while :; do
|
||||
case "$_ts" in 0?*) _ts=${_ts#0} ;; *) break ;; esac
|
||||
done
|
||||
_age=$(( $(now_epoch) - _ts ))
|
||||
# age 是負的代表 ts 在未來,那是時鐘偏移,不是助理停了,照樣算新鮮。
|
||||
if [ "$_age" -lt "$(ttl)" ]; then _st=fresh; else _st=stale; fi
|
||||
printf '%s\t%s\t%s\n' "$_st" "$_ts" "$_age"
|
||||
}
|
||||
|
||||
usage() {
|
||||
printf 'usage: heartbeat.sh {write|check|report|clear}\n' >&2
|
||||
exit 6
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
write)
|
||||
# 心跳檔的位置被目錄或別的東西佔住時要當場失敗。`mv` 遇到目標是目錄會把暫存檔搬進去,
|
||||
# 搬得成功、心跳檔卻永遠不存在,寫的那一端拿到 0,讀的那一端說助理沒啟動過。
|
||||
if [ -e "$STATE" ] && [ ! -f "$STATE" ]; then
|
||||
printf '[jsc][助理心跳][ERR]:%s 不是一般檔案,心跳寫不進去。\n' "$STATE" >&2
|
||||
exit 5
|
||||
fi
|
||||
mkdir -p "$STATE_DIR" 2>/dev/null || true
|
||||
_tmp="$STATE.tmp.$$"
|
||||
# stderr 先轉走再開檔:順序反過來的話,開檔失敗的訊息是 sh 自己印的,那時 stderr 還沒
|
||||
# 轉走,會漏到呼叫端的畫面上,蓋掉下面那句講得清楚的錯誤訊息。
|
||||
if ! printf 'ts=%s\npid=%s\ncli=%s\nsession=%s\n' \
|
||||
"$(now_epoch)" "$$" "$(cli_name)" "$(session_id)" 2>/dev/null > "$_tmp"; then
|
||||
rm -f "$_tmp" 2>/dev/null
|
||||
printf '[jsc][助理心跳][ERR]:寫不進 %s,助理這一拍沒有心跳。\n' "$STATE" >&2
|
||||
exit 5
|
||||
fi
|
||||
if ! mv -f "$_tmp" "$STATE" 2>/dev/null; then
|
||||
rm -f "$_tmp" 2>/dev/null
|
||||
printf '[jsc][助理心跳][ERR]:換不上 %s,助理這一拍沒有心跳。\n' "$STATE" >&2
|
||||
exit 5
|
||||
fi
|
||||
exit 0 ;;
|
||||
check)
|
||||
case "$(probe | cut -f1)" in
|
||||
fresh) exit 0 ;;
|
||||
stale) exit 1 ;;
|
||||
absent) exit 3 ;;
|
||||
*) exit 4 ;;
|
||||
esac ;;
|
||||
report)
|
||||
_p=$(probe)
|
||||
printf 'state=%s ts=%s age=%s ttl=%s pid=%s cli=%s session=%s file=%s\n' \
|
||||
"$(printf '%s' "$_p" | cut -f1)" \
|
||||
"$(printf '%s' "$_p" | cut -f2)" \
|
||||
"$(printf '%s' "$_p" | cut -f3)" \
|
||||
"$(ttl)" "$(field pid)" "$(field cli)" "$(field session)" "$STATE"
|
||||
exit 0 ;;
|
||||
clear)
|
||||
rm -f "$STATE" 2>/dev/null
|
||||
# 刪不掉就要講出來:檔案還在,別人讀到的心跳會說助理還在跑。
|
||||
if [ -e "$STATE" ]; then
|
||||
printf '[jsc][助理心跳][ERR]:刪不掉 %s,心跳檔還在。\n' "$STATE" >&2
|
||||
exit 5
|
||||
fi
|
||||
exit 0 ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user