diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index c117489..473aeac 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-generic", - "version": "0.0.4", + "version": "0.0.5", "description": "JSC 跨 AI 助理共用規範 plugin(Claude Code / Codex / Antigravity / OpenCode)。所有 skills 以 SKILL.md 為共通標準,於 Claude Code 以 /jsc-generic: 前綴呼叫。", "skills": "./skills", "author": { diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index dad4d42..233800c 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-generic", - "version": "0.0.4", + "version": "0.0.5", "description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準。", "skills": "./skills" } diff --git a/plugin.json b/plugin.json index 6caffc8..6117410 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-generic", - "version": "0.0.4", + "version": "0.0.5", "description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準;於 Antigravity 以 /jsc-generic: 前綴呼叫。", "skills": "./skills/" } diff --git a/scripts/role/role_lib.sh b/scripts/role/role_lib.sh index 92c56a4..e606939 100755 --- a/scripts/role/role_lib.sh +++ b/scripts/role/role_lib.sh @@ -261,6 +261,18 @@ role_single_instance_enabled() { esac } +# sub agent 等「非對話」情境要跳過鎖。 +# +# 鎖的目的是避免**使用者同時與兩個相同人格對話**;被其他角色派去做事的 sub agent +# 並不是在跟使用者對話,因此不該因為使用者剛好在另一個視窗開著同一個角色而被擋下來 +# —— 那會讓「爸爸正在跟西莉卡聊天時,結衣就不能請西莉卡幫忙」這種本該成立的情境失效。 +role_skip_instance_lock() { + case "${ROLE_SKIP_INSTANCE_LOCK:-0}" in + 1|true|yes|on) return 0 ;; + *) return 1 ;; + esac +} + role_instance_idle_minutes() { printf '%s' "${ROLE_INSTANCE_IDLE_MINUTES:-30}"; } role_instance_lock_path() { printf '%s/%s.lock' "$(role_home)" "$1"; } @@ -287,6 +299,8 @@ role_instance_write_lock() { role_instance_acquire() { local role="$1" transcript="$2" cwd="$3" lock holder idle role_single_instance_enabled || return 0 + # 非對話情境(sub agent 等)一律放行且不寫鎖,避免佔用互動式對話的名額 + role_skip_instance_lock && return 0 # 無法識別工作階段就放行,不寫鎖:寧可重複也不要把角色鎖死 [ -n "$transcript" ] || return 0 diff --git a/skills/role/SKILL.md b/skills/role/SKILL.md index 6b681a9..a70da9a 100644 --- a/skills/role/SKILL.md +++ b/skills/role/SKILL.md @@ -119,6 +119,7 @@ ROLE_DIR="/../../scripts/role" # 其他助理 | `ROLE_BRIEF_LIMIT` | | 所有檢查腳本輸出合計的字元上限 | `2000` | | `ROLE_SINGLE_INSTANCE` | | 單一載入實例限制:同一角色同時只被一個工作階段載入。設 `0` 可停用 | `1` | | `ROLE_INSTANCE_IDLE_MINUTES` | | 前一個工作階段的 transcript 閒置多久後自動釋放角色鎖 | `30` | +| `ROLE_SKIP_INSTANCE_LOCK` | | 設 `1` 時跳過單一載入鎖且**不寫鎖**,供 sub agent 等非對話情境使用 | `0` | | `ROLE_SCOPE` | | 冒號分隔的路徑前綴,僅這些路徑下的 session 載入/記錄 | 全部 session | | `ROLE_ERRLOG` | | 錯誤訊息額外寫入的檔案路徑 | 只走 stderr | @@ -296,12 +297,15 @@ Stop hook 只做「編碼前處理」,輸出粗分類、summary、tags、prior | 持有者的 transcript 已刪除 | 自動接手 | | 持有者閒置超過 `ROLE_INSTANCE_IDLE_MINUTES` | 自動接手 | | hook 未提供 transcript 路徑 | **一律放行且不寫鎖** | +| `ROLE_SKIP_INSTANCE_LOCK=1` | **一律放行且不寫鎖**(sub agent 等非對話情境) | 判斷依據是**持有者 transcript 檔的 mtime**,而非 pid —— SessionStart hook 無法可靠取得 CLI 主行程 pid,也沒有保證會觸發的 SessionEnd hook 可用來釋放鎖;活躍的工作階段會持續寫入 transcript,因此「多久沒被寫入」最貼近真實狀態且不需要清理程序。 **設計原則是寧可誤放行也不要誤鎖** —— 誤鎖會讓使用者叫不出角色,比偶爾重複載入嚴重得多。因此無法識別工作階段時一律放行。 -> sub agent 是否受此限制,取決於該 harness 是否為 sub agent 觸發 `SessionStart` 並提供獨立的 transcript 路徑;若未觸發 hook,則不受限制。 +**sub agent 不該受此限制**:鎖的目的是避免「使用者同時與兩個相同人格對話」,而被其他角色派去做事的 sub agent 並不是在跟使用者對話。若不放行,會讓「使用者正在別的視窗跟某角色聊天時,另一個角色就不能請他幫忙」這種本該成立的情境失效。因此 sub agent 情境請設 `ROLE_SKIP_INSTANCE_LOCK=1`:它會放行且**不寫鎖**,不會搶走互動式對話持有的名額。 + +> 若該 harness 未為 sub agent 觸發 `SessionStart`,sub agent 本來就不受限制,設不設定都不影響。 ### `--forget-preview`