@hasna/mementos 0.15.2 → 0.16.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 +75 -21
- package/dist/audit-contract.d.ts +87 -0
- package/dist/audit-contract.d.ts.map +1 -0
- 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-restore.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/project.d.ts.map +1 -1
- package/dist/cli/commands/system-session.d.ts.map +1 -1
- package/dist/cli/commands/system-synthesized-profile.d.ts.map +1 -1
- package/dist/cli/index.js +2093 -740
- 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/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 +3828 -1776
- package/dist/mcp/profile.d.ts +23 -0
- package/dist/mcp/profile.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/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 +109 -6
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +740 -25
- 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,6 +1090,291 @@ 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
|
const trimmed = (rawBaseUrl ?? MEMENTOS_DEFAULT_BASE_URL).trim().replace(/\/+$/, "") || MEMENTOS_DEFAULT_BASE_URL;
|
|
@@ -1044,6 +1603,8 @@ class MementosClient {
|
|
|
1044
1603
|
q["agent_id"] = filter.agent_id;
|
|
1045
1604
|
if (filter.project_id)
|
|
1046
1605
|
q["project_id"] = filter.project_id;
|
|
1606
|
+
if (filter.project_id && filter.include_unassigned_project)
|
|
1607
|
+
q["include_unassigned_project"] = true;
|
|
1047
1608
|
if (filter.session_id)
|
|
1048
1609
|
q["session_id"] = filter.session_id;
|
|
1049
1610
|
if (filter.namespace)
|
|
@@ -1202,6 +1763,126 @@ class MementosClient {
|
|
|
1202
1763
|
listAgentsByProject(projectId) {
|
|
1203
1764
|
return this.get(`/api/agents`, { project_id: projectId });
|
|
1204
1765
|
}
|
|
1766
|
+
async getMemoryAuditTrail(memoryId, options = {}) {
|
|
1767
|
+
const operation = "GET /v1/memories/:id/audit-trail";
|
|
1768
|
+
try {
|
|
1769
|
+
const filters = sdkAuditFilters({ memory_id: memoryId });
|
|
1770
|
+
const requestedLimit = sdkAuditLimit(options.limit);
|
|
1771
|
+
const limit = requestedLimit ?? AUDIT_DEFAULT_LIMIT;
|
|
1772
|
+
const requestedCursor = sdkAuditCursor(options.cursor);
|
|
1773
|
+
const cursor = requestedCursor ?? null;
|
|
1774
|
+
const response = await this.get(`/api/memories/${encodeURIComponent(filters.memory_id)}/audit-trail`, { limit: requestedLimit, cursor: requestedCursor });
|
|
1775
|
+
return validateAuditPage(response, { contract: AUDIT_TRAIL_CONTRACT, cursor, filters, limit });
|
|
1776
|
+
} catch (error) {
|
|
1777
|
+
return auditSdkError(operation, error);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
async exportAuditLog(options = {}) {
|
|
1781
|
+
const operation = "GET /v1/audit/export";
|
|
1782
|
+
try {
|
|
1783
|
+
const filters = sdkAuditFilters(options);
|
|
1784
|
+
const requestedLimit = sdkAuditLimit(options.limit);
|
|
1785
|
+
const limit = requestedLimit ?? AUDIT_DEFAULT_LIMIT;
|
|
1786
|
+
const requestedCursor = sdkAuditCursor(options.cursor);
|
|
1787
|
+
const cursor = requestedCursor ?? null;
|
|
1788
|
+
const response = await this.get("/api/audit/export", {
|
|
1789
|
+
since: filters.since ?? undefined,
|
|
1790
|
+
until: filters.until ?? undefined,
|
|
1791
|
+
operation: filters.operation ?? undefined,
|
|
1792
|
+
agent_id: filters.agent_id ?? undefined,
|
|
1793
|
+
limit: requestedLimit,
|
|
1794
|
+
cursor: requestedCursor
|
|
1795
|
+
});
|
|
1796
|
+
return validateAuditPage(response, { contract: AUDIT_EXPORT_CONTRACT, cursor, filters, limit });
|
|
1797
|
+
} catch (error) {
|
|
1798
|
+
return auditSdkError(operation, error);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
async getAuditStats() {
|
|
1802
|
+
const operation = "GET /v1/audit/stats";
|
|
1803
|
+
try {
|
|
1804
|
+
return validateAuditStats(await this.get("/api/audit/stats"));
|
|
1805
|
+
} catch (error) {
|
|
1806
|
+
return auditSdkError(operation, error);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
async listMachines() {
|
|
1810
|
+
const operation = "GET /v1/machines";
|
|
1811
|
+
const response = sdkObject(await this.get("/api/machines"), operation);
|
|
1812
|
+
if (response["contract"] !== MEMENTOS_MACHINE_LIST_CONTRACT || response["complete"] !== true || !Array.isArray(response["machines"])) {
|
|
1813
|
+
throw sdkProtocolError(operation, `expected contract '${MEMENTOS_MACHINE_LIST_CONTRACT}', complete=true, and a machines array`);
|
|
1814
|
+
}
|
|
1815
|
+
const machines = response["machines"].map((entry, index) => sdkMachine(entry, `${operation} machines[${index}]`));
|
|
1816
|
+
if (!Number.isSafeInteger(response["count"]) || response["count"] !== machines.length) {
|
|
1817
|
+
throw sdkProtocolError(operation, "count does not match machines.length");
|
|
1818
|
+
}
|
|
1819
|
+
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) {
|
|
1820
|
+
throw sdkProtocolError(operation, "machine ids, hostnames, or names are duplicated");
|
|
1821
|
+
}
|
|
1822
|
+
if (machines.filter((machine) => machine.is_primary).length > 1) {
|
|
1823
|
+
throw sdkProtocolError(operation, "more than one machine is primary");
|
|
1824
|
+
}
|
|
1825
|
+
return { machines, count: machines.length, complete: true };
|
|
1826
|
+
}
|
|
1827
|
+
async registerMachine(input) {
|
|
1828
|
+
const operation = "POST /v1/machines";
|
|
1829
|
+
const response = sdkObject(await this.post("/api/machines", input), operation);
|
|
1830
|
+
if (response["contract"] !== MEMENTOS_MACHINE_REGISTRATION_CONTRACT || typeof response["created"] !== "boolean") {
|
|
1831
|
+
throw sdkProtocolError(operation, `expected contract '${MEMENTOS_MACHINE_REGISTRATION_CONTRACT}' and a boolean created receipt`);
|
|
1832
|
+
}
|
|
1833
|
+
const machine = sdkMachine(response["machine"], operation);
|
|
1834
|
+
const identity = sdkObject(response["identity"], operation, "identity");
|
|
1835
|
+
if (identity["idempotency_key"] !== "normalized_hostname" || identity["stable_id"] !== machine.id) {
|
|
1836
|
+
throw sdkProtocolError(operation, "identity receipt does not match the stable machine id");
|
|
1837
|
+
}
|
|
1838
|
+
const expectedHostname = sdkMachineHostname(input.hostname.trim().replace(/\.+$/, "").toLowerCase(), operation);
|
|
1839
|
+
const expectedPlatform = sdkMachinePlatform(input.platform.trim().toLowerCase(), operation);
|
|
1840
|
+
if (machine.hostname !== expectedHostname || machine.platform !== expectedPlatform) {
|
|
1841
|
+
throw sdkProtocolError(operation, "registration receipt does not match the requested hostname/platform");
|
|
1842
|
+
}
|
|
1843
|
+
if (response["created"] === true) {
|
|
1844
|
+
const expectedName = input.name?.trim() || expectedHostname;
|
|
1845
|
+
if (machine.name !== expectedName)
|
|
1846
|
+
throw sdkProtocolError(operation, "created registration receipt does not match the requested name");
|
|
1847
|
+
}
|
|
1848
|
+
return machine;
|
|
1849
|
+
}
|
|
1850
|
+
async getMachine(id) {
|
|
1851
|
+
return sdkMachineMutation(await this.get(`/api/machines/${encodeURIComponent(id)}`), "GET /v1/machines/:id", id);
|
|
1852
|
+
}
|
|
1853
|
+
async renameMachine(id, name) {
|
|
1854
|
+
const normalizedName = name.trim();
|
|
1855
|
+
const machine = sdkMachineMutation(await this.patch(`/api/machines/${encodeURIComponent(id)}`, { name: normalizedName }), "PATCH /v1/machines/:id", id);
|
|
1856
|
+
if (machine.name !== normalizedName)
|
|
1857
|
+
throw sdkProtocolError("PATCH /v1/machines/:id", "returned name does not match the requested rename");
|
|
1858
|
+
return machine;
|
|
1859
|
+
}
|
|
1860
|
+
async setPrimaryMachine(id) {
|
|
1861
|
+
const machine = sdkMachineMutation(await this.post(`/api/machines/${encodeURIComponent(id)}/primary`), "POST /v1/machines/:id/primary", id);
|
|
1862
|
+
if (!machine.is_primary)
|
|
1863
|
+
throw sdkProtocolError("POST /v1/machines/:id/primary", "returned machine is not primary");
|
|
1864
|
+
return machine;
|
|
1865
|
+
}
|
|
1866
|
+
async touchMachine(id) {
|
|
1867
|
+
const operation = "POST /v1/machines/:id/touch";
|
|
1868
|
+
const response = sdkObject(await this.post(`/api/machines/${encodeURIComponent(id)}/touch`), operation);
|
|
1869
|
+
if (response["contract"] !== MEMENTOS_MACHINE_TOUCH_CONTRACT || response["touched"] !== true || response["id"] !== id) {
|
|
1870
|
+
throw sdkProtocolError(operation, "expected touched=true for the requested stable id");
|
|
1871
|
+
}
|
|
1872
|
+
const machine = sdkMachine(response["machine"], operation);
|
|
1873
|
+
if (machine.id !== id || response["touched_at"] !== machine.last_seen_at) {
|
|
1874
|
+
throw sdkProtocolError(operation, "touch receipt does not match the returned machine");
|
|
1875
|
+
}
|
|
1876
|
+
return machine;
|
|
1877
|
+
}
|
|
1878
|
+
async deleteMachine(id) {
|
|
1879
|
+
const operation = "DELETE /v1/machines/:id";
|
|
1880
|
+
const response = sdkObject(await this.delete(`/api/machines/${encodeURIComponent(id)}`), operation);
|
|
1881
|
+
if (response["contract"] !== MEMENTOS_MACHINE_MUTATION_CONTRACT || response["deleted"] !== true || response["id"] !== id) {
|
|
1882
|
+
throw sdkProtocolError(operation, "expected deleted=true for the requested stable id");
|
|
1883
|
+
}
|
|
1884
|
+
return { deleted: true, id };
|
|
1885
|
+
}
|
|
1205
1886
|
listProjects() {
|
|
1206
1887
|
return this.get("/api/projects");
|
|
1207
1888
|
}
|
|
@@ -1422,30 +2103,30 @@ class MementosClient {
|
|
|
1422
2103
|
}
|
|
1423
2104
|
async acquireLock(input) {
|
|
1424
2105
|
try {
|
|
1425
|
-
return await this.post("/api/locks", input);
|
|
1426
|
-
} catch (
|
|
1427
|
-
if (
|
|
2106
|
+
return decodeResourceLock(await this.post("/api/locks", input), "lock acquire");
|
|
2107
|
+
} catch (error) {
|
|
2108
|
+
if (error instanceof MementosError && error.status === 409)
|
|
1428
2109
|
return null;
|
|
1429
|
-
throw
|
|
2110
|
+
throw error;
|
|
1430
2111
|
}
|
|
1431
2112
|
}
|
|
1432
|
-
checkLock(resourceType, resourceId, lockType) {
|
|
2113
|
+
async checkLock(resourceType, resourceId, lockType) {
|
|
1433
2114
|
const params = { resource_type: resourceType, resource_id: resourceId };
|
|
1434
2115
|
if (lockType)
|
|
1435
2116
|
params["lock_type"] = lockType;
|
|
1436
|
-
return this.get("/api/locks", params);
|
|
2117
|
+
return decodeResourceLocks(await this.get("/api/locks", params), "lock list");
|
|
1437
2118
|
}
|
|
1438
|
-
releaseLock(lockId, agentId) {
|
|
1439
|
-
return this.request("DELETE", `/api/locks/${lockId}`, { agent_id: agentId });
|
|
2119
|
+
async releaseLock(lockId, agentId) {
|
|
2120
|
+
return decodeBooleanReceipt(await this.request("DELETE", `/api/locks/${encodeURIComponent(lockId)}`, { agent_id: agentId }), "released", "lock release");
|
|
1440
2121
|
}
|
|
1441
|
-
listAgentLocks(agentId) {
|
|
1442
|
-
return this.get(`/api/agents/${agentId}/locks`);
|
|
2122
|
+
async listAgentLocks(agentId) {
|
|
2123
|
+
return decodeResourceLocks(await this.get(`/api/agents/${encodeURIComponent(agentId)}/locks`), "agent lock list");
|
|
1443
2124
|
}
|
|
1444
|
-
releaseAllAgentLocks(agentId) {
|
|
1445
|
-
return this.request("DELETE", `/api/agents/${agentId}/locks`);
|
|
2125
|
+
async releaseAllAgentLocks(agentId) {
|
|
2126
|
+
return decodeCountReceipt(await this.request("DELETE", `/api/agents/${encodeURIComponent(agentId)}/locks`), "released", "agent lock release");
|
|
1446
2127
|
}
|
|
1447
|
-
cleanExpiredLocks() {
|
|
1448
|
-
return this.post("/api/locks/clean", {});
|
|
2128
|
+
async cleanExpiredLocks() {
|
|
2129
|
+
return decodeCountReceipt(await this.post("/api/locks/clean", {}), "cleaned", "lock cleanup");
|
|
1449
2130
|
}
|
|
1450
2131
|
createTask(input) {
|
|
1451
2132
|
return this.post("/api/tasks", input);
|
|
@@ -1567,27 +2248,53 @@ class MementosClient {
|
|
|
1567
2248
|
rollbackSynthesis(runId) {
|
|
1568
2249
|
return this.post(`/api/synthesis/rollback/${runId}`, {});
|
|
1569
2250
|
}
|
|
1570
|
-
ingestSession(input) {
|
|
1571
|
-
return this.post("/api/sessions/ingest", input);
|
|
2251
|
+
async ingestSession(input) {
|
|
2252
|
+
return decodeSessionIngestReceipt(await this.post("/api/sessions/ingest", input), input.transcript, input.session_id);
|
|
1572
2253
|
}
|
|
1573
|
-
getSessionJob(jobId) {
|
|
1574
|
-
return this.get(`/api/sessions/jobs/${jobId}`);
|
|
2254
|
+
async getSessionJob(jobId) {
|
|
2255
|
+
return decodeSessionJob(await this.get(`/api/sessions/jobs/${encodeURIComponent(jobId)}`), 0);
|
|
1575
2256
|
}
|
|
1576
|
-
listSessionJobs(filter) {
|
|
2257
|
+
async listSessionJobs(filter) {
|
|
1577
2258
|
const params = new URLSearchParams;
|
|
1578
2259
|
if (filter?.agent_id)
|
|
1579
2260
|
params.set("agent_id", filter.agent_id);
|
|
1580
2261
|
if (filter?.project_id)
|
|
1581
2262
|
params.set("project_id", filter.project_id);
|
|
2263
|
+
if (filter?.session_id)
|
|
2264
|
+
params.set("session_id", filter.session_id);
|
|
1582
2265
|
if (filter?.status)
|
|
1583
2266
|
params.set("status", filter.status);
|
|
1584
|
-
if (filter?.limit)
|
|
2267
|
+
if (filter?.limit !== undefined)
|
|
1585
2268
|
params.set("limit", String(filter.limit));
|
|
2269
|
+
if (filter?.offset !== undefined)
|
|
2270
|
+
params.set("offset", String(filter.offset));
|
|
1586
2271
|
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
1587
|
-
|
|
2272
|
+
const response = await this.get(`/api/sessions/jobs${qs}`);
|
|
2273
|
+
const page = decodeSessionJobsPage(response);
|
|
2274
|
+
if (filter?.limit !== undefined && page.limit !== filter.limit) {
|
|
2275
|
+
throw sessionProtocolError("server did not preserve requested 'limit'");
|
|
2276
|
+
}
|
|
2277
|
+
if (filter?.offset !== undefined && page.offset !== filter.offset) {
|
|
2278
|
+
throw sessionProtocolError("server did not preserve requested 'offset'");
|
|
2279
|
+
}
|
|
2280
|
+
for (const job of page.jobs) {
|
|
2281
|
+
if (filter?.agent_id !== undefined && job.agent_id !== filter.agent_id) {
|
|
2282
|
+
throw sessionProtocolError("server did not preserve requested 'agent_id'");
|
|
2283
|
+
}
|
|
2284
|
+
if (filter?.project_id !== undefined && job.project_id !== filter.project_id) {
|
|
2285
|
+
throw sessionProtocolError("server did not preserve requested 'project_id'");
|
|
2286
|
+
}
|
|
2287
|
+
if (filter?.session_id !== undefined && job.session_id !== filter.session_id) {
|
|
2288
|
+
throw sessionProtocolError("server did not preserve requested 'session_id'");
|
|
2289
|
+
}
|
|
2290
|
+
if (filter?.status !== undefined && job.status !== filter.status) {
|
|
2291
|
+
throw sessionProtocolError("server did not preserve requested 'status'");
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
return page;
|
|
1588
2295
|
}
|
|
1589
|
-
getSessionQueueStats() {
|
|
1590
|
-
return this.get("/api/sessions/queue/stats");
|
|
2296
|
+
async getSessionQueueStats() {
|
|
2297
|
+
return decodeQueueStats(await this.get("/api/sessions/queue/stats"));
|
|
1591
2298
|
}
|
|
1592
2299
|
}
|
|
1593
2300
|
var sdk_default = MementosClient;
|
|
@@ -1596,8 +2303,16 @@ export {
|
|
|
1596
2303
|
resolveMementosApiBase,
|
|
1597
2304
|
sdk_default as default,
|
|
1598
2305
|
__resetMementosSdkLocalNotice,
|
|
2306
|
+
SESSION_JOBS_PAGE_CONTRACT,
|
|
1599
2307
|
MementosError,
|
|
1600
2308
|
MementosConfigError,
|
|
1601
2309
|
MementosClient,
|
|
1602
|
-
|
|
2310
|
+
MEMENTOS_MACHINE_TOUCH_CONTRACT,
|
|
2311
|
+
MEMENTOS_MACHINE_REGISTRATION_CONTRACT,
|
|
2312
|
+
MEMENTOS_MACHINE_MUTATION_CONTRACT,
|
|
2313
|
+
MEMENTOS_MACHINE_LIST_CONTRACT,
|
|
2314
|
+
MEMENTOS_DEFAULT_BASE_URL,
|
|
2315
|
+
AUDIT_TRAIL_CONTRACT as MEMENTOS_AUDIT_TRAIL_CONTRACT,
|
|
2316
|
+
AUDIT_STATS_CONTRACT as MEMENTOS_AUDIT_STATS_CONTRACT,
|
|
2317
|
+
AUDIT_EXPORT_CONTRACT as MEMENTOS_AUDIT_EXPORT_CONTRACT
|
|
1603
2318
|
};
|