@hasna/mementos 0.15.2 → 0.17.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 +83 -22
- package/dist/audit-contract.d.ts +87 -0
- package/dist/audit-contract.d.ts.map +1 -0
- package/dist/cli/commands/agent.d.ts.map +1 -1
- package/dist/cli/commands/consolidation.d.ts.map +1 -1
- 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-export.d.ts.map +1 -1
- package/dist/cli/commands/io-restore.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-crud.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/memory-cmd-recall.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-remove.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-search.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-view.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-when-to-use.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-status.d.ts.map +1 -1
- package/dist/cli/commands/system-synthesized-profile.d.ts.map +1 -1
- package/dist/cli/helpers.d.ts +3 -1
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/index.js +2482 -823
- package/dist/cli/structured-json.d.ts +54 -0
- package/dist/cli/structured-json.d.ts.map +1 -0
- 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/conversations-transport.d.ts +21 -0
- package/dist/lib/conversations-transport.d.ts.map +1 -0
- package/dist/lib/export-v1.d.ts +2 -0
- package/dist/lib/export-v1.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 +3857 -1600
- package/dist/mcp/memory-broadcast.d.ts +6 -8
- package/dist/mcp/memory-broadcast.d.ts.map +1 -1
- package/dist/mcp/profile.d.ts +23 -0
- package/dist/mcp/profile.d.ts.map +1 -0
- package/dist/mcp/tools/bounded-output.d.ts +27 -0
- package/dist/mcp/tools/bounded-output.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/memory-io.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 +110 -7
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +746 -28
- 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);
|
|
@@ -7871,6 +8218,7 @@ __export(exports_helpers, {
|
|
|
7871
8218
|
positiveIntOrDefault: () => positiveIntOrDefault,
|
|
7872
8219
|
parseConfigValue: () => parseConfigValue,
|
|
7873
8220
|
outputYaml: () => outputYaml,
|
|
8221
|
+
outputJsonAndExit: () => outputJsonAndExit,
|
|
7874
8222
|
outputJson: () => outputJson,
|
|
7875
8223
|
makeHandleError: () => makeHandleError,
|
|
7876
8224
|
importanceColor: () => importanceColor,
|
|
@@ -7951,7 +8299,30 @@ function resolveEntityArg(nameOrId, type) {
|
|
|
7951
8299
|
process.exit(1);
|
|
7952
8300
|
}
|
|
7953
8301
|
function outputJson(data) {
|
|
7954
|
-
|
|
8302
|
+
process.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
8303
|
+
`);
|
|
8304
|
+
}
|
|
8305
|
+
function outputJsonAndExit(data, code) {
|
|
8306
|
+
const payload = `${JSON.stringify(data, null, 2)}
|
|
8307
|
+
`;
|
|
8308
|
+
return new Promise((resolve4) => {
|
|
8309
|
+
let settled = false;
|
|
8310
|
+
const finish = (exitCode) => {
|
|
8311
|
+
if (settled)
|
|
8312
|
+
return;
|
|
8313
|
+
settled = true;
|
|
8314
|
+
process.stdout.off("error", onError);
|
|
8315
|
+
process.exit(exitCode);
|
|
8316
|
+
resolve4(undefined);
|
|
8317
|
+
};
|
|
8318
|
+
const onError = () => finish(1);
|
|
8319
|
+
process.stdout.once("error", onError);
|
|
8320
|
+
try {
|
|
8321
|
+
process.stdout.write(payload, () => finish(code));
|
|
8322
|
+
} catch {
|
|
8323
|
+
finish(1);
|
|
8324
|
+
}
|
|
8325
|
+
});
|
|
7955
8326
|
}
|
|
7956
8327
|
function positiveIntOrDefault(value, fallback) {
|
|
7957
8328
|
const parsed = typeof value === "number" ? value : Number(value);
|
|
@@ -8146,9 +8517,9 @@ function makeHandleError(program2) {
|
|
|
8146
8517
|
return function handleError(e) {
|
|
8147
8518
|
const globalOpts = program2.opts();
|
|
8148
8519
|
if (globalOpts.json || globalOpts.format === "json") {
|
|
8149
|
-
|
|
8520
|
+
return outputJsonAndExit({
|
|
8150
8521
|
error: e instanceof Error ? e.message : String(e)
|
|
8151
|
-
});
|
|
8522
|
+
}, 1);
|
|
8152
8523
|
} else {
|
|
8153
8524
|
console.error(chalk.red(e instanceof Error ? e.message : String(e)));
|
|
8154
8525
|
}
|
|
@@ -9293,6 +9664,245 @@ var init_search = __esm(() => {
|
|
|
9293
9664
|
]);
|
|
9294
9665
|
});
|
|
9295
9666
|
|
|
9667
|
+
// src/lib/config.ts
|
|
9668
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, cpSync as cpSync2 } from "fs";
|
|
9669
|
+
import { homedir as homedir4 } from "os";
|
|
9670
|
+
import { basename, dirname as dirname4, join as join9, resolve as resolve14 } from "path";
|
|
9671
|
+
function isInMemoryDb2(path) {
|
|
9672
|
+
return path === ":memory:" || path.startsWith("file::memory:");
|
|
9673
|
+
}
|
|
9674
|
+
function homeDir() {
|
|
9675
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir4();
|
|
9676
|
+
}
|
|
9677
|
+
function deepMerge(target, source) {
|
|
9678
|
+
const result = { ...target };
|
|
9679
|
+
for (const key of Object.keys(source)) {
|
|
9680
|
+
const sourceVal = source[key];
|
|
9681
|
+
const targetVal = result[key];
|
|
9682
|
+
if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) {
|
|
9683
|
+
result[key] = deepMerge(targetVal, sourceVal);
|
|
9684
|
+
} else {
|
|
9685
|
+
result[key] = sourceVal;
|
|
9686
|
+
}
|
|
9687
|
+
}
|
|
9688
|
+
return result;
|
|
9689
|
+
}
|
|
9690
|
+
function isValidScope(value) {
|
|
9691
|
+
return VALID_SCOPES2.includes(value);
|
|
9692
|
+
}
|
|
9693
|
+
function isValidCategory(value) {
|
|
9694
|
+
return VALID_CATEGORIES2.includes(value);
|
|
9695
|
+
}
|
|
9696
|
+
function loadConfig() {
|
|
9697
|
+
const configPath = join9(getDataRoot(), "config.json");
|
|
9698
|
+
let fileConfig = {};
|
|
9699
|
+
if (existsSync6(configPath)) {
|
|
9700
|
+
try {
|
|
9701
|
+
const raw = readFileSync6(configPath, "utf-8");
|
|
9702
|
+
fileConfig = JSON.parse(raw);
|
|
9703
|
+
} catch {}
|
|
9704
|
+
}
|
|
9705
|
+
const merged = deepMerge(DEFAULT_CONFIG, fileConfig);
|
|
9706
|
+
const envScope = env.defaultScope();
|
|
9707
|
+
if (envScope && isValidScope(envScope)) {
|
|
9708
|
+
merged.default_scope = envScope;
|
|
9709
|
+
}
|
|
9710
|
+
const envCategory = env.defaultCategory();
|
|
9711
|
+
if (envCategory && isValidCategory(envCategory)) {
|
|
9712
|
+
merged.default_category = envCategory;
|
|
9713
|
+
}
|
|
9714
|
+
const envImportance = env.defaultImportance();
|
|
9715
|
+
if (envImportance) {
|
|
9716
|
+
const parsed = parseInt(envImportance, 10);
|
|
9717
|
+
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 10) {
|
|
9718
|
+
merged.default_importance = parsed;
|
|
9719
|
+
}
|
|
9720
|
+
}
|
|
9721
|
+
return merged;
|
|
9722
|
+
}
|
|
9723
|
+
function findFileWalkingUp(filename) {
|
|
9724
|
+
let dir = process.cwd();
|
|
9725
|
+
const legacyHomeMementosDb = resolve14(homeDir(), ".mementos", "mementos.db");
|
|
9726
|
+
while (true) {
|
|
9727
|
+
const candidate = join9(dir, filename);
|
|
9728
|
+
if (existsSync6(candidate) && resolve14(candidate) !== legacyHomeMementosDb) {
|
|
9729
|
+
return candidate;
|
|
9730
|
+
}
|
|
9731
|
+
const parent = dirname4(dir);
|
|
9732
|
+
if (parent === dir) {
|
|
9733
|
+
return null;
|
|
9734
|
+
}
|
|
9735
|
+
dir = parent;
|
|
9736
|
+
}
|
|
9737
|
+
}
|
|
9738
|
+
function findGitRoot2() {
|
|
9739
|
+
let dir = process.cwd();
|
|
9740
|
+
while (true) {
|
|
9741
|
+
if (existsSync6(join9(dir, ".git"))) {
|
|
9742
|
+
return dir;
|
|
9743
|
+
}
|
|
9744
|
+
const parent = dirname4(dir);
|
|
9745
|
+
if (parent === dir) {
|
|
9746
|
+
return null;
|
|
9747
|
+
}
|
|
9748
|
+
dir = parent;
|
|
9749
|
+
}
|
|
9750
|
+
}
|
|
9751
|
+
function profilesDir() {
|
|
9752
|
+
return join9(getDataRoot(), "profiles");
|
|
9753
|
+
}
|
|
9754
|
+
function globalConfigPath() {
|
|
9755
|
+
return join9(getDataRoot(), "config.json");
|
|
9756
|
+
}
|
|
9757
|
+
function readGlobalConfig() {
|
|
9758
|
+
const p = globalConfigPath();
|
|
9759
|
+
if (!existsSync6(p))
|
|
9760
|
+
return {};
|
|
9761
|
+
try {
|
|
9762
|
+
return JSON.parse(readFileSync6(p, "utf-8"));
|
|
9763
|
+
} catch {
|
|
9764
|
+
return {};
|
|
9765
|
+
}
|
|
9766
|
+
}
|
|
9767
|
+
function readGlobalConfigForWrite() {
|
|
9768
|
+
const p = globalConfigPath();
|
|
9769
|
+
if (!existsSync6(p))
|
|
9770
|
+
return {};
|
|
9771
|
+
try {
|
|
9772
|
+
const data = JSON.parse(readFileSync6(p, "utf-8"));
|
|
9773
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
9774
|
+
throw new Error("expected a JSON object");
|
|
9775
|
+
}
|
|
9776
|
+
return data;
|
|
9777
|
+
} catch (error) {
|
|
9778
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
9779
|
+
throw new Error(`Cannot update global config at ${p}: ${detail}`);
|
|
9780
|
+
}
|
|
9781
|
+
}
|
|
9782
|
+
function writeGlobalConfig(data) {
|
|
9783
|
+
const p = globalConfigPath();
|
|
9784
|
+
ensureDir2(dirname4(p));
|
|
9785
|
+
writeFileSync3(p, JSON.stringify(data, null, 2), "utf-8");
|
|
9786
|
+
}
|
|
9787
|
+
function getActiveProfile() {
|
|
9788
|
+
const envProfile = process.env["MEMENTOS_PROFILE"];
|
|
9789
|
+
if (envProfile)
|
|
9790
|
+
return envProfile.trim();
|
|
9791
|
+
const cfg = readGlobalConfig();
|
|
9792
|
+
return cfg["active_profile"] || null;
|
|
9793
|
+
}
|
|
9794
|
+
function setActiveProfile(name) {
|
|
9795
|
+
const cfg = readGlobalConfigForWrite();
|
|
9796
|
+
if (name === null) {
|
|
9797
|
+
delete cfg["active_profile"];
|
|
9798
|
+
} else {
|
|
9799
|
+
cfg["active_profile"] = name;
|
|
9800
|
+
}
|
|
9801
|
+
writeGlobalConfig(cfg);
|
|
9802
|
+
}
|
|
9803
|
+
function listProfiles() {
|
|
9804
|
+
const dir = profilesDir();
|
|
9805
|
+
if (!existsSync6(dir))
|
|
9806
|
+
return [];
|
|
9807
|
+
return readdirSync(dir).filter((f) => f.endsWith(".db")).map((f) => basename(f, ".db")).sort();
|
|
9808
|
+
}
|
|
9809
|
+
function deleteProfile(name) {
|
|
9810
|
+
const dbPath = join9(profilesDir(), `${name}.db`);
|
|
9811
|
+
if (!existsSync6(dbPath))
|
|
9812
|
+
return false;
|
|
9813
|
+
unlinkSync2(dbPath);
|
|
9814
|
+
if (getActiveProfile() === name)
|
|
9815
|
+
setActiveProfile(null);
|
|
9816
|
+
return true;
|
|
9817
|
+
}
|
|
9818
|
+
function getDbPath2() {
|
|
9819
|
+
const _home = homeDir();
|
|
9820
|
+
const _newDir = getDataRoot();
|
|
9821
|
+
const _oldDir = join9(_home, ".mementos");
|
|
9822
|
+
if (selectsMementosLocalStore() && !existsSync6(_newDir) && existsSync6(_oldDir)) {
|
|
9823
|
+
mkdirSync3(join9(_home, ".hasna"), { recursive: true });
|
|
9824
|
+
cpSync2(_oldDir, _newDir, { recursive: true });
|
|
9825
|
+
}
|
|
9826
|
+
const envDbPath = process.env["HASNA_MEMENTOS_DB_PATH"] ?? process.env["MEMENTOS_DB_PATH"];
|
|
9827
|
+
if (envDbPath) {
|
|
9828
|
+
if (isInMemoryDb2(envDbPath)) {
|
|
9829
|
+
return envDbPath;
|
|
9830
|
+
}
|
|
9831
|
+
const resolved = resolve14(envDbPath);
|
|
9832
|
+
ensureDir2(dirname4(resolved));
|
|
9833
|
+
return resolved;
|
|
9834
|
+
}
|
|
9835
|
+
const profile = getActiveProfile();
|
|
9836
|
+
if (profile) {
|
|
9837
|
+
const profilePath = join9(profilesDir(), `${profile}.db`);
|
|
9838
|
+
ensureDir2(dirname4(profilePath));
|
|
9839
|
+
return profilePath;
|
|
9840
|
+
}
|
|
9841
|
+
const dbScope = process.env["MEMENTOS_DB_SCOPE"];
|
|
9842
|
+
if (dbScope === "project") {
|
|
9843
|
+
const gitRoot = findGitRoot2();
|
|
9844
|
+
if (gitRoot) {
|
|
9845
|
+
const dbPath = join9(gitRoot, ".mementos", "mementos.db");
|
|
9846
|
+
ensureDir2(dirname4(dbPath));
|
|
9847
|
+
return dbPath;
|
|
9848
|
+
}
|
|
9849
|
+
}
|
|
9850
|
+
const found = findFileWalkingUp(join9(".mementos", "mementos.db"));
|
|
9851
|
+
if (found) {
|
|
9852
|
+
return found;
|
|
9853
|
+
}
|
|
9854
|
+
const fallback = join9(getDataRoot(), "mementos.db");
|
|
9855
|
+
ensureDir2(dirname4(fallback));
|
|
9856
|
+
return fallback;
|
|
9857
|
+
}
|
|
9858
|
+
function ensureDir2(dir) {
|
|
9859
|
+
if (!existsSync6(dir)) {
|
|
9860
|
+
mkdirSync3(dir, { recursive: true });
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
var DEFAULT_CONFIG, VALID_SCOPES2, VALID_CATEGORIES2;
|
|
9864
|
+
var init_config = __esm(() => {
|
|
9865
|
+
init_paths();
|
|
9866
|
+
init_env();
|
|
9867
|
+
init_local_opt_in();
|
|
9868
|
+
DEFAULT_CONFIG = {
|
|
9869
|
+
default_scope: "private",
|
|
9870
|
+
default_category: "knowledge",
|
|
9871
|
+
default_importance: 5,
|
|
9872
|
+
max_entries: 1000,
|
|
9873
|
+
max_entries_per_scope: {
|
|
9874
|
+
global: 500,
|
|
9875
|
+
shared: 300,
|
|
9876
|
+
private: 200,
|
|
9877
|
+
working: 100
|
|
9878
|
+
},
|
|
9879
|
+
injection: {
|
|
9880
|
+
max_tokens: 500,
|
|
9881
|
+
min_importance: 5,
|
|
9882
|
+
categories: ["preference", "fact"],
|
|
9883
|
+
refresh_interval: 5
|
|
9884
|
+
},
|
|
9885
|
+
extraction: {
|
|
9886
|
+
enabled: true,
|
|
9887
|
+
min_confidence: 0.5
|
|
9888
|
+
},
|
|
9889
|
+
sync_agents: ["claude", "codex", "gemini"],
|
|
9890
|
+
auto_cleanup: {
|
|
9891
|
+
enabled: true,
|
|
9892
|
+
expired_check_interval: 3600,
|
|
9893
|
+
unused_archive_days: 7,
|
|
9894
|
+
stale_deprioritize_days: 14
|
|
9895
|
+
}
|
|
9896
|
+
};
|
|
9897
|
+
VALID_SCOPES2 = ["global", "shared", "private", "working"];
|
|
9898
|
+
VALID_CATEGORIES2 = [
|
|
9899
|
+
"preference",
|
|
9900
|
+
"fact",
|
|
9901
|
+
"knowledge",
|
|
9902
|
+
"history"
|
|
9903
|
+
];
|
|
9904
|
+
});
|
|
9905
|
+
|
|
9296
9906
|
// src/db/relations.ts
|
|
9297
9907
|
function parseRelationRow(row) {
|
|
9298
9908
|
return {
|
|
@@ -10958,17 +11568,80 @@ var exports_profile_synthesizer = {};
|
|
|
10958
11568
|
__export(exports_profile_synthesizer, {
|
|
10959
11569
|
synthesizeProfile: () => synthesizeProfile,
|
|
10960
11570
|
markProfileStale: () => markProfileStale,
|
|
10961
|
-
getProfileKey: () => getProfileKey
|
|
11571
|
+
getProfileKey: () => getProfileKey,
|
|
11572
|
+
ProfileSynthesisError: () => ProfileSynthesisError,
|
|
11573
|
+
ProfileScopeError: () => ProfileScopeError,
|
|
11574
|
+
PROFILE_SYNTHESIS_CONTRACT: () => PROFILE_SYNTHESIS_CONTRACT
|
|
10962
11575
|
});
|
|
11576
|
+
function resolveProfileTarget(options) {
|
|
11577
|
+
const scope = options.scope ?? (options.project_id ? "project" : options.agent_id ? "agent" : "global");
|
|
11578
|
+
if (scope === "project") {
|
|
11579
|
+
if (!options.project_id)
|
|
11580
|
+
throw new ProfileScopeError("project scope requires project_id");
|
|
11581
|
+
return { scope, id: options.project_id, memory_scope: "shared", project_id: options.project_id };
|
|
11582
|
+
}
|
|
11583
|
+
if (scope === "agent") {
|
|
11584
|
+
if (!options.agent_id)
|
|
11585
|
+
throw new ProfileScopeError("agent scope requires agent_id");
|
|
11586
|
+
return { scope, id: options.agent_id, memory_scope: "private", agent_id: options.agent_id };
|
|
11587
|
+
}
|
|
11588
|
+
return { scope: "global", id: "global", memory_scope: "global" };
|
|
11589
|
+
}
|
|
10963
11590
|
function getProfileKey(scope, id) {
|
|
10964
11591
|
return `_profile_${scope}_${id}`;
|
|
10965
11592
|
}
|
|
11593
|
+
function listProfileMemories(category, target) {
|
|
11594
|
+
if (target.scope !== "global") {
|
|
11595
|
+
return listMemories({
|
|
11596
|
+
category,
|
|
11597
|
+
scope: target.memory_scope,
|
|
11598
|
+
project_id: target.project_id,
|
|
11599
|
+
agent_id: target.agent_id,
|
|
11600
|
+
machine_id: null,
|
|
11601
|
+
status: "active",
|
|
11602
|
+
limit: 30
|
|
11603
|
+
});
|
|
11604
|
+
}
|
|
11605
|
+
return [
|
|
11606
|
+
...listMemories({ category, scope: "global", machine_id: null, status: "active", limit: 30 }),
|
|
11607
|
+
...listMemories({ category, scope: "shared", machine_id: null, status: "active", limit: 1000 })
|
|
11608
|
+
].filter((memory) => memory.agent_id === null && memory.project_id === null).slice(0, 30);
|
|
11609
|
+
}
|
|
10966
11610
|
async function synthesizeProfile(options) {
|
|
10967
|
-
|
|
10968
|
-
|
|
10969
|
-
|
|
11611
|
+
if (!isServerContext() && isApiMode()) {
|
|
11612
|
+
const operation = "POST /profile/synthesize";
|
|
11613
|
+
const { data } = apiJson("POST", "/profile/synthesize", {
|
|
11614
|
+
project_id: options.project_id,
|
|
11615
|
+
agent_id: options.agent_id,
|
|
11616
|
+
scope: options.scope,
|
|
11617
|
+
force_refresh: options.force_refresh === true
|
|
11618
|
+
});
|
|
11619
|
+
const response = expectObject(data, operation);
|
|
11620
|
+
if (response["contract"] !== PROFILE_SYNTHESIS_CONTRACT) {
|
|
11621
|
+
throw new MementosApiProtocolError(operation, `expected contract '${PROFILE_SYNTHESIS_CONTRACT}'`);
|
|
11622
|
+
}
|
|
11623
|
+
if (!("profile" in response)) {
|
|
11624
|
+
throw new MementosApiProtocolError(operation, "missing 'profile'");
|
|
11625
|
+
}
|
|
11626
|
+
if (response["profile"] === null) {
|
|
11627
|
+
if (response["reason"] !== "no_memories") {
|
|
11628
|
+
throw new MementosApiProtocolError(operation, "null profile requires reason 'no_memories'");
|
|
11629
|
+
}
|
|
11630
|
+
return null;
|
|
11631
|
+
}
|
|
11632
|
+
if (typeof response["profile"] !== "string" || response["profile"].length === 0) {
|
|
11633
|
+
throw new MementosApiProtocolError(operation, "expected 'profile' to be a non-empty string or null");
|
|
11634
|
+
}
|
|
11635
|
+
return {
|
|
11636
|
+
profile: response["profile"],
|
|
11637
|
+
memory_count: expectNonNegativeInteger(response, "memory_count", operation),
|
|
11638
|
+
from_cache: expectBoolean(response, "from_cache", operation)
|
|
11639
|
+
};
|
|
11640
|
+
}
|
|
11641
|
+
const target = resolveProfileTarget(options);
|
|
11642
|
+
const profileKey = getProfileKey(target.scope, target.id);
|
|
10970
11643
|
if (!options.force_refresh) {
|
|
10971
|
-
const cached = getMemoryByKey(profileKey, "shared",
|
|
11644
|
+
const cached = getMemoryByKey(profileKey, "shared", target.agent_id, target.project_id);
|
|
10972
11645
|
if (cached) {
|
|
10973
11646
|
const age = Date.now() - new Date(cached.updated_at).getTime();
|
|
10974
11647
|
const maxAge = 24 * 60 * 60 * 1000;
|
|
@@ -10978,20 +11651,8 @@ async function synthesizeProfile(options) {
|
|
|
10978
11651
|
}
|
|
10979
11652
|
}
|
|
10980
11653
|
}
|
|
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
|
-
});
|
|
11654
|
+
const prefMemories = listProfileMemories("preference", target);
|
|
11655
|
+
const factMemories = listProfileMemories("fact", target);
|
|
10995
11656
|
const allMemories = [...prefMemories, ...factMemories];
|
|
10996
11657
|
if (allMemories.length === 0)
|
|
10997
11658
|
return null;
|
|
@@ -11001,7 +11662,7 @@ async function synthesizeProfile(options) {
|
|
|
11001
11662
|
`);
|
|
11002
11663
|
const fallbackProfile = `## Profile
|
|
11003
11664
|
${lines}`;
|
|
11004
|
-
saveProfile(profileKey, fallbackProfile, allMemories.length,
|
|
11665
|
+
saveProfile(profileKey, fallbackProfile, allMemories.length, target);
|
|
11005
11666
|
return { profile: fallbackProfile, memory_count: allMemories.length, from_cache: false };
|
|
11006
11667
|
}
|
|
11007
11668
|
try {
|
|
@@ -11023,41 +11684,63 @@ ${lines}`;
|
|
|
11023
11684
|
${memoryList}` }]
|
|
11024
11685
|
})
|
|
11025
11686
|
});
|
|
11026
|
-
if (!response.ok)
|
|
11687
|
+
if (!response.ok) {
|
|
11688
|
+
if (options.fail_on_provider_error)
|
|
11689
|
+
throw new ProfileSynthesisError;
|
|
11027
11690
|
return null;
|
|
11691
|
+
}
|
|
11028
11692
|
const data = await response.json();
|
|
11029
11693
|
const profile = data.content?.[0]?.text?.trim();
|
|
11030
|
-
if (!profile)
|
|
11694
|
+
if (!profile) {
|
|
11695
|
+
if (options.fail_on_provider_error)
|
|
11696
|
+
throw new ProfileSynthesisError;
|
|
11031
11697
|
return null;
|
|
11032
|
-
|
|
11698
|
+
}
|
|
11699
|
+
saveProfile(profileKey, profile, allMemories.length, target);
|
|
11033
11700
|
return { profile, memory_count: allMemories.length, from_cache: false };
|
|
11034
|
-
} catch {
|
|
11701
|
+
} catch (error) {
|
|
11702
|
+
if (options.fail_on_provider_error) {
|
|
11703
|
+
if (error instanceof ProfileSynthesisError)
|
|
11704
|
+
throw error;
|
|
11705
|
+
throw new ProfileSynthesisError;
|
|
11706
|
+
}
|
|
11035
11707
|
return null;
|
|
11036
11708
|
}
|
|
11037
11709
|
}
|
|
11038
|
-
function saveProfile(key, value, memoryCount,
|
|
11710
|
+
function saveProfile(key, value, memoryCount, target) {
|
|
11039
11711
|
try {
|
|
11040
11712
|
createMemory({
|
|
11041
11713
|
key,
|
|
11042
11714
|
value,
|
|
11043
|
-
category: "
|
|
11715
|
+
category: "resource",
|
|
11044
11716
|
scope: "shared",
|
|
11045
11717
|
importance: 10,
|
|
11046
11718
|
source: "auto",
|
|
11047
11719
|
tags: ["profile", "synthesized"],
|
|
11048
11720
|
when_to_use: "When needing to understand this agent's or project's preferences, style, and conventions",
|
|
11049
11721
|
metadata: { memory_count: memoryCount, synthesized_at: new Date().toISOString(), stale: false },
|
|
11050
|
-
agent_id:
|
|
11051
|
-
project_id:
|
|
11722
|
+
agent_id: target.agent_id,
|
|
11723
|
+
project_id: target.project_id
|
|
11052
11724
|
});
|
|
11053
11725
|
} catch {}
|
|
11054
11726
|
}
|
|
11055
|
-
function markProfileStale(projectId,
|
|
11727
|
+
function markProfileStale(projectId, agentId) {
|
|
11728
|
+
const targets = [{ scope: "global", id: "global", memory_scope: "global" }];
|
|
11729
|
+
if (projectId)
|
|
11730
|
+
targets.push({ scope: "project", id: projectId, memory_scope: "shared", project_id: projectId });
|
|
11731
|
+
if (agentId)
|
|
11732
|
+
targets.push({ scope: "agent", id: agentId, memory_scope: "private", agent_id: agentId });
|
|
11056
11733
|
try {
|
|
11057
|
-
const
|
|
11058
|
-
const
|
|
11059
|
-
|
|
11060
|
-
|
|
11734
|
+
const db = !isServerContext() && isApiMode() ? undefined : (init_database(), __toCommonJS(exports_database)).getDatabase();
|
|
11735
|
+
for (const target of targets) {
|
|
11736
|
+
const cached = getMemoryByKey(getProfileKey(target.scope, target.id), "shared", target.agent_id, target.project_id, undefined, db);
|
|
11737
|
+
if (!cached)
|
|
11738
|
+
continue;
|
|
11739
|
+
updateMemory(cached.id, {
|
|
11740
|
+
metadata: { ...cached.metadata ?? {}, stale: true },
|
|
11741
|
+
version: cached.version
|
|
11742
|
+
}, db);
|
|
11743
|
+
}
|
|
11061
11744
|
} catch {}
|
|
11062
11745
|
}
|
|
11063
11746
|
var PROFILE_PROMPT = `You synthesize a coherent agent/project profile from individual preference and fact memories.
|
|
@@ -11070,9 +11753,26 @@ Output a concise profile (200-300 words max) organized by:
|
|
|
11070
11753
|
- **Key Facts**: Architecture decisions, constraints, team conventions
|
|
11071
11754
|
|
|
11072
11755
|
Only include sections that have relevant data. Be specific and actionable.
|
|
11073
|
-
Output in markdown format
|
|
11756
|
+
Output in markdown format.`, PROFILE_SYNTHESIS_CONTRACT = "mementos.profile.synthesize.v2", ProfileScopeError, ProfileSynthesisError;
|
|
11074
11757
|
var init_profile_synthesizer = __esm(() => {
|
|
11075
11758
|
init_memories();
|
|
11759
|
+
init_api_mode();
|
|
11760
|
+
init_api_response_contract();
|
|
11761
|
+
init_storage();
|
|
11762
|
+
ProfileScopeError = class ProfileScopeError extends Error {
|
|
11763
|
+
code = "MEMENTOS_PROFILE_SCOPE";
|
|
11764
|
+
constructor(message) {
|
|
11765
|
+
super(message);
|
|
11766
|
+
this.name = "ProfileScopeError";
|
|
11767
|
+
}
|
|
11768
|
+
};
|
|
11769
|
+
ProfileSynthesisError = class ProfileSynthesisError extends Error {
|
|
11770
|
+
code = "MEMENTOS_PROFILE_SYNTHESIS";
|
|
11771
|
+
constructor() {
|
|
11772
|
+
super("Profile synthesis provider failed");
|
|
11773
|
+
this.name = "ProfileSynthesisError";
|
|
11774
|
+
}
|
|
11775
|
+
};
|
|
11076
11776
|
});
|
|
11077
11777
|
|
|
11078
11778
|
// src/lib/contradiction.ts
|
|
@@ -11356,6 +12056,22 @@ var init_built_in_hooks = __esm(() => {
|
|
|
11356
12056
|
} catch {}
|
|
11357
12057
|
}
|
|
11358
12058
|
});
|
|
12059
|
+
hookRegistry.register({
|
|
12060
|
+
type: "PostMemoryUpdate",
|
|
12061
|
+
blocking: false,
|
|
12062
|
+
builtin: true,
|
|
12063
|
+
priority: 65,
|
|
12064
|
+
description: "Mark synthesized profile as stale when a preference or fact memory is updated",
|
|
12065
|
+
handler: async (ctx) => {
|
|
12066
|
+
const category = ctx.memory?.category;
|
|
12067
|
+
if (category !== "preference" && category !== "fact")
|
|
12068
|
+
return;
|
|
12069
|
+
try {
|
|
12070
|
+
const { markProfileStale: markProfileStale2 } = await Promise.resolve().then(() => (init_profile_synthesizer(), exports_profile_synthesizer));
|
|
12071
|
+
markProfileStale2(ctx.projectId, ctx.agentId);
|
|
12072
|
+
} catch {}
|
|
12073
|
+
}
|
|
12074
|
+
});
|
|
11359
12075
|
hookRegistry.register({
|
|
11360
12076
|
type: "PostMemorySave",
|
|
11361
12077
|
blocking: false,
|
|
@@ -12213,10 +12929,20 @@ __export(exports_synthesis2, {
|
|
|
12213
12929
|
getSynthesisStatus: () => getSynthesisStatus
|
|
12214
12930
|
});
|
|
12215
12931
|
async function runSynthesis(options = {}) {
|
|
12216
|
-
const d = options.db || getDatabase();
|
|
12217
12932
|
const projectId = options.projectId ?? null;
|
|
12218
12933
|
const agentId = options.agentId ?? null;
|
|
12219
12934
|
const dryRun = options.dryRun ?? false;
|
|
12935
|
+
if (!options.db && isApiMode()) {
|
|
12936
|
+
const { data } = apiJson("POST", "/synthesis/run", {
|
|
12937
|
+
project_id: projectId,
|
|
12938
|
+
agent_id: agentId,
|
|
12939
|
+
dry_run: dryRun,
|
|
12940
|
+
max_proposals: options.maxProposals,
|
|
12941
|
+
provider: options.provider
|
|
12942
|
+
});
|
|
12943
|
+
return data;
|
|
12944
|
+
}
|
|
12945
|
+
const d = options.db || getDatabase();
|
|
12220
12946
|
const run = createSynthesisRun({
|
|
12221
12947
|
triggered_by: "manual",
|
|
12222
12948
|
project_id: projectId,
|
|
@@ -12305,6 +13031,10 @@ async function runSynthesis(options = {}) {
|
|
|
12305
13031
|
}
|
|
12306
13032
|
}
|
|
12307
13033
|
async function rollbackSynthesis(runId, db) {
|
|
13034
|
+
if (!db && isApiMode()) {
|
|
13035
|
+
const { data } = apiJson("POST", `/synthesis/rollback/${encodeURIComponent(runId)}`);
|
|
13036
|
+
return data;
|
|
13037
|
+
}
|
|
12308
13038
|
const d = db || getDatabase();
|
|
12309
13039
|
const result = await rollbackRun(runId, d);
|
|
12310
13040
|
if (result.errors.length === 0) {
|
|
@@ -12353,8 +13083,37 @@ __export(exports_session_jobs, {
|
|
|
12353
13083
|
getSessionJob: () => getSessionJob,
|
|
12354
13084
|
getNextPendingJob: () => getNextPendingJob,
|
|
12355
13085
|
createSessionJob: () => createSessionJob,
|
|
12356
|
-
claimSessionJob: () => claimSessionJob
|
|
13086
|
+
claimSessionJob: () => claimSessionJob,
|
|
13087
|
+
SESSION_JOBS_PAGE_CONTRACT: () => SESSION_JOBS_PAGE_CONTRACT,
|
|
13088
|
+
SESSION_INGEST_CONTRACT: () => SESSION_INGEST_CONTRACT
|
|
12357
13089
|
});
|
|
13090
|
+
function parseHostedSessionJob(value, operation) {
|
|
13091
|
+
const row = expectObject(value, operation);
|
|
13092
|
+
const source = expectString(row, "source", operation);
|
|
13093
|
+
const status = expectString(row, "status", operation);
|
|
13094
|
+
if (!SESSION_JOB_SOURCES.has(source)) {
|
|
13095
|
+
throw new MementosApiProtocolError(operation, "unsupported 'source'");
|
|
13096
|
+
}
|
|
13097
|
+
if (!SESSION_JOB_STATUSES.has(status)) {
|
|
13098
|
+
throw new MementosApiProtocolError(operation, "unsupported 'status'");
|
|
13099
|
+
}
|
|
13100
|
+
return {
|
|
13101
|
+
id: expectString(row, "id", operation),
|
|
13102
|
+
session_id: expectString(row, "session_id", operation),
|
|
13103
|
+
agent_id: expectNullableString(row, "agent_id", operation),
|
|
13104
|
+
project_id: expectNullableString(row, "project_id", operation),
|
|
13105
|
+
source,
|
|
13106
|
+
status,
|
|
13107
|
+
transcript: expectString(row, "transcript", operation, { allowEmpty: true }),
|
|
13108
|
+
chunk_count: expectNonNegativeInteger(row, "chunk_count", operation),
|
|
13109
|
+
memories_extracted: expectNonNegativeInteger(row, "memories_extracted", operation),
|
|
13110
|
+
error: expectNullableString(row, "error", operation),
|
|
13111
|
+
metadata: expectRecord(row, "metadata", operation),
|
|
13112
|
+
created_at: expectString(row, "created_at", operation),
|
|
13113
|
+
started_at: expectNullableString(row, "started_at", operation),
|
|
13114
|
+
completed_at: expectNullableString(row, "completed_at", operation)
|
|
13115
|
+
};
|
|
13116
|
+
}
|
|
12358
13117
|
function parseJobRow(row) {
|
|
12359
13118
|
return {
|
|
12360
13119
|
id: row["id"],
|
|
@@ -12374,6 +13133,28 @@ function parseJobRow(row) {
|
|
|
12374
13133
|
};
|
|
12375
13134
|
}
|
|
12376
13135
|
function createSessionJob(input, db) {
|
|
13136
|
+
if (!db && isApiMode()) {
|
|
13137
|
+
const operation = "POST /sessions/ingest";
|
|
13138
|
+
const { data } = apiJson("POST", "/sessions/ingest", {
|
|
13139
|
+
session_id: input.session_id,
|
|
13140
|
+
transcript: input.transcript,
|
|
13141
|
+
source: input.source ?? "manual",
|
|
13142
|
+
agent_id: input.agent_id,
|
|
13143
|
+
project_id: input.project_id,
|
|
13144
|
+
metadata: input.metadata ?? {}
|
|
13145
|
+
});
|
|
13146
|
+
const response = expectObject(data, operation);
|
|
13147
|
+
if (response["contract"] !== SESSION_INGEST_CONTRACT) {
|
|
13148
|
+
throw new MementosApiProtocolError(operation, `expected contract '${SESSION_INGEST_CONTRACT}'`);
|
|
13149
|
+
}
|
|
13150
|
+
const jobId = expectString(response, "job_id", operation);
|
|
13151
|
+
const jobObject = expectObject(response["job"], operation);
|
|
13152
|
+
const job = parseHostedSessionJob({ ...jobObject, transcript: input.transcript }, `${operation} job`);
|
|
13153
|
+
if (job.id !== jobId || job.session_id !== input.session_id) {
|
|
13154
|
+
throw new MementosApiProtocolError(operation, "job receipt identity does not match the request");
|
|
13155
|
+
}
|
|
13156
|
+
return job;
|
|
13157
|
+
}
|
|
12377
13158
|
const d = db || getDatabase();
|
|
12378
13159
|
const id = uuid();
|
|
12379
13160
|
const timestamp2 = now();
|
|
@@ -12395,10 +13176,11 @@ function createSessionJob(input, db) {
|
|
|
12395
13176
|
}
|
|
12396
13177
|
function getSessionJob(id, db) {
|
|
12397
13178
|
if (!db && isApiMode()) {
|
|
13179
|
+
const operation = `GET /sessions/jobs/${encodeURIComponent(id)}`;
|
|
12398
13180
|
const { status, data } = apiJson("GET", `/sessions/jobs/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
12399
|
-
if (status === 404
|
|
13181
|
+
if (status === 404)
|
|
12400
13182
|
return null;
|
|
12401
|
-
return data;
|
|
13183
|
+
return parseHostedSessionJob(data, operation);
|
|
12402
13184
|
}
|
|
12403
13185
|
const d = db || getDatabase();
|
|
12404
13186
|
const row = d.query("SELECT * FROM session_memory_jobs WHERE id = ?").get(id);
|
|
@@ -12412,10 +13194,58 @@ function listSessionJobs(filter, db) {
|
|
|
12412
13194
|
agent_id: filter?.agent_id,
|
|
12413
13195
|
project_id: filter?.project_id,
|
|
12414
13196
|
status: filter?.status,
|
|
12415
|
-
|
|
13197
|
+
session_id: filter?.session_id,
|
|
13198
|
+
limit: filter?.limit,
|
|
13199
|
+
offset: filter?.offset
|
|
12416
13200
|
});
|
|
13201
|
+
const operation = "GET /sessions/jobs";
|
|
12417
13202
|
const { data } = apiJson("GET", `/sessions/jobs${q}`);
|
|
12418
|
-
|
|
13203
|
+
const response = expectObject(data, operation);
|
|
13204
|
+
if (response["contract"] !== SESSION_JOBS_PAGE_CONTRACT) {
|
|
13205
|
+
throw new MementosApiProtocolError(operation, `expected contract '${SESSION_JOBS_PAGE_CONTRACT}'`);
|
|
13206
|
+
}
|
|
13207
|
+
const jobs = expectArray(response["jobs"], operation, "jobs").map((job, index) => parseHostedSessionJob(job, `${operation} item ${index}`));
|
|
13208
|
+
const count = expectNonNegativeInteger(response, "count", operation);
|
|
13209
|
+
const responseLimit = expectNonNegativeInteger(response, "limit", operation);
|
|
13210
|
+
const responseOffset = expectNonNegativeInteger(response, "offset", operation);
|
|
13211
|
+
const hasMore = expectBoolean(response, "has_more", operation);
|
|
13212
|
+
const nextOffset = response["next_offset"];
|
|
13213
|
+
if (responseLimit < 1) {
|
|
13214
|
+
throw new MementosApiProtocolError(operation, "expected 'limit' to be positive");
|
|
13215
|
+
}
|
|
13216
|
+
if (count !== jobs.length || count > responseLimit) {
|
|
13217
|
+
throw new MementosApiProtocolError(operation, "page count is inconsistent with jobs/limit");
|
|
13218
|
+
}
|
|
13219
|
+
if (nextOffset !== null && (!Number.isSafeInteger(nextOffset) || nextOffset < 0)) {
|
|
13220
|
+
throw new MementosApiProtocolError(operation, "expected 'next_offset' to be a non-negative safe integer or null");
|
|
13221
|
+
}
|
|
13222
|
+
if (hasMore && nextOffset !== responseOffset + jobs.length) {
|
|
13223
|
+
throw new MementosApiProtocolError(operation, "'has_more' requires the exact next offset");
|
|
13224
|
+
}
|
|
13225
|
+
if (!hasMore && nextOffset !== null) {
|
|
13226
|
+
throw new MementosApiProtocolError(operation, "terminal page must set 'next_offset' to null");
|
|
13227
|
+
}
|
|
13228
|
+
if (filter?.limit !== undefined && responseLimit !== filter.limit) {
|
|
13229
|
+
throw new MementosApiProtocolError(operation, "server did not preserve requested 'limit'");
|
|
13230
|
+
}
|
|
13231
|
+
if (filter?.offset !== undefined && responseOffset !== filter.offset) {
|
|
13232
|
+
throw new MementosApiProtocolError(operation, "server did not preserve requested 'offset'");
|
|
13233
|
+
}
|
|
13234
|
+
for (const job of jobs) {
|
|
13235
|
+
if (filter?.agent_id !== undefined && job.agent_id !== filter.agent_id) {
|
|
13236
|
+
throw new MementosApiProtocolError(operation, "server did not preserve requested 'agent_id'");
|
|
13237
|
+
}
|
|
13238
|
+
if (filter?.project_id !== undefined && job.project_id !== filter.project_id) {
|
|
13239
|
+
throw new MementosApiProtocolError(operation, "server did not preserve requested 'project_id'");
|
|
13240
|
+
}
|
|
13241
|
+
if (filter?.session_id !== undefined && job.session_id !== filter.session_id) {
|
|
13242
|
+
throw new MementosApiProtocolError(operation, "server did not preserve requested 'session_id'");
|
|
13243
|
+
}
|
|
13244
|
+
if (filter?.status !== undefined && job.status !== filter.status) {
|
|
13245
|
+
throw new MementosApiProtocolError(operation, "server did not preserve requested 'status'");
|
|
13246
|
+
}
|
|
13247
|
+
}
|
|
13248
|
+
return jobs;
|
|
12419
13249
|
}
|
|
12420
13250
|
const d = db || getDatabase();
|
|
12421
13251
|
const conditions = [];
|
|
@@ -12495,9 +13325,13 @@ function recoverStaleProcessingJobs(maxAgeMs, db) {
|
|
|
12495
13325
|
const result = d.run("UPDATE session_memory_jobs SET status = 'pending', started_at = NULL WHERE status = 'processing' AND started_at < ?", [cutoff]);
|
|
12496
13326
|
return result.changes;
|
|
12497
13327
|
}
|
|
13328
|
+
var SESSION_JOBS_PAGE_CONTRACT = "mementos.sessions.jobs.v2", SESSION_INGEST_CONTRACT = "mementos.sessions.ingest.v2", SESSION_JOB_SOURCES, SESSION_JOB_STATUSES;
|
|
12498
13329
|
var init_session_jobs = __esm(() => {
|
|
12499
13330
|
init_database();
|
|
12500
13331
|
init_api_mode();
|
|
13332
|
+
init_api_response_contract();
|
|
13333
|
+
SESSION_JOB_SOURCES = new Set(["claude-code", "codex", "manual", "open-sessions"]);
|
|
13334
|
+
SESSION_JOB_STATUSES = new Set(["pending", "processing", "completed", "failed"]);
|
|
12501
13335
|
});
|
|
12502
13336
|
|
|
12503
13337
|
// src/db/tool-events.ts
|
|
@@ -13120,12 +13954,25 @@ __export(exports_session_queue, {
|
|
|
13120
13954
|
enqueueSessionJob: () => enqueueSessionJob
|
|
13121
13955
|
});
|
|
13122
13956
|
function enqueueSessionJob(jobId) {
|
|
13957
|
+
if (!isServerContext() && isApiMode())
|
|
13958
|
+
return;
|
|
13123
13959
|
_pendingQueue.add(jobId);
|
|
13124
13960
|
if (!_isProcessing) {
|
|
13125
13961
|
_processNext();
|
|
13126
13962
|
}
|
|
13127
13963
|
}
|
|
13128
13964
|
function getSessionQueueStats() {
|
|
13965
|
+
if (!isServerContext() && isApiMode()) {
|
|
13966
|
+
const operation = "GET /sessions/queue/stats";
|
|
13967
|
+
const { data } = apiJson("GET", "/sessions/queue/stats");
|
|
13968
|
+
const response = expectObject(data, operation);
|
|
13969
|
+
return {
|
|
13970
|
+
pending: expectNonNegativeInteger(response, "pending", operation),
|
|
13971
|
+
processing: expectNonNegativeInteger(response, "processing", operation),
|
|
13972
|
+
completed: expectNonNegativeInteger(response, "completed", operation),
|
|
13973
|
+
failed: expectNonNegativeInteger(response, "failed", operation)
|
|
13974
|
+
};
|
|
13975
|
+
}
|
|
13129
13976
|
try {
|
|
13130
13977
|
const db = getDatabase();
|
|
13131
13978
|
const rows = db.query("SELECT status, COUNT(*) as count FROM session_memory_jobs GROUP BY status").all();
|
|
@@ -13146,7 +13993,9 @@ function getSessionQueueStats() {
|
|
|
13146
13993
|
stats.failed = row.count;
|
|
13147
13994
|
}
|
|
13148
13995
|
return stats;
|
|
13149
|
-
} catch {
|
|
13996
|
+
} catch (error) {
|
|
13997
|
+
if (isServerContext())
|
|
13998
|
+
throw error;
|
|
13150
13999
|
return {
|
|
13151
14000
|
pending: _pendingQueue.size,
|
|
13152
14001
|
processing: _isProcessing ? 1 : 0,
|
|
@@ -13158,6 +14007,8 @@ function getSessionQueueStats() {
|
|
|
13158
14007
|
function startSessionQueueWorker() {
|
|
13159
14008
|
if (_workerStarted)
|
|
13160
14009
|
return;
|
|
14010
|
+
if (!isServerContext() && isApiMode())
|
|
14011
|
+
return;
|
|
13161
14012
|
_workerStarted = true;
|
|
13162
14013
|
setInterval(() => {
|
|
13163
14014
|
try {
|
|
@@ -13199,6 +14050,9 @@ var init_session_queue = __esm(() => {
|
|
|
13199
14050
|
init_database();
|
|
13200
14051
|
init_session_jobs();
|
|
13201
14052
|
init_session_processor();
|
|
14053
|
+
init_api_mode();
|
|
14054
|
+
init_api_response_contract();
|
|
14055
|
+
init_storage();
|
|
13202
14056
|
_pendingQueue = new Set;
|
|
13203
14057
|
});
|
|
13204
14058
|
|
|
@@ -13207,6 +14061,8 @@ var exports_session_registry = {};
|
|
|
13207
14061
|
__export(exports_session_registry, {
|
|
13208
14062
|
updateSessionAgent: () => updateSessionAgent,
|
|
13209
14063
|
unregisterSession: () => unregisterSession,
|
|
14064
|
+
sessionRegistryUsesLocalStore: () => sessionRegistryUsesLocalStore,
|
|
14065
|
+
sessionRegistryPath: () => sessionRegistryPath,
|
|
13210
14066
|
registerSession: () => registerSession,
|
|
13211
14067
|
listSessions: () => listSessions,
|
|
13212
14068
|
heartbeatSession: () => heartbeatSession,
|
|
@@ -13214,17 +14070,36 @@ __export(exports_session_registry, {
|
|
|
13214
14070
|
getSessionByAgent: () => getSessionByAgent,
|
|
13215
14071
|
getSession: () => getSession,
|
|
13216
14072
|
closeRegistry: () => closeRegistry,
|
|
13217
|
-
cleanStaleSessions: () => cleanStaleSessions
|
|
14073
|
+
cleanStaleSessions: () => cleanStaleSessions,
|
|
14074
|
+
__resetProcessLocalRegistry: () => __resetProcessLocalRegistry,
|
|
14075
|
+
SESSION_REGISTRY_FILE: () => SESSION_REGISTRY_FILE
|
|
13218
14076
|
});
|
|
13219
14077
|
import { existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
|
|
13220
14078
|
import { dirname as dirname6, join as join14 } from "path";
|
|
14079
|
+
function sessionRegistryUsesLocalStore(env2 = process.env) {
|
|
14080
|
+
return isServerContext() || selectsMementosLocalStore(env2);
|
|
14081
|
+
}
|
|
14082
|
+
function sessionRegistryPath() {
|
|
14083
|
+
const store = getDbPath2();
|
|
14084
|
+
if (store === ":memory:")
|
|
14085
|
+
return ":memory:";
|
|
14086
|
+
return join14(dirname6(store), SESSION_REGISTRY_FILE);
|
|
14087
|
+
}
|
|
13221
14088
|
function getDb() {
|
|
13222
|
-
|
|
14089
|
+
const path = sessionRegistryPath();
|
|
14090
|
+
if (_db2 && _dbPath2 === path)
|
|
13223
14091
|
return _db2;
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
|
|
13227
|
-
|
|
14092
|
+
if (_db2) {
|
|
14093
|
+
_db2.close();
|
|
14094
|
+
_db2 = null;
|
|
14095
|
+
}
|
|
14096
|
+
if (path !== ":memory:") {
|
|
14097
|
+
const dir = dirname6(path);
|
|
14098
|
+
if (!existsSync10(dir))
|
|
14099
|
+
mkdirSync5(dir, { recursive: true });
|
|
14100
|
+
}
|
|
14101
|
+
_db2 = new SqliteAdapter(path);
|
|
14102
|
+
_dbPath2 = path;
|
|
13228
14103
|
_db2.run("PRAGMA journal_mode = WAL");
|
|
13229
14104
|
_db2.run("PRAGMA busy_timeout = 3000");
|
|
13230
14105
|
_db2.exec(`
|
|
@@ -13281,12 +14156,45 @@ function isProcessAlive(pid) {
|
|
|
13281
14156
|
return false;
|
|
13282
14157
|
}
|
|
13283
14158
|
}
|
|
14159
|
+
function matchesFilter(s, filter) {
|
|
14160
|
+
if (!filter)
|
|
14161
|
+
return true;
|
|
14162
|
+
if (filter.project_name && s.project_name !== filter.project_name)
|
|
14163
|
+
return false;
|
|
14164
|
+
if (filter.git_root && s.git_root !== filter.git_root)
|
|
14165
|
+
return false;
|
|
14166
|
+
if (filter.mcp_server && s.mcp_server !== filter.mcp_server)
|
|
14167
|
+
return false;
|
|
14168
|
+
if (filter.agent_name && s.agent_name !== filter.agent_name)
|
|
14169
|
+
return false;
|
|
14170
|
+
if (filter.exclude_pid && s.pid === filter.exclude_pid)
|
|
14171
|
+
return false;
|
|
14172
|
+
return true;
|
|
14173
|
+
}
|
|
13284
14174
|
function registerSession(opts) {
|
|
13285
|
-
const db = getDb();
|
|
13286
14175
|
const pid = process.pid;
|
|
13287
14176
|
const cwd = opts.cwd || process.cwd();
|
|
13288
|
-
const id = generateId();
|
|
13289
14177
|
const timestamp2 = now3();
|
|
14178
|
+
if (!sessionRegistryUsesLocalStore()) {
|
|
14179
|
+
const existing2 = [..._memory.values()].find((s) => s.pid === pid && s.mcp_server === opts.mcp_server);
|
|
14180
|
+
const record = {
|
|
14181
|
+
id: existing2?.id ?? generateId(),
|
|
14182
|
+
pid,
|
|
14183
|
+
cwd,
|
|
14184
|
+
git_root: opts.git_root || null,
|
|
14185
|
+
agent_name: opts.agent_name || null,
|
|
14186
|
+
project_name: opts.project_name || null,
|
|
14187
|
+
tty: opts.tty || null,
|
|
14188
|
+
mcp_server: opts.mcp_server,
|
|
14189
|
+
metadata: { ...opts.metadata || {} },
|
|
14190
|
+
registered_at: existing2?.registered_at ?? timestamp2,
|
|
14191
|
+
last_seen_at: timestamp2
|
|
14192
|
+
};
|
|
14193
|
+
_memory.set(record.id, record);
|
|
14194
|
+
return { ...record, metadata: { ...record.metadata } };
|
|
14195
|
+
}
|
|
14196
|
+
const db = getDb();
|
|
14197
|
+
const id = generateId();
|
|
13290
14198
|
const existing = db.query("SELECT id FROM sessions WHERE pid = ? AND mcp_server = ?").get(pid, opts.mcp_server);
|
|
13291
14199
|
if (existing) {
|
|
13292
14200
|
db.run(`UPDATE sessions SET agent_name = ?, project_name = ?, cwd = ?,
|
|
@@ -13319,19 +14227,41 @@ function registerSession(opts) {
|
|
|
13319
14227
|
return getSession(id);
|
|
13320
14228
|
}
|
|
13321
14229
|
function heartbeatSession(id) {
|
|
14230
|
+
if (!sessionRegistryUsesLocalStore()) {
|
|
14231
|
+
const s = _memory.get(id);
|
|
14232
|
+
if (s)
|
|
14233
|
+
s.last_seen_at = now3();
|
|
14234
|
+
return;
|
|
14235
|
+
}
|
|
13322
14236
|
const db = getDb();
|
|
13323
14237
|
db.run("UPDATE sessions SET last_seen_at = ? WHERE id = ?", [now3(), id]);
|
|
13324
14238
|
}
|
|
13325
14239
|
function unregisterSession(id) {
|
|
14240
|
+
if (!sessionRegistryUsesLocalStore()) {
|
|
14241
|
+
_memory.delete(id);
|
|
14242
|
+
return;
|
|
14243
|
+
}
|
|
13326
14244
|
const db = getDb();
|
|
13327
14245
|
db.run("DELETE FROM sessions WHERE id = ?", [id]);
|
|
13328
14246
|
}
|
|
13329
14247
|
function getSession(id) {
|
|
14248
|
+
if (!sessionRegistryUsesLocalStore()) {
|
|
14249
|
+
const s = _memory.get(id);
|
|
14250
|
+
return s ? { ...s, metadata: { ...s.metadata } } : null;
|
|
14251
|
+
}
|
|
13330
14252
|
const db = getDb();
|
|
13331
14253
|
const row = db.query("SELECT * FROM sessions WHERE id = ?").get(id);
|
|
13332
14254
|
return row ? parseRow2(row) : null;
|
|
13333
14255
|
}
|
|
13334
14256
|
function listSessions(filter) {
|
|
14257
|
+
if (!sessionRegistryUsesLocalStore()) {
|
|
14258
|
+
return [..._memory.values()].filter((s) => matchesFilter(s, filter)).filter((s) => {
|
|
14259
|
+
if (isProcessAlive(s.pid))
|
|
14260
|
+
return true;
|
|
14261
|
+
_memory.delete(s.id);
|
|
14262
|
+
return false;
|
|
14263
|
+
}).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 } }));
|
|
14264
|
+
}
|
|
13335
14265
|
const db = getDb();
|
|
13336
14266
|
const conditions = [];
|
|
13337
14267
|
const params = [];
|
|
@@ -13372,6 +14302,16 @@ function getSessionsByProject(projectName) {
|
|
|
13372
14302
|
return listSessions({ project_name: projectName });
|
|
13373
14303
|
}
|
|
13374
14304
|
function cleanStaleSessions() {
|
|
14305
|
+
if (!sessionRegistryUsesLocalStore()) {
|
|
14306
|
+
let cleaned2 = 0;
|
|
14307
|
+
for (const s of [..._memory.values()]) {
|
|
14308
|
+
if (!isProcessAlive(s.pid)) {
|
|
14309
|
+
_memory.delete(s.id);
|
|
14310
|
+
cleaned2++;
|
|
14311
|
+
}
|
|
14312
|
+
}
|
|
14313
|
+
return cleaned2;
|
|
14314
|
+
}
|
|
13375
14315
|
const db = getDb();
|
|
13376
14316
|
const rows = db.query("SELECT id, pid FROM sessions").all();
|
|
13377
14317
|
let cleaned = 0;
|
|
@@ -13384,20 +14324,35 @@ function cleanStaleSessions() {
|
|
|
13384
14324
|
return cleaned;
|
|
13385
14325
|
}
|
|
13386
14326
|
function updateSessionAgent(mcpServer, agentName) {
|
|
13387
|
-
const db = getDb();
|
|
13388
14327
|
const pid = process.pid;
|
|
14328
|
+
if (!sessionRegistryUsesLocalStore()) {
|
|
14329
|
+
for (const s of _memory.values()) {
|
|
14330
|
+
if (s.pid === pid && s.mcp_server === mcpServer) {
|
|
14331
|
+
s.agent_name = agentName;
|
|
14332
|
+
s.last_seen_at = now3();
|
|
14333
|
+
}
|
|
14334
|
+
}
|
|
14335
|
+
return;
|
|
14336
|
+
}
|
|
14337
|
+
const db = getDb();
|
|
13389
14338
|
db.run("UPDATE sessions SET agent_name = ?, last_seen_at = ? WHERE pid = ? AND mcp_server = ?", [agentName, now3(), pid, mcpServer]);
|
|
13390
14339
|
}
|
|
13391
14340
|
function closeRegistry() {
|
|
13392
14341
|
if (_db2) {
|
|
13393
14342
|
_db2.close();
|
|
13394
14343
|
_db2 = null;
|
|
14344
|
+
_dbPath2 = null;
|
|
13395
14345
|
}
|
|
13396
14346
|
}
|
|
13397
|
-
|
|
14347
|
+
function __resetProcessLocalRegistry() {
|
|
14348
|
+
_memory.clear();
|
|
14349
|
+
}
|
|
14350
|
+
var SESSION_REGISTRY_FILE = "sessions-registry.db", _db2 = null, _dbPath2 = null, _memory;
|
|
13398
14351
|
var init_session_registry = __esm(() => {
|
|
13399
14352
|
init_storage();
|
|
13400
|
-
|
|
14353
|
+
init_config();
|
|
14354
|
+
init_local_opt_in();
|
|
14355
|
+
_memory = new Map;
|
|
13401
14356
|
});
|
|
13402
14357
|
|
|
13403
14358
|
// src/db/pg-migrations.ts
|
|
@@ -14177,6 +15132,79 @@ var init_pg_migrations = __esm(() => {
|
|
|
14177
15132
|
`
|
|
14178
15133
|
${postgresMementosMemoryProjectLinkSchemaSql()}
|
|
14179
15134
|
INSERT INTO _migrations (id) VALUES (40) ON CONFLICT DO NOTHING;
|
|
15135
|
+
`,
|
|
15136
|
+
`
|
|
15137
|
+
DROP TABLE IF EXISTS pg_temp.mementos_m41_machine_preflight;
|
|
15138
|
+
CREATE TEMP TABLE mementos_m41_machine_preflight (
|
|
15139
|
+
id TEXT PRIMARY KEY,
|
|
15140
|
+
name TEXT NOT NULL CHECK(
|
|
15141
|
+
length(name) BETWEEN 1 AND 128
|
|
15142
|
+
AND name !~ '[[:cntrl:]]'
|
|
15143
|
+
),
|
|
15144
|
+
hostname TEXT NOT NULL UNIQUE CHECK(
|
|
15145
|
+
length(hostname) BETWEEN 1 AND 253
|
|
15146
|
+
AND hostname = lower(regexp_replace(btrim(hostname), '\\.+$', ''))
|
|
15147
|
+
AND hostname !~ '[[:space:]/\\\\]'
|
|
15148
|
+
),
|
|
15149
|
+
platform TEXT NOT NULL CHECK(
|
|
15150
|
+
length(platform) BETWEEN 1 AND 64
|
|
15151
|
+
AND platform = lower(btrim(platform))
|
|
15152
|
+
AND platform ~ '^[a-z0-9._-]+$'
|
|
15153
|
+
),
|
|
15154
|
+
is_primary BOOLEAN NOT NULL,
|
|
15155
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
15156
|
+
last_seen_at TIMESTAMPTZ NOT NULL CHECK(last_seen_at >= created_at)
|
|
15157
|
+
) ON COMMIT DROP;
|
|
15158
|
+
|
|
15159
|
+
INSERT INTO mementos_m41_machine_preflight
|
|
15160
|
+
(id, name, hostname, platform, is_primary, created_at, last_seen_at)
|
|
15161
|
+
SELECT
|
|
15162
|
+
id,
|
|
15163
|
+
name,
|
|
15164
|
+
lower(regexp_replace(btrim(hostname), '\\.+$', '')),
|
|
15165
|
+
platform,
|
|
15166
|
+
is_primary,
|
|
15167
|
+
created_at,
|
|
15168
|
+
last_seen_at
|
|
15169
|
+
FROM machines;
|
|
15170
|
+
|
|
15171
|
+
UPDATE machines machine
|
|
15172
|
+
SET hostname = preflight.hostname
|
|
15173
|
+
FROM mementos_m41_machine_preflight preflight
|
|
15174
|
+
WHERE preflight.id = machine.id;
|
|
15175
|
+
|
|
15176
|
+
DROP TRIGGER IF EXISTS machines_single_primary_insert ON machines;
|
|
15177
|
+
DROP TRIGGER IF EXISTS machines_single_primary_update ON machines;
|
|
15178
|
+
DROP INDEX IF EXISTS idx_machines_hostname;
|
|
15179
|
+
CREATE UNIQUE INDEX idx_machines_hostname ON machines(hostname);
|
|
15180
|
+
DROP INDEX IF EXISTS idx_machines_single_primary;
|
|
15181
|
+
CREATE UNIQUE INDEX idx_machines_single_primary
|
|
15182
|
+
ON machines ((1)) WHERE is_primary = TRUE;
|
|
15183
|
+
|
|
15184
|
+
ALTER TABLE machines DROP CONSTRAINT IF EXISTS machines_hostname_canonical;
|
|
15185
|
+
ALTER TABLE machines ADD CONSTRAINT machines_hostname_canonical CHECK(
|
|
15186
|
+
length(hostname) BETWEEN 1 AND 253
|
|
15187
|
+
AND hostname = lower(regexp_replace(btrim(hostname), '\\.+$', ''))
|
|
15188
|
+
AND hostname !~ '[[:space:]/\\\\]'
|
|
15189
|
+
);
|
|
15190
|
+
|
|
15191
|
+
ALTER TABLE machines DROP CONSTRAINT IF EXISTS machines_name_runtime;
|
|
15192
|
+
ALTER TABLE machines ADD CONSTRAINT machines_name_runtime CHECK(
|
|
15193
|
+
length(name) BETWEEN 1 AND 128
|
|
15194
|
+
AND name !~ '[[:cntrl:]]'
|
|
15195
|
+
);
|
|
15196
|
+
ALTER TABLE machines DROP CONSTRAINT IF EXISTS machines_platform_runtime;
|
|
15197
|
+
ALTER TABLE machines ADD CONSTRAINT machines_platform_runtime CHECK(
|
|
15198
|
+
length(platform) BETWEEN 1 AND 64
|
|
15199
|
+
AND platform = lower(btrim(platform))
|
|
15200
|
+
AND platform ~ '^[a-z0-9._-]+$'
|
|
15201
|
+
);
|
|
15202
|
+
ALTER TABLE machines DROP CONSTRAINT IF EXISTS machines_liveness_runtime;
|
|
15203
|
+
ALTER TABLE machines ADD CONSTRAINT machines_liveness_runtime CHECK(
|
|
15204
|
+
last_seen_at >= created_at
|
|
15205
|
+
);
|
|
15206
|
+
|
|
15207
|
+
INSERT INTO _migrations (id) VALUES (41) ON CONFLICT DO NOTHING;
|
|
14180
15208
|
`
|
|
14181
15209
|
];
|
|
14182
15210
|
});
|
|
@@ -14232,24 +15260,32 @@ async function applyPgMigrations(connectionString) {
|
|
|
14232
15260
|
totalMigrations: PG_MIGRATIONS.length
|
|
14233
15261
|
};
|
|
14234
15262
|
try {
|
|
14235
|
-
|
|
14236
|
-
|
|
14237
|
-
|
|
14238
|
-
|
|
14239
|
-
|
|
14240
|
-
|
|
14241
|
-
|
|
15263
|
+
const migrationLockKey = 21760041;
|
|
15264
|
+
await pg2.transaction(async (client) => {
|
|
15265
|
+
await client.query("SELECT pg_advisory_xact_lock($1)", [migrationLockKey]);
|
|
15266
|
+
await client.query(`CREATE TABLE IF NOT EXISTS _pg_migrations (
|
|
15267
|
+
id SERIAL PRIMARY KEY,
|
|
15268
|
+
version INT UNIQUE NOT NULL,
|
|
15269
|
+
applied_at TIMESTAMPTZ DEFAULT NOW()
|
|
15270
|
+
)`);
|
|
15271
|
+
});
|
|
14242
15272
|
for (let i = 0;i < PG_MIGRATIONS.length; i++) {
|
|
14243
|
-
if (appliedSet.has(i)) {
|
|
14244
|
-
result.alreadyApplied.push(i);
|
|
14245
|
-
continue;
|
|
14246
|
-
}
|
|
14247
15273
|
try {
|
|
14248
|
-
await pg2.
|
|
14249
|
-
|
|
14250
|
-
|
|
14251
|
-
|
|
14252
|
-
|
|
15274
|
+
const outcome = await pg2.transaction(async (client) => {
|
|
15275
|
+
await client.query("SELECT pg_advisory_xact_lock($1)", [migrationLockKey]);
|
|
15276
|
+
const already = await client.query("SELECT 1 FROM _pg_migrations WHERE version = $1", [i]);
|
|
15277
|
+
if (already.rowCount)
|
|
15278
|
+
return "already";
|
|
15279
|
+
await client.query(PG_MIGRATIONS[i]);
|
|
15280
|
+
await client.query("INSERT INTO _pg_migrations (version) VALUES ($1)", [i]);
|
|
15281
|
+
return "applied";
|
|
15282
|
+
});
|
|
15283
|
+
if (outcome === "already")
|
|
15284
|
+
result.alreadyApplied.push(i);
|
|
15285
|
+
else
|
|
15286
|
+
result.applied.push(i);
|
|
15287
|
+
} catch (error) {
|
|
15288
|
+
result.errors.push(`Migration ${i}: ${error instanceof Error ? error.message : String(error)}`);
|
|
14253
15289
|
break;
|
|
14254
15290
|
}
|
|
14255
15291
|
}
|
|
@@ -46679,7 +47715,7 @@ var require_token_io = __commonJS((exports, module) => {
|
|
|
46679
47715
|
module.exports = __toCommonJS2(token_io_exports);
|
|
46680
47716
|
var import_path2 = __toESM2(__require("path"));
|
|
46681
47717
|
var import_fs2 = __toESM2(__require("fs"));
|
|
46682
|
-
var
|
|
47718
|
+
var import_os2 = __toESM2(__require("os"));
|
|
46683
47719
|
var import_token_error = require_token_error();
|
|
46684
47720
|
function findRootDir() {
|
|
46685
47721
|
try {
|
|
@@ -46700,11 +47736,11 @@ var require_token_io = __commonJS((exports, module) => {
|
|
|
46700
47736
|
if (process.env.XDG_DATA_HOME) {
|
|
46701
47737
|
return process.env.XDG_DATA_HOME;
|
|
46702
47738
|
}
|
|
46703
|
-
switch (
|
|
47739
|
+
switch (import_os2.default.platform()) {
|
|
46704
47740
|
case "darwin":
|
|
46705
|
-
return import_path2.default.join(
|
|
47741
|
+
return import_path2.default.join(import_os2.default.homedir(), "Library/Application Support");
|
|
46706
47742
|
case "linux":
|
|
46707
|
-
return import_path2.default.join(
|
|
47743
|
+
return import_path2.default.join(import_os2.default.homedir(), ".local/share");
|
|
46708
47744
|
case "win32":
|
|
46709
47745
|
if (process.env.LOCALAPPDATA) {
|
|
46710
47746
|
return process.env.LOCALAPPDATA;
|
|
@@ -46814,10 +47850,10 @@ var require_oauth = __commonJS((exports, module) => {
|
|
|
46814
47850
|
refreshTokenRequest: () => refreshTokenRequest
|
|
46815
47851
|
});
|
|
46816
47852
|
module.exports = __toCommonJS2(oauth_exports);
|
|
46817
|
-
var
|
|
47853
|
+
var import_os2 = __require("os");
|
|
46818
47854
|
var VERCEL_ISSUER = "https://vercel.com";
|
|
46819
47855
|
var VERCEL_CLI_CLIENT_ID = "cl_HYyOPBNtFMfHhaUn9L4QPfTZz6TP47bp";
|
|
46820
|
-
var userAgent = `@vercel/oidc node-${process.version} ${(0,
|
|
47856
|
+
var userAgent = `@vercel/oidc node-${process.version} ${(0, import_os2.platform)()} (${(0, import_os2.arch)()}) ${(0, import_os2.hostname)()}`;
|
|
46821
47857
|
var _tokenEndpoint = null;
|
|
46822
47858
|
async function getTokenEndpoint() {
|
|
46823
47859
|
if (_tokenEndpoint) {
|
|
@@ -64449,65 +65485,283 @@ import { fileURLToPath as fileURLToPath5 } from "url";
|
|
|
64449
65485
|
|
|
64450
65486
|
// src/db/machines.ts
|
|
64451
65487
|
init_database();
|
|
65488
|
+
init_api_mode();
|
|
65489
|
+
init_api_response_contract();
|
|
65490
|
+
import { createHash } from "crypto";
|
|
64452
65491
|
import { hostname, platform } from "os";
|
|
64453
|
-
|
|
65492
|
+
var MACHINE_REGISTRATION_CONTRACT = "mementos.machine-registration.v1";
|
|
65493
|
+
var MACHINE_LIST_CONTRACT = "mementos.machines.v1";
|
|
65494
|
+
class MachineRegistryError extends Error {
|
|
65495
|
+
code;
|
|
65496
|
+
constructor(code, message) {
|
|
65497
|
+
super(message);
|
|
65498
|
+
this.code = code;
|
|
65499
|
+
this.name = "MachineRegistryError";
|
|
65500
|
+
}
|
|
65501
|
+
}
|
|
65502
|
+
var ISO_UTC_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
65503
|
+
function canonicalTimestamp(value, operation, field) {
|
|
65504
|
+
const stringValue = value instanceof Date ? value.toISOString() : value;
|
|
65505
|
+
if (typeof stringValue !== "string" || !ISO_UTC_TIMESTAMP.test(stringValue)) {
|
|
65506
|
+
throw new MementosApiProtocolError(operation, `expected '${field}' to be a canonical UTC ISO-8601 timestamp`);
|
|
65507
|
+
}
|
|
65508
|
+
const parsed = new Date(stringValue);
|
|
65509
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== stringValue) {
|
|
65510
|
+
throw new MementosApiProtocolError(operation, `expected '${field}' to be a real calendar timestamp`);
|
|
65511
|
+
}
|
|
65512
|
+
return stringValue;
|
|
65513
|
+
}
|
|
65514
|
+
function machineFromDb(row, operation = "machine database row") {
|
|
64454
65515
|
if (!row)
|
|
64455
65516
|
return null;
|
|
65517
|
+
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) {
|
|
65518
|
+
throw new Error(`${operation} is missing or violates required machine identity fields`);
|
|
65519
|
+
}
|
|
65520
|
+
const canonicalHostname = normalizeMachineHostname(row.hostname);
|
|
65521
|
+
const canonicalPlatform = normalizeMachinePlatform(row.platform);
|
|
65522
|
+
if (canonicalHostname !== row.hostname || canonicalPlatform !== row.platform) {
|
|
65523
|
+
throw new Error(`${operation} contains noncanonical hostname or platform data`);
|
|
65524
|
+
}
|
|
65525
|
+
if (!["boolean", "number"].includes(typeof row.is_primary)) {
|
|
65526
|
+
throw new Error(`${operation} has an invalid is_primary value`);
|
|
65527
|
+
}
|
|
65528
|
+
const createdValue = row.created_at instanceof Date ? row.created_at : new Date(String(row.created_at ?? ""));
|
|
65529
|
+
const lastSeenValue = row.last_seen_at instanceof Date ? row.last_seen_at : new Date(String(row.last_seen_at ?? ""));
|
|
65530
|
+
if (!Number.isFinite(createdValue.getTime()) || !Number.isFinite(lastSeenValue.getTime())) {
|
|
65531
|
+
throw new Error(`${operation} has an invalid timestamp`);
|
|
65532
|
+
}
|
|
65533
|
+
const createdAt = createdValue.toISOString();
|
|
65534
|
+
const lastSeenAt = lastSeenValue.toISOString();
|
|
65535
|
+
if (lastSeenAt < createdAt)
|
|
65536
|
+
throw new Error(`${operation} has last_seen_at before created_at`);
|
|
64456
65537
|
return {
|
|
64457
|
-
|
|
64458
|
-
|
|
64459
|
-
|
|
65538
|
+
id: row.id,
|
|
65539
|
+
name: row.name,
|
|
65540
|
+
hostname: canonicalHostname,
|
|
65541
|
+
platform: canonicalPlatform,
|
|
65542
|
+
is_primary: Boolean(row.is_primary),
|
|
65543
|
+
created_at: createdAt,
|
|
65544
|
+
last_seen_at: lastSeenAt
|
|
65545
|
+
};
|
|
65546
|
+
}
|
|
65547
|
+
function decodeMachine(value, operation) {
|
|
65548
|
+
const object = expectObject(value, operation);
|
|
65549
|
+
const name = expectString(object, "name", operation);
|
|
65550
|
+
if (name.length > 128 || /[\u0000-\u001f\u007f]/.test(name)) {
|
|
65551
|
+
throw new MementosApiProtocolError(operation, "machine.name is outside the public contract");
|
|
65552
|
+
}
|
|
65553
|
+
const rawHost = expectString(object, "hostname", operation);
|
|
65554
|
+
const rawPlatform = expectString(object, "platform", operation);
|
|
65555
|
+
let machineHostname;
|
|
65556
|
+
let machinePlatform;
|
|
65557
|
+
try {
|
|
65558
|
+
machineHostname = normalizeMachineHostname(rawHost);
|
|
65559
|
+
machinePlatform = normalizeMachinePlatform(rawPlatform);
|
|
65560
|
+
} catch {
|
|
65561
|
+
throw new MementosApiProtocolError(operation, "machine hostname or platform is not canonical");
|
|
65562
|
+
}
|
|
65563
|
+
if (machineHostname !== rawHost || machinePlatform !== rawPlatform) {
|
|
65564
|
+
throw new MementosApiProtocolError(operation, "machine hostname or platform is not canonical");
|
|
65565
|
+
}
|
|
65566
|
+
const createdAt = canonicalTimestamp(object["created_at"], operation, "created_at");
|
|
65567
|
+
const lastSeenAt = canonicalTimestamp(object["last_seen_at"], operation, "last_seen_at");
|
|
65568
|
+
if (lastSeenAt < createdAt) {
|
|
65569
|
+
throw new MementosApiProtocolError(operation, "last_seen_at precedes created_at");
|
|
65570
|
+
}
|
|
65571
|
+
return {
|
|
65572
|
+
id: expectString(object, "id", operation),
|
|
65573
|
+
name,
|
|
65574
|
+
hostname: machineHostname,
|
|
65575
|
+
platform: machinePlatform,
|
|
65576
|
+
is_primary: expectBoolean(object, "is_primary", operation),
|
|
65577
|
+
created_at: createdAt,
|
|
65578
|
+
last_seen_at: lastSeenAt
|
|
65579
|
+
};
|
|
65580
|
+
}
|
|
65581
|
+
function expectContract(object, contract, operation) {
|
|
65582
|
+
if (object["contract"] !== contract) {
|
|
65583
|
+
throw new MementosApiProtocolError(operation, `expected contract '${contract}'`);
|
|
65584
|
+
}
|
|
65585
|
+
}
|
|
65586
|
+
function decodeRegistration(value, expected) {
|
|
65587
|
+
const operation = "POST /v1/machines";
|
|
65588
|
+
const object = expectObject(value, operation);
|
|
65589
|
+
expectContract(object, MACHINE_REGISTRATION_CONTRACT, operation);
|
|
65590
|
+
const machine = decodeMachine(object["machine"], operation);
|
|
65591
|
+
const created = expectBoolean(object, "created", operation);
|
|
65592
|
+
const identity = expectObject(object["identity"], operation);
|
|
65593
|
+
if (identity["idempotency_key"] !== "normalized_hostname") {
|
|
65594
|
+
throw new MementosApiProtocolError(operation, "expected normalized-hostname identity semantics");
|
|
65595
|
+
}
|
|
65596
|
+
if (expectString(identity, "stable_id", operation) !== machine.id) {
|
|
65597
|
+
throw new MementosApiProtocolError(operation, "identity stable_id does not match machine.id");
|
|
65598
|
+
}
|
|
65599
|
+
const expectedHostname = normalizeMachineHostname(expected.hostname);
|
|
65600
|
+
const expectedPlatform = normalizeMachinePlatform(expected.platform);
|
|
65601
|
+
if (machine.hostname !== expectedHostname || machine.platform !== expectedPlatform) {
|
|
65602
|
+
throw new MementosApiProtocolError(operation, "registration receipt does not match the requested hostname/platform");
|
|
65603
|
+
}
|
|
65604
|
+
if (created && machine.name !== normalizeMachineName(expected.name, expectedHostname)) {
|
|
65605
|
+
throw new MementosApiProtocolError(operation, "created registration receipt does not match the requested name");
|
|
65606
|
+
}
|
|
65607
|
+
return { machine, created };
|
|
65608
|
+
}
|
|
65609
|
+
function decodeMachineList(value) {
|
|
65610
|
+
const operation = "GET /v1/machines";
|
|
65611
|
+
const object = expectObject(value, operation);
|
|
65612
|
+
expectContract(object, MACHINE_LIST_CONTRACT, operation);
|
|
65613
|
+
const values = expectArray(object["machines"], operation, "machines");
|
|
65614
|
+
const count = expectNonNegativeInteger(object, "count", operation);
|
|
65615
|
+
if (count !== values.length) {
|
|
65616
|
+
throw new MementosApiProtocolError(operation, "count does not match machines.length");
|
|
65617
|
+
}
|
|
65618
|
+
if (object["complete"] !== true) {
|
|
65619
|
+
throw new MementosApiProtocolError(operation, "expected complete=true");
|
|
65620
|
+
}
|
|
65621
|
+
const machines = values.map((entry, index) => decodeMachine(entry, `${operation} machines[${index}]`));
|
|
65622
|
+
const ids = new Set;
|
|
65623
|
+
const hosts = new Set;
|
|
65624
|
+
const names = new Set;
|
|
65625
|
+
let primaryCount = 0;
|
|
65626
|
+
for (const machine of machines) {
|
|
65627
|
+
if (ids.has(machine.id))
|
|
65628
|
+
throw new MementosApiProtocolError(operation, `duplicate machine id '${machine.id}'`);
|
|
65629
|
+
if (hosts.has(machine.hostname))
|
|
65630
|
+
throw new MementosApiProtocolError(operation, `duplicate machine hostname '${machine.hostname}'`);
|
|
65631
|
+
if (names.has(machine.name))
|
|
65632
|
+
throw new MementosApiProtocolError(operation, `duplicate machine name '${machine.name}'`);
|
|
65633
|
+
ids.add(machine.id);
|
|
65634
|
+
hosts.add(machine.hostname);
|
|
65635
|
+
names.add(machine.name);
|
|
65636
|
+
if (machine.is_primary)
|
|
65637
|
+
primaryCount += 1;
|
|
65638
|
+
}
|
|
65639
|
+
if (primaryCount > 1)
|
|
65640
|
+
throw new MementosApiProtocolError(operation, "more than one machine is primary");
|
|
65641
|
+
return machines;
|
|
65642
|
+
}
|
|
65643
|
+
function normalizeMachineHostname(host) {
|
|
65644
|
+
const normalized = host.trim().replace(/\.+$/, "").toLowerCase();
|
|
65645
|
+
if (!normalized || normalized.length > 253 || /[\u0000-\u001f\u007f/\\\s]/.test(normalized)) {
|
|
65646
|
+
throw new MachineRegistryError("MACHINE_INVALID_INPUT", "Machine hostname must be a non-empty hostname of at most 253 characters");
|
|
65647
|
+
}
|
|
65648
|
+
return normalized;
|
|
64460
65649
|
}
|
|
64461
|
-
function
|
|
64462
|
-
|
|
65650
|
+
function normalizeMachinePlatform(value) {
|
|
65651
|
+
const normalized = value.trim().toLowerCase();
|
|
65652
|
+
if (!normalized || normalized.length > 64 || !/^[a-z0-9._-]+$/.test(normalized)) {
|
|
65653
|
+
throw new MachineRegistryError("MACHINE_INVALID_INPUT", "Machine platform must contain 1-64 lowercase letters, digits, dots, underscores, or hyphens");
|
|
65654
|
+
}
|
|
65655
|
+
return normalized;
|
|
64463
65656
|
}
|
|
64464
|
-
function
|
|
64465
|
-
const
|
|
64466
|
-
|
|
64467
|
-
|
|
64468
|
-
const machineName = name?.trim() || host;
|
|
64469
|
-
const existing = parseMachine(db.query("SELECT * FROM machines WHERE hostname = ?").get(host));
|
|
64470
|
-
if (existing) {
|
|
64471
|
-
db.run("UPDATE machines SET last_seen_at = ? WHERE id = ?", [now(), existing.id]);
|
|
64472
|
-
return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(existing.id));
|
|
65657
|
+
function normalizeMachineName(value, fallback) {
|
|
65658
|
+
const normalized = value?.trim() || fallback;
|
|
65659
|
+
if (!normalized || normalized.length > 128 || /[\u0000-\u001f\u007f]/.test(normalized)) {
|
|
65660
|
+
throw new MachineRegistryError("MACHINE_INVALID_INPUT", "Machine name must contain 1-128 printable characters");
|
|
64473
65661
|
}
|
|
64474
|
-
|
|
64475
|
-
|
|
64476
|
-
|
|
64477
|
-
|
|
65662
|
+
return normalized;
|
|
65663
|
+
}
|
|
65664
|
+
function registerMachineRecord(input, db) {
|
|
65665
|
+
const d = db ?? getDatabase();
|
|
65666
|
+
const host = normalizeMachineHostname(input.hostname);
|
|
65667
|
+
const machineName = normalizeMachineName(input.name, host);
|
|
65668
|
+
const machinePlatform = normalizeMachinePlatform(input.platform);
|
|
65669
|
+
const proposedId = uuid();
|
|
65670
|
+
const seenAt = now();
|
|
65671
|
+
try {
|
|
65672
|
+
const row = d.query(`INSERT INTO machines (id, name, hostname, platform, created_at, last_seen_at)
|
|
65673
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
65674
|
+
ON CONFLICT(hostname) DO UPDATE SET
|
|
65675
|
+
platform = excluded.platform,
|
|
65676
|
+
last_seen_at = CASE
|
|
65677
|
+
WHEN machines.last_seen_at > excluded.last_seen_at THEN machines.last_seen_at
|
|
65678
|
+
ELSE excluded.last_seen_at
|
|
65679
|
+
END
|
|
65680
|
+
RETURNING *, id = ? AS registration_created`).get(proposedId, machineName, host, machinePlatform, seenAt, seenAt, proposedId);
|
|
65681
|
+
const machine = machineFromDb(row, "machine registration upsert");
|
|
65682
|
+
if (!machine || typeof row?.registration_created !== "boolean" && typeof row?.registration_created !== "number") {
|
|
65683
|
+
throw new MachineRegistryError("MACHINE_REGISTRATION_CONFLICT", "Machine registration returned no stable identity receipt");
|
|
65684
|
+
}
|
|
65685
|
+
return { machine, created: Boolean(row.registration_created) };
|
|
65686
|
+
} catch (error) {
|
|
65687
|
+
if (error instanceof MachineRegistryError)
|
|
65688
|
+
throw error;
|
|
65689
|
+
const details = error && typeof error === "object" ? error : {};
|
|
65690
|
+
const diagnostic = `${String(details.constraint ?? "")} ${String(details.message ?? "")}`;
|
|
65691
|
+
if (/machines(?:_|\.)name|machines_name_key/i.test(diagnostic)) {
|
|
65692
|
+
throw new MachineRegistryError("MACHINE_NAME_CONFLICT", `Machine name already taken: ${machineName}`);
|
|
65693
|
+
}
|
|
65694
|
+
throw error;
|
|
64478
65695
|
}
|
|
64479
|
-
const id = uuid();
|
|
64480
|
-
db.run("INSERT INTO machines (id, name, hostname, platform) VALUES (?, ?, ?, ?)", [id, finalName, host, plat]);
|
|
64481
|
-
return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(id));
|
|
64482
65696
|
}
|
|
64483
|
-
function
|
|
64484
|
-
|
|
65697
|
+
function registerMachine(name, db) {
|
|
65698
|
+
const host = normalizeMachineHostname(hostname());
|
|
65699
|
+
const plat = normalizeMachinePlatform(platform());
|
|
65700
|
+
if (!db && isApiMode()) {
|
|
65701
|
+
const input = { name: name?.trim() || undefined, hostname: host, platform: plat };
|
|
65702
|
+
const { data } = apiJson("POST", "/machines", input);
|
|
65703
|
+
return decodeRegistration(data, input).machine;
|
|
65704
|
+
}
|
|
65705
|
+
return registerMachineRecord({ name, hostname: host, platform: plat }, db).machine;
|
|
64485
65706
|
}
|
|
64486
|
-
function
|
|
64487
|
-
if (
|
|
65707
|
+
function listMachines(db) {
|
|
65708
|
+
if (!db && isApiMode()) {
|
|
65709
|
+
const { data } = apiJson("GET", "/machines");
|
|
65710
|
+
return decodeMachineList(data);
|
|
65711
|
+
}
|
|
65712
|
+
const d = db ?? getDatabase();
|
|
65713
|
+
const rows = d.query("SELECT * FROM machines ORDER BY is_primary DESC, last_seen_at DESC, created_at ASC, id ASC").all();
|
|
65714
|
+
return rows.map((row) => machineFromDb(row));
|
|
65715
|
+
}
|
|
65716
|
+
function getPrimaryMachine(db) {
|
|
65717
|
+
if (!db && isApiMode())
|
|
65718
|
+
return listMachines().find((machine) => machine.is_primary) ?? null;
|
|
65719
|
+
const d = db ?? getDatabase();
|
|
65720
|
+
return machineFromDb(d.query("SELECT * FROM machines WHERE is_primary = 1 LIMIT 1").get());
|
|
65721
|
+
}
|
|
65722
|
+
function getPrimaryMachineCandidate(db) {
|
|
65723
|
+
if (!db && isApiMode()) {
|
|
65724
|
+
const machines = listMachines();
|
|
65725
|
+
if (machines.some((machine) => machine.is_primary))
|
|
65726
|
+
return null;
|
|
65727
|
+
return [...machines].sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id))[0] ?? null;
|
|
65728
|
+
}
|
|
65729
|
+
const d = db ?? getDatabase();
|
|
65730
|
+
if (getPrimaryMachine(d))
|
|
64488
65731
|
return null;
|
|
64489
|
-
return
|
|
65732
|
+
return machineFromDb(d.query("SELECT * FROM machines ORDER BY created_at ASC, id ASC LIMIT 1").get());
|
|
64490
65733
|
}
|
|
64491
|
-
function getPrimaryMachineStartupWarning(db
|
|
65734
|
+
function getPrimaryMachineStartupWarning(db) {
|
|
64492
65735
|
if (getPrimaryMachine(db))
|
|
64493
65736
|
return null;
|
|
64494
65737
|
const candidate = getPrimaryMachineCandidate(db);
|
|
64495
|
-
if (!candidate)
|
|
65738
|
+
if (!candidate)
|
|
64496
65739
|
return "No primary machine configured. Fallback sync target is unset because no machines are registered yet.";
|
|
64497
|
-
}
|
|
64498
65740
|
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
65741
|
}
|
|
64500
|
-
|
|
64501
|
-
|
|
64502
|
-
|
|
64503
|
-
|
|
64504
|
-
|
|
64505
|
-
|
|
64506
|
-
|
|
64507
|
-
|
|
64508
|
-
|
|
65742
|
+
var hostedMachineCache = null;
|
|
65743
|
+
function getCurrentMachineId(db) {
|
|
65744
|
+
if (!db && isApiMode()) {
|
|
65745
|
+
const host = normalizeMachineHostname(hostname());
|
|
65746
|
+
const plat = normalizeMachinePlatform(platform());
|
|
65747
|
+
const configBefore = getApiConfig();
|
|
65748
|
+
if (!configBefore)
|
|
65749
|
+
throw new Error("mementos hosted machine identity requires a resolved API authority");
|
|
65750
|
+
const credentialFingerprint = createHash("sha256").update(configBefore.apiKey).digest("hex");
|
|
65751
|
+
const key = `${configBefore.baseUrl}\x00${credentialFingerprint}\x00${host}\x00${plat}`;
|
|
65752
|
+
if (hostedMachineCache?.key === key)
|
|
65753
|
+
return hostedMachineCache.id;
|
|
65754
|
+
const id = registerMachine().id;
|
|
65755
|
+
const configAfter = getApiConfig();
|
|
65756
|
+
const fingerprintAfter = configAfter ? createHash("sha256").update(configAfter.apiKey).digest("hex") : null;
|
|
65757
|
+
if (!configAfter || configAfter.baseUrl !== configBefore.baseUrl || fingerprintAfter !== credentialFingerprint) {
|
|
65758
|
+
throw new Error("MEMENTOS_AUTHORITY_CHANGED: the machine registry authority or credential changed while resolving the stable machine identity");
|
|
65759
|
+
}
|
|
65760
|
+
hostedMachineCache = { key, id };
|
|
65761
|
+
return id;
|
|
64509
65762
|
}
|
|
64510
|
-
|
|
65763
|
+
const d = db ?? getDatabase();
|
|
65764
|
+
return registerMachine(undefined, d).id;
|
|
64511
65765
|
}
|
|
64512
65766
|
|
|
64513
65767
|
// src/cli/startup-side-effects.ts
|
|
@@ -65256,7 +66510,7 @@ init_memories();
|
|
|
65256
66510
|
init_package_version();
|
|
65257
66511
|
init_schema();
|
|
65258
66512
|
init_identity();
|
|
65259
|
-
import { createHash as
|
|
66513
|
+
import { createHash as createHash3 } from "crypto";
|
|
65260
66514
|
|
|
65261
66515
|
class MemoryProjectLinkError extends Error {
|
|
65262
66516
|
code;
|
|
@@ -65296,7 +66550,7 @@ function canonicalJson(value) {
|
|
|
65296
66550
|
return JSON.stringify(canonicalize(value));
|
|
65297
66551
|
}
|
|
65298
66552
|
function digest(value) {
|
|
65299
|
-
return
|
|
66553
|
+
return createHash3("sha256").update(canonicalJson(value)).digest("hex");
|
|
65300
66554
|
}
|
|
65301
66555
|
function timestampString2(value) {
|
|
65302
66556
|
return value instanceof Date ? value.toISOString() : String(value);
|
|
@@ -65881,7 +67135,7 @@ function getMemoryProjectLinkReceipt(memoryId, receiptId, identity = linkAuthori
|
|
|
65881
67135
|
}
|
|
65882
67136
|
|
|
65883
67137
|
// src/project-registration/authority.ts
|
|
65884
|
-
import { createHash as
|
|
67138
|
+
import { createHash as createHash4 } from "crypto";
|
|
65885
67139
|
init_projects();
|
|
65886
67140
|
init_types2();
|
|
65887
67141
|
init_identity();
|
|
@@ -65903,7 +67157,7 @@ function canonicalize2(value) {
|
|
|
65903
67157
|
return out;
|
|
65904
67158
|
}
|
|
65905
67159
|
function digestMementosProjectRegistrationValue(value) {
|
|
65906
|
-
return
|
|
67160
|
+
return createHash4("sha256").update(canonicalMementosProjectRegistrationJson(value)).digest("hex");
|
|
65907
67161
|
}
|
|
65908
67162
|
// src/project-registration/http.ts
|
|
65909
67163
|
init_types2();
|
|
@@ -66518,18 +67772,20 @@ function registerCrudCommands(program2) {
|
|
|
66518
67772
|
handleError(e);
|
|
66519
67773
|
}
|
|
66520
67774
|
});
|
|
66521
|
-
program2.command("update <id>").description("Update a memory by ID").option("--value <text>", "New value").option("--importance <n>", "New importance 1-10", parseInt).option("--tags <tags>", "New comma-separated tags").option("--summary <text>", "New summary").option("--pin", "Pin the memory").option("--unpin", "Unpin the memory").option("-c, --category <cat>", "New category").option("--scope <scope>", "New scope").option("--status <status>", "New status: active, archived, expired").action((id, opts) => {
|
|
67775
|
+
program2.command("update <id>").description("Update a memory by ID").option("--value <text>", "New value").option("--importance <n>", "New importance 1-10", parseInt).option("--tags <tags>", "New comma-separated tags").option("--summary <text>", "New summary").option("--pin", "Pin the memory").option("--unpin", "Unpin the memory").option("-c, --category <cat>", "New category").option("--scope <scope>", "New scope").option("--status <status>", "New status: active, archived, expired").action(async (id, opts) => {
|
|
66522
67776
|
try {
|
|
66523
67777
|
const globalOpts = program2.opts();
|
|
66524
67778
|
const resolvedId = resolveMemoryId(id);
|
|
66525
67779
|
const existing = getMemory(resolvedId);
|
|
66526
67780
|
if (!existing) {
|
|
66527
67781
|
if (globalOpts.json) {
|
|
66528
|
-
|
|
67782
|
+
await outputJsonAndExit({ error: `Memory not found: ${id}` }, 1);
|
|
66529
67783
|
} else {
|
|
66530
67784
|
console.error(chalk2.red(`Memory not found: ${id}`));
|
|
66531
67785
|
}
|
|
66532
|
-
|
|
67786
|
+
if (!globalOpts.json)
|
|
67787
|
+
process.exit(1);
|
|
67788
|
+
return;
|
|
66533
67789
|
}
|
|
66534
67790
|
const updateInput = {
|
|
66535
67791
|
version: existing.version
|
|
@@ -66581,7 +67837,7 @@ function registerCrudCommands(program2) {
|
|
|
66581
67837
|
handleError(e);
|
|
66582
67838
|
}
|
|
66583
67839
|
});
|
|
66584
|
-
program2.command("forget <keyOrId>").description("Delete a memory by key or ID").option("--scope <scope>", "Filter by scope (global, shared, private)").option("--agent <agent>", "Filter by agent ID").option("--project <project>", "Filter by project ID").option("--all", "Delete ALL matching memories (no disambiguation needed)").action((keyOrId, opts) => {
|
|
67840
|
+
program2.command("forget <keyOrId>").description("Delete a memory by key or ID").option("--scope <scope>", "Filter by scope (global, shared, private)").option("--agent <agent>", "Filter by agent ID").option("--project <project>", "Filter by project ID").option("--all", "Delete ALL matching memories (no disambiguation needed)").action(async (keyOrId, opts) => {
|
|
66585
67841
|
try {
|
|
66586
67842
|
const globalOpts = program2.opts();
|
|
66587
67843
|
const idMatch = isApiMode() ? null : resolvePartialId(getDatabase(), "memories", keyOrId);
|
|
@@ -66605,11 +67861,13 @@ function registerCrudCommands(program2) {
|
|
|
66605
67861
|
return;
|
|
66606
67862
|
}
|
|
66607
67863
|
if (globalOpts.json) {
|
|
66608
|
-
|
|
67864
|
+
await outputJsonAndExit({ error: `No memory found: ${keyOrId}` }, 1);
|
|
66609
67865
|
} else {
|
|
66610
67866
|
console.error(chalk2.red(`No memory found: ${keyOrId}`));
|
|
66611
67867
|
}
|
|
66612
|
-
|
|
67868
|
+
if (!globalOpts.json)
|
|
67869
|
+
process.exit(1);
|
|
67870
|
+
return;
|
|
66613
67871
|
}
|
|
66614
67872
|
if (matches.length === 1) {
|
|
66615
67873
|
deleteMemory(matches[0].id);
|
|
@@ -66632,10 +67890,10 @@ function registerCrudCommands(program2) {
|
|
|
66632
67890
|
return;
|
|
66633
67891
|
}
|
|
66634
67892
|
if (globalOpts.json) {
|
|
66635
|
-
|
|
67893
|
+
await outputJsonAndExit({
|
|
66636
67894
|
error: `Ambiguous key "${keyOrId}" \u2014 ${matches.length} memories found. Use --all to delete all, or specify an ID.`,
|
|
66637
67895
|
matches: matches.map((m) => ({ id: m.id, key: redactCredentialKey(m.key), scope: m.scope, category: m.category, agent_id: m.agent_id }))
|
|
66638
|
-
});
|
|
67896
|
+
}, 1);
|
|
66639
67897
|
} else {
|
|
66640
67898
|
console.log(chalk2.yellow(`Ambiguous key "${keyOrId}" \u2014 ${matches.length} memories found:`));
|
|
66641
67899
|
for (const m of matches) {
|
|
@@ -66644,7 +67902,8 @@ function registerCrudCommands(program2) {
|
|
|
66644
67902
|
console.log(chalk2.dim(`
|
|
66645
67903
|
Use --all to delete all, or specify an ID.`));
|
|
66646
67904
|
}
|
|
66647
|
-
|
|
67905
|
+
if (!globalOpts.json)
|
|
67906
|
+
process.exit(1);
|
|
66648
67907
|
} catch (e) {
|
|
66649
67908
|
handleError(e);
|
|
66650
67909
|
}
|
|
@@ -66658,18 +67917,20 @@ init_redact();
|
|
|
66658
67917
|
import chalk3 from "chalk";
|
|
66659
67918
|
function registerViewCommands(program2) {
|
|
66660
67919
|
const handleError = makeHandleError(program2);
|
|
66661
|
-
program2.command("show <id>").description("Show full detail of a memory by ID (supports partial IDs)").action((id) => {
|
|
67920
|
+
program2.command("show <id>").description("Show full detail of a memory by ID (supports partial IDs)").action(async (id) => {
|
|
66662
67921
|
try {
|
|
66663
67922
|
const globalOpts = program2.opts();
|
|
66664
67923
|
const resolvedId = resolveMemoryId(id);
|
|
66665
67924
|
const memory = getMemory(resolvedId);
|
|
66666
67925
|
if (!memory) {
|
|
66667
67926
|
if (globalOpts.json) {
|
|
66668
|
-
|
|
67927
|
+
await outputJsonAndExit({ error: `Memory not found: ${id}` }, 1);
|
|
66669
67928
|
} else {
|
|
66670
67929
|
console.error(chalk3.red(`Memory not found: ${id}`));
|
|
66671
67930
|
}
|
|
66672
|
-
|
|
67931
|
+
if (!globalOpts.json)
|
|
67932
|
+
process.exit(1);
|
|
67933
|
+
return;
|
|
66673
67934
|
}
|
|
66674
67935
|
touchMemory(memory.id);
|
|
66675
67936
|
const safe = redactMemoryForOutput(memory);
|
|
@@ -66682,17 +67943,19 @@ function registerViewCommands(program2) {
|
|
|
66682
67943
|
handleError(e);
|
|
66683
67944
|
}
|
|
66684
67945
|
});
|
|
66685
|
-
program2.command("pin <keyOrId>").description("Pin a memory by key or partial ID").option("--scope <scope>", "Scope filter for key lookup").option("--agent <name>", "Agent filter for key lookup").option("--project <path>", "Project filter for key lookup").action((keyOrId, opts) => {
|
|
67946
|
+
program2.command("pin <keyOrId>").description("Pin a memory by key or partial ID").option("--scope <scope>", "Scope filter for key lookup").option("--agent <name>", "Agent filter for key lookup").option("--project <path>", "Project filter for key lookup").action(async (keyOrId, opts) => {
|
|
66686
67947
|
try {
|
|
66687
67948
|
const globalOpts = program2.opts();
|
|
66688
67949
|
const memory = resolveKeyOrId(keyOrId, opts, globalOpts);
|
|
66689
67950
|
if (!memory) {
|
|
66690
67951
|
if (globalOpts.json) {
|
|
66691
|
-
|
|
67952
|
+
await outputJsonAndExit({ error: `No memory found: ${keyOrId}` }, 1);
|
|
66692
67953
|
} else {
|
|
66693
67954
|
console.error(chalk3.red(`No memory found: ${keyOrId}`));
|
|
66694
67955
|
}
|
|
66695
|
-
|
|
67956
|
+
if (!globalOpts.json)
|
|
67957
|
+
process.exit(1);
|
|
67958
|
+
return;
|
|
66696
67959
|
}
|
|
66697
67960
|
const updated = updateMemory(memory.id, {
|
|
66698
67961
|
version: memory.version,
|
|
@@ -66707,17 +67970,19 @@ function registerViewCommands(program2) {
|
|
|
66707
67970
|
handleError(e);
|
|
66708
67971
|
}
|
|
66709
67972
|
});
|
|
66710
|
-
program2.command("unpin <keyOrId>").description("Unpin a memory by key or partial ID").option("--scope <scope>", "Scope filter for key lookup").option("--agent <name>", "Agent filter for key lookup").option("--project <path>", "Project filter for key lookup").action((keyOrId, opts) => {
|
|
67973
|
+
program2.command("unpin <keyOrId>").description("Unpin a memory by key or partial ID").option("--scope <scope>", "Scope filter for key lookup").option("--agent <name>", "Agent filter for key lookup").option("--project <path>", "Project filter for key lookup").action(async (keyOrId, opts) => {
|
|
66711
67974
|
try {
|
|
66712
67975
|
const globalOpts = program2.opts();
|
|
66713
67976
|
const memory = resolveKeyOrId(keyOrId, opts, globalOpts);
|
|
66714
67977
|
if (!memory) {
|
|
66715
67978
|
if (globalOpts.json) {
|
|
66716
|
-
|
|
67979
|
+
await outputJsonAndExit({ error: `No memory found: ${keyOrId}` }, 1);
|
|
66717
67980
|
} else {
|
|
66718
67981
|
console.error(chalk3.red(`No memory found: ${keyOrId}`));
|
|
66719
67982
|
}
|
|
66720
|
-
|
|
67983
|
+
if (!globalOpts.json)
|
|
67984
|
+
process.exit(1);
|
|
67985
|
+
return;
|
|
66721
67986
|
}
|
|
66722
67987
|
const updated = updateMemory(memory.id, {
|
|
66723
67988
|
version: memory.version,
|
|
@@ -66910,14 +68175,167 @@ init_helpers();
|
|
|
66910
68175
|
init_redact();
|
|
66911
68176
|
import chalk6 from "chalk";
|
|
66912
68177
|
import { resolve as resolve6 } from "path";
|
|
68178
|
+
|
|
68179
|
+
// src/cli/structured-json.ts
|
|
68180
|
+
init_helpers();
|
|
68181
|
+
var STRUCTURED_PAGE_MAX_ROWS = 1000;
|
|
68182
|
+
var STRUCTURED_ALL_MAX_ROWS = 1e5;
|
|
68183
|
+
var STRUCTURED_DEFAULT_MAX_BYTES = 32 * 1024;
|
|
68184
|
+
var STRUCTURED_FULL_MAX_BYTES = 64 * 1024;
|
|
68185
|
+
var STRUCTURED_ALL_MAX_BYTES = 64 * 1024 * 1024;
|
|
68186
|
+
var STRUCTURED_MIN_MAX_BYTES = 1024;
|
|
68187
|
+
function structuredMaxBytes(value, opts) {
|
|
68188
|
+
const fallback = opts.all ? STRUCTURED_ALL_MAX_BYTES : opts.detail === "full" ? STRUCTURED_FULL_MAX_BYTES : STRUCTURED_DEFAULT_MAX_BYTES;
|
|
68189
|
+
if (value === undefined)
|
|
68190
|
+
return fallback;
|
|
68191
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
68192
|
+
if (!Number.isInteger(parsed) || parsed < STRUCTURED_MIN_MAX_BYTES || parsed > STRUCTURED_ALL_MAX_BYTES) {
|
|
68193
|
+
throw new Error(`--max-bytes must be an integer from ${STRUCTURED_MIN_MAX_BYTES} to ${STRUCTURED_ALL_MAX_BYTES}`);
|
|
68194
|
+
}
|
|
68195
|
+
return parsed;
|
|
68196
|
+
}
|
|
68197
|
+
function structuredPageLimit(value, fallback) {
|
|
68198
|
+
const parsed = value === undefined ? fallback : Number(value);
|
|
68199
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > STRUCTURED_PAGE_MAX_ROWS) {
|
|
68200
|
+
throw new Error(`--limit must be an integer from 1 to ${STRUCTURED_PAGE_MAX_ROWS}`);
|
|
68201
|
+
}
|
|
68202
|
+
return parsed;
|
|
68203
|
+
}
|
|
68204
|
+
function compactMemory(memory, opts = {}) {
|
|
68205
|
+
const value = truncateText(memory.summary || memory.value, 240);
|
|
68206
|
+
return {
|
|
68207
|
+
id: memory.id,
|
|
68208
|
+
key: memory.key,
|
|
68209
|
+
value,
|
|
68210
|
+
scope: memory.scope,
|
|
68211
|
+
category: memory.category,
|
|
68212
|
+
importance: memory.importance,
|
|
68213
|
+
status: memory.status,
|
|
68214
|
+
pinned: memory.pinned,
|
|
68215
|
+
...Array.isArray(memory.tags) && memory.tags.length ? { tags: memory.tags.slice(0, 10) } : {},
|
|
68216
|
+
...memory.agent_id ? { agent_id: memory.agent_id } : {},
|
|
68217
|
+
...memory.project_id ? { project_id: memory.project_id } : {},
|
|
68218
|
+
...memory.session_id ? { session_id: memory.session_id } : {},
|
|
68219
|
+
...opts.history && memory.accessed_at ? { accessed_at: memory.accessed_at } : {},
|
|
68220
|
+
updated_at: memory.updated_at
|
|
68221
|
+
};
|
|
68222
|
+
}
|
|
68223
|
+
function compactProject(project) {
|
|
68224
|
+
return {
|
|
68225
|
+
id: project.id,
|
|
68226
|
+
name: project.name,
|
|
68227
|
+
path: truncateText(project.path, 240),
|
|
68228
|
+
...project.description ? { description: truncateText(project.description, 240) } : {},
|
|
68229
|
+
...project.memory_prefix ? { memory_prefix: project.memory_prefix } : {},
|
|
68230
|
+
updated_at: project.updated_at
|
|
68231
|
+
};
|
|
68232
|
+
}
|
|
68233
|
+
function compactAgent(agent) {
|
|
68234
|
+
return {
|
|
68235
|
+
id: agent.id,
|
|
68236
|
+
name: agent.name,
|
|
68237
|
+
role: agent.role || "agent",
|
|
68238
|
+
...agent.description ? { description: truncateText(agent.description, 240) } : {},
|
|
68239
|
+
...agent.active_project_id ? { active_project_id: agent.active_project_id } : {},
|
|
68240
|
+
last_seen_at: agent.last_seen_at
|
|
68241
|
+
};
|
|
68242
|
+
}
|
|
68243
|
+
function compactSearchResult(result) {
|
|
68244
|
+
return {
|
|
68245
|
+
memory: compactMemory(result.memory),
|
|
68246
|
+
score: result.score,
|
|
68247
|
+
match_type: result.match_type,
|
|
68248
|
+
...result.confidence !== undefined ? { confidence: result.confidence } : {},
|
|
68249
|
+
...result.highlights?.length ? { highlights: result.highlights.slice(0, 3).map((highlight) => ({
|
|
68250
|
+
field: highlight.field,
|
|
68251
|
+
snippet: truncateText(highlight.snippet, 240)
|
|
68252
|
+
})) } : {}
|
|
68253
|
+
};
|
|
68254
|
+
}
|
|
68255
|
+
function makeEnvelope(args, items, byteTruncated, omittedFromPage) {
|
|
68256
|
+
const hasMore = byteTruncated || args.sourceHasMore;
|
|
68257
|
+
const complete = args.offset === 0 && !hasMore;
|
|
68258
|
+
const nextCursor = hasMore ? args.offset + items.length : null;
|
|
68259
|
+
const envelope = {
|
|
68260
|
+
[args.collection]: items,
|
|
68261
|
+
_meta: {
|
|
68262
|
+
receipt: args.receipt,
|
|
68263
|
+
count: items.length,
|
|
68264
|
+
limit: args.all ? null : args.limit,
|
|
68265
|
+
offset: args.offset,
|
|
68266
|
+
next_cursor: nextCursor,
|
|
68267
|
+
has_more: hasMore,
|
|
68268
|
+
complete,
|
|
68269
|
+
all: args.all,
|
|
68270
|
+
detail: args.detail,
|
|
68271
|
+
max_rows: args.all ? STRUCTURED_ALL_MAX_ROWS : STRUCTURED_PAGE_MAX_ROWS,
|
|
68272
|
+
max_bytes: args.maxBytes,
|
|
68273
|
+
response_bytes: 0,
|
|
68274
|
+
truncated: hasMore,
|
|
68275
|
+
truncation_reason: byteTruncated ? "max_bytes" : args.sourceHasMore ? "limit" : null,
|
|
68276
|
+
omitted_from_page: omittedFromPage,
|
|
68277
|
+
next_arguments: nextCursor === null ? null : {
|
|
68278
|
+
cursor: nextCursor,
|
|
68279
|
+
limit: args.limit,
|
|
68280
|
+
...args.includeDetailInNextArguments !== false && args.detail === "full" ? { full: true } : {},
|
|
68281
|
+
max_bytes: args.maxBytes,
|
|
68282
|
+
...args.nextArguments
|
|
68283
|
+
},
|
|
68284
|
+
continuation_scope: "unchanged_snapshot"
|
|
68285
|
+
}
|
|
68286
|
+
};
|
|
68287
|
+
for (let attempt = 0;attempt < 8; attempt += 1) {
|
|
68288
|
+
const bytes = Buffer.byteLength(`${JSON.stringify(envelope)}
|
|
68289
|
+
`);
|
|
68290
|
+
const meta = envelope["_meta"];
|
|
68291
|
+
if (bytes === meta.response_bytes)
|
|
68292
|
+
break;
|
|
68293
|
+
meta.response_bytes = bytes;
|
|
68294
|
+
}
|
|
68295
|
+
return envelope;
|
|
68296
|
+
}
|
|
68297
|
+
function structuredCollectionOutput(args) {
|
|
68298
|
+
const complete = makeEnvelope(args, args.items, false, 0);
|
|
68299
|
+
const completeText = `${JSON.stringify(complete)}
|
|
68300
|
+
`;
|
|
68301
|
+
if (Buffer.byteLength(completeText) <= args.maxBytes)
|
|
68302
|
+
return completeText;
|
|
68303
|
+
if (args.all) {
|
|
68304
|
+
throw new Error(`Exhaustive structured output exceeds the hard safety limit of ${args.maxBytes} bytes; use paginated JSON output or raise --max-bytes explicitly`);
|
|
68305
|
+
}
|
|
68306
|
+
for (let count = args.items.length - 1;count >= 0; count -= 1) {
|
|
68307
|
+
const envelope = makeEnvelope({ ...args, sourceHasMore: true }, args.items.slice(0, count), true, args.items.length - count);
|
|
68308
|
+
const text = `${JSON.stringify(envelope)}
|
|
68309
|
+
`;
|
|
68310
|
+
if (Buffer.byteLength(text) > args.maxBytes)
|
|
68311
|
+
continue;
|
|
68312
|
+
if (count === 0 && args.items.length > 0) {
|
|
68313
|
+
throw new Error(`One structured row exceeds --max-bytes=${args.maxBytes}; use compact detail or raise --max-bytes`);
|
|
68314
|
+
}
|
|
68315
|
+
return text;
|
|
68316
|
+
}
|
|
68317
|
+
throw new Error(`Structured output metadata exceeds --max-bytes=${args.maxBytes}`);
|
|
68318
|
+
}
|
|
68319
|
+
|
|
68320
|
+
// src/cli/commands/memory-cmd-search.ts
|
|
66913
68321
|
function registerSearchCommand(program2) {
|
|
66914
68322
|
const handleError = makeHandleError(program2);
|
|
66915
|
-
program2.command("search <query>").description("Full-text search across memories").option("--scope <scope>", "Scope filter").option("-c, --category <cat>", "Category filter").option("--tags <tags>", "Comma-separated tags filter").option("--project <path>", "Project filter (path or name)").option("--agent <name>", "Agent filter").option("--session <id>", "Session ID filter").option("--limit <n>", "Max results", parseInt).option("--offset <n>", "Offset for pagination", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--format <fmt>", "Output format: compact (default), json, csv, yaml").option("--verbose", "Show match highlights and wider snippets").option("--history", "Show recent search queries instead of searching").option("--popular", "Show most popular search queries").action((query, opts) => {
|
|
68323
|
+
program2.command("search <query>").description("Full-text search across memories").option("--scope <scope>", "Scope filter").option("-c, --category <cat>", "Category filter").option("--tags <tags>", "Comma-separated tags filter").option("--project <path>", "Project filter (path or name)").option("--agent <name>", "Agent filter").option("--session <id>", "Session ID filter").option("--limit <n>", "Max results", parseInt).option("--offset <n>", "Offset for pagination", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--format <fmt>", "Output format: compact (default), json, csv, yaml").option("--verbose", "Show match highlights and wider snippets").option("--all", `Exhaust matching results in one explicit JSON receipt (hard max: ${STRUCTURED_ALL_MAX_ROWS})`).option("--full", "Return full legacy search-result objects in the bounded JSON receipt").option("--max-bytes <n>", "JSON response byte ceiling", parseInt).option("--history", "Show recent search queries instead of searching").option("--popular", "Show most popular search queries").action((query, opts) => {
|
|
66916
68324
|
try {
|
|
66917
68325
|
const fmt = getOutputFormat(program2, opts.format);
|
|
66918
68326
|
const isStructured = fmt === "json" || fmt === "csv" || fmt === "yaml";
|
|
66919
|
-
const
|
|
66920
|
-
const
|
|
68327
|
+
const jsonMode = fmt === "json";
|
|
68328
|
+
const all = Boolean(opts.all);
|
|
68329
|
+
const detail = opts.full ? "full" : "compact";
|
|
68330
|
+
if (!jsonMode && (all || opts.full || opts.maxBytes !== undefined)) {
|
|
68331
|
+
throw new Error("--all, --full, and --max-bytes require JSON output");
|
|
68332
|
+
}
|
|
68333
|
+
if (all && opts.limit !== undefined)
|
|
68334
|
+
throw new Error("--all cannot be combined with --limit");
|
|
68335
|
+
const limit = jsonMode ? structuredPageLimit(opts.limit, 20) : positiveIntOrDefault(opts.limit, isStructured ? 20 : DEFAULT_SEARCH_LIMIT);
|
|
68336
|
+
const offset = cursorOrOffset(opts.cursor, opts.offset) ?? 0;
|
|
68337
|
+
if (all && offset !== 0)
|
|
68338
|
+
throw new Error("--all requires --cursor/--offset 0");
|
|
66921
68339
|
if (opts.history) {
|
|
66922
68340
|
const history = getSearchHistory(limit);
|
|
66923
68341
|
if (fmt === "json") {
|
|
@@ -66965,16 +68383,36 @@ function registerSearchCommand(program2) {
|
|
|
66965
68383
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
|
|
66966
68384
|
project_id: projectId,
|
|
66967
68385
|
agent_id: agentId,
|
|
66968
|
-
session_id: opts.session || globalOpts.session
|
|
66969
|
-
limit: isStructured ? limit : limit + 1,
|
|
66970
|
-
offset
|
|
68386
|
+
session_id: opts.session || globalOpts.session
|
|
66971
68387
|
};
|
|
66972
|
-
const
|
|
68388
|
+
const target = all ? STRUCTURED_ALL_MAX_ROWS : limit;
|
|
68389
|
+
const { rows: fetched, hasMore } = collectPagedRows((cursor, pageLimit) => {
|
|
68390
|
+
const rows = searchMemories(query, { ...filter, limit: pageLimit, offset: cursor });
|
|
68391
|
+
return {
|
|
68392
|
+
rows,
|
|
68393
|
+
has_more: rows.length < pageLimit ? false : undefined,
|
|
68394
|
+
next_cursor: cursor + rows.length
|
|
68395
|
+
};
|
|
68396
|
+
}, target, offset);
|
|
68397
|
+
if (all && hasMore) {
|
|
68398
|
+
throw new Error(`Exhaustive search output exceeds the hard safety limit of ${STRUCTURED_ALL_MAX_ROWS} rows; use paginated JSON output`);
|
|
68399
|
+
}
|
|
66973
68400
|
const sanitized = fetched.map(redactSearchResultForOutput);
|
|
66974
|
-
const hasMore = !isStructured && sanitized.length > limit;
|
|
66975
68401
|
const results = hasMore ? sanitized.slice(0, limit) : sanitized;
|
|
66976
|
-
if (
|
|
66977
|
-
|
|
68402
|
+
if (jsonMode) {
|
|
68403
|
+
const items = detail === "full" ? results.map((result) => ({ ...result })) : results.map(compactSearchResult);
|
|
68404
|
+
process.stdout.write(structuredCollectionOutput({
|
|
68405
|
+
collection: "results",
|
|
68406
|
+
receipt: "mementos.search.page.v1",
|
|
68407
|
+
items,
|
|
68408
|
+
offset,
|
|
68409
|
+
limit,
|
|
68410
|
+
sourceHasMore: hasMore,
|
|
68411
|
+
all,
|
|
68412
|
+
detail,
|
|
68413
|
+
maxBytes: structuredMaxBytes(opts.maxBytes, { all, detail }),
|
|
68414
|
+
nextArguments: { query }
|
|
68415
|
+
}));
|
|
66978
68416
|
return;
|
|
66979
68417
|
}
|
|
66980
68418
|
if (fmt === "csv") {
|
|
@@ -67027,18 +68465,20 @@ init_redact();
|
|
|
67027
68465
|
import chalk7 from "chalk";
|
|
67028
68466
|
function registerWhenToUseCommand(program2) {
|
|
67029
68467
|
const handleError = makeHandleError(program2);
|
|
67030
|
-
program2.command("when-to-use <memory_id>").description("Show the when_to_use guidance for a memory").action((memoryId) => {
|
|
68468
|
+
program2.command("when-to-use <memory_id>").description("Show the when_to_use guidance for a memory").action(async (memoryId) => {
|
|
67031
68469
|
try {
|
|
67032
68470
|
const globalOpts = program2.opts();
|
|
67033
68471
|
const resolvedId = resolveMemoryId(memoryId);
|
|
67034
68472
|
const memory = getMemory(resolvedId);
|
|
67035
68473
|
if (!memory) {
|
|
67036
68474
|
if (globalOpts.json) {
|
|
67037
|
-
|
|
68475
|
+
await outputJsonAndExit({ error: `Memory not found: ${memoryId}` }, 1);
|
|
67038
68476
|
} else {
|
|
67039
68477
|
console.error(chalk7.red(`Memory not found: ${memoryId}`));
|
|
67040
68478
|
}
|
|
67041
|
-
|
|
68479
|
+
if (!globalOpts.json)
|
|
68480
|
+
process.exit(1);
|
|
68481
|
+
return;
|
|
67042
68482
|
}
|
|
67043
68483
|
const safe = redactMemoryForOutput(memory);
|
|
67044
68484
|
const whenToUse = safe.when_to_use ?? null;
|
|
@@ -67171,7 +68611,7 @@ init_memories();
|
|
|
67171
68611
|
init_helpers();
|
|
67172
68612
|
import chalk10 from "chalk";
|
|
67173
68613
|
function registerRemoveCommand(program2) {
|
|
67174
|
-
program2.command("remove <nameOrId>").description("Remove/delete a memory by name or ID (alias for memory forget)").option("--agent <id>", "Agent ID").option("--scope <scope>", "Filter by scope (when looking up by key)").action((nameOrId, opts) => {
|
|
68614
|
+
program2.command("remove <nameOrId>").description("Remove/delete a memory by name or ID (alias for memory forget)").option("--agent <id>", "Agent ID").option("--scope <scope>", "Filter by scope (when looking up by key)").action(async (nameOrId, opts) => {
|
|
67175
68615
|
const globalOpts = program2.opts();
|
|
67176
68616
|
const agentId = opts.agent || globalOpts.agent;
|
|
67177
68617
|
let id = isApiMode() ? null : resolvePartialId(getDatabase(), "memories", nameOrId);
|
|
@@ -67185,11 +68625,13 @@ function registerRemoveCommand(program2) {
|
|
|
67185
68625
|
}
|
|
67186
68626
|
if (!id) {
|
|
67187
68627
|
if (globalOpts.json) {
|
|
67188
|
-
|
|
68628
|
+
await outputJsonAndExit({ error: `Memory not found: ${nameOrId}` }, 1);
|
|
67189
68629
|
} else {
|
|
67190
68630
|
console.error(chalk10.red(`Memory not found: ${nameOrId}`));
|
|
67191
68631
|
}
|
|
67192
|
-
|
|
68632
|
+
if (!globalOpts.json)
|
|
68633
|
+
process.exit(1);
|
|
68634
|
+
return;
|
|
67193
68635
|
}
|
|
67194
68636
|
const deleted = deleteMemory(id);
|
|
67195
68637
|
if (deleted) {
|
|
@@ -67200,11 +68642,13 @@ function registerRemoveCommand(program2) {
|
|
|
67200
68642
|
}
|
|
67201
68643
|
} else {
|
|
67202
68644
|
if (globalOpts.json) {
|
|
67203
|
-
|
|
68645
|
+
await outputJsonAndExit({ error: `Memory not found: ${nameOrId}` }, 1);
|
|
67204
68646
|
} else {
|
|
67205
68647
|
console.error(chalk10.red(`Memory not found: ${nameOrId}`));
|
|
67206
68648
|
}
|
|
67207
|
-
|
|
68649
|
+
if (!globalOpts.json)
|
|
68650
|
+
process.exit(1);
|
|
68651
|
+
return;
|
|
67208
68652
|
}
|
|
67209
68653
|
});
|
|
67210
68654
|
}
|
|
@@ -67235,7 +68679,7 @@ var RECALL_EXIT_FUZZY = 2;
|
|
|
67235
68679
|
// src/cli/commands/memory-cmd-recall.ts
|
|
67236
68680
|
function registerRecallCommand(program2) {
|
|
67237
68681
|
const handleError = makeHandleError(program2);
|
|
67238
|
-
program2.command("recall <key>").alias("get").description("Recall a memory by exact key (use --fuzzy to fall back to the nearest match)").option("--scope <scope>", "Scope filter").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").option("--fuzzy", "If the exact key is absent, return the nearest match instead (exits 2)").action((key, opts) => {
|
|
68682
|
+
program2.command("recall <key>").alias("get").description("Recall a memory by exact key (use --fuzzy to fall back to the nearest match)").option("--scope <scope>", "Scope filter").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").option("--fuzzy", "If the exact key is absent, return the nearest match instead (exits 2)").action(async (key, opts) => {
|
|
67239
68683
|
try {
|
|
67240
68684
|
const globalOpts = program2.opts();
|
|
67241
68685
|
const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
|
|
@@ -67268,28 +68712,32 @@ function registerRecallCommand(program2) {
|
|
|
67268
68712
|
const safeBest = redactSearchResultForOutput(results[0]);
|
|
67269
68713
|
touchMemory(safeBest.memory.id);
|
|
67270
68714
|
if (globalOpts.json) {
|
|
67271
|
-
|
|
68715
|
+
await outputJsonAndExit({
|
|
67272
68716
|
fuzzy_match: true,
|
|
67273
68717
|
requested_key: key,
|
|
67274
68718
|
returned_key: safeBest.memory.key,
|
|
67275
68719
|
score: safeBest.score,
|
|
67276
68720
|
match_type: safeBest.match_type,
|
|
67277
68721
|
memory: safeBest.memory
|
|
67278
|
-
});
|
|
68722
|
+
}, RECALL_EXIT_FUZZY);
|
|
67279
68723
|
} else {
|
|
67280
68724
|
console.error(chalk11.yellow(`No memory with key "${key}". Showing the nearest match "${safeBest.memory.key}" ` + `(score: ${safeBest.score.toFixed(2)}, match: ${safeBest.match_type}) \u2014 this is a DIFFERENT record.`));
|
|
67281
68725
|
console.log(formatMemoryDetail(safeBest.memory));
|
|
67282
68726
|
}
|
|
67283
|
-
|
|
68727
|
+
if (!globalOpts.json)
|
|
68728
|
+
process.exit(RECALL_EXIT_FUZZY);
|
|
68729
|
+
return;
|
|
67284
68730
|
}
|
|
67285
68731
|
}
|
|
67286
68732
|
const message = opts.fuzzy ? `No memory found for key: ${key}` : `No memory found for key: ${key} (exact match; pass --fuzzy to return the nearest record instead)`;
|
|
67287
68733
|
if (globalOpts.json) {
|
|
67288
|
-
|
|
68734
|
+
await outputJsonAndExit({ error: message, requested_key: key }, RECALL_EXIT_NOT_FOUND);
|
|
67289
68735
|
} else {
|
|
67290
68736
|
console.error(chalk11.yellow(message));
|
|
67291
68737
|
}
|
|
67292
|
-
|
|
68738
|
+
if (!globalOpts.json)
|
|
68739
|
+
process.exit(RECALL_EXIT_NOT_FOUND);
|
|
68740
|
+
return;
|
|
67293
68741
|
} catch (e) {
|
|
67294
68742
|
handleError(e);
|
|
67295
68743
|
}
|
|
@@ -67303,16 +68751,164 @@ init_redact();
|
|
|
67303
68751
|
init_helpers();
|
|
67304
68752
|
import chalk12 from "chalk";
|
|
67305
68753
|
import { resolve as resolve8 } from "path";
|
|
68754
|
+
var STRUCTURED_PAGE_MAX_ROWS2 = 1000;
|
|
68755
|
+
var STRUCTURED_ALL_MAX_ROWS2 = 1e5;
|
|
68756
|
+
var STRUCTURED_DEFAULT_MAX_BYTES2 = 32 * 1024;
|
|
68757
|
+
var STRUCTURED_FULL_MAX_BYTES2 = 64 * 1024;
|
|
68758
|
+
var STRUCTURED_ALL_MAX_BYTES2 = 64 * 1024 * 1024;
|
|
68759
|
+
var STRUCTURED_MIN_MAX_BYTES2 = 1024;
|
|
68760
|
+
function structuredMaxBytes2(value, opts) {
|
|
68761
|
+
const fallback = opts.all ? STRUCTURED_ALL_MAX_BYTES2 : opts.detail === "full" ? STRUCTURED_FULL_MAX_BYTES2 : STRUCTURED_DEFAULT_MAX_BYTES2;
|
|
68762
|
+
if (value === undefined)
|
|
68763
|
+
return fallback;
|
|
68764
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
68765
|
+
if (!Number.isInteger(parsed) || parsed < STRUCTURED_MIN_MAX_BYTES2 || parsed > STRUCTURED_ALL_MAX_BYTES2) {
|
|
68766
|
+
throw new Error(`--max-bytes must be an integer from ${STRUCTURED_MIN_MAX_BYTES2} to ${STRUCTURED_ALL_MAX_BYTES2}`);
|
|
68767
|
+
}
|
|
68768
|
+
return parsed;
|
|
68769
|
+
}
|
|
68770
|
+
function compactMemoryForStructuredOutput(memory, opts = {}) {
|
|
68771
|
+
const value = truncateText(memory.value, 160);
|
|
68772
|
+
const summary = memory.summary ? truncateText(memory.summary, 160) : null;
|
|
68773
|
+
return {
|
|
68774
|
+
id: memory.id,
|
|
68775
|
+
key: memory.key,
|
|
68776
|
+
value,
|
|
68777
|
+
...summary ? { summary } : {},
|
|
68778
|
+
scope: memory.scope,
|
|
68779
|
+
category: memory.category,
|
|
68780
|
+
importance: memory.importance,
|
|
68781
|
+
status: memory.status,
|
|
68782
|
+
pinned: memory.pinned,
|
|
68783
|
+
...Array.isArray(memory.tags) && memory.tags.length ? { tags: memory.tags.slice(0, 10) } : {},
|
|
68784
|
+
...memory.agent_id ? { agent_id: memory.agent_id } : {},
|
|
68785
|
+
...memory.project_id ? { project_id: memory.project_id } : {},
|
|
68786
|
+
...memory.session_id ? { session_id: memory.session_id } : {},
|
|
68787
|
+
...opts.history && memory.accessed_at ? { accessed_at: memory.accessed_at } : {},
|
|
68788
|
+
updated_at: memory.updated_at
|
|
68789
|
+
};
|
|
68790
|
+
}
|
|
68791
|
+
function withResponseBytes(envelope) {
|
|
68792
|
+
for (let attempt = 0;attempt < 8; attempt += 1) {
|
|
68793
|
+
const bytes = Buffer.byteLength(`${JSON.stringify(envelope)}
|
|
68794
|
+
`);
|
|
68795
|
+
if (bytes === envelope._meta.response_bytes)
|
|
68796
|
+
return envelope;
|
|
68797
|
+
envelope._meta.response_bytes = bytes;
|
|
68798
|
+
}
|
|
68799
|
+
return envelope;
|
|
68800
|
+
}
|
|
68801
|
+
function makeStructuredEnvelope(args) {
|
|
68802
|
+
const hasMore = args.byteTruncated || args.sourceHasMore;
|
|
68803
|
+
const complete = args.offset === 0 && !hasMore;
|
|
68804
|
+
const nextCursor = hasMore ? args.offset + args.memories.length : null;
|
|
68805
|
+
const truncationReason = args.byteTruncated ? "max_bytes" : args.sourceHasMore ? "limit" : args.offset > 0 ? "cursor" : null;
|
|
68806
|
+
return withResponseBytes({
|
|
68807
|
+
memories: args.memories,
|
|
68808
|
+
_meta: {
|
|
68809
|
+
receipt: args.receipt,
|
|
68810
|
+
count: args.memories.length,
|
|
68811
|
+
limit: args.all ? null : args.limit,
|
|
68812
|
+
offset: args.offset,
|
|
68813
|
+
next_cursor: nextCursor,
|
|
68814
|
+
has_more: hasMore,
|
|
68815
|
+
complete,
|
|
68816
|
+
all: args.all,
|
|
68817
|
+
detail: args.detail,
|
|
68818
|
+
max_rows: args.all ? STRUCTURED_ALL_MAX_ROWS2 : STRUCTURED_PAGE_MAX_ROWS2,
|
|
68819
|
+
max_bytes: args.maxBytes,
|
|
68820
|
+
response_bytes: 0,
|
|
68821
|
+
truncated: !complete,
|
|
68822
|
+
truncation_reason: truncationReason,
|
|
68823
|
+
omitted_from_page: args.omittedFromPage,
|
|
68824
|
+
next_arguments: nextCursor === null ? null : {
|
|
68825
|
+
cursor: nextCursor,
|
|
68826
|
+
limit: args.limit,
|
|
68827
|
+
agent_json: true,
|
|
68828
|
+
...args.detail === "full" ? { full: true } : {},
|
|
68829
|
+
max_bytes: args.maxBytes
|
|
68830
|
+
},
|
|
68831
|
+
continuation_scope: "unchanged_snapshot"
|
|
68832
|
+
}
|
|
68833
|
+
});
|
|
68834
|
+
}
|
|
68835
|
+
function structuredMemoryOutput(args) {
|
|
68836
|
+
const projected = args.detail === "full" ? args.memories : args.memories.map((memory) => compactMemoryForStructuredOutput(memory, { history: args.history }));
|
|
68837
|
+
const completeEnvelope = makeStructuredEnvelope({
|
|
68838
|
+
memories: projected,
|
|
68839
|
+
receipt: args.receipt,
|
|
68840
|
+
offset: args.offset,
|
|
68841
|
+
limit: args.limit,
|
|
68842
|
+
sourceHasMore: args.sourceHasMore,
|
|
68843
|
+
all: args.all,
|
|
68844
|
+
detail: args.detail,
|
|
68845
|
+
maxBytes: args.maxBytes,
|
|
68846
|
+
byteTruncated: false,
|
|
68847
|
+
omittedFromPage: 0
|
|
68848
|
+
});
|
|
68849
|
+
const completeText = `${JSON.stringify(completeEnvelope)}
|
|
68850
|
+
`;
|
|
68851
|
+
if (Buffer.byteLength(completeText) <= args.maxBytes)
|
|
68852
|
+
return completeText;
|
|
68853
|
+
if (args.all) {
|
|
68854
|
+
throw new Error(`Exhaustive structured output exceeds the hard safety limit of ${args.maxBytes} bytes; use paginated JSON output instead`);
|
|
68855
|
+
}
|
|
68856
|
+
for (let count = projected.length - 1;count >= 0; count -= 1) {
|
|
68857
|
+
const envelope = makeStructuredEnvelope({
|
|
68858
|
+
memories: projected.slice(0, count),
|
|
68859
|
+
receipt: args.receipt,
|
|
68860
|
+
offset: args.offset,
|
|
68861
|
+
limit: args.limit,
|
|
68862
|
+
sourceHasMore: true,
|
|
68863
|
+
all: false,
|
|
68864
|
+
detail: args.detail,
|
|
68865
|
+
maxBytes: args.maxBytes,
|
|
68866
|
+
byteTruncated: true,
|
|
68867
|
+
omittedFromPage: projected.length - count
|
|
68868
|
+
});
|
|
68869
|
+
const text = `${JSON.stringify(envelope)}
|
|
68870
|
+
`;
|
|
68871
|
+
if (Buffer.byteLength(text) > args.maxBytes)
|
|
68872
|
+
continue;
|
|
68873
|
+
if (count === 0 && projected.length > 0) {
|
|
68874
|
+
throw new Error(`One structured memory row exceeds --max-bytes=${args.maxBytes}; use compact detail, a larger --max-bytes value, or mementos show <id>`);
|
|
68875
|
+
}
|
|
68876
|
+
return text;
|
|
68877
|
+
}
|
|
68878
|
+
throw new Error(`Structured output metadata exceeds --max-bytes=${args.maxBytes}`);
|
|
68879
|
+
}
|
|
68880
|
+
function assertReceiptFlags(opts, receiptMode, requestedFormat) {
|
|
68881
|
+
if (!receiptMode && (opts.all || opts.full || opts.maxBytes !== undefined)) {
|
|
68882
|
+
throw new Error("--all, --full, and --max-bytes require JSON receipt mode (--json, --format json, or --agent-json)");
|
|
68883
|
+
}
|
|
68884
|
+
if (receiptMode && requestedFormat !== undefined && requestedFormat !== "json") {
|
|
68885
|
+
throw new Error("--agent-json cannot be combined with a non-JSON --format");
|
|
68886
|
+
}
|
|
68887
|
+
if (opts.all && opts.limit !== undefined) {
|
|
68888
|
+
throw new Error("--all cannot be combined with --limit");
|
|
68889
|
+
}
|
|
68890
|
+
}
|
|
67306
68891
|
function registerListCommand(program2) {
|
|
67307
68892
|
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>",
|
|
68893
|
+
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_ROWS2})`, 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_ROWS2} 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_BYTES2})`, parseInt).action((opts) => {
|
|
67309
68894
|
try {
|
|
67310
68895
|
const globalOpts = program2.opts();
|
|
68896
|
+
const requestedFormat = opts.format ?? globalOpts.format;
|
|
67311
68897
|
const fmt = getOutputFormat(program2, opts.format);
|
|
68898
|
+
const receiptMode = Boolean(opts.agentJson || fmt === "json");
|
|
67312
68899
|
const isStructured = fmt === "json" || fmt === "csv" || fmt === "yaml";
|
|
68900
|
+
assertReceiptFlags(opts, receiptMode, requestedFormat);
|
|
67313
68901
|
const requestedLimit = opts.limit;
|
|
67314
|
-
const
|
|
68902
|
+
const all = Boolean(opts.all);
|
|
68903
|
+
const detail = opts.full ? "full" : "compact";
|
|
68904
|
+
const limit = requestedLimit === undefined ? receiptMode ? DEFAULT_COMPACT_LIMIT : isStructured ? undefined : DEFAULT_COMPACT_LIMIT : positiveIntOrDefault(requestedLimit, receiptMode ? DEFAULT_COMPACT_LIMIT : isStructured ? 50 : DEFAULT_COMPACT_LIMIT);
|
|
68905
|
+
if (receiptMode && limit !== undefined && limit > STRUCTURED_PAGE_MAX_ROWS2) {
|
|
68906
|
+
throw new Error(`--limit cannot exceed the agent JSON page ceiling of ${STRUCTURED_PAGE_MAX_ROWS2}; use --all for a bounded exhaustive read`);
|
|
68907
|
+
}
|
|
67315
68908
|
const offset = cursorOrOffset(opts.cursor, opts.offset) ?? 0;
|
|
68909
|
+
if (all && offset !== 0) {
|
|
68910
|
+
throw new Error("--all requires --cursor/--offset 0");
|
|
68911
|
+
}
|
|
67316
68912
|
const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
|
|
67317
68913
|
const projectPath = opts.project || globalOpts.project;
|
|
67318
68914
|
let projectId;
|
|
@@ -67334,6 +68930,7 @@ function registerListCommand(program2) {
|
|
|
67334
68930
|
status: opts.status,
|
|
67335
68931
|
session_id: opts.session || globalOpts.session
|
|
67336
68932
|
};
|
|
68933
|
+
const target = all ? STRUCTURED_ALL_MAX_ROWS2 : limit;
|
|
67337
68934
|
const { rows: collected, hasMore } = collectPagedRows((cursor, pageLimit) => {
|
|
67338
68935
|
const page = listMemoriesPage({
|
|
67339
68936
|
...filter,
|
|
@@ -67345,9 +68942,25 @@ function registerListCommand(program2) {
|
|
|
67345
68942
|
has_more: page.has_more,
|
|
67346
68943
|
next_cursor: page.next_cursor
|
|
67347
68944
|
};
|
|
67348
|
-
},
|
|
67349
|
-
|
|
68945
|
+
}, target, offset);
|
|
68946
|
+
if (all && hasMore) {
|
|
68947
|
+
throw new Error(`Exhaustive structured output exceeds the hard safety limit of ${STRUCTURED_ALL_MAX_ROWS2} rows; use paginated JSON output instead`);
|
|
68948
|
+
}
|
|
68949
|
+
const memories = target === undefined ? collected : collected.slice(0, target);
|
|
67350
68950
|
const sanitized = memories.map(redactMemoryForOutput);
|
|
68951
|
+
if (receiptMode) {
|
|
68952
|
+
process.stdout.write(structuredMemoryOutput({
|
|
68953
|
+
memories: sanitized,
|
|
68954
|
+
receipt: "mementos.list.page.v1",
|
|
68955
|
+
offset,
|
|
68956
|
+
limit: limit ?? DEFAULT_COMPACT_LIMIT,
|
|
68957
|
+
sourceHasMore: hasMore,
|
|
68958
|
+
all,
|
|
68959
|
+
detail,
|
|
68960
|
+
maxBytes: structuredMaxBytes2(opts.maxBytes, { all, detail })
|
|
68961
|
+
}));
|
|
68962
|
+
return;
|
|
68963
|
+
}
|
|
67351
68964
|
if (fmt === "json") {
|
|
67352
68965
|
outputJson(sanitized);
|
|
67353
68966
|
return;
|
|
@@ -67381,7 +68994,7 @@ function registerListCommand(program2) {
|
|
|
67381
68994
|
offset,
|
|
67382
68995
|
hasMore,
|
|
67383
68996
|
command: "mementos list",
|
|
67384
|
-
detailHint: "use mementos show <id> for full details
|
|
68997
|
+
detailHint: "use mementos show <id> for full details; JSON output is a bounded receipt, with --full/--all as explicit compatibility escapes"
|
|
67385
68998
|
});
|
|
67386
68999
|
} catch (e) {
|
|
67387
69000
|
handleError(e);
|
|
@@ -67808,13 +69421,33 @@ init_helpers();
|
|
|
67808
69421
|
import chalk16 from "chalk";
|
|
67809
69422
|
function registerHistoryCommand(program2) {
|
|
67810
69423
|
const handleError = makeHandleError(program2);
|
|
67811
|
-
program2.command("history").description("List memories sorted by most recently accessed").option("--limit <n>",
|
|
69424
|
+
program2.command("history").description("List memories sorted by most recently accessed").option("--limit <n>", `Max results (agent JSON page max: ${STRUCTURED_PAGE_MAX_ROWS2})`, 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_ROWS2} 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_BYTES2})`, parseInt).action((opts) => {
|
|
67812
69425
|
try {
|
|
67813
69426
|
const globalOpts = program2.opts();
|
|
67814
|
-
const
|
|
69427
|
+
const format = getOutputFormat(program2);
|
|
69428
|
+
const receiptMode = Boolean(opts.agentJson);
|
|
69429
|
+
const isJson = format === "json";
|
|
69430
|
+
if (!receiptMode && (opts.all || opts.full || opts.maxBytes !== undefined)) {
|
|
69431
|
+
throw new Error("--all, --full, and --max-bytes require --agent-json receipt mode");
|
|
69432
|
+
}
|
|
69433
|
+
if (receiptMode && globalOpts.format !== undefined && globalOpts.format !== "json") {
|
|
69434
|
+
throw new Error("--agent-json cannot be combined with a non-JSON --format");
|
|
69435
|
+
}
|
|
69436
|
+
if (opts.all && opts.limit !== undefined) {
|
|
69437
|
+
throw new Error("--all cannot be combined with --limit");
|
|
69438
|
+
}
|
|
67815
69439
|
const requestedLimit = opts.limit;
|
|
67816
|
-
const
|
|
69440
|
+
const all = Boolean(opts.all);
|
|
69441
|
+
const detail = opts.full ? "full" : "compact";
|
|
69442
|
+
const limit = requestedLimit === undefined ? receiptMode ? DEFAULT_SEARCH_LIMIT : isJson ? undefined : DEFAULT_SEARCH_LIMIT : positiveIntOrDefault(requestedLimit, receiptMode ? DEFAULT_SEARCH_LIMIT : isJson ? 20 : DEFAULT_SEARCH_LIMIT);
|
|
69443
|
+
if (receiptMode && limit !== undefined && limit > STRUCTURED_PAGE_MAX_ROWS2) {
|
|
69444
|
+
throw new Error(`--limit cannot exceed the agent JSON page ceiling of ${STRUCTURED_PAGE_MAX_ROWS2}; use --all for a bounded exhaustive read`);
|
|
69445
|
+
}
|
|
67817
69446
|
const offset = cursorOrOffset(opts.cursor, opts.offset) ?? 0;
|
|
69447
|
+
if (all && offset !== 0) {
|
|
69448
|
+
throw new Error("--all requires --cursor/--offset 0");
|
|
69449
|
+
}
|
|
69450
|
+
const target = all ? STRUCTURED_ALL_MAX_ROWS2 : limit;
|
|
67818
69451
|
const { rows: collected, hasMore } = collectPagedRows((cursor, pageLimit) => {
|
|
67819
69452
|
const page = listMemoryHistoryPage({ limit: pageLimit, offset: cursor });
|
|
67820
69453
|
return {
|
|
@@ -67822,10 +69455,27 @@ function registerHistoryCommand(program2) {
|
|
|
67822
69455
|
has_more: page.has_more,
|
|
67823
69456
|
next_cursor: page.next_cursor
|
|
67824
69457
|
};
|
|
67825
|
-
},
|
|
67826
|
-
|
|
69458
|
+
}, target, offset);
|
|
69459
|
+
if (all && hasMore) {
|
|
69460
|
+
throw new Error(`Exhaustive structured output exceeds the hard safety limit of ${STRUCTURED_ALL_MAX_ROWS2} rows; use paginated JSON output instead`);
|
|
69461
|
+
}
|
|
69462
|
+
const memories = target === undefined ? collected : collected.slice(0, target);
|
|
67827
69463
|
const sanitized = memories.map(redactMemoryForOutput);
|
|
67828
|
-
if (
|
|
69464
|
+
if (receiptMode) {
|
|
69465
|
+
process.stdout.write(structuredMemoryOutput({
|
|
69466
|
+
memories: sanitized,
|
|
69467
|
+
receipt: "mementos.history.page.v1",
|
|
69468
|
+
offset,
|
|
69469
|
+
limit: limit ?? DEFAULT_SEARCH_LIMIT,
|
|
69470
|
+
sourceHasMore: hasMore,
|
|
69471
|
+
all,
|
|
69472
|
+
detail,
|
|
69473
|
+
maxBytes: structuredMaxBytes2(opts.maxBytes, { all, detail }),
|
|
69474
|
+
history: true
|
|
69475
|
+
}));
|
|
69476
|
+
return;
|
|
69477
|
+
}
|
|
69478
|
+
if (isJson) {
|
|
67829
69479
|
outputJson(sanitized);
|
|
67830
69480
|
return;
|
|
67831
69481
|
}
|
|
@@ -67848,7 +69498,7 @@ function registerHistoryCommand(program2) {
|
|
|
67848
69498
|
offset,
|
|
67849
69499
|
hasMore,
|
|
67850
69500
|
command: "mementos history",
|
|
67851
|
-
detailHint: "use mementos show <id> for full details or --json for
|
|
69501
|
+
detailHint: "use mementos show <id> for full details, --json for the compatible full array, or --agent-json for a bounded receipt"
|
|
67852
69502
|
});
|
|
67853
69503
|
} catch (e) {
|
|
67854
69504
|
handleError(e);
|
|
@@ -67863,13 +69513,16 @@ import chalk17 from "chalk";
|
|
|
67863
69513
|
import { resolve as resolve11 } from "path";
|
|
67864
69514
|
|
|
67865
69515
|
// src/lib/machine-visibility.ts
|
|
69516
|
+
init_api_mode();
|
|
67866
69517
|
function resolveVisibleMachineId(machineId, db) {
|
|
67867
69518
|
if (machineId !== undefined) {
|
|
67868
69519
|
return machineId;
|
|
67869
69520
|
}
|
|
67870
69521
|
try {
|
|
67871
69522
|
return getCurrentMachineId(db);
|
|
67872
|
-
} catch {
|
|
69523
|
+
} catch (error) {
|
|
69524
|
+
if (!db && isApiMode())
|
|
69525
|
+
throw error;
|
|
67873
69526
|
return null;
|
|
67874
69527
|
}
|
|
67875
69528
|
}
|
|
@@ -67900,13 +69553,16 @@ function registerContextCommand(program2) {
|
|
|
67900
69553
|
const categoriesRaw = opts.categories;
|
|
67901
69554
|
const categories = categoriesRaw ? categoriesRaw.split(",").map((c) => c.trim()) : undefined;
|
|
67902
69555
|
const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
|
|
67903
|
-
const projectPath = opts.project
|
|
69556
|
+
const projectPath = opts.project ?? globalOpts.project;
|
|
67904
69557
|
const visibleMachineId = resolveVisibleMachineId(opts.machine);
|
|
67905
69558
|
let projectId;
|
|
67906
|
-
if (projectPath) {
|
|
69559
|
+
if (projectPath !== undefined) {
|
|
69560
|
+
if (!projectPath.trim())
|
|
69561
|
+
throw new Error("Project not found: empty project path");
|
|
67907
69562
|
const project = getProject(resolve11(projectPath));
|
|
67908
|
-
if (project)
|
|
67909
|
-
|
|
69563
|
+
if (!project)
|
|
69564
|
+
throw new Error(`Project not found: ${projectPath}`);
|
|
69565
|
+
projectId = project.id;
|
|
67910
69566
|
}
|
|
67911
69567
|
let memories;
|
|
67912
69568
|
if (query) {
|
|
@@ -67954,6 +69610,7 @@ function registerContextCommand(program2) {
|
|
|
67954
69610
|
...baseFilter,
|
|
67955
69611
|
scope: "private",
|
|
67956
69612
|
agent_id: agentId,
|
|
69613
|
+
project_id: projectId,
|
|
67957
69614
|
...visibleToMachineFilter(visibleMachineId)
|
|
67958
69615
|
}));
|
|
67959
69616
|
}
|
|
@@ -68022,7 +69679,7 @@ init_helpers();
|
|
|
68022
69679
|
import { resolve as resolve12 } from "path";
|
|
68023
69680
|
function registerExportCommand(program2) {
|
|
68024
69681
|
const handleError = makeHandleError(program2);
|
|
68025
|
-
program2.command("export").description("Export
|
|
69682
|
+
program2.command("export").description("Export a truthful full-detail JSON page; use --all for the exhaustive legacy array").option("--scope <scope>", "Scope filter").option("-c, --category <cat>", "Category filter").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").option("--limit <n>", "Page size (default: 100, maximum: 1000)", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--offset <n>", "Offset for pagination", parseInt).option("--max-bytes <n>", "Paginated response byte ceiling (default: 65536)", parseInt).option("--all", "Exhaust the complete query and emit the legacy full JSON array").action((opts) => {
|
|
68026
69683
|
try {
|
|
68027
69684
|
const globalOpts = program2.opts();
|
|
68028
69685
|
const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
|
|
@@ -68039,9 +69696,43 @@ function registerExportCommand(program2) {
|
|
|
68039
69696
|
agent_id: agentId,
|
|
68040
69697
|
project_id: projectId
|
|
68041
69698
|
};
|
|
68042
|
-
const
|
|
68043
|
-
const
|
|
68044
|
-
|
|
69699
|
+
const exhaustive = Boolean(opts.all);
|
|
69700
|
+
const offset = cursorOrOffset(opts.cursor, opts.offset) ?? 0;
|
|
69701
|
+
if (exhaustive && opts.limit !== undefined) {
|
|
69702
|
+
throw new Error("--all cannot be combined with --limit");
|
|
69703
|
+
}
|
|
69704
|
+
if (exhaustive && offset !== 0) {
|
|
69705
|
+
throw new Error("--all requires --cursor/--offset 0");
|
|
69706
|
+
}
|
|
69707
|
+
if (exhaustive && opts.maxBytes !== undefined) {
|
|
69708
|
+
throw new Error("--max-bytes applies to paginated exports; exhaustive --all is an explicit unbounded compatibility escape");
|
|
69709
|
+
}
|
|
69710
|
+
if (exhaustive) {
|
|
69711
|
+
const complete = listMemoriesBounded(filter, undefined).rows.map(redactMemoryForOutput);
|
|
69712
|
+
outputJson(complete);
|
|
69713
|
+
return;
|
|
69714
|
+
}
|
|
69715
|
+
const limit = structuredPageLimit(opts.limit, 100);
|
|
69716
|
+
const page = listMemoriesBounded({ ...filter, offset }, limit);
|
|
69717
|
+
const sanitized = page.rows.map(redactMemoryForOutput);
|
|
69718
|
+
process.stdout.write(structuredCollectionOutput({
|
|
69719
|
+
collection: "memories",
|
|
69720
|
+
receipt: "mementos.export.page.v1",
|
|
69721
|
+
items: sanitized,
|
|
69722
|
+
offset,
|
|
69723
|
+
limit,
|
|
69724
|
+
sourceHasMore: page.has_more,
|
|
69725
|
+
all: false,
|
|
69726
|
+
detail: "full",
|
|
69727
|
+
maxBytes: structuredMaxBytes(opts.maxBytes, { all: false, detail: "full" }),
|
|
69728
|
+
nextArguments: {
|
|
69729
|
+
...opts.scope ? { scope: opts.scope } : {},
|
|
69730
|
+
...opts.category ? { category: opts.category } : {},
|
|
69731
|
+
...agentId ? { agent: agentId } : {},
|
|
69732
|
+
...projectPath ? { project: projectPath } : {}
|
|
69733
|
+
},
|
|
69734
|
+
includeDetailInNextArguments: false
|
|
69735
|
+
}));
|
|
68045
69736
|
} catch (e) {
|
|
68046
69737
|
handleError(e);
|
|
68047
69738
|
}
|
|
@@ -68090,247 +69781,13 @@ function registerImportCommand(program2) {
|
|
|
68090
69781
|
}
|
|
68091
69782
|
|
|
68092
69783
|
// src/cli/commands/io-clean.ts
|
|
69784
|
+
init_config();
|
|
68093
69785
|
import chalk19 from "chalk";
|
|
68094
69786
|
|
|
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
69787
|
// src/lib/retention.ts
|
|
68332
69788
|
init_database();
|
|
68333
69789
|
init_memories();
|
|
69790
|
+
init_config();
|
|
68334
69791
|
function enforceQuotas(config, db) {
|
|
68335
69792
|
const d = db || getDatabase();
|
|
68336
69793
|
let totalEvicted = 0;
|
|
@@ -68438,11 +69895,108 @@ function registerCleanCommand(program2) {
|
|
|
68438
69895
|
|
|
68439
69896
|
// src/cli/commands/io-backup.ts
|
|
68440
69897
|
init_database();
|
|
69898
|
+
init_api_mode();
|
|
69899
|
+
init_memories();
|
|
68441
69900
|
init_paths();
|
|
68442
69901
|
init_helpers();
|
|
68443
69902
|
import chalk20 from "chalk";
|
|
68444
69903
|
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";
|
|
69904
|
+
import { existsSync as existsSync7, statSync, copyFileSync, mkdirSync as mkdirSync4, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
69905
|
+
function writeMemoriesBackupFile(dest, rows) {
|
|
69906
|
+
const { Database: Database3 } = __require("bun:sqlite");
|
|
69907
|
+
if (existsSync7(dest)) {
|
|
69908
|
+
unlinkSync3(dest);
|
|
69909
|
+
}
|
|
69910
|
+
const backupDb = new Database3(dest);
|
|
69911
|
+
try {
|
|
69912
|
+
backupDb.run(`
|
|
69913
|
+
CREATE TABLE IF NOT EXISTS _backup_carrier (
|
|
69914
|
+
format INTEGER NOT NULL
|
|
69915
|
+
)
|
|
69916
|
+
`);
|
|
69917
|
+
backupDb.run(`INSERT INTO _backup_carrier (format) VALUES (1)`);
|
|
69918
|
+
backupDb.run(`
|
|
69919
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
69920
|
+
id TEXT PRIMARY KEY,
|
|
69921
|
+
key TEXT NOT NULL,
|
|
69922
|
+
value TEXT NOT NULL,
|
|
69923
|
+
category TEXT,
|
|
69924
|
+
scope TEXT,
|
|
69925
|
+
summary TEXT,
|
|
69926
|
+
tags TEXT,
|
|
69927
|
+
importance INTEGER,
|
|
69928
|
+
source TEXT,
|
|
69929
|
+
status TEXT,
|
|
69930
|
+
pinned INTEGER,
|
|
69931
|
+
agent_id TEXT,
|
|
69932
|
+
project_id TEXT,
|
|
69933
|
+
session_id TEXT,
|
|
69934
|
+
machine_id TEXT,
|
|
69935
|
+
namespace TEXT,
|
|
69936
|
+
created_by_agent TEXT,
|
|
69937
|
+
when_to_use TEXT,
|
|
69938
|
+
sequence_group TEXT,
|
|
69939
|
+
sequence_order INTEGER,
|
|
69940
|
+
metadata TEXT,
|
|
69941
|
+
access_count INTEGER,
|
|
69942
|
+
version INTEGER,
|
|
69943
|
+
expires_at TEXT,
|
|
69944
|
+
valid_from TEXT,
|
|
69945
|
+
valid_until TEXT,
|
|
69946
|
+
ingested_at TEXT,
|
|
69947
|
+
created_at TEXT,
|
|
69948
|
+
updated_at TEXT,
|
|
69949
|
+
accessed_at TEXT
|
|
69950
|
+
)
|
|
69951
|
+
`);
|
|
69952
|
+
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)
|
|
69953
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
69954
|
+
const bind = (v) => {
|
|
69955
|
+
if (v === undefined || v === null)
|
|
69956
|
+
return null;
|
|
69957
|
+
return typeof v === "string" || typeof v === "number" ? v : JSON.stringify(v);
|
|
69958
|
+
};
|
|
69959
|
+
const boolBind = (v) => v === undefined || v === null ? null : v ? 1 : 0;
|
|
69960
|
+
for (const row of rows) {
|
|
69961
|
+
const params = [
|
|
69962
|
+
bind(row["id"]),
|
|
69963
|
+
bind(row["key"]),
|
|
69964
|
+
bind(row["value"]),
|
|
69965
|
+
bind(row["category"]),
|
|
69966
|
+
bind(row["scope"]),
|
|
69967
|
+
bind(row["summary"]),
|
|
69968
|
+
Array.isArray(row["tags"]) ? JSON.stringify(row["tags"]) : bind(row["tags"]),
|
|
69969
|
+
bind(row["importance"]),
|
|
69970
|
+
bind(row["source"]),
|
|
69971
|
+
bind(row["status"]),
|
|
69972
|
+
boolBind(row["pinned"]),
|
|
69973
|
+
bind(row["agent_id"]),
|
|
69974
|
+
bind(row["project_id"]),
|
|
69975
|
+
bind(row["session_id"]),
|
|
69976
|
+
bind(row["machine_id"]),
|
|
69977
|
+
bind(row["namespace"]),
|
|
69978
|
+
bind(row["created_by_agent"]),
|
|
69979
|
+
bind(row["when_to_use"]),
|
|
69980
|
+
bind(row["sequence_group"]),
|
|
69981
|
+
bind(row["sequence_order"]),
|
|
69982
|
+
bind(row["metadata"]),
|
|
69983
|
+
bind(row["access_count"]),
|
|
69984
|
+
bind(row["version"]),
|
|
69985
|
+
bind(row["expires_at"]),
|
|
69986
|
+
bind(row["valid_from"]),
|
|
69987
|
+
bind(row["valid_until"]),
|
|
69988
|
+
bind(row["ingested_at"]),
|
|
69989
|
+
bind(row["created_at"]),
|
|
69990
|
+
bind(row["updated_at"]),
|
|
69991
|
+
bind(row["accessed_at"])
|
|
69992
|
+
];
|
|
69993
|
+
insert.run(...params);
|
|
69994
|
+
}
|
|
69995
|
+
return rows.length;
|
|
69996
|
+
} finally {
|
|
69997
|
+
backupDb.close();
|
|
69998
|
+
}
|
|
69999
|
+
}
|
|
68446
70000
|
function registerBackupCommand(program2) {
|
|
68447
70001
|
const handleError = makeHandleError(program2);
|
|
68448
70002
|
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 +70045,6 @@ function registerBackupCommand(program2) {
|
|
|
68491
70045
|
}
|
|
68492
70046
|
return;
|
|
68493
70047
|
}
|
|
68494
|
-
const dbPath = getDbPath();
|
|
68495
|
-
if (!existsSync7(dbPath)) {
|
|
68496
|
-
console.error(chalk20.red(`Database not found at ${dbPath}`));
|
|
68497
|
-
process.exit(1);
|
|
68498
|
-
}
|
|
68499
70048
|
let dest;
|
|
68500
70049
|
if (targetPath) {
|
|
68501
70050
|
dest = resolve15(targetPath);
|
|
@@ -68511,6 +70060,26 @@ function registerBackupCommand(program2) {
|
|
|
68511
70060
|
if (!existsSync7(destDir)) {
|
|
68512
70061
|
mkdirSync4(destDir, { recursive: true });
|
|
68513
70062
|
}
|
|
70063
|
+
if (isApiMode()) {
|
|
70064
|
+
const { rows } = listMemoriesBounded({}, undefined);
|
|
70065
|
+
const count = writeMemoriesBackupFile(dest, rows);
|
|
70066
|
+
const st2 = statSync(dest);
|
|
70067
|
+
const sizeMB2 = (st2.size / (1024 * 1024)).toFixed(1);
|
|
70068
|
+
const sizeStr2 = st2.size >= 1024 * 1024 ? `${sizeMB2} MB` : `${(st2.size / 1024).toFixed(1)} KB`;
|
|
70069
|
+
if (globalOpts.json) {
|
|
70070
|
+
outputJson({ backed_up_to: dest, size: st2.size, source: "cloud-api", memories: count });
|
|
70071
|
+
return;
|
|
70072
|
+
}
|
|
70073
|
+
console.log(`Backed up to: ${chalk20.green(dest)} (size: ${sizeStr2})`);
|
|
70074
|
+
console.log(` Source: hosted store (cloud-api)`);
|
|
70075
|
+
console.log(` Memories: ${chalk20.cyan(String(count))}`);
|
|
70076
|
+
return;
|
|
70077
|
+
}
|
|
70078
|
+
const dbPath = getDbPath();
|
|
70079
|
+
if (!existsSync7(dbPath)) {
|
|
70080
|
+
console.error(chalk20.red(`Database not found at ${dbPath}`));
|
|
70081
|
+
process.exit(1);
|
|
70082
|
+
}
|
|
68514
70083
|
copyFileSync(dbPath, dest);
|
|
68515
70084
|
const st = statSync(dest);
|
|
68516
70085
|
const sizeMB = (st.size / (1024 * 1024)).toFixed(1);
|
|
@@ -68545,9 +70114,31 @@ function readMemoriesFromBackup(source) {
|
|
|
68545
70114
|
backupDb.close();
|
|
68546
70115
|
}
|
|
68547
70116
|
}
|
|
70117
|
+
function inspectLocalRestoreSource(source) {
|
|
70118
|
+
const { Database: Database3 } = __require("bun:sqlite");
|
|
70119
|
+
let backupDb;
|
|
70120
|
+
try {
|
|
70121
|
+
backupDb = new Database3(source, { readonly: true });
|
|
70122
|
+
} catch (error) {
|
|
70123
|
+
throw new Error(`Backup file is not a readable SQLite database: ${source} ` + `(${error instanceof Error ? error.message : String(error)})`);
|
|
70124
|
+
}
|
|
70125
|
+
try {
|
|
70126
|
+
const tables = backupDb.query("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((t) => t.name);
|
|
70127
|
+
if (tables.includes("_backup_carrier")) {
|
|
70128
|
+
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\`.`);
|
|
70129
|
+
}
|
|
70130
|
+
if (!tables.includes("memories")) {
|
|
70131
|
+
throw new Error(`Backup file has no mementos \`memories\` table: ${source} \u2014 it is not a mementos backup.`);
|
|
70132
|
+
}
|
|
70133
|
+
const row = backupDb.query("SELECT COUNT(*) as count FROM memories").get();
|
|
70134
|
+
return { memories: row?.count ?? 0 };
|
|
70135
|
+
} finally {
|
|
70136
|
+
backupDb.close();
|
|
70137
|
+
}
|
|
70138
|
+
}
|
|
68548
70139
|
function registerRestoreCommand(program2) {
|
|
68549
70140
|
const handleError = makeHandleError(program2);
|
|
68550
|
-
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) => {
|
|
70141
|
+
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(async (filePath, opts) => {
|
|
68551
70142
|
try {
|
|
68552
70143
|
const globalOpts = program2.opts();
|
|
68553
70144
|
const backupsDir = join11(getDataRoot(), "backups");
|
|
@@ -68608,7 +70199,7 @@ function registerRestoreCommand(program2) {
|
|
|
68608
70199
|
if (result.rejected > 0) {
|
|
68609
70200
|
const msg = `${result.rejected} of ${result.total} memories were rejected and did not persist. See errors.`;
|
|
68610
70201
|
if (globalOpts.json) {
|
|
68611
|
-
|
|
70202
|
+
await outputJsonAndExit({
|
|
68612
70203
|
action: "restore",
|
|
68613
70204
|
status: "failed",
|
|
68614
70205
|
source,
|
|
@@ -68618,11 +70209,12 @@ function registerRestoreCommand(program2) {
|
|
|
68618
70209
|
rejected: result.rejected,
|
|
68619
70210
|
total: result.total,
|
|
68620
70211
|
error: msg
|
|
68621
|
-
});
|
|
70212
|
+
}, 1);
|
|
68622
70213
|
} else {
|
|
68623
70214
|
console.error(chalk21.red(msg));
|
|
68624
70215
|
}
|
|
68625
|
-
|
|
70216
|
+
if (!globalOpts.json)
|
|
70217
|
+
process.exit(1);
|
|
68626
70218
|
}
|
|
68627
70219
|
if (globalOpts.json) {
|
|
68628
70220
|
outputJson({
|
|
@@ -68655,14 +70247,7 @@ function registerRestoreCommand(program2) {
|
|
|
68655
70247
|
currentCount = row?.count ?? 0;
|
|
68656
70248
|
} catch {}
|
|
68657
70249
|
}
|
|
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 {}
|
|
70250
|
+
const backupCount = inspectLocalRestoreSource(source).memories;
|
|
68666
70251
|
if (!opts.force) {
|
|
68667
70252
|
if (globalOpts.json) {
|
|
68668
70253
|
outputJson({
|
|
@@ -68784,20 +70369,40 @@ function registerAgentCommands(program2) {
|
|
|
68784
70369
|
handleError(e);
|
|
68785
70370
|
}
|
|
68786
70371
|
});
|
|
68787
|
-
program2.command("agents").description("List all registered agents").option("--limit <n>", "Max results (compact default: 20)", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--offset <n>", "Offset for pagination", parseInt).action((opts) => {
|
|
70372
|
+
program2.command("agents").description("List all registered agents").option("--limit <n>", "Max results (compact default: 20)", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--offset <n>", "Offset for pagination", parseInt).option("--all", `Exhaust all agents in one explicit JSON receipt (hard max: ${STRUCTURED_ALL_MAX_ROWS})`).option("--full", "Return full agent objects instead of compact projections").option("--max-bytes <n>", "JSON response byte ceiling", parseInt).action((opts) => {
|
|
68788
70373
|
try {
|
|
68789
70374
|
const globalOpts = program2.opts();
|
|
68790
|
-
const
|
|
70375
|
+
const jsonMode = Boolean(globalOpts.json);
|
|
70376
|
+
const all = Boolean(opts.all);
|
|
70377
|
+
const detail = opts.full ? "full" : "compact";
|
|
70378
|
+
if (!jsonMode && (all || opts.full || opts.maxBytes !== undefined)) {
|
|
70379
|
+
throw new Error("--all, --full, and --max-bytes require --json");
|
|
70380
|
+
}
|
|
70381
|
+
if (all && opts.limit !== undefined)
|
|
70382
|
+
throw new Error("--all cannot be combined with --limit");
|
|
70383
|
+
const limit = jsonMode ? structuredPageLimit(opts.limit, DEFAULT_COMPACT_LIMIT) : positiveIntOrDefault(opts.limit, DEFAULT_COMPACT_LIMIT);
|
|
68791
70384
|
const offset = cursorOrOffset(opts.cursor, opts.offset) ?? 0;
|
|
68792
|
-
|
|
68793
|
-
|
|
68794
|
-
|
|
68795
|
-
|
|
68796
|
-
|
|
68797
|
-
|
|
70385
|
+
if (all && offset !== 0)
|
|
70386
|
+
throw new Error("--all requires --cursor/--offset 0");
|
|
70387
|
+
const agents = all ? listAgents() : listAgents({ limit: limit + 1, offset });
|
|
70388
|
+
if (all && agents.length > STRUCTURED_ALL_MAX_ROWS) {
|
|
70389
|
+
throw new Error(`Exhaustive agent output exceeds the hard safety limit of ${STRUCTURED_ALL_MAX_ROWS} rows; use paginated JSON output`);
|
|
70390
|
+
}
|
|
70391
|
+
const hasMore = !all && agents.length > limit;
|
|
68798
70392
|
const displayAgents = hasMore ? agents.slice(0, limit) : agents;
|
|
68799
|
-
if (
|
|
68800
|
-
|
|
70393
|
+
if (jsonMode) {
|
|
70394
|
+
const items = detail === "full" ? displayAgents.map((agent) => ({ ...agent })) : displayAgents.map(compactAgent);
|
|
70395
|
+
process.stdout.write(structuredCollectionOutput({
|
|
70396
|
+
collection: "agents",
|
|
70397
|
+
receipt: "mementos.agents.page.v1",
|
|
70398
|
+
items,
|
|
70399
|
+
offset,
|
|
70400
|
+
limit,
|
|
70401
|
+
sourceHasMore: hasMore,
|
|
70402
|
+
all,
|
|
70403
|
+
detail,
|
|
70404
|
+
maxBytes: structuredMaxBytes(opts.maxBytes, { all, detail })
|
|
70405
|
+
}));
|
|
68801
70406
|
return;
|
|
68802
70407
|
}
|
|
68803
70408
|
if (displayAgents.length === 0) {
|
|
@@ -68821,7 +70426,7 @@ function registerAgentCommands(program2) {
|
|
|
68821
70426
|
handleError(e);
|
|
68822
70427
|
}
|
|
68823
70428
|
});
|
|
68824
|
-
program2.command("agent-update <id>").description("Update an agent's name, description, or role").option("--name <name>", "New agent name").option("-d, --description <text>", "New description").option("-r, --role <role>", "New role").action((id, opts) => {
|
|
70429
|
+
program2.command("agent-update <id>").description("Update an agent's name, description, or role").option("--name <name>", "New agent name").option("-d, --description <text>", "New description").option("-r, --role <role>", "New role").action(async (id, opts) => {
|
|
68825
70430
|
try {
|
|
68826
70431
|
const globalOpts = program2.opts();
|
|
68827
70432
|
const updates = {};
|
|
@@ -68833,20 +70438,24 @@ function registerAgentCommands(program2) {
|
|
|
68833
70438
|
updates.role = opts.role;
|
|
68834
70439
|
if (Object.keys(updates).length === 0) {
|
|
68835
70440
|
if (globalOpts.json) {
|
|
68836
|
-
|
|
70441
|
+
await outputJsonAndExit({ error: "No updates provided. Use --name, --description, or --role." }, 1);
|
|
68837
70442
|
} else {
|
|
68838
70443
|
console.error(chalk22.red("No updates provided. Use --name, --description, or --role."));
|
|
68839
70444
|
}
|
|
68840
|
-
|
|
70445
|
+
if (!globalOpts.json)
|
|
70446
|
+
process.exit(1);
|
|
70447
|
+
return;
|
|
68841
70448
|
}
|
|
68842
70449
|
const agent = updateAgent(id, updates);
|
|
68843
70450
|
if (!agent) {
|
|
68844
70451
|
if (globalOpts.json) {
|
|
68845
|
-
|
|
70452
|
+
await outputJsonAndExit({ error: `Agent not found: ${id}` }, 1);
|
|
68846
70453
|
} else {
|
|
68847
70454
|
console.error(chalk22.red(`Agent not found: ${id}`));
|
|
68848
70455
|
}
|
|
68849
|
-
|
|
70456
|
+
if (!globalOpts.json)
|
|
70457
|
+
process.exit(1);
|
|
70458
|
+
return;
|
|
68850
70459
|
}
|
|
68851
70460
|
if (globalOpts.json) {
|
|
68852
70461
|
outputJson(agent);
|
|
@@ -68934,7 +70543,7 @@ import { resolve as resolve17 } from "path";
|
|
|
68934
70543
|
init_helpers();
|
|
68935
70544
|
function registerProjectCommands(program2) {
|
|
68936
70545
|
const handleError = makeHandleError(program2);
|
|
68937
|
-
program2.command("projects").description("Manage projects").option("--add", "Add a new project").option("--update <id>", "Update a project by its exact stable ID").option("--name <name>", "Project name").option("--path <path>", "Project path").option("--description <text>", "Project description").option("--memory-prefix <prefix>", "Project memory prefix").option("--expected-revision <revision>", "Exact updated_at revision required for compare-and-swap").option("--idempotency-key <key>", "Caller-owned key for one guarded mutation").option("--operation-id <id>", "Operation identifier (defaults to the idempotency key)").option("--step-id <id>", "Step identifier (defaults to mementos_project_update)").option("--dry-run", "Validate and preview the guarded update without writing").option("--rollback-receipt <id>", "Restore the exact before snapshot from an accepted update receipt").option("--limit <n>", "Max results (compact default: 20)", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--offset <n>", "Offset for pagination", parseInt).action((opts) => {
|
|
70546
|
+
program2.command("projects").description("Manage projects").option("--add", "Add a new project").option("--update <id>", "Update a project by its exact stable ID").option("--name <name>", "Project name").option("--path <path>", "Project path").option("--description <text>", "Project description").option("--memory-prefix <prefix>", "Project memory prefix").option("--expected-revision <revision>", "Exact updated_at revision required for compare-and-swap").option("--idempotency-key <key>", "Caller-owned key for one guarded mutation").option("--operation-id <id>", "Operation identifier (defaults to the idempotency key)").option("--step-id <id>", "Step identifier (defaults to mementos_project_update)").option("--dry-run", "Validate and preview the guarded update without writing").option("--rollback-receipt <id>", "Restore the exact before snapshot from an accepted update receipt").option("--limit <n>", "Max results (compact default: 20)", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--offset <n>", "Offset for pagination", parseInt).option("--all", `Exhaust all projects in one explicit JSON receipt (hard max: ${STRUCTURED_ALL_MAX_ROWS})`).option("--full", "Return full project objects instead of compact projections").option("--max-bytes <n>", "JSON response byte ceiling", parseInt).action((opts) => {
|
|
68938
70547
|
try {
|
|
68939
70548
|
const globalOpts = program2.opts();
|
|
68940
70549
|
if (opts.add && opts.update) {
|
|
@@ -69015,15 +70624,39 @@ function registerProjectCommands(program2) {
|
|
|
69015
70624
|
}
|
|
69016
70625
|
return;
|
|
69017
70626
|
}
|
|
69018
|
-
const
|
|
69019
|
-
const
|
|
70627
|
+
const jsonMode = Boolean(globalOpts.json);
|
|
70628
|
+
const all = Boolean(opts.all);
|
|
70629
|
+
const detail = opts.full ? "full" : "compact";
|
|
70630
|
+
if (!jsonMode && (all || opts.full || opts.maxBytes !== undefined)) {
|
|
70631
|
+
throw new Error("--all, --full, and --max-bytes require --json");
|
|
70632
|
+
}
|
|
70633
|
+
if (all && opts.limit !== undefined) {
|
|
70634
|
+
throw new Error("--all cannot be combined with --limit");
|
|
70635
|
+
}
|
|
70636
|
+
const limit = jsonMode ? structuredPageLimit(opts.limit, DEFAULT_COMPACT_LIMIT) : positiveIntOrDefault(opts.limit, DEFAULT_COMPACT_LIMIT);
|
|
69020
70637
|
const offset = cursorOrOffset(opts.cursor, opts.offset) ?? 0;
|
|
69021
|
-
|
|
69022
|
-
|
|
69023
|
-
const
|
|
69024
|
-
|
|
69025
|
-
|
|
69026
|
-
|
|
70638
|
+
if (all && offset !== 0)
|
|
70639
|
+
throw new Error("--all requires --cursor/--offset 0");
|
|
70640
|
+
const allProjects = listProjects();
|
|
70641
|
+
if (all && allProjects.length > STRUCTURED_ALL_MAX_ROWS) {
|
|
70642
|
+
throw new Error(`Exhaustive project output exceeds the hard safety limit of ${STRUCTURED_ALL_MAX_ROWS} rows; use paginated JSON output`);
|
|
70643
|
+
}
|
|
70644
|
+
const page = all ? allProjects : allProjects.slice(offset, offset + limit + 1);
|
|
70645
|
+
const hasMore = !all && page.length > limit;
|
|
70646
|
+
const displayProjects = hasMore ? page.slice(0, limit) : page;
|
|
70647
|
+
if (jsonMode) {
|
|
70648
|
+
const items = detail === "full" ? displayProjects.map((project) => ({ ...project })) : displayProjects.map(compactProject);
|
|
70649
|
+
process.stdout.write(structuredCollectionOutput({
|
|
70650
|
+
collection: "projects",
|
|
70651
|
+
receipt: "mementos.projects.page.v1",
|
|
70652
|
+
items,
|
|
70653
|
+
offset,
|
|
70654
|
+
limit,
|
|
70655
|
+
sourceHasMore: hasMore,
|
|
70656
|
+
all,
|
|
70657
|
+
detail,
|
|
70658
|
+
maxBytes: structuredMaxBytes(opts.maxBytes, { all, detail })
|
|
70659
|
+
}));
|
|
69027
70660
|
return;
|
|
69028
70661
|
}
|
|
69029
70662
|
if (displayProjects.length === 0) {
|
|
@@ -69103,14 +70736,17 @@ function registerProjectCommands(program2) {
|
|
|
69103
70736
|
const categoriesRaw = opts.categories || "preference,fact,knowledge";
|
|
69104
70737
|
const categories = categoriesRaw.split(",").map((c) => c.trim());
|
|
69105
70738
|
const agentId = opts.agent || globalOpts.agent;
|
|
69106
|
-
const projectPath = opts.project
|
|
70739
|
+
const projectPath = opts.project ?? globalOpts.project;
|
|
69107
70740
|
const sessionId = opts.session || globalOpts.session;
|
|
69108
70741
|
const visibleMachineId = resolveVisibleMachineId(opts.machine);
|
|
69109
70742
|
let projectId;
|
|
69110
|
-
if (projectPath) {
|
|
70743
|
+
if (projectPath !== undefined) {
|
|
70744
|
+
if (!projectPath.trim())
|
|
70745
|
+
throw new Error("Project not found: empty project path");
|
|
69111
70746
|
const project = getProject(resolve17(projectPath));
|
|
69112
|
-
if (project)
|
|
69113
|
-
|
|
70747
|
+
if (!project)
|
|
70748
|
+
throw new Error(`Project not found: ${projectPath}`);
|
|
70749
|
+
projectId = project.id;
|
|
69114
70750
|
}
|
|
69115
70751
|
const allMemories = [];
|
|
69116
70752
|
const globalMems = listMemories({
|
|
@@ -69142,6 +70778,7 @@ function registerProjectCommands(program2) {
|
|
|
69142
70778
|
min_importance: minImportance,
|
|
69143
70779
|
status: "active",
|
|
69144
70780
|
agent_id: agentId,
|
|
70781
|
+
project_id: projectId,
|
|
69145
70782
|
session_id: sessionId,
|
|
69146
70783
|
...visibleToMachineFilter(visibleMachineId),
|
|
69147
70784
|
limit: 50
|
|
@@ -69901,6 +71538,8 @@ init_database();
|
|
|
69901
71538
|
init_memories();
|
|
69902
71539
|
init_agents();
|
|
69903
71540
|
init_projects();
|
|
71541
|
+
init_config();
|
|
71542
|
+
init_api_mode();
|
|
69904
71543
|
import chalk27 from "chalk";
|
|
69905
71544
|
import { join as join12 } from "path";
|
|
69906
71545
|
import { homedir as homedir5 } from "os";
|
|
@@ -69911,7 +71550,6 @@ import {
|
|
|
69911
71550
|
statSync as statSync3,
|
|
69912
71551
|
constants as fsConstants
|
|
69913
71552
|
} from "fs";
|
|
69914
|
-
init_api_mode();
|
|
69915
71553
|
init_helpers();
|
|
69916
71554
|
async function runCommandWithTimeout(args, timeoutMs) {
|
|
69917
71555
|
const proc = Bun.spawn(args, { stdout: "pipe", stderr: "pipe" });
|
|
@@ -70292,7 +71930,8 @@ function resolveApiStatus(version = getPackageVersion()) {
|
|
|
70292
71930
|
error = err instanceof Error ? err.message : String(err);
|
|
70293
71931
|
}
|
|
70294
71932
|
}
|
|
70295
|
-
const
|
|
71933
|
+
const resolvedApiUrl = error ? null : apiUrl ?? resolved?.baseUrl ?? null;
|
|
71934
|
+
const validBase = error ? null : apiBase ?? resolved?.baseUrl.replace(/\/v1$/, "") ?? null;
|
|
70296
71935
|
const apiKeyConfigured = configured?.apiKeyPresent ?? false;
|
|
70297
71936
|
let transport;
|
|
70298
71937
|
if (hasExplicitLocalDbPath()) {
|
|
@@ -70309,7 +71948,7 @@ function resolveApiStatus(version = getPackageVersion()) {
|
|
|
70309
71948
|
app: "mementos",
|
|
70310
71949
|
version,
|
|
70311
71950
|
transport,
|
|
70312
|
-
api_url:
|
|
71951
|
+
api_url: resolvedApiUrl,
|
|
70313
71952
|
api_base: validBase,
|
|
70314
71953
|
api_key_present: apiKeyConfigured || resolved !== null
|
|
70315
71954
|
},
|
|
@@ -70340,8 +71979,9 @@ function registerStatusCommand(program2) {
|
|
|
70340
71979
|
}
|
|
70341
71980
|
|
|
70342
71981
|
// src/cli/commands/system-config.ts
|
|
70343
|
-
|
|
71982
|
+
init_config();
|
|
70344
71983
|
init_helpers();
|
|
71984
|
+
import chalk28 from "chalk";
|
|
70345
71985
|
function registerConfigCommand(program2) {
|
|
70346
71986
|
program2.command("config [subcommand] [args...]").description("View or modify configuration. Subcommands: get <key>, set <key> <value>, reset [key], path").action((subcommand, args) => {
|
|
70347
71987
|
try {
|
|
@@ -70427,9 +72067,9 @@ function registerConfigCommand(program2) {
|
|
|
70427
72067
|
}
|
|
70428
72068
|
} else {
|
|
70429
72069
|
const configPath = getConfigPath2();
|
|
70430
|
-
const { unlinkSync:
|
|
72070
|
+
const { unlinkSync: unlinkSync4, existsSync: _existsSync } = __require("fs");
|
|
70431
72071
|
if (_existsSync(configPath)) {
|
|
70432
|
-
|
|
72072
|
+
unlinkSync4(configPath);
|
|
70433
72073
|
}
|
|
70434
72074
|
if (useJson) {
|
|
70435
72075
|
outputJson({ reset: true, all: true });
|
|
@@ -70449,10 +72089,11 @@ function registerConfigCommand(program2) {
|
|
|
70449
72089
|
}
|
|
70450
72090
|
|
|
70451
72091
|
// src/cli/commands/system-profile.ts
|
|
70452
|
-
|
|
70453
|
-
import { join as join13 } from "path";
|
|
72092
|
+
init_config();
|
|
70454
72093
|
init_paths();
|
|
70455
72094
|
init_helpers();
|
|
72095
|
+
import chalk29 from "chalk";
|
|
72096
|
+
import { join as join13 } from "path";
|
|
70456
72097
|
function registerProfileCommand(program2) {
|
|
70457
72098
|
const profileCmd = program2.command("profile").description("Manage named profile files and active-profile metadata");
|
|
70458
72099
|
profileCmd.command("list").description("List all available profiles").action(() => {
|
|
@@ -70860,10 +72501,12 @@ function registerSessionCommand(program2) {
|
|
|
70860
72501
|
const sessionCmd = program2.command("session").description("Session auto-memory \u2014 ingest session transcripts for memory extraction");
|
|
70861
72502
|
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
72503
|
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
72504
|
const transcript = _rfs(transcriptFile, "utf-8");
|
|
70866
72505
|
const sessionId = opts.sessionId ?? `cli-${Date.now()}`;
|
|
72506
|
+
const { isApiMode: isApiMode2 } = await Promise.resolve().then(() => (init_api_mode(), exports_api_mode));
|
|
72507
|
+
const hosted = isApiMode2();
|
|
72508
|
+
const { createSessionJob: createSessionJob2 } = await Promise.resolve().then(() => (init_session_jobs(), exports_session_jobs));
|
|
72509
|
+
const { enqueueSessionJob: enqueueSessionJob2 } = await Promise.resolve().then(() => (init_session_queue(), exports_session_queue));
|
|
70867
72510
|
const job = createSessionJob2({
|
|
70868
72511
|
session_id: sessionId,
|
|
70869
72512
|
transcript,
|
|
@@ -70872,7 +72515,7 @@ function registerSessionCommand(program2) {
|
|
|
70872
72515
|
project_id: opts.project
|
|
70873
72516
|
});
|
|
70874
72517
|
enqueueSessionJob2(job.id);
|
|
70875
|
-
console.log(chalk33.green(`\u2713 Session queued: ${chalk33.cyan(job.id)}`));
|
|
72518
|
+
console.log(chalk33.green(`\u2713 Session queued${hosted ? " (hosted store)" : ""}: ${chalk33.cyan(job.id)}`));
|
|
70876
72519
|
console.log(` Session: ${sessionId}`);
|
|
70877
72520
|
console.log(` Length: ${transcript.length} chars`);
|
|
70878
72521
|
});
|
|
@@ -70891,14 +72534,16 @@ function registerSessionCommand(program2) {
|
|
|
70891
72534
|
if (job.error)
|
|
70892
72535
|
console.log(chalk33.red(` Error: ${job.error}`));
|
|
70893
72536
|
});
|
|
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) => {
|
|
72537
|
+
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
72538
|
const globalOpts = program2.opts();
|
|
70896
72539
|
const { listSessionJobs: listSessionJobs2 } = await Promise.resolve().then(() => (init_session_jobs(), exports_session_jobs));
|
|
70897
72540
|
const jobs = listSessionJobs2({
|
|
70898
72541
|
agent_id: opts.agent,
|
|
70899
72542
|
project_id: opts.project,
|
|
70900
72543
|
status: opts.status,
|
|
70901
|
-
|
|
72544
|
+
session_id: opts.sessionId,
|
|
72545
|
+
limit: parseInt(opts.limit, 10),
|
|
72546
|
+
offset: parseInt(opts.offset, 10)
|
|
70902
72547
|
});
|
|
70903
72548
|
if (globalOpts.json) {
|
|
70904
72549
|
outputJson(jobs);
|
|
@@ -71044,7 +72689,7 @@ import chalk35 from "chalk";
|
|
|
71044
72689
|
import { resolve as resolve19 } from "path";
|
|
71045
72690
|
function registerSynthesizedProfileCommand(program2) {
|
|
71046
72691
|
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) => {
|
|
72692
|
+
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
72693
|
try {
|
|
71049
72694
|
const globalOpts = program2.opts();
|
|
71050
72695
|
const { synthesizeProfile: synthesizeProfile2 } = await Promise.resolve().then(() => (init_profile_synthesizer(), exports_profile_synthesizer));
|
|
@@ -71058,6 +72703,7 @@ function registerSynthesizedProfileCommand(program2) {
|
|
|
71058
72703
|
const result = await synthesizeProfile2({
|
|
71059
72704
|
project_id: projectId,
|
|
71060
72705
|
agent_id: globalOpts.agent,
|
|
72706
|
+
scope: opts.scope,
|
|
71061
72707
|
force_refresh: !!opts.refresh
|
|
71062
72708
|
});
|
|
71063
72709
|
if (!result) {
|
|
@@ -71575,6 +73221,15 @@ import chalk40 from "chalk";
|
|
|
71575
73221
|
// src/lib/storage-sync.ts
|
|
71576
73222
|
init_storage();
|
|
71577
73223
|
init_database();
|
|
73224
|
+
init_config();
|
|
73225
|
+
init_local_opt_in();
|
|
73226
|
+
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.";
|
|
73227
|
+
function openLocalSyncAdapter() {
|
|
73228
|
+
if (!isServerContext() && !selectsMementosLocalStore(process.env)) {
|
|
73229
|
+
throw new Error(STORAGE_SYNC_LOCAL_ONLY_MESSAGE);
|
|
73230
|
+
}
|
|
73231
|
+
return new SqliteAdapter(getDbPath2());
|
|
73232
|
+
}
|
|
71578
73233
|
var MEMORY_TABLE = "memories";
|
|
71579
73234
|
var MEMORY_SYNC_META_TABLE = "_mementos_storage_sync_meta";
|
|
71580
73235
|
var SOURCE_MACHINE_METADATA_KEY = "last_synced_source_machine";
|
|
@@ -71953,7 +73608,7 @@ function resolveTables(local, tables) {
|
|
|
71953
73608
|
function withManagedAdapters(options, fn) {
|
|
71954
73609
|
const localOwned = !options.local;
|
|
71955
73610
|
const remoteOwned = !options.remote;
|
|
71956
|
-
const local = options.local ??
|
|
73611
|
+
const local = options.local ?? openLocalSyncAdapter();
|
|
71957
73612
|
const remote = options.remote ?? new PgAdapter(getStorageConnectionString("mementos"));
|
|
71958
73613
|
try {
|
|
71959
73614
|
const currentMachineId = resolveCurrentMachineId(local, options.current_machine_id);
|
|
@@ -72009,7 +73664,7 @@ function pullStorageChanges(options = {}) {
|
|
|
72009
73664
|
function getStorageSyncStatus(options = {}) {
|
|
72010
73665
|
const backend = getStorageBackend();
|
|
72011
73666
|
const localOwned = !options.local;
|
|
72012
|
-
const local = options.local ??
|
|
73667
|
+
const local = options.local ?? openLocalSyncAdapter();
|
|
72013
73668
|
try {
|
|
72014
73669
|
const currentMachineId = resolveCurrentMachineId(local, options.current_machine_id);
|
|
72015
73670
|
return {
|
|
@@ -73581,11 +75236,13 @@ function registerConsolidationCommands(program2) {
|
|
|
73581
75236
|
}
|
|
73582
75237
|
} catch (error40) {
|
|
73583
75238
|
if (program2.opts().json) {
|
|
73584
|
-
|
|
75239
|
+
await outputJsonAndExit({ error: error40 instanceof Error ? error40.message : String(error40) }, 1);
|
|
73585
75240
|
} else {
|
|
73586
75241
|
console.error(chalk42.red(error40 instanceof Error ? error40.message : String(error40)));
|
|
73587
75242
|
}
|
|
73588
|
-
|
|
75243
|
+
if (!program2.opts().json)
|
|
75244
|
+
process.exit(1);
|
|
75245
|
+
return;
|
|
73589
75246
|
}
|
|
73590
75247
|
});
|
|
73591
75248
|
program2.command("reflect").description("Reflect on a session, task, or range and save structured lessons").requiredOption("--on <target>", "Trajectory target: session, task, range").option("--source <idOrRange>", "Session ID, task ID, or range as since..until").option("--dry-run", "Critique without writing lesson memories").option("--project <idOrPath>", "Project ID, name, or path").option("--agent <nameOrId>", "Agent name or ID").option("--since <iso>", "Range start timestamp").option("--until <iso>", "Range end timestamp").option("--provider <name>", "Critic provider: anthropic, openai, cerebras, grok").option("--model <name>", "Critic model").option("--max-tokens <n>", "Maximum critic output tokens", parseNumber2).option("--format <fmt>", "Output format: compact, json").action(async (opts) => {
|
|
@@ -73624,11 +75281,13 @@ function registerConsolidationCommands(program2) {
|
|
|
73624
75281
|
}
|
|
73625
75282
|
} catch (error40) {
|
|
73626
75283
|
if (program2.opts().json) {
|
|
73627
|
-
|
|
75284
|
+
await outputJsonAndExit({ error: error40 instanceof Error ? error40.message : String(error40) }, 1);
|
|
73628
75285
|
} else {
|
|
73629
75286
|
console.error(chalk42.red(error40 instanceof Error ? error40.message : String(error40)));
|
|
73630
75287
|
}
|
|
73631
|
-
|
|
75288
|
+
if (!program2.opts().json)
|
|
75289
|
+
process.exit(1);
|
|
75290
|
+
return;
|
|
73632
75291
|
}
|
|
73633
75292
|
});
|
|
73634
75293
|
}
|