Repository navigation
v0.48.0
[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 driftsource 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と WARNpoller:auto-yes-skipped-unclassified-frameに記録する(利用者の了承 2026-10-20。Auto-Yes を後から ON にしたとき既に開いている(y/n)も同じ)。AskUserQuestion の picker(isAskUserQuestion)と OpenCode V2 の質問フォーム(question)は人が答えるものとして答えず、新しい抑止の理由question-left-to-humanと WARNpoller: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)。PermissionRequesthook の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 は実機の許可の画面が無い)は従来どおり。PermissionRequesthook の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・
PermissionRequesthook・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、台帳の basisbrowser/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 Selectfooter)と、返答の素の番号リスト(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経由のundici7.30.0 に、過剰な content-encoding による未処理エラーでの DoS(high)があった。package-lock.jsonのみ更新し(メジャー更新なし)、npm audit --omit=devからundiciの指摘が消えたことを確認。