@agentmemory/agentmemory 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/.claude-plugin/marketplace.json +14 -0
- package/.github/workflows/ci.yml +22 -0
- package/.github/workflows/publish.yml +28 -0
- package/AGENTS.md +113 -0
- package/LICENSE +190 -0
- package/README.md +828 -0
- package/assets/banner.png +0 -0
- package/assets/demo.gif +0 -0
- package/assets/demo.mp4 +0 -0
- package/benchmark/QUALITY.md +73 -0
- package/benchmark/REAL-EMBEDDINGS.md +67 -0
- package/benchmark/SCALE.md +110 -0
- package/benchmark/dataset.ts +293 -0
- package/benchmark/quality-eval.ts +643 -0
- package/benchmark/real-embeddings-eval.ts +405 -0
- package/benchmark/scale-eval.ts +398 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +137 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/docker-compose.yml +14 -0
- package/dist/hooks/notification.d.mts +1 -0
- package/dist/hooks/notification.mjs +45 -0
- package/dist/hooks/notification.mjs.map +1 -0
- package/dist/hooks/post-tool-failure.d.mts +1 -0
- package/dist/hooks/post-tool-failure.mjs +45 -0
- package/dist/hooks/post-tool-failure.mjs.map +1 -0
- package/dist/hooks/post-tool-use.d.mts +1 -0
- package/dist/hooks/post-tool-use.mjs +53 -0
- package/dist/hooks/post-tool-use.mjs.map +1 -0
- package/dist/hooks/pre-compact.d.mts +1 -0
- package/dist/hooks/pre-compact.mjs +50 -0
- package/dist/hooks/pre-compact.mjs.map +1 -0
- package/dist/hooks/pre-tool-use.d.mts +1 -0
- package/dist/hooks/pre-tool-use.mjs +69 -0
- package/dist/hooks/pre-tool-use.mjs.map +1 -0
- package/dist/hooks/prompt-submit.d.mts +1 -0
- package/dist/hooks/prompt-submit.mjs +40 -0
- package/dist/hooks/prompt-submit.mjs.map +1 -0
- package/dist/hooks/session-end.d.mts +1 -0
- package/dist/hooks/session-end.mjs +61 -0
- package/dist/hooks/session-end.mjs.map +1 -0
- package/dist/hooks/session-start.d.mts +1 -0
- package/dist/hooks/session-start.mjs +42 -0
- package/dist/hooks/session-start.mjs.map +1 -0
- package/dist/hooks/stop.d.mts +1 -0
- package/dist/hooks/stop.mjs +33 -0
- package/dist/hooks/stop.mjs.map +1 -0
- package/dist/hooks/subagent-start.d.mts +1 -0
- package/dist/hooks/subagent-start.mjs +43 -0
- package/dist/hooks/subagent-start.mjs.map +1 -0
- package/dist/hooks/subagent-stop.d.mts +1 -0
- package/dist/hooks/subagent-stop.mjs +45 -0
- package/dist/hooks/subagent-stop.mjs.map +1 -0
- package/dist/hooks/task-completed.d.mts +1 -0
- package/dist/hooks/task-completed.mjs +46 -0
- package/dist/hooks/task-completed.mjs.map +1 -0
- package/dist/iii-config.yaml +51 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +13776 -0
- package/dist/index.mjs.map +1 -0
- package/dist/src-QxitMPfJ.mjs +13775 -0
- package/dist/src-QxitMPfJ.mjs.map +1 -0
- package/dist/standalone.d.mts +1 -0
- package/dist/standalone.mjs +1155 -0
- package/dist/standalone.mjs.map +1 -0
- package/dist/transformers-BX_tgxdO.mjs +38684 -0
- package/dist/transformers-BX_tgxdO.mjs.map +1 -0
- package/dist/transformers-KMm1i9no.mjs +38683 -0
- package/dist/transformers-KMm1i9no.mjs.map +1 -0
- package/docker-compose.yml +14 -0
- package/iii-config.yaml +51 -0
- package/package.json +59 -0
- package/plugin/.claude-plugin/plugin.json +10 -0
- package/plugin/hooks/hooks.json +77 -0
- package/plugin/scripts/diagnostics.mjs +551 -0
- package/plugin/scripts/notification.mjs +45 -0
- package/plugin/scripts/post-tool-failure.mjs +45 -0
- package/plugin/scripts/post-tool-use.mjs +53 -0
- package/plugin/scripts/pre-compact.mjs +50 -0
- package/plugin/scripts/pre-tool-use.mjs +69 -0
- package/plugin/scripts/prompt-submit.mjs +40 -0
- package/plugin/scripts/session-end.mjs +61 -0
- package/plugin/scripts/session-start.mjs +42 -0
- package/plugin/scripts/stop.mjs +33 -0
- package/plugin/scripts/subagent-start.mjs +43 -0
- package/plugin/scripts/subagent-stop.mjs +45 -0
- package/plugin/scripts/task-completed.mjs +46 -0
- package/plugin/skills/forget/SKILL.md +32 -0
- package/plugin/skills/recall/SKILL.md +18 -0
- package/plugin/skills/remember/SKILL.md +25 -0
- package/plugin/skills/session-history/SKILL.md +17 -0
- package/src/auth.ts +12 -0
- package/src/cli.ts +159 -0
- package/src/config.ts +221 -0
- package/src/eval/metrics-store.ts +65 -0
- package/src/eval/quality.ts +51 -0
- package/src/eval/schemas.ts +124 -0
- package/src/eval/self-correct.ts +28 -0
- package/src/eval/validator.ts +31 -0
- package/src/functions/actions.ts +288 -0
- package/src/functions/audit.ts +61 -0
- package/src/functions/auto-forget.ts +169 -0
- package/src/functions/branch-aware.ts +169 -0
- package/src/functions/cascade.ts +80 -0
- package/src/functions/checkpoints.ts +209 -0
- package/src/functions/claude-bridge.ts +161 -0
- package/src/functions/compress.ts +194 -0
- package/src/functions/consolidate.ts +212 -0
- package/src/functions/consolidation-pipeline.ts +258 -0
- package/src/functions/context.ts +169 -0
- package/src/functions/crystallize.ts +293 -0
- package/src/functions/dedup.ts +57 -0
- package/src/functions/diagnostics.ts +785 -0
- package/src/functions/enrich.ts +132 -0
- package/src/functions/evict.ts +163 -0
- package/src/functions/export-import.ts +508 -0
- package/src/functions/facets.ts +248 -0
- package/src/functions/file-index.ts +106 -0
- package/src/functions/flow-compress.ts +214 -0
- package/src/functions/frontier.ts +196 -0
- package/src/functions/governance.ts +131 -0
- package/src/functions/graph-retrieval.ts +277 -0
- package/src/functions/graph.ts +275 -0
- package/src/functions/leases.ts +216 -0
- package/src/functions/lessons.ts +253 -0
- package/src/functions/mesh.ts +434 -0
- package/src/functions/migrate.ts +165 -0
- package/src/functions/observe.ts +144 -0
- package/src/functions/obsidian-export.ts +310 -0
- package/src/functions/patterns.ts +138 -0
- package/src/functions/privacy.ts +39 -0
- package/src/functions/profile.ts +155 -0
- package/src/functions/query-expansion.ts +186 -0
- package/src/functions/relations.ts +237 -0
- package/src/functions/remember.ts +162 -0
- package/src/functions/retention.ts +235 -0
- package/src/functions/routines.ts +289 -0
- package/src/functions/search.ts +80 -0
- package/src/functions/sentinels.ts +417 -0
- package/src/functions/signals.ts +186 -0
- package/src/functions/sketches.ts +274 -0
- package/src/functions/sliding-window.ts +257 -0
- package/src/functions/smart-search.ts +115 -0
- package/src/functions/snapshot.ts +219 -0
- package/src/functions/summarize.ts +155 -0
- package/src/functions/team.ts +147 -0
- package/src/functions/temporal-graph.ts +476 -0
- package/src/functions/timeline.ts +138 -0
- package/src/functions/verify.ts +117 -0
- package/src/health/monitor.ts +110 -0
- package/src/health/thresholds.ts +73 -0
- package/src/hooks/notification.ts +52 -0
- package/src/hooks/post-tool-failure.ts +58 -0
- package/src/hooks/post-tool-use.ts +62 -0
- package/src/hooks/pre-compact.ts +60 -0
- package/src/hooks/pre-tool-use.ts +72 -0
- package/src/hooks/prompt-submit.ts +46 -0
- package/src/hooks/session-end.ts +71 -0
- package/src/hooks/session-start.ts +48 -0
- package/src/hooks/stop.ts +39 -0
- package/src/hooks/subagent-start.ts +49 -0
- package/src/hooks/subagent-stop.ts +54 -0
- package/src/hooks/task-completed.ts +54 -0
- package/src/index.ts +342 -0
- package/src/mcp/in-memory-kv.ts +61 -0
- package/src/mcp/server.ts +1455 -0
- package/src/mcp/standalone.ts +177 -0
- package/src/mcp/tools-registry.ts +769 -0
- package/src/mcp/transport.ts +91 -0
- package/src/prompts/compression.ts +67 -0
- package/src/prompts/consolidation.ts +48 -0
- package/src/prompts/graph-extraction.ts +35 -0
- package/src/prompts/summary.ts +38 -0
- package/src/prompts/xml.ts +26 -0
- package/src/providers/agent-sdk.ts +34 -0
- package/src/providers/anthropic.ts +35 -0
- package/src/providers/circuit-breaker.ts +82 -0
- package/src/providers/embedding/cohere.ts +46 -0
- package/src/providers/embedding/gemini.ts +54 -0
- package/src/providers/embedding/index.ts +39 -0
- package/src/providers/embedding/local.ts +52 -0
- package/src/providers/embedding/openai.ts +45 -0
- package/src/providers/embedding/openrouter.ts +51 -0
- package/src/providers/embedding/voyage.ts +46 -0
- package/src/providers/fallback-chain.ts +31 -0
- package/src/providers/index.ts +84 -0
- package/src/providers/openrouter.ts +71 -0
- package/src/providers/resilient.ts +37 -0
- package/src/state/hybrid-search.ts +295 -0
- package/src/state/index-persistence.ts +63 -0
- package/src/state/keyed-mutex.ts +18 -0
- package/src/state/kv.ts +33 -0
- package/src/state/schema.ts +71 -0
- package/src/state/search-index.ts +245 -0
- package/src/state/stemmer.ts +104 -0
- package/src/state/synonyms.ts +63 -0
- package/src/state/vector-index.ts +130 -0
- package/src/telemetry/setup.ts +116 -0
- package/src/triggers/api.ts +1904 -0
- package/src/triggers/events.ts +71 -0
- package/src/types.ts +769 -0
- package/src/version.ts +1 -0
- package/src/viewer/index.html +2497 -0
- package/src/viewer/server.ts +207 -0
- package/src/xenova.d.ts +3 -0
- package/test/actions.test.ts +490 -0
- package/test/audit.test.ts +108 -0
- package/test/auto-forget.test.ts +188 -0
- package/test/cascade.test.ts +277 -0
- package/test/checkpoints.test.ts +493 -0
- package/test/circuit-breaker.test.ts +107 -0
- package/test/claude-bridge.test.ts +178 -0
- package/test/confidence.test.ts +247 -0
- package/test/consistency.test.ts +61 -0
- package/test/consolidation-pipeline.test.ts +251 -0
- package/test/crystallize.test.ts +521 -0
- package/test/diagnostics.test.ts +638 -0
- package/test/embedding-provider.test.ts +49 -0
- package/test/enrich.test.ts +209 -0
- package/test/eval.test.ts +300 -0
- package/test/export-import.test.ts +251 -0
- package/test/facets.test.ts +448 -0
- package/test/fallback-chain.test.ts +93 -0
- package/test/frontier.test.ts +485 -0
- package/test/governance.test.ts +147 -0
- package/test/graph-retrieval.test.ts +186 -0
- package/test/graph.test.ts +160 -0
- package/test/helpers/mocks.ts +40 -0
- package/test/hybrid-search.test.ts +145 -0
- package/test/index-persistence.test.ts +124 -0
- package/test/integration.test.ts +265 -0
- package/test/leases.test.ts +399 -0
- package/test/mcp-prompts.test.ts +218 -0
- package/test/mcp-resources.test.ts +286 -0
- package/test/mcp-standalone.test.ts +113 -0
- package/test/mesh.test.ts +700 -0
- package/test/privacy.test.ts +87 -0
- package/test/profile.test.ts +161 -0
- package/test/query-expansion.test.ts +154 -0
- package/test/relations.test.ts +198 -0
- package/test/retention.test.ts +245 -0
- package/test/routines.test.ts +497 -0
- package/test/schema-fingerprint.test.ts +81 -0
- package/test/schema.test.ts +42 -0
- package/test/search-index.test.ts +128 -0
- package/test/sentinels.test.ts +626 -0
- package/test/signals.test.ts +410 -0
- package/test/sketches.test.ts +549 -0
- package/test/sliding-window.test.ts +199 -0
- package/test/smart-search.test.ts +169 -0
- package/test/snapshot.test.ts +165 -0
- package/test/team.test.ts +156 -0
- package/test/temporal-graph.test.ts +378 -0
- package/test/timeline.test.ts +148 -0
- package/test/vector-index.test.ts +79 -0
- package/test/verify.test.ts +209 -0
- package/test/xml.test.ts +65 -0
- package/tsconfig.json +22 -0
- package/tsdown.config.ts +62 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { IndexPersistence } from "../src/state/index-persistence.js";
|
|
3
|
+
import { SearchIndex } from "../src/state/search-index.js";
|
|
4
|
+
import { VectorIndex } from "../src/state/vector-index.js";
|
|
5
|
+
import type { CompressedObservation } from "../src/types.js";
|
|
6
|
+
|
|
7
|
+
function mockKV() {
|
|
8
|
+
const store = new Map<string, Map<string, unknown>>();
|
|
9
|
+
return {
|
|
10
|
+
get: async <T>(scope: string, key: string): Promise<T | null> => {
|
|
11
|
+
return (store.get(scope)?.get(key) as T) ?? null;
|
|
12
|
+
},
|
|
13
|
+
set: async <T>(scope: string, key: string, data: T): Promise<T> => {
|
|
14
|
+
if (!store.has(scope)) store.set(scope, new Map());
|
|
15
|
+
store.get(scope)!.set(key, data);
|
|
16
|
+
return data;
|
|
17
|
+
},
|
|
18
|
+
delete: async (scope: string, key: string): Promise<void> => {
|
|
19
|
+
store.get(scope)?.delete(key);
|
|
20
|
+
},
|
|
21
|
+
list: async <T>(scope: string): Promise<T[]> => {
|
|
22
|
+
const entries = store.get(scope);
|
|
23
|
+
return entries ? (Array.from(entries.values()) as T[]) : [];
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function makeObs(
|
|
29
|
+
overrides: Partial<CompressedObservation> = {},
|
|
30
|
+
): CompressedObservation {
|
|
31
|
+
return {
|
|
32
|
+
id: "obs_1",
|
|
33
|
+
sessionId: "ses_1",
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
type: "file_edit",
|
|
36
|
+
title: "Edit auth middleware",
|
|
37
|
+
subtitle: "JWT validation",
|
|
38
|
+
facts: ["Added token check"],
|
|
39
|
+
narrative: "Modified the auth middleware to validate JWT tokens",
|
|
40
|
+
concepts: ["authentication", "jwt"],
|
|
41
|
+
files: ["src/middleware/auth.ts"],
|
|
42
|
+
importance: 7,
|
|
43
|
+
...overrides,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe("IndexPersistence", () => {
|
|
48
|
+
let kv: ReturnType<typeof mockKV>;
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
vi.useFakeTimers();
|
|
52
|
+
kv = mockKV();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
vi.useRealTimers();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("saves and loads BM25 index round-trip", async () => {
|
|
60
|
+
const bm25 = new SearchIndex();
|
|
61
|
+
bm25.add(makeObs({ id: "obs_1", title: "auth handler" }));
|
|
62
|
+
|
|
63
|
+
const persistence = new IndexPersistence(kv as never, bm25, null);
|
|
64
|
+
await persistence.save();
|
|
65
|
+
|
|
66
|
+
const loaded = await persistence.load();
|
|
67
|
+
expect(loaded.bm25).not.toBeNull();
|
|
68
|
+
expect(loaded.bm25!.size).toBe(1);
|
|
69
|
+
const results = loaded.bm25!.search("auth");
|
|
70
|
+
expect(results.length).toBe(1);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("saves and loads vector index round-trip", async () => {
|
|
74
|
+
const bm25 = new SearchIndex();
|
|
75
|
+
const vector = new VectorIndex();
|
|
76
|
+
vector.add("obs_1", "ses_1", new Float32Array([0.1, 0.2, 0.3]));
|
|
77
|
+
|
|
78
|
+
const persistence = new IndexPersistence(kv as never, bm25, vector);
|
|
79
|
+
await persistence.save();
|
|
80
|
+
|
|
81
|
+
const loaded = await persistence.load();
|
|
82
|
+
expect(loaded.vector).not.toBeNull();
|
|
83
|
+
expect(loaded.vector!.size).toBe(1);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("scheduleSave debounces multiple calls", async () => {
|
|
87
|
+
const bm25 = new SearchIndex();
|
|
88
|
+
const persistence = new IndexPersistence(kv as never, bm25, null);
|
|
89
|
+
|
|
90
|
+
persistence.scheduleSave();
|
|
91
|
+
persistence.scheduleSave();
|
|
92
|
+
persistence.scheduleSave();
|
|
93
|
+
|
|
94
|
+
await expect(kv.get("mem:index:bm25", "data")).resolves.toBeNull();
|
|
95
|
+
|
|
96
|
+
vi.advanceTimersByTime(5000);
|
|
97
|
+
await vi.runAllTimersAsync();
|
|
98
|
+
|
|
99
|
+
const saved = await kv.get<string>("mem:index:bm25", "data");
|
|
100
|
+
expect(saved).not.toBeNull();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("stop clears the pending timer", async () => {
|
|
104
|
+
const bm25 = new SearchIndex();
|
|
105
|
+
bm25.add(makeObs({ id: "obs_1", title: "auth handler" }));
|
|
106
|
+
const persistence = new IndexPersistence(kv as never, bm25, null);
|
|
107
|
+
|
|
108
|
+
persistence.scheduleSave();
|
|
109
|
+
persistence.stop();
|
|
110
|
+
|
|
111
|
+
vi.advanceTimersByTime(10000);
|
|
112
|
+
const saved = await kv.get<string>("mem:index:bm25", "data");
|
|
113
|
+
expect(saved).toBeNull();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("returns null indexes when nothing has been saved", async () => {
|
|
117
|
+
const bm25 = new SearchIndex();
|
|
118
|
+
const persistence = new IndexPersistence(kv as never, bm25, null);
|
|
119
|
+
|
|
120
|
+
const loaded = await persistence.load();
|
|
121
|
+
expect(loaded.bm25).toBeNull();
|
|
122
|
+
expect(loaded.vector).toBeNull();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
+
|
|
3
|
+
const BASE_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111";
|
|
4
|
+
const SECRET = process.env["AGENTMEMORY_SECRET"] || "";
|
|
5
|
+
|
|
6
|
+
const SESSION_ID = `test_${Date.now()}`;
|
|
7
|
+
const PROJECT = "/tmp/test-project";
|
|
8
|
+
|
|
9
|
+
function url(path: string): string {
|
|
10
|
+
return `${BASE_URL}${path}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function authHeaders(): Record<string, string> {
|
|
14
|
+
const headers: Record<string, string> = {
|
|
15
|
+
"Content-Type": "application/json",
|
|
16
|
+
};
|
|
17
|
+
if (SECRET) {
|
|
18
|
+
headers["Authorization"] = `Bearer ${SECRET}`;
|
|
19
|
+
}
|
|
20
|
+
return headers;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function json(res: Response): Promise<unknown> {
|
|
24
|
+
const text = await res.text();
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(text);
|
|
27
|
+
} catch {
|
|
28
|
+
return text;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("agentmemory integration", () => {
|
|
33
|
+
beforeAll(async () => {
|
|
34
|
+
const res = await fetch(url("/agentmemory/health")).catch(() => null);
|
|
35
|
+
if (!res || !res.ok) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`agentmemory is not running at ${BASE_URL}. Start it with: docker compose up -d && npm start`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("health", () => {
|
|
43
|
+
it("returns ok", async () => {
|
|
44
|
+
const res = await fetch(url("/agentmemory/health"));
|
|
45
|
+
expect(res.status).toBe(200);
|
|
46
|
+
const body = (await json(res)) as { status: string; service: string };
|
|
47
|
+
expect(["ok", "healthy"]).toContain(body.status);
|
|
48
|
+
expect(body.service).toBe("agentmemory");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("session lifecycle", () => {
|
|
53
|
+
it("starts a session", async () => {
|
|
54
|
+
const res = await fetch(url("/agentmemory/session/start"), {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: authHeaders(),
|
|
57
|
+
body: JSON.stringify({
|
|
58
|
+
sessionId: SESSION_ID,
|
|
59
|
+
project: PROJECT,
|
|
60
|
+
cwd: PROJECT,
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
expect(res.status).toBe(200);
|
|
64
|
+
const body = (await json(res)) as {
|
|
65
|
+
session: { id: string; status: string };
|
|
66
|
+
context: string;
|
|
67
|
+
};
|
|
68
|
+
expect(body.session.id).toBe(SESSION_ID);
|
|
69
|
+
expect(body.session.status).toBe("active");
|
|
70
|
+
expect(typeof body.context).toBe("string");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("lists sessions including the new one", async () => {
|
|
74
|
+
const res = await fetch(url("/agentmemory/sessions"));
|
|
75
|
+
expect(res.status).toBe(200);
|
|
76
|
+
const body = (await json(res)) as {
|
|
77
|
+
sessions: Array<{ id: string }>;
|
|
78
|
+
};
|
|
79
|
+
expect(Array.isArray(body.sessions)).toBe(true);
|
|
80
|
+
const found = body.sessions.find((s) => s.id === SESSION_ID);
|
|
81
|
+
expect(found).toBeDefined();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("ends the session", async () => {
|
|
85
|
+
const res = await fetch(url("/agentmemory/session/end"), {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: authHeaders(),
|
|
88
|
+
body: JSON.stringify({ sessionId: SESSION_ID }),
|
|
89
|
+
});
|
|
90
|
+
expect(res.status).toBe(200);
|
|
91
|
+
const body = (await json(res)) as { success: boolean };
|
|
92
|
+
expect(body.success).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("session is marked completed", async () => {
|
|
96
|
+
const res = await fetch(url("/agentmemory/sessions"));
|
|
97
|
+
const body = (await json(res)) as {
|
|
98
|
+
sessions: Array<{ id: string; status: string; endedAt?: string }>;
|
|
99
|
+
};
|
|
100
|
+
const session = body.sessions.find((s) => s.id === SESSION_ID);
|
|
101
|
+
expect(session).toBeDefined();
|
|
102
|
+
expect(session!.status).toBe("completed");
|
|
103
|
+
expect(session!.endedAt).toBeDefined();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("observations", () => {
|
|
108
|
+
const OBS_SESSION = `test_obs_${Date.now()}`;
|
|
109
|
+
|
|
110
|
+
beforeAll(async () => {
|
|
111
|
+
await fetch(url("/agentmemory/session/start"), {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: authHeaders(),
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
sessionId: OBS_SESSION,
|
|
116
|
+
project: PROJECT,
|
|
117
|
+
cwd: PROJECT,
|
|
118
|
+
}),
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
afterAll(async () => {
|
|
123
|
+
await fetch(url("/agentmemory/session/end"), {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: authHeaders(),
|
|
126
|
+
body: JSON.stringify({ sessionId: OBS_SESSION }),
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("captures an observation", async () => {
|
|
131
|
+
const res = await fetch(url("/agentmemory/observe"), {
|
|
132
|
+
method: "POST",
|
|
133
|
+
headers: authHeaders(),
|
|
134
|
+
body: JSON.stringify({
|
|
135
|
+
hookType: "post_tool_use",
|
|
136
|
+
sessionId: OBS_SESSION,
|
|
137
|
+
project: PROJECT,
|
|
138
|
+
cwd: PROJECT,
|
|
139
|
+
timestamp: new Date().toISOString(),
|
|
140
|
+
data: {
|
|
141
|
+
tool: "Edit",
|
|
142
|
+
file: "src/auth.ts",
|
|
143
|
+
content: "Added JWT token validation middleware",
|
|
144
|
+
},
|
|
145
|
+
}),
|
|
146
|
+
});
|
|
147
|
+
expect(res.status).toBe(201);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("captures a second observation", async () => {
|
|
151
|
+
const res = await fetch(url("/agentmemory/observe"), {
|
|
152
|
+
method: "POST",
|
|
153
|
+
headers: authHeaders(),
|
|
154
|
+
body: JSON.stringify({
|
|
155
|
+
hookType: "post_tool_use",
|
|
156
|
+
sessionId: OBS_SESSION,
|
|
157
|
+
project: PROJECT,
|
|
158
|
+
cwd: PROJECT,
|
|
159
|
+
timestamp: new Date().toISOString(),
|
|
160
|
+
data: {
|
|
161
|
+
tool: "Bash",
|
|
162
|
+
command: "npm test",
|
|
163
|
+
output: "Tests: 12 passed, 0 failed",
|
|
164
|
+
},
|
|
165
|
+
}),
|
|
166
|
+
});
|
|
167
|
+
expect(res.status).toBe(201);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("lists observations for the session", async () => {
|
|
171
|
+
const res = await fetch(
|
|
172
|
+
url(`/agentmemory/observations?sessionId=${OBS_SESSION}`),
|
|
173
|
+
);
|
|
174
|
+
expect(res.status).toBe(200);
|
|
175
|
+
const body = (await json(res)) as {
|
|
176
|
+
observations: Array<{ id: string; sessionId: string }>;
|
|
177
|
+
};
|
|
178
|
+
expect(Array.isArray(body.observations)).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("returns 400 without sessionId", async () => {
|
|
182
|
+
const res = await fetch(url("/agentmemory/observations"));
|
|
183
|
+
expect(res.status).toBe(400);
|
|
184
|
+
const body = (await json(res)) as { error: string };
|
|
185
|
+
expect(body.error).toBe("sessionId required");
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe("search", () => {
|
|
190
|
+
it("searches observations", async () => {
|
|
191
|
+
const res = await fetch(url("/agentmemory/search"), {
|
|
192
|
+
method: "POST",
|
|
193
|
+
headers: authHeaders(),
|
|
194
|
+
body: JSON.stringify({ query: "auth", limit: 5 }),
|
|
195
|
+
});
|
|
196
|
+
expect(res.status).toBe(200);
|
|
197
|
+
const body = await json(res);
|
|
198
|
+
expect(body).toBeDefined();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("returns results for empty limit", async () => {
|
|
202
|
+
const res = await fetch(url("/agentmemory/search"), {
|
|
203
|
+
method: "POST",
|
|
204
|
+
headers: authHeaders(),
|
|
205
|
+
body: JSON.stringify({ query: "test" }),
|
|
206
|
+
});
|
|
207
|
+
expect(res.status).toBe(200);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe("context", () => {
|
|
212
|
+
it("generates context for a project", async () => {
|
|
213
|
+
const res = await fetch(url("/agentmemory/context"), {
|
|
214
|
+
method: "POST",
|
|
215
|
+
headers: authHeaders(),
|
|
216
|
+
body: JSON.stringify({
|
|
217
|
+
sessionId: "ctx-test",
|
|
218
|
+
project: PROJECT,
|
|
219
|
+
}),
|
|
220
|
+
});
|
|
221
|
+
expect(res.status).toBe(200);
|
|
222
|
+
const body = (await json(res)) as { context: string };
|
|
223
|
+
expect(typeof body.context).toBe("string");
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe("viewer", () => {
|
|
228
|
+
it("serves the viewer HTML", async () => {
|
|
229
|
+
const res = await fetch(url("/agentmemory/viewer"));
|
|
230
|
+
expect(res.status).toBe(200);
|
|
231
|
+
const body = await res.text();
|
|
232
|
+
expect(body).toContain("html");
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe("auth", () => {
|
|
237
|
+
it("health endpoint is always public", async () => {
|
|
238
|
+
const res = await fetch(url("/agentmemory/health"));
|
|
239
|
+
expect(res.status).toBe(200);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
if (SECRET) {
|
|
243
|
+
it("rejects unauthenticated requests", async () => {
|
|
244
|
+
const res = await fetch(url("/agentmemory/search"), {
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers: { "Content-Type": "application/json" },
|
|
247
|
+
body: JSON.stringify({ query: "test" }),
|
|
248
|
+
});
|
|
249
|
+
expect(res.status).toBe(401);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("rejects wrong bearer token", async () => {
|
|
253
|
+
const res = await fetch(url("/agentmemory/search"), {
|
|
254
|
+
method: "POST",
|
|
255
|
+
headers: {
|
|
256
|
+
"Content-Type": "application/json",
|
|
257
|
+
Authorization: "Bearer wrong-token",
|
|
258
|
+
},
|
|
259
|
+
body: JSON.stringify({ query: "test" }),
|
|
260
|
+
});
|
|
261
|
+
expect(res.status).toBe(401);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
});
|