neoagent 2.5.2-beta.9 → 3.0.1-beta.0
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/README.md +5 -1
- package/docs/integrations.md +11 -7
- package/flutter_app/lib/main_controller.dart +34 -0
- package/flutter_app/lib/main_security.dart +19 -0
- package/lib/schema_migrations.js +224 -0
- package/package.json +2 -2
- package/server/admin/admin.css +29 -0
- package/server/admin/admin.js +81 -1
- package/server/admin/index.html +97 -9
- package/server/db/database.js +57 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +32788 -32752
- package/server/routes/security.js +19 -0
- package/server/routes/settings.js +1 -0
- package/server/routes/skills.js +9 -5
- package/server/services/ai/deliverables/artifact_helpers.js +92 -9
- package/server/services/ai/deliverables/selector.js +17 -0
- package/server/services/ai/hooks.js +3 -2
- package/server/services/ai/learning.js +8 -3
- package/server/services/ai/loop/agent_engine_core.js +67 -0
- package/server/services/ai/loop/blank_recovery.js +37 -0
- package/server/services/ai/loop/conversation_loop.js +391 -252
- package/server/services/ai/loop/error_recovery.js +38 -0
- package/server/services/ai/loop/messaging_delivery.js +52 -6
- package/server/services/ai/loop/progress_classification.js +178 -0
- package/server/services/ai/loop/progress_monitor.js +6 -5
- package/server/services/ai/loop/tool_dispatch.js +1 -0
- package/server/services/ai/loopPolicy.js +15 -6
- package/server/services/ai/messagingFallback.js +23 -1
- package/server/services/ai/preModelCompaction.js +31 -0
- package/server/services/ai/repetitionGuard.js +47 -2
- package/server/services/ai/settings.js +8 -0
- package/server/services/ai/systemPrompt.js +24 -0
- package/server/services/ai/taskAnalysis.js +10 -0
- package/server/services/ai/toolEvidence.js +39 -3
- package/server/services/ai/toolResult.js +29 -0
- package/server/services/ai/toolRunner.js +262 -55
- package/server/services/ai/toolSelector.js +20 -3
- package/server/services/ai/tools.js +201 -31
- package/server/services/cli/shell_worker_pool.js +87 -5
- package/server/services/integrations/github/common.js +2 -2
- package/server/services/integrations/github/repos.js +123 -55
- package/server/services/manager.js +16 -0
- package/server/services/memory/manager.js +39 -24
- package/server/services/runtime/docker-vm-manager.js +21 -1
- package/server/services/runtime/manager.js +6 -2
- package/server/services/security/approval_gate_service.js +108 -5
- package/server/services/security/tool_categories.js +113 -4
- package/server/services/security/tool_security_hook.js +7 -2
- package/server/services/skills/runtime.js +2 -0
- package/server/services/workspace/manager.js +56 -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',
|
|
@@ -534,7 +511,12 @@ const githubToolDefinitions = [
|
|
|
534
511
|
},
|
|
535
512
|
content: {
|
|
536
513
|
type: 'string',
|
|
537
|
-
description: '
|
|
514
|
+
description: 'File content as normal UTF-8 text by default. The tool handles the GitHub Contents API encoding.',
|
|
515
|
+
},
|
|
516
|
+
encoding: {
|
|
517
|
+
type: 'string',
|
|
518
|
+
enum: ['utf-8', 'base64'],
|
|
519
|
+
description: 'Input encoding for content. Default utf-8. Use base64 only for already-encoded binary content.',
|
|
538
520
|
},
|
|
539
521
|
sha: {
|
|
540
522
|
type: 'string',
|
|
@@ -717,7 +699,7 @@ const githubToolDefinitions = [
|
|
|
717
699
|
{
|
|
718
700
|
name: 'github_api_request',
|
|
719
701
|
access: 'dynamic_http_method',
|
|
720
|
-
description: 'Make an authenticated GitHub API request for advanced operations not covered by dedicated tools.',
|
|
702
|
+
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
703
|
parameters: {
|
|
722
704
|
type: 'object',
|
|
723
705
|
properties: {
|
|
@@ -728,7 +710,19 @@ const githubToolDefinitions = [
|
|
|
728
710
|
},
|
|
729
711
|
path: {
|
|
730
712
|
type: 'string',
|
|
731
|
-
description: 'API path or
|
|
713
|
+
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.',
|
|
714
|
+
},
|
|
715
|
+
owner_repo: {
|
|
716
|
+
type: 'string',
|
|
717
|
+
description: 'Repository in "owner/repo" format. When provided together with a relative path, the /repos/{owner}/{repo} prefix is automatically prepended.',
|
|
718
|
+
},
|
|
719
|
+
endpoint: {
|
|
720
|
+
type: 'string',
|
|
721
|
+
description: 'Alias for path.',
|
|
722
|
+
},
|
|
723
|
+
url: {
|
|
724
|
+
type: 'string',
|
|
725
|
+
description: 'Full GitHub API URL (https://api.github.com/...).',
|
|
732
726
|
},
|
|
733
727
|
query: {
|
|
734
728
|
type: 'object',
|
|
@@ -738,8 +732,12 @@ const githubToolDefinitions = [
|
|
|
738
732
|
type: 'object',
|
|
739
733
|
description: 'Optional JSON request body.',
|
|
740
734
|
},
|
|
735
|
+
payload: {
|
|
736
|
+
type: 'object',
|
|
737
|
+
description: 'Alias for body.',
|
|
738
|
+
},
|
|
741
739
|
},
|
|
742
|
-
required: ['method'
|
|
740
|
+
required: ['method'],
|
|
743
741
|
},
|
|
744
742
|
},
|
|
745
743
|
];
|
|
@@ -749,6 +747,63 @@ function parseCommaSeparatedList(value) {
|
|
|
749
747
|
return String(value).split(',').map((s) => s.trim()).filter(Boolean);
|
|
750
748
|
}
|
|
751
749
|
|
|
750
|
+
function resolveApiRequestPath(args = {}) {
|
|
751
|
+
for (const key of ['path', 'endpoint', 'url']) {
|
|
752
|
+
const value = args?.[key];
|
|
753
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
754
|
+
}
|
|
755
|
+
return '';
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function hasOwnerRepoArg(args = {}) {
|
|
759
|
+
if (typeof args.owner_repo === 'string' && args.owner_repo.trim()) return true;
|
|
760
|
+
return Boolean(String(args.owner || '').trim() && String(args.repo || '').trim());
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function parseOwnerRepoArg(args = {}) {
|
|
764
|
+
if (typeof args.owner_repo === 'string' && args.owner_repo.trim()) {
|
|
765
|
+
return parseOwnerRepo(args.owner_repo);
|
|
766
|
+
}
|
|
767
|
+
const owner = String(args.owner || '').trim();
|
|
768
|
+
const repo = String(args.repo || '').trim();
|
|
769
|
+
if (owner && repo) {
|
|
770
|
+
return parseOwnerRepo(`${owner}/${repo}`);
|
|
771
|
+
}
|
|
772
|
+
return parseOwnerRepo(args.owner_repo);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function encodeGithubFileContent(args = {}) {
|
|
776
|
+
const content = String(args.content ?? '');
|
|
777
|
+
const encoding = String(args.encoding || args.content_encoding || 'utf-8').toLowerCase();
|
|
778
|
+
if (encoding === 'base64') {
|
|
779
|
+
return content.replace(/\s/g, '');
|
|
780
|
+
}
|
|
781
|
+
return Buffer.from(content, 'utf8').toString('base64');
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function resolveApiRequestBody(args = {}) {
|
|
785
|
+
if (args.body && typeof args.body === 'object') return args.body;
|
|
786
|
+
if (args.payload && typeof args.payload === 'object') return args.payload;
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
for (const definition of githubToolDefinitions) {
|
|
791
|
+
const parameters = definition.parameters;
|
|
792
|
+
const properties = parameters?.properties;
|
|
793
|
+
if (!properties?.owner_repo) continue;
|
|
794
|
+
properties.owner = {
|
|
795
|
+
type: 'string',
|
|
796
|
+
description: 'Repository owner alias. Use together with repo when owner_repo is not supplied.',
|
|
797
|
+
};
|
|
798
|
+
properties.repo = {
|
|
799
|
+
type: 'string',
|
|
800
|
+
description: 'Repository name alias. Use together with owner when owner_repo is not supplied.',
|
|
801
|
+
};
|
|
802
|
+
if (Array.isArray(parameters.required) && parameters.required.includes('owner_repo')) {
|
|
803
|
+
parameters.required = parameters.required.filter((item) => item !== 'owner_repo');
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
752
807
|
async function executeGithubTool(toolName, args, auth) {
|
|
753
808
|
switch (toolName) {
|
|
754
809
|
case 'github_get_auth_user': {
|
|
@@ -768,14 +823,14 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
768
823
|
}
|
|
769
824
|
|
|
770
825
|
case 'github_get_repo': {
|
|
771
|
-
const { owner, repo } =
|
|
826
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
772
827
|
return await githubApiRequest(auth, {
|
|
773
828
|
path: `/repos/${owner}/${repo}`,
|
|
774
829
|
});
|
|
775
830
|
}
|
|
776
831
|
|
|
777
832
|
case 'github_list_issues': {
|
|
778
|
-
const { owner, repo } =
|
|
833
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
779
834
|
const query = { per_page: Math.min(Number(args.max_results) || 30, 100) };
|
|
780
835
|
if (args.state) query.state = args.state;
|
|
781
836
|
if (args.labels) query.labels = args.labels;
|
|
@@ -789,14 +844,14 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
789
844
|
}
|
|
790
845
|
|
|
791
846
|
case 'github_get_issue': {
|
|
792
|
-
const { owner, repo } =
|
|
847
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
793
848
|
return await githubApiRequest(auth, {
|
|
794
849
|
path: `/repos/${owner}/${repo}/issues/${Number(args.issue_number)}`,
|
|
795
850
|
});
|
|
796
851
|
}
|
|
797
852
|
|
|
798
853
|
case 'github_create_issue': {
|
|
799
|
-
const { owner, repo } =
|
|
854
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
800
855
|
return await githubApiRequest(auth, {
|
|
801
856
|
method: 'POST',
|
|
802
857
|
path: `/repos/${owner}/${repo}/issues`,
|
|
@@ -810,7 +865,7 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
810
865
|
}
|
|
811
866
|
|
|
812
867
|
case 'github_update_issue': {
|
|
813
|
-
const { owner, repo } =
|
|
868
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
814
869
|
const body = {};
|
|
815
870
|
if (args.title) body.title = String(args.title);
|
|
816
871
|
if (args.body !== undefined) body.body = String(args.body);
|
|
@@ -825,7 +880,7 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
825
880
|
}
|
|
826
881
|
|
|
827
882
|
case 'github_add_issue_labels': {
|
|
828
|
-
const { owner, repo } =
|
|
883
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
829
884
|
return await githubApiRequest(auth, {
|
|
830
885
|
method: 'POST',
|
|
831
886
|
path: `/repos/${owner}/${repo}/issues/${Number(args.issue_number)}/labels`,
|
|
@@ -836,7 +891,7 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
836
891
|
}
|
|
837
892
|
|
|
838
893
|
case 'github_add_issue_assignees': {
|
|
839
|
-
const { owner, repo } =
|
|
894
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
840
895
|
return await githubApiRequest(auth, {
|
|
841
896
|
method: 'POST',
|
|
842
897
|
path: `/repos/${owner}/${repo}/issues/${Number(args.issue_number)}/assignees`,
|
|
@@ -847,7 +902,7 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
847
902
|
}
|
|
848
903
|
|
|
849
904
|
case 'github_list_prs': {
|
|
850
|
-
const { owner, repo } =
|
|
905
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
851
906
|
const query = { per_page: Math.min(Number(args.max_results) || 30, 100) };
|
|
852
907
|
if (args.state) query.state = args.state;
|
|
853
908
|
if (args.sort) query.sort = args.sort;
|
|
@@ -859,14 +914,14 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
859
914
|
}
|
|
860
915
|
|
|
861
916
|
case 'github_get_pr': {
|
|
862
|
-
const { owner, repo } =
|
|
917
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
863
918
|
return await githubApiRequest(auth, {
|
|
864
919
|
path: `/repos/${owner}/${repo}/pulls/${Number(args.pr_number)}`,
|
|
865
920
|
});
|
|
866
921
|
}
|
|
867
922
|
|
|
868
923
|
case 'github_create_pr': {
|
|
869
|
-
const { owner, repo } =
|
|
924
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
870
925
|
return await githubApiRequest(auth, {
|
|
871
926
|
method: 'POST',
|
|
872
927
|
path: `/repos/${owner}/${repo}/pulls`,
|
|
@@ -882,7 +937,7 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
882
937
|
}
|
|
883
938
|
|
|
884
939
|
case 'github_update_pr': {
|
|
885
|
-
const { owner, repo } =
|
|
940
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
886
941
|
const body = {};
|
|
887
942
|
if (args.title) body.title = String(args.title);
|
|
888
943
|
if (args.body !== undefined) body.body = String(args.body);
|
|
@@ -895,7 +950,7 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
895
950
|
}
|
|
896
951
|
|
|
897
952
|
case 'github_merge_pr': {
|
|
898
|
-
const { owner, repo } =
|
|
953
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
899
954
|
return await githubApiRequest(auth, {
|
|
900
955
|
method: 'PUT',
|
|
901
956
|
path: `/repos/${owner}/${repo}/pulls/${Number(args.pr_number)}/merge`,
|
|
@@ -908,7 +963,7 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
908
963
|
}
|
|
909
964
|
|
|
910
965
|
case 'github_list_commits': {
|
|
911
|
-
const { owner, repo } =
|
|
966
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
912
967
|
const query = { per_page: Math.min(Number(args.max_results) || 30, 100) };
|
|
913
968
|
if (args.sha) query.sha = String(args.sha);
|
|
914
969
|
if (args.path) query.path = String(args.path);
|
|
@@ -919,7 +974,7 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
919
974
|
}
|
|
920
975
|
|
|
921
976
|
case 'github_list_branches': {
|
|
922
|
-
const { owner, repo } =
|
|
977
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
923
978
|
return await githubApiRequest(auth, {
|
|
924
979
|
path: `/repos/${owner}/${repo}/branches`,
|
|
925
980
|
query: buildPaginationParams({ per_page: args.max_results }),
|
|
@@ -927,7 +982,7 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
927
982
|
}
|
|
928
983
|
|
|
929
984
|
case 'github_get_branch': {
|
|
930
|
-
const { owner, repo } =
|
|
985
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
931
986
|
const branch = encodeURIComponent(String(args.branch || ''));
|
|
932
987
|
return await githubApiRequest(auth, {
|
|
933
988
|
path: `/repos/${owner}/${repo}/branches/${branch}`,
|
|
@@ -935,7 +990,7 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
935
990
|
}
|
|
936
991
|
|
|
937
992
|
case 'github_list_collaborators': {
|
|
938
|
-
const { owner, repo } =
|
|
993
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
939
994
|
const maxResults = parsePositiveInt(args.max_results, 30);
|
|
940
995
|
const perPage = Math.min(100, maxResults);
|
|
941
996
|
const query = {
|
|
@@ -958,23 +1013,28 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
958
1013
|
}
|
|
959
1014
|
|
|
960
1015
|
case 'github_get_content': {
|
|
961
|
-
const { owner, repo } =
|
|
1016
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
962
1017
|
const query = {};
|
|
963
1018
|
if (args.ref) query.ref = String(args.ref);
|
|
964
|
-
|
|
1019
|
+
const result = await githubApiRequest(auth, {
|
|
965
1020
|
path: `/repos/${owner}/${repo}/contents/${String(args.path || '')}`,
|
|
966
1021
|
query,
|
|
967
1022
|
});
|
|
1023
|
+
if (result && result.encoding === 'base64' && typeof result.content === 'string') {
|
|
1024
|
+
result.content = Buffer.from(result.content.replace(/\n/g, ''), 'base64').toString('utf8');
|
|
1025
|
+
result.encoding = 'utf8';
|
|
1026
|
+
}
|
|
1027
|
+
return result;
|
|
968
1028
|
}
|
|
969
1029
|
|
|
970
1030
|
case 'github_create_or_update_file': {
|
|
971
|
-
const { owner, repo } =
|
|
1031
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
972
1032
|
return await githubApiRequest(auth, {
|
|
973
1033
|
method: 'PUT',
|
|
974
1034
|
path: `/repos/${owner}/${repo}/contents/${String(args.path || '')}`,
|
|
975
1035
|
body: {
|
|
976
1036
|
message: String(args.message || ''),
|
|
977
|
-
content:
|
|
1037
|
+
content: encodeGithubFileContent(args),
|
|
978
1038
|
sha: args.sha ? String(args.sha) : undefined,
|
|
979
1039
|
branch: args.branch ? String(args.branch) : undefined,
|
|
980
1040
|
},
|
|
@@ -982,7 +1042,7 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
982
1042
|
}
|
|
983
1043
|
|
|
984
1044
|
case 'github_delete_file': {
|
|
985
|
-
const { owner, repo } =
|
|
1045
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
986
1046
|
return await githubApiRequest(auth, {
|
|
987
1047
|
method: 'DELETE',
|
|
988
1048
|
path: `/repos/${owner}/${repo}/contents/${String(args.path || '')}`,
|
|
@@ -995,7 +1055,7 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
995
1055
|
}
|
|
996
1056
|
|
|
997
1057
|
case 'github_list_workflow_runs': {
|
|
998
|
-
const { owner, repo } =
|
|
1058
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
999
1059
|
let path = `/repos/${owner}/${repo}/actions/runs`;
|
|
1000
1060
|
if (args.workflow_id) {
|
|
1001
1061
|
path = `/repos/${owner}/${repo}/actions/workflows/${String(args.workflow_id)}/runs`;
|
|
@@ -1024,14 +1084,14 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
1024
1084
|
}
|
|
1025
1085
|
|
|
1026
1086
|
case 'github_get_workflow_run': {
|
|
1027
|
-
const { owner, repo } =
|
|
1087
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
1028
1088
|
return await githubApiRequest(auth, {
|
|
1029
1089
|
path: `/repos/${owner}/${repo}/actions/runs/${Number(args.run_id)}`,
|
|
1030
1090
|
});
|
|
1031
1091
|
}
|
|
1032
1092
|
|
|
1033
1093
|
case 'github_trigger_workflow': {
|
|
1034
|
-
const { owner, repo } =
|
|
1094
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
1035
1095
|
let inputs = {};
|
|
1036
1096
|
if (args.inputs) {
|
|
1037
1097
|
try {
|
|
@@ -1051,7 +1111,7 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
1051
1111
|
}
|
|
1052
1112
|
|
|
1053
1113
|
case 'github_list_workflows': {
|
|
1054
|
-
const { owner, repo } =
|
|
1114
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
1055
1115
|
return await githubApiRequest(auth, {
|
|
1056
1116
|
path: `/repos/${owner}/${repo}/actions/workflows`,
|
|
1057
1117
|
});
|
|
@@ -1085,7 +1145,10 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
1085
1145
|
|
|
1086
1146
|
case 'github_api_request': {
|
|
1087
1147
|
let baseUrl = 'https://api.github.com';
|
|
1088
|
-
let path =
|
|
1148
|
+
let path = resolveApiRequestPath(args);
|
|
1149
|
+
if (!path) {
|
|
1150
|
+
throw new Error('github_api_request requires path, endpoint, or url.');
|
|
1151
|
+
}
|
|
1089
1152
|
let query = args.query || null;
|
|
1090
1153
|
if (path.startsWith('http')) {
|
|
1091
1154
|
const url = new URL(path);
|
|
@@ -1103,12 +1166,17 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
1103
1166
|
...parsedQuery,
|
|
1104
1167
|
...(args.query && typeof args.query === 'object' ? args.query : {}),
|
|
1105
1168
|
};
|
|
1169
|
+
} else if (hasOwnerRepoArg(args) && !path.startsWith('/repos/') && !path.startsWith('/user') && !path.startsWith('/orgs/') && !path.startsWith('/search/')) {
|
|
1170
|
+
// Convenience: prepend /repos/{owner}/{repo} for relative paths
|
|
1171
|
+
const { owner, repo } = parseOwnerRepoArg(args);
|
|
1172
|
+
const relativePath = path.startsWith('/') ? path : `/${path}`;
|
|
1173
|
+
path = `/repos/${owner}/${repo}${relativePath}`;
|
|
1106
1174
|
}
|
|
1107
1175
|
return await githubApiRequest(auth, {
|
|
1108
1176
|
method: args.method || 'GET',
|
|
1109
1177
|
path,
|
|
1110
1178
|
query,
|
|
1111
|
-
body: args
|
|
1179
|
+
body: resolveApiRequestBody(args),
|
|
1112
1180
|
baseUrl,
|
|
1113
1181
|
});
|
|
1114
1182
|
}
|
|
@@ -1121,4 +1189,4 @@ async function executeGithubTool(toolName, args, auth) {
|
|
|
1121
1189
|
module.exports = {
|
|
1122
1190
|
executeGithubTool,
|
|
1123
1191
|
githubToolDefinitions,
|
|
1124
|
-
};
|
|
1192
|
+
};
|
|
@@ -571,6 +571,22 @@ async function startServices(app, io) {
|
|
|
571
571
|
async function stopServices(app) {
|
|
572
572
|
const tasks = [];
|
|
573
573
|
console.log('[Services] Stopping services');
|
|
574
|
+
if (app.locals.approvalGateService && typeof app.locals.approvalGateService.shutdown === 'function') {
|
|
575
|
+
try {
|
|
576
|
+
app.locals.approvalGateService.shutdown();
|
|
577
|
+
logServiceReady('Pending approvals expired');
|
|
578
|
+
} catch (err) {
|
|
579
|
+
console.error('[ApprovalGate] Shutdown error:', getErrorMessage(err));
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (app.locals.agentEngine && typeof app.locals.agentEngine.interruptAllActiveRuns === 'function') {
|
|
583
|
+
try {
|
|
584
|
+
app.locals.agentEngine.interruptAllActiveRuns();
|
|
585
|
+
logServiceReady('Active runs marked interrupted');
|
|
586
|
+
} catch (err) {
|
|
587
|
+
console.error('[AgentEngine] Interrupt error:', getErrorMessage(err));
|
|
588
|
+
}
|
|
589
|
+
}
|
|
574
590
|
if (app.locals.memoryManager) {
|
|
575
591
|
app.locals.memoryManager.stopEmbeddingIndexBackfill();
|
|
576
592
|
}
|
|
@@ -309,6 +309,34 @@ class MemoryManager {
|
|
|
309
309
|
return resolveAgentId(userId, options?.agentId || options?.agent_id || null);
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
_findExactMemory(userId, agentId, memoryHash, scope) {
|
|
313
|
+
return db.prepare(
|
|
314
|
+
`SELECT id FROM memories
|
|
315
|
+
WHERE user_id = ? AND agent_id = ? AND archived = 0
|
|
316
|
+
AND memory_hash = ?
|
|
317
|
+
AND COALESCE(scope_type, 'agent') = ?
|
|
318
|
+
AND COALESCE(scope_id, '') = COALESCE(?, '')
|
|
319
|
+
LIMIT 1`
|
|
320
|
+
).get(userId, agentId, memoryHash, scope.scopeType, scope.scopeId);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
_reinforceExactMemory(memoryId, importance) {
|
|
324
|
+
db.prepare(
|
|
325
|
+
`UPDATE memories
|
|
326
|
+
SET importance = MAX(importance, ?),
|
|
327
|
+
confidence = MIN(1, confidence + 0.03),
|
|
328
|
+
updated_at = datetime('now')
|
|
329
|
+
WHERE id = ?`
|
|
330
|
+
).run(importance, memoryId);
|
|
331
|
+
db.prepare(
|
|
332
|
+
`UPDATE memory_facts
|
|
333
|
+
SET confidence = MIN(1, confidence + 0.03),
|
|
334
|
+
updated_at = datetime('now')
|
|
335
|
+
WHERE memory_id = ? AND status = 'active'`
|
|
336
|
+
).run(memoryId);
|
|
337
|
+
return memoryId;
|
|
338
|
+
}
|
|
339
|
+
|
|
312
340
|
_backfillMemoryIntelligence(limit = 1000) {
|
|
313
341
|
try {
|
|
314
342
|
const rows = db.prepare(
|
|
@@ -1602,29 +1630,9 @@ class MemoryManager {
|
|
|
1602
1630
|
);
|
|
1603
1631
|
const embedding = embeddingResult?.vector || null;
|
|
1604
1632
|
|
|
1605
|
-
const exact =
|
|
1606
|
-
`SELECT id FROM memories
|
|
1607
|
-
WHERE user_id = ? AND agent_id = ? AND archived = 0
|
|
1608
|
-
AND memory_hash = ?
|
|
1609
|
-
AND COALESCE(scope_type, 'agent') = ?
|
|
1610
|
-
AND COALESCE(scope_id, '') = COALESCE(?, '')
|
|
1611
|
-
LIMIT 1`
|
|
1612
|
-
).get(userId, agentId, memoryHash, scope.scopeType, scope.scopeId);
|
|
1633
|
+
const exact = this._findExactMemory(userId, agentId, memoryHash, scope);
|
|
1613
1634
|
if (exact?.id) {
|
|
1614
|
-
|
|
1615
|
-
`UPDATE memories
|
|
1616
|
-
SET importance = MAX(importance, ?),
|
|
1617
|
-
confidence = MIN(1, confidence + 0.03),
|
|
1618
|
-
updated_at = datetime('now')
|
|
1619
|
-
WHERE id = ?`
|
|
1620
|
-
).run(importance, exact.id);
|
|
1621
|
-
db.prepare(
|
|
1622
|
-
`UPDATE memory_facts
|
|
1623
|
-
SET confidence = MIN(1, confidence + 0.03),
|
|
1624
|
-
updated_at = datetime('now')
|
|
1625
|
-
WHERE memory_id = ? AND status = 'active'`
|
|
1626
|
-
).run(exact.id);
|
|
1627
|
-
return exact.id;
|
|
1635
|
+
return this._reinforceExactMemory(exact.id, importance);
|
|
1628
1636
|
}
|
|
1629
1637
|
|
|
1630
1638
|
const duplicateCandidateIds = embedding
|
|
@@ -1717,8 +1725,8 @@ class MemoryManager {
|
|
|
1717
1725
|
|
|
1718
1726
|
// Save new
|
|
1719
1727
|
const id = uuidv4();
|
|
1720
|
-
db.prepare(
|
|
1721
|
-
`INSERT INTO memories (
|
|
1728
|
+
const insertResult = db.prepare(
|
|
1729
|
+
`INSERT OR IGNORE INTO memories (
|
|
1722
1730
|
id, user_id, agent_id, category, scope_type, scope_id, source_type, source_id, source_label,
|
|
1723
1731
|
stale_after_days, metadata_json, content, summary, importance, confidence, memory_hash, embedding,
|
|
1724
1732
|
embedding_provider, embedding_model, embedding_dimensions, embedded_at
|
|
@@ -1747,6 +1755,13 @@ class MemoryManager {
|
|
|
1747
1755
|
embeddingResult?.dimensions || null,
|
|
1748
1756
|
embedding ? new Date().toISOString() : null,
|
|
1749
1757
|
);
|
|
1758
|
+
if (insertResult.changes === 0) {
|
|
1759
|
+
const existingExact = this._findExactMemory(userId, agentId, memoryHash, scope);
|
|
1760
|
+
if (existingExact?.id) {
|
|
1761
|
+
return this._reinforceExactMemory(existingExact.id, importance);
|
|
1762
|
+
}
|
|
1763
|
+
throw new Error('Memory insert was ignored but no matching memory row was found');
|
|
1764
|
+
}
|
|
1750
1765
|
|
|
1751
1766
|
this._upsertMemoryIntelligence(userId, agentId, id, {
|
|
1752
1767
|
content,
|
|
@@ -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}`,
|
|
@@ -161,9 +161,13 @@ class RuntimeManager {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
async shutdown() {
|
|
164
|
-
|
|
164
|
+
const tasks = [
|
|
165
165
|
this.browserBackend?.shutdown?.(),
|
|
166
|
-
]
|
|
166
|
+
];
|
|
167
|
+
if (typeof this.shellWorkerPool?.shutdown === 'function') {
|
|
168
|
+
tasks.push(Promise.resolve().then(() => this.shellWorkerPool.shutdown()));
|
|
169
|
+
}
|
|
170
|
+
await Promise.allSettled(tasks);
|
|
167
171
|
}
|
|
168
172
|
}
|
|
169
173
|
|