throughline 0.8.2 → 0.8.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 CHANGED
@@ -10,6 +10,31 @@ shipped to npm but were not individually tagged on GitHub.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.8.4] — 2026-07-20
14
+
15
+ ### Fixed
16
+
17
+ - The installed Codex skill now selects Desktop, VS Code, or CLI from the
18
+ current Codex surface and passes an explicit `--open-host` value. A command
19
+ launched through an older persistent PTY can no longer silently redirect a
20
+ Desktop handoff to the PTY's inherited VS Code or Terminal host.
21
+ - `codex-handoff-start` now reports both requested and resolved open hosts in
22
+ JSON and text output while retaining the existing `openHost` field.
23
+
24
+ ## [0.8.3] — 2026-07-20
25
+
26
+ ### Fixed
27
+
28
+ - Codex fresh-thread handoffs now detect when they were launched from Codex
29
+ Desktop and open the new local task with the app's
30
+ `codex://threads/<thread-id>` deep link instead of spawning a Terminal
31
+ `codex resume` session. `--open-host desktop` is available explicitly;
32
+ VS Code and CLI opening behavior is unchanged.
33
+ - Concurrent CLI and Codex hook processes now configure a bounded SQLite busy
34
+ timeout and avoid reapplying WAL mode when it is already active. A transient
35
+ writer or WAL recovery lock no longer makes DB initialization fail
36
+ immediately, and a failed initialization is never retained as the singleton.
37
+
13
38
  ## [0.8.2] — 2026-07-20
14
39
 
15
40
  ### Fixed
@@ -1119,7 +1144,11 @@ two attempts, instrument first instead of patching again.
1119
1144
 
1120
1145
  ---
1121
1146
 
1122
- [Unreleased]: https://github.com/kitepon-rgb/Throughline/compare/v0.8.0...HEAD
1147
+ [Unreleased]: https://github.com/kitepon-rgb/Throughline/compare/v0.8.4...HEAD
1148
+ [0.8.4]: https://github.com/kitepon-rgb/Throughline/compare/v0.8.3...v0.8.4
1149
+ [0.8.3]: https://github.com/kitepon-rgb/Throughline/compare/v0.8.2...v0.8.3
1150
+ [0.8.2]: https://github.com/kitepon-rgb/Throughline/compare/v0.8.1...v0.8.2
1151
+ [0.8.1]: https://github.com/kitepon-rgb/Throughline/compare/v0.8.0...v0.8.1
1123
1152
  [0.8.0]: https://github.com/kitepon-rgb/Throughline/compare/v0.7.0...v0.8.0
1124
1153
  [0.7.0]: https://github.com/kitepon-rgb/Throughline/compare/v0.6.3...v0.7.0
1125
1154
  [0.6.3]: https://github.com/kitepon-rgb/Throughline/compare/v0.6.2...v0.6.3
package/README.md CHANGED
@@ -304,7 +304,7 @@ throughline codex-summarize --session codex:<thread-id> --json
304
304
  throughline codex-resume --session codex:<thread-id>
305
305
  throughline codex-resume --session codex:<thread-id> --format handoff
306
306
  throughline codex-handoff-start --session codex:<thread-id>
307
- throughline codex-handoff-start --session codex:<thread-id> --execute --open-host vscode
307
+ throughline codex-handoff-start --session codex:<thread-id> --execute --open-host desktop
308
308
  throughline codex-handoff-smoke --session codex:<thread-id>
309
309
  throughline codex-handoff-model-smoke --session codex:<thread-id> --dry-run --json
310
310
  THROUGHLINE_EXPERIMENTAL_CODEX_HANDOFF_MODEL_SMOKE=1 \
@@ -567,7 +567,12 @@ live model smoke, and can include the prompt with `--print-prompt`. With
567
567
  `--memo-stdin`, it also propagates `--memo-stdin` into the replay commands and
568
568
  reminds you to pipe the same memo when using them separately. Add `--execute`
569
569
  to create a new Codex app-server thread, inject the handoff memory as a
570
- developer item, and open it with `--open-host auto|vscode|cli|none`. The
570
+ developer item, and open it with `--open-host auto|desktop|vscode|cli|none`. `auto`
571
+ opens the new task in Codex Desktop when invoked there, while preserving the
572
+ existing VS Code and CLI routes. The result reports both the requested and
573
+ resolved host. The installed `$throughline` skill passes the current Codex
574
+ surface explicitly so a persistent shell or PTY cannot redirect the handoff.
575
+ The
571
576
  individual commands remain available: validate the fresh-thread handoff with
572
577
  `throughline codex-handoff-smoke --session codex:<thread-id>`, optionally audit
573
578
  the model-smoke boundary with
@@ -785,7 +790,7 @@ aggregate は collection が既定OFFで、canonical dotagents config の
785
790
  | `throughline codex-summarize --session codex:<id>` | Summarize captured Codex L2 into L1 with the Codex CLI backend |
786
791
  | `throughline codex-resume --session codex:<id>` | Render Codex active-work context from a captured Codex session |
787
792
  | `throughline codex-resume --session codex:<id> --format handoff` | Render a concise fresh-thread handoff prompt without mutating the current thread |
788
- | `throughline codex-handoff-start --session codex:<id>` | Guided start plan for moving handoff memory into a new Codex thread; add `--execute` to create the thread through app-server, inject developer memory, and open it with `--open-host auto\|vscode\|cli\|none`; use `--print-prompt` to include the prompt and `--memo-stdin` to carry a current-work memo |
793
+ | `throughline codex-handoff-start --session codex:<id>` | Guided start plan for moving handoff memory into a new Codex thread; add `--execute` to create the thread through app-server, inject developer memory, and open it with `--open-host auto\|desktop\|vscode\|cli\|none`; output includes requested and resolved host, and the installed `$throughline` skill passes the current Codex surface explicitly; use `--print-prompt` to include the prompt and `--memo-stdin` to carry a current-work memo |
789
794
  | `throughline codex-handoff-smoke --session codex:<id>` | Read-only validation that the fresh-thread handoff prompt is pasteable before starting a new thread |
