Ctrl K
ComunidadGuías…

lib/home-debate-tool-policy.js

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

Archivo original ↗Preguntar a KiBot
var splitShellSegments = require("./sdk-skill-discovery").splitShellSegments;
var isSafeBashSegment = require("./safe-bash-commands").isSafeBashSegment;

function hasTopicAnswer(session) {
  if (session && typeof session.homeDebateInitialTopic === "string" && session.homeDebateInitialTopic.trim()) return true;
  var history = session && Array.isArray(session.history) ? session.history : [];
  var firstQuestionId = null;
  for (var i = 0; i < history.length; i++) {
    var event = history[i];
    if (!firstQuestionId && event && event.type === "tool_executing" && event.name === "AskUserQuestion") firstQuestionId = event.id || null;
    if (firstQuestionId && event && event.type === "ask_user_answered" && event.toolId === firstQuestionId) return true;
  }
  return false;
}

function hasProviderMateQuestion(session) {
  var history = session && Array.isArray(session.history) ? session.history : [];
  for (var i = 0; i < history.length; i++) {
    var event = history[i];
    if (event && event.type === "tool_executing" && event.name === "AskUserQuestion" && typeof event.id === "string" && event.id.indexOf("mate_creation_intent_") !== 0) return true;
  }
  return false;
}

function initialToolDecision(session, toolName, input) {
  var debatePlanning = session && session.homeDebatePlanning === true;
  var mateCreation = session && session.mateCreationMode === true;
  if (!debatePlanning && !mateCreation) return null;
  if (mateCreation && toolName === "Skill") return { behavior: "deny", message: "The complete Mate interview contract is already provided by Clay." };
  var isSessionQuestion = toolName === "ask_user_questions" || toolName === "mcp__clay-ask-user__ask_user_questions";
  if (mateCreation && hasTopicAnswer(session) && !hasProviderMateQuestion(session)) {
    if (isSessionQuestion) return { behavior: "allow", updatedInput: input };
    if (toolName === "AskUserQuestion") return { behavior: "deny", message: "Use the session-bound ask_user_questions tool for the next Mate interview question." };
    return { behavior: "deny", message: "Present the next Mate interview question before inspecting context or using other tools." };
  }
  if (hasTopicAnswer(session)) return null;
  if (isSessionQuestion || toolName === "Skill") return { behavior: "allow", updatedInput: input };
  if (toolName === "AskUserQuestion") return { behavior: "deny", message: mateCreation ? "Use the session-bound ask_user_questions tool for the initial freeform Mate question." : "Use the session-bound ask_user_questions tool for the initial freeform topic question." };
  return { behavior: "deny", message: mateCreation ? "Ask what kind of Mate the user wants before inspecting context or using other tools." : "Ask for the debate topic before inspecting context or using other tools." };
}

var NON_MUTATING_TOOLS = {
  Read: true,
  Glob: true,
  Grep: true,
  WebFetch: true,
  WebSearch: true,
  Skill: true,
  TodoRead: true,
  TaskOutput: true,
  ListMcpResources: true,
  ReadMcpResource: true,
};

var NON_MUTATING_MCP_TOOLS = {
  "mcp__clay-workspace__list_projects": true,
  "mcp__clay-workspace__list_project_sessions": true,
  "mcp__clay-workspace__search_project_history": true,
  "mcp__clay-workspace__read_project_session": true,
  "mcp__clay-workspace__search_workspace_history": true,
  "mcp__clay-workspace__list_workspace_activity": true,
  "mcp__clay-workspace__get_assignment_status": true,
};

