@hasna/mementos 0.14.88 → 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 +119 -30
- package/bun.lock +4 -65
- 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/info-stale.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-chain.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-search.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-tail.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/storage.d.ts.map +1 -1
- package/dist/cli/commands/system-doctor.d.ts.map +1 -1
- package/dist/cli/commands/system-session.d.ts.map +1 -1
- package/dist/cli/commands/system-status.d.ts +43 -0
- package/dist/cli/commands/system-status.d.ts.map +1 -0
- package/dist/cli/commands/system-synthesized-profile.d.ts.map +1 -1
- package/dist/cli/commands/system.d.ts.map +1 -1
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/index.js +4964 -2168
- package/dist/cli/register-all.d.ts +1 -1
- package/dist/cli/register-all.d.ts.map +1 -1
- package/dist/db/api-mode.d.ts +145 -37
- 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/db/store-backend.d.ts +8 -2
- package/dist/db/store-backend.d.ts.map +1 -1
- package/dist/diagnostics/historical-project-registration-receipt.js +299 -50
- package/dist/generated/storage-kit/backend.d.ts +12 -2
- package/dist/generated/storage-kit/backend.d.ts.map +1 -1
- package/dist/generated/storage-kit/index.d.ts +1 -1
- package/dist/generated/storage-kit/index.d.ts.map +1 -1
- package/dist/generated/storage-kit/migrations.d.ts +21 -0
- package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
- package/dist/generated/storage-kit/pool.d.ts +2 -5
- package/dist/generated/storage-kit/pool.d.ts.map +1 -1
- package/dist/generated/storage-kit/query.d.ts +1 -1
- package/dist/generated/storage-kit/query.d.ts.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4328 -1485
- package/dist/lib/built-in-hooks.d.ts.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/env.d.ts +10 -0
- package/dist/lib/env.d.ts.map +1 -0
- package/dist/lib/gatherer.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/local-opt-in.d.ts +114 -0
- package/dist/lib/local-opt-in.d.ts.map +1 -0
- package/dist/lib/machine-visibility.d.ts.map +1 -1
- package/dist/lib/memory-lock.d.ts.map +1 -1
- package/dist/lib/paths.d.ts +9 -0
- package/dist/lib/paths.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/redact.d.ts +70 -10
- package/dist/lib/redact.d.ts.map +1 -1
- package/dist/lib/reflection.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 +6215 -2598
- 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 +549 -205
- package/dist/sdk/index.d.ts +282 -14
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +1691 -35
- package/dist/server/auth.d.ts +2 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/helpers.d.ts +21 -7
- package/dist/server/helpers.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +6040 -2475
- 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/storage.d.ts.map +1 -1
- package/dist/storage.js +78 -129
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/docker-entrypoint.sh +7 -0
- package/hasna.contract.json +19 -7
- package/package.json +12 -11
- package/postinstall.mjs +52 -1
- package/dashboard/bun.lock +0 -519
- 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/cli/brains.d.ts +0 -3
- package/dist/cli/brains.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/lib/retired-storage-mode.d.ts +0 -11
- package/dist/lib/retired-storage-mode.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 -108
- 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
|
@@ -72,16 +72,7 @@ function serverDataBackendEnvKeys(name) {
|
|
|
72
72
|
databaseUrlKeys: [`HASNA_${token}_DATABASE_URL`, `${token}_DATABASE_URL`]
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
|
-
function
|
|
76
|
-
const token = envToken(name);
|
|
77
|
-
return [
|
|
78
|
-
`HASNA_${token}_STORAGE_MODE`,
|
|
79
|
-
`HASNA_${token}_MODE`,
|
|
80
|
-
`${token}_STORAGE_MODE`,
|
|
81
|
-
`${token}_MODE`
|
|
82
|
-
];
|
|
83
|
-
}
|
|
84
|
-
function firstEnv(env, keys) {
|
|
75
|
+
function firstDatabaseUrl(env, keys) {
|
|
85
76
|
for (const key of keys) {
|
|
86
77
|
const value = ownString(env, key)?.trim();
|
|
87
78
|
if (value)
|
|
@@ -89,23 +80,8 @@ function firstEnv(env, keys) {
|
|
|
89
80
|
}
|
|
90
81
|
return null;
|
|
91
82
|
}
|
|
92
|
-
function firstDefinedEnvKey(env, keys) {
|
|
93
|
-
for (const key of keys) {
|
|
94
|
-
if (Object.hasOwn(env, key) && env[key] !== undefined)
|
|
95
|
-
return key;
|
|
96
|
-
}
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
function assertNoLegacyStorageMode(name, env = process.env) {
|
|
100
|
-
const legacyKey = firstDefinedEnvKey(env, legacyModeKeys(name));
|
|
101
|
-
if (!legacyKey)
|
|
102
|
-
return;
|
|
103
|
-
const canonicalDatabaseUrl = serverDataBackendEnvKeys(name).databaseUrlKeys[0];
|
|
104
|
-
throw new Error(`${legacyKey} was removed. Delete the storage-mode variable; ` + `set ${canonicalDatabaseUrl} to select the postgresql server backend, ` + `or leave it unset for sqlite.`);
|
|
105
|
-
}
|
|
106
83
|
function resolveServerDataBackend(name, env = process.env) {
|
|
107
|
-
|
|
108
|
-
const databaseUrl = firstEnv(env, serverDataBackendEnvKeys(name).databaseUrlKeys);
|
|
84
|
+
const databaseUrl = firstDatabaseUrl(env, serverDataBackendEnvKeys(name).databaseUrlKeys);
|
|
109
85
|
if (!databaseUrl) {
|
|
110
86
|
return {
|
|
111
87
|
backend: "sqlite",
|
|
@@ -122,113 +98,69 @@ function resolveServerDataBackend(name, env = process.env) {
|
|
|
122
98
|
};
|
|
123
99
|
}
|
|
124
100
|
function resolveDatabaseUrl(name, env = process.env) {
|
|
125
|
-
|
|
126
|
-
const hit = firstEnv(env, serverDataBackendEnvKeys(name).databaseUrlKeys);
|
|
127
|
-
return hit?.value ?? null;
|
|
101
|
+
return firstDatabaseUrl(env, serverDataBackendEnvKeys(name).databaseUrlKeys)?.value ?? null;
|
|
128
102
|
}
|
|
129
103
|
var init_backend = () => {};
|
|
130
104
|
|
|
131
|
-
// src/lib/
|
|
132
|
-
|
|
133
|
-
for (const key of keys) {
|
|
134
|
-
if (Object.hasOwn(env, key) && env[key] !== undefined)
|
|
135
|
-
return key;
|
|
136
|
-
}
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
function assertNoLegacyStorageMode2(env = process.env) {
|
|
140
|
-
const legacyKey = firstDefinedEnvKey2(env, LEGACY_STORAGE_MODE_KEYS);
|
|
141
|
-
if (!legacyKey)
|
|
142
|
-
return;
|
|
143
|
-
throw new Error(`${legacyKey} was removed. Deployment modes no longer exist: delete the storage-mode variable. ` + `The client uses the local SQLite store, or the HTTP API selected by ` + `HASNA_MEMENTOS_API_URL + HASNA_MEMENTOS_API_KEY. ` + `On the server, set HASNA_MEMENTOS_DATABASE_URL to select the postgresql backend, ` + `or leave it unset for sqlite.`);
|
|
144
|
-
}
|
|
145
|
-
var LEGACY_STORAGE_MODE_KEYS;
|
|
146
|
-
var init_retired_storage_mode = __esm(() => {
|
|
147
|
-
LEGACY_STORAGE_MODE_KEYS = [
|
|
148
|
-
"HASNA_MEMENTOS_STORAGE_MODE",
|
|
149
|
-
"HASNA_MEMENTOS_MODE",
|
|
150
|
-
"MEMENTOS_STORAGE_MODE",
|
|
151
|
-
"MEMENTOS_MODE"
|
|
152
|
-
];
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
|
|
105
|
+
// src/lib/paths.ts
|
|
106
|
+
import { existsSync } from "fs";
|
|
156
107
|
import { homedir } from "os";
|
|
157
|
-
import { join } from "path";
|
|
158
|
-
|
|
108
|
+
import { join, resolve } from "path";
|
|
109
|
+
import { homedir as pathsResolverHomedir } from "os";
|
|
110
|
+
import { join as pathsResolverJoin } from "path";
|
|
111
|
+
function pathsResolverAssertApp(app) {
|
|
159
112
|
if (typeof app !== "string" || app.length === 0) {
|
|
160
113
|
throw new TypeError("paths: app must be a non-empty string");
|
|
161
114
|
}
|
|
162
|
-
if (!
|
|
115
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
|
|
163
116
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
164
117
|
}
|
|
165
118
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const value = envOf(options)[KIND_ENV[kind]];
|
|
171
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
172
|
-
}
|
|
173
|
-
function isMacOS(platform) {
|
|
174
|
-
return platform === "darwin";
|
|
119
|
+
function pathsResolverAssertKind(kind) {
|
|
120
|
+
if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
|
|
121
|
+
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
|
|
122
|
+
}
|
|
175
123
|
}
|
|
176
|
-
function
|
|
177
|
-
|
|
178
|
-
|
|
124
|
+
function pathsResolverBaseDir(kind, options) {
|
|
125
|
+
pathsResolverAssertKind(kind);
|
|
126
|
+
const env = options.env ?? process.env;
|
|
127
|
+
const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
|
|
128
|
+
if (typeof override === "string" && override.length > 0)
|
|
179
129
|
return override;
|
|
180
|
-
const home = options.home ??
|
|
130
|
+
const home = options.home ?? pathsResolverHomedir();
|
|
181
131
|
const platform = options.platform ?? process.platform;
|
|
182
|
-
if (
|
|
132
|
+
if (platform === "darwin") {
|
|
183
133
|
switch (kind) {
|
|
184
|
-
case "config":
|
|
185
134
|
case "data":
|
|
186
|
-
return
|
|
135
|
+
return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
|
|
187
136
|
case "cache":
|
|
188
|
-
return
|
|
137
|
+
return pathsResolverJoin(home, "Library", "Caches", "Hasna");
|
|
189
138
|
case "state":
|
|
190
|
-
return
|
|
139
|
+
return pathsResolverJoin(home, "Library", "Logs", "Hasna");
|
|
191
140
|
}
|
|
192
141
|
}
|
|
193
142
|
switch (kind) {
|
|
194
|
-
case "config":
|
|
195
|
-
return join(home, ".config", "hasna");
|
|
196
143
|
case "data":
|
|
197
|
-
return
|
|
144
|
+
return pathsResolverJoin(home, ".local", "share", "hasna");
|
|
198
145
|
case "state":
|
|
199
|
-
return
|
|
146
|
+
return pathsResolverJoin(home, ".local", "state", "hasna");
|
|
200
147
|
case "cache":
|
|
201
|
-
return
|
|
148
|
+
return pathsResolverJoin(home, ".cache", "hasna");
|
|
202
149
|
}
|
|
203
150
|
}
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
const appSegment = options.internal === true ?
|
|
207
|
-
return
|
|
151
|
+
function pathsResolverResolve(kind, options) {
|
|
152
|
+
pathsResolverAssertApp(options.app);
|
|
153
|
+
const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
|
|
154
|
+
return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
|
|
208
155
|
}
|
|
209
156
|
function dataDir(options) {
|
|
210
|
-
return
|
|
157
|
+
return pathsResolverResolve("data", options);
|
|
211
158
|
}
|
|
212
|
-
var KIND_ENV, APP_SLUG_RE;
|
|
213
|
-
var init_dist = __esm(() => {
|
|
214
|
-
KIND_ENV = {
|
|
215
|
-
config: "HASNA_CONFIG_HOME",
|
|
216
|
-
data: "HASNA_DATA_HOME",
|
|
217
|
-
state: "HASNA_STATE_HOME",
|
|
218
|
-
cache: "HASNA_CACHE_HOME"
|
|
219
|
-
};
|
|
220
|
-
APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// src/lib/paths.ts
|
|
224
|
-
import { existsSync } from "fs";
|
|
225
|
-
import { homedir as homedir2 } from "os";
|
|
226
|
-
import { join as join2, resolve } from "path";
|
|
227
159
|
function effectiveHome() {
|
|
228
|
-
return process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
160
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir() || "/tmp";
|
|
229
161
|
}
|
|
230
162
|
function legacyDataRoot() {
|
|
231
|
-
return
|
|
163
|
+
return join(effectiveHome(), ".hasna", "mementos");
|
|
232
164
|
}
|
|
233
165
|
function resolverDataRoot() {
|
|
234
166
|
return dataDir({
|
|
@@ -240,7 +172,7 @@ function adoptResolverDataRoot(resolved, env = process.env) {
|
|
|
240
172
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
241
173
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
242
174
|
return true;
|
|
243
|
-
return existsSync(
|
|
175
|
+
return existsSync(join(resolved, "mementos.db"));
|
|
244
176
|
}
|
|
245
177
|
function exactDataRoot() {
|
|
246
178
|
for (const key of ["HASNA_MEMENTOS_HOME", "MEMENTOS_HOME"]) {
|
|
@@ -257,14 +189,34 @@ function getDataRoot() {
|
|
|
257
189
|
const resolved = resolverDataRoot();
|
|
258
190
|
return adoptResolverDataRoot(resolved) ? resolve(resolved) : resolve(legacyDataRoot());
|
|
259
191
|
}
|
|
192
|
+
var PATHS_RESOLVER_KIND_ENV, PATHS_RESOLVER_APP_SLUG_RE;
|
|
260
193
|
var init_paths = __esm(() => {
|
|
261
|
-
|
|
194
|
+
PATHS_RESOLVER_KIND_ENV = {
|
|
195
|
+
data: "HASNA_DATA_HOME",
|
|
196
|
+
state: "HASNA_STATE_HOME",
|
|
197
|
+
cache: "HASNA_CACHE_HOME"
|
|
198
|
+
};
|
|
199
|
+
PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// src/lib/env.ts
|
|
203
|
+
var alias = (canonical, legacy) => process.env[canonical] ?? process.env[legacy], env;
|
|
204
|
+
var init_env = __esm(() => {
|
|
205
|
+
env = {
|
|
206
|
+
apiKey: () => alias("HASNA_MEMENTOS_API_KEY", "MEMENTOS_API_KEY"),
|
|
207
|
+
defaultScope: () => alias("HASNA_MEMENTOS_DEFAULT_SCOPE", "MEMENTOS_DEFAULT_SCOPE"),
|
|
208
|
+
defaultCategory: () => alias("HASNA_MEMENTOS_DEFAULT_CATEGORY", "MEMENTOS_DEFAULT_CATEGORY"),
|
|
209
|
+
defaultImportance: () => alias("HASNA_MEMENTOS_DEFAULT_IMPORTANCE", "MEMENTOS_DEFAULT_IMPORTANCE"),
|
|
210
|
+
pgsyncQueryTimeoutMs: () => alias("HASNA_MEMENTOS_PGSYNC_QUERY_TIMEOUT_MS", "MEMENTOS_PGSYNC_QUERY_TIMEOUT_MS"),
|
|
211
|
+
reflectProvider: () => alias("HASNA_MEMENTOS_REFLECT_PROVIDER", "MEMENTOS_REFLECT_PROVIDER"),
|
|
212
|
+
reflectModel: () => alias("HASNA_MEMENTOS_REFLECT_MODEL", "MEMENTOS_REFLECT_MODEL")
|
|
213
|
+
};
|
|
262
214
|
});
|
|
263
215
|
|
|
264
216
|
// src/storage.ts
|
|
265
217
|
import { Database } from "bun:sqlite";
|
|
266
218
|
import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
267
|
-
import { join as
|
|
219
|
+
import { join as join2 } from "path";
|
|
268
220
|
import { fileURLToPath } from "url";
|
|
269
221
|
import { Worker } from "worker_threads";
|
|
270
222
|
import pg from "pg";
|
|
@@ -289,6 +241,7 @@ class SqliteAdapter {
|
|
|
289
241
|
db;
|
|
290
242
|
constructor(path) {
|
|
291
243
|
this.db = new Database(path, { create: true });
|
|
244
|
+
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
292
245
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
293
246
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
294
247
|
}
|
|
@@ -539,9 +492,9 @@ function getConfigPath() {
|
|
|
539
492
|
return STORAGE_CONFIG_PATH;
|
|
540
493
|
}
|
|
541
494
|
function getStorageDatabaseEnv() {
|
|
542
|
-
for (const
|
|
543
|
-
if (readEnv(
|
|
544
|
-
return
|
|
495
|
+
for (const env2 of DATABASE_ENV_NAMES) {
|
|
496
|
+
if (readEnv(env2.name))
|
|
497
|
+
return env2;
|
|
545
498
|
}
|
|
546
499
|
return null;
|
|
547
500
|
}
|
|
@@ -551,20 +504,19 @@ function getStorageEnvName(key) {
|
|
|
551
504
|
return readEnv(canonical) || !readEnv(fallback) ? canonical : fallback;
|
|
552
505
|
}
|
|
553
506
|
function getStorageDatabaseUrl() {
|
|
554
|
-
const
|
|
555
|
-
return
|
|
507
|
+
const env2 = getStorageDatabaseEnv();
|
|
508
|
+
return env2 ? readEnv(env2.name) : null;
|
|
556
509
|
}
|
|
557
510
|
function getStorageDatabaseEnvName() {
|
|
558
511
|
return getStorageEnvName("databaseUrl");
|
|
559
512
|
}
|
|
560
|
-
function getStorageBackend(
|
|
561
|
-
return resolveServerDataBackend("mementos",
|
|
513
|
+
function getStorageBackend(env2 = process.env) {
|
|
514
|
+
return resolveServerDataBackend("mementos", env2).backend;
|
|
562
515
|
}
|
|
563
|
-
function getStorageBackendDatabaseUrl(
|
|
564
|
-
return resolveDatabaseUrl("mementos",
|
|
516
|
+
function getStorageBackendDatabaseUrl(env2 = process.env) {
|
|
517
|
+
return resolveDatabaseUrl("mementos", env2);
|
|
565
518
|
}
|
|
566
519
|
function getStorageConfig() {
|
|
567
|
-
assertNoLegacyStorageMode2();
|
|
568
520
|
const fileConfig = readConfigFile();
|
|
569
521
|
const merged = {
|
|
570
522
|
...DEFAULT_STORAGE_CONFIG,
|
|
@@ -651,14 +603,14 @@ function storageEnvStatus(key) {
|
|
|
651
603
|
};
|
|
652
604
|
}
|
|
653
605
|
function postgresBackendStatus(config) {
|
|
654
|
-
const
|
|
655
|
-
const envUrl =
|
|
656
|
-
if (
|
|
606
|
+
const env2 = getStorageDatabaseEnv();
|
|
607
|
+
const envUrl = env2 ? readEnv(env2.name) : null;
|
|
608
|
+
if (env2 && envUrl) {
|
|
657
609
|
const validation = validatePostgresConnectionString(envUrl);
|
|
658
610
|
return {
|
|
659
611
|
configured: validation.ok,
|
|
660
612
|
source: "env",
|
|
661
|
-
env_name:
|
|
613
|
+
env_name: env2.name,
|
|
662
614
|
redacted_url: validation.redacted_url,
|
|
663
615
|
missing: [],
|
|
664
616
|
issues: validation.issues,
|
|
@@ -704,7 +656,6 @@ function getSafeStorageConfigSummary(config = getStorageConfig()) {
|
|
|
704
656
|
};
|
|
705
657
|
}
|
|
706
658
|
function getStorageStatus() {
|
|
707
|
-
assertNoLegacyStorageMode2();
|
|
708
659
|
const config = getStorageConfig();
|
|
709
660
|
const backend = getStorageBackend();
|
|
710
661
|
const postgresRequested = backend === "postgresql";
|
|
@@ -814,14 +765,12 @@ function resolveConfiguredConnectionString(dbName) {
|
|
|
814
765
|
return `postgres://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbName}${sslParam}`;
|
|
815
766
|
}
|
|
816
767
|
function getStorageConnectionString(dbName = "mementos") {
|
|
817
|
-
assertNoLegacyStorageMode2();
|
|
818
768
|
if (!isServerContext()) {
|
|
819
769
|
throw new Error("Refusing to construct an RDS Postgres DSN outside the mementos-serve server. " + "The raw database DSN is NEVER distributed to client machines. " + "Clients must use the HTTP API: set HASNA_MEMENTOS_API_URL and " + "HASNA_MEMENTOS_API_KEY (and unset HASNA_MEMENTOS_DATABASE_URL).");
|
|
820
770
|
}
|
|
821
771
|
return resolveConfiguredConnectionString(dbName);
|
|
822
772
|
}
|
|
823
773
|
function getStorageConnectionStringForOperator(dbName = "mementos") {
|
|
824
|
-
assertNoLegacyStorageMode2();
|
|
825
774
|
return resolveConfiguredConnectionString(dbName);
|
|
826
775
|
}
|
|
827
776
|
function isSyncExcludedTable(table) {
|
|
@@ -992,8 +941,8 @@ CREATE TABLE IF NOT EXISTS _sync_meta (
|
|
|
992
941
|
)`;
|
|
993
942
|
var init_storage = __esm(() => {
|
|
994
943
|
init_backend();
|
|
995
|
-
init_retired_storage_mode();
|
|
996
944
|
init_paths();
|
|
945
|
+
init_env();
|
|
997
946
|
PgSyncPool = class PgSyncPool {
|
|
998
947
|
worker;
|
|
999
948
|
status;
|
|
@@ -1003,7 +952,7 @@ var init_storage = __esm(() => {
|
|
|
1003
952
|
generation = 0;
|
|
1004
953
|
static DATA_BYTES = 128 * 1024 * 1024;
|
|
1005
954
|
static queryTimeoutMs() {
|
|
1006
|
-
const raw =
|
|
955
|
+
const raw = env.pgsyncQueryTimeoutMs()?.trim();
|
|
1007
956
|
const parsed = raw ? Number(raw) : Number.NaN;
|
|
1008
957
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 60000;
|
|
1009
958
|
}
|
|
@@ -1011,9 +960,9 @@ var init_storage = __esm(() => {
|
|
|
1011
960
|
const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
1012
961
|
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
1013
962
|
const candidates = [
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
963
|
+
join2(here, `pg-sync-worker${ext}`),
|
|
964
|
+
join2(here, "..", `pg-sync-worker${ext}`),
|
|
965
|
+
join2(here, "..", "..", `pg-sync-worker${ext}`)
|
|
1017
966
|
];
|
|
1018
967
|
for (const candidate of candidates) {
|
|
1019
968
|
if (existsSync2(candidate))
|
|
@@ -1119,8 +1068,8 @@ var init_storage = __esm(() => {
|
|
|
1119
1068
|
schedule_minutes: 0
|
|
1120
1069
|
}
|
|
1121
1070
|
};
|
|
1122
|
-
STORAGE_CONFIG_DIR =
|
|
1123
|
-
STORAGE_CONFIG_PATH =
|
|
1071
|
+
STORAGE_CONFIG_DIR = join2(LOCAL_DATA_DIR, "storage");
|
|
1072
|
+
STORAGE_CONFIG_PATH = join2(STORAGE_CONFIG_DIR, "config.json");
|
|
1124
1073
|
DATABASE_ENV_NAMES = [
|
|
1125
1074
|
{ name: MEMENTOS_STORAGE_ENV.databaseUrl, deprecated: false },
|
|
1126
1075
|
{ name: MEMENTOS_STORAGE_FALLBACK_ENV.databaseUrl, deprecated: false }
|
|
@@ -1144,28 +1093,96 @@ var init_storage = __esm(() => {
|
|
|
1144
1093
|
];
|
|
1145
1094
|
});
|
|
1146
1095
|
|
|
1096
|
+
// src/lib/local-opt-in.ts
|
|
1097
|
+
import {
|
|
1098
|
+
clientTransportEnvKeys,
|
|
1099
|
+
credentialOverrideEnvKey,
|
|
1100
|
+
credentialPointerEnvKey,
|
|
1101
|
+
CREDENTIAL_PROFILE_ENV_KEY
|
|
1102
|
+
} from "@hasna/contracts/client";
|
|
1103
|
+
function isMementosLocalOptIn(env2 = process.env) {
|
|
1104
|
+
return MEMENTOS_LOCAL_OPT_IN_ENV_KEYS.some((key) => (env2[key] ?? "").trim() !== "");
|
|
1105
|
+
}
|
|
1106
|
+
function hasExplicitLocalDbPath(env2 = process.env) {
|
|
1107
|
+
return MEMENTOS_DB_PATH_ENV_KEYS.some((key) => (env2[key] ?? "").trim() !== "");
|
|
1108
|
+
}
|
|
1109
|
+
function mementosAuthorityEnvKeys() {
|
|
1110
|
+
const keys = clientTransportEnvKeys("mementos");
|
|
1111
|
+
return [
|
|
1112
|
+
...keys.apiUrlKeys,
|
|
1113
|
+
...keys.apiKeyKeys,
|
|
1114
|
+
credentialOverrideEnvKey("mementos"),
|
|
1115
|
+
credentialPointerEnvKey("mementos"),
|
|
1116
|
+
CREDENTIAL_PROFILE_ENV_KEY
|
|
1117
|
+
];
|
|
1118
|
+
}
|
|
1119
|
+
function hasMementosEnvAuthorityIntent(env2 = process.env) {
|
|
1120
|
+
return mementosAuthorityEnvKeys().some((key) => (env2[key] ?? "").trim() !== "");
|
|
1121
|
+
}
|
|
1122
|
+
function selectsMementosLocalStore(env2 = process.env) {
|
|
1123
|
+
if (hasExplicitLocalDbPath(env2))
|
|
1124
|
+
return true;
|
|
1125
|
+
return !hasMementosEnvAuthorityIntent(env2) && isMementosLocalOptIn(env2);
|
|
1126
|
+
}
|
|
1127
|
+
function mementosResolverEnv(env2) {
|
|
1128
|
+
const blanks = mementosAuthorityEnvKeys().filter((key) => (key in env2) && (env2[key] ?? "").trim() === "");
|
|
1129
|
+
if (blanks.length === 0)
|
|
1130
|
+
return env2;
|
|
1131
|
+
const next = { ...env2 };
|
|
1132
|
+
for (const key of blanks)
|
|
1133
|
+
delete next[key];
|
|
1134
|
+
return next;
|
|
1135
|
+
}
|
|
1136
|
+
function isAmbientMementosEnv(env2) {
|
|
1137
|
+
if (typeof process !== "undefined" && env2 === process.env)
|
|
1138
|
+
return true;
|
|
1139
|
+
return env2[CONTRACTS_AMBIENT_ENVIRONMENT] === true;
|
|
1140
|
+
}
|
|
1141
|
+
function mementosResolverInputs(env2, credentials = {}) {
|
|
1142
|
+
const normalised = mementosResolverEnv(env2);
|
|
1143
|
+
if (normalised === env2)
|
|
1144
|
+
return { env: normalised, credentials };
|
|
1145
|
+
const keychain = { ...credentials.keychain };
|
|
1146
|
+
if (keychain.enabled === undefined && keychain.run === undefined) {
|
|
1147
|
+
keychain.enabled = isAmbientMementosEnv(env2);
|
|
1148
|
+
}
|
|
1149
|
+
return { env: normalised, credentials: { ...credentials, keychain } };
|
|
1150
|
+
}
|
|
1151
|
+
var MEMENTOS_LOCAL_OPT_IN_ENV_KEYS, MEMENTOS_DB_PATH_ENV_KEYS, CONTRACTS_AMBIENT_ENVIRONMENT;
|
|
1152
|
+
var init_local_opt_in = __esm(() => {
|
|
1153
|
+
MEMENTOS_LOCAL_OPT_IN_ENV_KEYS = ["HASNA_MEMENTOS_LOCAL", "MEMENTOS_LOCAL"];
|
|
1154
|
+
MEMENTOS_DB_PATH_ENV_KEYS = ["HASNA_MEMENTOS_DB_PATH", "MEMENTOS_DB_PATH"];
|
|
1155
|
+
CONTRACTS_AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1147
1158
|
// src/db/api-mode.ts
|
|
1148
1159
|
import { tmpdir } from "os";
|
|
1149
|
-
import { join as
|
|
1160
|
+
import { join as join3 } from "path";
|
|
1150
1161
|
import { writeFileSync as writeFileSync2, unlinkSync } from "fs";
|
|
1151
1162
|
import { randomUUID } from "crypto";
|
|
1152
|
-
|
|
1163
|
+
import {
|
|
1164
|
+
clientTransportEnvKeys as clientTransportEnvKeys2,
|
|
1165
|
+
resolveClientTransport,
|
|
1166
|
+
resolveCredential,
|
|
1167
|
+
toV1BaseUrl,
|
|
1168
|
+
ClientTransportConfigurationError
|
|
1169
|
+
} from "@hasna/contracts/client";
|
|
1170
|
+
function firstEnv(keys, env2 = process.env) {
|
|
1153
1171
|
for (const k of keys) {
|
|
1154
|
-
const v =
|
|
1172
|
+
const v = env2[k]?.trim();
|
|
1155
1173
|
if (v)
|
|
1156
1174
|
return v;
|
|
1157
1175
|
}
|
|
1158
1176
|
return;
|
|
1159
1177
|
}
|
|
1160
|
-
function
|
|
1161
|
-
|
|
1162
|
-
if (process.env[k]?.trim())
|
|
1163
|
-
return k;
|
|
1164
|
-
}
|
|
1165
|
-
return null;
|
|
1178
|
+
function hasDatabaseUrl(env2 = process.env) {
|
|
1179
|
+
return Boolean(firstEnv(DATABASE_URL_ENV_KEYS, env2));
|
|
1166
1180
|
}
|
|
1167
|
-
function
|
|
1168
|
-
|
|
1181
|
+
function assertCleanResolvedBase(baseUrl) {
|
|
1182
|
+
if (/[?#]/.test(baseUrl)) {
|
|
1183
|
+
throw new Error("mementos base URL must not contain userinfo, query, or fragment data");
|
|
1184
|
+
}
|
|
1185
|
+
return baseUrl;
|
|
1169
1186
|
}
|
|
1170
1187
|
function isLoopbackHost(rawHost) {
|
|
1171
1188
|
const host = rawHost.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
|
|
@@ -1186,50 +1203,63 @@ function assertRequestAllowedUnderTest(baseUrl) {
|
|
|
1186
1203
|
return;
|
|
1187
1204
|
throw new Error("api-mode: REFUSING to make a cloud request from a test process \u2014 this would write to or read " + "from the SHARED PRODUCTION memory store, where test fixtures are indistinguishable from real " + `memories.
|
|
1188
1205
|
` + ` host : ${host || "(unparseable base URL)"}
|
|
1189
|
-
` +
|
|
1206
|
+
` + " how this happens: a selector set at module scope (after the bun test preload ran), or `bun test` " + `invoked from a directory with no bunfig.toml so the preload never loaded.
|
|
1190
1207
|
` + " fix : build the child/process env via src/test-support/store-isolation.ts, or point the " + `suite at a loopback stub.
|
|
1191
1208
|
` + ` override : set ${ALLOW_REMOTE_API_IN_TESTS_ENV}=1 only for a test that must reach a remote endpoint.`);
|
|
1192
1209
|
}
|
|
1193
|
-
function
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
return base;
|
|
1197
|
-
return `${base}/v1`;
|
|
1210
|
+
function toStoreConfigError(error) {
|
|
1211
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1212
|
+
return new MementosStoreConfigError(`${message} Local SQLite is opt-in only (${DB_PATH_ENV_KEYS[0]}, or ${MEMENTOS_LOCAL_OPT_IN_ENV_KEYS[0]} = 1) and is disabled by default \u2014 failing closed`);
|
|
1198
1213
|
}
|
|
1199
|
-
function
|
|
1200
|
-
|
|
1201
|
-
if (firstEnvKey(DB_PATH_ENV_KEYS))
|
|
1202
|
-
return;
|
|
1203
|
-
if (hasDatabaseUrl())
|
|
1204
|
-
return;
|
|
1205
|
-
const urlKey = firstEnvKey(API_URL_ENV_KEYS);
|
|
1206
|
-
const keyKey = firstEnvKey(API_KEY_ENV_KEYS);
|
|
1207
|
-
if (urlKey && !keyKey) {
|
|
1208
|
-
throw new MementosStoreConfigError(`${urlKey} points at the cloud memory store but ${API_KEY_ENV_KEYS[0]} is not set. ` + `Refusing to serve the on-box SQLite store in its place, because it holds a different ` + `dataset. Set ${API_KEY_ENV_KEYS[0]} to reach the cloud store. If you meant to use the ` + `on-box SQLite store, unset ${urlKey} or set ${DB_PATH_ENV_KEYS[0]} explicitly.`);
|
|
1209
|
-
}
|
|
1210
|
-
if (keyKey && !urlKey) {
|
|
1211
|
-
throw new MementosStoreConfigError(`${keyKey} is set but ${API_URL_ENV_KEYS[0]} is not, so the cloud memory store cannot be ` + `reached. Refusing to serve the on-box SQLite store in its place, because it holds a ` + `different dataset. Set ${API_URL_ENV_KEYS[0]} to reach the cloud store. If you meant to ` + `use the on-box SQLite store, unset ${keyKey} or set ${DB_PATH_ENV_KEYS[0]} explicitly.`);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
function getApiConfig() {
|
|
1215
|
-
assertUnambiguousStoreEnv();
|
|
1216
|
-
if (firstEnvKey(DB_PATH_ENV_KEYS))
|
|
1214
|
+
function getApiConfig(env2 = process.env, options = {}) {
|
|
1215
|
+
if (hasDatabaseUrl(env2))
|
|
1217
1216
|
return null;
|
|
1218
|
-
|
|
1219
|
-
const apiKey = firstEnv2(API_KEY_ENV_KEYS);
|
|
1220
|
-
if (!rawBase || !apiKey)
|
|
1217
|
+
if (selectsMementosLocalStore(env2))
|
|
1221
1218
|
return null;
|
|
1222
|
-
|
|
1219
|
+
const inputs = mementosResolverInputs(env2, options.credentials);
|
|
1220
|
+
let credential;
|
|
1221
|
+
try {
|
|
1222
|
+
credential = resolveCredential("mementos", inputs.env, inputs.credentials);
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
throw toStoreConfigError(error);
|
|
1225
|
+
}
|
|
1226
|
+
let resolution;
|
|
1227
|
+
try {
|
|
1228
|
+
resolution = resolveClientTransport("mementos", inputs.env, {
|
|
1229
|
+
credentials: credential ? { ...inputs.credentials, apiKey: credential.apiKey } : inputs.credentials
|
|
1230
|
+
});
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
if (error instanceof ClientTransportConfigurationError && credential === null && /is not set and no API key could be resolved/.test(error.message)) {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
throw toStoreConfigError(error);
|
|
1236
|
+
}
|
|
1237
|
+
return { baseUrl: assertCleanResolvedBase(resolution.baseUrl), apiKey: credential.apiKey };
|
|
1223
1238
|
}
|
|
1224
1239
|
function isApiMode() {
|
|
1225
1240
|
if (hasDatabaseUrl())
|
|
1226
1241
|
return false;
|
|
1242
|
+
if (isServerContext())
|
|
1243
|
+
return false;
|
|
1227
1244
|
return getApiConfig() !== null;
|
|
1228
1245
|
}
|
|
1246
|
+
function assertClientStoreConfigured(env2 = process.env, options = {}) {
|
|
1247
|
+
if (selectsMementosLocalStore(env2))
|
|
1248
|
+
return;
|
|
1249
|
+
if (hasDatabaseUrl(env2))
|
|
1250
|
+
return;
|
|
1251
|
+
const config = getApiConfig(env2, options);
|
|
1252
|
+
if (config !== null)
|
|
1253
|
+
return;
|
|
1254
|
+
throw new MementosStoreConfigError(unconfiguredStoreMessage());
|
|
1255
|
+
}
|
|
1256
|
+
function unconfiguredStoreMessage() {
|
|
1257
|
+
return "mementos is not configured to reach any memory store, and will NOT fall back to the " + "on-box SQLite store (~/.hasna/mementos/mementos.db). " + "No credential could be resolved from the Keychain item hasna.credentials.mementos.api-key " + "(macOS only), ~/.hasna/mementos/config/credentials, or " + `${API_KEY_ENV_KEYS[0]}; the authority would be the fleet gateway ${"https://api.hasna.com/mementos"} ` + `(or ${API_URL_ENV_KEYS[0]} if set). ` + `Set ${API_URL_ENV_KEYS[0]} and ${API_KEY_ENV_KEYS[0]} (the resolver also accepts the legacy ` + `${API_URL_ENV_KEYS[1]} / ${API_KEY_ENV_KEYS[1]} aliases for one release) to use the fleet memory ` + `API, or opt into an explicit local SQLite file with ${DB_PATH_ENV_KEYS[0]} / ` + `${DB_PATH_ENV_KEYS[1]} (or ${MEMENTOS_LOCAL_OPT_IN_ENV_KEYS[0]}=1).`;
|
|
1258
|
+
}
|
|
1229
1259
|
function apiRequestRaw(method, path, body) {
|
|
1230
1260
|
const cfg = getApiConfig();
|
|
1231
1261
|
if (!cfg)
|
|
1232
|
-
throw new Error("api-mode: not configured (
|
|
1262
|
+
throw new Error("api-mode: not configured (no Hasna mementos credential resolved)");
|
|
1233
1263
|
assertRequestAllowedUnderTest(cfg.baseUrl);
|
|
1234
1264
|
const url = `${cfg.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
1235
1265
|
const hasBody = body !== undefined && body !== null;
|
|
@@ -1256,8 +1286,15 @@ x-api-key: ${cfg.apiKey}
|
|
|
1256
1286
|
];
|
|
1257
1287
|
let bodyFile;
|
|
1258
1288
|
if (hasBody) {
|
|
1259
|
-
bodyFile =
|
|
1260
|
-
|
|
1289
|
+
bodyFile = join3(tmpdir(), `mem-req-${process.pid}-${randomUUID()}.json`);
|
|
1290
|
+
try {
|
|
1291
|
+
writeFileSync2(bodyFile, JSON.stringify(body), { mode: 384 });
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
try {
|
|
1294
|
+
unlinkSync(bodyFile);
|
|
1295
|
+
} catch {}
|
|
1296
|
+
throw error;
|
|
1297
|
+
}
|
|
1261
1298
|
args.push("--data-binary", `@${bodyFile}`);
|
|
1262
1299
|
}
|
|
1263
1300
|
args.push(url);
|
|
@@ -1305,7 +1342,7 @@ function apiJson(method, path, body, options) {
|
|
|
1305
1342
|
try {
|
|
1306
1343
|
data = JSON.parse(raw.body);
|
|
1307
1344
|
} catch (e) {
|
|
1308
|
-
throw new ApiRequestError(`mementos cloud ${method} ${path} returned status ${raw.status} with a body that is not valid JSON (${e instanceof Error ? e.message : String(e)}) \u2014 the response is truncated or the server is unhealthy`, raw.status,
|
|
1345
|
+
throw new ApiRequestError(`mementos cloud ${method} ${path} returned status ${raw.status} with a body that is not valid JSON (${e instanceof Error ? e.message : String(e)}) \u2014 the response is truncated or the server is unhealthy`, raw.status, "");
|
|
1309
1346
|
}
|
|
1310
1347
|
} else {
|
|
1311
1348
|
data = undefined;
|
|
@@ -1344,13 +1381,14 @@ function toQuery(params) {
|
|
|
1344
1381
|
const s = sp.toString();
|
|
1345
1382
|
return s ? `?${s}` : "";
|
|
1346
1383
|
}
|
|
1347
|
-
var API_URL_ENV_KEYS, API_KEY_ENV_KEYS, DATABASE_URL_ENV_KEYS, ALLOW_REMOTE_API_IN_TESTS_ENV = "MEMENTOS_ALLOW_REMOTE_API_IN_TESTS",
|
|
1384
|
+
var API_URL_ENV_KEYS, API_KEY_ENV_KEYS, DATABASE_URL_ENV_KEYS, DB_PATH_ENV_KEYS, ALLOW_REMOTE_API_IN_TESTS_ENV = "MEMENTOS_ALLOW_REMOTE_API_IN_TESTS", MementosStoreConfigError, ApiRequestError, DEFAULT_TIMEOUT_S = "45";
|
|
1348
1385
|
var init_api_mode = __esm(() => {
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1386
|
+
init_local_opt_in();
|
|
1387
|
+
init_storage();
|
|
1388
|
+
API_URL_ENV_KEYS = clientTransportEnvKeys2("mementos").apiUrlKeys;
|
|
1389
|
+
API_KEY_ENV_KEYS = clientTransportEnvKeys2("mementos").apiKeyKeys;
|
|
1352
1390
|
DATABASE_URL_ENV_KEYS = ["HASNA_MEMENTOS_DATABASE_URL", "MEMENTOS_DATABASE_URL"];
|
|
1353
|
-
DB_PATH_ENV_KEYS =
|
|
1391
|
+
DB_PATH_ENV_KEYS = MEMENTOS_DB_PATH_ENV_KEYS;
|
|
1354
1392
|
MementosStoreConfigError = class MementosStoreConfigError extends Error {
|
|
1355
1393
|
code = "MEMENTOS_STORE_CONFIG";
|
|
1356
1394
|
constructor(message) {
|
|
@@ -2759,6 +2797,220 @@ INSERT OR IGNORE INTO _migrations (id) VALUES (38);
|
|
|
2759
2797
|
`
|
|
2760
2798
|
${sqliteMementosMemoryProjectLinkSchemaSql()}
|
|
2761
2799
|
INSERT OR IGNORE INTO _migrations (id) VALUES (40);
|
|
2800
|
+
`,
|
|
2801
|
+
`
|
|
2802
|
+
DROP TABLE IF EXISTS temp.mementos_m41_machine_preflight;
|
|
2803
|
+
CREATE TEMP TABLE mementos_m41_machine_preflight (
|
|
2804
|
+
id TEXT PRIMARY KEY,
|
|
2805
|
+
name TEXT NOT NULL CHECK(
|
|
2806
|
+
length(name) BETWEEN 1 AND 128
|
|
2807
|
+
AND instr(name, char(0)) = 0
|
|
2808
|
+
AND instr(name, char(1)) = 0
|
|
2809
|
+
AND instr(name, char(2)) = 0
|
|
2810
|
+
AND instr(name, char(3)) = 0
|
|
2811
|
+
AND instr(name, char(4)) = 0
|
|
2812
|
+
AND instr(name, char(5)) = 0
|
|
2813
|
+
AND instr(name, char(6)) = 0
|
|
2814
|
+
AND instr(name, char(7)) = 0
|
|
2815
|
+
AND instr(name, char(8)) = 0
|
|
2816
|
+
AND instr(name, char(9)) = 0
|
|
2817
|
+
AND instr(name, char(10)) = 0
|
|
2818
|
+
AND instr(name, char(11)) = 0
|
|
2819
|
+
AND instr(name, char(12)) = 0
|
|
2820
|
+
AND instr(name, char(13)) = 0
|
|
2821
|
+
AND instr(name, char(14)) = 0
|
|
2822
|
+
AND instr(name, char(15)) = 0
|
|
2823
|
+
AND instr(name, char(16)) = 0
|
|
2824
|
+
AND instr(name, char(17)) = 0
|
|
2825
|
+
AND instr(name, char(18)) = 0
|
|
2826
|
+
AND instr(name, char(19)) = 0
|
|
2827
|
+
AND instr(name, char(20)) = 0
|
|
2828
|
+
AND instr(name, char(21)) = 0
|
|
2829
|
+
AND instr(name, char(22)) = 0
|
|
2830
|
+
AND instr(name, char(23)) = 0
|
|
2831
|
+
AND instr(name, char(24)) = 0
|
|
2832
|
+
AND instr(name, char(25)) = 0
|
|
2833
|
+
AND instr(name, char(26)) = 0
|
|
2834
|
+
AND instr(name, char(27)) = 0
|
|
2835
|
+
AND instr(name, char(28)) = 0
|
|
2836
|
+
AND instr(name, char(29)) = 0
|
|
2837
|
+
AND instr(name, char(30)) = 0
|
|
2838
|
+
AND instr(name, char(31)) = 0
|
|
2839
|
+
AND instr(name, char(127)) = 0
|
|
2840
|
+
),
|
|
2841
|
+
hostname TEXT NOT NULL UNIQUE CHECK(
|
|
2842
|
+
length(hostname) BETWEEN 1 AND 253
|
|
2843
|
+
AND hostname = lower(rtrim(trim(hostname), '.'))
|
|
2844
|
+
AND instr(hostname, ' ') = 0
|
|
2845
|
+
AND instr(hostname, char(9)) = 0
|
|
2846
|
+
AND instr(hostname, char(10)) = 0
|
|
2847
|
+
AND instr(hostname, char(13)) = 0
|
|
2848
|
+
AND instr(hostname, '/') = 0
|
|
2849
|
+
AND instr(hostname, char(92)) = 0
|
|
2850
|
+
),
|
|
2851
|
+
platform TEXT NOT NULL CHECK(
|
|
2852
|
+
length(platform) BETWEEN 1 AND 64
|
|
2853
|
+
AND platform = lower(trim(platform))
|
|
2854
|
+
AND platform NOT GLOB '*[^a-z0-9._-]*'
|
|
2855
|
+
),
|
|
2856
|
+
is_primary INTEGER NOT NULL CHECK(is_primary IN (0, 1)),
|
|
2857
|
+
created_at TEXT NOT NULL CHECK(julianday(created_at) IS NOT NULL),
|
|
2858
|
+
last_seen_at TEXT NOT NULL CHECK(
|
|
2859
|
+
julianday(last_seen_at) IS NOT NULL
|
|
2860
|
+
AND julianday(last_seen_at) >= julianday(created_at)
|
|
2861
|
+
)
|
|
2862
|
+
);
|
|
2863
|
+
INSERT INTO mementos_m41_machine_preflight
|
|
2864
|
+
(id, name, hostname, platform, is_primary, created_at, last_seen_at)
|
|
2865
|
+
SELECT
|
|
2866
|
+
id,
|
|
2867
|
+
name,
|
|
2868
|
+
lower(rtrim(trim(hostname), '.')),
|
|
2869
|
+
platform,
|
|
2870
|
+
is_primary,
|
|
2871
|
+
created_at,
|
|
2872
|
+
last_seen_at
|
|
2873
|
+
FROM machines;
|
|
2874
|
+
|
|
2875
|
+
UPDATE machines
|
|
2876
|
+
SET hostname = (
|
|
2877
|
+
SELECT preflight.hostname
|
|
2878
|
+
FROM mementos_m41_machine_preflight preflight
|
|
2879
|
+
WHERE preflight.id = machines.id
|
|
2880
|
+
);
|
|
2881
|
+
|
|
2882
|
+
DROP TABLE mementos_m41_machine_preflight;
|
|
2883
|
+
DROP TRIGGER IF EXISTS machines_single_primary_insert;
|
|
2884
|
+
DROP TRIGGER IF EXISTS machines_single_primary_update;
|
|
2885
|
+
DROP INDEX IF EXISTS idx_machines_hostname;
|
|
2886
|
+
CREATE UNIQUE INDEX idx_machines_hostname ON machines(hostname);
|
|
2887
|
+
DROP INDEX IF EXISTS idx_machines_single_primary;
|
|
2888
|
+
CREATE UNIQUE INDEX idx_machines_single_primary
|
|
2889
|
+
ON machines(is_primary) WHERE is_primary = 1;
|
|
2890
|
+
|
|
2891
|
+
CREATE TRIGGER IF NOT EXISTS machines_hostname_canonical_insert
|
|
2892
|
+
BEFORE INSERT ON machines
|
|
2893
|
+
WHEN NEW.hostname != lower(rtrim(trim(NEW.hostname), '.'))
|
|
2894
|
+
OR length(NEW.hostname) NOT BETWEEN 1 AND 253
|
|
2895
|
+
OR instr(NEW.hostname, ' ') > 0
|
|
2896
|
+
OR instr(NEW.hostname, char(9)) > 0
|
|
2897
|
+
OR instr(NEW.hostname, char(10)) > 0
|
|
2898
|
+
OR instr(NEW.hostname, char(13)) > 0
|
|
2899
|
+
OR instr(NEW.hostname, '/') > 0
|
|
2900
|
+
OR instr(NEW.hostname, char(92)) > 0
|
|
2901
|
+
BEGIN
|
|
2902
|
+
SELECT RAISE(ABORT, 'machines.hostname must be canonical');
|
|
2903
|
+
END;
|
|
2904
|
+
|
|
2905
|
+
CREATE TRIGGER IF NOT EXISTS machines_hostname_canonical_update
|
|
2906
|
+
BEFORE UPDATE OF hostname ON machines
|
|
2907
|
+
WHEN NEW.hostname != lower(rtrim(trim(NEW.hostname), '.'))
|
|
2908
|
+
OR length(NEW.hostname) NOT BETWEEN 1 AND 253
|
|
2909
|
+
OR instr(NEW.hostname, ' ') > 0
|
|
2910
|
+
OR instr(NEW.hostname, char(9)) > 0
|
|
2911
|
+
OR instr(NEW.hostname, char(10)) > 0
|
|
2912
|
+
OR instr(NEW.hostname, char(13)) > 0
|
|
2913
|
+
OR instr(NEW.hostname, '/') > 0
|
|
2914
|
+
OR instr(NEW.hostname, char(92)) > 0
|
|
2915
|
+
BEGIN
|
|
2916
|
+
SELECT RAISE(ABORT, 'machines.hostname must be canonical');
|
|
2917
|
+
END;
|
|
2918
|
+
|
|
2919
|
+
CREATE TRIGGER IF NOT EXISTS machines_runtime_contract_insert
|
|
2920
|
+
BEFORE INSERT ON machines
|
|
2921
|
+
WHEN length(NEW.name) NOT BETWEEN 1 AND 128
|
|
2922
|
+
OR instr(NEW.name, char(0)) > 0
|
|
2923
|
+
OR instr(NEW.name, char(1)) > 0
|
|
2924
|
+
OR instr(NEW.name, char(2)) > 0
|
|
2925
|
+
OR instr(NEW.name, char(3)) > 0
|
|
2926
|
+
OR instr(NEW.name, char(4)) > 0
|
|
2927
|
+
OR instr(NEW.name, char(5)) > 0
|
|
2928
|
+
OR instr(NEW.name, char(6)) > 0
|
|
2929
|
+
OR instr(NEW.name, char(7)) > 0
|
|
2930
|
+
OR instr(NEW.name, char(8)) > 0
|
|
2931
|
+
OR instr(NEW.name, char(9)) > 0
|
|
2932
|
+
OR instr(NEW.name, char(10)) > 0
|
|
2933
|
+
OR instr(NEW.name, char(11)) > 0
|
|
2934
|
+
OR instr(NEW.name, char(12)) > 0
|
|
2935
|
+
OR instr(NEW.name, char(13)) > 0
|
|
2936
|
+
OR instr(NEW.name, char(14)) > 0
|
|
2937
|
+
OR instr(NEW.name, char(15)) > 0
|
|
2938
|
+
OR instr(NEW.name, char(16)) > 0
|
|
2939
|
+
OR instr(NEW.name, char(17)) > 0
|
|
2940
|
+
OR instr(NEW.name, char(18)) > 0
|
|
2941
|
+
OR instr(NEW.name, char(19)) > 0
|
|
2942
|
+
OR instr(NEW.name, char(20)) > 0
|
|
2943
|
+
OR instr(NEW.name, char(21)) > 0
|
|
2944
|
+
OR instr(NEW.name, char(22)) > 0
|
|
2945
|
+
OR instr(NEW.name, char(23)) > 0
|
|
2946
|
+
OR instr(NEW.name, char(24)) > 0
|
|
2947
|
+
OR instr(NEW.name, char(25)) > 0
|
|
2948
|
+
OR instr(NEW.name, char(26)) > 0
|
|
2949
|
+
OR instr(NEW.name, char(27)) > 0
|
|
2950
|
+
OR instr(NEW.name, char(28)) > 0
|
|
2951
|
+
OR instr(NEW.name, char(29)) > 0
|
|
2952
|
+
OR instr(NEW.name, char(30)) > 0
|
|
2953
|
+
OR instr(NEW.name, char(31)) > 0
|
|
2954
|
+
OR instr(NEW.name, char(127)) > 0
|
|
2955
|
+
OR length(NEW.platform) NOT BETWEEN 1 AND 64
|
|
2956
|
+
OR NEW.platform != lower(trim(NEW.platform))
|
|
2957
|
+
OR NEW.platform GLOB '*[^a-z0-9._-]*'
|
|
2958
|
+
OR NEW.is_primary NOT IN (0, 1)
|
|
2959
|
+
OR julianday(NEW.created_at) IS NULL
|
|
2960
|
+
OR julianday(NEW.last_seen_at) IS NULL
|
|
2961
|
+
OR julianday(NEW.last_seen_at) < julianday(NEW.created_at)
|
|
2962
|
+
BEGIN
|
|
2963
|
+
SELECT RAISE(ABORT, 'machines row violates the persisted runtime contract');
|
|
2964
|
+
END;
|
|
2965
|
+
|
|
2966
|
+
CREATE TRIGGER IF NOT EXISTS machines_runtime_contract_update
|
|
2967
|
+
BEFORE UPDATE OF name, platform, is_primary, created_at, last_seen_at ON machines
|
|
2968
|
+
WHEN length(NEW.name) NOT BETWEEN 1 AND 128
|
|
2969
|
+
OR instr(NEW.name, char(0)) > 0
|
|
2970
|
+
OR instr(NEW.name, char(1)) > 0
|
|
2971
|
+
OR instr(NEW.name, char(2)) > 0
|
|
2972
|
+
OR instr(NEW.name, char(3)) > 0
|
|
2973
|
+
OR instr(NEW.name, char(4)) > 0
|
|
2974
|
+
OR instr(NEW.name, char(5)) > 0
|
|
2975
|
+
OR instr(NEW.name, char(6)) > 0
|
|
2976
|
+
OR instr(NEW.name, char(7)) > 0
|
|
2977
|
+
OR instr(NEW.name, char(8)) > 0
|
|
2978
|
+
OR instr(NEW.name, char(9)) > 0
|
|
2979
|
+
OR instr(NEW.name, char(10)) > 0
|
|
2980
|
+
OR instr(NEW.name, char(11)) > 0
|
|
2981
|
+
OR instr(NEW.name, char(12)) > 0
|
|
2982
|
+
OR instr(NEW.name, char(13)) > 0
|
|
2983
|
+
OR instr(NEW.name, char(14)) > 0
|
|
2984
|
+
OR instr(NEW.name, char(15)) > 0
|
|
2985
|
+
OR instr(NEW.name, char(16)) > 0
|
|
2986
|
+
OR instr(NEW.name, char(17)) > 0
|
|
2987
|
+
OR instr(NEW.name, char(18)) > 0
|
|
2988
|
+
OR instr(NEW.name, char(19)) > 0
|
|
2989
|
+
OR instr(NEW.name, char(20)) > 0
|
|
2990
|
+
OR instr(NEW.name, char(21)) > 0
|
|
2991
|
+
OR instr(NEW.name, char(22)) > 0
|
|
2992
|
+
OR instr(NEW.name, char(23)) > 0
|
|
2993
|
+
OR instr(NEW.name, char(24)) > 0
|
|
2994
|
+
OR instr(NEW.name, char(25)) > 0
|
|
2995
|
+
OR instr(NEW.name, char(26)) > 0
|
|
2996
|
+
OR instr(NEW.name, char(27)) > 0
|
|
2997
|
+
OR instr(NEW.name, char(28)) > 0
|
|
2998
|
+
OR instr(NEW.name, char(29)) > 0
|
|
2999
|
+
OR instr(NEW.name, char(30)) > 0
|
|
3000
|
+
OR instr(NEW.name, char(31)) > 0
|
|
3001
|
+
OR instr(NEW.name, char(127)) > 0
|
|
3002
|
+
OR length(NEW.platform) NOT BETWEEN 1 AND 64
|
|
3003
|
+
OR NEW.platform != lower(trim(NEW.platform))
|
|
3004
|
+
OR NEW.platform GLOB '*[^a-z0-9._-]*'
|
|
3005
|
+
OR NEW.is_primary NOT IN (0, 1)
|
|
3006
|
+
OR julianday(NEW.created_at) IS NULL
|
|
3007
|
+
OR julianday(NEW.last_seen_at) IS NULL
|
|
3008
|
+
OR julianday(NEW.last_seen_at) < julianday(NEW.created_at)
|
|
3009
|
+
BEGIN
|
|
3010
|
+
SELECT RAISE(ABORT, 'machines row violates the persisted runtime contract');
|
|
3011
|
+
END;
|
|
3012
|
+
|
|
3013
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (41);
|
|
2762
3014
|
`
|
|
2763
3015
|
];
|
|
2764
3016
|
});
|
|
@@ -2774,10 +3026,11 @@ __export(exports_database, {
|
|
|
2774
3026
|
getDbPath: () => getDbPath,
|
|
2775
3027
|
getDatabase: () => getDatabase,
|
|
2776
3028
|
escapeLikePrefix: () => escapeLikePrefix,
|
|
2777
|
-
closeDatabase: () => closeDatabase
|
|
3029
|
+
closeDatabase: () => closeDatabase,
|
|
3030
|
+
applyMachineIdentityMigration: () => applyMachineIdentityMigration
|
|
2778
3031
|
});
|
|
2779
3032
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, cpSync } from "fs";
|
|
2780
|
-
import { dirname, join as
|
|
3033
|
+
import { dirname, join as join4, resolve as resolve2 } from "path";
|
|
2781
3034
|
function isInMemoryDb(path) {
|
|
2782
3035
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
2783
3036
|
}
|
|
@@ -2786,7 +3039,7 @@ function findNearestMementosDb(startDir) {
|
|
|
2786
3039
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
2787
3040
|
const legacyHomeDb = resolve2(home, ".mementos", "mementos.db");
|
|
2788
3041
|
while (true) {
|
|
2789
|
-
const candidate =
|
|
3042
|
+
const candidate = join4(dir, ".mementos", "mementos.db");
|
|
2790
3043
|
if (existsSync3(candidate) && resolve2(candidate) !== legacyHomeDb)
|
|
2791
3044
|
return candidate;
|
|
2792
3045
|
const parent = dirname(dir);
|
|
@@ -2799,7 +3052,7 @@ function findNearestMementosDb(startDir) {
|
|
|
2799
3052
|
function findGitRoot(startDir) {
|
|
2800
3053
|
let dir = resolve2(startDir);
|
|
2801
3054
|
while (true) {
|
|
2802
|
-
if (existsSync3(
|
|
3055
|
+
if (existsSync3(join4(dir, ".git")))
|
|
2803
3056
|
return dir;
|
|
2804
3057
|
const parent = dirname(dir);
|
|
2805
3058
|
if (parent === dir)
|
|
@@ -2809,9 +3062,11 @@ function findGitRoot(startDir) {
|
|
|
2809
3062
|
return null;
|
|
2810
3063
|
}
|
|
2811
3064
|
function migrateGlobalDir() {
|
|
3065
|
+
if (!selectsMementosLocalStore())
|
|
3066
|
+
return;
|
|
2812
3067
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
2813
3068
|
const newDir = getDataRoot();
|
|
2814
|
-
const oldDir =
|
|
3069
|
+
const oldDir = join4(home, ".mementos");
|
|
2815
3070
|
if (!existsSync3(newDir) && existsSync3(oldDir)) {
|
|
2816
3071
|
mkdirSync2(dirname(newDir), { recursive: true });
|
|
2817
3072
|
cpSync(oldDir, newDir, { recursive: true });
|
|
@@ -2830,11 +3085,11 @@ function getDbPath() {
|
|
|
2830
3085
|
if (process.env["MEMENTOS_DB_SCOPE"] === "project") {
|
|
2831
3086
|
const gitRoot = findGitRoot(cwd);
|
|
2832
3087
|
if (gitRoot) {
|
|
2833
|
-
return
|
|
3088
|
+
return join4(gitRoot, ".mementos", "mementos.db");
|
|
2834
3089
|
}
|
|
2835
3090
|
}
|
|
2836
3091
|
migrateGlobalDir();
|
|
2837
|
-
return
|
|
3092
|
+
return join4(getDataRoot(), "mementos.db");
|
|
2838
3093
|
}
|
|
2839
3094
|
function ensureDir(filePath) {
|
|
2840
3095
|
if (isInMemoryDb(filePath))
|
|
@@ -2870,6 +3125,9 @@ function getDatabase(dbPath) {
|
|
|
2870
3125
|
throw new Error("REFUSING-UNPINNED-TEST-OPEN: a test process tried to open the default local " + `SQLite store (${getDbPath()}) with no explicit dbPath argument and no ` + `${DB_PATH_ENV_KEYS.join("/")} set. Pass an explicit dbPath (e.g. ":memory:" ` + "or a scratch file), or set one of those env keys to a scratch path, before " + "calling getDatabase(). This guard exists because an unpinned test process " + "would otherwise silently read and write the real, shared, on-disk memory " + "store (todos 57b8b8c5).");
|
|
2871
3126
|
}
|
|
2872
3127
|
}
|
|
3128
|
+
if (process.env["NODE_ENV"] !== "test" && !isServerContext()) {
|
|
3129
|
+
assertClientStoreConfigured();
|
|
3130
|
+
}
|
|
2873
3131
|
}
|
|
2874
3132
|
const path = dbPath || getDbPath();
|
|
2875
3133
|
if (_db) {
|
|
@@ -2882,7 +3140,6 @@ function getDatabase(dbPath) {
|
|
|
2882
3140
|
ensureDir(path);
|
|
2883
3141
|
_db = new SqliteAdapter(path);
|
|
2884
3142
|
_db.run("PRAGMA journal_mode = WAL");
|
|
2885
|
-
_db.run("PRAGMA busy_timeout = 5000");
|
|
2886
3143
|
_db.run("PRAGMA foreign_keys = ON");
|
|
2887
3144
|
_db.run("PRAGMA wal_autocheckpoint = 100");
|
|
2888
3145
|
runMigrations(_db);
|
|
@@ -2898,32 +3155,62 @@ function getDatabase(dbPath) {
|
|
|
2898
3155
|
return _db;
|
|
2899
3156
|
}
|
|
2900
3157
|
function runMigrations(db) {
|
|
3158
|
+
let currentLevel = 0;
|
|
2901
3159
|
try {
|
|
2902
3160
|
const result = db.query("SELECT MAX(id) as max_id FROM _migrations").get();
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
for (let i = 0;i < MIGRATIONS.length; i++) {
|
|
2913
|
-
try {
|
|
2914
|
-
applyMigration(db, i);
|
|
2915
|
-
} catch (e) {
|
|
2916
|
-
console.warn(`[mementos] Migration ${i + 1} failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3161
|
+
currentLevel = result?.max_id ?? 0;
|
|
3162
|
+
} catch {}
|
|
3163
|
+
for (let i = currentLevel;i < MIGRATIONS.length; i++) {
|
|
3164
|
+
try {
|
|
3165
|
+
applyMigration(db, i);
|
|
3166
|
+
} catch (error) {
|
|
3167
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3168
|
+
if (i === 40) {
|
|
3169
|
+
throw new Error(`Mementos migration 41 refused unsafe machine identity data: ${message}`);
|
|
2917
3170
|
}
|
|
3171
|
+
console.warn(`[mementos] Migration ${i + 1} failed: ${message}`);
|
|
2918
3172
|
}
|
|
2919
3173
|
}
|
|
2920
3174
|
}
|
|
2921
3175
|
function applyMigration(db, index) {
|
|
2922
|
-
if (index === 32)
|
|
3176
|
+
if (index === 32)
|
|
2923
3177
|
ensureMachinePrimaryColumn(db);
|
|
3178
|
+
if (index === 40) {
|
|
3179
|
+
applyMachineIdentityMigration(db);
|
|
3180
|
+
return;
|
|
2924
3181
|
}
|
|
2925
3182
|
db.exec(MIGRATIONS[index]);
|
|
2926
3183
|
}
|
|
3184
|
+
function applyMachineIdentityMigration(db) {
|
|
3185
|
+
db.transaction(() => {
|
|
3186
|
+
const rows = db.query("SELECT id, name, hostname, platform, is_primary, created_at, last_seen_at FROM machines").all();
|
|
3187
|
+
const identities = new Map;
|
|
3188
|
+
let primaryCount = 0;
|
|
3189
|
+
for (const row of rows) {
|
|
3190
|
+
const canonical = row.hostname.trim().replace(/\.+$/, "").toLowerCase();
|
|
3191
|
+
const createdAt = new Date(row.created_at).getTime();
|
|
3192
|
+
const lastSeenAt = new Date(row.last_seen_at).getTime();
|
|
3193
|
+
const primaryValueValid = row.is_primary === 0 || row.is_primary === 1 || row.is_primary === false || row.is_primary === true;
|
|
3194
|
+
if (!row.id || !row.name || row.name.length > 128 || /[\u0000-\u001f\u007f]/.test(row.name) || !canonical || canonical.length > 253 || /[\u0000-\u001f\u007f/\\\s]/.test(canonical) || !row.platform || row.platform.length > 64 || row.platform !== row.platform.trim().toLowerCase() || !/^[a-z0-9._-]+$/.test(row.platform) || !primaryValueValid || !Number.isFinite(createdAt) || !Number.isFinite(lastSeenAt) || lastSeenAt < createdAt) {
|
|
3195
|
+
throw new Error(`invalid legacy machine row ${row.id || "(missing id)"}`);
|
|
3196
|
+
}
|
|
3197
|
+
const owner = identities.get(canonical);
|
|
3198
|
+
if (owner && owner !== row.id) {
|
|
3199
|
+
throw new Error(`ambiguous normalized hostname '${canonical}' belongs to both ${owner} and ${row.id}`);
|
|
3200
|
+
}
|
|
3201
|
+
identities.set(canonical, row.id);
|
|
3202
|
+
if (Boolean(row.is_primary))
|
|
3203
|
+
primaryCount += 1;
|
|
3204
|
+
}
|
|
3205
|
+
if (primaryCount > 1) {
|
|
3206
|
+
throw new Error(`legacy machine registry contains ${primaryCount} primary rows`);
|
|
3207
|
+
}
|
|
3208
|
+
db.exec(MIGRATIONS[40]);
|
|
3209
|
+
const receipt = db.query("SELECT id FROM _migrations WHERE id = 41").get();
|
|
3210
|
+
if (receipt?.id !== 41)
|
|
3211
|
+
throw new Error("machine identity migration did not record completion");
|
|
3212
|
+
});
|
|
3213
|
+
}
|
|
2927
3214
|
function ensureMachinePrimaryColumn(db) {
|
|
2928
3215
|
const columns = db.query("PRAGMA table_info(machines)").all();
|
|
2929
3216
|
if (columns.length === 0 || columns.some((column) => column.name === "is_primary")) {
|
|
@@ -2979,6 +3266,7 @@ var init_database = __esm(() => {
|
|
|
2979
3266
|
init_api_mode();
|
|
2980
3267
|
init_migrations();
|
|
2981
3268
|
init_paths();
|
|
3269
|
+
init_local_opt_in();
|
|
2982
3270
|
ALLOWED_TABLES = new Set([
|
|
2983
3271
|
"memories",
|
|
2984
3272
|
"agents",
|
|
@@ -3007,13 +3295,13 @@ import { createHash } from "crypto";
|
|
|
3007
3295
|
|
|
3008
3296
|
// src/lib/package-version.ts
|
|
3009
3297
|
import { readFileSync as readFileSync2 } from "fs";
|
|
3010
|
-
import { dirname as dirname2, join as
|
|
3298
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
3011
3299
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3012
3300
|
function getMementosPackageVersion() {
|
|
3013
3301
|
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
3014
3302
|
for (const candidate of [
|
|
3015
|
-
|
|
3016
|
-
|
|
3303
|
+
join5(here, "..", "..", "package.json"),
|
|
3304
|
+
join5(here, "..", "package.json")
|
|
3017
3305
|
]) {
|
|
3018
3306
|
try {
|
|
3019
3307
|
const parsed = JSON.parse(readFileSync2(candidate, "utf8"));
|
|
@@ -5200,6 +5488,17 @@ var SECRET_PATTERNS = [
|
|
|
5200
5488
|
{ name: "jwt", pattern: /eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g },
|
|
5201
5489
|
{ name: "hex_secret", pattern: /(?<=(?:key|token|secret|password|hash)\s*[:=]\s*["']?)[0-9a-f]{32,}(?=["']?)/gi }
|
|
5202
5490
|
];
|
|
5491
|
+
var KEY_TAG_SECRET_PATTERNS = SECRET_PATTERNS.filter((p) => new Set([
|
|
5492
|
+
"openai_key",
|
|
5493
|
+
"anthropic_key",
|
|
5494
|
+
"aws_key",
|
|
5495
|
+
"github_token",
|
|
5496
|
+
"github_oauth",
|
|
5497
|
+
"npm_token",
|
|
5498
|
+
"stripe_key",
|
|
5499
|
+
"slack_token",
|
|
5500
|
+
"jwt"
|
|
5501
|
+
]).has(p.name));
|
|
5203
5502
|
function redactSecrets(text) {
|
|
5204
5503
|
let result = text;
|
|
5205
5504
|
for (const { pattern } of SECRET_PATTERNS) {
|
|
@@ -5208,6 +5507,14 @@ function redactSecrets(text) {
|
|
|
5208
5507
|
}
|
|
5209
5508
|
return result;
|
|
5210
5509
|
}
|
|
5510
|
+
function redactCredentialKey(text) {
|
|
5511
|
+
let result = text;
|
|
5512
|
+
for (const { pattern } of KEY_TAG_SECRET_PATTERNS) {
|
|
5513
|
+
pattern.lastIndex = 0;
|
|
5514
|
+
result = result.replace(pattern, REDACTED);
|
|
5515
|
+
}
|
|
5516
|
+
return result;
|
|
5517
|
+
}
|
|
5211
5518
|
function containsSecrets(text) {
|
|
5212
5519
|
for (const { pattern } of SECRET_PATTERNS) {
|
|
5213
5520
|
pattern.lastIndex = 0;
|
|
@@ -5216,6 +5523,40 @@ function containsSecrets(text) {
|
|
|
5216
5523
|
}
|
|
5217
5524
|
return false;
|
|
5218
5525
|
}
|
|
5526
|
+
function redactValueTree(value) {
|
|
5527
|
+
if (typeof value === "string")
|
|
5528
|
+
return redactSecrets(value);
|
|
5529
|
+
if (Array.isArray(value))
|
|
5530
|
+
return value.map(redactValueTree);
|
|
5531
|
+
if (value !== null && typeof value === "object") {
|
|
5532
|
+
const out = {};
|
|
5533
|
+
for (const [k, v] of Object.entries(value)) {
|
|
5534
|
+
out[k] = redactValueTree(v);
|
|
5535
|
+
}
|
|
5536
|
+
return out;
|
|
5537
|
+
}
|
|
5538
|
+
return value;
|
|
5539
|
+
}
|
|
5540
|
+
function redactMemoryWriteInput(input) {
|
|
5541
|
+
const out = { ...input };
|
|
5542
|
+
if (typeof out.key === "string")
|
|
5543
|
+
out.key = redactCredentialKey(out.key);
|
|
5544
|
+
if (typeof out.value === "string")
|
|
5545
|
+
out.value = redactSecrets(out.value);
|
|
5546
|
+
if (out.summary !== undefined && out.summary !== null) {
|
|
5547
|
+
out.summary = redactSecrets(String(out.summary));
|
|
5548
|
+
}
|
|
5549
|
+
if (Array.isArray(out.tags)) {
|
|
5550
|
+
out.tags = out.tags.map((t) => redactCredentialKey(String(t)));
|
|
5551
|
+
}
|
|
5552
|
+
if (typeof out.when_to_use === "string") {
|
|
5553
|
+
out.when_to_use = redactSecrets(out.when_to_use);
|
|
5554
|
+
}
|
|
5555
|
+
if (out.metadata !== undefined) {
|
|
5556
|
+
out.metadata = redactValueTree(out.metadata);
|
|
5557
|
+
}
|
|
5558
|
+
return out;
|
|
5559
|
+
}
|
|
5219
5560
|
|
|
5220
5561
|
// src/lib/hooks.ts
|
|
5221
5562
|
var _idCounter = 0;
|
|
@@ -5528,6 +5869,7 @@ function createMemory(input, dedupeMode = "merge", db) {
|
|
|
5528
5869
|
if (reservedViolation) {
|
|
5529
5870
|
throw new Error(reservedViolation);
|
|
5530
5871
|
}
|
|
5872
|
+
input = redactMemoryWriteInput(input);
|
|
5531
5873
|
if (!db && isApiMode()) {
|
|
5532
5874
|
const { status, data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
|
|
5533
5875
|
if (!data || !data.id) {
|
|
@@ -5820,7 +6162,7 @@ function buildMemoryListConditions(filter) {
|
|
|
5820
6162
|
conditions.push("status = 'active'");
|
|
5821
6163
|
}
|
|
5822
6164
|
if (filter.project_id) {
|
|
5823
|
-
conditions.push("project_id = ?");
|
|
6165
|
+
conditions.push(filter.include_unassigned_project === true ? "(project_id = ? OR project_id IS NULL)" : "project_id = ?");
|
|
5824
6166
|
params.push(filter.project_id);
|
|
5825
6167
|
}
|
|
5826
6168
|
if (filter.agent_id) {
|
|
@@ -5900,6 +6242,7 @@ function listMemoriesPage(filter, db) {
|
|
|
5900
6242
|
pinned: f.pinned,
|
|
5901
6243
|
agent_id: f.agent_id,
|
|
5902
6244
|
project_id: f.project_id,
|
|
6245
|
+
include_unassigned_project: f.project_id && f.include_unassigned_project === true ? true : undefined,
|
|
5903
6246
|
session_id: f.session_id,
|
|
5904
6247
|
machine_id: f.machine_id,
|
|
5905
6248
|
visible_to_machine_id: f.visible_to_machine_id,
|
|
@@ -5924,7 +6267,7 @@ function listMemoriesPage(filter, db) {
|
|
|
5924
6267
|
if (conditions.length > 0) {
|
|
5925
6268
|
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
5926
6269
|
}
|
|
5927
|
-
sql += " ORDER BY importance DESC, created_at DESC";
|
|
6270
|
+
sql += " ORDER BY importance DESC, created_at DESC, id DESC";
|
|
5928
6271
|
if (f.limit) {
|
|
5929
6272
|
sql += " LIMIT ?";
|
|
5930
6273
|
params.push(f.limit);
|
|
@@ -5985,6 +6328,7 @@ function listMemoriesBounded(filter = {}, target, db) {
|
|
|
5985
6328
|
};
|
|
5986
6329
|
}
|
|
5987
6330
|
function updateMemory(id, input, db) {
|
|
6331
|
+
input = redactMemoryWriteInput(input);
|
|
5988
6332
|
if (!db && isApiMode()) {
|
|
5989
6333
|
const { status, data } = apiJson("PATCH", `/memories/${encodeURIComponent(id)}`, input, { allow404: true });
|
|
5990
6334
|
if (status === 404)
|