@cocorograph/hub-agent 0.5.1 → 0.5.3
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 +8 -0
- package/package.json +1 -1
- package/src/tmux.mjs +54 -1
package/bin/hub-agent.mjs
CHANGED
|
@@ -17,6 +17,14 @@ import path from "node:path"
|
|
|
17
17
|
|
|
18
18
|
import { Command } from "commander"
|
|
19
19
|
|
|
20
|
+
// launchd / systemd --user 等は LANG / LC_ALL を継承しないため、tmux が C ロケールで
|
|
21
|
+
// `-F` の format string に含まれる tab (0x09) を `_` に変換して出力する不具合がある
|
|
22
|
+
// (`D00000_cockpit\t1\t...` → `D00000_cockpit_1_...`)。これにより listSessions の
|
|
23
|
+
// パースが完全に壊れ、session 名が全 6 フィールドを `_` 連結した文字列になる。
|
|
24
|
+
// 子プロセスに継承される env を UTF-8 locale で固定する (既に何かしら設定済みなら尊重)。
|
|
25
|
+
if (!process.env.LANG) process.env.LANG = "C.UTF-8"
|
|
26
|
+
if (!process.env.LC_ALL) process.env.LC_ALL = "C.UTF-8"
|
|
27
|
+
|
|
20
28
|
import { hasConfig, readConfig } from "../src/config.mjs"
|
|
21
29
|
import { enroll } from "../src/enroll.mjs"
|
|
22
30
|
import { syncBundle } from "../src/hub-bundle.mjs"
|
package/package.json
CHANGED
package/src/tmux.mjs
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
* 仕様書: ナレッジ/インフラ/cockpit-hub-hosted-integration-spec (id=6080)
|
|
15
15
|
*/
|
|
16
16
|
import { execFile } from "node:child_process"
|
|
17
|
+
import fs from "node:fs/promises"
|
|
18
|
+
import os from "node:os"
|
|
19
|
+
import path from "node:path"
|
|
17
20
|
import { promisify } from "node:util"
|
|
18
21
|
|
|
19
22
|
import { detectSessionState } from "./state.mjs"
|
|
@@ -21,6 +24,53 @@ import { detectSessionState } from "./state.mjs"
|
|
|
21
24
|
const execFileP = promisify(execFile)
|
|
22
25
|
|
|
23
26
|
const DEFAULT_TMUX_BIN = "tmux"
|
|
27
|
+
|
|
28
|
+
const HUB_PROJECTS_BASE =
|
|
29
|
+
process.env.HUB_PROJECTS_BASE || path.join(os.homedir(), "hub", "projects")
|
|
30
|
+
|
|
31
|
+
function sanitizeTmuxName(s) {
|
|
32
|
+
return s.replace(/[.:]/g, "-")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* `~/hub/projects/<project>/.claude/worktrees/<wt>` を走査し、
|
|
37
|
+
* worktree_session_name → parent_session_name の Map を返す。
|
|
38
|
+
*
|
|
39
|
+
* - 各 worktree dir は `.git` ファイル/ディレクトリの存在で git worktree と判定
|
|
40
|
+
* - session 名はディレクトリ名を sanitize したもの (tmux session 名命名規則)
|
|
41
|
+
*
|
|
42
|
+
* 移植元: D00000_cockpit/webapp/lib/workspaces.ts (findWorktrees)
|
|
43
|
+
*/
|
|
44
|
+
async function buildWorktreeParentMap() {
|
|
45
|
+
const out = new Map()
|
|
46
|
+
let projects
|
|
47
|
+
try {
|
|
48
|
+
projects = await fs.readdir(HUB_PROJECTS_BASE, { withFileTypes: true })
|
|
49
|
+
} catch {
|
|
50
|
+
return out
|
|
51
|
+
}
|
|
52
|
+
for (const p of projects) {
|
|
53
|
+
if (!p.isDirectory() || p.name.startsWith(".")) continue
|
|
54
|
+
const wtBase = path.join(HUB_PROJECTS_BASE, p.name, ".claude", "worktrees")
|
|
55
|
+
let wts
|
|
56
|
+
try {
|
|
57
|
+
wts = await fs.readdir(wtBase, { withFileTypes: true })
|
|
58
|
+
} catch {
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
for (const wt of wts) {
|
|
62
|
+
if (!wt.isDirectory() || wt.name.startsWith(".")) continue
|
|
63
|
+
try {
|
|
64
|
+
// .git が存在する = 正規 git worktree
|
|
65
|
+
await fs.stat(path.join(wtBase, wt.name, ".git"))
|
|
66
|
+
} catch {
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
out.set(sanitizeTmuxName(wt.name), sanitizeTmuxName(p.name))
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return out
|
|
73
|
+
}
|
|
24
74
|
const DEFAULT_CLAUDE_CMD =
|
|
25
75
|
process.env.HUB_CLAUDE_CMD ||
|
|
26
76
|
"claude --continue --model claude-opus-4-7 --permission-mode auto || claude --model claude-opus-4-7 --permission-mode auto"
|
|
@@ -109,7 +159,8 @@ export async function listSessions(opts = {}) {
|
|
|
109
159
|
}
|
|
110
160
|
})
|
|
111
161
|
|
|
112
|
-
// state + cwd
|
|
162
|
+
// state + cwd + worktree 親判定を並列付与
|
|
163
|
+
const wtParentMap = await buildWorktreeParentMap()
|
|
113
164
|
return Promise.all(
|
|
114
165
|
base.map(async (s) => {
|
|
115
166
|
const [state, cwd] = await Promise.all([
|
|
@@ -121,6 +172,8 @@ export async function listSessions(opts = {}) {
|
|
|
121
172
|
status: state.status,
|
|
122
173
|
context_pct: state.context_pct,
|
|
123
174
|
cwd,
|
|
175
|
+
// worktree session の場合は親 workspace の session 名を入れる (旧 cockpit webapp 互換)
|
|
176
|
+
parent_session_name: wtParentMap.get(s.name) || null,
|
|
124
177
|
}
|
|
125
178
|
}),
|
|
126
179
|
)
|