throughline 0.16.12 → 0.16.13

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 CHANGED
@@ -10,6 +10,32 @@ shipped to npm but were not individually tagged on GitHub.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.16.13] — 2026-10-10
14
+
15
+ ### 修正
16
+
17
+ - Codex Desktopの自動継続が、手動の引き継ぎ(`$throughline`)などで始めたタスクを`handoff_memory_lineage_untracked`で止め、そのタスクでは何度入力しても同じ理由で止まり続けた。macOSの実機で1つのタスクが止まり、ほかの道具からの返信も始まらなくなった。前任をさかのぼれない記憶を受け取ったタスクも引き継ぎ、受け取った記憶の本文をそのまま後継へ渡す([ADR 0046](docs/adr/0046-codex-handoff-carries-untracked-inherited-memory.md))。
18
+ - 後継の記憶に「系列の最初のタスクが受け取っていた記憶(そのまま)」の節として載せる。その後継がさらに引き継ぐ時も載せる。120,000字を超える分は切り、切った事を書く。
19
+ - 0.16.12までで止まっていたタスクは、次の入力か`throughline auto-handoff resume --operation <ID>`で引き継がれる。
20
+
21
+ ### 対応の範囲
22
+
23
+ | | macOS | Linux | Windows |
24
+ |---|---|---|---|
25
+ | Codex(Desktop) | 直した。実機の止まっていたタスクの記録を読めた(受け取った記憶10,006字)。再開の結果は公開の後に確かめる | 直した。本物のCodex Desktopで、手動の引き継ぎの記憶を持つタスクから2回続けて引き継ぎ、記憶が2つの後継へ渡った | 直した。本物のCodex Desktopでは流していない。CIのWindowsで単体試験が通った |
26
+ | Claude Code | 対象外。Claude Codeの自動継続には、この理由で止める所が無い(記憶は通常のrecallで取る) | 同じ | 同じ |
27
+ | Grok | 対象外。自動継続の機能が無い | 同じ | 同じ |
28
+ | Cursor | 対象外。自動継続の機能が無い | 同じ | 同じ |
29
+
30
+ ### 変わらない事
31
+
32
+ - 自動継続の記録に前任があるタスクの記憶の集め方。実行中の子agent・設定の不一致で止まる扱い。
33
+
34
+ ### 確認していない範囲
35
+
36
+ - Windowsの本物のCodex Desktopでの通し。
37
+ - 受け取った記憶が120,000字を超える実物(試験では作り物で確かめた)。
38
+
13
39
  ## [0.16.12] — 2026-10-10
14
40
 
15
41
  ### 修正
@@ -2147,7 +2173,8 @@ two attempts, instrument first instead of patching again.
2147
2173
 
2148
2174
  ---
2149
2175
 
2150
- [Unreleased]: https://github.com/kitepon/Throughline/compare/v0.16.12...HEAD
2176
+ [Unreleased]: https://github.com/kitepon/Throughline/compare/v0.16.13...HEAD
2177
+ [0.16.13]: https://github.com/kitepon/Throughline/compare/v0.16.12...v0.16.13
2151
2178
  [0.16.12]: https://github.com/kitepon/Throughline/compare/v0.16.11...v0.16.12
2152
2179
  [0.16.11]: https://github.com/kitepon/Throughline/compare/v0.16.10...v0.16.11
2153
2180
  [0.16.10]: https://github.com/kitepon/Throughline/compare/v0.16.9...v0.16.10
package/README.ja.md CHANGED
@@ -33,7 +33,7 @@ throughline auto-handoff disable
33
33
 
34
34
  記憶は引き継ぎ全体の直近20ターンをL2全文、それより古いターンをL1で渡します。元のユーザー依頼と中断地点を保持し、tool結果とThinkingは注入しません。本文内の`auto-handoff detail`コマンドで、凍結したL2/L3を引き継ぎID・origin・turnから取得できます。A→B→Cの連続引き継ぎでも祖先の詳細を取得できます。
