throughline 0.10.15 → 0.10.17

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,23 @@ shipped to npm but were not individually tagged on GitHub.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.10.17] — 2026-09-12
14
+
15
+ ### 修正
16
+
17
+ - npm 12が単一要素配列で返す公開版情報を自己更新で受け入れるようにした。
18
+ 正常にpackageを更新しても`version_verification_failed`で停止する問題を修正した。
19
+ 旧文字列形式を維持し、複数版・空配列・不正な値は後続の設定・移行処理の前に拒否する。
20
+ - 公開前の梱包文書検査も、npm 12がpackage名をキーにして返す梱包情報に対応した。
21
+
22
+ ## [0.10.16] — 2026-09-07
23
+
24
+ ### 修正
25
+
26
+ - WindowsのDesktop引き継ぎで、稼働中のアプリが使うCodex実行ファイルを選ぶようにした。
27
+ PATH上の古いCodexがデスクトップ版の設定を読めず、新規タスクの作成に失敗する問題を修正した。
28
+ - 引き継ぎ結果へCodex実行元を表示する。アプリの実体を特定できない場合は理由を出して停止する。
29
+
13
30
  ## [0.10.15] — 2026-09-06
14
31
 
15
32
  ### Fixed
@@ -1449,7 +1466,9 @@ two attempts, instrument first instead of patching again.
1449
1466
 
1450
1467
  ---
1451
1468
 
1452
- [Unreleased]: https://github.com/kitepon/Throughline/compare/v0.10.15...HEAD
1469
+ [Unreleased]: https://github.com/kitepon/Throughline/compare/v0.10.17...HEAD
1470
+ [0.10.17]: https://github.com/kitepon/Throughline/compare/v0.10.16...v0.10.17
1471
+ [0.10.16]: https://github.com/kitepon/Throughline/compare/v0.10.15...v0.10.16
1453
1472
  [0.10.15]: https://github.com/kitepon/Throughline/compare/v0.10.14...v0.10.15
1454
1473
  [0.10.14]: https://github.com/kitepon/Throughline/compare/v0.10.13...v0.10.14
1455
1474
  [0.10.13]: https://github.com/kitepon/Throughline/compare/v0.10.12...v0.10.13
package/README.md CHANGED
@@ -638,6 +638,10 @@ opens the new task in Codex Desktop when invoked there, while preserving the
638
638
  existing VS Code and CLI routes. The result reports both the requested and
639
639
  resolved host. The installed `$throughline` skill passes the current Codex
640
640
  surface explicitly so a persistent shell or PTY cannot redirect the handoff.
641
+ WindowsのDesktop引き継ぎは、稼働中のCodex Desktopが使う実行ファイルを選びます。
642
+ PATH上の別バージョンによる設定読取りエラーを避け、選んだ実行元を結果へ表示します。
643
+ Desktopを起動してから実行してください。実行ファイルが見つからない場合や、異なる実行ファイルの
644
+ Desktopが複数稼働している場合は理由を出して停止します。明示した`--codex-app-server-bin`は優先します。
641
645
  The
642
646
  individual commands remain available: validate the fresh-thread handoff with
643
647
  `throughline codex-handoff-smoke --session codex:<thread-id>`, optionally audit
File without changes
@@ -44,6 +44,9 @@ Windows PowerShell 5.1へ切り替えない。更新前CLIを再利用せず、
44
44
  新CLI、公開PATHから起動したCLI、そのversion、`throughline.self_update.v1`成功結果がすべて
45
45
  一致した場合だけ完了とする。複数npm prefixが混在して公開PATHが旧実体を指す場合は失敗する。
46
46
 
