om-memory-system 3.3.0-next.17 → 3.3.1
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 +3 -5
- package/dist/adapters/opencode/backfill-models.d.ts +25 -0
- package/dist/adapters/opencode/backfill-models.js +39 -0
- package/dist/adapters/opencode/backfill-startup.d.ts +7 -0
- package/dist/adapters/opencode/backfill-startup.js +12 -0
- package/dist/adapters/opencode/import-command.js +3 -0
- package/dist/adapters/pi/backfill-models.d.ts +9 -0
- package/dist/adapters/pi/backfill-models.js +30 -0
- package/dist/adapters/pi/extension.js +50 -0
- package/dist/adapters/pi/import-command.js +3 -0
- package/dist/cli/index.js +13 -1
- package/dist/cli/web-command.d.ts +3 -0
- package/dist/cli/web-command.js +72 -0
- package/dist/config.d.ts +13 -1
- package/dist/config.js +28 -0
- package/dist/importer/auto-backfill.d.ts +20 -0
- package/dist/importer/auto-backfill.js +165 -0
- package/dist/importer/backfill-lock.d.ts +3 -0
- package/dist/importer/backfill-lock.js +44 -0
- package/dist/importer/backfill-model.d.ts +10 -0
- package/dist/importer/backfill-model.js +13 -0
- package/dist/importer/importer.js +14 -0
- package/dist/importer/manual-import-guard.d.ts +4 -0
- package/dist/importer/manual-import-guard.js +9 -0
- package/dist/importer/settings-health.d.ts +1 -2
- package/dist/importer/settings-health.js +2 -11
- package/dist/index.js +30 -0
- package/dist/services/backfill-state.d.ts +25 -0
- package/dist/services/backfill-state.js +68 -0
- package/dist/services/capture-diagnostics.d.ts +9 -0
- package/dist/services/capture-diagnostics.js +73 -6
- package/dist/services/global-config-writer.js +5 -1
- package/dist/services/live-captured-entries.d.ts +3 -0
- package/dist/services/live-captured-entries.js +34 -0
- package/dist/services/safe-health-error.d.ts +2 -0
- package/dist/services/safe-health-error.js +11 -0
- package/dist/services/settings-snapshot.js +14 -2
- package/dist/services/web-autostart.d.ts +29 -0
- package/dist/services/web-autostart.js +208 -0
- package/dist/services/web-server.js +11 -0
- package/dist/web/assets/index-B-_gMSn9.js +124 -0
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/assets/index-BMkPht7g.js +0 -124
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Remove configured credentials before returning a model error to the browser. */
|
|
2
|
+
export function safeHealthError(error, secrets) {
|
|
3
|
+
let message = error instanceof Error ? error.message : String(error);
|
|
4
|
+
for (const secret of secrets) {
|
|
5
|
+
if (secret && secret.length > 2)
|
|
6
|
+
message = message.replaceAll(secret, "[redacted]");
|
|
7
|
+
}
|
|
8
|
+
return message
|
|
9
|
+
.replace(/(Bearer\s+)[^\s]+/gi, "$1[redacted]")
|
|
10
|
+
.replace(/(api[_-]?key[=:]\s*)[^\s&]+/gi, "$1[redacted]");
|
|
11
|
+
}
|
|
@@ -12,6 +12,10 @@ const editable = [
|
|
|
12
12
|
"captureTrace",
|
|
13
13
|
"captureTraceRetentionDays",
|
|
14
14
|
"captureAttemptRetentionDays",
|
|
15
|
+
"autoBackfill",
|
|
16
|
+
"opencodeBackfillModel",
|
|
17
|
+
"piBackfillModel",
|
|
18
|
+
"webServerAutoStart",
|
|
15
19
|
];
|
|
16
20
|
function readSettingsFile(path) {
|
|
17
21
|
if (!path)
|
|
@@ -33,13 +37,21 @@ export function getSettingsSnapshot(directory) {
|
|
|
33
37
|
const project = readSettingsFile(projectFile(directory));
|
|
34
38
|
const settings = Object.fromEntries(editable.map((key) => {
|
|
35
39
|
const projectOverrides = Object.hasOwn(project, key) &&
|
|
36
|
-
(key === "captureTrace"
|
|
40
|
+
(key === "captureTrace"
|
|
41
|
+
? project[key] === false
|
|
42
|
+
: !key.endsWith("RetentionDays") &&
|
|
43
|
+
![
|
|
44
|
+
"autoBackfill",
|
|
45
|
+
"opencodeBackfillModel",
|
|
46
|
+
"piBackfillModel",
|
|
47
|
+
"webServerAutoStart",
|
|
48
|
+
].includes(key));
|
|
37
49
|
const source = projectOverrides
|
|
38
50
|
? "project"
|
|
39
51
|
: Object.hasOwn(global, key)
|
|
40
52
|
? "global"
|
|
41
53
|
: "default";
|
|
42
|
-
const value = projectOverrides ? project[key] : (
|
|
54
|
+
const value = projectOverrides ? project[key] : (global[key] ?? CONFIG[key]);
|
|
43
55
|
return [key, { value, source, globalValue: global[key] ?? CONFIG[key] }];
|
|
44
56
|
}));
|
|
45
57
|
const secrets = Object.fromEntries(["memoryApiKey", "embeddingApiKey", "webServerApiToken", "webServerAuthPassword"].map((key) => {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { CONFIG } from "../config.js";
|
|
2
|
+
type State = "installed" | "not-installed" | "unsupported" | "no-runtime" | "no-package";
|
|
3
|
+
export type WebAutostartStatus = {
|
|
4
|
+
state: State;
|
|
5
|
+
path?: string;
|
|
6
|
+
runtime?: string;
|
|
7
|
+
packagePath?: string;
|
|
8
|
+
command?: string;
|
|
9
|
+
};
|
|
10
|
+
export interface WebAutostartOptions {
|
|
11
|
+
home?: string;
|
|
12
|
+
platform?: string;
|
|
13
|
+
runtime?: string | null;
|
|
14
|
+
packageRoot?: string | null;
|
|
15
|
+
systemctlAvailable?: boolean;
|
|
16
|
+
run?: (command: string, args: string[]) => void;
|
|
17
|
+
start?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** OpenCode's executable is not a JavaScript runtime. */
|
|
20
|
+
export declare function resolveWebRuntime(execPath?: string, platform?: NodeJS.Platform): string | null;
|
|
21
|
+
/** Read the login item's state without changing any system files. */
|
|
22
|
+
export declare function webAutostartStatus(options?: WebAutostartOptions): WebAutostartStatus;
|
|
23
|
+
/** Install or update only the fixed-name OMMS item. `start` launches it now. */
|
|
24
|
+
export declare function installWebAutostart(options?: WebAutostartOptions): WebAutostartStatus;
|
|
25
|
+
/** Remove only an item carrying the OMMS marker. */
|
|
26
|
+
export declare function removeWebAutostart(options?: WebAutostartOptions): WebAutostartStatus;
|
|
27
|
+
/** Host start checks the global settings. A failed item cannot block a session. */
|
|
28
|
+
export declare function reconcileWebAutostart(config?: Pick<typeof CONFIG, "webServerAutoStart" | "webServerEnabled">, options?: WebAutostartOptions): void;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { accessSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, constants, } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { basename, dirname, join, delimiter, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { CONFIG } from "../config.js";
|
|
8
|
+
import { log } from "./logger.js";
|
|
9
|
+
const MARKER = "OMMS login item";
|
|
10
|
+
const NAME = "io.github.cmdaltctr.omms.web";
|
|
11
|
+
function executable(name) {
|
|
12
|
+
for (const part of (process.env.PATH ?? "").split(delimiter)) {
|
|
13
|
+
if (!part)
|
|
14
|
+
continue;
|
|
15
|
+
const path = resolve(part, name);
|
|
16
|
+
try {
|
|
17
|
+
accessSync(path, constants.X_OK);
|
|
18
|
+
return path;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
/* Search the next directory. */
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
/** OpenCode's executable is not a JavaScript runtime. */
|
|
27
|
+
export function resolveWebRuntime(execPath = process.execPath, platform = process.platform) {
|
|
28
|
+
const name = basename(execPath).toLowerCase();
|
|
29
|
+
if (["node", "bun", "node.exe", "bun.exe"].includes(name))
|
|
30
|
+
return execPath;
|
|
31
|
+
const suffix = platform === "win32" ? ".exe" : "";
|
|
32
|
+
return executable(`node${suffix}`) ?? executable(`bun${suffix}`);
|
|
33
|
+
}
|
|
34
|
+
function packageRoot() {
|
|
35
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
for (;;) {
|
|
37
|
+
const file = join(dir, "package.json");
|
|
38
|
+
try {
|
|
39
|
+
if (JSON.parse(readFileSync(file, "utf8")).name === "om-memory-system")
|
|
40
|
+
return dir;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
/* Try the parent. */
|
|
44
|
+
}
|
|
45
|
+
const parent = dirname(dir);
|
|
46
|
+
if (parent === dir)
|
|
47
|
+
return null;
|
|
48
|
+
dir = parent;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function itemPath(home, platform) {
|
|
52
|
+
if (platform === "darwin")
|
|
53
|
+
return join(home, "Library", "LaunchAgents", `${NAME}.plist`);
|
|
54
|
+
if (platform === "linux")
|
|
55
|
+
return join(home, ".config", "systemd", "user", "omms-web.service");
|
|
56
|
+
if (platform === "win32")
|
|
57
|
+
return join(home, "AppData", "Roaming", "Microsoft", "Windows", "Start Menu", "Programs", "Startup", "omms-web.cmd");
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function details(options) {
|
|
61
|
+
const home = options.home ?? homedir();
|
|
62
|
+
const platform = options.platform ?? process.platform;
|
|
63
|
+
const path = itemPath(home, platform);
|
|
64
|
+
const runtime = options.runtime === undefined ? resolveWebRuntime() : options.runtime;
|
|
65
|
+
const root = options.packageRoot === undefined ? packageRoot() : options.packageRoot;
|
|
66
|
+
const supported = platform !== "linux" || (options.systemctlAvailable ?? Boolean(executable("systemctl")));
|
|
67
|
+
return { home, platform, path, runtime, root, supported };
|
|
68
|
+
}
|
|
69
|
+
const xml = (value) => value
|
|
70
|
+
.replaceAll("&", "&")
|
|
71
|
+
.replaceAll("<", "<")
|
|
72
|
+
.replaceAll(">", ">")
|
|
73
|
+
.replaceAll('"', """);
|
|
74
|
+
const systemd = (value) => `"${value.replaceAll("%", "%%").replaceAll('"', '\\"')}"`;
|
|
75
|
+
const windows = (value) => `"${value.replaceAll("%", "%%")}"`;
|
|
76
|
+
function itemContent(platform, runtime, cli) {
|
|
77
|
+
if (platform === "darwin")
|
|
78
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
79
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
80
|
+
<plist version="1.0"><dict>
|
|
81
|
+
<!-- ${MARKER} -->
|
|
82
|
+
<key>Label</key><string>${NAME}</string>
|
|
83
|
+
<key>ProgramArguments</key><array><string>${xml(runtime)}</string><string>${xml(cli)}</string><string>web</string><string>--login-item</string></array>
|
|
84
|
+
<key>RunAtLoad</key><true/><key>KeepAlive</key><false/>
|
|
85
|
+
<key>StandardOutPath</key><string>/dev/null</string><key>StandardErrorPath</key><string>/dev/null</string>
|
|
86
|
+
</dict></plist>\n`;
|
|
87
|
+
if (platform === "linux")
|
|
88
|
+
return `# ${MARKER}
|
|
89
|
+
[Unit]\nDescription=OMMS web app\n[Service]\nType=simple\nExecStart=${systemd(runtime)} ${systemd(cli)} web --login-item\n[Install]\nWantedBy=default.target\n`;
|
|
90
|
+
return `@echo off\r\nrem ${MARKER}\r\nstart "" /min ${windows(runtime)} ${windows(cli)} web --login-item\r\n`;
|
|
91
|
+
}
|
|
92
|
+
function owned(path) {
|
|
93
|
+
return readFileSync(path, "utf8").includes(MARKER);
|
|
94
|
+
}
|
|
95
|
+
function runCommand(options, home, command, args) {
|
|
96
|
+
if (options.run)
|
|
97
|
+
options.run(command, args);
|
|
98
|
+
else
|
|
99
|
+
execFileSync(command, args, { cwd: home, stdio: "ignore", timeout: 5_000 });
|
|
100
|
+
}
|
|
101
|
+
/** Read the login item's state without changing any system files. */
|
|
102
|
+
export function webAutostartStatus(options = {}) {
|
|
103
|
+
const { path, runtime, root, supported } = details(options);
|
|
104
|
+
if (!path || !supported)
|
|
105
|
+
return { state: "unsupported" };
|
|
106
|
+
if (!runtime)
|
|
107
|
+
return { state: "no-runtime", path };
|
|
108
|
+
if (!root)
|
|
109
|
+
return { state: "no-package", path, runtime };
|
|
110
|
+
if (!existsSync(path))
|
|
111
|
+
return { state: "not-installed", path, runtime, packagePath: root };
|
|
112
|
+
if (!owned(path))
|
|
113
|
+
throw new Error("Login item at the OMMS path is not owned by OMMS");
|
|
114
|
+
return {
|
|
115
|
+
state: "installed",
|
|
116
|
+
path,
|
|
117
|
+
runtime,
|
|
118
|
+
packagePath: root,
|
|
119
|
+
command: readFileSync(path, "utf8"),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/** Install or update only the fixed-name OMMS item. `start` launches it now. */
|
|
123
|
+
export function installWebAutostart(options = {}) {
|
|
124
|
+
const status = webAutostartStatus(options);
|
|
125
|
+
if (status.state === "unsupported" ||
|
|
126
|
+
status.state === "no-runtime" ||
|
|
127
|
+
status.state === "no-package")
|
|
128
|
+
return status;
|
|
129
|
+
const { home, platform, path, runtime, root } = details(options);
|
|
130
|
+
const file = path;
|
|
131
|
+
const content = itemContent(platform, runtime, join(root, "dist", "cli", "index.js"));
|
|
132
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
133
|
+
const changed = !existsSync(file) || readFileSync(file, "utf8") !== content;
|
|
134
|
+
if (changed) {
|
|
135
|
+
const temp = `${file}.${randomUUID()}.tmp`;
|
|
136
|
+
try {
|
|
137
|
+
writeFileSync(temp, content, { mode: 0o600 });
|
|
138
|
+
renameSync(temp, file);
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
if (existsSync(temp))
|
|
142
|
+
unlinkSync(temp);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (platform === "linux") {
|
|
146
|
+
if (changed)
|
|
147
|
+
runCommand(options, home, "systemctl", ["--user", "daemon-reload"]);
|
|
148
|
+
// Re-enable even if the unit file is unchanged; the user service may have been disabled.
|
|
149
|
+
runCommand(options, home, "systemctl", ["--user", "enable", "omms-web.service"]);
|
|
150
|
+
if (options.start)
|
|
151
|
+
runCommand(options, home, "systemctl", ["--user", "start", "omms-web.service"]);
|
|
152
|
+
}
|
|
153
|
+
if (platform === "darwin" && options.start) {
|
|
154
|
+
const domain = `gui/${process.getuid?.() ?? 0}`;
|
|
155
|
+
try {
|
|
156
|
+
runCommand(options, home, "launchctl", ["bootout", domain, file]);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
/* Not loaded. */
|
|
160
|
+
}
|
|
161
|
+
runCommand(options, home, "launchctl", ["bootstrap", domain, file]);
|
|
162
|
+
}
|
|
163
|
+
return webAutostartStatus(options);
|
|
164
|
+
}
|
|
165
|
+
/** Remove only an item carrying the OMMS marker. */
|
|
166
|
+
export function removeWebAutostart(options = {}) {
|
|
167
|
+
const { home, platform, path, supported } = details(options);
|
|
168
|
+
if (!path)
|
|
169
|
+
return { state: "unsupported" };
|
|
170
|
+
if (!existsSync(path))
|
|
171
|
+
return { state: "not-installed", path };
|
|
172
|
+
if (!owned(path))
|
|
173
|
+
throw new Error("Login item at the OMMS path is not owned by OMMS");
|
|
174
|
+
if (platform === "darwin" && options.start) {
|
|
175
|
+
try {
|
|
176
|
+
runCommand(options, home, "launchctl", ["bootout", `gui/${process.getuid?.() ?? 0}`, path]);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
/* A registered item may not be loaded. */
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (platform === "linux" && supported) {
|
|
183
|
+
runCommand(options, home, "systemctl", [
|
|
184
|
+
"--user",
|
|
185
|
+
"disable",
|
|
186
|
+
...(options.start ? ["--now"] : []),
|
|
187
|
+
"omms-web.service",
|
|
188
|
+
]);
|
|
189
|
+
}
|
|
190
|
+
unlinkSync(path);
|
|
191
|
+
return { state: "not-installed", path };
|
|
192
|
+
}
|
|
193
|
+
/** Host start checks the global settings. A failed item cannot block a session. */
|
|
194
|
+
export function reconcileWebAutostart(config = CONFIG, options = {}) {
|
|
195
|
+
if (process.env.OMMS_DISABLE_WEB_AUTOSTART === "1")
|
|
196
|
+
return;
|
|
197
|
+
try {
|
|
198
|
+
if (config.webServerAutoStart && config.webServerEnabled)
|
|
199
|
+
installWebAutostart(options);
|
|
200
|
+
else
|
|
201
|
+
removeWebAutostart(options);
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
log("OMMS login item reconciliation failed", {
|
|
205
|
+
code: error instanceof Error ? error.name : "unknown",
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -415,6 +415,17 @@ export class WebServer {
|
|
|
415
415
|
const { getSettingsSnapshot } = await import("./settings-snapshot.js");
|
|
416
416
|
return this.jsonResponse(getSettingsSnapshot(this.config.directory ?? process.cwd()));
|
|
417
417
|
}
|
|
418
|
+
if (path === "/api/settings/backfill" && method === "GET") {
|
|
419
|
+
const { readBackfillStatus } = await import("./backfill-state.js");
|
|
420
|
+
return this.jsonResponse({
|
|
421
|
+
pi: await readBackfillStatus("pi"),
|
|
422
|
+
opencode: await readBackfillStatus("opencode"),
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
if (path === "/api/settings/web-autostart" && method === "GET") {
|
|
426
|
+
const { webAutostartStatus } = await import("./web-autostart.js");
|
|
427
|
+
return this.jsonResponse(webAutostartStatus());
|
|
428
|
+
}
|
|
418
429
|
if (path === "/api/settings" && method === "PATCH") {
|
|
419
430
|
const body = (await req.json());
|
|
420
431
|
if (!body ||
|