Repository navigation
v0.30.1
[0.30.1] - 2026-09-03
Highlight: v0.30.0 で新設したチャット出力面を、実機で見つかった 4 件の欠陥について修正した。返答でない行が Assistant の吹き出しとして全文描画される問題(実測で直近 50 行のうち antigravity 41 行 / codex 43 行)をツール承認チップへ畳み、ターンが履歴から永久に失われる経路を 2 つ(転写リーダーが最新 1 件しか書かない・起動バナー除外が 2000 字未満の返答を飲み込む)塞ぎ、確定行が届かないターンでも生成中の本文が消えないようにした。
Added
- feat(ui): 生成中バブルを確定行が届くまで保持し、progress 送出を観測可能にする (#2248): 生成中の本文を流す仕組み(#2199)は、
sessionStatusがrunningでなくなった瞬間にuseChatTurnProgressが本文を捨てる設計で、「running でなくなる」と「確定行が届く」の間に本文の置き場が無かった。確定行が書かれないターン(#2246 の Stop 起点書き込み、#2247 の取りこぼし、hooks 無しの環境)ではその間隔が無限大になり、一度画面に出て読者が読んでいた文章が二度と見えなくなっていたため、最後の frame を確定待ちとして保持するようにした(ChatTurnProgressView.settling)。保持中はスピナーもchatSurface.generatingも出さず(#2238 の「Responding… が消えない」を再発させないため、保持は「生成中」ではなく「確定待ち」でisGeneratingの値には触らない)、本文の class と位置は生成中とまったく同じまま(#2233 の規律)「まだ履歴に確定していません」の 1 行だけを差し替える。解除は (a) 同じturnKeyの確定行が届く、(b) 同じインスタンスで新しいターンが始まる(sessionStatusが再び running/transcript に新しい行が積まれる)、(c) 猶予 10 分の経過、のいずれか早いもの。保持はインスタンス単位で、split の切替や別インスタンスへの再指定では表示しない。WS 切断の扱いは live と held で分けた: live な本文は従来どおり切断で消す(#2199 の規則 2 は「live を騙る古い段落」を防ぐためのもの)が、held な本文は「まだ確定していない」と自ら宣言しているうえ、replay の無い push で切断こそ最後の 1 部が失われる瞬間なので保持し続ける。あわせて送出を観測可能にした: 全てlogger.debugだった送出結果をchat-turn-progress-published(turnKey/version/ 本文長)・chat-turn-progress-no-subscribers・chat-turn-progress-failedとしてlogger.infoに上げ、(outcome, turnKey)を同一性にターンごと 1 行へ畳んだ(poller の毎 tick が 1 行になると info が使い物にならなくなる)。クライアント側は保持状態をdata-settling="true"で公開する。publishChatTurnProgress()は「安い前半(worktree 行とhasRoomSubscribers)を caller が await し、高い後半(4 MiB の transcript 読みと broadcast)は従来どおり detach」に分割した。voidが守っていたのは後半のコストであって、送出結果のログはそれを生んだ tick の隣に残らなければ読み返せないため
Fixed
- fix(history): 転写リーダーが最新ターンしか書かず、poller の完了取りこぼしでターンが永久に欠落する問題を修正 (#2246): claude / codex / antigravity の転写リーダーは
built.turns.at(-1)の 1 ターンだけを書き、その起動条件も「scraper が完了と判定した poll」だけだったため、完了判定を 1 回取りこぼすと(#2247 の起動バナー誤判定など)次の完了時には「最新ターン」が次のターンに移っており、取りこぼしたターンを誰も書かないまま永久に失われていた(2026-09-02 実測: 転写 JSONL には全文が残っているのにチャット面に出ない返答が 1 件)。リーダーを「窓内のうちまだchat_messagesに無いターンを古い順に全部書く」形に変え、#2121 の二重化防止の根拠(過去ターンには scraper の行がある)はアンカーで保った — 窓内でこのリーダーが既に書いた最新ターンより後だけを対象にし、アンカーが無い窓(scraper 時代のセッション、/clear直後)は従来どおり最新 1 件だけ書く。あわせて hooks のstopイベントを第 2 の起動点にし(applyAgentStopEventからcaptureTranscriptTurnOnStop経由で poller と同じゲートを通す。poller 側の起動は hooks 無しセッションの fail-open として残す)、stop 受信時に最終 assistant 記録がまだ flush されていない場合に 1 回だけ 500 ms 後に読み直す(再読しても意味が無い「転写ファイルが無い」ケースでは待たない)。recordUserTurnにはadoptionFromMsを足し、遅れて読まれたターンの/send行を #2196 の対称 2 分窓の外まで遡って adopt できるようにした(下限は直前ターンの開始時刻=そのターン用の送信行が存在しうる最古の時刻で、窓は広がるだけで狭まらない) - fix(ui): チャット面で prompt 行と ANSI 付き scrape 行が Assistant の吹き出しとして全文描画される (#2245):
ChatMessageBubbleがmessage.roleしか見ておらずmessageType/promptDataを一切参照しなかったため、poller のダイアログ行(contentはペイン末尾 200 行 / 5000 字の丸写し)・Auto-Yes が同一ダイアログに対して 2 本目として挿入する行・hooks のPermissionRequest allow監査行が、すべて Assistant の返答として全文描かれていた(develop966b40f8の実機で直近 50 行のうち antigravity 41 行 / codex 43 行)。生産者(poller / Auto-Yes / hooks)には触れず表示層で吸収した:messageType === 'prompt'の行は本文を描かずツール承認チップとして描き、連続する行は既定で閉じた 1 グループに畳む(開けば 1 件ずつラベルが読める)。questionとapprovalTargetが同一で 5 秒以内の行は 1 件に統合し、掃き出しの推測であるterminalより実際の決定であるauto/humanを優先して表示する(統合規則は純関数として unit で pin)。あわせて非 Markdown 本文を描画とコピーの両方でstripAnsiに通し(codex / antigravity に cleaner が無く[32m●[39mが生で見えていた)、チップ行をuseHistorySearchの対象から外し(本文を描かない行にはハイライト先のdata-message-idが無い)、役割ヘッダはチップを跨いだ直前の非チップ行で判定してチップの前後で「Assistant」ヘッダが増減しないようにした。promptDataがnull/UNCLASSIFIED_PROMPT_TYPEの記録 / structured prompt の記録でも throw しない(1 行の不整合が会話面全体を白画面にしないため)。History(HistoryPane/ConversationPairCard)は #2232 の凍結どおり不変 - fix(polling): claude の起動バナー除外が 2000 字未満の返答を飲み込み、ターンが保存されなくなる問題を修正 (#2247):
extractResponseの Claude 起動バナー除外(787f7401以来)は、抽出結果が 2000 字未満で 4 つの anchor のいずれかに一致すると無条件に「未完了」を返していたが、その anchor のうち 2 つは返答も同じくらい普通に含む形だった(hasVersionInfoの素のv\d+\.\d+は返答が挙げるあらゆるバージョン文字列に、hasBannerArtの│は Claude Code が markdown 表を描く罫線に一致する)。実際に 2026-09-02 の実機で「GitHub Release v0.30.0 を公開しました …」(148 字)の完了済みターンが 2 秒ごとのポーリングで 11 分間ずっとisComplete: falseと判定され続け、しかもこの分岐は何もログを出さないためresponse-pollerは無音のまま、そのターンは保存されずに失われた。#1897 が copilot に与えたのと同じ形へ揃え、anchor は「そのペインにまだ 1 ターンも無い」ときだけ証拠として読むようにした(Claude は全プロンプトを❯ <text>として転写にエコーし、起動画面にはそれが 1 つも無い。判定には抽出と同じfindRecentUserPromptIndexを使う — footer の composer が描く dim なゴースト候補は stripAnsi 後に実エコーと同形なので、contentEndで切る境界が不可欠である、#1879)。あわせて anchor 自体もバナー固有へ絞り、除外したときは info ログを 1 回出して無音を解消した。回帰防止に Claude Code v2.1.258 の実ペイン 5 フレーム(200x1000、ANSI 付き生キャプチャ)をtests/fixtures/claude-live-2247に追加した