790
795
  | `throughline codex-handoff-model-smoke --session codex:<id>` | Experimental marker smoke for the handoff prompt. `--dry-run` checks readiness / command boundary without starting Codex exec; `--memo-stdin` carries a current-work memo; live `codex exec --ephemeral --sandbox read-only` requires explicit env opt-in |
791
796
  | `throughline codex-visibility-smoke --session codex:<id>` | Experimental Codex app-server marker smoke; injects memory and starts a model turn |
@@ -248,7 +248,7 @@ Usage:
248
248
  render command, optional --print-prompt, and
249
249
  --memo-stdin replay guidance. Use --execute to
250
250
  create a new app-server thread, inject handoff
251
- memory, and open it with --open-host auto|vscode|cli|none
251
+ memory, and open it with --open-host auto|desktop|vscode|cli|none
252
252
  throughline codex-visibility-smoke
253
253
  Experimental: inject Codex active-work memory and
254
254
  start a marker-check model turn. Requires
@@ -25,6 +25,17 @@ not ask for confirmation. Execute the handoff-start command directly. If it
25
25
  fails, report the error plainly instead of silently falling back to another
26
26
  memory source or current-thread rollback.
27
27
 
28
+ Choose the open host from the current Codex application surface, not from
29
+ environment variables inherited by the shell or a persistent PTY. Pass it
30
+ explicitly whenever the surface is known:
31
+
32
+ - Codex Desktop: `--open-host desktop`
33
+ - Codex in VS Code: `--open-host vscode`
34
+ - Codex CLI: `--open-host cli`
35
+
36
+ Use `--open-host auto` only when the Codex surface is genuinely unknown. In
37
+ that case, report both the requested and resolved hosts from the CLI result.
38
+
28
39
  ## Common Requests
29
40
 
30
41
  ### Bare "$throughline" / "use Throughline"
@@ -32,9 +43,13 @@ memory source or current-thread rollback.
32
43
  Run:
33
44
 
34
45
  ```bash
35
- throughline codex-handoff-start --execute
46
+ throughline codex-handoff-start --execute --open-host desktop
36
47
  ```
37
48
 
49
+ The example above is for Codex Desktop. Replace `desktop` with `vscode` or
50
+ `cli` when that is the current Codex surface. Do not copy the host identity
51
+ from the shell or PTY that happens to execute the command.
52
+
38
53
  This is the Codex new-thread continuation flow. It does not mutate the current
39
54
  Codex thread. Report the new thread id, open status, and any manual resume
40
55
  command if the host could not be opened automatically.
@@ -80,7 +95,7 @@ If the user wants to continue in a fresh Codex thread instead of mutating the
80
95
  current thread, use:
81
96
 
82
97
  ```bash
83
- throughline codex-handoff-start --session codex:<current-thread-id> --execute
98
+ throughline codex-handoff-start --session codex:<current-thread-id> --execute --open-host <current-codex-surface>
84
99
  ```
85
100
 
86
101
  If the user gave a current-work memo, pipe it with `--memo-stdin`. This starts a
@@ -106,7 +121,7 @@ memory, unless they explicitly ask to mutate the current Codex thread.
106
121
  Execute:
107
122
 
108
123
  ```bash
109
- throughline codex-handoff-start --execute
124
+ throughline codex-handoff-start --execute --open-host <current-codex-surface>
110
125
  ```
111
126
 
112
127
  Report only the essential outcome, especially the new thread id and open status.
@@ -120,7 +135,7 @@ throughline trim --dry-run --host codex
120
135
  Safe new-thread continuation:
121
136
 
122
137
  ```bash
123
- throughline codex-handoff-start --session codex:<current-thread-id> --execute --json
138
+ throughline codex-handoff-start --session codex:<current-thread-id> --execute --open-host <current-codex-surface> --json
124
139
  ```
125
140
 
126
141
  Report the context reduction estimate from the dry-run when present:
@@ -140,6 +140,7 @@ schema v4 で PostToolUse (`capture-tool`) は廃止、L2/L3 は Stop 内で一
140
140
  | **npm 公開 (v0.6.1): Spotter auditor context projection** | `throughline auditor-context`を追加。exact session/projectと最新完了L2 pairのorigin/turn/hash freshnessを検査し、fresh時だけbounded user/assistant本文をversioned JSONで返す。DBはread-onlyで開き、作成・migration・WAL書き込みをしない。Spotter側はproject opt-inでのみ利用する。580 tests、CI 6/6、pack scan、registry global install / doctorを通過し、npm / tag / GitHub Releaseを2026-07-13に公開した |
141
141
  | **npm 公開 (v0.6.2): factory diagnostics + local runtime error store** | `throughline factory-diagnostics --json` と `throughline runtime-errors ... --json` を追加。collection は canonical dotagents config の `collection.enabled: true` が明示された時だけ有効で既定OFF、store は network I/O を行わない。公開commit `e6ce6e3`、CI `29238704750`、npm `latest`、tag / GitHub Release、registry由来隔離installと診断snapshotを2026-07-13に確認した |
