@nordbyte/nordrelay 0.5.1 → 0.6.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 +65 -11
- package/README.md +97 -23
- package/dist/access-control.js +1 -0
- package/dist/activity-events.js +44 -0
- package/dist/agent-updates.js +18 -2
- package/dist/audit-log.js +40 -2
- package/dist/bot-rendering.js +10 -7
- package/dist/bot.js +492 -7
- package/dist/channel-actions.js +7 -2
- package/dist/channel-adapter.js +34 -7
- package/dist/channel-command-service.js +156 -0
- package/dist/channel-turn-service.js +237 -0
- package/dist/codex-cli.js +1 -1
- package/dist/config-metadata.js +80 -13
- package/dist/config.js +77 -7
- package/dist/context-key.js +77 -5
- package/dist/discord-artifacts.js +165 -0
- package/dist/discord-bot.js +2014 -0
- package/dist/discord-channel-runtime.js +133 -0
- package/dist/discord-command-surface.js +119 -0
- package/dist/discord-rate-limit.js +141 -0
- package/dist/index.js +16 -5
- package/dist/job-store.js +127 -0
- package/dist/metrics.js +41 -0
- package/dist/operations.js +176 -119
- package/dist/relay-external-activity-monitor.js +47 -6
- package/dist/relay-runtime.js +1003 -268
- package/dist/runtime-cache.js +57 -0
- package/dist/session-locks.js +10 -7
- package/dist/state-backend.js +3 -0
- package/dist/support-bundle.js +18 -1
- package/dist/telegram-access-commands.js +15 -2
- package/dist/telegram-access-middleware.js +16 -3
- package/dist/telegram-agent-commands.js +25 -0
- package/dist/telegram-artifact-commands.js +46 -0
- package/dist/telegram-diagnostics-command.js +5 -50
- package/dist/telegram-general-commands.js +2 -6
- package/dist/telegram-operational-commands.js +14 -6
- package/dist/telegram-queue-commands.js +74 -4
- package/dist/telegram-support-command.js +7 -0
- package/dist/telegram-update-commands.js +27 -0
- package/dist/user-management.js +208 -0
- package/dist/web-api-contract.js +9 -0
- package/dist/web-dashboard-access-routes.js +74 -1
- package/dist/web-dashboard-artifact-routes.js +3 -3
- package/dist/web-dashboard-assets.js +2 -0
- package/dist/web-dashboard-pages.js +97 -13
- package/dist/web-dashboard-runtime-routes.js +53 -8
- package/dist/web-dashboard-session-routes.js +27 -20
- package/dist/web-dashboard-ui.js +1 -0
- package/dist/web-dashboard.js +149 -6
- package/dist/web-state.js +33 -2
- package/dist/webui-assets/dashboard.css +75 -1
- package/dist/webui-assets/dashboard.js +358 -47
- package/package.json +3 -1
- package/plugins/nordrelay/.codex-plugin/plugin.json +1 -1
- package/plugins/nordrelay/scripts/nordrelay.mjs +468 -22
package/dist/operations.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { readFile, stat } from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
@@ -21,6 +21,8 @@ const CLAUDE_CODE_SDK_PACKAGE_NAME = "@anthropic-ai/claude-agent-sdk";
|
|
|
21
21
|
const DEFAULT_HOME = path.join(os.homedir(), ".nordrelay");
|
|
22
22
|
const SECRET_RE = /(bot|token|api[_-]?key|authorization|bearer|password|secret)(["'=: ]+)([^\s"',]+)/gi;
|
|
23
23
|
const DEFAULT_VERSION_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
24
|
+
const DEFAULT_CLI_VERSION_CACHE_TTL_MS = 60 * 1000;
|
|
25
|
+
const cliVersionCache = new Map();
|
|
24
26
|
export function getConnectorHome() {
|
|
25
27
|
return process.env.NORDRELAY_HOME || DEFAULT_HOME;
|
|
26
28
|
}
|
|
@@ -96,97 +98,86 @@ export async function getPackageVersion() {
|
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
export async function getVersionChecks(options = {}) {
|
|
99
|
-
const nordrelayVersion = await
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
const codexVersionLabel = codexCli.path
|
|
106
|
-
? detectCliVersion(codexCli.path)
|
|
107
|
-
: readInstalledPackageVersion(CODEX_PACKAGE_NAME) ?? "not installed";
|
|
108
|
-
const piVersionLabel = piCli.path
|
|
109
|
-
? detectCliVersion(piCli.path)
|
|
110
|
-
: readInstalledPackageVersion(PI_PACKAGE_NAME) ?? readInstalledPackageVersion(LEGACY_PI_PACKAGE_NAME) ?? "not installed";
|
|
111
|
-
const legacyPiPackageVersion = readInstalledPackageVersion(LEGACY_PI_PACKAGE_NAME);
|
|
112
|
-
const hermesVersionLabel = hermesCli.path ? detectCliVersion(hermesCli.path) : "not installed";
|
|
113
|
-
const openClawVersionLabel = openClawCli.path ? detectCliVersion(openClawCli.path) : "not installed";
|
|
114
|
-
const claudeCodeVersionLabel = claudeCodeCli.path
|
|
115
|
-
? detectCliVersion(claudeCodeCli.path)
|
|
116
|
-
: readInstalledPackageVersion(CLAUDE_CODE_SDK_PACKAGE_NAME) ?? "bundled";
|
|
117
|
-
const claudeCodePackageName = claudeCodeCli.path ? CLAUDE_CODE_PACKAGE_NAME : CLAUDE_CODE_SDK_PACKAGE_NAME;
|
|
118
|
-
return {
|
|
119
|
-
nordrelay: buildVersionCheck({
|
|
101
|
+
const [nordrelayVersion, cliVersions] = await Promise.all([
|
|
102
|
+
getPackageVersion(),
|
|
103
|
+
resolveAgentCliVersions(options),
|
|
104
|
+
]);
|
|
105
|
+
const [nordrelay, codex, pi, hermes, openclaw, claudeCode,] = await Promise.all([
|
|
106
|
+
buildVersionCheck({
|
|
120
107
|
label: "NordRelay",
|
|
121
108
|
packageName: PACKAGE_NAME,
|
|
122
109
|
installedLabel: nordrelayVersion,
|
|
123
110
|
installedVersion: extractVersion(nordrelayVersion),
|
|
124
111
|
}),
|
|
125
|
-
|
|
112
|
+
buildVersionCheck({
|
|
126
113
|
label: "Codex",
|
|
127
114
|
packageName: CODEX_PACKAGE_NAME,
|
|
128
|
-
installedLabel: codexVersionLabel,
|
|
129
|
-
installedVersion: extractVersion(codexVersionLabel),
|
|
130
|
-
notInstalled: codexVersionLabel === "not installed",
|
|
115
|
+
installedLabel: cliVersions.codexVersionLabel,
|
|
116
|
+
installedVersion: extractVersion(cliVersions.codexVersionLabel),
|
|
117
|
+
notInstalled: cliVersions.codexVersionLabel === "not installed",
|
|
131
118
|
}),
|
|
132
|
-
|
|
119
|
+
buildVersionCheck({
|
|
133
120
|
label: "Pi",
|
|
134
121
|
packageName: PI_PACKAGE_NAME,
|
|
135
|
-
installedLabel: piVersionLabel,
|
|
136
|
-
installedVersion: extractVersion(piVersionLabel),
|
|
137
|
-
notInstalled: piVersionLabel === "not installed",
|
|
138
|
-
detail: legacyPiPackageVersion ? `Legacy package ${LEGACY_PI_PACKAGE_NAME} is present; current package is ${PI_PACKAGE_NAME}.` : undefined,
|
|
122
|
+
installedLabel: cliVersions.piVersionLabel,
|
|
123
|
+
installedVersion: extractVersion(cliVersions.piVersionLabel),
|
|
124
|
+
notInstalled: cliVersions.piVersionLabel === "not installed",
|
|
125
|
+
detail: cliVersions.legacyPiPackageVersion ? `Legacy package ${LEGACY_PI_PACKAGE_NAME} is present; current package is ${PI_PACKAGE_NAME}.` : undefined,
|
|
139
126
|
}),
|
|
140
|
-
|
|
141
|
-
|
|
127
|
+
buildHermesVersionCheck(cliVersions.hermesVersionLabel),
|
|
128
|
+
buildVersionCheck({
|
|
142
129
|
label: "OpenClaw",
|
|
143
130
|
packageName: OPENCLAW_PACKAGE_NAME,
|
|
144
|
-
installedLabel: openClawVersionLabel,
|
|
145
|
-
installedVersion: extractVersion(openClawVersionLabel),
|
|
146
|
-
notInstalled: openClawVersionLabel === "not installed",
|
|
131
|
+
installedLabel: cliVersions.openClawVersionLabel,
|
|
132
|
+
installedVersion: extractVersion(cliVersions.openClawVersionLabel),
|
|
133
|
+
notInstalled: cliVersions.openClawVersionLabel === "not installed",
|
|
147
134
|
}),
|
|
148
|
-
|
|
135
|
+
buildVersionCheck({
|
|
149
136
|
label: "Claude Code",
|
|
150
|
-
packageName: claudeCodePackageName,
|
|
151
|
-
installedLabel: claudeCodeVersionLabel,
|
|
152
|
-
installedVersion: extractVersion(claudeCodeVersionLabel),
|
|
153
|
-
notInstalled: claudeCodeVersionLabel === "not installed",
|
|
137
|
+
packageName: cliVersions.claudeCodePackageName,
|
|
138
|
+
installedLabel: cliVersions.claudeCodeVersionLabel,
|
|
139
|
+
installedVersion: extractVersion(cliVersions.claudeCodeVersionLabel),
|
|
140
|
+
notInstalled: cliVersions.claudeCodeVersionLabel === "not installed",
|
|
154
141
|
}),
|
|
142
|
+
]);
|
|
143
|
+
return {
|
|
144
|
+
nordrelay,
|
|
145
|
+
codex,
|
|
146
|
+
pi,
|
|
147
|
+
hermes,
|
|
148
|
+
openclaw,
|
|
149
|
+
claudeCode,
|
|
155
150
|
};
|
|
156
151
|
}
|
|
157
152
|
export async function getConnectorHealth(options = {}) {
|
|
158
|
-
const rawState = await
|
|
159
|
-
|
|
153
|
+
const [rawState, version, cliVersions] = await Promise.all([
|
|
154
|
+
readConnectorState(),
|
|
155
|
+
getPackageVersion(),
|
|
156
|
+
resolveAgentCliVersions(options),
|
|
157
|
+
]);
|
|
160
158
|
const pidRunning = isProcessRunning(rawState.pid);
|
|
161
159
|
const appPidRunning = isProcessRunning(rawState.appPid);
|
|
162
160
|
const state = normalizeConnectorState(rawState, pidRunning, appPidRunning);
|
|
163
|
-
const codexCli = resolveCodexCli();
|
|
164
|
-
const piCli = resolvePiCli(process.env, options.piCliPath);
|
|
165
|
-
const hermesCli = resolveHermesCli(process.env, options.hermesCliPath);
|
|
166
|
-
const openClawCli = resolveOpenClawCli(process.env, options.openClawCliPath);
|
|
167
|
-
const claudeCodeCli = resolveClaudeCodeCli(process.env, options.claudeCodeCliPath);
|
|
168
161
|
return {
|
|
169
162
|
version,
|
|
170
163
|
state,
|
|
171
164
|
pidRunning,
|
|
172
165
|
appPidRunning,
|
|
173
|
-
codexCli: describeCodexCli(codexCli),
|
|
174
|
-
codexCliPath: codexCli.path ?? null,
|
|
175
|
-
codexCliVersion:
|
|
176
|
-
piCli: describePiCli(piCli),
|
|
177
|
-
piCliPath: piCli.path ?? null,
|
|
178
|
-
piCliVersion:
|
|
179
|
-
hermesCli: describeHermesCli(hermesCli),
|
|
180
|
-
hermesCliPath: hermesCli.path ?? null,
|
|
181
|
-
hermesCliVersion:
|
|
182
|
-
openClawCli: describeOpenClawCli(openClawCli),
|
|
183
|
-
openClawCliPath: openClawCli.path ?? null,
|
|
184
|
-
openClawCliVersion:
|
|
185
|
-
claudeCodeCli: describeClaudeCodeCli(claudeCodeCli),
|
|
186
|
-
claudeCodeCliPath: claudeCodeCli.path ?? null,
|
|
187
|
-
claudeCodeCliVersion:
|
|
188
|
-
? detectCliVersion(claudeCodeCli.path)
|
|
189
|
-
: readInstalledPackageVersion(CLAUDE_CODE_SDK_PACKAGE_NAME) ?? "bundled",
|
|
166
|
+
codexCli: describeCodexCli(cliVersions.codexCli),
|
|
167
|
+
codexCliPath: cliVersions.codexCli.path ?? null,
|
|
168
|
+
codexCliVersion: cliVersions.codexVersionLabel,
|
|
169
|
+
piCli: describePiCli(cliVersions.piCli),
|
|
170
|
+
piCliPath: cliVersions.piCli.path ?? null,
|
|
171
|
+
piCliVersion: cliVersions.piVersionLabel,
|
|
172
|
+
hermesCli: describeHermesCli(cliVersions.hermesCli),
|
|
173
|
+
hermesCliPath: cliVersions.hermesCli.path ?? null,
|
|
174
|
+
hermesCliVersion: cliVersions.hermesVersionLabel,
|
|
175
|
+
openClawCli: describeOpenClawCli(cliVersions.openClawCli),
|
|
176
|
+
openClawCliPath: cliVersions.openClawCli.path ?? null,
|
|
177
|
+
openClawCliVersion: cliVersions.openClawVersionLabel,
|
|
178
|
+
claudeCodeCli: describeClaudeCodeCli(cliVersions.claudeCodeCli),
|
|
179
|
+
claudeCodeCliPath: cliVersions.claudeCodeCli.path ?? null,
|
|
180
|
+
claudeCodeCliVersion: cliVersions.claudeCodeVersionLabel,
|
|
190
181
|
stateFile: getConnectorStatePath(),
|
|
191
182
|
logFile: getConnectorLogPath(),
|
|
192
183
|
databasePath: findLatestDatabase(),
|
|
@@ -208,23 +199,22 @@ export function spawnSelfUpdate() {
|
|
|
208
199
|
const script = getWrapperScriptPath();
|
|
209
200
|
const updateLog = getUpdateLogPath();
|
|
210
201
|
const method = detectSelfUpdateMethod(sourceRoot);
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
202
|
+
mkdirSync(path.dirname(updateLog), { recursive: true });
|
|
203
|
+
const child = spawn(process.execPath, [
|
|
204
|
+
script,
|
|
205
|
+
"update",
|
|
206
|
+
"--method",
|
|
207
|
+
method,
|
|
208
|
+
"--home",
|
|
209
|
+
getConnectorHome(),
|
|
210
|
+
"--keep-pending-updates",
|
|
211
|
+
], {
|
|
221
212
|
cwd: sourceRoot,
|
|
222
213
|
detached: true,
|
|
223
214
|
env: process.env,
|
|
224
|
-
stdio:
|
|
215
|
+
stdio: "ignore",
|
|
225
216
|
});
|
|
226
217
|
child.unref();
|
|
227
|
-
closeSync(logFd);
|
|
228
218
|
return {
|
|
229
219
|
logPath: updateLog,
|
|
230
220
|
method,
|
|
@@ -274,41 +264,62 @@ function normalizeConnectorState(state, pidRunning, appPidRunning) {
|
|
|
274
264
|
function redactSecrets(text) {
|
|
275
265
|
return text.replace(SECRET_RE, "$1$2[redacted]");
|
|
276
266
|
}
|
|
277
|
-
function
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
267
|
+
async function resolveAgentCliVersions(options = {}) {
|
|
268
|
+
const codexCli = resolveCodexCli();
|
|
269
|
+
const piCli = resolvePiCli(process.env, options.piCliPath);
|
|
270
|
+
const hermesCli = resolveHermesCli(process.env, options.hermesCliPath);
|
|
271
|
+
const openClawCli = resolveOpenClawCli(process.env, options.openClawCliPath);
|
|
272
|
+
const claudeCodeCli = resolveClaudeCodeCli(process.env, options.claudeCodeCliPath);
|
|
273
|
+
const legacyPiPackageVersion = readInstalledPackageVersion(LEGACY_PI_PACKAGE_NAME);
|
|
274
|
+
const [codexVersionLabel, piVersionLabel, hermesVersionLabel, openClawVersionLabel, claudeCodeVersionLabel,] = await Promise.all([
|
|
275
|
+
codexCli.path ? detectCliVersion(codexCli.path) : Promise.resolve(readInstalledPackageVersion(CODEX_PACKAGE_NAME) ?? "not installed"),
|
|
276
|
+
piCli.path ? detectCliVersion(piCli.path) : Promise.resolve(readInstalledPackageVersion(PI_PACKAGE_NAME) ?? legacyPiPackageVersion ?? "not installed"),
|
|
277
|
+
hermesCli.path ? detectCliVersion(hermesCli.path) : Promise.resolve("not installed"),
|
|
278
|
+
openClawCli.path ? detectCliVersion(openClawCli.path) : Promise.resolve("not installed"),
|
|
279
|
+
claudeCodeCli.path ? detectCliVersion(claudeCodeCli.path) : Promise.resolve(readInstalledPackageVersion(CLAUDE_CODE_SDK_PACKAGE_NAME) ?? "bundled"),
|
|
280
|
+
]);
|
|
281
|
+
return {
|
|
282
|
+
codexCli,
|
|
283
|
+
piCli,
|
|
284
|
+
hermesCli,
|
|
285
|
+
openClawCli,
|
|
286
|
+
claudeCodeCli,
|
|
287
|
+
codexVersionLabel,
|
|
288
|
+
piVersionLabel,
|
|
289
|
+
hermesVersionLabel,
|
|
290
|
+
openClawVersionLabel,
|
|
291
|
+
claudeCodeVersionLabel,
|
|
292
|
+
claudeCodePackageName: claudeCodeCli.path ? CLAUDE_CODE_PACKAGE_NAME : CLAUDE_CODE_SDK_PACKAGE_NAME,
|
|
293
|
+
legacyPiPackageVersion,
|
|
294
|
+
};
|
|
302
295
|
}
|
|
303
|
-
function detectCliVersion(commandPath) {
|
|
296
|
+
async function detectCliVersion(commandPath) {
|
|
304
297
|
if (!commandPath) {
|
|
305
298
|
return "not installed";
|
|
306
299
|
}
|
|
307
|
-
const
|
|
308
|
-
|
|
300
|
+
const ttlMs = parseCliVersionCacheTtlMs();
|
|
301
|
+
if (ttlMs > 0) {
|
|
302
|
+
const cached = cliVersionCache.get(commandPath);
|
|
303
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
304
|
+
if (cached.value !== undefined) {
|
|
305
|
+
return cached.value;
|
|
306
|
+
}
|
|
307
|
+
if (cached.promise) {
|
|
308
|
+
return cached.promise;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const promise = detectCliVersionUncached(commandPath);
|
|
312
|
+
cliVersionCache.set(commandPath, { promise, expiresAt: Date.now() + ttlMs });
|
|
313
|
+
const value = await promise;
|
|
314
|
+
cliVersionCache.set(commandPath, { value, expiresAt: Date.now() + ttlMs });
|
|
315
|
+
return value;
|
|
316
|
+
}
|
|
317
|
+
return detectCliVersionUncached(commandPath);
|
|
318
|
+
}
|
|
319
|
+
async function detectCliVersionUncached(commandPath) {
|
|
320
|
+
const result = await runCommand(commandPath, ["--version"], {
|
|
309
321
|
shell: isWindowsShellScript(commandPath),
|
|
310
322
|
timeout: 3000,
|
|
311
|
-
windowsHide: true,
|
|
312
323
|
});
|
|
313
324
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
314
325
|
if (result.error) {
|
|
@@ -319,9 +330,9 @@ function detectCliVersion(commandPath) {
|
|
|
319
330
|
}
|
|
320
331
|
return output || "unknown";
|
|
321
332
|
}
|
|
322
|
-
function buildHermesVersionCheck(installedLabel) {
|
|
333
|
+
async function buildHermesVersionCheck(installedLabel) {
|
|
323
334
|
if (installedLabel === "not installed") {
|
|
324
|
-
const latest = detectLatestNpmVersion(HERMES_PACKAGE_NAME);
|
|
335
|
+
const latest = await detectLatestNpmVersion(HERMES_PACKAGE_NAME);
|
|
325
336
|
return {
|
|
326
337
|
label: "Hermes",
|
|
327
338
|
packageName: HERMES_PACKAGE_NAME,
|
|
@@ -346,9 +357,9 @@ function buildHermesVersionCheck(installedLabel) {
|
|
|
346
357
|
detail: updateLine ?? (installedVersion ? undefined : "Could not parse Hermes version or update status"),
|
|
347
358
|
};
|
|
348
359
|
}
|
|
349
|
-
function buildVersionCheck(options) {
|
|
360
|
+
async function buildVersionCheck(options) {
|
|
350
361
|
if (options.notInstalled) {
|
|
351
|
-
const latest = options.skipLatest ? { version: null, error: undefined } : detectLatestNpmVersion(options.packageName);
|
|
362
|
+
const latest = options.skipLatest ? { version: null, error: undefined } : await detectLatestNpmVersion(options.packageName);
|
|
352
363
|
return {
|
|
353
364
|
label: options.label,
|
|
354
365
|
packageName: options.packageName,
|
|
@@ -370,7 +381,7 @@ function buildVersionCheck(options) {
|
|
|
370
381
|
detail: options.detail ?? "Latest-version lookup is not available for this package source",
|
|
371
382
|
};
|
|
372
383
|
}
|
|
373
|
-
const latest = detectLatestNpmVersion(options.packageName);
|
|
384
|
+
const latest = await detectLatestNpmVersion(options.packageName);
|
|
374
385
|
if (!options.installedVersion || !latest.version) {
|
|
375
386
|
return {
|
|
376
387
|
label: options.label,
|
|
@@ -392,7 +403,7 @@ function buildVersionCheck(options) {
|
|
|
392
403
|
detail: [options.detail, latest.error].filter(Boolean).join(" ") || undefined,
|
|
393
404
|
};
|
|
394
405
|
}
|
|
395
|
-
function detectLatestNpmVersion(packageName) {
|
|
406
|
+
async function detectLatestNpmVersion(packageName) {
|
|
396
407
|
const cached = readVersionCache(packageName);
|
|
397
408
|
if (cached) {
|
|
398
409
|
return cached;
|
|
@@ -401,11 +412,9 @@ function detectLatestNpmVersion(packageName) {
|
|
|
401
412
|
if (!npm) {
|
|
402
413
|
return { version: null, error: "npm was not found on PATH; latest-version lookup is unavailable" };
|
|
403
414
|
}
|
|
404
|
-
const result =
|
|
405
|
-
encoding: "utf8",
|
|
415
|
+
const result = await runCommand(npm.command, [...npm.argsPrefix, "view", packageName, "version", "--registry=https://registry.npmjs.org"], {
|
|
406
416
|
shell: npm.shell,
|
|
407
417
|
timeout: 5000,
|
|
408
|
-
windowsHide: true,
|
|
409
418
|
});
|
|
410
419
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
411
420
|
if (result.error) {
|
|
@@ -418,6 +427,49 @@ function detectLatestNpmVersion(packageName) {
|
|
|
418
427
|
writeVersionCache(packageName, resolved.version);
|
|
419
428
|
return resolved;
|
|
420
429
|
}
|
|
430
|
+
function runCommand(command, args, options) {
|
|
431
|
+
return new Promise((resolve) => {
|
|
432
|
+
const useShell = Boolean(options.shell);
|
|
433
|
+
execFile(useShell ? formatShellCommand(command, args) : command, useShell ? [] : args, {
|
|
434
|
+
encoding: "utf8",
|
|
435
|
+
shell: useShell,
|
|
436
|
+
timeout: options.timeout,
|
|
437
|
+
windowsHide: true,
|
|
438
|
+
env: process.env,
|
|
439
|
+
maxBuffer: 1024 * 1024,
|
|
440
|
+
}, (error, stdout, stderr) => {
|
|
441
|
+
const enriched = error;
|
|
442
|
+
resolve({
|
|
443
|
+
stdout: typeof stdout === "string" ? stdout : "",
|
|
444
|
+
stderr: typeof stderr === "string" ? stderr : "",
|
|
445
|
+
status: typeof enriched?.code === "number" ? enriched.code : error ? 1 : 0,
|
|
446
|
+
signal: enriched?.signal,
|
|
447
|
+
error: enriched ?? undefined,
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
function formatShellCommand(command, args) {
|
|
453
|
+
return [command, ...args].map(quoteShellArg).join(" ");
|
|
454
|
+
}
|
|
455
|
+
function quoteShellArg(value) {
|
|
456
|
+
if (process.platform === "win32") {
|
|
457
|
+
return quoteWindowsCmdArg(value);
|
|
458
|
+
}
|
|
459
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
460
|
+
}
|
|
461
|
+
function quoteWindowsCmdArg(value) {
|
|
462
|
+
if (value.length === 0) {
|
|
463
|
+
return "\"\"";
|
|
464
|
+
}
|
|
465
|
+
if (!/[\s"&|<>()^%]/.test(value)) {
|
|
466
|
+
return value;
|
|
467
|
+
}
|
|
468
|
+
return `"${value
|
|
469
|
+
.replace(/%/g, "%%")
|
|
470
|
+
.replace(/(\\*)"/g, '$1$1\\"')
|
|
471
|
+
.replace(/(\\+)$/g, "$1$1")}"`;
|
|
472
|
+
}
|
|
421
473
|
export function resolveNpmSpawnCommand(env = process.env) {
|
|
422
474
|
const npmExecPath = env.npm_execpath?.trim();
|
|
423
475
|
if (npmExecPath && existsSync(npmExecPath)) {
|
|
@@ -512,6 +564,14 @@ function parseVersionCacheTtlMs() {
|
|
|
512
564
|
const parsed = Number(raw);
|
|
513
565
|
return Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed)) : DEFAULT_VERSION_CACHE_TTL_MS;
|
|
514
566
|
}
|
|
567
|
+
function parseCliVersionCacheTtlMs() {
|
|
568
|
+
const raw = process.env.NORDRELAY_CLI_VERSION_CACHE_TTL_MS;
|
|
569
|
+
if (!raw) {
|
|
570
|
+
return DEFAULT_CLI_VERSION_CACHE_TTL_MS;
|
|
571
|
+
}
|
|
572
|
+
const parsed = Number(raw);
|
|
573
|
+
return Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed)) : DEFAULT_CLI_VERSION_CACHE_TTL_MS;
|
|
574
|
+
}
|
|
515
575
|
function readInstalledPackageVersion(packageName) {
|
|
516
576
|
try {
|
|
517
577
|
const packagePath = path.join(getSourceRoot(), "node_modules", ...packageName.split("/"), "package.json");
|
|
@@ -543,9 +603,6 @@ function compareVersions(left, right) {
|
|
|
543
603
|
function parseVersionParts(value) {
|
|
544
604
|
return value.split(/[.-]/).slice(0, 3).map((part) => Number.parseInt(part, 10) || 0);
|
|
545
605
|
}
|
|
546
|
-
function shellQuote(value) {
|
|
547
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
548
|
-
}
|
|
549
606
|
function formatLogLine(line) {
|
|
550
607
|
const trimmed = line.trim();
|
|
551
608
|
if (!trimmed) {
|
|
@@ -2,6 +2,10 @@ import {} from "./agent.js";
|
|
|
2
2
|
import { getExternalSnapshotForSession } from "./agent-activity.js";
|
|
3
3
|
import { friendlyErrorText } from "./error-messages.js";
|
|
4
4
|
import {} from "./web-state.js";
|
|
5
|
+
const CLI_ACTIVITY_ACTOR = {
|
|
6
|
+
channel: "cli",
|
|
7
|
+
label: "CLI",
|
|
8
|
+
};
|
|
5
9
|
export class RelayExternalActivityMonitor {
|
|
6
10
|
options;
|
|
7
11
|
mirror = null;
|
|
@@ -16,7 +20,9 @@ export class RelayExternalActivityMonitor {
|
|
|
16
20
|
if (!this.mirror) {
|
|
17
21
|
return null;
|
|
18
22
|
}
|
|
19
|
-
const startedAt = this.mirror.startedAt
|
|
23
|
+
const startedAt = this.mirror.startedAt instanceof Date
|
|
24
|
+
? this.mirror.startedAt.toISOString()
|
|
25
|
+
: this.mirror.startedAt ?? new Date().toISOString();
|
|
20
26
|
const startedMs = new Date(startedAt).getTime();
|
|
21
27
|
return {
|
|
22
28
|
id: this.mirror.turnId ?? "cli",
|
|
@@ -75,7 +81,7 @@ export class RelayExternalActivityMonitor {
|
|
|
75
81
|
startedAt: snapshot.activity.startedAt?.toISOString() ?? null,
|
|
76
82
|
};
|
|
77
83
|
if (snapshot.activity.active) {
|
|
78
|
-
this.startExternalTurn(snapshot);
|
|
84
|
+
this.startExternalTurn(snapshot, info);
|
|
79
85
|
}
|
|
80
86
|
return;
|
|
81
87
|
}
|
|
@@ -85,9 +91,9 @@ export class RelayExternalActivityMonitor {
|
|
|
85
91
|
mirror.turnId = snapshot.activity.turnId;
|
|
86
92
|
mirror.startedAt = snapshot.activity.startedAt?.toISOString() ?? null;
|
|
87
93
|
mirror.latestAgentLine = undefined;
|
|
88
|
-
this.startExternalTurn(snapshot);
|
|
94
|
+
this.startExternalTurn(snapshot, info);
|
|
89
95
|
}
|
|
90
|
-
this.broadcastExternalEvents(snapshot, snapshot.events.filter((event) => event.lineNumber > mirror.lastLine));
|
|
96
|
+
this.broadcastExternalEvents(snapshot, snapshot.events.filter((event) => event.lineNumber > mirror.lastLine), info);
|
|
91
97
|
mirror.lastLine = Math.max(mirror.lastLine, snapshot.lineCount);
|
|
92
98
|
mirror.latestStatus = externalStatusLine(snapshot, this.options.queueLength());
|
|
93
99
|
this.options.broadcastStatus(mirror.latestStatus, "info");
|
|
@@ -122,6 +128,7 @@ export class RelayExternalActivityMonitor {
|
|
|
122
128
|
threadId: snapshot.threadId,
|
|
123
129
|
workspace: info.workspace,
|
|
124
130
|
agentId: info.agentId,
|
|
131
|
+
actor: CLI_ACTIVITY_ACTOR,
|
|
125
132
|
prompt: snapshot.latestUserMessage ?? undefined,
|
|
126
133
|
detail: `${snapshot.agentLabel} CLI task ${terminalEvent.status ?? "finished"}.`,
|
|
127
134
|
durationMs: durationFromDates(externalStartedAt, terminalEvent.timestamp),
|
|
@@ -136,7 +143,7 @@ export class RelayExternalActivityMonitor {
|
|
|
136
143
|
}
|
|
137
144
|
mirror.lastLine = Math.max(mirror.lastLine, snapshot.lineCount);
|
|
138
145
|
}
|
|
139
|
-
startExternalTurn(snapshot) {
|
|
146
|
+
startExternalTurn(snapshot, info) {
|
|
140
147
|
const prompt = snapshot.latestUserMessage ?? `${snapshot.agentLabel} CLI task`;
|
|
141
148
|
this.options.chatStore.append({
|
|
142
149
|
threadId: snapshot.threadId,
|
|
@@ -158,11 +165,14 @@ export class RelayExternalActivityMonitor {
|
|
|
158
165
|
status: "running",
|
|
159
166
|
type: "cli_turn_started",
|
|
160
167
|
threadId: snapshot.threadId,
|
|
168
|
+
workspace: info.workspace,
|
|
169
|
+
agentId: info.agentId,
|
|
170
|
+
actor: CLI_ACTIVITY_ACTOR,
|
|
161
171
|
prompt,
|
|
162
172
|
detail: `${snapshot.sourceLabel}: ${snapshot.sourcePath}`,
|
|
163
173
|
});
|
|
164
174
|
}
|
|
165
|
-
broadcastExternalEvents(snapshot, events) {
|
|
175
|
+
broadcastExternalEvents(snapshot, events, info) {
|
|
166
176
|
for (const event of events) {
|
|
167
177
|
if (event.kind === "tool" && event.status === "started") {
|
|
168
178
|
this.options.broadcast({
|
|
@@ -176,6 +186,9 @@ export class RelayExternalActivityMonitor {
|
|
|
176
186
|
status: "running",
|
|
177
187
|
type: "cli_tool_started",
|
|
178
188
|
threadId: snapshot.threadId,
|
|
189
|
+
workspace: info.workspace,
|
|
190
|
+
agentId: info.agentId,
|
|
191
|
+
actor: CLI_ACTIVITY_ACTOR,
|
|
179
192
|
detail: event.toolName ?? "tool",
|
|
180
193
|
});
|
|
181
194
|
}
|
|
@@ -186,6 +199,34 @@ export class RelayExternalActivityMonitor {
|
|
|
186
199
|
toolCallId: `cli-${event.lineNumber}`,
|
|
187
200
|
isError: false,
|
|
188
201
|
});
|
|
202
|
+
this.options.appendActivity({
|
|
203
|
+
source: "cli",
|
|
204
|
+
status: "completed",
|
|
205
|
+
type: "cli_tool_completed",
|
|
206
|
+
threadId: snapshot.threadId,
|
|
207
|
+
workspace: info.workspace,
|
|
208
|
+
agentId: info.agentId,
|
|
209
|
+
actor: CLI_ACTIVITY_ACTOR,
|
|
210
|
+
detail: event.toolName ?? "tool",
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (event.kind === "tool" && event.status === "failed") {
|
|
214
|
+
this.options.broadcast({
|
|
215
|
+
type: "tool_end",
|
|
216
|
+
id: snapshot.activity.turnId ?? "cli",
|
|
217
|
+
toolCallId: `cli-${event.lineNumber}`,
|
|
218
|
+
isError: true,
|
|
219
|
+
});
|
|
220
|
+
this.options.appendActivity({
|
|
221
|
+
source: "cli",
|
|
222
|
+
status: "failed",
|
|
223
|
+
type: "cli_tool_failed",
|
|
224
|
+
threadId: snapshot.threadId,
|
|
225
|
+
workspace: info.workspace,
|
|
226
|
+
agentId: info.agentId,
|
|
227
|
+
actor: CLI_ACTIVITY_ACTOR,
|
|
228
|
+
detail: event.toolName ?? "tool",
|
|
229
|
+
});
|
|
189
230
|
}
|
|
190
231
|
}
|
|
191
232
|
}
|