# 起動とアカウント ## Codex CLI の起動と再開(#272) 種別で `Codex CLI` を選び、CLI コマンド(`codex` または実行ファイルの絶対パス)と作業ディレクトリを設定する。対応基準は **Codex CLI 0.160.0 以上**であり、古い CLI では起動前に診断を返す。アプリはインストール・認証・利用者のグローバル設定を変更しない。宣言済み `cli` は command が codex でも汎用のままであり、移行対象は旧 `ai` または kind の無いアカウントだけである。 **Codex の部屋の MCP は起動の行だけで渡し、ファイルには書かない(#297)。** 起動の `-c mcp_servers.<自席>={…}` が定義全体(コマンド・引数・`env_vars`・`enabled=true`・部屋の六道具の承認)を運ぶ。席の名前は Claude の席と同じアカウント/トピックごとの名前(`server_name_for`)である。席の属性(部屋 URL・名前・アカウント・部屋 id・色・未読の印)と部屋トークンは起動した CLI の環境にあり、定義は名前だけを `env_vars` で指す。部屋トークンと起動別のセッション操作 capability(#415)は設定・argv・プレビューに値を残さない。発言・履歴・利用量と、[セッション操作の三道具](1-room.md#ai-からのセッション操作415)を起動時に承認する。行に載るのは自席の定義だけで、他の席の名前は載らない——Codex の他の席の部屋サーバーはそれぞれの起動の行にしか無く、この席からは見えないため、無効化する他席の MCP 登録が無い。起動の前に有効 cwd に `.codex` フォルダーだけを用意する。Codex 0.160.0 の native discovery は `config.toml` の無い `.codex` フォルダーでも project layer を返し([loader](https://github.com/openai/codex/blob/rust-v0.160.0/codex-rs/config/src/loader/mod.rs) の `discover_project_layers`)、専用 hook の置き場はその layer から決まる(下記)。 **以前の版が `.codex/config.toml` に書いた部屋の登録は、その席が起動するときに消す(#297)。** 消すのは名前が `pullcept-room` で始まり、`env` に `PULLCEPT_ACCOUNT_ID` を持つ欄だけで、部屋 URL を問わない。他のキー・欄・コメントは残し、消す欄が無ければファイルを書き直さない。欄を消して `[mcp_servers]` が空になればその表も外す。ファイルそのものは消さない。対象は起動する有効 cwd のファイルだけであり、別のフォルダーに残った欄は、そのフォルダーで席が起動したときに消える。残っていても無効(`enabled = false`)で、サーバーは起動しない。消してよいのは、#272 以来どの版も同じ定義全体を起動の行で渡しており、ファイルの欄は既定で無効のまま一度もサーバーを起動していないためである。 **Claude の席にそろえる形と比べて、書かない形を選んだ(#297)。** Claude の席の `.mcp.json` は一つの登録をアカウント/トピックごとに書き、今の部屋 URL を持つ欄を残して古い URL の欄を消す(#40)。Claude Code は MCP をそのファイルから読むので、ファイルの欄が要る。#297 より前の Codex の登録もこの粒度と残し方に既にそろっていたが、ファイルの欄は起動の `-c` が丸ごと置き換え、使われていなかった。残る違いは、欄がファイルに溜まり、他の席を止める `-c …enabled=false` がその欄を数えて増えることだけだった。書かない形なら、欄が溜まらず、他の席を止める行も要らず、未信頼のフォルダーでも同じに動く。Claude の席の `.mcp.json` への書き込みと読み出しは変えない。 `-C パス` / `--cd パス` / `--cd=パス` は元の process cwd に対して解決し(短い `-C=パス` は明示拒否する)、設定登録・hook discovery・画面の cwd に同じ有効 cwd を使う。実対話の profile・`-c`・環境変数を保持する。app-server は `--profile` を受け付けないため、discovery には選んだ `$CODEX_HOME/<名前>.config.toml` の `project_root_markers` だけを渡し、利用者の `-c` をその後に適用する。profile 名は native runtime が受理する plain name を使う。0.160.0 はドットを含む名前を拒否する。読めない profile や安全に保持できない markers は拒否する。remote 接続・CLI 自身による `--worktree` 作成・非対話サブコマンドは、このローカル席の起動欄では使えない。 **初回は診断端末でフォルダーと専用 hook を信頼する。** `SessionStart` の一覧で `codex-session.mjs` と、ファイル方式なら Li+ の `codex-output-style.py` を確認し、それぞれの handler を信頼する。アプリは既存 handler と信頼状態を保持し、信頼 hash を書かず、全 hook の確認を迂回しない。MCP instructions は札・返信・履歴を案内する。キャラクターは下記の方式で選ぶ。AGENTS.md と Li+ の指示は CLI が既存どおり読み込む。パネルの五欄は rollout から読む(下記「[Codex の席は自分の様子を rollout で知らせる](#codex-の席は自分の様子を-rollout-で知らせる283)」)。 hook の場所は同条件の native `config/read` の project layer から決め、登録後に `hooks/list` でも列挙を確認する(未信頼フォルダーは通常の CLI 信頼画面へ進む)。standalone cwd・独立 Git root・サブディレクトリに加え、linked worktree では正式な root-checkout mapping を使う。`.git` の gitdir、commondir、登録 checkout、main checkout の所属を照合し、同一 Git repository の対応する相対位置に専用 handler だけを追記する。実際に書く設定/hook の境界外 symlink は拒否する。[native loader 0.160.0](https://github.com/openai/codex/blob/rust-v0.160.0/codex-rs/config/src/loader/mod.rs#L1106) と [trust resolver](https://github.com/openai/codex/blob/rust-v0.160.0/codex-rs/git-utils/src/trust.rs) に合わせた動作である。 初回の実会話で hook stdin の `session_id` を受け、起動 nonce・アカウント・トピック・生きている PTY を照合して索引へ保存する。最初のターン前は ID 未取得が正常である。子セッション・古い起動・別の席・保存 ID と異なる通知は採用しない。停止後は保存 UUID を `codex resume ` に渡す。`CODEX_HOME` の会話ファイルが正常な探索で見つからない時だけ新規に戻り、権限/I/O エラーで調べられない時は ID を保持して明示 resume を試す。過去を押し込まず、必要な履歴は席が道具で引く。 **Codex の席は代替画面を使わずに起動する(#293)。** 新規起動の行と再開の行(`codex resume …`)の両方の末尾に `--no-alt-screen` を足す。Codex 0.160.0 の TUI は既定(`tui.alternate_screen = "auto"`)で代替画面に描くため、端末ペインの scrollback に履歴が溜まらず、上へスクロールできない。このフラグで TUI は inline で描き、ペインに履歴が残る(`codex --help` / `codex resume --help` の「Runs the TUI in inline mode, preserving terminal scrollback history.」)。clap は再開の行でも位置引数の後のフラグを受け取る。入力欄は最下行にあり、キーを打つと最下部へ戻る(xterm.js の既定動作)。**起動オプションが画面の扱いを既に決めていれば足さない**——`--no-alt-screen` そのもの、または `-c` / `--config` の値で鍵が `tui.alternate_screen` のもの(`-c key=value` と `--config=key=value` のどちらの書き方でも)である。二重に指定して起動を落とさないためであり、利用者の指定がそのまま効く。プレビューは起動と同じ合成を通るため、この語もプレビューの行に出る。`Claude Code` の行には足さない。 ## Codex のキャラ選択(#281) キャラクター欄には `.codex/output-styles/character_codex_luna.md` の **拡張子を除いた名前** `character_codex_luna` を入力する。大文字小文字を区別し、ASCII の英数字で始まる英数字・`_`・`-` の128文字以下とする。空欄は project `.codex/config.toml` の `[liplus] output_style` に任せる。selector 未設定でも `.codex/output-styles` があれば `character_instance.md` を既定に使う。false は空欄時に persona を無効にする。アカウントに名前を指定すれば既定の本文を選択本文で置き換え、一つだけ配送する。共通 developer_instructions は変更しない。Claude と同じ artifact を共有できるが、Claude 側の共有リンク・frontmatter の設定は環境移行時に行う。 native `config/read` が返した有効 project layer のうち、selector 又は style フォルダーを持つ最優先の root を採用する。disabledReason のある layer を参照 root にせず、profile の project_root_markers、手動 -c、--cd、CODEX_HOME の discovery 条件を保持する。style 選択自体は project 設定専用で、profile/global/-c に置いた `[liplus]` との連動は約束しない。 Python 3.11 以上と、その root に installed Li+ helper `.codex/hooks/codex-output-style.py` が必要である。[loader 契約](https://github.com/Liplus-Project/liplus-language/blob/main/adapter/codex/character-config.md) の `resolve --cwd <有効cwd> --root <信頼済みroot> [--style <アカウント名>]` をプレビューと起動・再開で共通に使う。本文を返させず、protocol_version 1、mode、name、root、sha256、byte_count、handler metadata を検証する。本文 parser は Li+ helper だけが持つ。 専用 handler は native `hooks/list` の project source、enabled、trusted、同期実行、additionalContextLimit=0、startup/resume/clear/compact の一回ずつの登録を確認する。登録が無い・変更されて再信頼が必要・別 root の style handler と重複する場合は起動を止める。プレビューに方式と選択名を表示する。missing、空本文、不正 UTF-8、frontmatter 不整合、境界外 symlink/junction、上限128 KiB超などは loader と同じ結果で拒否し、旧方式には戻らない。エラーへ本文・CLI stderr・秘密を出さない。 アカウントの非空選択名だけを app-owned `LI_PLUS_OUTPUT_STYLE` に設定し、子プロセスへ渡す。本文は argv と環境へ載らず、長い本文でも Windows の8191文字制約を消費しない。空欄では inherited override を除去して project の既定を使う。原本 config と style、共通指示は更新しない。`codex-session.mjs` は ID callback 専用のままである。実環境での全文配送と画面起動は、Li+ loader 配備・個別 hook trust・新 Pullcept build の配備後の検証を要する。 ### 移行前の inline 選択(#276) selector も `.codex/output-styles` も無い project では、次の従来方式を維持する。 キャラクター欄には `character_Codex_Lin` のように見出しの名前を正確に入力する。定義元は、実 CLI がその起動条件で解決した **有効な developer_instructions 一文字列**だけである。ファイルを別途検索したり、複数 layer の本文を連結したりしない。CLI の通常の優先順位(user base → 選択 profile-v2 → 信頼済み project layer → 手動 `-c`、managed 設定は CLI の制約に従う)で一つの本文が決まる。上位の文字列は下位の文字列を置換するため、共通部分は有効な本文の中へ置く。未信頼の project 本文は採用しない。 モデルを呼ばない native `debug prompt-input` を、元 cwd・`--cd`・`--profile`・`-c`・`CODEX_HOME`・アカウント環境で実行する。ランダムな developer_instructions sentinel を上書きした比較実行と role/content 全体を照合し、その一か所の本文だけを取り出す。本文が無い時は別の permissions/skills/AGENTS を拾わない。比較が曖昧、TOML が壊れている、CLI が失敗、出力が8 MiB超、又は各実行が15秒超なら起動を止める。CLI stderr や他の指示・秘密はエラーへ出さない。 定義の例: ```toml developer_instructions = """ 共通指示をここへ置く。 # character_Codex_Lin NAME=Codex Lin NICKNAME=Lin Lin の本文。 ## 詳細 ここも Lin の本文。 # character_Codex_Lay NAME=Codex Lay NICKNAME=Lay Lay の本文。 # 共通の続き この見出し以降は両方に残る。 """ ``` 列0の `# character_<名前>` が開始、次の列0の H1 (`# `) が終端である。開始見出しの末尾空白を除いた文字列が選択名となる。名前の空白と空の名前は拒否する。H2以下、空行、区切り線は本文の一部で、バッククォート又はチルダ3個以上のコードフェンス内の見出しは区切りにしない。フェンスは同種で開始以上の長さ、残りが空白の行で閉じる。LF/CRLF・空白・区切りを保持し、選択したブロックを元位置に残して、他のキャラブロックだけを除く。キャラ外の共通指示はそのまま残る。全定義を検査し、重複・空本文を拒否する。未知名は別キャラへ代替しない。 選択した本文を最後の `-c developer_instructions=...` として合成する。TOML Unicode escape で日本語・改行・引用符・シェル文字を保持する。手動 `-c` の文字列/配列/inline table も同値に符号化し、持ち運べない他のオプションは省略せず拒否する。短いフラグの値は空白で分ける(`-p 名前`、`-C パス`、`-c key=value`)。`-p名前`/`-p=名前` 等の連結・等号形式は探索と起動で明示拒否し、長い `--profile=名前`/`--cd=パス`/`--config=key=value` は扱う。Windows の合成行が8191文字を超える時も切り詰めず拒否する。Codex の PTY は `cmd /C call` で空白を含む .cmd コマンドを保ち、文字列の % は TOML escape で渡す。プレビューと起動・再開は同じ helper/parser/argv 合成を通る。プレビューは編集したコマンド・環境も使い、失敗理由を表示する。 inline 方式の空欄は developer_instructions 上書きを行わず既存 CLI に任せる。旧保存値は削除しないが、自由文から名前選択へ意味が変わるため、そのままでは未知名として拒否する。Codex の `PULLCEPT_CHARACTER` を MCP instructions へ送る旧経路は無い。原本 developer_instructions・AGENTS.md・Li+・グローバル設定は変更しない。既存 app-owned MCP の登録更新と ID 専用 SessionStart hook の追記は従来どおりである。Claude の output style 選択は従来どおり。 以後の `.mcp.json`・`--settings`・output style・`--session-id` は Claude Code の作法であり、Codex の設定と初回 ID 取得はこの節を参照する。 ## Codex の席を app-server 経由で起動する(#299) **Codex の標準起動は、席ごとの app-server 方式である。** 新規アカウントと `codex_app_server` が欠落した設定では有効になり、明示保存された false(hook 方式)と true は保持する。他の種別から Codex CLI に変更するときも標準は有効である。アカウントの窓で種別が `Codex CLI` のときだけ「app-server 方式」の欄が出る。外して決定すれば次の起動から互換の hook 方式(上の #281)に戻る。編集・保存・プレビュー・実際の起動で同じ選択を使い、走っている席は切り替えても変わらない。2026-10-17 の実機往復と既定切り替えの依頼を受けて変更した(「[実装状況](4-status.md)」の #299 の検証範囲)。スマホ接続は別の opt-in で既定オフのままであり、Claude など他の種別の起動は変えない。 **一つの席に一つの app-server を立て、その上に本物の TUI を繋ぐ。** 起動は次の順に進む。 1. キャラクターを #281 と同じ loader で解決する。file mode では helper の `hook` を、hook がするのと同じ入力(`SessionStart`、`startup`、有効 cwd)と同じ環境(`LI_PLUS_OUTPUT_STYLE` に選択名)で一度だけ走らせ、hook が配るはずの本文を受け取る。本文の UTF-8 の長さと sha256 が `resolve` の metadata と一致しなければ、配らずに起動を止める(`sidecar/src/codex-style.mjs` の `body`)。移行前の inline 選択(#276)では、有効な developer instructions から見出しを選んだ本文を使う。persona が無効(false)なら本文は無い。 2. 席の app-server を `codex <オプション> -c mcp_servers.<自席>={…} -c features.codex_hooks=true -c hooks.state={…} app-server --listen ws://127.0.0.1:0 --ws-auth capability-token --ws-token-sha256 ` で起動する。部屋の MCP の定義は #297 と同じものを、今度は server の行に載せる。部屋の変数(部屋トークン・URL・名前・アカウント・部屋 id・色・未読の印)は server の環境にある。待ち受けの港は OS に選ばせ、server が標準出力に書く `listening on:` の行から読む。 3. アプリが server に繋ぎ(`Authorization: Bearer`)、`initialize`、`hooks/list`、`thread/start`(再開なら `thread/resume`)を送る。新しい会話には `thread/inject_items` で印を一つ記録する(下記)。 4. 端末で `codex resume <会話 id> --remote ws://127.0.0.1:<港> --remote-auth-token-env PULLCEPT_CODEX_REMOTE_TOKEN` を起動する。TUI は自分で thread を始めず、アプリが用意した会話に繋がる。 **キャラクターは `thread/start` の `developerInstructions` で渡し、hook では渡さない。** 二重に配らないために、その席の server でだけ Li+ の output style の SessionStart handler(`.codex/hooks/codex-output-style.py` を `hook` で呼ぶもの)を止める。止めるのは `-c hooks.state={''={enabled=false}}` であり、鍵は native `hooks/list` が返す `:session_start:<組>:<番号>` を #281 の検証で特定した一つだけである。`-c` の値は既存の `hooks.state` に再帰的にまざるので、その hook の信頼(`trusted_hash`)は残る。Li+ の他の hook(cold-start、Trigger Check Gate など)は止めない。`-c hooks.state."<鍵>".enabled=false` の書き方は使えない:Codex は `-c` の鍵を `.` で区切るため、パスを含む鍵が割れる(`codex-rs/config/src/overrides.rs`)。thread を作る前に `hooks/list` を読み直し、止めたはずの鍵が `enabled: false` で並び、`codex-output-style.py` を呼ぶ有効な SessionStart hook が一つも残っていないことを確かめる。残っていれば起動を止める。ファイル(`hooks.json`・`config.toml`)は書き換えない。 **再開でも毎回 `developerInstructions` を送り直す。** `thread/resume` にも同じ本文を載せる。アカウントのキャラクターを変えた後の再開は、新しい本文で始まる。 **file mode の `developerInstructions` は、利用者の共通指示、検証済みキャラ本文、部屋の一文の順に合成する(#303)。** 共通指示は #276 と同じモデル不要の native discovery(`codex-character.mjs`)で、その席のコマンド・cwd・起動オプション・環境から解決した有効な `developer_instructions` 一文字列である。空または未設定なら追加せず、取得失敗は起動・再開を止める。各本文の間に空行を置き、キャラ本文の改行・UTF-8 のバイトは変えない。本文の長さと sha256 の照合(上の 1.)はキャラ本文だけに行い、共通指示と部屋の一文は照合後に合成する。部屋の一文は Claude の席が `--append-system-prompt` で受け取るものと同じ関数(`crates/mcp-config` の `room_system_prompt`)で作る(下記「[席は部屋の道具の名を起動時に受け取る](#席は部屋の道具の名を起動時に受け取る)」)。起動と毎回の再開は同じ合成を `thread/start` と `thread/resume` に載せる。inline 選択では選択済み本文が共通指示を既に含むので、再追加せず、その後に部屋の一文だけを足す。 **キャラの本文が無い席は、CLI 自身の有効な developer instructions を先頭に置く。** `developerInstructions` は CLI の `developer_instructions` を置き換える(`codex-rs/core/src/config/mod.rs`、`developer_instructions.or(cfg.developer_instructions)`)。persona が無効(false)の席や、inline 方式で名前を選んでいない席では、部屋の一文だけを送るとその人の指示が消える。そこでその席では #276 と同じモデル不要の読み取り(`codex-character.mjs`)で有効な本文を読み、その後に部屋の一文を足す。有効な本文が無ければ部屋の一文だけを送る。読み取りに失敗すれば起動を止める。 **会話 id は `thread/start` の答えから取り、`codex-session.mjs` は使わない。** hook 方式では初回の実会話で hook が id を知らせていた(#272)。app-server 方式では起動した時点で id が決まるので、新しい会話はその場でトピックに記録する。この席では `codex-session.mjs` を `hooks.json` に登録せず、server の環境に `PULLCEPT_NATIVE_URL` も `PULLCEPT_LAUNCH_ID` も置かない。以前の起動が残した登録があっても、その補助は環境が無ければ何も送らずに終わる。五欄(#283)と利用上限の扱い(#294)はこの id から rollout を読むので、最初の会話を待たずに働き始める。 **空の会話には `thread/inject_items` で印を一つ記録する。`turn/start` は使わない。** Codex は記録の無い会話を rollout に書かず、記録の無い会話に TUI は繋がれない。そこで新しい会話に developer の発言を一つ、`Pullcept opened this conversation as a seat in a room. Room posts arrive as user input.` と記録する。比べた二つの方法は次のとおりで、前者を選んだ。 - `thread/inject_items`:モデルを呼ばない。キャラクターの口に言葉を入れない(記録は developer 役であり、キャラの発言ではない)。TUI はすぐ繋がり、席は最初の発言を待つ。 - 最初の部屋の発言で `turn/start` を打つ:その発言が来るまで端末を立てられず、席が開かない。一往復ぶんのモデル呼び出しがかかる。発言は端末を通らずに入るので、利用上限で止まった席の預かり(#294)や、人が打ちかけのときの保留を迂回する。 **認証トークンは最初から必須であり、認証の無い server は立てない。** トークンは起動のたびに新しく作る(UUID v4 二つ、乱数 244 ビット)。server が受け取るのは sha256 の digest だけで(`--ws-token-sha256`)、トークンそのものは argv・設定・プレビューに載らない。端末には環境変数 `PULLCEPT_CODEX_REMOTE_TOKEN` で渡し、行にはその名前だけが載る。アカウントの環境変数でこの名前は上書きできない(`APP_LAUNCH_ENV`)。待ち受けは `127.0.0.1` だけである。トークンの無い接続・違うトークンの接続は HTTP 401 で拒まれる(2026-10-06、0.160.1 の隔離 CODEX_HOME で実測)。 **起動オプションは、server・端末・会話のどれかに行き先がある物だけを受ける。** 行き先が無い物は省かずに起動を止める(プレビューと起動の前に同じ判定)。 - 会話へ(`thread/start` と `thread/resume` の引数):`-m`/`--model`、`-a`/`--ask-for-approval`(`untrusted`/`on-request`/`never`)、`-s`/`--sandbox`(`read-only`/`workspace-write`/`danger-full-access`)、`--yolo`(`--dangerously-bypass-approvals-and-sandbox`、承認 `never` と sandbox `danger-full-access` になる)、`-c model=…`、`-c approval_policy=…`、`-c sandbox_mode=…`、`-c model_reasoning_effort=…`。旗は同じ項目の `-c` より強い。`-c` の物は server の行にも載る。 - server の行へ:それ以外の `-c`(`tui.*` を除く)、`--enable`、`--disable`。 - 端末の行へ:`-c tui.*`、`--no-alt-screen`(#295)。人が画面の方式を選んでいなければ `--no-alt-screen` を足す。 - `-C`/`--cd`:作業ディレクトリとして読み、行には載せない。 - それ以外(`-p`/`--profile`、`--search`、`-i`、`--oss` など)は拒否する。 server の答えに入る model・承認・sandbox・effort を、渡した値と照らす。違えば起動を止める。 **server は席が閉じるときに必ず止める。** 三つの止め方を重ねる。 - 端末の終わり:席ごとの見張りが 500 ms ごとに端末を見て、終わっていれば server を木ごと止める(`taskkill /T /F`)。 - アプリの終わり:全端末を閉じるとき(`kill_all_ptys`)とアプリのイベントループの終わり(`RunEvent::Exit`)に、全部の server を止める。 - アプリのプロセスの終わり(強制終了を含む):server は作られた直後に `KILL_ON_JOB_CLOSE` の job object に入り、アプリのプロセスが消えると OS が木ごと止める(Windows)。 起動の途中で失敗したら、作った会話を `thread/delete` で消してから server を止める。端末の起動に失敗した場合も同じである。 **プレビューは二行を出す。** アカウントの窓のプレビューは、方式・server の行・端末の行を出す。トークンの digest、港、会話 id は起動時に決まるので、``、``、`<会話 id>` の印で示す。 ## 起動オプション セッションの起動オプションはアカウントの属性であり、アカウントのモーダルで編集する(下記「アカウント」)。決定するまで保存しない。 **アプリは channel のエントリを起動の行へ足さない(#195)。** 部屋の入力路は端末への入力であり、サイドカーは channel を宣言しない。以前はアプリ自身の `server:pullcept-room--` を利用者の `--dangerously-load-development-channels` へ統合していた。利用者が自分の channel サーバ(たとえば `github-webhook-mcp`)を書いていれば、それは書かれたまま載る——その人の行であり、部屋は触らない。`--channels` も同じ理由で拒否しなくなった。 Claude Code の起動する行へアプリが足すのは `--settings` の JSON 文字列である。部屋のサーバの承認(`enabledMcpjsonServers`)と、起動しない兄弟の登録(`disabledMcpjsonServers`)と、アカウントのキャラクター(下記「アカウントはキャラクターを持つ」)がそこに載る。承認するサーバの名前はアカウントごとに異なり、`.mcp.json` の登録鍵と同じ 1 つの事実である(下記「`.mcp.json` の登録単位」)。書いた行と走る行は違う。 実際に起動する行は画面に表示する。書いた行と走る行が違う以上、結果を見せるほうが説明より安い。表示は起動オプションの欄の隣、つまりモーダルの中に置く。編集している欄の隣であることが条件であり、欄が移れば表示も移る。名前の欄を編集しても表示は動かない。承認する名前がアカウント id から決まるためであり、これは要点である——起動しているのはアカウントであって、改名はそれを別のものにしない。 表示は省略記号で切らずに折り返す。切れる末尾はアプリが足した `--settings` であり、そこが「書いた行と走る行が違う」ことの中身である。一行のランチャ行には縦の余裕が無かったが、モーダルにはある(ランチャ行そのものは #62 で無くなった。下記「ランチャ行は無い」)。 **起動オプションは、その人が足したいものだけを書く欄である**(#156、決定3)。CLI ごとの作法は種別が持つ(下記「種別は CLI の作法を持つ」)。セッション id の渡し方はその筆頭であり、種別が `Claude Code` の席には `--session-id {session_id}` が種別の側から載る——ここへ書く欄ではなくなった。 種別 `CLI(汎用)` にはその作法が無い。`{session_id}` をここへ書けば、新しいセッションを始めるときに、このアプリが決めた UUID がそこへ入る(「[トピック](1-room.md#トピック)」)。書かなければ id は配られない。 **この欄が起動の行へ載らないことがある**(#154。下記「人が書いた値は、起動の行が運べる形でだけ載る」)。`cmd.exe` が反応する文字を持つ引数が一つでもあれば、欄ごと載せずに起動する。起動は止めない。 **id の差し込みは、行を組み立て終えてから一度だけ行う。** 種別が載せた置換子と、人が書いた置換子は同じ一つの置換子である。片方を先に埋めれば、もう片方は置換子のまま CLI へ渡る。同じ理由で、既に id の置き場を持っている行——再開の行、あるいは自分でフラグを書いた行——へ種別の側から二本目を足さない。一つのフラグが二つ並んだときに CLI がどちらを読むかを、このアプリは確かめていない(`--settings` の併記を拒否するのと同じ地面である)。 **実測済み(2026-08-21)**: 複数の channel サーバを同時に有効化した状態で往復が成立した(部屋の入力路が channel だった間の記録。#195 で外れた)。`--print` での確認は無意味である(対話の入力欄が走らないため)ことも実測しており、確認は対話セッションでのみ可能。 ## 再開コマンド **再開コマンド**は `{session_id}` を含む一行であり、先頭の語が command になる(例: `claude --resume {session_id}`)。トピックがアカウントごとのセッション id を持ち、再開時にそこへ差し込む(「[トピック](1-room.md#トピック)」)。置換は `crates/mcp-config/` に置き、テストの対象とする。 **戻り方は、まず種別の答えである**(#156、決定6)。CLI を名指す種別はその CLI の戻り方を作法として持つため、欄を出さない——既に答えのある問いに二つ目の答えを置けば、二つは食い違いうる。欄を持つのは `CLI(汎用)` であり、アプリが答えを持たない起動はそこで人が書く。 **一行まるごとである。** 起動オプションと別の欄なのは、戻り方が起動と同じ呼び出しとは限らないためであり、これは人がそのとき打つ行である。分割は起動オプションと同じ規則で行う。 **CLI を名指す種別では、再開の行にもアカウントの起動オプションが載る**(#167)。載る位置は戻り方の語(`--resume {session_id}`)の後ろである。再開は同じ席の同じ CLI であり、その人がすべての起動に求めたこと——`--dangerously-skip-permissions` がその筆頭である——はこの起動にも求められている。載らなければ、セッションは出ていったときと別のモードで戻る(2026-09-26、Master 観測。`--resume` で起動した二本の `claude.exe` のコマンドラインに、起動オプションのどのフラグも無かった)。#156 以降この種別の再開の行は画面に出ないため、人が書き足して補う道も無い。 - **載せ方は新規起動と同じ規則である。** 載せられない値を持つ起動オプションは欄ごと落ち(下記「人が書いた値は、起動の行が運べる形でだけ載る」)、戻り方の語は残る。判定は戻り方の語と合わせる前に起動オプションだけに対して行う——合わせてから判定すれば、落ちる欄が戻り方の語を道連れにし、再開のつもりで新しいセッションが立つ。 - **種別の作法が id を渡すためのフラグ(`--session-id`)は載せない。** 戻り方の語が既に id を名指しており、一つの行に id の置き場を二つ作らない(上記「起動オプション」の一度だけの差し込みと同じ地面である)。 - **`CLI(汎用)` では足さない。** その再開の行は人が欄に書いた一行まるごとであり、起動オプションを求めるならそこへ書ける。その CLI の戻り方が起動のオプションを受け取るかどうかを、このアプリは知らない(#156、決定5)。 **空は正当な状態であり、通常の状態である。** そのアカウントは開き直したトピックへ新規のセッションとして着席し、必要になったことは `read_room_history` で自分から引く。resume を持たない CLI も同じ状態に置かれる——種別がその CLI の戻り方を持たない場合も同じである。 **この一行が起動の行へ載らなければ、その起動は拒否する**(#154。下記「人が書いた値は、起動の行が運べる形でだけ載る」)。起動オプションのように落として起動する形は採らない——引数を落とした再開コマンドは再開ではなく、戻ったつもりで新しいセッションが立つ。 ## 作業ディレクトリ セッションの作業ディレクトリはアカウントの属性であり、アカウントのモーダルで表示・変更する。未設定のままアプリのプロセス cwd へ暗黙にフォールバックしない。未設定のアカウントの起動は拒否し、モーダルを開く。 `npm run tauri dev` ではアプリのプロセス cwd が `src-tauri/` になるため、暗黙のフォールバックはセッションを `src-tauri/` で起動させ、`.mcp.json` もそこへ書く。作業ディレクトリはユーザーの選択であって、アプリのプロセスがたまたま居た場所ではない。作業ディレクトリを持たないアカウントにはホームディレクトリを prefill するが、これは既定値ではなく prefill である。画面に出ていないディレクトリでセッションを起動しない。 ## アカウント アカウントは、起動していなくても存在する同一性である。作る、残る、起動する、止める。名簿は「今つながっている人」ではなく「誰が居るか」を示す面になる。 **同一性は id にある。** アカウントは一意な id を構造的に持ち、それだけが同一性である。名前・色・作業ディレクトリ・起動オプションはすべて属性であり、編集できる。名前が識別子ではなくなったため、改名しても、名前が衝突しても、誰が誰かを見失うものが無い。 **アカウントは #40 の機構を置き換え、目的を保つ。** #40 が直した欠陥は「タブが起動の手段でしかなく、同一性を持てなかった」ことである。同一性が無いところに名前を付ける唯一の形が「起動ごとに宣言する」だった。構造的な同一性ができたので、名前と色はそこへ移る——そして起動時宣言では持ちようがなかったものを得る。**動いていないアカウントにも名前と色がある。** #40 が守ろうとした「名指せること」は、より強い形で満たされている。 **アカウントは種別を持つ。** `admin`(人間)、`Claude Code`、`Codex CLI`、`CLI(汎用)`、`mcp`(ローカル MCP サーバ)の五つである(#156 決定1、`mcp` は #193)。人間の種別は #192 まで `user` と呼んでいたものであり、名前を役割に合わせて `admin` に改めた(Master 判断、2026-09-28)。振る舞いは変えていない。種別は作成時の宣言であり、部屋も画面もそれを推測しない——部屋は接続の種類しか知らず、別のクライアントから参加した人間はセッションと同じ種類の接続で来るため、推測すれば取り違える。宣言できる唯一の時点はアカウントを作る瞬間であり、そこには既にフォームがある。 **起動する種別は三つあり、違いはアプリがその下のコマンドについて何を知っているかである。** `Claude Code` と `Codex CLI` はこのアプリが作法を持つ CLI を名指す(下記「種別は CLI の作法を持つ」)。`CLI(汎用)` はそうでない起動であり、行はその人のものになる——部屋の席が要るもののほかに、アプリは何も足さない。`Codex CLI` は #272 で追加された種別であり、保存名は `codex_cli` である。 種別が決めるのは、参加者のリストのグループ(「[参加者パネル](2-screen.md#参加者パネル)」)と、起動する行が持つ作法の二つである。部屋の参加者は依然として一種類であり、フレームにも名簿の判定にも種別は現れない。 既存の `config.json` の種別は起動コマンドから読み直す(下記「既存設定からの移行」)。 **画面の本人は種別 `admin` のアカウントである。** #53 が保留にした判断をここで決めた(#59)。本人が行を持てなかったことは、参加者のリストが二つに割れていた一因でもある。移行は `localStorage` の名前と色をそのアカウントの属性にするだけであり、どちらのキーもこれ以降は書かない。どのアカウントが本人かは `localStorage`(`pullcept.local-account`)が持つ——本人であることはこの画面の性質であってアカウントの属性ではなく、同じ config を別の画面で開けば本人は別になる。 本人のアカウントは削除できず、種別も `admin` から変えられない。部屋に居ることが「ここに居る」ことそのものであり、削除すれば居るための何かが無くなる。種別を変えれば、本人が起動する種別の見出しの下に並び、その名前で CLI を起動する選択肢が出る。 種別 `admin` のアカウントは起動しない。人間の下に spawn する CLI は無く、`start_session` が拒否する。その行に開始も出さない——拒否が唯一の知る手段になるのは、拒否の使い方として誤りである。稼働中のアカウントの種別は変更できない。そのセッションが起動した作業ディレクトリと起動オプションを、動いている最中に捨てることになるためである。 **名前は人間が読んで選ぶものである。** UUID などの機械的な一意化で置き換えない。宛先の候補に出るのはニックネーム(未設定時はこの名前)であり、そこから選べることが目的である。id が機械的な一意性を担うのは、名前をその責務から解放するためである。新規アカウントの既定名は既存と重ならない番号を取る——既定値が同じ名前の参加者を並べるのでは、#40 の欠陥を既定で再現することになる。 名前が空のままの決定は拒否する。 **アカウントの一覧と稼働中の名簿は、アカウント id で突き合わせる。** 名前で結ばない。名前は衝突しうるものになり、稼働中に改名されることもある。突き合わせの結果は一つのリストである(「[参加者パネル](2-screen.md#参加者パネル)」)。 **オフラインのアカウントはリストに出し、宛先の候補には出さない。** 宛先の候補の出所は部屋の名簿だけであり(「[参加者パネル](2-screen.md#参加者パネル)」)、席の無いアカウントはそこに存在しない。届かない相手を名指せる状態は、宛先を名簿から引くという判断(#43)の理由に反する。 ### 種別は CLI の作法を持つ **CLI ごとの作法はアプリが 1 か所で持つ**(#156、決定2)。持ち手は種別であり、置き場は `crates/mcp-config/` の `Cli` である。答えは五つある。 | 作法 | `Claude Code` | |---|---| | セッション id の渡し方 | 起動する行へ `--session-id {session_id}` を足す | | 再開の仕方 | `claude --resume {session_id}` | | `--settings` のステータスライン | 載せる(#155) | | 会話の在り処 | `/.claude/projects/<作業ディレクトリの slug>/.jsonl`(#131、決定3) | | 部屋の道具の名乗り | 起動する行へ `--append-system-prompt` で載せる(#147) | **欄でなくなった理由は実測である**(2026-09-17、Master 観測)。Claude Lin のセッション id が「なし」だったのは、そのアカウントの起動オプションに `--session-id {session_id}` の宣言が無かったためである。id の受け渡しはこのアプリの機能であり、人が手で書く形は、書き忘れた瞬間に静かに効かなくなる。同じ構造がステータスライン(#155)にもあった——Claude Code の設定の書き方でありながら、当時は起動する全アカウントに載っていた。撤去済みの hook(#149)も当時は同じ位置に在った。 **答えは種別ごとの分岐である。** 二つ目の CLI は五つの答えそれぞれの二つ目の腕であり、最初の CLI を前提にした場所をアプリ中から探し直すことではない。腕を書き忘れればコンパイルが名指す。 **`CLI(汎用)` はこの表を持たない**(決定5)。起動する行はその人のものであり、アプリは自分の知っている形をその上へ載せない——載せられる正しい形を持たないためである。知らないものを推測で埋めない。`{session_id}` の手書きの宣言はこの種別では有効なままである(上記「起動オプション」)。 **その種別が失うのはステータスラインと名乗りである。** パネルの五欄は「—」のままになり、その行の note に「制限中」も出ず(出所が五欄のうち二つであるため。#161)、その席は部屋の道具の名を起動時に受け取らない。部屋の登録の承認(#143)と、他のアカウントの登録を止めること(#103)と、channel エントリの統合(上記「起動オプション」)は種別に依らず載る——それは部屋の席が要るものであって、CLI について知っていることではない。 **会話の在り処も種別の答えである。** 再開の前に会話が実在するかを見る判断(「[トピック](1-room.md#トピック)」、#131)は、その CLI の置き場を知っていて初めて成り立つ。知らない種別では「分からない」であり、「無い」ではない——記録は残る側へ倒す。 ### 席は部屋の道具の名を起動時に受け取る **起動する行へ一文を載せる**(#147、決定1)。`--append-system-prompt` であり、内容は、その席が部屋へ発言するときに呼ぶ道具の完全名(`mcp__<.mcp.json の登録鍵>__say_to_room`。上記「`.mcp.json` の登録単位」)と、端末への出力が部屋に届かないことの二つ、それに部屋の詳しい作法の在り処である(#301)。 **作法の本文は写さず、在り処だけを指す(#301)。** 作法はサイドカーの `instructions`(「[部屋の作法](1-room.md#部屋の作法)」)の一か所にだけ在る。一文は「部屋の詳しい作法は部屋の MCP サーバ `mcp__<登録鍵>` の instructions に在り、届いたらそれに従う」と言う。`instructions` は MCP サーバが繋がった後に届くため、最初の部屋の発言より遅れることがある。最初の発言から効かなければならないのは道具の完全名の方であり、それは「道具の一覧が届く前でもこの名前で呼ぶ」として残す。 **Codex の席にも同じ一文を渡す(#301)。** app-server 方式の席は `developerInstructions` のキャラの本文の後に載せる(上記「[Codex の席を app-server 経由で起動する](#codex-の席を-app-server-経由で起動する299)」)。本文は Claude の席と同じ関数から作り、種別ごとに書き分けない。プレビューには「部屋のルール(developerInstructions のキャラクターの後)」として本文を出す。Claude の席では起動の行の `--append-system-prompt` にそのまま見える。 **hook 方式の Codex の席には渡さない(#301)。** 調べた結果は次のとおりである。 - Li+ のキャラの配送とは衝突しない。Li+ の output style hook は本文を SessionStart の `additionalContext` で配り(`sidecar/src/codex-style.mjs` の `body` が読む `hookSpecificOutput.additionalContext`)、`developer_instructions` とは別の入口である。 - だが `-c developer_instructions=…` は CLI の全 layer(user・profile・信頼済み project・手動 `-c`)で決まった有効な本文を置き換える。部屋の一文を足すには、起動のたびに有効な本文を読み、それに一文を足した全文を `-c` で載せ直すしかない。 - それは #301 当時の既定の起動経路(hook 方式の file mode)に、今は走らないモデル不要の `debug prompt-input` 二回(各15秒上限)と、その失敗による起動停止を新たに持ち込む。さらに #281 の file mode は本文を argv にも環境にも載せない契約であり([要件](0-requirements.md))、有効な本文の全文(日本語は TOML の Unicode escape で数倍に膨らむ)を起動の行に載せればその契約を破り、Windows の 8191 文字の上限で起動を拒否する席を新たに生む。 したがって hook 方式の席は従来どおりサイドカーの `instructions` だけで作法を受け取る。部屋の一文が要る Codex の席は app-server 方式に切り替える。 **渡す時点が起動時であることが要件である。** 道具の一覧とサイドカーの `instructions` は、そのセッションが最初に何か道具を呼んだ後にまとめて届く(実測、2026-09-17。返事をしなかったセッションには両方の注入が一度も無く、Bash を一度踏んだ後に注入されて以後は部屋へ返せている別のセッションと並べて確定した)。部屋の発言だけで動くセッションにはその契機が来ないため、道具の存在を知らされないまま文章だけ返す状態に入る——実機で見えていたのは、端末には合言葉を答えているのに部屋のログには何も無い形であり、記録にも `say_to_room` の呼び出しが一度も無かった。**呼んで失敗したのではなく、呼ぶものがあることを知らない。** モデルの違いではない(同日の切り分けの一つめはそう読んだが、交絡であった)。**2026-09-28 の実測では、`instructions` は道具を一度も呼んでいないセッションに届いていた**(Claude Code 2.1.283、#195。「[部屋は channel を使わなくなった](0-requirements.md#部屋は-channel-を使わなくなった195)」)。CLI の版で挙動が変わったと見られる。この一文は残す——古い版の CLI で同じ状態に入らないためであり、札の読み方を起動の行で補う必要はない(#195)。 サイドカーの `instructions` は同じことを言っているが、言うのが遅い(`sidecar/src/index.ts`、「[部屋の作法](1-room.md#部屋の作法)」)。起動する行は最初のターンの手前に在る唯一の場所である。 **文面は経路に依らない事実だけを持つ**(決定4)。channel を前提にした言い方はしない——将来チャンネルの機構が無くなっても、「部屋へ発言する手段はこの道具である」「端末出力は部屋に届かない」は変わらないためである。#195 で部屋の入力路から channel が外れたとき、この文面は変えずに済んだ。 **短く保ち、会話の作法は書かない**(決定5)。道具の完全名と返信の経路だけであり、100 トークン前後に収める。作法はサイドカーの `instructions` と道具の説明が持っており(#273)、道具を一度でも呼んだセッションは必ず持っている(2.1.283 では呼ぶ前から持っている。上記)。二つ目の写しをここへ置けば、起動のたびに払い、かつ一つ目から離れていく。 **行に載せられない文面は載せない**(決定6)。判定は `line_safe_text` であり、`line_safe_word`(#155、上記「セッションは自分の様子をステータスラインで知らせる」)と同じ文字集合に空白を足したものである。**違いは適用範囲である**——語は `--settings` の JSON の中を通り、しかも空白を持たない語は Windows の引用を素通りして裸で行に乗る(`portable-pty-patch/src/cmdbuilder.rs` の `append_quoted`)。この文面はそれ自体が一つの引数であり、空白を持つ引数は同じ引用が `"` で包むため、中身は `cmd.exe` の引用の内側へ入る。だが内側か外側かを決めるのは手前の引数までの `"` の個数であって、この引数自身の形ではない——そこへは乗らず、文字集合の側で断る(#152 / #154)。非 ASCII も同じ理由で断るため、文面は ASCII の英語である。読み手はモデルであり、失うものが無い。載せられなかったときに失うのはこの一文であって、起動ではない。 **自分で書いている行には足さない。** `--append-system-prompt-file` を書いている行へ足すと CLI が二つの併記を拒否して起動しない(CLI の文字列、v2.1.273)。`--append-system-prompt` を既に書いている行は、一つのコマンドラインに載った 2 つの同じフラグをどちらの順で読むかをこのアプリが確かめていないため足さない(`--settings` の併記を拒否するのと同じ地面である。上記「アカウントはキャラクターを持つ」)。 **既に在る会話へは届かない。** Claude Code は system prompt を会話ごとに一度だけ記録し、以後のリクエストと再開へはその記録をそのまま送る——後から別の文面を渡しても、`/compact` までは変わらない(`--system-prompt-snapshot` の既定は `on`。CLI の文字列、v2.1.273)。したがってこの一文が効くのは新しいセッションであり、この変更より前に始まったトピックの再開はそのままである。**この性質は CLI の文字列から読んだものであって、実機では見ていない。** 2026-09-17 の切り分けでも新規起動と再開は分かれたが、そこで分かれた原因は道具の注入であり、この記録の性質ではない——同じ向きに見えることを根拠にしない。 ### アカウントはキャラクターを持つ アカウントは、そのセッションが誰として喋るかを属性として持つ。値は作業ディレクトリの `.claude/output-styles/` に置かれた output style の frontmatter の `name:` であり、ファイル名ではない(実測、2026-08-26)。 **この欄は作業ディレクトリを一本化するためにある。** 二つのアカウントが別々のディレクトリに置かれていた理由を実測で洗ったところ、設計上の差は output style の一枚だけだった。`CLAUDE.md` と `settings.json` の差は末尾改行、`settings.local.json` の差は起動のたびに増えた `enabledMcpjsonServers` の残骸であり、`state/` は設定ではない。その一枚がアカウント側へ移れば、ディレクトリを分ける理由が無くなる。部屋の側は既にそれを想定して作られている——`.mcp.json` の登録鍵はアカウントごとであり、同じディレクトリへ 2 セッションが起動しても互いの識別を上書きしない(下記「`.mcp.json` の登録単位」)。 memory の鍵は作業ディレクトリであるから、ディレクトリを共有すれば記憶の共有はそれだけで成立する。配線は要らない。セッション履歴は同じディレクトリでも別々のファイルに分かれるため合流しない。 **選択は起動引数で行い、ファイルは書き出さない。** `--settings` は JSON 文字列を直接受ける(`claude --help` の `--settings `)。アカウントごとの設定ファイルを共有ディレクトリへ書き出す形は採らない——一本化のために共有したディレクトリを、アカウントの数だけ増やすことになる。`.mcp.json` への書き出しがあるのは、その登録が名前で解決される必要があるためであって(「[成立条件](0-requirements.md#成立条件すべて実測で確定いずれも外すと不通になる)」)、書き出しが既定の形だからではない。 `--settings` は作業ディレクトリの `settings.json` の `outputStyle` に勝つ(実測、2026-08-26)。したがってアプリは `settings.json` を書き換えない。ディレクトリの既定はそのまま残り、アカウントの選択だけがその上に重なる。キャラクターを宣言しないアカウントは `outputStyle` を足さずに起動し、その既定で立つ(`--settings` 自体は部屋の登録の承認のために載る。下記「セッションは自分の部屋の登録を承認して起動する」)。空欄は宣言しないことであり、消せば既定へ戻る。 **この選択は Windows の `cmd.exe /C` を素通りする**(実機、2026-08-26、AI が操作。「[実機で確認済み](4-status.md#実機で確認済み)」)。アプリはセッションを `cmd.exe /C` の下で起こすため、引用符を含む JSON 文字列がこの一段で壊れれば欄は実機で効かない。走る行と、その子の `claude.exe` が受け取る引数は同一であった。共有した作業ディレクトリから宣言どおりの人格が立つところまで、同日に辿っている。 **キャラクターは `--append-system-prompt` では渡さない。** Li+ の `rules/model/character.md` は、キャラクターが設定されている状態を「output style のファイルが在る**かつ** settings で `outputStyle` が有効」と定義している。system prompt へ流し込む形はこの定義から外れ、実際にはキャラクターが居るのに Li+ 側は「未設定」と読む状態になる。**これはこの欄についての判断であり、フラグそのものの禁止ではない**——部屋の道具の名乗りは同じフラグに載る(#147、上記「席は部屋の道具の名を起動時に受け取る」)。あちらは Li+ が読む状態を一つも持たない事実であり、この定義に触れない。 **欄であることが要件である。** 起動オプションの文字列の中へ `--settings` を書いても走る行は同じになるが、それではキャラクターがアカウントの属性になっていない。名前と色が起動時宣言から属性へ移ったのと同じ軸である(上記「アカウント」、#40)。 起動オプションが自前の `--settings` を持ち、かつキャラクターも宣言されているアカウントは、起動を拒否する。二つは同じ設定を指しており、一つのコマンドラインに載った 2 つの `--settings` がどちらの順で読まれるかをこのアプリは確かめていない。片方を落とすのでも選ぶのでもなく拒否するのは、起動フラグの検査と同じ理由である(上記「起動オプション」)——黙って半分だけ効いた起動は、動いたように見える。キャラクターを宣言していないアカウントの `--settings` は素通りする。 **載せられない名前は宣言されなかったものとして扱う**(#154。下記「人が書いた値は、起動の行が運べる形でだけ載る」)。この値は `--settings` の JSON の文字列であり、その中身は `cmd.exe` から見て引用の外側に立つ(#152)。判定は `line_safe_text` であり、通らなければ `outputStyle` を載せずに起動する——空欄と同じ状態であり、作業ディレクトリの既定が立つ。失うのはキャラクターであって起動ではない。 種別 `admin` のアカウントはこの欄を持たない。人間は自分として喋り、起動しないため style を選ぶ瞬間が無い。 ### キャラクターの本文はアカウントの窓で直す(#100) **ファイルの場所は、アカウントの三つの欄で一つに決まる**(2026-10-06、Master): 種別(Claude Code なら `.claude`、Codex CLI なら `.codex`)・作業ディレクトリ・キャラクターから、`<作業ディレクトリ>/<.claude|.codex>/output-styles/<キャラクター>.md`。「キャラクター」の項目は、名前の欄の下にこのファイルの本文の欄(複数行、中でスクロールする)とフルパスを出す。欄を持つのは Claude Code と Codex CLI だけで、`CLI(汎用)`・`admin`・`mcp` には出ない。Codex の席が実際に読む project root は helper が信頼済み project の層から決めるため(上記「Codex のキャラ選択」)、作業ディレクトリの親の project や `--cd` の先になることがある。この欄はそれを辿らず、作業ディレクトリの直下を開く。 **開いたときに読み、無ければ空で開く。** 種別・作業ディレクトリ・キャラクターのどれかが変われば、その場で読み直す。本文に保存していない変更があるときは読み直さず、変更が捨てられることを出して「読み直す」を置く。場所が変わったまま決定すれば、どちらのファイルへ書くのかが決まらないため止める。 **決定で、本文が変わっていればそのパスへ書く。** 変わっていなければ書かない。`output-styles` のフォルダーもファイルも無ければ作る。作るのも直すのも同じ欄一つである。取消は何も書かない。削除の操作は持たない。保存した本文が効くのは、その席の次の起動からである(欄の下に出す)。 - **同じファイルを他のアカウントも着ているなら、その名前を出し、もう一度の決定で保存する。** 保存でそちらのキャラも変わるためである。数えるのはキャラクター欄に名前を書いたアカウントだけで、空欄(作業ディレクトリの既定)のアカウントは数えない——既定の名前は Claude では `settings.json` の層、Codex では project の selector から決まり、この欄はそれを解かない。 - **Codex の作業ディレクトリに `.codex/output-styles` が無ければ、そのことを出し、もう一度の決定で作る。** フォルダーが現れると、そこで起動する Codex の席はすべてファイル方式に変わり、キャラクター欄が空の席は `character_instance.md` を要する(上記「Codex のキャラ選択」)。 - **開いた後に他所でファイルが変わっていれば、上書きせずに止める。** 開いた時のバイト列の指紋と照らす。 - **frontmatter の `name:` がキャラクター欄と違うファイルには、そのことを出す。** Claude は `name:` で選ぶため(上記)その名前では選ばれず、Codex の helper はファイル名と違う `name:` で起動を止める。 書き込みはアプリ側(`crates/character-file`)で行い、パスを検査する: キャラクターは区切り文字・`..`・Windows が予約する名前を含まない素のファイル名であること(Codex は上記の文法)、作業ディレクトリが絶対パスで在ること、書き先が junction やリンクを辿っても作業ディレクトリの中にあること。在るファイルはその場で書き換える——リンクで共有された style は一つのファイルのままである。文字コードは UTF-8 で、読めた改行の形(CRLF/LF)と BOM を保って書き戻す。UTF-8 として読めないファイルは開かない。大きさは 128 KiB まで(Codex の helper と同じ上限)。 **Li+ の更新はこのファイルを上書きも削除もしない**(2026-10-06、Li+ main `778d698` の原文で確認)。`Li+update.md` は `.claude/output-styles/character_Instance.md` を「無ければ作る、在れば触らない」で扱い(「If Target exists: skip (Create-only)」「No tag-based overwrite」)、他の名前には触れない。古いファイルを消す手順は `.claude/rules/` と `.claude/skills/` だけが対象である。Codex 側の移行 helper(`scripts/migrate_codex_output_style.py`)は既存の style があれば「Existing user style is protected」で止まり、他の install helper が書くのは `.codex/hooks/`・`.codex/hooks.json`・`.codex/rules/` だけである。したがって名前による警告は置かない。Li+ の更新は `.claude/settings.json` を上書きするが、このアプリはキャラを `--settings` で渡すため(上記)、この欄には関わらない。 ### アカウントは環境変数を持つ アカウントは、起動する CLI のプロセスへ渡す環境変数を持つ(#163)。最初の用途は、Li+ の cold-start 差分状態をセッションごとに分ける `LI_PLUS_AGENT_KEY`、gh のログインをアカウントごとに分ける `GH_CONFIG_DIR`、トークンを直接渡す `GH_TOKEN` の三つである。変数名はアプリが固定しない。 **欄は一つであり、普通の値と秘密の値を区別しない**(Master 判断)。`名前=値` を一行ずつ書く。最初の `=` で分けるため、値は `=` を含んでよい。空行は読み飛ばす。名前と値の前後の空白は落とす——貼り付けたトークンの末尾の空白は、意図された値ではなく誤りである。`=` の無い行、名前の無い行、名前に空白を含む行、同じ名前の二度書き(大文字小文字を区別しない。Windows がそう読むため)は、行を名指して決定を止める。 **値は命令文に載せず、プロセスの環境変数として渡す。** 起動の命令文(`launch_args`)は画面にプレビューされる。`--settings` の `env` に入れる形は、命令文とプレビューの両方に値を出すため採らない。渡し方は `src-tauri/src/pty.rs` の `spawn_pty_with_env` であり、`CommandBuilder::env` で CLI とその子の全てへ届く。部屋のトークン(`PULLCEPT_ROOM_TOKEN`)が既に通っている経路と同じである。値はログにも出さない。復号できない変数の拒否は変数の名前だけを名指し、値を名指さない。 **値は保存の前に Windows の DPAPI(`CryptProtectData`、現在のユーザーのスコープ)で暗号化する。** ログインしているユーザーに結びついた鍵で暗号化され、Claude デスクトップ(Electron `safeStorage`)と同じ仕組みになる。Windows の資格情報マネージャーは使わない(Master 判断)。`config.json` には名前が平文で、値が `dpapi:` に続く 16 進の暗号文で残る。アプリ固有の entropy を混ぜるため、同じユーザーが走らせる別のプログラムが素の `CryptUnprotectData` で開くことはできない(秘密ではなく、条件を一つ足すものである)。 **Windows 以外では暗号化せず、保存もしない。** DPAPI の無い環境では、値のある欄の決定を理由を示して止め、保存済みの値は開けない。平文で保存する代替は採らない——値を平文で置かないという約束が、その経路でだけ黙って外れることになる。このアプリは Windows 向けである。 **画面は値を持たない。** 欄に描くのはアプリの側が作った `名前=<マスク>` の行だけであり(`config::account_env_text`)、暗号文を開けるのはアプリの側だけである。マスクは先頭と末尾を見せ、間を `...` で置く——`GH_TOKEN=github_pat...Nx4h`。先頭は最大 10 文字、末尾は最大 4 文字で、どちらも値の 4 分の 1 を超えない。したがって短い値でも半分以上は隠れ、4 文字未満の値は `...` だけになる。数えるのは文字であり、バイトではない。 **出たままの行は、決定しても元の値を保つ。** 名前がそのままで、値がその変数のマスクと同じ行は、保存済みの暗号文をそのまま使う(`account_env::settle`)。そのため、他の欄だけを直す編集で秘密を打ち直す必要は無く、秘密が画面に平文で戻ることも無い。それ以外の行は打たれた値であり、決定のときにアプリの側で暗号化してから保存する。消した行の変数は消える。欄に触れていなければ暗号化の往復そのものが起きない。欄の表示に失敗したときは、欄を読み取り専用のまま残し、決定しても保存済みの変数を変えない——描かれなかった空の欄を「全部消した」と読めば、変数がすべて失われる。 **値の両端を囲む引用符は外して保存する**(#165)。`GH_TOKEN="github_pat_..."` や `'...'` のように、同じ種類の引用符(`"` か `'`)一組で囲まれた値は、その一組だけを外した中身を値とする。引用符が片方の端にしか無い値(閉じ忘れた `"github_pat_...` など)や、両端の引用符の種類が違う値は、行と名前を名指して決定を止める。どちらの端が意図だったかを推測して保存すれば、誰も書いていない値が暗号化されるためである。本当に引用符で終わる値は、もう一方の種類の引用符で囲めば書ける(`"abc'"` は `abc'`)。引用符を外すのは、打たれた値だけである。出たままの行の判定は外す前の値で行うため、引用符を含んだまま保存された値の隠し表示も、そのままなら元の値を保つ。 **保存済みの名前の値が、その隠し表示を書き換えたものに見えるときは、決定を止める**(#165)。隠し表示に引用符を足したり外したりしたもの、または隠し表示の先頭と末尾を保ったまま `...` の周りを打ち直したもの(長さが隠し表示より 4 文字を超えて長くないもの)がそれに当たる。暗号化すれば、本当の値が隠し表示の十数文字で上書きされ、元の値は戻らない。実機では、先頭に `"` が付いたまま保存されたトークンの隠し表示から `"` だけを消して決定し、トークンが失われた。止めるときは行と名前を名指し、隠し表示をそのまま残すか、本当の値を貼り直すよう促す。調べるのは、すでに保存されている名前(大文字小文字を区別しない)の行だけである。新しい変数の値や、`...` を含むだけの値(パスやオプション)は、普通の値として保存する。隠し表示が `...` だけの短い値は、`...` そのもの(引用符付きを含む)だけを書き換えとみなす。 **値はエラーにもログにも出さない。** 引用符と隠し表示の書き換えによる拒否も、行の番号と変数の名前だけを名指す。 開けない値(別のユーザーや別の機械で暗号化されたもの、ファイル上で壊れたもの)は `(復号できません)` と描き、行は残す。そのままなら元の暗号文を保ち、消すか入れ直せばそれが効く。**そのアカウントの起動は拒否する。** 宣言したトークンや設定ディレクトリを欠いたまま起動すれば、CLI は別の誰かとして、何事も無いように走るためである。拒否は席を取る前に行い、何も書き出さない。 **アプリ自身が起動のたびに設定する名前(`PULLCEPT_ROOM_TOKEN`、`PULLCEPT_LAUNCHED_AS`、`PULLCEPT_ROOM_ID`)は、欄に書けば決定を止める。** 起動の時点で黙って上書きすれば、設定したように見えて効いていない変数になる。起動の組み立てでもアカウントの変数を先に、アプリの変数を後に置き、拒否が迂回されてもアプリの値が勝つ。名前の一覧は `crates/mcp-config` の `APP_LAUNCH_ENV` 一つであり、拒否も起動もそこから読む。`PULLCEPT_LAUNCHED_AS` がアカウントから書けると、別のアカウントの登録を自分の CLI から着席させられる(下記「セッションは自分以外の部屋の登録を起動しない」、#208)。 **起動した CLI のプロセスには、どの部屋(トピック)で起動したかも載せる**(`PULLCEPT_ROOM_ID`、`crates/mcp-config` の `ROOM_ID_ENV`、#264)。値は、その起動が書いた `.mcp.json` の登録の `env` に同じ名前で在る部屋 id と同じである。部屋のトークンはアドレスごとに一つで、同じ実行の部屋すべてが共有するため、トピックを見分けない。登録は(アカウント, 部屋)の組ごとに名前が決まり、セッションを止めても残るため、同じアカウントの登録が `.mcp.json` に複数並ぶことがある。プロセスの環境変数しか読めない外部のもの(フックなど)は、アカウント(`PULLCEPT_LAUNCHED_AS`)とこの部屋 id の二つで、自分の登録を一つに選ぶ。登録の側の形は変えない。 **共有している作業ディレクトリにファイルを増やさない。** 環境変数はアカウントの属性として `config.json` に在り、ディレクトリには何も書き出さない。キャラクターの選択を起動引数で行う理由と同じである(上記「アカウントはキャラクターを持つ」)。 ### 人が書いた値は、起動の行が運べる形でだけ載る Windows ではセッションの起動が `cmd.exe /C` を通る(`src-tauri/src/pty.rs`)。`& | < > ^ ( )` は `cmd.exe` が反応する文字であり、引用の外側に立てば行はその位置で終わる。`"` は反応される文字ではなく数えられる文字であり、引数の引用(`append_quoted`)が `\"` へ直しても `cmd.exe` は数えるだけであるため、偶奇が反転して以後の中身が引用の外へ出る(下記「セッションは自分の様子をステータスラインで知らせる」、#152)。 **アプリが自分で書く値はこの機構から塞いである**(#152 / #155 / #147)。**人が書いた値は三つ残っていた**(#154、#153 のレビューで観測)——キャラクター(`--settings` の JSON の文字列値)、起動オプション、command である。いずれも #151 より前から在る。最も起こりやすいのはキャラクターの欄であり、自由入力がそのまま JSON の文字列値へ入るためである。 **判定は二つに分かれる。分ける軸は、その値をアプリが書いたのか人が書いたのかである。** - **キャラクターは `line_safe_text`**(上記「席は部屋の道具の名を起動時に受け取る」と同じ集合)。`--settings` の JSON の中の文字列値であり、その位置にアプリが置く値は既にこの集合で保っている。載せられない名前は宣言されなかったものとして扱う——空欄と同じ状態であり、作業ディレクトリの既定が立つ。 - **起動オプションと command は `console_safe`**。`cmd.exe` が反応する文字(と行そのものを終わらせる制御文字)だけを断り、残りは通す。この二つは人が打つコマンドラインであり、上の集合では成り立たない——Windows のパスは `\` を持ち、フラグは `=` を持ち、このアプリ自身の置換子 `{session_id}` は波括弧を持つ。`cmd.exe` はどれにも反応しない。**これは四つ目の綴りではない**(決定5)。上の集合とは別の集合——`cmd.exe` が反応する文字そのもの——であり、それを書くのはここ一箇所である。#152 の判断が散文で名指していたものが述語になった。 **起動は止めない。載せられない値を載せずに起動する**(決定3)。#155 と #147 が採った形である。**ただし command には載せないという逃げ道が無い。** 載せられなければ起動そのものを拒否し、理由を画面に出す。**再開コマンドは一行まるごと同じ側である**——その引数は戻り方そのものであり、落として起動すれば戻らずに新しいセッションが立つ。黙って半分だけ効いた起動は、動いたように見える(起動フラグの検査、`--settings` の併記の拒否と同じ地面である)。 **起動オプションは欄ごと落とす。一つの引数だけを落とさない**(決定6)。オプションはフラグと値の組であり、値だけを落とせばフラグは行の次の引数を自分の値として取る——そこに在るのはアプリが載せた引数である。人はこの欄を一つの文字列として書くため、落ちるのもその一つである。値の中から文字を抜くことはしない——人が書いた行から、誰も書いていない行を作ることになる。落ちた欄が `{session_id}` を持っていた場合、その起動は id の置き場を持たない行であり、id を配らない(上記「起動オプション」の一度だけの差し込みと同じ判断である。配れば、CLI が受け取っていない id をトピックが記録する)。 **モーダルは、保存の前にそれを言う**(決定4)。どの欄が起動時にどうなるかを、欄を書いている最中に出す。保存は止めない——直すかどうかはその人の判断であり、`( )` を含むインストール先のように直せない場合もある。拒否の側(command と再開コマンド)は起動時にも拒否が立つ。そちらが権限であり、モーダルは先に言うだけである(キャラクターと `--settings` の併記が既に採っている形である)。判定はアプリの側が持ち(`session::launch_field_report`)、画面が持つのは文である——同じ文字集合の二つ目の実装を TypeScript に置けば、テストされている一つ目から離れていく(`parse_launch_options` と同じ理由である)。 ### アカウントは画像を持てる(#236) 発言の行と通知の畳みの見出しの丸印(「[会話面の見た目(#225)](2-screen.md#会話面の見た目225)」)に、頭文字の代わりに出す画像である。Master「アカウントのアイコンも画像入れたりできるとなおいい」(2026-10-02)。頭文字だけでは、頭文字の同じアカウント——Claude Lay と Claude Lin はどちらも「C」——が丸印では見分けにくい。置き場所・大きさ・形式・操作の場所は、Master の「確認とらずに進めて~」(2026-10-02)を受けて AI が決めた。 **操作はアカウントの窓の「画像」の欄にある。** 丸印の見本と「選ぶ」「外す」を並べる。行のメニューには足さない。他の欄と同じく決定まで何も書かない(「アカウントの窓は同一 webview 内のモーダルである」)——選んでから取消せば、ファイルは残らない。「外す」は見本に画像が出ている間だけ出る。 **保存されるのは、常に 128×128 の PNG 一枚である。** 選んだ画像は画面の側で中央を正方形に切り抜き、128×128 に縮めて PNG にしてからアプリへ渡す。読み込める形式は png・jpeg・webp・gif であり、動く gif は一枚目になる。元の画像の大きさは問わない。渡るものが常に小さな PNG 一枚であるため、上限を設けていない。アプリの側は、渡されたものが PNG であることだけを確かめて置く。 **置き場所は、アプリのデータ置き場(`config.json` と同じ場所)の `avatars/` である。** ファイル名はアカウント id に `.png` を付けたもの。id のうち ASCII の英数字と `-` と `_` 以外のバイトは `%XX` で書く——`mcp` アカウントの id は人が手で書くファイルのエントリ名から来るため(`mcp-<エントリ名>`)、パスの区切りを含みうる。`%` 自身も書き換えるため、二つの id が同じファイル名になることは無い。アプリが作る id(`account-1` や UUID)は書き換わらない。 **`config.json` が持つのは画像の有無だけである**(`avatar`、`true` / `false`)。画像そのものを持たせると、どのアカウントを保存しても画像ごと読み書きすることになる。画像の欄を持たない既存の `config.json` は `false` として読む——欄が無かった当時のアカウントは、どれも頭文字だった。 **画像を使うのはこの画面だけである。** 部屋の名簿にもソケットの取り決め(`crates/room-floor`)にも載せない。行の丸印はその行のアカウント id で画像を引く——名前は衝突しうるため、名前では引かない。アカウントを持たない話者と、画像を持たないアカウントは頭文字のままである。画面は起動時に、画像を持つアカウントのファイルを一度ずつ読み、届いた時点で、それより前に描いた丸印も描き直す。 **読めない画像は、黙って頭文字に戻す。** ファイルが無い、壊れているなど、読み込みに失敗した丸印は頭文字で描く。`avatar` の値は直さない——次に「選ぶ」で決定すれば、ファイルは書き直される。 **アカウントを削除すると、その画像も消す。** `avatar` の値によらず消す——残ったファイルを見つけるものは無く、同じエントリ名で `mcp` アカウントを作り直せば同じ id になる。 ### アカウントの窓は同一 webview 内のモーダルである 作成・編集・削除はモーダルで行う。OS の別ウィンドウにはしない——一つの行為の途中の一段であり、別ウィンドウは探し、動かし、アプリの裏へ失くすものである。 **モーダルは明示的な決定と取消を持つ。欄の `change` では保存しない。** フォームは下書きを持ち、決定して初めてアカウント一覧へ入る。`+` は空のフォームを開くだけであり、決定するまでアカウントを作らない。取消は何も残さない——新規なら作られておらず、編集なら反映されていない。Escape も取消である。 これは形の移動ではない。以前は各欄が `change` で保存されており、`+` を押した瞬間にアカウントが実在し、以後はその場で直していく形だった。決定も取消も無かった。モーダルへ移すこと自体はこれを直さない——モーダルの中で `change` 保存を続ければ同じである。直しているのは保存の時点である。 削除はモーダルの中にあり、2 クリックを取る。一度目は武装するだけで、二度目が実行する。稼働中のアカウントと、この画面の本人のアカウントは削除を拒否する。終了も #71 まではこの形であったが、確認のダイアログへ移った(「[診断面](2-screen.md#診断面)」)。削除を揃えるかは #72 で扱う。 ### アカウントの窓は項目を横に並べる **窓の欄は、横のメニューで項目に分ける(#304、2026-10-06 Master 提案)。** 欄が増えて一度に読めなくなったためである。スクロールだけにする案より、#100 でキャラクターの本文の欄が入った後も探しやすい。形は設定のダイアログと同じであり、左に項目の一覧、右にその項目の欄を置く。 - **基本**: 名前・種別・色・画像 - **キャラクター**: キャラクター名・本文の欄(#100、下記「[キャラクターの本文はアカウントの窓で直す](#キャラクターの本文はアカウントの窓で直す100)」) - **起動**: 作業ディレクトリ・CLI コマンド・(`Codex CLI` の注記と app-server 方式)・起動オプション・(`CLI(汎用)` の再開コマンド)・走る行の表示・起動の行に載らない値の知らせ - **環境変数** - **サーバ**: 種別 `mcp` のサーバの節(「[ローカル MCP サーバ](1-room.md#ローカル-mcp-サーバ)」) **項目は種別が決める。** `admin` は起動しないため「基本」だけを持ち、起動する三つの種別は「基本」「キャラクター」「起動」「環境変数」を、`mcp` は「基本」と「サーバ」を持つ。項目の中で種別によって出る欄(`Codex CLI` の app-server 方式、`CLI(汎用)` の再開コマンド)は #304 の前と同じである。種別を変えて今の項目が無くなれば「基本」へ戻る。窓は開くたびに「基本」で開く。 **項目は一つのフォームの面であり、別のフォームではない。** 項目を切り替えても書きかけの欄は消えず、決定は見えていない項目の欄も含めて全部を読む。決定を止める理由と、削除・取消・決定は、どの項目の下にも出る。止める理由が見えていない項目の欄についてのもの(名前・種別・起動オプションの `--settings`・環境変数・サーバ・画像)なら、その項目へ切り替えて欄に焦点を置く——見えていない欄は焦点を受け取れないためである。 **狭い窓では、横のメニューが上のタブの列になる。** 欄の名前も箱の横から上へ移る。窓の幅で判定する(CSS の container query)。 **「環境変数」の項目は、窓の幅に関わらず、見出しを上・欄を下に置く(#322、2026-10-07 Master 要望)。** 形は Claude デスクトップの「ローカル環境を編集」に倣う。この項目の欄は一つだけであり、横に見出しを置けば、一行に一つ書く `名前=値` の幅をその分だけ削るためである。欄は項目の幅いっぱいと、残りの高さを使い切り、10 行より低くはならない。字は等幅のままであり、長い行は折り返さずに欄の中で横へスクロールする。欄の下に「1 行に 1 つ、名前=値 の形で書きます。」と書き方を添える。「キャラクター」の項目の本文の欄は 14 行の高さを既に持つため、この形にはそろえない(AI 判断)。 ### ランチャ行は無い 会話面の上に、アカウントの選択と参加だけを持つ行があった。#59 が名前・色・作業ディレクトリ・起動オプションをモーダルへ移した結果、残ったのは `