throughline 0.4.2 → 0.4.4
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 +23 -0
- package/README.ja.md +8 -3
- package/README.md +30 -24
- package/codex/skills/throughline/SKILL.md +35 -30
- package/codex/skills/throughline/agents/openai.yaml +1 -1
- package/docs/THROUGHLINE_CODEX_FIRST_ROADMAP.md +3 -3
- package/docs/throughline-rollback-context-trim-insight.md +2 -0
- package/package.json +1 -1
- package/src/cli/install.mjs +14 -0
- package/src/cli/install.test.mjs +37 -2
- package/src/state-file.mjs +3 -4
- package/src/token-monitor.mjs +62 -22
- package/src/token-monitor.test.mjs +116 -0
- package/src/turn-processor.mjs +2 -3
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,29 @@ shipped to npm but were not individually tagged on GitHub.
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.4.4] — 2026-05-09
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Token monitor now treats Claude transcript and Codex rollout files as live
|
|
18
|
+
inputs. State-file `usage` snapshots remain a fallback, but the display and
|
|
19
|
+
stale hiding no longer wait for Stop hook completion when the live files are
|
|
20
|
+
still changing.
|
|
21
|
+
- `throughline install` now provisions or repairs the current project's VS Code
|
|
22
|
+
`Throughline Monitor` task when running under VS Code / Cursor / VSCodium, so
|
|
23
|
+
monitor auto-start setup no longer depends solely on the first hook event.
|
|
24
|
+
|
|
25
|
+
## [0.4.3] — 2026-05-09
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Changed the installed Codex `$throughline` skill so bare `$throughline` runs
|
|
30
|
+
the scripted current-thread refresh directly:
|
|
31
|
+
`throughline trim --execute --host codex --all --json`. Doctor, dry-run,
|
|
32
|
+
preflight, restore-safety analysis, host primitive audit, and fresh-thread
|
|
33
|
+
handoff remain available only when explicitly requested instead of being the
|
|
34
|
+
normal skill path.
|
|
35
|
+
|
|
13
36
|
## [0.4.2] — 2026-05-09
|
|
14
37
|
|
|
15
38
|
### Fixed
|
package/README.ja.md
CHANGED
|
@@ -188,7 +188,8 @@ adapter / projection として追加されます。
|
|
|
188
188
|
Codex sidecar を使えます。使えない場合は、従来どおり Claude Haiku 経路を使います。
|
|
189
189
|
|
|
190
190
|
Codex 側 trim (= same-thread context trim) は `throughline trim --execute --host codex`
|
|
191
|
-
で発火します。
|
|
191
|
+
で発火します。Codex の bare `$throughline` skill もこの scripted rollback + DB
|
|
192
|
+
memory inject を直接実行します。Claude 側は `/clear` での auto path 引継ぎが本線になったため、
|
|
192
193
|
`/tl-trim` slash command は v0.4.0 で廃止されました。current-work framing は
|
|
193
194
|
SessionStart 注入の Reading Contract / Continuation Instruction で同じ意図を
|
|
194
195
|
継承しています。
|
|
@@ -212,6 +213,10 @@ throughline monitor --session <id-prefix>
|
|
|
212
213
|
▶ Throughline 2ed5039c ████░░░░░░░░░░░░░░░░ 205.1k / 21% 残 794.9k claude-opus-4-6
|
|
213
214
|
```
|
|
214
215
|
|
|
216
|
+
監視中は Claude transcript / Codex rollout をライブに読み、Stop hook の state
|
|
217
|
+
snapshot はライブ usage が取れない場合の控えとして使います。これにより表示更新は
|
|
218
|
+
Stop 完了待ちではなくなります。
|
|
219
|
+
|
|
215
220
|
詳細仕様 (resize 追従、1M context 検出、ステイル隠し、Stop hook の非同期化など) は
|
|
216
221
|
[英語版 README](README.md#multi-session-token-monitor) を参照してください。
|
|
217
222
|
|
|
@@ -221,7 +226,7 @@ throughline monitor --session <id-prefix>
|
|
|
221
226
|
|
|
222
227
|
| コマンド | 役割 |
|
|
223
228
|
| --- | --- |
|
|
224
|
-
| `throughline install` |
|
|
229
|
+
| `throughline install` | hook / Codex skill を登録し、VS Code 配下なら現プロジェクトの monitor task も配置 |
|
|
225
230
|
| `throughline install --project` | 現リポジトリの `.claude/settings.json` だけに hook を登録 |
|
|
226
231
|
| `throughline uninstall` | hook を削除 |
|
|
227
232
|
| `throughline monitor` | マルチセッション監視を起動 |
|
|
@@ -233,7 +238,7 @@ throughline monitor --session <id-prefix>
|
|
|
233
238
|
| `throughline codex-sidecar-diagnostics` | この project の `codex-sidecar` diagnostics status を確認 |
|
|
234
239
|
| `throughline codex-sidecar-dry-run` | App Server を呼ばずに read-only sidecar request を正規化表示 |
|
|
235
240
|
| `throughline trim --dry-run --host codex` | Codex same-thread trim の dry-run preview |
|
|
236
|
-
| `throughline trim --execute --host codex` | Codex 同 thread の
|
|
241
|
+
| `throughline trim --execute --host codex` | Codex 同 thread の scripted rollback + DB memory inject |
|
|
237
242
|
| `throughline doctor --session <id-prefix>` | 特定セッションの state/transcript ズレを診断 |
|
|
238
243
|
| `throughline status` | DB 統計表示 (sessions / skeletons / bodies / details) |
|
|
239
244
|
| `throughline --version` | インストール済みバージョンを表示 |
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
npm install -g throughline
|
|
21
|
-
throughline install # registers
|
|
21
|
+
throughline install # registers hooks/skills and provisions the VS Code monitor task
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
That's it. Open any Claude Code session and your turns flow into
|
|
@@ -33,8 +33,10 @@ hook invokes the installed `bin/throughline.mjs` through an absolute Node path,
|
|
|
33
33
|
so Codex App Server PATH differences do not hide the command. It is registered
|
|
34
34
|
synchronously (`async: false`), matching the Codex hook behavior verified in
|
|
35
35
|
Caveat. Existing non-Throughline Codex hooks are preserved. It also installs a
|
|
36
|
-
global `$throughline` Codex skill
|
|
37
|
-
|
|
36
|
+
global `$throughline` Codex skill. Bare `$throughline` runs the scripted
|
|
37
|
+
current-thread rollback + Throughline DB memory injection directly; ask
|
|
38
|
+
explicitly for status, resume, summarize, diagnostics, or fresh-thread handoff
|
|
39
|
+
when you want those read-only surfaces instead.
|
|
38
40
|
|
|
39
41
|
## How it compares
|
|
40
42
|
|
|
@@ -344,6 +346,7 @@ throughline trim --dry-run --host codex --codex-thread-id <id>
|
|
|
344
346
|
throughline trim --dry-run --host codex --codex-thread-id <id> --preview-max-chars 4000
|
|
345
347
|
throughline trim --preflight --host codex --codex-thread-id <id>
|
|
346
348
|
CODEX_THREAD_ID=<id> throughline trim --preflight --host codex
|
|
349
|
+
throughline trim --execute --host codex --all --json
|
|
347
350
|
# read-only app-server process restart smoke; not full VS Code restart-safe proof:
|
|
348
351
|
# THROUGHLINE_EXPERIMENTAL_CODEX_RESTORE_SMOKE=1 throughline codex-restore-smoke --codex-thread-id <id> --json
|
|
349
352
|
# read-only local restore source inventory; not full VS Code restart-safe proof:
|
|
@@ -514,9 +517,10 @@ Example output:
|
|
|
514
517
|
(`input_tokens + cache_creation_input_tokens + cache_read_input_tokens`).
|
|
515
518
|
No `length / 4` approximation.
|
|
516
519
|
- **Codex token counts use the rollout `token_count` event when present.** The
|
|
517
|
-
Codex Stop hook writes `codex:<thread_id>` monitor state
|
|
518
|
-
|
|
519
|
-
|
|
520
|
+
Codex Stop hook writes `codex:<thread_id>` monitor state with the rollout
|
|
521
|
+
path. While the monitor is running it reads the live rollout every tick and
|
|
522
|
+
prefers the latest verified `token_count` sample. If a Codex rollout has no
|
|
523
|
+
token-count event, Throughline can show an explicit estimate with
|
|
520
524
|
`estimated: true` and the monitor marks it with `est`; it is not presented as
|
|
521
525
|
exact usage.
|
|
522
526
|
- **Codex auto-refresh mutates at the verified 90% threshold.** The Codex Stop
|
|
@@ -551,17 +555,17 @@ Example output:
|
|
|
551
555
|
frame so the previous, wrongly-sized frame can't stack beneath it.
|
|
552
556
|
- **Per-row "last updated" stamp.** Each session row carries an 8-cell
|
|
553
557
|
`just now` / `24m ago` stamp right after the session id, placed before the
|
|
554
|
-
bar so narrow terminals don't truncate it. It
|
|
555
|
-
|
|
556
|
-
|
|
558
|
+
bar so narrow terminals don't truncate it. It follows the newest state,
|
|
559
|
+
transcript, or Codex rollout mtime, so active sessions stay visible and the
|
|
560
|
+
stamp can move before the next Stop hook completes. When you need more detail,
|
|
557
561
|
`throughline doctor --session <id-prefix>` compares the state file against
|
|
558
562
|
the actual transcript JSONL and flags drift, idle time, and
|
|
559
563
|
`/clear`-induced transcript path staleness.
|
|
560
|
-
- **
|
|
561
|
-
persists the latest `tokens / model / contextWindowSize` back into
|
|
562
|
-
file. The monitor prefers
|
|
563
|
-
|
|
564
|
-
the
|
|
564
|
+
- **Live usage first, state snapshot as fallback.** When the Stop hook finishes
|
|
565
|
+
a turn it persists the latest `tokens / model / contextWindowSize` back into
|
|
566
|
+
the state file. The monitor now prefers live Claude transcript / Codex rollout
|
|
567
|
+
reads and uses the snapshot only when the live file cannot provide usage, so
|
|
568
|
+
the display no longer waits for Stop to update.
|
|
565
569
|
- **Host-aware state.** Missing `host` means an older Claude state file.
|
|
566
570
|
Codex states use `host: "codex"`, keep `transcriptPath: null`, and store the
|
|
567
571
|
Codex rollout path separately as `rolloutPath` so the Claude transcript parser
|
|
@@ -578,15 +582,17 @@ Example output:
|
|
|
578
582
|
|
|
579
583
|
### VS Code auto-start (automatic)
|
|
580
584
|
|
|
581
|
-
After `throughline install`,
|
|
582
|
-
gets `.vscode/tasks.json` provisioned
|
|
583
|
-
|
|
584
|
-
|
|
585
|
+
After `throughline install`, the current VS Code / Cursor / VSCodium project
|
|
586
|
+
gets `.vscode/tasks.json` provisioned immediately when VS Code environment
|
|
587
|
+
variables are present. Any other VS Code project you work in also gets the file
|
|
588
|
+
on the first session event. The file configures `runOn: folderOpen` so the
|
|
589
|
+
monitor appears in a dedicated terminal panel the next time you open that
|
|
590
|
+
folder.
|
|
585
591
|
|
|
586
|
-
**How it works.** `ensureMonitorTaskFile` is called from
|
|
587
|
-
(SessionStart, UserPromptSubmit, Stop)
|
|
588
|
-
first in your environment creates the file; the rest are idempotent
|
|
589
|
-
Once per project it inspects `.vscode/tasks.json`:
|
|
592
|
+
**How it works.** `ensureMonitorTaskFile` is called from `throughline install`
|
|
593
|
+
and from **all three hooks (SessionStart, UserPromptSubmit, Stop)**. Whichever
|
|
594
|
+
one fires first in your environment creates the file; the rest are idempotent
|
|
595
|
+
no-ops. Once per project it inspects `.vscode/tasks.json`:
|
|
590
596
|
|
|
591
597
|
- **No file yet** → creates one with a single `Throughline Monitor` task, and
|
|
592
598
|
emits a one-time `<system-reminder>` to stdout so Claude tells you a
|
|
@@ -644,7 +650,7 @@ entry to the `tasks` array yourself:
|
|
|
644
650
|
|
|
645
651
|
| Command | What it does |
|
|
646
652
|
| ---------------------------------------------- | ------------------------------------------------------------ |
|
|
647
|
-
| `throughline install` | Register Claude user hooks/slash commands, the global Codex Stop hook,
|
|
653
|
+
| `throughline install` | Register Claude user hooks/slash commands, the global Codex Stop hook, the global `$throughline` Codex skill, and the current VS Code monitor task when applicable |
|
|
648
654
|
| `throughline install --project` | Register Claude hooks/slash commands in this repo only |
|
|
649
655
|
| `throughline uninstall` | Remove Throughline-managed Claude hooks/slash commands, only the Throughline-managed Codex hook, and the `$throughline` Codex skill |
|
|
650
656
|
| `throughline monitor [--all] [--session <id>]` | Run the multi-session token monitor |
|
|
@@ -673,7 +679,7 @@ entry to the `tasks` array yourself:
|
|
|
673
679
|
| `throughline codex-sidecar-dry-run` | Print a normalized read-only sidecar request without running the app server |
|
|
674
680
|
| `throughline trim --dry-run --host codex` | Preview Codex same-thread context trim memory and host boundary; does not rollback automatically |
|
|
675
681
|
| `throughline trim --preflight --host codex` | Read/resume the explicit Codex thread and verify turn-count guards without rollback/inject |
|
|
676
|
-
| `throughline trim --execute --host codex` |
|
|
682
|
+
| `throughline trim --execute --host codex` | Scripted Codex current-thread rollback + Throughline DB memory inject; this is what bare `$throughline` runs in Codex |
|
|
677
683
|
| `throughline status` | Print DB statistics (sessions, skeletons, bodies, details) |
|
|
678
684
|
| `throughline --version` | Print the installed version |
|
|
679
685
|
|
|
@@ -8,20 +8,21 @@ description: Use when the user asks to use Throughline from Codex, continue or r
|
|
|
8
8
|
Use this skill to operate Throughline from Codex without making the user type long
|
|
9
9
|
commands.
|
|
10
10
|
|
|
11
|
-
If the user invokes `$throughline` by itself, treat that as a request to
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
If the user invokes `$throughline` by itself, treat that as a request to run the
|
|
12
|
+
scripted current-thread refresh now. The normal path is not an AI planning
|
|
13
|
+
exercise: it rolls back the current Codex thread and injects Throughline DB
|
|
14
|
+
memory using the original `/tl` contract.
|
|
15
15
|
|
|
16
16
|
## Core Rule
|
|
17
17
|
|
|
18
18
|
Do not ask the user for a Codex thread id when the current environment can
|
|
19
19
|
provide it. Prefer the current `CODEX_THREAD_ID` / `THROUGHLINE_CODEX_THREAD_ID`
|
|
20
|
-
identity
|
|
20
|
+
identity.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
For bare `$throughline`, do not run doctor / dry-run / handoff / preflight first
|
|
23
|
+
and do not ask for confirmation. Execute the script command directly. If it
|
|
24
|
+
fails, report the error plainly instead of silently falling back to another
|
|
25
|
+
memory source or a fresh-thread handoff.
|
|
25
26
|
|
|
26
27
|
## Common Requests
|
|
27
28
|
|
|
@@ -30,27 +31,21 @@ and hook logs first.
|
|
|
30
31
|
Run:
|
|
31
32
|
|
|
32
33
|
```bash
|
|
33
|
-
throughline
|
|
34
|
-
throughline trim --dry-run --host codex --all --json
|
|
35
|
-
throughline codex-handoff-start --session codex:<current-thread-id> --json
|
|
36
|
-
throughline trim --preflight --host codex --all --json
|
|
34
|
+
throughline trim --execute --host codex --all --json
|
|
37
35
|
```
|
|
38
36
|
|
|
39
|
-
This is the
|
|
40
|
-
rollback
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
without mutating the current thread. Report the preflight result, handoff-start
|
|
44
|
-
status, and context reduction estimate from the dry-run. Do not claim the
|
|
45
|
-
refresh happened unless `trim --execute` or auto-refresh actually ran.
|
|
37
|
+
This is the scripted Codex context-refresh flow. It mutates the current Codex
|
|
38
|
+
thread by sending rollback + Throughline DB memory injection. Report only the
|
|
39
|
+
execution status, whether rollback / inject were sent, whether durable evidence
|
|
40
|
+
was observed, and the selected memory session.
|
|
46
41
|
|
|
47
42
|
The injected memory must preserve the original `/tl` memory contract:
|
|
48
43
|
|
|
49
|
-
- older turns: L1 summaries
|
|
50
44
|
- recent work: L2 full bodies for the latest 20 turns
|
|
45
|
+
- older turns: L1 summaries
|
|
51
46
|
- L3: detail references only; L3 bodies / tool payloads are not injected
|
|
52
47
|
|
|
53
|
-
If
|
|
48
|
+
If there are no captured turns or no injectable Throughline DB memory, say that
|
|
54
49
|
clearly.
|
|
55
50
|
|
|
56
51
|
### "Throughline status" / "doctor"
|
|
@@ -104,9 +99,19 @@ back to Claude Haiku.
|
|
|
104
99
|
|
|
105
100
|
### "trim" / "rewind" / "rollback" / "context cleanup"
|
|
106
101
|
|
|
107
|
-
Default to the same
|
|
102
|
+
Default to the same scripted execute flow as bare `$throughline` when the user
|
|
108
103
|
asks to trim, rewind, rollback, clean up context, or use Throughline memory.
|
|
109
104
|
|
|
105
|
+
Execute:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
throughline trim --execute --host codex --all --json
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Report only the essential outcome. Do not introduce fresh-thread handoff,
|
|
112
|
+
restore-safety analysis, host primitive audit, or dry-run planning unless the
|
|
113
|
+
user explicitly asks for those diagnostics.
|
|
114
|
+
|
|
110
115
|
Preview:
|
|
111
116
|
|
|
112
117
|
```bash
|
|
@@ -141,17 +146,17 @@ Execute path:
|
|
|
141
146
|
throughline trim --execute --host codex --all
|
|
142
147
|
```
|
|
143
148
|
|
|
144
|
-
|
|
145
|
-
rollback + Throughline DB memory injection after the app-server guard checks.
|
|
149
|
+
This is the same command used by bare `$throughline`.
|
|
146
150
|
|
|
147
151
|
## User-Facing Explanation
|
|
148
152
|
|
|
149
153
|
Explain the behavior simply:
|
|
150
154
|
|
|
151
155
|
- normal Codex turn end: Stop hook captures DB memory and writes monitor state
|
|
152
|
-
- `$throughline` / context refresh:
|
|
153
|
-
|
|
154
|
-
-
|
|
155
|
-
|
|
156
|
-
-
|
|
157
|
-
|
|
156
|
+
- `$throughline` / context refresh: one script command mutates the current Codex
|
|
157
|
+
thread by rollback + memory inject
|
|
158
|
+
- injected memory is L2 latest 20 full bodies + older L1 summaries + L3
|
|
159
|
+
references only
|
|
160
|
+
- diagnostics such as doctor, dry-run, preflight, fresh-thread handoff, restore
|
|
161
|
+
safety, and host primitive audit are optional tools, not the normal
|
|
162
|
+
`$throughline` path
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Throughline"
|
|
3
3
|
short_description: "Operate Throughline memory, Codex capture, resume, and trim"
|
|
4
|
-
default_prompt: "Use $throughline to
|
|
4
|
+
default_prompt: "Use $throughline to run the scripted current-thread rollback + Throughline memory inject, or explicitly ask for status, resume, summarize, diagnostics, or fresh-thread handoff."
|
|
5
5
|
|
|
6
6
|
policy:
|
|
7
7
|
allow_implicit_invocation: true
|
|
@@ -440,7 +440,7 @@ Phase 5 implementation status (2026-05-06):
|
|
|
440
440
|
- [x] `doctor --codex` は Claude settings を変更しない。Codex primary entrypoint の診断に限定する。
|
|
441
441
|
- [x] 実セッションで `doctor --codex` -> `codex-capture` -> `codex-resume --format item-json` -> `doctor --codex` の local smoke を実施した。`codex-resume` は developer message item JSON を描画し、再診断で captured DB session が 1 件として表示された。
|
|
442
442
|
- [x] 実セッションで `codex-visibility-smoke` を実施した。`THROUGHLINE_EXPERIMENTAL_CODEX_MODEL_VISIBLE_SMOKE=1` を必須にし、長い model turn に備えて `--request-timeout-ms 150000` / `--timeout-ms 180000` を使えるようにした。
|
|
443
|
-
- [x] Codex primary の setup / install 手順は README に記録した。global install は Codex Stop hook と `$throughline` skill を自動登録する。2026-05-
|
|
443
|
+
- [x] Codex primary の setup / install 手順は README に記録した。global install は Codex Stop hook と `$throughline` skill を自動登録する。2026-05-09 以降、bare `$throughline` は AI による診断 flow ではなく `trim --execute --host codex --all --json` を直接走らせる scripted current-thread refresh とする。diagnostics / dry-run / preflight / fresh-thread handoff は明示要求時だけ使う。
|
|
444
444
|
- [x] `codex-summarize` を明示診断・運用 flow に追加した。Codex CLI backend を使い、Claude Haiku へ fallback しない。
|
|
445
445
|
- [x] Codex primary の summarize / guarded execute まで含む end-to-end smoke を実施した。2026-05-07 correction では guarded execute を live app-server smoke としてのみ扱った。2026-05-08 unblock 後は、controlled rollback model-visible smoke の `not-reproduced` と current-thread live run の `execute-durable-verified` を合わせて、Codex current-thread trim 完了条件に含める。
|
|
446
446
|
- `codex-capture`: `capturedTurns = 41`, `capturedRows = 75`, `capturedDetails = 2424`
|
|
@@ -459,8 +459,8 @@ Phase 5 implementation status (2026-05-06):
|
|
|
459
459
|
- 絶対パス型の再実測: `npm install -g .` -> `throughline install` 後、`~/.codex/hooks.json` の Stop 先頭は `/usr/bin/node /home/kite/projects/Throughline/bin/throughline.mjs codex-hook stop`、`async: false`、`timeoutSec: 300` になり、Caveat / Spotter hooks は 2 番目以降に保持された。`doctor --codex` は `Codex hooks feature: enabled` / `Codex Stop hook: registered` を表示した。`codex exec --json -C /home/kite/projects/Throughline "Reply exactly: TL_CODEX_ABSOLUTE_STOP_SMOKE_20260506"` で child thread `019dfd5e-1248-7c11-8ddc-97e1b0701e10` が作られ、直後の `throughline doctor --codex` で latest DB session が `codex:019dfd5e-1248-7c11-8ddc-97e1b0701e10` に進んだ。
|
|
460
460
|
- 追加確認: current VSCode-origin parent thread `019dfd38-c530-71c3-b7b8-180bdd3054bc` は hook shape 変更前に開始していたため、変更後の自然 Stop smoke としては不適格。assistant final 後に rollout は `task_complete` まで進んだが latest DB session は exec child のままだった。次に VSCode-origin を見る場合は、hook shape 変更後に新しく開始した Codex session で確認する。
|
|
461
461
|
- 最終確認: hook shape 変更後に新しく開始した VSCode-origin Codex session で 1 turn 完了後、`throughline doctor --codex` を実行した。`current Codex thread` は `019dfd62-9a9d-7211-bf91-89d8e3fc908e`、`latest DB session` は `codex:019dfd62-9a9d-7211-bf91-89d8e3fc908e` で一致し、`Codex hooks feature: enabled`、`Codex Stop hook: registered`、command は `/usr/bin/node /home/kite/projects/Throughline/bin/throughline.mjs codex-hook stop`、`async: false`、`timeoutSec: 300` だった。VSCode-origin の自然 Stop hook による DB capture も解決済みとして扱う。
|
|
462
|
-
- 2026-05-07 correction: 以下の Codex trim smoke は live app-server primitive の履歴として残す。ただし 2026-05-06 incident 後、restart / reconnect 越しの durable context trim
|
|
463
|
-
- UX 修正:
|
|
462
|
+
- historical 2026-05-07 correction: 以下の Codex trim smoke は live app-server primitive の履歴として残す。ただし 2026-05-06 incident 後、restart / reconnect 越しの durable context trim 成功とは一時的に扱わず、`$throughline` / Codex Stop hook の automatic mutation を止めていた。
|
|
463
|
+
- 2026-05-09 UX 修正: bare `$throughline` は `doctor --codex` / `trim --dry-run --all` / `trim --preflight --all` を AI に順番実行させる説明 surface ではなく、`throughline trim --execute --host codex --all --json` を直接実行する scripted current-thread refresh に戻す。注入 memory は L2 最新 20 full bodies + L1 older summaries + L3 references only。diagnostics / dry-run / preflight / fresh-thread handoff は明示要求時だけ使う。
|
|
464
464
|
- memory contract 修正: Codex guarded trim でも注入 memory は元の `/tl` 思想を正とする。古い turn は L1 summaries、直近 20 turn は L2 full bodies、L3 は reference only で、L3 bodies / tool payloads は注入しない。rollout source は rollback candidate / app-server turn count guard の根拠であり、Throughline DB memory がある場合に rollout active work preview を注入 memory として使わない。DB memory が無い execute は rollout preview を注入せず、mutation 前に拒否する。
|
|
465
465
|
- doctor visibility 修正: `doctor --codex` は旧 context refresh readiness として rollback source、inject memory source、memory contract、L1 summaries / recent L2 bodies / L3 references-only count、heuristic reduction estimate を表示していた。実 thread `019dfd62-9a9d-7211-bf91-89d8e3fc908e` では live readiness として `context refresh: ready`、`rollback source: codex-rollout`、`inject memory source: throughline-db`、`memory contract: older L1 + latest 20 L2 full bodies + L3 references only` を確認した。ただし incident 後は restart-safe readiness ではない。
|
|
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 で削減できない」という意味ではない。
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
|
|
22
22
|
2026-05-08 unblock: その後の切り分けで、incident thread の retained text は app-server response 上では `aggregatedOutput` など quoted/tool-output field に分類され、controlled rollback model-visible smoke は app-server restart 境界と VS Code reload/reconnect 境界の両方で `not-reproduced` だった。これを受け、Codex `trim --execute --host codex` と Stop hook auto-refresh の過剰 blocker は解除する。`compacted.replacement_history` retention、restore-safety risk、host primitive audit は引き続き diagnostics だが、単独では mutation 前 refusal にしない。DB memory 不在と rollout/app-server turn-count 不一致は引き続き mutation 前 blocker。
|
|
23
23
|
|
|
24
|
+
2026-05-09 skill UX: bare `$throughline` は diagnostics / dry-run / preflight を AI に順番実行させる surface ではなく、`throughline trim --execute --host codex --all --json` を直接走らせる scripted current-thread refresh とする。目的は rollback と、Throughline DB の L2 最新 20 full bodies + older L1 summaries + L3 references-only memory injection のみ。doctor / dry-run / preflight / fresh-thread handoff / restore-safety / host primitive audit は明示診断用であり、通常 `$throughline` の前段にはしない。
|
|
25
|
+
|
|
24
26
|
2026-05-07 host primitive audit: `throughline codex-host-primitive-audit` で installed Codex app-server schema を機械監査した。`thread/rollback` / `thread/inject_items` / `thread/compact/start` / `thread/start` / `thread/fork` / `thread/resume` は存在するが、rollback 済み user text を current-thread の model-visible input へ復活させない deletion / isolation / projection primitive は見つからなかった。`thread/resume(history)` は schema 上 `[UNSTABLE] FOR CODEX CLOUD - DO NOT USE` で、`thread_id` も ignored になるため、Throughline の current-thread repair primitive には採用しない。
|
|
25
27
|
|
|
26
28
|
## 概要
|
package/package.json
CHANGED
package/src/cli/install.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, copyFi
|
|
|
15
15
|
import { join, dirname, resolve, delimiter } from 'node:path';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
17
|
import { homedir } from 'node:os';
|
|
18
|
+
import { ensureMonitorTaskFile, shouldRecommendGitignore } from '../vscode-task.mjs';
|
|
18
19
|
|
|
19
20
|
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
20
21
|
const SLASH_COMMANDS_SRC = join(PACKAGE_ROOT, '.claude', 'commands');
|
|
@@ -400,6 +401,10 @@ export async function run(args = []) {
|
|
|
400
401
|
const { installed: installedCommands, skipped } = installSlashCommands(commandsDir);
|
|
401
402
|
const codex = args.includes('--project') ? null : installCodexHooks();
|
|
402
403
|
const codexSkills = args.includes('--project') ? { installed: [], skipped: null } : installCodexSkills(codexSkillsDir);
|
|
404
|
+
const monitorTask = ensureMonitorTaskFile({
|
|
405
|
+
cwd: process.cwd(),
|
|
406
|
+
env: { ...process.env, THROUGHLINE_SUPPRESS_VSCODE_NOTICES: '1' },
|
|
407
|
+
});
|
|
403
408
|
|
|
404
409
|
const scope = args.includes('--project') ? 'プロジェクトローカル' : 'グローバル(全プロジェクト)';
|
|
405
410
|
console.log(`Throughline hooks をインストールしました [${scope}]`);
|
|
@@ -436,6 +441,15 @@ export async function run(args = []) {
|
|
|
436
441
|
console.log('注意: パッケージ内に Codex skills のソースが見つからないためスキップしました。');
|
|
437
442
|
console.log('');
|
|
438
443
|
}
|
|
444
|
+
if (monitorTask.action === 'created' || monitorTask.action === 'merged' || monitorTask.action === 'repaired') {
|
|
445
|
+
console.log(`VSCode monitor task を${monitorTask.action === 'repaired' ? '修復' : '配置'}しました:`);
|
|
446
|
+
console.log(` ${monitorTask.path}`);
|
|
447
|
+
console.log(' 既に VSCode でこのフォルダを開いている場合は Developer: Reload Window を 1 回実行してください。');
|
|
448
|
+
if (shouldRecommendGitignore(process.cwd())) {
|
|
449
|
+
console.log(' 共有リポジトリでは .vscode/tasks.json を .gitignore に追加することを推奨します。');
|
|
450
|
+
}
|
|
451
|
+
console.log('');
|
|
452
|
+
}
|
|
439
453
|
console.log(' アンインストール: throughline uninstall');
|
|
440
454
|
|
|
441
455
|
if (!resolveThroughlineOnPath()) {
|
package/src/cli/install.test.mjs
CHANGED
|
@@ -30,13 +30,16 @@ function silence() {
|
|
|
30
30
|
const origLog = console.log;
|
|
31
31
|
const origErr = console.error;
|
|
32
32
|
const origStderrWrite = process.stderr.write.bind(process.stderr);
|
|
33
|
+
const origStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
33
34
|
console.log = () => {};
|
|
34
35
|
console.error = () => {};
|
|
35
36
|
process.stderr.write = () => true;
|
|
37
|
+
process.stdout.write = () => true;
|
|
36
38
|
return () => {
|
|
37
39
|
console.log = origLog;
|
|
38
40
|
console.error = origErr;
|
|
39
41
|
process.stderr.write = origStderrWrite;
|
|
42
|
+
process.stdout.write = origStdoutWrite;
|
|
40
43
|
};
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -133,16 +136,48 @@ test('global install copies Throughline Codex skill to ~/.codex/skills/', async
|
|
|
133
136
|
const metadataBody = readFileSync(metadata, 'utf8');
|
|
134
137
|
assert.match(skillBody, /name: throughline/);
|
|
135
138
|
assert.match(skillBody, /Bare "\$throughline"/);
|
|
136
|
-
assert.match(skillBody, /throughline codex-handoff-start --session codex:<current-thread-id> --json/);
|
|
137
139
|
assert.match(skillBody, /throughline trim --execute --host codex --all/);
|
|
138
|
-
assert.match(
|
|
140
|
+
assert.match(skillBody, /do not run doctor \/ dry-run \/ handoff \/ preflight first/);
|
|
141
|
+
assert.match(metadataBody, /scripted current-thread rollback \+ Throughline memory inject/);
|
|
139
142
|
assert.doesNotMatch(metadataBody, /preview blocked Codex trim/);
|
|
143
|
+
assert.doesNotMatch(metadataBody, /inspect guarded Codex trim/);
|
|
140
144
|
} finally {
|
|
141
145
|
unsilence();
|
|
142
146
|
home.restore();
|
|
143
147
|
}
|
|
144
148
|
});
|
|
145
149
|
|
|
150
|
+
test('global install provisions VSCode monitor task for the current project when running under VSCode', async () => {
|
|
151
|
+
const home = makeTempHome();
|
|
152
|
+
if (home.resolved !== home.dir) {
|
|
153
|
+
home.restore();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const projectDir = mkdtempSync(join(tmpdir(), 'tl-install-monitor-'));
|
|
157
|
+
const origCwd = process.cwd();
|
|
158
|
+
const origVscodePid = process.env.VSCODE_PID;
|
|
159
|
+
process.chdir(projectDir);
|
|
160
|
+
process.env.VSCODE_PID = '12345';
|
|
161
|
+
const unsilence = silence();
|
|
162
|
+
try {
|
|
163
|
+
await run([]);
|
|
164
|
+
const tasksPath = join(projectDir, '.vscode', 'tasks.json');
|
|
165
|
+
assert.ok(existsSync(tasksPath), 'install should create current-project VSCode tasks.json');
|
|
166
|
+
const tasks = JSON.parse(readFileSync(tasksPath, 'utf8'));
|
|
167
|
+
const task = tasks.tasks.find((t) => t.label === 'Throughline Monitor');
|
|
168
|
+
assert.ok(task, 'Throughline Monitor task should be present');
|
|
169
|
+
assert.deepEqual(task.args.slice(1), ['monitor']);
|
|
170
|
+
assert.deepEqual(task.runOptions, { runOn: 'folderOpen' });
|
|
171
|
+
} finally {
|
|
172
|
+
unsilence();
|
|
173
|
+
if (origVscodePid === undefined) delete process.env.VSCODE_PID;
|
|
174
|
+
else process.env.VSCODE_PID = origVscodePid;
|
|
175
|
+
process.chdir(origCwd);
|
|
176
|
+
home.restore();
|
|
177
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
146
181
|
test('global install preserves existing Codex hooks and is idempotent', async () => {
|
|
147
182
|
const home = makeTempHome();
|
|
148
183
|
if (home.resolved !== home.dir) {
|
package/src/state-file.mjs
CHANGED
|
@@ -47,10 +47,9 @@ export function normalizeProjectPath(p) {
|
|
|
47
47
|
* host?: 'claude'|'codex',
|
|
48
48
|
* }} data
|
|
49
49
|
*
|
|
50
|
-
* usage:
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* transcriptPath を読んでフォールバック)。
|
|
50
|
+
* usage: Stop hook 完了時点の tokens/model/contextWindowSize fallback snapshot。
|
|
51
|
+
* monitor はライブ transcript / rollout を優先して読み、ライブ usage が取れない場合だけ
|
|
52
|
+
* この snapshot を使う。旧バージョン互換のため optional。
|
|
54
53
|
*/
|
|
55
54
|
export function writeSessionState({
|
|
56
55
|
sessionId,
|
package/src/token-monitor.mjs
CHANGED
|
@@ -13,16 +13,18 @@
|
|
|
13
13
|
* - 状態ファイルはセッション単位 (~/.throughline/state/<session_id>.json)
|
|
14
14
|
* - setInterval (1s) + mtime 差分検知で更新を捕捉
|
|
15
15
|
* - updatedAt 降順ソート、先頭行を ▶ でハイライト
|
|
16
|
-
* - stale は
|
|
16
|
+
* - stale は state 更新時刻 + live transcript / rollout mtime で判定
|
|
17
17
|
* - Claude は transcript JSONL の最新 assistant usage を直読
|
|
18
|
-
* - Codex は
|
|
18
|
+
* - Codex は rollout JSONL の token_count / active-text estimate を直読
|
|
19
|
+
* - state.usage は live ファイルが読めない場合の最後の既知値としてだけ使う
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
import { basename, dirname, join } from 'node:path';
|
|
22
23
|
import { stripVTControlCharacters } from 'node:util';
|
|
23
24
|
import { statSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
24
25
|
import { homedir } from 'node:os';
|
|
25
|
-
import { getStateDir, readAllSessionStates, snapshotStateMtimes, normalizeProjectPath } from './state-file.mjs';
|
|
26
|
+
import { getStateDir, readAllSessionStates, snapshotStateMtimes, normalizeProjectPath, STALE_HIDE_MS } from './state-file.mjs';
|
|
27
|
+
import { buildCodexMonitorUsage } from './codex-usage.mjs';
|
|
26
28
|
import { readLatestUsage } from './transcript-usage.mjs';
|
|
27
29
|
import { startSizeQuery } from './terminal-size.mjs';
|
|
28
30
|
|
|
@@ -332,6 +334,44 @@ function formatLine({ state, usage, isActive, now = Date.now() }) {
|
|
|
332
334
|
return `${marker} ${projectCol} ${hostCol} ${idCol} ${agoCol} ${barCol} ${tokCol} ${modelCol}${warn}`;
|
|
333
335
|
}
|
|
334
336
|
|
|
337
|
+
function statFile(path) {
|
|
338
|
+
if (!path || !existsSync(path)) return null;
|
|
339
|
+
try {
|
|
340
|
+
return statSync(path);
|
|
341
|
+
} catch {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function liveActivityMs(state) {
|
|
347
|
+
const transcript = statFile(state.transcriptPath);
|
|
348
|
+
const rollout = statFile(state.rolloutPath);
|
|
349
|
+
return Math.max(
|
|
350
|
+
Number(state.updatedAt) || 0,
|
|
351
|
+
transcript?.mtimeMs ?? 0,
|
|
352
|
+
rollout?.mtimeMs ?? 0,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function withLiveActivity(state, now = Date.now()) {
|
|
357
|
+
const updatedAt = liveActivityMs(state);
|
|
358
|
+
return {
|
|
359
|
+
...state,
|
|
360
|
+
updatedAt,
|
|
361
|
+
stale: now - updatedAt > STALE_HIDE_MS,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function resolveMonitorUsage(state) {
|
|
366
|
+
if (state.host === 'codex' && state.rolloutPath) {
|
|
367
|
+
return buildCodexMonitorUsage(state.rolloutPath) ?? state.usage ?? null;
|
|
368
|
+
}
|
|
369
|
+
if (state.transcriptPath) {
|
|
370
|
+
return readLatestUsage(state.transcriptPath) ?? state.usage ?? null;
|
|
371
|
+
}
|
|
372
|
+
return state.usage ?? null;
|
|
373
|
+
}
|
|
374
|
+
|
|
335
375
|
// --- フィルタ ---
|
|
336
376
|
/**
|
|
337
377
|
* セッション一覧に表示フィルタを適用する。
|
|
@@ -356,12 +396,12 @@ let lastRenderedLines = 0;
|
|
|
356
396
|
let lastRenderKey = '';
|
|
357
397
|
|
|
358
398
|
/**
|
|
359
|
-
* 再描画要否の判定キー。state ファイル群の mtime と transcript JSONL の
|
|
399
|
+
* 再描画要否の判定キー。state ファイル群の mtime と transcript / rollout JSONL の
|
|
400
|
+
* size + mtime を
|
|
360
401
|
* 1 本の文字列にまとめてハッシュキーとする。キーが前回と同じなら描画スキップ。
|
|
361
402
|
*
|
|
362
|
-
* 注:
|
|
363
|
-
*
|
|
364
|
-
* Stop hook のタイミングで更新され、transcript は Claude の stream 中に太る)。
|
|
403
|
+
* 注: state-file の mtime は Stop hook のタイミングで更新されるが、
|
|
404
|
+
* transcript / rollout は実行中に太る。その live file 変化も render key に含める。
|
|
365
405
|
*/
|
|
366
406
|
function computeRenderKey() {
|
|
367
407
|
const parts = [];
|
|
@@ -369,16 +409,18 @@ function computeRenderKey() {
|
|
|
369
409
|
const mtimes = snapshotStateMtimes();
|
|
370
410
|
const names = Array.from(mtimes.keys()).sort();
|
|
371
411
|
for (const name of names) parts.push(`s:${name}:${mtimes.get(name)}`);
|
|
372
|
-
// transcript sizes(state ファイルを読まずに直接 stat、IO 最小化)
|
|
412
|
+
// live transcript / rollout sizes(state ファイルを読まずに直接 stat、IO 最小化)
|
|
373
413
|
try {
|
|
374
414
|
const states = readAllSessionStates();
|
|
375
415
|
for (const st of states) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
416
|
+
for (const [kind, path] of [['t', st.transcriptPath], ['r', st.rolloutPath]]) {
|
|
417
|
+
if (!path || !existsSync(path)) continue;
|
|
418
|
+
try {
|
|
419
|
+
const stat = statSync(path);
|
|
420
|
+
parts.push(`${kind}:${st.sessionId}:${stat.size}:${stat.mtimeMs}`);
|
|
421
|
+
} catch {
|
|
422
|
+
// stat 失敗は無視(次フレームで回復)
|
|
423
|
+
}
|
|
382
424
|
}
|
|
383
425
|
}
|
|
384
426
|
} catch {
|
|
@@ -405,7 +447,8 @@ function resetRenderKeyCache() {
|
|
|
405
447
|
}
|
|
406
448
|
|
|
407
449
|
function renderFrame(args) {
|
|
408
|
-
const
|
|
450
|
+
const now = Date.now();
|
|
451
|
+
const states = readAllSessionStates().map((state) => withLiveActivity(state, now));
|
|
409
452
|
const filtered = filterStates(states, args, process.cwd()).sort(
|
|
410
453
|
(a, b) => b.updatedAt - a.updatedAt,
|
|
411
454
|
);
|
|
@@ -428,15 +471,9 @@ function renderFrame(args) {
|
|
|
428
471
|
`[Throughline] ${filtered.length} セッション${args.all ? ' (--all)' : ''}`,
|
|
429
472
|
);
|
|
430
473
|
lines.push(header);
|
|
431
|
-
const now = Date.now();
|
|
432
474
|
for (let i = 0; i < filtered.length; i++) {
|
|
433
475
|
const state = filtered[i];
|
|
434
|
-
|
|
435
|
-
// 旧バージョンが書いた Claude state や usage スナップショットが取れなかったターンでは
|
|
436
|
-
// transcriptPath を直読。state 側の情報が 1 本化されると
|
|
437
|
-
// 「state が古い JSONL を指している」時の表示ブレが減る。
|
|
438
|
-
const usage = state.usage
|
|
439
|
-
?? (state.transcriptPath ? readLatestUsage(state.transcriptPath) : null);
|
|
476
|
+
const usage = resolveMonitorUsage(state);
|
|
440
477
|
lines.push(formatLine({ state, usage, isActive: i === 0, now }));
|
|
441
478
|
}
|
|
442
479
|
}
|
|
@@ -698,6 +735,9 @@ export const _internal = {
|
|
|
698
735
|
shouldForceFullRedraw,
|
|
699
736
|
resolveColumns,
|
|
700
737
|
setMeasuredColumns,
|
|
738
|
+
liveActivityMs,
|
|
739
|
+
withLiveActivity,
|
|
740
|
+
resolveMonitorUsage,
|
|
701
741
|
};
|
|
702
742
|
|
|
703
743
|
// --- エントリポイント自動起動 ---
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { test } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
3
6
|
|
|
4
7
|
import { _internal } from './token-monitor.mjs';
|
|
5
8
|
import { normalizeProjectPath } from './state-file.mjs';
|
|
@@ -17,6 +20,8 @@ const {
|
|
|
17
20
|
shouldForceFullRedraw,
|
|
18
21
|
resolveColumns,
|
|
19
22
|
setMeasuredColumns,
|
|
23
|
+
withLiveActivity,
|
|
24
|
+
resolveMonitorUsage,
|
|
20
25
|
} = _internal;
|
|
21
26
|
|
|
22
27
|
// state-file は projectPath を resolve + lowercase 正規化する。
|
|
@@ -128,6 +133,117 @@ test('filterStates: cwd 不一致は除外(--session も --all もなし)',
|
|
|
128
133
|
assert.equal(result[0].sessionId, 'a');
|
|
129
134
|
});
|
|
130
135
|
|
|
136
|
+
test('withLiveActivity: transcript mtime keeps a long-running session visible before Stop', () => {
|
|
137
|
+
const dir = mkdtempSync(join(tmpdir(), 'tl-monitor-live-activity-'));
|
|
138
|
+
try {
|
|
139
|
+
const transcript = join(dir, 'session.jsonl');
|
|
140
|
+
writeFileSync(transcript, '{"type":"user","message":{"content":"working"}}\n');
|
|
141
|
+
const now = Date.now();
|
|
142
|
+
const old = now - (20 * 60 * 1000);
|
|
143
|
+
const state = withLiveActivity({
|
|
144
|
+
sessionId: 'live-session',
|
|
145
|
+
host: 'claude',
|
|
146
|
+
projectPath: CWD_FOO,
|
|
147
|
+
transcriptPath: transcript,
|
|
148
|
+
rolloutPath: null,
|
|
149
|
+
updatedAt: old,
|
|
150
|
+
stale: true,
|
|
151
|
+
}, now);
|
|
152
|
+
|
|
153
|
+
assert.equal(state.stale, false);
|
|
154
|
+
assert.ok(state.updatedAt > old);
|
|
155
|
+
} finally {
|
|
156
|
+
rmSync(dir, { recursive: true, force: true });
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('resolveMonitorUsage: live Claude transcript overrides stale Stop snapshot', () => {
|
|
161
|
+
const dir = mkdtempSync(join(tmpdir(), 'tl-monitor-live-usage-'));
|
|
162
|
+
try {
|
|
163
|
+
const transcript = join(dir, 'session.jsonl');
|
|
164
|
+
writeFileSync(transcript, [
|
|
165
|
+
JSON.stringify({
|
|
166
|
+
type: 'assistant',
|
|
167
|
+
message: {
|
|
168
|
+
model: 'claude-opus-4-6',
|
|
169
|
+
usage: {
|
|
170
|
+
input_tokens: 1234,
|
|
171
|
+
cache_creation_input_tokens: 200,
|
|
172
|
+
cache_read_input_tokens: 300,
|
|
173
|
+
output_tokens: 10,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
}),
|
|
177
|
+
'',
|
|
178
|
+
].join('\n'));
|
|
179
|
+
|
|
180
|
+
const usage = resolveMonitorUsage({
|
|
181
|
+
sessionId: 'claude-session',
|
|
182
|
+
host: 'claude',
|
|
183
|
+
projectPath: CWD_FOO,
|
|
184
|
+
transcriptPath: transcript,
|
|
185
|
+
rolloutPath: null,
|
|
186
|
+
updatedAt: Date.now(),
|
|
187
|
+
usage: {
|
|
188
|
+
tokens: 1,
|
|
189
|
+
model: 'old-snapshot',
|
|
190
|
+
contextWindowSize: 200_000,
|
|
191
|
+
outputTokens: 0,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
assert.equal(usage.tokens, 1734);
|
|
196
|
+
assert.equal(usage.model, 'claude-opus-4-6');
|
|
197
|
+
} finally {
|
|
198
|
+
rmSync(dir, { recursive: true, force: true });
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('resolveMonitorUsage: live Codex rollout overrides stale Stop snapshot', () => {
|
|
203
|
+
const dir = mkdtempSync(join(tmpdir(), 'tl-monitor-live-codex-'));
|
|
204
|
+
try {
|
|
205
|
+
const rollout = join(dir, 'rollout.jsonl');
|
|
206
|
+
writeFileSync(rollout, [
|
|
207
|
+
JSON.stringify({
|
|
208
|
+
type: 'turn_context',
|
|
209
|
+
payload: { model: 'gpt-5.5' },
|
|
210
|
+
}),
|
|
211
|
+
JSON.stringify({
|
|
212
|
+
type: 'event_msg',
|
|
213
|
+
payload: {
|
|
214
|
+
type: 'token_count',
|
|
215
|
+
info: {
|
|
216
|
+
last_token_usage: { input_tokens: 4567, output_tokens: 89 },
|
|
217
|
+
model_context_window: 258400,
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
}),
|
|
221
|
+
'',
|
|
222
|
+
].join('\n'));
|
|
223
|
+
|
|
224
|
+
const usage = resolveMonitorUsage({
|
|
225
|
+
sessionId: 'codex:thread',
|
|
226
|
+
host: 'codex',
|
|
227
|
+
projectPath: CWD_FOO,
|
|
228
|
+
transcriptPath: null,
|
|
229
|
+
rolloutPath: rollout,
|
|
230
|
+
updatedAt: Date.now(),
|
|
231
|
+
usage: {
|
|
232
|
+
tokens: 1,
|
|
233
|
+
model: 'old-snapshot',
|
|
234
|
+
contextWindowSize: 200_000,
|
|
235
|
+
outputTokens: 0,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
assert.equal(usage.tokens, 4567);
|
|
240
|
+
assert.equal(usage.model, 'gpt-5.5');
|
|
241
|
+
assert.equal(usage.estimated, false);
|
|
242
|
+
} finally {
|
|
243
|
+
rmSync(dir, { recursive: true, force: true });
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
131
247
|
// ─── cellWidth ─────────────────────────────────────────────────────
|
|
132
248
|
|
|
133
249
|
test('cellWidth: ASCII は 1 セル', () => {
|
package/src/turn-processor.mjs
CHANGED
|
@@ -279,9 +279,8 @@ export async function run() {
|
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
-
// monitor
|
|
283
|
-
//
|
|
284
|
-
// 書き出し済みなので readLatestUsage が最新値を返す。
|
|
282
|
+
// monitor の fallback 用に、Stop 完了時点で確定している usage を state ファイルにも
|
|
283
|
+
// 保存する。通常表示はライブ transcript を優先し、読めない時だけ snapshot を使う。
|
|
285
284
|
// 取得失敗は致命ではないので try/catch で握る(stderr には出す)。
|
|
286
285
|
try {
|
|
287
286
|
const usage = transcript_path ? readLatestUsage(transcript_path) : null;
|