35
35
 
36
- 設定不一致、実行中の子agentや既知のnativeコマンド、追跡されていない旧handoff記憶では固定理由を残して停止します。旧タスクの順番待ちに残っている入力(まだ送られていない物)は、後継が作業を続けた後に、順番を保って後継の順番待ちへ運びます(0.16.12から。それより前の版は、ここで引き継ぎを止めていました)。失敗時はローカルの説明ページを開きます。新タスクだけで容量が尽きる場合も、新タスクを増やし続けません。結果不明の指示は自動で再送しません。
36
+ 設定不一致、実行中の子agentや既知のnativeコマンドでは固定理由を残して停止します。手動の引き継ぎなど、前任をさかのぼれない記憶を受け取ったタスクは、受け取った記憶をそのまま後継へ渡して引き継ぎます(0.16.13から。それより前の版は、ここで引き継ぎを止めていました)。旧タスクの順番待ちに残っている入力(まだ送られていない物)は、後継が作業を続けた後に、順番を保って後継の順番待ちへ運びます(0.16.12から。それより前の版は、ここで引き継ぎを止めていました)。失敗時はローカルの説明ページを開きます。新タスクだけで容量が尽きる場合も、新タスクを増やし続けません。結果不明の指示は自動で再送しません。
37
37
 
38
38
  ```bash
39
39
  throughline auto-handoff status --operation <handoff-id> --json
package/README.md CHANGED
@@ -32,7 +32,7 @@ throughline auto-handoff disable
32
32
 
33
33
  記憶は引き継ぎ全体の直近20ターンをL2全文、それより古いターンをL1で渡します。元のユーザー依頼と中断地点を保持し、tool結果とThinkingは注入しません。本文内の`auto-handoff detail`コマンドで、凍結したL2/L3を引き継ぎID・origin・turnから取得できます。A→B→Cの連続引き継ぎでも祖先の詳細を取得できます。
34
34
 
35
- 設定不一致、実行中の子agentや既知のnativeコマンド、追跡されていない旧handoff記憶では固定理由を残して停止します。旧タスクの順番待ちに残っている入力(まだ送られていない物)は、後継が作業を続けた後に、順番を保って後継の順番待ちへ運びます(0.16.12から。それより前の版は、ここで引き継ぎを止めていました)。失敗時はローカルの説明ページを開きます。新タスクだけで容量が尽きる場合も、新タスクを増やし続けません。結果不明の指示は自動で再送しません。
35
+ 設定不一致、実行中の子agentや既知のnativeコマンドでは固定理由を残して停止します。手動の引き継ぎなど、前任をさかのぼれない記憶を受け取ったタスクは、受け取った記憶をそのまま後継へ渡して引き継ぎます(0.16.13から。それより前の版は、ここで引き継ぎを止めていました)。旧タスクの順番待ちに残っている入力(まだ送られていない物)は、後継が作業を続けた後に、順番を保って後継の順番待ちへ運びます(0.16.12から。それより前の版は、ここで引き継ぎを止めていました)。失敗時はローカルの説明ページを開きます。新タスクだけで容量が尽きる場合も、新タスクを増やし続けません。結果不明の指示は自動で再送しません。
36
36
 
37
37
  ```bash
38
38
  throughline auto-handoff status --operation <handoff-id> --json
