@cocorograph/hub-agent 0.7.60 → 0.7.61

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/usage.mjs +64 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocorograph/hub-agent",
3
- "version": "0.7.60",
3
+ "version": "0.7.61",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
package/src/usage.mjs CHANGED
@@ -1284,14 +1284,67 @@ function codexSessionsDir() {
1284
1284
  // (Claude の readOfficial の bucketize と同じ約束事)。
1285
1285
  const CODEX_DEFAULT_LIMIT = 100
1286
1286
 
1287
- /** threadId から rollout jsonl の絶対パスを検索する(日付シャード配下、直近 N 日のみ走査)。
1288
- * 7d 窓 + バッファで 9 日分を既定にする(月またぎでも大抵 1〜2 ディレクトリの readdir で済む)。
1287
+ // threadId → rollout 絶対パスのメモ (2026-07-15)。rollout ファイルは threadId ごとに
1288
+ // 不変(作成日ディレクトリに置かれ、以降はそのファイルへ追記され続ける)ため、一度解決
1289
+ // できたら永続的にキャッシュしてよい。フォールバック全走査 (下記) のコストを usage.request
1290
+ // ポーリング (既定 60s) のたびに払わないための保険。存在確認 (stat) で削除検知して捨てる。
1291
+ const _codexRolloutPathMemo = new Map() // threadId → fp
1292
+
1293
+ export function _resetCodexRolloutPathMemo() {
1294
+ _codexRolloutPathMemo.clear()
1295
+ }
1296
+
1297
+ /** base 配下の全 Y/M/D ディレクトリを新しい順に走査して threadId の rollout を探す。
1298
+ * 直近 lookbackDays の高速パスから漏れた「窓外の古い(=9 日以上前に作成された)長寿命
1299
+ * スレッド」を拾うためのフォールバック。resume で復帰した古いスレッドが該当する。
1300
+ * @returns {Promise<string|null>}
1301
+ */
1302
+ async function scanAllDatesForCodexRollout(base, threadId) {
1303
+ const suffix = `-${threadId}.jsonl`
1304
+ const years = await fs.readdir(base).catch(() => [])
1305
+ // 新しい年→月→日から辿ると、resume される(=比較的最近使われた)スレッドに早く当たる。
1306
+ for (const y of years.filter((s) => /^\d{4}$/.test(s)).sort().reverse()) {
1307
+ const yDir = path.join(base, y)
1308
+ const months = await fs.readdir(yDir).catch(() => [])
1309
+ for (const m of months.sort().reverse()) {
1310
+ const mDir = path.join(yDir, m)
1311
+ const days = await fs.readdir(mDir).catch(() => [])
1312
+ for (const d of days.sort().reverse()) {
1313
+ const dDir = path.join(mDir, d)
1314
+ const files = await fs.readdir(dDir).catch(() => [])
1315
+ const match = files.find((f) => f.endsWith(suffix))
1316
+ if (match) return path.join(dDir, match)
1317
+ }
1318
+ }
1319
+ }
1320
+ return null
1321
+ }
1322
+
1323
+ /** threadId から rollout jsonl の絶対パスを検索する(日付シャード配下)。
1324
+ * 高速パス = 直近 `lookbackDays` 日の Y/M/D を走査(月またぎでも 1〜2 readdir で済む)。
1325
+ * ⚠️ rollout ファイルは「スレッド作成日」のディレクトリに置かれ、以降そのファイルへ
1326
+ * 追記され続ける。そのため 9 日以上前に作成されたスレッドを resume で復帰させると、
1327
+ * 作成日ディレクトリが高速パスの窓外にあり見つからず、context ドーナツ・5h/7d が
1328
+ * 表示できなくなる(2026-07-15 の resume 修正で古いスレッド復帰が正しく動くようになり
1329
+ * 露出)。高速パスで見つからなければ全日付フォールバック走査に落とし、結果を threadId
1330
+ * でメモ化する。
1289
1331
  * `session-mirror.mjs`(Stream C ターミナルミラー)からも再利用するため export する。
1290
1332
  * @returns {Promise<string|null>}
1291
1333
  */
1292
1334
  export async function findCodexRolloutFile(threadId, { lookbackDays = 9, now = Date.now() } = {}) {
1293
1335
  if (!threadId) return null
1336
+ // メモ命中: ファイルが今も存在するなら即返す(消えていれば捨てて再探索)。
1337
+ const memoed = _codexRolloutPathMemo.get(threadId)
1338
+ if (memoed) {
1339
+ try {
1340
+ await fs.stat(memoed)
1341
+ return memoed
1342
+ } catch {
1343
+ _codexRolloutPathMemo.delete(threadId)
1344
+ }
1345
+ }
1294
1346
  const base = codexSessionsDir()
1347
+ // 高速パス: 直近 lookbackDays の日付シャードのみ。
1295
1348
  for (let i = 0; i < lookbackDays; i++) {
1296
1349
  const d = new Date(now - i * 24 * 60 * 60 * 1000)
1297
1350
  const y = String(d.getFullYear())
@@ -1301,9 +1354,16 @@ export async function findCodexRolloutFile(threadId, { lookbackDays = 9, now = D
1301
1354
  const files = await fs.readdir(dir).catch(() => null)
1302
1355
  if (!files) continue
1303
1356
  const match = files.find((f) => f.endsWith(`-${threadId}.jsonl`))
1304
- if (match) return path.join(dir, match)
1357
+ if (match) {
1358
+ const fp = path.join(dir, match)
1359
+ _codexRolloutPathMemo.set(threadId, fp)
1360
+ return fp
1361
+ }
1305
1362
  }
1306
- return null
1363
+ // フォールバック: 窓外の古い(長寿命 resume)スレッドを全日付走査で拾う。
1364
+ const scanned = await scanAllDatesForCodexRollout(base, threadId)
1365
+ if (scanned) _codexRolloutPathMemo.set(threadId, scanned)
1366
+ return scanned
1307
1367
  }
1308
1368
 
1309
1369
  /** P5(perf): rollout ファイルの token_count 抽出結果を mtime+size でメモ化。 */