@nordbyte/nordrelay 0.4.1 → 0.5.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/.env.example +155 -64
- package/README.md +69 -59
- package/dist/access-control.js +124 -115
- package/dist/agent-updates.js +19 -1
- package/dist/bot-rendering.js +838 -0
- package/dist/bot.js +87 -1288
- package/dist/channel-runtime.js +89 -0
- package/dist/config-metadata.js +238 -0
- package/dist/config.js +0 -58
- package/dist/index.js +8 -0
- package/dist/relay-runtime.js +36 -12
- package/dist/settings-service.js +2 -117
- package/dist/telegram-access-commands.js +123 -0
- package/dist/telegram-access-middleware.js +129 -0
- package/dist/telegram-channel-runtime.js +132 -0
- package/dist/telegram-command-menu.js +54 -0
- package/dist/telegram-output.js +216 -0
- package/dist/telegram-update-commands.js +88 -0
- package/dist/user-management.js +708 -0
- package/dist/web-api-contract.js +56 -0
- package/dist/web-dashboard-assets.js +33 -2
- package/dist/web-dashboard-ui.js +14 -14
- package/dist/web-dashboard.js +595 -133
- package/dist/webui-assets/dashboard.css +919 -0
- package/dist/webui-assets/dashboard.js +1611 -0
- package/package.json +6 -3
- package/plugins/nordrelay/.codex-plugin/plugin.json +1 -1
- package/plugins/nordrelay/commands/remote.md +1 -1
- package/plugins/nordrelay/scripts/nordrelay.mjs +227 -78
- package/plugins/nordrelay/skills/telegram-remote/SKILL.md +1 -1
- package/dist/web-dashboard-client.js +0 -275
- package/dist/web-dashboard-style.js +0 -9
package/dist/access-control.js
CHANGED
|
@@ -1,11 +1,68 @@
|
|
|
1
|
-
|
|
1
|
+
import { permissionForWebRequestFromContract } from "./web-api-contract.js";
|
|
2
|
+
export const ALL_PERMISSIONS = [
|
|
2
3
|
"inspect",
|
|
3
|
-
"sessions",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
4
|
+
"sessions.read",
|
|
5
|
+
"sessions.write",
|
|
6
|
+
"prompt.send",
|
|
7
|
+
"prompt.abort",
|
|
8
|
+
"files.read",
|
|
9
|
+
"files.write",
|
|
10
|
+
"settings.read",
|
|
11
|
+
"settings.write",
|
|
12
|
+
"auth.manage",
|
|
13
|
+
"diagnostics.read",
|
|
14
|
+
"logs.read",
|
|
15
|
+
"logs.clear",
|
|
16
|
+
"queue.read",
|
|
17
|
+
"queue.write",
|
|
18
|
+
"updates.run",
|
|
19
|
+
"system.restart",
|
|
20
|
+
"users.read",
|
|
21
|
+
"users.write",
|
|
22
|
+
"audit.read",
|
|
23
|
+
];
|
|
24
|
+
export const ADMIN_GROUP_ID = "admin";
|
|
25
|
+
export const USER_GROUP_ID = "user";
|
|
26
|
+
export const READONLY_GROUP_ID = "readonly";
|
|
27
|
+
export const BUILTIN_GROUPS = [
|
|
28
|
+
{
|
|
29
|
+
id: ADMIN_GROUP_ID,
|
|
30
|
+
name: "Admin",
|
|
31
|
+
description: "Full access to every NordRelay feature, user management, updates, and system controls.",
|
|
32
|
+
permissions: ALL_PERMISSIONS,
|
|
33
|
+
system: true,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: USER_GROUP_ID,
|
|
37
|
+
name: "User",
|
|
38
|
+
description: "Normal read/write use of agents, sessions, prompts, files, and personal agent auth.",
|
|
39
|
+
permissions: [
|
|
40
|
+
"inspect",
|
|
41
|
+
"sessions.read",
|
|
42
|
+
"sessions.write",
|
|
43
|
+
"prompt.send",
|
|
44
|
+
"prompt.abort",
|
|
45
|
+
"files.read",
|
|
46
|
+
"files.write",
|
|
47
|
+
"settings.read",
|
|
48
|
+
"auth.manage",
|
|
49
|
+
"queue.read",
|
|
50
|
+
"queue.write",
|
|
51
|
+
],
|
|
52
|
+
system: true,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: READONLY_GROUP_ID,
|
|
56
|
+
name: "Read Only",
|
|
57
|
+
description: "Read-only access to status, sessions, activity, and artifacts.",
|
|
58
|
+
permissions: [
|
|
59
|
+
"inspect",
|
|
60
|
+
"sessions.read",
|
|
61
|
+
"files.read",
|
|
62
|
+
"settings.read",
|
|
63
|
+
],
|
|
64
|
+
system: true,
|
|
65
|
+
},
|
|
9
66
|
];
|
|
10
67
|
const COMMAND_PERMISSIONS = new Map([
|
|
11
68
|
["start", "inspect"],
|
|
@@ -15,141 +72,93 @@ const COMMAND_PERMISSIONS = new Map([
|
|
|
15
72
|
["version", "inspect"],
|
|
16
73
|
["channels", "inspect"],
|
|
17
74
|
["agents", "inspect"],
|
|
18
|
-
["diagnostics", "admin"],
|
|
19
75
|
["tasks", "inspect"],
|
|
20
76
|
["progress", "inspect"],
|
|
21
|
-
["activity", "
|
|
22
|
-
["audit", "admin"],
|
|
23
|
-
["mirror", "settings"],
|
|
24
|
-
["notify", "settings"],
|
|
25
|
-
["workspaces", "sessions"],
|
|
26
|
-
["voice", "inspect"],
|
|
27
|
-
["agent", "settings"],
|
|
28
|
-
["session", "sessions"],
|
|
29
|
-
["sessions", "sessions"],
|
|
30
|
-
["switch", "sessions"],
|
|
31
|
-
["pinned", "sessions"],
|
|
32
|
-
["pin", "sessions"],
|
|
33
|
-
["unpin", "sessions"],
|
|
34
|
-
["attach", "sessions"],
|
|
35
|
-
["handback", "sessions"],
|
|
36
|
-
["new", "sessions"],
|
|
37
|
-
["sync", "sessions"],
|
|
38
|
-
["lock", "sessions"],
|
|
39
|
-
["unlock", "sessions"],
|
|
40
|
-
["locks", "sessions"],
|
|
41
|
-
["queue", "inspect"],
|
|
42
|
-
["cancel", "prompt"],
|
|
43
|
-
["clearqueue", "prompt"],
|
|
44
|
-
["retry", "prompt"],
|
|
45
|
-
["abort", "prompt"],
|
|
46
|
-
["stop", "prompt"],
|
|
47
|
-
["artifacts", "files"],
|
|
48
|
-
["launch", "settings"],
|
|
49
|
-
["launch_profiles", "settings"],
|
|
50
|
-
["launch-profiles", "settings"],
|
|
51
|
-
["fast", "settings"],
|
|
52
|
-
["model", "settings"],
|
|
53
|
-
["reasoning", "settings"],
|
|
54
|
-
["effort", "settings"],
|
|
77
|
+
["activity", "sessions.read"],
|
|
55
78
|
["auth", "inspect"],
|
|
56
|
-
["
|
|
57
|
-
["
|
|
58
|
-
["
|
|
59
|
-
["
|
|
60
|
-
["
|
|
79
|
+
["voice", "inspect"],
|
|
80
|
+
["whoami", "inspect"],
|
|
81
|
+
["diagnostics", "diagnostics.read"],
|
|
82
|
+
["logs", "logs.read"],
|
|
83
|
+
["audit", "audit.read"],
|
|
84
|
+
["restart", "system.restart"],
|
|
85
|
+
["update", "updates.run"],
|
|
86
|
+
["workspaces", "sessions.read"],
|
|
87
|
+
["session", "sessions.read"],
|
|
88
|
+
["sessions", "sessions.read"],
|
|
89
|
+
["pinned", "sessions.read"],
|
|
90
|
+
["locks", "sessions.read"],
|
|
91
|
+
["queue", "queue.read"],
|
|
92
|
+
["artifacts", "files.read"],
|
|
93
|
+
["agent", "settings.write"],
|
|
94
|
+
["mirror", "settings.write"],
|
|
95
|
+
["notify", "settings.write"],
|
|
96
|
+
["launch", "settings.write"],
|
|
97
|
+
["launch_profiles", "settings.write"],
|
|
98
|
+
["launch-profiles", "settings.write"],
|
|
99
|
+
["fast", "settings.write"],
|
|
100
|
+
["model", "settings.write"],
|
|
101
|
+
["reasoning", "settings.write"],
|
|
102
|
+
["effort", "settings.write"],
|
|
103
|
+
["login", "auth.manage"],
|
|
104
|
+
["logout", "auth.manage"],
|
|
105
|
+
["new", "sessions.write"],
|
|
106
|
+
["switch", "sessions.write"],
|
|
107
|
+
["attach", "sessions.write"],
|
|
108
|
+
["handback", "sessions.write"],
|
|
109
|
+
["sync", "sessions.write"],
|
|
110
|
+
["pin", "sessions.write"],
|
|
111
|
+
["unpin", "sessions.write"],
|
|
112
|
+
["lock", "sessions.write"],
|
|
113
|
+
["unlock", "sessions.write"],
|
|
114
|
+
["retry", "prompt.send"],
|
|
115
|
+
["clearqueue", "queue.write"],
|
|
116
|
+
["cancel", "queue.write"],
|
|
117
|
+
["abort", "prompt.abort"],
|
|
118
|
+
["stop", "prompt.abort"],
|
|
119
|
+
["register_chat", "users.write"],
|
|
120
|
+
["chat_access", "users.write"],
|
|
121
|
+
["link", "inspect"],
|
|
61
122
|
]);
|
|
62
|
-
export function createDefaultRolePolicies() {
|
|
63
|
-
return {
|
|
64
|
-
admin: new Set(ALL_PERMISSIONS),
|
|
65
|
-
operator: new Set(["inspect", "sessions", "prompt", "files", "settings", "auth"]),
|
|
66
|
-
readonly: new Set(["inspect", "sessions"]),
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
export function parseRolePoliciesJson(raw) {
|
|
70
|
-
const policies = createDefaultRolePolicies();
|
|
71
|
-
if (!raw) {
|
|
72
|
-
return policies;
|
|
73
|
-
}
|
|
74
|
-
let parsed;
|
|
75
|
-
try {
|
|
76
|
-
parsed = JSON.parse(raw);
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
throw new Error(`Invalid TELEGRAM_ROLE_POLICIES_JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
80
|
-
}
|
|
81
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
82
|
-
throw new Error("TELEGRAM_ROLE_POLICIES_JSON must be an object keyed by role");
|
|
83
|
-
}
|
|
84
|
-
for (const [role, rawPermissions] of Object.entries(parsed)) {
|
|
85
|
-
if (!isTelegramRole(role)) {
|
|
86
|
-
throw new Error(`Invalid TELEGRAM_ROLE_POLICIES_JSON role: ${role}`);
|
|
87
|
-
}
|
|
88
|
-
policies[role] = parsePermissionList(rawPermissions, role);
|
|
89
|
-
}
|
|
90
|
-
if (!policies.admin.has("admin")) {
|
|
91
|
-
policies.admin.add("admin");
|
|
92
|
-
}
|
|
93
|
-
return policies;
|
|
94
|
-
}
|
|
95
|
-
export function hasTelegramPermission(policies, role, permission) {
|
|
96
|
-
return policies[role].has(permission) || policies[role].has("admin");
|
|
97
|
-
}
|
|
98
123
|
export function permissionForCommand(command) {
|
|
99
124
|
if (!command) {
|
|
100
|
-
return
|
|
125
|
+
return null;
|
|
101
126
|
}
|
|
102
|
-
return COMMAND_PERMISSIONS.get(command.toLowerCase()) ??
|
|
127
|
+
return COMMAND_PERMISSIONS.get(command.toLowerCase()) ?? null;
|
|
103
128
|
}
|
|
104
129
|
export function permissionForCallbackData(callbackData) {
|
|
105
130
|
if (!callbackData) {
|
|
106
|
-
return
|
|
131
|
+
return null;
|
|
107
132
|
}
|
|
108
133
|
if (callbackData === "noop_page") {
|
|
109
134
|
return "inspect";
|
|
110
135
|
}
|
|
111
136
|
if (/^(sess_|ws_)/.test(callbackData)) {
|
|
112
|
-
return "sessions";
|
|
137
|
+
return "sessions.write";
|
|
113
138
|
}
|
|
114
139
|
if (/^(launch_|launchconfirm_|model_|effort_|agent_)/.test(callbackData)) {
|
|
115
|
-
return "settings";
|
|
140
|
+
return "settings.write";
|
|
116
141
|
}
|
|
117
142
|
if (callbackData.startsWith("upd_")) {
|
|
118
|
-
return "
|
|
143
|
+
return "updates.run";
|
|
119
144
|
}
|
|
120
145
|
if (callbackData.startsWith("approval_") || callbackData.startsWith("codex_abort:") || callbackData.startsWith("agent_abort:")) {
|
|
121
|
-
return "prompt";
|
|
146
|
+
return "prompt.abort";
|
|
122
147
|
}
|
|
123
148
|
if (callbackData.startsWith("queue_")) {
|
|
124
|
-
return "
|
|
149
|
+
return "queue.write";
|
|
150
|
+
}
|
|
151
|
+
if (callbackData.startsWith("artifact_delete")) {
|
|
152
|
+
return "files.write";
|
|
125
153
|
}
|
|
126
154
|
if (callbackData.startsWith("artifact_")) {
|
|
127
|
-
return "files";
|
|
155
|
+
return "files.read";
|
|
128
156
|
}
|
|
129
|
-
return
|
|
157
|
+
return null;
|
|
130
158
|
}
|
|
131
|
-
export function
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
function parsePermissionList(rawPermissions, role) {
|
|
135
|
-
if (rawPermissions === "*") {
|
|
136
|
-
return new Set(ALL_PERMISSIONS);
|
|
137
|
-
}
|
|
138
|
-
if (!Array.isArray(rawPermissions)) {
|
|
139
|
-
throw new Error(`TELEGRAM_ROLE_POLICIES_JSON.${role} must be an array or "*"`);
|
|
140
|
-
}
|
|
141
|
-
const permissions = new Set();
|
|
142
|
-
for (const rawPermission of rawPermissions) {
|
|
143
|
-
if (rawPermission === "*") {
|
|
144
|
-
return new Set(ALL_PERMISSIONS);
|
|
145
|
-
}
|
|
146
|
-
if (typeof rawPermission !== "string" || !isTelegramPermission(rawPermission)) {
|
|
147
|
-
throw new Error(`Invalid TELEGRAM_ROLE_POLICIES_JSON permission for ${role}: ${String(rawPermission)}`);
|
|
148
|
-
}
|
|
149
|
-
permissions.add(rawPermission);
|
|
150
|
-
}
|
|
151
|
-
return permissions;
|
|
159
|
+
export function permissionForWebRequest(method, pathname) {
|
|
160
|
+
return permissionForWebRequestFromContract(method, pathname);
|
|
152
161
|
}
|
|
153
|
-
function
|
|
162
|
+
export function isPermission(value) {
|
|
154
163
|
return ALL_PERMISSIONS.includes(value);
|
|
155
164
|
}
|
package/dist/agent-updates.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { agentLabel } from "./agent.js";
|
|
@@ -34,6 +34,9 @@ export class AgentUpdateManager {
|
|
|
34
34
|
}
|
|
35
35
|
readLog(id) {
|
|
36
36
|
const job = this.requireJob(id);
|
|
37
|
+
if (job.logDeletedAt) {
|
|
38
|
+
return { job: this.snapshot(job), plain: `Update log was deleted at ${job.logDeletedAt}.` };
|
|
39
|
+
}
|
|
37
40
|
try {
|
|
38
41
|
return { job: this.snapshot(job), plain: redactText(readFileSync(job.logPath, "utf8")) };
|
|
39
42
|
}
|
|
@@ -44,6 +47,17 @@ export class AgentUpdateManager {
|
|
|
44
47
|
};
|
|
45
48
|
}
|
|
46
49
|
}
|
|
50
|
+
deleteLog(id) {
|
|
51
|
+
const job = this.requireJob(id);
|
|
52
|
+
if (job.status === "running") {
|
|
53
|
+
throw new Error("Cannot delete the update log while the update job is still running.");
|
|
54
|
+
}
|
|
55
|
+
const snapshot = this.snapshot(job);
|
|
56
|
+
rmSync(job.logPath, { force: true });
|
|
57
|
+
this.jobs.delete(id);
|
|
58
|
+
this.persistJobs();
|
|
59
|
+
return snapshot;
|
|
60
|
+
}
|
|
47
61
|
start(agentId, context = {}) {
|
|
48
62
|
const running = [...this.jobs.values()].find((job) => job.agentId === agentId && job.status === "running");
|
|
49
63
|
if (running) {
|
|
@@ -181,6 +195,10 @@ export class AgentUpdateManager {
|
|
|
181
195
|
const parsed = JSON.parse(readFileSync(this.manifestPath, "utf8"));
|
|
182
196
|
let changed = false;
|
|
183
197
|
for (const snapshot of parsed) {
|
|
198
|
+
if (snapshot.logDeletedAt) {
|
|
199
|
+
changed = true;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
184
202
|
const staleRunning = snapshot.status === "running" && !isProcessRunning(snapshot.ownerPid);
|
|
185
203
|
if (staleRunning) {
|
|
186
204
|
changed = true;
|