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/bin/0agent.js +8 -3
- package/dist/daemon.mjs +1045 -506
- package/package.json +1 -1
package/dist/daemon.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// packages/daemon/src/ZeroAgentDaemon.ts
|
|
2
|
-
import { writeFileSync as
|
|
3
|
-
import { resolve as
|
|
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
|
|
1691
|
-
import { resolve as
|
|
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/
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
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
|
-
|
|
1702
|
-
|
|
1704
|
+
query: { type: "string", description: "Search query" },
|
|
1705
|
+
num_results: { type: "number", description: "Number of results (default 5, max 10)" }
|
|
1703
1706
|
},
|
|
1704
|
-
required: ["
|
|
1707
|
+
required: ["query"]
|
|
1705
1708
|
}
|
|
1706
|
-
}
|
|
1707
|
-
{
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
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
|
-
|
|
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(/&/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
|
-
|
|
1721
|
-
|
|
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
|
-
|
|
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: ["
|
|
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
|
-
|
|
1732
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1742
|
-
|
|
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
|
-
|
|
1747
|
-
|
|
2039
|
+
command: { type: "string", description: "Shell command to execute" },
|
|
2040
|
+
timeout_ms: { type: "number", description: "Timeout (default 30000ms)" }
|
|
1748
2041
|
},
|
|
1749
|
-
required: ["
|
|
2042
|
+
required: ["command"]
|
|
1750
2043
|
}
|
|
1751
|
-
}
|
|
1752
|
-
{
|
|
1753
|
-
|
|
1754
|
-
|
|
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
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
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: ["
|
|
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
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
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
|
-
|
|
1772
|
-
|
|
1773
|
-
return !!this.config.api_key?.trim();
|
|
2138
|
+
register(cap) {
|
|
2139
|
+
this.capabilities.set(cap.name, cap);
|
|
1774
2140
|
}
|
|
1775
|
-
|
|
1776
|
-
|
|
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
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
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
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
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
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
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
|
-
|
|
2124
|
-
|
|
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((
|
|
2291
|
+
return new Promise((resolve7) => {
|
|
2182
2292
|
const chunks = [];
|
|
2183
|
-
const proc =
|
|
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
|
-
|
|
2302
|
+
resolve7(output || (code === 0 ? "(command completed, no output)" : `exit code ${code}`));
|
|
2193
2303
|
});
|
|
2194
2304
|
proc.on("error", (err) => {
|
|
2195
|
-
|
|
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
|
-
|
|
2203
|
-
|
|
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 (!
|
|
2211
|
-
const content =
|
|
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 (!
|
|
2412
|
+
if (!existsSync3(safe)) return `Directory not found: ${dirPath}`;
|
|
2303
2413
|
try {
|
|
2304
|
-
const entries =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 (!
|
|
2821
|
-
const files =
|
|
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 =
|
|
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
|
-
|
|
3101
|
+
mkdirSync3(this.customDir, { recursive: true });
|
|
2860
3102
|
const filePath = join(this.customDir, `${name}.yaml`);
|
|
2861
|
-
|
|
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 (
|
|
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
|
|
2888
|
-
import { resolve as
|
|
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
|
|
3126
|
-
|
|
3127
|
-
|
|
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
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
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
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
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
|
-
|
|
3211
|
-
|
|
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 =
|
|
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 =
|
|
3235
|
-
if (!
|
|
3236
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
3890
|
+
import { resolve as resolve6 } from "node:path";
|
|
3352
3891
|
import { homedir as homedir4 } from "node:os";
|
|
3353
|
-
import { existsSync as
|
|
3354
|
-
var CONFIG_PATH = process.env["ZEROAGENT_CONFIG"] ??
|
|
3355
|
-
if (!
|
|
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
|
|