@agentmemory/agentmemory 0.7.2 → 0.7.3
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/AGENTS.md +2 -2
- package/README.md +41 -68
- package/dist/cli.mjs +3 -3
- package/dist/index.mjs +3 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{src-1fTKFEtN.mjs → src-sYZDDbiA.mjs} +4 -3
- package/dist/src-sYZDDbiA.mjs.map +1 -0
- package/dist/standalone.mjs +1 -1
- package/dist/standalone.mjs.map +1 -1
- package/package.json +10 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/scripts/notification.d.mts +1 -0
- package/plugin/scripts/notification.mjs.map +1 -0
- package/plugin/scripts/post-tool-failure.d.mts +1 -0
- package/plugin/scripts/post-tool-failure.mjs.map +1 -0
- package/plugin/scripts/post-tool-use.d.mts +1 -0
- package/plugin/scripts/post-tool-use.mjs.map +1 -0
- package/plugin/scripts/pre-compact.d.mts +1 -0
- package/plugin/scripts/pre-compact.mjs.map +1 -0
- package/plugin/scripts/pre-tool-use.d.mts +1 -0
- package/plugin/scripts/pre-tool-use.mjs.map +1 -0
- package/plugin/scripts/prompt-submit.d.mts +1 -0
- package/plugin/scripts/prompt-submit.mjs.map +1 -0
- package/plugin/scripts/session-end.d.mts +1 -0
- package/plugin/scripts/session-end.mjs.map +1 -0
- package/plugin/scripts/session-start.d.mts +1 -0
- package/plugin/scripts/session-start.mjs.map +1 -0
- package/plugin/scripts/stop.d.mts +1 -0
- package/plugin/scripts/stop.mjs.map +1 -0
- package/plugin/scripts/subagent-start.d.mts +1 -0
- package/plugin/scripts/subagent-start.mjs.map +1 -0
- package/plugin/scripts/subagent-stop.d.mts +1 -0
- package/plugin/scripts/subagent-stop.mjs.map +1 -0
- package/plugin/scripts/task-completed.d.mts +1 -0
- package/plugin/scripts/task-completed.mjs.map +1 -0
- package/.claude-plugin/marketplace.json +0 -14
- package/.github/workflows/ci.yml +0 -22
- package/.github/workflows/publish.yml +0 -28
- package/assets/banner.png +0 -0
- package/assets/demo.gif +0 -0
- package/assets/demo.mp4 +0 -0
- package/benchmark/QUALITY.md +0 -73
- package/benchmark/REAL-EMBEDDINGS.md +0 -67
- package/benchmark/SCALE.md +0 -110
- package/benchmark/dataset.ts +0 -293
- package/benchmark/quality-eval.ts +0 -643
- package/benchmark/real-embeddings-eval.ts +0 -405
- package/benchmark/scale-eval.ts +0 -398
- package/dist/src-1fTKFEtN.mjs.map +0 -1
- package/src/auth.ts +0 -12
- package/src/cli.ts +0 -251
- package/src/config.ts +0 -221
- package/src/eval/metrics-store.ts +0 -65
- package/src/eval/quality.ts +0 -51
- package/src/eval/schemas.ts +0 -124
- package/src/eval/self-correct.ts +0 -28
- package/src/eval/validator.ts +0 -31
- package/src/functions/actions.ts +0 -288
- package/src/functions/audit.ts +0 -61
- package/src/functions/auto-forget.ts +0 -169
- package/src/functions/branch-aware.ts +0 -169
- package/src/functions/cascade.ts +0 -80
- package/src/functions/checkpoints.ts +0 -209
- package/src/functions/claude-bridge.ts +0 -161
- package/src/functions/compress.ts +0 -194
- package/src/functions/consolidate.ts +0 -212
- package/src/functions/consolidation-pipeline.ts +0 -258
- package/src/functions/context.ts +0 -169
- package/src/functions/crystallize.ts +0 -293
- package/src/functions/dedup.ts +0 -57
- package/src/functions/diagnostics.ts +0 -785
- package/src/functions/enrich.ts +0 -132
- package/src/functions/evict.ts +0 -163
- package/src/functions/export-import.ts +0 -508
- package/src/functions/facets.ts +0 -248
- package/src/functions/file-index.ts +0 -106
- package/src/functions/flow-compress.ts +0 -214
- package/src/functions/frontier.ts +0 -196
- package/src/functions/governance.ts +0 -131
- package/src/functions/graph-retrieval.ts +0 -277
- package/src/functions/graph.ts +0 -275
- package/src/functions/leases.ts +0 -216
- package/src/functions/lessons.ts +0 -253
- package/src/functions/mesh.ts +0 -434
- package/src/functions/migrate.ts +0 -165
- package/src/functions/observe.ts +0 -144
- package/src/functions/obsidian-export.ts +0 -310
- package/src/functions/patterns.ts +0 -138
- package/src/functions/privacy.ts +0 -39
- package/src/functions/profile.ts +0 -155
- package/src/functions/query-expansion.ts +0 -186
- package/src/functions/relations.ts +0 -237
- package/src/functions/remember.ts +0 -162
- package/src/functions/retention.ts +0 -235
- package/src/functions/routines.ts +0 -289
- package/src/functions/search.ts +0 -80
- package/src/functions/sentinels.ts +0 -417
- package/src/functions/signals.ts +0 -186
- package/src/functions/sketches.ts +0 -274
- package/src/functions/sliding-window.ts +0 -257
- package/src/functions/smart-search.ts +0 -115
- package/src/functions/snapshot.ts +0 -219
- package/src/functions/summarize.ts +0 -155
- package/src/functions/team.ts +0 -147
- package/src/functions/temporal-graph.ts +0 -476
- package/src/functions/timeline.ts +0 -138
- package/src/functions/verify.ts +0 -117
- package/src/health/monitor.ts +0 -110
- package/src/health/thresholds.ts +0 -73
- package/src/hooks/notification.ts +0 -52
- package/src/hooks/post-tool-failure.ts +0 -58
- package/src/hooks/post-tool-use.ts +0 -62
- package/src/hooks/pre-compact.ts +0 -60
- package/src/hooks/pre-tool-use.ts +0 -72
- package/src/hooks/prompt-submit.ts +0 -46
- package/src/hooks/session-end.ts +0 -71
- package/src/hooks/session-start.ts +0 -48
- package/src/hooks/stop.ts +0 -39
- package/src/hooks/subagent-start.ts +0 -49
- package/src/hooks/subagent-stop.ts +0 -54
- package/src/hooks/task-completed.ts +0 -54
- package/src/index.ts +0 -342
- package/src/mcp/in-memory-kv.ts +0 -61
- package/src/mcp/server.ts +0 -1455
- package/src/mcp/standalone.ts +0 -177
- package/src/mcp/tools-registry.ts +0 -769
- package/src/mcp/transport.ts +0 -91
- package/src/prompts/compression.ts +0 -67
- package/src/prompts/consolidation.ts +0 -48
- package/src/prompts/graph-extraction.ts +0 -35
- package/src/prompts/summary.ts +0 -38
- package/src/prompts/xml.ts +0 -26
- package/src/providers/agent-sdk.ts +0 -34
- package/src/providers/anthropic.ts +0 -35
- package/src/providers/circuit-breaker.ts +0 -82
- package/src/providers/embedding/cohere.ts +0 -46
- package/src/providers/embedding/gemini.ts +0 -54
- package/src/providers/embedding/index.ts +0 -39
- package/src/providers/embedding/local.ts +0 -52
- package/src/providers/embedding/openai.ts +0 -45
- package/src/providers/embedding/openrouter.ts +0 -51
- package/src/providers/embedding/voyage.ts +0 -46
- package/src/providers/fallback-chain.ts +0 -31
- package/src/providers/index.ts +0 -84
- package/src/providers/openrouter.ts +0 -71
- package/src/providers/resilient.ts +0 -37
- package/src/state/hybrid-search.ts +0 -295
- package/src/state/index-persistence.ts +0 -63
- package/src/state/keyed-mutex.ts +0 -18
- package/src/state/kv.ts +0 -33
- package/src/state/schema.ts +0 -71
- package/src/state/search-index.ts +0 -245
- package/src/state/stemmer.ts +0 -104
- package/src/state/synonyms.ts +0 -63
- package/src/state/vector-index.ts +0 -130
- package/src/telemetry/setup.ts +0 -116
- package/src/triggers/api.ts +0 -1904
- package/src/triggers/events.ts +0 -71
- package/src/types.ts +0 -769
- package/src/version.ts +0 -1
- package/src/viewer/index.html +0 -2556
- package/src/viewer/server.ts +0 -207
- package/src/xenova.d.ts +0 -3
- package/test/actions.test.ts +0 -490
- package/test/audit.test.ts +0 -108
- package/test/auto-forget.test.ts +0 -188
- package/test/cascade.test.ts +0 -277
- package/test/checkpoints.test.ts +0 -493
- package/test/circuit-breaker.test.ts +0 -107
- package/test/claude-bridge.test.ts +0 -178
- package/test/confidence.test.ts +0 -247
- package/test/consistency.test.ts +0 -61
- package/test/consolidation-pipeline.test.ts +0 -251
- package/test/crystallize.test.ts +0 -521
- package/test/diagnostics.test.ts +0 -638
- package/test/embedding-provider.test.ts +0 -49
- package/test/enrich.test.ts +0 -209
- package/test/eval.test.ts +0 -300
- package/test/export-import.test.ts +0 -251
- package/test/facets.test.ts +0 -448
- package/test/fallback-chain.test.ts +0 -93
- package/test/frontier.test.ts +0 -485
- package/test/governance.test.ts +0 -147
- package/test/graph-retrieval.test.ts +0 -186
- package/test/graph.test.ts +0 -160
- package/test/helpers/mocks.ts +0 -40
- package/test/hybrid-search.test.ts +0 -145
- package/test/index-persistence.test.ts +0 -124
- package/test/integration.test.ts +0 -265
- package/test/leases.test.ts +0 -399
- package/test/mcp-prompts.test.ts +0 -218
- package/test/mcp-resources.test.ts +0 -286
- package/test/mcp-standalone.test.ts +0 -113
- package/test/mesh.test.ts +0 -700
- package/test/privacy.test.ts +0 -87
- package/test/profile.test.ts +0 -161
- package/test/query-expansion.test.ts +0 -154
- package/test/relations.test.ts +0 -198
- package/test/retention.test.ts +0 -245
- package/test/routines.test.ts +0 -497
- package/test/schema-fingerprint.test.ts +0 -81
- package/test/schema.test.ts +0 -42
- package/test/search-index.test.ts +0 -128
- package/test/sentinels.test.ts +0 -626
- package/test/signals.test.ts +0 -410
- package/test/sketches.test.ts +0 -549
- package/test/sliding-window.test.ts +0 -199
- package/test/smart-search.test.ts +0 -169
- package/test/snapshot.test.ts +0 -165
- package/test/team.test.ts +0 -156
- package/test/temporal-graph.test.ts +0 -378
- package/test/timeline.test.ts +0 -148
- package/test/vector-index.test.ts +0 -79
- package/test/verify.test.ts +0 -209
- package/test/xml.test.ts +0 -65
- package/tsconfig.json +0 -22
- package/tsdown.config.ts +0 -62
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import type { ISdk } from "iii-sdk";
|
|
2
|
-
import type { StateKV } from "../state/kv.js";
|
|
3
|
-
import { KV } from "../state/schema.js";
|
|
4
|
-
import type { Session } from "../types.js";
|
|
5
|
-
import { execFile } from "node:child_process";
|
|
6
|
-
import { resolve } from "node:path";
|
|
7
|
-
|
|
8
|
-
function execAsync(
|
|
9
|
-
cmd: string,
|
|
10
|
-
args: string[],
|
|
11
|
-
cwd: string,
|
|
12
|
-
): Promise<string> {
|
|
13
|
-
return new Promise((resolve, reject) => {
|
|
14
|
-
execFile(cmd, args, { cwd, timeout: 5000 }, (err, stdout) => {
|
|
15
|
-
if (err) reject(err);
|
|
16
|
-
else resolve(stdout.trim());
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function registerBranchAwareFunction(sdk: ISdk, kv: StateKV): void {
|
|
22
|
-
sdk.registerFunction(
|
|
23
|
-
{ id: "mem::detect-worktree" },
|
|
24
|
-
async (data: { cwd: string }) => {
|
|
25
|
-
if (!data.cwd) {
|
|
26
|
-
return { success: false, error: "cwd is required" };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const gitDir = await execAsync(
|
|
31
|
-
"git",
|
|
32
|
-
["rev-parse", "--git-dir"],
|
|
33
|
-
data.cwd,
|
|
34
|
-
);
|
|
35
|
-
const commonDir = await execAsync(
|
|
36
|
-
"git",
|
|
37
|
-
["rev-parse", "--git-common-dir"],
|
|
38
|
-
data.cwd,
|
|
39
|
-
);
|
|
40
|
-
const branch = await execAsync(
|
|
41
|
-
"git",
|
|
42
|
-
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
43
|
-
data.cwd,
|
|
44
|
-
).catch(() => "detached");
|
|
45
|
-
|
|
46
|
-
const topLevel = await execAsync(
|
|
47
|
-
"git",
|
|
48
|
-
["rev-parse", "--show-toplevel"],
|
|
49
|
-
data.cwd,
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
const isWorktree = resolve(data.cwd, gitDir) !== resolve(data.cwd, commonDir);
|
|
53
|
-
const mainRepoRoot = isWorktree
|
|
54
|
-
? resolve(data.cwd, commonDir, "..")
|
|
55
|
-
: topLevel;
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
success: true,
|
|
59
|
-
isWorktree,
|
|
60
|
-
branch,
|
|
61
|
-
topLevel,
|
|
62
|
-
mainRepoRoot,
|
|
63
|
-
gitDir: resolve(data.cwd, gitDir),
|
|
64
|
-
commonDir: resolve(data.cwd, commonDir),
|
|
65
|
-
};
|
|
66
|
-
} catch {
|
|
67
|
-
return {
|
|
68
|
-
success: true,
|
|
69
|
-
isWorktree: false,
|
|
70
|
-
branch: null,
|
|
71
|
-
topLevel: data.cwd,
|
|
72
|
-
mainRepoRoot: data.cwd,
|
|
73
|
-
gitDir: null,
|
|
74
|
-
commonDir: null,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
sdk.registerFunction(
|
|
81
|
-
{ id: "mem::list-worktrees" },
|
|
82
|
-
async (data: { cwd: string }) => {
|
|
83
|
-
if (!data.cwd) {
|
|
84
|
-
return { success: false, error: "cwd is required" };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
const output = await execAsync(
|
|
89
|
-
"git",
|
|
90
|
-
["worktree", "list", "--porcelain"],
|
|
91
|
-
data.cwd,
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
const worktrees: Array<{
|
|
95
|
-
path: string;
|
|
96
|
-
head: string;
|
|
97
|
-
branch: string;
|
|
98
|
-
bare: boolean;
|
|
99
|
-
}> = [];
|
|
100
|
-
|
|
101
|
-
const blocks = output.split("\n\n").filter(Boolean);
|
|
102
|
-
for (const block of blocks) {
|
|
103
|
-
const lines = block.split("\n");
|
|
104
|
-
const wt: { path: string; head: string; branch: string; bare: boolean } = {
|
|
105
|
-
path: "",
|
|
106
|
-
head: "",
|
|
107
|
-
branch: "",
|
|
108
|
-
bare: false,
|
|
109
|
-
};
|
|
110
|
-
for (const line of lines) {
|
|
111
|
-
if (line.startsWith("worktree ")) wt.path = line.slice(9);
|
|
112
|
-
else if (line.startsWith("HEAD ")) wt.head = line.slice(5);
|
|
113
|
-
else if (line.startsWith("branch "))
|
|
114
|
-
wt.branch = line.slice(7).replace("refs/heads/", "");
|
|
115
|
-
else if (line === "bare") wt.bare = true;
|
|
116
|
-
}
|
|
117
|
-
if (wt.path) worktrees.push(wt);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return { success: true, worktrees };
|
|
121
|
-
} catch {
|
|
122
|
-
return { success: true, worktrees: [] };
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
sdk.registerFunction(
|
|
128
|
-
{ id: "mem::branch-sessions" },
|
|
129
|
-
async (data: { cwd: string; branch?: string }) => {
|
|
130
|
-
if (!data.cwd) {
|
|
131
|
-
return { success: false, error: "cwd is required" };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const worktreeInfo = await sdk.trigger<
|
|
135
|
-
{ cwd: string },
|
|
136
|
-
{
|
|
137
|
-
success: boolean;
|
|
138
|
-
isWorktree: boolean;
|
|
139
|
-
mainRepoRoot: string;
|
|
140
|
-
branch: string | null;
|
|
141
|
-
}
|
|
142
|
-
>("mem::detect-worktree", { cwd: data.cwd });
|
|
143
|
-
|
|
144
|
-
const projectRoot = worktreeInfo.mainRepoRoot || data.cwd;
|
|
145
|
-
const branch = data.branch || worktreeInfo.branch;
|
|
146
|
-
|
|
147
|
-
const sessions = await kv.list<Session>(KV.sessions);
|
|
148
|
-
|
|
149
|
-
const matching = sessions.filter((s) => {
|
|
150
|
-
if (s.project === projectRoot || s.cwd === projectRoot) return true;
|
|
151
|
-
if (s.cwd.startsWith(projectRoot + "/")) return true;
|
|
152
|
-
return false;
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
matching.sort(
|
|
156
|
-
(a, b) =>
|
|
157
|
-
new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime(),
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
success: true,
|
|
162
|
-
sessions: matching,
|
|
163
|
-
projectRoot,
|
|
164
|
-
branch,
|
|
165
|
-
isWorktree: worktreeInfo.isWorktree,
|
|
166
|
-
};
|
|
167
|
-
},
|
|
168
|
-
);
|
|
169
|
-
}
|
package/src/functions/cascade.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import type { ISdk } from "iii-sdk";
|
|
2
|
-
import type { StateKV } from "../state/kv.js";
|
|
3
|
-
import { KV } from "../state/schema.js";
|
|
4
|
-
import type { Memory, GraphNode, GraphEdge } from "../types.js";
|
|
5
|
-
|
|
6
|
-
export function registerCascadeFunction(sdk: ISdk, kv: StateKV): void {
|
|
7
|
-
sdk.registerFunction(
|
|
8
|
-
{ id: "mem::cascade-update" },
|
|
9
|
-
async (data: { supersededMemoryId: string }) => {
|
|
10
|
-
if (!data.supersededMemoryId || typeof data.supersededMemoryId !== "string") {
|
|
11
|
-
return { success: false, error: "supersededMemoryId is required" };
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const superseded = await kv.get<Memory>(KV.memories, data.supersededMemoryId);
|
|
15
|
-
if (!superseded) {
|
|
16
|
-
return { success: false, error: "superseded memory not found" };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
let flaggedNodes = 0;
|
|
20
|
-
let flaggedEdges = 0;
|
|
21
|
-
let flaggedMemories = 0;
|
|
22
|
-
|
|
23
|
-
const obsIds = new Set(superseded.sourceObservationIds || []);
|
|
24
|
-
|
|
25
|
-
if (obsIds.size > 0) {
|
|
26
|
-
const now = new Date().toISOString();
|
|
27
|
-
const nodes = await kv.list<GraphNode>(KV.graphNodes);
|
|
28
|
-
for (const node of nodes) {
|
|
29
|
-
if (node.stale) continue;
|
|
30
|
-
const overlap = (node.sourceObservationIds ?? []).some((id) => obsIds.has(id));
|
|
31
|
-
if (overlap) {
|
|
32
|
-
node.stale = true;
|
|
33
|
-
node.updatedAt = now;
|
|
34
|
-
await kv.set(KV.graphNodes, node.id, node);
|
|
35
|
-
flaggedNodes++;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const edges = await kv.list<GraphEdge>(KV.graphEdges);
|
|
40
|
-
for (const edge of edges) {
|
|
41
|
-
if (edge.stale) continue;
|
|
42
|
-
const overlap = (edge.sourceObservationIds ?? []).some((id) => obsIds.has(id));
|
|
43
|
-
if (overlap) {
|
|
44
|
-
edge.stale = true;
|
|
45
|
-
await kv.set(KV.graphEdges, edge.id, edge);
|
|
46
|
-
flaggedEdges++;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const supersededConcepts = new Set(
|
|
52
|
-
(superseded.concepts ?? []).map((c) => c.toLowerCase()),
|
|
53
|
-
);
|
|
54
|
-
if (supersededConcepts.size >= 2) {
|
|
55
|
-
const allMemories = await kv.list<Memory>(KV.memories);
|
|
56
|
-
for (const mem of allMemories) {
|
|
57
|
-
if (mem.id === data.supersededMemoryId) continue;
|
|
58
|
-
if (!mem.isLatest) continue;
|
|
59
|
-
|
|
60
|
-
const sharedCount = (mem.concepts ?? []).filter((c) =>
|
|
61
|
-
supersededConcepts.has(c.toLowerCase()),
|
|
62
|
-
).length;
|
|
63
|
-
if (sharedCount >= 2) {
|
|
64
|
-
flaggedMemories++;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
success: true,
|
|
71
|
-
flagged: {
|
|
72
|
-
nodes: flaggedNodes,
|
|
73
|
-
edges: flaggedEdges,
|
|
74
|
-
siblingMemories: flaggedMemories,
|
|
75
|
-
},
|
|
76
|
-
total: flaggedNodes + flaggedEdges + flaggedMemories,
|
|
77
|
-
};
|
|
78
|
-
},
|
|
79
|
-
);
|
|
80
|
-
}
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import type { ISdk } from "iii-sdk";
|
|
2
|
-
import type { StateKV } from "../state/kv.js";
|
|
3
|
-
import { KV, generateId } from "../state/schema.js";
|
|
4
|
-
import { withKeyedLock } from "../state/keyed-mutex.js";
|
|
5
|
-
import type { Action, ActionEdge, Checkpoint } from "../types.js";
|
|
6
|
-
|
|
7
|
-
export function registerCheckpointsFunction(sdk: ISdk, kv: StateKV): void {
|
|
8
|
-
sdk.registerFunction(
|
|
9
|
-
{ id: "mem::checkpoint-create" },
|
|
10
|
-
async (data: {
|
|
11
|
-
name: string;
|
|
12
|
-
description?: string;
|
|
13
|
-
type?: Checkpoint["type"];
|
|
14
|
-
linkedActionIds?: string[];
|
|
15
|
-
expiresInMs?: number;
|
|
16
|
-
}) => {
|
|
17
|
-
if (!data.name) {
|
|
18
|
-
return { success: false, error: "name is required" };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const validTypes: Checkpoint["type"][] = ["ci", "approval", "deploy", "external", "timer"];
|
|
22
|
-
if (data.type && !validTypes.includes(data.type)) {
|
|
23
|
-
return { success: false, error: `invalid checkpoint type: ${data.type}. Must be one of: ${validTypes.join(", ")}` };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const now = new Date();
|
|
27
|
-
const checkpoint: Checkpoint = {
|
|
28
|
-
id: generateId("ckpt"),
|
|
29
|
-
name: data.name.trim(),
|
|
30
|
-
description: (data.description || "").trim(),
|
|
31
|
-
status: "pending",
|
|
32
|
-
type: data.type || "external",
|
|
33
|
-
createdAt: now.toISOString(),
|
|
34
|
-
linkedActionIds: data.linkedActionIds || [],
|
|
35
|
-
expiresAt: data.expiresInMs
|
|
36
|
-
? new Date(now.getTime() + data.expiresInMs).toISOString()
|
|
37
|
-
: undefined,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
if (data.linkedActionIds && data.linkedActionIds.length > 0) {
|
|
41
|
-
for (const actionId of data.linkedActionIds) {
|
|
42
|
-
const action = await kv.get<Action>(KV.actions, actionId);
|
|
43
|
-
if (!action) {
|
|
44
|
-
return { success: false, error: `linked action not found: ${actionId}` };
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
await kv.set(KV.checkpoints, checkpoint.id, checkpoint);
|
|
50
|
-
|
|
51
|
-
if (data.linkedActionIds && data.linkedActionIds.length > 0) {
|
|
52
|
-
for (const actionId of data.linkedActionIds) {
|
|
53
|
-
const edge: ActionEdge = {
|
|
54
|
-
id: generateId("ae"),
|
|
55
|
-
type: "gated_by",
|
|
56
|
-
sourceActionId: actionId,
|
|
57
|
-
targetActionId: checkpoint.id,
|
|
58
|
-
createdAt: now.toISOString(),
|
|
59
|
-
};
|
|
60
|
-
await kv.set(KV.actionEdges, edge.id, edge);
|
|
61
|
-
|
|
62
|
-
const action = await kv.get<Action>(KV.actions, actionId);
|
|
63
|
-
if (action && action.status === "pending") {
|
|
64
|
-
action.status = "blocked";
|
|
65
|
-
action.updatedAt = now.toISOString();
|
|
66
|
-
await kv.set(KV.actions, action.id, action);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return { success: true, checkpoint };
|
|
72
|
-
},
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
sdk.registerFunction(
|
|
76
|
-
{ id: "mem::checkpoint-resolve" },
|
|
77
|
-
async (data: {
|
|
78
|
-
checkpointId: string;
|
|
79
|
-
status: "passed" | "failed";
|
|
80
|
-
resolvedBy?: string;
|
|
81
|
-
result?: unknown;
|
|
82
|
-
}) => {
|
|
83
|
-
if (!data.checkpointId || !data.status) {
|
|
84
|
-
return {
|
|
85
|
-
success: false,
|
|
86
|
-
error: "checkpointId and status are required",
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return withKeyedLock(
|
|
91
|
-
`mem:checkpoint:${data.checkpointId}`,
|
|
92
|
-
async () => {
|
|
93
|
-
const checkpoint = await kv.get<Checkpoint>(
|
|
94
|
-
KV.checkpoints,
|
|
95
|
-
data.checkpointId,
|
|
96
|
-
);
|
|
97
|
-
if (!checkpoint) {
|
|
98
|
-
return { success: false, error: "checkpoint not found" };
|
|
99
|
-
}
|
|
100
|
-
if (checkpoint.status !== "pending") {
|
|
101
|
-
return {
|
|
102
|
-
success: false,
|
|
103
|
-
error: `checkpoint already ${checkpoint.status}`,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
checkpoint.status = data.status;
|
|
108
|
-
checkpoint.resolvedAt = new Date().toISOString();
|
|
109
|
-
checkpoint.resolvedBy = data.resolvedBy;
|
|
110
|
-
checkpoint.result = data.result;
|
|
111
|
-
|
|
112
|
-
await kv.set(KV.checkpoints, checkpoint.id, checkpoint);
|
|
113
|
-
|
|
114
|
-
let unblockedCount = 0;
|
|
115
|
-
if (data.status === "passed" && checkpoint.linkedActionIds.length > 0) {
|
|
116
|
-
const allEdges = await kv.list<ActionEdge>(KV.actionEdges);
|
|
117
|
-
const allCheckpoints = await kv.list<Checkpoint>(KV.checkpoints);
|
|
118
|
-
const cpMap = new Map(allCheckpoints.map((c) => [c.id, c]));
|
|
119
|
-
|
|
120
|
-
for (const actionId of checkpoint.linkedActionIds) {
|
|
121
|
-
await withKeyedLock(`mem:action:${actionId}`, async () => {
|
|
122
|
-
const action = await kv.get<Action>(KV.actions, actionId);
|
|
123
|
-
if (action && action.status === "blocked") {
|
|
124
|
-
const gates = allEdges.filter(
|
|
125
|
-
(e) => e.sourceActionId === actionId && e.type === "gated_by",
|
|
126
|
-
);
|
|
127
|
-
const allGatesPassed = gates.every((g) => {
|
|
128
|
-
const cp = cpMap.get(g.targetActionId);
|
|
129
|
-
return cp && cp.status === "passed";
|
|
130
|
-
});
|
|
131
|
-
const requires = allEdges.filter(
|
|
132
|
-
(e) => e.sourceActionId === actionId && e.type === "requires",
|
|
133
|
-
);
|
|
134
|
-
const allActions = await kv.list<Action>(KV.actions);
|
|
135
|
-
const actionMap = new Map(allActions.map((a) => [a.id, a]));
|
|
136
|
-
const allRequiresMet = requires.every((r) => {
|
|
137
|
-
const dep = actionMap.get(r.targetActionId);
|
|
138
|
-
return dep && dep.status === "done";
|
|
139
|
-
});
|
|
140
|
-
if (allGatesPassed && allRequiresMet) {
|
|
141
|
-
action.status = "pending";
|
|
142
|
-
action.updatedAt = new Date().toISOString();
|
|
143
|
-
await kv.set(KV.actions, action.id, action);
|
|
144
|
-
unblockedCount++;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return { success: true, checkpoint, unblockedCount };
|
|
152
|
-
},
|
|
153
|
-
);
|
|
154
|
-
},
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
sdk.registerFunction(
|
|
158
|
-
{ id: "mem::checkpoint-list" },
|
|
159
|
-
async (data: { status?: string; type?: string }) => {
|
|
160
|
-
let checkpoints = await kv.list<Checkpoint>(KV.checkpoints);
|
|
161
|
-
|
|
162
|
-
if (data.status) {
|
|
163
|
-
checkpoints = checkpoints.filter((c) => c.status === data.status);
|
|
164
|
-
}
|
|
165
|
-
if (data.type) {
|
|
166
|
-
checkpoints = checkpoints.filter((c) => c.type === data.type);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
checkpoints.sort(
|
|
170
|
-
(a, b) =>
|
|
171
|
-
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
return { success: true, checkpoints };
|
|
175
|
-
},
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
sdk.registerFunction(
|
|
179
|
-
{ id: "mem::checkpoint-expire" },
|
|
180
|
-
async () => {
|
|
181
|
-
const checkpoints = await kv.list<Checkpoint>(KV.checkpoints);
|
|
182
|
-
const now = Date.now();
|
|
183
|
-
let expired = 0;
|
|
184
|
-
|
|
185
|
-
for (const cp of checkpoints) {
|
|
186
|
-
if (
|
|
187
|
-
cp.status === "pending" &&
|
|
188
|
-
cp.expiresAt &&
|
|
189
|
-
new Date(cp.expiresAt).getTime() <= now
|
|
190
|
-
) {
|
|
191
|
-
const didExpire = await withKeyedLock(
|
|
192
|
-
`mem:checkpoint:${cp.id}`,
|
|
193
|
-
async () => {
|
|
194
|
-
const fresh = await kv.get<Checkpoint>(KV.checkpoints, cp.id);
|
|
195
|
-
if (!fresh || fresh.status !== "pending") return false;
|
|
196
|
-
fresh.status = "expired";
|
|
197
|
-
fresh.resolvedAt = new Date().toISOString();
|
|
198
|
-
await kv.set(KV.checkpoints, fresh.id, fresh);
|
|
199
|
-
return true;
|
|
200
|
-
},
|
|
201
|
-
);
|
|
202
|
-
if (didExpire) expired++;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return { success: true, expired };
|
|
207
|
-
},
|
|
208
|
-
);
|
|
209
|
-
}
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import type { ISdk } from "iii-sdk";
|
|
2
|
-
import { getContext } from "iii-sdk";
|
|
3
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
4
|
-
import { dirname } from "node:path";
|
|
5
|
-
import type { Memory, ClaudeBridgeConfig } from "../types.js";
|
|
6
|
-
import { KV } from "../state/schema.js";
|
|
7
|
-
import type { StateKV } from "../state/kv.js";
|
|
8
|
-
import { recordAudit } from "./audit.js";
|
|
9
|
-
|
|
10
|
-
function parseMemoryMd(content: string): {
|
|
11
|
-
sections: Map<string, string>;
|
|
12
|
-
raw: string;
|
|
13
|
-
} {
|
|
14
|
-
const sections = new Map<string, string>();
|
|
15
|
-
let currentSection = "";
|
|
16
|
-
let currentContent: string[] = [];
|
|
17
|
-
|
|
18
|
-
for (const line of content.split("\n")) {
|
|
19
|
-
if (line.startsWith("## ")) {
|
|
20
|
-
if (currentSection) {
|
|
21
|
-
sections.set(currentSection, currentContent.join("\n").trim());
|
|
22
|
-
}
|
|
23
|
-
currentSection = line.slice(3).trim();
|
|
24
|
-
currentContent = [];
|
|
25
|
-
} else {
|
|
26
|
-
currentContent.push(line);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
if (currentSection) {
|
|
30
|
-
sections.set(currentSection, currentContent.join("\n").trim());
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return { sections, raw: content };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function serializeToMemoryMd(
|
|
37
|
-
memories: Memory[],
|
|
38
|
-
projectSummary: string,
|
|
39
|
-
lineBudget: number,
|
|
40
|
-
): string {
|
|
41
|
-
const lines: string[] = [];
|
|
42
|
-
lines.push("# Agent Memory (auto-synced by agentmemory)");
|
|
43
|
-
lines.push("");
|
|
44
|
-
|
|
45
|
-
if (projectSummary) {
|
|
46
|
-
lines.push("## Project Summary");
|
|
47
|
-
lines.push(projectSummary);
|
|
48
|
-
lines.push("");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
lines.push("## Key Memories");
|
|
52
|
-
lines.push("");
|
|
53
|
-
|
|
54
|
-
const sorted = [...memories]
|
|
55
|
-
.filter((m) => m.isLatest)
|
|
56
|
-
.sort((a, b) => b.strength - a.strength);
|
|
57
|
-
|
|
58
|
-
for (const mem of sorted) {
|
|
59
|
-
if (lines.length >= lineBudget - 2) break;
|
|
60
|
-
lines.push(`### ${mem.title}`);
|
|
61
|
-
const contentLines = mem.content.split("\n");
|
|
62
|
-
for (const cl of contentLines) {
|
|
63
|
-
if (lines.length >= lineBudget - 1) break;
|
|
64
|
-
lines.push(cl);
|
|
65
|
-
}
|
|
66
|
-
lines.push("");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return lines.slice(0, lineBudget).join("\n");
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function registerClaudeBridgeFunction(
|
|
73
|
-
sdk: ISdk,
|
|
74
|
-
kv: StateKV,
|
|
75
|
-
config: ClaudeBridgeConfig,
|
|
76
|
-
): void {
|
|
77
|
-
sdk.registerFunction(
|
|
78
|
-
{ id: "mem::claude-bridge-read" },
|
|
79
|
-
async () => {
|
|
80
|
-
const ctx = getContext();
|
|
81
|
-
if (!config.enabled || !config.memoryFilePath) {
|
|
82
|
-
return { success: false, error: "Claude bridge not configured" };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
if (!existsSync(config.memoryFilePath)) {
|
|
87
|
-
return { success: true, content: "", parsed: false };
|
|
88
|
-
}
|
|
89
|
-
const content = readFileSync(config.memoryFilePath, "utf-8");
|
|
90
|
-
const { sections } = parseMemoryMd(content);
|
|
91
|
-
|
|
92
|
-
await kv.set(KV.claudeBridge, "last-read", {
|
|
93
|
-
timestamp: new Date().toISOString(),
|
|
94
|
-
sections: Object.fromEntries(sections),
|
|
95
|
-
lineCount: content.split("\n").length,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
ctx.logger.info("Claude bridge: read MEMORY.md", {
|
|
99
|
-
path: config.memoryFilePath,
|
|
100
|
-
lines: content.split("\n").length,
|
|
101
|
-
});
|
|
102
|
-
return { success: true, content, sections: Object.fromEntries(sections) };
|
|
103
|
-
} catch (err) {
|
|
104
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
105
|
-
ctx.logger.error("Claude bridge read failed", { error: msg });
|
|
106
|
-
return { success: false, error: msg };
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
sdk.registerFunction(
|
|
112
|
-
{ id: "mem::claude-bridge-sync" },
|
|
113
|
-
async () => {
|
|
114
|
-
const ctx = getContext();
|
|
115
|
-
if (!config.enabled || !config.memoryFilePath) {
|
|
116
|
-
return { success: false, error: "Claude bridge not configured" };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
const memories = await kv.list<Memory>(KV.memories);
|
|
121
|
-
const latestMemories = memories.filter((m) => m.isLatest);
|
|
122
|
-
|
|
123
|
-
let projectSummary = "";
|
|
124
|
-
if (config.projectPath) {
|
|
125
|
-
const profile = await kv
|
|
126
|
-
.get<{ summary?: string }>(KV.profiles, config.projectPath)
|
|
127
|
-
.catch(() => null);
|
|
128
|
-
projectSummary = profile?.summary || "";
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const md = serializeToMemoryMd(
|
|
132
|
-
latestMemories,
|
|
133
|
-
projectSummary,
|
|
134
|
-
config.lineBudget,
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
const dir = dirname(config.memoryFilePath);
|
|
138
|
-
if (!existsSync(dir)) {
|
|
139
|
-
mkdirSync(dir, { recursive: true });
|
|
140
|
-
}
|
|
141
|
-
writeFileSync(config.memoryFilePath, md, "utf-8");
|
|
142
|
-
|
|
143
|
-
await recordAudit(kv, "export", "mem::claude-bridge-sync", [], {
|
|
144
|
-
path: config.memoryFilePath,
|
|
145
|
-
memoryCount: latestMemories.length,
|
|
146
|
-
lines: md.split("\n").length,
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
ctx.logger.info("Claude bridge: synced to MEMORY.md", {
|
|
150
|
-
path: config.memoryFilePath,
|
|
151
|
-
memories: latestMemories.length,
|
|
152
|
-
});
|
|
153
|
-
return { success: true, path: config.memoryFilePath, lines: md.split("\n").length };
|
|
154
|
-
} catch (err) {
|
|
155
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
156
|
-
ctx.logger.error("Claude bridge sync failed", { error: msg });
|
|
157
|
-
return { success: false, error: msg };
|
|
158
|
-
}
|
|
159
|
-
},
|
|
160
|
-
);
|
|
161
|
-
}
|