om-memory-system 4.4.0-next.67 → 4.4.0-next.70
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 +8 -4
- package/bin/omms-launch.mjs +198 -0
- package/dist/adapters/claude-code/hook-client.d.ts +2 -0
- package/dist/adapters/claude-code/hook-client.js +27 -2
- package/dist/adapters/claude-code/hook-command.d.ts +2 -1
- package/dist/adapters/claude-code/hook-command.js +11 -0
- package/dist/adapters/claude-code/status.d.ts +25 -0
- package/dist/adapters/claude-code/status.js +33 -0
- package/dist/adapters/opencode/tui-status.d.ts +0 -4
- package/dist/adapters/opencode/tui-status.js +1 -23
- package/dist/adapters/pi/extension.js +18 -8
- package/dist/cli/index.js +5 -0
- package/dist/cli/web-command.js +1 -59
- package/dist/index.js +15 -2
- package/dist/services/global-version.d.ts +11 -20
- package/dist/services/global-version.js +74 -44
- package/dist/services/runtime-handoff.d.ts +60 -0
- package/dist/services/runtime-handoff.js +134 -0
- package/dist/services/runtime-record.d.ts +47 -0
- package/dist/services/runtime-record.js +143 -0
- package/dist/services/update-check.d.ts +4 -0
- package/dist/services/update-check.js +23 -0
- package/dist/services/web-autostart.d.ts +9 -7
- package/dist/services/web-autostart.js +33 -54
- package/dist/services/web-ensure.d.ts +11 -0
- package/dist/services/web-ensure.js +37 -5
- package/dist/services/web-handover.d.ts +22 -0
- package/dist/services/web-handover.js +62 -0
- package/dist/services/web-server.js +3 -2
- package/dist/web/assets/{index-45F5ouFN.js → index-D_e39BA6.js} +2 -2
- package/dist/web/index.html +1 -1
- package/package.json +2 -1
|
@@ -6,6 +6,7 @@ import { basename, dirname, join, delimiter, resolve } from "node:path";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { CONFIG } from "../config.js";
|
|
8
8
|
import { log } from "./logger.js";
|
|
9
|
+
import { copyVersion, ensureLauncher, launcherPath, recordedCopy, } from "./runtime-record.js";
|
|
9
10
|
import { compareVersions } from "./version-compare.js";
|
|
10
11
|
const MARKER = "OMMS login item";
|
|
11
12
|
const NAME = "io.github.cmdaltctr.omms.web";
|
|
@@ -70,53 +71,18 @@ function packageRoot() {
|
|
|
70
71
|
dir = parent;
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
|
-
function packageVersionAt(root) {
|
|
74
|
-
try {
|
|
75
|
-
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
76
|
-
if (pkg.name !== "om-memory-system" || !existsSync(join(root, "dist", "cli", "index.js"))) {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
return typeof pkg.version === "string" ? pkg.version : null;
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
/** The package folder a written login item runs, read back from its command line. */
|
|
86
|
-
export function itemPackageRoot(file) {
|
|
87
|
-
try {
|
|
88
|
-
const content = readFileSync(file, "utf8");
|
|
89
|
-
const match = /([^"<>\r\n]*?)[\\/]dist[\\/]cli[\\/]index\.js/.exec(content);
|
|
90
|
-
const path = match?.[1]?.replace(/^.*?<string>/, "").trim();
|
|
91
|
-
if (!path)
|
|
92
|
-
return null;
|
|
93
|
-
// Only plists escape XML. Decode ampersands last to preserve literal entity text.
|
|
94
|
-
return content.includes("<plist")
|
|
95
|
-
? path
|
|
96
|
-
.replaceAll(""", '"')
|
|
97
|
-
.replaceAll("'", "'")
|
|
98
|
-
.replaceAll("<", "<")
|
|
99
|
-
.replaceAll(">", ">")
|
|
100
|
-
.replaceAll("&", "&")
|
|
101
|
-
: path;
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
74
|
/**
|
|
108
|
-
* The newest OMMS copy among
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* the login item.
|
|
75
|
+
* The newest valid OMMS copy among the candidates. On equal versions the first
|
|
76
|
+
* wins, so a host's cached copy goes last. This is the copy the launcher starts,
|
|
77
|
+
* and it matches the launcher's own choice.
|
|
112
78
|
*/
|
|
113
79
|
export function preferredPackageRoot(candidates) {
|
|
114
80
|
let best = null;
|
|
115
81
|
for (const root of candidates) {
|
|
116
82
|
if (!root)
|
|
117
83
|
continue;
|
|
118
|
-
const version =
|
|
119
|
-
if (!version
|
|
84
|
+
const version = copyVersion(root);
|
|
85
|
+
if (!version)
|
|
120
86
|
continue;
|
|
121
87
|
if (!best || (compareVersions(version, best.version) ?? 0) > 0)
|
|
122
88
|
best = { root, version };
|
|
@@ -145,15 +111,20 @@ function details(options) {
|
|
|
145
111
|
const platform = options.platform ?? process.platform;
|
|
146
112
|
const path = itemPath(home, platform);
|
|
147
113
|
const runtime = options.runtime === undefined ? resolveWebRuntime() : options.runtime;
|
|
148
|
-
const
|
|
149
|
-
const
|
|
114
|
+
const dir = join(home, ".omms");
|
|
115
|
+
const own = options.ownRoot !== undefined
|
|
116
|
+
? options.ownRoot
|
|
117
|
+
: options.packageRoot !== undefined
|
|
118
|
+
? options.packageRoot
|
|
119
|
+
: packageRoot();
|
|
120
|
+
const recorded = recordedCopy(dir, logCode)?.root ?? null;
|
|
150
121
|
const root = options.packageRoot === undefined
|
|
151
|
-
?
|
|
152
|
-
|
|
153
|
-
: own;
|
|
122
|
+
? preferredPackageRoot([recorded, globalPackageRoot(runtime ?? null, platform), own])
|
|
123
|
+
: options.packageRoot;
|
|
154
124
|
const supported = platform !== "linux" || (options.systemctlAvailable ?? Boolean(executable("systemctl")));
|
|
155
|
-
return { home, platform, path, runtime, root, supported };
|
|
125
|
+
return { home, platform, path, runtime, root, own, recorded, dir, supported };
|
|
156
126
|
}
|
|
127
|
+
const logCode = (code, data) => log("Runtime record", { code, ...data });
|
|
157
128
|
const xml = (value) => value
|
|
158
129
|
.replaceAll("&", "&")
|
|
159
130
|
.replaceAll("<", "<")
|
|
@@ -161,21 +132,21 @@ const xml = (value) => value
|
|
|
161
132
|
.replaceAll('"', """);
|
|
162
133
|
const systemd = (value) => `"${value.replaceAll("%", "%%").replaceAll('"', '\\"')}"`;
|
|
163
134
|
const windows = (value) => `"${value.replaceAll("%", "%%")}"`;
|
|
164
|
-
function itemContent(platform, runtime,
|
|
135
|
+
function itemContent(platform, runtime, launcher) {
|
|
165
136
|
if (platform === "darwin")
|
|
166
137
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
167
138
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
168
139
|
<plist version="1.0"><dict>
|
|
169
140
|
<!-- ${MARKER} -->
|
|
170
141
|
<key>Label</key><string>${NAME}</string>
|
|
171
|
-
<key>ProgramArguments</key><array><string>${xml(runtime)}</string><string>${xml(
|
|
142
|
+
<key>ProgramArguments</key><array><string>${xml(runtime)}</string><string>${xml(launcher)}</string><string>web</string><string>--login-item</string></array>
|
|
172
143
|
<key>RunAtLoad</key><true/><key>KeepAlive</key><false/>
|
|
173
144
|
<key>StandardOutPath</key><string>/dev/null</string><key>StandardErrorPath</key><string>/dev/null</string>
|
|
174
145
|
</dict></plist>\n`;
|
|
175
146
|
if (platform === "linux")
|
|
176
147
|
return `# ${MARKER}
|
|
177
|
-
[Unit]\nDescription=OMMS web app\n[Service]\nType=simple\nExecStart=${systemd(runtime)} ${systemd(
|
|
178
|
-
return `@echo off\r\nrem ${MARKER}\r\nstart "" /min ${windows(runtime)} ${windows(
|
|
148
|
+
[Unit]\nDescription=OMMS web app\n[Service]\nType=simple\nExecStart=${systemd(runtime)} ${systemd(launcher)} web --login-item\n[Install]\nWantedBy=default.target\n`;
|
|
149
|
+
return `@echo off\r\nrem ${MARKER}\r\nstart "" /min ${windows(runtime)} ${windows(launcher)} web --login-item\r\n`;
|
|
179
150
|
}
|
|
180
151
|
function owned(path) {
|
|
181
152
|
return readFileSync(path, "utf8").includes(MARKER);
|
|
@@ -188,15 +159,16 @@ function runCommand(options, home, command, args) {
|
|
|
188
159
|
}
|
|
189
160
|
/** Read the login item's state without changing any system files. */
|
|
190
161
|
export function webAutostartStatus(options = {}) {
|
|
191
|
-
const { path, runtime, root, supported } = details(options);
|
|
162
|
+
const { path, runtime, root, dir, supported } = details(options);
|
|
192
163
|
if (!path || !supported)
|
|
193
164
|
return { state: "unsupported" };
|
|
194
165
|
if (!runtime)
|
|
195
166
|
return { state: "no-runtime", path };
|
|
196
167
|
if (!root)
|
|
197
168
|
return { state: "no-package", path, runtime };
|
|
169
|
+
const launcher = launcherPath(dir);
|
|
198
170
|
if (!existsSync(path))
|
|
199
|
-
return { state: "not-installed", path, runtime, packagePath: root };
|
|
171
|
+
return { state: "not-installed", path, runtime, packagePath: root, launcher };
|
|
200
172
|
if (!owned(path))
|
|
201
173
|
throw new Error("Login item at the OMMS path is not owned by OMMS");
|
|
202
174
|
return {
|
|
@@ -204,6 +176,7 @@ export function webAutostartStatus(options = {}) {
|
|
|
204
176
|
path,
|
|
205
177
|
runtime,
|
|
206
178
|
packagePath: root,
|
|
179
|
+
launcher,
|
|
207
180
|
command: readFileSync(path, "utf8"),
|
|
208
181
|
};
|
|
209
182
|
}
|
|
@@ -214,9 +187,15 @@ export function installWebAutostart(options = {}) {
|
|
|
214
187
|
status.state === "no-runtime" ||
|
|
215
188
|
status.state === "no-package")
|
|
216
189
|
return status;
|
|
217
|
-
const { home, platform, path, runtime, root } = details(options);
|
|
190
|
+
const { home, platform, path, runtime, root, own, recorded, dir } = details(options);
|
|
191
|
+
// The item runs a fixed launcher, so the launcher must exist before the item does.
|
|
192
|
+
if (!ensureLauncher({ dir, sources: [root, own, recorded], log: logCode })) {
|
|
193
|
+
// A valid package exists. Only the launcher is missing, so say that.
|
|
194
|
+
logCode("launcher-missing", {});
|
|
195
|
+
return { state: "no-launcher", path: path, runtime: runtime };
|
|
196
|
+
}
|
|
218
197
|
const file = path;
|
|
219
|
-
const content = itemContent(platform, runtime,
|
|
198
|
+
const content = itemContent(platform, runtime, launcherPath(dir));
|
|
220
199
|
mkdirSync(dirname(file), { recursive: true });
|
|
221
200
|
const changed = !existsSync(file) || readFileSync(file, "utf8") !== content;
|
|
222
201
|
if (changed) {
|
|
@@ -37,6 +37,8 @@ export interface EnsureDeps {
|
|
|
37
37
|
resolveRuntime: () => Promise<string | null> | string | null;
|
|
38
38
|
/** The `om-memory-system` script that `web` is run from. */
|
|
39
39
|
cliScript: string;
|
|
40
|
+
/** `~/.omms/bin/omms-launch.mjs` when it exists. `web` then starts through it, on the newest recorded copy. */
|
|
41
|
+
launcherScript: string | null;
|
|
40
42
|
lockPath: string;
|
|
41
43
|
lockFs: LockFs;
|
|
42
44
|
pidAlive: (pid: number) => boolean;
|
|
@@ -51,6 +53,15 @@ export interface EnsureOptions {
|
|
|
51
53
|
budgetMs: number;
|
|
52
54
|
/** `false` starts the web app and returns at once (session starts). Default `true`. */
|
|
53
55
|
wait?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Replace a running web app older than `version`: ask it to step aside, then
|
|
58
|
+
* start as for an empty port. `headers` are the local token headers the
|
|
59
|
+
* step-aside route needs. Without it, any running OMMS web app is used.
|
|
60
|
+
*/
|
|
61
|
+
replaceOlder?: {
|
|
62
|
+
version: string;
|
|
63
|
+
headers: Record<string, string>;
|
|
64
|
+
};
|
|
54
65
|
deps?: Partial<EnsureDeps>;
|
|
55
66
|
}
|
|
56
67
|
/** A lock older than this is stale. Also the longest a background start holds it. */
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { linkSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { existsSync, linkSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { launcherPath } from "./runtime-record.js";
|
|
8
|
+
import { isOlderVersion } from "./version-compare.js";
|
|
9
|
+
import { negotiateOwner, readWebVersion } from "./web-handover.js";
|
|
7
10
|
const HEALTH_PROBE_MS = 1_000;
|
|
8
11
|
const POLL_INTERVAL_MS = 250;
|
|
9
12
|
/** A lock older than this is stale. Also the longest a background start holds it. */
|
|
@@ -106,6 +109,13 @@ function processAlive(pid) {
|
|
|
106
109
|
return error.code === "EPERM";
|
|
107
110
|
}
|
|
108
111
|
}
|
|
112
|
+
/** Tests set the switch, so they never start the developer's real recorded copy. */
|
|
113
|
+
function existingLauncher() {
|
|
114
|
+
if (process.env.OMMS_DISABLE_RUNTIME_RECORD === "1")
|
|
115
|
+
return null;
|
|
116
|
+
const file = launcherPath(join(homedir(), ".omms"));
|
|
117
|
+
return existsSync(file) ? file : null;
|
|
118
|
+
}
|
|
109
119
|
function productionDefaults() {
|
|
110
120
|
return {
|
|
111
121
|
fetch: globalThis.fetch,
|
|
@@ -115,6 +125,7 @@ function productionDefaults() {
|
|
|
115
125
|
resolveRuntime: async () => (await import("./web-autostart.js")).resolveWebRuntime(),
|
|
116
126
|
// From dist/services/ to the CLI entry point.
|
|
117
127
|
cliScript: fileURLToPath(new URL("../cli/index.js", import.meta.url)),
|
|
128
|
+
launcherScript: existingLauncher(),
|
|
118
129
|
lockPath: startLockPath(),
|
|
119
130
|
lockFs: nodeLockFs,
|
|
120
131
|
pidAlive: processAlive,
|
|
@@ -211,7 +222,12 @@ export async function ensureWebApp(options) {
|
|
|
211
222
|
let held = false;
|
|
212
223
|
try {
|
|
213
224
|
const first = await probe(settings.baseUrl, probeMs(), deps);
|
|
214
|
-
|
|
225
|
+
const { replaceOlder } = options;
|
|
226
|
+
// An OMMS web app that is older than the newest copy is replaced; any other is used.
|
|
227
|
+
const replacing = first === "omms" &&
|
|
228
|
+
replaceOlder !== undefined &&
|
|
229
|
+
isOlderVersion((await readWebVersion(deps.fetch, settings.baseUrl, replaceOlder.headers)) ?? "", replaceOlder.version);
|
|
230
|
+
if (first === "omms" && !replacing)
|
|
215
231
|
return "running";
|
|
216
232
|
if (first === "other") {
|
|
217
233
|
await logCode("port-busy");
|
|
@@ -220,11 +236,27 @@ export async function ensureWebApp(options) {
|
|
|
220
236
|
const runtime = await deps.resolveRuntime();
|
|
221
237
|
if (!runtime) {
|
|
222
238
|
await logCode("no-runtime");
|
|
223
|
-
return "no-runtime";
|
|
239
|
+
return replacing ? "running" : "no-runtime";
|
|
224
240
|
}
|
|
225
241
|
held = takeStartLock(deps);
|
|
242
|
+
// The caller that holds the lock replaces the web app; the others use it.
|
|
226
243
|
if (!held)
|
|
227
|
-
return await waitForOther(settings.baseUrl, wait, remaining, deps);
|
|
244
|
+
return replacing ? "running" : await waitForOther(settings.baseUrl, wait, remaining, deps);
|
|
245
|
+
if (replacing && replaceOlder) {
|
|
246
|
+
const outcome = await negotiateOwner({
|
|
247
|
+
fetchFn: deps.fetch,
|
|
248
|
+
sleep: deps.sleep,
|
|
249
|
+
url: settings.baseUrl,
|
|
250
|
+
port: Number(new URL(settings.baseUrl).port) || 80,
|
|
251
|
+
headers: replaceOlder.headers,
|
|
252
|
+
version: replaceOlder.version,
|
|
253
|
+
});
|
|
254
|
+
if (outcome.kind !== "handed") {
|
|
255
|
+
if (outcome.kind === "stuck")
|
|
256
|
+
await logCode("step-aside-failed");
|
|
257
|
+
return "running";
|
|
258
|
+
}
|
|
259
|
+
}
|
|
228
260
|
// Another caller may have finished its start before this one took the lock.
|
|
229
261
|
const second = await probe(settings.baseUrl, probeMs(), deps);
|
|
230
262
|
if (second === "omms")
|
|
@@ -233,7 +265,7 @@ export async function ensureWebApp(options) {
|
|
|
233
265
|
await logCode("port-busy");
|
|
234
266
|
return "port-busy";
|
|
235
267
|
}
|
|
236
|
-
const child = deps.spawn(runtime, [deps.cliScript, "web"], {
|
|
268
|
+
const child = deps.spawn(runtime, [deps.launcherScript ?? deps.cliScript, "web"], {
|
|
237
269
|
detached: true,
|
|
238
270
|
stdio: "ignore",
|
|
239
271
|
cwd: homedir(),
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const POLL_MS = 250;
|
|
2
|
+
export declare const POLL_LIMIT = 40;
|
|
3
|
+
/** The version an OMMS web app reports on `url`, or null when none answers with one. */
|
|
4
|
+
export declare function readWebVersion(fetchFn: typeof fetch, url: string, headers: Record<string, string>): Promise<string | null>;
|
|
5
|
+
export type Handover = {
|
|
6
|
+
kind: "none";
|
|
7
|
+
} | {
|
|
8
|
+
kind: "handed";
|
|
9
|
+
} | {
|
|
10
|
+
kind: "stuck" | "same" | "newer";
|
|
11
|
+
owner: string;
|
|
12
|
+
};
|
|
13
|
+
export interface HandoverContext {
|
|
14
|
+
fetchFn: typeof fetch;
|
|
15
|
+
sleep: (ms: number) => Promise<void>;
|
|
16
|
+
url: string;
|
|
17
|
+
port: number;
|
|
18
|
+
headers: Record<string, string>;
|
|
19
|
+
version: string;
|
|
20
|
+
}
|
|
21
|
+
/** Ask an older OMMS web app on the configured port to step aside, and wait for the port. */
|
|
22
|
+
export declare function negotiateOwner(ctx: HandoverContext): Promise<Handover>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { compareVersions } from "./version-compare.js";
|
|
2
|
+
// The version handover shared by `web install` and the host start: ask an older
|
|
3
|
+
// OMMS web app to step aside, then wait for its port. It imports no host adapter,
|
|
4
|
+
// no store, and no embedding model.
|
|
5
|
+
export const POLL_MS = 250;
|
|
6
|
+
export const POLL_LIMIT = 40;
|
|
7
|
+
const REQUEST_TIMEOUT_MS = 2_000;
|
|
8
|
+
/** The version an OMMS web app reports on `url`, or null when none answers with one. */
|
|
9
|
+
export async function readWebVersion(fetchFn, url, headers) {
|
|
10
|
+
try {
|
|
11
|
+
const response = await fetchFn(`${url}/api/settings/version`, {
|
|
12
|
+
headers,
|
|
13
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
14
|
+
});
|
|
15
|
+
if (!response.ok)
|
|
16
|
+
return null;
|
|
17
|
+
const body = (await response.json());
|
|
18
|
+
return typeof body.running === "string" ? body.running : null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function portAnswers(fetchFn, url) {
|
|
25
|
+
try {
|
|
26
|
+
await fetchFn(`${url}/api/health`, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Ask an older OMMS web app on the configured port to step aside, and wait for the port. */
|
|
34
|
+
export async function negotiateOwner(ctx) {
|
|
35
|
+
const owner = await readWebVersion(ctx.fetchFn, ctx.url, ctx.headers);
|
|
36
|
+
const order = owner === null ? null : compareVersions(owner, ctx.version);
|
|
37
|
+
// A version that cannot be placed is never stopped.
|
|
38
|
+
if (owner === null || order === null)
|
|
39
|
+
return { kind: "none" };
|
|
40
|
+
if (order === 0)
|
|
41
|
+
return { kind: "same", owner };
|
|
42
|
+
if (order > 0)
|
|
43
|
+
return { kind: "newer", owner };
|
|
44
|
+
try {
|
|
45
|
+
const reply = await ctx.fetchFn(`${ctx.url}/api/web/step-aside`, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: { ...ctx.headers, "content-type": "application/json" },
|
|
48
|
+
body: JSON.stringify({ version: ctx.version }),
|
|
49
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
50
|
+
});
|
|
51
|
+
if (reply.status !== 202)
|
|
52
|
+
return { kind: "stuck", owner };
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return { kind: "stuck", owner };
|
|
56
|
+
}
|
|
57
|
+
// If the old app is slow to stop, the login item still takes the port later.
|
|
58
|
+
for (let i = 0; i < POLL_LIMIT && (await portAnswers(ctx.fetchFn, ctx.url)); i++) {
|
|
59
|
+
await ctx.sleep(POLL_MS);
|
|
60
|
+
}
|
|
61
|
+
return { kind: "handed" };
|
|
62
|
+
}
|
|
@@ -802,17 +802,18 @@ export class WebServer {
|
|
|
802
802
|
return this.handleStepAside(remoteAddress, body);
|
|
803
803
|
}
|
|
804
804
|
if (path === "/api/settings/version" && method === "GET") {
|
|
805
|
-
const [{ packageVersion }, { globalCommandVersion }] = await Promise.all([
|
|
805
|
+
const [{ packageVersion }, { globalCommandVersion, globalRelation }] = await Promise.all([
|
|
806
806
|
import("./package-version.js"),
|
|
807
807
|
import("./global-version.js"),
|
|
808
808
|
]);
|
|
809
809
|
const running = packageVersion();
|
|
810
|
-
const global =
|
|
810
|
+
const global = globalCommandVersion();
|
|
811
811
|
return this.jsonResponse({
|
|
812
812
|
running,
|
|
813
813
|
global: global.version,
|
|
814
814
|
globalPath: global.path,
|
|
815
815
|
mismatch: global.version !== null && global.version !== running,
|
|
816
|
+
relation: globalRelation(running, global.version),
|
|
816
817
|
});
|
|
817
818
|
}
|
|
818
819
|
if (path === "/api/settings/web-autostart" && method === "GET") {
|