function safeBash(input) {
  if (!input || typeof input.command !== "string" || !input.command.trim()) return false;
  var command = input.command.trim();
  if (/[<>`]/.test(command) || /\$\(/.test(command) || /(^|\s)sudo(\s|$)/.test(command)) return false;
  var segments = splitShellSegments(command);
  if (!segments.length) return false;
  for (var i = 0; i < segments.length; i++) {
    if (!isSafeBashSegment(segments[i])) return false;
    var segment = segments[i].trim();
    var commandName = segment.replace(/^(?:\w+=\S*\s+)*/, "").split(/\s+/)[0];
    if ({ env: true, command: true, time: true, tee: true, xargs: true, wget: true, http: true }[commandName]) return false;
    if (commandName === "find" && /\s-(?:delete|exec|execdir|ok|okdir)(?:\s|$)/.test(segment)) return false;
    if (commandName === "sed" && /\s-(?:[^\s]*i[^\s]*|file)(?:\s|$)/.test(segment)) return false;
    if (commandName === "awk" && /\bsystem\s*\(/.test(segment)) return false;
    if (commandName === "curl" && /\s(?:-o|-O|-T|-d|-F|--output|--remote-name|--upload-file|--request|--data|--form|-X)(?:\s|=|$)/.test(segment)) return false;
  }
  return true;
}

var DISPLAYABLE_COMMANDS = {
  rg: true, grep: true, git: true, find: true, ls: true, pwd: true, wc: true, head: true, tail: true,
  sed: true, awk: true, sort: true, uniq: true, diff: true, stat: true, file: true, tree: true, curl: true,
  rm: true, mv: true, cp: true, mkdir: true, touch: true, chmod: true, chown: true, npm: true, node: true,
};

function shellCommandNames(input) {
  if (!input || typeof input.command !== "string") return [];
  var segments = splitShellSegments(input.command.trim());
  var names = [];
  for (var i = 0; i < segments.length && names.length < 3; i++) {
    var name = segments[i].trim().replace(/^(?:\w+=\S*\s+)*/, "").split(/\s+/)[0] || "";
    name = name.split("/").pop().replace(/[^a-zA-Z0-9._-]/g, "").slice(0, 24);
    if (DISPLAYABLE_COMMANDS[name] && names.indexOf(name) === -1) names.push(name);
  }
  return names;
}

function toolAction(toolName, input, allowed) {
  if (toolName === "Bash") {
    var names = shellCommandNames(input);
    return (allowed ? "Run read-only shell commands" : "Run shell commands") + (names.length ? " (" + names.join(", ") + ")" : "");
  }
  if (toolName === "Read") return "Read project files";
  if (toolName === "Glob") return "Find project files";
  if (toolName === "Grep") return "Search project text";
  if (toolName === "WebSearch") return "Search the web";
  if (toolName === "WebFetch") return "Read a web page";
  if (toolName === "Edit" || toolName === "NotebookEdit") return "Edit a project file";
  if (toolName === "Write") return "Write a project file";
  if (toolName.indexOf("search_workspace_history") !== -1) return "Search workspace history";
  if (toolName.indexOf("read_project_session") !== -1) return "Read a project session";
  if (toolName.indexOf("list_project_sessions") !== -1) return "List project sessions";
  if (toolName.indexOf("list_projects") !== -1) return "List projects";
  return "Use " + displayToolName(toolName);
}

function decisionReason(toolName, allowed) {
  if (allowed) return "Read-only investigation";
  if (toolName === "Edit" || toolName === "Write" || toolName === "NotebookEdit") return "Would modify project files";
  if (toolName === "Bash") return "Command may change the system";
  return "Not verified as read-only";
}

function auditCommand(toolName, input) {
  if (toolName !== "Bash" || !input || typeof input.command !== "string") return { command: "", truncated: false };
  var clean = input.command.replace(/\r/g, "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "").trim();
  return { command: clean.slice(0, 4000), truncated: clean.length > 4000 };
}

function debateToolDecision(toolName, input) {
  toolName = typeof toolName === "string" ? toolName : "";
  var allowed = NON_MUTATING_TOOLS[toolName] === true;
  if (toolName === "Bash") allowed = safeBash(input);
  if (NON_MUTATING_MCP_TOOLS[toolName] === true || toolName.indexOf("mcp__clay-history__") === 0) allowed = true;
  var audit = auditCommand(toolName, input);
  return {
    allowed: allowed,
    behavior: allowed ? "allow" : "deny",
    updatedInput: allowed ? input : undefined,
    message: allowed ? undefined : "The moderator blocked this tool because debate participants may not change the system.",
    action: toolAction(toolName, input, allowed),
    reason: decisionReason(toolName, allowed),
    command: audit.command,
    commandTruncated: audit.truncated,
  };
}

function displayToolName(toolName) {
  var name = typeof toolName === "string" ? toolName : "Tool";
  if (name.indexOf("mcp__") === 0) name = name.substring(name.lastIndexOf("__") + 2);
  name = name.replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
  if (!name) return "Tool";
  return name.length > 48 ? name.slice(0, 45).trim() + "..." : name;
}

module.exports = {
  initialToolDecision: initialToolDecision,
  debateToolDecision: debateToolDecision,
  displayToolName: displayToolName,
};