@cocorograph/hub-agent 0.8.78 → 0.8.80
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
|
@@ -48,6 +48,29 @@ function idleRank(item) {
|
|
|
48
48
|
return typeof item?.idle_ms === "number" && item.idle_ms >= 0 ? item.idle_ms : Infinity
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* 記述子の会話 ID。claude は `session_id`、codex は `thread_id` に入る。
|
|
53
|
+
* まだ確定していない会話 (初回入力前の attach 等) は両方とも欠けるので null を返す。
|
|
54
|
+
*/
|
|
55
|
+
function conversationIdOf(item) {
|
|
56
|
+
if (typeof item?.session_id === "string" && item.session_id) return item.session_id
|
|
57
|
+
if (typeof item?.thread_id === "string" && item.thread_id) return item.thread_id
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 記述子が示す接続状態。⚠️ **2 つの CLI で持ち方が逆**で、claude は `attached`
|
|
63
|
+
* (claude-stream-bridge.mjs `_describeCandidate`)、codex は `detached`
|
|
64
|
+
* (codex-stream-bridge.mjs `_describe` = `!!session.detachRequested`) を返す。
|
|
65
|
+
* 片方だけを見ると、もう片方は接続中でも常に「非接続」として記録され、旧インスタンスの
|
|
66
|
+
* 残留調査を誤らせる。どちらも無ければ **false ではなく null** (不明) を返す。
|
|
67
|
+
*/
|
|
68
|
+
function attachedOf(item) {
|
|
69
|
+
if (typeof item?.attached === "boolean") return item.attached
|
|
70
|
+
if (typeof item?.detached === "boolean") return !item.detached
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
51
74
|
/** ローカル検出した宛先と、毎回取得するライブ会話を返す。 */
|
|
52
75
|
export async function buildChatSessionsSnapshot({
|
|
53
76
|
bridges,
|
|
@@ -96,11 +119,24 @@ export async function buildChatSessionsSnapshot({
|
|
|
96
119
|
}
|
|
97
120
|
const kept = live[prior]
|
|
98
121
|
const replace = idleRank(item) < idleRank(kept)
|
|
122
|
+
const winner = replace ? item : kept
|
|
123
|
+
const loser = replace ? kept : item
|
|
124
|
+
// ⚠️ cwd だけでは衝突の正体が分からない。想定していたのは「別 workspace 配下の同名
|
|
125
|
+
// worktree」(cwd が違う) だが、実際に観測されたのは **cwd が同じ** 衝突で、これは
|
|
126
|
+
// 同名 worktree では説明できない。会話 ID を出さないと「同じ会話が二重に生きている」
|
|
127
|
+
// (bridge に旧インスタンスが残る) のか「同じフォルダで別会話が 2 本走っている」のかを
|
|
128
|
+
// 区別できず、2026-09-17 に実際にログだけでは判別できずに詰まった。attached は
|
|
129
|
+
// 「bridge から見て今ブラウザが繋がっているか」で、旧インスタンスの残骸は false に
|
|
130
|
+
// なりやすい (ブラウザ側の実際の生存を保証する値ではない)。
|
|
99
131
|
logger?.warn?.(
|
|
100
132
|
{
|
|
101
133
|
session_name: sessionName,
|
|
102
134
|
kept_cwd: replace ? cwd : kept.cwd,
|
|
103
135
|
dropped_cwd: replace ? kept.cwd : cwd,
|
|
136
|
+
kept_conversation_id: conversationIdOf(winner),
|
|
137
|
+
dropped_conversation_id: conversationIdOf(loser),
|
|
138
|
+
kept_attached: attachedOf(winner),
|
|
139
|
+
dropped_attached: attachedOf(loser),
|
|
104
140
|
},
|
|
105
141
|
"chat.sessions: session name collision (folded)",
|
|
106
142
|
)
|
|
@@ -2870,12 +2870,20 @@ export class ClaudeStreamBridge extends EventEmitter {
|
|
|
2870
2870
|
// 新規 query を起動せずそのセッションへ再アタッチする。これにより端末スリープ/再接続でも
|
|
2871
2871
|
// ターンは落ちず、以降のイベントは新しい stream_id 経由でライブに流れる
|
|
2872
2872
|
// (= 通常の生成中表示と同じ)。再接続前の確定分は browser の jsonl hydrate で復元する。
|
|
2873
|
+
// ⚠️ 新規生成へ落ちた理由を 1 語で残す。ここを通ると同じ cwd に会話オブジェクトが 1 つ増え、
|
|
2874
|
+
// 古い方は close されないので一覧に残り続ける (chat.sessions の名前衝突になる)。理由が
|
|
2875
|
+
// `index_miss` なら「browser が持っている resume id が索引に無い」= /clear・compaction での
|
|
2876
|
+
// session_id 回転後に古い id で再接続した疑い (下の onEvent で旧キーを delete しているため)、
|
|
2877
|
+
// `not_reattachable` なら「索引にはあるが常駐 query が終了済み」= 古い session の掃除漏れで、
|
|
2878
|
+
// 直し方が正反対になる。2026-09-17 にログからこの 2 つを区別できず判別に詰まった。
|
|
2879
|
+
let reattachSkip = "no_resume"
|
|
2873
2880
|
if (resumeSessionId) {
|
|
2874
2881
|
// ⚠️ cwd 境界 (T06271): _liveBySession は **複合キー (cwd+session_id)** なので、別 cwd の
|
|
2875
2882
|
// 同一 session_id は別エントリに分離される。よって cwd=A の resume を cwd=B から
|
|
2876
2883
|
// attach しても引き当たらず、越境 reattach が原理的に起きない (session_id は本来
|
|
2877
2884
|
// ユニークだが異常系でも cwd を跨がない)。
|
|
2878
2885
|
const live = this._liveBySession.get(this._liveKeyFor(scwd, resumeSessionId))
|
|
2886
|
+
reattachSkip = live ? "not_reattachable" : "index_miss"
|
|
2879
2887
|
// R1(b): !_closed だけでなく「常駐子プロセスが実生存しているか」も確認する。子が死んで
|
|
2880
2888
|
// いれば reattach せず下の新規 spawn 経路へフォールバックし、死んだ session を引き当てない。
|
|
2881
2889
|
if (live && live._isReattachable()) {
|
|
@@ -3026,7 +3034,16 @@ export class ClaudeStreamBridge extends EventEmitter {
|
|
|
3026
3034
|
this._liveBySession.set(key, session)
|
|
3027
3035
|
}
|
|
3028
3036
|
this.logger?.info(
|
|
3029
|
-
{
|
|
3037
|
+
{
|
|
3038
|
+
stream_id,
|
|
3039
|
+
cwd: session.cwd,
|
|
3040
|
+
model: session.model,
|
|
3041
|
+
// ⚠️ ブール値ではなく id そのものを出す。reattach 側 (上) は id を出しているので、
|
|
3042
|
+
// 同じ値で突き合わせて「その id が過去に live だったか」を追える。ブールだけだと
|
|
3043
|
+
// 「古い id で来た」と「知らない id で来た」を区別できない。
|
|
3044
|
+
resume: resumeSessionId || null,
|
|
3045
|
+
reattach_skip: reattachSkip,
|
|
3046
|
+
},
|
|
3030
3047
|
"claude stream attached",
|
|
3031
3048
|
)
|
|
3032
3049
|
// per-message モデルでは attach 時点では query() を起動しない (即 ready)。
|