@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.
- package/package.json +1 -1
- package/src/chat-sessions-snapshot.mjs +10 -4
- package/src/chat-signals.mjs +81 -247
- package/src/claude-stream-bridge.mjs +30 -7
- package/src/codex-stream-bridge.mjs +30 -4
- package/src/dispatch-scheduler.mjs +5 -0
- package/src/main.mjs +206 -558
- package/src/tmux.mjs +7 -3
- package/src/usage.mjs +8 -3
|
@@ -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
|
-
|
|
1301
|
-
this.
|
|
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"
|