throughline 0.4.6 → 0.4.7
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/CHANGELOG.md +16 -0
- package/README.ja.md +32 -29
- package/README.md +22 -16
- package/docs/PUBLIC_RELEASE_PLAN.md +4 -3
- package/docs/THROUGHLINE_CLEAR_AUTO_HANDOFF_PLAN.md +1 -1
- package/docs/THROUGHLINE_CODEX_FIRST_ROADMAP.md +3 -3
- package/docs/THROUGHLINE_CODEX_MONITOR_IMPLEMENTATION_PLAN.md +19 -0
- package/docs/THROUGHLINE_CODEX_TRIM_IMPLEMENTATION_PLAN.md +2 -2
- package/docs/THROUGHLINE_CODEX_TRIM_ROLLBACK_FIX_PLAN.md +2 -2
- package/docs/throughline-rollback-context-trim-insight.md +1 -1
- package/package.json +1 -1
- package/src/cli/codex-hook.test.mjs +1 -1
- package/src/codex-auto-refresh.mjs +1 -1
- package/src/codex-auto-refresh.test.mjs +4 -4
- package/src/token-monitor.mjs +97 -11
- package/src/token-monitor.test.mjs +84 -3
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,22 @@ shipped to npm but were not individually tagged on GitHub.
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.4.7] — 2026-05-09
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Codex Stop hook auto-refresh now uses an 80% verified-usage threshold instead
|
|
18
|
+
of 90%, so Throughline can refresh before Codex native auto-compact while
|
|
19
|
+
still staying above the monitor's 70% warning band. Estimate-only usage and
|
|
20
|
+
estimated context windows still do not mutate the thread.
|
|
21
|
+
- Token monitor now discovers active Codex rollout files directly from
|
|
22
|
+
`~/.codex/sessions/**/rollout-*.jsonl`, so current Codex sessions appear even
|
|
23
|
+
when the Codex Stop hook has not written a Throughline state file.
|
|
24
|
+
- Token monitor now displays Codex session ids as the raw first 8 thread-id
|
|
25
|
+
characters (`019e085c`) instead of the confusing prefixed slice (`codex:01`).
|
|
26
|
+
Codex in-flight turns still overlay transient `output_tokens` in the token
|
|
27
|
+
count, but the model column no longer adds a separate `live+<tokens>` marker.
|
|
28
|
+
|
|
13
29
|
## [0.4.6] — 2026-05-09
|
|
14
30
|
|
|
15
31
|
### Changed
|
package/README.ja.md
CHANGED
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
npm install -g throughline
|
|
21
|
-
throughline install #
|
|
21
|
+
throughline install # hook / Codex skill / VS Code monitor task を登録
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
これだけ。Claude Code のセッションを開けば、以後すべてのターンが
|
|
25
25
|
`~/.throughline/throughline.db` に自動で流れていく。50 ターン作業した後、
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
`/clear` を打てば新セッションはゼロからではなく、**思考の途中から再開** される。
|
|
27
|
+
`/clear` を経由しない新規 chat / VS Code 再起動では `/tl` で前任を指名できる。
|
|
28
28
|
|
|
29
29
|
## 他の手段との比較
|
|
30
30
|
|
|
@@ -32,10 +32,10 @@ throughline install # ~/.claude/settings.json に hook を登録
|
|
|
32
32
|
|---|---|---|---|
|
|
33
33
|
| **圧縮の軸** | コンテンツの **種類** (テキスト vs ツール I/O) | **新旧** (古い → 要約) | 無し |
|
|
34
34
|
| **コーディング用途への適合** | 高 — ツール I/O こそ重い 80% | 中 — 残したい部分まで圧縮される | — |
|
|
35
|
-
| **`/clear` 後の生存** | ✅ SQLite + `/tl` バトン | ホスト依存 | ❌ |
|
|
36
|
-
| **誤継承リスク** |
|
|
35
|
+
| **`/clear` 後の生存** | ✅ SQLite + typed `/clear` / `/tl` バトン | ホスト依存 | ❌ |
|
|
36
|
+
| **誤継承リスク** | 低 (typed `/clear` / `/tl` が前任を指名) | 高 | — |
|
|
37
37
|
| **ランタイム依存** | **ゼロ** (Node 22.5+ 同梱の `node:sqlite`) | 多数 | — |
|
|
38
|
-
| **マルチセッション トークン監視** | ✅ 実測 `message.usage
|
|
38
|
+
| **マルチセッション トークン監視** | ✅ Claude 実測 `message.usage`、Codex rollout `token_count` | — | — |
|
|
39
39
|
|
|
40
40
|
<details>
|
|
41
41
|
<summary><b>なぜこれが効くのか — 80% ツール I/O 問題</b></summary>
|
|
@@ -126,33 +126,34 @@ L3 に保存された `kind` 別 (ツール入力 / ツール出力 / hook 出
|
|
|
126
126
|
|
|
127
127
|
---
|
|
128
128
|
|
|
129
|
-
## 引き継ぎ: `/clear`
|
|
129
|
+
## 引き継ぎ: typed `/clear` / `/tl` が前任を指名、source-`clear` は補助
|
|
130
130
|
|
|
131
|
-
Throughline 0.4.
|
|
131
|
+
Throughline 0.4.1+ の引き継ぎは 2 経路です。主経路は typed `/clear` または
|
|
132
|
+
`/tl` が書く baton で、`source='clear'` の auto path は `/clear` が
|
|
133
|
+
UserPromptSubmit hook に届かない場合の補助です。
|
|
132
134
|
|
|
133
|
-
###
|
|
135
|
+
### baton path (primary): typed `/clear` または `/tl`
|
|
134
136
|
|
|
135
|
-
|
|
136
|
-
`
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
ユーザーが prompt に `/clear` または `/tl` を打つと、UserPromptSubmit hook が
|
|
138
|
+
**そのセッションの** `session_id` を `handoff_batons` に書きます。次の
|
|
139
|
+
SessionStart は 1 時間以内の baton を消費し、その前任を確定的に merge します。
|
|
140
|
+
複数ウィンドウで「最新更新セッション」と「今 `/clear` したセッション」が違っても、
|
|
141
|
+
指名された前任だけを引き継ぎます。
|
|
139
142
|
|
|
140
|
-
|
|
143
|
+
### auto path (fallback): `source='clear'`
|
|
141
144
|
|
|
142
|
-
|
|
145
|
+
baton が無く、SessionStart の `source='clear'` が届いた場合だけ、同 project の
|
|
146
|
+
最新 Claude predecessor を選んで merge します。これは VS Code 拡張メニューなど、
|
|
147
|
+
typed `/clear` が UserPromptSubmit hook に届かない経路のための補助です。
|
|
143
148
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
- `/clear` 経由しないで引き継ぎたい (新 chat / VSCode 再起動など)
|
|
148
|
-
|
|
149
|
-
新セッションを開く前に `/tl` を打つと `UserPromptSubmit` hook が baton を書き、
|
|
150
|
-
次の `SessionStart` (1 時間以内) が baton を消費して merge します。
|
|
151
|
-
`source` 値関係なく発火します。
|
|
149
|
+
`THROUGHLINE_DISABLE_AUTO_HANDOFF=1` はこの fallback path だけを OFF にします。
|
|
150
|
+
typed `/clear` と `/tl` はユーザーの明示意思なので、この env に関係なく baton を
|
|
151
|
+
書いて引き継ぎます。
|
|
152
152
|
|
|
153
153
|
```
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
typed /clear: Session A → /clear → Session B (A の baton を消費して merge)
|
|
155
|
+
typed /tl: Session A → /tl → 新 chat / 再起動 → Session B (A の baton を消費して merge)
|
|
156
|
+
fallback: baton 無し + source='clear' → latest predecessor を merge
|
|
156
157
|
```
|
|
157
158
|
|
|
158
159
|
### 注入されるもの
|
|
@@ -214,10 +215,12 @@ throughline monitor --session <id-prefix>
|
|
|
214
215
|
```
|
|
215
216
|
|
|
216
217
|
監視中は Claude transcript / Codex rollout をライブに読み、Stop hook の state
|
|
217
|
-
snapshot はライブ usage
|
|
218
|
-
Stop
|
|
219
|
-
Codex は open turn
|
|
220
|
-
`
|
|
218
|
+
snapshot はライブ usage が取れない場合の控えとして使います。Codex については
|
|
219
|
+
`~/.codex/sessions/**/rollout-*.jsonl` も直接 discovery するため、Stop hook が
|
|
220
|
+
Throughline state をまだ書いていない現在セッションも表示できます。Codex は open turn
|
|
221
|
+
中だけ `input_tokens + output_tokens` を表示し、`task_complete` 後は verified
|
|
222
|
+
`input_tokens` のみに戻ります。表示 ID は `codex:01` ではなく、raw thread id の
|
|
223
|
+
先頭 8 桁 (`019e085c` など) です。
|
|
221
224
|
|
|
222
225
|
詳細仕様 (resize 追従、1M context 検出、ステイル隠し、Stop hook の非同期化など) は
|
|
223
226
|
[英語版 README](README.md#multi-session-token-monitor) を参照してください。
|
package/README.md
CHANGED
|
@@ -250,7 +250,9 @@ reconnect, but controlled model-visible rollback smokes did not reproduce that
|
|
|
250
250
|
path. `throughline trim --execute --host codex` now sends the guarded
|
|
251
251
|
rollback + Throughline DB memory injection when app-server turn-count guards and
|
|
252
252
|
injectable DB memory are available. Codex Stop hook auto-refresh also attempts
|
|
253
|
-
the same live refresh when verified usage reaches the
|
|
253
|
+
the same live refresh when verified usage reaches the 80% threshold, which runs
|
|
254
|
+
before Codex native auto-compact while staying above the monitor's 70% warning
|
|
255
|
+
band.
|
|
254
256
|
|
|
255
257
|
`throughline codex-host-primitive-audit` can inspect the installed Codex
|
|
256
258
|
app-server schema read-only. On the current tested Codex CLI, it finds
|
|
@@ -508,7 +510,7 @@ Example output:
|
|
|
508
510
|
```
|
|
509
511
|
[Throughline] 1 セッション
|
|
510
512
|
▶ Throughline Claude 2ed5039c just now ██░░░░░░░░ 205.1k / 1.0M claude-opus-4-6
|
|
511
|
-
Throughline Codex
|
|
513
|
+
Throughline Codex 019e085c just now ██████░░░░ 151.9k / 258.4k gpt-5.5
|
|
512
514
|
```
|
|
513
515
|
|
|
514
516
|
- **Claude token counts are accurate.** Read straight from the latest
|
|
@@ -517,15 +519,17 @@ Example output:
|
|
|
517
519
|
(`input_tokens + cache_creation_input_tokens + cache_read_input_tokens`).
|
|
518
520
|
No `length / 4` approximation.
|
|
519
521
|
- **Codex token counts use the rollout `token_count` event when present.** The
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
the monitor
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
522
|
+
monitor discovers live Codex rollouts directly from
|
|
523
|
+
`~/.codex/sessions/**/rollout-*.jsonl`, so a current Codex session can appear
|
|
524
|
+
even before the Codex Stop hook writes `codex:<thread_id>` monitor state.
|
|
525
|
+
While the monitor is running it reads the live rollout every tick and prefers
|
|
526
|
+
the latest verified `token_count` sample. During an open Codex turn, the
|
|
527
|
+
monitor overlays transient `output_tokens` on top of `input_tokens`; when
|
|
528
|
+
`task_complete` arrives it drops back to verified `input_tokens` only. If a
|
|
529
|
+
Codex rollout has no token-count event, Throughline can show an explicit
|
|
530
|
+
estimate with `estimated: true` and the monitor marks it with `est`; it is not
|
|
531
|
+
presented as exact usage.
|
|
532
|
+
- **Codex auto-refresh mutates at the verified 80% threshold.** The Codex Stop
|
|
529
533
|
hook captures DB memory, writes monitor state, and when verified usage reaches
|
|
530
534
|
the threshold it attempts rollback + Throughline DB memory injection for the
|
|
531
535
|
current thread.
|
|
@@ -533,11 +537,13 @@ Example output:
|
|
|
533
537
|
transcript, falls back to string matching on `1M context`, and finally
|
|
534
538
|
promotes to 1M if observed usage exceeds 200k.
|
|
535
539
|
- **Multi-session view.** Each Claude Code or Codex session writes its own
|
|
536
|
-
state file (`~/.throughline/state/<session_id>.json`)
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
540
|
+
state file (`~/.throughline/state/<session_id>.json`), and active Codex
|
|
541
|
+
rollouts are discovered directly from the Codex session directory. Codex
|
|
542
|
+
session ids are stored as `codex:<thread_id>` in JSON state, while the display
|
|
543
|
+
shows the raw first 8 thread-id characters (for example `019e085c`) to avoid
|
|
544
|
+
the ambiguous `codex:01` prefix slice. The monitor scans every second and
|
|
545
|
+
displays one row per live session, sorted by last activity. The most recent
|
|
546
|
+
one is highlighted with `▶`.
|
|
541
547
|
- **Stale hiding.** Sessions that haven't been touched in 15 minutes drop out of
|
|
542
548
|
the default view; files older than 24 hours are deleted entirely. This is the
|
|
543
549
|
only time threshold in the system and is used solely for display hygiene — no
|
|
@@ -119,7 +119,7 @@ schema v4 で PostToolUse (`capture-tool`) は廃止、L2/L3 は Stop 内で一
|
|
|
119
119
|
| **npm 公開 (v0.3.24)** | 2026-05-02 v0.3.23 の補完: `.vscode/tasks.json` には現環境の絶対パスが書き込まれるため、**そもそも commit すべきではない**。`shouldRecommendGitignore` を [src/vscode-task.mjs](../src/vscode-task.mjs) に追加し、`ensureMonitorTaskFile` が created / merged / repaired を返すタイミングで「git リポジトリ内かつ `.gitignore` に `.vscode/tasks.json` 系エントリが無い」を判定。該当時に `<system-reminder>` で `.gitignore` 追加推奨を 1 度だけ stdout 通知 (`.throughline-gitignore-noted` marker で抑止)。否定パターン (`!.vscode/tasks.json`) はスキップ判定 = 推奨を出す。README Troubleshooting にも明示。配布物 (npm tarball) には絶対パスは入っていない (`files` フィールドが `.vscode/` を含まない、ソースに hard-coded path 無し) ことを再確認 |
|
|
120
120
|
| **npm 公開 (v0.3.25): Claude-primary / Codex-sidecar groundwork** | `HandoffRecord` projection、`throughline handoff-preview`、`throughline_handoff` example、`codex-sidecar-diagnostics` / `codex-sidecar-dry-run` を追加。Claude Code hooks / slash command / transcript / baton / resume behavior は正本として維持し、Codex 対応は adapter / projection として足す |
|
|
121
121
|
| **npm 公開 (v0.3.25): optional Codex-sidecar L1 summarization** | Claude primary の L2→L1 要約は、`codex-sidecar` が `summarize-l1` preset で configured の場合だけ sidecar を使う。disabled / unavailable / run failure では、ユーザー許可済み互換経路として既存 Claude Haiku 要約を維持する。Codex primary の L2→L1 backend は次フェーズ計画で Codex CLI 本線として扱う。Claude CLI smoke / test で Claude を呼ぶ場合は Haiku を使う |
|
|
122
|
-
| **npm 公開 (v0.3.25): `/tl-trim` dry-run** | `throughline trim --dry-run`、`--preflight`、guarded `--execute`、`--host`、`--keep-recent`、`--all`、`--memo-stdin`、`--preview-max-chars`、`--codex-thread-id`、`THROUGHLINE_CODEX_THREAD_ID` / `CODEX_THREAD_ID`、`throughline codex-threads`、`throughline doctor --trim`、Claude slash command `/tl-trim` を追加。Codex app-server の rollback / inject primitive は live app-server 上で実測済み。2026-05-06 incident 後は一時 blocked としたが、2026-05-08 の controlled rollback model-visible smoke が app-server restart 境界と VS Code reload/reconnect 境界の両方で `not-reproduced` だったため、過剰 blocker は解除済み。`trim --execute --host codex` は明示実行で current-thread rollback + Throughline DB memory inject を送り、Codex Stop hook auto-refresh は verified usage
|
|
122
|
+
| **npm 公開 (v0.3.25): `/tl-trim` dry-run** | `throughline trim --dry-run`、`--preflight`、guarded `--execute`、`--host`、`--keep-recent`、`--all`、`--memo-stdin`、`--preview-max-chars`、`--codex-thread-id`、`THROUGHLINE_CODEX_THREAD_ID` / `CODEX_THREAD_ID`、`throughline codex-threads`、`throughline doctor --trim`、Claude slash command `/tl-trim` を追加。Codex app-server の rollback / inject primitive は live app-server 上で実測済み。2026-05-06 incident 後は一時 blocked としたが、2026-05-08 の controlled rollback model-visible smoke が app-server restart 境界と VS Code reload/reconnect 境界の両方で `not-reproduced` だったため、過剰 blocker は解除済み。`trim --execute --host codex` は明示実行で current-thread rollback + Throughline DB memory inject を送り、Codex Stop hook auto-refresh は verified usage 80% 以上で同じ guarded path を試行する。DB memory 不在、Codex thread identity 不在、rollout/app-server turn-count 不一致は mutation 前に拒否する。restore-safety / planned restore-safety / host primitive audit は diagnostics として表示する |
|
|
123
123
|
| **npm 公開 (v0.3.25): Codex primary capture** | `throughline codex-capture --codex-thread-id <id>` を追加。Codex rollout JSONL の active turns を `codex:<thread_id>` session として DB `bodies` に保存し、`function_call` / `function_call_output` を DB `details` の L3 tool input / output として保存する。`thread_rolled_back` 適用後の active thread だけを再構成し、rollback 済み tail は current L2/L3 に残さない。Codex thread id は明示指定または env 指定のみで、自動推測しない |
|
|
124
124
|
| **npm 公開 (v0.3.25): Codex-primary L1 backend** | `summarizeToL1` に `hostMode` 分岐を追加。Claude primary は既存の `codex-sidecar` / Claude Haiku 互換経路を維持し、Codex primary は Codex CLI backend を使う。Codex CLI failure は `source = codex-cli` / `reason` 付き explicit error とし、Claude Haiku / `raw_l2` に silent fallback しない |
|
|
125
125
|
| **npm 公開 (v0.3.25): Codex-primary L1 CLI** | `throughline codex-summarize --session codex:<thread_id>` を追加。captured Codex L2 が L2 window を超えた場合、最古の未要約 turn を Codex CLI backend で L1 skeleton に書く。Codex CLI failure は explicit error |
|
|
@@ -131,11 +131,12 @@ schema v4 で PostToolUse (`capture-tool`) は廃止、L2/L3 は Stop 内で一
|
|
|
131
131
|
| **npm 公開 (v0.3.25): Codex VS Code restore smoke protocol** | `throughline codex-vscode-restore-smoke --prepare/--verify --codex-thread-id <id>` を追加。`--prepare` は hidden active-work marker memory を app-server へ注入し、VS Code reload / reconnect 後に marker を含まない prompt を送る二段階手順を出す。`--verify` は rollout を読み、prepare 後の marker-free smoke prompt、assistant の marker-only answer、user prompt への marker leak 不在を確認する。prepare は `THROUGHLINE_EXPERIMENTAL_CODEX_VSCODE_RESTORE_SMOKE=1` 必須。実 VS Code reload / reconnect marker proof は `TL_CODEX_VSCODE_RESTORE_46888202` で成功済み。ただしこれは hidden developer memory visibility 証明であり、rollback 済み user turn の非復活証明ではない |
|
|
132
132
|
| **npm 公開 (v0.3.25): Codex VS Code rollback smoke verifier** | `throughline codex-vscode-rollback-smoke --verify --codex-thread-id <id>` を追加。rollout を read-only で読み、rollback event、rollback 済み user text、rollback 後 user turn、`restoreSafety.status = ok` を必須条件にする。`--after-vscode-restart` がある場合だけ `restartSafe: true` を返す。実 incident-shaped live rollback run では `thread_rolled_back` と injected memory は記録されたが、rollback 対象 user text が `compacted.replacement_history` に残り、後続 verifier では rollback 済み user text の再出現も観測した。後続分類で app-server response 上の retained text は `aggregatedOutput` に限定され、controlled rollback model-visible smoke は再現しなかったため、これは現在は diagnostic evidence として扱う |
|
|
133
133
|
| **npm 公開 (v0.3.25): Codex primary doctor** | `throughline doctor --codex` を追加。現在 project の Codex thread env identity、rollout candidates、captured `codex:<thread_id>` DB sessions、context-refresh memory contract、new-thread handoff readiness、safe continuation status、host primitive audit status、次に使う capture / handoff / resume / audit command を表示する。doctor 自体は read-only で、Codex thread / DB / Claude settings を変更しない。`doctor --trim --host codex` も host primitive audit status を表示する |
|
|
134
|
-
| **npm 公開 (v0.3.25): Codex global Stop hook / skill install** | `throughline install` が Claude hooks / slash commands に加えて `~/.codex/hooks.json` に絶対 node + installed `bin/throughline.mjs codex-hook stop` を `async: false` で登録し、`~/.codex/config.toml` の `[features].codex_hooks = true` を有効化し、`~/.codex/skills/throughline` に `$throughline` skill を配置する。Codex App Server / VSCode host の PATH 差分で bare `throughline` が見えない可能性があるため、hook は Caveat と同じ絶対パス型に寄せる。既存 Caveat / Spotter などの Codex hooks は保持し、`throughline uninstall` は Throughline 管理の Codex hook / skill だけを削除する。既に bare command または `async: true` で登録済みの Throughline Codex Stop hook は次回 install で更新する。実環境では `codex exec --json` child thread `019dfd4f-93ff-7522-8f89-bd1e1996c8d7` が Stop hook で自然 capture され、`doctor --codex` の latest DB session が `codex:019dfd4f-93ff-7522-8f89-bd1e1996c8d7` に進むことを確認した。さらに絶対パス型へ更新後、child thread `019dfd5e-1248-7c11-8ddc-97e1b0701e10` でも latest DB session が `codex:019dfd5e-1248-7c11-8ddc-97e1b0701e10` に進むことを確認した。hook shape 変更後に新規開始した VSCode-origin thread `019dfd62-9a9d-7211-bf91-89d8e3fc908e` でも `doctor --codex` の current thread と latest DB session が一致し、自然 Stop hook capture を確認済み。hook shape 変更前から開いていた VSCode-origin parent thread は、変更後の自然 Stop smoke としては扱わない。Caveat 側にも `async: false` Stop hook が動く実測があるため、Codex 側は Caveat と同じ同期 hook 方針に寄せる。`codex-capture` / `codex-summarize` / `codex-resume --memo-stdin` は診断・明示操作 surface として維持し、model-visible smoke は明示 opt-in。2026-05-08 以降、Stop hook auto-refresh は verified usage
|
|
134
|
+
| **npm 公開 (v0.3.25): Codex global Stop hook / skill install** | `throughline install` が Claude hooks / slash commands に加えて `~/.codex/hooks.json` に絶対 node + installed `bin/throughline.mjs codex-hook stop` を `async: false` で登録し、`~/.codex/config.toml` の `[features].codex_hooks = true` を有効化し、`~/.codex/skills/throughline` に `$throughline` skill を配置する。Codex App Server / VSCode host の PATH 差分で bare `throughline` が見えない可能性があるため、hook は Caveat と同じ絶対パス型に寄せる。既存 Caveat / Spotter などの Codex hooks は保持し、`throughline uninstall` は Throughline 管理の Codex hook / skill だけを削除する。既に bare command または `async: true` で登録済みの Throughline Codex Stop hook は次回 install で更新する。実環境では `codex exec --json` child thread `019dfd4f-93ff-7522-8f89-bd1e1996c8d7` が Stop hook で自然 capture され、`doctor --codex` の latest DB session が `codex:019dfd4f-93ff-7522-8f89-bd1e1996c8d7` に進むことを確認した。さらに絶対パス型へ更新後、child thread `019dfd5e-1248-7c11-8ddc-97e1b0701e10` でも latest DB session が `codex:019dfd5e-1248-7c11-8ddc-97e1b0701e10` に進むことを確認した。hook shape 変更後に新規開始した VSCode-origin thread `019dfd62-9a9d-7211-bf91-89d8e3fc908e` でも `doctor --codex` の current thread と latest DB session が一致し、自然 Stop hook capture を確認済み。hook shape 変更前から開いていた VSCode-origin parent thread は、変更後の自然 Stop smoke としては扱わない。Caveat 側にも `async: false` Stop hook が動く実測があるため、Codex 側は Caveat と同じ同期 hook 方針に寄せる。`codex-capture` / `codex-summarize` / `codex-resume --memo-stdin` は診断・明示操作 surface として維持し、model-visible smoke は明示 opt-in。2026-05-08 以降、Stop hook auto-refresh は verified usage 80% 以上で guarded rollback / inject を試行し、estimate usage では mutation しない。2026-05-09 以降は Codex native auto-compact より先に Throughline refresh を走らせつつ、70% warning よりは mutation を遅らせる |
|
|
135
135
|
| **npm 公開 (v0.3.25): Codex-first roadmap** | [THROUGHLINE_CODEX_FIRST_ROADMAP.md](THROUGHLINE_CODEX_FIRST_ROADMAP.md) を追加。次フェーズは Codex primary 実用化、Codex Rewind 互換、Claude 側 finalization の順で進める。Codex primary の L2→L1 backend は Codex CLI を本線とし、`codex-sidecar` は Claude primary からの review / risk-check / second opinion / 互換 L2→L1 経路として整理する |
|
|
136
136
|
| **npm 公開 (v0.3.25): npm docs packaging** | README から参照する `docs/` と `CHANGELOG.md` を npm `files` に追加。`docs/throughline-handoff-context.example.json` を含め、README の sidecar dry-run 例が tarball 内でも成立するようにする |
|
|
137
137
|
| **npm 公開 (v0.4.0): /clear auto-handoff + memo / save-inflight / /tl-trim retire** | 2026-05-08 Claude Code 2.1.128 で `source='clear'` が reliable になったため、`/clear` で自動引継ぎがデフォルト ON になる auto path を追加。`THROUGHLINE_DISABLE_AUTO_HANDOFF=1` で OFF にできる。`/tl` slash command は明示意思マーカーへ簡素化 (memo 4 項目入力廃止、`save-inflight` CLI 削除、`/tl-trim` slash command 廃止、`updateBatonMemo` 関数削除、`handoff_batons.memo_text` を schema v8 で drop)。注入は L1 + L2 + L3 references のみに簡素化し、memo / 中断直前 thinking セクションを削除。Codex 側 trim path は維持。詳細は [CHANGELOG.md](../CHANGELOG.md) と [THROUGHLINE_CLEAR_AUTO_HANDOFF_PLAN.md](THROUGHLINE_CLEAR_AUTO_HANDOFF_PLAN.md) |
|
|
138
138
|
| **npm 公開 (v0.4.1): typed `/clear` も baton を書く + 2 経路の優先順位入れ替え** | 2026-05-09 `/clear` を UserPromptSubmit hook で検出した時点で当該セッションの `session_id` を `handoff_batons` に書き込み、次 SessionStart が確定的にそのセッションを引き継ぐ。これで multi-window で「最新更新セッション ≠ /clear したセッション」になるシナリオで `findLatestClaudePredecessor` heuristic が誤った前任を選ぶ問題を解消。2 経路の優先順位を **baton path = primary、auto path = fallback** に変更 (auto path は VSCode 拡張メニュー由来など UserPromptSubmit に届かない経路のフォールバック)。`THROUGHLINE_DISABLE_AUTO_HANDOFF=1` は fallback path のみに作用するようになった (typed `/clear` / `/tl` は env と無関係に発火する)。あわせて `.vscode/tasks.json` を git 追跡から外し (gitignore)、`ensureMonitorTaskFile` が hook 発火ごとに絶対パスを書き換える挙動による別環境での dirty diff を解消。`src/prompt-submit.test.mjs` を新設し、`isClearCommand` / `isBatonCommand` 判定 14 件と subprocess+DB 実体テスト 3 件を追加。詳細は [CHANGELOG.md](../CHANGELOG.md) |
|
|
139
|
+
| **npm 公開 (v0.4.7): Codex monitor direct discovery + 80% auto-refresh** | 2026-05-09 Codex Stop hook auto-refresh の verified usage threshold を 90% から 80% に変更し、Codex native auto-compact より先に Throughline DB memory refresh を試行する。estimate usage / estimated context window では mutation しない。`throughline monitor` は `~/.throughline/state` に加えて `~/.codex/sessions/**/rollout-*.jsonl` を直接 discovery し、Throughline state が未生成の現在 Codex thread も表示する。既存 state がある場合は state の usage snapshot を保持しつつ discovered rollout path / mtime を合流する。Codex 表示 ID は `codex:01` ではなく raw thread id 先頭 8 桁 (`019e085c`) にした。Codex open turn の transient `output_tokens` は token count に overlay するが、モデル欄の `live+<tokens>` marker は表示しない |
|
|
139
140
|
| **グローバル E2E 検証** | 2026-04-17 別ディレクトリから `throughline doctor` 全緑を確認 |
|
|
140
141
|
|
|
141
142
|
### ❌ 未完タスク
|
|
@@ -147,7 +148,7 @@ schema v4 で PostToolUse (`capture-tool`) は廃止、L2/L3 は Stop 内で一
|
|
|
147
148
|
| **GitHub Actions 自動 publish** | `release` タグ push をトリガー(Phase 3+、Trusted Publishing 使用) |
|
|
148
149
|
| **Claude Code プラグインマーケットプレース登録** | npm 公開の後継ステップ(Phase 3+) |
|
|
149
150
|
| **turn-processor.test.mjs の 10 秒タイムアウト解消** | `main()` が stdin を待ち続けるためテストファイルがハングする既存の問題。実装動作は無影響、テスト個別 9/9 は pass |
|
|
150
|
-
| **automatic context rollback / inject** | Codex Stop hook auto-refresh は verified usage
|
|
151
|
+
| **automatic context rollback / inject** | Codex Stop hook auto-refresh は verified usage 80% 以上で guarded rollback + Throughline DB memory inject を試行する。controlled rollback model-visible smoke で復活が未再現となったため、2026-05-06 incident 後の overbroad blocker は解除済み。estimate usage では実行しない |
|
|
151
152
|
|
|
152
153
|
---
|
|
153
154
|
|
|
@@ -278,7 +278,7 @@ Codex 側 v0.3.25 の以下は本計画で **完全に無変更**:
|
|
|
278
278
|
- `codex-capture` / `codex-summarize` / `codex-resume` (Codex primary L1/L2/L3 path)
|
|
279
279
|
- `codex-resume --format handoff` (新規 Codex thread 用 prompt)
|
|
280
280
|
- `trim --execute --host codex` / `--preflight --host codex` (app-server `thread/rollback` + `thread/inject_items`)
|
|
281
|
-
- Codex Stop hook
|
|
281
|
+
- Codex Stop hook 80% auto-refresh
|
|
282
282
|
- restore-safety / host primitive audit diagnostics
|
|
283
283
|
- Codex skill ([codex/skills/throughline](../codex/skills/throughline)) の trim 機能 (= 機能自体は無変更、SKILL.md 内の `/tl-trim` 言及があれば 4 TODO で update)
|
|
284
284
|
- [src/codex-app-server.mjs](../src/codex-app-server.mjs), [src/codex-rollout-memory.mjs](../src/codex-rollout-memory.mjs)
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
- Claude-primary の現行 L2 -> L1 要約は `codex-sidecar` が configured の場合に sidecar を優先し、使えない場合は Claude Haiku 経路に戻る。Codex-primary は Codex CLI backend 失敗を明示 error にし、Claude Haiku / raw L2 へ fallback しない。
|
|
29
29
|
- Codex guarded trim は、Codex app-server の `thread/read` / `thread/resume` / `thread/rollback` / `thread/inject_items` を使う。明示 thread identity、rollout/app-server turn count guard、injectable memory がない場合は mutation 前に拒否する。
|
|
30
30
|
- Claude `/rewind` 自動化はまだ有効化しない。
|
|
31
|
-
- Codex automatic refresh mutation は再有効化済み。`
|
|
31
|
+
- Codex automatic refresh mutation は再有効化済み。`80%` の verified usage threshold で rollback / Throughline DB memory inject を試行し、estimate usage では実行しない。Codex native auto-compact より先に Throughline refresh を走らせつつ、70% warning よりは mutation を遅らせる。
|
|
32
32
|
|
|
33
33
|
## 新セッション引き継ぎ
|
|
34
34
|
|
|
@@ -466,9 +466,9 @@ Phase 5 implementation status (2026-05-06):
|
|
|
466
466
|
- 削減量の記録: `trim --dry-run --host codex` は rollout text がある場合に `contextReductionEstimate` を返す。2026-05-06 の現在 thread `019dfd62-9a9d-7211-bf91-89d8e3fc908e` では通常 keep-recent preview だと `capturedTurns = 14` / `keepRecent = 20` のため `rollbackTurns = 0`、推定削減量も 0 だった。旧 bare `$throughline` context refresh は `--all` を使っていたため、この keep-recent preview の 0 は「Codex で削減できない」という意味ではない。
|
|
467
467
|
- historical live-only `$throughline` context refresh smoke: 同じ VSCode-origin thread `019dfd62-9a9d-7211-bf91-89d8e3fc908e` で `trim --dry-run --host codex --all --json` -> `trim --preflight --host codex --all --json` -> `trim --execute --host codex --all` を実行した。preflight は rollout/app-server turn count `20 / 20` match、execute は `rollbackSent = true`、`injectSent = true`、`injectedItems = 1`、`rollback candidate turns = 20`。直前見積もりは `rollbackEstimatedTokens = 179780`、`injectedMemoryEstimatedTokens = 2009`、`netEstimatedTokens = 177771`、`reductionPct = 99` (`chars / 4` heuristic)。これは live app-server smoke であり、restart-safe durability の証明ではない。
|
|
468
468
|
- post-refresh confirmation: execute 後の次 user turn で `doctor --codex` は current thread と latest DB session の一致を維持し、latest DB session は `2026-05-06 22:41:10` へ更新された。`trim --dry-run --host codex --all --json` は `activeTurns = 2`、`rollbackEvents = 1`、`rolledBackTurns = 20` を返し、`codex-resume` は rollback 後の短い active work context を L2 として描画した。
|
|
469
|
-
- monitor adapter 修正: `throughline monitor` は Claude / Codex host-aware state を読む。Codex Stop hook は `codex:<thread_id>` monitor state を書き、Codex rollout path は Claude transcript 用 `transcriptPath` ではなく `rolloutPath` に保存する。rollout に `event_msg` / `token_count` がある場合は verified usage として表示し、無い場合だけ `estimated: true` / `source = codex-rollout-chars-div-4` の明示 estimate を使う。state filename は URL encode し、`codex:` session id を Windows
|
|
469
|
+
- monitor adapter 修正: `throughline monitor` は Claude / Codex host-aware state を読む。Codex Stop hook は `codex:<thread_id>` monitor state を書き、Codex rollout path は Claude transcript 用 `transcriptPath` ではなく `rolloutPath` に保存する。2026-05-09 以降、monitor は `~/.throughline/state` だけでなく `~/.codex/sessions/**/rollout-*.jsonl` も直接 discovery するため、state 未生成の現在 Codex thread も表示できる。既存 state がある場合は usage snapshot を保持しつつ discovered rollout path / mtime を合流する。rollout に `event_msg` / `token_count` がある場合は verified usage として表示し、無い場合だけ `estimated: true` / `source = codex-rollout-chars-div-4` の明示 estimate を使う。state filename は URL encode し、`codex:` session id を Windows でも保存可能にした。表示 ID は `codex:` prefix を外した raw thread id 先頭 8 桁。
|
|
470
470
|
- monitor doctor 修正: Codex Stop hook stdout は VSCode chat に必ず見えるとは限らないため、`doctor --codex` に `.vscode/tasks.json` の Throughline Monitor task 診断を追加した。登録済み / 未登録 / JSONC / parse error / broken absolute path と `runOn`、および初回作成後は `Developer: Reload Window` が必要という note を read-only で表示する。
|
|
471
|
-
- historical automatic refresh 実装: Codex Stop hook は capture / L1 summarize / monitor state 書き込み後、verified usage が verified context window の `90%` 以上なら automatic refresh を試行する経路を持っていた。2026-05-06 incident 後、この経路は blocker として扱い、monitor state / warning / diagnostics は残しても rollback + inject
|
|
471
|
+
- historical automatic refresh 実装: Codex Stop hook は capture / L1 summarize / monitor state 書き込み後、verified usage が verified context window の `90%` 以上なら automatic refresh を試行する経路を持っていた。2026-05-06 incident 後、この経路は blocker として扱い、monitor state / warning / diagnostics は残しても rollback + inject は送らない状態にした。2026-05-08 unblock 後は guarded rollback / inject を再有効化し、2026-05-09 以降は Codex native auto-compact より先に Throughline refresh を走らせるため verified usage `80%` 以上を既定閾値にする。
|
|
472
472
|
|
|
473
473
|
## Phase 6: Claude Side Finalization
|
|
474
474
|
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Status: implemented
|
|
4
4
|
Date: 2026-05-06
|
|
5
|
+
Amended: 2026-05-09 — monitor now also discovers Codex rollouts directly
|
|
6
|
+
from `~/.codex/sessions/**/rollout-*.jsonl`, so Codex visibility no longer
|
|
7
|
+
depends on the Stop hook writing a Throughline state file.
|
|
5
8
|
|
|
6
9
|
この計画は、既存の Claude-primary monitor 経路を置き換えずに、
|
|
7
10
|
`throughline monitor` へ Codex 対応を追加するためのもの。
|
|
@@ -24,6 +27,10 @@ Codex 対応では、既存 monitor contract の周辺に Codex adapter / projec
|
|
|
24
27
|
Claude hooks、transcript parsing、slash command、baton、resume behavior は rename / 劣化
|
|
25
28
|
させない。
|
|
26
29
|
|
|
30
|
+
2026-05-09 以降は、Codex Stop hook が state を書く経路に加えて、
|
|
31
|
+
monitor reader 自体が Codex rollout index を直接 discovery する。これにより、
|
|
32
|
+
host が Stop hook をまだ dispatch していない現在 thread でも monitor に表示される。
|
|
33
|
+
|
|
27
34
|
## 非目的
|
|
28
35
|
|
|
29
36
|
- Claude transcript usage parsing を置き換えない。
|
|
@@ -39,6 +46,8 @@ Monitor reader:
|
|
|
39
46
|
|
|
40
47
|
- `throughline monitor` は `src/token-monitor.mjs` に dispatch される。
|
|
41
48
|
- `~/.throughline/state/` の session state files を読む。
|
|
49
|
+
- Codex については `~/.codex/sessions/**/rollout-*.jsonl` から現在 project の
|
|
50
|
+
rollout candidates も読む。`--all` または `--session` 時は全 project 候補を読む。
|
|
42
51
|
- 現在の state file は主に次を持つ。
|
|
43
52
|
`sessionId`, `projectPath`, `transcriptPath`, `pid`, `updatedAt`, optional `usage`
|
|
44
53
|
- `state.usage` があれば monitor はそれをそのまま表示する。
|
|
@@ -50,6 +59,9 @@ Monitor reader:
|
|
|
50
59
|
- `pid` は既存 state schema に残っているが、現行 stale 判定は `updatedAt` ベース。
|
|
51
60
|
Codex Stop hook は短命 process なので、Codex の active 判定に hook process PID を
|
|
52
61
|
意味づけない。
|
|
62
|
+
- state file が無い Codex rollout candidate は、monitor 内で synthetic state として扱う。
|
|
63
|
+
既存 state がある場合は、state の `usage` snapshot を保持しつつ discovered
|
|
64
|
+
`rolloutPath` / `updatedAt` を合流する。
|
|
53
65
|
|
|
54
66
|
Claude writer:
|
|
55
67
|
|
|
@@ -148,6 +160,8 @@ adapter 側に置く。
|
|
|
148
160
|
- Claude state + real usage: 既存どおり表示。
|
|
149
161
|
- Codex state + estimated usage: token bar を表示し、`est` marker を付ける。
|
|
150
162
|
- Codex state + usage 無し: active session として表示し、token usage は未取得扱い。
|
|
163
|
+
- Codex discovered rollout + state 無し: synthetic Codex state として表示し、live rollout
|
|
164
|
+
usage を読む。表示 ID は `codex:` prefix を外した raw thread id 先頭 8 桁にする。
|
|
151
165
|
|
|
152
166
|
### 5. VSCode monitor task provision を Codex にも接続する
|
|
153
167
|
|
|
@@ -181,6 +195,9 @@ Codex Stop hook でも、Claude hooks と同じく
|
|
|
181
195
|
- [x] Codex context window が未検証の場合は `contextWindowEstimated: true` を保持する。
|
|
182
196
|
- [x] `src/cli/codex-hook.mjs` で Codex Stop hook state writing を追加する。
|
|
183
197
|
- [x] monitor session id は `codex:<thread_id>` にする。
|
|
198
|
+
- [x] monitor が Codex rollout を直接 discovery し、state 未生成の current thread も表示する。
|
|
199
|
+
- [x] discovered rollout と既存 Codex state を merge し、state の usage snapshot を保持する。
|
|
200
|
+
- [x] Codex 表示 ID は `codex:` prefix を外した raw thread id 先頭 8 桁にする。
|
|
184
201
|
- [x] Codex state の `pid` を active 判定に使わないことを test / docs で固定する。
|
|
185
202
|
- [x] Codex state では `transcriptPath: null` とし、rollout path は `rolloutPath` に保存する。
|
|
186
203
|
- [x] Codex Stop hook から `ensureMonitorTaskFile` を呼ぶ。
|
|
@@ -203,6 +220,8 @@ Codex Stop hook でも、Claude hooks と同じく
|
|
|
203
220
|
## 受け入れ条件
|
|
204
221
|
|
|
205
222
|
- Codex Stop hook capture 後、`throughline monitor` に現在の Codex session が出る。
|
|
223
|
+
- Codex Stop hook state が無くても、現在 project の Codex rollout があれば
|
|
224
|
+
`throughline monitor` に現在の Codex session が出る。
|
|
206
225
|
- Claude monitor behavior は既存どおり。
|
|
207
226
|
- Codex session は Codex と分かる。
|
|
208
227
|
- Codex token usage が estimate の場合、estimate と分かる。
|
|
@@ -372,13 +372,13 @@ Phase 6 result (2026-05-06):
|
|
|
372
372
|
- `thread/inject_items` に raw Responses API item `{ type: "message", role: "developer", content: [{ type: "input_text", text: "..." }] }` を渡し、次の `turn/start` で injected memory が model-visible になることを確認した。marker `TL_PHASE6_INJECT_OK` を正しく返した。
|
|
373
373
|
- その後の Codex primary 実装で、`codex-resume` が描画する active-work developer message も実 Codex host で model-visible になることを確認した。marker `TL_CODEX_VISIBLE_REAL_20260506_C` が `item/agentMessage/delta` に出た。詳細な実測値は [THROUGHLINE_CODEX_FIRST_ROADMAP.md](THROUGHLINE_CODEX_FIRST_ROADMAP.md) の Phase 3 result を正とする。
|
|
374
374
|
- さらに `thread/inject_items` 後にもう一度 `thread/resume` してから `turn/start` を呼ぶ smoke も追加し、実 Codex host で marker `TL_CODEX_RESUME_AFTER_INJECT_REAL_20260506` が `item/agentMessage/delta` に出ることを確認した。
|
|
375
|
-
- Codex host primitive は live app-server 上では実測済み。ただし restart-safe durability は未証明。現在は明示 `--codex-thread-id` または env thread identity と rollout/app-server turn count guard が live mutation の最低条件であり、durable success は別分類で扱う。2026-05-06 incident 後はいったん Codex Stop hook 後の automatic refresh mutation を blocked としたが、2026-05-08 unblock 後は
|
|
375
|
+
- Codex host primitive は live app-server 上では実測済み。ただし restart-safe durability は未証明。現在は明示 `--codex-thread-id` または env thread identity と rollout/app-server turn count guard が live mutation の最低条件であり、durable success は別分類で扱う。2026-05-06 incident 後はいったん Codex Stop hook 後の automatic refresh mutation を blocked としたが、2026-05-08 unblock 後は guarded rollback / inject を試行する。2026-05-09 以降は Codex native auto-compact より先に Throughline refresh を走らせるため、verified usage 80% 以上を既定閾値にする。
|
|
376
376
|
- Claude `/rewind conversation only` は手動 UX として扱う。外部ツールからの自動化 surface は未確認。Claude host の automatic rollback / inject は `manual-only`。
|
|
377
377
|
- Phase 8 では dry-run / preflight を本線にしつつ、Codex だけ guarded execute を追加した。Claude は manual-only のまま扱う。
|
|
378
378
|
|
|
379
379
|
完了条件:
|
|
380
380
|
|
|
381
|
-
- [x] rollback trim を自動実装してよい host と、手動案内に留める host が判断できる。Codex は 2026-05-08 unblock 後、
|
|
381
|
+
- [x] rollback trim を自動実装してよい host と、手動案内に留める host が判断できる。Codex は 2026-05-08 unblock 後、Stop hook auto-refresh と明示 execute を許可する。2026-05-09 以降の auto-refresh 既定閾値は verified usage 80%。Claude は manual-only。
|
|
382
382
|
|
|
383
383
|
## Phase 7: Trim Handoff Model
|
|
384
384
|
|
|
@@ -19,7 +19,7 @@ model-visible input に入ることは controlled smoke で再現していませ
|
|
|
19
19
|
run の retained text は risk evidence として残す一方、単独では mutation 前 blocker にしません。
|
|
20
20
|
Codex current-thread trim は、明示 `--execute`、Throughline DB injectable memory、
|
|
21
21
|
Codex thread identity、rollout/app-server turn-count guard を条件に実行します。Codex Stop hook
|
|
22
|
-
auto-refresh は verified usage
|
|
22
|
+
auto-refresh は verified usage 80% 以上で同じ guarded path を試行し、estimate usage では実行しません。
|
|
23
23
|
|
|
24
24
|
最初のインシデント仮説に対する重要な訂正:
|
|
25
25
|
|
|
@@ -644,7 +644,7 @@ TODO:
|
|
|
644
644
|
という記述を訂正する。
|
|
645
645
|
- [x] hypothesis を compacted replacement-history restore に言及する形へ更新する。
|
|
646
646
|
- [x] Codex Rewind-equivalent trim が complete だという roadmap claim を格下げする。
|
|
647
|
-
- [x] Codex auto-refresh は verified usage
|
|
647
|
+
- [x] Codex auto-refresh は verified usage 80% 以上で guarded rollback / inject を
|
|
648
648
|
実行する。estimate usage では実行しない。
|
|
649
649
|
- [x] 新セッションが古い「Codex side complete」claim ではなく、この fix plan から
|
|
650
650
|
再開できるように `CLAUDE.md` を更新する。
|
|
@@ -263,7 +263,7 @@ Codex で考えられる流れ:
|
|
|
263
263
|
9. ユーザーは同じ Codex thread で続行する。
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
-
現行実装では、Codex Stop hook 後の
|
|
266
|
+
現行実装では、Codex Stop hook 後の 80% automatic refresh は guarded rollback / inject
|
|
267
267
|
mutation を試行する。明示 CLI の `throughline trim --execute --host codex --codex-thread-id <id>`
|
|
268
268
|
も env gate なしで実行する。明示 Codex thread identity、injectable Throughline DB memory、
|
|
269
269
|
rollout/app-server turn count guard は live mutation の最低条件であり、durable success は
|
package/package.json
CHANGED
|
@@ -121,7 +121,7 @@ test('codex-hook stop captures rollout using Codex stdin payload fields', async
|
|
|
121
121
|
}
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
-
test('codex-hook stop runs auto refresh when verified usage reaches
|
|
124
|
+
test('codex-hook stop runs auto refresh when verified usage reaches 80%', async () => {
|
|
125
125
|
const codexHome = mkdtempSync(join(tmpdir(), 'tl-codex-hook-home-'));
|
|
126
126
|
const project = mkdtempSync(join(tmpdir(), 'tl-codex-hook-project-'));
|
|
127
127
|
const threadId = '019dfaba-f87e-7f41-a144-d5ca7c6dd7f9';
|
|
@@ -2,7 +2,7 @@ import { runCodexTrimExecution } from './codex-app-server.mjs';
|
|
|
2
2
|
import { buildCodexRolloutTrimSource } from './codex-rollout-memory.mjs';
|
|
3
3
|
import { buildTrimPlan } from './trim-model.mjs';
|
|
4
4
|
|
|
5
|
-
export const CODEX_AUTO_REFRESH_THRESHOLD = 0.
|
|
5
|
+
export const CODEX_AUTO_REFRESH_THRESHOLD = 0.8;
|
|
6
6
|
|
|
7
7
|
export function evaluateCodexAutoRefreshUsage(usage, { threshold = CODEX_AUTO_REFRESH_THRESHOLD } = {}) {
|
|
8
8
|
if (!usage) {
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
runCodexAutoRefresh,
|
|
8
8
|
} from './codex-auto-refresh.mjs';
|
|
9
9
|
|
|
10
|
-
test('evaluateCodexAutoRefreshUsage: default threshold is
|
|
11
|
-
assert.equal(CODEX_AUTO_REFRESH_THRESHOLD, 0.
|
|
10
|
+
test('evaluateCodexAutoRefreshUsage: default threshold is 80%', () => {
|
|
11
|
+
assert.equal(CODEX_AUTO_REFRESH_THRESHOLD, 0.8);
|
|
12
12
|
const below = evaluateCodexAutoRefreshUsage({
|
|
13
|
-
tokens:
|
|
13
|
+
tokens: 206_719,
|
|
14
14
|
contextWindowSize: 258_400,
|
|
15
15
|
estimated: false,
|
|
16
16
|
contextWindowEstimated: false,
|
|
@@ -19,7 +19,7 @@ test('evaluateCodexAutoRefreshUsage: default threshold is 90%', () => {
|
|
|
19
19
|
assert.equal(below.reason, 'below_threshold');
|
|
20
20
|
|
|
21
21
|
const atThreshold = evaluateCodexAutoRefreshUsage({
|
|
22
|
-
tokens:
|
|
22
|
+
tokens: 206_720,
|
|
23
23
|
contextWindowSize: 258_400,
|
|
24
24
|
estimated: false,
|
|
25
25
|
contextWindowEstimated: false,
|
package/src/token-monitor.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import { statSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
|
25
25
|
import { homedir } from 'node:os';
|
|
26
26
|
import { getStateDir, readAllSessionStates, snapshotStateMtimes, normalizeProjectPath, STALE_HIDE_MS } from './state-file.mjs';
|
|
27
27
|
import { buildCodexMonitorUsage } from './codex-usage.mjs';
|
|
28
|
+
import { listCodexThreadCandidates } from './codex-thread-index.mjs';
|
|
28
29
|
import { readLatestUsage } from './transcript-usage.mjs';
|
|
29
30
|
import { startSizeQuery } from './terminal-size.mjs';
|
|
30
31
|
|
|
@@ -290,7 +291,7 @@ export function resolveColumns() {
|
|
|
290
291
|
|
|
291
292
|
function formatLine({ state, usage, isActive, now = Date.now() }) {
|
|
292
293
|
const project = basename(state.projectPath || '?');
|
|
293
|
-
const shortId = state
|
|
294
|
+
const shortId = formatShortSessionId(state);
|
|
294
295
|
const host = state.host === 'codex' ? 'Codex' : state.host === 'unknown' ? 'Unknown' : 'Claude';
|
|
295
296
|
const tokens = usage?.tokens ?? 0;
|
|
296
297
|
const max = usage?.contextWindowSize ?? 200_000;
|
|
@@ -318,11 +319,8 @@ function formatLine({ state, usage, isActive, now = Date.now() }) {
|
|
|
318
319
|
const tokCol = `${formatNumber(tokens).padStart(6)} / ${formatNumber(max).padStart(6)}`;
|
|
319
320
|
const estimateMark = usage?.estimated ? ' est' : '';
|
|
320
321
|
const windowMark = usage?.contextWindowEstimated ? ' win?' : '';
|
|
321
|
-
const liveMark = usage?.liveTurn && usage?.transientOutputTokens
|
|
322
|
-
? ` live+${formatNumber(usage.transientOutputTokens)}`
|
|
323
|
-
: '';
|
|
324
322
|
const modelCol = usage?.model
|
|
325
|
-
? color(ANSI.dim, `${usage.model}${estimateMark}${windowMark}
|
|
323
|
+
? color(ANSI.dim, `${usage.model}${estimateMark}${windowMark}`)
|
|
326
324
|
: color(ANSI.dim, '(未取得)');
|
|
327
325
|
// 最終更新からの経過: 表示が「止まって見える」とき、それが idle なのか障害なのかを
|
|
328
326
|
// 即座に判別できるようにする。updatedAt は state.writeSessionState 時の Date.now()。
|
|
@@ -337,6 +335,14 @@ function formatLine({ state, usage, isActive, now = Date.now() }) {
|
|
|
337
335
|
return `${marker} ${projectCol} ${hostCol} ${idCol} ${agoCol} ${barCol} ${tokCol} ${modelCol}${warn}`;
|
|
338
336
|
}
|
|
339
337
|
|
|
338
|
+
function formatShortSessionId(state) {
|
|
339
|
+
const sessionId = String(state?.sessionId ?? '');
|
|
340
|
+
if (state?.host === 'codex' && sessionId.startsWith('codex:')) {
|
|
341
|
+
return sessionId.slice('codex:'.length, 'codex:'.length + 8);
|
|
342
|
+
}
|
|
343
|
+
return sessionId.slice(0, 8);
|
|
344
|
+
}
|
|
345
|
+
|
|
340
346
|
function statFile(path) {
|
|
341
347
|
if (!path || !existsSync(path)) return null;
|
|
342
348
|
try {
|
|
@@ -375,6 +381,82 @@ function resolveMonitorUsage(state) {
|
|
|
375
381
|
return state.usage ?? null;
|
|
376
382
|
}
|
|
377
383
|
|
|
384
|
+
let lastCodexDiscoveryError = null;
|
|
385
|
+
|
|
386
|
+
function reportCodexDiscoveryError(err) {
|
|
387
|
+
const msg = err instanceof Error ? err.message : 'unknown error';
|
|
388
|
+
if (msg === lastCodexDiscoveryError) return;
|
|
389
|
+
lastCodexDiscoveryError = msg;
|
|
390
|
+
process.stderr.write(`[Throughline] codex discovery error: ${msg}\n`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function codexDiscoveryOptions(args = {}, cwd = process.cwd()) {
|
|
394
|
+
const allProjects = Boolean(args.all || args.session);
|
|
395
|
+
return {
|
|
396
|
+
projectPath: cwd,
|
|
397
|
+
allProjects,
|
|
398
|
+
limit: allProjects ? 100 : 30,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function discoverCodexSessionStates(args = {}, cwd = process.cwd()) {
|
|
403
|
+
let candidates;
|
|
404
|
+
try {
|
|
405
|
+
candidates = listCodexThreadCandidates(codexDiscoveryOptions(args, cwd));
|
|
406
|
+
} catch (err) {
|
|
407
|
+
reportCodexDiscoveryError(err);
|
|
408
|
+
return [];
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
lastCodexDiscoveryError = null;
|
|
412
|
+
return candidates.map((candidate) => ({
|
|
413
|
+
sessionId: `codex:${candidate.id}`,
|
|
414
|
+
host: 'codex',
|
|
415
|
+
projectPath: normalizeProjectPath(candidate.cwd ?? cwd),
|
|
416
|
+
transcriptPath: null,
|
|
417
|
+
rolloutPath: candidate.rolloutPath,
|
|
418
|
+
pid: null,
|
|
419
|
+
updatedAt: codexCandidateUpdatedAt(candidate),
|
|
420
|
+
discoveredFrom: 'codex-rollout-discovery',
|
|
421
|
+
}));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function codexCandidateUpdatedAt(candidate) {
|
|
425
|
+
const mtime = Number(candidate?.mtimeMs);
|
|
426
|
+
if (Number.isFinite(mtime) && mtime > 0) return mtime;
|
|
427
|
+
const parsed = Date.parse(candidate?.updatedAt ?? '');
|
|
428
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function mergeCodexDiscoveredStates(states, discovered) {
|
|
432
|
+
const bySession = new Map();
|
|
433
|
+
for (const state of states) bySession.set(state.sessionId, state);
|
|
434
|
+
|
|
435
|
+
for (const state of discovered) {
|
|
436
|
+
const existing = bySession.get(state.sessionId);
|
|
437
|
+
if (!existing) {
|
|
438
|
+
bySession.set(state.sessionId, state);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
bySession.set(state.sessionId, {
|
|
443
|
+
...existing,
|
|
444
|
+
host: existing.host ?? state.host,
|
|
445
|
+
projectPath: existing.projectPath || state.projectPath,
|
|
446
|
+
rolloutPath: state.rolloutPath ?? existing.rolloutPath,
|
|
447
|
+
updatedAt: Math.max(Number(existing.updatedAt) || 0, Number(state.updatedAt) || 0),
|
|
448
|
+
discoveredFrom: state.discoveredFrom,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return Array.from(bySession.values());
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function readMonitorStates(args = {}, cwd = process.cwd()) {
|
|
456
|
+
const states = readAllSessionStates();
|
|
457
|
+
return mergeCodexDiscoveredStates(states, discoverCodexSessionStates(args, cwd));
|
|
458
|
+
}
|
|
459
|
+
|
|
378
460
|
// --- フィルタ ---
|
|
379
461
|
/**
|
|
380
462
|
* セッション一覧に表示フィルタを適用する。
|
|
@@ -406,7 +488,7 @@ let lastRenderKey = '';
|
|
|
406
488
|
* 注: state-file の mtime は Stop hook のタイミングで更新されるが、
|
|
407
489
|
* transcript / rollout は実行中に太る。その live file 変化も render key に含める。
|
|
408
490
|
*/
|
|
409
|
-
function computeRenderKey() {
|
|
491
|
+
function computeRenderKey(args = {}, cwd = process.cwd()) {
|
|
410
492
|
const parts = [];
|
|
411
493
|
// state mtimes
|
|
412
494
|
const mtimes = snapshotStateMtimes();
|
|
@@ -414,7 +496,7 @@ function computeRenderKey() {
|
|
|
414
496
|
for (const name of names) parts.push(`s:${name}:${mtimes.get(name)}`);
|
|
415
497
|
// live transcript / rollout sizes(state ファイルを読まずに直接 stat、IO 最小化)
|
|
416
498
|
try {
|
|
417
|
-
const states =
|
|
499
|
+
const states = readMonitorStates(args, cwd);
|
|
418
500
|
for (const st of states) {
|
|
419
501
|
for (const [kind, path] of [['t', st.transcriptPath], ['r', st.rolloutPath]]) {
|
|
420
502
|
if (!path || !existsSync(path)) continue;
|
|
@@ -435,8 +517,8 @@ function computeRenderKey() {
|
|
|
435
517
|
/**
|
|
436
518
|
* 前回と比べてキーが変化していれば true。副作用として lastRenderKey を更新する。
|
|
437
519
|
*/
|
|
438
|
-
function needsRerender() {
|
|
439
|
-
const key = computeRenderKey();
|
|
520
|
+
function needsRerender(args = {}, cwd = process.cwd()) {
|
|
521
|
+
const key = computeRenderKey(args, cwd);
|
|
440
522
|
if (key !== lastRenderKey) {
|
|
441
523
|
lastRenderKey = key;
|
|
442
524
|
return true;
|
|
@@ -451,7 +533,7 @@ function resetRenderKeyCache() {
|
|
|
451
533
|
|
|
452
534
|
function renderFrame(args) {
|
|
453
535
|
const now = Date.now();
|
|
454
|
-
const states =
|
|
536
|
+
const states = readMonitorStates(args).map((state) => withLiveActivity(state, now));
|
|
455
537
|
const filtered = filterStates(states, args, process.cwd()).sort(
|
|
456
538
|
(a, b) => b.updatedAt - a.updatedAt,
|
|
457
539
|
);
|
|
@@ -665,7 +747,7 @@ export function main() {
|
|
|
665
747
|
safeRenderFrame(args);
|
|
666
748
|
return;
|
|
667
749
|
}
|
|
668
|
-
if (needsRerender()) safeRenderFrame(args);
|
|
750
|
+
if (needsRerender(args)) safeRenderFrame(args);
|
|
669
751
|
if (Date.now() - lastTimeAgoRefresh > TIME_AGO_REFRESH_MS) {
|
|
670
752
|
lastTimeAgoRefresh = Date.now();
|
|
671
753
|
safeRenderFrame(args);
|
|
@@ -741,6 +823,10 @@ export const _internal = {
|
|
|
741
823
|
liveActivityMs,
|
|
742
824
|
withLiveActivity,
|
|
743
825
|
resolveMonitorUsage,
|
|
826
|
+
codexDiscoveryOptions,
|
|
827
|
+
discoverCodexSessionStates,
|
|
828
|
+
mergeCodexDiscoveredStates,
|
|
829
|
+
readMonitorStates,
|
|
744
830
|
};
|
|
745
831
|
|
|
746
832
|
// --- エントリポイント自動起動 ---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { test } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
|
|
@@ -22,6 +22,8 @@ const {
|
|
|
22
22
|
setMeasuredColumns,
|
|
23
23
|
withLiveActivity,
|
|
24
24
|
resolveMonitorUsage,
|
|
25
|
+
discoverCodexSessionStates,
|
|
26
|
+
mergeCodexDiscoveredStates,
|
|
25
27
|
} = _internal;
|
|
26
28
|
|
|
27
29
|
// state-file は projectPath を resolve + lowercase 正規化する。
|
|
@@ -244,6 +246,73 @@ test('resolveMonitorUsage: live Codex rollout overrides stale Stop snapshot', ()
|
|
|
244
246
|
}
|
|
245
247
|
});
|
|
246
248
|
|
|
249
|
+
test('discoverCodexSessionStates: finds Codex rollout without a monitor state file', () => {
|
|
250
|
+
const home = mkdtempSync(join(tmpdir(), 'tl-monitor-codex-home-'));
|
|
251
|
+
const project = mkdtempSync(join(tmpdir(), 'tl-monitor-codex-project-'));
|
|
252
|
+
const oldHome = process.env.CODEX_HOME;
|
|
253
|
+
try {
|
|
254
|
+
process.env.CODEX_HOME = home;
|
|
255
|
+
const threadId = '019e085c-d3b9-7b43-8445-0efe32416e3d';
|
|
256
|
+
const dir = join(home, 'sessions', '2026', '05', '09');
|
|
257
|
+
mkdirSync(dir, { recursive: true });
|
|
258
|
+
const rollout = join(dir, `rollout-2026-05-09T01-12-41-${threadId}.jsonl`);
|
|
259
|
+
writeFileSync(
|
|
260
|
+
rollout,
|
|
261
|
+
JSON.stringify({
|
|
262
|
+
type: 'session_meta',
|
|
263
|
+
payload: {
|
|
264
|
+
id: threadId,
|
|
265
|
+
cwd: project,
|
|
266
|
+
source: 'vscode',
|
|
267
|
+
cli_version: '0.129.0-alpha.15',
|
|
268
|
+
},
|
|
269
|
+
}) + '\n',
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const states = discoverCodexSessionStates({ all: false, session: null }, project);
|
|
273
|
+
|
|
274
|
+
assert.equal(states.length, 1);
|
|
275
|
+
assert.equal(states[0].sessionId, `codex:${threadId}`);
|
|
276
|
+
assert.equal(states[0].host, 'codex');
|
|
277
|
+
assert.equal(states[0].projectPath, normalizeProjectPath(project));
|
|
278
|
+
assert.equal(states[0].rolloutPath, rollout);
|
|
279
|
+
assert.equal(states[0].discoveredFrom, 'codex-rollout-discovery');
|
|
280
|
+
} finally {
|
|
281
|
+
if (oldHome === undefined) delete process.env.CODEX_HOME;
|
|
282
|
+
else process.env.CODEX_HOME = oldHome;
|
|
283
|
+
rmSync(home, { recursive: true, force: true });
|
|
284
|
+
rmSync(project, { recursive: true, force: true });
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('mergeCodexDiscoveredStates: discovered rollout updates an existing Codex state entry', () => {
|
|
289
|
+
const existing = {
|
|
290
|
+
sessionId: 'codex:thread-a',
|
|
291
|
+
host: 'codex',
|
|
292
|
+
projectPath: CWD_FOO,
|
|
293
|
+
transcriptPath: null,
|
|
294
|
+
rolloutPath: '/old/rollout.jsonl',
|
|
295
|
+
updatedAt: 100,
|
|
296
|
+
usage: { tokens: 1 },
|
|
297
|
+
};
|
|
298
|
+
const discovered = {
|
|
299
|
+
sessionId: 'codex:thread-a',
|
|
300
|
+
host: 'codex',
|
|
301
|
+
projectPath: CWD_FOO,
|
|
302
|
+
transcriptPath: null,
|
|
303
|
+
rolloutPath: '/new/rollout.jsonl',
|
|
304
|
+
updatedAt: 200,
|
|
305
|
+
discoveredFrom: 'codex-rollout-discovery',
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const states = mergeCodexDiscoveredStates([existing], [discovered]);
|
|
309
|
+
|
|
310
|
+
assert.equal(states.length, 1);
|
|
311
|
+
assert.equal(states[0].rolloutPath, '/new/rollout.jsonl');
|
|
312
|
+
assert.equal(states[0].updatedAt, 200);
|
|
313
|
+
assert.deepEqual(states[0].usage, { tokens: 1 });
|
|
314
|
+
});
|
|
315
|
+
|
|
247
316
|
// ─── cellWidth ─────────────────────────────────────────────────────
|
|
248
317
|
|
|
249
318
|
test('cellWidth: ASCII は 1 セル', () => {
|
|
@@ -451,7 +520,18 @@ test('formatLine: Codex estimated usage は host と est marker を表示する'
|
|
|
451
520
|
assert.ok(out.includes('codex est win?'));
|
|
452
521
|
});
|
|
453
522
|
|
|
454
|
-
test('formatLine: Codex
|
|
523
|
+
test('formatLine: Codex session id は host prefix を外して 8 桁表示する', () => {
|
|
524
|
+
const args = makeLineArgs(0.5);
|
|
525
|
+
args.state.host = 'codex';
|
|
526
|
+
args.state.sessionId = 'codex:019e085c-d3b9-7b43-8445-0efe32416e3d';
|
|
527
|
+
|
|
528
|
+
const out = stripColors(formatLine(args));
|
|
529
|
+
|
|
530
|
+
assert.ok(out.includes('019e085c'), `expected raw Codex short id in output: ${out}`);
|
|
531
|
+
assert.ok(!out.includes('codex:01'), `did not expect prefixed short id in output: ${out}`);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
test('formatLine: Codex live turn usage は transient output marker を表示しない', () => {
|
|
455
535
|
const args = makeLineArgs(0.5);
|
|
456
536
|
args.state.host = 'codex';
|
|
457
537
|
args.usage = {
|
|
@@ -469,7 +549,8 @@ test('formatLine: Codex live turn usage は transient output marker を表示す
|
|
|
469
549
|
const out = stripColors(formatLine(args));
|
|
470
550
|
assert.ok(out.includes('Codex'));
|
|
471
551
|
assert.ok(out.includes('101.2k / 200.0k'));
|
|
472
|
-
assert.ok(out.includes('gpt-5.5
|
|
552
|
+
assert.ok(out.includes('gpt-5.5'));
|
|
553
|
+
assert.ok(!out.includes('live+'));
|
|
473
554
|
});
|
|
474
555
|
|
|
475
556
|
test('formatLine: 70% 以上で "!" マーカーと弱めの文言', () => {
|