om-memory-system 4.4.0-next.65 → 4.4.0-next.69
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/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/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-D2Gueurd.js → index-D_e39BA6.js} +3 -3
- package/dist/web/index.html +1 -1
- package/package.json +2 -1
|
@@ -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") {
|