Skip to content

v0.48.0

Choose a tag to compare

@Kewton Kewton released this 25 Oct 09:08
· 2 commits to main since this release
e77b9ce

[0.48.0] - 2026-10-25

Highlight: Auto-Yes の答え方を見直すリリースです。答え方のモード(おまかせ recommended/慎重 careful)を画面・CLI・API から選べるようになり、おまかせは質問に推奨と書かれた選択肢でだけ答え、慎重は質問を人に返します(#3863)。許可の確認は「今回だけ許可」の選択肢だけを選び(#3822)、ツールの根拠が無い (y/n)・読めない選択画面・ブラウザからの自動回答には答えなくなりました(#3822 / #3821)。送信が結果不明や途中で失敗した回答を次の tick で打ち直さず(#3880 / #3839)、確認画面の種類と承認対象の完全性を読み取りの時点で決めて、決められない画面は答えない側に倒します(#3881)。更新前から開いていたブラウザのタブは再読み込みしてください。

Added

  • feat(orchestrate): 共有状態の書き手・読む経路・割込みを計画と契約に書かせて起動前に検査し、重い検証と precheck を同じ資源制御に入れて待ちと実行を分けて記録する (#3884): 前半(計画・契約の検査): /orchestrate の依存判定がファイル単位だったため、同じ共有状態(poller の tick と送信・prompt-push の猶予・current-output の公開・wait の人待ち)を変える PR が「箇所が違うので弱依存」として同じ波で並び、取り込みの衝突と独立レビューでの await 順序・別経路の漏れが続いていた(2026-10-23〜24 の run)。plan と同じ run のフォルダに plan-<issues>.state.yaml(波・work item・共有状態ごとの書き手・公開値・読む経路・不変条件)を置けるようにし、新しい scripts/orchestrate/plan-check.mjs check --plan <file> が送信の前に、同じ状態の書き手が同じ波に 2 本(shared-writer)・書き手の契約に経路表が無い(routes-missing)・状態の読む経路に結び付いたテストが無い(routes-reader)・重いゲート(unit / integration / build / e2e)を持つ item が 1 つの波に上限(既定 1、heavyGateLimit)を超える(heavy-gates)を 1 行ずつ出して exit 1 で止める(状態ファイルが無い run は skipped で exit 0、今までどおり)。contract.mjs の設定に省略可能な routes(行=無効化・手動回答・区間変更・世代変更・欠落・旧形式、列=poller / hook / prompt-response / current-output / WS / push / wait / 観測 / PC / スマホ/タイル、extraColumns / extraRows で追加)を足し、すべてのセルをテストか理由つきの除外で埋めないと生成が止まる(テストが 1 つも無い表も止まる)。書いたときだけ goal に「経路表と割込み表」の節が入り、省略時の goal は変わらない(既存の設定 67 本で生成結果が 1 バイトも変わらないことを確認)。plan-check.mjs routes --item <id> は item ごとの「割込み順序と読者の一覧」を出し、事前レビュー(docs/orchestrate/trials.md)と consistency-review.mjs --routes <file> の依頼文に入る(無いときは「渡されていない」)。再レビューの雛形は、前回の指摘の判定に加えて修正した共通規則の全読者を一覧と呼び出しの連鎖で再確認させる。再レビューの上限と試行の期限は変えない。手順は docs/orchestrate/plan-check.md。 後半(重い検証の資源制御と待ちの記録): 検証の実行枠(MAX_CONCURRENT_VERIFICATIONS = 2)は run が最初に取って最後まで持っていたため、cpu.heavy を持つ run と待つ run の 2 本で枠が埋まり、3 本目の run は 5 秒の lint も始められなかった(H-11)。枠を「同時に実行するゲートの数」の上限に改め、ゲートが mutex を待つ間は枠を返し(ロックが空いていれば返さない。acquireMachineLock の onWait)、ロックを取ったら始まるのを待っている run より先に枠を取り直す。同時に実行するゲートは今までどおり 2 本まで、重いゲートは cpu.heavy で 1 本ずつなので、裁定を受ける負荷は増えない。mutex を持つゲートと枠を待ったゲートの log_tail の最後の行に [timing] queue=<秒>s mutex=<秒>s run=<秒>s を書き(run は duration_ms、[mutex] waited= は 1 行目のまま、どちらも待たなかったゲートの log は変えない)、verify --json・verify show に出る。ログ本文を持たない verify history --json(GET /api/verification/runs)には、この行だけを数に直した timing({ queueSec, mutexSec, runSec })をゲートごとに載せる(行の無い旧い run は欄を省く)。mutex を待つ間に枠を返した run を取消すと、枠を取り直さずに閉じ(ほかの 2 枠の終了を待たない)、ロックを取った後の枠待ちも取消せてロックを返す。scripts/orchestrate/precheck.mjs は、テストの段が広いとき(明示したテストが 50 ファイル以上、または vitest related と同じく import を間接にたどって変更したファイルに届く unit テストが 50 ファイル以上か変更 20 ファイル以上。型だけの参照(import type・export type・指定子がすべて type の import)は変換で消えるのでたどらない。tests/unit/guards・tests/unit/docs は数えず一律に mutex を付けない)だけ同じ cpu.heavy(~/.commandmate/locks/cpu.heavy.lock、CM_VERIFY_LOCK_ROOT)を同期で取り、5400 秒待ち切ったらその段を走らせず(<段>=held)、ほかに落ちた段が無ければ記録を skip にして exit 3(裁定不能。ok の 0 とも不合格の 1 とも別で、再実行する)で終える(H-10)。run の記録(run-log.mjs)の verify / precheck の行は queueSec・mutexSec・runSec を省略可能な欄として持て(--queue-sec / --mutex-sec / --run-sec。無い行は今までどおり読む)、precheck は mutexSec・runSec を自分で書く。テスト全体と unit-related の分け方・同じ tree での再利用・流用しないもの(別 task の work-evidence / scope / env-clean)・省略しないもの(CI の全体検証と API・DB の統合検証)を docs/orchestrate/ci-merge.md に書いた(G6-4・G6-5)。

  • feat(auto-yes): Auto-Yes に答え方のモード(おまかせ recommended/慎重 careful)を足し、質問に推奨の選択肢で答えるかを画面・CLI・API から選べるようにする (#3863): 既定のおまかせは従来どおり質問にも答えるが、選ぶのは推奨と書かれた選択肢(選択肢のタイトルの末尾の独立した (Recommended)・(推奨)・(推奨) がちょうど 1 つ。無ければ既定=カーソル、無ければ 1 番)に変わった。推奨が 2 つ以上・タイトルと説明を区別できない選択肢がある・選ぶ選択肢が自由記述・複数選択の質問は答えず、新しい理由 recommended-option-unidentified で人に返す(wait の文言・台帳・agent-health・docs)。質問に答えさせたくなければ慎重で ON にする(question-left-to-human、#3861 の動き)。許可の確認は両モードとも「今回だけ許可」の選択肢だけを選ぶ(#3822)。最終確認(Submit / Cancel)と OpenCode / V2 の質問はどちらのモードでも人が答え、copilot の質問は判別できないため慎重でも答えることがある。推奨の認定は reader(共通の block reader・command-code の質問の reader・claude の AskUserQuestion の parser)が選択肢ごとに true / false / 'unknown' を付けて渡す(claude は「次の行の最初の語が選択肢の行に収まったなら折り返しではない」で説明と区別し、区別できなければ unknown。既存の label と deny の照合の対象は変えず、PromptDetectionResult.optionRecommendations として promptData の外に置く)。質問の認定はダイアログの判定から切り離した(CM_AUTOYES_DIALOG_GATE=<tool>=legacy でも慎重は質問に答えない。command-code の質問も慎重なら見送る)。モードは ON にするたびに決まり(worktree × instance)、OFF(期限切れを含む)→ ON は新規で省略は recommended、期限内の ON → ON は省略すると今のモードを保つ。モードの変更は Auto-Yes の状態の置換なので、進行中の tick と旧いモードの判断(autoYesDecision)は残らず、送信の直前にもモードを読み直す。契約の autoYes が上限で、モードはその下。CLI: commandmate auto-yes <id> --enable --answer-mode recommended|careful、commandmate send … --auto-yes --auto-yes-answer-mode …(不正な値・--disable との併用・--auto-yes なしは exit 2)、指定したときと既定以外のときに Answer mode: … を表示。API: POST /api/worktrees/:id/auto-yes の body の answerMode(不正は 400)、応答(POST・?cliToolId の GET・全体の GET の最上位)の answerMode、全体の GET の answerModeByInstance、current-output と配信の autoYesAnswerMode、capture --json / instances --json の autoYesAnswerMode、wait の見送りの行の answerMode= と exit 10 の autoYesSuppression.answerMode。台帳の見送りの区間・送信の試行・checkpoint・確認を閉じる行に送信時点の answerMode を残し(確認を閉じる行には、回答した送信のモード answeredAnswerMode も別に残す。回答後に同じ画面のままモードを変えても回答のモードは動かない。旧ログの欠落は不明)、agent-health の auto-yes-observation にモード別の回答数(回答した送信のモードで数える)・見送りの区間数を足した。commandmate prompt --json の autoYes.contractEvaluation も同じ画面の質問の分類・推奨の読み・実効のモードで読み直し、poller が見送る質問は base-withholds(recommended-option-unidentified、慎重なら question-left-to-human)になる。マーカーだけの行の後にタイトルの続きかもしれない行があるときは、タイトルの終端が分からないので unknown にする。画面: Auto Yes を ON にするダイアログ(PC の分割ペイン・スマホの入力欄・Sessions のタイル)と新規タスクのダイアログの Auto-Yes の欄(時間を選ぶと出る)に「質問は推奨で回答(おまかせ)」「質問は自分で回答(慎重)」の 2 択を足した(既定はおまかせ。どちらも許可の確認には自動で答えると書く)。選んだモードを有効化の POST /auto-yes の answerMode に渡し、既定も省略せず明示して送る(有効中の省略は今のモードを保つので、別タブが慎重で ON にした後のおまかせが効かなくなるのを防ぐ)。ON の間はトグルの横にモード(おまかせ/慎重)を表示する。ワークツリー画面はモードを current-output のポーリング・全体の GET /auto-yes(有効中の定期的な読み直しで別タブの変更も反映)・トグルの応答の 3 経路で引き継ぎ、instance ごとに保つ。Sessions のタイルはトグルの応答と、GET /api/worktrees の各行に足した autoYesAnswerModeByInstance(ON の instance ごとのモード。autoYesByInstance の横に置き、ON が無い行では省く)から表示し、再読込・別タブの変更も一覧の読み直しで反映する(トグルの応答は一覧の取得が実際に進むまで保ち、WebSocket の状態の更新で行がコピーされただけでは古い一覧の値に戻さない)。モードを名乗らない古いサーバーでは表示を出さない(おまかせと決めつけない)。

Changed

  • refactor(detection): detectMultipleChoicePrompt を段ごとの小関数に分け、ESLint の複雑度を 67 から 13 へ下げる (#3894): 走査窓の決定(findScanWindow)・/model オーバーレイの検出・確認フッターの検出・Pass 1 の ❯ 行の有無・Pass 2 の逆走査(collectOptions と行ごとの isMetadataRow / tryCollectOption / scanNonOptionLine)・Layer 3〜5 の検証(passesOptionValidation / passesQuestionLineValidation)を同じファイル内の関数に分けた。判定の条件の順序と短絡評価、正規表現・上限値・コメントは不変で、公開 export も import 元も変わらない。挙動は不変。

  • refactor(auto-yes): auto-yes-poller.ts を 1,880 行から 1,479 行へ分割(振る舞いは不変) (#3893): tick の画面読み取りと記録(readPromptFrame・noteNoPromptOnScreen・tickDecisionRecorder・ledgerCallOf・attemptResultOfThrow・answerBasisOf・captureTopOf)を auto-yes-poller-tick.ts、回答先の待ち(fixAnswerTarget・answerTargetHolds)を auto-yes-poller-answer-target.ts、セッション不在の判定(isSessionMissing)を auto-yes-poller-session-presence.ts、複合キーの照会(getAutoYesPollerCompositeKeys・isAnyPollerActiveForWorktree)を auto-yes-poller-keys.ts へ移した。公開 export は元ファイルが再 export するので import 元は不変。polling/response-checker は auto-yes-manager 経由でポーラーへ戻るため、分割先はそれを import せず、フレームの読み手 detectPromptOnCleanFrame をポーラーから引数で受け取る(新しい import 循環なし)。判定の順序・タイマー・await の位置・ログは変えていない。

  • refactor(session): waiting-episode-binding.ts を観測の計画と束縛の決定の 2 ファイルに分けて 752 行から 346 行へ (#3892): 観測側の型・startWaitingObservation / startWorktreeWaitingObservations・captureFrameTime / captureObservedFrame・表 O-1 / O-2 の planWaitingObservation を waiting-episode-binding-observation.ts へ、resolveEpisodeBinding と内部の generationOf を waiting-episode-binding-resolve.ts へ移した。元ファイルはすべてを再 export し、エピソードを書く側(commitWaitingObservation / observeWaiting / applyTurnOutcomesToEpisode / settleLegacyStop)と resolver・turn sink の登録、hook stop の世代の状態を持ち続ける。分割先は元ファイルを import しない。import 元・振る舞い・ログ名は変わらない。

  • refactor(detection): prompt-detect-multiple-choice.ts から承認対象の範囲を決める関数群を別ファイルへ分ける (#3866): findApprovalContextStart / joinApprovalTarget / APPROVAL_TARGET_MAX_LOOKBACK と、その下請け(TURN_BOUNDARY_PATTERN・RESULT_MARKER_PATTERN・indentOf・panelFrameIndent・isTurnBoundaryLine・isMarkerRowAtFrameAbove)、共有の SEPARATOR_LINE_PATTERN を新しい prompt-detect-multiple-choice-approval-context.ts へ一字も変えずに移した(1,520 → 1,335 行)。元のモジュールは公開名を再 export するので import 元は変えていない。新しいモジュールは元のモジュールを import しない。挙動不変。

  • refactor(session): 待ちエピソードの T テーブル verdictForOutcome を新モジュール waiting-episode-verdict.ts へ移し、case ごとの関数に分けて複雑度を 33 から 15 に下げる (#3864): generation-fenced・restored・stopped・session-ended・decision-opened・decision-answered の各行を小さな関数に分け、T6〜T21 の対応コメントと outcome satisfies never の網羅検査は保った。waiting-episode-binding.ts は verdictForOutcome を同じ名前で再 export し、sameGeneration と Verdict 型を新モジュールから import する。振る舞いは変えていない(新モジュールは型だけを import し、waiting-episode-binding を import しない)。

  • refactor(auto-yes): closeLedgerHookRequest の確認の決着を非公開関数 settleHookConfirmation へ分け、複雑度を 25→19 に (#3849): hook リクエストが確認そのものだったときの auto-yes-confirmation-attempt-settled と auto-yes-confirmation-closed の 2 行を出す処理(delivered による ok / failed の分岐 7 箇所)を同じファイル内の関数へ移した。公開シグネチャ・出す event 名とフィールドの値・emit と state 更新の順序と回数は不変(挙動不変)。

  • refactor(auto-yes): 確認の記録(ledger)の定数・状態の型・1 行の書き出し・状態の小さな補助関数を auto-yes-ledger-records.ts へ分割 (#3848): src/lib/polling/auto-yes-confirmation-ledger.ts を 1,477 行から 1,201 行へ。移したのは定数(AUTO_YES_LEDGER_*・各上限・停止理由の集合)、LedgerState などの型、emit / currentLogLevel、keyOf / callKey / callsOf / endedCallsOf / currentEntry / rememberBounded で、本文・コメントはそのまま。状態の持ち主(ledgerState()・時計・テスト用の差し口)は元のファイルに残し、公開名は元のファイルから再 export するので呼び出し元の import は不変。振る舞いの変更なし。

  • refactor(auto-yes): logIfWithheldForWantOfReceipt の複雑度を 26 から 22 に下げる (#3838): 見送りログの 1 分 1 回の制限(直近の記録の判定・期限切れの掃除・記録)を同じファイル内の非公開関数 claimWithheldLogSlot() へ分けた。公開シグネチャ・戻り値・ログの event 名とフィールド・recordPolicySuppression の呼び出し順序と回数は不変。

Fixed

  • fix(orchestrate): wait-verify.mjs のログ・.exit・検証・完了の合図・run の記録を、実行ごとの execution key に結び付ける (#3883): 同じ Issue の 2 本の PR と各再試行が wait-<issue>.log とその .exit を共有し、並行する child が他方の終了コードを読める状態だったのを、呼び出しごとにログを排他的に取る形に直した(1 回目は従来どおり wait-<issue>.log、2 回目からは wait-<issue>.a<n>.log、新しい省略可能な --item <名前> で wait-<issue>-<名前>.log。既にある --log は exit 2 で拒否し、.exit はこの呼び出しだけが知る印つきで書いて読む)。新しい scripts/orchestrate/execution-key.mjs が <run の Issue 範囲>:<work item>:<task>:a<試行> を作り、verify の記録に item と key を入れる。run-log.mjs は --item / --key を受け取り、latestByStage / headsOf / findLatest / summarize が work item ごとに分けて再開地点を出す(#3883/d1。item の無い既存の行は Issue 自身の行として今までどおり読む)。precheck.mjs / publish-pr.mjs / merge-pr.mjs も --item を受け取ってその work item の行だけを読み書きし(--item 無しは item の無い行だけ)、同じ Issue の別の PR が後から書いた行で publish-pr が「no result」で止まることや、別の PR の unit ゲートで Unit Tests の待ちを飛ばすことを無くした(precheck のログは同じ HEAD の再実行でも上書きせず .a<n>.log、module-reference の写しは work item ごと、run の記録にある別の work item の PR は 2 本目の PR として止めず、merge-pr --item は --close <N> が無ければ Issue を閉じない)。consistency-review.mjs も --item を受け取り、review-<issue>-<item> の名前を .prompt.txt の排他作成で取る。検証の開始時刻はログの Verifying: <wt> (run <id>) の run を verify show で読み(verify history --limit 1 の「worktree の最新の run」をやめた)、完了の合図は reply --since <task の作成時刻> のこの task の返事の中だけで数え、返事がまだ無い・読めないときは画面の裸の IMPL_COMPLETED で補わず(前の task の行が残ったまま途中のターン終了が来るため)、task の作成より後のコミットである今の HEAD を名指す IMPL_COMPLETED head=<sha> だけを画面から数えて、そのときの HEAD に結び付ける。合図が要るかは解決した instance の担当ツール(cliToolId)で決め、antigravity-2 などの追加 instance とその alias も Antigravity として扱う。合図が要る担当(Antigravity・--require-signal)は合図を見るまで検証を始めず(途中の hook_stop で wait --verify を走らせない)、検証が already in progress で始められないときは旧い run の終了を verify show で待ってから始め直して、この呼び出しの run の結果だけを裁定する(記録に waited-for=run <id>。exit 99 で終わらせない)。Auto-Yes を切った後に capture --json が読めないときは off と記録せず読み直し、最後まで読めなければ auto-yes=unknown(--auto-yes-off は exit 1)。consistency-review.mjs は --item を付けて --worktree を省くと、Issue の ../commandmate-issue-<N> ではなく work item の ../commandmate-issue-<N>-<item> を見る。runbook(/orchestrate)は「同じ Issue の複数 PR では 3-1 の送信から 6-2 のマージまで全段に同じ --item を渡す」規則と、tasks.tsv の 6 列目に明示した item=<名前> から各段の --item を作る雛形(docs/orchestrate/run-log.md。列の数では新旧を分けず、6 列目が無い・item= で始まらない行は --item を付けないので、6 列目に再試行のラベルを書いた旧い run も item 無しの記録のまま再開・公開できる。切り替えはこれから送る Issue だけ。6-2 は (Issue, work item) ごとに最新の行だけをマージし、d1→d2→d1 の再試行でも Issue を閉じる --close はその Issue の最後の対象にだけ付ける。item 無しの行は (Issue, worktree) を 1 本の PR とし、旧い run の同じ Issue の別 PR を再試行として捨てず、最後の対象より前には --close - を付ける。item 有りと無しが混ざる Issue は自動では閉じない)を置き、6-4 は work item ごとの控え module-reference-<N>-<item>.md を読む。Antigravity の手動の合図確認(docs/orchestrate/antigravity.md)は、返事の裸の行と head=<sha> の行を分けて後者を今の HEAD と照合し(別の HEAD を名指す返事は not yet)、確かめた HEAD を裁定まで保ち(合図の後に HEAD が動けば再検証)、後半の最後のターン終了も同じ instance(antigravity-2 など)、git も解決済みの work item の worktree で読む。後半(P3・P6・H-8): Claude のワーカーの送信を新しい scripts/orchestrate/launch-worker.mjs send に集め、worktree の .claude/settings.local.json に契約のモデルを opus でも明示して書く(opus は opus[1m]、sonnet は sonnet。以前は opus のとき model を消して ~/.claude/settings.json のグローバル既定を継がせていたので、既定が fable に変わった run で opus 指定のワーカーが fable で動いた。利用者のグローバル設定は読みも書きもせず、git が無視していないファイルには書かない)。セッションは作業を頼まない起動のメッセージ(Auto-Yes 無し)で始め、capture --json の model が契約のモデルと一致してから契約と Auto-Yes を送る。合わない・読めないときは 1 回だけ起動し直し、なお合わなければセッションを止めて exit 40(MISMATCH)/ 41(UNKNOWN)で終わる(契約はそのセッションに送らない)。止めるたびに kill の exit code と一覧の running を確かめ、確かめられなければ何も送らずに exit 42。起動の前にはセッションの有無によらず Auto-Yes を切って状態の保存先から off を読み返し、確かめられなければ exit 43。runbook の 3-1・3-5・3-5b はこの helper を呼び、送信の出力は呼び出しごとに排他的に取る send-<issue>[-<item>][.a<n>].out / .err、5-1 の契約なしの検証は 6 列目の item= を読み execution-key.mjs allocate で取る verify-<issue>[-<item>][.a<n>].json と execution key を記録する(同じ Issue の送信・検証が上書きし合わない)。wait-verify.mjs は実行の入力の manifest(~/.claude/settings.json の model・worktree の settings・package-lock.json の hash・node_modules の所在と直接の依存の版。HOME 全体は辿らない、env-manifest.mjs)を開始・各検証の直前・裁定の後に取ってログと同じ名前の .env.json に execution key と残し、最後の検証の開始から裁定までに変われば裁定しない(exit 3)、作業中の変化は env-changed=<項目> と env=<hash> で記録する(どの呼び出しも wait --verify を使わず、wait の後に manifest を取ってから verify を始める)。precheck も自分の結果に env= を記録し、precheck の再利用・verify ゲートの省略と publish-pr / merge-pr の記録の確認は、これらの env= の manifest を今の worktree と比べ、違えば・manifest が無ければ検証・precheck をやり直す(HEAD が進んだときに lockfile と依存の版を比べないのは、今の HEAD の precheck が今の環境で通っているときだけ。merge-pr は依存だけの差なら先にその precheck を走らせ、合格してから判定し直す)(env= の無い以前の記録は警告を出して従来どおり使う)。5-1 の契約なしの検証も env-manifest.mjs start / end で同じ記録を残す。consistency-review.mjs は ask の前後で対象とリポジトリの全 worktree の node_modules 直下(@scope/ も)のリンクを読み(dependency-links.mjs)、所有する worktree の外を指すリンク(node_modules そのものが外へのリンクになった・向き先が変わった場合も)が増えたら裁定に使わず review=fail・exit 1 で報告し、雛形は依存に書かない・実験は mktemp -d の隔離コピーで行うよう依頼する。

  • fix(agent-health): 回答時の答え方のモードの欠落を unknown として集計する (#3882): Auto-Yes 観測の answeredRecommended / answeredCareful が、answeredAnswerMode の無い closed 行(hot reload で引き継いだ旧 entry・旧ログ)を、後で変わる最後の判断の answerMode で数えていた。欠落は answeredAnswerModeUnknown に数え、同じ confirmation ID の成功 attempt 行がモードを名指ししている場合だけそこから復元する。復元は台帳と同じく結果が戻った順で最初の成功を採り(送信開始時の採番の最小番号ではない)、closed 行の attempts.ok と窓内の一意な成功 attempt 数が一致し、それより前の番号の attempt 行も揃うとき(窓の外・欠けた成功が無いと示せるとき)だけに限る。同じ atMs の成功は確定順が分からないため、最も早い時刻の成功が複数でモードが食い違えば unknown にする。成功時の snapshot がある行(recommended の回答後に careful へ変更)は従来どおり回答時のモードで数え、人の回答・未知の値・両欄なしの行の扱いと自動回答の判断は変えない。

  • fix(detection): 確認画面の種類と承認対象の完全性を読み取りの時点で決め、保存とチャットの分類まで共有する (#3881): Claude の 16 桁の Ready to submit your answers? 画面(tests/fixtures/claude-confirm-live-3791/tab-row-wrapped-16x1000.txt、claude-cli 2.1.310 の実機)は、Auto-Yes の poller が question-confirm-page として人に任せる一方、チャットでは「ツール承認」と表示されていた。汎用 parser の保存した選択肢が Submit(折り返された answers が落ちる)で、質問文も review の ● 一覧より下しか持たず、チャット(chat-tool-approvals)が削られた文字列から種類を決め直していたため。応答 poller の保存(savePromptMessage)で、poller と同じ述語(isAutoYesDeclinedConfirmFrame・isAskUserQuestion・selectAllowOnceOption)を使って画面を question / permission / confirmation / menu / unknown に読み、live の行範囲・承認対象の完全性・証拠の種類とあわせて promptData.reading(version 1)に保存するようにした(DB の migration なし。JSON 列に足すだけ)。チャットは reading があればそれをそのまま使い、無い旧い行だけを従来の互換ルールで読む(未知の旧い行を question にはしない。unknown は「質問ではない」ではなく既定の承認チップで表示)。あわせて H-3: findApprovalContextRange が承認対象の上端が境界か(complete)、12 行の上限で切れたか(truncated)、フレームの先頭まで境界が無かったか(top-unknown)を返し、truncated のときは promptData.approvalTargetCompleteness に載せる。契約に deny パターンがあり、読めた部分に一致しなかった場合、resolver はそれを合格とせず deny-pattern-unusable で答えない(deny パターンの無い契約・境界で止まった対象は従来どおり)。読み取りを決めてよい場所と読み戻す場所は tests/unit/guards/prompt-reading-sites-3881.test.ts で固定した。agy の受信記録つき番号リスト・copilot / codex の質問の証拠・yes/no の最初の一致(H-4)は後半の対応で、この変更では各ツールの判定は変えていない。 レビュー指摘の 2 件も同じ変更で直した。(a) キャプチャは画面の末尾 N 行なので、承認対象の走査がフレームの先頭まで境界なしで届いた場合(top-unknown)、キャプチャが窓を満たしていれば(isCaptureWindowSaturated と同じ判定の captureTopOf が clipped)上側が欠けている可能性があるとして promptData.approvalTargetCompleteness: 'top-unknown' を載せ、deny パターンのある契約では一致しなくても答えない。窓より短いキャプチャ(履歴の先頭から取れたもの)は従来どおり答える。Auto-Yes の poller は detectAndRespondToPrompt にキャプチャの窓(captureLines)を渡し、応答 poller の保存も CACHE_MAX_CAPTURE_LINES で同じ判定をして reading に captureTop を残す。(b) Auto-Yes が応答 poller より先に答えたとき recordAnsweredPrompt が新しく作る監査の行にも、tick の正規化済みフレームから同じ promptDataForStorage の reading を付ける。既存の pending 行は自分の reading を保ち、reading の無い旧い行には回答時の reading を補う。これで行の kind / phase が保存の順に依存しない(確認文と同じ文言を尋ねる通常の AskUserQuestion が先に自動回答されても、確認チップにならない)。 2 回目のレビュー対応: alternate screen(claude / copilot / opencode。履歴を持たず、キャプチャは見えている画面そのもの)では「窓より少ない行数=履歴の先頭」が成り立たないため、上端を窓ではなく画面から読む(captureTopOf(capture, window, cliToolId)。内容が画面の最下行まで届くか最上行に内容があれば screen-full、上の余白行と下の空きがあれば screen-start。窓が画面より狭く埋まっていれば clipped)。stopPattern 付きの 5,000 行窓で、スクロールで上が欠けた画面の deny 不一致に答えていた問題と、1,000 行窓で全体が見えている新しい画面を clipped として止めていた問題を両方直す。画面・スマホ・commandmate respond からの手動回答の監査行にも、送信前に検証したフレームの reading を付ける(/prompt-response の finishAnsweredPrompt。送信と保留の処理は不変)。GET /current-output は自分のキャプチャ窓で同じ上端の完全性を promptData に載せ、observe=1 の契約の再評価が poller と同じキャプチャで同じ判定(deny-pattern-unusable)になる。 3 回目のレビュー対応: tmux は各行を改行で終えるため 1,000 行の画面は split で 1,001 要素になり、キャプチャキャッシュの sliceOutput が 1,000 行の要求で画面の先頭行を落としていた。行数を終端の改行を除いた行で数え、終端は残すようにした(sliceOutput)。窓がペインの高さちょうどのとき行数からは切ったかどうか分からないため、キャプチャ層が実際に先頭を切ったか(sliceCutsHead)を Auto-Yes の poller へ渡し(capturePollerFrame の headCut)、切っていれば clipped として deny 不一致に答えない。これで stopPattern の有無(5,000 行と 1,000 行の窓)で全体の見えている画面の判定が揃い、落ちた先頭行に deny 対象があった画面に答えていた問題も直る。 4 回目のレビュー対応: 通常の画面(codex など scrollback のツール)でも、キャプチャ層が先頭を切っていないと報告したキャプチャは履歴の先頭からの完全なものとして読み、窓を満たしていても clipped にしない。窓を満たしたかも終端の改行を除いた行で数える。これまでは同じ完全な 1,000 行の履歴が 1,000 行窓(stopPattern なし)でだけ top-unknown になり、deny パターンのある契約で境界なしに先頭へ届く正常な確認を deny-pattern-unusable で止めていた(999 行でも終端の改行の有無で判定が変わっていた)。実際に先頭を切ったキャプチャは引き続き答えない。後半(各ツールの証拠): (1) 慎重モード(careful)では、copilot の質問(ask_user)・codex の質問(request_user_input)と区別できない番号付きリスト(選択肢の行が実機で採取した許可の文言として読めないもの)を question-left-to-human で人へ渡す(classifyAutoYesQuestion の unidentified。おまかせは従来どおり答え、許可の文言が読める確認画面は今回だけ許可で答える)。(2) agy の、カーソルも許可の見出しも footer も無い返答の番号リストは、hook の受信記録があっても答えない — 受信記録はツール呼び出しがあった証拠で、そのリストが承認画面である証拠ではないため。答えるのは画面に証拠(agy のリーダが読む許可画面、/feedback の footer、agy の許可の文言の選択肢)があるときだけ(isAntigravityGenericReadingOnScreen。#1928 の受入基準「返答本文の番号リストで Auto-Yes が撃たない」に沿う。表示側の読みは不変)。(3) yes/no は末尾の窓でパターン順の最初の一致ではなく最新の候補を採る(H-4。古い (y/n) が下の生きた [y/N] に勝ち、古い行で deny を判定していた)。同じブロックに別の yes/no があるときと、0 列の > の行(here-document の続きと前のターンの入力を区別できない)で上への走査が止まったとき(#3832 の制限 3)は承認対象を truncated とし、deny の不一致を許可にしない。(4) deny の照合の対象が文字数の上限(1 項目 20,000 文字)で欠けたときの不一致も deny-pattern-unusable にする(hook の構造化入力も同じ関数)。(5) captureTopOf は capture 層の headCut の未報告と false を分け、未報告で窓をちょうど満たした 1,000 行の alternate screen を空白の両端だけで screen-start にしない(clipped)。あわせて /respond の更新は reading の無い旧い行を旧い行のまま互換の reader に任せることを固定し、CLI の型の写し(api-responses.ts)に approvalTargetCompleteness・reading を、cli-operations-guide(ja / en)の deny-pattern-unusable と慎重モードの説明に上の評価不能と見送りを足した。レビュー対応: Approve? も 4 つの yes/no と同じ候補集合で最新を採り、同じブロックの競合を見る(古い (y/n) が下の Approve? に勝っていた)。yes/no の上への走査で、境界より下の深い > 行(字下げした here-document)が対象の上端を決めた場合も切った行を検査して truncated にする(境界の ● だけを見て complete にしていた)。current-output?observe=1 と commandmate prompt の契約の再評価も poller と同じ表(autoYesQuestionTreatment)と tool ID で読み、慎重の copilot / codex の判別できないリストを base-withholds(question-left-to-human)とする。agent-health の Auto-Yes の読み(readsAsAutoYesPrompt)は agy の画面の証拠の条件を受信記録と切り離して当てる。wait の exit 10 の JSON に approvalTargetCompleteness・reading を写す。webapp-guide(ja / en)の慎重モードの copilot / codex の説明を合わせた。上端まで境界の無い走査でも、深い > 行で対象を切ったときは(履歴の上端が既知でも)truncated にする。

  • fix(auto-yes,cli-tools): tmux への送信が結果不明で終わった回答を次の tick で打ち直さず、メッセージ送信で最後の再送 Enter を読み返して codex の入力欄を Enter の前後で同じ読み方にする (#3880): 回答(Auto-Yes・手動回答):tmux send-keys の execFile が timeout で kill された(tmux にはキーが届いているかもしれない)ときも、Auto-Yes は「キーが画面にあると分かっている」場合(#3839 の keysSent: true)しか保持しなかったため、最初のキー/最初のテキスト送信の結果不明では次の tick が同じ確認に同じ回答(y など)を打ち直しえた。sendKeys / sendSpecialKeys は失敗を TmuxSendError で投げ、delivery(not-started = tmux が終了コードで拒否・起動できず・送信前の検証で拒否 / indeterminate = 終了コード無しで終わった=timeout・signal)と、失敗より前に tmux が受け取ったキー数 keysDelivered を載せる(分類はエラーの形で行い、文言では行わない。メッセージは従来どおり)。delivered は tmux の正常終了を見たことだけを意味し、TUI の受理や画面の進行は含まない。sendPromptAnswer はこれを回答単位の answerDelivery にまとめ、not-started で何も送っていなければ keysSent: false(再試行してよい)、stillCurrent 無し(手動回答)の経路でも失敗前のキー数を数える。poller は indeterminate を #3839 の途中で止まった回答と同じく保持し(step 2.5、台帳 #3820 では failed =「画面にあるか分からない」のまま)、保持は画面の変化・セッションの再起動(世代)・人の回答の終わり方で解ける:POST /prompt-response の手動回答は開始では保持に触れず(進行中の tick の失効と手動の保留は従来どおり)、送信が通る・キー無しで断られると解除、手動送信自体がキーを残した/結果不明(500)なら保持を残す(無ければその画面に記録する)ので、手動の保留が切れた後の tick が同じ確認に打ち直さない。commandmate wait の partial-answer の文言と日英の cli-operations-guide.md は「入力が残った、または送信の結果を確認できない」と、解除の条件を述べるようにした(#3713 の文は残して後ろに足した)。手動回答の終わり方は、その回答に照合する:開始時の順序と、最初のキーの直前に固定した画面・session epoch を持つので、遅れて通った回答が後発の手動送信の保持を外さず、遅れて結果不明に終わった回答は再起動後・プロンプトが消えた後には保持を作らず、後発の保持を上書きしない。画面の取得に失敗したまま送った手動回答が結果不明になったときも、次に読んだ確認をその画面として保持する(プロンプトの無い画面・別の確認・再起動・人の回答で解ける)。手動送信が結果不明に終わると進行中の tick は失効し、ownership の確認を待っていた tick も同じ確認へキーを送らない。agy の受信窓の外でも、ダイアログが残っていれば手動の保持を残し、partial-answer として公開する。tmux から戻る前の手動送信も、ungated の読みが同じダイアログを見ている間は失効させない。手動送信の保持の順序は回答の開始でなく実際の送信の登録で採るので、画面の再確認を待った回答が後発より後に送ったときは、その回答の画面を保持する。 メッセージ送信:sendMessageWithSubmitVerification の読み戻しは、入力欄にまだ本文が残っていると Enter を打ち直し、最後の反復でも Enter を打った直後に読み返さず throw していたため、最後の Enter だけで送られたメッセージが submit could not be confirmed(commandmate ask / send の exit 99、submit-unconfirmed)になっていた。Enter の上限(初回+verifyAttempts)はそのままに、どの Enter の後にも読み返しを 1 回置き、最後の読み返しの後には Enter を足さない。また paste(512 bytes 超)の到着確認だけが末尾 12 行の › 行を読む別の読み方をしていたため、codex の入力欄が 12 行を超える(20 行の本文・狭いペインで折り返した 600 bytes)と本文が入っていても unseen、続きの行が > で始まる(Markdown の引用)と mismatch と読み、本文を取り除いて body-not-landed で失敗していた(同じ画面を Enter の後は pending と読む)。到着確認とエラー文の入力欄の表示も Enter の後と同じ読み方(複数行の codex 入力欄は extractComposerText)にした。codex の入力欄は(抜粋が 1 行でも)、UI 用の抜粋(2,000 文字・40 行・空行で切れる)ではなく生の行を › 行からフッターの手前まで読み、空白を除いて本文全体と一致したときだけ到着とする(先頭だけなら待機、本文+余分な入力や本文の途中は不一致。本文の中に [Pasted Content 600 chars] のような表記があっても本文として照合する)。送信失敗の分類(classifySendFailure)は「本文を入力した後」の文言を先に照合し、エラーに載る入力欄の抜粋(利用者の本文)に prompt not ready などがあっても not-sent にならない。argv(=本文)を載せる tmux の失敗文(Command failed: tmux …)も、本文の語句にかかわらず indeterminate にする。日本語/ASCII・512 bytes の前後・複数行・狭いペイン・placeholder・遅い描画を 1 つの corpus にして 3 つの読み方へ通すテストを置いた。読み取れずに失敗した送信は従来どおり indeterminate で、not-sent にはしない。

  • fix(catalog): codex 0.164.0 の /presets をスラッシュコマンドカタログに追加 (#3869): catalog:refresh --check が報告した codex のドリフト(新規 1 件・attestation drift source now lists /presets・verifiedAgainst 0.163.0 → 0.164.0)を無人実行で反映。codex-rs/tui/src/slash_command.rs @ rust-v0.164.0 を rust-v0.163.0 と突き合わせ、差は Presets("run or save reusable prompts and slash commands")の追加だけ(ほかの差分は supports_inline_args の拡張など集合に影響しないもの)と確認して、codex の attestation を 0.164.0 / 63 件 / observedAt 2026-10-24 に採り直した。en/ja の説明を追加し [要レビュー] は 0 件。除外(exclusions.json)と claude / antigravity / command-code / copilot / opencode の attestation は変更なし。

  • fix(auto-yes): 送信の途中で失敗した答えを、次の tick が同じ確認に最初から打ち直す問題を修正 (#3839): 世代がそのままの tick(ふつうの失敗)でも、例外が keysSent: true なら settleFailedTick が settleSendAttempt(attempt, true) で「途中で止まった答え」を記録するようにした(世代を失った tick と同じ扱い)。次の tick は同じ画面に答えず(step 2.5、人に返す)、画面が消えた・別の確認に変わったときは既存の forgetPartialAnswer で記憶が消えて次の確認に答える。あわせて sendPromptAnswer が、キーを 1 つでも送った後の例外に keysSent: true を付けて投げ直す — テキストの経路の文字の後の Enter の失敗(これまでは y が入力欄に残ったまま次の tick が yy + Enter を打ちえた)、stillCurrent を渡された特殊キーの列で前のキーが届いた後の失敗(claude の複数選択の Space の後など。届いた数は shouldContinue を聞かれた回数で数える)、Command Code の複数選択でトグルの後の確定行の送信の失敗。送る前の失敗は今のまま、MultiSelectAnswerRejectedError・PromptAnswerAbortedError など keysSent を持つ例外は自分の値のまま。#3820 の記録では、keysSent が null から true に変わった失敗は failed ではなく partial として数えられる(数え方自体は不変)。手動の送信(/prompt-response・respond)の応答は不変。

  • fix(detection): 本文が道具の呼び出しと同じ形でも deny の判定対象を切らない/質問の抽出は利用者のエコーより上を取らない (#3832): #3817 で残した制限 4 点を実画面(tests/fixtures の claude・codex・agy・copilot・opencode の 66 画面)と本番ログ(2026-10-10〜10-23)で先に測り、出ていた形と答える側の誤りだけを直した。findApprovalContextStart が枠の列の行を前のパネルの見出しと読むのは、今のパネルが同じ見出しを描いているとき(agy の Command → ⎿ User declined the tool call → Command)だけにした。0 列のコマンドの下に字下げした └── の木の出力が続き、その上に ● の行がある画面で、コマンドの行が approvalTarget から外れて denyPatterns が効かなかった(答える側の誤り)。extractQuestionText(multiple_choice の question)は、上 5 行の中に利用者のエコー(claude の ❯ msg、codex の › msg。❯ 1. のような選択肢の行は除く)があればそこから取る。エコーとみなすのは、枠より左の列にあり、直上が前のターンの終わりの行(✻ Baked for 3s・Worked for 17s)のときだけ(本文の ❯ / › 行で question が切れてコマンドが deny の判定対象から外れないように)。codex の実画面で前のターンの • HTTP/2 200・Worked for 17s が、本番ログで ⏺ IMPL_COMPLETED・✻ Cogitated for … が question に入り、deny の判定対象になっていた(止めすぎる側)。> はエコーとみなさない(codex の信頼確認の > You are in <dir> と here-document の本文の行があるため。question が deny の判定対象に残す)。前のターンの結果に印の無い継続行がある形と、0 列の > 行で切れる形は実画面に出ていなかったので直さず、コードに既知の制限として書いた。66 画面の approvalTarget は変わらず、question が変わったのは codex の 1 画面だけ。

  • fix(auto-yes): ツールの根拠が無い汎用の yes/no と質問に答えず、送信の直前に許可と契約を読み直す (#3822): ダイアログゲート(evaluateAutoYesDialogGate)が enforce のツール(claude / codex / copilot / opencode / opencode-v2)では yes_no も裁くようにした。これらのツールは (y/n) のダイアログを描かないので、画面の (y/n)(返答が引用したもの、シェルの確認)はツール自身の detectDialog が保証しない限り答えず、unclassified-frame を lastSuppression と WARN poller:auto-yes-skipped-unclassified-frame に記録する(利用者の了承 2026-10-20。Auto-Yes を後から ON にしたとき既に開いている (y/n) も同じ)。AskUserQuestion の picker(isAskUserQuestion)と OpenCode V2 の質問フォーム(question)は人が答えるものとして答えず、新しい抑止の理由 question-left-to-human と WARN poller:auto-yes-skipped-question を記録する(commandmate wait は「because the agent is asking a question, and Auto-Yes answers permissions only; …」と表示し、確認の記録と agent-health の集計もこの語で数える。質問の後の Submit / Cancel の確認画面は従来どおり question-confirm-page。allow-listed の allowPromptTypes: [multiple_choice] でも委任とは読まない)。最初のキーを送る直前(所有権確認・Enter フォールバックの取り直しの後)に、Auto-Yes が有効で期限内か(poller の tick のみ)と、契約をキャッシュを通さず読み直して mode: off・denyPatterns が今も答えてよいかを確かめ、だめならキーを送らない(読み直しの失敗は policy-lookup-failed)。PermissionRequest hook の allow は応答そのものが答えなので対象外。legacy のツール(gemini / antigravity / vibe-local / command-code)と CM_AUTOYES_DIALOG_GATE=<tool>=legacy は従来どおり、履歴と /prompt-response が読む evaluateDialogPresence も不変。 利用ガイド(ja / en)と docs/design/task-contract.md の「mode: safe は yes_no を自動応答する」は、enforce のツールでは根拠の無い yes_no に答えないと改めた。 commandmate auto-yes --enable が mode: safe の契約に添える Note: も、enforce のツールでは汎用の (y/n) に答えないと述べるようにした。既定が legacy のツール(gemini・vibe-local・command-code)と hook の受信がある antigravity の確認は従来どおりで、運用者が CM_AUTOYES_DIALOG_GATE=<tool>=legacy を立てると、そのツールの yes/no と質問への回答が戻る。 許可の確認の選択肢を効果で読むようにした(新モジュール src/lib/polling/auto-yes-option-effect.ts)。claude・codex・agy(antigravity)・command-code・copilot の実機の画面の文言から「今回だけ許可」「永続の許可(don't ask again・always allow・Persist to settings.json)」「モード変更(allow all edits・Full Access ほか)」「拒否」を読み、Auto-Yes は今回だけ許可の選択肢を、カーソルや既定がどこにあってもその番号で答える(agy でカーソルが 4. No に残った画面へ 4 を送っていたのを 1 にする)。今回だけ許可が無い・2 つ以上ある許可の確認には答えず、新しい理由 allow-once-unidentified を lastSuppression・確認の記録・agent-health の集計に記録する(commandmate wait は「because no single option of the permission dialog allows this call only …」と表示)。どの行も読めない番号付きの一覧と、表を持たないツール(opencode・opencode-v2 は許可が番号付きの画面にならず hook の allowOnce で答える。gemini・vibe-local は実機の許可の画面が無い)は従来どおり。PermissionRequest hook の allow は今回だけ(allowOnce)で永続の許可を返していないことを確かめた。 allow-once-unidentified は契約より前の基本の判定なので、commandmate prompt --json の autoYes.contractEvaluation は契約の有無によらず新しい状態 base-withholds(理由つき)で返し(policy-withholds は契約の判定だけ)、agy の受信窓の後のログ antigravity-autoyes-withheld-by-policy の hint も「契約の autoYes を確認」ではなく「今回だけ許可の選択肢を 1 つに特定できない(基本の判定)」と案内し、commandmate prompt(--json なし)の contract re-evaluation: 行も base-withholds reason=allow-once-unidentified と理由を出す。

  • fix(auto-yes): ブラウザの自動回答と読めない選択画面への Enter の補いをやめ、Auto-Yes 中でも答えない確認には人が画面から答えられるようにする (#3821): 自動で答えるのはサーバー(画面を読む poller・PermissionRequest hook・API の裁定)だけにした。useAutoYes は /prompt-response へ何も送らず(同時実行の上限で poller が起動していないエージェントへ、契約を通さずに答えていた)、トグル横の「Auto responded」の表示も外した。/prompt-response は origin: auto-yes-browser の要求を最初の副作用の前に 409(reason: browser-auto-yes-retired)で拒否する(origin の無い人の回答は従来どおり。origin を付ける前=#3820 より前のバンドルのタブからの回答は人の回答と区別できないので、再読み込みが要る)。読めない選択画面へ送っていた裸の Enter(#3397)は両方の経路で止め、CM_AUTOYES_ENTER_FALLBACK でも戻らない(画面は unclassified-frame で人に任せる)。session epoch・通常の回答の Enter・人の手動入力は変えず、autoYes.lastEnterFallback は互換のため欄を残して常に null、台帳の basis browser / enter-fallback と過去の [Enter] の履歴は読めるまま。人の操作への復帰として、poller の tick が作る「いまの確認についての判断」(answering / withheld+理由 / none)を grant の同一性・session epoch・画面のキーに結び付けて記録し(OFF→ON・期限切れ・再起動・人の回答で失効、計測の台帳は正本にしない。別の確認かどうかは質問・選択肢に加えて承認の対象(approvalTarget。選択肢の行のカーソルだけを見ず、本文の字下げ・>・空行は #3817 と同じくそのまま比べる。上が切れて最後まで照合できない読みは hook の呼び出しが同じでも none)と agy の hook の呼び出しで見て、確かめられなければ none。送信後の重複抑止の間も answering は送信した確認にだけ続ける)、current-output の autoYesDecision として同じ応答の promptData に対応づけて公開する。Auto-Yes が有効でも poller が動いていないインスタンスの確認は withheld(server-poller-inactive)。PC は terminal snapshot・fingerprint(判断だけが変わっても push)・HTTP と push 共通の applier(後から届いた古い判断を上書きしない)まで、スマホは controller の応答まで通し(判断にはどの確認についてかを表す confirmation(サーバーの同一性の判定と同じ規則)を付け、PC は同じ confirmation のときだけ新しい方を残し、別の確認の判断は引き継がない)、回答欄は Auto-Yes 中も autoYesDecision が answering でなければ出す(欄の無い古いサーバーの応答も出す。OpenCode / V2 の API の質問は常に出す)。人が回答欄から答え始めると、所有権の確認や送信の途中で待っている poller の tick はそれ以上キーを送らず、その後約 10 秒はそのインスタンスの確認を人に任せる(answered-by-human。Auto-Yes は有効のまま)。commandmate wait は withheld の確認を Auto-Yes の猶予で待たずにすぐ報告し、理由を stderr に出す。

  • fix(detection): agy の番号付き画面を既定で答えないようにし、欠番の許可の画面へ Auto-Yes が答える問題を修正 (#3818): #3808 の isAntigravityUnvouchedNumberedScreen は「答えない画面」を 1 つずつ足す形で、番号が 1 ずつ減る連番を要件にしていたため、汎用の番号付き解析が描画欠けとして許す欠番 1 件(> 1. Yes / 3. / 4. No)の許可の画面は汎用解析へ戻り、ポーラーの入口(detectPromptOnCleanFrame)・/prompt-response・実ポーラーのいずれでも答えられる状態だった(実機 agy 1.1.27 の dialog-bash-oneline.txt から 2 の行を除いた画面で確認)。答える側(Auto-Yes の入口・/prompt-response・agent-health の読み)に新しい判定 isAntigravityUnansweredNumberedScreen を足して判定を逆にし、agy の番号付き画面で汎用解析の読みに答えるのは /feedback(直下の esc Cancel · 1-6 Select footer)と、返答の素の番号リスト(1.〜N. が欠番なく続き、どの行にも > カーソルが無く、下に ↑/↓ footer が無く、上に agy の許可の見出し(Requesting permission for: / Do you want to proceed?)も無い — #1928 の legacy 経路が答えている形。見出しの条件は、footer とカーソルの行(選択肢 3・4)がともに画面外で 1.・2. だけが見える許可の画面を答えない側にするため)だけにした。それ以外(欠番・1. に届かない=カーソルの行が画面外・カーソル付きで footer が画面外・↑/↓ footer はあるが許可の footer でない)は、agy 専用のリーダが読んだ場合を除き答えない。リーダが読む 1.1.27 の許可の画面と /feedback は従来どおり答える。「人が待っているか」という状態の判定(detectSessionStatus。サイドバーの waiting・通知)は変えず、#3808 の isAntigravityUnvouchedNumberedScreen のまま(欠番の画面などは従来どおり waiting と出し、答えないだけ)。あわせて agent-health の screen-checks.ts に readsAsAutoYesPrompt() を足し、agy の isPrompt を detectPromptOnCleanFrame で読むようにした。日次確認のプローブ(scripts/agent-health/probe-tool.ts の pickerReadsAsPrompt())がこれを使う(ほかのツールは従来どおり detectPrompt の読み)。

  • fix(push): ブラウザを開いていなくても、答え済みの確認の waiting の区間をターン終了(stop)で閉じる (#3798): agy のように返答を hook で受け取るツールでは、waiting の区間(waitingSince)を閉じる経路が status の問い合わせ(ブラウザ)と応答 poller の返答の記録しかなく、ブラウザが無いと Auto-Yes が答えてターンが終わっても区間が開いたままになり、hold の終わりの再判定や次の確認で prompt-push-allowed (auto-yes-not-answered) の誤通知が出ていた(UAT 2026-10-18 項目 D)。区間に ID と帰属(どの turn・世代で開いたか)を持たせ、turn model の遷移が「実際に何をしたか」(stopped / stop-without-turn / stop-rejected / opened / decision-answered / generation-fenced 等)を返すようにし、recordAgentEvent の末尾の同じ同期区間で、その turn の区間だけを閉じる・付け替える(waiting-episode-binding の T 表)。agy のように user_prompt_submit が無く最初の post_tool_use で turn が開くツールでも、確認が turn より先に開いた区間は post_tool_use で同じ区間のまま付け替わり、post_tool_use が届かず stop だけが来ても閉じる。別 session の stop・世代より前の stop は区間を閉じず、opencode / V2 のように stop を記録した後に await する経路でも、await の後では区間に触れない。claude-done(識別子の無い旧形式)は最初の capture の前に対象の区間を固定し、同じ区間・同じ世代で、その世代に hook の stop が無いときだけ閉じる。並列ツールの post_tool_use による decision の一括の release では閉じず、明示の回答(permission_replied 等)で閉じる。getWaitingEpisode と onWaitingTransition の形は変えず、ID つきの購読 onWaitingEpisodeTransition を足した。push 側も区間を ID で見分ける: waiting の通知は onWaitingEpisodeTransition で購読し、Auto-Yes の hold・通知の dedup・抑止記録の帰属・reminder と hold の終わりの再判定を区間 ID で照合する(ID を渡さない呼び手とは「片方に ID が無ければ since が同じなら同じ待ち」の規則でまとめ、#3796 の予約 tick は hold の開始時に 1 回だけ読む)。同じ since の 2 つの待ちは 2 回通知され、区間の置き換え(replaced)は古い待ちのタイマーと hold を捨てて新しい hold を始め、解除通知を出さない。カードのマークに最後に通知した区間 ID を持たせ(時刻の行と同じ 1 文で書く。時刻だけの旧いマークは ID 無し)、解除の push に resolvedEpisodeId を載せ、Service Worker は、解除を決めた後に送られた別の区間のカード(worktree ごとの連番 order が解除より大きい。連番は DB に置き、カードの送信と解除の決定のたびに 1 文で進める)が先に届いていればそれを無音で出し直し、それ以外のカードは閉じて解除を出す(ID が違うだけ・区間の since が遅いだけでは出し直さないので、端末ごとの配送の失敗で残った古いカードも閉じ、since を使い回す次の待ちも消さない。どの分岐でも showNotification は 1 回。ID の無い解除は従来どおり)。画面の観測者(status の問い合わせ・応答 poller・Auto-Yes poller)は受信窓を見ない共通の読み方(readWaitingScreen)と共通の観測関数(observeWaiting、表 O-1 / O-2)を通り、最初の await の前に区間の版と世代を固定して、その間に区間が動いていれば開く・閉じる・付け替えるをしない(一覧・詳細の API は listSessions() の前に固定するので、一覧の後に始まった session の待ちを「session が無い」で閉じない)。閉じた区間と同じ確認を閉じる前に取ったフレーム(5 秒キャッシュの古い値、再検証の取り直しが閉じた後に書き戻した値、閉じた同じミリ秒の cache の値を含む。フレームは取得の開始時刻と取得元で判定する)では開き直さず、応答 poller はそのとき保存も dedup の登録も push もしない(次の tick の新しいフレームで取り直す。別の確認なら開く)。答えの後の survey・picker・信頼確認のように人だけが終わらせられる画面は screen の区間になってターン終了では閉じず、Auto-Yes poller もブラウザ無しでこれを開き・付け替え、画面が消えたのを screen になった後に取ったフレームで読んだときに閉じる(Auto-Yes poller は自分が答えていない screen 以外の区間は閉じない)。API で答えられる許可(#3184)は Auto-Yes poller も prompt と読み、観測者が交互に読んでも区間の種類と通知の文言が menu に入れ替わらない。区間の turn とは別の turn の確認が出たときや、終わった turn の区間に別の確認が出たときは、古い区間を 1 回の replaced で置き換える。Auto-Yes poller は答えを送った直後の同期区間で、答えた確認を新しい確認として観測し(区間が無ければ開く)、答えた区間 ID を lastAnswer に持ち、答えた画面が消えたのを読んだ tick でその区間だけを poller-answered で閉じる(ブラウザも hook の stop も要らない。許可の取り消し・session の作り直しで世代を失った tick は記録しない)。答えていない区間・置き換えられた前の区間への答え・capture の失敗・分類できない画面・tick の途中で動いた区間は閉じない。答えた後の同じ turn の別の確認(画面キーが違う)は、poller 自身の答えでも、応答 poller の読み(poller の lastAnswer を渡す)でも古い区間を replaced で置き換え、新しい hold をかける(使い切った前の区間の hold で即通知しない)。抑止記録(autoYes.lastSuppression)は書いた時点で開いている区間の episodeId を持ち(agy の受信ログの再刻印は元の記録の ID を引き継ぐ)、current-output は今開いている区間を autoYes.waitingEpisodeId で載せ、commandmate wait は両方の ID があれば ID の一致で抑止記録が今の待ちの理由かを決め(違う・区間が無ければ古い記録、同じなら記録の古さを問わない。push 側と同じ)、どちらかが無ければ従来の 60 秒の窓に戻る。答えの対象の区間は送信を始めた時点で固定し、送信の間に別の区間へ入れ替わっていれば、また同じ区間でも人が操作する画面(survey・picker)に変わっていれば lastAnswer を記録しない(その画面は通知される)。push の gate は poller が答えた区間(lastAnswer と同じ ID で、まだ開いていて人が操作する画面でないもの)の猶予の終わりを auto-yes-not-answered にせず(auto-yes-answered。通知器は猶予ごとに聞き直し、survey・picker に変われば通知する)、答えた後に分類できない画面が続いても、答える前に同じ区間で残った抑止記録があっても誤通知しない(答えた後の新しい抑止は通知する)。current-output はこの判定を autoYes.lastSuppressionAnswered で公開し、commandmate wait も答える前の記録で猶予を飛ばして exit 10 にしない。応答 poller は区間を commit してから、確定した区間 ID を gate と push に渡し(同じミリ秒の別の区間の抑止記録を今の理由と読まない・配信カードに区間 ID と順序が付く)、commit の edge の通知は fanOutAfterCommit でその後に回して同じ ID で重複を落とす。wait の抑止記録の説明(日英のガイド・wait --help)も ID の一致で決まる形に揃えた。人が要る画面は従来どおり約 9 秒で通知可。

  • fix(auto-yes): 同じ問いへの再回答を待つ間の誤通知と、狭い・低いペインでの Claude の確認画面の取りこぼしを直す (#3791): push gate の auto-yes-answering の保留(9 秒)は「最後の回答から cooldown と 2 tick で次を答える」前提だったが、poller の重複ガード(#306)は直前に答えたのと型・質問が同じプロンプト(Claude の Bash 承認は続けて Do you want to proceed?)を DUPLICATE_RETRY_EXPIRY_MS(10 秒)答えず、その後の tick で答えるため、答える流れでも 9 秒で auto-yes-not-answered の通知が 1 回出ていた。poller がガードで今の画面を実際に保留したときだけ、その予約(duplicateRetryReservation: ガードのキーと一致した画面・この待ちが始まってからの tick・生きている poller。別の画面・プロンプトの無い tick・失敗した tick・セッション無しで外す。ガードが切れた同じプロンプトの再送の tick は、所有権の確認と送信を待つ間も予約を保ち、送信が決着したら外す)を公開し、gate は予約があるときだけ保留を「その回答+10 秒+2 tick」まで延ばす(autoYesHoldEndsAt())。回答の時刻だけでは延ばさないので、直前に答えた後の別の画面(自動で答えない画面)・エラーの backoff・回答の無い画面・停止した poller は従来どおり 9 秒、契約の差し控えは即時に通知する。isClaudeAskUserQuestionConfirmPage は claude-cli 2.1.310 の実機で採取した形(tests/fixtures/claude-confirm-live-3791/)だけ読めるようにした: 狭いペインで質問・見出し・選択肢 1 が折り返す形、タブ行が 2 行に折り返す形(16 桁)、長い質問でタブ行が質問の 200 行より上にある形、ペインが画面より低く頭(タブ行)が切れた形。後の 2 つは入力欄の無いフレームで、質問から上が回答一覧の行だけで Review your answers とタブ行(または画面の上端)に届くときに限る。2. Cancel の下の未知のフッターは実機で出なかったので読まない。文言を含む Bash 承認・その出力の引用・同じ文を問う AskUserQuestion は実機の陰性として残した。

  • fix(cli): daemon・GUI 更新・スクリプトのサーバーログの権限と後始末をそろえる (#3752): #3731 の後始末。GUI 更新ログ(update.log)を openSync(..., 'a') ではなく daemon と同じ openServerLog() で開くようにし、0600 で作成・起動時 10MB/3 世代でローテートする。scripts/start.sh と scripts/build-and-start.sh の server.log と退避世代は 0640 から 0600 にそろえた(CLI と同じファイルを指す場合の食い違い解消)。使われていなかった LogPathResolver(commandmate[-N].log)を削除し、ログパスは getServerLogPath()(server[-N].log)に一本化。start / start-core のテストが stdio: 'ignore' 固定のままだったので、ログを開けたとき(記述子)と開けないとき('ignore')の両方を確かめるようにした。

  • fix(realtime): ターミナルの購読で前の購読の解除が reject すると新しい購読が黙って消える問題と、解除が遅いと再購読の connected:true の後に古い connected:false が届く問題を修正 (#3749): terminal_subscribe が前の購読の unsubscribe() を try/catch 無しで await していたため、reject するとハンドラがそこで終わり新しい購読が成立しなかった(best-effort として握りつぶし、そのまま新しい購読へ進む)。terminal_unsubscribe は解除を await した後に connected:false を送るため、その間に始まった再購読が先に connected:true を送ると、生きている購読と矛盾する connected:false が後から届いた(購読の世代 terminalSubscriptionSeq が解除の開始時から動いていれば送らない)。切断時の解除の reject が unhandled rejection になる経路も塞いだ。あわせて relays cancel の CLI 埋め込みドキュメント(commandmate docs)に、配達中(返信・prompt notice)の取り消しは 409(exit 2)で拒否され、配達は通常そのまま届き、失敗した場合は relay が open のまま戻るので試行の後にもう一度取り消せることを追記し、scheduleRepaintInvalidation のコメントを #3734 の世代更新後の実態に合わせた。

Security

  • fix(deps): 間接依存 undici を 7.30.1 以上に更新し GHSA-xjj6-f33q-929c を解消 (#3891): jsdom@28.1.0 経由の undici 7.30.0 に、過剰な content-encoding による未処理エラーでの DoS(high)があった。package-lock.json のみ更新し(メジャー更新なし)、npm audit --omit=dev から undici の指摘が消えたことを確認。