@beevibe/daemon 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.js +694 -19
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -408,7 +408,10 @@ async function listFilesRecursive(dir) {
408
408
  return out;
409
409
  }
410
410
  // ../core/dist/services/skills/tier-filter.js
411
- var UNIVERSAL_SKILLS = ["beevibe-pre-task-setup"];
411
+ var UNIVERSAL_SKILLS = [
412
+ "beevibe-pre-task-setup",
413
+ "beevibe-verify-pr"
414
+ ];
412
415
  var TEAM_ONLY_SKILLS = ["beevibe-team-mesh-negotiation"];
413
416
  function tierFilterFor(level) {
414
417
  if (level === "ic")
@@ -876,9 +879,6 @@ class ClaudeCodeRuntime {
876
879
  args.push("--max-turns", String(maxTurns));
877
880
  if (context.resume_session_id)
878
881
  args.push("--resume", context.resume_session_id);
879
- if (context.disallowed_tools?.length) {
880
- args.push("--disallowedTools", context.disallowed_tools.join(","));
881
- }
882
882
  if (context.system_prompt_append.length > 0) {
883
883
  args.push("--append-system-prompt", context.system_prompt_append);
884
884
  }
@@ -1672,6 +1672,9 @@ function createDefaultRuntimeRegistry() {
1672
1672
  opencode: new OpenCodeRuntime({})
1673
1673
  };
1674
1674
  }
1675
+ function runtimeMissingError(cli) {
1676
+ return `No runtime registered for dispatch payload type '${cli}'`;
1677
+ }
1675
1678
 
1676
1679
  // src/api-client.ts
1677
1680
  import WebSocket from "ws";
@@ -1732,8 +1735,681 @@ class ApiClient {
1732
1735
  }
1733
1736
  }
1734
1737
 
