@staix/agent-mem 0.20.3 → 0.22.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 +30 -3
- package/package.json +2 -2
- package/src/cli.ts +36 -3
- package/src/commands-table.ts +3 -0
- package/src/hook-spool.ts +45 -3
- package/src/index.ts +31 -2
- package/src/memory/bridge.ts +66 -18
- package/src/memory/candidates.ts +71 -9
- package/src/memory/commands.ts +3 -2
- 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-activation.ts +4 -4
- 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 +66 -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-cadence.ts +2 -1
- package/src/memory/sync-placement-abandon.ts +142 -0
- package/src/memory/sync-placement-activation-locks.ts +28 -0
- package/src/memory/sync-placement-activation-path.ts +2 -0
- package/src/memory/sync-placement-activation-proof.ts +68 -0
- package/src/memory/sync-placement-activation-storage.ts +167 -0
- package/src/memory/sync-placement-activation.ts +185 -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 +373 -0
- package/src/memory/sync-placement-dependencies.ts +255 -0
- package/src/memory/sync-placement-effective-writers.ts +84 -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 +104 -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 +240 -0
- package/src/memory/sync-placement-journal-format.ts +114 -0
- package/src/memory/sync-placement-journal-storage.ts +557 -0
- package/src/memory/sync-placement-journal.ts +160 -0
- package/src/memory/sync-placement-materialize.ts +149 -0
- package/src/memory/sync-placement-plan.ts +171 -0
- package/src/memory/sync-placement-prerequisites.ts +50 -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 +500 -0
- package/src/memory/sync-placement-writer-history.ts +278 -0
- package/src/memory/sync-placement-writer-renewal.ts +179 -0
- package/src/memory/sync-placement-writer-revision-format.ts +122 -0
- package/src/memory/sync-placement-writer-roots.ts +167 -0
- package/src/memory/sync-placement-writers.ts +135 -0
- package/src/memory/sync-policy-inspection.ts +126 -0
- package/src/memory/sync-screen.ts +128 -33
- package/src/memory/sync.ts +19 -64
- package/src/setup.ts +101 -14
- package/src/sync-placement-writer-context.ts +136 -0
- 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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { canonicalJSON, hashJSON } from "./generation.ts";
|
|
2
|
+
import { memoryPolicySnapshot } from "./policy.ts";
|
|
3
|
+
import { PLACEMENT_FORMAT } from "./sync-placement-format.ts";
|
|
4
|
+
import type { SyncPolicyCategory } from "./sync-screen.ts";
|
|
5
|
+
|
|
6
|
+
export const PLACEMENT_EPOCH_SCHEMA = "agent-mem.sync-placement-epoch/v1";
|
|
7
|
+
export const PLACEMENT_RECORD_SCHEMA = "agent-mem.sync-placement-record/v1";
|
|
8
|
+
export const PLACEMENT_ACTIVE_FENCE_SCHEMA = "agent-mem.sync-placement-fence/v1";
|
|
9
|
+
export const PLACEMENT_JOURNAL_MAX_RECORDS = 20_000, PLACEMENT_JOURNAL_MAX_OBJECTS = 256;
|
|
10
|
+
export const PLACEMENT_JOURNAL_CHUNK_MAX_BYTES = 64 * 1024, PLACEMENT_JOURNAL_ROOT_MAX_BYTES = 256 * 1024;
|
|
11
|
+
export type SyncPlacementJournalErrorCode = "invalid" | "corrupt" | "bound" | "conflict" | "pending" | "changed" | "authorization" | "policy" | "local" | "busy";
|
|
12
|
+
const messages: Record<SyncPlacementJournalErrorCode, string> = {
|
|
13
|
+
invalid: "Sync placement journal data is invalid or unsupported; inspect the retained state and use a compatible runtime.",
|
|
14
|
+
corrupt: "Sync placement journal evidence is incomplete or inconsistent; restore the exact retained evidence before continuing.",
|
|
15
|
+
bound: "Sync placement journal exceeds its supported bounds; a person-reviewed bounded epoch transition is required.",
|
|
16
|
+
conflict: "Sync placement journal identity or request conflicts with retained evidence; inspect the exact request before retrying.",
|
|
17
|
+
pending: "Sync placement journal has an unfinished transaction; resume its exact recorded request before continuing.",
|
|
18
|
+
changed: "Sync placement journal or source bindings changed; capture and review a fresh request before continuing.",
|
|
19
|
+
authorization: "Sync placement change requires explicit person authorization of the exact reviewed request.",
|
|
20
|
+
policy: "Current mandatory policy refuses this sync placement change; inspect its safe policy classification.",
|
|
21
|
+
local: "An intrinsically local source cannot be released for publication; preserve its source placement.",
|
|
22
|
+
busy: "Sync placement journal is busy or its owner is unavailable; wait for the existing transaction before retrying.",
|
|
23
|
+
};
|
|
24
|
+
export class SyncPlacementJournalError extends Error {
|
|
25
|
+
readonly code: SyncPlacementJournalErrorCode;
|
|
26
|
+
constructor(code: SyncPlacementJournalErrorCode = "invalid") {
|
|
27
|
+
const safe = typeof code === "string" && Object.hasOwn(messages, code) ? code : "invalid";
|
|
28
|
+
super(messages[safe]); this.code = safe; this.name = "SyncPlacementJournalError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export interface PlacementEpoch {
|
|
32
|
+
schema: typeof PLACEMENT_EPOCH_SCHEMA; publicationFormat: typeof PLACEMENT_FORMAT;
|
|
33
|
+
generation: string; homeHash: string; contextHash: string; nonce: string;
|
|
34
|
+
}
|
|
35
|
+
export interface PlacementActiveFence { schema: typeof PLACEMENT_ACTIVE_FENCE_SCHEMA; epochHash: string; homeHash: string; generation: string }
|
|
36
|
+
export type PlacementRealm = { scope: "user" } | { scope: "project"; keyHash: string };
|
|
37
|
+
export interface PlacementKey { realm: PlacementRealm; id: string; rowHash: string }
|
|
38
|
+
export type PlacementHoldReason = SyncPolicyCategory | "person-local" | "related-local";
|
|
39
|
+
export interface PlacementAmbiguousOutcome { attemptHash: string; remoteHash: string; refHash: string }
|
|
40
|
+
export interface PlacementCommittedOutcome extends PlacementAmbiguousOutcome { commit: string; proofHash: string }
|
|
41
|
+
interface EventFields { requestId: string; key: PlacementKey; sourcePlacement: "local" | "synced"; effectHash: string }
|
|
42
|
+
export type PlacementEvent = EventFields & (
|
|
43
|
+
{ kind: "hold"; reason: PlacementHoldReason } | { kind: "release" }
|
|
44
|
+
| { kind: "publication-committed"; outcome: PlacementCommittedOutcome }
|
|
45
|
+
| { kind: "publication-ambiguous"; outcome: PlacementAmbiguousOutcome }
|
|
46
|
+
);
|
|
47
|
+
export type PlacementRecord = PlacementEvent & { schema: typeof PLACEMENT_RECORD_SCHEMA; epochHash: string; sequence: number; previousHash: string; seal: string };
|
|
48
|
+
const eventFields = ["requestId", "key", "sourcePlacement", "effectHash", "kind"];
|
|
49
|
+
const reasons: readonly PlacementHoldReason[] = ["secret", "personal-data", "deny-path", "private-or-invalid", "host", "person-local", "related-local"];
|
|
50
|
+
const fail = (code: SyncPlacementJournalErrorCode = "invalid"): never => { throw new SyncPlacementJournalError(code); };
|
|
51
|
+
const hex = (value: unknown, length: number): value is string => typeof value === "string" && value.length === length && /^[a-f0-9]+$/.test(value);
|
|
52
|
+
function object(value: unknown, required: readonly string[], optional: readonly string[] = []): Record<string, unknown> {
|
|
53
|
+
// Shared descriptor-only snapshot rejects proxies, getters, functions, private flags and unbounded structure without running caller code.
|
|
54
|
+
const copy = memoryPolicySnapshot(value);
|
|
55
|
+
if (!copy || typeof copy !== "object" || Array.isArray(copy)) return fail();
|
|
56
|
+
const keys = Object.keys(copy), allowed = new Set([...required, ...optional]);
|
|
57
|
+
if (keys.length < required.length || keys.length > allowed.size || required.some(key => !Object.hasOwn(copy, key)) || keys.some(key => !allowed.has(key))) return fail();
|
|
58
|
+
return copy as Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
function bounded<T>(value: T): T { if (Buffer.byteLength(canonicalJSON(value) + "\n") > PLACEMENT_JOURNAL_CHUNK_MAX_BYTES) return fail("bound"); return value; }
|
|
61
|
+
export function validatePlacementEpoch(value: unknown): PlacementEpoch {
|
|
62
|
+
const row = object(value, ["schema", "publicationFormat", "generation", "homeHash", "contextHash", "nonce"]);
|
|
63
|
+
if (row.schema !== PLACEMENT_EPOCH_SCHEMA || row.publicationFormat !== PLACEMENT_FORMAT || !hex(row.generation, 32) || !hex(row.homeHash, 64) || !hex(row.contextHash, 64) || !hex(row.nonce, 32)) return fail();
|
|
64
|
+
return bounded<PlacementEpoch>({ schema: PLACEMENT_EPOCH_SCHEMA, publicationFormat: PLACEMENT_FORMAT, generation: row.generation, homeHash: row.homeHash, contextHash: row.contextHash, nonce: row.nonce });
|
|
65
|
+
}
|
|
66
|
+
export function validatePlacementActiveFence(value: unknown): PlacementActiveFence {
|
|
67
|
+
const row = object(value, ["schema", "epochHash", "homeHash", "generation"]);
|
|
68
|
+
if (row.schema !== PLACEMENT_ACTIVE_FENCE_SCHEMA || !hex(row.epochHash, 64) || !hex(row.homeHash, 64) || !hex(row.generation, 32)) return fail();
|
|
69
|
+
return bounded<PlacementActiveFence>({ schema: PLACEMENT_ACTIVE_FENCE_SCHEMA, epochHash: row.epochHash, homeHash: row.homeHash, generation: row.generation });
|
|
70
|
+
}
|
|
71
|
+
export function validatePlacementKey(value: unknown): PlacementKey {
|
|
72
|
+
const row = object(value, ["realm", "id", "rowHash"]), realm = object(row.realm, ["scope"], ["keyHash"]);
|
|
73
|
+
if (!hex(row.id, 32) || !hex(row.rowHash, 64)) return fail();
|
|
74
|
+
if (realm.scope === "user" && !Object.hasOwn(realm, "keyHash")) return { realm: { scope: "user" }, id: row.id, rowHash: row.rowHash };
|
|
75
|
+
if (realm.scope === "project" && hex(realm.keyHash, 64)) return { realm: { scope: "project", keyHash: realm.keyHash }, id: row.id, rowHash: row.rowHash };
|
|
76
|
+
return fail();
|
|
77
|
+
}
|
|
78
|
+
function outcome(value: unknown, committed: boolean): PlacementAmbiguousOutcome | PlacementCommittedOutcome {
|
|
79
|
+
const row = object(value, ["attemptHash", "remoteHash", "refHash", ...(committed ? ["commit", "proofHash"] : [])]);
|
|
80
|
+
if (!hex(row.attemptHash, 64) || !hex(row.remoteHash, 64) || !hex(row.refHash, 64)) return fail();
|
|
81
|
+
const base = { attemptHash: row.attemptHash, remoteHash: row.remoteHash, refHash: row.refHash };
|
|
82
|
+
if (!committed) return base;
|
|
83
|
+
if (!hex(row.commit, 40) || !hex(row.proofHash, 64)) return fail();
|
|
84
|
+
return { ...base, commit: row.commit, proofHash: row.proofHash };
|
|
85
|
+
}
|
|
86
|
+
export function validatePlacementEvent(value: unknown): PlacementEvent {
|
|
87
|
+
const row = object(value, eventFields, ["reason", "outcome"]);
|
|
88
|
+
if (!hex(row.requestId, 64) || !hex(row.effectHash, 64) || !["local", "synced"].includes(row.sourcePlacement as string)) return fail();
|
|
89
|
+
const base: EventFields = { requestId: row.requestId, key: validatePlacementKey(row.key), sourcePlacement: row.sourcePlacement as "local" | "synced", effectHash: row.effectHash };
|
|
90
|
+
if (row.kind === "hold" && !Object.hasOwn(row, "outcome") && typeof row.reason === "string" && reasons.includes(row.reason as PlacementHoldReason)) return bounded<PlacementEvent>({ ...base, kind: "hold", reason: row.reason as PlacementHoldReason });
|
|
91
|
+
if (Object.hasOwn(row, "reason")) return fail();
|
|
92
|
+
if (row.kind === "release" && !Object.hasOwn(row, "outcome")) return bounded<PlacementEvent>({ ...base, kind: "release" });
|
|
93
|
+
if (row.kind === "publication-committed") return bounded<PlacementEvent>({ ...base, kind: row.kind, outcome: outcome(row.outcome, true) as PlacementCommittedOutcome });
|
|
94
|
+
if (row.kind === "publication-ambiguous") return bounded<PlacementEvent>({ ...base, kind: row.kind, outcome: outcome(row.outcome, false) });
|
|
95
|
+
return fail();
|
|
96
|
+
}
|
|
97
|
+
export function validatePlacementRecord(value: unknown): PlacementRecord {
|
|
98
|
+
const row = object(value, ["schema", "epochHash", "sequence", "previousHash", "seal", ...eventFields], ["reason", "outcome"]);
|
|
99
|
+
if (row.schema !== PLACEMENT_RECORD_SCHEMA || !hex(row.epochHash, 64) || !hex(row.previousHash, 64) || !hex(row.seal, 64)
|
|
100
|
+
|| typeof row.sequence !== "number" || !Number.isSafeInteger(row.sequence) || row.sequence < 1) return fail();
|
|
101
|
+
if (row.sequence > PLACEMENT_JOURNAL_MAX_RECORDS) return fail("bound");
|
|
102
|
+
const rawEvent: Record<string, unknown> = {}; for (const field of [...eventFields, "reason", "outcome"]) if (Object.hasOwn(row, field)) rawEvent[field] = row[field];
|
|
103
|
+
const event = validatePlacementEvent(rawEvent), body = { ...event, schema: PLACEMENT_RECORD_SCHEMA as typeof PLACEMENT_RECORD_SCHEMA, epochHash: row.epochHash, sequence: row.sequence, previousHash: row.previousHash };
|
|
104
|
+
if (row.sequence === 1 && row.previousHash !== row.epochHash || hashJSON(body) !== row.seal) return fail("corrupt");
|
|
105
|
+
return bounded<PlacementRecord>({ ...body, seal: row.seal });
|
|
106
|
+
}
|
|
107
|
+
export function createPlacementRecord(epochHash: string, sequence: number, previousHash: string, value: PlacementEvent): PlacementRecord {
|
|
108
|
+
if (!hex(epochHash, 64) || !hex(previousHash, 64) || typeof sequence !== "number" || !Number.isSafeInteger(sequence) || sequence < 1) return fail();
|
|
109
|
+
if (sequence > PLACEMENT_JOURNAL_MAX_RECORDS) return fail("bound");
|
|
110
|
+
const event = validatePlacementEvent(value), body = { ...event, schema: PLACEMENT_RECORD_SCHEMA, epochHash, sequence, previousHash };
|
|
111
|
+
return validatePlacementRecord({ ...body, seal: hashJSON(body) });
|
|
112
|
+
}
|
|
113
|
+
export const placementEpochHash = (epoch: PlacementEpoch): string => hashJSON(validatePlacementEpoch(epoch));
|
|
114
|
+
export const placementKeyId = (key: PlacementKey): string => hashJSON(validatePlacementKey(key));
|