baa-conductor


baa-conductor / apps / conductor-daemon / src
codex@macbookpro  ·  2026-04-03

index.test.js

    1import assert from "node:assert/strict";
    2import { EventEmitter } from "node:events";
    3import { createServer } from "node:http";
    4import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
    5import { createConnection } from "node:net";
    6import { homedir, tmpdir } from "node:os";
    7import { join } from "node:path";
    8import test from "node:test";
    9
   10import {
   11  ARTIFACTS_DIRNAME,
   12  ARTIFACT_DB_FILENAME,
   13  ArtifactStore
   14} from "../../../packages/artifact-db/dist/index.js";
   15import "./artifacts.test.js";
   16import "./renewal/utils.test.js";
   17import { ConductorLocalControlPlane } from "../dist/local-control-plane.js";
   18import { FirefoxCommandBroker } from "../dist/firefox-bridge.js";
   19import {
   20  BaaInstructionCenter,
   21  BaaLiveInstructionIngest,
   22  InMemoryBaaInstructionDeduper,
   23  InMemoryBaaLiveInstructionMessageDeduper,
   24  BrowserRequestPolicyController,
   25  ConductorDaemon,
   26  ConductorTimedJobs,
   27  ConductorRuntime,
   28  DEFAULT_BAA_EXEC_INSTRUCTION_TIMEOUT_MS,
   29  DEFAULT_BAA_INSTRUCTION_TIMEOUT_MS,
   30  FirefoxBridgeError,
   31  PersistentBaaInstructionDeduper,
   32  PersistentBaaLiveInstructionMessageDeduper,
   33  PersistentBaaLiveInstructionSnapshotStore,
   34  createDefaultBaaInstructionPolicyTargetTools,
   35  createArtifactStoreBrowserRequestPolicyPersistence,
   36  createFetchControlApiClient,
   37  createRenewalDispatcherRunner,
   38  createRenewalProjectorRunner,
   39  evaluateBaaInstructionPolicy,
   40  executeBaaInstruction,
   41  extractBaaInstructionBlocks,
   42  handleConductorHttpRequest,
   43  normalizeBaaInstruction,
   44  parseBaaInstructionBlock,
   45  parseConductorCliRequest,
   46  routeBaaInstruction,
   47  recordAssistantMessageAutomationSignal,
   48  recordAutomationFailureSignal,
   49  recordRenewalPayloadSignal,
   50  shouldRenew,
   51  writeHttpResponse
   52} from "../dist/index.js";
   53import { observeRenewalConversation } from "../dist/renewal/conversations.js";
   54
   55const BROWSER_REQUEST_WAITER_TIMEOUT_MS = 120_000;
   56
   57function createLeaseResult({
   58  holderId,
   59  holderHost = "mini",
   60  term,
   61  leaseExpiresAt,
   62  renewedAt,
   63  isLeader,
   64  operation
   65}) {
   66  return {
   67    holderId,
   68    holderHost,
   69    term,
   70    leaseExpiresAt,
   71    renewedAt,
   72    isLeader,
   73    operation,
   74    lease: {
   75      leaseName: "global",
   76      holderId,
   77      holderHost,
   78      term,
   79      leaseExpiresAt,
   80      renewedAt,
   81      preferredHolderId: holderId,
   82      metadataJson: null
   83    }
   84  };
   85}
   86
   87async function createLocalApiFixture(options = {}) {
   88  const sharedToken = "local-shared-token";
   89  const controlPlane = new ConductorLocalControlPlane({
   90    databasePath: options.databasePath ?? ":memory:"
   91  });
   92  await controlPlane.initialize();
   93
   94  const repository = controlPlane.repository;
   95  const now = 100;
   96  const lease = await repository.acquireLeaderLease({
   97    controllerId: "mini-main",
   98    host: "mini",
   99    preferred: true,
  100    ttlSec: 30,
  101    now
  102  });
  103
  104  await repository.heartbeatController({
  105    controllerId: "mini-main",
  106    heartbeatAt: now,
  107    host: "mini",
  108    priority: 100,
  109    role: "primary",
  110    startedAt: now,
  111    status: "alive",
  112    version: "1.2.3"
  113  });
  114
  115  await repository.upsertWorker({
  116    workerId: "worker-shell-1",
  117    controllerId: "mini-main",
  118    host: "mini",
  119    workerType: "shell",
  120    status: "idle",
  121    maxParallelism: 1,
  122    currentLoad: 0,
  123    lastHeartbeatAt: now,
  124    capabilitiesJson: JSON.stringify({
  125      kinds: ["shell"]
  126    }),
  127    metadataJson: null
  128  });
  129
  130  await repository.insertTask({
  131    acceptanceJson: JSON.stringify(["returns local data"]),
  132    assignedControllerId: "mini-main",
  133    baseRef: "main",
  134    branchName: "feat/demo-task",
  135    constraintsJson: JSON.stringify({
  136      target_host: "mini"
  137    }),
  138    createdAt: now,
  139    currentStepIndex: 0,
  140    errorText: null,
  141    finishedAt: null,
  142    goal: "Validate local API reads",
  143    metadataJson: JSON.stringify({
  144      requested_by: "test"
  145    }),
  146    plannerProvider: "manual",
  147    planningStrategy: "single_step",
  148    priority: 50,
  149    repo: "/Users/george/code/baa-conductor",
  150    resultJson: null,
  151    resultSummary: null,
  152    source: "local_test",
  153    startedAt: now,
  154    status: "running",
  155    targetHost: "mini",
  156    taskId: "task_demo",
  157    taskType: "shell",
  158    title: "Demo task",
  159    updatedAt: now
  160  });
  161
  162  await repository.insertTaskStep({
  163    stepId: "step_demo",
  164    taskId: "task_demo",
  165    stepIndex: 0,
  166    stepName: "Run local smoke",
  167    stepKind: "shell",
  168    status: "running",
  169    assignedWorkerId: "worker-shell-1",
  170    assignedControllerId: "mini-main",
  171    timeoutSec: 300,
  172    retryLimit: 0,
  173    retryCount: 0,
  174    leaseExpiresAt: now + 30,
  175    inputJson: JSON.stringify({
  176      cmd: "echo hi"
  177    }),
  178    outputJson: null,
  179    summary: null,
  180    errorText: null,
  181    createdAt: now,
  182    updatedAt: now,
  183    startedAt: now,
  184    finishedAt: null
  185  });
  186
  187  await repository.insertTaskRun({
  188    runId: "run_demo",
  189    taskId: "task_demo",
  190    stepId: "step_demo",
  191    workerId: "worker-shell-1",
  192    controllerId: "mini-main",
  193    host: "mini",
  194    pid: 4321,
  195    status: "running",
  196    leaseExpiresAt: now + 30,
  197    heartbeatAt: now,
  198    logDir: "/tmp/demo-run",
  199    stdoutPath: "/tmp/demo-run/stdout.log",
  200    stderrPath: null,
  201    workerLogPath: "/tmp/demo-run/worker.log",
  202    checkpointSeq: 1,
  203    exitCode: null,
  204    resultJson: null,
  205    errorText: null,
  206    createdAt: now,
  207    startedAt: now,
  208    finishedAt: null
  209  });
  210
  211  await repository.appendTaskLog({
  212    taskId: "task_demo",
  213    stepId: "step_demo",
  214    runId: "run_demo",
  215    seq: 1,
  216    stream: "stdout",
  217    level: "info",
  218    message: "hello from local api",
  219    createdAt: now
  220  });
  221
  222  const snapshot = {
  223    claudeCoded: {
  224      localApiBase: null
  225    },
  226    codexd: {
  227      localApiBase: null
  228    },
  229    controlApi: {
  230      baseUrl: "https://control.example.test",
  231      browserWsUrl: "ws://127.0.0.1:4317/ws/browser",
  232      firefoxWsUrl: "ws://127.0.0.1:4317/ws/firefox",
  233      hasSharedToken: true,
  234      localApiBase: "http://127.0.0.1:4317",
  235      usesPlaceholderToken: false
  236    },
  237    daemon: {
  238      currentLeaderId: lease.holderId,
  239      currentTerm: lease.term,
  240      host: "mini",
  241      lastError: null,
  242      leaseExpiresAt: lease.leaseExpiresAt,
  243      leaseState: "leader",
  244      nodeId: "mini-main",
  245      role: "primary",
  246      schedulerEnabled: true
  247    },
  248    identity: "mini-main@mini(primary)",
  249    runtime: {
  250      pid: 123,
  251      started: true,
  252      startedAt: now
  253    },
  254    warnings: []
  255  };
  256
  257  return {
  258    controlPlane,
  259    repository,
  260    sharedToken,
  261    snapshot
  262  };
  263}
  264
  265function createTimedJobRunnerContext({
  266  artifactStore,
  267  batchId = "timed-jobs-test-batch",
  268  config = {
  269    intervalMs: 5_000,
  270    maxMessagesPerTick: 10,
  271    maxTasksPerTick: 10,
  272    settleDelayMs: 0
  273  },
  274  logDir = null,
  275  trigger = "manual"
  276} = {}) {
  277  const entries = [];
  278
  279  return {
  280    entries,
  281    context: {
  282      artifactStore: artifactStore ?? null,
  283      batchId,
  284      config,
  285      controllerId: "mini-main",
  286      host: "mini",
  287      log: (input) => {
  288        entries.push(input);
  289      },
  290      logDir,
  291      maxMessagesPerTick: config.maxMessagesPerTick,
  292      maxTasksPerTick: config.maxTasksPerTick,
  293      settleDelayMs: config.settleDelayMs,
  294      term: 2,
  295      trigger
  296    }
  297  };
  298}
  299
  300function createInstructionEnvelope({
  301  blockIndex = 0,
  302  params = null,
  303  paramsKind = params == null
  304    ? "none"
  305    : typeof params === "string"
  306      ? "inline_string"
  307      : "inline_json",
  308  target = "conductor",
  309  tool = "describe"
  310} = {}) {
  311  return {
  312    assistantMessageId: "msg-route-test",
  313    blockIndex,
  314    conversationId: "conv-route-test",
  315    dedupeBasis: {
  316      assistant_message_id: "msg-route-test",
  317      block_index: blockIndex,
  318      conversation_id: "conv-route-test",
  319      params,
  320      platform: "claude",
  321      target,
  322      tool,
  323      version: "baa.v1"
  324    },
  325    dedupeKey: `dedupe:${blockIndex}:${target}:${tool}`,
  326    envelopeVersion: "baa.v1",
  327    instructionId: `instruction-${blockIndex}`,
  328    params,
  329    paramsKind,
  330    platform: "claude",
  331    rawBlock: "```baa\nplaceholder\n```",
  332    rawInstruction: `@${target}::${tool}`,
  333    target,
  334    tool
  335  };
  336}
  337
  338async function startCodexdStubServer() {
  339  const requests = [];
  340  const sessions = [
  341    {
  342      sessionId: "session-demo",
  343      purpose: "duplex",
  344      threadId: "thread-demo",
  345      status: "active",
  346      endpoint: "http://127.0.0.1:0",
  347      childPid: 43210,
  348      createdAt: "2026-03-23T00:00:00.000Z",
  349      updatedAt: "2026-03-23T00:00:00.000Z",
  350      cwd: "/Users/george/code/baa-conductor",
  351      model: "gpt-5.4",
  352      modelProvider: "openai",
  353      serviceTier: "default",
  354      reasoningEffort: "medium",
  355      currentTurnId: null,
  356      lastTurnId: "turn-demo",
  357      lastTurnStatus: "completed",
  358      metadata: {
  359        origin: "stub"
  360      }
  361    }
  362  ];
  363
  364  const server = createServer(async (request, response) => {
  365    const method = (request.method ?? "GET").toUpperCase();
  366    const url = new URL(request.url ?? "/", "http://127.0.0.1");
  367    let rawBody = "";
  368
  369    request.setEncoding("utf8");
  370    for await (const chunk of request) {
  371      rawBody += chunk;
  372    }
  373
  374    const parsedBody = rawBody === "" ? null : JSON.parse(rawBody);
  375    requests.push({
  376      body: parsedBody,
  377      method,
  378      path: url.pathname
  379    });
  380
  381    const address = server.address();
  382    const port = typeof address === "object" && address ? address.port : 0;
  383    const baseUrl = `http://127.0.0.1:${port}`;
  384    const eventStreamUrl = `ws://127.0.0.1:${port}/v1/codexd/events`;
  385
  386    const writeJson = (status, payload) => {
  387      response.statusCode = status;
  388      response.setHeader("content-type", "application/json; charset=utf-8");
  389      response.end(`${JSON.stringify(payload, null, 2)}\n`);
  390    };
  391
  392    const currentSessions = sessions.map((session) => ({
  393      ...session,
  394      endpoint: baseUrl
  395    }));
  396
  397    if (method === "GET" && url.pathname === "/v1/codexd/status") {
  398      writeJson(200, {
  399        ok: true,
  400        data: {
  401          service: {
  402            configuredBaseUrl: baseUrl,
  403            eventStreamPath: "/v1/codexd/events",
  404            eventStreamUrl,
  405            listening: true,
  406            resolvedBaseUrl: baseUrl,
  407            websocketClients: 0
  408          },
  409          snapshot: {
  410            identity: {
  411              daemonId: "codexd-demo",
  412              nodeId: "mini",
  413              repoRoot: "/Users/george/code/baa-conductor",
  414              version: "1.2.3"
  415            },
  416            daemon: {
  417              started: true,
  418              startedAt: "2026-03-23T00:00:00.000Z",
  419              updatedAt: "2026-03-23T00:00:01.000Z",
  420              child: {
  421                endpoint: baseUrl,
  422                lastError: null,
  423                pid: 43210,
  424                status: "running",
  425                strategy: "spawn"
  426              }
  427            },
  428            sessionRegistry: {
  429              updatedAt: "2026-03-23T00:00:02.000Z",
  430              sessions: currentSessions
  431            },
  432            recentEvents: {
  433              updatedAt: "2026-03-23T00:00:03.000Z",
  434              events: [
  435                {
  436                  seq: 1,
  437                  createdAt: "2026-03-23T00:00:03.000Z",
  438                  level: "info",
  439                  type: "session.created",
  440                  message: "Created session session-demo.",
  441                  detail: {
  442                    sessionId: "session-demo",
  443                    threadId: "thread-demo"
  444                  }
  445                }
  446              ]
  447            }
  448          }
  449        }
  450      });
  451      return;
  452    }
  453
  454    if (method === "GET" && url.pathname === "/v1/codexd/sessions") {
  455      writeJson(200, {
  456        ok: true,
  457        data: {
  458          sessions: currentSessions
  459        }
  460      });
  461      return;
  462    }
  463
  464    if (method === "POST" && url.pathname === "/v1/codexd/sessions") {
  465      const nextSession = {
  466        ...sessions[0],
  467        sessionId: `session-${sessions.length + 1}`,
  468        purpose: parsedBody?.purpose ?? "duplex",
  469        cwd: parsedBody?.cwd ?? sessions[0].cwd,
  470        model: parsedBody?.model ?? sessions[0].model,
  471        updatedAt: "2026-03-23T00:00:04.000Z"
  472      };
  473      sessions.push(nextSession);
  474      writeJson(201, {
  475        ok: true,
  476        data: {
  477          session: {
  478            ...nextSession,
  479            endpoint: baseUrl
  480          }
  481        }
  482      });
  483      return;
  484    }
  485
  486    if (method === "POST" && url.pathname === "/v1/codexd/turn") {
  487      const sessionId = parsedBody?.sessionId;
  488      const session = sessions.find((entry) => entry.sessionId === sessionId);
  489
  490      if (!session) {
  491        writeJson(404, {
  492          ok: false,
  493          error: "not_found",
  494          message: `Unknown codexd session "${sessionId}".`
  495        });
  496        return;
  497      }
  498
  499      session.lastTurnId = "turn-created";
  500      session.lastTurnStatus = "accepted";
  501      session.updatedAt = "2026-03-23T00:00:05.000Z";
  502      writeJson(202, {
  503        ok: true,
  504        data: {
  505          accepted: true,
  506          session: {
  507            ...session,
  508            endpoint: baseUrl
  509          },
  510          turnId: "turn-created"
  511        }
  512      });
  513      return;
  514    }
  515
  516    const sessionMatch = url.pathname.match(/^\/v1\/codexd\/sessions\/([^/]+)$/u);
  517
  518    if (method === "GET" && sessionMatch?.[1]) {
  519      const session = sessions.find((entry) => entry.sessionId === decodeURIComponent(sessionMatch[1]));
  520
  521      if (!session) {
  522        writeJson(404, {
  523          ok: false,
  524          error: "not_found",
  525          message: `Unknown codexd session "${decodeURIComponent(sessionMatch[1])}".`
  526        });
  527        return;
  528      }
  529
  530      writeJson(200, {
  531        ok: true,
  532        data: {
  533          session: {
  534            ...session,
  535            endpoint: baseUrl
  536          },
  537          recentEvents: [
  538            {
  539              seq: 1,
  540              type: "turn.completed"
  541            }
  542          ]
  543        }
  544      });
  545      return;
  546    }
  547
  548    writeJson(404, {
  549      ok: false,
  550      error: "not_found",
  551      message: `Unknown codexd route ${method} ${url.pathname}.`
  552    });
  553  });
  554
  555  await new Promise((resolve, reject) => {
  556    server.once("error", reject);
  557    server.listen(0, "127.0.0.1", resolve);
  558  });
  559
  560  const address = server.address();
  561  const port = typeof address === "object" && address ? address.port : 0;
  562
  563  return {
  564    baseUrl: `http://127.0.0.1:${port}`,
  565    requests,
  566    async stop() {
  567      await new Promise((resolve, reject) => {
  568        server.close((error) => {
  569          if (error) {
  570            reject(error);
  571            return;
  572          }
  573
  574          resolve();
  575        });
  576        server.closeAllConnections?.();
  577      });
  578    }
  579  };
  580}
  581
  582async function withArtifactStoreFixture(callback) {
  583  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-artifact-store-"));
  584  const artifactStore = new ArtifactStore({
  585    artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
  586    databasePath: join(stateDir, ARTIFACT_DB_FILENAME),
  587    publicBaseUrl: "https://conductor.makefile.so"
  588  });
  589
  590  try {
  591    return await callback({
  592      artifactStore,
  593      stateDir
  594    });
  595  } finally {
  596    artifactStore.close();
  597    rmSync(stateDir, {
  598      force: true,
  599      recursive: true
  600    });
  601  }
  602}
  603
  604async function withConductorUiDistFixture(callback) {
  605  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-ui-dist-"));
  606  const uiDistDir = join(stateDir, "dist");
  607  const assetFile = "app-shell.js";
  608
  609  mkdirSync(join(uiDistDir, "assets"), {
  610    recursive: true
  611  });
  612  writeFileSync(
  613    join(uiDistDir, "index.html"),
  614    [
  615      "<!doctype html>",
  616      "<html lang=\"zh-CN\">",
  617      "<head><meta charset=\"UTF-8\" /><title>Conductor UI Shell Fixture</title></head>",
  618      "<body>",
  619      "<div id=\"app\">fixture</div>",
  620      `<script type="module" src="/app/assets/${assetFile}"></script>`,
  621      "</body>",
  622      "</html>"
  623    ].join("")
  624  );
  625  writeFileSync(join(uiDistDir, "assets", assetFile), "console.log('fixture ui shell');\n");
  626
  627  try {
  628    return await callback({
  629      assetFile,
  630      uiDistDir
  631    });
  632  } finally {
  633    rmSync(stateDir, {
  634      force: true,
  635      recursive: true
  636    });
  637  }
  638}
  639
  640function parseJsonBody(response) {
  641  return JSON.parse(response.body);
  642}
  643
  644function readBinaryBodyText(response) {
  645  return typeof response.body === "string" ? response.body : Buffer.from(response.body).toString("utf8");
  646}
  647
  648test(
  649  "BAA instruction extraction and parsing supports the four parameter forms and ignores ordinary code fences",
  650  () => {
  651    const message = [
  652      "Ignore the ordinary code block first.",
  653      "```js",
  654      "@conductor::exec::printf 'ignore-me'",
  655      "```",
  656      "",
  657      "```baa",
  658      "@conductor::describe",
  659      "```",
  660      "",
  661      "```baa",
  662      "@conductor::exec::printf 'inline-string'",
  663      "```",
  664      "",
  665      "```baa",
  666      '@conductor::files/read::{"cwd":"/tmp","path":"README.md"}',
  667      "```",
  668      "",
  669      "```baa",
  670      "@conductor::exec",
  671      "printf 'multiline-body'",
  672      "pwd",
  673      "```"
  674    ].join("\n");
  675
  676    const blocks = extractBaaInstructionBlocks(message);
  677
  678    assert.equal(blocks.length, 4);
  679    assert.deepEqual(
  680      blocks.map((block) => block.blockIndex),
  681      [0, 1, 2, 3]
  682    );
  683
  684    const parsed = blocks.map((block) => parseBaaInstructionBlock(block));
  685
  686    assert.equal(parsed[0].target, "conductor");
  687    assert.equal(parsed[0].tool, "describe");
  688    assert.equal(parsed[0].paramsKind, "none");
  689    assert.equal(parsed[0].params, null);
  690
  691    assert.equal(parsed[1].tool, "exec");
  692    assert.equal(parsed[1].paramsKind, "inline_string");
  693    assert.equal(parsed[1].params, "printf 'inline-string'");
  694
  695    assert.equal(parsed[2].tool, "files/read");
  696    assert.equal(parsed[2].paramsKind, "inline_json");
  697    assert.deepEqual(parsed[2].params, {
  698      cwd: "/tmp",
  699      path: "README.md"
  700    });
  701
  702    assert.equal(parsed[3].tool, "exec");
  703    assert.equal(parsed[3].paramsKind, "body");
  704    assert.equal(parsed[3].params, "printf 'multiline-body'\npwd");
  705  }
  706);
  707
  708test("BAA instruction extraction ignores unterminated baa blocks and keeps closed ones", () => {
  709  const message = [
  710    "```baa",
  711    "@conductor::describe",
  712    "```",
  713    "",
  714    "```baa",
  715    "@conductor::exec::printf 'broken-tail'"
  716  ].join("\n");
  717
  718  const blocks = extractBaaInstructionBlocks(message);
  719
  720  assert.equal(blocks.length, 1);
  721  assert.equal(blocks[0].blockIndex, 0);
  722  assert.equal(parseBaaInstructionBlock(blocks[0]).tool, "describe");
  723});
  724
  725test("BAA instruction extraction accepts fence attributes and rejects non-baa languages", () => {
  726  const message = [
  727    "```baa",
  728    "@conductor::describe",
  729    "```",
  730    "",
  731    '```baa id="p4k2vt"',
  732    "@conductor::status",
  733    "```",
  734    "",
  735    "```javascript",
  736    "@conductor::exec::printf 'ignore-js'",
  737    "```",
  738    "",
  739    "```baa-ext",
  740    "@conductor::exec::printf 'ignore-baa-ext'",
  741    "```"
  742  ].join("\n");
  743
  744  const blocks = extractBaaInstructionBlocks(message);
  745
  746  assert.equal(blocks.length, 2);
  747  assert.deepEqual(
  748    blocks.map((block) => block.content),
  749    ["@conductor::describe", "@conductor::status"]
  750  );
  751  assert.equal(blocks[1].rawBlock, '```baa id="p4k2vt"\n@conductor::status\n```');
  752});
  753
  754test("InMemoryBaaInstructionDeduper evicts the oldest keys when maxSize is exceeded", () => {
  755  const deduper = new InMemoryBaaInstructionDeduper({
  756    maxSize: 2
  757  });
  758
  759  deduper.add({
  760    dedupeKey: "sha256:key-1"
  761  });
  762  deduper.add({
  763    dedupeKey: "sha256:key-2"
  764  });
  765  deduper.add({
  766    dedupeKey: "sha256:key-3"
  767  });
  768
  769  assert.equal(deduper.has("sha256:key-1"), false);
  770  assert.equal(deduper.has("sha256:key-2"), true);
  771  assert.equal(deduper.has("sha256:key-3"), true);
  772});
  773
  774test("InMemoryBaaLiveInstructionMessageDeduper evicts the oldest keys when maxSize is exceeded", () => {
  775  const deduper = new InMemoryBaaLiveInstructionMessageDeduper({
  776    maxSize: 2
  777  });
  778
  779  deduper.add("sha256:msg-1");
  780  deduper.add("sha256:msg-2");
  781  deduper.add("sha256:msg-3");
  782
  783  assert.equal(deduper.has("sha256:msg-1"), false);
  784  assert.equal(deduper.has("sha256:msg-2"), true);
  785  assert.equal(deduper.has("sha256:msg-3"), true);
  786});
  787
  788test("BAA instruction normalization keeps auditable fields and stable dedupe keys", () => {
  789  const source = {
  790    assistantMessageId: "msg-001",
  791    conversationId: "conv-001",
  792    platform: "claude"
  793  };
  794  const left = normalizeBaaInstruction(source, {
  795    blockIndex: 0,
  796    params: {
  797      path: "/tmp/demo.txt",
  798      overwrite: true,
  799      content: "hello"
  800    },
  801    paramsKind: "inline_json",
  802    rawBlock: "```baa\n@conductor::files/write::{}\n```",
  803    rawInstruction: '@conductor::files/write::{"path":"/tmp/demo.txt","overwrite":true,"content":"hello"}',
  804    target: "conductor",
  805    tool: "files/write"
  806  });
  807  const right = normalizeBaaInstruction(source, {
  808    blockIndex: 0,
  809    params: {
  810      content: "hello",
  811      path: "/tmp/demo.txt",
  812      overwrite: true
  813    },
  814    paramsKind: "inline_json",
  815    rawBlock: "```baa\n@conductor::files/write::{}\n```",
  816    rawInstruction: '@conductor::files/write::{"content":"hello","path":"/tmp/demo.txt","overwrite":true}',
  817    target: "conductor",
  818    tool: "files/write"
  819  });
  820
  821  assert.equal(left.dedupeKey, right.dedupeKey);
  822  assert.equal(left.instructionId, right.instructionId);
  823  assert.deepEqual(left.dedupeBasis, {
  824    assistant_message_id: "msg-001",
  825    block_index: 0,
  826    conversation_id: "conv-001",
  827    params: {
  828      content: "hello",
  829      overwrite: true,
  830      path: "/tmp/demo.txt"
  831    },
  832    platform: "claude",
  833    target: "conductor",
  834    tool: "files/write",
  835    version: "baa.v1"
  836  });
  837});
  838
  839test("routeBaaInstruction maps browser send/current targets to the existing local browser routes", () => {
  840  for (const platform of ["claude", "chatgpt", "gemini"]) {
  841    const sendRoute = routeBaaInstruction(createInstructionEnvelope({
  842      params: `hello ${platform} from baa`,
  843      target: `browser.${platform}`,
  844      tool: "send"
  845    }));
  846    assert.deepEqual(sendRoute, {
  847      body: {
  848        prompt: `hello ${platform} from baa`
  849      },
  850      key: `local.browser.${platform}.send`,
  851      method: "POST",
  852      path: `/v1/browser/${platform}/send`,
  853      requiresSharedToken: false,
  854      timeoutMs: DEFAULT_BAA_INSTRUCTION_TIMEOUT_MS
  855    });
  856
  857    const currentRoute = routeBaaInstruction(createInstructionEnvelope({
  858      target: `browser.${platform}`,
  859      tool: "current"
  860    }));
  861    assert.deepEqual(currentRoute, {
  862      body: null,
  863      key: `local.browser.${platform}.current`,
  864      method: "GET",
  865      path: `/v1/browser/${platform}/current`,
  866      requiresSharedToken: false,
  867      timeoutMs: DEFAULT_BAA_INSTRUCTION_TIMEOUT_MS
  868    });
  869  }
  870});
  871
  872test("routeBaaInstruction applies a 60s default timeout to conductor exec", () => {
  873  const execRoute = routeBaaInstruction(createInstructionEnvelope({
  874    params: "printf 'exec-timeout-default'",
  875    tool: "exec"
  876  }));
  877
  878  assert.equal(execRoute.timeoutMs, DEFAULT_BAA_EXEC_INSTRUCTION_TIMEOUT_MS);
  879  assert.deepEqual(execRoute.body, {
  880    command: "printf 'exec-timeout-default'",
  881    timeoutMs: DEFAULT_BAA_EXEC_INSTRUCTION_TIMEOUT_MS
  882  });
  883});
  884
  885test("evaluateBaaInstructionPolicy keeps the default Phase 1 policy behavior", () => {
  886  const allowed = evaluateBaaInstructionPolicy(createInstructionEnvelope({
  887    target: "browser.chatgpt",
  888    tool: "send"
  889  }));
  890  const denied = evaluateBaaInstructionPolicy(createInstructionEnvelope({
  891    target: "browser.chatgpt",
  892    tool: "reload"
  893  }));
  894
  895  assert.deepEqual(allowed, {
  896    code: null,
  897    message: null,
  898    ok: true
  899  });
  900  assert.equal(denied.ok, false);
  901  assert.equal(denied.code, "unsupported_tool");
  902  assert.match(denied.message ?? "", /not supported in Phase 1/i);
  903});
  904
  905test("executeBaaInstruction returns a structured timeout failure when the local handler stalls", async () => {
  906  const { controlPlane, snapshot } = await createLocalApiFixture();
  907  const instruction = createInstructionEnvelope({
  908    params: "printf 'timeout-protected'",
  909    tool: "exec"
  910  });
  911  const route = {
  912    ...routeBaaInstruction(instruction),
  913    timeoutMs: 20
  914  };
  915  let requestSignal = null;
  916  let resolveAbortObserved;
  917  const abortObserved = new Promise((resolve) => {
  918    resolveAbortObserved = resolve;
  919  });
  920
  921  try {
  922    const result = await executeBaaInstruction(
  923      instruction,
  924      route,
  925      {
  926        repository: null,
  927        sharedToken: "local-shared-token",
  928        snapshotLoader: () => snapshot
  929      },
  930      {
  931        requestHandler: (request) => {
  932          requestSignal = request.signal ?? null;
  933          request.signal?.addEventListener("abort", () => {
  934            resolveAbortObserved();
  935          }, {
  936            once: true
  937          });
  938          return new Promise(() => {});
  939        }
  940      }
  941    );
  942
  943    await abortObserved;
  944
  945    assert.equal(result.ok, false);
  946    assert.equal(result.httpStatus, 504);
  947    assert.equal(result.error, "execution_timeout");
  948    assert.equal(result.message, "Local instruction execution timed out after 20ms.");
  949    assert.deepEqual(result.details, {
  950      timeout_ms: 20
  951    });
  952    assert.equal(result.route.key, "local.exec");
  953    assert.ok(result.artifact);
  954    assert.equal(requestSignal?.aborted, true);
  955  } finally {
  956    controlPlane.close();
  957  }
  958});
  959
  960test("BaaInstructionCenter applies custom policy overrides for target and tool allowlists", async () => {
  961  const { controlPlane, repository, sharedToken, snapshot } = await createLocalApiFixture();
  962  const targets = createDefaultBaaInstructionPolicyTargetTools();
  963  const chatgptTools = targets.get("browser.chatgpt");
  964  assert.ok(chatgptTools);
  965  targets.delete("browser.gemini");
  966  chatgptTools.add("reload");
  967  const center = new BaaInstructionCenter({
  968    localApiContext: {
  969      fetchImpl: globalThis.fetch,
  970      repository,
  971      sharedToken,
  972      snapshotLoader: () => snapshot
  973    },
  974    policy: {
  975      targets
  976    }
  977  });
  978  const message = [
  979    "```baa",
  980    "@conductor::describe",
  981    "```",
  982    "",
  983    "```baa",
  984    "@browser.chatgpt::reload",
  985    "```",
  986    "",
  987    "```baa",
  988    "@browser.gemini::send::hello gemini",
  989    "```"
  990  ].join("\n");
  991
  992  try {
  993    const result = await center.processAssistantMessage({
  994      assistantMessageId: "msg-custom-policy-1",
  995      conversationId: "conv-custom-policy-1",
  996      platform: "claude",
  997      text: message
  998    });
  999
 1000    assert.equal(result.status, "executed");
 1001    assert.equal(result.executions.length, 1);
 1002    assert.equal(result.executions[0]?.tool, "describe");
 1003    assert.equal(result.denied.length, 2);
 1004
 1005    const expandedToolDeny = result.denied.find(
 1006      (entry) => entry.instruction.target === "browser.chatgpt" && entry.instruction.tool === "reload"
 1007    );
 1008    assert.ok(expandedToolDeny);
 1009    assert.equal(expandedToolDeny.stage, "route");
 1010    assert.equal(expandedToolDeny.code, null);
 1011    assert.match(expandedToolDeny.reason, /No Phase 1 route exists/i);
 1012
 1013    const removedTargetDeny = result.denied.find(
 1014      (entry) => entry.instruction.target === "browser.gemini" && entry.instruction.tool === "send"
 1015    );
 1016    assert.ok(removedTargetDeny);
 1017    assert.equal(removedTargetDeny.stage, "policy");
 1018    assert.equal(removedTargetDeny.code, "unsupported_target");
 1019  } finally {
 1020    controlPlane.close();
 1021  }
 1022});
 1023
 1024test("BaaInstructionCenter runs the Phase 1 execution loop and dedupes replayed messages", async () => {
 1025  const { controlPlane, repository, sharedToken, snapshot } = await createLocalApiFixture();
 1026  const hostOpsDir = mkdtempSync(join(tmpdir(), "baa-instruction-center-"));
 1027  const readableFilePath = join(hostOpsDir, "seed.txt");
 1028  const localApiContext = {
 1029    fetchImpl: globalThis.fetch,
 1030    repository,
 1031    sharedToken,
 1032    snapshotLoader: () => snapshot
 1033  };
 1034  const center = new BaaInstructionCenter({
 1035    localApiContext
 1036  });
 1037  const message = [
 1038    "Run the minimal Phase 1 toolset.",
 1039    "```baa",
 1040    "@conductor::describe",
 1041    "```",
 1042    "",
 1043    "```baa",
 1044    "@conductor::status",
 1045    "```",
 1046    "",
 1047    "```baa",
 1048    `@conductor::exec::{"command":"printf 'instruction-loop-ok'","cwd":${JSON.stringify(hostOpsDir)}}`,
 1049    "```",
 1050    "",
 1051    "```baa",
 1052    `@conductor::files/write::{"path":"written.txt","cwd":${JSON.stringify(hostOpsDir)},"content":"hello from baa","overwrite":true,"createParents":true}`,
 1053    "```",
 1054    "",
 1055    "```baa",
 1056    `@conductor::files/read::{"path":"seed.txt","cwd":${JSON.stringify(hostOpsDir)}}`,
 1057    "```"
 1058  ].join("\n");
 1059
 1060  writeFileSync(readableFilePath, "seed from fixture", "utf8");
 1061
 1062  try {
 1063    const firstPass = await center.processAssistantMessage({
 1064      assistantMessageId: "msg-loop-1",
 1065      conversationId: "conv-loop-1",
 1066      platform: "claude",
 1067      text: message
 1068    });
 1069
 1070    assert.equal(firstPass.status, "executed");
 1071    assert.equal(firstPass.duplicates.length, 0);
 1072    assert.equal(firstPass.instructions.length, 5);
 1073    assert.equal(firstPass.executions.length, 5);
 1074    assert.equal(firstPass.parseErrors.length, 0);
 1075
 1076    const describeExecution = firstPass.executions.find((execution) => execution.tool === "describe");
 1077    assert.ok(describeExecution);
 1078    assert.equal(describeExecution.ok, true);
 1079    assert.equal(describeExecution.data.name, "baa-conductor-daemon");
 1080
 1081    const statusExecution = firstPass.executions.find((execution) => execution.tool === "status");
 1082    assert.ok(statusExecution);
 1083    assert.equal(statusExecution.ok, true);
 1084    assert.equal(statusExecution.data.mode, "running");
 1085    assert.equal(statusExecution.data.activeRuns, 1);
 1086
 1087    const execExecution = firstPass.executions.find((execution) => execution.tool === "exec");
 1088    assert.ok(execExecution);
 1089    assert.equal(execExecution.ok, true);
 1090    assert.equal(execExecution.data.operation, "exec");
 1091    assert.equal(execExecution.data.result.stdout, "instruction-loop-ok");
 1092
 1093    const writeExecution = firstPass.executions.find(
 1094      (execution) => execution.tool === "files/write"
 1095    );
 1096    assert.ok(writeExecution);
 1097    assert.equal(writeExecution.ok, true);
 1098    assert.equal(writeExecution.data.operation, "files/write");
 1099    assert.equal(writeExecution.data.result.created, true);
 1100
 1101    const readExecution = firstPass.executions.find(
 1102      (execution) => execution.tool === "files/read"
 1103    );
 1104    assert.ok(readExecution);
 1105    assert.equal(readExecution.ok, true);
 1106    assert.equal(readExecution.data.operation, "files/read");
 1107    assert.equal(readExecution.data.result.content, "seed from fixture");
 1108
 1109    const replayPass = await center.processAssistantMessage({
 1110      assistantMessageId: "msg-loop-1",
 1111      conversationId: "conv-loop-1",
 1112      platform: "claude",
 1113      text: message
 1114    });
 1115
 1116    assert.equal(replayPass.status, "duplicate_only");
 1117    assert.equal(replayPass.duplicates.length, 5);
 1118    assert.equal(replayPass.executions.length, 0);
 1119    assert.equal(replayPass.parseErrors.length, 0);
 1120  } finally {
 1121    controlPlane.close();
 1122    rmSync(hostOpsDir, {
 1123      force: true,
 1124      recursive: true
 1125    });
 1126  }
 1127});
 1128
 1129test("BaaInstructionCenter blocks ordinary instructions while system automation is paused and resumes through control instructions", async () => {
 1130  const { controlPlane, repository, sharedToken, snapshot } = await createLocalApiFixture();
 1131  const hostOpsDir = mkdtempSync(join(tmpdir(), "baa-instruction-center-system-paused-"));
 1132  const outputPath = join(hostOpsDir, "system-gate-restored.txt");
 1133  const center = new BaaInstructionCenter({
 1134    localApiContext: {
 1135      fetchImpl: globalThis.fetch,
 1136      repository,
 1137      sharedToken,
 1138      snapshotLoader: () => snapshot
 1139    }
 1140  });
 1141  const writeMessage = [
 1142    "```baa",
 1143    `@conductor::files/write::${JSON.stringify({
 1144      path: "system-gate-restored.txt",
 1145      cwd: hostOpsDir,
 1146      content: "system gate lifted",
 1147      overwrite: true
 1148    })}`,
 1149    "```"
 1150  ].join("\n");
 1151
 1152  try {
 1153    await repository.setAutomationMode("paused", 250);
 1154
 1155    const blocked = await center.processAssistantMessage({
 1156      assistantMessageId: "msg-system-paused-1",
 1157      conversationId: "conv-system-paused-1",
 1158      platform: "claude",
 1159      text: writeMessage
 1160    });
 1161
 1162    assert.equal(blocked.status, "system_paused");
 1163    assert.equal(blocked.executions.length, 0);
 1164    assert.equal(existsSync(outputPath), false);
 1165
 1166    const resume = await center.processAssistantMessage({
 1167      assistantMessageId: "msg-system-paused-resume-1",
 1168      conversationId: "conv-system-paused-1",
 1169      platform: "claude",
 1170      text: ["```baa", "@conductor::system/resume", "```"].join("\n")
 1171    });
 1172
 1173    assert.equal(resume.status, "executed");
 1174    assert.equal(resume.executions.length, 1);
 1175    assert.equal(resume.executions[0]?.ok, true);
 1176    assert.equal((await repository.getAutomationState())?.mode, "running");
 1177
 1178    const resumed = await center.processAssistantMessage({
 1179      assistantMessageId: "msg-system-paused-2",
 1180      conversationId: "conv-system-paused-1",
 1181      platform: "claude",
 1182      text: writeMessage
 1183    });
 1184
 1185    assert.equal(resumed.status, "executed");
 1186    assert.equal(resumed.executions.length, 1);
 1187    assert.equal(resumed.executions[0]?.ok, true);
 1188    assert.equal(readFileSync(outputPath, "utf8"), "system gate lifted");
 1189  } finally {
 1190    controlPlane.close();
 1191    rmSync(hostOpsDir, {
 1192      force: true,
 1193      recursive: true
 1194    });
 1195  }
 1196});
 1197
 1198test("BaaInstructionCenter executes browser.claude send/current through the Phase 1 route layer", async () => {
 1199  const { controlPlane, repository, snapshot } = await createLocalApiFixture();
 1200  const browser = createBrowserBridgeStub();
 1201  const center = new BaaInstructionCenter({
 1202    localApiContext: {
 1203      ...browser.context,
 1204      fetchImpl: globalThis.fetch,
 1205      repository,
 1206      snapshotLoader: () => snapshot
 1207    }
 1208  });
 1209  const message = [
 1210    "```baa",
 1211    "@browser.claude::send::hello from instruction center",
 1212    "```",
 1213    "",
 1214    "```baa",
 1215    "@browser.claude::current",
 1216    "```"
 1217  ].join("\n");
 1218
 1219  try {
 1220    const result = await center.processAssistantMessage({
 1221      assistantMessageId: "msg-browser-claude-1",
 1222      conversationId: "conv-browser-claude-1",
 1223      platform: "claude",
 1224      text: message
 1225    });
 1226
 1227    assert.equal(result.status, "executed");
 1228    assert.equal(result.denied.length, 0);
 1229    assert.equal(result.executions.length, 2);
 1230
 1231    const sendExecution = result.executions.find((execution) => execution.tool === "send");
 1232    assert.ok(sendExecution);
 1233    assert.equal(sendExecution.ok, true);
 1234    assert.equal(sendExecution.route.path, "/v1/browser/claude/send");
 1235    assert.equal(sendExecution.data.conversation.conversation_id, "conv-1");
 1236    assert.equal(sendExecution.data.response.accepted, true);
 1237
 1238    const currentExecution = result.executions.find((execution) => execution.tool === "current");
 1239    assert.ok(currentExecution);
 1240    assert.equal(currentExecution.ok, true);
 1241    assert.equal(currentExecution.route.path, "/v1/browser/claude/current");
 1242    assert.equal(currentExecution.data.conversation.conversation_id, "conv-1");
 1243    assert.equal(currentExecution.data.messages.length, 2);
 1244
 1245    const completionCall = browser.calls.find(
 1246      (call) =>
 1247        call.kind === "apiRequest"
 1248        && call.path === "/api/organizations/org-1/chat_conversations/conv-1/completion"
 1249    );
 1250    assert.ok(completionCall);
 1251    assert.deepEqual(completionCall.body, {
 1252      prompt: "hello from instruction center"
 1253    });
 1254
 1255    const currentConversationCall = browser.calls.find(
 1256      (call) =>
 1257        call.kind === "apiRequest"
 1258        && call.path === "/api/organizations/org-1/chat_conversations/conv-1"
 1259    );
 1260    assert.ok(currentConversationCall);
 1261  } finally {
 1262    controlPlane.close();
 1263  }
 1264});
 1265
 1266test("BaaInstructionCenter executes browser.chatgpt and browser.gemini send/current through the Phase 1 route layer", async () => {
 1267  const { controlPlane, repository, snapshot } = await createLocalApiFixture();
 1268  const browser = createBrowserBridgeStub();
 1269  const browserState = browser.context.browserStateLoader();
 1270
 1271  browserState.clients[0].request_hooks.push(
 1272    {
 1273      account: "ops@example.com",
 1274      credential_fingerprint: "fp-chatgpt-stub",
 1275      platform: "chatgpt",
 1276      endpoint_count: 1,
 1277      endpoint_metadata: [
 1278        {
 1279          method: "POST",
 1280          path: "/backend-api/conversation",
 1281          first_seen_at: 1710000002600,
 1282          last_seen_at: 1710000003600
 1283        }
 1284      ],
 1285      endpoints: [
 1286        "POST /backend-api/conversation"
 1287      ],
 1288      last_verified_at: 1710000003650,
 1289      updated_at: 1710000003550
 1290    },
 1291    {
 1292      account: "ops@example.com",
 1293      credential_fingerprint: "fp-gemini-stub",
 1294      platform: "gemini",
 1295      endpoint_count: 1,
 1296      endpoint_metadata: [
 1297        {
 1298          method: "POST",
 1299          path: "/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate",
 1300          first_seen_at: 1710000002700,
 1301          last_seen_at: 1710000003700
 1302        }
 1303      ],
 1304      endpoints: [
 1305        "POST /_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"
 1306      ],
 1307      last_verified_at: 1710000003750,
 1308      updated_at: 1710000003650
 1309    }
 1310  );
 1311  browserState.clients[0].shell_runtime.push(
 1312    buildShellRuntime("chatgpt", {
 1313      actual: {
 1314        ...buildShellRuntime("chatgpt").actual,
 1315        url: "https://chatgpt.com/c/conv-chatgpt-current"
 1316      }
 1317    }),
 1318    buildShellRuntime("gemini", {
 1319      actual: {
 1320        ...buildShellRuntime("gemini").actual,
 1321        url: "https://gemini.google.com/app/conv-gemini-current"
 1322      }
 1323    })
 1324  );
 1325  browserState.clients[0].final_messages.push(
 1326    {
 1327      conversation_id: "conv-chatgpt-current",
 1328      observed_at: 1710000003800,
 1329      page_title: "ChatGPT Current",
 1330      page_url: "https://chatgpt.com/c/conv-chatgpt-current",
 1331      platform: "chatgpt",
 1332      raw_text: "hello from chatgpt current"
 1333    },
 1334    {
 1335      conversation_id: "conv-gemini-current",
 1336      observed_at: 1710000003900,
 1337      page_title: "Gemini Current",
 1338      page_url: "https://gemini.google.com/app/conv-gemini-current",
 1339      platform: "gemini",
 1340      raw_text: "hello from gemini current"
 1341    }
 1342  );
 1343
 1344  const center = new BaaInstructionCenter({
 1345    localApiContext: {
 1346      ...browser.context,
 1347      browserStateLoader: () => browserState,
 1348      fetchImpl: globalThis.fetch,
 1349      repository,
 1350      snapshotLoader: () => snapshot
 1351    }
 1352  });
 1353  const message = [
 1354    "```baa",
 1355    "@browser.chatgpt::send::hello chatgpt",
 1356    "```",
 1357    "",
 1358    "```baa",
 1359    "@browser.chatgpt::current",
 1360    "```",
 1361    "",
 1362    "```baa",
 1363    "@browser.gemini::send::hello gemini",
 1364    "```",
 1365    "",
 1366    "```baa",
 1367    "@browser.gemini::current",
 1368    "```"
 1369  ].join("\n");
 1370
 1371  try {
 1372    const result = await center.processAssistantMessage({
 1373      assistantMessageId: "msg-browser-compat-1",
 1374      conversationId: "conv-browser-compat-1",
 1375      platform: "claude",
 1376      text: message
 1377    });
 1378
 1379    assert.equal(result.status, "executed");
 1380    assert.equal(result.denied.length, 0);
 1381    assert.equal(result.executions.length, 4);
 1382
 1383    const chatgptSend = result.executions.find((execution) => execution.target === "browser.chatgpt" && execution.tool === "send");
 1384    assert.ok(chatgptSend);
 1385    assert.equal(chatgptSend.ok, true);
 1386    assert.equal(chatgptSend.route.path, "/v1/browser/chatgpt/send");
 1387    assert.equal(chatgptSend.data.proxy.path, "/backend-api/conversation");
 1388
 1389    const chatgptCurrent = result.executions.find((execution) => execution.target === "browser.chatgpt" && execution.tool === "current");
 1390    assert.ok(chatgptCurrent);
 1391    assert.equal(chatgptCurrent.ok, true);
 1392    assert.equal(chatgptCurrent.route.path, "/v1/browser/chatgpt/current");
 1393    assert.equal(chatgptCurrent.data.conversation.conversation_id, "conv-chatgpt-current");
 1394    assert.equal(chatgptCurrent.data.messages[0].text, "hello from chatgpt current");
 1395    assert.equal(chatgptCurrent.data.proxy.path, "/backend-api/conversation/conv-chatgpt-current");
 1396
 1397    const geminiSend = result.executions.find((execution) => execution.target === "browser.gemini" && execution.tool === "send");
 1398    assert.ok(geminiSend);
 1399    assert.equal(geminiSend.ok, true);
 1400    assert.equal(geminiSend.route.path, "/v1/browser/gemini/send");
 1401    assert.equal(
 1402      geminiSend.data.proxy.path,
 1403      "/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"
 1404    );
 1405
 1406    const geminiCurrent = result.executions.find((execution) => execution.target === "browser.gemini" && execution.tool === "current");
 1407    assert.ok(geminiCurrent);
 1408    assert.equal(geminiCurrent.ok, true);
 1409    assert.equal(geminiCurrent.route.path, "/v1/browser/gemini/current");
 1410    assert.equal(geminiCurrent.data.conversation.conversation_id, "conv-gemini-current");
 1411    assert.equal(geminiCurrent.data.messages[0].text, "hello from gemini current");
 1412    assert.equal(geminiCurrent.data.proxy, null);
 1413
 1414    assert.ok(
 1415      browser.calls.find(
 1416        (call) => call.kind === "apiRequest" && call.path === "/backend-api/conversation"
 1417      )
 1418    );
 1419    assert.ok(
 1420      browser.calls.find(
 1421        (call) =>
 1422          call.kind === "apiRequest"
 1423          && call.path === "/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"
 1424      )
 1425    );
 1426    assert.ok(
 1427      browser.calls.find(
 1428        (call) =>
 1429          call.kind === "apiRequest"
 1430          && call.path === "/backend-api/conversation/conv-chatgpt-current"
 1431      )
 1432    );
 1433  } finally {
 1434    controlPlane.close();
 1435  }
 1436});
 1437
 1438test("BaaInstructionCenter keeps supported instructions running when one instruction is denied", async () => {
 1439  const { controlPlane, repository, sharedToken, snapshot } = await createLocalApiFixture();
 1440  const hostOpsDir = mkdtempSync(join(tmpdir(), "baa-instruction-center-partial-deny-"));
 1441  const allowedFilePath = join(hostOpsDir, "still-runs.txt");
 1442  const center = new BaaInstructionCenter({
 1443    localApiContext: {
 1444      fetchImpl: globalThis.fetch,
 1445      repository,
 1446      sharedToken,
 1447      snapshotLoader: () => snapshot
 1448    }
 1449  });
 1450  const message = [
 1451    "```baa",
 1452    `@conductor::files/write::{"path":"still-runs.txt","cwd":${JSON.stringify(hostOpsDir)},"content":"partial deny is open","overwrite":true}`,
 1453    "```",
 1454    "",
 1455    "```baa",
 1456    "@browser.gemini::reload",
 1457    "```",
 1458    "",
 1459    "```baa",
 1460    `@conductor::exec::{"command":"printf 'instruction-still-runs'","cwd":${JSON.stringify(hostOpsDir)}}`,
 1461    "```"
 1462  ].join("\n");
 1463
 1464  try {
 1465    const result = await center.processAssistantMessage({
 1466      assistantMessageId: "msg-partial-deny-1",
 1467      conversationId: "conv-partial-deny-1",
 1468      platform: "claude",
 1469      text: message
 1470    });
 1471
 1472    assert.equal(result.status, "executed");
 1473    assert.equal(result.instructions.length, 3);
 1474    assert.equal(result.duplicates.length, 0);
 1475    assert.equal(result.executions.length, 2);
 1476    assert.equal(result.denied.length, 1);
 1477
 1478    const writeExecution = result.executions.find((execution) => execution.tool === "files/write");
 1479    assert.ok(writeExecution);
 1480    assert.equal(writeExecution.ok, true);
 1481    assert.equal(readFileSync(allowedFilePath, "utf8"), "partial deny is open");
 1482
 1483    const execExecution = result.executions.find((execution) => execution.tool === "exec");
 1484    assert.ok(execExecution);
 1485    assert.equal(execExecution.ok, true);
 1486    assert.equal(execExecution.data.result.stdout, "instruction-still-runs");
 1487
 1488    assert.equal(result.denied[0].blockIndex, 1);
 1489    assert.equal(result.denied[0].stage, "policy");
 1490    assert.equal(result.denied[0].code, "unsupported_tool");
 1491    assert.equal(result.denied[0].instruction.target, "browser.gemini");
 1492    assert.equal(result.denied[0].instruction.tool, "reload");
 1493    assert.match(result.denied[0].reason, /not supported in Phase 1/i);
 1494  } finally {
 1495    controlPlane.close();
 1496    rmSync(hostOpsDir, {
 1497      force: true,
 1498      recursive: true
 1499    });
 1500  }
 1501});
 1502
 1503test("BaaInstructionCenter skips malformed blocks and keeps later valid blocks running", async () => {
 1504  const { controlPlane, repository, sharedToken, snapshot } = await createLocalApiFixture();
 1505  const hostOpsDir = mkdtempSync(join(tmpdir(), "baa-instruction-center-parse-isolation-"));
 1506  const outputPath = join(hostOpsDir, "after-parse-error.txt");
 1507  const center = new BaaInstructionCenter({
 1508    localApiContext: {
 1509      fetchImpl: globalThis.fetch,
 1510      repository,
 1511      sharedToken,
 1512      snapshotLoader: () => snapshot
 1513    }
 1514  });
 1515  const message = [
 1516    "```baa",
 1517    '@conductor::files/write::{"path":"broken.txt"',
 1518    "```",
 1519    "",
 1520    "```baa",
 1521    `@conductor::files/write::{"path":"after-parse-error.txt","cwd":${JSON.stringify(hostOpsDir)},"content":"still-runs","overwrite":true}`,
 1522    "```"
 1523  ].join("\n");
 1524
 1525  try {
 1526    const result = await center.processAssistantMessage({
 1527      assistantMessageId: "msg-parse-isolation-1",
 1528      conversationId: "conv-parse-isolation-1",
 1529      platform: "claude",
 1530      text: message
 1531    });
 1532
 1533    assert.equal(result.status, "executed");
 1534    assert.equal(result.instructions.length, 1);
 1535    assert.equal(result.duplicates.length, 0);
 1536    assert.equal(result.denied.length, 0);
 1537    assert.equal(result.executions.length, 1);
 1538    assert.equal(result.parseErrors.length, 1);
 1539    assert.equal(result.parseErrors[0].blockIndex, 0);
 1540    assert.equal(result.parseErrors[0].stage, "parse");
 1541    assert.match(result.parseErrors[0].message, /Failed to parse inline JSON params/i);
 1542    assert.equal(result.executions[0].tool, "files/write");
 1543    assert.equal(result.executions[0].ok, true);
 1544    assert.equal(readFileSync(outputPath, "utf8"), "still-runs");
 1545  } finally {
 1546    controlPlane.close();
 1547    rmSync(hostOpsDir, {
 1548      force: true,
 1549      recursive: true
 1550    });
 1551  }
 1552});
 1553
 1554test("BaaInstructionCenter returns denied_only when every pending instruction is denied", async () => {
 1555  const { controlPlane, repository, sharedToken, snapshot } = await createLocalApiFixture();
 1556  const center = new BaaInstructionCenter({
 1557    localApiContext: {
 1558      fetchImpl: globalThis.fetch,
 1559      repository,
 1560      sharedToken,
 1561      snapshotLoader: () => snapshot
 1562    }
 1563  });
 1564  const message = [
 1565    "```baa",
 1566    "@browser.chatgpt::reload",
 1567    "```",
 1568    "",
 1569    "```baa",
 1570    "@browser.gemini::reload",
 1571    "```"
 1572  ].join("\n");
 1573
 1574  try {
 1575    const result = await center.processAssistantMessage({
 1576      assistantMessageId: "msg-denied-only-1",
 1577      conversationId: "conv-denied-only-1",
 1578      platform: "claude",
 1579      text: message
 1580    });
 1581
 1582    assert.equal(result.status, "denied_only");
 1583    assert.equal(result.executions.length, 0);
 1584    assert.equal(result.denied.length, 2);
 1585    assert.deepEqual(
 1586      result.denied.map((entry) => ({
 1587        code: entry.code,
 1588        target: entry.instruction.target,
 1589        tool: entry.instruction.tool
 1590      })),
 1591      [
 1592        {
 1593          code: "unsupported_tool",
 1594          target: "browser.chatgpt",
 1595          tool: "reload"
 1596        },
 1597        {
 1598          code: "unsupported_tool",
 1599          target: "browser.gemini",
 1600          tool: "reload"
 1601        }
 1602      ]
 1603    );
 1604  } finally {
 1605    controlPlane.close();
 1606  }
 1607});
 1608
 1609test("BaaLiveInstructionIngest ignores plain messages, dedupes replayed browser final messages, tolerates missing conversation ids, and surfaces partial denies", async () => {
 1610  const { controlPlane, repository, sharedToken, snapshot } = await createLocalApiFixture();
 1611  const hostOpsDir = mkdtempSync(join(tmpdir(), "baa-live-instruction-ingest-"));
 1612  const dedupeFilePath = join(hostOpsDir, "dedupe-count.txt");
 1613  const ingest = new BaaLiveInstructionIngest({
 1614    localApiContext: {
 1615      fetchImpl: globalThis.fetch,
 1616      repository,
 1617      sharedToken,
 1618      snapshotLoader: () => snapshot
 1619    },
 1620    now: () => 1_710_000_100_000
 1621  });
 1622  const executableMessage = [
 1623    "```baa",
 1624    `@conductor::exec::{"command":"printf 'live-hit\\n' >> dedupe-count.txt","cwd":${JSON.stringify(hostOpsDir)}}`,
 1625    "```"
 1626  ].join("\n");
 1627
 1628  try {
 1629    const ignored = await ingest.ingestAssistantFinalMessage({
 1630      assistantMessageId: "msg-live-plain",
 1631      conversationId: null,
 1632      observedAt: 1_710_000_001_000,
 1633      platform: "chatgpt",
 1634      source: "browser.final_message",
 1635      text: [
 1636        "plain markdown must stay inert",
 1637        "```js",
 1638        "console.log('no baa');",
 1639        "```"
 1640      ].join("\n")
 1641    });
 1642
 1643    assert.equal(ignored.summary.status, "ignored_no_instructions");
 1644    assert.equal(ignored.summary.execution_count, 0);
 1645    assert.equal(ignored.summary.conversation_id, null);
 1646    assert.equal(ignored.summary.parse_error_count, 0);
 1647
 1648    const firstPass = await ingest.ingestAssistantFinalMessage({
 1649      assistantMessageId: "msg-live-exec",
 1650      conversationId: null,
 1651      observedAt: 1_710_000_002_000,
 1652      platform: "chatgpt",
 1653      source: "browser.final_message",
 1654      text: executableMessage
 1655    });
 1656
 1657    assert.equal(firstPass.summary.status, "executed");
 1658    assert.equal(firstPass.summary.execution_count, 1);
 1659    assert.equal(firstPass.summary.execution_ok_count, 1);
 1660    assert.equal(firstPass.summary.instruction_tools[0], "conductor::exec");
 1661    assert.equal(firstPass.summary.conversation_id, null);
 1662    assert.equal(firstPass.summary.parse_error_count, 0);
 1663    assert.equal(readFileSync(dedupeFilePath, "utf8"), "live-hit\n");
 1664
 1665    const replayPass = await ingest.ingestAssistantFinalMessage({
 1666      assistantMessageId: "msg-live-exec",
 1667      conversationId: "conv-should-not-matter",
 1668      observedAt: 1_710_000_003_000,
 1669      platform: "chatgpt",
 1670      source: "browser.final_message",
 1671      text: executableMessage
 1672    });
 1673
 1674    assert.equal(replayPass.summary.status, "duplicate_message");
 1675    assert.equal(replayPass.summary.execution_count, 0);
 1676    assert.equal(replayPass.summary.parse_error_count, 0);
 1677    assert.equal(readFileSync(dedupeFilePath, "utf8"), "live-hit\n");
 1678
 1679    const partialDeny = await ingest.ingestAssistantFinalMessage({
 1680      assistantMessageId: "msg-live-partial-deny",
 1681      conversationId: null,
 1682      observedAt: 1_710_000_004_000,
 1683      platform: "chatgpt",
 1684      source: "browser.final_message",
 1685      text: [
 1686        "```baa",
 1687        `@conductor::exec::{"command":"printf 'partial-live\\n' >> dedupe-count.txt","cwd":${JSON.stringify(hostOpsDir)}}`,
 1688        "```",
 1689        "",
 1690        "```baa",
 1691        "@conductor::files/write::should route fail",
 1692        "```"
 1693      ].join("\n")
 1694    });
 1695
 1696    assert.equal(partialDeny.summary.status, "executed");
 1697    assert.equal(partialDeny.summary.execution_count, 1);
 1698    assert.equal(partialDeny.summary.execution_ok_count, 1);
 1699    assert.equal(partialDeny.summary.error_stage, null);
 1700    assert.equal(partialDeny.summary.parse_error_count, 0);
 1701    assert.ok(partialDeny.processResult);
 1702    assert.equal(partialDeny.processResult.executions.length, 1);
 1703    assert.equal(partialDeny.processResult.denied.length, 1);
 1704    assert.equal(partialDeny.processResult.parseErrors.length, 0);
 1705    assert.equal(partialDeny.processResult.denied[0].blockIndex, 1);
 1706    assert.equal(partialDeny.processResult.denied[0].stage, "route");
 1707    assert.equal(partialDeny.processResult.denied[0].code, null);
 1708    assert.equal(partialDeny.processResult.denied[0].instruction.tool, "files/write");
 1709    assert.match(partialDeny.processResult.denied[0].reason, /requires JSON object params/i);
 1710    assert.equal(readFileSync(dedupeFilePath, "utf8"), "live-hit\npartial-live\n");
 1711
 1712    const deniedOnly = await ingest.ingestAssistantFinalMessage({
 1713      assistantMessageId: "msg-live-denied-only",
 1714      conversationId: null,
 1715      observedAt: 1_710_000_005_000,
 1716      platform: "chatgpt",
 1717      source: "browser.final_message",
 1718      text: [
 1719        "```baa",
 1720        "@browser.chatgpt::reload",
 1721        "```"
 1722      ].join("\n")
 1723    });
 1724
 1725    assert.equal(deniedOnly.summary.status, "denied_only");
 1726    assert.equal(deniedOnly.summary.execution_count, 0);
 1727    assert.equal(deniedOnly.summary.execution_ok_count, 0);
 1728    assert.equal(deniedOnly.summary.error_stage, null);
 1729    assert.equal(deniedOnly.summary.parse_error_count, 0);
 1730    assert.ok(deniedOnly.processResult);
 1731    assert.equal(deniedOnly.processResult.executions.length, 0);
 1732    assert.equal(deniedOnly.processResult.denied.length, 1);
 1733    assert.equal(deniedOnly.processResult.parseErrors.length, 0);
 1734    assert.equal(deniedOnly.processResult.denied[0].stage, "policy");
 1735    assert.equal(deniedOnly.processResult.denied[0].code, "unsupported_tool");
 1736
 1737    assert.equal(ingest.getSnapshot().last_ingest?.assistant_message_id, "msg-live-denied-only");
 1738    assert.equal(ingest.getSnapshot().last_execute?.status, "denied_only");
 1739  } finally {
 1740    controlPlane.close();
 1741    rmSync(hostOpsDir, {
 1742      force: true,
 1743      recursive: true
 1744    });
 1745  }
 1746});
 1747
 1748test("BaaLiveInstructionIngest records parse errors while later valid blocks still execute", async () => {
 1749  const { controlPlane, repository, sharedToken, snapshot } = await createLocalApiFixture();
 1750  const hostOpsDir = mkdtempSync(join(tmpdir(), "baa-live-instruction-parse-isolation-"));
 1751  const outputPath = join(hostOpsDir, "parse-isolation.txt");
 1752  const ingest = new BaaLiveInstructionIngest({
 1753    localApiContext: {
 1754      fetchImpl: globalThis.fetch,
 1755      repository,
 1756      sharedToken,
 1757      snapshotLoader: () => snapshot
 1758    },
 1759    now: () => 1_710_000_110_000
 1760  });
 1761
 1762  try {
 1763    const result = await ingest.ingestAssistantFinalMessage({
 1764      assistantMessageId: "msg-live-parse-isolation",
 1765      conversationId: "conv-live-parse-isolation",
 1766      observedAt: 1_710_000_111_000,
 1767      platform: "chatgpt",
 1768      source: "browser.final_message",
 1769      text: [
 1770        "```baa",
 1771        '@conductor::files/write::{"path":"broken.txt"',
 1772        "```",
 1773        "",
 1774        "```baa",
 1775        `@conductor::exec::{"command":"printf 'parse-live\\n' >> parse-isolation.txt","cwd":${JSON.stringify(hostOpsDir)}}`,
 1776        "```"
 1777      ].join("\n")
 1778    });
 1779
 1780    assert.equal(result.summary.status, "executed");
 1781    assert.equal(result.summary.error_stage, null);
 1782    assert.equal(result.summary.parse_error_count, 1);
 1783    assert.equal(result.summary.parse_errors.length, 1);
 1784    assert.equal(result.summary.parse_errors[0].block_index, 0);
 1785    assert.equal(result.summary.parse_errors[0].stage, "parse");
 1786    assert.match(result.summary.parse_errors[0].message, /Failed to parse inline JSON params/i);
 1787    assert.equal(result.summary.instruction_count, 1);
 1788    assert.equal(result.summary.execution_count, 1);
 1789    assert.equal(result.summary.execution_ok_count, 1);
 1790    assert.ok(result.processResult);
 1791    assert.equal(result.processResult.instructions.length, 1);
 1792    assert.equal(result.processResult.executions.length, 1);
 1793    assert.equal(result.processResult.parseErrors.length, 1);
 1794    assert.equal(result.processResult.parseErrors[0].blockIndex, 0);
 1795    assert.equal(result.processResult.parseErrors[0].stage, "parse");
 1796    assert.equal(readFileSync(outputPath, "utf8"), "parse-live\n");
 1797    assert.equal(ingest.getSnapshot().last_ingest?.parse_error_count, 1);
 1798    assert.equal(ingest.getSnapshot().last_execute?.parse_error_count, 1);
 1799  } finally {
 1800    controlPlane.close();
 1801    rmSync(hostOpsDir, {
 1802      force: true,
 1803      recursive: true
 1804    });
 1805  }
 1806});
 1807
 1808function createManualTimerScheduler() {
 1809  let now = 0;
 1810  let nextId = 1;
 1811  const timers = new Map();
 1812
 1813  const pickNextTimerId = (deadline) => {
 1814    let selectedId = null;
 1815    let selectedDueAt = Number.POSITIVE_INFINITY;
 1816
 1817    for (const [timerId, timer] of timers.entries()) {
 1818      if (timer.dueAt > deadline) {
 1819        continue;
 1820      }
 1821
 1822      if (
 1823        selectedId === null
 1824        || timer.dueAt < selectedDueAt
 1825        || (timer.dueAt === selectedDueAt && timerId < selectedId)
 1826      ) {
 1827        selectedId = timerId;
 1828        selectedDueAt = timer.dueAt;
 1829      }
 1830    }
 1831
 1832    return selectedId;
 1833  };
 1834
 1835  return {
 1836    advanceBy(durationMs) {
 1837      const deadline = now + Math.max(0, durationMs);
 1838
 1839      while (true) {
 1840        const timerId = pickNextTimerId(deadline);
 1841        if (timerId == null) {
 1842          break;
 1843        }
 1844
 1845        const timer = timers.get(timerId);
 1846        timers.delete(timerId);
 1847        now = timer.dueAt;
 1848        timer.handler();
 1849      }
 1850
 1851      now = deadline;
 1852    },
 1853    clearTimeout(timerId) {
 1854      timers.delete(timerId);
 1855    },
 1856    now: () => now,
 1857    setTimeout(handler, timeoutMs) {
 1858      const timerId = nextId++;
 1859      timers.set(timerId, {
 1860        dueAt: now + Math.max(0, timeoutMs),
 1861        handler
 1862      });
 1863      return timerId;
 1864    }
 1865  };
 1866}
 1867
 1868function createManualIntervalScheduler() {
 1869  let nextId = 1;
 1870  const intervals = new Map();
 1871
 1872  return {
 1873    clearInterval(intervalId) {
 1874      intervals.delete(intervalId);
 1875    },
 1876    fireAll() {
 1877      for (const interval of [...intervals.values()]) {
 1878        interval.handler();
 1879      }
 1880    },
 1881    getActiveCount() {
 1882      return intervals.size;
 1883    },
 1884    setInterval(handler, intervalMs) {
 1885      const intervalId = nextId++;
 1886      intervals.set(intervalId, {
 1887        handler,
 1888        intervalMs
 1889      });
 1890      return intervalId;
 1891    }
 1892  };
 1893}
 1894
 1895function readJsonlEntries(dirPath) {
 1896  const fileNames = readdirSync(dirPath).filter((name) => name.endsWith(".jsonl")).sort();
 1897  assert.ok(fileNames.length > 0, `expected at least one jsonl file in ${dirPath}`);
 1898  return readFileSync(join(dirPath, fileNames[0]), "utf8")
 1899    .trim()
 1900    .split("\n")
 1901    .filter(Boolean)
 1902    .map((line) => JSON.parse(line));
 1903}
 1904
 1905async function waitForJsonlEntries(dirPath, predicate = null, timeoutMs = 1_000) {
 1906  const deadline = Date.now() + timeoutMs;
 1907  let lastError = null;
 1908
 1909  while (Date.now() < deadline) {
 1910    try {
 1911      const entries = readJsonlEntries(dirPath);
 1912
 1913      if (predicate == null || predicate(entries)) {
 1914        return entries;
 1915      }
 1916    } catch (error) {
 1917      lastError = error;
 1918    }
 1919
 1920    await new Promise((resolve) => setTimeout(resolve, 10));
 1921  }
 1922
 1923  if (lastError != null) {
 1924    throw lastError;
 1925  }
 1926
 1927  return readJsonlEntries(dirPath);
 1928}
 1929
 1930async function flushAsyncWork() {
 1931  await Promise.resolve();
 1932  await Promise.resolve();
 1933}
 1934
 1935function getPolicyPlatformSnapshot(policy, platform) {
 1936  return policy.getSnapshot().platforms.find((entry) => entry.platform === platform) ?? null;
 1937}
 1938
 1939function getPolicyTargetSnapshot(policy, clientId, platform) {
 1940  return policy.getSnapshot().targets.find(
 1941    (entry) => entry.clientId === clientId && entry.platform === platform
 1942  ) ?? null;
 1943}
 1944
 1945async function readResponseBodyText(response) {
 1946  let text = response.body;
 1947
 1948  if (response.streamBody != null) {
 1949    for await (const chunk of response.streamBody) {
 1950      text += chunk;
 1951    }
 1952  }
 1953
 1954  return text;
 1955}
 1956
 1957function parseSseFrames(text) {
 1958  return String(text || "")
 1959    .split(/\n\n+/u)
 1960    .map((chunk) => chunk.trim())
 1961    .filter(Boolean)
 1962    .map((chunk) => {
 1963      const lines = chunk.split("\n");
 1964      const eventLine = lines.find((line) => line.startsWith("event:"));
 1965      const dataLines = lines
 1966        .filter((line) => line.startsWith("data:"))
 1967        .map((line) => line.slice(5).trimStart());
 1968
 1969      return {
 1970        data: JSON.parse(dataLines.join("\n")),
 1971        event: eventLine ? eventLine.slice(6).trim() : null
 1972      };
 1973    });
 1974}
 1975
 1976function getShellRuntimeDefaults(platform) {
 1977  switch (platform) {
 1978    case "chatgpt":
 1979      return {
 1980        actualUrl: "https://chatgpt.com/c/http",
 1981        shellUrl: "https://chatgpt.com/",
 1982        title: "ChatGPT HTTP"
 1983      };
 1984    case "gemini":
 1985      return {
 1986        actualUrl: "https://gemini.google.com/app",
 1987        shellUrl: "https://gemini.google.com/",
 1988        title: "Gemini HTTP"
 1989      };
 1990    case "claude":
 1991    default:
 1992      return {
 1993        actualUrl: "https://claude.ai/chats/http",
 1994        shellUrl: "https://claude.ai/",
 1995        title: "Claude HTTP"
 1996      };
 1997  }
 1998}
 1999
 2000function buildShellRuntime(platform, overrides = {}) {
 2001  const defaults = getShellRuntimeDefaults(platform);
 2002
 2003  return {
 2004    platform,
 2005    desired: {
 2006      exists: true,
 2007      shell_url: defaults.shellUrl,
 2008      source: "integration",
 2009      reason: "test",
 2010      updated_at: 1710000002000,
 2011      last_action: "tab_open",
 2012      last_action_at: 1710000002100
 2013    },
 2014    actual: {
 2015      exists: true,
 2016      tab_id: 321,
 2017      url: defaults.actualUrl,
 2018      title: defaults.title,
 2019      window_id: 91,
 2020      active: true,
 2021      status: "complete",
 2022      discarded: false,
 2023      hidden: false,
 2024      healthy: true,
 2025      issue: null,
 2026      last_seen_at: 1710000002200,
 2027      last_ready_at: 1710000002300,
 2028      candidate_tab_id: null,
 2029      candidate_url: null
 2030    },
 2031    drift: {
 2032      aligned: true,
 2033      needs_restore: false,
 2034      unexpected_actual: false,
 2035      reason: "aligned"
 2036    },
 2037    ...overrides
 2038  };
 2039}
 2040
 2041function sendPluginActionResult(socket, input) {
 2042  const shellRuntime = input.shell_runtime ?? (input.platform ? [buildShellRuntime(input.platform)] : []);
 2043  const results =
 2044    input.results
 2045    ?? shellRuntime.map((runtime) => ({
 2046      delivery_ack: input.deliveryAck ?? null,
 2047      ok: true,
 2048      platform: runtime.platform,
 2049      restored: input.restored ?? false,
 2050      shell_runtime: runtime,
 2051      skipped: input.skipped ?? null,
 2052      tab_id: runtime.actual.tab_id
 2053    }));
 2054
 2055  socket.send(
 2056    JSON.stringify({
 2057      type: "action_result",
 2058      requestId: input.requestId,
 2059      action: input.action,
 2060      command_type: input.commandType ?? input.type ?? input.action,
 2061      accepted: input.accepted ?? true,
 2062      completed: input.completed ?? true,
 2063      failed: input.failed ?? false,
 2064      reason: input.reason ?? null,
 2065      target: {
 2066        platform: input.platform ?? null,
 2067        requested_platform: input.platform ?? null
 2068      },
 2069      result: {
 2070        actual_count: shellRuntime.filter((runtime) => runtime.actual.exists).length,
 2071        desired_count: shellRuntime.filter((runtime) => runtime.desired.exists).length,
 2072        drift_count: shellRuntime.filter((runtime) => runtime.drift.aligned === false).length,
 2073        failed_count: results.filter((entry) => entry.ok === false).length,
 2074        ok_count: results.filter((entry) => entry.ok).length,
 2075        platform_count: shellRuntime.length,
 2076        restored_count: results.filter((entry) => entry.restored === true).length,
 2077        skipped_reasons: results.map((entry) => entry.skipped).filter(Boolean)
 2078      },
 2079      results,
 2080      shell_runtime: shellRuntime
 2081    })
 2082  );
 2083}
 2084
 2085function buildDeliveryAck(statusCode, overrides = {}) {
 2086  const resolvedStatusCode = Number.isFinite(Number(statusCode)) ? Number(statusCode) : null;
 2087  return {
 2088    confirmed_at: overrides.confirmed_at ?? overrides.confirmedAt ?? 1710000000250,
 2089    failed: overrides.failed ?? resolvedStatusCode !== 200,
 2090    level: overrides.level ?? (resolvedStatusCode == null ? 0 : 1),
 2091    reason:
 2092      overrides.reason
 2093      ?? (resolvedStatusCode != null && resolvedStatusCode !== 200 ? `downstream_status_${resolvedStatusCode}` : null),
 2094    status_code: resolvedStatusCode
 2095  };
 2096}
 2097
 2098function buildProxyDeliveryActionResult(options = {}) {
 2099  const platform = options.platform ?? "claude";
 2100  const shellRuntime = options.shell_runtime ?? [buildShellRuntime(platform)];
 2101  const deliveryAck = options.deliveryAck ?? null;
 2102  const results = options.results ?? shellRuntime.map((entry) => ({
 2103    delivery_ack: deliveryAck,
 2104    ok: true,
 2105    platform: entry.platform,
 2106    restored: false,
 2107    shell_runtime: entry,
 2108    skipped: null,
 2109    tab_id: entry.actual.tab_id
 2110  }));
 2111
 2112  return {
 2113    accepted: options.accepted ?? true,
 2114    action: "proxy_delivery",
 2115    completed: options.completed ?? true,
 2116    failed: options.failed ?? false,
 2117    reason: options.reason ?? null,
 2118    received_at: options.receivedAt ?? 1710000000300,
 2119    request_id: options.requestId ?? "proxy-delivery-result",
 2120    result: {
 2121      actual_count: shellRuntime.filter((entry) => entry.actual.exists).length,
 2122      desired_count: shellRuntime.filter((entry) => entry.desired.exists).length,
 2123      drift_count: shellRuntime.filter((entry) => entry.drift.aligned === false).length,
 2124      failed_count: results.filter((entry) => entry.ok === false).length,
 2125      ok_count: results.filter((entry) => entry.ok).length,
 2126      platform_count: shellRuntime.length,
 2127      restored_count: results.filter((entry) => entry.restored === true).length,
 2128      skipped_reasons: results.map((entry) => entry.skipped).filter(Boolean)
 2129    },
 2130    results,
 2131    shell_runtime: shellRuntime,
 2132    target: {
 2133      client_id: options.clientId ?? `firefox-${platform}`,
 2134      connection_id: options.connectionId ?? `conn-firefox-${platform}`,
 2135      platform,
 2136      requested_client_id: options.clientId ?? `firefox-${platform}`,
 2137      requested_platform: platform
 2138    },
 2139    type: "browser.proxy_delivery"
 2140  };
 2141}
 2142
 2143function createBrowserBridgeStub() {
 2144  const calls = [];
 2145  const buildActionDispatch = ({
 2146    action,
 2147    clientId,
 2148    connectionId = "conn-firefox-claude",
 2149    dispatchedAt,
 2150    platform = null,
 2151    reason = null,
 2152    type
 2153  }) => {
 2154    const shellRuntime = platform ? [buildShellRuntime(platform)] : [buildShellRuntime("claude")];
 2155    const requestId = `${type}-stub-${calls.length + 1}`;
 2156
 2157    return {
 2158      clientId,
 2159      connectionId,
 2160      dispatchedAt,
 2161      requestId,
 2162      result: Promise.resolve({
 2163        accepted: true,
 2164        action,
 2165        completed: true,
 2166        failed: false,
 2167        reason,
 2168        received_at: dispatchedAt + 25,
 2169        request_id: requestId,
 2170        result: {
 2171          actual_count: shellRuntime.filter((entry) => entry.actual.exists).length,
 2172          desired_count: shellRuntime.filter((entry) => entry.desired.exists).length,
 2173          drift_count: shellRuntime.filter((entry) => entry.drift.aligned === false).length,
 2174          failed_count: 0,
 2175          ok_count: shellRuntime.length,
 2176          platform_count: shellRuntime.length,
 2177          restored_count: 0,
 2178          skipped_reasons: []
 2179        },
 2180        results: shellRuntime.map((entry) => ({
 2181          ok: true,
 2182          platform: entry.platform,
 2183          restored: false,
 2184          shell_runtime: entry,
 2185          skipped: null,
 2186          tab_id: entry.actual.tab_id
 2187        })),
 2188        shell_runtime: shellRuntime,
 2189        target: {
 2190          client_id: clientId,
 2191          connection_id: connectionId,
 2192          platform,
 2193          requested_client_id: clientId,
 2194          requested_platform: platform
 2195        },
 2196        type
 2197      }),
 2198      type
 2199    };
 2200  };
 2201  const browserState = {
 2202    active_client_id: "firefox-claude",
 2203    active_connection_id: "conn-firefox-claude",
 2204    client_count: 1,
 2205    clients: [
 2206      {
 2207        client_id: "firefox-claude",
 2208        connected_at: 1710000000000,
 2209        connection_id: "conn-firefox-claude",
 2210        credentials: [
 2211          {
 2212            account: "ops@example.com",
 2213            account_captured_at: 1710000000500,
 2214            account_last_seen_at: 1710000000900,
 2215            platform: "claude",
 2216            captured_at: 1710000001000,
 2217            credential_fingerprint: "fp-claude-stub",
 2218            freshness: "fresh",
 2219            header_count: 3,
 2220            last_seen_at: 1710000001100
 2221          }
 2222        ],
 2223        final_messages: [],
 2224        last_action_result: {
 2225          accepted: true,
 2226          action: "plugin_status",
 2227          completed: true,
 2228          failed: false,
 2229          reason: null,
 2230          received_at: 1710000003600,
 2231          request_id: "plugin_status-stub-1",
 2232          result: {
 2233            actual_count: 1,
 2234            desired_count: 1,
 2235            drift_count: 0,
 2236            failed_count: 0,
 2237            ok_count: 1,
 2238            platform_count: 1,
 2239            restored_count: 0,
 2240            skipped_reasons: []
 2241          },
 2242          results: [
 2243            {
 2244              ok: true,
 2245              platform: "claude",
 2246              restored: false,
 2247              shell_runtime: buildShellRuntime("claude"),
 2248              skipped: null,
 2249              tab_id: 88
 2250            }
 2251          ],
 2252          shell_runtime: [buildShellRuntime("claude")],
 2253          target: {
 2254            client_id: "firefox-claude",
 2255            connection_id: "conn-firefox-claude",
 2256            platform: "claude",
 2257            requested_client_id: "firefox-claude",
 2258            requested_platform: "claude"
 2259          },
 2260          type: "plugin_status"
 2261        },
 2262        last_message_at: 1710000002000,
 2263        node_category: "proxy",
 2264        node_platform: "firefox",
 2265        node_type: "browser",
 2266        request_hooks: [
 2267          {
 2268            account: "ops@example.com",
 2269            credential_fingerprint: "fp-claude-stub",
 2270            platform: "claude",
 2271            endpoint_count: 3,
 2272            endpoint_metadata: [
 2273              {
 2274                method: "GET",
 2275                path: "/api/organizations",
 2276                first_seen_at: 1710000001500,
 2277                last_seen_at: 1710000002500
 2278              }
 2279            ],
 2280            endpoints: [
 2281              "GET /api/organizations",
 2282              "GET /api/organizations/{id}/chat_conversations/{id}",
 2283              "POST /api/organizations/{id}/chat_conversations/{id}/completion"
 2284            ],
 2285            last_verified_at: 1710000003500,
 2286            updated_at: 1710000003000
 2287          }
 2288        ],
 2289        shell_runtime: [buildShellRuntime("claude")]
 2290      }
 2291    ],
 2292    ws_path: "/ws/browser",
 2293    ws_url: "ws://127.0.0.1:4317/ws/browser"
 2294  };
 2295
 2296  const buildApiResponse = ({ body, clientId = "firefox-claude", error = null, id = null, status = 200 }) => ({
 2297    body,
 2298    clientId,
 2299    connectionId: "conn-firefox-claude",
 2300    error,
 2301    id: id ?? `browser-${calls.length + 1}`,
 2302    ok: error == null && status < 400,
 2303    respondedAt: 1710000004000 + calls.length,
 2304    status
 2305  });
 2306
 2307  const buildApiStream = ({ events, input }) => ({
 2308    clientId: input.clientId || "firefox-claude",
 2309    connectionId: "conn-firefox-claude",
 2310    requestId: input.id || `browser-stream-${calls.length + 1}`,
 2311    streamId: input.streamId || input.id || `browser-stream-${calls.length + 1}`,
 2312    cancel(reason = null) {
 2313      calls.push({
 2314        id: input.id,
 2315        kind: "streamCancel",
 2316        reason
 2317      });
 2318    },
 2319    async *[Symbol.asyncIterator]() {
 2320      for (const event of events) {
 2321        yield event;
 2322      }
 2323    }
 2324  });
 2325
 2326  return {
 2327    calls,
 2328    context: {
 2329      browserBridge: {
 2330        async apiRequest(input) {
 2331          calls.push({
 2332            ...input,
 2333            kind: "apiRequest"
 2334          });
 2335
 2336          if (input.path === "/api/organizations") {
 2337            return buildApiResponse({
 2338              id: input.id,
 2339              body: {
 2340                organizations: [
 2341                  {
 2342                    uuid: "org-1",
 2343                    name: "Claude Org",
 2344                    is_default: true
 2345                  }
 2346                ]
 2347              }
 2348            });
 2349          }
 2350
 2351          if (input.path === "/api/organizations/org-1/chat_conversations") {
 2352            return buildApiResponse({
 2353              id: input.id,
 2354              body: {
 2355                chat_conversations: [
 2356                  {
 2357                    uuid: "conv-1",
 2358                    name: "Current Claude Chat",
 2359                    updated_at: "2026-03-24T12:00:00.000Z",
 2360                    selected: true
 2361                  }
 2362                ]
 2363              }
 2364            });
 2365          }
 2366
 2367          if (input.path === "/api/organizations/org-1/chat_conversations/conv-1") {
 2368            return buildApiResponse({
 2369              id: input.id,
 2370              body: {
 2371                conversation: {
 2372                  uuid: "conv-1",
 2373                  name: "Current Claude Chat",
 2374                  updated_at: "2026-03-24T12:00:00.000Z"
 2375                },
 2376                messages: [
 2377                  {
 2378                    uuid: "msg-user-1",
 2379                    sender: "human",
 2380                    text: "hello claude"
 2381                  },
 2382                  {
 2383                    uuid: "msg-assistant-1",
 2384                    sender: "assistant",
 2385                    content: [
 2386                      {
 2387                        text: "hello from claude"
 2388                      }
 2389                    ]
 2390                  }
 2391                ]
 2392              }
 2393            });
 2394          }
 2395
 2396          if (input.path === "/api/organizations/org-1/chat_conversations/conv-1/completion") {
 2397            return buildApiResponse({
 2398              id: input.id,
 2399              body: {
 2400                accepted: true,
 2401                conversation_uuid: "conv-1"
 2402              },
 2403              status: 202
 2404            });
 2405          }
 2406
 2407          if (input.path === "/api/stream-buffered-smoke") {
 2408            return buildApiResponse({
 2409              id: input.id,
 2410              body: [
 2411                "event: completion",
 2412                'data: {"type":"completion","completion":"Hello "}',
 2413                "",
 2414                "event: completion",
 2415                'data: {"type":"completion","completion":"world"}',
 2416                "",
 2417                "event: completion",
 2418                'data: {"type":"completion","completion":"","id":"chatcompl_01-buffered","stop":"\\n\\nHuman:","log_id":"log_01-buffered","messageLimit":{"type":"within_limit","resetsAt":"2026-03-28T00:00:00.000Z"}}',
 2419                ""
 2420              ].join("\n")
 2421            });
 2422          }
 2423
 2424          if (input.path === "/backend-api/conversation-buffered-smoke") {
 2425            return buildApiResponse({
 2426              id: input.id,
 2427              body: [
 2428                "event: message",
 2429                'data: {"message":{"id":"msg-chatgpt-buffered-1","author":{"role":"assistant"},"content":{"content_type":"text","parts":["Buffered ChatGPT answer"]}}}',
 2430                ""
 2431              ].join("\n")
 2432            });
 2433          }
 2434
 2435          if (input.path === "/backend-api/conversation") {
 2436            return buildApiResponse({
 2437              id: input.id,
 2438              body: {
 2439                conversation_id: "conv-chatgpt-send",
 2440                message: {
 2441                  id: "msg-chatgpt-send"
 2442                },
 2443                accepted: true
 2444              },
 2445              status: 202
 2446            });
 2447          }
 2448
 2449          if (input.path === "/backend-api/conversation/conv-chatgpt-current") {
 2450            return buildApiResponse({
 2451              id: input.id,
 2452              body: {
 2453                conversation_id: "conv-chatgpt-current",
 2454                title: "Current ChatGPT Chat"
 2455              }
 2456            });
 2457          }
 2458
 2459          if (input.path === "/backend-api/models") {
 2460            return buildApiResponse({
 2461              id: input.id,
 2462              body: {
 2463                models: [
 2464                  {
 2465                    slug: "gpt-5.4"
 2466                  }
 2467                ]
 2468              }
 2469            });
 2470          }
 2471
 2472          if (
 2473            input.path
 2474            === "/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"
 2475          ) {
 2476            return buildApiResponse({
 2477              id: input.id,
 2478              body: {
 2479                conversation_id: "conv-gemini-current",
 2480                accepted: true
 2481              },
 2482              status: 202
 2483            });
 2484          }
 2485
 2486          throw new Error(`unexpected browser proxy path: ${input.path}`);
 2487        },
 2488        cancelApiRequest(input = {}) {
 2489          calls.push({
 2490            ...input,
 2491            kind: "cancelApiRequest"
 2492          });
 2493
 2494          return {
 2495            clientId: input.clientId || "firefox-claude",
 2496            connectionId: "conn-firefox-claude",
 2497            dispatchedAt: 1710000004500,
 2498            reason: input.reason || null,
 2499            requestId: input.requestId || "browser-cancel",
 2500            streamId: input.streamId || null,
 2501            type: "request_cancel"
 2502          };
 2503        },
 2504        dispatchPluginAction(input = {}) {
 2505          calls.push({
 2506            ...input,
 2507            kind: "dispatchPluginAction"
 2508          });
 2509
 2510          return buildActionDispatch({
 2511            action: input.action || "plugin_status",
 2512            clientId: input.clientId || "firefox-claude",
 2513            dispatchedAt: 1710000004750,
 2514            platform: input.platform || null,
 2515            reason: input.reason || null,
 2516            type: input.action || "plugin_status"
 2517          });
 2518        },
 2519        openTab(input = {}) {
 2520          calls.push({
 2521            ...input,
 2522            kind: "openTab"
 2523          });
 2524
 2525          return buildActionDispatch({
 2526            action: "tab_open",
 2527            clientId: input.clientId || "firefox-claude",
 2528            dispatchedAt: 1710000005000,
 2529            platform: input.platform || null,
 2530            type: "open_tab"
 2531          });
 2532        },
 2533        reload(input = {}) {
 2534          calls.push({
 2535            ...input,
 2536            kind: "reload"
 2537          });
 2538
 2539          return buildActionDispatch({
 2540            action: input.platform ? "tab_reload" : "controller_reload",
 2541            clientId: input.clientId || "firefox-claude",
 2542            dispatchedAt: 1710000006000,
 2543            platform: input.platform || null,
 2544            reason: input.reason || null,
 2545            type: "reload"
 2546          });
 2547        },
 2548        requestCredentials(input = {}) {
 2549          calls.push({
 2550            ...input,
 2551            kind: "requestCredentials"
 2552          });
 2553
 2554          return buildActionDispatch({
 2555            action: "request_credentials",
 2556            clientId: input.clientId || "firefox-claude",
 2557            dispatchedAt: 1710000007000,
 2558            platform: input.platform || null,
 2559            reason: input.reason || null,
 2560            type: "request_credentials"
 2561          });
 2562        },
 2563        streamRequest(input) {
 2564          calls.push({
 2565            ...input,
 2566            kind: "streamRequest"
 2567          });
 2568
 2569          return buildApiStream({
 2570            input,
 2571            events: [
 2572              {
 2573                clientId: input.clientId || "firefox-claude",
 2574                connectionId: "conn-firefox-claude",
 2575                meta: {
 2576                  path: input.path
 2577                },
 2578                openedAt: 1710000008000,
 2579                requestId: input.id || "browser-stream-1",
 2580                status: 200,
 2581                streamId: input.streamId || input.id || "browser-stream-1",
 2582                type: "stream_open"
 2583              },
 2584              {
 2585                clientId: input.clientId || "firefox-claude",
 2586                connectionId: "conn-firefox-claude",
 2587                data: {
 2588                  type: "content_block_delta",
 2589                  delta: {
 2590                    type: "text_delta",
 2591                    text: "hello from claude stream"
 2592                  }
 2593                },
 2594                event: "message",
 2595                raw: 'data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"hello from claude stream"}}',
 2596                receivedAt: 1710000008001,
 2597                requestId: input.id || "browser-stream-1",
 2598                seq: 1,
 2599                streamId: input.streamId || input.id || "browser-stream-1",
 2600                type: "stream_event"
 2601              },
 2602              {
 2603                clientId: input.clientId || "firefox-claude",
 2604                connectionId: "conn-firefox-claude",
 2605                endedAt: 1710000008002,
 2606                partial: {
 2607                  buffered_bytes: 120,
 2608                  event_count: 1,
 2609                  last_seq: 1,
 2610                  opened: true
 2611                },
 2612                requestId: input.id || "browser-stream-1",
 2613                status: 200,
 2614                streamId: input.streamId || input.id || "browser-stream-1",
 2615                type: "stream_end"
 2616              }
 2617            ]
 2618          });
 2619        }
 2620      },
 2621      browserStateLoader: () => browserState
 2622    }
 2623  };
 2624}
 2625
 2626async function withMockedPlatform(platform, callback) {
 2627  const descriptor = Object.getOwnPropertyDescriptor(process, "platform");
 2628
 2629  assert.ok(descriptor);
 2630
 2631  Object.defineProperty(process, "platform", {
 2632    configurable: true,
 2633    enumerable: descriptor.enumerable ?? true,
 2634    value: platform
 2635  });
 2636
 2637  try {
 2638    return await callback();
 2639  } finally {
 2640    Object.defineProperty(process, "platform", descriptor);
 2641  }
 2642}
 2643
 2644function assertEmptyExecResultShape(result) {
 2645  assert.equal(result.stdout, "");
 2646  assert.equal(result.stderr, "");
 2647  assert.equal(result.exitCode, null);
 2648  assert.equal(result.signal, null);
 2649  assert.equal(result.durationMs, 0);
 2650  assert.equal(typeof result.startedAt, "string");
 2651  assert.equal(typeof result.finishedAt, "string");
 2652  assert.equal(result.timedOut, false);
 2653}
 2654
 2655function createWebSocketMessageQueue(socket) {
 2656  const messages = [];
 2657  const waiters = [];
 2658
 2659  const onMessage = (event) => {
 2660    let payload = null;
 2661
 2662    try {
 2663      payload = JSON.parse(event.data);
 2664    } catch {
 2665      return;
 2666    }
 2667
 2668    const waiterIndex = waiters.findIndex((waiter) => waiter.predicate(payload));
 2669
 2670    if (waiterIndex >= 0) {
 2671      const [waiter] = waiters.splice(waiterIndex, 1);
 2672
 2673      if (waiter) {
 2674        clearTimeout(waiter.timer);
 2675        waiter.resolve(payload);
 2676      }
 2677
 2678      return;
 2679    }
 2680
 2681    messages.push(payload);
 2682  };
 2683
 2684  const onClose = () => {
 2685    while (waiters.length > 0) {
 2686      const waiter = waiters.shift();
 2687
 2688      if (waiter) {
 2689        clearTimeout(waiter.timer);
 2690        waiter.reject(new Error("websocket closed before the expected message arrived"));
 2691      }
 2692    }
 2693  };
 2694
 2695  socket.addEventListener("message", onMessage);
 2696  socket.addEventListener("close", onClose);
 2697
 2698  return {
 2699    async next(predicate, timeoutMs = 5_000) {
 2700      const existingIndex = messages.findIndex((message) => predicate(message));
 2701
 2702      if (existingIndex >= 0) {
 2703        const [message] = messages.splice(existingIndex, 1);
 2704        return message;
 2705      }
 2706
 2707      return await new Promise((resolve, reject) => {
 2708        const timer = setTimeout(() => {
 2709          const waiterIndex = waiters.findIndex((waiter) => waiter.timer === timer);
 2710
 2711          if (waiterIndex >= 0) {
 2712            waiters.splice(waiterIndex, 1);
 2713          }
 2714
 2715          reject(new Error("timed out waiting for websocket message"));
 2716        }, timeoutMs);
 2717
 2718        waiters.push({
 2719          predicate,
 2720          reject,
 2721          resolve,
 2722          timer
 2723        });
 2724      });
 2725    },
 2726    stop() {
 2727      socket.removeEventListener("message", onMessage);
 2728      socket.removeEventListener("close", onClose);
 2729      onClose();
 2730    }
 2731  };
 2732}
 2733
 2734async function waitForWebSocketOpen(socket) {
 2735  if (socket.readyState === WebSocket.OPEN) {
 2736    return;
 2737  }
 2738
 2739  await new Promise((resolve, reject) => {
 2740    const onOpen = () => {
 2741      socket.removeEventListener("error", onError);
 2742      resolve();
 2743    };
 2744    const onError = () => {
 2745      socket.removeEventListener("open", onOpen);
 2746      reject(new Error("websocket failed to open"));
 2747    };
 2748
 2749    socket.addEventListener("open", onOpen, {
 2750      once: true
 2751    });
 2752    socket.addEventListener("error", onError, {
 2753      once: true
 2754    });
 2755  });
 2756}
 2757
 2758async function waitForWebSocketClose(socket) {
 2759  if (socket.readyState === WebSocket.CLOSED) {
 2760    return {
 2761      code: null,
 2762      reason: null
 2763    };
 2764  }
 2765
 2766  return await new Promise((resolve) => {
 2767    socket.addEventListener("close", (event) => {
 2768      resolve({
 2769        code: event.code ?? null,
 2770        reason: event.reason ?? null
 2771      });
 2772    }, {
 2773      once: true
 2774    });
 2775  });
 2776}
 2777
 2778async function waitForCondition(assertion, timeoutMs = 2_000, intervalMs = 50) {
 2779  const deadline = Date.now() + timeoutMs;
 2780  let lastError = null;
 2781
 2782  while (Date.now() < deadline) {
 2783    try {
 2784      return await assertion();
 2785    } catch (error) {
 2786      lastError = error;
 2787      await new Promise((resolve) => setTimeout(resolve, intervalMs));
 2788    }
 2789  }
 2790
 2791  throw lastError ?? new Error("timed out waiting for condition");
 2792}
 2793
 2794async function expectQueueTimeout(queue, predicate, timeoutMs = 400) {
 2795  await assert.rejects(
 2796    () => queue.next(predicate, timeoutMs),
 2797    /timed out waiting for websocket message/u
 2798  );
 2799}
 2800
 2801class MockWritableResponse extends EventEmitter {
 2802  constructor(onWrite) {
 2803    super();
 2804    this.destroyed = false;
 2805    this.endCalls = [];
 2806    this.headers = {};
 2807    this.onWrite = onWrite;
 2808    this.statusCode = 200;
 2809    this.writableEnded = false;
 2810    this.writeCalls = [];
 2811  }
 2812
 2813  setHeader(name, value) {
 2814    this.headers[name] = value;
 2815  }
 2816
 2817  write(chunk) {
 2818    this.writeCalls.push(chunk);
 2819    return this.onWrite(chunk, this.writeCalls.length, this);
 2820  }
 2821
 2822  end(chunk) {
 2823    if (chunk !== undefined) {
 2824      this.endCalls.push(chunk);
 2825    }
 2826
 2827    this.writableEnded = true;
 2828  }
 2829}
 2830
 2831async function assertSettlesWithin(promise, timeoutMs = 250) {
 2832  let timeoutId = null;
 2833
 2834  try {
 2835    return await Promise.race([
 2836      promise,
 2837      new Promise((_, reject) => {
 2838        timeoutId = setTimeout(() => {
 2839          reject(new Error(`expected promise to settle within ${timeoutMs}ms`));
 2840        }, timeoutMs);
 2841      })
 2842    ]);
 2843  } finally {
 2844    if (timeoutId != null) {
 2845      clearTimeout(timeoutId);
 2846    }
 2847  }
 2848}
 2849
 2850async function fetchJson(url, init) {
 2851  const response = await fetch(url, init);
 2852  const text = await response.text();
 2853
 2854  return {
 2855    payload: text === "" ? null : JSON.parse(text),
 2856    response,
 2857    text
 2858  };
 2859}
 2860
 2861async function connectBrowserBridgeClient(wsUrl, clientId, nodePlatform = "firefox") {
 2862  const socket = new WebSocket(wsUrl);
 2863  const queue = createWebSocketMessageQueue(socket);
 2864
 2865  await waitForWebSocketOpen(socket);
 2866  socket.send(
 2867    JSON.stringify({
 2868      type: "hello",
 2869      clientId,
 2870      nodeType: "browser",
 2871      nodeCategory: "proxy",
 2872      nodePlatform
 2873    })
 2874  );
 2875
 2876  const helloAck = await queue.next(
 2877    (message) => message.type === "hello_ack" && message.clientId === clientId
 2878  );
 2879  const initialSnapshot = await queue.next(
 2880    (message) => message.type === "state_snapshot" && message.reason === "hello"
 2881  );
 2882  const credentialRequest = await queue.next(
 2883    (message) => message.type === "request_credentials" && message.reason === "hello"
 2884  );
 2885
 2886  return {
 2887    credentialRequest,
 2888    helloAck,
 2889    initialSnapshot,
 2890    queue,
 2891    socket
 2892  };
 2893}
 2894
 2895async function connectFirefoxBridgeClient(wsUrl, clientId) {
 2896  return await connectBrowserBridgeClient(wsUrl, clientId, "firefox");
 2897}
 2898
 2899test("writeHttpResponse stops waiting for body backpressure when the client closes", async () => {
 2900  const response = new MockWritableResponse((_chunk, writeCount, writableResponse) => {
 2901    assert.equal(writeCount, 1);
 2902    queueMicrotask(() => {
 2903      writableResponse.destroyed = true;
 2904      writableResponse.emit("close");
 2905    });
 2906
 2907    return false;
 2908  });
 2909
 2910  await assertSettlesWithin(
 2911    writeHttpResponse(response, {
 2912      body: "preface",
 2913      headers: {
 2914        "content-type": "text/plain; charset=utf-8"
 2915      },
 2916      status: 200,
 2917      streamBody: (async function* () {
 2918        yield "tail";
 2919      })()
 2920    })
 2921  );
 2922
 2923  assert.deepEqual(response.writeCalls, ["preface"]);
 2924  assert.equal(response.writableEnded, false);
 2925  assert.equal(response.listenerCount("drain"), 0);
 2926  assert.equal(response.listenerCount("close"), 0);
 2927  assert.equal(response.listenerCount("error"), 0);
 2928});
 2929
 2930test("writeHttpResponse stops waiting for stream backpressure when the client closes", async () => {
 2931  const response = new MockWritableResponse((_chunk, writeCount, writableResponse) => {
 2932    assert.equal(writeCount, 1);
 2933    queueMicrotask(() => {
 2934      writableResponse.destroyed = true;
 2935      writableResponse.emit("close");
 2936    });
 2937
 2938    return false;
 2939  });
 2940
 2941  await assertSettlesWithin(
 2942    writeHttpResponse(response, {
 2943      body: "",
 2944      headers: {
 2945        "content-type": "text/event-stream; charset=utf-8"
 2946      },
 2947      status: 200,
 2948      streamBody: (async function* () {
 2949        yield "chunk-1";
 2950        yield "chunk-2";
 2951      })()
 2952    })
 2953  );
 2954
 2955  assert.deepEqual(response.writeCalls, ["chunk-1"]);
 2956  assert.equal(response.writableEnded, false);
 2957  assert.equal(response.listenerCount("drain"), 0);
 2958  assert.equal(response.listenerCount("close"), 0);
 2959  assert.equal(response.listenerCount("error"), 0);
 2960});
 2961
 2962test("writeHttpResponse continues streaming after drain and finishes the response", async () => {
 2963  const response = new MockWritableResponse((_chunk, writeCount, writableResponse) => {
 2964    if (writeCount === 1) {
 2965      queueMicrotask(() => {
 2966        writableResponse.emit("drain");
 2967      });
 2968
 2969      return false;
 2970    }
 2971
 2972    return true;
 2973  });
 2974
 2975  await assertSettlesWithin(
 2976    writeHttpResponse(response, {
 2977      body: "preface",
 2978      headers: {
 2979        "content-type": "text/event-stream; charset=utf-8"
 2980      },
 2981      status: 200,
 2982      streamBody: (async function* () {
 2983        yield "chunk-1";
 2984        yield "chunk-2";
 2985      })()
 2986    })
 2987  );
 2988
 2989  assert.deepEqual(response.writeCalls, ["preface", "chunk-1", "chunk-2"]);
 2990  assert.equal(response.writableEnded, true);
 2991  assert.equal(response.listenerCount("drain"), 0);
 2992  assert.equal(response.listenerCount("close"), 0);
 2993  assert.equal(response.listenerCount("error"), 0);
 2994});
 2995
 2996async function withRuntimeFixture(callback) {
 2997  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-runtime-fixture-"));
 2998  const runtime = new ConductorRuntime(
 2999    {
 3000      nodeId: "mini-main",
 3001      host: "mini",
 3002      role: "primary",
 3003      controlApiBase: "https://control.example.test",
 3004      localApiBase: "http://127.0.0.1:0",
 3005      sharedToken: "replace-me",
 3006      paths: {
 3007        runsDir: "/tmp/runs",
 3008        stateDir
 3009      }
 3010    },
 3011    {
 3012      autoStartLoops: false,
 3013      now: () => 100
 3014    }
 3015  );
 3016
 3017  try {
 3018    return await callback({
 3019      runtime,
 3020      stateDir
 3021    });
 3022  } finally {
 3023    await runtime.stop();
 3024    rmSync(stateDir, {
 3025      force: true,
 3026      recursive: true
 3027    });
 3028  }
 3029}
 3030
 3031async function assertLocalApiListenerClosed(baseUrl) {
 3032  const { hostname, port } = new URL(baseUrl);
 3033
 3034  await new Promise((resolve, reject) => {
 3035    const socket = createConnection({
 3036      host: hostname,
 3037      port: Number(port)
 3038    });
 3039
 3040    socket.once("connect", () => {
 3041      socket.destroy();
 3042      reject(new Error(`expected local API listener to be closed: ${baseUrl}`));
 3043    });
 3044    socket.once("error", (error) => {
 3045      socket.destroy();
 3046
 3047      if (error && typeof error === "object" && "code" in error && error.code === "ECONNREFUSED") {
 3048        resolve();
 3049        return;
 3050      }
 3051
 3052      reject(error);
 3053    });
 3054  });
 3055}
 3056
 3057test("start enters leader state and allows scheduler work only for the lease holder", async () => {
 3058  const heartbeatRequests = [];
 3059  const leaseRequests = [];
 3060  let currentNow = 100;
 3061  const daemon = new ConductorDaemon(
 3062    {
 3063      nodeId: "mini-main",
 3064      host: "mini",
 3065      role: "primary",
 3066      controlApiBase: "https://control.example.test"
 3067    },
 3068    {
 3069      autoStartLoops: false,
 3070      client: {
 3071        async acquireLeaderLease(request) {
 3072          leaseRequests.push(request);
 3073
 3074          return createLeaseResult({
 3075            holderId: "mini-main",
 3076            term: 4,
 3077            leaseExpiresAt: 130,
 3078            renewedAt: 100,
 3079            isLeader: true,
 3080            operation: "acquire"
 3081          });
 3082        },
 3083        async sendControllerHeartbeat(request) {
 3084          heartbeatRequests.push(request);
 3085        }
 3086      },
 3087      now: () => currentNow
 3088    }
 3089  );
 3090
 3091  const state = await daemon.start();
 3092  assert.equal(state, "leader");
 3093  assert.equal(heartbeatRequests.length, 1);
 3094  assert.equal(leaseRequests.length, 1);
 3095  assert.equal(daemon.canSchedule(), true);
 3096  assert.equal(daemon.getNextLeaseOperation(), "renew");
 3097
 3098  let executed = false;
 3099  const decision = await daemon.runSchedulerPass(async () => {
 3100    executed = true;
 3101  });
 3102
 3103  assert.equal(decision, "scheduled");
 3104  assert.equal(executed, true);
 3105
 3106  currentNow = 131;
 3107  assert.equal(daemon.canSchedule(), false);
 3108});
 3109
 3110test("standby responses keep scheduler disabled", async () => {
 3111  const daemon = new ConductorDaemon(
 3112    {
 3113      nodeId: "mac-standby",
 3114      host: "mac",
 3115      role: "standby",
 3116      controlApiBase: "https://control.example.test"
 3117    },
 3118    {
 3119      autoStartLoops: false,
 3120      client: {
 3121        async acquireLeaderLease() {
 3122          return createLeaseResult({
 3123            holderId: "mini-main",
 3124            term: 7,
 3125            leaseExpiresAt: 230,
 3126            renewedAt: 200,
 3127            isLeader: false,
 3128            operation: "acquire"
 3129          });
 3130        },
 3131        async sendControllerHeartbeat() {}
 3132      },
 3133      now: () => 200
 3134    }
 3135  );
 3136
 3137  const state = await daemon.start();
 3138  assert.equal(state, "standby");
 3139  assert.equal(daemon.canSchedule(), false);
 3140  assert.equal(daemon.getStatusSnapshot().schedulerEnabled, false);
 3141
 3142  let executed = false;
 3143  const decision = await daemon.runSchedulerPass(async () => {
 3144    executed = true;
 3145  });
 3146
 3147  assert.equal(decision, "skipped_not_leader");
 3148  assert.equal(executed, false);
 3149});
 3150
 3151test("repeated renew failures degrade the daemon after the configured threshold", async () => {
 3152  const calls = [];
 3153  let currentNow = 100;
 3154  const daemon = new ConductorDaemon(
 3155    {
 3156      nodeId: "mini-main",
 3157      host: "mini",
 3158      role: "primary",
 3159      controlApiBase: "https://control.example.test",
 3160      renewFailureThreshold: 2
 3161    },
 3162    {
 3163      autoStartLoops: false,
 3164      client: {
 3165        async acquireLeaderLease(request) {
 3166          calls.push(request);
 3167
 3168          if (calls.length === 1) {
 3169            return createLeaseResult({
 3170              holderId: "mini-main",
 3171              term: 1,
 3172              leaseExpiresAt: 130,
 3173              renewedAt: 100,
 3174              isLeader: true,
 3175              operation: "acquire"
 3176            });
 3177          }
 3178
 3179          throw new Error("lease endpoint timeout");
 3180        },
 3181        async sendControllerHeartbeat() {}
 3182      },
 3183      now: () => currentNow
 3184    }
 3185  );
 3186
 3187  await daemon.start();
 3188  assert.equal(daemon.getStatusSnapshot().leaseState, "leader");
 3189
 3190  currentNow = 110;
 3191  await assert.rejects(() => daemon.runLeaseCycle(), /lease endpoint timeout/);
 3192  assert.equal(daemon.getStatusSnapshot().leaseState, "leader");
 3193  assert.equal(daemon.getStatusSnapshot().consecutiveRenewFailures, 1);
 3194
 3195  currentNow = 115;
 3196  await assert.rejects(() => daemon.runLeaseCycle(), /lease endpoint timeout/);
 3197  assert.equal(daemon.getStatusSnapshot().leaseState, "degraded");
 3198  assert.equal(daemon.getStatusSnapshot().consecutiveRenewFailures, 2);
 3199});
 3200
 3201test("parseConductorCliRequest reads timed-jobs defaults from env and allows CLI overrides", () => {
 3202  const request = parseConductorCliRequest(
 3203    [
 3204      "start",
 3205      "--run-once",
 3206      "--timed-jobs-interval-ms",
 3207      "6000",
 3208      "--timed-jobs-max-tasks-per-tick",
 3209      "9"
 3210    ],
 3211    {
 3212      BAA_NODE_ID: "mini-main",
 3213      BAA_CONDUCTOR_HOST: "mini",
 3214      BAA_CONDUCTOR_ROLE: "primary",
 3215      BAA_CONDUCTOR_PUBLIC_API_BASE: "https://public.example.test/",
 3216      BAA_SHARED_TOKEN: "replace-me",
 3217      BAA_TIMED_JOBS_INTERVAL_MS: "10000",
 3218      BAA_TIMED_JOBS_MAX_MESSAGES_PER_TICK: "11",
 3219      BAA_TIMED_JOBS_MAX_TASKS_PER_TICK: "12",
 3220      BAA_TIMED_JOBS_SETTLE_DELAY_MS: "15000"
 3221    }
 3222  );
 3223
 3224  assert.equal(request.action, "start");
 3225
 3226  if (request.action !== "start") {
 3227    throw new Error("expected start action");
 3228  }
 3229
 3230  assert.equal(request.config.timedJobsIntervalMs, 6000);
 3231  assert.equal(request.config.timedJobsMaxMessagesPerTick, 11);
 3232  assert.equal(request.config.timedJobsMaxTasksPerTick, 9);
 3233  assert.equal(request.config.timedJobsSettleDelayMs, 15000);
 3234});
 3235
 3236test("ConductorTimedJobs runs registered runners on leader ticks and writes JSONL logs", async () => {
 3237  const logsDir = mkdtempSync(join(tmpdir(), "baa-timed-jobs-leader-"));
 3238  const daemon = new ConductorDaemon(
 3239    {
 3240      nodeId: "mini-main",
 3241      host: "mini",
 3242      role: "primary",
 3243      controlApiBase: "https://control.example.test"
 3244    },
 3245    {
 3246      autoStartLoops: false,
 3247      client: {
 3248        async acquireLeaderLease() {
 3249          return createLeaseResult({
 3250            holderId: "mini-main",
 3251            term: 4,
 3252            leaseExpiresAt: 130,
 3253            renewedAt: 100,
 3254            isLeader: true,
 3255            operation: "acquire"
 3256          });
 3257        },
 3258        async sendControllerHeartbeat() {}
 3259      },
 3260      now: () => 100
 3261    }
 3262  );
 3263  const observedRuns = [];
 3264  const timedJobs = new ConductorTimedJobs(
 3265    {
 3266      intervalMs: 5_000,
 3267      maxMessagesPerTick: 10,
 3268      maxTasksPerTick: 8,
 3269      settleDelayMs: 12_000
 3270    },
 3271    {
 3272      autoStart: false,
 3273      logDir: logsDir,
 3274      schedule: (work) => daemon.runSchedulerPass(work)
 3275    }
 3276  );
 3277
 3278  timedJobs.registerRunner({
 3279    name: "renewal.projector",
 3280    async run(context) {
 3281      observedRuns.push({
 3282        batchId: context.batchId,
 3283        maxMessagesPerTick: context.maxMessagesPerTick,
 3284        settleDelayMs: context.settleDelayMs,
 3285        term: context.term
 3286      });
 3287      context.log({
 3288        stage: "scan_window",
 3289        result: "ok",
 3290        details: {
 3291          cursor: "message:1"
 3292        }
 3293      });
 3294      return {
 3295        result: "ok",
 3296        details: {
 3297          projected_messages: 0
 3298        }
 3299      };
 3300    }
 3301  });
 3302
 3303  try {
 3304    await daemon.start();
 3305    await timedJobs.start();
 3306
 3307    const tick = await timedJobs.runTick("manual");
 3308
 3309    assert.equal(tick.decision, "scheduled");
 3310    assert.equal(observedRuns.length, 1);
 3311    assert.equal(observedRuns[0].maxMessagesPerTick, 10);
 3312    assert.equal(observedRuns[0].settleDelayMs, 12_000);
 3313    assert.equal(observedRuns[0].term, 4);
 3314
 3315    await timedJobs.stop();
 3316
 3317    const entries = await waitForJsonlEntries(
 3318      logsDir,
 3319      (items) => items.some((entry) => entry.runner === "renewal.projector" && entry.stage === "job_projected")
 3320    );
 3321    assert.ok(
 3322      entries.find(
 3323        (entry) =>
 3324          entry.runner === "renewal.projector"
 3325          && entry.stage === "runner_started"
 3326          && entry.result === "running"
 3327      )
 3328    );
 3329    assert.ok(
 3330      entries.find(
 3331        (entry) =>
 3332          entry.runner === "renewal.projector"
 3333          && entry.stage === "scan_window"
 3334          && entry.cursor === "message:1"
 3335      )
 3336    );
 3337
 3338    const completedEntry = entries.find(
 3339      (entry) => entry.runner === "renewal.projector" && entry.stage === "runner_completed"
 3340    );
 3341    assert.ok(completedEntry);
 3342    assert.equal(completedEntry.result, "ok");
 3343    assert.equal(typeof completedEntry.batch_id, "string");
 3344    assert.equal(typeof completedEntry.duration_ms, "number");
 3345    assert.equal(completedEntry.error, null);
 3346  } finally {
 3347    await timedJobs.stop();
 3348    rmSync(logsDir, {
 3349      force: true,
 3350      recursive: true
 3351    });
 3352  }
 3353});
 3354
 3355test("ConductorTimedJobs does not block ticks on pending async log writes and drains them on stop", async () => {
 3356  const logsDir = mkdtempSync(join(tmpdir(), "baa-timed-jobs-async-log-"));
 3357  const pendingWrites = [];
 3358  const timedJobs = new ConductorTimedJobs(
 3359    {
 3360      intervalMs: 5_000,
 3361      maxMessagesPerTick: 10,
 3362      maxTasksPerTick: 8,
 3363      settleDelayMs: 12_000
 3364    },
 3365    {
 3366      appendFileImpl: async (filePath, data) => {
 3367        await new Promise((resolve) => {
 3368          pendingWrites.push(() => {
 3369            writeFileSync(filePath, data, {
 3370              flag: "a"
 3371            });
 3372            resolve();
 3373          });
 3374        });
 3375      },
 3376      autoStart: false,
 3377      logDir: logsDir,
 3378      schedule: async (work) => {
 3379        await work({
 3380          controllerId: "mini-main",
 3381          host: "mini",
 3382          term: 4
 3383        });
 3384        return "scheduled";
 3385      }
 3386    }
 3387  );
 3388  let stopCompleted = false;
 3389
 3390  timedJobs.registerRunner({
 3391    name: "renewal.projector",
 3392    async run(context) {
 3393      context.log({
 3394        stage: "scan_window",
 3395        result: "ok",
 3396        details: {
 3397          cursor: "message:1"
 3398        }
 3399      });
 3400
 3401      return {
 3402        result: "ok"
 3403      };
 3404    }
 3405  });
 3406
 3407  try {
 3408    await timedJobs.start();
 3409
 3410    const tick = await Promise.race([
 3411      timedJobs.runTick("manual"),
 3412      new Promise((_, reject) => {
 3413        setTimeout(() => reject(new Error("timed out waiting for timed-jobs tick")), 1_000);
 3414      })
 3415    ]);
 3416
 3417    assert.equal(tick.decision, "scheduled");
 3418    assert.ok(pendingWrites.length > 0);
 3419
 3420    const stopPromise = timedJobs.stop();
 3421    void stopPromise.then(() => {
 3422      stopCompleted = true;
 3423    });
 3424
 3425    await flushAsyncWork();
 3426    assert.equal(stopCompleted, false);
 3427
 3428    while (!stopCompleted) {
 3429      const releaseWrite = pendingWrites.shift();
 3430
 3431      if (releaseWrite == null) {
 3432        await flushAsyncWork();
 3433        continue;
 3434      }
 3435
 3436      releaseWrite();
 3437      await flushAsyncWork();
 3438    }
 3439
 3440    await stopPromise;
 3441
 3442    const entries = await waitForJsonlEntries(
 3443      logsDir,
 3444      (items) => items.some((entry) => entry.runner === "timed-jobs.framework" && entry.stage === "stopped")
 3445    );
 3446    assert.ok(
 3447      entries.find(
 3448        (entry) => entry.runner === "timed-jobs.framework" && entry.stage === "started"
 3449      )
 3450    );
 3451    assert.ok(
 3452      entries.find(
 3453        (entry) => entry.runner === "renewal.projector" && entry.stage === "scan_window"
 3454      )
 3455    );
 3456    assert.ok(
 3457      entries.find(
 3458        (entry) => entry.runner === "timed-jobs.framework" && entry.stage === "stopped"
 3459      )
 3460    );
 3461  } finally {
 3462    while (pendingWrites.length > 0) {
 3463      pendingWrites.shift()();
 3464      await flushAsyncWork();
 3465    }
 3466
 3467    await timedJobs.stop();
 3468    rmSync(logsDir, {
 3469      force: true,
 3470      recursive: true
 3471    });
 3472  }
 3473});
 3474
 3475test("renewal projector scans settled messages with cursor semantics and skips ineligible conversations", async () => {
 3476  const rootDir = mkdtempSync(join(tmpdir(), "baa-renewal-projector-"));
 3477  const logsDir = mkdtempSync(join(tmpdir(), "baa-renewal-projector-logs-"));
 3478  const controlPlane = new ConductorLocalControlPlane({
 3479    databasePath: join(rootDir, "control-plane.db")
 3480  });
 3481  await controlPlane.initialize();
 3482  const repository = controlPlane.repository;
 3483  const artifactStore = new ArtifactStore({
 3484    artifactDir: join(rootDir, "artifacts"),
 3485    databasePath: join(rootDir, "artifact.db"),
 3486    publicBaseUrl: "https://conductor.makefile.so"
 3487  });
 3488  const nowMs = Date.UTC(2026, 2, 30, 10, 0, 0);
 3489  let timedJobs = null;
 3490
 3491  try {
 3492    await artifactStore.upsertLocalConversation({
 3493      automationStatus: "manual",
 3494      localConversationId: "lc_manual",
 3495      platform: "claude"
 3496    });
 3497    await artifactStore.upsertConversationLink({
 3498      clientId: "firefox-manual",
 3499      linkId: "link_manual",
 3500      localConversationId: "lc_manual",
 3501      observedAt: nowMs - 9_000,
 3502      pageUrl: "https://claude.ai/chat/conv_manual",
 3503      platform: "claude",
 3504      remoteConversationId: "conv_manual",
 3505      targetKind: "browser.proxy_delivery",
 3506      targetPayload: {
 3507        clientId: "firefox-manual",
 3508        tabId: 1
 3509      }
 3510    });
 3511    const manualMessage = await artifactStore.insertMessage({
 3512      conversationId: "conv_manual",
 3513      id: "msg_manual",
 3514      observedAt: nowMs - 9_000,
 3515      platform: "claude",
 3516      rawText: "manual conversation should not project",
 3517      role: "assistant"
 3518    });
 3519
 3520    await artifactStore.upsertLocalConversation({
 3521      automationStatus: "paused",
 3522      localConversationId: "lc_paused",
 3523      pausedAt: nowMs - 8_000,
 3524      platform: "claude"
 3525    });
 3526    await artifactStore.upsertConversationLink({
 3527      clientId: "firefox-paused",
 3528      linkId: "link_paused",
 3529      localConversationId: "lc_paused",
 3530      observedAt: nowMs - 8_000,
 3531      pageUrl: "https://claude.ai/chat/conv_paused",
 3532      platform: "claude",
 3533      remoteConversationId: "conv_paused",
 3534      targetKind: "browser.proxy_delivery",
 3535      targetPayload: {
 3536        clientId: "firefox-paused",
 3537        tabId: 2
 3538      }
 3539    });
 3540    const pausedMessage = await artifactStore.insertMessage({
 3541      conversationId: "conv_paused",
 3542      id: "msg_paused",
 3543      observedAt: nowMs - 8_000,
 3544      platform: "claude",
 3545      rawText: "paused conversation should not project",
 3546      role: "assistant"
 3547    });
 3548
 3549    await artifactStore.upsertLocalConversation({
 3550      automationStatus: "auto",
 3551      cooldownUntil: nowMs + 30_000,
 3552      localConversationId: "lc_cooldown",
 3553      platform: "claude"
 3554    });
 3555    await artifactStore.upsertConversationLink({
 3556      clientId: "firefox-cooldown",
 3557      linkId: "link_cooldown",
 3558      localConversationId: "lc_cooldown",
 3559      observedAt: nowMs - 7_000,
 3560      pageUrl: "https://claude.ai/chat/conv_cooldown",
 3561      platform: "claude",
 3562      remoteConversationId: "conv_cooldown",
 3563      targetKind: "browser.proxy_delivery",
 3564      targetPayload: {
 3565        clientId: "firefox-cooldown",
 3566        tabId: 3
 3567      }
 3568    });
 3569    const cooldownMessage = await artifactStore.insertMessage({
 3570      conversationId: "conv_cooldown",
 3571      id: "msg_cooldown",
 3572      observedAt: nowMs - 7_000,
 3573      platform: "claude",
 3574      rawText: "cooldown conversation should not project",
 3575      role: "assistant"
 3576    });
 3577
 3578    await artifactStore.upsertLocalConversation({
 3579      automationStatus: "auto",
 3580      localConversationId: "lc_auto",
 3581      platform: "claude"
 3582    });
 3583    await artifactStore.upsertConversationLink({
 3584      clientId: "firefox-auto",
 3585      linkId: "link_auto",
 3586      localConversationId: "lc_auto",
 3587      observedAt: nowMs - 6_000,
 3588      pageTitle: "Claude Auto",
 3589      pageUrl: "https://claude.ai/chat/conv_auto",
 3590      platform: "claude",
 3591      remoteConversationId: "conv_auto",
 3592      routeParams: {
 3593        conversationId: "conv_auto"
 3594      },
 3595      routePath: "/chat/conv_auto",
 3596      routePattern: "/chat/:conversationId",
 3597      targetId: "client:firefox-auto",
 3598      targetKind: "browser.proxy_delivery",
 3599      targetPayload: {
 3600        clientId: "firefox-auto",
 3601        tabId: 4
 3602      }
 3603    });
 3604    const autoMessage = await artifactStore.insertMessage({
 3605      conversationId: "conv_auto",
 3606      id: "msg_auto",
 3607      observedAt: nowMs - 6_000,
 3608      platform: "claude",
 3609      rawText: "eligible auto conversation should project",
 3610      role: "assistant"
 3611    });
 3612
 3613    await artifactStore.upsertLocalConversation({
 3614      automationStatus: "auto",
 3615      localConversationId: "lc_non_proxy_target",
 3616      platform: "claude"
 3617    });
 3618    await artifactStore.upsertConversationLink({
 3619      clientId: "firefox-non-proxy-target",
 3620      linkId: "link_non_proxy_target",
 3621      localConversationId: "lc_non_proxy_target",
 3622      observedAt: nowMs - 5_000,
 3623      pageUrl: "https://claude.ai/chat/conv_non_proxy_target",
 3624      platform: "claude",
 3625      remoteConversationId: "conv_non_proxy_target",
 3626      targetId: "tab:5",
 3627      targetKind: "browser.shell_page",
 3628      targetPayload: {
 3629        clientId: "firefox-non-proxy-target",
 3630        tabId: 5
 3631      }
 3632    });
 3633    const nonProxyTargetMessage = await artifactStore.insertMessage({
 3634      conversationId: "conv_non_proxy_target",
 3635      id: "msg_non_proxy_target",
 3636      observedAt: nowMs - 5_000,
 3637      platform: "claude",
 3638      rawText: "non proxy delivery target should not project",
 3639      role: "assistant"
 3640    });
 3641
 3642    await artifactStore.upsertLocalConversation({
 3643      automationStatus: "auto",
 3644      localConversationId: "lc_shell_page",
 3645      platform: "claude"
 3646    });
 3647    await artifactStore.upsertConversationLink({
 3648      clientId: "firefox-shell-page",
 3649      linkId: "link_shell_page",
 3650      localConversationId: "lc_shell_page",
 3651      observedAt: nowMs - 4_000,
 3652      pageUrl: "https://claude.ai/chat/conv_shell_page",
 3653      platform: "claude",
 3654      remoteConversationId: "conv_shell_page",
 3655      targetId: "tab:6",
 3656      targetKind: "browser.proxy_delivery",
 3657      targetPayload: {
 3658        clientId: "firefox-shell-page",
 3659        shellPage: true,
 3660        tabId: 6
 3661      }
 3662    });
 3663    const shellPageMessage = await artifactStore.insertMessage({
 3664      conversationId: "conv_shell_page",
 3665      id: "msg_shell_page",
 3666      observedAt: nowMs - 4_000,
 3667      platform: "claude",
 3668      rawText: "shell page route should not project",
 3669      role: "assistant"
 3670    });
 3671
 3672    await artifactStore.upsertLocalConversation({
 3673      automationStatus: "auto",
 3674      localConversationId: "lc_missing_tab_id",
 3675      platform: "claude"
 3676    });
 3677    await artifactStore.upsertConversationLink({
 3678      clientId: "firefox-missing-tab-id",
 3679      linkId: "link_missing_tab_id",
 3680      localConversationId: "lc_missing_tab_id",
 3681      observedAt: nowMs - 3_000,
 3682      pageUrl: "https://claude.ai/chat/conv_missing_tab_id",
 3683      platform: "claude",
 3684      remoteConversationId: "conv_missing_tab_id",
 3685      targetKind: "browser.proxy_delivery",
 3686      targetPayload: {
 3687        clientId: "firefox-missing-tab-id"
 3688      }
 3689    });
 3690    const missingTabIdMessage = await artifactStore.insertMessage({
 3691      conversationId: "conv_missing_tab_id",
 3692      id: "msg_missing_tab_id",
 3693      observedAt: nowMs - 3_000,
 3694      platform: "claude",
 3695      rawText: "missing tab id but conversation route should still project",
 3696      role: "assistant"
 3697    });
 3698
 3699    timedJobs = new ConductorTimedJobs(
 3700      {
 3701        intervalMs: 5_000,
 3702        maxMessagesPerTick: 10,
 3703        maxTasksPerTick: 10,
 3704        settleDelayMs: 1_000
 3705      },
 3706      {
 3707        artifactStore,
 3708        autoStart: false,
 3709        logDir: logsDir,
 3710        schedule: async (work) => {
 3711          await work({
 3712            controllerId: "mini-main",
 3713            host: "mini",
 3714            term: 2
 3715          });
 3716          return "scheduled";
 3717        }
 3718      }
 3719    );
 3720    timedJobs.registerRunner(
 3721      createRenewalProjectorRunner({
 3722        now: () => nowMs,
 3723        repository
 3724      })
 3725    );
 3726
 3727    await timedJobs.start();
 3728    const tick = await timedJobs.runTick("manual");
 3729    assert.equal(tick.decision, "scheduled");
 3730
 3731    const jobs = await artifactStore.listRenewalJobs({});
 3732    assert.equal(jobs.length, 2);
 3733    assert.deepEqual(
 3734      jobs.map((job) => job.messageId).sort(),
 3735      [autoMessage.id, missingTabIdMessage.id].sort()
 3736    );
 3737
 3738    const autoJob = jobs.find((job) => job.messageId === autoMessage.id);
 3739    const routeOnlyJob = jobs.find((job) => job.messageId === missingTabIdMessage.id);
 3740    assert.ok(autoJob);
 3741    assert.ok(routeOnlyJob);
 3742    assert.equal(autoJob.status, "pending");
 3743    assert.equal(autoJob.payloadKind, "json");
 3744    assert.equal(typeof autoJob.logPath, "string");
 3745    assert.match(autoJob.logPath, /\.jsonl$/u);
 3746    assert.equal(routeOnlyJob.status, "pending");
 3747    assert.equal(routeOnlyJob.payloadKind, "json");
 3748
 3749    const payload = JSON.parse(autoJob.payload);
 3750    assert.equal(payload.template, "summary_with_link");
 3751    assert.match(payload.text, /\[renewal\] Context summary:/u);
 3752    assert.equal(payload.sourceMessage.id, autoMessage.id);
 3753    assert.equal(payload.linkUrl, "https://claude.ai/chat/conv_auto");
 3754
 3755    const targetSnapshot = JSON.parse(autoJob.targetSnapshot);
 3756    assert.equal(targetSnapshot.target.kind, "browser.proxy_delivery");
 3757    assert.equal(targetSnapshot.route.pattern, "/chat/:conversationId");
 3758    assert.equal(targetSnapshot.target.payload.tabId, 4);
 3759    assert.equal(targetSnapshot.target.id, "client:firefox-auto");
 3760
 3761    const routeOnlyTargetSnapshot = JSON.parse(routeOnlyJob.targetSnapshot);
 3762    assert.equal(routeOnlyTargetSnapshot.target.kind, "browser.proxy_delivery");
 3763    assert.equal(routeOnlyTargetSnapshot.pageUrl, "https://claude.ai/chat/conv_missing_tab_id");
 3764    assert.equal(routeOnlyTargetSnapshot.target.payload.clientId, "firefox-missing-tab-id");
 3765    assert.equal(routeOnlyTargetSnapshot.target.payload.tabId, undefined);
 3766
 3767    const cursorState = await repository.getSystemState("renewal.projector.cursor");
 3768    assert.ok(cursorState);
 3769    assert.equal(cursorState.updatedAt, nowMs);
 3770    assert.deepEqual(JSON.parse(cursorState.valueJson), {
 3771      message_id: missingTabIdMessage.id,
 3772      observed_at: missingTabIdMessage.observedAt
 3773    });
 3774
 3775    const entries = await waitForJsonlEntries(
 3776      logsDir,
 3777      (items) => items.filter((entry) => entry.runner === "renewal.projector" && entry.stage === "job_projected").length >= 2
 3778    );
 3779    assert.equal(
 3780      entries.filter((entry) => entry.runner === "renewal.projector" && entry.stage === "job_projected").length,
 3781      2
 3782    );
 3783    assert.ok(
 3784      entries.find(
 3785        (entry) => entry.runner === "renewal.projector" && entry.stage === "message_skipped" && entry.result === "automation_manual"
 3786      )
 3787    );
 3788    assert.ok(
 3789      entries.find(
 3790        (entry) => entry.runner === "renewal.projector" && entry.stage === "message_skipped" && entry.result === "automation_paused"
 3791      )
 3792    );
 3793    assert.ok(
 3794      entries.find(
 3795        (entry) => entry.runner === "renewal.projector" && entry.stage === "message_skipped" && entry.result === "cooldown_active"
 3796      )
 3797    );
 3798    const routeUnavailableEntries = entries.filter(
 3799      (entry) => entry.runner === "renewal.projector" && entry.stage === "message_skipped" && entry.result === "route_unavailable"
 3800    );
 3801    assert.ok(
 3802      routeUnavailableEntries.find(
 3803        (entry) =>
 3804          entry.message_id === nonProxyTargetMessage.id
 3805          && entry.route_unavailable_reason === "target_kind_not_proxy_delivery"
 3806      )
 3807    );
 3808    assert.ok(
 3809      routeUnavailableEntries.find(
 3810        (entry) =>
 3811          entry.message_id === shellPageMessage.id
 3812          && entry.route_unavailable_reason === "shell_page"
 3813      )
 3814    );
 3815    assert.ok(
 3816      entries.find(
 3817        (entry) =>
 3818          entry.runner === "renewal.projector"
 3819          && entry.stage === "scan_completed"
 3820          && entry.cursor_after === `message:${missingTabIdMessage.observedAt}:${missingTabIdMessage.id}`
 3821      )
 3822    );
 3823
 3824    await timedJobs.stop();
 3825    assert.equal(manualMessage.id, "msg_manual");
 3826    assert.equal(pausedMessage.id, "msg_paused");
 3827    assert.equal(cooldownMessage.id, "msg_cooldown");
 3828  } finally {
 3829    await timedJobs?.stop();
 3830    artifactStore.close();
 3831    rmSync(rootDir, {
 3832      force: true,
 3833      recursive: true
 3834    });
 3835    rmSync(logsDir, {
 3836      force: true,
 3837      recursive: true
 3838    });
 3839  }
 3840});
 3841
 3842test("renewal projector restores cursor from valueJson even when legacy system_state.updated_at is second-based", async () => {
 3843  const rootDir = mkdtempSync(join(tmpdir(), "baa-renewal-projector-cursor-restore-legacy-"));
 3844  const stateDir = join(rootDir, "state");
 3845  const logsDir = mkdtempSync(join(tmpdir(), "baa-renewal-projector-cursor-restore-legacy-logs-"));
 3846  const localApiFixture = await createLocalApiFixture({
 3847    databasePath: join(rootDir, "control-plane.sqlite")
 3848  });
 3849  const artifactStore = new ArtifactStore({
 3850    artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
 3851    databasePath: join(stateDir, ARTIFACT_DB_FILENAME),
 3852    publicBaseUrl: "https://artifacts.example.test"
 3853  });
 3854  const nowMs = Date.UTC(2026, 2, 30, 12, 0, 0);
 3855  let timedJobs = null;
 3856
 3857  try {
 3858    await artifactStore.upsertLocalConversation({
 3859      automationStatus: "auto",
 3860      localConversationId: "lc_restore_cursor",
 3861      platform: "claude",
 3862      updatedAt: nowMs - 60_000
 3863    });
 3864    await artifactStore.upsertConversationLink({
 3865      clientId: "firefox-restore-cursor",
 3866      linkId: "link_restore_cursor",
 3867      localConversationId: "lc_restore_cursor",
 3868      observedAt: nowMs - 60_000,
 3869      pageUrl: "https://claude.ai/chat/conv_restore_cursor",
 3870      platform: "claude",
 3871      remoteConversationId: "conv_restore_cursor",
 3872      targetId: "tab:17",
 3873      targetKind: "browser.proxy_delivery",
 3874      targetPayload: {
 3875        clientId: "firefox-restore-cursor",
 3876        tabId: 17
 3877      }
 3878    });
 3879
 3880    const firstMessage = await artifactStore.insertMessage({
 3881      conversationId: "conv_restore_cursor",
 3882      id: "msg_restore_cursor_first",
 3883      observedAt: nowMs - 30_000,
 3884      platform: "claude",
 3885      rawText: "already processed before unit migration",
 3886      role: "assistant"
 3887    });
 3888    const secondMessage = await artifactStore.insertMessage({
 3889      conversationId: "conv_restore_cursor",
 3890      id: "msg_restore_cursor_second",
 3891      observedAt: nowMs - 20_000,
 3892      platform: "claude",
 3893      rawText: "should project after restoring legacy cursor",
 3894      role: "assistant"
 3895    });
 3896
 3897    await localApiFixture.repository.putSystemState({
 3898      stateKey: "renewal.projector.cursor",
 3899      updatedAt: Math.floor(nowMs / 1000),
 3900      valueJson: JSON.stringify({
 3901        message_id: firstMessage.id,
 3902        observed_at: firstMessage.observedAt
 3903      })
 3904    });
 3905
 3906    timedJobs = new ConductorTimedJobs(
 3907      {
 3908        intervalMs: 5_000,
 3909        maxMessagesPerTick: 10,
 3910        maxTasksPerTick: 10,
 3911        settleDelayMs: 0
 3912      },
 3913      {
 3914        artifactStore,
 3915        autoStart: false,
 3916        logDir: logsDir,
 3917        schedule: async (work) => {
 3918          await work({
 3919            controllerId: "mini-main",
 3920            host: "mini",
 3921            term: 2
 3922          });
 3923          return "scheduled";
 3924        }
 3925      }
 3926    );
 3927    timedJobs.registerRunner(
 3928      createRenewalProjectorRunner({
 3929        now: () => nowMs,
 3930        repository: localApiFixture.repository
 3931      })
 3932    );
 3933
 3934    await timedJobs.start();
 3935    const tick = await timedJobs.runTick("manual");
 3936    assert.equal(tick.decision, "scheduled");
 3937
 3938    const jobs = await artifactStore.listRenewalJobs({});
 3939    assert.equal(jobs.length, 1);
 3940    assert.equal(jobs[0].messageId, secondMessage.id);
 3941
 3942    const cursorState = await localApiFixture.repository.getSystemState("renewal.projector.cursor");
 3943    assert.ok(cursorState);
 3944    assert.equal(cursorState.updatedAt, nowMs);
 3945    assert.deepEqual(JSON.parse(cursorState.valueJson), {
 3946      message_id: secondMessage.id,
 3947      observed_at: secondMessage.observedAt
 3948    });
 3949
 3950    await timedJobs.stop();
 3951  } finally {
 3952    await timedJobs?.stop();
 3953    artifactStore.close();
 3954    localApiFixture.controlPlane.close();
 3955    rmSync(rootDir, {
 3956      force: true,
 3957      recursive: true
 3958    });
 3959    rmSync(logsDir, {
 3960      force: true,
 3961      recursive: true
 3962    });
 3963  }
 3964});
 3965
 3966test("shouldRenew keeps route_unavailable while exposing structured route failure details", async () => {
 3967  const nowMs = Date.UTC(2026, 2, 30, 10, 5, 0);
 3968  const baseCandidate = {
 3969    conversation: {
 3970      automationStatus: "auto",
 3971      cooldownUntil: null
 3972    },
 3973    link: {
 3974      isActive: true,
 3975      targetId: "tab:42",
 3976      targetKind: "browser.proxy_delivery",
 3977      targetPayload: JSON.stringify({
 3978        clientId: "firefox-route-detail",
 3979        tabId: 42
 3980      })
 3981    },
 3982    message: {
 3983      conversationId: "conv_route_detail",
 3984      id: "msg_route_detail",
 3985      observedAt: nowMs - 1_000,
 3986      organizationId: null,
 3987      pageTitle: null,
 3988      pageUrl: null,
 3989      platform: "claude",
 3990      rawText: "plain renewal candidate",
 3991      role: "assistant",
 3992      staticPath: "msg/msg_route_detail.txt",
 3993      summary: null
 3994    }
 3995  };
 3996
 3997  const cases = [
 3998    {
 3999      expectedReason: "inactive_link",
 4000      link: {
 4001        isActive: false
 4002      }
 4003    },
 4004    {
 4005      expectedReason: "target_kind_not_proxy_delivery",
 4006      link: {
 4007        targetKind: "browser.shell_page"
 4008      }
 4009    },
 4010    {
 4011      expectedReason: "shell_page",
 4012      link: {
 4013        targetPayload: JSON.stringify({
 4014          clientId: "firefox-route-detail",
 4015          shellPage: true,
 4016          tabId: 42
 4017        })
 4018      }
 4019    },
 4020    {
 4021      expectedReason: "missing_delivery_context",
 4022      link: {
 4023        targetId: null,
 4024        targetPayload: JSON.stringify({
 4025          clientId: "firefox-route-detail"
 4026        })
 4027      }
 4028    }
 4029  ];
 4030
 4031  for (const [index, testCase] of cases.entries()) {
 4032    const decision = await shouldRenew({
 4033      candidate: {
 4034        ...baseCandidate,
 4035        link: {
 4036          ...baseCandidate.link,
 4037          ...testCase.link
 4038        },
 4039        message: {
 4040          ...baseCandidate.message,
 4041          id: `${baseCandidate.message.id}_${index}`
 4042        }
 4043      },
 4044      now: nowMs,
 4045      store: {
 4046        async listRenewalJobs() {
 4047          assert.fail("route-unavailable branches should short-circuit before duplicate-job lookup");
 4048        }
 4049      }
 4050    });
 4051
 4052    assert.equal(decision.eligible, false);
 4053    assert.equal(decision.reason, "route_unavailable");
 4054    assert.equal(decision.routeUnavailableReason, testCase.expectedReason);
 4055  }
 4056});
 4057
 4058test("shouldRenew skips assistant messages that contain baa instruction blocks", async () => {
 4059  const decision = await shouldRenew({
 4060    candidate: {
 4061      conversation: {
 4062        automationStatus: "auto",
 4063        cooldownUntil: null
 4064      },
 4065      link: {
 4066        isActive: true,
 4067        targetId: "tab:42",
 4068        targetKind: "browser.proxy_delivery",
 4069        targetPayload: JSON.stringify({
 4070          clientId: "firefox-instruction-message",
 4071          tabId: 42
 4072        })
 4073      },
 4074      message: {
 4075        conversationId: "conv_instruction_message",
 4076        id: "msg_instruction_message",
 4077        observedAt: Date.UTC(2026, 2, 30, 10, 6, 0),
 4078        organizationId: null,
 4079        pageTitle: null,
 4080        pageUrl: null,
 4081        platform: "claude",
 4082        rawText: "```baa\n@conductor::describe\n```",
 4083        role: "assistant",
 4084        staticPath: "msg/msg_instruction_message.txt",
 4085        summary: null
 4086      }
 4087    },
 4088    now: Date.UTC(2026, 2, 30, 10, 6, 30),
 4089    store: {
 4090      async listRenewalJobs() {
 4091        assert.fail("instruction_message branches should short-circuit before duplicate-job lookup");
 4092      }
 4093    }
 4094  });
 4095
 4096  assert.equal(decision.eligible, false);
 4097  assert.equal(decision.reason, "instruction_message");
 4098});
 4099
 4100test("automation signal helpers pause repeated messages, repeated renewals, and consecutive failures", async () => {
 4101  const rootDir = mkdtempSync(join(tmpdir(), "baa-automation-signal-helpers-"));
 4102  const artifactStore = new ArtifactStore({
 4103    artifactDir: join(rootDir, ARTIFACTS_DIRNAME),
 4104    databasePath: join(rootDir, ARTIFACT_DB_FILENAME)
 4105  });
 4106  const baseConversation = await artifactStore.upsertLocalConversation({
 4107    automationStatus: "auto",
 4108    localConversationId: "lc_signal_helpers",
 4109    platform: "claude",
 4110    updatedAt: Date.UTC(2026, 2, 30, 10, 10, 0)
 4111  });
 4112
 4113  try {
 4114    let conversation = baseConversation;
 4115
 4116    for (let index = 0; index < 3; index += 1) {
 4117      conversation = await recordAssistantMessageAutomationSignal({
 4118        conversation,
 4119        observedAt: Date.UTC(2026, 2, 30, 10, 10, index),
 4120        rawText: "same assistant final message",
 4121        store: artifactStore
 4122      });
 4123    }
 4124
 4125    assert.equal(conversation.automationStatus, "paused");
 4126    assert.equal(conversation.pauseReason, "repeated_message");
 4127
 4128    conversation = await artifactStore.upsertLocalConversation({
 4129      automationStatus: "auto",
 4130      consecutiveFailureCount: 0,
 4131      lastError: null,
 4132      lastMessageFingerprint: null,
 4133      lastRenewalFingerprint: null,
 4134      localConversationId: conversation.localConversationId,
 4135      pauseReason: null,
 4136      pausedAt: null,
 4137      platform: conversation.platform,
 4138      repeatedMessageCount: 0,
 4139      repeatedRenewalCount: 0,
 4140      updatedAt: Date.UTC(2026, 2, 30, 10, 11, 0)
 4141    });
 4142
 4143    for (let index = 0; index < 3; index += 1) {
 4144      conversation = await recordRenewalPayloadSignal({
 4145        conversation,
 4146        observedAt: Date.UTC(2026, 2, 30, 10, 11, index),
 4147        payloadText: "[renewal] repeated payload",
 4148        store: artifactStore
 4149      });
 4150    }
 4151
 4152    assert.equal(conversation.automationStatus, "paused");
 4153    assert.equal(conversation.pauseReason, "repeated_renewal");
 4154
 4155    conversation = await artifactStore.upsertLocalConversation({
 4156      automationStatus: "auto",
 4157      consecutiveFailureCount: 0,
 4158      lastError: null,
 4159      lastMessageFingerprint: null,
 4160      lastRenewalFingerprint: null,
 4161      localConversationId: conversation.localConversationId,
 4162      pauseReason: null,
 4163      pausedAt: null,
 4164      platform: conversation.platform,
 4165      repeatedMessageCount: 0,
 4166      repeatedRenewalCount: 0,
 4167      updatedAt: Date.UTC(2026, 2, 30, 10, 12, 0)
 4168    });
 4169
 4170    for (let index = 0; index < 3; index += 1) {
 4171      conversation = await recordAutomationFailureSignal({
 4172        conversation,
 4173        errorMessage: "browser_action_timeout",
 4174        observedAt: Date.UTC(2026, 2, 30, 10, 12, index),
 4175        store: artifactStore
 4176      });
 4177    }
 4178
 4179    assert.equal(conversation.automationStatus, "paused");
 4180    assert.equal(conversation.pauseReason, "execution_failure");
 4181    assert.equal(conversation.lastError, "browser_action_timeout");
 4182  } finally {
 4183    artifactStore.close();
 4184    rmSync(rootDir, {
 4185      force: true,
 4186      recursive: true
 4187    });
 4188  }
 4189});
 4190
 4191test("renewal dispatcher sends due pending jobs through browser.proxy_delivery and marks them done", async () => {
 4192  const rootDir = mkdtempSync(join(tmpdir(), "baa-renewal-dispatcher-success-"));
 4193  const stateDir = join(rootDir, "state");
 4194  const logsDir = mkdtempSync(join(tmpdir(), "baa-renewal-dispatcher-success-logs-"));
 4195  const artifactStore = new ArtifactStore({
 4196    artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
 4197    databasePath: join(stateDir, ARTIFACT_DB_FILENAME)
 4198  });
 4199  const nowMs = Date.UTC(2026, 2, 30, 12, 0, 0);
 4200  const browserCalls = [];
 4201  const runner = createRenewalDispatcherRunner({
 4202    browserBridge: {
 4203      proxyDelivery(input) {
 4204        browserCalls.push(input);
 4205        return {
 4206          clientId: input.clientId || "firefox-chatgpt",
 4207          connectionId: "conn-firefox-chatgpt",
 4208          dispatchedAt: nowMs,
 4209          requestId: "proxy-dispatch-1",
 4210          result: Promise.resolve(buildProxyDeliveryActionResult({
 4211            clientId: input.clientId || "firefox-chatgpt",
 4212            connectionId: "conn-firefox-chatgpt",
 4213            deliveryAck: buildDeliveryAck(200, {
 4214              confirmed_at: nowMs + 40
 4215            }),
 4216            platform: input.platform,
 4217            receivedAt: nowMs + 50,
 4218            requestId: "proxy-dispatch-1"
 4219          })),
 4220          type: "browser.proxy_delivery"
 4221        };
 4222      }
 4223    },
 4224    now: () => nowMs
 4225  });
 4226
 4227  try {
 4228    await artifactStore.insertMessage({
 4229      conversationId: "conv_dispatch_success",
 4230      id: "msg_dispatch_success",
 4231      observedAt: nowMs - 60_000,
 4232      platform: "chatgpt",
 4233      rawText: "renewal dispatcher success message",
 4234      role: "assistant"
 4235    });
 4236    await artifactStore.upsertLocalConversation({
 4237      automationStatus: "auto",
 4238      localConversationId: "lc_dispatch_success",
 4239      platform: "chatgpt",
 4240      updatedAt: nowMs - 30_000
 4241    });
 4242    await artifactStore.upsertConversationLink({
 4243      clientId: "firefox-chatgpt",
 4244      linkId: "link_dispatch_success",
 4245      localConversationId: "lc_dispatch_success",
 4246      observedAt: nowMs - 30_000,
 4247      pageTitle: "Dispatch Success",
 4248      pageUrl: "https://chatgpt.com/c/conv_dispatch_success",
 4249      platform: "chatgpt",
 4250      remoteConversationId: "conv_dispatch_success",
 4251      routeParams: {
 4252        conversationId: "conv_dispatch_success"
 4253      },
 4254      routePath: "/c/conv_dispatch_success",
 4255      routePattern: "/c/:conversationId",
 4256      targetId: "tab:17",
 4257      targetKind: "browser.proxy_delivery",
 4258      targetPayload: {
 4259        clientId: "firefox-chatgpt",
 4260        conversationId: "conv_dispatch_success",
 4261        pageUrl: "https://chatgpt.com/c/conv_dispatch_success",
 4262        tabId: 17
 4263      }
 4264    });
 4265    await artifactStore.insertRenewalJob({
 4266      jobId: "job_dispatch_success",
 4267      localConversationId: "lc_dispatch_success",
 4268      messageId: "msg_dispatch_success",
 4269      nextAttemptAt: nowMs,
 4270      payload: JSON.stringify({
 4271        kind: "renewal.message",
 4272        sourceMessage: {
 4273          id: "msg_dispatch_success"
 4274        },
 4275        template: "summary_with_link",
 4276        text: "[renewal] keepalive",
 4277        version: 1
 4278      }),
 4279      payloadKind: "json",
 4280      targetSnapshot: {
 4281        stale: true
 4282      }
 4283    });
 4284
 4285    const { context, entries } = createTimedJobRunnerContext({
 4286      artifactStore,
 4287      logDir: logsDir
 4288    });
 4289    const result = await runner.run(context);
 4290    const job = await artifactStore.getRenewalJob("job_dispatch_success");
 4291    const conversation = await artifactStore.getLocalConversation("lc_dispatch_success");
 4292
 4293    assert.equal(result.result, "ok");
 4294    assert.equal(browserCalls.length, 1);
 4295    assert.equal(browserCalls[0].messageText, "[renewal] keepalive");
 4296    assert.equal(browserCalls[0].conversationId, "conv_dispatch_success");
 4297    assert.equal(browserCalls[0].pageUrl, "https://chatgpt.com/c/conv_dispatch_success");
 4298    assert.equal(browserCalls[0].tabId, null);
 4299    assert.equal(job.status, "done");
 4300    assert.equal(job.attemptCount, 1);
 4301    assert.equal(job.lastError, null);
 4302    assert.equal(job.nextAttemptAt, null);
 4303    assert.equal(typeof job.finishedAt, "number");
 4304    assert.equal(JSON.parse(job.targetSnapshot).target.kind, "browser.proxy_delivery");
 4305    assert.equal(conversation.cooldownUntil, nowMs + 60_000);
 4306    assert.equal(conversation.updatedAt, nowMs);
 4307    assert.ok(entries.find((entry) => entry.stage === "job_attempt_started"));
 4308    assert.ok(
 4309      entries.find(
 4310        (entry) =>
 4311          entry.stage === "job_completed"
 4312          && entry.result === "attempt_succeeded"
 4313          && entry.details?.downstream_status_code === 200
 4314      )
 4315    );
 4316  } finally {
 4317    artifactStore.close();
 4318    rmSync(rootDir, {
 4319      force: true,
 4320      recursive: true
 4321    });
 4322    rmSync(logsDir, {
 4323      force: true,
 4324      recursive: true
 4325    });
 4326  }
 4327});
 4328
 4329test("renewal dispatcher adds inter-job jitter before consecutive dispatches and logs it", async () => {
 4330  const rootDir = mkdtempSync(join(tmpdir(), "baa-renewal-dispatcher-jitter-"));
 4331  const stateDir = join(rootDir, "state");
 4332  const artifactStore = new ArtifactStore({
 4333    artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
 4334    databasePath: join(stateDir, ARTIFACT_DB_FILENAME)
 4335  });
 4336  const dispatchTimes = [];
 4337  const randomValues = [0, 1];
 4338  let randomIndex = 0;
 4339  const runner = createRenewalDispatcherRunner({
 4340    browserBridge: {
 4341      proxyDelivery(input) {
 4342        const dispatchedAt = Date.now();
 4343        dispatchTimes.push(dispatchedAt);
 4344
 4345        return {
 4346          clientId: input.clientId || "firefox-chatgpt",
 4347          connectionId: "conn-firefox-chatgpt",
 4348          dispatchedAt,
 4349          requestId: `proxy-dispatch-jitter-${dispatchTimes.length}`,
 4350          result: Promise.resolve(buildProxyDeliveryActionResult({
 4351            clientId: input.clientId || "firefox-chatgpt",
 4352            connectionId: "conn-firefox-chatgpt",
 4353            deliveryAck: buildDeliveryAck(200, {
 4354              confirmed_at: dispatchedAt + 4
 4355            }),
 4356            platform: input.platform,
 4357            receivedAt: dispatchedAt + 5,
 4358            requestId: `proxy-dispatch-jitter-${dispatchTimes.length}`
 4359          })),
 4360          type: "browser.proxy_delivery"
 4361        };
 4362      }
 4363    },
 4364    interJobJitterMaxMs: 45,
 4365    interJobJitterMinMs: 25,
 4366    random: () => randomValues[randomIndex++] ?? 0.5
 4367  });
 4368  const baseNowMs = Date.now() - 1_000;
 4369
 4370  try {
 4371    for (let index = 1; index <= 3; index += 1) {
 4372      const conversationId = `conv_dispatch_jitter_${index}`;
 4373      const localConversationId = `lc_dispatch_jitter_${index}`;
 4374      const messageId = `msg_dispatch_jitter_${index}`;
 4375      const jobId = `job_dispatch_jitter_${index}`;
 4376
 4377      await artifactStore.insertMessage({
 4378        conversationId,
 4379        id: messageId,
 4380        observedAt: baseNowMs - (index * 1_000),
 4381        platform: "chatgpt",
 4382        rawText: `renewal dispatcher jitter message ${index}`,
 4383        role: "assistant"
 4384      });
 4385      await artifactStore.upsertLocalConversation({
 4386        automationStatus: "auto",
 4387        localConversationId,
 4388        platform: "chatgpt",
 4389        updatedAt: baseNowMs - 500
 4390      });
 4391      await artifactStore.upsertConversationLink({
 4392        clientId: "firefox-chatgpt",
 4393        linkId: `link_dispatch_jitter_${index}`,
 4394        localConversationId,
 4395        observedAt: baseNowMs - 500,
 4396        pageTitle: `Dispatch Jitter ${index}`,
 4397        pageUrl: `https://chatgpt.com/c/${conversationId}`,
 4398        platform: "chatgpt",
 4399        remoteConversationId: conversationId,
 4400        routeParams: {
 4401          conversationId
 4402        },
 4403        routePath: `/c/${conversationId}`,
 4404        routePattern: "/c/:conversationId",
 4405        targetId: `tab:${30 + index}`,
 4406        targetKind: "browser.proxy_delivery",
 4407        targetPayload: {
 4408          clientId: "firefox-chatgpt",
 4409          conversationId,
 4410          pageUrl: `https://chatgpt.com/c/${conversationId}`,
 4411          tabId: 30 + index
 4412        }
 4413      });
 4414      await artifactStore.insertRenewalJob({
 4415        jobId,
 4416        localConversationId,
 4417        messageId,
 4418        nextAttemptAt: baseNowMs,
 4419        payload: `[renewal] jitter ${index}`,
 4420        payloadKind: "text"
 4421      });
 4422    }
 4423
 4424    const { context, entries } = createTimedJobRunnerContext({
 4425      artifactStore,
 4426      config: {
 4427        intervalMs: 5_000,
 4428        maxMessagesPerTick: 10,
 4429        maxTasksPerTick: 10,
 4430        settleDelayMs: 0
 4431      }
 4432    });
 4433    const result = await runner.run(context);
 4434    const jitterEntries = entries.filter((entry) => entry.stage === "job_dispatch_jitter");
 4435    const startedEntries = entries.filter((entry) => entry.stage === "job_attempt_started");
 4436
 4437    assert.equal(result.result, "ok");
 4438    assert.equal(result.details.successful_jobs, 3);
 4439    assert.equal(dispatchTimes.length, 3);
 4440    assert.ok(
 4441      dispatchTimes[1] - dispatchTimes[0] >= 20,
 4442      `expected second dispatch gap >= 20ms, got ${dispatchTimes[1] - dispatchTimes[0]}ms`
 4443    );
 4444    assert.ok(
 4445      dispatchTimes[2] - dispatchTimes[1] >= 35,
 4446      `expected third dispatch gap >= 35ms, got ${dispatchTimes[2] - dispatchTimes[1]}ms`
 4447    );
 4448    assert.deepEqual(
 4449      jitterEntries.map((entry) => entry.details?.jitter_ms),
 4450      [25, 45]
 4451    );
 4452    assert.deepEqual(
 4453      jitterEntries.map((entry) => entry.details?.dispatch_sequence_in_tick),
 4454      [2, 3]
 4455    );
 4456    assert.deepEqual(
 4457      startedEntries.map((entry) => entry.details?.inter_job_jitter_ms),
 4458      [0, 25, 45]
 4459    );
 4460  } finally {
 4461    artifactStore.close();
 4462    rmSync(rootDir, {
 4463      force: true,
 4464      recursive: true
 4465    });
 4466  }
 4467});
 4468
 4469test("renewal dispatcher classifies downstream proxy_delivery statuses for retry and terminal failure", async () => {
 4470  const rootDir = mkdtempSync(join(tmpdir(), "baa-renewal-dispatcher-http-status-"));
 4471  const stateDir = join(rootDir, "state");
 4472  const artifactStore = new ArtifactStore({
 4473    artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
 4474    databasePath: join(stateDir, ARTIFACT_DB_FILENAME)
 4475  });
 4476  const nowMs = Date.UTC(2026, 2, 30, 12, 30, 0);
 4477  const browserCalls = [];
 4478  const runner = createRenewalDispatcherRunner({
 4479    browserBridge: {
 4480      proxyDelivery(input) {
 4481        browserCalls.push(input);
 4482        const statusCode = input.conversationId === "conv_dispatch_http_retry" ? 429 : 401;
 4483
 4484        return {
 4485          clientId: input.clientId || "firefox-chatgpt",
 4486          connectionId: "conn-firefox-chatgpt",
 4487          dispatchedAt: nowMs,
 4488          requestId: `proxy-http-${statusCode}`,
 4489          result: Promise.resolve(buildProxyDeliveryActionResult({
 4490            clientId: input.clientId || "firefox-chatgpt",
 4491            connectionId: "conn-firefox-chatgpt",
 4492            deliveryAck: buildDeliveryAck(statusCode, {
 4493              confirmed_at: nowMs + 10
 4494            }),
 4495            platform: input.platform,
 4496            receivedAt: nowMs + 25,
 4497            requestId: `proxy-http-${statusCode}`
 4498          })),
 4499          type: "browser.proxy_delivery"
 4500        };
 4501      }
 4502    },
 4503    now: () => nowMs,
 4504    retryBaseDelayMs: 1,
 4505    retryMaxDelayMs: 1
 4506  });
 4507
 4508  try {
 4509    const definitions = [
 4510      {
 4511        conversationId: "conv_dispatch_http_retry",
 4512        expectedStatus: "pending",
 4513        jobId: "job_dispatch_http_retry",
 4514        lastError: "downstream_status_429",
 4515        localConversationId: "lc_dispatch_http_retry",
 4516        messageId: "msg_dispatch_http_retry",
 4517        pageTitle: "HTTP Retry Renewal",
 4518        tabId: 41
 4519      },
 4520      {
 4521        conversationId: "conv_dispatch_http_fail",
 4522        expectedStatus: "failed",
 4523        jobId: "job_dispatch_http_fail",
 4524        lastError: "downstream_status_401",
 4525        localConversationId: "lc_dispatch_http_fail",
 4526        messageId: "msg_dispatch_http_fail",
 4527        pageTitle: "HTTP Fail Renewal",
 4528        tabId: 42
 4529      }
 4530    ];
 4531
 4532    for (const definition of definitions) {
 4533      await artifactStore.insertMessage({
 4534        conversationId: definition.conversationId,
 4535        id: definition.messageId,
 4536        observedAt: nowMs - 60_000,
 4537        platform: "chatgpt",
 4538        rawText: `${definition.conversationId} renewal message`,
 4539        role: "assistant"
 4540      });
 4541      await artifactStore.upsertLocalConversation({
 4542        automationStatus: "auto",
 4543        localConversationId: definition.localConversationId,
 4544        platform: "chatgpt",
 4545        updatedAt: nowMs - 30_000
 4546      });
 4547      await artifactStore.upsertConversationLink({
 4548        clientId: "firefox-chatgpt",
 4549        linkId: `link_${definition.localConversationId}`,
 4550        localConversationId: definition.localConversationId,
 4551        observedAt: nowMs - 30_000,
 4552        pageTitle: definition.pageTitle,
 4553        pageUrl: `https://chatgpt.com/c/${definition.conversationId}`,
 4554        platform: "chatgpt",
 4555        remoteConversationId: definition.conversationId,
 4556        routeParams: {
 4557          conversationId: definition.conversationId
 4558        },
 4559        routePath: `/c/${definition.conversationId}`,
 4560        routePattern: "/c/:conversationId",
 4561        targetId: `tab:${definition.tabId}`,
 4562        targetKind: "browser.proxy_delivery",
 4563        targetPayload: {
 4564          clientId: "firefox-chatgpt",
 4565          conversationId: definition.conversationId,
 4566          pageUrl: `https://chatgpt.com/c/${definition.conversationId}`,
 4567          tabId: definition.tabId
 4568        }
 4569      });
 4570      await artifactStore.insertRenewalJob({
 4571        jobId: definition.jobId,
 4572        localConversationId: definition.localConversationId,
 4573        maxAttempts: 2,
 4574        messageId: definition.messageId,
 4575        nextAttemptAt: nowMs,
 4576        payload: "[renewal] http confirmation",
 4577        payloadKind: "text"
 4578      });
 4579    }
 4580
 4581    const { context, entries } = createTimedJobRunnerContext({
 4582      artifactStore
 4583    });
 4584    const result = await runner.run(context);
 4585    const retryJob = await artifactStore.getRenewalJob("job_dispatch_http_retry");
 4586    const failedJob = await artifactStore.getRenewalJob("job_dispatch_http_fail");
 4587
 4588    assert.equal(result.result, "ok");
 4589    assert.equal(result.details.retried_jobs, 1);
 4590    assert.equal(result.details.failed_jobs, 1);
 4591    assert.equal(browserCalls.length, 2);
 4592    assert.equal(retryJob.status, "pending");
 4593    assert.equal(retryJob.attemptCount, 1);
 4594    assert.equal(retryJob.lastError, "downstream_status_429");
 4595    assert.equal(retryJob.nextAttemptAt, nowMs + 1);
 4596    assert.equal(failedJob.status, "failed");
 4597    assert.equal(failedJob.attemptCount, 1);
 4598    assert.equal(failedJob.lastError, "downstream_status_401");
 4599    assert.equal(failedJob.nextAttemptAt, null);
 4600    assert.ok(
 4601      entries.find(
 4602        (entry) =>
 4603          entry.stage === "job_retry_scheduled"
 4604          && entry.result === "downstream_status_429"
 4605          && entry.details?.downstream_status_code === 429
 4606      )
 4607    );
 4608    assert.ok(
 4609      entries.find(
 4610        (entry) =>
 4611          entry.stage === "job_failed"
 4612          && entry.result === "downstream_status_401"
 4613          && entry.details?.downstream_status_code === 401
 4614      )
 4615    );
 4616  } finally {
 4617    artifactStore.close();
 4618    rmSync(rootDir, {
 4619      force: true,
 4620      recursive: true
 4621    });
 4622  }
 4623});
 4624
 4625test("renewal dispatcher uses short retries for ChatGPT cold-start template misses and logs warmup recovery", async () => {
 4626  const rootDir = mkdtempSync(join(tmpdir(), "baa-renewal-dispatcher-chatgpt-cold-start-"));
 4627  const stateDir = join(rootDir, "state");
 4628  const artifactStore = new ArtifactStore({
 4629    artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
 4630    databasePath: join(stateDir, ARTIFACT_DB_FILENAME)
 4631  });
 4632  const coldStartReason = "delivery.template_missing: missing ChatGPT send template; send one real ChatGPT message first";
 4633  let nowMs = Date.UTC(2026, 2, 30, 12, 45, 0);
 4634  const browserCalls = [];
 4635  const runner = createRenewalDispatcherRunner({
 4636    browserBridge: {
 4637      proxyDelivery(input) {
 4638        browserCalls.push({
 4639          ...input,
 4640          calledAt: nowMs
 4641        });
 4642        const requestId = `proxy-chatgpt-cold-start-${browserCalls.length}`;
 4643
 4644        if (browserCalls.length === 1) {
 4645          return {
 4646            clientId: input.clientId || "firefox-chatgpt",
 4647            connectionId: "conn-firefox-chatgpt",
 4648            dispatchedAt: nowMs,
 4649            requestId,
 4650            result: Promise.resolve(buildProxyDeliveryActionResult({
 4651              accepted: true,
 4652              clientId: input.clientId || "firefox-chatgpt",
 4653              connectionId: "conn-firefox-chatgpt",
 4654              failed: true,
 4655              platform: input.platform,
 4656              reason: coldStartReason,
 4657              receivedAt: nowMs + 20,
 4658              requestId,
 4659              results: []
 4660            })),
 4661            type: "browser.proxy_delivery"
 4662          };
 4663        }
 4664
 4665        return {
 4666          clientId: input.clientId || "firefox-chatgpt",
 4667          connectionId: "conn-firefox-chatgpt",
 4668          dispatchedAt: nowMs,
 4669          requestId,
 4670          result: Promise.resolve(buildProxyDeliveryActionResult({
 4671            clientId: input.clientId || "firefox-chatgpt",
 4672            connectionId: "conn-firefox-chatgpt",
 4673            deliveryAck: buildDeliveryAck(200, {
 4674              confirmed_at: nowMs + 30
 4675            }),
 4676            platform: input.platform,
 4677            receivedAt: nowMs + 40,
 4678            requestId
 4679          })),
 4680          type: "browser.proxy_delivery"
 4681        };
 4682      }
 4683    },
 4684    now: () => nowMs,
 4685    random: () => 0.5,
 4686    retryBaseDelayMs: 30_000,
 4687    retryMaxDelayMs: 30_000
 4688  });
 4689
 4690  try {
 4691    await artifactStore.insertMessage({
 4692      conversationId: "conv_dispatch_chatgpt_cold_start",
 4693      id: "msg_dispatch_chatgpt_cold_start",
 4694      observedAt: nowMs - 60_000,
 4695      platform: "chatgpt",
 4696      rawText: "renewal dispatcher cold start message",
 4697      role: "assistant"
 4698    });
 4699    await artifactStore.upsertLocalConversation({
 4700      automationStatus: "auto",
 4701      localConversationId: "lc_dispatch_chatgpt_cold_start",
 4702      platform: "chatgpt",
 4703      updatedAt: nowMs - 30_000
 4704    });
 4705    await artifactStore.upsertConversationLink({
 4706      clientId: "firefox-chatgpt",
 4707      linkId: "link_dispatch_chatgpt_cold_start",
 4708      localConversationId: "lc_dispatch_chatgpt_cold_start",
 4709      observedAt: nowMs - 30_000,
 4710      pageTitle: "ChatGPT Cold Start",
 4711      pageUrl: "https://chatgpt.com/c/conv_dispatch_chatgpt_cold_start",
 4712      platform: "chatgpt",
 4713      remoteConversationId: "conv_dispatch_chatgpt_cold_start",
 4714      routeParams: {
 4715        conversationId: "conv_dispatch_chatgpt_cold_start"
 4716      },
 4717      routePath: "/c/conv_dispatch_chatgpt_cold_start",
 4718      routePattern: "/c/:conversationId",
 4719      targetId: "tab:61",
 4720      targetKind: "browser.proxy_delivery",
 4721      targetPayload: {
 4722        clientId: "firefox-chatgpt",
 4723        conversationId: "conv_dispatch_chatgpt_cold_start",
 4724        pageUrl: "https://chatgpt.com/c/conv_dispatch_chatgpt_cold_start",
 4725        tabId: 61
 4726      }
 4727    });
 4728    await artifactStore.insertRenewalJob({
 4729      jobId: "job_dispatch_chatgpt_cold_start",
 4730      localConversationId: "lc_dispatch_chatgpt_cold_start",
 4731      maxAttempts: 3,
 4732      messageId: "msg_dispatch_chatgpt_cold_start",
 4733      nextAttemptAt: nowMs,
 4734      payload: "[renewal] cold start recovery",
 4735      payloadKind: "text"
 4736    });
 4737
 4738    const firstTick = createTimedJobRunnerContext({
 4739      artifactStore
 4740    });
 4741    const firstResult = await runner.run(firstTick.context);
 4742    const pendingJob = await artifactStore.getRenewalJob("job_dispatch_chatgpt_cold_start");
 4743
 4744    assert.equal(firstResult.result, "ok");
 4745    assert.equal(firstResult.details.retried_jobs, 1);
 4746    assert.equal(browserCalls.length, 1);
 4747    assert.equal(pendingJob.status, "pending");
 4748    assert.equal(pendingJob.attemptCount, 1);
 4749    assert.equal(pendingJob.lastError, coldStartReason);
 4750    assert.equal(pendingJob.nextAttemptAt, nowMs + 5_000);
 4751    assert.ok(
 4752      firstTick.entries.find(
 4753        (entry) =>
 4754          entry.stage === "chatgpt_cold_start_delivery"
 4755          && entry.result === "waiting_for_template_warmup"
 4756          && entry.details?.retry_base_delay_ms === 5_000
 4757          && entry.details?.retry_delay_ms === 5_000
 4758      )
 4759    );
 4760    assert.ok(
 4761      firstTick.entries.find(
 4762        (entry) =>
 4763          entry.stage === "job_retry_scheduled"
 4764          && entry.result === coldStartReason
 4765          && entry.details?.cold_start_waiting_for_template === true
 4766          && entry.details?.retry_base_delay_ms === 5_000
 4767      )
 4768    );
 4769
 4770    nowMs = pendingJob.nextAttemptAt;
 4771
 4772    const secondTick = createTimedJobRunnerContext({
 4773      artifactStore
 4774    });
 4775    const secondResult = await runner.run(secondTick.context);
 4776    const completedJob = await artifactStore.getRenewalJob("job_dispatch_chatgpt_cold_start");
 4777
 4778    assert.equal(secondResult.result, "ok");
 4779    assert.equal(secondResult.details.successful_jobs, 1);
 4780    assert.equal(browserCalls.length, 2);
 4781    assert.equal(completedJob.status, "done");
 4782    assert.equal(completedJob.attemptCount, 2);
 4783    assert.equal(completedJob.lastError, null);
 4784    assert.equal(completedJob.nextAttemptAt, null);
 4785    assert.ok(
 4786      secondTick.entries.find(
 4787        (entry) =>
 4788          entry.stage === "chatgpt_template_warmup"
 4789          && entry.result === "template_warmup_completed"
 4790          && entry.details?.previous_error === coldStartReason
 4791      )
 4792    );
 4793    assert.ok(
 4794      secondTick.entries.find(
 4795        (entry) =>
 4796          entry.stage === "job_completed"
 4797          && entry.result === "attempt_succeeded"
 4798          && entry.details?.recovered_from_cold_start === true
 4799      )
 4800    );
 4801  } finally {
 4802    artifactStore.close();
 4803    rmSync(rootDir, {
 4804      force: true,
 4805      recursive: true
 4806    });
 4807  }
 4808});
 4809
 4810test("renewal dispatcher success writes cooldownUntil and blocks projector from projecting follow-up messages during cooldown", async () => {
 4811  const rootDir = mkdtempSync(join(tmpdir(), "baa-renewal-dispatcher-cooldown-chain-"));
 4812  const stateDir = join(rootDir, "state");
 4813  const localApiFixture = await createLocalApiFixture({
 4814    databasePath: join(rootDir, "control-plane.sqlite")
 4815  });
 4816  const artifactStore = new ArtifactStore({
 4817    artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
 4818    databasePath: join(stateDir, ARTIFACT_DB_FILENAME)
 4819  });
 4820  let nowMs = Date.UTC(2026, 2, 30, 14, 0, 0);
 4821  const browserCalls = [];
 4822  const projector = createRenewalProjectorRunner({
 4823    now: () => nowMs,
 4824    repository: localApiFixture.repository
 4825  });
 4826  const dispatcher = createRenewalDispatcherRunner({
 4827    browserBridge: {
 4828      proxyDelivery(input) {
 4829        browserCalls.push(input);
 4830        return {
 4831          clientId: input.clientId || "firefox-claude",
 4832          connectionId: "conn-firefox-claude",
 4833          dispatchedAt: nowMs,
 4834          requestId: "proxy-chain-1",
 4835          result: Promise.resolve(buildProxyDeliveryActionResult({
 4836            clientId: input.clientId || "firefox-claude",
 4837            connectionId: "conn-firefox-claude",
 4838            deliveryAck: buildDeliveryAck(200, {
 4839              confirmed_at: nowMs + 40
 4840            }),
 4841            platform: input.platform,
 4842            receivedAt: nowMs + 50,
 4843            requestId: "proxy-chain-1"
 4844          })),
 4845          type: "browser.proxy_delivery"
 4846        };
 4847      }
 4848    },
 4849    now: () => nowMs,
 4850    successCooldownMs: 60_000
 4851  });
 4852
 4853  try {
 4854    await artifactStore.upsertLocalConversation({
 4855      automationStatus: "auto",
 4856      localConversationId: "lc_dispatch_chain",
 4857      platform: "claude",
 4858      updatedAt: nowMs - 60_000
 4859    });
 4860    await artifactStore.upsertConversationLink({
 4861      clientId: "firefox-claude",
 4862      linkId: "link_dispatch_chain",
 4863      localConversationId: "lc_dispatch_chain",
 4864      observedAt: nowMs - 60_000,
 4865      pageTitle: "Dispatch Cooldown Chain",
 4866      pageUrl: "https://claude.ai/chat/conv_dispatch_chain",
 4867      platform: "claude",
 4868      remoteConversationId: "conv_dispatch_chain",
 4869      routeParams: {
 4870        conversationId: "conv_dispatch_chain"
 4871      },
 4872      routePath: "/chat/conv_dispatch_chain",
 4873      routePattern: "/chat/:conversationId",
 4874      targetId: "tab:21",
 4875      targetKind: "browser.proxy_delivery",
 4876      targetPayload: {
 4877        clientId: "firefox-claude",
 4878        conversationId: "conv_dispatch_chain",
 4879        pageUrl: "https://claude.ai/chat/conv_dispatch_chain",
 4880        tabId: 21
 4881      }
 4882    });
 4883    await artifactStore.insertMessage({
 4884      conversationId: "conv_dispatch_chain",
 4885      id: "msg_dispatch_chain_seed",
 4886      observedAt: nowMs - 30_000,
 4887      platform: "claude",
 4888      rawText: "first renewal candidate should dispatch successfully",
 4889      role: "assistant"
 4890    });
 4891
 4892    const initialProjectorTick = createTimedJobRunnerContext({
 4893      artifactStore,
 4894      config: {
 4895        intervalMs: 10_000,
 4896        maxMessagesPerTick: 10,
 4897        maxTasksPerTick: 10,
 4898        settleDelayMs: 0
 4899      }
 4900    });
 4901    const projected = await projector.run(initialProjectorTick.context);
 4902    const projectedJobs = await artifactStore.listRenewalJobs({});
 4903
 4904    assert.equal(projected.result, "ok");
 4905    assert.equal(projected.details.projected_jobs, 1);
 4906    assert.equal(projectedJobs.length, 1);
 4907    assert.equal(projectedJobs[0].messageId, "msg_dispatch_chain_seed");
 4908
 4909    const initialDispatcherTick = createTimedJobRunnerContext({
 4910      artifactStore,
 4911      config: {
 4912        intervalMs: 10_000,
 4913        maxMessagesPerTick: 10,
 4914        maxTasksPerTick: 10,
 4915        settleDelayMs: 0
 4916      }
 4917    });
 4918    const dispatched = await dispatcher.run(initialDispatcherTick.context);
 4919    const conversationAfterSuccess = await artifactStore.getLocalConversation("lc_dispatch_chain");
 4920
 4921    assert.equal(dispatched.result, "ok");
 4922    assert.equal(dispatched.details.successful_jobs, 1);
 4923    assert.equal(browserCalls.length, 1);
 4924    assert.ok(conversationAfterSuccess);
 4925    assert.equal(conversationAfterSuccess.cooldownUntil, nowMs + 60_000);
 4926
 4927    nowMs += 10_000;
 4928    await artifactStore.insertMessage({
 4929      conversationId: "conv_dispatch_chain",
 4930      id: "msg_dispatch_chain_followup",
 4931      observedAt: nowMs - 1_000,
 4932      platform: "claude",
 4933      rawText: "follow-up message should be skipped by cooldown",
 4934      role: "assistant"
 4935    });
 4936
 4937    const cooldownProjectorTick = createTimedJobRunnerContext({
 4938      artifactStore,
 4939      config: {
 4940        intervalMs: 10_000,
 4941        maxMessagesPerTick: 10,
 4942        maxTasksPerTick: 10,
 4943        settleDelayMs: 0
 4944      }
 4945    });
 4946    const cooldownProjected = await projector.run(cooldownProjectorTick.context);
 4947    const followupJobs = await artifactStore.listRenewalJobs({
 4948      messageId: "msg_dispatch_chain_followup"
 4949    });
 4950
 4951    assert.equal(cooldownProjected.result, "ok");
 4952    assert.equal(cooldownProjected.details.projected_jobs, 0);
 4953    assert.equal(followupJobs.length, 0);
 4954    assert.ok(
 4955      cooldownProjectorTick.entries.find(
 4956        (entry) =>
 4957          entry.stage === "message_skipped"
 4958          && entry.result === "cooldown_active"
 4959          && entry.details?.message_id === "msg_dispatch_chain_followup"
 4960      )
 4961    );
 4962  } finally {
 4963    artifactStore.close();
 4964    localApiFixture.controlPlane.close();
 4965    rmSync(rootDir, {
 4966      force: true,
 4967      recursive: true
 4968    });
 4969  }
 4970});
 4971
 4972test("renewal dispatcher adds retry jitter so same-attempt failures do not reschedule to the same timestamp", async () => {
 4973  const rootDir = mkdtempSync(join(tmpdir(), "baa-renewal-dispatcher-retry-jitter-"));
 4974  const stateDir = join(rootDir, "state");
 4975  const artifactStore = new ArtifactStore({
 4976    artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
 4977    databasePath: join(stateDir, ARTIFACT_DB_FILENAME)
 4978  });
 4979  const nowMs = Date.UTC(2026, 2, 30, 13, 30, 0);
 4980  const randomValues = [0, 1];
 4981  let randomIndex = 0;
 4982  const runner = createRenewalDispatcherRunner({
 4983    browserBridge: {
 4984      proxyDelivery() {
 4985        assert.fail("jobs missing active links should not call browser.proxy_delivery");
 4986      }
 4987    },
 4988    now: () => nowMs,
 4989    random: () => randomValues[randomIndex++] ?? 0.5,
 4990    retryBaseDelayMs: 1_000,
 4991    retryJitterFactor: 0.3,
 4992    retryMaxDelayMs: 10_000
 4993  });
 4994
 4995  try {
 4996    for (let index = 1; index <= 2; index += 1) {
 4997      const conversationId = `conv_dispatch_retry_jitter_${index}`;
 4998      const localConversationId = `lc_dispatch_retry_jitter_${index}`;
 4999      const messageId = `msg_dispatch_retry_jitter_${index}`;
 5000
 5001      await artifactStore.insertMessage({
 5002        conversationId,
 5003        id: messageId,
 5004        observedAt: nowMs - (index * 1_000),
 5005        platform: "claude",
 5006        rawText: `renewal dispatcher retry jitter message ${index}`,
 5007        role: "assistant"
 5008      });
 5009      await artifactStore.upsertLocalConversation({
 5010        automationStatus: "auto",
 5011        localConversationId,
 5012        platform: "claude",
 5013        updatedAt: nowMs - 500
 5014      });
 5015      await artifactStore.insertRenewalJob({
 5016        jobId: `job_dispatch_retry_jitter_${index}`,
 5017        localConversationId,
 5018        maxAttempts: 3,
 5019        messageId,
 5020        nextAttemptAt: nowMs,
 5021        payload: `[renewal] retry jitter ${index}`,
 5022        payloadKind: "text"
 5023      });
 5024    }
 5025
 5026    const { context, entries } = createTimedJobRunnerContext({
 5027      artifactStore
 5028    });
 5029    const result = await runner.run(context);
 5030    const firstJob = await artifactStore.getRenewalJob("job_dispatch_retry_jitter_1");
 5031    const secondJob = await artifactStore.getRenewalJob("job_dispatch_retry_jitter_2");
 5032    const retryEntries = entries.filter((entry) => entry.stage === "job_retry_scheduled");
 5033
 5034    assert.equal(result.result, "ok");
 5035    assert.equal(result.details.retried_jobs, 2);
 5036    assert.equal(firstJob.status, "pending");
 5037    assert.equal(secondJob.status, "pending");
 5038    assert.equal(firstJob.nextAttemptAt, nowMs + 700);
 5039    assert.equal(secondJob.nextAttemptAt, nowMs + 1_300);
 5040    assert.notEqual(firstJob.nextAttemptAt, secondJob.nextAttemptAt);
 5041    assert.deepEqual(
 5042      retryEntries.map((entry) => entry.details?.retry_base_delay_ms),
 5043      [1_000, 1_000]
 5044    );
 5045    assert.deepEqual(
 5046      retryEntries.map((entry) => entry.details?.retry_jitter_ms),
 5047      [-300, 300]
 5048    );
 5049    assert.deepEqual(
 5050      retryEntries.map((entry) => entry.details?.retry_delay_ms),
 5051      [700, 1_300]
 5052    );
 5053  } finally {
 5054    artifactStore.close();
 5055    rmSync(rootDir, {
 5056      force: true,
 5057      recursive: true
 5058    });
 5059  }
 5060});
 5061
 5062test("renewal dispatcher defers paused jobs and retries transient proxy failures until failed", async () => {
 5063  const rootDir = mkdtempSync(join(tmpdir(), "baa-renewal-dispatcher-retry-"));
 5064  const stateDir = join(rootDir, "state");
 5065  const artifactStore = new ArtifactStore({
 5066    artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
 5067    databasePath: join(stateDir, ARTIFACT_DB_FILENAME)
 5068  });
 5069  let nowMs = Date.UTC(2026, 2, 30, 13, 0, 0);
 5070  const browserCalls = [];
 5071  const retryRunner = createRenewalDispatcherRunner({
 5072    browserBridge: {
 5073      proxyDelivery(input) {
 5074        browserCalls.push(input);
 5075        return {
 5076          clientId: input.clientId || "firefox-claude",
 5077          connectionId: "conn-firefox-claude",
 5078          dispatchedAt: nowMs,
 5079          requestId: `proxy-retry-${browserCalls.length}`,
 5080          result: Promise.resolve({
 5081            accepted: false,
 5082            action: "proxy_delivery",
 5083            completed: true,
 5084            failed: true,
 5085            reason: "no_active_client",
 5086            received_at: nowMs + 25,
 5087            request_id: `proxy-retry-${browserCalls.length}`,
 5088            result: {
 5089              actual_count: 0,
 5090              desired_count: 1,
 5091              drift_count: 0,
 5092              failed_count: 1,
 5093              ok_count: 0,
 5094              platform_count: 1,
 5095              restored_count: 0,
 5096              skipped_reasons: ["no_active_client"]
 5097            },
 5098            results: [],
 5099            shell_runtime: [],
 5100            target: {
 5101              client_id: input.clientId || "firefox-claude",
 5102              connection_id: "conn-firefox-claude",
 5103              platform: input.platform,
 5104              requested_client_id: input.clientId || "firefox-claude",
 5105              requested_platform: input.platform
 5106            },
 5107            type: "browser.proxy_delivery"
 5108          }),
 5109          type: "browser.proxy_delivery"
 5110        };
 5111      }
 5112    },
 5113    now: () => nowMs,
 5114    retryBaseDelayMs: 1,
 5115    retryMaxDelayMs: 1
 5116  });
 5117
 5118  try {
 5119    await artifactStore.insertMessage({
 5120      conversationId: "conv_dispatch_paused",
 5121      id: "msg_dispatch_paused",
 5122      observedAt: nowMs - 60_000,
 5123      platform: "claude",
 5124      rawText: "renewal dispatcher paused message",
 5125      role: "assistant"
 5126    });
 5127    await artifactStore.insertMessage({
 5128      conversationId: "conv_dispatch_retry",
 5129      id: "msg_dispatch_retry",
 5130      observedAt: nowMs - 55_000,
 5131      platform: "claude",
 5132      rawText: "renewal dispatcher retry message",
 5133      role: "assistant"
 5134    });
 5135    await artifactStore.upsertLocalConversation({
 5136      automationStatus: "paused",
 5137      localConversationId: "lc_dispatch_paused",
 5138      pausedAt: nowMs - 5_000,
 5139      platform: "claude"
 5140    });
 5141    await artifactStore.upsertLocalConversation({
 5142      automationStatus: "auto",
 5143      localConversationId: "lc_dispatch_retry",
 5144      platform: "claude"
 5145    });
 5146    await artifactStore.upsertConversationLink({
 5147      clientId: "firefox-claude",
 5148      linkId: "link_dispatch_paused",
 5149      localConversationId: "lc_dispatch_paused",
 5150      observedAt: nowMs - 20_000,
 5151      pageTitle: "Paused Renewal",
 5152      pageUrl: "https://claude.ai/chat/conv_dispatch_paused",
 5153      platform: "claude",
 5154      remoteConversationId: "conv_dispatch_paused",
 5155      routeParams: {
 5156        conversationId: "conv_dispatch_paused"
 5157      },
 5158      routePath: "/chat/conv_dispatch_paused",
 5159      routePattern: "/chat/:conversationId",
 5160      targetId: "tab:11",
 5161      targetKind: "browser.proxy_delivery",
 5162      targetPayload: {
 5163        clientId: "firefox-claude",
 5164        conversationId: "conv_dispatch_paused",
 5165        pageUrl: "https://claude.ai/chat/conv_dispatch_paused",
 5166        tabId: 11
 5167      }
 5168    });
 5169    await artifactStore.upsertConversationLink({
 5170      clientId: "firefox-claude",
 5171      linkId: "link_dispatch_retry",
 5172      localConversationId: "lc_dispatch_retry",
 5173      observedAt: nowMs - 20_000,
 5174      pageTitle: "Retry Renewal",
 5175      pageUrl: "https://claude.ai/chat/conv_dispatch_retry",
 5176      platform: "claude",
 5177      remoteConversationId: "conv_dispatch_retry",
 5178      routeParams: {
 5179        conversationId: "conv_dispatch_retry"
 5180      },
 5181      routePath: "/chat/conv_dispatch_retry",
 5182      routePattern: "/chat/:conversationId",
 5183      targetId: "tab:12",
 5184      targetKind: "browser.proxy_delivery",
 5185      targetPayload: {
 5186        clientId: "firefox-claude",
 5187        conversationId: "conv_dispatch_retry",
 5188        pageUrl: "https://claude.ai/chat/conv_dispatch_retry",
 5189        tabId: 12
 5190      }
 5191    });
 5192    await artifactStore.insertRenewalJob({
 5193      jobId: "job_dispatch_paused",
 5194      localConversationId: "lc_dispatch_paused",
 5195      messageId: "msg_dispatch_paused",
 5196      nextAttemptAt: nowMs,
 5197      payload: "[renewal] paused",
 5198      payloadKind: "text"
 5199    });
 5200    await artifactStore.insertRenewalJob({
 5201      jobId: "job_dispatch_retry",
 5202      localConversationId: "lc_dispatch_retry",
 5203      maxAttempts: 2,
 5204      messageId: "msg_dispatch_retry",
 5205      nextAttemptAt: nowMs,
 5206      payload: "[renewal] retry",
 5207      payloadKind: "text"
 5208    });
 5209
 5210    const pausedContext = createTimedJobRunnerContext({
 5211      artifactStore,
 5212      config: {
 5213        intervalMs: 5_000,
 5214        maxMessagesPerTick: 10,
 5215        maxTasksPerTick: 10,
 5216        settleDelayMs: 0
 5217      }
 5218    });
 5219    const firstResult = await retryRunner.run(pausedContext.context);
 5220    const pausedJob = await artifactStore.getRenewalJob("job_dispatch_paused");
 5221    const retryAfterFirstAttempt = await artifactStore.getRenewalJob("job_dispatch_retry");
 5222
 5223    assert.equal(firstResult.result, "ok");
 5224    assert.equal(pausedJob.status, "pending");
 5225    assert.equal(pausedJob.attemptCount, 0);
 5226    assert.equal(pausedJob.nextAttemptAt, nowMs + 10_000);
 5227    assert.equal(retryAfterFirstAttempt.status, "pending");
 5228    assert.equal(retryAfterFirstAttempt.attemptCount, 1);
 5229    assert.equal(retryAfterFirstAttempt.lastError, "no_active_client");
 5230    assert.equal(retryAfterFirstAttempt.nextAttemptAt, nowMs + 1);
 5231    assert.equal(browserCalls.length, 1);
 5232    assert.ok(
 5233      pausedContext.entries.find((entry) => entry.stage === "job_deferred" && entry.result === "automation_paused")
 5234    );
 5235    assert.ok(
 5236      pausedContext.entries.find((entry) => entry.stage === "job_retry_scheduled" && entry.result === "no_active_client")
 5237    );
 5238
 5239    nowMs = retryAfterFirstAttempt.nextAttemptAt;
 5240    const secondContext = createTimedJobRunnerContext({
 5241      artifactStore
 5242    });
 5243    const secondResult = await retryRunner.run(secondContext.context);
 5244    const retryAfterSecondAttempt = await artifactStore.getRenewalJob("job_dispatch_retry");
 5245
 5246    assert.equal(secondResult.result, "ok");
 5247    assert.equal(retryAfterSecondAttempt.status, "failed");
 5248    assert.equal(retryAfterSecondAttempt.attemptCount, 2);
 5249    assert.equal(retryAfterSecondAttempt.lastError, "no_active_client");
 5250    assert.equal(retryAfterSecondAttempt.nextAttemptAt, null);
 5251    assert.equal(browserCalls.length, 2);
 5252    assert.ok(
 5253      secondContext.entries.find((entry) => entry.stage === "job_failed" && entry.result === "no_active_client")
 5254    );
 5255  } finally {
 5256    artifactStore.close();
 5257    rmSync(rootDir, {
 5258      force: true,
 5259      recursive: true
 5260    });
 5261  }
 5262});
 5263
 5264test("renewal dispatcher defers jobs when the local conversation execution lock is busy", async () => {
 5265  const rootDir = mkdtempSync(join(tmpdir(), "baa-renewal-dispatcher-busy-"));
 5266  const artifactStore = new ArtifactStore({
 5267    artifactDir: join(rootDir, ARTIFACTS_DIRNAME),
 5268    databasePath: join(rootDir, ARTIFACT_DB_FILENAME)
 5269  });
 5270  const nowMs = Date.UTC(2026, 2, 30, 13, 30, 0);
 5271  let browserCalls = 0;
 5272  const runner = createRenewalDispatcherRunner({
 5273    browserBridge: {
 5274      proxyDelivery() {
 5275        browserCalls += 1;
 5276        throw new Error("proxyDelivery should not run while the instruction lock is held");
 5277      }
 5278    },
 5279    now: () => nowMs
 5280  });
 5281
 5282  try {
 5283    await artifactStore.insertMessage({
 5284      conversationId: "conv_dispatch_busy",
 5285      id: "msg_dispatch_busy",
 5286      observedAt: nowMs - 60_000,
 5287      platform: "claude",
 5288      rawText: "renewal dispatcher busy message",
 5289      role: "assistant"
 5290    });
 5291    await artifactStore.upsertLocalConversation({
 5292      automationStatus: "auto",
 5293      executionState: "instruction_running",
 5294      localConversationId: "lc_dispatch_busy",
 5295      platform: "claude",
 5296      updatedAt: nowMs - 1_000
 5297    });
 5298    await artifactStore.upsertConversationLink({
 5299      clientId: "firefox-claude",
 5300      linkId: "link_dispatch_busy",
 5301      localConversationId: "lc_dispatch_busy",
 5302      observedAt: nowMs - 20_000,
 5303      pageTitle: "Busy Renewal",
 5304      pageUrl: "https://claude.ai/chat/conv_dispatch_busy",
 5305      platform: "claude",
 5306      remoteConversationId: "conv_dispatch_busy",
 5307      routeParams: {
 5308        conversationId: "conv_dispatch_busy"
 5309      },
 5310      routePath: "/chat/conv_dispatch_busy",
 5311      routePattern: "/chat/:conversationId",
 5312      targetId: "tab:14",
 5313      targetKind: "browser.proxy_delivery",
 5314      targetPayload: {
 5315        clientId: "firefox-claude",
 5316        conversationId: "conv_dispatch_busy",
 5317        pageUrl: "https://claude.ai/chat/conv_dispatch_busy",
 5318        tabId: 14
 5319      }
 5320    });
 5321    await artifactStore.insertRenewalJob({
 5322      jobId: "job_dispatch_busy",
 5323      localConversationId: "lc_dispatch_busy",
 5324      messageId: "msg_dispatch_busy",
 5325      nextAttemptAt: nowMs,
 5326      payload: "[renewal] busy",
 5327      payloadKind: "text"
 5328    });
 5329
 5330    const busyContext = createTimedJobRunnerContext({
 5331      artifactStore
 5332    });
 5333    const result = await runner.run(busyContext.context);
 5334    const deferredJob = await artifactStore.getRenewalJob("job_dispatch_busy");
 5335
 5336    assert.equal(result.result, "ok");
 5337    assert.equal(browserCalls, 0);
 5338    assert.equal(deferredJob.status, "pending");
 5339    assert.equal(deferredJob.attemptCount, 0);
 5340    assert.equal(deferredJob.nextAttemptAt, nowMs + 10_000);
 5341    assert.ok(
 5342      busyContext.entries.find((entry) => entry.stage === "job_deferred" && entry.result === "automation_busy")
 5343    );
 5344  } finally {
 5345    artifactStore.close();
 5346    rmSync(rootDir, {
 5347      force: true,
 5348      recursive: true
 5349    });
 5350  }
 5351});
 5352
 5353test("renewal dispatcher records timeout failures with a distinct timeout result and timeout_ms", async () => {
 5354  const rootDir = mkdtempSync(join(tmpdir(), "baa-renewal-dispatcher-timeout-"));
 5355  const stateDir = join(rootDir, "state");
 5356  const artifactStore = new ArtifactStore({
 5357    artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
 5358    databasePath: join(stateDir, ARTIFACT_DB_FILENAME)
 5359  });
 5360  let nowMs = Date.UTC(2026, 2, 30, 14, 0, 0);
 5361  const timeoutMs = 250;
 5362  let requestCount = 0;
 5363  const runner = createRenewalDispatcherRunner({
 5364    browserBridge: {
 5365      proxyDelivery(input) {
 5366        requestCount += 1;
 5367        const requestId = `proxy-timeout-${requestCount}`;
 5368
 5369        return {
 5370          clientId: input.clientId || "firefox-claude",
 5371          connectionId: "conn-firefox-claude",
 5372          dispatchedAt: nowMs,
 5373          requestId,
 5374          result: Promise.reject(
 5375            new FirefoxBridgeError(
 5376              "action_timeout",
 5377              `Firefox client "${input.clientId || "firefox-claude"}" did not report action_result "${requestId}" within ${timeoutMs}ms.`,
 5378              {
 5379                clientId: input.clientId || "firefox-claude",
 5380                connectionId: "conn-firefox-claude",
 5381                requestId,
 5382                timeoutMs
 5383              }
 5384            )
 5385          ),
 5386          type: "browser.proxy_delivery"
 5387        };
 5388      }
 5389    },
 5390    executionTimeoutMs: timeoutMs,
 5391    now: () => nowMs,
 5392    retryBaseDelayMs: 1,
 5393    retryMaxDelayMs: 1
 5394  });
 5395
 5396  try {
 5397    await artifactStore.insertMessage({
 5398      conversationId: "conv_dispatch_timeout",
 5399      id: "msg_dispatch_timeout",
 5400      observedAt: nowMs - 60_000,
 5401      platform: "claude",
 5402      rawText: "renewal dispatcher timeout message",
 5403      role: "assistant"
 5404    });
 5405    await artifactStore.upsertLocalConversation({
 5406      automationStatus: "auto",
 5407      localConversationId: "lc_dispatch_timeout",
 5408      platform: "claude"
 5409    });
 5410    await artifactStore.upsertConversationLink({
 5411      clientId: "firefox-claude",
 5412      linkId: "link_dispatch_timeout",
 5413      localConversationId: "lc_dispatch_timeout",
 5414      observedAt: nowMs - 20_000,
 5415      pageTitle: "Timeout Renewal",
 5416      pageUrl: "https://claude.ai/chat/conv_dispatch_timeout",
 5417      platform: "claude",
 5418      remoteConversationId: "conv_dispatch_timeout",
 5419      routeParams: {
 5420        conversationId: "conv_dispatch_timeout"
 5421      },
 5422      routePath: "/chat/conv_dispatch_timeout",
 5423      routePattern: "/chat/:conversationId",
 5424      targetId: "tab:13",
 5425      targetKind: "browser.proxy_delivery",
 5426      targetPayload: {
 5427        clientId: "firefox-claude",
 5428        conversationId: "conv_dispatch_timeout",
 5429        pageUrl: "https://claude.ai/chat/conv_dispatch_timeout",
 5430        tabId: 13
 5431      }
 5432    });
 5433    await artifactStore.insertRenewalJob({
 5434      jobId: "job_dispatch_timeout",
 5435      localConversationId: "lc_dispatch_timeout",
 5436      maxAttempts: 2,
 5437      messageId: "msg_dispatch_timeout",
 5438      nextAttemptAt: nowMs,
 5439      payload: "[renewal] timeout",
 5440      payloadKind: "text"
 5441    });
 5442
 5443    const firstContext = createTimedJobRunnerContext({
 5444      artifactStore
 5445    });
 5446    const firstResult = await runner.run(firstContext.context);
 5447    const retryJob = await artifactStore.getRenewalJob("job_dispatch_timeout");
 5448
 5449    assert.equal(firstResult.result, "ok");
 5450    assert.equal(retryJob.status, "pending");
 5451    assert.equal(retryJob.attemptCount, 1);
 5452    assert.equal(retryJob.lastError, "browser_action_timeout");
 5453    assert.equal(retryJob.nextAttemptAt, nowMs + 1);
 5454    assert.ok(
 5455      firstContext.entries.find(
 5456        (entry) =>
 5457          entry.stage === "job_retry_scheduled"
 5458          && entry.result === "browser_action_timeout"
 5459          && entry.details?.error_code === "action_timeout"
 5460          && entry.details?.timeout_ms === timeoutMs
 5461      )
 5462    );
 5463
 5464    nowMs = retryJob.nextAttemptAt;
 5465    const secondContext = createTimedJobRunnerContext({
 5466      artifactStore
 5467    });
 5468    const secondResult = await runner.run(secondContext.context);
 5469    const failedJob = await artifactStore.getRenewalJob("job_dispatch_timeout");
 5470
 5471    assert.equal(secondResult.result, "ok");
 5472    assert.equal(failedJob.status, "failed");
 5473    assert.equal(failedJob.attemptCount, 2);
 5474    assert.equal(failedJob.lastError, "browser_action_timeout");
 5475    assert.equal(failedJob.nextAttemptAt, null);
 5476    assert.ok(
 5477      secondContext.entries.find(
 5478        (entry) =>
 5479          entry.stage === "job_failed"
 5480          && entry.result === "browser_action_timeout"
 5481          && entry.details?.error_code === "action_timeout"
 5482          && entry.details?.timeout_ms === timeoutMs
 5483      )
 5484    );
 5485  } finally {
 5486    artifactStore.close();
 5487    rmSync(rootDir, {
 5488      force: true,
 5489      recursive: true
 5490    });
 5491  }
 5492});
 5493
 5494test("ConductorTimedJobs keeps standby runners idle and clears interval handles on stop", async () => {
 5495  const logsDir = mkdtempSync(join(tmpdir(), "baa-timed-jobs-standby-"));
 5496  const intervalScheduler = createManualIntervalScheduler();
 5497  const daemon = new ConductorDaemon(
 5498    {
 5499      nodeId: "mac-standby",
 5500      host: "mac",
 5501      role: "standby",
 5502      controlApiBase: "https://control.example.test"
 5503    },
 5504    {
 5505      autoStartLoops: false,
 5506      client: {
 5507        async acquireLeaderLease() {
 5508          return createLeaseResult({
 5509            holderId: "mini-main",
 5510            term: 7,
 5511            leaseExpiresAt: 230,
 5512            renewedAt: 200,
 5513            isLeader: false,
 5514            operation: "acquire"
 5515          });
 5516        },
 5517        async sendControllerHeartbeat() {}
 5518      },
 5519      now: () => 200
 5520    }
 5521  );
 5522  let runCount = 0;
 5523  const timedJobs = new ConductorTimedJobs(
 5524    {
 5525      intervalMs: 5_000,
 5526      maxMessagesPerTick: 10,
 5527      maxTasksPerTick: 10,
 5528      settleDelayMs: 10_000
 5529    },
 5530    {
 5531      autoStart: true,
 5532      clearIntervalImpl: intervalScheduler.clearInterval,
 5533      logDir: logsDir,
 5534      schedule: (work) => daemon.runSchedulerPass(work),
 5535      setIntervalImpl: intervalScheduler.setInterval
 5536    }
 5537  );
 5538
 5539  timedJobs.registerRunner({
 5540    name: "renewal.dispatcher",
 5541    async run() {
 5542      runCount += 1;
 5543      return {
 5544        result: "ok"
 5545      };
 5546    }
 5547  });
 5548
 5549  try {
 5550    await daemon.start();
 5551    await timedJobs.start();
 5552
 5553    assert.equal(intervalScheduler.getActiveCount(), 1);
 5554
 5555    const tick = await timedJobs.runTick("manual");
 5556    assert.equal(tick.decision, "skipped_not_leader");
 5557    assert.equal(runCount, 0);
 5558
 5559    await timedJobs.stop();
 5560    assert.equal(intervalScheduler.getActiveCount(), 0);
 5561
 5562    const entries = await waitForJsonlEntries(logsDir);
 5563    assert.ok(
 5564      entries.find(
 5565        (entry) =>
 5566          entry.runner === "renewal.dispatcher"
 5567          && entry.stage === "runner_skipped"
 5568          && entry.result === "skipped_not_leader"
 5569      )
 5570    );
 5571  } finally {
 5572    await timedJobs.stop();
 5573    rmSync(logsDir, {
 5574      force: true,
 5575      recursive: true
 5576    });
 5577  }
 5578});
 5579
 5580test("ConductorRuntime skips timed-jobs ticks while system automation is paused", async () => {
 5581  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-runtime-system-paused-state-"));
 5582  const logsDir = mkdtempSync(join(tmpdir(), "baa-conductor-runtime-system-paused-logs-"));
 5583  const runtime = new ConductorRuntime(
 5584    {
 5585      nodeId: "mini-main",
 5586      host: "mini",
 5587      role: "primary",
 5588      controlApiBase: "https://control.example.test",
 5589      localApiBase: "http://127.0.0.1:0",
 5590      sharedToken: "replace-me",
 5591      timedJobsIntervalMs: 50,
 5592      paths: {
 5593        logsDir,
 5594        runsDir: "/tmp/runs",
 5595        stateDir
 5596      }
 5597    },
 5598    {
 5599      now: () => 250
 5600    }
 5601  );
 5602
 5603  try {
 5604    const snapshot = await runtime.start();
 5605    const baseUrl = snapshot.controlApi.localApiBase;
 5606    const pauseResponse = await fetch(`${baseUrl}/v1/system/pause`, {
 5607      body: JSON.stringify({
 5608        reason: "pause_before_timed_jobs_tick",
 5609        requested_by: "integration_test"
 5610      }),
 5611      headers: {
 5612        "content-type": "application/json"
 5613      },
 5614      method: "POST"
 5615    });
 5616    assert.equal(pauseResponse.status, 200);
 5617
 5618    const timedJobsEntries = await waitForJsonlEntries(
 5619      join(logsDir, "timed-jobs"),
 5620      (items) =>
 5621        items.some((entry) =>
 5622          entry.runner === "timed-jobs.framework"
 5623          && entry.stage === "tick_completed"
 5624          && entry.result === "skipped_system_paused"
 5625        )
 5626        && items.some((entry) =>
 5627          entry.runner === "renewal.projector"
 5628          && entry.stage === "runner_skipped"
 5629          && entry.result === "skipped_system_paused"
 5630        )
 5631        && items.some((entry) =>
 5632          entry.runner === "renewal.dispatcher"
 5633          && entry.stage === "runner_skipped"
 5634          && entry.result === "skipped_system_paused"
 5635        )
 5636    );
 5637
 5638    assert.ok(
 5639      timedJobsEntries.find((entry) =>
 5640        entry.runner === "timed-jobs.framework"
 5641        && entry.stage === "tick_completed"
 5642        && entry.result === "skipped_system_paused"
 5643      )
 5644    );
 5645    assert.ok(
 5646      timedJobsEntries.find((entry) =>
 5647        entry.runner === "renewal.projector"
 5648        && entry.stage === "runner_skipped"
 5649        && entry.result === "skipped_system_paused"
 5650      )
 5651    );
 5652    assert.ok(
 5653      timedJobsEntries.find((entry) =>
 5654        entry.runner === "renewal.dispatcher"
 5655        && entry.stage === "runner_skipped"
 5656        && entry.result === "skipped_system_paused"
 5657      )
 5658    );
 5659  } finally {
 5660    await runtime.stop();
 5661    rmSync(stateDir, {
 5662      force: true,
 5663      recursive: true
 5664    });
 5665    rmSync(logsDir, {
 5666      force: true,
 5667      recursive: true
 5668    });
 5669  }
 5670});
 5671
 5672test("createFetchControlApiClient unwraps control-api envelopes and sends bearer auth", async () => {
 5673  const observedRequests = [];
 5674  const client = createFetchControlApiClient(
 5675    "https://control.example.test/",
 5676    async (url, init) => {
 5677      observedRequests.push({
 5678        url,
 5679        init
 5680      });
 5681
 5682      return new Response(
 5683        JSON.stringify({
 5684          ok: true,
 5685          request_id: "req-1",
 5686          data: {
 5687            holder_id: "mini-main",
 5688            holder_host: "mini",
 5689            term: 3,
 5690            lease_expires_at: 130,
 5691            renewed_at: 100,
 5692            is_leader: true,
 5693            operation: "renew",
 5694            lease: {
 5695              lease_name: "global",
 5696              holder_id: "mini-main",
 5697              holder_host: "mini",
 5698              term: 3,
 5699              lease_expires_at: 130,
 5700              renewed_at: 100,
 5701              preferred_holder_id: "mini-main",
 5702              metadata_json: null
 5703            }
 5704          }
 5705        }),
 5706        {
 5707          status: 200,
 5708          headers: {
 5709            "content-type": "application/json"
 5710          }
 5711        }
 5712      );
 5713    },
 5714    {
 5715      bearerToken: "secret-token"
 5716    }
 5717  );
 5718
 5719  const result = await client.acquireLeaderLease({
 5720    controllerId: "mini-main",
 5721    host: "mini",
 5722    ttlSec: 30,
 5723    preferred: true,
 5724    now: 100
 5725  });
 5726
 5727  assert.equal(observedRequests.length, 1);
 5728  assert.equal(String(observedRequests[0].url), "https://control.example.test/v1/leader/acquire");
 5729  assert.equal(
 5730    new Headers(observedRequests[0].init.headers).get("authorization"),
 5731    "Bearer secret-token"
 5732  );
 5733  assert.deepEqual(JSON.parse(String(observedRequests[0].init.body)), {
 5734    controller_id: "mini-main",
 5735    host: "mini",
 5736    ttl_sec: 30,
 5737    preferred: true,
 5738    now: 100
 5739  });
 5740  assert.equal(result.holderId, "mini-main");
 5741  assert.equal(result.holderHost, "mini");
 5742  assert.equal(result.term, 3);
 5743  assert.equal(result.operation, "renew");
 5744  assert.equal(result.isLeader, true);
 5745});
 5746
 5747test("parseConductorCliRequest merges launchd env defaults with CLI overrides", () => {
 5748  const request = parseConductorCliRequest(["start", "--role", "standby", "--run-once"], {
 5749    BAA_NODE_ID: "mini-main",
 5750    BAA_CONDUCTOR_HOST: "mini",
 5751    BAA_CONDUCTOR_ROLE: "primary",
 5752    BAA_CONDUCTOR_PUBLIC_API_BASE: "https://public.example.test/",
 5753    BAA_CODE_ROOT_DIR: "/tmp/code-root/",
 5754    BAA_CODEXD_LOCAL_API_BASE: "http://127.0.0.1:4323/",
 5755    BAA_CONDUCTOR_LOCAL_API: "http://127.0.0.1:4317/",
 5756    BAA_SHARED_TOKEN: "replace-me",
 5757    BAA_RUNS_DIR: "/tmp/runs"
 5758  });
 5759
 5760  assert.equal(request.action, "start");
 5761
 5762  if (request.action !== "start") {
 5763    throw new Error("expected start action");
 5764  }
 5765
 5766  assert.equal(request.runOnce, true);
 5767  assert.equal(request.config.role, "standby");
 5768  assert.equal(request.config.nodeId, "mini-main");
 5769  assert.equal(request.config.publicApiBase, "https://public.example.test");
 5770  assert.equal(request.config.controlApiBase, "https://public.example.test");
 5771  assert.equal(request.config.codeRootDir, "/tmp/code-root");
 5772  assert.equal(request.config.codexdLocalApiBase, "http://127.0.0.1:4323");
 5773  assert.equal(request.config.localApiBase, "http://127.0.0.1:4317");
 5774  assert.equal(request.config.paths.runsDir, "/tmp/runs");
 5775});
 5776
 5777test("parseConductorCliRequest prefers the canonical public API env name over the legacy alias", () => {
 5778  const request = parseConductorCliRequest(["config"], {
 5779    BAA_NODE_ID: "mini-main",
 5780    BAA_CONDUCTOR_HOST: "mini",
 5781    BAA_CONDUCTOR_ROLE: "primary",
 5782    BAA_CONDUCTOR_PUBLIC_API_BASE: "https://public.example.test/",
 5783    BAA_CONTROL_API_BASE: "https://legacy.example.test/",
 5784    BAA_SHARED_TOKEN: "replace-me"
 5785  });
 5786
 5787  assert.equal(request.action, "config");
 5788
 5789  if (request.action !== "config") {
 5790    throw new Error("expected config action");
 5791  }
 5792
 5793  assert.equal(request.config.publicApiBase, "https://public.example.test");
 5794  assert.equal(request.config.controlApiBase, "https://public.example.test");
 5795});
 5796
 5797test("parseConductorCliRequest prefers --public-api-base over --control-api-base", () => {
 5798  const request = parseConductorCliRequest(
 5799    [
 5800      "config",
 5801      "--control-api-base",
 5802      "https://legacy-cli.example.test/",
 5803      "--public-api-base",
 5804      "https://public-cli.example.test/"
 5805    ],
 5806    {
 5807      BAA_NODE_ID: "mini-main",
 5808      BAA_CONDUCTOR_HOST: "mini",
 5809      BAA_CONDUCTOR_ROLE: "primary",
 5810      BAA_SHARED_TOKEN: "replace-me"
 5811    }
 5812  );
 5813
 5814  assert.equal(request.action, "config");
 5815
 5816  if (request.action !== "config") {
 5817    throw new Error("expected config action");
 5818  }
 5819
 5820  assert.equal(request.config.publicApiBase, "https://public-cli.example.test");
 5821  assert.equal(request.config.controlApiBase, "https://public-cli.example.test");
 5822});
 5823
 5824test("parseConductorCliRequest allows an explicitly listed Tailscale local API host", () => {
 5825  const request = parseConductorCliRequest(["config"], {
 5826    BAA_NODE_ID: "mini-main",
 5827    BAA_CONDUCTOR_HOST: "mini",
 5828    BAA_CONDUCTOR_ROLE: "primary",
 5829    BAA_CONTROL_API_BASE: "https://control.example.test/",
 5830    BAA_CONDUCTOR_LOCAL_API: "http://100.71.210.78:4317/",
 5831    BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS: "100.71.210.78",
 5832    BAA_SHARED_TOKEN: "replace-me"
 5833  });
 5834
 5835  assert.equal(request.action, "config");
 5836
 5837  if (request.action !== "config") {
 5838    throw new Error("expected config action");
 5839  }
 5840
 5841  assert.equal(request.config.localApiBase, "http://100.71.210.78:4317");
 5842  assert.deepEqual(request.config.localApiAllowedHosts, ["100.71.210.78"]);
 5843});
 5844
 5845test("parseConductorCliRequest rejects unlisted or non-Tailscale local API hosts", () => {
 5846  assert.throws(
 5847    () =>
 5848      parseConductorCliRequest(["config"], {
 5849        BAA_NODE_ID: "mini-main",
 5850        BAA_CONDUCTOR_HOST: "mini",
 5851        BAA_CONDUCTOR_ROLE: "primary",
 5852        BAA_CONTROL_API_BASE: "https://control.example.test/",
 5853        BAA_CONDUCTOR_LOCAL_API: "http://100.71.210.78:4317/",
 5854        BAA_SHARED_TOKEN: "replace-me"
 5855      }),
 5856    /explicitly allowed Tailscale 100\.x host/
 5857  );
 5858
 5859  assert.throws(
 5860    () =>
 5861      parseConductorCliRequest(["config"], {
 5862        BAA_NODE_ID: "mini-main",
 5863        BAA_CONDUCTOR_HOST: "mini",
 5864        BAA_CONDUCTOR_ROLE: "primary",
 5865        BAA_CONTROL_API_BASE: "https://control.example.test/",
 5866        BAA_CONDUCTOR_LOCAL_API: "http://100.71.210.78:4317/",
 5867        BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS: "192.168.1.50",
 5868        BAA_SHARED_TOKEN: "replace-me"
 5869      }),
 5870    /localApiAllowedHosts/
 5871  );
 5872});
 5873
 5874test("handleConductorHttpRequest keeps degraded runtimes observable but not ready", async () => {
 5875  const snapshot = {
 5876    claudeCoded: {
 5877      localApiBase: null
 5878    },
 5879    codexd: {
 5880      localApiBase: null
 5881    },
 5882    daemon: {
 5883      nodeId: "mini-main",
 5884      host: "mini",
 5885      role: "primary",
 5886      leaseState: "degraded",
 5887      schedulerEnabled: false,
 5888      currentLeaderId: null,
 5889      currentTerm: null,
 5890      leaseExpiresAt: null,
 5891      lastHeartbeatAt: 100,
 5892      lastLeaseOperation: "acquire",
 5893      nextLeaseOperation: "acquire",
 5894      consecutiveRenewFailures: 2,
 5895      lastError: "lease endpoint timeout"
 5896    },
 5897    identity: "mini-main@mini(primary)",
 5898    controlApi: {
 5899      baseUrl: "https://control.example.test",
 5900      localApiBase: "http://127.0.0.1:4317",
 5901      hasSharedToken: true,
 5902      usesPlaceholderToken: false
 5903    },
 5904    runtime: {
 5905      pid: 123,
 5906      started: true,
 5907      startedAt: 100
 5908    },
 5909    startupChecklist: [],
 5910    warnings: []
 5911  };
 5912
 5913  const readyResponse = await handleConductorHttpRequest(
 5914    {
 5915      method: "GET",
 5916      path: "/readyz"
 5917    },
 5918    {
 5919      repository: null,
 5920      snapshotLoader: () => snapshot
 5921    }
 5922  );
 5923  assert.equal(readyResponse.status, 503);
 5924  assert.equal(readyResponse.body, "not_ready\n");
 5925
 5926  const roleResponse = await handleConductorHttpRequest(
 5927    {
 5928      method: "GET",
 5929      path: "/rolez"
 5930    },
 5931    {
 5932      repository: null,
 5933      snapshotLoader: () => snapshot
 5934    }
 5935  );
 5936  assert.equal(roleResponse.status, 200);
 5937  assert.equal(roleResponse.body, "standby\n");
 5938});
 5939
 5940test("handleConductorHttpRequest serves the migrated local business endpoints from the local repository", async () => {
 5941  const { repository, sharedToken, snapshot } = await createLocalApiFixture();
 5942  const browser = createBrowserBridgeStub();
 5943  const codexd = await startCodexdStubServer();
 5944  const hostOpsDir = mkdtempSync(join(tmpdir(), "baa-conductor-local-host-http-"));
 5945  const authorizedHeaders = {
 5946    authorization: `Bearer ${sharedToken}`
 5947  };
 5948  snapshot.codexd.localApiBase = codexd.baseUrl;
 5949  const browserState = browser.context.browserStateLoader();
 5950  browserState.clients[0].request_hooks.push(
 5951    {
 5952      account: "ops@example.com",
 5953      credential_fingerprint: "fp-chatgpt-stub",
 5954      platform: "chatgpt",
 5955      endpoint_count: 1,
 5956      endpoint_metadata: [
 5957        {
 5958          method: "POST",
 5959          path: "/backend-api/conversation",
 5960          first_seen_at: 1710000002600,
 5961          last_seen_at: 1710000003600
 5962        }
 5963      ],
 5964      endpoints: [
 5965        "POST /backend-api/conversation"
 5966      ],
 5967      last_verified_at: 1710000003650,
 5968      updated_at: 1710000003550
 5969    },
 5970    {
 5971      account: "ops@example.com",
 5972      credential_fingerprint: "fp-gemini-stub",
 5973      platform: "gemini",
 5974      endpoint_count: 1,
 5975      endpoint_metadata: [
 5976        {
 5977          method: "POST",
 5978          path: "/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate",
 5979          first_seen_at: 1710000002700,
 5980          last_seen_at: 1710000003700
 5981        }
 5982      ],
 5983      endpoints: [
 5984        "POST /_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"
 5985      ],
 5986      last_verified_at: 1710000003750,
 5987      updated_at: 1710000003650
 5988    }
 5989  );
 5990  browserState.clients[0].shell_runtime.push(
 5991    buildShellRuntime("chatgpt", {
 5992      actual: {
 5993        ...buildShellRuntime("chatgpt").actual,
 5994        url: "https://chatgpt.com/c/conv-chatgpt-current"
 5995      }
 5996    }),
 5997    buildShellRuntime("gemini", {
 5998      actual: {
 5999        ...buildShellRuntime("gemini").actual,
 6000        url: "https://gemini.google.com/app/conv-gemini-current"
 6001      }
 6002    })
 6003  );
 6004  browserState.clients[0].final_messages.push(
 6005    {
 6006      conversation_id: "conv-chatgpt-current",
 6007      observed_at: 1710000003800,
 6008      page_title: "ChatGPT Current",
 6009      page_url: "https://chatgpt.com/c/conv-chatgpt-current",
 6010      platform: "chatgpt",
 6011      raw_text: "hello from chatgpt current"
 6012    },
 6013    {
 6014      conversation_id: "conv-gemini-current",
 6015      observed_at: 1710000003900,
 6016      page_title: "Gemini Current",
 6017      page_url: "https://gemini.google.com/app/conv-gemini-current",
 6018      platform: "gemini",
 6019      raw_text: "hello from gemini current"
 6020    }
 6021  );
 6022  const localApiContext = {
 6023    ...browser.context,
 6024    browserStateLoader: () => browserState,
 6025    codexdLocalApiBase: codexd.baseUrl,
 6026    fetchImpl: globalThis.fetch,
 6027    repository,
 6028    sharedToken,
 6029    snapshotLoader: () => snapshot
 6030  };
 6031  const versionedLocalApiContext = {
 6032    ...localApiContext,
 6033    version: "1.2.3"
 6034  };
 6035
 6036  try {
 6037    const describeResponse = await handleConductorHttpRequest(
 6038      {
 6039        method: "GET",
 6040        path: "/describe"
 6041      },
 6042      versionedLocalApiContext
 6043    );
 6044    assert.equal(describeResponse.status, 200);
 6045    const describePayload = parseJsonBody(describeResponse);
 6046    assert.equal(describePayload.ok, true);
 6047    assert.equal(describePayload.data.name, "baa-conductor-daemon");
 6048    assert.equal(describePayload.data.system.mode, "running");
 6049    assert.equal(describePayload.data.describe_endpoints.business.path, "/describe/business");
 6050    assert.equal(describePayload.data.describe_endpoints.control.path, "/describe/control");
 6051    assert.equal(describePayload.data.codex.enabled, true);
 6052    assert.equal(describePayload.data.codex.target_base_url, codexd.baseUrl);
 6053    assert.equal(describePayload.data.browser.route_prefix, "/v1/browser");
 6054    assert.equal(describePayload.data.browser.action_contract.route.path, "/v1/browser/actions");
 6055    assert.equal(describePayload.data.browser.request_contract.route.path, "/v1/browser/request");
 6056    assert.equal(
 6057      describePayload.data.browser.cancel_contract.route.path,
 6058      "/v1/browser/request/cancel"
 6059    );
 6060    assert.equal(describePayload.data.host_operations.enabled, true);
 6061    assert.equal(describePayload.data.host_operations.auth.header, "Authorization: Bearer <BAA_SHARED_TOKEN>");
 6062    assert.equal(describePayload.data.host_operations.auth.configured, true);
 6063    assert.deepEqual(
 6064      describePayload.data.browser.routes.map((route) => route.path),
 6065      [
 6066        "/v1/browser",
 6067        "/v1/browser/actions",
 6068        "/v1/browser/request",
 6069        "/v1/browser/request/cancel"
 6070      ]
 6071    );
 6072    assert.deepEqual(
 6073      describePayload.data.browser.request_contract.supported_platforms,
 6074      ["claude", "chatgpt", "gemini"]
 6075    );
 6076    assert.deepEqual(
 6077      describePayload.data.browser.action_contract.supported_platforms,
 6078      ["claude", "chatgpt"]
 6079    );
 6080    const legacyClaudeOpen = describePayload.data.browser.legacy_routes.find(
 6081      (route) => route.path === "/v1/browser/claude/open"
 6082    );
 6083    assert.equal(legacyClaudeOpen.lifecycle, "legacy");
 6084    assert.equal(legacyClaudeOpen.legacy_replacement_path, "/v1/browser/actions");
 6085    assert.deepEqual(
 6086      describePayload.data.browser.legacy_helper_platforms,
 6087      ["claude", "chatgpt", "gemini"]
 6088    );
 6089    assert.ok(
 6090      describePayload.data.browser.legacy_routes.find(
 6091        (route) => route.path === "/v1/browser/chatgpt/send"
 6092      )
 6093    );
 6094    assert.ok(
 6095      describePayload.data.browser.legacy_routes.find(
 6096        (route) => route.path === "/v1/browser/gemini/current"
 6097      )
 6098    );
 6099    assert.doesNotMatch(JSON.stringify(describePayload.data.codex.routes), /\/v1\/codex\/runs/u);
 6100    assert.doesNotMatch(JSON.stringify(describePayload.data.capabilities.read_endpoints), /\/v1\/runs/u);
 6101
 6102    const businessDescribeResponse = await handleConductorHttpRequest(
 6103      {
 6104        method: "GET",
 6105        path: "/describe/business"
 6106      },
 6107      versionedLocalApiContext
 6108    );
 6109    assert.equal(businessDescribeResponse.status, 200);
 6110    const businessDescribePayload = parseJsonBody(businessDescribeResponse);
 6111    assert.equal(businessDescribePayload.data.surface, "business");
 6112    assert.match(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/tasks/u);
 6113    assert.match(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/codex/u);
 6114    assert.match(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/browser\/request/u);
 6115    assert.match(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/browser\/request\/cancel/u);
 6116    assert.match(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/browser\/claude\/current/u);
 6117    assert.match(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/browser\/chatgpt\/send/u);
 6118    assert.match(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/browser\/chatgpt\/current/u);
 6119    assert.match(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/browser\/gemini\/send/u);
 6120    assert.match(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/browser\/gemini\/current/u);
 6121    assert.doesNotMatch(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/browser\/actions/u);
 6122    assert.doesNotMatch(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/browser\/claude\/open/u);
 6123    assert.doesNotMatch(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/system\/pause/u);
 6124    assert.doesNotMatch(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/exec"/u);
 6125    assert.doesNotMatch(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/runs/u);
 6126    assert.equal(businessDescribePayload.data.codex.backend, "independent_codexd");
 6127    assert.equal(businessDescribePayload.data.browser.request_contract.route.path, "/v1/browser/request");
 6128    assert.deepEqual(
 6129      businessDescribePayload.data.browser.request_contract.supported_platforms,
 6130      ["claude", "chatgpt", "gemini"]
 6131    );
 6132    assert.match(
 6133      JSON.stringify(businessDescribePayload.data.browser.request_contract.supported_response_modes),
 6134      /"sse"/u
 6135    );
 6136
 6137    const controlDescribeResponse = await handleConductorHttpRequest(
 6138      {
 6139        method: "GET",
 6140        path: "/describe/control"
 6141      },
 6142      versionedLocalApiContext
 6143    );
 6144    assert.equal(controlDescribeResponse.status, 200);
 6145    const controlDescribePayload = parseJsonBody(controlDescribeResponse);
 6146    assert.equal(controlDescribePayload.data.surface, "control");
 6147    assert.match(JSON.stringify(controlDescribePayload.data.endpoints), /\/v1\/browser/u);
 6148    assert.match(JSON.stringify(controlDescribePayload.data.endpoints), /\/v1\/browser\/actions/u);
 6149    assert.match(JSON.stringify(controlDescribePayload.data.endpoints), /\/v1\/browser\/claude\/open/u);
 6150    assert.match(JSON.stringify(controlDescribePayload.data.endpoints), /\/v1\/system\/pause/u);
 6151    assert.match(JSON.stringify(controlDescribePayload.data.endpoints), /\/v1\/exec/u);
 6152    assert.doesNotMatch(JSON.stringify(controlDescribePayload.data.endpoints), /\/v1\/tasks/u);
 6153    assert.equal(controlDescribePayload.data.codex.target_base_url, codexd.baseUrl);
 6154    assert.equal(controlDescribePayload.data.browser.action_contract.route.path, "/v1/browser/actions");
 6155    assert.equal(controlDescribePayload.data.browser.action_contract.response_body.accepted, "布尔值;插件是否接受了该动作。");
 6156    assert.deepEqual(
 6157      controlDescribePayload.data.browser.action_contract.supported_platforms,
 6158      ["claude", "chatgpt"]
 6159    );
 6160    assert.equal(controlDescribePayload.data.host_operations.auth.header, "Authorization: Bearer <BAA_SHARED_TOKEN>");
 6161
 6162    const healthResponse = await handleConductorHttpRequest(
 6163      {
 6164        method: "GET",
 6165        path: "/health"
 6166      },
 6167      versionedLocalApiContext
 6168    );
 6169    assert.equal(healthResponse.status, 200);
 6170    assert.equal(parseJsonBody(healthResponse).data.status, "ok");
 6171
 6172    const capabilitiesResponse = await handleConductorHttpRequest(
 6173      {
 6174        method: "GET",
 6175        path: "/v1/capabilities"
 6176      },
 6177      versionedLocalApiContext
 6178    );
 6179    assert.equal(capabilitiesResponse.status, 200);
 6180    const capabilitiesPayload = parseJsonBody(capabilitiesResponse);
 6181    assert.equal(capabilitiesPayload.data.codex.backend, "independent_codexd");
 6182    assert.equal(capabilitiesPayload.data.browser.route_prefix, "/v1/browser");
 6183    assert.match(JSON.stringify(capabilitiesPayload.data.read_endpoints), /\/v1\/codex/u);
 6184    assert.match(JSON.stringify(capabilitiesPayload.data.read_endpoints), /\/v1\/browser/u);
 6185    assert.match(JSON.stringify(capabilitiesPayload.data.write_endpoints), /\/v1\/browser\/actions/u);
 6186    assert.match(JSON.stringify(capabilitiesPayload.data.write_endpoints), /\/v1\/browser\/request/u);
 6187    assert.doesNotMatch(JSON.stringify(capabilitiesPayload.data.read_endpoints), /\/v1\/runs/u);
 6188
 6189    const browserStatusResponse = await handleConductorHttpRequest(
 6190      {
 6191        method: "GET",
 6192        path: "/v1/browser"
 6193      },
 6194      localApiContext
 6195    );
 6196    assert.equal(browserStatusResponse.status, 200);
 6197    const browserStatusPayload = parseJsonBody(browserStatusResponse);
 6198    assert.equal(browserStatusPayload.data.bridge.client_count, 1);
 6199    assert.equal(browserStatusPayload.data.current_client.client_id, "firefox-claude");
 6200    assert.equal(browserStatusPayload.data.current_client.shell_runtime[0].platform, "claude");
 6201    assert.equal(browserStatusPayload.data.current_client.last_action_result.action, "plugin_status");
 6202    assert.equal(browserStatusPayload.data.claude.ready, true);
 6203    assert.equal(browserStatusPayload.data.claude.shell_runtime.platform, "claude");
 6204    assert.equal(browserStatusPayload.data.records[0].live.credentials.account, "ops@example.com");
 6205    assert.equal(browserStatusPayload.data.records[0].live.shell_runtime.platform, "claude");
 6206    assert.equal(browserStatusPayload.data.records[0].status, "fresh");
 6207
 6208    const browserActionsResponse = await handleConductorHttpRequest(
 6209      {
 6210        body: JSON.stringify({
 6211          action: "tab_open",
 6212          client_id: "firefox-claude",
 6213          platform: "claude"
 6214        }),
 6215        method: "POST",
 6216        path: "/v1/browser/actions"
 6217      },
 6218      localApiContext
 6219    );
 6220    assert.equal(browserActionsResponse.status, 200);
 6221    const browserActionPayload = parseJsonBody(browserActionsResponse);
 6222    assert.equal(browserActionPayload.data.action, "tab_open");
 6223    assert.equal(browserActionPayload.data.accepted, true);
 6224    assert.equal(browserActionPayload.data.completed, true);
 6225    assert.equal(browserActionPayload.data.shell_runtime[0].platform, "claude");
 6226
 6227    const browserPluginActionResponse = await handleConductorHttpRequest(
 6228      {
 6229        body: JSON.stringify({
 6230          action: "plugin_status",
 6231          client_id: "firefox-claude"
 6232        }),
 6233        method: "POST",
 6234        path: "/v1/browser/actions"
 6235      },
 6236      localApiContext
 6237    );
 6238    assert.equal(browserPluginActionResponse.status, 200);
 6239    const browserPluginActionPayload = parseJsonBody(browserPluginActionResponse);
 6240    assert.equal(browserPluginActionPayload.data.action, "plugin_status");
 6241    assert.equal(browserPluginActionPayload.data.result.platform_count, 1);
 6242
 6243    const browserReconnectActionResponse = await handleConductorHttpRequest(
 6244      {
 6245        body: JSON.stringify({
 6246          action: "ws_reconnect",
 6247          client_id: "firefox-claude",
 6248          disconnect_ms: 2500,
 6249          repeat_count: 3,
 6250          repeat_interval_ms: 750
 6251        }),
 6252        method: "POST",
 6253        path: "/v1/browser/actions"
 6254      },
 6255      localApiContext
 6256    );
 6257    assert.equal(browserReconnectActionResponse.status, 200);
 6258    const browserReconnectActionPayload = parseJsonBody(browserReconnectActionResponse);
 6259    assert.equal(browserReconnectActionPayload.data.action, "ws_reconnect");
 6260    const browserReconnectCall = browser.calls[browser.calls.length - 1];
 6261    assert.equal(browserReconnectCall.kind, "dispatchPluginAction");
 6262    assert.equal(browserReconnectCall.disconnectMs, 2500);
 6263    assert.equal(browserReconnectCall.repeatCount, 3);
 6264    assert.equal(browserReconnectCall.repeatIntervalMs, 750);
 6265
 6266    const browserRequestResponse = await handleConductorHttpRequest(
 6267      {
 6268        body: JSON.stringify({
 6269          platform: "claude",
 6270          prompt: "hello generic browser request"
 6271        }),
 6272        method: "POST",
 6273        path: "/v1/browser/request"
 6274      },
 6275      localApiContext
 6276    );
 6277    assert.equal(browserRequestResponse.status, 200);
 6278    const browserRequestPayload = parseJsonBody(browserRequestResponse);
 6279    assert.equal(browserRequestPayload.data.organization.organization_id, "org-1");
 6280    assert.equal(browserRequestPayload.data.conversation.conversation_id, "conv-1");
 6281    assert.equal(browserRequestPayload.data.request_mode, "claude_prompt");
 6282    assert.equal(
 6283      browserRequestPayload.data.proxy.path,
 6284      "/api/organizations/org-1/chat_conversations/conv-1/completion"
 6285    );
 6286    assert.equal(browserRequestPayload.data.policy.target_client_id, "firefox-claude");
 6287
 6288    const bufferedSseResponse = await handleConductorHttpRequest(
 6289      {
 6290        body: JSON.stringify({
 6291          platform: "claude",
 6292          method: "GET",
 6293          path: "/api/stream-buffered-smoke",
 6294          requestId: "browser-buffered-sse-123"
 6295        }),
 6296        method: "POST",
 6297        path: "/v1/browser/request"
 6298      },
 6299      localApiContext
 6300    );
 6301    assert.equal(bufferedSseResponse.status, 200);
 6302    const bufferedSsePayload = parseJsonBody(bufferedSseResponse);
 6303    assert.equal(bufferedSsePayload.data.request_mode, "api_request");
 6304    assert.equal(bufferedSsePayload.data.proxy.path, "/api/stream-buffered-smoke");
 6305    assert.equal(bufferedSsePayload.data.response.content_type, "text/event-stream");
 6306    assert.equal(bufferedSsePayload.data.response.events.length, 3);
 6307    assert.equal(bufferedSsePayload.data.response.events[0].event, "completion");
 6308    assert.equal(bufferedSsePayload.data.response.events[0].data.type, "completion");
 6309    assert.equal(bufferedSsePayload.data.response.events[0].data.completion, "Hello ");
 6310    assert.equal(bufferedSsePayload.data.response.events[1].data.completion, "world");
 6311    assert.equal(bufferedSsePayload.data.response.events[2].data.completion, "");
 6312    assert.equal(bufferedSsePayload.data.response.events[2].data.id, "chatcompl_01-buffered");
 6313    assert.equal(bufferedSsePayload.data.response.events[2].data.stop, "\n\nHuman:");
 6314    assert.equal(bufferedSsePayload.data.response.full_text, "Hello world");
 6315
 6316    const chatgptBufferedSseResponse = await handleConductorHttpRequest(
 6317      {
 6318        body: JSON.stringify({
 6319          platform: "chatgpt",
 6320          method: "GET",
 6321          path: "/backend-api/conversation-buffered-smoke",
 6322          requestId: "browser-chatgpt-buffered-sse-123"
 6323        }),
 6324        method: "POST",
 6325        path: "/v1/browser/request"
 6326      },
 6327      localApiContext
 6328    );
 6329    assert.equal(chatgptBufferedSseResponse.status, 200);
 6330    const chatgptBufferedSsePayload = parseJsonBody(chatgptBufferedSseResponse);
 6331    assert.equal(chatgptBufferedSsePayload.data.request_mode, "api_request");
 6332    assert.equal(chatgptBufferedSsePayload.data.proxy.path, "/backend-api/conversation-buffered-smoke");
 6333    assert.equal(chatgptBufferedSsePayload.data.response.content_type, "text/event-stream");
 6334    assert.equal(chatgptBufferedSsePayload.data.response.events[0].event, "message");
 6335    assert.equal(
 6336      chatgptBufferedSsePayload.data.response.events[0].data.message.content.parts[0],
 6337      "Buffered ChatGPT answer"
 6338    );
 6339    assert.equal(chatgptBufferedSsePayload.data.response.full_text, "Buffered ChatGPT answer");
 6340
 6341    const chatgptBufferedResponse = await handleConductorHttpRequest(
 6342      {
 6343        body: JSON.stringify({
 6344          platform: "chatgpt",
 6345          method: "GET",
 6346          path: "/backend-api/models",
 6347          requestId: "browser-chatgpt-buffered-123"
 6348        }),
 6349        method: "POST",
 6350        path: "/v1/browser/request"
 6351      },
 6352      localApiContext
 6353    );
 6354    assert.equal(chatgptBufferedResponse.status, 200);
 6355    const chatgptBufferedPayload = parseJsonBody(chatgptBufferedResponse);
 6356    assert.equal(chatgptBufferedPayload.data.request_mode, "api_request");
 6357    assert.equal(chatgptBufferedPayload.data.proxy.path, "/backend-api/models");
 6358    assert.equal(chatgptBufferedPayload.data.proxy.request_id, "browser-chatgpt-buffered-123");
 6359    assert.equal(chatgptBufferedPayload.data.response.models[0].slug, "gpt-5.4");
 6360    assert.equal(chatgptBufferedPayload.data.policy.platform, "chatgpt");
 6361
 6362    const geminiBufferedResponse = await handleConductorHttpRequest(
 6363      {
 6364        body: JSON.stringify({
 6365          conversationId: "conv-gemini-current",
 6366          platform: "gemini",
 6367          prompt: "hello generic gemini relay",
 6368          requestId: "browser-gemini-buffered-123",
 6369          path: "/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"
 6370        }),
 6371        method: "POST",
 6372        path: "/v1/browser/request"
 6373      },
 6374      localApiContext
 6375    );
 6376    assert.equal(geminiBufferedResponse.status, 200);
 6377    const geminiBufferedPayload = parseJsonBody(geminiBufferedResponse);
 6378    assert.equal(geminiBufferedPayload.data.request_mode, "api_request");
 6379    assert.equal(
 6380      geminiBufferedPayload.data.proxy.path,
 6381      "/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"
 6382    );
 6383    assert.equal(geminiBufferedPayload.data.proxy.request_id, "browser-gemini-buffered-123");
 6384    assert.equal(geminiBufferedPayload.data.response.conversation_id, "conv-gemini-current");
 6385    assert.equal(geminiBufferedPayload.data.policy.platform, "gemini");
 6386    const geminiBufferedCall = browser.calls.find(
 6387      (entry) => entry.kind === "apiRequest" && entry.id === "browser-gemini-buffered-123"
 6388    );
 6389    assert.ok(geminiBufferedCall);
 6390    assert.equal(geminiBufferedCall.conversationId, "conv-gemini-current");
 6391
 6392    const chatgptLegacySendResponse = await handleConductorHttpRequest(
 6393      {
 6394        body: JSON.stringify({
 6395          prompt: "legacy helper chatgpt"
 6396        }),
 6397        method: "POST",
 6398        path: "/v1/browser/chatgpt/send"
 6399      },
 6400      localApiContext
 6401    );
 6402    assert.equal(chatgptLegacySendResponse.status, 200);
 6403    const chatgptLegacySendPayload = parseJsonBody(chatgptLegacySendResponse);
 6404    assert.equal(chatgptLegacySendPayload.data.platform, "chatgpt");
 6405    assert.equal(chatgptLegacySendPayload.data.proxy.path, "/backend-api/conversation");
 6406
 6407    const chatgptLegacyCurrentResponse = await handleConductorHttpRequest(
 6408      {
 6409        method: "GET",
 6410        path: "/v1/browser/chatgpt/current"
 6411      },
 6412      localApiContext
 6413    );
 6414    assert.equal(chatgptLegacyCurrentResponse.status, 200);
 6415    const chatgptLegacyCurrentPayload = parseJsonBody(chatgptLegacyCurrentResponse);
 6416    assert.equal(chatgptLegacyCurrentPayload.data.conversation.conversation_id, "conv-chatgpt-current");
 6417    assert.equal(
 6418      chatgptLegacyCurrentPayload.data.proxy.path,
 6419      "/backend-api/conversation/conv-chatgpt-current"
 6420    );
 6421
 6422    const geminiLegacySendResponse = await handleConductorHttpRequest(
 6423      {
 6424        body: JSON.stringify({
 6425          prompt: "legacy helper gemini"
 6426        }),
 6427        method: "POST",
 6428        path: "/v1/browser/gemini/send"
 6429      },
 6430      localApiContext
 6431    );
 6432    assert.equal(geminiLegacySendResponse.status, 200);
 6433    const geminiLegacySendPayload = parseJsonBody(geminiLegacySendResponse);
 6434    assert.equal(geminiLegacySendPayload.data.platform, "gemini");
 6435    assert.equal(
 6436      geminiLegacySendPayload.data.proxy.path,
 6437      "/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"
 6438    );
 6439
 6440    const geminiLegacyCurrentResponse = await handleConductorHttpRequest(
 6441      {
 6442        method: "GET",
 6443        path: "/v1/browser/gemini/current"
 6444      },
 6445      localApiContext
 6446    );
 6447    assert.equal(geminiLegacyCurrentResponse.status, 200);
 6448    const geminiLegacyCurrentPayload = parseJsonBody(geminiLegacyCurrentResponse);
 6449    assert.equal(geminiLegacyCurrentPayload.data.conversation.conversation_id, "conv-gemini-current");
 6450    assert.equal(geminiLegacyCurrentPayload.data.proxy, null);
 6451
 6452    const browserStreamResponse = await handleConductorHttpRequest(
 6453      {
 6454        body: JSON.stringify({
 6455          platform: "claude",
 6456          prompt: "hello browser stream",
 6457          responseMode: "sse",
 6458          requestId: "browser-stream-123"
 6459        }),
 6460        method: "POST",
 6461        path: "/v1/browser/request"
 6462      },
 6463      localApiContext
 6464    );
 6465    assert.equal(browserStreamResponse.status, 200);
 6466    assert.equal(browserStreamResponse.headers["content-type"], "text/event-stream; charset=utf-8");
 6467    const browserStreamText = await readResponseBodyText(browserStreamResponse);
 6468    const browserStreamFrames = parseSseFrames(browserStreamText);
 6469    assert.deepEqual(
 6470      browserStreamFrames.map((frame) => frame.event),
 6471      ["stream_open", "stream_event", "stream_end"]
 6472    );
 6473    assert.equal(browserStreamFrames[0].data.request_id, "browser-stream-123");
 6474    assert.equal(browserStreamFrames[1].data.seq, 1);
 6475    assert.equal(browserStreamFrames[2].data.stream_id, "browser-stream-123");
 6476
 6477    const browserRequestCancelResponse = await handleConductorHttpRequest(
 6478      {
 6479        body: JSON.stringify({
 6480          platform: "claude",
 6481          request_id: "browser-stream-123"
 6482        }),
 6483        method: "POST",
 6484        path: "/v1/browser/request/cancel"
 6485      },
 6486      localApiContext
 6487    );
 6488    assert.equal(browserRequestCancelResponse.status, 200);
 6489    const browserRequestCancelPayload = parseJsonBody(browserRequestCancelResponse);
 6490    assert.equal(browserRequestCancelPayload.data.status, "cancel_requested");
 6491    assert.equal(browserRequestCancelPayload.data.type, "request_cancel");
 6492
 6493    const browserOpenResponse = await handleConductorHttpRequest(
 6494      {
 6495        body: JSON.stringify({
 6496          client_id: "firefox-claude"
 6497        }),
 6498        method: "POST",
 6499        path: "/v1/browser/claude/open"
 6500      },
 6501      localApiContext
 6502    );
 6503    assert.equal(browserOpenResponse.status, 200);
 6504    assert.equal(parseJsonBody(browserOpenResponse).data.client_id, "firefox-claude");
 6505
 6506    const controllersResponse = await handleConductorHttpRequest(
 6507      {
 6508        method: "GET",
 6509        path: "/v1/controllers?limit=5"
 6510      },
 6511      localApiContext
 6512    );
 6513    const controllersPayload = parseJsonBody(controllersResponse);
 6514    assert.equal(controllersPayload.data.count, 1);
 6515    assert.equal(controllersPayload.data.controllers[0].controller_id, "mini-main");
 6516    assert.equal(controllersPayload.data.controllers[0].is_leader, true);
 6517
 6518    const tasksResponse = await handleConductorHttpRequest(
 6519      {
 6520        method: "GET",
 6521        path: "/v1/tasks?status=running&limit=5"
 6522      },
 6523      localApiContext
 6524    );
 6525    const tasksPayload = parseJsonBody(tasksResponse);
 6526    assert.equal(tasksPayload.data.count, 1);
 6527    assert.equal(tasksPayload.data.tasks[0].task_id, "task_demo");
 6528
 6529    const taskResponse = await handleConductorHttpRequest(
 6530      {
 6531        method: "GET",
 6532        path: "/v1/tasks/task_demo"
 6533      },
 6534      localApiContext
 6535    );
 6536    assert.equal(parseJsonBody(taskResponse).data.task_id, "task_demo");
 6537
 6538    const taskLogsResponse = await handleConductorHttpRequest(
 6539      {
 6540        method: "GET",
 6541        path: "/v1/tasks/task_demo/logs?limit=10"
 6542      },
 6543      localApiContext
 6544    );
 6545    const taskLogsPayload = parseJsonBody(taskLogsResponse);
 6546    assert.equal(taskLogsPayload.data.task_id, "task_demo");
 6547    assert.equal(taskLogsPayload.data.entries.length, 1);
 6548    assert.equal(taskLogsPayload.data.entries[0].message, "hello from local api");
 6549
 6550    const runsResponse = await handleConductorHttpRequest(
 6551      {
 6552        method: "GET",
 6553        path: "/v1/runs?limit=5"
 6554      },
 6555      localApiContext
 6556    );
 6557    const runsPayload = parseJsonBody(runsResponse);
 6558    assert.equal(runsPayload.data.count, 1);
 6559    assert.equal(runsPayload.data.runs[0].run_id, "run_demo");
 6560
 6561    const codexStatusResponse = await handleConductorHttpRequest(
 6562      {
 6563        method: "GET",
 6564        path: "/v1/codex"
 6565      },
 6566      localApiContext
 6567    );
 6568    assert.equal(codexStatusResponse.status, 200);
 6569    const codexStatusPayload = parseJsonBody(codexStatusResponse);
 6570    assert.equal(codexStatusPayload.data.backend, "independent_codexd");
 6571    assert.equal(codexStatusPayload.data.proxy.target_base_url, codexd.baseUrl);
 6572    assert.equal(codexStatusPayload.data.sessions.count, 1);
 6573    assert.equal(codexStatusPayload.data.sessions.active_count, 1);
 6574    assert.doesNotMatch(JSON.stringify(codexStatusPayload.data.routes), /\/v1\/codex\/runs/u);
 6575
 6576    const codexSessionsResponse = await handleConductorHttpRequest(
 6577      {
 6578        method: "GET",
 6579        path: "/v1/codex/sessions"
 6580      },
 6581      localApiContext
 6582    );
 6583    assert.equal(codexSessionsResponse.status, 200);
 6584    const codexSessionsPayload = parseJsonBody(codexSessionsResponse);
 6585    assert.equal(codexSessionsPayload.data.sessions.length, 1);
 6586    assert.equal(codexSessionsPayload.data.sessions[0].sessionId, "session-demo");
 6587
 6588    const codexSessionReadResponse = await handleConductorHttpRequest(
 6589      {
 6590        method: "GET",
 6591        path: "/v1/codex/sessions/session-demo"
 6592      },
 6593      localApiContext
 6594    );
 6595    assert.equal(codexSessionReadResponse.status, 200);
 6596    const codexSessionReadPayload = parseJsonBody(codexSessionReadResponse);
 6597    assert.equal(codexSessionReadPayload.data.session.sessionId, "session-demo");
 6598
 6599    const codexSessionCreateResponse = await handleConductorHttpRequest(
 6600      {
 6601        body: JSON.stringify({
 6602          cwd: "/Users/george/code/baa-conductor",
 6603          model: "gpt-5.4",
 6604          purpose: "duplex"
 6605        }),
 6606        method: "POST",
 6607        path: "/v1/codex/sessions"
 6608      },
 6609      localApiContext
 6610    );
 6611    assert.equal(codexSessionCreateResponse.status, 201);
 6612    const codexSessionCreatePayload = parseJsonBody(codexSessionCreateResponse);
 6613    assert.equal(codexSessionCreatePayload.data.session.sessionId, "session-2");
 6614    assert.equal(codexSessionCreatePayload.data.session.purpose, "duplex");
 6615
 6616    const codexTurnResponse = await handleConductorHttpRequest(
 6617      {
 6618        body: JSON.stringify({
 6619          input: "Summarize pending work.",
 6620          sessionId: "session-demo"
 6621        }),
 6622        method: "POST",
 6623        path: "/v1/codex/turn"
 6624      },
 6625      localApiContext
 6626    );
 6627    assert.equal(codexTurnResponse.status, 202);
 6628    const codexTurnPayload = parseJsonBody(codexTurnResponse);
 6629    assert.equal(codexTurnPayload.data.accepted, true);
 6630    assert.equal(codexTurnPayload.data.turnId, "turn-created");
 6631
 6632    const browserSendResponse = await handleConductorHttpRequest(
 6633      {
 6634        body: JSON.stringify({
 6635          prompt: "hello claude"
 6636        }),
 6637        method: "POST",
 6638        path: "/v1/browser/claude/send"
 6639      },
 6640      localApiContext
 6641    );
 6642    assert.equal(browserSendResponse.status, 200);
 6643    const browserSendPayload = parseJsonBody(browserSendResponse);
 6644    assert.equal(browserSendPayload.data.organization.organization_id, "org-1");
 6645    assert.equal(browserSendPayload.data.conversation.conversation_id, "conv-1");
 6646    assert.equal(browserSendPayload.data.proxy.path, "/api/organizations/org-1/chat_conversations/conv-1/completion");
 6647    assert.equal(browserSendPayload.data.response.accepted, true);
 6648
 6649    const browserCurrentResponse = await handleConductorHttpRequest(
 6650      {
 6651        method: "GET",
 6652        path: "/v1/browser/claude/current"
 6653      },
 6654      localApiContext
 6655    );
 6656    assert.equal(browserCurrentResponse.status, 200);
 6657    const browserCurrentPayload = parseJsonBody(browserCurrentResponse);
 6658    assert.equal(browserCurrentPayload.data.organization.organization_id, "org-1");
 6659    assert.equal(browserCurrentPayload.data.conversation.conversation_id, "conv-1");
 6660    assert.equal(browserCurrentPayload.data.messages.length, 2);
 6661    assert.equal(browserCurrentPayload.data.messages[0].role, "user");
 6662    assert.equal(browserCurrentPayload.data.messages[1].role, "assistant");
 6663
 6664    const browserReloadResponse = await handleConductorHttpRequest(
 6665      {
 6666        body: JSON.stringify({
 6667          reason: "integration_test"
 6668        }),
 6669        method: "POST",
 6670        path: "/v1/browser/claude/reload"
 6671      },
 6672      localApiContext
 6673    );
 6674    assert.equal(browserReloadResponse.status, 200);
 6675    assert.equal(parseJsonBody(browserReloadResponse).data.type, "reload");
 6676
 6677    const runResponse = await handleConductorHttpRequest(
 6678      {
 6679        method: "GET",
 6680        path: "/v1/runs/run_demo"
 6681      },
 6682      localApiContext
 6683    );
 6684    assert.equal(parseJsonBody(runResponse).data.run_id, "run_demo");
 6685
 6686    const pauseResponse = await handleConductorHttpRequest(
 6687      {
 6688        body: JSON.stringify({
 6689          reason: "human_clicked_pause",
 6690          requested_by: "test"
 6691        }),
 6692        method: "POST",
 6693        path: "/v1/system/pause"
 6694      },
 6695      localApiContext
 6696    );
 6697    assert.equal(pauseResponse.status, 200);
 6698    const pausePayload = parseJsonBody(pauseResponse);
 6699    assert.equal(pausePayload.data.mode, "paused");
 6700    const automationState = await repository.getAutomationState();
 6701    assert.equal(automationState?.mode, "paused");
 6702    assert.equal(pausePayload.data.updated_at, automationState?.updatedAt);
 6703    assert.equal(pausePayload.data.automation.updated_at, automationState?.updatedAt);
 6704
 6705    const missingTokenExecResponse = await handleConductorHttpRequest(
 6706      {
 6707        body: JSON.stringify({
 6708          command: "printf 'host-http-ok'",
 6709          cwd: hostOpsDir,
 6710          timeoutMs: 2000
 6711        }),
 6712        method: "POST",
 6713        path: "/v1/exec"
 6714      },
 6715      localApiContext
 6716    );
 6717    assert.equal(missingTokenExecResponse.status, 401);
 6718    const missingTokenExecPayload = parseJsonBody(missingTokenExecResponse);
 6719    assert.equal(missingTokenExecPayload.error, "unauthorized");
 6720
 6721    const invalidTokenReadResponse = await handleConductorHttpRequest(
 6722      {
 6723        body: JSON.stringify({
 6724          path: "notes/demo.txt",
 6725          cwd: hostOpsDir
 6726        }),
 6727        headers: {
 6728          authorization: "Bearer wrong-token"
 6729        },
 6730        method: "POST",
 6731        path: "/v1/files/read"
 6732      },
 6733      localApiContext
 6734    );
 6735    assert.equal(invalidTokenReadResponse.status, 401);
 6736    const invalidTokenReadPayload = parseJsonBody(invalidTokenReadResponse);
 6737    assert.equal(invalidTokenReadPayload.error, "unauthorized");
 6738
 6739    const writeResponse = await handleConductorHttpRequest(
 6740      {
 6741        body: JSON.stringify({
 6742          path: "notes/demo.txt",
 6743          cwd: hostOpsDir,
 6744          content: "hello from local host ops",
 6745          overwrite: false,
 6746          createParents: true
 6747        }),
 6748        headers: authorizedHeaders,
 6749        method: "POST",
 6750        path: "/v1/files/write"
 6751      },
 6752      localApiContext
 6753    );
 6754    assert.equal(writeResponse.status, 200);
 6755    const writePayload = parseJsonBody(writeResponse);
 6756    assert.equal(writePayload.data.ok, true);
 6757    assert.equal(writePayload.data.operation, "files/write");
 6758    assert.equal(writePayload.data.result.created, true);
 6759
 6760    const readResponse = await handleConductorHttpRequest(
 6761      {
 6762        body: JSON.stringify({
 6763          path: "notes/demo.txt",
 6764          cwd: hostOpsDir
 6765        }),
 6766        headers: authorizedHeaders,
 6767        method: "POST",
 6768        path: "/v1/files/read"
 6769      },
 6770      localApiContext
 6771    );
 6772    assert.equal(readResponse.status, 200);
 6773    const readPayload = parseJsonBody(readResponse);
 6774    assert.equal(readPayload.data.ok, true);
 6775    assert.equal(readPayload.data.result.content, "hello from local host ops");
 6776
 6777    const duplicateWriteResponse = await handleConductorHttpRequest(
 6778      {
 6779        body: JSON.stringify({
 6780          path: "notes/demo.txt",
 6781          cwd: hostOpsDir,
 6782          content: "should not overwrite",
 6783          overwrite: false
 6784        }),
 6785        headers: authorizedHeaders,
 6786        method: "POST",
 6787        path: "/v1/files/write"
 6788      },
 6789      localApiContext
 6790    );
 6791    assert.equal(duplicateWriteResponse.status, 200);
 6792    const duplicateWritePayload = parseJsonBody(duplicateWriteResponse);
 6793    assert.equal(duplicateWritePayload.data.ok, false);
 6794    assert.equal(duplicateWritePayload.data.error.code, "FILE_ALREADY_EXISTS");
 6795
 6796    const execResponse = await handleConductorHttpRequest(
 6797      {
 6798        body: JSON.stringify({
 6799          command: "printf 'host-http-ok'",
 6800          cwd: hostOpsDir,
 6801          timeoutMs: 2000
 6802        }),
 6803        headers: authorizedHeaders,
 6804        method: "POST",
 6805        path: "/v1/exec"
 6806      },
 6807      localApiContext
 6808    );
 6809    assert.equal(execResponse.status, 200);
 6810    const execPayload = parseJsonBody(execResponse);
 6811    assert.equal(execPayload.data.ok, true);
 6812    assert.equal(execPayload.data.operation, "exec");
 6813    assert.equal(execPayload.data.result.stdout, "host-http-ok");
 6814
 6815    const invalidExecResponse = await handleConductorHttpRequest(
 6816      {
 6817        body: JSON.stringify({
 6818          command: ["echo", "hello"]
 6819        }),
 6820        headers: authorizedHeaders,
 6821        method: "POST",
 6822        path: "/v1/exec"
 6823      },
 6824      localApiContext
 6825    );
 6826    assert.equal(invalidExecResponse.status, 200);
 6827    const invalidExecPayload = parseJsonBody(invalidExecResponse);
 6828    assert.equal(invalidExecPayload.data.ok, false);
 6829    assert.equal(invalidExecPayload.data.error.code, "INVALID_INPUT");
 6830    assertEmptyExecResultShape(invalidExecPayload.data.result);
 6831
 6832    const systemStateResponse = await handleConductorHttpRequest(
 6833      {
 6834        method: "GET",
 6835        path: "/v1/system/state"
 6836      },
 6837      localApiContext
 6838    );
 6839    assert.equal(parseJsonBody(systemStateResponse).data.mode, "paused");
 6840
 6841    const statusViewResponse = await handleConductorHttpRequest(
 6842      {
 6843        method: "GET",
 6844        path: "/v1/status"
 6845      },
 6846      localApiContext
 6847    );
 6848    assert.equal(statusViewResponse.status, 200);
 6849    const statusViewPayload = parseJsonBody(statusViewResponse);
 6850    assert.deepEqual(Object.keys(statusViewPayload).sort(), ["data", "ok"]);
 6851    assert.equal(statusViewPayload.data.mode, "paused");
 6852    assert.equal(statusViewPayload.data.queueDepth, 0);
 6853    assert.equal(statusViewPayload.data.activeRuns, 1);
 6854
 6855    const statusViewUiResponse = await handleConductorHttpRequest(
 6856      {
 6857        method: "GET",
 6858        path: "/v1/status/ui"
 6859      },
 6860      localApiContext
 6861    );
 6862    assert.equal(statusViewUiResponse.status, 200);
 6863    assert.equal(statusViewUiResponse.headers["content-type"], "text/html; charset=utf-8");
 6864    assert.match(statusViewUiResponse.body, /JSON endpoint: <strong>\/v1\/status<\/strong>/u);
 6865    assert.match(statusViewUiResponse.body, /HTML endpoint: <strong>\/v1\/status\/ui<\/strong>/u);
 6866  } finally {
 6867    await codexd.stop();
 6868    rmSync(hostOpsDir, {
 6869      force: true,
 6870      recursive: true
 6871    });
 6872  }
 6873
 6874  assert.deepEqual(
 6875    codexd.requests.map((request) => request.path),
 6876    [
 6877      "/v1/codexd/status",
 6878      "/v1/codexd/sessions",
 6879      "/v1/codexd/sessions/session-demo",
 6880      "/v1/codexd/sessions",
 6881      "/v1/codexd/turn"
 6882    ]
 6883  );
 6884  assert.equal(codexd.requests[3].body.model, "gpt-5.4");
 6885  assert.equal(codexd.requests[4].body.sessionId, "session-demo");
 6886  assert.deepEqual(
 6887    browser.calls.map((entry) => entry.kind === "apiRequest" ? `${entry.kind}:${entry.method}:${entry.path}` : `${entry.kind}:${entry.platform || "-"}`),
 6888    [
 6889      "openTab:claude",
 6890      "dispatchPluginAction:-",
 6891      "dispatchPluginAction:-",
 6892      "apiRequest:GET:/api/organizations",
 6893      "apiRequest:GET:/api/organizations/org-1/chat_conversations",
 6894      "apiRequest:POST:/api/organizations/org-1/chat_conversations/conv-1/completion",
 6895      "apiRequest:GET:/api/stream-buffered-smoke",
 6896      "apiRequest:GET:/backend-api/conversation-buffered-smoke",
 6897      "apiRequest:GET:/backend-api/models",
 6898      "apiRequest:POST:/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate",
 6899      "apiRequest:POST:/backend-api/conversation",
 6900      "apiRequest:GET:/backend-api/conversation/conv-chatgpt-current",
 6901      "apiRequest:POST:/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate",
 6902      "apiRequest:GET:/api/organizations",
 6903      "apiRequest:GET:/api/organizations/org-1/chat_conversations",
 6904      "streamRequest:claude",
 6905      "cancelApiRequest:claude",
 6906      "openTab:claude",
 6907      "apiRequest:GET:/api/organizations",
 6908      "apiRequest:GET:/api/organizations/org-1/chat_conversations",
 6909      "apiRequest:POST:/api/organizations/org-1/chat_conversations/conv-1/completion",
 6910      "apiRequest:GET:/api/organizations",
 6911      "apiRequest:GET:/api/organizations/org-1/chat_conversations",
 6912      "apiRequest:GET:/api/organizations/org-1/chat_conversations/conv-1",
 6913      "reload:claude"
 6914    ]
 6915  );
 6916});
 6917
 6918test("handleConductorHttpRequest returns a codexd-specific availability error when the proxy target is down", async () => {
 6919  const { repository, snapshot } = await createLocalApiFixture();
 6920  snapshot.codexd.localApiBase = "http://127.0.0.1:65535";
 6921
 6922  const response = await handleConductorHttpRequest(
 6923    {
 6924      method: "GET",
 6925      path: "/v1/codex"
 6926    },
 6927    {
 6928      codexdLocalApiBase: snapshot.codexd.localApiBase,
 6929      fetchImpl: async () => {
 6930        throw new Error("connect ECONNREFUSED");
 6931      },
 6932      repository,
 6933      snapshotLoader: () => snapshot
 6934    }
 6935  );
 6936
 6937  assert.equal(response.status, 503);
 6938  const payload = parseJsonBody(response);
 6939  assert.equal(payload.error, "codexd_unavailable");
 6940  assert.match(payload.message, /Independent codexd is unavailable/u);
 6941  assert.doesNotMatch(payload.message, /bridge/u);
 6942});
 6943
 6944test("handleConductorHttpRequest returns browser_request_timeout with timeout details for stalled browser proxy calls", async () => {
 6945  const { repository, snapshot } = await createLocalApiFixture();
 6946  const browser = createBrowserBridgeStub();
 6947  browser.context.browserBridge.apiRequest = async () => {
 6948    throw new FirefoxBridgeError(
 6949      "request_timeout",
 6950      'Firefox client "firefox-claude" did not respond to api_request "browser-timeout-1" within 50ms.',
 6951      {
 6952        clientId: "firefox-claude",
 6953        connectionId: "conn-firefox-claude",
 6954        requestId: "browser-timeout-1",
 6955        timeoutMs: 50
 6956      }
 6957    );
 6958  };
 6959
 6960  const response = await handleConductorHttpRequest(
 6961    {
 6962      body: JSON.stringify({
 6963        path: "/api/organizations",
 6964        platform: "claude",
 6965        timeoutMs: 50
 6966      }),
 6967      method: "POST",
 6968      path: "/v1/browser/request"
 6969    },
 6970    {
 6971      ...browser.context,
 6972      repository,
 6973      snapshotLoader: () => snapshot
 6974    }
 6975  );
 6976
 6977  assert.equal(response.status, 504);
 6978  const payload = parseJsonBody(response);
 6979  assert.equal(payload.error, "browser_request_timeout");
 6980  assert.equal(payload.details.error_code, "request_timeout");
 6981  assert.equal(payload.details.timeout_ms, 50);
 6982  assert.equal(payload.details.bridge_request_id, "browser-timeout-1");
 6983});
 6984
 6985test("handleConductorHttpRequest returns a clear 503 for Claude browser actions without an active browser client", async () => {
 6986  const { repository, snapshot } = await createLocalApiFixture();
 6987
 6988  const response = await handleConductorHttpRequest(
 6989    {
 6990      method: "GET",
 6991      path: "/v1/browser/claude/current"
 6992    },
 6993    {
 6994      browserStateLoader: () => ({
 6995        active_client_id: null,
 6996        active_connection_id: null,
 6997        client_count: 0,
 6998        clients: [],
 6999        ws_path: "/ws/browser",
 7000        ws_url: snapshot.controlApi.browserWsUrl
 7001      }),
 7002      repository,
 7003      snapshotLoader: () => snapshot
 7004    }
 7005  );
 7006
 7007  assert.equal(response.status, 503);
 7008  const payload = parseJsonBody(response);
 7009  assert.equal(payload.ok, false);
 7010  assert.equal(payload.error, "browser_bridge_unavailable");
 7011});
 7012
 7013test("BrowserRequestPolicyController times out target slot waiters and removes them from the queue", async () => {
 7014  const scheduler = createManualTimerScheduler();
 7015  const policy = new BrowserRequestPolicyController({
 7016    clearTimeoutImpl: scheduler.clearTimeout,
 7017    config: {
 7018      jitter: {
 7019        maxMs: 0,
 7020        minMs: 0,
 7021        muMs: 0,
 7022        sigmaMs: 0
 7023      }
 7024    },
 7025    now: scheduler.now,
 7026    setTimeoutImpl: scheduler.setTimeout
 7027  });
 7028
 7029  const leakedLease = await policy.beginRequest({
 7030    clientId: "firefox-claude",
 7031    platform: "claude"
 7032  }, "request-leaked");
 7033  const waitingLeasePromise = policy.beginRequest({
 7034    clientId: "firefox-claude",
 7035    platform: "claude"
 7036  }, "request-blocked");
 7037
 7038  await flushAsyncWork();
 7039  assert.equal(getPolicyTargetSnapshot(policy, "firefox-claude", "claude")?.waiting, 1);
 7040
 7041  scheduler.advanceBy(BROWSER_REQUEST_WAITER_TIMEOUT_MS);
 7042
 7043  await assert.rejects(waitingLeasePromise, (error) => {
 7044    assert.equal(error.code, "waiter_timeout");
 7045    assert.equal(error.details.wait_scope, "target_slot");
 7046    assert.equal(error.details.client_id, "firefox-claude");
 7047    assert.equal(error.details.platform, "claude");
 7048    return true;
 7049  });
 7050
 7051  assert.equal(getPolicyTargetSnapshot(policy, "firefox-claude", "claude")?.waiting, 0);
 7052  assert.equal(getPolicyTargetSnapshot(policy, "firefox-claude", "claude")?.inFlight, 1);
 7053
 7054  leakedLease.complete({
 7055    status: "cancelled"
 7056  });
 7057
 7058  assert.equal(getPolicyTargetSnapshot(policy, "firefox-claude", "claude")?.inFlight, 0);
 7059});
 7060
 7061test("BrowserRequestPolicyController sweeps stale in-flight leases and lets the next waiter proceed", async () => {
 7062  const scheduler = createManualTimerScheduler();
 7063  const policy = new BrowserRequestPolicyController({
 7064    clearTimeoutImpl: scheduler.clearTimeout,
 7065    config: {
 7066      jitter: {
 7067        maxMs: 0,
 7068        minMs: 0,
 7069        muMs: 0,
 7070        sigmaMs: 0
 7071      },
 7072      staleLease: {
 7073        idleMs: 60_000,
 7074        sweepIntervalMs: 10_000
 7075      }
 7076    },
 7077    now: scheduler.now,
 7078    setTimeoutImpl: scheduler.setTimeout
 7079  });
 7080
 7081  const leakedLease = await policy.beginRequest({
 7082    clientId: "firefox-claude",
 7083    platform: "claude"
 7084  }, "request-leaked");
 7085  const recoveredLeasePromise = policy.beginRequest({
 7086    clientId: "firefox-claude",
 7087    platform: "claude"
 7088  }, "request-recovered");
 7089
 7090  await flushAsyncWork();
 7091  assert.equal(getPolicyTargetSnapshot(policy, "firefox-claude", "claude")?.waiting, 1);
 7092
 7093  scheduler.advanceBy(60_000);
 7094  await flushAsyncWork();
 7095
 7096  const recoveredLease = await recoveredLeasePromise;
 7097  const targetSnapshot = getPolicyTargetSnapshot(policy, "firefox-claude", "claude");
 7098  assert.equal(recoveredLease.admission.requestId, "request-recovered");
 7099  assert.equal(targetSnapshot?.inFlight, 1);
 7100  assert.equal(targetSnapshot?.waiting, 0);
 7101  assert.equal(targetSnapshot?.staleSweepCount, 1);
 7102  assert.equal(targetSnapshot?.lastStaleSweepAt, 60_000);
 7103  assert.equal(targetSnapshot?.lastStaleSweepRequestId, "request-leaked");
 7104  assert.equal(targetSnapshot?.lastStaleSweepReason, "background_interval:lease_idle_timeout");
 7105
 7106  leakedLease.complete({
 7107    status: "cancelled"
 7108  });
 7109
 7110  assert.equal(getPolicyTargetSnapshot(policy, "firefox-claude", "claude")?.inFlight, 1);
 7111
 7112  recoveredLease.complete({
 7113    status: "success"
 7114  });
 7115
 7116  assert.equal(getPolicyTargetSnapshot(policy, "firefox-claude", "claude")?.inFlight, 0);
 7117});
 7118
 7119test("BrowserRequestPolicyController does not sweep healthy leases that keep reporting activity", async () => {
 7120  const scheduler = createManualTimerScheduler();
 7121  const policy = new BrowserRequestPolicyController({
 7122    clearTimeoutImpl: scheduler.clearTimeout,
 7123    config: {
 7124      jitter: {
 7125        maxMs: 0,
 7126        minMs: 0,
 7127        muMs: 0,
 7128        sigmaMs: 0
 7129      },
 7130      staleLease: {
 7131        idleMs: 60_000,
 7132        sweepIntervalMs: 10_000
 7133      }
 7134    },
 7135    now: scheduler.now,
 7136    setTimeoutImpl: scheduler.setTimeout
 7137  });
 7138
 7139  const healthyLease = await policy.beginRequest({
 7140    clientId: "firefox-claude",
 7141    platform: "claude"
 7142  }, "request-healthy");
 7143  const waitingLeasePromise = policy.beginRequest({
 7144    clientId: "firefox-claude",
 7145    platform: "claude"
 7146  }, "request-waiting");
 7147  let waitingSettled = false;
 7148
 7149  void waitingLeasePromise.then(
 7150    () => {
 7151      waitingSettled = true;
 7152    },
 7153    () => {
 7154      waitingSettled = true;
 7155    }
 7156  );
 7157
 7158  await flushAsyncWork();
 7159
 7160  for (let index = 0; index < 4; index += 1) {
 7161    scheduler.advanceBy(20_000);
 7162    await flushAsyncWork();
 7163    healthyLease.touch("stream_event");
 7164    assert.equal(waitingSettled, false);
 7165    assert.equal(getPolicyTargetSnapshot(policy, "firefox-claude", "claude")?.staleSweepCount, 0);
 7166  }
 7167
 7168  const snapshotBeforeComplete = getPolicyTargetSnapshot(policy, "firefox-claude", "claude");
 7169  assert.equal(snapshotBeforeComplete?.inFlight, 1);
 7170  assert.equal(snapshotBeforeComplete?.lastActivityReason, "stream_event");
 7171  assert.equal(waitingSettled, false);
 7172
 7173  healthyLease.complete({
 7174    status: "success"
 7175  });
 7176  await flushAsyncWork();
 7177
 7178  const waitingLease = await waitingLeasePromise;
 7179  assert.equal(waitingLease.admission.requestId, "request-waiting");
 7180  waitingLease.complete({
 7181    status: "cancelled"
 7182  });
 7183
 7184  assert.equal(getPolicyTargetSnapshot(policy, "firefox-claude", "claude")?.inFlight, 0);
 7185});
 7186
 7187test("BrowserRequestPolicyController times out platform admission waiters and releases their target slot", async () => {
 7188  const scheduler = createManualTimerScheduler();
 7189  const policy = new BrowserRequestPolicyController({
 7190    clearTimeoutImpl: scheduler.clearTimeout,
 7191    config: {
 7192      jitter: {
 7193        maxMs: 0,
 7194        minMs: 0,
 7195        muMs: 0,
 7196        sigmaMs: 0
 7197      },
 7198      rateLimit: {
 7199        requestsPerMinutePerPlatform: 0,
 7200        windowMs: BROWSER_REQUEST_WAITER_TIMEOUT_MS + 60_000
 7201      }
 7202    },
 7203    now: scheduler.now,
 7204    setTimeoutImpl: scheduler.setTimeout
 7205  });
 7206
 7207  const blockedAdmissionPromise = policy.beginRequest({
 7208    clientId: "firefox-claude-a",
 7209    platform: "claude"
 7210  }, "request-admit-blocker");
 7211
 7212  await flushAsyncWork();
 7213
 7214  const waitingLeasePromise = policy.beginRequest({
 7215    clientId: "firefox-claude-b",
 7216    platform: "claude"
 7217  }, "request-admit-blocked");
 7218
 7219  await flushAsyncWork();
 7220  assert.equal(getPolicyPlatformSnapshot(policy, "claude")?.waiting, 1);
 7221
 7222  scheduler.advanceBy(BROWSER_REQUEST_WAITER_TIMEOUT_MS);
 7223
 7224  await assert.rejects(waitingLeasePromise, (error) => {
 7225    assert.equal(error.code, "waiter_timeout");
 7226    assert.equal(error.details.wait_scope, "platform_admission");
 7227    assert.equal(error.details.client_id, "firefox-claude-b");
 7228    assert.equal(error.details.platform, "claude");
 7229    return true;
 7230  });
 7231
 7232  assert.equal(getPolicyPlatformSnapshot(policy, "claude")?.waiting, 0);
 7233  assert.equal(getPolicyTargetSnapshot(policy, "firefox-claude-b", "claude")?.inFlight, 0);
 7234
 7235  scheduler.advanceBy(60_000);
 7236  const delayedLease = await blockedAdmissionPromise;
 7237  delayedLease.complete({
 7238    status: "cancelled"
 7239  });
 7240
 7241  assert.equal(getPolicyTargetSnapshot(policy, "firefox-claude-a", "claude")?.inFlight, 0);
 7242});
 7243
 7244test("FirefoxCommandBroker clears stream timers after the stream ends", async () => {
 7245  const scheduler = createManualTimerScheduler();
 7246  const sentMessages = [];
 7247  const client = {
 7248    clientId: "firefox-claude",
 7249    connectedAt: 0,
 7250    connectionId: "conn-firefox-claude",
 7251    lastMessageAt: 0,
 7252    sendJson(payload) {
 7253      sentMessages.push(payload);
 7254      return true;
 7255    }
 7256  };
 7257  const broker = new FirefoxCommandBroker({
 7258    clearTimeoutImpl: scheduler.clearTimeout,
 7259    now: scheduler.now,
 7260    resolveActiveClient: () => client,
 7261    resolveClientById: (clientId) => (clientId === client.clientId ? client : null),
 7262    setTimeoutImpl: scheduler.setTimeout
 7263  });
 7264
 7265  const stream = broker.openApiStream({
 7266    method: "GET",
 7267    path: "/api/organizations",
 7268    platform: "claude"
 7269  }, {
 7270    clientId: "firefox-claude",
 7271    idleTimeoutMs: 1_000,
 7272    openTimeoutMs: 1_000,
 7273    requestId: "stream-finish-smoke",
 7274    streamId: "stream-finish-smoke"
 7275  });
 7276
 7277  assert.equal(sentMessages.length, 1);
 7278  assert.equal(sentMessages[0].type, "api_request");
 7279
 7280  assert.equal(
 7281    broker.handleStreamOpen("conn-firefox-claude", {
 7282      id: "stream-finish-smoke",
 7283      status: 200,
 7284      streamId: "stream-finish-smoke"
 7285    }),
 7286    true
 7287  );
 7288  assert.equal(
 7289    broker.handleStreamEnd("conn-firefox-claude", {
 7290      id: "stream-finish-smoke",
 7291      status: 200,
 7292      streamId: "stream-finish-smoke"
 7293    }),
 7294    true
 7295  );
 7296
 7297  const openEvent = await stream.next();
 7298  assert.equal(openEvent.done, false);
 7299  assert.equal(openEvent.value.type, "stream_open");
 7300
 7301  const endEvent = await stream.next();
 7302  assert.equal(endEvent.done, false);
 7303  assert.equal(endEvent.value.type, "stream_end");
 7304
 7305  const closedEvent = await stream.next();
 7306  assert.equal(closedEvent.done, true);
 7307  assert.equal(closedEvent.value, undefined);
 7308
 7309  scheduler.advanceBy(10_000);
 7310
 7311  assert.equal(sentMessages.length, 1);
 7312});
 7313
 7314test("handleConductorHttpRequest returns a clear 503 when a leaked browser request lease blocks the target slot", async () => {
 7315  const { repository, sharedToken, snapshot } = await createLocalApiFixture();
 7316  const browser = createBrowserBridgeStub();
 7317  const scheduler = createManualTimerScheduler();
 7318  const policy = new BrowserRequestPolicyController({
 7319    clearTimeoutImpl: scheduler.clearTimeout,
 7320    config: {
 7321      jitter: {
 7322        maxMs: 0,
 7323        minMs: 0,
 7324        muMs: 0,
 7325        sigmaMs: 0
 7326      }
 7327    },
 7328    now: scheduler.now,
 7329    setTimeoutImpl: scheduler.setTimeout
 7330  });
 7331
 7332  const leakedLease = await policy.beginRequest({
 7333    clientId: "firefox-claude",
 7334    platform: "claude"
 7335  }, "request-leaked");
 7336
 7337  const responsePromise = handleConductorHttpRequest(
 7338    {
 7339      body: JSON.stringify({
 7340        platform: "claude",
 7341        prompt: "blocked browser request"
 7342      }),
 7343      method: "POST",
 7344      path: "/v1/browser/request"
 7345    },
 7346    {
 7347      ...browser.context,
 7348      browserRequestPolicy: policy,
 7349      repository,
 7350      sharedToken,
 7351      snapshotLoader: () => snapshot
 7352    }
 7353  );
 7354
 7355  await flushAsyncWork();
 7356  scheduler.advanceBy(BROWSER_REQUEST_WAITER_TIMEOUT_MS);
 7357
 7358  const response = await responsePromise;
 7359  assert.equal(response.status, 503);
 7360  const payload = parseJsonBody(response);
 7361  assert.equal(payload.ok, false);
 7362  assert.equal(payload.error, "browser_risk_limited");
 7363  assert.match(payload.message, /could not schedule browser request/u);
 7364  assert.equal(payload.details.error_code, "waiter_timeout");
 7365  assert.equal(payload.details.wait_scope, "target_slot");
 7366
 7367  leakedLease.complete({
 7368    status: "cancelled"
 7369  });
 7370});
 7371
 7372test("handleConductorHttpRequest exposes stale lease sweep diagnostics in /v1/browser", async () => {
 7373  const { repository, snapshot } = await createLocalApiFixture();
 7374  const browser = createBrowserBridgeStub();
 7375  const scheduler = createManualTimerScheduler();
 7376  const policy = new BrowserRequestPolicyController({
 7377    clearTimeoutImpl: scheduler.clearTimeout,
 7378    config: {
 7379      jitter: {
 7380        maxMs: 0,
 7381        minMs: 0,
 7382        muMs: 0,
 7383        sigmaMs: 0
 7384      },
 7385      staleLease: {
 7386        idleMs: 60_000,
 7387        sweepIntervalMs: 10_000
 7388      }
 7389    },
 7390    now: scheduler.now,
 7391    setTimeoutImpl: scheduler.setTimeout
 7392  });
 7393
 7394  await policy.beginRequest({
 7395    clientId: "firefox-claude",
 7396    platform: "claude"
 7397  }, "request-leaked");
 7398  const recoveredLeasePromise = policy.beginRequest({
 7399    clientId: "firefox-claude",
 7400    platform: "claude"
 7401  }, "request-recovered");
 7402
 7403  await flushAsyncWork();
 7404  scheduler.advanceBy(60_000);
 7405  await flushAsyncWork();
 7406
 7407  const recoveredLease = await recoveredLeasePromise;
 7408  recoveredLease.touch("stream_event");
 7409
 7410  const response = await handleConductorHttpRequest(
 7411    {
 7412      method: "GET",
 7413      path: "/v1/browser"
 7414    },
 7415    {
 7416      ...browser.context,
 7417      browserRequestPolicy: policy,
 7418      repository,
 7419      snapshotLoader: () => snapshot
 7420    }
 7421  );
 7422
 7423  assert.equal(response.status, 200);
 7424  const payload = parseJsonBody(response);
 7425  assert.equal(payload.data.policy.defaults.stale_lease.idle_ms, 60_000);
 7426  assert.equal(payload.data.policy.defaults.stale_lease.sweep_interval_ms, 10_000);
 7427  assert.equal(payload.data.policy.targets[0].client_id, "firefox-claude");
 7428  assert.equal(payload.data.policy.targets[0].platform, "claude");
 7429  assert.equal(payload.data.policy.targets[0].in_flight, 1);
 7430  assert.equal(payload.data.policy.targets[0].last_activity_at, 60_000);
 7431  assert.equal(payload.data.policy.targets[0].last_activity_reason, "stream_event");
 7432  assert.equal(payload.data.policy.targets[0].stale_sweep_count, 1);
 7433  assert.equal(payload.data.policy.targets[0].last_stale_sweep_at, 60_000);
 7434  assert.equal(payload.data.policy.targets[0].last_stale_sweep_idle_ms, 60_000);
 7435  assert.equal(
 7436    payload.data.policy.targets[0].last_stale_sweep_reason,
 7437    "background_interval:lease_idle_timeout"
 7438  );
 7439  assert.equal(payload.data.policy.targets[0].last_stale_sweep_request_id, "request-leaked");
 7440
 7441  recoveredLease.complete({
 7442    status: "cancelled"
 7443  });
 7444});
 7445
 7446test("BrowserRequestPolicyController restores persisted circuit state from artifact.db", async () => {
 7447  const scheduler = createManualTimerScheduler();
 7448
 7449  await withArtifactStoreFixture(async ({ artifactStore }) => {
 7450    const persistence = createArtifactStoreBrowserRequestPolicyPersistence(artifactStore, {
 7451      now: scheduler.now
 7452    });
 7453    const firstPolicy = new BrowserRequestPolicyController({
 7454      clearTimeoutImpl: scheduler.clearTimeout,
 7455      config: {
 7456        circuitBreaker: {
 7457          failureThreshold: 1,
 7458          openMs: 60_000
 7459        },
 7460        jitter: {
 7461          maxMs: 0,
 7462          minMs: 0,
 7463          muMs: 0,
 7464          sigmaMs: 0
 7465        }
 7466      },
 7467      now: scheduler.now,
 7468      persistence,
 7469      setTimeoutImpl: scheduler.setTimeout
 7470    });
 7471
 7472    await firstPolicy.initialize();
 7473
 7474    const failedLease = await firstPolicy.beginRequest({
 7475      clientId: "firefox-claude",
 7476      platform: "claude"
 7477    }, "request-persisted-circuit");
 7478
 7479    failedLease.complete({
 7480      code: "upstream_429",
 7481      status: "failure"
 7482    });
 7483    await firstPolicy.flush();
 7484
 7485    const persistedState = await artifactStore.getBrowserRequestPolicyState("global");
 7486    assert.ok(persistedState);
 7487
 7488    const restartedPolicy = new BrowserRequestPolicyController({
 7489      clearTimeoutImpl: scheduler.clearTimeout,
 7490      config: {
 7491        circuitBreaker: {
 7492          failureThreshold: 1,
 7493          openMs: 60_000
 7494        },
 7495        jitter: {
 7496          maxMs: 0,
 7497          minMs: 0,
 7498          muMs: 0,
 7499          sigmaMs: 0
 7500        }
 7501      },
 7502      now: scheduler.now,
 7503      persistence,
 7504      setTimeoutImpl: scheduler.setTimeout
 7505    });
 7506
 7507    await restartedPolicy.initialize();
 7508
 7509    assert.equal(getPolicyPlatformSnapshot(restartedPolicy, "claude")?.recentDispatchCount, 1);
 7510
 7511    await assert.rejects(
 7512      restartedPolicy.beginRequest({
 7513        clientId: "firefox-claude",
 7514        platform: "claude"
 7515      }, "request-after-restart"),
 7516      (error) => {
 7517        assert.equal(error.code, "circuit_open");
 7518        assert.equal(error.details.retry_after_ms, 60_000);
 7519        return true;
 7520      }
 7521    );
 7522  });
 7523});
 7524
 7525test("BrowserRequestPolicyController restores persisted rate-limit windows from artifact.db", async () => {
 7526  const scheduler = createManualTimerScheduler();
 7527
 7528  await withArtifactStoreFixture(async ({ artifactStore }) => {
 7529    const persistence = createArtifactStoreBrowserRequestPolicyPersistence(artifactStore, {
 7530      now: scheduler.now
 7531    });
 7532    const firstPolicy = new BrowserRequestPolicyController({
 7533      clearTimeoutImpl: scheduler.clearTimeout,
 7534      config: {
 7535        jitter: {
 7536          maxMs: 0,
 7537          minMs: 0,
 7538          muMs: 0,
 7539          sigmaMs: 0
 7540        },
 7541        rateLimit: {
 7542          requestsPerMinutePerPlatform: 1,
 7543          windowMs: 60_000
 7544        }
 7545      },
 7546      now: scheduler.now,
 7547      persistence,
 7548      setTimeoutImpl: scheduler.setTimeout
 7549    });
 7550
 7551    await firstPolicy.initialize();
 7552
 7553    const firstLease = await firstPolicy.beginRequest({
 7554      clientId: "firefox-chatgpt",
 7555      platform: "chatgpt"
 7556    }, "request-persisted-rate-limit");
 7557
 7558    firstLease.complete({
 7559      status: "success"
 7560    });
 7561    await firstPolicy.flush();
 7562
 7563    const restartedPolicy = new BrowserRequestPolicyController({
 7564      clearTimeoutImpl: scheduler.clearTimeout,
 7565      config: {
 7566        jitter: {
 7567          maxMs: 0,
 7568          minMs: 0,
 7569          muMs: 0,
 7570          sigmaMs: 0
 7571        },
 7572        rateLimit: {
 7573          requestsPerMinutePerPlatform: 1,
 7574          windowMs: 60_000
 7575        }
 7576      },
 7577      now: scheduler.now,
 7578      persistence,
 7579      setTimeoutImpl: scheduler.setTimeout
 7580    });
 7581
 7582    await restartedPolicy.initialize();
 7583
 7584    const delayedLeasePromise = restartedPolicy.beginRequest({
 7585      clientId: "firefox-chatgpt",
 7586      platform: "chatgpt"
 7587    }, "request-after-rate-limit-restart");
 7588    let delayedResolved = false;
 7589
 7590    void delayedLeasePromise.then(() => {
 7591      delayedResolved = true;
 7592    });
 7593
 7594    await flushAsyncWork();
 7595    assert.equal(delayedResolved, false);
 7596    assert.equal(getPolicyPlatformSnapshot(restartedPolicy, "chatgpt")?.recentDispatchCount, 1);
 7597
 7598    scheduler.advanceBy(60_000);
 7599
 7600    const delayedLease = await delayedLeasePromise;
 7601    assert.equal(delayedLease.admission.rateLimitDelayMs, 60_000);
 7602    delayedLease.complete({
 7603      status: "cancelled"
 7604    });
 7605  });
 7606});
 7607
 7608test(
 7609  "handleConductorHttpRequest normalizes exec failures that are blocked by macOS TCC preflight",
 7610  { concurrency: false },
 7611  async () => {
 7612    const { repository, sharedToken, snapshot } = await createLocalApiFixture();
 7613
 7614    const execResponse = await withMockedPlatform("darwin", () =>
 7615      handleConductorHttpRequest(
 7616        {
 7617          body: JSON.stringify({
 7618            command: "pwd",
 7619            cwd: join(homedir(), "Downloads"),
 7620            timeoutMs: 2_000
 7621          }),
 7622          headers: {
 7623            authorization: `Bearer ${sharedToken}`
 7624          },
 7625          method: "POST",
 7626          path: "/v1/exec"
 7627        },
 7628        {
 7629          repository,
 7630          sharedToken,
 7631          snapshotLoader: () => snapshot
 7632        }
 7633      )
 7634    );
 7635
 7636    assert.equal(execResponse.status, 200);
 7637    const execPayload = parseJsonBody(execResponse);
 7638    assert.equal(execPayload.data.ok, false);
 7639    assert.equal(execPayload.data.operation, "exec");
 7640    assert.equal(execPayload.data.error.code, "TCC_PERMISSION_DENIED");
 7641    assert.equal(execPayload.data.result.stdout, "");
 7642    assert.equal(execPayload.data.result.stderr, "");
 7643    assert.equal(execPayload.data.result.exitCode, null);
 7644    assert.equal(execPayload.data.result.signal, null);
 7645    assert.equal(execPayload.data.result.durationMs, 0);
 7646    assert.equal(execPayload.data.result.timedOut, false);
 7647    assert.equal(typeof execPayload.data.result.startedAt, "string");
 7648    assert.equal(typeof execPayload.data.result.finishedAt, "string");
 7649  }
 7650);
 7651
 7652test("ConductorRuntime serves health and migrated local API endpoints over HTTP", async () => {
 7653  const codexd = await startCodexdStubServer();
 7654  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-runtime-"));
 7655  const hostOpsDir = mkdtempSync(join(tmpdir(), "baa-conductor-runtime-host-"));
 7656  const runtime = new ConductorRuntime(
 7657    {
 7658      nodeId: "mini-main",
 7659      host: "mini",
 7660      role: "primary",
 7661      controlApiBase: "https://control.example.test",
 7662      codexdLocalApiBase: codexd.baseUrl,
 7663      localApiBase: "http://127.0.0.1:0",
 7664      sharedToken: "replace-me",
 7665      uiBrowserAdminPassword: "admin-secret",
 7666      uiReadonlyPassword: "readonly-secret",
 7667      paths: {
 7668        runsDir: "/tmp/runs",
 7669        stateDir
 7670      }
 7671    },
 7672    {
 7673      autoStartLoops: false,
 7674      now: () => 100
 7675    }
 7676  );
 7677
 7678  try {
 7679    const snapshot = await runtime.start();
 7680    assert.equal(snapshot.daemon.schedulerEnabled, true);
 7681    assert.equal(snapshot.codexd.localApiBase, codexd.baseUrl);
 7682    assert.match(snapshot.controlApi.localApiBase, /^http:\/\/127\.0\.0\.1:\d+$/u);
 7683    assert.match(snapshot.controlApi.browserWsUrl, /^ws:\/\/127\.0\.0\.1:\d+\/ws\/browser$/u);
 7684    assert.match(snapshot.controlApi.firefoxWsUrl, /^ws:\/\/127\.0\.0\.1:\d+\/ws\/firefox$/u);
 7685
 7686    const baseUrl = snapshot.controlApi.localApiBase;
 7687    const hostOpsHeaders = {
 7688      authorization: "Bearer replace-me",
 7689      "content-type": "application/json"
 7690    };
 7691
 7692    const healthResponse = await fetch(`${baseUrl}/healthz`);
 7693    assert.equal(healthResponse.status, 200);
 7694    assert.equal(await healthResponse.text(), "ok\n");
 7695
 7696    const apiHealthResponse = await fetch(`${baseUrl}/health`);
 7697    assert.equal(apiHealthResponse.status, 200);
 7698    const apiHealthPayload = await apiHealthResponse.json();
 7699    assert.equal(apiHealthPayload.ok, true);
 7700    assert.equal(apiHealthPayload.data.status, "ok");
 7701
 7702    const readyResponse = await fetch(`${baseUrl}/readyz`);
 7703    assert.equal(readyResponse.status, 200);
 7704    assert.equal(await readyResponse.text(), "ready\n");
 7705
 7706    const roleResponse = await fetch(`${baseUrl}/rolez`);
 7707    assert.equal(roleResponse.status, 200);
 7708    assert.equal(await roleResponse.text(), "leader\n");
 7709
 7710    const runtimeResponse = await fetch(`${baseUrl}/v1/runtime`);
 7711    assert.equal(runtimeResponse.status, 200);
 7712    const payload = await runtimeResponse.json();
 7713    assert.equal(payload.ok, true);
 7714    assert.equal(payload.data.identity, "mini-main@mini(primary)");
 7715    assert.equal(payload.data.controlApi.browserWsUrl, snapshot.controlApi.browserWsUrl);
 7716    assert.equal(payload.data.controlApi.firefoxWsUrl, snapshot.controlApi.firefoxWsUrl);
 7717    assert.equal(payload.data.controlApi.localApiBase, baseUrl);
 7718    assert.equal(payload.data.runtime.started, true);
 7719
 7720    const systemStateResponse = await fetch(`${baseUrl}/v1/system/state`);
 7721    assert.equal(systemStateResponse.status, 200);
 7722    const systemStatePayload = await systemStateResponse.json();
 7723    assert.equal(systemStatePayload.ok, true);
 7724    assert.equal(systemStatePayload.data.holder_id, "mini-main");
 7725    assert.equal(systemStatePayload.data.mode, "running");
 7726
 7727    const statusViewResponse = await fetch(`${baseUrl}/v1/status`);
 7728    assert.equal(statusViewResponse.status, 200);
 7729    const statusViewPayload = await statusViewResponse.json();
 7730    assert.deepEqual(Object.keys(statusViewPayload).sort(), ["data", "ok"]);
 7731    assert.equal(statusViewPayload.data.mode, "running");
 7732    assert.equal(statusViewPayload.data.queueDepth, 0);
 7733    assert.equal(statusViewPayload.data.activeRuns, 0);
 7734
 7735    const statusViewUiResponse = await fetch(`${baseUrl}/v1/status/ui`);
 7736    assert.equal(statusViewUiResponse.status, 200);
 7737    assert.equal(statusViewUiResponse.headers.get("content-type"), "text/html; charset=utf-8");
 7738    const statusViewUiHtml = await statusViewUiResponse.text();
 7739    assert.match(statusViewUiHtml, /Readable automation state for people and browser controls\./u);
 7740    assert.match(statusViewUiHtml, /HTML endpoint: <strong>\/v1\/status\/ui<\/strong>/u);
 7741
 7742    const appResponse = await fetch(`${baseUrl}/app`);
 7743    assert.equal(appResponse.status, 200);
 7744    assert.equal(appResponse.headers.get("cache-control"), "no-store");
 7745    assert.equal(appResponse.headers.get("content-type"), "text/html; charset=utf-8");
 7746    const appHtml = await appResponse.text();
 7747    assert.match(appHtml, /<div id="app"><\/div>/u);
 7748    const appAssetMatch = appHtml.match(/\/app\/assets\/[^"' )]+/u);
 7749    assert.ok(appAssetMatch);
 7750
 7751    const appControlResponse = await fetch(`${baseUrl}/app/control`);
 7752    assert.equal(appControlResponse.status, 200);
 7753    assert.equal(await appControlResponse.text(), appHtml);
 7754
 7755    const appAssetResponse = await fetch(`${baseUrl}${appAssetMatch[0]}`);
 7756    assert.equal(appAssetResponse.status, 200);
 7757    assert.equal(appAssetResponse.headers.get("cache-control"), "public, max-age=31536000, immutable");
 7758    assert.ok(appAssetResponse.headers.get("content-type"));
 7759
 7760    const conductorUiLoginResponse = await fetch(`${baseUrl}/app/login`);
 7761    assert.equal(conductorUiLoginResponse.status, 200);
 7762    assert.equal(conductorUiLoginResponse.headers.get("content-type"), "text/html; charset=utf-8");
 7763
 7764    const uiSessionMeResponse = await fetch(`${baseUrl}/v1/ui/session/me`);
 7765    assert.equal(uiSessionMeResponse.status, 200);
 7766    const uiSessionMePayload = await uiSessionMeResponse.json();
 7767    assert.equal(uiSessionMePayload.ok, true);
 7768    assert.equal(uiSessionMePayload.data.authenticated, false);
 7769    assert.deepEqual(uiSessionMePayload.data.available_roles, ["browser_admin", "readonly"]);
 7770    assert.equal(uiSessionMePayload.data.session, null);
 7771
 7772    const invalidUiLoginResponse = await fetch(`${baseUrl}/v1/ui/session/login`, {
 7773      method: "POST",
 7774      headers: {
 7775        "content-type": "application/json"
 7776      },
 7777      body: JSON.stringify({
 7778        password: "wrong-secret",
 7779        role: "readonly"
 7780      })
 7781    });
 7782    assert.equal(invalidUiLoginResponse.status, 401);
 7783    const invalidUiLoginPayload = await invalidUiLoginResponse.json();
 7784    assert.equal(invalidUiLoginPayload.ok, false);
 7785    assert.equal(invalidUiLoginPayload.error, "unauthorized");
 7786
 7787    const readonlyUiLoginResponse = await fetch(`${baseUrl}/v1/ui/session/login`, {
 7788      method: "POST",
 7789      headers: {
 7790        "content-type": "application/json"
 7791      },
 7792      body: JSON.stringify({
 7793        password: "readonly-secret",
 7794        role: "readonly"
 7795      })
 7796    });
 7797    assert.equal(readonlyUiLoginResponse.status, 200);
 7798    const readonlyUiLoginPayload = await readonlyUiLoginResponse.json();
 7799    assert.equal(readonlyUiLoginPayload.data.authenticated, true);
 7800    assert.equal(readonlyUiLoginPayload.data.session.role, "readonly");
 7801    const readonlySetCookie = readonlyUiLoginResponse.headers.get("set-cookie");
 7802    assert.ok(readonlySetCookie);
 7803    assert.match(readonlySetCookie, /baa_ui_session=/u);
 7804    assert.match(readonlySetCookie, /HttpOnly/u);
 7805    assert.match(readonlySetCookie, /SameSite=Lax/u);
 7806    assert.match(readonlySetCookie, /Path=\//u);
 7807    const readonlyCookie = readonlySetCookie.split(";", 1)[0];
 7808
 7809    const readonlyUiMeResponse = await fetch(`${baseUrl}/v1/ui/session/me`, {
 7810      headers: {
 7811        cookie: readonlyCookie
 7812      }
 7813    });
 7814    assert.equal(readonlyUiMeResponse.status, 200);
 7815    const readonlyUiMePayload = await readonlyUiMeResponse.json();
 7816    assert.equal(readonlyUiMePayload.data.authenticated, true);
 7817    assert.equal(readonlyUiMePayload.data.session.role, "readonly");
 7818    assert.match(readonlyUiMeResponse.headers.get("set-cookie") ?? "", /baa_ui_session=/u);
 7819
 7820    const readonlyUiLogoutResponse = await fetch(`${baseUrl}/v1/ui/session/logout`, {
 7821      method: "POST",
 7822      headers: {
 7823        cookie: readonlyCookie
 7824      }
 7825    });
 7826    assert.equal(readonlyUiLogoutResponse.status, 200);
 7827    assert.equal((await readonlyUiLogoutResponse.json()).data.authenticated, false);
 7828    assert.match(readonlyUiLogoutResponse.headers.get("set-cookie") ?? "", /Max-Age=0/u);
 7829
 7830    const staleReadonlyMeResponse = await fetch(`${baseUrl}/v1/ui/session/me`, {
 7831      headers: {
 7832        cookie: readonlyCookie
 7833      }
 7834    });
 7835    assert.equal(staleReadonlyMeResponse.status, 200);
 7836    const staleReadonlyMePayload = await staleReadonlyMeResponse.json();
 7837    assert.equal(staleReadonlyMePayload.data.authenticated, false);
 7838    assert.match(staleReadonlyMeResponse.headers.get("set-cookie") ?? "", /Max-Age=0/u);
 7839
 7840    const browserAdminLoginResponse = await fetch(`${baseUrl}/v1/ui/session/login`, {
 7841      method: "POST",
 7842      headers: {
 7843        "content-type": "application/json"
 7844      },
 7845      body: JSON.stringify({
 7846        password: "admin-secret",
 7847        role: "browser_admin"
 7848      })
 7849    });
 7850    assert.equal(browserAdminLoginResponse.status, 200);
 7851    const browserAdminLoginPayload = await browserAdminLoginResponse.json();
 7852    assert.equal(browserAdminLoginPayload.data.session.role, "browser_admin");
 7853
 7854    const codexStatusResponse = await fetch(`${baseUrl}/v1/codex`);
 7855    assert.equal(codexStatusResponse.status, 200);
 7856    const codexStatusPayload = await codexStatusResponse.json();
 7857    assert.equal(codexStatusPayload.data.proxy.target_base_url, codexd.baseUrl);
 7858    assert.equal(codexStatusPayload.data.sessions.count, 1);
 7859
 7860    const codexSessionsResponse = await fetch(`${baseUrl}/v1/codex/sessions`);
 7861    assert.equal(codexSessionsResponse.status, 200);
 7862    const codexSessionsPayload = await codexSessionsResponse.json();
 7863    assert.equal(codexSessionsPayload.data.sessions[0].sessionId, "session-demo");
 7864
 7865    const codexSessionCreateResponse = await fetch(`${baseUrl}/v1/codex/sessions`, {
 7866      method: "POST",
 7867      headers: {
 7868        "content-type": "application/json"
 7869      },
 7870      body: JSON.stringify({
 7871        cwd: "/Users/george/code/baa-conductor",
 7872        purpose: "duplex"
 7873      })
 7874    });
 7875    assert.equal(codexSessionCreateResponse.status, 201);
 7876    const codexSessionCreatePayload = await codexSessionCreateResponse.json();
 7877    assert.equal(codexSessionCreatePayload.data.session.sessionId, "session-2");
 7878
 7879    const codexTurnResponse = await fetch(`${baseUrl}/v1/codex/turn`, {
 7880      method: "POST",
 7881      headers: {
 7882        "content-type": "application/json"
 7883      },
 7884      body: JSON.stringify({
 7885        input: "Continue.",
 7886        sessionId: "session-demo"
 7887      })
 7888    });
 7889    assert.equal(codexTurnResponse.status, 202);
 7890    const codexTurnPayload = await codexTurnResponse.json();
 7891    assert.equal(codexTurnPayload.data.accepted, true);
 7892    assert.equal(codexTurnPayload.data.turnId, "turn-created");
 7893
 7894    const pauseResponse = await fetch(`${baseUrl}/v1/system/pause`, {
 7895      method: "POST",
 7896      headers: {
 7897        "content-type": "application/json"
 7898      },
 7899      body: JSON.stringify({
 7900        requested_by: "integration_test",
 7901        reason: "pause_for_verification"
 7902      })
 7903    });
 7904    assert.equal(pauseResponse.status, 200);
 7905    const pausePayload = await pauseResponse.json();
 7906    assert.equal(pausePayload.data.mode, "paused");
 7907
 7908    const pausedStateResponse = await fetch(`${baseUrl}/v1/system/state`);
 7909    const pausedStatePayload = await pausedStateResponse.json();
 7910    assert.equal(pausedStatePayload.data.mode, "paused");
 7911
 7912    const describeResponse = await fetch(`${baseUrl}/describe`);
 7913    assert.equal(describeResponse.status, 200);
 7914    const describePayload = await describeResponse.json();
 7915    assert.equal(describePayload.ok, true);
 7916    assert.equal(describePayload.data.name, "baa-conductor-daemon");
 7917    assert.equal(describePayload.data.codex.target_base_url, codexd.baseUrl);
 7918
 7919    const businessDescribeResponse = await fetch(`${baseUrl}/describe/business`);
 7920    assert.equal(businessDescribeResponse.status, 200);
 7921    const businessDescribePayload = await businessDescribeResponse.json();
 7922    assert.equal(businessDescribePayload.data.surface, "business");
 7923    assert.match(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/codex/u);
 7924    assert.match(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/status/u);
 7925
 7926    const controlDescribeResponse = await fetch(`${baseUrl}/describe/control`);
 7927    assert.equal(controlDescribeResponse.status, 200);
 7928    const controlDescribePayload = await controlDescribeResponse.json();
 7929    assert.equal(controlDescribePayload.data.surface, "control");
 7930    assert.match(JSON.stringify(controlDescribePayload.data.endpoints), /\/v1\/exec/u);
 7931    assert.equal(controlDescribePayload.data.host_operations.auth.header, "Authorization: Bearer <BAA_SHARED_TOKEN>");
 7932
 7933    const unauthorizedExecResponse = await fetch(`${baseUrl}/v1/exec`, {
 7934      method: "POST",
 7935      headers: {
 7936        "content-type": "application/json"
 7937      },
 7938      body: JSON.stringify({
 7939        command: "printf 'runtime-host-op'",
 7940        cwd: hostOpsDir,
 7941        timeoutMs: 2000
 7942      })
 7943    });
 7944    assert.equal(unauthorizedExecResponse.status, 401);
 7945    const unauthorizedExecPayload = await unauthorizedExecResponse.json();
 7946    assert.equal(unauthorizedExecPayload.error, "unauthorized");
 7947
 7948    const execResponse = await fetch(`${baseUrl}/v1/exec`, {
 7949      method: "POST",
 7950      headers: hostOpsHeaders,
 7951      body: JSON.stringify({
 7952        command: "printf 'runtime-host-op'",
 7953        cwd: hostOpsDir,
 7954        timeoutMs: 2000
 7955      })
 7956    });
 7957    assert.equal(execResponse.status, 200);
 7958    const execPayload = await execResponse.json();
 7959    assert.equal(execPayload.data.ok, true);
 7960    assert.equal(execPayload.data.result.stdout, "runtime-host-op");
 7961
 7962    const writeResponse = await fetch(`${baseUrl}/v1/files/write`, {
 7963      method: "POST",
 7964      headers: hostOpsHeaders,
 7965      body: JSON.stringify({
 7966        path: "runtime/demo.txt",
 7967        cwd: hostOpsDir,
 7968        content: "hello from runtime host ops",
 7969        overwrite: false,
 7970        createParents: true
 7971      })
 7972    });
 7973    assert.equal(writeResponse.status, 200);
 7974    const writePayload = await writeResponse.json();
 7975    assert.equal(writePayload.data.ok, true);
 7976    assert.equal(writePayload.data.result.created, true);
 7977
 7978    const duplicateWriteResponse = await fetch(`${baseUrl}/v1/files/write`, {
 7979      method: "POST",
 7980      headers: hostOpsHeaders,
 7981      body: JSON.stringify({
 7982        path: "runtime/demo.txt",
 7983        cwd: hostOpsDir,
 7984        content: "should not overwrite",
 7985        overwrite: false
 7986      })
 7987    });
 7988    assert.equal(duplicateWriteResponse.status, 200);
 7989    const duplicateWritePayload = await duplicateWriteResponse.json();
 7990    assert.equal(duplicateWritePayload.data.ok, false);
 7991    assert.equal(duplicateWritePayload.data.error.code, "FILE_ALREADY_EXISTS");
 7992
 7993    const readResponse = await fetch(`${baseUrl}/v1/files/read`, {
 7994      method: "POST",
 7995      headers: hostOpsHeaders,
 7996      body: JSON.stringify({
 7997        path: "runtime/demo.txt",
 7998        cwd: hostOpsDir
 7999      })
 8000    });
 8001    assert.equal(readResponse.status, 200);
 8002    const readPayload = await readResponse.json();
 8003    assert.equal(readPayload.data.ok, true);
 8004    assert.equal(readPayload.data.result.content, "hello from runtime host ops");
 8005
 8006    const stoppedSnapshot = await runtime.stop();
 8007    assert.equal(stoppedSnapshot.runtime.started, false);
 8008  } finally {
 8009    await codexd.stop();
 8010    rmSync(stateDir, {
 8011      force: true,
 8012      recursive: true
 8013    });
 8014    rmSync(hostOpsDir, {
 8015      force: true,
 8016      recursive: true
 8017    });
 8018  }
 8019});
 8020
 8021test("handleConductorHttpRequest serves artifact files and robots.txt", async () => {
 8022  const { repository, snapshot } = await createLocalApiFixture();
 8023
 8024  await withArtifactStoreFixture(async ({ artifactStore }) => {
 8025    await artifactStore.insertMessage({
 8026      conversationId: "conv_artifact",
 8027      id: "msg_artifact",
 8028      observedAt: 1_743_151_740_000,
 8029      platform: "claude",
 8030      rawText: "artifact message body",
 8031      role: "assistant"
 8032    });
 8033    await artifactStore.insertExecution({
 8034      executedAt: 1_743_151_800_000,
 8035      instructionId: "inst_artifact",
 8036      messageId: "msg_artifact",
 8037      params: {
 8038        command: "pnpm test"
 8039      },
 8040      paramsKind: "body",
 8041      resultData: {
 8042        exit_code: 0,
 8043        stdout: "ok"
 8044      },
 8045      resultOk: true,
 8046      target: "conductor",
 8047      tool: "exec"
 8048    });
 8049    await artifactStore.upsertSession({
 8050      conversationId: "conv_artifact",
 8051      executionCount: 1,
 8052      id: "session_artifact",
 8053      lastActivityAt: 1_743_151_800_000,
 8054      messageCount: 1,
 8055      platform: "claude",
 8056      startedAt: 1_743_151_740_000
 8057    });
 8058
 8059    const context = {
 8060      artifactStore,
 8061      repository,
 8062      snapshotLoader: () => snapshot
 8063    };
 8064
 8065    const robotsResponse = await handleConductorHttpRequest(
 8066      {
 8067        method: "GET",
 8068        path: "/robots.txt"
 8069      },
 8070      context
 8071    );
 8072    assert.equal(robotsResponse.status, 200);
 8073    assert.equal(robotsResponse.body, "User-agent: *\nAllow: /artifact/\n");
 8074
 8075    const messageTextResponse = await handleConductorHttpRequest(
 8076      {
 8077        method: "GET",
 8078        path: "/artifact/msg/msg_artifact.txt"
 8079      },
 8080      context
 8081    );
 8082    assert.equal(messageTextResponse.status, 200);
 8083    assert.equal(messageTextResponse.headers["content-type"], "text/plain; charset=utf-8");
 8084    assert.match(Buffer.from(messageTextResponse.body).toString("utf8"), /artifact message body/u);
 8085
 8086    const messageJsonResponse = await handleConductorHttpRequest(
 8087      {
 8088        method: "GET",
 8089        path: "/artifact/msg/msg_artifact.json"
 8090      },
 8091      context
 8092    );
 8093    assert.equal(messageJsonResponse.status, 200);
 8094    assert.equal(messageJsonResponse.headers["content-type"], "application/json; charset=utf-8");
 8095    assert.match(
 8096      Buffer.from(messageJsonResponse.body).toString("utf8"),
 8097      /https:\/\/conductor\.makefile\.so\/artifact\/msg\/msg_artifact\.txt/u
 8098    );
 8099
 8100    const sessionLatestResponse = await handleConductorHttpRequest(
 8101      {
 8102        method: "GET",
 8103        path: "/artifact/session/latest.txt"
 8104      },
 8105      context
 8106    );
 8107    assert.equal(sessionLatestResponse.status, 200);
 8108    assert.match(Buffer.from(sessionLatestResponse.body).toString("utf8"), /session_artifact/u);
 8109
 8110    const removedRepoRootResponse = await handleConductorHttpRequest(
 8111      {
 8112        method: "GET",
 8113        path: "/artifact/repo/demo-repo"
 8114      },
 8115      context
 8116    );
 8117    assert.equal(removedRepoRootResponse.status, 404);
 8118    assert.equal(parseJsonBody(removedRepoRootResponse).error, "not_found");
 8119
 8120    const removedRepoFileResponse = await handleConductorHttpRequest(
 8121      {
 8122        method: "GET",
 8123        path: "/artifact/repo/demo-repo/log.html"
 8124      },
 8125      context
 8126    );
 8127    assert.equal(removedRepoFileResponse.status, 404);
 8128    assert.equal(parseJsonBody(removedRepoFileResponse).error, "not_found");
 8129
 8130    const missingResponse = await handleConductorHttpRequest(
 8131      {
 8132        method: "GET",
 8133        path: "/artifact/msg/missing.txt"
 8134      },
 8135      context
 8136    );
 8137    assert.equal(missingResponse.status, 404);
 8138    assert.equal(parseJsonBody(missingResponse).error, "not_found");
 8139  });
 8140});
 8141
 8142test("handleConductorHttpRequest serves conductor-ui shell assets and history fallback", async () => {
 8143  const { repository, snapshot } = await createLocalApiFixture();
 8144
 8145  await withConductorUiDistFixture(async ({ assetFile, uiDistDir }) => {
 8146    const context = {
 8147      repository,
 8148      snapshotLoader: () => snapshot,
 8149      uiDistDir
 8150    };
 8151
 8152    const appResponse = await handleConductorHttpRequest(
 8153      {
 8154        method: "GET",
 8155        path: "/app"
 8156      },
 8157      context
 8158    );
 8159    assert.equal(appResponse.status, 200);
 8160    assert.equal(appResponse.headers["cache-control"], "no-store");
 8161    assert.equal(appResponse.headers["content-type"], "text/html; charset=utf-8");
 8162    const appHtml = readBinaryBodyText(appResponse);
 8163    assert.match(appHtml, /Conductor UI Shell Fixture/u);
 8164
 8165    const appSlashResponse = await handleConductorHttpRequest(
 8166      {
 8167        method: "GET",
 8168        path: "/app/"
 8169      },
 8170      context
 8171    );
 8172    assert.equal(readBinaryBodyText(appSlashResponse), appHtml);
 8173
 8174    const historyResponse = await handleConductorHttpRequest(
 8175      {
 8176        method: "GET",
 8177        path: "/app/control"
 8178      },
 8179      context
 8180    );
 8181    assert.equal(historyResponse.status, 200);
 8182    assert.equal(readBinaryBodyText(historyResponse), appHtml);
 8183
 8184    const assetResponse = await handleConductorHttpRequest(
 8185      {
 8186        method: "GET",
 8187        path: `/app/assets/${assetFile}`
 8188      },
 8189      context
 8190    );
 8191    assert.equal(assetResponse.status, 200);
 8192    assert.equal(assetResponse.headers["cache-control"], "public, max-age=31536000, immutable");
 8193    assert.equal(assetResponse.headers["content-type"], "text/javascript; charset=utf-8");
 8194    assert.equal(readBinaryBodyText(assetResponse), "console.log('fixture ui shell');\n");
 8195
 8196    const missingAssetResponse = await handleConductorHttpRequest(
 8197      {
 8198        method: "GET",
 8199        path: "/app/assets/missing.js"
 8200      },
 8201      context
 8202    );
 8203    assert.equal(missingAssetResponse.status, 404);
 8204    assert.equal(parseJsonBody(missingAssetResponse).error, "not_found");
 8205
 8206    const assetRootResponse = await handleConductorHttpRequest(
 8207      {
 8208        method: "GET",
 8209        path: "/app/assets"
 8210      },
 8211      context
 8212    );
 8213    assert.equal(assetRootResponse.status, 404);
 8214    assert.equal(parseJsonBody(assetRootResponse).error, "not_found");
 8215  });
 8216});
 8217
 8218test("handleConductorHttpRequest serves code files and blocks unsafe paths", async () => {
 8219  const { repository, snapshot } = await createLocalApiFixture();
 8220  const codeRootDir = mkdtempSync(join(tmpdir(), "baa-conductor-code-route-"));
 8221  const repoDir = join(codeRootDir, "demo-repo");
 8222
 8223  try {
 8224    mkdirSync(join(repoDir, "src"), { recursive: true });
 8225    mkdirSync(join(repoDir, ".git", "objects"), { recursive: true });
 8226    writeFileSync(join(repoDir, "package.json"), "{\n  \"name\": \"demo-repo\"\n}\n");
 8227    writeFileSync(join(repoDir, ".env"), "SECRET=1\n");
 8228    writeFileSync(join(repoDir, ".credentials"), "token=secret\n");
 8229    writeFileSync(join(repoDir, ".git", "objects", "secret"), "hidden\n");
 8230    writeFileSync(join(repoDir, "image.png"), Buffer.from([0x89, 0x50, 0x4e, 0x47]));
 8231    writeFileSync(join(repoDir, "src", "index.ts"), "export const demo = true;\n");
 8232
 8233    const context = {
 8234      codeRootDir,
 8235      repository,
 8236      snapshotLoader: () => snapshot
 8237    };
 8238
 8239    const fileResponse = await handleConductorHttpRequest(
 8240      {
 8241        method: "GET",
 8242        path: "/code/demo-repo/package.json"
 8243      },
 8244      context
 8245    );
 8246    assert.equal(fileResponse.status, 200);
 8247    assert.equal(fileResponse.headers["content-type"], "text/plain; charset=utf-8");
 8248    assert.equal(Buffer.from(fileResponse.body).toString("utf8"), "{\n  \"name\": \"demo-repo\"\n}\n");
 8249
 8250    const directoryResponse = await handleConductorHttpRequest(
 8251      {
 8252        method: "GET",
 8253        path: "/code/demo-repo"
 8254      },
 8255      context
 8256    );
 8257    assert.equal(directoryResponse.status, 200);
 8258    assert.deepEqual(Buffer.from(directoryResponse.body).toString("utf8").split("\n"), [
 8259      "package.json",
 8260      "src"
 8261    ]);
 8262
 8263    const hiddenEnvResponse = await handleConductorHttpRequest(
 8264      {
 8265        method: "GET",
 8266        path: "/code/demo-repo/.env"
 8267      },
 8268      context
 8269    );
 8270    assert.equal(hiddenEnvResponse.status, 403);
 8271    assert.equal(parseJsonBody(hiddenEnvResponse).error, "forbidden");
 8272
 8273    const hiddenCredentialsResponse = await handleConductorHttpRequest(
 8274      {
 8275        method: "GET",
 8276        path: "/code/demo-repo/.credentials"
 8277      },
 8278      context
 8279    );
 8280    assert.equal(hiddenCredentialsResponse.status, 403);
 8281    assert.equal(parseJsonBody(hiddenCredentialsResponse).error, "forbidden");
 8282
 8283    const hiddenGitObjectsResponse = await handleConductorHttpRequest(
 8284      {
 8285        method: "GET",
 8286        path: "/code/demo-repo/.git/objects/secret"
 8287      },
 8288      context
 8289    );
 8290    assert.equal(hiddenGitObjectsResponse.status, 403);
 8291    assert.equal(parseJsonBody(hiddenGitObjectsResponse).error, "forbidden");
 8292
 8293    const hiddenGitConfigResponse = await handleConductorHttpRequest(
 8294      {
 8295        method: "GET",
 8296        path: "/code/demo-repo/.git/config"
 8297      },
 8298      context
 8299    );
 8300    assert.equal(hiddenGitConfigResponse.status, 403);
 8301    assert.equal(parseJsonBody(hiddenGitConfigResponse).error, "forbidden");
 8302
 8303    const hiddenGitDirectoryResponse = await handleConductorHttpRequest(
 8304      {
 8305        method: "GET",
 8306        path: "/code/demo-repo/.git"
 8307      },
 8308      context
 8309    );
 8310    assert.equal(hiddenGitDirectoryResponse.status, 403);
 8311    assert.equal(parseJsonBody(hiddenGitDirectoryResponse).error, "forbidden");
 8312
 8313    const traversalResponse = await handleConductorHttpRequest(
 8314      {
 8315        method: "GET",
 8316        path: "/code/demo-repo/%2e%2e/%2e%2e/%2e%2e/etc/passwd"
 8317      },
 8318      context
 8319    );
 8320    assert.ok([403, 404].includes(traversalResponse.status));
 8321    assert.match(parseJsonBody(traversalResponse).error, /^(forbidden|not_found)$/u);
 8322
 8323    const binaryResponse = await handleConductorHttpRequest(
 8324      {
 8325        method: "GET",
 8326        path: "/code/demo-repo/image.png"
 8327      },
 8328      context
 8329    );
 8330    assert.equal(binaryResponse.status, 403);
 8331    assert.equal(parseJsonBody(binaryResponse).error, "forbidden");
 8332
 8333    const missingResponse = await handleConductorHttpRequest(
 8334      {
 8335        method: "GET",
 8336        path: "/code/demo-repo/missing.ts"
 8337      },
 8338      context
 8339    );
 8340    assert.equal(missingResponse.status, 404);
 8341    assert.equal(parseJsonBody(missingResponse).error, "not_found");
 8342  } finally {
 8343    rmSync(codeRootDir, {
 8344      force: true,
 8345      recursive: true
 8346    });
 8347  }
 8348});
 8349
 8350test("ConductorRuntime exposes a minimal runtime snapshot for CLI and status surfaces", async () => {
 8351  await withRuntimeFixture(async ({ runtime }) => {
 8352    assert.equal(runtime.getRuntimeSnapshot().runtime.started, false);
 8353
 8354    const startedSnapshot = await runtime.start();
 8355    assert.equal(startedSnapshot.runtime.started, true);
 8356    assert.equal(startedSnapshot.daemon.leaseState, "leader");
 8357    assert.equal(startedSnapshot.daemon.schedulerEnabled, true);
 8358    assert.equal(startedSnapshot.loops.heartbeat, false);
 8359    assert.equal(startedSnapshot.loops.lease, false);
 8360    assert.equal(startedSnapshot.controlApi.usesPlaceholderToken, true);
 8361    assert.match(startedSnapshot.warnings.join("\n"), /replace-me/);
 8362
 8363    const stoppedSnapshot = await runtime.stop();
 8364    assert.equal(stoppedSnapshot.runtime.started, false);
 8365  });
 8366});
 8367
 8368test("ConductorRuntime initializes timed-jobs logging during startup", async () => {
 8369  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-runtime-timed-jobs-state-"));
 8370  const logsDir = mkdtempSync(join(tmpdir(), "baa-conductor-runtime-timed-jobs-logs-"));
 8371  const runtime = new ConductorRuntime(
 8372    {
 8373      nodeId: "mini-main",
 8374      host: "mini",
 8375      role: "primary",
 8376      controlApiBase: "https://control.example.test",
 8377      localApiBase: "http://127.0.0.1:0",
 8378      sharedToken: "replace-me",
 8379      paths: {
 8380        logsDir,
 8381        runsDir: "/tmp/runs",
 8382        stateDir
 8383      }
 8384    },
 8385    {
 8386      autoStartLoops: false,
 8387      now: () => 100
 8388    }
 8389  );
 8390
 8391  try {
 8392    await runtime.start();
 8393
 8394    const timedJobsLogDir = join(logsDir, "timed-jobs");
 8395    assert.equal(existsSync(timedJobsLogDir), true);
 8396
 8397    const entries = await waitForJsonlEntries(
 8398      timedJobsLogDir,
 8399      (items) => items.some((entry) => entry.runner === "timed-jobs.framework" && entry.stage === "started")
 8400    );
 8401    assert.ok(
 8402      entries.find(
 8403        (entry) => entry.runner === "timed-jobs.framework" && entry.stage === "started"
 8404      )
 8405    );
 8406  } finally {
 8407    await runtime.stop();
 8408    rmSync(logsDir, {
 8409      force: true,
 8410      recursive: true
 8411    });
 8412    rmSync(stateDir, {
 8413      force: true,
 8414      recursive: true
 8415    });
 8416  }
 8417});
 8418
 8419function createStubRuntimeDaemon(overrides = {}) {
 8420  return {
 8421    start: async () => createStubRuntimeDaemonSnapshot(),
 8422    stop: async () => createStubRuntimeDaemonSnapshot(),
 8423    getStatusSnapshot: () => createStubRuntimeDaemonSnapshot(),
 8424    getStartupChecklist: () => [],
 8425    describeIdentity: () => "mini-main@mini(primary)",
 8426    getLoopStatus: () => ({
 8427      heartbeat: false,
 8428      lease: false
 8429    }),
 8430    ...overrides
 8431  };
 8432}
 8433
 8434function createStubRuntimeDaemonSnapshot() {
 8435  return {
 8436    nodeId: "mini-main",
 8437    host: "mini",
 8438    role: "primary",
 8439    leaseState: "standby",
 8440    schedulerEnabled: false,
 8441    currentLeaderId: null,
 8442    currentTerm: null,
 8443    leaseExpiresAt: null,
 8444    lastHeartbeatAt: null,
 8445    lastLeaseOperation: null,
 8446    nextLeaseOperation: "acquire",
 8447    consecutiveRenewFailures: 0,
 8448    lastError: null
 8449  };
 8450}
 8451
 8452test("ConductorRuntime awaits daemon shutdown when local API startup fails", async () => {
 8453  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-runtime-start-stop-await-"));
 8454  const runtime = new ConductorRuntime(
 8455    {
 8456      nodeId: "mini-main",
 8457      host: "mini",
 8458      role: "primary",
 8459      controlApiBase: "https://control.example.test",
 8460      localApiBase: "http://127.0.0.1:0",
 8461      sharedToken: "replace-me",
 8462      paths: {
 8463        runsDir: "/tmp/runs",
 8464        stateDir
 8465      }
 8466    },
 8467    {
 8468      autoStartLoops: false,
 8469      now: () => 100
 8470    }
 8471  );
 8472
 8473  let daemonStopResolved = false;
 8474
 8475  runtime.localControlPlaneInitialized = true;
 8476  runtime.daemon = createStubRuntimeDaemon({
 8477    stop: async () => {
 8478      await new Promise((resolve) => setTimeout(resolve, 0));
 8479      daemonStopResolved = true;
 8480      return createStubRuntimeDaemonSnapshot();
 8481    }
 8482  });
 8483  runtime.localApiServer = {
 8484    start: async () => {
 8485      throw new Error("local api start failed");
 8486    },
 8487    stop: async () => {},
 8488    getBaseUrl: () => "http://127.0.0.1:0",
 8489    getBrowserWebSocketUrl: () => null,
 8490    getFirefoxWebSocketUrl: () => null
 8491  };
 8492
 8493  try {
 8494    await assert.rejects(runtime.start(), /local api start failed/u);
 8495    assert.equal(daemonStopResolved, true);
 8496    assert.equal(runtime.getRuntimeSnapshot().runtime.started, false);
 8497  } finally {
 8498    rmSync(stateDir, {
 8499      force: true,
 8500      recursive: true
 8501    });
 8502  }
 8503});
 8504
 8505test("ConductorRuntime.stop waits for daemon shutdown before closing the local API server", async () => {
 8506  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-runtime-stop-await-"));
 8507  const runtime = new ConductorRuntime(
 8508    {
 8509      nodeId: "mini-main",
 8510      host: "mini",
 8511      role: "primary",
 8512      controlApiBase: "https://control.example.test",
 8513      localApiBase: "http://127.0.0.1:0",
 8514      sharedToken: "replace-me",
 8515      paths: {
 8516        runsDir: "/tmp/runs",
 8517        stateDir
 8518      }
 8519    },
 8520    {
 8521      autoStartLoops: false,
 8522      now: () => 100
 8523    }
 8524  );
 8525
 8526  let daemonStopResolved = false;
 8527  let releaseDaemonStop = null;
 8528  let localApiStopSawDaemonResolved = false;
 8529
 8530  runtime.started = true;
 8531  runtime.daemon = createStubRuntimeDaemon({
 8532    stop: async () => {
 8533      await new Promise((resolve) => {
 8534        releaseDaemonStop = () => {
 8535          daemonStopResolved = true;
 8536          resolve();
 8537        };
 8538      });
 8539      return createStubRuntimeDaemonSnapshot();
 8540    }
 8541  });
 8542  runtime.localApiServer = {
 8543    start: async () => {},
 8544    stop: async () => {
 8545      localApiStopSawDaemonResolved = daemonStopResolved;
 8546    },
 8547    getBaseUrl: () => "http://127.0.0.1:0",
 8548    getBrowserWebSocketUrl: () => null,
 8549    getFirefoxWebSocketUrl: () => null
 8550  };
 8551
 8552  try {
 8553    const stopPromise = runtime.stop();
 8554    await new Promise((resolve) => setTimeout(resolve, 0));
 8555    assert.equal(localApiStopSawDaemonResolved, false);
 8556
 8557    releaseDaemonStop();
 8558    const stoppedSnapshot = await stopPromise;
 8559
 8560    assert.equal(stoppedSnapshot.runtime.started, false);
 8561    assert.equal(localApiStopSawDaemonResolved, true);
 8562  } finally {
 8563    rmSync(stateDir, {
 8564      force: true,
 8565      recursive: true
 8566    });
 8567  }
 8568});
 8569
 8570test("ConductorRuntime.stop closes active Firefox bridge clients and releases the local API listener", async () => {
 8571  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-runtime-stop-"));
 8572  const runtime = new ConductorRuntime(
 8573    {
 8574      nodeId: "mini-main",
 8575      host: "mini",
 8576      role: "primary",
 8577      controlApiBase: "https://control.example.test",
 8578      localApiBase: "http://127.0.0.1:0",
 8579      sharedToken: "replace-me",
 8580      paths: {
 8581        runsDir: "/tmp/runs",
 8582        stateDir
 8583      }
 8584    },
 8585    {
 8586      autoStartLoops: false,
 8587      now: () => 100
 8588    }
 8589  );
 8590
 8591  let client = null;
 8592
 8593  try {
 8594    const snapshot = await runtime.start();
 8595    const baseUrl = snapshot.controlApi.localApiBase;
 8596    const wsUrl = snapshot.controlApi.firefoxWsUrl;
 8597
 8598    assert.equal(typeof baseUrl, "string");
 8599    assert.equal(typeof wsUrl, "string");
 8600
 8601    const healthResponse = await fetch(`${baseUrl}/healthz`);
 8602    assert.equal(healthResponse.status, 200);
 8603
 8604    client = await connectFirefoxBridgeClient(wsUrl, "firefox-stop");
 8605
 8606    const closePromise = waitForWebSocketClose(client.socket);
 8607    const stoppedSnapshot = await runtime.stop();
 8608
 8609    assert.equal(stoppedSnapshot.runtime.started, false);
 8610    assert.deepEqual(await closePromise, {
 8611      code: 1001,
 8612      reason: "server shutdown"
 8613    });
 8614    await assertLocalApiListenerClosed(baseUrl);
 8615  } finally {
 8616    client?.queue.stop();
 8617    client?.socket.close(1000, "done");
 8618    await runtime.stop();
 8619    rmSync(stateDir, {
 8620      force: true,
 8621      recursive: true
 8622    });
 8623  }
 8624});
 8625
 8626test("ConductorRuntime.stop remains idempotent after the local API listener is closed", async () => {
 8627  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-runtime-stop-repeat-"));
 8628  const runtime = new ConductorRuntime(
 8629    {
 8630      nodeId: "mini-main",
 8631      host: "mini",
 8632      role: "primary",
 8633      controlApiBase: "https://control.example.test",
 8634      localApiBase: "http://127.0.0.1:0",
 8635      sharedToken: "replace-me",
 8636      paths: {
 8637        runsDir: "/tmp/runs",
 8638        stateDir
 8639      }
 8640    },
 8641    {
 8642      autoStartLoops: false,
 8643      now: () => 100
 8644    }
 8645  );
 8646
 8647  try {
 8648    const snapshot = await runtime.start();
 8649    const baseUrl = snapshot.controlApi.localApiBase;
 8650
 8651    assert.equal(typeof baseUrl, "string");
 8652
 8653    const firstStoppedSnapshot = await runtime.stop();
 8654    assert.equal(firstStoppedSnapshot.runtime.started, false);
 8655    await assertLocalApiListenerClosed(baseUrl);
 8656
 8657    const secondStoppedSnapshot = await runtime.stop();
 8658    assert.equal(secondStoppedSnapshot.runtime.started, false);
 8659    assert.equal(secondStoppedSnapshot.controlApi.localApiBase, baseUrl);
 8660    await assertLocalApiListenerClosed(baseUrl);
 8661  } finally {
 8662    await runtime.stop();
 8663    rmSync(stateDir, {
 8664      force: true,
 8665      recursive: true
 8666    });
 8667  }
 8668});
 8669
 8670test("ConductorRuntime fixture closes the local API listener when a started test aborts", async () => {
 8671  let baseUrl = null;
 8672
 8673  await assert.rejects(
 8674    withRuntimeFixture(async ({ runtime }) => {
 8675      const snapshot = await runtime.start();
 8676      baseUrl = snapshot.controlApi.localApiBase;
 8677
 8678      const healthResponse = await fetch(`${baseUrl}/healthz`);
 8679      assert.equal(healthResponse.status, 200);
 8680
 8681      throw new Error("forced runtime fixture failure");
 8682    }),
 8683    /forced runtime fixture failure/u
 8684  );
 8685
 8686  assert.equal(typeof baseUrl, "string");
 8687  await assertLocalApiListenerClosed(baseUrl);
 8688});
 8689
 8690test("ConductorRuntime exposes a local Firefox websocket bridge over the local API listener", async () => {
 8691  await withRuntimeFixture(async ({ runtime }) => {
 8692    let socket = null;
 8693    let queue = null;
 8694
 8695    try {
 8696      const snapshot = await runtime.start();
 8697      const wsUrl = snapshot.controlApi.firefoxWsUrl;
 8698      const baseUrl = snapshot.controlApi.localApiBase;
 8699      socket = new WebSocket(wsUrl);
 8700      queue = createWebSocketMessageQueue(socket);
 8701
 8702      await waitForWebSocketOpen(socket);
 8703
 8704      socket.send(
 8705        JSON.stringify({
 8706          type: "hello",
 8707          clientId: "firefox-test",
 8708          nodeType: "browser",
 8709          nodeCategory: "proxy",
 8710          nodePlatform: "firefox"
 8711        })
 8712      );
 8713
 8714      const helloAck = await queue.next((message) => message.type === "hello_ack");
 8715      assert.equal(helloAck.clientId, "firefox-test");
 8716      assert.equal(helloAck.protocol, "baa.browser.local");
 8717      assert.equal(helloAck.wsUrl, snapshot.controlApi.browserWsUrl);
 8718      assert.deepEqual(helloAck.wsCompatUrls, [snapshot.controlApi.firefoxWsUrl]);
 8719
 8720      const initialSnapshot = await queue.next(
 8721        (message) => message.type === "state_snapshot" && message.reason === "hello"
 8722      );
 8723      assert.equal(initialSnapshot.snapshot.system.mode, "running");
 8724      assert.equal(initialSnapshot.snapshot.browser.client_count, 1);
 8725
 8726      const credentialRequest = await queue.next((message) => message.type === "request_credentials");
 8727      assert.equal(credentialRequest.reason, "hello");
 8728
 8729      socket.send(
 8730        JSON.stringify({
 8731          type: "api_endpoints",
 8732          platform: "chatgpt",
 8733          account: "agent@example.com",
 8734          credential_fingerprint: "fp-chatgpt-test",
 8735          updated_at: 1_760_000_000_500,
 8736          endpoints: ["/backend-api/conversation", "/backend-api/models"],
 8737          endpoint_metadata: [
 8738            {
 8739              method: "POST",
 8740              path: "/backend-api/conversation",
 8741              first_seen_at: 1_760_000_000_100,
 8742              last_seen_at: 1_760_000_000_400
 8743            },
 8744            {
 8745              method: "GET",
 8746              path: "/backend-api/models",
 8747              first_seen_at: 1_760_000_000_200,
 8748              last_seen_at: 1_760_000_000_500
 8749            }
 8750          ]
 8751        })
 8752      );
 8753      socket.send(
 8754        JSON.stringify({
 8755          type: "credentials",
 8756          platform: "chatgpt",
 8757          account: "agent@example.com",
 8758          credential_fingerprint: "fp-chatgpt-test",
 8759          freshness: "fresh",
 8760          captured_at: 1_760_000_000_000,
 8761          last_seen_at: 1_760_000_000_500,
 8762          headers: {
 8763            authorization: "Bearer test-token",
 8764            cookie: "session=test"
 8765          },
 8766          timestamp: 1_760_000_000_000
 8767        })
 8768      );
 8769
 8770      const browserSnapshot = await queue.next(
 8771        (message) =>
 8772          message.type === "state_snapshot"
 8773          && message.snapshot.browser.clients.some((client) =>
 8774            client.client_id === "firefox-test"
 8775            && client.credentials.some((entry) =>
 8776              entry.platform === "chatgpt"
 8777              && entry.account === "agent@example.com"
 8778              && entry.credential_fingerprint === "fp-chatgpt-test"
 8779              && entry.header_count === 2
 8780            )
 8781            && client.request_hooks.some((entry) =>
 8782              entry.platform === "chatgpt"
 8783              && entry.account === "agent@example.com"
 8784              && entry.endpoint_count === 2
 8785            )
 8786          )
 8787      );
 8788      assert.equal(browserSnapshot.snapshot.browser.client_count, 1);
 8789
 8790      socket.send(
 8791        JSON.stringify({
 8792          type: "action_request",
 8793          action: "pause",
 8794          requestId: "req-pause",
 8795          requestedBy: "integration_test",
 8796          reason: "pause_via_ws"
 8797        })
 8798      );
 8799
 8800      const actionResult = await queue.next(
 8801        (message) => message.type === "action_result" && message.requestId === "req-pause"
 8802      );
 8803      assert.equal(actionResult.ok, true);
 8804      assert.equal(actionResult.system.mode, "paused");
 8805
 8806      const pausedSnapshot = await queue.next(
 8807        (message) => message.type === "state_snapshot" && message.snapshot.system.mode === "paused"
 8808      );
 8809      assert.equal(pausedSnapshot.snapshot.system.mode, "paused");
 8810
 8811      const systemStateResponse = await fetch(`${baseUrl}/v1/system/state`);
 8812      assert.equal(systemStateResponse.status, 200);
 8813      assert.equal((await systemStateResponse.json()).data.mode, "paused");
 8814
 8815      const stoppedSnapshot = await runtime.stop();
 8816      assert.equal(stoppedSnapshot.runtime.started, false);
 8817    } finally {
 8818      queue?.stop();
 8819
 8820      if (socket) {
 8821        const closePromise = waitForWebSocketClose(socket);
 8822        socket.close(1000, "done");
 8823        await closePromise;
 8824      }
 8825    }
 8826  });
 8827});
 8828
 8829test("ConductorRuntime accepts Safari bridge clients on the canonical browser websocket path", async () => {
 8830  await withRuntimeFixture(async ({ runtime }) => {
 8831    let client = null;
 8832
 8833    try {
 8834      const snapshot = await runtime.start();
 8835      const wsUrl = snapshot.controlApi.browserWsUrl;
 8836      const baseUrl = snapshot.controlApi.localApiBase;
 8837
 8838      client = await connectBrowserBridgeClient(wsUrl, "safari-test", "safari");
 8839
 8840      assert.equal(client.helloAck.clientId, "safari-test");
 8841      assert.equal(client.helloAck.protocol, "baa.browser.local");
 8842      assert.equal(client.helloAck.wsUrl, snapshot.controlApi.browserWsUrl);
 8843      assert.deepEqual(client.helloAck.wsCompatUrls, [snapshot.controlApi.firefoxWsUrl]);
 8844      assert.equal(client.initialSnapshot.snapshot.browser.clients[0]?.node_platform, "safari");
 8845      assert.equal(client.initialSnapshot.snapshot.browser.ws_path, "/ws/browser");
 8846
 8847      const browserStatus = await fetch(`${baseUrl}/v1/browser`);
 8848      assert.equal(browserStatus.status, 200);
 8849      const browserPayload = await browserStatus.json();
 8850      assert.equal(browserPayload.data.bridge.transport, "local_browser_ws");
 8851      assert.equal(browserPayload.data.bridge.ws_path, "/ws/browser");
 8852      assert.equal(browserPayload.data.bridge.ws_url, snapshot.controlApi.browserWsUrl);
 8853      assert.equal(browserPayload.data.current_client.client_id, "safari-test");
 8854      assert.equal(browserPayload.data.current_client.node_platform, "safari");
 8855    } finally {
 8856      client?.queue.stop();
 8857      client?.socket.close();
 8858
 8859      if (client?.socket) {
 8860        await waitForWebSocketClose(client.socket);
 8861      }
 8862    }
 8863  });
 8864});
 8865
 8866test("ConductorRuntime exposes Firefox outbound bridge commands and api request responses", async () => {
 8867  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-firefox-bridge-"));
 8868  const runtime = new ConductorRuntime(
 8869    {
 8870      nodeId: "mini-main",
 8871      host: "mini",
 8872      role: "primary",
 8873      controlApiBase: "https://control.example.test",
 8874      localApiBase: "http://127.0.0.1:0",
 8875      sharedToken: "replace-me",
 8876      paths: {
 8877        runsDir: "/tmp/runs",
 8878        stateDir
 8879      }
 8880    },
 8881    {
 8882      autoStartLoops: false,
 8883      now: () => 100
 8884    }
 8885  );
 8886
 8887  let firstClient = null;
 8888  let secondClient = null;
 8889
 8890  try {
 8891    const snapshot = await runtime.start();
 8892    const bridge = runtime.getFirefoxBridgeService();
 8893
 8894    assert.ok(bridge);
 8895
 8896    firstClient = await connectFirefoxBridgeClient(snapshot.controlApi.firefoxWsUrl, "firefox-a");
 8897    secondClient = await connectFirefoxBridgeClient(snapshot.controlApi.firefoxWsUrl, "firefox-b");
 8898
 8899    const openTabReceipt = bridge.openTab({
 8900      clientId: "firefox-a",
 8901      platform: "chatgpt"
 8902    });
 8903    assert.equal(openTabReceipt.clientId, "firefox-a");
 8904
 8905    const openTabMessage = await firstClient.queue.next((message) => message.type === "open_tab");
 8906    assert.equal(openTabMessage.platform, "chatgpt");
 8907
 8908    await assert.rejects(
 8909      secondClient.queue.next((message) => message.type === "open_tab", 250),
 8910      /timed out waiting for websocket message/u
 8911    );
 8912
 8913    const reloadReceipt = bridge.reload({
 8914      reason: "integration_test"
 8915    });
 8916    assert.equal(reloadReceipt.clientId, "firefox-b");
 8917
 8918    const reloadMessage = await secondClient.queue.next((message) => message.type === "reload");
 8919    assert.equal(reloadMessage.reason, "integration_test");
 8920
 8921    await assert.rejects(
 8922      firstClient.queue.next((message) => message.type === "reload", 250),
 8923      /timed out waiting for websocket message/u
 8924    );
 8925
 8926    const credentialReceipt = bridge.requestCredentials({
 8927      clientId: "firefox-a",
 8928      platform: "chatgpt",
 8929      reason: "integration_test"
 8930    });
 8931    assert.equal(credentialReceipt.clientId, "firefox-a");
 8932
 8933    const credentialMessage = await firstClient.queue.next(
 8934      (message) => message.type === "request_credentials" && message.reason === "integration_test"
 8935    );
 8936    assert.equal(credentialMessage.platform, "chatgpt");
 8937
 8938    const apiRequestPromise = bridge.apiRequest({
 8939      clientId: "firefox-b",
 8940      platform: "chatgpt",
 8941      method: "POST",
 8942      path: "/backend-api/conversation",
 8943      body: {
 8944        prompt: "hello"
 8945      },
 8946      headers: {
 8947        authorization: "Bearer bridge-token"
 8948      }
 8949    });
 8950    const apiRequestMessage = await secondClient.queue.next((message) => message.type === "api_request");
 8951    assert.equal(apiRequestMessage.method, "POST");
 8952    assert.equal(apiRequestMessage.path, "/backend-api/conversation");
 8953    assert.equal(apiRequestMessage.platform, "chatgpt");
 8954    assert.equal(apiRequestMessage.headers.authorization, "Bearer bridge-token");
 8955
 8956    secondClient.socket.send(
 8957      JSON.stringify({
 8958        type: "api_response",
 8959        id: apiRequestMessage.id,
 8960        ok: true,
 8961        status: 202,
 8962        body: {
 8963          accepted: true
 8964        }
 8965      })
 8966    );
 8967
 8968    const apiResponse = await apiRequestPromise;
 8969    assert.equal(apiResponse.clientId, "firefox-b");
 8970    assert.equal(apiResponse.connectionId, reloadReceipt.connectionId);
 8971    assert.equal(apiResponse.id, apiRequestMessage.id);
 8972    assert.equal(apiResponse.ok, true);
 8973    assert.equal(apiResponse.status, 202);
 8974    assert.deepEqual(apiResponse.body, {
 8975      accepted: true
 8976    });
 8977  } finally {
 8978    firstClient?.queue.stop();
 8979    secondClient?.queue.stop();
 8980    firstClient?.socket.close(1000, "done");
 8981    secondClient?.socket.close(1000, "done");
 8982    await runtime.stop();
 8983    rmSync(stateDir, {
 8984      force: true,
 8985      recursive: true
 8986    });
 8987  }
 8988});
 8989
 8990test("ConductorRuntime routes browser.final_message into live instruction ingest and exposes the latest summary", async () => {
 8991  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-final-message-ingest-"));
 8992  const hostOpsDir = mkdtempSync(join(tmpdir(), "baa-conductor-final-message-host-"));
 8993  const runtime = new ConductorRuntime(
 8994    {
 8995      nodeId: "mini-main",
 8996      host: "mini",
 8997      role: "primary",
 8998      controlApiBase: "https://control.example.test",
 8999      localApiBase: "http://127.0.0.1:0",
 9000      sharedToken: "replace-me",
 9001      paths: {
 9002        runsDir: "/tmp/runs",
 9003        stateDir
 9004      }
 9005    },
 9006    {
 9007      autoStartLoops: false,
 9008      now: () => 100
 9009    }
 9010  );
 9011
 9012  let client = null;
 9013
 9014  try {
 9015    const snapshot = await runtime.start();
 9016    const baseUrl = snapshot.controlApi.localApiBase;
 9017    const messageText = [
 9018      "```baa",
 9019      "@conductor::describe",
 9020      "```",
 9021      "",
 9022      "```baa",
 9023      `@conductor::exec::{"command":"printf 'ws-live\\n' >> final-message-ingest.txt","cwd":${JSON.stringify(hostOpsDir)}}`,
 9024      "```"
 9025    ].join("\n");
 9026
 9027    client = await connectFirefoxBridgeClient(snapshot.controlApi.firefoxWsUrl, "firefox-final-message-ingest");
 9028
 9029    client.socket.send(
 9030      JSON.stringify({
 9031        type: "browser.final_message",
 9032        platform: "chatgpt",
 9033        assistant_message_id: "msg-final-message-ingest",
 9034        raw_text: messageText,
 9035        observed_at: 1_710_000_010_000
 9036      })
 9037    );
 9038
 9039    const executedSnapshot = await client.queue.next(
 9040      (message) =>
 9041        message.type === "state_snapshot"
 9042        && message.reason === "instruction_ingest"
 9043        && message.snapshot.browser.instruction_ingest.last_ingest?.assistant_message_id === "msg-final-message-ingest"
 9044        && message.snapshot.browser.instruction_ingest.last_ingest?.status === "executed"
 9045    );
 9046
 9047    assert.equal(
 9048      executedSnapshot.snapshot.browser.instruction_ingest.last_ingest.conversation_id,
 9049      null
 9050    );
 9051    assert.deepEqual(
 9052      executedSnapshot.snapshot.browser.instruction_ingest.last_ingest.instruction_tools,
 9053      ["conductor::describe", "conductor::exec"]
 9054    );
 9055    assert.equal(
 9056      executedSnapshot.snapshot.browser.instruction_ingest.last_ingest.execution_ok_count,
 9057      2
 9058    );
 9059    assert.equal(readFileSync(join(hostOpsDir, "final-message-ingest.txt"), "utf8"), "ws-live\n");
 9060
 9061    const persistedStore = new ArtifactStore({
 9062      artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
 9063      databasePath: join(stateDir, ARTIFACT_DB_FILENAME),
 9064      publicBaseUrl: "https://control.example.test"
 9065    });
 9066
 9067    try {
 9068      const persistedMessage = await persistedStore.getMessage("msg-final-message-ingest");
 9069      const persistedExecutions = await persistedStore.listExecutions({
 9070        messageId: "msg-final-message-ingest"
 9071      });
 9072      const persistedSessions = await persistedStore.getLatestSessions(1);
 9073
 9074      assert.equal(persistedMessage?.rawText, messageText);
 9075      assert.equal(existsSync(join(stateDir, ARTIFACTS_DIRNAME, "msg", "msg-final-message-ingest.txt")), true);
 9076      assert.equal(persistedExecutions.length, 2);
 9077      assert.ok(persistedExecutions.every((execution) =>
 9078        existsSync(join(stateDir, ARTIFACTS_DIRNAME, execution.staticPath))
 9079      ));
 9080      assert.equal(persistedSessions[0]?.messageCount, 1);
 9081      assert.equal(persistedSessions[0]?.executionCount, 2);
 9082      assert.equal(existsSync(join(stateDir, ARTIFACTS_DIRNAME, "session", "latest.txt")), true);
 9083    } finally {
 9084      persistedStore.close();
 9085    }
 9086
 9087    const browserStatusResponse = await fetch(`${baseUrl}/v1/browser`);
 9088    assert.equal(browserStatusResponse.status, 200);
 9089    const browserStatusPayload = await browserStatusResponse.json();
 9090    assert.equal(browserStatusPayload.data.instruction_ingest.last_ingest.status, "executed");
 9091    assert.equal(
 9092      browserStatusPayload.data.instruction_ingest.last_execute.assistant_message_id,
 9093      "msg-final-message-ingest"
 9094    );
 9095
 9096    client.socket.send(
 9097      JSON.stringify({
 9098        type: "browser.final_message",
 9099        platform: "chatgpt",
 9100        conversation_id: "conv-replayed",
 9101        assistant_message_id: "msg-final-message-ingest",
 9102        raw_text: messageText,
 9103        observed_at: 1_710_000_010_500
 9104      })
 9105    );
 9106
 9107    const duplicateSnapshot = await client.queue.next(
 9108      (message) =>
 9109        message.type === "state_snapshot"
 9110        && message.reason === "instruction_ingest"
 9111        && message.snapshot.browser.instruction_ingest.last_ingest?.assistant_message_id === "msg-final-message-ingest"
 9112        && message.snapshot.browser.instruction_ingest.last_ingest?.status === "duplicate_message"
 9113    );
 9114
 9115    assert.equal(
 9116      duplicateSnapshot.snapshot.browser.instruction_ingest.last_execute.status,
 9117      "executed"
 9118    );
 9119    assert.equal(readFileSync(join(hostOpsDir, "final-message-ingest.txt"), "utf8"), "ws-live\n");
 9120  } finally {
 9121    client?.queue.stop();
 9122    client?.socket.close(1000, "done");
 9123    await runtime.stop();
 9124    rmSync(stateDir, {
 9125      force: true,
 9126      recursive: true
 9127    });
 9128    rmSync(hostOpsDir, {
 9129      force: true,
 9130      recursive: true
 9131    });
 9132  }
 9133});
 9134
 9135test("observeRenewalConversation ignores inactive remote links and creates a new local conversation", async () => {
 9136  const rootDir = mkdtempSync(join(tmpdir(), "baa-conductor-renewal-observe-active-link-"));
 9137  const stateDir = join(rootDir, "state");
 9138  const store = new ArtifactStore({
 9139    artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
 9140    databasePath: join(stateDir, ARTIFACT_DB_FILENAME)
 9141  });
 9142
 9143  try {
 9144    const firstObservation = await observeRenewalConversation({
 9145      assistantMessageId: "msg-renewal-observe-1",
 9146      clientId: "firefox-chatgpt",
 9147      observedAt: Date.UTC(2026, 2, 30, 12, 0, 0),
 9148      pageTitle: "Renewal Thread",
 9149      pageUrl: "https://chatgpt.com/c/conv-renewal-observe",
 9150      platform: "chatgpt",
 9151      remoteConversationId: "conv-renewal-observe",
 9152      store
 9153    });
 9154
 9155    assert.equal(firstObservation.created, true);
 9156    assert.equal(firstObservation.resolvedBy, "new");
 9157
 9158    await store.upsertConversationLink({
 9159      isActive: false,
 9160      linkId: firstObservation.link.linkId,
 9161      localConversationId: firstObservation.conversation.localConversationId,
 9162      observedAt: firstObservation.link.observedAt,
 9163      platform: "chatgpt",
 9164      updatedAt: Date.UTC(2026, 2, 30, 12, 1, 0)
 9165    });
 9166
 9167    assert.equal(
 9168      await store.findConversationLinkByRemoteConversation("chatgpt", "conv-renewal-observe"),
 9169      null
 9170    );
 9171
 9172    const secondObservation = await observeRenewalConversation({
 9173      assistantMessageId: "msg-renewal-observe-2",
 9174      clientId: "firefox-chatgpt",
 9175      observedAt: Date.UTC(2026, 2, 30, 12, 2, 0),
 9176      pageTitle: "Renewal Thread Reopened",
 9177      pageUrl: "https://chatgpt.com/c/conv-renewal-observe",
 9178      platform: "chatgpt",
 9179      remoteConversationId: "conv-renewal-observe",
 9180      store
 9181    });
 9182
 9183    assert.equal(secondObservation.created, true);
 9184    assert.equal(secondObservation.resolvedBy, "new");
 9185    assert.notEqual(
 9186      secondObservation.conversation.localConversationId,
 9187      firstObservation.conversation.localConversationId
 9188    );
 9189    assert.equal(secondObservation.link.linkId, firstObservation.link.linkId);
 9190    assert.equal(secondObservation.link.localConversationId, secondObservation.conversation.localConversationId);
 9191    assert.equal(secondObservation.link.isActive, true);
 9192    assert.deepEqual(
 9193      await store.findConversationLinkByRemoteConversation("chatgpt", "conv-renewal-observe"),
 9194      secondObservation.link
 9195    );
 9196
 9197    const originalConversation = await store.getLocalConversation(firstObservation.conversation.localConversationId);
 9198    assert.ok(originalConversation);
 9199    assert.equal(originalConversation.lastMessageId, "msg-renewal-observe-1");
 9200
 9201    const replacementConversation = await store.getLocalConversation(secondObservation.conversation.localConversationId);
 9202    assert.ok(replacementConversation);
 9203    assert.equal(replacementConversation.lastMessageId, "msg-renewal-observe-2");
 9204  } finally {
 9205    store.close();
 9206    rmSync(rootDir, {
 9207      force: true,
 9208      recursive: true
 9209    });
 9210  }
 9211});
 9212
 9213test("observeRenewalConversation reuses the same link when remote conversation id is missing", async () => {
 9214  const rootDir = mkdtempSync(join(tmpdir(), "baa-conductor-renewal-observe-null-remote-"));
 9215  const stateDir = join(rootDir, "state");
 9216  const store = new ArtifactStore({
 9217    artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
 9218    databasePath: join(stateDir, ARTIFACT_DB_FILENAME)
 9219  });
 9220  const firstObservedAt = Date.UTC(2026, 2, 30, 12, 3, 0);
 9221
 9222  try {
 9223    const firstObservation = await observeRenewalConversation({
 9224      assistantMessageId: "msg-renewal-null-remote-1",
 9225      clientId: "firefox-gemini",
 9226      observedAt: firstObservedAt,
 9227      pageTitle: "Gemini Thread",
 9228      pageUrl: "https://gemini.google.com/app",
 9229      platform: "gemini",
 9230      store
 9231    });
 9232    const secondObservation = await observeRenewalConversation({
 9233      assistantMessageId: "msg-renewal-null-remote-2",
 9234      clientId: "firefox-gemini",
 9235      observedAt: firstObservedAt + 60_000,
 9236      pageTitle: "Gemini Thread Updated",
 9237      pageUrl: "https://gemini.google.com/app",
 9238      platform: "gemini",
 9239      store
 9240    });
 9241
 9242    assert.equal(firstObservation.created, true);
 9243    assert.equal(firstObservation.resolvedBy, "new");
 9244    assert.equal(secondObservation.created, false);
 9245    assert.equal(secondObservation.resolvedBy, "active_link");
 9246    assert.equal(
 9247      secondObservation.conversation.localConversationId,
 9248      firstObservation.conversation.localConversationId
 9249    );
 9250    assert.equal(secondObservation.link.linkId, firstObservation.link.linkId);
 9251    assert.equal(secondObservation.link.remoteConversationId, null);
 9252    assert.equal(secondObservation.link.routePath, "/app");
 9253    assert.equal(secondObservation.link.pageTitle, "Gemini Thread Updated");
 9254    assert.deepEqual(await store.listConversationLinks({ platform: "gemini" }), [secondObservation.link]);
 9255
 9256    const conversation = await store.getLocalConversation(firstObservation.conversation.localConversationId);
 9257    assert.ok(conversation);
 9258    assert.equal(conversation.lastMessageId, "msg-renewal-null-remote-2");
 9259  } finally {
 9260    store.close();
 9261    rmSync(rootDir, {
 9262      force: true,
 9263      recursive: true
 9264    });
 9265  }
 9266});
 9267
 9268test("observeRenewalConversation prefers conversation-derived business targets over stale legacy tab links", async () => {
 9269  const rootDir = mkdtempSync(join(tmpdir(), "baa-conductor-renewal-target-priority-"));
 9270  const stateDir = join(rootDir, "state");
 9271  const store = new ArtifactStore({
 9272    artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
 9273    databasePath: join(stateDir, ARTIFACT_DB_FILENAME)
 9274  });
 9275  const observedAt = Date.UTC(2026, 2, 30, 12, 5, 0);
 9276
 9277  try {
 9278    await store.upsertLocalConversation({
 9279      localConversationId: "lc-target-priority-tab-1",
 9280      platform: "chatgpt"
 9281    });
 9282    await store.upsertLocalConversation({
 9283      localConversationId: "lc-target-priority-tab-2",
 9284      platform: "chatgpt"
 9285    });
 9286
 9287    await store.upsertConversationLink({
 9288      clientId: "firefox-chatgpt",
 9289      createdAt: observedAt - 10_000,
 9290      isActive: true,
 9291      linkId: "link-target-priority-tab-1",
 9292      localConversationId: "lc-target-priority-tab-1",
 9293      observedAt: observedAt - 10_000,
 9294      pageTitle: "Old Tab State",
 9295      pageUrl: "https://chatgpt.com/",
 9296      platform: "chatgpt",
 9297      routePath: "/",
 9298      routePattern: "/",
 9299      targetId: "tab:1",
 9300      targetKind: "browser.proxy_delivery",
 9301      targetPayload: {
 9302        clientId: "firefox-chatgpt",
 9303        tabId: 1
 9304      },
 9305      updatedAt: observedAt - 10_000
 9306    });
 9307    await store.upsertConversationLink({
 9308      clientId: "firefox-chatgpt",
 9309      createdAt: observedAt - 5_000,
 9310      isActive: true,
 9311      linkId: "link-target-priority-tab-2",
 9312      localConversationId: "lc-target-priority-tab-2",
 9313      observedAt: observedAt - 5_000,
 9314      pageTitle: "Shared Thread",
 9315      pageUrl: "https://chatgpt.com/c/conv-target-priority",
 9316      platform: "chatgpt",
 9317      routePath: "/c/conv-target-priority",
 9318      routePattern: "/c/:conversationId",
 9319      targetId: "tab:2",
 9320      targetKind: "browser.proxy_delivery",
 9321      targetPayload: {
 9322        clientId: "firefox-chatgpt",
 9323        pageUrl: "https://chatgpt.com/c/conv-target-priority",
 9324        tabId: 2
 9325      },
 9326      updatedAt: observedAt - 5_000
 9327    });
 9328
 9329    const observation = await observeRenewalConversation({
 9330      assistantMessageId: "msg-target-priority",
 9331      clientId: "firefox-chatgpt",
 9332      observedAt,
 9333      pageTitle: "Shared Thread",
 9334      pageUrl: "https://chatgpt.com/c/conv-target-priority",
 9335      platform: "chatgpt",
 9336      route: {
 9337        assistantMessageId: "msg-target-priority",
 9338        conversationId: null,
 9339        observedAt,
 9340        organizationId: null,
 9341        pageTitle: "Shared Thread",
 9342        pageUrl: "https://chatgpt.com/c/conv-target-priority",
 9343        platform: "chatgpt",
 9344        shellPage: false,
 9345        tabId: 1
 9346      },
 9347      store
 9348    });
 9349
 9350    assert.equal(observation.created, false);
 9351    assert.equal(observation.resolvedBy, "active_link");
 9352    assert.equal(observation.conversation.localConversationId, "lc-target-priority-tab-2");
 9353    assert.equal(observation.link.linkId, "link-target-priority-tab-2");
 9354    assert.equal(observation.link.targetId, "conversation:chatgpt:conv-target-priority");
 9355    assert.equal(observation.link.pageTitle, "Shared Thread");
 9356    assert.equal(observation.link.pageUrl, "https://chatgpt.com/c/conv-target-priority");
 9357    assert.equal(observation.link.routePath, "/c/conv-target-priority");
 9358    assert.equal(observation.link.remoteConversationId, "conv-target-priority");
 9359
 9360    const preservedLink = await store.getConversationLink("link-target-priority-tab-2");
 9361    assert.ok(preservedLink);
 9362    assert.equal(preservedLink.isActive, true);
 9363    assert.equal(preservedLink.localConversationId, "lc-target-priority-tab-2");
 9364    assert.equal(preservedLink.remoteConversationId, "conv-target-priority");
 9365    assert.equal(preservedLink.targetId, "conversation:chatgpt:conv-target-priority");
 9366
 9367    const legacyTabLink = await store.getConversationLink("link-target-priority-tab-1");
 9368    assert.ok(legacyTabLink);
 9369    assert.equal(legacyTabLink.isActive, false);
 9370    assert.equal(legacyTabLink.localConversationId, "lc-target-priority-tab-1");
 9371
 9372    assert.deepEqual(
 9373      await store.findConversationLinkByRemoteConversation("chatgpt", "conv-target-priority"),
 9374      preservedLink
 9375    );
 9376  } finally {
 9377    store.close();
 9378    rmSync(rootDir, {
 9379      force: true,
 9380      recursive: true
 9381    });
 9382  }
 9383});
 9384
 9385test("observeRenewalConversation paginates exact-match scans and reports diagnostics at the scan limit", async () => {
 9386  const rootDir = mkdtempSync(join(tmpdir(), "baa-conductor-renewal-scan-limit-"));
 9387  const stateDir = join(rootDir, "state");
 9388  const store = new ArtifactStore({
 9389    artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
 9390    databasePath: join(stateDir, ARTIFACT_DB_FILENAME)
 9391  });
 9392  const observedAt = Date.UTC(2026, 2, 30, 12, 8, 0);
 9393  const diagnostics = [];
 9394
 9395  try {
 9396    for (let index = 0; index < 50; index += 1) {
 9397      const localConversationId = `lc-scan-limit-decoy-${index}`;
 9398      const linkId = `link-scan-limit-decoy-${index}`;
 9399      const decoyObservedAt = observedAt + 50_000 - index;
 9400
 9401      await store.upsertLocalConversation({
 9402        localConversationId,
 9403        platform: "chatgpt"
 9404      });
 9405      await store.upsertConversationLink({
 9406        clientId: "firefox-chatgpt",
 9407        createdAt: decoyObservedAt,
 9408        isActive: true,
 9409        linkId,
 9410        localConversationId,
 9411        observedAt: decoyObservedAt,
 9412        pageTitle: `Decoy Thread ${index}`,
 9413        pageUrl: `https://chatgpt.com/c/conv-scan-limit-decoy-${index}`,
 9414        platform: "chatgpt",
 9415        remoteConversationId: `conv-scan-limit-decoy-${index}`,
 9416        routePath: `/c/conv-scan-limit-decoy-${index}`,
 9417        routePattern: "/c/:conversationId",
 9418        targetId: "tab:1",
 9419        targetKind: "browser.proxy_delivery",
 9420        targetPayload: {
 9421          clientId: "firefox-chatgpt",
 9422          tabId: 1
 9423        },
 9424        updatedAt: decoyObservedAt
 9425      });
 9426    }
 9427
 9428    await store.upsertLocalConversation({
 9429      localConversationId: "lc-scan-limit-target",
 9430      platform: "chatgpt"
 9431    });
 9432    await store.upsertConversationLink({
 9433      clientId: "firefox-chatgpt",
 9434      createdAt: observedAt - 60_000,
 9435      isActive: true,
 9436      linkId: "link-scan-limit-target",
 9437      localConversationId: "lc-scan-limit-target",
 9438      observedAt: observedAt - 60_000,
 9439      pageTitle: "Shared Thread",
 9440      pageUrl: "https://chatgpt.com/c/conv-scan-limit-target",
 9441      platform: "chatgpt",
 9442      remoteConversationId: "conv-scan-limit-target-existing",
 9443      routePath: "/c/conv-scan-limit-target",
 9444      routePattern: "/c/:conversationId",
 9445      targetId: "tab:1",
 9446      targetKind: "browser.proxy_delivery",
 9447      targetPayload: {
 9448        clientId: "firefox-chatgpt",
 9449        pageUrl: "https://chatgpt.com/c/conv-scan-limit-target",
 9450        tabId: 1
 9451      },
 9452      updatedAt: observedAt - 60_000
 9453    });
 9454
 9455    const observation = await observeRenewalConversation({
 9456      assistantMessageId: "msg-scan-limit",
 9457      clientId: "firefox-chatgpt",
 9458      onDiagnostic: (diagnostic) => diagnostics.push(diagnostic),
 9459      observedAt,
 9460      pageTitle: "Shared Thread",
 9461      pageUrl: "https://chatgpt.com/c/conv-scan-limit-target",
 9462      platform: "chatgpt",
 9463      route: {
 9464        assistantMessageId: "msg-scan-limit",
 9465        conversationId: null,
 9466        observedAt,
 9467        organizationId: null,
 9468        pageTitle: "Shared Thread",
 9469        pageUrl: "https://chatgpt.com/c/conv-scan-limit-target",
 9470        platform: "chatgpt",
 9471        shellPage: false,
 9472        tabId: 1
 9473      },
 9474      store
 9475    });
 9476
 9477    assert.equal(observation.created, false);
 9478    assert.equal(observation.resolvedBy, "active_link");
 9479    assert.equal(observation.conversation.localConversationId, "lc-scan-limit-target");
 9480    assert.equal(observation.link.linkId, "link-scan-limit-target");
 9481    assert.equal(observation.link.targetId, "conversation:chatgpt:conv-scan-limit-target");
 9482
 9483    const legacyTabLinks = await store.listConversationLinks({
 9484      clientId: "firefox-chatgpt",
 9485      limit: 100,
 9486      platform: "chatgpt",
 9487      targetId: "tab:1"
 9488    });
 9489    assert.equal(legacyTabLinks.length, 50);
 9490    assert.equal(
 9491      legacyTabLinks.filter((link) => link.isActive).length,
 9492      0
 9493    );
 9494
 9495    const conversationLinks = await store.listConversationLinks({
 9496      clientId: "firefox-chatgpt",
 9497      limit: 10,
 9498      platform: "chatgpt",
 9499      targetId: "conversation:chatgpt:conv-scan-limit-target"
 9500    });
 9501    assert.equal(conversationLinks.length, 1);
 9502    assert.equal(conversationLinks[0]?.linkId, "link-scan-limit-target");
 9503    assert.equal(
 9504      conversationLinks.filter((link) => link.isActive).length,
 9505      1
 9506    );
 9507    assert.equal(
 9508      conversationLinks.find((link) => link.linkId === "link-scan-limit-target")?.isActive,
 9509      true
 9510    );
 9511    assert.deepEqual(diagnostics, [
 9512      {
 9513        clientId: "firefox-chatgpt",
 9514        code: "conversation_link_scan_limit_reached",
 9515        limit: 50,
 9516        offset: 0,
 9517        operation: "resolve",
 9518        platform: "chatgpt",
 9519        signal: "target_id"
 9520      },
 9521      {
 9522        clientId: "firefox-chatgpt",
 9523        code: "conversation_link_scan_limit_reached",
 9524        limit: 50,
 9525        offset: 0,
 9526        operation: "deactivate",
 9527        platform: "chatgpt",
 9528        signal: "target_id"
 9529      }
 9530    ]);
 9531  } finally {
 9532    store.close();
 9533    rmSync(rootDir, {
 9534      force: true,
 9535      recursive: true
 9536    });
 9537  }
 9538});
 9539
 9540test("ConductorRuntime persists renewal conversation links from browser.final_message and exposes renewal controls", async () => {
 9541  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-renewal-control-"));
 9542  const runtime = new ConductorRuntime(
 9543    {
 9544      nodeId: "mini-main",
 9545      host: "mini",
 9546      role: "primary",
 9547      controlApiBase: "https://control.example.test",
 9548      localApiBase: "http://127.0.0.1:0",
 9549      sharedToken: "replace-me",
 9550      paths: {
 9551        runsDir: "/tmp/runs",
 9552        stateDir
 9553      }
 9554    },
 9555    {
 9556      autoStartLoops: false,
 9557      now: () => 200
 9558    }
 9559  );
 9560
 9561  let client = null;
 9562
 9563  try {
 9564    const snapshot = await runtime.start();
 9565    const baseUrl = snapshot.controlApi.localApiBase;
 9566    client = await connectFirefoxBridgeClient(snapshot.controlApi.firefoxWsUrl, "firefox-renewal-control");
 9567
 9568    client.socket.send(
 9569      JSON.stringify({
 9570        type: "browser.final_message",
 9571        platform: "chatgpt",
 9572        conversation_id: "conv-renewal-control",
 9573        assistant_message_id: "msg-renewal-control-1",
 9574        raw_text: "hello renewal control",
 9575        observed_at: 1_710_000_030_000,
 9576        page_title: "ChatGPT Renewal",
 9577        page_url: "https://chatgpt.com/c/conv-renewal-control",
 9578        tab_id: 42
 9579      })
 9580    );
 9581
 9582    const linksPayload = await waitForCondition(async () => {
 9583      const linksResponse = await fetch(
 9584        `${baseUrl}/v1/renewal/links?platform=chatgpt&remote_conversation_id=conv-renewal-control`
 9585      );
 9586      assert.equal(linksResponse.status, 200);
 9587      const payload = await linksResponse.json();
 9588      assert.equal(payload.data.count, 1);
 9589      return payload;
 9590    }, 5_000, 50);
 9591    assert.equal(linksPayload.data.links[0].route.pattern, "/c/:conversationId");
 9592    assert.equal(linksPayload.data.links[0].target.kind, "browser.proxy_delivery");
 9593    assert.equal(linksPayload.data.links[0].target.payload.tabId, 42);
 9594    const localConversationId = linksPayload.data.links[0].local_conversation_id;
 9595
 9596    const conversationResponse = await fetch(`${baseUrl}/v1/renewal/conversations/${localConversationId}`);
 9597    assert.equal(conversationResponse.status, 200);
 9598    const conversationPayload = await conversationResponse.json();
 9599    assert.equal(conversationPayload.data.local_conversation_id, localConversationId);
 9600    assert.equal(conversationPayload.data.automation_status, "manual");
 9601    assert.equal(conversationPayload.data.last_message_id, "msg-renewal-control-1");
 9602    assert.equal(conversationPayload.data.links.length, 1);
 9603
 9604    const autoResponse = await fetch(
 9605      `${baseUrl}/v1/renewal/conversations/${localConversationId}/auto`,
 9606      {
 9607        method: "POST"
 9608      }
 9609    );
 9610    assert.equal(autoResponse.status, 200);
 9611    const autoPayload = await autoResponse.json();
 9612    assert.equal(autoPayload.data.automation_status, "auto");
 9613    assert.equal(autoPayload.data.paused_at, undefined);
 9614
 9615    const pausedResponse = await fetch(
 9616      `${baseUrl}/v1/renewal/conversations/${localConversationId}/paused`,
 9617      {
 9618        method: "POST"
 9619      }
 9620    );
 9621    assert.equal(pausedResponse.status, 200);
 9622    const pausedPayload = await pausedResponse.json();
 9623    assert.equal(pausedPayload.data.automation_status, "paused");
 9624    assert.equal(typeof pausedPayload.data.paused_at, "number");
 9625
 9626    client.socket.send(
 9627      JSON.stringify({
 9628        type: "browser.final_message",
 9629        platform: "chatgpt",
 9630        conversation_id: "conv-renewal-control",
 9631        assistant_message_id: "msg-renewal-control-2",
 9632        raw_text: "hello renewal control updated",
 9633        observed_at: 1_710_000_031_000,
 9634        page_title: "ChatGPT Renewal Updated",
 9635        page_url: "https://chatgpt.com/c/conv-renewal-control",
 9636        tab_id: 42
 9637      })
 9638    );
 9639
 9640    const updatedConversationPayload = await waitForCondition(async () => {
 9641      const updatedConversationResponse = await fetch(
 9642        `${baseUrl}/v1/renewal/conversations/${localConversationId}`
 9643      );
 9644      assert.equal(updatedConversationResponse.status, 200);
 9645      const payload = await updatedConversationResponse.json();
 9646      assert.equal(payload.data.last_message_id, "msg-renewal-control-2");
 9647      return payload;
 9648    }, 5_000, 50);
 9649    assert.equal(updatedConversationPayload.data.local_conversation_id, localConversationId);
 9650    assert.equal(updatedConversationPayload.data.automation_status, "paused");
 9651    assert.equal(updatedConversationPayload.data.last_message_id, "msg-renewal-control-2");
 9652    assert.equal(updatedConversationPayload.data.active_link.page_title, "ChatGPT Renewal Updated");
 9653
 9654    const listResponse = await fetch(`${baseUrl}/v1/renewal/conversations?platform=chatgpt&status=paused`);
 9655    assert.equal(listResponse.status, 200);
 9656    const listPayload = await listResponse.json();
 9657    assert.equal(listPayload.data.count, 1);
 9658    assert.equal(listPayload.data.conversations[0].local_conversation_id, localConversationId);
 9659    assert.equal(listPayload.data.conversations[0].active_link.link_id, linksPayload.data.links[0].link_id);
 9660
 9661    const manualResponse = await fetch(
 9662      `${baseUrl}/v1/renewal/conversations/${localConversationId}/manual`,
 9663      {
 9664        method: "POST"
 9665      }
 9666    );
 9667    assert.equal(manualResponse.status, 200);
 9668    const manualPayload = await manualResponse.json();
 9669    assert.equal(manualPayload.data.automation_status, "manual");
 9670    assert.equal(manualPayload.data.paused_at, undefined);
 9671  } finally {
 9672    client?.queue.stop();
 9673    client?.socket.close(1000, "done");
 9674    await runtime.stop();
 9675    rmSync(stateDir, {
 9676      force: true,
 9677      recursive: true
 9678    });
 9679  }
 9680});
 9681
 9682test("ConductorRuntime broadcasts conversation automation snapshots after renewal API mutations", async () => {
 9683  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-overlay-automation-sync-"));
 9684  const runtime = new ConductorRuntime(
 9685    {
 9686      nodeId: "mini-main",
 9687      host: "mini",
 9688      role: "primary",
 9689      controlApiBase: "https://control.example.test",
 9690      localApiBase: "http://127.0.0.1:0",
 9691      sharedToken: "replace-me",
 9692      paths: {
 9693        runsDir: "/tmp/runs",
 9694        stateDir
 9695      }
 9696    },
 9697    {
 9698      autoStartLoops: false,
 9699      now: () => 250
 9700    }
 9701  );
 9702
 9703  let client = null;
 9704
 9705  try {
 9706    const snapshot = await runtime.start();
 9707    const baseUrl = snapshot.controlApi.localApiBase;
 9708    client = await connectFirefoxBridgeClient(snapshot.controlApi.firefoxWsUrl, "firefox-overlay-automation");
 9709
 9710    client.socket.send(
 9711      JSON.stringify({
 9712        type: "browser.final_message",
 9713        platform: "chatgpt",
 9714        conversation_id: "conv-overlay-automation",
 9715        assistant_message_id: "msg-overlay-automation-1",
 9716        raw_text: "hello overlay automation",
 9717        observed_at: 1_710_000_060_000,
 9718        page_title: "ChatGPT Overlay Automation",
 9719        page_url: "https://chatgpt.com/c/conv-overlay-automation",
 9720        tab_id: 99
 9721      })
 9722    );
 9723
 9724    const localConversationId = await waitForCondition(async () => {
 9725      const linksResponse = await fetch(
 9726        `${baseUrl}/v1/renewal/links?platform=chatgpt&remote_conversation_id=conv-overlay-automation`
 9727      );
 9728      assert.equal(linksResponse.status, 200);
 9729      const payload = await linksResponse.json();
 9730      assert.equal(payload.data.count, 1);
 9731      return payload.data.links[0].local_conversation_id;
 9732    }, 5_000, 50);
 9733
 9734    const pauseResponse = await fetch(
 9735      `${baseUrl}/v1/renewal/conversations/${localConversationId}/paused`,
 9736      {
 9737        method: "POST",
 9738        headers: {
 9739          "content-type": "application/json"
 9740        },
 9741        body: JSON.stringify({
 9742          pause_reason: "user_pause"
 9743        })
 9744      }
 9745    );
 9746    assert.equal(pauseResponse.status, 200);
 9747
 9748    const pausedSnapshot = await client.queue.next(
 9749      (message) =>
 9750        message.type === "state_snapshot"
 9751        && Array.isArray(message.snapshot?.browser?.automation_conversations)
 9752        && message.snapshot.browser.automation_conversations.some((entry) =>
 9753          entry.platform === "chatgpt"
 9754          && entry.remote_conversation_id === "conv-overlay-automation"
 9755          && entry.automation_status === "paused"
 9756          && entry.pause_reason === "user_pause"
 9757        ),
 9758      8_000
 9759    );
 9760    assert.equal(
 9761      pausedSnapshot.snapshot.browser.automation_conversations.find((entry) =>
 9762        entry.remote_conversation_id === "conv-overlay-automation"
 9763      )?.local_conversation_id,
 9764      localConversationId
 9765    );
 9766
 9767    const autoResponse = await fetch(
 9768      `${baseUrl}/v1/renewal/conversations/${localConversationId}/auto`,
 9769      {
 9770        method: "POST"
 9771      }
 9772    );
 9773    assert.equal(autoResponse.status, 200);
 9774
 9775    await client.queue.next(
 9776      (message) =>
 9777        message.type === "state_snapshot"
 9778        && Array.isArray(message.snapshot?.browser?.automation_conversations)
 9779        && message.snapshot.browser.automation_conversations.some((entry) =>
 9780          entry.platform === "chatgpt"
 9781          && entry.remote_conversation_id === "conv-overlay-automation"
 9782          && entry.automation_status === "auto"
 9783        ),
 9784      8_000
 9785    );
 9786  } finally {
 9787    client?.queue.stop();
 9788    client?.socket.close(1000, "done");
 9789    await runtime.stop();
 9790    rmSync(stateDir, {
 9791      force: true,
 9792      recursive: true
 9793    });
 9794  }
 9795});
 9796
 9797test("ConductorRuntime gives control instructions priority over ordinary baa instructions and persists pause_reason", async () => {
 9798  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-control-priority-"));
 9799  const hostOpsDir = mkdtempSync(join(tmpdir(), "baa-conductor-control-priority-host-"));
 9800  const blockedOutputPath = join(hostOpsDir, "control-should-not-write.txt");
 9801  const runtime = new ConductorRuntime(
 9802    {
 9803      nodeId: "mini-main",
 9804      host: "mini",
 9805      role: "primary",
 9806      controlApiBase: "https://control.example.test",
 9807      localApiBase: "http://127.0.0.1:0",
 9808      sharedToken: "replace-me",
 9809      paths: {
 9810        runsDir: "/tmp/runs",
 9811        stateDir
 9812      }
 9813    },
 9814    {
 9815      autoStartLoops: false,
 9816      now: () => 400
 9817    }
 9818  );
 9819
 9820  let client = null;
 9821
 9822  try {
 9823    const snapshot = await runtime.start();
 9824    const baseUrl = snapshot.controlApi.localApiBase;
 9825    client = await connectFirefoxBridgeClient(snapshot.controlApi.firefoxWsUrl, "firefox-control-priority");
 9826
 9827    client.socket.send(
 9828      JSON.stringify({
 9829        type: "browser.final_message",
 9830        platform: "chatgpt",
 9831        conversation_id: "conv-control-priority",
 9832        assistant_message_id: "msg-control-priority-1",
 9833        raw_text: [
 9834          "```baa",
 9835          '@conductor::conversation/pause::{"scope":"current","reason":"rescue_wait"}',
 9836          "```",
 9837          "```baa",
 9838          `@conductor::files/write::${JSON.stringify({
 9839            path: "control-should-not-write.txt",
 9840            cwd: hostOpsDir,
 9841            content: "should not exist",
 9842            overwrite: true
 9843          })}`,
 9844          "```"
 9845        ].join("\n"),
 9846        observed_at: 1_710_000_040_000,
 9847        page_title: "ChatGPT Control Priority",
 9848        page_url: "https://chatgpt.com/c/conv-control-priority",
 9849        tab_id: 77
 9850      })
 9851    );
 9852
 9853    const localConversationId = await waitForCondition(async () => {
 9854      const linksResponse = await fetch(
 9855        `${baseUrl}/v1/renewal/links?platform=chatgpt&remote_conversation_id=conv-control-priority`
 9856      );
 9857      assert.equal(linksResponse.status, 200);
 9858      const payload = await linksResponse.json();
 9859      assert.equal(payload.data.count, 1);
 9860      return payload.data.links[0].local_conversation_id;
 9861    }, 5_000, 50);
 9862
 9863    const conversationPayload = await waitForCondition(async () => {
 9864      const conversationResponse = await fetch(`${baseUrl}/v1/renewal/conversations/${localConversationId}`);
 9865      assert.equal(conversationResponse.status, 200);
 9866      const payload = await conversationResponse.json();
 9867      assert.equal(payload.data.automation_status, "paused");
 9868      assert.equal(payload.data.pause_reason, "rescue_wait");
 9869      return payload;
 9870    }, 5_000, 50);
 9871
 9872    assert.equal(existsSync(blockedOutputPath), false);
 9873  } finally {
 9874    client?.queue.stop();
 9875    client?.socket.close(1000, "done");
 9876    await runtime.stop();
 9877    rmSync(stateDir, {
 9878      force: true,
 9879      recursive: true
 9880    });
 9881    rmSync(hostOpsDir, {
 9882      force: true,
 9883      recursive: true
 9884    });
 9885  }
 9886});
 9887
 9888test("ConductorRuntime exposes renewal jobs APIs and registers the renewal dispatcher runner", async () => {
 9889  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-renewal-jobs-api-"));
 9890  const runtime = new ConductorRuntime(
 9891    {
 9892      nodeId: "mini-main",
 9893      host: "mini",
 9894      role: "primary",
 9895      controlApiBase: "https://control.example.test",
 9896      localApiBase: "http://127.0.0.1:0",
 9897      sharedToken: "replace-me",
 9898      paths: {
 9899        runsDir: "/tmp/runs",
 9900        stateDir
 9901      }
 9902    },
 9903    {
 9904      autoStartLoops: false,
 9905      now: () => 300
 9906    }
 9907  );
 9908  const nowMs = Date.UTC(2026, 2, 30, 14, 0, 0);
 9909
 9910  try {
 9911    const snapshot = await runtime.start();
 9912    const baseUrl = snapshot.controlApi.localApiBase;
 9913    const artifactStore = runtime["artifactStore"];
 9914    const timedJobs = runtime["timedJobs"];
 9915
 9916    assert.ok(timedJobs.getRegisteredRunnerNames().includes("renewal.dispatcher"));
 9917
 9918    await artifactStore.insertMessage({
 9919      conversationId: "conv-renewal-job-api",
 9920      id: "msg-renewal-job-api",
 9921      observedAt: nowMs - 30_000,
 9922      platform: "chatgpt",
 9923      rawText: "renewal jobs api message",
 9924      role: "assistant"
 9925    });
 9926    await artifactStore.upsertLocalConversation({
 9927      automationStatus: "auto",
 9928      localConversationId: "lc-renewal-job-api",
 9929      platform: "chatgpt"
 9930    });
 9931    await artifactStore.upsertConversationLink({
 9932      clientId: "firefox-chatgpt",
 9933      linkId: "link-renewal-job-api",
 9934      localConversationId: "lc-renewal-job-api",
 9935      observedAt: nowMs - 20_000,
 9936      pageTitle: "Renewal Jobs API",
 9937      pageUrl: "https://chatgpt.com/c/conv-renewal-job-api",
 9938      platform: "chatgpt",
 9939      remoteConversationId: "conv-renewal-job-api",
 9940      routeParams: {
 9941        conversationId: "conv-renewal-job-api"
 9942      },
 9943      routePath: "/c/conv-renewal-job-api",
 9944      routePattern: "/c/:conversationId",
 9945      targetId: "tab:44",
 9946      targetKind: "browser.proxy_delivery",
 9947      targetPayload: {
 9948        clientId: "firefox-chatgpt",
 9949        conversationId: "conv-renewal-job-api",
 9950        pageUrl: "https://chatgpt.com/c/conv-renewal-job-api",
 9951        tabId: 44
 9952      }
 9953    });
 9954    await artifactStore.insertRenewalJob({
 9955      jobId: "job-renewal-job-api",
 9956      localConversationId: "lc-renewal-job-api",
 9957      messageId: "msg-renewal-job-api",
 9958      nextAttemptAt: nowMs,
 9959      payload: JSON.stringify({
 9960        kind: "renewal.message",
 9961        sourceMessage: {
 9962          id: "msg-renewal-job-api"
 9963        },
 9964        template: "summary_with_link",
 9965        text: "[renewal] api payload",
 9966        version: 1
 9967      }),
 9968      payloadKind: "json",
 9969      targetSnapshot: {
 9970        target: {
 9971          id: "tab:44",
 9972          kind: "browser.proxy_delivery",
 9973          payload: {
 9974            clientId: "firefox-chatgpt",
 9975            tabId: 44
 9976          }
 9977        }
 9978      }
 9979    });
 9980
 9981    const listResponse = await fetch(
 9982      `${baseUrl}/v1/renewal/jobs?status=pending&local_conversation_id=lc-renewal-job-api`
 9983    );
 9984    assert.equal(listResponse.status, 200);
 9985    const listPayload = await listResponse.json();
 9986    assert.equal(listPayload.data.count, 1);
 9987    assert.equal(listPayload.data.jobs[0].job_id, "job-renewal-job-api");
 9988    assert.equal(listPayload.data.jobs[0].payload_text, "[renewal] api payload");
 9989    assert.equal(listPayload.data.jobs[0].target_snapshot.target.kind, "browser.proxy_delivery");
 9990
 9991    const readResponse = await fetch(`${baseUrl}/v1/renewal/jobs/job-renewal-job-api`);
 9992    assert.equal(readResponse.status, 200);
 9993    const readPayload = await readResponse.json();
 9994    assert.equal(readPayload.data.job_id, "job-renewal-job-api");
 9995    assert.equal(readPayload.data.local_conversation_id, "lc-renewal-job-api");
 9996    assert.equal(readPayload.data.status, "pending");
 9997    assert.equal(readPayload.data.message_id, "msg-renewal-job-api");
 9998  } finally {
 9999    await runtime.stop();
10000    rmSync(stateDir, {
10001      force: true,
10002      recursive: true
10003    });
10004  }
10005});
10006
10007test("ConductorRuntime startup recovers stale automation locks and running renewal jobs", async () => {
10008  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-runtime-recovery-"));
10009  const artifactStore = new ArtifactStore({
10010    artifactDir: join(stateDir, ARTIFACTS_DIRNAME),
10011    databasePath: join(stateDir, ARTIFACT_DB_FILENAME)
10012  });
10013  const nowMs = 100_000;
10014  let runtime = null;
10015
10016  try {
10017    await artifactStore.insertMessage({
10018      conversationId: "conv-runtime-recovery",
10019      id: "msg-runtime-recovery",
10020      observedAt: nowMs - 30_000,
10021      platform: "claude",
10022      rawText: "runtime recovery message",
10023      role: "assistant"
10024    });
10025    await artifactStore.upsertLocalConversation({
10026      automationStatus: "auto",
10027      executionState: "renewal_running",
10028      localConversationId: "lc-runtime-recovery",
10029      platform: "claude",
10030      updatedAt: nowMs - 20_000
10031    });
10032    await artifactStore.insertRenewalJob({
10033      attemptCount: 1,
10034      createdAt: nowMs - 10_000,
10035      jobId: "job-runtime-recovery",
10036      localConversationId: "lc-runtime-recovery",
10037      messageId: "msg-runtime-recovery",
10038      nextAttemptAt: null,
10039      payload: "[renew]",
10040      startedAt: nowMs - 10_000,
10041      status: "running",
10042      updatedAt: nowMs - 10_000
10043    });
10044  } finally {
10045    artifactStore.close();
10046  }
10047
10048  try {
10049    runtime = new ConductorRuntime(
10050      {
10051        nodeId: "mini-main",
10052        host: "mini",
10053        role: "primary",
10054        controlApiBase: "https://control.example.test",
10055        localApiBase: "http://127.0.0.1:0",
10056        sharedToken: "replace-me",
10057        paths: {
10058          runsDir: "/tmp/runs",
10059          stateDir
10060        }
10061      },
10062      {
10063        autoStartLoops: false,
10064        now: () => 100
10065      }
10066    );
10067
10068    await runtime.start();
10069
10070    const recoveredStore = runtime["artifactStore"];
10071    const conversation = await recoveredStore.getLocalConversation("lc-runtime-recovery");
10072    const job = await recoveredStore.getRenewalJob("job-runtime-recovery");
10073
10074    assert.equal(conversation.executionState, "idle");
10075    assert.equal(job.status, "pending");
10076    assert.equal(job.startedAt, null);
10077    assert.equal(job.finishedAt, null);
10078    assert.equal(job.nextAttemptAt, 160_000);
10079  } finally {
10080    if (runtime != null) {
10081      await runtime.stop();
10082    }
10083
10084    rmSync(stateDir, {
10085      force: true,
10086      recursive: true
10087    });
10088  }
10089});
10090
10091test("ConductorRuntime registers renewal projector, projects auto messages once, and keeps cursor across restart", async () => {
10092  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-renewal-projector-runtime-"));
10093  const logsDir = mkdtempSync(join(tmpdir(), "baa-conductor-renewal-projector-runtime-logs-"));
10094  const nowSeconds = Math.floor(Date.UTC(2026, 2, 30, 11, 0, 0) / 1000);
10095  const runtimeConfig = {
10096    nodeId: "mini-main",
10097    host: "mini",
10098    role: "primary",
10099    controlApiBase: "https://control.example.test",
10100    localApiBase: "http://127.0.0.1:0",
10101    sharedToken: "replace-me",
10102    timedJobsIntervalMs: 60_000,
10103    timedJobsSettleDelayMs: 0,
10104    paths: {
10105      logsDir,
10106      runsDir: "/tmp/runs",
10107      stateDir
10108    }
10109  };
10110  const runtimeOptions = {
10111    autoStartLoops: false,
10112    now: () => nowSeconds
10113  };
10114  const observedAt = Date.UTC(2026, 2, 30, 10, 59, 0);
10115  let runtime = new ConductorRuntime(runtimeConfig, runtimeOptions);
10116  let client = null;
10117
10118  try {
10119    const snapshot = await runtime.start();
10120    const baseUrl = snapshot.controlApi.localApiBase;
10121    client = await connectFirefoxBridgeClient(snapshot.controlApi.firefoxWsUrl, "firefox-renewal-projector");
10122
10123    client.socket.send(
10124      JSON.stringify({
10125        type: "browser.final_message",
10126        platform: "chatgpt",
10127        conversation_id: "conv-runtime-projector",
10128        assistant_message_id: "msg-runtime-projector-1",
10129        raw_text: "runtime renewal projector message",
10130        observed_at: observedAt,
10131        page_title: "Runtime Projector",
10132        page_url: "https://chatgpt.com/c/conv-runtime-projector",
10133        tab_id: 77
10134      })
10135    );
10136
10137    const linksPayload = await waitForCondition(async () => {
10138      const response = await fetch(
10139        `${baseUrl}/v1/renewal/links?platform=chatgpt&remote_conversation_id=conv-runtime-projector`
10140      );
10141      assert.equal(response.status, 200);
10142      const payload = await response.json();
10143      assert.equal(payload.data.count, 1);
10144      return payload;
10145    }, 5_000, 50);
10146    const localConversationId = linksPayload.data.links[0].local_conversation_id;
10147
10148    const autoResponse = await fetch(
10149      `${baseUrl}/v1/renewal/conversations/${localConversationId}/auto`,
10150      {
10151        method: "POST"
10152      }
10153    );
10154    assert.equal(autoResponse.status, 200);
10155
10156    const timedJobs = runtime["timedJobs"];
10157    const artifactStore = runtime["artifactStore"];
10158    assert.ok(timedJobs.getRegisteredRunnerNames().includes("renewal.projector"));
10159
10160    const firstTick = await timedJobs.runTick("manual");
10161    assert.equal(firstTick.decision, "scheduled");
10162
10163    const jobsAfterFirstTick = await artifactStore.listRenewalJobs({});
10164    assert.equal(jobsAfterFirstTick.length, 1);
10165    assert.equal(jobsAfterFirstTick[0].messageId, "msg-runtime-projector-1");
10166    assert.equal(jobsAfterFirstTick[0].status, "pending");
10167
10168    const secondTick = await timedJobs.runTick("manual");
10169    assert.equal(secondTick.decision, "scheduled");
10170    assert.deepEqual(await artifactStore.listRenewalJobs({}), jobsAfterFirstTick);
10171
10172    await runtime.stop();
10173    runtime = new ConductorRuntime(runtimeConfig, runtimeOptions);
10174    const restartedSnapshot = await runtime.start();
10175    const restartedTimedJobs = runtime["timedJobs"];
10176    const restartedStore = runtime["artifactStore"];
10177
10178    assert.equal(restartedSnapshot.controlApi.localApiBase.startsWith("http://127.0.0.1:"), true);
10179    const restartTick = await restartedTimedJobs.runTick("manual");
10180    assert.equal(restartTick.decision, "scheduled");
10181    const jobsAfterRestart = await restartedStore.listRenewalJobs({});
10182    assert.equal(jobsAfterRestart.length, 1);
10183    assert.equal(jobsAfterRestart[0].messageId, "msg-runtime-projector-1");
10184
10185    const timedJobsEntries = await waitForJsonlEntries(
10186      join(logsDir, "timed-jobs"),
10187      (items) => items.some((entry) => entry.runner === "renewal.projector" && entry.stage === "job_projected")
10188    );
10189    assert.ok(
10190      timedJobsEntries.find(
10191        (entry) => entry.runner === "renewal.projector" && entry.stage === "job_projected"
10192      )
10193    );
10194    assert.ok(
10195      timedJobsEntries.find(
10196        (entry) =>
10197          entry.runner === "renewal.projector"
10198          && entry.stage === "scan_completed"
10199          && entry.cursor_after === `message:${observedAt}:msg-runtime-projector-1`
10200      )
10201    );
10202  } finally {
10203    client?.queue.stop();
10204    client?.socket.close(1000, "done");
10205    await runtime.stop();
10206    rmSync(stateDir, {
10207      force: true,
10208      recursive: true
10209    });
10210    rmSync(logsDir, {
10211      force: true,
10212      recursive: true
10213    });
10214  }
10215});
10216
10217test("persistent live ingest survives restart and /v1/browser restores recent history from the journal", async () => {
10218  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-final-message-persist-"));
10219  const databasePath = join(stateDir, "control-plane.sqlite");
10220  const hostOpsDir = mkdtempSync(join(tmpdir(), "baa-conductor-final-message-history-"));
10221  const outputPath = join(hostOpsDir, "final-message-persist.txt");
10222  const messageText = [
10223    "```baa",
10224    `@conductor::exec::{"command":"printf 'persisted-live\\n' >> final-message-persist.txt","cwd":${JSON.stringify(hostOpsDir)}}`,
10225    "```"
10226  ].join("\n");
10227  let nowMs = 1_710_000_020_000;
10228  let runtime = null;
10229
10230  const buildPersistentIngest = (repository, sharedToken, snapshot) =>
10231    new BaaLiveInstructionIngest({
10232      center: new BaaInstructionCenter({
10233        deduper: new PersistentBaaInstructionDeduper(repository, () => nowMs),
10234        localApiContext: {
10235          fetchImpl: globalThis.fetch,
10236          repository,
10237          sharedToken,
10238          snapshotLoader: () => snapshot
10239        }
10240      }),
10241      historyLimit: 10,
10242      messageDeduper: new PersistentBaaLiveInstructionMessageDeduper(repository, () => nowMs),
10243      now: () => nowMs,
10244      snapshotStore: new PersistentBaaLiveInstructionSnapshotStore(repository, 10)
10245    });
10246
10247  let firstFixture = null;
10248  let restartedControlPlane = null;
10249
10250  try {
10251    firstFixture = await createLocalApiFixture({
10252      databasePath
10253    });
10254    const firstIngest = buildPersistentIngest(
10255      firstFixture.repository,
10256      firstFixture.sharedToken,
10257      firstFixture.snapshot
10258    );
10259
10260    await firstIngest.initialize();
10261    const firstPass = await firstIngest.ingestAssistantFinalMessage({
10262      assistantMessageId: "msg-final-message-persist",
10263      conversationId: null,
10264      observedAt: 1_710_000_020_000,
10265      platform: "chatgpt",
10266      source: "browser.final_message",
10267      text: messageText
10268    });
10269
10270    assert.equal(firstPass.summary.status, "executed");
10271    assert.equal(readFileSync(outputPath, "utf8"), "persisted-live\n");
10272
10273    firstFixture.controlPlane.close();
10274    firstFixture = null;
10275
10276    restartedControlPlane = new ConductorLocalControlPlane({
10277      databasePath
10278    });
10279    await restartedControlPlane.initialize();
10280
10281    const restartedIngest = buildPersistentIngest(
10282      restartedControlPlane.repository,
10283      "local-shared-token",
10284      {
10285        claudeCoded: {
10286          localApiBase: null
10287        },
10288        codexd: {
10289          localApiBase: null
10290        },
10291        controlApi: {
10292          baseUrl: "https://control.example.test",
10293          browserWsUrl: "ws://127.0.0.1:4317/ws/browser",
10294          firefoxWsUrl: "ws://127.0.0.1:4317/ws/firefox",
10295          hasSharedToken: true,
10296          localApiBase: "http://127.0.0.1:4317",
10297          usesPlaceholderToken: false
10298        },
10299        daemon: {
10300          currentLeaderId: "mini-main",
10301          currentTerm: 1,
10302          host: "mini",
10303          lastError: null,
10304          leaseExpiresAt: 130,
10305          leaseState: "leader",
10306          nodeId: "mini-main",
10307          role: "primary",
10308          schedulerEnabled: true
10309        },
10310        identity: "mini-main@mini(primary)",
10311        runtime: {
10312          pid: 123,
10313          started: true,
10314          startedAt: 100
10315        },
10316        warnings: []
10317      }
10318    );
10319
10320    await restartedIngest.initialize();
10321    assert.equal(restartedIngest.getSnapshot().recent_ingests[0].status, "executed");
10322    assert.equal(restartedIngest.getSnapshot().recent_executes[0].status, "executed");
10323
10324    nowMs = 1_710_000_020_500;
10325
10326    const replayPass = await restartedIngest.ingestAssistantFinalMessage({
10327      assistantMessageId: "msg-final-message-persist",
10328      conversationId: "conv-replayed",
10329      observedAt: 1_710_000_020_500,
10330      platform: "chatgpt",
10331      source: "browser.final_message",
10332      text: messageText
10333    });
10334
10335    assert.equal(replayPass.summary.status, "duplicate_message");
10336    assert.equal(readFileSync(outputPath, "utf8"), "persisted-live\n");
10337    assert.equal(restartedIngest.getSnapshot().recent_ingests[0].status, "duplicate_message");
10338    assert.equal(restartedIngest.getSnapshot().recent_ingests[1].status, "executed");
10339    assert.equal(restartedIngest.getSnapshot().recent_executes[0].status, "executed");
10340
10341    restartedControlPlane.close();
10342    restartedControlPlane = null;
10343
10344    runtime = new ConductorRuntime(
10345      {
10346        nodeId: "mini-main",
10347        host: "mini",
10348        role: "primary",
10349        controlApiBase: "https://control.example.test",
10350        localApiBase: "http://127.0.0.1:0",
10351        sharedToken: "replace-me",
10352        paths: {
10353          runsDir: "/tmp/runs",
10354          stateDir
10355        }
10356      },
10357      {
10358        autoStartLoops: false,
10359        now: () => 100
10360      }
10361    );
10362
10363    const runtimeSnapshot = await runtime.start();
10364    const browserStatus = await fetchJson(`${runtimeSnapshot.controlApi.localApiBase}/v1/browser`);
10365
10366    assert.equal(browserStatus.response.status, 200);
10367    assert.equal(browserStatus.payload.data.instruction_ingest.last_ingest.status, "duplicate_message");
10368    assert.equal(browserStatus.payload.data.instruction_ingest.last_execute.status, "executed");
10369    assert.equal(browserStatus.payload.data.instruction_ingest.recent_ingests[0].status, "duplicate_message");
10370    assert.equal(browserStatus.payload.data.instruction_ingest.recent_ingests[1].status, "executed");
10371    assert.equal(browserStatus.payload.data.instruction_ingest.recent_executes[0].status, "executed");
10372  } finally {
10373    restartedControlPlane?.close();
10374    firstFixture?.controlPlane.close();
10375
10376    if (runtime != null) {
10377      await runtime.stop();
10378    }
10379
10380    rmSync(stateDir, {
10381      force: true,
10382      recursive: true
10383    });
10384    rmSync(hostOpsDir, {
10385      force: true,
10386      recursive: true
10387    });
10388  }
10389});
10390
10391test("ConductorRuntime exposes proxy-delivery browser snapshots with routed business-page targets", async () => {
10392  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-delivery-text-"));
10393  const hostOpsDir = mkdtempSync(join(tmpdir(), "baa-conductor-delivery-text-host-"));
10394  const runtime = new ConductorRuntime(
10395    {
10396      nodeId: "mini-main",
10397      host: "mini",
10398      role: "primary",
10399      controlApiBase: "https://control.example.test",
10400      localApiBase: "http://127.0.0.1:0",
10401      sharedToken: "replace-me",
10402      paths: {
10403        runsDir: "/tmp/runs",
10404        stateDir
10405      }
10406    },
10407    {
10408      autoStartLoops: false,
10409      now: () => 100
10410    }
10411  );
10412
10413  let client = null;
10414
10415  try {
10416    const snapshot = await runtime.start();
10417    client = await connectFirefoxBridgeClient(snapshot.controlApi.firefoxWsUrl, "firefox-delivery-artifact");
10418    const execCommand = "i=1; while [ $i -le 260 ]; do printf 'line-%s\\n' \"$i\"; i=$((i+1)); done";
10419
10420    client.socket.send(
10421      JSON.stringify({
10422        type: "browser.final_message",
10423        platform: "chatgpt",
10424        conversation_id: "conv-delivery-artifact",
10425        assistant_message_id: "msg-delivery-artifact",
10426        page_title: "Delivery Target",
10427        page_url: "https://chatgpt.com/c/conv-delivery-artifact",
10428        raw_text: [
10429          "```baa",
10430          `@conductor::exec::${JSON.stringify({
10431            command: execCommand,
10432            cwd: hostOpsDir
10433          })}`,
10434          "```"
10435        ].join("\n"),
10436        observed_at: 1710000030000,
10437        shell_page: false
10438      })
10439    );
10440
10441    await expectQueueTimeout(
10442      client.queue,
10443      (message) => message.type === "browser.upload_artifacts",
10444      700
10445    );
10446    const proxyDelivery = await client.queue.next(
10447      (message) => message.type === "browser.proxy_delivery"
10448    );
10449    assert.match(proxyDelivery.message_text, /\[BAA 执行结果\]/u);
10450    assert.equal(
10451      proxyDelivery.message_text.includes("\"command\": \"i=1; while [ $i -le 260"),
10452      true
10453    );
10454    assert.match(
10455      proxyDelivery.message_text,
10456      /完整结果:https:\/\/control\.example\.test\/artifact\/exec\/[^ \n]+\.txt/u
10457    );
10458    assert.doesNotMatch(proxyDelivery.message_text, /line-260/u);
10459    assert.equal(proxyDelivery.page_url, "https://chatgpt.com/c/conv-delivery-artifact");
10460    assert.equal(proxyDelivery.target_tab_id, undefined);
10461
10462    sendPluginActionResult(client.socket, {
10463      action: "proxy_delivery",
10464      commandType: "browser.proxy_delivery",
10465      platform: "chatgpt",
10466      requestId: proxyDelivery.requestId,
10467      type: "browser.proxy_delivery"
10468    });
10469
10470    const browserStatus = await waitForCondition(async () => {
10471      const result = await fetchJson(`${snapshot.controlApi.localApiBase}/v1/browser`);
10472      assert.equal(result.response.status, 200);
10473      assert.equal(result.payload.data.delivery.last_session.stage, "completed");
10474      return result;
10475    });
10476
10477    assert.equal(browserStatus.payload.data.delivery.last_session.delivery_mode, "proxy");
10478    assert.equal(browserStatus.payload.data.delivery.last_session.message_truncated, true);
10479    assert.equal(browserStatus.payload.data.delivery.last_session.target_page_url, "https://chatgpt.com/c/conv-delivery-artifact");
10480    assert.equal(browserStatus.payload.data.delivery.last_session.target_tab_id, undefined);
10481    assert.equal(browserStatus.payload.data.delivery.last_route.page_url, "https://chatgpt.com/c/conv-delivery-artifact");
10482    assert.ok(
10483      browserStatus.payload.data.delivery.last_session.source_line_count
10484      > browserStatus.payload.data.delivery.last_session.message_line_count
10485    );
10486  } finally {
10487    client?.queue.stop();
10488    client?.socket.close(1000, "done");
10489    await runtime.stop();
10490    rmSync(stateDir, {
10491      force: true,
10492      recursive: true
10493    });
10494    rmSync(hostOpsDir, {
10495      force: true,
10496      recursive: true
10497    });
10498  }
10499});
10500
10501test("ConductorRuntime exposes /v1/browser Claude HTTP routes over the local Firefox bridge", async () => {
10502  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-browser-http-"));
10503  const runtime = new ConductorRuntime(
10504    {
10505      nodeId: "mini-main",
10506      host: "mini",
10507      role: "primary",
10508      controlApiBase: "https://control.example.test",
10509      localApiBase: "http://127.0.0.1:0",
10510      sharedToken: "replace-me",
10511      paths: {
10512        runsDir: "/tmp/runs",
10513        stateDir
10514      }
10515    },
10516    {
10517      autoStartLoops: false,
10518      now: () => 100
10519    }
10520  );
10521
10522  let client = null;
10523
10524  try {
10525    const snapshot = await runtime.start();
10526    client = await connectFirefoxBridgeClient(snapshot.controlApi.firefoxWsUrl, "firefox-claude-http");
10527    const baseUrl = snapshot.controlApi.localApiBase;
10528
10529    client.socket.send(
10530      JSON.stringify({
10531        type: "credentials",
10532        platform: "claude",
10533        account: "ops@example.com",
10534        credential_fingerprint: "fp-claude-http",
10535        freshness: "fresh",
10536        captured_at: 1710000001000,
10537        last_seen_at: 1710000001500,
10538        headers: {
10539          cookie: "session=1",
10540          "x-csrf-token": "token-1"
10541        },
10542        shell_runtime: buildShellRuntime("claude"),
10543        timestamp: 1710000001000
10544      })
10545    );
10546    await client.queue.next(
10547      (message) => message.type === "state_snapshot" && message.reason === "credentials"
10548    );
10549
10550    client.socket.send(
10551      JSON.stringify({
10552        type: "api_endpoints",
10553        platform: "claude",
10554        account: "ops@example.com",
10555        credential_fingerprint: "fp-claude-http",
10556        updated_at: 1710000002000,
10557        endpoints: [
10558          "GET /api/organizations",
10559          "GET /api/organizations/{id}/chat_conversations/{id}",
10560          "POST /api/organizations/{id}/chat_conversations/{id}/completion"
10561        ],
10562        endpoint_metadata: [
10563          {
10564            method: "GET",
10565            path: "/api/organizations",
10566            first_seen_at: 1710000001200,
10567            last_seen_at: 1710000002000
10568          }
10569        ],
10570        shell_runtime: buildShellRuntime("claude")
10571      })
10572    );
10573    await client.queue.next(
10574      (message) => message.type === "state_snapshot" && message.reason === "api_endpoints"
10575    );
10576
10577    const browserStatusResponse = await fetch(`${baseUrl}/v1/browser`);
10578    assert.equal(browserStatusResponse.status, 200);
10579    const browserStatusPayload = await browserStatusResponse.json();
10580    assert.equal(browserStatusPayload.data.bridge.client_count, 1);
10581    assert.equal(browserStatusPayload.data.claude.ready, true);
10582    assert.equal(browserStatusPayload.data.claude.shell_runtime.platform, "claude");
10583    assert.equal(browserStatusPayload.data.current_client.client_id, "firefox-claude-http");
10584    assert.equal(browserStatusPayload.data.current_client.shell_runtime[0].platform, "claude");
10585    assert.equal(browserStatusPayload.data.records[0].status, "fresh");
10586    assert.equal(browserStatusPayload.data.records[0].live.shell_runtime.platform, "claude");
10587    assert.equal(browserStatusPayload.data.records[0].persisted.credential_fingerprint, "fp-claude-http");
10588
10589    const openPromise = fetch(`${baseUrl}/v1/browser/actions`, {
10590      method: "POST",
10591      headers: {
10592        "content-type": "application/json"
10593      },
10594      body: JSON.stringify({
10595        action: "tab_open",
10596        client_id: "firefox-claude-http",
10597        platform: "claude"
10598      })
10599    });
10600    const openMessage = await client.queue.next((message) => message.type === "open_tab");
10601    assert.equal(openMessage.platform, "claude");
10602    sendPluginActionResult(client.socket, {
10603      action: "tab_open",
10604      platform: "claude",
10605      requestId: openMessage.requestId
10606    });
10607    const openResponse = await openPromise;
10608    assert.equal(openResponse.status, 200);
10609    const openPayload = await openResponse.json();
10610    assert.equal(openPayload.data.action, "tab_open");
10611    assert.equal(openPayload.data.accepted, true);
10612
10613    const sendPromise = fetch(`${baseUrl}/v1/browser/request`, {
10614      method: "POST",
10615      headers: {
10616        "content-type": "application/json"
10617      },
10618      body: JSON.stringify({
10619        platform: "claude",
10620        prompt: "hello from conductor http"
10621      })
10622    });
10623
10624    const orgRequest = await client.queue.next(
10625      (message) => message.type === "api_request" && message.path === "/api/organizations"
10626    );
10627    client.socket.send(
10628      JSON.stringify({
10629        type: "api_response",
10630        id: orgRequest.id,
10631        ok: true,
10632        status: 200,
10633        body: {
10634          organizations: [
10635            {
10636              uuid: "org-http-1",
10637              name: "HTTP Org",
10638              is_default: true
10639            }
10640          ]
10641        }
10642      })
10643    );
10644
10645    const conversationsRequest = await client.queue.next(
10646      (message) => message.type === "api_request" && message.path === "/api/organizations/org-http-1/chat_conversations"
10647    );
10648    client.socket.send(
10649      JSON.stringify({
10650        type: "api_response",
10651        id: conversationsRequest.id,
10652        ok: true,
10653        status: 200,
10654        body: {
10655          chat_conversations: [
10656            {
10657              uuid: "conv-http-1",
10658              name: "HTTP Chat",
10659              selected: true
10660            }
10661          ]
10662        }
10663      })
10664    );
10665
10666    const completionRequest = await client.queue.next(
10667      (message) =>
10668        message.type === "api_request"
10669        && message.path === "/api/organizations/org-http-1/chat_conversations/conv-http-1/completion"
10670    );
10671    assert.equal(completionRequest.body.prompt, "hello from conductor http");
10672    client.socket.send(
10673      JSON.stringify({
10674        type: "api_response",
10675        id: completionRequest.id,
10676        ok: true,
10677        status: 202,
10678        body: {
10679          accepted: true,
10680          conversation_uuid: "conv-http-1"
10681        }
10682      })
10683    );
10684
10685    const sendResponse = await sendPromise;
10686    assert.equal(sendResponse.status, 200);
10687    const sendPayload = await sendResponse.json();
10688    assert.equal(sendPayload.data.organization.organization_id, "org-http-1");
10689    assert.equal(sendPayload.data.conversation.conversation_id, "conv-http-1");
10690    assert.equal(sendPayload.data.request_mode, "claude_prompt");
10691    assert.equal(sendPayload.data.response.accepted, true);
10692
10693    const currentPromise = fetch(`${baseUrl}/v1/browser/claude/current`);
10694
10695    const currentOrgRequest = await client.queue.next(
10696      (message) => message.type === "api_request" && message.path === "/api/organizations"
10697    );
10698    client.socket.send(
10699      JSON.stringify({
10700        type: "api_response",
10701        id: currentOrgRequest.id,
10702        ok: true,
10703        status: 200,
10704        body: {
10705          organizations: [
10706            {
10707              uuid: "org-http-1",
10708              name: "HTTP Org",
10709              is_default: true
10710            }
10711          ]
10712        }
10713      })
10714    );
10715
10716    const currentConversationListRequest = await client.queue.next(
10717      (message) => message.type === "api_request" && message.path === "/api/organizations/org-http-1/chat_conversations"
10718    );
10719    client.socket.send(
10720      JSON.stringify({
10721        type: "api_response",
10722        id: currentConversationListRequest.id,
10723        ok: true,
10724        status: 200,
10725        body: {
10726          chat_conversations: [
10727            {
10728              uuid: "conv-http-1",
10729              name: "HTTP Chat",
10730              selected: true
10731            }
10732          ]
10733        }
10734      })
10735    );
10736
10737    const currentDetailRequest = await client.queue.next(
10738      (message) =>
10739        message.type === "api_request"
10740        && message.path === "/api/organizations/org-http-1/chat_conversations/conv-http-1"
10741    );
10742    client.socket.send(
10743      JSON.stringify({
10744        type: "api_response",
10745        id: currentDetailRequest.id,
10746        ok: true,
10747        status: 200,
10748        body: {
10749          conversation: {
10750            uuid: "conv-http-1",
10751            name: "HTTP Chat"
10752          },
10753          messages: [
10754            {
10755              uuid: "msg-http-user",
10756              sender: "human",
10757              text: "hello from conductor http"
10758            },
10759            {
10760              uuid: "msg-http-assistant",
10761              sender: "assistant",
10762              content: [
10763                {
10764                  text: "hello from claude http"
10765                }
10766              ]
10767            }
10768          ]
10769        }
10770      })
10771    );
10772
10773    const currentResponse = await currentPromise;
10774    assert.equal(currentResponse.status, 200);
10775    const currentPayload = await currentResponse.json();
10776    assert.equal(currentPayload.data.organization.organization_id, "org-http-1");
10777    assert.equal(currentPayload.data.messages.length, 2);
10778    assert.equal(currentPayload.data.messages[0].role, "user");
10779    assert.equal(currentPayload.data.messages[1].role, "assistant");
10780
10781    const reloadPromise = fetch(`${baseUrl}/v1/browser/actions`, {
10782      method: "POST",
10783      headers: {
10784        "content-type": "application/json"
10785      },
10786      body: JSON.stringify({
10787        action: "tab_reload",
10788        platform: "claude",
10789        reason: "http_integration_test"
10790      })
10791    });
10792    const reloadMessage = await client.queue.next((message) => message.type === "reload");
10793    sendPluginActionResult(client.socket, {
10794      action: "tab_reload",
10795      platform: "claude",
10796      requestId: reloadMessage.requestId,
10797      commandType: "reload"
10798    });
10799    const reloadResponse = await reloadPromise;
10800    assert.equal(reloadResponse.status, 200);
10801    const reloadPayload = await reloadResponse.json();
10802    assert.equal(reloadPayload.data.action, "tab_reload");
10803    assert.equal(reloadMessage.reason, "http_integration_test");
10804
10805    const reconnectPromise = fetch(`${baseUrl}/v1/browser/actions`, {
10806      method: "POST",
10807      headers: {
10808        "content-type": "application/json"
10809      },
10810      body: JSON.stringify({
10811        action: "ws_reconnect",
10812        disconnect_ms: 3000,
10813        repeat_count: 3,
10814        repeat_interval_ms: 500
10815      })
10816    });
10817    const reconnectMessage = await client.queue.next((message) => message.type === "ws_reconnect");
10818    sendPluginActionResult(client.socket, {
10819      action: "ws_reconnect",
10820      requestId: reconnectMessage.requestId,
10821      commandType: "ws_reconnect",
10822      completed: false
10823    });
10824    const reconnectResponse = await reconnectPromise;
10825    assert.equal(reconnectResponse.status, 200);
10826    const reconnectPayload = await reconnectResponse.json();
10827    assert.equal(reconnectPayload.data.action, "ws_reconnect");
10828    assert.equal(reconnectPayload.data.completed, false);
10829    assert.equal(reconnectMessage.disconnect_ms, 3000);
10830    assert.equal(reconnectMessage.repeat_count, 3);
10831    assert.equal(reconnectMessage.repeat_interval_ms, 500);
10832  } finally {
10833    client?.queue.stop();
10834    client?.socket.close(1000, "done");
10835    await runtime.stop();
10836    rmSync(stateDir, {
10837      force: true,
10838      recursive: true
10839    });
10840  }
10841});
10842
10843test("ConductorRuntime writes plugin diagnostic logs received over the Firefox bridge", async () => {
10844  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-plugin-log-state-"));
10845  const logsDir = mkdtempSync(join(tmpdir(), "baa-conductor-plugin-log-output-"));
10846  const runtime = new ConductorRuntime(
10847    {
10848      nodeId: "mini-main",
10849      host: "mini",
10850      role: "primary",
10851      controlApiBase: "https://control.example.test",
10852      localApiBase: "http://127.0.0.1:0",
10853      sharedToken: "replace-me",
10854      paths: {
10855        logsDir,
10856        runsDir: "/tmp/runs",
10857        stateDir
10858      }
10859    },
10860    {
10861      autoStartLoops: false,
10862      now: () => 100
10863    }
10864  );
10865
10866  let client = null;
10867
10868  try {
10869    const snapshot = await runtime.start();
10870    client = await connectFirefoxBridgeClient(snapshot.controlApi.firefoxWsUrl, "firefox-plugin-log");
10871
10872    client.socket.send(
10873      JSON.stringify({
10874        type: "plugin_diagnostic_log",
10875        ts: "2026-03-29T03:30:00.000Z",
10876        level: "info",
10877        text: "[PAGE] interceptor_active platform=chatgpt tab=17 / source=page-interceptor",
10878        client_id: "firefox-plugin-log"
10879      })
10880    );
10881
10882    const logPath = join(logsDir, "baa-plugin", "2026-03-29.jsonl");
10883    const entry = await waitForCondition(async () => {
10884      assert.equal(existsSync(logPath), true);
10885      const lines = readFileSync(logPath, "utf8").trim().split("\n");
10886      assert.equal(lines.length, 1);
10887      return JSON.parse(lines[0]);
10888    });
10889
10890    assert.equal(entry.type, "plugin_diagnostic_log");
10891    assert.equal(entry.ts, "2026-03-29T03:30:00.000Z");
10892    assert.equal(entry.level, "info");
10893    assert.equal(entry.text, "[PAGE] interceptor_active platform=chatgpt tab=17 / source=page-interceptor");
10894    assert.equal(entry.client_id, "firefox-plugin-log");
10895  } finally {
10896    client?.queue.stop();
10897    client?.socket.close(1000, "done");
10898    await runtime.stop();
10899    rmSync(logsDir, {
10900      force: true,
10901      recursive: true
10902    });
10903    rmSync(stateDir, {
10904      force: true,
10905      recursive: true
10906    });
10907  }
10908});
10909
10910test("ConductorRuntime persists browser metadata across disconnect and restart without leaking raw credentials", async () => {
10911  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-browser-persistence-"));
10912  const createRuntime = () =>
10913    new ConductorRuntime(
10914      {
10915        nodeId: "mini-main",
10916        host: "mini",
10917        role: "primary",
10918        controlApiBase: "https://control.example.test",
10919        localApiBase: "http://127.0.0.1:0",
10920        sharedToken: "replace-me",
10921        paths: {
10922          runsDir: "/tmp/runs",
10923          stateDir
10924        }
10925      },
10926      {
10927        autoStartLoops: false,
10928        now: () => 100
10929      }
10930    );
10931
10932  let runtime = createRuntime();
10933  let client = null;
10934
10935  try {
10936    const snapshot = await runtime.start();
10937    const baseUrl = snapshot.controlApi.localApiBase;
10938    client = await connectFirefoxBridgeClient(snapshot.controlApi.firefoxWsUrl, "firefox-persist");
10939
10940    client.socket.send(
10941      JSON.stringify({
10942        type: "credentials",
10943        platform: "claude",
10944        account: "persist@example.com",
10945        credential_fingerprint: "fp-claude-persist",
10946        freshness: "fresh",
10947        captured_at: 1710000001000,
10948        last_seen_at: 1710000001500,
10949        headers: {
10950          cookie: "session=persist-secret",
10951          "x-csrf-token": "csrf-persist-secret"
10952        }
10953      })
10954    );
10955    await client.queue.next(
10956      (message) => message.type === "state_snapshot" && message.reason === "credentials"
10957    );
10958
10959    client.socket.send(
10960      JSON.stringify({
10961        type: "api_endpoints",
10962        platform: "claude",
10963        account: "persist@example.com",
10964        credential_fingerprint: "fp-claude-persist",
10965        updated_at: 1710000002000,
10966        endpoints: [
10967          "GET /api/organizations",
10968          "POST /api/organizations/{id}/chat_conversations/{id}/completion"
10969        ],
10970        endpoint_metadata: [
10971          {
10972            method: "GET",
10973            path: "/api/organizations",
10974            first_seen_at: 1710000001200,
10975            last_seen_at: 1710000002000
10976          }
10977        ]
10978      })
10979    );
10980    await client.queue.next(
10981      (message) => message.type === "state_snapshot" && message.reason === "api_endpoints"
10982    );
10983
10984    const connectedStatus = await fetchJson(
10985      `${baseUrl}/v1/browser?platform=claude&account=persist%40example.com`
10986    );
10987    assert.equal(connectedStatus.response.status, 200);
10988    assert.equal(connectedStatus.payload.data.records.length, 1);
10989    assert.equal(connectedStatus.payload.data.records[0].view, "active_and_persisted");
10990    assert.equal(connectedStatus.payload.data.records[0].status, "fresh");
10991    assert.equal(connectedStatus.payload.data.records[0].live.credentials.account, "persist@example.com");
10992    assert.equal(
10993      connectedStatus.payload.data.records[0].persisted.credential_fingerprint,
10994      "fp-claude-persist"
10995    );
10996    assert.deepEqual(
10997      connectedStatus.payload.data.records[0].persisted.endpoints,
10998      [
10999        "GET /api/organizations",
11000        "POST /api/organizations/{id}/chat_conversations/{id}/completion"
11001      ]
11002    );
11003    assert.doesNotMatch(connectedStatus.text, /persist-secret/u);
11004
11005    const closePromise = waitForWebSocketClose(client.socket);
11006    client.socket.close(1000, "disconnect-persist");
11007    await closePromise;
11008    client.queue.stop();
11009    client = null;
11010
11011    const disconnectedStatus = await waitForCondition(async () => {
11012      const result = await fetchJson(
11013        `${baseUrl}/v1/browser?platform=claude&account=persist%40example.com`
11014      );
11015      assert.equal(result.payload.data.bridge.client_count, 0);
11016      assert.equal(result.payload.data.records.length, 1);
11017      assert.equal(result.payload.data.records[0].view, "persisted_only");
11018      assert.equal(result.payload.data.records[0].status, "stale");
11019      return result;
11020    });
11021    assert.equal(disconnectedStatus.payload.data.records[0].persisted.status, "stale");
11022
11023    await runtime.stop();
11024    runtime = null;
11025
11026    runtime = createRuntime();
11027    const restartedSnapshot = await runtime.start();
11028    const restartedStatus = await fetchJson(
11029      `${restartedSnapshot.controlApi.localApiBase}/v1/browser?platform=claude&account=persist%40example.com`
11030    );
11031    assert.equal(restartedStatus.response.status, 200);
11032    assert.equal(restartedStatus.payload.data.bridge.client_count, 0);
11033    assert.equal(restartedStatus.payload.data.records.length, 1);
11034    assert.equal(restartedStatus.payload.data.records[0].view, "persisted_only");
11035    assert.equal(restartedStatus.payload.data.records[0].persisted.credential_fingerprint, "fp-claude-persist");
11036  } finally {
11037    client?.queue.stop();
11038    client?.socket.close(1000, "done");
11039
11040    if (runtime != null) {
11041      await runtime.stop();
11042    }
11043
11044    rmSync(stateDir, {
11045      force: true,
11046      recursive: true
11047    });
11048  }
11049});
11050
11051test("ConductorRuntime ages browser login state from fresh to stale to lost when Firefox WS traffic goes quiet", async () => {
11052  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-browser-aging-"));
11053  let nowSeconds = 100;
11054  const runtime = new ConductorRuntime(
11055    {
11056      nodeId: "mini-main",
11057      host: "mini",
11058      role: "primary",
11059      controlApiBase: "https://control.example.test",
11060      localApiBase: "http://127.0.0.1:0",
11061      sharedToken: "replace-me",
11062      paths: {
11063        runsDir: "/tmp/runs",
11064        stateDir
11065      }
11066    },
11067    {
11068      autoStartLoops: false,
11069      now: () => nowSeconds
11070    }
11071  );
11072
11073  let client = null;
11074
11075  try {
11076    const snapshot = await runtime.start();
11077    const baseUrl = snapshot.controlApi.localApiBase;
11078    client = await connectFirefoxBridgeClient(snapshot.controlApi.firefoxWsUrl, "firefox-aging");
11079
11080    client.socket.send(
11081      JSON.stringify({
11082        type: "credentials",
11083        platform: "claude",
11084        account: "aging@example.com",
11085        credential_fingerprint: "fp-aging",
11086        freshness: "fresh",
11087        captured_at: 100_000,
11088        last_seen_at: 100_000,
11089        headers: {
11090          cookie: "session=aging"
11091        }
11092      })
11093    );
11094    await client.queue.next(
11095      (message) => message.type === "state_snapshot" && message.reason === "credentials"
11096    );
11097
11098    const freshStatus = await fetchJson(
11099      `${baseUrl}/v1/browser?platform=claude&account=aging%40example.com`
11100    );
11101    assert.equal(freshStatus.payload.data.records[0].status, "fresh");
11102
11103    nowSeconds = 160;
11104    await new Promise((resolve) => setTimeout(resolve, 2_200));
11105
11106    const staleStatus = await waitForCondition(async () => {
11107      const result = await fetchJson(
11108        `${baseUrl}/v1/browser?platform=claude&account=aging%40example.com`
11109      );
11110      assert.equal(result.payload.data.records[0].status, "stale");
11111      return result;
11112    }, 3_000, 100);
11113    assert.equal(staleStatus.payload.data.records[0].view, "active_and_persisted");
11114
11115    nowSeconds = 260;
11116    await new Promise((resolve) => setTimeout(resolve, 2_200));
11117
11118    const lostStatus = await waitForCondition(async () => {
11119      const result = await fetchJson(
11120        `${baseUrl}/v1/browser?platform=claude&account=aging%40example.com`
11121      );
11122      assert.equal(result.payload.data.records[0].status, "lost");
11123      return result;
11124    }, 3_000, 100);
11125    assert.equal(lostStatus.payload.data.records[0].persisted.status, "lost");
11126  } finally {
11127    client?.queue.stop();
11128    client?.socket.close(1000, "done");
11129    await runtime.stop();
11130    rmSync(stateDir, {
11131      force: true,
11132      recursive: true
11133    });
11134  }
11135});
11136
11137test("Firefox bridge api requests reject on timeout, disconnect, and replacement", async () => {
11138  const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-firefox-bridge-errors-"));
11139  const runtime = new ConductorRuntime(
11140    {
11141      nodeId: "mini-main",
11142      host: "mini",
11143      role: "primary",
11144      controlApiBase: "https://control.example.test",
11145      localApiBase: "http://127.0.0.1:0",
11146      sharedToken: "replace-me",
11147      paths: {
11148        runsDir: "/tmp/runs",
11149        stateDir
11150      }
11151    },
11152    {
11153      autoStartLoops: false,
11154      now: () => 100
11155    }
11156  );
11157
11158  let timeoutClient = null;
11159  let replacementClient = null;
11160  let replacementClientNext = null;
11161
11162  try {
11163    const snapshot = await runtime.start();
11164    const bridge = runtime.getFirefoxBridgeService();
11165
11166    assert.ok(bridge);
11167
11168    timeoutClient = await connectFirefoxBridgeClient(snapshot.controlApi.firefoxWsUrl, "firefox-timeout");
11169
11170    const timedOutPromise = bridge.apiRequest({
11171      clientId: "firefox-timeout",
11172      platform: "chatgpt",
11173      path: "/backend-api/models",
11174      timeoutMs: 50
11175    });
11176    const timedOutMessage = await timeoutClient.queue.next((message) => message.type === "api_request");
11177    assert.ok(timedOutMessage.id);
11178
11179    await assert.rejects(timedOutPromise, (error) => {
11180      assert.equal(error?.code, "request_timeout");
11181      assert.equal(error?.requestId, timedOutMessage.id);
11182      assert.equal(error?.timeoutMs, 50);
11183      return true;
11184    });
11185
11186    const disconnectedPromise = bridge.apiRequest({
11187      clientId: "firefox-timeout",
11188      platform: "chatgpt",
11189      path: "/backend-api/models",
11190      timeoutMs: 1_000
11191    });
11192    const disconnectedMessage = await timeoutClient.queue.next((message) => message.type === "api_request");
11193    const disconnectClose = waitForWebSocketClose(timeoutClient.socket);
11194    timeoutClient.socket.close(1000, "disconnect-test");
11195
11196    await assert.rejects(disconnectedPromise, (error) => {
11197      assert.equal(error?.code, "client_disconnected");
11198      assert.equal(error?.requestId, disconnectedMessage.id);
11199      return true;
11200    });
11201    await disconnectClose;
11202    timeoutClient.queue.stop();
11203    timeoutClient = null;
11204
11205    replacementClient = await connectFirefoxBridgeClient(snapshot.controlApi.firefoxWsUrl, "firefox-replace");
11206
11207    const replacedPromise = bridge.apiRequest({
11208      clientId: "firefox-replace",
11209      platform: "chatgpt",
11210      path: "/backend-api/models",
11211      timeoutMs: 1_000
11212    });
11213    const replacedMessage = await replacementClient.queue.next((message) => message.type === "api_request");
11214    const replacedClose = waitForWebSocketClose(replacementClient.socket);
11215    const replacedRejection = assert.rejects(replacedPromise, (error) => {
11216      assert.equal(error?.code, "client_replaced");
11217      assert.equal(error?.requestId, replacedMessage.id);
11218      return true;
11219    });
11220
11221    replacementClientNext = await connectFirefoxBridgeClient(
11222      snapshot.controlApi.firefoxWsUrl,
11223      "firefox-replace"
11224    );
11225
11226    await replacedRejection;
11227
11228    assert.deepEqual(await replacedClose, {
11229      code: 4001,
11230      reason: "replaced by a newer connection"
11231    });
11232  } finally {
11233    timeoutClient?.queue.stop();
11234    replacementClient?.queue.stop();
11235    replacementClientNext?.queue.stop();
11236    timeoutClient?.socket.close(1000, "done");
11237    replacementClient?.socket.close(1000, "done");
11238    replacementClientNext?.socket.close(1000, "done");
11239    await runtime.stop();
11240    rmSync(stateDir, {
11241      force: true,
11242      recursive: true
11243    });
11244  }
11245});