0agent 1.0.76 → 1.0.78

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 (2) hide show
  1. package/dist/daemon.mjs +426 -34
  2. package/package.json +1 -1
package/dist/daemon.mjs CHANGED
@@ -1978,6 +1978,13 @@ var init_LLMExecutor = __esm({
1978
1978
  constructor(config) {
1979
1979
  this.config = config;
1980
1980
  }
1981
+ /**
1982
+ * Create a new LLMExecutor with a different model but same provider/key.
1983
+ * Used by SmartModelRouter to switch to a fast model for simple messages.
1984
+ */
1985
+ withModel(model) {
1986
+ return new _LLMExecutor({ ...this.config, model });
1987
+ }
1981
1988
  get isConfigured() {
1982
1989
  if (this.config.provider === "ollama") return true;
1983
1990
  return !!this.config.api_key?.trim();
@@ -5043,6 +5050,88 @@ ${lines.join("\n")}`,
5043
5050
  }
5044
5051
  });
5045
5052
 
5053
+ // packages/daemon/src/capabilities/SessionSearchCapability.ts
5054
+ var SessionSearchCapability;
5055
+ var init_SessionSearchCapability = __esm({
5056
+ "packages/daemon/src/capabilities/SessionSearchCapability.ts"() {
5057
+ "use strict";
5058
+ SessionSearchCapability = class {
5059
+ name = "session_search";
5060
+ description = "Search across past conversation history to recall previous interactions and decisions.";
5061
+ toolDefinition = {
5062
+ name: "session_search",
5063
+ description: "Search across all past conversations for relevant context. Use this when the user references something from a previous session, or when you need to recall past decisions, outcomes, or context. Returns matching conversation excerpts with timestamps.",
5064
+ input_schema: {
5065
+ type: "object",
5066
+ properties: {
5067
+ query: { type: "string", description: "Search query \u2014 keywords, phrases, or natural language question" },
5068
+ max_results: { type: "string", description: "Maximum results to return (default 5)" }
5069
+ },
5070
+ required: ["query"]
5071
+ }
5072
+ };
5073
+ getDbPath;
5074
+ constructor(getDbPath) {
5075
+ this.getDbPath = getDbPath;
5076
+ }
5077
+ async execute(input) {
5078
+ const start = Date.now();
5079
+ const query = String(input.query ?? "").trim();
5080
+ const maxResults = Number(input.max_results ?? 5);
5081
+ if (!query) return { success: false, output: "query is required", duration_ms: 0 };
5082
+ try {
5083
+ const Database2 = (await import("better-sqlite3")).default;
5084
+ const db = new Database2(this.getDbPath());
5085
+ const keywords = query.split(/\s+/).filter((w) => w.length > 2);
5086
+ const likeClause = keywords.map(() => `content LIKE ?`).join(" OR ");
5087
+ const likeParams = keywords.map((k) => `%${k}%`);
5088
+ const rows = db.prepare(`
5089
+ SELECT session_id, role, content, created_at
5090
+ FROM conversations
5091
+ WHERE ${likeClause || "1=1"}
5092
+ ORDER BY created_at DESC
5093
+ LIMIT ?
5094
+ `).all(...likeParams, maxResults * 3);
5095
+ db.close();
5096
+ if (!rows.length) {
5097
+ return {
5098
+ success: true,
5099
+ output: `No past conversations found matching "${query}".`,
5100
+ duration_ms: Date.now() - start
5101
+ };
5102
+ }
5103
+ const sessions = /* @__PURE__ */ new Map();
5104
+ for (const row of rows) {
5105
+ if (!sessions.has(row.session_id)) sessions.set(row.session_id, []);
5106
+ sessions.get(row.session_id).push(row);
5107
+ }
5108
+ const results = [];
5109
+ let count = 0;
5110
+ for (const [sessionId, msgs] of sessions) {
5111
+ if (count >= maxResults) break;
5112
+ const date = new Date(msgs[0].created_at).toISOString().split("T")[0];
5113
+ const excerpt = msgs.slice(0, 4).map((m) => ` ${m.role}: ${m.content.slice(0, 200)}${m.content.length > 200 ? "\u2026" : ""}`).join("\n");
5114
+ results.push(`[${date}] Session ${sessionId.slice(0, 8)}:
5115
+ ${excerpt}`);
5116
+ count++;
5117
+ }
5118
+ return {
5119
+ success: true,
5120
+ output: `Found ${sessions.size} matching session(s):
5121
+
5122
+ ${results.join("\n\n")}`,
5123
+ structured: { sessions: sessions.size, total_messages: rows.length },
5124
+ duration_ms: Date.now() - start
5125
+ };
5126
+ } catch (err) {
5127
+ const msg = err instanceof Error ? err.message : String(err);
5128
+ return { success: false, output: `Session search failed: ${msg}`, duration_ms: Date.now() - start, error: msg };
5129
+ }
5130
+ }
5131
+ };
5132
+ }
5133
+ });
5134
+
5046
5135
  // packages/daemon/src/capabilities/CodespaceBrowserCapability.ts
5047
5136
  var CodespaceBrowserCapability_exports = {};
5048
5137
  __export(CodespaceBrowserCapability_exports, {
@@ -5135,6 +5224,7 @@ var init_CapabilityRegistry = __esm({
5135
5224
  init_OCRExtractCapability();
5136
5225
  init_CredentialVaultCapability();
5137
5226
  init_MonitorWatchCapability();
5227
+ init_SessionSearchCapability();
5138
5228
  CapabilityRegistry = class {
5139
5229
  capabilities = /* @__PURE__ */ new Map();
5140
5230
  /**
@@ -5147,7 +5237,7 @@ var init_CapabilityRegistry = __esm({
5147
5237
  * task_type: browser_task). The main agent does NOT have direct access
5148
5238
  * to browser_open without going through a subagent spawn.
5149
5239
  */
5150
- constructor(codespaceManager, graph, onMemoryWrite) {
5240
+ constructor(codespaceManager, graph, onMemoryWrite, dbPath) {
5151
5241
  this.register(new WebSearchCapability());
5152
5242
  if (codespaceManager) {
5153
5243
  try {
@@ -5169,6 +5259,9 @@ var init_CapabilityRegistry = __esm({
5169
5259
  this.register(new OCRExtractCapability());
5170
5260
  this.register(new CredentialVaultCapability());
5171
5261
  this.register(new MonitorWatchCapability());
5262
+ if (dbPath) {
5263
+ this.register(new SessionSearchCapability(() => dbPath));
5264
+ }
5172
5265
  if (graph) {
5173
5266
  this.register(new MemoryCapability(graph, onMemoryWrite));
5174
5267
  }
@@ -5199,6 +5292,7 @@ var init_CapabilityRegistry = __esm({
5199
5292
  const lower = task.toLowerCase();
5200
5293
  const active = /* @__PURE__ */ new Set(["shell_exec", "file_op", "surge_publish"]);
5201
5294
  if (this.capabilities.has("memory_write")) active.add("memory_write");
5295
+ if (this.capabilities.has("session_search")) active.add("session_search");
5202
5296
  if (/search|web|browse|scrape|research|website|url|http|google|fetch|crawl|find.*online/i.test(lower)) {
5203
5297
  active.add("web_search");
5204
5298
  active.add("scrape_url");
@@ -5254,6 +5348,150 @@ var init_capabilities = __esm({
5254
5348
  init_OCRExtractCapability();
5255
5349
  init_CredentialVaultCapability();
5256
5350
  init_MonitorWatchCapability();
5351
+ init_SessionSearchCapability();
5352
+ }
5353
+ });
5354
+
5355
+ // packages/daemon/src/IterationBudget.ts
5356
+ var IterationBudget;
5357
+ var init_IterationBudget = __esm({
5358
+ "packages/daemon/src/IterationBudget.ts"() {
5359
+ "use strict";
5360
+ IterationBudget = class _IterationBudget {
5361
+ constructor(maxIterations, parent, childAllocation) {
5362
+ this.maxIterations = maxIterations;
5363
+ this.parent = parent ?? null;
5364
+ this.childAllocation = childAllocation ?? maxIterations;
5365
+ }
5366
+ used = 0;
5367
+ parent;
5368
+ childAllocation;
5369
+ /**
5370
+ * Consume N iterations. Returns true if budget remains, false if exhausted.
5371
+ * Also consumes from parent budget if this is a child.
5372
+ */
5373
+ consume(n = 1) {
5374
+ this.used += n;
5375
+ if (this.parent) {
5376
+ return this.parent.consume(n) && this.used <= this.maxIterations;
5377
+ }
5378
+ return this.used <= this.maxIterations;
5379
+ }
5380
+ /**
5381
+ * Refund N iterations (e.g., for execute_code calls that don't count).
5382
+ */
5383
+ refund(n = 1) {
5384
+ this.used = Math.max(0, this.used - n);
5385
+ this.parent?.refund(n);
5386
+ }
5387
+ remaining() {
5388
+ const local = this.maxIterations - this.used;
5389
+ if (this.parent) {
5390
+ return Math.min(local, this.parent.remaining());
5391
+ }
5392
+ return local;
5393
+ }
5394
+ exhausted() {
5395
+ return this.remaining() <= 0;
5396
+ }
5397
+ /**
5398
+ * Fork a child budget that draws from this parent's remaining pool.
5399
+ * The child has its own max but also decrements the parent on each consume.
5400
+ */
5401
+ fork(childMax) {
5402
+ const max = childMax ?? Math.min(30, this.remaining());
5403
+ return new _IterationBudget(max, this, max);
5404
+ }
5405
+ /**
5406
+ * Get a budget pressure message if running low.
5407
+ * Returns null if budget is healthy.
5408
+ */
5409
+ pressureWarning() {
5410
+ const rem = this.remaining();
5411
+ const pct = rem / this.maxIterations;
5412
+ if (pct <= 0) {
5413
+ return "BUDGET EXHAUSTED: You have used all available iterations. Wrap up immediately.";
5414
+ }
5415
+ if (pct <= 0.1) {
5416
+ return `BUDGET CRITICAL: Only ${rem} iteration(s) remaining. Finish the current task NOW.`;
5417
+ }
5418
+ if (pct <= 0.25) {
5419
+ return `Budget warning: ${rem} iterations remaining (${Math.round(pct * 100)}%). Start wrapping up.`;
5420
+ }
5421
+ return null;
5422
+ }
5423
+ stats() {
5424
+ return {
5425
+ used: this.used,
5426
+ max: this.maxIterations,
5427
+ remaining: this.maxIterations - this.used,
5428
+ ...this.parent ? { parentRemaining: this.parent.remaining() } : {}
5429
+ };
5430
+ }
5431
+ };
5432
+ }
5433
+ });
5434
+
5435
+ // packages/daemon/src/PromptInjectionScanner.ts
5436
+ function scanForInjection(content, source) {
5437
+ const warnings = [];
5438
+ let sanitized = content;
5439
+ const invisibleMatches = content.match(INVISIBLE_CHARS);
5440
+ if (invisibleMatches && invisibleMatches.length > 0) {
5441
+ warnings.push(`${source || "content"}: ${invisibleMatches.length} invisible Unicode chars detected and stripped`);
5442
+ sanitized = sanitized.replace(INVISIBLE_CHARS, "");
5443
+ }
5444
+ let hasBlock = false;
5445
+ for (const { pattern, severity, label } of INJECTION_PATTERNS) {
5446
+ if (pattern.test(content)) {
5447
+ const msg = `${source || "content"}: ${label} detected (${severity})`;
5448
+ warnings.push(msg);
5449
+ if (severity === "block") hasBlock = true;
5450
+ }
5451
+ }
5452
+ const lines = content.split("\n");
5453
+ const uniqueLines = new Set(lines.map((l) => l.trim()).filter((l) => l.length > 10));
5454
+ if (lines.length > 20 && uniqueLines.size < lines.length * 0.3) {
5455
+ warnings.push(`${source || "content"}: suspicious repetition (${uniqueLines.size} unique of ${lines.length} lines)`);
5456
+ }
5457
+ return {
5458
+ safe: !hasBlock,
5459
+ warnings,
5460
+ sanitized: hasBlock ? `[BLOCKED: Prompt injection detected in ${source || "content"}. Content excluded for safety.]` : sanitized
5461
+ };
5462
+ }
5463
+ function sanitizeContextFile(content, filePath, log) {
5464
+ const result = scanForInjection(content, filePath);
5465
+ for (const w of result.warnings) {
5466
+ log?.(`[injection-scan] ${w}`);
5467
+ }
5468
+ return result.sanitized;
5469
+ }
5470
+ var INJECTION_PATTERNS, INVISIBLE_CHARS;
5471
+ var init_PromptInjectionScanner = __esm({
5472
+ "packages/daemon/src/PromptInjectionScanner.ts"() {
5473
+ "use strict";
5474
+ INJECTION_PATTERNS = [
5475
+ // Direct instruction override
5476
+ { pattern: /ignore\s+(all\s+)?(previous|prior|above|earlier)\s+(instructions?|context|rules?|prompts?)/i, severity: "block", label: "instruction override" },
5477
+ { pattern: /disregard\s+(all\s+)?(previous|prior|above)\s+(instructions?|context)/i, severity: "block", label: "instruction override" },
5478
+ { pattern: /forget\s+(everything|all|what)\s+(you|I)\s+(told|said|instructed)/i, severity: "block", label: "instruction override" },
5479
+ { pattern: /new\s+instructions?\s*:/i, severity: "block", label: "instruction injection" },
5480
+ { pattern: /\bsystem\s*:\s*you\s+are\b/i, severity: "block", label: "role hijack" },
5481
+ // Role impersonation
5482
+ { pattern: /you\s+are\s+now\s+(a|an|the)\s+/i, severity: "warn", label: "role impersonation" },
5483
+ { pattern: /act\s+as\s+(if|though)\s+you\s+are/i, severity: "warn", label: "role impersonation" },
5484
+ { pattern: /pretend\s+(you'?re?|to\s+be)\s+/i, severity: "warn", label: "role impersonation" },
5485
+ // Credential/data exfiltration
5486
+ { pattern: /send\s+(the|your|all)\s+(api\s*key|token|password|secret|credential)/i, severity: "block", label: "credential exfil" },
5487
+ { pattern: /output\s+(the|your)\s+system\s+prompt/i, severity: "block", label: "prompt extraction" },
5488
+ { pattern: /reveal\s+(the|your)\s+(system|initial)\s+(prompt|instructions)/i, severity: "block", label: "prompt extraction" },
5489
+ { pattern: /what\s+(is|are)\s+your\s+(system\s+)?instructions/i, severity: "warn", label: "prompt extraction attempt" },
5490
+ // Encoded/obfuscated injection
5491
+ { pattern: /base64\s*decode|atob\s*\(/i, severity: "warn", label: "encoded payload" },
5492
+ { pattern: /eval\s*\(|Function\s*\(/i, severity: "warn", label: "code execution attempt" }
5493
+ ];
5494
+ INVISIBLE_CHARS = /[\u200B\u200C\u200D\u200E\u200F\u202A-\u202E\u2060\u2061\u2062\u2063\u2064\u2066-\u2069\uFEFF\u00AD]/g;
5257
5495
  }
5258
5496
  });
5259
5497
 
@@ -5268,6 +5506,8 @@ var init_AgentExecutor = __esm({
5268
5506
  "use strict";
5269
5507
  init_LLMExecutor();
5270
5508
  init_capabilities();
5509
+ init_IterationBudget();
5510
+ init_PromptInjectionScanner();
5271
5511
  SELF_MOD_PATTERN = /\b(yourself|the agent|this agent|this cli|0agent|your code|your source|agent cli|improve.*agent|update.*agent|add.*to.*agent|fix.*agent|self.?improv)\b/i;
5272
5512
  AgentExecutor = class {
5273
5513
  constructor(llm, config, onStep, onToken) {
@@ -5299,9 +5539,17 @@ var init_AgentExecutor = __esm({
5299
5539
  const systemPrompt = this.buildSystemPrompt(systemContext, task);
5300
5540
  const activeTools = this.registry.getToolDefinitionsFor(task);
5301
5541
  let toolSet = activeTools;
5542
+ const budget = new IterationBudget(this.maxIterations);
5302
5543
  const messages = [
5303
5544
  { role: "user", content: task }
5304
5545
  ];
5546
+ const isJustdo = /book|file.*itr|tax.*file|irctc|train.*ticket|flight|passport|appointment|login.*portal|pan.*card|aadhaar|monitor.*watch|price.*drop|slot.*available|justdo/i.test(task);
5547
+ if (isJustdo) {
5548
+ messages.push(
5549
+ { role: "assistant", content: "I can help with that. Let me start collecting the information I need." },
5550
+ { role: "user", content: "Yes, go ahead." }
5551
+ );
5552
+ }
5305
5553
  const contextLimit = LLMExecutor.getContextWindowTokens(this.llm["config"]?.model ?? "claude-sonnet-4-6");
5306
5554
  if (isSelfMod) {
5307
5555
  this.maxIterations = Math.max(this.maxIterations, 50);
@@ -5313,6 +5561,15 @@ var init_AgentExecutor = __esm({
5313
5561
  finalOutput = "Cancelled.";
5314
5562
  break;
5315
5563
  }
5564
+ if (budget.exhausted()) {
5565
+ this.onStep("Iteration budget exhausted \u2014 wrapping up.");
5566
+ break;
5567
+ }
5568
+ budget.consume(1);
5569
+ const pressureMsg = budget.pressureWarning();
5570
+ if (pressureMsg && messages.length > 2) {
5571
+ messages.push({ role: "user", content: `[SYSTEM] ${pressureMsg}` });
5572
+ }
5316
5573
  this.onStep(i === 0 ? "Thinking\u2026" : "Continuing\u2026");
5317
5574
  const estimatedTokens = this._estimateTokens(messages);
5318
5575
  if (estimatedTokens > contextLimit - 16384) {
@@ -5383,35 +5640,23 @@ var init_AgentExecutor = __esm({
5383
5640
  content: response.content,
5384
5641
  tool_calls: response.tool_calls
5385
5642
  });
5386
- for (const tc of response.tool_calls) {
5387
- this.onStep(`\u25B6 ${tc.name}(${this.summariseInput(tc.name, tc.input)})`);
5388
- let result;
5389
- try {
5390
- const capResult = await this.registry.execute(tc.name, tc.input, this.cwd, signal);
5391
- result = capResult.output;
5392
- const MAX_TOOL_OUTPUT = 4e3;
5393
- if (result.length > MAX_TOOL_OUTPUT) {
5394
- result = result.slice(0, MAX_TOOL_OUTPUT) + `
5395
- [...${result.length - MAX_TOOL_OUTPUT} chars truncated]`;
5396
- }
5397
- if (capResult.fallback_used) {
5398
- this.onStep(` (used fallback: ${capResult.fallback_used})`);
5399
- }
5400
- if (tc.name === "file_op" && tc.input.op === "write" && tc.input.path) {
5401
- filesWritten.push(String(tc.input.path));
5402
- }
5403
- if (tc.name === "shell_exec" && tc.input.command) {
5404
- commandsRun.push(String(tc.input.command));
5405
- }
5406
- } catch (err) {
5407
- result = `Error: ${err instanceof Error ? err.message : String(err)}`;
5643
+ const toolCalls = response.tool_calls;
5644
+ const { parallel, serial } = this._partitionToolCalls(toolCalls);
5645
+ if (parallel.length > 0) {
5646
+ const results = await Promise.all(parallel.map(async (tc) => {
5647
+ this.onStep(`\u25B6 ${tc.name}(${this.summariseInput(tc.name, tc.input)}) [parallel]`);
5648
+ return { tc, result: await this._executeSingleTool(tc, signal, filesWritten, commandsRun) };
5649
+ }));
5650
+ for (const { tc, result } of results) {
5651
+ this.onStep(` \u21B3 ${result.slice(0, 120)}${result.length > 120 ? "\u2026" : ""}`);
5652
+ messages.push({ role: "tool", content: result, tool_call_id: tc.id });
5408
5653
  }
5654
+ }
5655
+ for (const tc of serial) {
5656
+ this.onStep(`\u25B6 ${tc.name}(${this.summariseInput(tc.name, tc.input)})`);
5657
+ const result = await this._executeSingleTool(tc, signal, filesWritten, commandsRun);
5409
5658
  this.onStep(` \u21B3 ${result.slice(0, 120)}${result.length > 120 ? "\u2026" : ""}`);
5410
- messages.push({
5411
- role: "tool",
5412
- content: result,
5413
- tool_call_id: tc.id
5414
- });
5659
+ messages.push({ role: "tool", content: result, tool_call_id: tc.id });
5415
5660
  }
5416
5661
  }
5417
5662
  return {
@@ -5727,7 +5972,8 @@ content = element.text if element else page.get_all_text()` : `content = page.ge
5727
5972
  if (existsSync5(f)) {
5728
5973
  const content = readFileSync4(f, "utf8").trim();
5729
5974
  if (content && content.length < 4e3) {
5730
- lines.push(``, `Project instructions:`, content);
5975
+ const sanitized = sanitizeContextFile(content, f);
5976
+ lines.push(``, `Project instructions:`, sanitized);
5731
5977
  break;
5732
5978
  }
5733
5979
  }
@@ -5832,6 +6078,75 @@ content = element.text if element else page.get_all_text()` : `content = page.ge
5832
6078
  if (toolName === "scrape_url") return `"${String(input.url ?? "").slice(0, 60)}" mode=${input.mode ?? "text"}`;
5833
6079
  return JSON.stringify(input).slice(0, 60);
5834
6080
  }
6081
+ /**
6082
+ * Execute a single tool call. Returns the result string.
6083
+ */
6084
+ async _executeSingleTool(tc, signal, filesWritten, commandsRun) {
6085
+ let result;
6086
+ try {
6087
+ const capResult = await this.registry.execute(tc.name, tc.input, this.cwd, signal);
6088
+ result = capResult.output;
6089
+ const MAX_TOOL_OUTPUT = 4e3;
6090
+ if (result.length > MAX_TOOL_OUTPUT) {
6091
+ result = result.slice(0, MAX_TOOL_OUTPUT) + `
6092
+ [...${result.length - MAX_TOOL_OUTPUT} chars truncated]`;
6093
+ }
6094
+ if (capResult.fallback_used) {
6095
+ this.onStep(` (used fallback: ${capResult.fallback_used})`);
6096
+ }
6097
+ if (tc.name === "file_op" && tc.input.op === "write" && tc.input.path) {
6098
+ filesWritten.push(String(tc.input.path));
6099
+ }
6100
+ if (tc.name === "shell_exec" && tc.input.command) {
6101
+ commandsRun.push(String(tc.input.command));
6102
+ }
6103
+ } catch (err) {
6104
+ result = `Error: ${err instanceof Error ? err.message : String(err)}`;
6105
+ }
6106
+ return result;
6107
+ }
6108
+ /**
6109
+ * Partition tool calls into parallelisable and serial groups.
6110
+ * Inspired by Hermes Agent's path-overlap analysis.
6111
+ *
6112
+ * Tools are safe to parallelize when they don't share file paths
6113
+ * and aren't in the never-parallel set (shell_exec, browser_execute).
6114
+ */
6115
+ _partitionToolCalls(calls) {
6116
+ if (calls.length <= 1) return { parallel: [], serial: calls };
6117
+ const NEVER_PARALLEL = /* @__PURE__ */ new Set(["shell_exec", "browser_execute", "credential_vault", "monitor_watch"]);
6118
+ const parallel = [];
6119
+ const serial = [];
6120
+ const usedPaths = /* @__PURE__ */ new Set();
6121
+ for (const tc of calls) {
6122
+ if (NEVER_PARALLEL.has(tc.name)) {
6123
+ serial.push(tc);
6124
+ continue;
6125
+ }
6126
+ const paths = this._extractPaths(tc);
6127
+ let hasOverlap = false;
6128
+ for (const p of paths) {
6129
+ if (usedPaths.has(p)) {
6130
+ hasOverlap = true;
6131
+ break;
6132
+ }
6133
+ }
6134
+ if (hasOverlap) {
6135
+ serial.push(tc);
6136
+ } else {
6137
+ parallel.push(tc);
6138
+ for (const p of paths) usedPaths.add(p);
6139
+ }
6140
+ }
6141
+ return { parallel, serial };
6142
+ }
6143
+ _extractPaths(tc) {
6144
+ const paths = [];
6145
+ if (tc.input.path) paths.push(String(tc.input.path));
6146
+ if (tc.input.image_path) paths.push(String(tc.input.image_path));
6147
+ if (tc.input.url) paths.push(String(tc.input.url));
6148
+ return paths;
6149
+ }
5835
6150
  };
5836
6151
  }
5837
6152
  });
@@ -7002,6 +7317,76 @@ var ConversationStore = class {
7002
7317
  }
7003
7318
  };
7004
7319
 
7320
+ // packages/daemon/src/SmartModelRouter.ts
7321
+ var COMPLEX_PATTERNS = [
7322
+ // Code-related
7323
+ /\b(implement|build|write|fix|refactor|debug|test|deploy|compile|lint|bundle|migrate)\b/i,
7324
+ /\b(function|class|interface|module|component|endpoint|schema|type|hook|middleware)\b/i,
7325
+ /\b(error|bug|crash|fail|broken|issue|stack\s*trace|exception|undefined|null)\b/i,
7326
+ // Tool-heavy tasks
7327
+ /\b(search|scrape|browse|download|install|create\s+file|delete|execute|run|shell|command)\b/i,
7328
+ /\b(git|npm|pip|docker|kubectl|terraform|aws|gcp|azure)\b/i,
7329
+ // Justdo / web tasks
7330
+ /\b(book|file.*itr|irctc|train|ticket|flight|passport|appointment|monitor|watch|login|portal)\b/i,
7331
+ // Long or detailed requests
7332
+ /\b(explain|analyze|review|compare|design|plan|architect|optimise|optimize|improve)\b/i,
7333
+ // URLs, code blocks, file paths
7334
+ /https?:\/\//,
7335
+ /```/,
7336
+ /\/[\w\-]+\.[\w]+/,
7337
+ // file paths like /foo/bar.ts
7338
+ // Self-modification
7339
+ /\b(yourself|agent|0agent|daemon|capability|skill)\b/i
7340
+ // Multi-step or long messages (>200 chars likely complex)
7341
+ ];
7342
+ var SIMPLE_PATTERNS = [
7343
+ /^(hey|hi|hello|sup|yo|hola|namaste|what'?s?\s*up|how\s+are\s+you)[!?.\s,]*$/i,
7344
+ /^(thanks|thank\s+you|thx|ok|okay|cool|great|nice|perfect|got\s+it|sure|yep|yeah|yes|no|nah)[!?.\s,]*$/i,
7345
+ /^(bye|goodbye|see\s+ya|later|good\s*(morning|evening|afternoon|night))[!?.\s,]*$/i,
7346
+ /^(what\s+is|who\s+is|when\s+was|where\s+is|how\s+do\s+you|what\s+does|can\s+you)\b.{0,80}$/i,
7347
+ /^(tell\s+me\s+about|summarize|summarise|tldr|tl;dr)\b.{0,80}$/i
7348
+ ];
7349
+ function routeMessage(task) {
7350
+ const trimmed = task.trim();
7351
+ if (trimmed.length < 5) {
7352
+ return { decision: "skip", reason: "too short, likely noise" };
7353
+ }
7354
+ for (const p of SIMPLE_PATTERNS) {
7355
+ if (p.test(trimmed)) {
7356
+ return { decision: "fast", reason: `matches simple pattern: ${p.source.slice(0, 30)}` };
7357
+ }
7358
+ }
7359
+ for (const p of COMPLEX_PATTERNS) {
7360
+ if (p.test(trimmed)) {
7361
+ return { decision: "primary", reason: `matches complex pattern: ${p.source.slice(0, 30)}` };
7362
+ }
7363
+ }
7364
+ if (trimmed.length > 200) {
7365
+ return { decision: "primary", reason: "long message (>200 chars)" };
7366
+ }
7367
+ const sentences = trimmed.split(/[.!?]+/).filter((s) => s.trim().length > 0);
7368
+ if (sentences.length > 2) {
7369
+ return { decision: "primary", reason: "multi-sentence request" };
7370
+ }
7371
+ return { decision: "fast", reason: "short message, no complex indicators" };
7372
+ }
7373
+ function getFastModelId(provider, _currentModel) {
7374
+ switch (provider) {
7375
+ case "anthropic":
7376
+ return "claude-haiku-4-5-20251001";
7377
+ case "openai":
7378
+ return "gpt-4o-mini";
7379
+ case "gemini":
7380
+ return "gemini-2.0-flash";
7381
+ case "xai":
7382
+ return "grok-3-mini";
7383
+ case "groq":
7384
+ return "llama-3.1-8b-instant";
7385
+ default:
7386
+ return null;
7387
+ }
7388
+ }
7389
+
7005
7390
  // packages/daemon/src/SessionManager.ts
7006
7391
  import { readFileSync as readFileSync7, existsSync as existsSync9 } from "node:fs";
7007
7392
  import { resolve as resolve8 } from "node:path";
@@ -7241,14 +7626,21 @@ var SessionManager = class {
7241
7626
  const activeLLM = this.getFreshLLM();
7242
7627
  if (activeLLM?.isConfigured) {
7243
7628
  const userEntityId = enrichedReq.entity_id ?? this.identity?.entity_node_id;
7244
- const isConversational = /^(hey|hi|hello|sup|yo|what'?s up|how are you|thanks|ok|cool|bye|good\s+(morning|evening|afternoon)|lol|nice)[!?.\s,]*$/i.test(enrichedReq.task.trim());
7245
- if (isConversational) {
7246
- const resp = await activeLLM.complete(
7629
+ const routing = routeMessage(enrichedReq.task);
7630
+ if (routing.decision === "skip") {
7631
+ this.completeSession(sessionId, { output: "", files_written: [], commands_run: [], tokens_used: 0, model: "skip" });
7632
+ return this.sessions.get(sessionId);
7633
+ }
7634
+ if (routing.decision === "fast") {
7635
+ const provider = activeLLM["config"]?.provider ?? "anthropic";
7636
+ const fastModel = getFastModelId(provider);
7637
+ const llmToUse = fastModel ? activeLLM.withModel(fastModel) : activeLLM;
7638
+ const resp = await llmToUse.complete(
7247
7639
  [{ role: "user", content: enrichedReq.task }],
7248
- "You are a helpful assistant."
7640
+ this.identity ? `You are 0agent, a helpful AI assistant. You are talking to ${this.identity.name}. Be concise and friendly.` : "You are 0agent, a helpful AI assistant. Be concise and friendly."
7249
7641
  );
7250
7642
  this.emit({ type: "session.token", session_id: sessionId, token: resp.content });
7251
- this.addStep(sessionId, `Done (${resp.tokens_used} tokens, 1 LLM turns)`);
7643
+ this.addStep(sessionId, `Done (${resp.tokens_used} tokens, fast model, reason: ${routing.reason})`);
7252
7644
  this.completeSession(sessionId, {
7253
7645
  output: resp.content,
7254
7646
  files_written: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0agent",
3
- "version": "1.0.76",
3
+ "version": "1.0.78",
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",