@@ -0,0 +1,39 @@
1
+ # ADR 0046: 前任をさかのぼれない記憶を受け取った Codex のタスクも引き継ぎ、受け取った記憶をそのまま後継へ渡す
2
+
3
+ 日付: 2026-10-10
4
+
5
+ ## Context
6
+
7
+ Codex Desktop の自動継続は、旧タスクが Throughline の記憶(`## Throughline: 自動継続文脈`・`Active Work Context`・
8
+ `New Codex Thread Handoff` で始まる developer の文)を受け取っていて、その前任が自動継続の記録に無い時、
9
+ `handoff_memory_lineage_untracked` で引き継ぎを止めていた。後継へ渡す記憶は、自動継続の記録をさかのぼって集める。
10
+ さかのぼれない前任の作業は集められないので、黙って落とさないための決まりだった。
11
+
12
+ 2026-10-10、macOS の実機で、手動の引き継ぎ(`$throughline`。`New Codex Thread Handoff`)で 10/8 に始めたタスクが、
13
+ この理由で止まった。止めた順番が問題だった。
14
+
15
+ - 自動継続は、`PreCompact(auto)` の hook でターンを止めてから、worker が引き継げるかを調べる。断った時、止めたターンは戻らない。
16
+ - 途中で止めたタスクは、Codex が順番待ちを動かさない。ほかの道具が入れた返信も始まらない(ADR 0045 と同じ)。
17
+ - 次に人が入力しても、文脈は上限のままなので `PreCompact(auto)` でまた止まり、同じ理由で断る。受け取った記憶は消えないので、
18
+ このタスクは何度入力しても引き継げない。
19
+
20
+ 受け取った記憶の本文は、旧タスクの rollout の最初の developer の文に残っている(実機の例は 10,006 字)。
21
+ 旧タスクのモデルが、それより前の作業について持っていた記憶は、この本文だけだった。
22
+
23
+ ## Decision
24
+
25
+ 1. 前任をさかのぼれない記憶を受け取ったタスクも、引き継ぐ。`handoff_memory_lineage_untracked` は出さない。
26
+ 2. 受け取った記憶の本文(最初の1つ)を、引き継ぎの記録(`runtime_json.inheritedMemory`)へ残す。120,000 字を超える分は切り、
27
+ 元の字数と切った事を残す。
28
+ 3. 後継へ渡す記憶に、「系列の最初のタスクが受け取っていた記憶(そのまま)」の節として載せる。L1・L2 より前に置く。
29
+ 元の `## Throughline: …` の見出しは「(元の見出し: …)」へ書き換え、後継の記憶の見出しを1つに保つ。
30
+ 4. その後継がさらに引き継ぐ時も、系列をさかのぼって同じ本文を載せる。
31
+ 5. 凍結した詳細の取得(`auto-handoff detail`)の対象には入れない。本文そのものを載せているので、取得する物が無い。
32
+ 6. 自動継続の記録に前任があるタスクの扱い(記録をさかのぼって L1・L2 を集める)は変えない。
33
+
34
+ ## Consequences
35
+
36
+ - 手動の引き継ぎで始めたタスク、ほかの端末や消えた記録から続いたタスクも、自動継続で引き継げる。
37
+ - 後継の記憶は、受け取った記憶の分だけ長くなる(上限 120,000 字)。引き継ぎを重ねても、同じ本文を1回載せるだけで増えない。
38
+ - 受け取った記憶より前の作業の L2・L3 は、後継から取得できない。旧タスクでも取得できなかったので、失う物は無い。
39
+ - 0.16.12 までで、この理由で止まっていたタスクは、次の入力か `auto-handoff resume --operation <ID>` で引き継がれる。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "throughline",
3
- "version": "0.16.12",
3
+ "version": "0.16.13",
4
4
  "type": "module",
5
5
  "description": "Persistent memory hooks for Claude Code, Codex, Grok, and Cursor (/clear-safe context compression)",
