Ctrl K
ComunidadGuías…

server/bot-continuity.e2e.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { expect, it } from "vitest";
import { launchVerificationServer, runControlOmb } from "../scripts/control-omb.ts";

it("keeps ordinary work out of setup and carries explicit bot defaults and file locations into rooms", async () => {
  const fixture = await launchVerificationServer();
  const evidence: unknown[] = [{ fixture: fixture.info }];
  const control = async (args: string[]) => {
    const result = await runControlOmb([...args, "--url", fixture.info.url]) as any;
    evidence.push({ command: args, result });
    return result;
  };
  const api = async (method: string, path: string, body?: unknown, status?: number) => {
    const response = await fetch(`${fixture.info.url}${path}`, {
      method, headers: { "content-type": "application/json" },
      ...(body === undefined ? {} : { body: JSON.stringify(body) }),
    });
    const result = await response.json() as any;
    evidence.push({ method, path, body, status: response.status });
    if (status) expect(response.status, JSON.stringify(result)).toBe(status);
    else expect(response.ok, JSON.stringify(result)).toBe(true);
    return result;
  };
  const receipt = () => {
    const dump = JSON.parse(readFileSync(fixture.fixtureDumpPath, "utf8"));
    // Never retain the launch environment/MCP config: it holds fixture tokens.
    const result = { model: dump.argv[dump.argv.indexOf("--model") + 1], system: dump.systemPrompt as string, prompt: dump.prompt };
    evidence.push({ receipt: result });
    return result;
  };
  try {
    const catalog = await control(["models"]);
    const [a, b] = catalog.instances.find((item: any) => item.instanceId === "claude").models.options.map((item: any) => item.id);
    const selection = (model: string) => ({ instanceId: "claude", model });
    const { bot } = await api("POST", "/api/bots", { name: "Garden helper", title: "Gardener", modelSelection: selection(a) });
    const direct = async (text: string) => {
      await control(["send", "--bot", bot.id, "--task", bot.threadId, "--text", text]);
      expect((await control(["wait", "--bot", bot.id, "--task", bot.threadId, "--timeout", "30"])).status).toBe("settled");
      return receipt();
    };
    const ordinary = await direct("What is 17 + 25? Answer with the number.");
    expect(ordinary.system).not.toContain("at most four questions");
    expect(ordinary.system).not.toContain("The user explicitly asked you to set yourself up");
    const oldFile = join(fixture.info.dataDir, "workspaces", bot.id, "garden-plan.txt");
    writeFileSync(oldFile, "Water the garden on Friday. Fixture code: MOSS-42.\n");
    // The prompt JSON-encodes paths, including Windows backslashes.
    expect(ordinary.system).toContain(JSON.stringify(join(fixture.info.dataDir, "workspaces", bot.id)));
    expect(ordinary.system).toContain(JSON.stringify(join(fixture.info.dataDir, "task-workspaces", bot.id, bot.threadId)));

    const setup = await direct("/setup Help me track garden watering");
    expect(setup.system).toContain("The user explicitly asked you to set yourself up");
    expect(setup.system).toContain("at most four questions");
    const afterSetup = await direct("Never mind setup. Summarize: the garden needs water Friday.");
    expect(afterSetup.system).not.toContain("at most four questions");
    expect(JSON.stringify(afterSetup.prompt)).toContain("Never mind setup");

    await api("PATCH", `/api/bots/${bot.id}`, { soul: "You look after the garden. Keep its watering plan.", description: "Garden helper" });
    const { task: sibling } = await api("POST", `/api/bots/${bot.id}/tasks`, { title: "Sibling follows the bot" });
    // Change an inactive task, not the newly selected sibling.
    await api("PATCH", `/api/bots/${bot.id}/tasks/${bot.threadId}`, { modelSelection: selection(b), updateBotDefault: true });
    expect((await direct("Tell me your responsibility in one sentence.")).model).toBe(b);
    const { group } = await api("POST", "/api/groups", {
      name: "Garden room", memberIds: [bot.id],
      setup: { bulletin: "", defaultResponder: { kind: "member", botId: bot.id } },
    });
    const room = async (text: string) => {
      await control(["send-channel", "--channel", group.id, "--text", text]);
      expect((await control(["wait", "--channel", group.id, "--timeout", "30"])).status).toBe("settled");
      return receipt();
    };
    const shared = await room("Read the garden-plan.txt you made earlier and tell me its fixture code.");
    expect(shared.model).toBe(b);
    expect(shared.system).toContain("You look after the garden");
    expect(shared.system).toContain(JSON.stringify(join(fixture.info.dataDir, "task-workspaces", bot.id)));
    expect(shared.system).toContain(JSON.stringify(join(fixture.info.dataDir, "workspaces", bot.id)));
    expect(readFileSync(oldFile, "utf8")).toContain("MOSS-42");
    const state = (await api("GET", "/api/bots")).bots.find((item: any) => item.id === bot.id);
    expect(state.modelSelection).toEqual(selection(b));
    expect(state.tasks.find((task: any) => task.threadId === sibling.threadId)).toMatchObject({ modelSelection: selection(b), followsBotModel: true });
    const { task: future } = await api("POST", `/api/bots/${bot.id}/tasks`, { title: "Future uses default" });
    expect(future.modelSelection).toEqual(selection(b));

    await api("PATCH", `/api/bots/${bot.id}/tasks/${bot.threadId}`, { modelSelection: selection(a) });
    expect((await direct("Reply once for the thread-only model test.")).model).toBe(a);
    expect((await room("Reply once for the unchanged room default.")).model).toBe(b);
    await api("PATCH", `/api/bots/${bot.id}/tasks/${bot.threadId}`, { updateBotDefault: true }, 400);
    await api("PATCH", `/api/bots/${bot.id}/tasks/${bot.threadId}`, { modelSelection: selection(a), updateBotDefault: "yes" }, 400);
    await api("PATCH", `/api/bots/${bot.id}/tasks/not-a-thread`, { modelSelection: selection(a), updateBotDefault: true }, 404);
    await control(["messages", "--bot", bot.id, "--task", bot.threadId, "--limit", "20"]);
    await control(["messages", "--channel", group.id, "--limit", "20"]);
  } finally {
    const evidencePath = `${fixture.info.logPath}.bot-continuity.json`;
    writeFileSync(evidencePath, JSON.stringify(evidence, null, 2));
    console.info(JSON.stringify({ evidencePath }));
    await fixture.close();
  }
}, 90_000);