@hasna/mementos 0.14.81 → 0.14.82

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.
@@ -965,7 +965,7 @@ var init_storage = __esm(() => {
965
965
 
966
966
  // src/db/api-mode.ts
967
967
  import { tmpdir } from "os";
968
- import { join as join3 } from "path";
968
+ import { join as join2 } from "path";
969
969
  import { writeFileSync as writeFileSync2, unlinkSync } from "fs";
970
970
  import { randomUUID } from "crypto";
971
971
  function firstEnv(keys) {
@@ -1074,7 +1074,7 @@ x-api-key: ${cfg.apiKey}
1074
1074
  ];
1075
1075
  let bodyFile;
1076
1076
  if (hasBody) {
1077
- bodyFile = join3(tmpdir(), `mem-req-${process.pid}-${randomUUID()}.json`);
1077
+ bodyFile = join2(tmpdir(), `mem-req-${process.pid}-${randomUUID()}.json`);
1078
1078
  writeFileSync2(bodyFile, JSON.stringify(body), { mode: 384 });
1079
1079
  args.push("--data-binary", `@${bodyFile}`);
1080
1080
  }
@@ -2585,7 +2585,7 @@ __export(exports_database, {
2585
2585
  closeDatabase: () => closeDatabase
2586
2586
  });
2587
2587
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, cpSync } from "fs";
2588
- import { dirname as dirname2, join as join4, resolve } from "path";
2588
+ import { dirname, join as join3, resolve } from "path";
2589
2589
  function isInMemoryDb(path) {
2590
2590
  return path === ":memory:" || path.startsWith("file::memory:");
2591
2591
  }
@@ -2594,10 +2594,10 @@ function findNearestMementosDb(startDir) {
2594
2594
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2595
2595
  const legacyHomeDb = resolve(home, ".mementos", "mementos.db");
2596
2596
  while (true) {
2597
- const candidate = join4(dir, ".mementos", "mementos.db");
2597
+ const candidate = join3(dir, ".mementos", "mementos.db");
2598
2598
  if (existsSync2(candidate) && resolve(candidate) !== legacyHomeDb)
2599
2599
  return candidate;
2600
- const parent = dirname2(dir);
2600
+ const parent = dirname(dir);
2601
2601
  if (parent === dir)
2602
2602
  break;
2603
2603
  dir = parent;
@@ -2607,9 +2607,9 @@ function findNearestMementosDb(startDir) {
2607
2607
  function findGitRoot(startDir) {
2608
2608
  let dir = resolve(startDir);
2609
2609
  while (true) {
2610
- if (existsSync2(join4(dir, ".git")))
2610
+ if (existsSync2(join3(dir, ".git")))
2611
2611
  return dir;
2612
- const parent = dirname2(dir);
2612
+ const parent = dirname(dir);
2613
2613
  if (parent === dir)
2614
2614
  break;
2615
2615
  dir = parent;
@@ -2618,10 +2618,10 @@ function findGitRoot(startDir) {
2618
2618
  }
2619
2619
  function migrateGlobalDir() {
2620
2620
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2621
- const newDir = join4(home, ".hasna", "mementos");
2622
- const oldDir = join4(home, ".mementos");
2621
+ const newDir = join3(home, ".hasna", "mementos");
2622
+ const oldDir = join3(home, ".mementos");
2623
2623
  if (!existsSync2(newDir) && existsSync2(oldDir)) {
2624
- mkdirSync2(join4(home, ".hasna"), { recursive: true });
2624
+ mkdirSync2(join3(home, ".hasna"), { recursive: true });
2625
2625
  cpSync(oldDir, newDir, { recursive: true });
2626
2626
  }
2627
2627
  }
@@ -2638,17 +2638,17 @@ function getDbPath() {
2638
2638
  if (process.env["MEMENTOS_DB_SCOPE"] === "project") {
2639
2639
  const gitRoot = findGitRoot(cwd);
2640
2640
  if (gitRoot) {
2641
- return join4(gitRoot, ".mementos", "mementos.db");
2641
+ return join3(gitRoot, ".mementos", "mementos.db");
2642
2642
  }
2643
2643
  }
2644
2644
  migrateGlobalDir();
2645
2645
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2646
- return join4(home, ".hasna", "mementos", "mementos.db");
2646
+ return join3(home, ".hasna", "mementos", "mementos.db");
2647
2647
  }
2648
2648
  function ensureDir(filePath) {
2649
2649
  if (isInMemoryDb(filePath))
2650
2650
  return;
2651
- const dir = dirname2(resolve(filePath));
2651
+ const dir = dirname(resolve(filePath));
2652
2652
  if (!existsSync2(dir)) {
2653
2653
  mkdirSync2(dir, { recursive: true });
2654
2654
  }
@@ -2808,15 +2808,20 @@ var init_database = __esm(() => {
2808
2808
  import { createHash as createHash2 } from "crypto";
2809
2809
  import { resolve as resolve2 } from "path";
2810
2810
 
2811
+ // src/db/projects.ts
2812
+ init_database();
2813
+ init_api_mode();
2814
+ import { createHash } from "crypto";
2815
+
2811
2816
  // src/lib/package-version.ts
2812
2817
  import { readFileSync as readFileSync2 } from "fs";
2813
- import { dirname, join as join2 } from "path";
2818
+ import { dirname as dirname2, join as join4 } from "path";
2814
2819
  import { fileURLToPath as fileURLToPath2 } from "url";
2815
2820
  function getMementosPackageVersion() {
2816
- const here = dirname(fileURLToPath2(import.meta.url));
2821
+ const here = dirname2(fileURLToPath2(import.meta.url));
2817
2822
  for (const candidate of [
2818
- join2(here, "..", "..", "package.json"),
2819
- join2(here, "..", "package.json")
2823
+ join4(here, "..", "..", "package.json"),
2824
+ join4(here, "..", "package.json")
2820
2825
  ]) {
2821
2826
  try {
2822
2827
  const parsed = JSON.parse(readFileSync2(candidate, "utf8"));
@@ -2827,16 +2832,18 @@ function getMementosPackageVersion() {
2827
2832
  return "0.0.0";
2828
2833
  }
2829
2834
 
2830
- // src/db/projects.ts
2831
- init_database();
2832
- init_api_mode();
2833
- import { createHash } from "crypto";
2834
-
2835
2835
  // src/project-registration/types.ts
2836
2836
  var MEMENTOS_PROJECT_REGISTRATION_ROUTE = "mementos.project-registration.v1";
2837
2837
  var MEMENTOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1";
2838
2838
  var MEMENTOS_PROJECT_REGISTRATION_SCHEMA_VERSION = 1;
2839
2839
  var MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE = "mementos.project-guarded-update.v1";
2840
+ var MEMENTOS_PROJECT_RESOURCE_ROUTE = "mementos.project-resources.v1";
2841
+ var MEMENTOS_PROJECT_RESOURCE_KINDS = [
2842
+ "project",
2843
+ "knowledge",
2844
+ "memory",
2845
+ "session"
2846
+ ];
2840
2847
 
2841
2848
  class MementosProjectRegistrationError extends Error {
2842
2849
  code;
@@ -2849,6 +2856,73 @@ class MementosProjectRegistrationError extends Error {
2849
2856
  }
2850
2857
  }
2851
2858
 
2859
+ // src/project-registration/identity.ts
2860
+ var MEMENTOS_PROJECT_AUTHORITY_ENV = {
2861
+ authorityId: "MEMENTOS_PROJECT_AUTHORITY_ID",
2862
+ tenantId: "MEMENTOS_PROJECT_TENANT_ID",
2863
+ corpusId: "MEMENTOS_PROJECT_CORPUS_ID"
2864
+ };
2865
+ function configuredValue(override, envKey) {
2866
+ return override?.trim() || process.env[envKey]?.trim() || null;
2867
+ }
2868
+
2869
+ class MementosProjectAuthorityIdentityError extends Error {
2870
+ missing_env;
2871
+ code = "MEMENTOS_PROJECT_AUTHORITY_UNCONFIGURED";
2872
+ constructor(missing_env) {
2873
+ super("Mementos project authority identity is not configured; set " + missing_env.join(", "));
2874
+ this.missing_env = missing_env;
2875
+ this.name = "MementosProjectAuthorityIdentityError";
2876
+ }
2877
+ }
2878
+ function resolveMementosProjectAuthorityIdentity(options = {}) {
2879
+ const authorityId = configuredValue(options.authorityId, MEMENTOS_PROJECT_AUTHORITY_ENV.authorityId);
2880
+ const tenantId = configuredValue(options.tenantId, MEMENTOS_PROJECT_AUTHORITY_ENV.tenantId);
2881
+ const corpusId = configuredValue(options.corpusId, MEMENTOS_PROJECT_AUTHORITY_ENV.corpusId);
2882
+ if (!authorityId || !tenantId || !corpusId) {
2883
+ const missingEnv = [];
2884
+ if (!authorityId)
2885
+ missingEnv.push(MEMENTOS_PROJECT_AUTHORITY_ENV.authorityId);
2886
+ if (!tenantId)
2887
+ missingEnv.push(MEMENTOS_PROJECT_AUTHORITY_ENV.tenantId);
2888
+ if (!corpusId)
2889
+ missingEnv.push(MEMENTOS_PROJECT_AUTHORITY_ENV.corpusId);
2890
+ throw new MementosProjectAuthorityIdentityError(missingEnv);
2891
+ }
2892
+ return {
2893
+ authority_id: authorityId,
2894
+ tenant_id: tenantId,
2895
+ corpus_id: corpusId
2896
+ };
2897
+ }
2898
+ function buildMementosProjectRegistrationCapability(options = {}) {
2899
+ const identity = resolveMementosProjectAuthorityIdentity(options);
2900
+ return {
2901
+ authority: "mementos",
2902
+ route: MEMENTOS_PROJECT_REGISTRATION_ROUTE,
2903
+ package_version: options.packageVersion ?? getMementosPackageVersion(),
2904
+ ...identity,
2905
+ supported_resources: ["project"],
2906
+ conditional_create: true,
2907
+ immutable_receipts: true,
2908
+ exact_terminal_lookup: true,
2909
+ exact_readback: true,
2910
+ conditional_inverse: true,
2911
+ ambiguous_outcome_reconciliation: true,
2912
+ guarded_update: true,
2913
+ guarded_update_route: MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
2914
+ no_write_dry_run: true,
2915
+ expected_revision_compare_and_swap: true,
2916
+ caller_idempotency: true,
2917
+ exact_inverse_rollback: true,
2918
+ project_resource_enumeration: true,
2919
+ project_resource_route: MEMENTOS_PROJECT_RESOURCE_ROUTE,
2920
+ project_resource_kinds: ["project", "knowledge", "memory", "session"],
2921
+ stable_keyset_pagination: true,
2922
+ explicit_membership_only: true
2923
+ };
2924
+ }
2925
+
2852
2926
  // src/db/projects.ts
2853
2927
  function parseProjectRow(row) {
2854
2928
  return {
@@ -2883,12 +2957,17 @@ class ProjectGuardedUpdateError extends Error {
2883
2957
  this.name = "ProjectGuardedUpdateError";
2884
2958
  }
2885
2959
  }
2886
- var PROJECT_UPDATE_AUTHORITY = {
2887
- authority_id: "mementos",
2888
- tenant_id: "default",
2889
- corpus_id: "default"
2890
- };
2891
2960
  var BOUNDED_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
2961
+ function projectUpdateAuthority() {
2962
+ try {
2963
+ return resolveMementosProjectAuthorityIdentity();
2964
+ } catch (error) {
2965
+ if (error instanceof MementosProjectAuthorityIdentityError) {
2966
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_AUTHORITY_MISMATCH", error.message, { authority_code: error.code, missing_env: error.missing_env });
2967
+ }
2968
+ throw error;
2969
+ }
2970
+ }
2892
2971
  function canonicalizeProjectUpdateValue(value) {
2893
2972
  if (Array.isArray(value))
2894
2973
  return value.map(canonicalizeProjectUpdateValue);
@@ -2961,7 +3040,7 @@ function normalizeProjectUpdateInput(input) {
2961
3040
  }
2962
3041
  return normalized;
2963
3042
  }
2964
- function assertProjectUpdateIdentity(identity, expectedIdentity = PROJECT_UPDATE_AUTHORITY) {
3043
+ function assertProjectUpdateIdentity(identity, expectedIdentity = projectUpdateAuthority()) {
2965
3044
  if (identity.authority_id !== expectedIdentity.authority_id || identity.tenant_id !== expectedIdentity.tenant_id || identity.corpus_id !== expectedIdentity.corpus_id) {
2966
3045
  throw new ProjectGuardedUpdateError("PROJECT_UPDATE_AUTHORITY_MISMATCH", "guarded project update does not match this authority, tenant, and corpus");
2967
3046
  }
@@ -2971,7 +3050,7 @@ function assertBoundedIdentifier(value, field) {
2971
3050
  throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", `${field} must be an 8-128 character bounded identifier`);
2972
3051
  }
2973
3052
  }
2974
- function assertProjectUpdateRequest(request, expectedIdentity = PROJECT_UPDATE_AUTHORITY) {
3053
+ function assertProjectUpdateRequest(request, expectedIdentity = projectUpdateAuthority()) {
2975
3054
  assertProjectUpdateIdentity(request, expectedIdentity);
2976
3055
  assertBoundedIdentifier(request.operation_id, "operation_id");
2977
3056
  assertBoundedIdentifier(request.step_id, "step_id");
@@ -3149,7 +3228,7 @@ function listProjects(db) {
3149
3228
  const rows = d.query("SELECT * FROM projects ORDER BY updated_at DESC").all();
3150
3229
  return rows.map(parseProjectRow);
3151
3230
  }
3152
- function previewProjectUpdate(id, request, db, expectedIdentity = PROJECT_UPDATE_AUTHORITY) {
3231
+ function previewProjectUpdate(id, request, db, expectedIdentity = projectUpdateAuthority()) {
3153
3232
  assertProjectUpdateRequest(request, expectedIdentity);
3154
3233
  const normalized = normalizeProjectUpdateInput(request.updates);
3155
3234
  if (!db && isApiMode()) {
@@ -3172,7 +3251,7 @@ function previewProjectUpdate(id, request, db, expectedIdentity = PROJECT_UPDATE
3172
3251
  receipt: null
3173
3252
  };
3174
3253
  }
3175
- function applyProjectUpdate(id, request, db, expectedIdentity = PROJECT_UPDATE_AUTHORITY, resultDigestForProject) {
3254
+ function applyProjectUpdate(id, request, db, expectedIdentity = projectUpdateAuthority(), resultDigestForProject) {
3176
3255
  assertProjectUpdateRequest(request, expectedIdentity);
3177
3256
  const normalized = normalizeProjectUpdateInput(request.updates);
3178
3257
  if (!db && isApiMode()) {
@@ -3241,7 +3320,7 @@ function applyProjectUpdate(id, request, db, expectedIdentity = PROJECT_UPDATE_A
3241
3320
  return { dry_run: false, applied: true, project: readback, receipt };
3242
3321
  });
3243
3322
  }
3244
- function rollbackProjectUpdate(id, request, db, expectedIdentity = PROJECT_UPDATE_AUTHORITY, resultDigestForProject) {
3323
+ function rollbackProjectUpdate(id, request, db, expectedIdentity = projectUpdateAuthority(), resultDigestForProject) {
3245
3324
  assertProjectUpdateRequest(request, expectedIdentity);
3246
3325
  assertBoundedIdentifier(request.accepted_receipt_id, "accepted_receipt_id");
3247
3326
  if (!db && isApiMode()) {
@@ -3315,7 +3394,7 @@ function rollbackProjectUpdate(id, request, db, expectedIdentity = PROJECT_UPDAT
3315
3394
  return { dry_run: false, applied: true, project: readback, receipt };
3316
3395
  });
3317
3396
  }
3318
- function getProjectUpdateReceipt(id, receiptId, identity = PROJECT_UPDATE_AUTHORITY, db, expectedIdentity = PROJECT_UPDATE_AUTHORITY) {
3397
+ function getProjectUpdateReceipt(id, receiptId, identity = projectUpdateAuthority(), db, expectedIdentity = projectUpdateAuthority()) {
3319
3398
  assertProjectUpdateIdentity(identity, expectedIdentity);
3320
3399
  if (!db && isApiMode()) {
3321
3400
  const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/update-receipts/lookup`, { ...identity, receipt_id: receiptId });
@@ -3954,27 +4033,7 @@ class PackageOwnedMementosProjectRegistrationAuthority {
3954
4033
  this.db = db;
3955
4034
  this.now = options.now ?? (() => new Date().toISOString());
3956
4035
  this.faultInjector = options.faultInjector;
3957
- this.capabilityValue = {
3958
- authority: "mementos",
3959
- route: MEMENTOS_PROJECT_REGISTRATION_ROUTE,
3960
- package_version: options.packageVersion ?? getMementosPackageVersion(),
3961
- authority_id: options.authorityId ?? "mementos",
3962
- tenant_id: options.tenantId ?? "default",
3963
- corpus_id: options.corpusId ?? "default",
3964
- supported_resources: ["project"],
3965
- conditional_create: true,
3966
- immutable_receipts: true,
3967
- exact_terminal_lookup: true,
3968
- exact_readback: true,
3969
- conditional_inverse: true,
3970
- ambiguous_outcome_reconciliation: true,
3971
- guarded_update: true,
3972
- guarded_update_route: MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
3973
- no_write_dry_run: true,
3974
- expected_revision_compare_and_swap: true,
3975
- caller_idempotency: true,
3976
- exact_inverse_rollback: true
3977
- };
4036
+ this.capabilityValue = buildMementosProjectRegistrationCapability(options);
3978
4037
  }
3979
4038
  fault(point, request) {
3980
4039
  this.faultInjector?.(point, {
@@ -4670,26 +4729,1400 @@ class MementosProjectRegistrationHttpClient {
4670
4729
  function createMementosProjectRegistrationHttpClient(options) {
4671
4730
  return new MementosProjectRegistrationHttpClient(options);
4672
4731
  }
4732
+ // src/project-registration/project-resources.ts
4733
+ init_api_mode();
4734
+ init_database();
4735
+
4736
+ // src/types/index.ts
4737
+ class AgentConflictError extends Error {
4738
+ conflict = true;
4739
+ existing_id;
4740
+ existing_name;
4741
+ last_seen_at;
4742
+ session_hint;
4743
+ working_dir;
4744
+ constructor(opts) {
4745
+ const msg = `Agent "${opts.existing_name}" is already active (session hint: ${opts.session_hint ?? "unknown"}, last seen ${opts.last_seen_at}). Wait 30 minutes or use a different name.`;
4746
+ super(msg);
4747
+ this.name = "AgentConflictError";
4748
+ this.existing_id = opts.existing_id;
4749
+ this.existing_name = opts.existing_name;
4750
+ this.last_seen_at = opts.last_seen_at;
4751
+ this.session_hint = opts.session_hint;
4752
+ this.working_dir = opts.working_dir ?? null;
4753
+ }
4754
+ }
4755
+ class EntityNotFoundError extends Error {
4756
+ constructor(id) {
4757
+ super(`Entity not found: ${id}`);
4758
+ this.name = "EntityNotFoundError";
4759
+ }
4760
+ }
4761
+
4762
+ class MemoryNotFoundError extends Error {
4763
+ constructor(id) {
4764
+ super(`Memory not found: ${id}`);
4765
+ this.name = "MemoryNotFoundError";
4766
+ }
4767
+ }
4768
+
4769
+ class DuplicateMemoryError extends Error {
4770
+ constructor(key, scope) {
4771
+ super(`Memory already exists with key "${key}" in scope "${scope}"`);
4772
+ this.name = "DuplicateMemoryError";
4773
+ }
4774
+ }
4775
+
4776
+ class MemoryExpiredError extends Error {
4777
+ constructor(id) {
4778
+ super(`Memory has expired: ${id}`);
4779
+ this.name = "MemoryExpiredError";
4780
+ }
4781
+ }
4782
+
4783
+ class InvalidScopeError extends Error {
4784
+ constructor(message) {
4785
+ super(message);
4786
+ this.name = "InvalidScopeError";
4787
+ }
4788
+ }
4789
+
4790
+ class VersionConflictError extends Error {
4791
+ expected;
4792
+ actual;
4793
+ constructor(id, expected, actual) {
4794
+ super(`Version conflict for memory ${id}: expected ${expected}, got ${actual}`);
4795
+ this.name = "VersionConflictError";
4796
+ this.expected = expected;
4797
+ this.actual = actual;
4798
+ }
4799
+ }
4800
+
4801
+ class MemoryConflictError extends Error {
4802
+ existingId;
4803
+ existingAgentId;
4804
+ existingUpdatedAt;
4805
+ constructor(key, existing) {
4806
+ super(`Memory conflict: key "${key}" already exists (last written by ${existing.agent_id ?? "unknown"} at ${existing.updated_at}). Use conflict:"overwrite" to replace it.`);
4807
+ this.name = "MemoryConflictError";
4808
+ this.existingId = existing.id;
4809
+ this.existingAgentId = existing.agent_id;
4810
+ this.existingUpdatedAt = existing.updated_at;
4811
+ }
4812
+ }
4813
+
4814
+ // src/db/memories.ts
4815
+ init_database();
4816
+
4817
+ // src/lib/embeddings.ts
4818
+ function cosineSimilarity(a, b) {
4819
+ if (a.length !== b.length || a.length === 0)
4820
+ return 0;
4821
+ let dot = 0, magA = 0, magB = 0;
4822
+ for (let i = 0;i < a.length; i++) {
4823
+ dot += a[i] * b[i];
4824
+ magA += a[i] * a[i];
4825
+ magB += b[i] * b[i];
4826
+ }
4827
+ const denom = Math.sqrt(magA) * Math.sqrt(magB);
4828
+ return denom === 0 ? 0 : dot / denom;
4829
+ }
4830
+ var OPENAI_EMBED_URL = "https://api.openai.com/v1/embeddings";
4831
+ var EMBED_MODEL = "text-embedding-3-small";
4832
+ var EMBED_DIMENSIONS = 1536;
4833
+ async function openAIEmbed(text, apiKey) {
4834
+ const res = await fetch(OPENAI_EMBED_URL, {
4835
+ method: "POST",
4836
+ headers: {
4837
+ "Content-Type": "application/json",
4838
+ Authorization: `Bearer ${apiKey}`
4839
+ },
4840
+ body: JSON.stringify({
4841
+ model: EMBED_MODEL,
4842
+ input: text.slice(0, 8192)
4843
+ }),
4844
+ signal: AbortSignal.timeout(1e4)
4845
+ });
4846
+ if (!res.ok) {
4847
+ throw new Error(`OpenAI embedding API ${res.status}: ${await res.text()}`);
4848
+ }
4849
+ const data = await res.json();
4850
+ return data.data[0].embedding;
4851
+ }
4852
+ function tfidfVector(text) {
4853
+ const DIMS = 512;
4854
+ const vec = new Float32Array(DIMS);
4855
+ const tokens = text.toLowerCase().match(/\b\w+\b/g) ?? [];
4856
+ for (const token of tokens) {
4857
+ let hash = 2166136261;
4858
+ for (let i = 0;i < token.length; i++) {
4859
+ hash ^= token.charCodeAt(i);
4860
+ hash = hash * 16777619 >>> 0;
4861
+ }
4862
+ vec[hash % DIMS] += 1;
4863
+ }
4864
+ let norm = 0;
4865
+ for (let i = 0;i < DIMS; i++)
4866
+ norm += vec[i] * vec[i];
4867
+ norm = Math.sqrt(norm);
4868
+ if (norm > 0)
4869
+ for (let i = 0;i < DIMS; i++)
4870
+ vec[i] /= norm;
4871
+ return Array.from(vec);
4872
+ }
4873
+ async function generateEmbedding(text) {
4874
+ const apiKey = process.env["OPENAI_API_KEY"];
4875
+ if (apiKey) {
4876
+ try {
4877
+ const embedding2 = await openAIEmbed(text, apiKey);
4878
+ return { embedding: embedding2, model: EMBED_MODEL, dimensions: EMBED_DIMENSIONS };
4879
+ } catch {}
4880
+ }
4881
+ const embedding = tfidfVector(text);
4882
+ return { embedding, model: "tfidf-512", dimensions: 512 };
4883
+ }
4884
+ function deserializeEmbedding(raw) {
4885
+ return JSON.parse(raw);
4886
+ }
4887
+
4888
+ // src/lib/redact.ts
4889
+ var REDACTED = "[REDACTED]";
4890
+ var SECRET_PATTERNS = [
4891
+ { name: "openai_key", pattern: /sk-[a-zA-Z0-9_-]{20,}/g },
4892
+ { name: "anthropic_key", pattern: /sk-ant-[a-zA-Z0-9_-]{20,}/g },
4893
+ { name: "generic_key", pattern: /(?:pk|tok|key|token|api[_-]?key)[_-][a-zA-Z0-9_-]{16,}/gi },
4894
+ { name: "aws_key", pattern: /AKIA[A-Z0-9]{16}/g },
4895
+ { name: "aws_secret", pattern: /(?<=AWS_SECRET_ACCESS_KEY\s*=\s*)[A-Za-z0-9/+=]{40}/g },
4896
+ { name: "github_token", pattern: /gh[ps]_[a-zA-Z0-9]{36,}/g },
4897
+ { name: "github_oauth", pattern: /gho_[a-zA-Z0-9]{36,}/g },
4898
+ { name: "npm_token", pattern: /npm_[a-zA-Z0-9]{36,}/g },
4899
+ { name: "bearer", pattern: /Bearer\s+[a-zA-Z0-9_\-.]{20,}/g },
4900
+ { name: "conn_string", pattern: /(?:postgres|postgresql|mysql|mongodb|redis|amqp|mqtt):\/\/[^\s"'`]+@[^\s"'`]+/gi },
4901
+ { name: "env_secret", pattern: /(?:SECRET|TOKEN|PASSWORD|PASSPHRASE|API_KEY|PRIVATE_KEY|AUTH|CREDENTIAL)[_A-Z]*\s*=\s*["']?[^\s"'\n]{8,}["']?/gi },
4902
+ { name: "stripe_key", pattern: /(?:sk|pk|rk)_(?:test|live)_[a-zA-Z0-9]{20,}/g },
4903
+ { name: "slack_token", pattern: /xox[bpras]-[a-zA-Z0-9-]{20,}/g },
4904
+ { name: "jwt", pattern: /eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g },
4905
+ { name: "hex_secret", pattern: /(?<=(?:key|token|secret|password|hash)\s*[:=]\s*["']?)[0-9a-f]{32,}(?=["']?)/gi }
4906
+ ];
4907
+ function redactSecrets(text) {
4908
+ let result = text;
4909
+ for (const { pattern } of SECRET_PATTERNS) {
4910
+ pattern.lastIndex = 0;
4911
+ result = result.replace(pattern, REDACTED);
4912
+ }
4913
+ return result;
4914
+ }
4915
+ function containsSecrets(text) {
4916
+ for (const { pattern } of SECRET_PATTERNS) {
4917
+ pattern.lastIndex = 0;
4918
+ if (pattern.test(text))
4919
+ return true;
4920
+ }
4921
+ return false;
4922
+ }
4923
+
4924
+ // src/lib/hooks.ts
4925
+ var _idCounter = 0;
4926
+ function generateHookId() {
4927
+ return `hook_${++_idCounter}_${Date.now().toString(36)}`;
4928
+ }
4929
+
4930
+ class HookRegistry {
4931
+ hooks = new Map;
4932
+ register(reg) {
4933
+ const id = generateHookId();
4934
+ const hook = {
4935
+ ...reg,
4936
+ id,
4937
+ priority: reg.priority ?? 50
4938
+ };
4939
+ this.hooks.set(id, hook);
4940
+ return id;
4941
+ }
4942
+ unregister(hookId) {
4943
+ const hook = this.hooks.get(hookId);
4944
+ if (!hook)
4945
+ return false;
4946
+ if (hook.builtin)
4947
+ return false;
4948
+ this.hooks.delete(hookId);
4949
+ return true;
4950
+ }
4951
+ list(type) {
4952
+ const all = [...this.hooks.values()];
4953
+ if (!type)
4954
+ return all;
4955
+ return all.filter((h) => h.type === type);
4956
+ }
4957
+ async runHooks(type, context) {
4958
+ const matching = this.getMatchingHooks(type, context);
4959
+ if (matching.length === 0)
4960
+ return true;
4961
+ matching.sort((a, b) => a.priority - b.priority);
4962
+ for (const hook of matching) {
4963
+ if (hook.blocking) {
4964
+ try {
4965
+ const result = await hook.handler(context);
4966
+ if (result === false)
4967
+ return false;
4968
+ } catch (err) {
4969
+ console.error(`[hooks] blocking hook ${hook.id} (${type}) threw:`, err);
4970
+ }
4971
+ } else {
4972
+ Promise.resolve().then(() => hook.handler(context)).catch((err) => console.error(`[hooks] non-blocking hook ${hook.id} (${type}) threw:`, err));
4973
+ }
4974
+ }
4975
+ return true;
4976
+ }
4977
+ getMatchingHooks(type, context) {
4978
+ const ctx = context;
4979
+ return [...this.hooks.values()].filter((hook) => {
4980
+ if (hook.type !== type)
4981
+ return false;
4982
+ if (hook.agentId && hook.agentId !== ctx.agentId)
4983
+ return false;
4984
+ if (hook.projectId && hook.projectId !== ctx.projectId)
4985
+ return false;
4986
+ return true;
4987
+ });
4988
+ }
4989
+ stats() {
4990
+ const all = [...this.hooks.values()];
4991
+ const byType = {};
4992
+ for (const hook of all) {
4993
+ byType[hook.type] = (byType[hook.type] ?? 0) + 1;
4994
+ }
4995
+ return {
4996
+ total: all.length,
4997
+ byType,
4998
+ blocking: all.filter((h) => h.blocking).length,
4999
+ nonBlocking: all.filter((h) => !h.blocking).length
5000
+ };
5001
+ }
5002
+ }
5003
+ var hookRegistry = new HookRegistry;
5004
+
5005
+ // src/lib/poisoning.ts
5006
+ var INSTRUCTION_PATTERNS = [
5007
+ /\byou\s+must\b/i,
5008
+ /\byou\s+should\b/i,
5009
+ /\byou\s+have\s+to\b/i,
5010
+ /\balways\b/i,
5011
+ /\bnever\b/i
5012
+ ];
5013
+ var PROMOTIONAL_PATTERNS = [
5014
+ /\brecommend\b/i,
5015
+ /\bbuy\b/i,
5016
+ /\bbest\s+product\b/i,
5017
+ /\bpurchase\b/i,
5018
+ /\bdiscount\b/i,
5019
+ /\baffiliate\b/i
5020
+ ];
5021
+ function computeTrustScore(value, key, existingMemories, claimedImportance) {
5022
+ let score = 1;
5023
+ for (const pattern of INSTRUCTION_PATTERNS) {
5024
+ if (pattern.test(value)) {
5025
+ score -= 0.2;
5026
+ break;
5027
+ }
5028
+ }
5029
+ for (const pattern of PROMOTIONAL_PATTERNS) {
5030
+ if (pattern.test(value)) {
5031
+ score -= 0.3;
5032
+ break;
5033
+ }
5034
+ }
5035
+ if (existingMemories && existingMemories.length > 0) {
5036
+ for (const existing of existingMemories) {
5037
+ if (existing.key !== key)
5038
+ continue;
5039
+ if (existing.importance < 7)
5040
+ continue;
5041
+ if (existing.status !== "active")
5042
+ continue;
5043
+ const existingLower = existing.value.toLowerCase().trim();
5044
+ const newLower = value.toLowerCase().trim();
5045
+ if (existingLower === newLower)
5046
+ continue;
5047
+ const existingWords = new Set(existingLower.split(/\s+/));
5048
+ const newWords = new Set(newLower.split(/\s+/));
5049
+ let overlap = 0;
5050
+ for (const w of newWords) {
5051
+ if (existingWords.has(w))
5052
+ overlap++;
5053
+ }
5054
+ const totalUnique = new Set([...newWords, ...existingWords]).size;
5055
+ const overlapRatio = totalUnique > 0 ? overlap / totalUnique : 0;
5056
+ if (overlapRatio < 0.3) {
5057
+ score -= 0.3;
5058
+ break;
5059
+ }
5060
+ }
5061
+ }
5062
+ if (value.length < 10 && (claimedImportance ?? 5) >= 8) {
5063
+ score -= 0.1;
5064
+ }
5065
+ return Math.max(0, Math.min(1, score));
5066
+ }
5067
+
5068
+ // src/db/entity-memories.ts
5069
+ init_database();
5070
+ init_api_mode();
5071
+ function parseEntityRow(row) {
5072
+ return {
5073
+ id: row["id"],
5074
+ name: row["name"],
5075
+ type: row["type"],
5076
+ description: row["description"] || null,
5077
+ metadata: JSON.parse(row["metadata"] || "{}"),
5078
+ project_id: row["project_id"] || null,
5079
+ created_at: row["created_at"],
5080
+ updated_at: row["updated_at"]
5081
+ };
5082
+ }
5083
+ function parseEntityMemoryRow(row) {
5084
+ return {
5085
+ entity_id: row["entity_id"],
5086
+ memory_id: row["memory_id"],
5087
+ role: row["role"],
5088
+ created_at: row["created_at"]
5089
+ };
5090
+ }
5091
+ function linkEntityToMemory(entityId, memoryId, role = "context", db) {
5092
+ if (!db && isApiMode()) {
5093
+ const { data } = apiJson("POST", `/entities/${encodeURIComponent(entityId)}/memories`, {
5094
+ memory_id: memoryId,
5095
+ role
5096
+ });
5097
+ return data;
5098
+ }
5099
+ const d = db || getDatabase();
5100
+ const timestamp = now();
5101
+ d.run(`INSERT OR IGNORE INTO entity_memories (entity_id, memory_id, role, created_at)
5102
+ VALUES (?, ?, ?, ?)`, [entityId, memoryId, role, timestamp]);
5103
+ const row = d.query("SELECT * FROM entity_memories WHERE entity_id = ? AND memory_id = ?").get(entityId, memoryId);
5104
+ return parseEntityMemoryRow(row);
5105
+ }
5106
+ function unlinkEntityFromMemory(entityId, memoryId, db) {
5107
+ if (!db && isApiMode()) {
5108
+ apiJson("DELETE", `/entities/${encodeURIComponent(entityId)}/memories/${encodeURIComponent(memoryId)}`);
5109
+ return;
5110
+ }
5111
+ const d = db || getDatabase();
5112
+ d.run("DELETE FROM entity_memories WHERE entity_id = ? AND memory_id = ?", [entityId, memoryId]);
5113
+ }
5114
+ function getMemoriesForEntity(entityId, db) {
5115
+ if (!db && isApiMode()) {
5116
+ const { data } = apiJson("GET", `/entities/${encodeURIComponent(entityId)}/memories`);
5117
+ return data?.memories ?? [];
5118
+ }
5119
+ const d = db || getDatabase();
5120
+ const rows = d.query(`SELECT m.* FROM memories m
5121
+ INNER JOIN entity_memories em ON em.memory_id = m.id
5122
+ WHERE em.entity_id = ?
5123
+ ORDER BY m.importance DESC, m.created_at DESC`).all(entityId);
5124
+ return rows.map(parseMemoryRow);
5125
+ }
5126
+ function getEntitiesForMemory(memoryId, db) {
5127
+ const d = db || getDatabase();
5128
+ const rows = d.query(`SELECT e.* FROM entities e
5129
+ INNER JOIN entity_memories em ON em.entity_id = e.id
5130
+ WHERE em.memory_id = ?
5131
+ ORDER BY e.name ASC`).all(memoryId);
5132
+ return rows.map(parseEntityRow);
5133
+ }
5134
+ function bulkLinkEntities(entityIds, memoryId, role = "context", db) {
5135
+ const d = db || getDatabase();
5136
+ const timestamp = now();
5137
+ d.transaction(() => {
5138
+ const stmt = d.prepare(`INSERT OR IGNORE INTO entity_memories (entity_id, memory_id, role, created_at)
5139
+ VALUES (?, ?, ?, ?)`);
5140
+ for (const entityId of entityIds) {
5141
+ stmt.run(entityId, memoryId, role, timestamp);
5142
+ }
5143
+ });
5144
+ }
5145
+ function getEntityMemoryLinks(entityId, memoryId, db) {
5146
+ const d = db || getDatabase();
5147
+ const conditions = [];
5148
+ const params = [];
5149
+ if (entityId) {
5150
+ conditions.push("entity_id = ?");
5151
+ params.push(entityId);
5152
+ }
5153
+ if (memoryId) {
5154
+ conditions.push("memory_id = ?");
5155
+ params.push(memoryId);
5156
+ }
5157
+ let sql = "SELECT * FROM entity_memories";
5158
+ if (conditions.length > 0) {
5159
+ sql += ` WHERE ${conditions.join(" AND ")}`;
5160
+ }
5161
+ sql += " ORDER BY created_at DESC";
5162
+ const rows = d.query(sql).all(...params);
5163
+ return rows.map(parseEntityMemoryRow);
5164
+ }
5165
+
5166
+ // src/db/memories.ts
5167
+ init_api_mode();
5168
+ function runEntityExtraction(_memory, _projectId, _d) {}
5169
+ function applyContentType(d, id, memory, contentType) {
5170
+ if (!contentType || contentType === "text")
5171
+ return;
5172
+ try {
5173
+ d.run("UPDATE memories SET content_type = ? WHERE id = ?", [contentType, id]);
5174
+ memory.content_type = contentType;
5175
+ } catch {}
5176
+ }
5177
+ function parseMemoryRow(row) {
5178
+ return {
5179
+ id: row["id"],
5180
+ key: row["key"],
5181
+ value: row["value"],
5182
+ category: row["category"],
5183
+ scope: row["scope"],
5184
+ summary: row["summary"] || null,
5185
+ tags: JSON.parse(row["tags"] || "[]"),
5186
+ importance: row["importance"],
5187
+ source: row["source"],
5188
+ status: row["status"],
5189
+ pinned: !!row["pinned"],
5190
+ agent_id: row["agent_id"] || null,
5191
+ project_id: row["project_id"] || null,
5192
+ session_id: row["session_id"] || null,
5193
+ machine_id: row["machine_id"] || null,
5194
+ flag: row["flag"] || null,
5195
+ when_to_use: row["when_to_use"] || null,
5196
+ sequence_group: row["sequence_group"] || null,
5197
+ sequence_order: row["sequence_order"] ?? null,
5198
+ content_type: row["content_type"] || "text",
5199
+ namespace: row["namespace"] || null,
5200
+ created_by_agent: row["created_by_agent"] || null,
5201
+ updated_by_agent: row["updated_by_agent"] || null,
5202
+ trust_score: row["trust_score"] != null ? row["trust_score"] : null,
5203
+ metadata: JSON.parse(row["metadata"] || "{}"),
5204
+ access_count: row["access_count"],
5205
+ version: row["version"],
5206
+ expires_at: row["expires_at"] || null,
5207
+ valid_from: row["valid_from"] || null,
5208
+ valid_until: row["valid_until"] || null,
5209
+ ingested_at: row["ingested_at"] || null,
5210
+ created_at: row["created_at"],
5211
+ updated_at: row["updated_at"],
5212
+ accessed_at: row["accessed_at"] || null
5213
+ };
5214
+ }
5215
+ function createMemory(input, dedupeMode = "merge", db) {
5216
+ if (!db && isApiMode()) {
5217
+ const { status, data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
5218
+ if (!data || !data.id) {
5219
+ throw new ApiRequestError(`mementos cloud POST /memories \u2192 ${status} but no memory was returned; the write did not persist (key: ${input.key})`, status, "");
5220
+ }
5221
+ return data;
5222
+ }
5223
+ const d = db || getDatabase();
5224
+ const timestamp = now();
5225
+ if (input.project_id) {
5226
+ const resolved = resolvePartialId(d, "projects", input.project_id);
5227
+ if (resolved) {
5228
+ input = { ...input, project_id: resolved };
5229
+ }
5230
+ }
5231
+ let expiresAt = input.expires_at || null;
5232
+ if (input.ttl_ms && !expiresAt) {
5233
+ expiresAt = new Date(Date.now() + input.ttl_ms).toISOString();
5234
+ }
5235
+ if (input.scope === "working" && !expiresAt) {
5236
+ expiresAt = new Date(Date.now() + 60 * 60 * 1000).toISOString();
5237
+ }
5238
+ const id = uuid();
5239
+ const tags = input.tags || [];
5240
+ const tagsJson = JSON.stringify(tags);
5241
+ const metadataJson = JSON.stringify(input.metadata || {});
5242
+ const safeValue = redactSecrets(input.value);
5243
+ const safeSummary = input.summary ? redactSecrets(input.summary) : null;
5244
+ const effectiveMode = dedupeMode === "overwrite" ? "merge" : dedupeMode === "version-fork" ? "create" : dedupeMode;
5245
+ if (effectiveMode === "error") {
5246
+ const existing = d.query(`SELECT id, agent_id, updated_at FROM memories
5247
+ WHERE key = ? AND scope = ? AND COALESCE(project_id, '') = ? AND status = 'active'
5248
+ LIMIT 1`).get(input.key, input.scope || "private", input.project_id || "");
5249
+ if (existing) {
5250
+ throw new MemoryConflictError(input.key, existing);
5251
+ }
5252
+ }
5253
+ if (effectiveMode === "merge") {
5254
+ const existing = d.query(`SELECT id, version FROM memories
5255
+ WHERE key = ? AND scope = ?
5256
+ AND COALESCE(agent_id, '') = ?
5257
+ AND COALESCE(project_id, '') = ?
5258
+ AND COALESCE(session_id, '') = ?`).get(input.key, input.scope || "private", input.agent_id || "", input.project_id || "", input.session_id || "");
5259
+ if (existing) {
5260
+ d.run(`UPDATE memories SET
5261
+ value = ?, category = ?, summary = ?, tags = ?,
5262
+ importance = ?, metadata = ?, expires_at = ?,
5263
+ when_to_use = ?,
5264
+ pinned = COALESCE(pinned, 0),
5265
+ version = version + 1, updated_at = ?
5266
+ WHERE id = ?`, [
5267
+ safeValue,
5268
+ input.category || "knowledge",
5269
+ safeSummary,
5270
+ tagsJson,
5271
+ input.importance ?? 5,
5272
+ metadataJson,
5273
+ expiresAt,
5274
+ input.when_to_use || null,
5275
+ timestamp,
5276
+ existing.id
5277
+ ]);
5278
+ d.run("DELETE FROM memory_tags WHERE memory_id = ?", [existing.id]);
5279
+ const insertTag2 = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
5280
+ for (const tag of tags) {
5281
+ insertTag2.run(existing.id, tag);
5282
+ }
5283
+ const merged = getMemory(existing.id, d);
5284
+ applyContentType(d, existing.id, merged, input.content_type);
5285
+ try {
5286
+ const existingMemories = listMemoriesByKey(input.key, d);
5287
+ const trustScore = computeTrustScore(safeValue, input.key, existingMemories, input.importance);
5288
+ d.run("UPDATE memories SET trust_score = ? WHERE id = ?", [trustScore, existing.id]);
5289
+ } catch {}
5290
+ try {
5291
+ const oldLinks = getEntityMemoryLinks(undefined, merged.id, d);
5292
+ for (const link of oldLinks) {
5293
+ unlinkEntityFromMemory(link.entity_id, merged.id, d);
5294
+ }
5295
+ runEntityExtraction(merged, input.project_id, d);
5296
+ } catch {}
5297
+ return merged;
5298
+ }
5299
+ }
5300
+ ensureMemoryReferences(d, input);
5301
+ d.run(`INSERT INTO memories (id, key, value, category, scope, summary, tags, importance, source, status, pinned, agent_id, project_id, session_id, machine_id, namespace, created_by_agent, when_to_use, sequence_group, sequence_order, metadata, access_count, version, expires_at, valid_from, valid_until, ingested_at, created_at, updated_at)
5302
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', FALSE, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 1, ?, ?, ?, ?, ?, ?)`, [
5303
+ id,
5304
+ input.key,
5305
+ safeValue,
5306
+ input.category || "knowledge",
5307
+ input.scope || "private",
5308
+ input.summary || null,
5309
+ tagsJson,
5310
+ input.importance ?? 5,
5311
+ input.source || "agent",
5312
+ input.agent_id || null,
5313
+ input.project_id || null,
5314
+ input.session_id || null,
5315
+ input.machine_id || null,
5316
+ input.namespace || null,
5317
+ input.agent_id || null,
5318
+ input.when_to_use || null,
5319
+ input.sequence_group || null,
5320
+ input.sequence_order ?? null,
5321
+ metadataJson,
5322
+ expiresAt,
5323
+ input.metadata?.valid_from ?? timestamp,
5324
+ input.metadata?.valid_until ?? null,
5325
+ timestamp,
5326
+ timestamp,
5327
+ timestamp
5328
+ ]);
5329
+ const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
5330
+ for (const tag of tags) {
5331
+ insertTag.run(id, tag);
5332
+ }
5333
+ const memory = getMemory(id, d);
5334
+ applyContentType(d, id, memory, input.content_type);
5335
+ try {
5336
+ const existingMemories = listMemoriesByKey(input.key, d);
5337
+ const trustScore = computeTrustScore(safeValue, input.key, existingMemories, input.importance);
5338
+ d.run("UPDATE memories SET trust_score = ? WHERE id = ?", [trustScore, id]);
5339
+ } catch {}
5340
+ runEntityExtraction(memory, input.project_id, d);
5341
+ hookRegistry.runHooks("PostMemorySave", {
5342
+ memory,
5343
+ wasUpdated: false,
5344
+ agentId: input.agent_id,
5345
+ projectId: input.project_id,
5346
+ sessionId: input.session_id,
5347
+ timestamp: Date.now()
5348
+ });
5349
+ return memory;
5350
+ }
5351
+ function ensureMemoryReferences(d, input) {
5352
+ const t = now();
5353
+ const tryRun = (sql, params) => {
5354
+ try {
5355
+ d.run(sql, params);
5356
+ } catch {}
5357
+ };
5358
+ if (input.agent_id) {
5359
+ tryRun("INSERT OR IGNORE INTO agents (id, name, role, created_at, last_seen_at) VALUES (?, ?, 'agent', ?, ?)", [input.agent_id, `imported-${input.agent_id}`, t, t]);
5360
+ }
5361
+ if (input.project_id) {
5362
+ tryRun("INSERT OR IGNORE INTO projects (id, name, path, created_at, updated_at) VALUES (?, ?, ?, ?, ?)", [input.project_id, `imported-${input.project_id}`, `imported://${input.project_id}`, t, t]);
5363
+ }
5364
+ if (input.machine_id) {
5365
+ tryRun("INSERT OR IGNORE INTO machines (id, name, hostname, platform, created_at, last_seen_at) VALUES (?, ?, ?, 'unknown', ?, ?)", [input.machine_id, `imported-${input.machine_id}`, `imported-${input.machine_id}`, t, t]);
5366
+ }
5367
+ if (input.session_id) {
5368
+ tryRun("INSERT OR IGNORE INTO sessions (id, agent_id, project_id, started_at, last_activity) VALUES (?, ?, ?, ?, ?)", [input.session_id, input.agent_id ?? null, input.project_id ?? null, t, t]);
5369
+ }
5370
+ }
5371
+ function listMemoriesByKey(key, db) {
5372
+ const rows = db.query("SELECT * FROM memories WHERE key = ? AND status = 'active' ORDER BY importance DESC LIMIT 10").all(key);
5373
+ return rows.map(parseMemoryRow);
5374
+ }
5375
+ function getMemory(id, db) {
5376
+ if (!db && isApiMode()) {
5377
+ const { status, data } = apiJson("GET", `/memories/${encodeURIComponent(id)}`, undefined, { allow404: true });
5378
+ return status === 404 ? null : data ?? null;
5379
+ }
5380
+ const d = db || getDatabase();
5381
+ const resolvedId = resolvePartialId(d, "memories", id) ?? id;
5382
+ const row = d.query("SELECT * FROM memories WHERE id = ?").get(resolvedId);
5383
+ if (!row)
5384
+ return null;
5385
+ return parseMemoryRow(row);
5386
+ }
5387
+ function getMemoryByKey(key, scope, agentId, projectId, sessionId, db, as_of) {
5388
+ if (!db && isApiMode()) {
5389
+ const q = toQuery({ key, scope, agent_id: agentId, project_id: projectId, session_id: sessionId, as_of, status: "active", limit: 1 });
5390
+ const { data } = apiJson("GET", `/memories${q}`);
5391
+ return data?.memories?.[0] ?? null;
5392
+ }
5393
+ const d = db || getDatabase();
5394
+ let sql = "SELECT * FROM memories WHERE key = ?";
5395
+ const params = [key];
5396
+ if (scope) {
5397
+ sql += " AND scope = ?";
5398
+ params.push(scope);
5399
+ }
5400
+ if (agentId) {
5401
+ sql += " AND agent_id = ?";
5402
+ params.push(agentId);
5403
+ }
5404
+ if (projectId) {
5405
+ sql += " AND project_id = ?";
5406
+ params.push(projectId);
5407
+ }
5408
+ if (sessionId) {
5409
+ sql += " AND session_id = ?";
5410
+ params.push(sessionId);
5411
+ }
5412
+ if (as_of) {
5413
+ sql += " AND (valid_from IS NULL OR valid_from <= ?)";
5414
+ params.push(as_of);
5415
+ sql += " AND (valid_until IS NULL OR valid_until > ?)";
5416
+ params.push(as_of);
5417
+ }
5418
+ sql += " AND status = 'active' ORDER BY importance DESC LIMIT 1";
5419
+ const row = d.query(sql).get(...params);
5420
+ if (!row)
5421
+ return null;
5422
+ return parseMemoryRow(row);
5423
+ }
5424
+ function getMemoriesByKey(key, scope, agentId, projectId, db) {
5425
+ if (!db && isApiMode()) {
5426
+ const q = toQuery({ key, scope, agent_id: agentId, project_id: projectId, status: "active" });
5427
+ const { data } = apiJson("GET", `/memories${q}`);
5428
+ return data?.memories ?? [];
5429
+ }
5430
+ const d = db || getDatabase();
5431
+ let sql = "SELECT * FROM memories WHERE key = ?";
5432
+ const params = [key];
5433
+ if (scope) {
5434
+ sql += " AND scope = ?";
5435
+ params.push(scope);
5436
+ }
5437
+ if (agentId) {
5438
+ sql += " AND agent_id = ?";
5439
+ params.push(agentId);
5440
+ }
5441
+ if (projectId) {
5442
+ sql += " AND project_id = ?";
5443
+ params.push(projectId);
5444
+ }
5445
+ sql += " AND status = 'active' ORDER BY importance DESC";
5446
+ const rows = d.query(sql).all(...params);
5447
+ return rows.map(parseMemoryRow);
5448
+ }
5449
+ function listMemories(filter, db) {
5450
+ if (!db && isApiMode()) {
5451
+ const f = filter || {};
5452
+ const q = toQuery({
5453
+ key: f.key,
5454
+ scope: f.scope,
5455
+ category: f.category,
5456
+ status: f.status,
5457
+ tags: f.tags,
5458
+ min_importance: f.min_importance,
5459
+ pinned: f.pinned,
5460
+ agent_id: f.agent_id,
5461
+ project_id: f.project_id,
5462
+ session_id: f.session_id,
5463
+ namespace: f.namespace,
5464
+ as_of: f.as_of,
5465
+ limit: f.limit,
5466
+ offset: f.offset
5467
+ });
5468
+ const { data } = apiJson("GET", `/memories${q}`);
5469
+ return data?.memories ?? [];
5470
+ }
5471
+ const d = db || getDatabase();
5472
+ const conditions = [];
5473
+ const params = [];
5474
+ if (filter) {
5475
+ if (filter.key) {
5476
+ conditions.push("key = ?");
5477
+ params.push(filter.key);
5478
+ }
5479
+ if (filter.scope) {
5480
+ if (Array.isArray(filter.scope)) {
5481
+ conditions.push(`scope IN (${filter.scope.map(() => "?").join(",")})`);
5482
+ params.push(...filter.scope);
5483
+ } else {
5484
+ conditions.push("scope = ?");
5485
+ params.push(filter.scope);
5486
+ }
5487
+ }
5488
+ if (filter.category) {
5489
+ if (Array.isArray(filter.category)) {
5490
+ conditions.push(`category IN (${filter.category.map(() => "?").join(",")})`);
5491
+ params.push(...filter.category);
5492
+ } else {
5493
+ conditions.push("category = ?");
5494
+ params.push(filter.category);
5495
+ }
5496
+ }
5497
+ if (filter.source) {
5498
+ if (Array.isArray(filter.source)) {
5499
+ conditions.push(`source IN (${filter.source.map(() => "?").join(",")})`);
5500
+ params.push(...filter.source);
5501
+ } else {
5502
+ conditions.push("source = ?");
5503
+ params.push(filter.source);
5504
+ }
5505
+ }
5506
+ if (filter.status) {
5507
+ if (Array.isArray(filter.status)) {
5508
+ conditions.push(`status IN (${filter.status.map(() => "?").join(",")})`);
5509
+ params.push(...filter.status);
5510
+ } else {
5511
+ conditions.push("status = ?");
5512
+ params.push(filter.status);
5513
+ }
5514
+ } else {
5515
+ conditions.push("status = 'active'");
5516
+ }
5517
+ if (filter.project_id) {
5518
+ conditions.push("project_id = ?");
5519
+ params.push(filter.project_id);
5520
+ }
5521
+ if (filter.agent_id) {
5522
+ conditions.push("agent_id = ?");
5523
+ params.push(filter.agent_id);
5524
+ }
5525
+ if (filter.session_id) {
5526
+ conditions.push("session_id = ?");
5527
+ params.push(filter.session_id);
5528
+ }
5529
+ if ("machine_id" in filter) {
5530
+ if (filter.machine_id === null) {
5531
+ conditions.push("machine_id IS NULL");
5532
+ } else if (filter.machine_id) {
5533
+ conditions.push("machine_id = ?");
5534
+ params.push(filter.machine_id);
5535
+ }
5536
+ }
5537
+ if ("visible_to_machine_id" in filter) {
5538
+ if (filter.visible_to_machine_id === null) {
5539
+ conditions.push("machine_id IS NULL");
5540
+ } else if (filter.visible_to_machine_id !== undefined) {
5541
+ conditions.push("(machine_id IS NULL OR machine_id = ?)");
5542
+ params.push(filter.visible_to_machine_id);
5543
+ }
5544
+ }
5545
+ if (filter.min_importance) {
5546
+ conditions.push("importance >= ?");
5547
+ params.push(filter.min_importance);
5548
+ }
5549
+ if (filter.pinned !== undefined) {
5550
+ conditions.push("pinned = ?");
5551
+ params.push(filter.pinned ? 1 : 0);
5552
+ }
5553
+ if (filter.flagged === true) {
5554
+ conditions.push("flag IS NOT NULL");
5555
+ } else if (filter.flag) {
5556
+ conditions.push("flag = ?");
5557
+ params.push(filter.flag);
5558
+ }
5559
+ if (filter.tags && filter.tags.length > 0) {
5560
+ for (const tag of filter.tags) {
5561
+ conditions.push("id IN (SELECT memory_id FROM memory_tags WHERE tag = ?)");
5562
+ params.push(tag);
5563
+ }
5564
+ }
5565
+ if (filter.namespace) {
5566
+ conditions.push("namespace = ?");
5567
+ params.push(filter.namespace);
5568
+ }
5569
+ if (filter.search) {
5570
+ conditions.push("(key LIKE ? OR value LIKE ? OR summary LIKE ?)");
5571
+ const term = `%${filter.search}%`;
5572
+ params.push(term, term, term);
5573
+ }
5574
+ if (filter.as_of) {
5575
+ conditions.push("(valid_from IS NULL OR valid_from <= ?)");
5576
+ params.push(filter.as_of);
5577
+ conditions.push("(valid_until IS NULL OR valid_until > ?)");
5578
+ params.push(filter.as_of);
5579
+ }
5580
+ } else {
5581
+ conditions.push("status = 'active'");
5582
+ }
5583
+ let sql = "SELECT * FROM memories";
5584
+ if (conditions.length > 0) {
5585
+ sql += ` WHERE ${conditions.join(" AND ")}`;
5586
+ }
5587
+ sql += " ORDER BY importance DESC, created_at DESC";
5588
+ if (filter?.limit) {
5589
+ sql += " LIMIT ?";
5590
+ params.push(filter.limit);
5591
+ }
5592
+ if (filter?.offset) {
5593
+ sql += " OFFSET ?";
5594
+ params.push(filter.offset);
5595
+ }
5596
+ const rows = d.query(sql).all(...params);
5597
+ return rows.map(parseMemoryRow);
5598
+ }
5599
+ function updateMemory(id, input, db) {
5600
+ if (!db && isApiMode()) {
5601
+ const { status, data } = apiJson("PATCH", `/memories/${encodeURIComponent(id)}`, input, { allow404: true });
5602
+ if (status === 404)
5603
+ throw new MemoryNotFoundError(id);
5604
+ if (data && typeof input.version === "number" && typeof data.version === "number" && data.version <= input.version) {
5605
+ throw new Error(`Update did not persist for memory ${id}: the server returned success but the record is unchanged ` + `(version still ${data.version}). Your data was NOT written. ` + `The server is likely running a build predating the partial-id fix \u2014 pass the full 36-character id as a workaround.`);
5606
+ }
5607
+ return data;
5608
+ }
5609
+ const d = db || getDatabase();
5610
+ const existing = getMemory(id, d);
5611
+ if (!existing)
5612
+ throw new MemoryNotFoundError(id);
5613
+ const memoryId = existing.id;
5614
+ if (existing.version !== input.version) {
5615
+ throw new VersionConflictError(id, input.version, existing.version);
5616
+ }
5617
+ const sets = ["version = version + 1", "updated_at = ?"];
5618
+ const params = [now()];
5619
+ if (input.value !== undefined) {
5620
+ sets.push("value = ?");
5621
+ params.push(redactSecrets(input.value));
5622
+ }
5623
+ if (input.category !== undefined) {
5624
+ sets.push("category = ?");
5625
+ params.push(input.category);
5626
+ }
5627
+ if (input.scope !== undefined) {
5628
+ sets.push("scope = ?");
5629
+ params.push(input.scope);
5630
+ }
5631
+ if (input.summary !== undefined) {
5632
+ sets.push("summary = ?");
5633
+ params.push(input.summary);
5634
+ }
5635
+ if (input.importance !== undefined) {
5636
+ sets.push("importance = ?");
5637
+ params.push(input.importance);
5638
+ }
5639
+ if (input.pinned !== undefined) {
5640
+ sets.push("pinned = ?");
5641
+ params.push(input.pinned ? 1 : 0);
5642
+ }
5643
+ if (input.status !== undefined) {
5644
+ sets.push("status = ?");
5645
+ params.push(input.status);
5646
+ }
5647
+ if (input.metadata !== undefined) {
5648
+ sets.push("metadata = ?");
5649
+ params.push(JSON.stringify(input.metadata));
5650
+ }
5651
+ if (input.expires_at !== undefined) {
5652
+ sets.push("expires_at = ?");
5653
+ params.push(input.expires_at);
5654
+ }
5655
+ if (input.flag !== undefined) {
5656
+ sets.push("flag = ?");
5657
+ params.push(input.flag ?? null);
5658
+ }
5659
+ if (input.when_to_use !== undefined) {
5660
+ sets.push("when_to_use = ?");
5661
+ params.push(input.when_to_use ?? null);
5662
+ }
5663
+ if (input.tags !== undefined) {
5664
+ sets.push("tags = ?");
5665
+ params.push(JSON.stringify(input.tags));
5666
+ d.run("DELETE FROM memory_tags WHERE memory_id = ?", [memoryId]);
5667
+ const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
5668
+ for (const tag of input.tags) {
5669
+ insertTag.run(memoryId, tag);
5670
+ }
5671
+ }
5672
+ params.push(memoryId);
5673
+ const result = d.run(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`, params);
5674
+ if (result.changes === 0) {
5675
+ throw new Error(`Update affected no rows for memory ${memoryId}: the record was read but not written. ` + `This is a bug in @hasna/mementos, not a bad argument \u2014 please report it.`);
5676
+ }
5677
+ const updated = getMemory(memoryId, d);
5678
+ if (input.value !== undefined) {
5679
+ try {
5680
+ const oldLinks = getEntityMemoryLinks(undefined, updated.id, d);
5681
+ for (const link of oldLinks) {
5682
+ unlinkEntityFromMemory(link.entity_id, updated.id, d);
5683
+ }
5684
+ } catch {}
5685
+ }
5686
+ hookRegistry.runHooks("PostMemoryUpdate", {
5687
+ memory: updated,
5688
+ previousValue: existing.value,
5689
+ agentId: existing.agent_id ?? undefined,
5690
+ projectId: existing.project_id ?? undefined,
5691
+ sessionId: existing.session_id ?? undefined,
5692
+ timestamp: Date.now()
5693
+ });
5694
+ return updated;
5695
+ }
5696
+ function deleteMemory(id, db) {
5697
+ if (!db && isApiMode()) {
5698
+ const { status } = apiJson("DELETE", `/memories/${encodeURIComponent(id)}`, undefined, { allow404: true });
5699
+ return status !== 404;
5700
+ }
5701
+ const d = db || getDatabase();
5702
+ const memoryId = resolvePartialId(d, "memories", id) ?? id;
5703
+ const result = d.run("DELETE FROM memories WHERE id = ?", [memoryId]);
5704
+ if (result.changes > 0) {
5705
+ hookRegistry.runHooks("PostMemoryDelete", {
5706
+ memoryId,
5707
+ timestamp: Date.now()
5708
+ });
5709
+ }
5710
+ return result.changes > 0;
5711
+ }
5712
+ function bulkDeleteMemories(ids, db) {
5713
+ if (ids.length === 0)
5714
+ return 0;
5715
+ if (!db && isApiMode()) {
5716
+ const { data } = apiJson("POST", "/memories/bulk-forget", { ids });
5717
+ return data?.deleted ?? 0;
5718
+ }
5719
+ const d = db || getDatabase();
5720
+ const resolvedIds = ids.map((id) => resolvePartialId(d, "memories", id) ?? id);
5721
+ const placeholders = resolvedIds.map(() => "?").join(",");
5722
+ const countRow = d.query(`SELECT COUNT(*) as c FROM memories WHERE id IN (${placeholders})`).get(...resolvedIds);
5723
+ const count = countRow.c;
5724
+ if (count > 0) {
5725
+ d.run(`DELETE FROM memories WHERE id IN (${placeholders})`, resolvedIds);
5726
+ }
5727
+ return count;
5728
+ }
5729
+ function touchMemory(id, db) {
5730
+ if (!db && isApiMode())
5731
+ return;
5732
+ const d = db || getDatabase();
5733
+ d.run("UPDATE memories SET access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now(), id]);
5734
+ }
5735
+ var RECALL_PROMOTE_THRESHOLD = 3;
5736
+ function incrementRecallCount(id, db) {
5737
+ if (!db && isApiMode())
5738
+ return;
5739
+ const d = db || getDatabase();
5740
+ try {
5741
+ d.run("UPDATE memories SET recall_count = recall_count + 1, access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now(), id]);
5742
+ const row = d.query("SELECT recall_count, importance FROM memories WHERE id = ?").get(id);
5743
+ if (!row)
5744
+ return;
5745
+ const promotions = Math.floor(row.recall_count / RECALL_PROMOTE_THRESHOLD);
5746
+ if (promotions > 0 && row.importance < 10) {
5747
+ const newImportance = Math.min(10, row.importance + 1);
5748
+ d.run("UPDATE memories SET importance = ? WHERE id = ? AND importance < 10", [newImportance, id]);
5749
+ }
5750
+ } catch {}
5751
+ }
5752
+ function cleanExpiredMemories(db) {
5753
+ if (!db && isApiMode()) {
5754
+ const { data } = apiJson("POST", "/memories/clean");
5755
+ return data?.cleaned ?? 0;
5756
+ }
5757
+ const d = db || getDatabase();
5758
+ const timestamp = now();
5759
+ const countRow = d.query("SELECT COUNT(*) as c FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?").get(timestamp);
5760
+ const count = countRow.c;
5761
+ if (count > 0) {
5762
+ d.run("DELETE FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?", [timestamp]);
5763
+ }
5764
+ return count;
5765
+ }
5766
+ function getMemoryVersions(memoryId, db) {
5767
+ if (!db && isApiMode()) {
5768
+ const { data } = apiJson("GET", `/memories/${encodeURIComponent(memoryId)}/versions`);
5769
+ return data?.versions ?? [];
5770
+ }
5771
+ const d = db || getDatabase();
5772
+ try {
5773
+ const rows = d.query("SELECT * FROM memory_versions WHERE memory_id = ? ORDER BY version ASC").all(memoryId);
5774
+ return rows.map((row) => ({
5775
+ id: row["id"],
5776
+ memory_id: row["memory_id"],
5777
+ version: row["version"],
5778
+ value: row["value"],
5779
+ importance: row["importance"],
5780
+ scope: row["scope"],
5781
+ category: row["category"],
5782
+ tags: JSON.parse(row["tags"] || "[]"),
5783
+ summary: row["summary"] || null,
5784
+ pinned: !!row["pinned"],
5785
+ status: row["status"],
5786
+ created_at: row["created_at"]
5787
+ }));
5788
+ } catch {
5789
+ return [];
5790
+ }
5791
+ }
5792
+
5793
+ // src/project-registration/project-resources.ts
5794
+ var DEFAULT_PAGE_LIMIT = 100;
5795
+ var MAX_PAGE_LIMIT = 1000;
5796
+ var CURSOR_SCHEMA = "mementos.project-resources.cursor.v1";
5797
+
5798
+ class MementosProjectResourceError extends Error {
5799
+ code;
5800
+ details;
5801
+ constructor(code, message, details = {}) {
5802
+ super(message);
5803
+ this.code = code;
5804
+ this.details = details;
5805
+ this.name = "MementosProjectResourceError";
5806
+ }
5807
+ }
5808
+ function timestamp(value) {
5809
+ return value instanceof Date ? value.toISOString() : String(value);
5810
+ }
5811
+ function normalizeSqlValue(value) {
5812
+ if (value instanceof Date)
5813
+ return value.toISOString();
5814
+ if (Array.isArray(value))
5815
+ return value.map(normalizeSqlValue);
5816
+ if (!value || typeof value !== "object")
5817
+ return value;
5818
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, normalizeSqlValue(item)]));
5819
+ }
5820
+ function exactProject(db, projectId) {
5821
+ const row = db.get("SELECT id, name, path, description, memory_prefix, created_at, updated_at FROM projects WHERE id = ? LIMIT 1", projectId);
5822
+ if (!row) {
5823
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_PROJECT_NOT_FOUND", `Mementos project not found: ${projectId}`, { project_id: projectId });
5824
+ }
5825
+ return row;
5826
+ }
5827
+ function resourceKey(resource) {
5828
+ const rank = MEMENTOS_PROJECT_RESOURCE_KINDS.indexOf(resource.resource_kind);
5829
+ return `${String(rank).padStart(2, "0")}:${resource.stable_id}`;
5830
+ }
5831
+ function projectResource(project) {
5832
+ const normalized = normalizeSqlValue(project);
5833
+ return {
5834
+ authority: "mementos",
5835
+ source_package: "@hasna/mementos",
5836
+ project_id: project.id,
5837
+ resource_kind: "project",
5838
+ stable_id: project.id,
5839
+ revision: timestamp(project.updated_at),
5840
+ digest: digestMementosProjectRegistrationValue(normalized),
5841
+ membership: "project_aggregate"
5842
+ };
5843
+ }
5844
+ function memoryResources(db, projectId) {
5845
+ const rows = db.all("SELECT * FROM memories WHERE project_id = ? ORDER BY id ASC", projectId);
5846
+ return rows.map((row) => {
5847
+ const memory = normalizeSqlValue(parseMemoryRow(row));
5848
+ return {
5849
+ authority: "mementos",
5850
+ source_package: "@hasna/mementos",
5851
+ project_id: projectId,
5852
+ resource_kind: row["category"] === "knowledge" ? "knowledge" : "memory",
5853
+ stable_id: String(row["id"]),
5854
+ revision: timestamp(row["updated_at"]),
5855
+ digest: digestMementosProjectRegistrationValue(memory),
5856
+ membership: "explicit_project_id_or_focus"
5857
+ };
5858
+ });
5859
+ }
5860
+ function sessionResources(db, projectId) {
5861
+ const rows = db.all("SELECT * FROM session_memory_jobs WHERE project_id = ? ORDER BY id ASC", projectId);
5862
+ return rows.map((row) => {
5863
+ const normalized = {
5864
+ id: String(row["id"]),
5865
+ session_id: String(row["session_id"]),
5866
+ agent_id: row["agent_id"] === null ? null : String(row["agent_id"] ?? "") || null,
5867
+ project_id: row["project_id"] === null ? null : String(row["project_id"] ?? "") || null,
5868
+ source: String(row["source"]),
5869
+ status: String(row["status"]),
5870
+ transcript: String(row["transcript"]),
5871
+ chunk_count: Number(row["chunk_count"]),
5872
+ memories_extracted: Number(row["memories_extracted"]),
5873
+ error: row["error"] === null ? null : String(row["error"] ?? "") || null,
5874
+ metadata: typeof row["metadata"] === "string" ? JSON.parse(row["metadata"] || "{}") : normalizeSqlValue(row["metadata"] ?? {}),
5875
+ created_at: timestamp(row["created_at"]),
5876
+ started_at: row["started_at"] === null ? null : timestamp(row["started_at"]),
5877
+ completed_at: row["completed_at"] === null ? null : timestamp(row["completed_at"])
5878
+ };
5879
+ return {
5880
+ authority: "mementos",
5881
+ source_package: "@hasna/mementos",
5882
+ project_id: projectId,
5883
+ resource_kind: "session",
5884
+ stable_id: String(row["id"]),
5885
+ revision: timestamp(row["completed_at"] ?? row["started_at"] ?? row["created_at"]),
5886
+ digest: digestMementosProjectRegistrationValue(normalized),
5887
+ membership: "explicit_project_id_or_focus"
5888
+ };
5889
+ });
5890
+ }
5891
+ function normalizeResourceKinds(value) {
5892
+ if (!value)
5893
+ return [...MEMENTOS_PROJECT_RESOURCE_KINDS];
5894
+ if (value.length === 0) {
5895
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", "resource_kinds must contain at least one supported resource kind");
5896
+ }
5897
+ const requested = new Set(value);
5898
+ for (const kind of requested) {
5899
+ if (!MEMENTOS_PROJECT_RESOURCE_KINDS.includes(kind)) {
5900
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", `Unsupported Mementos project resource kind: ${kind}`);
5901
+ }
5902
+ }
5903
+ return MEMENTOS_PROJECT_RESOURCE_KINDS.filter((kind) => requested.has(kind));
5904
+ }
5905
+ function normalizeLimit(value) {
5906
+ const limit = value ?? DEFAULT_PAGE_LIMIT;
5907
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_PAGE_LIMIT) {
5908
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", `limit must be an integer between 1 and ${MAX_PAGE_LIMIT}`);
5909
+ }
5910
+ return limit;
5911
+ }
5912
+ function encodeCursor(cursor) {
5913
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
5914
+ }
5915
+ function decodeCursor(raw) {
5916
+ try {
5917
+ const parsed = JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
5918
+ if (parsed.schema !== CURSOR_SCHEMA || typeof parsed.project_id !== "string" || typeof parsed.collection_revision !== "string" || !Array.isArray(parsed.resource_kinds) || typeof parsed.after_key !== "string") {
5919
+ throw new Error("invalid cursor shape");
5920
+ }
5921
+ return parsed;
5922
+ } catch {
5923
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", "cursor is not a valid Mementos project-resource cursor");
5924
+ }
5925
+ }
5926
+ function localPopulation(projectId, db, resourceKinds) {
5927
+ const project = exactProject(db, projectId);
5928
+ const selected = new Set(resourceKinds);
5929
+ const resources = [
5930
+ ...selected.has("project") ? [projectResource(project)] : [],
5931
+ ...memoryResources(db, projectId).filter((resource) => selected.has(resource.resource_kind)),
5932
+ ...selected.has("session") ? sessionResources(db, projectId) : []
5933
+ ].sort((left, right) => resourceKey(left).localeCompare(resourceKey(right)));
5934
+ const collectionRevision = digestMementosProjectRegistrationValue({
5935
+ schema: MEMENTOS_PROJECT_RESOURCE_ROUTE,
5936
+ project_id: projectId,
5937
+ project_revision: timestamp(project.updated_at),
5938
+ resource_kinds: resourceKinds,
5939
+ resources: resources.map((resource) => ({
5940
+ resource_kind: resource.resource_kind,
5941
+ stable_id: resource.stable_id,
5942
+ revision: resource.revision,
5943
+ digest: resource.digest
5944
+ }))
5945
+ });
5946
+ return { project, resources, collectionRevision };
5947
+ }
5948
+ function readMementosProjectResourcePage(projectId, options = {}, db, authorityOptions = {}) {
5949
+ const resourceKinds = normalizeResourceKinds(options.resource_kinds);
5950
+ const limit = normalizeLimit(options.limit);
5951
+ if (!db && isApiMode()) {
5952
+ const { data } = apiJson("GET", `/projects/${encodeURIComponent(projectId)}/resources${toQuery({
5953
+ limit,
5954
+ cursor: options.cursor ?? undefined,
5955
+ resource_kinds: resourceKinds.join(",")
5956
+ })}`);
5957
+ return data;
5958
+ }
5959
+ const d = db ?? getDatabase();
5960
+ const { project, resources, collectionRevision } = localPopulation(projectId, d, resourceKinds);
5961
+ let start = 0;
5962
+ if (options.cursor) {
5963
+ const cursor = decodeCursor(options.cursor);
5964
+ if (cursor.project_id !== projectId || JSON.stringify(cursor.resource_kinds) !== JSON.stringify(resourceKinds)) {
5965
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", "cursor does not belong to this project and resource-kind selection");
5966
+ }
5967
+ if (cursor.collection_revision !== collectionRevision) {
5968
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_COLLECTION_CHANGED", "Mementos project resource collection changed; restart from the first page", {
5969
+ cursor_collection_revision: cursor.collection_revision,
5970
+ current_collection_revision: collectionRevision
5971
+ });
5972
+ }
5973
+ const afterIndex = resources.findIndex((resource) => resourceKey(resource) === cursor.after_key);
5974
+ if (afterIndex < 0) {
5975
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_COLLECTION_CHANGED", "Mementos project resource cursor no longer names a member; restart from the first page");
5976
+ }
5977
+ start = afterIndex + 1;
5978
+ }
5979
+ const pageResources = resources.slice(start, start + limit);
5980
+ const hasMore = start + pageResources.length < resources.length;
5981
+ const nextCursor = hasMore && pageResources.length > 0 ? encodeCursor({
5982
+ schema: CURSOR_SCHEMA,
5983
+ project_id: projectId,
5984
+ collection_revision: collectionRevision,
5985
+ resource_kinds: resourceKinds,
5986
+ after_key: resourceKey(pageResources[pageResources.length - 1])
5987
+ }) : null;
5988
+ const capability = buildMementosProjectRegistrationCapability(authorityOptions);
5989
+ return {
5990
+ schema: "mementos.project-resources.v1",
5991
+ authority: {
5992
+ authority: capability.authority,
5993
+ authority_id: capability.authority_id,
5994
+ tenant_id: capability.tenant_id,
5995
+ corpus_id: capability.corpus_id,
5996
+ package_version: capability.package_version
5997
+ },
5998
+ project_id: projectId,
5999
+ project_revision: timestamp(project.updated_at),
6000
+ collection_revision: collectionRevision,
6001
+ resource_kinds: resourceKinds,
6002
+ resources: pageResources,
6003
+ count: pageResources.length,
6004
+ total: resources.length,
6005
+ limit,
6006
+ cursor: options.cursor ?? null,
6007
+ next_cursor: nextCursor,
6008
+ has_more: hasMore,
6009
+ complete: true,
6010
+ truncated: false
6011
+ };
6012
+ }
6013
+ function readAllMementosProjectResources(projectId, options = {}, db, authorityOptions = {}) {
6014
+ const pageSize = normalizeLimit(options.page_size);
6015
+ let cursor = null;
6016
+ let first = null;
6017
+ const resources = [];
6018
+ const seen = new Set;
6019
+ do {
6020
+ const page = readMementosProjectResourcePage(projectId, {
6021
+ limit: pageSize,
6022
+ cursor,
6023
+ resource_kinds: options.resource_kinds
6024
+ }, db, authorityOptions);
6025
+ if (!first)
6026
+ first = page;
6027
+ if (page.collection_revision !== first.collection_revision || page.total !== first.total || JSON.stringify(page.resource_kinds) !== JSON.stringify(first.resource_kinds)) {
6028
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_COLLECTION_CHANGED", "Mementos project resource collection changed during complete traversal");
6029
+ }
6030
+ for (const resource of page.resources) {
6031
+ const key = resourceKey(resource);
6032
+ if (seen.has(key)) {
6033
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INCOMPLETE", `Mementos project resource traversal returned duplicate stable ID: ${key}`);
6034
+ }
6035
+ seen.add(key);
6036
+ resources.push(resource);
6037
+ }
6038
+ if (page.has_more && !page.next_cursor) {
6039
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INCOMPLETE", "Mementos project resource page claimed more results without a continuation cursor");
6040
+ }
6041
+ cursor = page.next_cursor;
6042
+ } while (cursor);
6043
+ if (!first) {
6044
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INCOMPLETE", "Mementos project resource traversal returned no first page");
6045
+ }
6046
+ if (resources.length !== first.total) {
6047
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INCOMPLETE", `Mementos project resource traversal returned ${resources.length} of ${first.total} resources`);
6048
+ }
6049
+ return {
6050
+ ...first,
6051
+ resources,
6052
+ count: resources.length,
6053
+ total: resources.length,
6054
+ limit: pageSize,
6055
+ cursor: null,
6056
+ next_cursor: null,
6057
+ has_more: false,
6058
+ complete: true,
6059
+ truncated: false
6060
+ };
6061
+ }
6062
+ function getMementosProjectResourceExact(projectId, resourceKind, stableId, db, authorityOptions = {}) {
6063
+ if (!MEMENTOS_PROJECT_RESOURCE_KINDS.includes(resourceKind)) {
6064
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", `Unsupported Mementos project resource kind: ${resourceKind}`);
6065
+ }
6066
+ if (!db && isApiMode()) {
6067
+ const { data } = apiJson("GET", `/projects/${encodeURIComponent(projectId)}/resources/${encodeURIComponent(resourceKind)}/${encodeURIComponent(stableId)}`);
6068
+ return data;
6069
+ }
6070
+ const d = db ?? getDatabase();
6071
+ const { project, resources, collectionRevision } = localPopulation(projectId, d, [
6072
+ resourceKind
6073
+ ]);
6074
+ const resource = resources.find((candidate) => candidate.stable_id === stableId);
6075
+ if (!resource) {
6076
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_NOT_FOUND", `Mementos ${resourceKind} resource not found in project ${projectId}: ${stableId}`, { project_id: projectId, resource_kind: resourceKind, stable_id: stableId });
6077
+ }
6078
+ const capability = buildMementosProjectRegistrationCapability(authorityOptions);
6079
+ return {
6080
+ schema: "mementos.project-resource.v1",
6081
+ authority: {
6082
+ authority: capability.authority,
6083
+ authority_id: capability.authority_id,
6084
+ tenant_id: capability.tenant_id,
6085
+ corpus_id: capability.corpus_id,
6086
+ package_version: capability.package_version
6087
+ },
6088
+ project_id: projectId,
6089
+ project_revision: timestamp(project.updated_at),
6090
+ collection_revision: collectionRevision,
6091
+ resource,
6092
+ complete: true,
6093
+ truncated: false
6094
+ };
6095
+ }
4673
6096
  export {
4674
6097
  sqliteMementosProjectRegistrationSchemaSql,
4675
6098
  sqliteMementosProjectGuardedUpdateSchemaSql,
6099
+ resolveMementosProjectAuthorityIdentity,
6100
+ readMementosProjectResourcePage,
6101
+ readAllMementosProjectResources,
4676
6102
  postgresMementosProjectRegistrationSchemaSql,
4677
6103
  postgresMementosProjectGuardedUpdateSchemaSql,
4678
6104
  mementosProjectReferenceCounts,
4679
6105
  hasMementosProjectReferences,
4680
6106
  handleMementosProjectRegistrationHttpRequest,
6107
+ getMementosProjectResourceExact,
4681
6108
  digestMementosProjectRegistrationValue,
4682
6109
  deriveMementosProjectRegistrationIdempotencyKey,
4683
6110
  createMementosProjectRegistrationHttpClient,
4684
6111
  createMementosProjectRegistrationAuthority,
4685
6112
  createLocalMementosProjectRegistrationAuthority,
4686
6113
  canonicalMementosProjectRegistrationJson,
6114
+ buildMementosProjectRegistrationCapability,
4687
6115
  PackageOwnedMementosProjectRegistrationAuthority,
6116
+ MementosProjectResourceError,
4688
6117
  MementosProjectRegistrationHttpClient,
4689
6118
  MementosProjectRegistrationError,
6119
+ MementosProjectAuthorityIdentityError,
6120
+ MEMENTOS_PROJECT_RESOURCE_ROUTE,
6121
+ MEMENTOS_PROJECT_RESOURCE_KINDS,
4690
6122
  MEMENTOS_PROJECT_REGISTRATION_SCHEMA_VERSION,
4691
6123
  MEMENTOS_PROJECT_REGISTRATION_ROUTE,
4692
6124
  MEMENTOS_PROJECT_REGISTRATION_CALLER_ROUTE,
4693
6125
  MEMENTOS_PROJECT_REFERENCE_SURFACES,
4694
- MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE
6126
+ MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
6127
+ MEMENTOS_PROJECT_AUTHORITY_ENV
4695
6128
  };