6
6
  "keywords": [
@@ -95,6 +95,16 @@ export function renderAutoHandoffMemory(db, operation) {
95
95
  lines.push('### 中断地点の取得',
96
96
  reference({ handoffId: operation.handoff_id, origin_session_id: operation.source_session_id, turn_number: latestTurn }, operation), '');
97
97
  }
98
+ // 系列の最初のタスクが、前任をさかのぼれない記憶を受け取っていた。その記憶より前の作業は、この本文にしか無い。
99
+ const inherited = memory.lineage.map(entry => entry.operation.runtime?.inheritedMemory).find(Boolean);
100
+ if (inherited) {
101
+ lines.push('### 系列の最初のタスクが受け取っていた記憶(そのまま)',
102
+ 'この系列より前の作業の記憶です。取得コマンドの対象には入っていません。下の L1・L2 より古い内容です。');
103
+ if (inherited.truncated) lines.push(`(長いので先頭だけ載せています。元は ${inherited.chars} 字)`);
104
+ lines.push(inherited.text
105
+ ? inherited.text.replace(/^## Throughline: ([^\n]*)$/gm, '(元の見出し: $1)')
106
+ : '(本文を読めませんでした)', '');
107
+ }
98
108
  if (memory.olderSummaries.length) {
99
109
  lines.push('### 以前の記憶(L1)');
100
110
  for (const row of memory.olderSummaries) lines.push(`[${new Date(row.created_at).toISOString()}] ${row.summary} (詳細: ${reference(row, operation)})`);
@@ -361,6 +361,17 @@ export async function carryAutoHandoffQueue(db, operation, runtime, { connect =
361
361
  return summary();
362
362
  }
363
363
 
364
+ // 受け取った記憶をそのまま運ぶ上限。超えた分は切り、切った事を記録する(後継の記憶に出す)。
365
+ const INHERITED_MEMORY_MAX_CHARS = 120_000;
366
+
367
+ /** 前任をさかのぼれないタスクが最初に受け取っていた記憶を、引き継ぎの記録へ残す形にする。 */
368
+ export function untrackedInheritedMemory(text) {
369
+ const source = typeof text === 'string' ? text : '';
370
+ const chars = Array.from(source);
371
+ return { text: chars.length > INHERITED_MEMORY_MAX_CHARS ? chars.slice(0, INHERITED_MEMORY_MAX_CHARS).join('') : source,
372
+ chars: chars.length, truncated: chars.length > INHERITED_MEMORY_MAX_CHARS };
373
+ }
374
+
364
375
  /** 旧タスクが、名前を付けない版の作った後継だった時の名前。その後継を作った引き継ぎから、後継の名前を作り直す。 */
365
376
  export function autoHandoffSourceFallbackTitle(db, operation) {
366
377
  const creator = findAutoHandoffForTarget(db, operation.source_thread_id);
@@ -554,7 +565,10 @@ export async function runAutoHandoffWorker(id, { db = getDb(), resume = false, d
554
565
  return sourceBoundary(operation, state) ? state : null;
555
566
  }, { timeoutMs: 30_000, timeoutCode: 'handoff_source_stop_unconfirmed' });
556
567
  if (!state.settings) throw error('handoff_settings_unavailable');
557
- if (state.inheritedThroughlineMemory && !operation.previous_handoff_id) throw error('handoff_memory_lineage_untracked');
568
+ // 前任をさかのぼれない記憶を受け取ったタスク(手動の引き継ぎで始めたタスクなど)。断ると、止めたターンが
569
+ // そのまま残り、次の入力も同じ理由で止まる。受け取った記憶をそのまま後継へ渡して引き継ぐ(ADR 0046)。
570
+ const inheritedMemory = state.inheritedThroughlineMemory && !operation.previous_handoff_id
571
+ ? untrackedInheritedMemory(state.inheritedThroughlineMemoryText) : null;
558
572
  if (operation.previous_handoff_id) {
559
573
  const previous = getAutoHandoff(db, operation.previous_handoff_id);
560
574
  const continuation = deps.readSource(operation, { deliveryText: continuationInput(previous) });
@@ -563,7 +577,8 @@ export async function runAutoHandoffWorker(id, { db = getDb(), resume = false, d
563
577
  }
564
578
  }
565
579
  const native = await deps.nativeState(operation, runtime);
566
- update({ state: 'source_stopped', settings_json: state.settings, runtime_json: { ...native, databasePath: DB_PATH } });
580
+ update({ state: 'source_stopped', settings_json: state.settings,
581
+ runtime_json: { ...native, databasePath: DB_PATH, ...(inheritedMemory ? { inheritedMemory } : {}) } });
567
582
  }
568
583
  // 最中に来た入力のturnが止まり切るまでの間は、境界がまだ見えない。短く待ってから判定する。
569
584
  const confirmSourceBoundary = () => deps.wait(() => sourceBoundary(operation, deps.readSource(operation)) || null,
@@ -14,7 +14,8 @@ import { collectAutoHandoffMemory, ensureAutoHandoffSummaries, renderAutoHandoff
14
14
  import { readCodexHandoffState, settingsMatch, threadStartSettings, resolvePreparedSettings } from './hosts/codex-handoff-state.mjs';
15
15
  import { runAutoHandoffWorker, requestCodexAutoHandoff, sourceBoundary,
16
16
  autoHandoffTargetName, nameAutoHandoffTarget, findLiveAutoHandoffSuccessor,
17
- markAutoHandoffSource, markAutoHandoffSources, autoHandoffSourceFallbackTitle, carryAutoHandoffQueue } from './codex-auto-handoff.mjs';
17
+ markAutoHandoffSource, markAutoHandoffSources, autoHandoffSourceFallbackTitle, carryAutoHandoffQueue,
18
+ untrackedInheritedMemory } from './codex-auto-handoff.mjs';
18
19
  import { parseAutoHandoffArgs } from './cli/auto-handoff.mjs';
19
20
 
20
21
  async function withDb(fn) {
@@ -179,21 +180,80 @@ test('作成応答を失った時は結果不明を保存し、再開でも後
179
180
  assert.equal(counters.created, 1); assert.equal(counters.submitted, 0);
180
181
  }));
181
182
 
182
- test('実行中の子・出所不明の継承記憶では後継を作らない', async () => withDb(async db => {
183
- for (const code of ['handoff_native_child_pending', 'handoff_memory_lineage_untracked']) {
183
+ test('実行中の子がある時は後継を作らない', async () => withDb(async db => {
184
+ for (const code of ['handoff_native_child_pending']) {
184
185
  const op = request(db, code, { now: 1 }).operation;
185
186
  const counters = { created: 0, submitted: 0, notified: 0 };
186
187
  const deps = workerDependencies(db, {}, async () => { counters.submitted++; }, counters);
187
- if (code === 'handoff_memory_lineage_untracked') {
188
- const read = deps.readSource;
189
- deps.readSource = item => ({ ...read(item), inheritedThroughlineMemory: true });
190
- } else deps.nativeState = async () => { throw Object.assign(Error(code), { code }); };
188
+ deps.nativeState = async () => { throw Object.assign(Error(code), { code }); };
191
189
  const result = await runAutoHandoffWorker(op.handoff_id, { db, dependencies: deps });
192
190
  assert.equal(result.error_code, code); assert.equal(result.state, 'failed');
193
191
  assert.equal(counters.created, 0); assert.equal(counters.submitted, 0);
194
192
  }
195
193
  }));
196
194
 
195
+ test('rollout の developer 記憶から、受け取った Throughline の記憶の本文を最初の1つだけ読む', () => {
196
+ const dir = mkdtempSync(join(tmpdir(), 'tl-inherited-'));
197
+ const file = join(dir, 'rollout.jsonl');
198
+ const developer = text => ({ type: 'response_item', payload: { type: 'message', role: 'developer', content: [{ type: 'input_text', text }] } });
199
+ const rows = [{ type: 'session_meta', payload: { id: 'T', cwd: '/project', originator: 'Codex Desktop', source: 'vscode' } },
200
+ developer('ふつうの developer 文'),
201
+ developer('## Throughline: New Codex Thread Handoff\n最初に受け取った記憶'),
202
+ developer('## Throughline: Active Work Context\n後から入った別の記憶')];
203
+ writeFileSync(file, rows.map(row => JSON.stringify({ timestamp: '2026-10-08T00:00:01.000Z', ...row })).join('\n') + '\n');
204
+ const state = readCodexHandoffState(file, { threadId: 'T' });
205
+ assert.equal(state.inheritedThroughlineMemory, true);
206
+ assert.equal(state.inheritedThroughlineMemoryText, '## Throughline: New Codex Thread Handoff\n最初に受け取った記憶');
207
+ writeFileSync(file, rows.slice(0, 2).map(row => JSON.stringify({ timestamp: '2026-10-08T00:00:01.000Z', ...row })).join('\n') + '\n');
208
+ const plain = readCodexHandoffState(file, { threadId: 'T' });
209
+ assert.equal(plain.inheritedThroughlineMemory, false); assert.equal(plain.inheritedThroughlineMemoryText, null);
210
+ });
211
+
212
+ test('前任をさかのぼれない記憶を受け取ったタスクも引き継ぎ、受け取った記憶をそのまま後継へ渡す', async () => withDb(async db => {
213
+ const op = request(db, 'manual-origin', { now: 1 }).operation;
214
+ const counters = { created: 0, submitted: 0, notified: 0 };
215
+ const deps = workerDependencies(db, {}, async () => { counters.submitted++; return { queued_submission_id: 'receipt' }; }, counters);
216
+ const read = deps.readSource;
217
+ const inheritedText = '## Throughline: New Codex Thread Handoff\n手動の引き継ぎで受け取った記憶。前のタスクの合意: 4イベントを申請する';
218
+ deps.readSource = (item, options) => ({ ...read(item, options), inheritedThroughlineMemory: true, inheritedThroughlineMemoryText: inheritedText });
219
+ const result = await runAutoHandoffWorker(op.handoff_id, { db, dependencies: deps });
220
+ assert.equal(result.state, 'continued', '断らずに引き継ぐ');
221
+ assert.equal(result.error_code, null); assert.equal(counters.created, 1); assert.equal(counters.submitted, 1); assert.equal(counters.notified, 0);
222
+ assert.deepEqual(result.runtime.inheritedMemory, { text: inheritedText, chars: Array.from(inheritedText).length, truncated: false });
223
+ }));
224
+
225
+ test('受け取った記憶は系列の最初の引き継ぎに残り、その先の後継の記憶にも出る。見出しは入れ子にしない', async () => withDb(db => {
226
+ let a = snapshot(db, request(db, 'A').operation, 2);
227
+ a = updateAutoHandoff(db, a.handoff_id, { target_thread_id: 'B', state: 'continued',
228
+ runtime_json: { inheritedMemory: untrackedInheritedMemory('## Throughline: New Codex Thread Handoff\n前のタスクの合意: 4イベントを申請する') } }).operation;
229
+ const first = renderAutoHandoffMemory(db, a);
230
+ assert.ok(first.includes('### 系列の最初のタスクが受け取っていた記憶(そのまま)'));
231
+ assert.ok(first.includes('前のタスクの合意: 4イベントを申請する'));
232
+ assert.ok(first.includes('(元の見出し: New Codex Thread Handoff)'));
233
+ assert.equal(first.match(/^## Throughline: /gm).length, 1, '見出しは後継の記憶の1つだけ');
234
+ assert.ok(first.indexOf('系列の最初のタスクが受け取っていた記憶') < first.indexOf('直近'), '古い記憶を先に置く');
235
+
236
+ let b = snapshot(db, request(db, 'B').operation, 2, 100, continuationInput(a));
237
+ b = updateAutoHandoff(db, b.handoff_id, { target_thread_id: 'C' }).operation;
238
+ assert.equal(b.previous_handoff_id, a.handoff_id);
239
+ assert.ok(renderAutoHandoffMemory(db, b).includes('前のタスクの合意: 4イベントを申請する'), '次の後継にも渡る');
240
+
241
+ // 受け取った記憶が無い系列には、この節を出さない
242
+ const plain = snapshot(db, request(db, 'P').operation, 2);
243
+ assert.ok(!renderAutoHandoffMemory(db, plain).includes('受け取っていた記憶'));
244
+ }));
245
+
246
+ test('長すぎる受け取った記憶は先頭だけを運び、切った事を後継の記憶に書く', async () => withDb(db => {
247
+ const long = untrackedInheritedMemory('あ'.repeat(120_001));
248
+ assert.equal(Array.from(long.text).length, 120_000); assert.equal(long.chars, 120_001); assert.equal(long.truncated, true);
249
+ assert.deepEqual(untrackedInheritedMemory(null), { text: '', chars: 0, truncated: false });
250
+ const a = updateAutoHandoff(db, snapshot(db, request(db, 'A').operation, 1).handoff_id, { runtime_json: { inheritedMemory: long } }).operation;
251
+ const text = renderAutoHandoffMemory(db, a);
252
+ assert.ok(text.includes('(長いので先頭だけ載せています。元は 120001 字)'));
253
+ const empty = updateAutoHandoff(db, snapshot(db, request(db, 'E').operation, 1).handoff_id, { runtime_json: { inheritedMemory: untrackedInheritedMemory(null) } }).operation;
254
+ assert.ok(renderAutoHandoffMemory(db, empty).includes('(本文を読めませんでした)'));
255
+ }));
256
+
197
257
  test('開始eventからユーザー入力の保存までの間を別入力の混入と判定しない', async () => withDb(async db => {
198
258
  const op = request(db, 'gap-source', { now: 1 }).operation;
199
259
  const counters = { created: 0, submitted: 0, notified: 0 };
@@ -103,6 +103,8 @@ export function readCodexHandoffState(path, { threadId = null, turnId = null, de
103
103
  const pendingCalls = new Map();
104
104
  const nativeSessions = new Set();
105
105
  let inheritedThroughlineMemory = false;
106
+ // 受け取った記憶の本文(最初の1つ)。前任をさかのぼれないタスクを引き継ぐ時に、そのまま後継へ渡す。
107
+ let inheritedThroughlineMemoryText = null;
106
108
  let completedTools = 0;
107
109
  // turnId を指定した時だけ、その turn より後に始まった turn を並べる。closed は止まった(turn_aborted)か完了したか、
108
110
  // activity はモデルの発言・道具の呼び出し・圧縮・完了のどれかがあったか。入力を受けただけで止まった turn は activity が false。
@@ -147,7 +149,10 @@ export function readCodexHandoffState(path, { threadId = null, turnId = null, de
147
149
  if (row.type === 'response_item') {
148
150
  if (payload?.type === 'message' && payload.role === 'developer') {
149
151
  const text = (payload.content ?? []).map(c => c.text ?? '').join('\n');
150
- if (/^## Throughline: (?:自動継続文脈|Active Work Context|New Codex Thread Handoff)/m.test(text)) inheritedThroughlineMemory = true;
152
+ if (/^## Throughline: (?:自動継続文脈|Active Work Context|New Codex Thread Handoff)/m.test(text)) {
153
+ inheritedThroughlineMemory = true;
154
+ inheritedThroughlineMemoryText ??= text;
155
+ }
151
156
  }
152
157
  if (payload?.type === 'message' && payload.role === 'user') {
153
158
  const text = (payload.content ?? []).filter(c => typeof c.text === 'string').map(c => c.text).join('\n');
@@ -212,6 +217,6 @@ export function readCodexHandoffState(path, { threadId = null, turnId = null, de
212
217
  }
213
218
  return { meta, settings, rawSettings, preparedSettings, context, latestTurnId, turnStartAt, stoppedAt, stoppedReason,
214
219
  completedAt, correlatedTurnId, hasTurnInput, progress, completedTools, pendingCallCount: pendingCalls.size,
215
- pendingNativeSessionCount: nativeSessions.size, inheritedThroughlineMemory, laterTurns,
220
+ pendingNativeSessionCount: nativeSessions.size, inheritedThroughlineMemory, inheritedThroughlineMemoryText, laterTurns,
216
221
  compactedRows: rows.filter(r => r.type === 'compacted').length };
217
222
  }