@hasna/mementos 0.14.85 → 0.14.87
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/bun.lock +63 -4
- package/dist/cli/__fixtures__/io-restore-stub-server.d.ts +2 -0
- package/dist/cli/__fixtures__/io-restore-stub-server.d.ts.map +1 -0
- package/dist/cli/commands/info-stale.d.ts.map +1 -1
- package/dist/cli/commands/io-restore.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-list.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-remove.d.ts.map +1 -1
- package/dist/cli/index.js +623 -208
- package/dist/db/__fixtures__/list-filter-capture-server.d.ts +2 -0
- package/dist/db/__fixtures__/list-filter-capture-server.d.ts.map +1 -0
- package/dist/db/__fixtures__/list-filter-client-runner.d.ts +2 -0
- package/dist/db/__fixtures__/list-filter-client-runner.d.ts.map +1 -0
- package/dist/db/agents.d.ts +14 -0
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/analytics.d.ts +4 -0
- package/dist/db/analytics.d.ts.map +1 -1
- package/dist/db/memories.d.ts +6 -0
- package/dist/db/memories.d.ts.map +1 -1
- package/dist/db/session-jobs.d.ts +18 -0
- package/dist/db/session-jobs.d.ts.map +1 -1
- package/dist/db/webhook_hooks.d.ts +24 -1
- package/dist/db/webhook_hooks.d.ts.map +1 -1
- package/dist/diagnostics/historical-project-registration-receipt.js +36 -17
- package/dist/index.js +141 -52
- package/dist/lib/built-in-hooks.d.ts +22 -2
- package/dist/lib/built-in-hooks.d.ts.map +1 -1
- package/dist/lib/file-deps.d.ts +1 -1
- package/dist/lib/open-sessions-connector.d.ts +6 -6
- package/dist/lib/open-sessions-connector.d.ts.map +1 -1
- package/dist/lib/redact.d.ts +16 -0
- package/dist/lib/redact.d.ts.map +1 -1
- package/dist/lib/session-processor.d.ts.map +1 -1
- package/dist/lib/session-queue.d.ts.map +1 -1
- package/dist/lib/storage-sync.d.ts.map +1 -1
- package/dist/mcp/index.js +348 -70
- package/dist/mcp/tools/memory-lifecycle.d.ts.map +1 -1
- package/dist/pg-sync-worker.js +7 -5
- package/dist/project-registration.js +108 -36
- package/dist/sdk/index.d.ts +9 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +563 -299
- package/dist/server/routes/system-hooks.d.ts.map +1 -1
- package/dist/storage.d.ts +16 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +63 -26
- package/dist/test-support/pg-sync-stub-worker.d.ts +2 -0
- package/dist/test-support/pg-sync-stub-worker.d.ts.map +1 -0
- package/dist/types/hooks.d.ts +1 -1
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -1
- package/hasna.contract.json +5 -4
- package/package.json +4 -3
package/dist/cli/index.js
CHANGED
|
@@ -1011,7 +1011,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1011
1011
|
this._exitCallback = (err) => {
|
|
1012
1012
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
1013
1013
|
throw err;
|
|
1014
|
-
}
|
|
1014
|
+
} else {}
|
|
1015
1015
|
};
|
|
1016
1016
|
}
|
|
1017
1017
|
return this;
|
|
@@ -2274,6 +2274,7 @@ function translateSql(sql) {
|
|
|
2274
2274
|
let translated = sql.replace(/\?/g, () => `$${++parameterIndex}`);
|
|
2275
2275
|
const ISO_FMT = `'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'`;
|
|
2276
2276
|
translated = translated.replace(/datetime\s*\(\s*'now'\s*\)/gi, `to_char(now() AT TIME ZONE 'UTC', ${ISO_FMT})`);
|
|
2277
|
+
translated = translated.replace(/strftime\s*\(\s*'%Y-%m-%dT%H:%M:%fZ'\s*,\s*'now'\s*\)/gi, `to_char(now() AT TIME ZONE 'UTC', ${ISO_FMT})`);
|
|
2277
2278
|
translated = translated.replace(/datetime\s*\(\s*'now'\s*,\s*'(-?\d+)\s+(minutes?|hours?|days?|seconds?)'\s*\)/gi, (_match, amount, unit) => {
|
|
2278
2279
|
const parsed = parseInt(String(amount), 10);
|
|
2279
2280
|
const absolute = Math.abs(parsed);
|
|
@@ -2758,16 +2759,24 @@ function transferRows(target, table, rows, options) {
|
|
|
2758
2759
|
const conflictColumn = options.conflictColumn ?? "updated_at";
|
|
2759
2760
|
let written = 0;
|
|
2760
2761
|
let skipped = 0;
|
|
2762
|
+
let maxSyncedAt = null;
|
|
2761
2763
|
const errors = [];
|
|
2764
|
+
const bumpMaxSyncedAt = (row) => {
|
|
2765
|
+
const value = row[conflictColumn];
|
|
2766
|
+
if (typeof value === "string" && (maxSyncedAt === null || value > maxSyncedAt)) {
|
|
2767
|
+
maxSyncedAt = value;
|
|
2768
|
+
}
|
|
2769
|
+
};
|
|
2762
2770
|
if (rows.length === 0) {
|
|
2763
|
-
return { written, skipped, errors };
|
|
2771
|
+
return { written, skipped, errors, maxSyncedAt };
|
|
2764
2772
|
}
|
|
2765
2773
|
const columns = Object.keys(rows[0] ?? {});
|
|
2766
2774
|
if (!columns.includes(primaryKey)) {
|
|
2767
2775
|
return {
|
|
2768
2776
|
written,
|
|
2769
2777
|
skipped,
|
|
2770
|
-
errors: [`Table "${table}" has no "${primaryKey}" column; skipping`]
|
|
2778
|
+
errors: [`Table "${table}" has no "${primaryKey}" column; skipping`],
|
|
2779
|
+
maxSyncedAt
|
|
2771
2780
|
};
|
|
2772
2781
|
}
|
|
2773
2782
|
const hasConflictColumn = columns.includes(conflictColumn);
|
|
@@ -2780,6 +2789,7 @@ function transferRows(target, table, rows, options) {
|
|
|
2780
2789
|
const incomingTime = Date.parse(String(row[conflictColumn]));
|
|
2781
2790
|
if (Number.isFinite(existingTime) && Number.isFinite(incomingTime) && existingTime >= incomingTime) {
|
|
2782
2791
|
skipped++;
|
|
2792
|
+
bumpMaxSyncedAt(row);
|
|
2783
2793
|
continue;
|
|
2784
2794
|
}
|
|
2785
2795
|
}
|
|
@@ -2792,11 +2802,12 @@ function transferRows(target, table, rows, options) {
|
|
|
2792
2802
|
target.run(`INSERT INTO "${table}" (${columnList}) VALUES (${placeholders})`, ...columns.map((column) => row[column]));
|
|
2793
2803
|
}
|
|
2794
2804
|
written++;
|
|
2805
|
+
bumpMaxSyncedAt(row);
|
|
2795
2806
|
} catch (error) {
|
|
2796
2807
|
errors.push(`Row ${String(row[primaryKey] ?? "unknown")}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2797
2808
|
}
|
|
2798
2809
|
}
|
|
2799
|
-
return { written, skipped, errors };
|
|
2810
|
+
return { written, skipped, errors, maxSyncedAt };
|
|
2800
2811
|
}
|
|
2801
2812
|
function incrementalSyncPush(local, remote, tables, options = {}) {
|
|
2802
2813
|
return runIncrementalSync("push", local, remote, local, tables, options);
|
|
@@ -2834,22 +2845,29 @@ function runIncrementalSync(direction, source, target, metaDb, tables, options)
|
|
|
2834
2845
|
rows = source.all(`SELECT * FROM "${table}"`);
|
|
2835
2846
|
stat.first_sync = true;
|
|
2836
2847
|
}
|
|
2848
|
+
let maxSyncedAt = null;
|
|
2837
2849
|
for (let offset = 0;offset < rows.length; offset += batchSize) {
|
|
2838
2850
|
const batch = rows.slice(offset, offset + batchSize);
|
|
2839
2851
|
const result = transferRows(target, table, batch, options);
|
|
2840
2852
|
stat.synced_rows += result.written;
|
|
2841
2853
|
stat.skipped_rows += result.skipped;
|
|
2842
2854
|
stat.errors.push(...result.errors);
|
|
2855
|
+
if (result.maxSyncedAt !== null && (maxSyncedAt === null || result.maxSyncedAt > maxSyncedAt)) {
|
|
2856
|
+
maxSyncedAt = result.maxSyncedAt;
|
|
2857
|
+
}
|
|
2843
2858
|
}
|
|
2844
2859
|
if (rows.length === 0) {
|
|
2845
2860
|
stat.skipped_rows = stat.total_rows;
|
|
2846
2861
|
}
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2862
|
+
const nextCursor = maxSyncedAt ?? meta?.last_synced_at ?? null;
|
|
2863
|
+
if (rows.length > 0 && stat.errors.length === 0 && nextCursor !== null) {
|
|
2864
|
+
upsertSyncMeta(metaDb, {
|
|
2865
|
+
table_name: table,
|
|
2866
|
+
last_synced_at: nextCursor,
|
|
2867
|
+
last_synced_row_count: stat.synced_rows,
|
|
2868
|
+
direction
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
2853
2871
|
} catch (error) {
|
|
2854
2872
|
stat.errors.push(`Table "${table}": ${error instanceof Error ? error.message : String(error)}`);
|
|
2855
2873
|
}
|
|
@@ -2877,8 +2895,13 @@ var init_storage = __esm(() => {
|
|
|
2877
2895
|
data;
|
|
2878
2896
|
closed = false;
|
|
2879
2897
|
lastError = null;
|
|
2898
|
+
generation = 0;
|
|
2880
2899
|
static DATA_BYTES = 128 * 1024 * 1024;
|
|
2881
|
-
static
|
|
2900
|
+
static queryTimeoutMs() {
|
|
2901
|
+
const raw = process.env["MEMENTOS_PGSYNC_QUERY_TIMEOUT_MS"]?.trim();
|
|
2902
|
+
const parsed = raw ? Number(raw) : Number.NaN;
|
|
2903
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 60000;
|
|
2904
|
+
}
|
|
2882
2905
|
static resolveWorkerPath() {
|
|
2883
2906
|
const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
2884
2907
|
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
@@ -2893,12 +2916,12 @@ var init_storage = __esm(() => {
|
|
|
2893
2916
|
}
|
|
2894
2917
|
return candidates[0];
|
|
2895
2918
|
}
|
|
2896
|
-
constructor(connectionString) {
|
|
2897
|
-
const control = new SharedArrayBuffer(
|
|
2919
|
+
constructor(connectionString, workerPath) {
|
|
2920
|
+
const control = new SharedArrayBuffer(12);
|
|
2898
2921
|
const dataSab = new SharedArrayBuffer(PgSyncPool.DATA_BYTES);
|
|
2899
2922
|
this.status = new Int32Array(control);
|
|
2900
2923
|
this.data = new Uint8Array(dataSab);
|
|
2901
|
-
this.worker = new Worker(PgSyncPool.resolveWorkerPath(), {
|
|
2924
|
+
this.worker = new Worker(workerPath ?? PgSyncPool.resolveWorkerPath(), {
|
|
2902
2925
|
workerData: {
|
|
2903
2926
|
dsn: stripSslParams(connectionString),
|
|
2904
2927
|
ssl: sslConfigFor(connectionString),
|
|
@@ -2916,21 +2939,35 @@ var init_storage = __esm(() => {
|
|
|
2916
2939
|
throw new Error("PgSyncPool is closed");
|
|
2917
2940
|
if (this.lastError)
|
|
2918
2941
|
throw this.lastError;
|
|
2942
|
+
const timeoutMs = PgSyncPool.queryTimeoutMs();
|
|
2943
|
+
const gen = ++this.generation;
|
|
2919
2944
|
Atomics.store(this.status, 0, 0);
|
|
2920
|
-
this.
|
|
2921
|
-
|
|
2922
|
-
const
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2945
|
+
Atomics.store(this.status, 2, 0);
|
|
2946
|
+
this.worker.postMessage({ sql, params, gen });
|
|
2947
|
+
const deadline = Date.now() + timeoutMs;
|
|
2948
|
+
for (;; ) {
|
|
2949
|
+
const remaining = deadline - Date.now();
|
|
2950
|
+
const responding = Atomics.load(this.status, 0);
|
|
2951
|
+
if (responding === gen) {
|
|
2952
|
+
const code = Atomics.load(this.status, 2);
|
|
2953
|
+
const len = Atomics.load(this.status, 1);
|
|
2954
|
+
const payload = JSON.parse(new TextDecoder().decode(this.data.subarray(0, len)));
|
|
2955
|
+
if (code === 2) {
|
|
2956
|
+
throw new Error(payload.message ?? "PostgreSQL error");
|
|
2957
|
+
}
|
|
2958
|
+
return payload;
|
|
2959
|
+
}
|
|
2960
|
+
if (responding !== 0) {
|
|
2961
|
+
Atomics.compareExchange(this.status, 0, responding, 0);
|
|
2962
|
+
continue;
|
|
2963
|
+
}
|
|
2964
|
+
if (remaining <= 0) {
|
|
2965
|
+
if (this.lastError)
|
|
2966
|
+
throw this.lastError;
|
|
2967
|
+
throw new Error(`PostgreSQL query timed out after ${timeoutMs}ms`);
|
|
2968
|
+
}
|
|
2969
|
+
Atomics.wait(this.status, 0, 0, remaining);
|
|
2932
2970
|
}
|
|
2933
|
-
return payload;
|
|
2934
2971
|
}
|
|
2935
2972
|
end() {
|
|
2936
2973
|
if (this.closed)
|
|
@@ -4982,6 +5019,30 @@ function redactSecrets(text) {
|
|
|
4982
5019
|
}
|
|
4983
5020
|
return result;
|
|
4984
5021
|
}
|
|
5022
|
+
function redactValueTree(value) {
|
|
5023
|
+
if (typeof value === "string")
|
|
5024
|
+
return redactSecrets(value);
|
|
5025
|
+
if (Array.isArray(value))
|
|
5026
|
+
return value.map(redactValueTree);
|
|
5027
|
+
if (value !== null && typeof value === "object") {
|
|
5028
|
+
const out = {};
|
|
5029
|
+
for (const [k, v] of Object.entries(value)) {
|
|
5030
|
+
out[k] = redactValueTree(v);
|
|
5031
|
+
}
|
|
5032
|
+
return out;
|
|
5033
|
+
}
|
|
5034
|
+
return value;
|
|
5035
|
+
}
|
|
5036
|
+
function redactMemoryForOutput(memory) {
|
|
5037
|
+
return {
|
|
5038
|
+
...memory,
|
|
5039
|
+
key: redactSecrets(memory.key),
|
|
5040
|
+
value: redactSecrets(memory.value),
|
|
5041
|
+
summary: memory.summary ? redactSecrets(memory.summary) : null,
|
|
5042
|
+
when_to_use: memory.when_to_use ? redactSecrets(memory.when_to_use) : null,
|
|
5043
|
+
metadata: redactValueTree(memory.metadata)
|
|
5044
|
+
};
|
|
5045
|
+
}
|
|
4985
5046
|
var REDACTED = "[REDACTED]", SECRET_PATTERNS;
|
|
4986
5047
|
var init_redact = __esm(() => {
|
|
4987
5048
|
SECRET_PATTERNS = [
|
|
@@ -5267,6 +5328,7 @@ __export(exports_memories, {
|
|
|
5267
5328
|
updateMemory: () => updateMemory,
|
|
5268
5329
|
touchMemory: () => touchMemory,
|
|
5269
5330
|
semanticSearch: () => semanticSearch,
|
|
5331
|
+
reservedAgentIdViolation: () => reservedAgentIdViolation,
|
|
5270
5332
|
parseMemoryRow: () => parseMemoryRow,
|
|
5271
5333
|
listMemoryHistoryPage: () => listMemoryHistoryPage,
|
|
5272
5334
|
listMemoryHistory: () => listMemoryHistory,
|
|
@@ -5338,7 +5400,20 @@ function parseMemoryRow(row) {
|
|
|
5338
5400
|
accessed_at: row["accessed_at"] || null
|
|
5339
5401
|
};
|
|
5340
5402
|
}
|
|
5403
|
+
function reservedAgentIdViolation(agentId) {
|
|
5404
|
+
if (!agentId)
|
|
5405
|
+
return null;
|
|
5406
|
+
const normalized = agentId.trim().toLowerCase();
|
|
5407
|
+
if (RESERVED_AGENT_IDS.has(normalized)) {
|
|
5408
|
+
return `Reserved placeholder agent id "${agentId}" cannot own a memory. ` + `Refusing to write: test harnesses must not write memories under placeholder agent ` + `identities. Register a real agent (mementos register-agent <name>) and pass its id.`;
|
|
5409
|
+
}
|
|
5410
|
+
return null;
|
|
5411
|
+
}
|
|
5341
5412
|
function createMemory(input, dedupeMode = "merge", db) {
|
|
5413
|
+
const reservedViolation = reservedAgentIdViolation(input.agent_id);
|
|
5414
|
+
if (reservedViolation) {
|
|
5415
|
+
throw new Error(reservedViolation);
|
|
5416
|
+
}
|
|
5342
5417
|
if (!db && isApiMode()) {
|
|
5343
5418
|
const { status, data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
|
|
5344
5419
|
if (!data || !data.id) {
|
|
@@ -5388,7 +5463,8 @@ function createMemory(input, dedupeMode = "merge", db) {
|
|
|
5388
5463
|
importance = ?, metadata = ?, expires_at = ?,
|
|
5389
5464
|
when_to_use = ?,
|
|
5390
5465
|
pinned = COALESCE(pinned, 0),
|
|
5391
|
-
version = version + 1, updated_at =
|
|
5466
|
+
version = version + 1, updated_at = ?,
|
|
5467
|
+
updated_by_agent = ?
|
|
5392
5468
|
WHERE id = ?`, [
|
|
5393
5469
|
safeValue,
|
|
5394
5470
|
input.category || "knowledge",
|
|
@@ -5399,6 +5475,7 @@ function createMemory(input, dedupeMode = "merge", db) {
|
|
|
5399
5475
|
expiresAt,
|
|
5400
5476
|
input.when_to_use || null,
|
|
5401
5477
|
timestamp,
|
|
5478
|
+
input.agent_id || null,
|
|
5402
5479
|
existing.id
|
|
5403
5480
|
]);
|
|
5404
5481
|
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [existing.id]);
|
|
@@ -5503,6 +5580,12 @@ function bulkUpsertMemories(memories, db) {
|
|
|
5503
5580
|
errors.push(`Rejected "${key}": ${formatEnumViolation(violation)}`);
|
|
5504
5581
|
continue;
|
|
5505
5582
|
}
|
|
5583
|
+
const agentViolation = reservedAgentIdViolation(typeof mem["agent_id"] === "string" ? mem["agent_id"] : undefined);
|
|
5584
|
+
if (agentViolation) {
|
|
5585
|
+
rejected++;
|
|
5586
|
+
errors.push(`Rejected "${key}": ${agentViolation}`);
|
|
5587
|
+
continue;
|
|
5588
|
+
}
|
|
5506
5589
|
const timestamp = now();
|
|
5507
5590
|
let tags = [];
|
|
5508
5591
|
const rawTags = mem["tags"];
|
|
@@ -5781,6 +5864,11 @@ function listMemoriesPage(filter, db) {
|
|
|
5781
5864
|
agent_id: f.agent_id,
|
|
5782
5865
|
project_id: f.project_id,
|
|
5783
5866
|
session_id: f.session_id,
|
|
5867
|
+
machine_id: f.machine_id,
|
|
5868
|
+
visible_to_machine_id: f.visible_to_machine_id,
|
|
5869
|
+
search: f.search,
|
|
5870
|
+
source: f.source,
|
|
5871
|
+
flag: f.flag,
|
|
5784
5872
|
namespace: f.namespace,
|
|
5785
5873
|
as_of: f.as_of,
|
|
5786
5874
|
limit: f.limit,
|
|
@@ -6060,20 +6148,29 @@ function updateMemory(id, input, db) {
|
|
|
6060
6148
|
sets.push("when_to_use = ?");
|
|
6061
6149
|
params.push(input.when_to_use ?? null);
|
|
6062
6150
|
}
|
|
6151
|
+
if (input.updated_by_agent !== undefined) {
|
|
6152
|
+
sets.push("updated_by_agent = ?");
|
|
6153
|
+
params.push(input.updated_by_agent ?? null);
|
|
6154
|
+
}
|
|
6063
6155
|
if (input.tags !== undefined) {
|
|
6064
6156
|
sets.push("tags = ?");
|
|
6065
6157
|
params.push(JSON.stringify(input.tags));
|
|
6066
|
-
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [memoryId]);
|
|
6067
|
-
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
6068
|
-
for (const tag of input.tags) {
|
|
6069
|
-
insertTag.run(memoryId, tag);
|
|
6070
|
-
}
|
|
6071
6158
|
}
|
|
6072
6159
|
params.push(memoryId);
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6160
|
+
d.transaction(() => {
|
|
6161
|
+
const res = d.run(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
6162
|
+
if (res.changes === 0) {
|
|
6163
|
+
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.`);
|
|
6164
|
+
}
|
|
6165
|
+
if (input.tags !== undefined) {
|
|
6166
|
+
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [memoryId]);
|
|
6167
|
+
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
6168
|
+
for (const tag of input.tags) {
|
|
6169
|
+
insertTag.run(memoryId, tag);
|
|
6170
|
+
}
|
|
6171
|
+
}
|
|
6172
|
+
return res;
|
|
6173
|
+
});
|
|
6077
6174
|
const updated = getMemory(memoryId, d);
|
|
6078
6175
|
if (input.value !== undefined) {
|
|
6079
6176
|
try {
|
|
@@ -6244,7 +6341,7 @@ async function semanticSearch(queryText, options = {}, db) {
|
|
|
6244
6341
|
scored.sort((a, b) => b.score - a.score);
|
|
6245
6342
|
return scored.slice(0, limit);
|
|
6246
6343
|
}
|
|
6247
|
-
var RECALL_PROMOTE_THRESHOLD = 3;
|
|
6344
|
+
var RESERVED_AGENT_IDS, RECALL_PROMOTE_THRESHOLD = 3;
|
|
6248
6345
|
var init_memories = __esm(() => {
|
|
6249
6346
|
init_types();
|
|
6250
6347
|
init_database();
|
|
@@ -6254,9 +6351,32 @@ var init_memories = __esm(() => {
|
|
|
6254
6351
|
init_poisoning();
|
|
6255
6352
|
init_entity_memories();
|
|
6256
6353
|
init_api_mode();
|
|
6354
|
+
RESERVED_AGENT_IDS = new Set([
|
|
6355
|
+
"agent-a",
|
|
6356
|
+
"agent-x",
|
|
6357
|
+
"agent-z",
|
|
6358
|
+
"nonexistent-agent"
|
|
6359
|
+
]);
|
|
6257
6360
|
});
|
|
6258
6361
|
|
|
6259
6362
|
// src/db/agents.ts
|
|
6363
|
+
import { homedir as homedir3 } from "os";
|
|
6364
|
+
import { join as join5 } from "path";
|
|
6365
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
6366
|
+
function resolveWritingAgentName() {
|
|
6367
|
+
const envName = process.env["MEMENTOS_AGENT"]?.trim();
|
|
6368
|
+
if (envName)
|
|
6369
|
+
return envName;
|
|
6370
|
+
try {
|
|
6371
|
+
const path = join5(homedir3(), ".hasna", "conversations", "agent-id");
|
|
6372
|
+
if (existsSync4(path)) {
|
|
6373
|
+
const fileAgent = readFileSync2(path, "utf8").trim();
|
|
6374
|
+
if (fileAgent)
|
|
6375
|
+
return fileAgent;
|
|
6376
|
+
}
|
|
6377
|
+
} catch {}
|
|
6378
|
+
return null;
|
|
6379
|
+
}
|
|
6260
6380
|
function parseAgentRow(row) {
|
|
6261
6381
|
return {
|
|
6262
6382
|
id: row["id"],
|
|
@@ -6459,17 +6579,17 @@ var init_agents = __esm(() => {
|
|
|
6459
6579
|
});
|
|
6460
6580
|
|
|
6461
6581
|
// src/lib/package-version.ts
|
|
6462
|
-
import { readFileSync as
|
|
6463
|
-
import { dirname as dirname2, join as
|
|
6582
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
6583
|
+
import { dirname as dirname2, join as join6 } from "path";
|
|
6464
6584
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6465
6585
|
function getMementosPackageVersion() {
|
|
6466
6586
|
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
6467
6587
|
for (const candidate of [
|
|
6468
|
-
|
|
6469
|
-
|
|
6588
|
+
join6(here, "..", "..", "package.json"),
|
|
6589
|
+
join6(here, "..", "package.json")
|
|
6470
6590
|
]) {
|
|
6471
6591
|
try {
|
|
6472
|
-
const parsed = JSON.parse(
|
|
6592
|
+
const parsed = JSON.parse(readFileSync3(candidate, "utf8"));
|
|
6473
6593
|
if (typeof parsed.version === "string" && parsed.version.trim())
|
|
6474
6594
|
return parsed.version;
|
|
6475
6595
|
} catch {}
|
|
@@ -7472,13 +7592,13 @@ __export(exports_helpers, {
|
|
|
7472
7592
|
DEFAULT_COMPACT_LIMIT: () => DEFAULT_COMPACT_LIMIT
|
|
7473
7593
|
});
|
|
7474
7594
|
import chalk from "chalk";
|
|
7475
|
-
import { readFileSync as
|
|
7476
|
-
import { dirname as dirname3, join as
|
|
7595
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
7596
|
+
import { dirname as dirname3, join as join7, resolve as resolve2 } from "path";
|
|
7477
7597
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7478
7598
|
function getPackageVersion() {
|
|
7479
7599
|
try {
|
|
7480
|
-
const pkgPath =
|
|
7481
|
-
const pkg = JSON.parse(
|
|
7600
|
+
const pkgPath = join7(dirname3(fileURLToPath3(import.meta.url)), "..", "..", "package.json");
|
|
7601
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
7482
7602
|
return pkg.version || "0.0.0";
|
|
7483
7603
|
} catch {
|
|
7484
7604
|
return "0.0.0";
|
|
@@ -8037,16 +8157,16 @@ function validateConfigKeyValue(key, value, DEFAULT_CONFIG) {
|
|
|
8037
8157
|
return null;
|
|
8038
8158
|
}
|
|
8039
8159
|
function getConfigPath() {
|
|
8040
|
-
const { homedir:
|
|
8041
|
-
return
|
|
8160
|
+
const { homedir: homedir4 } = __require("os");
|
|
8161
|
+
return join7(homedir4(), ".hasna", "mementos", "config.json");
|
|
8042
8162
|
}
|
|
8043
8163
|
function readFileConfig() {
|
|
8044
|
-
const { existsSync:
|
|
8164
|
+
const { existsSync: existsSync5 } = __require("fs");
|
|
8045
8165
|
const configPath = getConfigPath();
|
|
8046
|
-
if (!
|
|
8166
|
+
if (!existsSync5(configPath))
|
|
8047
8167
|
return {};
|
|
8048
8168
|
try {
|
|
8049
|
-
const data = JSON.parse(
|
|
8169
|
+
const data = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
8050
8170
|
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
8051
8171
|
throw new Error("expected a JSON object");
|
|
8052
8172
|
}
|
|
@@ -8057,10 +8177,10 @@ function readFileConfig() {
|
|
|
8057
8177
|
}
|
|
8058
8178
|
}
|
|
8059
8179
|
function writeFileConfig(data) {
|
|
8060
|
-
const { existsSync:
|
|
8180
|
+
const { existsSync: existsSync5, writeFileSync: writeFileSync3, mkdirSync: mkdirSync3 } = __require("fs");
|
|
8061
8181
|
const configPath = getConfigPath();
|
|
8062
8182
|
const dir = dirname3(configPath);
|
|
8063
|
-
if (!
|
|
8183
|
+
if (!existsSync5(dir))
|
|
8064
8184
|
mkdirSync3(dir, { recursive: true });
|
|
8065
8185
|
writeFileSync3(configPath, JSON.stringify(data, null, 2) + `
|
|
8066
8186
|
`, "utf-8");
|
|
@@ -8488,7 +8608,7 @@ function buildFilterConditions(filter) {
|
|
|
8488
8608
|
const conditions = [];
|
|
8489
8609
|
const params = [];
|
|
8490
8610
|
conditions.push("m.status = 'active'");
|
|
8491
|
-
conditions.push("(m.expires_at IS NULL OR m.expires_at >=
|
|
8611
|
+
conditions.push("(m.expires_at IS NULL OR m.expires_at >= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))");
|
|
8492
8612
|
if (!filter)
|
|
8493
8613
|
return { conditions, params };
|
|
8494
8614
|
if (filter.scope) {
|
|
@@ -9856,6 +9976,7 @@ var init_auto_memory = __esm(() => {
|
|
|
9856
9976
|
// src/db/webhook_hooks.ts
|
|
9857
9977
|
var exports_webhook_hooks = {};
|
|
9858
9978
|
__export(exports_webhook_hooks, {
|
|
9979
|
+
validateWebhookHandlerUrl: () => validateWebhookHandlerUrl,
|
|
9859
9980
|
updateWebhookHook: () => updateWebhookHook,
|
|
9860
9981
|
recordWebhookInvocation: () => recordWebhookInvocation,
|
|
9861
9982
|
listWebhookHooks: () => listWebhookHooks,
|
|
@@ -9863,6 +9984,8 @@ __export(exports_webhook_hooks, {
|
|
|
9863
9984
|
deleteWebhookHook: () => deleteWebhookHook,
|
|
9864
9985
|
createWebhookHook: () => createWebhookHook
|
|
9865
9986
|
});
|
|
9987
|
+
import { isIP } from "net";
|
|
9988
|
+
import { lookup as dnsLookup } from "dns/promises";
|
|
9866
9989
|
function parseRow(row) {
|
|
9867
9990
|
return {
|
|
9868
9991
|
id: row["id"],
|
|
@@ -9879,7 +10002,153 @@ function parseRow(row) {
|
|
|
9879
10002
|
failureCount: row["failure_count"]
|
|
9880
10003
|
};
|
|
9881
10004
|
}
|
|
9882
|
-
function
|
|
10005
|
+
function isBlockedIpv4(parts) {
|
|
10006
|
+
const a = parts[0];
|
|
10007
|
+
const b = parts[1];
|
|
10008
|
+
if (a === 0)
|
|
10009
|
+
return true;
|
|
10010
|
+
if (a === 127)
|
|
10011
|
+
return true;
|
|
10012
|
+
if (a === 169 && b === 254)
|
|
10013
|
+
return true;
|
|
10014
|
+
if (a === 10)
|
|
10015
|
+
return true;
|
|
10016
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
10017
|
+
return true;
|
|
10018
|
+
if (a === 192 && b === 168)
|
|
10019
|
+
return true;
|
|
10020
|
+
return false;
|
|
10021
|
+
}
|
|
10022
|
+
function isBlockedIpv6(bytes) {
|
|
10023
|
+
const mapped = bytes.slice(0, 10).every((b) => b === 0) && bytes[10] === 255 && bytes[11] === 255;
|
|
10024
|
+
if (mapped)
|
|
10025
|
+
return isBlockedIpv4(bytes.slice(12, 16));
|
|
10026
|
+
if (bytes.every((b) => b === 0))
|
|
10027
|
+
return true;
|
|
10028
|
+
if (bytes.slice(0, 15).every((b) => b === 0) && bytes[15] === 1)
|
|
10029
|
+
return true;
|
|
10030
|
+
if ((bytes[0] & 254) === 252)
|
|
10031
|
+
return true;
|
|
10032
|
+
if (bytes[0] === 254 && (bytes[1] & 192) === 128)
|
|
10033
|
+
return true;
|
|
10034
|
+
return false;
|
|
10035
|
+
}
|
|
10036
|
+
function quadToGroups(quad) {
|
|
10037
|
+
const nums = quad.split(".").map((p) => Number(p));
|
|
10038
|
+
const [a, b, c, d] = nums;
|
|
10039
|
+
const valid = [a, b, c, d].every((n) => n !== undefined && Number.isInteger(n) && n >= 0 && n <= 255);
|
|
10040
|
+
if (!valid)
|
|
10041
|
+
return null;
|
|
10042
|
+
return [a << 8 | b, c << 8 | d];
|
|
10043
|
+
}
|
|
10044
|
+
function parseIpv6Bytes(host) {
|
|
10045
|
+
const groups = host.split("::");
|
|
10046
|
+
if (groups.length > 2)
|
|
10047
|
+
return null;
|
|
10048
|
+
const headRaw = groups[0] ?? "";
|
|
10049
|
+
const tailRaw = groups[1] ?? "";
|
|
10050
|
+
const head = headRaw === "" ? [] : headRaw.split(":");
|
|
10051
|
+
const tail = tailRaw === "" ? [] : tailRaw.split(":");
|
|
10052
|
+
const headNums = [];
|
|
10053
|
+
for (const g of head) {
|
|
10054
|
+
if (!/^[0-9a-f]{1,4}$/i.test(g))
|
|
10055
|
+
return null;
|
|
10056
|
+
headNums.push(parseInt(g, 16));
|
|
10057
|
+
}
|
|
10058
|
+
const tailNums = [];
|
|
10059
|
+
for (const g of tail) {
|
|
10060
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(g)) {
|
|
10061
|
+
const quads = quadToGroups(g);
|
|
10062
|
+
if (!quads)
|
|
10063
|
+
return null;
|
|
10064
|
+
tailNums.push(...quads);
|
|
10065
|
+
} else if (/^[0-9a-f]{1,4}$/i.test(g)) {
|
|
10066
|
+
tailNums.push(parseInt(g, 16));
|
|
10067
|
+
} else {
|
|
10068
|
+
return null;
|
|
10069
|
+
}
|
|
10070
|
+
}
|
|
10071
|
+
const hasCompression = groups.length === 2;
|
|
10072
|
+
if (!hasCompression && headNums.length !== 8)
|
|
10073
|
+
return null;
|
|
10074
|
+
if (hasCompression && headNums.length + tailNums.length >= 8)
|
|
10075
|
+
return null;
|
|
10076
|
+
const zeros = 8 - headNums.length - tailNums.length;
|
|
10077
|
+
const all = [...headNums, ...new Array(zeros).fill(0), ...tailNums];
|
|
10078
|
+
const bytes = [];
|
|
10079
|
+
for (const n of all) {
|
|
10080
|
+
bytes.push(n >> 8 & 255, n & 255);
|
|
10081
|
+
}
|
|
10082
|
+
return bytes;
|
|
10083
|
+
}
|
|
10084
|
+
function defaultResolveHost(hostname2) {
|
|
10085
|
+
return dnsLookup(hostname2, { all: true, verbatim: true });
|
|
10086
|
+
}
|
|
10087
|
+
function assertResolvedAddressPublic(address, url) {
|
|
10088
|
+
const version = isIP(address);
|
|
10089
|
+
if (version === 4) {
|
|
10090
|
+
if (isBlockedIpv4(address.split(".").map((p) => Number(p)))) {
|
|
10091
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
10092
|
+
}
|
|
10093
|
+
} else if (version === 6) {
|
|
10094
|
+
const bytes = parseIpv6Bytes(address);
|
|
10095
|
+
if (!bytes || isBlockedIpv6(bytes)) {
|
|
10096
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
10097
|
+
}
|
|
10098
|
+
} else {
|
|
10099
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
10100
|
+
}
|
|
10101
|
+
}
|
|
10102
|
+
async function validateWebhookHandlerUrl(url, opts) {
|
|
10103
|
+
const resolveHost = opts?.lookup ?? defaultResolveHost;
|
|
10104
|
+
let parsed;
|
|
10105
|
+
try {
|
|
10106
|
+
parsed = new URL(url);
|
|
10107
|
+
} catch {
|
|
10108
|
+
throw new Error(`Invalid webhook handler URL "${url}" \u2014 must be a valid http(s) URL`);
|
|
10109
|
+
}
|
|
10110
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
10111
|
+
throw new Error(`Invalid webhook handler URL "${url}" \u2014 only http and https are allowed`);
|
|
10112
|
+
}
|
|
10113
|
+
if (parsed.username || parsed.password) {
|
|
10114
|
+
throw new Error(`Invalid webhook handler URL "${url}" \u2014 embedded credentials are not allowed`);
|
|
10115
|
+
}
|
|
10116
|
+
const host = parsed.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
10117
|
+
if (host === "localhost" || host.endsWith(".localhost")) {
|
|
10118
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
10119
|
+
}
|
|
10120
|
+
const version = isIP(host);
|
|
10121
|
+
if (version === 4 || version === 6) {
|
|
10122
|
+
if (version === 4) {
|
|
10123
|
+
if (isBlockedIpv4(host.split(".").map((p) => Number(p)))) {
|
|
10124
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
10125
|
+
}
|
|
10126
|
+
} else {
|
|
10127
|
+
const bytes = parseIpv6Bytes(host);
|
|
10128
|
+
if (!bytes || isBlockedIpv6(bytes)) {
|
|
10129
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
10130
|
+
}
|
|
10131
|
+
}
|
|
10132
|
+
return;
|
|
10133
|
+
}
|
|
10134
|
+
if (/^[0-9]+(\.[0-9]+)*$/.test(host) || /^0x[0-9a-f]+$/i.test(host) || host.includes("%")) {
|
|
10135
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
10136
|
+
}
|
|
10137
|
+
let addrs;
|
|
10138
|
+
try {
|
|
10139
|
+
addrs = await resolveHost(host);
|
|
10140
|
+
} catch {
|
|
10141
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
10142
|
+
}
|
|
10143
|
+
if (addrs.length === 0) {
|
|
10144
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
10145
|
+
}
|
|
10146
|
+
for (const { address } of addrs) {
|
|
10147
|
+
assertResolvedAddressPublic(address, url);
|
|
10148
|
+
}
|
|
10149
|
+
}
|
|
10150
|
+
async function createWebhookHook(input, db, opts) {
|
|
10151
|
+
await validateWebhookHandlerUrl(input.handlerUrl, opts);
|
|
9883
10152
|
if (!db && isApiMode()) {
|
|
9884
10153
|
const { data } = apiJson("POST", "/webhooks", {
|
|
9885
10154
|
type: input.type,
|
|
@@ -9997,6 +10266,7 @@ function recordWebhookInvocation(id, success, db) {
|
|
|
9997
10266
|
d.run("UPDATE webhook_hooks SET invocation_count = invocation_count + 1, failure_count = failure_count + 1 WHERE id = ?", [id]);
|
|
9998
10267
|
}
|
|
9999
10268
|
}
|
|
10269
|
+
var BLOCKED_TARGET_MESSAGE = "Invalid webhook handler URL \u2014 loopback, link-local, and private network targets are not allowed";
|
|
10000
10270
|
var init_webhook_hooks = __esm(() => {
|
|
10001
10271
|
init_database();
|
|
10002
10272
|
init_api_mode();
|
|
@@ -10568,7 +10838,7 @@ async function detectContradiction(newKey, newValue, options = {}, db) {
|
|
|
10568
10838
|
conditions.push("project_id = ?");
|
|
10569
10839
|
params.push(project_id);
|
|
10570
10840
|
}
|
|
10571
|
-
conditions.push("(valid_until IS NULL OR valid_until >
|
|
10841
|
+
conditions.push("(valid_until IS NULL OR valid_until > strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))");
|
|
10572
10842
|
const sql = `SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY importance DESC LIMIT 10`;
|
|
10573
10843
|
const rows = d.query(sql).all(...params);
|
|
10574
10844
|
if (rows.length === 0) {
|
|
@@ -10615,6 +10885,7 @@ var init_contradiction = __esm(() => {
|
|
|
10615
10885
|
var exports_built_in_hooks = {};
|
|
10616
10886
|
__export(exports_built_in_hooks, {
|
|
10617
10887
|
reloadWebhooks: () => reloadWebhooks,
|
|
10888
|
+
makeWebhookHandler: () => makeWebhookHandler,
|
|
10618
10889
|
loadWebhooksFromDb: () => loadWebhooksFromDb
|
|
10619
10890
|
});
|
|
10620
10891
|
async function getAutoMemory() {
|
|
@@ -10624,13 +10895,19 @@ async function getAutoMemory() {
|
|
|
10624
10895
|
}
|
|
10625
10896
|
return _processConversationTurn;
|
|
10626
10897
|
}
|
|
10627
|
-
function loadWebhooksFromDb() {
|
|
10898
|
+
async function loadWebhooksFromDb() {
|
|
10628
10899
|
if (_webhooksLoaded)
|
|
10629
10900
|
return;
|
|
10630
10901
|
_webhooksLoaded = true;
|
|
10631
10902
|
try {
|
|
10632
10903
|
const webhooks = listWebhookHooks({ enabled: true });
|
|
10633
10904
|
for (const wh of webhooks) {
|
|
10905
|
+
try {
|
|
10906
|
+
await validateWebhookHandlerUrl(wh.handlerUrl);
|
|
10907
|
+
} catch (err) {
|
|
10908
|
+
console.error(`[hooks] Skipping webhook ${wh.id} (${wh.type}): ${err instanceof Error ? err.message : String(err)}`);
|
|
10909
|
+
continue;
|
|
10910
|
+
}
|
|
10634
10911
|
hookRegistry.register({
|
|
10635
10912
|
type: wh.type,
|
|
10636
10913
|
blocking: wh.blocking,
|
|
@@ -10648,9 +10925,10 @@ function loadWebhooksFromDb() {
|
|
|
10648
10925
|
console.error("[hooks] Failed to load webhooks from DB:", err);
|
|
10649
10926
|
}
|
|
10650
10927
|
}
|
|
10651
|
-
function makeWebhookHandler(webhookId, url) {
|
|
10928
|
+
function makeWebhookHandler(webhookId, url, opts) {
|
|
10652
10929
|
return async (context) => {
|
|
10653
10930
|
try {
|
|
10931
|
+
await validateWebhookHandlerUrl(url, opts);
|
|
10654
10932
|
const res = await fetch(url, {
|
|
10655
10933
|
method: "POST",
|
|
10656
10934
|
headers: { "Content-Type": "application/json" },
|
|
@@ -10663,9 +10941,9 @@ function makeWebhookHandler(webhookId, url) {
|
|
|
10663
10941
|
}
|
|
10664
10942
|
};
|
|
10665
10943
|
}
|
|
10666
|
-
function reloadWebhooks() {
|
|
10944
|
+
async function reloadWebhooks() {
|
|
10667
10945
|
_webhooksLoaded = false;
|
|
10668
|
-
loadWebhooksFromDb();
|
|
10946
|
+
await loadWebhooksFromDb();
|
|
10669
10947
|
}
|
|
10670
10948
|
var _processConversationTurn = null, _webhooksLoaded = false;
|
|
10671
10949
|
var init_built_in_hooks = __esm(() => {
|
|
@@ -11762,10 +12040,12 @@ var init_synthesis2 = __esm(() => {
|
|
|
11762
12040
|
var exports_session_jobs = {};
|
|
11763
12041
|
__export(exports_session_jobs, {
|
|
11764
12042
|
updateSessionJob: () => updateSessionJob,
|
|
12043
|
+
recoverStaleProcessingJobs: () => recoverStaleProcessingJobs,
|
|
11765
12044
|
listSessionJobs: () => listSessionJobs,
|
|
11766
12045
|
getSessionJob: () => getSessionJob,
|
|
11767
12046
|
getNextPendingJob: () => getNextPendingJob,
|
|
11768
|
-
createSessionJob: () => createSessionJob
|
|
12047
|
+
createSessionJob: () => createSessionJob,
|
|
12048
|
+
claimSessionJob: () => claimSessionJob
|
|
11769
12049
|
});
|
|
11770
12050
|
function parseJobRow(row) {
|
|
11771
12051
|
return {
|
|
@@ -11895,6 +12175,18 @@ function getNextPendingJob(db) {
|
|
|
11895
12175
|
return null;
|
|
11896
12176
|
return parseJobRow(row);
|
|
11897
12177
|
}
|
|
12178
|
+
function claimSessionJob(id, db) {
|
|
12179
|
+
const d = db || getDatabase();
|
|
12180
|
+
const startedAt = now();
|
|
12181
|
+
const result = d.run("UPDATE session_memory_jobs SET status = 'processing', started_at = ? WHERE id = ? AND status = 'pending'", [startedAt, id]);
|
|
12182
|
+
return result.changes;
|
|
12183
|
+
}
|
|
12184
|
+
function recoverStaleProcessingJobs(maxAgeMs, db) {
|
|
12185
|
+
const d = db || getDatabase();
|
|
12186
|
+
const cutoff = new Date(Date.now() - maxAgeMs).toISOString();
|
|
12187
|
+
const result = d.run("UPDATE session_memory_jobs SET status = 'pending', started_at = NULL WHERE status = 'processing' AND started_at < ?", [cutoff]);
|
|
12188
|
+
return result.changes;
|
|
12189
|
+
}
|
|
11898
12190
|
var init_session_jobs = __esm(() => {
|
|
11899
12191
|
init_database();
|
|
11900
12192
|
init_api_mode();
|
|
@@ -12433,7 +12725,11 @@ async function processSessionJob(jobId, db) {
|
|
|
12433
12725
|
return result;
|
|
12434
12726
|
}
|
|
12435
12727
|
try {
|
|
12436
|
-
|
|
12728
|
+
const changes = claimSessionJob(jobId, db);
|
|
12729
|
+
if (changes === 0) {
|
|
12730
|
+
result.errors.push(`Job already claimed or not pending: ${jobId}`);
|
|
12731
|
+
return result;
|
|
12732
|
+
}
|
|
12437
12733
|
} catch (e) {
|
|
12438
12734
|
result.errors.push(`Failed to mark job as processing: ${String(e)}`);
|
|
12439
12735
|
return result;
|
|
@@ -12556,6 +12852,9 @@ function startSessionQueueWorker() {
|
|
|
12556
12852
|
return;
|
|
12557
12853
|
_workerStarted = true;
|
|
12558
12854
|
setInterval(() => {
|
|
12855
|
+
try {
|
|
12856
|
+
recoverStaleProcessingJobs(30 * 60 * 1000);
|
|
12857
|
+
} catch {}
|
|
12559
12858
|
_processNext();
|
|
12560
12859
|
}, 5000);
|
|
12561
12860
|
}
|
|
@@ -12609,13 +12908,13 @@ __export(exports_session_registry, {
|
|
|
12609
12908
|
closeRegistry: () => closeRegistry,
|
|
12610
12909
|
cleanStaleSessions: () => cleanStaleSessions
|
|
12611
12910
|
});
|
|
12612
|
-
import { existsSync as
|
|
12613
|
-
import { dirname as dirname6, join as
|
|
12911
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
|
|
12912
|
+
import { dirname as dirname6, join as join10 } from "path";
|
|
12614
12913
|
function getDb() {
|
|
12615
12914
|
if (_db2)
|
|
12616
12915
|
return _db2;
|
|
12617
12916
|
const dir = dirname6(DB_PATH);
|
|
12618
|
-
if (!
|
|
12917
|
+
if (!existsSync9(dir))
|
|
12619
12918
|
mkdirSync5(dir, { recursive: true });
|
|
12620
12919
|
_db2 = new SqliteAdapter(DB_PATH);
|
|
12621
12920
|
_db2.run("PRAGMA journal_mode = WAL");
|
|
@@ -12790,7 +13089,7 @@ function closeRegistry() {
|
|
|
12790
13089
|
var DB_PATH, _db2 = null;
|
|
12791
13090
|
var init_session_registry = __esm(() => {
|
|
12792
13091
|
init_storage();
|
|
12793
|
-
DB_PATH =
|
|
13092
|
+
DB_PATH = join10(process.env["HOME"] || process.env["USERPROFILE"] || "~", ".open-sessions-registry.db");
|
|
12794
13093
|
});
|
|
12795
13094
|
|
|
12796
13095
|
// src/db/pg-migrations.ts
|
|
@@ -27266,7 +27565,7 @@ class JSONSchemaGenerator {
|
|
|
27266
27565
|
if (val === undefined) {
|
|
27267
27566
|
if (this.unrepresentable === "throw") {
|
|
27268
27567
|
throw new Error("Literal `undefined` cannot be represented in JSON Schema");
|
|
27269
|
-
}
|
|
27568
|
+
} else {}
|
|
27270
27569
|
} else if (typeof val === "bigint") {
|
|
27271
27570
|
if (this.unrepresentable === "throw") {
|
|
27272
27571
|
throw new Error("BigInt literals cannot be represented in JSON Schema");
|
|
@@ -48916,7 +49215,7 @@ var require_tracestate_impl = __commonJS((exports) => {
|
|
|
48916
49215
|
const value = listMember.slice(i + 1, part.length);
|
|
48917
49216
|
if ((0, tracestate_validators_1.validateKey)(key) && (0, tracestate_validators_1.validateValue)(value)) {
|
|
48918
49217
|
agg.set(key, value);
|
|
48919
|
-
}
|
|
49218
|
+
} else {}
|
|
48920
49219
|
}
|
|
48921
49220
|
return agg;
|
|
48922
49221
|
}, new Map);
|
|
@@ -62271,8 +62570,8 @@ var {
|
|
|
62271
62570
|
|
|
62272
62571
|
// src/cli/index.tsx
|
|
62273
62572
|
init_database();
|
|
62274
|
-
import { readFileSync as
|
|
62275
|
-
import { dirname as dirname8, join as
|
|
62573
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
62574
|
+
import { dirname as dirname8, join as join14 } from "path";
|
|
62276
62575
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
62277
62576
|
|
|
62278
62577
|
// src/db/machines.ts
|
|
@@ -64271,6 +64570,18 @@ function registerCrudCommands(program2) {
|
|
|
64271
64570
|
}
|
|
64272
64571
|
resolvedAgentId = ag.id;
|
|
64273
64572
|
}
|
|
64573
|
+
const requestedSource = opts.source;
|
|
64574
|
+
const claimsAgentAuthor = !requestedSource || requestedSource === "agent";
|
|
64575
|
+
if (!resolvedAgentId && claimsAgentAuthor) {
|
|
64576
|
+
const writingName = resolveWritingAgentName();
|
|
64577
|
+
if (writingName) {
|
|
64578
|
+
resolvedAgentId = getAgent(writingName)?.id ?? registerAgent(writingName).id;
|
|
64579
|
+
} else if (requestedSource === "agent") {
|
|
64580
|
+
throw new Error(`No agent identity: an agent-source save needs a writing agent, and none could be resolved.
|
|
64581
|
+
` + `Pass --agent <name>, set MEMENTOS_AGENT, or write the fleet identity file ` + `(~/.hasna/conversations/agent-id, produced by "conversations agents register").
|
|
64582
|
+
` + `To write without claiming an agent author, omit --source or pass ` + `--source user|system|auto|imported.`);
|
|
64583
|
+
}
|
|
64584
|
+
}
|
|
64274
64585
|
const input = {
|
|
64275
64586
|
key,
|
|
64276
64587
|
value,
|
|
@@ -64280,7 +64591,7 @@ function registerCrudCommands(program2) {
|
|
|
64280
64591
|
tags: mergedTags,
|
|
64281
64592
|
summary: opts.summary,
|
|
64282
64593
|
ttl_ms: opts.ttl ? parseDuration(opts.ttl) : undefined,
|
|
64283
|
-
source:
|
|
64594
|
+
source: requestedSource,
|
|
64284
64595
|
agent_id: resolvedAgentId,
|
|
64285
64596
|
session_id: globalOpts.session
|
|
64286
64597
|
};
|
|
@@ -64347,6 +64658,10 @@ function registerCrudCommands(program2) {
|
|
|
64347
64658
|
const updateInput = {
|
|
64348
64659
|
version: existing.version
|
|
64349
64660
|
};
|
|
64661
|
+
const writingName = resolveWritingAgentName();
|
|
64662
|
+
if (writingName) {
|
|
64663
|
+
updateInput.updated_by_agent = getAgent(writingName)?.id ?? registerAgent(writingName).id;
|
|
64664
|
+
}
|
|
64350
64665
|
if (opts.value !== undefined)
|
|
64351
64666
|
updateInput.value = opts.value;
|
|
64352
64667
|
if (opts.importance !== undefined)
|
|
@@ -64374,7 +64689,7 @@ function registerCrudCommands(program2) {
|
|
|
64374
64689
|
if (violation)
|
|
64375
64690
|
throw new Error(formatEnumViolation(violation));
|
|
64376
64691
|
}
|
|
64377
|
-
const changedFields = Object.keys(updateInput).filter((k) => k !== "version");
|
|
64692
|
+
const changedFields = Object.keys(updateInput).filter((k) => k !== "version" && k !== "updated_by_agent");
|
|
64378
64693
|
if (changedFields.length === 0) {
|
|
64379
64694
|
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.`);
|
|
64380
64695
|
}
|
|
@@ -64404,8 +64719,7 @@ function registerCrudCommands(program2) {
|
|
|
64404
64719
|
}
|
|
64405
64720
|
const matches = getMemoriesByKey(keyOrId, opts.scope, opts.agent, opts.project);
|
|
64406
64721
|
if (matches.length === 0) {
|
|
64407
|
-
|
|
64408
|
-
if (isApiMode() && looksLikeId && deleteMemory(keyOrId)) {
|
|
64722
|
+
if (isApiMode() && keyOrId.length > 0 && deleteMemory(keyOrId)) {
|
|
64409
64723
|
if (globalOpts.json) {
|
|
64410
64724
|
outputJson({ deleted: keyOrId });
|
|
64411
64725
|
} else {
|
|
@@ -64977,7 +65291,7 @@ function registerRemoveCommand(program2) {
|
|
|
64977
65291
|
if (mem)
|
|
64978
65292
|
id = mem.id;
|
|
64979
65293
|
}
|
|
64980
|
-
if (!id && isApiMode() &&
|
|
65294
|
+
if (!id && isApiMode() && nameOrId.length > 0) {
|
|
64981
65295
|
id = nameOrId;
|
|
64982
65296
|
}
|
|
64983
65297
|
if (!id) {
|
|
@@ -65094,6 +65408,7 @@ function registerRecallCommand(program2) {
|
|
|
65094
65408
|
// src/cli/commands/memory-cmd-list.ts
|
|
65095
65409
|
init_projects();
|
|
65096
65410
|
init_memories();
|
|
65411
|
+
init_redact();
|
|
65097
65412
|
init_helpers();
|
|
65098
65413
|
import chalk12 from "chalk";
|
|
65099
65414
|
import { resolve as resolve7 } from "path";
|
|
@@ -65141,36 +65456,37 @@ function registerListCommand(program2) {
|
|
|
65141
65456
|
};
|
|
65142
65457
|
}, limit, offset);
|
|
65143
65458
|
const memories = hasMore && limit !== undefined ? collected.slice(0, limit) : collected;
|
|
65459
|
+
const sanitized = memories.map(redactMemoryForOutput);
|
|
65144
65460
|
if (fmt === "json") {
|
|
65145
|
-
outputJson(
|
|
65461
|
+
outputJson(sanitized);
|
|
65146
65462
|
return;
|
|
65147
65463
|
}
|
|
65148
65464
|
if (fmt === "csv") {
|
|
65149
65465
|
console.log("key,value,scope,category,importance,id");
|
|
65150
|
-
for (const m of
|
|
65466
|
+
for (const m of sanitized) {
|
|
65151
65467
|
const v = m.value.replace(/"/g, '""');
|
|
65152
65468
|
console.log(`"${m.key}","${v}",${m.scope},${m.category},${m.importance},${m.id.slice(0, 8)}`);
|
|
65153
65469
|
}
|
|
65154
65470
|
return;
|
|
65155
65471
|
}
|
|
65156
65472
|
if (fmt === "yaml") {
|
|
65157
|
-
outputYaml(
|
|
65473
|
+
outputYaml(sanitized);
|
|
65158
65474
|
return;
|
|
65159
65475
|
}
|
|
65160
|
-
if (
|
|
65476
|
+
if (sanitized.length === 0) {
|
|
65161
65477
|
console.log(chalk12.yellow("No memories found."));
|
|
65162
65478
|
return;
|
|
65163
65479
|
}
|
|
65164
|
-
console.log(chalk12.bold(`${
|
|
65165
|
-
for (const m of
|
|
65480
|
+
console.log(chalk12.bold(`${sanitized.length}${hasMore ? "+" : ""} memor${sanitized.length === 1 ? "y" : "ies"}:`));
|
|
65481
|
+
for (const m of sanitized) {
|
|
65166
65482
|
console.log(formatMemoryLine(m, {
|
|
65167
65483
|
valueLength: opts.verbose ? 120 : 64,
|
|
65168
65484
|
preferSummary: !opts.verbose
|
|
65169
65485
|
}));
|
|
65170
65486
|
}
|
|
65171
65487
|
printPageHint({
|
|
65172
|
-
shown:
|
|
65173
|
-
limit: limit ??
|
|
65488
|
+
shown: sanitized.length,
|
|
65489
|
+
limit: limit ?? sanitized.length,
|
|
65174
65490
|
offset,
|
|
65175
65491
|
hasMore,
|
|
65176
65492
|
command: "mementos list",
|
|
@@ -65210,7 +65526,10 @@ function getMemoryStats(db) {
|
|
|
65210
65526
|
const byCategory = d.query("SELECT category, COUNT(*) as c FROM memories WHERE status = 'active' GROUP BY category").all();
|
|
65211
65527
|
const byStatus = d.query("SELECT status, COUNT(*) as c FROM memories WHERE status = 'active' GROUP BY status").all();
|
|
65212
65528
|
const pinnedCount = d.query("SELECT COUNT(*) as c FROM memories WHERE pinned = 1 AND status = 'active'").get().c;
|
|
65213
|
-
const expiredCount = d.query("SELECT COUNT(*) as c FROM memories WHERE status = 'expired'
|
|
65529
|
+
const expiredCount = d.query("SELECT COUNT(*) as c FROM memories WHERE status = 'expired'").get().c;
|
|
65530
|
+
const expiresAtCount = d.query("SELECT COUNT(*) as c FROM memories WHERE expires_at IS NOT NULL").get().c;
|
|
65531
|
+
const nowIso = new Date().toISOString();
|
|
65532
|
+
const expiredDueCount = d.query("SELECT COUNT(*) as c FROM memories WHERE status = 'expired' OR (expires_at IS NOT NULL AND expires_at < ?)").get(nowIso).c;
|
|
65214
65533
|
const stats = {
|
|
65215
65534
|
total,
|
|
65216
65535
|
by_scope: { global: 0, shared: 0, private: 0, working: 0 },
|
|
@@ -65218,7 +65537,9 @@ function getMemoryStats(db) {
|
|
|
65218
65537
|
by_status: { active: 0, archived: 0, expired: 0 },
|
|
65219
65538
|
by_agent: {},
|
|
65220
65539
|
pinned_count: pinnedCount,
|
|
65221
|
-
expired_count: expiredCount
|
|
65540
|
+
expired_count: expiredCount,
|
|
65541
|
+
expires_at_count: expiresAtCount,
|
|
65542
|
+
expired_due_count: expiredDueCount
|
|
65222
65543
|
};
|
|
65223
65544
|
for (const row of byScope)
|
|
65224
65545
|
if (row.scope in stats.by_scope)
|
|
@@ -65252,7 +65573,9 @@ function normalizeStats(data) {
|
|
|
65252
65573
|
by_status: { active: 0, archived: 0, expired: 0, ...data?.by_status ?? {} },
|
|
65253
65574
|
by_agent: data?.by_agent ?? {},
|
|
65254
65575
|
pinned_count: data?.pinned_count ?? 0,
|
|
65255
|
-
expired_count: data?.expired_count ?? 0
|
|
65576
|
+
expired_count: data?.expired_count ?? 0,
|
|
65577
|
+
expires_at_count: data?.expires_at_count ?? 0,
|
|
65578
|
+
expired_due_count: data?.expired_due_count ?? 0
|
|
65256
65579
|
};
|
|
65257
65580
|
}
|
|
65258
65581
|
function getMemoryReport(filter = {}, db) {
|
|
@@ -65310,7 +65633,7 @@ function getStaleMemoriesPage(filter = {}, db) {
|
|
|
65310
65633
|
const limit = filter.limit ?? 20;
|
|
65311
65634
|
const offset = filter.offset ?? 0;
|
|
65312
65635
|
if (!db && isApiMode()) {
|
|
65313
|
-
const q = toQuery({ days, project_id: filter.project_id, agent_id: filter.agent_id, limit, offset });
|
|
65636
|
+
const q = toQuery({ days, project_id: filter.project_id, agent_id: filter.agent_id, pinned: filter.pinned, limit, offset });
|
|
65314
65637
|
const { data } = apiJson("GET", `/memories/stale${q}`);
|
|
65315
65638
|
const rows2 = data?.memories ?? [];
|
|
65316
65639
|
return {
|
|
@@ -65322,8 +65645,14 @@ function getStaleMemoriesPage(filter = {}, db) {
|
|
|
65322
65645
|
}
|
|
65323
65646
|
const d = db || getDatabase();
|
|
65324
65647
|
const cutoffDate = new Date(Date.now() - days * 86400000).toISOString();
|
|
65325
|
-
const conds = ["status = 'active'", "(accessed_at IS NULL OR accessed_at < ?)"
|
|
65648
|
+
const conds = ["status = 'active'", "(accessed_at IS NULL OR accessed_at < ?)"];
|
|
65326
65649
|
const params = [cutoffDate];
|
|
65650
|
+
if (filter.pinned !== undefined) {
|
|
65651
|
+
conds.push("pinned = ?");
|
|
65652
|
+
params.push(filter.pinned ? 1 : 0);
|
|
65653
|
+
} else {
|
|
65654
|
+
conds.push("pinned = 0");
|
|
65655
|
+
}
|
|
65327
65656
|
if (filter.project_id) {
|
|
65328
65657
|
conds.push("project_id = ?");
|
|
65329
65658
|
params.push(filter.project_id);
|
|
@@ -65500,7 +65829,7 @@ init_projects();
|
|
|
65500
65829
|
init_helpers();
|
|
65501
65830
|
function registerStaleCommand(program2) {
|
|
65502
65831
|
const handleError = makeHandleError(program2);
|
|
65503
|
-
program2.command("stale").description("Find memories not accessed recently (for cleanup/review)").option("--days <n>", "Stale threshold in days (default: 30)", parseInt).option("--project <path>", "Project filter").option("--agent <name>", "Agent filter").option("--limit <n>", "Max results (default: 20)", 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").option("--verbose", "Show wider memory snippets").action((opts) => {
|
|
65832
|
+
program2.command("stale").description("Find memories not accessed recently (for cleanup/review)").option("--days <n>", "Stale threshold in days (default: 30)", parseInt).option("--pinned", "Review stale PINNED memories (default view excludes pinned)").option("--project <path>", "Project filter").option("--agent <name>", "Agent filter").option("--limit <n>", "Max results (default: 20)", 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").option("--verbose", "Show wider memory snippets").action((opts) => {
|
|
65504
65833
|
try {
|
|
65505
65834
|
const globalOpts = program2.opts();
|
|
65506
65835
|
const days = opts.days || 30;
|
|
@@ -65521,6 +65850,7 @@ function registerStaleCommand(program2) {
|
|
|
65521
65850
|
const { rows: collected, hasMore } = collectPagedRows((cursor, pageLimit) => {
|
|
65522
65851
|
const page = getStaleMemoriesPage({
|
|
65523
65852
|
days,
|
|
65853
|
+
pinned: opts.pinned ? true : undefined,
|
|
65524
65854
|
project_id: projectId,
|
|
65525
65855
|
agent_id: agentId,
|
|
65526
65856
|
limit: pageLimit,
|
|
@@ -65822,7 +66152,7 @@ init_memories();
|
|
|
65822
66152
|
init_helpers();
|
|
65823
66153
|
import chalk18 from "chalk";
|
|
65824
66154
|
import { resolve as resolve12 } from "path";
|
|
65825
|
-
import { readFileSync as
|
|
66155
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
65826
66156
|
function registerImportCommand(program2) {
|
|
65827
66157
|
const handleError = makeHandleError(program2);
|
|
65828
66158
|
program2.command("import [file]").description("Import memories from a JSON file or stdin (use '-' or pipe data)").option("--overwrite", "Overwrite existing memories (default: merge)").action(async (file, opts) => {
|
|
@@ -65832,7 +66162,7 @@ function registerImportCommand(program2) {
|
|
|
65832
66162
|
if (file === "-" || !file && !process.stdin.isTTY) {
|
|
65833
66163
|
raw = await Bun.stdin.text();
|
|
65834
66164
|
} else if (file) {
|
|
65835
|
-
raw =
|
|
66165
|
+
raw = readFileSync5(resolve12(file), "utf-8");
|
|
65836
66166
|
} else {
|
|
65837
66167
|
console.error(chalk18.red("No input: provide a file path, use '-' for stdin, or pipe data."));
|
|
65838
66168
|
process.exit(1);
|
|
@@ -65862,14 +66192,14 @@ function registerImportCommand(program2) {
|
|
|
65862
66192
|
import chalk19 from "chalk";
|
|
65863
66193
|
|
|
65864
66194
|
// src/lib/config.ts
|
|
65865
|
-
import { existsSync as
|
|
65866
|
-
import { homedir as
|
|
65867
|
-
import { basename, dirname as dirname4, join as
|
|
66195
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, cpSync as cpSync2 } from "fs";
|
|
66196
|
+
import { homedir as homedir4 } from "os";
|
|
66197
|
+
import { basename, dirname as dirname4, join as join8, resolve as resolve13 } from "path";
|
|
65868
66198
|
function isInMemoryDb2(path) {
|
|
65869
66199
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
65870
66200
|
}
|
|
65871
66201
|
function homeDir() {
|
|
65872
|
-
return process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
66202
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir4();
|
|
65873
66203
|
}
|
|
65874
66204
|
var DEFAULT_CONFIG = {
|
|
65875
66205
|
default_scope: "private",
|
|
@@ -65927,11 +66257,11 @@ function isValidCategory(value) {
|
|
|
65927
66257
|
return VALID_CATEGORIES2.includes(value);
|
|
65928
66258
|
}
|
|
65929
66259
|
function loadConfig() {
|
|
65930
|
-
const configPath =
|
|
66260
|
+
const configPath = join8(homeDir(), ".hasna", "mementos", "config.json");
|
|
65931
66261
|
let fileConfig = {};
|
|
65932
|
-
if (
|
|
66262
|
+
if (existsSync5(configPath)) {
|
|
65933
66263
|
try {
|
|
65934
|
-
const raw =
|
|
66264
|
+
const raw = readFileSync6(configPath, "utf-8");
|
|
65935
66265
|
fileConfig = JSON.parse(raw);
|
|
65936
66266
|
} catch {}
|
|
65937
66267
|
}
|
|
@@ -65957,8 +66287,8 @@ function findFileWalkingUp(filename) {
|
|
|
65957
66287
|
let dir = process.cwd();
|
|
65958
66288
|
const legacyHomeMementosDb = resolve13(homeDir(), ".mementos", "mementos.db");
|
|
65959
66289
|
while (true) {
|
|
65960
|
-
const candidate =
|
|
65961
|
-
if (
|
|
66290
|
+
const candidate = join8(dir, filename);
|
|
66291
|
+
if (existsSync5(candidate) && resolve13(candidate) !== legacyHomeMementosDb) {
|
|
65962
66292
|
return candidate;
|
|
65963
66293
|
}
|
|
65964
66294
|
const parent = dirname4(dir);
|
|
@@ -65971,7 +66301,7 @@ function findFileWalkingUp(filename) {
|
|
|
65971
66301
|
function findGitRoot2() {
|
|
65972
66302
|
let dir = process.cwd();
|
|
65973
66303
|
while (true) {
|
|
65974
|
-
if (
|
|
66304
|
+
if (existsSync5(join8(dir, ".git"))) {
|
|
65975
66305
|
return dir;
|
|
65976
66306
|
}
|
|
65977
66307
|
const parent = dirname4(dir);
|
|
@@ -65982,27 +66312,27 @@ function findGitRoot2() {
|
|
|
65982
66312
|
}
|
|
65983
66313
|
}
|
|
65984
66314
|
function profilesDir() {
|
|
65985
|
-
return
|
|
66315
|
+
return join8(homeDir(), ".hasna", "mementos", "profiles");
|
|
65986
66316
|
}
|
|
65987
66317
|
function globalConfigPath() {
|
|
65988
|
-
return
|
|
66318
|
+
return join8(homeDir(), ".hasna", "mementos", "config.json");
|
|
65989
66319
|
}
|
|
65990
66320
|
function readGlobalConfig() {
|
|
65991
66321
|
const p = globalConfigPath();
|
|
65992
|
-
if (!
|
|
66322
|
+
if (!existsSync5(p))
|
|
65993
66323
|
return {};
|
|
65994
66324
|
try {
|
|
65995
|
-
return JSON.parse(
|
|
66325
|
+
return JSON.parse(readFileSync6(p, "utf-8"));
|
|
65996
66326
|
} catch {
|
|
65997
66327
|
return {};
|
|
65998
66328
|
}
|
|
65999
66329
|
}
|
|
66000
66330
|
function readGlobalConfigForWrite() {
|
|
66001
66331
|
const p = globalConfigPath();
|
|
66002
|
-
if (!
|
|
66332
|
+
if (!existsSync5(p))
|
|
66003
66333
|
return {};
|
|
66004
66334
|
try {
|
|
66005
|
-
const data = JSON.parse(
|
|
66335
|
+
const data = JSON.parse(readFileSync6(p, "utf-8"));
|
|
66006
66336
|
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
66007
66337
|
throw new Error("expected a JSON object");
|
|
66008
66338
|
}
|
|
@@ -66035,13 +66365,13 @@ function setActiveProfile(name) {
|
|
|
66035
66365
|
}
|
|
66036
66366
|
function listProfiles() {
|
|
66037
66367
|
const dir = profilesDir();
|
|
66038
|
-
if (!
|
|
66368
|
+
if (!existsSync5(dir))
|
|
66039
66369
|
return [];
|
|
66040
66370
|
return readdirSync(dir).filter((f) => f.endsWith(".db")).map((f) => basename(f, ".db")).sort();
|
|
66041
66371
|
}
|
|
66042
66372
|
function deleteProfile(name) {
|
|
66043
|
-
const dbPath =
|
|
66044
|
-
if (!
|
|
66373
|
+
const dbPath = join8(profilesDir(), `${name}.db`);
|
|
66374
|
+
if (!existsSync5(dbPath))
|
|
66045
66375
|
return false;
|
|
66046
66376
|
unlinkSync2(dbPath);
|
|
66047
66377
|
if (getActiveProfile() === name)
|
|
@@ -66050,10 +66380,10 @@ function deleteProfile(name) {
|
|
|
66050
66380
|
}
|
|
66051
66381
|
function getDbPath2() {
|
|
66052
66382
|
const _home = homeDir();
|
|
66053
|
-
const _newDir =
|
|
66054
|
-
const _oldDir =
|
|
66055
|
-
if (!
|
|
66056
|
-
mkdirSync3(
|
|
66383
|
+
const _newDir = join8(_home, ".hasna", "mementos");
|
|
66384
|
+
const _oldDir = join8(_home, ".mementos");
|
|
66385
|
+
if (!existsSync5(_newDir) && existsSync5(_oldDir)) {
|
|
66386
|
+
mkdirSync3(join8(_home, ".hasna"), { recursive: true });
|
|
66057
66387
|
cpSync2(_oldDir, _newDir, { recursive: true });
|
|
66058
66388
|
}
|
|
66059
66389
|
const envDbPath = process.env["HASNA_MEMENTOS_DB_PATH"] ?? process.env["MEMENTOS_DB_PATH"];
|
|
@@ -66067,7 +66397,7 @@ function getDbPath2() {
|
|
|
66067
66397
|
}
|
|
66068
66398
|
const profile = getActiveProfile();
|
|
66069
66399
|
if (profile) {
|
|
66070
|
-
const profilePath =
|
|
66400
|
+
const profilePath = join8(profilesDir(), `${profile}.db`);
|
|
66071
66401
|
ensureDir2(dirname4(profilePath));
|
|
66072
66402
|
return profilePath;
|
|
66073
66403
|
}
|
|
@@ -66075,21 +66405,21 @@ function getDbPath2() {
|
|
|
66075
66405
|
if (dbScope === "project") {
|
|
66076
66406
|
const gitRoot = findGitRoot2();
|
|
66077
66407
|
if (gitRoot) {
|
|
66078
|
-
const dbPath =
|
|
66408
|
+
const dbPath = join8(gitRoot, ".mementos", "mementos.db");
|
|
66079
66409
|
ensureDir2(dirname4(dbPath));
|
|
66080
66410
|
return dbPath;
|
|
66081
66411
|
}
|
|
66082
66412
|
}
|
|
66083
|
-
const found = findFileWalkingUp(
|
|
66413
|
+
const found = findFileWalkingUp(join8(".mementos", "mementos.db"));
|
|
66084
66414
|
if (found) {
|
|
66085
66415
|
return found;
|
|
66086
66416
|
}
|
|
66087
|
-
const fallback =
|
|
66417
|
+
const fallback = join8(homeDir(), ".hasna", "mementos", "mementos.db");
|
|
66088
66418
|
ensureDir2(dirname4(fallback));
|
|
66089
66419
|
return fallback;
|
|
66090
66420
|
}
|
|
66091
66421
|
function ensureDir2(dir) {
|
|
66092
|
-
if (!
|
|
66422
|
+
if (!existsSync5(dir)) {
|
|
66093
66423
|
mkdirSync3(dir, { recursive: true });
|
|
66094
66424
|
}
|
|
66095
66425
|
}
|
|
@@ -66207,7 +66537,7 @@ init_database();
|
|
|
66207
66537
|
init_helpers();
|
|
66208
66538
|
import chalk20 from "chalk";
|
|
66209
66539
|
import { resolve as resolve14, dirname as dirname5 } from "path";
|
|
66210
|
-
import { existsSync as
|
|
66540
|
+
import { existsSync as existsSync6, statSync, copyFileSync, mkdirSync as mkdirSync4, readdirSync as readdirSync2 } from "fs";
|
|
66211
66541
|
function registerBackupCommand(program2) {
|
|
66212
66542
|
const handleError = makeHandleError(program2);
|
|
66213
66543
|
program2.command("backup [path]").description("Backup the SQLite database to a file").option("--list", "List available backups in ~/.hasna/mementos/backups/").action((targetPath, opts) => {
|
|
@@ -66216,7 +66546,7 @@ function registerBackupCommand(program2) {
|
|
|
66216
66546
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
66217
66547
|
const backupsDir = resolve14(home, ".hasna", "mementos", "backups");
|
|
66218
66548
|
if (opts.list) {
|
|
66219
|
-
if (!
|
|
66549
|
+
if (!existsSync6(backupsDir)) {
|
|
66220
66550
|
if (globalOpts.json) {
|
|
66221
66551
|
outputJson({ backups: [] });
|
|
66222
66552
|
return;
|
|
@@ -66258,7 +66588,7 @@ function registerBackupCommand(program2) {
|
|
|
66258
66588
|
return;
|
|
66259
66589
|
}
|
|
66260
66590
|
const dbPath = getDbPath();
|
|
66261
|
-
if (!
|
|
66591
|
+
if (!existsSync6(dbPath)) {
|
|
66262
66592
|
console.error(chalk20.red(`Database not found at ${dbPath}`));
|
|
66263
66593
|
process.exit(1);
|
|
66264
66594
|
}
|
|
@@ -66266,7 +66596,7 @@ function registerBackupCommand(program2) {
|
|
|
66266
66596
|
if (targetPath) {
|
|
66267
66597
|
dest = resolve14(targetPath);
|
|
66268
66598
|
} else {
|
|
66269
|
-
if (!
|
|
66599
|
+
if (!existsSync6(backupsDir)) {
|
|
66270
66600
|
mkdirSync4(backupsDir, { recursive: true });
|
|
66271
66601
|
}
|
|
66272
66602
|
const now3 = new Date;
|
|
@@ -66274,7 +66604,7 @@ function registerBackupCommand(program2) {
|
|
|
66274
66604
|
dest = resolve14(backupsDir, `mementos-${ts}.db`);
|
|
66275
66605
|
}
|
|
66276
66606
|
const destDir = dirname5(dest);
|
|
66277
|
-
if (!
|
|
66607
|
+
if (!existsSync6(destDir)) {
|
|
66278
66608
|
mkdirSync4(destDir, { recursive: true });
|
|
66279
66609
|
}
|
|
66280
66610
|
copyFileSync(dbPath, dest);
|
|
@@ -66295,29 +66625,31 @@ function registerBackupCommand(program2) {
|
|
|
66295
66625
|
// src/cli/commands/io-restore.ts
|
|
66296
66626
|
init_database();
|
|
66297
66627
|
init_api_mode();
|
|
66628
|
+
init_memories();
|
|
66298
66629
|
init_helpers();
|
|
66299
66630
|
import chalk21 from "chalk";
|
|
66300
66631
|
import { resolve as resolve15 } from "path";
|
|
66301
|
-
import { existsSync as
|
|
66632
|
+
import { existsSync as existsSync7, statSync as statSync2, copyFileSync as copyFileSync2, readdirSync as readdirSync3 } from "fs";
|
|
66633
|
+
function readMemoriesFromBackup(source) {
|
|
66634
|
+
const { Database: Database3 } = __require("bun:sqlite");
|
|
66635
|
+
const backupDb = new Database3(source, { readonly: true });
|
|
66636
|
+
try {
|
|
66637
|
+
const rows = backupDb.query("SELECT * FROM memories").all();
|
|
66638
|
+
return { rows, count: rows.length };
|
|
66639
|
+
} finally {
|
|
66640
|
+
backupDb.close();
|
|
66641
|
+
}
|
|
66642
|
+
}
|
|
66302
66643
|
function registerRestoreCommand(program2) {
|
|
66303
66644
|
const handleError = makeHandleError(program2);
|
|
66304
66645
|
program2.command("restore [file]").description("Restore the database from a backup file").option("--latest", "Restore the most recent backup from ~/.hasna/mementos/backups/").option("--force", "Skip confirmation and perform the restore").action((filePath, opts) => {
|
|
66305
66646
|
try {
|
|
66306
66647
|
const globalOpts = program2.opts();
|
|
66307
|
-
if (isApiMode()) {
|
|
66308
|
-
const msg = "restore operates on the local SQLite database and is not available in API mode (the self-hosted cloud store is authoritative). Unset HASNA_MEMENTOS_API_URL / HASNA_MEMENTOS_API_KEY to restore a local db.";
|
|
66309
|
-
if (globalOpts.json) {
|
|
66310
|
-
outputJson({ error: msg });
|
|
66311
|
-
} else {
|
|
66312
|
-
console.error(chalk21.red(msg));
|
|
66313
|
-
}
|
|
66314
|
-
process.exit(1);
|
|
66315
|
-
}
|
|
66316
66648
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
66317
66649
|
const backupsDir = resolve15(home, ".hasna", "mementos", "backups");
|
|
66318
66650
|
let source;
|
|
66319
66651
|
if (opts.latest) {
|
|
66320
|
-
if (!
|
|
66652
|
+
if (!existsSync7(backupsDir)) {
|
|
66321
66653
|
console.error(chalk21.red("No backups directory found."));
|
|
66322
66654
|
process.exit(1);
|
|
66323
66655
|
}
|
|
@@ -66337,16 +66669,82 @@ function registerRestoreCommand(program2) {
|
|
|
66337
66669
|
console.error(chalk21.red("Provide a backup file path or use --latest"));
|
|
66338
66670
|
process.exit(1);
|
|
66339
66671
|
}
|
|
66340
|
-
if (!
|
|
66672
|
+
if (!existsSync7(source)) {
|
|
66341
66673
|
console.error(chalk21.red(`Backup file not found: ${source}`));
|
|
66342
66674
|
process.exit(1);
|
|
66343
66675
|
}
|
|
66344
|
-
const dbPath = getDbPath();
|
|
66345
66676
|
const backupStat = statSync2(source);
|
|
66346
66677
|
const backupSizeMB = (backupStat.size / (1024 * 1024)).toFixed(1);
|
|
66347
66678
|
const backupSizeStr = backupStat.size >= 1024 * 1024 ? `${backupSizeMB} MB` : `${(backupStat.size / 1024).toFixed(1)} KB`;
|
|
66679
|
+
if (isApiMode()) {
|
|
66680
|
+
const { rows, count } = readMemoriesFromBackup(source);
|
|
66681
|
+
if (!opts.force) {
|
|
66682
|
+
if (globalOpts.json) {
|
|
66683
|
+
outputJson({
|
|
66684
|
+
action: "restore",
|
|
66685
|
+
source,
|
|
66686
|
+
target: "cloud-api",
|
|
66687
|
+
backup_size: backupStat.size,
|
|
66688
|
+
backup_memories: count,
|
|
66689
|
+
status: "dry_run",
|
|
66690
|
+
message: "Restores the backup's memories into the hosted store; existing store rows are never overwritten. Use --force to confirm."
|
|
66691
|
+
});
|
|
66692
|
+
return;
|
|
66693
|
+
}
|
|
66694
|
+
console.log(chalk21.bold("Restore preview (hosted store):"));
|
|
66695
|
+
console.log(` Source: ${chalk21.cyan(source)} (${backupSizeStr})`);
|
|
66696
|
+
console.log(` Backup memories: ${chalk21.green(String(count))}`);
|
|
66697
|
+
console.log(` Target: hosted store (cloud-api)`);
|
|
66698
|
+
console.log(` Semantics: merges the backup's memories; existing rows are never overwritten`);
|
|
66699
|
+
console.log();
|
|
66700
|
+
console.log(chalk21.yellow("Use --force to confirm restore"));
|
|
66701
|
+
return;
|
|
66702
|
+
}
|
|
66703
|
+
const result = bulkUpsertMemories(rows);
|
|
66704
|
+
if (result.rejected > 0) {
|
|
66705
|
+
const msg = `${result.rejected} of ${result.total} memories were rejected and did not persist. See errors.`;
|
|
66706
|
+
if (globalOpts.json) {
|
|
66707
|
+
outputJson({
|
|
66708
|
+
action: "restore",
|
|
66709
|
+
status: "failed",
|
|
66710
|
+
source,
|
|
66711
|
+
target: "cloud-api",
|
|
66712
|
+
inserted: result.inserted,
|
|
66713
|
+
skipped: result.skipped,
|
|
66714
|
+
rejected: result.rejected,
|
|
66715
|
+
total: result.total,
|
|
66716
|
+
error: msg
|
|
66717
|
+
});
|
|
66718
|
+
} else {
|
|
66719
|
+
console.error(chalk21.red(msg));
|
|
66720
|
+
}
|
|
66721
|
+
process.exit(1);
|
|
66722
|
+
}
|
|
66723
|
+
if (globalOpts.json) {
|
|
66724
|
+
outputJson({
|
|
66725
|
+
action: "restore",
|
|
66726
|
+
source,
|
|
66727
|
+
target: "cloud-api",
|
|
66728
|
+
backup_size: backupStat.size,
|
|
66729
|
+
backup_memories: count,
|
|
66730
|
+
restored_memories: result.inserted,
|
|
66731
|
+
inserted: result.inserted,
|
|
66732
|
+
skipped: result.skipped,
|
|
66733
|
+
rejected: result.rejected,
|
|
66734
|
+
total: result.total,
|
|
66735
|
+
status: "completed"
|
|
66736
|
+
});
|
|
66737
|
+
return;
|
|
66738
|
+
}
|
|
66739
|
+
console.log(`Restored from: ${chalk21.green(source)}`);
|
|
66740
|
+
console.log(` Restored memories: ${chalk21.green(String(result.inserted))} (inserted)`);
|
|
66741
|
+
console.log(` Skipped (already present): ${chalk21.yellow(String(result.skipped))}`);
|
|
66742
|
+
console.log(` Rejected: ${chalk21.yellow(String(result.rejected))}`);
|
|
66743
|
+
return;
|
|
66744
|
+
}
|
|
66745
|
+
const dbPath = getDbPath();
|
|
66348
66746
|
let currentCount = 0;
|
|
66349
|
-
if (
|
|
66747
|
+
if (existsSync7(dbPath)) {
|
|
66350
66748
|
try {
|
|
66351
66749
|
const db = getDatabase();
|
|
66352
66750
|
const row = db.query("SELECT COUNT(*) as count FROM memories").get();
|
|
@@ -67600,11 +67998,11 @@ init_memories();
|
|
|
67600
67998
|
init_agents();
|
|
67601
67999
|
init_projects();
|
|
67602
68000
|
import chalk27 from "chalk";
|
|
67603
|
-
import { join as
|
|
67604
|
-
import { homedir as
|
|
68001
|
+
import { join as join9 } from "path";
|
|
68002
|
+
import { homedir as homedir5 } from "os";
|
|
67605
68003
|
import {
|
|
67606
|
-
readFileSync as
|
|
67607
|
-
existsSync as
|
|
68004
|
+
readFileSync as readFileSync7,
|
|
68005
|
+
existsSync as existsSync8,
|
|
67608
68006
|
accessSync,
|
|
67609
68007
|
statSync as statSync3,
|
|
67610
68008
|
constants as fsConstants
|
|
@@ -67639,7 +68037,7 @@ function registerDoctorCommand(program2) {
|
|
|
67639
68037
|
checks.push({ name: "Version", status: "ok", detail: version });
|
|
67640
68038
|
const dbPath = getDbPath();
|
|
67641
68039
|
let db = null;
|
|
67642
|
-
if (dbPath !== ":memory:" &&
|
|
68040
|
+
if (dbPath !== ":memory:" && existsSync8(dbPath)) {
|
|
67643
68041
|
try {
|
|
67644
68042
|
accessSync(dbPath, fsConstants.R_OK | fsConstants.W_OK);
|
|
67645
68043
|
checks.push({ name: "Database file", status: "ok", detail: dbPath });
|
|
@@ -67658,7 +68056,7 @@ function registerDoctorCommand(program2) {
|
|
|
67658
68056
|
checks.push({ name: "Database connection", status: "fail", detail: e instanceof Error ? e.message : String(e) });
|
|
67659
68057
|
}
|
|
67660
68058
|
try {
|
|
67661
|
-
if (dbPath !== ":memory:" &&
|
|
68059
|
+
if (dbPath !== ":memory:" && existsSync8(dbPath)) {
|
|
67662
68060
|
const stats = statSync3(dbPath);
|
|
67663
68061
|
const sizeKb = (stats.size / 1024).toFixed(1);
|
|
67664
68062
|
const sizeMb = (stats.size / (1024 * 1024)).toFixed(2);
|
|
@@ -67791,9 +68189,9 @@ function registerDoctorCommand(program2) {
|
|
|
67791
68189
|
checks.push({ name: "MCP server", status: "warn", detail: "could not check (is claude CLI installed?)" });
|
|
67792
68190
|
}
|
|
67793
68191
|
try {
|
|
67794
|
-
const settingsFilePath =
|
|
67795
|
-
if (
|
|
67796
|
-
const settings = JSON.parse(
|
|
68192
|
+
const settingsFilePath = join9(homedir5(), ".claude", "settings.json");
|
|
68193
|
+
if (existsSync8(settingsFilePath)) {
|
|
68194
|
+
const settings = JSON.parse(readFileSync7(settingsFilePath, "utf-8"));
|
|
67797
68195
|
const hooksObj = settings["hooks"] || {};
|
|
67798
68196
|
const stopHooks = hooksObj["Stop"] || [];
|
|
67799
68197
|
const hasMementos = stopHooks.some((e) => e.hooks?.some((h) => h.command && h.command.includes("mementos")));
|
|
@@ -67809,11 +68207,11 @@ function registerDoctorCommand(program2) {
|
|
|
67809
68207
|
checks.push({ name: "Stop hook", status: "warn", detail: "could not check stop hook" });
|
|
67810
68208
|
}
|
|
67811
68209
|
if (process.platform === "darwin") {
|
|
67812
|
-
const plistFilePath =
|
|
68210
|
+
const plistFilePath = join9(homedir5(), "Library", "LaunchAgents", "com.hasna.mementos.plist");
|
|
67813
68211
|
checks.push({
|
|
67814
68212
|
name: "Auto-start",
|
|
67815
|
-
status:
|
|
67816
|
-
detail:
|
|
68213
|
+
status: existsSync8(plistFilePath) ? "ok" : "warn",
|
|
68214
|
+
detail: existsSync8(plistFilePath) ? "configured (starts on login)" : "not configured \u2192 run: mementos init"
|
|
67817
68215
|
});
|
|
67818
68216
|
} else {
|
|
67819
68217
|
checks.push({ name: "Auto-start", status: "ok", detail: `n/a on ${process.platform}` });
|
|
@@ -67892,9 +68290,9 @@ async function runCloudDoctor(globalOpts, checks) {
|
|
|
67892
68290
|
checks.push({ name: "MCP server", status: "warn", detail: "could not check (is claude CLI installed?)" });
|
|
67893
68291
|
}
|
|
67894
68292
|
try {
|
|
67895
|
-
const settingsFilePath =
|
|
67896
|
-
if (
|
|
67897
|
-
const settings = JSON.parse(
|
|
68293
|
+
const settingsFilePath = join9(homedir5(), ".claude", "settings.json");
|
|
68294
|
+
if (existsSync8(settingsFilePath)) {
|
|
68295
|
+
const settings = JSON.parse(readFileSync7(settingsFilePath, "utf-8"));
|
|
67898
68296
|
const hooksObj = settings["hooks"] || {};
|
|
67899
68297
|
const stopHooks = hooksObj["Stop"] || [];
|
|
67900
68298
|
const hasMementos = stopHooks.some((e) => e.hooks?.some((h) => h.command && h.command.includes("mementos")));
|
|
@@ -67910,11 +68308,11 @@ async function runCloudDoctor(globalOpts, checks) {
|
|
|
67910
68308
|
checks.push({ name: "Stop hook", status: "warn", detail: "could not check stop hook" });
|
|
67911
68309
|
}
|
|
67912
68310
|
if (process.platform === "darwin") {
|
|
67913
|
-
const plistFilePath =
|
|
68311
|
+
const plistFilePath = join9(homedir5(), "Library", "LaunchAgents", "com.hasna.mementos.plist");
|
|
67914
68312
|
checks.push({
|
|
67915
68313
|
name: "Auto-start",
|
|
67916
|
-
status:
|
|
67917
|
-
detail:
|
|
68314
|
+
status: existsSync8(plistFilePath) ? "ok" : "warn",
|
|
68315
|
+
detail: existsSync8(plistFilePath) ? "configured (starts on login)" : "not configured \u2192 run: mementos init"
|
|
67918
68316
|
});
|
|
67919
68317
|
} else {
|
|
67920
68318
|
checks.push({ name: "Auto-start", status: "ok", detail: `n/a on ${process.platform}` });
|
|
@@ -68358,7 +68756,7 @@ By type:`));
|
|
|
68358
68756
|
webhooksCmd.command("create <type> <url>").description("Create a persistent webhook hook").option("--blocking", "Block the operation until the webhook responds").option("--priority <n>", "Hook priority (default 50)", "50").option("--agent <id>", "Scope to specific agent").option("--project <id>", "Scope to specific project").option("--description <text>", "Human-readable description").action(async (type, url, opts) => {
|
|
68359
68757
|
const { createWebhookHook: createWebhookHook2 } = await Promise.resolve().then(() => (init_webhook_hooks(), exports_webhook_hooks));
|
|
68360
68758
|
const { reloadWebhooks: reloadWebhooks2 } = await Promise.resolve().then(() => (init_built_in_hooks(), exports_built_in_hooks));
|
|
68361
|
-
const wh = createWebhookHook2({
|
|
68759
|
+
const wh = await createWebhookHook2({
|
|
68362
68760
|
type,
|
|
68363
68761
|
handlerUrl: url,
|
|
68364
68762
|
blocking: opts.blocking ?? false,
|
|
@@ -68367,7 +68765,7 @@ By type:`));
|
|
|
68367
68765
|
projectId: opts.project,
|
|
68368
68766
|
description: opts.description
|
|
68369
68767
|
});
|
|
68370
|
-
reloadWebhooks2();
|
|
68768
|
+
await reloadWebhooks2();
|
|
68371
68769
|
console.log(chalk31.green("\u2713 Webhook created"));
|
|
68372
68770
|
console.log(` ID: ${chalk31.cyan(wh.id)}`);
|
|
68373
68771
|
console.log(` Type: ${wh.type}`);
|
|
@@ -68388,7 +68786,7 @@ By type:`));
|
|
|
68388
68786
|
const { reloadWebhooks: reloadWebhooks2 } = await Promise.resolve().then(() => (init_built_in_hooks(), exports_built_in_hooks));
|
|
68389
68787
|
const updated = updateWebhookHook2(id, { enabled: true });
|
|
68390
68788
|
if (updated) {
|
|
68391
|
-
reloadWebhooks2();
|
|
68789
|
+
await reloadWebhooks2();
|
|
68392
68790
|
console.log(chalk31.green(`\u2713 Webhook ${id} enabled`));
|
|
68393
68791
|
} else {
|
|
68394
68792
|
console.error(chalk31.red(`Webhook not found: ${id}`));
|
|
@@ -68400,7 +68798,7 @@ By type:`));
|
|
|
68400
68798
|
const { reloadWebhooks: reloadWebhooks2 } = await Promise.resolve().then(() => (init_built_in_hooks(), exports_built_in_hooks));
|
|
68401
68799
|
const updated = updateWebhookHook2(id, { enabled: false });
|
|
68402
68800
|
if (updated) {
|
|
68403
|
-
reloadWebhooks2();
|
|
68801
|
+
await reloadWebhooks2();
|
|
68404
68802
|
console.log(chalk31.yellow(`\u2298 Webhook ${id} disabled`));
|
|
68405
68803
|
} else {
|
|
68406
68804
|
console.error(chalk31.red(`Webhook not found: ${id}`));
|
|
@@ -69440,6 +69838,7 @@ function syncMemoriesTable(source, target, local, direction, currentMachineId) {
|
|
|
69440
69838
|
const countResult = source.get(`SELECT COUNT(*) as cnt FROM "${MEMORY_TABLE}"`);
|
|
69441
69839
|
stat.total_rows = countResult?.cnt ?? 0;
|
|
69442
69840
|
const rows = since ? source.all(`SELECT * FROM "${MEMORY_TABLE}" WHERE updated_at > ?`, since) : source.all(`SELECT * FROM "${MEMORY_TABLE}"`);
|
|
69841
|
+
const erroredRowIds = new Set;
|
|
69443
69842
|
for (const row of rows) {
|
|
69444
69843
|
try {
|
|
69445
69844
|
const sourceMachine = sourceMachineRef(row, currentMachineId);
|
|
@@ -69498,18 +69897,34 @@ function syncMemoriesTable(source, target, local, direction, currentMachineId) {
|
|
|
69498
69897
|
clearMemoryEmbedding(winnerDb, String(winner["id"]));
|
|
69499
69898
|
stat.synced_rows++;
|
|
69500
69899
|
} catch (error) {
|
|
69900
|
+
erroredRowIds.add(String(row["id"] ?? "unknown"));
|
|
69501
69901
|
stat.errors.push(`Memory ${String(row["id"] ?? "unknown")}: ${error instanceof Error ? error.message : String(error)}`);
|
|
69502
69902
|
}
|
|
69503
69903
|
}
|
|
69504
69904
|
if (rows.length === 0) {
|
|
69505
69905
|
stat.skipped_rows = stat.total_rows;
|
|
69506
69906
|
}
|
|
69507
|
-
|
|
69508
|
-
|
|
69509
|
-
|
|
69510
|
-
|
|
69511
|
-
|
|
69512
|
-
|
|
69907
|
+
if (rows.length > 0 && stat.errors.length === 0) {
|
|
69908
|
+
let maxSyncedAt = null;
|
|
69909
|
+
for (const row of rows) {
|
|
69910
|
+
if (erroredRowIds.has(String(row["id"] ?? "unknown"))) {
|
|
69911
|
+
continue;
|
|
69912
|
+
}
|
|
69913
|
+
const value = row["updated_at"];
|
|
69914
|
+
if (typeof value === "string" && (maxSyncedAt === null || value > maxSyncedAt)) {
|
|
69915
|
+
maxSyncedAt = value;
|
|
69916
|
+
}
|
|
69917
|
+
}
|
|
69918
|
+
const nextCursor = maxSyncedAt ?? syncMeta?.last_synced_at ?? null;
|
|
69919
|
+
if (nextCursor !== null) {
|
|
69920
|
+
upsertMemorySyncMeta(local, {
|
|
69921
|
+
table_name: MEMORY_TABLE,
|
|
69922
|
+
direction,
|
|
69923
|
+
last_synced_at: nextCursor,
|
|
69924
|
+
last_synced_row_count: stat.synced_rows
|
|
69925
|
+
});
|
|
69926
|
+
}
|
|
69927
|
+
}
|
|
69513
69928
|
} catch (error) {
|
|
69514
69929
|
stat.errors.push(error instanceof Error ? error.message : String(error));
|
|
69515
69930
|
}
|
|
@@ -69899,19 +70314,19 @@ function registerStorageCommands(program2) {
|
|
|
69899
70314
|
// src/cli/commands/init.ts
|
|
69900
70315
|
import chalk41 from "chalk";
|
|
69901
70316
|
import {
|
|
69902
|
-
readFileSync as
|
|
70317
|
+
readFileSync as readFileSync8,
|
|
69903
70318
|
writeFileSync as writeFileSync4,
|
|
69904
|
-
existsSync as
|
|
70319
|
+
existsSync as existsSync10,
|
|
69905
70320
|
copyFileSync as copyFileSync3,
|
|
69906
70321
|
mkdirSync as mkdirSync6
|
|
69907
70322
|
} from "fs";
|
|
69908
|
-
import { dirname as dirname7, join as
|
|
69909
|
-
import { homedir as
|
|
70323
|
+
import { dirname as dirname7, join as join11 } from "path";
|
|
70324
|
+
import { homedir as homedir6 } from "os";
|
|
69910
70325
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
69911
70326
|
function registerInitCommand(program2) {
|
|
69912
70327
|
program2.command("init").description("One-command setup: register MCP, install stop hook, configure auto-start").action(async () => {
|
|
69913
70328
|
const { platform: platform2 } = process;
|
|
69914
|
-
const home =
|
|
70329
|
+
const home = homedir6();
|
|
69915
70330
|
const isMac = platform2 === "darwin";
|
|
69916
70331
|
console.log("");
|
|
69917
70332
|
console.log(chalk41.bold(" mementos \u2014 setting up your memory layer"));
|
|
@@ -69966,17 +70381,17 @@ function registerInitCommand(program2) {
|
|
|
69966
70381
|
} else {
|
|
69967
70382
|
console.log(chalk41.green(" \u2713 MCP server registered with Claude Code"));
|
|
69968
70383
|
}
|
|
69969
|
-
const hooksDir =
|
|
69970
|
-
const hookDest =
|
|
69971
|
-
const settingsPath =
|
|
70384
|
+
const hooksDir = join11(home, ".claude", "hooks");
|
|
70385
|
+
const hookDest = join11(hooksDir, "mementos-stop-hook.ts");
|
|
70386
|
+
const settingsPath = join11(home, ".claude", "settings.json");
|
|
69972
70387
|
const hookCommand = `bun ${hookDest}`;
|
|
69973
70388
|
let hookAlreadyInstalled = false;
|
|
69974
70389
|
let hookError = null;
|
|
69975
70390
|
try {
|
|
69976
70391
|
let settings = {};
|
|
69977
|
-
if (
|
|
70392
|
+
if (existsSync10(settingsPath)) {
|
|
69978
70393
|
try {
|
|
69979
|
-
settings = JSON.parse(
|
|
70394
|
+
settings = JSON.parse(readFileSync8(settingsPath, "utf-8"));
|
|
69980
70395
|
} catch {
|
|
69981
70396
|
settings = {};
|
|
69982
70397
|
}
|
|
@@ -69987,19 +70402,19 @@ function registerInitCommand(program2) {
|
|
|
69987
70402
|
if (alreadyHasMementos) {
|
|
69988
70403
|
hookAlreadyInstalled = true;
|
|
69989
70404
|
} else {
|
|
69990
|
-
if (!
|
|
70405
|
+
if (!existsSync10(hooksDir)) {
|
|
69991
70406
|
mkdirSync6(hooksDir, { recursive: true });
|
|
69992
70407
|
}
|
|
69993
|
-
if (!
|
|
70408
|
+
if (!existsSync10(hookDest)) {
|
|
69994
70409
|
const packageDir = dirname7(dirname7(fileURLToPath4(import.meta.url)));
|
|
69995
70410
|
const candidatePaths = [
|
|
69996
|
-
|
|
69997
|
-
|
|
69998
|
-
|
|
70411
|
+
join11(packageDir, "scripts", "hooks", "claude-stop-hook.ts"),
|
|
70412
|
+
join11(packageDir, "..", "scripts", "hooks", "claude-stop-hook.ts"),
|
|
70413
|
+
join11(home, ".bun", "install", "global", "node_modules", "@hasna", "mementos", "scripts", "hooks", "claude-stop-hook.ts")
|
|
69999
70414
|
];
|
|
70000
70415
|
let hookSourceFound = false;
|
|
70001
70416
|
for (const src of candidatePaths) {
|
|
70002
|
-
if (
|
|
70417
|
+
if (existsSync10(src)) {
|
|
70003
70418
|
copyFileSync3(src, hookDest);
|
|
70004
70419
|
hookSourceFound = true;
|
|
70005
70420
|
break;
|
|
@@ -70065,7 +70480,7 @@ main().catch(() => {});
|
|
|
70065
70480
|
if (!isMac) {
|
|
70066
70481
|
console.log(chalk41.dim(` \xB7 Auto-start skipped (not macOS \u2014 platform: ${platform2})`));
|
|
70067
70482
|
} else {
|
|
70068
|
-
const plistPath =
|
|
70483
|
+
const plistPath = join11(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
|
|
70069
70484
|
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
70070
70485
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
70071
70486
|
<plist version="1.0">
|
|
@@ -70090,11 +70505,11 @@ main().catch(() => {});
|
|
|
70090
70505
|
</plist>
|
|
70091
70506
|
`;
|
|
70092
70507
|
try {
|
|
70093
|
-
if (
|
|
70508
|
+
if (existsSync10(plistPath)) {
|
|
70094
70509
|
autoStartAlreadyInstalled = true;
|
|
70095
70510
|
} else {
|
|
70096
|
-
const launchAgentsDir =
|
|
70097
|
-
if (!
|
|
70511
|
+
const launchAgentsDir = join11(home, "Library", "LaunchAgents");
|
|
70512
|
+
if (!existsSync10(launchAgentsDir)) {
|
|
70098
70513
|
mkdirSync6(launchAgentsDir, { recursive: true });
|
|
70099
70514
|
}
|
|
70100
70515
|
writeFileSync4(plistPath, plistContent, "utf-8");
|
|
@@ -70110,7 +70525,7 @@ main().catch(() => {});
|
|
|
70110
70525
|
console.log(chalk41.green(" \u2713 Auto-start configured (starts on login)"));
|
|
70111
70526
|
}
|
|
70112
70527
|
if (!autoStartAlreadyInstalled && !autoStartError) {
|
|
70113
|
-
const plistPath2 =
|
|
70528
|
+
const plistPath2 = join11(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
|
|
70114
70529
|
const loadResult = await run(["launchctl", "load", plistPath2]);
|
|
70115
70530
|
if (!loadResult.ok) {
|
|
70116
70531
|
console.log(chalk41.dim(` \xB7 launchctl load: ${loadResult.output || "already loaded"}`));
|
|
@@ -71224,13 +71639,13 @@ function lessonTagForCli(kind) {
|
|
|
71224
71639
|
|
|
71225
71640
|
// src/cli/brains.ts
|
|
71226
71641
|
import {
|
|
71227
|
-
existsSync as
|
|
71642
|
+
existsSync as existsSync12,
|
|
71228
71643
|
mkdirSync as mkdirSync8,
|
|
71229
71644
|
writeFileSync as writeFileSync6,
|
|
71230
71645
|
readdirSync as readdirSync4
|
|
71231
71646
|
} from "fs";
|
|
71232
|
-
import { homedir as
|
|
71233
|
-
import { join as
|
|
71647
|
+
import { homedir as homedir8 } from "os";
|
|
71648
|
+
import { join as join13 } from "path";
|
|
71234
71649
|
import chalk43 from "chalk";
|
|
71235
71650
|
|
|
71236
71651
|
// src/lib/gatherer.ts
|
|
@@ -71307,24 +71722,24 @@ var gatherTrainingData = async (options = {}) => {
|
|
|
71307
71722
|
};
|
|
71308
71723
|
|
|
71309
71724
|
// src/lib/model-config.ts
|
|
71310
|
-
import { existsSync as
|
|
71311
|
-
import { homedir as
|
|
71312
|
-
import { join as
|
|
71725
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
71726
|
+
import { homedir as homedir7 } from "os";
|
|
71727
|
+
import { join as join12 } from "path";
|
|
71313
71728
|
var DEFAULT_MODEL = "gpt-4o-mini";
|
|
71314
|
-
var CONFIG_DIR =
|
|
71315
|
-
var CONFIG_PATH =
|
|
71729
|
+
var CONFIG_DIR = join12(homedir7(), ".hasna", "mementos");
|
|
71730
|
+
var CONFIG_PATH = join12(CONFIG_DIR, "config.json");
|
|
71316
71731
|
function readConfig() {
|
|
71317
|
-
if (!
|
|
71732
|
+
if (!existsSync11(CONFIG_PATH))
|
|
71318
71733
|
return {};
|
|
71319
71734
|
try {
|
|
71320
|
-
const raw =
|
|
71735
|
+
const raw = readFileSync9(CONFIG_PATH, "utf-8");
|
|
71321
71736
|
return JSON.parse(raw);
|
|
71322
71737
|
} catch {
|
|
71323
71738
|
return {};
|
|
71324
71739
|
}
|
|
71325
71740
|
}
|
|
71326
71741
|
function writeConfig(config2) {
|
|
71327
|
-
if (!
|
|
71742
|
+
if (!existsSync11(CONFIG_DIR)) {
|
|
71328
71743
|
mkdirSync7(CONFIG_DIR, { recursive: true });
|
|
71329
71744
|
}
|
|
71330
71745
|
writeFileSync5(CONFIG_PATH, JSON.stringify(config2, null, 2) + `
|
|
@@ -71372,12 +71787,12 @@ function makeBrainsCommand() {
|
|
|
71372
71787
|
limit: opts.limit,
|
|
71373
71788
|
since
|
|
71374
71789
|
});
|
|
71375
|
-
const outputDir = opts.output ??
|
|
71376
|
-
if (!
|
|
71790
|
+
const outputDir = opts.output ?? join13(homedir8(), ".hasna", "mementos", "training");
|
|
71791
|
+
if (!existsSync12(outputDir)) {
|
|
71377
71792
|
mkdirSync8(outputDir, { recursive: true });
|
|
71378
71793
|
}
|
|
71379
71794
|
const timestamp2 = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
71380
|
-
const outputPath =
|
|
71795
|
+
const outputPath = join13(outputDir, `mementos-training-${timestamp2}.jsonl`);
|
|
71381
71796
|
const jsonl = result.examples.map((ex) => JSON.stringify(ex)).join(`
|
|
71382
71797
|
`);
|
|
71383
71798
|
writeFileSync6(outputPath, jsonl + `
|
|
@@ -71401,8 +71816,8 @@ function makeBrainsCommand() {
|
|
|
71401
71816
|
try {
|
|
71402
71817
|
let datasetPath = opts.dataset;
|
|
71403
71818
|
if (!datasetPath) {
|
|
71404
|
-
const trainingDir =
|
|
71405
|
-
if (!
|
|
71819
|
+
const trainingDir = join13(homedir8(), ".hasna", "mementos", "training");
|
|
71820
|
+
if (!existsSync12(trainingDir)) {
|
|
71406
71821
|
printError("No training data found. Run `mementos brains gather` first.");
|
|
71407
71822
|
process.exit(1);
|
|
71408
71823
|
}
|
|
@@ -71412,9 +71827,9 @@ function makeBrainsCommand() {
|
|
|
71412
71827
|
printError("No JSONL training files found. Run `mementos brains gather` first.");
|
|
71413
71828
|
process.exit(1);
|
|
71414
71829
|
}
|
|
71415
|
-
datasetPath =
|
|
71830
|
+
datasetPath = join13(trainingDir, latestFile);
|
|
71416
71831
|
}
|
|
71417
|
-
if (!datasetPath || !
|
|
71832
|
+
if (!datasetPath || !existsSync12(datasetPath)) {
|
|
71418
71833
|
printError(`Dataset file not found: ${datasetPath ?? "(unresolved)"}`);
|
|
71419
71834
|
process.exit(1);
|
|
71420
71835
|
}
|
|
@@ -71540,8 +71955,8 @@ function registerAllCommands(program2) {
|
|
|
71540
71955
|
// src/cli/index.tsx
|
|
71541
71956
|
function getPackageVersion2() {
|
|
71542
71957
|
try {
|
|
71543
|
-
const pkgPath =
|
|
71544
|
-
const pkg = JSON.parse(
|
|
71958
|
+
const pkgPath = join14(dirname8(fileURLToPath5(import.meta.url)), "..", "..", "package.json");
|
|
71959
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
71545
71960
|
return pkg.version || "0.0.0";
|
|
71546
71961
|
} catch {
|
|
71547
71962
|
return "0.0.0";
|