neoagent 2.4.2-beta.4 → 2.4.2-beta.6
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/.env.example +0 -9
- package/README.md +48 -18
- package/docs/configuration.md +0 -10
- package/flutter_app/lib/main.dart +0 -1
- package/flutter_app/lib/main_controller.dart +0 -160
- package/flutter_app/lib/main_models.dart +28 -0
- package/flutter_app/lib/main_operations.dart +17 -1
- package/flutter_app/lib/main_runtime.dart +0 -22
- package/flutter_app/lib/main_settings.dart +9 -0
- package/flutter_app/lib/main_shared.dart +0 -161
- package/flutter_app/lib/src/backend_client.dart +8 -0
- package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +0 -2
- package/flutter_app/pubspec.lock +0 -8
- package/flutter_app/pubspec.yaml +0 -1
- package/package.json +1 -1
- package/server/admin/admin.css +23 -0
- package/server/admin/admin.js +17 -19
- package/server/admin/analytics.js +254 -48
- package/server/admin/index.html +9 -2
- package/server/db/database.js +112 -0
- package/server/http/routes.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/AssetManifest.bin +1 -1
- package/server/public/assets/AssetManifest.bin.json +1 -1
- package/server/public/assets/NOTICES +0 -183
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +77723 -78469
- package/server/routes/admin.js +73 -24
- package/server/routes/mcp.js +9 -0
- package/server/routes/memory.js +35 -2
- package/server/routes/runtime.js +5 -2
- package/server/routes/settings.js +34 -1
- package/server/routes/skills.js +42 -0
- package/server/routes/task_webhooks.js +65 -0
- package/server/services/ai/engine.js +525 -24
- package/server/services/ai/learning.js +56 -8
- package/server/services/ai/models.js +4 -4
- package/server/services/ai/providers/anthropic.js +40 -8
- package/server/services/ai/providers/google.js +10 -0
- package/server/services/ai/providers/openai.js +3 -15
- package/server/services/ai/providers/openaiCompatible.js +11 -0
- package/server/services/ai/repetitionGuard.js +60 -0
- package/server/services/ai/systemPrompt.js +52 -16
- package/server/services/ai/taskAnalysis.js +15 -6
- package/server/services/ai/toolEvidence.js +3 -1
- package/server/services/ai/toolRunner.js +43 -1
- package/server/services/ai/toolSelector.js +92 -46
- package/server/services/ai/tools.js +89 -9
- package/server/services/ai/usage.js +114 -0
- package/server/services/manager.js +34 -0
- package/server/services/memory/manager.js +170 -4
- package/server/services/security/capability_audit.js +107 -0
- package/server/services/tasks/adapters/index.js +1 -0
- package/server/services/tasks/adapters/webhook.js +14 -0
- package/server/services/tasks/runtime.js +1 -1
- package/server/services/tasks/webhooks.js +146 -0
- package/server/services/workspace/code_navigation.js +194 -0
- package/server/services/workspace/structured_data.js +93 -0
- package/server/utils/cloud-security.js +24 -2
- package/flutter_app/lib/src/analytics_service.dart +0 -294
- package/server/config/analytics.js +0 -30
|
@@ -3,26 +3,40 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Tool selection strategy:
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* overhead is a fixed ~100 tokens/tool and the model always knows every tool
|
|
9
|
-
* that exists.
|
|
10
|
-
*
|
|
11
|
-
* MCP tools: user-defined and potentially numerous. Include all when the set
|
|
12
|
-
* is small; keyword-filter when the registry grows large.
|
|
6
|
+
* Every tool stays visible in the catalog. Only full JSON schemas are limited
|
|
7
|
+
* per model turn because several providers enforce schema-count limits.
|
|
13
8
|
*/
|
|
14
9
|
|
|
15
|
-
const
|
|
16
|
-
const MAX_TOOLS = 128; // Strict provider limit (e.g. Github Copilot / OpenAI)
|
|
10
|
+
const MAX_TOOLS = 20;
|
|
17
11
|
const ALWAYS_INCLUDE_BUILT_INS = [
|
|
12
|
+
'task_complete',
|
|
13
|
+
'activate_tools',
|
|
14
|
+
'think',
|
|
18
15
|
'send_message',
|
|
19
|
-
'
|
|
20
|
-
'list_tasks',
|
|
21
|
-
'delete_task',
|
|
22
|
-
'update_task',
|
|
16
|
+
'send_interim_update',
|
|
23
17
|
];
|
|
24
18
|
|
|
19
|
+
function compactDescription(value, maxChars = 180) {
|
|
20
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
21
|
+
if (text.length <= maxChars) return text;
|
|
22
|
+
return `${text.slice(0, maxChars - 3).trimEnd()}...`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildToolCatalog(tools = []) {
|
|
26
|
+
return tools
|
|
27
|
+
.filter((tool) => String(tool?.name || '').trim())
|
|
28
|
+
.map((tool) => ({
|
|
29
|
+
name: String(tool.name).trim(),
|
|
30
|
+
description: compactDescription(tool.description),
|
|
31
|
+
source: tool.serverId ? `mcp:${tool.serverId}` : 'built-in',
|
|
32
|
+
}))
|
|
33
|
+
.sort((left, right) => left.name.localeCompare(right.name))
|
|
34
|
+
.map((tool) => `${tool.name} | ${tool.source} | ${tool.description || 'No description supplied.'}`)
|
|
35
|
+
.join('\n');
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
function ensureRequiredTools(selectedTools = [], builtInTools = [], options = {}) {
|
|
39
|
+
const limit = Number(options.maxTools) || MAX_TOOLS;
|
|
26
40
|
const requiredNames = [...ALWAYS_INCLUDE_BUILT_INS];
|
|
27
41
|
if (options.widgetId) requiredNames.push('save_widget_snapshot');
|
|
28
42
|
if (!requiredNames.length) return selectedTools;
|
|
@@ -34,7 +48,7 @@ function ensureRequiredTools(selectedTools = [], builtInTools = [], options = {}
|
|
|
34
48
|
const required = builtInTools.find((tool) => tool?.name === toolName);
|
|
35
49
|
if (!required) continue;
|
|
36
50
|
|
|
37
|
-
if (selected.length <
|
|
51
|
+
if (selected.length < limit) {
|
|
38
52
|
selected.push(required);
|
|
39
53
|
continue;
|
|
40
54
|
}
|
|
@@ -57,43 +71,75 @@ function ensureRequiredTools(selectedTools = [], builtInTools = [], options = {}
|
|
|
57
71
|
return selected;
|
|
58
72
|
}
|
|
59
73
|
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
function selectInitialTools(allTools = [], suggestedNames = [], options = {}) {
|
|
75
|
+
const requested = new Set(
|
|
76
|
+
(Array.isArray(suggestedNames) ? suggestedNames : [])
|
|
77
|
+
.map((name) => String(name || '').trim())
|
|
78
|
+
.filter(Boolean),
|
|
79
|
+
);
|
|
80
|
+
const selected = allTools.filter((tool) => requested.has(tool?.name));
|
|
81
|
+
return ensureRequiredTools(selected.slice(0, MAX_TOOLS), allTools, options).slice(0, MAX_TOOLS);
|
|
82
|
+
}
|
|
63
83
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
84
|
+
function activateTools(currentTools = [], allTools = [], requestedNames = [], options = {}) {
|
|
85
|
+
const knownByName = new Map(allTools.map((tool) => [tool?.name, tool]));
|
|
86
|
+
let next = ensureRequiredTools(currentTools, allTools, options);
|
|
87
|
+
const activated = [];
|
|
88
|
+
const evicted = [];
|
|
89
|
+
const unknown = [];
|
|
90
|
+
const notActivated = [];
|
|
91
|
+
const requested = [...new Set(
|
|
92
|
+
(Array.isArray(requestedNames) ? requestedNames : [])
|
|
93
|
+
.map((rawName) => String(rawName || '').trim())
|
|
94
|
+
.filter(Boolean),
|
|
95
|
+
)];
|
|
96
|
+
for (const name of requested) {
|
|
97
|
+
const tool = knownByName.get(name);
|
|
98
|
+
if (!tool) {
|
|
99
|
+
unknown.push(name);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (next.some((item) => item?.name === name)) continue;
|
|
103
|
+
if (next.length >= MAX_TOOLS) {
|
|
104
|
+
const replaceIndex = next.findIndex((item) => (
|
|
105
|
+
!ALWAYS_INCLUDE_BUILT_INS.includes(item?.name)
|
|
106
|
+
&& !requested.includes(item?.name)
|
|
107
|
+
));
|
|
108
|
+
if (replaceIndex === -1) {
|
|
109
|
+
notActivated.push(name);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
evicted.push(next[replaceIndex].name);
|
|
113
|
+
next.splice(replaceIndex, 1);
|
|
114
|
+
}
|
|
115
|
+
next.push(tool);
|
|
116
|
+
activated.push(name);
|
|
117
|
+
}
|
|
118
|
+
next = ensureRequiredTools(next, allTools, options).slice(0, MAX_TOOLS);
|
|
119
|
+
return {
|
|
120
|
+
tools: next,
|
|
121
|
+
activated,
|
|
122
|
+
evicted,
|
|
123
|
+
unknown,
|
|
124
|
+
notActivated,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
68
127
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const name = String(tool.name || '').toLowerCase();
|
|
72
|
-
const original = String(tool.originalName || '').toLowerCase();
|
|
73
|
-
const server = String(tool.serverId || '').toLowerCase();
|
|
74
|
-
return normalized.includes(name) || normalized.includes(original) || (server && normalized.includes(server));
|
|
75
|
-
});
|
|
128
|
+
function selectMcpTools(_task, mcpTools = []) {
|
|
129
|
+
return Array.isArray(mcpTools) ? mcpTools : [];
|
|
76
130
|
}
|
|
77
131
|
|
|
78
132
|
function selectToolsForTask(task, builtInTools = [], mcpTools = [], _options = {}) {
|
|
79
133
|
const selectedMcp = selectMcpTools(task, mcpTools);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (builtInTools.length + selectedMcp.length <= MAX_TOOLS) {
|
|
84
|
-
selected = [...builtInTools, ...selectedMcp];
|
|
85
|
-
return ensureRequiredTools(selected, builtInTools, options);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// If we exceed the limit, prioritize base tools and take as many MCP tools as fit
|
|
89
|
-
const remainingSpace = MAX_TOOLS - builtInTools.length;
|
|
90
|
-
if (remainingSpace > 0) {
|
|
91
|
-
selected = [...builtInTools, ...selectedMcp.slice(0, remainingSpace)];
|
|
92
|
-
return ensureRequiredTools(selected, builtInTools, options);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
selected = builtInTools.slice(0, MAX_TOOLS);
|
|
96
|
-
return ensureRequiredTools(selected, builtInTools, options);
|
|
134
|
+
void _options;
|
|
135
|
+
return [...builtInTools, ...selectedMcp];
|
|
97
136
|
}
|
|
98
137
|
|
|
99
|
-
module.exports = {
|
|
138
|
+
module.exports = {
|
|
139
|
+
MAX_TOOLS,
|
|
140
|
+
activateTools,
|
|
141
|
+
buildToolCatalog,
|
|
142
|
+
selectInitialTools,
|
|
143
|
+
selectToolsForTask,
|
|
144
|
+
selectMcpTools,
|
|
145
|
+
};
|
|
@@ -73,7 +73,7 @@ function compactToolDefinition(tool, options = {}) {
|
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
if (options.includeDescriptions) {
|
|
76
|
-
compact.description = compactText(tool.description,
|
|
76
|
+
compact.description = compactText(tool.description, 320);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
if (tool.parameters?.properties) {
|
|
@@ -81,7 +81,7 @@ function compactToolDefinition(tool, options = {}) {
|
|
|
81
81
|
for (const [key, value] of Object.entries(tool.parameters.properties)) {
|
|
82
82
|
properties[key] = { ...value };
|
|
83
83
|
if (options.includeDescriptions && value.description) {
|
|
84
|
-
properties[key].description = compactText(value.description,
|
|
84
|
+
properties[key].description = compactText(value.description, 160);
|
|
85
85
|
} else {
|
|
86
86
|
delete properties[key].description;
|
|
87
87
|
}
|
|
@@ -786,9 +786,10 @@ function getAvailableTools(app, options = {}) {
|
|
|
786
786
|
type: 'object',
|
|
787
787
|
properties: {
|
|
788
788
|
key: { type: 'string', enum: ['user_profile', 'preferences', 'ai_personality'], description: 'user_profile: who the user is, preferences: standing likes/dislikes, ai_personality: concise durable notes for how the agent should behave for this user' },
|
|
789
|
-
value: { type: 'string', description: 'Value to set. Keep it concise — this is injected into every single prompt.' }
|
|
789
|
+
value: { type: 'string', description: 'Value to set. Keep it concise — this is injected into every single prompt.' },
|
|
790
|
+
confirmed: { type: 'boolean', description: 'Must be true only when the user explicitly requested this core-memory change in the current conversation.' }
|
|
790
791
|
},
|
|
791
|
-
required: ['key', 'value']
|
|
792
|
+
required: ['key', 'value', 'confirmed']
|
|
792
793
|
}
|
|
793
794
|
},
|
|
794
795
|
{
|
|
@@ -919,6 +920,37 @@ function getAvailableTools(app, options = {}) {
|
|
|
919
920
|
required: ['path', 'query']
|
|
920
921
|
}
|
|
921
922
|
},
|
|
923
|
+
{
|
|
924
|
+
name: 'code_navigate',
|
|
925
|
+
description: 'Navigate source code with ranked lexical matches, AST-derived JavaScript/TypeScript symbols, or workspace-scoped semantic retrieval. Returns excerpts and line references instead of complete files.',
|
|
926
|
+
parameters: {
|
|
927
|
+
type: 'object',
|
|
928
|
+
properties: {
|
|
929
|
+
mode: { type: 'string', enum: ['lexical', 'structure', 'semantic'], description: 'Search mode.' },
|
|
930
|
+
path: { type: 'string', description: 'Workspace file or directory.' },
|
|
931
|
+
query: { type: 'string', description: 'Required for lexical and semantic modes.' },
|
|
932
|
+
include: { type: 'string', description: 'Optional ripgrep glob for lexical mode.' },
|
|
933
|
+
limit: { type: 'number', description: 'Maximum semantic results.' }
|
|
934
|
+
},
|
|
935
|
+
required: ['mode', 'path']
|
|
936
|
+
}
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
name: 'query_structured_data',
|
|
940
|
+
description: 'Read workspace CSV, TSV, JSON, or SQLite data using format-aware parsers. SQLite statements must be read-only and parameters are bound separately.',
|
|
941
|
+
parameters: {
|
|
942
|
+
type: 'object',
|
|
943
|
+
properties: {
|
|
944
|
+
path: { type: 'string', description: 'Workspace data file.' },
|
|
945
|
+
sql: { type: 'string', description: 'Read-only SQLite query.' },
|
|
946
|
+
parameters: { type: 'object', description: 'Named SQLite query parameters.' },
|
|
947
|
+
columns: { type: 'array', items: { type: 'string' }, description: 'Columns to return for CSV, TSV, or JSON.' },
|
|
948
|
+
equals: { type: 'object', description: 'Exact field filters for CSV, TSV, or JSON.' },
|
|
949
|
+
limit: { type: 'number', description: 'Maximum rows, capped at 1000.' }
|
|
950
|
+
},
|
|
951
|
+
required: ['path']
|
|
952
|
+
}
|
|
953
|
+
},
|
|
922
954
|
{
|
|
923
955
|
name: 'http_request',
|
|
924
956
|
description: 'Make an HTTP request to any URL',
|
|
@@ -989,6 +1021,21 @@ function getAvailableTools(app, options = {}) {
|
|
|
989
1021
|
required: ['thought']
|
|
990
1022
|
}
|
|
991
1023
|
},
|
|
1024
|
+
{
|
|
1025
|
+
name: 'activate_tools',
|
|
1026
|
+
description: 'Activate tools by exact catalog name for later model turns. When the schema limit is full, unrelated active schemas are replaced; every catalog tool remains available for later activation.',
|
|
1027
|
+
parameters: {
|
|
1028
|
+
type: 'object',
|
|
1029
|
+
properties: {
|
|
1030
|
+
names: {
|
|
1031
|
+
type: 'array',
|
|
1032
|
+
items: { type: 'string' },
|
|
1033
|
+
description: 'Exact tool names from the available tool catalog.'
|
|
1034
|
+
}
|
|
1035
|
+
},
|
|
1036
|
+
required: ['names']
|
|
1037
|
+
}
|
|
1038
|
+
},
|
|
992
1039
|
{
|
|
993
1040
|
name: 'spawn_subagent',
|
|
994
1041
|
description: 'Spawn an independent sub-agent asynchronously. Returns a handle immediately so the parent run can continue, list helpers, wait later, or cancel if plans change.',
|
|
@@ -997,7 +1044,9 @@ function getAvailableTools(app, options = {}) {
|
|
|
997
1044
|
properties: {
|
|
998
1045
|
task: { type: 'string', description: 'The task for the sub-agent to complete' },
|
|
999
1046
|
model: { type: 'string', description: 'Model override for the sub-agent (e.g. gpt-4o-mini for cheap tasks)' },
|
|
1000
|
-
context: { type: 'string', description: '
|
|
1047
|
+
context: { type: 'string', description: 'Only the constraints and evidence the sub-agent needs.' },
|
|
1048
|
+
required_artifacts: { type: 'array', items: { type: 'string' }, description: 'Artifacts the sub-agent must return.' },
|
|
1049
|
+
tools: { type: 'array', items: { type: 'string' }, description: 'Exact active tool names the sub-agent may need.' }
|
|
1001
1050
|
},
|
|
1002
1051
|
required: ['task']
|
|
1003
1052
|
}
|
|
@@ -1879,7 +1928,10 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
1879
1928
|
case 'memory_update_core': {
|
|
1880
1929
|
const { MemoryManager } = require('../memory/manager');
|
|
1881
1930
|
const mm = new MemoryManager();
|
|
1882
|
-
|
|
1931
|
+
if (args.confirmed !== true) {
|
|
1932
|
+
return { error: 'Core memory updates require explicit current-session user confirmation.' };
|
|
1933
|
+
}
|
|
1934
|
+
mm.updateCore(userId, args.key, args.value, { agentId, confirmed: true });
|
|
1883
1935
|
return { success: true, key: args.key, message: 'Core memory updated' };
|
|
1884
1936
|
}
|
|
1885
1937
|
|
|
@@ -2258,6 +2310,26 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2258
2310
|
}
|
|
2259
2311
|
}
|
|
2260
2312
|
|
|
2313
|
+
case 'code_navigate': {
|
|
2314
|
+
const service = app?.locals?.codeNavigationService;
|
|
2315
|
+
if (!service) return { error: 'Code navigation service is unavailable.' };
|
|
2316
|
+
try {
|
|
2317
|
+
return await service.navigate(userId, args);
|
|
2318
|
+
} catch (err) {
|
|
2319
|
+
return { error: err.message };
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
case 'query_structured_data': {
|
|
2324
|
+
const service = app?.locals?.structuredDataService;
|
|
2325
|
+
if (!service) return { error: 'Structured data service is unavailable.' };
|
|
2326
|
+
try {
|
|
2327
|
+
return service.query(userId, args);
|
|
2328
|
+
} catch (err) {
|
|
2329
|
+
return { error: err.message };
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2261
2333
|
case 'http_request': {
|
|
2262
2334
|
const controller = new AbortController();
|
|
2263
2335
|
const timeoutMs = args.timeout_ms || 30000;
|
|
@@ -2776,11 +2848,12 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2776
2848
|
|
|
2777
2849
|
case 'spawn_subagent': {
|
|
2778
2850
|
try {
|
|
2779
|
-
|
|
2780
|
-
return await engine.spawnSubagent(userId, runId, task, {
|
|
2851
|
+
return await engine.spawnSubagent(userId, runId, args.task, {
|
|
2781
2852
|
app,
|
|
2782
2853
|
model: args.model || null,
|
|
2783
2854
|
context: args.context || null,
|
|
2855
|
+
requiredArtifacts: args.required_artifacts || [],
|
|
2856
|
+
selectedTools: args.tools || engine.getActiveTools(runId).map((tool) => tool.name),
|
|
2784
2857
|
agentId,
|
|
2785
2858
|
});
|
|
2786
2859
|
} catch (err) {
|
|
@@ -2788,6 +2861,13 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2788
2861
|
}
|
|
2789
2862
|
}
|
|
2790
2863
|
|
|
2864
|
+
case 'activate_tools':
|
|
2865
|
+
try {
|
|
2866
|
+
return engine.activateToolsForRun(runId, args.names || []);
|
|
2867
|
+
} catch (err) {
|
|
2868
|
+
return { error: `activate_tools failed: ${err.message}` };
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2791
2871
|
case 'delegate_to_agent': {
|
|
2792
2872
|
try {
|
|
2793
2873
|
if (triggerSource === 'agent_delegation') {
|
|
@@ -2862,7 +2942,7 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2862
2942
|
|
|
2863
2943
|
const skillRunner = sk();
|
|
2864
2944
|
if (skillRunner) {
|
|
2865
|
-
const skillResult = await skillRunner.executeTool(toolName, args, { userId });
|
|
2945
|
+
const skillResult = await skillRunner.executeTool(toolName, args, { userId, agentId, runId });
|
|
2866
2946
|
if (skillResult !== null) return skillResult;
|
|
2867
2947
|
}
|
|
2868
2948
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const db = require('../../db/database');
|
|
4
|
+
|
|
5
|
+
function finiteToken(value) {
|
|
6
|
+
const parsed = Number(value);
|
|
7
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function normalizeUsage(usage = {}) {
|
|
11
|
+
if (!usage || typeof usage !== 'object') return null;
|
|
12
|
+
const inputTokens = finiteToken(
|
|
13
|
+
usage.inputTokens
|
|
14
|
+
?? usage.promptTokens
|
|
15
|
+
?? usage.input_tokens
|
|
16
|
+
?? usage.prompt_tokens,
|
|
17
|
+
);
|
|
18
|
+
const outputTokens = finiteToken(
|
|
19
|
+
usage.outputTokens
|
|
20
|
+
?? usage.completionTokens
|
|
21
|
+
?? usage.output_tokens
|
|
22
|
+
?? usage.completion_tokens,
|
|
23
|
+
);
|
|
24
|
+
const reasoningTokens = finiteToken(
|
|
25
|
+
usage.reasoningTokens
|
|
26
|
+
?? usage.reasoning_tokens
|
|
27
|
+
?? usage.output_tokens_details?.reasoning_tokens
|
|
28
|
+
?? usage.completion_tokens_details?.reasoning_tokens,
|
|
29
|
+
);
|
|
30
|
+
const cachedReadTokens = finiteToken(
|
|
31
|
+
usage.cachedReadTokens
|
|
32
|
+
?? usage.cached_read_tokens
|
|
33
|
+
?? usage.cache_read_input_tokens
|
|
34
|
+
?? usage.prompt_tokens_details?.cached_tokens
|
|
35
|
+
?? usage.input_tokens_details?.cached_tokens
|
|
36
|
+
?? usage.cachedContentTokenCount,
|
|
37
|
+
);
|
|
38
|
+
const cacheWriteTokens = finiteToken(
|
|
39
|
+
usage.cacheWriteTokens
|
|
40
|
+
?? usage.cache_write_tokens
|
|
41
|
+
?? usage.cache_creation_input_tokens,
|
|
42
|
+
);
|
|
43
|
+
const explicitTotal = finiteToken(usage.totalTokens ?? usage.total_tokens);
|
|
44
|
+
const totalTokens = explicitTotal || inputTokens + outputTokens;
|
|
45
|
+
return {
|
|
46
|
+
inputTokens,
|
|
47
|
+
outputTokens,
|
|
48
|
+
reasoningTokens,
|
|
49
|
+
cachedReadTokens,
|
|
50
|
+
cacheWriteTokens,
|
|
51
|
+
totalTokens,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function mergeUsage(left, right) {
|
|
56
|
+
const a = normalizeUsage(left) || normalizeUsage({});
|
|
57
|
+
const b = normalizeUsage(right) || normalizeUsage({});
|
|
58
|
+
return {
|
|
59
|
+
inputTokens: a.inputTokens + b.inputTokens,
|
|
60
|
+
outputTokens: a.outputTokens + b.outputTokens,
|
|
61
|
+
reasoningTokens: a.reasoningTokens + b.reasoningTokens,
|
|
62
|
+
cachedReadTokens: a.cachedReadTokens + b.cachedReadTokens,
|
|
63
|
+
cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens,
|
|
64
|
+
totalTokens: a.totalTokens + b.totalTokens,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function recordModelUsage({
|
|
69
|
+
runId,
|
|
70
|
+
stepId = null,
|
|
71
|
+
userId,
|
|
72
|
+
agentId = null,
|
|
73
|
+
provider,
|
|
74
|
+
model,
|
|
75
|
+
phase = 'model_turn',
|
|
76
|
+
usage,
|
|
77
|
+
latencyMs = 0,
|
|
78
|
+
estimatedCostUsd = null,
|
|
79
|
+
metadata = {},
|
|
80
|
+
}) {
|
|
81
|
+
const normalized = normalizeUsage(usage);
|
|
82
|
+
if (!runId || !userId || !provider || !model || !normalized) return null;
|
|
83
|
+
const result = db.prepare(
|
|
84
|
+
`INSERT INTO agent_model_usage (
|
|
85
|
+
run_id, step_id, user_id, agent_id, provider, model, phase,
|
|
86
|
+
input_tokens, output_tokens, reasoning_tokens, cached_read_tokens,
|
|
87
|
+
cache_write_tokens, total_tokens, estimated_cost_usd, latency_ms, metadata_json
|
|
88
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
89
|
+
).run(
|
|
90
|
+
runId,
|
|
91
|
+
stepId,
|
|
92
|
+
userId,
|
|
93
|
+
agentId,
|
|
94
|
+
String(provider),
|
|
95
|
+
String(model),
|
|
96
|
+
String(phase),
|
|
97
|
+
normalized.inputTokens,
|
|
98
|
+
normalized.outputTokens,
|
|
99
|
+
normalized.reasoningTokens,
|
|
100
|
+
normalized.cachedReadTokens,
|
|
101
|
+
normalized.cacheWriteTokens,
|
|
102
|
+
normalized.totalTokens,
|
|
103
|
+
Number.isFinite(Number(estimatedCostUsd)) ? Number(estimatedCostUsd) : null,
|
|
104
|
+
Math.max(0, Math.floor(Number(latencyMs) || 0)),
|
|
105
|
+
JSON.stringify(metadata && typeof metadata === 'object' ? metadata : {}),
|
|
106
|
+
);
|
|
107
|
+
return Number(result.lastInsertRowid);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = {
|
|
111
|
+
mergeUsage,
|
|
112
|
+
normalizeUsage,
|
|
113
|
+
recordModelUsage,
|
|
114
|
+
};
|
|
@@ -21,6 +21,11 @@ const { MemoryIngestionService } = require('./memory/ingestion');
|
|
|
21
21
|
const { ArtifactStore } = require('./artifacts/store');
|
|
22
22
|
const { RuntimeManager } = require('./runtime/manager');
|
|
23
23
|
const { WorkspaceManager } = require('./workspace/manager');
|
|
24
|
+
const { CodeNavigationService } = require('./workspace/code_navigation');
|
|
25
|
+
const { StructuredDataService } = require('./workspace/structured_data');
|
|
26
|
+
const { TaskWebhookService } = require('./tasks/webhooks');
|
|
27
|
+
const { LearningManager } = require('./ai/learning');
|
|
28
|
+
const { CapabilityAuditService } = require('./security/capability_audit');
|
|
24
29
|
const { BrowserExtensionRegistry } = require('./browser/extension/registry');
|
|
25
30
|
const { DesktopCompanionRegistry } = require('./desktop/registry');
|
|
26
31
|
const { DesktopProvider } = require('./desktop/provider');
|
|
@@ -52,6 +57,8 @@ function createArtifactStore(app) {
|
|
|
52
57
|
|
|
53
58
|
function createWorkspaceManager(app) {
|
|
54
59
|
const workspaceManager = registerLocal(app, 'workspaceManager', new WorkspaceManager());
|
|
60
|
+
registerLocal(app, 'codeNavigationService', new CodeNavigationService({ workspaceManager }));
|
|
61
|
+
registerLocal(app, 'structuredDataService', new StructuredDataService({ workspaceManager }));
|
|
55
62
|
logServiceReady('Workspace manager ready');
|
|
56
63
|
return workspaceManager;
|
|
57
64
|
}
|
|
@@ -88,6 +95,22 @@ function createDesktopCompanionRegistry(app) {
|
|
|
88
95
|
|
|
89
96
|
function createMemoryManager(app) {
|
|
90
97
|
const memoryManager = registerLocal(app, 'memoryManager', new MemoryManager());
|
|
98
|
+
const reconcile = () => {
|
|
99
|
+
const users = db.prepare('SELECT id FROM users').all();
|
|
100
|
+
for (const user of users) {
|
|
101
|
+
const agents = db.prepare("SELECT id FROM agents WHERE user_id = ? AND status = 'active'").all(user.id);
|
|
102
|
+
for (const agent of agents) {
|
|
103
|
+
try {
|
|
104
|
+
memoryManager.reconcileFacts(user.id, { agentId: agent.id });
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.warn('[Memory] Fact reconciliation failed:', err.message);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const timer = setInterval(reconcile, 6 * 60 * 60 * 1000);
|
|
112
|
+
timer.unref?.();
|
|
113
|
+
registerLocal(app, 'memoryReconciliationTimer', timer);
|
|
91
114
|
logServiceReady('Memory manager ready');
|
|
92
115
|
return memoryManager;
|
|
93
116
|
}
|
|
@@ -473,6 +496,12 @@ async function startServices(app, io) {
|
|
|
473
496
|
skillRunner,
|
|
474
497
|
workspaceManager: app.locals.workspaceManager,
|
|
475
498
|
});
|
|
499
|
+
registerLocal(app, 'learningManager', new LearningManager(skillRunner, io));
|
|
500
|
+
registerLocal(app, 'capabilityAuditService', new CapabilityAuditService({
|
|
501
|
+
mcpClient,
|
|
502
|
+
skillRunner,
|
|
503
|
+
}));
|
|
504
|
+
agentEngine.learningManager = app.locals.learningManager;
|
|
476
505
|
|
|
477
506
|
createMultiStep(app, agentEngine, io);
|
|
478
507
|
createCommandRouter(app);
|
|
@@ -499,6 +528,7 @@ async function startServices(app, io) {
|
|
|
499
528
|
});
|
|
500
529
|
|
|
501
530
|
const taskRuntime = startTaskRuntime(app, io, agentEngine);
|
|
531
|
+
registerLocal(app, 'taskWebhookService', new TaskWebhookService({ taskRuntime }));
|
|
502
532
|
|
|
503
533
|
configureRealtime(app, io, {
|
|
504
534
|
agentEngine,
|
|
@@ -525,6 +555,10 @@ async function startServices(app, io) {
|
|
|
525
555
|
async function stopServices(app) {
|
|
526
556
|
const tasks = [];
|
|
527
557
|
console.log('[Services] Stopping services');
|
|
558
|
+
if (app.locals.memoryReconciliationTimer) {
|
|
559
|
+
clearInterval(app.locals.memoryReconciliationTimer);
|
|
560
|
+
app.locals.memoryReconciliationTimer = null;
|
|
561
|
+
}
|
|
528
562
|
|
|
529
563
|
if (app.locals.taskRuntime) {
|
|
530
564
|
tasks.push(
|