@cocorograph/hub-agent 0.7.63 → 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 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 { resolveSyncTargets, syncBundle } from "../src/hub-bundle.mjs"
30
+ import { resolveSyncTargets, syncBundleTargets } from "../src/hub-bundle.mjs"
31
31
  import {
32
32
  installPlugin,
33
33
  listInstallableExamples,
@@ -115,29 +115,32 @@ program
115
115
  targets = resolveSyncTargets(await detectAvailableClis())
116
116
  }
117
117
 
118
- // 各 CLI を **setup 完了まで同期的に**処理し、失敗は CLI 名付きで報告して continue
119
- // (一方の失敗で他方を止めない)。1 つでも失敗したら非 0 で終える。
120
- const failures = []
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
+ })
121
126
  for (const cliKind of targets) {
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}`,
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})` : ""),
134
133
  )
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)
134
+ continue
135
+ }
136
+ const mcpSummary = r.mcp
137
+ ? ` mcp_added=${r.mcp.added.length}${r.mcp.skipped.length > 0 ? ` mcp_skipped=${r.mcp.skipped.length}` : ""}`
138
+ : ""
139
+ console.log(
140
+ `hub bundle synced (${cliKind}): version=${r.version} written=${r.written.length} skipped_same=${r.skipped_same.length}${mcpSummary}`,
141
+ )
142
+ if (r.mcp && r.mcp.added.length > 0) {
143
+ console.log(` mcp servers added: ${r.mcp.added.join(", ")}`)
141
144
  }
142
145
  }
143
146
  if (failures.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocorograph/hub-agent",
3
- "version": "0.7.63",
3
+ "version": "0.7.64",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -651,12 +651,22 @@ do_enroll() {
651
651
  _hub_reconcile_bundles
652
652
  }
653
653
 
654
- # 検証対象の CLI を列挙する。claude は常に対象、codex は PATH にある時だけ対象。
655
- # 各行 "cli_kind:dir:config_md:hooks_file"(Codex 未インストール環境では codex を要求しない)。
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"。
656
658
  _hub_target_clis() {
657
- echo "claude:.claude:CLAUDE.md:settings.json"
659
+ local any=0
660
+ if present claude; then
661
+ echo "claude:.claude:CLAUDE.md:settings.json"
662
+ any=1
663
+ fi
658
664
  if present codex; then
659
665
  echo "codex:.codex:AGENTS.md:hooks.json"
666
+ any=1
667
+ fi
668
+ if [ "$any" -eq 0 ]; then
669
+ echo "claude:.claude:CLAUDE.md:settings.json"
660
670
  fi
661
671
  }
662
672
 
@@ -538,15 +538,60 @@ export async function syncBundle({ hubUrl, agentId, agentToken, logger, fetchImp
538
538
  try {
539
539
  await runPostInstall(bundle, { logger })
540
540
  } catch (err) {
541
- logger?.warn({ cliKind, err: err.message }, "post_install/setup partially failed (continuing)")
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
542
552
  }
553
+ // MCP 登録は fail-soft を維持(setup 完了後の付随処理。CLI が未インストール等でも同期自体は成功扱い)。
543
554
  let mcp = { added: [], skipped: [] }
544
555
  try {
545
556
  mcp = cliKind === "codex"
546
557
  ? await applyCodexMcpServers(bundle, { logger })
547
558
  : await applyMcpServers(bundle, { agentId, agentToken, logger })
548
559
  } catch (err) {
549
- logger?.warn({ err: err.message }, "applyMcpServers failed (continuing)")
560
+ logger?.warn({ cliKind, err: err.message }, "applyMcpServers failed (continuing)")
550
561
  }
551
562
  return { version: bundle.version, ...result, mcp }
552
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,13 +450,16 @@ 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?.({}, "kickSetupHubAi: setup_hub_ai --silent を起動 (hooks 同期)")
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
  }