@cocorograph/hub-agent 0.7.62 → 0.7.64
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 +24 -9
- package/package.json +1 -1
- package/scripts/install.sh +87 -41
- package/src/enroll.mjs +10 -23
- package/src/hub-bundle.mjs +126 -6
- package/src/main.mjs +42 -5
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 {
|
|
30
|
+
import { resolveSyncTargets, syncBundleTargets } from "../src/hub-bundle.mjs"
|
|
31
31
|
import {
|
|
32
32
|
installPlugin,
|
|
33
33
|
listInstallableExamples,
|
|
@@ -110,18 +110,29 @@ 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 完了まで同期的に**処理する(純関数 syncBundleTargets に分離)。
|
|
119
|
+
// 一方の失敗で他方を止めず、失敗した CLI は成功メッセージを出さない。
|
|
120
|
+
const { results, failures } = await syncBundleTargets({
|
|
121
|
+
targets,
|
|
122
|
+
hubUrl: cfg.hub_url,
|
|
123
|
+
agentId: cfg.agent_id,
|
|
124
|
+
agentToken: cfg.agent_token,
|
|
125
|
+
})
|
|
118
126
|
for (const cliKind of targets) {
|
|
119
|
-
const r =
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
127
|
+
const r = results[cliKind]
|
|
128
|
+
if (!r || r.error) {
|
|
129
|
+
// 失敗した CLI は success 行を出さない(reject が正しく届いている証跡)。
|
|
130
|
+
console.error(
|
|
131
|
+
`sync-bundle failed (${cliKind}): ${r?.error ?? "unknown"}` +
|
|
132
|
+
(r?.exitCode != null ? ` (exit ${r.exitCode})` : ""),
|
|
133
|
+
)
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
125
136
|
const mcpSummary = r.mcp
|
|
126
137
|
? ` mcp_added=${r.mcp.added.length}${r.mcp.skipped.length > 0 ? ` mcp_skipped=${r.mcp.skipped.length}` : ""}`
|
|
127
138
|
: ""
|
|
@@ -132,6 +143,10 @@ program
|
|
|
132
143
|
console.log(` mcp servers added: ${r.mcp.added.join(", ")}`)
|
|
133
144
|
}
|
|
134
145
|
}
|
|
146
|
+
if (failures.length > 0) {
|
|
147
|
+
console.error(`sync-bundle 失敗: ${failures.join(", ")}`)
|
|
148
|
+
process.exit(1)
|
|
149
|
+
}
|
|
135
150
|
} catch (err) {
|
|
136
151
|
console.error(`sync-bundle failed: ${err.message}`)
|
|
137
152
|
process.exit(1)
|
package/package.json
CHANGED
package/scripts/install.sh
CHANGED
|
@@ -646,30 +646,67 @@ 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
|
-
|
|
654
|
+
# 検証対象の CLI を列挙する。**PATH で検出された CLI だけ**を対象にし、どちらも未検出の
|
|
655
|
+
# ときのみ claude にフォールバックする(JS 側 resolveSyncTargets と一致。2026-07-15 差し戻し対応。
|
|
656
|
+
# 旧実装は claude を常に対象にしていたため、Codex-only 環境で不要な Claude bundle 欠損警告・
|
|
657
|
+
# bootstrap が走っていた)。各行 "cli_kind:dir:config_md:hooks_file"。
|
|
658
|
+
_hub_target_clis() {
|
|
659
|
+
local any=0
|
|
660
|
+
if present claude; then
|
|
661
|
+
echo "claude:.claude:CLAUDE.md:settings.json"
|
|
662
|
+
any=1
|
|
663
663
|
fi
|
|
664
|
-
if
|
|
665
|
-
|
|
664
|
+
if present codex; then
|
|
665
|
+
echo "codex:.codex:AGENTS.md:hooks.json"
|
|
666
|
+
any=1
|
|
666
667
|
fi
|
|
667
|
-
if [
|
|
668
|
-
|
|
668
|
+
if [ "$any" -eq 0 ]; then
|
|
669
|
+
echo "claude:.claude:CLAUDE.md:settings.json"
|
|
669
670
|
fi
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
# 単一 CLI の bundle 完全性を判定する。完全なら 0、欠けていれば 1。
|
|
674
|
+
# $1=dir(.claude/.codex) $2=config_md(CLAUDE.md/AGENTS.md) $3=hooks_file(settings.json/hooks.json)
|
|
675
|
+
# 検証項目: scripts/manifest.json / CONFIG_MD の HUB-AI-RULES マーカー /
|
|
676
|
+
# hooks 設定に Hub SessionStart hook(session_start.py) / 正規 .hub_token.json
|
|
677
|
+
_hub_bundle_incomplete_cli() {
|
|
678
|
+
local dir="$1" config_md="$2" hooks_file="$3"
|
|
679
|
+
local base="$HOME/$dir"
|
|
680
|
+
[[ -f "$base/scripts/manifest.json" ]] || return 0
|
|
681
|
+
[[ -f "$base/$config_md" ]] || return 0
|
|
682
|
+
grep -q "BEGIN HUB-AI-RULES" "$base/$config_md" 2>/dev/null || return 0
|
|
683
|
+
grep -q "session_start.py" "$base/$hooks_file" 2>/dev/null || return 0
|
|
684
|
+
[[ -f "$base/.hub_token.json" ]] || return 0
|
|
670
685
|
return 1
|
|
671
686
|
}
|
|
672
687
|
|
|
688
|
+
# 後方互換: 従来 claude 前提で呼んでいた箇所向けの薄いラッパ。
|
|
689
|
+
_hub_bundle_incomplete() {
|
|
690
|
+
_hub_bundle_incomplete_cli ".claude" "CLAUDE.md" "settings.json"
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
# 検出された各 CLI の bundle 完全性を検証し、**不完全な CLI だけ** を
|
|
694
|
+
# `hub-agent sync-bundle --cli-kind <kind>` で再同期する(Codex 未インストールなら対象外)。
|
|
695
|
+
_hub_reconcile_bundles() {
|
|
696
|
+
local line cli_kind dir config_md hooks_file
|
|
697
|
+
while IFS= read -r line; do
|
|
698
|
+
[[ -z "$line" ]] && continue
|
|
699
|
+
IFS=":" read -r cli_kind dir config_md hooks_file <<< "$line"
|
|
700
|
+
if _hub_bundle_incomplete_cli "$dir" "$config_md" "$hooks_file"; then
|
|
701
|
+
color_warn "Hub AI bundle が ~/$dir に完全展開されていない可能性 ($cli_kind)。sync-bundle を再試行"
|
|
702
|
+
if ! retry 2 hub-agent sync-bundle --cli-kind "$cli_kind"; then
|
|
703
|
+
color_warn "hub-agent sync-bundle --cli-kind $cli_kind が継続失敗。あとで手動実行してください:"
|
|
704
|
+
color_warn " hub-agent sync-bundle --cli-kind $cli_kind"
|
|
705
|
+
fi
|
|
706
|
+
fi
|
|
707
|
+
done < <(_hub_target_clis)
|
|
708
|
+
}
|
|
709
|
+
|
|
673
710
|
do_install_service() {
|
|
674
711
|
color_step "OS サービスとして自動起動を登録"
|
|
675
712
|
# install-service は launchctl bootout/bootstrap の transient で初回失敗する
|
|
@@ -687,17 +724,26 @@ do_install_service() {
|
|
|
687
724
|
# verify を成功させる。setup_hub_ai 不在 (bundle 同期失敗) のときは色付き
|
|
688
725
|
# warn を出して継続。再実行時は step_* が冪等処理を返すだけで害なし。
|
|
689
726
|
do_bootstrap_hub_ai() {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
727
|
+
# 検出された各 CLI(claude 常に / codex は present 時)の setup_hub_ai.py を
|
|
728
|
+
# 同期実行する。enroll/sync 内でも setup は走るが、install.sh では verify を確実に
|
|
729
|
+
# 成功させるため各 CLI で同期実行する(一方の失敗で他方を止めない)。2026-07-15 に
|
|
730
|
+
# Claude 固定から双方へ拡張。
|
|
731
|
+
local line cli_kind dir config_md hooks_file setup_script
|
|
732
|
+
while IFS= read -r line; do
|
|
733
|
+
[[ -z "$line" ]] && continue
|
|
734
|
+
IFS=":" read -r cli_kind dir config_md hooks_file <<< "$line"
|
|
735
|
+
setup_script="$HOME/$dir/scripts/setup_hub_ai.py"
|
|
736
|
+
if [[ ! -f "$setup_script" ]]; then
|
|
737
|
+
color_warn "setup_hub_ai.py 不在 ($cli_kind, bundle 同期が失敗した可能性)。スキップ"
|
|
738
|
+
continue
|
|
739
|
+
fi
|
|
740
|
+
color_step "setup_hub_ai.py --silent を実行 ($cli_kind: ~/$dir/$config_md / $hooks_file 初期化)"
|
|
741
|
+
if python3 "$setup_script" --silent; then
|
|
742
|
+
color_ok "Hub AI bootstrap 完了 ($cli_kind)"
|
|
743
|
+
else
|
|
744
|
+
color_warn "setup_hub_ai.py --silent が exit≠0 ($cli_kind, 継続)。手動再実行: python3 $setup_script"
|
|
745
|
+
fi
|
|
746
|
+
done < <(_hub_target_clis)
|
|
701
747
|
}
|
|
702
748
|
|
|
703
749
|
# セットアップ最終検証。
|
|
@@ -721,22 +767,22 @@ verify_setup() {
|
|
|
721
767
|
color_warn "Claude Code (claude) コマンドが見つかりません (cockpit から claude を呼ぶ場合は必要)"
|
|
722
768
|
fi
|
|
723
769
|
|
|
724
|
-
# 3. Hub AI bundle
|
|
725
|
-
#
|
|
726
|
-
#
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
770
|
+
# 3. Hub AI bundle 展開を **CLI 別** に検証(manifest / CONFIG_MD マーカー /
|
|
771
|
+
# hooks の Hub SessionStart hook / 正規 .hub_token.json)。Codex 未インストールなら
|
|
772
|
+
# codex は検証しない(2026-07-15)。
|
|
773
|
+
local line cli_kind dir config_md hooks_file mver
|
|
774
|
+
while IFS= read -r line; do
|
|
775
|
+
[[ -z "$line" ]] && continue
|
|
776
|
+
IFS=":" read -r cli_kind dir config_md hooks_file <<< "$line"
|
|
777
|
+
if ! _hub_bundle_incomplete_cli "$dir" "$config_md" "$hooks_file"; then
|
|
778
|
+
mver="$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$HOME/$dir/scripts/manifest.json" 2>/dev/null | head -1 | sed 's/.*"\([^"]*\)"$/\1/')"
|
|
779
|
+
color_ok "Hub AI bundle 展開済み ($cli_kind: ~/$dir/$config_md + scripts/manifest.json ${mver:+v$mver})"
|
|
734
780
|
else
|
|
735
|
-
color_warn "Hub AI bundle が半完了状態 (
|
|
781
|
+
color_warn "Hub AI bundle が半完了状態 ($cli_kind: ~/$dir の manifest / $config_md マーカー / $hooks_file の SessionStart hook / .hub_token.json のいずれか欠落)"
|
|
782
|
+
color_warn " 手動再同期: hub-agent sync-bundle --cli-kind $cli_kind"
|
|
783
|
+
errors=$((errors + 1))
|
|
736
784
|
fi
|
|
737
|
-
|
|
738
|
-
errors=$((errors + 1))
|
|
739
|
-
fi
|
|
785
|
+
done < <(_hub_target_clis)
|
|
740
786
|
|
|
741
787
|
# 4. OS サービス起動 (Darwin 限定の確認。Linux は systemctl --user で別途)
|
|
742
788
|
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,15 +538,60 @@ export async function syncBundle({ hubUrl, agentId, agentToken, logger, fetchImp
|
|
|
463
538
|
try {
|
|
464
539
|
await runPostInstall(bundle, { logger })
|
|
465
540
|
} catch (err) {
|
|
466
|
-
|
|
541
|
+
// ⚠️ post_install(=setup_hub_ai.py) 失敗は **bundle 同期失敗として呼び出し元へ伝播** する
|
|
542
|
+
// (2026-07-15 差し戻し対応)。旧実装は warn で握りつぶして成功を返していたため、hooks 未登録 /
|
|
543
|
+
// CONFIG_MD 未生成 / installed_version 未記録 / token 検証失敗 / setup 内 ping 失敗 / self-reexec
|
|
544
|
+
// 失敗があっても「同期成功」と誤報告していた(syncBundle の「成功を返す時点で setup 完了済み」
|
|
545
|
+
// という docstring 契約と実装が不一致だった)。MCP 登録の fail-soft(下記)とは明確に分ける。
|
|
546
|
+
// 元の exit code / 原因を失わないよう cause と cliKind を付けて再 throw する。
|
|
547
|
+
logger?.error?.({ cliKind, err: err.message }, "post_install/setup failed")
|
|
548
|
+
const wrapped = new Error(`bundle setup failed (${cliKind}): ${err.message}`)
|
|
549
|
+
wrapped.cause = err
|
|
550
|
+
wrapped.cliKind = cliKind
|
|
551
|
+
throw wrapped
|
|
467
552
|
}
|
|
553
|
+
// MCP 登録は fail-soft を維持(setup 完了後の付随処理。CLI が未インストール等でも同期自体は成功扱い)。
|
|
468
554
|
let mcp = { added: [], skipped: [] }
|
|
469
555
|
try {
|
|
470
556
|
mcp = cliKind === "codex"
|
|
471
557
|
? await applyCodexMcpServers(bundle, { logger })
|
|
472
558
|
: await applyMcpServers(bundle, { agentId, agentToken, logger })
|
|
473
559
|
} catch (err) {
|
|
474
|
-
logger?.warn({ err: err.message }, "applyMcpServers failed (continuing)")
|
|
560
|
+
logger?.warn({ cliKind, err: err.message }, "applyMcpServers failed (continuing)")
|
|
475
561
|
}
|
|
476
562
|
return { version: bundle.version, ...result, mcp }
|
|
477
563
|
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* 複数 CLI の syncBundle を順に実行し、**一方が失敗しても他方を試行**する純関数
|
|
567
|
+
* (bin/hub-agent.mjs の sync-bundle が process.exit する前段をテスト可能にするため分離。
|
|
568
|
+
* 2026-07-15)。
|
|
569
|
+
*
|
|
570
|
+
* @returns {{results: Record<string, any>, failures: string[]}}
|
|
571
|
+
* results[cliKind] は成功時 syncBundle の戻り、失敗時 `{ error, exitCode? }`。
|
|
572
|
+
* failures は失敗した cliKind の配列(1 件以上なら呼び出し側が exit 非 0 にする)。
|
|
573
|
+
*/
|
|
574
|
+
export async function syncBundleTargets({
|
|
575
|
+
targets,
|
|
576
|
+
hubUrl,
|
|
577
|
+
agentId,
|
|
578
|
+
agentToken,
|
|
579
|
+
fetchImpl,
|
|
580
|
+
logger,
|
|
581
|
+
syncImpl,
|
|
582
|
+
} = {}) {
|
|
583
|
+
const sync = syncImpl || syncBundle
|
|
584
|
+
const results = {}
|
|
585
|
+
const failures = []
|
|
586
|
+
for (const cliKind of Array.isArray(targets) ? targets : []) {
|
|
587
|
+
try {
|
|
588
|
+
results[cliKind] = await sync({ hubUrl, agentId, agentToken, fetchImpl, cliKind, logger })
|
|
589
|
+
} catch (err) {
|
|
590
|
+
// exit code を message から救い出す(runOnce は "... (exit N): ..." を投げる)。
|
|
591
|
+
const m = /exit (\d+)/.exec(err?.cause?.message || err?.message || "")
|
|
592
|
+
results[cliKind] = { error: err.message, exitCode: m ? Number(m[1]) : null }
|
|
593
|
+
failures.push(cliKind)
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return { results, failures }
|
|
597
|
+
}
|
package/src/main.mjs
CHANGED
|
@@ -437,8 +437,9 @@ export function kickSetupHubAi({
|
|
|
437
437
|
const setupScript = path.join(home, dirName, "scripts", "setup_hub_ai.py")
|
|
438
438
|
const exists = existsImpl || existsSync
|
|
439
439
|
if (!exists(setupScript)) {
|
|
440
|
+
// 全ログに cliKind を含める(Claude/Codex 2 行を識別できるように。2026-07-15 差し戻し対応)。
|
|
440
441
|
logger?.info?.(
|
|
441
|
-
{ setupScript },
|
|
442
|
+
{ cliKind, setupScript },
|
|
442
443
|
"kickSetupHubAi: setup_hub_ai.py 不在 - skip",
|
|
443
444
|
)
|
|
444
445
|
return false
|
|
@@ -449,17 +450,52 @@ export function kickSetupHubAi({
|
|
|
449
450
|
stdio: "ignore",
|
|
450
451
|
})
|
|
451
452
|
child?.on?.("error", (e) =>
|
|
452
|
-
logger?.warn?.({ err: e?.message }, "kickSetupHubAi: spawn error"),
|
|
453
|
+
logger?.warn?.({ cliKind, setupScript, err: e?.message }, "kickSetupHubAi: spawn error"),
|
|
453
454
|
)
|
|
454
455
|
child?.unref?.()
|
|
455
|
-
logger?.info?.(
|
|
456
|
+
logger?.info?.(
|
|
457
|
+
{ cliKind, setupScript },
|
|
458
|
+
"kickSetupHubAi: setup_hub_ai --silent を起動 (hooks 同期)",
|
|
459
|
+
)
|
|
456
460
|
return true
|
|
457
461
|
} catch (e) {
|
|
458
|
-
logger?.warn?.({ err: e?.message }, "kickSetupHubAi: 失敗 (無視)")
|
|
462
|
+
logger?.warn?.({ cliKind, err: e?.message }, "kickSetupHubAi: 失敗 (無視)")
|
|
459
463
|
return false
|
|
460
464
|
}
|
|
461
465
|
}
|
|
462
466
|
|
|
467
|
+
/**
|
|
468
|
+
* daemon 起動時 self-heal: 検出された Claude/Codex **双方**の setup_hub_ai.py を
|
|
469
|
+
* best-effort で kick する(2026-07-15)。旧実装は `kickSetupHubAi({logger})` = Claude 固定で、
|
|
470
|
+
* Codex が入っていても hooks.json/AGENTS.md/version が修復されなかった。
|
|
471
|
+
*
|
|
472
|
+
* - 対象決定は enroll / sync-bundle と共通の resolveSyncTargets に寄せる
|
|
473
|
+
* - 各 CLI を detached で best-effort 実行(既に整った環境では step_* が冪等で変更ゼロ)
|
|
474
|
+
* - kickSetupHubAi は例外を投げないため、一方の失敗で他方を止めない
|
|
475
|
+
* - ログには必ず cliKind を含める
|
|
476
|
+
*/
|
|
477
|
+
export async function kickSetupHubAiForDetectedClis({
|
|
478
|
+
logger,
|
|
479
|
+
detectImpl,
|
|
480
|
+
homedir,
|
|
481
|
+
spawnImpl,
|
|
482
|
+
existsImpl,
|
|
483
|
+
} = {}) {
|
|
484
|
+
let targets = ["claude"]
|
|
485
|
+
try {
|
|
486
|
+
const detect = detectImpl || detectAvailableClis
|
|
487
|
+
const { resolveSyncTargets } = await import("./hub-bundle.mjs")
|
|
488
|
+
targets = resolveSyncTargets(await detect())
|
|
489
|
+
} catch (e) {
|
|
490
|
+
logger?.warn?.({ err: e?.message }, "kickSetupHubAiForDetectedClis: CLI 検出失敗 - claude のみ")
|
|
491
|
+
targets = ["claude"]
|
|
492
|
+
}
|
|
493
|
+
for (const cliKind of targets) {
|
|
494
|
+
kickSetupHubAi({ logger, cliKind, homedir, spawnImpl, existsImpl })
|
|
495
|
+
}
|
|
496
|
+
return targets
|
|
497
|
+
}
|
|
498
|
+
|
|
463
499
|
export async function startDaemon({ version, ptyModule, claudeSdk } = {}) {
|
|
464
500
|
const config = await readConfig()
|
|
465
501
|
if (!config) {
|
|
@@ -511,7 +547,8 @@ export async function startDaemon({ version, ptyModule, claudeSdk } = {}) {
|
|
|
511
547
|
// `hub-agent restart` だけで CLAUDE.md と hooks を自動修復できるようにする。
|
|
512
548
|
// 既に整っている環境では step_* がマーカー範囲を冪等更新するだけで害なし
|
|
513
549
|
// (detached + stdio ignore で daemon は妨げない)。WHY: 2026-06-17 toma 環境調査。
|
|
514
|
-
|
|
550
|
+
// 2026-07-15: Claude 固定から **検出された Claude/Codex 双方** の kick へ拡張。
|
|
551
|
+
await kickSetupHubAiForDetectedClis({ logger })
|
|
515
552
|
|
|
516
553
|
// node-pty (Microsoft 公式) は macOS arm64 で PTY master fd を spawn ごとに leak し
|
|
517
554
|
// (kill/destroy でも解放されず)、kern.tty.ptmx_max=511 到達で「新規ターミナルが
|