Skip to content

v0.47.0

Choose a tag to compare

@Kewton Kewton released this 20 Oct 16:42
· 5 commits to main since this release
f1317f1

[0.47.0] - 2026-10-21

Highlight: Auto-Yes が答えずに止まった確認を、CLI から画面を確かめたうえで直接答えられるようになりました(commandmate prompt で 1 回観測し、respond --expect-screen <kind> --observation <token> で観測した画面にだけ答える)。あわせて Auto-Yes の安全側の修正をまとめました。agy のログイン方法の画面や footer の切れた画面に番号で答えない、Submit / Cancel の確認画面に答えない、hook 受信の窓(8 秒)と tick の競合で約 4 時間止まっていた問題(#3767)を直すなど。サーバーの再起動で外れた Auto-Yes を「再起動で解除」と表示し、start --daemon のサーバーの出力を logs/server.log に残すようになりました。

Added

  • feat(cli): 直近の send が「未送信/送信済み/不確定」のどれかを prompt --json の lastSend と send の失敗メッセージで示す (#3759): 起動の窓が時間切れになって最初の message が届かなかったとき(#3746: Antigravity prompt not ready)、その message が未送信なのか送信済みなのか不確定なのかを示すものが無かった。sendUserMessage が (worktree, tool, instance) ごとに直近の試行の結末をプロセスのメモリ(globalThis、上限 1024 件)へ記録する新モジュール src/lib/session/send-attempt-state.ts を追加し、記録は at・state・reason・messageDigest(本文の SHA-256 の先頭 16 桁のみ。本文は保存しない)を持つ。state は not-sent(本文のキーを 1 つも打つ前に失敗: prompt-not-ready / session-starting / prompt-waiting / foreign-session / model-switch-failed / composer-not-empty)、sent(submit を確かめた)、indeterminate(一部を打った後に失敗、または結果を確かめられない: body-not-landed / replaced-by-completion / submit-unconfirmed、submit を確かめられず claude / codex の composer にまだ本文が見えるときは composer-residue、それ以外の未知の失敗は send-error)。未知の失敗を not-sent にしない(「分からない」を「再送してよい」と読ませない)。commandmate prompt --json(#3755)の payload に lastSend: { at, state, reason } を足し(記録が無ければ null、古いサーバでは項目なし。digest は公開しない)、人向けの表示にも last send: 行を出す。send ルートの 500 の失敗メッセージ(prompt not ready など)の末尾に [send state: not-sent (prompt-not-ready): …] を足す(同じ本文で、この要求の開始後に作られた記録のときだけ)。ルートが send サービスの前で断る試行(セッション起動の失敗・他サーバのセッション)も not-sent として記録する。どの状態でも自動で再送しない: 再送するのは利用者/監督のエージェントで、indeterminate のときは capture --pane で画面を確かめる前に再送しないよう docs(ja / en の cli-operations-guide)に書いた。記録は try/catch の内側で行い、記録できなくても send の結果(SendUserMessageResult の形を含む)は変わらない。

  • feat(cli): respond --expect-screen <kind> --observation <token> — 観測した画面にだけ答える厳格モード (#3756): commandmate prompt の観測トークンを付けて答えると、サーバが送る直前に取り直した画面で kind・トークン(pane・session 世代・画面の出現・選択肢・選択中の位置)・信頼の確認のフォルダ(--expect-target、省くと何も送らず画面のフォルダを返す)・答えの選択肢・画面が pane の最下部で開いていること(生成中の返答に引用された信頼の確認には答えない)を確かめ、どれかが合わなければ何も送らず exit 40 と観測した kind を返す。専用の POST /api/worktrees/:id/prompt-response/strict へ送り、endpoint の無い古いサーバでは止まる(照合の無い /prompt-response へ fallback しない)。答えるのは antigravity-trust と antigravity-permission だけで、番号の無い信頼の確認は画面に見えている選択肢の label の一意一致(Yes → Yes, I trust this folder)を ↑/↓ と Enter として送り、番号・--default・同名や複数に当たる label・画面外の選択肢は拒否する。キーは 1 つずつ送り、矢印の後に画面を取り直して同じ画面のまま選択が動いたことを確かめてから次を送る。送信を始めた後の部分送信・結果不確定は exit 41(再送してはいけない)、Enter の後は outcome(advanced / next-prompt / session-ended / unchanged / indeterminate)を返し、成功は前の 3 つだけ。対象 instance の Auto-Yes が ON の間は antigravity-permission を厳格には答えない(exit 40 auto_yes_active。先に auto-yes --disable)。トークンは送る前に使い切り、同じ instance の厳格な送信はサーバ内で直列化、--operation-id は worktree・instance ごとに要求の中身と一緒に覚え、キーを送った操作の同じ再送は覚えている結果を返して二重に送らず、中身の違う再利用は operation_id_conflict(exit 41)、exit 40 の結果は覚えない。接続できなかった要求は exit 40(server_unreachable)。起動処理(#3757)の「Enter 1 回の借り」とは、厳格な送信の間は借りを取らせず、信頼の確認に答えたら借りを捨て、起動の窓が開いている間・借りが取られた直後は答えないことで二重に答えない。誰が・どの画面に・どの選択肢を送ったかを server.log(strict-prompt-answered / strict-prompt-response-refused)と task の履歴(prompt_answered_human)に残す。--json は stdout に結果オブジェクト 1 個。auto mode の許可を得る手段ではない。

  • feat(cli): 止まっている画面を機械で読める形で 1 回観測する commandmate prompt <wt> --instance <id> [--json] と観測トークンを追加 (#3755): GET /api/worktrees/:id/current-output?observe=1 が同じ capture から promptObservation(schemaVersion 付き)を返し、prompt はそれを表示する。payload は解決済みの対象(instance・pane)、観測時刻、session 世代、画面の kind・観測トークン・質問・承認対象(信頼確認ならフォルダ)・選択肢・現在の選択、回答経路、Auto-Yes の有効状態と poller の状態。kind は新しい共通の語彙(src/lib/prompt-observation/kinds.ts: antigravity-trust / antigravity-model-picker / antigravity-permission / antigravity-slash-menu / composer / none / unknown)で、promptData.type や agy の antigravity_selection_list(信頼確認・picker・slash メニューをまとめる)とは混ぜない。agy の信頼確認・picker・slash メニューは新しい読み取り(detection/tools/antigravity/screens.ts)で区別し、番号付きの許可は既存の dialog.ts を使う。計測していない画面は unknown。観測トークン(po1.<発行時刻>.<HMAC>、120 秒、プロセスごとの鍵)は instance・session 世代・pane・画面の出現(サーバが instance ごとに覚える appearedAt。別の画面・composer・none を観測すると捨てる)・kind・質問・選択肢・承認対象・選択中の位置に結び付き、使い捨て。unknown と session 世代が判らないときは発行しない(tokenWithheld に理由)。検証の入口 verifyObservationToken() / consumeObservationToken() / observationBindingOf() を後続の厳格な respond(#3756)のために export した(この変更では respond は変えない)。観測と観測の間に消えて同じ文面で再び出た画面は同じ出現と見なす。観測が失敗しても current-output は promptObservation を付けずに従来の body を返す。「答えなかった理由」は 2 項目に分けた: poller の実際の記録(pollerRecord、画面との対応 screenCorrelation は記録が画面を名指ししないため unknown / type-mismatch / no-prompt-on-screen のどれか)と、契約の方針をいま当て直した結果(contractEvaluation、理由は AutoYesSuppressionReason の語のみ)。記録が無いことを「回答可能」とは扱わない。取得できたら確認が無くても exit 0、セッションが無いときは exit 21。wait は poll に observe=1 を付け、exit 10 の payload に同じ observation を足す(exit code・待機・猶予・autoYesSuppression は不変、古いサーバでは付かない)。

  • feat(slash-commands): codex 0.163.0 の /config をスラッシュコマンドのカタログに追加 (#3722): openai/codex の slash_command.rs を rust-v0.161.0 と rust-v0.163.0 で比べ、SlashCommand::Config(説明 "configure Codex preferences")の追加を確認(集合は 61 → 62)。attestation の codex を 0.163.0 の読み取りから書き直した。

Changed

  • docs(claude-md): CLAUDE.md を実態に合わせて更新し CLI 節を圧縮 (#3737): 技術スタックの版を major のみに(Next.js 15 / React 19 / Tailwind CSS 4。AGENTS.md も同様)、ファイル構成ツリーを主要ディレクトリだけに絞って存在しない components/auth/ を除去し tests に e2e/ などを追記、コマンド表に /orchestrate ほか 7 本を追加、CLI 節を bash ブロックから事故防止の規則の箇条書きへ圧縮(20,817→約 15,300 bytes)、「Server Components 優先」を「必要なときだけ use client」へ、必須チェック節を .commandmate/verify.yaml 参照とサイズ guard 2 つに整理、本文の Issue #N 履歴表記を除去、pm-auto-dev.md のファイル構成更新条件を「トップレベルの増減時のみ」に変更。

  • refactor(search): terminal-highlight-markdown.ts を 510 行から 152 行に分割 (#3698): 文字参照(CHARACTER_REFERENCE〜referenceAt)を terminal-highlight-markdown-reference.ts へ、alignSubsequence・mapMarkdownPositionsToDom とそれだけが使う data-search-raw の読み戻し(parseAttr・parseSpans)を terminal-highlight-markdown-align.ts へ移した。SEARCH_RAW_ATTR と RawSpan は循環を避けるため align 側で定義し、元のモジュールは SEARCH_RAW_ATTR・mapMarkdownPositionsToDom を再 export する(既存の import 元は無変更)。依存は 本体 → align → reference の一方向。振る舞いは不変で、分割先 2 本に単体テストを追加した。

  • refactor(polling): migrateResponsePollerWorktreeIds を段ごとの小さな関数に分け、複雑度を 34 から 6 に下げる (#3697): 改名対象の収集・稼働中ポーラの振り分け・旧キーからの持ち上げ(Phase 1)・一時停止中チェーンと保留スクレイプの行き先の収集・行き先を占有していたチェーンの停止・新 ID での再始動(Phase 2)を response-poller-core.ts 内の非公開関数に移した。処理の順序・ログ名・コメントは変えず、関数は同期のまま(await は足していない)。公開 API と import 元は変わらない。

  • refactor(verification): scope-gate.ts の glob 照合と log_tail の書式を別モジュールへ分割 (#3647): 895 行に増えていた src/lib/verification/scope-gate.ts を 617 行に縮めた。globToRegExp〜ScopeMatcher と、それが使う ALWAYS_ALLOWED_PREFIX / SCOPE_EXEMPT_ALWAYS_ALLOWED / SCOPE_EXEMPT_CONTRACT_PATH / ScopeDecision を新しい scope-gate-glob.ts へ、formatPatterns〜formatOrchestratorExcluded と、それが使う MAX_REPORTED_VIOLATIONS / SCOPE_PATTERN_ARROW / ScopeAdmission を新しい scope-gate-format.ts へ、コメントごと書き換えずに移した。scope-gate.ts は移した公開名をすべて同じ名前で再 export するので import 元は変わらず、新しいモジュールは scope-gate.ts を import しない(循環なし)。scope ゲートの判定・log_tail の文言・並びは変わらない。分割先それぞれに単体テスト(scope-gate-glob.test.ts / scope-gate-format.test.ts)を追加した。

  • refactor(ui): MessageInput の送信ボタンとメタ行を message-input/ の下へ分ける (#3606): 複雑度 33 → 20。送信ボタン(ComposerSendButton)と Auto-Yes・キー案内のメタ行(ComposerMetaRow)を部分コンポーネントにした。DOM・class・data-testid・props・import 元は不変で、動きは変えていない。

  • refactor(ui): サイドバーのブランチ行 BranchListItem の複雑度を 33 から 24 に下げる (#3604): 進み具合の保存と表示の保持(#3594)をフック useWorktreeLifecycleStatus(src/components/sidebar/branch-list-item/use-worktree-lifecycle-status.ts)へ、進み具合の札と BranchRowStatusPillContext を WorktreeStatusPill(同 WorktreeStatusPill.tsx)へ、行ボタンの右余白と next action 行のクラス選びを branchRowButtonPaddingClass / branchNextActionClass(同 row-classes.ts)へ移した。BranchListItem.tsx は BranchRowStatusPillContext を同じ名前で再 export し、公開する名前・props・描画結果・クラス文字列は変えない。

  • refactor(ui): TerminalSplitContainer の状態・ハンドラ・描画を terminal-split-container/ の下へ分ける (#3603): 複雑度 46 → 24、行数 1,571 → 721(#3605 を同梱)。分割先は「…」パネル(ActionBarItemsOverflow)・リサイザ・2x2 グリッドの配置・レイアウト操作・History / Files トグル・単一分割のセッション枠の部分コンポーネントと、フォーカス/選択/配置の effect・リサイズ・項目ホスト・分割ごとのハンドラのフック。ActionBarItemsOverflow / clampOverflowPanelLeft / overflowPanelMaxWidth / InstancePlacementRequest / SelectedSplit は元のモジュールから再 export し、import 元と props は不変。動きは変えていない。

  • refactor(ui): TerminalSplitPaneContent の分岐を小さな関数へ移し、複雑度を 31 から 18 に下げる (#3569): 足元の表示判定(showPrompt / showNav / showEscapeHatch)を src/components/worktree/terminal-split-pane-content/pane-gates.ts の shouldShowPromptPanel / shouldShowNav / shouldShowEscapeHatch へ、入力欄の宛先表示("To: · ")を同 composer-target.ts の buildComposerTarget へ、式とコメントをそのまま移した。公開名・props・表示・送信先は不変。

  • refactor(ui): TerminalSplitPane の複雑度を下げる(振る舞い不変) (#3567): TerminalSplitPane の複雑度が ESLint complexity の 25 を超えていたため、ヘッダの操作群(SplitActionControls)・セッション周りのチップ(SplitSessionChips)・ドラッグ&ドロップ状態(useSplitDragDrop)・バー内メモ位置(useBarNotePosition)・セレクタ title(resolveSelectorTitle)を terminal-split-pane/ 配下の新モジュールへ移した。分岐・文言・DOM は変えていない。

  • refactor(ui): NewTaskDialog の表示部分を NewTaskDialogParts.tsx へ分け、複雑度を 43 から 25 未満に下げた (#3566): 一覧状態の通知・直近の宛先・リポジトリ/ブランチ/エージェントの選択・起動の通知・モデル欄・Auto-Yes 欄・失敗表示を別コンポーネントに切り出しただけで、振る舞い・文言・DOM は変えていない。

  • refactor(chat): splitChatThinking が節ごとの原文の行範囲 lineRanges を返し、chat-search-sections の目印差し込みと再実行をやめた (#3524): 検索用の思考節の範囲を、見出しになりえない行を目印に置き換えた原文を splitChatThinking に再度通して求めていた処理を、分割の走査(scanThinkingSections)がすでに持つ [start, end) を結果に出す形へ置き換えた。body / reasoning / blocks と chatSearchSections の結果は変えていない。

Fixed

  • fix(cli): 厳格 respond が agy の許可の確認でカーソル以外の選択肢を選べない問題を修正 (#3812): agy の許可の確認(antigravity-permission)は承認対象(approvalTarget)を footer まで読むため、選択肢の行と > の印がそこに入っており、Down のたびにトークンの束縛と画面の出現の指紋が変わって、キーの間の照合が「画面が動いた」(exit 41 screen_moved_mid_send)と判定していた(#3796 の実機 UAT 2026-10-19: No, cancel を送ると Down 1 回で止まり、再試行のたびにカーソルが 2 → 3 と進んで (Persist to settings.json) に残った)。束縛と指紋は、agy のリーダが選択肢と読んだ行(承認対象の末尾の番号つきの選択肢のまとまりで、ラベルが画面の選択肢と一致するもの)の > と字下げだけをそろえた承認対象で作り、カーソルの位置は selectedPosition だけが持つ。それより上の本文(コマンドや here-document の中の 1. item のような番号つきの行とその字下げ)は 1 文字も変えない。選択肢の文言・数・質問・承認を求めるコマンドが変わったときは従来どおり 41 で止まり、信頼確認(承認対象はフォルダ)の動きは変わらない。あわせて、Enter の前で止まった結果(delivery: 'partial')には、同じ画面の最後の読み取りで分かったカーソルの位置を highlightLeftAt({ position, label }、分からなければ null)として返し、message にも書く(CLI の利用ガイド ja/en の --json の項目にも追記。永続許可の選択肢に残ったことを、人が pane で Enter を押す前に分かるようにするため。キーを戻す操作は、止まった原因の画面へさらにキーを送ることになるので行わない)。

  • fix(ui): agy のターミナル面が黒く見える問題を修正 (#3809): ターミナル面は全 1000 行の capture を渡し、antigravity は自動追従が ON で空行の圧縮も対象外だったため、末尾の約 970 行の空行へスクロールして可視の文字が 0 になっていた(headless・有頭の Chromium のどちらでも再現)。LAYOUT_PADDING_COMPACTED_TOOLS(src/config/terminal-display-compaction.ts)に antigravity を加え、PC のターミナル面・/sessions のタイル・スマホが同じ設定を通って末尾の空行を落とす(表示のみ。状態検出・Auto-Yes・保存用の生の capture は不変)。claude / codex / opencode / copilot の表示は変えない。

  • fix(detection): agy のログイン方法の画面に Auto-Yes が番号で答えないようにする (#3808): agy 1.4 の未ログイン画面(Select login method: の下に > 1. Google OAuth / 2. Use a Google Cloud project、footer ↑/↓ Navigate · enter Select)は番号付きダイアログの構造判定を通り、agy の読み取り(readAntigravityNumberedDialog)が multiple_choice のプロンプトとして読んでいたため、状態判定は prompt_detected(approvalTarget にロゴの行まで入る)になり、同じ読み取りを使う Auto-Yes のポーラーが番号でログイン方法を選びえた(#3797 は観測の kind だけを unknown にしていた)。読み取りは footer 行が許可の画面の ↑/↓ Navigate · tab Amend のときだけ読むようにし(新しい ANTIGRAVITY_PERMISSION_FOOTER_PATTERN、#3797 と同じ条件)、観測(classify.ts)の個別の footer 判定はこれに寄せて削除。読み取りが断った番号付きの agy 画面は汎用の番号リスト解析にも渡さない(detectPromptOnCleanFrame と assessPromptAnswerability)ので、ポーラー・/prompt-response・状態判定(未分類のフレーム unknown_frame)のどれも答えない。狭い幅で footer が切れた・折り返した、または版違いで綴りが違う許可の画面も同じく答えない側に倒れる。footer が画面外にある・↑/↓ Nav で切れている画面も、footer に依らない判定(新しい isAntigravityUnvouchedNumberedScreen: 最下部が 1. からの 2 つ以上の連番の選択肢で、どれかに > が付き、その下に入力欄が無く、許可の読み取りが読まず、最後の選択肢の直下が /feedback の esc Cancel · 1-6 Select footer でもない)で汎用解析に戻さず、状態判定も unknown_frame にする。/feedback のメニューと、> の付かない返答の番号リストの読みは変わらず、生成中の画面(spinner・Generating)や番号で始まる送信文のエコーは確認画面と読まない。1.1.27 の許可の画面(コマンドの実行・ファイルの作成)への応答は変わらない。

  • fix(agent-health): codex の /model が起動完了前に拒否されると screen-picker を不合格にしていた問題を修正 (#3803): 日次確認は「変化の止まった最初の画面」を起動完了とみなして(waitForStartup)すぐに /model を 1 回だけ送っていたが、codex 0.163.0 は入力欄を描いた後もしばらく起動中で、そのとき /model に「Model selection is disabled until startup completes.」と返し選択画面を開かない(2026-10-19 のレポート)。scripts/agent-health/tool-table.ts の選択画面の定義に、起動中の拒否を示す notReady(codex は CODEX_PICKER_NOT_READY)を追加し、送信後にその文言が新たに出たら 3 秒おいて同じコマンドを送り直す(最初の送信から 45 秒まで。新しいモジュール src/lib/agent-health/picker-open.ts の sendUntilPickerOpens)。送るたびに Enter は 1 回だけで、選択画面が開いている可能性のあるあいだは閉じるキー以外を送らない(#3053 の約束は不変)。拒否が続いて開かなかったときは summary に「起動完了前として N 回拒否された」と出す。notReady を持たない claude の選択画面と、開いた・何も出なかったときの動きは従来どおり。

  • fix(cli): agy 1.4 の /model picker を antigravity-model-picker と読み、番号付きのログイン方法の画面を許可ダイアログと読まない (#3797): prompt --json の screen.kind が、agy 1.4 の /model picker(Switch Model と機種の行の間に Search: の入力欄と下線が入り、入力欄が picker の上に残る描画)で unknown になっていた(#3773 の UAT・#3788 の 3)。実機 agy 1.4.2 の画面(200x1000、私設 tmux)を tests/fixtures/prompt-observation-3797/ に採取し、src/lib/prompt-observation/antigravity-model-picker.ts で画面の形がすべてそろうときだけ picker と読む: 1 行のままの Keyboard: ↑/↓ Navigate ←/→ Effort enter Select esc Go Back|Clear search の footer が最後に描かれた行で(下は空行と右寄せの機種表示だけ)、その上 30 行以内に Switch Model→Search:→下線→機種の行(> はちょうど 1 つ)→Effort が順に並ぶこと。footer が折り返された画面・下に入力欄がある引用・強調が 0 か 2 の画面・検索で 0 件の画面は読まず、unknown のまま。1.1.27 の picker は従来どおり detection/tools/antigravity/screens.ts が読む。あわせて、未ログインの agy が出す番号付きの Select login method:(↑/↓ Navigate · enter Select)が antigravity-permission(厳格な respond が答える kind)と読まれていたのを、実測した許可ダイアログの footer(tab Amend)で終わる画面だけを antigravity-permission にして unknown に戻した。picker・slash・unknown は従来どおり厳格な respond が送る前に拒否する(答える範囲は広げていない)。ヘッドレスの Chromium で agy の端末が真っ黒になる件は headless 由来ではなく、上端寄せの 200x1000 ペインの末尾約 970 行の空行へ自動追従でスクロールするためで(有頭の Chromium でも同じ)、UI 側(src/config/terminal-display-compaction.ts)の扱いとして本変更には含めない。

  • fix(auto-yes): agy の送信が途中で切れた回の窓の超過・窓の後に固まった記録・セッション待ちの通知の遅れを直す (#3796): #3789 / #3791 の独立レビューで残った低指摘 3 件。いずれも誤答は起こさず、通知・表示が遅れる/古くなる側の問題で、どれも答える方向には倒していない(既存の陰性テストは不変)。1. 受信窓で受け付けた agy の確認への送信が noteAnswerSent の前に切れたとき(sendPromptAnswer の失敗、送信中に tick が世代を失った中断、Enter フォールバックの send-keys 失敗)、その確認は answered のまま無音で残り続けていた。切れた時点から猶予(18 秒)を数えるようにし、過ぎても確認が残っていれば no-hook-receipt として記録・ログする(hint は「送る途中で送信が止まり…キーの一部が入力に残っている可能性がある」)。送信を始める前の失敗(所有確認など)は従来どおり数えず、停止・再有効化の後に後継が新しい受信で受け付けた確認には、旧送信の帰還は中断でも成功でも猶予を付けない(送信開始時の受信区間だけを対象にする。通常送信・Enter フォールバックとも)。2. 窓の中で poller が記録した理由(partial-answer、契約の抑止など)は、窓が閉じて gated な読み取りが確認を見なくなると再刻印されず、cmate wait が 60 秒後に古いとして捨てていた(確認は画面に残ったまま)。受け付けた確認が残っている tick ごとに、その確認の初出以降の記録を再刻印し、同じ確認への no-hook-receipt より優先して残す。partial-answer(出来事の記憶)は確認が残る間ずっと再刻印し、契約由来の理由はその tick の現在の評価に従う(今の契約が抑止していれば、既存の記録が無くても・契約が外れて消した後に新しい契約が抑止しても、その理由を記録する。理由が変われば新しい理由を記録し、契約が外れれば記録を消す。残っている partial-answer はそれより優先する)。確認が消えれば再刻印は止まる。3. セッション待ち(MAX_BACKOFF_MS=60 秒)の poller は、待ちに入った直後にセッションが作られても次の tick まで答えず、push gate の保留もその tick+2 tick まで延びて人への通知が最大約 64 秒遅れた。createSession が作ったセッション名を lib/tmux/session-created-signal で知らせ、待っている poller は保留中の timer を差し替えて 1 poll 間隔後に tick する(timer 発火・別の timer・停止・置き換え後は何もしないので、二重の連鎖や停止後の再起動は生まない)。push gate の保留は、保留開始時に読んだ tick より早まった実際の tick(sessionWaitTickAt の前倒し、または sessionAppearedAt)に合わせて終わる(#3791 の値より後ろには延ばさない)。一度採用した早い時刻は hold 自身に保持し、前倒しした tick の capture 失敗・通常予約・再予約でも後ろへ戻らない(tick が走った時刻は sessionWaitTickRanAt に残す)。

  • fix(auto-yes): セッション待ちから戻る poller が答える流れの待機で、push gate が 9 秒で誤通知しないようにする (#3791): push gate の auto-yes-answering の保留(AUTO_YES_ANSWER_GRACE_MS=9 秒)は「次の tick は cooldown 以内」を前提にしていた。poller の tick 間隔はポーリング・cooldown・thinking(5 秒)なら 9 秒に収まるが、capture がセッション無しで失敗したときの待ち(MAX_BACKOFF_MS=60 秒)は収まらず、その間にセッションが立ち上がると(send --auto-yes はセッションより先に Auto-Yes を有効にする)次の tick で答える流れでも 9 秒で auto-yes-not-answered の通知が 1 回出ていた。poller はセッション待ちの tick だけ予定時刻を sessionWaitTickAt に記録し、gate は保留を始めたときにそれを一度だけ読んで、保留の終わりを「起点+9 秒」と「その tick+2 tick(AUTO_YES_ANSWER_AFTER_TICK_MS)」の遅い方にする(新設 autoYesHoldEndsAt())。エラーが続く backoff では延ばさない(Auto-Yes が壊れているときは従来どおり 9 秒で通知する)。記録した tick は、それを予定した poller が動いている間だけ数える(停止・off→on で新しい poller になれば捨て、on のままの再 enable では残す)。waiting-push-notifier の再判定タイマーは 9 秒ごとに聞き直すので、保留中に poller が止まっても来ない tick を待ち続けない。Claude の確認画面の取りこぼし形(#3770 の残り)は、隔離した実機の画面を資格情報を複製せずに採取できないため未対応。

  • fix(cli-tools): 起動処理が答えた信頼確認に Auto-Yes が窓の後で答え直さず、copilot の信頼確認も起動処理に任せる (#3790): #3776 の残りの指摘。gemini の起動処理が待ちの最後の試行で信頼確認に Enter を押すと、gemini が再起動の行を描く前に起動の窓が閉じ、poller が答えていない確認と読んで 1+Enter を重ねうる経路が 5 秒(GEMINI_TRUST_SETTLE_MS)より遅い再起動で残っていた。起動処理は押したキーを recordLaunchDialogPress で記録し、poller はその後 LAUNCH_DIALOG_PRESS_SETTLE_MS=30 秒は窓の有無にかかわらず確認を起動処理のものとして答えない(それを過ぎても描かれたままの確認は、キーが届かなかったものとして従来どおり安全な行のときだけ答える。次の起動で記録は消える)。copilot の「Confirm folder trust」は起動処理が 1 だけで答える一方、poller も 1+Enter を答えており、起動処理の 1 が先に届くと poller の 1 が入力欄に入ってユーザーの発言として送られることを実機で確かめた(copilot 1.0.91、私設 tmux・一時 HOME)。copilot の起動も gemini と同じ窓を開き、poller は窓の中・押した直後は agent-launch-dialog(dialog=copilot-folder-trust)で抑止し、窓の後は既定が 1. Yes(このセッションだけの信頼)の宣言どおりの並びのときだけ答える(2. は全 checkout 共通の ~/.copilot/config.json に書くので答えない)。claude の信頼確認(2.1.302、番号なし)は poller がそもそもプロンプトと読まないので変更なし。gemini の信頼確認への poller の 1+Enter は実 gemini 0.64.0 で 1 だけで確定し、Enter は再起動中に消えることを確かめた(変更なし)。隔離手順(docs/user-guide/uat-isolation.md)に、gemini の自動更新を止める設定(general.enableAutoUpdate / enableAutoUpdateNotification を false)と npm_config_prefix の逃げ道を実測つきで追記した。

  • fix(auto-yes): agy で答えた確認が画面に残り続けると Auto-Yes が理由を残さず黙る問題を修正 (#3789): #3767 の受信窓の判定は、答えた確認が窓(8 秒)の後も画面に残る tick を answered として記録もログもせずに見送っており、答えが届かなかったときや、受信の無い別の確認がそれと入れ替わったとき(#3767 のレビューの L2)に、セッションが lastSuppression null・ログ 0 件のまま止まっていた。Auto-Yes が受理した確認へ実際にキーを送ってから ANTIGRAVITY_ANSWERED_DIALOG_GRACE_MS(窓 8 秒+観測の猶予 5 秒+応答後の cooldown 5 秒=18 秒)を過ぎても確認が残っているときを新しい判定 outlived とし、既存の理由語 no-hook-receipt で lastSuppression に記録して、antigravity-autoyes-withheld-no-hook-receipt に「キーを送ってから 18 秒を過ぎても確認が画面に残っている(閉じなかったか、受信の無い別の確認と入れ替わった)」hint を出す。猶予の起点は送信が tmux に届いた時点で、受理しても送らなかった経路(他サーバのセッション・途中まで入った入力・基本ルールや契約が答えない など)は outlived にせず、その tick が記録した本当の理由を上書きしない。答えは足さない(もう一度は答えない)ので誤承認の側には動かず、猶予の内は従来どおり黙る。cmate wait の no-hook-receipt の文言とガイド(ja/en)の場合分けもこの状態を含む形に揃えた。読み取りの空白で episode を捨てる件(旧判定と同じ)と、答えた確認が新しい受信を使って入れ替わった確認が止まる件(L2)は、直すと #3767 の既存の対照(停止後の再開・置き換わりへの応答)と食い違うか誤承認の側に倒れるため直さず、テストで現状を固定した。持ち越しの 7 秒の閾値は、本番ログの実測(応答の間隔の上乗せは最大 0.63 秒、読み取りの間隔は最大約 5.6 秒)で余裕 1.37 秒以上を確かめて据え置いた。

  • fix(cli): 厳格な respond で信頼確認に No, exit と答えると exit 41 outcome_indeterminate になる問題を修正 (#3788): agy は No, exit で終了するが、tmux のセッションはシェルを残して生き続けるため、Enter 後の観測は running:true・kind:unknown になり、session-ended を「観測が unavailable か画面が無い」ときだけにしていた判定が indeterminate と読んでいた(#3773 の実機 UAT)。送った答えがその画面の「終了を選ぶ答え」(kinds.ts の EXITING_STRICT_ANSWERS。位置・選択肢の数・その位置のラベルの 3 つがすべて合うときだけ。信頼確認は 2 択の 2 番目が No, exit。並びが逆・選択肢が 3 つ・文言違いの画面では当てはまらず 41)で、かつ Enter 後の読み取りが unknown のときだけペインの前景コマンド(#{pane_current_command})を引き、シェル(zsh / bash / -zsh など、#3089 の isShellPaneCommand)に戻っていれば session-ended(exit 0)にする。前景は #1922 の gateway である cli-session の新しい readSessionPaneCommand() で読み、createStrictRespondDeps が StrictRespondDeps.paneCommand として渡す。Yes や permission の答えのあとにシェルが前景に戻っても(agy が別の理由で落ちた)、前景が agy のまま・取得できない(null・例外)・画面が読めなかった・同じ画面のまま、はどれも従来どおり exit 41(迷ったら 41 の安全側は不変)。agy 1.4.1 の実機で、起動中は agy、終了後は zsh になることを私設 tmux(-L)・一時 HOME で確かめ、終了後のペインを fixture にした。

  • fix(cli-tools): gemini と agy の起動時の信頼確認を、カーソルが安全な行にあるときだけ Enter で通し、Auto-Yes と二重に答えない (#3776): 起動時の確認を宣言(質問文・押してよい行・カーソル行・キー)と純関数 decideLaunchDialog で読む共通部品 src/lib/cli-tools/launch-dialog.ts を追加し、gemini と antigravity の waitForReady(agy は #3757 の「次の send が借りの Enter を送る」経路も)をこれに乗せた。以前は質問文が画面にあるだけで Enter を送っていたので、カーソルが gemini の 3. Don't trust(trustedFolders.json に永続)や agy の No, exit(agy が終了)にあってもそれを確定していた。安全な行(gemini は 1. Trust folder、agy は Yes, I trust this folder)以外では送らず launch-dialog-held を、文言や描画が宣言と合わないときは launch-dialog-unrecognized を warn に残す(画面本文はログに出さない)。実機測定(gemini v0.62.0・私設 tmux socket・一時 HOME、2026-10-17)で、Auto-Yes の poller がこの確認を既定(=カーソル行)の multiple choice と読んで 1(カーソルが Don't trust なら 3)を答えていたと分かったため、codex の起動ダイアログと同じ agent-launch-dialog で抑止する。抑止は起動処理の待ちの間(新しい src/lib/cli-tools/launch-window.ts の窓)だけで、窓の後に残った確認(Enter が届かなかった・自動更新などで遅れて出た)はカーソルが安全な行にあれば poller が既定を答える(文言や描画が宣言と合わない画面でも、選択肢が信頼確認のものなら、既定が 1. Trust folder でない限り答えない。窓は Enter の後 5 秒は閉じない)。確認とみなすのは質問文の下に宣言した選択肢の行が並ぶ画面だけで、答える prompt がその選択肢であるときに限る(返答中の引用や diff、別の許可画面は抑止しない)(信頼の確認の Enter の後の gemini は自分で再起動し、未サインインの HOME では認証方式の選択が出るので、二重の Enter はそこで既定を確定しうる)。あわせて forgetAutoYesHold(key, since) が別 episode の Auto-Yes hold を消さないようにした(waiting-push-notifier の hold 解除が次の episode の hold まで消し、通知が最大 1 hold 分遅れていた)。

  • fix(send): Command Code の plan review による送信拒否を lastSend で not-sent (plan-review) と読む/信頼の確認の行を webapp-guide に追記 (#3772): 本文を打つ前の拒否が文言で判別されず indeterminate(send-error) になっていたため、エラーに code(PLAN_REVIEW_SEND_REFUSED)を付け classifySendFailure が code で判別する。webapp-guide(ja・en)のダイアログカード表に「信頼の確認(agy)」の行を追加。timer-manager / relay-delivery の失敗理由には send state を付けない(既存テストが文言を固定。lastSend で読める)。

  • fix(cli): 厳格な respond と観測トークンの残りの指摘(#3755・#3756 のレビューの続き)を修正 (#3771): ①引用された信頼確認を trust と読まない — ↑/↓ Navigate の行の下に生成中の出力が続くフレームは unknown 側(下に空行と右寄せのモデル状態行しか無い実フレームは従来どおり trust)。②strict の経路で prompt-observation が投げた例外は 40 observation_failed(画面の本文を含まない)で受け、ログに出す例外は名前・code・message 先頭 80 文字までにする。③answerStrictly が途中で例外を投げても、実際に送った keysSent を結果に使い、キーを 1 つでも送っていれば task 履歴に prompt_answered_human を残す。TaskEventPayload に任意の answeredBy と option を足した(後方互換、マイグレーション無し)。④信頼確認の screen.answerVia を terminal から strict(respond --expect-screen で答えられる)に変更(permission 画面・picker・slash は不変)。⑤agy の起動窓が信頼確認へ Enter を送る直前に strict の hold を見て、hold 中は送らない(残余競合を狭めた)。⑥exit 41 でも operation_in_progress / operation_id_conflict は何も送っていない(keysSent 0)ことを docs(ja/en)に明記。

  • fix(auto-yes): push gate の Auto-Yes hold を off/episode 終了/1 時間で捨て、agy の起動窓を参照カウント化し、gemini の信頼確認の文言違いを直す (#3770): autoYesHolds の起点が Auto-Yes を off→on にしても古いまま残り on 直後に auto-yes-not-answered の誤通知が 1 回出る問題を、auto-yes-inactive を返したとき・episode が閉じたとき・enabledAt が起点より新しいときの起点更新と、1 時間経った hold の掃除で解消。beginStartupWindow/endStartupWindow は参照カウントを持ち、並行 relaunch で先の finally が後の窓を閉じない。gemini は隔離環境で実測(v0.62.0 は起動約 2 秒で Do you trust the files in this folder? を出す=30 回の窓は十分)し、窓切れではなく文言違い(旧 Do you trust this folder? のみ照合)で信頼確認が一度も処理されないことを直し、capture の例外を debug ログに残す。Claude の確認画面の述語は実機の画面が無いため緩めず、取りこぼす合成 fixture が現状 false であることを固定した。

  • fix(auto-yes): agy の確認が hook 受信の窓(8 秒)を 5 秒周期の tick にまたがれて答えられなくなる問題を修正 (#3767): agy は hook に応答してから数秒後に確認を描くため、poller の次の tick が受信から 8.7 秒後(窓の外)になり、以後は新しい受信が無く約 4 時間答えなかった(server.log 実測: 最初の withheld はすべて直前の受信から 8.1〜9.6 秒後)。受信窓の判定を「tick の時刻」から「確認が最初に画面に出た時刻(直前に確認が無かった tick と受信の遅い方)」に変更。受信より前から出ていた確認・窓より後に出た確認・受信が無いとき・使い済みの受信には従来どおり答えない(受信は 1 つの確認にだけ使う)。止めたときは新しい理由 no-hook-receipt を lastSuppression・wait・prompt --json に載せ、ログの hint を「一度も受信が無い」「受信から N 秒後に出た(窓外)」「受信が途絶えた」「受信より前から出ていた」で区別する。利用ガイド(ja/en)に理由語の行と止まったときの対処を追記。

  • fix(ui): agy の信頼の確認(番号の付かない選択)で、UI が「端末で操作が必要(信頼の確認)」と表示するように (#3758): agy の Do you trust the contents of this project? は番号が付かないため isAntigravityNumberedDialog の対象外で、検出は waiting / antigravity_selection_list(isSelectionListActive)、prompt にはならない(PromptPanel・スマホのシートは出ない)。Auto-Yes もこの画面には答えない(#3757)のに、チャット面は汎用の「選択リストが開いています」の帯、ターミナル面(PC の分割ペインのフッタ・スマホのドック・/sessions のタイル)は矢印のパッドだけで、エージェントが答えを待って止まっていることをどの面も言わなかった。selection-shape に isTrustConfirmationFrame(frame, cliToolId)(検出器の isAntigravityTrustScreen を検出器と同じ末尾 15 行に当てるだけ。新しい検出の写しではない)を足し、チャット面の帯をその画面のときだけ chatSurface.reasonTrustConfirmation に替え(data-reason は selectionList のまま、カードと矢印は不変)、ターミナル面は SelectionListKeys がパッドの上に trustConfirmation.notice を出す。フレームを持たないスマホのドックは、controller の poll が同じ応答のフレームから isTrustConfirmationActive を読んで渡す。画面から答えを送る新しいボタンは作らない(承認の送信は #3756)。番号付きの prompt と、Switch Model など他の選択リストの表示は変わらない。本物の buildCurrentOutput に実フレーム(antigravity-live-2364/trust-dialog.txt)を通した応答を、本物の親(PC は TerminalSplitPaneContent、スマホは WorktreeDetailRefactored)から描いて固定した。

  • fix(antigravity): 起動の窓より後に出た信頼の確認を一度だけ処理し、auto-yes が答えない画面で通知を抑え続けない (#3757): agy の起動は 1〜60 秒超でばらつき(隔離 tmux 実測で信頼の確認が起動の 61 秒後に出た)、waitForReady の約 34 秒の窓が先に終わると確認は誰にも処理されなかった。窓が Enter を送らずに終わったときは「Enter を 1 回借りている」と記録し、次の send の waitForPrompt が確認を見たら一度だけ Enter を送る(記録は同期的に取り出すので、同じペインを待つ 2 つの send でも Enter は 1 回。窓で送った場合は記録しない。待ちはこの間だけ 60 秒)。時間切れの log antigravity-prompt-detection-timeout に画面の状態(screen と末尾数行)・reason: startup-trust-unhandled・capture の例外の回数と最後の例外を出し、その後の prompt not ready は信頼確認が未処理であることをエラー文に書く。auto-yes の poller は信頼確認を番号なしのためプロンプトと読めず(detectPromptOnCleanFrame が isPrompt: false → dialog gate にも Enter fallback にも届かない)何も記録していなかったので、起動の窓の外ではこの画面に理由 startup-trust-unhandled(AutoYesSuppressionReason に追加、cmate wait の文言つき)を記録して通知させる(窓の中で処理される確認は従来どおり無音)。push gate は記録が無いときの auto-yes-answering を AUTO_YES_ANSWER_GRACE_MS(cooldown+2 tick = 9 秒)に限り、過ぎた待ちは auto-yes-not-answered で通知する。猶予の起点は episode の since(hook 由来で古いことがあり、連続する prompt の間は凍結される)ではなく、gate がその episode を最初に抑えた時刻と poller の最後の応答時刻の遅い方。waiting-push-notifier は保留が終わる時点で gate に問い直す(問い直しで再び抑えたときのログは debug)。

  • fix(auto-yes): 再起動の解除表示の取り残し 4 点を修正 (#3753): ID 移送の待ち中に OFF → shutdown 記録 → 移送の順で、停止記録に旧 ID が残り次回起動で「再起動で解除」が戻る問題を、移送後に記録を書き直して直した。getAutoYesState が無効な状態にも期限判定をかけ、手動 OFF や状態の無い key への OFF が stopReason: 'expired' に書き換わる問題を、有効な許可の期限切れだけに限って直した。GET /api/worktrees/:id/auto-yes の instances が instanceId だけで畳まれ、ツールの違う同 instanceId の 2 key が後勝ちになる問題を、roster のツールの key を採用して直した。webapp-guide(ja・en)に「再起動で解除」の表示と消える条件を追記。

  • fix(auto-yes): AskUserQuestion の確認画面(Submit / Cancel)に、ブラウザ側の Auto-Yes と Claude の確認画面でも答えない (#3748): #3497 はサーバーの poller だけで Command Code の確認画面(❯ 1. Submit / 2. Cancel)への応答を止めており、サーバーの poller が動いていないときはブラウザ側の useAutoYes が画面を見ずに 1 を送っていた。また Claude の確認画面(Ready to submit your answers? / ❯ 1. Submit answers / 2. Cancel)には、サーバー側でも 1 を送っていた。判定を共有の述語 isAutoYesDeclinedConfirmPage(src/lib/polling/auto-yes-confirm-page.ts。Command Code は isCommandCodeQuestionConfirmPage、Claude は新設の isClaudeAskUserQuestionConfirmPage で、転写の末尾が 1. Submit answers / 2. Cancel(カーソル付き)・その直上の行そのものが Ready to submit your answers?・さらに上に ✔ Submit のタブ行、の 3 条件をすべて満たすときだけ真。文言を含む Bash / Edit の許可や質問は確認画面と読まない)にまとめ、サーバーの poller(抑止の手順 3.3)がこれを通る。この述語はサーバー専用(logger → fs に届く)なので、ブラウザは client 安全な isConfirmPageShapedPrompt(src/lib/polling/auto-yes-confirm-shape.ts)で、選択肢がちょうど Submit(Submit answers)/ Cancel の prompt には画面に関わらず答えない。抑止の理由は確認画面専用の question-confirm-page を新設し(これまでは unclassified-frame を流用しており、cmate wait の文言が「どの検出器も画面を認識しなかった」と実際と逆を言っていた)、cmate wait は「Submit/Cancel の確認は今の回答をそのまま確定するので人に返す」と表示する。poller:response-sent のログに promptType・質問の本文(500 文字で切る)・送った答え(番号・y・[Enter])を追加。Claude の通常の番号付き許可(Yes/No)と yes/no の質問には従来どおり答える。

  • fix(schedule): 同期中に止めた worktree / 全体の cron が readCmateFile の完了後に作り直される問題を修正 (#3735): syncSchedules は readCmateFile の await の後、manager の状態も停止も確かめずに cron を作成・差し替えていたため、stopScheduleForWorktree(リポジトリ削除・セッション後始末)や stopAllSchedules(shutdown)の直後に同期が戻ると、削除済み worktree の cron や shutdown 後の cron が生き返り、次の同期(60 秒)で stale として消えるまでにジョブが発火しうる状態だった。ManagerState に manager の世代(stopAllSchedules で加算)と worktree ごとの停止回数(stopScheduleForWorktree で加算)を持たせ、同期の開始時に読み、await の後に同期的に比較する。全体停止があれば同期を打ち切り(部分的な activeScheduleIds で再起動後の schedule を stale 判定しないため)、その worktree が止められていれば飛ばす(activeScheduleIds にも足さず、mtime キャッシュも捨てて次の同期で拾い直す)。開始済みジョブの実行・記録と DB の upsert は止めない。initialized=false 中の syncSchedulesNow を no-op にする案は、世代判定で足りたため採らなかった。

  • fix(tmux): 送信の直後の capture が、送信の前に始まった capture の古い画面を受け取る・キャッシュに書き戻す問題を修正 (#3734): invalidateCache() は cache だけを消して inflight を残していたため、無効化の後に来た呼び手も無効化の前に始まった fetch に合流して送信前の画面を受け取り、その fetch が #2297 の 250 ms 後の再無効化より遅く返ると送信前の画面が CACHE_TTL_MS(5 秒)残っていた。capture cache に世代(session ごと+全体)を持たせ、invalidateCache() は session の世代を進めて該当の inflight も消し、clearAllCache() は全体の世代を進める。getOrFetchCapture() と captureSessionOutputFresh() は始めた時点の世代を控え、await の後に世代が変わっていれば書き戻さない(呼び手自身には結果を返す)。getOrFetchCapture() の finally は自分の promise のときだけ inflight を消すので、clearAllCache() の後に始まった新しい fetch の singleflight が外れない。無効化が無いときの動き(同時の呼び手が 1 回の fetch を共有し結果をキャッシュする)は変わらない。

  • fix(ws): 待ちの間に要らなくなった terminal 購読と OpenCode V2 の model の latch を捨てる (#3733): handleTerminalSubscribe は旧購読の解除・checkSessionOwnership・transport.subscribe を await した後、まだ求められているかを見ずに購読を登録していたため、待ちの間に切断されると registry に subscriber が残り(1 session 4 本の上限まで漏れると server の再起動まで「Terminal subscriber limit reached」)、待ちの間の terminal_unsubscribe は効かず、同じ socket の 2 回目の subscribe は後に返った方が先を上書きして先を解除しなかった。ClientInfo.terminalSubscriptionSeq を subscribe の開始・unsubscribe・切断で進め、各 await の後に見比べて、古くなっていれば得た購読をすぐ解除して何も送らず返る。onError の fallback(snapshot と terminal_error)、onOutput・onExit も送る直前に現役かを見る。入力・resize は直さない(受け取った時点の購読へ送るのが意図どおりで、所有権は #3334 の確認が守る)。動くのは TMUX_CONTROL_MODE_ENABLED=true のときだけ。OpenCode V2 では syncOpencodeV2History / recordOpencodeV2TurnEnd に isCurrent を足し(usage と同じ形)、読み取りの間に購読が閉じた・置き換わったときは model の latch(と #2357 の変化の broadcast・Push)だけを飛ばす。閉じた turn の行は従来どおり保存する(requestId で重複しない)。

  • fix(relay): 取り消しが成功を返した relay の返答・通知が依頼元に送られる問題を修正 (#3732): deliverOne は待機判定(findRelayHoldReason)を await した後も一覧取得時点の本文をそのまま sendUserMessage していたため、その間(または前の relay の送信中)に relays cancel が 200 を返した relay の返答・prompt 通知が composer に入り、一覧の後に期限切れ掃除で保留が expired 通知に置き換わった場合も古い返答を送ったうえで期限切れ通知を消していた。送信直前に同期的に行を読み直し、保留の kind と本文が一覧時点と同じで(期限切れ通知以外は)relay が open のときだけ進み、同じ区間で「確保」(globalThis の集合、src/lib/relay/relay-delivery-claims.ts)を取る。確保中の取り消しは withdrawRelay が { ok: false, reason: 'delivering' }、POST /api/relays/:id/cancel が 409 RELAY_DELIVERING で答え(配達はそのまま完了して delivered になる)、commandmate relays cancel はこれをエラーとして exit 2 で表示する。送信が prompt_waiting・失敗・例外で終わったとき、および prompt 通知の配達後は確保を外すので以後の取り消しは成功する。確保中の relay は期限切れ掃除も見送る(返答と期限切れ通知の二重送信と、通知の消去を防ぐ)。期限切れ通知の配達は取り消しの有無にかかわらず従来どおり。

  • fix(cli): start --daemon で起動したサーバーの出力を logs/server.log に残す (#3731): DaemonManager.start() が npm run start を stdio: 'ignore' で spawn していたため、start --daemon とそれで再起動する commandmate update のサーバーは stdout / stderr がすべて /dev/null に捨てられ、docs が案内する tail -f ~/.commandmate/logs/server.log のファイルは存在しなかった。以後は設定ディレクトリの logs/server.log(--issue <N> の並列サーバーは logs/server-<N>.log。新設の getServerLogPath())を O_APPEND | O_NOFOLLOW・0600 で開いて子の stdout / stderr に渡し(親は spawn 後に自分の記述子を閉じる)、起動ごとに区切り行を書く。起動時に 10MB 以上なら scripts/build-and-start.sh と同じく .1〜.3 の 3 世代へ回す(rotateServerLog()。回した各世代も 0600 にし、scripts/start.sh の 0640 のファイルを回しても権限が緩まない。シンボリックリンクは回さず辿らない)。ログを開けないときは警告して従来どおり出力を捨て、サーバーの起動は止めない。commandmate status は稼働中と PID ファイルが古いままのサーバーに Log: 行でパスを出す(--all 側も同じ)。CM_LOG_DIR は使わない — サーバー自身が .env から読む会話ログの置き場所であり、このパスは spawn の前と status から .env 無しに決まる必要があるため。

  • fix(auto-yes): サーバーの再起動で外れた Auto-Yes を「再起動で解除」と表示し、poller:stopped に止まった理由を出す (#3730): 再起動で Auto-Yes の許可は全部オフに戻るが、そのことがどこにも出ず、もともとオフだった instance と区別がつかなかった(orchestrate の worker が /rebuild の後に許可ダイアログで黙って止まる)。許可は自動では戻さないまま、正常停止(SIGINT / SIGTERM)のときに「その時点で有効かつ期限内だった許可」(poller が未起動のものを含む)を DB ごとのファイル <CM_DB_PATH>.auto-yes-shutdown.json(一時ファイル→rename、0600。cm.db と cm-N.db は別ファイル)に記録し、次の起動で両方の listen より前に読み込んで、期限内のものを enabled: false・stopReason: 'server-restart' の状態として入れる(poller は起動しない)。表示は元の許可の期限まで続き、ON / OFF の操作で消え、期限内なら 2 回目の再起動でも残る。見える場所: capture --json の autoYes.stopReason、GET /api/worktrees/:id/auto-yes の stopReason(1 件と instances の各項目)、GET /api/worktrees の行の autoYesStopReasonByInstance(autoYesByInstance は武装中だけのまま)、ls の AUTO_YES off(restart)、instances の AUTO_YES no(restart)(--json は autoYesStopReason)、wait の人待ち(exit 10 と --on-prompt human)の stderr の 1 行(再有効化のコマンドは待機対象の --instance 付き。付けないと既定の primary が有効になる)、PC の分割・スマホ・Sessions のタイルのトグル横の「再起動で解除」(en: Released by restart。PC は通知がある間も全インスタンスの状態を読み直すので、alias の表示も期限で消える)。exit 10 の条件・Push・permission hook の判定は変えていない。記録が書けない・読めない・壊れている・消せないときは従来どおり全部オフ・理由なしで起動を続け、SIGKILL やクラッシュでは何も記録されない。停止が始まった後に ownership の待ちから戻った有効化は 503 で断り、記録の後に受け付けた OFF・セッション停止・instance 削除は記録を書き直して取り下げる(次の起動で消した表示が戻らない)。起動時の worktree ID の移動は、移動先に既にある状態(復元の後の ON / OFF)を復元した通知で上書きしない。あわせて stopAutoYesPolling(compositeKey, reason) にし、poller:stopped が全経路で reason を出す(manual / expired / not-enabled / invalid-key / stop-pattern / consecutive-errors / session-killed / instance-removed / worktree-deleted / orphan-swept / shutdown / worktree-id-migrated / replaced / reset)。poller の停止理由は許可を外さない(ID の移動と置き換えでは許可はそのまま)。

  • fix(agent-health): ツールの利用上限(429)に達した画面の check を不具合として起票せず、usage-limit の skip にする (#3729): 2026-10-16 の command-code 1.81.0 は You've reached your weekly usage limit. Resets in 1d 17h(裏で POST /alpha/generate → 429)でプロンプトを処理せず、hook-correlation・screen-running・screen-approval の 3 本(#3717〜#3719)が CommandMate の不具合として自動起票されていた。AgentHealthSkipKind に環境要因の種別 usage-limit(要約での呼び方「利用上限に達している」)を足し、新設の src/lib/agent-health/usage-limit.ts の judgeUsageLimit で、fail になる判定のうち判定した画面(hook-correlation はそのツールのセッションで見た画面)に上限の表示があるものだけを skip にする(判定していればの summary は skipReason、画面の末尾は evidence に残す。pass はそのまま)。上限の文言はツールごとの行の表 USAGE_LIMIT_SIGNATURES(command-code の実測 2 行と claude・codex の文言)で、「上限に達した」ことを言う文言だけを見る — usage limit・429・Resets in 単独や、claude の「up to 50% of your weekly usage limit」・codex の「You have 1 usage limit reset available」は上限と読まない。skip は coverage.counts.skipByKind と要約の 1 行目に数えるので、上限が続いても検出の空洞化が見える。

  • fix(auto-yes): 契約が止めた agy のダイアログを hooks.json の向き先のせいにしない (#3724): 契約の autoYes(mode: off など)が antigravity の許可ダイアログへの応答を止めると、サーバーは ask と答えるのでダイアログは画面に残り、hook の受信記録の窓(8 秒)を過ぎた後は毎分 antigravity-autoyes-withheld-no-hook-receipt(「~/.gemini/config/hooks.json の向き先(#2622)を確認」)が出ていた(実例: 477 件。hook は届いており原因は契約)。logIfWithheldForWantOfReceipt は案内を出す前に、関門なしで読んだ promptData を poller 本来の判定 resolveAutoAnswerWithPolicy(基本ルール → 契約の順)に通し、契約が抑止した場合だけ別名の info ログ antigravity-autoyes-withheld-by-policy(worktreeId / instanceId / reason(mode-off / deny-pattern / type-not-allowed など)/ mode / taskId / hint: 契約の autoYes を確認)を出す。基本ルールが答えない確認(checkbox・入力を要する選択肢)と、契約が無い・答えを許している場合は従来どおり hooks.json の行。毎分 1 回の間引き(WITHHELD_NO_RECEIPT_LOG_INTERVAL_MS)は両方の行で同じ記録を共有する。reason/mode と taskId を同じ task から取るため、auto-yes-policy.ts に 1 回の引き当てで両方を返す getSessionAutoYesPolicyEntry() を追加(getSessionAutoYesPolicy と同じ TTL キャッシュ。既存の呼び出し元は不変)。lastSuppression は変えない。

  • fix(auto-yes): 契約が autoYes.mode: off で止めている instance を、有効化の時点と ls・instances・UI で見えるようにする (#3723): 契約が mode: off の instance で Auto-Yes を有効にしても一度も答えないのに、auto-yes --enable / send --auto-yes は「enabled」と出して exit 0、ls は残り時間、instances は yes、UI は ON を表示し、理由は capture --json の autoYes.lastSuppression にしか出なかった(CommandAgent #680 で antigravity が許可ダイアログのまま 11 時間停止)。判定は poller と同じ getSessionAutoYesPolicy を通し、blocked は mode === 'off' のときだけ。POST /api/worktrees/:id/auto-yes(有効化)と GET …/auto-yes?cliToolId= の応答に contractPolicy({ taskId, mode, allowPromptTypes, denyPatternCount } か null)、GET /api/worktrees の autoYesByInstance[instance] と GET …/auto-yes の instances に省略可の blockedByContract: true を追加。CLI は mode: off で stderr に Warning:(exit 0 のまま、send は有効化の応答を読むようになった。send --contract … --auto-yes は task がまだ pending でサーバーの contractPolicy が null なので、送る契約の autoYes で判定する)、safe / allow-listed は答える種類を Note: の 1 行で添える。ls / instances の AUTO_YES 列は blocked、instances --json は該当行にだけ autoYesBlockedByContract: true。ワークツリー詳細の Auto Yes トグルは ON のまま「契約で停止中(mode: off)」を表示し(ja / en)、いずれかの instance で Auto-Yes が有効なあいだは 12 秒ごとに Auto-Yes の状態を取り直して、ページを開いたまま契約が始まる・終わるのに追従する。契約が無い・mode が null・safe・allow-listed の instance の表示と JSON は変更前と同じ。

  • fix(auto-yes): Command Code の AskUserQuestion 確認画面(❯ 1. Submit / 2. Cancel)に Auto-Yes が 1 で答えない (#3497): 自由記述の質問でワーカー自身の提案が回答候補に入った確認画面に、Auto-Yes が Submit で答え、ワーカーがその提案を人の承認として実装し始めた(Kewton/Musunest#283)。Review ページの読み取り(#2755)は ← to go back and edit 行まで揃ったときだけ画面を断っており、その行の無いフレームは汎用の番号リスト解析が Submit を既定とする 2 択の multiple_choice として読み、選択肢が Submit / Cancel だけの自由記述の質問は質問リーダー自身が同じ形で読んでいた。新しい isCommandCodeQuestionConfirmPage(raw)(最後の罫線の直下にタブ帯があり、1. Submit と 2. Cancel の両方がある)を Auto-Yes の抑止 3.3 として判定し、該当すれば鍵を送らず人に返す。既定で外し、設定は無い。抑止は既存の unclassified-frame として記録され(capture --json の autoYes.lastSuppression / cmate wait が理由を出せる)、WARN poller:auto-yes-skipped-question-confirm に質問の本文(500 文字まで)と選択肢を残す。状態判定・回答パネル・履歴は変えないので人には今までどおり質問が見える。通常の yes/no と選択のプロンプト(#2521 / #2754 の質問画面を含む)への自動応答は変わらない。

Documentation

  • docs(docs): CLI 利用ガイドの no-hook-receipt (6) に送信中断時の説明を追加 (#3810): agy の確認への送信が途中で切れた場合(#3796)も、切れた時点から猶予(18 秒)を数えて過ぎても確認が残れば no-hook-receipt として記録される旨を、日英の CLI 利用ガイドの no-hook-receipt 理由一覧の (6) に追記(入力欄にキーの一部が残る可能性と、中断後に partial-answer が記録された場合はその理由を保ち no-hook-receipt としない例外を含む)。

  • docs(auto-yes): 利用ガイドの lastSuppression に、載っていなかった理由語 4 つを追記 (#3721): agent-launch-dialog / unclassified-frame / startup-trust-unhandled / question-confirm-page の意味と cmate wait の表示の文言を、ja・en の cli-operations-guide.md に表で足し、型 AutoYesSuppressionReason の 9 語すべてをガイドで引けるようにした。mode が null になることと、prompt --json の pollerRecord / contractEvaluation が同じ語を使うことも書いた。コードの変更は無い。