0agent 1.0.44 → 1.0.46

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.
Files changed (3) hide show
  1. package/bin/chat.js +129 -93
  2. package/dist/daemon.mjs +50 -26
  3. package/package.json +1 -1
package/bin/chat.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * /model to switch. /key to add provider keys. Never forgets previous keys.
8
8
  */
9
9
 
10
- import { createInterface, emitKeypressEvents, moveCursor, clearLine as rlClearLine } from 'node:readline';
10
+ import { createInterface, emitKeypressEvents } from 'node:readline';
11
11
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
12
12
  import { resolve } from 'node:path';
13
13
  import { homedir } from 'node:os';
@@ -836,61 +836,114 @@ async function handleCommand(input) {
836
836
  }
837
837
  }
838
838
 
839
- // ─── Live slash-command menu ──────────────────────────────────────────────────
840
- // Drawn below the prompt as the user types. Uses moveCursor to avoid cursor
841
- // save/restore conflicts with readline.
842
- let _menuLines = 0; // how many lines the current menu occupies below the cursor
839
+ // ─── Command palette ──────────────────────────────────────────────────────────
840
+ // Fully takes over stdin when open. No cursor tricks pauses readline,
841
+ // reads raw bytes directly, draws with \x1b[NA\x1b[0J (up + clear-to-end).
842
+ // Returns the selected command string, or null if cancelled.
843
843
 
