@cocorograph/hub-agent 0.8.88 → 0.8.89
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
|
@@ -50,6 +50,29 @@ function idleRank(item) {
|
|
|
50
50
|
return typeof item?.idle_ms === "number" && item.idle_ms >= 0 ? item.idle_ms : Infinity
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* この記述子が、その cwd の表示信号の持ち主 (= 代表会話) か。
|
|
55
|
+
*
|
|
56
|
+
* ⚠️ 畳み込みで信号の持ち主を落とすと、frontend (chatSessionsAdapter.ts の
|
|
57
|
+
* signalBelongsTo) が「別の会話の信号」として捨て、生成中でも turn_active=false の赤に
|
|
58
|
+
* なる。idle_ms は attach 中の端末の活性からしか作られず (claude-stream-bridge.mjs
|
|
59
|
+
* `_describeCandidate`)、ワークスペースを離れると -1 になるので、idle_ms だけで選ぶと
|
|
60
|
+
* 離れた後の生成中の会話がほぼ必ず負ける (2026-09-22 に実測)。
|
|
61
|
+
*
|
|
62
|
+
* 会話 ID が片方でも未確定なら持ち主とは言えないので false (従来の idle_ms 規則に委ねる)。
|
|
63
|
+
* 信号の取得失敗も false。失敗の記録は attachChatSignal が行うので、ここでは黙る。
|
|
64
|
+
*/
|
|
65
|
+
function ownsChatSignal(item, cwd, getChatSignal) {
|
|
66
|
+
const id = conversationIdOf(item)
|
|
67
|
+
if (!id || !cwd) return false
|
|
68
|
+
try {
|
|
69
|
+
const sig = getChatSignal(cwd, item.kind)
|
|
70
|
+
return typeof sig?.sessionId === "string" && sig.sessionId === id
|
|
71
|
+
} catch {
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
53
76
|
/**
|
|
54
77
|
* 記述子の会話 ID。claude は `session_id`、codex は `thread_id` に入る。
|
|
55
78
|
* まだ確定していない会話 (初回入力前の attach 等) は両方とも欠けるので null を返す。
|
|
@@ -107,6 +130,23 @@ export async function buildChatSessionsSnapshot({
|
|
|
107
130
|
const descriptors = describeLiveSessionsUnion(bridges)
|
|
108
131
|
const { targets, errors } = await listChatTargets({ logger })
|
|
109
132
|
const targetByCwd = chatTargetsByCwd(targets)
|
|
133
|
+
// 畳み込みでは同じ (cwd, kind) の信号を何度も引く (所有者判定 ×2 + 付与)。readRowChatState は
|
|
134
|
+
// ライブ会話の走査を伴うので、この同期区間の中だけで結果 (例外も含む) を共有する。
|
|
135
|
+
// ⚠️ await をまたいで使わない。送信直前の付け直しは refreshChatSessionsSignals が行う。
|
|
136
|
+
const signalMemo = new Map()
|
|
137
|
+
const signalFor = (cwd, kind) => {
|
|
138
|
+
const key = `${kind}\u0000${cwd}`
|
|
139
|
+
if (!signalMemo.has(key)) {
|
|
140
|
+
try {
|
|
141
|
+
signalMemo.set(key, { value: getChatSignal(cwd, kind) })
|
|
142
|
+
} catch (error) {
|
|
143
|
+
signalMemo.set(key, { error })
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const hit = signalMemo.get(key)
|
|
147
|
+
if (hit.error) throw hit.error
|
|
148
|
+
return hit.value
|
|
149
|
+
}
|
|
110
150
|
const named = descriptors.map((descriptor) => {
|
|
111
151
|
const item = { ...descriptor }
|
|
112
152
|
const cwd = typeof item.cwd === "string" ? item.cwd : ""
|
|
@@ -122,10 +162,12 @@ export async function buildChatSessionsSnapshot({
|
|
|
122
162
|
// 成立していた (同名 worktree は片方しか起動できなかった)。会話を tmux から切り離すと
|
|
123
163
|
// 両方ライブになれるため、agent 側で畳んで不変条件を維持する。
|
|
124
164
|
//
|
|
125
|
-
// 残す 1
|
|
126
|
-
//
|
|
127
|
-
// ⚠️ 表示シグナルは cwd
|
|
128
|
-
//
|
|
165
|
+
// 残す 1 本は、まず「その cwd の表示信号の持ち主 (代表会話)」、次に「直近に動いた方」
|
|
166
|
+
// (idle_ms が小さい方、同値なら先勝ち)。
|
|
167
|
+
// ⚠️ 表示シグナルは cwd キーで代表会話の値なので、代表でない会話を残すと frontend が
|
|
168
|
+
// 帰属不一致で信号を捨てて赤に落ちる (ownsChatSignal 参照)。同じ cwd に会話が複数
|
|
169
|
+
// 生きるのは通常運用 (warm 境界は cwd あたり K=3) なので、ここが実際の分岐になる。
|
|
170
|
+
// frontend の adaptChatSessions も同名を畳むが、それは畳まない旧 agent 向けの互換経路。
|
|
129
171
|
const live = []
|
|
130
172
|
const indexByName = new Map()
|
|
131
173
|
for (const { item, cwd, sessionName } of named) {
|
|
@@ -137,11 +179,13 @@ export async function buildChatSessionsSnapshot({
|
|
|
137
179
|
const prior = indexByName.get(sessionName)
|
|
138
180
|
if (prior === undefined) {
|
|
139
181
|
indexByName.set(sessionName, live.length)
|
|
140
|
-
live.push(attachChatSignal(item, cwd,
|
|
182
|
+
live.push(attachChatSignal(item, cwd, signalFor, logger))
|
|
141
183
|
continue
|
|
142
184
|
}
|
|
143
185
|
const kept = live[prior]
|
|
144
|
-
const
|
|
186
|
+
const itemOwns = ownsChatSignal(item, cwd, signalFor)
|
|
187
|
+
const keptOwns = ownsChatSignal(kept, kept.cwd, signalFor)
|
|
188
|
+
const replace = itemOwns !== keptOwns ? itemOwns : idleRank(item) < idleRank(kept)
|
|
145
189
|
const winner = replace ? item : kept
|
|
146
190
|
const loser = replace ? kept : item
|
|
147
191
|
// ⚠️ cwd だけでは衝突の正体が分からない。想定していたのは「別 workspace 配下の同名
|
|
@@ -163,7 +207,7 @@ export async function buildChatSessionsSnapshot({
|
|
|
163
207
|
},
|
|
164
208
|
"chat.sessions: session name collision (folded)",
|
|
165
209
|
)
|
|
166
|
-
if (replace) live[prior] = attachChatSignal(item, cwd,
|
|
210
|
+
if (replace) live[prior] = attachChatSignal(item, cwd, signalFor, logger)
|
|
167
211
|
}
|
|
168
212
|
// chat-targets.mjs の fs.readdir(hubProjectsBase()) と同じ絶対パスに揃える(~ は展開しない)。
|
|
169
213
|
const projects_base = path.resolve(hubProjectsBase())
|