0agent 1.0.22 → 1.0.24
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 +3 -0
- package/bin/chat.js +63 -35
- package/dist/daemon.mjs +1575 -1543
- package/package.json +1 -1
package/dist/daemon.mjs
CHANGED
|
@@ -1490,7 +1490,7 @@ var init_EdgeWeightUpdater = __esm({
|
|
|
1490
1490
|
this.weightLog.append(event);
|
|
1491
1491
|
}
|
|
1492
1492
|
sleep(ms) {
|
|
1493
|
-
return new Promise((
|
|
1493
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
1494
1494
|
}
|
|
1495
1495
|
};
|
|
1496
1496
|
}
|
|
@@ -2613,7 +2613,7 @@ var init_AgentExecutor = __esm({
|
|
|
2613
2613
|
}
|
|
2614
2614
|
}
|
|
2615
2615
|
shellExec(command, timeoutMs) {
|
|
2616
|
-
return new Promise((
|
|
2616
|
+
return new Promise((resolve13) => {
|
|
2617
2617
|
const chunks = [];
|
|
2618
2618
|
const proc = spawn2("bash", ["-c", command], {
|
|
2619
2619
|
cwd: this.cwd,
|
|
@@ -2624,10 +2624,10 @@ var init_AgentExecutor = __esm({
|
|
|
2624
2624
|
proc.stderr.on("data", (d) => chunks.push(d.toString()));
|
|
2625
2625
|
proc.on("close", (code) => {
|
|
2626
2626
|
const output = chunks.join("").trim();
|
|
2627
|
-
|
|
2627
|
+
resolve13(output || (code === 0 ? "(command completed, no output)" : `exit code ${code}`));
|
|
2628
2628
|
});
|
|
2629
2629
|
proc.on("error", (err) => {
|
|
2630
|
-
|
|
2630
|
+
resolve13(`Error: ${err.message}`);
|
|
2631
2631
|
});
|
|
2632
2632
|
});
|
|
2633
2633
|
}
|
|
@@ -2933,8 +2933,8 @@ __export(ProactiveSurface_exports, {
|
|
|
2933
2933
|
ProactiveSurface: () => ProactiveSurface
|
|
2934
2934
|
});
|
|
2935
2935
|
import { execSync as execSync4 } from "node:child_process";
|
|
2936
|
-
import { existsSync as
|
|
2937
|
-
import { resolve as
|
|
2936
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11, statSync, readdirSync as readdirSync5 } from "node:fs";
|
|
2937
|
+
import { resolve as resolve10, join as join3 } from "node:path";
|
|
2938
2938
|
function readdirSafe(dir) {
|
|
2939
2939
|
try {
|
|
2940
2940
|
return readdirSync5(dir);
|
|
@@ -2983,7 +2983,7 @@ var init_ProactiveSurface = __esm({
|
|
|
2983
2983
|
return [...this.insights];
|
|
2984
2984
|
}
|
|
2985
2985
|
async poll() {
|
|
2986
|
-
if (!
|
|
2986
|
+
if (!existsSync11(resolve10(this.cwd, ".git"))) return;
|
|
2987
2987
|
const newInsights = [];
|
|
2988
2988
|
const gitInsight = this.checkGitActivity();
|
|
2989
2989
|
if (gitInsight) newInsights.push(gitInsight);
|
|
@@ -3027,13 +3027,13 @@ var init_ProactiveSurface = __esm({
|
|
|
3027
3027
|
];
|
|
3028
3028
|
for (const dir of outputPaths) {
|
|
3029
3029
|
try {
|
|
3030
|
-
if (!
|
|
3030
|
+
if (!existsSync11(dir)) continue;
|
|
3031
3031
|
const xmlFiles = readdirSafe(dir).filter((f) => f.endsWith(".xml"));
|
|
3032
3032
|
for (const xml of xmlFiles) {
|
|
3033
3033
|
const path = join3(dir, xml);
|
|
3034
3034
|
const stat = statSync(path);
|
|
3035
3035
|
if (stat.mtimeMs < this.lastPollAt) continue;
|
|
3036
|
-
const content =
|
|
3036
|
+
const content = readFileSync11(path, "utf8");
|
|
3037
3037
|
const failures = [...content.matchAll(/<failure[^>]*message="([^"]+)"/g)].length;
|
|
3038
3038
|
if (failures > 0) {
|
|
3039
3039
|
return this.makeInsight(
|
|
@@ -3088,9 +3088,9 @@ var init_ProactiveSurface = __esm({
|
|
|
3088
3088
|
|
|
3089
3089
|
// packages/daemon/src/ZeroAgentDaemon.ts
|
|
3090
3090
|
init_src();
|
|
3091
|
-
import { writeFileSync as writeFileSync7, unlinkSync as unlinkSync2, existsSync as
|
|
3092
|
-
import { resolve as
|
|
3093
|
-
import { homedir as
|
|
3091
|
+
import { writeFileSync as writeFileSync7, unlinkSync as unlinkSync2, existsSync as existsSync12, mkdirSync as mkdirSync6 } from "node:fs";
|
|
3092
|
+
import { resolve as resolve11 } from "node:path";
|
|
3093
|
+
import { homedir as homedir7 } from "node:os";
|
|
3094
3094
|
|
|
3095
3095
|
// packages/daemon/src/config/DaemonConfig.ts
|
|
3096
3096
|
import { readFileSync, existsSync } from "node:fs";
|
|
@@ -3312,1669 +3312,1701 @@ var EntityScopedContextLoader = class {
|
|
|
3312
3312
|
}
|
|
3313
3313
|
};
|
|
3314
3314
|
|
|
3315
|
-
// packages/daemon/src/
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
var BASE_URL = "https://raw.githubusercontent.com/anthropics/skills/main";
|
|
3320
|
-
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
3321
|
-
var ANTHROPIC_SKILLS = [
|
|
3322
|
-
"algorithmic-art",
|
|
3323
|
-
"brand-guidelines",
|
|
3324
|
-
"canvas-design",
|
|
3325
|
-
"claude-api",
|
|
3326
|
-
"doc-coauthoring",
|
|
3327
|
-
"docx",
|
|
3328
|
-
"frontend-design",
|
|
3329
|
-
"internal-comms",
|
|
3330
|
-
"mcp-builder",
|
|
3331
|
-
"pdf",
|
|
3332
|
-
"pptx",
|
|
3333
|
-
"skill-creator",
|
|
3334
|
-
"slack-gif-creator",
|
|
3335
|
-
"theme-factory",
|
|
3336
|
-
"web-artifacts-builder",
|
|
3337
|
-
"webapp-testing",
|
|
3338
|
-
"xlsx"
|
|
3339
|
-
];
|
|
3340
|
-
var AnthropicSkillFetcher = class {
|
|
3341
|
-
cache = /* @__PURE__ */ new Map();
|
|
3342
|
-
/**
|
|
3343
|
-
* Fetch a skill's instructions from the Anthropic repo.
|
|
3344
|
-
* Returns the Markdown body as a role_prompt string.
|
|
3345
|
-
* Caches for 1 hour.
|
|
3346
|
-
*/
|
|
3347
|
-
async fetch(skillName) {
|
|
3348
|
-
const cached = this.cache.get(skillName);
|
|
3349
|
-
if (cached && Date.now() - cached.fetched_at < CACHE_TTL_MS) {
|
|
3350
|
-
return this.parseSkillMd(skillName, cached.prompt, true);
|
|
3351
|
-
}
|
|
3352
|
-
const urls = [
|
|
3353
|
-
`${BASE_URL}/skills/${skillName}/SKILL.md`,
|
|
3354
|
-
`${BASE_URL}/${skillName}/SKILL.md`,
|
|
3355
|
-
`${BASE_URL}/skills/${skillName}/README.md`
|
|
3356
|
-
];
|
|
3357
|
-
for (const url of urls) {
|
|
3358
|
-
try {
|
|
3359
|
-
const res = await fetch(url, {
|
|
3360
|
-
headers: { "User-Agent": "0agent/1.0" },
|
|
3361
|
-
signal: AbortSignal.timeout(8e3)
|
|
3362
|
-
});
|
|
3363
|
-
if (res.ok) {
|
|
3364
|
-
const text = await res.text();
|
|
3365
|
-
this.cache.set(skillName, { prompt: text, fetched_at: Date.now() });
|
|
3366
|
-
return this.parseSkillMd(skillName, text, false);
|
|
3367
|
-
}
|
|
3368
|
-
} catch {
|
|
3369
|
-
}
|
|
3370
|
-
}
|
|
3371
|
-
return null;
|
|
3372
|
-
}
|
|
3373
|
-
/**
|
|
3374
|
-
* Parse a SKILL.md file into a FetchedSkill.
|
|
3375
|
-
* Extracts name/description from YAML frontmatter, instructions from body.
|
|
3376
|
-
*/
|
|
3377
|
-
parseSkillMd(skillName, raw, cached) {
|
|
3378
|
-
let name = skillName;
|
|
3379
|
-
let description = "";
|
|
3380
|
-
let instructions = raw;
|
|
3381
|
-
const frontmatterMatch = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
3382
|
-
if (frontmatterMatch) {
|
|
3383
|
-
const frontmatter = frontmatterMatch[1];
|
|
3384
|
-
instructions = frontmatterMatch[2].trim();
|
|
3385
|
-
const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
|
|
3386
|
-
if (nameMatch) name = nameMatch[1].trim().replace(/["']/g, "");
|
|
3387
|
-
const descMatch = frontmatter.match(/^description:\s*([\s\S]*?)(?=^\w|\Z)/m);
|
|
3388
|
-
if (descMatch) description = descMatch[1].replace(/\s+/g, " ").trim().replace(/["']/g, "");
|
|
3389
|
-
}
|
|
3390
|
-
return {
|
|
3391
|
-
name,
|
|
3392
|
-
description,
|
|
3393
|
-
instructions,
|
|
3394
|
-
source_url: `https://github.com/anthropics/skills/tree/main/skills/${skillName}`,
|
|
3395
|
-
cached
|
|
3396
|
-
};
|
|
3397
|
-
}
|
|
3398
|
-
/**
|
|
3399
|
-
* Build a system prompt that injects the Anthropic skill instructions.
|
|
3400
|
-
* This is what gets passed to the agent as context.
|
|
3401
|
-
*/
|
|
3402
|
-
buildSystemPrompt(skill) {
|
|
3403
|
-
return [
|
|
3404
|
-
`You are using the "${skill.name}" skill from Anthropic's skill library.`,
|
|
3405
|
-
``,
|
|
3406
|
-
`Skill description: ${skill.description}`,
|
|
3407
|
-
``,
|
|
3408
|
-
`Instructions:`,
|
|
3409
|
-
`---`,
|
|
3410
|
-
skill.instructions,
|
|
3411
|
-
`---`,
|
|
3412
|
-
``,
|
|
3413
|
-
`Use the tools available to you (shell_exec, file_op, web_search, scrape_url, browser_open)`,
|
|
3414
|
-
`to complete the task following these instructions.`
|
|
3415
|
-
].join("\n");
|
|
3416
|
-
}
|
|
3417
|
-
/**
|
|
3418
|
-
* List all known Anthropic skills.
|
|
3419
|
-
*/
|
|
3420
|
-
listAvailable() {
|
|
3421
|
-
return [...ANTHROPIC_SKILLS];
|
|
3422
|
-
}
|
|
3423
|
-
/**
|
|
3424
|
-
* Check if a skill name is a known Anthropic skill.
|
|
3425
|
-
*/
|
|
3426
|
-
isAnthropicSkill(name) {
|
|
3427
|
-
return ANTHROPIC_SKILLS.includes(name);
|
|
3428
|
-
}
|
|
3429
|
-
/**
|
|
3430
|
-
* Clear the cache (force re-fetch on next request).
|
|
3431
|
-
*/
|
|
3432
|
-
clearCache() {
|
|
3433
|
-
this.cache.clear();
|
|
3315
|
+
// packages/daemon/src/LLMExecutor.ts
|
|
3316
|
+
var LLMExecutor = class {
|
|
3317
|
+
constructor(config) {
|
|
3318
|
+
this.config = config;
|
|
3434
3319
|
}
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
import { execSync as execSync3 } from "node:child_process";
|
|
3439
|
-
import { readFileSync as readFileSync4, existsSync as existsSync4 } from "node:fs";
|
|
3440
|
-
import { join } from "node:path";
|
|
3441
|
-
import { createServer } from "node:net";
|
|
3442
|
-
var PORTS_TO_CHECK = [3e3, 3001, 4e3, 4200, 5e3, 5173, 8e3, 8080, 8888];
|
|
3443
|
-
var ProjectScanner = class {
|
|
3444
|
-
constructor(cwd) {
|
|
3445
|
-
this.cwd = cwd;
|
|
3320
|
+
get isConfigured() {
|
|
3321
|
+
if (this.config.provider === "ollama") return true;
|
|
3322
|
+
return !!this.config.api_key?.trim();
|
|
3446
3323
|
}
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
const
|
|
3450
|
-
|
|
3451
|
-
const runningPorts = await this.getRunningPorts();
|
|
3452
|
-
const readmeSummary = this.getReadmeSummary();
|
|
3453
|
-
return {
|
|
3454
|
-
cwd: this.cwd,
|
|
3455
|
-
stack,
|
|
3456
|
-
name,
|
|
3457
|
-
recent_commits: recentCommits,
|
|
3458
|
-
dirty_files: dirtyFiles,
|
|
3459
|
-
running_ports: runningPorts,
|
|
3460
|
-
readme_summary: readmeSummary
|
|
3461
|
-
};
|
|
3324
|
+
// ─── Single completion (no tools, no streaming) ──────────────────────────
|
|
3325
|
+
async complete(messages, system) {
|
|
3326
|
+
const res = await this.completeWithTools(messages, [], system, void 0);
|
|
3327
|
+
return { content: res.content, tokens_used: res.tokens_used, model: res.model };
|
|
3462
3328
|
}
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
}
|
|
3479
|
-
if (ctx.readme_summary) {
|
|
3480
|
-
lines.push(`README: ${ctx.readme_summary}`);
|
|
3329
|
+
// ─── Tool-calling completion with optional streaming ─────────────────────
|
|
3330
|
+
async completeWithTools(messages, tools, system, onToken) {
|
|
3331
|
+
switch (this.config.provider) {
|
|
3332
|
+
case "anthropic":
|
|
3333
|
+
return this.anthropic(messages, tools, system, onToken);
|
|
3334
|
+
case "openai":
|
|
3335
|
+
return this.openai(messages, tools, system, onToken);
|
|
3336
|
+
case "xai":
|
|
3337
|
+
return this.openai(messages, tools, system, onToken, "https://api.x.ai/v1");
|
|
3338
|
+
case "gemini":
|
|
3339
|
+
return this.openai(messages, tools, system, onToken, "https://generativelanguage.googleapis.com/v1beta/openai");
|
|
3340
|
+
case "ollama":
|
|
3341
|
+
return this.ollama(messages, system, onToken);
|
|
3342
|
+
default:
|
|
3343
|
+
return this.openai(messages, tools, system, onToken);
|
|
3481
3344
|
}
|
|
3482
|
-
return lines.join("\n");
|
|
3483
3345
|
}
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
const
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
if (deps.typescript || existsSync4(join(this.cwd, "tsconfig.json"))) stack.push("typescript");
|
|
3495
|
-
if (deps.react) stack.push("react");
|
|
3496
|
-
if (deps.vue) stack.push("vue");
|
|
3497
|
-
if (deps.svelte) stack.push("svelte");
|
|
3498
|
-
if (deps.next) stack.push("next.js");
|
|
3499
|
-
if (deps.express || deps.fastify || deps.hono) stack.push("backend");
|
|
3500
|
-
} catch {
|
|
3346
|
+
// ─── Anthropic ───────────────────────────────────────────────────────────
|
|
3347
|
+
async anthropic(messages, tools, system, onToken) {
|
|
3348
|
+
const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
|
|
3349
|
+
const filtered = messages.filter((m) => m.role !== "system");
|
|
3350
|
+
const anthropicMsgs = filtered.map((m) => {
|
|
3351
|
+
if (m.role === "tool") {
|
|
3352
|
+
return {
|
|
3353
|
+
role: "user",
|
|
3354
|
+
content: [{ type: "tool_result", tool_use_id: m.tool_call_id, content: m.content }]
|
|
3355
|
+
};
|
|
3501
3356
|
}
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3357
|
+
if (m.role === "assistant" && m.tool_calls?.length) {
|
|
3358
|
+
return {
|
|
3359
|
+
role: "assistant",
|
|
3360
|
+
content: [
|
|
3361
|
+
...m.content ? [{ type: "text", text: m.content }] : [],
|
|
3362
|
+
...m.tool_calls.map((tc) => ({
|
|
3363
|
+
type: "tool_use",
|
|
3364
|
+
id: tc.id,
|
|
3365
|
+
name: tc.name,
|
|
3366
|
+
input: tc.input
|
|
3367
|
+
}))
|
|
3368
|
+
]
|
|
3369
|
+
};
|
|
3510
3370
|
}
|
|
3371
|
+
return { role: m.role, content: m.content };
|
|
3372
|
+
});
|
|
3373
|
+
const body = {
|
|
3374
|
+
model: this.config.model,
|
|
3375
|
+
max_tokens: 8192,
|
|
3376
|
+
messages: anthropicMsgs,
|
|
3377
|
+
stream: true
|
|
3378
|
+
};
|
|
3379
|
+
if (sysContent) body.system = sysContent;
|
|
3380
|
+
if (tools.length > 0) {
|
|
3381
|
+
body.tools = tools.map((t) => ({
|
|
3382
|
+
name: t.name,
|
|
3383
|
+
description: t.description,
|
|
3384
|
+
input_schema: t.input_schema
|
|
3385
|
+
}));
|
|
3511
3386
|
}
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
}).trim();
|
|
3525
|
-
return out ? out.split("\n").map((l) => l.trim()) : [];
|
|
3526
|
-
} catch {
|
|
3527
|
-
return [];
|
|
3387
|
+
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
3388
|
+
method: "POST",
|
|
3389
|
+
headers: {
|
|
3390
|
+
"Content-Type": "application/json",
|
|
3391
|
+
"x-api-key": this.config.api_key,
|
|
3392
|
+
"anthropic-version": "2023-06-01"
|
|
3393
|
+
},
|
|
3394
|
+
body: JSON.stringify(body)
|
|
3395
|
+
});
|
|
3396
|
+
if (!res.ok) {
|
|
3397
|
+
const err = await res.text();
|
|
3398
|
+
throw new Error(`Anthropic ${res.status}: ${err}`);
|
|
3528
3399
|
}
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3400
|
+
let textContent = "";
|
|
3401
|
+
let stopReason = "end_turn";
|
|
3402
|
+
let inputTokens = 0;
|
|
3403
|
+
let outputTokens = 0;
|
|
3404
|
+
let modelName = this.config.model;
|
|
3405
|
+
const toolCalls = [];
|
|
3406
|
+
const toolInputBuffers = {};
|
|
3407
|
+
let currentToolId = "";
|
|
3408
|
+
const reader = res.body.getReader();
|
|
3409
|
+
const decoder = new TextDecoder();
|
|
3410
|
+
let buf = "";
|
|
3411
|
+
while (true) {
|
|
3412
|
+
const { done, value } = await reader.read();
|
|
3413
|
+
if (done) break;
|
|
3414
|
+
buf += decoder.decode(value, { stream: true });
|
|
3415
|
+
const lines = buf.split("\n");
|
|
3416
|
+
buf = lines.pop() ?? "";
|
|
3417
|
+
for (const line of lines) {
|
|
3418
|
+
if (!line.startsWith("data: ")) continue;
|
|
3419
|
+
const data = line.slice(6).trim();
|
|
3420
|
+
if (data === "[DONE]" || data === "") continue;
|
|
3421
|
+
let evt;
|
|
3422
|
+
try {
|
|
3423
|
+
evt = JSON.parse(data);
|
|
3424
|
+
} catch {
|
|
3425
|
+
continue;
|
|
3426
|
+
}
|
|
3427
|
+
const type = evt.type;
|
|
3428
|
+
if (type === "message_start") {
|
|
3429
|
+
const usage = evt.message?.usage;
|
|
3430
|
+
inputTokens = usage?.input_tokens ?? 0;
|
|
3431
|
+
modelName = evt.message?.model ?? modelName;
|
|
3432
|
+
} else if (type === "content_block_start") {
|
|
3433
|
+
const block = evt.content_block;
|
|
3434
|
+
if (block?.type === "tool_use") {
|
|
3435
|
+
currentToolId = block.id;
|
|
3436
|
+
toolInputBuffers[currentToolId] = "";
|
|
3437
|
+
toolCalls.push({ id: currentToolId, name: block.name, input: {} });
|
|
3438
|
+
}
|
|
3439
|
+
} else if (type === "content_block_delta") {
|
|
3440
|
+
const delta = evt.delta;
|
|
3441
|
+
if (delta?.type === "text_delta") {
|
|
3442
|
+
const token = delta.text ?? "";
|
|
3443
|
+
textContent += token;
|
|
3444
|
+
if (onToken && token) onToken(token);
|
|
3445
|
+
} else if (delta?.type === "input_json_delta") {
|
|
3446
|
+
toolInputBuffers[currentToolId] = (toolInputBuffers[currentToolId] ?? "") + (delta.partial_json ?? "");
|
|
3447
|
+
}
|
|
3448
|
+
} else if (type === "content_block_stop") {
|
|
3449
|
+
if (currentToolId && toolInputBuffers[currentToolId]) {
|
|
3450
|
+
const tc = toolCalls.find((t) => t.id === currentToolId);
|
|
3451
|
+
if (tc) {
|
|
3452
|
+
try {
|
|
3453
|
+
tc.input = JSON.parse(toolInputBuffers[currentToolId]);
|
|
3454
|
+
} catch {
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
} else if (type === "message_delta") {
|
|
3459
|
+
const usage = evt.usage;
|
|
3460
|
+
outputTokens = usage?.output_tokens ?? 0;
|
|
3461
|
+
const stop = evt.delta?.stop_reason;
|
|
3462
|
+
if (stop === "tool_use") stopReason = "tool_use";
|
|
3463
|
+
else if (stop === "end_turn") stopReason = "end_turn";
|
|
3464
|
+
else if (stop === "max_tokens") stopReason = "max_tokens";
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3540
3467
|
}
|
|
3468
|
+
return {
|
|
3469
|
+
content: textContent,
|
|
3470
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : null,
|
|
3471
|
+
stop_reason: stopReason,
|
|
3472
|
+
tokens_used: inputTokens + outputTokens,
|
|
3473
|
+
model: modelName
|
|
3474
|
+
};
|
|
3541
3475
|
}
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3476
|
+
// ─── OpenAI (also xAI, Gemini) ───────────────────────────────────────────
|
|
3477
|
+
async openai(messages, tools, system, onToken, baseUrl = "https://api.openai.com/v1") {
|
|
3478
|
+
const allMessages = [];
|
|
3479
|
+
const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
|
|
3480
|
+
if (sysContent) allMessages.push({ role: "system", content: sysContent });
|
|
3481
|
+
for (const m of messages.filter((m2) => m2.role !== "system")) {
|
|
3482
|
+
if (m.role === "tool") {
|
|
3483
|
+
allMessages.push({ role: "tool", tool_call_id: m.tool_call_id, content: m.content });
|
|
3484
|
+
} else if (m.role === "assistant" && m.tool_calls?.length) {
|
|
3485
|
+
allMessages.push({
|
|
3486
|
+
role: "assistant",
|
|
3487
|
+
content: m.content || null,
|
|
3488
|
+
tool_calls: m.tool_calls.map((tc) => ({
|
|
3489
|
+
id: tc.id,
|
|
3490
|
+
type: "function",
|
|
3491
|
+
function: { name: tc.name, arguments: JSON.stringify(tc.input) }
|
|
3492
|
+
}))
|
|
3554
3493
|
});
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3494
|
+
} else {
|
|
3495
|
+
allMessages.push({ role: m.role, content: m.content });
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
const body = {
|
|
3499
|
+
model: this.config.model,
|
|
3500
|
+
messages: allMessages,
|
|
3501
|
+
max_tokens: 8192,
|
|
3502
|
+
stream: true,
|
|
3503
|
+
stream_options: { include_usage: true }
|
|
3504
|
+
};
|
|
3505
|
+
if (tools.length > 0) {
|
|
3506
|
+
body.tools = tools.map((t) => ({
|
|
3507
|
+
type: "function",
|
|
3508
|
+
function: { name: t.name, description: t.description, parameters: t.input_schema }
|
|
3509
|
+
}));
|
|
3510
|
+
}
|
|
3511
|
+
const res = await fetch(`${this.config.base_url ?? baseUrl}/chat/completions`, {
|
|
3512
|
+
method: "POST",
|
|
3513
|
+
headers: {
|
|
3514
|
+
"Content-Type": "application/json",
|
|
3515
|
+
"Authorization": `Bearer ${this.config.api_key}`
|
|
3516
|
+
},
|
|
3517
|
+
body: JSON.stringify(body)
|
|
3518
|
+
});
|
|
3519
|
+
if (!res.ok) {
|
|
3520
|
+
const err = await res.text();
|
|
3521
|
+
throw new Error(`OpenAI ${res.status}: ${err}`);
|
|
3522
|
+
}
|
|
3523
|
+
let textContent = "";
|
|
3524
|
+
let tokensUsed = 0;
|
|
3525
|
+
let modelName = this.config.model;
|
|
3526
|
+
let stopReason = "end_turn";
|
|
3527
|
+
const toolCallMap = {};
|
|
3528
|
+
const reader = res.body.getReader();
|
|
3529
|
+
const decoder = new TextDecoder();
|
|
3530
|
+
let buf = "";
|
|
3531
|
+
while (true) {
|
|
3532
|
+
const { done, value } = await reader.read();
|
|
3533
|
+
if (done) break;
|
|
3534
|
+
buf += decoder.decode(value, { stream: true });
|
|
3535
|
+
const lines = buf.split("\n");
|
|
3536
|
+
buf = lines.pop() ?? "";
|
|
3537
|
+
for (const line of lines) {
|
|
3538
|
+
if (!line.startsWith("data: ")) continue;
|
|
3539
|
+
const data = line.slice(6).trim();
|
|
3540
|
+
if (data === "[DONE]") continue;
|
|
3541
|
+
let evt;
|
|
3567
3542
|
try {
|
|
3568
|
-
|
|
3543
|
+
evt = JSON.parse(data);
|
|
3569
3544
|
} catch {
|
|
3545
|
+
continue;
|
|
3546
|
+
}
|
|
3547
|
+
modelName = evt.model ?? modelName;
|
|
3548
|
+
const usage = evt.usage;
|
|
3549
|
+
if (usage?.total_tokens) tokensUsed = usage.total_tokens;
|
|
3550
|
+
const choices = evt.choices;
|
|
3551
|
+
if (!choices?.length) continue;
|
|
3552
|
+
const delta = choices[0].delta;
|
|
3553
|
+
if (!delta) continue;
|
|
3554
|
+
const finish = choices[0].finish_reason;
|
|
3555
|
+
if (finish === "tool_calls") stopReason = "tool_use";
|
|
3556
|
+
else if (finish === "stop") stopReason = "end_turn";
|
|
3557
|
+
const token = delta.content;
|
|
3558
|
+
if (token) {
|
|
3559
|
+
textContent += token;
|
|
3560
|
+
if (onToken) onToken(token);
|
|
3561
|
+
}
|
|
3562
|
+
const toolCallDeltas = delta.tool_calls;
|
|
3563
|
+
if (toolCallDeltas) {
|
|
3564
|
+
for (const tc of toolCallDeltas) {
|
|
3565
|
+
const idx = tc.index;
|
|
3566
|
+
if (!toolCallMap[idx]) {
|
|
3567
|
+
toolCallMap[idx] = { id: "", name: "", args: "" };
|
|
3568
|
+
}
|
|
3569
|
+
const fn = tc.function;
|
|
3570
|
+
if (tc.id) toolCallMap[idx].id = tc.id;
|
|
3571
|
+
if (fn?.name) toolCallMap[idx].name = fn.name;
|
|
3572
|
+
if (fn?.arguments) toolCallMap[idx].args += fn.arguments;
|
|
3573
|
+
}
|
|
3570
3574
|
}
|
|
3571
3575
|
}
|
|
3572
3576
|
}
|
|
3573
|
-
|
|
3577
|
+
const toolCalls = Object.values(toolCallMap).filter((tc) => tc.id && tc.name).map((tc) => {
|
|
3578
|
+
let input = {};
|
|
3579
|
+
try {
|
|
3580
|
+
input = JSON.parse(tc.args);
|
|
3581
|
+
} catch {
|
|
3582
|
+
}
|
|
3583
|
+
return { id: tc.id, name: tc.name, input };
|
|
3584
|
+
});
|
|
3585
|
+
return {
|
|
3586
|
+
content: textContent,
|
|
3587
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : null,
|
|
3588
|
+
stop_reason: stopReason,
|
|
3589
|
+
tokens_used: tokensUsed,
|
|
3590
|
+
model: modelName
|
|
3591
|
+
};
|
|
3574
3592
|
}
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
this.adapter = adapter;
|
|
3592
|
-
}
|
|
3593
|
-
initialised = false;
|
|
3594
|
-
init() {
|
|
3595
|
-
if (this.initialised) return;
|
|
3596
|
-
this.adapter.db.exec(CREATE_TABLE);
|
|
3597
|
-
this.initialised = true;
|
|
3598
|
-
}
|
|
3599
|
-
append(msg) {
|
|
3600
|
-
this.init();
|
|
3601
|
-
const db = this.adapter.db;
|
|
3602
|
-
db.prepare(
|
|
3603
|
-
`INSERT INTO conversations (id, session_id, user_entity_id, role, content, created_at)
|
|
3604
|
-
VALUES (?, ?, ?, ?, ?, ?)`
|
|
3605
|
-
).run(msg.id, msg.session_id, msg.user_entity_id, msg.role, msg.content, msg.created_at);
|
|
3606
|
-
}
|
|
3607
|
-
getHistory(userEntityId, limit = 20) {
|
|
3608
|
-
this.init();
|
|
3609
|
-
const db = this.adapter.db;
|
|
3610
|
-
const rows = db.prepare(
|
|
3611
|
-
`SELECT * FROM conversations WHERE user_entity_id = ?
|
|
3612
|
-
ORDER BY created_at DESC LIMIT ?`
|
|
3613
|
-
).all(userEntityId, limit);
|
|
3614
|
-
return rows.reverse();
|
|
3615
|
-
}
|
|
3616
|
-
/**
|
|
3617
|
-
* Build conversation history as LLM messages for context injection.
|
|
3618
|
-
*/
|
|
3619
|
-
buildContextMessages(userEntityId, limit = 10) {
|
|
3620
|
-
return this.getHistory(userEntityId, limit).map((m) => ({
|
|
3621
|
-
role: m.role,
|
|
3622
|
-
content: m.content
|
|
3623
|
-
}));
|
|
3624
|
-
}
|
|
3625
|
-
clearHistory(userEntityId) {
|
|
3626
|
-
this.init();
|
|
3627
|
-
const db = this.adapter.db;
|
|
3628
|
-
db.prepare(`DELETE FROM conversations WHERE user_entity_id = ?`).run(userEntityId);
|
|
3593
|
+
// ─── Ollama (no streaming for simplicity) ────────────────────────────────
|
|
3594
|
+
async ollama(messages, system, onToken) {
|
|
3595
|
+
const baseUrl = this.config.base_url ?? "http://localhost:11434";
|
|
3596
|
+
const allMessages = [];
|
|
3597
|
+
const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
|
|
3598
|
+
if (sysContent) allMessages.push({ role: "system", content: sysContent });
|
|
3599
|
+
allMessages.push(...messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content })));
|
|
3600
|
+
const res = await fetch(`${baseUrl}/api/chat`, {
|
|
3601
|
+
method: "POST",
|
|
3602
|
+
headers: { "Content-Type": "application/json" },
|
|
3603
|
+
body: JSON.stringify({ model: this.config.model, messages: allMessages, stream: false })
|
|
3604
|
+
});
|
|
3605
|
+
if (!res.ok) throw new Error(`Ollama error ${res.status}`);
|
|
3606
|
+
const data = await res.json();
|
|
3607
|
+
if (onToken) onToken(data.message.content);
|
|
3608
|
+
return { content: data.message.content, tool_calls: null, stop_reason: "end_turn", tokens_used: data.eval_count ?? 0, model: this.config.model };
|
|
3629
3609
|
}
|
|
3630
3610
|
};
|
|
3631
3611
|
|
|
3632
3612
|
// packages/daemon/src/SessionManager.ts
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
}
|
|
3660
|
-
/**
|
|
3661
|
-
* Create a new session with status 'pending'.
|
|
3662
|
-
*/
|
|
3663
|
-
createSession(req) {
|
|
3664
|
-
const id = crypto.randomUUID();
|
|
3665
|
-
const session = {
|
|
3666
|
-
id,
|
|
3667
|
-
task: req.task,
|
|
3668
|
-
skill: req.skill,
|
|
3669
|
-
entity_id: req.entity_id,
|
|
3670
|
-
status: "pending",
|
|
3671
|
-
created_at: Date.now(),
|
|
3672
|
-
steps: []
|
|
3673
|
-
};
|
|
3674
|
-
this.sessions.set(id, session);
|
|
3675
|
-
return session;
|
|
3676
|
-
}
|
|
3613
|
+
init_AgentExecutor();
|
|
3614
|
+
|
|
3615
|
+
// packages/daemon/src/AnthropicSkillFetcher.ts
|
|
3616
|
+
var BASE_URL = "https://raw.githubusercontent.com/anthropics/skills/main";
|
|
3617
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
3618
|
+
var ANTHROPIC_SKILLS = [
|
|
3619
|
+
"algorithmic-art",
|
|
3620
|
+
"brand-guidelines",
|
|
3621
|
+
"canvas-design",
|
|
3622
|
+
"claude-api",
|
|
3623
|
+
"doc-coauthoring",
|
|
3624
|
+
"docx",
|
|
3625
|
+
"frontend-design",
|
|
3626
|
+
"internal-comms",
|
|
3627
|
+
"mcp-builder",
|
|
3628
|
+
"pdf",
|
|
3629
|
+
"pptx",
|
|
3630
|
+
"skill-creator",
|
|
3631
|
+
"slack-gif-creator",
|
|
3632
|
+
"theme-factory",
|
|
3633
|
+
"web-artifacts-builder",
|
|
3634
|
+
"webapp-testing",
|
|
3635
|
+
"xlsx"
|
|
3636
|
+
];
|
|
3637
|
+
var AnthropicSkillFetcher = class {
|
|
3638
|
+
cache = /* @__PURE__ */ new Map();
|
|
3677
3639
|
/**
|
|
3678
|
-
*
|
|
3640
|
+
* Fetch a skill's instructions from the Anthropic repo.
|
|
3641
|
+
* Returns the Markdown body as a role_prompt string.
|
|
3642
|
+
* Caches for 1 hour.
|
|
3679
3643
|
*/
|
|
3680
|
-
async
|
|
3681
|
-
const
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3644
|
+
async fetch(skillName) {
|
|
3645
|
+
const cached = this.cache.get(skillName);
|
|
3646
|
+
if (cached && Date.now() - cached.fetched_at < CACHE_TTL_MS) {
|
|
3647
|
+
return this.parseSkillMd(skillName, cached.prompt, true);
|
|
3648
|
+
}
|
|
3649
|
+
const urls = [
|
|
3650
|
+
`${BASE_URL}/skills/${skillName}/SKILL.md`,
|
|
3651
|
+
`${BASE_URL}/${skillName}/SKILL.md`,
|
|
3652
|
+
`${BASE_URL}/skills/${skillName}/README.md`
|
|
3653
|
+
];
|
|
3654
|
+
for (const url of urls) {
|
|
3690
3655
|
try {
|
|
3691
|
-
const
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3656
|
+
const res = await fetch(url, {
|
|
3657
|
+
headers: { "User-Agent": "0agent/1.0" },
|
|
3658
|
+
signal: AbortSignal.timeout(8e3)
|
|
3659
|
+
});
|
|
3660
|
+
if (res.ok) {
|
|
3661
|
+
const text = await res.text();
|
|
3662
|
+
this.cache.set(skillName, { prompt: text, fetched_at: Date.now() });
|
|
3663
|
+
return this.parseSkillMd(skillName, text, false);
|
|
3695
3664
|
}
|
|
3696
3665
|
} catch {
|
|
3697
3666
|
}
|
|
3698
3667
|
}
|
|
3699
|
-
return
|
|
3668
|
+
return null;
|
|
3700
3669
|
}
|
|
3701
3670
|
/**
|
|
3702
|
-
*
|
|
3671
|
+
* Parse a SKILL.md file into a FetchedSkill.
|
|
3672
|
+
* Extracts name/description from YAML frontmatter, instructions from body.
|
|
3703
3673
|
*/
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3674
|
+
parseSkillMd(skillName, raw, cached) {
|
|
3675
|
+
let name = skillName;
|
|
3676
|
+
let description = "";
|
|
3677
|
+
let instructions = raw;
|
|
3678
|
+
const frontmatterMatch = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
3679
|
+
if (frontmatterMatch) {
|
|
3680
|
+
const frontmatter = frontmatterMatch[1];
|
|
3681
|
+
instructions = frontmatterMatch[2].trim();
|
|
3682
|
+
const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
|
|
3683
|
+
if (nameMatch) name = nameMatch[1].trim().replace(/["']/g, "");
|
|
3684
|
+
const descMatch = frontmatter.match(/^description:\s*([\s\S]*?)(?=^\w|\Z)/m);
|
|
3685
|
+
if (descMatch) description = descMatch[1].replace(/\s+/g, " ").trim().replace(/["']/g, "");
|
|
3686
|
+
}
|
|
3687
|
+
return {
|
|
3688
|
+
name,
|
|
3708
3689
|
description,
|
|
3709
|
-
|
|
3710
|
-
|
|
3690
|
+
instructions,
|
|
3691
|
+
source_url: `https://github.com/anthropics/skills/tree/main/skills/${skillName}`,
|
|
3692
|
+
cached
|
|
3711
3693
|
};
|
|
3712
|
-
session.steps.push(step);
|
|
3713
|
-
this.emit({
|
|
3714
|
-
type: "session.step",
|
|
3715
|
-
session_id: session.id,
|
|
3716
|
-
step: description,
|
|
3717
|
-
result: result ?? null
|
|
3718
|
-
});
|
|
3719
|
-
return step;
|
|
3720
3694
|
}
|
|
3721
3695
|
/**
|
|
3722
|
-
*
|
|
3696
|
+
* Build a system prompt that injects the Anthropic skill instructions.
|
|
3697
|
+
* This is what gets passed to the agent as context.
|
|
3723
3698
|
*/
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3699
|
+
buildSystemPrompt(skill) {
|
|
3700
|
+
return [
|
|
3701
|
+
`You are using the "${skill.name}" skill from Anthropic's skill library.`,
|
|
3702
|
+
``,
|
|
3703
|
+
`Skill description: ${skill.description}`,
|
|
3704
|
+
``,
|
|
3705
|
+
`Instructions:`,
|
|
3706
|
+
`---`,
|
|
3707
|
+
skill.instructions,
|
|
3708
|
+
`---`,
|
|
3709
|
+
``,
|
|
3710
|
+
`Use the tools available to you (shell_exec, file_op, web_search, scrape_url, browser_open)`,
|
|
3711
|
+
`to complete the task following these instructions.`
|
|
3712
|
+
].join("\n");
|
|
3735
3713
|
}
|
|
3736
3714
|
/**
|
|
3737
|
-
*
|
|
3715
|
+
* List all known Anthropic skills.
|
|
3738
3716
|
*/
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
session.status = "failed";
|
|
3742
|
-
session.completed_at = Date.now();
|
|
3743
|
-
session.error = error;
|
|
3744
|
-
this.emit({
|
|
3745
|
-
type: "session.failed",
|
|
3746
|
-
session_id: session.id,
|
|
3747
|
-
error
|
|
3748
|
-
});
|
|
3749
|
-
return session;
|
|
3717
|
+
listAvailable() {
|
|
3718
|
+
return [...ANTHROPIC_SKILLS];
|
|
3750
3719
|
}
|
|
3751
3720
|
/**
|
|
3752
|
-
*
|
|
3721
|
+
* Check if a skill name is a known Anthropic skill.
|
|
3753
3722
|
*/
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
session.status = "cancelled";
|
|
3757
|
-
session.completed_at = Date.now();
|
|
3758
|
-
session.error = "cancelled";
|
|
3759
|
-
this.emit({
|
|
3760
|
-
type: "session.failed",
|
|
3761
|
-
session_id: session.id,
|
|
3762
|
-
error: "cancelled"
|
|
3763
|
-
});
|
|
3764
|
-
return session;
|
|
3723
|
+
isAnthropicSkill(name) {
|
|
3724
|
+
return ANTHROPIC_SKILLS.includes(name);
|
|
3765
3725
|
}
|
|
3766
3726
|
/**
|
|
3767
|
-
*
|
|
3727
|
+
* Clear the cache (force re-fetch on next request).
|
|
3768
3728
|
*/
|
|
3769
|
-
|
|
3770
|
-
|
|
3729
|
+
clearCache() {
|
|
3730
|
+
this.cache.clear();
|
|
3771
3731
|
}
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3732
|
+
};
|
|
3733
|
+
|
|
3734
|
+
// packages/daemon/src/ProjectScanner.ts
|
|
3735
|
+
import { execSync as execSync3 } from "node:child_process";
|
|
3736
|
+
import { readFileSync as readFileSync4, existsSync as existsSync4 } from "node:fs";
|
|
3737
|
+
import { join } from "node:path";
|
|
3738
|
+
import { createServer } from "node:net";
|
|
3739
|
+
var PORTS_TO_CHECK = [3e3, 3001, 4e3, 4200, 5e3, 5173, 8e3, 8080, 8888];
|
|
3740
|
+
var ProjectScanner = class {
|
|
3741
|
+
constructor(cwd) {
|
|
3742
|
+
this.cwd = cwd;
|
|
3743
|
+
}
|
|
3744
|
+
async scan() {
|
|
3745
|
+
const [stack, name] = this.detectStack();
|
|
3746
|
+
const recentCommits = this.getRecentCommits();
|
|
3747
|
+
const dirtyFiles = this.getDirtyFiles();
|
|
3748
|
+
const runningPorts = await this.getRunningPorts();
|
|
3749
|
+
const readmeSummary = this.getReadmeSummary();
|
|
3750
|
+
return {
|
|
3751
|
+
cwd: this.cwd,
|
|
3752
|
+
stack,
|
|
3753
|
+
name,
|
|
3754
|
+
recent_commits: recentCommits,
|
|
3755
|
+
dirty_files: dirtyFiles,
|
|
3756
|
+
running_ports: runningPorts,
|
|
3757
|
+
readme_summary: readmeSummary
|
|
3758
|
+
};
|
|
3779
3759
|
}
|
|
3780
3760
|
/**
|
|
3781
|
-
*
|
|
3782
|
-
* Wraps everything in try/catch to fail gracefully.
|
|
3761
|
+
* Build a compact system prompt injection from the context.
|
|
3783
3762
|
*/
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
if (
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
enrichedReq = {
|
|
3791
|
-
...req,
|
|
3792
|
-
context: {
|
|
3793
|
-
...req.context,
|
|
3794
|
-
system_context: [
|
|
3795
|
-
scopedCtx.personality_prompt,
|
|
3796
|
-
...req.context?.system_context != null ? [String(req.context.system_context)] : []
|
|
3797
|
-
].join("\n\n"),
|
|
3798
|
-
entity_label: scopedCtx.entity_label,
|
|
3799
|
-
parent_entity_labels: scopedCtx.parent_entity_labels
|
|
3800
|
-
}
|
|
3801
|
-
};
|
|
3802
|
-
}
|
|
3763
|
+
static buildContextPrompt(ctx) {
|
|
3764
|
+
const lines = [`Working directory: ${ctx.cwd}`];
|
|
3765
|
+
if (ctx.name) lines.push(`Project: ${ctx.name}`);
|
|
3766
|
+
if (ctx.stack.length) lines.push(`Stack: ${ctx.stack.join(", ")}`);
|
|
3767
|
+
if (ctx.recent_commits.length) {
|
|
3768
|
+
lines.push(`Recent commits: ${ctx.recent_commits.slice(0, 3).join(" | ")}`);
|
|
3803
3769
|
}
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
await this.startSession(session.id);
|
|
3807
|
-
this.addStep(session.id, `Extracting entities from: "${req.task.slice(0, 60)}${req.task.length > 60 ? "\u2026" : ""}"`);
|
|
3808
|
-
this.addStep(session.id, "Querying knowledge graph (structural + semantic)\u2026");
|
|
3809
|
-
if (session.plan) {
|
|
3810
|
-
const edge = session.plan.selected_edge;
|
|
3811
|
-
if (edge) {
|
|
3812
|
-
this.addStep(
|
|
3813
|
-
session.id,
|
|
3814
|
-
`Selected plan: ${edge.from_label} \u2192 ${edge.to_label} (weight: ${edge.weight.toFixed(2)}, mode: ${edge.mode})`,
|
|
3815
|
-
session.plan
|
|
3816
|
-
);
|
|
3817
|
-
} else {
|
|
3818
|
-
this.addStep(session.id, `No prior plan found \u2014 bootstrapping from scratch`, session.plan);
|
|
3819
|
-
}
|
|
3820
|
-
if (session.plan.skill) {
|
|
3821
|
-
this.addStep(session.id, `Matched skill: /${session.plan.skill}`);
|
|
3822
|
-
}
|
|
3823
|
-
} else {
|
|
3824
|
-
this.addStep(session.id, "No inference engine connected \u2014 executing task directly");
|
|
3825
|
-
}
|
|
3826
|
-
let anthropicContext;
|
|
3827
|
-
if (enrichedReq.skill && this.anthropicFetcher.isAnthropicSkill(enrichedReq.skill)) {
|
|
3828
|
-
this.addStep(session.id, `Fetching skill instructions: ${enrichedReq.skill}`);
|
|
3829
|
-
const fetched = await this.anthropicFetcher.fetch(enrichedReq.skill);
|
|
3830
|
-
if (fetched) {
|
|
3831
|
-
anthropicContext = this.anthropicFetcher.buildSystemPrompt(fetched);
|
|
3832
|
-
this.addStep(session.id, `Loaded skill: ${fetched.name} (${fetched.cached ? "cached" : "fresh"})`);
|
|
3833
|
-
}
|
|
3834
|
-
}
|
|
3835
|
-
if (this.llm?.isConfigured) {
|
|
3836
|
-
const executor = new AgentExecutor(
|
|
3837
|
-
this.llm,
|
|
3838
|
-
{ cwd: this.cwd },
|
|
3839
|
-
// step callback → emit session.step events
|
|
3840
|
-
(step) => this.addStep(session.id, step),
|
|
3841
|
-
// token callback → emit session.token events
|
|
3842
|
-
(token) => this.emit({ type: "session.token", session_id: session.id, token })
|
|
3843
|
-
);
|
|
3844
|
-
const identityContext = this.identity ? `You are talking to ${this.identity.name} (device: ${this.identity.device_id}, timezone: ${this.identity.timezone}).` : void 0;
|
|
3845
|
-
const projectCtx = this.projectContext ? ProjectScanner.buildContextPrompt(this.projectContext) : void 0;
|
|
3846
|
-
const userEntityId = enrichedReq.entity_id ?? this.identity?.entity_node_id;
|
|
3847
|
-
let conversationHistory;
|
|
3848
|
-
if (this.conversationStore && userEntityId) {
|
|
3849
|
-
const history = this.conversationStore.buildContextMessages(userEntityId, 8);
|
|
3850
|
-
if (history.length > 0) {
|
|
3851
|
-
const historyStr = history.map((m) => `${m.role === "user" ? "User" : "Agent"}: ${m.content.slice(0, 400)}`).join("\n");
|
|
3852
|
-
conversationHistory = `CONVERSATION HISTORY (use this for context on follow-up requests):
|
|
3853
|
-
${historyStr}
|
|
3854
|
-
|
|
3855
|
-
Current task:`;
|
|
3856
|
-
}
|
|
3857
|
-
}
|
|
3858
|
-
const systemContext = [
|
|
3859
|
-
identityContext,
|
|
3860
|
-
projectCtx,
|
|
3861
|
-
conversationHistory,
|
|
3862
|
-
anthropicContext,
|
|
3863
|
-
enrichedReq.context?.system_context ? String(enrichedReq.context.system_context) : void 0
|
|
3864
|
-
].filter(Boolean).join("\n\n") || void 0;
|
|
3865
|
-
let agentResult;
|
|
3866
|
-
try {
|
|
3867
|
-
const { SelfHealLoop: SelfHealLoop2 } = await Promise.resolve().then(() => (init_SelfHealLoop(), SelfHealLoop_exports));
|
|
3868
|
-
const healLoop = new SelfHealLoop2(
|
|
3869
|
-
this.llm,
|
|
3870
|
-
{ cwd: this.cwd },
|
|
3871
|
-
(step) => this.addStep(session.id, step),
|
|
3872
|
-
(token) => this.emit({ type: "session.token", session_id: session.id, token })
|
|
3873
|
-
);
|
|
3874
|
-
agentResult = await healLoop.executeWithHealing(enrichedReq.task, systemContext);
|
|
3875
|
-
} catch {
|
|
3876
|
-
agentResult = await executor.execute(enrichedReq.task, systemContext);
|
|
3877
|
-
}
|
|
3878
|
-
if (this.conversationStore && userEntityId) {
|
|
3879
|
-
const sessionId = session.id;
|
|
3880
|
-
const now = Date.now();
|
|
3881
|
-
this.conversationStore.append({
|
|
3882
|
-
id: crypto.randomUUID(),
|
|
3883
|
-
session_id: sessionId,
|
|
3884
|
-
user_entity_id: userEntityId,
|
|
3885
|
-
role: "user",
|
|
3886
|
-
content: enrichedReq.task,
|
|
3887
|
-
created_at: now
|
|
3888
|
-
});
|
|
3889
|
-
this.conversationStore.append({
|
|
3890
|
-
id: crypto.randomUUID(),
|
|
3891
|
-
session_id: sessionId,
|
|
3892
|
-
user_entity_id: userEntityId,
|
|
3893
|
-
role: "assistant",
|
|
3894
|
-
content: agentResult.output.slice(0, 1e3),
|
|
3895
|
-
// cap stored output length
|
|
3896
|
-
created_at: now + 1
|
|
3897
|
-
});
|
|
3898
|
-
}
|
|
3899
|
-
const selectedEdgeId = session.plan?.selected_edge?.edge_id;
|
|
3900
|
-
if (selectedEdgeId && this.weightUpdater && this.graph) {
|
|
3901
|
-
const outcomeSignal = this.computeOutcomeSignal(agentResult);
|
|
3902
|
-
if (outcomeSignal !== 0) {
|
|
3903
|
-
const edge = this.graph.getEdge(selectedEdgeId);
|
|
3904
|
-
if (edge && !edge.locked) {
|
|
3905
|
-
const newWeight = Math.max(0, Math.min(
|
|
3906
|
-
1,
|
|
3907
|
-
edge.weight + outcomeSignal * 0.1
|
|
3908
|
-
// learning rate 0.1
|
|
3909
|
-
));
|
|
3910
|
-
await this.weightUpdater.update(
|
|
3911
|
-
edge.id,
|
|
3912
|
-
edge.weight,
|
|
3913
|
-
newWeight,
|
|
3914
|
-
outcomeSignal > 0 ? "task_outcome_positive" : "task_outcome_negative",
|
|
3915
|
-
session.id
|
|
3916
|
-
);
|
|
3917
|
-
this.emit({ type: "graph.weight_updated", edge_id: edge.id, old_weight: edge.weight, new_weight: newWeight });
|
|
3918
|
-
}
|
|
3919
|
-
}
|
|
3920
|
-
}
|
|
3921
|
-
if (agentResult.files_written.length > 0) {
|
|
3922
|
-
this.addStep(session.id, `Files written: ${agentResult.files_written.join(", ")}`);
|
|
3923
|
-
}
|
|
3924
|
-
if (agentResult.commands_run.length > 0) {
|
|
3925
|
-
this.addStep(session.id, `Commands run: ${agentResult.commands_run.length}`);
|
|
3926
|
-
}
|
|
3927
|
-
this.addStep(session.id, `Done (${agentResult.tokens_used} tokens, ${agentResult.iterations} LLM turns)`);
|
|
3928
|
-
this.completeSession(session.id, {
|
|
3929
|
-
output: agentResult.output,
|
|
3930
|
-
files_written: agentResult.files_written,
|
|
3931
|
-
commands_run: agentResult.commands_run,
|
|
3932
|
-
tokens_used: agentResult.tokens_used,
|
|
3933
|
-
model: agentResult.model
|
|
3934
|
-
});
|
|
3935
|
-
} else {
|
|
3936
|
-
const output = session.plan?.reasoning ?? "No LLM configured \u2014 add api_key to ~/.0agent/config.yaml";
|
|
3937
|
-
this.addStep(session.id, "No LLM API key configured");
|
|
3938
|
-
this.completeSession(session.id, { output });
|
|
3939
|
-
}
|
|
3940
|
-
} catch (err) {
|
|
3941
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3942
|
-
this.failSession(session.id, message);
|
|
3770
|
+
if (ctx.dirty_files.length) {
|
|
3771
|
+
lines.push(`Uncommitted changes: ${ctx.dirty_files.slice(0, 5).join(", ")}`);
|
|
3943
3772
|
}
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
/**
|
|
3947
|
-
* Return the number of active (running) sessions.
|
|
3948
|
-
*/
|
|
3949
|
-
activeSessionCount() {
|
|
3950
|
-
let count = 0;
|
|
3951
|
-
for (const s of this.sessions.values()) {
|
|
3952
|
-
if (s.status === "running") count++;
|
|
3773
|
+
if (ctx.running_ports.length) {
|
|
3774
|
+
lines.push(`Running servers: ports ${ctx.running_ports.join(", ")}`);
|
|
3953
3775
|
}
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
// ─── Private helpers ───────────────────────────────
|
|
3957
|
-
getSessionOrThrow(id) {
|
|
3958
|
-
const session = this.sessions.get(id);
|
|
3959
|
-
if (!session) {
|
|
3960
|
-
throw new Error(`Session not found: ${id}`);
|
|
3776
|
+
if (ctx.readme_summary) {
|
|
3777
|
+
lines.push(`README: ${ctx.readme_summary}`);
|
|
3961
3778
|
}
|
|
3962
|
-
return
|
|
3779
|
+
return lines.join("\n");
|
|
3963
3780
|
}
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3781
|
+
detectStack() {
|
|
3782
|
+
const stack = [];
|
|
3783
|
+
let name = "";
|
|
3784
|
+
const pkgPath = join(this.cwd, "package.json");
|
|
3785
|
+
if (existsSync4(pkgPath)) {
|
|
3786
|
+
try {
|
|
3787
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
3788
|
+
name = pkg.name ?? "";
|
|
3789
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3790
|
+
stack.push("node");
|
|
3791
|
+
if (deps.typescript || existsSync4(join(this.cwd, "tsconfig.json"))) stack.push("typescript");
|
|
3792
|
+
if (deps.react) stack.push("react");
|
|
3793
|
+
if (deps.vue) stack.push("vue");
|
|
3794
|
+
if (deps.svelte) stack.push("svelte");
|
|
3795
|
+
if (deps.next) stack.push("next.js");
|
|
3796
|
+
if (deps.express || deps.fastify || deps.hono) stack.push("backend");
|
|
3797
|
+
} catch {
|
|
3798
|
+
}
|
|
3967
3799
|
}
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
if (
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
if (
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3800
|
+
if (existsSync4(join(this.cwd, "Cargo.toml"))) {
|
|
3801
|
+
stack.push("rust");
|
|
3802
|
+
try {
|
|
3803
|
+
const cargo = readFileSync4(join(this.cwd, "Cargo.toml"), "utf8");
|
|
3804
|
+
const nameMatch = cargo.match(/^name\s*=\s*"([^"]+)"/m);
|
|
3805
|
+
if (nameMatch && !name) name = nameMatch[1];
|
|
3806
|
+
} catch {
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
if (existsSync4(join(this.cwd, "pyproject.toml")) || existsSync4(join(this.cwd, "requirements.txt"))) {
|
|
3810
|
+
stack.push("python");
|
|
3811
|
+
}
|
|
3812
|
+
if (existsSync4(join(this.cwd, "go.mod"))) stack.push("go");
|
|
3813
|
+
return [stack, name];
|
|
3814
|
+
}
|
|
3815
|
+
getRecentCommits() {
|
|
3816
|
+
try {
|
|
3817
|
+
const out = execSync3("git log --oneline -5 2>/dev/null", {
|
|
3818
|
+
cwd: this.cwd,
|
|
3819
|
+
timeout: 3e3,
|
|
3820
|
+
encoding: "utf8"
|
|
3821
|
+
}).trim();
|
|
3822
|
+
return out ? out.split("\n").map((l) => l.trim()) : [];
|
|
3823
|
+
} catch {
|
|
3824
|
+
return [];
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
getDirtyFiles() {
|
|
3828
|
+
try {
|
|
3829
|
+
const out = execSync3("git status --short 2>/dev/null", {
|
|
3830
|
+
cwd: this.cwd,
|
|
3831
|
+
timeout: 3e3,
|
|
3832
|
+
encoding: "utf8"
|
|
3833
|
+
}).trim();
|
|
3834
|
+
return out ? out.split("\n").map((l) => l.slice(3).trim()) : [];
|
|
3835
|
+
} catch {
|
|
3836
|
+
return [];
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
async getRunningPorts() {
|
|
3840
|
+
const open = [];
|
|
3841
|
+
await Promise.all(PORTS_TO_CHECK.map(
|
|
3842
|
+
(port) => new Promise((resolve13) => {
|
|
3843
|
+
const s = createServer();
|
|
3844
|
+
s.listen(port, "127.0.0.1", () => {
|
|
3845
|
+
s.close();
|
|
3846
|
+
resolve13();
|
|
3847
|
+
});
|
|
3848
|
+
s.on("error", () => {
|
|
3849
|
+
open.push(port);
|
|
3850
|
+
resolve13();
|
|
3851
|
+
});
|
|
3852
|
+
setTimeout(() => {
|
|
3853
|
+
s.close();
|
|
3854
|
+
resolve13();
|
|
3855
|
+
}, 200);
|
|
3856
|
+
})
|
|
3857
|
+
));
|
|
3858
|
+
return open;
|
|
3859
|
+
}
|
|
3860
|
+
getReadmeSummary() {
|
|
3861
|
+
for (const name of ["README.md", "readme.md", "README.txt", "README"]) {
|
|
3862
|
+
const p = join(this.cwd, name);
|
|
3863
|
+
if (existsSync4(p)) {
|
|
3864
|
+
try {
|
|
3865
|
+
return readFileSync4(p, "utf8").slice(0, 300).replace(/\n+/g, " ").trim();
|
|
3866
|
+
} catch {
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
return "";
|
|
3985
3871
|
}
|
|
3986
3872
|
};
|
|
3987
3873
|
|
|
3988
|
-
// packages/daemon/src/
|
|
3989
|
-
var
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
this.clients.delete(ws);
|
|
4004
|
-
});
|
|
3874
|
+
// packages/daemon/src/ConversationStore.ts
|
|
3875
|
+
var CREATE_TABLE = `
|
|
3876
|
+
CREATE TABLE IF NOT EXISTS conversations (
|
|
3877
|
+
id TEXT PRIMARY KEY,
|
|
3878
|
+
session_id TEXT NOT NULL,
|
|
3879
|
+
user_entity_id TEXT NOT NULL,
|
|
3880
|
+
role TEXT NOT NULL,
|
|
3881
|
+
content TEXT NOT NULL,
|
|
3882
|
+
created_at INTEGER NOT NULL
|
|
3883
|
+
);
|
|
3884
|
+
CREATE INDEX IF NOT EXISTS idx_conv_user ON conversations(user_entity_id, created_at);
|
|
3885
|
+
`;
|
|
3886
|
+
var ConversationStore = class {
|
|
3887
|
+
constructor(adapter) {
|
|
3888
|
+
this.adapter = adapter;
|
|
4005
3889
|
}
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
this.
|
|
3890
|
+
initialised = false;
|
|
3891
|
+
init() {
|
|
3892
|
+
if (this.initialised) return;
|
|
3893
|
+
this.adapter.db.exec(CREATE_TABLE);
|
|
3894
|
+
this.initialised = true;
|
|
3895
|
+
}
|
|
3896
|
+
append(msg) {
|
|
3897
|
+
this.init();
|
|
3898
|
+
const db = this.adapter.db;
|
|
3899
|
+
db.prepare(
|
|
3900
|
+
`INSERT INTO conversations (id, session_id, user_entity_id, role, content, created_at)
|
|
3901
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
3902
|
+
).run(msg.id, msg.session_id, msg.user_entity_id, msg.role, msg.content, msg.created_at);
|
|
3903
|
+
}
|
|
3904
|
+
getHistory(userEntityId, limit = 20) {
|
|
3905
|
+
this.init();
|
|
3906
|
+
const db = this.adapter.db;
|
|
3907
|
+
const rows = db.prepare(
|
|
3908
|
+
`SELECT * FROM conversations WHERE user_entity_id = ?
|
|
3909
|
+
ORDER BY created_at DESC LIMIT ?`
|
|
3910
|
+
).all(userEntityId, limit);
|
|
3911
|
+
return rows.reverse();
|
|
4011
3912
|
}
|
|
4012
3913
|
/**
|
|
4013
|
-
*
|
|
3914
|
+
* Build conversation history as LLM messages for context injection.
|
|
4014
3915
|
*/
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
3916
|
+
buildContextMessages(userEntityId, limit = 10) {
|
|
3917
|
+
return this.getHistory(userEntityId, limit).map((m) => ({
|
|
3918
|
+
role: m.role,
|
|
3919
|
+
content: m.content
|
|
3920
|
+
}));
|
|
3921
|
+
}
|
|
3922
|
+
clearHistory(userEntityId) {
|
|
3923
|
+
this.init();
|
|
3924
|
+
const db = this.adapter.db;
|
|
3925
|
+
db.prepare(`DELETE FROM conversations WHERE user_entity_id = ?`).run(userEntityId);
|
|
3926
|
+
}
|
|
3927
|
+
};
|
|
3928
|
+
|
|
3929
|
+
// packages/daemon/src/SessionManager.ts
|
|
3930
|
+
import { readFileSync as readFileSync5, existsSync as existsSync6 } from "node:fs";
|
|
3931
|
+
import { resolve as resolve5 } from "node:path";
|
|
3932
|
+
import { homedir as homedir2 } from "node:os";
|
|
3933
|
+
import YAML2 from "yaml";
|
|
3934
|
+
var SessionManager = class {
|
|
3935
|
+
sessions = /* @__PURE__ */ new Map();
|
|
3936
|
+
inferenceEngine;
|
|
3937
|
+
eventBus;
|
|
3938
|
+
graph;
|
|
3939
|
+
llm;
|
|
3940
|
+
cwd;
|
|
3941
|
+
identity;
|
|
3942
|
+
projectContext;
|
|
3943
|
+
conversationStore;
|
|
3944
|
+
weightUpdater;
|
|
3945
|
+
anthropicFetcher = new AnthropicSkillFetcher();
|
|
3946
|
+
constructor(deps = {}) {
|
|
3947
|
+
this.inferenceEngine = deps.inferenceEngine;
|
|
3948
|
+
this.eventBus = deps.eventBus;
|
|
3949
|
+
this.graph = deps.graph;
|
|
3950
|
+
this.llm = deps.llm;
|
|
3951
|
+
this.cwd = deps.cwd ?? process.cwd();
|
|
3952
|
+
this.identity = deps.identity;
|
|
3953
|
+
this.projectContext = deps.projectContext;
|
|
3954
|
+
if (deps.adapter) {
|
|
3955
|
+
this.conversationStore = new ConversationStore(deps.adapter);
|
|
3956
|
+
this.conversationStore.init();
|
|
3957
|
+
const wLog = new WeightEventLog(deps.adapter);
|
|
3958
|
+
this.weightUpdater = new EdgeWeightUpdater(deps.adapter, wLog);
|
|
4022
3959
|
}
|
|
4023
|
-
this.broadcast(typed);
|
|
4024
3960
|
}
|
|
4025
3961
|
/**
|
|
4026
|
-
*
|
|
4027
|
-
* Returns an unsubscribe function.
|
|
3962
|
+
* Create a new session with status 'pending'.
|
|
4028
3963
|
*/
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
3964
|
+
createSession(req) {
|
|
3965
|
+
const id = crypto.randomUUID();
|
|
3966
|
+
const session = {
|
|
3967
|
+
id,
|
|
3968
|
+
task: req.task,
|
|
3969
|
+
skill: req.skill,
|
|
3970
|
+
entity_id: req.entity_id,
|
|
3971
|
+
status: "pending",
|
|
3972
|
+
created_at: Date.now(),
|
|
3973
|
+
steps: []
|
|
4033
3974
|
};
|
|
3975
|
+
this.sessions.set(id, session);
|
|
3976
|
+
return session;
|
|
4034
3977
|
}
|
|
4035
3978
|
/**
|
|
4036
|
-
*
|
|
3979
|
+
* Transition session to 'running' and optionally invoke inference engine.
|
|
4037
3980
|
*/
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
3981
|
+
async startSession(id) {
|
|
3982
|
+
const session = this.getSessionOrThrow(id);
|
|
3983
|
+
session.status = "running";
|
|
3984
|
+
session.started_at = Date.now();
|
|
3985
|
+
this.emit({
|
|
3986
|
+
type: "session.started",
|
|
3987
|
+
session_id: session.id,
|
|
3988
|
+
task: session.task
|
|
3989
|
+
});
|
|
3990
|
+
if (this.inferenceEngine) {
|
|
4042
3991
|
try {
|
|
4043
|
-
|
|
3992
|
+
const plan = await this.inferenceEngine.resolve(session.task);
|
|
3993
|
+
session.plan = plan;
|
|
3994
|
+
if (plan.skill) {
|
|
3995
|
+
session.skill = plan.skill;
|
|
3996
|
+
}
|
|
4044
3997
|
} catch {
|
|
4045
|
-
this.clients.delete(ws);
|
|
4046
3998
|
}
|
|
4047
3999
|
}
|
|
4000
|
+
return session;
|
|
4048
4001
|
}
|
|
4049
4002
|
/**
|
|
4050
|
-
*
|
|
4051
|
-
* Runs every 30 seconds.
|
|
4052
|
-
*/
|
|
4053
|
-
startStatsHeartbeat(getStats) {
|
|
4054
|
-
this.stopStatsHeartbeat();
|
|
4055
|
-
this.heartbeatTimer = setInterval(() => {
|
|
4056
|
-
const stats = getStats();
|
|
4057
|
-
this.emit({
|
|
4058
|
-
type: "daemon.stats",
|
|
4059
|
-
graph_nodes: stats.graph_nodes,
|
|
4060
|
-
active_sessions: stats.active_sessions
|
|
4061
|
-
});
|
|
4062
|
-
}, 3e4);
|
|
4063
|
-
}
|
|
4064
|
-
/**
|
|
4065
|
-
* Stop the stats heartbeat.
|
|
4003
|
+
* Append a step to a running session.
|
|
4066
4004
|
*/
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4005
|
+
addStep(id, description, result) {
|
|
4006
|
+
const session = this.getSessionOrThrow(id);
|
|
4007
|
+
const step = {
|
|
4008
|
+
index: session.steps.length,
|
|
4009
|
+
description,
|
|
4010
|
+
result,
|
|
4011
|
+
started_at: Date.now()
|
|
4012
|
+
};
|
|
4013
|
+
session.steps.push(step);
|
|
4014
|
+
this.emit({
|
|
4015
|
+
type: "session.step",
|
|
4016
|
+
session_id: session.id,
|
|
4017
|
+
step: description,
|
|
4018
|
+
result: result ?? null
|
|
4019
|
+
});
|
|
4020
|
+
return step;
|
|
4078
4021
|
}
|
|
4079
4022
|
/**
|
|
4080
|
-
*
|
|
4023
|
+
* Mark session as completed with a result.
|
|
4081
4024
|
*/
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
compactor_interval_ms: 24 * 60 * 60 * 1e3,
|
|
4094
|
-
// 24 hours
|
|
4095
|
-
enrichment_interval_ms: 5 * 60 * 1e3
|
|
4096
|
-
// 5 minutes
|
|
4097
|
-
};
|
|
4098
|
-
var BackgroundWorkers = class {
|
|
4099
|
-
graph;
|
|
4100
|
-
decayScheduler;
|
|
4101
|
-
traceStore;
|
|
4102
|
-
config;
|
|
4103
|
-
proactiveSurface;
|
|
4104
|
-
teamSync;
|
|
4105
|
-
timers = /* @__PURE__ */ new Map();
|
|
4106
|
-
lastRunTimes = /* @__PURE__ */ new Map();
|
|
4107
|
-
running = false;
|
|
4108
|
-
constructor(deps = {}) {
|
|
4109
|
-
this.graph = deps.graph;
|
|
4110
|
-
this.decayScheduler = deps.decayScheduler;
|
|
4111
|
-
this.traceStore = deps.traceStore;
|
|
4112
|
-
this.config = { ...DEFAULT_CONFIG2, ...deps.config };
|
|
4113
|
-
this.proactiveSurface = deps.proactiveSurface;
|
|
4114
|
-
this.teamSync = deps.teamSync;
|
|
4025
|
+
completeSession(id, result) {
|
|
4026
|
+
const session = this.getSessionOrThrow(id);
|
|
4027
|
+
session.status = "completed";
|
|
4028
|
+
session.completed_at = Date.now();
|
|
4029
|
+
session.result = result;
|
|
4030
|
+
this.emit({
|
|
4031
|
+
type: "session.completed",
|
|
4032
|
+
session_id: session.id,
|
|
4033
|
+
result: result ?? null
|
|
4034
|
+
});
|
|
4035
|
+
return session;
|
|
4115
4036
|
}
|
|
4116
4037
|
/**
|
|
4117
|
-
*
|
|
4038
|
+
* Mark session as failed with an error message.
|
|
4118
4039
|
*/
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
}, this.config.decay_interval_ms);
|
|
4131
|
-
this.timers.set("decay", timer);
|
|
4132
|
-
}
|
|
4133
|
-
if (this.traceStore) {
|
|
4134
|
-
const timer = setInterval(() => {
|
|
4135
|
-
try {
|
|
4136
|
-
this.resolveDeferredTraces();
|
|
4137
|
-
this.lastRunTimes.set("deferred_traces", Date.now());
|
|
4138
|
-
} catch (err) {
|
|
4139
|
-
console.error("[BackgroundWorkers] Deferred trace resolution failed:", err);
|
|
4140
|
-
}
|
|
4141
|
-
}, this.config.deferred_trace_interval_ms);
|
|
4142
|
-
this.timers.set("deferred_traces", timer);
|
|
4143
|
-
}
|
|
4144
|
-
{
|
|
4145
|
-
const timer = setInterval(() => {
|
|
4146
|
-
console.log("[BackgroundWorkers] compactor not yet implemented");
|
|
4147
|
-
this.lastRunTimes.set("compactor", Date.now());
|
|
4148
|
-
}, this.config.compactor_interval_ms);
|
|
4149
|
-
this.timers.set("compactor", timer);
|
|
4150
|
-
}
|
|
4151
|
-
{
|
|
4152
|
-
const timer = setInterval(() => {
|
|
4153
|
-
this.lastRunTimes.set("enrichment", Date.now());
|
|
4154
|
-
}, this.config.enrichment_interval_ms);
|
|
4155
|
-
this.timers.set("enrichment", timer);
|
|
4156
|
-
}
|
|
4157
|
-
if (this.proactiveSurface) {
|
|
4158
|
-
this.proactiveSurface.start();
|
|
4159
|
-
this.lastRunTimes.set("proactive_surface", Date.now());
|
|
4160
|
-
}
|
|
4161
|
-
if (this.teamSync) {
|
|
4162
|
-
this.teamSync.start();
|
|
4163
|
-
this.lastRunTimes.set("team_sync", Date.now());
|
|
4164
|
-
}
|
|
4040
|
+
failSession(id, error) {
|
|
4041
|
+
const session = this.getSessionOrThrow(id);
|
|
4042
|
+
session.status = "failed";
|
|
4043
|
+
session.completed_at = Date.now();
|
|
4044
|
+
session.error = error;
|
|
4045
|
+
this.emit({
|
|
4046
|
+
type: "session.failed",
|
|
4047
|
+
session_id: session.id,
|
|
4048
|
+
error
|
|
4049
|
+
});
|
|
4050
|
+
return session;
|
|
4165
4051
|
}
|
|
4166
4052
|
/**
|
|
4167
|
-
*
|
|
4053
|
+
* Cancel a session.
|
|
4168
4054
|
*/
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
this.
|
|
4175
|
-
|
|
4176
|
-
|
|
4055
|
+
cancelSession(id) {
|
|
4056
|
+
const session = this.getSessionOrThrow(id);
|
|
4057
|
+
session.status = "cancelled";
|
|
4058
|
+
session.completed_at = Date.now();
|
|
4059
|
+
session.error = "cancelled";
|
|
4060
|
+
this.emit({
|
|
4061
|
+
type: "session.failed",
|
|
4062
|
+
session_id: session.id,
|
|
4063
|
+
error: "cancelled"
|
|
4064
|
+
});
|
|
4065
|
+
return session;
|
|
4177
4066
|
}
|
|
4178
4067
|
/**
|
|
4179
|
-
*
|
|
4068
|
+
* Get session by ID, or null if not found.
|
|
4180
4069
|
*/
|
|
4181
|
-
|
|
4182
|
-
return this.
|
|
4070
|
+
getSession(id) {
|
|
4071
|
+
return this.sessions.get(id) ?? null;
|
|
4183
4072
|
}
|
|
4184
4073
|
/**
|
|
4185
|
-
*
|
|
4074
|
+
* List all sessions sorted by created_at descending.
|
|
4186
4075
|
*/
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
active: this.timers.has(name),
|
|
4192
|
-
last_run_at: this.lastRunTimes.get(name) ?? null
|
|
4193
|
-
}));
|
|
4076
|
+
listSessions() {
|
|
4077
|
+
return [...this.sessions.values()].sort(
|
|
4078
|
+
(a, b) => b.created_at - a.created_at
|
|
4079
|
+
);
|
|
4194
4080
|
}
|
|
4195
|
-
// ─── Private ───────────────────────────────────────
|
|
4196
4081
|
/**
|
|
4197
|
-
*
|
|
4082
|
+
* End-to-end session run: create -> start -> resolve -> step -> complete.
|
|
4083
|
+
* Wraps everything in try/catch to fail gracefully.
|
|
4198
4084
|
*/
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
if (
|
|
4205
|
-
|
|
4085
|
+
async runSession(req) {
|
|
4086
|
+
let enrichedReq = req;
|
|
4087
|
+
if (req.entity_id && this.graph) {
|
|
4088
|
+
const loader = new EntityScopedContextLoader(this.graph);
|
|
4089
|
+
const scopedCtx = loader.load(req.entity_id);
|
|
4090
|
+
if (scopedCtx?.personality_prompt) {
|
|
4091
|
+
enrichedReq = {
|
|
4092
|
+
...req,
|
|
4093
|
+
context: {
|
|
4094
|
+
...req.context,
|
|
4095
|
+
system_context: [
|
|
4096
|
+
scopedCtx.personality_prompt,
|
|
4097
|
+
...req.context?.system_context != null ? [String(req.context.system_context)] : []
|
|
4098
|
+
].join("\n\n"),
|
|
4099
|
+
entity_label: scopedCtx.entity_label,
|
|
4100
|
+
parent_entity_labels: scopedCtx.parent_entity_labels
|
|
4101
|
+
}
|
|
4102
|
+
};
|
|
4206
4103
|
}
|
|
4207
4104
|
}
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
this.customDir = opts?.customDir ?? join2(homedir2(), ".0agent", "skills", "custom");
|
|
4224
|
-
}
|
|
4225
|
-
/**
|
|
4226
|
-
* Load all skills from builtin + custom directories.
|
|
4227
|
-
*/
|
|
4228
|
-
async loadAll() {
|
|
4229
|
-
this.skills.clear();
|
|
4230
|
-
this.builtinNames.clear();
|
|
4231
|
-
this.loadFromDir(this.builtinDir, true);
|
|
4232
|
-
this.loadFromDir(this.customDir, false);
|
|
4233
|
-
}
|
|
4234
|
-
loadFromDir(dir, isBuiltin) {
|
|
4235
|
-
if (!existsSync6(dir)) return;
|
|
4236
|
-
const files = readdirSync3(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
4237
|
-
for (const file of files) {
|
|
4238
|
-
try {
|
|
4239
|
-
const raw = readFileSync5(join2(dir, file), "utf8");
|
|
4240
|
-
const skill = YAML2.parse(raw);
|
|
4241
|
-
if (skill.name) {
|
|
4242
|
-
this.skills.set(skill.name, skill);
|
|
4243
|
-
if (isBuiltin) this.builtinNames.add(skill.name);
|
|
4105
|
+
const session = this.createSession(enrichedReq);
|
|
4106
|
+
try {
|
|
4107
|
+
await this.startSession(session.id);
|
|
4108
|
+
this.addStep(session.id, `Extracting entities from: "${req.task.slice(0, 60)}${req.task.length > 60 ? "\u2026" : ""}"`);
|
|
4109
|
+
this.addStep(session.id, "Querying knowledge graph (structural + semantic)\u2026");
|
|
4110
|
+
if (session.plan) {
|
|
4111
|
+
const edge = session.plan.selected_edge;
|
|
4112
|
+
if (edge) {
|
|
4113
|
+
this.addStep(
|
|
4114
|
+
session.id,
|
|
4115
|
+
`Selected plan: ${edge.from_label} \u2192 ${edge.to_label} (weight: ${edge.weight.toFixed(2)}, mode: ${edge.mode})`,
|
|
4116
|
+
session.plan
|
|
4117
|
+
);
|
|
4118
|
+
} else {
|
|
4119
|
+
this.addStep(session.id, `No prior plan found \u2014 bootstrapping from scratch`, session.plan);
|
|
4244
4120
|
}
|
|
4245
|
-
|
|
4246
|
-
|
|
4121
|
+
if (session.plan.skill) {
|
|
4122
|
+
this.addStep(session.id, `Matched skill: /${session.plan.skill}`);
|
|
4123
|
+
}
|
|
4124
|
+
} else {
|
|
4125
|
+
this.addStep(session.id, "No inference engine connected \u2014 executing task directly");
|
|
4247
4126
|
}
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4127
|
+
let anthropicContext;
|
|
4128
|
+
if (enrichedReq.skill && this.anthropicFetcher.isAnthropicSkill(enrichedReq.skill)) {
|
|
4129
|
+
this.addStep(session.id, `Fetching skill instructions: ${enrichedReq.skill}`);
|
|
4130
|
+
const fetched = await this.anthropicFetcher.fetch(enrichedReq.skill);
|
|
4131
|
+
if (fetched) {
|
|
4132
|
+
anthropicContext = this.anthropicFetcher.buildSystemPrompt(fetched);
|
|
4133
|
+
this.addStep(session.id, `Loaded skill: ${fetched.name} (${fetched.cached ? "cached" : "fresh"})`);
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
const activeLLM = this.getFreshLLM();
|
|
4137
|
+
if (activeLLM?.isConfigured) {
|
|
4138
|
+
const executor = new AgentExecutor(
|
|
4139
|
+
activeLLM,
|
|
4140
|
+
{ cwd: this.cwd },
|
|
4141
|
+
// step callback → emit session.step events
|
|
4142
|
+
(step) => this.addStep(session.id, step),
|
|
4143
|
+
// token callback → emit session.token events
|
|
4144
|
+
(token) => this.emit({ type: "session.token", session_id: session.id, token })
|
|
4145
|
+
);
|
|
4146
|
+
const identityContext = this.identity ? `You are talking to ${this.identity.name} (device: ${this.identity.device_id}, timezone: ${this.identity.timezone}).` : void 0;
|
|
4147
|
+
const projectCtx = this.projectContext ? ProjectScanner.buildContextPrompt(this.projectContext) : void 0;
|
|
4148
|
+
const userEntityId = enrichedReq.entity_id ?? this.identity?.entity_node_id;
|
|
4149
|
+
let conversationHistory;
|
|
4150
|
+
if (this.conversationStore && userEntityId) {
|
|
4151
|
+
const history = this.conversationStore.buildContextMessages(userEntityId, 8);
|
|
4152
|
+
if (history.length > 0) {
|
|
4153
|
+
const historyStr = history.map((m) => `${m.role === "user" ? "User" : "Agent"}: ${m.content.slice(0, 400)}`).join("\n");
|
|
4154
|
+
conversationHistory = `CONVERSATION HISTORY (use this for context on follow-up requests):
|
|
4155
|
+
${historyStr}
|
|
4156
|
+
|
|
4157
|
+
Current task:`;
|
|
4158
|
+
}
|
|
4159
|
+
}
|
|
4160
|
+
const systemContext = [
|
|
4161
|
+
identityContext,
|
|
4162
|
+
projectCtx,
|
|
4163
|
+
conversationHistory,
|
|
4164
|
+
anthropicContext,
|
|
4165
|
+
enrichedReq.context?.system_context ? String(enrichedReq.context.system_context) : void 0
|
|
4166
|
+
].filter(Boolean).join("\n\n") || void 0;
|
|
4167
|
+
let agentResult;
|
|
4168
|
+
try {
|
|
4169
|
+
const { SelfHealLoop: SelfHealLoop2 } = await Promise.resolve().then(() => (init_SelfHealLoop(), SelfHealLoop_exports));
|
|
4170
|
+
const healLoop = new SelfHealLoop2(
|
|
4171
|
+
activeLLM,
|
|
4172
|
+
{ cwd: this.cwd },
|
|
4173
|
+
(step) => this.addStep(session.id, step),
|
|
4174
|
+
(token) => this.emit({ type: "session.token", session_id: session.id, token })
|
|
4175
|
+
);
|
|
4176
|
+
agentResult = await healLoop.executeWithHealing(enrichedReq.task, systemContext);
|
|
4177
|
+
} catch {
|
|
4178
|
+
agentResult = await executor.execute(enrichedReq.task, systemContext);
|
|
4179
|
+
}
|
|
4180
|
+
if (this.conversationStore && userEntityId) {
|
|
4181
|
+
const sessionId = session.id;
|
|
4182
|
+
const now = Date.now();
|
|
4183
|
+
this.conversationStore.append({
|
|
4184
|
+
id: crypto.randomUUID(),
|
|
4185
|
+
session_id: sessionId,
|
|
4186
|
+
user_entity_id: userEntityId,
|
|
4187
|
+
role: "user",
|
|
4188
|
+
content: enrichedReq.task,
|
|
4189
|
+
created_at: now
|
|
4190
|
+
});
|
|
4191
|
+
this.conversationStore.append({
|
|
4192
|
+
id: crypto.randomUUID(),
|
|
4193
|
+
session_id: sessionId,
|
|
4194
|
+
user_entity_id: userEntityId,
|
|
4195
|
+
role: "assistant",
|
|
4196
|
+
content: agentResult.output.slice(0, 1e3),
|
|
4197
|
+
// cap stored output length
|
|
4198
|
+
created_at: now + 1
|
|
4199
|
+
});
|
|
4200
|
+
}
|
|
4201
|
+
const selectedEdgeId = session.plan?.selected_edge?.edge_id;
|
|
4202
|
+
if (selectedEdgeId && this.weightUpdater && this.graph) {
|
|
4203
|
+
const outcomeSignal = this.computeOutcomeSignal(agentResult);
|
|
4204
|
+
if (outcomeSignal !== 0) {
|
|
4205
|
+
const edge = this.graph.getEdge(selectedEdgeId);
|
|
4206
|
+
if (edge && !edge.locked) {
|
|
4207
|
+
const newWeight = Math.max(0, Math.min(
|
|
4208
|
+
1,
|
|
4209
|
+
edge.weight + outcomeSignal * 0.1
|
|
4210
|
+
// learning rate 0.1
|
|
4211
|
+
));
|
|
4212
|
+
await this.weightUpdater.update(
|
|
4213
|
+
edge.id,
|
|
4214
|
+
edge.weight,
|
|
4215
|
+
newWeight,
|
|
4216
|
+
outcomeSignal > 0 ? "task_outcome_positive" : "task_outcome_negative",
|
|
4217
|
+
session.id
|
|
4218
|
+
);
|
|
4219
|
+
this.emit({ type: "graph.weight_updated", edge_id: edge.id, old_weight: edge.weight, new_weight: newWeight });
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
if (agentResult.files_written.length > 0) {
|
|
4224
|
+
this.addStep(session.id, `Files written: ${agentResult.files_written.join(", ")}`);
|
|
4225
|
+
}
|
|
4226
|
+
if (agentResult.commands_run.length > 0) {
|
|
4227
|
+
this.addStep(session.id, `Commands run: ${agentResult.commands_run.length}`);
|
|
4228
|
+
}
|
|
4229
|
+
this.addStep(session.id, `Done (${agentResult.tokens_used} tokens, ${agentResult.iterations} LLM turns)`);
|
|
4230
|
+
this.completeSession(session.id, {
|
|
4231
|
+
output: agentResult.output,
|
|
4232
|
+
files_written: agentResult.files_written,
|
|
4233
|
+
commands_run: agentResult.commands_run,
|
|
4234
|
+
tokens_used: agentResult.tokens_used,
|
|
4235
|
+
model: agentResult.model
|
|
4236
|
+
});
|
|
4237
|
+
} else {
|
|
4238
|
+
const cfgPath = resolve5(homedir2(), ".0agent", "config.yaml");
|
|
4239
|
+
const output = `No LLM API key found. Add one to ${cfgPath} or run: 0agent init`;
|
|
4240
|
+
this.addStep(session.id, "\u26A0 No LLM API key configured \u2014 run: 0agent init");
|
|
4241
|
+
this.completeSession(session.id, { output });
|
|
4242
|
+
}
|
|
4243
|
+
} catch (err) {
|
|
4244
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4245
|
+
this.failSession(session.id, message);
|
|
4246
|
+
}
|
|
4247
|
+
return this.sessions.get(session.id);
|
|
4265
4248
|
}
|
|
4266
4249
|
/**
|
|
4267
|
-
*
|
|
4268
|
-
* Throws if name conflicts with built-in.
|
|
4250
|
+
* Return the number of active (running) sessions.
|
|
4269
4251
|
*/
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4252
|
+
activeSessionCount() {
|
|
4253
|
+
let count = 0;
|
|
4254
|
+
for (const s of this.sessions.values()) {
|
|
4255
|
+
if (s.status === "running") count++;
|
|
4256
|
+
}
|
|
4257
|
+
return count;
|
|
4258
|
+
}
|
|
4259
|
+
// ─── Private helpers ───────────────────────────────
|
|
4260
|
+
getSessionOrThrow(id) {
|
|
4261
|
+
const session = this.sessions.get(id);
|
|
4262
|
+
if (!session) {
|
|
4263
|
+
throw new Error(`Session not found: ${id}`);
|
|
4264
|
+
}
|
|
4265
|
+
return session;
|
|
4266
|
+
}
|
|
4267
|
+
emit(event) {
|
|
4268
|
+
if (this.eventBus) {
|
|
4269
|
+
this.eventBus.emit(event);
|
|
4273
4270
|
}
|
|
4274
|
-
mkdirSync3(this.customDir, { recursive: true });
|
|
4275
|
-
const filePath = join2(this.customDir, `${name}.yaml`);
|
|
4276
|
-
writeFileSync3(filePath, yamlContent, "utf8");
|
|
4277
|
-
const skill = YAML2.parse(yamlContent);
|
|
4278
|
-
this.skills.set(name, skill);
|
|
4279
|
-
return skill;
|
|
4280
4271
|
}
|
|
4281
4272
|
/**
|
|
4282
|
-
*
|
|
4273
|
+
* Always returns a fresh LLM executor with the latest API key from disk.
|
|
4274
|
+
* Fixes stale-daemon problem: if the user sets their key AFTER the daemon
|
|
4275
|
+
* started, the next session picks it up automatically.
|
|
4283
4276
|
*/
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4277
|
+
getFreshLLM() {
|
|
4278
|
+
try {
|
|
4279
|
+
const configPath = resolve5(homedir2(), ".0agent", "config.yaml");
|
|
4280
|
+
if (!existsSync6(configPath)) return this.llm;
|
|
4281
|
+
const raw = readFileSync5(configPath, "utf8");
|
|
4282
|
+
const cfg = YAML2.parse(raw);
|
|
4283
|
+
const providers = cfg.llm_providers;
|
|
4284
|
+
if (!providers?.length) return this.llm;
|
|
4285
|
+
const def = providers.find((p) => p.is_default) ?? providers[0];
|
|
4286
|
+
if (!def) return this.llm;
|
|
4287
|
+
const freshExec = new LLMExecutor({
|
|
4288
|
+
provider: String(def.provider ?? "anthropic"),
|
|
4289
|
+
model: String(def.model ?? "claude-sonnet-4-6"),
|
|
4290
|
+
api_key: String(def.api_key ?? ""),
|
|
4291
|
+
base_url: def.base_url ? String(def.base_url) : void 0
|
|
4292
|
+
});
|
|
4293
|
+
if (freshExec.isConfigured) return freshExec;
|
|
4294
|
+
} catch {
|
|
4291
4295
|
}
|
|
4292
|
-
this.
|
|
4296
|
+
return this.llm;
|
|
4293
4297
|
}
|
|
4294
|
-
|
|
4295
|
-
|
|
4298
|
+
/**
|
|
4299
|
+
* Convert a task result into a weight signal for the knowledge graph.
|
|
4300
|
+
*
|
|
4301
|
+
* Signal scale: -0.3 (failed after retries) to +0.3 (verified success first try).
|
|
4302
|
+
* Neutral (0) when no verification was possible — don't penalise unverifiable tasks.
|
|
4303
|
+
*/
|
|
4304
|
+
computeOutcomeSignal(result) {
|
|
4305
|
+
const healAttempts = result["heal_attempts"];
|
|
4306
|
+
if (!healAttempts || healAttempts.length === 0) return 0;
|
|
4307
|
+
const last = healAttempts[healAttempts.length - 1];
|
|
4308
|
+
const verification = last?.["verification"];
|
|
4309
|
+
if (!verification || verification["method"] === "none") return 0;
|
|
4310
|
+
const success = verification["success"] === true;
|
|
4311
|
+
const healed = result["healed"] === true;
|
|
4312
|
+
if (success) return healed ? 0.1 : 0.3;
|
|
4313
|
+
return -0.2;
|
|
4296
4314
|
}
|
|
4297
4315
|
};
|
|
4298
4316
|
|
|
4299
|
-
// packages/daemon/src/
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
return c.json({ ok: true, timestamp: Date.now(), ...status });
|
|
4313
|
-
});
|
|
4314
|
-
return app;
|
|
4315
|
-
}
|
|
4316
|
-
|
|
4317
|
-
// packages/daemon/src/routes/sessions.ts
|
|
4318
|
-
import { Hono as Hono2 } from "hono";
|
|
4319
|
-
function sessionRoutes(deps) {
|
|
4320
|
-
const app = new Hono2();
|
|
4321
|
-
app.post("/", async (c) => {
|
|
4322
|
-
const body = await c.req.json();
|
|
4323
|
-
if (!body.task || typeof body.task !== "string") {
|
|
4324
|
-
return c.json({ error: "task is required and must be a string" }, 400);
|
|
4325
|
-
}
|
|
4326
|
-
const session = deps.sessions.createSession(body);
|
|
4327
|
-
deps.sessions.runSession(body).catch(() => {
|
|
4317
|
+
// packages/daemon/src/WebSocketEvents.ts
|
|
4318
|
+
var WebSocketEventBus = class {
|
|
4319
|
+
clients = /* @__PURE__ */ new Set();
|
|
4320
|
+
handlers = /* @__PURE__ */ new Set();
|
|
4321
|
+
heartbeatTimer = null;
|
|
4322
|
+
/**
|
|
4323
|
+
* Register a connected WebSocket client.
|
|
4324
|
+
* Automatically removes on close.
|
|
4325
|
+
*/
|
|
4326
|
+
addClient(ws) {
|
|
4327
|
+
this.clients.add(ws);
|
|
4328
|
+
ws.on("close", () => {
|
|
4329
|
+
this.clients.delete(ws);
|
|
4328
4330
|
});
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4331
|
+
ws.on("error", () => {
|
|
4332
|
+
this.clients.delete(ws);
|
|
4333
|
+
});
|
|
4334
|
+
}
|
|
4335
|
+
/**
|
|
4336
|
+
* Manually remove a WebSocket client.
|
|
4337
|
+
*/
|
|
4338
|
+
removeClient(ws) {
|
|
4339
|
+
this.clients.delete(ws);
|
|
4340
|
+
}
|
|
4341
|
+
/**
|
|
4342
|
+
* Emit an event to all local handlers and broadcast to WS clients.
|
|
4343
|
+
*/
|
|
4344
|
+
emit(event) {
|
|
4345
|
+
const typed = event;
|
|
4346
|
+
for (const handler of this.handlers) {
|
|
4347
|
+
try {
|
|
4348
|
+
handler(typed);
|
|
4349
|
+
} catch {
|
|
4350
|
+
}
|
|
4340
4351
|
}
|
|
4341
|
-
|
|
4342
|
-
}
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4352
|
+
this.broadcast(typed);
|
|
4353
|
+
}
|
|
4354
|
+
/**
|
|
4355
|
+
* Register a local event handler.
|
|
4356
|
+
* Returns an unsubscribe function.
|
|
4357
|
+
*/
|
|
4358
|
+
onEvent(handler) {
|
|
4359
|
+
this.handlers.add(handler);
|
|
4360
|
+
return () => {
|
|
4361
|
+
this.handlers.delete(handler);
|
|
4362
|
+
};
|
|
4363
|
+
}
|
|
4364
|
+
/**
|
|
4365
|
+
* Send an event to all connected WebSocket clients.
|
|
4366
|
+
*/
|
|
4367
|
+
broadcast(event) {
|
|
4368
|
+
if (this.clients.size === 0) return;
|
|
4369
|
+
const data = JSON.stringify(event);
|
|
4370
|
+
for (const ws of this.clients) {
|
|
4371
|
+
try {
|
|
4372
|
+
ws.send(data);
|
|
4373
|
+
} catch {
|
|
4374
|
+
this.clients.delete(ws);
|
|
4375
|
+
}
|
|
4348
4376
|
}
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4377
|
+
}
|
|
4378
|
+
/**
|
|
4379
|
+
* Start a periodic heartbeat that emits daemon.stats events.
|
|
4380
|
+
* Runs every 30 seconds.
|
|
4381
|
+
*/
|
|
4382
|
+
startStatsHeartbeat(getStats) {
|
|
4383
|
+
this.stopStatsHeartbeat();
|
|
4384
|
+
this.heartbeatTimer = setInterval(() => {
|
|
4385
|
+
const stats = getStats();
|
|
4386
|
+
this.emit({
|
|
4387
|
+
type: "daemon.stats",
|
|
4388
|
+
graph_nodes: stats.graph_nodes,
|
|
4389
|
+
active_sessions: stats.active_sessions
|
|
4390
|
+
});
|
|
4391
|
+
}, 3e4);
|
|
4392
|
+
}
|
|
4393
|
+
/**
|
|
4394
|
+
* Stop the stats heartbeat.
|
|
4395
|
+
*/
|
|
4396
|
+
stopStatsHeartbeat() {
|
|
4397
|
+
if (this.heartbeatTimer !== null) {
|
|
4398
|
+
clearInterval(this.heartbeatTimer);
|
|
4399
|
+
this.heartbeatTimer = null;
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
/**
|
|
4403
|
+
* Get the number of connected WS clients.
|
|
4404
|
+
*/
|
|
4405
|
+
clientCount() {
|
|
4406
|
+
return this.clients.size;
|
|
4407
|
+
}
|
|
4408
|
+
/**
|
|
4409
|
+
* Get the number of registered local handlers.
|
|
4410
|
+
*/
|
|
4411
|
+
handlerCount() {
|
|
4412
|
+
return this.handlers.size;
|
|
4413
|
+
}
|
|
4414
|
+
};
|
|
4354
4415
|
|
|
4355
|
-
// packages/daemon/src/
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4416
|
+
// packages/daemon/src/BackgroundWorkers.ts
|
|
4417
|
+
var DEFAULT_CONFIG2 = {
|
|
4418
|
+
decay_interval_ms: 6 * 60 * 60 * 1e3,
|
|
4419
|
+
// 6 hours
|
|
4420
|
+
deferred_trace_interval_ms: 6e4,
|
|
4421
|
+
// 60 seconds
|
|
4422
|
+
compactor_interval_ms: 24 * 60 * 60 * 1e3,
|
|
4423
|
+
// 24 hours
|
|
4424
|
+
enrichment_interval_ms: 5 * 60 * 1e3
|
|
4425
|
+
// 5 minutes
|
|
4426
|
+
};
|
|
4427
|
+
var BackgroundWorkers = class {
|
|
4428
|
+
graph;
|
|
4429
|
+
decayScheduler;
|
|
4430
|
+
traceStore;
|
|
4431
|
+
config;
|
|
4432
|
+
proactiveSurface;
|
|
4433
|
+
teamSync;
|
|
4434
|
+
timers = /* @__PURE__ */ new Map();
|
|
4435
|
+
lastRunTimes = /* @__PURE__ */ new Map();
|
|
4436
|
+
running = false;
|
|
4437
|
+
constructor(deps = {}) {
|
|
4438
|
+
this.graph = deps.graph;
|
|
4439
|
+
this.decayScheduler = deps.decayScheduler;
|
|
4440
|
+
this.traceStore = deps.traceStore;
|
|
4441
|
+
this.config = { ...DEFAULT_CONFIG2, ...deps.config };
|
|
4442
|
+
this.proactiveSurface = deps.proactiveSurface;
|
|
4443
|
+
this.teamSync = deps.teamSync;
|
|
4444
|
+
}
|
|
4445
|
+
/**
|
|
4446
|
+
* Start all background workers.
|
|
4447
|
+
*/
|
|
4448
|
+
start() {
|
|
4449
|
+
if (this.running) return;
|
|
4450
|
+
this.running = true;
|
|
4451
|
+
if (this.decayScheduler) {
|
|
4452
|
+
const timer = setInterval(async () => {
|
|
4453
|
+
try {
|
|
4454
|
+
await this.decayScheduler.runCycle();
|
|
4455
|
+
this.lastRunTimes.set("decay", Date.now());
|
|
4456
|
+
} catch (err) {
|
|
4457
|
+
console.error("[BackgroundWorkers] Decay cycle failed:", err);
|
|
4458
|
+
}
|
|
4459
|
+
}, this.config.decay_interval_ms);
|
|
4460
|
+
this.timers.set("decay", timer);
|
|
4376
4461
|
}
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
edges = deps.graph.getEdgesFrom(from_node);
|
|
4388
|
-
} else if (to_node) {
|
|
4389
|
-
edges = deps.graph.getEdgesTo(to_node);
|
|
4390
|
-
} else {
|
|
4391
|
-
edges = deps.graph.getAllEdges();
|
|
4462
|
+
if (this.traceStore) {
|
|
4463
|
+
const timer = setInterval(() => {
|
|
4464
|
+
try {
|
|
4465
|
+
this.resolveDeferredTraces();
|
|
4466
|
+
this.lastRunTimes.set("deferred_traces", Date.now());
|
|
4467
|
+
} catch (err) {
|
|
4468
|
+
console.error("[BackgroundWorkers] Deferred trace resolution failed:", err);
|
|
4469
|
+
}
|
|
4470
|
+
}, this.config.deferred_trace_interval_ms);
|
|
4471
|
+
this.timers.set("deferred_traces", timer);
|
|
4392
4472
|
}
|
|
4393
|
-
|
|
4394
|
-
|
|
4473
|
+
{
|
|
4474
|
+
const timer = setInterval(() => {
|
|
4475
|
+
console.log("[BackgroundWorkers] compactor not yet implemented");
|
|
4476
|
+
this.lastRunTimes.set("compactor", Date.now());
|
|
4477
|
+
}, this.config.compactor_interval_ms);
|
|
4478
|
+
this.timers.set("compactor", timer);
|
|
4395
4479
|
}
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
structural: body.structural ?? {},
|
|
4402
|
-
semantic: body.semantic,
|
|
4403
|
-
limit: body.limit
|
|
4404
|
-
});
|
|
4405
|
-
return c.json(results);
|
|
4406
|
-
});
|
|
4407
|
-
return app;
|
|
4408
|
-
}
|
|
4409
|
-
|
|
4410
|
-
// packages/daemon/src/routes/entities.ts
|
|
4411
|
-
import { Hono as Hono4 } from "hono";
|
|
4412
|
-
function entityRoutes(deps) {
|
|
4413
|
-
const app = new Hono4();
|
|
4414
|
-
app.get("/", (c) => {
|
|
4415
|
-
const results = deps.graph.queryStructural({
|
|
4416
|
-
node_type: "entity",
|
|
4417
|
-
limit: 200
|
|
4418
|
-
});
|
|
4419
|
-
return c.json(results.map((r) => r.node));
|
|
4420
|
-
});
|
|
4421
|
-
app.get("/:id", (c) => {
|
|
4422
|
-
const id = c.req.param("id");
|
|
4423
|
-
const node = deps.graph.getNode(id);
|
|
4424
|
-
if (!node) {
|
|
4425
|
-
return c.json({ error: "Entity not found" }, 404);
|
|
4480
|
+
{
|
|
4481
|
+
const timer = setInterval(() => {
|
|
4482
|
+
this.lastRunTimes.set("enrichment", Date.now());
|
|
4483
|
+
}, this.config.enrichment_interval_ms);
|
|
4484
|
+
this.timers.set("enrichment", timer);
|
|
4426
4485
|
}
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4486
|
+
if (this.proactiveSurface) {
|
|
4487
|
+
this.proactiveSurface.start();
|
|
4488
|
+
this.lastRunTimes.set("proactive_surface", Date.now());
|
|
4489
|
+
}
|
|
4490
|
+
if (this.teamSync) {
|
|
4491
|
+
this.teamSync.start();
|
|
4492
|
+
this.lastRunTimes.set("team_sync", Date.now());
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
/**
|
|
4496
|
+
* Stop all background workers and clear timers.
|
|
4497
|
+
*/
|
|
4498
|
+
stop() {
|
|
4499
|
+
for (const timer of this.timers.values()) {
|
|
4500
|
+
clearInterval(timer);
|
|
4501
|
+
}
|
|
4502
|
+
this.timers.clear();
|
|
4503
|
+
this.proactiveSurface?.stop();
|
|
4504
|
+
this.teamSync?.stop();
|
|
4505
|
+
this.running = false;
|
|
4506
|
+
}
|
|
4507
|
+
/**
|
|
4508
|
+
* Whether workers are currently running.
|
|
4509
|
+
*/
|
|
4510
|
+
isRunning() {
|
|
4511
|
+
return this.running;
|
|
4512
|
+
}
|
|
4513
|
+
/**
|
|
4514
|
+
* Get status of all registered workers.
|
|
4515
|
+
*/
|
|
4516
|
+
getWorkerStatus() {
|
|
4517
|
+
const workerNames = ["decay", "deferred_traces", "compactor", "enrichment", "proactive_surface", "team_sync"];
|
|
4518
|
+
return workerNames.map((name) => ({
|
|
4519
|
+
name,
|
|
4520
|
+
active: this.timers.has(name),
|
|
4521
|
+
last_run_at: this.lastRunTimes.get(name) ?? null
|
|
4522
|
+
}));
|
|
4523
|
+
}
|
|
4524
|
+
// ─── Private ───────────────────────────────────────
|
|
4525
|
+
/**
|
|
4526
|
+
* Find expired deferred traces and resolve them with signal 0.0.
|
|
4527
|
+
*/
|
|
4528
|
+
resolveDeferredTraces() {
|
|
4529
|
+
if (!this.traceStore) return;
|
|
4530
|
+
const now = Date.now();
|
|
4531
|
+
const deferred = this.traceStore.query({ deferred: true, limit: 100 });
|
|
4532
|
+
for (const trace of deferred) {
|
|
4533
|
+
if (trace.deferred_until !== null && trace.deferred_until <= now) {
|
|
4534
|
+
this.traceStore.updateOutcome(trace.id, 0, "timeout", now);
|
|
4436
4535
|
}
|
|
4437
|
-
return c.json({
|
|
4438
|
-
...node,
|
|
4439
|
-
subgraph_summary: {
|
|
4440
|
-
nodeCount: nodes.length,
|
|
4441
|
-
edgeCount: edges.length,
|
|
4442
|
-
last_seen
|
|
4443
|
-
}
|
|
4444
|
-
});
|
|
4445
|
-
} catch {
|
|
4446
|
-
return c.json({
|
|
4447
|
-
...node,
|
|
4448
|
-
subgraph_summary: {
|
|
4449
|
-
nodeCount: 1,
|
|
4450
|
-
edgeCount: 0,
|
|
4451
|
-
last_seen: node.last_seen
|
|
4452
|
-
}
|
|
4453
|
-
});
|
|
4454
4536
|
}
|
|
4455
|
-
}
|
|
4456
|
-
|
|
4457
|
-
}
|
|
4537
|
+
}
|
|
4538
|
+
};
|
|
4458
4539
|
|
|
4459
|
-
// packages/daemon/src/
|
|
4460
|
-
import {
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4540
|
+
// packages/daemon/src/SkillRegistry.ts
|
|
4541
|
+
import { readFileSync as readFileSync6, readdirSync as readdirSync3, existsSync as existsSync7, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
4542
|
+
import { join as join2 } from "node:path";
|
|
4543
|
+
import { homedir as homedir3 } from "node:os";
|
|
4544
|
+
import YAML3 from "yaml";
|
|
4545
|
+
var SkillRegistry = class {
|
|
4546
|
+
skills = /* @__PURE__ */ new Map();
|
|
4547
|
+
builtinNames = /* @__PURE__ */ new Set();
|
|
4548
|
+
builtinDir;
|
|
4549
|
+
customDir;
|
|
4550
|
+
constructor(opts) {
|
|
4551
|
+
this.builtinDir = opts?.builtinDir ?? join2(homedir3(), ".0agent", "skills", "builtin");
|
|
4552
|
+
this.customDir = opts?.customDir ?? join2(homedir3(), ".0agent", "skills", "custom");
|
|
4553
|
+
}
|
|
4554
|
+
/**
|
|
4555
|
+
* Load all skills from builtin + custom directories.
|
|
4556
|
+
*/
|
|
4557
|
+
async loadAll() {
|
|
4558
|
+
this.skills.clear();
|
|
4559
|
+
this.builtinNames.clear();
|
|
4560
|
+
this.loadFromDir(this.builtinDir, true);
|
|
4561
|
+
this.loadFromDir(this.customDir, false);
|
|
4562
|
+
}
|
|
4563
|
+
loadFromDir(dir, isBuiltin) {
|
|
4564
|
+
if (!existsSync7(dir)) return;
|
|
4565
|
+
const files = readdirSync3(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
4566
|
+
for (const file of files) {
|
|
4567
|
+
try {
|
|
4568
|
+
const raw = readFileSync6(join2(dir, file), "utf8");
|
|
4569
|
+
const skill = YAML3.parse(raw);
|
|
4570
|
+
if (skill.name) {
|
|
4571
|
+
this.skills.set(skill.name, skill);
|
|
4572
|
+
if (isBuiltin) this.builtinNames.add(skill.name);
|
|
4573
|
+
}
|
|
4574
|
+
} catch (err) {
|
|
4575
|
+
console.warn(`Failed to load skill ${file}: ${err}`);
|
|
4576
|
+
}
|
|
4481
4577
|
}
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4578
|
+
}
|
|
4579
|
+
/**
|
|
4580
|
+
* Reload all skills (after create/delete).
|
|
4581
|
+
*/
|
|
4582
|
+
async reload() {
|
|
4583
|
+
await this.loadAll();
|
|
4584
|
+
}
|
|
4585
|
+
get(name) {
|
|
4586
|
+
const normalized = name.replace(/^\//, "");
|
|
4587
|
+
return this.skills.get(normalized);
|
|
4588
|
+
}
|
|
4589
|
+
list() {
|
|
4590
|
+
return [...this.skills.values()];
|
|
4591
|
+
}
|
|
4592
|
+
isBuiltin(name) {
|
|
4593
|
+
return this.builtinNames.has(name);
|
|
4594
|
+
}
|
|
4595
|
+
/**
|
|
4596
|
+
* Create a custom skill. Returns the SkillDefinition.
|
|
4597
|
+
* Throws if name conflicts with built-in.
|
|
4598
|
+
*/
|
|
4599
|
+
createCustom(name, yamlContent) {
|
|
4600
|
+
if (this.builtinNames.has(name)) {
|
|
4601
|
+
throw new Error(`Cannot override built-in skill: ${name}`);
|
|
4602
|
+
}
|
|
4603
|
+
mkdirSync3(this.customDir, { recursive: true });
|
|
4604
|
+
const filePath = join2(this.customDir, `${name}.yaml`);
|
|
4605
|
+
writeFileSync3(filePath, yamlContent, "utf8");
|
|
4606
|
+
const skill = YAML3.parse(yamlContent);
|
|
4607
|
+
this.skills.set(name, skill);
|
|
4608
|
+
return skill;
|
|
4609
|
+
}
|
|
4610
|
+
/**
|
|
4611
|
+
* Remove a custom skill. Throws if built-in.
|
|
4612
|
+
*/
|
|
4613
|
+
removeCustom(name) {
|
|
4614
|
+
if (this.builtinNames.has(name)) {
|
|
4615
|
+
throw new Error(`Cannot delete built-in skill: ${name}`);
|
|
4616
|
+
}
|
|
4617
|
+
const filePath = join2(this.customDir, `${name}.yaml`);
|
|
4618
|
+
if (existsSync7(filePath)) {
|
|
4619
|
+
unlinkSync(filePath);
|
|
4620
|
+
}
|
|
4621
|
+
this.skills.delete(name);
|
|
4622
|
+
}
|
|
4623
|
+
get size() {
|
|
4624
|
+
return this.skills.size;
|
|
4625
|
+
}
|
|
4626
|
+
};
|
|
4486
4627
|
|
|
4487
|
-
// packages/daemon/src/
|
|
4488
|
-
import { Hono as
|
|
4489
|
-
|
|
4490
|
-
|
|
4628
|
+
// packages/daemon/src/HTTPServer.ts
|
|
4629
|
+
import { Hono as Hono10 } from "hono";
|
|
4630
|
+
import { serve } from "@hono/node-server";
|
|
4631
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
4632
|
+
import { resolve as resolve6, dirname as dirname3 } from "node:path";
|
|
4633
|
+
import { fileURLToPath } from "node:url";
|
|
4634
|
+
|
|
4635
|
+
// packages/daemon/src/routes/health.ts
|
|
4636
|
+
import { Hono } from "hono";
|
|
4637
|
+
function healthRoutes(deps) {
|
|
4638
|
+
const app = new Hono();
|
|
4491
4639
|
app.get("/", (c) => {
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
app.delete("/:id", (c) => {
|
|
4495
|
-
return c.json({ error: "No subagent system in Phase 2" }, 404);
|
|
4640
|
+
const status = deps.getStatus();
|
|
4641
|
+
return c.json({ ok: true, timestamp: Date.now(), ...status });
|
|
4496
4642
|
});
|
|
4497
4643
|
return app;
|
|
4498
4644
|
}
|
|
4499
4645
|
|
|
4500
|
-
// packages/daemon/src/routes/
|
|
4501
|
-
import { Hono as
|
|
4502
|
-
function
|
|
4503
|
-
const app = new
|
|
4504
|
-
app.get("/", (c) => {
|
|
4505
|
-
const skills = deps.skillRegistry.list();
|
|
4506
|
-
return c.json(skills);
|
|
4507
|
-
});
|
|
4508
|
-
app.get("/:name", (c) => {
|
|
4509
|
-
const name = c.req.param("name");
|
|
4510
|
-
const skill = deps.skillRegistry.get(name);
|
|
4511
|
-
if (!skill) {
|
|
4512
|
-
return c.json({ error: "Skill not found" }, 404);
|
|
4513
|
-
}
|
|
4514
|
-
return c.json(skill);
|
|
4515
|
-
});
|
|
4646
|
+
// packages/daemon/src/routes/sessions.ts
|
|
4647
|
+
import { Hono as Hono2 } from "hono";
|
|
4648
|
+
function sessionRoutes(deps) {
|
|
4649
|
+
const app = new Hono2();
|
|
4516
4650
|
app.post("/", async (c) => {
|
|
4517
4651
|
const body = await c.req.json();
|
|
4518
|
-
if (!body.
|
|
4519
|
-
return c.json({ error: "
|
|
4520
|
-
}
|
|
4521
|
-
if (!body.yaml || typeof body.yaml !== "string") {
|
|
4522
|
-
return c.json({ error: "yaml is required" }, 400);
|
|
4523
|
-
}
|
|
4524
|
-
if (deps.skillRegistry.isBuiltin(body.name)) {
|
|
4525
|
-
return c.json({ error: "Conflicts with built-in skill" }, 409);
|
|
4526
|
-
}
|
|
4527
|
-
try {
|
|
4528
|
-
const skill = deps.skillRegistry.createCustom(body.name, body.yaml);
|
|
4529
|
-
return c.json(skill, 201);
|
|
4530
|
-
} catch (err) {
|
|
4531
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
4532
|
-
return c.json({ error: message }, 500);
|
|
4533
|
-
}
|
|
4534
|
-
});
|
|
4535
|
-
app.delete("/:name", (c) => {
|
|
4536
|
-
const name = c.req.param("name");
|
|
4537
|
-
if (deps.skillRegistry.isBuiltin(name)) {
|
|
4538
|
-
return c.json({ error: "Cannot delete built-in skill" }, 403);
|
|
4539
|
-
}
|
|
4540
|
-
const skill = deps.skillRegistry.get(name);
|
|
4541
|
-
if (!skill) {
|
|
4542
|
-
return c.json({ error: "Skill not found" }, 404);
|
|
4543
|
-
}
|
|
4544
|
-
try {
|
|
4545
|
-
deps.skillRegistry.removeCustom(name);
|
|
4546
|
-
return c.json({ ok: true });
|
|
4547
|
-
} catch (err) {
|
|
4548
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
4549
|
-
return c.json({ error: message }, 500);
|
|
4652
|
+
if (!body.task || typeof body.task !== "string") {
|
|
4653
|
+
return c.json({ error: "task is required and must be a string" }, 400);
|
|
4550
4654
|
}
|
|
4655
|
+
const session = deps.sessions.createSession(body);
|
|
4656
|
+
deps.sessions.runSession(body).catch(() => {
|
|
4657
|
+
});
|
|
4658
|
+
return c.json({ session_id: session.id, status: "pending" }, 201);
|
|
4551
4659
|
});
|
|
4552
|
-
return app;
|
|
4553
|
-
}
|
|
4554
|
-
|
|
4555
|
-
// packages/daemon/src/routes/insights.ts
|
|
4556
|
-
import { Hono as Hono8 } from "hono";
|
|
4557
|
-
function insightsRoutes(deps) {
|
|
4558
|
-
const app = new Hono8();
|
|
4559
4660
|
app.get("/", (c) => {
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
const insights = seen === "false" ? deps.proactiveSurface.getUnseen() : deps.proactiveSurface.getAll();
|
|
4563
|
-
return c.json(insights);
|
|
4564
|
-
});
|
|
4565
|
-
app.post("/:id/seen", (c) => {
|
|
4566
|
-
if (!deps.proactiveSurface) return c.json({ ok: false, error: "not available" }, 404);
|
|
4567
|
-
deps.proactiveSurface.markSeen(c.req.param("id"));
|
|
4568
|
-
return c.json({ ok: true });
|
|
4569
|
-
});
|
|
4570
|
-
return app;
|
|
4571
|
-
}
|
|
4572
|
-
|
|
4573
|
-
// packages/daemon/src/routes/memory.ts
|
|
4574
|
-
import { Hono as Hono9 } from "hono";
|
|
4575
|
-
function memoryRoutes(deps) {
|
|
4576
|
-
const app = new Hono9();
|
|
4577
|
-
app.post("/push", async (c) => {
|
|
4578
|
-
const sync = deps.getSync();
|
|
4579
|
-
if (!sync) return c.json({ error: "GitHub memory not configured. Run: 0agent memory connect github" }, 404);
|
|
4580
|
-
const result = await sync.push();
|
|
4581
|
-
return c.json(result);
|
|
4661
|
+
const sessions = deps.sessions.listSessions();
|
|
4662
|
+
return c.json(sessions);
|
|
4582
4663
|
});
|
|
4583
|
-
app.
|
|
4584
|
-
const
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4664
|
+
app.get("/:id", (c) => {
|
|
4665
|
+
const id = c.req.param("id");
|
|
4666
|
+
const session = deps.sessions.getSession(id);
|
|
4667
|
+
if (!session) {
|
|
4668
|
+
return c.json({ error: "Session not found" }, 404);
|
|
4669
|
+
}
|
|
4670
|
+
return c.json(session);
|
|
4588
4671
|
});
|
|
4589
|
-
app.
|
|
4590
|
-
const
|
|
4591
|
-
|
|
4592
|
-
|
|
4672
|
+
app.delete("/:id", (c) => {
|
|
4673
|
+
const id = c.req.param("id");
|
|
4674
|
+
const session = deps.sessions.getSession(id);
|
|
4675
|
+
if (!session) {
|
|
4676
|
+
return c.json({ error: "Session not found" }, 404);
|
|
4677
|
+
}
|
|
4678
|
+
deps.sessions.cancelSession(id);
|
|
4679
|
+
return c.json({ ok: true });
|
|
4593
4680
|
});
|
|
4594
4681
|
return app;
|
|
4595
4682
|
}
|
|
4596
4683
|
|
|
4597
|
-
// packages/daemon/src/
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
} catch {
|
|
4611
|
-
}
|
|
4612
|
-
}
|
|
4613
|
-
return candidates[0];
|
|
4614
|
-
}
|
|
4615
|
-
var GRAPH_HTML_PATH = findGraphHtml();
|
|
4616
|
-
var HTTPServer = class {
|
|
4617
|
-
app;
|
|
4618
|
-
server = null;
|
|
4619
|
-
deps;
|
|
4620
|
-
constructor(deps) {
|
|
4621
|
-
this.deps = deps;
|
|
4622
|
-
this.app = new Hono10();
|
|
4623
|
-
this.app.route("/api/health", healthRoutes({ getStatus: deps.getStatus }));
|
|
4624
|
-
this.app.route("/api/sessions", sessionRoutes({ sessions: deps.sessions }));
|
|
4625
|
-
this.app.route("/api/graph", graphRoutes({ graph: deps.graph }));
|
|
4626
|
-
this.app.route("/api/entities", entityRoutes({ graph: deps.graph }));
|
|
4627
|
-
this.app.route("/api/traces", traceRoutes({ traceStore: deps.traceStore }));
|
|
4628
|
-
this.app.route("/api/subagents", subagentRoutes());
|
|
4629
|
-
this.app.route("/api/skills", skillRoutes({ skillRegistry: deps.skillRegistry }));
|
|
4630
|
-
this.app.route("/api/insights", insightsRoutes({ proactiveSurface: deps.proactiveSurface ?? null }));
|
|
4631
|
-
this.app.route("/api/memory", memoryRoutes({ getSync: deps.getMemorySync ?? (() => null) }));
|
|
4632
|
-
const serveGraph = (c) => {
|
|
4633
|
-
try {
|
|
4634
|
-
const html = readFileSync6(GRAPH_HTML_PATH, "utf8");
|
|
4635
|
-
return c.html(html);
|
|
4636
|
-
} catch {
|
|
4637
|
-
return c.html("<p>Graph UI not found. Run: pnpm build</p>");
|
|
4638
|
-
}
|
|
4639
|
-
};
|
|
4640
|
-
this.app.get("/", serveGraph);
|
|
4641
|
-
this.app.get("/graph", serveGraph);
|
|
4642
|
-
}
|
|
4643
|
-
start() {
|
|
4644
|
-
return new Promise((resolve12) => {
|
|
4645
|
-
this.server = serve(
|
|
4646
|
-
{
|
|
4647
|
-
fetch: this.app.fetch,
|
|
4648
|
-
port: this.deps.port,
|
|
4649
|
-
hostname: this.deps.host
|
|
4650
|
-
},
|
|
4651
|
-
() => {
|
|
4652
|
-
resolve12();
|
|
4653
|
-
}
|
|
4654
|
-
);
|
|
4655
|
-
});
|
|
4656
|
-
}
|
|
4657
|
-
stop() {
|
|
4658
|
-
return new Promise((resolve12, reject) => {
|
|
4659
|
-
if (!this.server) {
|
|
4660
|
-
resolve12();
|
|
4661
|
-
return;
|
|
4662
|
-
}
|
|
4663
|
-
this.server.close((err) => {
|
|
4664
|
-
if (err) reject(err);
|
|
4665
|
-
else resolve12();
|
|
4666
|
-
});
|
|
4667
|
-
});
|
|
4668
|
-
}
|
|
4669
|
-
getApp() {
|
|
4670
|
-
return this.app;
|
|
4671
|
-
}
|
|
4672
|
-
};
|
|
4673
|
-
|
|
4674
|
-
// packages/daemon/src/LLMExecutor.ts
|
|
4675
|
-
var LLMExecutor = class {
|
|
4676
|
-
constructor(config) {
|
|
4677
|
-
this.config = config;
|
|
4678
|
-
}
|
|
4679
|
-
get isConfigured() {
|
|
4680
|
-
if (this.config.provider === "ollama") return true;
|
|
4681
|
-
return !!this.config.api_key?.trim();
|
|
4682
|
-
}
|
|
4683
|
-
// ─── Single completion (no tools, no streaming) ──────────────────────────
|
|
4684
|
-
async complete(messages, system) {
|
|
4685
|
-
const res = await this.completeWithTools(messages, [], system, void 0);
|
|
4686
|
-
return { content: res.content, tokens_used: res.tokens_used, model: res.model };
|
|
4687
|
-
}
|
|
4688
|
-
// ─── Tool-calling completion with optional streaming ─────────────────────
|
|
4689
|
-
async completeWithTools(messages, tools, system, onToken) {
|
|
4690
|
-
switch (this.config.provider) {
|
|
4691
|
-
case "anthropic":
|
|
4692
|
-
return this.anthropic(messages, tools, system, onToken);
|
|
4693
|
-
case "openai":
|
|
4694
|
-
return this.openai(messages, tools, system, onToken);
|
|
4695
|
-
case "xai":
|
|
4696
|
-
return this.openai(messages, tools, system, onToken, "https://api.x.ai/v1");
|
|
4697
|
-
case "gemini":
|
|
4698
|
-
return this.openai(messages, tools, system, onToken, "https://generativelanguage.googleapis.com/v1beta/openai");
|
|
4699
|
-
case "ollama":
|
|
4700
|
-
return this.ollama(messages, system, onToken);
|
|
4701
|
-
default:
|
|
4702
|
-
return this.openai(messages, tools, system, onToken);
|
|
4703
|
-
}
|
|
4704
|
-
}
|
|
4705
|
-
// ─── Anthropic ───────────────────────────────────────────────────────────
|
|
4706
|
-
async anthropic(messages, tools, system, onToken) {
|
|
4707
|
-
const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
|
|
4708
|
-
const filtered = messages.filter((m) => m.role !== "system");
|
|
4709
|
-
const anthropicMsgs = filtered.map((m) => {
|
|
4710
|
-
if (m.role === "tool") {
|
|
4711
|
-
return {
|
|
4712
|
-
role: "user",
|
|
4713
|
-
content: [{ type: "tool_result", tool_use_id: m.tool_call_id, content: m.content }]
|
|
4714
|
-
};
|
|
4715
|
-
}
|
|
4716
|
-
if (m.role === "assistant" && m.tool_calls?.length) {
|
|
4717
|
-
return {
|
|
4718
|
-
role: "assistant",
|
|
4719
|
-
content: [
|
|
4720
|
-
...m.content ? [{ type: "text", text: m.content }] : [],
|
|
4721
|
-
...m.tool_calls.map((tc) => ({
|
|
4722
|
-
type: "tool_use",
|
|
4723
|
-
id: tc.id,
|
|
4724
|
-
name: tc.name,
|
|
4725
|
-
input: tc.input
|
|
4726
|
-
}))
|
|
4727
|
-
]
|
|
4728
|
-
};
|
|
4729
|
-
}
|
|
4730
|
-
return { role: m.role, content: m.content };
|
|
4684
|
+
// packages/daemon/src/routes/graph.ts
|
|
4685
|
+
import { Hono as Hono3 } from "hono";
|
|
4686
|
+
function graphRoutes(deps) {
|
|
4687
|
+
const app = new Hono3();
|
|
4688
|
+
app.get("/nodes", (c) => {
|
|
4689
|
+
const graph_id = c.req.query("graph_id");
|
|
4690
|
+
const type = c.req.query("type");
|
|
4691
|
+
const limitStr = c.req.query("limit");
|
|
4692
|
+
const limit = limitStr ? parseInt(limitStr, 10) : 50;
|
|
4693
|
+
const results = deps.graph.queryStructural({
|
|
4694
|
+
graph_id: graph_id || void 0,
|
|
4695
|
+
node_type: type || void 0,
|
|
4696
|
+
limit
|
|
4731
4697
|
});
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
if (tools.length > 0) {
|
|
4740
|
-
body.tools = tools.map((t) => ({
|
|
4741
|
-
name: t.name,
|
|
4742
|
-
description: t.description,
|
|
4743
|
-
input_schema: t.input_schema
|
|
4744
|
-
}));
|
|
4698
|
+
return c.json(results.map((r) => r.node));
|
|
4699
|
+
});
|
|
4700
|
+
app.get("/nodes/:id", (c) => {
|
|
4701
|
+
const id = c.req.param("id");
|
|
4702
|
+
const node = deps.graph.getNode(id);
|
|
4703
|
+
if (!node) {
|
|
4704
|
+
return c.json({ error: "Node not found" }, 404);
|
|
4745
4705
|
}
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4706
|
+
return c.json(node);
|
|
4707
|
+
});
|
|
4708
|
+
app.get("/edges", (c) => {
|
|
4709
|
+
const from_node = c.req.query("from_node");
|
|
4710
|
+
const to_node = c.req.query("to_node");
|
|
4711
|
+
const type = c.req.query("type");
|
|
4712
|
+
let edges;
|
|
4713
|
+
if (from_node && to_node) {
|
|
4714
|
+
edges = deps.graph.getEdgesBetween(from_node, to_node);
|
|
4715
|
+
} else if (from_node) {
|
|
4716
|
+
edges = deps.graph.getEdgesFrom(from_node);
|
|
4717
|
+
} else if (to_node) {
|
|
4718
|
+
edges = deps.graph.getEdgesTo(to_node);
|
|
4719
|
+
} else {
|
|
4720
|
+
edges = deps.graph.getAllEdges();
|
|
4721
|
+
}
|
|
4722
|
+
if (type) {
|
|
4723
|
+
edges = edges.filter((e) => e.type === type);
|
|
4724
|
+
}
|
|
4725
|
+
return c.json(edges);
|
|
4726
|
+
});
|
|
4727
|
+
app.post("/query", async (c) => {
|
|
4728
|
+
const body = await c.req.json();
|
|
4729
|
+
const results = deps.graph.queryMerged({
|
|
4730
|
+
structural: body.structural ?? {},
|
|
4731
|
+
semantic: body.semantic,
|
|
4732
|
+
limit: body.limit
|
|
4754
4733
|
});
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4734
|
+
return c.json(results);
|
|
4735
|
+
});
|
|
4736
|
+
return app;
|
|
4737
|
+
}
|
|
4738
|
+
|
|
4739
|
+
// packages/daemon/src/routes/entities.ts
|
|
4740
|
+
import { Hono as Hono4 } from "hono";
|
|
4741
|
+
function entityRoutes(deps) {
|
|
4742
|
+
const app = new Hono4();
|
|
4743
|
+
app.get("/", (c) => {
|
|
4744
|
+
const results = deps.graph.queryStructural({
|
|
4745
|
+
node_type: "entity",
|
|
4746
|
+
limit: 200
|
|
4747
|
+
});
|
|
4748
|
+
return c.json(results.map((r) => r.node));
|
|
4749
|
+
});
|
|
4750
|
+
app.get("/:id", (c) => {
|
|
4751
|
+
const id = c.req.param("id");
|
|
4752
|
+
const node = deps.graph.getNode(id);
|
|
4753
|
+
if (!node) {
|
|
4754
|
+
return c.json({ error: "Entity not found" }, 404);
|
|
4758
4755
|
}
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
const reader = res.body.getReader();
|
|
4768
|
-
const decoder = new TextDecoder();
|
|
4769
|
-
let buf = "";
|
|
4770
|
-
while (true) {
|
|
4771
|
-
const { done, value } = await reader.read();
|
|
4772
|
-
if (done) break;
|
|
4773
|
-
buf += decoder.decode(value, { stream: true });
|
|
4774
|
-
const lines = buf.split("\n");
|
|
4775
|
-
buf = lines.pop() ?? "";
|
|
4776
|
-
for (const line of lines) {
|
|
4777
|
-
if (!line.startsWith("data: ")) continue;
|
|
4778
|
-
const data = line.slice(6).trim();
|
|
4779
|
-
if (data === "[DONE]" || data === "") continue;
|
|
4780
|
-
let evt;
|
|
4781
|
-
try {
|
|
4782
|
-
evt = JSON.parse(data);
|
|
4783
|
-
} catch {
|
|
4784
|
-
continue;
|
|
4785
|
-
}
|
|
4786
|
-
const type = evt.type;
|
|
4787
|
-
if (type === "message_start") {
|
|
4788
|
-
const usage = evt.message?.usage;
|
|
4789
|
-
inputTokens = usage?.input_tokens ?? 0;
|
|
4790
|
-
modelName = evt.message?.model ?? modelName;
|
|
4791
|
-
} else if (type === "content_block_start") {
|
|
4792
|
-
const block = evt.content_block;
|
|
4793
|
-
if (block?.type === "tool_use") {
|
|
4794
|
-
currentToolId = block.id;
|
|
4795
|
-
toolInputBuffers[currentToolId] = "";
|
|
4796
|
-
toolCalls.push({ id: currentToolId, name: block.name, input: {} });
|
|
4797
|
-
}
|
|
4798
|
-
} else if (type === "content_block_delta") {
|
|
4799
|
-
const delta = evt.delta;
|
|
4800
|
-
if (delta?.type === "text_delta") {
|
|
4801
|
-
const token = delta.text ?? "";
|
|
4802
|
-
textContent += token;
|
|
4803
|
-
if (onToken && token) onToken(token);
|
|
4804
|
-
} else if (delta?.type === "input_json_delta") {
|
|
4805
|
-
toolInputBuffers[currentToolId] = (toolInputBuffers[currentToolId] ?? "") + (delta.partial_json ?? "");
|
|
4806
|
-
}
|
|
4807
|
-
} else if (type === "content_block_stop") {
|
|
4808
|
-
if (currentToolId && toolInputBuffers[currentToolId]) {
|
|
4809
|
-
const tc = toolCalls.find((t) => t.id === currentToolId);
|
|
4810
|
-
if (tc) {
|
|
4811
|
-
try {
|
|
4812
|
-
tc.input = JSON.parse(toolInputBuffers[currentToolId]);
|
|
4813
|
-
} catch {
|
|
4814
|
-
}
|
|
4815
|
-
}
|
|
4816
|
-
}
|
|
4817
|
-
} else if (type === "message_delta") {
|
|
4818
|
-
const usage = evt.usage;
|
|
4819
|
-
outputTokens = usage?.output_tokens ?? 0;
|
|
4820
|
-
const stop = evt.delta?.stop_reason;
|
|
4821
|
-
if (stop === "tool_use") stopReason = "tool_use";
|
|
4822
|
-
else if (stop === "end_turn") stopReason = "end_turn";
|
|
4823
|
-
else if (stop === "max_tokens") stopReason = "max_tokens";
|
|
4756
|
+
try {
|
|
4757
|
+
const subgraph = deps.graph.getSubGraph(id, 2);
|
|
4758
|
+
const nodes = subgraph.getNodes();
|
|
4759
|
+
const edges = subgraph.getEdges();
|
|
4760
|
+
let last_seen = node.last_seen;
|
|
4761
|
+
for (const n of nodes) {
|
|
4762
|
+
if (n.last_seen > last_seen) {
|
|
4763
|
+
last_seen = n.last_seen;
|
|
4824
4764
|
}
|
|
4825
4765
|
}
|
|
4766
|
+
return c.json({
|
|
4767
|
+
...node,
|
|
4768
|
+
subgraph_summary: {
|
|
4769
|
+
nodeCount: nodes.length,
|
|
4770
|
+
edgeCount: edges.length,
|
|
4771
|
+
last_seen
|
|
4772
|
+
}
|
|
4773
|
+
});
|
|
4774
|
+
} catch {
|
|
4775
|
+
return c.json({
|
|
4776
|
+
...node,
|
|
4777
|
+
subgraph_summary: {
|
|
4778
|
+
nodeCount: 1,
|
|
4779
|
+
edgeCount: 0,
|
|
4780
|
+
last_seen: node.last_seen
|
|
4781
|
+
}
|
|
4782
|
+
});
|
|
4826
4783
|
}
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
const
|
|
4838
|
-
const
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4784
|
+
});
|
|
4785
|
+
return app;
|
|
4786
|
+
}
|
|
4787
|
+
|
|
4788
|
+
// packages/daemon/src/routes/traces.ts
|
|
4789
|
+
import { Hono as Hono5 } from "hono";
|
|
4790
|
+
function traceRoutes(deps) {
|
|
4791
|
+
const app = new Hono5();
|
|
4792
|
+
app.get("/", (c) => {
|
|
4793
|
+
const session_id = c.req.query("session_id");
|
|
4794
|
+
const deferredStr = c.req.query("deferred");
|
|
4795
|
+
const limitStr = c.req.query("limit");
|
|
4796
|
+
const deferred = deferredStr !== void 0 && deferredStr !== "" ? deferredStr === "true" : void 0;
|
|
4797
|
+
const limit = limitStr ? parseInt(limitStr, 10) : void 0;
|
|
4798
|
+
const traces = deps.traceStore.query({
|
|
4799
|
+
session_id: session_id || void 0,
|
|
4800
|
+
deferred,
|
|
4801
|
+
limit
|
|
4802
|
+
});
|
|
4803
|
+
return c.json(traces);
|
|
4804
|
+
});
|
|
4805
|
+
app.get("/:id", (c) => {
|
|
4806
|
+
const id = c.req.param("id");
|
|
4807
|
+
const trace = deps.traceStore.get(id);
|
|
4808
|
+
if (!trace) {
|
|
4809
|
+
return c.json({ error: "Trace not found" }, 404);
|
|
4810
|
+
}
|
|
4811
|
+
return c.json(trace);
|
|
4812
|
+
});
|
|
4813
|
+
return app;
|
|
4814
|
+
}
|
|
4815
|
+
|
|
4816
|
+
// packages/daemon/src/routes/subagents.ts
|
|
4817
|
+
import { Hono as Hono6 } from "hono";
|
|
4818
|
+
function subagentRoutes() {
|
|
4819
|
+
const app = new Hono6();
|
|
4820
|
+
app.get("/", (c) => {
|
|
4821
|
+
return c.json([]);
|
|
4822
|
+
});
|
|
4823
|
+
app.delete("/:id", (c) => {
|
|
4824
|
+
return c.json({ error: "No subagent system in Phase 2" }, 404);
|
|
4825
|
+
});
|
|
4826
|
+
return app;
|
|
4827
|
+
}
|
|
4828
|
+
|
|
4829
|
+
// packages/daemon/src/routes/skills.ts
|
|
4830
|
+
import { Hono as Hono7 } from "hono";
|
|
4831
|
+
function skillRoutes(deps) {
|
|
4832
|
+
const app = new Hono7();
|
|
4833
|
+
app.get("/", (c) => {
|
|
4834
|
+
const skills = deps.skillRegistry.list();
|
|
4835
|
+
return c.json(skills);
|
|
4836
|
+
});
|
|
4837
|
+
app.get("/:name", (c) => {
|
|
4838
|
+
const name = c.req.param("name");
|
|
4839
|
+
const skill = deps.skillRegistry.get(name);
|
|
4840
|
+
if (!skill) {
|
|
4841
|
+
return c.json({ error: "Skill not found" }, 404);
|
|
4856
4842
|
}
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
};
|
|
4864
|
-
if (tools.length > 0) {
|
|
4865
|
-
body.tools = tools.map((t) => ({
|
|
4866
|
-
type: "function",
|
|
4867
|
-
function: { name: t.name, description: t.description, parameters: t.input_schema }
|
|
4868
|
-
}));
|
|
4843
|
+
return c.json(skill);
|
|
4844
|
+
});
|
|
4845
|
+
app.post("/", async (c) => {
|
|
4846
|
+
const body = await c.req.json();
|
|
4847
|
+
if (!body.name || typeof body.name !== "string") {
|
|
4848
|
+
return c.json({ error: "name is required" }, 400);
|
|
4869
4849
|
}
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
headers: {
|
|
4873
|
-
"Content-Type": "application/json",
|
|
4874
|
-
"Authorization": `Bearer ${this.config.api_key}`
|
|
4875
|
-
},
|
|
4876
|
-
body: JSON.stringify(body)
|
|
4877
|
-
});
|
|
4878
|
-
if (!res.ok) {
|
|
4879
|
-
const err = await res.text();
|
|
4880
|
-
throw new Error(`OpenAI ${res.status}: ${err}`);
|
|
4850
|
+
if (!body.yaml || typeof body.yaml !== "string") {
|
|
4851
|
+
return c.json({ error: "yaml is required" }, 400);
|
|
4881
4852
|
}
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
let modelName = this.config.model;
|
|
4885
|
-
let stopReason = "end_turn";
|
|
4886
|
-
const toolCallMap = {};
|
|
4887
|
-
const reader = res.body.getReader();
|
|
4888
|
-
const decoder = new TextDecoder();
|
|
4889
|
-
let buf = "";
|
|
4890
|
-
while (true) {
|
|
4891
|
-
const { done, value } = await reader.read();
|
|
4892
|
-
if (done) break;
|
|
4893
|
-
buf += decoder.decode(value, { stream: true });
|
|
4894
|
-
const lines = buf.split("\n");
|
|
4895
|
-
buf = lines.pop() ?? "";
|
|
4896
|
-
for (const line of lines) {
|
|
4897
|
-
if (!line.startsWith("data: ")) continue;
|
|
4898
|
-
const data = line.slice(6).trim();
|
|
4899
|
-
if (data === "[DONE]") continue;
|
|
4900
|
-
let evt;
|
|
4901
|
-
try {
|
|
4902
|
-
evt = JSON.parse(data);
|
|
4903
|
-
} catch {
|
|
4904
|
-
continue;
|
|
4905
|
-
}
|
|
4906
|
-
modelName = evt.model ?? modelName;
|
|
4907
|
-
const usage = evt.usage;
|
|
4908
|
-
if (usage?.total_tokens) tokensUsed = usage.total_tokens;
|
|
4909
|
-
const choices = evt.choices;
|
|
4910
|
-
if (!choices?.length) continue;
|
|
4911
|
-
const delta = choices[0].delta;
|
|
4912
|
-
if (!delta) continue;
|
|
4913
|
-
const finish = choices[0].finish_reason;
|
|
4914
|
-
if (finish === "tool_calls") stopReason = "tool_use";
|
|
4915
|
-
else if (finish === "stop") stopReason = "end_turn";
|
|
4916
|
-
const token = delta.content;
|
|
4917
|
-
if (token) {
|
|
4918
|
-
textContent += token;
|
|
4919
|
-
if (onToken) onToken(token);
|
|
4920
|
-
}
|
|
4921
|
-
const toolCallDeltas = delta.tool_calls;
|
|
4922
|
-
if (toolCallDeltas) {
|
|
4923
|
-
for (const tc of toolCallDeltas) {
|
|
4924
|
-
const idx = tc.index;
|
|
4925
|
-
if (!toolCallMap[idx]) {
|
|
4926
|
-
toolCallMap[idx] = { id: "", name: "", args: "" };
|
|
4927
|
-
}
|
|
4928
|
-
const fn = tc.function;
|
|
4929
|
-
if (tc.id) toolCallMap[idx].id = tc.id;
|
|
4930
|
-
if (fn?.name) toolCallMap[idx].name = fn.name;
|
|
4931
|
-
if (fn?.arguments) toolCallMap[idx].args += fn.arguments;
|
|
4932
|
-
}
|
|
4933
|
-
}
|
|
4934
|
-
}
|
|
4853
|
+
if (deps.skillRegistry.isBuiltin(body.name)) {
|
|
4854
|
+
return c.json({ error: "Conflicts with built-in skill" }, 409);
|
|
4935
4855
|
}
|
|
4936
|
-
|
|
4937
|
-
|
|
4856
|
+
try {
|
|
4857
|
+
const skill = deps.skillRegistry.createCustom(body.name, body.yaml);
|
|
4858
|
+
return c.json(skill, 201);
|
|
4859
|
+
} catch (err) {
|
|
4860
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4861
|
+
return c.json({ error: message }, 500);
|
|
4862
|
+
}
|
|
4863
|
+
});
|
|
4864
|
+
app.delete("/:name", (c) => {
|
|
4865
|
+
const name = c.req.param("name");
|
|
4866
|
+
if (deps.skillRegistry.isBuiltin(name)) {
|
|
4867
|
+
return c.json({ error: "Cannot delete built-in skill" }, 403);
|
|
4868
|
+
}
|
|
4869
|
+
const skill = deps.skillRegistry.get(name);
|
|
4870
|
+
if (!skill) {
|
|
4871
|
+
return c.json({ error: "Skill not found" }, 404);
|
|
4872
|
+
}
|
|
4873
|
+
try {
|
|
4874
|
+
deps.skillRegistry.removeCustom(name);
|
|
4875
|
+
return c.json({ ok: true });
|
|
4876
|
+
} catch (err) {
|
|
4877
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4878
|
+
return c.json({ error: message }, 500);
|
|
4879
|
+
}
|
|
4880
|
+
});
|
|
4881
|
+
return app;
|
|
4882
|
+
}
|
|
4883
|
+
|
|
4884
|
+
// packages/daemon/src/routes/insights.ts
|
|
4885
|
+
import { Hono as Hono8 } from "hono";
|
|
4886
|
+
function insightsRoutes(deps) {
|
|
4887
|
+
const app = new Hono8();
|
|
4888
|
+
app.get("/", (c) => {
|
|
4889
|
+
if (!deps.proactiveSurface) return c.json([]);
|
|
4890
|
+
const seen = c.req.query("seen");
|
|
4891
|
+
const insights = seen === "false" ? deps.proactiveSurface.getUnseen() : deps.proactiveSurface.getAll();
|
|
4892
|
+
return c.json(insights);
|
|
4893
|
+
});
|
|
4894
|
+
app.post("/:id/seen", (c) => {
|
|
4895
|
+
if (!deps.proactiveSurface) return c.json({ ok: false, error: "not available" }, 404);
|
|
4896
|
+
deps.proactiveSurface.markSeen(c.req.param("id"));
|
|
4897
|
+
return c.json({ ok: true });
|
|
4898
|
+
});
|
|
4899
|
+
return app;
|
|
4900
|
+
}
|
|
4901
|
+
|
|
4902
|
+
// packages/daemon/src/routes/memory.ts
|
|
4903
|
+
import { Hono as Hono9 } from "hono";
|
|
4904
|
+
function memoryRoutes(deps) {
|
|
4905
|
+
const app = new Hono9();
|
|
4906
|
+
app.post("/push", async (c) => {
|
|
4907
|
+
const sync = deps.getSync();
|
|
4908
|
+
if (!sync) return c.json({ error: "GitHub memory not configured. Run: 0agent memory connect github" }, 404);
|
|
4909
|
+
const result = await sync.push();
|
|
4910
|
+
return c.json(result);
|
|
4911
|
+
});
|
|
4912
|
+
app.post("/pull", async (c) => {
|
|
4913
|
+
const sync = deps.getSync();
|
|
4914
|
+
if (!sync) return c.json({ error: "GitHub memory not configured." }, 404);
|
|
4915
|
+
const result = await sync.pull();
|
|
4916
|
+
return c.json(result);
|
|
4917
|
+
});
|
|
4918
|
+
app.get("/status", (c) => {
|
|
4919
|
+
const sync = deps.getSync();
|
|
4920
|
+
if (!sync) return c.json({ configured: false });
|
|
4921
|
+
return c.json({ configured: true, ...sync.getLastSyncTimes() });
|
|
4922
|
+
});
|
|
4923
|
+
return app;
|
|
4924
|
+
}
|
|
4925
|
+
|
|
4926
|
+
// packages/daemon/src/HTTPServer.ts
|
|
4927
|
+
function findGraphHtml() {
|
|
4928
|
+
const candidates = [
|
|
4929
|
+
resolve6(dirname3(fileURLToPath(import.meta.url)), "graph.html"),
|
|
4930
|
+
// dev (src/)
|
|
4931
|
+
resolve6(dirname3(fileURLToPath(import.meta.url)), "..", "graph.html"),
|
|
4932
|
+
// bundled (dist/../)
|
|
4933
|
+
resolve6(dirname3(fileURLToPath(import.meta.url)), "..", "dist", "graph.html")
|
|
4934
|
+
];
|
|
4935
|
+
for (const p of candidates) {
|
|
4936
|
+
try {
|
|
4937
|
+
readFileSync7(p);
|
|
4938
|
+
return p;
|
|
4939
|
+
} catch {
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
return candidates[0];
|
|
4943
|
+
}
|
|
4944
|
+
var GRAPH_HTML_PATH = findGraphHtml();
|
|
4945
|
+
var HTTPServer = class {
|
|
4946
|
+
app;
|
|
4947
|
+
server = null;
|
|
4948
|
+
deps;
|
|
4949
|
+
constructor(deps) {
|
|
4950
|
+
this.deps = deps;
|
|
4951
|
+
this.app = new Hono10();
|
|
4952
|
+
this.app.route("/api/health", healthRoutes({ getStatus: deps.getStatus }));
|
|
4953
|
+
this.app.route("/api/sessions", sessionRoutes({ sessions: deps.sessions }));
|
|
4954
|
+
this.app.route("/api/graph", graphRoutes({ graph: deps.graph }));
|
|
4955
|
+
this.app.route("/api/entities", entityRoutes({ graph: deps.graph }));
|
|
4956
|
+
this.app.route("/api/traces", traceRoutes({ traceStore: deps.traceStore }));
|
|
4957
|
+
this.app.route("/api/subagents", subagentRoutes());
|
|
4958
|
+
this.app.route("/api/skills", skillRoutes({ skillRegistry: deps.skillRegistry }));
|
|
4959
|
+
this.app.route("/api/insights", insightsRoutes({ proactiveSurface: deps.proactiveSurface ?? null }));
|
|
4960
|
+
this.app.route("/api/memory", memoryRoutes({ getSync: deps.getMemorySync ?? (() => null) }));
|
|
4961
|
+
const serveGraph = (c) => {
|
|
4938
4962
|
try {
|
|
4939
|
-
|
|
4963
|
+
const html = readFileSync7(GRAPH_HTML_PATH, "utf8");
|
|
4964
|
+
return c.html(html);
|
|
4940
4965
|
} catch {
|
|
4966
|
+
return c.html("<p>Graph UI not found. Run: pnpm build</p>");
|
|
4941
4967
|
}
|
|
4942
|
-
return { id: tc.id, name: tc.name, input };
|
|
4943
|
-
});
|
|
4944
|
-
return {
|
|
4945
|
-
content: textContent,
|
|
4946
|
-
tool_calls: toolCalls.length > 0 ? toolCalls : null,
|
|
4947
|
-
stop_reason: stopReason,
|
|
4948
|
-
tokens_used: tokensUsed,
|
|
4949
|
-
model: modelName
|
|
4950
4968
|
};
|
|
4969
|
+
this.app.get("/", serveGraph);
|
|
4970
|
+
this.app.get("/graph", serveGraph);
|
|
4951
4971
|
}
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4972
|
+
start() {
|
|
4973
|
+
return new Promise((resolve13) => {
|
|
4974
|
+
this.server = serve(
|
|
4975
|
+
{
|
|
4976
|
+
fetch: this.app.fetch,
|
|
4977
|
+
port: this.deps.port,
|
|
4978
|
+
hostname: this.deps.host
|
|
4979
|
+
},
|
|
4980
|
+
() => {
|
|
4981
|
+
resolve13();
|
|
4982
|
+
}
|
|
4983
|
+
);
|
|
4963
4984
|
});
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4985
|
+
}
|
|
4986
|
+
stop() {
|
|
4987
|
+
return new Promise((resolve13, reject) => {
|
|
4988
|
+
if (!this.server) {
|
|
4989
|
+
resolve13();
|
|
4990
|
+
return;
|
|
4991
|
+
}
|
|
4992
|
+
this.server.close((err) => {
|
|
4993
|
+
if (err) reject(err);
|
|
4994
|
+
else resolve13();
|
|
4995
|
+
});
|
|
4996
|
+
});
|
|
4997
|
+
}
|
|
4998
|
+
getApp() {
|
|
4999
|
+
return this.app;
|
|
4968
5000
|
}
|
|
4969
5001
|
};
|
|
4970
5002
|
|
|
4971
5003
|
// packages/daemon/src/IdentityManager.ts
|
|
4972
5004
|
init_src();
|
|
4973
|
-
import { readFileSync as
|
|
4974
|
-
import { resolve as
|
|
4975
|
-
import { homedir as
|
|
4976
|
-
import
|
|
4977
|
-
var IDENTITY_PATH =
|
|
5005
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "node:fs";
|
|
5006
|
+
import { resolve as resolve7, dirname as dirname4 } from "node:path";
|
|
5007
|
+
import { homedir as homedir4, hostname } from "node:os";
|
|
5008
|
+
import YAML4 from "yaml";
|
|
5009
|
+
var IDENTITY_PATH = resolve7(homedir4(), ".0agent", "identity.yaml");
|
|
4978
5010
|
var DEFAULT_IDENTITY = {
|
|
4979
5011
|
name: "User",
|
|
4980
5012
|
device_id: `unknown-device`,
|
|
@@ -4990,9 +5022,9 @@ var IdentityManager = class {
|
|
|
4990
5022
|
* Load or create identity. Call once at daemon startup.
|
|
4991
5023
|
*/
|
|
4992
5024
|
async init() {
|
|
4993
|
-
if (
|
|
4994
|
-
const raw =
|
|
4995
|
-
this.identity =
|
|
5025
|
+
if (existsSync8(IDENTITY_PATH)) {
|
|
5026
|
+
const raw = readFileSync8(IDENTITY_PATH, "utf8");
|
|
5027
|
+
this.identity = YAML4.parse(raw);
|
|
4996
5028
|
} else {
|
|
4997
5029
|
this.identity = {
|
|
4998
5030
|
...DEFAULT_IDENTITY,
|
|
@@ -5043,24 +5075,24 @@ var IdentityManager = class {
|
|
|
5043
5075
|
}
|
|
5044
5076
|
save() {
|
|
5045
5077
|
const dir = dirname4(IDENTITY_PATH);
|
|
5046
|
-
if (!
|
|
5078
|
+
if (!existsSync8(dir)) {
|
|
5047
5079
|
mkdirSync4(dir, { recursive: true });
|
|
5048
5080
|
}
|
|
5049
|
-
writeFileSync4(IDENTITY_PATH,
|
|
5081
|
+
writeFileSync4(IDENTITY_PATH, YAML4.stringify(this.identity), "utf8");
|
|
5050
5082
|
}
|
|
5051
5083
|
};
|
|
5052
5084
|
|
|
5053
5085
|
// packages/daemon/src/TeamManager.ts
|
|
5054
|
-
import { readFileSync as
|
|
5055
|
-
import { resolve as
|
|
5056
|
-
import { homedir as
|
|
5057
|
-
import
|
|
5058
|
-
var TEAMS_PATH =
|
|
5086
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "node:fs";
|
|
5087
|
+
import { resolve as resolve8 } from "node:path";
|
|
5088
|
+
import { homedir as homedir5 } from "node:os";
|
|
5089
|
+
import YAML5 from "yaml";
|
|
5090
|
+
var TEAMS_PATH = resolve8(homedir5(), ".0agent", "teams.yaml");
|
|
5059
5091
|
var TeamManager = class {
|
|
5060
5092
|
config;
|
|
5061
5093
|
constructor() {
|
|
5062
|
-
if (
|
|
5063
|
-
this.config =
|
|
5094
|
+
if (existsSync9(TEAMS_PATH)) {
|
|
5095
|
+
this.config = YAML5.parse(readFileSync9(TEAMS_PATH, "utf8"));
|
|
5064
5096
|
} else {
|
|
5065
5097
|
this.config = { memberships: [] };
|
|
5066
5098
|
}
|
|
@@ -5115,8 +5147,8 @@ var TeamManager = class {
|
|
|
5115
5147
|
}
|
|
5116
5148
|
}
|
|
5117
5149
|
save() {
|
|
5118
|
-
mkdirSync5(
|
|
5119
|
-
writeFileSync5(TEAMS_PATH,
|
|
5150
|
+
mkdirSync5(resolve8(homedir5(), ".0agent"), { recursive: true });
|
|
5151
|
+
writeFileSync5(TEAMS_PATH, YAML5.stringify(this.config), "utf8");
|
|
5120
5152
|
}
|
|
5121
5153
|
};
|
|
5122
5154
|
|
|
@@ -5199,9 +5231,9 @@ var TeamSync = class {
|
|
|
5199
5231
|
};
|
|
5200
5232
|
|
|
5201
5233
|
// packages/daemon/src/GitHubMemorySync.ts
|
|
5202
|
-
import { readFileSync as
|
|
5203
|
-
import { resolve as
|
|
5204
|
-
import { homedir as
|
|
5234
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync10, readdirSync as readdirSync4 } from "node:fs";
|
|
5235
|
+
import { resolve as resolve9 } from "node:path";
|
|
5236
|
+
import { homedir as homedir6 } from "node:os";
|
|
5205
5237
|
var GITHUB_API = "https://api.github.com";
|
|
5206
5238
|
async function ghFetch(path, token, opts) {
|
|
5207
5239
|
return fetch(`${GITHUB_API}${path}`, {
|
|
@@ -5320,10 +5352,10 @@ var GitHubMemorySync = class {
|
|
|
5320
5352
|
)
|
|
5321
5353
|
);
|
|
5322
5354
|
}
|
|
5323
|
-
const customSkillsDir =
|
|
5324
|
-
if (
|
|
5355
|
+
const customSkillsDir = resolve9(homedir6(), ".0agent", "skills", "custom");
|
|
5356
|
+
if (existsSync10(customSkillsDir)) {
|
|
5325
5357
|
for (const file of readdirSync4(customSkillsDir).filter((f) => f.endsWith(".yaml"))) {
|
|
5326
|
-
const content =
|
|
5358
|
+
const content = readFileSync10(resolve9(customSkillsDir, file), "utf8");
|
|
5327
5359
|
pushes.push(putFile(token, owner, repo, `skills/custom/${file}`, content, commitMsg));
|
|
5328
5360
|
}
|
|
5329
5361
|
}
|
|
@@ -5508,7 +5540,7 @@ var GitHubMemorySync = class {
|
|
|
5508
5540
|
}
|
|
5509
5541
|
async pullCustomSkills() {
|
|
5510
5542
|
const { token, owner, repo } = this.config;
|
|
5511
|
-
const dir =
|
|
5543
|
+
const dir = resolve9(homedir6(), ".0agent", "skills", "custom");
|
|
5512
5544
|
try {
|
|
5513
5545
|
const res = await ghFetch(`/repos/${owner}/${repo}/contents/skills/custom`, token);
|
|
5514
5546
|
if (!res.ok) return;
|
|
@@ -5518,7 +5550,7 @@ var GitHubMemorySync = class {
|
|
|
5518
5550
|
if (content) {
|
|
5519
5551
|
const { mkdirSync: mkdirSync7 } = await import("node:fs");
|
|
5520
5552
|
mkdirSync7(dir, { recursive: true });
|
|
5521
|
-
writeFileSync6(
|
|
5553
|
+
writeFileSync6(resolve9(dir, file.name), content, "utf8");
|
|
5522
5554
|
}
|
|
5523
5555
|
}
|
|
5524
5556
|
} catch {
|
|
@@ -5582,12 +5614,12 @@ var ZeroAgentDaemon = class {
|
|
|
5582
5614
|
startedAt = 0;
|
|
5583
5615
|
pidFilePath;
|
|
5584
5616
|
constructor() {
|
|
5585
|
-
this.pidFilePath =
|
|
5617
|
+
this.pidFilePath = resolve11(homedir7(), ".0agent", "daemon.pid");
|
|
5586
5618
|
}
|
|
5587
5619
|
async start(opts) {
|
|
5588
5620
|
this.config = await loadConfig(opts?.config_path);
|
|
5589
|
-
const dotDir =
|
|
5590
|
-
if (!
|
|
5621
|
+
const dotDir = resolve11(homedir7(), ".0agent");
|
|
5622
|
+
if (!existsSync12(dotDir)) {
|
|
5591
5623
|
mkdirSync6(dotDir, { recursive: true });
|
|
5592
5624
|
}
|
|
5593
5625
|
this.adapter = new SQLiteAdapter({ db_path: this.config.graph.db_path });
|
|
@@ -5739,7 +5771,7 @@ var ZeroAgentDaemon = class {
|
|
|
5739
5771
|
this.graph = null;
|
|
5740
5772
|
}
|
|
5741
5773
|
this.adapter = null;
|
|
5742
|
-
if (
|
|
5774
|
+
if (existsSync12(this.pidFilePath)) {
|
|
5743
5775
|
try {
|
|
5744
5776
|
unlinkSync2(this.pidFilePath);
|
|
5745
5777
|
} catch {
|
|
@@ -5769,11 +5801,11 @@ var ZeroAgentDaemon = class {
|
|
|
5769
5801
|
};
|
|
5770
5802
|
|
|
5771
5803
|
// packages/daemon/src/start.ts
|
|
5772
|
-
import { resolve as
|
|
5773
|
-
import { homedir as
|
|
5774
|
-
import { existsSync as
|
|
5775
|
-
var CONFIG_PATH = process.env["ZEROAGENT_CONFIG"] ??
|
|
5776
|
-
if (!
|
|
5804
|
+
import { resolve as resolve12 } from "node:path";
|
|
5805
|
+
import { homedir as homedir8 } from "node:os";
|
|
5806
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
5807
|
+
var CONFIG_PATH = process.env["ZEROAGENT_CONFIG"] ?? resolve12(homedir8(), ".0agent", "config.yaml");
|
|
5808
|
+
if (!existsSync13(CONFIG_PATH)) {
|
|
5777
5809
|
console.error(`
|
|
5778
5810
|
0agent is not initialised.
|
|
5779
5811
|
|