0agent 1.0.25 → 1.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/0agent.js CHANGED
@@ -1318,8 +1318,26 @@ async function isDaemonRunning() {
1318
1318
  }
1319
1319
  }
1320
1320
 
1321
+ async function isDaemonFresh() {
1322
+ // Check if the running daemon has the /api/llm/ping route (1.0.26+)
1323
+ try {
1324
+ const res = await fetch(`${BASE_URL}/api/llm/ping`, { method: 'POST', signal: AbortSignal.timeout(2000) });
1325
+ return res.status !== 404;
1326
+ } catch { return false; }
1327
+ }
1328
+
1321
1329
  async function requireDaemon() {
1322
- if (await isDaemonRunning()) return;
1330
+ if (await isDaemonRunning()) {
1331
+ // Kill and restart if it's an old version without key routes
1332
+ if (!(await isDaemonFresh())) {
1333
+ process.stdout.write(' Updating daemon...');
1334
+ try { execSync('pkill -f "daemon.mjs" 2>/dev/null; true', { stdio: 'ignore' }); } catch {}
1335
+ await sleep(800);
1336
+ // fall through to start fresh daemon below
1337
+ } else {
1338
+ return; // up to date, no action needed
1339
+ }
1340
+ }
1323
1341
 
1324
1342
  // Auto-start if config exists — no manual `0agent start` needed
1325
1343
  if (!existsSync(CONFIG_PATH)) {
package/bin/chat.js CHANGED
@@ -457,54 +457,108 @@ printInsights();
457
457
  // Connect WebSocket for live events
458
458
  connectWS();
459
459
 
460
- // ── Startup: check daemon + LLM connection ──────────────────────────────────
460
+ // ── Startup: ensure fresh daemon + verify LLM ────────────────────────────────
461
+ async function _spawnDaemon() {
462
+ const pkgRoot = resolve(new URL(import.meta.url).pathname, '..', '..');
463
+ const bundled = resolve(pkgRoot, 'dist', 'daemon.mjs');
464
+ if (!existsSync(bundled) || !existsSync(CONFIG_PATH)) return false;
465
+ const { spawn } = await import('node:child_process');
466
+ const child = spawn(process.execPath, [bundled], {
467
+ detached: true, stdio: 'ignore',
468
+ env: { ...process.env, ZEROAGENT_CONFIG: CONFIG_PATH },
469
+ });
470
+ child.unref();
471
+ // Wait up to 10s for daemon to be ready
472
+ for (let i = 0; i < 20; i++) {
473
+ await new Promise(r => setTimeout(r, 500));
474
+ try {
475
+ await fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(500) });
476
+ return true;
477
+ } catch {}
478
+ }
479
+ return false;
480
+ }
481
+
482
+ async function _safeJsonFetch(url, opts) {
483
+ const res = await fetch(url, opts);
484
+ const text = await res.text();
485
+ try { return { status: res.status, data: JSON.parse(text) }; }
486
+ catch { return { status: res.status, data: null, raw: text }; }
487
+ }
488
+
461
489
  (async () => {
462
490
  const startSpin = new Spinner('Starting daemon');
463
491
 
464
- // Step 1: ensure daemon is running
492
+ // Step 1: Check if daemon is running AND up-to-date (has /api/llm/ping)
465
493
  let daemonOk = false;
494
+ let needsRestart = false;
495
+
466
496
  try {
467
497
  await fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(1500) });
468
- daemonOk = true;
469
- } catch {
470
- // Auto-start
471
- startSpin.start();
472
- const pkgRoot = resolve(new URL(import.meta.url).pathname, '..', '..');
473
- const bundled = resolve(pkgRoot, 'dist', 'daemon.mjs');
474
- if (existsSync(bundled) && existsSync(CONFIG_PATH)) {
475
- const { spawn } = await import('node:child_process');
476
- const child = spawn(process.execPath, [bundled], { detached: true, stdio: 'ignore', env: { ...process.env, ZEROAGENT_CONFIG: CONFIG_PATH } });
477
- child.unref();
478
- for (let i = 0; i < 20; i++) {
479
- await new Promise(r => setTimeout(r, 500));
480
- try { await fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(500) }); daemonOk = true; break; } catch {}
481
- }
498
+ // Daemon running — check if it has the new /api/llm/ping route
499
+ const probe = await _safeJsonFetch(`${BASE_URL}/api/llm/ping`, {
500
+ method: 'POST', signal: AbortSignal.timeout(3000),
501
+ });
502
+ if (probe.status === 404 || probe.data === null) {
503
+ // Old daemon without this route — needs restart
504
+ needsRestart = true;
505
+ } else {
506
+ daemonOk = true;
482
507
  }
483
- startSpin.stop();
484
- if (daemonOk) process.stdout.write(` ${fmt(C.green, '✓')} Daemon ready\n`);
485
- else { console.log(` ${fmt(C.red, '✗')} Daemon failed. Run: 0agent start`); rl.prompt(); return; }
508
+ } catch {
509
+ // Daemon not running at all
486
510
  }
487
511
 
