@cocorograph/hub-agent 0.8.81 → 0.8.83

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.
@@ -1,3 +1,4 @@
1
+ import { recordConversationFocus } from "./chat-signals.mjs"
1
2
  /**
2
3
  * Codex 版チャットブリッジ (Cockpit Codex 両対応 item#3, Slice 2)。
3
4
  *
@@ -1109,6 +1110,7 @@ export class CodexStreamBridge extends EventEmitter {
1109
1110
  settleReady = resolve
1110
1111
  })
1111
1112
  this.sessions.set(stream_id, session)
1113
+ recordConversationFocus(session.cwd, "codex", this.sessions.get(stream_id), this)
1112
1114
  this._wireClient(session)
1113
1115
  const resuming = !!resumeThreadId
1114
1116
  // #3: initialize と thread op を分離し、thread op に到達したかを記録する。initialize / config
@@ -1297,9 +1299,8 @@ export class CodexStreamBridge extends EventEmitter {
1297
1299
  turnActive: turnState.turnActive,
1298
1300
  }
1299
1301
  } catch (err) {
1300
- this.sessions.delete(stream_id)
1301
- this._settleAllPendingApprovals(session, { respond: false })
1302
- this._disposeSessionHistory(session)
1302
+ // 代表実体も終了扱いにし、exit 転送と producer usage の解放を既存経路へ通す。
1303
+ this._onClientExit(session, { code: 1, signal: null })
1303
1304
  client.stop()
1304
1305
  settleReady(null) // reattach 待ちへ「失敗」を通知 (reject しない)
1305
1306
  // 失敗も 1 行に落とす。**_classifyAttachFailure より前**に出す (分類は rollout の disk 走査を
@@ -1850,6 +1851,7 @@ export class CodexStreamBridge extends EventEmitter {
1850
1851
  session.streamIds.add(newStreamId)
1851
1852
  session.lastActivityByStream.set(newStreamId, now)
1852
1853
  this.sessions.set(newStreamId, session)
1854
+ recordConversationFocus(session.cwd, "codex", this.sessions.get(newStreamId), this)
1853
1855
  session.stream_id = newStreamId
1854
1856
  session.detachRequested = false
1855
1857
  session.browserDetachedAtMs = null
@@ -1989,6 +1991,14 @@ export class CodexStreamBridge extends EventEmitter {
1989
1991
  return 1
1990
1992
  }
1991
1993
 
1994
+ /** 指定 cwd の live 会話をすべて停止する。Map の変更前に対象を確定する。 */
1995
+ stopSessionsForCwd({ cwd, reason }) {
1996
+ const sessions = this._liveSessions().filter((session) => session.cwd === cwd)
1997
+ const now = Date.now()
1998
+ for (const session of sessions) this._reapSession(session, now, reason)
1999
+ return sessions.length
2000
+ }
2001
+
1992
2002
  async operateSession(options) {
1993
2003
  const invalid = validateCodexSessionOperation(options)
1994
2004
  if (invalid) return { ok: false, error: invalid }
@@ -3678,6 +3688,7 @@ export class CodexStreamBridge extends EventEmitter {
3678
3688
  this.logger?.info?.(payload, "codex app-server exited")
3679
3689
  }
3680
3690
  this.emit("exit", {
3691
+ cwd: session.cwd,
3681
3692
  stream_id: session.stream_id,
3682
3693
  producer_seq: session.seq,
3683
3694
  thread_id: session.threadId,
@@ -4200,12 +4211,16 @@ export class CodexStreamBridge extends EventEmitter {
4200
4211
  * @returns {object|null} 権威セッション(無ければ null)
4201
4212
  */
4202
4213
  liveSessionForCwd(cwd) {
4214
+ return this._selectLiveSession(cwd, () => true)
4215
+ }
4216
+
4217
+ _selectLiveSession(cwd, matches) {
4203
4218
  if (!cwd) return null
4204
4219
  let best = null
4205
4220
  let bestAt = -1
4206
4221
  // 案B: distinct session で走査(多対一 Map の重複を除く)。
4207
4222
  for (const session of this._liveSessions()) {
4208
- if (session.cwd !== cwd || !session.threadId) continue
4223
+ if (session.cwd !== cwd || !session.threadId || session.threadId.startsWith("fresh:") || !matches(session)) continue
4209
4224
  const at = session.lastTurnAtMs ?? session.lastActivityMs ?? 0
4210
4225
  if (at >= bestAt) {
4211
4226
  bestAt = at
@@ -4741,6 +4756,7 @@ export class CodexStreamBridge extends EventEmitter {
4741
4756
  "codex session reaped",
4742
4757
  )
4743
4758
  this.emit("exit", {
4759
+ cwd: session.cwd,
4744
4760
  stream_id: session.stream_id,
4745
4761
  producer_seq: session.seq,
4746
4762
  thread_id: session.threadId,
@@ -4947,6 +4963,7 @@ export class CodexStreamBridge extends EventEmitter {
4947
4963
  // (forgetCodexLiveTokenUsage) を必ず通す。これが無いと profile 切替ごとに producer Map が
4948
4964
  // リークする。dead は既に上で立てているので二重 emit にはならない。
4949
4965
  this.emit("exit", {
4966
+ cwd: session.cwd,
4950
4967
  stream_id: session.stream_id,
4951
4968
  producer_seq: session.seq,
4952
4969
  thread_id: session.threadId,
@@ -4958,6 +4975,15 @@ export class CodexStreamBridge extends EventEmitter {
4958
4975
  this.sessions.clear()
4959
4976
  }
4960
4977
 
4978
+ liveSessionsForCwd(cwd) {
4979
+ return this._liveSessions().filter((session) => session.cwd === cwd)
4980
+ }
4981
+
4982
+ getThreadId(stream_id) {
4983
+ const session = this.sessions.get(stream_id)
4984
+ return session && !session.dead ? session.threadId ?? null : null
4985
+ }
4986
+
4961
4987
  getCwd(stream_id) {
4962
4988
  const session = this.sessions.get(stream_id)
4963
4989
  if (!session || session.dead) return null
@@ -189,6 +189,11 @@ export class DispatchScheduler {
189
189
  if (type === "usage.request") return "usage"
190
190
 
191
191
  if (type === "chat.session.stop") {
192
+ if (msg.scope === "cwd") {
193
+ const cwd = typeof msg.cwd === "string" ? msg.cwd.trim() : ""
194
+ return cwd && (msg.cli_kind === "claude" || msg.cli_kind === "codex")
195
+ ? `${msg.cli_kind}:cwd:${cwd}` : "control"
196
+ }
192
197
  if (msg.cli_kind === "claude") {
193
198
  const cwd = typeof msg.cwd === "string" ? msg.cwd.trim() : ""
194
199
  return cwd ? `claude:cwd:${cwd}` : "control"