0agent 1.0.7 → 1.0.8
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 +18 -5
- package/dist/daemon.mjs +592 -153
- package/package.json +1 -1
package/bin/0agent.js
CHANGED
|
@@ -415,6 +415,7 @@ async function streamSession(sessionId) {
|
|
|
415
415
|
|
|
416
416
|
return new Promise((resolve) => {
|
|
417
417
|
const ws = new WS(`ws://localhost:4200/ws`);
|
|
418
|
+
let streaming = false; // true when mid-token-stream
|
|
418
419
|
|
|
419
420
|
ws.on('open', () => {
|
|
420
421
|
ws.send(JSON.stringify({ type: 'subscribe', topics: ['sessions'] }));
|
|
@@ -427,18 +428,30 @@ async function streamSession(sessionId) {
|
|
|
427
428
|
|
|
428
429
|
switch (event.type) {
|
|
429
430
|
case 'session.step':
|
|
430
|
-
|
|
431
|
+
// Newline before step if we were mid-stream
|
|
432
|
+
if (streaming) { process.stdout.write('\n'); streaming = false; }
|
|
433
|
+
console.log(` \x1b[2m›\x1b[0m ${event.step}`);
|
|
434
|
+
break;
|
|
435
|
+
case 'session.token':
|
|
436
|
+
// Token-by-token streaming — print without newline
|
|
437
|
+
if (!streaming) { process.stdout.write('\n '); streaming = true; }
|
|
438
|
+
process.stdout.write(event.token);
|
|
431
439
|
break;
|
|
432
440
|
case 'session.completed': {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
441
|
+
if (streaming) { process.stdout.write('\n'); streaming = false; }
|
|
442
|
+
// Show files written + commands run
|
|
443
|
+
const r = event.result ?? {};
|
|
444
|
+
if (r.files_written?.length) console.log(`\n \x1b[32m✓\x1b[0m Files: ${r.files_written.join(', ')}`);
|
|
445
|
+
if (r.commands_run?.length) console.log(` \x1b[32m✓\x1b[0m Commands run: ${r.commands_run.length}`);
|
|
446
|
+
if (r.tokens_used) console.log(` \x1b[2m${r.tokens_used} tokens · ${r.model}\x1b[0m`);
|
|
447
|
+
console.log('\n \x1b[32m✓ Done\x1b[0m\n');
|
|
436
448
|
ws.close();
|
|
437
449
|
resolve();
|
|
438
450
|
break;
|
|
439
451
|
}
|
|
440
452
|
case 'session.failed':
|
|
441
|
-
|
|
453
|
+
if (streaming) { process.stdout.write('\n'); streaming = false; }
|
|
454
|
+
console.log(`\n \x1b[31m✗ Failed:\x1b[0m ${event.error}\n`);
|
|
442
455
|
ws.close();
|
|
443
456
|
resolve();
|
|
444
457
|
break;
|
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 writeFileSync3, unlinkSync as unlinkSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "node:fs";
|
|
3
|
+
import { resolve as resolve4 } from "node:path";
|
|
4
4
|
import { homedir as homedir3 } from "node:os";
|
|
5
5
|
|
|
6
6
|
// packages/core/src/graph/GraphNode.ts
|
|
@@ -1685,6 +1685,537 @@ var EntityScopedContextLoader = class {
|
|
|
1685
1685
|
}
|
|
1686
1686
|
};
|
|
1687
1687
|
|
|
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";
|
|
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.",
|
|
1698
|
+
input_schema: {
|
|
1699
|
+
type: "object",
|
|
1700
|
+
properties: {
|
|
1701
|
+
command: { type: "string", description: "Shell command to execute" },
|
|
1702
|
+
timeout_ms: { type: "number", description: "Timeout in milliseconds (default 30000)" }
|
|
1703
|
+
},
|
|
1704
|
+
required: ["command"]
|
|
1705
|
+
}
|
|
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" }
|
|
1715
|
+
},
|
|
1716
|
+
required: ["path", "content"]
|
|
1717
|
+
}
|
|
1718
|
+
},
|
|
1719
|
+
{
|
|
1720
|
+
name: "read_file",
|
|
1721
|
+
description: "Read a file's contents.",
|
|
1722
|
+
input_schema: {
|
|
1723
|
+
type: "object",
|
|
1724
|
+
properties: {
|
|
1725
|
+
path: { type: "string", description: "File path relative to working directory" }
|
|
1726
|
+
},
|
|
1727
|
+
required: ["path"]
|
|
1728
|
+
}
|
|
1729
|
+
},
|
|
1730
|
+
{
|
|
1731
|
+
name: "list_dir",
|
|
1732
|
+
description: "List files and directories.",
|
|
1733
|
+
input_schema: {
|
|
1734
|
+
type: "object",
|
|
1735
|
+
properties: {
|
|
1736
|
+
path: { type: "string", description: 'Directory path relative to working directory (default: ".")' }
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
];
|
|
1741
|
+
var LLMExecutor = class {
|
|
1742
|
+
constructor(config) {
|
|
1743
|
+
this.config = config;
|
|
1744
|
+
}
|
|
1745
|
+
get isConfigured() {
|
|
1746
|
+
if (this.config.provider === "ollama") return true;
|
|
1747
|
+
return !!this.config.api_key?.trim();
|
|
1748
|
+
}
|
|
1749
|
+
// ─── Single completion (no tools, no streaming) ──────────────────────────
|
|
1750
|
+
async complete(messages, system) {
|
|
1751
|
+
const res = await this.completeWithTools(messages, [], system, void 0);
|
|
1752
|
+
return { content: res.content, tokens_used: res.tokens_used, model: res.model };
|
|
1753
|
+
}
|
|
1754
|
+
// ─── Tool-calling completion with optional streaming ─────────────────────
|
|
1755
|
+
async completeWithTools(messages, tools, system, onToken) {
|
|
1756
|
+
switch (this.config.provider) {
|
|
1757
|
+
case "anthropic":
|
|
1758
|
+
return this.anthropic(messages, tools, system, onToken);
|
|
1759
|
+
case "openai":
|
|
1760
|
+
return this.openai(messages, tools, system, onToken);
|
|
1761
|
+
case "xai":
|
|
1762
|
+
return this.openai(messages, tools, system, onToken, "https://api.x.ai/v1");
|
|
1763
|
+
case "gemini":
|
|
1764
|
+
return this.openai(messages, tools, system, onToken, "https://generativelanguage.googleapis.com/v1beta/openai");
|
|
1765
|
+
case "ollama":
|
|
1766
|
+
return this.ollama(messages, system, onToken);
|
|
1767
|
+
default:
|
|
1768
|
+
return this.openai(messages, tools, system, onToken);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
// ─── Anthropic ───────────────────────────────────────────────────────────
|
|
1772
|
+
async anthropic(messages, tools, system, onToken) {
|
|
1773
|
+
const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
|
|
1774
|
+
const filtered = messages.filter((m) => m.role !== "system");
|
|
1775
|
+
const anthropicMsgs = filtered.map((m) => {
|
|
1776
|
+
if (m.role === "tool") {
|
|
1777
|
+
return {
|
|
1778
|
+
role: "user",
|
|
1779
|
+
content: [{ type: "tool_result", tool_use_id: m.tool_call_id, content: m.content }]
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
if (m.role === "assistant" && m.tool_calls?.length) {
|
|
1783
|
+
return {
|
|
1784
|
+
role: "assistant",
|
|
1785
|
+
content: [
|
|
1786
|
+
...m.content ? [{ type: "text", text: m.content }] : [],
|
|
1787
|
+
...m.tool_calls.map((tc) => ({
|
|
1788
|
+
type: "tool_use",
|
|
1789
|
+
id: tc.id,
|
|
1790
|
+
name: tc.name,
|
|
1791
|
+
input: tc.input
|
|
1792
|
+
}))
|
|
1793
|
+
]
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
return { role: m.role, content: m.content };
|
|
1797
|
+
});
|
|
1798
|
+
const body = {
|
|
1799
|
+
model: this.config.model,
|
|
1800
|
+
max_tokens: 8192,
|
|
1801
|
+
messages: anthropicMsgs,
|
|
1802
|
+
stream: true
|
|
1803
|
+
};
|
|
1804
|
+
if (sysContent) body.system = sysContent;
|
|
1805
|
+
if (tools.length > 0) {
|
|
1806
|
+
body.tools = tools.map((t) => ({
|
|
1807
|
+
name: t.name,
|
|
1808
|
+
description: t.description,
|
|
1809
|
+
input_schema: t.input_schema
|
|
1810
|
+
}));
|
|
1811
|
+
}
|
|
1812
|
+
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
1813
|
+
method: "POST",
|
|
1814
|
+
headers: {
|
|
1815
|
+
"Content-Type": "application/json",
|
|
1816
|
+
"x-api-key": this.config.api_key,
|
|
1817
|
+
"anthropic-version": "2023-06-01"
|
|
1818
|
+
},
|
|
1819
|
+
body: JSON.stringify(body)
|
|
1820
|
+
});
|
|
1821
|
+
if (!res.ok) {
|
|
1822
|
+
const err = await res.text();
|
|
1823
|
+
throw new Error(`Anthropic ${res.status}: ${err}`);
|
|
1824
|
+
}
|
|
1825
|
+
let textContent = "";
|
|
1826
|
+
let stopReason = "end_turn";
|
|
1827
|
+
let inputTokens = 0;
|
|
1828
|
+
let outputTokens = 0;
|
|
1829
|
+
let modelName = this.config.model;
|
|
1830
|
+
const toolCalls = [];
|
|
1831
|
+
const toolInputBuffers = {};
|
|
1832
|
+
let currentToolId = "";
|
|
1833
|
+
const reader = res.body.getReader();
|
|
1834
|
+
const decoder = new TextDecoder();
|
|
1835
|
+
let buf = "";
|
|
1836
|
+
while (true) {
|
|
1837
|
+
const { done, value } = await reader.read();
|
|
1838
|
+
if (done) break;
|
|
1839
|
+
buf += decoder.decode(value, { stream: true });
|
|
1840
|
+
const lines = buf.split("\n");
|
|
1841
|
+
buf = lines.pop() ?? "";
|
|
1842
|
+
for (const line of lines) {
|
|
1843
|
+
if (!line.startsWith("data: ")) continue;
|
|
1844
|
+
const data = line.slice(6).trim();
|
|
1845
|
+
if (data === "[DONE]" || data === "") continue;
|
|
1846
|
+
let evt;
|
|
1847
|
+
try {
|
|
1848
|
+
evt = JSON.parse(data);
|
|
1849
|
+
} catch {
|
|
1850
|
+
continue;
|
|
1851
|
+
}
|
|
1852
|
+
const type = evt.type;
|
|
1853
|
+
if (type === "message_start") {
|
|
1854
|
+
const usage = evt.message?.usage;
|
|
1855
|
+
inputTokens = usage?.input_tokens ?? 0;
|
|
1856
|
+
modelName = evt.message?.model ?? modelName;
|
|
1857
|
+
} else if (type === "content_block_start") {
|
|
1858
|
+
const block = evt.content_block;
|
|
1859
|
+
if (block?.type === "tool_use") {
|
|
1860
|
+
currentToolId = block.id;
|
|
1861
|
+
toolInputBuffers[currentToolId] = "";
|
|
1862
|
+
toolCalls.push({ id: currentToolId, name: block.name, input: {} });
|
|
1863
|
+
}
|
|
1864
|
+
} else if (type === "content_block_delta") {
|
|
1865
|
+
const delta = evt.delta;
|
|
1866
|
+
if (delta?.type === "text_delta") {
|
|
1867
|
+
const token = delta.text ?? "";
|
|
1868
|
+
textContent += token;
|
|
1869
|
+
if (onToken && token) onToken(token);
|
|
1870
|
+
} else if (delta?.type === "input_json_delta") {
|
|
1871
|
+
toolInputBuffers[currentToolId] = (toolInputBuffers[currentToolId] ?? "") + (delta.partial_json ?? "");
|
|
1872
|
+
}
|
|
1873
|
+
} else if (type === "content_block_stop") {
|
|
1874
|
+
if (currentToolId && toolInputBuffers[currentToolId]) {
|
|
1875
|
+
const tc = toolCalls.find((t) => t.id === currentToolId);
|
|
1876
|
+
if (tc) {
|
|
1877
|
+
try {
|
|
1878
|
+
tc.input = JSON.parse(toolInputBuffers[currentToolId]);
|
|
1879
|
+
} catch {
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
} else if (type === "message_delta") {
|
|
1884
|
+
const usage = evt.usage;
|
|
1885
|
+
outputTokens = usage?.output_tokens ?? 0;
|
|
1886
|
+
const stop = evt.delta?.stop_reason;
|
|
1887
|
+
if (stop === "tool_use") stopReason = "tool_use";
|
|
1888
|
+
else if (stop === "end_turn") stopReason = "end_turn";
|
|
1889
|
+
else if (stop === "max_tokens") stopReason = "max_tokens";
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
return {
|
|
1894
|
+
content: textContent,
|
|
1895
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : null,
|
|
1896
|
+
stop_reason: stopReason,
|
|
1897
|
+
tokens_used: inputTokens + outputTokens,
|
|
1898
|
+
model: modelName
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
// ─── OpenAI (also xAI, Gemini) ───────────────────────────────────────────
|
|
1902
|
+
async openai(messages, tools, system, onToken, baseUrl = "https://api.openai.com/v1") {
|
|
1903
|
+
const allMessages = [];
|
|
1904
|
+
const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
|
|
1905
|
+
if (sysContent) allMessages.push({ role: "system", content: sysContent });
|
|
1906
|
+
for (const m of messages.filter((m2) => m2.role !== "system")) {
|
|
1907
|
+
if (m.role === "tool") {
|
|
1908
|
+
allMessages.push({ role: "tool", tool_call_id: m.tool_call_id, content: m.content });
|
|
1909
|
+
} else if (m.role === "assistant" && m.tool_calls?.length) {
|
|
1910
|
+
allMessages.push({
|
|
1911
|
+
role: "assistant",
|
|
1912
|
+
content: m.content || null,
|
|
1913
|
+
tool_calls: m.tool_calls.map((tc) => ({
|
|
1914
|
+
id: tc.id,
|
|
1915
|
+
type: "function",
|
|
1916
|
+
function: { name: tc.name, arguments: JSON.stringify(tc.input) }
|
|
1917
|
+
}))
|
|
1918
|
+
});
|
|
1919
|
+
} else {
|
|
1920
|
+
allMessages.push({ role: m.role, content: m.content });
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
const body = {
|
|
1924
|
+
model: this.config.model,
|
|
1925
|
+
messages: allMessages,
|
|
1926
|
+
max_tokens: 8192,
|
|
1927
|
+
stream: true,
|
|
1928
|
+
stream_options: { include_usage: true }
|
|
1929
|
+
};
|
|
1930
|
+
if (tools.length > 0) {
|
|
1931
|
+
body.tools = tools.map((t) => ({
|
|
1932
|
+
type: "function",
|
|
1933
|
+
function: { name: t.name, description: t.description, parameters: t.input_schema }
|
|
1934
|
+
}));
|
|
1935
|
+
}
|
|
1936
|
+
const res = await fetch(`${this.config.base_url ?? baseUrl}/chat/completions`, {
|
|
1937
|
+
method: "POST",
|
|
1938
|
+
headers: {
|
|
1939
|
+
"Content-Type": "application/json",
|
|
1940
|
+
"Authorization": `Bearer ${this.config.api_key}`
|
|
1941
|
+
},
|
|
1942
|
+
body: JSON.stringify(body)
|
|
1943
|
+
});
|
|
1944
|
+
if (!res.ok) {
|
|
1945
|
+
const err = await res.text();
|
|
1946
|
+
throw new Error(`OpenAI ${res.status}: ${err}`);
|
|
1947
|
+
}
|
|
1948
|
+
let textContent = "";
|
|
1949
|
+
let tokensUsed = 0;
|
|
1950
|
+
let modelName = this.config.model;
|
|
1951
|
+
let stopReason = "end_turn";
|
|
1952
|
+
const toolCallMap = {};
|
|
1953
|
+
const reader = res.body.getReader();
|
|
1954
|
+
const decoder = new TextDecoder();
|
|
1955
|
+
let buf = "";
|
|
1956
|
+
while (true) {
|
|
1957
|
+
const { done, value } = await reader.read();
|
|
1958
|
+
if (done) break;
|
|
1959
|
+
buf += decoder.decode(value, { stream: true });
|
|
1960
|
+
const lines = buf.split("\n");
|
|
1961
|
+
buf = lines.pop() ?? "";
|
|
1962
|
+
for (const line of lines) {
|
|
1963
|
+
if (!line.startsWith("data: ")) continue;
|
|
1964
|
+
const data = line.slice(6).trim();
|
|
1965
|
+
if (data === "[DONE]") continue;
|
|
1966
|
+
let evt;
|
|
1967
|
+
try {
|
|
1968
|
+
evt = JSON.parse(data);
|
|
1969
|
+
} catch {
|
|
1970
|
+
continue;
|
|
1971
|
+
}
|
|
1972
|
+
modelName = evt.model ?? modelName;
|
|
1973
|
+
const usage = evt.usage;
|
|
1974
|
+
if (usage?.total_tokens) tokensUsed = usage.total_tokens;
|
|
1975
|
+
const choices = evt.choices;
|
|
1976
|
+
if (!choices?.length) continue;
|
|
1977
|
+
const delta = choices[0].delta;
|
|
1978
|
+
if (!delta) continue;
|
|
1979
|
+
const finish = choices[0].finish_reason;
|
|
1980
|
+
if (finish === "tool_calls") stopReason = "tool_use";
|
|
1981
|
+
else if (finish === "stop") stopReason = "end_turn";
|
|
1982
|
+
const token = delta.content;
|
|
1983
|
+
if (token) {
|
|
1984
|
+
textContent += token;
|
|
1985
|
+
if (onToken) onToken(token);
|
|
1986
|
+
}
|
|
1987
|
+
const toolCallDeltas = delta.tool_calls;
|
|
1988
|
+
if (toolCallDeltas) {
|
|
1989
|
+
for (const tc of toolCallDeltas) {
|
|
1990
|
+
const idx = tc.index;
|
|
1991
|
+
if (!toolCallMap[idx]) {
|
|
1992
|
+
toolCallMap[idx] = { id: "", name: "", args: "" };
|
|
1993
|
+
}
|
|
1994
|
+
const fn = tc.function;
|
|
1995
|
+
if (tc.id) toolCallMap[idx].id = tc.id;
|
|
1996
|
+
if (fn?.name) toolCallMap[idx].name = fn.name;
|
|
1997
|
+
if (fn?.arguments) toolCallMap[idx].args += fn.arguments;
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
const toolCalls = Object.values(toolCallMap).filter((tc) => tc.id && tc.name).map((tc) => {
|
|
2003
|
+
let input = {};
|
|
2004
|
+
try {
|
|
2005
|
+
input = JSON.parse(tc.args);
|
|
2006
|
+
} catch {
|
|
2007
|
+
}
|
|
2008
|
+
return { id: tc.id, name: tc.name, input };
|
|
2009
|
+
});
|
|
2010
|
+
return {
|
|
2011
|
+
content: textContent,
|
|
2012
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : null,
|
|
2013
|
+
stop_reason: stopReason,
|
|
2014
|
+
tokens_used: tokensUsed,
|
|
2015
|
+
model: modelName
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2018
|
+
// ─── Ollama (no streaming for simplicity) ────────────────────────────────
|
|
2019
|
+
async ollama(messages, system, onToken) {
|
|
2020
|
+
const baseUrl = this.config.base_url ?? "http://localhost:11434";
|
|
2021
|
+
const allMessages = [];
|
|
2022
|
+
const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
|
|
2023
|
+
if (sysContent) allMessages.push({ role: "system", content: sysContent });
|
|
2024
|
+
allMessages.push(...messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content })));
|
|
2025
|
+
const res = await fetch(`${baseUrl}/api/chat`, {
|
|
2026
|
+
method: "POST",
|
|
2027
|
+
headers: { "Content-Type": "application/json" },
|
|
2028
|
+
body: JSON.stringify({ model: this.config.model, messages: allMessages, stream: false })
|
|
2029
|
+
});
|
|
2030
|
+
if (!res.ok) throw new Error(`Ollama error ${res.status}`);
|
|
2031
|
+
const data = await res.json();
|
|
2032
|
+
if (onToken) onToken(data.message.content);
|
|
2033
|
+
return { content: data.message.content, tool_calls: null, stop_reason: "end_turn", tokens_used: data.eval_count ?? 0, model: this.config.model };
|
|
2034
|
+
}
|
|
2035
|
+
};
|
|
2036
|
+
|
|
2037
|
+
// packages/daemon/src/AgentExecutor.ts
|
|
2038
|
+
var AgentExecutor = class {
|
|
2039
|
+
constructor(llm, config, onStep, onToken) {
|
|
2040
|
+
this.llm = llm;
|
|
2041
|
+
this.config = config;
|
|
2042
|
+
this.onStep = onStep;
|
|
2043
|
+
this.onToken = onToken;
|
|
2044
|
+
this.cwd = config.cwd;
|
|
2045
|
+
this.maxIterations = config.max_iterations ?? 20;
|
|
2046
|
+
this.maxCommandMs = config.max_command_ms ?? 3e4;
|
|
2047
|
+
}
|
|
2048
|
+
cwd;
|
|
2049
|
+
maxIterations;
|
|
2050
|
+
maxCommandMs;
|
|
2051
|
+
async execute(task, systemContext) {
|
|
2052
|
+
const filesWritten = [];
|
|
2053
|
+
const commandsRun = [];
|
|
2054
|
+
let totalTokens = 0;
|
|
2055
|
+
let modelName = "";
|
|
2056
|
+
const systemPrompt = this.buildSystemPrompt(systemContext);
|
|
2057
|
+
const messages = [
|
|
2058
|
+
{ role: "user", content: task }
|
|
2059
|
+
];
|
|
2060
|
+
let finalOutput = "";
|
|
2061
|
+
for (let i = 0; i < this.maxIterations; i++) {
|
|
2062
|
+
this.onStep(i === 0 ? "Thinking\u2026" : "Continuing\u2026");
|
|
2063
|
+
let response;
|
|
2064
|
+
try {
|
|
2065
|
+
response = await this.llm.completeWithTools(
|
|
2066
|
+
messages,
|
|
2067
|
+
AGENT_TOOLS,
|
|
2068
|
+
systemPrompt,
|
|
2069
|
+
// Only stream tokens on the final (non-tool) turn
|
|
2070
|
+
(token) => {
|
|
2071
|
+
this.onToken(token);
|
|
2072
|
+
finalOutput += token;
|
|
2073
|
+
}
|
|
2074
|
+
);
|
|
2075
|
+
} catch (err) {
|
|
2076
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2077
|
+
this.onStep(`LLM error: ${msg}`);
|
|
2078
|
+
finalOutput = `Error: ${msg}`;
|
|
2079
|
+
break;
|
|
2080
|
+
}
|
|
2081
|
+
totalTokens += response.tokens_used;
|
|
2082
|
+
modelName = response.model;
|
|
2083
|
+
if (response.stop_reason === "end_turn" || !response.tool_calls?.length) {
|
|
2084
|
+
if (!finalOutput && response.content) finalOutput = response.content;
|
|
2085
|
+
break;
|
|
2086
|
+
}
|
|
2087
|
+
finalOutput = "";
|
|
2088
|
+
messages.push({
|
|
2089
|
+
role: "assistant",
|
|
2090
|
+
content: response.content,
|
|
2091
|
+
tool_calls: response.tool_calls
|
|
2092
|
+
});
|
|
2093
|
+
for (const tc of response.tool_calls) {
|
|
2094
|
+
this.onStep(`\u25B6 ${tc.name}(${this.summariseInput(tc.name, tc.input)})`);
|
|
2095
|
+
let result;
|
|
2096
|
+
try {
|
|
2097
|
+
result = await this.executeTool(tc.name, tc.input);
|
|
2098
|
+
if (tc.name === "write_file" && tc.input.path) {
|
|
2099
|
+
filesWritten.push(String(tc.input.path));
|
|
2100
|
+
}
|
|
2101
|
+
if (tc.name === "shell_exec" && tc.input.command) {
|
|
2102
|
+
commandsRun.push(String(tc.input.command));
|
|
2103
|
+
}
|
|
2104
|
+
} catch (err) {
|
|
2105
|
+
result = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
2106
|
+
}
|
|
2107
|
+
this.onStep(` \u21B3 ${result.slice(0, 120)}${result.length > 120 ? "\u2026" : ""}`);
|
|
2108
|
+
messages.push({
|
|
2109
|
+
role: "tool",
|
|
2110
|
+
content: result,
|
|
2111
|
+
tool_call_id: tc.id
|
|
2112
|
+
});
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
return {
|
|
2116
|
+
output: finalOutput || "(no output)",
|
|
2117
|
+
files_written: filesWritten,
|
|
2118
|
+
commands_run: commandsRun,
|
|
2119
|
+
tokens_used: totalTokens,
|
|
2120
|
+
model: modelName,
|
|
2121
|
+
iterations: messages.filter((m) => m.role === "assistant").length
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
// ─── Tool execution ────────────────────────────────────────────────────────
|
|
2125
|
+
async executeTool(name, input) {
|
|
2126
|
+
switch (name) {
|
|
2127
|
+
case "shell_exec":
|
|
2128
|
+
return this.shellExec(
|
|
2129
|
+
String(input.command ?? ""),
|
|
2130
|
+
Number(input.timeout_ms ?? this.maxCommandMs)
|
|
2131
|
+
);
|
|
2132
|
+
case "write_file":
|
|
2133
|
+
return this.writeFile(String(input.path ?? ""), String(input.content ?? ""));
|
|
2134
|
+
case "read_file":
|
|
2135
|
+
return this.readFile(String(input.path ?? ""));
|
|
2136
|
+
case "list_dir":
|
|
2137
|
+
return this.listDir(input.path ? String(input.path) : void 0);
|
|
2138
|
+
default:
|
|
2139
|
+
return `Unknown tool: ${name}`;
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
shellExec(command, timeoutMs) {
|
|
2143
|
+
return new Promise((resolve6) => {
|
|
2144
|
+
const chunks = [];
|
|
2145
|
+
const proc = spawn("bash", ["-c", command], {
|
|
2146
|
+
cwd: this.cwd,
|
|
2147
|
+
env: { ...process.env, TERM: "dumb" },
|
|
2148
|
+
timeout: timeoutMs
|
|
2149
|
+
});
|
|
2150
|
+
proc.stdout.on("data", (d) => chunks.push(d.toString()));
|
|
2151
|
+
proc.stderr.on("data", (d) => chunks.push(d.toString()));
|
|
2152
|
+
proc.on("close", (code) => {
|
|
2153
|
+
const output = chunks.join("").trim();
|
|
2154
|
+
resolve6(output || (code === 0 ? "(command completed, no output)" : `exit code ${code}`));
|
|
2155
|
+
});
|
|
2156
|
+
proc.on("error", (err) => {
|
|
2157
|
+
resolve6(`Error: ${err.message}`);
|
|
2158
|
+
});
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
writeFile(filePath, content) {
|
|
2162
|
+
const safe = this.safePath(filePath);
|
|
2163
|
+
if (!safe) return "Error: path outside working directory";
|
|
2164
|
+
mkdirSync(dirname(safe), { recursive: true });
|
|
2165
|
+
writeFileSync(safe, content, "utf8");
|
|
2166
|
+
const rel = relative(this.cwd, safe);
|
|
2167
|
+
return `Written: ${rel} (${content.length} bytes)`;
|
|
2168
|
+
}
|
|
2169
|
+
readFile(filePath) {
|
|
2170
|
+
const safe = this.safePath(filePath);
|
|
2171
|
+
if (!safe) return "Error: path outside working directory";
|
|
2172
|
+
if (!existsSync2(safe)) return `File not found: ${filePath}`;
|
|
2173
|
+
const content = readFileSync2(safe, "utf8");
|
|
2174
|
+
return content.length > 8e3 ? content.slice(0, 8e3) + `
|
|
2175
|
+
\u2026[truncated, ${content.length} total bytes]` : content;
|
|
2176
|
+
}
|
|
2177
|
+
listDir(dirPath) {
|
|
2178
|
+
const safe = this.safePath(dirPath ?? ".");
|
|
2179
|
+
if (!safe) return "Error: path outside working directory";
|
|
2180
|
+
if (!existsSync2(safe)) return `Directory not found: ${dirPath}`;
|
|
2181
|
+
try {
|
|
2182
|
+
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");
|
|
2183
|
+
return entries || "(empty directory)";
|
|
2184
|
+
} catch (e) {
|
|
2185
|
+
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
2189
|
+
safePath(p) {
|
|
2190
|
+
const resolved = resolve2(this.cwd, p);
|
|
2191
|
+
return resolved.startsWith(this.cwd) ? resolved : null;
|
|
2192
|
+
}
|
|
2193
|
+
buildSystemPrompt(extra) {
|
|
2194
|
+
const lines = [
|
|
2195
|
+
`You are 0agent, an AI software engineer. You can execute shell commands and manage files.`,
|
|
2196
|
+
`Working directory: ${this.cwd}`,
|
|
2197
|
+
``,
|
|
2198
|
+
`Instructions:`,
|
|
2199
|
+
`- Use tools to actually accomplish tasks, don't just describe what to do`,
|
|
2200
|
+
`- For web servers: write the files, then start the server with & (background)`,
|
|
2201
|
+
`- For npm/node projects: check package.json first with read_file or list_dir`,
|
|
2202
|
+
`- After write_file, verify with read_file if needed`,
|
|
2203
|
+
`- After shell_exec, check output for errors and retry if needed`,
|
|
2204
|
+
`- Use relative paths from the working directory`,
|
|
2205
|
+
`- Be concise in your final response: state what was done and where to find it`
|
|
2206
|
+
];
|
|
2207
|
+
if (extra) lines.push(``, `Context:`, extra);
|
|
2208
|
+
return lines.join("\n");
|
|
2209
|
+
}
|
|
2210
|
+
summariseInput(toolName, input) {
|
|
2211
|
+
if (toolName === "shell_exec") return `"${String(input.command ?? "").slice(0, 60)}"`;
|
|
2212
|
+
if (toolName === "write_file") return `"${input.path}"`;
|
|
2213
|
+
if (toolName === "read_file") return `"${input.path}"`;
|
|
2214
|
+
if (toolName === "list_dir") return `"${input.path ?? "."}"`;
|
|
2215
|
+
return JSON.stringify(input).slice(0, 60);
|
|
2216
|
+
}
|
|
2217
|
+
};
|
|
2218
|
+
|
|
1688
2219
|
// packages/daemon/src/SessionManager.ts
|
|
1689
2220
|
var SessionManager = class {
|
|
1690
2221
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -1692,11 +2223,13 @@ var SessionManager = class {
|
|
|
1692
2223
|
eventBus;
|
|
1693
2224
|
graph;
|
|
1694
2225
|
llm;
|
|
2226
|
+
cwd;
|
|
1695
2227
|
constructor(deps = {}) {
|
|
1696
2228
|
this.inferenceEngine = deps.inferenceEngine;
|
|
1697
2229
|
this.eventBus = deps.eventBus;
|
|
1698
2230
|
this.graph = deps.graph;
|
|
1699
2231
|
this.llm = deps.llm;
|
|
2232
|
+
this.cwd = deps.cwd ?? process.cwd();
|
|
1700
2233
|
}
|
|
1701
2234
|
/**
|
|
1702
2235
|
* Create a new session with status 'pending'.
|
|
@@ -1864,30 +2397,36 @@ var SessionManager = class {
|
|
|
1864
2397
|
} else {
|
|
1865
2398
|
this.addStep(session.id, "No inference engine connected \u2014 executing task directly");
|
|
1866
2399
|
}
|
|
1867
|
-
this.addStep(session.id, "Calling LLM\u2026");
|
|
1868
|
-
let output = "";
|
|
1869
2400
|
if (this.llm?.isConfigured) {
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
this.
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
2401
|
+
const executor = new AgentExecutor(
|
|
2402
|
+
this.llm,
|
|
2403
|
+
{ cwd: this.cwd },
|
|
2404
|
+
// step callback → emit session.step events
|
|
2405
|
+
(step) => this.addStep(session.id, step),
|
|
2406
|
+
// token callback → emit session.token events
|
|
2407
|
+
(token) => this.emit({ type: "session.token", session_id: session.id, token })
|
|
2408
|
+
);
|
|
2409
|
+
const systemContext = enrichedReq.context?.system_context ? String(enrichedReq.context.system_context) : void 0;
|
|
2410
|
+
const agentResult = await executor.execute(enrichedReq.task, systemContext);
|
|
2411
|
+
if (agentResult.files_written.length > 0) {
|
|
2412
|
+
this.addStep(session.id, `Files written: ${agentResult.files_written.join(", ")}`);
|
|
2413
|
+
}
|
|
2414
|
+
if (agentResult.commands_run.length > 0) {
|
|
2415
|
+
this.addStep(session.id, `Commands run: ${agentResult.commands_run.length}`);
|
|
1881
2416
|
}
|
|
2417
|
+
this.addStep(session.id, `Done (${agentResult.tokens_used} tokens, ${agentResult.iterations} LLM turns)`);
|
|
2418
|
+
this.completeSession(session.id, {
|
|
2419
|
+
output: agentResult.output,
|
|
2420
|
+
files_written: agentResult.files_written,
|
|
2421
|
+
commands_run: agentResult.commands_run,
|
|
2422
|
+
tokens_used: agentResult.tokens_used,
|
|
2423
|
+
model: agentResult.model
|
|
2424
|
+
});
|
|
1882
2425
|
} else {
|
|
1883
|
-
output = session.plan?.reasoning ?? "No LLM configured \u2014 add
|
|
1884
|
-
this.addStep(session.id, "No LLM
|
|
2426
|
+
const output = session.plan?.reasoning ?? "No LLM configured \u2014 add api_key to ~/.0agent/config.yaml";
|
|
2427
|
+
this.addStep(session.id, "No LLM API key configured");
|
|
2428
|
+
this.completeSession(session.id, { output });
|
|
1885
2429
|
}
|
|
1886
|
-
this.completeSession(session.id, {
|
|
1887
|
-
output,
|
|
1888
|
-
plan: session.plan ?? null,
|
|
1889
|
-
steps: session.steps.length
|
|
1890
|
-
});
|
|
1891
2430
|
} catch (err) {
|
|
1892
2431
|
const message = err instanceof Error ? err.message : String(err);
|
|
1893
2432
|
this.failSession(session.id, message);
|
|
@@ -2130,7 +2669,7 @@ var BackgroundWorkers = class {
|
|
|
2130
2669
|
};
|
|
2131
2670
|
|
|
2132
2671
|
// packages/daemon/src/SkillRegistry.ts
|
|
2133
|
-
import { readFileSync as
|
|
2672
|
+
import { readFileSync as readFileSync3, readdirSync as readdirSync2, existsSync as existsSync3, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "node:fs";
|
|
2134
2673
|
import { join } from "node:path";
|
|
2135
2674
|
import { homedir as homedir2 } from "node:os";
|
|
2136
2675
|
import YAML2 from "yaml";
|
|
@@ -2153,11 +2692,11 @@ var SkillRegistry = class {
|
|
|
2153
2692
|
this.loadFromDir(this.customDir, false);
|
|
2154
2693
|
}
|
|
2155
2694
|
loadFromDir(dir, isBuiltin) {
|
|
2156
|
-
if (!
|
|
2157
|
-
const files =
|
|
2695
|
+
if (!existsSync3(dir)) return;
|
|
2696
|
+
const files = readdirSync2(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
2158
2697
|
for (const file of files) {
|
|
2159
2698
|
try {
|
|
2160
|
-
const raw =
|
|
2699
|
+
const raw = readFileSync3(join(dir, file), "utf8");
|
|
2161
2700
|
const skill = YAML2.parse(raw);
|
|
2162
2701
|
if (skill.name) {
|
|
2163
2702
|
this.skills.set(skill.name, skill);
|
|
@@ -2192,9 +2731,9 @@ var SkillRegistry = class {
|
|
|
2192
2731
|
if (this.builtinNames.has(name)) {
|
|
2193
2732
|
throw new Error(`Cannot override built-in skill: ${name}`);
|
|
2194
2733
|
}
|
|
2195
|
-
|
|
2734
|
+
mkdirSync2(this.customDir, { recursive: true });
|
|
2196
2735
|
const filePath = join(this.customDir, `${name}.yaml`);
|
|
2197
|
-
|
|
2736
|
+
writeFileSync2(filePath, yamlContent, "utf8");
|
|
2198
2737
|
const skill = YAML2.parse(yamlContent);
|
|
2199
2738
|
this.skills.set(name, skill);
|
|
2200
2739
|
return skill;
|
|
@@ -2207,7 +2746,7 @@ var SkillRegistry = class {
|
|
|
2207
2746
|
throw new Error(`Cannot delete built-in skill: ${name}`);
|
|
2208
2747
|
}
|
|
2209
2748
|
const filePath = join(this.customDir, `${name}.yaml`);
|
|
2210
|
-
if (
|
|
2749
|
+
if (existsSync3(filePath)) {
|
|
2211
2750
|
unlinkSync(filePath);
|
|
2212
2751
|
}
|
|
2213
2752
|
this.skills.delete(name);
|
|
@@ -2220,8 +2759,8 @@ var SkillRegistry = class {
|
|
|
2220
2759
|
// packages/daemon/src/HTTPServer.ts
|
|
2221
2760
|
import { Hono as Hono8 } from "hono";
|
|
2222
2761
|
import { serve } from "@hono/node-server";
|
|
2223
|
-
import { readFileSync as
|
|
2224
|
-
import { resolve as
|
|
2762
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
2763
|
+
import { resolve as resolve3, dirname as dirname2 } from "node:path";
|
|
2225
2764
|
import { fileURLToPath } from "node:url";
|
|
2226
2765
|
|
|
2227
2766
|
// packages/daemon/src/routes/health.ts
|
|
@@ -2476,15 +3015,15 @@ function skillRoutes(deps) {
|
|
|
2476
3015
|
// packages/daemon/src/HTTPServer.ts
|
|
2477
3016
|
function findGraphHtml() {
|
|
2478
3017
|
const candidates = [
|
|
2479
|
-
|
|
3018
|
+
resolve3(dirname2(fileURLToPath(import.meta.url)), "graph.html"),
|
|
2480
3019
|
// dev (src/)
|
|
2481
|
-
|
|
3020
|
+
resolve3(dirname2(fileURLToPath(import.meta.url)), "..", "graph.html"),
|
|
2482
3021
|
// bundled (dist/../)
|
|
2483
|
-
|
|
3022
|
+
resolve3(dirname2(fileURLToPath(import.meta.url)), "..", "dist", "graph.html")
|
|
2484
3023
|
];
|
|
2485
3024
|
for (const p of candidates) {
|
|
2486
3025
|
try {
|
|
2487
|
-
|
|
3026
|
+
readFileSync4(p);
|
|
2488
3027
|
return p;
|
|
2489
3028
|
} catch {
|
|
2490
3029
|
}
|
|
@@ -2508,7 +3047,7 @@ var HTTPServer = class {
|
|
|
2508
3047
|
this.app.route("/api/skills", skillRoutes({ skillRegistry: deps.skillRegistry }));
|
|
2509
3048
|
const serveGraph = (c) => {
|
|
2510
3049
|
try {
|
|
2511
|
-
const html =
|
|
3050
|
+
const html = readFileSync4(GRAPH_HTML_PATH, "utf8");
|
|
2512
3051
|
return c.html(html);
|
|
2513
3052
|
} catch {
|
|
2514
3053
|
return c.html("<p>Graph UI not found. Run: pnpm build</p>");
|
|
@@ -2518,7 +3057,7 @@ var HTTPServer = class {
|
|
|
2518
3057
|
this.app.get("/graph", serveGraph);
|
|
2519
3058
|
}
|
|
2520
3059
|
start() {
|
|
2521
|
-
return new Promise((
|
|
3060
|
+
return new Promise((resolve6) => {
|
|
2522
3061
|
this.server = serve(
|
|
2523
3062
|
{
|
|
2524
3063
|
fetch: this.app.fetch,
|
|
@@ -2526,20 +3065,20 @@ var HTTPServer = class {
|
|
|
2526
3065
|
hostname: this.deps.host
|
|
2527
3066
|
},
|
|
2528
3067
|
() => {
|
|
2529
|
-
|
|
3068
|
+
resolve6();
|
|
2530
3069
|
}
|
|
2531
3070
|
);
|
|
2532
3071
|
});
|
|
2533
3072
|
}
|
|
2534
3073
|
stop() {
|
|
2535
|
-
return new Promise((
|
|
3074
|
+
return new Promise((resolve6, reject) => {
|
|
2536
3075
|
if (!this.server) {
|
|
2537
|
-
|
|
3076
|
+
resolve6();
|
|
2538
3077
|
return;
|
|
2539
3078
|
}
|
|
2540
3079
|
this.server.close((err) => {
|
|
2541
3080
|
if (err) reject(err);
|
|
2542
|
-
else
|
|
3081
|
+
else resolve6();
|
|
2543
3082
|
});
|
|
2544
3083
|
});
|
|
2545
3084
|
}
|
|
@@ -2548,107 +3087,6 @@ var HTTPServer = class {
|
|
|
2548
3087
|
}
|
|
2549
3088
|
};
|
|
2550
3089
|
|
|
2551
|
-
// packages/daemon/src/LLMExecutor.ts
|
|
2552
|
-
var LLMExecutor = class {
|
|
2553
|
-
constructor(config) {
|
|
2554
|
-
this.config = config;
|
|
2555
|
-
}
|
|
2556
|
-
async complete(messages, system) {
|
|
2557
|
-
switch (this.config.provider) {
|
|
2558
|
-
case "anthropic":
|
|
2559
|
-
return this.callAnthropic(messages, system);
|
|
2560
|
-
case "openai":
|
|
2561
|
-
return this.callOpenAI(messages, system);
|
|
2562
|
-
case "xai":
|
|
2563
|
-
return this.callOpenAI(messages, system, "https://api.x.ai/v1");
|
|
2564
|
-
case "gemini":
|
|
2565
|
-
return this.callOpenAI(messages, system, "https://generativelanguage.googleapis.com/v1beta/openai");
|
|
2566
|
-
case "ollama":
|
|
2567
|
-
return this.callOllama(messages, system);
|
|
2568
|
-
default:
|
|
2569
|
-
return this.callOpenAI(messages, system);
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
async callAnthropic(messages, system) {
|
|
2573
|
-
const body = {
|
|
2574
|
-
model: this.config.model,
|
|
2575
|
-
max_tokens: 8192,
|
|
2576
|
-
messages: messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content }))
|
|
2577
|
-
};
|
|
2578
|
-
if (system) body.system = system;
|
|
2579
|
-
else {
|
|
2580
|
-
const sysMsg = messages.find((m) => m.role === "system");
|
|
2581
|
-
if (sysMsg) body.system = sysMsg.content;
|
|
2582
|
-
}
|
|
2583
|
-
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
2584
|
-
method: "POST",
|
|
2585
|
-
headers: {
|
|
2586
|
-
"Content-Type": "application/json",
|
|
2587
|
-
"x-api-key": this.config.api_key,
|
|
2588
|
-
"anthropic-version": "2023-06-01"
|
|
2589
|
-
},
|
|
2590
|
-
body: JSON.stringify(body)
|
|
2591
|
-
});
|
|
2592
|
-
if (!res.ok) {
|
|
2593
|
-
const err = await res.text();
|
|
2594
|
-
throw new Error(`Anthropic API error ${res.status}: ${err}`);
|
|
2595
|
-
}
|
|
2596
|
-
const data = await res.json();
|
|
2597
|
-
return {
|
|
2598
|
-
content: data.content.filter((c) => c.type === "text").map((c) => c.text).join(""),
|
|
2599
|
-
tokens_used: (data.usage?.input_tokens ?? 0) + (data.usage?.output_tokens ?? 0),
|
|
2600
|
-
model: data.model
|
|
2601
|
-
};
|
|
2602
|
-
}
|
|
2603
|
-
async callOpenAI(messages, system, baseUrl = "https://api.openai.com/v1") {
|
|
2604
|
-
const allMessages = [];
|
|
2605
|
-
const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
|
|
2606
|
-
if (sysContent) allMessages.push({ role: "system", content: sysContent });
|
|
2607
|
-
allMessages.push(...messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content })));
|
|
2608
|
-
const res = await fetch(`${this.config.base_url ?? baseUrl}/chat/completions`, {
|
|
2609
|
-
method: "POST",
|
|
2610
|
-
headers: {
|
|
2611
|
-
"Content-Type": "application/json",
|
|
2612
|
-
"Authorization": `Bearer ${this.config.api_key}`
|
|
2613
|
-
},
|
|
2614
|
-
body: JSON.stringify({
|
|
2615
|
-
model: this.config.model,
|
|
2616
|
-
messages: allMessages,
|
|
2617
|
-
max_tokens: 8192
|
|
2618
|
-
})
|
|
2619
|
-
});
|
|
2620
|
-
if (!res.ok) {
|
|
2621
|
-
const err = await res.text();
|
|
2622
|
-
throw new Error(`OpenAI API error ${res.status}: ${err}`);
|
|
2623
|
-
}
|
|
2624
|
-
const data = await res.json();
|
|
2625
|
-
return {
|
|
2626
|
-
content: data.choices[0]?.message?.content ?? "",
|
|
2627
|
-
tokens_used: data.usage?.total_tokens ?? 0,
|
|
2628
|
-
model: data.model
|
|
2629
|
-
};
|
|
2630
|
-
}
|
|
2631
|
-
async callOllama(messages, system) {
|
|
2632
|
-
const baseUrl = this.config.base_url ?? "http://localhost:11434";
|
|
2633
|
-
const allMessages = [];
|
|
2634
|
-
const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
|
|
2635
|
-
if (sysContent) allMessages.push({ role: "system", content: sysContent });
|
|
2636
|
-
allMessages.push(...messages.filter((m) => m.role !== "system"));
|
|
2637
|
-
const res = await fetch(`${baseUrl}/api/chat`, {
|
|
2638
|
-
method: "POST",
|
|
2639
|
-
headers: { "Content-Type": "application/json" },
|
|
2640
|
-
body: JSON.stringify({ model: this.config.model, messages: allMessages, stream: false })
|
|
2641
|
-
});
|
|
2642
|
-
if (!res.ok) throw new Error(`Ollama error ${res.status}`);
|
|
2643
|
-
const data = await res.json();
|
|
2644
|
-
return { content: data.message.content, tokens_used: data.eval_count ?? 0, model: this.config.model };
|
|
2645
|
-
}
|
|
2646
|
-
get isConfigured() {
|
|
2647
|
-
if (this.config.provider === "ollama") return true;
|
|
2648
|
-
return !!this.config.api_key?.trim();
|
|
2649
|
-
}
|
|
2650
|
-
};
|
|
2651
|
-
|
|
2652
3090
|
// packages/daemon/src/ZeroAgentDaemon.ts
|
|
2653
3091
|
var ZeroAgentDaemon = class {
|
|
2654
3092
|
config = null;
|
|
@@ -2664,13 +3102,13 @@ var ZeroAgentDaemon = class {
|
|
|
2664
3102
|
startedAt = 0;
|
|
2665
3103
|
pidFilePath;
|
|
2666
3104
|
constructor() {
|
|
2667
|
-
this.pidFilePath =
|
|
3105
|
+
this.pidFilePath = resolve4(homedir3(), ".0agent", "daemon.pid");
|
|
2668
3106
|
}
|
|
2669
3107
|
async start(opts) {
|
|
2670
3108
|
this.config = await loadConfig(opts?.config_path);
|
|
2671
|
-
const dotDir =
|
|
2672
|
-
if (!
|
|
2673
|
-
|
|
3109
|
+
const dotDir = resolve4(homedir3(), ".0agent");
|
|
3110
|
+
if (!existsSync4(dotDir)) {
|
|
3111
|
+
mkdirSync3(dotDir, { recursive: true });
|
|
2674
3112
|
}
|
|
2675
3113
|
this.adapter = new SQLiteAdapter({ db_path: this.config.graph.db_path });
|
|
2676
3114
|
this.graph = new KnowledgeGraph(this.adapter);
|
|
@@ -2698,7 +3136,8 @@ var ZeroAgentDaemon = class {
|
|
|
2698
3136
|
inferenceEngine: this.inferenceEngine,
|
|
2699
3137
|
eventBus: this.eventBus,
|
|
2700
3138
|
graph: this.graph,
|
|
2701
|
-
llm: llmExecutor
|
|
3139
|
+
llm: llmExecutor,
|
|
3140
|
+
cwd: process.env["ZEROAGENT_CWD"] ?? process.cwd()
|
|
2702
3141
|
});
|
|
2703
3142
|
this.backgroundWorkers = new BackgroundWorkers({
|
|
2704
3143
|
graph: this.graph,
|
|
@@ -2720,7 +3159,7 @@ var ZeroAgentDaemon = class {
|
|
|
2720
3159
|
getStatus: () => this.getStatus()
|
|
2721
3160
|
});
|
|
2722
3161
|
await this.httpServer.start();
|
|
2723
|
-
|
|
3162
|
+
writeFileSync3(this.pidFilePath, String(process.pid), "utf8");
|
|
2724
3163
|
console.log(
|
|
2725
3164
|
`[0agent] Daemon started on ${this.config.server.host}:${this.config.server.port} (PID: ${process.pid})`
|
|
2726
3165
|
);
|
|
@@ -2754,7 +3193,7 @@ var ZeroAgentDaemon = class {
|
|
|
2754
3193
|
this.graph = null;
|
|
2755
3194
|
}
|
|
2756
3195
|
this.adapter = null;
|
|
2757
|
-
if (
|
|
3196
|
+
if (existsSync4(this.pidFilePath)) {
|
|
2758
3197
|
try {
|
|
2759
3198
|
unlinkSync2(this.pidFilePath);
|
|
2760
3199
|
} catch {
|
|
@@ -2784,11 +3223,11 @@ var ZeroAgentDaemon = class {
|
|
|
2784
3223
|
};
|
|
2785
3224
|
|
|
2786
3225
|
// packages/daemon/src/start.ts
|
|
2787
|
-
import { resolve as
|
|
3226
|
+
import { resolve as resolve5 } from "node:path";
|
|
2788
3227
|
import { homedir as homedir4 } from "node:os";
|
|
2789
|
-
import { existsSync as
|
|
2790
|
-
var CONFIG_PATH = process.env["ZEROAGENT_CONFIG"] ??
|
|
2791
|
-
if (!
|
|
3228
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
3229
|
+
var CONFIG_PATH = process.env["ZEROAGENT_CONFIG"] ?? resolve5(homedir4(), ".0agent", "config.yaml");
|
|
3230
|
+
if (!existsSync5(CONFIG_PATH)) {
|
|
2792
3231
|
console.error(`
|
|
2793
3232
|
0agent is not initialised.
|
|
2794
3233
|
|