@cocorograph/hub-agent 0.5.15 → 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 +1 -1
- package/src/enroll.mjs +5 -0
- package/src/main.mjs +35 -2
- package/src/service-install.mjs +2 -52
- package/src/tmux.mjs +50 -11
package/package.json
CHANGED
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:
|
|
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:
|
|
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/service-install.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* テンプレ内の __HUB_AGENT_BIN__ / __HOME__ / __PATH__ を実行時に置換する。
|
|
10
10
|
* 仕様書: ナレッジ/インフラ/cockpit-hub-hosted-integration-spec (id=6080)
|
|
11
11
|
*/
|
|
12
|
-
import { promises as fs
|
|
12
|
+
import { promises as fs } from "node:fs"
|
|
13
13
|
import os from "node:os"
|
|
14
14
|
import path from "node:path"
|
|
15
15
|
import { spawnSync } from "node:child_process"
|
|
@@ -63,59 +63,10 @@ function run(cmd, args, opts = {}) {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/**
|
|
67
|
-
* `which hub-agent` の結果を、launchd / systemd から長期間 exec 可能な
|
|
68
|
-
* 安定パスに正規化する。pure 関数。`opts` で fs を差し替えてテスト可能。
|
|
69
|
-
*
|
|
70
|
-
* 背景: fnm 利用者の `which hub-agent` は現在のシェル専用の一時パス
|
|
71
|
-
* (`~/.local/state/fnm_multishells/<PID>_<ts>/bin/hub-agent`) を返す。
|
|
72
|
-
* それを plist に書き込むと、install-service を叩いたシェル終了後に
|
|
73
|
-
* symlink が消えて launchd が exec できず、agent が無音で停止する事故が
|
|
74
|
-
* 発生する (2026-05-19, シェル PID 4683 で install したまま放置されて
|
|
75
|
-
* 検知が遅れた)。
|
|
76
|
-
*
|
|
77
|
-
* 優先順:
|
|
78
|
-
* 1. fnm default alias `~/.local/share/fnm/aliases/default/bin/hub-agent`
|
|
79
|
-
* が存在すればそれを返す (fnm 利用者にとって最も安定)
|
|
80
|
-
* 2. whichPath が `/fnm_multishells/` を含むなら realpath で
|
|
81
|
-
* `~/.local/share/fnm/node-versions/vX.Y.Z/.../bin/hub-agent.mjs`
|
|
82
|
-
* に展開 (node アップグレード前提でも install-service 再実行で済む)
|
|
83
|
-
* 3. それ以外 (brew / nvm / 通常 PATH) は whichPath をそのまま返す
|
|
84
|
-
*/
|
|
85
|
-
function normalizeBinPath(whichPath, opts = {}) {
|
|
86
|
-
const home = opts.home ?? os.homedir()
|
|
87
|
-
const fileExists = opts.fileExists ?? existsSync
|
|
88
|
-
const resolveReal = opts.realpath ?? realpathSync
|
|
89
|
-
|
|
90
|
-
const fnmDefaultBin = path.join(
|
|
91
|
-
home,
|
|
92
|
-
".local",
|
|
93
|
-
"share",
|
|
94
|
-
"fnm",
|
|
95
|
-
"aliases",
|
|
96
|
-
"default",
|
|
97
|
-
"bin",
|
|
98
|
-
"hub-agent",
|
|
99
|
-
)
|
|
100
|
-
if (fileExists(fnmDefaultBin)) return fnmDefaultBin
|
|
101
|
-
|
|
102
|
-
if (!whichPath) return null
|
|
103
|
-
if (whichPath.includes("/fnm_multishells/")) {
|
|
104
|
-
try {
|
|
105
|
-
return resolveReal(whichPath)
|
|
106
|
-
} catch {
|
|
107
|
-
// realpath 失敗時は whichPath をそのまま返す (既存挙動の保持)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return whichPath
|
|
111
|
-
}
|
|
112
|
-
|
|
113
66
|
/** インストール先の hub-agent CLI のフルパスを返す。`hub-agent` が PATH にある前提。 */
|
|
114
67
|
function detectHubAgentBin() {
|
|
115
68
|
const r = spawnSync("/usr/bin/which", ["hub-agent"], { encoding: "utf-8" })
|
|
116
|
-
|
|
117
|
-
const normalized = normalizeBinPath(whichPath)
|
|
118
|
-
if (normalized) return normalized
|
|
69
|
+
if (r.status === 0 && r.stdout.trim()) return r.stdout.trim()
|
|
119
70
|
// fallback: node $repo/bin/hub-agent.mjs
|
|
120
71
|
return path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "bin", "hub-agent.mjs")
|
|
121
72
|
}
|
|
@@ -228,7 +179,6 @@ export async function uninstallService() {
|
|
|
228
179
|
export const _internal = {
|
|
229
180
|
expandTemplate,
|
|
230
181
|
detectHubAgentBin,
|
|
231
|
-
normalizeBinPath,
|
|
232
182
|
macPlistPath,
|
|
233
183
|
linuxUnitPath,
|
|
234
184
|
repoTemplatesDir,
|
package/src/tmux.mjs
CHANGED
|
@@ -180,23 +180,59 @@ async function buildWorktreeParentMap() {
|
|
|
180
180
|
}
|
|
181
181
|
return out
|
|
182
182
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
|
193
|
-
*
|
|
229
|
+
* claudeCmd の各セグメント末尾に initialPrompt を shell-safe quoting で
|
|
230
|
+
* 末尾引数として注入する。
|
|
194
231
|
*
|
|
195
|
-
* `
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
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
|
|
244
|
+
return claudeCmd
|
|
245
|
+
.split("||")
|
|
246
|
+
.map((part) => `${part.trim()} ${quoted}`)
|
|
247
|
+
.join(" || ")
|
|
209
248
|
}
|
|
210
249
|
|
|
211
250
|
/**
|