844
- function _drawMenu(filter) {
845
- if (pendingResolve) { _clearMenu(); return; } // don't show while session running
844
+ let _paletteOpen = false;
846
845
 
847
- const items = filter === null ? [] :
848
- SLASH_COMMANDS.filter(c =>
849
- !filter || c.cmd.slice(1).toLowerCase().startsWith(filter.toLowerCase())
850
- ).slice(0, 10);
846
+ async function openPalette(initialFilter = '') {
847
+ if (_paletteOpen || pendingResolve) return null;
848
+ _paletteOpen = true;
851
849
 
852
- // If nothing changed (same count), skip redraw to avoid flicker
853
- if (items.length === 0) { _clearMenu(); return; }
850
+ // Pause readline so it stops consuming stdin events
851
+ rl.pause();
852
+ // Resume the raw stream so our data handler receives bytes
853
+ process.stdin.resume();
854
+ if (process.stdin.isTTY) process.stdin.setRawMode(true);
854
855
 
855
- const needed = items.length + 1; // +1 for blank line
856
+ let filter = initialFilter.toLowerCase();
857
+ let idx = 0;
858
+ let drawn = 0; // number of lines we've printed so far
856
859
 
857
- // Move down past existing menu lines (or 0), then clear downward
858
- const existingLines = _menuLines;
859
- if (existingLines > 0) {
860
- moveCursor(process.stdout, 0, existingLines);
861
- for (let i = 0; i < existingLines; i++) {
862
- rlClearLine(process.stdout, 0);
863
- if (i < existingLines - 1) moveCursor(process.stdout, 0, -1);
864
- }
865
- moveCursor(process.stdout, 0, -(existingLines - 1));
866
- }
860
+ const getItems = () => SLASH_COMMANDS.filter(c =>
861
+ !filter || c.cmd.slice(1).startsWith(filter)
862
+ );
867
863
 
868
- // Print blank separator + menu items, tracking column 0
869
- process.stdout.write('\n');
870
- for (const m of items) {
871
- process.stdout.write(
872
- ` ${fmt(C.cyan, m.cmd.padEnd(20))} ${fmt(C.dim, m.desc)}\x1b[K\n`
873
- );
874
- }
864
+ const paint = () => {
865
+ // Erase previous draw: move up `drawn` lines, clear everything below
866
+ if (drawn > 0) process.stdout.write(`\x1b[${drawn}A\x1b[0J`);
875
867
 
876
- // Move back up to the prompt line and restore cursor after the typed text
877
- moveCursor(process.stdout, 0, -(needed));
878
- // Jump to end of current line (readline already put cursor there)
879
- moveCursor(process.stdout, 999, 0);
868
+ const items = getItems();
869
+ const show = items.slice(0, 14);
870
+ if (idx >= items.length) idx = Math.max(0, items.length - 1);
880
871
 
881
- _menuLines = needed;
882
- }
872
+ const lines = [];
883
873
 
884
- function _clearMenu() {
885
- if (_menuLines === 0) return;
886
- const n = _menuLines;
887
- _menuLines = 0;
888
- moveCursor(process.stdout, 0, n);
889
- for (let i = 0; i < n; i++) {
890
- rlClearLine(process.stdout, 0);
891
- moveCursor(process.stdout, 0, -1);
892
- }
893
- moveCursor(process.stdout, 0, 1); // back to prompt line
874
+ // Top border
875
+ lines.push(` \x1b[2m${'─'.repeat(58)}\x1b[0m`);
876
+
877
+ if (show.length === 0) {
878
+ lines.push(` \x1b[2m no commands match "/${filter}"\x1b[0m`);
879
+ } else {
880
+ for (let i = 0; i < show.length; i++) {
881
+ const m = show[i];
882
+ const sel = i === idx;
883
+ if (sel) {
884
+ lines.push(
885
+ ` \x1b[36;1m›\x1b[0m \x1b[36;1m${m.cmd.padEnd(22)}\x1b[0m \x1b[0m${m.desc}\x1b[0m`
886
+ );
887
+ } else {
888
+ lines.push(
889
+ ` \x1b[36m${m.cmd.padEnd(22)}\x1b[0m \x1b[2m${m.desc}\x1b[0m`
890
+ );
891
+ }
892
+ }
893
+ }
894
+
895
+ if (items.length > 14) lines.push(` \x1b[2m …${items.length - 14} more\x1b[0m`);
896
+
897
+ // Bottom: search input line
898
+ lines.push(` \x1b[2m${'─'.repeat(58)}\x1b[0m`);
899
+ lines.push(` ${fmt(C.cyan, '/')}${filter}\x1b[K \x1b[2m↑↓ navigate · Enter select · Esc cancel\x1b[0m`);
900
+
901
+ const out = lines.join('\n') + '\n';
902
+ process.stdout.write(out);
903
+ drawn = lines.length;
904
+ };
905
+
906
+ paint();
907
+
908
+ return new Promise((resolve) => {
909
+ const onData = (chunk) => {
910
+ const s = chunk.toString();
911
+
912
+ if (s === '\r' || s === '\n') { // Enter — select
913
+ const sel = getItems()[idx]?.cmd ?? null;
914
+ finish(sel);
915
+ } else if (s === '\x1b' || s === '\x03') { // Esc / Ctrl-C — cancel
916
+ finish(null);
917
+ } else if (s === '\x1b[A') { // Up arrow
918
+ idx = Math.max(0, idx - 1);
919
+ paint();
920
+ } else if (s === '\x1b[B') { // Down arrow
921
+ idx = Math.min(getItems().length - 1, idx + 1);
922
+ paint();
923
+ } else if (s === '\x7f' || s === '\x08') { // Backspace
924
+ filter = filter.slice(0, -1);
925
+ idx = 0;
926
+ paint();
927
+ } else if (/^[a-z0-9\-_]$/i.test(s)) { // Printable letter/digit/hyphen
928
+ filter += s.toLowerCase();
929
+ idx = 0;
930
+ paint();
931
+ }
932
+ };
933
+
934
+ const finish = (selected) => {
935
+ process.stdin.removeListener('data', onData);
936
+ // Erase the palette
937
+ if (drawn > 0) process.stdout.write(`\x1b[${drawn}A\x1b[0J`);
938
+ drawn = 0;
939
+ _paletteOpen = false;
940
+ // Hand control back to readline
941
+ rl.resume();
942
+ resolve(selected);
943
+ };
944
+
945
+ process.stdin.on('data', onData);
946
+ });
894
947
  }
895
948
 
896
949
  // ─── Main REPL ────────────────────────────────────────────────────────────────
@@ -902,50 +955,31 @@ const rl = createInterface({
902
955
  completer: (line, callback) => {
903
956
  if (!line.startsWith('/')) return callback(null, [[], line]);
904
957
 
905
- const filter = line.slice(1).toLowerCase();
906
- const matches = SLASH_COMMANDS.filter(c =>
907
- !filter || c.cmd.slice(1).startsWith(filter)
908
- );
909
-
910
- if (matches.length === 0) return callback(null, [[], line]);
958
+ const filter = line.slice(1).toLowerCase();
959
+ const matches = SLASH_COMMANDS.filter(c => !filter || c.cmd.slice(1).startsWith(filter));
911
960
 
912
- // Single match — let readline silently auto-complete
913
- if (matches.length === 1) {
914
- _clearMenu();
915
- return callback(null, [[matches[0].cmd + ' '], line]);
916
- }
961
+ // Single exact match — let readline silently auto-complete
962
+ if (matches.length === 1) return callback(null, [[matches[0].cmd + ' '], line]);
917
963
 
918
- // Multiple matches print formatted menu, suppress readline's plain list
919
- _clearMenu();
920
- process.stdout.write('\n\n');
921
- for (const m of matches.slice(0, 12)) {
922
- process.stdout.write(` ${fmt(C.cyan, m.cmd.padEnd(22))} ${fmt(C.dim, m.desc)}\n`);
923
- }
924
- if (matches.length > 12) {
925
- process.stdout.write(` ${fmt(C.dim, `…and ${matches.length - 12} more`)}\n`);
926
- }
927
- process.stdout.write('\n');
928
- setImmediate(() => rl.prompt(true));
964
+ // Multiple (or bare /) open interactive palette
965
+ // Schedule after this tick so readline finishes its Tab handling first
966
+ setImmediate(async () => {
967
+ // Clear the typed fragment from the prompt line before opening palette
968
+ process.stdout.write('\r\x1b[2K');
969
+ const selected = await openPalette(filter);
970
+ if (selected) {
971
+ await executeInput(selected);
972
+ } else {
973
+ rl.prompt();
974
+ }
975
+ });
929
976
  return callback(null, [[], line]);
930
977
  },
931
978
  });
932
979
 
933
- // Live menu on keypress draws below the prompt as user types
980
+ // Trigger palette when user types exactly '/' and presses Tab or Enter isn't needed —
981
+ // the completer above handles Tab. For bare '/' + Enter, handled in rl.on('line') below.
934
982
  emitKeypressEvents(process.stdin, rl);
935
- process.stdin.on('keypress', (_char, key) => {
936
- if (key?.name === 'return' || key?.name === 'enter') {
937
- _clearMenu(); // clear before readline processes the line
938
- return;
939
- }
940
- setImmediate(() => {
941
- const line = rl.line ?? '';
942
- if (line.startsWith('/') && !pendingResolve) {
943
- _drawMenu(line.slice(1));
944
- } else {
945
- _clearMenu();
946
- }
947
- });
948
- });
949
983
 
950
984
  printHeader();
951
985
  printInsights();
@@ -1253,19 +1287,21 @@ async function drainQueue() {
1253
1287
  }
1254
1288
 
1255
1289
  rl.on('line', async (input) => {
1256
- _clearMenu(); // always clear menu when a line is submitted
1257
1290
  const line = input.trim();
1258
1291
  if (!line) { rl.prompt(); return; }
1259
1292
 
1260
- // Bare `/` → show full command palette
1261
- if (line === '/') {
1262
- console.log('');
1263
- for (const m of SLASH_COMMANDS) {
1264
- console.log(` ${fmt(C.cyan, m.cmd.padEnd(22))} ${fmt(C.dim, m.desc)}`);
1293
+ // Bare `/` or partial `/cmd` with no session running open interactive palette
1294
+ if (line.startsWith('/') && !pendingResolve) {
1295
+ const filter = line === '/' ? '' : line.slice(1);
1296
+ const exact = SLASH_COMMANDS.find(c => c.cmd === line);
1297
+ if (!exact) {
1298
+ // Clear the echoed input line before opening palette
1299
+ process.stdout.write('\x1b[1A\r\x1b[2K');
1300
+ const selected = await openPalette(filter);
1301
+ if (selected) await executeInput(selected);
1302
+ else rl.prompt();
1303
+ return;
1265
1304
  }
1266
- console.log('');
1267
- rl.prompt();
1268
- return;
1269
1305
  }
1270
1306
 
1271
1307
  // If a session is already running, queue the message.
package/dist/daemon.mjs CHANGED
@@ -2493,8 +2493,9 @@ var init_MemoryCapability = __esm({
2493
2493
  "use strict";
2494
2494
  init_src();
2495
2495
  MemoryCapability = class {
2496
- constructor(graph) {
2496
+ constructor(graph, onWrite) {
2497
2497
  this.graph = graph;
2498
+ this.onWrite = onWrite;
2498
2499
  }
2499
2500
  name = "memory_write";
2500
2501
  description = "Persist a discovered fact to long-term memory so it survives across sessions.";
@@ -2537,11 +2538,13 @@ var init_MemoryCapability = __esm({
2537
2538
  });
2538
2539
  this.graph.addNode(node);
2539
2540
  }
2540
- return {
2541
+ const result = {
2541
2542
  success: true,
2542
2543
  output: `Remembered: "${label}" = ${content.slice(0, 120)}${content.length > 120 ? "\u2026" : ""}`,
2543
2544
  duration_ms: Date.now() - start
2544
2545
  };
2546
+ this.onWrite?.();
2547
+ return result;
2545
2548
  } catch (err) {
2546
2549
  return {
2547
2550
  success: false,
@@ -2651,7 +2654,7 @@ var init_CapabilityRegistry = __esm({
2651
2654
  * task_type: browser_task). The main agent does NOT have direct access
2652
2655
  * to browser_open without going through a subagent spawn.
2653
2656
  */
2654
- constructor(codespaceManager, graph) {
2657
+ constructor(codespaceManager, graph, onMemoryWrite) {
2655
2658
  this.register(new WebSearchCapability());
2656
2659
  if (codespaceManager) {
2657
2660
  try {
@@ -2667,7 +2670,7 @@ var init_CapabilityRegistry = __esm({
2667
2670
  this.register(new ShellCapability());
2668
2671
  this.register(new FileCapability());
2669
2672
  if (graph) {
2670
- this.register(new MemoryCapability(graph));
2673
+ this.register(new MemoryCapability(graph, onMemoryWrite));
2671
2674
  }
2672
2675
  }
2673
2676
  register(cap) {
@@ -2734,7 +2737,7 @@ var init_AgentExecutor = __esm({
2734
2737
  this.maxIterations = config.max_iterations ?? 20;
2735
2738
  this.maxCommandMs = config.max_command_ms ?? 3e4;
2736
2739
  this.agentRoot = config.agent_root;
2737
- this.registry = new CapabilityRegistry(void 0, config.graph);
2740
+ this.registry = new CapabilityRegistry(void 0, config.graph, config.onMemoryWrite);
2738
2741
  }
2739
2742
  cwd;
2740
2743
  maxIterations;
@@ -4735,7 +4738,7 @@ var SessionManager = class {
4735
4738
  if (activeLLM?.isConfigured) {
4736
4739
  const executor = new AgentExecutor(
4737
4740
  activeLLM,
4738
- { cwd: this.cwd, agent_root: this.agentRoot, graph: this.graph },
4741
+ { cwd: this.cwd, agent_root: this.agentRoot, graph: this.graph, onMemoryWrite: this.onMemoryWritten },
4739
4742
  // step callback → emit session.step events
4740
4743
  (step) => this.addStep(sessionId, step),
4741
4744
  // token callback → emit session.token events
@@ -4901,33 +4904,48 @@ Current task:`;
4901
4904
  */
4902
4905
  async _extractAndPersistFacts(task, output, llm) {
4903
4906
  if (!this.graph || !llm.isConfigured) return;
4907
+ const combined = `${task} ${output}`;
4908
+ if (combined.trim().length < 20) return;
4904
4909
  const prompt = `Extract factual entities from this conversation that should be remembered long-term.
4905
- Return ONLY a JSON array, no other text, max 12 items.
4910
+ Return ONLY a valid JSON array (no markdown, no explanation), max 12 items.
4911
+ If nothing worth remembering, return [].
4906
4912
 
4907
4913
  Types: identity (name/role), project (apps/products), tech (stack/tools), preference, url, path, config, outcome
4908
4914
 
4909
- Format: [{"label":"snake_case_key","content":"value to remember","type":"type"}]
4915
+ Format: [{"label":"snake_case_key","content":"value","type":"type"}]
4910
4916
 
4911
4917
  Examples:
4912
- - User says "my name is Sahil" \u2192 {"label":"user_name","content":"Sahil","type":"identity"}
4913
- - User says "we have a telegram bot" \u2192 {"label":"project_telegram_bot","content":"user has a Telegram bot project","type":"project"}
4914
- - User says "I use React and Next.js" \u2192 {"label":"tech_stack","content":"React, Next.js","type":"tech"}
4918
+ - "my name is Sahil" \u2192 {"label":"user_name","content":"Sahil","type":"identity"}
4919
+ - "we have a telegram bot" \u2192 {"label":"project_telegram_bot","content":"user has a Telegram bot","type":"project"}
4920
+ - "I use React and Next.js" \u2192 {"label":"tech_stack","content":"React, Next.js","type":"tech"}
4921
+ - ngrok URL found \u2192 {"label":"ngrok_url","content":"https://abc.ngrok.io","type":"url"}
4915
4922
 
4916
4923
  Conversation:
4917
4924
  User: ${task.slice(0, 600)}
4918
- Agent: ${output.slice(0, 400)}`;
4925
+ Agent: ${output.slice(0, 500)}`;
4919
4926
  try {
4920
4927
  const resp = await llm.complete(
4921
4928
  [{ role: "user", content: prompt }],
4922
- "You are a concise memory extraction system. Extract only factual, durable information. Skip generic statements."
4929
+ "You are a memory extraction system. Be concise. Extract only factual, durable information. Return valid JSON only."
4923
4930
  );
4924
- const jsonMatch = resp.content.match(/\[[\s\S]*?\]/);
4925
- if (!jsonMatch) return;
4926
- const entities = JSON.parse(jsonMatch[0]);
4931
+ let entities = [];
4932
+ const raw = resp.content.trim();
4933
+ try {
4934
+ const parsed = JSON.parse(raw);
4935
+ if (Array.isArray(parsed)) entities = parsed;
4936
+ } catch {
4937
+ const match = raw.match(/\[\s*\{[\s\S]*?\}\s*\]/);
4938
+ if (match) {
4939
+ try {
4940
+ entities = JSON.parse(match[0]);
4941
+ } catch {
4942
+ }
4943
+ }
4944
+ }
4927
4945
  if (!Array.isArray(entities) || entities.length === 0) return;
4928
4946
  let wrote = 0;
4929
4947
  for (const e of entities.slice(0, 12)) {
4930
- if (!e.label?.trim() || !e.content?.trim()) continue;
4948
+ if (!e?.label?.trim() || !e?.content?.trim()) continue;
4931
4949
  const nodeId = `memory:${e.label.toLowerCase().replace(/[^a-z0-9_]/g, "_")}`;
4932
4950
  try {
4933
4951
  const existing = this.graph.getNode(nodeId);
@@ -4946,14 +4964,16 @@ Agent: ${output.slice(0, 400)}`;
4946
4964
  }));
4947
4965
  }
4948
4966
  wrote++;
4949
- } catch {
4967
+ } catch (err) {
4968
+ console.warn(`[0agent] Memory write failed for "${e.label}":`, err instanceof Error ? err.message : err);
4950
4969
  }
4951
4970
  }
4952
4971
  if (wrote > 0) {
4953
- console.log(`[0agent] Memory: extracted ${wrote} facts from session`);
4972
+ console.log(`[0agent] Memory: persisted ${wrote} facts \u2192 graph`);
4954
4973
  this.onMemoryWritten?.();
4955
4974
  }
4956
- } catch {
4975
+ } catch (err) {
4976
+ console.warn("[0agent] Memory extraction failed:", err instanceof Error ? err.message : String(err));
4957
4977
  }
4958
4978
  }
4959
4979
  /**
@@ -7303,20 +7323,24 @@ var ZeroAgentDaemon = class {
7303
7323
  adapter: this.adapter,
7304
7324
  agentRoot,
7305
7325
  // agent source path — self-improvement tasks read the right files
7306
- // Mark GitHub memory dirty immediately when facts are extracted pushes within 2min
7326
+ // Push to GitHub immediately when facts are extracted from a session
7307
7327
  onMemoryWritten: () => {
7308
7328
  this.githubMemorySync?.markDirty();
7329
+ if (this.githubMemorySync) {
7330
+ this.githubMemorySync.push("sync: new facts learned").then((r) => {
7331
+ if (r.pushed) console.log(`[0agent] Memory pushed: ${r.nodes_synced} nodes \u2192 github`);
7332
+ }).catch(() => {
7333
+ });
7334
+ }
7309
7335
  }
7310
7336
  });
7311
7337
  const teamSync = identity && teams.length > 0 ? new TeamSync(teamManager, this.adapter, identity.entity_node_id) : null;
7312
7338
  if (this.githubMemorySync) {
7313
7339
  const memSync = this.githubMemorySync;
7314
7340
  this.memorySyncTimer = setInterval(async () => {
7315
- if (memSync.hasPendingChanges()) {
7316
- const result = await memSync.push().catch(() => null);
7317
- if (result?.pushed) {
7318
- console.log(`[0agent] Memory auto-synced: ${result.nodes_synced} nodes`);
7319
- }
7341
+ const result = await memSync.push().catch(() => null);
7342
+ if (result?.pushed && result.nodes_synced > 0) {
7343
+ console.log(`[0agent] Memory synced: ${result.nodes_synced} nodes \u2192 github`);
7320
7344
  }
7321
7345
  }, 2 * 60 * 1e3);
7322
7346
  if (typeof this.memorySyncTimer === "object") this.memorySyncTimer.unref?.();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0agent",
3
- "version": "1.0.44",
3
+ "version": "1.0.46",
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",