@hasna/mementos 0.14.68 → 0.14.70
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 +155 -73
- package/dist/cli/__fixtures__/clean-fallback-stub-server.d.ts +2 -0
- package/dist/cli/__fixtures__/clean-fallback-stub-server.d.ts.map +1 -0
- package/dist/cli/commands/io-clean.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
- package/dist/cli/commands/storage.d.ts.map +1 -1
- package/dist/cli/commands/system-mcp.d.ts.map +1 -1
- package/dist/cli/commands/system-profile.d.ts.map +1 -1
- package/dist/cli/global-options.d.ts +27 -0
- package/dist/cli/global-options.d.ts.map +1 -0
- package/dist/cli/index.js +699 -301
- package/dist/cli/register-all.d.ts +17 -0
- package/dist/cli/register-all.d.ts.map +1 -0
- package/dist/cli/startup-side-effects.d.ts +9 -0
- package/dist/cli/startup-side-effects.d.ts.map +1 -0
- package/dist/db/__fixtures__/fail-closed-stub-server.d.ts +2 -0
- package/dist/db/__fixtures__/fail-closed-stub-server.d.ts.map +1 -0
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/api-mode.d.ts +70 -2
- package/dist/db/api-mode.d.ts.map +1 -1
- package/dist/db/database.d.ts +16 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/locks.d.ts.map +1 -1
- package/dist/db/memories.d.ts +24 -6
- package/dist/db/memories.d.ts.map +1 -1
- package/dist/db/migrations.d.ts +1 -0
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/session-jobs.d.ts.map +1 -1
- package/dist/db/store-backend.d.ts +33 -0
- package/dist/db/store-backend.d.ts.map +1 -0
- package/dist/index.js +196 -62
- package/dist/lib/auto-memory-queue.d.ts +2 -0
- package/dist/lib/auto-memory-queue.d.ts.map +1 -1
- package/dist/lib/auto-memory.d.ts +1 -0
- package/dist/lib/auto-memory.d.ts.map +1 -1
- package/dist/lib/built-in-hooks.d.ts.map +1 -1
- package/dist/lib/enum-validation.d.ts +20 -0
- package/dist/lib/enum-validation.d.ts.map +1 -0
- package/dist/lib/gdpr.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +301 -78
- package/dist/mcp/tools/system-tools-memory-admin.d.ts.map +1 -1
- package/dist/server/helpers.d.ts +11 -0
- package/dist/server/helpers.d.ts.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +304 -72
- package/dist/storage.d.ts +11 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +42 -11
- package/dist/test-support/preload-local-store.d.ts +2 -0
- package/dist/test-support/preload-local-store.d.ts.map +1 -0
- package/dist/test-support/store-isolation.d.ts +83 -0
- package/dist/test-support/store-isolation.d.ts.map +1 -0
- package/dist/types/index.d.ts +8 -4
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2099,11 +2099,32 @@ var require_commander = __commonJS((exports) => {
|
|
|
2099
2099
|
exports.InvalidOptionArgumentError = InvalidArgumentError;
|
|
2100
2100
|
});
|
|
2101
2101
|
|
|
2102
|
+
// src/generated/storage-kit/mode.ts
|
|
2103
|
+
function normalizeStorageMode(value) {
|
|
2104
|
+
const normalized = value.trim().toLowerCase().replace(/-/g, "_");
|
|
2105
|
+
if (normalized === "local")
|
|
2106
|
+
return { mode: "local", deprecatedAlias: null };
|
|
2107
|
+
if (normalized === "cloud")
|
|
2108
|
+
return { mode: "cloud", deprecatedAlias: null };
|
|
2109
|
+
if (DEPRECATED_STORAGE_MODE_ALIASES.includes(normalized)) {
|
|
2110
|
+
return { mode: "cloud", deprecatedAlias: normalized };
|
|
2111
|
+
}
|
|
2112
|
+
throw new Error(`Unknown storage mode: ${value}. Use local or cloud.`);
|
|
2113
|
+
}
|
|
2114
|
+
var DEPRECATED_STORAGE_MODE_ALIASES;
|
|
2115
|
+
var init_mode = __esm(() => {
|
|
2116
|
+
DEPRECATED_STORAGE_MODE_ALIASES = [
|
|
2117
|
+
"remote",
|
|
2118
|
+
"hybrid",
|
|
2119
|
+
"self_hosted"
|
|
2120
|
+
];
|
|
2121
|
+
});
|
|
2122
|
+
|
|
2102
2123
|
// src/storage.ts
|
|
2103
2124
|
import { Database } from "bun:sqlite";
|
|
2104
|
-
import { existsSync
|
|
2105
|
-
import { homedir
|
|
2106
|
-
import { join
|
|
2125
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2126
|
+
import { homedir } from "os";
|
|
2127
|
+
import { join } from "path";
|
|
2107
2128
|
import { fileURLToPath } from "url";
|
|
2108
2129
|
import { Worker } from "worker_threads";
|
|
2109
2130
|
import pg from "pg";
|
|
@@ -2357,21 +2378,23 @@ function warnDeprecatedStorageMode(alias) {
|
|
|
2357
2378
|
warnedDeprecatedModes.add(alias);
|
|
2358
2379
|
process.emitWarning(`${MEMENTOS_STORAGE_ENV.mode}="${alias}" is deprecated; use "cloud". ` + `"${alias}" now maps to pure-remote cloud storage. The local<->remote ` + `sync path is deprecated and is not the fleet cutover path.`, { type: "DeprecationWarning", code: "MEMENTOS_STORAGE_MODE_ALIAS" });
|
|
2359
2380
|
}
|
|
2360
|
-
function
|
|
2361
|
-
if (!value)
|
|
2381
|
+
function normalizeStorageMode2(value, source) {
|
|
2382
|
+
if (!value || !value.trim())
|
|
2362
2383
|
return null;
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2384
|
+
let normalized;
|
|
2385
|
+
try {
|
|
2386
|
+
normalized = normalizeStorageMode(value);
|
|
2387
|
+
} catch (error) {
|
|
2388
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2389
|
+
throw new Error(`mementos: ${source}=${value} is not a valid mode. ${detail}`);
|
|
2366
2390
|
}
|
|
2367
|
-
if (normalized
|
|
2368
|
-
warnDeprecatedStorageMode(normalized);
|
|
2369
|
-
return "cloud";
|
|
2391
|
+
if (normalized.deprecatedAlias) {
|
|
2392
|
+
warnDeprecatedStorageMode(normalized.deprecatedAlias);
|
|
2370
2393
|
}
|
|
2371
|
-
return
|
|
2394
|
+
return normalized.mode;
|
|
2372
2395
|
}
|
|
2373
2396
|
function readConfigFile() {
|
|
2374
|
-
if (!
|
|
2397
|
+
if (!existsSync(STORAGE_CONFIG_PATH)) {
|
|
2375
2398
|
return {};
|
|
2376
2399
|
}
|
|
2377
2400
|
try {
|
|
@@ -2398,7 +2421,7 @@ function getStorageDatabaseUrl() {
|
|
|
2398
2421
|
}
|
|
2399
2422
|
function getStorageModeOverride() {
|
|
2400
2423
|
for (const env of MODE_ENV_NAMES) {
|
|
2401
|
-
const value =
|
|
2424
|
+
const value = normalizeStorageMode2(readEnv(env.name) ?? undefined, env.name);
|
|
2402
2425
|
if (value)
|
|
2403
2426
|
return value;
|
|
2404
2427
|
}
|
|
@@ -2408,7 +2431,7 @@ function getStorageConfig() {
|
|
|
2408
2431
|
const fileConfig = readConfigFile();
|
|
2409
2432
|
const modeOverride = getStorageModeOverride();
|
|
2410
2433
|
const envConnectionString = getConfiguredConnectionString();
|
|
2411
|
-
const fileMode =
|
|
2434
|
+
const fileMode = normalizeStorageMode2(fileConfig.mode, `${STORAGE_CONFIG_PATH} "mode"`);
|
|
2412
2435
|
const merged = {
|
|
2413
2436
|
...DEFAULT_STORAGE_CONFIG,
|
|
2414
2437
|
...fileConfig,
|
|
@@ -2819,6 +2842,7 @@ CREATE TABLE IF NOT EXISTS _sync_meta (
|
|
|
2819
2842
|
direction TEXT DEFAULT 'push'
|
|
2820
2843
|
)`;
|
|
2821
2844
|
var init_storage = __esm(() => {
|
|
2845
|
+
init_mode();
|
|
2822
2846
|
PgSyncPool = class PgSyncPool {
|
|
2823
2847
|
worker;
|
|
2824
2848
|
status;
|
|
@@ -2831,12 +2855,12 @@ var init_storage = __esm(() => {
|
|
|
2831
2855
|
const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
2832
2856
|
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
2833
2857
|
const candidates = [
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2858
|
+
join(here, `pg-sync-worker${ext}`),
|
|
2859
|
+
join(here, "..", `pg-sync-worker${ext}`),
|
|
2860
|
+
join(here, "..", "..", `pg-sync-worker${ext}`)
|
|
2837
2861
|
];
|
|
2838
2862
|
for (const candidate of candidates) {
|
|
2839
|
-
if (
|
|
2863
|
+
if (existsSync(candidate))
|
|
2840
2864
|
return candidate;
|
|
2841
2865
|
}
|
|
2842
2866
|
return candidates[0];
|
|
@@ -2911,7 +2935,7 @@ var init_storage = __esm(() => {
|
|
|
2911
2935
|
databaseUrl: "MEMENTOS_DATABASE_URL",
|
|
2912
2936
|
mode: "MEMENTOS_STORAGE_MODE"
|
|
2913
2937
|
};
|
|
2914
|
-
LOCAL_DATA_DIR =
|
|
2938
|
+
LOCAL_DATA_DIR = join(homedir(), ".hasna", "mementos");
|
|
2915
2939
|
DEFAULT_STORAGE_CONFIG = {
|
|
2916
2940
|
rds: {
|
|
2917
2941
|
host: "",
|
|
@@ -2927,8 +2951,8 @@ var init_storage = __esm(() => {
|
|
|
2927
2951
|
schedule_minutes: 0
|
|
2928
2952
|
}
|
|
2929
2953
|
};
|
|
2930
|
-
STORAGE_CONFIG_DIR =
|
|
2931
|
-
STORAGE_CONFIG_PATH =
|
|
2954
|
+
STORAGE_CONFIG_DIR = join(LOCAL_DATA_DIR, "storage");
|
|
2955
|
+
STORAGE_CONFIG_PATH = join(STORAGE_CONFIG_DIR, "config.json");
|
|
2932
2956
|
DATABASE_ENV_NAMES = [
|
|
2933
2957
|
{ name: MEMENTOS_STORAGE_ENV.databaseUrl, deprecated: false },
|
|
2934
2958
|
{ name: MEMENTOS_STORAGE_FALLBACK_ENV.databaseUrl, deprecated: false }
|
|
@@ -2958,10 +2982,10 @@ var init_storage = __esm(() => {
|
|
|
2958
2982
|
|
|
2959
2983
|
// src/db/api-mode.ts
|
|
2960
2984
|
import { tmpdir } from "os";
|
|
2961
|
-
import { join as
|
|
2985
|
+
import { join as join2 } from "path";
|
|
2962
2986
|
import { writeFileSync as writeFileSync2, unlinkSync } from "fs";
|
|
2963
|
-
import { randomUUID
|
|
2964
|
-
function firstEnv(
|
|
2987
|
+
import { randomUUID } from "crypto";
|
|
2988
|
+
function firstEnv(keys) {
|
|
2965
2989
|
for (const k of keys) {
|
|
2966
2990
|
const v = process.env[k]?.trim();
|
|
2967
2991
|
if (v)
|
|
@@ -2969,8 +2993,45 @@ function firstEnv(...keys) {
|
|
|
2969
2993
|
}
|
|
2970
2994
|
return;
|
|
2971
2995
|
}
|
|
2996
|
+
function firstEnvKey(keys) {
|
|
2997
|
+
for (const k of keys) {
|
|
2998
|
+
if (process.env[k]?.trim())
|
|
2999
|
+
return k;
|
|
3000
|
+
}
|
|
3001
|
+
return null;
|
|
3002
|
+
}
|
|
2972
3003
|
function hasDatabaseUrl() {
|
|
2973
|
-
return Boolean(firstEnv(
|
|
3004
|
+
return Boolean(firstEnv(DATABASE_URL_ENV_KEYS));
|
|
3005
|
+
}
|
|
3006
|
+
function getApiModeEnvSources() {
|
|
3007
|
+
return {
|
|
3008
|
+
urlKey: firstEnvKey(API_URL_ENV_KEYS),
|
|
3009
|
+
keyKey: firstEnvKey(API_KEY_ENV_KEYS),
|
|
3010
|
+
databaseUrlKey: firstEnvKey(DATABASE_URL_ENV_KEYS)
|
|
3011
|
+
};
|
|
3012
|
+
}
|
|
3013
|
+
function isLoopbackHost(rawHost) {
|
|
3014
|
+
const host = rawHost.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
|
|
3015
|
+
return host === "localhost" || host === "::1" || /^127\./.test(host);
|
|
3016
|
+
}
|
|
3017
|
+
function assertRequestAllowedUnderTest(baseUrl) {
|
|
3018
|
+
if (process.env["NODE_ENV"] !== "test")
|
|
3019
|
+
return;
|
|
3020
|
+
if (process.env[ALLOW_REMOTE_API_IN_TESTS_ENV]?.trim())
|
|
3021
|
+
return;
|
|
3022
|
+
let host;
|
|
3023
|
+
try {
|
|
3024
|
+
host = new URL(baseUrl).hostname;
|
|
3025
|
+
} catch {
|
|
3026
|
+
host = "";
|
|
3027
|
+
}
|
|
3028
|
+
if (host && isLoopbackHost(host))
|
|
3029
|
+
return;
|
|
3030
|
+
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.
|
|
3031
|
+
` + ` host : ${host || "(unparseable base URL)"}
|
|
3032
|
+
` + ` 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.
|
|
3033
|
+
` + " fix : build the child/process env via src/test-support/store-isolation.ts, or point the " + `suite at a loopback stub.
|
|
3034
|
+
` + ` override : set ${ALLOW_REMOTE_API_IN_TESTS_ENV}=1 only for a test that must reach a remote endpoint.`);
|
|
2974
3035
|
}
|
|
2975
3036
|
function normalizeBase(raw) {
|
|
2976
3037
|
let base = raw.trim().replace(/\/+$/, "");
|
|
@@ -2978,9 +3039,24 @@ function normalizeBase(raw) {
|
|
|
2978
3039
|
return base;
|
|
2979
3040
|
return `${base}/v1`;
|
|
2980
3041
|
}
|
|
3042
|
+
function assertUnambiguousStoreEnv() {
|
|
3043
|
+
if (firstEnvKey(DB_PATH_ENV_KEYS))
|
|
3044
|
+
return;
|
|
3045
|
+
if (hasDatabaseUrl())
|
|
3046
|
+
return;
|
|
3047
|
+
const urlKey = firstEnvKey(API_URL_ENV_KEYS);
|
|
3048
|
+
const keyKey = firstEnvKey(API_KEY_ENV_KEYS);
|
|
3049
|
+
if (urlKey && !keyKey) {
|
|
3050
|
+
throw new MementosStoreConfigError(`${urlKey} points at the cloud memory store but ${API_KEY_ENV_KEYS[0]} is not set. ` + `Refusing to serve the on-box SQLite store in its place, because it holds a different ` + `dataset. Set ${API_KEY_ENV_KEYS[0]} to reach the cloud store. If you meant to use the ` + `on-box SQLite store, unset ${urlKey} or set ${DB_PATH_ENV_KEYS[0]} explicitly.`);
|
|
3051
|
+
}
|
|
3052
|
+
if (keyKey && !urlKey) {
|
|
3053
|
+
throw new MementosStoreConfigError(`${keyKey} is set but ${API_URL_ENV_KEYS[0]} is not, so the cloud memory store cannot be ` + `reached. Refusing to serve the on-box SQLite store in its place, because it holds a ` + `different dataset. Set ${API_URL_ENV_KEYS[0]} to reach the cloud store. If you meant to ` + `use the on-box SQLite store, unset ${keyKey} or set ${DB_PATH_ENV_KEYS[0]} explicitly.`);
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
2981
3056
|
function getApiConfig() {
|
|
2982
|
-
|
|
2983
|
-
const
|
|
3057
|
+
assertUnambiguousStoreEnv();
|
|
3058
|
+
const rawBase = firstEnv(API_URL_ENV_KEYS);
|
|
3059
|
+
const apiKey = firstEnv(API_KEY_ENV_KEYS);
|
|
2984
3060
|
if (!rawBase || !apiKey)
|
|
2985
3061
|
return null;
|
|
2986
3062
|
return { baseUrl: normalizeBase(rawBase), apiKey };
|
|
@@ -2994,6 +3070,7 @@ function apiRequestRaw(method, path, body) {
|
|
|
2994
3070
|
const cfg = getApiConfig();
|
|
2995
3071
|
if (!cfg)
|
|
2996
3072
|
throw new Error("api-mode: not configured (HASNA_MEMENTOS_API_URL / HASNA_MEMENTOS_API_KEY)");
|
|
3073
|
+
assertRequestAllowedUnderTest(cfg.baseUrl);
|
|
2997
3074
|
const url = `${cfg.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
2998
3075
|
const hasBody = body !== undefined && body !== null;
|
|
2999
3076
|
const timeout = process.env["HASNA_MEMENTOS_API_TIMEOUT"] || DEFAULT_TIMEOUT_S;
|
|
@@ -3019,7 +3096,7 @@ x-api-key: ${cfg.apiKey}
|
|
|
3019
3096
|
];
|
|
3020
3097
|
let bodyFile;
|
|
3021
3098
|
if (hasBody) {
|
|
3022
|
-
bodyFile =
|
|
3099
|
+
bodyFile = join2(tmpdir(), `mem-req-${process.pid}-${randomUUID()}.json`);
|
|
3023
3100
|
writeFileSync2(bodyFile, JSON.stringify(body), { mode: 384 });
|
|
3024
3101
|
args.push("--data-binary", `@${bodyFile}`);
|
|
3025
3102
|
}
|
|
@@ -3060,13 +3137,13 @@ x-api-key: ${cfg.apiKey}
|
|
|
3060
3137
|
}
|
|
3061
3138
|
return { status, body: respBody };
|
|
3062
3139
|
}
|
|
3063
|
-
function apiJson(method, path, body) {
|
|
3140
|
+
function apiJson(method, path, body, options) {
|
|
3064
3141
|
const raw = apiRequestRaw(method, path, body);
|
|
3065
3142
|
if (raw.status >= 200 && raw.status < 300) {
|
|
3066
3143
|
const data = raw.body.trim() ? JSON.parse(raw.body) : undefined;
|
|
3067
3144
|
return { status: raw.status, data };
|
|
3068
3145
|
}
|
|
3069
|
-
if (raw.status === 404) {
|
|
3146
|
+
if (raw.status === 404 && options?.allow404) {
|
|
3070
3147
|
return { status: 404, data: undefined };
|
|
3071
3148
|
}
|
|
3072
3149
|
let msg = `mementos cloud ${method} ${path} \u2192 ${raw.status}`;
|
|
@@ -3098,8 +3175,19 @@ function toQuery(params) {
|
|
|
3098
3175
|
const s = sp.toString();
|
|
3099
3176
|
return s ? `?${s}` : "";
|
|
3100
3177
|
}
|
|
3101
|
-
var ApiRequestError, DEFAULT_TIMEOUT_S = "45";
|
|
3178
|
+
var API_URL_ENV_KEYS, API_KEY_ENV_KEYS, DATABASE_URL_ENV_KEYS, ALLOW_REMOTE_API_IN_TESTS_ENV = "MEMENTOS_ALLOW_REMOTE_API_IN_TESTS", DB_PATH_ENV_KEYS, MementosStoreConfigError, ApiRequestError, DEFAULT_TIMEOUT_S = "45";
|
|
3102
3179
|
var init_api_mode = __esm(() => {
|
|
3180
|
+
API_URL_ENV_KEYS = ["HASNA_MEMENTOS_API_URL", "MEMENTOS_API_URL"];
|
|
3181
|
+
API_KEY_ENV_KEYS = ["HASNA_MEMENTOS_API_KEY", "MEMENTOS_API_KEY"];
|
|
3182
|
+
DATABASE_URL_ENV_KEYS = ["HASNA_MEMENTOS_DATABASE_URL", "MEMENTOS_DATABASE_URL"];
|
|
3183
|
+
DB_PATH_ENV_KEYS = ["HASNA_MEMENTOS_DB_PATH", "MEMENTOS_DB_PATH"];
|
|
3184
|
+
MementosStoreConfigError = class MementosStoreConfigError extends Error {
|
|
3185
|
+
code = "MEMENTOS_STORE_CONFIG";
|
|
3186
|
+
constructor(message) {
|
|
3187
|
+
super(message);
|
|
3188
|
+
this.name = "MementosStoreConfigError";
|
|
3189
|
+
}
|
|
3190
|
+
};
|
|
3103
3191
|
ApiRequestError = class ApiRequestError extends Error {
|
|
3104
3192
|
status;
|
|
3105
3193
|
body;
|
|
@@ -3113,7 +3201,24 @@ var init_api_mode = __esm(() => {
|
|
|
3113
3201
|
});
|
|
3114
3202
|
|
|
3115
3203
|
// src/db/migrations.ts
|
|
3116
|
-
var
|
|
3204
|
+
var MEMORY_VERSION_SNAPSHOT_TRIGGER = `
|
|
3205
|
+
CREATE TRIGGER IF NOT EXISTS memories_version_snapshot
|
|
3206
|
+
BEFORE UPDATE ON memories
|
|
3207
|
+
WHEN NEW.version > OLD.version
|
|
3208
|
+
BEGIN
|
|
3209
|
+
INSERT OR IGNORE INTO memory_versions (
|
|
3210
|
+
id, memory_id, version, value, importance, scope, category, tags,
|
|
3211
|
+
summary, pinned, status, when_to_use, created_at
|
|
3212
|
+
) VALUES (
|
|
3213
|
+
lower(hex(randomblob(4))) || '-' || lower(hex(randomblob(2))) || '-' ||
|
|
3214
|
+
lower(hex(randomblob(2))) || '-' || lower(hex(randomblob(2))) || '-' ||
|
|
3215
|
+
lower(hex(randomblob(6))),
|
|
3216
|
+
OLD.id, OLD.version, OLD.value, OLD.importance, OLD.scope, OLD.category,
|
|
3217
|
+
OLD.tags, OLD.summary, OLD.pinned, OLD.status, OLD.when_to_use,
|
|
3218
|
+
OLD.updated_at
|
|
3219
|
+
);
|
|
3220
|
+
END;
|
|
3221
|
+
`, MIGRATIONS;
|
|
3117
3222
|
var init_migrations = __esm(() => {
|
|
3118
3223
|
MIGRATIONS = [
|
|
3119
3224
|
`
|
|
@@ -4011,6 +4116,10 @@ CREATE INDEX IF NOT EXISTS idx_memory_reflection_lessons_memory ON memory_reflec
|
|
|
4011
4116
|
CREATE INDEX IF NOT EXISTS idx_memory_reflection_lessons_kind ON memory_reflection_lessons(kind);
|
|
4012
4117
|
|
|
4013
4118
|
INSERT OR IGNORE INTO _migrations (id) VALUES (35);
|
|
4119
|
+
`,
|
|
4120
|
+
`
|
|
4121
|
+
${MEMORY_VERSION_SNAPSHOT_TRIGGER}
|
|
4122
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (36);
|
|
4014
4123
|
`
|
|
4015
4124
|
];
|
|
4016
4125
|
});
|
|
@@ -4022,13 +4131,14 @@ __export(exports_database, {
|
|
|
4022
4131
|
shortUuid: () => shortUuid,
|
|
4023
4132
|
resolvePartialId: () => resolvePartialId,
|
|
4024
4133
|
resetDatabase: () => resetDatabase,
|
|
4025
|
-
now: () =>
|
|
4134
|
+
now: () => now,
|
|
4026
4135
|
getDbPath: () => getDbPath,
|
|
4027
4136
|
getDatabase: () => getDatabase,
|
|
4137
|
+
escapeLikePrefix: () => escapeLikePrefix,
|
|
4028
4138
|
closeDatabase: () => closeDatabase
|
|
4029
4139
|
});
|
|
4030
|
-
import { existsSync as
|
|
4031
|
-
import { dirname, join as
|
|
4140
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, cpSync } from "fs";
|
|
4141
|
+
import { dirname, join as join3, resolve } from "path";
|
|
4032
4142
|
function isInMemoryDb(path) {
|
|
4033
4143
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
4034
4144
|
}
|
|
@@ -4037,8 +4147,8 @@ function findNearestMementosDb(startDir) {
|
|
|
4037
4147
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
4038
4148
|
const legacyHomeDb = resolve(home, ".mementos", "mementos.db");
|
|
4039
4149
|
while (true) {
|
|
4040
|
-
const candidate =
|
|
4041
|
-
if (
|
|
4150
|
+
const candidate = join3(dir, ".mementos", "mementos.db");
|
|
4151
|
+
if (existsSync2(candidate) && resolve(candidate) !== legacyHomeDb)
|
|
4042
4152
|
return candidate;
|
|
4043
4153
|
const parent = dirname(dir);
|
|
4044
4154
|
if (parent === dir)
|
|
@@ -4050,7 +4160,7 @@ function findNearestMementosDb(startDir) {
|
|
|
4050
4160
|
function findGitRoot(startDir) {
|
|
4051
4161
|
let dir = resolve(startDir);
|
|
4052
4162
|
while (true) {
|
|
4053
|
-
if (
|
|
4163
|
+
if (existsSync2(join3(dir, ".git")))
|
|
4054
4164
|
return dir;
|
|
4055
4165
|
const parent = dirname(dir);
|
|
4056
4166
|
if (parent === dir)
|
|
@@ -4061,10 +4171,10 @@ function findGitRoot(startDir) {
|
|
|
4061
4171
|
}
|
|
4062
4172
|
function migrateGlobalDir() {
|
|
4063
4173
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
4064
|
-
const newDir =
|
|
4065
|
-
const oldDir =
|
|
4066
|
-
if (!
|
|
4067
|
-
mkdirSync2(
|
|
4174
|
+
const newDir = join3(home, ".hasna", "mementos");
|
|
4175
|
+
const oldDir = join3(home, ".mementos");
|
|
4176
|
+
if (!existsSync2(newDir) && existsSync2(oldDir)) {
|
|
4177
|
+
mkdirSync2(join3(home, ".hasna"), { recursive: true });
|
|
4068
4178
|
cpSync(oldDir, newDir, { recursive: true });
|
|
4069
4179
|
}
|
|
4070
4180
|
}
|
|
@@ -4080,18 +4190,18 @@ function getDbPath() {
|
|
|
4080
4190
|
if (process.env["MEMENTOS_DB_SCOPE"] === "project") {
|
|
4081
4191
|
const gitRoot = findGitRoot(cwd);
|
|
4082
4192
|
if (gitRoot) {
|
|
4083
|
-
return
|
|
4193
|
+
return join3(gitRoot, ".mementos", "mementos.db");
|
|
4084
4194
|
}
|
|
4085
4195
|
}
|
|
4086
4196
|
migrateGlobalDir();
|
|
4087
4197
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
4088
|
-
return
|
|
4198
|
+
return join3(home, ".hasna", "mementos", "mementos.db");
|
|
4089
4199
|
}
|
|
4090
4200
|
function ensureDir(filePath) {
|
|
4091
4201
|
if (isInMemoryDb(filePath))
|
|
4092
4202
|
return;
|
|
4093
4203
|
const dir = dirname(resolve(filePath));
|
|
4094
|
-
if (!
|
|
4204
|
+
if (!existsSync2(dir)) {
|
|
4095
4205
|
mkdirSync2(dir, { recursive: true });
|
|
4096
4206
|
}
|
|
4097
4207
|
}
|
|
@@ -4190,7 +4300,7 @@ function resetDatabase() {
|
|
|
4190
4300
|
_db = null;
|
|
4191
4301
|
_pg = null;
|
|
4192
4302
|
}
|
|
4193
|
-
function
|
|
4303
|
+
function now() {
|
|
4194
4304
|
return new Date().toISOString();
|
|
4195
4305
|
}
|
|
4196
4306
|
function uuid() {
|
|
@@ -4199,15 +4309,20 @@ function uuid() {
|
|
|
4199
4309
|
function shortUuid() {
|
|
4200
4310
|
return crypto.randomUUID().slice(0, 8);
|
|
4201
4311
|
}
|
|
4312
|
+
function escapeLikePrefix(s) {
|
|
4313
|
+
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
4314
|
+
}
|
|
4202
4315
|
function resolvePartialId(db, table, partialId) {
|
|
4203
4316
|
if (!ALLOWED_TABLES.has(table)) {
|
|
4204
4317
|
throw new Error(`Invalid table name: ${table}`);
|
|
4205
4318
|
}
|
|
4319
|
+
if (partialId === "")
|
|
4320
|
+
return null;
|
|
4206
4321
|
if (partialId.length >= 36) {
|
|
4207
4322
|
const row = db.query(`SELECT id FROM ${table} WHERE id = ?`).get(partialId);
|
|
4208
4323
|
return row?.id ?? null;
|
|
4209
4324
|
}
|
|
4210
|
-
const rows = db.query(`SELECT id FROM ${table} WHERE id LIKE
|
|
4325
|
+
const rows = db.query(`SELECT id FROM ${table} WHERE id LIKE ? ESCAPE '\\'`).all(`${escapeLikePrefix(partialId)}%`);
|
|
4211
4326
|
if (rows.length === 1) {
|
|
4212
4327
|
return rows[0].id;
|
|
4213
4328
|
}
|
|
@@ -4236,8 +4351,19 @@ var init_database = __esm(() => {
|
|
|
4236
4351
|
});
|
|
4237
4352
|
|
|
4238
4353
|
// src/types/index.ts
|
|
4239
|
-
var AgentConflictError, EntityNotFoundError, MemoryNotFoundError, VersionConflictError, MemoryConflictError;
|
|
4354
|
+
var MEMORY_SCOPES, MEMORY_CATEGORIES, MEMORY_SOURCES, MEMORY_STATUSES, AgentConflictError, EntityNotFoundError, MemoryNotFoundError, VersionConflictError, MemoryConflictError;
|
|
4240
4355
|
var init_types = __esm(() => {
|
|
4356
|
+
MEMORY_SCOPES = ["global", "shared", "private", "working"];
|
|
4357
|
+
MEMORY_CATEGORIES = [
|
|
4358
|
+
"preference",
|
|
4359
|
+
"fact",
|
|
4360
|
+
"knowledge",
|
|
4361
|
+
"history",
|
|
4362
|
+
"procedural",
|
|
4363
|
+
"resource"
|
|
4364
|
+
];
|
|
4365
|
+
MEMORY_SOURCES = ["user", "agent", "system", "auto", "imported"];
|
|
4366
|
+
MEMORY_STATUSES = ["active", "archived", "expired"];
|
|
4241
4367
|
AgentConflictError = class AgentConflictError extends Error {
|
|
4242
4368
|
conflict = true;
|
|
4243
4369
|
existing_id;
|
|
@@ -4394,6 +4520,41 @@ var init_redact = __esm(() => {
|
|
|
4394
4520
|
];
|
|
4395
4521
|
});
|
|
4396
4522
|
|
|
4523
|
+
// src/lib/enum-validation.ts
|
|
4524
|
+
function formatEnumViolation(v) {
|
|
4525
|
+
return `Invalid ${v.field}: "${v.value}". Allowed values: ${v.allowed.join(", ")}.`;
|
|
4526
|
+
}
|
|
4527
|
+
function validateEnumField(field, value) {
|
|
4528
|
+
const allowed = ENUM_FIELDS[field];
|
|
4529
|
+
if (!allowed)
|
|
4530
|
+
return null;
|
|
4531
|
+
if (value === undefined || value === null || value === "")
|
|
4532
|
+
return null;
|
|
4533
|
+
if (typeof value === "string" && allowed.includes(value))
|
|
4534
|
+
return null;
|
|
4535
|
+
return { field, value: String(value), allowed };
|
|
4536
|
+
}
|
|
4537
|
+
function validateMemoryEnums(input) {
|
|
4538
|
+
for (const field of Object.keys(ENUM_FIELDS)) {
|
|
4539
|
+
if (!(field in input))
|
|
4540
|
+
continue;
|
|
4541
|
+
const violation = validateEnumField(field, input[field]);
|
|
4542
|
+
if (violation)
|
|
4543
|
+
return violation;
|
|
4544
|
+
}
|
|
4545
|
+
return null;
|
|
4546
|
+
}
|
|
4547
|
+
var ENUM_FIELDS;
|
|
4548
|
+
var init_enum_validation = __esm(() => {
|
|
4549
|
+
init_types();
|
|
4550
|
+
ENUM_FIELDS = {
|
|
4551
|
+
category: MEMORY_CATEGORIES,
|
|
4552
|
+
scope: MEMORY_SCOPES,
|
|
4553
|
+
source: MEMORY_SOURCES,
|
|
4554
|
+
status: MEMORY_STATUSES
|
|
4555
|
+
};
|
|
4556
|
+
});
|
|
4557
|
+
|
|
4397
4558
|
// src/lib/hooks.ts
|
|
4398
4559
|
var exports_hooks = {};
|
|
4399
4560
|
__export(exports_hooks, {
|
|
@@ -4565,7 +4726,7 @@ function linkEntityToMemory(entityId, memoryId, role = "context", db) {
|
|
|
4565
4726
|
return data;
|
|
4566
4727
|
}
|
|
4567
4728
|
const d = db || getDatabase();
|
|
4568
|
-
const timestamp =
|
|
4729
|
+
const timestamp = now();
|
|
4569
4730
|
d.run(`INSERT OR IGNORE INTO entity_memories (entity_id, memory_id, role, created_at)
|
|
4570
4731
|
VALUES (?, ?, ?, ?)`, [entityId, memoryId, role, timestamp]);
|
|
4571
4732
|
const row = d.query("SELECT * FROM entity_memories WHERE entity_id = ? AND memory_id = ?").get(entityId, memoryId);
|
|
@@ -4691,11 +4852,14 @@ function parseMemoryRow(row) {
|
|
|
4691
4852
|
}
|
|
4692
4853
|
function createMemory(input, dedupeMode = "merge", db) {
|
|
4693
4854
|
if (!db && isApiMode()) {
|
|
4694
|
-
const { data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
|
|
4855
|
+
const { status, data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
|
|
4856
|
+
if (!data || !data.id) {
|
|
4857
|
+
throw new ApiRequestError(`mementos cloud POST /memories \u2192 ${status} but no memory was returned; the write did not persist (key: ${input.key})`, status, "");
|
|
4858
|
+
}
|
|
4695
4859
|
return data;
|
|
4696
4860
|
}
|
|
4697
4861
|
const d = db || getDatabase();
|
|
4698
|
-
const timestamp =
|
|
4862
|
+
const timestamp = now();
|
|
4699
4863
|
if (input.project_id) {
|
|
4700
4864
|
const resolved = resolvePartialId(d, "projects", input.project_id);
|
|
4701
4865
|
if (resolved) {
|
|
@@ -4830,19 +4994,28 @@ function bulkUpsertMemories(memories, db) {
|
|
|
4830
4994
|
const d = db || getDatabase();
|
|
4831
4995
|
let inserted = 0;
|
|
4832
4996
|
let skipped = 0;
|
|
4997
|
+
let rejected = 0;
|
|
4833
4998
|
const errors = [];
|
|
4834
|
-
const insert = d.prepare(`INSERT
|
|
4835
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4999
|
+
const insert = d.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)
|
|
5000
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
5001
|
+
ON CONFLICT DO NOTHING`);
|
|
4836
5002
|
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
4837
5003
|
for (const mem of memories) {
|
|
4838
5004
|
const key = mem["key"];
|
|
4839
5005
|
const id = mem["id"] || uuid();
|
|
4840
5006
|
try {
|
|
4841
5007
|
if (!key) {
|
|
4842
|
-
|
|
5008
|
+
rejected++;
|
|
5009
|
+
errors.push(`rejected row without key (id=${id})`);
|
|
5010
|
+
continue;
|
|
5011
|
+
}
|
|
5012
|
+
const violation = validateMemoryEnums(mem);
|
|
5013
|
+
if (violation) {
|
|
5014
|
+
rejected++;
|
|
5015
|
+
errors.push(`Rejected "${key}": ${formatEnumViolation(violation)}`);
|
|
4843
5016
|
continue;
|
|
4844
5017
|
}
|
|
4845
|
-
const timestamp =
|
|
5018
|
+
const timestamp = now();
|
|
4846
5019
|
let tags = [];
|
|
4847
5020
|
const rawTags = mem["tags"];
|
|
4848
5021
|
if (Array.isArray(rawTags)) {
|
|
@@ -4888,13 +5061,14 @@ function bulkUpsertMemories(memories, db) {
|
|
|
4888
5061
|
skipped++;
|
|
4889
5062
|
}
|
|
4890
5063
|
} catch (e) {
|
|
5064
|
+
rejected++;
|
|
4891
5065
|
errors.push(`Failed "${String(key)}": ${e instanceof Error ? e.message : String(e)}`);
|
|
4892
5066
|
}
|
|
4893
5067
|
}
|
|
4894
|
-
return { inserted, skipped, errors, total: memories.length };
|
|
5068
|
+
return { inserted, skipped, rejected, errors, total: memories.length };
|
|
4895
5069
|
}
|
|
4896
5070
|
function ensureMemoryReferences(d, input) {
|
|
4897
|
-
const t =
|
|
5071
|
+
const t = now();
|
|
4898
5072
|
const tryRun = (sql, params) => {
|
|
4899
5073
|
try {
|
|
4900
5074
|
d.run(sql, params);
|
|
@@ -4919,7 +5093,7 @@ function listMemoriesByKey(key, db) {
|
|
|
4919
5093
|
}
|
|
4920
5094
|
function getMemory(id, db) {
|
|
4921
5095
|
if (!db && isApiMode()) {
|
|
4922
|
-
const { status, data } = apiJson("GET", `/memories/${encodeURIComponent(id)}
|
|
5096
|
+
const { status, data } = apiJson("GET", `/memories/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
4923
5097
|
return status === 404 ? null : data ?? null;
|
|
4924
5098
|
}
|
|
4925
5099
|
const d = db || getDatabase();
|
|
@@ -5252,38 +5426,24 @@ function getMemoryEmbeddings(ids, db) {
|
|
|
5252
5426
|
}
|
|
5253
5427
|
function updateMemory(id, input, db) {
|
|
5254
5428
|
if (!db && isApiMode()) {
|
|
5255
|
-
const { status, data } = apiJson("PATCH", `/memories/${encodeURIComponent(id)}`, input);
|
|
5429
|
+
const { status, data } = apiJson("PATCH", `/memories/${encodeURIComponent(id)}`, input, { allow404: true });
|
|
5256
5430
|
if (status === 404)
|
|
5257
5431
|
throw new MemoryNotFoundError(id);
|
|
5432
|
+
if (data && typeof input.version === "number" && typeof data.version === "number" && data.version <= input.version) {
|
|
5433
|
+
throw new Error(`Update did not persist for memory ${id}: the server returned success but the record is unchanged ` + `(version still ${data.version}). Your data was NOT written. ` + `The server is likely running a build predating the partial-id fix \u2014 pass the full 36-character id as a workaround.`);
|
|
5434
|
+
}
|
|
5258
5435
|
return data;
|
|
5259
5436
|
}
|
|
5260
5437
|
const d = db || getDatabase();
|
|
5261
5438
|
const existing = getMemory(id, d);
|
|
5262
5439
|
if (!existing)
|
|
5263
5440
|
throw new MemoryNotFoundError(id);
|
|
5441
|
+
const memoryId = existing.id;
|
|
5264
5442
|
if (existing.version !== input.version) {
|
|
5265
5443
|
throw new VersionConflictError(id, input.version, existing.version);
|
|
5266
5444
|
}
|
|
5267
|
-
try {
|
|
5268
|
-
d.run(`INSERT OR IGNORE INTO memory_versions (id, memory_id, version, value, importance, scope, category, tags, summary, pinned, status, when_to_use, created_at)
|
|
5269
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
5270
|
-
uuid(),
|
|
5271
|
-
existing.id,
|
|
5272
|
-
existing.version,
|
|
5273
|
-
existing.value,
|
|
5274
|
-
existing.importance,
|
|
5275
|
-
existing.scope,
|
|
5276
|
-
existing.category,
|
|
5277
|
-
JSON.stringify(existing.tags),
|
|
5278
|
-
existing.summary,
|
|
5279
|
-
existing.pinned ? 1 : 0,
|
|
5280
|
-
existing.status,
|
|
5281
|
-
existing.when_to_use || null,
|
|
5282
|
-
existing.updated_at
|
|
5283
|
-
]);
|
|
5284
|
-
} catch {}
|
|
5285
5445
|
const sets = ["version = version + 1", "updated_at = ?"];
|
|
5286
|
-
const params = [
|
|
5446
|
+
const params = [now()];
|
|
5287
5447
|
if (input.value !== undefined) {
|
|
5288
5448
|
sets.push("value = ?");
|
|
5289
5449
|
params.push(redactSecrets(input.value));
|
|
@@ -5331,15 +5491,18 @@ function updateMemory(id, input, db) {
|
|
|
5331
5491
|
if (input.tags !== undefined) {
|
|
5332
5492
|
sets.push("tags = ?");
|
|
5333
5493
|
params.push(JSON.stringify(input.tags));
|
|
5334
|
-
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [
|
|
5494
|
+
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [memoryId]);
|
|
5335
5495
|
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
5336
5496
|
for (const tag of input.tags) {
|
|
5337
|
-
insertTag.run(
|
|
5497
|
+
insertTag.run(memoryId, tag);
|
|
5338
5498
|
}
|
|
5339
5499
|
}
|
|
5340
|
-
params.push(
|
|
5341
|
-
d.run(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
5342
|
-
|
|
5500
|
+
params.push(memoryId);
|
|
5501
|
+
const result = d.run(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
5502
|
+
if (result.changes === 0) {
|
|
5503
|
+
throw new Error(`Update affected no rows for memory ${memoryId}: the record was read but not written. ` + `This is a bug in @hasna/mementos, not a bad argument \u2014 please report it.`);
|
|
5504
|
+
}
|
|
5505
|
+
const updated = getMemory(memoryId, d);
|
|
5343
5506
|
if (input.value !== undefined) {
|
|
5344
5507
|
try {
|
|
5345
5508
|
const oldLinks = getEntityMemoryLinks(undefined, updated.id, d);
|
|
@@ -5360,14 +5523,15 @@ function updateMemory(id, input, db) {
|
|
|
5360
5523
|
}
|
|
5361
5524
|
function deleteMemory(id, db) {
|
|
5362
5525
|
if (!db && isApiMode()) {
|
|
5363
|
-
const { status } = apiJson("DELETE", `/memories/${encodeURIComponent(id)}
|
|
5526
|
+
const { status } = apiJson("DELETE", `/memories/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
5364
5527
|
return status !== 404;
|
|
5365
5528
|
}
|
|
5366
5529
|
const d = db || getDatabase();
|
|
5367
|
-
const
|
|
5530
|
+
const memoryId = resolvePartialId(d, "memories", id) ?? id;
|
|
5531
|
+
const result = d.run("DELETE FROM memories WHERE id = ?", [memoryId]);
|
|
5368
5532
|
if (result.changes > 0) {
|
|
5369
5533
|
hookRegistry.runHooks("PostMemoryDelete", {
|
|
5370
|
-
memoryId
|
|
5534
|
+
memoryId,
|
|
5371
5535
|
timestamp: Date.now()
|
|
5372
5536
|
});
|
|
5373
5537
|
}
|
|
@@ -5381,11 +5545,12 @@ function bulkDeleteMemories(ids, db) {
|
|
|
5381
5545
|
return data?.deleted ?? 0;
|
|
5382
5546
|
}
|
|
5383
5547
|
const d = db || getDatabase();
|
|
5384
|
-
const
|
|
5385
|
-
const
|
|
5548
|
+
const resolvedIds = ids.map((id) => resolvePartialId(d, "memories", id) ?? id);
|
|
5549
|
+
const placeholders = resolvedIds.map(() => "?").join(",");
|
|
5550
|
+
const countRow = d.query(`SELECT COUNT(*) as c FROM memories WHERE id IN (${placeholders})`).get(...resolvedIds);
|
|
5386
5551
|
const count = countRow.c;
|
|
5387
5552
|
if (count > 0) {
|
|
5388
|
-
d.run(`DELETE FROM memories WHERE id IN (${placeholders})`,
|
|
5553
|
+
d.run(`DELETE FROM memories WHERE id IN (${placeholders})`, resolvedIds);
|
|
5389
5554
|
}
|
|
5390
5555
|
return count;
|
|
5391
5556
|
}
|
|
@@ -5393,14 +5558,14 @@ function touchMemory(id, db) {
|
|
|
5393
5558
|
if (!db && isApiMode())
|
|
5394
5559
|
return;
|
|
5395
5560
|
const d = db || getDatabase();
|
|
5396
|
-
d.run("UPDATE memories SET access_count = access_count + 1, accessed_at = ? WHERE id = ?", [
|
|
5561
|
+
d.run("UPDATE memories SET access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now(), id]);
|
|
5397
5562
|
}
|
|
5398
5563
|
function incrementRecallCount(id, db) {
|
|
5399
5564
|
if (!db && isApiMode())
|
|
5400
5565
|
return;
|
|
5401
5566
|
const d = db || getDatabase();
|
|
5402
5567
|
try {
|
|
5403
|
-
d.run("UPDATE memories SET recall_count = recall_count + 1, access_count = access_count + 1, accessed_at = ? WHERE id = ?", [
|
|
5568
|
+
d.run("UPDATE memories SET recall_count = recall_count + 1, access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now(), id]);
|
|
5404
5569
|
const row = d.query("SELECT recall_count, importance FROM memories WHERE id = ?").get(id);
|
|
5405
5570
|
if (!row)
|
|
5406
5571
|
return;
|
|
@@ -5417,7 +5582,7 @@ function cleanExpiredMemories(db) {
|
|
|
5417
5582
|
return data?.cleaned ?? 0;
|
|
5418
5583
|
}
|
|
5419
5584
|
const d = db || getDatabase();
|
|
5420
|
-
const timestamp =
|
|
5585
|
+
const timestamp = now();
|
|
5421
5586
|
const countRow = d.query("SELECT COUNT(*) as c FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?").get(timestamp);
|
|
5422
5587
|
const count = countRow.c;
|
|
5423
5588
|
if (count > 0) {
|
|
@@ -5512,6 +5677,7 @@ var init_memories = __esm(() => {
|
|
|
5512
5677
|
init_types();
|
|
5513
5678
|
init_database();
|
|
5514
5679
|
init_redact();
|
|
5680
|
+
init_enum_validation();
|
|
5515
5681
|
init_hooks();
|
|
5516
5682
|
init_poisoning();
|
|
5517
5683
|
init_entity_memories();
|
|
@@ -5547,7 +5713,7 @@ function registerProject(name, path, description, memoryPrefix, db) {
|
|
|
5547
5713
|
return data;
|
|
5548
5714
|
}
|
|
5549
5715
|
const d = db || getDatabase();
|
|
5550
|
-
const timestamp =
|
|
5716
|
+
const timestamp = now();
|
|
5551
5717
|
const existing = d.query("SELECT * FROM projects WHERE path = ?").get(path);
|
|
5552
5718
|
if (existing) {
|
|
5553
5719
|
const existingId = existing["id"];
|
|
@@ -5563,7 +5729,7 @@ function registerProject(name, path, description, memoryPrefix, db) {
|
|
|
5563
5729
|
}
|
|
5564
5730
|
function getProject(idOrPath, db) {
|
|
5565
5731
|
if (!db && isApiMode()) {
|
|
5566
|
-
const { status, data } = apiJson("GET", `/projects/${encodeURIComponent(idOrPath)}
|
|
5732
|
+
const { status, data } = apiJson("GET", `/projects/${encodeURIComponent(idOrPath)}`, undefined, { allow404: true });
|
|
5567
5733
|
if (status === 404 || !data)
|
|
5568
5734
|
return null;
|
|
5569
5735
|
return data;
|
|
@@ -5613,7 +5779,7 @@ function createEntity(input, db) {
|
|
|
5613
5779
|
return data;
|
|
5614
5780
|
}
|
|
5615
5781
|
const d = db || getDatabase();
|
|
5616
|
-
const timestamp =
|
|
5782
|
+
const timestamp = now();
|
|
5617
5783
|
const metadataJson = JSON.stringify(input.metadata || {});
|
|
5618
5784
|
const existing = d.query(`SELECT * FROM entities
|
|
5619
5785
|
WHERE name = ? AND type = ? AND COALESCE(project_id, '') = ?`).get(input.name, input.type, input.project_id || "");
|
|
@@ -5656,7 +5822,7 @@ function createEntity(input, db) {
|
|
|
5656
5822
|
}
|
|
5657
5823
|
function getEntity(id, db) {
|
|
5658
5824
|
if (!db && isApiMode()) {
|
|
5659
|
-
const { status, data } = apiJson("GET", `/entities/${encodeURIComponent(id)}
|
|
5825
|
+
const { status, data } = apiJson("GET", `/entities/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
5660
5826
|
if (status === 404 || !data)
|
|
5661
5827
|
throw new EntityNotFoundError(id);
|
|
5662
5828
|
return data;
|
|
@@ -5739,7 +5905,7 @@ function listEntities(filter = {}, db) {
|
|
|
5739
5905
|
}
|
|
5740
5906
|
function deleteEntity(id, db) {
|
|
5741
5907
|
if (!db && isApiMode()) {
|
|
5742
|
-
const { status } = apiJson("DELETE", `/entities/${encodeURIComponent(id)}
|
|
5908
|
+
const { status } = apiJson("DELETE", `/entities/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
5743
5909
|
if (status === 404)
|
|
5744
5910
|
throw new EntityNotFoundError(id);
|
|
5745
5911
|
return;
|
|
@@ -5789,7 +5955,7 @@ function mergeEntities(sourceId, targetId, db) {
|
|
|
5789
5955
|
d.run(`UPDATE entity_memories SET entity_id = ? WHERE entity_id = ?`, [tgt, src]);
|
|
5790
5956
|
d.run("DELETE FROM entity_memories WHERE entity_id = ?", [src]);
|
|
5791
5957
|
d.run("DELETE FROM entities WHERE id = ?", [src]);
|
|
5792
|
-
d.run("UPDATE entities SET updated_at = ? WHERE id = ?", [
|
|
5958
|
+
d.run("UPDATE entities SET updated_at = ? WHERE id = ?", [now(), tgt]);
|
|
5793
5959
|
return getEntity(tgt, d);
|
|
5794
5960
|
}
|
|
5795
5961
|
var init_entities = __esm(() => {
|
|
@@ -6454,7 +6620,7 @@ function registerAgent(name, sessionId, description, role, projectId, db) {
|
|
|
6454
6620
|
return data;
|
|
6455
6621
|
}
|
|
6456
6622
|
const d = db || getDatabase();
|
|
6457
|
-
const timestamp =
|
|
6623
|
+
const timestamp = now();
|
|
6458
6624
|
const normalizedName = name.trim().toLowerCase();
|
|
6459
6625
|
if (projectId) {
|
|
6460
6626
|
const resolvedProjectId = resolvePartialId(d, "projects", projectId);
|
|
@@ -6503,7 +6669,7 @@ function registerAgent(name, sessionId, description, role, projectId, db) {
|
|
|
6503
6669
|
}
|
|
6504
6670
|
function getAgent(idOrName, db) {
|
|
6505
6671
|
if (!db && isApiMode()) {
|
|
6506
|
-
const { status, data } = apiJson("GET", `/agents/${encodeURIComponent(idOrName)}
|
|
6672
|
+
const { status, data } = apiJson("GET", `/agents/${encodeURIComponent(idOrName)}`, undefined, { allow404: true });
|
|
6507
6673
|
if (status === 404 || !data)
|
|
6508
6674
|
return null;
|
|
6509
6675
|
return data;
|
|
@@ -6515,7 +6681,7 @@ function getAgent(idOrName, db) {
|
|
|
6515
6681
|
row = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(idOrName.trim().toLowerCase());
|
|
6516
6682
|
if (row)
|
|
6517
6683
|
return parseAgentRow(row);
|
|
6518
|
-
const rows = d.query("SELECT * FROM agents WHERE id LIKE ?").all(`${idOrName}%`);
|
|
6684
|
+
const rows = d.query("SELECT * FROM agents WHERE id LIKE ? ESCAPE '\\'").all(`${escapeLikePrefix(idOrName)}%`);
|
|
6519
6685
|
if (rows.length === 1)
|
|
6520
6686
|
return parseAgentRow(rows[0]);
|
|
6521
6687
|
return null;
|
|
@@ -6541,7 +6707,7 @@ function touchAgent(idOrName, db) {
|
|
|
6541
6707
|
const agent = getAgent(idOrName, d);
|
|
6542
6708
|
if (!agent)
|
|
6543
6709
|
return;
|
|
6544
|
-
d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [
|
|
6710
|
+
d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [now(), agent.id]);
|
|
6545
6711
|
}
|
|
6546
6712
|
function listAgentsByProject(projectId, db) {
|
|
6547
6713
|
if (!db && isApiMode()) {
|
|
@@ -6556,7 +6722,7 @@ function listAgentsByProject(projectId, db) {
|
|
|
6556
6722
|
}
|
|
6557
6723
|
function updateAgent(id, updates, db) {
|
|
6558
6724
|
if (!db && isApiMode()) {
|
|
6559
|
-
const { status, data } = apiJson("PATCH", `/agents/${encodeURIComponent(id)}`, updates);
|
|
6725
|
+
const { status, data } = apiJson("PATCH", `/agents/${encodeURIComponent(id)}`, updates, { allow404: true });
|
|
6560
6726
|
if (status === 404 || !data)
|
|
6561
6727
|
return null;
|
|
6562
6728
|
return data;
|
|
@@ -6565,7 +6731,7 @@ function updateAgent(id, updates, db) {
|
|
|
6565
6731
|
const agent = getAgent(id, d);
|
|
6566
6732
|
if (!agent)
|
|
6567
6733
|
return null;
|
|
6568
|
-
const timestamp =
|
|
6734
|
+
const timestamp = now();
|
|
6569
6735
|
if (updates.name) {
|
|
6570
6736
|
const normalizedNewName = updates.name.trim().toLowerCase();
|
|
6571
6737
|
if (normalizedNewName !== agent.name) {
|
|
@@ -7402,7 +7568,7 @@ function createRelation(input, db) {
|
|
|
7402
7568
|
}
|
|
7403
7569
|
const d = db || getDatabase();
|
|
7404
7570
|
const id = shortUuid();
|
|
7405
|
-
const timestamp =
|
|
7571
|
+
const timestamp = now();
|
|
7406
7572
|
const weight = input.weight ?? 1;
|
|
7407
7573
|
const metadata = JSON.stringify(input.metadata ?? {});
|
|
7408
7574
|
d.run(`INSERT INTO relations (id, source_entity_id, target_entity_id, relation_type, weight, metadata, created_at)
|
|
@@ -7455,7 +7621,7 @@ function listRelations(filter, db) {
|
|
|
7455
7621
|
}
|
|
7456
7622
|
function deleteRelation(id, db) {
|
|
7457
7623
|
if (!db && isApiMode()) {
|
|
7458
|
-
const { status } = apiJson("DELETE", `/relations/${encodeURIComponent(id)}
|
|
7624
|
+
const { status } = apiJson("DELETE", `/relations/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
7459
7625
|
if (status === 404)
|
|
7460
7626
|
throw new Error(`Relation not found: ${id}`);
|
|
7461
7627
|
return;
|
|
@@ -8109,6 +8275,32 @@ class AutoMemoryQueue {
|
|
|
8109
8275
|
getStats() {
|
|
8110
8276
|
return { ...this.stats, pending: this.queue.length };
|
|
8111
8277
|
}
|
|
8278
|
+
async waitForIdleForTests(timeoutMs = 3000) {
|
|
8279
|
+
const start = Date.now();
|
|
8280
|
+
while (Date.now() - start < timeoutMs) {
|
|
8281
|
+
if (this.queue.length === 0 && this.activeCount === 0)
|
|
8282
|
+
return;
|
|
8283
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
8284
|
+
}
|
|
8285
|
+
throw new Error("autoMemoryQueue did not become idle before test reset");
|
|
8286
|
+
}
|
|
8287
|
+
resetForTests(handler) {
|
|
8288
|
+
if (this.activeCount !== 0) {
|
|
8289
|
+
throw new Error("Cannot reset autoMemoryQueue while jobs are processing");
|
|
8290
|
+
}
|
|
8291
|
+
this.queue = [];
|
|
8292
|
+
this.running = false;
|
|
8293
|
+
this.stats = {
|
|
8294
|
+
pending: 0,
|
|
8295
|
+
processing: 0,
|
|
8296
|
+
processed: 0,
|
|
8297
|
+
failed: 0,
|
|
8298
|
+
dropped: 0
|
|
8299
|
+
};
|
|
8300
|
+
if (handler !== undefined) {
|
|
8301
|
+
this.handler = handler;
|
|
8302
|
+
}
|
|
8303
|
+
}
|
|
8112
8304
|
startLoop() {
|
|
8113
8305
|
this.running = true;
|
|
8114
8306
|
this.loop();
|
|
@@ -8154,6 +8346,7 @@ var init_auto_memory_queue = __esm(() => {
|
|
|
8154
8346
|
// src/lib/auto-memory.ts
|
|
8155
8347
|
var exports_auto_memory = {};
|
|
8156
8348
|
__export(exports_auto_memory, {
|
|
8349
|
+
resetAutoMemoryForTests: () => resetAutoMemoryForTests,
|
|
8157
8350
|
processConversationTurn: () => processConversationTurn,
|
|
8158
8351
|
getAutoMemoryStats: () => getAutoMemoryStats,
|
|
8159
8352
|
configureAutoMemory: () => configureAutoMemory
|
|
@@ -8314,6 +8507,12 @@ function getAutoMemoryStats() {
|
|
|
8314
8507
|
function configureAutoMemory(config) {
|
|
8315
8508
|
providerRegistry.configure(config);
|
|
8316
8509
|
}
|
|
8510
|
+
async function resetAutoMemoryForTests() {
|
|
8511
|
+
if (autoMemoryQueue.getStats().processing > 0) {
|
|
8512
|
+
await autoMemoryQueue.waitForIdleForTests();
|
|
8513
|
+
}
|
|
8514
|
+
autoMemoryQueue.resetForTests(processJob);
|
|
8515
|
+
}
|
|
8317
8516
|
var DEDUP_SIMILARITY_THRESHOLD = 0.85;
|
|
8318
8517
|
var init_auto_memory = __esm(() => {
|
|
8319
8518
|
init_memories();
|
|
@@ -8367,7 +8566,7 @@ function createWebhookHook(input, db) {
|
|
|
8367
8566
|
}
|
|
8368
8567
|
const d = db || getDatabase();
|
|
8369
8568
|
const id = shortUuid();
|
|
8370
|
-
const timestamp =
|
|
8569
|
+
const timestamp = now();
|
|
8371
8570
|
d.run(`INSERT INTO webhook_hooks
|
|
8372
8571
|
(id, type, handler_url, priority, blocking, agent_id, project_id, description, enabled, created_at, invocation_count, failure_count)
|
|
8373
8572
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, 0, 0)`, [
|
|
@@ -8385,7 +8584,7 @@ function createWebhookHook(input, db) {
|
|
|
8385
8584
|
}
|
|
8386
8585
|
function getWebhookHook(id, db) {
|
|
8387
8586
|
if (!db && isApiMode()) {
|
|
8388
|
-
const { status, data } = apiJson("GET", `/webhooks/${encodeURIComponent(id)}
|
|
8587
|
+
const { status, data } = apiJson("GET", `/webhooks/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
8389
8588
|
if (status === 404 || !data)
|
|
8390
8589
|
return null;
|
|
8391
8590
|
return data;
|
|
@@ -8421,7 +8620,7 @@ function updateWebhookHook(id, updates, db) {
|
|
|
8421
8620
|
enabled: updates.enabled,
|
|
8422
8621
|
priority: updates.priority,
|
|
8423
8622
|
description: updates.description
|
|
8424
|
-
});
|
|
8623
|
+
}, { allow404: true });
|
|
8425
8624
|
if (status === 404 || !data)
|
|
8426
8625
|
return null;
|
|
8427
8626
|
return data;
|
|
@@ -8452,7 +8651,7 @@ function updateWebhookHook(id, updates, db) {
|
|
|
8452
8651
|
}
|
|
8453
8652
|
function deleteWebhookHook(id, db) {
|
|
8454
8653
|
if (!db && isApiMode()) {
|
|
8455
|
-
const { status } = apiJson("DELETE", `/webhooks/${encodeURIComponent(id)}
|
|
8654
|
+
const { status } = apiJson("DELETE", `/webhooks/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
8456
8655
|
return status === 204 || status === 200;
|
|
8457
8656
|
}
|
|
8458
8657
|
const d = db || getDatabase();
|
|
@@ -8550,7 +8749,7 @@ function parseEventRow(row) {
|
|
|
8550
8749
|
function createSynthesisRun(input, db) {
|
|
8551
8750
|
const d = db || getDatabase();
|
|
8552
8751
|
const id = shortUuid();
|
|
8553
|
-
const timestamp =
|
|
8752
|
+
const timestamp = now();
|
|
8554
8753
|
d.run(`INSERT INTO synthesis_runs (id, triggered_by, project_id, agent_id, corpus_size, proposals_generated, proposals_accepted, proposals_rejected, status, started_at)
|
|
8555
8754
|
VALUES (?, ?, ?, ?, ?, 0, 0, 0, 'pending', ?)`, [
|
|
8556
8755
|
id,
|
|
@@ -8651,7 +8850,7 @@ function updateSynthesisRun(id, updates, db) {
|
|
|
8651
8850
|
function createProposal(input, db) {
|
|
8652
8851
|
const d = db || getDatabase();
|
|
8653
8852
|
const id = shortUuid();
|
|
8654
|
-
const timestamp =
|
|
8853
|
+
const timestamp = now();
|
|
8655
8854
|
d.run(`INSERT INTO synthesis_proposals (id, run_id, proposal_type, memory_ids, target_memory_id, proposed_changes, reasoning, confidence, status, created_at)
|
|
8656
8855
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?)`, [
|
|
8657
8856
|
id,
|
|
@@ -8710,7 +8909,7 @@ function updateProposal(id, updates, db) {
|
|
|
8710
8909
|
function createMetric(input, db) {
|
|
8711
8910
|
const d = db || getDatabase();
|
|
8712
8911
|
const id = shortUuid();
|
|
8713
|
-
const timestamp =
|
|
8912
|
+
const timestamp = now();
|
|
8714
8913
|
d.run(`INSERT INTO synthesis_metrics (id, run_id, metric_type, value, baseline, created_at)
|
|
8715
8914
|
VALUES (?, ?, ?, ?, ?, ?)`, [id, input.run_id, input.metric_type, input.value, input.baseline ?? null, timestamp]);
|
|
8716
8915
|
return { id, run_id: input.run_id, metric_type: input.metric_type, value: input.value, baseline: input.baseline ?? null, created_at: timestamp };
|
|
@@ -8724,7 +8923,7 @@ function recordSynthesisEvent(input, db) {
|
|
|
8724
8923
|
try {
|
|
8725
8924
|
const d = db || getDatabase();
|
|
8726
8925
|
const id = shortUuid();
|
|
8727
|
-
const timestamp =
|
|
8926
|
+
const timestamp = now();
|
|
8728
8927
|
d.run(`INSERT INTO synthesis_events (id, event_type, memory_id, agent_id, project_id, session_id, query, importance_at_time, metadata, created_at)
|
|
8729
8928
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
8730
8929
|
id,
|
|
@@ -8978,7 +9177,7 @@ __export(exports_contradiction, {
|
|
|
8978
9177
|
});
|
|
8979
9178
|
function invalidateFact(oldMemoryId, newMemoryId, db) {
|
|
8980
9179
|
const d = db || getDatabase();
|
|
8981
|
-
const timestamp =
|
|
9180
|
+
const timestamp = now();
|
|
8982
9181
|
d.run("UPDATE memories SET valid_until = ?, updated_at = ? WHERE id = ?", [timestamp, timestamp, oldMemoryId]);
|
|
8983
9182
|
if (newMemoryId) {
|
|
8984
9183
|
const row = d.query("SELECT metadata FROM memories WHERE id = ?").get(newMemoryId);
|
|
@@ -9151,6 +9350,8 @@ var init_built_in_hooks = __esm(() => {
|
|
|
9151
9350
|
priority: 100,
|
|
9152
9351
|
description: "Trigger async LLM entity extraction when a memory is saved",
|
|
9153
9352
|
handler: async (ctx) => {
|
|
9353
|
+
if (process.env["NODE_ENV"] === "test")
|
|
9354
|
+
return;
|
|
9154
9355
|
if (ctx.wasUpdated)
|
|
9155
9356
|
return;
|
|
9156
9357
|
const processConversationTurn2 = await getAutoMemory();
|
|
@@ -9456,7 +9657,7 @@ async function buildCorpus(options) {
|
|
|
9456
9657
|
duplicateCandidates,
|
|
9457
9658
|
lowImportanceHighRecall,
|
|
9458
9659
|
highImportanceLowRecall,
|
|
9459
|
-
generatedAt:
|
|
9660
|
+
generatedAt: now()
|
|
9460
9661
|
};
|
|
9461
9662
|
}
|
|
9462
9663
|
var init_corpus_builder = __esm(() => {
|
|
@@ -9789,7 +9990,7 @@ async function executeProposals(runId, proposals, db) {
|
|
|
9789
9990
|
const rollback = executeProposal(proposal, d);
|
|
9790
9991
|
updateProposal(proposal.id, {
|
|
9791
9992
|
status: "accepted",
|
|
9792
|
-
executed_at:
|
|
9993
|
+
executed_at: now(),
|
|
9793
9994
|
rollback_data: rollback
|
|
9794
9995
|
}, d);
|
|
9795
9996
|
rollbackData[proposal.id] = rollback;
|
|
@@ -9838,7 +10039,7 @@ function executeArchive(proposal, d) {
|
|
|
9838
10039
|
if (!mem)
|
|
9839
10040
|
continue;
|
|
9840
10041
|
rollback[memId] = mem.status;
|
|
9841
|
-
d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [
|
|
10042
|
+
d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [now(), memId]);
|
|
9842
10043
|
}
|
|
9843
10044
|
return { old_status: rollback };
|
|
9844
10045
|
}
|
|
@@ -9853,7 +10054,7 @@ function executePromote(proposal, d) {
|
|
|
9853
10054
|
if (!mem)
|
|
9854
10055
|
continue;
|
|
9855
10056
|
rollback[memId] = mem.importance;
|
|
9856
|
-
d.run("UPDATE memories SET importance = ?, updated_at = ? WHERE id = ?", [Math.max(1, Math.min(10, Math.round(newImportance))),
|
|
10057
|
+
d.run("UPDATE memories SET importance = ?, updated_at = ? WHERE id = ?", [Math.max(1, Math.min(10, Math.round(newImportance))), now(), memId]);
|
|
9857
10058
|
}
|
|
9858
10059
|
return { old_importance: rollback };
|
|
9859
10060
|
}
|
|
@@ -9870,7 +10071,7 @@ function executeUpdateValue(proposal, d) {
|
|
|
9870
10071
|
if (!mem)
|
|
9871
10072
|
throw new Error(`Memory ${memId} not found`);
|
|
9872
10073
|
rollback[memId] = { value: mem.value, version: mem.version };
|
|
9873
|
-
d.run("UPDATE memories SET value = ?, version = version + 1, updated_at = ? WHERE id = ?", [newValue,
|
|
10074
|
+
d.run("UPDATE memories SET value = ?, version = version + 1, updated_at = ? WHERE id = ?", [newValue, now(), memId]);
|
|
9874
10075
|
return { old_state: rollback };
|
|
9875
10076
|
}
|
|
9876
10077
|
function executeAddTag(proposal, d) {
|
|
@@ -9885,7 +10086,7 @@ function executeAddTag(proposal, d) {
|
|
|
9885
10086
|
continue;
|
|
9886
10087
|
rollback[memId] = [...mem.tags];
|
|
9887
10088
|
const newTags = Array.from(new Set([...mem.tags, ...tagsToAdd]));
|
|
9888
|
-
d.run("UPDATE memories SET tags = ?, updated_at = ? WHERE id = ?", [JSON.stringify(newTags),
|
|
10089
|
+
d.run("UPDATE memories SET tags = ?, updated_at = ? WHERE id = ?", [JSON.stringify(newTags), now(), memId]);
|
|
9889
10090
|
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
9890
10091
|
for (const tag of tagsToAdd) {
|
|
9891
10092
|
insertTag.run(memId, tag);
|
|
@@ -9918,11 +10119,11 @@ function executeMerge(proposal, d) {
|
|
|
9918
10119
|
const mergedValue = proposal.proposed_changes["merged_value"] ?? [target.value, ...sourceValues].join(`
|
|
9919
10120
|
---
|
|
9920
10121
|
`);
|
|
9921
|
-
d.run("UPDATE memories SET value = ?, version = version + 1, updated_at = ? WHERE id = ?", [mergedValue,
|
|
10122
|
+
d.run("UPDATE memories SET value = ?, version = version + 1, updated_at = ? WHERE id = ?", [mergedValue, now(), targetId]);
|
|
9922
10123
|
for (const memId of proposal.memory_ids) {
|
|
9923
10124
|
if (memId === targetId)
|
|
9924
10125
|
continue;
|
|
9925
|
-
d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [
|
|
10126
|
+
d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [now(), memId]);
|
|
9926
10127
|
}
|
|
9927
10128
|
return rollback;
|
|
9928
10129
|
}
|
|
@@ -9938,7 +10139,7 @@ function executeRemoveDuplicate(proposal, d) {
|
|
|
9938
10139
|
if (mem.id === keepId)
|
|
9939
10140
|
continue;
|
|
9940
10141
|
rollback[mem.id] = mem.status;
|
|
9941
|
-
d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [
|
|
10142
|
+
d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [now(), mem.id]);
|
|
9942
10143
|
}
|
|
9943
10144
|
return { old_status: rollback, kept_id: keepId };
|
|
9944
10145
|
}
|
|
@@ -9970,7 +10171,7 @@ function rollbackProposal(proposal, d) {
|
|
|
9970
10171
|
if (!oldStatus)
|
|
9971
10172
|
break;
|
|
9972
10173
|
for (const [memId, status] of Object.entries(oldStatus)) {
|
|
9973
|
-
d.run("UPDATE memories SET status = ?, updated_at = ? WHERE id = ?", [status,
|
|
10174
|
+
d.run("UPDATE memories SET status = ?, updated_at = ? WHERE id = ?", [status, now(), memId]);
|
|
9974
10175
|
}
|
|
9975
10176
|
break;
|
|
9976
10177
|
}
|
|
@@ -9979,7 +10180,7 @@ function rollbackProposal(proposal, d) {
|
|
|
9979
10180
|
if (!oldImportance)
|
|
9980
10181
|
break;
|
|
9981
10182
|
for (const [memId, importance] of Object.entries(oldImportance)) {
|
|
9982
|
-
d.run("UPDATE memories SET importance = ?, updated_at = ? WHERE id = ?", [importance,
|
|
10183
|
+
d.run("UPDATE memories SET importance = ?, updated_at = ? WHERE id = ?", [importance, now(), memId]);
|
|
9983
10184
|
}
|
|
9984
10185
|
break;
|
|
9985
10186
|
}
|
|
@@ -9988,7 +10189,7 @@ function rollbackProposal(proposal, d) {
|
|
|
9988
10189
|
if (!oldState)
|
|
9989
10190
|
break;
|
|
9990
10191
|
for (const [memId, state] of Object.entries(oldState)) {
|
|
9991
|
-
d.run("UPDATE memories SET value = ?, version = ?, updated_at = ? WHERE id = ?", [state.value, state.version,
|
|
10192
|
+
d.run("UPDATE memories SET value = ?, version = ?, updated_at = ? WHERE id = ?", [state.value, state.version, now(), memId]);
|
|
9992
10193
|
}
|
|
9993
10194
|
break;
|
|
9994
10195
|
}
|
|
@@ -9997,7 +10198,7 @@ function rollbackProposal(proposal, d) {
|
|
|
9997
10198
|
if (!oldTags)
|
|
9998
10199
|
break;
|
|
9999
10200
|
for (const [memId, tags] of Object.entries(oldTags)) {
|
|
10000
|
-
d.run("UPDATE memories SET tags = ?, updated_at = ? WHERE id = ?", [JSON.stringify(tags),
|
|
10201
|
+
d.run("UPDATE memories SET tags = ?, updated_at = ? WHERE id = ?", [JSON.stringify(tags), now(), memId]);
|
|
10001
10202
|
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [memId]);
|
|
10002
10203
|
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
10003
10204
|
for (const tag of tags) {
|
|
@@ -10012,11 +10213,11 @@ function rollbackProposal(proposal, d) {
|
|
|
10012
10213
|
const archivedMemories = rb["archived_memories"];
|
|
10013
10214
|
const targetId = proposal.target_memory_id;
|
|
10014
10215
|
if (targetId && targetOldValue !== undefined && targetOldVersion !== undefined) {
|
|
10015
|
-
d.run("UPDATE memories SET value = ?, version = ?, updated_at = ? WHERE id = ?", [targetOldValue, targetOldVersion,
|
|
10216
|
+
d.run("UPDATE memories SET value = ?, version = ?, updated_at = ? WHERE id = ?", [targetOldValue, targetOldVersion, now(), targetId]);
|
|
10016
10217
|
}
|
|
10017
10218
|
if (archivedMemories) {
|
|
10018
10219
|
for (const [memId, status] of Object.entries(archivedMemories)) {
|
|
10019
|
-
d.run("UPDATE memories SET status = ?, updated_at = ? WHERE id = ?", [status,
|
|
10220
|
+
d.run("UPDATE memories SET status = ?, updated_at = ? WHERE id = ?", [status, now(), memId]);
|
|
10020
10221
|
}
|
|
10021
10222
|
}
|
|
10022
10223
|
break;
|
|
@@ -10150,12 +10351,12 @@ async function runSynthesis(options = {}) {
|
|
|
10150
10351
|
proposals_accepted: execResult.executed,
|
|
10151
10352
|
proposals_rejected: validation.rejectedProposals.length + execResult.failed,
|
|
10152
10353
|
status: "completed",
|
|
10153
|
-
completed_at:
|
|
10354
|
+
completed_at: now()
|
|
10154
10355
|
}, d);
|
|
10155
10356
|
} else {
|
|
10156
10357
|
updateSynthesisRun(run.id, {
|
|
10157
10358
|
status: "completed",
|
|
10158
|
-
completed_at:
|
|
10359
|
+
completed_at: now()
|
|
10159
10360
|
}, d);
|
|
10160
10361
|
}
|
|
10161
10362
|
let effectivenessReport = null;
|
|
@@ -10177,7 +10378,7 @@ async function runSynthesis(options = {}) {
|
|
|
10177
10378
|
updateSynthesisRun(run.id, {
|
|
10178
10379
|
status: "failed",
|
|
10179
10380
|
error: err instanceof Error ? err.message : String(err),
|
|
10180
|
-
completed_at:
|
|
10381
|
+
completed_at: now()
|
|
10181
10382
|
}, d);
|
|
10182
10383
|
const failedRun = listSynthesisRuns({ project_id: projectId, limit: 1 }, d)[0] ?? run;
|
|
10183
10384
|
return {
|
|
@@ -10193,7 +10394,7 @@ async function rollbackSynthesis(runId, db) {
|
|
|
10193
10394
|
const d = db || getDatabase();
|
|
10194
10395
|
const result = await rollbackRun(runId, d);
|
|
10195
10396
|
if (result.errors.length === 0) {
|
|
10196
|
-
updateSynthesisRun(runId, { status: "rolled_back", completed_at:
|
|
10397
|
+
updateSynthesisRun(runId, { status: "rolled_back", completed_at: now() }, d);
|
|
10197
10398
|
}
|
|
10198
10399
|
return result;
|
|
10199
10400
|
}
|
|
@@ -10259,7 +10460,7 @@ function parseJobRow(row) {
|
|
|
10259
10460
|
function createSessionJob(input, db) {
|
|
10260
10461
|
const d = db || getDatabase();
|
|
10261
10462
|
const id = uuid();
|
|
10262
|
-
const timestamp =
|
|
10463
|
+
const timestamp = now();
|
|
10263
10464
|
const source = input.source ?? "manual";
|
|
10264
10465
|
const metadata = JSON.stringify(input.metadata ?? {});
|
|
10265
10466
|
d.run(`INSERT INTO session_memory_jobs
|
|
@@ -10278,7 +10479,7 @@ function createSessionJob(input, db) {
|
|
|
10278
10479
|
}
|
|
10279
10480
|
function getSessionJob(id, db) {
|
|
10280
10481
|
if (!db && isApiMode()) {
|
|
10281
|
-
const { status, data } = apiJson("GET", `/sessions/jobs/${encodeURIComponent(id)}
|
|
10482
|
+
const { status, data } = apiJson("GET", `/sessions/jobs/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
10282
10483
|
if (status === 404 || !data)
|
|
10283
10484
|
return null;
|
|
10284
10485
|
return data;
|
|
@@ -10408,7 +10609,7 @@ function saveToolEvent(input, db) {
|
|
|
10408
10609
|
}
|
|
10409
10610
|
const d = db || getDatabase();
|
|
10410
10611
|
const id = uuid();
|
|
10411
|
-
const timestamp =
|
|
10612
|
+
const timestamp = now();
|
|
10412
10613
|
const metadataJson = JSON.stringify(input.metadata || {});
|
|
10413
10614
|
d.run(`INSERT INTO tool_events (id, tool_name, action, success, error_type, error_message, tokens_used, latency_ms, context, lesson, when_to_use, agent_id, project_id, session_id, metadata, created_at)
|
|
10414
10615
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
@@ -11974,6 +12175,31 @@ var init_pg_migrations = __esm(() => {
|
|
|
11974
12175
|
);
|
|
11975
12176
|
CREATE INDEX IF NOT EXISTS idx_task_comments_task ON task_comments(task_id);
|
|
11976
12177
|
CREATE INDEX IF NOT EXISTS idx_task_comments_agent ON task_comments(agent_id);
|
|
12178
|
+
`,
|
|
12179
|
+
`
|
|
12180
|
+
CREATE OR REPLACE FUNCTION snapshot_memory_version() RETURNS trigger AS $$
|
|
12181
|
+
BEGIN
|
|
12182
|
+
INSERT INTO memory_versions (
|
|
12183
|
+
id, memory_id, version, value, importance, scope, category, tags,
|
|
12184
|
+
summary, pinned, status, when_to_use, created_at
|
|
12185
|
+
) VALUES (
|
|
12186
|
+
gen_random_uuid()::text,
|
|
12187
|
+
OLD.id, OLD.version, OLD.value, OLD.importance, OLD.scope, OLD.category,
|
|
12188
|
+
OLD.tags, OLD.summary, OLD.pinned, OLD.status, OLD.when_to_use,
|
|
12189
|
+
OLD.updated_at
|
|
12190
|
+
) ON CONFLICT DO NOTHING;
|
|
12191
|
+
RETURN NEW;
|
|
12192
|
+
END;
|
|
12193
|
+
$$ LANGUAGE plpgsql;
|
|
12194
|
+
|
|
12195
|
+
DROP TRIGGER IF EXISTS memories_version_snapshot ON memories;
|
|
12196
|
+
CREATE TRIGGER memories_version_snapshot
|
|
12197
|
+
BEFORE UPDATE ON memories
|
|
12198
|
+
FOR EACH ROW
|
|
12199
|
+
WHEN (NEW.version > OLD.version)
|
|
12200
|
+
EXECUTE FUNCTION snapshot_memory_version();
|
|
12201
|
+
|
|
12202
|
+
INSERT INTO _migrations (id) VALUES (36) ON CONFLICT DO NOTHING;
|
|
11977
12203
|
`
|
|
11978
12204
|
];
|
|
11979
12205
|
});
|
|
@@ -58701,15 +58927,108 @@ var {
|
|
|
58701
58927
|
Help
|
|
58702
58928
|
} = import__.default;
|
|
58703
58929
|
|
|
58930
|
+
// src/cli/index.tsx
|
|
58931
|
+
init_database();
|
|
58932
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
58933
|
+
import { dirname as dirname7, join as join12 } from "path";
|
|
58934
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
58935
|
+
|
|
58936
|
+
// src/db/machines.ts
|
|
58937
|
+
init_database();
|
|
58938
|
+
import { hostname, platform } from "os";
|
|
58939
|
+
function parseMachine(row) {
|
|
58940
|
+
if (!row)
|
|
58941
|
+
return null;
|
|
58942
|
+
return {
|
|
58943
|
+
...row,
|
|
58944
|
+
is_primary: Boolean(row.is_primary)
|
|
58945
|
+
};
|
|
58946
|
+
}
|
|
58947
|
+
function normalizeHostname(host) {
|
|
58948
|
+
return host.replace(/\.(local|lan|home|internal)$/i, "");
|
|
58949
|
+
}
|
|
58950
|
+
function registerMachine(name, db = getDatabase()) {
|
|
58951
|
+
const rawHost = hostname();
|
|
58952
|
+
const host = normalizeHostname(rawHost);
|
|
58953
|
+
const plat = platform();
|
|
58954
|
+
const machineName = name?.trim() || host;
|
|
58955
|
+
const existing = parseMachine(db.query("SELECT * FROM machines WHERE hostname = ?").get(host));
|
|
58956
|
+
if (existing) {
|
|
58957
|
+
db.run("UPDATE machines SET last_seen_at = ? WHERE id = ?", [now(), existing.id]);
|
|
58958
|
+
return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(existing.id));
|
|
58959
|
+
}
|
|
58960
|
+
let finalName = machineName;
|
|
58961
|
+
let suffix = 2;
|
|
58962
|
+
while (db.query("SELECT id FROM machines WHERE name = ?").get(finalName)) {
|
|
58963
|
+
finalName = `${machineName}-${suffix++}`;
|
|
58964
|
+
}
|
|
58965
|
+
const id = uuid();
|
|
58966
|
+
db.run("INSERT INTO machines (id, name, hostname, platform) VALUES (?, ?, ?, ?)", [id, finalName, host, plat]);
|
|
58967
|
+
return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(id));
|
|
58968
|
+
}
|
|
58969
|
+
function getPrimaryMachine(db = getDatabase()) {
|
|
58970
|
+
return parseMachine(db.query("SELECT * FROM machines WHERE is_primary = 1 LIMIT 1").get());
|
|
58971
|
+
}
|
|
58972
|
+
function getPrimaryMachineCandidate(db = getDatabase()) {
|
|
58973
|
+
if (getPrimaryMachine(db))
|
|
58974
|
+
return null;
|
|
58975
|
+
return parseMachine(db.query("SELECT * FROM machines ORDER BY created_at ASC, id ASC LIMIT 1").get());
|
|
58976
|
+
}
|
|
58977
|
+
function getPrimaryMachineStartupWarning(db = getDatabase()) {
|
|
58978
|
+
if (getPrimaryMachine(db))
|
|
58979
|
+
return null;
|
|
58980
|
+
const candidate = getPrimaryMachineCandidate(db);
|
|
58981
|
+
if (!candidate) {
|
|
58982
|
+
return "No primary machine configured. Fallback sync target is unset because no machines are registered yet.";
|
|
58983
|
+
}
|
|
58984
|
+
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.`;
|
|
58985
|
+
}
|
|
58986
|
+
function touchMachine(id, db = getDatabase()) {
|
|
58987
|
+
db.run("UPDATE machines SET last_seen_at = ? WHERE id = ?", [now(), id]);
|
|
58988
|
+
}
|
|
58989
|
+
function getCurrentMachineId(db = getDatabase()) {
|
|
58990
|
+
const host = normalizeHostname(hostname());
|
|
58991
|
+
const m = db.query("SELECT id FROM machines WHERE hostname = ?").get(host);
|
|
58992
|
+
if (m) {
|
|
58993
|
+
touchMachine(m.id, db);
|
|
58994
|
+
return m.id;
|
|
58995
|
+
}
|
|
58996
|
+
return registerMachine(undefined, db).id;
|
|
58997
|
+
}
|
|
58998
|
+
|
|
58999
|
+
// src/cli/startup-side-effects.ts
|
|
59000
|
+
var NO_STARTUP_DB_ACCESS = new WeakSet;
|
|
59001
|
+
function withoutStartupDbAccess(command) {
|
|
59002
|
+
NO_STARTUP_DB_ACCESS.add(command);
|
|
59003
|
+
return command;
|
|
59004
|
+
}
|
|
59005
|
+
function skipsStartupDbAccess(command) {
|
|
59006
|
+
return command !== undefined && NO_STARTUP_DB_ACCESS.has(command);
|
|
59007
|
+
}
|
|
59008
|
+
|
|
59009
|
+
// src/cli/global-options.ts
|
|
59010
|
+
var GLOBAL_OPTIONS = [
|
|
59011
|
+
["-p, --project <path>", "Project path for scoping"],
|
|
59012
|
+
["-j, --json", "Output as JSON"],
|
|
59013
|
+
["-f, --format <fmt>", "Output format: compact, json, csv, yaml"],
|
|
59014
|
+
["-a, --agent <name>", "Agent name or ID"],
|
|
59015
|
+
["-s, --session <id>", "Session ID"]
|
|
59016
|
+
];
|
|
59017
|
+
function applyGlobalOptions(program2) {
|
|
59018
|
+
for (const [flags, description] of GLOBAL_OPTIONS)
|
|
59019
|
+
program2.option(flags, description);
|
|
59020
|
+
return program2;
|
|
59021
|
+
}
|
|
59022
|
+
|
|
58704
59023
|
// node_modules/@hasna/events/dist/commander.js
|
|
58705
59024
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
58706
|
-
import { existsSync } from "fs";
|
|
58707
|
-
import { homedir } from "os";
|
|
58708
|
-
import { join } from "path";
|
|
59025
|
+
import { existsSync as existsSync3 } from "fs";
|
|
59026
|
+
import { homedir as homedir2 } from "os";
|
|
59027
|
+
import { join as join4 } from "path";
|
|
58709
59028
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
58710
|
-
import { randomUUID } from "crypto";
|
|
58711
|
-
import { spawn } from "child_process";
|
|
58712
59029
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
59030
|
+
import { spawn } from "child_process";
|
|
59031
|
+
import { randomUUID as randomUUID22 } from "crypto";
|
|
58713
59032
|
function getPathValue(input, path) {
|
|
58714
59033
|
return path.split(".").reduce((value, part) => {
|
|
58715
59034
|
if (value && typeof value === "object" && part in value) {
|
|
@@ -58754,7 +59073,7 @@ function channelMatchesEvent(channel, event) {
|
|
|
58754
59073
|
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
58755
59074
|
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
58756
59075
|
function getEventsDataDir(override) {
|
|
58757
|
-
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] ||
|
|
59076
|
+
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join4(homedir2(), ".hasna", "events");
|
|
58758
59077
|
}
|
|
58759
59078
|
|
|
58760
59079
|
class JsonEventsStore {
|
|
@@ -58764,9 +59083,9 @@ class JsonEventsStore {
|
|
|
58764
59083
|
deliveriesPath;
|
|
58765
59084
|
constructor(dataDir = getEventsDataDir()) {
|
|
58766
59085
|
this.dataDir = dataDir;
|
|
58767
|
-
this.channelsPath =
|
|
58768
|
-
this.eventsPath =
|
|
58769
|
-
this.deliveriesPath =
|
|
59086
|
+
this.channelsPath = join4(dataDir, "channels.json");
|
|
59087
|
+
this.eventsPath = join4(dataDir, "events.json");
|
|
59088
|
+
this.deliveriesPath = join4(dataDir, "deliveries.json");
|
|
58770
59089
|
}
|
|
58771
59090
|
async init() {
|
|
58772
59091
|
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
@@ -58838,7 +59157,7 @@ class JsonEventsStore {
|
|
|
58838
59157
|
};
|
|
58839
59158
|
}
|
|
58840
59159
|
async ensureArrayFile(path) {
|
|
58841
|
-
if (!
|
|
59160
|
+
if (!existsSync3(path)) {
|
|
58842
59161
|
await writeFile(path, `[]
|
|
58843
59162
|
`, { encoding: "utf-8", mode: 384 });
|
|
58844
59163
|
}
|
|
@@ -58876,7 +59195,7 @@ function signPayload(secret, timestamp, body) {
|
|
|
58876
59195
|
const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
|
|
58877
59196
|
return `sha256=${digest}`;
|
|
58878
59197
|
}
|
|
58879
|
-
function
|
|
59198
|
+
function now2() {
|
|
58880
59199
|
return new Date().toISOString();
|
|
58881
59200
|
}
|
|
58882
59201
|
function truncate(value, max = 4096) {
|
|
@@ -58903,7 +59222,7 @@ function buildWebhookRequest(event, channel) {
|
|
|
58903
59222
|
async function dispatchWebhook(event, channel, options = {}) {
|
|
58904
59223
|
if (!channel.webhook)
|
|
58905
59224
|
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
58906
|
-
const startedAt =
|
|
59225
|
+
const startedAt = now2();
|
|
58907
59226
|
const { body, headers } = buildWebhookRequest(event, channel);
|
|
58908
59227
|
const controller = new AbortController;
|
|
58909
59228
|
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
@@ -58919,7 +59238,7 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
58919
59238
|
attempt: 1,
|
|
58920
59239
|
status: response.ok ? "success" : "failed",
|
|
58921
59240
|
startedAt,
|
|
58922
|
-
completedAt:
|
|
59241
|
+
completedAt: now2(),
|
|
58923
59242
|
responseStatus: response.status,
|
|
58924
59243
|
responseBody,
|
|
58925
59244
|
error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
|
|
@@ -58929,7 +59248,7 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
58929
59248
|
attempt: 1,
|
|
58930
59249
|
status: "failed",
|
|
58931
59250
|
startedAt,
|
|
58932
|
-
completedAt:
|
|
59251
|
+
completedAt: now2(),
|
|
58933
59252
|
error: error instanceof Error ? error.message : String(error)
|
|
58934
59253
|
};
|
|
58935
59254
|
} finally {
|
|
@@ -58939,7 +59258,7 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
58939
59258
|
async function dispatchCommand(event, channel) {
|
|
58940
59259
|
if (!channel.command)
|
|
58941
59260
|
throw new Error(`Channel ${channel.id} has no command config`);
|
|
58942
|
-
const startedAt =
|
|
59261
|
+
const startedAt = now2();
|
|
58943
59262
|
const eventJson = JSON.stringify(event);
|
|
58944
59263
|
const env = {
|
|
58945
59264
|
...process.env,
|
|
@@ -58955,7 +59274,7 @@ async function dispatchCommand(event, channel) {
|
|
|
58955
59274
|
HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
|
|
58956
59275
|
HASNA_EVENT_JSON: eventJson
|
|
58957
59276
|
};
|
|
58958
|
-
return new Promise((
|
|
59277
|
+
return new Promise((resolve2) => {
|
|
58959
59278
|
const child = spawn(channel.command.command, channel.command.args ?? [], {
|
|
58960
59279
|
cwd: channel.command.cwd,
|
|
58961
59280
|
env,
|
|
@@ -58973,11 +59292,11 @@ async function dispatchCommand(event, channel) {
|
|
|
58973
59292
|
});
|
|
58974
59293
|
child.on("error", (error) => {
|
|
58975
59294
|
clearTimeout(timeout);
|
|
58976
|
-
|
|
59295
|
+
resolve2({
|
|
58977
59296
|
attempt: 1,
|
|
58978
59297
|
status: "failed",
|
|
58979
59298
|
startedAt,
|
|
58980
|
-
completedAt:
|
|
59299
|
+
completedAt: now2(),
|
|
58981
59300
|
stdout: truncate(stdout),
|
|
58982
59301
|
stderr: truncate(stderr),
|
|
58983
59302
|
error: error.message
|
|
@@ -58986,11 +59305,11 @@ async function dispatchCommand(event, channel) {
|
|
|
58986
59305
|
child.on("close", (code, signal) => {
|
|
58987
59306
|
clearTimeout(timeout);
|
|
58988
59307
|
const success = code === 0;
|
|
58989
|
-
|
|
59308
|
+
resolve2({
|
|
58990
59309
|
attempt: 1,
|
|
58991
59310
|
status: success ? "success" : "failed",
|
|
58992
59311
|
startedAt,
|
|
58993
|
-
completedAt:
|
|
59312
|
+
completedAt: now2(),
|
|
58994
59313
|
stdout: truncate(stdout),
|
|
58995
59314
|
stderr: truncate(stderr),
|
|
58996
59315
|
error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
|
|
@@ -59006,27 +59325,27 @@ async function dispatchChannel(event, channel, options = {}) {
|
|
|
59006
59325
|
return {
|
|
59007
59326
|
attempt: 1,
|
|
59008
59327
|
status: "skipped",
|
|
59009
|
-
startedAt:
|
|
59010
|
-
completedAt:
|
|
59328
|
+
startedAt: now2(),
|
|
59329
|
+
completedAt: now2(),
|
|
59011
59330
|
error: `Unsupported transport: ${channel.transport}`
|
|
59012
59331
|
};
|
|
59013
59332
|
}
|
|
59014
59333
|
function createDeliveryResult(event, channel, attempts) {
|
|
59015
59334
|
const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
|
|
59016
59335
|
return {
|
|
59017
|
-
id:
|
|
59336
|
+
id: randomUUID2(),
|
|
59018
59337
|
eventId: event.id,
|
|
59019
59338
|
channelId: channel.id,
|
|
59020
59339
|
transport: channel.transport,
|
|
59021
59340
|
status,
|
|
59022
59341
|
attempts,
|
|
59023
|
-
createdAt: attempts[0]?.startedAt ??
|
|
59024
|
-
completedAt: attempts.at(-1)?.completedAt ??
|
|
59342
|
+
createdAt: attempts[0]?.startedAt ?? now2(),
|
|
59343
|
+
completedAt: attempts.at(-1)?.completedAt ?? now2()
|
|
59025
59344
|
};
|
|
59026
59345
|
}
|
|
59027
59346
|
function createEvent(input) {
|
|
59028
59347
|
return {
|
|
59029
|
-
id: input.id ??
|
|
59348
|
+
id: input.id ?? randomUUID22(),
|
|
59030
59349
|
source: input.source,
|
|
59031
59350
|
type: input.type,
|
|
59032
59351
|
time: normalizeTime(input.time),
|
|
@@ -59385,75 +59704,6 @@ function collectValues(value, previous) {
|
|
|
59385
59704
|
return previous;
|
|
59386
59705
|
}
|
|
59387
59706
|
|
|
59388
|
-
// src/cli/index.tsx
|
|
59389
|
-
init_database();
|
|
59390
|
-
import { readFileSync as readFileSync8 } from "fs";
|
|
59391
|
-
import { dirname as dirname7, join as join12 } from "path";
|
|
59392
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
59393
|
-
|
|
59394
|
-
// src/db/machines.ts
|
|
59395
|
-
init_database();
|
|
59396
|
-
import { hostname, platform } from "os";
|
|
59397
|
-
function parseMachine(row) {
|
|
59398
|
-
if (!row)
|
|
59399
|
-
return null;
|
|
59400
|
-
return {
|
|
59401
|
-
...row,
|
|
59402
|
-
is_primary: Boolean(row.is_primary)
|
|
59403
|
-
};
|
|
59404
|
-
}
|
|
59405
|
-
function normalizeHostname(host) {
|
|
59406
|
-
return host.replace(/\.(local|lan|home|internal)$/i, "");
|
|
59407
|
-
}
|
|
59408
|
-
function registerMachine(name, db = getDatabase()) {
|
|
59409
|
-
const rawHost = hostname();
|
|
59410
|
-
const host = normalizeHostname(rawHost);
|
|
59411
|
-
const plat = platform();
|
|
59412
|
-
const machineName = name?.trim() || host;
|
|
59413
|
-
const existing = parseMachine(db.query("SELECT * FROM machines WHERE hostname = ?").get(host));
|
|
59414
|
-
if (existing) {
|
|
59415
|
-
db.run("UPDATE machines SET last_seen_at = ? WHERE id = ?", [now2(), existing.id]);
|
|
59416
|
-
return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(existing.id));
|
|
59417
|
-
}
|
|
59418
|
-
let finalName = machineName;
|
|
59419
|
-
let suffix = 2;
|
|
59420
|
-
while (db.query("SELECT id FROM machines WHERE name = ?").get(finalName)) {
|
|
59421
|
-
finalName = `${machineName}-${suffix++}`;
|
|
59422
|
-
}
|
|
59423
|
-
const id = uuid();
|
|
59424
|
-
db.run("INSERT INTO machines (id, name, hostname, platform) VALUES (?, ?, ?, ?)", [id, finalName, host, plat]);
|
|
59425
|
-
return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(id));
|
|
59426
|
-
}
|
|
59427
|
-
function getPrimaryMachine(db = getDatabase()) {
|
|
59428
|
-
return parseMachine(db.query("SELECT * FROM machines WHERE is_primary = 1 LIMIT 1").get());
|
|
59429
|
-
}
|
|
59430
|
-
function getPrimaryMachineCandidate(db = getDatabase()) {
|
|
59431
|
-
if (getPrimaryMachine(db))
|
|
59432
|
-
return null;
|
|
59433
|
-
return parseMachine(db.query("SELECT * FROM machines ORDER BY created_at ASC, id ASC LIMIT 1").get());
|
|
59434
|
-
}
|
|
59435
|
-
function getPrimaryMachineStartupWarning(db = getDatabase()) {
|
|
59436
|
-
if (getPrimaryMachine(db))
|
|
59437
|
-
return null;
|
|
59438
|
-
const candidate = getPrimaryMachineCandidate(db);
|
|
59439
|
-
if (!candidate) {
|
|
59440
|
-
return "No primary machine configured. Fallback sync target is unset because no machines are registered yet.";
|
|
59441
|
-
}
|
|
59442
|
-
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.`;
|
|
59443
|
-
}
|
|
59444
|
-
function touchMachine(id, db = getDatabase()) {
|
|
59445
|
-
db.run("UPDATE machines SET last_seen_at = ? WHERE id = ?", [now2(), id]);
|
|
59446
|
-
}
|
|
59447
|
-
function getCurrentMachineId(db = getDatabase()) {
|
|
59448
|
-
const host = normalizeHostname(hostname());
|
|
59449
|
-
const m = db.query("SELECT id FROM machines WHERE hostname = ?").get(host);
|
|
59450
|
-
if (m) {
|
|
59451
|
-
touchMachine(m.id, db);
|
|
59452
|
-
return m.id;
|
|
59453
|
-
}
|
|
59454
|
-
return registerMachine(undefined, db).id;
|
|
59455
|
-
}
|
|
59456
|
-
|
|
59457
59707
|
// src/cli/commands/memory.ts
|
|
59458
59708
|
init_helpers();
|
|
59459
59709
|
|
|
@@ -59510,10 +59760,12 @@ var FORMAT_UNITS = [
|
|
|
59510
59760
|
];
|
|
59511
59761
|
|
|
59512
59762
|
// src/cli/commands/memory-cmd-crud.ts
|
|
59763
|
+
init_enum_validation();
|
|
59764
|
+
init_types();
|
|
59513
59765
|
init_helpers();
|
|
59514
59766
|
function registerCrudCommands(program2) {
|
|
59515
59767
|
const handleError = makeHandleError(program2);
|
|
59516
|
-
program2.command("save <key> <value>").description("Save a memory (create or upsert)").option("-c, --category <cat>",
|
|
59768
|
+
program2.command("save <key> <value>").description("Save a memory (create or upsert)").option("-c, --category <cat>", `Category: ${MEMORY_CATEGORIES.join(", ")}`).option("--scope <scope>", `Scope: ${MEMORY_SCOPES.join(", ")}`).option("--importance <n>", "Importance 1-10", parseInt).option("--tags <tags>", "Comma-separated tags").option("--summary <text>", "Brief summary").option("--ttl <duration>", "Time-to-live: 30s, 5m, 2h, 1d, 1w, or milliseconds").option("--source <src>", "Source: user, agent, system, auto, imported").option("--template <name>", "Apply a template: correction, preference, decision, learning").option("--dedupe <mode>", "Conflict handling: merge (default, upsert the matching row), create (fork a new row under the same key), error").action((key, value, opts) => {
|
|
59517
59769
|
try {
|
|
59518
59770
|
const globalOpts = program2.opts();
|
|
59519
59771
|
const templates = {
|
|
@@ -59551,6 +59803,20 @@ function registerCrudCommands(program2) {
|
|
|
59551
59803
|
}
|
|
59552
59804
|
templateDefaults = tpl;
|
|
59553
59805
|
}
|
|
59806
|
+
for (const [flag, value2] of [
|
|
59807
|
+
["category", opts.category],
|
|
59808
|
+
["scope", opts.scope],
|
|
59809
|
+
["source", opts.source]
|
|
59810
|
+
]) {
|
|
59811
|
+
const violation = validateEnumField(flag, value2);
|
|
59812
|
+
if (!violation)
|
|
59813
|
+
continue;
|
|
59814
|
+
let msg = formatEnumViolation(violation);
|
|
59815
|
+
if (flag === "category" && templates[violation.value]) {
|
|
59816
|
+
msg += ` Did you mean --template ${violation.value}?`;
|
|
59817
|
+
}
|
|
59818
|
+
throw new Error(msg);
|
|
59819
|
+
}
|
|
59554
59820
|
const explicitTags = opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined;
|
|
59555
59821
|
const mergedTags = explicitTags ? explicitTags : templateDefaults?.tags && templateDefaults.tags.length > 0 ? templateDefaults.tags : undefined;
|
|
59556
59822
|
let resolvedAgentId;
|
|
@@ -59576,17 +59842,40 @@ function registerCrudCommands(program2) {
|
|
|
59576
59842
|
if (project)
|
|
59577
59843
|
input.project_id = project.id;
|
|
59578
59844
|
}
|
|
59579
|
-
const
|
|
59845
|
+
const bucket = (m) => [m.scope ?? "private", m.agent_id ?? "", m.project_id ?? "", m.session_id ?? ""].join("\x1F");
|
|
59846
|
+
const targetBucket = bucket({
|
|
59847
|
+
scope: input.scope ?? "private",
|
|
59848
|
+
agent_id: input.agent_id,
|
|
59849
|
+
project_id: input.project_id,
|
|
59850
|
+
session_id: input.session_id
|
|
59851
|
+
});
|
|
59852
|
+
const dedupe = opts.dedupe;
|
|
59853
|
+
const forkRequested = dedupe === "create" || dedupe === "version-fork";
|
|
59854
|
+
let willUpdateExisting = false;
|
|
59855
|
+
if (!forkRequested) {
|
|
59856
|
+
const sameKey = getMemoriesByKey(key);
|
|
59857
|
+
const match = sameKey.find((m) => bucket(m) === targetBucket);
|
|
59858
|
+
willUpdateExisting = Boolean(match);
|
|
59859
|
+
if (!match && sameKey.length > 0) {
|
|
59860
|
+
const rows = sameKey.map((m) => ` ${m.id.slice(0, 8)} scope=${m.scope} project=${m.project_id ?? "none"} session=${m.session_id ?? "none"} agent=${m.agent_id ?? "none"}`).join(`
|
|
59861
|
+
`);
|
|
59862
|
+
throw new Error(`Refusing to fork key "${key}": ${sameKey.length} active memor${sameKey.length === 1 ? "y" : "ies"} ` + `already ${sameKey.length === 1 ? "uses" : "use"} it, ` + `but none matches the scope/project/session this save targets ` + `(scope=${input.scope ?? "private"}, project=${input.project_id ?? "none"}, session=${input.session_id ?? "none"}).
|
|
59863
|
+
` + `${rows}
|
|
59864
|
+
` + `Saving would create a second active row under the same key. Either target the existing row ` + `(match its scope/project/session flags, or use \`mementos update <id>\`), or pass \`--dedupe create\` ` + `to fork deliberately.`);
|
|
59865
|
+
}
|
|
59866
|
+
}
|
|
59867
|
+
const memory = forkRequested ? createMemory(input, dedupe) : createMemory(input);
|
|
59868
|
+
const outcome = willUpdateExisting ? "Updated" : "Created";
|
|
59580
59869
|
if (globalOpts.json) {
|
|
59581
|
-
outputJson(memory);
|
|
59870
|
+
outputJson({ ...memory, outcome: outcome.toLowerCase() });
|
|
59582
59871
|
} else {
|
|
59583
|
-
console.log(chalk2.green(
|
|
59872
|
+
console.log(chalk2.green(`${outcome}: ${memory.key} (${memory.id.slice(0, 8)})`));
|
|
59584
59873
|
}
|
|
59585
59874
|
} catch (e) {
|
|
59586
59875
|
handleError(e);
|
|
59587
59876
|
}
|
|
59588
59877
|
});
|
|
59589
|
-
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("
|
|
59878
|
+
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) => {
|
|
59590
59879
|
try {
|
|
59591
59880
|
const globalOpts = program2.opts();
|
|
59592
59881
|
const resolvedId = resolveMemoryId(id);
|
|
@@ -59620,17 +59909,31 @@ function registerCrudCommands(program2) {
|
|
|
59620
59909
|
updateInput.scope = opts.scope;
|
|
59621
59910
|
if (opts.status !== undefined)
|
|
59622
59911
|
updateInput.status = opts.status;
|
|
59912
|
+
for (const [flag, value] of [
|
|
59913
|
+
["category", opts.category],
|
|
59914
|
+
["scope", opts.scope],
|
|
59915
|
+
["status", opts.status]
|
|
59916
|
+
]) {
|
|
59917
|
+
const violation = validateEnumField(flag, value);
|
|
59918
|
+
if (violation)
|
|
59919
|
+
throw new Error(formatEnumViolation(violation));
|
|
59920
|
+
}
|
|
59921
|
+
const changedFields = Object.keys(updateInput).filter((k) => k !== "version");
|
|
59922
|
+
if (changedFields.length === 0) {
|
|
59923
|
+
throw new Error(`Nothing to update: no fields were given for ${existing.key} (${existing.id.slice(0, 8)}). ` + `Pass at least one of --value, --scope, --category, --status, --importance, --tags, --summary, --pin/--unpin. ` + `Note that -s is the global --session, not --scope.`);
|
|
59924
|
+
}
|
|
59623
59925
|
const updated = updateMemory(resolvedId, updateInput);
|
|
59624
59926
|
if (globalOpts.json) {
|
|
59625
|
-
outputJson(updated);
|
|
59927
|
+
outputJson({ ...updated, updated_fields: changedFields });
|
|
59626
59928
|
} else {
|
|
59627
|
-
|
|
59929
|
+
const n = changedFields.length;
|
|
59930
|
+
console.log(chalk2.green(`Updated ${n} field${n === 1 ? "" : "s"}: ${updated.key} (${updated.id.slice(0, 8)})`) + chalk2.dim(` [${changedFields.join(", ")}]`));
|
|
59628
59931
|
}
|
|
59629
59932
|
} catch (e) {
|
|
59630
59933
|
handleError(e);
|
|
59631
59934
|
}
|
|
59632
59935
|
});
|
|
59633
|
-
program2.command("forget <keyOrId>").description("Delete a memory by key or ID").option("
|
|
59936
|
+
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) => {
|
|
59634
59937
|
try {
|
|
59635
59938
|
const globalOpts = program2.opts();
|
|
59636
59939
|
const idMatch = isApiMode() ? null : resolvePartialId(getDatabase(), "memories", keyOrId);
|
|
@@ -59730,7 +60033,7 @@ function registerViewCommands(program2) {
|
|
|
59730
60033
|
handleError(e);
|
|
59731
60034
|
}
|
|
59732
60035
|
});
|
|
59733
|
-
program2.command("pin <keyOrId>").description("Pin a memory by key or partial ID").option("
|
|
60036
|
+
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) => {
|
|
59734
60037
|
try {
|
|
59735
60038
|
const globalOpts = program2.opts();
|
|
59736
60039
|
const memory = resolveKeyOrId(keyOrId, opts, globalOpts);
|
|
@@ -59755,7 +60058,7 @@ function registerViewCommands(program2) {
|
|
|
59755
60058
|
handleError(e);
|
|
59756
60059
|
}
|
|
59757
60060
|
});
|
|
59758
|
-
program2.command("unpin <keyOrId>").description("Unpin a memory by key or partial ID").option("
|
|
60061
|
+
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) => {
|
|
59759
60062
|
try {
|
|
59760
60063
|
const globalOpts = program2.opts();
|
|
59761
60064
|
const memory = resolveKeyOrId(keyOrId, opts, globalOpts);
|
|
@@ -59780,7 +60083,7 @@ function registerViewCommands(program2) {
|
|
|
59780
60083
|
handleError(e);
|
|
59781
60084
|
}
|
|
59782
60085
|
});
|
|
59783
|
-
program2.command("archive <keyOrId>").description("Archive a memory by key or ID (hides from lists, keeps history)").option("
|
|
60086
|
+
program2.command("archive <keyOrId>").description("Archive a memory by key or ID (hides from lists, keeps history)").option("--scope <scope>", "Scope filter for key lookup").action((keyOrId, opts) => {
|
|
59784
60087
|
try {
|
|
59785
60088
|
const globalOpts = program2.opts();
|
|
59786
60089
|
const memory = resolveKeyOrId(keyOrId, opts, globalOpts);
|
|
@@ -59799,7 +60102,7 @@ function registerViewCommands(program2) {
|
|
|
59799
60102
|
process.exit(1);
|
|
59800
60103
|
}
|
|
59801
60104
|
});
|
|
59802
|
-
program2.command("versions <keyOrId>").description("Show version history for a memory").option("
|
|
60105
|
+
program2.command("versions <keyOrId>").description("Show version history for a memory").option("--scope <scope>", "Scope filter for key lookup").action((keyOrId, opts) => {
|
|
59803
60106
|
try {
|
|
59804
60107
|
const globalOpts = program2.opts();
|
|
59805
60108
|
const memory = resolveKeyOrId(keyOrId, opts, globalOpts);
|
|
@@ -59841,7 +60144,7 @@ import chalk4 from "chalk";
|
|
|
59841
60144
|
import { resolve as resolve4 } from "path";
|
|
59842
60145
|
function registerTailCommand(program2) {
|
|
59843
60146
|
const handleError = makeHandleError(program2);
|
|
59844
|
-
program2.command("tail").description("Watch for new/updated memories in real-time (like tail -f)").option("
|
|
60147
|
+
program2.command("tail").description("Watch for new/updated memories in real-time (like tail -f)").option("--scope <scope>", "Scope filter: global, shared, private, working").option("-c, --category <cat>", "Category filter: preference, fact, knowledge, history, procedural, resource").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").option("--interval <ms>", "Poll interval in milliseconds (default: 2000)", parseInt).option("--notify", "Send macOS notifications for each change").action((opts) => {
|
|
59845
60148
|
try {
|
|
59846
60149
|
const globalOpts = program2.opts();
|
|
59847
60150
|
const jsonMode = !!globalOpts.json;
|
|
@@ -59955,7 +60258,7 @@ import chalk6 from "chalk";
|
|
|
59955
60258
|
import { resolve as resolve5 } from "path";
|
|
59956
60259
|
function registerSearchCommand(program2) {
|
|
59957
60260
|
const handleError = makeHandleError(program2);
|
|
59958
|
-
program2.command("search <query>").description("Full-text search across memories").option("
|
|
60261
|
+
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) => {
|
|
59959
60262
|
try {
|
|
59960
60263
|
const fmt = getOutputFormat(program2, opts.format);
|
|
59961
60264
|
const isStructured = fmt === "json" || fmt === "csv" || fmt === "yaml";
|
|
@@ -60216,7 +60519,7 @@ init_memories();
|
|
|
60216
60519
|
init_helpers();
|
|
60217
60520
|
import chalk10 from "chalk";
|
|
60218
60521
|
function registerRemoveCommand(program2) {
|
|
60219
|
-
program2.command("remove <nameOrId>").description("Remove/delete a memory by name or ID (alias for memory forget)").option("--agent <id>", "Agent ID").option("
|
|
60522
|
+
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) => {
|
|
60220
60523
|
const globalOpts = program2.opts();
|
|
60221
60524
|
const agentId = opts.agent || globalOpts.agent;
|
|
60222
60525
|
let id = isApiMode() ? null : resolvePartialId(getDatabase(), "memories", nameOrId);
|
|
@@ -60273,7 +60576,7 @@ import chalk11 from "chalk";
|
|
|
60273
60576
|
import { resolve as resolve6 } from "path";
|
|
60274
60577
|
function registerRecallCommand(program2) {
|
|
60275
60578
|
const handleError = makeHandleError(program2);
|
|
60276
|
-
program2.command("recall <key>").description("Recall a memory by key").option("
|
|
60579
|
+
program2.command("recall <key>").description("Recall a memory by key").option("--scope <scope>", "Scope filter").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").action((key, opts) => {
|
|
60277
60580
|
try {
|
|
60278
60581
|
const globalOpts = program2.opts();
|
|
60279
60582
|
const agentId = opts.agent || globalOpts.agent;
|
|
@@ -60331,7 +60634,7 @@ import chalk12 from "chalk";
|
|
|
60331
60634
|
import { resolve as resolve7 } from "path";
|
|
60332
60635
|
function registerListCommand(program2) {
|
|
60333
60636
|
const handleError = makeHandleError(program2);
|
|
60334
|
-
program2.command("list").description("List memories with optional filters").option("
|
|
60637
|
+
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", 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").action((opts) => {
|
|
60335
60638
|
try {
|
|
60336
60639
|
const globalOpts = program2.opts();
|
|
60337
60640
|
const fmt = getOutputFormat(program2, opts.format);
|
|
@@ -60980,7 +61283,7 @@ init_helpers();
|
|
|
60980
61283
|
import { resolve as resolve11 } from "path";
|
|
60981
61284
|
function registerExportCommand(program2) {
|
|
60982
61285
|
const handleError = makeHandleError(program2);
|
|
60983
|
-
program2.command("export").description("Export memories as JSON").option("
|
|
61286
|
+
program2.command("export").description("Export memories as JSON").option("--scope <scope>", "Scope filter").option("-c, --category <cat>", "Category filter").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").action((opts) => {
|
|
60984
61287
|
try {
|
|
60985
61288
|
const globalOpts = program2.opts();
|
|
60986
61289
|
const agentId = opts.agent || globalOpts.agent;
|
|
@@ -61296,7 +61599,7 @@ function enforceQuotas(config, db) {
|
|
|
61296
61599
|
}
|
|
61297
61600
|
function archiveStale(staleDays, db) {
|
|
61298
61601
|
const d = db || getDatabase();
|
|
61299
|
-
const timestamp =
|
|
61602
|
+
const timestamp = now();
|
|
61300
61603
|
const cutoff = new Date(Date.now() - staleDays * 24 * 60 * 60 * 1000).toISOString();
|
|
61301
61604
|
const archiveWhere = `status = 'active' AND pinned = 0 AND COALESCE(accessed_at, created_at) < ?`;
|
|
61302
61605
|
const count = d.query(`SELECT COUNT(*) as c FROM memories WHERE ${archiveWhere}`).get(cutoff).c;
|
|
@@ -61307,7 +61610,7 @@ function archiveStale(staleDays, db) {
|
|
|
61307
61610
|
}
|
|
61308
61611
|
function archiveUnused(days, db) {
|
|
61309
61612
|
const d = db || getDatabase();
|
|
61310
|
-
const timestamp =
|
|
61613
|
+
const timestamp = now();
|
|
61311
61614
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
61312
61615
|
const unusedWhere = `status = 'active' AND pinned = 0 AND access_count = 0 AND created_at < ?`;
|
|
61313
61616
|
const count = d.query(`SELECT COUNT(*) as c FROM memories WHERE ${unusedWhere}`).get(cutoff).c;
|
|
@@ -61318,7 +61621,7 @@ function archiveUnused(days, db) {
|
|
|
61318
61621
|
}
|
|
61319
61622
|
function deprioritizeStale(days, db) {
|
|
61320
61623
|
const d = db || getDatabase();
|
|
61321
|
-
const timestamp =
|
|
61624
|
+
const timestamp = now();
|
|
61322
61625
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
61323
61626
|
const deprioWhere = `status = 'active' AND pinned = 0 AND importance > 1 AND COALESCE(accessed_at, updated_at) < ?`;
|
|
61324
61627
|
const count = d.query(`SELECT COUNT(*) as c FROM memories WHERE ${deprioWhere}`).get(cutoff).c;
|
|
@@ -61346,7 +61649,9 @@ init_api_mode();
|
|
|
61346
61649
|
init_helpers();
|
|
61347
61650
|
function runCleanupViaApi() {
|
|
61348
61651
|
const empty = { expired: 0, evicted: 0, archived: 0, unused_archived: 0, deprioritized: 0 };
|
|
61349
|
-
const { status, data } = apiJson("POST", "/maintenance/cleanup"
|
|
61652
|
+
const { status, data } = apiJson("POST", "/maintenance/cleanup", undefined, {
|
|
61653
|
+
allow404: true
|
|
61654
|
+
});
|
|
61350
61655
|
if (status !== 404 && data)
|
|
61351
61656
|
return { ...empty, ...data };
|
|
61352
61657
|
const legacy = apiJson("POST", "/memories/clean");
|
|
@@ -61637,7 +61942,7 @@ function getFocus(agentId) {
|
|
|
61637
61942
|
init_helpers();
|
|
61638
61943
|
function registerAgentCommands(program2) {
|
|
61639
61944
|
const handleError = makeHandleError(program2);
|
|
61640
|
-
program2.command("register-agent <name>").alias("init-agent").description("Register an agent (returns ID)").option("-d, --description <text>", "Agent description").option("-r, --role <role>", "Agent role").option("
|
|
61945
|
+
program2.command("register-agent <name>").alias("init-agent").description("Register an agent (returns ID)").option("-d, --description <text>", "Agent description").option("-r, --role <role>", "Agent role").option("--project <id>", "Lock agent to a project (sets active_project_id)").action((name, opts) => {
|
|
61641
61946
|
try {
|
|
61642
61947
|
const globalOpts = program2.opts();
|
|
61643
61948
|
const agent = registerAgent(name, undefined, opts.description, opts.role, opts.project);
|
|
@@ -62810,12 +63115,12 @@ function registerDoctorCommand(program2) {
|
|
|
62810
63115
|
const activeProfile = getActiveProfile();
|
|
62811
63116
|
const profiles = listProfiles();
|
|
62812
63117
|
if (activeProfile) {
|
|
62813
|
-
checks.push({ name: "
|
|
63118
|
+
checks.push({ name: "Profile metadata", status: "ok", detail: `${activeProfile} active (${profiles.length} total); verify runtime DB with storage mode` });
|
|
62814
63119
|
} else {
|
|
62815
|
-
checks.push({ name: "
|
|
63120
|
+
checks.push({ name: "Profile metadata", status: "ok", detail: `none active \u2014 ${profiles.length} profile(s) available; verify runtime DB with storage mode` });
|
|
62816
63121
|
}
|
|
62817
63122
|
} catch (e) {
|
|
62818
|
-
checks.push({ name: "
|
|
63123
|
+
checks.push({ name: "Profile metadata", status: "warn", detail: e instanceof Error ? e.message : String(e) });
|
|
62819
63124
|
}
|
|
62820
63125
|
try {
|
|
62821
63126
|
const mementosUrl = process.env["MEMENTOS_URL"] || `http://127.0.0.1:19428`;
|
|
@@ -63124,7 +63429,7 @@ function registerConfigCommand(program2) {
|
|
|
63124
63429
|
import chalk29 from "chalk";
|
|
63125
63430
|
init_helpers();
|
|
63126
63431
|
function registerProfileCommand(program2) {
|
|
63127
|
-
const profileCmd = program2.command("profile").description("Manage
|
|
63432
|
+
const profileCmd = program2.command("profile").description("Manage named profile files and active-profile metadata");
|
|
63128
63433
|
profileCmd.command("list").description("List all available profiles").action(() => {
|
|
63129
63434
|
const globalOpts = program2.opts();
|
|
63130
63435
|
const profiles = listProfiles();
|
|
@@ -63144,7 +63449,7 @@ function registerProfileCommand(program2) {
|
|
|
63144
63449
|
}
|
|
63145
63450
|
if (!active) {
|
|
63146
63451
|
console.log(chalk29.dim(`
|
|
63147
|
-
(no active
|
|
63452
|
+
(no active-profile metadata set)`));
|
|
63148
63453
|
}
|
|
63149
63454
|
});
|
|
63150
63455
|
profileCmd.command("get").description("Show the currently active profile").action(() => {
|
|
@@ -63157,20 +63462,21 @@ function registerProfileCommand(program2) {
|
|
|
63157
63462
|
console.log(chalk29.dim("(from MEMENTOS_PROFILE env var)"));
|
|
63158
63463
|
}
|
|
63159
63464
|
} else {
|
|
63160
|
-
console.log(chalk29.dim("No active
|
|
63465
|
+
console.log(chalk29.dim("No active-profile metadata set."));
|
|
63161
63466
|
}
|
|
63162
63467
|
});
|
|
63163
|
-
profileCmd.command("set <name>").description("
|
|
63468
|
+
profileCmd.command("set <name>").description("Set the active-profile metadata").action((name) => {
|
|
63164
63469
|
const clean = name.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-");
|
|
63165
63470
|
if (!clean) {
|
|
63166
63471
|
console.error(chalk29.red("Invalid profile name. Use letters, numbers, hyphens, underscores."));
|
|
63167
63472
|
process.exit(1);
|
|
63168
63473
|
}
|
|
63169
63474
|
setActiveProfile(clean);
|
|
63170
|
-
console.log(chalk29.green(`\u2713
|
|
63171
|
-
console.log(chalk29.dim(`
|
|
63475
|
+
console.log(chalk29.green(`\u2713 Active-profile metadata set: ${clean}`));
|
|
63476
|
+
console.log(chalk29.dim(` Profile file: ~/.hasna/mementos/profiles/${clean}.db`));
|
|
63477
|
+
console.log(chalk29.dim(" Run `mementos storage mode` to verify the live runtime database."));
|
|
63172
63478
|
});
|
|
63173
|
-
profileCmd.command("unset").description("Clear the active
|
|
63479
|
+
profileCmd.command("unset").description("Clear the active-profile metadata").action(() => {
|
|
63174
63480
|
const was = getActiveProfile();
|
|
63175
63481
|
setActiveProfile(null);
|
|
63176
63482
|
if (was) {
|
|
@@ -63178,7 +63484,7 @@ function registerProfileCommand(program2) {
|
|
|
63178
63484
|
} else {
|
|
63179
63485
|
console.log(chalk29.dim("No active profile was set."));
|
|
63180
63486
|
}
|
|
63181
|
-
console.log(chalk29.dim("
|
|
63487
|
+
console.log(chalk29.dim(" Run `mementos storage mode` to verify the live runtime database."));
|
|
63182
63488
|
});
|
|
63183
63489
|
profileCmd.command("delete <name>").description("Delete a profile and its DB file (irreversible)").option("-y, --yes", "Skip confirmation prompt").action(async (name, opts) => {
|
|
63184
63490
|
if (!opts.yes) {
|
|
@@ -63971,19 +64277,20 @@ function registerMiscCommands(program2) {
|
|
|
63971
64277
|
// src/cli/commands/system-mcp.ts
|
|
63972
64278
|
import chalk38 from "chalk";
|
|
63973
64279
|
function registerMcpCommand(program2) {
|
|
63974
|
-
program2.command("mcp").description("Install mementos MCP server into Claude Code, Codex, or Gemini").option("--claude", "Install into Claude Code (~/.claude/.mcp.json)").option("--codex", "Install into Codex (~/.codex/config.toml)").option("--gemini", "Install into Gemini (~/.gemini/settings.json)").option("--all", "Install into all supported agents").option("--uninstall", "Remove mementos MCP from config").action((opts) => {
|
|
63975
|
-
const { readFileSync: _rfs, writeFileSync: _wfs, existsSync: fileExists } = __require("fs");
|
|
64280
|
+
program2.command("mcp").description("Install mementos MCP server into Claude Code, Codex, Cursor, or Gemini").option("--claude", "Install into Claude Code (~/.claude/.mcp.json)").option("--codex", "Install into Codex (~/.codex/config.toml)").option("--cursor", "Install into Cursor (~/.cursor/mcp.json)").option("--gemini", "Install into Gemini (~/.gemini/settings.json)").option("--all", "Install into all supported agents").option("--uninstall", "Remove mementos MCP from config").action((opts) => {
|
|
64281
|
+
const { readFileSync: _rfs, writeFileSync: _wfs, existsSync: fileExists, mkdirSync: makeDir } = __require("fs");
|
|
63976
64282
|
const { join: pathJoin } = __require("path");
|
|
63977
64283
|
const { homedir: getHome } = __require("os");
|
|
63978
64284
|
const home = getHome();
|
|
63979
64285
|
const mementosCmd = process.argv[0]?.includes("bun") ? pathJoin(home, ".bun", "bin", "mementos-mcp") : "mementos-mcp";
|
|
63980
|
-
const targets = opts.all ? ["claude", "codex", "gemini"] : [
|
|
64286
|
+
const targets = opts.all ? ["claude", "codex", "cursor", "gemini"] : [
|
|
63981
64287
|
opts.claude ? "claude" : null,
|
|
63982
64288
|
opts.codex ? "codex" : null,
|
|
64289
|
+
opts.cursor ? "cursor" : null,
|
|
63983
64290
|
opts.gemini ? "gemini" : null
|
|
63984
64291
|
].filter(Boolean);
|
|
63985
64292
|
if (targets.length === 0) {
|
|
63986
|
-
console.log(chalk38.yellow("Specify a target: --claude, --codex, --gemini, or --all"));
|
|
64293
|
+
console.log(chalk38.yellow("Specify a target: --claude, --codex, --cursor, --gemini, or --all"));
|
|
63987
64294
|
console.log(chalk38.gray("Example: mementos mcp --all"));
|
|
63988
64295
|
return;
|
|
63989
64296
|
}
|
|
@@ -64029,6 +64336,28 @@ args = []
|
|
|
64029
64336
|
console.log(chalk38.yellow(`Codex config not found: ${configPath}`));
|
|
64030
64337
|
}
|
|
64031
64338
|
}
|
|
64339
|
+
if (target === "cursor") {
|
|
64340
|
+
const configDir = pathJoin(home, ".cursor");
|
|
64341
|
+
const configPath = pathJoin(configDir, "mcp.json");
|
|
64342
|
+
let config = {};
|
|
64343
|
+
if (fileExists(configPath)) {
|
|
64344
|
+
config = JSON.parse(_rfs(configPath, "utf-8"));
|
|
64345
|
+
} else if (opts.uninstall) {
|
|
64346
|
+
console.log(chalk38.yellow(`mementos was not installed in Cursor: ${configPath}`));
|
|
64347
|
+
continue;
|
|
64348
|
+
}
|
|
64349
|
+
const servers = config["mcpServers"] || {};
|
|
64350
|
+
if (opts.uninstall) {
|
|
64351
|
+
delete servers["mementos"];
|
|
64352
|
+
} else {
|
|
64353
|
+
servers["mementos"] = { command: mementosCmd, args: [] };
|
|
64354
|
+
}
|
|
64355
|
+
config["mcpServers"] = servers;
|
|
64356
|
+
makeDir(configDir, { recursive: true });
|
|
64357
|
+
_wfs(configPath, JSON.stringify(config, null, 2) + `
|
|
64358
|
+
`, "utf-8");
|
|
64359
|
+
console.log(chalk38.green(`${opts.uninstall ? "Removed from" : "Installed into"} Cursor: ${configPath}`));
|
|
64360
|
+
}
|
|
64032
64361
|
if (target === "gemini") {
|
|
64033
64362
|
const configPath = pathJoin(home, ".gemini", "settings.json");
|
|
64034
64363
|
let config = {};
|
|
@@ -64060,7 +64389,7 @@ import chalk39 from "chalk";
|
|
|
64060
64389
|
import { resolve as resolve19 } from "path";
|
|
64061
64390
|
function registerWatchCommand(program2) {
|
|
64062
64391
|
const handleError = makeHandleError(program2);
|
|
64063
|
-
program2.command("watch").description("Watch for new and changed memories in real-time").option("
|
|
64392
|
+
program2.command("watch").description("Watch for new and changed memories in real-time").option("--scope <scope>", "Scope filter: global, shared, private, working").option("-c, --category <cat>", "Category filter: preference, fact, knowledge, history, procedural, resource").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").option("--interval <ms>", "Poll interval in milliseconds", parseInt).action((opts) => {
|
|
64064
64393
|
try {
|
|
64065
64394
|
const globalOpts = program2.opts();
|
|
64066
64395
|
const agentId = opts.agent || globalOpts.agent;
|
|
@@ -64656,6 +64985,35 @@ function getStorageSyncStatus(options = {}) {
|
|
|
64656
64985
|
}
|
|
64657
64986
|
}
|
|
64658
64987
|
|
|
64988
|
+
// src/db/store-backend.ts
|
|
64989
|
+
init_database();
|
|
64990
|
+
init_api_mode();
|
|
64991
|
+
init_storage();
|
|
64992
|
+
function resolveStoreBackend() {
|
|
64993
|
+
const apiMode = isApiMode();
|
|
64994
|
+
const apiConfig = getApiConfig();
|
|
64995
|
+
const storageMode = getStorageMode();
|
|
64996
|
+
const sources = getApiModeEnvSources();
|
|
64997
|
+
const backend = apiMode ? "cloud-api" : storageMode === "cloud" ? "cloud-postgres" : "local-sqlite";
|
|
64998
|
+
let selectedBy = "default";
|
|
64999
|
+
if (apiMode) {
|
|
65000
|
+
selectedBy = `${sources.urlKey} + ${sources.keyKey} (presence)`;
|
|
65001
|
+
} else if (backend === "cloud-postgres") {
|
|
65002
|
+
const modeKey = [MEMENTOS_STORAGE_ENV.mode, MEMENTOS_STORAGE_FALLBACK_ENV.mode].find((key) => process.env[key]?.trim());
|
|
65003
|
+
selectedBy = modeKey ?? sources.databaseUrlKey ?? "storage config file";
|
|
65004
|
+
}
|
|
65005
|
+
return {
|
|
65006
|
+
schema: "mementos.store_backend.v1",
|
|
65007
|
+
backend,
|
|
65008
|
+
api_mode: apiMode,
|
|
65009
|
+
storage_mode: storageMode,
|
|
65010
|
+
db_path: getDbPath(),
|
|
65011
|
+
api_endpoint: apiConfig?.baseUrl ?? null,
|
|
65012
|
+
api_key_present: Boolean(apiConfig?.apiKey),
|
|
65013
|
+
selected_by: selectedBy
|
|
65014
|
+
};
|
|
65015
|
+
}
|
|
65016
|
+
|
|
64659
65017
|
// src/cli/commands/storage.ts
|
|
64660
65018
|
function parseTables(raw) {
|
|
64661
65019
|
if (!raw) {
|
|
@@ -64680,7 +65038,38 @@ function printSyncResult(result) {
|
|
|
64680
65038
|
}
|
|
64681
65039
|
}
|
|
64682
65040
|
function installStorageSubcommands(storage, program2) {
|
|
64683
|
-
storage.command("
|
|
65041
|
+
withoutStartupDbAccess(storage.command("mode").description("Show which store this process will actually read and write (no DB or network access)").option("--json", "Output JSON").action((opts) => {
|
|
65042
|
+
const useJson = Boolean(opts.json || program2.opts().json);
|
|
65043
|
+
let report;
|
|
65044
|
+
try {
|
|
65045
|
+
report = resolveStoreBackend();
|
|
65046
|
+
} catch (error) {
|
|
65047
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
65048
|
+
if (useJson)
|
|
65049
|
+
outputJson2(true, { ok: false, error: message });
|
|
65050
|
+
else
|
|
65051
|
+
console.error(chalk40.red(message));
|
|
65052
|
+
process.exitCode = 1;
|
|
65053
|
+
return;
|
|
65054
|
+
}
|
|
65055
|
+
if (useJson) {
|
|
65056
|
+
outputJson2(true, report);
|
|
65057
|
+
return;
|
|
65058
|
+
}
|
|
65059
|
+
const label = report.backend === "local-sqlite" ? chalk40.green(report.backend) : chalk40.yellow(report.backend);
|
|
65060
|
+
console.log(`Backend: ${label}`);
|
|
65061
|
+
console.log(`Selected by: ${report.selected_by}`);
|
|
65062
|
+
console.log(`API mode: ${report.api_mode ? "yes" : "no"}`);
|
|
65063
|
+
console.log(`Storage mode: ${report.storage_mode}`);
|
|
65064
|
+
if (report.backend === "local-sqlite") {
|
|
65065
|
+
console.log(`Database: ${report.db_path}`);
|
|
65066
|
+
} else {
|
|
65067
|
+
console.log(`API endpoint: ${report.api_endpoint ?? "(none)"}`);
|
|
65068
|
+
console.log(`API key: ${report.api_key_present ? "configured" : "not configured"}`);
|
|
65069
|
+
console.log(`Local SQLite (not authoritative): ${report.db_path}`);
|
|
65070
|
+
}
|
|
65071
|
+
}));
|
|
65072
|
+
storage.command("status").description("Show local database, legacy sync, and storage runtime status").option("--json", "Output JSON").action((opts) => {
|
|
64684
65073
|
const useJson = Boolean(opts.json || program2.opts().json);
|
|
64685
65074
|
const status = getStorageSyncStatus();
|
|
64686
65075
|
const config = getStorageConfig();
|
|
@@ -64837,7 +65226,7 @@ function installStorageSubcommands(storage, program2) {
|
|
|
64837
65226
|
process.exitCode = 1;
|
|
64838
65227
|
}
|
|
64839
65228
|
});
|
|
64840
|
-
storage.command("feedback").description("Save feedback
|
|
65229
|
+
storage.command("feedback").description("Save feedback to the selected store").argument("<message>", "Feedback message").option("--email <email>", "Contact email").option("--category <category>", "Feedback category", "general").option("--json", "Output JSON").action((message, opts) => {
|
|
64841
65230
|
const useJson = Boolean(opts.json || program2.opts().json);
|
|
64842
65231
|
try {
|
|
64843
65232
|
const { saveFeedback: saveFeedback2 } = (init_feedback(), __toCommonJS(exports_feedback));
|
|
@@ -64864,7 +65253,7 @@ function installStorageSubcommands(storage, program2) {
|
|
|
64864
65253
|
});
|
|
64865
65254
|
}
|
|
64866
65255
|
function registerStorageCommands(program2) {
|
|
64867
|
-
const storage = program2.command("storage").description("
|
|
65256
|
+
const storage = program2.command("storage").description("Inspect storage and manage migrations or legacy row sync");
|
|
64868
65257
|
installStorageSubcommands(storage, program2);
|
|
64869
65258
|
}
|
|
64870
65259
|
|
|
@@ -65140,7 +65529,7 @@ function parseMemoryLink(row) {
|
|
|
65140
65529
|
function createMemoryLink(input, db) {
|
|
65141
65530
|
const d = db || getDatabase();
|
|
65142
65531
|
const id = shortUuid();
|
|
65143
|
-
const timestamp =
|
|
65532
|
+
const timestamp = now();
|
|
65144
65533
|
d.run(`INSERT OR IGNORE INTO memory_links (id, source_memory_id, target_memory_id, relation_type, run_id, metadata, created_at)
|
|
65145
65534
|
VALUES (?, ?, ?, ?, ?, ?, ?)`, [
|
|
65146
65535
|
id,
|
|
@@ -65226,7 +65615,7 @@ function createRun(options, db) {
|
|
|
65226
65615
|
options.projectId ?? null,
|
|
65227
65616
|
options.agentId ?? null,
|
|
65228
65617
|
options.dryRun ?? true ? 1 : 0,
|
|
65229
|
-
|
|
65618
|
+
now()
|
|
65230
65619
|
]);
|
|
65231
65620
|
return getRun(id, db);
|
|
65232
65621
|
}
|
|
@@ -65271,7 +65660,7 @@ function persistAction(action, db) {
|
|
|
65271
65660
|
action.reason,
|
|
65272
65661
|
JSON.stringify(action.plannedChanges),
|
|
65273
65662
|
action.applied ? 1 : 0,
|
|
65274
|
-
|
|
65663
|
+
now()
|
|
65275
65664
|
]);
|
|
65276
65665
|
}
|
|
65277
65666
|
function markActionApplied(action, db) {
|
|
@@ -65646,11 +66035,11 @@ async function runConsolidation(options = {}) {
|
|
|
65646
66035
|
actions = applied;
|
|
65647
66036
|
}
|
|
65648
66037
|
const summary = buildSummary2(actions);
|
|
65649
|
-
run = updateRun(run.id, { status: "completed", summary, completed_at:
|
|
66038
|
+
run = updateRun(run.id, { status: "completed", summary, completed_at: now() }, db);
|
|
65650
66039
|
return { run, actions, dryRun, summary };
|
|
65651
66040
|
} catch (error) {
|
|
65652
66041
|
const message = error instanceof Error ? error.message : String(error);
|
|
65653
|
-
run = updateRun(run.id, { status: "failed", error: message, completed_at:
|
|
66042
|
+
run = updateRun(run.id, { status: "failed", error: message, completed_at: now() }, db);
|
|
65654
66043
|
return {
|
|
65655
66044
|
run,
|
|
65656
66045
|
actions: [],
|
|
@@ -65723,7 +66112,7 @@ function createRun2(options, memoryIds, db) {
|
|
|
65723
66112
|
options.provider ?? null,
|
|
65724
66113
|
options.model ?? null,
|
|
65725
66114
|
JSON.stringify(memoryIds),
|
|
65726
|
-
|
|
66115
|
+
now()
|
|
65727
66116
|
]);
|
|
65728
66117
|
return getRun2(id, db);
|
|
65729
66118
|
}
|
|
@@ -65766,7 +66155,7 @@ function insertLessonRow(runId, lesson, db) {
|
|
|
65766
66155
|
lesson.lesson,
|
|
65767
66156
|
JSON.stringify(lesson.evidence),
|
|
65768
66157
|
lesson.importance,
|
|
65769
|
-
|
|
66158
|
+
now()
|
|
65770
66159
|
]);
|
|
65771
66160
|
}
|
|
65772
66161
|
function listToolEventsForTrajectory(options, db) {
|
|
@@ -66059,7 +66448,7 @@ async function reflectOnTrajectory(options) {
|
|
|
66059
66448
|
}
|
|
66060
66449
|
});
|
|
66061
66450
|
}
|
|
66062
|
-
run = updateRun2(run.id, { status: "completed", summary: criticResult.summary, completed_at:
|
|
66451
|
+
run = updateRun2(run.id, { status: "completed", summary: criticResult.summary, completed_at: now() }, db);
|
|
66063
66452
|
return {
|
|
66064
66453
|
run,
|
|
66065
66454
|
dryRun,
|
|
@@ -66073,7 +66462,7 @@ async function reflectOnTrajectory(options) {
|
|
|
66073
66462
|
};
|
|
66074
66463
|
} catch (error40) {
|
|
66075
66464
|
const message = error40 instanceof Error ? error40.message : String(error40);
|
|
66076
|
-
run = updateRun2(run.id, { status: "failed", error: message, completed_at:
|
|
66465
|
+
run = updateRun2(run.id, { status: "failed", error: message, completed_at: now() }, db);
|
|
66077
66466
|
return {
|
|
66078
66467
|
run,
|
|
66079
66468
|
dryRun,
|
|
@@ -66489,6 +66878,26 @@ function makeBrainsCommand() {
|
|
|
66489
66878
|
return brains;
|
|
66490
66879
|
}
|
|
66491
66880
|
|
|
66881
|
+
// src/cli/register-all.ts
|
|
66882
|
+
function registerAllCommands(program2) {
|
|
66883
|
+
registerInitCommand(program2);
|
|
66884
|
+
registerMemoryCommands(program2);
|
|
66885
|
+
registerInfoCommands(program2);
|
|
66886
|
+
registerIoCommands(program2);
|
|
66887
|
+
registerAgentCommands(program2);
|
|
66888
|
+
registerProjectCommands(program2);
|
|
66889
|
+
registerProjectPanelCommand(program2);
|
|
66890
|
+
registerEntityCommands(program2);
|
|
66891
|
+
registerRelationCommands(program2);
|
|
66892
|
+
registerGraphCommands(program2);
|
|
66893
|
+
registerSystemCommands(program2);
|
|
66894
|
+
registerStorageCommands(program2);
|
|
66895
|
+
registerConsolidationCommands(program2);
|
|
66896
|
+
program2.addCommand(makeBrainsCommand());
|
|
66897
|
+
registerEventsCommands(program2, { source: "mementos" });
|
|
66898
|
+
return program2;
|
|
66899
|
+
}
|
|
66900
|
+
|
|
66492
66901
|
// src/cli/index.tsx
|
|
66493
66902
|
function getPackageVersion2() {
|
|
66494
66903
|
try {
|
|
@@ -66500,9 +66909,12 @@ function getPackageVersion2() {
|
|
|
66500
66909
|
}
|
|
66501
66910
|
}
|
|
66502
66911
|
var program2 = new Command;
|
|
66503
|
-
program2.name("mementos").description("Universal memory system for AI agents").version(getPackageVersion2())
|
|
66912
|
+
program2.name("mementos").description("Universal memory system for AI agents").version(getPackageVersion2());
|
|
66913
|
+
applyGlobalOptions(program2);
|
|
66504
66914
|
var startupWarningShown = false;
|
|
66505
|
-
program2.hook("preAction", () => {
|
|
66915
|
+
program2.hook("preAction", (_thisCommand, actionCommand) => {
|
|
66916
|
+
if (skipsStartupDbAccess(actionCommand))
|
|
66917
|
+
return;
|
|
66506
66918
|
if (startupWarningShown)
|
|
66507
66919
|
return;
|
|
66508
66920
|
startupWarningShown = true;
|
|
@@ -66513,19 +66925,5 @@ program2.hook("preAction", () => {
|
|
|
66513
66925
|
}
|
|
66514
66926
|
} catch {}
|
|
66515
66927
|
});
|
|
66516
|
-
|
|
66517
|
-
registerMemoryCommands(program2);
|
|
66518
|
-
registerInfoCommands(program2);
|
|
66519
|
-
registerIoCommands(program2);
|
|
66520
|
-
registerAgentCommands(program2);
|
|
66521
|
-
registerProjectCommands(program2);
|
|
66522
|
-
registerProjectPanelCommand(program2);
|
|
66523
|
-
registerEntityCommands(program2);
|
|
66524
|
-
registerRelationCommands(program2);
|
|
66525
|
-
registerGraphCommands(program2);
|
|
66526
|
-
registerSystemCommands(program2);
|
|
66527
|
-
registerStorageCommands(program2);
|
|
66528
|
-
registerConsolidationCommands(program2);
|
|
66529
|
-
program2.addCommand(makeBrainsCommand());
|
|
66530
|
-
registerEventsCommands(program2, { source: "mementos" });
|
|
66928
|
+
registerAllCommands(program2);
|
|
66531
66929
|
program2.parse(process.argv);
|