@cocorograph/hub-agent 0.8.101 → 0.8.102

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.8.101",
3
+ "version": "0.8.102",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの Claude / Codex チャットと tmux セッションを中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -128,6 +128,15 @@ export async function buildChatSessionsSnapshot({
128
128
  getChatSignal = defaultGetChatSignal,
129
129
  }) {
130
130
  const descriptors = describeLiveSessionsUnion(bridges)
131
+ // 行名で畳む前に、完全一致の cwd・CLI ごとに会話を数える。
132
+ // ID 未確定の記述子は同一会話と判断できないため、それぞれ 1 本とする。
133
+ const conversationsByCwd = new Map()
134
+ for (const descriptor of descriptors) {
135
+ if (!conversationsByCwd.has(descriptor.cwd)) conversationsByCwd.set(descriptor.cwd, new Map())
136
+ const byKind = conversationsByCwd.get(descriptor.cwd)
137
+ if (!byKind.has(descriptor.kind)) byKind.set(descriptor.kind, new Set())
138
+ byKind.get(descriptor.kind).add(conversationIdOf(descriptor) ?? Symbol())
139
+ }
131
140
  const { targets, errors } = await listChatTargets({ logger })
132
141
  const targetByCwd = chatTargetsByCwd(targets)
133
142
  // 畳み込みでは同じ (cwd, kind) の信号を何度も引く (所有者判定 ×2 + 付与)。readRowChatState は
@@ -148,7 +157,10 @@ export async function buildChatSessionsSnapshot({
148
157
  return hit.value
149
158
  }
150
159
  const named = descriptors.map((descriptor) => {
151
- const item = { ...descriptor }
160
+ const item = {
161
+ ...descriptor,
162
+ live_count: conversationsByCwd.get(descriptor.cwd).get(descriptor.kind).size,
163
+ }
152
164
  const cwd = typeof item.cwd === "string" ? item.cwd : ""
153
165
  return { item, cwd, sessionName: chatRowSessionName(cwd, item.kind, targetByCwd) }
154
166
  })
@@ -195,7 +207,7 @@ export async function buildChatSessionsSnapshot({
195
207
  // 区別できず、2026-09-17 に実際にログだけでは判別できずに詰まった。attached は
196
208
  // 「bridge から見て今ブラウザが繋がっているか」で、旧インスタンスの残骸は false に
197
209
  // なりやすい (ブラウザ側の実際の生存を保証する値ではない)。
198
- logger?.warn?.(
210
+ logger?.[winner.cwd === loser.cwd ? "debug" : "warn"]?.(
199
211
  {
200
212
  session_name: sessionName,
201
213
  kept_cwd: replace ? cwd : kept.cwd,
@@ -3501,7 +3501,7 @@ export class ClaudeStreamBridge extends EventEmitter {
3501
3501
  *
3502
3502
  * ⚠️ ここに出るのは SDK チャットセッションのみ (tmux の TUI claude は対象外)。
3503
3503
  * @param {string} [cwd] 指定時はその cwd のみ
3504
- * @returns {Array<{kind: string, cwd: string, session_id: string|null, idle_ms: number}>}
3504
+ * @returns {Array<{kind: string, cwd: string, session_id: string|null, busy: boolean, idle_ms: number}>}
3505
3505
  */
3506
3506
  describeLiveSessions(cwd) {
3507
3507
  return this._liveCandidatesForCwd(cwd, false).map(({ session, attached }) =>
@@ -3578,6 +3578,7 @@ export class ClaudeStreamBridge extends EventEmitter {
3578
3578
  kind: "claude",
3579
3579
  cwd: session.cwd,
3580
3580
  session_id: session.sessionId || null,
3581
+ busy: session._busy === true,
3581
3582
  idle_ms: at ? now - at : -1,
3582
3583
  attached,
3583
3584
  }
package/src/main.mjs CHANGED
@@ -4191,13 +4191,32 @@ export async function dispatch(msg, ctx) {
4191
4191
  projectsRoot,
4192
4192
  logger: ctx.logger,
4193
4193
  })
4194
+ // 履歴一覧に、同じ cwd の bridge が持つ現在の live / busy を併合する。
4195
+ // ⚠️ live の列挙に失敗しても取得済みの履歴は返す。その場合は live / busy を付けない
4196
+ // (= 不明。false にすると「常駐していない」と断定してしまう)。途中まで作った Map は使わない。
4197
+ let liveBySession = new Map()
4198
+ try {
4199
+ const next = new Map()
4200
+ for (const s of ctx.claudeBridge?.describeLiveSessions?.(cwd) ?? []) {
4201
+ if (s.session_id) next.set(s.session_id, { live: true, busy: !!s.busy })
4202
+ }
4203
+ liveBySession = next
4204
+ } catch (err) {
4205
+ liveBySession = null
4206
+ ctx.logger?.warn?.({ err: String(err?.message || err), cwd }, "claude.sessions: live 列挙に失敗 (live/busy を付けずに履歴を返す)")
4207
+ }
4208
+ const withLive = (result.sessions || []).map((s) => (liveBySession ? {
4209
+ ...s,
4210
+ live: liveBySession.get(s.session_id)?.live === true,
4211
+ busy: liveBySession.get(s.session_id)?.busy === true,
4212
+ } : s))
4194
4213
  ctx.client.send({
4195
4214
  type: "claude.sessions.response",
4196
4215
  stream_id,
4197
4216
  cwd,
4198
4217
  epoch,
4199
4218
  profile_id: profileId,
4200
- sessions: result.sessions || [],
4219
+ sessions: withLive,
4201
4220
  error: result.error,
4202
4221
  })
4203
4222
  return