@cocorograph/hub-agent 0.5.2 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tmux.mjs +54 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocorograph/hub-agent",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
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
  )