@hasna/mementos 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +496 -9
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/entities.d.ts.map +1 -1
- package/dist/db/memories.d.ts.map +1 -1
- package/dist/db/relations.d.ts.map +1 -1
- package/dist/db/webhook_hooks.d.ts +25 -0
- package/dist/db/webhook_hooks.d.ts.map +1 -0
- package/dist/index.js +160 -9
- package/dist/lib/built-in-hooks.d.ts +12 -0
- package/dist/lib/built-in-hooks.d.ts.map +1 -0
- package/dist/lib/focus.d.ts.map +1 -1
- package/dist/lib/hooks.d.ts +50 -0
- package/dist/lib/hooks.d.ts.map +1 -0
- package/dist/mcp/index.js +6805 -6178
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2263 -1682
- package/dist/types/hooks.d.ts +136 -0
- package/dist/types/hooks.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -346,6 +346,25 @@ var MIGRATIONS = [
|
|
|
346
346
|
ALTER TABLE memories ADD COLUMN recall_count INTEGER NOT NULL DEFAULT 0;
|
|
347
347
|
CREATE INDEX IF NOT EXISTS idx_memories_recall_count ON memories(recall_count DESC);
|
|
348
348
|
INSERT OR IGNORE INTO _migrations (id) VALUES (9);
|
|
349
|
+
`,
|
|
350
|
+
`
|
|
351
|
+
CREATE TABLE IF NOT EXISTS webhook_hooks (
|
|
352
|
+
id TEXT PRIMARY KEY,
|
|
353
|
+
type TEXT NOT NULL,
|
|
354
|
+
handler_url TEXT NOT NULL,
|
|
355
|
+
priority INTEGER NOT NULL DEFAULT 50,
|
|
356
|
+
blocking INTEGER NOT NULL DEFAULT 0,
|
|
357
|
+
agent_id TEXT,
|
|
358
|
+
project_id TEXT,
|
|
359
|
+
description TEXT,
|
|
360
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
361
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
362
|
+
invocation_count INTEGER NOT NULL DEFAULT 0,
|
|
363
|
+
failure_count INTEGER NOT NULL DEFAULT 0
|
|
364
|
+
);
|
|
365
|
+
CREATE INDEX IF NOT EXISTS idx_webhook_hooks_type ON webhook_hooks(type);
|
|
366
|
+
CREATE INDEX IF NOT EXISTS idx_webhook_hooks_enabled ON webhook_hooks(enabled);
|
|
367
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (10);
|
|
349
368
|
`
|
|
350
369
|
];
|
|
351
370
|
var _db = null;
|
|
@@ -443,6 +462,87 @@ function containsSecrets(text) {
|
|
|
443
462
|
return false;
|
|
444
463
|
}
|
|
445
464
|
|
|
465
|
+
// src/lib/hooks.ts
|
|
466
|
+
var _idCounter = 0;
|
|
467
|
+
function generateHookId() {
|
|
468
|
+
return `hook_${++_idCounter}_${Date.now().toString(36)}`;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
class HookRegistry {
|
|
472
|
+
hooks = new Map;
|
|
473
|
+
register(reg) {
|
|
474
|
+
const id = generateHookId();
|
|
475
|
+
const hook = {
|
|
476
|
+
...reg,
|
|
477
|
+
id,
|
|
478
|
+
priority: reg.priority ?? 50
|
|
479
|
+
};
|
|
480
|
+
this.hooks.set(id, hook);
|
|
481
|
+
return id;
|
|
482
|
+
}
|
|
483
|
+
unregister(hookId) {
|
|
484
|
+
const hook = this.hooks.get(hookId);
|
|
485
|
+
if (!hook)
|
|
486
|
+
return false;
|
|
487
|
+
if (hook.builtin)
|
|
488
|
+
return false;
|
|
489
|
+
this.hooks.delete(hookId);
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
list(type) {
|
|
493
|
+
const all = [...this.hooks.values()];
|
|
494
|
+
if (!type)
|
|
495
|
+
return all;
|
|
496
|
+
return all.filter((h) => h.type === type);
|
|
497
|
+
}
|
|
498
|
+
async runHooks(type, context) {
|
|
499
|
+
const matching = this.getMatchingHooks(type, context);
|
|
500
|
+
if (matching.length === 0)
|
|
501
|
+
return true;
|
|
502
|
+
matching.sort((a, b) => a.priority - b.priority);
|
|
503
|
+
for (const hook of matching) {
|
|
504
|
+
if (hook.blocking) {
|
|
505
|
+
try {
|
|
506
|
+
const result = await hook.handler(context);
|
|
507
|
+
if (result === false)
|
|
508
|
+
return false;
|
|
509
|
+
} catch (err) {
|
|
510
|
+
console.error(`[hooks] blocking hook ${hook.id} (${type}) threw:`, err);
|
|
511
|
+
}
|
|
512
|
+
} else {
|
|
513
|
+
Promise.resolve().then(() => hook.handler(context)).catch((err) => console.error(`[hooks] non-blocking hook ${hook.id} (${type}) threw:`, err));
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
getMatchingHooks(type, context) {
|
|
519
|
+
const ctx = context;
|
|
520
|
+
return [...this.hooks.values()].filter((hook) => {
|
|
521
|
+
if (hook.type !== type)
|
|
522
|
+
return false;
|
|
523
|
+
if (hook.agentId && hook.agentId !== ctx.agentId)
|
|
524
|
+
return false;
|
|
525
|
+
if (hook.projectId && hook.projectId !== ctx.projectId)
|
|
526
|
+
return false;
|
|
527
|
+
return true;
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
stats() {
|
|
531
|
+
const all = [...this.hooks.values()];
|
|
532
|
+
const byType = {};
|
|
533
|
+
for (const hook of all) {
|
|
534
|
+
byType[hook.type] = (byType[hook.type] ?? 0) + 1;
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
total: all.length,
|
|
538
|
+
byType,
|
|
539
|
+
blocking: all.filter((h) => h.blocking).length,
|
|
540
|
+
nonBlocking: all.filter((h) => !h.blocking).length
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
var hookRegistry = new HookRegistry;
|
|
545
|
+
|
|
446
546
|
// src/db/entity-memories.ts
|
|
447
547
|
function parseEntityRow(row) {
|
|
448
548
|
return {
|
|
@@ -628,9 +728,15 @@ function createMemory(input, dedupeMode = "merge", db) {
|
|
|
628
728
|
insertTag.run(id, tag);
|
|
629
729
|
}
|
|
630
730
|
const memory = getMemory(id, d);
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
731
|
+
runEntityExtraction(memory, input.project_id, d);
|
|
732
|
+
hookRegistry.runHooks("PostMemorySave", {
|
|
733
|
+
memory,
|
|
734
|
+
wasUpdated: false,
|
|
735
|
+
agentId: input.agent_id,
|
|
736
|
+
projectId: input.project_id,
|
|
737
|
+
sessionId: input.session_id,
|
|
738
|
+
timestamp: Date.now()
|
|
739
|
+
});
|
|
634
740
|
return memory;
|
|
635
741
|
}
|
|
636
742
|
function getMemory(id, db) {
|
|
@@ -854,20 +960,33 @@ function updateMemory(id, input, db) {
|
|
|
854
960
|
params.push(id);
|
|
855
961
|
d.run(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
856
962
|
const updated = getMemory(id, d);
|
|
857
|
-
|
|
858
|
-
|
|
963
|
+
if (input.value !== undefined) {
|
|
964
|
+
try {
|
|
859
965
|
const oldLinks = getEntityMemoryLinks(undefined, updated.id, d);
|
|
860
966
|
for (const link of oldLinks) {
|
|
861
967
|
unlinkEntityFromMemory(link.entity_id, updated.id, d);
|
|
862
968
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
969
|
+
} catch {}
|
|
970
|
+
}
|
|
971
|
+
hookRegistry.runHooks("PostMemoryUpdate", {
|
|
972
|
+
memory: updated,
|
|
973
|
+
previousValue: existing.value,
|
|
974
|
+
agentId: existing.agent_id ?? undefined,
|
|
975
|
+
projectId: existing.project_id ?? undefined,
|
|
976
|
+
sessionId: existing.session_id ?? undefined,
|
|
977
|
+
timestamp: Date.now()
|
|
978
|
+
});
|
|
866
979
|
return updated;
|
|
867
980
|
}
|
|
868
981
|
function deleteMemory(id, db) {
|
|
869
982
|
const d = db || getDatabase();
|
|
870
983
|
const result = d.run("DELETE FROM memories WHERE id = ?", [id]);
|
|
984
|
+
if (result.changes > 0) {
|
|
985
|
+
hookRegistry.runHooks("PostMemoryDelete", {
|
|
986
|
+
memoryId: id,
|
|
987
|
+
timestamp: Date.now()
|
|
988
|
+
});
|
|
989
|
+
}
|
|
871
990
|
return result.changes > 0;
|
|
872
991
|
}
|
|
873
992
|
function bulkDeleteMemories(ids, db) {
|
|
@@ -1182,8 +1301,25 @@ class MemoryLockConflictError extends Error {
|
|
|
1182
1301
|
// src/lib/focus.ts
|
|
1183
1302
|
var sessionFocus = new Map;
|
|
1184
1303
|
function setFocus(agentId, projectId) {
|
|
1304
|
+
const previous = getFocusCached(agentId);
|
|
1185
1305
|
sessionFocus.set(agentId, projectId);
|
|
1186
1306
|
updateAgent(agentId, { active_project_id: projectId });
|
|
1307
|
+
if (projectId && projectId !== previous) {
|
|
1308
|
+
hookRegistry.runHooks("OnSessionStart", {
|
|
1309
|
+
agentId,
|
|
1310
|
+
projectId,
|
|
1311
|
+
timestamp: Date.now()
|
|
1312
|
+
});
|
|
1313
|
+
} else if (!projectId && previous) {
|
|
1314
|
+
hookRegistry.runHooks("OnSessionEnd", {
|
|
1315
|
+
agentId,
|
|
1316
|
+
projectId: previous,
|
|
1317
|
+
timestamp: Date.now()
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
function getFocusCached(agentId) {
|
|
1322
|
+
return sessionFocus.get(agentId) ?? null;
|
|
1187
1323
|
}
|
|
1188
1324
|
function getFocus(agentId) {
|
|
1189
1325
|
if (sessionFocus.has(agentId)) {
|
|
@@ -1319,6 +1455,13 @@ function createEntity(input, db) {
|
|
|
1319
1455
|
timestamp,
|
|
1320
1456
|
timestamp
|
|
1321
1457
|
]);
|
|
1458
|
+
hookRegistry.runHooks("PostEntityCreate", {
|
|
1459
|
+
entityId: id,
|
|
1460
|
+
name: input.name,
|
|
1461
|
+
entityType: input.type,
|
|
1462
|
+
projectId: input.project_id,
|
|
1463
|
+
timestamp: Date.now()
|
|
1464
|
+
});
|
|
1322
1465
|
return getEntity(id, d);
|
|
1323
1466
|
}
|
|
1324
1467
|
function getEntity(id, db) {
|
|
@@ -2491,7 +2634,15 @@ function createRelation(input, db) {
|
|
|
2491
2634
|
DO UPDATE SET weight = excluded.weight, metadata = excluded.metadata`, [id, input.source_entity_id, input.target_entity_id, input.relation_type, weight, metadata, timestamp]);
|
|
2492
2635
|
const row = d.query(`SELECT * FROM relations
|
|
2493
2636
|
WHERE source_entity_id = ? AND target_entity_id = ? AND relation_type = ?`).get(input.source_entity_id, input.target_entity_id, input.relation_type);
|
|
2494
|
-
|
|
2637
|
+
const relation = parseRelationRow(row);
|
|
2638
|
+
hookRegistry.runHooks("PostRelationCreate", {
|
|
2639
|
+
relationId: relation.id,
|
|
2640
|
+
sourceEntityId: relation.source_entity_id,
|
|
2641
|
+
targetEntityId: relation.target_entity_id,
|
|
2642
|
+
relationType: relation.relation_type,
|
|
2643
|
+
timestamp: Date.now()
|
|
2644
|
+
});
|
|
2645
|
+
return relation;
|
|
2495
2646
|
}
|
|
2496
2647
|
function getRelation(id, db) {
|
|
2497
2648
|
const d = db || getDatabase();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in hooks — registered at server/MCP startup.
|
|
3
|
+
* These are system-level hooks that power the auto-memory pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Built-in hooks cannot be unregistered (builtin: true).
|
|
6
|
+
* They are always non-blocking so they never delay the calling operation.
|
|
7
|
+
*/
|
|
8
|
+
import type { HookType } from "../types/hooks.js";
|
|
9
|
+
export declare function loadWebhooksFromDb(): void;
|
|
10
|
+
export declare function reloadWebhooks(): void;
|
|
11
|
+
export type { HookType };
|
|
12
|
+
//# sourceMappingURL=built-in-hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"built-in-hooks.d.ts","sourceRoot":"","sources":["../../src/lib/built-in-hooks.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AA2ElD,wBAAgB,kBAAkB,IAAI,IAAI,CAyBzC;AAuBD,wBAAgB,cAAc,IAAI,IAAI,CAGrC;AAGD,YAAY,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/lib/focus.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"focus.d.ts","sourceRoot":"","sources":["../../src/lib/focus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"focus.d.ts","sourceRoot":"","sources":["../../src/lib/focus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAqBxE;AAOD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWvD;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAClC,iBAAiB,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAC3C,MAAM,GAAG,IAAI,CAUf;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAClC,iBAAiB,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAC5C,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GACvC,gBAAgB,GAAG,IAAI,CAazB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAKnC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook registry — the central nervous system connecting all memory operations.
|
|
3
|
+
*
|
|
4
|
+
* Blocking hooks: await handler, return false = cancel the operation.
|
|
5
|
+
* Non-blocking hooks: fire-and-forget in background, never delay caller.
|
|
6
|
+
*
|
|
7
|
+
* Hooks run in priority order (ascending — lower number first).
|
|
8
|
+
* Per-agent and per-project scoping supported.
|
|
9
|
+
*/
|
|
10
|
+
import type { Hook, HookType, HookHandler, HookRegistration, HookContextMap } from "../types/hooks.js";
|
|
11
|
+
declare class HookRegistry {
|
|
12
|
+
private hooks;
|
|
13
|
+
/**
|
|
14
|
+
* Register a hook. Returns the assigned hookId.
|
|
15
|
+
* Built-in hooks (builtin: true) cannot be unregistered.
|
|
16
|
+
*/
|
|
17
|
+
register<T extends HookType>(reg: HookRegistration<T>): string;
|
|
18
|
+
/**
|
|
19
|
+
* Unregister a hook by ID.
|
|
20
|
+
* Returns false if hook not found or is a built-in.
|
|
21
|
+
*/
|
|
22
|
+
unregister(hookId: string): boolean;
|
|
23
|
+
/** List all hooks, optionally filtered by type */
|
|
24
|
+
list(type?: HookType): Hook[];
|
|
25
|
+
/**
|
|
26
|
+
* Run all hooks of a given type for a given context.
|
|
27
|
+
*
|
|
28
|
+
* Returns true if the operation should proceed.
|
|
29
|
+
* Returns false if any blocking hook cancelled it.
|
|
30
|
+
*
|
|
31
|
+
* Non-blocking hooks are fired async and never delay the return.
|
|
32
|
+
*/
|
|
33
|
+
runHooks<T extends HookType>(type: T, context: HookContextMap[T]): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Get hooks matching type + agent/project scope.
|
|
36
|
+
* A hook with no agentId/projectId matches everything.
|
|
37
|
+
*/
|
|
38
|
+
private getMatchingHooks;
|
|
39
|
+
/** Get stats about registered hooks */
|
|
40
|
+
stats(): {
|
|
41
|
+
total: number;
|
|
42
|
+
byType: Record<string, number>;
|
|
43
|
+
blocking: number;
|
|
44
|
+
nonBlocking: number;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Singleton — shared across the whole process */
|
|
48
|
+
export declare const hookRegistry: HookRegistry;
|
|
49
|
+
export type { Hook, HookType, HookHandler, HookRegistration };
|
|
50
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/lib/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,IAAI,EACJ,QAAQ,EACR,WAAW,EACX,gBAAgB,EAChB,cAAc,EACf,MAAM,mBAAmB,CAAC;AAO3B,cAAM,YAAY;IAChB,OAAO,CAAC,KAAK,CAA2B;IAExC;;;OAGG;IACH,QAAQ,CAAC,CAAC,SAAS,QAAQ,EAAE,GAAG,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,MAAM;IAW9D;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAQnC,kDAAkD;IAClD,IAAI,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,EAAE;IAM7B;;;;;;;OAOG;IACG,QAAQ,CAAC,CAAC,SAAS,QAAQ,EAC/B,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,OAAO,CAAC;IA8BnB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAaxB,uCAAuC;IACvC,KAAK,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE;CAalG;AAED,kDAAkD;AAClD,eAAO,MAAM,YAAY,cAAqB,CAAC;AAG/C,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC"}
|