throughline 0.14.2 → 0.15.0
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/.claude/commands/sc-detail.md +4 -1
- package/CHANGELOG.md +47 -1
- package/README.ja.md +19 -3
- package/README.md +46 -4
- package/bin/throughline.mjs +7 -4
- package/docs/adr/0034-project-recent-sessions-context.md +73 -0
- package/package.json +1 -1
- package/src/cli/handoff-context.mjs +60 -9
- package/src/cli/handoff-context.test.mjs +193 -0
- package/src/completed-turn-receipts.mjs +12 -10
- package/src/completed-turn-receipts.test.mjs +24 -1
- package/src/os/windows-acl.mjs +27 -0
- package/src/resume-context.mjs +272 -6
- package/src/resume-context.test.mjs +278 -0
- package/src/runtime-error-store.mjs +11 -10
- package/src/runtime-error-store.test.mjs +22 -1
- package/src/sc-detail.mjs +85 -43
- package/src/sc-detail.test.mjs +184 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Throughline の退避済みターン(L2 本文 + L3 ツール入出力)を時刻で復元する
|
|
3
|
-
argument-hint: <HH:MM:SS
|
|
3
|
+
argument-hint: <HH:MM:SS>、<HH:MM:SS>-<HH:MM:SS>、または <YYYY-MM-DD>T<HH:MM:SS>
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
以下の Bash コマンドを実行し、結果を全文そのまま表示してください(要約せず、一字一句)。
|
|
@@ -13,6 +13,9 @@ throughline detail "$ARGUMENTS"
|
|
|
13
13
|
|
|
14
14
|
- `/sc-detail 14:23:05` — 指定時刻のターンを復元
|
|
15
15
|
- `/sc-detail 14:23-14:30` — 時刻範囲のターンをまとめて復元
|
|
16
|
+
- `/sc-detail 2026-10-04T14:23:05` — 日付を指定してターンを復元
|
|
17
|
+
|
|
18
|
+
日付を省くと、今日から遡って、その時刻のターンがある最も新しい日を対象にします。
|
|
16
19
|
|
|
17
20
|
## 何が返るか
|
|
18
21
|
|
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,50 @@ shipped to npm but were not individually tagged on GitHub.
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.15.0] — 2026-10-05
|
|
14
|
+
|
|
15
|
+
### 追加
|
|
16
|
+
|
|
17
|
+
- `handoff-context --project <path> --json` に `--sessions recent` を足す([ADR 0034](docs/adr/0034-project-recent-sessions-context.md))。同じprojectの直近の複数sessionから、同じ9,500字の予算で文脈を作る。sessionを合流させず、起動のたびに新しいsessionができるlauncher(BellTeam)向け。これまでは会話本文を持つ最新の1 sessionだけを返していたので、長い作業の後に1ターンの短い会話を挟むと、作業の内容が文脈に入らなかった。
|
|
18
|
+
- 最新のsessionは、既定と同じ文(ヘッダ・現在地・案内・L2)で先頭に置く。この部分は1字も変わらない。JSONの`sessionId`もこのsessionのまま。
|
|
19
|
+
- 過去の会話は、その後ろの余った予算にだけ入れる。最新のsessionのターンが全部入っている時だけ、それより前のsessionを新しい順にターン単位で足す。入らないターンが出たらそこで止める。新しいターンを飛ばして古いターンは載せない。
|
|
20
|
+
- 過去の会話は別の節にまとめ、会話ごとに日時・ターン数・session idを見出しに出す。「直前の会話」「短い返事はGO」の案内は付けず、次のユーザー入力をその会話への返事として扱わないよう書く。
|
|
21
|
+
- 本文を載せなかったsessionは5件まで、`throughline recall --l2 --session <id> --before <ISO> --last <N>`を付けた一覧にする。余りが足りない時は付けない。
|
|
22
|
+
- 同じ`(origin_session_id, turn_number)`は1回だけ載せる。別のprojectのsessionは入れない。
|
|
23
|
+
- JSONに`sessions`(`sessionId`・`role`・`firstTurnAt`・`lastTurnAt`・`turns`・`includedTurns`)を足す。schemaは`throughline.handoff_context.v1`のまま。
|
|
24
|
+
- 引数を付けない時(`--sessions latest`と同じ)の出力は変わらない。`--session`の意味も変わらない。`--sessions`を`--session`と一緒に付けると、使い方の誤りとして終了コード2を返す。sessionが1つのprojectでは、`--sessions recent`の`context`は既定と同じになる。
|
|
25
|
+
- `throughline detail` が `<YYYY-MM-DD>T<HH:MM:SS>` を受け取る。範囲は `<YYYY-MM-DD>T<HH:MM:SS>-<HH:MM:SS>`。
|
|
26
|
+
|
|
27
|
+
### 修正
|
|
28
|
+
|
|
29
|
+
- `throughline detail <HH:MM:SS>` が、実行した日のターンしか探さなかった不具合を直す。注入文の`[HH:MM:SS]`は過去の時刻で、日付が変わった後(UTCの端末では日本時間の朝9時以降)は前日のターンを指す。その時は「該当するターンが見つかりませんでした」と返っていた。日付を省いた時は、今日から1日ずつ遡り、その時刻のターンがある最も新しい日を対象にする。今日以外の日のターンを返す時は、見出しに日付を出す。
|
|
30
|
+
|
|
31
|
+
### 互換性
|
|
32
|
+
|
|
33
|
+
- 0.14.3以前に`--sessions`を渡すと終了コード2になる。呼び手は、Throughlineを先に上げてから引数を付ける。
|
|
34
|
+
|
|
35
|
+
### 確認した範囲
|
|
36
|
+
|
|
37
|
+
- BellTeamのコンテナ(Linux)の実データ。Botの席35のうち、会話のあるsessionが1つの6席は既定と同じ文、複数ある29席も最新のsessionの部分は全部、既定と同じ文。22席で過去の会話の本文が入り、文脈の中央値は4,225字から9,110字、最大9,500字で、上限を超えた席は無い。数えたのは件数と字数だけ。
|
|
38
|
+
- 同じ実データで、9/25以後のsession開始428回を、その時点より前に始まったsessionだけで再生した。「直前の会話が1ターン・その前が5ターン以上」の場面は19回(14席)。既定で作業の会話が入ったのは0回、`--sessions recent`では19回(入ったターンは中央値5、最少2)。
|
|
39
|
+
- Claude Code 2.1.289を隔離した置き場で動かした。6ターンの作業(最後に「追記してよければ『はい』と言ってください」と申し出て終わる)と、その後の1ターンの会話(別の申し出で終わる)を用意し、文脈を起動時の指示へ入れた。「はい」だけの入力は、Haiku 3回・Sonnet 1回とも最新の会話の申し出として扱われ、過去の会話の申し出は実行されなかった。過去の会話で決めた事柄を尋ねると、`--sessions recent`の文脈では道具を使わずに答え、既定の文脈では答えられなかった。過去の作業をあらためて頼んだ時は実行した。Codex・Grok・Cursorでは確かめていない。
|
|
40
|
+
- macOS 27.0(arm64)とWindows 11(10.0.26200)の実DBをread-onlyで読み、上限内・`sessionId`の一致・最新のsessionの部分が既定と同じ文であることを確かめた。試験はLinuxとmacOSで全件、Windowsで関係する7ファイル。
|
|
41
|
+
- `detail`は、UTCのコンテナで前日のターンの時刻を引き、0.14.3が「見つかりませんでした」と返す場面で、そのターンを返すことを確かめた。
|
|
42
|
+
|
|
43
|
+
## [0.14.3] — 2026-10-05
|
|
44
|
+
|
|
45
|
+
### 修正
|
|
46
|
+
|
|
47
|
+
- Windowsで、同じhookが2本同時に走った時に、片方が`Windows owner-only ACL verification failed`で失敗することがあった不具合を直す。Cursorは同じStop hookを2本ほぼ同時に起動する。新しいフォルダでの最初の応答では、1本目が受領記録のlockファイルを作り、その後で所有者だけの権限(ACL)を付けていた。権限を付け終わるまでの約1秒、lockファイルはフォルダから継承した権限のまま見える。2本目がその間に権限を検証して落ち、`HOOK_PROCESS_TURN_FAILED`に数えられていた。会話の保存は同じStopで済んでいて、失われていない。
|
|
48
|
+
- lockファイルは、別名で作って権限を付け、検証してから、hard linkで最終の場所へ出す。最終の場所に現れた時点で、所有者だけの権限が付いている。先に出した側が勝ち、後から来た側は既にあるlockを検証して使う。
|
|
49
|
+
- 実行時エラーの記録(`runtime-errors`)のlockファイルも同じ作りだったので、同じ形に直す。
|
|
50
|
+
- macOSとLinuxの動作は変わらない(作る時点で`0600`を指定しているので、この隙間は無い)。既にあるlockファイルの検証は変えていない。
|
|
51
|
+
|
|
52
|
+
### 確認した範囲
|
|
53
|
+
|
|
54
|
+
- Windows 11(10.0.26200)の実機。0.14.2では、lockファイルが作られて権限がまだ付いていない状態で2本目を走らせると、Cursorの実際の失敗と同じ文面・同じ呼び出し位置で落ちる。0.14.3は、同じ場面(1本目が別名を作った直後)で2本とも成功し、lockファイルと受領記録の権限は所有者だけになる。
|
|
55
|
+
- 同じ実機で、新しい置き場に2本・4本を同時に起動する確認を、受領記録で51回、実行時エラーの記録で15回行い、失敗0。0.14.2は同じ確認18回のうち1回で失敗した。
|
|
56
|
+
|
|
13
57
|
## [0.14.2] — 2026-10-05
|
|
14
58
|
|
|
15
59
|
### 修正
|
|
@@ -1710,7 +1754,9 @@ two attempts, instrument first instead of patching again.
|
|
|
1710
1754
|
|
|
1711
1755
|
---
|
|
1712
1756
|
|
|
1713
|
-
[Unreleased]: https://github.com/kitepon/Throughline/compare/v0.
|
|
1757
|
+
[Unreleased]: https://github.com/kitepon/Throughline/compare/v0.15.0...HEAD
|
|
1758
|
+
[0.15.0]: https://github.com/kitepon/Throughline/compare/v0.14.3...v0.15.0
|
|
1759
|
+
[0.14.3]: https://github.com/kitepon/Throughline/compare/v0.14.2...v0.14.3
|
|
1714
1760
|
[0.14.2]: https://github.com/kitepon/Throughline/compare/v0.14.1...v0.14.2
|
|
1715
1761
|
[0.14.1]: https://github.com/kitepon/Throughline/compare/v0.14.0...v0.14.1
|
|
1716
1762
|
[0.14.0]: https://github.com/kitepon/Throughline/compare/v0.13.0...v0.14.0
|
package/README.ja.md
CHANGED
|
@@ -444,7 +444,7 @@ v0.10.4以前には `self-update` が存在しない。該当版からの初回
|
|
|
444
444
|
`throughline self-update` を実行する。以後の更新は `throughline self-update` だけで完結する。
|
|
445
445
|
| `throughline monitor` | マルチセッション監視を起動 |
|
|
446
446
|
| `throughline monitor --diag` | TTY/columns/env 診断ダンプ (描画バグ切り分け用) |
|
|
447
|
-
| `throughline detail <時刻>` | あるターンの L2 本文と L3 ツール I/O を取得 (Claude が使う) |
|
|
447
|
+
| `throughline detail [<日付>T]<時刻>` | あるターンの L2 本文と L3 ツール I/O を取得 (Claude が使う)。日付を省くと、今日から遡って、その時刻のターンがある最も新しい日を対象にする |
|
|
448
448
|
| `throughline recall --l2\|--l1 --session <id> --before <ISO> ...` | 注入の案内セクションが指す古い記憶を pull (read-only、正確なコマンドは注入に焼き込み済み) |
|
|
449
449
|
| `throughline doctor` | Node バージョン、hook 登録状況、DB、PATH をチェック |
|
|
450
450
|
| `throughline doctor --trim --host claude` | trim boundary と手動手順を診断 |
|
|
@@ -457,7 +457,7 @@ v0.10.4以前には `self-update` が存在しない。該当版からの初回
|
|
|
457
457
|
| `throughline runtime-errors report --json` | 未受領のaggregateを今1回送る。`sent`と`nothing_pending`だけexit 0 |
|
|
458
458
|
| `throughline runtime-errors report-status --json` | 最後に試した時刻と結果の固定codeを読む。宛先・credential・pathは出さない |
|
|
459
459
|
| `throughline handoff-preview --session <id>` | Codex 向け `throughline_handoff` JSON projection を表示 |
|
|
460
|
-
| `throughline handoff-context (--session <id> \| --project <path>) --json` | SessionStartと同じ引き継ぎ文脈を取得。project指定時は会話本文を持つ最新sessionを選び、`--disclosure silent`に対応し、本文がなければ`empty
|
|
460
|
+
| `throughline handoff-context (--session <id> \| --project <path>) --json` | SessionStartと同じ引き継ぎ文脈を取得。project指定時は会話本文を持つ最新sessionを選び、`--disclosure silent`に対応し、本文がなければ`empty`を返す。`--sessions recent`を付けると、同じprojectの直近の複数sessionから同じ9,500字枠で文脈を作る。session指定時はproject束縛済み補足を同じ9,500字枠へ合成できる |
|
|
461
461
|
| `throughline latest-session --project <absolute-path> --json` | 指定した1プロジェクトだけを対象に直近セッションIDを読み取る。既存DBをread-onlyで開き、記録がなければ`empty`を返す |
|
|
462
462
|
| `throughline grok-continue --session <id>` | handoff-context を初手 user 文にした対話 Grok 席を立てる。cwd は源の `project_path`。ready でなければ spawn しない。`--rules` なし。macOS Terminal のみ |
|
|
463
463
|
| `throughline trim --dry-run --host codex` | Codex same-thread trim の dry-run preview |
|
|
@@ -511,12 +511,28 @@ macOS/Linuxでは、本人所有でgroup/otherに権限が無く、symlinkでな
|
|
|
511
511
|
```bash
|
|
512
512
|
throughline handoff-context --session codex:<thread-id> --json
|
|
513
513
|
throughline handoff-context --project /absolute/bot/project --json --disclosure silent
|
|
514
|
+
throughline handoff-context --project /absolute/bot/project --json --disclosure silent --sessions recent
|
|
514
515
|
```
|
|
515
516
|
|
|
516
517
|
`throughline.handoff_context.v1`は`schema`、`status`、`sessionId`、`context`だけを返す。
|
|
517
518
|
`--project`ではそのproject内で会話本文を持つ最新sessionを選び、存在しなければ`empty`を返す。
|
|
518
519
|
`--disclosure silent`は長期記憶を追加せずThroughlineの案内を消す。`context`はSessionStartと
|
|
519
|
-
|
|
520
|
+
同じ予算付き継承文脈である。
|
|
521
|
+
|
|
522
|
+
`--project`は既定で1つのsessionを読む(`--sessions latest`)。launcherがsessionを合流させず、
|
|
523
|
+
起動のたびに新しいsessionができる場合は`--sessions recent`を付ける
|
|
524
|
+
([ADR 0034](docs/adr/0034-project-recent-sessions-context.md))。最新のsessionは今までと同じ形で
|
|
525
|
+
先頭に置き、`sessionId`もそのsessionを指す。そのターンが全部入って予算が余った時だけ、同じprojectの
|
|
526
|
+
それより前のsessionを新しい順に、ターン単位で足す。過去の会話は別の節にまとめ、会話ごとに日時・
|
|
527
|
+
ターン数・session idを見出しに出し、次のユーザー入力をその会話への返事として扱わないよう案内する。
|
|
528
|
+
入らなかったsessionは5件まで、`throughline recall --l2 --session <id> --before <ISO> --last <N>`を
|
|
529
|
+
付けた一覧にする。全体は同じ9,500字枠に収める。この時だけJSONに`sessions`が加わり、文脈に載せた
|
|
530
|
+
sessionごとに`sessionId`、`role`(`current` / `past`)、`firstTurnAt`、`lastTurnAt`、`turns`、
|
|
531
|
+
`includedTurns`(一覧にだけ載せたsessionは0)を返す。`context`は、既定の`context`そのままか、その後ろに
|
|
532
|
+
過去の会話の節を足したものになり、最新のsessionの部分が減ることは無い。sessionが1つのprojectでは、
|
|
533
|
+
`--sessions recent`の有無で`context`は変わらない。`--sessions`は`--session`と一緒には使えない。
|
|
534
|
+
|
|
535
|
+
`--session`で使える任意の`--supplement-file <path>`には
|
|
520
536
|
`throughline.handoff_supplement.v1`、源sessionと同じ`projectPath`、`title`と`content`からなる
|
|
521
537
|
`sections`を指定する。任意の`handoffDisclosure`は`visible`(既定)または`silent`を受け取る。
|
|
522
538
|
補足は会話記憶と同じ9,500字枠へ入り、別projectの補足は拒否する。
|
package/README.md
CHANGED
|
@@ -895,7 +895,7 @@ once with `npm install --global throughline@latest`, then run
|
|
|
895
895
|
`throughline self-update`. Later updates use `throughline self-update` alone.
|
|
896
896
|
| `throughline monitor [--all] [--session <id>]` | Run the multi-session token monitor |
|
|
897
897
|
| `throughline monitor --diag` | Dump TTY/columns/env diagnostics (for debugging monitor render bugs) |
|
|
898
|
-
| `throughline detail <time>`
|
|
898
|
+
| `throughline detail [<date>T]<time>` | Retrieve L2 body text and L3 tool I/O for a turn (see below) |
|
|
899
899
|
| `throughline recall --l2\|--l1 --session <id> --before <ISO> ...` | Pull older memory referenced by the injection's guidance section (read-only; the exact command is baked into each injection) |
|
|
900
900
|
| `throughline caveat-context --session <id> --project <root> --json` | Return three completed dialogue turns and available Thinking for Caveat, without tool logs; `--host claude\|codex --transcript <path>` checks freshness |
|
|
901
901
|
| `throughline room-context --json` | Record a room turn from JSON stdin and return the latest three turns through that message as `throughline.room_context.v1` |
|
|
@@ -999,6 +999,7 @@ performing a normal handoff:
|
|
|
999
999
|
```bash
|
|
1000
1000
|
throughline handoff-context --session codex:<thread-id> --json
|
|
1001
1001
|
throughline handoff-context --project /absolute/bot/project --json --disclosure silent
|
|
1002
|
+
throughline handoff-context --project /absolute/bot/project --json --disclosure silent --sessions recent
|
|
1002
1003
|
```
|
|
1003
1004
|
|
|
1004
1005
|
The `throughline.handoff_context.v1` object contains only `schema`, `status`,
|
|
@@ -1006,7 +1007,42 @@ The `throughline.handoff_context.v1` object contains only `schema`, `status`,
|
|
|
1006
1007
|
session in that project that has dialogue context; it returns `status: "empty"`
|
|
1007
1008
|
when none exists. `--disclosure silent` removes the Throughline announcement
|
|
1008
1009
|
without adding long-term memory. The context uses the same budget as
|
|
1009
|
-
SessionStart.
|
|
1010
|
+
SessionStart.
|
|
1011
|
+
|
|
1012
|
+
`--project` reads one session by default (`--sessions latest`). Add
|
|
1013
|
+
`--sessions recent` when the launcher does not merge sessions and each start
|
|
1014
|
+
creates a new one ([ADR 0034](docs/adr/0034-project-recent-sessions-context.md)).
|
|
1015
|
+
The newest session is still rendered first, exactly as before, and `sessionId`
|
|
1016
|
+
still names it. When all of its turns fit and budget remains, earlier sessions
|
|
1017
|
+
of the same project are appended newest first, whole turns only, under a
|
|
1018
|
+
separate "past conversations" section that carries each session's time range,
|
|
1019
|
+
turn count and session id, and that tells the model not to treat the next user
|
|
1020
|
+
message as a reply to them. Sessions that do not fit are listed (up to five)
|
|
1021
|
+
with a ready-to-run `throughline recall --l2 --session <id> --before <ISO>
|
|
1022
|
+
--last <N>` command. The total stays within the same 9,500-character budget.
|
|
1023
|
+
The JSON object then also carries `sessions`, one entry per session mentioned
|
|
1024
|
+
in the context:
|
|
1025
|
+
|
|
1026
|
+
```json
|
|
1027
|
+
{
|
|
1028
|
+
"sessionId": "0d5f…",
|
|
1029
|
+
"role": "past",
|
|
1030
|
+
"firstTurnAt": "2026-10-04T10:48:31.870Z",
|
|
1031
|
+
"lastTurnAt": "2026-10-04T16:52:33.267Z",
|
|
1032
|
+
"turns": 30,
|
|
1033
|
+
"includedTurns": 1
|
|
1034
|
+
}
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
`role` is `current` for the newest session and `past` otherwise;
|
|
1038
|
+
`includedTurns` is `0` for a session that is only listed. The `context` is
|
|
1039
|
+
either the default `context` unchanged, or the default `context` followed by
|
|
1040
|
+
the past-conversations section; the newest session's part never shrinks. A
|
|
1041
|
+
project with a single session returns the same `context` with or without
|
|
1042
|
+
`--sessions recent`.
|
|
1043
|
+
`--sessions` cannot be combined with `--session`.
|
|
1044
|
+
|
|
1045
|
+
With `--session`, a launcher may append
|
|
1010
1046
|
`--supplement-file <path>` with a `throughline.handoff_supplement.v1` JSON object:
|
|
1011
1047
|
|
|
1012
1048
|
```json
|
|
@@ -1131,10 +1167,16 @@ footer explicitly instructs Claude to run this via its Bash tool when a past
|
|
|
1131
1167
|
turn's tool I/O becomes relevant.
|
|
1132
1168
|
|
|
1133
1169
|
```bash
|
|
1134
|
-
throughline detail 14:23:05
|
|
1135
|
-
throughline detail 14:23-14:30
|
|
1170
|
+
throughline detail 14:23:05 # single timestamp
|
|
1171
|
+
throughline detail 14:23-14:30 # timestamp range
|
|
1172
|
+
throughline detail 2026-10-04T14:23:05 # a turn on a specific day
|
|
1136
1173
|
```
|
|
1137
1174
|
|
|
1175
|
+
Without a date, Throughline looks at today first and then walks back one day
|
|
1176
|
+
at a time to the most recent day that has a turn at that time, because the
|
|
1177
|
+
`[HH:MM:SS]` stamps in an injected context may refer to a previous day. The
|
|
1178
|
+
header names the day when it is not today.
|
|
1179
|
+
|
|
1138
1180
|
Output groups records by `kind`: L2 conversation bodies, then L3 tool input/
|
|
1139
1181
|
output, then system messages (hook output), then images. Records are scoped to
|
|
1140
1182
|
the current project's merge chain so Claude only sees turns from its own
|
package/bin/throughline.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* throughline session-start # SessionStart hook (Claude Code から呼ばれる)
|
|
11
11
|
* throughline pre-compact # PreCompact hook (Claude Code から呼ばれる。自動継続: 自動圧縮を止める)
|
|
12
12
|
* throughline pre-tool-use # PreToolUse hook (Claude Code から呼ばれる。自動継続: 旧い会話を止める)
|
|
13
|
-
* throughline detail <時刻> # L2+L3 詳細取得 (Claude が Bash 経由で呼ぶ想定)
|
|
13
|
+
* throughline detail [<日付>T]<時刻> # L2+L3 詳細取得 (Claude が Bash 経由で呼ぶ想定)
|
|
14
14
|
* throughline recall --l2|--l1 # 注入案内から辿る pull 用 read-only 記憶取得
|
|
15
15
|
* throughline handoff-preview # Codex-facing throughline_handoff JSON preview
|
|
16
16
|
* throughline handoff-context (--session <id> | --project <path>) --json # Read-only inheritance context JSON
|
|
@@ -263,14 +263,17 @@ Usage:
|
|
|
263
263
|
throughline install Register Claude, Codex, and Grok hooks
|
|
264
264
|
throughline uninstall Remove hooks
|
|
265
265
|
throughline monitor Multi-session token monitor (use --all, --session <id>)
|
|
266
|
-
throughline detail <time> Retrieve L2+L3 detail for a turn (e.g. 14:23:05
|
|
266
|
+
throughline detail <time> Retrieve L2+L3 detail for a turn (e.g. 14:23:05, 14:23-14:30,
|
|
267
|
+
or 2026-10-04T14:23:05 for a turn on another day)
|
|
267
268
|
throughline handoff-preview Print Codex-facing throughline_handoff JSON
|
|
268
269
|
throughline handoff-context (--session <id> | --project <path>) --json
|
|
269
270
|
Print the exact inheritance context without
|
|
270
271
|
changing database ownership. Project mode selects
|
|
271
272
|
the newest session with dialogue and may use
|
|
272
|
-
--disclosure silent.
|
|
273
|
-
|
|
273
|
+
--disclosure silent. Project mode with
|
|
274
|
+
--sessions recent also appends earlier sessions
|
|
275
|
+
of that project within the same budget. Session
|
|
276
|
+
mode may use a project-bound --supplement-file
|
|
274
277
|
throughline latest-session --project <absolute-path> --json
|
|
275
278
|
Read the latest session id for exactly one project
|
|
276
279
|
throughline grok-continue --session <id>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# ADR 0034: `handoff-context --project` は、引数で選んだ時だけ直近の複数の会話から文脈を作る
|
|
2
|
+
|
|
3
|
+
日付: 2026-10-05
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
`handoff-context --project <path>`(0.10.14)は、その project で会話本文を持つ最新の1 session だけを返す。
|
|
8
|
+
1 session に絞った理由を書いた記録は無い。通常の引き継ぎは前任の行を後継へ張り替えて1本にするので、
|
|
9
|
+
最新の1 session に全部の記憶が載る。
|
|
10
|
+
|
|
11
|
+
BellTeam は Bot の席を起こすたびにこの CLI を読み、返った文を起動時の指示へ入れる。張り替えはしないので、
|
|
12
|
+
session は分かれたまま残る。席の session は、30分動きが無い時・夜間の再起動・反映のたびに替わる。
|
|
13
|
+
2026-09-25〜10-05 の記録を件数だけ数えた結果(BellTeam のビスケット):
|
|
14
|
+
|
|
15
|
+
- 席の 422 session で `merged_into` は 0 件。
|
|
16
|
+
- 起動 353 回のうち 113 回(32%)は、前の session が1ターン以下。
|
|
17
|
+
- 「5ターン以上の作業 → 1ターン以下の session → 起動」が 18 回(13席)。この時、作業の内容は文脈に入らない。
|
|
18
|
+
- 起動時の文脈は中央値 4,226 字で、上限 9,500 字の 44%。
|
|
19
|
+
|
|
20
|
+
オーナーは、直近の複数 session から文脈を作る変更を承認した(2026-10-05、Approval Box K-8GPX8C)。
|
|
21
|
+
|
|
22
|
+
## Decision
|
|
23
|
+
|
|
24
|
+
1. **引数で選ぶ**: `--project <path>` に `--sessions recent` を付けた時だけ、複数の session をまたぐ。
|
|
25
|
+
省略時(`--sessions latest`)の出力は1字も変えない。`--session` と一緒に付けたら使い方の誤り(終了コード 2)。
|
|
26
|
+
`--project` の「最新の1 session」は README に書いた公開の約束で、既定の意味を変えると、Throughline を
|
|
27
|
+
上げた時に呼び手が全部一度に変わり、戻す手段が版下げだけになる。引数なら呼び手が1行で切り替えて1行で戻せる。
|
|
28
|
+
2. **現在の会話**: 文脈を持つ最新の session(`--sessions latest` と同じ選び方)を、`--sessions latest` と
|
|
29
|
+
同じ文(ヘッダ・現在地・案内・L2)で先頭に置く。この部分は、過去の会話の有無で1字も変わらない。
|
|
30
|
+
公開 JSON の `sessionId` はこの session のまま。
|
|
31
|
+
3. **過去の会話**: 現在の会話を置いた後の余った予算にだけ入れる。現在の会話のターンが全部入っている時だけ、
|
|
32
|
+
それより前の session を新しい順に足す。足す単位はターン。入らないターンが出たらそこで止める。
|
|
33
|
+
新しいターンを飛ばして古いターンを載せない。現在の会話に載せ残しがある時は、過去の会話の本文を足さない。
|
|
34
|
+
4. **見出しと案内を分ける**: 過去の会話は「この project で記録された過去の会話」として1つの節にまとめ、
|
|
35
|
+
会話ごとに日時・ターン数・session id を見出しに出す。「直前の会話」「短い返事は GO」の案内は付けず、
|
|
36
|
+
「次のユーザー入力は、ここへの返事ではない」「載っている依頼は、あらためて頼まれた時だけ実行する」と書く。
|
|
37
|
+
行頭の時刻は `[YYYY-MM-DDTHH:MM:SS]` にする(`throughline detail` へそのまま渡せる)。
|
|
38
|
+
5. **一覧**: 本文を載せなかった会話は、新しい順に5件まで、日時・ターン数と
|
|
39
|
+
`throughline recall --l2 --session <id> --before <ISO> --last <N>` を1行にして付ける。`--last` は 10 まで。
|
|
40
|
+
途中まで載せた会話は、見出しの下に残りの recall を付ける。過去の会話の本文を足す時は、それより古い
|
|
41
|
+
会話の一覧2件分を残す。余りが足りない時は、一覧も節の見出しも付けない(現在の会話は削らない)。
|
|
42
|
+
6. **予算**: 全体で 9,500 字(`INJECTION_BUDGET_CHARS`)のまま。session ごとにはかけない。
|
|
43
|
+
7. **重ねない・混ぜない**: 同じ `(origin_session_id, turn_number)` は、新しい会話の1回だけ載せる。
|
|
44
|
+
別の project の session は入れない。DB は read-only で開き、所有権は変えない。
|
|
45
|
+
8. **公開 JSON**: schema は `throughline.handoff_context.v1` のまま。`--sessions recent` の時だけ `sessions` を足す。
|
|
46
|
+
各要素は `sessionId`・`role`(`current` / `past`)・`firstTurnAt`・`lastTurnAt`(ISO)・`turns`・`includedTurns`
|
|
47
|
+
(0 は一覧にだけ載せた会話)。
|
|
48
|
+
9. **予定の実行・子の AI の session**: 区別せず、`updated_at` の新しい順に入れる。`sessions` の列には
|
|
49
|
+
起動の仕方を表すものが無く、host を替えた時の session とも見分けられない。予定の実行は席が自分で
|
|
50
|
+
動いたターンで、済ませたことを次の起動で知らないと同じことをやり直す。時間の重なりで外す方法は、
|
|
51
|
+
本物の会話を黙って落とす恐れがあるので採らない。過去の会話の見出しは「あなた自身の会話」とは書かない。
|
|
52
|
+
10. **`detail` の日付**: 過去の会話は別の日のことが多い。`throughline detail` は `<YYYY-MM-DD>T<HH:MM:SS>` を
|
|
53
|
+
受け取る。日付を省いた時は、今日から1日ずつ遡り、その時刻のターンがある最も新しい日を対象にする
|
|
54
|
+
(これまでは実行した日だけを見ていたので、前日のターンは注入文に時刻が載っていても引けなかった。ADR 0016 の既知の制約)。
|
|
55
|
+
|
|
56
|
+
## Consequences
|
|
57
|
+
|
|
58
|
+
- `--sessions recent` の `context` は、`--sessions latest` の `context` と同じか、その後ろに過去の会話の節を
|
|
59
|
+
足したものになる。`--sessions latest` で受け取れていた文が減ることは無い。
|
|
60
|
+
- 現在の会話が長く、余りが節の見出しと一覧1行(約 500 字)に足りない時は、過去の会話は何も付かない。
|
|
61
|
+
最初の案では一覧の分を先に差し引いたが、現在の会話のターンが1つ減り、全体が既定より短くなる席があった
|
|
62
|
+
(実データで 9,058 字 → 8,226 字)ので、やめた。
|
|
63
|
+
- 予定の実行が頻繁な席では、1ターンの予定実行が並んで予算を使い、作業の会話は一覧にしか載らないことがある。
|
|
64
|
+
足りなければ、席の本来の会話を表す印を、起動する製品(BellTeam・Aiterm)から渡す形を別に決める。
|
|
65
|
+
- 子の AI の session が最新になった時に現在の会話として扱われる点は、`--sessions latest` と同じで、変わらない。
|
|
66
|
+
- 一覧は5件までで、それより古い会話の session id は文脈に載らない。project の session 一覧を返す公開 CLI は無い。
|
|
67
|
+
- 古い版に `--sessions` を渡すと終了コード 2 になる。呼び手は、Throughline を先に上げてから引数を付ける。
|
|
68
|
+
|
|
69
|
+
## 確認した範囲
|
|
70
|
+
|
|
71
|
+
- `src/resume-context.test.mjs` の `recent:` 6件、`src/cli/handoff-context.test.mjs` の `--sessions` 4件、
|
|
72
|
+
`src/sc-detail.test.mjs` 4件。
|
|
73
|
+
- 実地の確認の結果は CHANGELOG の該当版に書く。
|
package/package.json
CHANGED
|
@@ -2,10 +2,11 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
4
4
|
|
|
5
|
-
import { sameProjectPath } from '../project-path.mjs';
|
|
5
|
+
import { normalizeProjectPathForCompare, sameProjectPath } from '../project-path.mjs';
|
|
6
6
|
import { openReadOnlyDb } from '../db.mjs';
|
|
7
7
|
import {
|
|
8
8
|
buildBudgetedResumeContext,
|
|
9
|
+
buildRecentSessionsContext,
|
|
9
10
|
INJECTION_BUDGET_CHARS,
|
|
10
11
|
} from '../resume-context.mjs';
|
|
11
12
|
|
|
@@ -25,6 +26,7 @@ export function parseArgs(argv = []) {
|
|
|
25
26
|
|
|
26
27
|
let supplementFile = null;
|
|
27
28
|
let handoffDisclosure = 'visible';
|
|
29
|
+
let projectSessions = 'latest';
|
|
28
30
|
const seen = new Set();
|
|
29
31
|
for (let index = 3; index < argv.length; index += 2) {
|
|
30
32
|
const option = argv[index];
|
|
@@ -41,12 +43,15 @@ export function parseArgs(argv = []) {
|
|
|
41
43
|
supplementFile = value;
|
|
42
44
|
} else if (option === '--disclosure' && ['visible', 'silent'].includes(value)) {
|
|
43
45
|
handoffDisclosure = value;
|
|
46
|
+
} else if (option === '--sessions' && ['latest', 'recent'].includes(value)) {
|
|
47
|
+
projectSessions = value;
|
|
44
48
|
} else {
|
|
45
49
|
throw new TypeError('usage error');
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
52
|
if (
|
|
49
53
|
(argv[0] === '--project' && supplementFile) ||
|
|
54
|
+
(argv[0] === '--session' && seen.has('--sessions')) ||
|
|
50
55
|
(supplementFile && seen.has('--disclosure'))
|
|
51
56
|
) {
|
|
52
57
|
throw new TypeError('usage error');
|
|
@@ -57,6 +62,7 @@ export function parseArgs(argv = []) {
|
|
|
57
62
|
projectPath: argv[0] === '--project' ? resolve(argv[1]) : null,
|
|
58
63
|
supplementFile,
|
|
59
64
|
handoffDisclosure,
|
|
65
|
+
projectSessions,
|
|
60
66
|
};
|
|
61
67
|
}
|
|
62
68
|
|
|
@@ -155,6 +161,43 @@ export function readLatestProjectHandoffContext(projectPath, {
|
|
|
155
161
|
}
|
|
156
162
|
}
|
|
157
163
|
|
|
164
|
+
/**
|
|
165
|
+
* 同じ project の直近の会話をまたいだ文脈 (`--project <path> --sessions recent`、ADR 0034)。
|
|
166
|
+
* 現在の会話は readLatestProjectHandoffContext と同じ session を選ぶ。
|
|
167
|
+
*/
|
|
168
|
+
export function readRecentProjectHandoffContext(projectPath, {
|
|
169
|
+
dbPath = join(homedir(), '.throughline', 'throughline.db'),
|
|
170
|
+
handoffDisclosure = 'visible',
|
|
171
|
+
} = {}) {
|
|
172
|
+
if (!existsSync(dbPath)) return null;
|
|
173
|
+
|
|
174
|
+
const db = openReadOnlyDb(dbPath);
|
|
175
|
+
try {
|
|
176
|
+
const target = normalizeProjectPathForCompare(projectPath);
|
|
177
|
+
const sameProject = new Map();
|
|
178
|
+
const sessionIds = [];
|
|
179
|
+
const sessions = db.prepare(
|
|
180
|
+
`SELECT session_id, project_path
|
|
181
|
+
FROM sessions
|
|
182
|
+
ORDER BY updated_at DESC`,
|
|
183
|
+
).all();
|
|
184
|
+
for (const session of sessions) {
|
|
185
|
+
let same = sameProject.get(session.project_path);
|
|
186
|
+
if (same === undefined) {
|
|
187
|
+
same = normalizeProjectPathForCompare(session.project_path) === target;
|
|
188
|
+
sameProject.set(session.project_path, same);
|
|
189
|
+
}
|
|
190
|
+
if (same) sessionIds.push(session.session_id);
|
|
191
|
+
}
|
|
192
|
+
const result = buildRecentSessionsContext(db, { sessionIds, handoffDisclosure });
|
|
193
|
+
return result
|
|
194
|
+
? { sessionId: result.sessionId, context: result.text, sessions: result.sessions }
|
|
195
|
+
: null;
|
|
196
|
+
} finally {
|
|
197
|
+
db.close();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
158
201
|
export function readSessionProjectPath(sessionId, {
|
|
159
202
|
dbPath = join(homedir(), '.throughline', 'throughline.db'),
|
|
160
203
|
} = {}) {
|
|
@@ -179,28 +222,34 @@ export function run(argv = [], {
|
|
|
179
222
|
stderr = process.stderr,
|
|
180
223
|
readContext = readHandoffContext,
|
|
181
224
|
readProjectContext = readLatestProjectHandoffContext,
|
|
225
|
+
readRecentProjectContext = readRecentProjectHandoffContext,
|
|
182
226
|
} = {}) {
|
|
183
227
|
let sessionId;
|
|
184
228
|
let projectPath;
|
|
185
229
|
let supplementFile;
|
|
186
230
|
let handoffDisclosure;
|
|
231
|
+
let projectSessions;
|
|
187
232
|
try {
|
|
188
|
-
({ sessionId, projectPath, supplementFile, handoffDisclosure } = parseArgs(argv));
|
|
233
|
+
({ sessionId, projectPath, supplementFile, handoffDisclosure, projectSessions } = parseArgs(argv));
|
|
189
234
|
} catch {
|
|
190
235
|
stderr.write(
|
|
191
|
-
'Usage: throughline handoff-context (--session <id> | --project <path>) --json [--disclosure visible|silent | --supplement-file <path>]\n',
|
|
236
|
+
'Usage: throughline handoff-context (--session <id> | --project <path>) --json [--disclosure visible|silent | --supplement-file <path>] [--sessions latest|recent]\n',
|
|
192
237
|
);
|
|
193
238
|
return 2;
|
|
194
239
|
}
|
|
195
240
|
|
|
241
|
+
// `sessions` は `--sessions recent` の時だけ足す。付けない時の出力は変えない。
|
|
242
|
+
const recent = Boolean(projectPath) && projectSessions === 'recent';
|
|
196
243
|
let result;
|
|
197
244
|
try {
|
|
198
|
-
result = projectPath
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
245
|
+
if (recent) result = readRecentProjectContext(projectPath, { handoffDisclosure });
|
|
246
|
+
else if (projectPath) result = readProjectContext(projectPath, { handoffDisclosure });
|
|
247
|
+
else {
|
|
248
|
+
result = {
|
|
249
|
+
sessionId,
|
|
250
|
+
context: readContext(sessionId, { supplementFile, handoffDisclosure }),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
204
253
|
} catch {
|
|
205
254
|
stderr.write('Throughline handoff context could not be read.\n');
|
|
206
255
|
return 1;
|
|
@@ -211,6 +260,7 @@ export function run(argv = [], {
|
|
|
211
260
|
status: 'empty',
|
|
212
261
|
sessionId: null,
|
|
213
262
|
context: '',
|
|
263
|
+
...(recent ? { sessions: [] } : {}),
|
|
214
264
|
})}\n`);
|
|
215
265
|
return 0;
|
|
216
266
|
}
|
|
@@ -224,6 +274,7 @@ export function run(argv = [], {
|
|
|
224
274
|
status: 'ready',
|
|
225
275
|
sessionId: result.sessionId,
|
|
226
276
|
context: result.context,
|
|
277
|
+
...(recent ? { sessions: result.sessions } : {}),
|
|
227
278
|
})}\n`);
|
|
228
279
|
return 0;
|
|
229
280
|
}
|