testSupervisedRestartsKeepTheBranch

tcmts.cli.test.e2e.mcp.testSupervisedRestartsKeepTheBranch · #r77uas3j · term

A session's server the supervisor starts again goes on with the session's branch, the one it moved onto too: when tcm is replaced, and when the server crashes (what it hadn't answered is answered, to ask again).

import { okIn, tcmBin, tempDir, withChildren } from "#codebase/tcmts/cli/test/e2e/harness";
import { startedSession } from "#codebase/tcmts/cli/test/e2e/mcp";
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";

export const testSupervisedRestartsKeepTheBranch = () =>
  withChildren(async (keep) => {
    const dir = tempDir("mcp supervised restart");
    const tcm = (...args: string[]) => okIn(dir, ...args);
    tcm("init");
    fs.rmSync(path.join(dir, "scratch.ts"), { force: true });
    tcm("branch", "agent/b");
    const bin = path.join(dir, "tcm-bin.mjs");
    fs.writeFileSync(bin, `import ${JSON.stringify(pathToFileURL(tcmBin()).href)};\n`);
    const s = await startedSession(dir, keep, {}, [bin, "mcp", "--branch", "agent/a"]);
    assert.equal(s.branch, "agent/a");
    const moved = await s.call("tcm_cli", { args: ["switch", "agent/b"] });
    assert.equal(moved.isError, false, moved.text);
    // (a named branch it leaves stays)
    assert.match(moved.text, /agent\/a, which it leaves, stays/);
    // (none, for a moment, between one stopping and the next starting)
    const server = () => {
      try {
        return execFileSync("pgrep", ["-n", "-P", String(s.pid)], { encoding: "utf8" }).trim();
      } catch {
        return "";
      }
    };
    const first = server();
    fs.writeFileSync(bin, `${fs.readFileSync(bin, "utf8")}// v2\n`);
    for (
      const until = Date.now() + 30_000;
      !s.notes.some((n) => n.method === "notifications/tools/list_changed");
      await new Promise((r) => setTimeout(r, 100))
    )
      if (Date.now() > until) assert.fail("the server wasn't started again");
    assert.match((await s.call("tcm_status")).text, /^branch\s+agent\/b/m);

    const replaced = server();
    assert.notEqual(replaced, first);
    process.kill(Number(replaced), "SIGKILL");
    for (
      const until = Date.now() + 30_000;
      ["", replaced].includes(server());
      await new Promise((r) => setTimeout(r, 100))
    )
      if (Date.now() > until) assert.fail("the server wasn't started again after it crashed");
    assert.match((await s.call("tcm_status")).text, /^branch\s+agent\/b/m);
    s.close();
  })

uses