142
142
  | **npm 公開 (v0.6.3): Codex diagnostics ready 集約 + Windows bounded observer** | 3 managed Codex hooks が canonical `ready` のとき hook summary / Codex connector を `ready` とし、未検査 Claude connector は明示 `unverified` のまま Codex-only overall から除外する。Windows runtime-error observer は、既存 state の使用前検証と新規 file の ACL 付与・read-back を維持しつつ、同一 mutation 内の重複 PowerShell ACL 検証を除いた。公開commit `fc83ddf`、CI `29284655280`(9/9 green)、npm `throughline@0.6.3`、tag / GitHub Release、npm shasum `4f3fcd2598a75f026358dae7f3eb3165242b580b` を2026-07-14に確認した |
143
+ | **release candidate (v0.8.4): Codex handoff host境界の明示化** | `$throughline` skillは現在のCodex UI surfaceからDesktop/VS Code/CLIを選び、`--open-host`を明示する。shell/永続PTYの継承環境はsurface判定に使わない。`codex-handoff-start`は互換の`openHost`を残し、requested / resolved hostをJSONとtextで報告する。古いVS Code由来PTY事故形と配布skillをfocused testで固定し、Claude-facing hook/`/tl`/baton/resume契約は変更しない。公開実績はpush後CI、npm、tag / GitHub Release、registry由来global installの確認後に追記する |
143
144
  | **未リリース: Codex current-session 75% trigger** | Codex 自動発火を token-monitor に依存させず、global install が Codex `UserPromptSubmit` / `PostToolUse` hooks も登録する。hook は当該 Codex session の rollout `token_count` を直接読み、verified 75% 以上なら同じ user turn または tool loop 継続前に `$throughline` workflow 実行指示を `additionalContext` で注入する。Stop hook の guarded auto-refresh は残す。`~/.codex/config.toml` は旧 `codex_hooks = true` に加えて現行 `hooks = true` も有効化する |
144
145
  | **グローバル E2E 検証** | 2026-04-17 別ディレクトリから `throughline doctor` 全緑を確認 |
145
146
 
@@ -28,7 +28,7 @@
28
28
  - Claude-primary の現行 L2 -> L1 要約は `codex-sidecar` が configured の場合に sidecar を優先し、使えない場合は Claude Haiku 経路に戻る。Codex-primary は Codex CLI backend 失敗を明示 error にし、Claude Haiku / raw L2 へ fallback しない。
29
29
  - Codex guarded trim は、Codex app-server の `thread/read` / `thread/resume` / `thread/rollback` / `thread/inject_items` を使う。明示 thread identity と injectable memory がない場合は mutation 前に拒否する。rollout/app-server turn count mismatch は診断に残し、`thread/read` / `thread/resume` が同じ count を返す場合は app-server 側の差分で rollback `numTurns` を補正する。
30
30
  - Claude `/rewind` 自動化はまだ有効化しない。
31
- - 2026-05-10 update: Codex automatic current-thread refresh mutation は無効化する。live rollback / inject で token_count が一時的に落ちても同一 thread で戻る実測があるため、`UserPromptSubmit` / `PostToolUse` / `Stop` hooks は capture / monitor state write のみ行い、`codex_auto_refresh_disabled` で quiet にする。bare `$throughline` は `codex-handoff-start --execute` による app-server 新スレッド handoff とし、明示 `trim --execute --host codex` だけを診断用 current-thread rollback / inject path として残す。
31
+ - 2026-05-10 update: Codex automatic current-thread refresh mutation は無効化する。live rollback / inject で token_count が一時的に落ちても同一 thread で戻る実測があるため、`UserPromptSubmit` / `PostToolUse` / `Stop` hooks は capture / monitor state write のみ行い、`codex_auto_refresh_disabled` で quiet にする。bare `$throughline` は `codex-handoff-start --execute --open-host <current-codex-surface>` による app-server 新スレッド handoff とし、明示 `trim --execute --host codex` だけを診断用 current-thread rollback / inject path として残す。current surfaceはCodex UI contextから決め、shell/永続PTYの継承環境へ委ねない。
32
32
 
33
33
  ## 新セッション引き継ぎ
34
34
 
@@ -271,7 +271,7 @@ Phase 3 implementation result (2026-05-06):
271
271
  - [x] `throughline codex-resume --format handoff` は current thread を mutate せず、新規 Codex thread に貼る短い handoff prompt を返す。handoff view は L2 件数 / 本文長 / detail refs を cap し、full active-work context は通常 text renderer に残す。
272
272
  - [x] `throughline codex-handoff-smoke --session codex:<thread_id>` は新規 thread handoff prompt を read-only に検査し、prompt size / required sections / mutation boundary / detail command dedupe を固定する。
273
273
  - [x] `throughline codex-handoff-model-smoke --session codex:<thread_id>` は明示 opt-in 時だけ `codex exec --ephemeral --ignore-user-config --ignore-rules --sandbox read-only` で handoff prompt の marker model smoke を行う。`--dry-run` は env なしで readiness / command boundary を監査し、`--print-prompt` で結合 prompt を出せる。`--memo-stdin` で Codex-primary current-work memo も同じ prompt に含める。structural handoff smoke が ready でなければ拒否し、current thread は mutate しない。
