@cocorograph/hub-agent 0.5.14 → 0.5.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocorograph/hub-agent",
3
- "version": "0.5.14",
3
+ "version": "0.5.16",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
package/src/enroll.mjs CHANGED
@@ -57,6 +57,11 @@ export async function enroll(enrollmentToken, opts = {}) {
57
57
  agent_id: data.agent_id,
58
58
  agent_token: data.agent_token,
59
59
  hub_url: hubUrl,
60
+ // Hub から取得した Claude CLI 設定 (cockpit 管理画面でユーザーが指定可能)。
61
+ // backend 未対応時は空文字で保存され、起動時の cmd 組み立てで無視される。
62
+ claude_model: typeof data.claude_model === "string" ? data.claude_model : "",
63
+ claude_permission_mode:
64
+ typeof data.claude_permission_mode === "string" ? data.claude_permission_mode : "",
60
65
  })
61
66
 
62
67
  // Hub AI bundle を続けて同期する (CLAUDE.md / scripts / hooks / projects.json
package/src/main.mjs CHANGED
@@ -25,6 +25,7 @@ import { listAgents } from "./agents.mjs"
25
25
  import { listSkills } from "./skills.mjs"
26
26
  import { listSessionStates } from "./state.mjs"
27
27
  import {
28
+ buildClaudeCmd,
28
29
  createSession as createTmuxSession,
29
30
  createWorktreeDir,
30
31
  execTmux,
@@ -40,6 +41,32 @@ const BUNDLE_MANIFEST_PATH =
40
41
  process.env.HUB_BUNDLE_MANIFEST ||
41
42
  path.join(os.homedir(), ".claude", "scripts", "manifest.json")
42
43
 
44
+ /**
45
+ * `~/.hub/agent.json` の Claude CLI 設定から claude コマンド文字列を組み立てる。
46
+ *
47
+ * - enrollment 時に Hub から取得した `claude_model` / `claude_permission_mode` が
48
+ * 入っていれば、それを使って `claude --continue --model ... || claude --model ...` を生成
49
+ * - 両方空なら `undefined` を返す (createSession 内で DEFAULT_CLAUDE_CMD =
50
+ * 環境変数 HUB_CLAUDE_CMD か最小デフォルト `claude --continue || claude` が使われる)
51
+ *
52
+ * cockpit 管理画面でユーザーが Hub 側で model / permission-mode を変更すると、
53
+ * 次の enrollment / 再起動時に新しい値で組み立てられる。
54
+ */
55
+ function claudeCmdFromAgentConfig(config) {
56
+ const cmd = buildClaudeCmd({
57
+ model: config?.claude_model,
58
+ permissionMode: config?.claude_permission_mode,
59
+ })
60
+ // buildClaudeCmd は config も env も空なら最小デフォルトを返す。
61
+ // ここでは「明示的に Hub 側で何か指定されていた場合のみ上書き、なければ
62
+ // createSession 側の DEFAULT_CLAUDE_CMD に委ねる」挙動にするため、
63
+ // model も mode も空なら undefined を返す。
64
+ const model = (config?.claude_model || "").trim()
65
+ const mode = (config?.claude_permission_mode || "").trim()
66
+ if (!model && !mode) return undefined
67
+ return cmd
68
+ }
69
+
43
70
  /**
44
71
  * `~/.claude/scripts/manifest.json` から Hub AI bundle の version を読む。
45
72
  * 未セットアップなら null を返す。
@@ -398,7 +425,10 @@ async function dispatch(msg, ctx) {
398
425
  }
399
426
  try {
400
427
  await createTmuxSession(name, cwd, {
401
- claudeCmd: typeof msg.claude_cmd === "string" ? msg.claude_cmd : undefined,
428
+ claudeCmd:
429
+ typeof msg.claude_cmd === "string"
430
+ ? msg.claude_cmd
431
+ : claudeCmdFromAgentConfig(ctx.config),
402
432
  initialPrompt:
403
433
  typeof msg.initial_prompt === "string" ? msg.initial_prompt : undefined,
404
434
  logger: ctx.logger,
@@ -445,7 +475,10 @@ async function dispatch(msg, ctx) {
445
475
  const { wtName, wtPath, createdBranch } = await createWorktreeDir(dir, branch)
446
476
  // worktree dir で tmux session を作成 (claude_cmd は createSession の default)
447
477
  await createTmuxSession(wtName, wtPath, {
448
- claudeCmd: typeof msg.claude_cmd === "string" ? msg.claude_cmd : undefined,
478
+ claudeCmd:
479
+ typeof msg.claude_cmd === "string"
480
+ ? msg.claude_cmd
481
+ : claudeCmdFromAgentConfig(ctx.config),
449
482
  initialPrompt:
450
483
  typeof msg.initial_prompt === "string" ? msg.initial_prompt : undefined,
451
484
  logger: ctx.logger,
package/src/tmux.mjs CHANGED
@@ -180,23 +180,59 @@ async function buildWorktreeParentMap() {
180
180
  }
181
181
  return out
182
182
  }
183
- const DEFAULT_CLAUDE_CMD =
184
- process.env.HUB_CLAUDE_CMD ||
185
- "claude --continue --model claude-opus-4-7 --permission-mode auto || claude --model claude-opus-4-7 --permission-mode auto"
183
+ /**
184
+ * Claude CLI コマンドを組み立てる。
185
+ *
186
+ * 優先順:
187
+ * 1. Hub から配信された agent 設定 (opts.model / opts.permissionMode)
188
+ * - cockpit 管理画面で設定 → enrollment / heartbeat で配信
189
+ * 2. 環境変数 `HUB_CLAUDE_CMD`
190
+ * - shell startup file 等で各人が自由にカスタマイズ
191
+ * 3. 最小デフォルト `claude --continue || claude`
192
+ * - Claude Code 標準デフォルト (model / mode 共に Claude 側に委ねる)
193
+ *
194
+ * `--model` / `--permission-mode` を hard-code しない理由:
195
+ * - model は個人の好み (Opus / Sonnet / Haiku) で分かれる
196
+ * - `--permission-mode auto` は Team プラン限定機能で、個人プランでは
197
+ * "Allowed choices are acceptEdits, ..." と CLI が拒否する事故 (2026-05-20 葛西)
198
+ */
199
+ export function buildClaudeCmd(opts = {}) {
200
+ const fromHub = composeClaudeCmdFromOptions(opts)
201
+ if (fromHub) return fromHub
202
+ if (process.env.HUB_CLAUDE_CMD) return process.env.HUB_CLAUDE_CMD
203
+ return "claude --continue || claude"
204
+ }
205
+
206
+ /**
207
+ * agent 設定 (model / permissionMode) から claude コマンド文字列を組み立てる。
208
+ * いずれも null / 空文字なら未指定とみなし、両方未指定なら null を返す
209
+ * (上位で環境変数 / デフォルトにフォールバック)。
210
+ */
211
+ export function composeClaudeCmdFromOptions(opts = {}) {
212
+ const model = (opts.model || "").trim()
213
+ const mode = (opts.permissionMode || "").trim()
214
+ if (!model && !mode) return null
215
+ const flags = []
216
+ if (model) flags.push(`--model ${model}`)
217
+ if (mode) flags.push(`--permission-mode ${mode}`)
218
+ const tail = flags.join(" ")
219
+ return `claude --continue ${tail} || claude ${tail}`.replace(/\s+/g, " ").trim()
220
+ }
221
+
222
+ const DEFAULT_CLAUDE_CMD = buildClaudeCmd()
186
223
 
187
224
  function tmuxBin(opts = {}) {
188
225
  return opts.tmuxBin || DEFAULT_TMUX_BIN
189
226
  }
190
227
 
191
228
  /**
192
- * claudeCmd の各 `claude ... --permission-mode auto` の直後に
193
- * initialPrompt を shell-safe quoting で末尾引数として注入する。
229
+ * claudeCmd の各セグメント末尾に initialPrompt shell-safe quoting
230
+ * 末尾引数として注入する。
194
231
  *
195
- * `claude --foo --permission-mode auto || claude --bar --permission-mode auto`
196
- * のように `||` で連結された OR 構造の両側に同じ prompt を埋め込むため、
197
- * グローバル置換を行う。マッチが無ければ無加工の claudeCmd を返す
198
- * (ユーザー定義 claudeCmd が独自フォーマットで `--permission-mode auto`
199
- * 含まない場合は noop)。
232
+ * `cmd1 || cmd2` のように `||` で連結された OR 構造の両側に同じ prompt を
233
+ * 埋め込むため、`||` split して各セグメント末尾に追加する。
234
+ * 旧実装は `--permission-mode auto` を marker として正規表現マッチしていたが、
235
+ * model / permission-mode が個人ごとに変わる前提で marker 非依存に変更した。
200
236
  *
201
237
  * `JSON.stringify` は `"` `\` 制御文字を `\u00xx` 等で安全にエスケープし、
202
238
  * 結果は double-quoted な shell リテラルとしてそのまま使える。
@@ -205,7 +241,10 @@ export function injectInitialPrompt(claudeCmd, initialPrompt) {
205
241
  if (!initialPrompt || typeof initialPrompt !== "string") return claudeCmd
206
242
  if (typeof claudeCmd !== "string" || claudeCmd.length === 0) return claudeCmd
207
243
  const quoted = JSON.stringify(initialPrompt)
208
- return claudeCmd.replace(/--permission-mode auto/g, `--permission-mode auto ${quoted}`)
244
+ return claudeCmd
245
+ .split("||")
246
+ .map((part) => `${part.trim()} ${quoted}`)
247
+ .join(" || ")
209
248
  }
210
249
 
211
250
  /**