throughline 0.16.5 → 0.16.6
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 +25 -1
- package/README.ja.md +2 -2
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/cli/doctor.mjs +4 -3
- package/src/cli/doctor.test.mjs +24 -0
- package/src/codex-auto-handoff.mjs +2 -1
- package/src/codex-auto-handoff.test.mjs +22 -0
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.16.6] — 2026-10-08
|
|
14
|
+
|
|
15
|
+
### 修正
|
|
16
|
+
|
|
17
|
+
- WindowsのCodex Desktopで、自動継続が1回も引き継げなかったのを直す。`PreCompact(auto)`のhookが、会話記録の場所を確かめる所で`EISDIR: illegal operation on a directory, lstat 'C:'`で落ち、旧ターンだけを止めて、引き継ぎを作らずに終わっていた。Desktopが起動したhookの中では、Node.jsのJS実装の`fs.realpathSync`がdriveの根を読めない。OSの`realpath`(`fs.realpathSync.native`)を使う。製品のほかの所は、すでにこちらを使っていた。
|
|
18
|
+
- Windowsの端末の実測(Codex Desktop 26.930、同梱CLI 0.160.1、Node.js 24.20.0)。同じhookの中で、JS実装は13回とも同じ理由で落ち、OSの`realpath`は13回とも通った。
|
|
19
|
+
- Windowsで自動継続を有効にしていた端末は、今まで無い(既定は無効。有効にする前の確認で見つけた)。
|
|
20
|
+
- `throughline doctor --codex`が、Windowsで承認済みのhookを「0/3 trusted - accept hooks in Codex menu」と表示していたのを直す。Codexは、`\`を含むkey(Windowsのpath)を`config.toml`へTOMLのliteral string(`[hooks.state.'C:\Users\…']`)で書く。doctorは`"…"`の形しか読んでいなかった。hookの動きと、Codexの公式API(`hooks/list`)が返す承認状態は、今までも正しかった。
|
|
21
|
+
|
|
22
|
+
### 確認した範囲
|
|
23
|
+
|
|
24
|
+
- WindowsのCodex Desktopで、公式の`PreCompact(auto)`を実際に発火させ、A→B→Cの2回の引き継ぎを人の操作なしで通した([実測](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-08-windows-codex-auto-handoff.md))。2回とも`continued`。3つの工程の値は元の指示と一致し、3つ目のタスクが最初のタスクのL3を取得して完了した。圧縮の記録は0件。後継の名前は`<project名>|<作業の概要>(自動引き継ぎ)`で付いた。
|
|
25
|
+
|
|
26
|
+
### 変わらない事
|
|
27
|
+
|
|
28
|
+
- macOSの自動継続の動き。schema(v12)。Claude Code・Grok・Cursor。
|
|
29
|
+
|
|
30
|
+
### 確認していない範囲
|
|
31
|
+
|
|
32
|
+
- Windowsで、Desktopの画面から人が始めたタスクを最初の旧タスクにした引き継ぎ。実測の最初のタスクは、製品が後継を作るのと同じ道(Desktop同梱CLIのapp-server)で作り、Desktopで開いて実行させた。
|
|
33
|
+
- Windowsで、容量の既定の上限まで進んだ長い会話の引き継ぎ(実測は、試験用フォルダだけ上限を60,000 tokensへ下げた)。
|
|
34
|
+
- Windowsの、失敗した時の説明ページの表示と、`resume`。
|
|
35
|
+
|
|
13
36
|
## [0.16.5] — 2026-10-08
|
|
14
37
|
|
|
15
38
|
### 修正
|
|
@@ -1982,7 +2005,8 @@ two attempts, instrument first instead of patching again.
|
|
|
1982
2005
|
|
|
1983
2006
|
---
|
|
1984
2007
|
|
|
1985
|
-
[Unreleased]: https://github.com/kitepon/Throughline/compare/v0.16.
|
|
2008
|
+
[Unreleased]: https://github.com/kitepon/Throughline/compare/v0.16.6...HEAD
|
|
2009
|
+
[0.16.6]: https://github.com/kitepon/Throughline/compare/v0.16.5...v0.16.6
|
|
1986
2010
|
[0.16.5]: https://github.com/kitepon/Throughline/compare/v0.16.4...v0.16.5
|
|
1987
2011
|
[0.16.4]: https://github.com/kitepon/Throughline/compare/v0.16.3...v0.16.4
|
|
1988
2012
|
[0.16.3]: https://github.com/kitepon/Throughline/compare/v0.16.2...v0.16.3
|
package/README.ja.md
CHANGED
|
@@ -27,7 +27,7 @@ throughline auto-handoff status --json
|
|
|
27
27
|
throughline auto-handoff disable
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
既定では無効です。`enable`は必要な`PreCompact(auto)`フックを登録し、そのフックの承認と有効状態を公式APIで確認します。`--project`を省略すると全projectを対象にします。macOSのCodex Desktop
|
|
30
|
+
既定では無効です。`enable`は必要な`PreCompact(auto)`フックを登録し、そのフックの承認と有効状態を公式APIで確認します。`--project`を省略すると全projectを対象にします。macOSとWindowsのCodex Desktopで実機確認済みです(Windowsは0.16.6から)。VS Code・CLI・Linuxの自動継続は未検証です。
|
|
31
31
|
|
|
32
32
|
自動圧縮の開始前に旧ターンを停止し、同じprojectの新しいタスクへ記憶を注入して表示します。新しいタスクには、前任のタスクの題から`<project名>|<作業の概要>(自動引き継ぎ)`の名前を付けます。元のモデル・推論強度・権限・モードなどが表示後も一致することを確認してから、継続指示を一度だけ送ります。後継でその入力と作業の進捗を観測して完了とします。手動`/compact`は発火対象に含めません。
|
|
33
33
|
|
|
@@ -42,7 +42,7 @@ throughline auto-handoff resume --operation <handoff-id> --json
|
|
|
42
42
|
|
|
43
43
|
原因を解消した後の`resume`は同じ引き継ぎと既知の後継を再利用します。配送後の結果不明は実際の入力・開始を観測した場合だけ回復し、作成結果が不明な後継は再作成しません。従来の手動`$throughline`とcurrent-thread実験の設定は独立しています。
|
|
44
44
|
|
|
45
|
-
[実測と検証範囲](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-03-codex-auto-continuation.md)を参照してください。
|
|
45
|
+
[実測と検証範囲](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-03-codex-auto-continuation.md)と、[Windowsの実測](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-08-windows-codex-auto-handoff.md)を参照してください。
|
|
46
46
|
|
|
47
47
|
## Claude Codeの自動継続
|
|
48
48
|
|
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ throughline auto-handoff status --json
|
|
|
26
26
|
throughline auto-handoff disable
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
既定では無効です。`enable`は必要な`PreCompact(auto)`フックを登録し、そのフックの承認と有効状態を公式APIで確認します。`--project`を省略すると全projectを対象にします。macOSのCodex Desktop
|
|
29
|
+
既定では無効です。`enable`は必要な`PreCompact(auto)`フックを登録し、そのフックの承認と有効状態を公式APIで確認します。`--project`を省略すると全projectを対象にします。macOSとWindowsのCodex Desktopで実機確認済みです(Windowsは0.16.6から)。VS Code・CLI・Linuxの自動継続は未検証です。
|
|
30
30
|
|
|
31
31
|
自動圧縮の開始前に旧ターンを停止し、同じprojectの新しいタスクへ記憶を注入して表示します。新しいタスクには、前任のタスクの題から`<project名>|<作業の概要>(自動引き継ぎ)`の名前を付けます。元のモデル・推論強度・権限・モードなどが表示後も一致することを確認してから、継続指示を一度だけ送ります。後継でその入力と作業の進捗を観測して完了とします。手動`/compact`は発火対象に含めません。
|
|
32
32
|
|
|
@@ -41,7 +41,7 @@ throughline auto-handoff resume --operation <handoff-id> --json
|
|
|
41
41
|
|
|
42
42
|
原因を解消した後の`resume`は同じ引き継ぎと既知の後継を再利用します。配送後の結果不明は実際の入力・開始を観測した場合だけ回復し、作成結果が不明な後継は再作成しません。従来の手動`$throughline`とcurrent-thread実験の設定は独立しています。
|
|
43
43
|
|
|
44
|
-
[実測と検証範囲](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-03-codex-auto-continuation.md)を参照してください。
|
|
44
|
+
[実測と検証範囲](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-03-codex-auto-continuation.md)と、[Windowsの実測](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-08-windows-codex-auto-handoff.md)を参照してください。
|
|
45
45
|
|
|
46
46
|
## Claude Codeの自動継続
|
|
47
47
|
|
package/package.json
CHANGED
package/src/cli/doctor.mjs
CHANGED
|
@@ -405,14 +405,15 @@ function parseCodexTrustedHookState(configText) {
|
|
|
405
405
|
let currentTrusted = false;
|
|
406
406
|
|
|
407
407
|
function flush() {
|
|
408
|
-
if (currentKey && currentTrusted) trustedKeys.add(
|
|
408
|
+
if (currentKey && currentTrusted) for (const key of currentKey) trustedKeys.add(key);
|
|
409
409
|
}
|
|
410
410
|
|
|
411
411
|
for (const line of configText.split(/\r?\n/)) {
|
|
412
|
-
|
|
412
|
+
// Codexは、`\` を含むkey(Windowsのpath)をTOMLのliteral string('…')で書く。basic string("…")の時は `\\` を戻す。
|
|
413
|
+
const section = line.match(/^\s*\[hooks\.state\.(?:"((?:[^"\\]|\\.)+)"|'([^']+)')\]\s*$/);
|
|
413
414
|
if (section) {
|
|
414
415
|
flush();
|
|
415
|
-
currentKey = section[1];
|
|
416
|
+
currentKey = section[2] != null ? [section[2]] : [section[1], section[1].replace(/\\(["\\])/g, '$1')];
|
|
416
417
|
currentTrusted = false;
|
|
417
418
|
continue;
|
|
418
419
|
}
|
package/src/cli/doctor.test.mjs
CHANGED
|
@@ -456,6 +456,30 @@ test('readCodexHookDiagnosis detects Codex prompt and Stop hooks', () => {
|
|
|
456
456
|
}
|
|
457
457
|
});
|
|
458
458
|
|
|
459
|
+
test('readCodexHookDiagnosis: Windowsのpathのkey(TOMLのliteral string・escapeしたbasic string)の承認を読む', () => {
|
|
460
|
+
const codexHome = mkdtempSync(join(tmpdir(), 'throughline-doctor-codex-trust-'));
|
|
461
|
+
try {
|
|
462
|
+
const hook = (name, timeout) => [{ hooks: [{ type: 'command', command: `/usr/bin/node /pkg/bin/throughline.mjs codex-hook ${name}`, timeout, async: false }] }];
|
|
463
|
+
writeFileSync(join(codexHome, 'hooks.json'), JSON.stringify({ hooks: {
|
|
464
|
+
UserPromptSubmit: hook('user-prompt-submit', 30), PostToolUse: hook('post-tool-use', 30), Stop: hook('stop', 300) } }) + '\n');
|
|
465
|
+
// foxの実測(2026-10-08): Codex 0.160 は `[hooks.state.'C:\Users\…\hooks.json:stop:0:0']` と書く。
|
|
466
|
+
const hooksPath = join(codexHome, 'hooks.json');
|
|
467
|
+
const hash = 'trusted_hash = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"';
|
|
468
|
+
writeFileSync(join(codexHome, 'config.toml'), ['[features]', 'hooks = true', '',
|
|
469
|
+
`[hooks.state.'${hooksPath}:user_prompt_submit:0:0']`, hash, '',
|
|
470
|
+
`[hooks.state.${JSON.stringify(`${hooksPath}:post_tool_use:0:0`)}]`, hash, '',
|
|
471
|
+
`[hooks.state.'${hooksPath}:stop:0:0']`, 'enabled = true', ''].join('\n'));
|
|
472
|
+
const diagnosis = readCodexHookDiagnosis(codexHome);
|
|
473
|
+
assert.equal(diagnosis.managedPromptHooks[0].throughlineDoctorTrusted, true);
|
|
474
|
+
assert.equal(diagnosis.managedPostToolUseHooks[0].throughlineDoctorTrusted, true);
|
|
475
|
+
// trusted_hash の無い項目は、承認済みに数えない。
|
|
476
|
+
assert.equal(diagnosis.managedStopHooks[0].throughlineDoctorTrusted, false);
|
|
477
|
+
assert.equal(diagnosis.managedHookTrust.trustedCount, 2);
|
|
478
|
+
} finally {
|
|
479
|
+
rmSync(codexHome, { recursive: true, force: true });
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
459
483
|
// ─── formatAgo ──────────────────────────────────────────────────────
|
|
460
484
|
|
|
461
485
|
test('formatAgo: 60 秒未満は秒表示', () => {
|
|
@@ -164,7 +164,8 @@ export async function requestCodexAutoHandoff({ payload, db = null,
|
|
|
164
164
|
typeof payload.transcript_path !== 'string' || !isAbsolute(payload.transcript_path) || !isAbsolute(payload.cwd ?? '')) {
|
|
165
165
|
throw error('handoff_hook_identity_invalid');
|
|
166
166
|
}
|
|
167
|
-
|
|
167
|
+
// JS実装のrealpathは、WindowsのCodex Desktopが起動したhookの中で `EISDIR: lstat 'C:'` で落ちる。OSのrealpathを使う。
|
|
168
|
+
const rolloutPath = realpathSync.native(payload.transcript_path);
|
|
168
169
|
const state = readCodexHandoffState(rolloutPath, { threadId, turnId });
|
|
169
170
|
if (state.meta.originator !== 'Codex Desktop' || state.meta.source !== 'vscode') return { status: 'skipped', reason: 'handoff_host_unsupported' };
|
|
170
171
|
if (!sameProjectPath(state.meta.cwd, payload.cwd) || state.latestTurnId !== turnId) throw error('handoff_hook_source_mismatch');
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { test } from 'node:test';
|
|
3
3
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync } from 'node:fs';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { syncBuiltinESMExports } from 'node:module';
|
|
4
6
|
import { spawnSync } from 'node:child_process';
|
|
5
7
|
import { fileURLToPath } from 'node:url';
|
|
6
8
|
import { tmpdir } from 'node:os';
|
|
@@ -216,6 +218,26 @@ test('PreCompactの重複ではworkerを増やさず、manualと別projectでは
|
|
|
216
218
|
assert.equal((await requestCodexAutoHandoff({ ...args, config: { ...config, projects: ['/other'] } })).status, 'skipped');
|
|
217
219
|
}));
|
|
218
220
|
|
|
221
|
+
test('PreCompactの入口は、JS実装のrealpathが使えない環境(WindowsのCodex Desktopのhook)でも引き継ぎを作る', async () => withDb(async (db, home) => {
|
|
222
|
+
const thread = '11111111-1111-1111-1111-111111111111', turn = '22222222-2222-2222-2222-222222222222';
|
|
223
|
+
const dir = join(home, 'codex', 'sessions'); mkdirSync(dir, { recursive: true });
|
|
224
|
+
const file = join(dir, 'rollout.jsonl');
|
|
225
|
+
writeFileSync(file, [{ type: 'session_meta', payload: { id: thread, originator: 'Codex Desktop', source: 'vscode', cwd: home } },
|
|
226
|
+
{ type: 'event_msg', payload: { type: 'task_started', turn_id: turn } }].map(JSON.stringify).join('\n') + '\n');
|
|
227
|
+
// foxの実測(2026-10-08): Desktopが起動したhookの中では、JS実装が drive の根を lstat できずに落ちた。
|
|
228
|
+
const original = fs.realpathSync;
|
|
229
|
+
const broken = () => { throw Object.assign(new Error("EISDIR: illegal operation on a directory, lstat 'C:'"), { code: 'EISDIR' }); };
|
|
230
|
+
broken.native = original.native;
|
|
231
|
+
fs.realpathSync = broken; syncBuiltinESMExports();
|
|
232
|
+
try {
|
|
233
|
+
let launched = 0;
|
|
234
|
+
const result = await requestCodexAutoHandoff({ db, config: { enabled: true, projects: [home], openHost: 'desktop' },
|
|
235
|
+
payload: { trigger: 'auto', cwd: home, session_id: thread, turn_id: turn, transcript_path: file },
|
|
236
|
+
launchWorker: async () => { launched++; } });
|
|
237
|
+
assert.equal(result.status, 'ok'); assert.equal(result.inserted, true); assert.equal(launched, 1);
|
|
238
|
+
} finally { fs.realpathSync = original; syncBuiltinESMExports(); }
|
|
239
|
+
}));
|
|
240
|
+
|
|
219
241
|
function desktopSource(home, thread, turn) {
|
|
220
242
|
const dir = join(home, 'codex', 'sessions'); mkdirSync(dir, { recursive: true });
|
|
221
243
|
const file = join(dir, `rollout-${thread}.jsonl`);
|