274
- - [x] `throughline codex-handoff-start --session codex:<thread_id>` は safe continuation の guided entrypoint として、structural smoke / model smoke dry-run / handoff render / optional live smoke / `--print-prompt` をまとめて表示する。`--execute` では app-server `thread/start` + `thread/inject_items` で新 thread に developer memory を注入し、`--open-host auto|vscode|cli|none` で表示を開く。`--memo-stdin` 時は replay 用コマンドにも `--memo-stdin` を伝播し、same memo を pipe する注意を出す。
274
+ - [x] `throughline codex-handoff-start --session codex:<thread_id>` は safe continuation の guided entrypoint として、structural smoke / model smoke dry-run / handoff render / optional live smoke / `--print-prompt` をまとめて表示する。`--execute` では app-server `thread/start` + `thread/inject_items` で新 thread に developer memory を注入し、`--open-host auto|desktop|vscode|cli|none` で表示を開く。CLIはrequested / resolved hostを両方報告する。`auto` は Codex Desktop 起点(`CODEX_INTERNAL_ORIGINATOR_OVERRIDE="Codex Desktop"` または `__CFBundleIdentifier=com.openai.codex`)を CLI より先に識別するが、bare `$throughline` skillは現在のCodex UI surfaceを明示指定し、永続PTYの継承環境へhost選択を委ねない。Desktop の `codex://threads/<thread-id>` deep linkとVS Code / CLI の既存経路は維持する。`--memo-stdin` 時は replay 用コマンドにも `--memo-stdin` を伝播し、same memo を pipe する注意を出す。
275
275
  - [x] `throughline codex-resume --format item-json` は Codex developer message item JSON を返す。
276
276
  - [x] `codex-resume` は explicit session を受け取れる。省略時は cwd の最新 `codex:%` session だけを対象にし、Claude session を混ぜない。
277
277
  - [x] `src/codex-handoff.test.mjs` / `src/codex-resume.test.mjs` で renderer と CLI shape を固定した。
