nexus-agentd 0.2.3 → 0.2.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/LICENSE +22 -22
- package/README.md +223 -202
- package/dist/a2a/runtime.d.ts +3 -0
- package/dist/a2a/runtime.js +119 -12
- package/dist/acp/runtime.d.ts +5 -1
- package/dist/acp/runtime.js +180 -36
- package/dist/auth.js +29 -8
- package/dist/completion-contract.js +1 -1
- package/dist/control-plane.js +11 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +23 -5
- package/dist/run-store.d.ts +5 -0
- package/dist/run-store.js +10 -1
- package/dist/server.js +58 -8
- package/dist/session-contract.d.ts +1 -0
- package/dist/session.d.ts +14 -2
- package/dist/session.js +153 -74
- package/dist/types.d.ts +14 -0
- package/dist/webui/app/sources.d.ts +1 -0
- package/dist/webui/app/sources.js +17 -0
- package/dist/webui/icons.d.ts +17 -0
- package/dist/webui/icons.js +23 -6
- package/dist/webui/index.d.ts +1 -0
- package/dist/webui/index.js +32 -12
- package/dist/webui/markup.js +67 -65
- package/dist/webui/styles.js +746 -256
- package/dist/workspace.d.ts +30 -0
- package/dist/workspace.js +67 -2
- package/nexus-agentd.example.json +40 -40
- package/package.json +54 -52
- package/dist/webui/app.d.ts +0 -1
- package/dist/webui/app.js +0 -914
package/dist/workspace.d.ts
CHANGED
|
@@ -5,3 +5,33 @@ export declare class WorkspacePolicy {
|
|
|
5
5
|
resolve(input: string): Promise<string>;
|
|
6
6
|
listRoots(): string[];
|
|
7
7
|
}
|
|
8
|
+
export declare function isWithin(root: string, candidate: string): boolean;
|
|
9
|
+
export type ContainedReadFailure = 'outside' | 'swapped' | 'not-a-file' | 'too-large';
|
|
10
|
+
/**
|
|
11
|
+
* Reported as a kind rather than a finished message because each caller owns a
|
|
12
|
+
* different error contract: workspace publish maps it to an HTTP status, while a
|
|
13
|
+
* MEDIA marker maps it to a terminal_output event.
|
|
14
|
+
*/
|
|
15
|
+
export declare class ContainedReadError extends Error {
|
|
16
|
+
readonly failure: ContainedReadFailure;
|
|
17
|
+
constructor(failure: ContainedReadFailure, message: string);
|
|
18
|
+
}
|
|
19
|
+
export interface ContainedFile {
|
|
20
|
+
root: string;
|
|
21
|
+
path: string;
|
|
22
|
+
relative: string;
|
|
23
|
+
filename: string;
|
|
24
|
+
bytes: Buffer;
|
|
25
|
+
size: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Reads a file that must resolve inside one of `roots`.
|
|
29
|
+
*
|
|
30
|
+
* `roots` must already be realpath'd absolute paths; `WorkspacePolicy.listRoots()`
|
|
31
|
+
* satisfies this. A caller that passes an unresolved root fails closed, because
|
|
32
|
+
* containment is checked against a realpath'd target. `requested` may be absolute
|
|
33
|
+
* (an agent-declared path) or relative to `roots[0]` (a client-supplied path).
|
|
34
|
+
*/
|
|
35
|
+
export declare function readContainedFile(roots: readonly string[], requested: string, options: {
|
|
36
|
+
maxBytes: number;
|
|
37
|
+
}): Promise<ContainedFile>;
|
package/dist/workspace.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { realpath } from 'node:fs/promises';
|
|
1
|
+
import { open, realpath } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
export class WorkspacePolicy {
|
|
4
4
|
roots;
|
|
@@ -22,13 +22,78 @@ export class WorkspacePolicy {
|
|
|
22
22
|
return [...this.roots];
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
function isWithin(root, candidate) {
|
|
25
|
+
export function isWithin(root, candidate) {
|
|
26
26
|
const relative = path.relative(root, candidate);
|
|
27
27
|
return (relative === '' ||
|
|
28
28
|
(!relative.startsWith(`..${path.sep}`) &&
|
|
29
29
|
relative !== '..' &&
|
|
30
30
|
!path.isAbsolute(relative)));
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Reported as a kind rather than a finished message because each caller owns a
|
|
34
|
+
* different error contract: workspace publish maps it to an HTTP status, while a
|
|
35
|
+
* MEDIA marker maps it to a terminal_output event.
|
|
36
|
+
*/
|
|
37
|
+
export class ContainedReadError extends Error {
|
|
38
|
+
failure;
|
|
39
|
+
constructor(failure, message) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.failure = failure;
|
|
42
|
+
this.name = 'ContainedReadError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Reads a file that must resolve inside one of `roots`.
|
|
47
|
+
*
|
|
48
|
+
* `roots` must already be realpath'd absolute paths; `WorkspacePolicy.listRoots()`
|
|
49
|
+
* satisfies this. A caller that passes an unresolved root fails closed, because
|
|
50
|
+
* containment is checked against a realpath'd target. `requested` may be absolute
|
|
51
|
+
* (an agent-declared path) or relative to `roots[0]` (a client-supplied path).
|
|
52
|
+
*/
|
|
53
|
+
export async function readContainedFile(roots, requested, options) {
|
|
54
|
+
if (!roots.length) {
|
|
55
|
+
throw new ContainedReadError('outside', 'no workspace root is configured');
|
|
56
|
+
}
|
|
57
|
+
const unresolved = path.resolve(roots[0], requested);
|
|
58
|
+
const target = await realpath(unresolved);
|
|
59
|
+
const root = roots.find((candidate) => isWithin(candidate, target));
|
|
60
|
+
if (!root)
|
|
61
|
+
throw outsideError(requested);
|
|
62
|
+
let handle;
|
|
63
|
+
try {
|
|
64
|
+
handle = await open(target, 'r');
|
|
65
|
+
// Resolve again after opening so a swapped symlink cannot silently
|
|
66
|
+
// redirect the read outside the allowlist.
|
|
67
|
+
const verified = await realpath(unresolved);
|
|
68
|
+
if (!isWithin(root, verified))
|
|
69
|
+
throw outsideError(requested);
|
|
70
|
+
if (verified !== target) {
|
|
71
|
+
throw new ContainedReadError('swapped', 'file changed while it was being opened');
|
|
72
|
+
}
|
|
73
|
+
const stats = await handle.stat();
|
|
74
|
+
if (!stats.isFile()) {
|
|
75
|
+
throw new ContainedReadError('not-a-file', 'path is not a regular file');
|
|
76
|
+
}
|
|
77
|
+
if (stats.size > options.maxBytes) {
|
|
78
|
+
throw new ContainedReadError('too-large', `file exceeds the ${options.maxBytes} byte limit`);
|
|
79
|
+
}
|
|
80
|
+
const bytes = await handle.readFile();
|
|
81
|
+
return {
|
|
82
|
+
root,
|
|
83
|
+
path: verified,
|
|
84
|
+
relative: path.relative(root, verified).split(path.sep).join('/'),
|
|
85
|
+
filename: path.basename(verified),
|
|
86
|
+
bytes,
|
|
87
|
+
size: bytes.length
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
await handle?.close();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function outsideError(requested) {
|
|
95
|
+
return new ContainedReadError('outside', `path is outside the configured allowlist: ${requested}`);
|
|
96
|
+
}
|
|
32
97
|
function normalize(value) {
|
|
33
98
|
return path.resolve(value);
|
|
34
99
|
}
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
{
|
|
2
|
-
"initialized": false,
|
|
3
|
-
"listen": {
|
|
4
|
-
"host": "0.0.0.0",
|
|
5
|
-
"port": 8787
|
|
6
|
-
},
|
|
7
|
-
"workspaceRoots": [
|
|
8
|
-
"/data/repos"
|
|
9
|
-
],
|
|
10
|
-
"apiKeys": [],
|
|
11
|
-
"maxRequestBytes": 1048576,
|
|
12
|
-
"maxAttachmentBytes": 33554432,
|
|
13
|
-
"requestTimeoutMs": 30000,
|
|
14
|
-
"promptTimeoutMs": 1800000,
|
|
15
|
-
"cleanupIntervalMs": 60000,
|
|
16
|
-
"maxSessions": 64,
|
|
17
|
-
"maxSseConnections": 128,
|
|
18
|
-
"maxConnections": 256,
|
|
19
|
-
"sessionTtlMs": 86400000,
|
|
20
|
-
"agents": {
|
|
21
|
-
"codex": {
|
|
22
|
-
"protocol": "acp",
|
|
23
|
-
"driver": "codex",
|
|
24
|
-
"name": "Codex",
|
|
25
|
-
"command": "codex-acp",
|
|
26
|
-
"workspace": "/data/repos/project",
|
|
27
|
-
"permissionPolicy": "ask"
|
|
28
|
-
},
|
|
29
|
-
"research": {
|
|
30
|
-
"protocol": "a2a",
|
|
31
|
-
"name": "Research Agent",
|
|
32
|
-
"agentCardUrl": "http://192.168.1.20:8080/.well-known/agent-card.json",
|
|
33
|
-
"preferredTransport": "auto",
|
|
34
|
-
"auth": {
|
|
35
|
-
"type": "none"
|
|
36
|
-
},
|
|
37
|
-
"timeoutMs": 60000
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"initialized": false,
|
|
3
|
+
"listen": {
|
|
4
|
+
"host": "0.0.0.0",
|
|
5
|
+
"port": 8787
|
|
6
|
+
},
|
|
7
|
+
"workspaceRoots": [
|
|
8
|
+
"/data/repos"
|
|
9
|
+
],
|
|
10
|
+
"apiKeys": [],
|
|
11
|
+
"maxRequestBytes": 1048576,
|
|
12
|
+
"maxAttachmentBytes": 33554432,
|
|
13
|
+
"requestTimeoutMs": 30000,
|
|
14
|
+
"promptTimeoutMs": 1800000,
|
|
15
|
+
"cleanupIntervalMs": 60000,
|
|
16
|
+
"maxSessions": 64,
|
|
17
|
+
"maxSseConnections": 128,
|
|
18
|
+
"maxConnections": 256,
|
|
19
|
+
"sessionTtlMs": 86400000,
|
|
20
|
+
"agents": {
|
|
21
|
+
"codex": {
|
|
22
|
+
"protocol": "acp",
|
|
23
|
+
"driver": "codex",
|
|
24
|
+
"name": "Codex",
|
|
25
|
+
"command": "codex-acp",
|
|
26
|
+
"workspace": "/data/repos/project",
|
|
27
|
+
"permissionPolicy": "ask"
|
|
28
|
+
},
|
|
29
|
+
"research": {
|
|
30
|
+
"protocol": "a2a",
|
|
31
|
+
"name": "Research Agent",
|
|
32
|
+
"agentCardUrl": "http://192.168.1.20:8080/.well-known/agent-card.json",
|
|
33
|
+
"preferredTransport": "auto",
|
|
34
|
+
"auth": {
|
|
35
|
+
"type": "none"
|
|
36
|
+
},
|
|
37
|
+
"timeoutMs": 60000
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
package/package.json
CHANGED
|
@@ -1,52 +1,54 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "nexus-agentd",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Local Nexus Gateway daemon
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"bin": {
|
|
9
|
-
"nexus-agentd": "dist/cli.js"
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
"dist",
|
|
13
|
-
"nexus-agentd.example.json",
|
|
14
|
-
"README.md"
|
|
15
|
-
],
|
|
16
|
-
"engines": {
|
|
17
|
-
"node": ">=20.0.0"
|
|
18
|
-
},
|
|
19
|
-
"author": "lumia1998",
|
|
20
|
-
"repository": {
|
|
21
|
-
"type": "git",
|
|
22
|
-
"url": "git+https://github.com/lumia1998/nexus-gateway.git"
|
|
23
|
-
},
|
|
24
|
-
"bugs": {
|
|
25
|
-
"url": "https://github.com/lumia1998/nexus-gateway/issues"
|
|
26
|
-
},
|
|
27
|
-
"homepage": "https://github.com/lumia1998/nexus-gateway#readme",
|
|
28
|
-
"keywords": [
|
|
29
|
-
"agent-client-protocol",
|
|
30
|
-
"acp",
|
|
31
|
-
"agent",
|
|
32
|
-
"gateway",
|
|
33
|
-
"coding-agent"
|
|
34
|
-
],
|
|
35
|
-
"scripts": {
|
|
36
|
-
"build": "node scripts/clean-dist.mjs && tsc -p tsconfig.json",
|
|
37
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
38
|
-
"test": "tsx --test test/*.test.ts",
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"@
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "nexus-agentd",
|
|
3
|
+
"version": "0.2.6",
|
|
4
|
+
"description": "Local Nexus Gateway daemon with an HTTP/SSE API and WebUI for allowlisted ACP and remote A2A agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"nexus-agentd": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"nexus-agentd.example.json",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20.0.0"
|
|
18
|
+
},
|
|
19
|
+
"author": "lumia1998",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/lumia1998/nexus-gateway.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/lumia1998/nexus-gateway/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/lumia1998/nexus-gateway#readme",
|
|
28
|
+
"keywords": [
|
|
29
|
+
"agent-client-protocol",
|
|
30
|
+
"acp",
|
|
31
|
+
"agent",
|
|
32
|
+
"gateway",
|
|
33
|
+
"coding-agent"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "node scripts/generate-webui-sources.mjs && node scripts/clean-dist.mjs && tsc -p tsconfig.json",
|
|
37
|
+
"typecheck": "node scripts/generate-webui-sources.mjs && tsc -p tsconfig.json --noEmit",
|
|
38
|
+
"test": "tsx --test test/*.test.ts",
|
|
39
|
+
"test:webui": "node scripts/generate-webui-sources.mjs && playwright test",
|
|
40
|
+
"prepack": "npm run build"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@a2a-js/sdk": "^1.0.1",
|
|
44
|
+
"@agentclientprotocol/sdk": "1.3.0",
|
|
45
|
+
"zod": "^3.25.76"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@playwright/test": "^1.62.1",
|
|
49
|
+
"@types/node": "^20.14.0",
|
|
50
|
+
"tsx": "^4.21.0",
|
|
51
|
+
"typescript": "^5.6.0"
|
|
52
|
+
},
|
|
53
|
+
"license": "MIT"
|
|
54
|
+
}
|
package/dist/webui/app.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const app: string;
|