47
+ 公開版の照合は、npmの旧文字列形式と[npm 12の単一結果配列](https://docs.npmjs.com/cli/v12/commands/npm-view/#output)を受け入れる。
48
+ 複数版・空配列・不正な版情報から一つを推測して選ばず、後続の設定・移行処理の前に失敗を返す。
49
+
47
50
  ## 明示的失敗の契約
48
51
 
49
52
  想定外の状態、外部入力違反、I/O失敗、依存不足を成功扱いにしない。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "throughline",
3
- "version": "0.10.15",
3
+ "version": "0.10.17",
4
4
  "type": "module",
5
5
  "description": "Persistent memory hooks for Claude Code, Codex, Grok, and Cursor (/clear-safe context compression)",
6
6
  "keywords": [
@@ -9,6 +9,7 @@ import { sameProjectPath } from '../project-path.mjs';
9
9
  import { shQuote } from '../os/shell.mjs';
10
10
  import { openUrlWithOsHandler } from '../os/open-url.mjs';
11
11
  import { runTerminalDoScript } from '../os/macos-terminal.mjs';
12
+ import { resolveCodexRuntime } from '../os/codex-runtime.mjs';
12
13
 
13
14
  async function readStdin() {
14
15
  let raw = '';
@@ -242,6 +243,7 @@ function renderTextResult(result) {
242
243
  lines.push(` execute: ${result.execute ? 'yes' : 'no'}`);
243
244
  lines.push(` open requested: ${result.requestedOpenHost ?? result.openHost}`);
244
245
  lines.push(` open resolved: ${result.resolvedOpenHost ?? result.open?.host ?? result.openHost}`);
246
+ if (result.runtime) lines.push(` Codex実行元: ${result.runtime.command} (${result.runtime.source})`);
245
247
  lines.push(` handoff smoke: ${result.handoffSmoke.status}`);
246
248
  lines.push(` prompt chars: ${result.handoffSmoke.promptChars}/${result.handoffSmoke.maxPromptChars}`);
247
249
  lines.push(` model prompt: ${result.modelPromptChars}`);
@@ -452,11 +454,16 @@ export async function run(args) {
452
454
  }
453
455
 
454
456
  if (parsed.execute && result.status === 'ready') {
457
+ const runtime = resolveCodexRuntime({
458
+ host: result.resolvedOpenHost,
459
+ override: parsed.codexAppServerBin,
460
+ });
461
+ result.runtime = runtime;
455
462
  const startResult = await runCodexNewThreadHandoff({
456
463
  cwd: process.cwd(),
457
464
  prompt: handoffPrompt,
458
- command: parsed.codexAppServerBin ?? 'codex',
459
- commandArgs: parsed.codexAppServerBin ? [] : ['app-server', '--listen', 'stdio://'],
465
+ command: runtime.command,
466
+ commandArgs: runtime.commandArgs,
460
467
  timeoutMs: parsed.timeoutMs,
461
468
  requestTimeoutMs: parsed.requestTimeoutMs,
462
469
  waitForTurn: false,
@@ -310,6 +310,9 @@ test('codex-handoff-start execute creates a new app-server thread and can skip o
310
310
  assert.equal(payload.startThreadManually, false);
311
311
  assert.equal(payload.newThread.threadId, '019e2000-0000-7000-8000-000000000001');
312
312
  assert.equal(payload.newThread.delivery, 'developer-item');
313
+ assert.equal(payload.runtime.command, script);
314
+ assert.equal(payload.runtime.source, 'explicit');
315
+ assert.deepEqual(payload.runtime.commandArgs, []);
313
316
  assert.equal(payload.newThread.injectSent, true);
314
317
  assert.equal(payload.newThread.turnStatus, 'not-started');
315
318
  assert.equal(payload.open.status, 'skipped');
@@ -77,7 +77,8 @@ function succeeded(result) {
77
77
  function parseRegistryVersion(result) {
78
78
  if (!succeeded(result)) return null;
79
79
  try {
80
- const value = JSON.parse(result.stdout);
80
+ let value = JSON.parse(result.stdout);
81
+ if (Array.isArray(value) && value.length === 1) [value] = value;
81
82
  return typeof value === 'string' && VERSION_PATTERN.test(value) ? value : null;
82
83
  } catch {
83
84
  return null;
@@ -181,6 +181,52 @@ test('new CLI completes install, migration, and diagnostics in order', () => {
181
181
  });
182
182
  });
183
183
 
184
+ test('自己更新はnpmの単一版を文字列と単一要素配列から読み取る', () => {
185
+ for (const platform of ['darwin', 'linux', 'win32']) {
186
+ for (const registryVersion of ['0.10.16', ['0.10.16']]) {
187
+ const stdout = sink();
188
+ const calls = [];
189
+ const runner = (command, args) => {
190
+ calls.push([command, ...args]);
191
+ if (command === 'npm' || command === 'pwsh.exe') return ok(JSON.stringify(registryVersion));
192
+ if (args.at(-1) === 'install') return ok();
193
+ if (args.at(-2) === 'migrate') return ok(JSON.stringify(migration()));
194
+ if (args.at(-2) === 'factory-diagnostics') return ok(JSON.stringify(readyDiagnostics('0.10.16')));
195
+ throw new Error('想定外の更新工程');
196
+ };
197
+ assert.equal(run(['--json'], {
198
+ stdout, stderr: sink(), runner, platform, packageVersion: '0.10.16',
199
+ nodePath: '/node', cliPath: '/throughline.mjs',
200
+ env: {
201
+ THROUGHLINE_SELF_UPDATE_PHASE: 'post-install',
202
+ THROUGHLINE_SELF_UPDATE_BEFORE_VERSION: '0.10.15',
203
+ THROUGHLINE_SELF_UPDATE_EXPECTED_VERSION: '0.10.16',
204
+ },
205
+ }), 0, `${platform}: ${JSON.stringify(registryVersion)}`);
206
+ assert.equal(calls.length, 4);
207
+ assert.deepEqual(JSON.parse(stdout.values.join('')), updateSuccess('0.10.15', '0.10.16'));
208
+ }
209
+ }
210
+ });
211
+
212
+ test('自己更新は複数版・空配列・不正な版情報で後続工程を始めない', () => {
213
+ for (const value of [[], ['0.10.16', '0.10.17'], [['0.10.16']], [null], [{ version: '0.10.16' }], ['invalid']]) {
214
+ const stdout = sink();
215
+ let calls = 0;
216
+ assert.equal(run(['--json'], {
217
+ stdout, stderr: sink(), packageVersion: '0.10.16', platform: 'darwin',
218
+ runner: () => { calls += 1; return ok(JSON.stringify(value)); },
219
+ env: {
220
+ THROUGHLINE_SELF_UPDATE_PHASE: 'post-install',
221
+ THROUGHLINE_SELF_UPDATE_BEFORE_VERSION: '0.10.15',
222
+ THROUGHLINE_SELF_UPDATE_EXPECTED_VERSION: '0.10.16',
223
+ },
224
+ }), 1);
225
+ assert.equal(calls, 1);
226
+ assert.equal(JSON.parse(stdout.values.join('')).stage, 'version_verification_failed');
227
+ }
228
+ });
229
+
184
230
  test('self-update fails closed without reflecting command output', () => {
185
231
  const stdout = sink();
186
232
  const stderr = sink();
@@ -6,9 +6,20 @@ import { fileURLToPath } from 'node:url';
6
6
  import {
7
7
  extractMarkdownTargets,
8
8
  findMissingPackedTargets,
9
+ packedFileSet,
9
10
  } from '../scripts/verify-pack-markdown-links.mjs';
10
11
  import { spawnPortableSync } from './os/portable-spawn-sync.mjs';
11
12
 
13
+ test('梱包文書検査はnpmの旧配列形式とpackage名をキーにする形式を読む', () => {
14
+ const packed = { name: 'throughline', files: [{ path: 'README.md' }, { path: 'docs/00_overview.md' }] };
15
+ for (const report of [[packed], { throughline: packed }]) {
16
+ assert.deepEqual([...packedFileSet(report)], ['README.md', 'docs/00_overview.md']);
17
+ }
18
+ for (const report of [null, {}, [], [packed, packed], { throughline: { files: null } }]) {
19
+ assert.throws(() => packedFileSet(report), /npm pack dry-run JSON shape invalid/);
20
+ }
21
+ });
22
+
12
23
  test('Markdown-only CI runs the product-owned documentation contract', () => {
13
24
  const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
14
25
  assert.equal(
@@ -0,0 +1,36 @@
1
+ import { spawnPortableSync } from './portable-spawn-sync.mjs';
2
+
3
+ function discoverWindowsDesktopCodex() {
4
+ const result = spawnPortableSync('pwsh.exe', ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', `
5
+ $ErrorActionPreference = 'Stop'
6
+ $processes = @(Get-CimInstance Win32_Process)
7
+ $paths = @($processes | Where-Object { $_.Name -eq 'codex.exe' } | ForEach-Object {
8
+ $child = $_
9
+ $parent = $processes | Where-Object { $_.ProcessId -eq $child.ParentProcessId }
10
+ if ($parent.ExecutablePath -match '[\\\\/]OpenAI\\.Codex_[^\\\\/]+[\\\\/]app[\\\\/]ChatGPT\\.exe$' -and $child.ExecutablePath) {
11
+ $child.ExecutablePath
12
+ }
13
+ })
14
+ ConvertTo-Json -InputObject $paths -Compress
15
+ `], { encoding: 'utf8', timeout: 15000 });
16
+ if (result.error || result.status !== 0) {
17
+ throw new Error(`Codex Desktopの実行ファイルを取得できません: ${result.error?.message ?? result.stderr.trim()}`);
18
+ }
19
+ return JSON.parse(result.stdout.trim());
20
+ }
21
+
22
+ export function resolveCodexRuntime({ host, override = null, platform = process.platform,
23
+ discover = discoverWindowsDesktopCodex } = {}) {
24
+ if (override) return { command: override, commandArgs: [], source: 'explicit' };
25
+ const commandArgs = ['app-server', '--listen', 'stdio://'];
26
+ if (platform !== 'win32' || host !== 'desktop') {
27
+ return { command: 'codex', commandArgs, source: 'path' };
28
+ }
29
+ const candidates = [...new Set(discover())];
30
+ if (candidates.length !== 1) {
31
+ throw new Error(candidates.length === 0
32
+ ? '稼働中のCodex Desktopの実行ファイルが見つかりません。アプリを起動してから再実行してください。'
33
+ : '複数のCodex Desktop実行ファイルが稼働しています。使用するアプリだけを残して再実行してください。');
34
+ }
35
+ return { command: candidates[0], commandArgs, source: 'desktop-process' };
36
+ }
@@ -0,0 +1,31 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { resolveCodexRuntime } from './codex-runtime.mjs';
4
+
5
+ test('Windows Desktopは稼働中アプリのCodexを使う', () => {
6
+ const runtime = resolveCodexRuntime({ platform: 'win32', host: 'desktop',
7
+ discover: () => ['C:\\Codex\\codex.exe', 'C:\\Codex\\codex.exe'] });
8
+ assert.equal(runtime.command, 'C:\\Codex\\codex.exe');
9
+ assert.deepEqual(runtime.commandArgs, ['app-server', '--listen', 'stdio://']);
10
+ assert.equal(runtime.source, 'desktop-process');
11
+ });
12
+
13
+ test('明示したapp-serverはDesktopの探索より優先する', () => {
14
+ const runtime = resolveCodexRuntime({ platform: 'win32', host: 'desktop', override: 'fake.mjs',
15
+ discover: () => { throw new Error('探索してはいけない'); } });
16
+ assert.equal(runtime.command, 'fake.mjs');
17
+ assert.deepEqual(runtime.commandArgs, []);
18
+ });
19
+
20
+ test('対象外のOSとhostは従来のCodexを使う', () => {
21
+ for (const [platform, host] of [['darwin', 'desktop'], ['win32', 'cli'], ['win32', 'vscode']]) {
22
+ assert.equal(resolveCodexRuntime({ platform, host }).command, 'codex');
23
+ }
24
+ });
25
+
26
+ test('Desktopの実体がない場合や複数ある場合は古いCLIへ切り替えない', () => {
27
+ for (const candidates of [[], ['C:\\one\\codex.exe', 'C:\\two\\codex.exe']]) {
28
+ assert.throws(() => resolveCodexRuntime({ platform: 'win32', host: 'desktop',
29
+ discover: () => candidates }), /Codex Desktop/);
30
+ }
31
+ });