@hasna/mementos 0.15.2 → 0.16.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/README.md +75 -21
- package/dist/audit-contract.d.ts +87 -0
- package/dist/audit-contract.d.ts.map +1 -0
- package/dist/cli/commands/info-context.d.ts.map +1 -1
- package/dist/cli/commands/info-history.d.ts.map +1 -1
- package/dist/cli/commands/io-backup.d.ts.map +1 -1
- package/dist/cli/commands/io-restore.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-list.d.ts +25 -0
- package/dist/cli/commands/memory-cmd-list.d.ts.map +1 -1
- package/dist/cli/commands/project.d.ts.map +1 -1
- package/dist/cli/commands/system-session.d.ts.map +1 -1
- package/dist/cli/commands/system-synthesized-profile.d.ts.map +1 -1
- package/dist/cli/index.js +2093 -740
- package/dist/db/api-mode.d.ts +10 -0
- package/dist/db/api-mode.d.ts.map +1 -1
- package/dist/db/api-response-contract.d.ts +23 -0
- package/dist/db/api-response-contract.d.ts.map +1 -0
- package/dist/db/audit.d.ts +34 -45
- package/dist/db/audit.d.ts.map +1 -1
- package/dist/db/database.d.ts +1 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/locks.d.ts.map +1 -1
- package/dist/db/machines.d.ts +50 -13
- package/dist/db/machines.d.ts.map +1 -1
- package/dist/db/memories.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/pg-migrate.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/session-jobs.d.ts +2 -0
- package/dist/db/session-jobs.d.ts.map +1 -1
- package/dist/diagnostics/historical-project-registration-receipt.js +215 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1523 -206
- package/dist/lib/built-in-hooks.d.ts.map +1 -1
- package/dist/lib/injection-project.d.ts +10 -0
- package/dist/lib/injection-project.d.ts.map +1 -0
- package/dist/lib/injector.d.ts +1 -0
- package/dist/lib/injector.d.ts.map +1 -1
- package/dist/lib/machine-visibility.d.ts.map +1 -1
- package/dist/lib/memory-lock.d.ts.map +1 -1
- package/dist/lib/profile-synthesizer.d.ts +13 -2
- package/dist/lib/profile-synthesizer.d.ts.map +1 -1
- package/dist/lib/session-queue.d.ts.map +1 -1
- package/dist/lib/session-registry.d.ts +38 -1
- package/dist/lib/session-registry.d.ts.map +1 -1
- package/dist/lib/storage-sync.d.ts +5 -0
- package/dist/lib/storage-sync.d.ts.map +1 -1
- package/dist/lib/synthesis/index.d.ts.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +3828 -1776
- package/dist/mcp/profile.d.ts +23 -0
- package/dist/mcp/profile.d.ts.map +1 -0
- package/dist/mcp/tools/lock-tools.d.ts.map +1 -1
- package/dist/mcp/tools/memory-audit.d.ts +1 -1
- package/dist/mcp/tools/memory-audit.d.ts.map +1 -1
- package/dist/mcp/tools/memory-crud.d.ts.map +1 -1
- package/dist/mcp/tools/memory-inject.d.ts.map +1 -1
- package/dist/mcp/tools/project-tools.d.ts.map +1 -1
- package/dist/mcp/tools/session-tools.d.ts.map +1 -1
- package/dist/mcp/tools/tool-registry.d.ts +22 -19
- package/dist/mcp/tools/tool-registry.d.ts.map +1 -1
- package/dist/mcp/tools/utility-tools.d.ts +2 -1
- package/dist/mcp/tools/utility-tools.d.ts.map +1 -1
- package/dist/project-registration.js +276 -20
- package/dist/sdk/index.d.ts +109 -6
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +740 -25
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3195 -1134
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/routes/audit.d.ts +2 -0
- package/dist/server/routes/audit.d.ts.map +1 -0
- package/dist/server/routes/machines.d.ts +2 -0
- package/dist/server/routes/machines.d.ts.map +1 -0
- package/dist/server/routes/system-sessions.d.ts.map +1 -1
- package/dist/server/routes/system-synthesis.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/hasna.contract.json +2 -2
- package/package.json +8 -5
- package/dist/cli/__fixtures__/clean-fallback-stub-server.d.ts +0 -2
- package/dist/cli/__fixtures__/clean-fallback-stub-server.d.ts.map +0 -1
- package/dist/cli/__fixtures__/io-restore-stub-server.d.ts +0 -2
- package/dist/cli/__fixtures__/io-restore-stub-server.d.ts.map +0 -1
- package/dist/db/__fixtures__/fail-closed-stub-server.d.ts +0 -2
- package/dist/db/__fixtures__/fail-closed-stub-server.d.ts.map +0 -1
- package/dist/db/__fixtures__/list-filter-capture-server.d.ts +0 -2
- package/dist/db/__fixtures__/list-filter-capture-server.d.ts.map +0 -1
- package/dist/db/__fixtures__/list-filter-client-runner.d.ts +0 -2
- package/dist/db/__fixtures__/list-filter-client-runner.d.ts.map +0 -1
- package/dist/test-support/memories-page-stub-server.d.ts +0 -2
- package/dist/test-support/memories-page-stub-server.d.ts.map +0 -1
- package/dist/test-support/memories-page-stub.d.ts +0 -28
- package/dist/test-support/memories-page-stub.d.ts.map +0 -1
- package/dist/test-support/pg-sync-stub-worker.d.ts +0 -2
- package/dist/test-support/pg-sync-stub-worker.d.ts.map +0 -1
- package/dist/test-support/preload-local-store.d.ts +0 -2
- package/dist/test-support/preload-local-store.d.ts.map +0 -1
- package/dist/test-support/project-authority-identity.d.ts +0 -8
- package/dist/test-support/project-authority-identity.d.ts.map +0 -1
- package/dist/test-support/store-isolation.d.ts +0 -147
- package/dist/test-support/store-isolation.d.ts.map +0 -1
- package/dist/test-support/strip-ansi.d.ts +0 -3
- package/dist/test-support/strip-ansi.d.ts.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -2176,312 +2176,6 @@ var init_local_opt_in = __esm(() => {
|
|
|
2176
2176
|
CONTRACTS_AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
2177
2177
|
});
|
|
2178
2178
|
|
|
2179
|
-
// src/db/api-mode.ts
|
|
2180
|
-
import { tmpdir } from "os";
|
|
2181
|
-
import { join } from "path";
|
|
2182
|
-
import { writeFileSync, unlinkSync } from "fs";
|
|
2183
|
-
import { randomUUID } from "crypto";
|
|
2184
|
-
import {
|
|
2185
|
-
clientTransportEnvKeys as clientTransportEnvKeys2,
|
|
2186
|
-
resolveClientTransport,
|
|
2187
|
-
resolveCredential,
|
|
2188
|
-
toV1BaseUrl,
|
|
2189
|
-
ClientTransportConfigurationError
|
|
2190
|
-
} from "@hasna/contracts/client";
|
|
2191
|
-
function firstEnv(keys, env = process.env) {
|
|
2192
|
-
for (const k of keys) {
|
|
2193
|
-
const v = env[k]?.trim();
|
|
2194
|
-
if (v)
|
|
2195
|
-
return v;
|
|
2196
|
-
}
|
|
2197
|
-
return;
|
|
2198
|
-
}
|
|
2199
|
-
function firstEnvKey(keys, env = process.env) {
|
|
2200
|
-
for (const k of keys) {
|
|
2201
|
-
if (env[k]?.trim())
|
|
2202
|
-
return k;
|
|
2203
|
-
}
|
|
2204
|
-
return null;
|
|
2205
|
-
}
|
|
2206
|
-
function hasDatabaseUrl(env = process.env) {
|
|
2207
|
-
return Boolean(firstEnv(DATABASE_URL_ENV_KEYS, env));
|
|
2208
|
-
}
|
|
2209
|
-
function getApiModeEnvSources(env = process.env) {
|
|
2210
|
-
return {
|
|
2211
|
-
urlKey: firstEnvKey(API_URL_ENV_KEYS, env),
|
|
2212
|
-
keyKey: firstEnvKey(API_KEY_ENV_KEYS, env),
|
|
2213
|
-
databaseUrlKey: firstEnvKey(DATABASE_URL_ENV_KEYS, env)
|
|
2214
|
-
};
|
|
2215
|
-
}
|
|
2216
|
-
function assertCleanResolvedBase(baseUrl) {
|
|
2217
|
-
if (/[?#]/.test(baseUrl)) {
|
|
2218
|
-
throw new Error("mementos base URL must not contain userinfo, query, or fragment data");
|
|
2219
|
-
}
|
|
2220
|
-
return baseUrl;
|
|
2221
|
-
}
|
|
2222
|
-
function getConfiguredApiEnv(env = process.env, options = {}) {
|
|
2223
|
-
const rawBase = firstEnv(API_URL_ENV_KEYS, env);
|
|
2224
|
-
let baseUrl = null;
|
|
2225
|
-
if (rawBase) {
|
|
2226
|
-
const normalized = toV1BaseUrl(rawBase);
|
|
2227
|
-
assertCleanResolvedBase(normalized);
|
|
2228
|
-
baseUrl = normalized;
|
|
2229
|
-
}
|
|
2230
|
-
const inputs = mementosResolverInputs(env, options.credentials);
|
|
2231
|
-
return {
|
|
2232
|
-
baseUrl,
|
|
2233
|
-
apiKeyPresent: resolveCredential("mementos", inputs.env, inputs.credentials) !== null,
|
|
2234
|
-
dbPathKey: firstEnvKey(DB_PATH_ENV_KEYS, env)
|
|
2235
|
-
};
|
|
2236
|
-
}
|
|
2237
|
-
function isLoopbackHost(rawHost) {
|
|
2238
|
-
const host = rawHost.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
|
|
2239
|
-
return host === "localhost" || host === "::1" || /^127\./.test(host);
|
|
2240
|
-
}
|
|
2241
|
-
function assertRequestAllowedUnderTest(baseUrl) {
|
|
2242
|
-
if (process.env["NODE_ENV"] !== "test")
|
|
2243
|
-
return;
|
|
2244
|
-
if (process.env[ALLOW_REMOTE_API_IN_TESTS_ENV]?.trim())
|
|
2245
|
-
return;
|
|
2246
|
-
let host;
|
|
2247
|
-
try {
|
|
2248
|
-
host = new URL(baseUrl).hostname;
|
|
2249
|
-
} catch {
|
|
2250
|
-
host = "";
|
|
2251
|
-
}
|
|
2252
|
-
if (host && isLoopbackHost(host))
|
|
2253
|
-
return;
|
|
2254
|
-
throw new Error("api-mode: REFUSING to make a cloud request from a test process \u2014 this would write to or read " + "from the SHARED PRODUCTION memory store, where test fixtures are indistinguishable from real " + `memories.
|
|
2255
|
-
` + ` host : ${host || "(unparseable base URL)"}
|
|
2256
|
-
` + " how this happens: a selector set at module scope (after the bun test preload ran), or `bun test` " + `invoked from a directory with no bunfig.toml so the preload never loaded.
|
|
2257
|
-
` + " fix : build the child/process env via src/test-support/store-isolation.ts, or point the " + `suite at a loopback stub.
|
|
2258
|
-
` + ` override : set ${ALLOW_REMOTE_API_IN_TESTS_ENV}=1 only for a test that must reach a remote endpoint.`);
|
|
2259
|
-
}
|
|
2260
|
-
function toStoreConfigError(error) {
|
|
2261
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2262
|
-
return new MementosStoreConfigError(`${message} Local SQLite is opt-in only (${DB_PATH_ENV_KEYS[0]}, or ${MEMENTOS_LOCAL_OPT_IN_ENV_KEYS[0]} = 1) and is disabled by default \u2014 failing closed`);
|
|
2263
|
-
}
|
|
2264
|
-
function getApiConfig(env = process.env, options = {}) {
|
|
2265
|
-
if (hasDatabaseUrl(env))
|
|
2266
|
-
return null;
|
|
2267
|
-
if (selectsMementosLocalStore(env))
|
|
2268
|
-
return null;
|
|
2269
|
-
const inputs = mementosResolverInputs(env, options.credentials);
|
|
2270
|
-
let credential;
|
|
2271
|
-
try {
|
|
2272
|
-
credential = resolveCredential("mementos", inputs.env, inputs.credentials);
|
|
2273
|
-
} catch (error) {
|
|
2274
|
-
throw toStoreConfigError(error);
|
|
2275
|
-
}
|
|
2276
|
-
let resolution;
|
|
2277
|
-
try {
|
|
2278
|
-
resolution = resolveClientTransport("mementos", inputs.env, {
|
|
2279
|
-
credentials: credential ? { ...inputs.credentials, apiKey: credential.apiKey } : inputs.credentials
|
|
2280
|
-
});
|
|
2281
|
-
} catch (error) {
|
|
2282
|
-
if (error instanceof ClientTransportConfigurationError && credential === null && /is not set and no API key could be resolved/.test(error.message)) {
|
|
2283
|
-
return null;
|
|
2284
|
-
}
|
|
2285
|
-
throw toStoreConfigError(error);
|
|
2286
|
-
}
|
|
2287
|
-
return { baseUrl: assertCleanResolvedBase(resolution.baseUrl), apiKey: credential.apiKey };
|
|
2288
|
-
}
|
|
2289
|
-
function isApiMode() {
|
|
2290
|
-
if (hasDatabaseUrl())
|
|
2291
|
-
return false;
|
|
2292
|
-
return getApiConfig() !== null;
|
|
2293
|
-
}
|
|
2294
|
-
function getResolvedApiModeReport(env = process.env, options = {}) {
|
|
2295
|
-
if (hasDatabaseUrl(env))
|
|
2296
|
-
return null;
|
|
2297
|
-
if (selectsMementosLocalStore(env))
|
|
2298
|
-
return null;
|
|
2299
|
-
const inputs = mementosResolverInputs(env, options.credentials);
|
|
2300
|
-
let credential;
|
|
2301
|
-
try {
|
|
2302
|
-
credential = resolveCredential("mementos", inputs.env, inputs.credentials);
|
|
2303
|
-
} catch (error) {
|
|
2304
|
-
throw toStoreConfigError(error);
|
|
2305
|
-
}
|
|
2306
|
-
if (!credential) {
|
|
2307
|
-
try {
|
|
2308
|
-
resolveClientTransport("mementos", inputs.env, { credentials: inputs.credentials });
|
|
2309
|
-
} catch (error) {
|
|
2310
|
-
if (error instanceof ClientTransportConfigurationError && /is not set and no API key could be resolved/.test(error.message)) {
|
|
2311
|
-
return null;
|
|
2312
|
-
}
|
|
2313
|
-
throw toStoreConfigError(error);
|
|
2314
|
-
}
|
|
2315
|
-
return null;
|
|
2316
|
-
}
|
|
2317
|
-
const resolution = resolveClientTransport("mementos", inputs.env, {
|
|
2318
|
-
credentials: { ...inputs.credentials, apiKey: credential.apiKey }
|
|
2319
|
-
});
|
|
2320
|
-
assertCleanResolvedBase(resolution.baseUrl);
|
|
2321
|
-
return {
|
|
2322
|
-
baseUrl: resolution.baseUrl,
|
|
2323
|
-
apiUrlSource: resolution.apiUrlSource,
|
|
2324
|
-
apiKeySource: credential.source,
|
|
2325
|
-
apiKeyTier: credential.tier
|
|
2326
|
-
};
|
|
2327
|
-
}
|
|
2328
|
-
function assertClientStoreConfigured(env = process.env, options = {}) {
|
|
2329
|
-
if (selectsMementosLocalStore(env))
|
|
2330
|
-
return;
|
|
2331
|
-
if (hasDatabaseUrl(env))
|
|
2332
|
-
return;
|
|
2333
|
-
const config = getApiConfig(env, options);
|
|
2334
|
-
if (config !== null)
|
|
2335
|
-
return;
|
|
2336
|
-
throw new MementosStoreConfigError(unconfiguredStoreMessage());
|
|
2337
|
-
}
|
|
2338
|
-
function unconfiguredStoreMessage() {
|
|
2339
|
-
return "mementos is not configured to reach any memory store, and will NOT fall back to the " + "on-box SQLite store (~/.hasna/mementos/mementos.db). " + "No credential could be resolved from the Keychain item hasna.credentials.mementos.api-key " + "(macOS only), ~/.hasna/mementos/config/credentials, or " + `${API_KEY_ENV_KEYS[0]}; the authority would be the fleet gateway ${"https://api.hasna.com/mementos"} ` + `(or ${API_URL_ENV_KEYS[0]} if set). ` + `Set ${API_URL_ENV_KEYS[0]} and ${API_KEY_ENV_KEYS[0]} (the resolver also accepts the legacy ` + `${API_URL_ENV_KEYS[1]} / ${API_KEY_ENV_KEYS[1]} aliases for one release) to use the fleet memory ` + `API, or opt into an explicit local SQLite file with ${DB_PATH_ENV_KEYS[0]} / ` + `${DB_PATH_ENV_KEYS[1]} (or ${MEMENTOS_LOCAL_OPT_IN_ENV_KEYS[0]}=1).`;
|
|
2340
|
-
}
|
|
2341
|
-
function apiRequestRaw(method, path, body) {
|
|
2342
|
-
const cfg = getApiConfig();
|
|
2343
|
-
if (!cfg)
|
|
2344
|
-
throw new Error("api-mode: not configured (no Hasna mementos credential resolved)");
|
|
2345
|
-
assertRequestAllowedUnderTest(cfg.baseUrl);
|
|
2346
|
-
const url = `${cfg.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
2347
|
-
const hasBody = body !== undefined && body !== null;
|
|
2348
|
-
const timeout = process.env["HASNA_MEMENTOS_API_TIMEOUT"] || DEFAULT_TIMEOUT_S;
|
|
2349
|
-
const headerLines = `Authorization: Bearer ${cfg.apiKey}
|
|
2350
|
-
x-api-key: ${cfg.apiKey}
|
|
2351
|
-
`;
|
|
2352
|
-
const args = [
|
|
2353
|
-
"curl",
|
|
2354
|
-
"-sS",
|
|
2355
|
-
"--fail-with-body",
|
|
2356
|
-
"-m",
|
|
2357
|
-
timeout,
|
|
2358
|
-
"-X",
|
|
2359
|
-
method,
|
|
2360
|
-
"-H",
|
|
2361
|
-
"@-",
|
|
2362
|
-
"-H",
|
|
2363
|
-
"Content-Type: application/json",
|
|
2364
|
-
"-H",
|
|
2365
|
-
"Accept: application/json",
|
|
2366
|
-
"-w",
|
|
2367
|
-
"\\n%{http_code}"
|
|
2368
|
-
];
|
|
2369
|
-
let bodyFile;
|
|
2370
|
-
if (hasBody) {
|
|
2371
|
-
bodyFile = join(tmpdir(), `mem-req-${process.pid}-${randomUUID()}.json`);
|
|
2372
|
-
writeFileSync(bodyFile, JSON.stringify(body), { mode: 384 });
|
|
2373
|
-
args.push("--data-binary", `@${bodyFile}`);
|
|
2374
|
-
}
|
|
2375
|
-
args.push(url);
|
|
2376
|
-
const childEnv = {};
|
|
2377
|
-
for (const [k, v] of Object.entries(process.env)) {
|
|
2378
|
-
if (v === undefined)
|
|
2379
|
-
continue;
|
|
2380
|
-
if (k === "HASNA_MEMENTOS_API_KEY" || k === "MEMENTOS_API_KEY")
|
|
2381
|
-
continue;
|
|
2382
|
-
childEnv[k] = v;
|
|
2383
|
-
}
|
|
2384
|
-
let out = "";
|
|
2385
|
-
let err = "";
|
|
2386
|
-
try {
|
|
2387
|
-
const proc = Bun.spawnSync(args, {
|
|
2388
|
-
stdin: Buffer.from(headerLines),
|
|
2389
|
-
stdout: "pipe",
|
|
2390
|
-
stderr: "pipe",
|
|
2391
|
-
env: childEnv
|
|
2392
|
-
});
|
|
2393
|
-
out = proc.stdout ? new TextDecoder().decode(proc.stdout) : "";
|
|
2394
|
-
err = proc.stderr ? new TextDecoder().decode(proc.stderr) : "";
|
|
2395
|
-
} finally {
|
|
2396
|
-
if (bodyFile) {
|
|
2397
|
-
try {
|
|
2398
|
-
unlinkSync(bodyFile);
|
|
2399
|
-
} catch {}
|
|
2400
|
-
}
|
|
2401
|
-
}
|
|
2402
|
-
const nl = out.lastIndexOf(`
|
|
2403
|
-
`);
|
|
2404
|
-
const codeStr = nl >= 0 ? out.slice(nl + 1).trim() : "";
|
|
2405
|
-
const respBody = nl >= 0 ? out.slice(0, nl) : out;
|
|
2406
|
-
const status = parseInt(codeStr, 10);
|
|
2407
|
-
if (!Number.isFinite(status) || status === 0) {
|
|
2408
|
-
throw new ApiRequestError(`mementos cloud request failed (${method} ${path}): ${err.trim() || "no HTTP status"}`, 0, respBody);
|
|
2409
|
-
}
|
|
2410
|
-
return { status, body: respBody };
|
|
2411
|
-
}
|
|
2412
|
-
function apiJson(method, path, body, options) {
|
|
2413
|
-
const raw = apiRequestRaw(method, path, body);
|
|
2414
|
-
if (raw.status >= 200 && raw.status < 300) {
|
|
2415
|
-
let data;
|
|
2416
|
-
if (raw.body.trim()) {
|
|
2417
|
-
try {
|
|
2418
|
-
data = JSON.parse(raw.body);
|
|
2419
|
-
} catch (e) {
|
|
2420
|
-
throw new ApiRequestError(`mementos cloud ${method} ${path} returned status ${raw.status} with a body that is not valid JSON (${e instanceof Error ? e.message : String(e)}) \u2014 the response is truncated or the server is unhealthy`, raw.status, raw.body.slice(0, 500));
|
|
2421
|
-
}
|
|
2422
|
-
} else {
|
|
2423
|
-
data = undefined;
|
|
2424
|
-
}
|
|
2425
|
-
return { status: raw.status, data };
|
|
2426
|
-
}
|
|
2427
|
-
if (raw.status === 404 && options?.allow404) {
|
|
2428
|
-
return { status: 404, data: undefined };
|
|
2429
|
-
}
|
|
2430
|
-
let msg = `mementos cloud ${method} ${path} \u2192 ${raw.status}`;
|
|
2431
|
-
try {
|
|
2432
|
-
const parsed = JSON.parse(raw.body);
|
|
2433
|
-
if (parsed.error || parsed.message)
|
|
2434
|
-
msg += `: ${parsed.error || parsed.message}`;
|
|
2435
|
-
} catch {
|
|
2436
|
-
if (raw.body.trim())
|
|
2437
|
-
msg += `: ${raw.body.slice(0, 200)}`;
|
|
2438
|
-
}
|
|
2439
|
-
throw new ApiRequestError(msg, raw.status, raw.body);
|
|
2440
|
-
}
|
|
2441
|
-
function toQuery(params) {
|
|
2442
|
-
const sp = new URLSearchParams;
|
|
2443
|
-
for (const [k, v] of Object.entries(params)) {
|
|
2444
|
-
if (v === undefined || v === null || v === "")
|
|
2445
|
-
continue;
|
|
2446
|
-
if (Array.isArray(v)) {
|
|
2447
|
-
if (v.length === 0)
|
|
2448
|
-
continue;
|
|
2449
|
-
sp.set(k, v.join(","));
|
|
2450
|
-
} else if (typeof v === "boolean") {
|
|
2451
|
-
sp.set(k, v ? "true" : "false");
|
|
2452
|
-
} else {
|
|
2453
|
-
sp.set(k, String(v));
|
|
2454
|
-
}
|
|
2455
|
-
}
|
|
2456
|
-
const s = sp.toString();
|
|
2457
|
-
return s ? `?${s}` : "";
|
|
2458
|
-
}
|
|
2459
|
-
var API_URL_ENV_KEYS, API_KEY_ENV_KEYS, DATABASE_URL_ENV_KEYS, DB_PATH_ENV_KEYS, ALLOW_REMOTE_API_IN_TESTS_ENV = "MEMENTOS_ALLOW_REMOTE_API_IN_TESTS", MementosStoreConfigError, ApiRequestError, DEFAULT_TIMEOUT_S = "45";
|
|
2460
|
-
var init_api_mode = __esm(() => {
|
|
2461
|
-
init_local_opt_in();
|
|
2462
|
-
API_URL_ENV_KEYS = clientTransportEnvKeys2("mementos").apiUrlKeys;
|
|
2463
|
-
API_KEY_ENV_KEYS = clientTransportEnvKeys2("mementos").apiKeyKeys;
|
|
2464
|
-
DATABASE_URL_ENV_KEYS = ["HASNA_MEMENTOS_DATABASE_URL", "MEMENTOS_DATABASE_URL"];
|
|
2465
|
-
DB_PATH_ENV_KEYS = MEMENTOS_DB_PATH_ENV_KEYS;
|
|
2466
|
-
MementosStoreConfigError = class MementosStoreConfigError extends Error {
|
|
2467
|
-
code = "MEMENTOS_STORE_CONFIG";
|
|
2468
|
-
constructor(message) {
|
|
2469
|
-
super(message);
|
|
2470
|
-
this.name = "MementosStoreConfigError";
|
|
2471
|
-
}
|
|
2472
|
-
};
|
|
2473
|
-
ApiRequestError = class ApiRequestError extends Error {
|
|
2474
|
-
status;
|
|
2475
|
-
body;
|
|
2476
|
-
constructor(message, status, body) {
|
|
2477
|
-
super(message);
|
|
2478
|
-
this.status = status;
|
|
2479
|
-
this.body = body;
|
|
2480
|
-
this.name = "ApiRequestError";
|
|
2481
|
-
}
|
|
2482
|
-
};
|
|
2483
|
-
});
|
|
2484
|
-
|
|
2485
2179
|
// src/generated/storage-kit/own.ts
|
|
2486
2180
|
function ownProp(source, key) {
|
|
2487
2181
|
if (source === null || source === undefined)
|
|
@@ -2538,7 +2232,7 @@ var init_backend = () => {};
|
|
|
2538
2232
|
// src/lib/paths.ts
|
|
2539
2233
|
import { existsSync } from "fs";
|
|
2540
2234
|
import { homedir } from "os";
|
|
2541
|
-
import { join
|
|
2235
|
+
import { join, resolve } from "path";
|
|
2542
2236
|
import { homedir as pathsResolverHomedir } from "os";
|
|
2543
2237
|
import { join as pathsResolverJoin } from "path";
|
|
2544
2238
|
function pathsResolverAssertApp(app) {
|
|
@@ -2593,7 +2287,7 @@ function effectiveHome() {
|
|
|
2593
2287
|
return process.env["HOME"] || process.env["USERPROFILE"] || homedir() || "/tmp";
|
|
2594
2288
|
}
|
|
2595
2289
|
function legacyDataRoot() {
|
|
2596
|
-
return
|
|
2290
|
+
return join(effectiveHome(), ".hasna", "mementos");
|
|
2597
2291
|
}
|
|
2598
2292
|
function resolverDataRoot() {
|
|
2599
2293
|
return dataDir({
|
|
@@ -2605,7 +2299,7 @@ function adoptResolverDataRoot(resolved, env = process.env) {
|
|
|
2605
2299
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
2606
2300
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
2607
2301
|
return true;
|
|
2608
|
-
return existsSync(
|
|
2302
|
+
return existsSync(join(resolved, "mementos.db"));
|
|
2609
2303
|
}
|
|
2610
2304
|
function exactDataRoot() {
|
|
2611
2305
|
for (const key of ["HASNA_MEMENTOS_HOME", "MEMENTOS_HOME"]) {
|
|
@@ -2648,8 +2342,8 @@ var init_env = __esm(() => {
|
|
|
2648
2342
|
|
|
2649
2343
|
// src/storage.ts
|
|
2650
2344
|
import { Database } from "bun:sqlite";
|
|
2651
|
-
import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync
|
|
2652
|
-
import { join as
|
|
2345
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2346
|
+
import { join as join2 } from "path";
|
|
2653
2347
|
import { fileURLToPath } from "url";
|
|
2654
2348
|
import { Worker } from "worker_threads";
|
|
2655
2349
|
import pg from "pg";
|
|
@@ -3359,9 +3053,9 @@ var init_storage = __esm(() => {
|
|
|
3359
3053
|
const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
3360
3054
|
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
3361
3055
|
const candidates = [
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3056
|
+
join2(here, `pg-sync-worker${ext}`),
|
|
3057
|
+
join2(here, "..", `pg-sync-worker${ext}`),
|
|
3058
|
+
join2(here, "..", "..", `pg-sync-worker${ext}`)
|
|
3365
3059
|
];
|
|
3366
3060
|
for (const candidate of candidates) {
|
|
3367
3061
|
if (existsSync2(candidate))
|
|
@@ -3466,8 +3160,8 @@ var init_storage = __esm(() => {
|
|
|
3466
3160
|
schedule_minutes: 0
|
|
3467
3161
|
}
|
|
3468
3162
|
};
|
|
3469
|
-
STORAGE_CONFIG_DIR =
|
|
3470
|
-
STORAGE_CONFIG_PATH =
|
|
3163
|
+
STORAGE_CONFIG_DIR = join2(LOCAL_DATA_DIR, "storage");
|
|
3164
|
+
STORAGE_CONFIG_PATH = join2(STORAGE_CONFIG_DIR, "config.json");
|
|
3471
3165
|
DATABASE_ENV_NAMES = [
|
|
3472
3166
|
{ name: MEMENTOS_STORAGE_ENV.databaseUrl, deprecated: false },
|
|
3473
3167
|
{ name: MEMENTOS_STORAGE_FALLBACK_ENV.databaseUrl, deprecated: false }
|
|
@@ -3490,6 +3184,349 @@ var init_storage = __esm(() => {
|
|
|
3490
3184
|
];
|
|
3491
3185
|
});
|
|
3492
3186
|
|
|
3187
|
+
// src/db/api-mode.ts
|
|
3188
|
+
var exports_api_mode = {};
|
|
3189
|
+
__export(exports_api_mode, {
|
|
3190
|
+
unconfiguredStoreMessage: () => unconfiguredStoreMessage,
|
|
3191
|
+
toQuery: () => toQuery,
|
|
3192
|
+
isApiMode: () => isApiMode,
|
|
3193
|
+
getResolvedApiModeReport: () => getResolvedApiModeReport,
|
|
3194
|
+
getConfiguredApiEnv: () => getConfiguredApiEnv,
|
|
3195
|
+
getApiModeEnvSources: () => getApiModeEnvSources,
|
|
3196
|
+
getApiConfig: () => getApiConfig,
|
|
3197
|
+
assertUnambiguousStoreEnv: () => assertUnambiguousStoreEnv,
|
|
3198
|
+
assertClientStoreConfigured: () => assertClientStoreConfigured,
|
|
3199
|
+
apiJson: () => apiJson,
|
|
3200
|
+
MementosStoreConfigError: () => MementosStoreConfigError,
|
|
3201
|
+
DB_PATH_ENV_KEYS: () => DB_PATH_ENV_KEYS,
|
|
3202
|
+
DATABASE_URL_ENV_KEYS: () => DATABASE_URL_ENV_KEYS,
|
|
3203
|
+
ApiRequestError: () => ApiRequestError,
|
|
3204
|
+
API_URL_ENV_KEYS: () => API_URL_ENV_KEYS,
|
|
3205
|
+
API_KEY_ENV_KEYS: () => API_KEY_ENV_KEYS,
|
|
3206
|
+
ALLOW_REMOTE_API_IN_TESTS_ENV: () => ALLOW_REMOTE_API_IN_TESTS_ENV
|
|
3207
|
+
});
|
|
3208
|
+
import { tmpdir } from "os";
|
|
3209
|
+
import { join as join3 } from "path";
|
|
3210
|
+
import { writeFileSync as writeFileSync2, unlinkSync } from "fs";
|
|
3211
|
+
import { randomUUID } from "crypto";
|
|
3212
|
+
import {
|
|
3213
|
+
clientTransportEnvKeys as clientTransportEnvKeys2,
|
|
3214
|
+
resolveClientTransport,
|
|
3215
|
+
resolveCredential,
|
|
3216
|
+
toV1BaseUrl,
|
|
3217
|
+
ClientTransportConfigurationError
|
|
3218
|
+
} from "@hasna/contracts/client";
|
|
3219
|
+
function firstEnv(keys, env2 = process.env) {
|
|
3220
|
+
for (const k of keys) {
|
|
3221
|
+
const v = env2[k]?.trim();
|
|
3222
|
+
if (v)
|
|
3223
|
+
return v;
|
|
3224
|
+
}
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
3227
|
+
function firstEnvKey(keys, env2 = process.env) {
|
|
3228
|
+
for (const k of keys) {
|
|
3229
|
+
if (env2[k]?.trim())
|
|
3230
|
+
return k;
|
|
3231
|
+
}
|
|
3232
|
+
return null;
|
|
3233
|
+
}
|
|
3234
|
+
function hasDatabaseUrl(env2 = process.env) {
|
|
3235
|
+
return Boolean(firstEnv(DATABASE_URL_ENV_KEYS, env2));
|
|
3236
|
+
}
|
|
3237
|
+
function getApiModeEnvSources(env2 = process.env) {
|
|
3238
|
+
return {
|
|
3239
|
+
urlKey: firstEnvKey(API_URL_ENV_KEYS, env2),
|
|
3240
|
+
keyKey: firstEnvKey(API_KEY_ENV_KEYS, env2),
|
|
3241
|
+
databaseUrlKey: firstEnvKey(DATABASE_URL_ENV_KEYS, env2)
|
|
3242
|
+
};
|
|
3243
|
+
}
|
|
3244
|
+
function assertCleanResolvedBase(baseUrl) {
|
|
3245
|
+
if (/[?#]/.test(baseUrl)) {
|
|
3246
|
+
throw new Error("mementos base URL must not contain userinfo, query, or fragment data");
|
|
3247
|
+
}
|
|
3248
|
+
return baseUrl;
|
|
3249
|
+
}
|
|
3250
|
+
function getConfiguredApiEnv(env2 = process.env, options = {}) {
|
|
3251
|
+
const rawBase = firstEnv(API_URL_ENV_KEYS, env2);
|
|
3252
|
+
let baseUrl = null;
|
|
3253
|
+
if (rawBase) {
|
|
3254
|
+
const normalized = toV1BaseUrl(rawBase);
|
|
3255
|
+
assertCleanResolvedBase(normalized);
|
|
3256
|
+
baseUrl = normalized;
|
|
3257
|
+
}
|
|
3258
|
+
const inputs = mementosResolverInputs(env2, options.credentials);
|
|
3259
|
+
return {
|
|
3260
|
+
baseUrl,
|
|
3261
|
+
apiKeyPresent: resolveCredential("mementos", inputs.env, inputs.credentials) !== null,
|
|
3262
|
+
dbPathKey: firstEnvKey(DB_PATH_ENV_KEYS, env2)
|
|
3263
|
+
};
|
|
3264
|
+
}
|
|
3265
|
+
function isLoopbackHost(rawHost) {
|
|
3266
|
+
const host = rawHost.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
|
|
3267
|
+
return host === "localhost" || host === "::1" || /^127\./.test(host);
|
|
3268
|
+
}
|
|
3269
|
+
function assertRequestAllowedUnderTest(baseUrl) {
|
|
3270
|
+
if (process.env["NODE_ENV"] !== "test")
|
|
3271
|
+
return;
|
|
3272
|
+
if (process.env[ALLOW_REMOTE_API_IN_TESTS_ENV]?.trim())
|
|
3273
|
+
return;
|
|
3274
|
+
let host;
|
|
3275
|
+
try {
|
|
3276
|
+
host = new URL(baseUrl).hostname;
|
|
3277
|
+
} catch {
|
|
3278
|
+
host = "";
|
|
3279
|
+
}
|
|
3280
|
+
if (host && isLoopbackHost(host))
|
|
3281
|
+
return;
|
|
3282
|
+
throw new Error("api-mode: REFUSING to make a cloud request from a test process \u2014 this would write to or read " + "from the SHARED PRODUCTION memory store, where test fixtures are indistinguishable from real " + `memories.
|
|
3283
|
+
` + ` host : ${host || "(unparseable base URL)"}
|
|
3284
|
+
` + " how this happens: a selector set at module scope (after the bun test preload ran), or `bun test` " + `invoked from a directory with no bunfig.toml so the preload never loaded.
|
|
3285
|
+
` + " fix : build the child/process env via src/test-support/store-isolation.ts, or point the " + `suite at a loopback stub.
|
|
3286
|
+
` + ` override : set ${ALLOW_REMOTE_API_IN_TESTS_ENV}=1 only for a test that must reach a remote endpoint.`);
|
|
3287
|
+
}
|
|
3288
|
+
function toStoreConfigError(error) {
|
|
3289
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3290
|
+
return new MementosStoreConfigError(`${message} Local SQLite is opt-in only (${DB_PATH_ENV_KEYS[0]}, or ${MEMENTOS_LOCAL_OPT_IN_ENV_KEYS[0]} = 1) and is disabled by default \u2014 failing closed`);
|
|
3291
|
+
}
|
|
3292
|
+
function getApiConfig(env2 = process.env, options = {}) {
|
|
3293
|
+
if (hasDatabaseUrl(env2))
|
|
3294
|
+
return null;
|
|
3295
|
+
if (selectsMementosLocalStore(env2))
|
|
3296
|
+
return null;
|
|
3297
|
+
const inputs = mementosResolverInputs(env2, options.credentials);
|
|
3298
|
+
let credential;
|
|
3299
|
+
try {
|
|
3300
|
+
credential = resolveCredential("mementos", inputs.env, inputs.credentials);
|
|
3301
|
+
} catch (error) {
|
|
3302
|
+
throw toStoreConfigError(error);
|
|
3303
|
+
}
|
|
3304
|
+
let resolution;
|
|
3305
|
+
try {
|
|
3306
|
+
resolution = resolveClientTransport("mementos", inputs.env, {
|
|
3307
|
+
credentials: credential ? { ...inputs.credentials, apiKey: credential.apiKey } : inputs.credentials
|
|
3308
|
+
});
|
|
3309
|
+
} catch (error) {
|
|
3310
|
+
if (error instanceof ClientTransportConfigurationError && credential === null && /is not set and no API key could be resolved/.test(error.message)) {
|
|
3311
|
+
return null;
|
|
3312
|
+
}
|
|
3313
|
+
throw toStoreConfigError(error);
|
|
3314
|
+
}
|
|
3315
|
+
return { baseUrl: assertCleanResolvedBase(resolution.baseUrl), apiKey: credential.apiKey };
|
|
3316
|
+
}
|
|
3317
|
+
function isApiMode() {
|
|
3318
|
+
if (hasDatabaseUrl())
|
|
3319
|
+
return false;
|
|
3320
|
+
if (isServerContext())
|
|
3321
|
+
return false;
|
|
3322
|
+
return getApiConfig() !== null;
|
|
3323
|
+
}
|
|
3324
|
+
function getResolvedApiModeReport(env2 = process.env, options = {}) {
|
|
3325
|
+
if (hasDatabaseUrl(env2))
|
|
3326
|
+
return null;
|
|
3327
|
+
if (selectsMementosLocalStore(env2))
|
|
3328
|
+
return null;
|
|
3329
|
+
const inputs = mementosResolverInputs(env2, options.credentials);
|
|
3330
|
+
let credential;
|
|
3331
|
+
try {
|
|
3332
|
+
credential = resolveCredential("mementos", inputs.env, inputs.credentials);
|
|
3333
|
+
} catch (error) {
|
|
3334
|
+
throw toStoreConfigError(error);
|
|
3335
|
+
}
|
|
3336
|
+
if (!credential) {
|
|
3337
|
+
try {
|
|
3338
|
+
resolveClientTransport("mementos", inputs.env, { credentials: inputs.credentials });
|
|
3339
|
+
} catch (error) {
|
|
3340
|
+
if (error instanceof ClientTransportConfigurationError && /is not set and no API key could be resolved/.test(error.message)) {
|
|
3341
|
+
return null;
|
|
3342
|
+
}
|
|
3343
|
+
throw toStoreConfigError(error);
|
|
3344
|
+
}
|
|
3345
|
+
return null;
|
|
3346
|
+
}
|
|
3347
|
+
const resolution = resolveClientTransport("mementos", inputs.env, {
|
|
3348
|
+
credentials: { ...inputs.credentials, apiKey: credential.apiKey }
|
|
3349
|
+
});
|
|
3350
|
+
assertCleanResolvedBase(resolution.baseUrl);
|
|
3351
|
+
return {
|
|
3352
|
+
baseUrl: resolution.baseUrl,
|
|
3353
|
+
apiUrlSource: resolution.apiUrlSource,
|
|
3354
|
+
apiKeySource: credential.source,
|
|
3355
|
+
apiKeyTier: credential.tier
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
function assertUnambiguousStoreEnv(env2 = process.env, options = {}) {
|
|
3359
|
+
if (hasExplicitLocalDbPath(env2))
|
|
3360
|
+
return;
|
|
3361
|
+
if (hasDatabaseUrl(env2))
|
|
3362
|
+
return;
|
|
3363
|
+
getApiConfig(env2, options);
|
|
3364
|
+
}
|
|
3365
|
+
function assertClientStoreConfigured(env2 = process.env, options = {}) {
|
|
3366
|
+
if (selectsMementosLocalStore(env2))
|
|
3367
|
+
return;
|
|
3368
|
+
if (hasDatabaseUrl(env2))
|
|
3369
|
+
return;
|
|
3370
|
+
const config = getApiConfig(env2, options);
|
|
3371
|
+
if (config !== null)
|
|
3372
|
+
return;
|
|
3373
|
+
throw new MementosStoreConfigError(unconfiguredStoreMessage());
|
|
3374
|
+
}
|
|
3375
|
+
function unconfiguredStoreMessage() {
|
|
3376
|
+
return "mementos is not configured to reach any memory store, and will NOT fall back to the " + "on-box SQLite store (~/.hasna/mementos/mementos.db). " + "No credential could be resolved from the Keychain item hasna.credentials.mementos.api-key " + "(macOS only), ~/.hasna/mementos/config/credentials, or " + `${API_KEY_ENV_KEYS[0]}; the authority would be the fleet gateway ${"https://api.hasna.com/mementos"} ` + `(or ${API_URL_ENV_KEYS[0]} if set). ` + `Set ${API_URL_ENV_KEYS[0]} and ${API_KEY_ENV_KEYS[0]} (the resolver also accepts the legacy ` + `${API_URL_ENV_KEYS[1]} / ${API_KEY_ENV_KEYS[1]} aliases for one release) to use the fleet memory ` + `API, or opt into an explicit local SQLite file with ${DB_PATH_ENV_KEYS[0]} / ` + `${DB_PATH_ENV_KEYS[1]} (or ${MEMENTOS_LOCAL_OPT_IN_ENV_KEYS[0]}=1).`;
|
|
3377
|
+
}
|
|
3378
|
+
function apiRequestRaw(method, path, body) {
|
|
3379
|
+
const cfg = getApiConfig();
|
|
3380
|
+
if (!cfg)
|
|
3381
|
+
throw new Error("api-mode: not configured (no Hasna mementos credential resolved)");
|
|
3382
|
+
assertRequestAllowedUnderTest(cfg.baseUrl);
|
|
3383
|
+
const url = `${cfg.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
3384
|
+
const hasBody = body !== undefined && body !== null;
|
|
3385
|
+
const timeout = process.env["HASNA_MEMENTOS_API_TIMEOUT"] || DEFAULT_TIMEOUT_S;
|
|
3386
|
+
const headerLines = `Authorization: Bearer ${cfg.apiKey}
|
|
3387
|
+
x-api-key: ${cfg.apiKey}
|
|
3388
|
+
`;
|
|
3389
|
+
const args = [
|
|
3390
|
+
"curl",
|
|
3391
|
+
"-sS",
|
|
3392
|
+
"--fail-with-body",
|
|
3393
|
+
"-m",
|
|
3394
|
+
timeout,
|
|
3395
|
+
"-X",
|
|
3396
|
+
method,
|
|
3397
|
+
"-H",
|
|
3398
|
+
"@-",
|
|
3399
|
+
"-H",
|
|
3400
|
+
"Content-Type: application/json",
|
|
3401
|
+
"-H",
|
|
3402
|
+
"Accept: application/json",
|
|
3403
|
+
"-w",
|
|
3404
|
+
"\\n%{http_code}"
|
|
3405
|
+
];
|
|
3406
|
+
let bodyFile;
|
|
3407
|
+
if (hasBody) {
|
|
3408
|
+
bodyFile = join3(tmpdir(), `mem-req-${process.pid}-${randomUUID()}.json`);
|
|
3409
|
+
try {
|
|
3410
|
+
writeFileSync2(bodyFile, JSON.stringify(body), { mode: 384 });
|
|
3411
|
+
} catch (error) {
|
|
3412
|
+
try {
|
|
3413
|
+
unlinkSync(bodyFile);
|
|
3414
|
+
} catch {}
|
|
3415
|
+
throw error;
|
|
3416
|
+
}
|
|
3417
|
+
args.push("--data-binary", `@${bodyFile}`);
|
|
3418
|
+
}
|
|
3419
|
+
args.push(url);
|
|
3420
|
+
const childEnv = {};
|
|
3421
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
3422
|
+
if (v === undefined)
|
|
3423
|
+
continue;
|
|
3424
|
+
if (k === "HASNA_MEMENTOS_API_KEY" || k === "MEMENTOS_API_KEY")
|
|
3425
|
+
continue;
|
|
3426
|
+
childEnv[k] = v;
|
|
3427
|
+
}
|
|
3428
|
+
let out = "";
|
|
3429
|
+
let err = "";
|
|
3430
|
+
try {
|
|
3431
|
+
const proc = Bun.spawnSync(args, {
|
|
3432
|
+
stdin: Buffer.from(headerLines),
|
|
3433
|
+
stdout: "pipe",
|
|
3434
|
+
stderr: "pipe",
|
|
3435
|
+
env: childEnv
|
|
3436
|
+
});
|
|
3437
|
+
out = proc.stdout ? new TextDecoder().decode(proc.stdout) : "";
|
|
3438
|
+
err = proc.stderr ? new TextDecoder().decode(proc.stderr) : "";
|
|
3439
|
+
} finally {
|
|
3440
|
+
if (bodyFile) {
|
|
3441
|
+
try {
|
|
3442
|
+
unlinkSync(bodyFile);
|
|
3443
|
+
} catch {}
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
const nl = out.lastIndexOf(`
|
|
3447
|
+
`);
|
|
3448
|
+
const codeStr = nl >= 0 ? out.slice(nl + 1).trim() : "";
|
|
3449
|
+
const respBody = nl >= 0 ? out.slice(0, nl) : out;
|
|
3450
|
+
const status = parseInt(codeStr, 10);
|
|
3451
|
+
if (!Number.isFinite(status) || status === 0) {
|
|
3452
|
+
throw new ApiRequestError(`mementos cloud request failed (${method} ${path}): ${err.trim() || "no HTTP status"}`, 0, respBody);
|
|
3453
|
+
}
|
|
3454
|
+
return { status, body: respBody };
|
|
3455
|
+
}
|
|
3456
|
+
function apiJson(method, path, body, options) {
|
|
3457
|
+
const raw = apiRequestRaw(method, path, body);
|
|
3458
|
+
if (raw.status >= 200 && raw.status < 300) {
|
|
3459
|
+
let data;
|
|
3460
|
+
if (raw.body.trim()) {
|
|
3461
|
+
try {
|
|
3462
|
+
data = JSON.parse(raw.body);
|
|
3463
|
+
} catch (e) {
|
|
3464
|
+
throw new ApiRequestError(`mementos cloud ${method} ${path} returned status ${raw.status} with a body that is not valid JSON (${e instanceof Error ? e.message : String(e)}) \u2014 the response is truncated or the server is unhealthy`, raw.status, "");
|
|
3465
|
+
}
|
|
3466
|
+
} else {
|
|
3467
|
+
data = undefined;
|
|
3468
|
+
}
|
|
3469
|
+
return { status: raw.status, data };
|
|
3470
|
+
}
|
|
3471
|
+
if (raw.status === 404 && options?.allow404) {
|
|
3472
|
+
return { status: 404, data: undefined };
|
|
3473
|
+
}
|
|
3474
|
+
let msg = `mementos cloud ${method} ${path} \u2192 ${raw.status}`;
|
|
3475
|
+
try {
|
|
3476
|
+
const parsed = JSON.parse(raw.body);
|
|
3477
|
+
if (parsed.error || parsed.message)
|
|
3478
|
+
msg += `: ${parsed.error || parsed.message}`;
|
|
3479
|
+
} catch {
|
|
3480
|
+
if (raw.body.trim())
|
|
3481
|
+
msg += `: ${raw.body.slice(0, 200)}`;
|
|
3482
|
+
}
|
|
3483
|
+
throw new ApiRequestError(msg, raw.status, raw.body);
|
|
3484
|
+
}
|
|
3485
|
+
function toQuery(params) {
|
|
3486
|
+
const sp = new URLSearchParams;
|
|
3487
|
+
for (const [k, v] of Object.entries(params)) {
|
|
3488
|
+
if (v === undefined || v === null || v === "")
|
|
3489
|
+
continue;
|
|
3490
|
+
if (Array.isArray(v)) {
|
|
3491
|
+
if (v.length === 0)
|
|
3492
|
+
continue;
|
|
3493
|
+
sp.set(k, v.join(","));
|
|
3494
|
+
} else if (typeof v === "boolean") {
|
|
3495
|
+
sp.set(k, v ? "true" : "false");
|
|
3496
|
+
} else {
|
|
3497
|
+
sp.set(k, String(v));
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
const s = sp.toString();
|
|
3501
|
+
return s ? `?${s}` : "";
|
|
3502
|
+
}
|
|
3503
|
+
var API_URL_ENV_KEYS, API_KEY_ENV_KEYS, DATABASE_URL_ENV_KEYS, DB_PATH_ENV_KEYS, ALLOW_REMOTE_API_IN_TESTS_ENV = "MEMENTOS_ALLOW_REMOTE_API_IN_TESTS", MementosStoreConfigError, ApiRequestError, DEFAULT_TIMEOUT_S = "45";
|
|
3504
|
+
var init_api_mode = __esm(() => {
|
|
3505
|
+
init_local_opt_in();
|
|
3506
|
+
init_storage();
|
|
3507
|
+
API_URL_ENV_KEYS = clientTransportEnvKeys2("mementos").apiUrlKeys;
|
|
3508
|
+
API_KEY_ENV_KEYS = clientTransportEnvKeys2("mementos").apiKeyKeys;
|
|
3509
|
+
DATABASE_URL_ENV_KEYS = ["HASNA_MEMENTOS_DATABASE_URL", "MEMENTOS_DATABASE_URL"];
|
|
3510
|
+
DB_PATH_ENV_KEYS = MEMENTOS_DB_PATH_ENV_KEYS;
|
|
3511
|
+
MementosStoreConfigError = class MementosStoreConfigError extends Error {
|
|
3512
|
+
code = "MEMENTOS_STORE_CONFIG";
|
|
3513
|
+
constructor(message) {
|
|
3514
|
+
super(message);
|
|
3515
|
+
this.name = "MementosStoreConfigError";
|
|
3516
|
+
}
|
|
3517
|
+
};
|
|
3518
|
+
ApiRequestError = class ApiRequestError extends Error {
|
|
3519
|
+
status;
|
|
3520
|
+
body;
|
|
3521
|
+
constructor(message, status, body) {
|
|
3522
|
+
super(message);
|
|
3523
|
+
this.status = status;
|
|
3524
|
+
this.body = body;
|
|
3525
|
+
this.name = "ApiRequestError";
|
|
3526
|
+
}
|
|
3527
|
+
};
|
|
3528
|
+
});
|
|
3529
|
+
|
|
3493
3530
|
// src/project-registration/schema.ts
|
|
3494
3531
|
function sqliteMementosProjectRegistrationSchemaSql() {
|
|
3495
3532
|
return `
|
|
@@ -4845,6 +4882,220 @@ INSERT OR IGNORE INTO _migrations (id) VALUES (38);
|
|
|
4845
4882
|
`
|
|
4846
4883
|
${sqliteMementosMemoryProjectLinkSchemaSql()}
|
|
4847
4884
|
INSERT OR IGNORE INTO _migrations (id) VALUES (40);
|
|
4885
|
+
`,
|
|
4886
|
+
`
|
|
4887
|
+
DROP TABLE IF EXISTS temp.mementos_m41_machine_preflight;
|
|
4888
|
+
CREATE TEMP TABLE mementos_m41_machine_preflight (
|
|
4889
|
+
id TEXT PRIMARY KEY,
|
|
4890
|
+
name TEXT NOT NULL CHECK(
|
|
4891
|
+
length(name) BETWEEN 1 AND 128
|
|
4892
|
+
AND instr(name, char(0)) = 0
|
|
4893
|
+
AND instr(name, char(1)) = 0
|
|
4894
|
+
AND instr(name, char(2)) = 0
|
|
4895
|
+
AND instr(name, char(3)) = 0
|
|
4896
|
+
AND instr(name, char(4)) = 0
|
|
4897
|
+
AND instr(name, char(5)) = 0
|
|
4898
|
+
AND instr(name, char(6)) = 0
|
|
4899
|
+
AND instr(name, char(7)) = 0
|
|
4900
|
+
AND instr(name, char(8)) = 0
|
|
4901
|
+
AND instr(name, char(9)) = 0
|
|
4902
|
+
AND instr(name, char(10)) = 0
|
|
4903
|
+
AND instr(name, char(11)) = 0
|
|
4904
|
+
AND instr(name, char(12)) = 0
|
|
4905
|
+
AND instr(name, char(13)) = 0
|
|
4906
|
+
AND instr(name, char(14)) = 0
|
|
4907
|
+
AND instr(name, char(15)) = 0
|
|
4908
|
+
AND instr(name, char(16)) = 0
|
|
4909
|
+
AND instr(name, char(17)) = 0
|
|
4910
|
+
AND instr(name, char(18)) = 0
|
|
4911
|
+
AND instr(name, char(19)) = 0
|
|
4912
|
+
AND instr(name, char(20)) = 0
|
|
4913
|
+
AND instr(name, char(21)) = 0
|
|
4914
|
+
AND instr(name, char(22)) = 0
|
|
4915
|
+
AND instr(name, char(23)) = 0
|
|
4916
|
+
AND instr(name, char(24)) = 0
|
|
4917
|
+
AND instr(name, char(25)) = 0
|
|
4918
|
+
AND instr(name, char(26)) = 0
|
|
4919
|
+
AND instr(name, char(27)) = 0
|
|
4920
|
+
AND instr(name, char(28)) = 0
|
|
4921
|
+
AND instr(name, char(29)) = 0
|
|
4922
|
+
AND instr(name, char(30)) = 0
|
|
4923
|
+
AND instr(name, char(31)) = 0
|
|
4924
|
+
AND instr(name, char(127)) = 0
|
|
4925
|
+
),
|
|
4926
|
+
hostname TEXT NOT NULL UNIQUE CHECK(
|
|
4927
|
+
length(hostname) BETWEEN 1 AND 253
|
|
4928
|
+
AND hostname = lower(rtrim(trim(hostname), '.'))
|
|
4929
|
+
AND instr(hostname, ' ') = 0
|
|
4930
|
+
AND instr(hostname, char(9)) = 0
|
|
4931
|
+
AND instr(hostname, char(10)) = 0
|
|
4932
|
+
AND instr(hostname, char(13)) = 0
|
|
4933
|
+
AND instr(hostname, '/') = 0
|
|
4934
|
+
AND instr(hostname, char(92)) = 0
|
|
4935
|
+
),
|
|
4936
|
+
platform TEXT NOT NULL CHECK(
|
|
4937
|
+
length(platform) BETWEEN 1 AND 64
|
|
4938
|
+
AND platform = lower(trim(platform))
|
|
4939
|
+
AND platform NOT GLOB '*[^a-z0-9._-]*'
|
|
4940
|
+
),
|
|
4941
|
+
is_primary INTEGER NOT NULL CHECK(is_primary IN (0, 1)),
|
|
4942
|
+
created_at TEXT NOT NULL CHECK(julianday(created_at) IS NOT NULL),
|
|
4943
|
+
last_seen_at TEXT NOT NULL CHECK(
|
|
4944
|
+
julianday(last_seen_at) IS NOT NULL
|
|
4945
|
+
AND julianday(last_seen_at) >= julianday(created_at)
|
|
4946
|
+
)
|
|
4947
|
+
);
|
|
4948
|
+
INSERT INTO mementos_m41_machine_preflight
|
|
4949
|
+
(id, name, hostname, platform, is_primary, created_at, last_seen_at)
|
|
4950
|
+
SELECT
|
|
4951
|
+
id,
|
|
4952
|
+
name,
|
|
4953
|
+
lower(rtrim(trim(hostname), '.')),
|
|
4954
|
+
platform,
|
|
4955
|
+
is_primary,
|
|
4956
|
+
created_at,
|
|
4957
|
+
last_seen_at
|
|
4958
|
+
FROM machines;
|
|
4959
|
+
|
|
4960
|
+
UPDATE machines
|
|
4961
|
+
SET hostname = (
|
|
4962
|
+
SELECT preflight.hostname
|
|
4963
|
+
FROM mementos_m41_machine_preflight preflight
|
|
4964
|
+
WHERE preflight.id = machines.id
|
|
4965
|
+
);
|
|
4966
|
+
|
|
4967
|
+
DROP TABLE mementos_m41_machine_preflight;
|
|
4968
|
+
DROP TRIGGER IF EXISTS machines_single_primary_insert;
|
|
4969
|
+
DROP TRIGGER IF EXISTS machines_single_primary_update;
|
|
4970
|
+
DROP INDEX IF EXISTS idx_machines_hostname;
|
|
4971
|
+
CREATE UNIQUE INDEX idx_machines_hostname ON machines(hostname);
|
|
4972
|
+
DROP INDEX IF EXISTS idx_machines_single_primary;
|
|
4973
|
+
CREATE UNIQUE INDEX idx_machines_single_primary
|
|
4974
|
+
ON machines(is_primary) WHERE is_primary = 1;
|
|
4975
|
+
|
|
4976
|
+
CREATE TRIGGER IF NOT EXISTS machines_hostname_canonical_insert
|
|
4977
|
+
BEFORE INSERT ON machines
|
|
4978
|
+
WHEN NEW.hostname != lower(rtrim(trim(NEW.hostname), '.'))
|
|
4979
|
+
OR length(NEW.hostname) NOT BETWEEN 1 AND 253
|
|
4980
|
+
OR instr(NEW.hostname, ' ') > 0
|
|
4981
|
+
OR instr(NEW.hostname, char(9)) > 0
|
|
4982
|
+
OR instr(NEW.hostname, char(10)) > 0
|
|
4983
|
+
OR instr(NEW.hostname, char(13)) > 0
|
|
4984
|
+
OR instr(NEW.hostname, '/') > 0
|
|
4985
|
+
OR instr(NEW.hostname, char(92)) > 0
|
|
4986
|
+
BEGIN
|
|
4987
|
+
SELECT RAISE(ABORT, 'machines.hostname must be canonical');
|
|
4988
|
+
END;
|
|
4989
|
+
|
|
4990
|
+
CREATE TRIGGER IF NOT EXISTS machines_hostname_canonical_update
|
|
4991
|
+
BEFORE UPDATE OF hostname ON machines
|
|
4992
|
+
WHEN NEW.hostname != lower(rtrim(trim(NEW.hostname), '.'))
|
|
4993
|
+
OR length(NEW.hostname) NOT BETWEEN 1 AND 253
|
|
4994
|
+
OR instr(NEW.hostname, ' ') > 0
|
|
4995
|
+
OR instr(NEW.hostname, char(9)) > 0
|
|
4996
|
+
OR instr(NEW.hostname, char(10)) > 0
|
|
4997
|
+
OR instr(NEW.hostname, char(13)) > 0
|
|
4998
|
+
OR instr(NEW.hostname, '/') > 0
|
|
4999
|
+
OR instr(NEW.hostname, char(92)) > 0
|
|
5000
|
+
BEGIN
|
|
5001
|
+
SELECT RAISE(ABORT, 'machines.hostname must be canonical');
|
|
5002
|
+
END;
|
|
5003
|
+
|
|
5004
|
+
CREATE TRIGGER IF NOT EXISTS machines_runtime_contract_insert
|
|
5005
|
+
BEFORE INSERT ON machines
|
|
5006
|
+
WHEN length(NEW.name) NOT BETWEEN 1 AND 128
|
|
5007
|
+
OR instr(NEW.name, char(0)) > 0
|
|
5008
|
+
OR instr(NEW.name, char(1)) > 0
|
|
5009
|
+
OR instr(NEW.name, char(2)) > 0
|
|
5010
|
+
OR instr(NEW.name, char(3)) > 0
|
|
5011
|
+
OR instr(NEW.name, char(4)) > 0
|
|
5012
|
+
OR instr(NEW.name, char(5)) > 0
|
|
5013
|
+
OR instr(NEW.name, char(6)) > 0
|
|
5014
|
+
OR instr(NEW.name, char(7)) > 0
|
|
5015
|
+
OR instr(NEW.name, char(8)) > 0
|
|
5016
|
+
OR instr(NEW.name, char(9)) > 0
|
|
5017
|
+
OR instr(NEW.name, char(10)) > 0
|
|
5018
|
+
OR instr(NEW.name, char(11)) > 0
|
|
5019
|
+
OR instr(NEW.name, char(12)) > 0
|
|
5020
|
+
OR instr(NEW.name, char(13)) > 0
|
|
5021
|
+
OR instr(NEW.name, char(14)) > 0
|
|
5022
|
+
OR instr(NEW.name, char(15)) > 0
|
|
5023
|
+
OR instr(NEW.name, char(16)) > 0
|
|
5024
|
+
OR instr(NEW.name, char(17)) > 0
|
|
5025
|
+
OR instr(NEW.name, char(18)) > 0
|
|
5026
|
+
OR instr(NEW.name, char(19)) > 0
|
|
5027
|
+
OR instr(NEW.name, char(20)) > 0
|
|
5028
|
+
OR instr(NEW.name, char(21)) > 0
|
|
5029
|
+
OR instr(NEW.name, char(22)) > 0
|
|
5030
|
+
OR instr(NEW.name, char(23)) > 0
|
|
5031
|
+
OR instr(NEW.name, char(24)) > 0
|
|
5032
|
+
OR instr(NEW.name, char(25)) > 0
|
|
5033
|
+
OR instr(NEW.name, char(26)) > 0
|
|
5034
|
+
OR instr(NEW.name, char(27)) > 0
|
|
5035
|
+
OR instr(NEW.name, char(28)) > 0
|
|
5036
|
+
OR instr(NEW.name, char(29)) > 0
|
|
5037
|
+
OR instr(NEW.name, char(30)) > 0
|
|
5038
|
+
OR instr(NEW.name, char(31)) > 0
|
|
5039
|
+
OR instr(NEW.name, char(127)) > 0
|
|
5040
|
+
OR length(NEW.platform) NOT BETWEEN 1 AND 64
|
|
5041
|
+
OR NEW.platform != lower(trim(NEW.platform))
|
|
5042
|
+
OR NEW.platform GLOB '*[^a-z0-9._-]*'
|
|
5043
|
+
OR NEW.is_primary NOT IN (0, 1)
|
|
5044
|
+
OR julianday(NEW.created_at) IS NULL
|
|
5045
|
+
OR julianday(NEW.last_seen_at) IS NULL
|
|
5046
|
+
OR julianday(NEW.last_seen_at) < julianday(NEW.created_at)
|
|
5047
|
+
BEGIN
|
|
5048
|
+
SELECT RAISE(ABORT, 'machines row violates the persisted runtime contract');
|
|
5049
|
+
END;
|
|
5050
|
+
|
|
5051
|
+
CREATE TRIGGER IF NOT EXISTS machines_runtime_contract_update
|
|
5052
|
+
BEFORE UPDATE OF name, platform, is_primary, created_at, last_seen_at ON machines
|
|
5053
|
+
WHEN length(NEW.name) NOT BETWEEN 1 AND 128
|
|
5054
|
+
OR instr(NEW.name, char(0)) > 0
|
|
5055
|
+
OR instr(NEW.name, char(1)) > 0
|
|
5056
|
+
OR instr(NEW.name, char(2)) > 0
|
|
5057
|
+
OR instr(NEW.name, char(3)) > 0
|
|
5058
|
+
OR instr(NEW.name, char(4)) > 0
|
|
5059
|
+
OR instr(NEW.name, char(5)) > 0
|
|
5060
|
+
OR instr(NEW.name, char(6)) > 0
|
|
5061
|
+
OR instr(NEW.name, char(7)) > 0
|
|
5062
|
+
OR instr(NEW.name, char(8)) > 0
|
|
5063
|
+
OR instr(NEW.name, char(9)) > 0
|
|
5064
|
+
OR instr(NEW.name, char(10)) > 0
|
|
5065
|
+
OR instr(NEW.name, char(11)) > 0
|
|
5066
|
+
OR instr(NEW.name, char(12)) > 0
|
|
5067
|
+
OR instr(NEW.name, char(13)) > 0
|
|
5068
|
+
OR instr(NEW.name, char(14)) > 0
|
|
5069
|
+
OR instr(NEW.name, char(15)) > 0
|
|
5070
|
+
OR instr(NEW.name, char(16)) > 0
|
|
5071
|
+
OR instr(NEW.name, char(17)) > 0
|
|
5072
|
+
OR instr(NEW.name, char(18)) > 0
|
|
5073
|
+
OR instr(NEW.name, char(19)) > 0
|
|
5074
|
+
OR instr(NEW.name, char(20)) > 0
|
|
5075
|
+
OR instr(NEW.name, char(21)) > 0
|
|
5076
|
+
OR instr(NEW.name, char(22)) > 0
|
|
5077
|
+
OR instr(NEW.name, char(23)) > 0
|
|
5078
|
+
OR instr(NEW.name, char(24)) > 0
|
|
5079
|
+
OR instr(NEW.name, char(25)) > 0
|
|
5080
|
+
OR instr(NEW.name, char(26)) > 0
|
|
5081
|
+
OR instr(NEW.name, char(27)) > 0
|
|
5082
|
+
OR instr(NEW.name, char(28)) > 0
|
|
5083
|
+
OR instr(NEW.name, char(29)) > 0
|
|
5084
|
+
OR instr(NEW.name, char(30)) > 0
|
|
5085
|
+
OR instr(NEW.name, char(31)) > 0
|
|
5086
|
+
OR instr(NEW.name, char(127)) > 0
|
|
5087
|
+
OR length(NEW.platform) NOT BETWEEN 1 AND 64
|
|
5088
|
+
OR NEW.platform != lower(trim(NEW.platform))
|
|
5089
|
+
OR NEW.platform GLOB '*[^a-z0-9._-]*'
|
|
5090
|
+
OR NEW.is_primary NOT IN (0, 1)
|
|
5091
|
+
OR julianday(NEW.created_at) IS NULL
|
|
5092
|
+
OR julianday(NEW.last_seen_at) IS NULL
|
|
5093
|
+
OR julianday(NEW.last_seen_at) < julianday(NEW.created_at)
|
|
5094
|
+
BEGIN
|
|
5095
|
+
SELECT RAISE(ABORT, 'machines row violates the persisted runtime contract');
|
|
5096
|
+
END;
|
|
5097
|
+
|
|
5098
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (41);
|
|
4848
5099
|
`
|
|
4849
5100
|
];
|
|
4850
5101
|
});
|
|
@@ -4860,7 +5111,8 @@ __export(exports_database, {
|
|
|
4860
5111
|
getDbPath: () => getDbPath,
|
|
4861
5112
|
getDatabase: () => getDatabase,
|
|
4862
5113
|
escapeLikePrefix: () => escapeLikePrefix,
|
|
4863
|
-
closeDatabase: () => closeDatabase
|
|
5114
|
+
closeDatabase: () => closeDatabase,
|
|
5115
|
+
applyMachineIdentityMigration: () => applyMachineIdentityMigration
|
|
4864
5116
|
});
|
|
4865
5117
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, cpSync } from "fs";
|
|
4866
5118
|
import { dirname, join as join4, resolve as resolve2 } from "path";
|
|
@@ -4988,32 +5240,62 @@ function getDatabase(dbPath) {
|
|
|
4988
5240
|
return _db;
|
|
4989
5241
|
}
|
|
4990
5242
|
function runMigrations(db) {
|
|
5243
|
+
let currentLevel = 0;
|
|
4991
5244
|
try {
|
|
4992
5245
|
const result = db.query("SELECT MAX(id) as max_id FROM _migrations").get();
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
for (let i = 0;i < MIGRATIONS.length; i++) {
|
|
5003
|
-
try {
|
|
5004
|
-
applyMigration(db, i);
|
|
5005
|
-
} catch (e) {
|
|
5006
|
-
console.warn(`[mementos] Migration ${i + 1} failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
5246
|
+
currentLevel = result?.max_id ?? 0;
|
|
5247
|
+
} catch {}
|
|
5248
|
+
for (let i = currentLevel;i < MIGRATIONS.length; i++) {
|
|
5249
|
+
try {
|
|
5250
|
+
applyMigration(db, i);
|
|
5251
|
+
} catch (error) {
|
|
5252
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5253
|
+
if (i === 40) {
|
|
5254
|
+
throw new Error(`Mementos migration 41 refused unsafe machine identity data: ${message}`);
|
|
5007
5255
|
}
|
|
5256
|
+
console.warn(`[mementos] Migration ${i + 1} failed: ${message}`);
|
|
5008
5257
|
}
|
|
5009
5258
|
}
|
|
5010
5259
|
}
|
|
5011
5260
|
function applyMigration(db, index) {
|
|
5012
|
-
if (index === 32)
|
|
5261
|
+
if (index === 32)
|
|
5013
5262
|
ensureMachinePrimaryColumn(db);
|
|
5263
|
+
if (index === 40) {
|
|
5264
|
+
applyMachineIdentityMigration(db);
|
|
5265
|
+
return;
|
|
5014
5266
|
}
|
|
5015
5267
|
db.exec(MIGRATIONS[index]);
|
|
5016
5268
|
}
|
|
5269
|
+
function applyMachineIdentityMigration(db) {
|
|
5270
|
+
db.transaction(() => {
|
|
5271
|
+
const rows = db.query("SELECT id, name, hostname, platform, is_primary, created_at, last_seen_at FROM machines").all();
|
|
5272
|
+
const identities = new Map;
|
|
5273
|
+
let primaryCount = 0;
|
|
5274
|
+
for (const row of rows) {
|
|
5275
|
+
const canonical = row.hostname.trim().replace(/\.+$/, "").toLowerCase();
|
|
5276
|
+
const createdAt = new Date(row.created_at).getTime();
|
|
5277
|
+
const lastSeenAt = new Date(row.last_seen_at).getTime();
|
|
5278
|
+
const primaryValueValid = row.is_primary === 0 || row.is_primary === 1 || row.is_primary === false || row.is_primary === true;
|
|
5279
|
+
if (!row.id || !row.name || row.name.length > 128 || /[\u0000-\u001f\u007f]/.test(row.name) || !canonical || canonical.length > 253 || /[\u0000-\u001f\u007f/\\\s]/.test(canonical) || !row.platform || row.platform.length > 64 || row.platform !== row.platform.trim().toLowerCase() || !/^[a-z0-9._-]+$/.test(row.platform) || !primaryValueValid || !Number.isFinite(createdAt) || !Number.isFinite(lastSeenAt) || lastSeenAt < createdAt) {
|
|
5280
|
+
throw new Error(`invalid legacy machine row ${row.id || "(missing id)"}`);
|
|
5281
|
+
}
|
|
5282
|
+
const owner = identities.get(canonical);
|
|
5283
|
+
if (owner && owner !== row.id) {
|
|
5284
|
+
throw new Error(`ambiguous normalized hostname '${canonical}' belongs to both ${owner} and ${row.id}`);
|
|
5285
|
+
}
|
|
5286
|
+
identities.set(canonical, row.id);
|
|
5287
|
+
if (Boolean(row.is_primary))
|
|
5288
|
+
primaryCount += 1;
|
|
5289
|
+
}
|
|
5290
|
+
if (primaryCount > 1) {
|
|
5291
|
+
throw new Error(`legacy machine registry contains ${primaryCount} primary rows`);
|
|
5292
|
+
}
|
|
5293
|
+
db.exec(MIGRATIONS[40]);
|
|
5294
|
+
const receipt = db.query("SELECT id FROM _migrations WHERE id = 41").get();
|
|
5295
|
+
if (receipt?.id !== 41)
|
|
5296
|
+
throw new Error("machine identity migration did not record completion");
|
|
5297
|
+
});
|
|
5298
|
+
}
|
|
5017
5299
|
function ensureMachinePrimaryColumn(db) {
|
|
5018
5300
|
const columns = db.query("PRAGMA table_info(machines)").all();
|
|
5019
5301
|
if (columns.length === 0 || columns.some((column) => column.name === "is_primary")) {
|
|
@@ -5087,6 +5369,70 @@ var init_database = __esm(() => {
|
|
|
5087
5369
|
]);
|
|
5088
5370
|
});
|
|
5089
5371
|
|
|
5372
|
+
// src/db/api-response-contract.ts
|
|
5373
|
+
function expectObject(value, operation) {
|
|
5374
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
5375
|
+
throw new MementosApiProtocolError(operation, "expected a JSON object");
|
|
5376
|
+
}
|
|
5377
|
+
return value;
|
|
5378
|
+
}
|
|
5379
|
+
function expectArray(value, operation, field) {
|
|
5380
|
+
if (!Array.isArray(value)) {
|
|
5381
|
+
throw new MementosApiProtocolError(operation, field ? `expected '${field}' to be an array` : "expected a JSON array");
|
|
5382
|
+
}
|
|
5383
|
+
return value;
|
|
5384
|
+
}
|
|
5385
|
+
function expectString(object, field, operation, options = {}) {
|
|
5386
|
+
const value = object[field];
|
|
5387
|
+
if (typeof value !== "string" || !options.allowEmpty && value.length === 0) {
|
|
5388
|
+
throw new MementosApiProtocolError(operation, `expected '${field}' to be ${options.allowEmpty ? "a string" : "a non-empty string"}`);
|
|
5389
|
+
}
|
|
5390
|
+
return value;
|
|
5391
|
+
}
|
|
5392
|
+
function expectNullableString(object, field, operation) {
|
|
5393
|
+
const value = object[field];
|
|
5394
|
+
if (value === null)
|
|
5395
|
+
return null;
|
|
5396
|
+
if (typeof value !== "string") {
|
|
5397
|
+
throw new MementosApiProtocolError(operation, `expected '${field}' to be a string or null`);
|
|
5398
|
+
}
|
|
5399
|
+
return value.length === 0 ? null : value;
|
|
5400
|
+
}
|
|
5401
|
+
function expectBoolean(object, field, operation) {
|
|
5402
|
+
const value = object[field];
|
|
5403
|
+
if (typeof value !== "boolean") {
|
|
5404
|
+
throw new MementosApiProtocolError(operation, `expected '${field}' to be a boolean`);
|
|
5405
|
+
}
|
|
5406
|
+
return value;
|
|
5407
|
+
}
|
|
5408
|
+
function expectNonNegativeInteger(object, field, operation) {
|
|
5409
|
+
const value = object[field];
|
|
5410
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
5411
|
+
throw new MementosApiProtocolError(operation, `expected '${field}' to be a non-negative safe integer`);
|
|
5412
|
+
}
|
|
5413
|
+
return value;
|
|
5414
|
+
}
|
|
5415
|
+
function expectRecord(object, field, operation) {
|
|
5416
|
+
try {
|
|
5417
|
+
return expectObject(object[field], operation);
|
|
5418
|
+
} catch (error) {
|
|
5419
|
+
if (error instanceof MementosApiProtocolError) {
|
|
5420
|
+
throw new MementosApiProtocolError(operation, `expected '${field}' to be a JSON object`);
|
|
5421
|
+
}
|
|
5422
|
+
throw error;
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5425
|
+
var MementosApiProtocolError;
|
|
5426
|
+
var init_api_response_contract = __esm(() => {
|
|
5427
|
+
MementosApiProtocolError = class MementosApiProtocolError extends Error {
|
|
5428
|
+
code = "MEMENTOS_API_PROTOCOL";
|
|
5429
|
+
constructor(operation, detail) {
|
|
5430
|
+
super(`mementos cloud ${operation} returned a malformed 2xx response: ${detail}`);
|
|
5431
|
+
this.name = "MementosApiProtocolError";
|
|
5432
|
+
}
|
|
5433
|
+
};
|
|
5434
|
+
});
|
|
5435
|
+
|
|
5090
5436
|
// src/lib/redact.ts
|
|
5091
5437
|
function redactSecrets(text) {
|
|
5092
5438
|
let result = text;
|
|
@@ -6076,7 +6422,7 @@ function buildMemoryListConditions(filter) {
|
|
|
6076
6422
|
conditions.push("status = 'active'");
|
|
6077
6423
|
}
|
|
6078
6424
|
if (filter.project_id) {
|
|
6079
|
-
conditions.push("project_id = ?");
|
|
6425
|
+
conditions.push(filter.include_unassigned_project === true ? "(project_id = ? OR project_id IS NULL)" : "project_id = ?");
|
|
6080
6426
|
params.push(filter.project_id);
|
|
6081
6427
|
}
|
|
6082
6428
|
if (filter.agent_id) {
|
|
@@ -6156,6 +6502,7 @@ function listMemoriesPage(filter, db) {
|
|
|
6156
6502
|
pinned: f.pinned,
|
|
6157
6503
|
agent_id: f.agent_id,
|
|
6158
6504
|
project_id: f.project_id,
|
|
6505
|
+
include_unassigned_project: f.project_id && f.include_unassigned_project === true ? true : undefined,
|
|
6159
6506
|
session_id: f.session_id,
|
|
6160
6507
|
machine_id: f.machine_id,
|
|
6161
6508
|
visible_to_machine_id: f.visible_to_machine_id,
|
|
@@ -6180,7 +6527,7 @@ function listMemoriesPage(filter, db) {
|
|
|
6180
6527
|
if (conditions.length > 0) {
|
|
6181
6528
|
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
6182
6529
|
}
|
|
6183
|
-
sql += " ORDER BY importance DESC, created_at DESC";
|
|
6530
|
+
sql += " ORDER BY importance DESC, created_at DESC, id DESC";
|
|
6184
6531
|
if (f.limit) {
|
|
6185
6532
|
sql += " LIMIT ?";
|
|
6186
6533
|
params.push(f.limit);
|
|
@@ -6324,7 +6671,7 @@ function listMemoryHistoryPage(opts = {}, db) {
|
|
|
6324
6671
|
}
|
|
6325
6672
|
const d = db || getDatabase();
|
|
6326
6673
|
const params = [limit];
|
|
6327
|
-
let sql = "SELECT * FROM memories WHERE status = 'active' AND accessed_at IS NOT NULL ORDER BY accessed_at DESC LIMIT ?";
|
|
6674
|
+
let sql = "SELECT * FROM memories WHERE status = 'active' AND accessed_at IS NOT NULL ORDER BY accessed_at DESC, id DESC LIMIT ?";
|
|
6328
6675
|
if (offset) {
|
|
6329
6676
|
sql += " OFFSET ?";
|
|
6330
6677
|
params.push(offset);
|
|
@@ -7093,7 +7440,7 @@ __export(exports_projects, {
|
|
|
7093
7440
|
ProjectGuardedUpdateError: () => ProjectGuardedUpdateError,
|
|
7094
7441
|
ProjectCollisionError: () => ProjectCollisionError
|
|
7095
7442
|
});
|
|
7096
|
-
import { createHash } from "crypto";
|
|
7443
|
+
import { createHash as createHash2 } from "crypto";
|
|
7097
7444
|
function parseProjectRow(row) {
|
|
7098
7445
|
return {
|
|
7099
7446
|
id: row["id"],
|
|
@@ -7132,7 +7479,7 @@ function canonicalProjectUpdateJson(value) {
|
|
|
7132
7479
|
return JSON.stringify(canonicalizeProjectUpdateValue(value));
|
|
7133
7480
|
}
|
|
7134
7481
|
function digestProjectUpdateValue(value) {
|
|
7135
|
-
return
|
|
7482
|
+
return createHash2("sha256").update(canonicalProjectUpdateJson(value)).digest("hex");
|
|
7136
7483
|
}
|
|
7137
7484
|
function timestampString(value) {
|
|
7138
7485
|
return value instanceof Date ? value.toISOString() : String(value);
|
|
@@ -9293,6 +9640,245 @@ var init_search = __esm(() => {
|
|
|
9293
9640
|
]);
|
|
9294
9641
|
});
|
|
9295
9642
|
|
|
9643
|
+
// src/lib/config.ts
|
|
9644
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, cpSync as cpSync2 } from "fs";
|
|
9645
|
+
import { homedir as homedir4 } from "os";
|
|
9646
|
+
import { basename, dirname as dirname4, join as join9, resolve as resolve14 } from "path";
|
|
9647
|
+
function isInMemoryDb2(path) {
|
|
9648
|
+
return path === ":memory:" || path.startsWith("file::memory:");
|
|
9649
|
+
}
|
|
9650
|
+
function homeDir() {
|
|
9651
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir4();
|
|
9652
|
+
}
|
|
9653
|
+
function deepMerge(target, source) {
|
|
9654
|
+
const result = { ...target };
|
|
9655
|
+
for (const key of Object.keys(source)) {
|
|
9656
|
+
const sourceVal = source[key];
|
|
9657
|
+
const targetVal = result[key];
|
|
9658
|
+
if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) {
|
|
9659
|
+
result[key] = deepMerge(targetVal, sourceVal);
|
|
9660
|
+
} else {
|
|
9661
|
+
result[key] = sourceVal;
|
|
9662
|
+
}
|
|
9663
|
+
}
|
|
9664
|
+
return result;
|
|
9665
|
+
}
|
|
9666
|
+
function isValidScope(value) {
|
|
9667
|
+
return VALID_SCOPES2.includes(value);
|
|
9668
|
+
}
|
|
9669
|
+
function isValidCategory(value) {
|
|
9670
|
+
return VALID_CATEGORIES2.includes(value);
|
|
9671
|
+
}
|
|
9672
|
+
function loadConfig() {
|
|
9673
|
+
const configPath = join9(getDataRoot(), "config.json");
|
|
9674
|
+
let fileConfig = {};
|
|
9675
|
+
if (existsSync6(configPath)) {
|
|
9676
|
+
try {
|
|
9677
|
+
const raw = readFileSync6(configPath, "utf-8");
|
|
9678
|
+
fileConfig = JSON.parse(raw);
|
|
9679
|
+
} catch {}
|
|
9680
|
+
}
|
|
9681
|
+
const merged = deepMerge(DEFAULT_CONFIG, fileConfig);
|
|
9682
|
+
const envScope = env.defaultScope();
|
|
9683
|
+
if (envScope && isValidScope(envScope)) {
|
|
9684
|
+
merged.default_scope = envScope;
|
|
9685
|
+
}
|
|
9686
|
+
const envCategory = env.defaultCategory();
|
|
9687
|
+
if (envCategory && isValidCategory(envCategory)) {
|
|
9688
|
+
merged.default_category = envCategory;
|
|
9689
|
+
}
|
|
9690
|
+
const envImportance = env.defaultImportance();
|
|
9691
|
+
if (envImportance) {
|
|
9692
|
+
const parsed = parseInt(envImportance, 10);
|
|
9693
|
+
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 10) {
|
|
9694
|
+
merged.default_importance = parsed;
|
|
9695
|
+
}
|
|
9696
|
+
}
|
|
9697
|
+
return merged;
|
|
9698
|
+
}
|
|
9699
|
+
function findFileWalkingUp(filename) {
|
|
9700
|
+
let dir = process.cwd();
|
|
9701
|
+
const legacyHomeMementosDb = resolve14(homeDir(), ".mementos", "mementos.db");
|
|
9702
|
+
while (true) {
|
|
9703
|
+
const candidate = join9(dir, filename);
|
|
9704
|
+
if (existsSync6(candidate) && resolve14(candidate) !== legacyHomeMementosDb) {
|
|
9705
|
+
return candidate;
|
|
9706
|
+
}
|
|
9707
|
+
const parent = dirname4(dir);
|
|
9708
|
+
if (parent === dir) {
|
|
9709
|
+
return null;
|
|
9710
|
+
}
|
|
9711
|
+
dir = parent;
|
|
9712
|
+
}
|
|
9713
|
+
}
|
|
9714
|
+
function findGitRoot2() {
|
|
9715
|
+
let dir = process.cwd();
|
|
9716
|
+
while (true) {
|
|
9717
|
+
if (existsSync6(join9(dir, ".git"))) {
|
|
9718
|
+
return dir;
|
|
9719
|
+
}
|
|
9720
|
+
const parent = dirname4(dir);
|
|
9721
|
+
if (parent === dir) {
|
|
9722
|
+
return null;
|
|
9723
|
+
}
|
|
9724
|
+
dir = parent;
|
|
9725
|
+
}
|
|
9726
|
+
}
|
|
9727
|
+
function profilesDir() {
|
|
9728
|
+
return join9(getDataRoot(), "profiles");
|
|
9729
|
+
}
|
|
9730
|
+
function globalConfigPath() {
|
|
9731
|
+
return join9(getDataRoot(), "config.json");
|
|
9732
|
+
}
|
|
9733
|
+
function readGlobalConfig() {
|
|
9734
|
+
const p = globalConfigPath();
|
|
9735
|
+
if (!existsSync6(p))
|
|
9736
|
+
return {};
|
|
9737
|
+
try {
|
|
9738
|
+
return JSON.parse(readFileSync6(p, "utf-8"));
|
|
9739
|
+
} catch {
|
|
9740
|
+
return {};
|
|
9741
|
+
}
|
|
9742
|
+
}
|
|
9743
|
+
function readGlobalConfigForWrite() {
|
|
9744
|
+
const p = globalConfigPath();
|
|
9745
|
+
if (!existsSync6(p))
|
|
9746
|
+
return {};
|
|
9747
|
+
try {
|
|
9748
|
+
const data = JSON.parse(readFileSync6(p, "utf-8"));
|
|
9749
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
9750
|
+
throw new Error("expected a JSON object");
|
|
9751
|
+
}
|
|
9752
|
+
return data;
|
|
9753
|
+
} catch (error) {
|
|
9754
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
9755
|
+
throw new Error(`Cannot update global config at ${p}: ${detail}`);
|
|
9756
|
+
}
|
|
9757
|
+
}
|
|
9758
|
+
function writeGlobalConfig(data) {
|
|
9759
|
+
const p = globalConfigPath();
|
|
9760
|
+
ensureDir2(dirname4(p));
|
|
9761
|
+
writeFileSync3(p, JSON.stringify(data, null, 2), "utf-8");
|
|
9762
|
+
}
|
|
9763
|
+
function getActiveProfile() {
|
|
9764
|
+
const envProfile = process.env["MEMENTOS_PROFILE"];
|
|
9765
|
+
if (envProfile)
|
|
9766
|
+
return envProfile.trim();
|
|
9767
|
+
const cfg = readGlobalConfig();
|
|
9768
|
+
return cfg["active_profile"] || null;
|
|
9769
|
+
}
|
|
9770
|
+
function setActiveProfile(name) {
|
|
9771
|
+
const cfg = readGlobalConfigForWrite();
|
|
9772
|
+
if (name === null) {
|
|
9773
|
+
delete cfg["active_profile"];
|
|
9774
|
+
} else {
|
|
9775
|
+
cfg["active_profile"] = name;
|
|
9776
|
+
}
|
|
9777
|
+
writeGlobalConfig(cfg);
|
|
9778
|
+
}
|
|
9779
|
+
function listProfiles() {
|
|
9780
|
+
const dir = profilesDir();
|
|
9781
|
+
if (!existsSync6(dir))
|
|
9782
|
+
return [];
|
|
9783
|
+
return readdirSync(dir).filter((f) => f.endsWith(".db")).map((f) => basename(f, ".db")).sort();
|
|
9784
|
+
}
|
|
9785
|
+
function deleteProfile(name) {
|
|
9786
|
+
const dbPath = join9(profilesDir(), `${name}.db`);
|
|
9787
|
+
if (!existsSync6(dbPath))
|
|
9788
|
+
return false;
|
|
9789
|
+
unlinkSync2(dbPath);
|
|
9790
|
+
if (getActiveProfile() === name)
|
|
9791
|
+
setActiveProfile(null);
|
|
9792
|
+
return true;
|
|
9793
|
+
}
|
|
9794
|
+
function getDbPath2() {
|
|
9795
|
+
const _home = homeDir();
|
|
9796
|
+
const _newDir = getDataRoot();
|
|
9797
|
+
const _oldDir = join9(_home, ".mementos");
|
|
9798
|
+
if (selectsMementosLocalStore() && !existsSync6(_newDir) && existsSync6(_oldDir)) {
|
|
9799
|
+
mkdirSync3(join9(_home, ".hasna"), { recursive: true });
|
|
9800
|
+
cpSync2(_oldDir, _newDir, { recursive: true });
|
|
9801
|
+
}
|
|
9802
|
+
const envDbPath = process.env["HASNA_MEMENTOS_DB_PATH"] ?? process.env["MEMENTOS_DB_PATH"];
|
|
9803
|
+
if (envDbPath) {
|
|
9804
|
+
if (isInMemoryDb2(envDbPath)) {
|
|
9805
|
+
return envDbPath;
|
|
9806
|
+
}
|
|
9807
|
+
const resolved = resolve14(envDbPath);
|
|
9808
|
+
ensureDir2(dirname4(resolved));
|
|
9809
|
+
return resolved;
|
|
9810
|
+
}
|
|
9811
|
+
const profile = getActiveProfile();
|
|
9812
|
+
if (profile) {
|
|
9813
|
+
const profilePath = join9(profilesDir(), `${profile}.db`);
|
|
9814
|
+
ensureDir2(dirname4(profilePath));
|
|
9815
|
+
return profilePath;
|
|
9816
|
+
}
|
|
9817
|
+
const dbScope = process.env["MEMENTOS_DB_SCOPE"];
|
|
9818
|
+
if (dbScope === "project") {
|
|
9819
|
+
const gitRoot = findGitRoot2();
|
|
9820
|
+
if (gitRoot) {
|
|
9821
|
+
const dbPath = join9(gitRoot, ".mementos", "mementos.db");
|
|
9822
|
+
ensureDir2(dirname4(dbPath));
|
|
9823
|
+
return dbPath;
|
|
9824
|
+
}
|
|
9825
|
+
}
|
|
9826
|
+
const found = findFileWalkingUp(join9(".mementos", "mementos.db"));
|
|
9827
|
+
if (found) {
|
|
9828
|
+
return found;
|
|
9829
|
+
}
|
|
9830
|
+
const fallback = join9(getDataRoot(), "mementos.db");
|
|
9831
|
+
ensureDir2(dirname4(fallback));
|
|
9832
|
+
return fallback;
|
|
9833
|
+
}
|
|
9834
|
+
function ensureDir2(dir) {
|
|
9835
|
+
if (!existsSync6(dir)) {
|
|
9836
|
+
mkdirSync3(dir, { recursive: true });
|
|
9837
|
+
}
|
|
9838
|
+
}
|
|
9839
|
+
var DEFAULT_CONFIG, VALID_SCOPES2, VALID_CATEGORIES2;
|
|
9840
|
+
var init_config = __esm(() => {
|
|
9841
|
+
init_paths();
|
|
9842
|
+
init_env();
|
|
9843
|
+
init_local_opt_in();
|
|
9844
|
+
DEFAULT_CONFIG = {
|
|
9845
|
+
default_scope: "private",
|
|
9846
|
+
default_category: "knowledge",
|
|
9847
|
+
default_importance: 5,
|
|
9848
|
+
max_entries: 1000,
|
|
9849
|
+
max_entries_per_scope: {
|
|
9850
|
+
global: 500,
|
|
9851
|
+
shared: 300,
|
|
9852
|
+
private: 200,
|
|
9853
|
+
working: 100
|
|
9854
|
+
},
|
|
9855
|
+
injection: {
|
|
9856
|
+
max_tokens: 500,
|
|
9857
|
+
min_importance: 5,
|
|
9858
|
+
categories: ["preference", "fact"],
|
|
9859
|
+
refresh_interval: 5
|
|
9860
|
+
},
|
|
9861
|
+
extraction: {
|
|
9862
|
+
enabled: true,
|
|
9863
|
+
min_confidence: 0.5
|
|
9864
|
+
},
|
|
9865
|
+
sync_agents: ["claude", "codex", "gemini"],
|
|
9866
|
+
auto_cleanup: {
|
|
9867
|
+
enabled: true,
|
|
9868
|
+
expired_check_interval: 3600,
|
|
9869
|
+
unused_archive_days: 7,
|
|
9870
|
+
stale_deprioritize_days: 14
|
|
9871
|
+
}
|
|
9872
|
+
};
|
|
9873
|
+
VALID_SCOPES2 = ["global", "shared", "private", "working"];
|
|
9874
|
+
VALID_CATEGORIES2 = [
|
|
9875
|
+
"preference",
|
|
9876
|
+
"fact",
|
|
9877
|
+
"knowledge",
|
|
9878
|
+
"history"
|
|
9879
|
+
];
|
|
9880
|
+
});
|
|
9881
|
+
|
|
9296
9882
|
// src/db/relations.ts
|
|
9297
9883
|
function parseRelationRow(row) {
|
|
9298
9884
|
return {
|
|
@@ -10958,17 +11544,80 @@ var exports_profile_synthesizer = {};
|
|
|
10958
11544
|
__export(exports_profile_synthesizer, {
|
|
10959
11545
|
synthesizeProfile: () => synthesizeProfile,
|
|
10960
11546
|
markProfileStale: () => markProfileStale,
|
|
10961
|
-
getProfileKey: () => getProfileKey
|
|
11547
|
+
getProfileKey: () => getProfileKey,
|
|
11548
|
+
ProfileSynthesisError: () => ProfileSynthesisError,
|
|
11549
|
+
ProfileScopeError: () => ProfileScopeError,
|
|
11550
|
+
PROFILE_SYNTHESIS_CONTRACT: () => PROFILE_SYNTHESIS_CONTRACT
|
|
10962
11551
|
});
|
|
11552
|
+
function resolveProfileTarget(options) {
|
|
11553
|
+
const scope = options.scope ?? (options.project_id ? "project" : options.agent_id ? "agent" : "global");
|
|
11554
|
+
if (scope === "project") {
|
|
11555
|
+
if (!options.project_id)
|
|
11556
|
+
throw new ProfileScopeError("project scope requires project_id");
|
|
11557
|
+
return { scope, id: options.project_id, memory_scope: "shared", project_id: options.project_id };
|
|
11558
|
+
}
|
|
11559
|
+
if (scope === "agent") {
|
|
11560
|
+
if (!options.agent_id)
|
|
11561
|
+
throw new ProfileScopeError("agent scope requires agent_id");
|
|
11562
|
+
return { scope, id: options.agent_id, memory_scope: "private", agent_id: options.agent_id };
|
|
11563
|
+
}
|
|
11564
|
+
return { scope: "global", id: "global", memory_scope: "global" };
|
|
11565
|
+
}
|
|
10963
11566
|
function getProfileKey(scope, id) {
|
|
10964
11567
|
return `_profile_${scope}_${id}`;
|
|
10965
11568
|
}
|
|
11569
|
+
function listProfileMemories(category, target) {
|
|
11570
|
+
if (target.scope !== "global") {
|
|
11571
|
+
return listMemories({
|
|
11572
|
+
category,
|
|
11573
|
+
scope: target.memory_scope,
|
|
11574
|
+
project_id: target.project_id,
|
|
11575
|
+
agent_id: target.agent_id,
|
|
11576
|
+
machine_id: null,
|
|
11577
|
+
status: "active",
|
|
11578
|
+
limit: 30
|
|
11579
|
+
});
|
|
11580
|
+
}
|
|
11581
|
+
return [
|
|
11582
|
+
...listMemories({ category, scope: "global", machine_id: null, status: "active", limit: 30 }),
|
|
11583
|
+
...listMemories({ category, scope: "shared", machine_id: null, status: "active", limit: 1000 })
|
|
11584
|
+
].filter((memory) => memory.agent_id === null && memory.project_id === null).slice(0, 30);
|
|
11585
|
+
}
|
|
10966
11586
|
async function synthesizeProfile(options) {
|
|
10967
|
-
|
|
10968
|
-
|
|
10969
|
-
|
|
11587
|
+
if (!isServerContext() && isApiMode()) {
|
|
11588
|
+
const operation = "POST /profile/synthesize";
|
|
11589
|
+
const { data } = apiJson("POST", "/profile/synthesize", {
|
|
11590
|
+
project_id: options.project_id,
|
|
11591
|
+
agent_id: options.agent_id,
|
|
11592
|
+
scope: options.scope,
|
|
11593
|
+
force_refresh: options.force_refresh === true
|
|
11594
|
+
});
|
|
11595
|
+
const response = expectObject(data, operation);
|
|
11596
|
+
if (response["contract"] !== PROFILE_SYNTHESIS_CONTRACT) {
|
|
11597
|
+
throw new MementosApiProtocolError(operation, `expected contract '${PROFILE_SYNTHESIS_CONTRACT}'`);
|
|
11598
|
+
}
|
|
11599
|
+
if (!("profile" in response)) {
|
|
11600
|
+
throw new MementosApiProtocolError(operation, "missing 'profile'");
|
|
11601
|
+
}
|
|
11602
|
+
if (response["profile"] === null) {
|
|
11603
|
+
if (response["reason"] !== "no_memories") {
|
|
11604
|
+
throw new MementosApiProtocolError(operation, "null profile requires reason 'no_memories'");
|
|
11605
|
+
}
|
|
11606
|
+
return null;
|
|
11607
|
+
}
|
|
11608
|
+
if (typeof response["profile"] !== "string" || response["profile"].length === 0) {
|
|
11609
|
+
throw new MementosApiProtocolError(operation, "expected 'profile' to be a non-empty string or null");
|
|
11610
|
+
}
|
|
11611
|
+
return {
|
|
11612
|
+
profile: response["profile"],
|
|
11613
|
+
memory_count: expectNonNegativeInteger(response, "memory_count", operation),
|
|
11614
|
+
from_cache: expectBoolean(response, "from_cache", operation)
|
|
11615
|
+
};
|
|
11616
|
+
}
|
|
11617
|
+
const target = resolveProfileTarget(options);
|
|
11618
|
+
const profileKey = getProfileKey(target.scope, target.id);
|
|
10970
11619
|
if (!options.force_refresh) {
|
|
10971
|
-
const cached = getMemoryByKey(profileKey, "shared",
|
|
11620
|
+
const cached = getMemoryByKey(profileKey, "shared", target.agent_id, target.project_id);
|
|
10972
11621
|
if (cached) {
|
|
10973
11622
|
const age = Date.now() - new Date(cached.updated_at).getTime();
|
|
10974
11623
|
const maxAge = 24 * 60 * 60 * 1000;
|
|
@@ -10978,20 +11627,8 @@ async function synthesizeProfile(options) {
|
|
|
10978
11627
|
}
|
|
10979
11628
|
}
|
|
10980
11629
|
}
|
|
10981
|
-
const prefMemories =
|
|
10982
|
-
|
|
10983
|
-
project_id: options.project_id,
|
|
10984
|
-
machine_id: null,
|
|
10985
|
-
status: "active",
|
|
10986
|
-
limit: 30
|
|
10987
|
-
});
|
|
10988
|
-
const factMemories = listMemories({
|
|
10989
|
-
category: "fact",
|
|
10990
|
-
project_id: options.project_id,
|
|
10991
|
-
machine_id: null,
|
|
10992
|
-
status: "active",
|
|
10993
|
-
limit: 30
|
|
10994
|
-
});
|
|
11630
|
+
const prefMemories = listProfileMemories("preference", target);
|
|
11631
|
+
const factMemories = listProfileMemories("fact", target);
|
|
10995
11632
|
const allMemories = [...prefMemories, ...factMemories];
|
|
10996
11633
|
if (allMemories.length === 0)
|
|
10997
11634
|
return null;
|
|
@@ -11001,7 +11638,7 @@ async function synthesizeProfile(options) {
|
|
|
11001
11638
|
`);
|
|
11002
11639
|
const fallbackProfile = `## Profile
|
|
11003
11640
|
${lines}`;
|
|
11004
|
-
saveProfile(profileKey, fallbackProfile, allMemories.length,
|
|
11641
|
+
saveProfile(profileKey, fallbackProfile, allMemories.length, target);
|
|
11005
11642
|
return { profile: fallbackProfile, memory_count: allMemories.length, from_cache: false };
|
|
11006
11643
|
}
|
|
11007
11644
|
try {
|
|
@@ -11023,41 +11660,63 @@ ${lines}`;
|
|
|
11023
11660
|
${memoryList}` }]
|
|
11024
11661
|
})
|
|
11025
11662
|
});
|
|
11026
|
-
if (!response.ok)
|
|
11663
|
+
if (!response.ok) {
|
|
11664
|
+
if (options.fail_on_provider_error)
|
|
11665
|
+
throw new ProfileSynthesisError;
|
|
11027
11666
|
return null;
|
|
11667
|
+
}
|
|
11028
11668
|
const data = await response.json();
|
|
11029
11669
|
const profile = data.content?.[0]?.text?.trim();
|
|
11030
|
-
if (!profile)
|
|
11670
|
+
if (!profile) {
|
|
11671
|
+
if (options.fail_on_provider_error)
|
|
11672
|
+
throw new ProfileSynthesisError;
|
|
11031
11673
|
return null;
|
|
11032
|
-
|
|
11674
|
+
}
|
|
11675
|
+
saveProfile(profileKey, profile, allMemories.length, target);
|
|
11033
11676
|
return { profile, memory_count: allMemories.length, from_cache: false };
|
|
11034
|
-
} catch {
|
|
11677
|
+
} catch (error) {
|
|
11678
|
+
if (options.fail_on_provider_error) {
|
|
11679
|
+
if (error instanceof ProfileSynthesisError)
|
|
11680
|
+
throw error;
|
|
11681
|
+
throw new ProfileSynthesisError;
|
|
11682
|
+
}
|
|
11035
11683
|
return null;
|
|
11036
11684
|
}
|
|
11037
11685
|
}
|
|
11038
|
-
function saveProfile(key, value, memoryCount,
|
|
11686
|
+
function saveProfile(key, value, memoryCount, target) {
|
|
11039
11687
|
try {
|
|
11040
11688
|
createMemory({
|
|
11041
11689
|
key,
|
|
11042
11690
|
value,
|
|
11043
|
-
category: "
|
|
11691
|
+
category: "resource",
|
|
11044
11692
|
scope: "shared",
|
|
11045
11693
|
importance: 10,
|
|
11046
11694
|
source: "auto",
|
|
11047
11695
|
tags: ["profile", "synthesized"],
|
|
11048
11696
|
when_to_use: "When needing to understand this agent's or project's preferences, style, and conventions",
|
|
11049
11697
|
metadata: { memory_count: memoryCount, synthesized_at: new Date().toISOString(), stale: false },
|
|
11050
|
-
agent_id:
|
|
11051
|
-
project_id:
|
|
11698
|
+
agent_id: target.agent_id,
|
|
11699
|
+
project_id: target.project_id
|
|
11052
11700
|
});
|
|
11053
11701
|
} catch {}
|
|
11054
11702
|
}
|
|
11055
|
-
function markProfileStale(projectId,
|
|
11703
|
+
function markProfileStale(projectId, agentId) {
|
|
11704
|
+
const targets = [{ scope: "global", id: "global", memory_scope: "global" }];
|
|
11705
|
+
if (projectId)
|
|
11706
|
+
targets.push({ scope: "project", id: projectId, memory_scope: "shared", project_id: projectId });
|
|
11707
|
+
if (agentId)
|
|
11708
|
+
targets.push({ scope: "agent", id: agentId, memory_scope: "private", agent_id: agentId });
|
|
11056
11709
|
try {
|
|
11057
|
-
const
|
|
11058
|
-
const
|
|
11059
|
-
|
|
11060
|
-
|
|
11710
|
+
const db = !isServerContext() && isApiMode() ? undefined : (init_database(), __toCommonJS(exports_database)).getDatabase();
|
|
11711
|
+
for (const target of targets) {
|
|
11712
|
+
const cached = getMemoryByKey(getProfileKey(target.scope, target.id), "shared", target.agent_id, target.project_id, undefined, db);
|
|
11713
|
+
if (!cached)
|
|
11714
|
+
continue;
|
|
11715
|
+
updateMemory(cached.id, {
|
|
11716
|
+
metadata: { ...cached.metadata ?? {}, stale: true },
|
|
11717
|
+
version: cached.version
|
|
11718
|
+
}, db);
|
|
11719
|
+
}
|
|
11061
11720
|
} catch {}
|
|
11062
11721
|
}
|
|
11063
11722
|
var PROFILE_PROMPT = `You synthesize a coherent agent/project profile from individual preference and fact memories.
|
|
@@ -11070,9 +11729,26 @@ Output a concise profile (200-300 words max) organized by:
|
|
|
11070
11729
|
- **Key Facts**: Architecture decisions, constraints, team conventions
|
|
11071
11730
|
|
|
11072
11731
|
Only include sections that have relevant data. Be specific and actionable.
|
|
11073
|
-
Output in markdown format
|
|
11732
|
+
Output in markdown format.`, PROFILE_SYNTHESIS_CONTRACT = "mementos.profile.synthesize.v2", ProfileScopeError, ProfileSynthesisError;
|
|
11074
11733
|
var init_profile_synthesizer = __esm(() => {
|
|
11075
11734
|
init_memories();
|
|
11735
|
+
init_api_mode();
|
|
11736
|
+
init_api_response_contract();
|
|
11737
|
+
init_storage();
|
|
11738
|
+
ProfileScopeError = class ProfileScopeError extends Error {
|
|
11739
|
+
code = "MEMENTOS_PROFILE_SCOPE";
|
|
11740
|
+
constructor(message) {
|
|
11741
|
+
super(message);
|
|
11742
|
+
this.name = "ProfileScopeError";
|
|
11743
|
+
}
|
|
11744
|
+
};
|
|
11745
|
+
ProfileSynthesisError = class ProfileSynthesisError extends Error {
|
|
11746
|
+
code = "MEMENTOS_PROFILE_SYNTHESIS";
|
|
11747
|
+
constructor() {
|
|
11748
|
+
super("Profile synthesis provider failed");
|
|
11749
|
+
this.name = "ProfileSynthesisError";
|
|
11750
|
+
}
|
|
11751
|
+
};
|
|
11076
11752
|
});
|
|
11077
11753
|
|
|
11078
11754
|
// src/lib/contradiction.ts
|
|
@@ -11356,6 +12032,22 @@ var init_built_in_hooks = __esm(() => {
|
|
|
11356
12032
|
} catch {}
|
|
11357
12033
|
}
|
|
11358
12034
|
});
|
|
12035
|
+
hookRegistry.register({
|
|
12036
|
+
type: "PostMemoryUpdate",
|
|
12037
|
+
blocking: false,
|
|
12038
|
+
builtin: true,
|
|
12039
|
+
priority: 65,
|
|
12040
|
+
description: "Mark synthesized profile as stale when a preference or fact memory is updated",
|
|
12041
|
+
handler: async (ctx) => {
|
|
12042
|
+
const category = ctx.memory?.category;
|
|
12043
|
+
if (category !== "preference" && category !== "fact")
|
|
12044
|
+
return;
|
|
12045
|
+
try {
|
|
12046
|
+
const { markProfileStale: markProfileStale2 } = await Promise.resolve().then(() => (init_profile_synthesizer(), exports_profile_synthesizer));
|
|
12047
|
+
markProfileStale2(ctx.projectId, ctx.agentId);
|
|
12048
|
+
} catch {}
|
|
12049
|
+
}
|
|
12050
|
+
});
|
|
11359
12051
|
hookRegistry.register({
|
|
11360
12052
|
type: "PostMemorySave",
|
|
11361
12053
|
blocking: false,
|
|
@@ -12213,10 +12905,20 @@ __export(exports_synthesis2, {
|
|
|
12213
12905
|
getSynthesisStatus: () => getSynthesisStatus
|
|
12214
12906
|
});
|
|
12215
12907
|
async function runSynthesis(options = {}) {
|
|
12216
|
-
const d = options.db || getDatabase();
|
|
12217
12908
|
const projectId = options.projectId ?? null;
|
|
12218
12909
|
const agentId = options.agentId ?? null;
|
|
12219
12910
|
const dryRun = options.dryRun ?? false;
|
|
12911
|
+
if (!options.db && isApiMode()) {
|
|
12912
|
+
const { data } = apiJson("POST", "/synthesis/run", {
|
|
12913
|
+
project_id: projectId,
|
|
12914
|
+
agent_id: agentId,
|
|
12915
|
+
dry_run: dryRun,
|
|
12916
|
+
max_proposals: options.maxProposals,
|
|
12917
|
+
provider: options.provider
|
|
12918
|
+
});
|
|
12919
|
+
return data;
|
|
12920
|
+
}
|
|
12921
|
+
const d = options.db || getDatabase();
|
|
12220
12922
|
const run = createSynthesisRun({
|
|
12221
12923
|
triggered_by: "manual",
|
|
12222
12924
|
project_id: projectId,
|
|
@@ -12305,6 +13007,10 @@ async function runSynthesis(options = {}) {
|
|
|
12305
13007
|
}
|
|
12306
13008
|
}
|
|
12307
13009
|
async function rollbackSynthesis(runId, db) {
|
|
13010
|
+
if (!db && isApiMode()) {
|
|
13011
|
+
const { data } = apiJson("POST", `/synthesis/rollback/${encodeURIComponent(runId)}`);
|
|
13012
|
+
return data;
|
|
13013
|
+
}
|
|
12308
13014
|
const d = db || getDatabase();
|
|
12309
13015
|
const result = await rollbackRun(runId, d);
|
|
12310
13016
|
if (result.errors.length === 0) {
|
|
@@ -12353,8 +13059,37 @@ __export(exports_session_jobs, {
|
|
|
12353
13059
|
getSessionJob: () => getSessionJob,
|
|
12354
13060
|
getNextPendingJob: () => getNextPendingJob,
|
|
12355
13061
|
createSessionJob: () => createSessionJob,
|
|
12356
|
-
claimSessionJob: () => claimSessionJob
|
|
13062
|
+
claimSessionJob: () => claimSessionJob,
|
|
13063
|
+
SESSION_JOBS_PAGE_CONTRACT: () => SESSION_JOBS_PAGE_CONTRACT,
|
|
13064
|
+
SESSION_INGEST_CONTRACT: () => SESSION_INGEST_CONTRACT
|
|
12357
13065
|
});
|
|
13066
|
+
function parseHostedSessionJob(value, operation) {
|
|
13067
|
+
const row = expectObject(value, operation);
|
|
13068
|
+
const source = expectString(row, "source", operation);
|
|
13069
|
+
const status = expectString(row, "status", operation);
|
|
13070
|
+
if (!SESSION_JOB_SOURCES.has(source)) {
|
|
13071
|
+
throw new MementosApiProtocolError(operation, "unsupported 'source'");
|
|
13072
|
+
}
|
|
13073
|
+
if (!SESSION_JOB_STATUSES.has(status)) {
|
|
13074
|
+
throw new MementosApiProtocolError(operation, "unsupported 'status'");
|
|
13075
|
+
}
|
|
13076
|
+
return {
|
|
13077
|
+
id: expectString(row, "id", operation),
|
|
13078
|
+
session_id: expectString(row, "session_id", operation),
|
|
13079
|
+
agent_id: expectNullableString(row, "agent_id", operation),
|
|
13080
|
+
project_id: expectNullableString(row, "project_id", operation),
|
|
13081
|
+
source,
|
|
13082
|
+
status,
|
|
13083
|
+
transcript: expectString(row, "transcript", operation, { allowEmpty: true }),
|
|
13084
|
+
chunk_count: expectNonNegativeInteger(row, "chunk_count", operation),
|
|
13085
|
+
memories_extracted: expectNonNegativeInteger(row, "memories_extracted", operation),
|
|
13086
|
+
error: expectNullableString(row, "error", operation),
|
|
13087
|
+
metadata: expectRecord(row, "metadata", operation),
|
|
13088
|
+
created_at: expectString(row, "created_at", operation),
|
|
13089
|
+
started_at: expectNullableString(row, "started_at", operation),
|
|
13090
|
+
completed_at: expectNullableString(row, "completed_at", operation)
|
|
13091
|
+
};
|
|
13092
|
+
}
|
|
12358
13093
|
function parseJobRow(row) {
|
|
12359
13094
|
return {
|
|
12360
13095
|
id: row["id"],
|
|
@@ -12374,6 +13109,28 @@ function parseJobRow(row) {
|
|
|
12374
13109
|
};
|
|
12375
13110
|
}
|
|
12376
13111
|
function createSessionJob(input, db) {
|
|
13112
|
+
if (!db && isApiMode()) {
|
|
13113
|
+
const operation = "POST /sessions/ingest";
|
|
13114
|
+
const { data } = apiJson("POST", "/sessions/ingest", {
|
|
13115
|
+
session_id: input.session_id,
|
|
13116
|
+
transcript: input.transcript,
|
|
13117
|
+
source: input.source ?? "manual",
|
|
13118
|
+
agent_id: input.agent_id,
|
|
13119
|
+
project_id: input.project_id,
|
|
13120
|
+
metadata: input.metadata ?? {}
|
|
13121
|
+
});
|
|
13122
|
+
const response = expectObject(data, operation);
|
|
13123
|
+
if (response["contract"] !== SESSION_INGEST_CONTRACT) {
|
|
13124
|
+
throw new MementosApiProtocolError(operation, `expected contract '${SESSION_INGEST_CONTRACT}'`);
|
|
13125
|
+
}
|
|
13126
|
+
const jobId = expectString(response, "job_id", operation);
|
|
13127
|
+
const jobObject = expectObject(response["job"], operation);
|
|
13128
|
+
const job = parseHostedSessionJob({ ...jobObject, transcript: input.transcript }, `${operation} job`);
|
|
13129
|
+
if (job.id !== jobId || job.session_id !== input.session_id) {
|
|
13130
|
+
throw new MementosApiProtocolError(operation, "job receipt identity does not match the request");
|
|
13131
|
+
}
|
|
13132
|
+
return job;
|
|
13133
|
+
}
|
|
12377
13134
|
const d = db || getDatabase();
|
|
12378
13135
|
const id = uuid();
|
|
12379
13136
|
const timestamp2 = now();
|
|
@@ -12395,10 +13152,11 @@ function createSessionJob(input, db) {
|
|
|
12395
13152
|
}
|
|
12396
13153
|
function getSessionJob(id, db) {
|
|
12397
13154
|
if (!db && isApiMode()) {
|
|
13155
|
+
const operation = `GET /sessions/jobs/${encodeURIComponent(id)}`;
|
|
12398
13156
|
const { status, data } = apiJson("GET", `/sessions/jobs/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
12399
|
-
if (status === 404
|
|
13157
|
+
if (status === 404)
|
|
12400
13158
|
return null;
|
|
12401
|
-
return data;
|
|
13159
|
+
return parseHostedSessionJob(data, operation);
|
|
12402
13160
|
}
|
|
12403
13161
|
const d = db || getDatabase();
|
|
12404
13162
|
const row = d.query("SELECT * FROM session_memory_jobs WHERE id = ?").get(id);
|
|
@@ -12412,10 +13170,58 @@ function listSessionJobs(filter, db) {
|
|
|
12412
13170
|
agent_id: filter?.agent_id,
|
|
12413
13171
|
project_id: filter?.project_id,
|
|
12414
13172
|
status: filter?.status,
|
|
12415
|
-
|
|
13173
|
+
session_id: filter?.session_id,
|
|
13174
|
+
limit: filter?.limit,
|
|
13175
|
+
offset: filter?.offset
|
|
12416
13176
|
});
|
|
13177
|
+
const operation = "GET /sessions/jobs";
|
|
12417
13178
|
const { data } = apiJson("GET", `/sessions/jobs${q}`);
|
|
12418
|
-
|
|
13179
|
+
const response = expectObject(data, operation);
|
|
13180
|
+
if (response["contract"] !== SESSION_JOBS_PAGE_CONTRACT) {
|
|
13181
|
+
throw new MementosApiProtocolError(operation, `expected contract '${SESSION_JOBS_PAGE_CONTRACT}'`);
|
|
13182
|
+
}
|
|
13183
|
+
const jobs = expectArray(response["jobs"], operation, "jobs").map((job, index) => parseHostedSessionJob(job, `${operation} item ${index}`));
|
|
13184
|
+
const count = expectNonNegativeInteger(response, "count", operation);
|
|
13185
|
+
const responseLimit = expectNonNegativeInteger(response, "limit", operation);
|
|
13186
|
+
const responseOffset = expectNonNegativeInteger(response, "offset", operation);
|
|
13187
|
+
const hasMore = expectBoolean(response, "has_more", operation);
|
|
13188
|
+
const nextOffset = response["next_offset"];
|
|
13189
|
+
if (responseLimit < 1) {
|
|
13190
|
+
throw new MementosApiProtocolError(operation, "expected 'limit' to be positive");
|
|
13191
|
+
}
|
|
13192
|
+
if (count !== jobs.length || count > responseLimit) {
|
|
13193
|
+
throw new MementosApiProtocolError(operation, "page count is inconsistent with jobs/limit");
|
|
13194
|
+
}
|
|
13195
|
+
if (nextOffset !== null && (!Number.isSafeInteger(nextOffset) || nextOffset < 0)) {
|
|
13196
|
+
throw new MementosApiProtocolError(operation, "expected 'next_offset' to be a non-negative safe integer or null");
|
|
13197
|
+
}
|
|
13198
|
+
if (hasMore && nextOffset !== responseOffset + jobs.length) {
|
|
13199
|
+
throw new MementosApiProtocolError(operation, "'has_more' requires the exact next offset");
|
|
13200
|
+
}
|
|
13201
|
+
if (!hasMore && nextOffset !== null) {
|
|
13202
|
+
throw new MementosApiProtocolError(operation, "terminal page must set 'next_offset' to null");
|
|
13203
|
+
}
|
|
13204
|
+
if (filter?.limit !== undefined && responseLimit !== filter.limit) {
|
|
13205
|
+
throw new MementosApiProtocolError(operation, "server did not preserve requested 'limit'");
|
|
13206
|
+
}
|
|
13207
|
+
if (filter?.offset !== undefined && responseOffset !== filter.offset) {
|
|
13208
|
+
throw new MementosApiProtocolError(operation, "server did not preserve requested 'offset'");
|
|
13209
|
+
}
|
|
13210
|
+
for (const job of jobs) {
|
|
13211
|
+
if (filter?.agent_id !== undefined && job.agent_id !== filter.agent_id) {
|
|
13212
|
+
throw new MementosApiProtocolError(operation, "server did not preserve requested 'agent_id'");
|
|
13213
|
+
}
|
|
13214
|
+
if (filter?.project_id !== undefined && job.project_id !== filter.project_id) {
|
|
13215
|
+
throw new MementosApiProtocolError(operation, "server did not preserve requested 'project_id'");
|
|
13216
|
+
}
|
|
13217
|
+
if (filter?.session_id !== undefined && job.session_id !== filter.session_id) {
|
|
13218
|
+
throw new MementosApiProtocolError(operation, "server did not preserve requested 'session_id'");
|
|
13219
|
+
}
|
|
13220
|
+
if (filter?.status !== undefined && job.status !== filter.status) {
|
|
13221
|
+
throw new MementosApiProtocolError(operation, "server did not preserve requested 'status'");
|
|
13222
|
+
}
|
|
13223
|
+
}
|
|
13224
|
+
return jobs;
|
|
12419
13225
|
}
|
|
12420
13226
|
const d = db || getDatabase();
|
|
12421
13227
|
const conditions = [];
|
|
@@ -12495,9 +13301,13 @@ function recoverStaleProcessingJobs(maxAgeMs, db) {
|
|
|
12495
13301
|
const result = d.run("UPDATE session_memory_jobs SET status = 'pending', started_at = NULL WHERE status = 'processing' AND started_at < ?", [cutoff]);
|
|
12496
13302
|
return result.changes;
|
|
12497
13303
|
}
|
|
13304
|
+
var SESSION_JOBS_PAGE_CONTRACT = "mementos.sessions.jobs.v2", SESSION_INGEST_CONTRACT = "mementos.sessions.ingest.v2", SESSION_JOB_SOURCES, SESSION_JOB_STATUSES;
|
|
12498
13305
|
var init_session_jobs = __esm(() => {
|
|
12499
13306
|
init_database();
|
|
12500
13307
|
init_api_mode();
|
|
13308
|
+
init_api_response_contract();
|
|
13309
|
+
SESSION_JOB_SOURCES = new Set(["claude-code", "codex", "manual", "open-sessions"]);
|
|
13310
|
+
SESSION_JOB_STATUSES = new Set(["pending", "processing", "completed", "failed"]);
|
|
12501
13311
|
});
|
|
12502
13312
|
|
|
12503
13313
|
// src/db/tool-events.ts
|
|
@@ -13120,12 +13930,25 @@ __export(exports_session_queue, {
|
|
|
13120
13930
|
enqueueSessionJob: () => enqueueSessionJob
|
|
13121
13931
|
});
|
|
13122
13932
|
function enqueueSessionJob(jobId) {
|
|
13933
|
+
if (!isServerContext() && isApiMode())
|
|
13934
|
+
return;
|
|
13123
13935
|
_pendingQueue.add(jobId);
|
|
13124
13936
|
if (!_isProcessing) {
|
|
13125
13937
|
_processNext();
|
|
13126
13938
|
}
|
|
13127
13939
|
}
|
|
13128
13940
|
function getSessionQueueStats() {
|
|
13941
|
+
if (!isServerContext() && isApiMode()) {
|
|
13942
|
+
const operation = "GET /sessions/queue/stats";
|
|
13943
|
+
const { data } = apiJson("GET", "/sessions/queue/stats");
|
|
13944
|
+
const response = expectObject(data, operation);
|
|
13945
|
+
return {
|
|
13946
|
+
pending: expectNonNegativeInteger(response, "pending", operation),
|
|
13947
|
+
processing: expectNonNegativeInteger(response, "processing", operation),
|
|
13948
|
+
completed: expectNonNegativeInteger(response, "completed", operation),
|
|
13949
|
+
failed: expectNonNegativeInteger(response, "failed", operation)
|
|
13950
|
+
};
|
|
13951
|
+
}
|
|
13129
13952
|
try {
|
|
13130
13953
|
const db = getDatabase();
|
|
13131
13954
|
const rows = db.query("SELECT status, COUNT(*) as count FROM session_memory_jobs GROUP BY status").all();
|
|
@@ -13146,7 +13969,9 @@ function getSessionQueueStats() {
|
|
|
13146
13969
|
stats.failed = row.count;
|
|
13147
13970
|
}
|
|
13148
13971
|
return stats;
|
|
13149
|
-
} catch {
|
|
13972
|
+
} catch (error) {
|
|
13973
|
+
if (isServerContext())
|
|
13974
|
+
throw error;
|
|
13150
13975
|
return {
|
|
13151
13976
|
pending: _pendingQueue.size,
|
|
13152
13977
|
processing: _isProcessing ? 1 : 0,
|
|
@@ -13158,6 +13983,8 @@ function getSessionQueueStats() {
|
|
|
13158
13983
|
function startSessionQueueWorker() {
|
|
13159
13984
|
if (_workerStarted)
|
|
13160
13985
|
return;
|
|
13986
|
+
if (!isServerContext() && isApiMode())
|
|
13987
|
+
return;
|
|
13161
13988
|
_workerStarted = true;
|
|
13162
13989
|
setInterval(() => {
|
|
13163
13990
|
try {
|
|
@@ -13199,6 +14026,9 @@ var init_session_queue = __esm(() => {
|
|
|
13199
14026
|
init_database();
|
|
13200
14027
|
init_session_jobs();
|
|
13201
14028
|
init_session_processor();
|
|
14029
|
+
init_api_mode();
|
|
14030
|
+
init_api_response_contract();
|
|
14031
|
+
init_storage();
|
|
13202
14032
|
_pendingQueue = new Set;
|
|
13203
14033
|
});
|
|
13204
14034
|
|
|
@@ -13207,6 +14037,8 @@ var exports_session_registry = {};
|
|
|
13207
14037
|
__export(exports_session_registry, {
|
|
13208
14038
|
updateSessionAgent: () => updateSessionAgent,
|
|
13209
14039
|
unregisterSession: () => unregisterSession,
|
|
14040
|
+
sessionRegistryUsesLocalStore: () => sessionRegistryUsesLocalStore,
|
|
14041
|
+
sessionRegistryPath: () => sessionRegistryPath,
|
|
13210
14042
|
registerSession: () => registerSession,
|
|
13211
14043
|
listSessions: () => listSessions,
|
|
13212
14044
|
heartbeatSession: () => heartbeatSession,
|
|
@@ -13214,17 +14046,36 @@ __export(exports_session_registry, {
|
|
|
13214
14046
|
getSessionByAgent: () => getSessionByAgent,
|
|
13215
14047
|
getSession: () => getSession,
|
|
13216
14048
|
closeRegistry: () => closeRegistry,
|
|
13217
|
-
cleanStaleSessions: () => cleanStaleSessions
|
|
14049
|
+
cleanStaleSessions: () => cleanStaleSessions,
|
|
14050
|
+
__resetProcessLocalRegistry: () => __resetProcessLocalRegistry,
|
|
14051
|
+
SESSION_REGISTRY_FILE: () => SESSION_REGISTRY_FILE
|
|
13218
14052
|
});
|
|
13219
14053
|
import { existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
|
|
13220
14054
|
import { dirname as dirname6, join as join14 } from "path";
|
|
14055
|
+
function sessionRegistryUsesLocalStore(env2 = process.env) {
|
|
14056
|
+
return isServerContext() || selectsMementosLocalStore(env2);
|
|
14057
|
+
}
|
|
14058
|
+
function sessionRegistryPath() {
|
|
14059
|
+
const store = getDbPath2();
|
|
14060
|
+
if (store === ":memory:")
|
|
14061
|
+
return ":memory:";
|
|
14062
|
+
return join14(dirname6(store), SESSION_REGISTRY_FILE);
|
|
14063
|
+
}
|
|
13221
14064
|
function getDb() {
|
|
13222
|
-
|
|
14065
|
+
const path = sessionRegistryPath();
|
|
14066
|
+
if (_db2 && _dbPath2 === path)
|
|
13223
14067
|
return _db2;
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
|
|
13227
|
-
|
|
14068
|
+
if (_db2) {
|
|
14069
|
+
_db2.close();
|
|
14070
|
+
_db2 = null;
|
|
14071
|
+
}
|
|
14072
|
+
if (path !== ":memory:") {
|
|
14073
|
+
const dir = dirname6(path);
|
|
14074
|
+
if (!existsSync10(dir))
|
|
14075
|
+
mkdirSync5(dir, { recursive: true });
|
|
14076
|
+
}
|
|
14077
|
+
_db2 = new SqliteAdapter(path);
|
|
14078
|
+
_dbPath2 = path;
|
|
13228
14079
|
_db2.run("PRAGMA journal_mode = WAL");
|
|
13229
14080
|
_db2.run("PRAGMA busy_timeout = 3000");
|
|
13230
14081
|
_db2.exec(`
|
|
@@ -13281,12 +14132,45 @@ function isProcessAlive(pid) {
|
|
|
13281
14132
|
return false;
|
|
13282
14133
|
}
|
|
13283
14134
|
}
|
|
14135
|
+
function matchesFilter(s, filter) {
|
|
14136
|
+
if (!filter)
|
|
14137
|
+
return true;
|
|
14138
|
+
if (filter.project_name && s.project_name !== filter.project_name)
|
|
14139
|
+
return false;
|
|
14140
|
+
if (filter.git_root && s.git_root !== filter.git_root)
|
|
14141
|
+
return false;
|
|
14142
|
+
if (filter.mcp_server && s.mcp_server !== filter.mcp_server)
|
|
14143
|
+
return false;
|
|
14144
|
+
if (filter.agent_name && s.agent_name !== filter.agent_name)
|
|
14145
|
+
return false;
|
|
14146
|
+
if (filter.exclude_pid && s.pid === filter.exclude_pid)
|
|
14147
|
+
return false;
|
|
14148
|
+
return true;
|
|
14149
|
+
}
|
|
13284
14150
|
function registerSession(opts) {
|
|
13285
|
-
const db = getDb();
|
|
13286
14151
|
const pid = process.pid;
|
|
13287
14152
|
const cwd = opts.cwd || process.cwd();
|
|
13288
|
-
const id = generateId();
|
|
13289
14153
|
const timestamp2 = now3();
|
|
14154
|
+
if (!sessionRegistryUsesLocalStore()) {
|
|
14155
|
+
const existing2 = [..._memory.values()].find((s) => s.pid === pid && s.mcp_server === opts.mcp_server);
|
|
14156
|
+
const record = {
|
|
14157
|
+
id: existing2?.id ?? generateId(),
|
|
14158
|
+
pid,
|
|
14159
|
+
cwd,
|
|
14160
|
+
git_root: opts.git_root || null,
|
|
14161
|
+
agent_name: opts.agent_name || null,
|
|
14162
|
+
project_name: opts.project_name || null,
|
|
14163
|
+
tty: opts.tty || null,
|
|
14164
|
+
mcp_server: opts.mcp_server,
|
|
14165
|
+
metadata: { ...opts.metadata || {} },
|
|
14166
|
+
registered_at: existing2?.registered_at ?? timestamp2,
|
|
14167
|
+
last_seen_at: timestamp2
|
|
14168
|
+
};
|
|
14169
|
+
_memory.set(record.id, record);
|
|
14170
|
+
return { ...record, metadata: { ...record.metadata } };
|
|
14171
|
+
}
|
|
14172
|
+
const db = getDb();
|
|
14173
|
+
const id = generateId();
|
|
13290
14174
|
const existing = db.query("SELECT id FROM sessions WHERE pid = ? AND mcp_server = ?").get(pid, opts.mcp_server);
|
|
13291
14175
|
if (existing) {
|
|
13292
14176
|
db.run(`UPDATE sessions SET agent_name = ?, project_name = ?, cwd = ?,
|
|
@@ -13319,19 +14203,41 @@ function registerSession(opts) {
|
|
|
13319
14203
|
return getSession(id);
|
|
13320
14204
|
}
|
|
13321
14205
|
function heartbeatSession(id) {
|
|
14206
|
+
if (!sessionRegistryUsesLocalStore()) {
|
|
14207
|
+
const s = _memory.get(id);
|
|
14208
|
+
if (s)
|
|
14209
|
+
s.last_seen_at = now3();
|
|
14210
|
+
return;
|
|
14211
|
+
}
|
|
13322
14212
|
const db = getDb();
|
|
13323
14213
|
db.run("UPDATE sessions SET last_seen_at = ? WHERE id = ?", [now3(), id]);
|
|
13324
14214
|
}
|
|
13325
14215
|
function unregisterSession(id) {
|
|
14216
|
+
if (!sessionRegistryUsesLocalStore()) {
|
|
14217
|
+
_memory.delete(id);
|
|
14218
|
+
return;
|
|
14219
|
+
}
|
|
13326
14220
|
const db = getDb();
|
|
13327
14221
|
db.run("DELETE FROM sessions WHERE id = ?", [id]);
|
|
13328
14222
|
}
|
|
13329
14223
|
function getSession(id) {
|
|
14224
|
+
if (!sessionRegistryUsesLocalStore()) {
|
|
14225
|
+
const s = _memory.get(id);
|
|
14226
|
+
return s ? { ...s, metadata: { ...s.metadata } } : null;
|
|
14227
|
+
}
|
|
13330
14228
|
const db = getDb();
|
|
13331
14229
|
const row = db.query("SELECT * FROM sessions WHERE id = ?").get(id);
|
|
13332
14230
|
return row ? parseRow2(row) : null;
|
|
13333
14231
|
}
|
|
13334
14232
|
function listSessions(filter) {
|
|
14233
|
+
if (!sessionRegistryUsesLocalStore()) {
|
|
14234
|
+
return [..._memory.values()].filter((s) => matchesFilter(s, filter)).filter((s) => {
|
|
14235
|
+
if (isProcessAlive(s.pid))
|
|
14236
|
+
return true;
|
|
14237
|
+
_memory.delete(s.id);
|
|
14238
|
+
return false;
|
|
14239
|
+
}).sort((a, b) => a.last_seen_at < b.last_seen_at ? 1 : a.last_seen_at > b.last_seen_at ? -1 : 0).map((s) => ({ ...s, metadata: { ...s.metadata } }));
|
|
14240
|
+
}
|
|
13335
14241
|
const db = getDb();
|
|
13336
14242
|
const conditions = [];
|
|
13337
14243
|
const params = [];
|
|
@@ -13372,6 +14278,16 @@ function getSessionsByProject(projectName) {
|
|
|
13372
14278
|
return listSessions({ project_name: projectName });
|
|
13373
14279
|
}
|
|
13374
14280
|
function cleanStaleSessions() {
|
|
14281
|
+
if (!sessionRegistryUsesLocalStore()) {
|
|
14282
|
+
let cleaned2 = 0;
|
|
14283
|
+
for (const s of [..._memory.values()]) {
|
|
14284
|
+
if (!isProcessAlive(s.pid)) {
|
|
14285
|
+
_memory.delete(s.id);
|
|
14286
|
+
cleaned2++;
|
|
14287
|
+
}
|
|
14288
|
+
}
|
|
14289
|
+
return cleaned2;
|
|
14290
|
+
}
|
|
13375
14291
|
const db = getDb();
|
|
13376
14292
|
const rows = db.query("SELECT id, pid FROM sessions").all();
|
|
13377
14293
|
let cleaned = 0;
|
|
@@ -13384,20 +14300,35 @@ function cleanStaleSessions() {
|
|
|
13384
14300
|
return cleaned;
|
|
13385
14301
|
}
|
|
13386
14302
|
function updateSessionAgent(mcpServer, agentName) {
|
|
13387
|
-
const db = getDb();
|
|
13388
14303
|
const pid = process.pid;
|
|
14304
|
+
if (!sessionRegistryUsesLocalStore()) {
|
|
14305
|
+
for (const s of _memory.values()) {
|
|
14306
|
+
if (s.pid === pid && s.mcp_server === mcpServer) {
|
|
14307
|
+
s.agent_name = agentName;
|
|
14308
|
+
s.last_seen_at = now3();
|
|
14309
|
+
}
|
|
14310
|
+
}
|
|
14311
|
+
return;
|
|
14312
|
+
}
|
|
14313
|
+
const db = getDb();
|
|
13389
14314
|
db.run("UPDATE sessions SET agent_name = ?, last_seen_at = ? WHERE pid = ? AND mcp_server = ?", [agentName, now3(), pid, mcpServer]);
|
|
13390
14315
|
}
|
|
13391
14316
|
function closeRegistry() {
|
|
13392
14317
|
if (_db2) {
|
|
13393
14318
|
_db2.close();
|
|
13394
14319
|
_db2 = null;
|
|
14320
|
+
_dbPath2 = null;
|
|
13395
14321
|
}
|
|
13396
14322
|
}
|
|
13397
|
-
|
|
14323
|
+
function __resetProcessLocalRegistry() {
|
|
14324
|
+
_memory.clear();
|
|
14325
|
+
}
|
|
14326
|
+
var SESSION_REGISTRY_FILE = "sessions-registry.db", _db2 = null, _dbPath2 = null, _memory;
|
|
13398
14327
|
var init_session_registry = __esm(() => {
|
|
13399
14328
|
init_storage();
|
|
13400
|
-
|
|
14329
|
+
init_config();
|
|
14330
|
+
init_local_opt_in();
|
|
14331
|
+
_memory = new Map;
|
|
13401
14332
|
});
|
|
13402
14333
|
|
|
13403
14334
|
// src/db/pg-migrations.ts
|
|
@@ -14177,6 +15108,79 @@ var init_pg_migrations = __esm(() => {
|
|
|
14177
15108
|
`
|
|
14178
15109
|
${postgresMementosMemoryProjectLinkSchemaSql()}
|
|
14179
15110
|
INSERT INTO _migrations (id) VALUES (40) ON CONFLICT DO NOTHING;
|
|
15111
|
+
`,
|
|
15112
|
+
`
|
|
15113
|
+
DROP TABLE IF EXISTS pg_temp.mementos_m41_machine_preflight;
|
|
15114
|
+
CREATE TEMP TABLE mementos_m41_machine_preflight (
|
|
15115
|
+
id TEXT PRIMARY KEY,
|
|
15116
|
+
name TEXT NOT NULL CHECK(
|
|
15117
|
+
length(name) BETWEEN 1 AND 128
|
|
15118
|
+
AND name !~ '[[:cntrl:]]'
|
|
15119
|
+
),
|
|
15120
|
+
hostname TEXT NOT NULL UNIQUE CHECK(
|
|
15121
|
+
length(hostname) BETWEEN 1 AND 253
|
|
15122
|
+
AND hostname = lower(regexp_replace(btrim(hostname), '\\.+$', ''))
|
|
15123
|
+
AND hostname !~ '[[:space:]/\\\\]'
|
|
15124
|
+
),
|
|
15125
|
+
platform TEXT NOT NULL CHECK(
|
|
15126
|
+
length(platform) BETWEEN 1 AND 64
|
|
15127
|
+
AND platform = lower(btrim(platform))
|
|
15128
|
+
AND platform ~ '^[a-z0-9._-]+$'
|
|
15129
|
+
),
|
|
15130
|
+
is_primary BOOLEAN NOT NULL,
|
|
15131
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
15132
|
+
last_seen_at TIMESTAMPTZ NOT NULL CHECK(last_seen_at >= created_at)
|
|
15133
|
+
) ON COMMIT DROP;
|
|
15134
|
+
|
|
15135
|
+
INSERT INTO mementos_m41_machine_preflight
|
|
15136
|
+
(id, name, hostname, platform, is_primary, created_at, last_seen_at)
|
|
15137
|
+
SELECT
|
|
15138
|
+
id,
|
|
15139
|
+
name,
|
|
15140
|
+
lower(regexp_replace(btrim(hostname), '\\.+$', '')),
|
|
15141
|
+
platform,
|
|
15142
|
+
is_primary,
|
|
15143
|
+
created_at,
|
|
15144
|
+
last_seen_at
|
|
15145
|
+
FROM machines;
|
|
15146
|
+
|
|
15147
|
+
UPDATE machines machine
|
|
15148
|
+
SET hostname = preflight.hostname
|
|
15149
|
+
FROM mementos_m41_machine_preflight preflight
|
|
15150
|
+
WHERE preflight.id = machine.id;
|
|
15151
|
+
|
|
15152
|
+
DROP TRIGGER IF EXISTS machines_single_primary_insert ON machines;
|
|
15153
|
+
DROP TRIGGER IF EXISTS machines_single_primary_update ON machines;
|
|
15154
|
+
DROP INDEX IF EXISTS idx_machines_hostname;
|
|
15155
|
+
CREATE UNIQUE INDEX idx_machines_hostname ON machines(hostname);
|
|
15156
|
+
DROP INDEX IF EXISTS idx_machines_single_primary;
|
|
15157
|
+
CREATE UNIQUE INDEX idx_machines_single_primary
|
|
15158
|
+
ON machines ((1)) WHERE is_primary = TRUE;
|
|
15159
|
+
|
|
15160
|
+
ALTER TABLE machines DROP CONSTRAINT IF EXISTS machines_hostname_canonical;
|
|
15161
|
+
ALTER TABLE machines ADD CONSTRAINT machines_hostname_canonical CHECK(
|
|
15162
|
+
length(hostname) BETWEEN 1 AND 253
|
|
15163
|
+
AND hostname = lower(regexp_replace(btrim(hostname), '\\.+$', ''))
|
|
15164
|
+
AND hostname !~ '[[:space:]/\\\\]'
|
|
15165
|
+
);
|
|
15166
|
+
|
|
15167
|
+
ALTER TABLE machines DROP CONSTRAINT IF EXISTS machines_name_runtime;
|
|
15168
|
+
ALTER TABLE machines ADD CONSTRAINT machines_name_runtime CHECK(
|
|
15169
|
+
length(name) BETWEEN 1 AND 128
|
|
15170
|
+
AND name !~ '[[:cntrl:]]'
|
|
15171
|
+
);
|
|
15172
|
+
ALTER TABLE machines DROP CONSTRAINT IF EXISTS machines_platform_runtime;
|
|
15173
|
+
ALTER TABLE machines ADD CONSTRAINT machines_platform_runtime CHECK(
|
|
15174
|
+
length(platform) BETWEEN 1 AND 64
|
|
15175
|
+
AND platform = lower(btrim(platform))
|
|
15176
|
+
AND platform ~ '^[a-z0-9._-]+$'
|
|
15177
|
+
);
|
|
15178
|
+
ALTER TABLE machines DROP CONSTRAINT IF EXISTS machines_liveness_runtime;
|
|
15179
|
+
ALTER TABLE machines ADD CONSTRAINT machines_liveness_runtime CHECK(
|
|
15180
|
+
last_seen_at >= created_at
|
|
15181
|
+
);
|
|
15182
|
+
|
|
15183
|
+
INSERT INTO _migrations (id) VALUES (41) ON CONFLICT DO NOTHING;
|
|
14180
15184
|
`
|
|
14181
15185
|
];
|
|
14182
15186
|
});
|
|
@@ -14232,24 +15236,32 @@ async function applyPgMigrations(connectionString) {
|
|
|
14232
15236
|
totalMigrations: PG_MIGRATIONS.length
|
|
14233
15237
|
};
|
|
14234
15238
|
try {
|
|
14235
|
-
|
|
14236
|
-
|
|
14237
|
-
|
|
14238
|
-
|
|
14239
|
-
|
|
14240
|
-
|
|
14241
|
-
|
|
15239
|
+
const migrationLockKey = 21760041;
|
|
15240
|
+
await pg2.transaction(async (client) => {
|
|
15241
|
+
await client.query("SELECT pg_advisory_xact_lock($1)", [migrationLockKey]);
|
|
15242
|
+
await client.query(`CREATE TABLE IF NOT EXISTS _pg_migrations (
|
|
15243
|
+
id SERIAL PRIMARY KEY,
|
|
15244
|
+
version INT UNIQUE NOT NULL,
|
|
15245
|
+
applied_at TIMESTAMPTZ DEFAULT NOW()
|
|
15246
|
+
)`);
|
|
15247
|
+
});
|
|
14242
15248
|
for (let i = 0;i < PG_MIGRATIONS.length; i++) {
|
|
14243
|
-
if (appliedSet.has(i)) {
|
|
14244
|
-
result.alreadyApplied.push(i);
|
|
14245
|
-
continue;
|
|
14246
|
-
}
|
|
14247
15249
|
try {
|
|
14248
|
-
await pg2.
|
|
14249
|
-
|
|
14250
|
-
|
|
14251
|
-
|
|
14252
|
-
|
|
15250
|
+
const outcome = await pg2.transaction(async (client) => {
|
|
15251
|
+
await client.query("SELECT pg_advisory_xact_lock($1)", [migrationLockKey]);
|
|
15252
|
+
const already = await client.query("SELECT 1 FROM _pg_migrations WHERE version = $1", [i]);
|
|
15253
|
+
if (already.rowCount)
|
|
15254
|
+
return "already";
|
|
15255
|
+
await client.query(PG_MIGRATIONS[i]);
|
|
15256
|
+
await client.query("INSERT INTO _pg_migrations (version) VALUES ($1)", [i]);
|
|
15257
|
+
return "applied";
|
|
15258
|
+
});
|
|
15259
|
+
if (outcome === "already")
|
|
15260
|
+
result.alreadyApplied.push(i);
|
|
15261
|
+
else
|
|
15262
|
+
result.applied.push(i);
|
|
15263
|
+
} catch (error) {
|
|
15264
|
+
result.errors.push(`Migration ${i}: ${error instanceof Error ? error.message : String(error)}`);
|
|
14253
15265
|
break;
|
|
14254
15266
|
}
|
|
14255
15267
|
}
|
|
@@ -46679,7 +47691,7 @@ var require_token_io = __commonJS((exports, module) => {
|
|
|
46679
47691
|
module.exports = __toCommonJS2(token_io_exports);
|
|
46680
47692
|
var import_path2 = __toESM2(__require("path"));
|
|
46681
47693
|
var import_fs2 = __toESM2(__require("fs"));
|
|
46682
|
-
var
|
|
47694
|
+
var import_os2 = __toESM2(__require("os"));
|
|
46683
47695
|
var import_token_error = require_token_error();
|
|
46684
47696
|
function findRootDir() {
|
|
46685
47697
|
try {
|
|
@@ -46700,11 +47712,11 @@ var require_token_io = __commonJS((exports, module) => {
|
|
|
46700
47712
|
if (process.env.XDG_DATA_HOME) {
|
|
46701
47713
|
return process.env.XDG_DATA_HOME;
|
|
46702
47714
|
}
|
|
46703
|
-
switch (
|
|
47715
|
+
switch (import_os2.default.platform()) {
|
|
46704
47716
|
case "darwin":
|
|
46705
|
-
return import_path2.default.join(
|
|
47717
|
+
return import_path2.default.join(import_os2.default.homedir(), "Library/Application Support");
|
|
46706
47718
|
case "linux":
|
|
46707
|
-
return import_path2.default.join(
|
|
47719
|
+
return import_path2.default.join(import_os2.default.homedir(), ".local/share");
|
|
46708
47720
|
case "win32":
|
|
46709
47721
|
if (process.env.LOCALAPPDATA) {
|
|
46710
47722
|
return process.env.LOCALAPPDATA;
|
|
@@ -46814,10 +47826,10 @@ var require_oauth = __commonJS((exports, module) => {
|
|
|
46814
47826
|
refreshTokenRequest: () => refreshTokenRequest
|
|
46815
47827
|
});
|
|
46816
47828
|
module.exports = __toCommonJS2(oauth_exports);
|
|
46817
|
-
var
|
|
47829
|
+
var import_os2 = __require("os");
|
|
46818
47830
|
var VERCEL_ISSUER = "https://vercel.com";
|
|
46819
47831
|
var VERCEL_CLI_CLIENT_ID = "cl_HYyOPBNtFMfHhaUn9L4QPfTZz6TP47bp";
|
|
46820
|
-
var userAgent = `@vercel/oidc node-${process.version} ${(0,
|
|
47832
|
+
var userAgent = `@vercel/oidc node-${process.version} ${(0, import_os2.platform)()} (${(0, import_os2.arch)()}) ${(0, import_os2.hostname)()}`;
|
|
46821
47833
|
var _tokenEndpoint = null;
|
|
46822
47834
|
async function getTokenEndpoint() {
|
|
46823
47835
|
if (_tokenEndpoint) {
|
|
@@ -64449,65 +65461,283 @@ import { fileURLToPath as fileURLToPath5 } from "url";
|
|
|
64449
65461
|
|
|
64450
65462
|
// src/db/machines.ts
|
|
64451
65463
|
init_database();
|
|
65464
|
+
init_api_mode();
|
|
65465
|
+
init_api_response_contract();
|
|
65466
|
+
import { createHash } from "crypto";
|
|
64452
65467
|
import { hostname, platform } from "os";
|
|
64453
|
-
|
|
65468
|
+
var MACHINE_REGISTRATION_CONTRACT = "mementos.machine-registration.v1";
|
|
65469
|
+
var MACHINE_LIST_CONTRACT = "mementos.machines.v1";
|
|
65470
|
+
class MachineRegistryError extends Error {
|
|
65471
|
+
code;
|
|
65472
|
+
constructor(code, message) {
|
|
65473
|
+
super(message);
|
|
65474
|
+
this.code = code;
|
|
65475
|
+
this.name = "MachineRegistryError";
|
|
65476
|
+
}
|
|
65477
|
+
}
|
|
65478
|
+
var ISO_UTC_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
65479
|
+
function canonicalTimestamp(value, operation, field) {
|
|
65480
|
+
const stringValue = value instanceof Date ? value.toISOString() : value;
|
|
65481
|
+
if (typeof stringValue !== "string" || !ISO_UTC_TIMESTAMP.test(stringValue)) {
|
|
65482
|
+
throw new MementosApiProtocolError(operation, `expected '${field}' to be a canonical UTC ISO-8601 timestamp`);
|
|
65483
|
+
}
|
|
65484
|
+
const parsed = new Date(stringValue);
|
|
65485
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== stringValue) {
|
|
65486
|
+
throw new MementosApiProtocolError(operation, `expected '${field}' to be a real calendar timestamp`);
|
|
65487
|
+
}
|
|
65488
|
+
return stringValue;
|
|
65489
|
+
}
|
|
65490
|
+
function machineFromDb(row, operation = "machine database row") {
|
|
64454
65491
|
if (!row)
|
|
64455
65492
|
return null;
|
|
65493
|
+
if (typeof row.id !== "string" || !row.id || typeof row.name !== "string" || !row.name || row.name.length > 128 || /[\u0000-\u001f\u007f]/.test(row.name) || typeof row.hostname !== "string" || !row.hostname || typeof row.platform !== "string" || !row.platform) {
|
|
65494
|
+
throw new Error(`${operation} is missing or violates required machine identity fields`);
|
|
65495
|
+
}
|
|
65496
|
+
const canonicalHostname = normalizeMachineHostname(row.hostname);
|
|
65497
|
+
const canonicalPlatform = normalizeMachinePlatform(row.platform);
|
|
65498
|
+
if (canonicalHostname !== row.hostname || canonicalPlatform !== row.platform) {
|
|
65499
|
+
throw new Error(`${operation} contains noncanonical hostname or platform data`);
|
|
65500
|
+
}
|
|
65501
|
+
if (!["boolean", "number"].includes(typeof row.is_primary)) {
|
|
65502
|
+
throw new Error(`${operation} has an invalid is_primary value`);
|
|
65503
|
+
}
|
|
65504
|
+
const createdValue = row.created_at instanceof Date ? row.created_at : new Date(String(row.created_at ?? ""));
|
|
65505
|
+
const lastSeenValue = row.last_seen_at instanceof Date ? row.last_seen_at : new Date(String(row.last_seen_at ?? ""));
|
|
65506
|
+
if (!Number.isFinite(createdValue.getTime()) || !Number.isFinite(lastSeenValue.getTime())) {
|
|
65507
|
+
throw new Error(`${operation} has an invalid timestamp`);
|
|
65508
|
+
}
|
|
65509
|
+
const createdAt = createdValue.toISOString();
|
|
65510
|
+
const lastSeenAt = lastSeenValue.toISOString();
|
|
65511
|
+
if (lastSeenAt < createdAt)
|
|
65512
|
+
throw new Error(`${operation} has last_seen_at before created_at`);
|
|
64456
65513
|
return {
|
|
64457
|
-
|
|
64458
|
-
|
|
64459
|
-
|
|
65514
|
+
id: row.id,
|
|
65515
|
+
name: row.name,
|
|
65516
|
+
hostname: canonicalHostname,
|
|
65517
|
+
platform: canonicalPlatform,
|
|
65518
|
+
is_primary: Boolean(row.is_primary),
|
|
65519
|
+
created_at: createdAt,
|
|
65520
|
+
last_seen_at: lastSeenAt
|
|
65521
|
+
};
|
|
65522
|
+
}
|
|
65523
|
+
function decodeMachine(value, operation) {
|
|
65524
|
+
const object = expectObject(value, operation);
|
|
65525
|
+
const name = expectString(object, "name", operation);
|
|
65526
|
+
if (name.length > 128 || /[\u0000-\u001f\u007f]/.test(name)) {
|
|
65527
|
+
throw new MementosApiProtocolError(operation, "machine.name is outside the public contract");
|
|
65528
|
+
}
|
|
65529
|
+
const rawHost = expectString(object, "hostname", operation);
|
|
65530
|
+
const rawPlatform = expectString(object, "platform", operation);
|
|
65531
|
+
let machineHostname;
|
|
65532
|
+
let machinePlatform;
|
|
65533
|
+
try {
|
|
65534
|
+
machineHostname = normalizeMachineHostname(rawHost);
|
|
65535
|
+
machinePlatform = normalizeMachinePlatform(rawPlatform);
|
|
65536
|
+
} catch {
|
|
65537
|
+
throw new MementosApiProtocolError(operation, "machine hostname or platform is not canonical");
|
|
65538
|
+
}
|
|
65539
|
+
if (machineHostname !== rawHost || machinePlatform !== rawPlatform) {
|
|
65540
|
+
throw new MementosApiProtocolError(operation, "machine hostname or platform is not canonical");
|
|
65541
|
+
}
|
|
65542
|
+
const createdAt = canonicalTimestamp(object["created_at"], operation, "created_at");
|
|
65543
|
+
const lastSeenAt = canonicalTimestamp(object["last_seen_at"], operation, "last_seen_at");
|
|
65544
|
+
if (lastSeenAt < createdAt) {
|
|
65545
|
+
throw new MementosApiProtocolError(operation, "last_seen_at precedes created_at");
|
|
65546
|
+
}
|
|
65547
|
+
return {
|
|
65548
|
+
id: expectString(object, "id", operation),
|
|
65549
|
+
name,
|
|
65550
|
+
hostname: machineHostname,
|
|
65551
|
+
platform: machinePlatform,
|
|
65552
|
+
is_primary: expectBoolean(object, "is_primary", operation),
|
|
65553
|
+
created_at: createdAt,
|
|
65554
|
+
last_seen_at: lastSeenAt
|
|
65555
|
+
};
|
|
65556
|
+
}
|
|
65557
|
+
function expectContract(object, contract, operation) {
|
|
65558
|
+
if (object["contract"] !== contract) {
|
|
65559
|
+
throw new MementosApiProtocolError(operation, `expected contract '${contract}'`);
|
|
65560
|
+
}
|
|
65561
|
+
}
|
|
65562
|
+
function decodeRegistration(value, expected) {
|
|
65563
|
+
const operation = "POST /v1/machines";
|
|
65564
|
+
const object = expectObject(value, operation);
|
|
65565
|
+
expectContract(object, MACHINE_REGISTRATION_CONTRACT, operation);
|
|
65566
|
+
const machine = decodeMachine(object["machine"], operation);
|
|
65567
|
+
const created = expectBoolean(object, "created", operation);
|
|
65568
|
+
const identity = expectObject(object["identity"], operation);
|
|
65569
|
+
if (identity["idempotency_key"] !== "normalized_hostname") {
|
|
65570
|
+
throw new MementosApiProtocolError(operation, "expected normalized-hostname identity semantics");
|
|
65571
|
+
}
|
|
65572
|
+
if (expectString(identity, "stable_id", operation) !== machine.id) {
|
|
65573
|
+
throw new MementosApiProtocolError(operation, "identity stable_id does not match machine.id");
|
|
65574
|
+
}
|
|
65575
|
+
const expectedHostname = normalizeMachineHostname(expected.hostname);
|
|
65576
|
+
const expectedPlatform = normalizeMachinePlatform(expected.platform);
|
|
65577
|
+
if (machine.hostname !== expectedHostname || machine.platform !== expectedPlatform) {
|
|
65578
|
+
throw new MementosApiProtocolError(operation, "registration receipt does not match the requested hostname/platform");
|
|
65579
|
+
}
|
|
65580
|
+
if (created && machine.name !== normalizeMachineName(expected.name, expectedHostname)) {
|
|
65581
|
+
throw new MementosApiProtocolError(operation, "created registration receipt does not match the requested name");
|
|
65582
|
+
}
|
|
65583
|
+
return { machine, created };
|
|
65584
|
+
}
|
|
65585
|
+
function decodeMachineList(value) {
|
|
65586
|
+
const operation = "GET /v1/machines";
|
|
65587
|
+
const object = expectObject(value, operation);
|
|
65588
|
+
expectContract(object, MACHINE_LIST_CONTRACT, operation);
|
|
65589
|
+
const values = expectArray(object["machines"], operation, "machines");
|
|
65590
|
+
const count = expectNonNegativeInteger(object, "count", operation);
|
|
65591
|
+
if (count !== values.length) {
|
|
65592
|
+
throw new MementosApiProtocolError(operation, "count does not match machines.length");
|
|
65593
|
+
}
|
|
65594
|
+
if (object["complete"] !== true) {
|
|
65595
|
+
throw new MementosApiProtocolError(operation, "expected complete=true");
|
|
65596
|
+
}
|
|
65597
|
+
const machines = values.map((entry, index) => decodeMachine(entry, `${operation} machines[${index}]`));
|
|
65598
|
+
const ids = new Set;
|
|
65599
|
+
const hosts = new Set;
|
|
65600
|
+
const names = new Set;
|
|
65601
|
+
let primaryCount = 0;
|
|
65602
|
+
for (const machine of machines) {
|
|
65603
|
+
if (ids.has(machine.id))
|
|
65604
|
+
throw new MementosApiProtocolError(operation, `duplicate machine id '${machine.id}'`);
|
|
65605
|
+
if (hosts.has(machine.hostname))
|
|
65606
|
+
throw new MementosApiProtocolError(operation, `duplicate machine hostname '${machine.hostname}'`);
|
|
65607
|
+
if (names.has(machine.name))
|
|
65608
|
+
throw new MementosApiProtocolError(operation, `duplicate machine name '${machine.name}'`);
|
|
65609
|
+
ids.add(machine.id);
|
|
65610
|
+
hosts.add(machine.hostname);
|
|
65611
|
+
names.add(machine.name);
|
|
65612
|
+
if (machine.is_primary)
|
|
65613
|
+
primaryCount += 1;
|
|
65614
|
+
}
|
|
65615
|
+
if (primaryCount > 1)
|
|
65616
|
+
throw new MementosApiProtocolError(operation, "more than one machine is primary");
|
|
65617
|
+
return machines;
|
|
65618
|
+
}
|
|
65619
|
+
function normalizeMachineHostname(host) {
|
|
65620
|
+
const normalized = host.trim().replace(/\.+$/, "").toLowerCase();
|
|
65621
|
+
if (!normalized || normalized.length > 253 || /[\u0000-\u001f\u007f/\\\s]/.test(normalized)) {
|
|
65622
|
+
throw new MachineRegistryError("MACHINE_INVALID_INPUT", "Machine hostname must be a non-empty hostname of at most 253 characters");
|
|
65623
|
+
}
|
|
65624
|
+
return normalized;
|
|
65625
|
+
}
|
|
65626
|
+
function normalizeMachinePlatform(value) {
|
|
65627
|
+
const normalized = value.trim().toLowerCase();
|
|
65628
|
+
if (!normalized || normalized.length > 64 || !/^[a-z0-9._-]+$/.test(normalized)) {
|
|
65629
|
+
throw new MachineRegistryError("MACHINE_INVALID_INPUT", "Machine platform must contain 1-64 lowercase letters, digits, dots, underscores, or hyphens");
|
|
65630
|
+
}
|
|
65631
|
+
return normalized;
|
|
64460
65632
|
}
|
|
64461
|
-
function
|
|
64462
|
-
|
|
65633
|
+
function normalizeMachineName(value, fallback) {
|
|
65634
|
+
const normalized = value?.trim() || fallback;
|
|
65635
|
+
if (!normalized || normalized.length > 128 || /[\u0000-\u001f\u007f]/.test(normalized)) {
|
|
65636
|
+
throw new MachineRegistryError("MACHINE_INVALID_INPUT", "Machine name must contain 1-128 printable characters");
|
|
65637
|
+
}
|
|
65638
|
+
return normalized;
|
|
64463
65639
|
}
|
|
64464
|
-
function
|
|
64465
|
-
const
|
|
64466
|
-
const host =
|
|
64467
|
-
const
|
|
64468
|
-
const
|
|
64469
|
-
const
|
|
64470
|
-
|
|
64471
|
-
|
|
64472
|
-
|
|
65640
|
+
function registerMachineRecord(input, db) {
|
|
65641
|
+
const d = db ?? getDatabase();
|
|
65642
|
+
const host = normalizeMachineHostname(input.hostname);
|
|
65643
|
+
const machineName = normalizeMachineName(input.name, host);
|
|
65644
|
+
const machinePlatform = normalizeMachinePlatform(input.platform);
|
|
65645
|
+
const proposedId = uuid();
|
|
65646
|
+
const seenAt = now();
|
|
65647
|
+
try {
|
|
65648
|
+
const row = d.query(`INSERT INTO machines (id, name, hostname, platform, created_at, last_seen_at)
|
|
65649
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
65650
|
+
ON CONFLICT(hostname) DO UPDATE SET
|
|
65651
|
+
platform = excluded.platform,
|
|
65652
|
+
last_seen_at = CASE
|
|
65653
|
+
WHEN machines.last_seen_at > excluded.last_seen_at THEN machines.last_seen_at
|
|
65654
|
+
ELSE excluded.last_seen_at
|
|
65655
|
+
END
|
|
65656
|
+
RETURNING *, id = ? AS registration_created`).get(proposedId, machineName, host, machinePlatform, seenAt, seenAt, proposedId);
|
|
65657
|
+
const machine = machineFromDb(row, "machine registration upsert");
|
|
65658
|
+
if (!machine || typeof row?.registration_created !== "boolean" && typeof row?.registration_created !== "number") {
|
|
65659
|
+
throw new MachineRegistryError("MACHINE_REGISTRATION_CONFLICT", "Machine registration returned no stable identity receipt");
|
|
65660
|
+
}
|
|
65661
|
+
return { machine, created: Boolean(row.registration_created) };
|
|
65662
|
+
} catch (error) {
|
|
65663
|
+
if (error instanceof MachineRegistryError)
|
|
65664
|
+
throw error;
|
|
65665
|
+
const details = error && typeof error === "object" ? error : {};
|
|
65666
|
+
const diagnostic = `${String(details.constraint ?? "")} ${String(details.message ?? "")}`;
|
|
65667
|
+
if (/machines(?:_|\.)name|machines_name_key/i.test(diagnostic)) {
|
|
65668
|
+
throw new MachineRegistryError("MACHINE_NAME_CONFLICT", `Machine name already taken: ${machineName}`);
|
|
65669
|
+
}
|
|
65670
|
+
throw error;
|
|
65671
|
+
}
|
|
65672
|
+
}
|
|
65673
|
+
function registerMachine(name, db) {
|
|
65674
|
+
const host = normalizeMachineHostname(hostname());
|
|
65675
|
+
const plat = normalizeMachinePlatform(platform());
|
|
65676
|
+
if (!db && isApiMode()) {
|
|
65677
|
+
const input = { name: name?.trim() || undefined, hostname: host, platform: plat };
|
|
65678
|
+
const { data } = apiJson("POST", "/machines", input);
|
|
65679
|
+
return decodeRegistration(data, input).machine;
|
|
64473
65680
|
}
|
|
64474
|
-
|
|
64475
|
-
|
|
64476
|
-
|
|
64477
|
-
|
|
65681
|
+
return registerMachineRecord({ name, hostname: host, platform: plat }, db).machine;
|
|
65682
|
+
}
|
|
65683
|
+
function listMachines(db) {
|
|
65684
|
+
if (!db && isApiMode()) {
|
|
65685
|
+
const { data } = apiJson("GET", "/machines");
|
|
65686
|
+
return decodeMachineList(data);
|
|
64478
65687
|
}
|
|
64479
|
-
const
|
|
64480
|
-
|
|
64481
|
-
return
|
|
65688
|
+
const d = db ?? getDatabase();
|
|
65689
|
+
const rows = d.query("SELECT * FROM machines ORDER BY is_primary DESC, last_seen_at DESC, created_at ASC, id ASC").all();
|
|
65690
|
+
return rows.map((row) => machineFromDb(row));
|
|
64482
65691
|
}
|
|
64483
|
-
function getPrimaryMachine(db
|
|
64484
|
-
|
|
65692
|
+
function getPrimaryMachine(db) {
|
|
65693
|
+
if (!db && isApiMode())
|
|
65694
|
+
return listMachines().find((machine) => machine.is_primary) ?? null;
|
|
65695
|
+
const d = db ?? getDatabase();
|
|
65696
|
+
return machineFromDb(d.query("SELECT * FROM machines WHERE is_primary = 1 LIMIT 1").get());
|
|
64485
65697
|
}
|
|
64486
|
-
function getPrimaryMachineCandidate(db
|
|
64487
|
-
if (
|
|
65698
|
+
function getPrimaryMachineCandidate(db) {
|
|
65699
|
+
if (!db && isApiMode()) {
|
|
65700
|
+
const machines = listMachines();
|
|
65701
|
+
if (machines.some((machine) => machine.is_primary))
|
|
65702
|
+
return null;
|
|
65703
|
+
return [...machines].sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id))[0] ?? null;
|
|
65704
|
+
}
|
|
65705
|
+
const d = db ?? getDatabase();
|
|
65706
|
+
if (getPrimaryMachine(d))
|
|
64488
65707
|
return null;
|
|
64489
|
-
return
|
|
65708
|
+
return machineFromDb(d.query("SELECT * FROM machines ORDER BY created_at ASC, id ASC LIMIT 1").get());
|
|
64490
65709
|
}
|
|
64491
|
-
function getPrimaryMachineStartupWarning(db
|
|
65710
|
+
function getPrimaryMachineStartupWarning(db) {
|
|
64492
65711
|
if (getPrimaryMachine(db))
|
|
64493
65712
|
return null;
|
|
64494
65713
|
const candidate = getPrimaryMachineCandidate(db);
|
|
64495
|
-
if (!candidate)
|
|
65714
|
+
if (!candidate)
|
|
64496
65715
|
return "No primary machine configured. Fallback sync target is unset because no machines are registered yet.";
|
|
64497
|
-
}
|
|
64498
65716
|
return `No primary machine configured. Fallback sync target is unset. Candidate: ${candidate.name} (${candidate.id.slice(0, 8)} / ${candidate.hostname}). Confirm it with set_primary_machine.`;
|
|
64499
65717
|
}
|
|
64500
|
-
|
|
64501
|
-
|
|
64502
|
-
|
|
64503
|
-
|
|
64504
|
-
|
|
64505
|
-
|
|
64506
|
-
|
|
64507
|
-
|
|
64508
|
-
|
|
65718
|
+
var hostedMachineCache = null;
|
|
65719
|
+
function getCurrentMachineId(db) {
|
|
65720
|
+
if (!db && isApiMode()) {
|
|
65721
|
+
const host = normalizeMachineHostname(hostname());
|
|
65722
|
+
const plat = normalizeMachinePlatform(platform());
|
|
65723
|
+
const configBefore = getApiConfig();
|
|
65724
|
+
if (!configBefore)
|
|
65725
|
+
throw new Error("mementos hosted machine identity requires a resolved API authority");
|
|
65726
|
+
const credentialFingerprint = createHash("sha256").update(configBefore.apiKey).digest("hex");
|
|
65727
|
+
const key = `${configBefore.baseUrl}\x00${credentialFingerprint}\x00${host}\x00${plat}`;
|
|
65728
|
+
if (hostedMachineCache?.key === key)
|
|
65729
|
+
return hostedMachineCache.id;
|
|
65730
|
+
const id = registerMachine().id;
|
|
65731
|
+
const configAfter = getApiConfig();
|
|
65732
|
+
const fingerprintAfter = configAfter ? createHash("sha256").update(configAfter.apiKey).digest("hex") : null;
|
|
65733
|
+
if (!configAfter || configAfter.baseUrl !== configBefore.baseUrl || fingerprintAfter !== credentialFingerprint) {
|
|
65734
|
+
throw new Error("MEMENTOS_AUTHORITY_CHANGED: the machine registry authority or credential changed while resolving the stable machine identity");
|
|
65735
|
+
}
|
|
65736
|
+
hostedMachineCache = { key, id };
|
|
65737
|
+
return id;
|
|
64509
65738
|
}
|
|
64510
|
-
|
|
65739
|
+
const d = db ?? getDatabase();
|
|
65740
|
+
return registerMachine(undefined, d).id;
|
|
64511
65741
|
}
|
|
64512
65742
|
|
|
64513
65743
|
// src/cli/startup-side-effects.ts
|
|
@@ -65256,7 +66486,7 @@ init_memories();
|
|
|
65256
66486
|
init_package_version();
|
|
65257
66487
|
init_schema();
|
|
65258
66488
|
init_identity();
|
|
65259
|
-
import { createHash as
|
|
66489
|
+
import { createHash as createHash3 } from "crypto";
|
|
65260
66490
|
|
|
65261
66491
|
class MemoryProjectLinkError extends Error {
|
|
65262
66492
|
code;
|
|
@@ -65296,7 +66526,7 @@ function canonicalJson(value) {
|
|
|
65296
66526
|
return JSON.stringify(canonicalize(value));
|
|
65297
66527
|
}
|
|
65298
66528
|
function digest(value) {
|
|
65299
|
-
return
|
|
66529
|
+
return createHash3("sha256").update(canonicalJson(value)).digest("hex");
|
|
65300
66530
|
}
|
|
65301
66531
|
function timestampString2(value) {
|
|
65302
66532
|
return value instanceof Date ? value.toISOString() : String(value);
|
|
@@ -65881,7 +67111,7 @@ function getMemoryProjectLinkReceipt(memoryId, receiptId, identity = linkAuthori
|
|
|
65881
67111
|
}
|
|
65882
67112
|
|
|
65883
67113
|
// src/project-registration/authority.ts
|
|
65884
|
-
import { createHash as
|
|
67114
|
+
import { createHash as createHash4 } from "crypto";
|
|
65885
67115
|
init_projects();
|
|
65886
67116
|
init_types2();
|
|
65887
67117
|
init_identity();
|
|
@@ -65903,7 +67133,7 @@ function canonicalize2(value) {
|
|
|
65903
67133
|
return out;
|
|
65904
67134
|
}
|
|
65905
67135
|
function digestMementosProjectRegistrationValue(value) {
|
|
65906
|
-
return
|
|
67136
|
+
return createHash4("sha256").update(canonicalMementosProjectRegistrationJson(value)).digest("hex");
|
|
65907
67137
|
}
|
|
65908
67138
|
// src/project-registration/http.ts
|
|
65909
67139
|
init_types2();
|
|
@@ -67303,16 +68533,164 @@ init_redact();
|
|
|
67303
68533
|
init_helpers();
|
|
67304
68534
|
import chalk12 from "chalk";
|
|
67305
68535
|
import { resolve as resolve8 } from "path";
|
|
68536
|
+
var STRUCTURED_PAGE_MAX_ROWS = 1000;
|
|
68537
|
+
var STRUCTURED_ALL_MAX_ROWS = 5000;
|
|
68538
|
+
var STRUCTURED_DEFAULT_MAX_BYTES = 32 * 1024;
|
|
68539
|
+
var STRUCTURED_FULL_MAX_BYTES = 64 * 1024;
|
|
68540
|
+
var STRUCTURED_ALL_MAX_BYTES = 1024 * 1024;
|
|
68541
|
+
var STRUCTURED_MIN_MAX_BYTES = 1024;
|
|
68542
|
+
function structuredMaxBytes(value, opts) {
|
|
68543
|
+
const fallback = opts.all ? STRUCTURED_ALL_MAX_BYTES : opts.detail === "full" ? STRUCTURED_FULL_MAX_BYTES : STRUCTURED_DEFAULT_MAX_BYTES;
|
|
68544
|
+
if (value === undefined)
|
|
68545
|
+
return fallback;
|
|
68546
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
68547
|
+
if (!Number.isInteger(parsed) || parsed < STRUCTURED_MIN_MAX_BYTES || parsed > STRUCTURED_ALL_MAX_BYTES) {
|
|
68548
|
+
throw new Error(`--max-bytes must be an integer from ${STRUCTURED_MIN_MAX_BYTES} to ${STRUCTURED_ALL_MAX_BYTES}`);
|
|
68549
|
+
}
|
|
68550
|
+
return parsed;
|
|
68551
|
+
}
|
|
68552
|
+
function compactMemoryForStructuredOutput(memory, opts = {}) {
|
|
68553
|
+
const value = truncateText(memory.value, 160);
|
|
68554
|
+
const summary = memory.summary ? truncateText(memory.summary, 160) : null;
|
|
68555
|
+
return {
|
|
68556
|
+
id: memory.id,
|
|
68557
|
+
key: memory.key,
|
|
68558
|
+
value,
|
|
68559
|
+
...summary ? { summary } : {},
|
|
68560
|
+
scope: memory.scope,
|
|
68561
|
+
category: memory.category,
|
|
68562
|
+
importance: memory.importance,
|
|
68563
|
+
status: memory.status,
|
|
68564
|
+
pinned: memory.pinned,
|
|
68565
|
+
...Array.isArray(memory.tags) && memory.tags.length ? { tags: memory.tags.slice(0, 10) } : {},
|
|
68566
|
+
...memory.agent_id ? { agent_id: memory.agent_id } : {},
|
|
68567
|
+
...memory.project_id ? { project_id: memory.project_id } : {},
|
|
68568
|
+
...memory.session_id ? { session_id: memory.session_id } : {},
|
|
68569
|
+
...opts.history && memory.accessed_at ? { accessed_at: memory.accessed_at } : {},
|
|
68570
|
+
updated_at: memory.updated_at
|
|
68571
|
+
};
|
|
68572
|
+
}
|
|
68573
|
+
function withResponseBytes(envelope) {
|
|
68574
|
+
for (let attempt = 0;attempt < 8; attempt += 1) {
|
|
68575
|
+
const bytes = Buffer.byteLength(`${JSON.stringify(envelope)}
|
|
68576
|
+
`);
|
|
68577
|
+
if (bytes === envelope._meta.response_bytes)
|
|
68578
|
+
return envelope;
|
|
68579
|
+
envelope._meta.response_bytes = bytes;
|
|
68580
|
+
}
|
|
68581
|
+
return envelope;
|
|
68582
|
+
}
|
|
68583
|
+
function makeStructuredEnvelope(args) {
|
|
68584
|
+
const hasMore = args.byteTruncated || args.sourceHasMore;
|
|
68585
|
+
const complete = args.offset === 0 && !hasMore;
|
|
68586
|
+
const nextCursor = hasMore ? args.offset + args.memories.length : null;
|
|
68587
|
+
const truncationReason = args.byteTruncated ? "max_bytes" : args.sourceHasMore ? "limit" : args.offset > 0 ? "cursor" : null;
|
|
68588
|
+
return withResponseBytes({
|
|
68589
|
+
memories: args.memories,
|
|
68590
|
+
_meta: {
|
|
68591
|
+
receipt: args.receipt,
|
|
68592
|
+
count: args.memories.length,
|
|
68593
|
+
limit: args.all ? null : args.limit,
|
|
68594
|
+
offset: args.offset,
|
|
68595
|
+
next_cursor: nextCursor,
|
|
68596
|
+
has_more: hasMore,
|
|
68597
|
+
complete,
|
|
68598
|
+
all: args.all,
|
|
68599
|
+
detail: args.detail,
|
|
68600
|
+
max_rows: args.all ? STRUCTURED_ALL_MAX_ROWS : STRUCTURED_PAGE_MAX_ROWS,
|
|
68601
|
+
max_bytes: args.maxBytes,
|
|
68602
|
+
response_bytes: 0,
|
|
68603
|
+
truncated: !complete,
|
|
68604
|
+
truncation_reason: truncationReason,
|
|
68605
|
+
omitted_from_page: args.omittedFromPage,
|
|
68606
|
+
next_arguments: nextCursor === null ? null : {
|
|
68607
|
+
cursor: nextCursor,
|
|
68608
|
+
limit: args.limit,
|
|
68609
|
+
agent_json: true,
|
|
68610
|
+
...args.detail === "full" ? { full: true } : {},
|
|
68611
|
+
max_bytes: args.maxBytes
|
|
68612
|
+
},
|
|
68613
|
+
continuation_scope: "unchanged_snapshot"
|
|
68614
|
+
}
|
|
68615
|
+
});
|
|
68616
|
+
}
|
|
68617
|
+
function structuredMemoryOutput(args) {
|
|
68618
|
+
const projected = args.detail === "full" ? args.memories : args.memories.map((memory) => compactMemoryForStructuredOutput(memory, { history: args.history }));
|
|
68619
|
+
const completeEnvelope = makeStructuredEnvelope({
|
|
68620
|
+
memories: projected,
|
|
68621
|
+
receipt: args.receipt,
|
|
68622
|
+
offset: args.offset,
|
|
68623
|
+
limit: args.limit,
|
|
68624
|
+
sourceHasMore: args.sourceHasMore,
|
|
68625
|
+
all: args.all,
|
|
68626
|
+
detail: args.detail,
|
|
68627
|
+
maxBytes: args.maxBytes,
|
|
68628
|
+
byteTruncated: false,
|
|
68629
|
+
omittedFromPage: 0
|
|
68630
|
+
});
|
|
68631
|
+
const completeText = `${JSON.stringify(completeEnvelope)}
|
|
68632
|
+
`;
|
|
68633
|
+
if (Buffer.byteLength(completeText) <= args.maxBytes)
|
|
68634
|
+
return completeText;
|
|
68635
|
+
if (args.all) {
|
|
68636
|
+
throw new Error(`Exhaustive structured output exceeds the hard safety limit of ${args.maxBytes} bytes; use paginated JSON output instead`);
|
|
68637
|
+
}
|
|
68638
|
+
for (let count = projected.length - 1;count >= 0; count -= 1) {
|
|
68639
|
+
const envelope = makeStructuredEnvelope({
|
|
68640
|
+
memories: projected.slice(0, count),
|
|
68641
|
+
receipt: args.receipt,
|
|
68642
|
+
offset: args.offset,
|
|
68643
|
+
limit: args.limit,
|
|
68644
|
+
sourceHasMore: true,
|
|
68645
|
+
all: false,
|
|
68646
|
+
detail: args.detail,
|
|
68647
|
+
maxBytes: args.maxBytes,
|
|
68648
|
+
byteTruncated: true,
|
|
68649
|
+
omittedFromPage: projected.length - count
|
|
68650
|
+
});
|
|
68651
|
+
const text = `${JSON.stringify(envelope)}
|
|
68652
|
+
`;
|
|
68653
|
+
if (Buffer.byteLength(text) > args.maxBytes)
|
|
68654
|
+
continue;
|
|
68655
|
+
if (count === 0 && projected.length > 0) {
|
|
68656
|
+
throw new Error(`One structured memory row exceeds --max-bytes=${args.maxBytes}; use compact detail, a larger --max-bytes value, or mementos show <id>`);
|
|
68657
|
+
}
|
|
68658
|
+
return text;
|
|
68659
|
+
}
|
|
68660
|
+
throw new Error(`Structured output metadata exceeds --max-bytes=${args.maxBytes}`);
|
|
68661
|
+
}
|
|
68662
|
+
function assertReceiptFlags(opts, receiptMode, requestedFormat) {
|
|
68663
|
+
if (!receiptMode && (opts.all || opts.full || opts.maxBytes !== undefined)) {
|
|
68664
|
+
throw new Error("--all, --full, and --max-bytes require --agent-json receipt mode");
|
|
68665
|
+
}
|
|
68666
|
+
if (receiptMode && requestedFormat !== undefined && requestedFormat !== "json") {
|
|
68667
|
+
throw new Error("--agent-json cannot be combined with a non-JSON --format");
|
|
68668
|
+
}
|
|
68669
|
+
if (opts.all && opts.limit !== undefined) {
|
|
68670
|
+
throw new Error("--all cannot be combined with --limit");
|
|
68671
|
+
}
|
|
68672
|
+
}
|
|
67306
68673
|
function registerListCommand(program2) {
|
|
67307
68674
|
const handleError = makeHandleError(program2);
|
|
67308
|
-
program2.command("list").description("List memories with optional filters").option("--scope <scope>", "Scope filter").option("-c, --category <cat>", "Category filter").option("--tags <tags>", "Comma-separated tags filter").option("--importance-min <n>", "Minimum importance", parseInt).option("--pinned", "Show only pinned").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").option("--session <id>", "Session ID filter").option("--limit <n>",
|
|
68675
|
+
program2.command("list").description("List memories with optional filters").option("--scope <scope>", "Scope filter").option("-c, --category <cat>", "Category filter").option("--tags <tags>", "Comma-separated tags filter").option("--importance-min <n>", "Minimum importance", parseInt).option("--pinned", "Show only pinned").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").option("--session <id>", "Session ID filter").option("--limit <n>", `Max results (agent JSON page max: ${STRUCTURED_PAGE_MAX_ROWS})`, parseInt).option("--offset <n>", "Offset for pagination", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--status <status>", "Status filter: active, archived, expired").option("--format <fmt>", "Output format: compact (default), json, csv, yaml").option("--verbose", "Show wider memory snippets in human output").option("--agent-json", "Output a bounded, receipt-bearing JSON page").option("--all", `Exhaust agent JSON results from offset zero (hard max: ${STRUCTURED_ALL_MAX_ROWS} rows)`).option("--full", "Emit full memory objects in agent JSON instead of compact projections").option("--max-bytes <n>", `Agent JSON response byte ceiling (hard max: ${STRUCTURED_ALL_MAX_BYTES})`, parseInt).action((opts) => {
|
|
67309
68676
|
try {
|
|
67310
68677
|
const globalOpts = program2.opts();
|
|
68678
|
+
const requestedFormat = opts.format ?? globalOpts.format;
|
|
67311
68679
|
const fmt = getOutputFormat(program2, opts.format);
|
|
68680
|
+
const receiptMode = Boolean(opts.agentJson);
|
|
67312
68681
|
const isStructured = fmt === "json" || fmt === "csv" || fmt === "yaml";
|
|
68682
|
+
assertReceiptFlags(opts, receiptMode, requestedFormat);
|
|
67313
68683
|
const requestedLimit = opts.limit;
|
|
67314
|
-
const
|
|
68684
|
+
const all = Boolean(opts.all);
|
|
68685
|
+
const detail = opts.full ? "full" : "compact";
|
|
68686
|
+
const limit = requestedLimit === undefined ? receiptMode ? DEFAULT_COMPACT_LIMIT : isStructured ? undefined : DEFAULT_COMPACT_LIMIT : positiveIntOrDefault(requestedLimit, receiptMode ? DEFAULT_COMPACT_LIMIT : isStructured ? 50 : DEFAULT_COMPACT_LIMIT);
|
|
68687
|
+
if (receiptMode && limit !== undefined && limit > STRUCTURED_PAGE_MAX_ROWS) {
|
|
68688
|
+
throw new Error(`--limit cannot exceed the agent JSON page ceiling of ${STRUCTURED_PAGE_MAX_ROWS}; use --all for a bounded exhaustive read`);
|
|
68689
|
+
}
|
|
67315
68690
|
const offset = cursorOrOffset(opts.cursor, opts.offset) ?? 0;
|
|
68691
|
+
if (all && offset !== 0) {
|
|
68692
|
+
throw new Error("--all requires --cursor/--offset 0");
|
|
68693
|
+
}
|
|
67316
68694
|
const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
|
|
67317
68695
|
const projectPath = opts.project || globalOpts.project;
|
|
67318
68696
|
let projectId;
|
|
@@ -67334,6 +68712,7 @@ function registerListCommand(program2) {
|
|
|
67334
68712
|
status: opts.status,
|
|
67335
68713
|
session_id: opts.session || globalOpts.session
|
|
67336
68714
|
};
|
|
68715
|
+
const target = all ? STRUCTURED_ALL_MAX_ROWS : limit;
|
|
67337
68716
|
const { rows: collected, hasMore } = collectPagedRows((cursor, pageLimit) => {
|
|
67338
68717
|
const page = listMemoriesPage({
|
|
67339
68718
|
...filter,
|
|
@@ -67345,9 +68724,25 @@ function registerListCommand(program2) {
|
|
|
67345
68724
|
has_more: page.has_more,
|
|
67346
68725
|
next_cursor: page.next_cursor
|
|
67347
68726
|
};
|
|
67348
|
-
},
|
|
67349
|
-
|
|
68727
|
+
}, target, offset);
|
|
68728
|
+
if (all && hasMore) {
|
|
68729
|
+
throw new Error(`Exhaustive structured output exceeds the hard safety limit of ${STRUCTURED_ALL_MAX_ROWS} rows; use paginated JSON output instead`);
|
|
68730
|
+
}
|
|
68731
|
+
const memories = target === undefined ? collected : collected.slice(0, target);
|
|
67350
68732
|
const sanitized = memories.map(redactMemoryForOutput);
|
|
68733
|
+
if (receiptMode) {
|
|
68734
|
+
process.stdout.write(structuredMemoryOutput({
|
|
68735
|
+
memories: sanitized,
|
|
68736
|
+
receipt: "mementos.list.page.v1",
|
|
68737
|
+
offset,
|
|
68738
|
+
limit: limit ?? DEFAULT_COMPACT_LIMIT,
|
|
68739
|
+
sourceHasMore: hasMore,
|
|
68740
|
+
all,
|
|
68741
|
+
detail,
|
|
68742
|
+
maxBytes: structuredMaxBytes(opts.maxBytes, { all, detail })
|
|
68743
|
+
}));
|
|
68744
|
+
return;
|
|
68745
|
+
}
|
|
67351
68746
|
if (fmt === "json") {
|
|
67352
68747
|
outputJson(sanitized);
|
|
67353
68748
|
return;
|
|
@@ -67381,7 +68776,7 @@ function registerListCommand(program2) {
|
|
|
67381
68776
|
offset,
|
|
67382
68777
|
hasMore,
|
|
67383
68778
|
command: "mementos list",
|
|
67384
|
-
detailHint: "use mementos show <id> for full details or --json for
|
|
68779
|
+
detailHint: "use mementos show <id> for full details, --json for the compatible full array, or --agent-json for a bounded receipt"
|
|
67385
68780
|
});
|
|
67386
68781
|
} catch (e) {
|
|
67387
68782
|
handleError(e);
|
|
@@ -67808,13 +69203,33 @@ init_helpers();
|
|
|
67808
69203
|
import chalk16 from "chalk";
|
|
67809
69204
|
function registerHistoryCommand(program2) {
|
|
67810
69205
|
const handleError = makeHandleError(program2);
|
|
67811
|
-
program2.command("history").description("List memories sorted by most recently accessed").option("--limit <n>",
|
|
69206
|
+
program2.command("history").description("List memories sorted by most recently accessed").option("--limit <n>", `Max results (agent JSON page max: ${STRUCTURED_PAGE_MAX_ROWS})`, parseInt).option("--offset <n>", "Offset for pagination", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--verbose", "Show wider memory snippets").option("--agent-json", "Output a bounded, receipt-bearing JSON page").option("--all", `Exhaust agent JSON results from offset zero (hard max: ${STRUCTURED_ALL_MAX_ROWS} rows)`).option("--full", "Emit full memory objects in agent JSON instead of compact projections").option("--max-bytes <n>", `Agent JSON response byte ceiling (hard max: ${STRUCTURED_ALL_MAX_BYTES})`, parseInt).action((opts) => {
|
|
67812
69207
|
try {
|
|
67813
69208
|
const globalOpts = program2.opts();
|
|
67814
|
-
const
|
|
69209
|
+
const format = getOutputFormat(program2);
|
|
69210
|
+
const receiptMode = Boolean(opts.agentJson);
|
|
69211
|
+
const isJson = format === "json";
|
|
69212
|
+
if (!receiptMode && (opts.all || opts.full || opts.maxBytes !== undefined)) {
|
|
69213
|
+
throw new Error("--all, --full, and --max-bytes require --agent-json receipt mode");
|
|
69214
|
+
}
|
|
69215
|
+
if (receiptMode && globalOpts.format !== undefined && globalOpts.format !== "json") {
|
|
69216
|
+
throw new Error("--agent-json cannot be combined with a non-JSON --format");
|
|
69217
|
+
}
|
|
69218
|
+
if (opts.all && opts.limit !== undefined) {
|
|
69219
|
+
throw new Error("--all cannot be combined with --limit");
|
|
69220
|
+
}
|
|
67815
69221
|
const requestedLimit = opts.limit;
|
|
67816
|
-
const
|
|
69222
|
+
const all = Boolean(opts.all);
|
|
69223
|
+
const detail = opts.full ? "full" : "compact";
|
|
69224
|
+
const limit = requestedLimit === undefined ? receiptMode ? DEFAULT_SEARCH_LIMIT : isJson ? undefined : DEFAULT_SEARCH_LIMIT : positiveIntOrDefault(requestedLimit, receiptMode ? DEFAULT_SEARCH_LIMIT : isJson ? 20 : DEFAULT_SEARCH_LIMIT);
|
|
69225
|
+
if (receiptMode && limit !== undefined && limit > STRUCTURED_PAGE_MAX_ROWS) {
|
|
69226
|
+
throw new Error(`--limit cannot exceed the agent JSON page ceiling of ${STRUCTURED_PAGE_MAX_ROWS}; use --all for a bounded exhaustive read`);
|
|
69227
|
+
}
|
|
67817
69228
|
const offset = cursorOrOffset(opts.cursor, opts.offset) ?? 0;
|
|
69229
|
+
if (all && offset !== 0) {
|
|
69230
|
+
throw new Error("--all requires --cursor/--offset 0");
|
|
69231
|
+
}
|
|
69232
|
+
const target = all ? STRUCTURED_ALL_MAX_ROWS : limit;
|
|
67818
69233
|
const { rows: collected, hasMore } = collectPagedRows((cursor, pageLimit) => {
|
|
67819
69234
|
const page = listMemoryHistoryPage({ limit: pageLimit, offset: cursor });
|
|
67820
69235
|
return {
|
|
@@ -67822,10 +69237,27 @@ function registerHistoryCommand(program2) {
|
|
|
67822
69237
|
has_more: page.has_more,
|
|
67823
69238
|
next_cursor: page.next_cursor
|
|
67824
69239
|
};
|
|
67825
|
-
},
|
|
67826
|
-
|
|
69240
|
+
}, target, offset);
|
|
69241
|
+
if (all && hasMore) {
|
|
69242
|
+
throw new Error(`Exhaustive structured output exceeds the hard safety limit of ${STRUCTURED_ALL_MAX_ROWS} rows; use paginated JSON output instead`);
|
|
69243
|
+
}
|
|
69244
|
+
const memories = target === undefined ? collected : collected.slice(0, target);
|
|
67827
69245
|
const sanitized = memories.map(redactMemoryForOutput);
|
|
67828
|
-
if (
|
|
69246
|
+
if (receiptMode) {
|
|
69247
|
+
process.stdout.write(structuredMemoryOutput({
|
|
69248
|
+
memories: sanitized,
|
|
69249
|
+
receipt: "mementos.history.page.v1",
|
|
69250
|
+
offset,
|
|
69251
|
+
limit: limit ?? DEFAULT_SEARCH_LIMIT,
|
|
69252
|
+
sourceHasMore: hasMore,
|
|
69253
|
+
all,
|
|
69254
|
+
detail,
|
|
69255
|
+
maxBytes: structuredMaxBytes(opts.maxBytes, { all, detail }),
|
|
69256
|
+
history: true
|
|
69257
|
+
}));
|
|
69258
|
+
return;
|
|
69259
|
+
}
|
|
69260
|
+
if (isJson) {
|
|
67829
69261
|
outputJson(sanitized);
|
|
67830
69262
|
return;
|
|
67831
69263
|
}
|
|
@@ -67848,7 +69280,7 @@ function registerHistoryCommand(program2) {
|
|
|
67848
69280
|
offset,
|
|
67849
69281
|
hasMore,
|
|
67850
69282
|
command: "mementos history",
|
|
67851
|
-
detailHint: "use mementos show <id> for full details or --json for
|
|
69283
|
+
detailHint: "use mementos show <id> for full details, --json for the compatible full array, or --agent-json for a bounded receipt"
|
|
67852
69284
|
});
|
|
67853
69285
|
} catch (e) {
|
|
67854
69286
|
handleError(e);
|
|
@@ -67863,13 +69295,16 @@ import chalk17 from "chalk";
|
|
|
67863
69295
|
import { resolve as resolve11 } from "path";
|
|
67864
69296
|
|
|
67865
69297
|
// src/lib/machine-visibility.ts
|
|
69298
|
+
init_api_mode();
|
|
67866
69299
|
function resolveVisibleMachineId(machineId, db) {
|
|
67867
69300
|
if (machineId !== undefined) {
|
|
67868
69301
|
return machineId;
|
|
67869
69302
|
}
|
|
67870
69303
|
try {
|
|
67871
69304
|
return getCurrentMachineId(db);
|
|
67872
|
-
} catch {
|
|
69305
|
+
} catch (error) {
|
|
69306
|
+
if (!db && isApiMode())
|
|
69307
|
+
throw error;
|
|
67873
69308
|
return null;
|
|
67874
69309
|
}
|
|
67875
69310
|
}
|
|
@@ -67900,13 +69335,16 @@ function registerContextCommand(program2) {
|
|
|
67900
69335
|
const categoriesRaw = opts.categories;
|
|
67901
69336
|
const categories = categoriesRaw ? categoriesRaw.split(",").map((c) => c.trim()) : undefined;
|
|
67902
69337
|
const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
|
|
67903
|
-
const projectPath = opts.project
|
|
69338
|
+
const projectPath = opts.project ?? globalOpts.project;
|
|
67904
69339
|
const visibleMachineId = resolveVisibleMachineId(opts.machine);
|
|
67905
69340
|
let projectId;
|
|
67906
|
-
if (projectPath) {
|
|
69341
|
+
if (projectPath !== undefined) {
|
|
69342
|
+
if (!projectPath.trim())
|
|
69343
|
+
throw new Error("Project not found: empty project path");
|
|
67907
69344
|
const project = getProject(resolve11(projectPath));
|
|
67908
|
-
if (project)
|
|
67909
|
-
|
|
69345
|
+
if (!project)
|
|
69346
|
+
throw new Error(`Project not found: ${projectPath}`);
|
|
69347
|
+
projectId = project.id;
|
|
67910
69348
|
}
|
|
67911
69349
|
let memories;
|
|
67912
69350
|
if (query) {
|
|
@@ -67954,6 +69392,7 @@ function registerContextCommand(program2) {
|
|
|
67954
69392
|
...baseFilter,
|
|
67955
69393
|
scope: "private",
|
|
67956
69394
|
agent_id: agentId,
|
|
69395
|
+
project_id: projectId,
|
|
67957
69396
|
...visibleToMachineFilter(visibleMachineId)
|
|
67958
69397
|
}));
|
|
67959
69398
|
}
|
|
@@ -68090,247 +69529,13 @@ function registerImportCommand(program2) {
|
|
|
68090
69529
|
}
|
|
68091
69530
|
|
|
68092
69531
|
// src/cli/commands/io-clean.ts
|
|
69532
|
+
init_config();
|
|
68093
69533
|
import chalk19 from "chalk";
|
|
68094
69534
|
|
|
68095
|
-
// src/lib/config.ts
|
|
68096
|
-
init_paths();
|
|
68097
|
-
init_env();
|
|
68098
|
-
init_local_opt_in();
|
|
68099
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, cpSync as cpSync2 } from "fs";
|
|
68100
|
-
import { homedir as homedir4 } from "os";
|
|
68101
|
-
import { basename, dirname as dirname4, join as join9, resolve as resolve14 } from "path";
|
|
68102
|
-
function isInMemoryDb2(path) {
|
|
68103
|
-
return path === ":memory:" || path.startsWith("file::memory:");
|
|
68104
|
-
}
|
|
68105
|
-
function homeDir() {
|
|
68106
|
-
return process.env["HOME"] || process.env["USERPROFILE"] || homedir4();
|
|
68107
|
-
}
|
|
68108
|
-
var DEFAULT_CONFIG = {
|
|
68109
|
-
default_scope: "private",
|
|
68110
|
-
default_category: "knowledge",
|
|
68111
|
-
default_importance: 5,
|
|
68112
|
-
max_entries: 1000,
|
|
68113
|
-
max_entries_per_scope: {
|
|
68114
|
-
global: 500,
|
|
68115
|
-
shared: 300,
|
|
68116
|
-
private: 200,
|
|
68117
|
-
working: 100
|
|
68118
|
-
},
|
|
68119
|
-
injection: {
|
|
68120
|
-
max_tokens: 500,
|
|
68121
|
-
min_importance: 5,
|
|
68122
|
-
categories: ["preference", "fact"],
|
|
68123
|
-
refresh_interval: 5
|
|
68124
|
-
},
|
|
68125
|
-
extraction: {
|
|
68126
|
-
enabled: true,
|
|
68127
|
-
min_confidence: 0.5
|
|
68128
|
-
},
|
|
68129
|
-
sync_agents: ["claude", "codex", "gemini"],
|
|
68130
|
-
auto_cleanup: {
|
|
68131
|
-
enabled: true,
|
|
68132
|
-
expired_check_interval: 3600,
|
|
68133
|
-
unused_archive_days: 7,
|
|
68134
|
-
stale_deprioritize_days: 14
|
|
68135
|
-
}
|
|
68136
|
-
};
|
|
68137
|
-
function deepMerge(target, source) {
|
|
68138
|
-
const result = { ...target };
|
|
68139
|
-
for (const key of Object.keys(source)) {
|
|
68140
|
-
const sourceVal = source[key];
|
|
68141
|
-
const targetVal = result[key];
|
|
68142
|
-
if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) {
|
|
68143
|
-
result[key] = deepMerge(targetVal, sourceVal);
|
|
68144
|
-
} else {
|
|
68145
|
-
result[key] = sourceVal;
|
|
68146
|
-
}
|
|
68147
|
-
}
|
|
68148
|
-
return result;
|
|
68149
|
-
}
|
|
68150
|
-
var VALID_SCOPES2 = ["global", "shared", "private", "working"];
|
|
68151
|
-
var VALID_CATEGORIES2 = [
|
|
68152
|
-
"preference",
|
|
68153
|
-
"fact",
|
|
68154
|
-
"knowledge",
|
|
68155
|
-
"history"
|
|
68156
|
-
];
|
|
68157
|
-
function isValidScope(value) {
|
|
68158
|
-
return VALID_SCOPES2.includes(value);
|
|
68159
|
-
}
|
|
68160
|
-
function isValidCategory(value) {
|
|
68161
|
-
return VALID_CATEGORIES2.includes(value);
|
|
68162
|
-
}
|
|
68163
|
-
function loadConfig() {
|
|
68164
|
-
const configPath = join9(getDataRoot(), "config.json");
|
|
68165
|
-
let fileConfig = {};
|
|
68166
|
-
if (existsSync6(configPath)) {
|
|
68167
|
-
try {
|
|
68168
|
-
const raw = readFileSync6(configPath, "utf-8");
|
|
68169
|
-
fileConfig = JSON.parse(raw);
|
|
68170
|
-
} catch {}
|
|
68171
|
-
}
|
|
68172
|
-
const merged = deepMerge(DEFAULT_CONFIG, fileConfig);
|
|
68173
|
-
const envScope = env.defaultScope();
|
|
68174
|
-
if (envScope && isValidScope(envScope)) {
|
|
68175
|
-
merged.default_scope = envScope;
|
|
68176
|
-
}
|
|
68177
|
-
const envCategory = env.defaultCategory();
|
|
68178
|
-
if (envCategory && isValidCategory(envCategory)) {
|
|
68179
|
-
merged.default_category = envCategory;
|
|
68180
|
-
}
|
|
68181
|
-
const envImportance = env.defaultImportance();
|
|
68182
|
-
if (envImportance) {
|
|
68183
|
-
const parsed = parseInt(envImportance, 10);
|
|
68184
|
-
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 10) {
|
|
68185
|
-
merged.default_importance = parsed;
|
|
68186
|
-
}
|
|
68187
|
-
}
|
|
68188
|
-
return merged;
|
|
68189
|
-
}
|
|
68190
|
-
function findFileWalkingUp(filename) {
|
|
68191
|
-
let dir = process.cwd();
|
|
68192
|
-
const legacyHomeMementosDb = resolve14(homeDir(), ".mementos", "mementos.db");
|
|
68193
|
-
while (true) {
|
|
68194
|
-
const candidate = join9(dir, filename);
|
|
68195
|
-
if (existsSync6(candidate) && resolve14(candidate) !== legacyHomeMementosDb) {
|
|
68196
|
-
return candidate;
|
|
68197
|
-
}
|
|
68198
|
-
const parent = dirname4(dir);
|
|
68199
|
-
if (parent === dir) {
|
|
68200
|
-
return null;
|
|
68201
|
-
}
|
|
68202
|
-
dir = parent;
|
|
68203
|
-
}
|
|
68204
|
-
}
|
|
68205
|
-
function findGitRoot2() {
|
|
68206
|
-
let dir = process.cwd();
|
|
68207
|
-
while (true) {
|
|
68208
|
-
if (existsSync6(join9(dir, ".git"))) {
|
|
68209
|
-
return dir;
|
|
68210
|
-
}
|
|
68211
|
-
const parent = dirname4(dir);
|
|
68212
|
-
if (parent === dir) {
|
|
68213
|
-
return null;
|
|
68214
|
-
}
|
|
68215
|
-
dir = parent;
|
|
68216
|
-
}
|
|
68217
|
-
}
|
|
68218
|
-
function profilesDir() {
|
|
68219
|
-
return join9(getDataRoot(), "profiles");
|
|
68220
|
-
}
|
|
68221
|
-
function globalConfigPath() {
|
|
68222
|
-
return join9(getDataRoot(), "config.json");
|
|
68223
|
-
}
|
|
68224
|
-
function readGlobalConfig() {
|
|
68225
|
-
const p = globalConfigPath();
|
|
68226
|
-
if (!existsSync6(p))
|
|
68227
|
-
return {};
|
|
68228
|
-
try {
|
|
68229
|
-
return JSON.parse(readFileSync6(p, "utf-8"));
|
|
68230
|
-
} catch {
|
|
68231
|
-
return {};
|
|
68232
|
-
}
|
|
68233
|
-
}
|
|
68234
|
-
function readGlobalConfigForWrite() {
|
|
68235
|
-
const p = globalConfigPath();
|
|
68236
|
-
if (!existsSync6(p))
|
|
68237
|
-
return {};
|
|
68238
|
-
try {
|
|
68239
|
-
const data = JSON.parse(readFileSync6(p, "utf-8"));
|
|
68240
|
-
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
68241
|
-
throw new Error("expected a JSON object");
|
|
68242
|
-
}
|
|
68243
|
-
return data;
|
|
68244
|
-
} catch (error) {
|
|
68245
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
68246
|
-
throw new Error(`Cannot update global config at ${p}: ${detail}`);
|
|
68247
|
-
}
|
|
68248
|
-
}
|
|
68249
|
-
function writeGlobalConfig(data) {
|
|
68250
|
-
const p = globalConfigPath();
|
|
68251
|
-
ensureDir2(dirname4(p));
|
|
68252
|
-
writeFileSync3(p, JSON.stringify(data, null, 2), "utf-8");
|
|
68253
|
-
}
|
|
68254
|
-
function getActiveProfile() {
|
|
68255
|
-
const envProfile = process.env["MEMENTOS_PROFILE"];
|
|
68256
|
-
if (envProfile)
|
|
68257
|
-
return envProfile.trim();
|
|
68258
|
-
const cfg = readGlobalConfig();
|
|
68259
|
-
return cfg["active_profile"] || null;
|
|
68260
|
-
}
|
|
68261
|
-
function setActiveProfile(name) {
|
|
68262
|
-
const cfg = readGlobalConfigForWrite();
|
|
68263
|
-
if (name === null) {
|
|
68264
|
-
delete cfg["active_profile"];
|
|
68265
|
-
} else {
|
|
68266
|
-
cfg["active_profile"] = name;
|
|
68267
|
-
}
|
|
68268
|
-
writeGlobalConfig(cfg);
|
|
68269
|
-
}
|
|
68270
|
-
function listProfiles() {
|
|
68271
|
-
const dir = profilesDir();
|
|
68272
|
-
if (!existsSync6(dir))
|
|
68273
|
-
return [];
|
|
68274
|
-
return readdirSync(dir).filter((f) => f.endsWith(".db")).map((f) => basename(f, ".db")).sort();
|
|
68275
|
-
}
|
|
68276
|
-
function deleteProfile(name) {
|
|
68277
|
-
const dbPath = join9(profilesDir(), `${name}.db`);
|
|
68278
|
-
if (!existsSync6(dbPath))
|
|
68279
|
-
return false;
|
|
68280
|
-
unlinkSync2(dbPath);
|
|
68281
|
-
if (getActiveProfile() === name)
|
|
68282
|
-
setActiveProfile(null);
|
|
68283
|
-
return true;
|
|
68284
|
-
}
|
|
68285
|
-
function getDbPath2() {
|
|
68286
|
-
const _home = homeDir();
|
|
68287
|
-
const _newDir = getDataRoot();
|
|
68288
|
-
const _oldDir = join9(_home, ".mementos");
|
|
68289
|
-
if (selectsMementosLocalStore() && !existsSync6(_newDir) && existsSync6(_oldDir)) {
|
|
68290
|
-
mkdirSync3(join9(_home, ".hasna"), { recursive: true });
|
|
68291
|
-
cpSync2(_oldDir, _newDir, { recursive: true });
|
|
68292
|
-
}
|
|
68293
|
-
const envDbPath = process.env["HASNA_MEMENTOS_DB_PATH"] ?? process.env["MEMENTOS_DB_PATH"];
|
|
68294
|
-
if (envDbPath) {
|
|
68295
|
-
if (isInMemoryDb2(envDbPath)) {
|
|
68296
|
-
return envDbPath;
|
|
68297
|
-
}
|
|
68298
|
-
const resolved = resolve14(envDbPath);
|
|
68299
|
-
ensureDir2(dirname4(resolved));
|
|
68300
|
-
return resolved;
|
|
68301
|
-
}
|
|
68302
|
-
const profile = getActiveProfile();
|
|
68303
|
-
if (profile) {
|
|
68304
|
-
const profilePath = join9(profilesDir(), `${profile}.db`);
|
|
68305
|
-
ensureDir2(dirname4(profilePath));
|
|
68306
|
-
return profilePath;
|
|
68307
|
-
}
|
|
68308
|
-
const dbScope = process.env["MEMENTOS_DB_SCOPE"];
|
|
68309
|
-
if (dbScope === "project") {
|
|
68310
|
-
const gitRoot = findGitRoot2();
|
|
68311
|
-
if (gitRoot) {
|
|
68312
|
-
const dbPath = join9(gitRoot, ".mementos", "mementos.db");
|
|
68313
|
-
ensureDir2(dirname4(dbPath));
|
|
68314
|
-
return dbPath;
|
|
68315
|
-
}
|
|
68316
|
-
}
|
|
68317
|
-
const found = findFileWalkingUp(join9(".mementos", "mementos.db"));
|
|
68318
|
-
if (found) {
|
|
68319
|
-
return found;
|
|
68320
|
-
}
|
|
68321
|
-
const fallback = join9(getDataRoot(), "mementos.db");
|
|
68322
|
-
ensureDir2(dirname4(fallback));
|
|
68323
|
-
return fallback;
|
|
68324
|
-
}
|
|
68325
|
-
function ensureDir2(dir) {
|
|
68326
|
-
if (!existsSync6(dir)) {
|
|
68327
|
-
mkdirSync3(dir, { recursive: true });
|
|
68328
|
-
}
|
|
68329
|
-
}
|
|
68330
|
-
|
|
68331
69535
|
// src/lib/retention.ts
|
|
68332
69536
|
init_database();
|
|
68333
69537
|
init_memories();
|
|
69538
|
+
init_config();
|
|
68334
69539
|
function enforceQuotas(config, db) {
|
|
68335
69540
|
const d = db || getDatabase();
|
|
68336
69541
|
let totalEvicted = 0;
|
|
@@ -68438,11 +69643,108 @@ function registerCleanCommand(program2) {
|
|
|
68438
69643
|
|
|
68439
69644
|
// src/cli/commands/io-backup.ts
|
|
68440
69645
|
init_database();
|
|
69646
|
+
init_api_mode();
|
|
69647
|
+
init_memories();
|
|
68441
69648
|
init_paths();
|
|
68442
69649
|
init_helpers();
|
|
68443
69650
|
import chalk20 from "chalk";
|
|
68444
69651
|
import { join as join10, resolve as resolve15, dirname as dirname5 } from "path";
|
|
68445
|
-
import { existsSync as existsSync7, statSync, copyFileSync, mkdirSync as mkdirSync4, readdirSync as readdirSync2 } from "fs";
|
|
69652
|
+
import { existsSync as existsSync7, statSync, copyFileSync, mkdirSync as mkdirSync4, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
69653
|
+
function writeMemoriesBackupFile(dest, rows) {
|
|
69654
|
+
const { Database: Database3 } = __require("bun:sqlite");
|
|
69655
|
+
if (existsSync7(dest)) {
|
|
69656
|
+
unlinkSync3(dest);
|
|
69657
|
+
}
|
|
69658
|
+
const backupDb = new Database3(dest);
|
|
69659
|
+
try {
|
|
69660
|
+
backupDb.run(`
|
|
69661
|
+
CREATE TABLE IF NOT EXISTS _backup_carrier (
|
|
69662
|
+
format INTEGER NOT NULL
|
|
69663
|
+
)
|
|
69664
|
+
`);
|
|
69665
|
+
backupDb.run(`INSERT INTO _backup_carrier (format) VALUES (1)`);
|
|
69666
|
+
backupDb.run(`
|
|
69667
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
69668
|
+
id TEXT PRIMARY KEY,
|
|
69669
|
+
key TEXT NOT NULL,
|
|
69670
|
+
value TEXT NOT NULL,
|
|
69671
|
+
category TEXT,
|
|
69672
|
+
scope TEXT,
|
|
69673
|
+
summary TEXT,
|
|
69674
|
+
tags TEXT,
|
|
69675
|
+
importance INTEGER,
|
|
69676
|
+
source TEXT,
|
|
69677
|
+
status TEXT,
|
|
69678
|
+
pinned INTEGER,
|
|
69679
|
+
agent_id TEXT,
|
|
69680
|
+
project_id TEXT,
|
|
69681
|
+
session_id TEXT,
|
|
69682
|
+
machine_id TEXT,
|
|
69683
|
+
namespace TEXT,
|
|
69684
|
+
created_by_agent TEXT,
|
|
69685
|
+
when_to_use TEXT,
|
|
69686
|
+
sequence_group TEXT,
|
|
69687
|
+
sequence_order INTEGER,
|
|
69688
|
+
metadata TEXT,
|
|
69689
|
+
access_count INTEGER,
|
|
69690
|
+
version INTEGER,
|
|
69691
|
+
expires_at TEXT,
|
|
69692
|
+
valid_from TEXT,
|
|
69693
|
+
valid_until TEXT,
|
|
69694
|
+
ingested_at TEXT,
|
|
69695
|
+
created_at TEXT,
|
|
69696
|
+
updated_at TEXT,
|
|
69697
|
+
accessed_at TEXT
|
|
69698
|
+
)
|
|
69699
|
+
`);
|
|
69700
|
+
const insert = backupDb.prepare(`INSERT INTO memories (id, key, value, category, scope, summary, tags, importance, source, status, pinned, agent_id, project_id, session_id, machine_id, namespace, created_by_agent, when_to_use, sequence_group, sequence_order, metadata, access_count, version, expires_at, valid_from, valid_until, ingested_at, created_at, updated_at, accessed_at)
|
|
69701
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
69702
|
+
const bind = (v) => {
|
|
69703
|
+
if (v === undefined || v === null)
|
|
69704
|
+
return null;
|
|
69705
|
+
return typeof v === "string" || typeof v === "number" ? v : JSON.stringify(v);
|
|
69706
|
+
};
|
|
69707
|
+
const boolBind = (v) => v === undefined || v === null ? null : v ? 1 : 0;
|
|
69708
|
+
for (const row of rows) {
|
|
69709
|
+
const params = [
|
|
69710
|
+
bind(row["id"]),
|
|
69711
|
+
bind(row["key"]),
|
|
69712
|
+
bind(row["value"]),
|
|
69713
|
+
bind(row["category"]),
|
|
69714
|
+
bind(row["scope"]),
|
|
69715
|
+
bind(row["summary"]),
|
|
69716
|
+
Array.isArray(row["tags"]) ? JSON.stringify(row["tags"]) : bind(row["tags"]),
|
|
69717
|
+
bind(row["importance"]),
|
|
69718
|
+
bind(row["source"]),
|
|
69719
|
+
bind(row["status"]),
|
|
69720
|
+
boolBind(row["pinned"]),
|
|
69721
|
+
bind(row["agent_id"]),
|
|
69722
|
+
bind(row["project_id"]),
|
|
69723
|
+
bind(row["session_id"]),
|
|
69724
|
+
bind(row["machine_id"]),
|
|
69725
|
+
bind(row["namespace"]),
|
|
69726
|
+
bind(row["created_by_agent"]),
|
|
69727
|
+
bind(row["when_to_use"]),
|
|
69728
|
+
bind(row["sequence_group"]),
|
|
69729
|
+
bind(row["sequence_order"]),
|
|
69730
|
+
bind(row["metadata"]),
|
|
69731
|
+
bind(row["access_count"]),
|
|
69732
|
+
bind(row["version"]),
|
|
69733
|
+
bind(row["expires_at"]),
|
|
69734
|
+
bind(row["valid_from"]),
|
|
69735
|
+
bind(row["valid_until"]),
|
|
69736
|
+
bind(row["ingested_at"]),
|
|
69737
|
+
bind(row["created_at"]),
|
|
69738
|
+
bind(row["updated_at"]),
|
|
69739
|
+
bind(row["accessed_at"])
|
|
69740
|
+
];
|
|
69741
|
+
insert.run(...params);
|
|
69742
|
+
}
|
|
69743
|
+
return rows.length;
|
|
69744
|
+
} finally {
|
|
69745
|
+
backupDb.close();
|
|
69746
|
+
}
|
|
69747
|
+
}
|
|
68446
69748
|
function registerBackupCommand(program2) {
|
|
68447
69749
|
const handleError = makeHandleError(program2);
|
|
68448
69750
|
program2.command("backup [path]").description("Backup the SQLite database to a file").option("--list", "List available backups in the mementos backups dir").action((targetPath, opts) => {
|
|
@@ -68491,11 +69793,6 @@ function registerBackupCommand(program2) {
|
|
|
68491
69793
|
}
|
|
68492
69794
|
return;
|
|
68493
69795
|
}
|
|
68494
|
-
const dbPath = getDbPath();
|
|
68495
|
-
if (!existsSync7(dbPath)) {
|
|
68496
|
-
console.error(chalk20.red(`Database not found at ${dbPath}`));
|
|
68497
|
-
process.exit(1);
|
|
68498
|
-
}
|
|
68499
69796
|
let dest;
|
|
68500
69797
|
if (targetPath) {
|
|
68501
69798
|
dest = resolve15(targetPath);
|
|
@@ -68511,6 +69808,26 @@ function registerBackupCommand(program2) {
|
|
|
68511
69808
|
if (!existsSync7(destDir)) {
|
|
68512
69809
|
mkdirSync4(destDir, { recursive: true });
|
|
68513
69810
|
}
|
|
69811
|
+
if (isApiMode()) {
|
|
69812
|
+
const { rows } = listMemoriesBounded({}, undefined);
|
|
69813
|
+
const count = writeMemoriesBackupFile(dest, rows);
|
|
69814
|
+
const st2 = statSync(dest);
|
|
69815
|
+
const sizeMB2 = (st2.size / (1024 * 1024)).toFixed(1);
|
|
69816
|
+
const sizeStr2 = st2.size >= 1024 * 1024 ? `${sizeMB2} MB` : `${(st2.size / 1024).toFixed(1)} KB`;
|
|
69817
|
+
if (globalOpts.json) {
|
|
69818
|
+
outputJson({ backed_up_to: dest, size: st2.size, source: "cloud-api", memories: count });
|
|
69819
|
+
return;
|
|
69820
|
+
}
|
|
69821
|
+
console.log(`Backed up to: ${chalk20.green(dest)} (size: ${sizeStr2})`);
|
|
69822
|
+
console.log(` Source: hosted store (cloud-api)`);
|
|
69823
|
+
console.log(` Memories: ${chalk20.cyan(String(count))}`);
|
|
69824
|
+
return;
|
|
69825
|
+
}
|
|
69826
|
+
const dbPath = getDbPath();
|
|
69827
|
+
if (!existsSync7(dbPath)) {
|
|
69828
|
+
console.error(chalk20.red(`Database not found at ${dbPath}`));
|
|
69829
|
+
process.exit(1);
|
|
69830
|
+
}
|
|
68514
69831
|
copyFileSync(dbPath, dest);
|
|
68515
69832
|
const st = statSync(dest);
|
|
68516
69833
|
const sizeMB = (st.size / (1024 * 1024)).toFixed(1);
|
|
@@ -68545,6 +69862,28 @@ function readMemoriesFromBackup(source) {
|
|
|
68545
69862
|
backupDb.close();
|
|
68546
69863
|
}
|
|
68547
69864
|
}
|
|
69865
|
+
function inspectLocalRestoreSource(source) {
|
|
69866
|
+
const { Database: Database3 } = __require("bun:sqlite");
|
|
69867
|
+
let backupDb;
|
|
69868
|
+
try {
|
|
69869
|
+
backupDb = new Database3(source, { readonly: true });
|
|
69870
|
+
} catch (error) {
|
|
69871
|
+
throw new Error(`Backup file is not a readable SQLite database: ${source} ` + `(${error instanceof Error ? error.message : String(error)})`);
|
|
69872
|
+
}
|
|
69873
|
+
try {
|
|
69874
|
+
const tables = backupDb.query("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((t) => t.name);
|
|
69875
|
+
if (tables.includes("_backup_carrier")) {
|
|
69876
|
+
throw new Error(`${source} is a memories-only cloud backup (created by \`mementos backup\` in hosted mode), ` + `not a full local database backup. Restoring it over the on-box database would drop every ` + `non-memories table and its migration history, and the next migration run would collide with ` + `the half-shaped schema. Restore it into the hosted store instead (run \`mementos restore\` with ` + `a hosted credential), or re-import its memories with \`mementos import\`.`);
|
|
69877
|
+
}
|
|
69878
|
+
if (!tables.includes("memories")) {
|
|
69879
|
+
throw new Error(`Backup file has no mementos \`memories\` table: ${source} \u2014 it is not a mementos backup.`);
|
|
69880
|
+
}
|
|
69881
|
+
const row = backupDb.query("SELECT COUNT(*) as count FROM memories").get();
|
|
69882
|
+
return { memories: row?.count ?? 0 };
|
|
69883
|
+
} finally {
|
|
69884
|
+
backupDb.close();
|
|
69885
|
+
}
|
|
69886
|
+
}
|
|
68548
69887
|
function registerRestoreCommand(program2) {
|
|
68549
69888
|
const handleError = makeHandleError(program2);
|
|
68550
69889
|
program2.command("restore [file]").description("Restore the database from a backup file").option("--latest", "Restore the most recent backup from the mementos backups dir").option("--force", "Skip confirmation and perform the restore").action((filePath, opts) => {
|
|
@@ -68655,14 +69994,7 @@ function registerRestoreCommand(program2) {
|
|
|
68655
69994
|
currentCount = row?.count ?? 0;
|
|
68656
69995
|
} catch {}
|
|
68657
69996
|
}
|
|
68658
|
-
|
|
68659
|
-
try {
|
|
68660
|
-
const { Database: Database3 } = __require("bun:sqlite");
|
|
68661
|
-
const backupDb = new Database3(source, { readonly: true });
|
|
68662
|
-
const row = backupDb.query("SELECT COUNT(*) as count FROM memories").get();
|
|
68663
|
-
backupCount = row?.count ?? 0;
|
|
68664
|
-
backupDb.close();
|
|
68665
|
-
} catch {}
|
|
69997
|
+
const backupCount = inspectLocalRestoreSource(source).memories;
|
|
68666
69998
|
if (!opts.force) {
|
|
68667
69999
|
if (globalOpts.json) {
|
|
68668
70000
|
outputJson({
|
|
@@ -69103,14 +70435,17 @@ function registerProjectCommands(program2) {
|
|
|
69103
70435
|
const categoriesRaw = opts.categories || "preference,fact,knowledge";
|
|
69104
70436
|
const categories = categoriesRaw.split(",").map((c) => c.trim());
|
|
69105
70437
|
const agentId = opts.agent || globalOpts.agent;
|
|
69106
|
-
const projectPath = opts.project
|
|
70438
|
+
const projectPath = opts.project ?? globalOpts.project;
|
|
69107
70439
|
const sessionId = opts.session || globalOpts.session;
|
|
69108
70440
|
const visibleMachineId = resolveVisibleMachineId(opts.machine);
|
|
69109
70441
|
let projectId;
|
|
69110
|
-
if (projectPath) {
|
|
70442
|
+
if (projectPath !== undefined) {
|
|
70443
|
+
if (!projectPath.trim())
|
|
70444
|
+
throw new Error("Project not found: empty project path");
|
|
69111
70445
|
const project = getProject(resolve17(projectPath));
|
|
69112
|
-
if (project)
|
|
69113
|
-
|
|
70446
|
+
if (!project)
|
|
70447
|
+
throw new Error(`Project not found: ${projectPath}`);
|
|
70448
|
+
projectId = project.id;
|
|
69114
70449
|
}
|
|
69115
70450
|
const allMemories = [];
|
|
69116
70451
|
const globalMems = listMemories({
|
|
@@ -69142,6 +70477,7 @@ function registerProjectCommands(program2) {
|
|
|
69142
70477
|
min_importance: minImportance,
|
|
69143
70478
|
status: "active",
|
|
69144
70479
|
agent_id: agentId,
|
|
70480
|
+
project_id: projectId,
|
|
69145
70481
|
session_id: sessionId,
|
|
69146
70482
|
...visibleToMachineFilter(visibleMachineId),
|
|
69147
70483
|
limit: 50
|
|
@@ -69901,6 +71237,8 @@ init_database();
|
|
|
69901
71237
|
init_memories();
|
|
69902
71238
|
init_agents();
|
|
69903
71239
|
init_projects();
|
|
71240
|
+
init_config();
|
|
71241
|
+
init_api_mode();
|
|
69904
71242
|
import chalk27 from "chalk";
|
|
69905
71243
|
import { join as join12 } from "path";
|
|
69906
71244
|
import { homedir as homedir5 } from "os";
|
|
@@ -69911,7 +71249,6 @@ import {
|
|
|
69911
71249
|
statSync as statSync3,
|
|
69912
71250
|
constants as fsConstants
|
|
69913
71251
|
} from "fs";
|
|
69914
|
-
init_api_mode();
|
|
69915
71252
|
init_helpers();
|
|
69916
71253
|
async function runCommandWithTimeout(args, timeoutMs) {
|
|
69917
71254
|
const proc = Bun.spawn(args, { stdout: "pipe", stderr: "pipe" });
|
|
@@ -70340,8 +71677,9 @@ function registerStatusCommand(program2) {
|
|
|
70340
71677
|
}
|
|
70341
71678
|
|
|
70342
71679
|
// src/cli/commands/system-config.ts
|
|
70343
|
-
|
|
71680
|
+
init_config();
|
|
70344
71681
|
init_helpers();
|
|
71682
|
+
import chalk28 from "chalk";
|
|
70345
71683
|
function registerConfigCommand(program2) {
|
|
70346
71684
|
program2.command("config [subcommand] [args...]").description("View or modify configuration. Subcommands: get <key>, set <key> <value>, reset [key], path").action((subcommand, args) => {
|
|
70347
71685
|
try {
|
|
@@ -70427,9 +71765,9 @@ function registerConfigCommand(program2) {
|
|
|
70427
71765
|
}
|
|
70428
71766
|
} else {
|
|
70429
71767
|
const configPath = getConfigPath2();
|
|
70430
|
-
const { unlinkSync:
|
|
71768
|
+
const { unlinkSync: unlinkSync4, existsSync: _existsSync } = __require("fs");
|
|
70431
71769
|
if (_existsSync(configPath)) {
|
|
70432
|
-
|
|
71770
|
+
unlinkSync4(configPath);
|
|
70433
71771
|
}
|
|
70434
71772
|
if (useJson) {
|
|
70435
71773
|
outputJson({ reset: true, all: true });
|
|
@@ -70449,10 +71787,11 @@ function registerConfigCommand(program2) {
|
|
|
70449
71787
|
}
|
|
70450
71788
|
|
|
70451
71789
|
// src/cli/commands/system-profile.ts
|
|
70452
|
-
|
|
70453
|
-
import { join as join13 } from "path";
|
|
71790
|
+
init_config();
|
|
70454
71791
|
init_paths();
|
|
70455
71792
|
init_helpers();
|
|
71793
|
+
import chalk29 from "chalk";
|
|
71794
|
+
import { join as join13 } from "path";
|
|
70456
71795
|
function registerProfileCommand(program2) {
|
|
70457
71796
|
const profileCmd = program2.command("profile").description("Manage named profile files and active-profile metadata");
|
|
70458
71797
|
profileCmd.command("list").description("List all available profiles").action(() => {
|
|
@@ -70860,10 +72199,12 @@ function registerSessionCommand(program2) {
|
|
|
70860
72199
|
const sessionCmd = program2.command("session").description("Session auto-memory \u2014 ingest session transcripts for memory extraction");
|
|
70861
72200
|
sessionCmd.command("ingest <transcriptFile>").description("Ingest a session transcript file for memory extraction").option("--session-id <id>", "Session ID (default: auto-generated)").option("--agent <id>", "Agent ID").option("--project <id>", "Project ID").option("--source <source>", "Source (claude-code, codex, manual, open-sessions)", "manual").action(async (transcriptFile, opts) => {
|
|
70862
72201
|
const { readFileSync: _rfs } = await import("fs");
|
|
70863
|
-
const { createSessionJob: createSessionJob2 } = await Promise.resolve().then(() => (init_session_jobs(), exports_session_jobs));
|
|
70864
|
-
const { enqueueSessionJob: enqueueSessionJob2 } = await Promise.resolve().then(() => (init_session_queue(), exports_session_queue));
|
|
70865
72202
|
const transcript = _rfs(transcriptFile, "utf-8");
|
|
70866
72203
|
const sessionId = opts.sessionId ?? `cli-${Date.now()}`;
|
|
72204
|
+
const { isApiMode: isApiMode2 } = await Promise.resolve().then(() => (init_api_mode(), exports_api_mode));
|
|
72205
|
+
const hosted = isApiMode2();
|
|
72206
|
+
const { createSessionJob: createSessionJob2 } = await Promise.resolve().then(() => (init_session_jobs(), exports_session_jobs));
|
|
72207
|
+
const { enqueueSessionJob: enqueueSessionJob2 } = await Promise.resolve().then(() => (init_session_queue(), exports_session_queue));
|
|
70867
72208
|
const job = createSessionJob2({
|
|
70868
72209
|
session_id: sessionId,
|
|
70869
72210
|
transcript,
|
|
@@ -70872,7 +72213,7 @@ function registerSessionCommand(program2) {
|
|
|
70872
72213
|
project_id: opts.project
|
|
70873
72214
|
});
|
|
70874
72215
|
enqueueSessionJob2(job.id);
|
|
70875
|
-
console.log(chalk33.green(`\u2713 Session queued: ${chalk33.cyan(job.id)}`));
|
|
72216
|
+
console.log(chalk33.green(`\u2713 Session queued${hosted ? " (hosted store)" : ""}: ${chalk33.cyan(job.id)}`));
|
|
70876
72217
|
console.log(` Session: ${sessionId}`);
|
|
70877
72218
|
console.log(` Length: ${transcript.length} chars`);
|
|
70878
72219
|
});
|
|
@@ -70891,14 +72232,16 @@ function registerSessionCommand(program2) {
|
|
|
70891
72232
|
if (job.error)
|
|
70892
72233
|
console.log(chalk33.red(` Error: ${job.error}`));
|
|
70893
72234
|
});
|
|
70894
|
-
sessionCmd.command("list").description("List session extraction jobs").option("--agent <id>", "Filter by agent").option("--project <id>", "Filter by project").option("--status <status>", "Filter by status").option("--limit <n>", "Max results", "20").action(async (opts) => {
|
|
72235
|
+
sessionCmd.command("list").description("List session extraction jobs").option("--agent <id>", "Filter by agent").option("--project <id>", "Filter by project").option("--status <status>", "Filter by status").option("--session-id <id>", "Filter by session ID").option("--limit <n>", "Max results", "20").option("--offset <n>", "Result offset", "0").action(async (opts) => {
|
|
70895
72236
|
const globalOpts = program2.opts();
|
|
70896
72237
|
const { listSessionJobs: listSessionJobs2 } = await Promise.resolve().then(() => (init_session_jobs(), exports_session_jobs));
|
|
70897
72238
|
const jobs = listSessionJobs2({
|
|
70898
72239
|
agent_id: opts.agent,
|
|
70899
72240
|
project_id: opts.project,
|
|
70900
72241
|
status: opts.status,
|
|
70901
|
-
|
|
72242
|
+
session_id: opts.sessionId,
|
|
72243
|
+
limit: parseInt(opts.limit, 10),
|
|
72244
|
+
offset: parseInt(opts.offset, 10)
|
|
70902
72245
|
});
|
|
70903
72246
|
if (globalOpts.json) {
|
|
70904
72247
|
outputJson(jobs);
|
|
@@ -71044,7 +72387,7 @@ import chalk35 from "chalk";
|
|
|
71044
72387
|
import { resolve as resolve19 } from "path";
|
|
71045
72388
|
function registerSynthesizedProfileCommand(program2) {
|
|
71046
72389
|
const handleError = makeHandleError(program2);
|
|
71047
|
-
program2.command("synthesized-profile").description("Show or refresh the synthesized agent/project profile").option("--project-id <id>", "Project ID").option("--refresh", "Force refresh the profile (re-synthesize from memories)").action(async (opts) => {
|
|
72390
|
+
program2.command("synthesized-profile").description("Show or refresh the synthesized agent/project profile").option("--project-id <id>", "Project ID").option("--scope <scope>", "Profile scope: agent, project, or global").option("--refresh", "Force refresh the profile (re-synthesize from memories)").action(async (opts) => {
|
|
71048
72391
|
try {
|
|
71049
72392
|
const globalOpts = program2.opts();
|
|
71050
72393
|
const { synthesizeProfile: synthesizeProfile2 } = await Promise.resolve().then(() => (init_profile_synthesizer(), exports_profile_synthesizer));
|
|
@@ -71058,6 +72401,7 @@ function registerSynthesizedProfileCommand(program2) {
|
|
|
71058
72401
|
const result = await synthesizeProfile2({
|
|
71059
72402
|
project_id: projectId,
|
|
71060
72403
|
agent_id: globalOpts.agent,
|
|
72404
|
+
scope: opts.scope,
|
|
71061
72405
|
force_refresh: !!opts.refresh
|
|
71062
72406
|
});
|
|
71063
72407
|
if (!result) {
|
|
@@ -71575,6 +72919,15 @@ import chalk40 from "chalk";
|
|
|
71575
72919
|
// src/lib/storage-sync.ts
|
|
71576
72920
|
init_storage();
|
|
71577
72921
|
init_database();
|
|
72922
|
+
init_config();
|
|
72923
|
+
init_local_opt_in();
|
|
72924
|
+
var STORAGE_SYNC_LOCAL_ONLY_MESSAGE = "REMOTE_COMMAND_UNSUPPORTED: mementos storage push/pull/sync/status operate on the on-box SQLite store, " + "which is opt-in only (" + `${MEMENTOS_LOCAL_OPT_IN_ENV_KEYS[0]}=1 or an explicit ${MEMENTOS_DB_PATH_ENV_KEYS[0]}` + ") and disabled by default. On the hosted route there is no local store to sync \u2014 refusing rather than " + "creating one. Run these verbs where the on-box store deliberately lives (the local opt-in) or in mementos-serve.";
|
|
72925
|
+
function openLocalSyncAdapter() {
|
|
72926
|
+
if (!isServerContext() && !selectsMementosLocalStore(process.env)) {
|
|
72927
|
+
throw new Error(STORAGE_SYNC_LOCAL_ONLY_MESSAGE);
|
|
72928
|
+
}
|
|
72929
|
+
return new SqliteAdapter(getDbPath2());
|
|
72930
|
+
}
|
|
71578
72931
|
var MEMORY_TABLE = "memories";
|
|
71579
72932
|
var MEMORY_SYNC_META_TABLE = "_mementos_storage_sync_meta";
|
|
71580
72933
|
var SOURCE_MACHINE_METADATA_KEY = "last_synced_source_machine";
|
|
@@ -71953,7 +73306,7 @@ function resolveTables(local, tables) {
|
|
|
71953
73306
|
function withManagedAdapters(options, fn) {
|
|
71954
73307
|
const localOwned = !options.local;
|
|
71955
73308
|
const remoteOwned = !options.remote;
|
|
71956
|
-
const local = options.local ??
|
|
73309
|
+
const local = options.local ?? openLocalSyncAdapter();
|
|
71957
73310
|
const remote = options.remote ?? new PgAdapter(getStorageConnectionString("mementos"));
|
|
71958
73311
|
try {
|
|
71959
73312
|
const currentMachineId = resolveCurrentMachineId(local, options.current_machine_id);
|
|
@@ -72009,7 +73362,7 @@ function pullStorageChanges(options = {}) {
|
|
|
72009
73362
|
function getStorageSyncStatus(options = {}) {
|
|
72010
73363
|
const backend = getStorageBackend();
|
|
72011
73364
|
const localOwned = !options.local;
|
|
72012
|
-
const local = options.local ??
|
|
73365
|
+
const local = options.local ?? openLocalSyncAdapter();
|
|
72013
73366
|
try {
|
|
72014
73367
|
const currentMachineId = resolveCurrentMachineId(local, options.current_machine_id);
|
|
72015
73368
|
return {
|