# claude agents と /bg：Agent View 実装の分析

# `claude agents` と `/bg` —— Agent View {実装|じっそう}の{分析|ぶんせき}

> ソースコード：`2.1.139-unbundled/extracted/src/entrypoints/cli.js`（ビルド{版|ばん} `208bf4b4`、2026-05-11）。
> {公式|こうしき}ブログ{参考|さんこう}：[Agent view in Claude Code](https://claude.com/blog/agent-view-in-claude-code)。

"Agent view" は、**1つの{機能|きのう}、3つの{入口|いりぐち}、ディスク{上|じょう}の1つの job モデル**です：

| {入口|いりぐち} | {動作|どうさ} |
|---|---|
| `claude agents`（CLI サブコマンド） | daemon が{有効|ゆうこう}な{場合|ばあい}は **fast path** に{進|すす}み、Commander が argv を{解析|かいせき}する{前|まえ}にインターセプトして **FleetView** TUI をマウントします。daemon が{無効|むこう}な{場合|ばあい}は、Commander に{登録|とうろく}された `agentsHandler` にフォールバックしますが、それは *{設定済|せっていず}みサブエージェント（subagent）{定義|ていぎ}* のプレーンテキスト{一覧|いちらん}にすぎません。 |
| `claude --bg [task]` / `--background` | {新規|しんき}セッションは on-demand daemon によってそのままバックグラウンドで{起動|きどう}され、{親|おや}プロセスは{短|みじか}いプロンプトを{表示|ひょうじ}して{終了|しゅうりょう}します。 |
| `/bg`（スラッシュコマンド、`/background` の{別名|べつめい}） | **{現在|げんざい}**の REPL セッションをバックグラウンドに fork し、フォアグラウンドプロセスは{終了|しゅうりょう}します。インタラクティブ REPL では、**{空入力|からにゅうりょく}で{左方向|ひだりほうこう}キーを{押|お}す**ことでも{同|おな}じ fork パスがトリガーされ、さらに**プロセス{内|ない}で FleetView を{再|さい}マウント**します。 |

3つのパスはいずれも{最終的|さいしゅうてき}に `~/.claude/bg-jobs/<short>/state.json` ファイル + Unix ドメインソケット daemon（つまり "on-demand daemon"）へ{収束|しゅうそく}します。FleetView は{本質的|ほんしつてき}には、そのディレクトリをポーリングして{読|よ}み{取|と}るリーダーであり、daemon に{接続|せつぞく}する RPC クライアントでもあります。

{以下|いか}で{使|つか}う{命名規則|めいめいきそく}：{難読化|なんどくか}されたシンボルを{意味|いみ}のある{名前|なまえ}に{改名|かいめい}し、{初出|しょしゅつ}のときに `/* <元の名前> */` を{付|つ}けます。{改名|かいめい}していないもの（`oqH`、`pT`、`Q7`、`B9`、`Q`、`yH`、`xH` など）は、ホストプロセスが{共有|きょうゆう}するヘルパー{関数|かんすう}（{状態|じょうたい}ファイル IO、テレメトリ、{優雅|ゆうが}な{終了|しゅうりょう}）です。ほかのプロジェクトに{移植|いしょく}する{際|さい}は、1つずつ{置|お}き{換|か}える{必要|ひつよう}があります。

---

## 1. データモデル

```ts
// state.json —— バックグラウンド job ごとに1つ、~/.claude/bg-jobs/<short>/ に保存される
interface JobState {
  proto: number;                       // I1、schema バージョン番号
  short: string;                       // 8桁の16進プレフィックス、CLI / FleetView でのアドレス指定に使う
  sessionId: string;                   // 完全な uuid、transcript ファイル名に対応
  createdAt: string;                   // ISO
  updatedAt: string;                   // ISO、書き込むたびに bump
  firstTerminalAt?: string;            // 初めて終止状態に入った時刻

  // 身元 / 表示
  template: { name: string; description: string; initialPrompt?: string };
  agent?: string;                      // --agent を使った場合、ここは subagent 型
  intent: string;                      // 最初のユーザーメッセージ、≤200 文字
  name?: string;                       // 明示的な -n/--name。なければ自動派生
  nameSource?: "user" | "auto";
  detail?: string;                     // 直近の assistant 出力の抜粋、≤120 文字
  color?: string;

  // 活動状態
  state: "running" | "stopped" | "errored" | "done" | ...;
  tempo?: "active" | "idle" | "blocked";
  needs?: string;                      // agent が待っているもの
  block?: { questions?: Question[] };  // tempo === "blocked" のとき
  suggestedReply?: string;             // PeekPane で Tab を押すと補完できる事前入力返信
  output?: Record<string, string>;     // インライン表示するいくつかの headline 行
  inFlight?: unknown;
  resumeSessionId?: string;

  // ソート / ピン留め
  sortOrder?: number;
  stateSortOrder?: number;
  pinned?: boolean;

  // 位置
  cwd: string;
  originCwd?: string;
  worktreePath?: string;
  worktreeBranch?: string;
  worktreeHookBased?: boolean;

  // リンク
  children?: Array<id: string; kind: "frame">;
  source?: "shell" | "slash" | "fleet" | "spare" | "respawn" | "left_arrow";

  // その他の帳簿付け
  respawnFlags?: string[];
  env?: Record<string, string>;
}

interface Question { text: string; options: string[] }

// FleetView のメモリ内で1つの JobState から構築される行構造
interface JobRow id: string;          // === short
  state: JobState;
  activity: "flowing"

// FleetView はこの action を通じて mountFleetView に報告する
type FleetAction =
  | { type: "done" }
  | type: "open"; job: JobRow; query?: string; collapsed: string[];
      groupMode: "state";
```

`agentsHandler` のフォールバック処理器が見る「agents」は別物です——*subagent 定義* の一覧（`uk()` が返す `activeAgents`）で、出所（user / project / local / plugin / built-in）ごとにグループ化されています。**これは FleetView 内の「session 行」とは関係ありません。**

---

## 2. 定数と文言

```ts
// 全体スイッチ
const ENV_DISABLE = "CLAUDE_CODE_DISABLE_AGENT_VIEW";
const SETTING_DISABLE = "disableAgentView";            // 管理対象設定
const SETTING_DEFAULT_TO_FLEET = "defaultToAgentsView";// ユーザー設定（462620 行）

// fork 内で `claude agents` を起動するときに job を事前選択
const ENV_PRESELECT = "CLAUDE_AGENTS_SELECT";

// フォーカスを失った fleet view を自動再起動（ハートビート）
const ENV_AUTORELAUNCH = "CLAUDE_AGENTS_AUTO_RELAUNCHED_AT";
const AUTO_RELAUNCH_UNFOCUSED_MS = 3_600_000;          // 1 時間
const AUTO_RELAUNCH_MIN_INTERVAL_MS = 21_600_000;      // 6 時間

// daemon プロトコルバージョン
declare const I1: number;

// `claude --bg < piped` 時の stdin 上限
const zB8 = 16 * 1024;                                 // バイト

// bg dispatch のたびに表示されるヒントブロック
function formatPostBackgroundHints(short: string, idle?: string) {
  return [
    `backgrounded · ${cyan(short)}${idle ? dim(" " + idle) : ""}`,
    dim("  claude agents".padEnd(28) + "list sessions"),
    dim(`  claude attach ${short}`.padEnd(28) + "open in this terminal"),
    dim(`  claude logs   ${short}`.padEnd(28) + "show recent output"),
    dim(`  claude stop   ${short}`.padEnd(28) + "stop this session"),
  ].join("\n");
}
```

バックグラウンド化に関する重要な文言：

* `"Backgrounding…"`—— バックグラウンドで立ち上げている最中の spinner。
* `"<N> tasks running — the forked session won't carry live processes."`—— 確認ダイアログのサブタイトル。
* `"Background anyway (tasks will be abandoned)"` / `"Stay"`—— 確認 / キャンセルボタン。
* `"Cannot background — session persistence is disabled, so the forked job would have nothing to resume."`

---

## 3. Host-Framework 契約

次の表は、agent view モジュールが依存している、cli.js 内で他モジュールから提供される補助関数です。別のプロジェクトへ移植する場合は、各項目をそちら側の対応する実装に置き換える必要があります。

| シンボル | シグネチャ | 行番号 | 用途 |
|---|---|---|---|
| `oqH` | `(seed) => JobState` | 542886 で使用 | デフォルト値から新しい `JobState` を構築する。 |
| `pT` | `(jobDir, partial) => Promise<void>` | 542884 で使用 | `state.json` を atomic に merge 書き込みする。 |
| `Q7` | `(jobDir) => Promise<JobState\|null>` | 542964 | `state.json` を読む。 |
| `W4` | `(short) => string` | 542879 | `~/.claude/bg-jobs/<short>/`。 |
| `rW` | `() => string` | 543233 | jobs のルートディレクトリ。prefix 検索ではここを `readdir` で走査する。 |
| `k_` | `() => string` | 542814 | 現在プロセスの session id（fork 内で設定される）。 |
| `L7` | `() => boolean` | 543959 | `CLAUDE_JOB_DIR` が存在するか → つまり「自分がすでにバックグラウンド fork かどうか」。 |
| `getCurrentJobDir` / `Xv3` | `() => string \| undefined` | 544011 | `process.env.CLAUDE_JOB_DIR` を返す。 |
| `V$` | `(req, opts?) => Promise<DaemonResp>` | 543031 | daemon との Unix socket RPC（`op: "list" \| "dispatch"`）。 |
| `mW_` | `() => Promise<{ok,reason?}>` | 543277 | daemon のヘルスチェック（`claude attach` 用）。 |
| `MQ` | `(short) => Promise<{outcome,msg?}>` | 543286 | 「自分をこの job に接続する」RPC。 |
| `PGK` | `(short, {alreadyInAlt}) => Promise<AttachResult>` | 606723 | attach 中にローカル PTY ↔ daemon 内 job の双方向ブリッジを担当する。 |
| `fW6` | `(jobId, {force?,knownState?,knownAlive?}) => Promise<RespawnResult>` | 606700 | worker がすでに死んでいれば起動する（Re·spawn）。 |
| `B9` | `(exitCode, reason, opts?) => Promise<never>` | 543885 | フォアグラウンドプロセスの graceful exit。 |
| `aH7.setupGracefulShutdown` | — | 651511 | SIGINT/SIGTERM を `B9` に接続する。 |
| `cH.createRoot` / `Mu` | `(opts) => Promise<InkRoot>` | 651518 | stdout を共有する Ink ルートノードを作成する。 |
| `v3_` | `({exitOnCtrlC}) => Promise<InkRoot>` | 606748 | PTY 引き渡し後に Ink ルートを再構築する。 |
| `Q5.get(process.stdout)?.handoffAltScreen` | — | 606681 | attach 用に alt-screen の制御権を PTY に渡す。 |
| `Q5.get(process.stdout)?.unmount` | — | 606926 | in-process で fleet view を再マウントする前に Ink をアンマウントする。 |
| `Q("tengu_*")` / `yH` / `xH` / `j6` | テレメトリ sink | 各所 | `tengu_bg_dispatch`、`tengu_background_fork`、`fleet_view_*`。 |
| `IK("allow_remote_sessions")` | ポリシーチェック | 644468 | 組織レベルのスイッチ。 |
| `Xc` = `isAgentsFleetEnabled()` | — | 149497 | 総合ゲート：managed config、env、fast-path policy を統合する。 |
| `fleetGateRejected(verb)` | `(string) => Promise<never>` | 651424 | 拒否理由を出力して終了する。 |
| `EAH("claude agents")` | — | 604574 | ターミナルウィンドウのタイトルを設定する。 |
| `vh()` / `zAH()` | 文字列 | 606686/606747 | alt-screen に入る / 退出する ANSI シーケンス。 |
| `b_()` / `H6(updater)` | 設定リポジトリ | 604558/605650 | `fleetViewGroupMode` などを永続化する。 |
| `B26(cwd, {q,collapsed})` / `ebK` / `qxK` | bg-view UI 状態 | 604647 | cwd ごとに「前回の検索と折りたたみ状態」を記憶する。 |
| `s6()` | `() => {columns, rows}` | 604583 | ターミナルサイズ。 |
| `pA()` | `() => boolean` | 604578 | ターミナルが現在フォーカスされているか。 |
| `un_()` | alt-screen flag | 606680 | alt-screen を所有しているか。 |
| `lT()` | `() => WorktreeInfo \| null` | 606956 | 現在の worktree 情報。 |
| `g$()` / `qp()` | name 検索 | 543840 | ユーザー設定 / 自動派生された job 名。 |
| `WE(msg)` / `oI(msg)` | transcript 補助 | 543828 | assistant/user メッセージ本文を抽出する。 |
| `viH(text)` | メタ情報テキスト判定 | 543834 | intent を選ぶ際にスラッシュコマンドのエコーをスキップするために使う。 |
| `LD(state)` | `(JobState) => boolean` | 544019 | job が終了状態にある。 |
| `npH(state)` | activity 分類器 | 602954 | `"success"`/`"failure"`/`"stopped"` または undefined。 |

daemon プロトコル（`V$`）は、少なくとも次のリクエストを受け付ける：

```ts
type DaemonReq =
  | { proto: number; op: "list" }
  | { proto: number; op: "dispatch"; d: JobDispatch; timeoutMs?: number };
type JobDispatch = {  // 543001-543027
  proto: number; short: string; sessionId: string; createdAt: number;
  source: "shell"|"slash"|"fleet"|"spare"|"respawn"|"left_arrow";
  cwd: string;
  launch:
    | { mode: "prompt"; args: string[] }
    | { mode: "resume"; sessionId: string; fork: boolean; flagArgs: string[] };
  respawnFlags: string[]; env: Record<string,string>; reattachEnv?: unknown;
  worktree?: { path: string; ownershipToken: string };
  isolation: "worktree" | "none";
  agent?: string; routine?: string;
  seed: { intent: string; name?: string };
  cols?: number; rows?: number;
};
```

---

## 4. 登録

### 4a. `/bg` スラッシュコマンド（543995 行）

```ts
const backgroundCommand = {
  type: "local-jsx",
  name: "background",
  aliases: ["bg"],
  description: "Continue this session in the background and free the terminal",
  immediate: (raw) => !raw.trim(),               // 追加の prompt がない場合は確認をスキップ
  isEnabled: () => true,
  load: () => Promise.resolve().then(() => (_loadBgImpl(), _bgImplExports)),
};
```

`load()` を解決すると、`{ default: handleBgSlashCommand }` をエクスポートするモジュールが得られる。その中身は次のとおり。

```ts
// 543958 行 —— Yv3
const handleBgSlashCommand = async (
  onDone: (err?: string) => void,
  ctx: { messages: Message[] },
  args: string
) => {
  if (L7() /* 自分自身が bg session */) {
    Q("tengu_background_already_bg", {});
    onDone(); q4H();
    return null;
  }
  if (YDH() /* セッション永続化が無効 */)
    return (onDone(
      "Cannot background — session persistence is disabled, so the forked job would have nothing to resume."
    ), null);

  const extraPrompt = (args ?? "").trim();
  const seed = extractSeedFromTranscript /*Bf6*/ (ctx.messages, extraPrompt);
  if (seed === null)
    return (onDone("Nothing to background yet — send a message first."), null);

  return React.createElement(BackgroundConfirmDialog /*Jv3*/, {
    onDone, prompt: extraPrompt, seed, messages: ctx.messages,
  });
};
```

### 4b. `/stop` スラッシュコマンド（bg job 内でのみ有効、544066 行）

```ts
const stopJsx = {
  type: "local-jsx", name: "stop", immediate: true, isEnabled: L7,
  description: "Stop this background session; transcript and worktree are kept",
  requires: { ink: true },
  load: () => ({ call: (done) => (done(), markSelfStoppedAndExit("stop_command"), null) }),
};
const stopNonInteractive = {
  type: "local", name: "stop", supportsNonInteractive: true,
  isEnabled: L7,
  load: () => ({ call: () => (markSelfStoppedAndExit("bridge"), { type: "skip" }) }),
};
```

### 4c. `claude agents` Commander サブコマンド（647846 行、**fallback のみ**）

```ts
H.command("agents")
  .description("Manage background and configured agents")
  .option("--setting-sources <sources>",
          "Comma-separated list of setting sources to load (user, project, local).")
  .action(async () => {
    const [{ agentsHandler }, { createSubcommandRoot }] = await Promise.all([
      import("./agents-handler.js"),
      import("./subcommand-root.js"),
    ]);
    await agentsHandler(await createSubcommandRoot());
    process.exit(0);
  });
```

### 4d. 総合スイッチ設定項目（53308 行）

```ts
disableAgentView: {
  type: "boolean",
  description:
    "Disable agent view (`claude agents`, `--bg`, /background, the on-demand daemon). " +
    "Typically set in managed settings. Equivalent to CLAUDE_CODE_DISABLE_AGENT_VIEW=1.",
};
```

---

## 5. エントリ / ディスパッチ —— `mainCliEntry()`（CLI の `main`）

`mainCliEntry`（難読化名 `je3`）は **Commander より前**に走り、`claude agents` が `main()` の重い import 経路を通らず、直接 FleetView に入れるようにしている。

```ts
// 651265
async function mainCliEntry /*je3*/ () {
  const argv = process.argv.slice(2);

  // …  --version / --daemon-worker / --bg-pty-host / --bg-spare / bridge / daemon などの早期 return 経路

  // ───────── bg 動詞 fast path（651387 行） ─────────
  if (
    argv[0] === "logs" || argv[0] === "attach"  || argv[0] === "stop" ||
    argv[0] === "kill" || argv[0] === "respawn" || argv[0] === "rm"   ||
    argv.includes("--bg") || argv.includes("--background")
  ) {
    if (!isAgentsFleetEnabled()) return fleetGateRejected(verb);
    const bg = await import("./bg-cli.js"); // QGK
    switch (argv[0]) {
      case "logs":    return bg.logsHandler(argv[1]);
      case "attach":  return bg.attachHandler(argv[1]);
      case "stop":    case "kill":  return bg.stopHandler(argv[1]);
      case "rm":      return bg.rmHandler(argv[1]);
      case "respawn": return bg.respawnHandler(argv[1]);
      default: {
        // --bg / --background はまだ argv 内にあり、最初の positional が prompt になる
        Q("tengu_background", { via_flag: true, via: "flag" });
        await bg.handleBgFlag(argv);
        return process.exit(process.exitCode ?? 0);
      }
    }
  }

  // ───────── fleet-view fast path（651485 行） ─────────
  const wantsAgents = argv[0] === "agents" && argsAreOnlyDebugFlags(argv.slice(1));
  const wantsDefault = argsAreOnlyDebugFlags(argv) && process.stdin.isTTY && process.stdout.isTTY;
  if ((wantsAgents || wantsDefault)) {
    enableConfigs();
    const defaultsToAgents = getGlobalConfig().defaultToAgentsView === true;
    if (wantsAgents || defaultsToAgents) {
      const policyErr = await loadFastPathPolicy();
      if (policyErr) exitWithError(policyErr);
      await ensureFleetGateHydrated();
      if (isAgentsFleetEnabled()) {
        initSinks(); setupGracefulShutdown(); setIsInteractive(true);
        Q("tengu_fleetview", { defaultToAgentsView: defaultsToAgents });
        const root = await createRoot({ exitOnCtrlC: false });
        consumeEarlyInput();
        await mountFleetView(root);
        await gracefulShutdown(0, "other", { suppressResumeHint: true });
        return;
      }
    }
  }

  // …それ以外 → main()（つまり Commander。agents サブコマンドの fallback を含む）
}

function argsAreOnlyDebugFlags /*caK*/ (argv) {
  for (let i = 0; i < argv.length; i++) {
    const a = argv[i];
    if (["--debug","-d","--debug-to-stderr","-d2e"].includes(a)
        || a.startsWith("--debug=") || a.startsWith("--debug-file=")) continue;
    if (a === "--debug-file" && i + 1 < argv.length) { i++; continue; }
    return false;
  }
  return true;
}
```

> 重要な結論は 2 つ：
> 1. `claude agents` は Commander サブコマンドの中で、parse 前に**唯一**横取りされる。そのため実際の実装は `mountFleetView` であり、`.action()` 内の body ではない —— 後者が実行されるのは `isAgentsFleetEnabled()` が false を返した場合だけ。
> 2. 引数なしの `claude` も、`defaultToAgentsView: true` の場合は直接 FleetView に入る。それ以外の場合は通常どおり `main()` に落ちて REPL に進む。

---

## 6. `--bg` フラグ → `handleBgFlag` → daemon dispatch

```ts
// 543089 行 —— sN3
async function handleBgFlag(argvWithoutFlag: string[]) {
  // argvWithoutFlag は --bg / --background を取り除いた後の argv
  const rest = argvWithoutFlag.filter((a) => !["--bg","--background"].includes(a));
  const stdinTail = await readBgStdin();      // pGK；16 KiB を超えると切り詰められる
  const merged = stdinTail ? withStdinPositional(rest, stdinTail) : rest;

  const res = await spawnBgSession(merged);   // pHH → aN3
  if (!res.ok) {
    process.stderr.write(res.error + "\n");
    process.exitCode = 1;
    return;
  }
  process.stdout.write(formatPostBackgroundHints(res.short, res.idle ? "(idle — waiting for…)" : undefined) + "\n");
}
```

### 6a. `spawnBgSession` の中核 —— `spawnBgSessionAndDispatch` / `aN3`（542926 行）

以下はそれを分解した後の擬似コード：

```ts
async function spawnBgSessionAndDispatch /*aN3*/ (
  argv, source, cwd, seed /* Bf6 の結果 */, reattachEnv, t /*ids*/
) {
  const { sessionId, short, jobDir, freshDir } = t;
  const dashDash = argv.indexOf("--");
  const flagArgs = dashDash >= 0 ? argv.slice(0, dashDash) : argv;

  const agentName  = readFlag("--agent", flagArgs);
  const sessionName = readFlag("--name", flagArgs) ?? readFlag("-n", flagArgs);
  const resumeId    = parseResumeTarget(flagArgs);       // FGK
  const promptArg   = dashDash >= 0
    ? argv.slice(dashDash + 1).join(" ")
    : positionalArgFor(argv, resumeId);

  const isResume = flagArgs.some(f => f === "-c" || f === "--continue" ||
                                       f === "-r" || f === "--resume" ||
                                       f.startsWith("--resume=") || f.startsWith("-r="));
  const isFork = flagArgs.includes("--fork-session");
  const respawnFlags = respawnableFlags(flagArgs);       // Ov3
  const dispatchFlagArgs = dashDash >= 0 ? respawnFlags : flagsWithoutPositional(respawnFlags);
  const resumingSelf = resumeId !== undefined && resumeId === sessionId;
  const forkFlag = isResume && !isFork ? ["--fork-session"] : [];
  const sessionArgs = resumingSelf ? [] : ["--session-id", sessionId, ...forkFlag];

  if (source === "shell" && readFlag("--session-id", flagArgs))
    warn("--bg manages the session id; ignoring --session-id (use --resume <id> instead)");

  const subagent = agentName
    ? (await uk(cwd ?? process.cwd())).activeAgents.find(a => a.agentType === agentName)
    : undefined;
  if (agentName && !subagent && source === "shell")
    warn(`no agent named '${agentName}' — spawning with default template`);

  const intent = seed?.intent ?? promptArg ?? "";
  const isIdle = !agentName && intent === "" && !seed?.detail;

  // state.json を事前書き込み（fleet / spare 由来はスキップ。これらは dispatch 経由で seed を運ぶ）
  if (source !== "fleet" && source !== "spare") {
    const prior = freshDir ? null : await readJobState(jobDir);
    if (prior === null)
      writeJobState(jobDir, oqH({
        template: {
          name: agentName ?? "bg",
          description: subagent?.whenToUse ?? "",
          initialPrompt: subagent?.initialPrompt,
        },
        respawnFlags: dispatchFlagArgs,
        intent,
        name: sessionName ?? seed?.name,
        nameSource: sessionName ? "user" : seed?.nameSource,
        detail: seed?.detail ?? (isIdle ? "(blocked)" : undefined),
        tempo: isIdle ? "blocked" : undefined,
        sessionId, cwd: cwd ?? process.cwd(),
        worktreePath: seed?.worktree?.path,
        worktreeBranch: seed?.worktree?.branch,
        worktreeHookBased: seed?.worktree?.hookBased,
        originCwd: seed?.worktree?.originCwd,
      }));
    else if (dispatchFlagArgs.length && !prior.respawnFlags.length)
      writeJobState(jobDir, { ...prior, respawnFlags: dispatchFlagArgs });
  }

  // daemon に投げる
  const dispatch = {
    proto: I1, short, sessionId, createdAt: Date.now(),
    source: source === "repl" ? "slash" : source,
    cwd: cwd ?? process.cwd(),
    launch: isResume && resumeId
      ? mode: "resume", sessionId: resumeId,
          fork: !resumingSelf && (isFork
      : { mode: "prompt", args: [...sessionArgs, ...positionalsAfterFlags(argv)] },
    respawnFlags, env: bgEnvWhitelist(), reattachEnv,
    worktree: seed?.worktree && { path: seed.worktree.path, ownershipToken: sessionId },
    isolation: subagent?.isolation === "worktree" && subagent.source !== "built-in" ? "worktree" : "none",
    agent: agentName, routine: undefined,
    seed: { intent, name: sessionName ?? seed?.name },
    cols: process.stdout.columns, rows: process.stdout.rows,
  };
  const r = await daemonRpcDispatch(dispatch);          // AB8 → V$
  if (r.ok) return { ok: true, short, sessionId, idle: isIdle };

  // フォールバック分岐：
  //  - ack-timeout / enoconn だが op:"list" で worker がまだ見える → 成功と判定
  //  - ack-timeout かつ list に現れない → 1 回再投下
  //  - short-alive → "session is already running" という親切なエラー
  //  - stale-short → "前のセッションがまだ終了中なので、あとで再試行"
  //  - daemon-unreachable → 終了し、status の実行を促す
  //（543030-543087 を参照）
}
```

### 6b. stdin を positional としてマージ —— `withStdinPositional` / `BGK`（543129 行）

ユーザーが prompt テキストを `--bg` にパイプし、かつ argv にすでに positional がある場合、stdin の末尾は**最後の positional に追加**される（間は改行で区切られる）。そのため daemon からは、マージ済みの prompt が 1 つだけ見える。argv に positional がない場合は、`--` センチネルの後ろに新しい 1 件が追加される。

### 6c. 関連 CLI 動詞（543217 行以降）

* `claude logs <prefix>` → `logsHandler` —— daemon に 500 ms の RPC 購読を開き、`streamTail` スナップショットを出力して終了する。
* `claude attach <prefix>` → `attachHandler` —— まずヘルスチェック（`mW_`）を行い、その後 `MQ(short)` が PTY を引き継ぐ。`ERESPAWNING` に遭遇した場合は最大 20 回リトライする。`ENOJOB` の場合は、より有用なエラー情報のために `state.json` から内容を読み取る。
* `claude stop / kill / rm / respawn <prefix>` → 対応するハンドラ（542861 行で `M_(QGK,…)` によりまとめて登録）。

すべての prefix 引数は `resolveJobByShortPrefix /* jB8 */`（543217 行）で解決される：`readdir` で `~/.claude/bg-jobs/` を走査し、prefix が一意であることを確認する。

---

## 7. `/bg` スラッシュコマンド —— 確認ダイアログと seed 抽出

### 7a. `extractSeedFromTranscript` —— `Bf6`（543822 行）

transcript を下から上へ走査し、`{ intent, name, nameSource, detail }` を生成する：

```ts
function extractSeedFromTranscript(messages, override) {
  let firstUserText = override, hasNonMetaUser = false, lastAssistant: string|undefined;
  for (let i = messages.length - 1; i >= 0; i--) {
    const m = messages[i];
    if (m.type === "assistant" && lastAssistant === undefined) {
      const text = WE(m);                   // 最下部の assistant メッセージ本文
      if (text) lastAssistant = text.replace(/\s+/g," ").trim().slice(0,120);
    }
    if (m.type === "user" && !m.isMeta && !QY6(m)) {
      const text = oI(m)?.trim();
      if (text && viH(text)) continue;      // スラッシュコマンドのエコーをスキップ
      hasNonMetaUser = true;
      if (!firstUserText && text) firstUserText = text;
    }
    if (hasNonMetaUser && firstUserText && lastAssistant !== undefined) break;
  }
  if (!hasNonMetaUser) return null;         // "Nothing to background yet"
  const userName = g$(jobSessionId());
  const autoName = qp(jobSessionId());
  return intent: (firstUserText;
}
```

### 7b. `BackgroundConfirmDialog` —— `Jv3`（543849 行）

* `J_` を通じて 2 つの AppState フィールドを読む：
  * `effortValue`（`Dv3`）
  * `taskCount`（`jv3` —— `state.tasks`）
* **in-flight task 数が 0 の場合**、コンポーネントの初回レンダー時に自動で確認済みにする（`React.useState(taskCount === 0)`）。
* それ以外の場合は `<ConfirmDialog>` をレンダーし、タイトルは **"Background this session?"**、サブタイトルは `"<N> tasks running — the forked session won't carry live processes."`、ボタンはそれぞれ **"Background anyway (tasks will be abandoned)"** と **"Stay"**。
* 確認後：
  ```ts
  const r = await forkCurrentSessionToBackground /*pf6*/ (
    seed, extraPrompt, effortValue, "command", messages
  );
  Q("tengu_background_fork", {
    confirmed: taskCount > 0, inflight_count: taskCount,
    had_prompt: extraPrompt.length > 0,
    had_worktree: r.hadWorktree, worktree_handed_off: r.handedOff,
  });
  onDone();
  await B9(0, "prompt_input_exit", {
    suppressResumeHint: true,
    finalMessage: formatPostBackgroundHints(r.short, r.handedOff ? "(worktree handed off)" : undefined),
  });
  ```
* `forkCurrentSessionToBackground`（`pf6`、同モジュール）—— `source: "slash"` で `spawnBgSession` を呼び出し、アクティブな worktree を引き渡し、bridge / daemon リスナーを取り外す（`DV()`）。

### 7c. `markSelfStoppedAndExit` —— `Uf6`（544013 行）

```ts
async function markSelfStoppedAndExit(source: "stop_command"|"bridge") {
  Q("tengu_bg_agent_action", { action: "stop", source, jobSessionId: jobSessionId() });
  const dir = process.env.CLAUDE_JOB_DIR;
  if (L7() && dir) {
    const now = new Date().toISOString();
    const cur = await readJobState(dir);
    if (cur && !isJobTerminal(cur))
      await writeJobState(dir, {
        ...cur, state: "stopped", detail: "stopped from session",
        tempo: "idle", needs: undefined, block: undefined, inFlight: undefined,
        updatedAt: now,
        firstTerminalAt: cur.firstTerminalAt ?? now,
      });
    if (isInteractive()) process.stdout.write(formatExitNotice("Session stopped."));
  }
  return B9(0, "prompt_input_exit", { suppressResumeHint: true });
}
```

---

## 8. FleetView —— TUI 部分

### 8a. `mountFleetView` —— `uQ3`（606636 行）

メインループは、**描画 → アクション待ち → 必要なら attach → アンマウントして再マウント**という流れ。agent view のライフサイクル全体を通して alt-screen を専有する。

```ts
async function mountFleetView(inkRoot) {
  Q("tengu_bg_agent_action", { action: "list_open" });
  setTerminalTitle("claude agents");

  const preselectId = process.env.CLAUDE_AGENTS_SELECT;
  delete process.env.CLAUDE_AGENTS_SELECT;               // 1 回限りの使用
  const persisted = await loadFleetUiState(currentCwd());// qxK
  let query     = persisted?.q;
  let collapsed = persisted?.collapsed;
  let jobId     = preselectId;
  let error: string | undefined;
  let groupMode: "state"|"directory"|undefined;
  let savedAppState: any;

  for (;;) {
    const action = await new Promise<FleetAction>((resolve) => {
      inkRoot.render(
        <ProcessTitleProvider>
          <AppStateBoundary
            initialState={savedAppState && {...savedAppState, notifications:{current:null,queue:[]}}}
            onChangeAppState={({newState}) => savedAppState = newState}
          >
            <ContextProviders>
              <ScrollFrame>
                <FleetView
                  onAction={resolve}
                  initialJobId={jobId}
                  initialQuery={query}
                  initialCollapsed={collapsed}
                  initialError={error}
                  initialGroupMode={groupMode}
                />
              </ScrollFrame>
            </ContextProviders>
          </AppStateBoundary>
        </ProcessTitleProvider>,
      );
    });

    const inAlt = ownsAltScreen();                       // un_
    if (inAlt && action.type === "open") handoffAltScreen();
    if (!inAlt) inkRoot.render(null);
    inkRoot.unmount();

    if (action.type === "done") break;

    // ── action.type === "open" → attach ──
    if (isWindows() && process.stdin.isTTY) { process.stdin.setRawMode(true); process.stdin.ref(); }
    const restoreCursor = inAlt
      ? batchUpdate(() => process.stdout.write(altScreenReentry()))
      : () => {};

    jobId     = action.job.id;
    query     = action.query;
    collapsed = action.collapsed;
    groupMode = action.groupMode;
    cachedJobs       = action.jobs;
    cachedLoopKicks  = action.loopKicks;
    cachedStatuses   = action.statuses;
    cachedStatusesTs = action.statusesTs;
    cachedPrStatuses = action.prStatuses;
    cachedFirstMount = true;

    const respawn = action.respawnResult ?? await respawnJob(action.job.id, {
      knownState: action.freshDispatch ? undefined : action.job.state,
      knownAlive: (Date.now() - action.statusesTs < 1500
                   && action.statuses.get(action.job.state.resumeSessionId
                                          ?? action.job.state.sessionId) !== undefined)
                  ? true : undefined,
    });

    if (respawn.ok || respawn.alive) {
      logFleetViewLifecycle("attach", action.job.state);
      process.stdout.write(SET_TITLE(renderJobLabel(action.job.state, true)));
      let attach = await attachJob(respawn.short ?? action.job.id, { alreadyInAlt: inAlt });

      if (attach.kind === "error" && attach.orphaned) {
        // 最後の手段：強制 respawn してから再試行
        const re = await respawnJob(action.job.id, { force: true, knownState: action.job.state });
        if (re.ok || re.alive) attach = await attachJob(re.short ?? action.job.id, { alreadyInAlt: inAlt });
        else attach = { kind: "error", msg: re.error };
      }
      if (attach.kind === "error" && !attach.ended) {
        error = attach.msg;
        xH("fleet_view_open", "attach_failed");
      } else {
        if (attach.msg) error = attach.msg;
        yH("fleet_view_open");
      }
      logFleetViewLifecycle("detach", action.job.state, { attachDurationMs: Date.now() - tStart });
    } else {
      error = respawn.error;
      xH("fleet_view_open", "respawn_failed");
    }

    if (inAlt) process.stdout.write(restoreAltMargins());
    inkRoot = await createRoot({ exitOnCtrlC: false });
    restoreCursor();
  }
}
```

### 8b. `FleetView` コンポーネント —— `tmK`（604467 行）—— 状態機械

コンポーネント内部の状態（おおむね `useState` / `useRef` の順）：

| スロット | 用途 |
|---|---|
| `jobs` / `setJobs` (`A, z`) | 現在マウントされている JobRow のリスト。bootstrap 中は `null`。スナップショットは `$.current` にミラーされ、コールバックから使われる。 |
| `incomingJobs` (`Y, w`) | 今回のマウント内で新規開始され、まだ state.json の書き込みを待っている job。 |
| `prStatuses` (`F, g`) | PR check 状態のキャッシュ。 |
| `cursorIndex` (`d, a`) | フラット化後の行リスト `h9` のインデックス。 |
| `peekJob` (`_H, t`) | 現在 peek 中の job。 |
| `dispatchFolded` (`qH, i`) | `"queued/folded"` セクションを折りたたむかどうか。 |
| `replyDrafts` (`e.current`) | `Map<jobId, draftText>`：peek パネルのインライン返信ドラフト。 |
| `replyErr` (`$H, AH`) | 直近の返信失敗情報。 |
| `helpOpen` (`s, o`) / `confirmDelete` (`HH, KH`) / `selectMenu` (`jH, YH`) | 各種 overlay モーダル。 |
| `exiting` (`MH, kH`) | 終了の二重確認。`LH = ph(setExiting, () => onAction({type:"done"}))` が Ctrl+C を連結する。 |
| `renaming` (`WH, DH`) | リネーム中の job、または null。 |
| `attaching` (`XH, NH`) | attach 前に respawn 中の job id。 |
| `error` (`$9, L6`) | toast / エラー行。 |
| `groupMode` (`tH, v_`) —— `"state" \| "directory"` | `b_().fleetViewGroupMode` に永続化される。 |
| `collapsedGroups` (`UH, BH`) —— `Set<string>` | cwd ごとに永続化される。 |
| `manuallyCollapsedPinned` (`FH, q_`) | pinned セクションを手動で折りたたむが、永続化集合には影響させない場合に使う。 |
| `cwd` (`L`) → 最終的に repo ルート（`_Y(cwd)`）へ解決される。 |
| `pinnedDir` map (`u, b`) | cwd ごとの高速 pin マッピング。 |
| `query` / `cursorOffset`（`v0(...)` 複数行入力 hook 由来） | 検索ボックス。 |

同じモジュール（602820–602850 行）から、`repoGroup({cwd})` (`Nd8`)、`pickIcon`、`jobLabel`、`childStatusColor` などの補助関数もいくつかインポートしている。

### 8c. キーバインド —— `handleFleetViewKeyDown` / `wS`（605589 行）

```markdown
```text
overlay 表示中（renaming / attaching）:
  Ctrl+C        overlay をキャンセル
  up/down       overlay 自身のスクロールロジックへ素通し
  その他        overlay 内部の入力処理へ委譲

グローバル:
  Ctrl+C        入力をクリア → ヘルプ/削除確認が開いていれば閉じる → それ以外なら終了
  Escape        ヘルプを閉じる → 削除確認を閉じる → 折りたたみヘルプを閉じる → メニューを閉じる → 入力をクリア → armed アクションを取り消す → onAction("done")
  Shift+up/dn   グループ内で job を移動（Sy/SY）
  Ctrl+R        選択中の job をリネーム   （renaming = job.id）
  Ctrl+S        "state" ↔ "directory" のグルーピングモードを切り替え
  Ctrl+G        クエリ内で展開 / 置換
  Ctrl+T        選択中の job を pin / unpin
  up / Ctrl+P   カーソルを移動（または補完候補をスクロール）
  down / Ctrl+N カーソルを移動
  Ctrl+X（ヘルプ表示中）→ armed アクションの対象を kill
  Tab           スラッシュ候補を受け入れる / dispatchFolded を切り替え
  →             現在行に attach（ブログでいう right-arrow）
  ⌘/Super+1-9   現在の cwd グループの N 行目に attach
  Return        入力が空なら: /exit/quit を認識して終了 → それ以外なら候補を送信 → "<template> <intent>" を解析し、
                  新しい bg job を dispatch →（Shift+Return ⇒ さらに {type:"open", freshDispatch:true} で自動 attach）
  空入力で Space 現在行の peek モードを切り替え（ブログでいう "peek"）
```

FleetView の「左方向キー」は複数行入力 hook（`v0` のデフォルト cancel）で処理される —— 入力が空のときは `onCancel: setExiting` にフォールバックし、`ph` が引き継いで「もう一度押すと終了確認」（`LH`）を行う。

### 8d. peek パネル —— `PeekPane` / `WQ3`（603580 行）

これはブログで説明されている "peek mode"、つまり特定の行を選択するが完全には attach しないモード。

* `q.state.updatedAt` を読み、`r7(now - updated)` をレンダリングして "Last interaction X ago" を表示する。直近 1 分以内は 1 秒ごとに再レンダリングし、それ以外は 30 秒ごと。
* `q.state.output` から情報行を 3 つ選ぶ（インライン子リストのラベルと重複するものは除外）。さらに `state.needs` と `state.detail` を加え、端末の高さに応じて自動調整する。
* これは**独自**の `v0` 入力欄を持ち、`replyDrafts.get(jobId)` にバインドされる:
  * `onSpaceOnEmpty: onBack` —— Space で peek を抜ける。
  * `onTabOnEmpty: () => setQuery(suggestedReply); Q("tengu_prompt_suggestion", { outcome: "accepted", source: "fleetview_peek" })` —— Tab で `state.suggestedReply` を受け入れる（`tempo === "blocked"` かつ questions フォームがない場合にのみ存在）。
  * `onExit` → `onReply(formattedDraft)` で下書きを送信する。成功後、daemon 側の worker が自動で再開する —— これがブログでいう「インライン返信の自動 resume」の実装。
* `state.tempo === "blocked"` かつ `state.block.questions` が存在する場合、`<QuestionForm>` をレンダリングする（`jH = 2 + opts.length + 1` 行分の追加レイアウト領域）。

### 8e. 視覚インジケータのルール

* **行の色 / activity グリフ** = `jobActivityForRow(jobRow, prStatuses)` → `"flowing" | "slowing" | "stuck" | "success" | "failure" | "stopped"`。"slowing" / "stuck" のしきい値は `updatedAt` 由来:
  * `tempo === "active"`: 3 分 → "flowing"、15 分超 → "stuck"。
  * それ以外: 15 分 → "flowing"、75 分超 → "stuck"。
* **"awaits input"** = `tempo === "blocked"`（block.questions が存在）または `state.needs` が空でない。
* **"actively processing"** = `tempo === "active"` かつ終端状態ではない。
* **"completed"** = `LD(state)` が真。
* **"next run time"**（ループ job）= `vd8(jobRow, loopNextRun)` で、`"in <時間>"` と表示する。

---
```

## 9. 永続化

* **job ごとに 1 つの `state.json`** —— FleetView と `claude logs/attach/...` にとって唯一の真実の情報源。`pT(jobDir, partial)` によりアトミックに merge 書き込みされる。
* **`~/.claude/bg-jobs/`** —— `<short>/` サブディレクトリの集合。stdout/stderr は worker の PTY 履歴（daemon RPC 経由で読み取られ、ディスクは**経由しない**）。
* **FleetView UI 状態（cwd ごと）** —— `B26(cwd, { q, collapsed })` は repo パスを key とし、読み込み時は `qxK(repoRoot)` 経由、書き込みには 300 ms の debounce がある。
* **グループ化モード設定** —— `b_().fleetViewGroupMode` は `H6(updater)` 経由でユーザー設定リポジトリに書き込まれる。
* **Daemon socket / ハートビート** —— `.fleetview-heartbeat` ロックファイル（`Moq`、104916 行）。

---

## 10. エンドツーエンドの時系列

```text
A) `claude --bg "ship the redis fix"`

  argv が mainCliEntry() に入る → `argv.includes("--bg")` に命中（651394 行）
        │
        ▼
  fleetGate ok → handleBgFlag(argv)                  [543089]
        │   stdin (TTY) → ""；rest = argv \ ["--bg"]
        ▼
  spawnBgSession(rest)                               [pHH, 542905]
        │ sessionId, short, jobDir を生成
        ▼
  spawnBgSessionAndDispatch                          [aN3, 542926]
        │  • state.json を書き込み（seed データ）
        │  • JobDispatch を構築
        ▼
  daemon V$ {op:"dispatch", d}                       [543031]
        │  daemon は ok / ack-timeout / enoconn / short-alive / stale-short を返す可能性
        ▼  ack-timeout の再投下 / レスキューループ
  stdout ← formatPostBackgroundHints(short, idle?)   [543177]
  exit 0


B) リアルタイムセッション内で `/bg` を入力

  REPL が /bg を受け取る → handleBgSlashCommand をロード          [Yv3, 543958]
        │ ガード：すでに bg / 永続化が無効
        ▼
  extractSeedFromTranscript が transcript をたどる → {intent,name,detail}
        ▼
  <BackgroundConfirmDialog/>                         [Jv3, 543849]
        │  task=0 は自動確認；それ以外は <ConfirmDialog> を表示
        ▼
  forkCurrentSessionToBackground(seed, prompt, effort, "command", messages)  [pf6]
        │  --bg と同じ dispatch 経路だが、source="slash"、worktree を移譲し、bridge を切り離す
        ▼
  Q("tengu_background_fork", …)
  onDone() → B9(0, "prompt_input_exit", {finalMessage: formatPostBackgroundHints(short, …)})


C) `claude agents`（または REPL 中で ←）

  REPL 中で ← → forkAndOpenFleetViewFromRepl(messages, effort, autoName)  [KpK, 606947]
        │  extractSeedFromTranscript（空を許可）→ wB8 が state.json を事前書き込み
        │  forkCurrentSessionToBackground が bg fork を起動
        │  Q("tengu_open_agents_via_left")
        │  tengu_bg_leftarrow_inprocess スイッチが true の場合：
        │    → remountFleetViewInProcess（この short を事前選択）
        │  それ以外：
        │    → FjH({ args:["agents"], env:{CLAUDE_AGENTS_SELECT: short} })  ← 新しい claude を exec

  `claude agents`  →  mainCliEntry() → argsAreOnlyDebugFlags を通過 → isAgentsFleetEnabled()
        │
        ▼
  mountFleetView(root)                               [uQ3, 606636]
        ┌──────────────── ループ ────────────────┐
        ▼                                      │
  <FleetView/> をレンダリング → ユーザーがナビゲート ────────────────┤
        │                                      │
        ▼ onAction === "open"                  │
  respawnJob → attachJob (PTY)                 │
        │ ユーザー作業 → 最終的に detach        │
        ▼ onAction === "done"                  │
  gracefulShutdown(0)                          │
        └──────────────────────────────────────┘
```

---

## 11. 落とし穴 / 校正メモ

1. **`claude agents` は 2 つのコマンドの合体。** 647846 行で Commander に登録されている `agentsHandler`（`Cs3`）は、fleet view が **閉じている** ときだけ有効になる。これは source ごとにグループ化された subagent 定義リストを出力する。ブログで言う "agent view" は、実際には 651485 行の fast-path FleetView。
2. **`/bg` はエイリアス。** 本名は `/background`（543999 行）で、`bg` は `aliases` にある。`immediate: (raw) => !raw.trim()` は、単に `/bg<Enter>` と入力した場合は JSX の確認をスキップする、という意味。prompt テキスト付きだと React ツリーで止まる。
3. **`/bg` と `--bg` は同じ dispatch を通る。** 唯一の違いは、(a) `state.json` に書かれる `source` フィールドが異なること、(b) 親プロセス側で worktree / bridge を分解する必要があるかどうか。daemon 側はまったく区別しない。
4. **`L7()`（「bg 内かどうか」）≠「セッションがバックグラウンド化済み」。** これは本プロセスの `CLAUDE_JOB_DIR` が設定されている場合にだけ true を返す。つまり「自分がその fork である」という意味。fork を起動した直後の前景プロセスは、まだ「バックグラウンド化済み」には属さない。そのため `/bg` は dispatch 後に `B9(0, "prompt_input_exit")` を呼び、前景側を能動的に終了させる。
5. **`CLAUDE_AGENTS_SELECT` は使い捨て。** `mountFleetView` は受け取るとすぐ env から `delete` する（606640 行）。長寿命の fleet view が古い id に固定されるのを避けるため。
6. **"Background anyway" の文言が指すのは tasks であって files ではない。** 読んでいる `taskCount`（`jv3 = state => state.tasks`）は AppState の `tasks` map 由来、つまり bash / 長時間実行ツールのこと。**in-flight リクエストではない**。別概念だが、UI 文言では tasks としか言っていない。
7. **左方向キーでの終了は入力が空のときだけ有効。** 実際に発火するのは複数行入力 hook `v0` の `onCancel`。入力内容がある場合、左キーは単にカーソルを移動するだけ。
8. **`disableAgentView` は唯一の一括遮断スイッチ。** いったん有効になると（管理設定または env）、`claude agents` は `agentsHandler` にフォールバックし、`--bg` / `--background` は gate で止められ、`/background` スラッシュコマンドは `isEnabled` が常に true でも内部の `pHH` が `fleetGateRejected` で失敗し、daemon も起動を拒否する。
9. **`spawnBgSession` は 2 種類の daemon 障害を救済する**：`ack-timeout`（worker はまだ生きているが daemon の応答が失われた）と `enoconn`（RPC 途中で socket が消えた）。まず `op:"list"` を送って探り、成功宣言するか、1 回だけ再投入を試みる。
10. **ポーリング間隔。** FleetView 内の peek タイムスタンプは、直近 1 分間は 1 秒ごと、それ以上は 30 秒ごとに再レンダリングされる。ただし file watcher / RPC poller の頻度は別モジュール内（602820 付近の `seedLastJobs` と別 hook 群）。移植時はここを別途書き直す必要がある。
11. **`claude` は `defaultToAgentsView: true` と組み合わせるとデフォルトで FleetView に入る。** ユーザーが設定パネル（462620 行）で有効化したあとに素の `claude` を実行すると、そのまま入る。意図せず発火しやすい。

---

## 12. 他プロジェクトへ移植するための最小実装チェックリスト

「agent view + /bg」を別の shell（たとえば Anthropic CLI の fork、あるいは無関係なアシスタント CLI）へ持っていきたい場合：

1. **ディスクモデル。** `~/.claude/bg-jobs/<short>/state.json`：アトミックな merge 書き込み、`readdir` による prefix 検索、第 1 節で定義した `JobState` schema。
2. **Daemon。** Unix socket サービスを 1 つ用意し、最低限 `{op:"list"}`、`{op:"dispatch", d:JobDispatch}`、および job ごとの PTY attach RPC をサポートする。macOS/Linux はユーザーごとに 1 つの `unix:` プロトコル socket；Windows は同じ JSON 封筒の named pipe。管理設定でハードに無効化しやすいよう、`fast_path_policy` フラグを付ける。
3. **Worker。** daemon が `JobDispatch.env` 内の環境で起動する。環境には `CLAUDE_JOB_DIR=<dir>`、`--session-id <uuid>` を含め、prompt は最後の positional にする。worker 自身が `state.json`（intent、name、output、tempo、block）を update する。
4. **`/bg` スラッシュコマンド。** `local-jsx`、`aliases: ["bg"]`。seed は transcript を下から上へ走査して得る（meta とスラッシュコマンドのエコーはスキップ）。`state.tasks > 0` のときは確認を出し、それ以外は自動確認。
5. **`--bg` CLI フラグ。** この flag を取り除き、pipe stdin を読む（16 KiB で切り詰め）、同じ dispatcher に流し込む。成功時は 5 行のヒントブロックを表示する。
6. **`claude logs / attach / stop / kill / rm / respawn <prefix>`。** prefix を完全な short に解決して RPC を送る。`attach` は要注意：PTY を引き継ぎ、`ERESPAWNING` に耐えられる必要がある。
7. **FleetView TUI。**
   * `mountFleetView(inkRoot)` ループ：レンダリング → `type:"open"` を待つ → `open` なら respawn + attachJob + detach 後に再レンダリング。
   * コンポーネント内部：cwd ごとに永続化される UI 状態（query + collapsed）、state/directory のグルーピング切り替え、二重用途の peek パネル（見る／返信する）、複数行入力、リネーム overlay、削除確認 overlay、ヘルプ overlay、グループ内ドラッグソート。
   * `attaching` ref を別途維持し、同じ行のダブルクリックで 2 回発火しないようにする。
8. **ゲート。** 管理設定 `disableAgentView` + env `CLAUDE_CODE_DISABLE_AGENT_VIEW` + 組織ポリシーが返す `fast_path_policy`。各入口の前で必ず同じ `isAgentsFleetEnabled()` を通す。
9. **テレメトリ。** 最低限：`tengu_background`（flag/slash）、`tengu_background_fork`、`tengu_bg_dispatch`、`tengu_bg_dispatch_fallback`、`tengu_bg_dispatch_rescued`、`tengu_open_agents_via_left`、`tengu_fleetview`、`tengu_bg_agent_action`、`tengu_prompt_suggestion`、およびすべての `fleet_view_*` アクション動詞（open / rename / pin_toggle / reorder / stop / attach / detach）。
10. **左方向キー → agents。** REPL 入力欄の「空 + 左方向キー」を hook する：(a) 現在の transcript から fresh bg fork を seed、(b) 現在の worktree/bridge を分解、(c) in-process で fleet view を再マウントするのを優先；失敗時は `exec` にフォールバック。
11. **リスク声明の gating。** `--bg` と `bypassPermissions` または `auto` 権限モードを組み合わせる場合、先に対話式で 1 回確認が必要 —— 543643–543646 行のチェックを迂回してはならない。headless dispatch が UX ゲートをすり抜けるのを避けるため。

## 13. シンボル対応早見表

### bg-cli とスラッシュコマンドモジュール（`QGK` / `gW_`）

| リネーム後 | 難読化名 | 行番号 |
|---|---|---|
| `preSeedReplBgJob` | `wB8` | 542877 |
| `spawnBgSession` | `pHH` | 542905 |
| `spawnBgSessionAndDispatch` | `aN3` | 542926 |
| `markBgDispatchSucceeded` | `IGK` | 542802 |
| `markBgDispatchFellThrough` | `OB8` | 542820 |
| `handleBgFlag` | `sN3` | 543089 |
| `readBgStdin` | `pGK` | 543108 |
| `withStdinPositional` | `BGK` | 543129 |
| `humanizeDispatchFailureReason` | `tN3` | 543163 |
| `formatPostBackgroundHints` | `mf6` | 543177 |
| `bgVerbExtraArgsNote` | `UGK` | 543188 |
| `resolveJobByShortPrefix` | `jB8` | 543217 |
| `logsHandler` | `eN3` | 543246 |
| `attachHandler` | `Hv3` | 543271 |
| `stopHandler` | `qv3` | （export は 542863 を参照） |
| `rmHandler` | `Kv3` | （export は 542865 を参照） |
| `respawnHandler` | `_v3` | （export は 542866 を参照） |
| `parseResumeTarget` | `FGK` | （export は 542869 を参照） |
| `flagsWithoutPositional` | `gGK` | （export は 542873 を参照） |
| `extractSeedFromTranscript` | `Bf6` | 543822 |
| `BackgroundConfirmDialog` | `Jv3` | 543849 |
| `handleBgSlashCommand` | `Yv3` | 543958 |
| `BG_CLI_FLAG_ALIASES`（`["--bg","--background"]`） | `oN3` | 543733 |
| `forkCurrentSessionToBackground` | `pf6` | （呼び出しは 543875、606981 を参照） |
| `BG_SLASH_COMMAND_DEF` | `Mv3` / `fv3` | 543999 |
| `getCurrentJobDir` | `Xv3` | 544011 |
| `markSelfStoppedAndExit` | `Uf6` | 544013 |
| `stopSlashJsxCall` | `Pv3` | 544047 |
| `stopBridgeNonInteractiveCall` | `Wv3` | 544055 |

### FleetView モジュール（`bd8` / `xd8`）

| リネーム後 | 難読化名 | 行番号 |
|---|---|---|
| `FleetView`（コンポーネント） | `tmK` | 604467 |
| `mountFleetView` | `uQ3` | 606636 |
| `remountFleetViewInProcess` | `emK` | 606921 |
| `forkAndOpenFleetViewFromRepl` | `KpK` | 606947 |
| `PeekPane`（コンポーネント） | `WQ3` | 603580 |
| `renderJobLabel` | `ZW6` | 602868 |
| `formatJobAgeOrCountdown` | `vd8` | 602863 |
| `classifyJobActivity` | `jW6` | 602953 |
| `deriveStateBand` | `IvH` | 602971 |
| `repoGroupKey` | `Nd8` | 604497 |
| `parseDispatchInput` | `Zd8` | 603040 |
| `parseSearchQuery` | `BmK` | 603002 |
| `pruneMap` | `Pd8` | 602855 |
| `logFleetViewLifecycle("peek"\|"attach"\|"detach")` | `Rd8` | 603598 |
| `handleFleetViewKeyDown` | `wS` | 605589 |
| `swapAdjacentJobsInGroup` | `SY` | 605532 |
| `attachJobOrSurfaceError` | `YS` | 605454 |
| `runRowKeyAction` | `lj` | 605425 |

### CLI エントリと Commander

| リネーム後 | 難読化名 | 行番号 |
|---|---|---|
| `mainCliEntry` | `je3` | 651265 |
| `argsAreOnlyDebugFlags` | `caK` | 651245 |
| `agentsHandler`（fallback —— agent 定義を列挙するもので、jobs ではない） | `Cs3` | 644428 |
| `AgentsList`（fallback コンポーネント） | `FoK` | 644390 |
| `formatAgentDefinitionRow` | `BoK` | 644383 |
| `renderAgentSourceGroup` | `Es3` | 644416 |
| `isAgentsFleetEnabled` | `Xc` | 149497 |
| `fleetGateRejected` | （同名で re-export） | 651424 |

### 設定 / 定数

| リネーム後 | 難読化名 | 行番号 |
|---|---|---|
| `SETTING_DISABLE`（`"disableAgentView"`） | `y` | 53308 |
| `SETTING_DEFAULT_TO_FLEET`（`"defaultToAgentsView"`） | — | 462620 |
| `ENV_PRESELECT`（`"CLAUDE_AGENTS_SELECT"`） | — | 606639 |
| `ENV_AUTORELAUNCH`（`"CLAUDE_AGENTS_AUTO_RELAUNCHED_AT"`） | `Xd8` | 606765 |
| `AUTO_RELAUNCH_UNFOCUSED_MS` | `YW6` | 606763 |
| `AUTO_RELAUNCH_MIN_INTERVAL_MS` | `umK` | 606764 |
| `FLEETVIEW_HEARTBEAT`（`.fleetview-heartbeat`） | `Moq` | 104916 |
