本文へスキップ
郭立 (leeguoo)

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

Claude Code 2.1.139 のソースコードをもとに、claude agents、--bg、/bg がバックグラウンド job モデル、daemon、FleetView TUI をどのように共有しているかを分解します。

このページの目次
1.1. データモデル2.2. 定数と文言3.3. Host-Framework 契約4.4. 登録5.4a. /bg スラッシュコマンド(543995 行)6.4b. /stop スラッシュコマンド(bg job 内でのみ有効、544066 行)7.4c. claude agents Commander サブコマンド(647846 行、fallback のみ)8.4d. 総合スイッチ設定項目(53308 行)9.5. エントリ / ディスパッチ —— mainCliEntry()(CLI の main)10.6. --bg フラグ → handleBgFlag → daemon dispatch11.6a. spawnBgSession の中核 —— spawnBgSessionAndDispatch / aN3(542926 行)12.6b. stdin を positional としてマージ —— withStdinPositional / BGK(543129 行)13.6c. 関連 CLI 動詞(543217 行以降)14.7. /bg スラッシュコマンド —— 確認ダイアログと seed 抽出15.7a. extractSeedFromTranscript —— Bf6(543822 行)16.7b. BackgroundConfirmDialog —— Jv3(543849 行)17.7c. markSelfStoppedAndExit —— Uf6(544013 行)18.8. FleetView —— TUI 部分19.8a. mountFleetView —— uQ3(606636 行)20.8b. FleetView コンポーネント —— tmK(604467 行)—— 状態機械21.8c. キーバインド —— handleFleetViewKeyDown / wS(605589 行)22.8d. peek パネル —— PeekPane / WQ3(603580 行)23.8e. 視覚インジケータのルール24.9. 永続化25.10. エンドツーエンドの時系列26.11. 落とし穴 / 校正メモ27.12. 他プロジェクトへ移植するための最小実装チェックリスト28.13. シンボル対応早見表29.bg-cli とスラッシュコマンドモジュール(QGK / gW_)30.FleetView モジュール(bd8 / xd8)31.CLI エントリと Commander32.設定 / 定数

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

ソースコード:2.1.139-unbundled/extracted/src/entrypoints/cli.js(ビルド版ばん 208bf4b4、2026-05-11)。 公式こうしきブログ参考さんこう: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) => JobState542886 で使用デフォルト値から新しい JobState を構築する。
pT(jobDir, partial) => Promise<void>542884 で使用state.json を atomic に merge 書き込みする。
Q7(jobDir) => Promise<JobState|null>542964state.json を読む。
W4(short) => string542879~/.claude/bg-jobs/<short>/。
rW() => string543233jobs のルートディレクトリ。prefix 検索ではここを readdir で走査する。
k_() => string542814現在プロセスの session id(fork 内で設定される)。
L7() => boolean543959CLAUDE_JOB_DIR が存在するか → つまり「自分がすでにバックグラウンド fork かどうか」。
getCurrentJobDir / Xv3() => string | undefined544011process.env.CLAUDE_JOB_DIR を返す。
V$(req, opts?) => Promise<DaemonResp>543031daemon との Unix socket RPC(op: "list" | "dispatch")。
mW_() => Promise<{ok,reason?}>543277daemon のヘルスチェック(claude attach 用)。
MQ(short) => Promise<{outcome,msg?}>543286「自分をこの job に接続する」RPC。
PGK(short, {alreadyInAlt}) => Promise<AttachResult>606723attach 中にローカル PTY ↔ daemon 内 job の双方向ブリッジを担当する。
fW6(jobId, {force?,knownState?,knownAlive?}) => Promise<RespawnResult>606700worker がすでに死んでいれば起動する(Re·spawn)。
B9(exitCode, reason, opts?) => Promise<never>543885フォアグラウンドプロセスの graceful exit。
aH7.setupGracefulShutdown—651511SIGINT/SIGTERM を B9 に接続する。
cH.createRoot / Mu(opts) => Promise<InkRoot>651518stdout を共有する Ink ルートノードを作成する。
v3_({exitOnCtrlC}) => Promise<InkRoot>606748PTY 引き渡し後に Ink ルートを再構築する。
Q5.get(process.stdout)?.handoffAltScreen—606681attach 用に alt-screen の制御権を PTY に渡す。
Q5.get(process.stdout)?.unmount—606926in-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/606747alt-screen に入る / 退出する ANSI シーケンス。
b_() / H6(updater)設定リポジトリ604558/605650fleetViewGroupMode などを永続化する。
B26(cwd, {q,collapsed}) / ebK / qxKbg-view UI 状態604647cwd ごとに「前回の検索と折りたたみ状態」を記憶する。
s6()() => {columns, rows}604583ターミナルサイズ。
pA()() => boolean604578ターミナルが現在フォーカスされているか。
un_()alt-screen flag606680alt-screen を所有しているか。
lT()() => WorktreeInfo | null606956現在の worktree 情報。
g$() / qp()name 検索543840ユーザー設定 / 自動派生された job 名。
WE(msg) / oI(msg)transcript 補助543828assistant/user メッセージ本文を抽出する。
viH(text)メタ情報テキスト判定543834intent を選ぶ際にスラッシュコマンドのエコーをスキップするために使う。
LD(state)(JobState) => boolean544019job が終了状態にある。
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_)

