@cocorograph/hub-agent 0.6.52 → 0.6.53
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/claude-history.mjs +31 -3
- package/src/main.mjs +11 -2
package/package.json
CHANGED
package/src/claude-history.mjs
CHANGED
|
@@ -256,22 +256,50 @@ export async function listSessions({ cwd, projectsRoot, limit = 30, logger }) {
|
|
|
256
256
|
* - `viewingSessionId`: フロントが今表示している (閲覧ハートビートが運ぶ) session_id。
|
|
257
257
|
* - `newestSessionId`: cwd 配下で実際に最新 (mtime 降順先頭) の session_id。
|
|
258
258
|
* - `lastNotifiedNewId`: 同一 new への多重通知を防ぐため、最後に通知した new_session_id。
|
|
259
|
+
* - `lastNotifiedAt` / `now`: 再通知スロットリング用のタイムスタンプ (ms)。両方与えられた
|
|
260
|
+
* ときだけ時間判定が効く (無ければ従来どおり「同一 new は一度きり」)。
|
|
261
|
+
* - `reNotifyMs`: 同一 new でも再通知を許す最小間隔 (既定 4000ms)。
|
|
262
|
+
*
|
|
263
|
+
* 回転とみなす条件:
|
|
264
|
+
* 最新が存在し、閲覧中 id と異なり、かつ次のいずれか:
|
|
265
|
+
* (a) 最後に通知した new と異なる (= さらに別 new へ回転した) → 即通知。
|
|
266
|
+
* (b) 最後に通知した new と同じだが、`reNotifyMs` 以上経過している → **再通知**。
|
|
267
|
+
*
|
|
268
|
+
* (b) が肝。回転通知は one-shot broadcast で、多タブ・再接続・hidden タブ等で取りこぼすと
|
|
269
|
+
* クライアントが旧 session に**恒久固着**する (ゾンビ閲覧者バグ)。viewer が依然 stale な
|
|
270
|
+
* session_id を報告し続けている (= newest と不一致) という事実は「まだ切り替わっていない」
|
|
271
|
+
* 証拠なので、スロットリングしつつ繰り返し通知して救済する。切り替わった viewer は次の
|
|
272
|
+
* ハートビートで viewingSessionId === newest を報告し、条件 (最新≠閲覧中) を満たさなくなる
|
|
273
|
+
* ため自然に再通知が止まる。`reNotifyMs` をハートビート間隔 (5s) よりやや短くしておくと、
|
|
274
|
+
* 固着している間は毎ハートビートで 1 回ずつ通知が飛ぶ。
|
|
259
275
|
*
|
|
260
|
-
* 回転とみなす条件: 最新が存在し、閲覧中 id と異なり、かつ直近で同じ new を通知済みでない。
|
|
261
276
|
* 注意: 過去セッションを意図的に開いている (= newest 非追従) ビューでは呼び出し側が
|
|
262
277
|
* `follow_newest=false` でこの判定自体をスキップすること (ピン留め閲覧を勝手に最新へ
|
|
263
278
|
* 引きずらないため)。本関数は「追従中ビュー」前提で newest とのズレだけを見る。
|
|
264
279
|
*
|
|
265
|
-
* @param {{viewingSessionId?: string|null, newestSessionId?: string|null, lastNotifiedNewId?: string|null}} args
|
|
280
|
+
* @param {{viewingSessionId?: string|null, newestSessionId?: string|null, lastNotifiedNewId?: string|null, lastNotifiedAt?: number|null, now?: number|null, reNotifyMs?: number}} args
|
|
266
281
|
* @returns {{rotated: boolean, newSessionId?: string}}
|
|
267
282
|
*/
|
|
268
283
|
export function decideSessionRotation({
|
|
269
284
|
viewingSessionId,
|
|
270
285
|
newestSessionId,
|
|
271
286
|
lastNotifiedNewId,
|
|
287
|
+
lastNotifiedAt,
|
|
288
|
+
now,
|
|
289
|
+
reNotifyMs = 4000,
|
|
272
290
|
} = {}) {
|
|
273
291
|
if (!newestSessionId || !viewingSessionId) return { rotated: false }
|
|
274
292
|
if (newestSessionId === viewingSessionId) return { rotated: false }
|
|
275
|
-
if (newestSessionId === lastNotifiedNewId)
|
|
293
|
+
if (newestSessionId === lastNotifiedNewId) {
|
|
294
|
+
// 同一 new。時刻情報が揃っていて再通知間隔を超えていれば、固着救済のため再通知する。
|
|
295
|
+
if (
|
|
296
|
+
typeof now === "number" &&
|
|
297
|
+
typeof lastNotifiedAt === "number" &&
|
|
298
|
+
now - lastNotifiedAt >= reNotifyMs
|
|
299
|
+
) {
|
|
300
|
+
return { rotated: true, newSessionId: newestSessionId }
|
|
301
|
+
}
|
|
302
|
+
return { rotated: false }
|
|
303
|
+
}
|
|
276
304
|
return { rotated: true, newSessionId: newestSessionId }
|
|
277
305
|
}
|
package/src/main.mjs
CHANGED
|
@@ -1148,13 +1148,22 @@ async function dispatch(msg, ctx) {
|
|
|
1148
1148
|
})
|
|
1149
1149
|
const newestId = sessions?.[0]?.session_id || null
|
|
1150
1150
|
const key = viewName || viewCwd
|
|
1151
|
+
const prev = ctx.tuiRotationNotified.get(key)
|
|
1152
|
+
const now = Date.now()
|
|
1151
1153
|
const { rotated, newSessionId } = decideSessionRotation({
|
|
1152
1154
|
viewingSessionId: viewSid,
|
|
1153
1155
|
newestSessionId: newestId,
|
|
1154
|
-
|
|
1156
|
+
// 旧形式 (文字列) と新形式 ({newId, ts}) の両対応。
|
|
1157
|
+
lastNotifiedNewId:
|
|
1158
|
+
prev && typeof prev === "object" ? prev.newId : prev,
|
|
1159
|
+
lastNotifiedAt:
|
|
1160
|
+
prev && typeof prev === "object" ? prev.ts : null,
|
|
1161
|
+
now,
|
|
1155
1162
|
})
|
|
1156
1163
|
if (!rotated) return
|
|
1157
|
-
|
|
1164
|
+
// {newId, ts} で保存し、同一 new への再通知をスロットリングする
|
|
1165
|
+
// (固着した viewer は throttle 間隔ごとに 1 回ずつ救済通知を受ける)。
|
|
1166
|
+
ctx.tuiRotationNotified.set(key, { newId: newSessionId, ts: now })
|
|
1158
1167
|
// 冪等マップも新 id へ更新し、将来の remount bind が旧 id へ
|
|
1159
1168
|
// respawn (claude 再起動) してしまうのを防ぐ。
|
|
1160
1169
|
if (viewName) ctx.tuiReboundSessions.set(viewName, newSessionId)
|