488
- // Step 2: lightweight direct API ping (1 token — fast, no daemon involved)
489
- const provider = getCurrentProvider(cfg);
490
- if (!provider?.api_key?.trim() && provider?.provider !== 'ollama') {
491
- console.log(` ${fmt(C.yellow, '⚠')} No API key. Use: ${fmt(C.cyan, '/key ' + (provider?.provider ?? 'anthropic') + ' <key>')}\n`);
492
- } else {
493
- const llmSpin = new Spinner(`Checking ${provider.provider}/${provider.model}`);
494
- llmSpin.start();
512
+ if (needsRestart) {
513
+ startSpin.start('Restarting daemon (new version)');
514
+ // Kill old daemon
495
515
  try {
496
- const result = await pingLLM(provider);
497
- llmSpin.stop();
498
- if (result.ok) {
499
- console.log(` ${fmt(C.green, '✓')} ${fmt(C.cyan, result.model ?? provider.model)} is ready\n`);
500
- } else {
501
- console.log(` ${fmt(C.red, '✗')} LLM error: ${result.error}`);
502
- console.log(` ${fmt(C.dim, 'Fix with: /key ' + provider.provider + ' <new-key>')}\n`);
503
- }
504
- } catch (e) {
505
- llmSpin.stop();
506
- console.log(` ${fmt(C.yellow, '⚠')} Could not reach ${provider.provider}: ${e.message}\n`);
516
+ const { execSync } = await import('node:child_process');
517
+ execSync('pkill -f "daemon.mjs" 2>/dev/null; true', { stdio: 'ignore' });
518
+ } catch {}
519
+ await new Promise(r => setTimeout(r, 800));
520
+ daemonOk = await _spawnDaemon();
521
+ } else if (!daemonOk) {
522
+ startSpin.start('Starting daemon');
523
+ daemonOk = await _spawnDaemon();
524
+ }
525
+
526
+ startSpin.stop();
527
+ if (!daemonOk) {
528
+ console.log(` ${fmt(C.red, '✗')} Daemon failed to start. Run: 0agent start`);
529
+ rl.prompt();
530
+ return;
531
+ }
532
+ if (needsRestart) {
533
+ process.stdout.write(` ${fmt(C.green, '✓')} Daemon updated\n`);
534
+ } else if (!daemonOk) {
535
+ process.stdout.write(` ${fmt(C.green, '✓')} Daemon ready\n`);
536
+ } else {
537
+ // Was already running and up to date — show nothing (already running)
538
+ process.stdout.write(` ${fmt(C.green, '✓')} Daemon ready\n`);
539
+ }
540
+
541
+ // Step 2: LLM check via daemon (not direct — this proves daemon↔API works)
542
+ const provider = getCurrentProvider(cfg);
543
+ const llmSpin = new Spinner(`Checking ${provider?.provider ?? 'LLM'}/${provider?.model ?? '...'}`);
544
+ llmSpin.start();
545
+ try {
546
+ const { data } = await _safeJsonFetch(`${BASE_URL}/api/llm/ping`, {
547
+ method: 'POST',
548
+ signal: AbortSignal.timeout(15_000),
549
+ });
550
+ llmSpin.stop();
551
+ if (data?.ok) {
552
+ console.log(` ${fmt(C.green, '✓')} ${fmt(C.cyan, (data.provider ?? '') + '/' + (data.model ?? ''))} — ${data.latency_ms}ms\n`);
553
+ } else if (data) {
554
+ console.log(` ${fmt(C.red, '✗')} LLM error: ${data.error}`);
555
+ console.log(` ${fmt(C.dim, 'Fix: /key ' + (provider?.provider ?? 'anthropic') + ' <api-key>')}\n`);
556
+ } else {
557
+ console.log(` ${fmt(C.yellow, '⚠')} LLM ping returned unexpected response\n`);
507
558
  }
559
+ } catch (e) {
560
+ llmSpin.stop();
561
+ console.log(` ${fmt(C.yellow, '⚠')} LLM check failed: ${e.message}\n`);
508
562
  }
509
563
 
510
564
  rl.prompt();
@@ -532,6 +586,24 @@ rl.on('close', () => {
532
586
  });
533
587
 
534
588
  process.on('SIGINT', () => {
535
- console.log(`\n ${fmt(C.dim, 'Ctrl+C — type /help for commands or Ctrl+C again to exit')}\n`);
536
- rl.prompt();
589
+ if (pendingResolve) {
590
+ // Session in progress — cancel it, don't exit
591
+ process.stdout.write(`\n ${fmt(C.yellow, '↩')} Cancelled\n`);
592
+ spinner.stop();
593
+ if (sessionId) {
594
+ fetch(`${BASE_URL}/api/sessions/${sessionId}`, { method: 'DELETE' }).catch(() => {});
595
+ }
596
+ const resolve_ = pendingResolve;
597
+ pendingResolve = null;
598
+ sessionId = null;
599
+ resolve_();
600
+ rl.prompt();
601
+ } else {
602
+ // Not busy — show hint on first press
603
+ process.stdout.write(`\n ${fmt(C.dim, 'Press Ctrl+C again to exit')}\n`);
604
+ rl.prompt();
605
+ // Second Ctrl+C within 1.5s exits
606
+ const timeout = setTimeout(() => {}, 1500);
607
+ process.once('SIGINT', () => { clearTimeout(timeout); process.exit(0); });
608
+ }
537
609
  });
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((resolve13) => setTimeout(resolve13, ms));
1493
+ return new Promise((resolve14) => setTimeout(resolve14, 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((resolve13) => {
2616
+ return new Promise((resolve14) => {
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
- resolve13(output || (code === 0 ? "(command completed, no output)" : `exit code ${code}`));
2627
+ resolve14(output || (code === 0 ? "(command completed, no output)" : `exit code ${code}`));
2628
2628
  });
2629
2629
  proc.on("error", (err) => {
2630
- resolve13(`Error: ${err.message}`);
2630
+ resolve14(`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 existsSync11, readFileSync as readFileSync11, statSync, readdirSync as readdirSync5 } from "node:fs";
2937
- import { resolve as resolve10, join as join3 } from "node:path";
2936
+ import { existsSync as existsSync12, readFileSync as readFileSync12, statSync, readdirSync as readdirSync5 } from "node:fs";
2937
+ import { resolve as resolve11, 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 (!existsSync11(resolve10(this.cwd, ".git"))) return;
2986
+ if (!existsSync12(resolve11(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 (!existsSync11(dir)) continue;
3030
+ if (!existsSync12(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 = readFileSync11(path, "utf8");
3036
+ const content = readFileSync12(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 existsSync12, mkdirSync as mkdirSync6 } from "node:fs";
3092
- import { resolve as resolve11 } from "node:path";
3093
- import { homedir as homedir7 } from "node:os";
3091
+ import { writeFileSync as writeFileSync7, unlinkSync as unlinkSync2, existsSync as existsSync13, mkdirSync as mkdirSync6 } from "node:fs";
3092
+ import { resolve as resolve12 } from "node:path";
3093
+ import { homedir as homedir8 } from "node:os";
3094
3094
 
3095
3095
  // packages/daemon/src/config/DaemonConfig.ts
3096
3096
  import { readFileSync, existsSync } from "node:fs";
@@ -3391,7 +3391,9 @@ var LLMExecutor = class {
3391
3391
  "x-api-key": this.config.api_key,
3392
3392
  "anthropic-version": "2023-06-01"
3393
3393
  },
3394
- body: JSON.stringify(body)
3394
+ body: JSON.stringify(body),
3395
+ signal: AbortSignal.timeout(6e4)
3396
+ // 60s timeout
3395
3397
  });
3396
3398
  if (!res.ok) {
3397
3399
  const err = await res.text();
@@ -3514,7 +3516,8 @@ var LLMExecutor = class {
3514
3516
  "Content-Type": "application/json",
3515
3517
  "Authorization": `Bearer ${this.config.api_key}`
3516
3518
  },
3517
- body: JSON.stringify(body)
3519
+ body: JSON.stringify(body),
3520
+ signal: AbortSignal.timeout(6e4)
3518
3521
  });
3519
3522
  if (!res.ok) {
3520
3523
  const err = await res.text();
@@ -3839,19 +3842,19 @@ var ProjectScanner = class {
3839
3842
  async getRunningPorts() {
3840
3843
  const open = [];
3841
3844
  await Promise.all(PORTS_TO_CHECK.map(
3842
- (port) => new Promise((resolve13) => {
3845
+ (port) => new Promise((resolve14) => {
3843
3846
  const s = createServer();
3844
3847
  s.listen(port, "127.0.0.1", () => {
3845
3848
  s.close();
3846
- resolve13();
3849
+ resolve14();
3847
3850
  });
3848
3851
  s.on("error", () => {
3849
3852
  open.push(port);
3850
- resolve13();
3853
+ resolve14();
3851
3854
  });
3852
3855
  setTimeout(() => {
3853
3856
  s.close();
3854
- resolve13();
3857
+ resolve14();
3855
3858
  }, 200);
3856
3859
  })
3857
3860
  ));
@@ -3984,7 +3987,7 @@ var SessionManager = class {
3984
3987
  session.started_at = Date.now();
3985
3988
  this.emit({
3986
3989
  type: "session.started",
3987
- session_id: session.id,
3990
+ session_id: id,
3988
3991
  task: session.task
3989
3992
  });
3990
3993
  if (this.inferenceEngine) {
@@ -4013,7 +4016,7 @@ var SessionManager = class {
4013
4016
  session.steps.push(step);
4014
4017
  this.emit({
4015
4018
  type: "session.step",
4016
- session_id: session.id,
4019
+ session_id: id,
4017
4020
  step: description,
4018
4021
  result: result ?? null
4019
4022
  });
@@ -4029,7 +4032,7 @@ var SessionManager = class {
4029
4032
  session.result = result;
4030
4033
  this.emit({
4031
4034
  type: "session.completed",
4032
- session_id: session.id,
4035
+ session_id: id,
4033
4036
  result: result ?? null
4034
4037
  });
4035
4038
  return session;
@@ -4044,7 +4047,7 @@ var SessionManager = class {
4044
4047
  session.error = error;
4045
4048
  this.emit({
4046
4049
  type: "session.failed",
4047
- session_id: session.id,
4050
+ session_id: id,
4048
4051
  error
4049
4052
  });
4050
4053
  return session;
@@ -4059,7 +4062,7 @@ var SessionManager = class {
4059
4062
  session.error = "cancelled";
4060
4063
  this.emit({
4061
4064
  type: "session.failed",
4062
- session_id: session.id,
4065
+ session_id: id,
4063
4066
  error: "cancelled"
4064
4067
  });
4065
4068
  return session;
@@ -4082,6 +4085,14 @@ var SessionManager = class {
4082
4085
  * End-to-end session run: create -> start -> resolve -> step -> complete.
4083
4086
  * Wraps everything in try/catch to fail gracefully.
4084
4087
  */
4088
+ /**
4089
+ * Called by the route AFTER it already created the session.
4090
+ * Uses the existing session ID — no duplicate creation.
4091
+ */
4092
+ async runExistingSession(sessionId, req) {
4093
+ return this._executeSession(sessionId, req);
4094
+ }
4095
+ /** @deprecated Use runExistingSession from routes — this creates a duplicate session. */
4085
4096
  async runSession(req) {
4086
4097
  let enrichedReq = req;
4087
4098
  if (req.entity_id && this.graph) {
@@ -4103,34 +4114,42 @@ var SessionManager = class {
4103
4114
  }
4104
4115
  }
4105
4116
  const session = this.createSession(enrichedReq);
4117
+ return this._executeSession(session.id, enrichedReq);
4118
+ }
4119
+ /**
4120
+ * Core execution — takes an EXISTING session ID and runs it.
4121
+ * All callers must have created the session first.
4122
+ */
4123
+ async _executeSession(sessionId, enrichedReq) {
4106
4124
  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;
4125
+ await this.startSession(sessionId);
4126
+ this.addStep(sessionId, `Extracting entities from: "${enrichedReq.task.slice(0, 60)}${enrichedReq.task.length > 60 ? "\u2026" : ""}"`);
4127
+ this.addStep(sessionId, "Querying knowledge graph (structural + semantic)\u2026");
4128
+ const plan = this.getSession(sessionId)?.plan;
4129
+ if (plan) {
4130
+ const edge = plan.selected_edge;
4112
4131
  if (edge) {
4113
4132
  this.addStep(
4114
- session.id,
4133
+ sessionId,
4115
4134
  `Selected plan: ${edge.from_label} \u2192 ${edge.to_label} (weight: ${edge.weight.toFixed(2)}, mode: ${edge.mode})`,
4116
- session.plan
4135
+ plan
4117
4136
  );
4118
4137
  } else {
4119
- this.addStep(session.id, `No prior plan found \u2014 bootstrapping from scratch`, session.plan);
4138
+ this.addStep(sessionId, `No prior plan found \u2014 bootstrapping from scratch`, plan);
4120
4139
  }
4121
- if (session.plan.skill) {
4122
- this.addStep(session.id, `Matched skill: /${session.plan.skill}`);
4140
+ if (plan.skill) {
4141
+ this.addStep(sessionId, `Matched skill: /${plan.skill}`);
4123
4142
  }
4124
4143
  } else {
4125
- this.addStep(session.id, "No inference engine connected \u2014 executing task directly");
4144
+ this.addStep(sessionId, "No inference engine connected \u2014 executing task directly");
4126
4145
  }
4127
4146
  let anthropicContext;
4128
4147
  if (enrichedReq.skill && this.anthropicFetcher.isAnthropicSkill(enrichedReq.skill)) {
4129
- this.addStep(session.id, `Fetching skill instructions: ${enrichedReq.skill}`);
4148
+ this.addStep(sessionId, `Fetching skill instructions: ${enrichedReq.skill}`);
4130
4149
  const fetched = await this.anthropicFetcher.fetch(enrichedReq.skill);
4131
4150
  if (fetched) {
4132
4151
  anthropicContext = this.anthropicFetcher.buildSystemPrompt(fetched);
4133
- this.addStep(session.id, `Loaded skill: ${fetched.name} (${fetched.cached ? "cached" : "fresh"})`);
4152
+ this.addStep(sessionId, `Loaded skill: ${fetched.name} (${fetched.cached ? "cached" : "fresh"})`);
4134
4153
  }
4135
4154
  }
4136
4155
  const activeLLM = this.getFreshLLM();
@@ -4139,9 +4158,9 @@ var SessionManager = class {
4139
4158
  activeLLM,
4140
4159
  { cwd: this.cwd },
4141
4160
  // step callback → emit session.step events
4142
- (step) => this.addStep(session.id, step),
4161
+ (step) => this.addStep(sessionId, step),
4143
4162
  // token callback → emit session.token events
4144
- (token) => this.emit({ type: "session.token", session_id: session.id, token })
4163
+ (token) => this.emit({ type: "session.token", session_id: sessionId, token })
4145
4164
  );
4146
4165
  const identityContext = this.identity ? `You are talking to ${this.identity.name} (device: ${this.identity.device_id}, timezone: ${this.identity.timezone}).` : void 0;
4147
4166
  const projectCtx = this.projectContext ? ProjectScanner.buildContextPrompt(this.projectContext) : void 0;
@@ -4170,15 +4189,14 @@ Current task:`;
4170
4189
  const healLoop = new SelfHealLoop2(
4171
4190
  activeLLM,
4172
4191
  { cwd: this.cwd },
4173
- (step) => this.addStep(session.id, step),
4174
- (token) => this.emit({ type: "session.token", session_id: session.id, token })
4192
+ (step) => this.addStep(sessionId, step),
4193
+ (token) => this.emit({ type: "session.token", session_id: sessionId, token })
4175
4194
  );
4176
4195
  agentResult = await healLoop.executeWithHealing(enrichedReq.task, systemContext);
4177
4196
  } catch {
4178
4197
  agentResult = await executor.execute(enrichedReq.task, systemContext);
4179
4198
  }
4180
4199
  if (this.conversationStore && userEntityId) {
4181
- const sessionId = session.id;
4182
4200
  const now = Date.now();
4183
4201
  this.conversationStore.append({
4184
4202
  id: crypto.randomUUID(),
@@ -4198,7 +4216,7 @@ Current task:`;
4198
4216
  created_at: now + 1
4199
4217
  });
4200
4218
  }
4201
- const selectedEdgeId = session.plan?.selected_edge?.edge_id;
4219
+ const selectedEdgeId = this.getSession(sessionId)?.plan?.selected_edge?.edge_id;
4202
4220
  if (selectedEdgeId && this.weightUpdater && this.graph) {
4203
4221
  const outcomeSignal = this.computeOutcomeSignal(agentResult);
4204
4222
  if (outcomeSignal !== 0) {
@@ -4214,20 +4232,20 @@ Current task:`;
4214
4232
  edge.weight,
4215
4233
  newWeight,
4216
4234
  outcomeSignal > 0 ? "task_outcome_positive" : "task_outcome_negative",
4217
- session.id
4235
+ sessionId
4218
4236
  );
4219
4237
  this.emit({ type: "graph.weight_updated", edge_id: edge.id, old_weight: edge.weight, new_weight: newWeight });
4220
4238
  }
4221
4239
  }
4222
4240
  }
4223
4241
  if (agentResult.files_written.length > 0) {
4224
- this.addStep(session.id, `Files written: ${agentResult.files_written.join(", ")}`);
4242
+ this.addStep(sessionId, `Files written: ${agentResult.files_written.join(", ")}`);
4225
4243
  }
4226
4244
  if (agentResult.commands_run.length > 0) {
4227
- this.addStep(session.id, `Commands run: ${agentResult.commands_run.length}`);
4245
+ this.addStep(sessionId, `Commands run: ${agentResult.commands_run.length}`);
4228
4246
  }
4229
- this.addStep(session.id, `Done (${agentResult.tokens_used} tokens, ${agentResult.iterations} LLM turns)`);
4230
- this.completeSession(session.id, {
4247
+ this.addStep(sessionId, `Done (${agentResult.tokens_used} tokens, ${agentResult.iterations} LLM turns)`);
4248
+ this.completeSession(sessionId, {
4231
4249
  output: agentResult.output,
4232
4250
  files_written: agentResult.files_written,
4233
4251
  commands_run: agentResult.commands_run,
@@ -4237,14 +4255,14 @@ Current task:`;
4237
4255
  } else {
4238
4256
  const cfgPath = resolve5(homedir2(), ".0agent", "config.yaml");
4239
4257
  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 });
4258
+ this.addStep(sessionId, "\u26A0 No LLM API key configured \u2014 run: 0agent init");
4259
+ this.completeSession(sessionId, { output });
4242
4260
  }
4243
4261
  } catch (err) {
4244
4262
  const message = err instanceof Error ? err.message : String(err);
4245
- this.failSession(session.id, message);
4263
+ this.failSession(sessionId, message);
4246
4264
  }
4247
- return this.sessions.get(session.id);
4265
+ return this.sessions.get(sessionId);
4248
4266
  }
4249
4267
  /**
4250
4268
  * Return the number of active (running) sessions.
@@ -4626,10 +4644,10 @@ var SkillRegistry = class {
4626
4644
  };
4627
4645
 
4628
4646
  // packages/daemon/src/HTTPServer.ts
4629
- import { Hono as Hono10 } from "hono";
4647
+ import { Hono as Hono11 } from "hono";
4630
4648
  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";
4649
+ import { readFileSync as readFileSync8 } from "node:fs";
4650
+ import { resolve as resolve7, dirname as dirname3 } from "node:path";
4633
4651
  import { fileURLToPath } from "node:url";
4634
4652
 
4635
4653
  // packages/daemon/src/routes/health.ts
@@ -4653,28 +4671,23 @@ function sessionRoutes(deps) {
4653
4671
  return c.json({ error: "task is required and must be a string" }, 400);
4654
4672
  }
4655
4673
  const session = deps.sessions.createSession(body);
4656
- deps.sessions.runSession(body).catch(() => {
4674
+ deps.sessions.runExistingSession(session.id, body).catch(() => {
4657
4675
  });
4658
4676
  return c.json({ session_id: session.id, status: "pending" }, 201);
4659
4677
  });
4660
4678
  app.get("/", (c) => {
4661
- const sessions = deps.sessions.listSessions();
4662
- return c.json(sessions);
4679
+ return c.json(deps.sessions.listSessions());
4663
4680
  });
4664
4681
  app.get("/:id", (c) => {
4665
4682
  const id = c.req.param("id");
4666
4683
  const session = deps.sessions.getSession(id);
4667
- if (!session) {
4668
- return c.json({ error: "Session not found" }, 404);
4669
- }
4684
+ if (!session) return c.json({ error: "Session not found" }, 404);
4670
4685
  return c.json(session);
4671
4686
  });
4672
4687
  app.delete("/:id", (c) => {
4673
4688
  const id = c.req.param("id");
4674
4689
  const session = deps.sessions.getSession(id);
4675
- if (!session) {
4676
- return c.json({ error: "Session not found" }, 404);
4677
- }
4690
+ if (!session) return c.json({ error: "Session not found" }, 404);
4678
4691
  deps.sessions.cancelSession(id);
4679
4692
  return c.json({ ok: true });
4680
4693
  });
@@ -4923,18 +4936,71 @@ function memoryRoutes(deps) {
4923
4936
  return app;
4924
4937
  }
4925
4938
 
4939
+ // packages/daemon/src/routes/llm.ts
4940
+ import { Hono as Hono10 } from "hono";
4941
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "node:fs";
4942
+ import { resolve as resolve6 } from "node:path";
4943
+ import { homedir as homedir4 } from "node:os";
4944
+ import YAML4 from "yaml";
4945
+ function llmRoutes() {
4946
+ const app = new Hono10();
4947
+ app.post("/ping", async (c) => {
4948
+ const start = Date.now();
4949
+ try {
4950
+ const configPath = resolve6(homedir4(), ".0agent", "config.yaml");
4951
+ if (!existsSync8(configPath)) {
4952
+ return c.json({ ok: false, error: "Config not found. Run: 0agent init" });
4953
+ }
4954
+ const cfg = YAML4.parse(readFileSync7(configPath, "utf8"));
4955
+ const providers = cfg.llm_providers;
4956
+ const def = providers?.find((p) => p.is_default) ?? providers?.[0];
4957
+ if (!def) {
4958
+ return c.json({ ok: false, error: "No LLM provider in config" });
4959
+ }
4960
+ const apiKey = String(def.api_key ?? "").trim();
4961
+ if (!apiKey && def.provider !== "ollama") {
4962
+ return c.json({ ok: false, error: `No API key for ${def.provider}. Run: 0agent init` });
4963
+ }
4964
+ const executor = new LLMExecutor({
4965
+ provider: String(def.provider),
4966
+ model: String(def.model),
4967
+ api_key: apiKey,
4968
+ base_url: def.base_url ? String(def.base_url) : void 0
4969
+ });
4970
+ const result = await executor.complete(
4971
+ [{ role: "user", content: "Reply with the word: ready" }],
4972
+ "You are a helpful assistant. Reply with exactly one word."
4973
+ );
4974
+ return c.json({
4975
+ ok: true,
4976
+ model: String(def.model),
4977
+ provider: String(def.provider),
4978
+ latency_ms: Date.now() - start,
4979
+ response: result.content.trim().slice(0, 20)
4980
+ });
4981
+ } catch (err) {
4982
+ return c.json({
4983
+ ok: false,
4984
+ error: err instanceof Error ? err.message : String(err),
4985
+ latency_ms: Date.now() - start
4986
+ });
4987
+ }
4988
+ });
4989
+ return app;
4990
+ }
4991
+
4926
4992
  // packages/daemon/src/HTTPServer.ts
4927
4993
  function findGraphHtml() {
4928
4994
  const candidates = [
4929
- resolve6(dirname3(fileURLToPath(import.meta.url)), "graph.html"),
4995
+ resolve7(dirname3(fileURLToPath(import.meta.url)), "graph.html"),
4930
4996
  // dev (src/)
4931
- resolve6(dirname3(fileURLToPath(import.meta.url)), "..", "graph.html"),
4997
+ resolve7(dirname3(fileURLToPath(import.meta.url)), "..", "graph.html"),
4932
4998
  // bundled (dist/../)
4933
- resolve6(dirname3(fileURLToPath(import.meta.url)), "..", "dist", "graph.html")
4999
+ resolve7(dirname3(fileURLToPath(import.meta.url)), "..", "dist", "graph.html")
4934
5000
  ];
4935
5001
  for (const p of candidates) {
4936
5002
  try {
4937
- readFileSync7(p);
5003
+ readFileSync8(p);
4938
5004
  return p;
4939
5005
  } catch {
4940
5006
  }
@@ -4948,7 +5014,7 @@ var HTTPServer = class {
4948
5014
  deps;
4949
5015
  constructor(deps) {
4950
5016
  this.deps = deps;
4951
- this.app = new Hono10();
5017
+ this.app = new Hono11();
4952
5018
  this.app.route("/api/health", healthRoutes({ getStatus: deps.getStatus }));
4953
5019
  this.app.route("/api/sessions", sessionRoutes({ sessions: deps.sessions }));
4954
5020
  this.app.route("/api/graph", graphRoutes({ graph: deps.graph }));
@@ -4958,9 +5024,10 @@ var HTTPServer = class {
4958
5024
  this.app.route("/api/skills", skillRoutes({ skillRegistry: deps.skillRegistry }));
4959
5025
  this.app.route("/api/insights", insightsRoutes({ proactiveSurface: deps.proactiveSurface ?? null }));
4960
5026
  this.app.route("/api/memory", memoryRoutes({ getSync: deps.getMemorySync ?? (() => null) }));
5027
+ this.app.route("/api/llm", llmRoutes());
4961
5028
  const serveGraph = (c) => {
4962
5029
  try {
4963
- const html = readFileSync7(GRAPH_HTML_PATH, "utf8");
5030
+ const html = readFileSync8(GRAPH_HTML_PATH, "utf8");
4964
5031
  return c.html(html);
4965
5032
  } catch {
4966
5033
  return c.html("<p>Graph UI not found. Run: pnpm build</p>");
@@ -4970,7 +5037,7 @@ var HTTPServer = class {
4970
5037
  this.app.get("/graph", serveGraph);
4971
5038
  }
4972
5039
  start() {
4973
- return new Promise((resolve13) => {
5040
+ return new Promise((resolve14) => {
4974
5041
  this.server = serve(
4975
5042
  {
4976
5043
  fetch: this.app.fetch,
@@ -4978,20 +5045,20 @@ var HTTPServer = class {
4978
5045
  hostname: this.deps.host
4979
5046
  },
4980
5047
  () => {
4981
- resolve13();
5048
+ resolve14();
4982
5049
  }
4983
5050
  );
4984
5051
  });
4985
5052
  }
4986
5053
  stop() {
4987
- return new Promise((resolve13, reject) => {
5054
+ return new Promise((resolve14, reject) => {
4988
5055
  if (!this.server) {
4989
- resolve13();
5056
+ resolve14();
4990
5057
  return;
4991
5058
  }
4992
5059
  this.server.close((err) => {
4993
5060
  if (err) reject(err);
4994
- else resolve13();
5061
+ else resolve14();
4995
5062
  });
4996
5063
  });
4997
5064
  }
@@ -5002,11 +5069,11 @@ var HTTPServer = class {
5002
5069
 
5003
5070
  // packages/daemon/src/IdentityManager.ts
5004
5071
  init_src();
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");
5072
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "node:fs";
5073
+ import { resolve as resolve8, dirname as dirname4 } from "node:path";
5074
+ import { homedir as homedir5, hostname } from "node:os";
5075
+ import YAML5 from "yaml";
5076
+ var IDENTITY_PATH = resolve8(homedir5(), ".0agent", "identity.yaml");
5010
5077
  var DEFAULT_IDENTITY = {
5011
5078
  name: "User",
5012
5079
  device_id: `unknown-device`,
@@ -5022,9 +5089,9 @@ var IdentityManager = class {
5022
5089
  * Load or create identity. Call once at daemon startup.
5023
5090
  */
5024
5091
  async init() {
5025
- if (existsSync8(IDENTITY_PATH)) {
5026
- const raw = readFileSync8(IDENTITY_PATH, "utf8");
5027
- this.identity = YAML4.parse(raw);
5092
+ if (existsSync9(IDENTITY_PATH)) {
5093
+ const raw = readFileSync9(IDENTITY_PATH, "utf8");
5094
+ this.identity = YAML5.parse(raw);
5028
5095
  } else {
5029
5096
  this.identity = {
5030
5097
  ...DEFAULT_IDENTITY,
@@ -5075,24 +5142,24 @@ var IdentityManager = class {
5075
5142
  }
5076
5143
  save() {
5077
5144
  const dir = dirname4(IDENTITY_PATH);
5078
- if (!existsSync8(dir)) {
5145
+ if (!existsSync9(dir)) {
5079
5146
  mkdirSync4(dir, { recursive: true });
5080
5147
  }
5081
- writeFileSync4(IDENTITY_PATH, YAML4.stringify(this.identity), "utf8");
5148
+ writeFileSync4(IDENTITY_PATH, YAML5.stringify(this.identity), "utf8");
5082
5149
  }
5083
5150
  };
5084
5151
 
5085
5152
  // packages/daemon/src/TeamManager.ts
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");
5153
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "node:fs";
5154
+ import { resolve as resolve9 } from "node:path";
5155
+ import { homedir as homedir6 } from "node:os";
5156
+ import YAML6 from "yaml";
5157
+ var TEAMS_PATH = resolve9(homedir6(), ".0agent", "teams.yaml");
5091
5158
  var TeamManager = class {
5092
5159
  config;
5093
5160
  constructor() {
5094
- if (existsSync9(TEAMS_PATH)) {
5095
- this.config = YAML5.parse(readFileSync9(TEAMS_PATH, "utf8"));
5161
+ if (existsSync10(TEAMS_PATH)) {
5162
+ this.config = YAML6.parse(readFileSync10(TEAMS_PATH, "utf8"));
5096
5163
  } else {
5097
5164
  this.config = { memberships: [] };
5098
5165
  }
@@ -5147,8 +5214,8 @@ var TeamManager = class {
5147
5214
  }
5148
5215
  }
5149
5216
  save() {
5150
- mkdirSync5(resolve8(homedir5(), ".0agent"), { recursive: true });
5151
- writeFileSync5(TEAMS_PATH, YAML5.stringify(this.config), "utf8");
5217
+ mkdirSync5(resolve9(homedir6(), ".0agent"), { recursive: true });
5218
+ writeFileSync5(TEAMS_PATH, YAML6.stringify(this.config), "utf8");
5152
5219
  }
5153
5220
  };
5154
5221
 
@@ -5231,9 +5298,9 @@ var TeamSync = class {
5231
5298
  };
5232
5299
 
5233
5300
  // packages/daemon/src/GitHubMemorySync.ts
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";
5301
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, existsSync as existsSync11, readdirSync as readdirSync4 } from "node:fs";
5302
+ import { resolve as resolve10 } from "node:path";
5303
+ import { homedir as homedir7 } from "node:os";
5237
5304
  var GITHUB_API = "https://api.github.com";
5238
5305
  async function ghFetch(path, token, opts) {
5239
5306
  return fetch(`${GITHUB_API}${path}`, {
@@ -5352,10 +5419,10 @@ var GitHubMemorySync = class {
5352
5419
  )
5353
5420
  );
5354
5421
  }
5355
- const customSkillsDir = resolve9(homedir6(), ".0agent", "skills", "custom");
5356
- if (existsSync10(customSkillsDir)) {
5422
+ const customSkillsDir = resolve10(homedir7(), ".0agent", "skills", "custom");
5423
+ if (existsSync11(customSkillsDir)) {
5357
5424
  for (const file of readdirSync4(customSkillsDir).filter((f) => f.endsWith(".yaml"))) {
5358
- const content = readFileSync10(resolve9(customSkillsDir, file), "utf8");
5425
+ const content = readFileSync11(resolve10(customSkillsDir, file), "utf8");
5359
5426
  pushes.push(putFile(token, owner, repo, `skills/custom/${file}`, content, commitMsg));
5360
5427
  }
5361
5428
  }
@@ -5540,7 +5607,7 @@ var GitHubMemorySync = class {
5540
5607
  }
5541
5608
  async pullCustomSkills() {
5542
5609
  const { token, owner, repo } = this.config;
5543
- const dir = resolve9(homedir6(), ".0agent", "skills", "custom");
5610
+ const dir = resolve10(homedir7(), ".0agent", "skills", "custom");
5544
5611
  try {
5545
5612
  const res = await ghFetch(`/repos/${owner}/${repo}/contents/skills/custom`, token);
5546
5613
  if (!res.ok) return;
@@ -5550,7 +5617,7 @@ var GitHubMemorySync = class {
5550
5617
  if (content) {
5551
5618
  const { mkdirSync: mkdirSync7 } = await import("node:fs");
5552
5619
  mkdirSync7(dir, { recursive: true });
5553
- writeFileSync6(resolve9(dir, file.name), content, "utf8");
5620
+ writeFileSync6(resolve10(dir, file.name), content, "utf8");
5554
5621
  }
5555
5622
  }
5556
5623
  } catch {
@@ -5614,12 +5681,12 @@ var ZeroAgentDaemon = class {
5614
5681
  startedAt = 0;
5615
5682
  pidFilePath;
5616
5683
  constructor() {
5617
- this.pidFilePath = resolve11(homedir7(), ".0agent", "daemon.pid");
5684
+ this.pidFilePath = resolve12(homedir8(), ".0agent", "daemon.pid");
5618
5685
  }
5619
5686
  async start(opts) {
5620
5687
  this.config = await loadConfig(opts?.config_path);
5621
- const dotDir = resolve11(homedir7(), ".0agent");
5622
- if (!existsSync12(dotDir)) {
5688
+ const dotDir = resolve12(homedir8(), ".0agent");
5689
+ if (!existsSync13(dotDir)) {
5623
5690
  mkdirSync6(dotDir, { recursive: true });
5624
5691
  }
5625
5692
  this.adapter = new SQLiteAdapter({ db_path: this.config.graph.db_path });
@@ -5771,7 +5838,7 @@ var ZeroAgentDaemon = class {
5771
5838
  this.graph = null;
5772
5839
  }
5773
5840
  this.adapter = null;
5774
- if (existsSync12(this.pidFilePath)) {
5841
+ if (existsSync13(this.pidFilePath)) {
5775
5842
  try {
5776
5843
  unlinkSync2(this.pidFilePath);
5777
5844
  } catch {
@@ -5801,11 +5868,11 @@ var ZeroAgentDaemon = class {
5801
5868
  };
5802
5869
 
5803
5870
  // packages/daemon/src/start.ts
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)) {
5871
+ import { resolve as resolve13 } from "node:path";
5872
+ import { homedir as homedir9 } from "node:os";
5873
+ import { existsSync as existsSync14 } from "node:fs";
5874
+ var CONFIG_PATH = process.env["ZEROAGENT_CONFIG"] ?? resolve13(homedir9(), ".0agent", "config.yaml");
5875
+ if (!existsSync14(CONFIG_PATH)) {
5809
5876
  console.error(`
5810
5877
  0agent is not initialised.
5811
5878
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0agent",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "A persistent, learning AI agent that runs on your machine. An agent that learns.",
5
5
  "private": false,
6
6
  "license": "Apache-2.0",