feat(assistant): 助理主體收攏 start、status、stop 三個操作
What: - 新增 assistant 技能,提供啟動、查現況、停止三個操作。 - 刪掉原本獨立的 status 技能,行為清單與 README 的技能目錄一起改。 - 三份 manifest 的版本一起提升,對 jsc-hooks 的下限提到含心跳腳本的那一版。 Why: - 助理的生命週期是一件事,拆在兩支技能裡,靠描述自動觸發的 CLI 等於擲骰子挑一支。準則也明寫技能目標不得重複。 - 刪除的代價這時候最低:這個 domain 才剛落地,status 是它唯一一支技能,除了自己的文件沒有別的東西指向它。晚一步等各處都引用了再收攏,成本差很多。 How: - 心跳的判定一律交給 jsc-hooks 的心跳腳本,三個操作都讀它的回報,不自己解析心跳檔。判定有兩份就會漂移,狀態與訊息就會對不上。 - 那支腳本的每一個結束碼都在技能裡有明確處置,包含「腳本自己沒跑起來」那一種——那時候既不能說助理在跑,也不能說助理停了。 - 時間戳壞掉一律當成不新鮮,絕不退回判成新鮮。 - 異常結束不需要額外的清理機制:心跳是時間戳,過了門檻自動失效。反過來說,stop 以外的任何路徑都不該寫心跳,否則就是留一個假心跳。 - stop 清心跳是它的職責,不算助理界線裡「不刪狀態檔」那一條。技能內文把這個例外寫明白,免得日後照界線把 stop 砍掉。 - stop 的收尾同時講兩件事:心跳清掉之後閘門會擋下技能呼叫,以及閘門目前還沒接線所以這一刻擋不到誰。前者是設計後果,停助理的人一定要知道;後者不講就是說一件還沒成真的事。 - 排程這一輪不做,start 只寫第一次心跳,並講明心跳不會自動更新。 Who: 助理落地的第二塊:心跳有了,接著要有人寫它、讀它、清它。
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-assist",
|
"name": "jsc-assist",
|
||||||
"version": "0.0.2",
|
"version": "0.1.0",
|
||||||
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_{HASH} wiki 頁)",
|
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_{HASH} wiki 頁)",
|
||||||
"skills": "./skills",
|
"skills": "./skills",
|
||||||
"author": {
|
"author": {
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"jsc-cli": ">=0.2.7",
|
"jsc-cli": ">=0.2.7",
|
||||||
"jsc-gitea": ">=0.2.0",
|
"jsc-gitea": ">=0.2.0",
|
||||||
"jsc-hooks": ">=0.3.4",
|
"jsc-hooks": ">=0.3.7",
|
||||||
"jsc-log": ">=0.1.4"
|
"jsc-log": ">=0.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-assist",
|
"name": "jsc-assist",
|
||||||
"version": "0.0.2",
|
"version": "0.1.0",
|
||||||
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_{HASH} wiki 頁)",
|
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_{HASH} wiki 頁)",
|
||||||
"skills": "./skills",
|
"skills": "./skills",
|
||||||
"jsc": {
|
"jsc": {
|
||||||
"requires": {
|
"requires": {
|
||||||
"jsc-cli": ">=0.2.7",
|
"jsc-cli": ">=0.2.7",
|
||||||
"jsc-gitea": ">=0.2.0",
|
"jsc-gitea": ">=0.2.0",
|
||||||
"jsc-hooks": ">=0.3.4",
|
"jsc-hooks": ">=0.3.7",
|
||||||
"jsc-log": ">=0.1.4"
|
"jsc-log": ">=0.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
|
|||||||
|
|
||||||
<!-- JSC-SKILLS:START -->
|
<!-- JSC-SKILLS:START -->
|
||||||
|
|
||||||
### `status`
|
### `assistant`
|
||||||
|
|
||||||
查助理現在的狀況,全程唯讀。讀心跳檔判斷助理是不是還在跑——檔案在、而且 `ts` 距現在不到 300 秒才算新鮮,過期就是沒在跑;接著列出待辦簿裡的每一筆,印成一張現況表。心跳檔不存在時印「助理未運行」,不當成錯誤。這支不寫檔、不寫 wiki、不碰閘門。
|
助理主體,三個操作:`start` 啟動、`status` 查現況、`stop` 停止。心跳的寫入、判定與清除一律交給 `jsc-hooks` 的 `hooks/heartbeat.sh`,判定只有那一份。`start` 寫下第一次心跳,並講明排程還沒接線、心跳不會自動更新。`status` 全程唯讀,讀心跳現況與待辦簿,印成一張表;助理沒在跑就印「助理未運行」,不當成錯誤。`stop` 清掉心跳,並講明心跳清掉之後閘門會擋下技能呼叫、但閘門目前還沒接線。這支不碰 wiki、不參與閘門判定。
|
||||||
|
|
||||||
<!-- JSC-SKILLS:END -->
|
<!-- JSC-SKILLS:END -->
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `jsc-cli` | `>=0.2.7` | CLI 偵測與委派 |
|
| `jsc-cli` | `>=0.2.7` | CLI 偵測與委派 |
|
||||||
| `jsc-gitea` | `>=0.2.0` | 監控頁的所有 wiki 讀寫,一律經 `tools/gitea.sh` |
|
| `jsc-gitea` | `>=0.2.0` | 監控頁的所有 wiki 讀寫,一律經 `tools/gitea.sh` |
|
||||||
| `jsc-hooks` | `>=0.3.4` | 心跳、閘門與事件來源(`$JSC_HOME` 底下的狀態檔) |
|
| `jsc-hooks` | `>=0.3.7` | 心跳、閘門與事件來源(`$JSC_HOME` 底下的狀態檔)。心跳的寫入、判定與清除一律走 `hooks/heartbeat.sh`,那支腳本是 `0.3.7` 才有的 |
|
||||||
| `jsc-log` | `>=0.1.4` | 使用統計與工作日誌的資料來源 |
|
| `jsc-log` | `>=0.1.4` | 使用統計與工作日誌的資料來源 |
|
||||||
|
|
||||||
## 參考與工具
|
## 參考與工具
|
||||||
|
|||||||
+2
-2
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-assist",
|
"name": "jsc-assist",
|
||||||
"version": "0.0.2",
|
"version": "0.1.0",
|
||||||
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_{HASH} wiki 頁)",
|
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_{HASH} wiki 頁)",
|
||||||
"skills": "./skills/",
|
"skills": "./skills/",
|
||||||
"jsc": {
|
"jsc": {
|
||||||
"requires": {
|
"requires": {
|
||||||
"jsc-cli": ">=0.2.7",
|
"jsc-cli": ">=0.2.7",
|
||||||
"jsc-gitea": ">=0.2.0",
|
"jsc-gitea": ">=0.2.0",
|
||||||
"jsc-hooks": ">=0.3.4",
|
"jsc-hooks": ">=0.3.7",
|
||||||
"jsc-log": ">=0.1.4"
|
"jsc-log": ">=0.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
本頁記錄 jsc-assist 每支技能的行為基準,供技能驗證比對。技能異動時,在同一個 PR 內一起更新這一頁。
|
本頁記錄 jsc-assist 每支技能的行為基準,供技能驗證比對。技能異動時,在同一個 PR 內一起更新這一頁。
|
||||||
|
|
||||||
## status
|
## assistant
|
||||||
|
|
||||||
| 項目 | 內容 |
|
| 項目 | 內容 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| 觸發時機 | 有人問助理現在還在不在跑,或問待辦簿裡剩下哪幾筆時用。啟動與停止助理不走這支。執行環境健檢不走這支,走 `jsc-cli:doctor`。技能使用次數不走這支,走 `jsc-log:stats` |
|
| 觸發時機 | 要啟動助理、要停止助理,或要問助理現在還在不在跑、待辦簿剩下哪幾筆時用。三個操作 `start`、`status`、`stop` 都走這一支。執行環境健檢不走這支,走 `jsc-cli:doctor`。技能使用次數不走這支,走 `jsc-log:stats` |
|
||||||
| 關鍵步驟 | 解出 `$JSC_HOME`(未設定就退回 `~/.jsc`)並組出 `assistant/` 目錄、讀 `heartbeat` 的 `ts`、`pid`、`cli`、`session`、以 `ts` 距現在是否不到 300 秒判成新鮮或過期、不看 pid 存活、列出 `tasks/` 底下每一個檔案並解析 `state`、`title`、`next_run`、`fail_count`、把心跳區塊與逐筆待辦印成一張表、`fail_count` 大於 0 的列標上「已連續失敗 N 次」 |
|
| 關鍵步驟 | 先認出使用者要的是哪一個操作。`start`:跑 `heartbeat.sh write` 寫第一次心跳、跑 `heartbeat.sh report` 確認寫進去了、印出心跳路徑與時間並註明排程還沒接線、心跳過了門檻要再跑一次 start。`status`:跑 `heartbeat.sh report` 取心跳現況、把 `state` 對映成新鮮、過期、心跳檔損壞、不存在、不自己解析心跳檔也不自己判定、從 `file=` 解出助理目錄後列出 `tasks/` 底下每一個檔案並解析 `state`、`title`、`next_run`、`fail_count`、印成一張表、`fail_count` 大於 0 的列標上「已連續失敗 N 次」。`stop`:先跑 `heartbeat.sh report` 留下原本的狀態、再跑 `heartbeat.sh clear` 清掉心跳、印出停止訊息並說明心跳清掉之後閘門會擋人、同時說明閘門還沒接線所以現在擋不到人 |
|
||||||
| 外部呼叫 | 無。只讀 `$JSC_HOME/assistant/heartbeat` 與 `$JSC_HOME/assistant/tasks/` 底下的檔案。不呼叫腳本、不呼叫其他技能、不碰 wiki、不啟動也不停止助理 |
|
| 外部呼叫 | `jsc-hooks/hooks/heartbeat.sh` 的 `write`、`report`、`clear` 三個子命令,六個結束碼各有處置:0 往下走、1 與 3 印「助理未運行」、2 回報判不出狀態並停下、4 當成不新鮮並回報心跳檔損壞、5 是嚴重狀況要吵出來且不得回報成功、6 是呼叫寫錯要更正後重跑。另外唯讀 `$JSC_HOME/assistant/tasks/` 底下的檔案。呼叫端沒講清楚要哪一個操作時,走 `jsc-ask:ask` 的決策樹問。不碰 wiki、不參與閘門判定 |
|
||||||
| 完成條件 | 印出現況表,或印出「助理未運行」並說明是哪個路徑讀不到。心跳檔不存在、待辦簿目錄不存在、待辦簿零筆,三種都算正常結束,不得以非 0 結束 |
|
| 完成條件 | `start` 要 `write` 回 0 且 `report` 回 `state=fresh`,才算啟動成功;`write` 回 5 一律回報失敗並停下,不得宣稱啟動。`status` 要印出現況表,或印出「助理未運行」並說明原因;心跳不存在、待辦簿目錄不存在、待辦簿零筆,三種都算正常結束。`stop` 要 `clear` 回 0,並印出帶兩段話的停止訊息;`clear` 回 5 就回報心跳檔還在、助理沒有確實停掉,不印停止訊息 |
|
||||||
| 可驗證跡象 | 無寫入跡象,只有回報內容 |
|
| 可驗證跡象 | `start` 之後 `$JSC_HOME/assistant/heartbeat` 存在,`ts` 是剛才的時間。`stop` 之後同一個路徑不存在。`status` 無寫入跡象,只有回報內容。三個操作都不動 `tasks/` 底下的檔案,也不動 worktree 與 wiki 頁 |
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
name: assistant
|
||||||
|
description: Start, inspect, or stop the background assistant, with jsc-hooks/hooks/heartbeat.sh owning the single freshness verdict. start writes the first heartbeat and says no scheduler keeps it alive yet; status turns heartbeat.sh report plus the task book under $JSC_HOME/assistant/tasks/ into one read-only table; stop clears the heartbeat and states what a cleared heartbeat means for the jsc skill gate. A heartbeat that cannot be written or cleared (exit 5) is reported as a failure, never as success. Use when someone starts the assistant, stops it, or asks whether it is running and what is queued; not for environment health checks (jsc-cli:doctor), not for skill usage counts (jsc-log:stats).
|
||||||
|
---
|
||||||
|
|
||||||
|
# assistant — start, status, stop
|
||||||
|
|
||||||
|
The background assistant runs where nobody is watching it. Its heartbeat is the only evidence that it is alive, so this skill is the single entry point for the three operations that touch that evidence: `start` writes it, `status` reads it, `stop` clears it.
|
||||||
|
|
||||||
|
`jsc-hooks/hooks/heartbeat.sh` owns every heartbeat operation, including the freshness verdict. Never read, parse, write or delete `$JSC_HOME/assistant/heartbeat` directly — one verdict, one source. This skill adds no `tools/` script of its own: the heartbeat logic already lives in that script, and the task book is one directory listing.
|
||||||
|
|
||||||
|
## Pick the operation
|
||||||
|
|
||||||
|
Run exactly one operation per invocation. Take it from the request: starting, launching or waking the assistant is `start`; asking whether it runs, what it is doing, or what is queued is `status`; stopping, halting or shutting it down is `stop`. When the request names none of the three, or names more than one, ask through the `jsc-ask:ask` decision tree with those three as the options, each stating its effect — `start` writes a heartbeat, `status` changes nothing, `stop` deletes the heartbeat. Never guess, and never run a second operation the caller did not ask for. Completion condition: exactly one of `start`, `status`, `stop` is chosen and named in the report.
|
||||||
|
|
||||||
|
## Data sources
|
||||||
|
|
||||||
|
| Path | Read by | Format |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `$JSC_HOME/assistant/heartbeat` | `heartbeat.sh` only, never this skill | `key=value` lines: `ts`, `pid`, `cli`, `session` |
|
||||||
|
| `$JSC_HOME/assistant/tasks/{id}` | this skill, read-only | `key=value` lines, one task per file: `id`, `kind` (`check` / `todo`), `title`, `action`, `trigger`, `recur`, `repo`, `due`, `state` (`pending` / `done` / `paused`), `last_run`, `next_run`, `fail_count`, `origin` (`user` / `assistant`) |
|
||||||
|
|
||||||
|
`$JSC_HOME` defaults to `~/.jsc`. `heartbeat.sh report` prints the resolved heartbeat path in its `file=` field, so take the assistant directory from there rather than rebuilding it.
|
||||||
|
|
||||||
|
**The verdict is time-based only.** A heartbeat counts as fresh when the file exists and its `ts` is less than the TTL behind now (300 seconds by default, `JSC_ASSISTANT_HEARTBEAT_TTL` overrides it). `pid` liveness is never tested: five CLIs and container processes cannot see each other's pids, so a live-looking pid proves nothing and a missing one proves nothing either. Report `pid` as a hint for whoever has to find a blocking process, and give it no weight in the verdict.
|
||||||
|
|
||||||
|
## heartbeat.sh exit codes
|
||||||
|
|
||||||
|
Every call in every operation below is judged by this table. Report the code you got, then take the row's action — never retry a code silently, and never downgrade a failure into a success.
|
||||||
|
|
||||||
|
| Code | Meaning | What to do |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 0 | `write` wrote the heartbeat, `clear` finished and the file is gone, `report` printed its line, `check` says fresh | Carry on with the operation's next step. For `report`, the state still has to be read out of the printed `state=` field |
|
||||||
|
| 1 | `check`: the heartbeat exists but is at or past the TTL — the assistant ran and has stopped | Report `助理未運行`, name the age in seconds, and say the assistant has to be started again. `report` returns this state as `state=stale` with exit 0 |
|
||||||
|
| 2 | The script did not run at all — it failed to load its `lib.sh` | Report that the heartbeat state is unknown, name the script path and the code, and stop the operation. Never claim the assistant is running, and never claim it is stopped |
|
||||||
|
| 3 | `check`: no heartbeat file — the assistant has never been started | Report `助理未運行` and say to run `start`. `report` returns this state as `state=absent` with exit 0. In `stop` this state cannot appear, because `clear` treats a missing file as success |
|
||||||
|
| 4 | `check`: the heartbeat exists but its `ts` is missing, empty or not a number — the file is damaged, the assistant is not merely stopped | Treat it as not fresh; falling back to fresh is forbidden. Report the file as damaged, say the state cannot be read from it, and tell the operator to run `stop` and then `start` to rebuild it. `report` returns this state as `state=invalid` with exit 0 |
|
||||||
|
| 5 | Filesystem failure — `write` could not write the file, or `clear` could not delete it and the file is still there | Serious. Report it loudly with the stderr text and the path, and follow the operation's own step for this code. Never report the operation as done |
|
||||||
|
| 6 | Usage error — an unknown subcommand, or none at all | This is a defect in the call, not a state of the assistant. Report the exact command line that was run, correct it to one of `write`, `check`, `report`, `clear`, and run it once more. Report a second exit 6 as a defect in this skill and stop |
|
||||||
|
|
||||||
|
## Boundaries
|
||||||
|
|
||||||
|
The six limits in `AGENTS.md`「助理的界線」 hold for all three operations. Two of them need saying out loud here:
|
||||||
|
|
||||||
|
- **This skill never judges a gate.** It maintains the heartbeat and prints what the heartbeat says. Whether a stale heartbeat blocks a skill call is decided by a hook, synchronously and offline; nothing in this skill blocks or waves through anything.
|
||||||
|
- **`stop` clearing the heartbeat is not a breach of 界線 5「不刪除狀態檔」.** That limit protects state that records work — the task book, worktrees, wiki pages — from a background process nobody is watching. The heartbeat records one fact only, "the assistant is alive", so a `stop` that leaves it behind leaves a lie behind. Clearing it is the whole job of `stop`, and it is the only deletion any operation here performs: `stop` touches nothing under `tasks/`, no worktree and no wiki page. Do not "restore" this limit later by taking the `clear` call out of `stop`.
|
||||||
|
|
||||||
|
## Crash exit needs no cleanup
|
||||||
|
|
||||||
|
An assistant that is killed, crashes, or dies with the machine writes no farewell. It does not need to. The heartbeat is a timestamp, not a lock: the last one written stays on disk, ages past the TTL on its own, and every reader from then on sees 過期. No shutdown handler, no cleanup hook and no pid check is involved, so there is nothing left that can fail to run.
|
||||||
|
|
||||||
|
That property holds only while nothing fakes a heartbeat. **`write` is called by `start` and by the scheduler that keeps a running assistant alive — nowhere else.** `status` never writes one, `stop` never writes one, and no other skill writes one. A heartbeat written by anything that is not a live assistant says a dead assistant is alive, and the reader has no way to tell the difference.
|
||||||
|
|
||||||
|
## start
|
||||||
|
|
||||||
|
Scheduling is not wired in this round. `start` writes the first heartbeat and reports; it installs no timer, no cron entry and no daemon. Until the scheduler lands, the heartbeat is never refreshed on its own and expires once the TTL passes.
|
||||||
|
|
||||||
|
1. **Write the first heartbeat.** Run `jsc-hooks/hooks/heartbeat.sh write`. On exit 5 the assistant cannot start: without a heartbeat its own gate reads it as not running, so report the failure, quote the script's stderr line and the heartbeat path, name the likely causes (a full disk, a permission problem on `$JSC_HOME/assistant/`, or something other than a regular file sitting at the heartbeat path), and stop — do not run step 2, and do not report a started assistant. On exit 2 or 6, follow that code's row in the exit-code table and stop. Completion condition: `write` exited 0, or the failure report naming the code and the path has been printed and no start was claimed.
|
||||||
|
|
||||||
|
2. **Confirm what was written.** Run `jsc-hooks/hooks/heartbeat.sh report` and read its `state=`, `ts=`, `ttl=`, `pid=`, `cli=`, `session=` and `file=` fields. `state=fresh` is the expected result. Any other state right after a successful `write` means something rewrote or removed the file in between: report the state, the path and that the heartbeat did not survive its own write, and do not claim a started assistant. Completion condition: the report line was read and either `state=fresh` was recorded with its seven fields, or the mismatch was reported.
|
||||||
|
|
||||||
|
3. **Report the start.** Print the heartbeat path, the local time of `ts`, the TTL in seconds, and `pid`, `cli`, `session` as hints. Then print this literally, with `{ttl}` replaced by the TTL just read:
|
||||||
|
|
||||||
|
> 助理已啟動,第一次心跳寫好了。排程還沒接線,心跳不會自動更新;過了 {ttl} 秒心跳就會過期,屆時請再跑一次 start。
|
||||||
|
|
||||||
|
Completion condition: the report carries the path, the local heartbeat time, the TTL and the three hint fields, and the notice above appears with the real TTL substituted.
|
||||||
|
|
||||||
|
## status
|
||||||
|
|
||||||
|
Read-only throughout. This operation creates, modifies and deletes nothing under `$JSC_HOME`, and it never calls `write` or `clear`.
|
||||||
|
|
||||||
|
1. **Read the heartbeat through the script.** Run `jsc-hooks/hooks/heartbeat.sh report` and split the line on spaces, taking `file=` last so a path containing spaces stays intact. Map `state=` to the verdict: `fresh` → `新鮮`, `stale` → `過期`, `invalid` → `心跳檔損壞`, `absent` → `不存在`. Print `助理未運行` for `stale`, `invalid` and `absent`. Never re-derive the verdict from `ts` yourself, and never treat `invalid` as fresh. On exit 2 or 6, follow that code's row, record the heartbeat state as unknown, and carry on to step 2 — the task book is still worth printing. Completion condition: the heartbeat state holds one of `新鮮`, `過期`, `心跳檔損壞`, `不存在` or unknown, and `ts`, `age`, `ttl`, `pid`, `cli`, `session` and `file` are recorded as read or as empty.
|
||||||
|
|
||||||
|
2. **Read the task book.** Take the assistant directory from the `file=` path of step 1, list the regular files directly under its `tasks/` subdirectory, and parse each one as `key=value` lines. Branch on the outcome.
|
||||||
|
|
||||||
|
| Outcome | Do |
|
||||||
|
| --- | --- |
|
||||||
|
| Directory absent | Report zero entries. This is a normal result, not an error |
|
||||||
|
| Directory present, no files | Report zero entries |
|
||||||
|
| A file cannot be read, or holds no recognisable key | Keep it as one row, put the file name in the title column, name the read or parse error in that row, and carry on with the remaining files |
|
||||||
|
| A key is missing from a readable file | Print `-` in that column |
|
||||||
|
|
||||||
|
Completion condition: every file under `tasks/` produced exactly one row, or zero entries was reported.
|
||||||
|
|
||||||
|
3. **Print the status table.** Lead with the heartbeat block — verdict, last heartbeat time rendered from `ts` in local time, age in seconds, TTL, `cli`, `session`, `pid`, and the task count. Follow it with one row per task carrying `state`, `title`, `next_run` and `fail_count`, in the order the files were listed. Completion condition: the heartbeat block holds all eight values and the row count equals the task count from step 2.
|
||||||
|
|
||||||
|
4. **Flag the repeatedly failing tasks.** Append 已連續失敗 N 次 to every row whose `fail_count` is above 0, with `N` taken verbatim from the file. A broken entry that retries every round with nobody noticing is the reason this field exists, so let no such row leave the table unmarked. Completion condition: every row with `fail_count` above 0 carries the marker and its number matches the file.
|
||||||
|
|
||||||
|
5. **Finish successfully.** `助理未運行`, an absent `tasks/` directory and an empty `tasks/` directory are normal results — never exit non-zero for any of them. Reserve a failure report for a condition none of the tables above covers, and state which path and which error produced it. Completion condition: the report is printed and nothing under `$JSC_HOME` has been created, modified or deleted.
|
||||||
|
|
||||||
|
## stop
|
||||||
|
|
||||||
|
1. **Record what is being stopped.** Run `jsc-hooks/hooks/heartbeat.sh report` first and keep its `state=`, `ts=`, `pid=`, `cli=` and `file=` fields for the closing report — after the clear they are gone for good. `state=absent` means the assistant was already stopped; say so and still run step 2, because `clear` on a missing file is a success and leaves the outcome unambiguous. On exit 2 or 6, follow that code's row, record the previous state as unknown, and carry on to step 2. Completion condition: the previous state and its fields are recorded, or the previous state is recorded as unknown with its code.
|
||||||
|
|
||||||
|
2. **Clear the heartbeat.** Run `jsc-hooks/hooks/heartbeat.sh clear`. On exit 5 the file is still there: report the failure with the script's stderr line and the path, say plainly that every reader still sees a heartbeat claiming the assistant is running and that the assistant is therefore not reliably stopped, name the manual fix (delete that path by hand, then run `status` to confirm `助理未運行`), and skip step 3 — the closing notice must not be printed after a failed clear. On exit 2 or 6, follow that code's row and stop the same way. Completion condition: `clear` exited 0, or the failure report naming the code, the path and the manual fix has been printed and no stop was claimed.
|
||||||
|
|
||||||
|
3. **Report the stop and what it means for the gate.** Print the previous state and heartbeat time from step 1, then this literally:
|
||||||
|
|
||||||
|
> 助理已停止,心跳清掉了。靠心跳判定的 jsc 技能閘門一讀到沒有心跳就會擋下技能呼叫;閘門目前還沒接線,所以這一刻誰都擋不到。要再工作就先跑一次 start。
|
||||||
|
|
||||||
|
Say it exactly this way. The blocking is the designed consequence of a cleared heartbeat, and whoever stops the assistant has to know it is coming; the second clause is the part that keeps the notice honest while the gate is still unwired. When the gate is wired, that clause is what gets rewritten — not the first one. Completion condition: the notice appears with both clauses, and the previous state and heartbeat time are printed above it.
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
---
|
|
||||||
name: status
|
|
||||||
description: Report the background assistant's current state read-only, from the heartbeat file and the task book under $JSC_HOME/assistant/. Heartbeat counts as fresh only when the file exists and its ts is less than 300 seconds old, and pid liveness is never checked. Print one table covering heartbeat freshness, last heartbeat time, cli, session, task count, and every task's state, title, next_run and fail_count, flagging each task whose fail_count is above zero. A missing heartbeat file prints 助理未運行 and still counts as a normal result rather than an error. Use when someone asks whether the assistant is running or what is queued; not for starting or stopping it, not for environment health checks (jsc-cli:doctor), and not for skill usage counts (jsc-log:stats).
|
|
||||||
---
|
|
||||||
|
|
||||||
# status — assistant heartbeat and task book snapshot
|
|
||||||
|
|
||||||
Read-only snapshot of the background assistant. This skill reads two paths and prints one table. It writes no file, writes no wiki page, calls no gate, and never starts or stops the assistant.
|
|
||||||
|
|
||||||
No `tools/` script backs this skill. Two paths and one table stay below the extraction bar; re-evaluate when the assistant body itself lands.
|
|
||||||
|
|
||||||
## Data sources
|
|
||||||
|
|
||||||
| Path | Format | Keys |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `$JSC_HOME/assistant/heartbeat` | plain text, one `key=value` per line | `ts` (epoch seconds), `pid`, `cli`, `session` |
|
|
||||||
| `$JSC_HOME/assistant/tasks/{id}` | plain text, one `key=value` per line, one entry per file | `id`, `kind` (`check` or `todo`), `title`, `action`, `trigger`, `recur`, `repo`, `due`, `state` (`pending` / `done` / `paused`), `last_run`, `next_run`, `fail_count`, `origin` (`user` or `assistant`) |
|
|
||||||
|
|
||||||
`$JSC_HOME` defaults to `~/.jsc`.
|
|
||||||
|
|
||||||
**Freshness is time-based only.** The heartbeat is fresh when the file exists and `ts` is less than 300 seconds behind the current time. An older `ts` means the assistant is not running. Never test whether `pid` is alive: the five CLIs and the processes inside containers cannot see each other's pids, so a live-looking pid proves nothing and a missing one proves nothing either. Report `pid` as a hint for whoever has to find a blocking process, and give it no weight in the verdict.
|
|
||||||
|
|
||||||
## Steps
|
|
||||||
|
|
||||||
1. **Resolve the assistant directory.** Take `$JSC_HOME` from the environment; when it is unset or empty, use `~/.jsc`. Append `assistant/` to get the directory this skill reads. When that directory is absent or cannot be listed, print `助理未運行`, name the resolved path and the reason (the variable was unset and the default path does not exist, or the listing was denied), skip steps 2 to 6, and finish per step 7. Done when one absolute assistant directory path is recorded, or the not-running report naming that path is printed.
|
|
||||||
|
|
||||||
2. **Read the heartbeat.** Read `{assistant}/heartbeat` and split each line on its first `=`. Branch on the outcome.
|
|
||||||
|
|
||||||
| Outcome | Do |
|
|
||||||
| --- | --- |
|
|
||||||
| File absent | Set heartbeat state to `不存在`, print `助理未運行`, continue at step 4 — the task book is still worth printing |
|
|
||||||
| File unreadable (permission denied, I/O error) | Set heartbeat state to `不存在`, print `助理未運行`, name the error text as the reason, continue at step 4 |
|
|
||||||
| File present, `ts` absent or not an integer | Set heartbeat state to `過期`, name the malformed value, continue at step 4 |
|
|
||||||
| File present with an integer `ts` | Continue at step 3 |
|
|
||||||
|
|
||||||
Done when the heartbeat state holds one of `不存在`, `過期`, or a pending verdict handed to step 3, and the values of `pid`, `cli` and `session` are recorded as read or as absent.
|
|
||||||
|
|
||||||
3. **Judge freshness.** Subtract `ts` from the current epoch seconds. A difference below 300 sets the state to `新鮮`; 300 or above sets it to `過期`. Done when the state is `新鮮` or `過期` and the age in seconds is recorded.
|
|
||||||
|
|
||||||
4. **Read the task book.** List the regular files directly under `{assistant}/tasks/` and parse each one as `key=value` lines. Branch on the outcome.
|
|
||||||
|
|
||||||
| Outcome | Do |
|
|
||||||
| --- | --- |
|
|
||||||
| Directory absent | Report zero entries. This is a normal result, not an error |
|
|
||||||
| Directory present, no files | Report zero entries |
|
|
||||||
| A file cannot be read or holds no recognisable key | Keep it as one row, put the file name in the title column, name the read or parse error in that row, and carry on with the remaining files |
|
|
||||||
| A key is missing from a readable file | Print `-` in that column |
|
|
||||||
|
|
||||||
Done when every file under `tasks/` has produced exactly one row, or zero entries has been reported.
|
|
||||||
|
|
||||||
5. **Print the status table.** Lead with the heartbeat block — state (`新鮮` / `過期` / `不存在`), last heartbeat time rendered from `ts` in local time, `cli`, `session`, and the task count. Follow it with one row per task carrying `state`, `title`, `next_run` and `fail_count`, in the order the files were listed. Done when the heartbeat block holds all five values and the row count equals the task count reported in step 4.
|
|
||||||
|
|
||||||
6. **Flag the repeatedly failing tasks.** Append `已連續失敗 N 次` to every row whose `fail_count` is above 0, with `N` taken verbatim from the file. A broken entry that retries every round with nobody noticing is the reason this field exists, so let no such row leave the table unmarked. Done when every row with `fail_count` above 0 carries the marker and its number matches the file.
|
|
||||||
|
|
||||||
7. **Finish successfully.** `助理未運行`, an absent tasks directory and an empty tasks directory are normal results — never exit non-zero for any of them. Reserve a failure report for a condition none of the tables above covers, and state which path and which error produced it. Done when the report is printed and nothing under `$JSC_HOME` has been created, modified or deleted.
|
|
||||||
Reference in New Issue
Block a user