0agent 1.0.11 → 1.0.13

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.
package/dist/daemon.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // packages/daemon/src/ZeroAgentDaemon.ts
2
- import { writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "node:fs";
3
- import { resolve as resolve4 } from "node:path";
2
+ import { writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "node:fs";
3
+ import { resolve as resolve5 } from "node:path";
4
4
  import { homedir as homedir3 } from "node:os";
5
5
 
6
6
  // packages/core/src/graph/GraphNode.ts
@@ -1686,429 +1686,535 @@ var EntityScopedContextLoader = class {
1686
1686
  };
1687
1687
 
1688
1688
  // packages/daemon/src/AgentExecutor.ts
1689
- import { spawn } from "node:child_process";
1690
- import { writeFileSync, readFileSync as readFileSync2, readdirSync, mkdirSync, existsSync as existsSync2 } from "node:fs";
1691
- import { resolve as resolve2, dirname, relative } from "node:path";
1689
+ import { spawn as spawn2 } from "node:child_process";
1690
+ import { writeFileSync as writeFileSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "node:fs";
1691
+ import { resolve as resolve3, dirname as dirname2, relative } from "node:path";
1692
1692
 
1693
- // packages/daemon/src/LLMExecutor.ts
1694
- var AGENT_TOOLS = [
1695
- {
1696
- name: "shell_exec",
1697
- description: "Execute a shell command in the working directory. Use for running servers (with & for background), installing packages, running tests, git operations, etc. Returns stdout + stderr.",
1693
+ // packages/daemon/src/capabilities/WebSearchCapability.ts
1694
+ import { execSync, spawnSync } from "node:child_process";
1695
+ var WebSearchCapability = class {
1696
+ name = "web_search";
1697
+ description = "Search the web. Returns titles, URLs, and snippets. No API key needed.";
1698
+ toolDefinition = {
1699
+ name: "web_search",
1700
+ description: "Search the web and return titles, URLs, and snippets. No API key needed. Use first to find pages, then scrape_url for full content.",
1698
1701
  input_schema: {
1699
1702
  type: "object",
1700
1703
  properties: {
1701
- command: { type: "string", description: "Shell command to execute" },
1702
- timeout_ms: { type: "number", description: "Timeout in milliseconds (default 30000)" }
1704
+ query: { type: "string", description: "Search query" },
1705
+ num_results: { type: "number", description: "Number of results (default 5, max 10)" }
1703
1706
  },
1704
- required: ["command"]
1707
+ required: ["query"]
1705
1708
  }
1706
- },
1707
- {
1708
- name: "write_file",
1709
- description: "Write content to a file. Creates parent directories if needed. Use for creating HTML, CSS, JS, config files, etc.",
1710
- input_schema: {
1711
- type: "object",
1712
- properties: {
1713
- path: { type: "string", description: "File path relative to working directory" },
1714
- content: { type: "string", description: "Full file content to write" }
1709
+ };
1710
+ async execute(input, _cwd) {
1711
+ const query = String(input.query ?? "");
1712
+ const n = Math.min(10, Number(input.num_results ?? 5));
1713
+ const start = Date.now();
1714
+ try {
1715
+ const output = await this.ddgHtml(query, n);
1716
+ if (output && output.length > 50) {
1717
+ return { success: true, output, duration_ms: Date.now() - start };
1718
+ }
1719
+ } catch {
1720
+ }
1721
+ try {
1722
+ const output = await this.browserSearch(query, n);
1723
+ return { success: true, output, fallback_used: "browser", duration_ms: Date.now() - start };
1724
+ } catch (err) {
1725
+ return {
1726
+ success: false,
1727
+ output: `Search failed: ${err instanceof Error ? err.message : String(err)}`,
1728
+ duration_ms: Date.now() - start
1729
+ };
1730
+ }
1731
+ }
1732
+ async ddgHtml(query, n) {
1733
+ const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}&kl=us-en`;
1734
+ const res = await fetch(url, {
1735
+ headers: {
1736
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/120.0 Safari/537.36",
1737
+ "Accept": "text/html",
1738
+ "Accept-Language": "en-US,en;q=0.9"
1715
1739
  },
1716
- required: ["path", "content"]
1740
+ signal: AbortSignal.timeout(1e4)
1741
+ });
1742
+ const html = await res.text();
1743
+ const titleRe = /<a[^>]+class="result__a"[^>]+href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/g;
1744
+ const snippetRe = /<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
1745
+ const titles = [];
1746
+ const snippets = [];
1747
+ let m;
1748
+ while ((m = titleRe.exec(html)) !== null && titles.length < n) {
1749
+ let href = m[1];
1750
+ const title = m[2].replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").trim();
1751
+ const uddg = href.match(/[?&]uddg=([^&]+)/);
1752
+ if (uddg) href = decodeURIComponent(uddg[1]);
1753
+ if (href.startsWith("http") && title) titles.push({ url: href, title });
1754
+ }
1755
+ while ((m = snippetRe.exec(html)) !== null && snippets.length < n) {
1756
+ snippets.push(m[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim());
1757
+ }
1758
+ if (titles.length === 0) throw new Error("No results parsed from DDG");
1759
+ return titles.map(
1760
+ (t, i) => `${i + 1}. ${t.title}
1761
+ URL: ${t.url}${snippets[i] ? `
1762
+ ${snippets[i]}` : ""}`
1763
+ ).join("\n\n");
1764
+ }
1765
+ async browserSearch(query, n) {
1766
+ const searchUrl = `https://duckduckgo.com/?q=${encodeURIComponent(query)}`;
1767
+ try {
1768
+ const result = spawnSync("node", ["-e", `
1769
+ const { chromium } = require('playwright');
1770
+ (async () => {
1771
+ const b = await chromium.launch({ headless: true });
1772
+ const p = await b.newPage();
1773
+ await p.goto('${searchUrl}', { timeout: 15000 });
1774
+ await p.waitForSelector('[data-result]', { timeout: 8000 }).catch(() => {});
1775
+ const results = await p.$$eval('[data-result]', els =>
1776
+ els.slice(0, ${n}).map(el => ({
1777
+ title: el.querySelector('h2')?.innerText ?? '',
1778
+ url: el.querySelector('a')?.href ?? '',
1779
+ snippet: el.querySelector('[data-result="snippet"]')?.innerText ?? ''
1780
+ }))
1781
+ );
1782
+ await b.close();
1783
+ console.log(JSON.stringify(results));
1784
+ })();
1785
+ `], { timeout: 25e3, encoding: "utf8" });
1786
+ if (result.status === 0 && result.stdout) {
1787
+ const results = JSON.parse(result.stdout);
1788
+ return results.map(
1789
+ (r, i) => `${i + 1}. ${r.title}
1790
+ URL: ${r.url}${r.snippet ? `
1791
+ ${r.snippet}` : ""}`
1792
+ ).join("\n\n");
1793
+ }
1794
+ } catch {
1795
+ }
1796
+ const chrome = this.findChrome();
1797
+ if (chrome) {
1798
+ const result = spawnSync(chrome, [
1799
+ "--headless",
1800
+ "--no-sandbox",
1801
+ "--disable-gpu",
1802
+ `--dump-dom`,
1803
+ searchUrl
1804
+ ], { timeout: 15e3, encoding: "utf8" });
1805
+ if (result.stdout) {
1806
+ const html = result.stdout;
1807
+ const titles = [...html.matchAll(/<h2[^>]*>([\s\S]*?)<\/h2>/g)].map((m) => m[1].replace(/<[^>]+>/g, "").trim()).filter((t) => t.length > 5).slice(0, n);
1808
+ if (titles.length > 0) return titles.map((t, i) => `${i + 1}. ${t}`).join("\n");
1809
+ }
1810
+ }
1811
+ throw new Error("No browser available for fallback search");
1812
+ }
1813
+ findChrome() {
1814
+ const candidates = [
1815
+ "google-chrome",
1816
+ "google-chrome-stable",
1817
+ "chromium-browser",
1818
+ "chromium",
1819
+ "/usr/bin/google-chrome",
1820
+ "/usr/bin/chromium-browser",
1821
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
1822
+ ];
1823
+ for (const c of candidates) {
1824
+ try {
1825
+ execSync(`which "${c}" 2>/dev/null || test -f "${c}"`, { stdio: "pipe" });
1826
+ return c;
1827
+ } catch {
1828
+ }
1717
1829
  }
1718
- },
1719
- {
1720
- name: "read_file",
1721
- description: "Read a file's contents.",
1830
+ return null;
1831
+ }
1832
+ };
1833
+
1834
+ // packages/daemon/src/capabilities/BrowserCapability.ts
1835
+ import { spawnSync as spawnSync2, execSync as execSync2 } from "node:child_process";
1836
+ var BrowserCapability = class {
1837
+ name = "browser_open";
1838
+ description = "Open a URL in a real browser. Returns page content, can take screenshots. Use when scrape_url fails on JS-heavy pages.";
1839
+ toolDefinition = {
1840
+ name: "browser_open",
1841
+ description: "Open a URL in a real headless browser. Handles JavaScript-rendered pages, SPAs, login flows. Use when scrape_url fails.",
1722
1842
  input_schema: {
1723
1843
  type: "object",
1724
1844
  properties: {
1725
- path: { type: "string", description: "File path relative to working directory" }
1845
+ url: { type: "string", description: "URL to open" },
1846
+ action: { type: "string", description: 'What to do: "read" (default), "screenshot", "click <selector>", "fill <selector> <value>"' },
1847
+ wait_for: { type: "string", description: "CSS selector to wait for before extracting content" },
1848
+ extract: { type: "string", description: "CSS selector to extract specific element text" }
1726
1849
  },
1727
- required: ["path"]
1850
+ required: ["url"]
1851
+ }
1852
+ };
1853
+ async execute(input, _cwd) {
1854
+ const url = String(input.url ?? "");
1855
+ const action = String(input.action ?? "read");
1856
+ const waitFor = input.wait_for ? String(input.wait_for) : null;
1857
+ const extract = input.extract ? String(input.extract) : null;
1858
+ const start = Date.now();
1859
+ if (!url.startsWith("http")) {
1860
+ return { success: false, output: "URL must start with http:// or https://", duration_ms: 0 };
1861
+ }
1862
+ try {
1863
+ const output = await this.playwrightFetch(url, action, waitFor, extract);
1864
+ return { success: true, output, duration_ms: Date.now() - start };
1865
+ } catch {
1866
+ }
1867
+ try {
1868
+ const output = await this.chromeFetch(url);
1869
+ return { success: true, output, fallback_used: "system-chrome", duration_ms: Date.now() - start };
1870
+ } catch {
1871
+ }
1872
+ try {
1873
+ const res = await fetch(url, {
1874
+ headers: {
1875
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/120.0 Safari/537.36",
1876
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
1877
+ "Accept-Language": "en-US,en;q=0.9",
1878
+ "Accept-Encoding": "gzip, deflate, br"
1879
+ },
1880
+ signal: AbortSignal.timeout(15e3)
1881
+ });
1882
+ const text = await res.text();
1883
+ const plain = text.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 5e3);
1884
+ return { success: true, output: plain, fallback_used: "fetch", duration_ms: Date.now() - start };
1885
+ } catch (err) {
1886
+ return {
1887
+ success: false,
1888
+ output: `Browser unavailable. Install Playwright: npx playwright install chromium`,
1889
+ error: err instanceof Error ? err.message : String(err),
1890
+ duration_ms: Date.now() - start
1891
+ };
1892
+ }
1893
+ }
1894
+ async playwrightFetch(url, action, waitFor, extract) {
1895
+ const waitLine = waitFor ? `await p.waitForSelector('${waitFor}', { timeout: 8000 }).catch(() => {});` : "";
1896
+ const extractLine = extract ? `const text = await p.$eval('${extract}', el => el.innerText).catch(() => page_text);` : `const text = page_text;`;
1897
+ const script = `
1898
+ const { chromium } = require('playwright');
1899
+ (async () => {
1900
+ const b = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
1901
+ const p = await b.newPage();
1902
+ await p.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36');
1903
+ await p.goto('${url}', { waitUntil: 'domcontentloaded', timeout: 20000 });
1904
+ ${waitLine}
1905
+ const page_text = await p.evaluate(() => document.body.innerText ?? '');
1906
+ ${extractLine}
1907
+ console.log(text.slice(0, 6000));
1908
+ await b.close();
1909
+ })().catch(e => { console.error(e.message); process.exit(1); });
1910
+ `;
1911
+ const result = spawnSync2("node", ["-e", script], { timeout: 3e4, encoding: "utf8" });
1912
+ if (result.status !== 0) throw new Error(result.stderr || "Playwright failed");
1913
+ return result.stdout.trim();
1914
+ }
1915
+ async chromeFetch(url) {
1916
+ const candidates = [
1917
+ "google-chrome",
1918
+ "chromium-browser",
1919
+ "chromium",
1920
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
1921
+ ];
1922
+ for (const chrome of candidates) {
1923
+ try {
1924
+ execSync2(`which "${chrome}" 2>/dev/null || test -f "${chrome}"`, { stdio: "pipe" });
1925
+ const result = spawnSync2(chrome, ["--headless", "--no-sandbox", "--disable-gpu", "--dump-dom", url], {
1926
+ timeout: 15e3,
1927
+ encoding: "utf8"
1928
+ });
1929
+ if (result.status === 0 && result.stdout) {
1930
+ return result.stdout.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 5e3);
1931
+ }
1932
+ } catch {
1933
+ }
1728
1934
  }
1729
- },
1730
- {
1731
- name: "list_dir",
1732
- description: "List files and directories.",
1935
+ throw new Error("No system Chrome found");
1936
+ }
1937
+ };
1938
+
1939
+ // packages/daemon/src/capabilities/ScraperCapability.ts
1940
+ import { spawnSync as spawnSync3 } from "node:child_process";
1941
+ var ScraperCapability = class {
1942
+ name = "scrape_url";
1943
+ description = "Scrape a URL. Tries fast HTTP fetch, then Scrapling, then browser if needed.";
1944
+ browser = new BrowserCapability();
1945
+ toolDefinition = {
1946
+ name: "scrape_url",
1947
+ description: "Scrape a URL and return clean content. Handles JS-rendered pages. Fallback chain: HTTP \u2192 Scrapling \u2192 Browser.",
1733
1948
  input_schema: {
1734
1949
  type: "object",
1735
1950
  properties: {
1736
- path: { type: "string", description: 'Directory path relative to working directory (default: ".")' }
1951
+ url: { type: "string", description: "URL to scrape" },
1952
+ mode: { type: "string", description: '"text" (default), "links", "tables", "markdown"' },
1953
+ selector: { type: "string", description: "Optional CSS selector to target element" }
1954
+ },
1955
+ required: ["url"]
1956
+ }
1957
+ };
1958
+ async execute(input, cwd) {
1959
+ const url = String(input.url ?? "");
1960
+ const mode = String(input.mode ?? "text");
1961
+ const selector = input.selector ? String(input.selector) : null;
1962
+ const start = Date.now();
1963
+ if (!url.startsWith("http")) {
1964
+ return { success: false, output: "URL must start with http:// or https://", duration_ms: 0 };
1965
+ }
1966
+ try {
1967
+ const output = await this.plainFetch(url, mode, selector);
1968
+ if (output && output.length > 100) {
1969
+ return { success: true, output, duration_ms: Date.now() - start };
1737
1970
  }
1971
+ } catch {
1738
1972
  }
1739
- },
1740
- {
1741
- name: "web_search",
1742
- description: "Search the web and return titles, URLs, and snippets. No API key needed. Use this first to find relevant pages, then scrape_url for full content.",
1973
+ try {
1974
+ const output = await this.scraplingFetch(url, mode);
1975
+ if (output && output.length > 100) {
1976
+ return { success: true, output, fallback_used: "scrapling", duration_ms: Date.now() - start };
1977
+ }
1978
+ } catch {
1979
+ }
1980
+ const browserResult = await this.browser.execute({ url, extract: selector ?? void 0 }, cwd);
1981
+ return { ...browserResult, fallback_used: "browser", duration_ms: Date.now() - start };
1982
+ }
1983
+ async plainFetch(url, mode, selector) {
1984
+ const res = await fetch(url, {
1985
+ headers: { "User-Agent": "Mozilla/5.0 AppleWebKit/537.36 Chrome/120.0" },
1986
+ signal: AbortSignal.timeout(12e3)
1987
+ });
1988
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1989
+ const html = await res.text();
1990
+ if (mode === "links") {
1991
+ const links = [...html.matchAll(/href="(https?:\/\/[^"]+)"/g)].map((m) => m[1]).filter((v, i, a) => a.indexOf(v) === i).slice(0, 30);
1992
+ return links.join("\n");
1993
+ }
1994
+ if (mode === "tables") {
1995
+ const tables = [...html.matchAll(/<table[\s\S]*?<\/table>/gi)].map(
1996
+ (m) => m[0].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim()
1997
+ ).slice(0, 5);
1998
+ return tables.join("\n---\n");
1999
+ }
2000
+ const text = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
2001
+ return text.slice(0, 6e3) + (text.length > 6e3 ? "\n\u2026[truncated]" : "");
2002
+ }
2003
+ async scraplingFetch(url, mode) {
2004
+ const extractLine = mode === "links" ? `result = [a.attrib.get('href','') for a in page.find_all('a') if a.attrib.get('href','').startswith('http')]` : `result = page.get_all_text()`;
2005
+ const script = `
2006
+ import sys
2007
+ try:
2008
+ from scrapling import Fetcher
2009
+ except ImportError:
2010
+ import subprocess
2011
+ subprocess.run([sys.executable,'-m','pip','install','scrapling','-q'],check=True)
2012
+ from scrapling import Fetcher
2013
+ f = Fetcher(auto_match=False)
2014
+ page = f.get('${url}', timeout=20)
2015
+ ${extractLine}
2016
+ if isinstance(result, list):
2017
+ print('\\n'.join(str(r) for r in result[:30]))
2018
+ else:
2019
+ t = str(result).strip()
2020
+ print(t[:5000] + ('...' if len(t)>5000 else ''))
2021
+ `.trim();
2022
+ const result = spawnSync3("python3", ["-c", script], { timeout: 35e3, encoding: "utf8" });
2023
+ if (result.status !== 0) throw new Error(result.stderr || "Scrapling failed");
2024
+ return result.stdout.trim();
2025
+ }
2026
+ };
2027
+
2028
+ // packages/daemon/src/capabilities/ShellCapability.ts
2029
+ import { spawn } from "node:child_process";
2030
+ var ShellCapability = class {
2031
+ name = "shell_exec";
2032
+ description = "Execute shell commands in the working directory.";
2033
+ toolDefinition = {
2034
+ name: "shell_exec",
2035
+ description: "Execute a shell command. Use & for background processes. Returns stdout+stderr.",
1743
2036
  input_schema: {
1744
2037
  type: "object",
1745
2038
  properties: {
1746
- query: { type: "string", description: "Search query" },
1747
- num_results: { type: "number", description: "Number of results (default 5, max 10)" }
2039
+ command: { type: "string", description: "Shell command to execute" },
2040
+ timeout_ms: { type: "number", description: "Timeout (default 30000ms)" }
1748
2041
  },
1749
- required: ["query"]
2042
+ required: ["command"]
1750
2043
  }
1751
- },
1752
- {
1753
- name: "scrape_url",
1754
- description: "Scrape a URL and return clean structured content. Handles JavaScript-rendered pages, auto-adapts to page structure, returns text/links/metadata. Better than shell curl for web pages.",
2044
+ };
2045
+ async execute(input, cwd) {
2046
+ const command = String(input.command ?? "");
2047
+ const timeout = Number(input.timeout_ms ?? 3e4);
2048
+ const start = Date.now();
2049
+ return new Promise((resolve_) => {
2050
+ const chunks = [];
2051
+ const proc = spawn("bash", ["-c", command], {
2052
+ cwd,
2053
+ env: { ...process.env, TERM: "dumb" },
2054
+ timeout
2055
+ });
2056
+ proc.stdout.on("data", (d) => chunks.push(d.toString()));
2057
+ proc.stderr.on("data", (d) => chunks.push(d.toString()));
2058
+ proc.on("close", (code) => {
2059
+ const output = chunks.join("").trim();
2060
+ resolve_({
2061
+ success: code === 0,
2062
+ output: output || (code === 0 ? "(no output)" : `exit ${code}`),
2063
+ duration_ms: Date.now() - start,
2064
+ ...code !== 0 && { error: `exit code ${code}` }
2065
+ });
2066
+ });
2067
+ proc.on("error", (err) => {
2068
+ resolve_({ success: false, output: err.message, error: err.message, duration_ms: Date.now() - start });
2069
+ });
2070
+ });
2071
+ }
2072
+ };
2073
+
2074
+ // packages/daemon/src/capabilities/FileCapability.ts
2075
+ import { readFileSync as readFileSync2, writeFileSync, readdirSync, mkdirSync, existsSync as existsSync2 } from "node:fs";
2076
+ import { resolve as resolve2, dirname } from "node:path";
2077
+ var FileCapability = class {
2078
+ name = "file_op";
2079
+ description = "Read, write, or list files. Scoped to working directory.";
2080
+ toolDefinition = {
2081
+ name: "file_op",
2082
+ description: "Read, write, or list files in the working directory.",
1755
2083
  input_schema: {
1756
2084
  type: "object",
1757
2085
  properties: {
1758
- url: { type: "string", description: "URL to scrape" },
1759
- mode: { type: "string", description: 'What to extract: "text" (default), "links", "tables", "full", "markdown"' },
1760
- selector: { type: "string", description: "Optional CSS selector to target specific element" },
1761
- wait_ms: { type: "number", description: "Wait N ms after page load (for JS-heavy pages, default 0)" }
2086
+ op: { type: "string", description: '"read", "write", or "list"' },
2087
+ path: { type: "string", description: "File or directory path (relative to cwd)" },
2088
+ content: { type: "string", description: "Content for write operation" }
1762
2089
  },
1763
- required: ["url"]
2090
+ required: ["op", "path"]
2091
+ }
2092
+ };
2093
+ async execute(input, cwd) {
2094
+ const op = String(input.op ?? "read");
2095
+ const rel = String(input.path ?? ".");
2096
+ const safe = resolve2(cwd, rel);
2097
+ const start = Date.now();
2098
+ if (!safe.startsWith(cwd)) {
2099
+ return { success: false, output: "Path outside working directory", duration_ms: 0 };
2100
+ }
2101
+ try {
2102
+ if (op === "read") {
2103
+ if (!existsSync2(safe)) return { success: false, output: `Not found: ${rel}`, duration_ms: Date.now() - start };
2104
+ const content = readFileSync2(safe, "utf8");
2105
+ return {
2106
+ success: true,
2107
+ output: content.length > 8e3 ? content.slice(0, 8e3) + "\n\u2026[truncated]" : content,
2108
+ duration_ms: Date.now() - start
2109
+ };
2110
+ }
2111
+ if (op === "write") {
2112
+ mkdirSync(dirname(safe), { recursive: true });
2113
+ writeFileSync(safe, String(input.content ?? ""), "utf8");
2114
+ return { success: true, output: `Written: ${rel} (${String(input.content ?? "").length} bytes)`, duration_ms: Date.now() - start };
2115
+ }
2116
+ if (op === "list") {
2117
+ if (!existsSync2(safe)) return { success: false, output: `Not found: ${rel}`, duration_ms: Date.now() - start };
2118
+ const entries = readdirSync(safe, { withFileTypes: true }).filter((e) => !e.name.startsWith(".") && e.name !== "node_modules").map((e) => `${e.isDirectory() ? "d" : "f"} ${e.name}`).join("\n");
2119
+ return { success: true, output: entries || "(empty)", duration_ms: Date.now() - start };
2120
+ }
2121
+ return { success: false, output: `Unknown op: ${op}`, duration_ms: Date.now() - start };
2122
+ } catch (err) {
2123
+ return { success: false, output: `Error: ${err instanceof Error ? err.message : String(err)}`, duration_ms: Date.now() - start };
1764
2124
  }
1765
2125
  }
1766
- ];
1767
- var LLMExecutor = class {
1768
- constructor(config) {
1769
- this.config = config;
2126
+ };
2127
+
2128
+ // packages/daemon/src/capabilities/CapabilityRegistry.ts
2129
+ var CapabilityRegistry = class {
2130
+ capabilities = /* @__PURE__ */ new Map();
2131
+ constructor() {
2132
+ this.register(new WebSearchCapability());
2133
+ this.register(new BrowserCapability());
2134
+ this.register(new ScraperCapability());
2135
+ this.register(new ShellCapability());
2136
+ this.register(new FileCapability());
1770
2137
  }
1771
- get isConfigured() {
1772
- if (this.config.provider === "ollama") return true;
1773
- return !!this.config.api_key?.trim();
2138
+ register(cap) {
2139
+ this.capabilities.set(cap.name, cap);
1774
2140
  }
1775
- // ─── Single completion (no tools, no streaming) ──────────────────────────
1776
- async complete(messages, system) {
1777
- const res = await this.completeWithTools(messages, [], system, void 0);
1778
- return { content: res.content, tokens_used: res.tokens_used, model: res.model };
2141
+ get(name) {
2142
+ return this.capabilities.get(name);
1779
2143
  }
1780
- // ─── Tool-calling completion with optional streaming ─────────────────────
1781
- async completeWithTools(messages, tools, system, onToken) {
1782
- switch (this.config.provider) {
1783
- case "anthropic":
1784
- return this.anthropic(messages, tools, system, onToken);
1785
- case "openai":
1786
- return this.openai(messages, tools, system, onToken);
1787
- case "xai":
1788
- return this.openai(messages, tools, system, onToken, "https://api.x.ai/v1");
1789
- case "gemini":
1790
- return this.openai(messages, tools, system, onToken, "https://generativelanguage.googleapis.com/v1beta/openai");
1791
- case "ollama":
1792
- return this.ollama(messages, system, onToken);
1793
- default:
1794
- return this.openai(messages, tools, system, onToken);
2144
+ getToolDefinitions() {
2145
+ return [...this.capabilities.values()].map((c) => c.toolDefinition);
2146
+ }
2147
+ async execute(toolName, input, cwd) {
2148
+ const cap = this.capabilities.get(toolName);
2149
+ if (!cap) {
2150
+ return { success: false, output: `Unknown capability: ${toolName}`, duration_ms: 0 };
2151
+ }
2152
+ try {
2153
+ return await cap.execute(input, cwd);
2154
+ } catch (err) {
2155
+ return {
2156
+ success: false,
2157
+ output: `Capability error: ${err instanceof Error ? err.message : String(err)}`,
2158
+ duration_ms: 0
2159
+ };
1795
2160
  }
1796
2161
  }
1797
- // ─── Anthropic ───────────────────────────────────────────────────────────
1798
- async anthropic(messages, tools, system, onToken) {
1799
- const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
1800
- const filtered = messages.filter((m) => m.role !== "system");
1801
- const anthropicMsgs = filtered.map((m) => {
1802
- if (m.role === "tool") {
1803
- return {
1804
- role: "user",
1805
- content: [{ type: "tool_result", tool_use_id: m.tool_call_id, content: m.content }]
1806
- };
2162
+ list() {
2163
+ return [...this.capabilities.values()].map((c) => ({ name: c.name, description: c.description }));
2164
+ }
2165
+ };
2166
+
2167
+ // packages/daemon/src/AgentExecutor.ts
2168
+ var AgentExecutor = class {
2169
+ constructor(llm, config, onStep, onToken) {
2170
+ this.llm = llm;
2171
+ this.config = config;
2172
+ this.onStep = onStep;
2173
+ this.onToken = onToken;
2174
+ this.cwd = config.cwd;
2175
+ this.maxIterations = config.max_iterations ?? 20;
2176
+ this.maxCommandMs = config.max_command_ms ?? 3e4;
2177
+ this.registry = new CapabilityRegistry();
2178
+ }
2179
+ cwd;
2180
+ maxIterations;
2181
+ maxCommandMs;
2182
+ registry;
2183
+ async execute(task, systemContext) {
2184
+ const filesWritten = [];
2185
+ const commandsRun = [];
2186
+ let totalTokens = 0;
2187
+ let modelName = "";
2188
+ const systemPrompt = this.buildSystemPrompt(systemContext);
2189
+ const messages = [
2190
+ { role: "user", content: task }
2191
+ ];
2192
+ let finalOutput = "";
2193
+ for (let i = 0; i < this.maxIterations; i++) {
2194
+ this.onStep(i === 0 ? "Thinking\u2026" : "Continuing\u2026");
2195
+ let response;
2196
+ try {
2197
+ response = await this.llm.completeWithTools(
2198
+ messages,
2199
+ this.registry.getToolDefinitions(),
2200
+ systemPrompt,
2201
+ // Only stream tokens on the final (non-tool) turn
2202
+ (token) => {
2203
+ this.onToken(token);
2204
+ finalOutput += token;
2205
+ }
2206
+ );
2207
+ } catch (err) {
2208
+ const msg = err instanceof Error ? err.message : String(err);
2209
+ this.onStep(`LLM error: ${msg}`);
2210
+ finalOutput = `Error: ${msg}`;
2211
+ break;
1807
2212
  }
1808
- if (m.role === "assistant" && m.tool_calls?.length) {
1809
- return {
1810
- role: "assistant",
1811
- content: [
1812
- ...m.content ? [{ type: "text", text: m.content }] : [],
1813
- ...m.tool_calls.map((tc) => ({
1814
- type: "tool_use",
1815
- id: tc.id,
1816
- name: tc.name,
1817
- input: tc.input
1818
- }))
1819
- ]
1820
- };
1821
- }
1822
- return { role: m.role, content: m.content };
1823
- });
1824
- const body = {
1825
- model: this.config.model,
1826
- max_tokens: 8192,
1827
- messages: anthropicMsgs,
1828
- stream: true
1829
- };
1830
- if (sysContent) body.system = sysContent;
1831
- if (tools.length > 0) {
1832
- body.tools = tools.map((t) => ({
1833
- name: t.name,
1834
- description: t.description,
1835
- input_schema: t.input_schema
1836
- }));
1837
- }
1838
- const res = await fetch("https://api.anthropic.com/v1/messages", {
1839
- method: "POST",
1840
- headers: {
1841
- "Content-Type": "application/json",
1842
- "x-api-key": this.config.api_key,
1843
- "anthropic-version": "2023-06-01"
1844
- },
1845
- body: JSON.stringify(body)
1846
- });
1847
- if (!res.ok) {
1848
- const err = await res.text();
1849
- throw new Error(`Anthropic ${res.status}: ${err}`);
1850
- }
1851
- let textContent = "";
1852
- let stopReason = "end_turn";
1853
- let inputTokens = 0;
1854
- let outputTokens = 0;
1855
- let modelName = this.config.model;
1856
- const toolCalls = [];
1857
- const toolInputBuffers = {};
1858
- let currentToolId = "";
1859
- const reader = res.body.getReader();
1860
- const decoder = new TextDecoder();
1861
- let buf = "";
1862
- while (true) {
1863
- const { done, value } = await reader.read();
1864
- if (done) break;
1865
- buf += decoder.decode(value, { stream: true });
1866
- const lines = buf.split("\n");
1867
- buf = lines.pop() ?? "";
1868
- for (const line of lines) {
1869
- if (!line.startsWith("data: ")) continue;
1870
- const data = line.slice(6).trim();
1871
- if (data === "[DONE]" || data === "") continue;
1872
- let evt;
1873
- try {
1874
- evt = JSON.parse(data);
1875
- } catch {
1876
- continue;
1877
- }
1878
- const type = evt.type;
1879
- if (type === "message_start") {
1880
- const usage = evt.message?.usage;
1881
- inputTokens = usage?.input_tokens ?? 0;
1882
- modelName = evt.message?.model ?? modelName;
1883
- } else if (type === "content_block_start") {
1884
- const block = evt.content_block;
1885
- if (block?.type === "tool_use") {
1886
- currentToolId = block.id;
1887
- toolInputBuffers[currentToolId] = "";
1888
- toolCalls.push({ id: currentToolId, name: block.name, input: {} });
1889
- }
1890
- } else if (type === "content_block_delta") {
1891
- const delta = evt.delta;
1892
- if (delta?.type === "text_delta") {
1893
- const token = delta.text ?? "";
1894
- textContent += token;
1895
- if (onToken && token) onToken(token);
1896
- } else if (delta?.type === "input_json_delta") {
1897
- toolInputBuffers[currentToolId] = (toolInputBuffers[currentToolId] ?? "") + (delta.partial_json ?? "");
1898
- }
1899
- } else if (type === "content_block_stop") {
1900
- if (currentToolId && toolInputBuffers[currentToolId]) {
1901
- const tc = toolCalls.find((t) => t.id === currentToolId);
1902
- if (tc) {
1903
- try {
1904
- tc.input = JSON.parse(toolInputBuffers[currentToolId]);
1905
- } catch {
1906
- }
1907
- }
1908
- }
1909
- } else if (type === "message_delta") {
1910
- const usage = evt.usage;
1911
- outputTokens = usage?.output_tokens ?? 0;
1912
- const stop = evt.delta?.stop_reason;
1913
- if (stop === "tool_use") stopReason = "tool_use";
1914
- else if (stop === "end_turn") stopReason = "end_turn";
1915
- else if (stop === "max_tokens") stopReason = "max_tokens";
1916
- }
1917
- }
1918
- }
1919
- return {
1920
- content: textContent,
1921
- tool_calls: toolCalls.length > 0 ? toolCalls : null,
1922
- stop_reason: stopReason,
1923
- tokens_used: inputTokens + outputTokens,
1924
- model: modelName
1925
- };
1926
- }
1927
- // ─── OpenAI (also xAI, Gemini) ───────────────────────────────────────────
1928
- async openai(messages, tools, system, onToken, baseUrl = "https://api.openai.com/v1") {
1929
- const allMessages = [];
1930
- const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
1931
- if (sysContent) allMessages.push({ role: "system", content: sysContent });
1932
- for (const m of messages.filter((m2) => m2.role !== "system")) {
1933
- if (m.role === "tool") {
1934
- allMessages.push({ role: "tool", tool_call_id: m.tool_call_id, content: m.content });
1935
- } else if (m.role === "assistant" && m.tool_calls?.length) {
1936
- allMessages.push({
1937
- role: "assistant",
1938
- content: m.content || null,
1939
- tool_calls: m.tool_calls.map((tc) => ({
1940
- id: tc.id,
1941
- type: "function",
1942
- function: { name: tc.name, arguments: JSON.stringify(tc.input) }
1943
- }))
1944
- });
1945
- } else {
1946
- allMessages.push({ role: m.role, content: m.content });
1947
- }
1948
- }
1949
- const body = {
1950
- model: this.config.model,
1951
- messages: allMessages,
1952
- max_tokens: 8192,
1953
- stream: true,
1954
- stream_options: { include_usage: true }
1955
- };
1956
- if (tools.length > 0) {
1957
- body.tools = tools.map((t) => ({
1958
- type: "function",
1959
- function: { name: t.name, description: t.description, parameters: t.input_schema }
1960
- }));
1961
- }
1962
- const res = await fetch(`${this.config.base_url ?? baseUrl}/chat/completions`, {
1963
- method: "POST",
1964
- headers: {
1965
- "Content-Type": "application/json",
1966
- "Authorization": `Bearer ${this.config.api_key}`
1967
- },
1968
- body: JSON.stringify(body)
1969
- });
1970
- if (!res.ok) {
1971
- const err = await res.text();
1972
- throw new Error(`OpenAI ${res.status}: ${err}`);
1973
- }
1974
- let textContent = "";
1975
- let tokensUsed = 0;
1976
- let modelName = this.config.model;
1977
- let stopReason = "end_turn";
1978
- const toolCallMap = {};
1979
- const reader = res.body.getReader();
1980
- const decoder = new TextDecoder();
1981
- let buf = "";
1982
- while (true) {
1983
- const { done, value } = await reader.read();
1984
- if (done) break;
1985
- buf += decoder.decode(value, { stream: true });
1986
- const lines = buf.split("\n");
1987
- buf = lines.pop() ?? "";
1988
- for (const line of lines) {
1989
- if (!line.startsWith("data: ")) continue;
1990
- const data = line.slice(6).trim();
1991
- if (data === "[DONE]") continue;
1992
- let evt;
1993
- try {
1994
- evt = JSON.parse(data);
1995
- } catch {
1996
- continue;
1997
- }
1998
- modelName = evt.model ?? modelName;
1999
- const usage = evt.usage;
2000
- if (usage?.total_tokens) tokensUsed = usage.total_tokens;
2001
- const choices = evt.choices;
2002
- if (!choices?.length) continue;
2003
- const delta = choices[0].delta;
2004
- if (!delta) continue;
2005
- const finish = choices[0].finish_reason;
2006
- if (finish === "tool_calls") stopReason = "tool_use";
2007
- else if (finish === "stop") stopReason = "end_turn";
2008
- const token = delta.content;
2009
- if (token) {
2010
- textContent += token;
2011
- if (onToken) onToken(token);
2012
- }
2013
- const toolCallDeltas = delta.tool_calls;
2014
- if (toolCallDeltas) {
2015
- for (const tc of toolCallDeltas) {
2016
- const idx = tc.index;
2017
- if (!toolCallMap[idx]) {
2018
- toolCallMap[idx] = { id: "", name: "", args: "" };
2019
- }
2020
- const fn = tc.function;
2021
- if (tc.id) toolCallMap[idx].id = tc.id;
2022
- if (fn?.name) toolCallMap[idx].name = fn.name;
2023
- if (fn?.arguments) toolCallMap[idx].args += fn.arguments;
2024
- }
2025
- }
2026
- }
2027
- }
2028
- const toolCalls = Object.values(toolCallMap).filter((tc) => tc.id && tc.name).map((tc) => {
2029
- let input = {};
2030
- try {
2031
- input = JSON.parse(tc.args);
2032
- } catch {
2033
- }
2034
- return { id: tc.id, name: tc.name, input };
2035
- });
2036
- return {
2037
- content: textContent,
2038
- tool_calls: toolCalls.length > 0 ? toolCalls : null,
2039
- stop_reason: stopReason,
2040
- tokens_used: tokensUsed,
2041
- model: modelName
2042
- };
2043
- }
2044
- // ─── Ollama (no streaming for simplicity) ────────────────────────────────
2045
- async ollama(messages, system, onToken) {
2046
- const baseUrl = this.config.base_url ?? "http://localhost:11434";
2047
- const allMessages = [];
2048
- const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
2049
- if (sysContent) allMessages.push({ role: "system", content: sysContent });
2050
- allMessages.push(...messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content })));
2051
- const res = await fetch(`${baseUrl}/api/chat`, {
2052
- method: "POST",
2053
- headers: { "Content-Type": "application/json" },
2054
- body: JSON.stringify({ model: this.config.model, messages: allMessages, stream: false })
2055
- });
2056
- if (!res.ok) throw new Error(`Ollama error ${res.status}`);
2057
- const data = await res.json();
2058
- if (onToken) onToken(data.message.content);
2059
- return { content: data.message.content, tool_calls: null, stop_reason: "end_turn", tokens_used: data.eval_count ?? 0, model: this.config.model };
2060
- }
2061
- };
2062
-
2063
- // packages/daemon/src/AgentExecutor.ts
2064
- var AgentExecutor = class {
2065
- constructor(llm, config, onStep, onToken) {
2066
- this.llm = llm;
2067
- this.config = config;
2068
- this.onStep = onStep;
2069
- this.onToken = onToken;
2070
- this.cwd = config.cwd;
2071
- this.maxIterations = config.max_iterations ?? 20;
2072
- this.maxCommandMs = config.max_command_ms ?? 3e4;
2073
- }
2074
- cwd;
2075
- maxIterations;
2076
- maxCommandMs;
2077
- async execute(task, systemContext) {
2078
- const filesWritten = [];
2079
- const commandsRun = [];
2080
- let totalTokens = 0;
2081
- let modelName = "";
2082
- const systemPrompt = this.buildSystemPrompt(systemContext);
2083
- const messages = [
2084
- { role: "user", content: task }
2085
- ];
2086
- let finalOutput = "";
2087
- for (let i = 0; i < this.maxIterations; i++) {
2088
- this.onStep(i === 0 ? "Thinking\u2026" : "Continuing\u2026");
2089
- let response;
2090
- try {
2091
- response = await this.llm.completeWithTools(
2092
- messages,
2093
- AGENT_TOOLS,
2094
- systemPrompt,
2095
- // Only stream tokens on the final (non-tool) turn
2096
- (token) => {
2097
- this.onToken(token);
2098
- finalOutput += token;
2099
- }
2100
- );
2101
- } catch (err) {
2102
- const msg = err instanceof Error ? err.message : String(err);
2103
- this.onStep(`LLM error: ${msg}`);
2104
- finalOutput = `Error: ${msg}`;
2105
- break;
2106
- }
2107
- totalTokens += response.tokens_used;
2108
- modelName = response.model;
2109
- if (response.stop_reason === "end_turn" || !response.tool_calls?.length) {
2110
- if (!finalOutput && response.content) finalOutput = response.content;
2111
- break;
2213
+ totalTokens += response.tokens_used;
2214
+ modelName = response.model;
2215
+ if (response.stop_reason === "end_turn" || !response.tool_calls?.length) {
2216
+ if (!finalOutput && response.content) finalOutput = response.content;
2217
+ break;
2112
2218
  }
2113
2219
  finalOutput = "";
2114
2220
  messages.push({
@@ -2120,8 +2226,12 @@ var AgentExecutor = class {
2120
2226
  this.onStep(`\u25B6 ${tc.name}(${this.summariseInput(tc.name, tc.input)})`);
2121
2227
  let result;
2122
2228
  try {
2123
- result = await this.executeTool(tc.name, tc.input);
2124
- if (tc.name === "write_file" && tc.input.path) {
2229
+ const capResult = await this.registry.execute(tc.name, tc.input, this.cwd);
2230
+ result = capResult.output;
2231
+ if (capResult.fallback_used) {
2232
+ this.onStep(` (used fallback: ${capResult.fallback_used})`);
2233
+ }
2234
+ if (tc.name === "file_op" && tc.input.op === "write" && tc.input.path) {
2125
2235
  filesWritten.push(String(tc.input.path));
2126
2236
  }
2127
2237
  if (tc.name === "shell_exec" && tc.input.command) {
@@ -2178,9 +2288,9 @@ var AgentExecutor = class {
2178
2288
  }
2179
2289
  }
2180
2290
  shellExec(command, timeoutMs) {
2181
- return new Promise((resolve6) => {
2291
+ return new Promise((resolve7) => {
2182
2292
  const chunks = [];
2183
- const proc = spawn("bash", ["-c", command], {
2293
+ const proc = spawn2("bash", ["-c", command], {
2184
2294
  cwd: this.cwd,
2185
2295
  env: { ...process.env, TERM: "dumb" },
2186
2296
  timeout: timeoutMs
@@ -2189,26 +2299,26 @@ var AgentExecutor = class {
2189
2299
  proc.stderr.on("data", (d) => chunks.push(d.toString()));
2190
2300
  proc.on("close", (code) => {
2191
2301
  const output = chunks.join("").trim();
2192
- resolve6(output || (code === 0 ? "(command completed, no output)" : `exit code ${code}`));
2302
+ resolve7(output || (code === 0 ? "(command completed, no output)" : `exit code ${code}`));
2193
2303
  });
2194
2304
  proc.on("error", (err) => {
2195
- resolve6(`Error: ${err.message}`);
2305
+ resolve7(`Error: ${err.message}`);
2196
2306
  });
2197
2307
  });
2198
2308
  }
2199
2309
  writeFile(filePath, content) {
2200
2310
  const safe = this.safePath(filePath);
2201
2311
  if (!safe) return "Error: path outside working directory";
2202
- mkdirSync(dirname(safe), { recursive: true });
2203
- writeFileSync(safe, content, "utf8");
2312
+ mkdirSync2(dirname2(safe), { recursive: true });
2313
+ writeFileSync2(safe, content, "utf8");
2204
2314
  const rel = relative(this.cwd, safe);
2205
2315
  return `Written: ${rel} (${content.length} bytes)`;
2206
2316
  }
2207
2317
  readFile(filePath) {
2208
2318
  const safe = this.safePath(filePath);
2209
2319
  if (!safe) return "Error: path outside working directory";
2210
- if (!existsSync2(safe)) return `File not found: ${filePath}`;
2211
- const content = readFileSync2(safe, "utf8");
2320
+ if (!existsSync3(safe)) return `File not found: ${filePath}`;
2321
+ const content = readFileSync3(safe, "utf8");
2212
2322
  return content.length > 8e3 ? content.slice(0, 8e3) + `
2213
2323
  \u2026[truncated, ${content.length} total bytes]` : content;
2214
2324
  }
@@ -2299,9 +2409,9 @@ content = element.text if element else page.get_all_text()` : `content = page.ge
2299
2409
  listDir(dirPath) {
2300
2410
  const safe = this.safePath(dirPath ?? ".");
2301
2411
  if (!safe) return "Error: path outside working directory";
2302
- if (!existsSync2(safe)) return `Directory not found: ${dirPath}`;
2412
+ if (!existsSync3(safe)) return `Directory not found: ${dirPath}`;
2303
2413
  try {
2304
- const entries = readdirSync(safe, { withFileTypes: true }).filter((e) => !e.name.startsWith(".") && e.name !== "node_modules").map((e) => `${e.isDirectory() ? "d" : "f"} ${e.name}`).join("\n");
2414
+ const entries = readdirSync2(safe, { withFileTypes: true }).filter((e) => !e.name.startsWith(".") && e.name !== "node_modules").map((e) => `${e.isDirectory() ? "d" : "f"} ${e.name}`).join("\n");
2305
2415
  return entries || "(empty directory)";
2306
2416
  } catch (e) {
2307
2417
  return `Error: ${e instanceof Error ? e.message : String(e)}`;
@@ -2309,7 +2419,7 @@ content = element.text if element else page.get_all_text()` : `content = page.ge
2309
2419
  }
2310
2420
  // ─── Helpers ───────────────────────────────────────────────────────────────
2311
2421
  safePath(p) {
2312
- const resolved = resolve2(this.cwd, p);
2422
+ const resolved = resolve3(this.cwd, p);
2313
2423
  return resolved.startsWith(this.cwd) ? resolved : null;
2314
2424
  }
2315
2425
  buildSystemPrompt(extra) {
@@ -2341,6 +2451,125 @@ content = element.text if element else page.get_all_text()` : `content = page.ge
2341
2451
  }
2342
2452
  };
2343
2453
 
2454
+ // packages/daemon/src/AnthropicSkillFetcher.ts
2455
+ var BASE_URL = "https://raw.githubusercontent.com/anthropics/skills/main";
2456
+ var CACHE_TTL_MS = 60 * 60 * 1e3;
2457
+ var ANTHROPIC_SKILLS = [
2458
+ "algorithmic-art",
2459
+ "brand-guidelines",
2460
+ "canvas-design",
2461
+ "claude-api",
2462
+ "doc-coauthoring",
2463
+ "docx",
2464
+ "frontend-design",
2465
+ "internal-comms",
2466
+ "mcp-builder",
2467
+ "pdf",
2468
+ "pptx",
2469
+ "skill-creator",
2470
+ "slack-gif-creator",
2471
+ "theme-factory",
2472
+ "web-artifacts-builder",
2473
+ "webapp-testing",
2474
+ "xlsx"
2475
+ ];
2476
+ var AnthropicSkillFetcher = class {
2477
+ cache = /* @__PURE__ */ new Map();
2478
+ /**
2479
+ * Fetch a skill's instructions from the Anthropic repo.
2480
+ * Returns the Markdown body as a role_prompt string.
2481
+ * Caches for 1 hour.
2482
+ */
2483
+ async fetch(skillName) {
2484
+ const cached = this.cache.get(skillName);
2485
+ if (cached && Date.now() - cached.fetched_at < CACHE_TTL_MS) {
2486
+ return this.parseSkillMd(skillName, cached.prompt, true);
2487
+ }
2488
+ const urls = [
2489
+ `${BASE_URL}/skills/${skillName}/SKILL.md`,
2490
+ `${BASE_URL}/${skillName}/SKILL.md`,
2491
+ `${BASE_URL}/skills/${skillName}/README.md`
2492
+ ];
2493
+ for (const url of urls) {
2494
+ try {
2495
+ const res = await fetch(url, {
2496
+ headers: { "User-Agent": "0agent/1.0" },
2497
+ signal: AbortSignal.timeout(8e3)
2498
+ });
2499
+ if (res.ok) {
2500
+ const text = await res.text();
2501
+ this.cache.set(skillName, { prompt: text, fetched_at: Date.now() });
2502
+ return this.parseSkillMd(skillName, text, false);
2503
+ }
2504
+ } catch {
2505
+ }
2506
+ }
2507
+ return null;
2508
+ }
2509
+ /**
2510
+ * Parse a SKILL.md file into a FetchedSkill.
2511
+ * Extracts name/description from YAML frontmatter, instructions from body.
2512
+ */
2513
+ parseSkillMd(skillName, raw, cached) {
2514
+ let name = skillName;
2515
+ let description = "";
2516
+ let instructions = raw;
2517
+ const frontmatterMatch = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
2518
+ if (frontmatterMatch) {
2519
+ const frontmatter = frontmatterMatch[1];
2520
+ instructions = frontmatterMatch[2].trim();
2521
+ const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
2522
+ if (nameMatch) name = nameMatch[1].trim().replace(/["']/g, "");
2523
+ const descMatch = frontmatter.match(/^description:\s*([\s\S]*?)(?=^\w|\Z)/m);
2524
+ if (descMatch) description = descMatch[1].replace(/\s+/g, " ").trim().replace(/["']/g, "");
2525
+ }
2526
+ return {
2527
+ name,
2528
+ description,
2529
+ instructions,
2530
+ source_url: `https://github.com/anthropics/skills/tree/main/skills/${skillName}`,
2531
+ cached
2532
+ };
2533
+ }
2534
+ /**
2535
+ * Build a system prompt that injects the Anthropic skill instructions.
2536
+ * This is what gets passed to the agent as context.
2537
+ */
2538
+ buildSystemPrompt(skill) {
2539
+ return [
2540
+ `You are using the "${skill.name}" skill from Anthropic's skill library.`,
2541
+ ``,
2542
+ `Skill description: ${skill.description}`,
2543
+ ``,
2544
+ `Instructions:`,
2545
+ `---`,
2546
+ skill.instructions,
2547
+ `---`,
2548
+ ``,
2549
+ `Use the tools available to you (shell_exec, file_op, web_search, scrape_url, browser_open)`,
2550
+ `to complete the task following these instructions.`
2551
+ ].join("\n");
2552
+ }
2553
+ /**
2554
+ * List all known Anthropic skills.
2555
+ */
2556
+ listAvailable() {
2557
+ return [...ANTHROPIC_SKILLS];
2558
+ }
2559
+ /**
2560
+ * Check if a skill name is a known Anthropic skill.
2561
+ */
2562
+ isAnthropicSkill(name) {
2563
+ return ANTHROPIC_SKILLS.includes(name);
2564
+ }
2565
+ /**
2566
+ * Clear the cache (force re-fetch on next request).
2567
+ */
2568
+ clearCache() {
2569
+ this.cache.clear();
2570
+ }
2571
+ };
2572
+
2344
2573
  // packages/daemon/src/SessionManager.ts
2345
2574
  var SessionManager = class {
2346
2575
  sessions = /* @__PURE__ */ new Map();
@@ -2349,6 +2578,7 @@ var SessionManager = class {
2349
2578
  graph;
2350
2579
  llm;
2351
2580
  cwd;
2581
+ anthropicFetcher = new AnthropicSkillFetcher();
2352
2582
  constructor(deps = {}) {
2353
2583
  this.inferenceEngine = deps.inferenceEngine;
2354
2584
  this.eventBus = deps.eventBus;
@@ -2522,6 +2752,15 @@ var SessionManager = class {
2522
2752
  } else {
2523
2753
  this.addStep(session.id, "No inference engine connected \u2014 executing task directly");
2524
2754
  }
2755
+ let anthropicContext;
2756
+ if (enrichedReq.skill && this.anthropicFetcher.isAnthropicSkill(enrichedReq.skill)) {
2757
+ this.addStep(session.id, `Fetching skill instructions: ${enrichedReq.skill}`);
2758
+ const fetched = await this.anthropicFetcher.fetch(enrichedReq.skill);
2759
+ if (fetched) {
2760
+ anthropicContext = this.anthropicFetcher.buildSystemPrompt(fetched);
2761
+ this.addStep(session.id, `Loaded skill: ${fetched.name} (${fetched.cached ? "cached" : "fresh"})`);
2762
+ }
2763
+ }
2525
2764
  if (this.llm?.isConfigured) {
2526
2765
  const executor = new AgentExecutor(
2527
2766
  this.llm,
@@ -2531,7 +2770,10 @@ var SessionManager = class {
2531
2770
  // token callback → emit session.token events
2532
2771
  (token) => this.emit({ type: "session.token", session_id: session.id, token })
2533
2772
  );
2534
- const systemContext = enrichedReq.context?.system_context ? String(enrichedReq.context.system_context) : void 0;
2773
+ const systemContext = [
2774
+ anthropicContext,
2775
+ enrichedReq.context?.system_context ? String(enrichedReq.context.system_context) : void 0
2776
+ ].filter(Boolean).join("\n\n") || void 0;
2535
2777
  const agentResult = await executor.execute(enrichedReq.task, systemContext);
2536
2778
  if (agentResult.files_written.length > 0) {
2537
2779
  this.addStep(session.id, `Files written: ${agentResult.files_written.join(", ")}`);
@@ -2794,7 +3036,7 @@ var BackgroundWorkers = class {
2794
3036
  };
2795
3037
 
2796
3038
  // packages/daemon/src/SkillRegistry.ts
2797
- import { readFileSync as readFileSync3, readdirSync as readdirSync2, existsSync as existsSync3, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "node:fs";
3039
+ import { readFileSync as readFileSync4, readdirSync as readdirSync3, existsSync as existsSync4, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync3 } from "node:fs";
2798
3040
  import { join } from "node:path";
2799
3041
  import { homedir as homedir2 } from "node:os";
2800
3042
  import YAML2 from "yaml";
@@ -2817,11 +3059,11 @@ var SkillRegistry = class {
2817
3059
  this.loadFromDir(this.customDir, false);
2818
3060
  }
2819
3061
  loadFromDir(dir, isBuiltin) {
2820
- if (!existsSync3(dir)) return;
2821
- const files = readdirSync2(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
3062
+ if (!existsSync4(dir)) return;
3063
+ const files = readdirSync3(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
2822
3064
  for (const file of files) {
2823
3065
  try {
2824
- const raw = readFileSync3(join(dir, file), "utf8");
3066
+ const raw = readFileSync4(join(dir, file), "utf8");
2825
3067
  const skill = YAML2.parse(raw);
2826
3068
  if (skill.name) {
2827
3069
  this.skills.set(skill.name, skill);
@@ -2856,9 +3098,9 @@ var SkillRegistry = class {
2856
3098
  if (this.builtinNames.has(name)) {
2857
3099
  throw new Error(`Cannot override built-in skill: ${name}`);
2858
3100
  }
2859
- mkdirSync2(this.customDir, { recursive: true });
3101
+ mkdirSync3(this.customDir, { recursive: true });
2860
3102
  const filePath = join(this.customDir, `${name}.yaml`);
2861
- writeFileSync2(filePath, yamlContent, "utf8");
3103
+ writeFileSync3(filePath, yamlContent, "utf8");
2862
3104
  const skill = YAML2.parse(yamlContent);
2863
3105
  this.skills.set(name, skill);
2864
3106
  return skill;
@@ -2871,7 +3113,7 @@ var SkillRegistry = class {
2871
3113
  throw new Error(`Cannot delete built-in skill: ${name}`);
2872
3114
  }
2873
3115
  const filePath = join(this.customDir, `${name}.yaml`);
2874
- if (existsSync3(filePath)) {
3116
+ if (existsSync4(filePath)) {
2875
3117
  unlinkSync(filePath);
2876
3118
  }
2877
3119
  this.skills.delete(name);
@@ -2884,8 +3126,8 @@ var SkillRegistry = class {
2884
3126
  // packages/daemon/src/HTTPServer.ts
2885
3127
  import { Hono as Hono8 } from "hono";
2886
3128
  import { serve } from "@hono/node-server";
2887
- import { readFileSync as readFileSync4 } from "node:fs";
2888
- import { resolve as resolve3, dirname as dirname2 } from "node:path";
3129
+ import { readFileSync as readFileSync5 } from "node:fs";
3130
+ import { resolve as resolve4, dirname as dirname3 } from "node:path";
2889
3131
  import { fileURLToPath } from "node:url";
2890
3132
 
2891
3133
  // packages/daemon/src/routes/health.ts
@@ -3116,99 +3358,396 @@ function skillRoutes(deps) {
3116
3358
  const message = err instanceof Error ? err.message : String(err);
3117
3359
  return c.json({ error: message }, 500);
3118
3360
  }
3119
- });
3120
- app.delete("/:name", (c) => {
3121
- const name = c.req.param("name");
3122
- if (deps.skillRegistry.isBuiltin(name)) {
3123
- return c.json({ error: "Cannot delete built-in skill" }, 403);
3361
+ });
3362
+ app.delete("/:name", (c) => {
3363
+ const name = c.req.param("name");
3364
+ if (deps.skillRegistry.isBuiltin(name)) {
3365
+ return c.json({ error: "Cannot delete built-in skill" }, 403);
3366
+ }
3367
+ const skill = deps.skillRegistry.get(name);
3368
+ if (!skill) {
3369
+ return c.json({ error: "Skill not found" }, 404);
3370
+ }
3371
+ try {
3372
+ deps.skillRegistry.removeCustom(name);
3373
+ return c.json({ ok: true });
3374
+ } catch (err) {
3375
+ const message = err instanceof Error ? err.message : String(err);
3376
+ return c.json({ error: message }, 500);
3377
+ }
3378
+ });
3379
+ return app;
3380
+ }
3381
+
3382
+ // packages/daemon/src/HTTPServer.ts
3383
+ function findGraphHtml() {
3384
+ const candidates = [
3385
+ resolve4(dirname3(fileURLToPath(import.meta.url)), "graph.html"),
3386
+ // dev (src/)
3387
+ resolve4(dirname3(fileURLToPath(import.meta.url)), "..", "graph.html"),
3388
+ // bundled (dist/../)
3389
+ resolve4(dirname3(fileURLToPath(import.meta.url)), "..", "dist", "graph.html")
3390
+ ];
3391
+ for (const p of candidates) {
3392
+ try {
3393
+ readFileSync5(p);
3394
+ return p;
3395
+ } catch {
3396
+ }
3397
+ }
3398
+ return candidates[0];
3399
+ }
3400
+ var GRAPH_HTML_PATH = findGraphHtml();
3401
+ var HTTPServer = class {
3402
+ app;
3403
+ server = null;
3404
+ deps;
3405
+ constructor(deps) {
3406
+ this.deps = deps;
3407
+ this.app = new Hono8();
3408
+ this.app.route("/api/health", healthRoutes({ getStatus: deps.getStatus }));
3409
+ this.app.route("/api/sessions", sessionRoutes({ sessions: deps.sessions }));
3410
+ this.app.route("/api/graph", graphRoutes({ graph: deps.graph }));
3411
+ this.app.route("/api/entities", entityRoutes({ graph: deps.graph }));
3412
+ this.app.route("/api/traces", traceRoutes({ traceStore: deps.traceStore }));
3413
+ this.app.route("/api/subagents", subagentRoutes());
3414
+ this.app.route("/api/skills", skillRoutes({ skillRegistry: deps.skillRegistry }));
3415
+ const serveGraph = (c) => {
3416
+ try {
3417
+ const html = readFileSync5(GRAPH_HTML_PATH, "utf8");
3418
+ return c.html(html);
3419
+ } catch {
3420
+ return c.html("<p>Graph UI not found. Run: pnpm build</p>");
3421
+ }
3422
+ };
3423
+ this.app.get("/", serveGraph);
3424
+ this.app.get("/graph", serveGraph);
3425
+ }
3426
+ start() {
3427
+ return new Promise((resolve7) => {
3428
+ this.server = serve(
3429
+ {
3430
+ fetch: this.app.fetch,
3431
+ port: this.deps.port,
3432
+ hostname: this.deps.host
3433
+ },
3434
+ () => {
3435
+ resolve7();
3436
+ }
3437
+ );
3438
+ });
3439
+ }
3440
+ stop() {
3441
+ return new Promise((resolve7, reject) => {
3442
+ if (!this.server) {
3443
+ resolve7();
3444
+ return;
3445
+ }
3446
+ this.server.close((err) => {
3447
+ if (err) reject(err);
3448
+ else resolve7();
3449
+ });
3450
+ });
3451
+ }
3452
+ getApp() {
3453
+ return this.app;
3454
+ }
3455
+ };
3456
+
3457
+ // packages/daemon/src/LLMExecutor.ts
3458
+ var LLMExecutor = class {
3459
+ constructor(config) {
3460
+ this.config = config;
3461
+ }
3462
+ get isConfigured() {
3463
+ if (this.config.provider === "ollama") return true;
3464
+ return !!this.config.api_key?.trim();
3465
+ }
3466
+ // ─── Single completion (no tools, no streaming) ──────────────────────────
3467
+ async complete(messages, system) {
3468
+ const res = await this.completeWithTools(messages, [], system, void 0);
3469
+ return { content: res.content, tokens_used: res.tokens_used, model: res.model };
3470
+ }
3471
+ // ─── Tool-calling completion with optional streaming ─────────────────────
3472
+ async completeWithTools(messages, tools, system, onToken) {
3473
+ switch (this.config.provider) {
3474
+ case "anthropic":
3475
+ return this.anthropic(messages, tools, system, onToken);
3476
+ case "openai":
3477
+ return this.openai(messages, tools, system, onToken);
3478
+ case "xai":
3479
+ return this.openai(messages, tools, system, onToken, "https://api.x.ai/v1");
3480
+ case "gemini":
3481
+ return this.openai(messages, tools, system, onToken, "https://generativelanguage.googleapis.com/v1beta/openai");
3482
+ case "ollama":
3483
+ return this.ollama(messages, system, onToken);
3484
+ default:
3485
+ return this.openai(messages, tools, system, onToken);
3486
+ }
3487
+ }
3488
+ // ─── Anthropic ───────────────────────────────────────────────────────────
3489
+ async anthropic(messages, tools, system, onToken) {
3490
+ const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
3491
+ const filtered = messages.filter((m) => m.role !== "system");
3492
+ const anthropicMsgs = filtered.map((m) => {
3493
+ if (m.role === "tool") {
3494
+ return {
3495
+ role: "user",
3496
+ content: [{ type: "tool_result", tool_use_id: m.tool_call_id, content: m.content }]
3497
+ };
3498
+ }
3499
+ if (m.role === "assistant" && m.tool_calls?.length) {
3500
+ return {
3501
+ role: "assistant",
3502
+ content: [
3503
+ ...m.content ? [{ type: "text", text: m.content }] : [],
3504
+ ...m.tool_calls.map((tc) => ({
3505
+ type: "tool_use",
3506
+ id: tc.id,
3507
+ name: tc.name,
3508
+ input: tc.input
3509
+ }))
3510
+ ]
3511
+ };
3512
+ }
3513
+ return { role: m.role, content: m.content };
3514
+ });
3515
+ const body = {
3516
+ model: this.config.model,
3517
+ max_tokens: 8192,
3518
+ messages: anthropicMsgs,
3519
+ stream: true
3520
+ };
3521
+ if (sysContent) body.system = sysContent;
3522
+ if (tools.length > 0) {
3523
+ body.tools = tools.map((t) => ({
3524
+ name: t.name,
3525
+ description: t.description,
3526
+ input_schema: t.input_schema
3527
+ }));
3528
+ }
3529
+ const res = await fetch("https://api.anthropic.com/v1/messages", {
3530
+ method: "POST",
3531
+ headers: {
3532
+ "Content-Type": "application/json",
3533
+ "x-api-key": this.config.api_key,
3534
+ "anthropic-version": "2023-06-01"
3535
+ },
3536
+ body: JSON.stringify(body)
3537
+ });
3538
+ if (!res.ok) {
3539
+ const err = await res.text();
3540
+ throw new Error(`Anthropic ${res.status}: ${err}`);
3541
+ }
3542
+ let textContent = "";
3543
+ let stopReason = "end_turn";
3544
+ let inputTokens = 0;
3545
+ let outputTokens = 0;
3546
+ let modelName = this.config.model;
3547
+ const toolCalls = [];
3548
+ const toolInputBuffers = {};
3549
+ let currentToolId = "";
3550
+ const reader = res.body.getReader();
3551
+ const decoder = new TextDecoder();
3552
+ let buf = "";
3553
+ while (true) {
3554
+ const { done, value } = await reader.read();
3555
+ if (done) break;
3556
+ buf += decoder.decode(value, { stream: true });
3557
+ const lines = buf.split("\n");
3558
+ buf = lines.pop() ?? "";
3559
+ for (const line of lines) {
3560
+ if (!line.startsWith("data: ")) continue;
3561
+ const data = line.slice(6).trim();
3562
+ if (data === "[DONE]" || data === "") continue;
3563
+ let evt;
3564
+ try {
3565
+ evt = JSON.parse(data);
3566
+ } catch {
3567
+ continue;
3568
+ }
3569
+ const type = evt.type;
3570
+ if (type === "message_start") {
3571
+ const usage = evt.message?.usage;
3572
+ inputTokens = usage?.input_tokens ?? 0;
3573
+ modelName = evt.message?.model ?? modelName;
3574
+ } else if (type === "content_block_start") {
3575
+ const block = evt.content_block;
3576
+ if (block?.type === "tool_use") {
3577
+ currentToolId = block.id;
3578
+ toolInputBuffers[currentToolId] = "";
3579
+ toolCalls.push({ id: currentToolId, name: block.name, input: {} });
3580
+ }
3581
+ } else if (type === "content_block_delta") {
3582
+ const delta = evt.delta;
3583
+ if (delta?.type === "text_delta") {
3584
+ const token = delta.text ?? "";
3585
+ textContent += token;
3586
+ if (onToken && token) onToken(token);
3587
+ } else if (delta?.type === "input_json_delta") {
3588
+ toolInputBuffers[currentToolId] = (toolInputBuffers[currentToolId] ?? "") + (delta.partial_json ?? "");
3589
+ }
3590
+ } else if (type === "content_block_stop") {
3591
+ if (currentToolId && toolInputBuffers[currentToolId]) {
3592
+ const tc = toolCalls.find((t) => t.id === currentToolId);
3593
+ if (tc) {
3594
+ try {
3595
+ tc.input = JSON.parse(toolInputBuffers[currentToolId]);
3596
+ } catch {
3597
+ }
3598
+ }
3599
+ }
3600
+ } else if (type === "message_delta") {
3601
+ const usage = evt.usage;
3602
+ outputTokens = usage?.output_tokens ?? 0;
3603
+ const stop = evt.delta?.stop_reason;
3604
+ if (stop === "tool_use") stopReason = "tool_use";
3605
+ else if (stop === "end_turn") stopReason = "end_turn";
3606
+ else if (stop === "max_tokens") stopReason = "max_tokens";
3607
+ }
3608
+ }
3609
+ }
3610
+ return {
3611
+ content: textContent,
3612
+ tool_calls: toolCalls.length > 0 ? toolCalls : null,
3613
+ stop_reason: stopReason,
3614
+ tokens_used: inputTokens + outputTokens,
3615
+ model: modelName
3616
+ };
3617
+ }
3618
+ // ─── OpenAI (also xAI, Gemini) ───────────────────────────────────────────
3619
+ async openai(messages, tools, system, onToken, baseUrl = "https://api.openai.com/v1") {
3620
+ const allMessages = [];
3621
+ const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
3622
+ if (sysContent) allMessages.push({ role: "system", content: sysContent });
3623
+ for (const m of messages.filter((m2) => m2.role !== "system")) {
3624
+ if (m.role === "tool") {
3625
+ allMessages.push({ role: "tool", tool_call_id: m.tool_call_id, content: m.content });
3626
+ } else if (m.role === "assistant" && m.tool_calls?.length) {
3627
+ allMessages.push({
3628
+ role: "assistant",
3629
+ content: m.content || null,
3630
+ tool_calls: m.tool_calls.map((tc) => ({
3631
+ id: tc.id,
3632
+ type: "function",
3633
+ function: { name: tc.name, arguments: JSON.stringify(tc.input) }
3634
+ }))
3635
+ });
3636
+ } else {
3637
+ allMessages.push({ role: m.role, content: m.content });
3638
+ }
3124
3639
  }
3125
- const skill = deps.skillRegistry.get(name);
3126
- if (!skill) {
3127
- return c.json({ error: "Skill not found" }, 404);
3640
+ const body = {
3641
+ model: this.config.model,
3642
+ messages: allMessages,
3643
+ max_tokens: 8192,
3644
+ stream: true,
3645
+ stream_options: { include_usage: true }
3646
+ };
3647
+ if (tools.length > 0) {
3648
+ body.tools = tools.map((t) => ({
3649
+ type: "function",
3650
+ function: { name: t.name, description: t.description, parameters: t.input_schema }
3651
+ }));
3128
3652
  }
3129
- try {
3130
- deps.skillRegistry.removeCustom(name);
3131
- return c.json({ ok: true });
3132
- } catch (err) {
3133
- const message = err instanceof Error ? err.message : String(err);
3134
- return c.json({ error: message }, 500);
3653
+ const res = await fetch(`${this.config.base_url ?? baseUrl}/chat/completions`, {
3654
+ method: "POST",
3655
+ headers: {
3656
+ "Content-Type": "application/json",
3657
+ "Authorization": `Bearer ${this.config.api_key}`
3658
+ },
3659
+ body: JSON.stringify(body)
3660
+ });
3661
+ if (!res.ok) {
3662
+ const err = await res.text();
3663
+ throw new Error(`OpenAI ${res.status}: ${err}`);
3135
3664
  }
3136
- });
3137
- return app;
3138
- }
3139
-
3140
- // packages/daemon/src/HTTPServer.ts
3141
- function findGraphHtml() {
3142
- const candidates = [
3143
- resolve3(dirname2(fileURLToPath(import.meta.url)), "graph.html"),
3144
- // dev (src/)
3145
- resolve3(dirname2(fileURLToPath(import.meta.url)), "..", "graph.html"),
3146
- // bundled (dist/../)
3147
- resolve3(dirname2(fileURLToPath(import.meta.url)), "..", "dist", "graph.html")
3148
- ];
3149
- for (const p of candidates) {
3150
- try {
3151
- readFileSync4(p);
3152
- return p;
3153
- } catch {
3665
+ let textContent = "";
3666
+ let tokensUsed = 0;
3667
+ let modelName = this.config.model;
3668
+ let stopReason = "end_turn";
3669
+ const toolCallMap = {};
3670
+ const reader = res.body.getReader();
3671
+ const decoder = new TextDecoder();
3672
+ let buf = "";
3673
+ while (true) {
3674
+ const { done, value } = await reader.read();
3675
+ if (done) break;
3676
+ buf += decoder.decode(value, { stream: true });
3677
+ const lines = buf.split("\n");
3678
+ buf = lines.pop() ?? "";
3679
+ for (const line of lines) {
3680
+ if (!line.startsWith("data: ")) continue;
3681
+ const data = line.slice(6).trim();
3682
+ if (data === "[DONE]") continue;
3683
+ let evt;
3684
+ try {
3685
+ evt = JSON.parse(data);
3686
+ } catch {
3687
+ continue;
3688
+ }
3689
+ modelName = evt.model ?? modelName;
3690
+ const usage = evt.usage;
3691
+ if (usage?.total_tokens) tokensUsed = usage.total_tokens;
3692
+ const choices = evt.choices;
3693
+ if (!choices?.length) continue;
3694
+ const delta = choices[0].delta;
3695
+ if (!delta) continue;
3696
+ const finish = choices[0].finish_reason;
3697
+ if (finish === "tool_calls") stopReason = "tool_use";
3698
+ else if (finish === "stop") stopReason = "end_turn";
3699
+ const token = delta.content;
3700
+ if (token) {
3701
+ textContent += token;
3702
+ if (onToken) onToken(token);
3703
+ }
3704
+ const toolCallDeltas = delta.tool_calls;
3705
+ if (toolCallDeltas) {
3706
+ for (const tc of toolCallDeltas) {
3707
+ const idx = tc.index;
3708
+ if (!toolCallMap[idx]) {
3709
+ toolCallMap[idx] = { id: "", name: "", args: "" };
3710
+ }
3711
+ const fn = tc.function;
3712
+ if (tc.id) toolCallMap[idx].id = tc.id;
3713
+ if (fn?.name) toolCallMap[idx].name = fn.name;
3714
+ if (fn?.arguments) toolCallMap[idx].args += fn.arguments;
3715
+ }
3716
+ }
3717
+ }
3154
3718
  }
3155
- }
3156
- return candidates[0];
3157
- }
3158
- var GRAPH_HTML_PATH = findGraphHtml();
3159
- var HTTPServer = class {
3160
- app;
3161
- server = null;
3162
- deps;
3163
- constructor(deps) {
3164
- this.deps = deps;
3165
- this.app = new Hono8();
3166
- this.app.route("/api/health", healthRoutes({ getStatus: deps.getStatus }));
3167
- this.app.route("/api/sessions", sessionRoutes({ sessions: deps.sessions }));
3168
- this.app.route("/api/graph", graphRoutes({ graph: deps.graph }));
3169
- this.app.route("/api/entities", entityRoutes({ graph: deps.graph }));
3170
- this.app.route("/api/traces", traceRoutes({ traceStore: deps.traceStore }));
3171
- this.app.route("/api/subagents", subagentRoutes());
3172
- this.app.route("/api/skills", skillRoutes({ skillRegistry: deps.skillRegistry }));
3173
- const serveGraph = (c) => {
3719
+ const toolCalls = Object.values(toolCallMap).filter((tc) => tc.id && tc.name).map((tc) => {
3720
+ let input = {};
3174
3721
  try {
3175
- const html = readFileSync4(GRAPH_HTML_PATH, "utf8");
3176
- return c.html(html);
3722
+ input = JSON.parse(tc.args);
3177
3723
  } catch {
3178
- return c.html("<p>Graph UI not found. Run: pnpm build</p>");
3179
3724
  }
3180
- };
3181
- this.app.get("/", serveGraph);
3182
- this.app.get("/graph", serveGraph);
3183
- }
3184
- start() {
3185
- return new Promise((resolve6) => {
3186
- this.server = serve(
3187
- {
3188
- fetch: this.app.fetch,
3189
- port: this.deps.port,
3190
- hostname: this.deps.host
3191
- },
3192
- () => {
3193
- resolve6();
3194
- }
3195
- );
3725
+ return { id: tc.id, name: tc.name, input };
3196
3726
  });
3727
+ return {
3728
+ content: textContent,
3729
+ tool_calls: toolCalls.length > 0 ? toolCalls : null,
3730
+ stop_reason: stopReason,
3731
+ tokens_used: tokensUsed,
3732
+ model: modelName
3733
+ };
3197
3734
  }
3198
- stop() {
3199
- return new Promise((resolve6, reject) => {
3200
- if (!this.server) {
3201
- resolve6();
3202
- return;
3203
- }
3204
- this.server.close((err) => {
3205
- if (err) reject(err);
3206
- else resolve6();
3207
- });
3735
+ // ─── Ollama (no streaming for simplicity) ────────────────────────────────
3736
+ async ollama(messages, system, onToken) {
3737
+ const baseUrl = this.config.base_url ?? "http://localhost:11434";
3738
+ const allMessages = [];
3739
+ const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
3740
+ if (sysContent) allMessages.push({ role: "system", content: sysContent });
3741
+ allMessages.push(...messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content })));
3742
+ const res = await fetch(`${baseUrl}/api/chat`, {
3743
+ method: "POST",
3744
+ headers: { "Content-Type": "application/json" },
3745
+ body: JSON.stringify({ model: this.config.model, messages: allMessages, stream: false })
3208
3746
  });
3209
- }
3210
- getApp() {
3211
- return this.app;
3747
+ if (!res.ok) throw new Error(`Ollama error ${res.status}`);
3748
+ const data = await res.json();
3749
+ if (onToken) onToken(data.message.content);
3750
+ return { content: data.message.content, tool_calls: null, stop_reason: "end_turn", tokens_used: data.eval_count ?? 0, model: this.config.model };
3212
3751
  }
3213
3752
  };
3214
3753
 
@@ -3227,13 +3766,13 @@ var ZeroAgentDaemon = class {
3227
3766
  startedAt = 0;
3228
3767
  pidFilePath;
3229
3768
  constructor() {
3230
- this.pidFilePath = resolve4(homedir3(), ".0agent", "daemon.pid");
3769
+ this.pidFilePath = resolve5(homedir3(), ".0agent", "daemon.pid");
3231
3770
  }
3232
3771
  async start(opts) {
3233
3772
  this.config = await loadConfig(opts?.config_path);
3234
- const dotDir = resolve4(homedir3(), ".0agent");
3235
- if (!existsSync4(dotDir)) {
3236
- mkdirSync3(dotDir, { recursive: true });
3773
+ const dotDir = resolve5(homedir3(), ".0agent");
3774
+ if (!existsSync5(dotDir)) {
3775
+ mkdirSync4(dotDir, { recursive: true });
3237
3776
  }
3238
3777
  this.adapter = new SQLiteAdapter({ db_path: this.config.graph.db_path });
3239
3778
  this.graph = new KnowledgeGraph(this.adapter);
@@ -3284,7 +3823,7 @@ var ZeroAgentDaemon = class {
3284
3823
  getStatus: () => this.getStatus()
3285
3824
  });
3286
3825
  await this.httpServer.start();
3287
- writeFileSync3(this.pidFilePath, String(process.pid), "utf8");
3826
+ writeFileSync4(this.pidFilePath, String(process.pid), "utf8");
3288
3827
  console.log(
3289
3828
  `[0agent] Daemon started on ${this.config.server.host}:${this.config.server.port} (PID: ${process.pid})`
3290
3829
  );
@@ -3318,7 +3857,7 @@ var ZeroAgentDaemon = class {
3318
3857
  this.graph = null;
3319
3858
  }
3320
3859
  this.adapter = null;
3321
- if (existsSync4(this.pidFilePath)) {
3860
+ if (existsSync5(this.pidFilePath)) {
3322
3861
  try {
3323
3862
  unlinkSync2(this.pidFilePath);
3324
3863
  } catch {
@@ -3348,11 +3887,11 @@ var ZeroAgentDaemon = class {
3348
3887
  };
3349
3888
 
3350
3889
  // packages/daemon/src/start.ts
3351
- import { resolve as resolve5 } from "node:path";
3890
+ import { resolve as resolve6 } from "node:path";
3352
3891
  import { homedir as homedir4 } from "node:os";
3353
- import { existsSync as existsSync5 } from "node:fs";
3354
- var CONFIG_PATH = process.env["ZEROAGENT_CONFIG"] ?? resolve5(homedir4(), ".0agent", "config.yaml");
3355
- if (!existsSync5(CONFIG_PATH)) {
3892
+ import { existsSync as existsSync6 } from "node:fs";
3893
+ var CONFIG_PATH = process.env["ZEROAGENT_CONFIG"] ?? resolve6(homedir4(), ".0agent", "config.yaml");
3894
+ if (!existsSync6(CONFIG_PATH)) {
3356
3895
  console.error(`
3357
3896
  0agent is not initialised.
3358
3897