Ctrl K
ComunidadGuías…

server/chief-level-every-hop.e2e.test.ts

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

Archivo original ↗Preguntar a KiBot
// Real server + injected MCP tools; only the provider's planning is scripted. Ported from @AdityaUmale's test.
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { expect, it } from "vitest";
import { launchVerificationServer, runControlOmb } from "../scripts/control-omb.ts";
import { handleToolCall, request } from "../scripts/mcp-server.ts";

// A Chief's level reached the teammate it handed work to and stopped there: Clive on Approve for me, Ada ran at auto,
// and the work Ada handed Bea ran at Ask ("default").
it("carries a Chief's level down every hop it hands out, in threads and in a room, and never an ordinary bot's", async () => {
  const fixture = await launchVerificationServer({}, undefined, undefined, undefined, undefined, { scripted: true });
  const { url, dataDir, logPath } = fixture.info;
  const planPath = join(dataDir, "room-plan.json");
  const plans: Record<string, { turns: any[] }> = {};
  const providerTurns = (): any[] => existsSync(`${planPath}.evidence.jsonl`)
    ? readFileSync(`${planPath}.evidence.jsonl`, "utf8").trim().split("\n").filter(Boolean).map(line => JSON.parse(line)) : [];
  const api = async (method: string, path: string, body?: unknown, status = 200) => {
    const response = await fetch(`${url}${path}`, { method,
      headers: { "content-type": "application/json", origin: url },
      ...(body === undefined ? {} : { body: JSON.stringify(body) }), signal: AbortSignal.timeout(10_000),
    });
    const result = await response.json() as any;
    expect(response.status, `${method} ${path}: ${JSON.stringify(result)}`).toBe(status);
    return result;
  };
  const cli = (...args: string[]) => runControlOmb([...args, "--url", url]) as Promise<any>;
  const tool = (name: string, args: Record<string, unknown>) =>
    handleToolCall(name, args, (path, options) => request(path, options, url)) as Promise<any>;
  const bots = async () => (await api("GET", "/api/bots")).bots as any[];
  const messages = async (threadId: string) => (await api("GET", `/api/threads/${threadId}/messages?limit=100`)).messages as any[];
  const unanswered = async (threadId: string) => (await messages(threadId)).filter(message => message.card && !message.card.answered && !message.card.dismissed);
  const chips = async (threadId: string) => (await messages(threadId))
    .filter(message => message.kind === "activity" && / — handed on by /.test(message.tool?.name ?? "")).map(message => message.tool.name);
  const coordinate = (botId: string, key: string, message: string) =>
    ({ tool: "coordinate_bots", arguments: { bot_ids: [botId], request_key: key, message }, expectError: false });
  const plan = (botId: string, ...turns: any[]) => { (plans[botId] ??= { turns: [] }).turns.push(...turns); writeFileSync(planPath, JSON.stringify(plans)); };
  const turnOf = (botId: string, index = 0) => providerTurns().filter(turn => turn.botId === botId)[index];
  try {
    const chief = (await cli("new-bot", "--name", "Clive", "--section", "Operations")).bot;
    await api("PATCH", `/api/bots/${chief.id}`, { chiefOfStaff: true });
    const ada = (await cli("new-bot", "--name", "Ada", "--section", "Operations")).bot;
    const bea = (await cli("new-bot", "--name", "Bea", "--section", "Operations")).bot;
    const ola = (await cli("new-bot", "--name", "Ola", "--section", "Operations")).bot;
    // The person put Clive, and Ola, an ordinary bot, on Approve for me; Ada and Bea on Ask, below the Full default.
    for (const auto of [chief, ola]) await api("PATCH", `/api/bots/${auto.id}`, { approvalMode: "auto", acknowledgeLocalAuto: true, applyToAllThreads: true });
    for (const teammate of [ada, bea]) await api("PATCH", `/api/bots/${teammate.id}`, { approvalMode: "ask" });

    // Clive → Ada → Bea, each in a thread of its own.
    plan(chief.id,
      { steps: [coordinate(ada.id, "hop-ada", "Have Bea run the check; do not run it yourself.")], reply: "Handed to Ada." },
      { reply: "Ada reported Bea's result." });
    plan(ada.id,
      { steps: [coordinate(bea.id, "hop-bea", "Run the check and report the result.")], reply: "Handed to Bea." },
      { reply: "Bea ran the check." });
    plan(bea.id, { reply: "Check done." });
    await cli("send", "--bot", chief.id, "--task", chief.activeTaskId, "--text", "Ask Ada to get Bea to run the check.");
    await expect.poll(() => providerTurns().filter(turn => turn.botId === chief.id).length, { timeout: 30_000 }).toBe(2);
    expect((await cli("wait", "--bot", chief.id, "--task", chief.activeTaskId, "--timeout", "25")).status).toBe("settled");
    const adaTurn = turnOf(ada.id);
    const beaTurn = turnOf(bea.id);
    expect(adaTurn.permissionMode).toBe("auto");
    expect(beaTurn.permissionMode, "the second hop runs at the Chief's level too").toBe("auto");
    expect(await unanswered(adaTurn.threadId)).toHaveLength(0);
    expect(await unanswered(beaTurn.threadId)).toHaveLength(0);
    const current = await bots();
    expect(current.find(bot => bot.id === ada.id).approvalMode).toBe("ask");
    expect(current.find(bot => bot.id === bea.id).approvalMode).toBe("ask");
    expect(current.find(bot => bot.id === bea.id).tasks.find((task: any) => task.threadId === beaTurn.threadId).approvalMode).toBe("auto");
    expect(await chips(adaTurn.threadId)).toEqual(["Approve for me — handed on by Clive, a Chief of Staff on Approve for me"]);
    expect(await chips(beaTurn.threadId)).toEqual(["Approve for me — handed on by Ada, from Clive, a Chief of Staff on Approve for me"]);

    // Ola's own Approve for me came from the person, not a Chief: the work she hands Bea stays on Bea's level.
    plan(ola.id,
      { steps: [coordinate(bea.id, "ordinary-bea", "Run the check and report the result.")], reply: "Handed to Bea." },
      { reply: "Bea ran the check." });
    plan(bea.id, { reply: "Check done again." });
    await cli("send", "--bot", ola.id, "--task", ola.activeTaskId, "--text", "Ask Bea to run the check.");
    await expect.poll(() => providerTurns().filter(turn => turn.botId === ola.id).length, { timeout: 30_000 }).toBe(2);
    expect((await cli("wait", "--bot", ola.id, "--task", ola.activeTaskId, "--timeout", "25")).status).toBe("settled");
    const ordinaryTurn = turnOf(bea.id, 1);
    expect(ordinaryTurn.threadId).not.toBe(beaTurn.threadId);
    expect(ordinaryTurn.permissionMode).toBe("default");
    expect(await chips(ordinaryTurn.threadId)).toEqual([]);

    // The same chain inside a room. The room thread is shared, so nothing is patched onto it: each turn's level rides
    // its hand-off back up to Clive.
    const room = (await tool("create_channel", { name: "Operations room", member_ids: [chief.id, ada.id, bea.id] })).channel;
    plan(chief.id,
      { steps: [coordinate(ada.id, "room-ada", "Have Bea run the check here; do not run it yourself.")], reply: "Handed to Ada in the room." },
      { reply: "Ada reported Bea's room result." });
    plan(ada.id,
      { steps: [coordinate(bea.id, "room-bea", "Run the check and report the result.")], reply: "Handed to Bea in the room." },
      { reply: "Bea ran the room check." });
    plan(bea.id, { reply: "Room check done." });
    await cli("send-channel", "--channel", room.id, "--text", "@Clive Ask Ada to get Bea to run the check.");
    await expect.poll(() => providerTurns().filter(turn => turn.botId === chief.id).length, { timeout: 30_000 }).toBe(4);
    expect((await cli("wait", "--channel", room.id, "--timeout", "30")).status).toBe("settled");
    const roomAda = turnOf(ada.id, 2);
    const roomBea = turnOf(bea.id, 2);
    expect(roomAda.threadId).toBe(room.activeTaskId);
    expect(roomBea.threadId).toBe(room.activeTaskId);
    expect(roomAda.permissionMode).toBe("auto");
    expect(roomBea.permissionMode, "the second hop in a room runs at the Chief's level too").toBe("auto");
    expect(await unanswered(room.activeTaskId)).toHaveLength(0);
    const after = await bots();
    expect(after.find(bot => bot.id === ada.id).approvalMode).toBe("ask");
    expect(after.find(bot => bot.id === bea.id).approvalMode).toBe("ask");
    expect(readFileSync(logPath, "utf8")).not.toMatch(/ReferenceError|change listener threw|Unexpected extra fixture turn/);
  } finally {
    console.info(JSON.stringify({ logPath, providerTurns: providerTurns().map(turn => ({ botId: turn.botId, threadId: turn.threadId, permissionMode: turn.permissionMode })) }));
    await fixture.close();
  }
}, 150_000);