atagen

roundabout

public
Star0
Watch0
4e5ca2e2040139a56cdd8bc50993e0f82ceab09a
1
ssh://brewery@pls.lobotomise.me:33/atagen/roundabout.git

Blame: plugin/omp/index.js

4e5ca2e20401atagen2026-09-28 11:42 UTC1
import { spawn } from "node:child_process";
4e5ca2e20401atagen2026-09-28 11:42 UTC2
import { existsSync } from "node:fs";
4e5ca2e20401atagen2026-09-28 11:42 UTC3
import { fileURLToPath } from "node:url";
4e5ca2e20401atagen2026-09-28 11:42 UTC4
4e5ca2e20401atagen2026-09-28 11:42 UTC5
const packagedCli = fileURLToPath(new URL("./bin/rabt", import.meta.url));
4e5ca2e20401atagen2026-09-28 11:42 UTC6
4e5ca2e20401atagen2026-09-28 11:42 UTC7
function cli() {
4e5ca2e20401atagen2026-09-28 11:42 UTC8
  return process.env.ROUNDABOUT_CLI || (existsSync(packagedCli) ? packagedCli : "rabt");
4e5ca2e20401atagen2026-09-28 11:42 UTC9
}
4e5ca2e20401atagen2026-09-28 11:42 UTC10
4e5ca2e20401atagen2026-09-28 11:42 UTC11
function invoke(event, context, input = {}) {
4e5ca2e20401atagen2026-09-28 11:42 UTC12
  return new Promise((resolve) => {
4e5ca2e20401atagen2026-09-28 11:42 UTC13
    const child = spawn(cli(), ["hook", event], { stdio: ["pipe", "pipe", "ignore"] });
4e5ca2e20401atagen2026-09-28 11:42 UTC14
    let output = "";
4e5ca2e20401atagen2026-09-28 11:42 UTC15
    const timeout = setTimeout(() => child.kill("SIGKILL"), event === "omp-session-end" ? 2000 : 10000);
4e5ca2e20401atagen2026-09-28 11:42 UTC16
    timeout.unref();
4e5ca2e20401atagen2026-09-28 11:42 UTC17
    child.on("error", () => {});
4e5ca2e20401atagen2026-09-28 11:42 UTC18
    child.stdout.on("data", (chunk) => {
4e5ca2e20401atagen2026-09-28 11:42 UTC19
      output += chunk;
4e5ca2e20401atagen2026-09-28 11:42 UTC20
    });
4e5ca2e20401atagen2026-09-28 11:42 UTC21
    child.on("close", (code) => {
4e5ca2e20401atagen2026-09-28 11:42 UTC22
      clearTimeout(timeout);
4e5ca2e20401atagen2026-09-28 11:42 UTC23
      if (code !== 0 || !output) return resolve(undefined);
4e5ca2e20401atagen2026-09-28 11:42 UTC24
      try {
4e5ca2e20401atagen2026-09-28 11:42 UTC25
        resolve(JSON.parse(output));
4e5ca2e20401atagen2026-09-28 11:42 UTC26
      } catch {
4e5ca2e20401atagen2026-09-28 11:42 UTC27
        resolve(undefined);
4e5ca2e20401atagen2026-09-28 11:42 UTC28
      }
4e5ca2e20401atagen2026-09-28 11:42 UTC29
    });
4e5ca2e20401atagen2026-09-28 11:42 UTC30
    child.stdin.on("error", () => {});
4e5ca2e20401atagen2026-09-28 11:42 UTC31
    child.stdin.end(JSON.stringify({
4e5ca2e20401atagen2026-09-28 11:42 UTC32
      session_id: context.sessionManager.getSessionId(),
4e5ca2e20401atagen2026-09-28 11:42 UTC33
      cwd: context.cwd,
4e5ca2e20401atagen2026-09-28 11:42 UTC34
      ...input,
4e5ca2e20401atagen2026-09-28 11:42 UTC35
    }));
4e5ca2e20401atagen2026-09-28 11:42 UTC36
  });
4e5ca2e20401atagen2026-09-28 11:42 UTC37
}
4e5ca2e20401atagen2026-09-28 11:42 UTC38
4e5ca2e20401atagen2026-09-28 11:42 UTC39
export default function roundabout(pi) {
4e5ca2e20401atagen2026-09-28 11:42 UTC40
  pi.on("tool_call", async (event, context) => {
4e5ca2e20401atagen2026-09-28 11:42 UTC41
    if (event.toolName !== "bash") return;
4e5ca2e20401atagen2026-09-28 11:42 UTC42
    const output = await invoke("omp-pre-tool-use", context, {
4e5ca2e20401atagen2026-09-28 11:42 UTC43
      cwd: event.input.cwd || context.cwd,
4e5ca2e20401atagen2026-09-28 11:42 UTC44
      tool_name: "Bash",
4e5ca2e20401atagen2026-09-28 11:42 UTC45
      tool_input: event.input,
4e5ca2e20401atagen2026-09-28 11:42 UTC46
    });
4e5ca2e20401atagen2026-09-28 11:42 UTC47
    const verdict = output?.hookSpecificOutput;
4e5ca2e20401atagen2026-09-28 11:42 UTC48
    if (verdict?.permissionDecision === "deny") {
4e5ca2e20401atagen2026-09-28 11:42 UTC49
      return { block: true, reason: verdict.permissionDecisionReason };
4e5ca2e20401atagen2026-09-28 11:42 UTC50
    }
4e5ca2e20401atagen2026-09-28 11:42 UTC51
    return {
4e5ca2e20401atagen2026-09-28 11:42 UTC52
      input: {
4e5ca2e20401atagen2026-09-28 11:42 UTC53
        ...event.input,
4e5ca2e20401atagen2026-09-28 11:42 UTC54
        ...verdict?.updatedInput,
4e5ca2e20401atagen2026-09-28 11:42 UTC55
        env: {
4e5ca2e20401atagen2026-09-28 11:42 UTC56
          ...event.input.env,
4e5ca2e20401atagen2026-09-28 11:42 UTC57
          ROUNDABOUT_SESSION_ID: context.sessionManager.getSessionId(),
4e5ca2e20401atagen2026-09-28 11:42 UTC58
        },
4e5ca2e20401atagen2026-09-28 11:42 UTC59
      },
4e5ca2e20401atagen2026-09-28 11:42 UTC60
    };
4e5ca2e20401atagen2026-09-28 11:42 UTC61
  });
4e5ca2e20401atagen2026-09-28 11:42 UTC62
4e5ca2e20401atagen2026-09-28 11:42 UTC63
  pi.on("tool_result", async (event, context) => {
4e5ca2e20401atagen2026-09-28 11:42 UTC64
    if (event.isError) return;
4e5ca2e20401atagen2026-09-28 11:42 UTC65
    const output = await invoke("omp-post-tool-use", context);
4e5ca2e20401atagen2026-09-28 11:42 UTC66
    const reminder = output?.hookSpecificOutput?.additionalContext;
4e5ca2e20401atagen2026-09-28 11:42 UTC67
    if (reminder) {
4e5ca2e20401atagen2026-09-28 11:42 UTC68
      return { content: [...event.content, { type: "text", text: reminder }] };
4e5ca2e20401atagen2026-09-28 11:42 UTC69
    }
4e5ca2e20401atagen2026-09-28 11:42 UTC70
  });
4e5ca2e20401atagen2026-09-28 11:42 UTC71
4e5ca2e20401atagen2026-09-28 11:42 UTC72
  pi.on("before_agent_start", async (_event, context) => {
4e5ca2e20401atagen2026-09-28 11:42 UTC73
    const output = await invoke("omp-prompt-submit", context);
4e5ca2e20401atagen2026-09-28 11:42 UTC74
    const reminder = output?.hookSpecificOutput?.additionalContext;
4e5ca2e20401atagen2026-09-28 11:42 UTC75
    if (reminder) {
4e5ca2e20401atagen2026-09-28 11:42 UTC76
      return {
4e5ca2e20401atagen2026-09-28 11:42 UTC77
        message: {
4e5ca2e20401atagen2026-09-28 11:42 UTC78
          customType: "roundabout",
4e5ca2e20401atagen2026-09-28 11:42 UTC79
          content: reminder,
4e5ca2e20401atagen2026-09-28 11:42 UTC80
          display: true,
4e5ca2e20401atagen2026-09-28 11:42 UTC81
          attribution: "agent",
4e5ca2e20401atagen2026-09-28 11:42 UTC82
        },
4e5ca2e20401atagen2026-09-28 11:42 UTC83
      };
4e5ca2e20401atagen2026-09-28 11:42 UTC84
    }
4e5ca2e20401atagen2026-09-28 11:42 UTC85
  });
4e5ca2e20401atagen2026-09-28 11:42 UTC86
4e5ca2e20401atagen2026-09-28 11:42 UTC87
  pi.on("session_stop", async (event, context) => {
4e5ca2e20401atagen2026-09-28 11:42 UTC88
    const output = await invoke("omp-stop", context);
4e5ca2e20401atagen2026-09-28 11:42 UTC89
    if (output?.decision === "block") {
4e5ca2e20401atagen2026-09-28 11:42 UTC90
      return { decision: "block", reason: output.reason };
4e5ca2e20401atagen2026-09-28 11:42 UTC91
    }
4e5ca2e20401atagen2026-09-28 11:42 UTC92
    if (event.stop_hook_active) return;
4e5ca2e20401atagen2026-09-28 11:42 UTC93
    const reminder = output?.hookSpecificOutput?.additionalContext;
4e5ca2e20401atagen2026-09-28 11:42 UTC94
    if (reminder) return { continue: true, additionalContext: reminder };
4e5ca2e20401atagen2026-09-28 11:42 UTC95
    if (output?.systemMessage && context.hasUI) {
4e5ca2e20401atagen2026-09-28 11:42 UTC96
      context.ui.notify(output.systemMessage, "warning");
4e5ca2e20401atagen2026-09-28 11:42 UTC97
    }
4e5ca2e20401atagen2026-09-28 11:42 UTC98
  });
4e5ca2e20401atagen2026-09-28 11:42 UTC99
4e5ca2e20401atagen2026-09-28 11:42 UTC100
  pi.on("agent_end", async (_event, context) => {
4e5ca2e20401atagen2026-09-28 11:42 UTC101
    if (context.agent.kind !== "sub") return;
4e5ca2e20401atagen2026-09-28 11:42 UTC102
    const output = await invoke("omp-subagent-stop", context);
4e5ca2e20401atagen2026-09-28 11:42 UTC103
    const reminder = output?.reason || output?.hookSpecificOutput?.additionalContext || output?.systemMessage;
4e5ca2e20401atagen2026-09-28 11:42 UTC104
    if (reminder && context.hasUI) context.ui.notify(reminder, "warning");
4e5ca2e20401atagen2026-09-28 11:42 UTC105
  });
4e5ca2e20401atagen2026-09-28 11:42 UTC106
4e5ca2e20401atagen2026-09-28 11:42 UTC107
  pi.on("session_before_switch", async (_event, context) => {
4e5ca2e20401atagen2026-09-28 11:42 UTC108
    await invoke("omp-session-end", context);
4e5ca2e20401atagen2026-09-28 11:42 UTC109
  });
4e5ca2e20401atagen2026-09-28 11:42 UTC110
4e5ca2e20401atagen2026-09-28 11:42 UTC111
  pi.on("session_shutdown", async (_event, context) => {
4e5ca2e20401atagen2026-09-28 11:42 UTC112
    await invoke("omp-session-end", context);
4e5ca2e20401atagen2026-09-28 11:42 UTC113
  });
4e5ca2e20401atagen2026-09-28 11:42 UTC114
}