neoagent 2.5.2-beta.2 → 2.5.2-beta.21
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/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +4 -4
- package/server/services/ai/deliverables/artifact_helpers.js +109 -16
- package/server/services/ai/deliverables/selector.js +17 -0
- package/server/services/ai/engine.js +2 -4312
- package/server/services/ai/loop/agent_engine_core.js +1590 -0
- package/server/services/ai/loop/blank_recovery.js +37 -0
- package/server/services/ai/loop/callbacks.js +151 -0
- package/server/services/ai/loop/completion_judge.js +252 -0
- package/server/services/ai/loop/conversation_loop.js +2398 -0
- package/server/services/ai/loop/delivery_state.js +27 -0
- package/server/services/ai/loop/error_recovery.js +38 -0
- package/server/services/ai/loop/iteration_budget.js +24 -0
- package/server/services/ai/loop/messaging_delivery.js +296 -0
- package/server/services/ai/loop/model_io.js +258 -0
- package/server/services/ai/loop/progress_classification.js +177 -0
- package/server/services/ai/loop/progress_monitor.js +81 -0
- package/server/services/ai/loop/run_state.js +356 -0
- package/server/services/ai/loop/tool_dispatch.js +230 -0
- package/server/services/ai/loopPolicy.js +13 -6
- package/server/services/ai/messagingFallback.js +20 -0
- package/server/services/ai/repetitionGuard.js +47 -2
- package/server/services/ai/systemPrompt.js +5 -0
- package/server/services/ai/taskAnalysis.js +6 -0
- package/server/services/ai/toolEvidence.js +8 -1
- package/server/services/ai/tools.js +82 -11
- package/server/services/integrations/github/repos.js +39 -29
- package/server/services/messaging/manager.js +7 -0
- package/server/services/runtime/backends/local-vm.js +7 -7
- package/server/services/runtime/docker-vm-manager.js +21 -1
- package/server/services/workspace/manager.js +1 -0
|
@@ -490,29 +490,6 @@ const githubToolDefinitions = [
|
|
|
490
490
|
required: ['owner_repo'],
|
|
491
491
|
},
|
|
492
492
|
},
|
|
493
|
-
{
|
|
494
|
-
name: 'github_get_content',
|
|
495
|
-
access: 'read',
|
|
496
|
-
description: 'Get file or directory contents from a repository.',
|
|
497
|
-
parameters: {
|
|
498
|
-
type: 'object',
|
|
499
|
-
properties: {
|
|
500
|
-
owner_repo: {
|
|
501
|
-
type: 'string',
|
|
502
|
-
description: 'Repository in format "owner/repo".',
|
|
503
|
-
},
|
|
504
|
-
path: {
|
|
505
|
-
type: 'string',
|
|
506
|
-
description: 'File or directory path.',
|
|
507
|
-
},
|
|
508
|
-
ref: {
|
|
509
|
-
type: 'string',
|
|
510
|
-
description: 'Git ref (branch, tag, or SHA).',
|
|
511
|
-
},
|
|
512
|
-
},
|
|
513
|
-
required: ['owner_repo', 'path'],
|
|
514
|
-
},
|
|
515
|
-
},
|
|
516
493
|
{
|
|
517
494
|
name: 'github_create_or_update_file',
|
|
518
495
|
access: 'write',
|
|
@@ -717,7 +694,7 @@ const githubToolDefinitions = [
|
|
|
717
694
|
{
|
|
718
695
|
name: 'github_api_request',
|
|
719
696
|
access: 'dynamic_http_method',
|
|
720
|
-
description: 'Make an authenticated GitHub API request for advanced operations not covered by dedicated tools.',
|
|
697
|
+
description: 'Make an authenticated GitHub API request for advanced operations not covered by dedicated tools. Path must be the FULL API path starting with /repos/{owner}/{repo}/... — e.g. /repos/NeoLabs-Systems/NeoAgent/git/trees/main?recursive=1. Alternatively, supply owner_repo and a relative path like /git/trees/main and the prefix is prepended automatically.',
|
|
721
698
|
parameters: {
|
|
722
699
|
type: 'object',
|
|
723
700
|
properties: {
|
|
@@ -728,7 +705,19 @@ const githubToolDefinitions = [
|
|
|
728
705
|
},
|
|
729
706
|
path: {
|
|
730
707
|
type: 'string',
|
|
731
|
-
description: 'API path or
|
|
708
|
+
description: 'Full API path (e.g. /repos/owner/repo/git/trees/main) or a relative path like /git/trees/main when owner_repo is also provided.',
|
|
709
|
+
},
|
|
710
|
+
owner_repo: {
|
|
711
|
+
type: 'string',
|
|
712
|
+
description: 'Repository in "owner/repo" format. When provided together with a relative path, the /repos/{owner}/{repo} prefix is automatically prepended.',
|
|
713
|
+
},
|
|
714
|
+
endpoint: {
|
|
715
|
+
type: 'string',
|
|
716
|
+
description: 'Alias for path.',
|
|
717
|
+
},
|
|
718
|
+
url: {
|
|
719
|
+
type: 'string',
|
|
720
|
+
description: 'Full GitHub API URL (https://api.github.com/...).',
|
|
732
721
|
},
|
|
733
722
|
query: {
|
|
734
723
|
type: 'object',
|
|
@@ -739,7 +728,7 @@ const githubToolDefinitions = [
|
|
|
739
728
|
description: 'Optional JSON request body.',
|
|
740
729
|
},
|
|
741
730
|
},
|
|
742
|
-
required: ['method'
|
|
731
|
+
required: ['method'],
|
|
743
732
|
},
|
|
744
733
|
},
|
|
745
734
|
];
|
|
@@ -749,6 +738,14 @@ function parseCommaSeparatedList(value) {
|
|
|
749
738
|
return String(value).split(',').map((s) => s.trim()).filter(Boolean);
|
|
750
739
|
}
|
|
751
740
|
|
|
741
|
+
function resolveApiRequestPath(args = {}) {
|
|
742
|
+
for (const key of ['path', 'endpoint', 'url']) {
|
|
743
|
+
const value = args?.[key];
|
|
744
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
745
|
+
}
|
|
746
|
+
return '';
|
|
747
|
+
}
|
|
748
|
+
|
|
752
749
|
async function executeGithubTool(toolName, args, auth) {
|
|
753
750
|
switch (toolName) {
|
|
754
751
|
case 'github_get_auth_user': {
|
|
@@ -961,10 +958,15 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
961
958
|
const { owner, repo } = parseOwnerRepo(args.owner_repo);
|
|
962
959
|
const query = {};
|
|
963
960
|
if (args.ref) query.ref = String(args.ref);
|
|
964
|
-
|
|
961
|
+
const result = await githubApiRequest(auth, {
|
|
965
962
|
path: `/repos/${owner}/${repo}/contents/${String(args.path || '')}`,
|
|
966
963
|
query,
|
|
967
964
|
});
|
|
965
|
+
if (result && result.encoding === 'base64' && typeof result.content === 'string') {
|
|
966
|
+
result.content = Buffer.from(result.content.replace(/\n/g, ''), 'base64').toString('utf8');
|
|
967
|
+
result.encoding = 'utf8';
|
|
968
|
+
}
|
|
969
|
+
return result;
|
|
968
970
|
}
|
|
969
971
|
|
|
970
972
|
case 'github_create_or_update_file': {
|
|
@@ -1085,7 +1087,10 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
1085
1087
|
|
|
1086
1088
|
case 'github_api_request': {
|
|
1087
1089
|
let baseUrl = 'https://api.github.com';
|
|
1088
|
-
let path =
|
|
1090
|
+
let path = resolveApiRequestPath(args);
|
|
1091
|
+
if (!path) {
|
|
1092
|
+
throw new Error('github_api_request requires path, endpoint, or url.');
|
|
1093
|
+
}
|
|
1089
1094
|
let query = args.query || null;
|
|
1090
1095
|
if (path.startsWith('http')) {
|
|
1091
1096
|
const url = new URL(path);
|
|
@@ -1103,6 +1108,11 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
1103
1108
|
...parsedQuery,
|
|
1104
1109
|
...(args.query && typeof args.query === 'object' ? args.query : {}),
|
|
1105
1110
|
};
|
|
1111
|
+
} else if (args.owner_repo && !path.startsWith('/repos/') && !path.startsWith('/user') && !path.startsWith('/orgs/') && !path.startsWith('/search/')) {
|
|
1112
|
+
// Convenience: prepend /repos/{owner}/{repo} for relative paths
|
|
1113
|
+
const { owner, repo } = parseOwnerRepo(args.owner_repo);
|
|
1114
|
+
const relativePath = path.startsWith('/') ? path : `/${path}`;
|
|
1115
|
+
path = `/repos/${owner}/${repo}${relativePath}`;
|
|
1106
1116
|
}
|
|
1107
1117
|
return await githubApiRequest(auth, {
|
|
1108
1118
|
method: args.method || 'GET',
|
|
@@ -1121,4 +1131,4 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
1121
1131
|
module.exports = {
|
|
1122
1132
|
executeGithubTool,
|
|
1123
1133
|
githubToolDefinitions,
|
|
1124
|
-
};
|
|
1134
|
+
};
|
|
@@ -515,6 +515,13 @@ class MessagingManager extends EventEmitter {
|
|
|
515
515
|
}
|
|
516
516
|
|
|
517
517
|
const result = await platform.sendMessage(to, normalizedContent, sendOptions);
|
|
518
|
+
if (result?.success === false) {
|
|
519
|
+
const reason = result.error || result.reason || 'platform rejected the message';
|
|
520
|
+
const error = new Error(`Platform ${platformName} delivery failed: ${reason}`);
|
|
521
|
+
error.code = 'MESSAGING_DELIVERY_FAILED';
|
|
522
|
+
error.deliveryResult = result;
|
|
523
|
+
throw error;
|
|
524
|
+
}
|
|
518
525
|
|
|
519
526
|
db.prepare('INSERT INTO messages (user_id, agent_id, run_id, role, content, platform, platform_chat_id, media_path, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
520
527
|
.run(userId, agentId, runId, 'assistant', normalizedContent, platformName, to, mediaPath, metadata ? JSON.stringify(metadata) : null);
|
|
@@ -250,8 +250,8 @@ class VmBrowserProvider {
|
|
|
250
250
|
async typeText(text, options = {}) { return this.#materialize(await this.client.request('POST', '/browser/type-text', { text, ...options })); }
|
|
251
251
|
async pressKey(key, screenshot = true) { return this.#materialize(await this.client.request('POST', '/browser/press-key', { key, screenshot })); }
|
|
252
252
|
async scroll(deltaX, deltaY, screenshot = true) { return this.#materialize(await this.client.request('POST', '/browser/scroll', { deltaX, deltaY, screenshot })); }
|
|
253
|
-
extract(selector, attribute, all = false) { return this.client.request('POST', '/browser/extract', { selector, attribute, all }); }
|
|
254
|
-
evaluate(script) { return this.client.request('POST', '/browser/execute', { code: script }); }
|
|
253
|
+
async extract(selector, attribute, all = false) { return this.client.request('POST', '/browser/extract', { selector, attribute, all }); }
|
|
254
|
+
async evaluate(script) { return this.client.request('POST', '/browser/execute', { code: script }); }
|
|
255
255
|
async screenshot(options = {}) { return this.#materialize(await this.client.request('POST', '/browser/screenshot', options)); }
|
|
256
256
|
async screenshotJpeg(quality = 80, options = {}) {
|
|
257
257
|
const result = await this.client.request('POST', '/browser/screenshot-jpeg', { ...options, quality });
|
|
@@ -259,11 +259,11 @@ class VmBrowserProvider {
|
|
|
259
259
|
if (!content) throw new Error('VM browser screenshot-jpeg returned no data.');
|
|
260
260
|
return Buffer.from(content, 'base64');
|
|
261
261
|
}
|
|
262
|
-
launch(options = {}) { return this.client.request('POST', '/browser/launch', options); }
|
|
263
|
-
closeBrowser() { return this.client.request('POST', '/browser/close'); }
|
|
264
|
-
fill(selector, value) { return this.type(selector, value); }
|
|
265
|
-
extractContent(options = {}) { return this.client.request('POST', '/browser/extract', options); }
|
|
266
|
-
executeJS(code) { return this.evaluate(code); }
|
|
262
|
+
async launch(options = {}) { return this.client.request('POST', '/browser/launch', options); }
|
|
263
|
+
async closeBrowser() { return this.client.request('POST', '/browser/close'); }
|
|
264
|
+
async fill(selector, value) { return this.type(selector, value); }
|
|
265
|
+
async extractContent(options = {}) { return this.client.request('POST', '/browser/extract', options); }
|
|
266
|
+
async executeJS(code) { return this.evaluate(code); }
|
|
267
267
|
async getPageInfo() {
|
|
268
268
|
const status = await this.client.request('GET', '/browser/status');
|
|
269
269
|
this.headless = status?.headless !== false;
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { spawnSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
4
6
|
const http = require('http');
|
|
5
7
|
const net = require('net');
|
|
8
|
+
const { AGENT_DATA_DIR } = require('../../../runtime/paths');
|
|
9
|
+
const { sanitizeWorkspaceKey } = require('../workspace/manager');
|
|
6
10
|
|
|
7
11
|
const CONTAINER_IMAGE = 'mcr.microsoft.com/playwright:v1.44.0-focal';
|
|
8
12
|
const CONTAINER_LABEL = 'neoagent.managed=1';
|
|
13
|
+
// The per-user host workspace is bind-mounted here so the shell (execute_command)
|
|
14
|
+
// and the workspace file tools (read_file/write_file/list_directory/search_files)
|
|
15
|
+
// operate on the SAME files. The guest agent defaults its shell cwd to this path.
|
|
16
|
+
const GUEST_WORKSPACE = '/workspace';
|
|
17
|
+
|
|
18
|
+
// Host path of a user's workspace — must match WorkspaceManager's layout exactly
|
|
19
|
+
// (AGENT_DATA_DIR/workspaces/<sanitized key>) so both sides see one directory.
|
|
20
|
+
function hostWorkspaceDir(key) {
|
|
21
|
+
return path.join(AGENT_DATA_DIR, 'workspaces', sanitizeWorkspaceKey(key));
|
|
22
|
+
}
|
|
9
23
|
|
|
10
24
|
// ─── Guest agent ─────────────────────────────────────────────────────────────
|
|
11
25
|
// Injected into every container. Pure Node.js — only built-in modules + playwright
|
|
@@ -104,7 +118,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
104
118
|
if (req.method === 'POST' && url === '/exec') {
|
|
105
119
|
const timeoutMs = Math.min(Number(b.timeout) || 15 * 60 * 1000, 20 * 60 * 1000);
|
|
106
120
|
const child = spawn('sh', ['-c', b.command || 'true'], {
|
|
107
|
-
cwd: b.cwd || '/
|
|
121
|
+
cwd: b.cwd || '/workspace',
|
|
108
122
|
env: { ...process.env, ...b.env },
|
|
109
123
|
});
|
|
110
124
|
const pid = child.pid;
|
|
@@ -325,6 +339,10 @@ class DockerVMManager {
|
|
|
325
339
|
const port = await findAvailablePort();
|
|
326
340
|
console.log(`[DockerVM:${this.profile}] Starting container for user ${key} on port ${port}`);
|
|
327
341
|
|
|
342
|
+
// Bind-mount the user's host workspace so shell and file tools share one place.
|
|
343
|
+
const workspaceDir = hostWorkspaceDir(key);
|
|
344
|
+
try { fs.mkdirSync(workspaceDir, { recursive: true }); } catch { /* best effort */ }
|
|
345
|
+
|
|
328
346
|
const containerId = docker([
|
|
329
347
|
'run', '-d',
|
|
330
348
|
'--memory', `${this.memoryMb}m`,
|
|
@@ -334,6 +352,8 @@ class DockerVMManager {
|
|
|
334
352
|
...(this.guestToken ? ['-e', `NEOAGENT_VM_GUEST_TOKEN=${this.guestToken}`] : []),
|
|
335
353
|
'--shm-size=2g',
|
|
336
354
|
'--security-opt', 'no-new-privileges',
|
|
355
|
+
'-v', `${workspaceDir}:${GUEST_WORKSPACE}`,
|
|
356
|
+
'-w', GUEST_WORKSPACE,
|
|
337
357
|
'--label', CONTAINER_LABEL,
|
|
338
358
|
'--label', `neoagent.profile=${this.profile}`,
|
|
339
359
|
'--label', `neoagent.user=${key}`,
|