@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/mcp/index.js
CHANGED
|
@@ -320,6 +320,7 @@ function translateSql(sql) {
|
|
|
320
320
|
let translated = sql.replace(/\?/g, () => `$${++parameterIndex}`);
|
|
321
321
|
const ISO_FMT = `'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'`;
|
|
322
322
|
translated = translated.replace(/datetime\s*\(\s*'now'\s*\)/gi, `to_char(now() AT TIME ZONE 'UTC', ${ISO_FMT})`);
|
|
323
|
+
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})`);
|
|
323
324
|
translated = translated.replace(/datetime\s*\(\s*'now'\s*,\s*'(-?\d+)\s+(minutes?|hours?|days?|seconds?)'\s*\)/gi, (_match, amount, unit) => {
|
|
324
325
|
const parsed = parseInt(String(amount), 10);
|
|
325
326
|
const absolute = Math.abs(parsed);
|
|
@@ -804,16 +805,24 @@ function transferRows(target, table, rows, options) {
|
|
|
804
805
|
const conflictColumn = options.conflictColumn ?? "updated_at";
|
|
805
806
|
let written = 0;
|
|
806
807
|
let skipped = 0;
|
|
808
|
+
let maxSyncedAt = null;
|
|
807
809
|
const errors = [];
|
|
810
|
+
const bumpMaxSyncedAt = (row) => {
|
|
811
|
+
const value = row[conflictColumn];
|
|
812
|
+
if (typeof value === "string" && (maxSyncedAt === null || value > maxSyncedAt)) {
|
|
813
|
+
maxSyncedAt = value;
|
|
814
|
+
}
|
|
815
|
+
};
|
|
808
816
|
if (rows.length === 0) {
|
|
809
|
-
return { written, skipped, errors };
|
|
817
|
+
return { written, skipped, errors, maxSyncedAt };
|
|
810
818
|
}
|
|
811
819
|
const columns = Object.keys(rows[0] ?? {});
|
|
812
820
|
if (!columns.includes(primaryKey)) {
|
|
813
821
|
return {
|
|
814
822
|
written,
|
|
815
823
|
skipped,
|
|
816
|
-
errors: [`Table "${table}" has no "${primaryKey}" column; skipping`]
|
|
824
|
+
errors: [`Table "${table}" has no "${primaryKey}" column; skipping`],
|
|
825
|
+
maxSyncedAt
|
|
817
826
|
};
|
|
818
827
|
}
|
|
819
828
|
const hasConflictColumn = columns.includes(conflictColumn);
|
|
@@ -826,6 +835,7 @@ function transferRows(target, table, rows, options) {
|
|
|
826
835
|
const incomingTime = Date.parse(String(row[conflictColumn]));
|
|
827
836
|
if (Number.isFinite(existingTime) && Number.isFinite(incomingTime) && existingTime >= incomingTime) {
|
|
828
837
|
skipped++;
|
|
838
|
+
bumpMaxSyncedAt(row);
|
|
829
839
|
continue;
|
|
830
840
|
}
|
|
831
841
|
}
|
|
@@ -838,11 +848,12 @@ function transferRows(target, table, rows, options) {
|
|
|
838
848
|
target.run(`INSERT INTO "${table}" (${columnList}) VALUES (${placeholders})`, ...columns.map((column) => row[column]));
|
|
839
849
|
}
|
|
840
850
|
written++;
|
|
851
|
+
bumpMaxSyncedAt(row);
|
|
841
852
|
} catch (error) {
|
|
842
853
|
errors.push(`Row ${String(row[primaryKey] ?? "unknown")}: ${error instanceof Error ? error.message : String(error)}`);
|
|
843
854
|
}
|
|
844
855
|
}
|
|
845
|
-
return { written, skipped, errors };
|
|
856
|
+
return { written, skipped, errors, maxSyncedAt };
|
|
846
857
|
}
|
|
847
858
|
function incrementalSyncPush(local, remote, tables, options = {}) {
|
|
848
859
|
return runIncrementalSync("push", local, remote, local, tables, options);
|
|
@@ -880,22 +891,29 @@ function runIncrementalSync(direction, source, target, metaDb, tables, options)
|
|
|
880
891
|
rows = source.all(`SELECT * FROM "${table}"`);
|
|
881
892
|
stat.first_sync = true;
|
|
882
893
|
}
|
|
894
|
+
let maxSyncedAt = null;
|
|
883
895
|
for (let offset = 0;offset < rows.length; offset += batchSize) {
|
|
884
896
|
const batch = rows.slice(offset, offset + batchSize);
|
|
885
897
|
const result = transferRows(target, table, batch, options);
|
|
886
898
|
stat.synced_rows += result.written;
|
|
887
899
|
stat.skipped_rows += result.skipped;
|
|
888
900
|
stat.errors.push(...result.errors);
|
|
901
|
+
if (result.maxSyncedAt !== null && (maxSyncedAt === null || result.maxSyncedAt > maxSyncedAt)) {
|
|
902
|
+
maxSyncedAt = result.maxSyncedAt;
|
|
903
|
+
}
|
|
889
904
|
}
|
|
890
905
|
if (rows.length === 0) {
|
|
891
906
|
stat.skipped_rows = stat.total_rows;
|
|
892
907
|
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
908
|
+
const nextCursor = maxSyncedAt ?? meta?.last_synced_at ?? null;
|
|
909
|
+
if (rows.length > 0 && stat.errors.length === 0 && nextCursor !== null) {
|
|
910
|
+
upsertSyncMeta(metaDb, {
|
|
911
|
+
table_name: table,
|
|
912
|
+
last_synced_at: nextCursor,
|
|
913
|
+
last_synced_row_count: stat.synced_rows,
|
|
914
|
+
direction
|
|
915
|
+
});
|
|
916
|
+
}
|
|
899
917
|
} catch (error) {
|
|
900
918
|
stat.errors.push(`Table "${table}": ${error instanceof Error ? error.message : String(error)}`);
|
|
901
919
|
}
|
|
@@ -923,8 +941,13 @@ var init_storage = __esm(() => {
|
|
|
923
941
|
data;
|
|
924
942
|
closed = false;
|
|
925
943
|
lastError = null;
|
|
944
|
+
generation = 0;
|
|
926
945
|
static DATA_BYTES = 128 * 1024 * 1024;
|
|
927
|
-
static
|
|
946
|
+
static queryTimeoutMs() {
|
|
947
|
+
const raw = process.env["MEMENTOS_PGSYNC_QUERY_TIMEOUT_MS"]?.trim();
|
|
948
|
+
const parsed = raw ? Number(raw) : Number.NaN;
|
|
949
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 60000;
|
|
950
|
+
}
|
|
928
951
|
static resolveWorkerPath() {
|
|
929
952
|
const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
930
953
|
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
@@ -939,12 +962,12 @@ var init_storage = __esm(() => {
|
|
|
939
962
|
}
|
|
940
963
|
return candidates[0];
|
|
941
964
|
}
|
|
942
|
-
constructor(connectionString) {
|
|
943
|
-
const control = new SharedArrayBuffer(
|
|
965
|
+
constructor(connectionString, workerPath) {
|
|
966
|
+
const control = new SharedArrayBuffer(12);
|
|
944
967
|
const dataSab = new SharedArrayBuffer(PgSyncPool.DATA_BYTES);
|
|
945
968
|
this.status = new Int32Array(control);
|
|
946
969
|
this.data = new Uint8Array(dataSab);
|
|
947
|
-
this.worker = new Worker(PgSyncPool.resolveWorkerPath(), {
|
|
970
|
+
this.worker = new Worker(workerPath ?? PgSyncPool.resolveWorkerPath(), {
|
|
948
971
|
workerData: {
|
|
949
972
|
dsn: stripSslParams(connectionString),
|
|
950
973
|
ssl: sslConfigFor(connectionString),
|
|
@@ -962,21 +985,35 @@ var init_storage = __esm(() => {
|
|
|
962
985
|
throw new Error("PgSyncPool is closed");
|
|
963
986
|
if (this.lastError)
|
|
964
987
|
throw this.lastError;
|
|
988
|
+
const timeoutMs = PgSyncPool.queryTimeoutMs();
|
|
989
|
+
const gen = ++this.generation;
|
|
965
990
|
Atomics.store(this.status, 0, 0);
|
|
966
|
-
this.
|
|
967
|
-
|
|
968
|
-
const
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
991
|
+
Atomics.store(this.status, 2, 0);
|
|
992
|
+
this.worker.postMessage({ sql, params, gen });
|
|
993
|
+
const deadline = Date.now() + timeoutMs;
|
|
994
|
+
for (;; ) {
|
|
995
|
+
const remaining = deadline - Date.now();
|
|
996
|
+
const responding = Atomics.load(this.status, 0);
|
|
997
|
+
if (responding === gen) {
|
|
998
|
+
const code = Atomics.load(this.status, 2);
|
|
999
|
+
const len = Atomics.load(this.status, 1);
|
|
1000
|
+
const payload = JSON.parse(new TextDecoder().decode(this.data.subarray(0, len)));
|
|
1001
|
+
if (code === 2) {
|
|
1002
|
+
throw new Error(payload.message ?? "PostgreSQL error");
|
|
1003
|
+
}
|
|
1004
|
+
return payload;
|
|
1005
|
+
}
|
|
1006
|
+
if (responding !== 0) {
|
|
1007
|
+
Atomics.compareExchange(this.status, 0, responding, 0);
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
if (remaining <= 0) {
|
|
1011
|
+
if (this.lastError)
|
|
1012
|
+
throw this.lastError;
|
|
1013
|
+
throw new Error(`PostgreSQL query timed out after ${timeoutMs}ms`);
|
|
1014
|
+
}
|
|
1015
|
+
Atomics.wait(this.status, 0, 0, remaining);
|
|
978
1016
|
}
|
|
979
|
-
return payload;
|
|
980
1017
|
}
|
|
981
1018
|
end() {
|
|
982
1019
|
if (this.closed)
|
|
@@ -3252,6 +3289,7 @@ __export(exports_memories, {
|
|
|
3252
3289
|
updateMemory: () => updateMemory,
|
|
3253
3290
|
touchMemory: () => touchMemory,
|
|
3254
3291
|
semanticSearch: () => semanticSearch,
|
|
3292
|
+
reservedAgentIdViolation: () => reservedAgentIdViolation,
|
|
3255
3293
|
parseMemoryRow: () => parseMemoryRow,
|
|
3256
3294
|
listMemoryHistoryPage: () => listMemoryHistoryPage,
|
|
3257
3295
|
listMemoryHistory: () => listMemoryHistory,
|
|
@@ -3323,7 +3361,20 @@ function parseMemoryRow(row) {
|
|
|
3323
3361
|
accessed_at: row["accessed_at"] || null
|
|
3324
3362
|
};
|
|
3325
3363
|
}
|
|
3364
|
+
function reservedAgentIdViolation(agentId) {
|
|
3365
|
+
if (!agentId)
|
|
3366
|
+
return null;
|
|
3367
|
+
const normalized = agentId.trim().toLowerCase();
|
|
3368
|
+
if (RESERVED_AGENT_IDS.has(normalized)) {
|
|
3369
|
+
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.`;
|
|
3370
|
+
}
|
|
3371
|
+
return null;
|
|
3372
|
+
}
|
|
3326
3373
|
function createMemory(input, dedupeMode = "merge", db) {
|
|
3374
|
+
const reservedViolation = reservedAgentIdViolation(input.agent_id);
|
|
3375
|
+
if (reservedViolation) {
|
|
3376
|
+
throw new Error(reservedViolation);
|
|
3377
|
+
}
|
|
3327
3378
|
if (!db && isApiMode()) {
|
|
3328
3379
|
const { status, data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
|
|
3329
3380
|
if (!data || !data.id) {
|
|
@@ -3373,7 +3424,8 @@ function createMemory(input, dedupeMode = "merge", db) {
|
|
|
3373
3424
|
importance = ?, metadata = ?, expires_at = ?,
|
|
3374
3425
|
when_to_use = ?,
|
|
3375
3426
|
pinned = COALESCE(pinned, 0),
|
|
3376
|
-
version = version + 1, updated_at =
|
|
3427
|
+
version = version + 1, updated_at = ?,
|
|
3428
|
+
updated_by_agent = ?
|
|
3377
3429
|
WHERE id = ?`, [
|
|
3378
3430
|
safeValue,
|
|
3379
3431
|
input.category || "knowledge",
|
|
@@ -3384,6 +3436,7 @@ function createMemory(input, dedupeMode = "merge", db) {
|
|
|
3384
3436
|
expiresAt,
|
|
3385
3437
|
input.when_to_use || null,
|
|
3386
3438
|
timestamp,
|
|
3439
|
+
input.agent_id || null,
|
|
3387
3440
|
existing.id
|
|
3388
3441
|
]);
|
|
3389
3442
|
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [existing.id]);
|
|
@@ -3488,6 +3541,12 @@ function bulkUpsertMemories(memories, db) {
|
|
|
3488
3541
|
errors.push(`Rejected "${key}": ${formatEnumViolation(violation)}`);
|
|
3489
3542
|
continue;
|
|
3490
3543
|
}
|
|
3544
|
+
const agentViolation = reservedAgentIdViolation(typeof mem["agent_id"] === "string" ? mem["agent_id"] : undefined);
|
|
3545
|
+
if (agentViolation) {
|
|
3546
|
+
rejected++;
|
|
3547
|
+
errors.push(`Rejected "${key}": ${agentViolation}`);
|
|
3548
|
+
continue;
|
|
3549
|
+
}
|
|
3491
3550
|
const timestamp = now();
|
|
3492
3551
|
let tags = [];
|
|
3493
3552
|
const rawTags = mem["tags"];
|
|
@@ -3766,6 +3825,11 @@ function listMemoriesPage(filter, db) {
|
|
|
3766
3825
|
agent_id: f.agent_id,
|
|
3767
3826
|
project_id: f.project_id,
|
|
3768
3827
|
session_id: f.session_id,
|
|
3828
|
+
machine_id: f.machine_id,
|
|
3829
|
+
visible_to_machine_id: f.visible_to_machine_id,
|
|
3830
|
+
search: f.search,
|
|
3831
|
+
source: f.source,
|
|
3832
|
+
flag: f.flag,
|
|
3769
3833
|
namespace: f.namespace,
|
|
3770
3834
|
as_of: f.as_of,
|
|
3771
3835
|
limit: f.limit,
|
|
@@ -4045,20 +4109,29 @@ function updateMemory(id, input, db) {
|
|
|
4045
4109
|
sets.push("when_to_use = ?");
|
|
4046
4110
|
params.push(input.when_to_use ?? null);
|
|
4047
4111
|
}
|
|
4112
|
+
if (input.updated_by_agent !== undefined) {
|
|
4113
|
+
sets.push("updated_by_agent = ?");
|
|
4114
|
+
params.push(input.updated_by_agent ?? null);
|
|
4115
|
+
}
|
|
4048
4116
|
if (input.tags !== undefined) {
|
|
4049
4117
|
sets.push("tags = ?");
|
|
4050
4118
|
params.push(JSON.stringify(input.tags));
|
|
4051
|
-
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [memoryId]);
|
|
4052
|
-
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
4053
|
-
for (const tag of input.tags) {
|
|
4054
|
-
insertTag.run(memoryId, tag);
|
|
4055
|
-
}
|
|
4056
4119
|
}
|
|
4057
4120
|
params.push(memoryId);
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4121
|
+
d.transaction(() => {
|
|
4122
|
+
const res = d.run(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
4123
|
+
if (res.changes === 0) {
|
|
4124
|
+
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.`);
|
|
4125
|
+
}
|
|
4126
|
+
if (input.tags !== undefined) {
|
|
4127
|
+
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [memoryId]);
|
|
4128
|
+
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
4129
|
+
for (const tag of input.tags) {
|
|
4130
|
+
insertTag.run(memoryId, tag);
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
return res;
|
|
4134
|
+
});
|
|
4062
4135
|
const updated = getMemory(memoryId, d);
|
|
4063
4136
|
if (input.value !== undefined) {
|
|
4064
4137
|
try {
|
|
@@ -4229,7 +4302,7 @@ async function semanticSearch(queryText, options = {}, db) {
|
|
|
4229
4302
|
scored.sort((a, b) => b.score - a.score);
|
|
4230
4303
|
return scored.slice(0, limit);
|
|
4231
4304
|
}
|
|
4232
|
-
var RECALL_PROMOTE_THRESHOLD = 3;
|
|
4305
|
+
var RESERVED_AGENT_IDS, RECALL_PROMOTE_THRESHOLD = 3;
|
|
4233
4306
|
var init_memories = __esm(() => {
|
|
4234
4307
|
init_types();
|
|
4235
4308
|
init_database();
|
|
@@ -4239,6 +4312,12 @@ var init_memories = __esm(() => {
|
|
|
4239
4312
|
init_poisoning();
|
|
4240
4313
|
init_entity_memories();
|
|
4241
4314
|
init_api_mode();
|
|
4315
|
+
RESERVED_AGENT_IDS = new Set([
|
|
4316
|
+
"agent-a",
|
|
4317
|
+
"agent-x",
|
|
4318
|
+
"agent-z",
|
|
4319
|
+
"nonexistent-agent"
|
|
4320
|
+
]);
|
|
4242
4321
|
});
|
|
4243
4322
|
|
|
4244
4323
|
// src/db/machines.ts
|
|
@@ -4333,6 +4412,8 @@ var init_machines = __esm(() => {
|
|
|
4333
4412
|
});
|
|
4334
4413
|
|
|
4335
4414
|
// src/db/webhook_hooks.ts
|
|
4415
|
+
import { isIP } from "net";
|
|
4416
|
+
import { lookup as dnsLookup } from "dns/promises";
|
|
4336
4417
|
function parseRow(row) {
|
|
4337
4418
|
return {
|
|
4338
4419
|
id: row["id"],
|
|
@@ -4349,7 +4430,153 @@ function parseRow(row) {
|
|
|
4349
4430
|
failureCount: row["failure_count"]
|
|
4350
4431
|
};
|
|
4351
4432
|
}
|
|
4352
|
-
function
|
|
4433
|
+
function isBlockedIpv4(parts) {
|
|
4434
|
+
const a = parts[0];
|
|
4435
|
+
const b = parts[1];
|
|
4436
|
+
if (a === 0)
|
|
4437
|
+
return true;
|
|
4438
|
+
if (a === 127)
|
|
4439
|
+
return true;
|
|
4440
|
+
if (a === 169 && b === 254)
|
|
4441
|
+
return true;
|
|
4442
|
+
if (a === 10)
|
|
4443
|
+
return true;
|
|
4444
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
4445
|
+
return true;
|
|
4446
|
+
if (a === 192 && b === 168)
|
|
4447
|
+
return true;
|
|
4448
|
+
return false;
|
|
4449
|
+
}
|
|
4450
|
+
function isBlockedIpv6(bytes) {
|
|
4451
|
+
const mapped = bytes.slice(0, 10).every((b) => b === 0) && bytes[10] === 255 && bytes[11] === 255;
|
|
4452
|
+
if (mapped)
|
|
4453
|
+
return isBlockedIpv4(bytes.slice(12, 16));
|
|
4454
|
+
if (bytes.every((b) => b === 0))
|
|
4455
|
+
return true;
|
|
4456
|
+
if (bytes.slice(0, 15).every((b) => b === 0) && bytes[15] === 1)
|
|
4457
|
+
return true;
|
|
4458
|
+
if ((bytes[0] & 254) === 252)
|
|
4459
|
+
return true;
|
|
4460
|
+
if (bytes[0] === 254 && (bytes[1] & 192) === 128)
|
|
4461
|
+
return true;
|
|
4462
|
+
return false;
|
|
4463
|
+
}
|
|
4464
|
+
function quadToGroups(quad) {
|
|
4465
|
+
const nums = quad.split(".").map((p) => Number(p));
|
|
4466
|
+
const [a, b, c, d] = nums;
|
|
4467
|
+
const valid = [a, b, c, d].every((n) => n !== undefined && Number.isInteger(n) && n >= 0 && n <= 255);
|
|
4468
|
+
if (!valid)
|
|
4469
|
+
return null;
|
|
4470
|
+
return [a << 8 | b, c << 8 | d];
|
|
4471
|
+
}
|
|
4472
|
+
function parseIpv6Bytes(host) {
|
|
4473
|
+
const groups = host.split("::");
|
|
4474
|
+
if (groups.length > 2)
|
|
4475
|
+
return null;
|
|
4476
|
+
const headRaw = groups[0] ?? "";
|
|
4477
|
+
const tailRaw = groups[1] ?? "";
|
|
4478
|
+
const head = headRaw === "" ? [] : headRaw.split(":");
|
|
4479
|
+
const tail = tailRaw === "" ? [] : tailRaw.split(":");
|
|
4480
|
+
const headNums = [];
|
|
4481
|
+
for (const g of head) {
|
|
4482
|
+
if (!/^[0-9a-f]{1,4}$/i.test(g))
|
|
4483
|
+
return null;
|
|
4484
|
+
headNums.push(parseInt(g, 16));
|
|
4485
|
+
}
|
|
4486
|
+
const tailNums = [];
|
|
4487
|
+
for (const g of tail) {
|
|
4488
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(g)) {
|
|
4489
|
+
const quads = quadToGroups(g);
|
|
4490
|
+
if (!quads)
|
|
4491
|
+
return null;
|
|
4492
|
+
tailNums.push(...quads);
|
|
4493
|
+
} else if (/^[0-9a-f]{1,4}$/i.test(g)) {
|
|
4494
|
+
tailNums.push(parseInt(g, 16));
|
|
4495
|
+
} else {
|
|
4496
|
+
return null;
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
const hasCompression = groups.length === 2;
|
|
4500
|
+
if (!hasCompression && headNums.length !== 8)
|
|
4501
|
+
return null;
|
|
4502
|
+
if (hasCompression && headNums.length + tailNums.length >= 8)
|
|
4503
|
+
return null;
|
|
4504
|
+
const zeros = 8 - headNums.length - tailNums.length;
|
|
4505
|
+
const all = [...headNums, ...new Array(zeros).fill(0), ...tailNums];
|
|
4506
|
+
const bytes = [];
|
|
4507
|
+
for (const n of all) {
|
|
4508
|
+
bytes.push(n >> 8 & 255, n & 255);
|
|
4509
|
+
}
|
|
4510
|
+
return bytes;
|
|
4511
|
+
}
|
|
4512
|
+
function defaultResolveHost(hostname2) {
|
|
4513
|
+
return dnsLookup(hostname2, { all: true, verbatim: true });
|
|
4514
|
+
}
|
|
4515
|
+
function assertResolvedAddressPublic(address, url) {
|
|
4516
|
+
const version = isIP(address);
|
|
4517
|
+
if (version === 4) {
|
|
4518
|
+
if (isBlockedIpv4(address.split(".").map((p) => Number(p)))) {
|
|
4519
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
4520
|
+
}
|
|
4521
|
+
} else if (version === 6) {
|
|
4522
|
+
const bytes = parseIpv6Bytes(address);
|
|
4523
|
+
if (!bytes || isBlockedIpv6(bytes)) {
|
|
4524
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
4525
|
+
}
|
|
4526
|
+
} else {
|
|
4527
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
async function validateWebhookHandlerUrl(url, opts) {
|
|
4531
|
+
const resolveHost = opts?.lookup ?? defaultResolveHost;
|
|
4532
|
+
let parsed;
|
|
4533
|
+
try {
|
|
4534
|
+
parsed = new URL(url);
|
|
4535
|
+
} catch {
|
|
4536
|
+
throw new Error(`Invalid webhook handler URL "${url}" \u2014 must be a valid http(s) URL`);
|
|
4537
|
+
}
|
|
4538
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
4539
|
+
throw new Error(`Invalid webhook handler URL "${url}" \u2014 only http and https are allowed`);
|
|
4540
|
+
}
|
|
4541
|
+
if (parsed.username || parsed.password) {
|
|
4542
|
+
throw new Error(`Invalid webhook handler URL "${url}" \u2014 embedded credentials are not allowed`);
|
|
4543
|
+
}
|
|
4544
|
+
const host = parsed.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
4545
|
+
if (host === "localhost" || host.endsWith(".localhost")) {
|
|
4546
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
4547
|
+
}
|
|
4548
|
+
const version = isIP(host);
|
|
4549
|
+
if (version === 4 || version === 6) {
|
|
4550
|
+
if (version === 4) {
|
|
4551
|
+
if (isBlockedIpv4(host.split(".").map((p) => Number(p)))) {
|
|
4552
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
4553
|
+
}
|
|
4554
|
+
} else {
|
|
4555
|
+
const bytes = parseIpv6Bytes(host);
|
|
4556
|
+
if (!bytes || isBlockedIpv6(bytes)) {
|
|
4557
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
return;
|
|
4561
|
+
}
|
|
4562
|
+
if (/^[0-9]+(\.[0-9]+)*$/.test(host) || /^0x[0-9a-f]+$/i.test(host) || host.includes("%")) {
|
|
4563
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
4564
|
+
}
|
|
4565
|
+
let addrs;
|
|
4566
|
+
try {
|
|
4567
|
+
addrs = await resolveHost(host);
|
|
4568
|
+
} catch {
|
|
4569
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
4570
|
+
}
|
|
4571
|
+
if (addrs.length === 0) {
|
|
4572
|
+
throw new Error(`${BLOCKED_TARGET_MESSAGE}: "${url}"`);
|
|
4573
|
+
}
|
|
4574
|
+
for (const { address } of addrs) {
|
|
4575
|
+
assertResolvedAddressPublic(address, url);
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
async function createWebhookHook(input, db, opts) {
|
|
4579
|
+
await validateWebhookHandlerUrl(input.handlerUrl, opts);
|
|
4353
4580
|
if (!db && isApiMode()) {
|
|
4354
4581
|
const { data } = apiJson("POST", "/webhooks", {
|
|
4355
4582
|
type: input.type,
|
|
@@ -4467,6 +4694,7 @@ function recordWebhookInvocation(id, success, db) {
|
|
|
4467
4694
|
d.run("UPDATE webhook_hooks SET invocation_count = invocation_count + 1, failure_count = failure_count + 1 WHERE id = ?", [id]);
|
|
4468
4695
|
}
|
|
4469
4696
|
}
|
|
4697
|
+
var BLOCKED_TARGET_MESSAGE = "Invalid webhook handler URL \u2014 loopback, link-local, and private network targets are not allowed";
|
|
4470
4698
|
var init_webhook_hooks = __esm(() => {
|
|
4471
4699
|
init_database();
|
|
4472
4700
|
init_api_mode();
|
|
@@ -5116,7 +5344,7 @@ function buildFilterConditions(filter) {
|
|
|
5116
5344
|
const conditions = [];
|
|
5117
5345
|
const params = [];
|
|
5118
5346
|
conditions.push("m.status = 'active'");
|
|
5119
|
-
conditions.push("(m.expires_at IS NULL OR m.expires_at >=
|
|
5347
|
+
conditions.push("(m.expires_at IS NULL OR m.expires_at >= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))");
|
|
5120
5348
|
if (!filter)
|
|
5121
5349
|
return { conditions, params };
|
|
5122
5350
|
if (filter.scope) {
|
|
@@ -7125,7 +7353,7 @@ async function detectContradiction(newKey, newValue, options = {}, db) {
|
|
|
7125
7353
|
conditions.push("project_id = ?");
|
|
7126
7354
|
params.push(project_id);
|
|
7127
7355
|
}
|
|
7128
|
-
conditions.push("(valid_until IS NULL OR valid_until >
|
|
7356
|
+
conditions.push("(valid_until IS NULL OR valid_until > strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))");
|
|
7129
7357
|
const sql = `SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY importance DESC LIMIT 10`;
|
|
7130
7358
|
const rows = d.query(sql).all(...params);
|
|
7131
7359
|
if (rows.length === 0) {
|
|
@@ -7172,6 +7400,7 @@ var init_contradiction = __esm(() => {
|
|
|
7172
7400
|
var exports_built_in_hooks = {};
|
|
7173
7401
|
__export(exports_built_in_hooks, {
|
|
7174
7402
|
reloadWebhooks: () => reloadWebhooks,
|
|
7403
|
+
makeWebhookHandler: () => makeWebhookHandler,
|
|
7175
7404
|
loadWebhooksFromDb: () => loadWebhooksFromDb
|
|
7176
7405
|
});
|
|
7177
7406
|
async function getAutoMemory() {
|
|
@@ -7181,13 +7410,19 @@ async function getAutoMemory() {
|
|
|
7181
7410
|
}
|
|
7182
7411
|
return _processConversationTurn;
|
|
7183
7412
|
}
|
|
7184
|
-
function loadWebhooksFromDb() {
|
|
7413
|
+
async function loadWebhooksFromDb() {
|
|
7185
7414
|
if (_webhooksLoaded)
|
|
7186
7415
|
return;
|
|
7187
7416
|
_webhooksLoaded = true;
|
|
7188
7417
|
try {
|
|
7189
7418
|
const webhooks = listWebhookHooks({ enabled: true });
|
|
7190
7419
|
for (const wh of webhooks) {
|
|
7420
|
+
try {
|
|
7421
|
+
await validateWebhookHandlerUrl(wh.handlerUrl);
|
|
7422
|
+
} catch (err) {
|
|
7423
|
+
console.error(`[hooks] Skipping webhook ${wh.id} (${wh.type}): ${err instanceof Error ? err.message : String(err)}`);
|
|
7424
|
+
continue;
|
|
7425
|
+
}
|
|
7191
7426
|
hookRegistry.register({
|
|
7192
7427
|
type: wh.type,
|
|
7193
7428
|
blocking: wh.blocking,
|
|
@@ -7205,9 +7440,10 @@ function loadWebhooksFromDb() {
|
|
|
7205
7440
|
console.error("[hooks] Failed to load webhooks from DB:", err);
|
|
7206
7441
|
}
|
|
7207
7442
|
}
|
|
7208
|
-
function makeWebhookHandler(webhookId, url) {
|
|
7443
|
+
function makeWebhookHandler(webhookId, url, opts) {
|
|
7209
7444
|
return async (context) => {
|
|
7210
7445
|
try {
|
|
7446
|
+
await validateWebhookHandlerUrl(url, opts);
|
|
7211
7447
|
const res = await fetch(url, {
|
|
7212
7448
|
method: "POST",
|
|
7213
7449
|
headers: { "Content-Type": "application/json" },
|
|
@@ -7220,9 +7456,9 @@ function makeWebhookHandler(webhookId, url) {
|
|
|
7220
7456
|
}
|
|
7221
7457
|
};
|
|
7222
7458
|
}
|
|
7223
|
-
function reloadWebhooks() {
|
|
7459
|
+
async function reloadWebhooks() {
|
|
7224
7460
|
_webhooksLoaded = false;
|
|
7225
|
-
loadWebhooksFromDb();
|
|
7461
|
+
await loadWebhooksFromDb();
|
|
7226
7462
|
}
|
|
7227
7463
|
var _processConversationTurn = null, _webhooksLoaded = false;
|
|
7228
7464
|
var init_built_in_hooks = __esm(() => {
|
|
@@ -23314,7 +23550,7 @@ class JSONSchemaGenerator {
|
|
|
23314
23550
|
if (val === undefined) {
|
|
23315
23551
|
if (this.unrepresentable === "throw") {
|
|
23316
23552
|
throw new Error("Literal `undefined` cannot be represented in JSON Schema");
|
|
23317
|
-
}
|
|
23553
|
+
} else {}
|
|
23318
23554
|
} else if (typeof val === "bigint") {
|
|
23319
23555
|
if (this.unrepresentable === "throw") {
|
|
23320
23556
|
throw new Error("BigInt literals cannot be represented in JSON Schema");
|
|
@@ -44964,7 +45200,7 @@ var require_tracestate_impl = __commonJS((exports) => {
|
|
|
44964
45200
|
const value = listMember.slice(i + 1, part.length);
|
|
44965
45201
|
if ((0, tracestate_validators_1.validateKey)(key) && (0, tracestate_validators_1.validateValue)(value)) {
|
|
44966
45202
|
agg.set(key, value);
|
|
44967
|
-
}
|
|
45203
|
+
} else {}
|
|
44968
45204
|
}
|
|
44969
45205
|
return agg;
|
|
44970
45206
|
}, new Map);
|
|
@@ -60192,7 +60428,10 @@ function getMemoryStats(db) {
|
|
|
60192
60428
|
const byCategory = d.query("SELECT category, COUNT(*) as c FROM memories WHERE status = 'active' GROUP BY category").all();
|
|
60193
60429
|
const byStatus = d.query("SELECT status, COUNT(*) as c FROM memories WHERE status = 'active' GROUP BY status").all();
|
|
60194
60430
|
const pinnedCount = d.query("SELECT COUNT(*) as c FROM memories WHERE pinned = 1 AND status = 'active'").get().c;
|
|
60195
|
-
const expiredCount = d.query("SELECT COUNT(*) as c FROM memories WHERE status = 'expired'
|
|
60431
|
+
const expiredCount = d.query("SELECT COUNT(*) as c FROM memories WHERE status = 'expired'").get().c;
|
|
60432
|
+
const expiresAtCount = d.query("SELECT COUNT(*) as c FROM memories WHERE expires_at IS NOT NULL").get().c;
|
|
60433
|
+
const nowIso = new Date().toISOString();
|
|
60434
|
+
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;
|
|
60196
60435
|
const stats = {
|
|
60197
60436
|
total,
|
|
60198
60437
|
by_scope: { global: 0, shared: 0, private: 0, working: 0 },
|
|
@@ -60200,7 +60439,9 @@ function getMemoryStats(db) {
|
|
|
60200
60439
|
by_status: { active: 0, archived: 0, expired: 0 },
|
|
60201
60440
|
by_agent: {},
|
|
60202
60441
|
pinned_count: pinnedCount,
|
|
60203
|
-
expired_count: expiredCount
|
|
60442
|
+
expired_count: expiredCount,
|
|
60443
|
+
expires_at_count: expiresAtCount,
|
|
60444
|
+
expired_due_count: expiredDueCount
|
|
60204
60445
|
};
|
|
60205
60446
|
for (const row of byScope)
|
|
60206
60447
|
if (row.scope in stats.by_scope)
|
|
@@ -60234,7 +60475,9 @@ function normalizeStats(data) {
|
|
|
60234
60475
|
by_status: { active: 0, archived: 0, expired: 0, ...data?.by_status ?? {} },
|
|
60235
60476
|
by_agent: data?.by_agent ?? {},
|
|
60236
60477
|
pinned_count: data?.pinned_count ?? 0,
|
|
60237
|
-
expired_count: data?.expired_count ?? 0
|
|
60478
|
+
expired_count: data?.expired_count ?? 0,
|
|
60479
|
+
expires_at_count: data?.expires_at_count ?? 0,
|
|
60480
|
+
expired_due_count: data?.expired_due_count ?? 0
|
|
60238
60481
|
};
|
|
60239
60482
|
}
|
|
60240
60483
|
function getMemoryActivity(filter = {}, db) {
|
|
@@ -60332,7 +60575,7 @@ function getStaleMemoriesPage(filter = {}, db) {
|
|
|
60332
60575
|
const limit = filter.limit ?? 20;
|
|
60333
60576
|
const offset = filter.offset ?? 0;
|
|
60334
60577
|
if (!db && isApiMode()) {
|
|
60335
|
-
const q = toQuery({ days, project_id: filter.project_id, agent_id: filter.agent_id, limit, offset });
|
|
60578
|
+
const q = toQuery({ days, project_id: filter.project_id, agent_id: filter.agent_id, pinned: filter.pinned, limit, offset });
|
|
60336
60579
|
const { data } = apiJson("GET", `/memories/stale${q}`);
|
|
60337
60580
|
const rows2 = data?.memories ?? [];
|
|
60338
60581
|
return {
|
|
@@ -60344,8 +60587,14 @@ function getStaleMemoriesPage(filter = {}, db) {
|
|
|
60344
60587
|
}
|
|
60345
60588
|
const d = db || getDatabase();
|
|
60346
60589
|
const cutoffDate = new Date(Date.now() - days * 86400000).toISOString();
|
|
60347
|
-
const conds = ["status = 'active'", "(accessed_at IS NULL OR accessed_at < ?)"
|
|
60590
|
+
const conds = ["status = 'active'", "(accessed_at IS NULL OR accessed_at < ?)"];
|
|
60348
60591
|
const params = [cutoffDate];
|
|
60592
|
+
if (filter.pinned !== undefined) {
|
|
60593
|
+
conds.push("pinned = ?");
|
|
60594
|
+
params.push(filter.pinned ? 1 : 0);
|
|
60595
|
+
} else {
|
|
60596
|
+
conds.push("pinned = 0");
|
|
60597
|
+
}
|
|
60349
60598
|
if (filter.project_id) {
|
|
60350
60599
|
conds.push("project_id = ?");
|
|
60351
60600
|
params.push(filter.project_id);
|
|
@@ -60543,7 +60792,7 @@ function hasFts5Table2(db) {
|
|
|
60543
60792
|
function buildScopeFilter(opts) {
|
|
60544
60793
|
const conditions = [
|
|
60545
60794
|
"m.status = 'active'",
|
|
60546
|
-
"(m.expires_at IS NULL OR m.expires_at >=
|
|
60795
|
+
"(m.expires_at IS NULL OR m.expires_at >= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))",
|
|
60547
60796
|
"m.category IN ('preference', 'fact', 'knowledge')"
|
|
60548
60797
|
];
|
|
60549
60798
|
const params = [];
|
|
@@ -60811,7 +61060,7 @@ async function runTemporalAgent(db, query, opts) {
|
|
|
60811
61060
|
const maxResults = opts.max_results ?? 20;
|
|
60812
61061
|
const limit = maxResults * 3;
|
|
60813
61062
|
const conditions = [
|
|
60814
|
-
"(m.expires_at IS NULL OR m.expires_at >=
|
|
61063
|
+
"(m.expires_at IS NULL OR m.expires_at >= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))"
|
|
60815
61064
|
];
|
|
60816
61065
|
const params = [];
|
|
60817
61066
|
if (opts.project_id) {
|
|
@@ -61529,6 +61778,7 @@ function registerMemoryLifecycleTools(server) {
|
|
|
61529
61778
|
});
|
|
61530
61779
|
server.tool("memory_stale", "Find memories not accessed recently. Useful for cleanup or review.", {
|
|
61531
61780
|
days: exports_external.coerce.number().optional(),
|
|
61781
|
+
pinned: exports_external.boolean().optional().describe("Review stale pinned memories (default excludes pinned)"),
|
|
61532
61782
|
project_id: exports_external.string().optional(),
|
|
61533
61783
|
agent_id: exports_external.string().optional(),
|
|
61534
61784
|
limit: exports_external.coerce.number().optional()
|
|
@@ -61537,6 +61787,7 @@ function registerMemoryLifecycleTools(server) {
|
|
|
61537
61787
|
const days = args.days || 30;
|
|
61538
61788
|
const rows = getStaleMemories({
|
|
61539
61789
|
days,
|
|
61790
|
+
pinned: args.pinned,
|
|
61540
61791
|
project_id: args.project_id,
|
|
61541
61792
|
agent_id: args.agent_id,
|
|
61542
61793
|
limit: args.limit || 20
|
|
@@ -63213,6 +63464,7 @@ function syncMemoriesTable(source, target, local, direction, currentMachineId) {
|
|
|
63213
63464
|
const countResult = source.get(`SELECT COUNT(*) as cnt FROM "${MEMORY_TABLE}"`);
|
|
63214
63465
|
stat.total_rows = countResult?.cnt ?? 0;
|
|
63215
63466
|
const rows = since ? source.all(`SELECT * FROM "${MEMORY_TABLE}" WHERE updated_at > ?`, since) : source.all(`SELECT * FROM "${MEMORY_TABLE}"`);
|
|
63467
|
+
const erroredRowIds = new Set;
|
|
63216
63468
|
for (const row of rows) {
|
|
63217
63469
|
try {
|
|
63218
63470
|
const sourceMachine = sourceMachineRef(row, currentMachineId);
|
|
@@ -63271,18 +63523,34 @@ function syncMemoriesTable(source, target, local, direction, currentMachineId) {
|
|
|
63271
63523
|
clearMemoryEmbedding(winnerDb, String(winner["id"]));
|
|
63272
63524
|
stat.synced_rows++;
|
|
63273
63525
|
} catch (error) {
|
|
63526
|
+
erroredRowIds.add(String(row["id"] ?? "unknown"));
|
|
63274
63527
|
stat.errors.push(`Memory ${String(row["id"] ?? "unknown")}: ${error instanceof Error ? error.message : String(error)}`);
|
|
63275
63528
|
}
|
|
63276
63529
|
}
|
|
63277
63530
|
if (rows.length === 0) {
|
|
63278
63531
|
stat.skipped_rows = stat.total_rows;
|
|
63279
63532
|
}
|
|
63280
|
-
|
|
63281
|
-
|
|
63282
|
-
|
|
63283
|
-
|
|
63284
|
-
|
|
63285
|
-
|
|
63533
|
+
if (rows.length > 0 && stat.errors.length === 0) {
|
|
63534
|
+
let maxSyncedAt = null;
|
|
63535
|
+
for (const row of rows) {
|
|
63536
|
+
if (erroredRowIds.has(String(row["id"] ?? "unknown"))) {
|
|
63537
|
+
continue;
|
|
63538
|
+
}
|
|
63539
|
+
const value = row["updated_at"];
|
|
63540
|
+
if (typeof value === "string" && (maxSyncedAt === null || value > maxSyncedAt)) {
|
|
63541
|
+
maxSyncedAt = value;
|
|
63542
|
+
}
|
|
63543
|
+
}
|
|
63544
|
+
const nextCursor = maxSyncedAt ?? syncMeta?.last_synced_at ?? null;
|
|
63545
|
+
if (nextCursor !== null) {
|
|
63546
|
+
upsertMemorySyncMeta(local, {
|
|
63547
|
+
table_name: MEMORY_TABLE,
|
|
63548
|
+
direction,
|
|
63549
|
+
last_synced_at: nextCursor,
|
|
63550
|
+
last_synced_row_count: stat.synced_rows
|
|
63551
|
+
});
|
|
63552
|
+
}
|
|
63553
|
+
}
|
|
63286
63554
|
} catch (error) {
|
|
63287
63555
|
stat.errors.push(error instanceof Error ? error.message : String(error));
|
|
63288
63556
|
}
|
|
@@ -63683,7 +63951,7 @@ function acquireLock(agentId, resourceType, resourceId, lockType = "exclusive",
|
|
|
63683
63951
|
}
|
|
63684
63952
|
const d = db || getDatabase();
|
|
63685
63953
|
cleanExpiredLocks(d);
|
|
63686
|
-
const ownLock = d.query("SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND agent_id = ? AND lock_type = ? AND expires_at >
|
|
63954
|
+
const ownLock = d.query("SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND agent_id = ? AND lock_type = ? AND expires_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')").get(resourceType, resourceId, agentId, lockType);
|
|
63687
63955
|
if (ownLock) {
|
|
63688
63956
|
const newExpiry = new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
|
63689
63957
|
d.run("UPDATE resource_locks SET expires_at = ? WHERE id = ?", [
|
|
@@ -63693,7 +63961,7 @@ function acquireLock(agentId, resourceType, resourceId, lockType = "exclusive",
|
|
|
63693
63961
|
return parseLockRow({ ...ownLock, expires_at: newExpiry });
|
|
63694
63962
|
}
|
|
63695
63963
|
if (lockType === "exclusive") {
|
|
63696
|
-
const existing = d.query("SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = 'exclusive' AND agent_id != ? AND expires_at >
|
|
63964
|
+
const existing = d.query("SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = 'exclusive' AND agent_id != ? AND expires_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')").get(resourceType, resourceId, agentId);
|
|
63697
63965
|
if (existing) {
|
|
63698
63966
|
return null;
|
|
63699
63967
|
}
|
|
@@ -63729,7 +63997,7 @@ function checkLock(resourceType, resourceId, lockType, db) {
|
|
|
63729
63997
|
}
|
|
63730
63998
|
const d = db || getDatabase();
|
|
63731
63999
|
cleanExpiredLocks(d);
|
|
63732
|
-
const query = lockType ? "SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = ? AND expires_at >
|
|
64000
|
+
const query = lockType ? "SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = ? AND expires_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')" : "SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND expires_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
|
|
63733
64001
|
const rows = lockType ? d.query(query).all(resourceType, resourceId, lockType) : d.query(query).all(resourceType, resourceId);
|
|
63734
64002
|
return rows.map(parseLockRow);
|
|
63735
64003
|
}
|
|
@@ -63740,14 +64008,14 @@ function listAgentLocks(agentId, db) {
|
|
|
63740
64008
|
}
|
|
63741
64009
|
const d = db || getDatabase();
|
|
63742
64010
|
cleanExpiredLocks(d);
|
|
63743
|
-
const rows = d.query("SELECT * FROM resource_locks WHERE agent_id = ? AND expires_at >
|
|
64011
|
+
const rows = d.query("SELECT * FROM resource_locks WHERE agent_id = ? AND expires_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now') ORDER BY locked_at DESC").all(agentId);
|
|
63744
64012
|
return rows.map(parseLockRow);
|
|
63745
64013
|
}
|
|
63746
64014
|
function cleanExpiredLocksWithInfo(db) {
|
|
63747
64015
|
const d = db || getDatabase();
|
|
63748
|
-
const expired = d.query("SELECT id, resource_type, resource_id, agent_id, lock_type FROM resource_locks WHERE expires_at <=
|
|
64016
|
+
const expired = d.query("SELECT id, resource_type, resource_id, agent_id, lock_type FROM resource_locks WHERE expires_at <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now')").all();
|
|
63749
64017
|
if (expired.length > 0) {
|
|
63750
|
-
d.run("DELETE FROM resource_locks WHERE expires_at <=
|
|
64018
|
+
d.run("DELETE FROM resource_locks WHERE expires_at <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now')");
|
|
63751
64019
|
}
|
|
63752
64020
|
return expired;
|
|
63753
64021
|
}
|
|
@@ -63757,7 +64025,7 @@ function cleanExpiredLocks(db) {
|
|
|
63757
64025
|
return data?.cleaned ?? 0;
|
|
63758
64026
|
}
|
|
63759
64027
|
const d = db || getDatabase();
|
|
63760
|
-
const result = d.run("DELETE FROM resource_locks WHERE expires_at <=
|
|
64028
|
+
const result = d.run("DELETE FROM resource_locks WHERE expires_at <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now')");
|
|
63761
64029
|
return result.changes;
|
|
63762
64030
|
}
|
|
63763
64031
|
|
|
@@ -64096,7 +64364,7 @@ ${lines.join(`
|
|
|
64096
64364
|
}, async (args) => {
|
|
64097
64365
|
try {
|
|
64098
64366
|
const { reloadWebhooks: reloadWebhooks2 } = await Promise.resolve().then(() => (init_built_in_hooks(), exports_built_in_hooks));
|
|
64099
|
-
const wh = createWebhookHook({
|
|
64367
|
+
const wh = await createWebhookHook({
|
|
64100
64368
|
type: args.type,
|
|
64101
64369
|
handlerUrl: args.handler_url,
|
|
64102
64370
|
priority: args.priority,
|
|
@@ -64105,7 +64373,7 @@ ${lines.join(`
|
|
|
64105
64373
|
projectId: args.project_id,
|
|
64106
64374
|
description: args.description
|
|
64107
64375
|
});
|
|
64108
|
-
reloadWebhooks2();
|
|
64376
|
+
await reloadWebhooks2();
|
|
64109
64377
|
return { content: [{ type: "text", text: JSON.stringify(wh, null, 2) }] };
|
|
64110
64378
|
} catch (e) {
|
|
64111
64379
|
return { content: [{ type: "text", text: formatError6(e) }], isError: true };
|
|
@@ -65547,6 +65815,12 @@ function getNextPendingJob(db) {
|
|
|
65547
65815
|
return null;
|
|
65548
65816
|
return parseJobRow(row);
|
|
65549
65817
|
}
|
|
65818
|
+
function claimSessionJob(id, db) {
|
|
65819
|
+
const d = db || getDatabase();
|
|
65820
|
+
const startedAt = now();
|
|
65821
|
+
const result = d.run("UPDATE session_memory_jobs SET status = 'processing', started_at = ? WHERE id = ? AND status = 'pending'", [startedAt, id]);
|
|
65822
|
+
return result.changes;
|
|
65823
|
+
}
|
|
65550
65824
|
|
|
65551
65825
|
// src/lib/session-queue.ts
|
|
65552
65826
|
init_database();
|
|
@@ -65890,7 +66164,11 @@ async function processSessionJob(jobId, db) {
|
|
|
65890
66164
|
return result;
|
|
65891
66165
|
}
|
|
65892
66166
|
try {
|
|
65893
|
-
|
|
66167
|
+
const changes = claimSessionJob(jobId, db);
|
|
66168
|
+
if (changes === 0) {
|
|
66169
|
+
result.errors.push(`Job already claimed or not pending: ${jobId}`);
|
|
66170
|
+
return result;
|
|
66171
|
+
}
|
|
65894
66172
|
} catch (e) {
|
|
65895
66173
|
result.errors.push(`Failed to mark job as processing: ${String(e)}`);
|
|
65896
66174
|
return result;
|
|
@@ -68348,7 +68626,7 @@ async function prepareMcpRuntime() {
|
|
|
68348
68626
|
}
|
|
68349
68627
|
} catch {}
|
|
68350
68628
|
await ensureRestServerRunning();
|
|
68351
|
-
loadWebhooksFromDb();
|
|
68629
|
+
await loadWebhooksFromDb();
|
|
68352
68630
|
}
|
|
68353
68631
|
async function main() {
|
|
68354
68632
|
if (hasFlag("--help", "-h")) {
|