0agent 1.0.43 → 1.0.44

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/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 } from 'node:readline';
10
+ import { createInterface, emitKeypressEvents, moveCursor, clearLine as rlClearLine } 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';
@@ -31,6 +31,7 @@ const SLASH_COMMANDS = [
31
31
  { cmd: '/security-audit',desc: 'Security audit — find vulnerabilities' },
32
32
  { cmd: '/design-review', desc: 'Design review — architecture and patterns' },
33
33
  // Built-ins
34
+ { cmd: '/telegram', desc: 'Connect Telegram bot — forward messages to 0agent'},
34
35
  { cmd: '/model', desc: 'Show or switch the LLM model' },
35
36
  { cmd: '/key', desc: 'Update a stored API key' },
36
37
  { cmd: '/status', desc: 'Daemon health, graph stats, active sessions' },
@@ -754,6 +755,33 @@ async function handleCommand(input) {
754
755
  break;
755
756
  }
756
757
 
758
+ // /telegram — configure Telegram bot token
759
+ case '/telegram': {
760
+ if (!cfg) { console.log(fmt(C.red, ' No config found. Run: 0agent init')); break; }
761
+ const existingToken = cfg?.telegram?.token;
762
+ if (existingToken) {
763
+ console.log(`\n Telegram bot: ${fmt(C.green, '✓ configured')}`);
764
+ console.log(` Token: ${existingToken.slice(0, 10)}••••`);
765
+ console.log(` ${fmt(C.dim, 'To update: /telegram <new-token>\n')}`);
766
+ }
767
+ const token = parts[1];
768
+ if (!token) {
769
+ if (!existingToken) {
770
+ console.log('\n Connect your Telegram bot to 0agent:\n');
771
+ console.log(` 1. Create a bot: ${fmt(C.cyan, 'https://t.me/BotFather')} → /newbot`);
772
+ console.log(` 2. Copy the token and run: ${fmt(C.cyan, '/telegram <token>')}`);
773
+ console.log(` 3. Restart daemon: ${fmt(C.dim, '0agent stop && 0agent start')}\n`);
774
+ }
775
+ break;
776
+ }
777
+ if (!cfg.telegram) cfg.telegram = {};
778
+ cfg.telegram.token = token;
779
+ saveConfig(cfg);
780
+ console.log(` ${fmt(C.green, '✓')} Telegram token saved`);
781
+ console.log(` ${fmt(C.dim, 'Restart daemon for changes to take effect: 0agent stop && 0agent start\n')}`);
782
+ break;
783
+ }
784
+
757
785
  case '/skills': {
758
786
  try {
759
787
  const skills = await fetch(`${BASE_URL}/api/skills`).then(r => r.json());
@@ -831,7 +859,7 @@ function _drawMenu(filter) {
831
859
  if (existingLines > 0) {
832
860
  moveCursor(process.stdout, 0, existingLines);
833
861
  for (let i = 0; i < existingLines; i++) {
834
- clearLine(process.stdout, 0);
862
+ rlClearLine(process.stdout, 0);
835
863
  if (i < existingLines - 1) moveCursor(process.stdout, 0, -1);
836
864
  }
837
865
  moveCursor(process.stdout, 0, -(existingLines - 1));
@@ -859,7 +887,7 @@ function _clearMenu() {
859
887
  _menuLines = 0;
860
888
  moveCursor(process.stdout, 0, n);
861
889
  for (let i = 0; i < n; i++) {
862
- clearLine(process.stdout, 0);
890
+ rlClearLine(process.stdout, 0);
863
891
  moveCursor(process.stdout, 0, -1);
864
892
  }
865
893
  moveCursor(process.stdout, 0, 1); // back to prompt line
package/dist/daemon.mjs CHANGED
@@ -3027,13 +3027,19 @@ content = element.text if element else page.get_all_text()` : `content = page.ge
3027
3027
  `- Be concise in your final response: state what was done and where to find it`,
3028
3028
  ...hasMemory ? [
3029
3029
  ``,
3030
- `Memory (IMPORTANT):`,
3031
- `- ALWAYS call memory_write after discovering important facts:`,
3032
- ` \xB7 Live URLs (ngrok, deployed apps, APIs): memory_write({label:"ngrok_url", content:"https://...", type:"url"})`,
3030
+ `Memory (CRITICAL \u2014 write EVERYTHING you learn):`,
3031
+ `- Call memory_write for ANY fact you discover \u2014 conversational OR from tools:`,
3032
+ ` \xB7 User's name/identity: memory_write({label:"user_name", content:"Sahil", type:"identity"})`,
3033
+ ` \xB7 Projects they mention: memory_write({label:"project_telegram_bot", content:"user has a Telegram bot", type:"project"})`,
3034
+ ` \xB7 Tech stack / tools: memory_write({label:"tech_stack", content:"Node.js, Telegram", type:"tech"})`,
3035
+ ` \xB7 Preferences and decisions they express`,
3036
+ ` \xB7 Live URLs (ngrok, deployed apps): memory_write({label:"ngrok_url", content:"https://...", type:"url"})`,
3033
3037
  ` \xB7 Server ports: memory_write({label:"dev_server_port", content:"3000", type:"config"})`,
3034
3038
  ` \xB7 File paths of created projects: memory_write({label:"project_path", content:"/path/to/project", type:"path"})`,
3035
- ` \xB7 Task outcomes: memory_write({label:"last_task_result", content:"...", type:"outcome"})`,
3036
- `- Call memory_write immediately when you find the value, not just at the end`
3039
+ ` \xB7 Task outcomes: memory_write({label:"last_outcome", content:"...", type:"outcome"})`,
3040
+ `- Write to memory FIRST when the user tells you something about themselves or their work`,
3041
+ `- If the user says "my name is X" \u2192 memory_write immediately, before anything else`,
3042
+ `- If they say "we have a Y" or "our Y" \u2192 memory_write it as a project fact`
3037
3043
  ] : []
3038
3044
  ];
3039
3045
  if (isSelfMod && this.agentRoot) {
@@ -4516,6 +4522,7 @@ var SessionManager = class {
4516
4522
  weightUpdater;
4517
4523
  anthropicFetcher = new AnthropicSkillFetcher();
4518
4524
  agentRoot;
4525
+ onMemoryWritten;
4519
4526
  constructor(deps = {}) {
4520
4527
  this.inferenceEngine = deps.inferenceEngine;
4521
4528
  this.eventBus = deps.eventBus;
@@ -4525,6 +4532,7 @@ var SessionManager = class {
4525
4532
  this.identity = deps.identity;
4526
4533
  this.projectContext = deps.projectContext;
4527
4534
  this.agentRoot = deps.agentRoot;
4535
+ this.onMemoryWritten = deps.onMemoryWritten;
4528
4536
  if (deps.adapter) {
4529
4537
  this.conversationStore = new ConversationStore(deps.adapter);
4530
4538
  this.conversationStore.init();
@@ -4816,6 +4824,8 @@ Current task:`;
4816
4824
  this.addStep(sessionId, `Commands run: ${agentResult.commands_run.length}`);
4817
4825
  }
4818
4826
  this.addStep(sessionId, `Done (${agentResult.tokens_used} tokens, ${agentResult.iterations} LLM turns)`);
4827
+ this._extractAndPersistFacts(enrichedReq.task, agentResult.output, activeLLM).catch(() => {
4828
+ });
4819
4829
  this.completeSession(sessionId, {
4820
4830
  output: agentResult.output,
4821
4831
  files_written: agentResult.files_written,
@@ -4884,6 +4894,68 @@ Current task:`;
4884
4894
  }
4885
4895
  return this.llm;
4886
4896
  }
4897
+ /**
4898
+ * After every session, run a lightweight LLM pass to extract factual entities
4899
+ * (name, projects, tech, preferences, URLs) and persist them to the graph.
4900
+ * This catches everything the agent didn't explicitly memory_write during execution.
4901
+ */
4902
+ async _extractAndPersistFacts(task, output, llm) {
4903
+ if (!this.graph || !llm.isConfigured) return;
4904
+ 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.
4906
+
4907
+ Types: identity (name/role), project (apps/products), tech (stack/tools), preference, url, path, config, outcome
4908
+
4909
+ Format: [{"label":"snake_case_key","content":"value to remember","type":"type"}]
4910
+
4911
+ 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"}
4915
+
4916
+ Conversation:
4917
+ User: ${task.slice(0, 600)}
4918
+ Agent: ${output.slice(0, 400)}`;
4919
+ try {
4920
+ const resp = await llm.complete(
4921
+ [{ role: "user", content: prompt }],
4922
+ "You are a concise memory extraction system. Extract only factual, durable information. Skip generic statements."
4923
+ );
4924
+ const jsonMatch = resp.content.match(/\[[\s\S]*?\]/);
4925
+ if (!jsonMatch) return;
4926
+ const entities = JSON.parse(jsonMatch[0]);
4927
+ if (!Array.isArray(entities) || entities.length === 0) return;
4928
+ let wrote = 0;
4929
+ for (const e of entities.slice(0, 12)) {
4930
+ if (!e.label?.trim() || !e.content?.trim()) continue;
4931
+ const nodeId = `memory:${e.label.toLowerCase().replace(/[^a-z0-9_]/g, "_")}`;
4932
+ try {
4933
+ const existing = this.graph.getNode(nodeId);
4934
+ if (existing) {
4935
+ this.graph.updateNode(nodeId, {
4936
+ label: e.label,
4937
+ metadata: { ...existing.metadata, content: e.content, type: e.type ?? "note", updated_at: (/* @__PURE__ */ new Date()).toISOString() }
4938
+ });
4939
+ } else {
4940
+ this.graph.addNode(createNode({
4941
+ id: nodeId,
4942
+ graph_id: "root",
4943
+ label: e.label,
4944
+ type: "context" /* CONTEXT */,
4945
+ metadata: { content: e.content, type: e.type ?? "note", saved_at: (/* @__PURE__ */ new Date()).toISOString() }
4946
+ }));
4947
+ }
4948
+ wrote++;
4949
+ } catch {
4950
+ }
4951
+ }
4952
+ if (wrote > 0) {
4953
+ console.log(`[0agent] Memory: extracted ${wrote} facts from session`);
4954
+ this.onMemoryWritten?.();
4955
+ }
4956
+ } catch {
4957
+ }
4958
+ }
4887
4959
  /**
4888
4960
  * Convert a task result into a weight signal for the knowledge graph.
4889
4961
  *
@@ -6930,6 +7002,192 @@ var CodespaceManager = class {
6930
7002
 
6931
7003
  // packages/daemon/src/ZeroAgentDaemon.ts
6932
7004
  init_RuntimeSelfHeal();
7005
+
7006
+ // packages/daemon/src/TelegramBridge.ts
7007
+ var TelegramBridge = class {
7008
+ constructor(config, sessions, eventBus) {
7009
+ this.config = config;
7010
+ this.sessions = sessions;
7011
+ this.eventBus = eventBus;
7012
+ this.token = config.token;
7013
+ this.allowedUsers = new Set(config.allowed_users ?? []);
7014
+ }
7015
+ token;
7016
+ allowedUsers;
7017
+ offset = 0;
7018
+ pollTimer = null;
7019
+ running = false;
7020
+ // session_id per chat for streaming
7021
+ pendingSessions = /* @__PURE__ */ new Map();
7022
+ start() {
7023
+ if (this.running) return;
7024
+ this.running = true;
7025
+ console.log("[0agent] Telegram: bot polling started");
7026
+ this._poll();
7027
+ this.eventBus.onEvent((event) => {
7028
+ const chatId = this._getChatIdForSession(String(event.session_id ?? ""));
7029
+ if (!chatId) return;
7030
+ if (event.type === "session.completed") {
7031
+ const result = event.result;
7032
+ const output = String(result?.output ?? "").trim();
7033
+ if (output && output !== "(no output)") {
7034
+ this._send(chatId, output).catch(() => {
7035
+ });
7036
+ }
7037
+ this.pendingSessions.delete(chatId);
7038
+ } else if (event.type === "session.failed") {
7039
+ const err = String(event.error ?? "Task failed");
7040
+ this._send(chatId, `\u26A0\uFE0F ${err}`).catch(() => {
7041
+ });
7042
+ this.pendingSessions.delete(chatId);
7043
+ }
7044
+ });
7045
+ }
7046
+ stop() {
7047
+ this.running = false;
7048
+ if (this.pollTimer) {
7049
+ clearTimeout(this.pollTimer);
7050
+ this.pollTimer = null;
7051
+ }
7052
+ }
7053
+ _getChatIdForSession(sessionId) {
7054
+ for (const [chatId, sid] of this.pendingSessions) {
7055
+ if (sid === sessionId) return chatId;
7056
+ }
7057
+ return null;
7058
+ }
7059
+ async _poll() {
7060
+ if (!this.running) return;
7061
+ try {
7062
+ const updates = await this._getUpdates();
7063
+ for (const u of updates) {
7064
+ await this._handleUpdate(u);
7065
+ }
7066
+ } catch {
7067
+ }
7068
+ if (this.running) {
7069
+ this.pollTimer = setTimeout(() => this._poll(), 1e3);
7070
+ }
7071
+ }
7072
+ async _getUpdates() {
7073
+ const res = await fetch(
7074
+ `https://api.telegram.org/bot${this.token}/getUpdates?offset=${this.offset}&timeout=10&limit=20`,
7075
+ { signal: AbortSignal.timeout(15e3) }
7076
+ );
7077
+ if (!res.ok) return [];
7078
+ const data = await res.json();
7079
+ if (!data.ok || !data.result.length) return [];
7080
+ this.offset = data.result[data.result.length - 1].update_id + 1;
7081
+ return data.result;
7082
+ }
7083
+ async _handleUpdate(u) {
7084
+ const msg = u.message;
7085
+ if (!msg?.text || !msg.from) return;
7086
+ const chatId = msg.chat.id;
7087
+ const userId = msg.from.id;
7088
+ const text = msg.text.trim();
7089
+ const userName = msg.from.first_name ?? msg.from.username ?? "User";
7090
+ if (this.allowedUsers.size > 0 && !this.allowedUsers.has(userId)) {
7091
+ await this._send(chatId, "\u26D4 You are not authorised to use this agent.");
7092
+ return;
7093
+ }
7094
+ if (text === "/start" || text === "/help") {
7095
+ await this._send(
7096
+ chatId,
7097
+ `\u{1F44B} Hi ${userName}! I'm 0agent \u2014 your AI that runs on your machine.
7098
+
7099
+ Send me any task and I'll get it done:
7100
+ \u2022 "make a website for my coffee shop"
7101
+ \u2022 "research my competitor's pricing"
7102
+ \u2022 "fix the bug in auth.ts"
7103
+
7104
+ I remember everything across sessions.`
7105
+ );
7106
+ return;
7107
+ }
7108
+ if (text === "/status") {
7109
+ try {
7110
+ const r = await fetch("http://localhost:4200/api/health", { signal: AbortSignal.timeout(2e3) });
7111
+ const h = await r.json();
7112
+ await this._send(
7113
+ chatId,
7114
+ `\u2705 Daemon running
7115
+ Graph: ${h.graph_nodes} nodes \xB7 ${h.graph_edges} edges
7116
+ Sessions: ${h.active_sessions} active`
7117
+ );
7118
+ } catch {
7119
+ await this._send(chatId, "\u26A0\uFE0F Daemon not reachable");
7120
+ }
7121
+ return;
7122
+ }
7123
+ await this._sendAction(chatId, "typing");
7124
+ await this._send(chatId, `\u23F3 Working on it\u2026`);
7125
+ try {
7126
+ const res = await fetch("http://localhost:4200/api/sessions", {
7127
+ method: "POST",
7128
+ headers: { "Content-Type": "application/json" },
7129
+ body: JSON.stringify({
7130
+ task: text,
7131
+ context: { system_context: `User's name: ${userName}. Message from Telegram.` }
7132
+ }),
7133
+ signal: AbortSignal.timeout(5e3)
7134
+ });
7135
+ const session = await res.json();
7136
+ const sessionId = session.session_id ?? session.id;
7137
+ if (sessionId) {
7138
+ this.pendingSessions.set(chatId, sessionId);
7139
+ } else {
7140
+ await this._send(chatId, "\u26A0\uFE0F Could not start session");
7141
+ }
7142
+ } catch (e) {
7143
+ await this._send(chatId, `\u26A0\uFE0F Error: ${e instanceof Error ? e.message : String(e)}`);
7144
+ }
7145
+ }
7146
+ async _send(chatId, text) {
7147
+ const chunks = this._splitMessage(text);
7148
+ for (const chunk of chunks) {
7149
+ await fetch(`https://api.telegram.org/bot${this.token}/sendMessage`, {
7150
+ method: "POST",
7151
+ headers: { "Content-Type": "application/json" },
7152
+ body: JSON.stringify({ chat_id: chatId, text: chunk, parse_mode: "Markdown" }),
7153
+ signal: AbortSignal.timeout(1e4)
7154
+ }).catch(() => {
7155
+ return fetch(`https://api.telegram.org/bot${this.token}/sendMessage`, {
7156
+ method: "POST",
7157
+ headers: { "Content-Type": "application/json" },
7158
+ body: JSON.stringify({ chat_id: chatId, text: chunk }),
7159
+ signal: AbortSignal.timeout(1e4)
7160
+ }).catch(() => {
7161
+ });
7162
+ });
7163
+ }
7164
+ }
7165
+ async _sendAction(chatId, action) {
7166
+ await fetch(`https://api.telegram.org/bot${this.token}/sendChatAction`, {
7167
+ method: "POST",
7168
+ headers: { "Content-Type": "application/json" },
7169
+ body: JSON.stringify({ chat_id: chatId, action }),
7170
+ signal: AbortSignal.timeout(5e3)
7171
+ }).catch(() => {
7172
+ });
7173
+ }
7174
+ _splitMessage(text) {
7175
+ if (text.length <= 4e3) return [text];
7176
+ const chunks = [];
7177
+ let i = 0;
7178
+ while (i < text.length) {
7179
+ chunks.push(text.slice(i, i + 4e3));
7180
+ i += 4e3;
7181
+ }
7182
+ return chunks;
7183
+ }
7184
+ static isConfigured(config) {
7185
+ const c = config;
7186
+ return !!(c?.token && typeof c.token === "string" && c.token.length > 10);
7187
+ }
7188
+ };
7189
+
7190
+ // packages/daemon/src/ZeroAgentDaemon.ts
6933
7191
  import { fileURLToPath as fileURLToPath3 } from "node:url";
6934
7192
  import { dirname as dirname6 } from "node:path";
6935
7193
  var ZeroAgentDaemon = class {
@@ -6949,6 +7207,7 @@ var ZeroAgentDaemon = class {
6949
7207
  codespaceManager = null;
6950
7208
  schedulerManager = null;
6951
7209
  runtimeHealer = null;
7210
+ telegramBridge = null;
6952
7211
  startedAt = 0;
6953
7212
  pidFilePath;
6954
7213
  constructor() {
@@ -7042,8 +7301,12 @@ var ZeroAgentDaemon = class {
7042
7301
  identity: identity ?? void 0,
7043
7302
  projectContext: projectContext ?? void 0,
7044
7303
  adapter: this.adapter,
7045
- agentRoot
7304
+ agentRoot,
7046
7305
  // agent source path — self-improvement tasks read the right files
7306
+ // Mark GitHub memory dirty immediately when facts are extracted — pushes within 2min
7307
+ onMemoryWritten: () => {
7308
+ this.githubMemorySync?.markDirty();
7309
+ }
7047
7310
  });
7048
7311
  const teamSync = identity && teams.length > 0 ? new TeamSync(teamManager, this.adapter, identity.entity_node_id) : null;
7049
7312
  if (this.githubMemorySync) {
@@ -7055,7 +7318,7 @@ var ZeroAgentDaemon = class {
7055
7318
  console.log(`[0agent] Memory auto-synced: ${result.nodes_synced} nodes`);
7056
7319
  }
7057
7320
  }
7058
- }, 30 * 60 * 1e3);
7321
+ }, 2 * 60 * 1e3);
7059
7322
  if (typeof this.memorySyncTimer === "object") this.memorySyncTimer.unref?.();
7060
7323
  }
7061
7324
  let proactiveSurface = null;
@@ -7085,6 +7348,11 @@ var ZeroAgentDaemon = class {
7085
7348
  }
7086
7349
  this.schedulerManager = new SchedulerManager(this.adapter, this.sessionManager, this.eventBus);
7087
7350
  this.schedulerManager.start();
7351
+ const tgCfg = this.config["telegram"];
7352
+ if (TelegramBridge.isConfigured(tgCfg) && this.sessionManager && this.eventBus) {
7353
+ this.telegramBridge = new TelegramBridge(tgCfg, this.sessionManager, this.eventBus);
7354
+ this.telegramBridge.start();
7355
+ }
7088
7356
  this.backgroundWorkers = new BackgroundWorkers({
7089
7357
  graph: this.graph,
7090
7358
  traceStore: this.traceStore,
@@ -7156,6 +7424,8 @@ var ZeroAgentDaemon = class {
7156
7424
  this.memorySyncTimer = null;
7157
7425
  }
7158
7426
  this.githubMemorySync = null;
7427
+ this.telegramBridge?.stop();
7428
+ this.telegramBridge = null;
7159
7429
  this.schedulerManager?.stop();
7160
7430
  this.schedulerManager = null;
7161
7431
  this.codespaceManager?.closeTunnel();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0agent",
3
- "version": "1.0.43",
3
+ "version": "1.0.44",
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",