1738
+ // ../sandbox/dist/orchestrator.js
1739
+ import { spawn as spawn3 } from "node:child_process";
1740
+ import { mkdir as mkdir2, readFile as readFile2, readdir, writeFile as writeFile2 } from "node:fs/promises";
1741
+ import { dirname as dirname2, join as join7, resolve } from "node:path";
1742
+ import { fileURLToPath } from "node:url";
1743
+
1744
+ // ../sandbox/dist/docker.js
1745
+ import { spawn as spawn2 } from "node:child_process";
1746
+ import { randomBytes as randomBytes2 } from "node:crypto";
1747
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
1748
+ import { tmpdir as tmpdir4 } from "node:os";
1749
+ import { join as join6 } from "node:path";
1750
+ var DEFAULT_IMAGE = "python:3.12-slim";
1751
+ var DEFAULT_LIMITS = {
1752
+ cmd_timeout_seconds: 300,
1753
+ cpus: 2,
1754
+ memory: "2g",
1755
+ storage: "4g",
1756
+ network: true
1757
+ };
1758
+ async function createSandbox(opts = {}) {
1759
+ const image = opts.image ?? DEFAULT_IMAGE;
1760
+ const limits = { ...DEFAULT_LIMITS, ...opts.limits ?? {} };
1761
+ const id = generateId(opts.label ?? "bv-sbx");
1762
+ const artifact_dir = await mkdtemp(join6(tmpdir4(), `${id}-artifacts-`));
1763
+ const args = [
1764
+ "run",
1765
+ "--detach",
1766
+ "--name",
1767
+ id,
1768
+ `--cpus=${limits.cpus}`,
1769
+ `--memory=${limits.memory}`,
1770
+ `--storage-opt=size=${limits.storage}`,
1771
+ "--user",
1772
+ "1000:1000",
1773
+ "--tmpfs",
1774
+ "/tmp:rw,size=512m",
1775
+ "-v",
1776
+ `${artifact_dir}:/sandbox/artifacts:rw`,
1777
+ "--workdir",
1778
+ "/sandbox",
1779
+ "--entrypoint",
1780
+ "tail"
1781
+ ];
1782
+ if (!limits.network) {
1783
+ args.push("--network=none");
1784
+ }
1785
+ args.push(image, "-f", "/dev/null");
1786
+ const result = await runDocker(args, { timeoutMs: 30000 });
1787
+ if (result.exit_code !== 0) {
1788
+ await rm(artifact_dir, { recursive: true, force: true }).catch(() => {});
1789
+ throw new SandboxError(`Failed to create sandbox: ${result.stderr.trim() || "docker run exited " + result.exit_code}`);
1790
+ }
1791
+ return {
1792
+ id,
1793
+ image,
1794
+ artifact_dir,
1795
+ created_at: new Date
1796
+ };
1797
+ }
1798
+ async function exec(sandbox, cmd, opts = {}) {
1799
+ const cwd = opts.cwd ?? "/sandbox";
1800
+ const timeoutMs = (opts.timeout_seconds ?? DEFAULT_LIMITS.cmd_timeout_seconds) * 1000;
1801
+ const args = ["exec", "--workdir", cwd];
1802
+ for (const [k, v] of Object.entries(opts.env ?? {})) {
1803
+ args.push("--env", `${k}=${v}`);
1804
+ }
1805
+ args.push(sandbox.id, "sh", "-c", cmd);
1806
+ const startedAt = Date.now();
1807
+ const r = await runDocker(args, { timeoutMs });
1808
+ return {
1809
+ stdout: r.stdout,
1810
+ stderr: r.stderr,
1811
+ exit_code: r.exit_code,
1812
+ timed_out: r.timed_out,
1813
+ duration_seconds: (Date.now() - startedAt) / 1000
1814
+ };
1815
+ }
1816
+ async function destroySandbox(sandbox) {
1817
+ await runDocker(["rm", "-f", sandbox.id], { timeoutMs: 30000 }).catch(() => {});
1818
+ }
1819
+ async function prepareBaseEnvironment(sandbox) {
1820
+ const r = await runDocker([
1821
+ "exec",
1822
+ "--user",
1823
+ "0",
1824
+ sandbox.id,
1825
+ "sh",
1826
+ "-c",
1827
+ "apt-get update -qq && apt-get install -y -qq --no-install-recommends git curl ca-certificates && rm -rf /var/lib/apt/lists/* && mkdir -p /sandbox && chown -R 1000:1000 /sandbox"
1828
+ ], { timeoutMs: 180000 });
1829
+ if (r.exit_code !== 0) {
1830
+ throw new SandboxError(`base prep failed (exit ${r.exit_code}): ${r.stderr.trim().slice(0, 500)}`);
1831
+ }
1832
+ }
1833
+
1834
+ class SandboxError extends Error {
1835
+ constructor(message) {
1836
+ super(message);
1837
+ this.name = "SandboxError";
1838
+ }
1839
+ }
1840
+ function runDocker(args, opts) {
1841
+ return new Promise((resolve) => {
1842
+ const proc = spawn2("docker", args, { stdio: ["ignore", "pipe", "pipe"] });
1843
+ let stdout = "";
1844
+ let stderr = "";
1845
+ let timedOut = false;
1846
+ const timer = setTimeout(() => {
1847
+ timedOut = true;
1848
+ proc.kill("SIGKILL");
1849
+ }, opts.timeoutMs);
1850
+ proc.stdout?.on("data", (c) => {
1851
+ stdout += c.toString("utf8");
1852
+ });
1853
+ proc.stderr?.on("data", (c) => {
1854
+ stderr += c.toString("utf8");
1855
+ });
1856
+ proc.on("error", (err) => {
1857
+ clearTimeout(timer);
1858
+ resolve({
1859
+ stdout,
1860
+ stderr: stderr + `
1861
+ <spawn-error>${err.message}</spawn-error>`,
1862
+ exit_code: -1,
1863
+ timed_out: timedOut
1864
+ });
1865
+ });
1866
+ proc.on("close", (code) => {
1867
+ clearTimeout(timer);
1868
+ resolve({
1869
+ stdout,
1870
+ stderr,
1871
+ exit_code: code ?? -1,
1872
+ timed_out: timedOut
1873
+ });
1874
+ });
1875
+ });
1876
+ }
1877
+ function generateId(label) {
1878
+ const suffix = randomBytes2(4).toString("hex");
1879
+ const safe = label.toLowerCase().replace(/[^a-z0-9_-]/g, "-").slice(0, 32);
1880
+ return `${safe}-${suffix}`;
1881
+ }
1882
+
1883
+ // ../sandbox/dist/orchestrator.js
1884
+ var DEFAULT_PROMPT_HEADER = `You are a Beevibe sandbox child agent running inside a fresh Docker container.
1885
+
1886
+ You have ONLY these five MCP tools to touch the container. They are in your
1887
+ tool list as deferred MCP tools — you MUST load each one via ToolSearch
1888
+ before using it. ToolSearch's "select:" form only accepts ONE tool name per
1889
+ call, so make five separate calls at the very start of your work:
1890
+
1891
+ ToolSearch({ "query": "select:mcp__beevibe-sandbox__sandbox_exec", "max_results": 1 })
1892
+ ToolSearch({ "query": "select:mcp__beevibe-sandbox__sandbox_read_file", "max_results": 1 })
1893
+ ToolSearch({ "query": "select:mcp__beevibe-sandbox__sandbox_write_file", "max_results": 1 })
1894
+ ToolSearch({ "query": "select:mcp__beevibe-sandbox__sandbox_list", "max_results": 1 })
1895
+ ToolSearch({ "query": "select:mcp__beevibe-sandbox__sandbox_export_artifact", "max_results": 1 })
1896
+
1897
+ After those five ToolSearch calls, you may invoke the MCP tools directly:
1898
+
1899
+ mcp__beevibe-sandbox__sandbox_exec(cmd, cwd?, timeout_seconds?)
1900
+ mcp__beevibe-sandbox__sandbox_read_file(path, max_bytes?)
1901
+ mcp__beevibe-sandbox__sandbox_write_file(path, content)
1902
+ mcp__beevibe-sandbox__sandbox_list(path)
1903
+ mcp__beevibe-sandbox__sandbox_export_artifact(sandbox_path, title?)
1904
+
1905
+ You have NO Bash, NO Read, NO Edit, NO Write tool — those will return errors.
1906
+
1907
+ The container has Python 3.12, git, and curl pre-installed. Operate inside
1908
+ /sandbox. Use a project-local venv at /sandbox/venv.
1909
+
1910
+ Your job: use the external repo the user gives you to produce a real artifact
1911
+ for the goal. Plan, install, run, verify, export. You succeed by exporting at
1912
+ least one artifact under /sandbox/artifacts/ via sandbox_export_artifact. Stop
1913
+ as soon as you've exported something useful — don't keep exploring. If you
1914
+ can't, write REASON.txt explaining why and export that.`;
1915
+ function nowIso() {
1916
+ return new Date().toISOString();
1917
+ }
1918
+ var MAX_TRANSCRIPT_EVENTS = 500;
1919
+ var MAX_EVENT_TEXT_BYTES = 4000;
1920
+ function classifyStartupError(err) {
1921
+ const raw = err instanceof Error ? err.message : String(err);
1922
+ if (/Cannot connect to the Docker daemon/i.test(raw)) {
1923
+ return "Docker isn't running. Start Docker Desktop and try the run again.";
1924
+ }
1925
+ if (/ENOENT.*docker/i.test(raw)) {
1926
+ return "The `docker` CLI isn't on PATH. Install Docker Desktop (or set DOCKER_HOST).";
1927
+ }
1928
+ if (/no space left on device/i.test(raw)) {
1929
+ return "Docker is out of disk. Prune containers/images or expand Docker Desktop's allotment.";
1930
+ }
1931
+ return raw;
1932
+ }
1933
+ async function runRepoAgent(opts) {
1934
+ const state = {
1935
+ run_id: opts.run_id,
1936
+ status: "starting",
1937
+ repo_url: opts.repo_url,
1938
+ goal: opts.goal,
1939
+ started_at: nowIso(),
1940
+ transcript: [],
1941
+ artifacts: []
1942
+ };
1943
+ const emit = () => opts.on_state?.({ ...state, transcript: state.transcript.slice(), artifacts: state.artifacts.slice() });
1944
+ const log = (kind, text) => {
1945
+ const trimmed = text.length > MAX_EVENT_TEXT_BYTES ? text.slice(0, MAX_EVENT_TEXT_BYTES) + `
1946
+ …[truncated ${text.length - MAX_EVENT_TEXT_BYTES} bytes]…` : text;
1947
+ state.transcript.push({ at: nowIso(), kind, text: trimmed });
1948
+ if (state.transcript.length > MAX_TRANSCRIPT_EVENTS) {
1949
+ const overflow = state.transcript.length - MAX_TRANSCRIPT_EVENTS;
1950
+ state.transcript.splice(1, overflow);
1951
+ const alreadyMarked = state.transcript[1]?.text.startsWith("[log truncated:");
1952
+ if (!alreadyMarked) {
1953
+ state.transcript.splice(1, 0, {
1954
+ at: nowIso(),
1955
+ kind: "log",
1956
+ text: `[log truncated: dropped ${overflow} earlier event${overflow === 1 ? "" : "s"}]`
1957
+ });
1958
+ }
1959
+ }
1960
+ emit();
1961
+ };
1962
+ let sandbox = null;
1963
+ try {
1964
+ log("log", "Creating sandbox container…");
1965
+ state.status = "preparing";
1966
+ emit();
1967
+ try {
1968
+ sandbox = await createSandbox({ label: `bv-run-${opts.run_id}` });
1969
+ } catch (err) {
1970
+ throw new Error(classifyStartupError(err));
1971
+ }
1972
+ state.sandbox_id = sandbox.id;
1973
+ log("log", `Sandbox ${sandbox.id} created (image ${sandbox.image}).`);
1974
+ log("log", "Installing git + curl in the sandbox base image…");
1975
+ await prepareBaseEnvironment(sandbox);
1976
+ log("log", "Base environment ready.");
1977
+ if (opts.input_url) {
1978
+ const filename = opts.input_filename ?? "input.bin";
1979
+ log("log", `Fetching input into /sandbox/inputs/${filename}…`);
1980
+ const r = await exec(sandbox, `mkdir -p /sandbox/inputs && curl -fsSL ${shellQuote(opts.input_url)} -o /sandbox/inputs/${shellQuote(filename)}`, { timeout_seconds: 120 });
1981
+ if (r.exit_code !== 0) {
1982
+ throw new Error(`input fetch failed: ${r.stderr.trim().slice(0, 400)}`);
1983
+ }
1984
+ log("log", "Input file ready.");
1985
+ }
1986
+ const mcpServerCommand = opts.mcp_server_command ?? defaultMcpServerCommand();
1987
+ const mcpConfigPath = join7(sandbox.artifact_dir, "mcp-config.json");
1988
+ const mcpConfig = {
1989
+ mcpServers: {
1990
+ "beevibe-sandbox": {
1991
+ command: mcpServerCommand.command,
1992
+ args: mcpServerCommand.args,
1993
+ env: {
1994
+ BEEVIBE_SANDBOX_ID: sandbox.id,
1995
+ BEEVIBE_SANDBOX_ARTIFACTS: sandbox.artifact_dir
1996
+ }
1997
+ }
1998
+ }
1999
+ };
2000
+ await writeFile2(mcpConfigPath, JSON.stringify(mcpConfig, null, 2), "utf8");
2001
+ log("log", `MCP config written: ${mcpConfigPath}`);
2002
+ const userPrompt = buildUserPrompt(opts);
2003
+ const systemPromptAppend = DEFAULT_PROMPT_HEADER;
2004
+ state.status = "running";
2005
+ log("log", "Spawning child claude session…");
2006
+ emit();
2007
+ const claudeResult = await runClaude({
2008
+ claudeBin: opts.claude_bin ?? "claude",
2009
+ mcpConfigPath,
2010
+ systemPromptAppend,
2011
+ userPrompt,
2012
+ maxBudgetUsd: opts.max_budget_usd ?? 2,
2013
+ timeoutSeconds: opts.max_runtime_seconds ?? 600,
2014
+ onTranscript: (kind, text) => log(kind, text)
2015
+ });
2016
+ if (claudeResult.exit_code !== 0 && claudeResult.exit_code !== null) {
2017
+ const tail = claudeResult.stderr.slice(-500);
2018
+ log("error", `Child claude exited with code ${claudeResult.exit_code}: ${tail}`);
2019
+ state.status = claudeResult.timed_out ? "blocked" : "failed";
2020
+ state.error = claudeResult.timed_out ? `Run hit the ${opts.max_runtime_seconds ?? 600}s wall-clock budget — agent didn't finish in time.` : `Claude exited ${claudeResult.exit_code}.${tail ? " " + tail.slice(-200) : ""}`;
2021
+ state.finished_at = nowIso();
2022
+ emit();
2023
+ return state;
2024
+ }
2025
+ log("log", "Child claude exited cleanly. Collecting artifacts…");
2026
+ state.artifacts = await collectArtifacts(sandbox);
2027
+ if (state.artifacts.length === 0) {
2028
+ state.status = "blocked";
2029
+ state.error = "agent produced no artifacts";
2030
+ } else {
2031
+ state.status = "succeeded";
2032
+ }
2033
+ state.finished_at = nowIso();
2034
+ emit();
2035
+ return state;
2036
+ } catch (err) {
2037
+ log("error", err instanceof Error ? err.message : String(err));
2038
+ state.status = "failed";
2039
+ state.error = err instanceof Error ? err.message : String(err);
2040
+ state.finished_at = nowIso();
2041
+ emit();
2042
+ return state;
2043
+ } finally {
2044
+ if (sandbox) {
2045
+ try {
2046
+ log("log", `Destroying sandbox ${sandbox.id}…`);
2047
+ await destroySandbox(sandbox);
2048
+ } catch (err) {
2049
+ log("log", `Sandbox cleanup note: ${err instanceof Error ? err.message : String(err)}. Run with \`docker ps -a --filter name=bv-run-\` to inspect.`);
2050
+ }
2051
+ }
2052
+ }
2053
+ }
2054
+ function buildUserPrompt(opts) {
2055
+ const lines = [
2056
+ `Goal: ${opts.goal}`,
2057
+ `Repo: ${opts.repo_url}`
2058
+ ];
2059
+ if (opts.input_url) {
2060
+ lines.push(`Input file (pre-fetched): /sandbox/inputs/${opts.input_filename ?? "input.bin"}`);
2061
+ }
2062
+ lines.push("");
2063
+ lines.push("Work inside /sandbox. Clone the repo to /sandbox/repo. Install in a venv. ");
2064
+ lines.push("Produce at least one artifact under /sandbox/artifacts/, then call ");
2065
+ lines.push("sandbox_export_artifact() on each one with a short, human-readable title. ");
2066
+ lines.push("Stop as soon as you've exported a useful artifact — don't keep exploring.");
2067
+ return lines.join(`
2068
+ `);
2069
+ }
2070
+ var ALLOWED_TOOLS = [
2071
+ "mcp__beevibe-sandbox__sandbox_exec",
2072
+ "mcp__beevibe-sandbox__sandbox_read_file",
2073
+ "mcp__beevibe-sandbox__sandbox_write_file",
2074
+ "mcp__beevibe-sandbox__sandbox_list",
2075
+ "mcp__beevibe-sandbox__sandbox_export_artifact"
2076
+ ];
2077
+ var DISALLOWED_TOOLS = ["Bash", "Read", "Edit", "Write", "BashOutput", "KillBash"];
2078
+ function runClaude(args) {
2079
+ return new Promise((resolve2) => {
2080
+ const cliArgs = [
2081
+ "--print",
2082
+ "--mcp-config",
2083
+ args.mcpConfigPath,
2084
+ "--allowed-tools",
2085
+ ALLOWED_TOOLS.join(","),
2086
+ "--disallowed-tools",
2087
+ DISALLOWED_TOOLS.join(","),
2088
+ "--append-system-prompt",
2089
+ args.systemPromptAppend,
2090
+ "--max-budget-usd",
2091
+ String(args.maxBudgetUsd),
2092
+ "--output-format",
2093
+ "stream-json",
2094
+ "--verbose"
2095
+ ];
2096
+ const proc = spawn3(args.claudeBin, cliArgs, {
2097
+ stdio: ["pipe", "pipe", "pipe"],
2098
+ env: { ...process.env },
2099
+ detached: true
2100
+ });
2101
+ proc.unref();
2102
+ let stdout = "";
2103
+ let stderr = "";
2104
+ let timedOut = false;
2105
+ let stdoutBuffer = "";
2106
+ const timer = setTimeout(() => {
2107
+ timedOut = true;
2108
+ proc.kill("SIGTERM");
2109
+ setTimeout(() => proc.kill("SIGKILL"), 5000);
2110
+ }, args.timeoutSeconds * 1000);
2111
+ proc.stdout.on("data", (chunk) => {
2112
+ const s = chunk.toString("utf8");
2113
+ stdout += s;
2114
+ stdoutBuffer += s;
2115
+ let nl;
2116
+ while ((nl = stdoutBuffer.indexOf(`
2117
+ `)) !== -1) {
2118
+ const line = stdoutBuffer.slice(0, nl);
2119
+ stdoutBuffer = stdoutBuffer.slice(nl + 1);
2120
+ if (line.trim().length === 0)
2121
+ continue;
2122
+ try {
2123
+ const evt = JSON.parse(line);
2124
+ if (evt && typeof evt === "object" && evt.type === "system" && evt.subtype === "init") {
2125
+ const init = evt;
2126
+ const servers = init.mcp_servers;
2127
+ if (servers) {
2128
+ for (const sv of servers) {
2129
+ args.onTranscript("log", `mcp server ${sv.name}: ${sv.status}`);
2130
+ }
2131
+ }
2132
+ const tools = init.tools;
2133
+ const mcpTools = (tools ?? []).filter((t) => t.startsWith("mcp__beevibe-sandbox__"));
2134
+ args.onTranscript("log", `mcp tools exposed: ${mcpTools.length ? mcpTools.join(", ") : "none"}`);
2135
+ }
2136
+ handleStreamEvent(evt, args.onTranscript);
2137
+ } catch {}
2138
+ }
2139
+ });
2140
+ proc.stderr.on("data", (chunk) => {
2141
+ stderr += chunk.toString("utf8");
2142
+ });
2143
+ proc.on("error", (err) => {
2144
+ clearTimeout(timer);
2145
+ const friendly = /ENOENT/i.test(err.message) ? `Claude CLI not found at ${args.claudeBin}. Set BEEVIBE_CLAUDE_BIN to the binary path.` : `claude spawn error: ${err.message}`;
2146
+ args.onTranscript("error", friendly);
2147
+ resolve2({ exit_code: -1, stdout, stderr, timed_out: timedOut });
2148
+ });
2149
+ proc.on("close", (code) => {
2150
+ clearTimeout(timer);
2151
+ if (code !== 0 && code !== null) {
2152
+ const tail = stderr.slice(-800).trim();
2153
+ if (tail) {
2154
+ args.onTranscript("error", `claude stderr tail: ${tail}`);
2155
+ }
2156
+ }
2157
+ resolve2({ exit_code: code, stdout, stderr, timed_out: timedOut });
2158
+ });
2159
+ proc.stdin.write(args.userPrompt);
2160
+ proc.stdin.end();
2161
+ });
2162
+ }
2163
+ function handleStreamEvent(evt, emit) {
2164
+ if (!evt || typeof evt !== "object")
2165
+ return;
2166
+ const e = evt;
2167
+ const t = e.type;
2168
+ if (t === "assistant" || t === "assistant_message") {
2169
+ const message = e.message;
2170
+ if (message && Array.isArray(message.content)) {
2171
+ for (const item of message.content) {
2172
+ if (!item || typeof item !== "object")
2173
+ continue;
2174
+ const it = item;
2175
+ if (it.type === "text" && typeof it.text === "string") {
2176
+ emit("agent", it.text);
2177
+ } else if (it.type === "tool_use") {
2178
+ const name = String(it.name ?? "unknown");
2179
+ const input = it.input ? compactJson(it.input) : "";
2180
+ emit("tool_call", `${name}(${input})`);
2181
+ }
2182
+ }
2183
+ } else if (typeof e.content === "string") {
2184
+ emit("agent", e.content);
2185
+ }
2186
+ } else if (t === "user" || t === "user_message") {
2187
+ const message = e.message;
2188
+ if (message && Array.isArray(message.content)) {
2189
+ for (const item of message.content) {
2190
+ if (!item || typeof item !== "object")
2191
+ continue;
2192
+ const it = item;
2193
+ if (it.type === "tool_result") {
2194
+ const content = it.content;
2195
+ let text;
2196
+ if (typeof content === "string") {
2197
+ text = content;
2198
+ } else if (Array.isArray(content)) {
2199
+ text = content.map((c) => c && typeof c === "object" && typeof c.text === "string" ? c.text : "").filter(Boolean).join(" ");
2200
+ } else {
2201
+ text = JSON.stringify(content ?? null);
2202
+ }
2203
+ const isErr = it.is_error === true;
2204
+ emit(isErr ? "error" : "tool_call", `→ ${text.slice(0, 300)}${text.length > 300 ? "…" : ""}`);
2205
+ }
2206
+ }
2207
+ }
2208
+ } else if (t === "result" && typeof e.result === "string") {
2209
+ emit("agent", e.result);
2210
+ } else if (t === "error") {
2211
+ emit("error", typeof e.message === "string" ? e.message : "claude error");
2212
+ }
2213
+ }
2214
+ function compactJson(v) {
2215
+ try {
2216
+ const s = JSON.stringify(v);
2217
+ return s.length > 200 ? s.slice(0, 200) + "…" : s;
2218
+ } catch {
2219
+ return "?";
2220
+ }
2221
+ }
2222
+ async function collectArtifacts(sandbox) {
2223
+ const out = [];
2224
+ await mkdir2(sandbox.artifact_dir, { recursive: true });
2225
+ const entries = await readdir(sandbox.artifact_dir);
2226
+ const sidecars = new Map;
2227
+ for (const e of entries) {
2228
+ if (e.endsWith(".meta.json")) {
2229
+ try {
2230
+ const raw = await readFile2(join7(sandbox.artifact_dir, e), "utf8");
2231
+ sidecars.set(e.replace(/\.meta\.json$/, ""), JSON.parse(raw));
2232
+ } catch {}
2233
+ }
2234
+ }
2235
+ for (const e of entries) {
2236
+ if (e.endsWith(".meta.json") || e === "mcp-config.json")
2237
+ continue;
2238
+ const hostPath = join7(sandbox.artifact_dir, e);
2239
+ const stat = await readFile2(hostPath).then((b) => ({ size: b.byteLength }), () => null);
2240
+ if (!stat)
2241
+ continue;
2242
+ const meta = sidecars.get(e);
2243
+ out.push({
2244
+ filename: e,
2245
+ title: typeof meta?.title === "string" && meta.title || e.replace(/\.[^.]+$/, ""),
2246
+ size_bytes: stat.size,
2247
+ host_path: hostPath,
2248
+ sandbox_path: typeof meta?.sandbox_path === "string" && meta.sandbox_path || undefined
2249
+ });
2250
+ }
2251
+ return out;
2252
+ }
2253
+ function defaultMcpServerCommand() {
2254
+ const here = dirname2(fileURLToPath(import.meta.url));
2255
+ const isTsxRun = here.endsWith("/src");
2256
+ const mcpServerPath = resolve(here, isTsxRun ? "./mcp-server.ts" : "./mcp-server.js");
2257
+ return isTsxRun ? { command: "npx", args: ["--no-install", "tsx", mcpServerPath] } : { command: "node", args: ["--enable-source-maps", mcpServerPath] };
2258
+ }
2259
+ function shellQuote(s) {
2260
+ return `'${s.replace(/'/g, `'\\''`)}'`;
2261
+ }
2262
+
2263
+ // src/repo-runs.ts
2264
+ var CLAUDE_BIN = process.env.BEEVIBE_CLAUDE_BIN ?? "claude";
2265
+ async function runRepoDispatch(deps, payload, abortSignal) {
2266
+ const rr = payload.run_repo;
2267
+ if (!rr) {
2268
+ throw new Error("run_repo dispatch missing payload.run_repo");
2269
+ }
2270
+ console.log(`[daemon/repo-run] sess=${payload.session_id} repo_run=${rr.repo_run_id} repo=${rr.repo_url}`);
2271
+ const buffer = [];
2272
+ let flushTimer;
2273
+ const flush = async () => {
2274
+ if (buffer.length === 0)
2275
+ return;
2276
+ const events = buffer.splice(0);
2277
+ try {
2278
+ await deps.api.post("/runtime/events", { events });
2279
+ } catch (err) {
2280
+ console.warn("[daemon/repo-run] /runtime/events POST failed:", err instanceof Error ? err.message : String(err));
2281
+ }
2282
+ };
2283
+ const scheduleFlush = () => {
2284
+ if (flushTimer)
2285
+ return;
2286
+ flushTimer = setTimeout(() => {
2287
+ flushTimer = undefined;
2288
+ flush();
2289
+ }, 250);
2290
+ };
2291
+ let pushedUpTo = 0;
2292
+ const pushNew = (transcript) => {
2293
+ for (let i = pushedUpTo;i < transcript.length; i++) {
2294
+ const ev = transcript[i];
2295
+ if (!ev)
2296
+ continue;
2297
+ buffer.push({
2298
+ session_id: payload.session_id,
2299
+ kind: mapKind(ev.kind),
2300
+ content: ev.text
2301
+ });
2302
+ }
2303
+ pushedUpTo = transcript.length;
2304
+ if (buffer.length >= 16)
2305
+ flush();
2306
+ else
2307
+ scheduleFlush();
2308
+ };
2309
+ const installLines = [];
2310
+ let invocation;
2311
+ const noteCommandFromToolCall = (text) => {
2312
+ const m = text.match(/sandbox_exec\(\{?"?cmd"?:?\s*"([^"]+)"/);
2313
+ if (!m)
2314
+ return;
2315
+ const cmd = m[1] ?? "";
2316
+ if (!cmd)
2317
+ return;
2318
+ if (isInstallCommand(cmd))
2319
+ installLines.push(cmd);
2320
+ else
2321
+ invocation = cmd;
2322
+ };
2323
+ const result = await runRepoAgent({
2324
+ run_id: rr.repo_run_id,
2325
+ repo_url: rr.repo_url,
2326
+ goal: rr.goal,
2327
+ input_url: rr.input_url,
2328
+ input_filename: rr.input_filename,
2329
+ claude_bin: CLAUDE_BIN,
2330
+ max_runtime_seconds: (rr.limits?.wall_clock_minutes ?? 20) * 60,
2331
+ on_state: (s) => {
2332
+ pushNew(s.transcript);
2333
+ for (let i = pushedUpTo - (s.transcript.length - 0);i < s.transcript.length; i++) {
2334
+ if (i < 0)
2335
+ continue;
2336
+ const ev = s.transcript[i];
2337
+ if (ev?.kind === "tool_call")
2338
+ noteCommandFromToolCall(ev.text);
2339
+ }
2340
+ if (abortSignal?.aborted) {
2341
+ buffer.push({
2342
+ session_id: payload.session_id,
2343
+ kind: "summary",
2344
+ content: "[run cancelled by user]"
2345
+ });
2346
+ flush();
2347
+ }
2348
+ }
2349
+ });
2350
+ if (flushTimer) {
2351
+ clearTimeout(flushTimer);
2352
+ flushTimer = undefined;
2353
+ }
2354
+ await flush();
2355
+ const status = result.status === "succeeded" ? "succeeded" : result.status === "blocked" ? "failed" : result.status === "failed" ? "failed" : "failed";
2356
+ const artifacts = result.artifacts.map((a) => ({
2357
+ filename: a.filename,
2358
+ title: a.title,
2359
+ size_bytes: a.size_bytes,
2360
+ host_path: a.host_path,
2361
+ sandbox_path: a.sandbox_path
2362
+ }));
2363
+ const done = {
2364
+ session_id: payload.session_id,
2365
+ status,
2366
+ result_summary: result.status === "succeeded" ? `Exported ${artifacts.length} artifact${artifacts.length === 1 ? "" : "s"}.` : result.error ?? "Run did not produce an artifact.",
2367
+ error: result.error,
2368
+ run_repo: {
2369
+ repo_run_id: rr.repo_run_id,
2370
+ install_log: installLines.length ? installLines.join(`
2371
+ `) : undefined,
2372
+ invocation,
2373
+ artifacts
2374
+ }
2375
+ };
2376
+ if (status === "succeeded") {
2377
+ console.log(`[daemon/repo-run] sess=${payload.session_id} succeeded artifacts=${artifacts.length}`);
2378
+ } else {
2379
+ console.error(`[daemon/repo-run] sess=${payload.session_id} status=${status}` + (result.error ? `
2380
+ error:
2381
+ ${result.error.split(`
2382
+ `).join(`
2383
+ `)}` : ""));
2384
+ }
2385
+ try {
2386
+ await deps.api.post("/runtime/done", done);
2387
+ } catch (err) {
2388
+ console.error("[daemon/repo-run] /runtime/done POST failed:", err instanceof Error ? err.message : String(err));
2389
+ }
2390
+ }
2391
+ function mapKind(kind) {
2392
+ switch (kind) {
2393
+ case "agent":
2394
+ return "agent";
2395
+ case "tool_call":
2396
+ return "tool_call";
2397
+ case "log":
2398
+ return "summary";
2399
+ case "error":
2400
+ return "tool_result";
2401
+ }
2402
+ }
2403
+ function isInstallCommand(cmd) {
2404
+ return /^(pip\s|pip3\s|apt-get\s|apt\s|brew\s|npm\s+(install|i)|yarn\s+(add|install)|pnpm\s+(add|install)|git\s+clone)/.test(cmd.trim());
2405
+ }
2406
+
1735
2407
  // src/spawner.ts
1736
2408
  async function runDispatch(deps, payload, abortSignal) {
2409
+ if (payload.type === "run_repo") {
2410
+ await runRepoDispatch({ api: deps.api }, payload, abortSignal);
2411
+ return;
2412
+ }
1737
2413
  const syntheticAgent = {
1738
2414
  id: payload.agent_id,
1739
2415
  api_key: payload.agent_api_key,
@@ -1745,7 +2421,7 @@ async function runDispatch(deps, payload, abortSignal) {
1745
2421
  const registry = deps.runtimeRegistry ?? createDefaultRuntimeRegistry();
1746
2422
  const runtime3 = registry[payload.runtime_type];
1747
2423
  if (!runtime3) {
1748
- throw new Error(`No runtime registered for dispatch payload type '${payload.runtime_type}'`);
2424
+ throw new Error(runtimeMissingError(payload.runtime_type));
1749
2425
  }
1750
2426
  const buffer = [];
1751
2427
  let flushTimer;
@@ -1788,7 +2464,6 @@ async function runDispatch(deps, payload, abortSignal) {
1788
2464
  system_prompt_append: payload.system_prompt_append,
1789
2465
  model: payload.model,
1790
2466
  max_turns: payload.max_turns,
1791
- disallowed_tools: payload.disallowed_tools,
1792
2467
  env: payload.env,
1793
2468
  resume_session_id: payload.resume_session_id,
1794
2469
  abort_signal: abortSignal,
@@ -1964,14 +2639,14 @@ class Claimer {
1964
2639
  // src/skills-cache.ts
1965
2640
  import { promises as fs2 } from "node:fs";
1966
2641
  import { homedir as homedir3 } from "node:os";
1967
- import { join as join6 } from "node:path";
2642
+ import { join as join8 } from "node:path";
1968
2643
  function skillsCacheDir() {
1969
- return join6(homedir3(), ".beevibe", "skills");
2644
+ return join8(homedir3(), ".beevibe", "skills");
1970
2645
  }
1971
2646
  var VERSION_FILE = ".version";
1972
2647
  async function readCachedVersion() {
1973
2648
  try {
1974
- return (await fs2.readFile(join6(skillsCacheDir(), VERSION_FILE), "utf8")).trim();
2649
+ return (await fs2.readFile(join8(skillsCacheDir(), VERSION_FILE), "utf8")).trim();
1975
2650
  } catch {
1976
2651
  return;
1977
2652
  }
@@ -1992,19 +2667,19 @@ async function syncSkillsCache(api) {
1992
2667
  if (!dirent.isDirectory())
1993
2668
  continue;
1994
2669
  if (dirent.name === "beevibe" || dirent.name.startsWith("beevibe-")) {
1995
- await fs2.rm(join6(cache, dirent.name), { recursive: true, force: true });
2670
+ await fs2.rm(join8(cache, dirent.name), { recursive: true, force: true });
1996
2671
  }
1997
2672
  }
1998
2673
  for (const skill of res.skills) {
1999
- const skillDir = join6(cache, skill.name);
2674
+ const skillDir = join8(cache, skill.name);
2000
2675
  await fs2.mkdir(skillDir, { recursive: true, mode: 448 });
2001
2676
  for (const file of skill.files) {
2002
- const filePath = join6(skillDir, file.path);
2003
- await fs2.mkdir(join6(filePath, ".."), { recursive: true });
2677
+ const filePath = join8(skillDir, file.path);
2678
+ await fs2.mkdir(join8(filePath, ".."), { recursive: true });
2004
2679
  await fs2.writeFile(filePath, file.content, { mode: 384 });
2005
2680
  }
2006
2681
  }
2007
- await fs2.writeFile(join6(cache, VERSION_FILE), res.version, { mode: 384 });
2682
+ await fs2.writeFile(join8(cache, VERSION_FILE), res.version, { mode: 384 });
2008
2683
  return cache;
2009
2684
  }
2010
2685
 
@@ -2137,8 +2812,8 @@ async function runSync() {
2137
2812
  // src/update.ts
2138
2813
  import { createHash } from "node:crypto";
2139
2814
  import { createWriteStream, mkdtempSync, rmSync as rmSync2, chmodSync, renameSync } from "node:fs";
2140
- import { tmpdir as tmpdir4 } from "node:os";
2141
- import { join as join7 } from "node:path";
2815
+ import { tmpdir as tmpdir5 } from "node:os";
2816
+ import { join as join9 } from "node:path";
2142
2817
  import { Readable } from "node:stream";
2143
2818
  import { pipeline } from "node:stream/promises";
2144
2819
  import { createInterface } from "node:readline/promises";
@@ -2152,7 +2827,7 @@ var PLATFORM_ASSETS = {
2152
2827
  "linux-arm64": "beevibe-daemon-linux-arm64"
2153
2828
  };
2154
2829
  function currentVersion() {
2155
- return "0.1.3";
2830
+ return "0.1.5";
2156
2831
  }
2157
2832
  function isCompiledBinary() {
2158
2833
  if (!process.versions.bun)
@@ -2265,8 +2940,8 @@ async function runUpdate(opts = {}) {
2265
2940
  return;
2266
2941
  }
2267
2942
  }
2268
- const stagingDir = mkdtempSync(join7(tmpdir4(), "beevibe-daemon-update-"));
2269
- const stagingPath = join7(stagingDir, asset);
2943
+ const stagingDir = mkdtempSync(join9(tmpdir5(), "beevibe-daemon-update-"));
2944
+ const stagingPath = join9(stagingDir, asset);
2270
2945
  try {
2271
2946
  console.log(`Downloading ${asset}…`);
2272
2947
  const downloadUrl = `${DOWNLOAD_BASE}/${latest}/${asset}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beevibe/daemon",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Beevibe daemon — runs on each user's machine, claims pending sessions and spawns the CLI locally",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Zhe Pang",