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.
- package/package.json +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +4369 -4361
- package/server/services/ai/compaction.js +4 -4
- package/server/services/ai/history.js +2 -2
- package/server/services/ai/settings.js +4 -4
- package/server/services/ai/systemPrompt.js +4 -2
- package/server/services/ai/toolResult.js +26 -0
- package/server/services/commands/router.js +1 -1
- package/server/services/memory/manager.js +55 -6
|
@@ -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 <
|
|
6
|
+
if (nonSystem.length < 24) return messages;
|
|
7
7
|
|
|
8
|
-
const keepRecent =
|
|
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:
|
|
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.
|
|
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
|
-
const MAX_SUMMARY_CHARS =
|
|
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:
|
|
83
|
-
tool_replay_budget_chars:
|
|
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(
|
|
199
|
-
settings.tool_replay_budget_chars = Math.max(
|
|
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,
|
|
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,
|
|
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
|
|
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 (
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
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
|
}
|