Skip to content

v0.34.1

Choose a tag to compare

@Kewton Kewton released this 12 Sep 08:12
· 23 commits to main since this release
cfda271

[0.34.1] - 2026-09-12

Highlight: 並列オーケストレーション 1 回分(7 Issue)をまとめたパッチ。/rebuild が本番サーバを落としたまま残す競合(#2488)と、unit テストが本番の opencode ポート台帳を書き換える汚染(#2490)という運用を直接壊していた 2 件を潰し、remote 実行中に PC からログインできず併用できなかった制約に --auth remote-only という出口を用意した(#2489)。公開面(public-messaging / README / LP)は「orchestrate」を看板にした軸へ揃えた(#2493 / #2494 / #2495)。

Added

  • feat(remote): commandmate remote --auth <all|remote-only> を追加し、remote 実行中も PC ローカルからはログイン無しで使えるようにした (#2489): 既定の all は現行どおり全経路で認証する。remote-only は サーバに 127.0.0.1 の 2 本目の listener(CM_REMOTE_INGRESS_PORT)を立てて Provider をそちらだけに向け、ユーザ自身が使う既存ポートの listener だけを認証免除する。判定は「どの listener に着いたか」だけで行い、送信元 IP・Host・X-Forwarded-* では行わない — Tailscale Serve も Cloudflare Quick Tunnel も upstream が http://127.0.0.1:<port> なので、tunnel 経由のリクエストも req.socket.remoteAddress は 127.0.0.1 で、loopback を免除すると公開 URL が丸ごと無認証になる(Host は Provider が書き換える実測あり: docs/qa/1937-remote-uat-record.md D-2)。server.ts は両 listener で内部ヘッダ x-cm-ingress をクライアント値を捨てて上書きし、src/middleware.ts(Edge / HTTP)と src/lib/ws-server.ts(Node / WS upgrade)はその値だけを読む。免除の判定は全段 fail-closed(scope が remote-only 以外・stamp が local 以外・未 stamp・2 本目の listener が開けなかった場合はすべて認証する)で、CM_ALLOWED_IPS の IP 制限は免除に含めない。ガードは 2 重で、CLI は --auth の不正値と CM_BIND が loopback 以外(.env 適用後の実効値を loadEffectiveEnv() で読む)を exit 2 で拒否し、サーバ側も同条件を再検査して process.env.CM_AUTH_SCOPE を all に書き戻す(局所変数に留めると middleware/ws-server が毎リクエスト remote-only を読み続ける)。remote status はテキストに Auth scope: 行(免除されるポートと認証されるポートの両方を名指す)、--json に .remote.authScope と .remote.server.remoteIngressPort を出す。gracefulShutdown は両 listener を閉じ、process.exit(0) は両方が drain し切るまで待つ(従来 server.close() だけだったため、local の接続が捌けた瞬間の process.exit(0) が Provider 経由で処理中のリクエストを切っていた。local だけが 3 秒の猶予を持つ非対称だった)。--auth all では listener が 1 本なので挙動は不変。remote.json の新フィールドは optional なので #2489 以前に記録されたセッションも従来どおり読めて(all として扱われ)、生きた tunnel が孤児にならない。macOS 実機の隔離サーバで実測(2026-09-12、dist/server ビルド / 私設 HOME・DB・tmux socket): remote-only で local listener は画面 200 / API 200 / WS 101、Provider listener は画面 307 / Authorization つき API 401 / WS 401 で、x-cm-ingress: local + X-Real-IP + X-Forwarded-For + Host を偽装しても 307 / 401 のまま、正しい token では 200 / 101。--auth all・scope 未設定・CM_BIND=0.0.0.0・ingress port 未設定 / 重複・scope の綴り違いはいずれも local listener が 307(=現行挙動)で 2 本目の listener を開かない。Provider 経由の実機(スマホ / Tailscale Serve)は未確認。

Changed

  • docs(readme): README en/ja を orchestrate 軸で再構成し、hero を orchestrate run の収録へ差し替えた (#2494): docs/design/public-messaging.md(#2493 で改訂済み)の §1 / §1b / §2 / §3 / §4b / §11b から逐語でコピーして、README.md と docs/ja/README.md を Problem → Product(run) → Proof → Philosophy → Details の 15 節構成へ組み替えた。H1 は From vibe coding to Vibe Engineering. /「vibe coding から、Vibe Engineering へ。」を廃止せず §13 Philosophy 節の ## 見出しへ降ろし、hero は §1 の新 H1 One agent leads. Gates decide what's done. /「指揮するのは、いつもの Agent。判定するのは、ゲート。」+ lede 2 文 + 事実行に差し替えた。新設は ## More agents, same you(#2493 §1b の 4 行+締め 1 文を逐語)/ ## One agent leads(run の 1 段落+cmate-task-contract §3.1 最小形の契約例 version / title / goal / scope.allow / verify.gates + Measured 表)/ ## Review by another agent(commandmate ask と cmate-delegate、cross-model review はランナーの外だと明記)/ ## Know which one needs you(通知・スマホ・Conversation view を集約)/ ## What it does not do(#2493 §4b の 5 行を逐語)の 5 節で、Key Features は ## What it gives you の下に One agent leads / Verified, not vibe-checked / Know which one needs you / Method as a system / Also の 5 群(###)へ並べ替え、既存 13 行の文は 1 文字も変えずに新規 2 行(cmate-orchestrate Skill = plan → dispatch → merge → uat・--approve なしに何も書き換わらない、Delegation between sessions = ask / ask --async + relays / peers / cmate-delegate)だけを足した。### Supported agents は ## へ昇格し、## Use cases は #2493 §11b の実測範囲に合わせて 5 行を入れ替えた。Measured 表の 3 行は収録記録と一致する(workspace/market/posts/ は .gitignore 対象なのでコミットされないが、05 qa.md: 送信 15:43:54 → 報告 8 分 39 秒・waves [[7,8,9],[10]]・PR #13〜#16 merged・uat-report verdicts.go = 4、06 brief.md / qa.md: 17:25:25 送信 → 17:33:11 報告の 7 分 46 秒・作業者 4 体とも Command Code・PR #25〜#28・integration_verify.outcome: pass・UAT go 4/4・収録後に人が develop 8849f29 を pull して npm test 47/47、04 brief.md: 司令塔 Command Code / Codex 実装 / Claude Code テスト / Antigravity レビューで REJECT 344s → 修正 → APPROVE 457s → verify --gates unit で RESULT passed)。表の下に as observed を明記した。Issue 本文からの意図的な逸脱は 1 つだけ: Measured 3 行目の見出しを 1 issue, review loop ではなく 1 issue, with a review step にした — loop は #2493 §11b の「書かない表現」かつ本 Issue の受入条件でもあるため。ただし §4b 1 行目の Nothing loops forever は §11b がその禁止の理由として引いている文なので逐語で残してあり、README 中の loop はこの 1 箇所だけである(grep -in loop README.md = 1 行、ja は 0 行)。hero メディアは Issue の推奨 A を採った: 06 の clip.mp4(1,007,180 バイト / 1080² / 15 秒)を .claude/skills/video-to-gif/scripts/to-gif.sh --width 560 --max-bytes 1400000 で GIF 化して docs/images/demo-hero.en.gif = demo-hero.ja.gif(1,369,261 バイト、560² / 10fps / 256 色、wc -c 実測で予算 1.5MB 内。06 の収録は英語版のみなので 2 パスは byte-identical = git blob 1 つ)へ置き、現行の UX 30 秒 GIF は git mv で docs/images/demo-ux.{en,ja}.gif(1,406,883 / 1,552,144 バイト、再エンコードなし)へ改名して §8 へ移した。§8 にはさらに 07 の clip.mp4 から docs/images/demo-sessions.gif(1,131,939 バイト、560² / 8fps)を起こした。alt は 3 本とも映っているコマだけから書いた(GIF から抜いたフレームのコンタクトシートを目視して確認。07 の「4/4 unit gate」は映像に無いので alt に書いていない)。Security の 1 文は en / ja とも Claude CLI's own API calls → the agent CLI's own API calls /「Claude CLI 自体の」→「エージェント CLI 自体の」に直し、How it works の mermaid に L["Lead session\n(any agent)"] -->|"send --contract / ask / wait --verify"| B を 1 本足した。Supported agents の run the same loop against a model you host yourself は禁止語 loop を含むため the same worktree session, contract and gates, against a model you host yourself に置き換えた(ja も同様)。ガード(tests/unit/docs/public-messaging.test.ts)は HERO_H1_EN/JA を新 H1 に差し替え、旧 H1 を AXIS_EN/JA として追加し、concept ja/en は AXIS_* のみ(思想の正本なので pitch は要求しない)、README は HERO_H1_* と AXIS_* の両方を要求するよう付け替えた。public-messaging.md 側の照合も hero / axis / 定義文の 6 本へ広げてある。変異注入で実効性を確認済み: README の hero を別文へ差し替えると must open with the en hero verbatim が 1 件赤、§13 の見出しを別語へ差し替えると must name the axis verbatim が 1 件赤、対照は 23/23 緑。## の数は en / ja とも 11 → 18 で揃っている。npm run test:unit 1545 ファイル 27,827 件、npm run test:integration 115 ファイル 1,509 件(skip 2)、lint / tsc --noEmit / build:cli / build:server / build すべて緑。別 Issue が要る積み残し: docs/features/product-highlights.md:226-227 と docs/en/features/product-highlights.md:237-238 が demo-hero.en.gif(1,406,883 バイト)/ demo-hero.ja.gif(1,552,144 バイト)という旧ファイルのバイト数を地の文で持っており、この改名で古くなったが、どちらも本 Issue の scope 外(画像参照ではなく散文なのでリンク切れは無い)。#2493 §12 の脚注が指摘しているとおり product-highlights を縛るテストは 1 つも無いので、追随用の Issue を別に立てること。

  • docs(website): LP を orchestrate 軸へ改訂(h1 / title / og、Problem 節、See it running を先頭へ、4 カードの付け替え) (#2495): website/index.html の H1 を From vibe coding to Vibe Engineering. から One agent leads. Gates decide what's done. へ差し替え、lede ・ <title> ・ og:title ・ description / og:description を docs/design/public-messaging.md §1(#2493 改訂版)から逐語でコピーした。hero の直下に Problem 節「More agents, same you」(§1b)を新設し、See it running を The loop より上へ移動してその先頭に Command Code を lead にした実ラン 1 本(website/assets/media/orchestrate-run.mp4、1080x1080 / 15 秒 / 0.96MB、キャプション One message to Command Code. Four issues, four workers, 4/4 gates, then UAT.)を置き、既存デモ 4 本は §5 の対応表どおりに並べ替えた。4 カードは §3 の順と題(One agent leads / Verified, not vibe-checked / Know which one needs you / Method as a system)へ、With / Without の直後に §4b の 5 行を並べた What it does not do 節を、footer の上に From vibe coding to Vibe Engineering. を見出しにした Philosophy 節(定義文 ・ 一節 ・ Willison 脚注)を新設した。定義文の def:en 逐語一致と Willison 脚注は hero から Philosophy 節へ移っただけで、文字列は変えていない。tests/unit/website/landing-page.test.ts は期待値を public-messaging.md から読む形に変え(§1 hero 行 ・ §3 カード表 ・ §5 キャプション表 ・ §11b「言えないこと」)、#2493 のような文言差し替えがテストを素通りしないようにした(hero 予算 HERO_BUDGET_BYTES=100KB は据え置き)。README(#2494)・ チュートリアル ・ docs/concept.md は対象外。

  • docs(design): 公開面の単一ソース public-messaging.md を orchestrate 軸へ改訂し、禁止語に control layer を追加 (#2493): hero の H1 を From vibe coding to Vibe Engineering. から One agent leads. Gates decide what's done.(ja: 指揮するのは、いつもの Agent。判定するのは、ゲート。)へ差し替え、lede を「lead エージェントに他を走らせる仕組みを渡す — タスクごとに worktree 1 つと契約 1 つ・完了を決めるゲート・毎ランの記録」+ 8 ツール列挙 + You approve; you don't relay. の 1 セルに置き換え、hero 直下に事実行 Open source (MIT) · runs on your machine · macOS / Linux / Windows (WSL2) · no app to install(比較ではなく事実として書く旨の注記つき)を新設した。旧 H1 は廃止していない — §2 を「思想の名と定義文」に広げ、旧 H1 を en / ja の表で「思想の名(README / LP では Philosophy 節の ## 見出し)」として降ろした。これは tests/unit/docs/public-messaging.test.ts の HERO_H1_EN/JA が文書に対する toContain である性質を利用した段取りで、定数そのものの差し替え(HERO_H1_* → 新 H1、旧 H1 を AXIS_* 化)は README を同じ PR で直せる #2494 へ後送りした(本 PR 単独では README 側のピン opens with the hero… が赤になるため)。新設は 3 節: §1b More agents, same you(読者の困りごとを読者の言葉で。en / ja をコピペ可能な fenced block で併記)、§4b What it does not do(常駐ではなく 1 回のラン・コードは読まない・別エージェントのレビューは利用者が足すステップ・承認は人に来る・tmux / worktree / ターミナル / CLI を置き換えない、の 5 行)、§11b 言えること / 言えないこと(実測の範囲)(言える: lead は Claude Code / Command Code で 4 Issue → 4/4・8m39s / 7m46s、worker は Codex / Claude Code / Antigravity / Command Code、別 CLI レビューで REJECT → 修正 → APPROVE、plan は dry-run、mutation は --approve の invocation だけ、gate 不合格で停止、修正ループは上限つき / 言えない: your orchestrator runs 24/7 self-managing backlog loop orchestrate with any agent as the lead review by a fresh agent is built in the only … no supervision とモデルの序列・未計測の数字)。節番号は 1〜12 を 1 つも動かしていない — 新設を 1b / 4b / 11b の英字サフィックスで挿入したのは、.claude/skills/demo-video/storyboard/readme-hero.yaml:39,42 と docs/images/features/storyboards/{01,03,11}-*.yaml が public-messaging.md §5 / §6 を番号で指しており、振り直すと絵コンテ側の由来コメントが一斉に嘘になるため。§3 の 4 カードは順序と題を入れ替え(1 One agent leads(旧 Any agent, in parallel を吸収)/ 2 Verified, not vibe-checked(「証拠は要約ではなくラン」の 1 文を追加)/ 3 Know which one needs you(旧 Stay in control, anywhere を改題し、入力待ちが hooks 由来の状態であることを明示)/ 4 Method as a system)、§5 のデモ 4 本の「対応カード」列を新カード番号へ付け替えた。§6 のテロップ表(16 行)は 1 文字も触っていない — デモ 3 の across seven agent CLIs / 7 種の CLI から選べる は撮影済み映像に焼かれ tests/unit/skills/demo-video/storyboard.test.ts が逐語で固定しているので、「数の権威は §11 の CLI_TOOL_IDS = 8 id であり、直すには映像と絵コンテを撮り直す(別 Issue)」という注記を §6 に置いた(#2300 が残した「§3 / §5 が seven のまま §11 と自己矛盾」のうち §3 は本 Issue のカード差し替えで数を述べない文言になり解消、残るのは §6 のテロップ 1 箇所のみ)。禁止語は表と BANNED_TERMS 配列へ control layer / コントロールレイヤー を同じ commit で追加し、片方だけ落とす変異注入 2 本(表の行を削る / 配列の要素を削る)でいずれも publishes the banned-term list this test enforces が 1 件赤になることを実測してから戻した。追加前に README.md / docs/ja/README.md / docs/concept.md / docs/en/concept.md / website/index.html を大文字小文字無視で grep し全 0 件であることを確認済み(実在すれば uses none of the banned terms が即赤になる)。§12 の後続 Issue 表に #2494(README)/ #2495(LP)を追加。README / LP 本体・docs/concept.md の Mission / Vision・§4 の With / Without 表は本 Issue では触っていない。検証: public-messaging.test.ts 22/22 緑、landing-page.test.ts + storyboard.test.ts 282/282 緑(def:en マーカーと hero テロップ 8 行の照合を含む)

Fixed

  • fix(test): unit テストが本物の ~/.commandmate/opencode-ports.json を書き換える問題を修正 (#2490): tests/setup.ts に CM_OPENCODE_PORT_FILE の隔離既定($TMPDIR/commandmate-test-opencode-ports/<worker pid>.json)を追加し、スイートが opencode のポート台帳へ到達できないようにした。既定値 ~/.commandmate/opencode-ports.json は本番サーバの生きた状態で、rememberOpencodePort() / forgetOpencodePort() は read-modify-write なので、テストは「行を足す」のではなく読んだ内容でファイル全体を書き戻す — その間にサーバが作った割り当ては消え、pane は生きたままポートが二度と見つからず、そのセッションは最後まで scraper に落ちる(reattach.ts:220 / slash-commands/opencode-live.ts:113 が読む先)。#1942 の COPILOT_HOME と違い将来への柵ではなく実害の後始末で、2026-09-12 実測では実ファイルの 7 エントリが全て fixture(wt-alpha / wt-beta / wt-1898 / wt-respond / wt-recheck、いずれも /tmp/wt*)=利用者自身の割り当ては既に消えていた。opencode テスト 19 本は自分で CM_OPENCODE_PORT_FILE を向けており問題ではなく、漏れていたのは module を import するだけで既定に落ちる 10 本前後(respond-decision-id-1932 / respond-question-2039 / respond-sole-decision-2040 ほか)なので、修正は気づいたファイルではなく既定側に置いた。verify の env-clean が見逃していたのは構造的な理由による — 同ゲートは ~/.commandmate 直下のエントリ増減しか見ず、既存ファイルの中身が書き換わっても数は動かない(#2487 の run 750 は commandmate-entries clean を返しながらこれをやっていた)。COPILOT_HOME と並ぶ 2 本目の worker pid 配下にしたのは、この既定にも実在の書き手がいて書き込みが単一 JSON 文書への read-modify-write だからで、固定名だと並列 checkout 同士が互いのキーを落とす。空文字は未設定扱い(resolveSafeDirectory が '' を未設定と読んで homedir へ戻すため)、vi.stubEnv と明示代入は従来どおり既定に勝つ。ガードは tests/unit/lib/hooks/sources/opencode/port-file-isolation-2490.test.ts(#1873 / #1942 と同型: 既定が外れたら赤になり、tautology でないことを「変数を消すと実ファイルを指す」で裏取りし、実書き込みを 1 本流して pinned 側に載り実台帳には載らないことまで見る(実台帳とのバイト列一致は敢えて見ない — 同居する本番サーバの正当な書き込みで赤くなるガードは他人の正しい挙動を報告するだけなので))。実測: 通常の npm run test:unit 全体で実台帳は mtime・バイト列とも不変、HOME を空の番兵ディレクトリへ向けた unit 全体でも .commandmate/opencode-ports.json は作られない。

  • fix(api): POST /send だけが旧い解決順のままで、登録行の無い --instance <tool> と別 tool の要求が食い違いにならなかった問題を修正 (#2491): POST /api/worktrees/:id/send の tool / instance 解決を、#1925 で一本化した resolveSessionTargetStrict()(src/lib/session/resolve-session-target.ts)へ付け替えた。この route だけが最後まで resolveInstanceCliTool()(src/lib/db/agent-instances-db.ts)を使っており、その解決順は「roster の登録行 → 要求された tool → tool 名の instance id(#868 の primary anchor)」なので、登録行が無いと要求された tool をそのまま採る — #2487 が入れた規則(登録行が無くても tool 名の instance id は自分の tool を宣言しており、別 tool の要求は食い違い)がこの door には届いていなかった。結果、登録行の無い worktree で {"instanceId": "antigravity", "cliToolId": "command-code"} を投げると 400 にならず mcbd-command-code-<wt>-antigravity を立ててそこへ送る(instance 名だけを名乗る側からは誰も読まないセッション)。今後は ほかの副作用 route(kill-session / terminal / respond / auto-yes POST、DR3-015)と同じく 400 instance_tool_conflict + describeSessionTargetConflict() の文言+ conflict の各フィールド(instanceId / rosterCliTool / requestedCliTool / primary anchor のときだけ primaryAnchor: true)を返し、tmux には一切触れない(CLIToolManager.getTool() より手前で return する)。CLI は挙動不変 — send / ask / respond は送る前に /resolve-target(strict)で解決するので #2487 のマージ後は exit 2 で止まり、この route には届かない。Web UI も挙動不変(roster の行から両方を取るので食い違わない)。影響を受けるのは API を直接叩く経路(スクリプト・Skill 等)だけで、そこでも従来どおり送れるものは全部送れる: ad-hoc の id(codex-3)+ cliToolId の --register 経路、tool 名の id 単独、instance も tool も無い要求(worktree 既定へ)、roster と一致する明示指定。roster との食い違いは #1629 から一貫して 400 で、本件で code が付いた。あわせて resolveRelaySession()(src/lib/relay/relay-session-ref.ts)も同じ resolver へ揃えた — relay は tool を名指ししないので 4 段の解決結果は完全に同一(配送の挙動は変えていない)で、揃えたのは「/send と同じ権威を使う」という #2377 の docblock の主張を真に戻すため。route 自前の DEFAULT_CLI_TOOL = 'claude' は削除した(設計 §4 D5 決定 4 が literal を許すのは resolve-session-target.ts だけで、worktree.cliToolId || 'claude' の意味は resolver の worktree-default / fallback 段そのもの)。resolveInstanceCliTool() は本件で本番の呼び出し元がゼロになったため @deprecated とし、新規の呼び出しを禁じる旨を docblock に明記した(#1629 の unit テストが残っているので削除は別件)。

  • fix(scripts): stop.sh → build-and-start.sh --daemon が旧サーバーの終了処理と競合して「Server is already running」でビルド前に中断し、サーバーが落ちたまま残る問題を修正 (#2488): 停止の判定をポートの静けさからプロセスの生死へ移した。server.ts の gracefulShutdown は待ち受けソケットを先に閉じ、残っている接続(ダッシュボードを開いたタブの keep-alive)を最大 3 秒待ってから強制終了するので、SIGTERM と exit の間には「誰も LISTEN していないのに旧プロセスは生きていて logs/server.pid も握ったまま」という窓ができる。stop.sh はその窓を sleep 2 + LISTEN 再検索で「停止済み」と読んで戻り、&& で続く build-and-start.sh --daemon が生きている PID を見てビルド前に exit 1していた(2026-09-11 22:44〜22:47 JST、本番ポート 3000 に LISTEN 無し・.next/BUILD_ID 据え置き・GET / が 000 のまま 3 分間)。stop.sh / stop-server.sh は自分が SIGTERM を送った PID そのものを kill -0 で 100ms 間隔にポーリングし、猶予(CM_STOP_GRACE_SECONDS、既定 10 秒 = server.ts の 3 秒強制終了より長い)を超えた分だけ SIGKILL へ上げる。stop.sh には stop-server.sh にしか無かった PID ファイル段(nohup npm start = node の親。LISTEN しないのでポート検索には決して映らず、まさに Server is already running として読み返される PID)を入れ、2 つの停止スクリプトの挙動を揃えた。build-and-start.sh / start.sh は PID ファイルの PID が生きていても即断せず同じ猶予だけ待ち、待っても生きていれば従来どおり exit 1(本当に動いているサーバーは猶予を越えるので拒否は変わらない)。server.ts 側は server.close() の後に server.closeIdleConnections() を足して、リクエストが飛んでいない keep-alive ソケットだけを落とす(処理中のリクエストは idle ではないので従来どおり 3 秒もらえる)——窓そのものが通常ミリ秒に縮む。#2473 の要件は維持: 待つのも殺すのも LISTEN 限定の検索が返した PID だけなので、ポートに接続しているだけのプロセス(ブラウザの network service、他セッションの CLI)は依然として一切触らない。検証は tests/unit/scripts/stop-waits-for-exit-2488.test.ts — 終了に 3 秒かかるサーバーを模した孤児プロセス+接続中クライアントで、stop.sh が ✓ Application stopped を出した時点の kill -0 が ESRCH であること、stop.sh && start.sh --daemon が中断せず起動しきること、変異対照(wait_for_exit を修正前の sleep 2 + LISTEN 再検索に戻すと旧プロセスが生きたまま「停止済み」と出る)まで含む。