om-memory-system 3.0.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/LICENSE +21 -0
- package/README.md +587 -0
- package/dist/adapters/opencode/auto-capture-host.d.ts +3 -0
- package/dist/adapters/opencode/auto-capture-host.js +29 -0
- package/dist/adapters/opencode/auto-capture-summary.d.ts +4 -0
- package/dist/adapters/opencode/auto-capture-summary.js +147 -0
- package/dist/adapters/opencode/conversation.d.ts +2 -0
- package/dist/adapters/opencode/conversation.js +50 -0
- package/dist/adapters/opencode/user-prompt.d.ts +9 -0
- package/dist/adapters/opencode/user-prompt.js +26 -0
- package/dist/adapters/pi/capture.d.ts +41 -0
- package/dist/adapters/pi/capture.js +91 -0
- package/dist/adapters/pi/conversation.d.ts +38 -0
- package/dist/adapters/pi/conversation.js +118 -0
- package/dist/adapters/pi/extension.d.ts +18 -0
- package/dist/adapters/pi/extension.js +234 -0
- package/dist/adapters/pi/import-command.d.ts +29 -0
- package/dist/adapters/pi/import-command.js +232 -0
- package/dist/adapters/pi/profile.d.ts +39 -0
- package/dist/adapters/pi/profile.js +148 -0
- package/dist/adapters/pi/provider.d.ts +50 -0
- package/dist/adapters/pi/provider.js +77 -0
- package/dist/config.d.ts +290 -0
- package/dist/config.js +742 -0
- package/dist/core/capture-context.d.ts +4 -0
- package/dist/core/capture-context.js +132 -0
- package/dist/core/capture.d.ts +15 -0
- package/dist/core/capture.js +68 -0
- package/dist/core/extraction.d.ts +80 -0
- package/dist/core/extraction.js +140 -0
- package/dist/core/host.d.ts +52 -0
- package/dist/core/host.js +1 -0
- package/dist/core/memory-operations.d.ts +27 -0
- package/dist/core/memory-operations.js +247 -0
- package/dist/core/retrieval.d.ts +12 -0
- package/dist/core/retrieval.js +72 -0
- package/dist/importer/discovery.d.ts +31 -0
- package/dist/importer/discovery.js +88 -0
- package/dist/importer/importer.d.ts +87 -0
- package/dist/importer/importer.js +260 -0
- package/dist/importer/ledger.d.ts +44 -0
- package/dist/importer/ledger.js +137 -0
- package/dist/importer/session-loader.d.ts +19 -0
- package/dist/importer/session-loader.js +12 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +601 -0
- package/dist/plugin.d.ts +9 -0
- package/dist/plugin.js +11 -0
- package/dist/services/ai/ai-provider-factory.d.ts +7 -0
- package/dist/services/ai/ai-provider-factory.js +41 -0
- package/dist/services/ai/api-key-placeholder.d.ts +1 -0
- package/dist/services/ai/api-key-placeholder.js +14 -0
- package/dist/services/ai/internal-capture-sessions.d.ts +6 -0
- package/dist/services/ai/internal-capture-sessions.js +33 -0
- package/dist/services/ai/opencode-diagnostics.d.ts +7 -0
- package/dist/services/ai/opencode-diagnostics.js +36 -0
- package/dist/services/ai/opencode-host-config.d.ts +10 -0
- package/dist/services/ai/opencode-host-config.js +51 -0
- package/dist/services/ai/opencode-provider-loader.d.ts +3 -0
- package/dist/services/ai/opencode-provider-loader.js +4 -0
- package/dist/services/ai/opencode-provider.d.ts +92 -0
- package/dist/services/ai/opencode-provider.js +419 -0
- package/dist/services/ai/opencode-sdk-client.d.ts +6 -0
- package/dist/services/ai/opencode-sdk-client.js +38 -0
- package/dist/services/ai/profile-llm-client.d.ts +2 -0
- package/dist/services/ai/profile-llm-client.js +27 -0
- package/dist/services/ai/provider-config.d.ts +17 -0
- package/dist/services/ai/provider-config.js +30 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts +34 -0
- package/dist/services/ai/providers/anthropic-messages.js +220 -0
- package/dist/services/ai/providers/base-provider.d.ts +24 -0
- package/dist/services/ai/providers/base-provider.js +24 -0
- package/dist/services/ai/providers/google-gemini.d.ts +15 -0
- package/dist/services/ai/providers/google-gemini.js +228 -0
- package/dist/services/ai/providers/minimax.d.ts +35 -0
- package/dist/services/ai/providers/minimax.js +55 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts +22 -0
- package/dist/services/ai/providers/openai-chat-completion.js +387 -0
- package/dist/services/ai/providers/openai-responses.d.ts +13 -0
- package/dist/services/ai/providers/openai-responses.js +182 -0
- package/dist/services/ai/providers/orcarouter.d.ts +51 -0
- package/dist/services/ai/providers/orcarouter.js +66 -0
- package/dist/services/ai/session/ai-session-manager.d.ts +24 -0
- package/dist/services/ai/session/ai-session-manager.js +211 -0
- package/dist/services/ai/session/session-types.d.ts +42 -0
- package/dist/services/ai/session/session-types.js +1 -0
- package/dist/services/ai/tools/tool-schema.d.ts +40 -0
- package/dist/services/ai/tools/tool-schema.js +24 -0
- package/dist/services/ai/validators/user-profile-validator.d.ts +12 -0
- package/dist/services/ai/validators/user-profile-validator.js +114 -0
- package/dist/services/api-handlers.d.ts +186 -0
- package/dist/services/api-handlers.js +1284 -0
- package/dist/services/auth-token.d.ts +14 -0
- package/dist/services/auth-token.js +59 -0
- package/dist/services/auto-capture.d.ts +3 -0
- package/dist/services/auto-capture.js +127 -0
- package/dist/services/cleanup-service.d.ts +22 -0
- package/dist/services/cleanup-service.js +109 -0
- package/dist/services/client.d.ts +134 -0
- package/dist/services/client.js +327 -0
- package/dist/services/context.d.ts +10 -0
- package/dist/services/context.js +27 -0
- package/dist/services/cors.d.ts +12 -0
- package/dist/services/cors.js +52 -0
- package/dist/services/deduplication-service.d.ts +28 -0
- package/dist/services/deduplication-service.js +111 -0
- package/dist/services/embedding.d.ts +36 -0
- package/dist/services/embedding.js +200 -0
- package/dist/services/jsonc.d.ts +6 -0
- package/dist/services/jsonc.js +76 -0
- package/dist/services/language-detector.d.ts +2 -0
- package/dist/services/language-detector.js +33 -0
- package/dist/services/legacy-migration.d.ts +77 -0
- package/dist/services/legacy-migration.js +343 -0
- package/dist/services/logger.d.ts +1 -0
- package/dist/services/logger.js +119 -0
- package/dist/services/memory-portability-service.d.ts +37 -0
- package/dist/services/memory-portability-service.js +340 -0
- package/dist/services/memory-scope.d.ts +16 -0
- package/dist/services/memory-scope.js +43 -0
- package/dist/services/migration-service.d.ts +42 -0
- package/dist/services/migration-service.js +307 -0
- package/dist/services/onnxruntime-resolve.d.ts +24 -0
- package/dist/services/onnxruntime-resolve.js +174 -0
- package/dist/services/privacy.d.ts +26 -0
- package/dist/services/privacy.js +69 -0
- package/dist/services/request-timeouts.d.ts +9 -0
- package/dist/services/request-timeouts.js +11 -0
- package/dist/services/runtime-require.d.ts +17 -0
- package/dist/services/runtime-require.js +75 -0
- package/dist/services/secret-resolver.d.ts +1 -0
- package/dist/services/secret-resolver.js +65 -0
- package/dist/services/shard-inventory-service.d.ts +69 -0
- package/dist/services/shard-inventory-service.js +315 -0
- package/dist/services/shard-path-migration-service.d.ts +31 -0
- package/dist/services/shard-path-migration-service.js +367 -0
- package/dist/services/tag-prefix-migration.d.ts +60 -0
- package/dist/services/tag-prefix-migration.js +439 -0
- package/dist/services/tag-prefix-scan.d.ts +26 -0
- package/dist/services/tag-prefix-scan.js +91 -0
- package/dist/services/tags.d.ts +29 -0
- package/dist/services/tags.js +251 -0
- package/dist/services/turso/connection-manager.d.ts +15 -0
- package/dist/services/turso/connection-manager.js +163 -0
- package/dist/services/turso/cross-process-write-lock.d.ts +1 -0
- package/dist/services/turso/cross-process-write-lock.js +89 -0
- package/dist/services/turso/legacy-migrator.d.ts +1 -0
- package/dist/services/turso/legacy-migrator.js +540 -0
- package/dist/services/turso/lifecycle.d.ts +1 -0
- package/dist/services/turso/lifecycle.js +14 -0
- package/dist/services/turso/operation-lock.d.ts +2 -0
- package/dist/services/turso/operation-lock.js +61 -0
- package/dist/services/turso/ready.d.ts +2 -0
- package/dist/services/turso/ready.js +36 -0
- package/dist/services/turso/shard-manager.d.ts +38 -0
- package/dist/services/turso/shard-manager.js +496 -0
- package/dist/services/turso/sqlite-handle-release.d.ts +16 -0
- package/dist/services/turso/sqlite-handle-release.js +56 -0
- package/dist/services/turso/turso-db.d.ts +18 -0
- package/dist/services/turso/turso-db.js +53 -0
- package/dist/services/turso/types.d.ts +42 -0
- package/dist/services/turso/types.js +1 -0
- package/dist/services/turso/vector-search.d.ts +41 -0
- package/dist/services/turso/vector-search.js +384 -0
- package/dist/services/turso/vector-utils.d.ts +7 -0
- package/dist/services/turso/vector-utils.js +67 -0
- package/dist/services/user-memory-learning.d.ts +97 -0
- package/dist/services/user-memory-learning.js +673 -0
- package/dist/services/user-profile/ai-cleanup.d.ts +58 -0
- package/dist/services/user-profile/ai-cleanup.js +486 -0
- package/dist/services/user-profile/profile-context.d.ts +1 -0
- package/dist/services/user-profile/profile-context.js +105 -0
- package/dist/services/user-profile/profile-utils.d.ts +2 -0
- package/dist/services/user-profile/profile-utils.js +45 -0
- package/dist/services/user-profile/types.d.ts +92 -0
- package/dist/services/user-profile/types.js +1 -0
- package/dist/services/user-profile/user-profile-manager.d.ts +65 -0
- package/dist/services/user-profile/user-profile-manager.js +1660 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts +53 -0
- package/dist/services/user-prompt/user-prompt-manager.js +303 -0
- package/dist/services/web-api-auth.d.ts +3 -0
- package/dist/services/web-api-auth.js +25 -0
- package/dist/services/web-auth.d.ts +46 -0
- package/dist/services/web-auth.js +105 -0
- package/dist/services/web-server.d.ts +57 -0
- package/dist/services/web-server.js +659 -0
- package/dist/shared/api/index.d.ts +4 -0
- package/dist/shared/api/index.js +2 -0
- package/dist/shared/api/portability-schemas.d.ts +56 -0
- package/dist/shared/api/portability-schemas.js +39 -0
- package/dist/shared/api/schemas.d.ts +295 -0
- package/dist/shared/api/schemas.js +134 -0
- package/dist/shared/api/types.d.ts +28 -0
- package/dist/shared/api/types.js +1 -0
- package/dist/types/index.d.ts +27 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/context-limit.d.ts +10 -0
- package/dist/utils/context-limit.js +43 -0
- package/dist/utils/math.d.ts +7 -0
- package/dist/utils/math.js +48 -0
- package/dist/utils/profile.d.ts +2 -0
- package/dist/utils/profile.js +15 -0
- package/dist/v2/adapter.d.ts +5 -0
- package/dist/v2/adapter.js +218 -0
- package/dist/v2/legacy-client.d.ts +15 -0
- package/dist/v2/legacy-client.js +257 -0
- package/dist/v2/memory-bridge.d.ts +18 -0
- package/dist/v2/memory-bridge.js +29 -0
- package/dist/v2/plugin.d.ts +3 -0
- package/dist/v2/plugin.js +24 -0
- package/dist/web/assets/index-DPjEpD1h.css +2 -0
- package/dist/web/assets/index-do26c4JJ.js +122 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +14 -0
- package/dist/web/omms-icon.png +0 -0
- package/package.json +138 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
4
|
+
import { join, dirname, extname, normalize, sep } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { log } from "./logger.js";
|
|
7
|
+
import { corsPreflightResponse, disallowedCorsResponse, isAllowedBrowserOrigin } from "./cors.js";
|
|
8
|
+
import { assertWebServerNetworkAuth, authorizeApiRequest } from "./web-api-auth.js";
|
|
9
|
+
import { getOrCreateAuthToken, isAuthorizedApiRequest } from "./auth-token.js";
|
|
10
|
+
import { WebAuth } from "./web-auth.js";
|
|
11
|
+
import { NODE_HTTP_IDLE_TIMEOUT_MS } from "./request-timeouts.js";
|
|
12
|
+
import { handleListTags, handleListMemories, handleAddMemory, handleDeleteMemory, handleBulkDelete, handleUpdateMemory, handleSearch, handleStats, handlePinMemory, handleUnpinMemory, handleRunCleanup, handleRunDeduplication, handleDetectMigration, handleRunMigration, handleDetectTagMigration, handleRunTagMigrationBatch, handleGetTagMigrationProgress, handleDeletePrompt, handleBulkDeletePrompts, handleGetUserProfile, handleGetProfileChangelog, handleGetProfileSnapshot, handleRefreshProfile, handleAICleanup, handleApplyCleanup, handleUpdateProfileItem, } from "./api-handlers.js";
|
|
13
|
+
const isBun = typeof globalThis.Bun !== "undefined";
|
|
14
|
+
const MIN_FAILED_TAKEOVERS = 3;
|
|
15
|
+
export function attachNodeDisconnectHandlers(req, res, onDisconnect) {
|
|
16
|
+
let disconnected = false;
|
|
17
|
+
const disconnectOnce = () => {
|
|
18
|
+
if (disconnected || res.writableEnded)
|
|
19
|
+
return;
|
|
20
|
+
disconnected = true;
|
|
21
|
+
onDisconnect();
|
|
22
|
+
};
|
|
23
|
+
req.on("aborted", disconnectOnce);
|
|
24
|
+
req.on("close", () => {
|
|
25
|
+
if (req.aborted || !req.complete)
|
|
26
|
+
disconnectOnce();
|
|
27
|
+
});
|
|
28
|
+
req.socket.on("error", disconnectOnce);
|
|
29
|
+
req.socket.on("close", disconnectOnce);
|
|
30
|
+
res.on("close", disconnectOnce);
|
|
31
|
+
}
|
|
32
|
+
function serveFetch(opts) {
|
|
33
|
+
if (isBun) {
|
|
34
|
+
const bunHandle = globalThis.Bun.serve({
|
|
35
|
+
port: opts.port,
|
|
36
|
+
hostname: opts.hostname,
|
|
37
|
+
fetch: opts.fetch,
|
|
38
|
+
});
|
|
39
|
+
return { stop: () => bunHandle.stop() };
|
|
40
|
+
}
|
|
41
|
+
// Node path: wrap node:http around the fetch-style handler. The adapter
|
|
42
|
+
// converts IncomingMessage → Web Request and Web Response → ServerResponse.
|
|
43
|
+
// Bodies stream both directions via the WHATWG Streams ↔ Node Streams
|
|
44
|
+
// helpers that ship with Node 18+.
|
|
45
|
+
const server = createServer(async (req, res) => {
|
|
46
|
+
let destroyed = false;
|
|
47
|
+
const abortController = new AbortController();
|
|
48
|
+
const cleanup = () => {
|
|
49
|
+
if (destroyed)
|
|
50
|
+
return;
|
|
51
|
+
destroyed = true;
|
|
52
|
+
abortController.abort();
|
|
53
|
+
if (!res.writableEnded)
|
|
54
|
+
res.destroy();
|
|
55
|
+
if (!req.socket.destroyed)
|
|
56
|
+
req.socket.destroy();
|
|
57
|
+
};
|
|
58
|
+
attachNodeDisconnectHandlers(req, res, cleanup);
|
|
59
|
+
try {
|
|
60
|
+
const url = `http://${opts.hostname}:${opts.port}${req.url ?? "/"}`;
|
|
61
|
+
const method = req.method ?? "GET";
|
|
62
|
+
const hasBody = method !== "GET" && method !== "HEAD";
|
|
63
|
+
const webReq = new Request(url, {
|
|
64
|
+
method,
|
|
65
|
+
headers: req.headers,
|
|
66
|
+
body: hasBody ? Readable.toWeb(req) : undefined,
|
|
67
|
+
signal: abortController.signal,
|
|
68
|
+
...(hasBody ? { duplex: "half" } : {}),
|
|
69
|
+
});
|
|
70
|
+
const webRes = await opts.fetch(webReq);
|
|
71
|
+
if (destroyed)
|
|
72
|
+
return;
|
|
73
|
+
res.statusCode = webRes.status;
|
|
74
|
+
webRes.headers.forEach((value, name) => res.setHeader(name, value));
|
|
75
|
+
res.setHeader("Connection", "close");
|
|
76
|
+
if (webRes.body) {
|
|
77
|
+
const src = Readable.fromWeb(webRes.body);
|
|
78
|
+
res.on("close", () => {
|
|
79
|
+
if (!src.destroyed)
|
|
80
|
+
src.destroy();
|
|
81
|
+
});
|
|
82
|
+
src.pipe(res);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
res.end();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (!res.headersSent) {
|
|
90
|
+
res.statusCode = 500;
|
|
91
|
+
res.setHeader("Content-Type", "text/plain");
|
|
92
|
+
}
|
|
93
|
+
if (!res.writableEnded) {
|
|
94
|
+
res.end(`Internal Server Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
// Surface EADDRINUSE synchronously so callers can detect the
|
|
99
|
+
// already-running-instance case the same way they do under Bun.
|
|
100
|
+
let listenError;
|
|
101
|
+
server.on("error", (err) => {
|
|
102
|
+
if (err.code === "EADDRINUSE") {
|
|
103
|
+
listenError = err;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
// exclusive: false disables SO_EXCLUSIVEADDRUSE on Windows, allowing
|
|
107
|
+
// rebind after a crashed predecessor left orphaned sockets behind.
|
|
108
|
+
server.listen({ port: opts.port, host: opts.hostname, reuseAddr: true, exclusive: false });
|
|
109
|
+
server.unref();
|
|
110
|
+
server.timeout = NODE_HTTP_IDLE_TIMEOUT_MS;
|
|
111
|
+
server.keepAliveTimeout = 10000;
|
|
112
|
+
server.headersTimeout = 11000;
|
|
113
|
+
if (listenError) {
|
|
114
|
+
throw listenError;
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
stop: () => {
|
|
118
|
+
server.closeAllConnections();
|
|
119
|
+
server.close();
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
124
|
+
const __dirname = dirname(__filename);
|
|
125
|
+
/**
|
|
126
|
+
* Port fallback policy for the takeover loop. A port that fails to bind AND
|
|
127
|
+
* answers no HTTP is treated as orphaned Windows kernel residue; after
|
|
128
|
+
* `minFailedTakeovers` consecutive failed takeovers the web server moves to
|
|
129
|
+
* `currentPort + 1`, bounded by `maxFallbackPort`.
|
|
130
|
+
*/
|
|
131
|
+
export function nextFallbackPort(currentPort, failedTakeovers, maxFallbackPort, minFailedTakeovers = MIN_FAILED_TAKEOVERS) {
|
|
132
|
+
if (failedTakeovers < minFailedTakeovers || currentPort >= maxFallbackPort) {
|
|
133
|
+
return currentPort;
|
|
134
|
+
}
|
|
135
|
+
return currentPort + 1;
|
|
136
|
+
}
|
|
137
|
+
export class WebServer {
|
|
138
|
+
server = null;
|
|
139
|
+
config;
|
|
140
|
+
isOwner = false;
|
|
141
|
+
startPromise = null;
|
|
142
|
+
healthCheckInterval = null;
|
|
143
|
+
onTakeoverCallback = null;
|
|
144
|
+
onPortsExhaustedCallback = null;
|
|
145
|
+
portsExhaustedNotified = false;
|
|
146
|
+
takeoverFailures = 0;
|
|
147
|
+
maxFallbackPort;
|
|
148
|
+
constructor(config) {
|
|
149
|
+
this.config = config;
|
|
150
|
+
this.maxFallbackPort = config.port + 10;
|
|
151
|
+
}
|
|
152
|
+
setOnTakeoverCallback(callback) {
|
|
153
|
+
this.onTakeoverCallback = callback;
|
|
154
|
+
}
|
|
155
|
+
setOnPortsExhaustedCallback(callback) {
|
|
156
|
+
this.onPortsExhaustedCallback = callback;
|
|
157
|
+
}
|
|
158
|
+
async start() {
|
|
159
|
+
if (this.startPromise) {
|
|
160
|
+
return this.startPromise;
|
|
161
|
+
}
|
|
162
|
+
this.startPromise = this._start();
|
|
163
|
+
return this.startPromise;
|
|
164
|
+
}
|
|
165
|
+
async _start() {
|
|
166
|
+
if (!this.config.enabled) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
assertWebServerNetworkAuth(this.config.host, this.config.apiToken, this.config.auth?.isEnabled() ?? false);
|
|
170
|
+
try {
|
|
171
|
+
this.server = serveFetch({
|
|
172
|
+
port: this.config.port,
|
|
173
|
+
hostname: this.config.host,
|
|
174
|
+
fetch: this.handleRequest.bind(this),
|
|
175
|
+
});
|
|
176
|
+
this.isOwner = true;
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
const errorMsg = String(error);
|
|
180
|
+
if (errorMsg.includes("EADDRINUSE") ||
|
|
181
|
+
errorMsg.includes("address already in use") ||
|
|
182
|
+
/Failed to start server.*Is port \d+ in use/.test(errorMsg)) {
|
|
183
|
+
this.isOwner = false;
|
|
184
|
+
this.server = null;
|
|
185
|
+
this.startHealthCheckLoop();
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
this.isOwner = false;
|
|
189
|
+
this.server = null;
|
|
190
|
+
log("Web server failed to start", { error: errorMsg });
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
startHealthCheckLoop() {
|
|
196
|
+
if (this.healthCheckInterval) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
this.healthCheckInterval = setInterval(async () => {
|
|
200
|
+
const isAvailable = await this.checkServerAvailable();
|
|
201
|
+
if (!isAvailable) {
|
|
202
|
+
this.stopHealthCheckLoop();
|
|
203
|
+
await this.attemptTakeover();
|
|
204
|
+
}
|
|
205
|
+
}, 5000);
|
|
206
|
+
}
|
|
207
|
+
stopHealthCheckLoop() {
|
|
208
|
+
if (this.healthCheckInterval) {
|
|
209
|
+
clearInterval(this.healthCheckInterval);
|
|
210
|
+
this.healthCheckInterval = null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async attemptTakeover() {
|
|
214
|
+
// prevent thundering herd: multiple non-owners racing to bind port
|
|
215
|
+
const jitterMs = 500 + Math.random() * 1000;
|
|
216
|
+
await new Promise((resolve) => setTimeout(resolve, jitterMs));
|
|
217
|
+
if (await this.checkServerAvailable()) {
|
|
218
|
+
// The original owner recovered. Reset the failure counter so a stale
|
|
219
|
+
// count can't advance the port on the next, unrelated failure.
|
|
220
|
+
this.takeoverFailures = 0;
|
|
221
|
+
this.startHealthCheckLoop();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// Windows can leave an orphaned LISTEN socket in the TCP table after a
|
|
225
|
+
// crash: the port refuses to bind (EADDRINUSE) yet nothing answers HTTP,
|
|
226
|
+
// so repeated takeovers fail forever. After a few consecutive failures,
|
|
227
|
+
// fall back to a free neighbor port instead of looping.
|
|
228
|
+
this.takeoverFailures += 1;
|
|
229
|
+
const nextPort = nextFallbackPort(this.config.port, this.takeoverFailures, this.maxFallbackPort);
|
|
230
|
+
if (nextPort !== this.config.port) {
|
|
231
|
+
this.takeoverFailures = 0;
|
|
232
|
+
log("Web server port held by a non-responsive process; falling back to next port", {
|
|
233
|
+
previousPort: this.config.port,
|
|
234
|
+
newPort: nextPort,
|
|
235
|
+
});
|
|
236
|
+
this.config.port = nextPort;
|
|
237
|
+
}
|
|
238
|
+
else if (this.config.port >= this.maxFallbackPort &&
|
|
239
|
+
this.takeoverFailures >= MIN_FAILED_TAKEOVERS) {
|
|
240
|
+
// Every candidate port is held by a non-responsive process. Stop the
|
|
241
|
+
// health loop instead of retrying every five seconds forever.
|
|
242
|
+
this.stopHealthCheckLoop();
|
|
243
|
+
this.notifyPortsExhausted();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
// Reset startPromise so _start() can run again
|
|
248
|
+
this.startPromise = null;
|
|
249
|
+
await this._start();
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
this.startHealthCheckLoop();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (this.isOwner) {
|
|
256
|
+
this.takeoverFailures = 0;
|
|
257
|
+
log("Web server takeover successful", { port: this.config.port });
|
|
258
|
+
if (this.onTakeoverCallback) {
|
|
259
|
+
try {
|
|
260
|
+
await this.onTakeoverCallback();
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
log("Takeover callback error", { error: String(error) });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
notifyPortsExhausted() {
|
|
269
|
+
if (this.portsExhaustedNotified)
|
|
270
|
+
return;
|
|
271
|
+
this.portsExhaustedNotified = true;
|
|
272
|
+
log("Web server unavailable: every candidate port is held by a non-responsive process", {
|
|
273
|
+
port: this.config.port,
|
|
274
|
+
maxFallbackPort: this.maxFallbackPort,
|
|
275
|
+
});
|
|
276
|
+
try {
|
|
277
|
+
this.onPortsExhaustedCallback?.();
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
log("Ports exhausted callback error", { error: String(error) });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async stop() {
|
|
284
|
+
this.stopHealthCheckLoop();
|
|
285
|
+
if (!this.isOwner || !this.server) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
this.server.stop();
|
|
289
|
+
this.server = null;
|
|
290
|
+
this.isOwner = false;
|
|
291
|
+
}
|
|
292
|
+
isRunning() {
|
|
293
|
+
return this.server !== null;
|
|
294
|
+
}
|
|
295
|
+
isServerOwner() {
|
|
296
|
+
return this.isOwner;
|
|
297
|
+
}
|
|
298
|
+
getUrl() {
|
|
299
|
+
return `http://${this.config.host}:${this.config.port}`;
|
|
300
|
+
}
|
|
301
|
+
async checkServerAvailable() {
|
|
302
|
+
try {
|
|
303
|
+
const headers = this.config.apiToken
|
|
304
|
+
? { Authorization: `Bearer ${this.config.apiToken}` }
|
|
305
|
+
: undefined;
|
|
306
|
+
const endpoint = this.config.apiToken ? "/api/stats" : "/api/health";
|
|
307
|
+
const response = await fetch(`${this.getUrl()}${endpoint}`, {
|
|
308
|
+
method: "GET",
|
|
309
|
+
headers,
|
|
310
|
+
signal: AbortSignal.timeout(2000),
|
|
311
|
+
});
|
|
312
|
+
if (!response.ok)
|
|
313
|
+
return false;
|
|
314
|
+
// Fallback spans 10 neighbor ports; any 2xx from an unrelated local
|
|
315
|
+
// service must not be mistaken for an omms owner. Require the
|
|
316
|
+
// response to carry our API envelope.
|
|
317
|
+
const body = (await response.json());
|
|
318
|
+
if (endpoint === "/api/health") {
|
|
319
|
+
return body.success === true && body.status === "ok";
|
|
320
|
+
}
|
|
321
|
+
return body.success === true;
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// --- HTTP request handling ---
|
|
328
|
+
async handleRequest(req) {
|
|
329
|
+
const url = new URL(req.url);
|
|
330
|
+
const path = url.pathname;
|
|
331
|
+
const method = req.method;
|
|
332
|
+
const origin = req.headers.get("Origin");
|
|
333
|
+
const auth = this.config.auth;
|
|
334
|
+
const corsOptions = { httpAuthEnabled: auth?.isEnabled() ?? false };
|
|
335
|
+
if (!isAllowedBrowserOrigin(origin, corsOptions)) {
|
|
336
|
+
return disallowedCorsResponse();
|
|
337
|
+
}
|
|
338
|
+
if (method === "OPTIONS") {
|
|
339
|
+
return corsPreflightResponse(req, corsOptions);
|
|
340
|
+
}
|
|
341
|
+
if (auth?.isEnabled()) {
|
|
342
|
+
const authCheck = auth.check(req, path);
|
|
343
|
+
if (!authCheck.ok && authCheck.response)
|
|
344
|
+
return authCheck.response;
|
|
345
|
+
}
|
|
346
|
+
if (path.startsWith("/api/") && path !== "/api/health" && !isAuthorizedApiRequest(req)) {
|
|
347
|
+
const configuredTokenFailure = this.config.apiToken
|
|
348
|
+
? authorizeApiRequest(req, this.config.apiToken)
|
|
349
|
+
: null;
|
|
350
|
+
if (!this.config.apiToken || configuredTokenFailure) {
|
|
351
|
+
return (configuredTokenFailure ??
|
|
352
|
+
this.jsonResponse({ success: false, error: "Unauthorized" }, 401));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
if (path === "/api/health" && method === "GET") {
|
|
357
|
+
return this.jsonResponse({
|
|
358
|
+
success: true,
|
|
359
|
+
status: "ok",
|
|
360
|
+
authEnabled: auth?.isEnabled() ?? false,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
if (path === "/" || path === "/index.html") {
|
|
364
|
+
return this.serveStaticFile("index.html", "text/html");
|
|
365
|
+
}
|
|
366
|
+
if (path === "/favicon.ico") {
|
|
367
|
+
return this.serveStaticFile("favicon.ico", "image/x-icon");
|
|
368
|
+
}
|
|
369
|
+
// Vite production assets (hashed JS/CSS/fonts) and other static files.
|
|
370
|
+
if (method === "GET" && !path.startsWith("/api/")) {
|
|
371
|
+
const relative = path.replace(/^\/+/, "");
|
|
372
|
+
if (relative && !relative.includes("..")) {
|
|
373
|
+
const staticResponse = this.serveStaticFile(relative, this.contentTypeFor(relative));
|
|
374
|
+
if (staticResponse.status !== 404) {
|
|
375
|
+
return staticResponse;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (path === "/api/tags" && method === "GET") {
|
|
380
|
+
const result = await handleListTags();
|
|
381
|
+
return this.jsonResponse(result);
|
|
382
|
+
}
|
|
383
|
+
if (path === "/api/memories" && method === "GET") {
|
|
384
|
+
const tag = url.searchParams.get("tag") || undefined;
|
|
385
|
+
const page = parseInt(url.searchParams.get("page") || "1");
|
|
386
|
+
const pageSize = parseInt(url.searchParams.get("pageSize") || "20");
|
|
387
|
+
const includePrompts = url.searchParams.get("includePrompts") !== "false";
|
|
388
|
+
const result = await handleListMemories(tag, page, pageSize, includePrompts);
|
|
389
|
+
return this.jsonResponse(result);
|
|
390
|
+
}
|
|
391
|
+
if (path === "/api/memories" && method === "POST") {
|
|
392
|
+
const body = (await req.json());
|
|
393
|
+
const result = await handleAddMemory(body);
|
|
394
|
+
return this.jsonResponse(result);
|
|
395
|
+
}
|
|
396
|
+
if (path.startsWith("/api/memories/") && method === "DELETE") {
|
|
397
|
+
const parts = path.split("/");
|
|
398
|
+
const id = parts[3];
|
|
399
|
+
if (!id || id === "bulk-delete") {
|
|
400
|
+
return this.jsonResponse({ success: false, error: "Invalid ID" });
|
|
401
|
+
}
|
|
402
|
+
const cascade = url.searchParams.get("cascade") === "true";
|
|
403
|
+
const result = await handleDeleteMemory(id, cascade);
|
|
404
|
+
return this.jsonResponse(result);
|
|
405
|
+
}
|
|
406
|
+
if (path.startsWith("/api/memories/") && method === "PUT") {
|
|
407
|
+
const id = path.split("/").pop();
|
|
408
|
+
if (!id) {
|
|
409
|
+
return this.jsonResponse({ success: false, error: "Invalid ID" });
|
|
410
|
+
}
|
|
411
|
+
const body = (await req.json());
|
|
412
|
+
const result = await handleUpdateMemory(id, body);
|
|
413
|
+
return this.jsonResponse(result);
|
|
414
|
+
}
|
|
415
|
+
if (path === "/api/memories/bulk-delete" && method === "POST") {
|
|
416
|
+
const body = (await req.json());
|
|
417
|
+
const cascade = body.cascade !== false;
|
|
418
|
+
const result = await handleBulkDelete(body.ids || [], cascade);
|
|
419
|
+
return this.jsonResponse(result);
|
|
420
|
+
}
|
|
421
|
+
if (path === "/api/search" && method === "GET") {
|
|
422
|
+
const query = url.searchParams.get("q");
|
|
423
|
+
const tag = url.searchParams.get("tag") || undefined;
|
|
424
|
+
const page = parseInt(url.searchParams.get("page") || "1");
|
|
425
|
+
const pageSize = parseInt(url.searchParams.get("pageSize") || "20");
|
|
426
|
+
if (!query) {
|
|
427
|
+
return this.jsonResponse({ success: false, error: "query parameter required" });
|
|
428
|
+
}
|
|
429
|
+
const result = await handleSearch(query, tag, page, pageSize);
|
|
430
|
+
return this.jsonResponse(result);
|
|
431
|
+
}
|
|
432
|
+
if (path === "/api/stats" && method === "GET") {
|
|
433
|
+
const result = await handleStats();
|
|
434
|
+
return this.jsonResponse(result);
|
|
435
|
+
}
|
|
436
|
+
if (path.match(/^\/api\/memories\/[^/]+\/pin$/) && method === "POST") {
|
|
437
|
+
const id = path.split("/")[3];
|
|
438
|
+
if (!id) {
|
|
439
|
+
return this.jsonResponse({ success: false, error: "Invalid ID" });
|
|
440
|
+
}
|
|
441
|
+
const result = await handlePinMemory(id);
|
|
442
|
+
return this.jsonResponse(result);
|
|
443
|
+
}
|
|
444
|
+
if (path.match(/^\/api\/memories\/[^/]+\/unpin$/) && method === "POST") {
|
|
445
|
+
const id = path.split("/")[3];
|
|
446
|
+
if (!id) {
|
|
447
|
+
return this.jsonResponse({ success: false, error: "Invalid ID" });
|
|
448
|
+
}
|
|
449
|
+
const result = await handleUnpinMemory(id);
|
|
450
|
+
return this.jsonResponse(result);
|
|
451
|
+
}
|
|
452
|
+
if (path === "/api/cleanup" && method === "POST") {
|
|
453
|
+
const result = await handleRunCleanup();
|
|
454
|
+
return this.jsonResponse(result);
|
|
455
|
+
}
|
|
456
|
+
if (path === "/api/deduplicate" && method === "POST") {
|
|
457
|
+
const result = await handleRunDeduplication();
|
|
458
|
+
return this.jsonResponse(result);
|
|
459
|
+
}
|
|
460
|
+
if (path === "/api/migration/detect" && method === "GET") {
|
|
461
|
+
const result = await handleDetectMigration();
|
|
462
|
+
return this.jsonResponse(result);
|
|
463
|
+
}
|
|
464
|
+
if (path === "/api/migration/tags/detect" && method === "GET") {
|
|
465
|
+
const result = await handleDetectTagMigration();
|
|
466
|
+
return this.jsonResponse(result);
|
|
467
|
+
}
|
|
468
|
+
if (path === "/api/migration/tags/run-batch" && method === "POST") {
|
|
469
|
+
const body = (await req.json());
|
|
470
|
+
const batchSize = body?.batchSize || 5;
|
|
471
|
+
const result = await handleRunTagMigrationBatch(batchSize);
|
|
472
|
+
return this.jsonResponse(result);
|
|
473
|
+
}
|
|
474
|
+
if (path === "/api/migration/tags/progress" && method === "GET") {
|
|
475
|
+
const result = await handleGetTagMigrationProgress();
|
|
476
|
+
return this.jsonResponse(result);
|
|
477
|
+
}
|
|
478
|
+
if (path === "/api/migration/run" && method === "POST") {
|
|
479
|
+
const body = (await req.json());
|
|
480
|
+
const strategy = body.strategy || "fresh-start";
|
|
481
|
+
if (strategy !== "fresh-start" && strategy !== "re-embed") {
|
|
482
|
+
return this.jsonResponse({ success: false, error: "Invalid strategy" });
|
|
483
|
+
}
|
|
484
|
+
const result = await handleRunMigration(strategy);
|
|
485
|
+
return this.jsonResponse(result);
|
|
486
|
+
}
|
|
487
|
+
if (path.startsWith("/api/prompts/") && method === "DELETE") {
|
|
488
|
+
const parts = path.split("/");
|
|
489
|
+
const id = parts[3];
|
|
490
|
+
if (!id || id === "bulk-delete") {
|
|
491
|
+
return this.jsonResponse({ success: false, error: "Invalid ID" });
|
|
492
|
+
}
|
|
493
|
+
const cascade = url.searchParams.get("cascade") === "true";
|
|
494
|
+
const result = await handleDeletePrompt(id, cascade);
|
|
495
|
+
return this.jsonResponse(result);
|
|
496
|
+
}
|
|
497
|
+
if (path === "/api/prompts/bulk-delete" && method === "POST") {
|
|
498
|
+
const body = (await req.json());
|
|
499
|
+
const cascade = body.cascade !== false;
|
|
500
|
+
const result = await handleBulkDeletePrompts(body.ids || [], cascade);
|
|
501
|
+
return this.jsonResponse(result);
|
|
502
|
+
}
|
|
503
|
+
if (path === "/api/user-profile" && method === "GET") {
|
|
504
|
+
const userId = url.searchParams.get("userId") || undefined;
|
|
505
|
+
const result = await handleGetUserProfile(userId);
|
|
506
|
+
return this.jsonResponse(result);
|
|
507
|
+
}
|
|
508
|
+
if (path === "/api/user-profile/changelog" && method === "GET") {
|
|
509
|
+
const profileId = url.searchParams.get("profileId");
|
|
510
|
+
const limit = parseInt(url.searchParams.get("limit") || "5");
|
|
511
|
+
if (!profileId) {
|
|
512
|
+
return this.jsonResponse({ success: false, error: "profileId parameter required" });
|
|
513
|
+
}
|
|
514
|
+
const result = await handleGetProfileChangelog(profileId, limit);
|
|
515
|
+
return this.jsonResponse(result);
|
|
516
|
+
}
|
|
517
|
+
if (path === "/api/user-profile/snapshot" && method === "GET") {
|
|
518
|
+
const changelogId = url.searchParams.get("chlogId");
|
|
519
|
+
if (!changelogId) {
|
|
520
|
+
return this.jsonResponse({ success: false, error: "changelogId parameter required" });
|
|
521
|
+
}
|
|
522
|
+
const result = await handleGetProfileSnapshot(changelogId);
|
|
523
|
+
return this.jsonResponse(result);
|
|
524
|
+
}
|
|
525
|
+
if (path === "/api/user-profile/refresh" && method === "POST") {
|
|
526
|
+
const body = (await req.json().catch(() => ({})));
|
|
527
|
+
const userId = body.userId || undefined;
|
|
528
|
+
const result = await handleRefreshProfile(userId);
|
|
529
|
+
return this.jsonResponse(result);
|
|
530
|
+
}
|
|
531
|
+
if (path === "/api/user-profile/ai-cleanup" && method === "POST") {
|
|
532
|
+
const body = (await req.json().catch(() => ({})));
|
|
533
|
+
const userId = body.userId || undefined;
|
|
534
|
+
const includeIds = Array.isArray(body.includeIds)
|
|
535
|
+
? body.includeIds
|
|
536
|
+
: undefined;
|
|
537
|
+
const profileVersion = typeof body.profileVersion === "number" ? body.profileVersion : undefined;
|
|
538
|
+
const result = await handleAICleanup(userId, includeIds, profileVersion);
|
|
539
|
+
return this.jsonResponse(result);
|
|
540
|
+
}
|
|
541
|
+
if (path === "/api/user-profile/ai-cleanup/apply" && method === "POST") {
|
|
542
|
+
const body = (await req.json().catch(() => ({})));
|
|
543
|
+
const userId = body.userId || undefined;
|
|
544
|
+
const result = await handleApplyCleanup(userId, body);
|
|
545
|
+
return this.jsonResponse(result);
|
|
546
|
+
}
|
|
547
|
+
if (path === "/api/user-profile/item" && method === "PATCH") {
|
|
548
|
+
const body = (await req.json().catch(() => ({})));
|
|
549
|
+
const result = await handleUpdateProfileItem(body);
|
|
550
|
+
return this.jsonResponse(result);
|
|
551
|
+
}
|
|
552
|
+
// SPA fallback for client routes (e.g. /profile) — serve the app shell.
|
|
553
|
+
if (method === "GET" && !path.startsWith("/api/") && !extname(path)) {
|
|
554
|
+
return this.serveStaticFile("index.html", "text/html");
|
|
555
|
+
}
|
|
556
|
+
return new Response("Not Found", { status: 404 });
|
|
557
|
+
}
|
|
558
|
+
catch (error) {
|
|
559
|
+
return this.jsonResponse({
|
|
560
|
+
success: false,
|
|
561
|
+
error: String(error),
|
|
562
|
+
}, 500);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
contentTypeFor(filename) {
|
|
566
|
+
switch (extname(filename).toLowerCase()) {
|
|
567
|
+
case ".html":
|
|
568
|
+
return "text/html";
|
|
569
|
+
case ".js":
|
|
570
|
+
case ".mjs":
|
|
571
|
+
return "application/javascript";
|
|
572
|
+
case ".css":
|
|
573
|
+
return "text/css";
|
|
574
|
+
case ".ico":
|
|
575
|
+
return "image/x-icon";
|
|
576
|
+
case ".svg":
|
|
577
|
+
return "image/svg+xml";
|
|
578
|
+
case ".png":
|
|
579
|
+
return "image/png";
|
|
580
|
+
case ".jpg":
|
|
581
|
+
case ".jpeg":
|
|
582
|
+
return "image/jpeg";
|
|
583
|
+
case ".woff":
|
|
584
|
+
return "font/woff";
|
|
585
|
+
case ".woff2":
|
|
586
|
+
return "font/woff2";
|
|
587
|
+
case ".ttf":
|
|
588
|
+
return "font/ttf";
|
|
589
|
+
case ".json":
|
|
590
|
+
return "application/json";
|
|
591
|
+
case ".map":
|
|
592
|
+
return "application/json";
|
|
593
|
+
default:
|
|
594
|
+
return "application/octet-stream";
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
serveStaticFile(filename, contentType) {
|
|
598
|
+
try {
|
|
599
|
+
const webDir = [
|
|
600
|
+
join(__dirname, "..", "web"),
|
|
601
|
+
join(__dirname, "..", "..", "dist", "web"),
|
|
602
|
+
].find((candidate) => existsSync(candidate));
|
|
603
|
+
if (!webDir) {
|
|
604
|
+
return new Response("File not found", { status: 404 });
|
|
605
|
+
}
|
|
606
|
+
const filePath = normalize(join(webDir, filename));
|
|
607
|
+
const normalizedWebDir = normalize(webDir);
|
|
608
|
+
if (filePath !== normalizedWebDir && !filePath.startsWith(`${normalizedWebDir}${sep}`)) {
|
|
609
|
+
return new Response("File not found", { status: 404 });
|
|
610
|
+
}
|
|
611
|
+
if (!existsSync(filePath)) {
|
|
612
|
+
return new Response("File not found", { status: 404 });
|
|
613
|
+
}
|
|
614
|
+
const cacheControl = filename.startsWith("assets/") || contentType.startsWith("font/")
|
|
615
|
+
? "public, max-age=31536000, immutable"
|
|
616
|
+
: contentType.startsWith("image/")
|
|
617
|
+
? "public, max-age=86400"
|
|
618
|
+
: "no-cache";
|
|
619
|
+
if (contentType.startsWith("image/") ||
|
|
620
|
+
contentType.startsWith("font/") ||
|
|
621
|
+
contentType === "application/octet-stream") {
|
|
622
|
+
const content = readFileSync(filePath);
|
|
623
|
+
return new Response(content, {
|
|
624
|
+
headers: {
|
|
625
|
+
"Content-Type": contentType,
|
|
626
|
+
"Cache-Control": cacheControl,
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
let content = readFileSync(filePath, "utf-8");
|
|
631
|
+
if (filename === "index.html" || filename.endsWith("/index.html")) {
|
|
632
|
+
const token = getOrCreateAuthToken();
|
|
633
|
+
content = content.replace("</head>", `<script>window.__OMMS_TOKEN__=${JSON.stringify(token)};</script></head>`);
|
|
634
|
+
}
|
|
635
|
+
return new Response(content, {
|
|
636
|
+
headers: {
|
|
637
|
+
"Content-Type": contentType,
|
|
638
|
+
"Cache-Control": cacheControl,
|
|
639
|
+
},
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
catch {
|
|
643
|
+
return new Response("File not found", { status: 404 });
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
jsonResponse(data, status = 200) {
|
|
647
|
+
return new Response(JSON.stringify(data), {
|
|
648
|
+
status,
|
|
649
|
+
headers: {
|
|
650
|
+
"Content-Type": "application/json",
|
|
651
|
+
},
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
export async function startWebServer(config) {
|
|
656
|
+
const server = new WebServer(config);
|
|
657
|
+
await server.start();
|
|
658
|
+
return server;
|
|
659
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { AddMemoryRequestSchema, AICleanupRequestSchema, ApiResultSchema, ApplyCleanupRequestSchema, BulkIdsRequestSchema, CleanupChangesSchema, MemoryItemSchema, PaginatedMemoriesSchema, PendingCleanupSchema, ProfileDataSchema, ProfileItemSchema, TagInfoSchema, UpdateMemoryRequestSchema, UpdateProfileItemRequestSchema, UserProfileSchema, } from "./schemas.js";
|
|
2
|
+
export { ExportedMemorySchema, MemoryExportDocumentSchema, PORTABILITY_SCHEMA_VERSION, } from "./portability-schemas.js";
|
|
3
|
+
export type { ExportedMemory, MemoryExportDocument } from "./portability-schemas.js";
|
|
4
|
+
export type { AddMemoryRequest, AICleanupRequest, ApiResult, ApplyCleanupRequest, BulkIdsRequest, CleanupChanges, MemoryGroup, MemoryItem, PaginatedMemories, PendingCleanup, ProfileData, ProfileItem, TagInfo, UpdateMemoryRequest, UpdateProfileItemRequest, UserProfile, } from "./types.js";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { AddMemoryRequestSchema, AICleanupRequestSchema, ApiResultSchema, ApplyCleanupRequestSchema, BulkIdsRequestSchema, CleanupChangesSchema, MemoryItemSchema, PaginatedMemoriesSchema, PendingCleanupSchema, ProfileDataSchema, ProfileItemSchema, TagInfoSchema, UpdateMemoryRequestSchema, UpdateProfileItemRequestSchema, UserProfileSchema, } from "./schemas.js";
|
|
2
|
+
export { ExportedMemorySchema, MemoryExportDocumentSchema, PORTABILITY_SCHEMA_VERSION, } from "./portability-schemas.js";
|