@beevibe/daemon 0.1.3 → 0.1.4

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