atagen

roundabout

public
Star0
Watch0
7770b8fcfa8b72ae913db787fd5ef641f31bd302
1
ssh://brewery@pls.lobotomise.me:33/atagen/roundabout.git
plugin/omp/index.jsJavascript114 lines3.6 KB
1
import { spawn } from "node:child_process";
2
import { existsSync } from "node:fs";
3
import { fileURLToPath } from "node:url";
4
5
const packagedCli = fileURLToPath(new URL("./bin/rabt", import.meta.url));
6
7
function cli() {
8
  return process.env.ROUNDABOUT_CLI || (existsSync(packagedCli) ? packagedCli : "rabt");
9
}
10
11
function invoke(event, context, input = {}) {
12
  return new Promise((resolve) => {
13
    const child = spawn(cli(), ["hook", event], { stdio: ["pipe", "pipe", "ignore"] });
14
    let output = "";
15
    const timeout = setTimeout(() => child.kill("SIGKILL"), event === "omp-session-end" ? 2000 : 10000);
16
    timeout.unref();
17
    child.on("error", () => {});
18
    child.stdout.on("data", (chunk) => {
19
      output += chunk;
20
    });
21
    child.on("close", (code) => {
22
      clearTimeout(timeout);
23
      if (code !== 0 || !output) return resolve(undefined);
24
      try {
25
        resolve(JSON.parse(output));
26
      } catch {
27
        resolve(undefined);
28
      }
29
    });
30
    child.stdin.on("error", () => {});
31
    child.stdin.end(JSON.stringify({
32
      session_id: context.sessionManager.getSessionId(),
33
      cwd: context.cwd,
34
      ...input,
35
    }));
36
  });
37
}
38
39
export default function roundabout(pi) {
40
  pi.on("tool_call", async (event, context) => {
41
    if (event.toolName !== "bash") return;
42
    const output = await invoke("omp-pre-tool-use", context, {
43
      cwd: event.input.cwd || context.cwd,
44
      tool_name: "Bash",
45
      tool_input: event.input,
46
    });
47
    const verdict = output?.hookSpecificOutput;
48
    if (verdict?.permissionDecision === "deny") {
49
      return { block: true, reason: verdict.permissionDecisionReason };
50
    }
51
    return {
52
      input: {
53
        ...event.input,
54
        ...verdict?.updatedInput,
55
        env: {
56
          ...event.input.env,
57
          ROUNDABOUT_SESSION_ID: context.sessionManager.getSessionId(),
58
        },
59
      },
60
    };
61
  });
62
63
  pi.on("tool_result", async (event, context) => {
64
    if (event.isError) return;
65
    const output = await invoke("omp-post-tool-use", context);
66
    const reminder = output?.hookSpecificOutput?.additionalContext;
67
    if (reminder) {
68
      return { content: [...event.content, { type: "text", text: reminder }] };
69
    }
70
  });
71
72
  pi.on("before_agent_start", async (_event, context) => {
73
    const output = await invoke("omp-prompt-submit", context);
74
    const reminder = output?.hookSpecificOutput?.additionalContext;
75
    if (reminder) {
76
      return {
77
        message: {
78
          customType: "roundabout",
79
          content: reminder,
80
          display: true,
81
          attribution: "agent",
82
        },
83
      };
84
    }
85
  });
86
87
  pi.on("session_stop", async (event, context) => {
88
    const output = await invoke("omp-stop", context);
89
    if (output?.decision === "block") {
90
      return { decision: "block", reason: output.reason };
91
    }
92
    if (event.stop_hook_active) return;
93
    const reminder = output?.hookSpecificOutput?.additionalContext;
94
    if (reminder) return { continue: true, additionalContext: reminder };
95
    if (output?.systemMessage && context.hasUI) {
96
      context.ui.notify(output.systemMessage, "warning");
97
    }
98
  });
99
100
  pi.on("agent_end", async (_event, context) => {
101
    if (context.agent.kind !== "sub") return;
102
    const output = await invoke("omp-subagent-stop", context);
103
    const reminder = output?.reason || output?.hookSpecificOutput?.additionalContext || output?.systemMessage;
104
    if (reminder && context.hasUI) context.ui.notify(reminder, "warning");
105
  });
106
107
  pi.on("session_before_switch", async (_event, context) => {
108
    await invoke("omp-session-end", context);
109
  });
110
111
  pi.on("session_shutdown", async (_event, context) => {
112
    await invoke("omp-session-end", context);
113
  });
114
}