neoagent 2.1.17-beta.21 → 2.1.17-beta.23

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.
@@ -3,9 +3,9 @@ async function compact(messages, provider, model, contextWindow = null) {
3
3
  const nonSystem = messages.filter(m => m.role !== 'system');
4
4
  const beforeTokens = estimateTokenCount(messages);
5
5
 
6
- if (nonSystem.length < 12) return messages;
6
+ if (nonSystem.length < 24) return messages;
7
7
 
8
- const keepRecent = 10;
8
+ const keepRecent = 24;
9
9
  const toCompact = nonSystem.slice(0, -keepRecent);
10
10
  const recent = nonSystem.slice(-keepRecent);
11
11
 
@@ -26,7 +26,7 @@ async function compact(messages, provider, model, contextWindow = null) {
26
26
  ];
27
27
 
28
28
  try {
29
- const response = await provider.chat(summaryPrompt, [], { model, maxTokens: 900 });
29
+ const response = await provider.chat(summaryPrompt, [], { model, maxTokens: 1600 });
30
30
  const summary = response.content || 'Previous conversation context (summary unavailable).';
31
31
 
32
32
  const compactedMessages = [];
@@ -72,7 +72,7 @@ function estimateTokenCount(messages) {
72
72
 
73
73
  function shouldCompact(messages, contextWindow) {
74
74
  const used = estimateTokenCount(messages);
75
- return used > contextWindow * 0.85;
75
+ return used > contextWindow * 0.92;
76
76
  }
77
77
 
78
78
  module.exports = { compact, estimateTokenCount, shouldCompact };
@@ -2,8 +2,8 @@ const db = require('../../db/database');
2
2
 
3
3
  const WEB_SUMMARY_KEY = 'web_chat_summary';
4
4
  const WEB_SUMMARY_COUNT_KEY = 'web_chat_summary_count';
5
- const SUMMARY_TRIGGER_COUNT = 6;
6
- const MAX_SUMMARY_CHARS = 3200;
5
+ const SUMMARY_TRIGGER_COUNT = 12;
6
+ const MAX_SUMMARY_CHARS = 6000;
7
7
 
8
8
  function clampSummary(text) {
9
9
  const str = String(text || '').trim();
@@ -79,8 +79,8 @@ function createDefaultProviderConfigs() {
79
79
  function createDefaultAiSettings() {
80
80
  return {
81
81
  cost_mode: 'balanced_auto',
82
- chat_history_window: 8,
83
- tool_replay_budget_chars: 1800,
82
+ chat_history_window: 20,
83
+ tool_replay_budget_chars: 6000,
84
84
  subagent_max_iterations: 6,
85
85
  assistant_behavior_notes: '',
86
86
  auto_skill_learning: false,
@@ -195,8 +195,8 @@ function getAiSettings(userId) {
195
195
  settings[row.key] = parseSettingValue(row.value);
196
196
  }
197
197
 
198
- settings.chat_history_window = Math.max(4, Math.min(Number(settings.chat_history_window) || DEFAULT_AI_SETTINGS.chat_history_window, 12));
199
- settings.tool_replay_budget_chars = Math.max(600, Math.min(Number(settings.tool_replay_budget_chars) || DEFAULT_AI_SETTINGS.tool_replay_budget_chars, 3000));
198
+ settings.chat_history_window = Math.max(6, Math.min(Number(settings.chat_history_window) || DEFAULT_AI_SETTINGS.chat_history_window, 40));
199
+ settings.tool_replay_budget_chars = Math.max(1200, Math.min(Number(settings.tool_replay_budget_chars) || DEFAULT_AI_SETTINGS.tool_replay_budget_chars, 12000));
200
200
  settings.subagent_max_iterations = Math.max(2, Math.min(Number(settings.subagent_max_iterations) || DEFAULT_AI_SETTINGS.subagent_max_iterations, 12));
201
201
  settings.cost_mode = typeof settings.cost_mode === 'string' ? settings.cost_mode : DEFAULT_AI_SETTINGS.cost_mode;
202
202
  settings.assistant_behavior_notes = typeof settings.assistant_behavior_notes === 'string'
@@ -61,6 +61,8 @@ When prior context makes the goal clear, act on it. Only ask a clarifying questi
61
61
  REPORT ACTUAL RESULTS
62
62
  When a tool returns data, share the relevant parts — summarized if large, direct if short. Never paste raw JSON as the answer. Never narrate what you're about to do at length before doing it.
63
63
  Never promise an action in the final answer unless you already took that action in this run. Do not say "I'll check", "I'll fix it", or "I'll send it" and then stop. Either do it first or say you have not done it yet.
64
+ For scheduler or task-config changes, never claim that a cron job was created, updated, deleted, enabled, disabled, or “fixed” unless the corresponding scheduler tool call succeeded in this run. If you did not verify the actual task config, say that clearly instead of guessing.
65
+ If the user asks you to debug scheduler timing or frequency, inspect the current scheduled-task list first and separate three things clearly: what you observed, what you infer, and what you actually changed.
64
66
 
65
67
  RELIABILITY
66
68
  If a claim depends on current external facts, status, timelines, or ambiguous relative dates, verify it with fresh evidence before stating it as fact. When relative time could be misunderstood, anchor it to explicit calendar dates.
@@ -164,13 +166,13 @@ async function buildSystemPrompt(userId, context = {}, memoryManager) {
164
166
  }
165
167
 
166
168
  const memCtx = await memoryManager.buildContext(userId);
167
- const compactMemory = clampSection(memCtx, 1800);
169
+ const compactMemory = clampSection(memCtx, 3200);
168
170
  if (compactMemory) {
169
171
  base.push(compactMemory);
170
172
  }
171
173
 
172
174
  if (context.additionalContext) {
173
- base.push(`Additional context:\n${clampSection(context.additionalContext, 900)}`);
175
+ base.push(`Additional context:\n${clampSection(context.additionalContext, 1800)}`);
174
176
  }
175
177
 
176
178
  const prompt = base.filter(Boolean).join('\n\n');
@@ -132,6 +132,28 @@ function compactToolResult(toolName, toolArgs = {}, toolResult, options = {}) {
132
132
  });
133
133
  break;
134
134
 
135
+ case 'list_scheduled_tasks':
136
+ envelope = trimObject({
137
+ tool: toolName,
138
+ status: toolResult?.success === false || toolResult?.error ? 'error' : 'ok',
139
+ message: clampText(toolResult?.message || toolResult?.error || '', Math.floor(softLimit * 0.3)),
140
+ count: typeof toolResult?.count === 'number'
141
+ ? toolResult.count
142
+ : (Array.isArray(toolResult?.tasks) ? toolResult.tasks.length : undefined),
143
+ tasks: Array.isArray(toolResult?.tasks)
144
+ ? toolResult.tasks.slice(0, 8).map((task) => trimObject({
145
+ id: task?.id,
146
+ name: task?.name,
147
+ cronExpression: task?.cronExpression,
148
+ ...(task?.oneTime ? { runAt: task?.runAt } : {}),
149
+ oneTime: task?.oneTime,
150
+ enabled: task?.enabled,
151
+ ...(task?.model ? { model: task.model } : {})
152
+ }))
153
+ : undefined
154
+ });
155
+ break;
156
+
135
157
  case 'send_message':
136
158
  case 'make_call':
137
159
  case 'memory_save':
@@ -139,6 +161,10 @@ function compactToolResult(toolName, toolArgs = {}, toolResult, options = {}) {
139
161
  case 'memory_update_core':
140
162
  case 'memory_read':
141
163
  case 'memory_write':
164
+ case 'create_scheduled_task':
165
+ case 'schedule_run':
166
+ case 'delete_scheduled_task':
167
+ case 'update_scheduled_task':
142
168
  envelope = trimObject({
143
169
  tool: toolName,
144
170
  status: toolResult?.success === false || toolResult?.error ? 'error' : 'ok',
@@ -270,7 +270,7 @@ class CommandRouter {
270
270
 
271
271
  if (mode === 'scheduled') {
272
272
  const scheduled = db
273
- .prepare('SELECT id, name, enabled, cron_expression, run_at, one_time, last_run FROM scheduled_tasks WHERE user_id = ? ORDER BY created_at DESC LIMIT 12')
273
+ .prepare('SELECT id, name, enabled, cron_expression, run_at, one_time, last_run FROM scheduled_tasks WHERE user_id = ? ORDER BY created_at DESC')
274
274
  .all(userId);
275
275
  if (!scheduled.length) {
276
276
  return { handled: true, content: '**Tasks**\n- No scheduled tasks found.' };
@@ -80,6 +80,21 @@ function buildExcerpt(text, query) {
80
80
  return `${prefix}${raw.slice(start, end)}${suffix}`;
81
81
  }
82
82
 
83
+ function tokenizeRecallQuery(query) {
84
+ return (String(query || '').toLowerCase().match(/[\p{L}\p{N}_-]{3,}/gu) || [])
85
+ .slice(0, 12);
86
+ }
87
+
88
+ function scoreSchedulerRunMatch(queryTokens, title, finalResponse) {
89
+ if (!queryTokens.length) return 0;
90
+ const haystack = `${String(title || '')} ${String(finalResponse || '')}`.toLowerCase();
91
+ let score = 0;
92
+ for (const token of queryTokens) {
93
+ if (haystack.includes(token)) score += 1;
94
+ }
95
+ return score;
96
+ }
97
+
83
98
  class MemoryManager {
84
99
  constructor() {
85
100
  this._ensureDirs();
@@ -809,13 +824,47 @@ class MemoryManager {
809
824
  async buildRecallMessage(userId, query) {
810
825
  if (!userId || !query || !query.trim()) return null;
811
826
  try {
827
+ const sections = [];
812
828
  const recalled = await this.recallMemory(userId, query, 5);
813
- if (!recalled.length) return null;
814
- const lines = recalled.map(m => {
815
- const badge = m.category !== 'episodic' ? ` [${m.category}]` : '';
816
- return `- ${m.content}${badge}`;
817
- });
818
- return `[Recalled memory — relevant background for the current message]\n${lines.join('\n')}`;
829
+ if (recalled.length) {
830
+ const memoryLines = recalled.map(m => {
831
+ const badge = m.category !== 'episodic' ? ` [${m.category}]` : '';
832
+ return `- ${m.content}${badge}`;
833
+ });
834
+ sections.push(`Relevant memory:\n${memoryLines.join('\n')}`);
835
+ }
836
+
837
+ const queryTokens = tokenizeRecallQuery(query);
838
+ if (queryTokens.length) {
839
+ const recentSchedulerRuns = db.prepare(
840
+ `SELECT title, final_response, completed_at
841
+ FROM agent_runs
842
+ WHERE user_id = ? AND trigger_source = 'scheduler' AND status = 'completed'
843
+ ORDER BY completed_at DESC, created_at DESC
844
+ LIMIT 12`
845
+ ).all(userId);
846
+
847
+ const schedulerMatches = recentSchedulerRuns
848
+ .map((run) => ({
849
+ ...run,
850
+ score: scoreSchedulerRunMatch(queryTokens, run.title, run.final_response),
851
+ }))
852
+ .filter((run) => run.score > 0)
853
+ .slice(0, 3);
854
+
855
+ if (schedulerMatches.length) {
856
+ const schedulerLines = schedulerMatches.map((run) => {
857
+ const when = run.completed_at ? String(run.completed_at) : 'unknown time';
858
+ const title = String(run.title || 'scheduler task').replace(/\s+/g, ' ').trim();
859
+ const outcome = buildExcerpt(String(run.final_response || ''), query) || String(run.final_response || '').slice(0, 180);
860
+ return `- ${when}: ${title} -> ${outcome || '(no final response stored)'}`;
861
+ });
862
+ sections.push(`Relevant recent scheduler runs:\n${schedulerLines.join('\n')}`);
863
+ }
864
+ }
865
+
866
+ if (!sections.length) return null;
867
+ return `[Recalled context — relevant background for the current message]\n${sections.join('\n\n')}`;
819
868
  } catch {
820
869
  return null;
821
870
  }