feat(canvas): Agent + Hubs + view-as personas (#1)
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
// Per-tenant agent memory (mini-hindsight). Stored in localStorage, keyed by
|
||||
// tenant + actor.user_id. Three fact kinds: world, experience, opinion.
|
||||
// Survives reloads; cleared on Settings → "Forget all".
|
||||
|
||||
export type FactKind = "world" | "experience" | "opinion";
|
||||
|
||||
export interface Fact {
|
||||
id: string;
|
||||
kind: FactKind;
|
||||
content: string;
|
||||
tags: string[];
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
const NS = "fm.mc.agent.mem.v1";
|
||||
|
||||
function key(tenant: string, userId: string): string {
|
||||
return `${NS}:${tenant}:${userId}`;
|
||||
}
|
||||
|
||||
function load(tenant: string, userId: string): Fact[] {
|
||||
if (typeof localStorage === "undefined") return [];
|
||||
try {
|
||||
const raw = localStorage.getItem(key(tenant, userId));
|
||||
if (!raw) return [];
|
||||
return JSON.parse(raw) as Fact[];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function save(tenant: string, userId: string, facts: Fact[]): void {
|
||||
if (typeof localStorage === "undefined") return;
|
||||
try {
|
||||
localStorage.setItem(key(tenant, userId), JSON.stringify(facts.slice(-500)));
|
||||
} catch {
|
||||
/* quota: drop silently */
|
||||
}
|
||||
}
|
||||
|
||||
export const agentMemory = {
|
||||
list(tenant: string, userId: string): Fact[] {
|
||||
return load(tenant, userId);
|
||||
},
|
||||
retain(tenant: string, userId: string, kind: FactKind, content: string, tags: string[] = []): Fact {
|
||||
const facts = load(tenant, userId);
|
||||
const fact: Fact = {
|
||||
id: `f_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
||||
kind,
|
||||
content: content.trim(),
|
||||
tags,
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
facts.push(fact);
|
||||
save(tenant, userId, facts);
|
||||
return fact;
|
||||
},
|
||||
recall(tenant: string, userId: string, query: string, limit = 8): Fact[] {
|
||||
const facts = load(tenant, userId);
|
||||
if (!query.trim()) return facts.slice(-limit).reverse();
|
||||
const q = query.toLowerCase();
|
||||
const tokens = q.split(/\s+/).filter(Boolean);
|
||||
const scored = facts.map((f) => {
|
||||
const hay = `${f.content} ${f.tags.join(" ")}`.toLowerCase();
|
||||
let score = 0;
|
||||
for (const t of tokens) {
|
||||
if (hay.includes(t)) score += 1;
|
||||
}
|
||||
return { f, score };
|
||||
});
|
||||
return scored
|
||||
.filter((s) => s.score > 0)
|
||||
.sort((a, b) => b.score - a.score || b.f.createdAt - a.f.createdAt)
|
||||
.slice(0, limit)
|
||||
.map((s) => s.f);
|
||||
},
|
||||
forget(tenant: string, userId: string): void {
|
||||
if (typeof localStorage === "undefined") return;
|
||||
try {
|
||||
localStorage.removeItem(key(tenant, userId));
|
||||
} catch {
|
||||
/* swallow */
|
||||
}
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user