リネーム後難読化名行番号
preSeedReplBgJobwB8542877
spawnBgSessionpHH542905
spawnBgSessionAndDispatchaN3542926
markBgDispatchSucceededIGK542802
markBgDispatchFellThroughOB8542820
handleBgFlagsN3543089
readBgStdinpGK543108
withStdinPositionalBGK543129
humanizeDispatchFailureReasontN3543163
formatPostBackgroundHintsmf6543177
bgVerbExtraArgsNoteUGK543188
resolveJobByShortPrefixjB8543217
logsHandlereN3543246
attachHandlerHv3543271
stopHandlerqv3(export は 542863 を参照)
rmHandlerKv3(export は 542865 を参照)
respawnHandler_v3(export は 542866 を参照)
parseResumeTargetFGK(export は 542869 を参照)
flagsWithoutPositionalgGK(export は 542873 を参照)
extractSeedFromTranscriptBf6543822
BackgroundConfirmDialogJv3543849
handleBgSlashCommandYv3543958
BG_CLI_FLAG_ALIASES(["--bg","--background"])oN3543733
forkCurrentSessionToBackgroundpf6(呼び出しは 543875、606981 を参照)
BG_SLASH_COMMAND_DEFMv3 / fv3543999
getCurrentJobDirXv3544011
markSelfStoppedAndExitUf6544013
stopSlashJsxCallPv3544047
stopBridgeNonInteractiveCallWv3544055

FleetView モジュール(bd8 / xd8)

リネーム後難読化名行番号
FleetView(コンポーネント)tmK604467
mountFleetViewuQ3606636
remountFleetViewInProcessemK606921
forkAndOpenFleetViewFromReplKpK606947
PeekPane(コンポーネント)WQ3603580
renderJobLabelZW6602868
formatJobAgeOrCountdownvd8602863
classifyJobActivityjW6602953
deriveStateBandIvH602971
repoGroupKeyNd8604497
parseDispatchInputZd8603040
parseSearchQueryBmK603002
pruneMapPd8602855
logFleetViewLifecycle("peek"|"attach"|"detach")Rd8603598
handleFleetViewKeyDownwS605589
swapAdjacentJobsInGroupSY605532
attachJobOrSurfaceErrorYS605454
runRowKeyActionlj605425

CLI エントリと Commander

リネーム後難読化名行番号
mainCliEntryje3651265
argsAreOnlyDebugFlagscaK651245
agentsHandler(fallback —— agent 定義を列挙するもので、jobs ではない)Cs3644428
AgentsList(fallback コンポーネント)FoK644390
formatAgentDefinitionRowBoK644383
renderAgentSourceGroupEs3644416
isAgentsFleetEnabledXc149497
fleetGateRejected(同名で re-export)651424

設定 / 定数

リネーム後難読化名行番号
SETTING_DISABLE("disableAgentView")y53308
SETTING_DEFAULT_TO_FLEET("defaultToAgentsView")—462620
ENV_PRESELECT("CLAUDE_AGENTS_SELECT")—606639
ENV_AUTORELAUNCH("CLAUDE_AGENTS_AUTO_RELAUNCHED_AT")Xd8606765
AUTO_RELAUNCH_UNFOCUSED_MSYW6606763
AUTO_RELAUNCH_MIN_INTERVAL_MSumK606764
FLEETVIEW_HEARTBEAT(.fleetview-heartbeat)Moq104916

同じカテゴリの記事 · AI & Agent

← 前の記事
/goal コマンド実装の分析
次の記事 →
WeChat macOS 4.0.1.52 のローカルデータベース解錠経路をどのように確認したか

コメント

コメントは即時公開されますが、ポリシー違反時は非表示になる場合があります。

最大 1000 文字。