@hasna/mementos 0.15.2 → 0.17.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/README.md +83 -22
- package/dist/audit-contract.d.ts +87 -0
- package/dist/audit-contract.d.ts.map +1 -0
- package/dist/cli/commands/agent.d.ts.map +1 -1
- package/dist/cli/commands/consolidation.d.ts.map +1 -1
- package/dist/cli/commands/info-context.d.ts.map +1 -1
- package/dist/cli/commands/info-history.d.ts.map +1 -1
- package/dist/cli/commands/io-backup.d.ts.map +1 -1
- package/dist/cli/commands/io-export.d.ts.map +1 -1
- package/dist/cli/commands/io-restore.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-list.d.ts +25 -0
- package/dist/cli/commands/memory-cmd-list.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-recall.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-remove.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-search.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-view.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-when-to-use.d.ts.map +1 -1
- package/dist/cli/commands/project.d.ts.map +1 -1
- package/dist/cli/commands/system-session.d.ts.map +1 -1
- package/dist/cli/commands/system-status.d.ts.map +1 -1
- package/dist/cli/commands/system-synthesized-profile.d.ts.map +1 -1
- package/dist/cli/helpers.d.ts +3 -1
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/index.js +2482 -823
- package/dist/cli/structured-json.d.ts +54 -0
- package/dist/cli/structured-json.d.ts.map +1 -0
- package/dist/db/api-mode.d.ts +10 -0
- package/dist/db/api-mode.d.ts.map +1 -1
- package/dist/db/api-response-contract.d.ts +23 -0
- package/dist/db/api-response-contract.d.ts.map +1 -0
- package/dist/db/audit.d.ts +34 -45
- package/dist/db/audit.d.ts.map +1 -1
- package/dist/db/database.d.ts +1 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/locks.d.ts.map +1 -1
- package/dist/db/machines.d.ts +50 -13
- package/dist/db/machines.d.ts.map +1 -1
- package/dist/db/memories.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/pg-migrate.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/session-jobs.d.ts +2 -0
- package/dist/db/session-jobs.d.ts.map +1 -1
- package/dist/diagnostics/historical-project-registration-receipt.js +215 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1523 -206
- package/dist/lib/built-in-hooks.d.ts.map +1 -1
- package/dist/lib/conversations-transport.d.ts +21 -0
- package/dist/lib/conversations-transport.d.ts.map +1 -0
- package/dist/lib/export-v1.d.ts +2 -0
- package/dist/lib/export-v1.d.ts.map +1 -1
- package/dist/lib/injection-project.d.ts +10 -0
- package/dist/lib/injection-project.d.ts.map +1 -0
- package/dist/lib/injector.d.ts +1 -0
- package/dist/lib/injector.d.ts.map +1 -1
- package/dist/lib/machine-visibility.d.ts.map +1 -1
- package/dist/lib/memory-lock.d.ts.map +1 -1
- package/dist/lib/profile-synthesizer.d.ts +13 -2
- package/dist/lib/profile-synthesizer.d.ts.map +1 -1
- package/dist/lib/session-queue.d.ts.map +1 -1
- package/dist/lib/session-registry.d.ts +38 -1
- package/dist/lib/session-registry.d.ts.map +1 -1
- package/dist/lib/storage-sync.d.ts +5 -0
- package/dist/lib/storage-sync.d.ts.map +1 -1
- package/dist/lib/synthesis/index.d.ts.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +3857 -1600
- package/dist/mcp/memory-broadcast.d.ts +6 -8
- package/dist/mcp/memory-broadcast.d.ts.map +1 -1
- package/dist/mcp/profile.d.ts +23 -0
- package/dist/mcp/profile.d.ts.map +1 -0
- package/dist/mcp/tools/bounded-output.d.ts +27 -0
- package/dist/mcp/tools/bounded-output.d.ts.map +1 -0
- package/dist/mcp/tools/lock-tools.d.ts.map +1 -1
- package/dist/mcp/tools/memory-audit.d.ts +1 -1
- package/dist/mcp/tools/memory-audit.d.ts.map +1 -1
- package/dist/mcp/tools/memory-crud.d.ts.map +1 -1
- package/dist/mcp/tools/memory-inject.d.ts.map +1 -1
- package/dist/mcp/tools/memory-io.d.ts.map +1 -1
- package/dist/mcp/tools/project-tools.d.ts.map +1 -1
- package/dist/mcp/tools/session-tools.d.ts.map +1 -1
- package/dist/mcp/tools/tool-registry.d.ts +22 -19
- package/dist/mcp/tools/tool-registry.d.ts.map +1 -1
- package/dist/mcp/tools/utility-tools.d.ts +2 -1
- package/dist/mcp/tools/utility-tools.d.ts.map +1 -1
- package/dist/project-registration.js +276 -20
- package/dist/sdk/index.d.ts +110 -7
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +746 -28
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3195 -1134
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/routes/audit.d.ts +2 -0
- package/dist/server/routes/audit.d.ts.map +1 -0
- package/dist/server/routes/machines.d.ts +2 -0
- package/dist/server/routes/machines.d.ts.map +1 -0
- package/dist/server/routes/system-sessions.d.ts.map +1 -1
- package/dist/server/routes/system-synthesis.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/hasna.contract.json +2 -2
- package/package.json +8 -5
- package/dist/cli/__fixtures__/clean-fallback-stub-server.d.ts +0 -2
- package/dist/cli/__fixtures__/clean-fallback-stub-server.d.ts.map +0 -1
- package/dist/cli/__fixtures__/io-restore-stub-server.d.ts +0 -2
- package/dist/cli/__fixtures__/io-restore-stub-server.d.ts.map +0 -1
- package/dist/db/__fixtures__/fail-closed-stub-server.d.ts +0 -2
- package/dist/db/__fixtures__/fail-closed-stub-server.d.ts.map +0 -1
- package/dist/db/__fixtures__/list-filter-capture-server.d.ts +0 -2
- package/dist/db/__fixtures__/list-filter-capture-server.d.ts.map +0 -1
- package/dist/db/__fixtures__/list-filter-client-runner.d.ts +0 -2
- package/dist/db/__fixtures__/list-filter-client-runner.d.ts.map +0 -1
- package/dist/test-support/memories-page-stub-server.d.ts +0 -2
- package/dist/test-support/memories-page-stub-server.d.ts.map +0 -1
- package/dist/test-support/memories-page-stub.d.ts +0 -28
- package/dist/test-support/memories-page-stub.d.ts.map +0 -1
- package/dist/test-support/pg-sync-stub-worker.d.ts +0 -2
- package/dist/test-support/pg-sync-stub-worker.d.ts.map +0 -1
- package/dist/test-support/preload-local-store.d.ts +0 -2
- package/dist/test-support/preload-local-store.d.ts.map +0 -1
- package/dist/test-support/project-authority-identity.d.ts +0 -8
- package/dist/test-support/project-authority-identity.d.ts.map +0 -1
- package/dist/test-support/store-isolation.d.ts +0 -147
- package/dist/test-support/store-isolation.d.ts.map +0 -1
- package/dist/test-support/strip-ansi.d.ts +0 -3
- package/dist/test-support/strip-ansi.d.ts.map +0 -1
package/dist/sdk/index.js
CHANGED
|
@@ -1,5 +1,274 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
//
|
|
2
|
+
// src/audit-contract.ts
|
|
3
|
+
var AUDIT_TRAIL_CONTRACT = "mementos.audit.trail.v1";
|
|
4
|
+
var AUDIT_EXPORT_CONTRACT = "mementos.audit.export.v1";
|
|
5
|
+
var AUDIT_STATS_CONTRACT = "mementos.audit.stats.v1";
|
|
6
|
+
var AUDIT_DEFAULT_LIMIT = 50;
|
|
7
|
+
var AUDIT_OPERATIONS = [
|
|
8
|
+
"create",
|
|
9
|
+
"update",
|
|
10
|
+
"delete",
|
|
11
|
+
"archive",
|
|
12
|
+
"restore",
|
|
13
|
+
"read"
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
class AuditContractError extends Error {
|
|
17
|
+
code = "MEMENTOS_AUDIT_CONTRACT";
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "AuditContractError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
var ISO_UTC = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
24
|
+
var MD5_HEX = /^[0-9a-f]{32}$/;
|
|
25
|
+
var ENTRY_KEYS = new Set([
|
|
26
|
+
"id",
|
|
27
|
+
"memory_id",
|
|
28
|
+
"memory_key",
|
|
29
|
+
"operation",
|
|
30
|
+
"agent_id",
|
|
31
|
+
"old_value_hash",
|
|
32
|
+
"new_value_hash",
|
|
33
|
+
"changes",
|
|
34
|
+
"created_at"
|
|
35
|
+
]);
|
|
36
|
+
var PAGE_KEYS = new Set([
|
|
37
|
+
"contract",
|
|
38
|
+
"entries",
|
|
39
|
+
"count",
|
|
40
|
+
"total",
|
|
41
|
+
"limit",
|
|
42
|
+
"cursor",
|
|
43
|
+
"next_cursor",
|
|
44
|
+
"consumed",
|
|
45
|
+
"has_more",
|
|
46
|
+
"complete",
|
|
47
|
+
"snapshot_at",
|
|
48
|
+
"filters",
|
|
49
|
+
"sort"
|
|
50
|
+
]);
|
|
51
|
+
function object(value, label) {
|
|
52
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
53
|
+
throw new AuditContractError(`${label} must be a JSON object`);
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
function exactKeys(value, allowed, label) {
|
|
58
|
+
const unexpected = Object.keys(value).filter((key) => !allowed.has(key));
|
|
59
|
+
if (unexpected.length)
|
|
60
|
+
throw new AuditContractError(`${label} has unexpected field '${unexpected[0]}'`);
|
|
61
|
+
}
|
|
62
|
+
function string(value, label, options = {}) {
|
|
63
|
+
if (options.nullable && value === null)
|
|
64
|
+
return null;
|
|
65
|
+
if (typeof value !== "string" || value.length === 0 || value.length > (options.max ?? 2048) || /[\u0000-\u001f\u007f]/.test(value)) {
|
|
66
|
+
throw new AuditContractError(`${label} must be a non-empty printable string of at most ${options.max ?? 2048} characters`);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
function cursorString(value, label) {
|
|
71
|
+
if (value === null)
|
|
72
|
+
return null;
|
|
73
|
+
if (typeof value !== "string" || !value || value.length > 4096 || !/^[A-Za-z0-9_-]+$/.test(value)) {
|
|
74
|
+
throw new AuditContractError(`${label} must be a bounded base64url cursor or null`);
|
|
75
|
+
}
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
function integer(value, label, minimum = 0) {
|
|
79
|
+
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
80
|
+
throw new AuditContractError(`${label} must be a safe integer >= ${minimum}`);
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
function boolean(value, label) {
|
|
85
|
+
if (typeof value !== "boolean")
|
|
86
|
+
throw new AuditContractError(`${label} must be a boolean`);
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
function canonicalAuditTimestamp(value, label) {
|
|
90
|
+
if (value instanceof Date)
|
|
91
|
+
value = value.toISOString();
|
|
92
|
+
if (typeof value !== "string")
|
|
93
|
+
throw new AuditContractError(`${label} must be a canonical UTC timestamp`);
|
|
94
|
+
const candidate = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{1,3})?$/.test(value) ? `${value.replace(" ", "T")}${value.includes(".") ? "" : ".000"}Z` : value;
|
|
95
|
+
if (!ISO_UTC.test(candidate))
|
|
96
|
+
throw new AuditContractError(`${label} must use YYYY-MM-DDTHH:mm:ss.sssZ`);
|
|
97
|
+
const parsed = new Date(candidate);
|
|
98
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== candidate) {
|
|
99
|
+
throw new AuditContractError(`${label} must be a real calendar timestamp`);
|
|
100
|
+
}
|
|
101
|
+
return candidate;
|
|
102
|
+
}
|
|
103
|
+
function auditOperation(value, label = "operation") {
|
|
104
|
+
if (typeof value !== "string" || !AUDIT_OPERATIONS.includes(value)) {
|
|
105
|
+
throw new AuditContractError(`${label} must be one of: ${AUDIT_OPERATIONS.join(", ")}`);
|
|
106
|
+
}
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
function nullableHash(value, label) {
|
|
110
|
+
if (value === null)
|
|
111
|
+
return null;
|
|
112
|
+
if (typeof value !== "string" || !MD5_HEX.test(value)) {
|
|
113
|
+
throw new AuditContractError(`${label} must be a lowercase md5 hex digest or null`);
|
|
114
|
+
}
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
function record(value, label) {
|
|
118
|
+
return object(value, label);
|
|
119
|
+
}
|
|
120
|
+
function validateAuditEntry(value, label = "audit entry") {
|
|
121
|
+
const row = object(value, label);
|
|
122
|
+
exactKeys(row, ENTRY_KEYS, label);
|
|
123
|
+
return {
|
|
124
|
+
id: string(row.id, `${label}.id`, { max: 512 }),
|
|
125
|
+
memory_id: string(row.memory_id, `${label}.memory_id`, { max: 512 }),
|
|
126
|
+
memory_key: string(row.memory_key, `${label}.memory_key`, { nullable: true, max: 4096 }),
|
|
127
|
+
operation: auditOperation(row.operation, `${label}.operation`),
|
|
128
|
+
agent_id: string(row.agent_id, `${label}.agent_id`, { nullable: true, max: 512 }),
|
|
129
|
+
old_value_hash: nullableHash(row.old_value_hash, `${label}.old_value_hash`),
|
|
130
|
+
new_value_hash: nullableHash(row.new_value_hash, `${label}.new_value_hash`),
|
|
131
|
+
changes: record(row.changes, `${label}.changes`),
|
|
132
|
+
created_at: canonicalAuditTimestamp(row.created_at, `${label}.created_at`)
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function nullableTimestamp(value, label) {
|
|
136
|
+
return value === null ? null : canonicalAuditTimestamp(value, label);
|
|
137
|
+
}
|
|
138
|
+
function validateFilters(value) {
|
|
139
|
+
const filters = object(value, "audit page filters");
|
|
140
|
+
const expected = new Set(["memory_id", "since", "until", "operation", "agent_id"]);
|
|
141
|
+
exactKeys(filters, expected, "audit page filters");
|
|
142
|
+
const result = {
|
|
143
|
+
memory_id: string(filters.memory_id, "filters.memory_id", { nullable: true, max: 512 }),
|
|
144
|
+
since: nullableTimestamp(filters.since, "filters.since"),
|
|
145
|
+
until: nullableTimestamp(filters.until, "filters.until"),
|
|
146
|
+
operation: filters.operation === null ? null : auditOperation(filters.operation, "filters.operation"),
|
|
147
|
+
agent_id: string(filters.agent_id, "filters.agent_id", { nullable: true, max: 512 })
|
|
148
|
+
};
|
|
149
|
+
if (result.since && result.until && result.since > result.until) {
|
|
150
|
+
throw new AuditContractError("filters.since must not be after filters.until");
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
function compareEntryOrder(left, right) {
|
|
155
|
+
if (left.created_at !== right.created_at)
|
|
156
|
+
return left.created_at > right.created_at ? -1 : 1;
|
|
157
|
+
return left.id > right.id ? -1 : left.id < right.id ? 1 : 0;
|
|
158
|
+
}
|
|
159
|
+
function validateAuditPage(value, expected) {
|
|
160
|
+
const page = object(value, "audit page");
|
|
161
|
+
exactKeys(page, PAGE_KEYS, "audit page");
|
|
162
|
+
if (page.contract !== expected.contract)
|
|
163
|
+
throw new AuditContractError(`expected contract '${expected.contract}'`);
|
|
164
|
+
const entries = Array.isArray(page.entries) ? page.entries.map((entry, index) => validateAuditEntry(entry, `entries[${index}]`)) : (() => {
|
|
165
|
+
throw new AuditContractError("entries must be an array");
|
|
166
|
+
})();
|
|
167
|
+
const count = integer(page.count, "count");
|
|
168
|
+
const total = integer(page.total, "total");
|
|
169
|
+
const limit = integer(page.limit, "limit", 1);
|
|
170
|
+
const requestedLimit = integer(expected.limit, "requested limit", 1);
|
|
171
|
+
if (limit !== requestedLimit)
|
|
172
|
+
throw new AuditContractError("limit receipt does not match the request");
|
|
173
|
+
const consumed = integer(page.consumed, "consumed");
|
|
174
|
+
const cursor = cursorString(page.cursor, "cursor");
|
|
175
|
+
const nextCursor = cursorString(page.next_cursor, "next_cursor");
|
|
176
|
+
const hasMore = boolean(page.has_more, "has_more");
|
|
177
|
+
const complete = boolean(page.complete, "complete");
|
|
178
|
+
const snapshotAt = canonicalAuditTimestamp(page.snapshot_at, "snapshot_at");
|
|
179
|
+
const filters = validateFilters(page.filters);
|
|
180
|
+
const sort = object(page.sort, "sort");
|
|
181
|
+
exactKeys(sort, new Set(["field", "direction", "tie_breaker"]), "sort");
|
|
182
|
+
if (sort.field !== "created_at" || sort.direction !== "desc" || sort.tie_breaker !== "id") {
|
|
183
|
+
throw new AuditContractError("sort must be created_at desc with id tie-breaker");
|
|
184
|
+
}
|
|
185
|
+
if (cursor !== expected.cursor)
|
|
186
|
+
throw new AuditContractError("cursor receipt does not match the request");
|
|
187
|
+
for (const key of ["memory_id", "since", "until", "operation", "agent_id"]) {
|
|
188
|
+
if (filters[key] !== expected.filters[key]) {
|
|
189
|
+
throw new AuditContractError(`filters.${key} receipt does not match the request`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (count !== entries.length || count > limit || total < count || consumed < count || consumed > total) {
|
|
193
|
+
throw new AuditContractError("count/limit/consumed/total fields are inconsistent");
|
|
194
|
+
}
|
|
195
|
+
if (hasMore !== consumed < total)
|
|
196
|
+
throw new AuditContractError("has_more does not match consumed/total");
|
|
197
|
+
if (hasMore !== (nextCursor !== null))
|
|
198
|
+
throw new AuditContractError("next_cursor does not match has_more");
|
|
199
|
+
if (total > 0 && count === 0)
|
|
200
|
+
throw new AuditContractError("non-empty audit result must make progress on every page");
|
|
201
|
+
if (hasMore && nextCursor === cursor)
|
|
202
|
+
throw new AuditContractError("next_cursor must advance beyond the request cursor");
|
|
203
|
+
const shouldBeComplete = cursor === null && !hasMore && consumed === total;
|
|
204
|
+
if (complete !== shouldBeComplete)
|
|
205
|
+
throw new AuditContractError("complete is not truthful for this page");
|
|
206
|
+
if (cursor === null && consumed !== count)
|
|
207
|
+
throw new AuditContractError("initial page consumed must equal count");
|
|
208
|
+
for (let index = 1;index < entries.length; index++) {
|
|
209
|
+
if (compareEntryOrder(entries[index - 1], entries[index]) >= 0) {
|
|
210
|
+
throw new AuditContractError("entries are not strictly ordered by created_at desc, id desc");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const ids = new Set;
|
|
214
|
+
for (const entry of entries) {
|
|
215
|
+
if (ids.has(entry.id))
|
|
216
|
+
throw new AuditContractError(`duplicate audit entry id '${entry.id}'`);
|
|
217
|
+
ids.add(entry.id);
|
|
218
|
+
if (filters.memory_id && entry.memory_id !== filters.memory_id)
|
|
219
|
+
throw new AuditContractError("entry violates memory_id filter");
|
|
220
|
+
if (filters.operation && entry.operation !== filters.operation)
|
|
221
|
+
throw new AuditContractError("entry violates operation filter");
|
|
222
|
+
if (filters.agent_id && entry.agent_id !== filters.agent_id)
|
|
223
|
+
throw new AuditContractError("entry violates agent_id filter");
|
|
224
|
+
if (filters.since && entry.created_at < filters.since)
|
|
225
|
+
throw new AuditContractError("entry violates since filter");
|
|
226
|
+
if (filters.until && entry.created_at > filters.until)
|
|
227
|
+
throw new AuditContractError("entry violates until filter");
|
|
228
|
+
if (entry.created_at > snapshotAt)
|
|
229
|
+
throw new AuditContractError("entry is newer than the snapshot boundary");
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
contract: expected.contract,
|
|
233
|
+
entries,
|
|
234
|
+
count,
|
|
235
|
+
total,
|
|
236
|
+
limit,
|
|
237
|
+
cursor,
|
|
238
|
+
next_cursor: nextCursor,
|
|
239
|
+
consumed,
|
|
240
|
+
has_more: hasMore,
|
|
241
|
+
complete,
|
|
242
|
+
snapshot_at: snapshotAt,
|
|
243
|
+
filters,
|
|
244
|
+
sort: { field: "created_at", direction: "desc", tie_breaker: "id" }
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function validateAuditStats(value) {
|
|
248
|
+
const stats = object(value, "audit stats");
|
|
249
|
+
exactKeys(stats, new Set(["contract", "total_entries", "by_operation", "recent_24h", "snapshot_at"]), "audit stats");
|
|
250
|
+
if (stats.contract !== AUDIT_STATS_CONTRACT)
|
|
251
|
+
throw new AuditContractError(`expected contract '${AUDIT_STATS_CONTRACT}'`);
|
|
252
|
+
const total = integer(stats.total_entries, "total_entries");
|
|
253
|
+
const recent = integer(stats.recent_24h, "recent_24h");
|
|
254
|
+
const byOperationObject = object(stats.by_operation, "by_operation");
|
|
255
|
+
exactKeys(byOperationObject, new Set(AUDIT_OPERATIONS), "by_operation");
|
|
256
|
+
const byOperation = Object.fromEntries(AUDIT_OPERATIONS.map((operation) => [operation, integer(byOperationObject[operation], `by_operation.${operation}`)]));
|
|
257
|
+
if (Object.values(byOperation).reduce((sum, count) => sum + count, 0) !== total) {
|
|
258
|
+
throw new AuditContractError("by_operation counts do not sum to total_entries");
|
|
259
|
+
}
|
|
260
|
+
if (recent > total)
|
|
261
|
+
throw new AuditContractError("recent_24h exceeds total_entries");
|
|
262
|
+
return {
|
|
263
|
+
contract: AUDIT_STATS_CONTRACT,
|
|
264
|
+
total_entries: total,
|
|
265
|
+
by_operation: byOperation,
|
|
266
|
+
recent_24h: recent,
|
|
267
|
+
snapshot_at: canonicalAuditTimestamp(stats.snapshot_at, "snapshot_at")
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ../../node_modules/.bun/@hasna+contracts@1.0.2+e8014c875821e0be/node_modules/@hasna/contracts/dist/client/transport.js
|
|
3
272
|
import { isIP } from "net";
|
|
4
273
|
import { spawnSync } from "child_process";
|
|
5
274
|
import { closeSync, fstatSync, openSync, readFileSync } from "fs";
|
|
@@ -798,6 +1067,11 @@ function mementosResolverInputs(env, credentials = {}) {
|
|
|
798
1067
|
}
|
|
799
1068
|
|
|
800
1069
|
// src/sdk/index.ts
|
|
1070
|
+
var MEMENTOS_MACHINE_REGISTRATION_CONTRACT = "mementos.machine-registration.v1";
|
|
1071
|
+
var MEMENTOS_MACHINE_LIST_CONTRACT = "mementos.machines.v1";
|
|
1072
|
+
var MEMENTOS_MACHINE_MUTATION_CONTRACT = "mementos.machine-mutation.v1";
|
|
1073
|
+
var MEMENTOS_MACHINE_TOUCH_CONTRACT = "mementos.machine-touch.v1";
|
|
1074
|
+
|
|
801
1075
|
class MementosError extends Error {
|
|
802
1076
|
status;
|
|
803
1077
|
details;
|
|
@@ -816,9 +1090,297 @@ class MementosConfigError extends Error {
|
|
|
816
1090
|
this.name = "MementosConfigError";
|
|
817
1091
|
}
|
|
818
1092
|
}
|
|
1093
|
+
var SESSION_JOBS_PAGE_CONTRACT = "mementos.sessions.jobs.v2";
|
|
1094
|
+
function sdkProtocolError(operation, detail) {
|
|
1095
|
+
return new MementosError(`mementos ${operation} returned a malformed 2xx response: ${detail}`, 502);
|
|
1096
|
+
}
|
|
1097
|
+
function sessionProtocolError(detail) {
|
|
1098
|
+
return sdkProtocolError("session jobs", detail);
|
|
1099
|
+
}
|
|
1100
|
+
function auditSdkError(operation, error) {
|
|
1101
|
+
if (error instanceof MementosError)
|
|
1102
|
+
throw error;
|
|
1103
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1104
|
+
throw sdkProtocolError(operation, detail);
|
|
1105
|
+
}
|
|
1106
|
+
function sdkAuditIdentifier(value, label) {
|
|
1107
|
+
if (value === undefined)
|
|
1108
|
+
return null;
|
|
1109
|
+
if (!value || value.length > 512 || /[\u0000-\u001f\u007f]/.test(value)) {
|
|
1110
|
+
throw new AuditContractError(`${label} must be a non-empty printable string of at most 512 characters`);
|
|
1111
|
+
}
|
|
1112
|
+
return value;
|
|
1113
|
+
}
|
|
1114
|
+
function sdkAuditFilters(input) {
|
|
1115
|
+
const filters = {
|
|
1116
|
+
memory_id: sdkAuditIdentifier(input.memory_id, "memory_id"),
|
|
1117
|
+
since: input.since === undefined ? null : canonicalAuditTimestamp(input.since, "since"),
|
|
1118
|
+
until: input.until === undefined ? null : canonicalAuditTimestamp(input.until, "until"),
|
|
1119
|
+
operation: input.operation === undefined ? null : auditOperation(input.operation),
|
|
1120
|
+
agent_id: sdkAuditIdentifier(input.agent_id, "agent_id")
|
|
1121
|
+
};
|
|
1122
|
+
if (filters.since && filters.until && filters.since > filters.until) {
|
|
1123
|
+
throw new AuditContractError("since must not be after until");
|
|
1124
|
+
}
|
|
1125
|
+
return filters;
|
|
1126
|
+
}
|
|
1127
|
+
function sdkAuditCursor(cursor) {
|
|
1128
|
+
if (cursor === undefined)
|
|
1129
|
+
return;
|
|
1130
|
+
if (!cursor || cursor.length > 4096 || !/^[A-Za-z0-9_-]+$/.test(cursor)) {
|
|
1131
|
+
throw new AuditContractError("cursor must be a bounded base64url audit cursor");
|
|
1132
|
+
}
|
|
1133
|
+
return cursor;
|
|
1134
|
+
}
|
|
1135
|
+
function sdkAuditLimit(limit) {
|
|
1136
|
+
if (limit === undefined)
|
|
1137
|
+
return;
|
|
1138
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1000) {
|
|
1139
|
+
throw new AuditContractError("limit must be an integer between 1 and 1000");
|
|
1140
|
+
}
|
|
1141
|
+
return limit;
|
|
1142
|
+
}
|
|
1143
|
+
function sdkObject(value, operation, field = "response") {
|
|
1144
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1145
|
+
throw sdkProtocolError(operation, `expected ${field} to be a JSON object`);
|
|
1146
|
+
}
|
|
1147
|
+
return value;
|
|
1148
|
+
}
|
|
1149
|
+
var SDK_MACHINE_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
1150
|
+
function sdkMachineHostname(value, operation) {
|
|
1151
|
+
const normalized = value.trim().replace(/\.+$/, "").toLowerCase();
|
|
1152
|
+
if (!normalized || normalized.length > 253 || /[\u0000-\u001f\u007f/\\\s]/.test(normalized) || normalized !== value) {
|
|
1153
|
+
throw sdkProtocolError(operation, "machine.hostname is not canonical");
|
|
1154
|
+
}
|
|
1155
|
+
return normalized;
|
|
1156
|
+
}
|
|
1157
|
+
function sdkMachinePlatform(value, operation) {
|
|
1158
|
+
const normalized = value.trim().toLowerCase();
|
|
1159
|
+
if (!normalized || normalized.length > 64 || !/^[a-z0-9._-]+$/.test(normalized) || normalized !== value) {
|
|
1160
|
+
throw sdkProtocolError(operation, "machine.platform is not canonical");
|
|
1161
|
+
}
|
|
1162
|
+
return normalized;
|
|
1163
|
+
}
|
|
1164
|
+
function sdkMachineName(value, operation) {
|
|
1165
|
+
if (typeof value !== "string" || !value || value.length > 128 || /[\u0000-\u001f\u007f]/.test(value)) {
|
|
1166
|
+
throw sdkProtocolError(operation, "machine.name is outside the public contract");
|
|
1167
|
+
}
|
|
1168
|
+
return value;
|
|
1169
|
+
}
|
|
1170
|
+
function sdkMachineTimestamp(value, operation, field) {
|
|
1171
|
+
if (typeof value !== "string" || !SDK_MACHINE_TIMESTAMP.test(value)) {
|
|
1172
|
+
throw sdkProtocolError(operation, `machine.${field} is not a canonical UTC timestamp`);
|
|
1173
|
+
}
|
|
1174
|
+
const parsed = new Date(value);
|
|
1175
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== value) {
|
|
1176
|
+
throw sdkProtocolError(operation, `machine.${field} is not a real calendar timestamp`);
|
|
1177
|
+
}
|
|
1178
|
+
return value;
|
|
1179
|
+
}
|
|
1180
|
+
function sdkMachine(value, operation) {
|
|
1181
|
+
const machine = sdkObject(value, operation, "machine");
|
|
1182
|
+
if (typeof machine["id"] !== "string" || !machine["id"]) {
|
|
1183
|
+
throw sdkProtocolError(operation, "expected machine.id to be a non-empty string");
|
|
1184
|
+
}
|
|
1185
|
+
if (typeof machine["hostname"] !== "string" || typeof machine["platform"] !== "string") {
|
|
1186
|
+
throw sdkProtocolError(operation, "expected machine hostname/platform strings");
|
|
1187
|
+
}
|
|
1188
|
+
if (typeof machine["is_primary"] !== "boolean") {
|
|
1189
|
+
throw sdkProtocolError(operation, "expected machine.is_primary to be a boolean");
|
|
1190
|
+
}
|
|
1191
|
+
const createdAt = sdkMachineTimestamp(machine["created_at"], operation, "created_at");
|
|
1192
|
+
const lastSeenAt = sdkMachineTimestamp(machine["last_seen_at"], operation, "last_seen_at");
|
|
1193
|
+
if (lastSeenAt < createdAt)
|
|
1194
|
+
throw sdkProtocolError(operation, "machine.last_seen_at precedes created_at");
|
|
1195
|
+
return {
|
|
1196
|
+
id: machine["id"],
|
|
1197
|
+
name: sdkMachineName(machine["name"], operation),
|
|
1198
|
+
hostname: sdkMachineHostname(machine["hostname"], operation),
|
|
1199
|
+
platform: sdkMachinePlatform(machine["platform"], operation),
|
|
1200
|
+
is_primary: machine["is_primary"],
|
|
1201
|
+
created_at: createdAt,
|
|
1202
|
+
last_seen_at: lastSeenAt
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
function sdkMachineMutation(value, operation, expectedId) {
|
|
1206
|
+
const response = sdkObject(value, operation);
|
|
1207
|
+
if (response["contract"] !== MEMENTOS_MACHINE_MUTATION_CONTRACT) {
|
|
1208
|
+
throw sdkProtocolError(operation, `expected contract '${MEMENTOS_MACHINE_MUTATION_CONTRACT}'`);
|
|
1209
|
+
}
|
|
1210
|
+
const machine = sdkMachine(response["machine"], operation);
|
|
1211
|
+
if (machine.id !== expectedId) {
|
|
1212
|
+
throw sdkProtocolError(operation, "returned machine id does not match the requested stable id");
|
|
1213
|
+
}
|
|
1214
|
+
return machine;
|
|
1215
|
+
}
|
|
1216
|
+
function sessionObject(value, field = "response") {
|
|
1217
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1218
|
+
throw sessionProtocolError(`expected ${field} to be a JSON object`);
|
|
1219
|
+
}
|
|
1220
|
+
return value;
|
|
1221
|
+
}
|
|
1222
|
+
function sessionString(object2, field, nullable = false) {
|
|
1223
|
+
const value = object2[field];
|
|
1224
|
+
if (nullable && value === null)
|
|
1225
|
+
return null;
|
|
1226
|
+
if (typeof value !== "string" || !nullable && value.length === 0) {
|
|
1227
|
+
throw sessionProtocolError(`expected '${field}' to be ${nullable ? "a string or null" : "a non-empty string"}`);
|
|
1228
|
+
}
|
|
1229
|
+
return value;
|
|
1230
|
+
}
|
|
1231
|
+
function sessionCount(object2, field) {
|
|
1232
|
+
const value = object2[field];
|
|
1233
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
1234
|
+
throw sessionProtocolError(`expected '${field}' to be a non-negative safe integer`);
|
|
1235
|
+
}
|
|
1236
|
+
return value;
|
|
1237
|
+
}
|
|
1238
|
+
function decodeSessionJob(value, index) {
|
|
1239
|
+
const job = sessionObject(value, `jobs[${index}]`);
|
|
1240
|
+
const source = sessionString(job, "source");
|
|
1241
|
+
if (!["claude-code", "codex", "manual", "open-sessions"].includes(source)) {
|
|
1242
|
+
throw sessionProtocolError(`jobs[${index}] has unsupported 'source'`);
|
|
1243
|
+
}
|
|
1244
|
+
const status = sessionString(job, "status");
|
|
1245
|
+
if (!["pending", "processing", "completed", "failed"].includes(status)) {
|
|
1246
|
+
throw sessionProtocolError(`jobs[${index}] has unsupported 'status'`);
|
|
1247
|
+
}
|
|
1248
|
+
const metadata = sessionObject(job["metadata"], `jobs[${index}].metadata`);
|
|
1249
|
+
return {
|
|
1250
|
+
id: sessionString(job, "id"),
|
|
1251
|
+
session_id: sessionString(job, "session_id"),
|
|
1252
|
+
agent_id: sessionString(job, "agent_id", true),
|
|
1253
|
+
project_id: sessionString(job, "project_id", true),
|
|
1254
|
+
source,
|
|
1255
|
+
status,
|
|
1256
|
+
transcript: typeof job["transcript"] === "string" ? job["transcript"] : (() => {
|
|
1257
|
+
throw sessionProtocolError(`jobs[${index}] expected 'transcript' to be a string`);
|
|
1258
|
+
})(),
|
|
1259
|
+
chunk_count: sessionCount(job, "chunk_count"),
|
|
1260
|
+
memories_extracted: sessionCount(job, "memories_extracted"),
|
|
1261
|
+
error: sessionString(job, "error", true),
|
|
1262
|
+
metadata,
|
|
1263
|
+
created_at: sessionString(job, "created_at"),
|
|
1264
|
+
started_at: sessionString(job, "started_at", true),
|
|
1265
|
+
completed_at: sessionString(job, "completed_at", true)
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
function decodeSessionJobsPage(value) {
|
|
1269
|
+
const page = sessionObject(value);
|
|
1270
|
+
if (page["contract"] !== SESSION_JOBS_PAGE_CONTRACT) {
|
|
1271
|
+
throw sessionProtocolError(`expected contract '${SESSION_JOBS_PAGE_CONTRACT}'`);
|
|
1272
|
+
}
|
|
1273
|
+
if (!Array.isArray(page["jobs"]))
|
|
1274
|
+
throw sessionProtocolError("expected 'jobs' to be an array");
|
|
1275
|
+
const jobs = page["jobs"].map(decodeSessionJob);
|
|
1276
|
+
const count = sessionCount(page, "count");
|
|
1277
|
+
const limit = sessionCount(page, "limit");
|
|
1278
|
+
const offset = sessionCount(page, "offset");
|
|
1279
|
+
if (limit < 1)
|
|
1280
|
+
throw sessionProtocolError("expected 'limit' to be positive");
|
|
1281
|
+
if (count !== jobs.length || count > limit) {
|
|
1282
|
+
throw sessionProtocolError("page count is inconsistent with jobs/limit");
|
|
1283
|
+
}
|
|
1284
|
+
if (typeof page["has_more"] !== "boolean")
|
|
1285
|
+
throw sessionProtocolError("expected 'has_more' to be a boolean");
|
|
1286
|
+
const nextOffset = page["next_offset"];
|
|
1287
|
+
if (nextOffset !== null && (!Number.isSafeInteger(nextOffset) || nextOffset < 0)) {
|
|
1288
|
+
throw sessionProtocolError("expected 'next_offset' to be a non-negative safe integer or null");
|
|
1289
|
+
}
|
|
1290
|
+
if (page["has_more"] === true && nextOffset !== offset + jobs.length) {
|
|
1291
|
+
throw sessionProtocolError("'has_more' requires the exact next offset");
|
|
1292
|
+
}
|
|
1293
|
+
if (page["has_more"] === false && nextOffset !== null) {
|
|
1294
|
+
throw sessionProtocolError("terminal page must set 'next_offset' to null");
|
|
1295
|
+
}
|
|
1296
|
+
return {
|
|
1297
|
+
contract: SESSION_JOBS_PAGE_CONTRACT,
|
|
1298
|
+
jobs,
|
|
1299
|
+
count,
|
|
1300
|
+
limit,
|
|
1301
|
+
offset,
|
|
1302
|
+
has_more: page["has_more"],
|
|
1303
|
+
next_offset: nextOffset
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
function decodeSessionIngestReceipt(value, transcript, expectedSessionId) {
|
|
1307
|
+
const receipt = sessionObject(value);
|
|
1308
|
+
if (receipt["contract"] !== "mementos.sessions.ingest.v2") {
|
|
1309
|
+
throw sdkProtocolError("session ingest", "expected contract 'mementos.sessions.ingest.v2'");
|
|
1310
|
+
}
|
|
1311
|
+
const jobId = sessionString(receipt, "job_id");
|
|
1312
|
+
if (receipt["status"] !== "queued")
|
|
1313
|
+
throw sdkProtocolError("session ingest", "expected status 'queued'");
|
|
1314
|
+
const message = sessionString(receipt, "message");
|
|
1315
|
+
const rawJob = sessionObject(receipt["job"], "job");
|
|
1316
|
+
const job = decodeSessionJob({ ...rawJob, transcript }, 0);
|
|
1317
|
+
if (job.id !== jobId || job.session_id !== expectedSessionId) {
|
|
1318
|
+
throw sdkProtocolError("session ingest", "job receipt identity does not match the request");
|
|
1319
|
+
}
|
|
1320
|
+
return {
|
|
1321
|
+
contract: "mementos.sessions.ingest.v2",
|
|
1322
|
+
job_id: jobId,
|
|
1323
|
+
status: "queued",
|
|
1324
|
+
message,
|
|
1325
|
+
job
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
function decodeQueueStats(value) {
|
|
1329
|
+
const stats = sessionObject(value);
|
|
1330
|
+
return {
|
|
1331
|
+
pending: sessionCount(stats, "pending"),
|
|
1332
|
+
processing: sessionCount(stats, "processing"),
|
|
1333
|
+
completed: sessionCount(stats, "completed"),
|
|
1334
|
+
failed: sessionCount(stats, "failed")
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
var SDK_RESOURCE_TYPES = new Set(["project", "memory", "entity", "agent", "connector", "file"]);
|
|
1338
|
+
var SDK_LOCK_TYPES = new Set(["advisory", "exclusive"]);
|
|
1339
|
+
function decodeResourceLock(value, operation) {
|
|
1340
|
+
const lock = sessionObject(value, "lock");
|
|
1341
|
+
const resourceType = sessionString(lock, "resource_type");
|
|
1342
|
+
const lockType = sessionString(lock, "lock_type");
|
|
1343
|
+
if (!SDK_RESOURCE_TYPES.has(resourceType)) {
|
|
1344
|
+
throw sdkProtocolError(operation, "unsupported 'resource_type'");
|
|
1345
|
+
}
|
|
1346
|
+
if (!SDK_LOCK_TYPES.has(lockType)) {
|
|
1347
|
+
throw sdkProtocolError(operation, "unsupported 'lock_type'");
|
|
1348
|
+
}
|
|
1349
|
+
return {
|
|
1350
|
+
id: sessionString(lock, "id"),
|
|
1351
|
+
resource_type: resourceType,
|
|
1352
|
+
resource_id: sessionString(lock, "resource_id"),
|
|
1353
|
+
agent_id: sessionString(lock, "agent_id"),
|
|
1354
|
+
lock_type: lockType,
|
|
1355
|
+
locked_at: sessionString(lock, "locked_at"),
|
|
1356
|
+
expires_at: sessionString(lock, "expires_at")
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
function decodeResourceLocks(value, operation) {
|
|
1360
|
+
if (!Array.isArray(value))
|
|
1361
|
+
throw sdkProtocolError(operation, "expected a JSON array");
|
|
1362
|
+
return value.map((lock) => decodeResourceLock(lock, operation));
|
|
1363
|
+
}
|
|
1364
|
+
function decodeBooleanReceipt(value, field, operation) {
|
|
1365
|
+
const receipt = sessionObject(value);
|
|
1366
|
+
if (typeof receipt[field] !== "boolean")
|
|
1367
|
+
throw sdkProtocolError(operation, `expected '${field}' to be a boolean`);
|
|
1368
|
+
return { [field]: receipt[field] };
|
|
1369
|
+
}
|
|
1370
|
+
function decodeCountReceipt(value, field, operation) {
|
|
1371
|
+
const receipt = sessionObject(value);
|
|
1372
|
+
const count = receipt[field];
|
|
1373
|
+
if (!Number.isSafeInteger(count) || count < 0) {
|
|
1374
|
+
throw sdkProtocolError(operation, `expected '${field}' to be a non-negative safe integer`);
|
|
1375
|
+
}
|
|
1376
|
+
return { [field]: count };
|
|
1377
|
+
}
|
|
819
1378
|
var MEMENTOS_DEFAULT_BASE_URL = "http://localhost:19428";
|
|
820
1379
|
function resolveMementosApiBase(rawBaseUrl, explicitPrefix) {
|
|
821
|
-
|
|
1380
|
+
if (rawBaseUrl === undefined || rawBaseUrl.trim() === "") {
|
|
1381
|
+
throw new Error("mementos base URL is required for this explicit-base helper; ordinary SDK clients must resolve the hosted authority through the credential chain or opt into local mode");
|
|
1382
|
+
}
|
|
1383
|
+
const trimmed = rawBaseUrl.trim().replace(/\/+$/, "");
|
|
822
1384
|
let url;
|
|
823
1385
|
try {
|
|
824
1386
|
url = new URL(trimmed);
|
|
@@ -927,10 +1489,10 @@ class MementosClient {
|
|
|
927
1489
|
_resolveOptions;
|
|
928
1490
|
_pinnedAuthority = null;
|
|
929
1491
|
constructor(config = {}) {
|
|
930
|
-
const
|
|
1492
|
+
const prefix = config.baseUrl !== undefined ? resolveMementosApiBase(config.baseUrl, config.prefix).prefix : config.prefix?.replace(/\/+$/, "") || "/v1";
|
|
931
1493
|
this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
932
1494
|
this.apiKey = config.apiKey;
|
|
933
|
-
this.prefix =
|
|
1495
|
+
this.prefix = prefix;
|
|
934
1496
|
this._resolveOptions = {
|
|
935
1497
|
...config.baseUrl !== undefined ? { baseUrl: config.baseUrl } : {},
|
|
936
1498
|
...config.apiKey !== undefined ? { apiKey: config.apiKey } : {},
|
|
@@ -1044,6 +1606,8 @@ class MementosClient {
|
|
|
1044
1606
|
q["agent_id"] = filter.agent_id;
|
|
1045
1607
|
if (filter.project_id)
|
|
1046
1608
|
q["project_id"] = filter.project_id;
|
|
1609
|
+
if (filter.project_id && filter.include_unassigned_project)
|
|
1610
|
+
q["include_unassigned_project"] = true;
|
|
1047
1611
|
if (filter.session_id)
|
|
1048
1612
|
q["session_id"] = filter.session_id;
|
|
1049
1613
|
if (filter.namespace)
|
|
@@ -1202,6 +1766,126 @@ class MementosClient {
|
|
|
1202
1766
|
listAgentsByProject(projectId) {
|
|
1203
1767
|
return this.get(`/api/agents`, { project_id: projectId });
|
|
1204
1768
|
}
|
|
1769
|
+
async getMemoryAuditTrail(memoryId, options = {}) {
|
|
1770
|
+
const operation = "GET /v1/memories/:id/audit-trail";
|
|
1771
|
+
try {
|
|
1772
|
+
const filters = sdkAuditFilters({ memory_id: memoryId });
|
|
1773
|
+
const requestedLimit = sdkAuditLimit(options.limit);
|
|
1774
|
+
const limit = requestedLimit ?? AUDIT_DEFAULT_LIMIT;
|
|
1775
|
+
const requestedCursor = sdkAuditCursor(options.cursor);
|
|
1776
|
+
const cursor = requestedCursor ?? null;
|
|
1777
|
+
const response = await this.get(`/api/memories/${encodeURIComponent(filters.memory_id)}/audit-trail`, { limit: requestedLimit, cursor: requestedCursor });
|
|
1778
|
+
return validateAuditPage(response, { contract: AUDIT_TRAIL_CONTRACT, cursor, filters, limit });
|
|
1779
|
+
} catch (error) {
|
|
1780
|
+
return auditSdkError(operation, error);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
async exportAuditLog(options = {}) {
|
|
1784
|
+
const operation = "GET /v1/audit/export";
|
|
1785
|
+
try {
|
|
1786
|
+
const filters = sdkAuditFilters(options);
|
|
1787
|
+
const requestedLimit = sdkAuditLimit(options.limit);
|
|
1788
|
+
const limit = requestedLimit ?? AUDIT_DEFAULT_LIMIT;
|
|
1789
|
+
const requestedCursor = sdkAuditCursor(options.cursor);
|
|
1790
|
+
const cursor = requestedCursor ?? null;
|
|
1791
|
+
const response = await this.get("/api/audit/export", {
|
|
1792
|
+
since: filters.since ?? undefined,
|
|
1793
|
+
until: filters.until ?? undefined,
|
|
1794
|
+
operation: filters.operation ?? undefined,
|
|
1795
|
+
agent_id: filters.agent_id ?? undefined,
|
|
1796
|
+
limit: requestedLimit,
|
|
1797
|
+
cursor: requestedCursor
|
|
1798
|
+
});
|
|
1799
|
+
return validateAuditPage(response, { contract: AUDIT_EXPORT_CONTRACT, cursor, filters, limit });
|
|
1800
|
+
} catch (error) {
|
|
1801
|
+
return auditSdkError(operation, error);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
async getAuditStats() {
|
|
1805
|
+
const operation = "GET /v1/audit/stats";
|
|
1806
|
+
try {
|
|
1807
|
+
return validateAuditStats(await this.get("/api/audit/stats"));
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
return auditSdkError(operation, error);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
async listMachines() {
|
|
1813
|
+
const operation = "GET /v1/machines";
|
|
1814
|
+
const response = sdkObject(await this.get("/api/machines"), operation);
|
|
1815
|
+
if (response["contract"] !== MEMENTOS_MACHINE_LIST_CONTRACT || response["complete"] !== true || !Array.isArray(response["machines"])) {
|
|
1816
|
+
throw sdkProtocolError(operation, `expected contract '${MEMENTOS_MACHINE_LIST_CONTRACT}', complete=true, and a machines array`);
|
|
1817
|
+
}
|
|
1818
|
+
const machines = response["machines"].map((entry, index) => sdkMachine(entry, `${operation} machines[${index}]`));
|
|
1819
|
+
if (!Number.isSafeInteger(response["count"]) || response["count"] !== machines.length) {
|
|
1820
|
+
throw sdkProtocolError(operation, "count does not match machines.length");
|
|
1821
|
+
}
|
|
1822
|
+
if (new Set(machines.map((machine) => machine.id)).size !== machines.length || new Set(machines.map((machine) => machine.hostname)).size !== machines.length || new Set(machines.map((machine) => machine.name)).size !== machines.length) {
|
|
1823
|
+
throw sdkProtocolError(operation, "machine ids, hostnames, or names are duplicated");
|
|
1824
|
+
}
|
|
1825
|
+
if (machines.filter((machine) => machine.is_primary).length > 1) {
|
|
1826
|
+
throw sdkProtocolError(operation, "more than one machine is primary");
|
|
1827
|
+
}
|
|
1828
|
+
return { machines, count: machines.length, complete: true };
|
|
1829
|
+
}
|
|
1830
|
+
async registerMachine(input) {
|
|
1831
|
+
const operation = "POST /v1/machines";
|
|
1832
|
+
const response = sdkObject(await this.post("/api/machines", input), operation);
|
|
1833
|
+
if (response["contract"] !== MEMENTOS_MACHINE_REGISTRATION_CONTRACT || typeof response["created"] !== "boolean") {
|
|
1834
|
+
throw sdkProtocolError(operation, `expected contract '${MEMENTOS_MACHINE_REGISTRATION_CONTRACT}' and a boolean created receipt`);
|
|
1835
|
+
}
|
|
1836
|
+
const machine = sdkMachine(response["machine"], operation);
|
|
1837
|
+
const identity = sdkObject(response["identity"], operation, "identity");
|
|
1838
|
+
if (identity["idempotency_key"] !== "normalized_hostname" || identity["stable_id"] !== machine.id) {
|
|
1839
|
+
throw sdkProtocolError(operation, "identity receipt does not match the stable machine id");
|
|
1840
|
+
}
|
|
1841
|
+
const expectedHostname = sdkMachineHostname(input.hostname.trim().replace(/\.+$/, "").toLowerCase(), operation);
|
|
1842
|
+
const expectedPlatform = sdkMachinePlatform(input.platform.trim().toLowerCase(), operation);
|
|
1843
|
+
if (machine.hostname !== expectedHostname || machine.platform !== expectedPlatform) {
|
|
1844
|
+
throw sdkProtocolError(operation, "registration receipt does not match the requested hostname/platform");
|
|
1845
|
+
}
|
|
1846
|
+
if (response["created"] === true) {
|
|
1847
|
+
const expectedName = input.name?.trim() || expectedHostname;
|
|
1848
|
+
if (machine.name !== expectedName)
|
|
1849
|
+
throw sdkProtocolError(operation, "created registration receipt does not match the requested name");
|
|
1850
|
+
}
|
|
1851
|
+
return machine;
|
|
1852
|
+
}
|
|
1853
|
+
async getMachine(id) {
|
|
1854
|
+
return sdkMachineMutation(await this.get(`/api/machines/${encodeURIComponent(id)}`), "GET /v1/machines/:id", id);
|
|
1855
|
+
}
|
|
1856
|
+
async renameMachine(id, name) {
|
|
1857
|
+
const normalizedName = name.trim();
|
|
1858
|
+
const machine = sdkMachineMutation(await this.patch(`/api/machines/${encodeURIComponent(id)}`, { name: normalizedName }), "PATCH /v1/machines/:id", id);
|
|
1859
|
+
if (machine.name !== normalizedName)
|
|
1860
|
+
throw sdkProtocolError("PATCH /v1/machines/:id", "returned name does not match the requested rename");
|
|
1861
|
+
return machine;
|
|
1862
|
+
}
|
|
1863
|
+
async setPrimaryMachine(id) {
|
|
1864
|
+
const machine = sdkMachineMutation(await this.post(`/api/machines/${encodeURIComponent(id)}/primary`), "POST /v1/machines/:id/primary", id);
|
|
1865
|
+
if (!machine.is_primary)
|
|
1866
|
+
throw sdkProtocolError("POST /v1/machines/:id/primary", "returned machine is not primary");
|
|
1867
|
+
return machine;
|
|
1868
|
+
}
|
|
1869
|
+
async touchMachine(id) {
|
|
1870
|
+
const operation = "POST /v1/machines/:id/touch";
|
|
1871
|
+
const response = sdkObject(await this.post(`/api/machines/${encodeURIComponent(id)}/touch`), operation);
|
|
1872
|
+
if (response["contract"] !== MEMENTOS_MACHINE_TOUCH_CONTRACT || response["touched"] !== true || response["id"] !== id) {
|
|
1873
|
+
throw sdkProtocolError(operation, "expected touched=true for the requested stable id");
|
|
1874
|
+
}
|
|
1875
|
+
const machine = sdkMachine(response["machine"], operation);
|
|
1876
|
+
if (machine.id !== id || response["touched_at"] !== machine.last_seen_at) {
|
|
1877
|
+
throw sdkProtocolError(operation, "touch receipt does not match the returned machine");
|
|
1878
|
+
}
|
|
1879
|
+
return machine;
|
|
1880
|
+
}
|
|
1881
|
+
async deleteMachine(id) {
|
|
1882
|
+
const operation = "DELETE /v1/machines/:id";
|
|
1883
|
+
const response = sdkObject(await this.delete(`/api/machines/${encodeURIComponent(id)}`), operation);
|
|
1884
|
+
if (response["contract"] !== MEMENTOS_MACHINE_MUTATION_CONTRACT || response["deleted"] !== true || response["id"] !== id) {
|
|
1885
|
+
throw sdkProtocolError(operation, "expected deleted=true for the requested stable id");
|
|
1886
|
+
}
|
|
1887
|
+
return { deleted: true, id };
|
|
1888
|
+
}
|
|
1205
1889
|
listProjects() {
|
|
1206
1890
|
return this.get("/api/projects");
|
|
1207
1891
|
}
|
|
@@ -1422,30 +2106,30 @@ class MementosClient {
|
|
|
1422
2106
|
}
|
|
1423
2107
|
async acquireLock(input) {
|
|
1424
2108
|
try {
|
|
1425
|
-
return await this.post("/api/locks", input);
|
|
1426
|
-
} catch (
|
|
1427
|
-
if (
|
|
2109
|
+
return decodeResourceLock(await this.post("/api/locks", input), "lock acquire");
|
|
2110
|
+
} catch (error) {
|
|
2111
|
+
if (error instanceof MementosError && error.status === 409)
|
|
1428
2112
|
return null;
|
|
1429
|
-
throw
|
|
2113
|
+
throw error;
|
|
1430
2114
|
}
|
|
1431
2115
|
}
|
|
1432
|
-
checkLock(resourceType, resourceId, lockType) {
|
|
2116
|
+
async checkLock(resourceType, resourceId, lockType) {
|
|
1433
2117
|
const params = { resource_type: resourceType, resource_id: resourceId };
|
|
1434
2118
|
if (lockType)
|
|
1435
2119
|
params["lock_type"] = lockType;
|
|
1436
|
-
return this.get("/api/locks", params);
|
|
2120
|
+
return decodeResourceLocks(await this.get("/api/locks", params), "lock list");
|
|
1437
2121
|
}
|
|
1438
|
-
releaseLock(lockId, agentId) {
|
|
1439
|
-
return this.request("DELETE", `/api/locks/${lockId}`, { agent_id: agentId });
|
|
2122
|
+
async releaseLock(lockId, agentId) {
|
|
2123
|
+
return decodeBooleanReceipt(await this.request("DELETE", `/api/locks/${encodeURIComponent(lockId)}`, { agent_id: agentId }), "released", "lock release");
|
|
1440
2124
|
}
|
|
1441
|
-
listAgentLocks(agentId) {
|
|
1442
|
-
return this.get(`/api/agents/${agentId}/locks`);
|
|
2125
|
+
async listAgentLocks(agentId) {
|
|
2126
|
+
return decodeResourceLocks(await this.get(`/api/agents/${encodeURIComponent(agentId)}/locks`), "agent lock list");
|
|
1443
2127
|
}
|
|
1444
|
-
releaseAllAgentLocks(agentId) {
|
|
1445
|
-
return this.request("DELETE", `/api/agents/${agentId}/locks`);
|
|
2128
|
+
async releaseAllAgentLocks(agentId) {
|
|
2129
|
+
return decodeCountReceipt(await this.request("DELETE", `/api/agents/${encodeURIComponent(agentId)}/locks`), "released", "agent lock release");
|
|
1446
2130
|
}
|
|
1447
|
-
cleanExpiredLocks() {
|
|
1448
|
-
return this.post("/api/locks/clean", {});
|
|
2131
|
+
async cleanExpiredLocks() {
|
|
2132
|
+
return decodeCountReceipt(await this.post("/api/locks/clean", {}), "cleaned", "lock cleanup");
|
|
1449
2133
|
}
|
|
1450
2134
|
createTask(input) {
|
|
1451
2135
|
return this.post("/api/tasks", input);
|
|
@@ -1567,27 +2251,53 @@ class MementosClient {
|
|
|
1567
2251
|
rollbackSynthesis(runId) {
|
|
1568
2252
|
return this.post(`/api/synthesis/rollback/${runId}`, {});
|
|
1569
2253
|
}
|
|
1570
|
-
ingestSession(input) {
|
|
1571
|
-
return this.post("/api/sessions/ingest", input);
|
|
2254
|
+
async ingestSession(input) {
|
|
2255
|
+
return decodeSessionIngestReceipt(await this.post("/api/sessions/ingest", input), input.transcript, input.session_id);
|
|
1572
2256
|
}
|
|
1573
|
-
getSessionJob(jobId) {
|
|
1574
|
-
return this.get(`/api/sessions/jobs/${jobId}`);
|
|
2257
|
+
async getSessionJob(jobId) {
|
|
2258
|
+
return decodeSessionJob(await this.get(`/api/sessions/jobs/${encodeURIComponent(jobId)}`), 0);
|
|
1575
2259
|
}
|
|
1576
|
-
listSessionJobs(filter) {
|
|
2260
|
+
async listSessionJobs(filter) {
|
|
1577
2261
|
const params = new URLSearchParams;
|
|
1578
2262
|
if (filter?.agent_id)
|
|
1579
2263
|
params.set("agent_id", filter.agent_id);
|
|
1580
2264
|
if (filter?.project_id)
|
|
1581
2265
|
params.set("project_id", filter.project_id);
|
|
2266
|
+
if (filter?.session_id)
|
|
2267
|
+
params.set("session_id", filter.session_id);
|
|
1582
2268
|
if (filter?.status)
|
|
1583
2269
|
params.set("status", filter.status);
|
|
1584
|
-
if (filter?.limit)
|
|
2270
|
+
if (filter?.limit !== undefined)
|
|
1585
2271
|
params.set("limit", String(filter.limit));
|
|
2272
|
+
if (filter?.offset !== undefined)
|
|
2273
|
+
params.set("offset", String(filter.offset));
|
|
1586
2274
|
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
1587
|
-
|
|
2275
|
+
const response = await this.get(`/api/sessions/jobs${qs}`);
|
|
2276
|
+
const page = decodeSessionJobsPage(response);
|
|
2277
|
+
if (filter?.limit !== undefined && page.limit !== filter.limit) {
|
|
2278
|
+
throw sessionProtocolError("server did not preserve requested 'limit'");
|
|
2279
|
+
}
|
|
2280
|
+
if (filter?.offset !== undefined && page.offset !== filter.offset) {
|
|
2281
|
+
throw sessionProtocolError("server did not preserve requested 'offset'");
|
|
2282
|
+
}
|
|
2283
|
+
for (const job of page.jobs) {
|
|
2284
|
+
if (filter?.agent_id !== undefined && job.agent_id !== filter.agent_id) {
|
|
2285
|
+
throw sessionProtocolError("server did not preserve requested 'agent_id'");
|
|
2286
|
+
}
|
|
2287
|
+
if (filter?.project_id !== undefined && job.project_id !== filter.project_id) {
|
|
2288
|
+
throw sessionProtocolError("server did not preserve requested 'project_id'");
|
|
2289
|
+
}
|
|
2290
|
+
if (filter?.session_id !== undefined && job.session_id !== filter.session_id) {
|
|
2291
|
+
throw sessionProtocolError("server did not preserve requested 'session_id'");
|
|
2292
|
+
}
|
|
2293
|
+
if (filter?.status !== undefined && job.status !== filter.status) {
|
|
2294
|
+
throw sessionProtocolError("server did not preserve requested 'status'");
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
return page;
|
|
1588
2298
|
}
|
|
1589
|
-
getSessionQueueStats() {
|
|
1590
|
-
return this.get("/api/sessions/queue/stats");
|
|
2299
|
+
async getSessionQueueStats() {
|
|
2300
|
+
return decodeQueueStats(await this.get("/api/sessions/queue/stats"));
|
|
1591
2301
|
}
|
|
1592
2302
|
}
|
|
1593
2303
|
var sdk_default = MementosClient;
|
|
@@ -1596,8 +2306,16 @@ export {
|
|
|
1596
2306
|
resolveMementosApiBase,
|
|
1597
2307
|
sdk_default as default,
|
|
1598
2308
|
__resetMementosSdkLocalNotice,
|
|
2309
|
+
SESSION_JOBS_PAGE_CONTRACT,
|
|
1599
2310
|
MementosError,
|
|
1600
2311
|
MementosConfigError,
|
|
1601
2312
|
MementosClient,
|
|
1602
|
-
|
|
2313
|
+
MEMENTOS_MACHINE_TOUCH_CONTRACT,
|
|
2314
|
+
MEMENTOS_MACHINE_REGISTRATION_CONTRACT,
|
|
2315
|
+
MEMENTOS_MACHINE_MUTATION_CONTRACT,
|
|
2316
|
+
MEMENTOS_MACHINE_LIST_CONTRACT,
|
|
2317
|
+
MEMENTOS_DEFAULT_BASE_URL,
|
|
2318
|
+
AUDIT_TRAIL_CONTRACT as MEMENTOS_AUDIT_TRAIL_CONTRACT,
|
|
2319
|
+
AUDIT_STATS_CONTRACT as MEMENTOS_AUDIT_STATS_CONTRACT,
|
|
2320
|
+
AUDIT_EXPORT_CONTRACT as MEMENTOS_AUDIT_EXPORT_CONTRACT
|
|
1603
2321
|
};
|