@staix/agent-mem 0.20.3 → 0.21.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/plugin.json +1 -1
- package/README.md +4 -3
- package/package.json +2 -2
- package/src/cli.ts +3 -0
- package/src/hook-spool.ts +45 -3
- package/src/index.ts +25 -2
- package/src/memory/bridge.ts +66 -18
- package/src/memory/candidates.ts +71 -9
- package/src/memory/generation-index.ts +95 -18
- package/src/memory/generation-sync.ts +3 -0
- package/src/memory/generation.ts +33 -15
- package/src/memory/platform.ts +38 -3
- package/src/memory/policy.ts +28 -15
- package/src/memory/prune-publisher-lineage.ts +50 -12
- package/src/memory/public-prune-advance.ts +11 -5
- package/src/memory/public-prune-inputs.ts +17 -16
- package/src/memory/shard-sync-applications.ts +153 -0
- package/src/memory/shard-sync-format.ts +15 -0
- package/src/memory/shard-sync-graph.ts +6 -4
- package/src/memory/shard-sync-history.ts +238 -0
- package/src/memory/shard-sync.ts +67 -151
- package/src/memory/shard.ts +32 -6
- package/src/memory/store.ts +14 -1
- package/src/memory/sync-binding.ts +63 -0
- package/src/memory/sync-placement-active.ts +75 -0
- package/src/memory/sync-placement-cache.ts +32 -0
- package/src/memory/sync-placement-capture.ts +292 -0
- package/src/memory/sync-placement-dependencies.ts +255 -0
- package/src/memory/sync-placement-format.ts +182 -0
- package/src/memory/sync-placement-git.ts +95 -0
- package/src/memory/sync-placement-guard.ts +98 -0
- package/src/memory/sync-placement-inspection-evidence.ts +257 -0
- package/src/memory/sync-placement-inspection-pending.ts +66 -0
- package/src/memory/sync-placement-inspection.ts +236 -0
- package/src/memory/sync-placement-journal-format.ts +114 -0
- package/src/memory/sync-placement-journal-storage.ts +513 -0
- package/src/memory/sync-placement-journal.ts +153 -0
- package/src/memory/sync-placement-materialize.ts +149 -0
- package/src/memory/sync-placement-plan.ts +171 -0
- package/src/memory/sync-placement-projection.ts +79 -0
- package/src/memory/sync-placement-publication-plan.ts +189 -0
- package/src/memory/sync-placement-publication-storage.ts +87 -0
- package/src/memory/sync-placement-transport.ts +489 -0
- package/src/memory/sync-policy-inspection.ts +126 -0
- package/src/memory/sync-screen.ts +128 -33
- package/src/memory/sync.ts +20 -64
- package/src/ui/inbox-cache.ts +219 -0
- package/src/ui/inbox-worker-version.ts +14 -0
- package/src/ui/inbox-worker.ts +32 -0
- package/src/ui/inbox.ts +281 -0
- package/src/ui/panel.js +7 -3
- package/src/ui/plugin.ts +26 -53
package/src/ui/plugin.ts
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* Review writes run the same library calls as `amem review accept|reject` and `review walk`'s edit, stamped as the person.
|
|
5
5
|
*/
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
|
+
import { InboxCache } from "./inbox-cache.ts";
|
|
8
|
+
import { candidateText, candidateTargets as targetsOf, visibleCandidate as visible } from "./inbox.ts";
|
|
9
|
+
export { candidateText } from "./inbox.ts";
|
|
7
10
|
import { existsSync, readFileSync } from "node:fs";
|
|
8
11
|
import { join } from "node:path";
|
|
9
12
|
import { createMemoryMcp } from "../mcp-memory.ts";
|
|
@@ -35,14 +38,9 @@ type ActionResult =
|
|
|
35
38
|
| ({ ok: true; message?: string; undo?: { action: string } & Args } & Args)
|
|
36
39
|
| { ok: false; error: string }
|
|
37
40
|
| { ok: false; needsConfirm: true; preview: string[]; fingerprint: string; verb?: string; changes?: Change[]; warnings?: string[] };
|
|
38
|
-
interface InboxItem {
|
|
39
|
-
id: string; group: "candidate" | "alert"; kind: string; title: string; detail?: string; project?: string; at: string;
|
|
40
|
-
chip?: { text: string; tone: "ok" | "warn" | "err" | "neutral" };
|
|
41
|
-
verbs: { id: string; label: string; primary?: boolean; reason?: "optional" | "required" }[];
|
|
42
|
-
link: { panel: string; params?: Record<string, string> };
|
|
43
|
-
}
|
|
44
41
|
interface Provider {
|
|
45
42
|
assets: { js: string; css?: string };
|
|
43
|
+
close(): Promise<void>;
|
|
46
44
|
snapshot(input: { after: number; panel: string } & Args): Promise<Args>;
|
|
47
45
|
action(input: { action: string; confirmed?: true; fingerprint?: string } & Args): Promise<ActionResult>;
|
|
48
46
|
}
|
|
@@ -60,25 +58,12 @@ const sha = (v: unknown) => createHash("sha256").update(JSON.stringify(v)).diges
|
|
|
60
58
|
const errorText = (e: unknown) => (e instanceof Error ? e.message : String(e)).split("\n")[0]!;
|
|
61
59
|
const oneLine = (text: string, max = 160) => { const flat = text.replace(/\s+/g, " ").trim(); return flat.length > max ? `${flat.slice(0, max - 3)}...` : flat; };
|
|
62
60
|
|
|
63
|
-
/** What a candidate proposes, as text. */
|
|
64
|
-
export function candidateText(input: CandidateInput): string {
|
|
65
|
-
switch (input.kind) {
|
|
66
|
-
case "memory": return input.title ? `${input.title}: ${input.text}` : input.text;
|
|
67
|
-
case "fail": return `Approach: ${input.approach}\nSymptom: ${input.symptom}\nCause: ${input.cause}\nRetry when: ${input.retryWhen}`;
|
|
68
|
-
case "correction": return `${input.proposedText}\n\nReason: ${input.reason}`;
|
|
69
|
-
case "decay": return input.reason;
|
|
70
|
-
case "promotion": return `Promote mem:${input.target.id} to long-term for goal:${input.goalId}`;
|
|
71
|
-
case "consolidation": return `${input.action}: ${input.text}`;
|
|
72
|
-
case "model-answer": return input.text;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
61
|
/** The text `review walk`'s edit opens for a memory or fail candidate. */
|
|
76
62
|
function editText(input: CandidateInput): string | null {
|
|
77
63
|
if (input.kind === "fail") return `Approach: ${input.approach}\nSymptom: ${input.symptom}\nCause: ${input.cause}\nEvidence: ${input.evidenceRefs.join(", ")}\nRetry when: ${input.retryWhen}\n`;
|
|
78
64
|
if (input.kind !== "memory") return null;
|
|
79
65
|
return input.noteKind === "fail" && !hasFailLabel(input.text) ? `Approach: ${input.text}\nSymptom: \nCause: \nEvidence: \nRetry when: \n` : input.text;
|
|
80
66
|
}
|
|
81
|
-
const targetsOf = (input: CandidateInput) => "targets" in input ? input.targets : "target" in input && input.target ? [input.target] : [];
|
|
82
67
|
|
|
83
68
|
/** The CLI's curatedStore on an explicit home (src/cli.ts keeps its own copy; cli.ts is not a library module). */
|
|
84
69
|
function openCurated(base: string, project: string, opts: StoreOptions, reconciled = true): MemoryStore {
|
|
@@ -154,7 +139,8 @@ export const plugin = {
|
|
|
154
139
|
const pick = (args: Args, list: string[]) => {
|
|
155
140
|
const asked = str(args.project), key = asked ? resolveAlias(base, asked) : undefined;
|
|
156
141
|
if (key && !list.includes(key) && existsSync(base) && exists(key)) list.push(key);
|
|
157
|
-
if (key && list.includes(key))
|
|
142
|
+
if (key && !list.includes(key)) return undefined;
|
|
143
|
+
if (key) chosen = key;
|
|
158
144
|
return chosen && list.includes(chosen) ? chosen : list[0];
|
|
159
145
|
};
|
|
160
146
|
|
|
@@ -176,16 +162,6 @@ export const plugin = {
|
|
|
176
162
|
try { return await mcp.tools[tool](forward(tool, args)); } finally { mcp.close(); }
|
|
177
163
|
}
|
|
178
164
|
|
|
179
|
-
/** The CLI's rule for agent-marked review, applied to the person here: the console shows only what passes the screen, targets included. */
|
|
180
|
-
const visible = (candidate: Candidate, project: string, store: MemoryStore | undefined, p: StandalonePolicy): boolean => {
|
|
181
|
-
if (!p.screenStandalone(candidate)) return false;
|
|
182
|
-
const input = candidate.input, targets = targetsOf(input);
|
|
183
|
-
if (!targets.length && input.kind !== "promotion") return true;
|
|
184
|
-
if (!store) return false;
|
|
185
|
-
if (targets.some(t => { const view = store.get(t.id, project); return !view || view.project !== project || !p.screenStandalone(view); })) return false;
|
|
186
|
-
if (input.kind === "promotion") { const goal = store.goalOf(input.goalId, project); if (!goal || !p.screenStandalone(goal)) return false; }
|
|
187
|
-
return true;
|
|
188
|
-
};
|
|
189
165
|
const projectOf = new Map<string, string>();
|
|
190
166
|
async function pending(project: string, detail: boolean) {
|
|
191
167
|
if (!hasCandidates()) return { rows: [], hidden: 0 };
|
|
@@ -218,26 +194,7 @@ export const plugin = {
|
|
|
218
194
|
if (!await policy().screen(report)) return { withheld: true as const };
|
|
219
195
|
return { report, applyNeeded: report.current.capMb !== report.proposal.capMb || report.current.retentionDays !== report.proposal.retentionDays };
|
|
220
196
|
}
|
|
221
|
-
|
|
222
|
-
const firstSeen = new Map<string, string>();
|
|
223
|
-
const since = (id: string) => { const at = firstSeen.get(id) ?? new Date().toISOString(); firstSeen.set(id, at); return at; };
|
|
224
|
-
async function inbox(): Promise<InboxItem[]> {
|
|
225
|
-
const items: InboxItem[] = [];
|
|
226
|
-
for (const project of projects()) {
|
|
227
|
-
for (const row of (await pending(project, false)).rows) items.push({
|
|
228
|
-
id: row.id, group: "candidate", kind: row.kind, title: oneLine(row.text, 120), detail: `${row.kind} candidate in ${project}`, project, at: row.createdAt,
|
|
229
|
-
verbs: [...row.acceptable ? [{ id: "accept", label: "Accept", primary: true }] : [], { id: "reject", label: "Reject", reason: "optional" as const }],
|
|
230
|
-
link: { panel: "review", params: { project, id: row.id } },
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
const cap = await capacity();
|
|
234
|
-
if (cap && "report" in cap && cap.applyNeeded) {
|
|
235
|
-
const { current, proposal } = cap.report;
|
|
236
|
-
items.push({ id: "capacity:proposal", group: "alert", kind: "capacity", title: `Capture capacity: ${current.capMb} MB / ${current.retentionDays} d, proposed ${proposal.capMb} MB / ${proposal.retentionDays} d`,
|
|
237
|
-
detail: proposal.reason, at: since("capacity:proposal"), chip: { text: "proposal", tone: "warn" }, verbs: [{ id: "capacity_apply", label: "Apply..." }], link: { panel: "capacity" } });
|
|
238
|
-
} else firstSeen.delete("capacity:proposal");
|
|
239
|
-
return items;
|
|
240
|
-
}
|
|
197
|
+
const inboxCache = new InboxCache(base, { log: ctx.log });
|
|
241
198
|
|
|
242
199
|
function searchHit(hit: Args, project: string, q: string) {
|
|
243
200
|
const view = hit.view as View | undefined;
|
|
@@ -440,10 +397,16 @@ export const plugin = {
|
|
|
440
397
|
|
|
441
398
|
return {
|
|
442
399
|
assets: { js: asset("./panel.js"), css: asset("./panel.css") },
|
|
400
|
+
close: () => inboxCache.close(),
|
|
443
401
|
async snapshot(input) {
|
|
444
402
|
const args = input as Args;
|
|
445
|
-
if (input.panel === "inbox")
|
|
403
|
+
if (input.panel === "inbox") {
|
|
404
|
+
const snapshot = await inboxCache.snapshot();
|
|
405
|
+
for (const item of snapshot.items) if (item.group === "candidate" && item.link.params?.project) projectOf.set(item.id, item.link.params.project);
|
|
406
|
+
return { ...snapshot };
|
|
407
|
+
}
|
|
446
408
|
const list = projects(), project = pick(args, list);
|
|
409
|
+
if (str(args.project) && !project) return { projects: list, project: null, error: "The requested memory project is unavailable; choose an active project from the project selector." };
|
|
447
410
|
if (input.panel === "search") {
|
|
448
411
|
const q = str(args.q);
|
|
449
412
|
if (!q || !project) return { hits: [], total: 0 };
|
|
@@ -461,6 +424,17 @@ export const plugin = {
|
|
|
461
424
|
}
|
|
462
425
|
},
|
|
463
426
|
async action(input) {
|
|
427
|
+
const result = await performAction(input);
|
|
428
|
+
if (result.ok) {
|
|
429
|
+
const id = str(input.id);
|
|
430
|
+
const hard = ["capacity_apply", "project_forget", "prune", "decay_apply", "accept_undo"].includes(input.action);
|
|
431
|
+
const removes = ["accept", "reject", "edit_accept"].includes(input.action);
|
|
432
|
+
inboxCache.invalidate({ hard, capacity: hard || input.action === "gc", ...(removes && id ? { removeIds: [id] } : {}), ...(input.action === "reject_undo" ? { added: 1 } : {}) });
|
|
433
|
+
}
|
|
434
|
+
return result;
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
async function performAction(input: { action: string; confirmed?: true; fingerprint?: string } & Args): Promise<ActionResult> {
|
|
464
438
|
const args = input as Args;
|
|
465
439
|
try {
|
|
466
440
|
switch (input.action) {
|
|
@@ -499,8 +473,7 @@ export const plugin = {
|
|
|
499
473
|
ctx.log(`amem: ${input.action} failed: ${errorText(e)}`);
|
|
500
474
|
return { ok: false, error: errorText(e) };
|
|
501
475
|
}
|
|
502
|
-
|
|
503
|
-
};
|
|
476
|
+
}
|
|
504
477
|
},
|
|
505
478
|
};
|
|
506
479
|
|