@cocorograph/hub-agent 0.7.61 → 0.7.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/hub-agent.mjs +29 -17
- package/package.json +1 -1
- package/scripts/install.sh +79 -43
- package/src/enroll.mjs +10 -23
- package/src/hub-bundle.mjs +80 -5
- package/src/main.mjs +34 -1
- package/src/usage.mjs +14 -7
package/bin/hub-agent.mjs
CHANGED
|
@@ -27,7 +27,7 @@ if (!process.env.LC_ALL) process.env.LC_ALL = "C.UTF-8"
|
|
|
27
27
|
|
|
28
28
|
import { hasConfig, readConfig } from "../src/config.mjs"
|
|
29
29
|
import { enroll } from "../src/enroll.mjs"
|
|
30
|
-
import { syncBundle } from "../src/hub-bundle.mjs"
|
|
30
|
+
import { resolveSyncTargets, syncBundle } from "../src/hub-bundle.mjs"
|
|
31
31
|
import {
|
|
32
32
|
installPlugin,
|
|
33
33
|
listInstallableExamples,
|
|
@@ -110,28 +110,40 @@ program
|
|
|
110
110
|
}
|
|
111
111
|
targets = [opts.cliKind]
|
|
112
112
|
} else {
|
|
113
|
+
// 対象決定は enroll / self-heal と共通の resolveSyncTargets に寄せる。
|
|
113
114
|
const { detectAvailableClis } = await import("../src/config.mjs")
|
|
114
|
-
|
|
115
|
-
targets = detected.length > 0 ? detected : ["claude"]
|
|
115
|
+
targets = resolveSyncTargets(await detectAvailableClis())
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// 各 CLI を **setup 完了まで同期的に**処理し、失敗は CLI 名付きで報告して continue
|
|
119
|
+
// (一方の失敗で他方を止めない)。1 つでも失敗したら非 0 で終える。
|
|
120
|
+
const failures = []
|
|
118
121
|
for (const cliKind of targets) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
122
|
+
try {
|
|
123
|
+
const r = await syncBundle({
|
|
124
|
+
hubUrl: cfg.hub_url,
|
|
125
|
+
agentId: cfg.agent_id,
|
|
126
|
+
agentToken: cfg.agent_token,
|
|
127
|
+
cliKind,
|
|
128
|
+
})
|
|
129
|
+
const mcpSummary = r.mcp
|
|
130
|
+
? ` mcp_added=${r.mcp.added.length}${r.mcp.skipped.length > 0 ? ` mcp_skipped=${r.mcp.skipped.length}` : ""}`
|
|
131
|
+
: ""
|
|
132
|
+
console.log(
|
|
133
|
+
`hub bundle synced (${cliKind}): version=${r.version} written=${r.written.length} skipped_same=${r.skipped_same.length}${mcpSummary}`,
|
|
134
|
+
)
|
|
135
|
+
if (r.mcp && r.mcp.added.length > 0) {
|
|
136
|
+
console.log(` mcp servers added: ${r.mcp.added.join(", ")}`)
|
|
137
|
+
}
|
|
138
|
+
} catch (err) {
|
|
139
|
+
console.error(`sync-bundle failed (${cliKind}): ${err.message}`)
|
|
140
|
+
failures.push(cliKind)
|
|
133
141
|
}
|
|
134
142
|
}
|
|
143
|
+
if (failures.length > 0) {
|
|
144
|
+
console.error(`sync-bundle 失敗: ${failures.join(", ")}`)
|
|
145
|
+
process.exit(1)
|
|
146
|
+
}
|
|
135
147
|
} catch (err) {
|
|
136
148
|
console.error(`sync-bundle failed: ${err.message}`)
|
|
137
149
|
process.exit(1)
|
package/package.json
CHANGED
package/scripts/install.sh
CHANGED
|
@@ -646,30 +646,57 @@ do_enroll() {
|
|
|
646
646
|
# マーカーは通常 syncBundle の最終ステップで書かれるが、scripts/manifest.json
|
|
647
647
|
# が無いと SessionStart hook / hub-helper.py が動かないため両方必須。どちらかが
|
|
648
648
|
# 欠けていれば半完了状態とみなして sync-bundle を retry。
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
color_warn "hub-agent sync-bundle が継続失敗。あとで手動実行してください:"
|
|
653
|
-
color_warn " hub-agent sync-bundle"
|
|
654
|
-
fi
|
|
655
|
-
fi
|
|
649
|
+
# 検出された Claude/Codex 双方を個別検証し、不完全な CLI だけ再同期する
|
|
650
|
+
# (2026-07-15。旧実装は ~/.claude しか見ず Codex の欠損を検出・修復できなかった)。
|
|
651
|
+
_hub_reconcile_bundles
|
|
656
652
|
}
|
|
657
653
|
|
|
658
|
-
#
|
|
659
|
-
#
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if ! grep -q "BEGIN HUB-AI-RULES" "$HOME/.claude/CLAUDE.md" 2>/dev/null; then
|
|
665
|
-
return 0
|
|
666
|
-
fi
|
|
667
|
-
if [[ ! -f "$HOME/.claude/scripts/manifest.json" ]]; then
|
|
668
|
-
return 0
|
|
654
|
+
# 検証対象の CLI を列挙する。claude は常に対象、codex は PATH にある時だけ対象。
|
|
655
|
+
# 各行 "cli_kind:dir:config_md:hooks_file"(Codex 未インストール環境では codex を要求しない)。
|
|
656
|
+
_hub_target_clis() {
|
|
657
|
+
echo "claude:.claude:CLAUDE.md:settings.json"
|
|
658
|
+
if present codex; then
|
|
659
|
+
echo "codex:.codex:AGENTS.md:hooks.json"
|
|
669
660
|
fi
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
# 単一 CLI の bundle 完全性を判定する。完全なら 0、欠けていれば 1。
|
|
664
|
+
# $1=dir(.claude/.codex) $2=config_md(CLAUDE.md/AGENTS.md) $3=hooks_file(settings.json/hooks.json)
|
|
665
|
+
# 検証項目: scripts/manifest.json / CONFIG_MD の HUB-AI-RULES マーカー /
|
|
666
|
+
# hooks 設定に Hub SessionStart hook(session_start.py) / 正規 .hub_token.json
|
|
667
|
+
_hub_bundle_incomplete_cli() {
|
|
668
|
+
local dir="$1" config_md="$2" hooks_file="$3"
|
|
669
|
+
local base="$HOME/$dir"
|
|
670
|
+
[[ -f "$base/scripts/manifest.json" ]] || return 0
|
|
671
|
+
[[ -f "$base/$config_md" ]] || return 0
|
|
672
|
+
grep -q "BEGIN HUB-AI-RULES" "$base/$config_md" 2>/dev/null || return 0
|
|
673
|
+
grep -q "session_start.py" "$base/$hooks_file" 2>/dev/null || return 0
|
|
674
|
+
[[ -f "$base/.hub_token.json" ]] || return 0
|
|
670
675
|
return 1
|
|
671
676
|
}
|
|
672
677
|
|
|
678
|
+
# 後方互換: 従来 claude 前提で呼んでいた箇所向けの薄いラッパ。
|
|
679
|
+
_hub_bundle_incomplete() {
|
|
680
|
+
_hub_bundle_incomplete_cli ".claude" "CLAUDE.md" "settings.json"
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
# 検出された各 CLI の bundle 完全性を検証し、**不完全な CLI だけ** を
|
|
684
|
+
# `hub-agent sync-bundle --cli-kind <kind>` で再同期する(Codex 未インストールなら対象外)。
|
|
685
|
+
_hub_reconcile_bundles() {
|
|
686
|
+
local line cli_kind dir config_md hooks_file
|
|
687
|
+
while IFS= read -r line; do
|
|
688
|
+
[[ -z "$line" ]] && continue
|
|
689
|
+
IFS=":" read -r cli_kind dir config_md hooks_file <<< "$line"
|
|
690
|
+
if _hub_bundle_incomplete_cli "$dir" "$config_md" "$hooks_file"; then
|
|
691
|
+
color_warn "Hub AI bundle が ~/$dir に完全展開されていない可能性 ($cli_kind)。sync-bundle を再試行"
|
|
692
|
+
if ! retry 2 hub-agent sync-bundle --cli-kind "$cli_kind"; then
|
|
693
|
+
color_warn "hub-agent sync-bundle --cli-kind $cli_kind が継続失敗。あとで手動実行してください:"
|
|
694
|
+
color_warn " hub-agent sync-bundle --cli-kind $cli_kind"
|
|
695
|
+
fi
|
|
696
|
+
fi
|
|
697
|
+
done < <(_hub_target_clis)
|
|
698
|
+
}
|
|
699
|
+
|
|
673
700
|
do_install_service() {
|
|
674
701
|
color_step "OS サービスとして自動起動を登録"
|
|
675
702
|
# install-service は launchctl bootout/bootstrap の transient で初回失敗する
|
|
@@ -687,17 +714,26 @@ do_install_service() {
|
|
|
687
714
|
# verify を成功させる。setup_hub_ai 不在 (bundle 同期失敗) のときは色付き
|
|
688
715
|
# warn を出して継続。再実行時は step_* が冪等処理を返すだけで害なし。
|
|
689
716
|
do_bootstrap_hub_ai() {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
717
|
+
# 検出された各 CLI(claude 常に / codex は present 時)の setup_hub_ai.py を
|
|
718
|
+
# 同期実行する。enroll/sync 内でも setup は走るが、install.sh では verify を確実に
|
|
719
|
+
# 成功させるため各 CLI で同期実行する(一方の失敗で他方を止めない)。2026-07-15 に
|
|
720
|
+
# Claude 固定から双方へ拡張。
|
|
721
|
+
local line cli_kind dir config_md hooks_file setup_script
|
|
722
|
+
while IFS= read -r line; do
|
|
723
|
+
[[ -z "$line" ]] && continue
|
|
724
|
+
IFS=":" read -r cli_kind dir config_md hooks_file <<< "$line"
|
|
725
|
+
setup_script="$HOME/$dir/scripts/setup_hub_ai.py"
|
|
726
|
+
if [[ ! -f "$setup_script" ]]; then
|
|
727
|
+
color_warn "setup_hub_ai.py 不在 ($cli_kind, bundle 同期が失敗した可能性)。スキップ"
|
|
728
|
+
continue
|
|
729
|
+
fi
|
|
730
|
+
color_step "setup_hub_ai.py --silent を実行 ($cli_kind: ~/$dir/$config_md / $hooks_file 初期化)"
|
|
731
|
+
if python3 "$setup_script" --silent; then
|
|
732
|
+
color_ok "Hub AI bootstrap 完了 ($cli_kind)"
|
|
733
|
+
else
|
|
734
|
+
color_warn "setup_hub_ai.py --silent が exit≠0 ($cli_kind, 継続)。手動再実行: python3 $setup_script"
|
|
735
|
+
fi
|
|
736
|
+
done < <(_hub_target_clis)
|
|
701
737
|
}
|
|
702
738
|
|
|
703
739
|
# セットアップ最終検証。
|
|
@@ -721,22 +757,22 @@ verify_setup() {
|
|
|
721
757
|
color_warn "Claude Code (claude) コマンドが見つかりません (cockpit から claude を呼ぶ場合は必要)"
|
|
722
758
|
fi
|
|
723
759
|
|
|
724
|
-
# 3. Hub AI bundle
|
|
725
|
-
#
|
|
726
|
-
#
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
760
|
+
# 3. Hub AI bundle 展開を **CLI 別** に検証(manifest / CONFIG_MD マーカー /
|
|
761
|
+
# hooks の Hub SessionStart hook / 正規 .hub_token.json)。Codex 未インストールなら
|
|
762
|
+
# codex は検証しない(2026-07-15)。
|
|
763
|
+
local line cli_kind dir config_md hooks_file mver
|
|
764
|
+
while IFS= read -r line; do
|
|
765
|
+
[[ -z "$line" ]] && continue
|
|
766
|
+
IFS=":" read -r cli_kind dir config_md hooks_file <<< "$line"
|
|
767
|
+
if ! _hub_bundle_incomplete_cli "$dir" "$config_md" "$hooks_file"; then
|
|
768
|
+
mver="$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$HOME/$dir/scripts/manifest.json" 2>/dev/null | head -1 | sed 's/.*"\([^"]*\)"$/\1/')"
|
|
769
|
+
color_ok "Hub AI bundle 展開済み ($cli_kind: ~/$dir/$config_md + scripts/manifest.json ${mver:+v$mver})"
|
|
734
770
|
else
|
|
735
|
-
color_warn "Hub AI bundle が半完了状態 (
|
|
771
|
+
color_warn "Hub AI bundle が半完了状態 ($cli_kind: ~/$dir の manifest / $config_md マーカー / $hooks_file の SessionStart hook / .hub_token.json のいずれか欠落)"
|
|
772
|
+
color_warn " 手動再同期: hub-agent sync-bundle --cli-kind $cli_kind"
|
|
773
|
+
errors=$((errors + 1))
|
|
736
774
|
fi
|
|
737
|
-
|
|
738
|
-
errors=$((errors + 1))
|
|
739
|
-
fi
|
|
775
|
+
done < <(_hub_target_clis)
|
|
740
776
|
|
|
741
777
|
# 4. OS サービス起動 (Darwin 限定の確認。Linux は systemctl --user で別途)
|
|
742
778
|
if [[ "$(uname)" == "Darwin" ]]; then
|
package/src/enroll.mjs
CHANGED
|
@@ -82,15 +82,18 @@ export async function enroll(enrollmentToken, opts = {}) {
|
|
|
82
82
|
// 無ければ codex のみ同期する。
|
|
83
83
|
const bundleResults = {}
|
|
84
84
|
if (!opts.skipBundle) {
|
|
85
|
-
const { syncBundle } = await import("./hub-bundle.mjs")
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
const detected = availableClis.filter((c) => c === "claude" || c === "codex")
|
|
91
|
-
const targets = detected.length > 0 ? detected : ["claude"]
|
|
85
|
+
const { resolveSyncTargets, syncBundle } = await import("./hub-bundle.mjs")
|
|
86
|
+
// 検出された claude/codex を対象にする(Hub backend は cli_kind='claude'/'codex'
|
|
87
|
+
// のみ対応)。未検出は fail-soft で claude フォールバック。対象決定は
|
|
88
|
+
// resolveSyncTargets() に共通化し、enroll / sync-bundle CLI / self-heal で分岐させない。
|
|
89
|
+
const targets = resolveSyncTargets(availableClis)
|
|
92
90
|
for (const cliKind of targets) {
|
|
93
91
|
try {
|
|
92
|
+
// syncBundle が applyBundle → post_install(setup_hub_ai.py --silent) を **同期実行**
|
|
93
|
+
// するため、ここで別途 setup を kick する必要は無い(2026-07-15 に一本化。旧実装は
|
|
94
|
+
// enroll だけ別経路で detached kickSetupHubAi を叩き、sync-bundle / self-heal と
|
|
95
|
+
// 挙動が分岐していた)。CONFIG_MD / hooks / version / 旧 JWT の正規 token 移行まで
|
|
96
|
+
// syncBundle 完了時に揃う。
|
|
94
97
|
bundleResults[cliKind] = await syncBundle({
|
|
95
98
|
hubUrl,
|
|
96
99
|
agentId: data.agent_id,
|
|
@@ -103,22 +106,6 @@ export async function enroll(enrollmentToken, opts = {}) {
|
|
|
103
106
|
opts.logger?.warn?.({ cliKind, err: err.message }, "hub bundle sync failed (enroll still ok)")
|
|
104
107
|
bundleResults[cliKind] = { error: err.message }
|
|
105
108
|
}
|
|
106
|
-
|
|
107
|
-
// bundle 同期で ~/.claude/scripts/setup_hub_ai.py (or ~/.codex/...) が
|
|
108
|
-
// 配置された直後に kick する。設計穴: 初回 enroll では誰も
|
|
109
|
-
// setup_hub_ai.py を実行しないため、`step_claude_md` が走らず
|
|
110
|
-
// CLAUDE.md/AGENTS.md がいつまでも作成されない事故があった
|
|
111
|
-
// (2026-06-17 toma 環境)。bundle 配信は scripts と
|
|
112
|
-
// claude_md_fragment.md は含むが、CLAUDE.md/AGENTS.md と
|
|
113
|
-
// settings.json/hooks.json は含まないため、誰かが setup_hub_ai を
|
|
114
|
-
// 一度叩かないと self-bootstrap しない。enroll 直後に best-effort で
|
|
115
|
-
// 1 回叩く。既存環境では step_claude_md がマーカー範囲を冪等更新
|
|
116
|
-
// するだけで害なし。
|
|
117
|
-
try {
|
|
118
|
-
kickSetupHubAi({ logger: opts.logger, cliKind })
|
|
119
|
-
} catch (err) {
|
|
120
|
-
opts.logger?.warn?.({ cliKind, err: err.message }, "kickSetupHubAi from enroll failed (ignored)")
|
|
121
|
-
}
|
|
122
109
|
}
|
|
123
110
|
}
|
|
124
111
|
|
package/src/hub-bundle.mjs
CHANGED
|
@@ -180,8 +180,24 @@ export async function applyBundle({ bundle, logger, cliKind } = {}) {
|
|
|
180
180
|
logger?.info({ dest, mode: mode.toString(8) }, "hub-bundle wrote file")
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
//
|
|
183
|
+
// token を保存する。役割分担(2026-07-15 整理、G):
|
|
184
|
+
// - 正規 `<cliDir>/.hub_token.json`(`{access, refresh}` のみ)= setup_hub_ai.py が
|
|
185
|
+
// step_token で読む SoT。これを書くことで sync/enroll 経由でも setup が token を
|
|
186
|
+
// 見つけて認証をスキップでき、Codex でも setup が完走する(旧実装は下記 legacy しか
|
|
187
|
+
// 書かず、Codex では setup が token を見つけられず --silent で中断していた)。
|
|
188
|
+
// - legacy `<cliDir>/scripts/.hub_jwt.json`(version 付き)= 後方互換で従来どおり残す。
|
|
189
|
+
// 既存端末の reader やロールバックのため削除しない。
|
|
190
|
+
// cliKind 別ディレクトリに書くため Claude/Codex の token が混在しない。
|
|
184
191
|
if (bundle.token) {
|
|
192
|
+
const canonical = {}
|
|
193
|
+
if (typeof bundle.token.access === "string") canonical.access = bundle.token.access
|
|
194
|
+
if (typeof bundle.token.refresh === "string") canonical.refresh = bundle.token.refresh
|
|
195
|
+
if (canonical.access && canonical.refresh) {
|
|
196
|
+
const canonPath = path.join(resolveClaudeDir(kind), ".hub_token.json")
|
|
197
|
+
await fs.mkdir(path.dirname(canonPath), { recursive: true })
|
|
198
|
+
await fs.writeFile(canonPath, JSON.stringify(canonical), { mode: 0o600 })
|
|
199
|
+
written.push(canonPath)
|
|
200
|
+
}
|
|
185
201
|
const tp = tokenPath(kind)
|
|
186
202
|
await fs.mkdir(path.dirname(tp), { recursive: true })
|
|
187
203
|
await fs.writeFile(
|
|
@@ -256,17 +272,44 @@ function extractMarkerBlock(text) {
|
|
|
256
272
|
* post_install_commands を順次実行する。manifest に list で書く想定。
|
|
257
273
|
* Hub backend が冪等なコマンドを記述する責任を持つ (例: chmod +x ...)。
|
|
258
274
|
*/
|
|
259
|
-
export async function runPostInstall(bundle, { logger } = {}) {
|
|
275
|
+
export async function runPostInstall(bundle, { logger, isWindows } = {}) {
|
|
276
|
+
const win = isWindows ?? process.platform === "win32"
|
|
260
277
|
const cmds = Array.isArray(bundle.post_install_commands)
|
|
261
278
|
? bundle.post_install_commands
|
|
262
279
|
: []
|
|
263
280
|
for (const cmd of cmds) {
|
|
264
|
-
const argv =
|
|
281
|
+
const argv = _postInstallArgv(cmd, win)
|
|
265
282
|
if (!Array.isArray(argv) || argv.length === 0) continue
|
|
266
283
|
await runOnce(argv, { logger })
|
|
267
284
|
}
|
|
268
285
|
}
|
|
269
286
|
|
|
287
|
+
/**
|
|
288
|
+
* post_install エントリを実行 argv に解決する。次の 3 形式を受ける:
|
|
289
|
+
* - 文字列: シェルコマンドとして `/bin/sh -c <str>`(Windows は `cmd /c <str>`)
|
|
290
|
+
* - 配列: そのまま argv(`spawn(bin, args)`)
|
|
291
|
+
* - オブジェクト `{cmd_unix, cmd_windows, description}`: OS に応じて cmd_unix /
|
|
292
|
+
* cmd_windows を選びシェルコマンドとして実行する
|
|
293
|
+
*
|
|
294
|
+
* ⚠️ 旧実装はオブジェクト形式を `typeof==="string"` でも `Array.isArray` でもないため
|
|
295
|
+
* 黙って `continue` していた(= backend manifest の `{cmd_unix,...}` 形式 post_install が
|
|
296
|
+
* 一切実行されず、sync-bundle 後の setup_hub_ai.py が走らなかった。2026-07-15 修正)。
|
|
297
|
+
* cli_kind 別パス(`~/.claude` / `~/.codex`)は backend が `{{CLI_DIR_NAME}}` を解決済みで
|
|
298
|
+
* 返すため、ここでは OS 選択だけを行い、不用意な追加 shell 展開はしない。
|
|
299
|
+
*/
|
|
300
|
+
function _postInstallArgv(cmd, win) {
|
|
301
|
+
if (typeof cmd === "string") {
|
|
302
|
+
return win ? ["cmd", "/c", cmd] : ["/bin/sh", "-c", cmd]
|
|
303
|
+
}
|
|
304
|
+
if (Array.isArray(cmd)) return cmd
|
|
305
|
+
if (cmd && typeof cmd === "object") {
|
|
306
|
+
const line = win ? cmd.cmd_windows : cmd.cmd_unix
|
|
307
|
+
if (typeof line !== "string" || !line.trim()) return null
|
|
308
|
+
return win ? ["cmd", "/c", line] : ["/bin/sh", "-c", line]
|
|
309
|
+
}
|
|
310
|
+
return null
|
|
311
|
+
}
|
|
312
|
+
|
|
270
313
|
function runOnce(argv, { logger } = {}) {
|
|
271
314
|
return new Promise((resolve, reject) => {
|
|
272
315
|
const [bin, ...args] = argv
|
|
@@ -453,9 +496,41 @@ async function resolveCliBin(binName) {
|
|
|
453
496
|
}
|
|
454
497
|
|
|
455
498
|
/**
|
|
456
|
-
*
|
|
499
|
+
* 同期対象の CLI を決定する(共通ロジック、2026-07-15)。
|
|
500
|
+
*
|
|
501
|
+
* enroll / `sync-bundle` CLI / daemon self-heal で同じ規則を使い、経路ごとの分岐を無くす:
|
|
502
|
+
* - `detectAvailableClis()` の結果から backend 対応の `claude` / `codex` だけを採る
|
|
503
|
+
* - 明示 cliKind 指定時はそれ 1 件だけ(呼び出し側で `[cliKind]` を渡す)
|
|
504
|
+
* - 検出ゼロ(PATH 検出失敗等)は既存互換として `['claude']` にフォールバック
|
|
505
|
+
*
|
|
506
|
+
* @param {string[]} availableClis detectAvailableClis() の戻り
|
|
507
|
+
* @returns {Array<'claude'|'codex'>}
|
|
508
|
+
*/
|
|
509
|
+
export function resolveSyncTargets(availableClis) {
|
|
510
|
+
const detected = (Array.isArray(availableClis) ? availableClis : []).filter(
|
|
511
|
+
(c) => c === "claude" || c === "codex",
|
|
512
|
+
)
|
|
513
|
+
return detected.length > 0 ? detected : ["claude"]
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* fetch + apply + post_install(setup) + MCP server 自動登録を **同期的に** まとめて実行。
|
|
457
518
|
* cliKind='codex' で Codex 向け(`~/.codex/`、`codex mcp add`)に切り替わる
|
|
458
519
|
* (Stream A2、2026-07-02。既定 'claude' は完全に従来どおり)。
|
|
520
|
+
*
|
|
521
|
+
* **責務と SoT(2026-07-15、二重取得の整理)**:
|
|
522
|
+
* - `applyBundle` = 最小ブートストラップ。files[] 配置・CONFIG_MD マーカー・token
|
|
523
|
+
* (正規 `.hub_token.json` + legacy `.hub_jwt.json`)を置き、**setup_hub_ai.py が存在し
|
|
524
|
+
* 実行できる状態**を作る。
|
|
525
|
+
* - `runPostInstall` は backend が cli_kind 別にレンダリングした post_install_commands
|
|
526
|
+
* (= `python3 <cliDir>/scripts/setup_hub_ai.py --silent`)を **await で同期実行** する。
|
|
527
|
+
* `setup_hub_ai.py` が **完全セットアップの SoT**:hooks の冪等マージ(settings.json /
|
|
528
|
+
* hooks.json)・installed_version 記録・旧 JWT の正規 token 移行・CONFIG_MD 生成を担う。
|
|
529
|
+
* その中で `step_install_files` が bundle を再取得して files を冪等に再検証する(= 2 回目の
|
|
530
|
+
* fetch。SoT の自己完結検証として許容。applyBundle の配置と重複しても冪等)。
|
|
531
|
+
* - よって **本関数が成功を返す時点で setup は完了しており、detached の待ち残りは無い**
|
|
532
|
+
* (手動 `hub-agent sync-bundle` は setup 完了まで同期的に待つ)。CLI 別パスは backend が
|
|
533
|
+
* `{{CLI_DIR_NAME}}` を解決して返すため、Codex 同期で `~/.claude` の setup を誤実行しない。
|
|
459
534
|
*/
|
|
460
535
|
export async function syncBundle({ hubUrl, agentId, agentToken, logger, fetchImpl, cliKind = "claude" } = {}) {
|
|
461
536
|
const bundle = await fetchBundle({ hubUrl, agentId, agentToken, fetchImpl, cliKind })
|
|
@@ -463,7 +538,7 @@ export async function syncBundle({ hubUrl, agentId, agentToken, logger, fetchImp
|
|
|
463
538
|
try {
|
|
464
539
|
await runPostInstall(bundle, { logger })
|
|
465
540
|
} catch (err) {
|
|
466
|
-
logger?.warn({ err: err.message }, "post_install partially failed (continuing)")
|
|
541
|
+
logger?.warn({ cliKind, err: err.message }, "post_install/setup partially failed (continuing)")
|
|
467
542
|
}
|
|
468
543
|
let mcp = { added: [], skipped: [] }
|
|
469
544
|
try {
|
package/src/main.mjs
CHANGED
|
@@ -460,6 +460,38 @@ export function kickSetupHubAi({
|
|
|
460
460
|
}
|
|
461
461
|
}
|
|
462
462
|
|
|
463
|
+
/**
|
|
464
|
+
* daemon 起動時 self-heal: 検出された Claude/Codex **双方**の setup_hub_ai.py を
|
|
465
|
+
* best-effort で kick する(2026-07-15)。旧実装は `kickSetupHubAi({logger})` = Claude 固定で、
|
|
466
|
+
* Codex が入っていても hooks.json/AGENTS.md/version が修復されなかった。
|
|
467
|
+
*
|
|
468
|
+
* - 対象決定は enroll / sync-bundle と共通の resolveSyncTargets に寄せる
|
|
469
|
+
* - 各 CLI を detached で best-effort 実行(既に整った環境では step_* が冪等で変更ゼロ)
|
|
470
|
+
* - kickSetupHubAi は例外を投げないため、一方の失敗で他方を止めない
|
|
471
|
+
* - ログには必ず cliKind を含める
|
|
472
|
+
*/
|
|
473
|
+
export async function kickSetupHubAiForDetectedClis({
|
|
474
|
+
logger,
|
|
475
|
+
detectImpl,
|
|
476
|
+
homedir,
|
|
477
|
+
spawnImpl,
|
|
478
|
+
existsImpl,
|
|
479
|
+
} = {}) {
|
|
480
|
+
let targets = ["claude"]
|
|
481
|
+
try {
|
|
482
|
+
const detect = detectImpl || detectAvailableClis
|
|
483
|
+
const { resolveSyncTargets } = await import("./hub-bundle.mjs")
|
|
484
|
+
targets = resolveSyncTargets(await detect())
|
|
485
|
+
} catch (e) {
|
|
486
|
+
logger?.warn?.({ err: e?.message }, "kickSetupHubAiForDetectedClis: CLI 検出失敗 - claude のみ")
|
|
487
|
+
targets = ["claude"]
|
|
488
|
+
}
|
|
489
|
+
for (const cliKind of targets) {
|
|
490
|
+
kickSetupHubAi({ logger, cliKind, homedir, spawnImpl, existsImpl })
|
|
491
|
+
}
|
|
492
|
+
return targets
|
|
493
|
+
}
|
|
494
|
+
|
|
463
495
|
export async function startDaemon({ version, ptyModule, claudeSdk } = {}) {
|
|
464
496
|
const config = await readConfig()
|
|
465
497
|
if (!config) {
|
|
@@ -511,7 +543,8 @@ export async function startDaemon({ version, ptyModule, claudeSdk } = {}) {
|
|
|
511
543
|
// `hub-agent restart` だけで CLAUDE.md と hooks を自動修復できるようにする。
|
|
512
544
|
// 既に整っている環境では step_* がマーカー範囲を冪等更新するだけで害なし
|
|
513
545
|
// (detached + stdio ignore で daemon は妨げない)。WHY: 2026-06-17 toma 環境調査。
|
|
514
|
-
|
|
546
|
+
// 2026-07-15: Claude 固定から **検出された Claude/Codex 双方** の kick へ拡張。
|
|
547
|
+
await kickSetupHubAiForDetectedClis({ logger })
|
|
515
548
|
|
|
516
549
|
// node-pty (Microsoft 公式) は macOS arm64 で PTY master fd を spawn ごとに leak し
|
|
517
550
|
// (kill/destroy でも解放されず)、kern.tty.ptmx_max=511 到達で「新規ターミナルが
|
package/src/usage.mjs
CHANGED
|
@@ -1445,15 +1445,22 @@ export async function codexUsageForThread(threadId) {
|
|
|
1445
1445
|
const rateLimits = found.payload.rate_limits
|
|
1446
1446
|
|
|
1447
1447
|
let contextPercent = null
|
|
1448
|
-
const totalTok = info?.total_token_usage
|
|
1449
1448
|
const windowSize = info?.model_context_window
|
|
1450
|
-
|
|
1449
|
+
// context 窓の「現在の占有率」は last_token_usage(直近ターンで実際に窓へ載った
|
|
1450
|
+
// トークン)で測る。total_token_usage は会話累積(キャッシュ再利用分も加算し
|
|
1451
|
+
// 単調増加)なので、ターンを重ねると窓サイズを超過し Math.min(100,…) で 100% に
|
|
1452
|
+
// クランプされてドーナツが固着する(2026-07-15 バグ修正: 実測で累積 1,341,719 /
|
|
1453
|
+
// 窓 258,400 = 519% → 100% 張り付き。実占有は last=66,973 → 25.9%)。Codex は
|
|
1454
|
+
// 自動コンパクションで実文脈を使い回すため、累積で割ると必ず張り付く。
|
|
1455
|
+
// last_token_usage が無い旧 Codex 版のみ従来の total_token_usage にフォールバック。
|
|
1456
|
+
const usageForContext = info?.last_token_usage ?? info?.total_token_usage
|
|
1457
|
+
if (usageForContext && typeof windowSize === "number" && windowSize > 0) {
|
|
1451
1458
|
const tokens =
|
|
1452
|
-
typeof
|
|
1453
|
-
?
|
|
1454
|
-
: (
|
|
1455
|
-
(
|
|
1456
|
-
(
|
|
1459
|
+
typeof usageForContext.total_tokens === "number"
|
|
1460
|
+
? usageForContext.total_tokens
|
|
1461
|
+
: (usageForContext.input_tokens || 0) +
|
|
1462
|
+
(usageForContext.cached_input_tokens || 0) +
|
|
1463
|
+
(usageForContext.output_tokens || 0)
|
|
1457
1464
|
contextPercent = Math.min(100, Math.round((tokens / windowSize) * 1000) / 10)
|
|
1458
1465
|
}
|
|
1459
1466
|
|