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/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((resolve12) => setTimeout(resolve12, ms));
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((resolve12) => {
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
- resolve12(output || (code === 0 ? "(command completed, no output)" : `exit code ${code}`));
2627
+ resolve13(output || (code === 0 ? "(command completed, no output)" : `exit code ${code}`));
2628
2628
  });
2629
2629
  proc.on("error", (err) => {
2630
- resolve12(`Error: ${err.message}`);
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 existsSync10, readFileSync as readFileSync10, statSync, readdirSync as readdirSync5 } from "node:fs";
2937
- import { resolve as resolve9, join as join3 } from "node:path";
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 (!existsSync10(resolve9(this.cwd, ".git"))) return;
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 (!existsSync10(dir)) continue;
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 = readFileSync10(path, "utf8");
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 existsSync11, mkdirSync as mkdirSync6 } from "node:fs";
3092
- import { resolve as resolve10 } from "node:path";
3093
- import { homedir as homedir6 } from "node:os";
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/SessionManager.ts
3316
- init_AgentExecutor();
3317
-
3318
- // packages/daemon/src/AnthropicSkillFetcher.ts
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
- // packages/daemon/src/ProjectScanner.ts
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
- async scan() {
3448
- const [stack, name] = this.detectStack();
3449
- const recentCommits = this.getRecentCommits();
3450
- const dirtyFiles = this.getDirtyFiles();
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
- * Build a compact system prompt injection from the context.
3465
- */
3466
- static buildContextPrompt(ctx) {
3467
- const lines = [`Working directory: ${ctx.cwd}`];
3468
- if (ctx.name) lines.push(`Project: ${ctx.name}`);
3469
- if (ctx.stack.length) lines.push(`Stack: ${ctx.stack.join(", ")}`);
3470
- if (ctx.recent_commits.length) {
3471
- lines.push(`Recent commits: ${ctx.recent_commits.slice(0, 3).join(" | ")}`);
3472
- }
3473
- if (ctx.dirty_files.length) {
3474
- lines.push(`Uncommitted changes: ${ctx.dirty_files.slice(0, 5).join(", ")}`);
3475
- }
3476
- if (ctx.running_ports.length) {
3477
- lines.push(`Running servers: ports ${ctx.running_ports.join(", ")}`);
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
- detectStack() {
3485
- const stack = [];
3486
- let name = "";
3487
- const pkgPath = join(this.cwd, "package.json");
3488
- if (existsSync4(pkgPath)) {
3489
- try {
3490
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
3491
- name = pkg.name ?? "";
3492
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
3493
- stack.push("node");
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
- if (existsSync4(join(this.cwd, "Cargo.toml"))) {
3504
- stack.push("rust");
3505
- try {
3506
- const cargo = readFileSync4(join(this.cwd, "Cargo.toml"), "utf8");
3507
- const nameMatch = cargo.match(/^name\s*=\s*"([^"]+)"/m);
3508
- if (nameMatch && !name) name = nameMatch[1];
3509
- } catch {
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
- if (existsSync4(join(this.cwd, "pyproject.toml")) || existsSync4(join(this.cwd, "requirements.txt"))) {
3513
- stack.push("python");
3514
- }
3515
- if (existsSync4(join(this.cwd, "go.mod"))) stack.push("go");
3516
- return [stack, name];
3517
- }
3518
- getRecentCommits() {
3519
- try {
3520
- const out = execSync3("git log --oneline -5 2>/dev/null", {
3521
- cwd: this.cwd,
3522
- timeout: 3e3,
3523
- encoding: "utf8"
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
- getDirtyFiles() {
3531
- try {
3532
- const out = execSync3("git status --short 2>/dev/null", {
3533
- cwd: this.cwd,
3534
- timeout: 3e3,
3535
- encoding: "utf8"
3536
- }).trim();
3537
- return out ? out.split("\n").map((l) => l.slice(3).trim()) : [];
3538
- } catch {
3539
- return [];
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
- async getRunningPorts() {
3543
- const open = [];
3544
- await Promise.all(PORTS_TO_CHECK.map(
3545
- (port) => new Promise((resolve12) => {
3546
- const s = createServer();
3547
- s.listen(port, "127.0.0.1", () => {
3548
- s.close();
3549
- resolve12();
3550
- });
3551
- s.on("error", () => {
3552
- open.push(port);
3553
- resolve12();
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
- setTimeout(() => {
3556
- s.close();
3557
- resolve12();
3558
- }, 200);
3559
- })
3560
- ));
3561
- return open;
3562
- }
3563
- getReadmeSummary() {
3564
- for (const name of ["README.md", "readme.md", "README.txt", "README"]) {
3565
- const p = join(this.cwd, name);
3566
- if (existsSync4(p)) {
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
- return readFileSync4(p, "utf8").slice(0, 300).replace(/\n+/g, " ").trim();
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
- return "";
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
- // packages/daemon/src/ConversationStore.ts
3578
- var CREATE_TABLE = `
3579
- CREATE TABLE IF NOT EXISTS conversations (
3580
- id TEXT PRIMARY KEY,
3581
- session_id TEXT NOT NULL,
3582
- user_entity_id TEXT NOT NULL,
3583
- role TEXT NOT NULL,
3584
- content TEXT NOT NULL,
3585
- created_at INTEGER NOT NULL
3586
- );
3587
- CREATE INDEX IF NOT EXISTS idx_conv_user ON conversations(user_entity_id, created_at);
3588
- `;
3589
- var ConversationStore = class {
3590
- constructor(adapter) {
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
- var SessionManager = class {
3634
- sessions = /* @__PURE__ */ new Map();
3635
- inferenceEngine;
3636
- eventBus;
3637
- graph;
3638
- llm;
3639
- cwd;
3640
- identity;
3641
- projectContext;
3642
- conversationStore;
3643
- weightUpdater;
3644
- anthropicFetcher = new AnthropicSkillFetcher();
3645
- constructor(deps = {}) {
3646
- this.inferenceEngine = deps.inferenceEngine;
3647
- this.eventBus = deps.eventBus;
3648
- this.graph = deps.graph;
3649
- this.llm = deps.llm;
3650
- this.cwd = deps.cwd ?? process.cwd();
3651
- this.identity = deps.identity;
3652
- this.projectContext = deps.projectContext;
3653
- if (deps.adapter) {
3654
- this.conversationStore = new ConversationStore(deps.adapter);
3655
- this.conversationStore.init();
3656
- const wLog = new WeightEventLog(deps.adapter);
3657
- this.weightUpdater = new EdgeWeightUpdater(deps.adapter, wLog);
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
- * Transition session to 'running' and optionally invoke inference engine.
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 startSession(id) {
3681
- const session = this.getSessionOrThrow(id);
3682
- session.status = "running";
3683
- session.started_at = Date.now();
3684
- this.emit({
3685
- type: "session.started",
3686
- session_id: session.id,
3687
- task: session.task
3688
- });
3689
- if (this.inferenceEngine) {
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 plan = await this.inferenceEngine.resolve(session.task);
3692
- session.plan = plan;
3693
- if (plan.skill) {
3694
- session.skill = plan.skill;
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 session;
3668
+ return null;
3700
3669
  }
3701
3670
  /**
3702
- * Append a step to a running session.
3671
+ * Parse a SKILL.md file into a FetchedSkill.
3672
+ * Extracts name/description from YAML frontmatter, instructions from body.
3703
3673
  */
3704
- addStep(id, description, result) {
3705
- const session = this.getSessionOrThrow(id);
3706
- const step = {
3707
- index: session.steps.length,
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
- result,
3710
- started_at: Date.now()
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
- * Mark session as completed with a result.
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
- completeSession(id, result) {
3725
- const session = this.getSessionOrThrow(id);
3726
- session.status = "completed";
3727
- session.completed_at = Date.now();
3728
- session.result = result;
3729
- this.emit({
3730
- type: "session.completed",
3731
- session_id: session.id,
3732
- result: result ?? null
3733
- });
3734
- return session;
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
- * Mark session as failed with an error message.
3715
+ * List all known Anthropic skills.
3738
3716
  */
3739
- failSession(id, error) {
3740
- const session = this.getSessionOrThrow(id);
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
- * Cancel a session.
3721
+ * Check if a skill name is a known Anthropic skill.
3753
3722
  */
3754
- cancelSession(id) {
3755
- const session = this.getSessionOrThrow(id);
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
- * Get session by ID, or null if not found.
3727
+ * Clear the cache (force re-fetch on next request).
3768
3728
  */
3769
- getSession(id) {
3770
- return this.sessions.get(id) ?? null;
3729
+ clearCache() {
3730
+ this.cache.clear();
3771
3731
  }
3772
- /**
3773
- * List all sessions sorted by created_at descending.
3774
- */
3775
- listSessions() {
3776
- return [...this.sessions.values()].sort(
3777
- (a, b) => b.created_at - a.created_at
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
- * End-to-end session run: create -> start -> resolve -> step -> complete.
3782
- * Wraps everything in try/catch to fail gracefully.
3761
+ * Build a compact system prompt injection from the context.
3783
3762
  */
3784
- async runSession(req) {
3785
- let enrichedReq = req;
3786
- if (req.entity_id && this.graph) {
3787
- const loader = new EntityScopedContextLoader(this.graph);
3788
- const scopedCtx = loader.load(req.entity_id);
3789
- if (scopedCtx?.personality_prompt) {
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
- const session = this.createSession(enrichedReq);
3805
- try {
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
- return this.sessions.get(session.id);
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
- return count;
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 session;
3779
+ return lines.join("\n");
3963
3780
  }
3964
- emit(event) {
3965
- if (this.eventBus) {
3966
- this.eventBus.emit(event);
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
- * Convert a task result into a weight signal for the knowledge graph.
3971
- *
3972
- * Signal scale: -0.3 (failed after retries) to +0.3 (verified success first try).
3973
- * Neutral (0) when no verification was possible — don't penalise unverifiable tasks.
3974
- */
3975
- computeOutcomeSignal(result) {
3976
- const healAttempts = result["heal_attempts"];
3977
- if (!healAttempts || healAttempts.length === 0) return 0;
3978
- const last = healAttempts[healAttempts.length - 1];
3979
- const verification = last?.["verification"];
3980
- if (!verification || verification["method"] === "none") return 0;
3981
- const success = verification["success"] === true;
3982
- const healed = result["healed"] === true;
3983
- if (success) return healed ? 0.1 : 0.3;
3984
- return -0.2;
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/WebSocketEvents.ts
3989
- var WebSocketEventBus = class {
3990
- clients = /* @__PURE__ */ new Set();
3991
- handlers = /* @__PURE__ */ new Set();
3992
- heartbeatTimer = null;
3993
- /**
3994
- * Register a connected WebSocket client.
3995
- * Automatically removes on close.
3996
- */
3997
- addClient(ws) {
3998
- this.clients.add(ws);
3999
- ws.on("close", () => {
4000
- this.clients.delete(ws);
4001
- });
4002
- ws.on("error", () => {
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
- * Manually remove a WebSocket client.
4008
- */
4009
- removeClient(ws) {
4010
- this.clients.delete(ws);
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
- * Emit an event to all local handlers and broadcast to WS clients.
3914
+ * Build conversation history as LLM messages for context injection.
4014
3915
  */
4015
- emit(event) {
4016
- const typed = event;
4017
- for (const handler of this.handlers) {
4018
- try {
4019
- handler(typed);
4020
- } catch {
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
- * Register a local event handler.
4027
- * Returns an unsubscribe function.
3962
+ * Create a new session with status 'pending'.
4028
3963
  */
4029
- onEvent(handler) {
4030
- this.handlers.add(handler);
4031
- return () => {
4032
- this.handlers.delete(handler);
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
- * Send an event to all connected WebSocket clients.
3979
+ * Transition session to 'running' and optionally invoke inference engine.
4037
3980
  */
4038
- broadcast(event) {
4039
- if (this.clients.size === 0) return;
4040
- const data = JSON.stringify(event);
4041
- for (const ws of this.clients) {
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
- ws.send(data);
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
- * Start a periodic heartbeat that emits daemon.stats events.
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
- stopStatsHeartbeat() {
4068
- if (this.heartbeatTimer !== null) {
4069
- clearInterval(this.heartbeatTimer);
4070
- this.heartbeatTimer = null;
4071
- }
4072
- }
4073
- /**
4074
- * Get the number of connected WS clients.
4075
- */
4076
- clientCount() {
4077
- return this.clients.size;
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
- * Get the number of registered local handlers.
4023
+ * Mark session as completed with a result.
4081
4024
  */
4082
- handlerCount() {
4083
- return this.handlers.size;
4084
- }
4085
- };
4086
-
4087
- // packages/daemon/src/BackgroundWorkers.ts
4088
- var DEFAULT_CONFIG2 = {
4089
- decay_interval_ms: 6 * 60 * 60 * 1e3,
4090
- // 6 hours
4091
- deferred_trace_interval_ms: 6e4,
4092
- // 60 seconds
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
- * Start all background workers.
4038
+ * Mark session as failed with an error message.
4118
4039
  */
4119
- start() {
4120
- if (this.running) return;
4121
- this.running = true;
4122
- if (this.decayScheduler) {
4123
- const timer = setInterval(async () => {
4124
- try {
4125
- await this.decayScheduler.runCycle();
4126
- this.lastRunTimes.set("decay", Date.now());
4127
- } catch (err) {
4128
- console.error("[BackgroundWorkers] Decay cycle failed:", err);
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
- * Stop all background workers and clear timers.
4053
+ * Cancel a session.
4168
4054
  */
4169
- stop() {
4170
- for (const timer of this.timers.values()) {
4171
- clearInterval(timer);
4172
- }
4173
- this.timers.clear();
4174
- this.proactiveSurface?.stop();
4175
- this.teamSync?.stop();
4176
- this.running = false;
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
- * Whether workers are currently running.
4068
+ * Get session by ID, or null if not found.
4180
4069
  */
4181
- isRunning() {
4182
- return this.running;
4070
+ getSession(id) {
4071
+ return this.sessions.get(id) ?? null;
4183
4072
  }
4184
4073
  /**
4185
- * Get status of all registered workers.
4074
+ * List all sessions sorted by created_at descending.
4186
4075
  */
4187
- getWorkerStatus() {
4188
- const workerNames = ["decay", "deferred_traces", "compactor", "enrichment", "proactive_surface", "team_sync"];
4189
- return workerNames.map((name) => ({
4190
- name,
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
- * Find expired deferred traces and resolve them with signal 0.0.
4082
+ * End-to-end session run: create -> start -> resolve -> step -> complete.
4083
+ * Wraps everything in try/catch to fail gracefully.
4198
4084
  */
4199
- resolveDeferredTraces() {
4200
- if (!this.traceStore) return;
4201
- const now = Date.now();
4202
- const deferred = this.traceStore.query({ deferred: true, limit: 100 });
4203
- for (const trace of deferred) {
4204
- if (trace.deferred_until !== null && trace.deferred_until <= now) {
4205
- this.traceStore.updateOutcome(trace.id, 0, "timeout", now);
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
- // packages/daemon/src/SkillRegistry.ts
4212
- import { readFileSync as readFileSync5, readdirSync as readdirSync3, existsSync as existsSync6, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync3 } from "node:fs";
4213
- import { join as join2 } from "node:path";
4214
- import { homedir as homedir2 } from "node:os";
4215
- import YAML2 from "yaml";
4216
- var SkillRegistry = class {
4217
- skills = /* @__PURE__ */ new Map();
4218
- builtinNames = /* @__PURE__ */ new Set();
4219
- builtinDir;
4220
- customDir;
4221
- constructor(opts) {
4222
- this.builtinDir = opts?.builtinDir ?? join2(homedir2(), ".0agent", "skills", "builtin");
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
- } catch (err) {
4246
- console.warn(`Failed to load skill ${file}: ${err}`);
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
- * Reload all skills (after create/delete).
4252
- */
4253
- async reload() {
4254
- await this.loadAll();
4255
- }
4256
- get(name) {
4257
- const normalized = name.replace(/^\//, "");
4258
- return this.skills.get(normalized);
4259
- }
4260
- list() {
4261
- return [...this.skills.values()];
4262
- }
4263
- isBuiltin(name) {
4264
- return this.builtinNames.has(name);
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
- * Create a custom skill. Returns the SkillDefinition.
4268
- * Throws if name conflicts with built-in.
4250
+ * Return the number of active (running) sessions.
4269
4251
  */
4270
- createCustom(name, yamlContent) {
4271
- if (this.builtinNames.has(name)) {
4272
- throw new Error(`Cannot override built-in skill: ${name}`);
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
- * Remove a custom skill. Throws if built-in.
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
- removeCustom(name) {
4285
- if (this.builtinNames.has(name)) {
4286
- throw new Error(`Cannot delete built-in skill: ${name}`);
4287
- }
4288
- const filePath = join2(this.customDir, `${name}.yaml`);
4289
- if (existsSync6(filePath)) {
4290
- unlinkSync(filePath);
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.skills.delete(name);
4296
+ return this.llm;
4293
4297
  }
4294
- get size() {
4295
- return this.skills.size;
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/HTTPServer.ts
4300
- import { Hono as Hono10 } from "hono";
4301
- import { serve } from "@hono/node-server";
4302
- import { readFileSync as readFileSync6 } from "node:fs";
4303
- import { resolve as resolve5, dirname as dirname3 } from "node:path";
4304
- import { fileURLToPath } from "node:url";
4305
-
4306
- // packages/daemon/src/routes/health.ts
4307
- import { Hono } from "hono";
4308
- function healthRoutes(deps) {
4309
- const app = new Hono();
4310
- app.get("/", (c) => {
4311
- const status = deps.getStatus();
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
- return c.json({ session_id: session.id, status: "pending" }, 201);
4330
- });
4331
- app.get("/", (c) => {
4332
- const sessions = deps.sessions.listSessions();
4333
- return c.json(sessions);
4334
- });
4335
- app.get("/:id", (c) => {
4336
- const id = c.req.param("id");
4337
- const session = deps.sessions.getSession(id);
4338
- if (!session) {
4339
- return c.json({ error: "Session not found" }, 404);
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
- return c.json(session);
4342
- });
4343
- app.delete("/:id", (c) => {
4344
- const id = c.req.param("id");
4345
- const session = deps.sessions.getSession(id);
4346
- if (!session) {
4347
- return c.json({ error: "Session not found" }, 404);
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
- deps.sessions.cancelSession(id);
4350
- return c.json({ ok: true });
4351
- });
4352
- return app;
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/routes/graph.ts
4356
- import { Hono as Hono3 } from "hono";
4357
- function graphRoutes(deps) {
4358
- const app = new Hono3();
4359
- app.get("/nodes", (c) => {
4360
- const graph_id = c.req.query("graph_id");
4361
- const type = c.req.query("type");
4362
- const limitStr = c.req.query("limit");
4363
- const limit = limitStr ? parseInt(limitStr, 10) : 50;
4364
- const results = deps.graph.queryStructural({
4365
- graph_id: graph_id || void 0,
4366
- node_type: type || void 0,
4367
- limit
4368
- });
4369
- return c.json(results.map((r) => r.node));
4370
- });
4371
- app.get("/nodes/:id", (c) => {
4372
- const id = c.req.param("id");
4373
- const node = deps.graph.getNode(id);
4374
- if (!node) {
4375
- return c.json({ error: "Node not found" }, 404);
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
- return c.json(node);
4378
- });
4379
- app.get("/edges", (c) => {
4380
- const from_node = c.req.query("from_node");
4381
- const to_node = c.req.query("to_node");
4382
- const type = c.req.query("type");
4383
- let edges;
4384
- if (from_node && to_node) {
4385
- edges = deps.graph.getEdgesBetween(from_node, to_node);
4386
- } else if (from_node) {
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
- if (type) {
4394
- edges = edges.filter((e) => e.type === type);
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
- return c.json(edges);
4397
- });
4398
- app.post("/query", async (c) => {
4399
- const body = await c.req.json();
4400
- const results = deps.graph.queryMerged({
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
- try {
4428
- const subgraph = deps.graph.getSubGraph(id, 2);
4429
- const nodes = subgraph.getNodes();
4430
- const edges = subgraph.getEdges();
4431
- let last_seen = node.last_seen;
4432
- for (const n of nodes) {
4433
- if (n.last_seen > last_seen) {
4434
- last_seen = n.last_seen;
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
- return app;
4457
- }
4537
+ }
4538
+ };
4458
4539
 
4459
- // packages/daemon/src/routes/traces.ts
4460
- import { Hono as Hono5 } from "hono";
4461
- function traceRoutes(deps) {
4462
- const app = new Hono5();
4463
- app.get("/", (c) => {
4464
- const session_id = c.req.query("session_id");
4465
- const deferredStr = c.req.query("deferred");
4466
- const limitStr = c.req.query("limit");
4467
- const deferred = deferredStr !== void 0 && deferredStr !== "" ? deferredStr === "true" : void 0;
4468
- const limit = limitStr ? parseInt(limitStr, 10) : void 0;
4469
- const traces = deps.traceStore.query({
4470
- session_id: session_id || void 0,
4471
- deferred,
4472
- limit
4473
- });
4474
- return c.json(traces);
4475
- });
4476
- app.get("/:id", (c) => {
4477
- const id = c.req.param("id");
4478
- const trace = deps.traceStore.get(id);
4479
- if (!trace) {
4480
- return c.json({ error: "Trace not found" }, 404);
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
- return c.json(trace);
4483
- });
4484
- return app;
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/routes/subagents.ts
4488
- import { Hono as Hono6 } from "hono";
4489
- function subagentRoutes() {
4490
- const app = new Hono6();
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
- return c.json([]);
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/skills.ts
4501
- import { Hono as Hono7 } from "hono";
4502
- function skillRoutes(deps) {
4503
- const app = new Hono7();
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.name || typeof body.name !== "string") {
4519
- return c.json({ error: "name is required" }, 400);
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
- if (!deps.proactiveSurface) return c.json([]);
4561
- const seen = c.req.query("seen");
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.post("/pull", async (c) => {
4584
- const sync = deps.getSync();
4585
- if (!sync) return c.json({ error: "GitHub memory not configured." }, 404);
4586
- const result = await sync.pull();
4587
- return c.json(result);
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.get("/status", (c) => {
4590
- const sync = deps.getSync();
4591
- if (!sync) return c.json({ configured: false });
4592
- return c.json({ configured: true, ...sync.getLastSyncTimes() });
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/HTTPServer.ts
4598
- function findGraphHtml() {
4599
- const candidates = [
4600
- resolve5(dirname3(fileURLToPath(import.meta.url)), "graph.html"),
4601
- // dev (src/)
4602
- resolve5(dirname3(fileURLToPath(import.meta.url)), "..", "graph.html"),
4603
- // bundled (dist/../)
4604
- resolve5(dirname3(fileURLToPath(import.meta.url)), "..", "dist", "graph.html")
4605
- ];
4606
- for (const p of candidates) {
4607
- try {
4608
- readFileSync6(p);
4609
- return p;
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
- const body = {
4733
- model: this.config.model,
4734
- max_tokens: 8192,
4735
- messages: anthropicMsgs,
4736
- stream: true
4737
- };
4738
- if (sysContent) body.system = sysContent;
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
- const res = await fetch("https://api.anthropic.com/v1/messages", {
4747
- method: "POST",
4748
- headers: {
4749
- "Content-Type": "application/json",
4750
- "x-api-key": this.config.api_key,
4751
- "anthropic-version": "2023-06-01"
4752
- },
4753
- body: JSON.stringify(body)
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
- if (!res.ok) {
4756
- const err = await res.text();
4757
- throw new Error(`Anthropic ${res.status}: ${err}`);
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
- let textContent = "";
4760
- let stopReason = "end_turn";
4761
- let inputTokens = 0;
4762
- let outputTokens = 0;
4763
- let modelName = this.config.model;
4764
- const toolCalls = [];
4765
- const toolInputBuffers = {};
4766
- let currentToolId = "";
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
- return {
4828
- content: textContent,
4829
- tool_calls: toolCalls.length > 0 ? toolCalls : null,
4830
- stop_reason: stopReason,
4831
- tokens_used: inputTokens + outputTokens,
4832
- model: modelName
4833
- };
4834
- }
4835
- // ─── OpenAI (also xAI, Gemini) ───────────────────────────────────────────
4836
- async openai(messages, tools, system, onToken, baseUrl = "https://api.openai.com/v1") {
4837
- const allMessages = [];
4838
- const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
4839
- if (sysContent) allMessages.push({ role: "system", content: sysContent });
4840
- for (const m of messages.filter((m2) => m2.role !== "system")) {
4841
- if (m.role === "tool") {
4842
- allMessages.push({ role: "tool", tool_call_id: m.tool_call_id, content: m.content });
4843
- } else if (m.role === "assistant" && m.tool_calls?.length) {
4844
- allMessages.push({
4845
- role: "assistant",
4846
- content: m.content || null,
4847
- tool_calls: m.tool_calls.map((tc) => ({
4848
- id: tc.id,
4849
- type: "function",
4850
- function: { name: tc.name, arguments: JSON.stringify(tc.input) }
4851
- }))
4852
- });
4853
- } else {
4854
- allMessages.push({ role: m.role, content: m.content });
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
- const body = {
4858
- model: this.config.model,
4859
- messages: allMessages,
4860
- max_tokens: 8192,
4861
- stream: true,
4862
- stream_options: { include_usage: true }
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
- const res = await fetch(`${this.config.base_url ?? baseUrl}/chat/completions`, {
4871
- method: "POST",
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
- let textContent = "";
4883
- let tokensUsed = 0;
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
- const toolCalls = Object.values(toolCallMap).filter((tc) => tc.id && tc.name).map((tc) => {
4937
- let input = {};
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
- input = JSON.parse(tc.args);
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
- // ─── Ollama (no streaming for simplicity) ────────────────────────────────
4953
- async ollama(messages, system, onToken) {
4954
- const baseUrl = this.config.base_url ?? "http://localhost:11434";
4955
- const allMessages = [];
4956
- const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
4957
- if (sysContent) allMessages.push({ role: "system", content: sysContent });
4958
- allMessages.push(...messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content })));
4959
- const res = await fetch(`${baseUrl}/api/chat`, {
4960
- method: "POST",
4961
- headers: { "Content-Type": "application/json" },
4962
- body: JSON.stringify({ model: this.config.model, messages: allMessages, stream: false })
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
- if (!res.ok) throw new Error(`Ollama error ${res.status}`);
4965
- const data = await res.json();
4966
- if (onToken) onToken(data.message.content);
4967
- return { content: data.message.content, tool_calls: null, stop_reason: "end_turn", tokens_used: data.eval_count ?? 0, model: this.config.model };
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 readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "node:fs";
4974
- import { resolve as resolve6, dirname as dirname4 } from "node:path";
4975
- import { homedir as homedir3, hostname } from "node:os";
4976
- import YAML3 from "yaml";
4977
- var IDENTITY_PATH = resolve6(homedir3(), ".0agent", "identity.yaml");
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 (existsSync7(IDENTITY_PATH)) {
4994
- const raw = readFileSync7(IDENTITY_PATH, "utf8");
4995
- this.identity = YAML3.parse(raw);
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 (!existsSync7(dir)) {
5078
+ if (!existsSync8(dir)) {
5047
5079
  mkdirSync4(dir, { recursive: true });
5048
5080
  }
5049
- writeFileSync4(IDENTITY_PATH, YAML3.stringify(this.identity), "utf8");
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 readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "node:fs";
5055
- import { resolve as resolve7 } from "node:path";
5056
- import { homedir as homedir4 } from "node:os";
5057
- import YAML4 from "yaml";
5058
- var TEAMS_PATH = resolve7(homedir4(), ".0agent", "teams.yaml");
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 (existsSync8(TEAMS_PATH)) {
5063
- this.config = YAML4.parse(readFileSync8(TEAMS_PATH, "utf8"));
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(resolve7(homedir4(), ".0agent"), { recursive: true });
5119
- writeFileSync5(TEAMS_PATH, YAML4.stringify(this.config), "utf8");
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 readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync9, readdirSync as readdirSync4 } from "node:fs";
5203
- import { resolve as resolve8 } from "node:path";
5204
- import { homedir as homedir5 } from "node:os";
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 = resolve8(homedir5(), ".0agent", "skills", "custom");
5324
- if (existsSync9(customSkillsDir)) {
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 = readFileSync9(resolve8(customSkillsDir, file), "utf8");
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 = resolve8(homedir5(), ".0agent", "skills", "custom");
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(resolve8(dir, file.name), content, "utf8");
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 = resolve10(homedir6(), ".0agent", "daemon.pid");
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 = resolve10(homedir6(), ".0agent");
5590
- if (!existsSync11(dotDir)) {
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 (existsSync11(this.pidFilePath)) {
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 resolve11 } from "node:path";
5773
- import { homedir as homedir7 } from "node:os";
5774
- import { existsSync as existsSync12 } from "node:fs";
5775
- var CONFIG_PATH = process.env["ZEROAGENT_CONFIG"] ?? resolve11(homedir7(), ".0agent", "config.yaml");
5776
- if (!existsSync12(CONFIG_PATH)) {
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