Ctrl K
ComunidadGuías…

test/claude-worker-startup.test.js

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

Archivo original ↗Preguntar a KiBot
var test = require("node:test");
var assert = require("node:assert/strict");
var fs = require("node:fs");
var path = require("node:path");
var vm = require("node:vm");
var EventEmitter = require("node:events");

function loadWorker(relativePath) {
  var socket = new EventEmitter();
  var sent = [];
  socket.write = function(line) {
    var message = JSON.parse(line);
    sent.push(message);
    socket.emit("daemon_message", message);
  };
  var filename = path.join(__dirname, "..", relativePath);
  var context = vm.createContext({
    require: function(name) {
      if (name === "net") return { connect: function() { return socket; } };
      if (name === "fs") return { writeSync: function() {}, existsSync: function() { return false; } };
      if (name === "child_process") return { execSync: function() { throw new Error("No CLI in test"); } };
      if (name.indexOf("./") === 0 || name.indexOf("../") === 0) {
        return require(path.resolve(path.dirname(filename), name));
      }
      return require(name);
    },
    __dirname: path.dirname(filename),
    process: { env: {}, argv: ["node", filename, "/test.sock"], pid: 1, on: function() {} },
    console: { log: function() {}, error: function() {} },
    AbortController: AbortController,
    setInterval: function() {},
    clearInterval: function() {},
    setTimeout: setTimeout,
  });
  vm.runInContext(fs.readFileSync(filename, "utf8"), context, { filename: filename });
  var resolveSDK;
  var rejectSDK;
  context.sdkModule = new Promise(function(resolve, reject) {
    resolveSDK = resolve;
    rejectSDK = reject;
  });
  var received = [];
  var signal;
  var sdk = {
    query: function(args) {
      signal = args.options.abortController.signal;
      return (async function*() {
        for await (var message of args.prompt) {
          received.push(message);
          yield { type: "assistant", message: message };
        }
      })();
    },
  };
  return {
    context: context,
    sent: sent,
    received: received,
    ready: function() { resolveSDK(sdk); },
    fail: function() { rejectSDK(new Error("SDK unavailable")); },
    signal: function() { return signal; },
    send: function(message) { socket.emit("data", Buffer.from(JSON.stringify(message) + "\n")); },
    sendBatch: function(messages) {
      socket.emit("data", Buffer.from(messages.map(function(message) {
        return JSON.stringify(message) + "\n";
      }).join("")));
    },
    done: function() {
      return new Promise(function(resolve, reject) {
        socket.on("daemon_message", function(message) {
          if (message.type === "query_done") resolve();
          if (message.type === "query_error") reject(new Error(message.error));
        });
      });
    },
  };
}

["lib/yoke/adapters/claude-worker.js"].forEach(function(workerPath) {
  test(workerPath + ": converts nullable schedule fields for worker MCP tools", function () {
    var worker = loadWorker(workerPath);
    var zod = require("zod");
    var z = zod.z || zod;
    var shape = worker.context.buildZodShape(z, { type: "object", properties: { cron: { type: ["string", "null"] } }, required: ["cron"] });
    assert.equal(shape.cron.parse(null), null);
    assert.equal(shape.cron.parse("0 9 * * *"), "0 9 * * *");
  });

  test(workerPath + ": handles query start and first input in one socket chunk", { timeout: 1000 }, async function() {
    var worker = loadWorker(workerPath);
    var done = worker.done();
    worker.sendBatch([
      { type: "query_start", options: {} },
      { type: "push_message", content: "first message" },
      { type: "end_messages" },
    ]);
    worker.ready();
    await done;
    assert.deepEqual(worker.received, ["first message"]);
  });

  test(workerPath + ": preserves the first prompt while SDK loading is pending", async function() {
    var worker = loadWorker(workerPath);
    var run = worker.context.handleQueryStart({ options: {}, singleTurn: true });
    worker.send({ type: "push_message", content: { type: "user", message: { role: "user", content: "first" } } });
    worker.ready();
    await run;
    assert.equal(worker.received.length, 1, "first message must reach SDK without a second send");
    assert.equal(worker.received[0].message.content, "first");
    assert.equal(worker.sent.at(-1).type, "query_done");
  });

  test(workerPath + ": preserves ordered messages and end during startup", async function() {
    var worker = loadWorker(workerPath);
    var run = worker.context.handleQueryStart({ options: {}, prompt: "initial" });
    worker.send({ type: "push_message", content: "first" });
    worker.send({ type: "push_message", content: "second" });
    worker.send({ type: "end_messages" });
    worker.ready();
    await run;
    assert.deepEqual(worker.received, ["initial", "first", "second"]);
    // A reused worker must allocate its next queue before even a cached SDK await.
    run = worker.context.handleQueryStart({ options: {}, singleTurn: true });
    worker.send({ type: "push_message", content: "next turn" });
    await run;
    assert.deepEqual(worker.received, ["initial", "first", "second", "next turn"]);
  });

  test(workerPath + ": retains Stop received during SDK loading", async function() {
    var worker = loadWorker(workerPath);
    var run = worker.context.handleQueryStart({ options: {}, singleTurn: true });
    worker.send({ type: "abort" });
    worker.ready();
    await run;
    assert.equal(worker.signal(), undefined, "cancelled startup must not invoke the SDK");
    assert.equal(worker.sent.at(-1).type, "query_done");
    assert.equal(worker.context.messageQueue, null);
    assert.equal(worker.context.abortController, null);
  });

  test(workerPath + ": releases startup state if SDK loading fails", async function() {
    var worker = loadWorker(workerPath);
    var run = worker.context.handleQueryStart({ options: {} });
    worker.send({ type: "push_message", content: "first" });
    worker.fail();
    await run;
    assert.equal(worker.sent.at(-1).type, "query_error");
    assert.match(worker.sent.at(-1).error, /SDK unavailable/);
    assert.equal(worker.context.messageQueue, null);
    assert.equal(worker.context.abortController, null);
  });
});

test("Worker IPC preserves SDK permission presentation hints", async function() {
  var worker = loadWorker("lib/yoke/adapters/claude-worker.js");
  var pending = worker.context.canUseTool("Write", {}, { defaultToNo: true, suppressAlwaysAllowRule: true });
  var message = worker.sent.find(function(item) { return item.type === "permission_request"; });
  assert.equal(message.defaultToNo, true);
  assert.equal(message.suppressAlwaysAllowRule, true);
  worker.send({ type: "permission_response", requestId: message.requestId, result: { behavior: "deny" } });
  assert.equal((await pending).behavior, "deny");
});

test("Worker IPC forwards the SDK-reported mcpServer name/source across the daemon boundary", async function() {
  var worker = loadWorker("lib/yoke/adapters/claude-worker.js");
  var pending = worker.context.canUseTool("mcp__clay-notes__write_note", {}, {
    mcpServer: { name: "clay-notes", source: "sdk" },
  });
  var message = worker.sent.find(function(item) { return item.type === "permission_request"; });
  assert.deepEqual(message.mcpServer, { name: "clay-notes", source: "sdk" });
  worker.send({ type: "permission_response", requestId: message.requestId, result: { behavior: "deny" } });
  await pending;
});

test("Worker IPC sends mcpServer: null rather than omitting it when the SDK reports none", async function() {
  var worker = loadWorker("lib/yoke/adapters/claude-worker.js");
  var pending = worker.context.canUseTool("Write", {}, {});
  var message = worker.sent.find(function(item) { return item.type === "permission_request"; });
  assert.equal(message.mcpServer, null);
  worker.send({ type: "permission_response", requestId: message.requestId, result: { behavior: "deny" } });
  await pending;
});