@nordbyte/nordrelay 0.5.0 → 0.5.2
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 +2 -0
- package/README.md +23 -14
- package/dist/access-control.js +2 -0
- package/dist/agent-updates.js +61 -10
- package/dist/bot-ui.js +1 -0
- package/dist/bot.js +142 -1065
- package/dist/channel-actions.js +8 -8
- package/dist/codex-cli.js +1 -1
- package/dist/config-metadata.js +2 -0
- package/dist/operations.js +233 -122
- package/dist/relay-artifact-service.js +126 -0
- package/dist/relay-external-activity-monitor.js +216 -0
- package/dist/relay-queue-service.js +66 -0
- package/dist/relay-runtime-types.js +1 -0
- package/dist/relay-runtime.js +119 -371
- package/dist/state-backend.js +3 -0
- package/dist/support-bundle.js +221 -0
- package/dist/telegram-agent-commands.js +212 -0
- package/dist/telegram-artifact-commands.js +139 -0
- package/dist/telegram-command-menu.js +1 -0
- package/dist/telegram-command-types.js +1 -0
- package/dist/telegram-diagnostics-command.js +102 -0
- package/dist/telegram-general-commands.js +52 -0
- package/dist/telegram-operational-commands.js +153 -0
- package/dist/telegram-preference-commands.js +198 -0
- package/dist/telegram-queue-commands.js +278 -0
- package/dist/telegram-support-command.js +53 -0
- package/dist/telegram-update-commands.js +6 -1
- package/dist/web-api-contract.js +79 -31
- package/dist/web-api-types.js +1 -0
- package/dist/web-dashboard-access-routes.js +163 -0
- package/dist/web-dashboard-artifact-routes.js +65 -0
- package/dist/web-dashboard-assets.js +2 -0
- package/dist/web-dashboard-http.js +143 -0
- package/dist/web-dashboard-pages.js +257 -0
- package/dist/web-dashboard-runtime-routes.js +92 -0
- package/dist/web-dashboard-session-routes.js +209 -0
- package/dist/web-dashboard.js +44 -882
- package/dist/webui-assets/dashboard.css +74 -4
- package/dist/webui-assets/dashboard.js +163 -24
- package/dist/zip-writer.js +83 -0
- package/package.json +10 -4
- package/plugins/nordrelay/.codex-plugin/plugin.json +1 -1
- package/plugins/nordrelay/scripts/nordrelay.mjs +258 -5
package/dist/channel-actions.js
CHANGED
|
@@ -52,13 +52,13 @@ export function renderAgentUpdatePickerAction(descriptors) {
|
|
|
52
52
|
"Agent updates:",
|
|
53
53
|
...available.map((descriptor) => `${descriptor.label}: /update ${descriptor.id}`),
|
|
54
54
|
"",
|
|
55
|
-
"Use /update jobs to list running and recent agent updates.",
|
|
55
|
+
"Use /update install <agent> for missing CLIs and /update jobs to list running and recent agent updates.",
|
|
56
56
|
].join("\n"),
|
|
57
57
|
html: [
|
|
58
58
|
"<b>Agent updates:</b>",
|
|
59
59
|
...available.map((descriptor) => `<b>${escapeHTML(descriptor.label)}:</b> <code>/update ${escapeHTML(descriptor.id)}</code>`),
|
|
60
60
|
"",
|
|
61
|
-
"Use <code>/update jobs</code> to list running and recent agent updates.",
|
|
61
|
+
"Use <code>/update install <agent></code> for missing CLIs and <code>/update jobs</code> to list running and recent agent updates.",
|
|
62
62
|
].join("\n"),
|
|
63
63
|
buttons,
|
|
64
64
|
};
|
|
@@ -86,13 +86,13 @@ export function renderAgentUpdateJobsAction(jobs) {
|
|
|
86
86
|
return {
|
|
87
87
|
plain: [
|
|
88
88
|
"Agent update jobs:",
|
|
89
|
-
...limited.map((job) => `${job.id}: ${job.agentLabel} · ${job.status} · ${formatLocalDateTime(new Date(job.updatedAt))}`),
|
|
89
|
+
...limited.map((job) => `${job.id}: ${job.agentLabel} ${job.operation ?? "update"} · ${job.status} · ${formatLocalDateTime(new Date(job.updatedAt))}`),
|
|
90
90
|
"",
|
|
91
91
|
"Use /update log <id>, /update cancel <id>, or /update input <id> <text>.",
|
|
92
92
|
].join("\n"),
|
|
93
93
|
html: [
|
|
94
94
|
"<b>Agent update jobs:</b>",
|
|
95
|
-
...limited.map((job) => `<code>${escapeHTML(job.id)}</code> ${escapeHTML(job.agentLabel)} · <b>${escapeHTML(job.status)}</b> · <code>${escapeHTML(formatLocalDateTime(new Date(job.updatedAt)))}</code>`),
|
|
95
|
+
...limited.map((job) => `<code>${escapeHTML(job.id)}</code> ${escapeHTML(job.agentLabel)} ${escapeHTML(job.operation ?? "update")} · <b>${escapeHTML(job.status)}</b> · <code>${escapeHTML(formatLocalDateTime(new Date(job.updatedAt)))}</code>`),
|
|
96
96
|
"",
|
|
97
97
|
"Use <code>/update log <id></code>, <code>/update cancel <id></code>, or <code>/update input <id> <text></code>.",
|
|
98
98
|
].join("\n"),
|
|
@@ -106,7 +106,7 @@ export function renderAgentUpdateJobAction(job) {
|
|
|
106
106
|
const tail = trimLine(job.outputTail || "(waiting for output)", 1200);
|
|
107
107
|
return {
|
|
108
108
|
plain: [
|
|
109
|
-
`${job.agentLabel} update ${job.status}.`,
|
|
109
|
+
`${job.agentLabel} ${job.operation ?? "update"} ${job.status}.`,
|
|
110
110
|
`ID: ${job.id}`,
|
|
111
111
|
`Method: ${job.method}`,
|
|
112
112
|
`Command: ${command}`,
|
|
@@ -120,7 +120,7 @@ export function renderAgentUpdateJobAction(job) {
|
|
|
120
120
|
tail,
|
|
121
121
|
].filter(Boolean).join("\n"),
|
|
122
122
|
html: [
|
|
123
|
-
`<b>${escapeHTML(job.agentLabel)} update ${escapeHTML(job.status)}.</b>`,
|
|
123
|
+
`<b>${escapeHTML(job.agentLabel)} ${escapeHTML(job.operation ?? "update")} ${escapeHTML(job.status)}.</b>`,
|
|
124
124
|
`<b>ID:</b> <code>${escapeHTML(job.id)}</code>`,
|
|
125
125
|
`<b>Method:</b> <code>${escapeHTML(job.method)}</code>`,
|
|
126
126
|
`<b>Command:</b> <code>${escapeHTML(command)}</code>`,
|
|
@@ -145,7 +145,7 @@ export function renderAgentUpdateLogAction(result) {
|
|
|
145
145
|
const tail = trimLine(result.plain || "(empty)", 3000);
|
|
146
146
|
return {
|
|
147
147
|
plain: [
|
|
148
|
-
`${result.job.agentLabel} update log`,
|
|
148
|
+
`${result.job.agentLabel} ${result.job.operation ?? "update"} log`,
|
|
149
149
|
`ID: ${result.job.id}`,
|
|
150
150
|
`Status: ${result.job.status}`,
|
|
151
151
|
`File: ${result.job.logPath}`,
|
|
@@ -153,7 +153,7 @@ export function renderAgentUpdateLogAction(result) {
|
|
|
153
153
|
tail,
|
|
154
154
|
].join("\n"),
|
|
155
155
|
html: [
|
|
156
|
-
`<b>${escapeHTML(result.job.agentLabel)} update log</b>`,
|
|
156
|
+
`<b>${escapeHTML(result.job.agentLabel)} ${escapeHTML(result.job.operation ?? "update")} log</b>`,
|
|
157
157
|
`<b>ID:</b> <code>${escapeHTML(result.job.id)}</code>`,
|
|
158
158
|
`<b>Status:</b> <code>${escapeHTML(result.job.status)}</code>`,
|
|
159
159
|
`<b>File:</b> <code>${escapeHTML(result.job.logPath)}</code>`,
|
package/dist/codex-cli.js
CHANGED
|
@@ -26,7 +26,7 @@ export function findExecutableOnPath(command, pathValue) {
|
|
|
26
26
|
return undefined;
|
|
27
27
|
}
|
|
28
28
|
const extensions = process.platform === "win32"
|
|
29
|
-
? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";")
|
|
29
|
+
? ["", ...(process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";")]
|
|
30
30
|
: [""];
|
|
31
31
|
for (const rawDirectory of pathValue.split(path.delimiter)) {
|
|
32
32
|
const directory = rawDirectory.trim();
|
package/dist/config-metadata.js
CHANGED
|
@@ -88,6 +88,7 @@ export const SETTING_DEFINITIONS = [
|
|
|
88
88
|
setting("NORDRELAY_AUDIT_MAX_EVENTS", "Audit max events", "Workspace", "number", "Retained audit events.", true),
|
|
89
89
|
setting("NORDRELAY_SESSION_LOCK_TTL_MS", "Session lock TTL", "Workspace", "number", "Write-lock TTL.", true),
|
|
90
90
|
setting("NORDRELAY_VERSION_CACHE_TTL_MS", "Version cache TTL", "Workspace", "number", "NPM version cache TTL.", true),
|
|
91
|
+
setting("NORDRELAY_CLI_VERSION_CACHE_TTL_MS", "CLI version cache TTL", "Workspace", "number", "Installed agent CLI version cache TTL.", true),
|
|
91
92
|
setting("OPENAI_API_KEY", "OpenAI API key", "Voice", "secret", "Whisper fallback API key.", true),
|
|
92
93
|
setting("VOICE_PREFERRED_BACKEND", "Voice backend", "Voice", "string", "auto, parakeet, faster-whisper, or openai.", false, ["auto", "parakeet", "faster-whisper", "openai"]),
|
|
93
94
|
setting("VOICE_DEFAULT_LANGUAGE", "Voice language", "Voice", "string", "Default transcription language.", false),
|
|
@@ -180,6 +181,7 @@ const EXAMPLE_VALUES = {
|
|
|
180
181
|
"NORDRELAY_AUDIT_MAX_EVENTS": "1000",
|
|
181
182
|
"NORDRELAY_SESSION_LOCK_TTL_MS": "1800000",
|
|
182
183
|
"NORDRELAY_VERSION_CACHE_TTL_MS": "3600000",
|
|
184
|
+
"NORDRELAY_CLI_VERSION_CACHE_TTL_MS": "60000",
|
|
183
185
|
"NORDRELAY_DASHBOARD_HOST": "127.0.0.1",
|
|
184
186
|
"NORDRELAY_DASHBOARD_PORT": "31878",
|
|
185
187
|
"NORDRELAY_ENV_FILE": "",
|
package/dist/operations.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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";
|
|
6
|
-
import { describeCodexCli, resolveCodexCli } from "./codex-cli.js";
|
|
6
|
+
import { describeCodexCli, findExecutableOnPath, resolveCodexCli } from "./codex-cli.js";
|
|
7
7
|
import { findLatestDatabase } from "./codex-state.js";
|
|
8
8
|
import { describeClaudeCodeCli, resolveClaudeCodeCli } from "./claude-code-cli.js";
|
|
9
9
|
import { describeHermesCli, resolveHermesCli } from "./hermes-cli.js";
|
|
@@ -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,40 +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"], {
|
|
321
|
+
shell: isWindowsShellScript(commandPath),
|
|
309
322
|
timeout: 3000,
|
|
310
|
-
windowsHide: true,
|
|
311
323
|
});
|
|
312
324
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
313
325
|
if (result.error) {
|
|
@@ -318,15 +330,17 @@ function detectCliVersion(commandPath) {
|
|
|
318
330
|
}
|
|
319
331
|
return output || "unknown";
|
|
320
332
|
}
|
|
321
|
-
function buildHermesVersionCheck(installedLabel) {
|
|
333
|
+
async function buildHermesVersionCheck(installedLabel) {
|
|
322
334
|
if (installedLabel === "not installed") {
|
|
335
|
+
const latest = await detectLatestNpmVersion(HERMES_PACKAGE_NAME);
|
|
323
336
|
return {
|
|
324
337
|
label: "Hermes",
|
|
325
338
|
packageName: HERMES_PACKAGE_NAME,
|
|
326
339
|
installedLabel: "not installed",
|
|
327
340
|
installedVersion: null,
|
|
328
|
-
latestVersion:
|
|
341
|
+
latestVersion: latest.version,
|
|
329
342
|
status: "not-installed",
|
|
343
|
+
detail: latest.error,
|
|
330
344
|
};
|
|
331
345
|
}
|
|
332
346
|
const lines = installedLabel.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
@@ -343,16 +357,17 @@ function buildHermesVersionCheck(installedLabel) {
|
|
|
343
357
|
detail: updateLine ?? (installedVersion ? undefined : "Could not parse Hermes version or update status"),
|
|
344
358
|
};
|
|
345
359
|
}
|
|
346
|
-
function buildVersionCheck(options) {
|
|
360
|
+
async function buildVersionCheck(options) {
|
|
347
361
|
if (options.notInstalled) {
|
|
362
|
+
const latest = options.skipLatest ? { version: null, error: undefined } : await detectLatestNpmVersion(options.packageName);
|
|
348
363
|
return {
|
|
349
364
|
label: options.label,
|
|
350
365
|
packageName: options.packageName,
|
|
351
366
|
installedLabel: "not installed",
|
|
352
367
|
installedVersion: null,
|
|
353
|
-
latestVersion:
|
|
368
|
+
latestVersion: latest.version,
|
|
354
369
|
status: "not-installed",
|
|
355
|
-
detail: options.detail,
|
|
370
|
+
detail: [options.detail, latest.error].filter(Boolean).join(" ") || undefined,
|
|
356
371
|
};
|
|
357
372
|
}
|
|
358
373
|
if (options.skipLatest) {
|
|
@@ -366,7 +381,7 @@ function buildVersionCheck(options) {
|
|
|
366
381
|
detail: options.detail ?? "Latest-version lookup is not available for this package source",
|
|
367
382
|
};
|
|
368
383
|
}
|
|
369
|
-
const latest = detectLatestNpmVersion(options.packageName);
|
|
384
|
+
const latest = await detectLatestNpmVersion(options.packageName);
|
|
370
385
|
if (!options.installedVersion || !latest.version) {
|
|
371
386
|
return {
|
|
372
387
|
label: options.label,
|
|
@@ -388,19 +403,22 @@ function buildVersionCheck(options) {
|
|
|
388
403
|
detail: [options.detail, latest.error].filter(Boolean).join(" ") || undefined,
|
|
389
404
|
};
|
|
390
405
|
}
|
|
391
|
-
function detectLatestNpmVersion(packageName) {
|
|
406
|
+
async function detectLatestNpmVersion(packageName) {
|
|
392
407
|
const cached = readVersionCache(packageName);
|
|
393
408
|
if (cached) {
|
|
394
409
|
return cached;
|
|
395
410
|
}
|
|
396
|
-
const
|
|
397
|
-
|
|
411
|
+
const npm = resolveNpmSpawnCommand();
|
|
412
|
+
if (!npm) {
|
|
413
|
+
return { version: null, error: "npm was not found on PATH; latest-version lookup is unavailable" };
|
|
414
|
+
}
|
|
415
|
+
const result = await runCommand(npm.command, [...npm.argsPrefix, "view", packageName, "version", "--registry=https://registry.npmjs.org"], {
|
|
416
|
+
shell: npm.shell,
|
|
398
417
|
timeout: 5000,
|
|
399
|
-
windowsHide: true,
|
|
400
418
|
});
|
|
401
419
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
402
420
|
if (result.error) {
|
|
403
|
-
return { version: null, error: result.error.message };
|
|
421
|
+
return { version: null, error: `${npm.display}: ${result.error.message}` };
|
|
404
422
|
}
|
|
405
423
|
if (result.status !== 0) {
|
|
406
424
|
return { version: null, error: output || `npm exited ${result.status ?? "unknown"}` };
|
|
@@ -409,6 +427,94 @@ function detectLatestNpmVersion(packageName) {
|
|
|
409
427
|
writeVersionCache(packageName, resolved.version);
|
|
410
428
|
return resolved;
|
|
411
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
|
+
}
|
|
473
|
+
export function resolveNpmSpawnCommand(env = process.env) {
|
|
474
|
+
const npmExecPath = env.npm_execpath?.trim();
|
|
475
|
+
if (npmExecPath && existsSync(npmExecPath)) {
|
|
476
|
+
return {
|
|
477
|
+
command: process.execPath,
|
|
478
|
+
argsPrefix: [npmExecPath],
|
|
479
|
+
display: `${process.execPath} ${npmExecPath}`,
|
|
480
|
+
shell: false,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
const pathMatch = findExecutableOnPath("npm", env.PATH);
|
|
484
|
+
if (pathMatch) {
|
|
485
|
+
return {
|
|
486
|
+
command: pathMatch,
|
|
487
|
+
argsPrefix: [],
|
|
488
|
+
display: pathMatch,
|
|
489
|
+
shell: isWindowsShellScript(pathMatch),
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
for (const candidate of commonNpmCandidates(env)) {
|
|
493
|
+
if (!existsSync(candidate)) {
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
command: candidate,
|
|
498
|
+
argsPrefix: [],
|
|
499
|
+
display: candidate,
|
|
500
|
+
shell: isWindowsShellScript(candidate),
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
function commonNpmCandidates(env) {
|
|
506
|
+
const names = process.platform === "win32" ? ["npm.cmd", "npm.bat", "npm"] : ["npm"];
|
|
507
|
+
const directories = [
|
|
508
|
+
path.dirname(process.execPath),
|
|
509
|
+
env.APPDATA ? path.join(env.APPDATA, "npm") : undefined,
|
|
510
|
+
env.ProgramFiles ? path.join(env.ProgramFiles, "nodejs") : undefined,
|
|
511
|
+
env["ProgramFiles(x86)"] ? path.join(env["ProgramFiles(x86)"], "nodejs") : undefined,
|
|
512
|
+
].filter((value) => Boolean(value));
|
|
513
|
+
return directories.flatMap((directory) => names.map((name) => path.join(directory, name)));
|
|
514
|
+
}
|
|
515
|
+
function isWindowsShellScript(filePath) {
|
|
516
|
+
return process.platform === "win32" && /\.(?:cmd|bat)$/i.test(filePath);
|
|
517
|
+
}
|
|
412
518
|
function readVersionCache(packageName) {
|
|
413
519
|
const ttlMs = parseVersionCacheTtlMs();
|
|
414
520
|
if (ttlMs <= 0) {
|
|
@@ -458,6 +564,14 @@ function parseVersionCacheTtlMs() {
|
|
|
458
564
|
const parsed = Number(raw);
|
|
459
565
|
return Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed)) : DEFAULT_VERSION_CACHE_TTL_MS;
|
|
460
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
|
+
}
|
|
461
575
|
function readInstalledPackageVersion(packageName) {
|
|
462
576
|
try {
|
|
463
577
|
const packagePath = path.join(getSourceRoot(), "node_modules", ...packageName.split("/"), "package.json");
|
|
@@ -489,9 +603,6 @@ function compareVersions(left, right) {
|
|
|
489
603
|
function parseVersionParts(value) {
|
|
490
604
|
return value.split(/[.-]/).slice(0, 3).map((part) => Number.parseInt(part, 10) || 0);
|
|
491
605
|
}
|
|
492
|
-
function shellQuote(value) {
|
|
493
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
494
|
-
}
|
|
495
606
|
function formatLogLine(line) {
|
|
496
607
|
const trimmed = line.trim();
|
|
497
608
|
if (!trimmed) {
|