@@ -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 hooks と `$throughline` skill を自動登録する。2026-05-10 以降、bare `$throughline` は `throughline codex-handoff-start --execute` による app-server 新スレッド handoff とする。hooks は capture / monitor state write だけを行い、current-thread rollback / inject は明示 `trim --execute --host codex` を要求された時だけ使う。
443
+ - [x] Codex primary の setup / install 手順は README に記録した。global install は Codex hooks と `$throughline` skill を自動登録する。2026-05-10 以降、bare `$throughline` は `throughline codex-handoff-start --execute --open-host <current-codex-surface>` による app-server 新スレッド handoffとし、current surfaceはCodex UI contextから明示する。hooks は capture / monitor state write だけを行い、current-thread rollback / inject は明示 `trim --execute --host codex` を要求された時だけ使う。
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`
@@ -460,7 +460,7 @@ Phase 5 implementation status (2026-05-06):
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
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-10 UX 修正: bare `$throughline` は `doctor --codex` / `trim --dry-run --all` / `trim --preflight --all` を AI に順番実行させる説明 surface でも、`trim --execute --host codex --all` を直接実行する current-thread refresh でもなく、`throughline codex-handoff-start --execute` による app-server 新スレッド handoff とする。current-thread rollback / inject は明示要求時だけ使う。
463
+ - 2026-05-10 UX 修正: bare `$throughline` は `doctor --codex` / `trim --dry-run --all` / `trim --preflight --all` を AI に順番実行させる説明 surface でも、`trim --execute --host codex --all` を直接実行する current-thread refresh でもなく、`throughline codex-handoff-start --execute --open-host <current-codex-surface>` による app-server 新スレッド handoff とする。current surfaceはCodex UI contextから明示し、current-thread rollback / inject は明示要求時だけ使う。
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 補正の根拠であり、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 で削減できない」という意味ではない。
@@ -25,8 +25,9 @@ count を返す場合は、その差分で rollback `numTurns` を補正しま
25
25
  一時的に下がっても同一 thread で戻る挙動を確認したため、Codex automatic
26
26
  current-thread refresh は無効化します。`UserPromptSubmit` / `PostToolUse` / `Stop`
27
27
  hooks は capture / monitor state write のみ行い、`codex_auto_refresh_disabled` で
28
- quiet にします。bare `$throughline` は `codex-handoff-start --execute` による
29
- app-server 新スレッド handoff とします。
28
+ quiet にします。bare `$throughline` は
29
+ `codex-handoff-start --execute --open-host <current-codex-surface>` による
30
+ app-server 新スレッド handoff とし、surfaceはCodex UI contextから明示します。
30
31
 
31
32
  最初のインシデント仮説に対する重要な訂正:
32
33
 
@@ -25,7 +25,7 @@
25
25
 
26
26
  2026-05-09 skill UX: bare `$throughline` は diagnostics / dry-run / preflight を AI に順番実行させる surface ではなく、`throughline trim --execute --host codex --all` を直接走らせる scripted current-thread refresh としていた。目的は rollback と、Throughline DB の L2 最新 20 full bodies + older L1 summaries + L3 references-only memory injection のみだった。2026-05-10 以降は `--json` の full plan / memory preview が tool output として context を再膨張させるため、通常 path では付けないようにした。
27
27
 
28
- 2026-05-10 rollback UX correction: 追加 live 実験で、current-thread rollback / inject 後に token_count が一時的に下がっても同一 thread で戻る挙動を確認した。これを受け、Codex automatic current-thread refresh は無効化し、`UserPromptSubmit` / `PostToolUse` / `Stop` hooks は capture / monitor state write のみ行う。bare `$throughline` は `throughline codex-handoff-start --execute` による app-server 新スレッド handoff とし、明示 `trim --execute --host codex` は診断用 current-thread path として残す。
28
+ 2026-05-10 rollback UX correction: 追加 live 実験で、current-thread rollback / inject 後に token_count が一時的に下がっても同一 thread で戻る挙動を確認した。これを受け、Codex automatic current-thread refresh は無効化し、`UserPromptSubmit` / `PostToolUse` / `Stop` hooks は capture / monitor state write のみ行う。bare `$throughline` は `throughline codex-handoff-start --execute --open-host <current-codex-surface>` による app-server 新スレッド handoffとし、surfaceはCodex UI contextから明示する。明示 `trim --execute --host codex` は診断用 current-thread path として残す。
29
29
 
30
30
  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 には採用しない。
31
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "throughline",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
4
4
  "type": "module",
5
5
  "description": "Claude Code hooks plugin for structured context compression (/clear-safe persistent memory)",
6
6
  "keywords": [
@@ -79,8 +79,8 @@ function parseArgs(args) {
79
79
  out.execute = true;
80
80
  } else if (arg === '--open-host') {
81
81
  const value = args[++i];
82
- if (!['auto', 'vscode', 'cli', 'none'].includes(value)) {
83
- throw new Error('--open-host must be auto, vscode, cli, or none');
82
+ if (!['auto', 'desktop', 'vscode', 'cli', 'none'].includes(value)) {
83
+ throw new Error('--open-host must be auto, desktop, vscode, cli, or none');
84
84
  }
85
85
  out.openHost = value;
86
86
  } else if (arg === '--codex-app-server-bin') {
@@ -190,6 +190,8 @@ function buildGuidance({ sessionId, parsed, handoffSmoke, handoffPrompt }) {
190
190
  ]),
191
191
  };
192
192
  const ready = handoffSmoke.status === 'ready';
193
+ const requestedOpenHost = parsed.openHost;
194
+ const resolvedOpenHost = resolveOpenHost(requestedOpenHost);
193
195
  return {
194
196
  status: ready ? 'ready' : 'not-ready',
195
197
  reason: ready ? 'fresh_thread_handoff_start_ready' : 'handoff_smoke_not_ready',
@@ -198,6 +200,8 @@ function buildGuidance({ sessionId, parsed, handoffSmoke, handoffPrompt }) {
198
200
  startThreadManually: !parsed.execute,
199
201
  execute: parsed.execute,
200
202
  openHost: parsed.openHost,
203
+ requestedOpenHost,
204
+ resolvedOpenHost,
201
205
  handoffSmoke,
202
206
  modelPromptChars: modelPrompt.length,
203
207
  estimatedModelPromptTokens: estimateTokens(modelPrompt),
@@ -234,7 +238,8 @@ function renderTextResult(result) {
234
238
  lines.push(` session: ${result.sessionId}`);
235
239
  lines.push(` mutates thread: ${result.mutatesCurrentThread ? 'yes' : 'no'}`);
236
240
  lines.push(` execute: ${result.execute ? 'yes' : 'no'}`);
237
- lines.push(` open host: ${result.openHost}`);
241
+ lines.push(` open requested: ${result.requestedOpenHost ?? result.openHost}`);
242
+ lines.push(` open resolved: ${result.resolvedOpenHost ?? result.open?.host ?? result.openHost}`);
238
243
  lines.push(` handoff smoke: ${result.handoffSmoke.status}`);
239
244
  lines.push(` prompt chars: ${result.handoffSmoke.promptChars}/${result.handoffSmoke.maxPromptChars}`);
240
245
  lines.push(` model prompt: ${result.modelPromptChars}`);
@@ -247,6 +252,7 @@ function renderTextResult(result) {
247
252
  }
248
253
  if (result.open) {
249
254
  lines.push(` open status: ${result.open.status}`);
255
+ lines.push(` desktop url: ${result.open.desktopUrl}`);
250
256
  lines.push(` vscode url: ${result.open.vscodeUrl}`);
251
257
  lines.push(` resume command: ${result.open.resumeCommand}`);
252
258
  }
@@ -268,21 +274,34 @@ function renderTextResult(result) {
268
274
  return lines.join('\n');
269
275
  }
270
276
 
271
- function resolveOpenHost(host) {
277
+ function isCodexDesktopEnvironment(env) {
278
+ const originator = env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE?.trim()
279
+ .toLowerCase()
280
+ .replace(/[_-]+/g, ' ');
281
+ return originator === 'codex desktop' || env.__CFBundleIdentifier === 'com.openai.codex';
282
+ }
283
+
284
+ function resolveOpenHost(host, env = process.env) {
272
285
  if (host !== 'auto') return host;
286
+ if (isCodexDesktopEnvironment(env)) return 'desktop';
273
287
  if (
274
- process.env.VSCODE_IPC_HOOK_CLI ||
275
- process.env.VSCODE_IPC_HOOK ||
276
- process.env.TERM_PROGRAM === 'vscode' ||
277
- process.env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === 'codex_vscode'
288
+ env.VSCODE_IPC_HOOK_CLI ||
289
+ env.VSCODE_IPC_HOOK ||
290
+ env.TERM_PROGRAM === 'vscode' ||
291
+ env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === 'codex_vscode'
278
292
  ) {
279
293
  return 'vscode';
280
294
  }
281
295
  return 'cli';
282
296
  }
283
297
 
298
+ function buildCodexDesktopUrl(threadId) {
299
+ return `codex://threads/${encodeURIComponent(threadId)}`;
300
+ }
301
+
284
302
  function openStartedCodexThread({ threadId, host, cwd }) {
285
303
  const resolvedHost = resolveOpenHost(host);
304
+ const desktopUrl = buildCodexDesktopUrl(threadId);
286
305
  const vscodeUrl = `vscode://openai.chatgpt/local/${encodeURIComponent(threadId)}`;
287
306
  const resumeCommand = `codex resume ${threadId} --no-alt-screen`;
288
307
  if (resolvedHost === 'none') {
@@ -290,23 +309,30 @@ function openStartedCodexThread({ threadId, host, cwd }) {
290
309
  status: 'skipped',
291
310
  reason: 'open_host_none',
292
311
  host: resolvedHost,
312
+ requestedHost: host,
313
+ resolvedHost,
314
+ desktopUrl,
293
315
  vscodeUrl,
294
316
  resumeCommand,
295
317
  };
296
318
  }
297
319
 
298
- if (resolvedHost === 'vscode') {
320
+ if (resolvedHost === 'desktop' || resolvedHost === 'vscode') {
321
+ const url = resolvedHost === 'desktop' ? desktopUrl : vscodeUrl;
299
322
  const result =
300
323
  process.platform === 'darwin'
301
- ? spawnSync('open', [vscodeUrl], { encoding: 'utf8' })
324
+ ? spawnSync('open', [url], { encoding: 'utf8' })
302
325
  : process.platform === 'win32'
303
- ? spawnSync('cmd.exe', ['/c', 'start', '', vscodeUrl], { encoding: 'utf8' })
304
- : spawnSync('xdg-open', [vscodeUrl], { encoding: 'utf8' });
326
+ ? spawnSync('cmd.exe', ['/c', 'start', '', url], { encoding: 'utf8' })
327
+ : spawnSync('xdg-open', [url], { encoding: 'utf8' });
305
328
  if (result.status !== 0) {
306
329
  return {
307
330
  status: 'failed',
308
- reason: 'vscode_deep_link_open_failed',
331
+ reason: `${resolvedHost}_deep_link_open_failed`,
309
332
  host: resolvedHost,
333
+ requestedHost: host,
334
+ resolvedHost,
335
+ desktopUrl,
310
336
  vscodeUrl,
311
337
  resumeCommand,
312
338
  error: (result.stderr || result.stdout || '').trim(),
@@ -314,8 +340,11 @@ function openStartedCodexThread({ threadId, host, cwd }) {
314
340
  }
315
341
  return {
316
342
  status: 'opened',
317
- reason: 'vscode_deep_link_opened',
343
+ reason: `${resolvedHost}_deep_link_opened`,
318
344
  host: resolvedHost,
345
+ requestedHost: host,
346
+ resolvedHost,
347
+ desktopUrl,
319
348
  vscodeUrl,
320
349
  resumeCommand,
321
350
  };
@@ -337,6 +366,9 @@ end tell
337
366
  status: 'failed',
338
367
  reason: 'terminal_open_failed',
339
368
  host: resolvedHost,
369
+ requestedHost: host,
370
+ resolvedHost,
371
+ desktopUrl,
340
372
  vscodeUrl,
341
373
  resumeCommand,
342
374
  error: (result.stderr || result.stdout || '').trim(),
@@ -346,6 +378,9 @@ end tell
346
378
  status: 'opened',
347
379
  reason: 'terminal_resume_opened',
348
380
  host: resolvedHost,
381
+ requestedHost: host,
382
+ resolvedHost,
383
+ desktopUrl,
349
384
  vscodeUrl,
350
385
  resumeCommand,
351
386
  };
@@ -354,6 +389,9 @@ end tell
354
389
  status: 'manual',
355
390
  reason: 'cli_auto_open_unsupported_on_platform',
356
391
  host: resolvedHost,
392
+ requestedHost: host,
393
+ resolvedHost,
394
+ desktopUrl,
357
395
  vscodeUrl,
358
396
  resumeCommand,
359
397
  };
@@ -363,6 +401,9 @@ end tell
363
401
  status: 'failed',
364
402
  reason: 'unsupported_open_host',
365
403
  host: resolvedHost,
404
+ requestedHost: host,
405
+ resolvedHost,
406
+ desktopUrl,
366
407
  vscodeUrl,
367
408
  resumeCommand,
368
409
  };
@@ -460,6 +501,9 @@ export async function run(args) {
460
501
  }
461
502
 
462
503
  export const _internal = {
504
+ buildCodexDesktopUrl,
505
+ isCodexDesktopEnvironment,
463
506
  parseArgs,
464
507
  renderTextResult,
508
+ resolveOpenHost,
465
509
  };
@@ -5,9 +5,58 @@ import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'nod
5
5
  import { tmpdir } from 'node:os';
6
6
  import { dirname, join } from 'node:path';
7
7
  import { fileURLToPath } from 'node:url';
8
+ import { _internal } from './codex-handoff-start.mjs';
8
9
 
9
10
  const REPO_ROOT = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
10
11
 
12
+ test('codex-handoff-start recognizes desktop as an explicit open host', () => {
13
+ assert.equal(_internal.parseArgs(['--open-host', 'desktop']).openHost, 'desktop');
14
+ });
15
+
16
+ test('codex-handoff-start auto-selects Codex Desktop before inherited VS Code signals', () => {
17
+ assert.equal(
18
+ _internal.resolveOpenHost('auto', {
19
+ CODEX_INTERNAL_ORIGINATOR_OVERRIDE: 'Codex Desktop',
20
+ VSCODE_IPC_HOOK_CLI: '/tmp/stale-vscode.sock',
21
+ }),
22
+ 'desktop',
23
+ );
24
+ assert.equal(
25
+ _internal.resolveOpenHost('auto', { __CFBundleIdentifier: 'com.openai.codex' }),
26
+ 'desktop',
27
+ );
28
+ });
29
+
30
+ test('codex-handoff-start does not mistake a stale VS Code PTY for Desktop without a Desktop signal', () => {
31
+ assert.equal(
32
+ _internal.resolveOpenHost('auto', {
33
+ VSCODE_IPC_HOOK_CLI: '/tmp/stale-vscode.sock',
34
+ }),
35
+ 'vscode',
36
+ );
37
+ assert.equal(
38
+ _internal.resolveOpenHost('desktop', {
39
+ VSCODE_IPC_HOOK_CLI: '/tmp/stale-vscode.sock',
40
+ }),
41
+ 'desktop',
42
+ );
43
+ });
44
+
45
+ test('codex-handoff-start keeps VS Code and CLI auto-open behavior', () => {
46
+ assert.equal(
47
+ _internal.resolveOpenHost('auto', { CODEX_INTERNAL_ORIGINATOR_OVERRIDE: 'codex_vscode' }),
48
+ 'vscode',
49
+ );
50
+ assert.equal(_internal.resolveOpenHost('auto', {}), 'cli');
51
+ });
52
+
53
+ test('codex-handoff-start builds the Codex Desktop local thread deep link', () => {
54
+ assert.equal(
55
+ _internal.buildCodexDesktopUrl('019f78e8-77ec-7d73-aee4-fb8e3869200a'),
56
+ 'codex://threads/019f78e8-77ec-7d73-aee4-fb8e3869200a',
57
+ );
58
+ });
59
+
11
60
  function makeTempHome() {
12
61
  return mkdtempSync(join(tmpdir(), 'tl-codex-handoff-start-home-'));
13
62
  }
@@ -115,6 +164,9 @@ test('codex-handoff-start prints guided ready JSON for latest Codex session', as
115
164
  assert.equal(payload.sessionId, 'codex:thread-handoff-start');
116
165
  assert.equal(payload.mutatesCurrentThread, false);
117
166
  assert.equal(payload.startThreadManually, true);
167
+ assert.equal(payload.openHost, 'auto');
168
+ assert.equal(payload.requestedOpenHost, 'auto');
169
+ assert.ok(['desktop', 'vscode', 'cli'].includes(payload.resolvedOpenHost));
118
170
  assert.equal(payload.memoStdin, false);
119
171
  assert.equal(payload.memoReplayNote, null);
120
172
  assert.equal(payload.handoffSmoke.status, 'ready');
@@ -144,6 +196,8 @@ test('codex-handoff-start can print the exact fresh-thread prompt', async () =>
144
196
  assert.equal(result.status, 0, result.stderr);
145
197
  assert.match(result.stdout, /throughline codex handoff start/);
146
198
  assert.match(result.stdout, /status:\s+ready/);
199
+ assert.match(result.stdout, /open requested:\s+auto/);
200
+ assert.match(result.stdout, /open resolved:\s+(desktop|vscode|cli)/);
147
201
  assert.match(result.stdout, /commands:/);
148
202
  assert.match(result.stdout, /## Throughline: New Codex Thread Handoff/);
149
203
  assert.match(result.stdout, /latest handoff start body/);
@@ -250,12 +304,22 @@ test('codex-handoff-start execute creates a new app-server thread and can skip o
250
304
  assert.equal(payload.status, 'started');
251
305
  assert.equal(payload.reason, 'new_thread_handoff_started');
252
306
  assert.equal(payload.execute, true);
307
+ assert.equal(payload.openHost, 'none');
308
+ assert.equal(payload.requestedOpenHost, 'none');
309
+ assert.equal(payload.resolvedOpenHost, 'none');
253
310
  assert.equal(payload.startThreadManually, false);
254
311
  assert.equal(payload.newThread.threadId, '019e2000-0000-7000-8000-000000000001');
255
312
  assert.equal(payload.newThread.delivery, 'developer-item');
256
313
  assert.equal(payload.newThread.injectSent, true);
257
314
  assert.equal(payload.newThread.turnStatus, 'not-started');
258
315
  assert.equal(payload.open.status, 'skipped');
316
+ assert.equal(payload.open.host, 'none');
317
+ assert.equal(payload.open.requestedHost, 'none');
318
+ assert.equal(payload.open.resolvedHost, 'none');
319
+ assert.equal(
320
+ payload.open.desktopUrl,
321
+ 'codex://threads/019e2000-0000-7000-8000-000000000001',
322
+ );
259
323
  assert.equal(payload.open.vscodeUrl, 'vscode://openai.chatgpt/local/019e2000-0000-7000-8000-000000000001');
260
324
  assert.match(payload.open.resumeCommand, /codex resume 019e2000-0000-7000-8000-000000000001/);
261
325
 
@@ -49,7 +49,7 @@ test('CLI help exposes guided Codex handoff and guarded execute semantics', () =
49
49
  }
50
50
  assert.match(result.stdout, /Fresh-thread Codex handoff start plan/);
51
51
  assert.match(result.stdout, /Use --execute to/);
52
- assert.match(result.stdout, /--open-host auto\|vscode\|cli\|none/);
52
+ assert.match(result.stdout, /--open-host auto\|desktop\|vscode\|cli\|none/);
53
53
  assert.match(result.stdout, /throughline trim --execute/);
54
54
  assert.match(result.stdout, /injectable DB memory/);
55
55
  assert.match(result.stdout, /matching/);
@@ -181,6 +181,10 @@ test('global install copies Throughline Codex skill to ~/.codex/skills/', async
181
181
  assert.match(skillBody, /name: throughline/);
182
182
  assert.match(skillBody, /Bare "\$throughline"/);
183
183
  assert.match(skillBody, /throughline codex-handoff-start --execute/);
184
+ assert.match(skillBody, /--open-host desktop/);
185
+ assert.match(skillBody, /--open-host vscode/);
186
+ assert.match(skillBody, /--open-host cli/);
187
+ assert.match(skillBody, /not from[\s\S]*persistent PTY/);
184
188
  assert.match(skillBody, /do not run doctor \/ dry-run \/ preflight first/);
185
189
  assert.match(metadataBody, /start a new Codex thread with Throughline handoff memory/);
186
190
  assert.doesNotMatch(metadataBody, /scripted current-thread rollback/);
@@ -0,0 +1,95 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { spawn, spawnSync } from 'node:child_process';
4
+ import { mkdtempSync, rmSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import { fileURLToPath, pathToFileURL } from 'node:url';
8
+
9
+ const DB_MODULE_URL = pathToFileURL(fileURLToPath(new URL('./db.mjs', import.meta.url))).href;
10
+
11
+ function waitForLine(stream, expected) {
12
+ return new Promise((resolve, reject) => {
13
+ let output = '';
14
+ const onData = (chunk) => {
15
+ output += chunk;
16
+ if (output.includes(expected)) {
17
+ stream.off('data', onData);
18
+ resolve();
19
+ }
20
+ };
21
+ stream.on('data', onData);
22
+ stream.once('error', reject);
23
+ });
24
+ }
25
+
26
+ function waitForExit(child) {
27
+ return new Promise((resolve, reject) => {
28
+ let stderr = '';
29
+ child.stderr.on('data', (chunk) => { stderr += chunk; });
30
+ child.once('error', reject);
31
+ child.once('close', (code) => resolve({ code, stderr }));
32
+ });
33
+ }
34
+
35
+ test('getDb configures a bounded SQLite busy timeout for concurrent hook processes', () => {
36
+ const home = mkdtempSync(join(tmpdir(), 'throughline-db-timeout-'));
37
+ try {
38
+ const result = spawnSync(process.execPath, ['--input-type=module', '-e', `
39
+ import { getDb } from ${JSON.stringify(DB_MODULE_URL)};
40
+ const db = getDb();
41
+ process.stdout.write(String(db.prepare('PRAGMA busy_timeout').get().timeout));
42
+ db.close();
43
+ `], {
44
+ env: { ...process.env, HOME: home, USERPROFILE: home },
45
+ encoding: 'utf8',
46
+ });
47
+ assert.equal(result.status, 0, result.stderr);
48
+ assert.equal(result.stdout, '5000');
49
+ } finally {
50
+ rmSync(home, { recursive: true, force: true });
51
+ }
52
+ });
53
+
54
+ test('getDb reuses an existing WAL database while another process owns the writer lock', async () => {
55
+ const home = mkdtempSync(join(tmpdir(), 'throughline-db-contention-'));
56
+ const env = { ...process.env, HOME: home, USERPROFILE: home };
57
+ const initialize = spawn(process.execPath, ['--input-type=module', '-e', `
58
+ import { getDb } from ${JSON.stringify(DB_MODULE_URL)};
59
+ getDb().close();
60
+ `], { env, stdio: ['ignore', 'ignore', 'pipe'] });
61
+ assert.deepEqual(await waitForExit(initialize), { code: 0, stderr: '' });
62
+
63
+ const holder = spawn(process.execPath, ['--input-type=module', '-e', `
64
+ import { DatabaseSync } from 'node:sqlite';
65
+ import { join } from 'node:path';
66
+ const db = new DatabaseSync(join(process.env.HOME, '.throughline', 'throughline.db'));
67
+ db.exec('CREATE TABLE IF NOT EXISTS contention_probe (value TEXT)');
68
+ db.exec("BEGIN IMMEDIATE; INSERT INTO contention_probe VALUES ('held')");
69
+ process.stdout.write('locked\\n');
70
+ setTimeout(() => {
71
+ db.exec('COMMIT');
72
+ db.close();
73
+ }, 500);
74
+ `], { env, stdio: ['ignore', 'pipe', 'pipe'] });
75
+ await waitForLine(holder.stdout, 'locked\n');
76
+
77
+ const contender = spawn(process.execPath, ['--input-type=module', '-e', `
78
+ import { getDb } from ${JSON.stringify(DB_MODULE_URL)};
79
+ const db = getDb();
80
+ const mode = db.prepare('PRAGMA journal_mode').get().journal_mode;
81
+ db.close();
82
+ if (String(mode).toLowerCase() !== 'wal') process.exit(2);
83
+ `], { env, stdio: ['ignore', 'ignore', 'pipe'] });
84
+
85
+ const [holderResult, contenderResult] = await Promise.all([
86
+ waitForExit(holder),
87
+ waitForExit(contender),
88
+ ]);
89
+ try {
90
+ assert.deepEqual(holderResult, { code: 0, stderr: '' });
91
+ assert.deepEqual(contenderResult, { code: 0, stderr: '' });
92
+ } finally {
93
+ rmSync(home, { recursive: true, force: true });
94
+ }
95
+ });
package/src/db.mjs CHANGED
@@ -9,6 +9,7 @@ import { join } from 'path';
9
9
 
10
10
  const DB_DIR = join(homedir(), '.throughline');
11
11
  const DB_PATH = join(DB_DIR, 'throughline.db');
12
+ const DB_BUSY_TIMEOUT_MS = 5_000;
12
13
  export const CURRENT_VERSION = 9;
13
14
 
14
15
  let _db = null;
@@ -248,11 +249,19 @@ export function getDb() {
248
249
 
249
250
  mkdirSync(DB_DIR, { recursive: true });
250
251
 
251
- _db = new DatabaseSync(DB_PATH);
252
- _db.exec('PRAGMA journal_mode = WAL');
253
- _db.exec('PRAGMA foreign_keys = ON');
254
-
255
- initSchema(_db);
256
-
257
- return _db;
252
+ const db = new DatabaseSync(DB_PATH);
253
+ try {
254
+ db.exec(`PRAGMA busy_timeout = ${DB_BUSY_TIMEOUT_MS}`);
255
+ const journalMode = db.prepare('PRAGMA journal_mode').get().journal_mode;
256
+ if (String(journalMode).toLowerCase() !== 'wal') {
257
+ db.exec('PRAGMA journal_mode = WAL');
258
+ }
259
+ db.exec('PRAGMA foreign_keys = ON');
260
+ initSchema(db);
261
+ _db = db;
262
+ return _db;
263
+ } catch (error) {
264
+ db.close();
265
+ throw error;
266
+ }
258
267
  }