@ponraaj/dimple-sdk 0.0.0-alpha.3 → 0.0.0-alpha.5

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/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { createRequire } from "node:module";
1
2
  var __defProp = Object.defineProperty;
2
3
  var __returnValue = (v) => v;
3
4
  function __exportSetter(name, newValue) {
@@ -12,9 +13,10 @@ var __export = (target, all) => {
12
13
  set: __exportSetter.bind(all, name)
13
14
  });
14
15
  };
16
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
15
17
 
16
18
  // src/make.ts
17
- import { Effect as Effect50, Exit as Exit7, Layer as Layer26, ManagedRuntime as ManagedRuntime2, Scope as Scope5 } from "effect";
19
+ import { Effect as Effect50, Exit as Exit7, Layer as Layer26, ManagedRuntime, Scope as Scope5 } from "effect";
18
20
 
19
21
  // ../core/src/core.ts
20
22
  import { Context, Schema } from "effect";
@@ -31,7 +33,7 @@ import { Effect as Effect39, Layer as Layer15, Schema as Schema24 } from "effect
31
33
 
32
34
  // ../store/src/store.ts
33
35
  import { Config as Config4, Effect as Effect26, Layer as Layer10, Schema as Schema19 } from "effect";
34
- import { eq as eq8, and as and8, desc as desc2, asc as asc5, inArray as inArray4, ne as ne4, or, isNull as isNull2, sql as sql7 } from "drizzle-orm";
36
+ import { eq as eq8, and as and8, desc as desc2, asc as asc5, inArray as inArray4, ne as ne4, or, isNull as isNull2, like, sql as sql7 } from "drizzle-orm";
35
37
 
36
38
  // ../store/src/db-backend.ts
37
39
  import { Context as Context2 } from "effect";
@@ -44,9 +46,6 @@ import { Effect as Effect9, Layer as Layer3, Config as Config2 } from "effect";
44
46
  import { createClient } from "@libsql/client";
45
47
  import { drizzle } from "drizzle-orm/libsql";
46
48
  import { sql as sql2 } from "drizzle-orm";
47
- import * as fs2 from "node:fs";
48
- import { dirname as dirname2 } from "node:path";
49
- import { migrate } from "drizzle-orm/libsql/migrator";
50
49
 
51
50
  // ../store/src/error.ts
52
51
  import { Schema as Schema2 } from "effect";
@@ -109,6 +108,11 @@ var EMBEDDED_MIGRATIONS = [
109
108
  name: "20260804035154_dream_state",
110
109
  hash: "376f6768a43edf8941614b92720222c895e258411504fb5857569dff5990b62f",
111
110
  sql: "CREATE TABLE `dream_state` (\n\t`model_key` text PRIMARY KEY,\n\t`last_dream_at` integer NOT NULL,\n\t`last_dream_memory_count` integer NOT NULL\n);\n"
111
+ },
112
+ {
113
+ name: "20260807074034_ttl_secs_nullable",
114
+ hash: "be3a8d0e64b0224021d869c1c7e217e40b6eb868615cc4c86a6717f5b7211721",
115
+ sql: "PRAGMA foreign_keys=OFF;--> statement-breakpoint\nCREATE TABLE `__new_memories` (\n\t`id` text PRIMARY KEY,\n\t`logical_key` text NOT NULL,\n\t`content` text NOT NULL,\n\t`content_type` text DEFAULT 'text/plain' NOT NULL,\n\t`state` text DEFAULT 'active' NOT NULL,\n\t`created_at` integer NOT NULL,\n\t`ttl_secs` integer DEFAULT 604800,\n\t`namespace` text DEFAULT 'default' NOT NULL,\n\t`metadata` text,\n\t`embedding` F32_BLOB(384),\n\t`embedding_model` text\n);\n--> statement-breakpoint\nINSERT INTO `__new_memories`(`id`, `logical_key`, `content`, `content_type`, `state`, `created_at`, `ttl_secs`, `namespace`, `metadata`, `embedding`, `embedding_model`) SELECT `id`, `logical_key`, `content`, `content_type`, `state`, `created_at`, `ttl_secs`, `namespace`, `metadata`, `embedding`, `embedding_model` FROM `memories`;--> statement-breakpoint\nDROP TABLE `memories`;--> statement-breakpoint\nALTER TABLE `__new_memories` RENAME TO `memories`;--> statement-breakpoint\nPRAGMA foreign_keys=ON;--> statement-breakpoint\nCREATE INDEX `idx_memories_ns_created` ON `memories` (`namespace`,`created_at`);--> statement-breakpoint\nCREATE INDEX `idx_memories_state_created` ON `memories` (`state`,`created_at`);--> statement-breakpoint\nCREATE UNIQUE INDEX `idx_memories_active_key` ON `memories` (`namespace`,`logical_key`) WHERE state = 'active';"
112
116
  }
113
117
  ];
114
118
 
@@ -148,7 +152,7 @@ var memories = sqliteTable("memories", {
148
152
  contentType: text("content_type").notNull().default("text/plain"),
149
153
  state: text("state", { enum: ["active", "superseded", "archived", "forgotten"] }).notNull().default("active"),
150
154
  createdAt: integer("created_at").notNull(),
151
- ttlSecs: integer("ttl_secs").notNull().default(604800),
155
+ ttlSecs: integer("ttl_secs").default(604800),
152
156
  namespace: text("namespace").notNull().default("default"),
153
157
  metadata: text("metadata"),
154
158
  embedding: float32Array("embedding", { dimensions: 384 }),
@@ -326,7 +330,7 @@ var artifactEdges = sqliteTable3("artifact_edges", {
326
330
  fromMemoryId: text3("from_memory_id").notNull().references(() => memories.id, { onDelete: "cascade" }),
327
331
  toMemoryId: text3("to_memory_id").notNull().references(() => memories.id, { onDelete: "cascade" }),
328
332
  edgeKind: text3("edge_kind", {
329
- enum: ["memory_supports", "memory_supersedes", "memory_contradicts"]
333
+ enum: ["memory_supports", "memory_supersedes", "memory_contradicts", "code_calls"]
330
334
  }).notNull(),
331
335
  weight: real3("weight").notNull().default(1),
332
336
  status: text3("status", { enum: ["pending", "confirmed", "rejected"] }).notNull().default("pending"),
@@ -378,7 +382,6 @@ import { Effect as Effect6, Schema as Schema6 } from "effect";
378
382
 
379
383
  // ../logger/src/service.ts
380
384
  import { Config, Context as Context3, Effect as Effect5, Layer, Logger, References } from "effect";
381
- import { closeSync, fsyncSync, openSync, writeSync } from "node:fs";
382
385
 
383
386
  // ../logger/src/format.ts
384
387
  function renderJson(entry) {
@@ -498,13 +501,14 @@ var consoleSink = (pretty) => {
498
501
  const render = pretty ? renderPretty : renderJson;
499
502
  return {
500
503
  write: (entry) => {
501
- process.stdout.write(render(entry) + `
504
+ process.stderr.write(render(entry) + `
502
505
  `);
503
506
  },
504
507
  dispose: () => {}
505
508
  };
506
509
  };
507
- var fileSink = (path) => {
510
+ var fileSink = (path) => Effect5.tryPromise(async () => {
511
+ const { openSync, closeSync, fsyncSync, writeSync } = await import("node:fs");
508
512
  const fd = openSync(path, "a");
509
513
  let disposed = false;
510
514
  const flush = () => {
@@ -537,11 +541,15 @@ var fileSink = (path) => {
537
541
  closeSync(fd);
538
542
  }
539
543
  };
540
- };
544
+ });
541
545
 
542
546
  class Service3 extends Context3.Service()("@dimple/logger/Logging") {
543
547
  }
544
- var make = (config) => {
548
+ var makeEffect = (config) => Effect5.gen(function* () {
549
+ const file = config.file === undefined ? undefined : yield* fileSink(config.file);
550
+ const sinks = [consoleSink((config.format ?? "jsonl") === "pretty")];
551
+ if (file !== undefined)
552
+ sinks.push(file);
545
553
  const level = config.level ?? "info";
546
554
  const floor = LEVEL_ORDER[level];
547
555
  const overrides = {};
@@ -551,9 +559,6 @@ var make = (config) => {
551
559
  overrides[scope] = order;
552
560
  }
553
561
  const ring = new RingBuffer(config.ringSize ?? 4096);
554
- const sinks = [consoleSink((config.format ?? "jsonl") === "pretty")];
555
- if (config.file !== undefined)
556
- sinks.push(fileSink(config.file));
557
562
  return Service3.of({
558
563
  ring,
559
564
  emit: (entry) => {
@@ -572,7 +577,7 @@ var make = (config) => {
572
577
  sink.dispose();
573
578
  }
574
579
  });
575
- };
580
+ });
576
581
  var LEVEL_MAP = {
577
582
  FATAL: "error",
578
583
  ERROR: "error",
@@ -637,7 +642,7 @@ var EFFECT_LEVEL = {
637
642
  };
638
643
  var minLevelLayer = (minLevel) => Layer.succeed(References.MinimumLogLevel, minLevel === "None" ? "None" : EFFECT_LEVEL[minLevel]);
639
644
  var layer = (config) => {
640
- const loggingLayer = Layer.effect(Service3, Effect5.acquireRelease(Effect5.sync(() => make(config)), (logging) => Effect5.sync(() => logging.dispose())));
645
+ const loggingLayer = Layer.effect(Service3, Effect5.acquireRelease(makeEffect(config), (logging) => Effect5.sync(() => logging.dispose())));
641
646
  const minLevel = config.enabled === false ? "None" : lowestLevel(config);
642
647
  const withLogger = Layer.provideMerge(loggingLayer)(loggerReplacement());
643
648
  return Layer.provideMerge(minLevelLayer(minLevel))(withLogger);
@@ -659,6 +664,7 @@ var EmbeddingModelConfig = Schema6.Struct({
659
664
  dimensions: Schema6.Number,
660
665
  vectorType: Schema6.optional(Schema6.String),
661
666
  apiKey: Schema6.optional(Schema6.String),
667
+ baseURL: Schema6.optional(Schema6.String),
662
668
  indexConfig: Schema6.optional(IndexConfig),
663
669
  device: Schema6.optional(Schema6.Literals([
664
670
  "auto",
@@ -687,7 +693,8 @@ var EmbeddingModelConfig = Schema6.Struct({
687
693
  ])),
688
694
  pooling: Schema6.optional(Schema6.Literals(["mean", "cls", "none"])),
689
695
  normalize: Schema6.optional(Schema6.Boolean),
690
- cacheDir: Schema6.optional(Schema6.String)
696
+ cacheDir: Schema6.optional(Schema6.String),
697
+ autoInstall: Schema6.optional(Schema6.Boolean)
691
698
  });
692
699
  var EmbeddingConfig = Schema6.Struct({
693
700
  defaultModel: Schema6.String,
@@ -754,8 +761,6 @@ import { Context as Context4, Effect as Effect8, Layer as Layer2 } from "effect"
754
761
  // ../config/src/loader.ts
755
762
  import { Effect as Effect7, Schema as Schema7 } from "effect";
756
763
  import { parse, parseTree, printParseErrorCode } from "jsonc-parser";
757
- import * as fs from "node:fs";
758
- import * as path from "node:path";
759
764
  var CONFIG_FILE_NAMES = ["dimple.jsonc", "dimple.json"];
760
765
  var GLOBAL_DIR = ".config/dimple";
761
766
  var defaults = {
@@ -855,11 +860,14 @@ var parseJsoncEffect = (text4, formatError) => {
855
860
  return Effect7.fail(formatError(result.error));
856
861
  return Effect7.succeed(result.value);
857
862
  };
858
- var readFile = (filePath) => Effect7.try({
859
- try: () => fs.readFileSync(filePath, "utf-8"),
863
+ var readFile = (filePath) => Effect7.tryPromise({
864
+ try: async () => (await import("node:fs")).promises.readFile(filePath, "utf8"),
860
865
  catch: (cause) => new Error(`failed to read config file ${filePath}: ${cause instanceof Error ? cause.message : String(cause)}`)
861
866
  }).pipe(Effect7.flatMap((content) => parseJsoncEffect(content, (cause) => new Error(`failed to read config file ${filePath}: ${cause.message}`))));
862
- var findProjectConfig = () => {
867
+ var loadFs = () => Effect7.tryPromise(() => import("node:fs"));
868
+ var findProjectConfig = () => Effect7.gen(function* () {
869
+ const fs = yield* loadFs();
870
+ const path = yield* Effect7.tryPromise(() => import("node:path"));
863
871
  let dir = process.cwd();
864
872
  for (let i = 0;i < 20; i++) {
865
873
  for (const name of CONFIG_FILE_NAMES) {
@@ -873,37 +881,40 @@ var findProjectConfig = () => {
873
881
  dir = parent;
874
882
  }
875
883
  return;
876
- };
877
- var getGlobalConfigPath = () => {
884
+ });
885
+ var getGlobalConfigPath = () => Effect7.gen(function* () {
878
886
  const home = process.env["HOME"] ?? process.env["USERPROFILE"];
879
887
  if (!home)
880
888
  return;
889
+ const fs = yield* loadFs();
890
+ const path = yield* Effect7.tryPromise(() => import("node:path"));
881
891
  for (const name of CONFIG_FILE_NAMES) {
882
892
  const p = path.join(home, GLOBAL_DIR, name);
883
893
  if (fs.existsSync(p))
884
894
  return p;
885
895
  }
886
896
  return;
887
- };
888
- var loadFromFiles = () => {
897
+ });
898
+ var loadFromFiles = () => Effect7.gen(function* () {
889
899
  const sources = [];
890
- const globalPath = getGlobalConfigPath();
900
+ const globalPath = yield* getGlobalConfigPath();
891
901
  if (globalPath)
892
902
  sources.push({ path: globalPath });
893
- const projectPath = findProjectConfig();
903
+ const projectPath = yield* findProjectConfig();
894
904
  if (projectPath)
895
905
  sources.push({ path: projectPath });
896
906
  const envPath = process.env["DIMPLE_CONFIG"];
897
907
  if (envPath) {
908
+ const fs = yield* loadFs();
898
909
  if (!fs.existsSync(envPath)) {
899
- return Effect7.fail(new Error(`DIMPLE_CONFIG path not found: ${envPath}`));
910
+ return yield* Effect7.fail(new Error(`DIMPLE_CONFIG path not found: ${envPath}`));
900
911
  }
901
912
  sources.push({ path: envPath });
902
913
  }
903
914
  if (sources.length === 0)
904
- return Effect7.succeed([]);
905
- return Effect7.forEach(sources, (s) => readFile(s.path));
906
- };
915
+ return [];
916
+ return yield* Effect7.forEach(sources, (s) => readFile(s.path));
917
+ });
907
918
  var loadFromEnvContent = () => {
908
919
  const content = process.env["DIMPLE_CONFIG_CONTENT"];
909
920
  if (!content)
@@ -934,15 +945,16 @@ var deepMerge = (base, over) => {
934
945
  }
935
946
  return out;
936
947
  };
937
- var loadConfigFromFile = (path2) => Effect7.gen(function* () {
948
+ var loadConfigFromFile = (path) => Effect7.gen(function* () {
949
+ const fs = yield* loadFs();
938
950
  const content = yield* Effect7.tryPromise({
939
- try: () => fs.promises.readFile(path2, "utf8"),
940
- catch: (cause) => new Error(`config read ${path2}: ${cause}`)
951
+ try: () => fs.promises.readFile(path, "utf8"),
952
+ catch: (cause) => new Error(`config read ${path}: ${cause}`)
941
953
  });
942
- const parsed = yield* parseJsoncEffect(content, (cause) => new Error(`config parse ${path2}: ${cause.message}`));
954
+ const parsed = yield* parseJsoncEffect(content, (cause) => new Error(`config parse ${path}: ${cause.message}`));
943
955
  const merged = deepMerge(defaults, parsed);
944
956
  const resolved = resolveEnvVars(merged);
945
- return yield* Schema7.decodeUnknownEffect(DimpleConfig)(resolved).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
957
+ return yield* Schema7.decodeUnknownEffect(DimpleConfig, { errors: "all" })(resolved).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
946
958
  });
947
959
  var loadConfig = (overrides) => Effect7.gen(function* () {
948
960
  const fileParts = yield* loadFromFiles();
@@ -955,7 +967,7 @@ var loadConfig = (overrides) => Effect7.gen(function* () {
955
967
  if (overrides)
956
968
  merged = deepMerge(merged, overrides);
957
969
  const resolved = resolveEnvVars(merged);
958
- return yield* Schema7.decodeUnknownEffect(DimpleConfig)(resolved).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
970
+ return yield* Schema7.decodeUnknownEffect(DimpleConfig, { errors: "all" })(resolved).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
959
971
  });
960
972
 
961
973
  // ../config/src/service.ts
@@ -1048,9 +1060,6 @@ var runEmbeddedMigrations = async (client) => {
1048
1060
  }
1049
1061
  };
1050
1062
  var makeClientDb = (url) => {
1051
- if (url.startsWith("file:")) {
1052
- fs2.mkdirSync(dirname2(url.slice("file:".length)), { recursive: true });
1053
- }
1054
1063
  const client = createClient({ url });
1055
1064
  const db = drizzle({ client });
1056
1065
  const rawTransaction = client.transaction.bind(client);
@@ -1113,25 +1122,29 @@ var makeClientDb = (url) => {
1113
1122
  };
1114
1123
  return { client, db };
1115
1124
  };
1125
+ var ensureFileDir = (url) => url.startsWith("file:") ? import("node:fs").then((fs) => import("node:path").then(({ dirname }) => {
1126
+ fs.mkdirSync(dirname(url.slice("file:".length)), { recursive: true });
1127
+ })) : Promise.resolve();
1116
1128
  var acquireMigrationLock = async (url) => {
1117
1129
  if (!url.startsWith("file:"))
1118
1130
  return;
1131
+ const fs = await import("node:fs");
1119
1132
  const dbPath = url.slice("file:".length);
1120
1133
  const lockPath = `${dbPath}.migrate-lock`;
1121
1134
  const deadline = Date.now() + 30000;
1122
1135
  for (;; ) {
1123
1136
  try {
1124
- const fd = fs2.openSync(lockPath, "wx");
1137
+ const fd = fs.openSync(lockPath, "wx");
1125
1138
  return () => {
1126
1139
  try {
1127
- fs2.closeSync(fd);
1128
- fs2.unlinkSync(lockPath);
1140
+ fs.closeSync(fd);
1141
+ fs.unlinkSync(lockPath);
1129
1142
  } catch {}
1130
1143
  };
1131
1144
  } catch {
1132
1145
  try {
1133
- if (Date.now() - fs2.statSync(lockPath).mtimeMs > 5 * 60000)
1134
- fs2.unlinkSync(lockPath);
1146
+ if (Date.now() - fs.statSync(lockPath).mtimeMs > 300000)
1147
+ fs.unlinkSync(lockPath);
1135
1148
  } catch {}
1136
1149
  if (Date.now() > deadline) {
1137
1150
  return Promise.reject(new Error(`timed out waiting for migration lock ${lockPath}`));
@@ -1141,6 +1154,10 @@ var acquireMigrationLock = async (url) => {
1141
1154
  }
1142
1155
  };
1143
1156
  var topicFilter = (topicKeys) => topicKeys && topicKeys.length > 0 ? ` AND m.id IN (SELECT memory_id FROM topic_memberships WHERE topic_key IN (${topicKeys.map(() => "?").join(", ")}) AND model_key = ?)` : "";
1157
+ var contentTypeFilter = (contentType) => {
1158
+ const kinds = typeof contentType === "string" ? [contentType] : contentType ?? [];
1159
+ return kinds.length === 0 ? "" : ` AND m.content_type IN (${kinds.map(() => "?").join(", ")})`;
1160
+ };
1144
1161
  var makeBackend = (url, client, db, migrationFolder) => ({
1145
1162
  listIsolatedMemories: (limit, modelKey) => Effect9.tryPromise({
1146
1163
  try: () => client.execute({
@@ -1176,6 +1193,7 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1176
1193
  try: async () => {
1177
1194
  try {
1178
1195
  if (migrationFolder !== undefined) {
1196
+ const { migrate } = await import("drizzle-orm/libsql/migrator");
1179
1197
  await migrate(db, { migrationsFolder: migrationFolder });
1180
1198
  } else {
1181
1199
  await runEmbeddedMigrations(client);
@@ -1206,25 +1224,25 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1206
1224
  topics,
1207
1225
  artifactEdges
1208
1226
  },
1209
- ftsSearch: (query, namespace, limit, topicKeys, modelKey) => Effect9.tryPromise({
1227
+ ftsSearch: (query, namespace, limit, topicKeys, modelKey, contentType) => Effect9.tryPromise({
1210
1228
  try: () => client.execute({
1211
1229
  sql: `SELECT m.*, f.rank
1212
1230
  FROM memories_fts f
1213
1231
  JOIN memories m ON m.rowid = f.rowid
1214
- WHERE f.content MATCH ? AND m.namespace = ? AND m.state = 'active'${topicFilter(topicKeys)}
1232
+ WHERE f.content MATCH ? AND m.namespace = ? AND m.state = 'active'${topicFilter(topicKeys)}${contentTypeFilter(contentType)}
1215
1233
  ORDER BY f.rank
1216
1234
  LIMIT ?`,
1217
- args: topicKeys && topicKeys.length > 0 ? [
1235
+ args: [
1218
1236
  `"${query.replaceAll('"', '""')}"`,
1219
1237
  namespace,
1220
- ...topicKeys,
1221
- modelKey ?? "",
1238
+ ...topicKeys && topicKeys.length > 0 ? [...topicKeys, modelKey ?? ""] : [],
1239
+ ...typeof contentType === "string" ? [contentType] : contentType ?? [],
1222
1240
  limit
1223
- ] : [`"${query.replaceAll('"', '""')}"`, namespace, limit]
1241
+ ]
1224
1242
  }).then((rs) => rs.rows),
1225
1243
  catch: (cause) => new StoreError({ message: `DbBackend.ftsSearch: ${cause}` })
1226
1244
  }),
1227
- vectorSearch: (query, namespace, limit, model, topicKeys) => Effect9.gen(function* () {
1245
+ vectorSearch: (query, namespace, limit, model, topicKeys, contentType) => Effect9.gen(function* () {
1228
1246
  for (const n of query) {
1229
1247
  if (!Number.isFinite(n)) {
1230
1248
  yield* new StoreError({
@@ -1235,7 +1253,13 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1235
1253
  const tableName = vectorTableName(model);
1236
1254
  const vecJson = JSON.stringify([...query]);
1237
1255
  const convertFn = VECTOR_CONVERT_FNS[model.vectorType ?? "float32"] ?? "vector32";
1238
- const args = topicKeys && topicKeys.length > 0 ? [vecJson, namespace, ...topicKeys, model.id, limit] : [vecJson, namespace, limit];
1256
+ const args = [
1257
+ vecJson,
1258
+ namespace,
1259
+ ...topicKeys && topicKeys.length > 0 ? [...topicKeys, model.id] : [],
1260
+ ...typeof contentType === "string" ? [contentType] : contentType ?? [],
1261
+ limit
1262
+ ];
1239
1263
  const rows = yield* Effect9.tryPromise({
1240
1264
  try: () => client.execute({
1241
1265
  sql: `
@@ -1243,7 +1267,7 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1243
1267
  vector_distance_cos(v.embedding, ${convertFn}(?)) AS distance
1244
1268
  FROM memories m
1245
1269
  JOIN ${tableName} v ON v.memory_id = m.id
1246
- WHERE m.namespace = ? AND m.state = 'active'${topicFilter(topicKeys)}
1270
+ WHERE m.namespace = ? AND m.state = 'active'${topicFilter(topicKeys)}${contentTypeFilter(contentType)}
1247
1271
  ORDER BY distance
1248
1272
  LIMIT ?
1249
1273
  `,
@@ -1281,6 +1305,23 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1281
1305
  }),
1282
1306
  catch: (cause) => new StoreError({ message: `DbBackend.sweep: ${cause}` })
1283
1307
  }),
1308
+ listSchema: () => Effect9.tryPromise({
1309
+ try: async () => {
1310
+ const tables = (await client.execute({
1311
+ sql: `SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name`
1312
+ })).rows;
1313
+ const out = [];
1314
+ for (const t of tables) {
1315
+ const cols = (await client.execute({ sql: `PRAGMA table_info(${t.name})` })).rows;
1316
+ out.push({
1317
+ table: t.name,
1318
+ columns: cols.map((c) => ({ name: c.name, type: c.type }))
1319
+ });
1320
+ }
1321
+ return out;
1322
+ },
1323
+ catch: (cause) => new StoreError({ message: `DbBackend.listSchema: ${cause}` })
1324
+ }),
1284
1325
  writeAuditRows: (rows) => rows.length === 0 ? Effect9.void : Effect9.tryPromise({
1285
1326
  try: () => db.insert(retrievalLog).values([...rows]),
1286
1327
  catch: (cause) => new StoreError({ message: `DbBackend.writeAuditRows: ${cause}` })
@@ -1329,10 +1370,12 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1329
1370
  const maxTotal = opts?.maxTotal ?? 80;
1330
1371
  const includePending = opts?.includePending ?? true;
1331
1372
  const modelKey = opts?.modelKey;
1332
- const statusClause = includePending ? `AND e.status IN ('active', 'confirmed', 'pending')` : `AND e.status IN ('active', 'confirmed')`;
1373
+ const statusClause = includePending ? `AND e.expired_at IS NULL AND e.status IN ('active', 'confirmed', 'pending')` : `AND e.expired_at IS NULL AND e.status IN ('active', 'confirmed')`;
1333
1374
  const modelClause = modelKey === undefined ? "" : `AND (e.from_kind <> 'topic'
1334
1375
  OR EXISTS (SELECT 1 FROM topic_memberships tm WHERE tm.topic_key = e.from_id AND tm.memory_id = e.to_id AND tm.model_key = ?)
1335
1376
  OR EXISTS (SELECT 1 FROM topic_relations tr WHERE tr.parent_key = e.from_id AND tr.child_key = e.to_id AND tr.model_key = ?))`;
1377
+ const kinds = opts?.edgeKinds;
1378
+ const kindClause = kinds === undefined || kinds.length === 0 ? "" : `AND e.edge_kind IN (${kinds.map(() => "?").join(",")})`;
1336
1379
  const results = [];
1337
1380
  const seenEdges = new Set;
1338
1381
  const visited = new Set([`${kind}:${id}`]);
@@ -1345,6 +1388,8 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1345
1388
  args.push(node.id, node.kind, node.id, node.kind);
1346
1389
  if (modelKey !== undefined)
1347
1390
  args.push(modelKey, modelKey);
1391
+ if (kinds !== undefined && kinds.length > 0)
1392
+ args.push(...kinds);
1348
1393
  const rows = yield* Effect9.tryPromise({
1349
1394
  try: () => client.execute({
1350
1395
  sql: `SELECT e.from_kind, e.from_id, e.to_kind, e.to_id, e.edge_kind, e.weight, e.status, e.source, e.valid_at, e.expired_at, ? AS depth
@@ -1352,6 +1397,7 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1352
1397
  WHERE (${touches})
1353
1398
  ${statusClause}
1354
1399
  ${modelClause}
1400
+ ${kindClause}
1355
1401
  ORDER BY e.from_id, e.edge_kind, e.to_id, e.to_kind`,
1356
1402
  args
1357
1403
  }).then((rs) => rs.rows),
@@ -1523,13 +1569,20 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1523
1569
  })
1524
1570
  });
1525
1571
  var layer3 = (url, options) => {
1526
- const effect = options?.releaseClient === true ? Effect9.acquireRelease(Effect9.sync(() => makeClientDb(url)), ({ client }) => Effect9.tryPromise({
1572
+ const effect = options?.releaseClient === true ? Effect9.acquireRelease(Effect9.tryPromise({
1573
+ try: () => ensureFileDir(url).then(() => makeClientDb(url)),
1574
+ catch: (cause) => new StoreError({ message: `DbBackend.client: ${cause}` })
1575
+ }), ({ client }) => Effect9.tryPromise({
1527
1576
  try: async () => client.close(),
1528
1577
  catch: () => new Error("DbBackend.close failed")
1529
1578
  }).pipe(Effect9.ignore)).pipe(Effect9.flatMap(({ client, db }) => {
1530
1579
  const backend = Service2.of(makeBackend(url, client, db, options?.migrationFolder));
1531
1580
  return backend.runMigrations().pipe(Effect9.as(backend));
1532
1581
  })) : Effect9.gen(function* () {
1582
+ yield* Effect9.tryPromise({
1583
+ try: () => ensureFileDir(url),
1584
+ catch: (cause) => new StoreError({ message: `DbBackend.client: ${cause}` })
1585
+ });
1533
1586
  const { client, db } = yield* Effect9.sync(() => makeClientDb(url));
1534
1587
  const backend = Service2.of(makeBackend(url, client, db, options?.migrationFolder));
1535
1588
  yield* backend.runMigrations();
@@ -1556,6 +1609,7 @@ var MemoryMetadata = Schema8.Struct({
1556
1609
  confidence: Schema8.Number,
1557
1610
  affordances: Schema8.Array(Schema8.String),
1558
1611
  language: Schema8.optional(Schema8.String),
1612
+ codeVersion: Schema8.optional(Schema8.String),
1559
1613
  pinned: Schema8.optional(Schema8.Boolean)
1560
1614
  });
1561
1615
  var parseMetadata = (blob) => {
@@ -2422,7 +2476,6 @@ import {
2422
2476
  } from "effect";
2423
2477
  import { PersistedQueue } from "effect/unstable/persistence";
2424
2478
  import { SqlClient } from "effect/unstable/sql";
2425
- import { randomUUID } from "node:crypto";
2426
2479
 
2427
2480
  class QueueKey extends Data.Class {
2428
2481
  }
@@ -2433,7 +2486,7 @@ var makeStore = Effect15.fnUntraced(function* (options) {
2433
2486
  const pollInterval = Duration.max(options?.pollInterval ? Duration.fromInputUnsafe(options.pollInterval) : Duration.millis(1000), Duration.millis(1));
2434
2487
  const lockRefreshInterval = Duration.max(options?.lockRefreshInterval ? Duration.fromInputUnsafe(options.lockRefreshInterval) : Duration.seconds(30), Duration.millis(1));
2435
2488
  const lockExpiration = Duration.max(options?.lockExpiration ? Duration.fromInputUnsafe(options.lockExpiration) : Duration.minutes(2), Duration.millis(1));
2436
- const workerId = randomUUID();
2489
+ const workerId = globalThis.crypto.randomUUID();
2437
2490
  const sqlNow = sql3.literal("CURRENT_TIMESTAMP");
2438
2491
  const expiresAt = sql3.literal(`datetime(CURRENT_TIMESTAMP, '-${Math.ceil(Duration.toSeconds(lockExpiration))} seconds')`);
2439
2492
  const workerIdSql = sql3.literal(`'${workerId}'`);
@@ -2573,7 +2626,7 @@ var storeSqliteLayer = Layer4.effect(PersistedQueue.PersistedQueueStore, makeSto
2573
2626
  // ../../node_modules/.bun/@effect+sql-libsql@4.0.0-beta.103+10c4c6971de0179f/node_modules/@effect/sql-libsql/dist/LibsqlClient.js
2574
2627
  var exports_LibsqlClient = {};
2575
2628
  __export(exports_LibsqlClient, {
2576
- make: () => make5,
2629
+ make: () => make4,
2577
2630
  layerConfig: () => layerConfig2,
2578
2631
  layer: () => layer5,
2579
2632
  TypeId: () => TypeId,
@@ -2602,7 +2655,7 @@ var classifyError = (cause, message, operation) => classifySqliteError(cause, {
2602
2655
  var TypeId = "~@effect/sql-libsql/LibsqlClient";
2603
2656
  var LibsqlClient = /* @__PURE__ */ Context6.Service("@effect/sql-libsql/LibsqlClient");
2604
2657
  var LibsqlTransaction = /* @__PURE__ */ Context6.Service("@effect/sql-libsql/LibsqlClient/LibsqlTransaction");
2605
- var make5 = (options) => Effect16.gen(function* () {
2658
+ var make4 = (options) => Effect16.gen(function* () {
2606
2659
  const compiler = Statement.makeCompilerSqlite(options.transformQueryNames);
2607
2660
  const transformRows = options.transformResultNames ? Statement.defaultTransforms(options.transformResultNames).array : undefined;
2608
2661
  const spanAttributes = [...options.spanAttributes ? Object.entries(options.spanAttributes) : [], [ATTR_DB_SYSTEM_NAME, "sqlite"]];
@@ -2717,11 +2770,9 @@ var make5 = (options) => Effect16.gen(function* () {
2717
2770
  sdk: connection.sdk
2718
2771
  });
2719
2772
  });
2720
- var layerConfig2 = (config) => Layer5.effectContext(Config3.unwrap(config).pipe(Effect16.flatMap(make5), Effect16.map((client) => Context6.make(LibsqlClient, client).pipe(Context6.add(Client.SqlClient, client))))).pipe(Layer5.provide(Reactivity.layer));
2721
- var layer5 = (config) => Layer5.effectContext(Effect16.map(make5(config), (client) => Context6.make(LibsqlClient, client).pipe(Context6.add(Client.SqlClient, client)))).pipe(Layer5.provide(Reactivity.layer));
2773
+ var layerConfig2 = (config) => Layer5.effectContext(Config3.unwrap(config).pipe(Effect16.flatMap(make4), Effect16.map((client) => Context6.make(LibsqlClient, client).pipe(Context6.add(Client.SqlClient, client))))).pipe(Layer5.provide(Reactivity.layer));
2774
+ var layer5 = (config) => Layer5.effectContext(Effect16.map(make4(config), (client) => Context6.make(LibsqlClient, client).pipe(Context6.add(Client.SqlClient, client)))).pipe(Layer5.provide(Reactivity.layer));
2722
2775
  // ../workflow/src/engine.ts
2723
- import { mkdirSync as mkdirSync2 } from "node:fs";
2724
- import { dirname as dirname3 } from "node:path";
2725
2776
  var Persistence = PersistenceNamespace2.Persistence;
2726
2777
  var PersistedQueueFactory = PersistedQueue2.PersistedQueueFactory;
2727
2778
  var layerDurable = Layer6.effect(WorkflowEngine2.WorkflowEngine)(Effect17.gen(function* () {
@@ -2922,7 +2973,9 @@ var workflowTagFor = (workflows, store, executionId) => Effect17.gen(function* (
2922
2973
  });
2923
2974
  var ensureDbDir = (url) => {
2924
2975
  if (url.startsWith("file:")) {
2925
- mkdirSync2(dirname3(url.slice("file:".length)), { recursive: true });
2976
+ import("node:fs").then((fs) => import("node:path").then(({ dirname }) => {
2977
+ fs.mkdirSync(dirname(url.slice("file:".length)), { recursive: true });
2978
+ }));
2926
2979
  }
2927
2980
  };
2928
2981
  var libsqlStack = (options) => {
@@ -4313,6 +4366,11 @@ var sameLogicalKeyFamily = (a, b) => {
4313
4366
  const fb = familyOf(b);
4314
4367
  return fa !== "" && fa === fb;
4315
4368
  };
4369
+ var sameFileChunkFamily = (a, b) => {
4370
+ if (a === undefined || b === undefined || a === "" || b === "")
4371
+ return false;
4372
+ return a.startsWith(`${b}#`) || b.startsWith(`${a}#`);
4373
+ };
4316
4374
  var topicsOf = (blob) => {
4317
4375
  if (blob === undefined || blob === null)
4318
4376
  return new Set;
@@ -4357,7 +4415,7 @@ var leafLocalCandidates = (newId, newVector, newCreatedAt, newLogicalKey, newMet
4357
4415
  });
4358
4416
  continue;
4359
4417
  }
4360
- if (cosine > bands.nearDuplicate) {
4418
+ if (cosine > bands.nearDuplicate && !sameFileChunkFamily(newLogicalKey, member.logicalKey)) {
4361
4419
  const dir = direction(member, newId, newCreatedAt);
4362
4420
  out.push({
4363
4421
  ...dir,
@@ -4369,7 +4427,7 @@ var leafLocalCandidates = (newId, newVector, newCreatedAt, newLogicalKey, newMet
4369
4427
  });
4370
4428
  continue;
4371
4429
  }
4372
- if (cosine > bands.conflictLow && (sameLogicalKeyFamily(newLogicalKey, member.logicalKey) || sharedMetadataTopics(newMetadata, member.metadata))) {
4430
+ if (cosine > bands.conflictLow && !sameFileChunkFamily(newLogicalKey, member.logicalKey) && (sameLogicalKeyFamily(newLogicalKey, member.logicalKey) || sharedMetadataTopics(newMetadata, member.metadata))) {
4373
4431
  const dir = direction(member, newId, newCreatedAt);
4374
4432
  out.push({
4375
4433
  ...dir,
@@ -4504,7 +4562,12 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4504
4562
  supersedeDemotion: GRAPH_SUPERSEDE_DEMOTION
4505
4563
  });
4506
4564
  });
4507
- const EDGE_KINDS = ["memory_supports", "memory_supersedes", "memory_contradicts"];
4565
+ const EDGE_KINDS = [
4566
+ "memory_supports",
4567
+ "memory_supersedes",
4568
+ "memory_contradicts",
4569
+ "code_calls"
4570
+ ];
4508
4571
  const EDGE_SOURCES = ["rule", "llm", "agent"];
4509
4572
  const write = Effect26.fn("Store.write")(function* (input) {
4510
4573
  if (input.content.includes("\x00")) {
@@ -4523,7 +4586,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4523
4586
  content: input.content,
4524
4587
  contentType: input.contentType ?? "text/plain",
4525
4588
  createdAt: now,
4526
- ttlSecs: input.ttlSecs ?? defaultTtlSecs,
4589
+ ttlSecs: input.ttlSecs === null ? null : input.ttlSecs ?? defaultTtlSecs,
4527
4590
  namespace: input.namespace ?? "default",
4528
4591
  metadata: metadataBlob
4529
4592
  });
@@ -4541,7 +4604,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4541
4604
  contentType: input.contentType ?? "text/plain",
4542
4605
  state: "active",
4543
4606
  createdAt: now,
4544
- ttlSecs: input.ttlSecs ?? defaultTtlSecs,
4607
+ ttlSecs: input.ttlSecs === null ? null : input.ttlSecs ?? defaultTtlSecs,
4545
4608
  namespace: input.namespace ?? "default",
4546
4609
  metadata: metadataBlob ?? undefined,
4547
4610
  embedding: undefined,
@@ -4585,7 +4648,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4585
4648
  });
4586
4649
  const searchFts5 = Effect26.fn("Store.searchFts5")(function* (query, namespace, limit, opts) {
4587
4650
  const start = Date.now();
4588
- const rows = yield* backend.ftsSearch(query, namespace, limit, opts?.topicKeys, modelConfig?.id);
4651
+ const rows = yield* backend.ftsSearch(query, namespace, limit, opts?.topicKeys, modelConfig?.id, opts?.contentType);
4589
4652
  if (opts?.audit !== false) {
4590
4653
  yield* logRetrieval(query, namespace, "fts", rows.length, start);
4591
4654
  }
@@ -4628,7 +4691,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4628
4691
  });
4629
4692
  allowedMemoryIds = new Set(conceptRows.map((r) => r.memoryId));
4630
4693
  }
4631
- const rows = yield* backend.vectorSearch(query, namespace, limit, modelConfig, opts?.topicKeys);
4694
+ const rows = yield* backend.vectorSearch(query, namespace, limit, modelConfig, opts?.topicKeys, opts?.contentType);
4632
4695
  const allowed = allowedMemoryIds;
4633
4696
  const scopedRows = allowed !== undefined ? rows.filter((row) => allowed.has(row.id)) : rows;
4634
4697
  if (opts?.audit !== false) {
@@ -4963,7 +5026,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4963
5026
  });
4964
5027
  return rows.map((row) => row.id);
4965
5028
  });
4966
- const flipToSupersededById = async (tx, memoryId, now) => {
5029
+ const flipToSupersededById = async (tx, memoryId, now, reason = "superseded") => {
4967
5030
  const rs = await tx.update(memories2).set({ state: "superseded" }).where(and8(eq8(memories2.id, memoryId), eq8(memories2.state, "active")));
4968
5031
  if (rs.rowsAffected === 0)
4969
5032
  return null;
@@ -4972,11 +5035,106 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4972
5035
  eventType: "superseded",
4973
5036
  fromState: "active",
4974
5037
  toState: "superseded",
4975
- reason: "superseded",
5038
+ reason,
4976
5039
  createdAt: now
4977
5040
  });
4978
5041
  return memoryId;
4979
5042
  };
5043
+ const reconcile = Effect26.fn("Store.reconcile")(function* (namespace, keyPrefix, keyRoot, walkedRoots, keptKeys) {
5044
+ const inDomain = (key) => {
5045
+ const rel = key.slice(keyPrefix.length).split("#")[0];
5046
+ const filePath = keyRoot === "/" ? `/${rel}` : `${keyRoot}/${rel}`;
5047
+ return walkedRoots.some((root) => filePath === root || filePath.startsWith(`${root}/`));
5048
+ };
5049
+ const owned = (key) => keptKeys.includes(key);
5050
+ const rows = yield* Effect26.tryPromise({
5051
+ try: () => db.select({
5052
+ id: memories2.id,
5053
+ logicalKey: memories2.logicalKey,
5054
+ embedding: memories2.embedding
5055
+ }).from(memories2).where(and8(eq8(memories2.namespace, namespace), eq8(memories2.state, "active"), like(memories2.logicalKey, `${keyPrefix}%`))),
5056
+ catch: (cause) => new StoreError({ message: `Store.reconcile: ${cause}` })
5057
+ });
5058
+ const vanished = rows.filter((r) => inDomain(r.logicalKey) && !owned(r.logicalKey));
5059
+ if (vanished.length > 0) {
5060
+ const now = Date.now();
5061
+ yield* Effect26.tryPromise({
5062
+ try: () => db.transaction(async (tx) => {
5063
+ for (const row of vanished) {
5064
+ await flipToSupersededById(tx, row.id, now, "source_removed");
5065
+ }
5066
+ }),
5067
+ catch: (cause) => new StoreError({ message: `Store.reconcile: ${cause}` })
5068
+ });
5069
+ }
5070
+ return vanished;
5071
+ });
5072
+ const writeSuperseding = Effect26.fn("Store.writeSuperseding")(function* (input) {
5073
+ if (input.content.includes("\x00")) {
5074
+ return yield* new StoreError({
5075
+ message: "Store.writeSuperseding: content contains a NUL byte (libSQL would silently truncate it)"
5076
+ });
5077
+ }
5078
+ const id = crypto.randomUUID();
5079
+ const now = Date.now();
5080
+ const metadataBlob = input.typedMetadata !== undefined ? mergeMetadataBlob(input.metadata, input.typedMetadata) : input.metadata ?? null;
5081
+ const namespace = input.namespace ?? "default";
5082
+ let supersededId;
5083
+ yield* Effect26.tryPromise({
5084
+ try: () => db.transaction(async (tx) => {
5085
+ const existing = await tx.select({ id: memories2.id }).from(memories2).where(and8(eq8(memories2.logicalKey, input.logicalKey), eq8(memories2.namespace, namespace), eq8(memories2.state, "active"))).limit(1);
5086
+ const old = existing[0];
5087
+ if (old !== undefined) {
5088
+ await flipToSupersededById(tx, old.id, now);
5089
+ supersededId = old.id;
5090
+ }
5091
+ await tx.insert(memories2).values({
5092
+ id,
5093
+ logicalKey: input.logicalKey,
5094
+ content: input.content,
5095
+ contentType: input.contentType ?? "text/plain",
5096
+ createdAt: now,
5097
+ ttlSecs: input.ttlSecs === null ? null : input.ttlSecs ?? defaultTtlSecs,
5098
+ namespace,
5099
+ metadata: metadataBlob
5100
+ });
5101
+ await tx.insert(memoryEvents2).values({ memoryId: id, eventType: "created", createdAt: now });
5102
+ if (old !== undefined) {
5103
+ await tx.insert(artifactEdges).values({
5104
+ id: crypto.randomUUID(),
5105
+ fromMemoryId: old.id,
5106
+ toMemoryId: id,
5107
+ edgeKind: "memory_supersedes",
5108
+ weight: 1,
5109
+ status: "confirmed",
5110
+ source: "rule",
5111
+ confidence: 1,
5112
+ validAt: now,
5113
+ expiredAt: null,
5114
+ createdAt: now,
5115
+ updatedAt: now
5116
+ });
5117
+ }
5118
+ }),
5119
+ catch: (cause) => backend.isUniqueViolation(cause) ? new DuplicateActiveKey({ logicalKey: input.logicalKey, namespace }) : new StoreError({ message: `Store.writeSuperseding: ${cause}` })
5120
+ });
5121
+ return {
5122
+ memory: {
5123
+ id,
5124
+ logicalKey: input.logicalKey,
5125
+ content: input.content,
5126
+ contentType: input.contentType ?? "text/plain",
5127
+ state: "active",
5128
+ createdAt: now,
5129
+ ttlSecs: input.ttlSecs === null ? null : input.ttlSecs ?? defaultTtlSecs,
5130
+ namespace,
5131
+ metadata: metadataBlob ?? undefined,
5132
+ embedding: undefined,
5133
+ embeddingModel: undefined
5134
+ },
5135
+ ...supersededId !== undefined ? { supersededId } : {}
5136
+ };
5137
+ });
4980
5138
  const addEdge = Effect26.fn("Store.addEdge")(function* (input) {
4981
5139
  if (input.fromMemoryId === input.toMemoryId) {
4982
5140
  return yield* new StoreError({ message: "Store.addEdge: self-loops are not allowed" });
@@ -5029,7 +5187,12 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
5029
5187
  updatedAt: now
5030
5188
  };
5031
5189
  yield* Effect26.tryPromise({
5032
- try: () => db.insert(artifactEdges).values(row),
5190
+ try: () => db.transaction(async (tx) => {
5191
+ await tx.insert(artifactEdges).values(row);
5192
+ if (input.confirmed && input.edgeKind === "memory_supersedes") {
5193
+ await flipToSupersededById(tx, input.fromMemoryId, now);
5194
+ }
5195
+ }),
5033
5196
  catch: (cause) => {
5034
5197
  if (backend.isUniqueViolation(cause)) {
5035
5198
  return new EdgeExists({
@@ -5168,6 +5331,42 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
5168
5331
  }),
5169
5332
  catch: (cause) => new StoreError({ message: `Store.writeCandidateEdge: ${cause}` })
5170
5333
  });
5334
+ const reconcileRenameCandidates = Effect26.fn("Store.reconcileRenameCandidates")(function* (vanished, newUnitIds) {
5335
+ const withVectors = vanished.filter((v) => v.embedding !== null);
5336
+ if (withVectors.length === 0 || newUnitIds.length === 0)
5337
+ return 0;
5338
+ const newRows = yield* Effect26.tryPromise({
5339
+ try: () => db.select({ id: memories2.id, embedding: memories2.embedding }).from(memories2).where(and8(inArray4(memories2.id, newUnitIds))),
5340
+ catch: (cause) => new StoreError({ message: `Store.reconcileRenameCandidates: ${cause}` })
5341
+ });
5342
+ const newWithVectors = newRows.filter((r) => r.embedding !== null);
5343
+ if (newWithVectors.length === 0)
5344
+ return 0;
5345
+ const now = Date.now();
5346
+ let inserted = 0;
5347
+ for (const v of withVectors) {
5348
+ const vVec = Float32Array.from(v.embedding);
5349
+ for (const n of newWithVectors) {
5350
+ if (v.id === n.id)
5351
+ continue;
5352
+ const cosine = cosineSimilarity(vVec, Float32Array.from(n.embedding));
5353
+ if (cosine >= GRAPH_CONFLICT_BANDS.nearDuplicate) {
5354
+ const ok = yield* writeCandidateEdge({
5355
+ fromMemoryId: v.id,
5356
+ toMemoryId: n.id,
5357
+ edgeKind: "memory_supersedes",
5358
+ source: "rule",
5359
+ status: "pending",
5360
+ confidence: cosine,
5361
+ reason: "rename"
5362
+ }, now);
5363
+ if (ok)
5364
+ inserted += 1;
5365
+ }
5366
+ }
5367
+ }
5368
+ return inserted;
5369
+ });
5171
5370
  const scanLeafCandidates = (memoryId, vector, topicKey, model) => Effect26.gen(function* () {
5172
5371
  const memberRows = yield* Effect26.tryPromise({
5173
5372
  try: () => db.select({ memoryId: topicMemberships.memoryId }).from(topicMemberships).where(and8(eq8(topicMemberships.topicKey, topicKey), eq8(topicMemberships.modelKey, model.id))).orderBy(asc5(topicMemberships.memoryId)),
@@ -5368,12 +5567,16 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
5368
5567
  });
5369
5568
  return {
5370
5569
  write,
5570
+ writeSuperseding,
5571
+ reconcile,
5572
+ reconcileRenameCandidates,
5371
5573
  get,
5372
5574
  getByKey,
5373
5575
  recency,
5374
5576
  searchFts5,
5375
5577
  searchVector,
5376
5578
  logAudit,
5579
+ listSchema: () => backend.listSchema(),
5377
5580
  supersede,
5378
5581
  forget,
5379
5582
  sweep,
@@ -5507,6 +5710,7 @@ __export(exports_graph, {
5507
5710
  farthestPointSeeds: () => farthestPointSeeds,
5508
5711
  dreamStateDefaults: () => dreamStateDefaults,
5509
5712
  dreamState: () => dreamState,
5713
+ cosineSimilarity: () => cosineSimilarity,
5510
5714
  conceptConfidence: () => conceptConfidence,
5511
5715
  conceptClusterHandler: () => conceptClusterHandler,
5512
5716
  clusterMembers: () => clusterMembers,
@@ -6260,7 +6464,7 @@ var MemoryUnit = Schema20.Struct({
6260
6464
  contentType: Schema20.String,
6261
6465
  state: MemoryState,
6262
6466
  createdAt: Schema20.Number,
6263
- ttlSecs: Schema20.Number,
6467
+ ttlSecs: Schema20.NullOr(Schema20.Number),
6264
6468
  namespace: Schema20.String,
6265
6469
  metadata: Schema20.optional(Schema20.String),
6266
6470
  embedding: Schema20.optional(Schema20.Array(Schema20.Number)),
@@ -6271,7 +6475,7 @@ var WriteMemoryInput = Schema20.Struct({
6271
6475
  content: Schema20.String,
6272
6476
  contentType: Schema20.optional(Schema20.String),
6273
6477
  namespace: Schema20.optional(Schema20.String),
6274
- ttlSecs: Schema20.optional(Schema20.Number),
6478
+ ttlSecs: Schema20.optional(Schema20.NullOr(Schema20.Number)),
6275
6479
  metadata: Schema20.optional(Schema20.String),
6276
6480
  typedMetadata: Schema20.optional(MemoryMetadata)
6277
6481
  });
@@ -6324,7 +6528,103 @@ class Service9 extends Context9.Service()("@dimple/embedder/EmbeddingModelRegist
6324
6528
  }
6325
6529
  // ../embedder/src/transformers.ts
6326
6530
  import { Config as Config5, Effect as Effect32, Layer as Layer11 } from "effect";
6327
- import { pipeline, env } from "@huggingface/transformers";
6531
+ import { existsSync as existsSync2, readFileSync } from "node:fs";
6532
+ import { join as join2 } from "node:path";
6533
+ import { pathToFileURL } from "node:url";
6534
+
6535
+ // ../embedder/src/auto-install.ts
6536
+ import { execFile } from "node:child_process";
6537
+ import { existsSync } from "node:fs";
6538
+ import { join } from "node:path";
6539
+ var detectPackageManager = (cwd) => {
6540
+ let dir = cwd;
6541
+ while (true) {
6542
+ if (existsSync(join(dir, "bun.lock")) || existsSync(join(dir, "bun.lockb"))) {
6543
+ return { pm: "bun", root: dir };
6544
+ }
6545
+ if (existsSync(join(dir, "pnpm-lock.yaml")))
6546
+ return { pm: "pnpm", root: dir };
6547
+ if (existsSync(join(dir, "yarn.lock")))
6548
+ return { pm: "yarn", root: dir };
6549
+ if (existsSync(join(dir, "package-lock.json")))
6550
+ return { pm: "npm", root: dir };
6551
+ const parent = join(dir, "..");
6552
+ if (parent === dir)
6553
+ return { pm: "bun", root: cwd };
6554
+ dir = parent;
6555
+ }
6556
+ };
6557
+ var installCommand = (pm) => {
6558
+ switch (pm) {
6559
+ case "bun":
6560
+ return ["bun", "add", "@huggingface/transformers"];
6561
+ case "npm":
6562
+ return [
6563
+ "npm",
6564
+ "install",
6565
+ "@huggingface/transformers",
6566
+ "--ignore-scripts",
6567
+ "--no-audit",
6568
+ "--no-fund"
6569
+ ];
6570
+ case "pnpm":
6571
+ return ["pnpm", "add", "@huggingface/transformers", "--ignore-scripts"];
6572
+ case "yarn":
6573
+ return ["yarn", "add", "@huggingface/transformers", "--ignore-scripts"];
6574
+ }
6575
+ };
6576
+ var installTransformers = (cwd) => new Promise((resolve, reject) => {
6577
+ const target = detectPackageManager(cwd);
6578
+ const [cmd, ...args] = installCommand(target.pm);
6579
+ execFile(cmd, args, { cwd: target.root, encoding: "utf8" }, (error) => {
6580
+ if (error) {
6581
+ reject(new Error(`auto-install of @huggingface/transformers failed (${target.pm} add …): ${error.message} — ` + "run it manually or set `autoInstall: false` and configure a hosted embedding provider"));
6582
+ return;
6583
+ }
6584
+ resolve();
6585
+ });
6586
+ });
6587
+
6588
+ // ../embedder/src/transformers.ts
6589
+ var transformersEntryPath = (root) => {
6590
+ let dir = root;
6591
+ while (true) {
6592
+ const pkgJson = join2(dir, "node_modules", "@huggingface", "transformers", "package.json");
6593
+ if (existsSync2(pkgJson)) {
6594
+ let entry;
6595
+ try {
6596
+ const pkg = JSON.parse(readFileSync(pkgJson, "utf8"));
6597
+ const ex = pkg.exports;
6598
+ const dot4 = typeof ex === "object" ? ex["."] : ex;
6599
+ entry = typeof dot4 === "string" ? dot4 : dot4?.import ?? dot4?.default ?? pkg.module ?? pkg.main ?? "index.js";
6600
+ } catch {
6601
+ entry = "index.js";
6602
+ }
6603
+ return join2(dir, "node_modules", "@huggingface", "transformers", entry);
6604
+ }
6605
+ const parent = join2(dir, "..");
6606
+ if (parent === dir)
6607
+ return null;
6608
+ dir = parent;
6609
+ }
6610
+ };
6611
+ var importFromDisk = () => new Promise((resolve, reject) => {
6612
+ try {
6613
+ const root = detectPackageManager(process.cwd()).root;
6614
+ const entry = transformersEntryPath(root);
6615
+ if (entry === null) {
6616
+ reject(new Error("@huggingface/transformers not found on disk after install"));
6617
+ return;
6618
+ }
6619
+ import(pathToFileURL(entry).href).then((mod) => {
6620
+ resolve(mod);
6621
+ }).catch((cause) => {
6622
+ reject(cause);
6623
+ });
6624
+ } catch (cause) {
6625
+ reject(cause);
6626
+ }
6627
+ });
6328
6628
  var defaults2 = {
6329
6629
  modelId: "Xenova/all-MiniLM-L6-v2",
6330
6630
  device: "cpu",
@@ -6332,14 +6632,31 @@ var defaults2 = {
6332
6632
  pooling: "mean",
6333
6633
  normalize: true
6334
6634
  };
6335
- var make6 = (options) => {
6635
+ var make5 = (options) => {
6336
6636
  let pipe = null;
6337
6637
  const getPipeline = Effect32.fnUntraced(function* () {
6338
6638
  if (pipe)
6339
6639
  return pipe;
6640
+ const loadModule = () => Effect32.tryPromise(() => import("@huggingface/transformers"));
6641
+ let transformers = yield* loadModule().pipe(Effect32.catch(() => {
6642
+ if (options.autoInstall === false) {
6643
+ return Effect32.fail(new EmbeddingError({
6644
+ message: "@huggingface/transformers is not installed — run `bun add @huggingface/transformers` " + "to use local embeddings (or configure a hosted embedding provider)"
6645
+ }));
6646
+ }
6647
+ return Effect32.gen(function* () {
6648
+ yield* Effect32.logInfo("auto-installing @huggingface/transformers (first local-embed use — scripts skipped)").pipe(Effect32.annotateLogs({ scope: "@dimple/embedder", event: "embedder.auto-install" }));
6649
+ yield* Effect32.tryPromise(() => installTransformers(process.cwd())).pipe(Effect32.timeout("5 minutes"), Effect32.catch((cause) => Effect32.fail(new EmbeddingError({
6650
+ message: cause._tag === "TimeoutError" ? "auto-install of @huggingface/transformers timed out (5 minutes) — run " + "`bun add @huggingface/transformers` manually" : cause.cause instanceof Error ? cause.cause.message : cause instanceof Error ? cause.message : `auto-install failed: ${String(cause)}`
6651
+ }))));
6652
+ return yield* Effect32.tryPromise(importFromDisk).pipe(Effect32.mapError(() => new EmbeddingError({
6653
+ message: "@huggingface/transformers still unavailable after auto-install — run `bun add " + "@huggingface/transformers` manually (or set `autoInstall: false` and configure a hosted embedding provider)"
6654
+ })));
6655
+ });
6656
+ }));
6340
6657
  if (options.cacheDir)
6341
- env.cacheDir = options.cacheDir;
6342
- pipe = yield* Effect32.promise(() => pipeline("feature-extraction", options.modelId ?? defaults2.modelId, {
6658
+ transformers.env.cacheDir = options.cacheDir;
6659
+ pipe = yield* Effect32.tryPromise(() => transformers.pipeline("feature-extraction", options.modelId ?? defaults2.modelId, {
6343
6660
  device: options.device ?? defaults2.device,
6344
6661
  dtype: options.dtype ?? defaults2.dtype
6345
6662
  })).pipe(Effect32.mapError((cause) => new EmbeddingError({ message: `load model: ${cause}` })));
@@ -6385,7 +6702,7 @@ var make6 = (options) => {
6385
6702
  return { embed, embedSingle };
6386
6703
  };
6387
6704
  var makeTransformersModel = (options = {}) => {
6388
- const impl = make6(options);
6705
+ const impl = make5(options);
6389
6706
  return {
6390
6707
  modelId: options.modelId ?? "local-minilm",
6391
6708
  provider: "transformers",
@@ -6394,46 +6711,72 @@ var makeTransformersModel = (options = {}) => {
6394
6711
  embedSingle: impl.embedSingle
6395
6712
  };
6396
6713
  };
6397
- // ../embedder/src/openai.ts
6398
- import { Effect as Effect33 } from "effect";
6399
- var makeOpenAIModel = (options) => {
6400
- const embed = Effect33.fn("OpenAIEmbedding.embed")(function* (texts) {
6401
- const response = yield* Effect33.tryPromise({
6402
- try: () => fetch("https://api.openai.com/v1/embeddings", {
6403
- method: "POST",
6404
- headers: {
6405
- "Content-Type": "application/json",
6406
- Authorization: `Bearer ${options.apiKey}`
6407
- },
6408
- body: JSON.stringify({
6409
- input: texts,
6410
- model: options.model,
6411
- dimensions: options.dimensions
6412
- })
6413
- }).then((r) => {
6414
- if (!r.ok) {
6415
- return Promise.reject(new Error(`OpenAI API error: ${r.status} ${r.statusText}`));
6714
+ // ../embedder/src/registry.ts
6715
+ import { Effect as Effect33, Layer as Layer12 } from "effect";
6716
+ // ../core/src/hosted-embedding.ts
6717
+ import { embedMany } from "ai";
6718
+ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
6719
+ import { createGoogleGenerativeAI } from "@ai-sdk/google";
6720
+ import { createCohere } from "@ai-sdk/cohere";
6721
+ import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock";
6722
+ import { Effect as Effect34 } from "effect";
6723
+ var makeHostedEmbeddingModel = (options) => {
6724
+ const provider = options.provider ?? "openai";
6725
+ const model = provider === "google" ? createGoogleGenerativeAI({
6726
+ apiKey: options.apiKey,
6727
+ ...options.baseURL !== undefined ? { baseURL: options.baseURL } : {}
6728
+ }).embedding(options.model) : provider === "cohere" ? createCohere({ apiKey: options.apiKey }).embedding(options.model) : provider === "bedrock" ? createAmazonBedrock({
6729
+ ...options.apiKey !== undefined ? { apiKey: options.apiKey } : {}
6730
+ }).embedding(options.model) : createOpenAICompatible({
6731
+ name: "openaiCompatible",
6732
+ baseURL: options.baseURL ?? "https://api.openai.com/v1",
6733
+ apiKey: options.apiKey
6734
+ }).embeddingModel(options.model);
6735
+ const providerOptions = (() => {
6736
+ switch (provider) {
6737
+ case "google":
6738
+ return { google: { outputDimensionality: options.dimensions } };
6739
+ case "bedrock":
6740
+ if (options.model.startsWith("amazon.nova")) {
6741
+ return { bedrock: { embeddingDimension: options.dimensions } };
6416
6742
  }
6417
- return r.json();
6418
- }),
6419
- catch: (cause) => new EmbeddingError({ message: `OpenAI embed: ${cause}` })
6743
+ if (options.model.startsWith("cohere.")) {
6744
+ return { bedrock: { inputType: "search_document" } };
6745
+ }
6746
+ return { bedrock: { dimensions: options.dimensions } };
6747
+ case "cohere":
6748
+ return [256, 512, 1024, 1536].includes(options.dimensions) ? { cohere: { inputType: "search_document", outputDimension: options.dimensions } } : { cohere: { inputType: "search_document" } };
6749
+ default:
6750
+ return { openaiCompatible: { dimensions: options.dimensions } };
6751
+ }
6752
+ })();
6753
+ const embed = Effect34.fn("HostedEmbedding.embed")(function* (texts) {
6754
+ const result = yield* Effect34.tryPromise({
6755
+ try: () => embedMany({ model, values: [...texts], providerOptions }),
6756
+ catch: (cause) => new EmbeddingError({ message: `hosted embedMany: ${cause}` })
6420
6757
  });
6421
- return response.data.map((d) => new Float32Array(d.embedding));
6758
+ for (const vector of result.embeddings) {
6759
+ if (vector.length !== options.dimensions) {
6760
+ return yield* new EmbeddingError({
6761
+ message: `hosted embedder dimension mismatch: endpoint returned ${vector.length} dims but config declares ${options.dimensions}`
6762
+ });
6763
+ }
6764
+ }
6765
+ return result.embeddings.map((e) => new Float32Array(e));
6422
6766
  });
6423
- const embedSingle = Effect33.fn("OpenAIEmbedding.embedSingle")(function* (text4) {
6767
+ const embedSingle = Effect34.fn("HostedEmbedding.embedSingle")(function* (text4) {
6424
6768
  const results = yield* embed([text4]);
6425
6769
  return results[0];
6426
6770
  });
6427
6771
  return {
6428
6772
  modelId: options.modelId,
6429
- provider: "openai",
6773
+ provider,
6430
6774
  dimensions: options.dimensions,
6431
6775
  embed,
6432
6776
  embedSingle
6433
6777
  };
6434
6778
  };
6435
- // ../embedder/src/registry.ts
6436
- import { Effect as Effect34, Layer as Layer12 } from "effect";
6779
+
6437
6780
  // ../retriever/src/hybrid.ts
6438
6781
  import { Context as Context10, Effect as Effect35, Layer as Layer13, Schema as Schema22 } from "effect";
6439
6782
 
@@ -6490,7 +6833,7 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6490
6833
  const namespace = options2?.namespace ?? "default";
6491
6834
  const limit = options2?.limit ?? defaultLimit;
6492
6835
  const k = options2?.k ?? defaultK;
6493
- const profile = typeof options2?.profile === "string" ? {} : options2?.profile ?? {};
6836
+ const profile = typeof options2?.profile === "string" ? options2.profile === "keyword" ? { modeWeights: { vector: 0, fts: 1 } } : options2.profile === "semantic" ? { modeWeights: { vector: 1, fts: 0 } } : {} : options2?.profile ?? {};
6494
6837
  const topicKeys = options2?.topics?.topicKeys;
6495
6838
  const fetchMultiplier = 2;
6496
6839
  const start = Date.now();
@@ -6502,21 +6845,24 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6502
6845
  vectorOpts.conceptIds = profile.conceptFilter;
6503
6846
  const vectorResults = yield* store.searchVector([...embedding], namespace, limit * fetchMultiplier, {
6504
6847
  ...vectorOpts,
6848
+ ...options2?.contentType !== undefined ? { contentType: options2.contentType } : {},
6505
6849
  audit: false
6506
6850
  }).pipe(wrapError("searchVector"));
6507
6851
  const fts5Results = yield* store.searchFts5(query2, namespace, limit * fetchMultiplier, {
6508
6852
  ...topicKeys !== undefined ? { topicKeys } : {},
6853
+ ...options2?.contentType !== undefined ? { contentType: options2.contentType } : {},
6509
6854
  audit: false
6510
6855
  }).pipe(wrapError("searchFts5"));
6511
6856
  const vWeight = profile.modeWeights?.vector ?? 1;
6512
6857
  const fWeight = profile.modeWeights?.fts ?? 1;
6858
+ const mode = vWeight === 0 && fWeight > 0 ? "keyword" : fWeight === 0 && vWeight > 0 ? "semantic" : "hybrid";
6513
6859
  const denseRanks = vectorResults.length > 0 ? vectorResults.map((s) => [s.memory.id, s.score * vWeight]) : null;
6514
6860
  const fts5Ranks = fts5Results.length > 0 ? fts5Results.map((s) => [s.memory.id, s.score * fWeight]) : null;
6515
6861
  if (!denseRanks && !fts5Ranks) {
6516
6862
  yield* store.logAudit({
6517
6863
  query: query2,
6518
6864
  namespace,
6519
- mode: "hybrid",
6865
+ mode,
6520
6866
  resultCount: 0,
6521
6867
  startTime: start,
6522
6868
  queryProfile: JSON.stringify({
@@ -6524,6 +6870,7 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6524
6870
  recencyBoost: profile.recencyBoost ?? 0,
6525
6871
  conceptFilter: profile.conceptFilter,
6526
6872
  topicKeys,
6873
+ contentType: options2?.contentType,
6527
6874
  legs: { vector: { count: 0, topScores: [] }, fts: { count: 0, topScores: [] } }
6528
6875
  }),
6529
6876
  sources: JSON.stringify({ vector: [], fts: [] })
@@ -6566,7 +6913,7 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6566
6913
  yield* store.logAudit({
6567
6914
  query: query2,
6568
6915
  namespace,
6569
- mode: "hybrid",
6916
+ mode,
6570
6917
  resultCount: results.length,
6571
6918
  startTime: start,
6572
6919
  queryProfile: JSON.stringify({
@@ -6574,6 +6921,7 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6574
6921
  recencyBoost: profile.recencyBoost ?? 0,
6575
6922
  conceptFilter: profile.conceptFilter,
6576
6923
  topicKeys,
6924
+ contentType: options2?.contentType,
6577
6925
  legs: {
6578
6926
  vector: {
6579
6927
  count: vectorResults.length,
@@ -6590,9 +6938,10 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6590
6938
  fts: fts5Results.map((s) => s.memory.id)
6591
6939
  })
6592
6940
  }).pipe(wrapError("logAudit"));
6593
- yield* Effect35.logDebug("hybrid.search").pipe(Effect35.annotateLogs({
6941
+ yield* Effect35.logInfo("hybrid.search").pipe(Effect35.annotateLogs({
6594
6942
  scope: "@dimple/retriever",
6595
6943
  event: "hybrid.search",
6944
+ mode,
6596
6945
  namespace,
6597
6946
  query: query2,
6598
6947
  resultCount: results.length
@@ -6730,9 +7079,9 @@ ${schemaJson}`,
6730
7079
  return { generateText, generateObject };
6731
7080
  };
6732
7081
  // ../llm/src/provider.ts
6733
- import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
7082
+ import { createOpenAICompatible as createOpenAICompatible2 } from "@ai-sdk/openai-compatible";
6734
7083
  var openAICompatible = (options) => {
6735
- const provider = createOpenAICompatible({
7084
+ const provider = createOpenAICompatible2({
6736
7085
  baseURL: options.baseURL,
6737
7086
  apiKey: options.apiKey,
6738
7087
  name: options.name ?? "openai-compatible"
@@ -6834,12 +7183,15 @@ var embedderFromConfig = (model) => model.provider === "transformers" ? Effect39
6834
7183
  ...model.dtype !== undefined ? { dtype: model.dtype } : {},
6835
7184
  ...model.pooling !== undefined ? { pooling: model.pooling } : {},
6836
7185
  ...model.normalize !== undefined ? { normalize: model.normalize } : {},
6837
- ...model.cacheDir !== undefined ? { cacheDir: model.cacheDir } : {}
6838
- })) : model.provider === "openai" ? model.apiKey === undefined ? Effect39.fail(new EmbeddingError({ message: `OpenAI model ${model.id} requires an apiKey` })) : Effect39.succeed(makeOpenAIModel({
7186
+ ...model.cacheDir !== undefined ? { cacheDir: model.cacheDir } : {},
7187
+ ...model.autoInstall !== undefined ? { autoInstall: model.autoInstall } : {}
7188
+ })) : model.provider === "openai" ? model.apiKey === undefined ? Effect39.fail(new EmbeddingError({ message: `OpenAI model ${model.id} requires an apiKey` })) : Effect39.succeed(makeHostedEmbeddingModel({
6839
7189
  modelId: model.id,
6840
7190
  model: model.model,
6841
7191
  dimensions: model.dimensions,
6842
- apiKey: model.apiKey
7192
+ apiKey: model.apiKey,
7193
+ ...model.provider !== "openai" ? { provider: model.provider } : {},
7194
+ ...model.baseURL !== undefined ? { baseURL: model.baseURL } : {}
6843
7195
  })) : Effect39.fail(new EmbeddingError({ message: `Unknown provider: ${model.provider}` }));
6844
7196
  var layer13 = (options) => {
6845
7197
  const topicConfig = Schema24.decodeUnknownSync(TopicConfig)(options.topics?.config ?? {});
@@ -7013,7 +7365,8 @@ var ExtractedChunk = Schema25.Struct({
7013
7365
  startLine: Schema25.Number,
7014
7366
  endLine: Schema25.Number,
7015
7367
  language: Schema25.String,
7016
- docstring: Schema25.optional(Schema25.String)
7368
+ docstring: Schema25.optional(Schema25.String),
7369
+ calls: Schema25.optional(Schema25.Array(Schema25.Struct({ name: Schema25.String, line: Schema25.Number })))
7017
7370
  });
7018
7371
  var ImportInfo = Schema25.Struct({
7019
7372
  source: Schema25.String,
@@ -7097,6 +7450,7 @@ function emit(source, semantic, filePath, lang, chunks) {
7097
7450
  }
7098
7451
  function makeChunk(source, _semantic, filePath, lang, kind, name, docstring, parentClass) {
7099
7452
  const id = parentClass ? `${filePath}:${parentClass}.${name}:${source.startPosition.row + 1}` : `${filePath}:${name}:${source.startPosition.row + 1}`;
7453
+ const calls = callsOf(source);
7100
7454
  return {
7101
7455
  id,
7102
7456
  filePath,
@@ -7106,9 +7460,49 @@ function makeChunk(source, _semantic, filePath, lang, kind, name, docstring, par
7106
7460
  startLine: source.startPosition.row + 1,
7107
7461
  endLine: source.endPosition.row + 1,
7108
7462
  language: lang,
7109
- docstring
7463
+ docstring,
7464
+ ...calls.length > 0 ? { calls } : {}
7110
7465
  };
7111
7466
  }
7467
+ function callsOf(node) {
7468
+ const out = [];
7469
+ const calleeName = (n) => {
7470
+ let cur = n;
7471
+ for (let guard = 0;guard < 4; guard++) {
7472
+ if (cur.type === "identifier")
7473
+ return cur.text;
7474
+ if (cur.type === "member_expression") {
7475
+ const prop = cur.childForFieldName("property");
7476
+ if (prop === null)
7477
+ return;
7478
+ cur = prop;
7479
+ continue;
7480
+ }
7481
+ if (cur.type === "non_null_expression" || cur.type === "parenthesized_expression") {
7482
+ const inner = cur.namedChildren[0];
7483
+ if (inner === undefined)
7484
+ return;
7485
+ cur = inner;
7486
+ continue;
7487
+ }
7488
+ return;
7489
+ }
7490
+ return;
7491
+ };
7492
+ const visit = (n) => {
7493
+ if (n.type === "call_expression") {
7494
+ const callee = n.childForFieldName("function");
7495
+ const name = callee === null ? undefined : calleeName(callee);
7496
+ if (name !== undefined && name.length > 0) {
7497
+ out.push({ name, line: n.startPosition.row + 1 });
7498
+ }
7499
+ }
7500
+ for (const child of n.namedChildren)
7501
+ visit(child);
7502
+ };
7503
+ visit(node);
7504
+ return out;
7505
+ }
7112
7506
  function unwrap2(node) {
7113
7507
  for (const child of node.namedChildren) {
7114
7508
  if (child.type !== "decorator" && child.type !== "comment")
@@ -7584,11 +7978,11 @@ class Service15 extends Context15.Service()("@dimple/wasm/WasmDownloader") {
7584
7978
  }
7585
7979
  // ../wasm/src/wasm-cache.ts
7586
7980
  import { Context as Context16, Effect as Effect43, Layer as Layer20 } from "effect";
7587
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, renameSync, writeFileSync } from "node:fs";
7588
- import { join as join2 } from "node:path";
7981
+ import { existsSync as existsSync3, mkdirSync, renameSync, writeFileSync } from "node:fs";
7982
+ import { join as join3 } from "node:path";
7589
7983
 
7590
7984
  // ../../node_modules/.bun/env-paths@4.0.0/node_modules/env-paths/index.js
7591
- import path2 from "node:path";
7985
+ import path from "node:path";
7592
7986
  import os from "node:os";
7593
7987
  import process2 from "node:process";
7594
7988
 
@@ -7628,36 +8022,36 @@ function assertSafeFilename(filename) {
7628
8022
  // ../../node_modules/.bun/env-paths@4.0.0/node_modules/env-paths/index.js
7629
8023
  var homedir = os.homedir();
7630
8024
  var tmpdir = os.tmpdir();
7631
- var { env: env2 } = process2;
8025
+ var { env } = process2;
7632
8026
  var macos = (name) => {
7633
- const library = path2.join(homedir, "Library");
8027
+ const library = path.join(homedir, "Library");
7634
8028
  return {
7635
- data: path2.join(library, "Application Support", name),
7636
- config: path2.join(library, "Preferences", name),
7637
- cache: path2.join(library, "Caches", name),
7638
- log: path2.join(library, "Logs", name),
7639
- temp: path2.join(tmpdir, name)
8029
+ data: path.join(library, "Application Support", name),
8030
+ config: path.join(library, "Preferences", name),
8031
+ cache: path.join(library, "Caches", name),
8032
+ log: path.join(library, "Logs", name),
8033
+ temp: path.join(tmpdir, name)
7640
8034
  };
7641
8035
  };
7642
8036
  var windows = (name) => {
7643
- const appData = env2.APPDATA || path2.join(homedir, "AppData", "Roaming");
7644
- const localAppData = env2.LOCALAPPDATA || path2.join(homedir, "AppData", "Local");
8037
+ const appData = env.APPDATA || path.join(homedir, "AppData", "Roaming");
8038
+ const localAppData = env.LOCALAPPDATA || path.join(homedir, "AppData", "Local");
7645
8039
  return {
7646
- data: path2.join(localAppData, name, "Data"),
7647
- config: path2.join(appData, name, "Config"),
7648
- cache: path2.join(localAppData, name, "Cache"),
7649
- log: path2.join(localAppData, name, "Log"),
7650
- temp: path2.join(tmpdir, name)
8040
+ data: path.join(localAppData, name, "Data"),
8041
+ config: path.join(appData, name, "Config"),
8042
+ cache: path.join(localAppData, name, "Cache"),
8043
+ log: path.join(localAppData, name, "Log"),
8044
+ temp: path.join(tmpdir, name)
7651
8045
  };
7652
8046
  };
7653
8047
  var linux = (name) => {
7654
- const username = path2.basename(homedir);
8048
+ const username = path.basename(homedir);
7655
8049
  return {
7656
- data: path2.join(env2.XDG_DATA_HOME || path2.join(homedir, ".local", "share"), name),
7657
- config: path2.join(env2.XDG_CONFIG_HOME || path2.join(homedir, ".config"), name),
7658
- cache: path2.join(env2.XDG_CACHE_HOME || path2.join(homedir, ".cache"), name),
7659
- log: path2.join(env2.XDG_STATE_HOME || path2.join(homedir, ".local", "state"), name),
7660
- temp: path2.join(tmpdir, username, name)
8050
+ data: path.join(env.XDG_DATA_HOME || path.join(homedir, ".local", "share"), name),
8051
+ config: path.join(env.XDG_CONFIG_HOME || path.join(homedir, ".config"), name),
8052
+ cache: path.join(env.XDG_CACHE_HOME || path.join(homedir, ".cache"), name),
8053
+ log: path.join(env.XDG_STATE_HOME || path.join(homedir, ".local", "state"), name),
8054
+ temp: path.join(tmpdir, username, name)
7661
8055
  };
7662
8056
  };
7663
8057
  function envPaths(name, { suffix = "nodejs" } = {}) {
@@ -7682,19 +8076,19 @@ class Service16 extends Context16.Service()("@dimple/wasm/WasmCache") {
7682
8076
  }
7683
8077
  var layer16 = Layer20.effect(Service16, Effect43.succeed(Service16.of({
7684
8078
  ensure: Effect43.fn("WasmCache.ensure")(function* (lang, version, download) {
7685
- const versionDir = join2(paths.cache, "tree-sitter", lang, version);
7686
- const finalPath = join2(versionDir, "parser.wasm");
8079
+ const versionDir = join3(paths.cache, "tree-sitter", lang, version);
8080
+ const finalPath = join3(versionDir, "parser.wasm");
7687
8081
  const exists = yield* Effect43.try({
7688
- try: () => existsSync2(finalPath),
8082
+ try: () => existsSync3(finalPath),
7689
8083
  catch: (cause) => new WasmCacheError({ message: `Failed to stat ${finalPath}`, cause })
7690
8084
  });
7691
8085
  if (exists)
7692
8086
  return finalPath;
7693
8087
  yield* Effect43.try({
7694
- try: () => mkdirSync3(versionDir, { recursive: true }),
8088
+ try: () => mkdirSync(versionDir, { recursive: true }),
7695
8089
  catch: (cause) => new WasmCacheError({ message: `Failed to create ${versionDir}`, cause })
7696
8090
  });
7697
- const tempPath = join2(versionDir, `.tmp-${process.pid}`);
8091
+ const tempPath = join3(versionDir, `.tmp-${process.pid}`);
7698
8092
  const wasm = yield* download;
7699
8093
  yield* Effect43.try({
7700
8094
  try: () => writeFileSync(tempPath, wasm),
@@ -7709,7 +8103,7 @@ var layer16 = Layer20.effect(Service16, Effect43.succeed(Service16.of({
7709
8103
  }).pipe(Effect43.catchCause(() => Effect43.succeed(false)));
7710
8104
  if (!renamed) {
7711
8105
  const existsAfter = yield* Effect43.try({
7712
- try: () => existsSync2(finalPath),
8106
+ try: () => existsSync3(finalPath),
7713
8107
  catch: (cause) => new WasmCacheError({ message: `Failed to stat ${finalPath}`, cause })
7714
8108
  });
7715
8109
  if (!existsAfter) {
@@ -7740,12 +8134,12 @@ var layer17 = Layer21.effect(Service17, Effect44.gen(function* () {
7740
8134
  message: `Failed to download grammar for: ${lang}`,
7741
8135
  cause
7742
8136
  })));
7743
- const path3 = yield* cache.ensure(lang, metadata.version, Effect44.succeed(wasmBytes)).pipe(Effect44.mapError((cause) => new GrammarProvisionError({
8137
+ const path2 = yield* cache.ensure(lang, metadata.version, Effect44.succeed(wasmBytes)).pipe(Effect44.mapError((cause) => new GrammarProvisionError({
7744
8138
  language: lang,
7745
8139
  message: `Failed to provision grammar for: ${lang}`,
7746
8140
  cause
7747
8141
  })));
7748
- return path3;
8142
+ return path2;
7749
8143
  });
7750
8144
  return Service17.of({ ensure });
7751
8145
  }));
@@ -7864,11 +8258,27 @@ class DuplicateEdgeError extends Schema33.TaggedErrorClass()("Sdk.DuplicateEdge"
7864
8258
  }) {
7865
8259
  }
7866
8260
 
8261
+ class EdgeNotFoundError extends Schema33.TaggedErrorClass()("Sdk.EdgeNotFound", {
8262
+ id: Schema33.optional(Schema33.String),
8263
+ message: Schema33.String
8264
+ }) {
8265
+ }
8266
+
7867
8267
  class StoreFailure extends Schema33.TaggedErrorClass()("Sdk.StoreFailure", {
7868
8268
  message: Schema33.String
7869
8269
  }) {
7870
8270
  }
7871
8271
 
8272
+ class ReloadError extends Schema33.TaggedErrorClass()("Sdk.Reload", {
8273
+ message: Schema33.String
8274
+ }) {
8275
+ }
8276
+
8277
+ class EmbeddingFailure extends Schema33.TaggedErrorClass()("Sdk.Embedding", {
8278
+ message: Schema33.String
8279
+ }) {
8280
+ }
8281
+
7872
8282
  class InputError extends Schema33.TaggedErrorClass()("Sdk.InputError", {
7873
8283
  message: Schema33.String
7874
8284
  }) {
@@ -7890,12 +8300,14 @@ var messageOf = (error) => {
7890
8300
  }
7891
8301
  };
7892
8302
  var remapError = (error) => {
7893
- if (error instanceof ConfigError || error instanceof NotFoundError || error instanceof DuplicateKeyError || error instanceof DuplicateEdgeError || error instanceof LLMNotConfiguredError || error instanceof JobFailedError || error instanceof StoreFailure || error instanceof InputError) {
8303
+ if (error instanceof ConfigError || error instanceof NotFoundError || error instanceof DuplicateKeyError || error instanceof DuplicateEdgeError || error instanceof EdgeNotFoundError || error instanceof LLMNotConfiguredError || error instanceof JobFailedError || error instanceof EmbeddingFailure || error instanceof StoreFailure || error instanceof ReloadError || error instanceof InputError) {
7894
8304
  return error;
7895
8305
  }
7896
8306
  const message = messageOf(error);
7897
8307
  if (error instanceof CoreConfigError)
7898
8308
  return new ConfigError({ message });
8309
+ if (error instanceof EmbeddingError)
8310
+ return new EmbeddingFailure({ message });
7899
8311
  if (error instanceof NotFound) {
7900
8312
  return new NotFoundError({
7901
8313
  ...error.id !== undefined ? { id: error.id } : {},
@@ -7917,6 +8329,12 @@ var remapError = (error) => {
7917
8329
  message: `Edge already exists: ${error.fromMemoryId} → ${error.toMemoryId} (${error.edgeKind})`
7918
8330
  });
7919
8331
  }
8332
+ if (error instanceof exports_graph.EdgeNotFound) {
8333
+ return new EdgeNotFoundError({
8334
+ ...error.id !== undefined ? { id: error.id } : {},
8335
+ message: `Edge not found: ${error.id ?? "unknown id"}`
8336
+ });
8337
+ }
7920
8338
  if (error instanceof JobFailed || error instanceof JobTimeout || error instanceof JobCancelled) {
7921
8339
  return message.includes("LLM not configured") ? new LLMNotConfiguredError({ message }) : new JobFailedError({ message });
7922
8340
  }
@@ -7933,8 +8351,9 @@ var configLayerFromInput = (input) => Layer25.effect(Service4, resolveConfigInpu
7933
8351
  import { Cause as Cause5, Effect as Effect49, Exit as Exit6 } from "effect";
7934
8352
 
7935
8353
  // src/indexer.ts
7936
- import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
7937
- import { basename, dirname as dirname4, extname, join as join3, resolve, sep } from "node:path";
8354
+ import { execFileSync } from "node:child_process";
8355
+ import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
8356
+ import { basename, dirname, extname, join as join4, resolve, sep } from "node:path";
7938
8357
  var INDEXABLE_EXTENSIONS = [
7939
8358
  ".ts",
7940
8359
  ".mts",
@@ -7971,52 +8390,68 @@ var LANGUAGE_BY_EXTENSION = {
7971
8390
  ".cjs": "javascript",
7972
8391
  ".jsx": "jsx"
7973
8392
  };
7974
- var languageForPath = (path3) => LANGUAGE_BY_EXTENSION[extname(path3)];
8393
+ var languageForPath = (path2) => LANGUAGE_BY_EXTENSION[extname(path2)];
7975
8394
  var walkFiles = (paths2, extensions) => {
7976
8395
  const extensionSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
7977
8396
  const files = [];
7978
8397
  const errors3 = [];
7979
8398
  let unsupported = 0;
7980
- const root = commonRoot([...paths2, process.cwd()]);
7981
- const visit = (path3) => {
8399
+ const resolved = paths2.map((p) => resolve(p));
8400
+ const root = commonRoot([...resolved, process.cwd()]);
8401
+ const visit = (path2) => {
7982
8402
  let stat;
7983
8403
  try {
7984
- stat = statSync2(path3);
8404
+ stat = statSync(path2);
7985
8405
  } catch (cause) {
7986
- errors3.push({ filePath: path3, message: `stat failed: ${messageOf2(cause)}` });
8406
+ errors3.push({ filePath: path2, message: `stat failed: ${messageOf2(cause)}` });
7987
8407
  return;
7988
8408
  }
7989
8409
  if (stat.isDirectory()) {
7990
- const name = basename(path3);
8410
+ const name = basename(path2);
7991
8411
  if (EXCLUDED_DIRS.has(name))
7992
8412
  return;
7993
8413
  let entries;
7994
8414
  try {
7995
- entries = readdirSync(path3);
8415
+ entries = readdirSync(path2);
7996
8416
  } catch (cause) {
7997
- errors3.push({ filePath: path3, message: `readdir failed: ${messageOf2(cause)}` });
8417
+ errors3.push({ filePath: path2, message: `readdir failed: ${messageOf2(cause)}` });
7998
8418
  return;
7999
8419
  }
8000
8420
  for (const entry of entries)
8001
- visit(join3(path3, entry));
8421
+ visit(join4(path2, entry));
8002
8422
  return;
8003
8423
  }
8004
8424
  if (!stat.isFile())
8005
8425
  return;
8006
- if (!extensionSet.has(extname(path3))) {
8426
+ if (!extensionSet.has(extname(path2))) {
8007
8427
  unsupported += 1;
8008
8428
  return;
8009
8429
  }
8010
- files.push(path3);
8430
+ files.push(path2);
8011
8431
  };
8012
- for (const path3 of paths2)
8013
- visit(resolve(path3));
8014
- return { files, unsupported, errors: errors3, root };
8432
+ for (const path2 of resolved)
8433
+ visit(path2);
8434
+ return { files, unsupported, errors: errors3, root, roots: resolved };
8015
8435
  };
8016
- var readCapped = (path3, maxBytes) => {
8017
- const text4 = readFileSync2(path3, "utf8");
8436
+ var readCapped = (path2, maxBytes) => {
8437
+ const text4 = readFileSync2(path2, "utf8");
8018
8438
  return text4.length > maxBytes ? text4.slice(0, maxBytes) : text4;
8019
8439
  };
8440
+ var currentCommit = (firstFile) => {
8441
+ if (firstFile === undefined)
8442
+ return null;
8443
+ try {
8444
+ const out = execFileSync("git", ["rev-parse", "HEAD"], {
8445
+ cwd: dirname(firstFile),
8446
+ encoding: "utf8",
8447
+ stdio: ["ignore", "pipe", "ignore"]
8448
+ });
8449
+ const sha = out.trim();
8450
+ return /^[0-9a-f]{7,40}$/.test(sha) ? sha : null;
8451
+ } catch {
8452
+ return null;
8453
+ }
8454
+ };
8020
8455
  var resolveImportTarget = (fromDir, specifier, extensions) => {
8021
8456
  if (!specifier.startsWith("."))
8022
8457
  return;
@@ -8024,11 +8459,11 @@ var resolveImportTarget = (fromDir, specifier, extensions) => {
8024
8459
  const candidates2 = [
8025
8460
  base,
8026
8461
  ...extensions.map((e) => base + e),
8027
- ...extensions.map((e) => join3(base, `index${e}`))
8462
+ ...extensions.map((e) => join4(base, `index${e}`))
8028
8463
  ];
8029
8464
  for (const candidate of candidates2) {
8030
8465
  try {
8031
- if (existsSync3(candidate) && statSync2(candidate).isFile())
8466
+ if (existsSync4(candidate) && statSync(candidate).isFile())
8032
8467
  return candidate;
8033
8468
  } catch {
8034
8469
  return;
@@ -8039,7 +8474,7 @@ var resolveImportTarget = (fromDir, specifier, extensions) => {
8039
8474
  var resolveImports = (filePath, sources, extensions) => {
8040
8475
  const targets = [];
8041
8476
  for (const source of sources) {
8042
- const target = resolveImportTarget(dirname4(filePath), source, extensions);
8477
+ const target = resolveImportTarget(dirname(filePath), source, extensions);
8043
8478
  if (target !== undefined)
8044
8479
  targets.push(target);
8045
8480
  }
@@ -8069,16 +8504,11 @@ var keyForPath = (root, filePath) => {
8069
8504
  var messageOf2 = (cause) => cause instanceof Error ? cause.message : String(cause);
8070
8505
 
8071
8506
  // src/dimple.ts
8072
- var makeFacade = (runtime, core, parser, extractor, dispose) => {
8073
- let disposed = false;
8074
- const run = (effect) => {
8075
- if (disposed) {
8076
- return Promise.reject(new StoreFailure({ message: "SDK disposed" }));
8077
- }
8078
- return runtime.runPromise(effect.pipe(Effect49.mapError(remapError))).catch((cause) => {
8079
- return Promise.reject(remapError(cause));
8080
- });
8081
- };
8507
+ var makeFacade = (state, reload, dispose) => {
8508
+ const run = state.run;
8509
+ const core = () => state.core();
8510
+ const parser = () => state.parser();
8511
+ const extractor = () => state.extractor();
8082
8512
  const validateQueryOptions = (options) => {
8083
8513
  const limit = options?.limit;
8084
8514
  if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {
@@ -8090,6 +8520,23 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8090
8520
  if (k !== undefined && (!Number.isInteger(k) || k < 0)) {
8091
8521
  return new InputError({ message: `query: k must be a non-negative integer (got ${k})` });
8092
8522
  }
8523
+ const budget = options?.budget;
8524
+ if (budget !== undefined) {
8525
+ if (budget === null || typeof budget !== "object" || Array.isArray(budget)) {
8526
+ return new InputError({ message: "query: budget must be an object" });
8527
+ }
8528
+ for (const [name, v] of [
8529
+ ["maxUnits", budget.maxUnits],
8530
+ ["maxChars", budget.maxChars],
8531
+ ["maxMs", budget.maxMs]
8532
+ ]) {
8533
+ if (v !== undefined && (!Number.isInteger(v) || v <= 0)) {
8534
+ return new InputError({
8535
+ message: `query: budget.${name} must be a positive integer (got ${String(v)})`
8536
+ });
8537
+ }
8538
+ }
8539
+ }
8093
8540
  const profile = options?.profile;
8094
8541
  if (profile !== undefined) {
8095
8542
  if (typeof profile === "string") {
@@ -8130,13 +8577,57 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8130
8577
  if (topicKeys !== undefined && (!Array.isArray(topicKeys) || topicKeys.some((t) => typeof t !== "string"))) {
8131
8578
  return new InputError({ message: "query: topics.topicKeys must be a string array" });
8132
8579
  }
8580
+ const contentType = options?.contentType;
8581
+ if (contentType !== undefined) {
8582
+ const kinds = typeof contentType === "string" ? [contentType] : contentType;
8583
+ if (kinds.length === 0 || kinds.some((k2) => typeof k2 !== "string" || k2.length === 0)) {
8584
+ return new InputError({
8585
+ message: "query: contentType must be a non-empty string or a non-empty string array"
8586
+ });
8587
+ }
8588
+ }
8133
8589
  return;
8134
8590
  };
8135
8591
  const runQuery = (text4, options) => run(Effect49.gen(function* () {
8592
+ if (typeof text4 !== "string") {
8593
+ return yield* new InputError({ message: "query: text must be a string" });
8594
+ }
8136
8595
  const invalid = validateQueryOptions(options);
8137
8596
  if (invalid !== undefined)
8138
8597
  return yield* invalid;
8139
- return yield* core.hybrid.hybrid(text4, options).pipe(Effect49.map((rows) => [...rows]));
8598
+ const { budget, ...rest } = options ?? {};
8599
+ const started = Date.now();
8600
+ const capLimit = budget?.maxUnits !== undefined && (rest.limit === undefined || rest.limit > budget.maxUnits) ? budget.maxUnits : rest.limit;
8601
+ let rows = yield* core().hybrid.hybrid(text4, {
8602
+ ...rest,
8603
+ ...capLimit !== undefined ? { limit: capLimit } : {}
8604
+ }).pipe(Effect49.map((r) => [...r]));
8605
+ let truncated = false;
8606
+ if (budget?.maxChars !== undefined && budget.maxChars > 0) {
8607
+ let used = 0;
8608
+ rows = rows.map((row) => {
8609
+ const content = row.memory.content;
8610
+ const room = budget.maxChars - used;
8611
+ if (room <= 0) {
8612
+ truncated = true;
8613
+ return { ...row, memory: { ...row.memory, content: "" } };
8614
+ }
8615
+ if (content.length > room) {
8616
+ truncated = true;
8617
+ used += room;
8618
+ return { ...row, memory: { ...row.memory, content: content.slice(0, room) } };
8619
+ }
8620
+ used += content.length;
8621
+ return row;
8622
+ });
8623
+ }
8624
+ if (budget?.maxMs !== undefined && Date.now() - started > budget.maxMs) {
8625
+ truncated = true;
8626
+ }
8627
+ const out = rows;
8628
+ if (truncated)
8629
+ out.truncated = true;
8630
+ return out;
8140
8631
  }));
8141
8632
  const validateWriteInput = (input) => {
8142
8633
  if (input === null || typeof input !== "object" || Array.isArray(input)) {
@@ -8155,9 +8646,9 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8155
8646
  return new InputError({ message: "write: namespace must be a string" });
8156
8647
  }
8157
8648
  const ttl = input.ttlSecs;
8158
- if (ttl !== undefined && (!Number.isInteger(ttl) || ttl < 0)) {
8649
+ if (typeof ttl === "number" && (!Number.isInteger(ttl) || ttl < 0)) {
8159
8650
  return new InputError({
8160
- message: `write: ttlSecs must be a non-negative integer (got ${String(ttl)})`
8651
+ message: `write: ttlSecs must be a non-negative integer or null (got ${String(ttl)})`
8161
8652
  });
8162
8653
  }
8163
8654
  if (input.metadata !== undefined && typeof input.metadata !== "string") {
@@ -8201,15 +8692,47 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8201
8692
  return;
8202
8693
  };
8203
8694
  const writeUnit = (input, embed) => Effect49.gen(function* () {
8204
- const mem = yield* core.store.write(input);
8695
+ const mem = yield* core().store.write(input);
8205
8696
  if (embed) {
8206
- yield* core.store.ensureTopicTree(core.defaultModel.id);
8207
- const vector = yield* core.defaultEmbedder.embedSingle(input.content);
8208
- yield* core.store.updateEmbedding(mem.id, [...vector], core.defaultModel.id);
8209
- return yield* core.store.get(mem.id);
8697
+ yield* core().store.ensureTopicTree(core().defaultModel.id);
8698
+ const vector = yield* core().defaultEmbedder.embedSingle(input.content);
8699
+ yield* core().store.updateEmbedding(mem.id, [...vector], core().defaultModel.id);
8700
+ return yield* core().store.get(mem.id);
8210
8701
  }
8211
8702
  return mem;
8212
8703
  });
8704
+ const upsertUnit = (input, embed) => Effect49.gen(function* () {
8705
+ const existing = yield* core().store.getByKey(input.logicalKey, input.namespace ?? "default").pipe(Effect49.catchTag("Store.NotFound", () => Effect49.succeed(undefined)));
8706
+ if (existing !== undefined && existing.content === input.content) {
8707
+ const stamp = codeVersionOf(input.metadata);
8708
+ if (stamp !== undefined && codeVersionOf(existing.metadata) === undefined) {
8709
+ yield* core().store.setMemoryMetadata(existing.id, { codeVersion: stamp });
8710
+ }
8711
+ if (embed && (existing.embedding === null || existing.embedding === undefined)) {
8712
+ yield* core().store.ensureTopicTree(core().defaultModel.id);
8713
+ const vector = yield* core().defaultEmbedder.embedSingle(input.content);
8714
+ yield* core().store.updateEmbedding(existing.id, [...vector], core().defaultModel.id);
8715
+ return { kind: "updated", id: existing.id };
8716
+ }
8717
+ return { kind: "skipped", id: existing.id };
8718
+ }
8719
+ if (existing !== undefined) {
8720
+ const { memory: memory2 } = yield* core().store.writeSuperseding(input);
8721
+ if (embed) {
8722
+ yield* core().store.ensureTopicTree(core().defaultModel.id);
8723
+ const vector = yield* core().defaultEmbedder.embedSingle(input.content);
8724
+ yield* core().store.updateEmbedding(memory2.id, [...vector], core().defaultModel.id);
8725
+ }
8726
+ return { kind: "updated", id: memory2.id };
8727
+ }
8728
+ const memory = yield* core().store.write(input);
8729
+ if (embed) {
8730
+ yield* core().store.ensureTopicTree(core().defaultModel.id);
8731
+ const vector = yield* core().defaultEmbedder.embedSingle(input.content);
8732
+ yield* core().store.updateEmbedding(memory.id, [...vector], core().defaultModel.id);
8733
+ }
8734
+ return { kind: "indexed", id: memory.id };
8735
+ });
8213
8736
  const validateIndexOptions = (options) => {
8214
8737
  if (options === null || typeof options !== "object" || Array.isArray(options)) {
8215
8738
  return new InputError({ message: "index: options must be an object" });
@@ -8244,16 +8767,32 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8244
8767
  return;
8245
8768
  };
8246
8769
  const messageOf3 = (cause) => cause instanceof Error ? cause.message : String(cause);
8770
+ const resolveMemory = (idOrKey) => core().store.get(idOrKey).pipe(Effect49.catchTag("Store.NotFound", () => core().store.getByKey(idOrKey, "default").pipe(Effect49.catchTag("Store.NotFound", () => Effect49.fail(new NotFoundError({ id: idOrKey, message: `Memory not found: ${idOrKey}` }))))));
8771
+ const codeVersionOf = (blob) => {
8772
+ if (blob === undefined || blob === null)
8773
+ return;
8774
+ try {
8775
+ const parsed = JSON.parse(blob);
8776
+ if (parsed !== null && typeof parsed === "object") {
8777
+ const v = parsed["codeVersion"];
8778
+ return typeof v === "string" ? v : undefined;
8779
+ }
8780
+ } catch {}
8781
+ return;
8782
+ };
8247
8783
  return {
8784
+ get defaultModelId() {
8785
+ return core().defaultModel.id;
8786
+ },
8248
8787
  write: (input, options) => run(Effect49.gen(function* () {
8249
8788
  const invalid = validateWriteInput(input);
8250
8789
  if (invalid !== undefined)
8251
8790
  return yield* invalid;
8252
8791
  return yield* writeUnit(input, options?.embed !== false);
8253
8792
  })),
8254
- get: (id) => run(core.store.get(id)),
8793
+ get: (id) => run(resolveMemory(id)),
8255
8794
  query: runQuery,
8256
- forget: (id) => run(core.store.forget(id)),
8795
+ forget: (id) => run(resolveMemory(id).pipe(Effect49.flatMap((memory) => core().store.forget(memory.id)))),
8257
8796
  index: (options) => run(Effect49.gen(function* () {
8258
8797
  const invalid = validateIndexOptions(options);
8259
8798
  if (invalid !== undefined)
@@ -8271,13 +8810,19 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8271
8810
  })
8272
8811
  });
8273
8812
  const failed = [...walk.errors];
8813
+ const commit = currentCommit(walk.files[0]);
8274
8814
  let indexed = 0;
8815
+ let updated = 0;
8816
+ let reconciled = 0;
8275
8817
  let skipped = 0;
8276
8818
  const unitIds = new Map;
8819
+ const freshIds = [];
8277
8820
  const pendingEdges = [];
8821
+ const pendingCallEdges = [];
8822
+ const stableChunkId = (chunkId) => chunkId.replace(/:\d+$/, "");
8278
8823
  const parentClassOf = (chunkId) => {
8279
- const parts = chunkId.split(":");
8280
- const parentAndMethod = parts[parts.length - 2];
8824
+ const parts = stableChunkId(chunkId).split(":");
8825
+ const parentAndMethod = parts[parts.length - 1];
8281
8826
  if (parentAndMethod === undefined || !parentAndMethod.includes("."))
8282
8827
  return;
8283
8828
  return parentAndMethod.split(".")[0];
@@ -8287,49 +8832,59 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8287
8832
  const language = languageForPath(filePath);
8288
8833
  if (language === undefined)
8289
8834
  continue;
8835
+ const chunkKeyOf = (chunkId) => `${keyPrefix}${relative}#${stableChunkId(chunkId)}`;
8290
8836
  const exit = yield* Effect49.exit(Effect49.gen(function* () {
8291
8837
  const source = yield* Effect49.try({
8292
8838
  try: () => readCapped(filePath, maxFileBytes),
8293
8839
  catch: (cause) => new InputError({ message: `read failed: ${messageOf3(cause)}` })
8294
8840
  });
8295
- const handle = yield* parser.parse({ lang: language, source });
8296
- const extracted = yield* extractor.extract({ filePath: relative, tree: handle });
8841
+ const handle = yield* parser().parse({ lang: language, source });
8842
+ const extracted = yield* extractor().extract({ filePath: relative, tree: handle });
8297
8843
  let units = 0;
8844
+ let upd = 0;
8298
8845
  let dups = 0;
8299
8846
  const fileKey = `${keyPrefix}${relative}`;
8300
- const fileOutcome = yield* writeUnit({
8847
+ const fileOutcome = yield* upsertUnit({
8301
8848
  logicalKey: fileKey,
8302
8849
  content: source,
8303
8850
  ...namespace !== undefined ? { namespace } : {},
8851
+ ttlSecs: null,
8304
8852
  metadata: JSON.stringify({
8305
8853
  filePath,
8306
8854
  language,
8307
8855
  kind: "file",
8308
- imports: extracted.imports ?? []
8856
+ imports: extracted.imports ?? [],
8857
+ ...commit !== null ? { codeVersion: commit } : {}
8309
8858
  }),
8310
8859
  contentType: `text/x-${language}`
8311
- }, embed).pipe(Effect49.catchTag("Store.DuplicateActiveKey", () => Effect49.succeed("duplicate")));
8312
- if (fileOutcome === "duplicate")
8860
+ }, embed);
8861
+ if (fileOutcome.kind === "skipped") {
8313
8862
  dups += 1;
8314
- else {
8315
- units += 1;
8316
8863
  unitIds.set(fileKey, fileOutcome.id);
8864
+ } else {
8865
+ if (fileOutcome.kind === "updated")
8866
+ upd += 1;
8867
+ else
8868
+ units += 1;
8869
+ unitIds.set(fileKey, fileOutcome.id);
8870
+ freshIds.push(fileOutcome.id);
8317
8871
  }
8318
8872
  const classKeys = new Map;
8319
8873
  for (const chunk of extracted.chunks) {
8320
8874
  if (chunk.kind === "class" || chunk.kind.endsWith("class")) {
8321
- classKeys.set(chunk.name, `${keyPrefix}${relative}#${chunk.id}`);
8875
+ classKeys.set(chunk.name, chunkKeyOf(chunk.id));
8322
8876
  }
8323
8877
  }
8324
8878
  for (const chunk of extracted.chunks) {
8325
- const chunkKey = `${keyPrefix}${relative}#${chunk.id}`;
8879
+ const chunkKey = chunkKeyOf(chunk.id);
8326
8880
  const content = [chunk.docstring, chunk.sourceText].filter((c) => c !== undefined && c.length > 0).join(`
8327
8881
 
8328
8882
  `).slice(0, maxChunkBytes);
8329
- const chunkOutcome = yield* writeUnit({
8883
+ const chunkOutcome = yield* upsertUnit({
8330
8884
  logicalKey: chunkKey,
8331
8885
  content,
8332
8886
  ...namespace !== undefined ? { namespace } : {},
8887
+ ttlSecs: null,
8333
8888
  metadata: JSON.stringify({
8334
8889
  filePath,
8335
8890
  language,
@@ -8337,17 +8892,23 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8337
8892
  name: chunk.name,
8338
8893
  startLine: chunk.startLine,
8339
8894
  endLine: chunk.endLine,
8340
- ...chunk.docstring !== undefined ? { docstring: chunk.docstring } : {}
8895
+ ...chunk.docstring !== undefined ? { docstring: chunk.docstring } : {},
8896
+ ...commit !== null ? { codeVersion: commit } : {}
8341
8897
  }),
8342
8898
  contentType: `text/x-${language}`
8343
- }, embed).pipe(Effect49.catchTag("Store.DuplicateActiveKey", () => Effect49.succeed("duplicate")));
8344
- if (chunkOutcome === "duplicate") {
8899
+ }, embed);
8900
+ if (chunkOutcome.kind === "skipped") {
8345
8901
  dups += 1;
8902
+ unitIds.set(chunkKey, chunkOutcome.id);
8346
8903
  } else {
8347
- units += 1;
8904
+ if (chunkOutcome.kind === "updated")
8905
+ upd += 1;
8906
+ else
8907
+ units += 1;
8348
8908
  unitIds.set(chunkKey, chunkOutcome.id);
8909
+ freshIds.push(chunkOutcome.id);
8349
8910
  }
8350
- if (chunkOutcome !== "duplicate" && chunk.kind === "method") {
8911
+ if (chunkOutcome.kind !== "skipped" && chunk.kind === "method") {
8351
8912
  const parent = parentClassOf(chunk.id);
8352
8913
  const classKey = parent === undefined ? undefined : classKeys.get(parent);
8353
8914
  if (classKey !== undefined) {
@@ -8361,37 +8922,68 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8361
8922
  to: fileKey
8362
8923
  });
8363
8924
  }
8364
- return { units, dups };
8925
+ const importRels = resolveImports(filePath, (extracted.imports ?? []).map((imp) => imp.source), extensions).map((target) => keyForPath(walk.root, target));
8926
+ for (const chunk of extracted.chunks) {
8927
+ const callerKey = chunkKeyOf(chunk.id);
8928
+ if (!unitIds.has(callerKey))
8929
+ continue;
8930
+ for (const call of chunk.calls ?? []) {
8931
+ const candidates2 = [
8932
+ `${keyPrefix}${relative}#${relative}:${call.name}`,
8933
+ ...importRels.map((rel) => `${keyPrefix}${rel}#${rel}:${call.name}`)
8934
+ ];
8935
+ const first = candidates2.find((key) => unitIds.has(key));
8936
+ if (first !== undefined) {
8937
+ pendingCallEdges.push({ from: callerKey, to: first });
8938
+ }
8939
+ }
8940
+ }
8941
+ return { units, upd, dups };
8365
8942
  }));
8366
8943
  if (Exit6.isFailure(exit)) {
8367
8944
  failed.push({ filePath, message: Cause5.pretty(exit.cause) });
8368
8945
  } else {
8369
8946
  indexed += exit.value.units;
8947
+ updated += exit.value.upd;
8370
8948
  skipped += exit.value.dups;
8371
8949
  }
8372
8950
  }
8373
- const edgeEffect = (edge) => {
8374
- const from = unitIds.get(edge.from);
8375
- const to = unitIds.get(edge.to);
8951
+ const vanished = yield* core().store.reconcile(namespace ?? "default", keyPrefix, walk.root, walk.roots, [...unitIds.keys()]);
8952
+ reconciled = vanished.length;
8953
+ yield* core().store.reconcileRenameCandidates(vanished, freshIds);
8954
+ const resolveKey = (key) => {
8955
+ const fromMap = unitIds.get(key);
8956
+ if (fromMap !== undefined)
8957
+ return Effect49.succeed(fromMap);
8958
+ return core().store.getByKey(key, namespace ?? "default").pipe(Effect49.map((m) => m.id), Effect49.catchTag("Store.NotFound", () => Effect49.succeed(undefined)));
8959
+ };
8960
+ const edgeEffect = (edge) => Effect49.gen(function* () {
8961
+ const [from, to] = yield* Effect49.all([resolveKey(edge.from), resolveKey(edge.to)]);
8376
8962
  if (from === undefined || to === undefined || from === to)
8377
- return Effect49.void;
8378
- return core.store.addEdge({
8963
+ return;
8964
+ yield* Effect49.exit(core().store.addEdge({
8379
8965
  fromMemoryId: from,
8380
8966
  toMemoryId: to,
8381
- edgeKind: "memory_supports",
8967
+ edgeKind: edge.edgeKind,
8382
8968
  source: "rule",
8383
8969
  confidence: 1,
8384
8970
  confirmed: true
8385
- });
8386
- };
8971
+ }));
8972
+ });
8387
8973
  for (const edge of pendingEdges) {
8388
- yield* Effect49.exit(edgeEffect(edge));
8974
+ yield* Effect49.exit(edgeEffect({ ...edge, edgeKind: "memory_supports" }));
8975
+ }
8976
+ for (const edge of pendingCallEdges) {
8977
+ yield* Effect49.exit(edgeEffect({ ...edge, edgeKind: "code_calls" }));
8389
8978
  }
8390
8979
  return {
8391
8980
  files: walk.files.length,
8392
8981
  indexed,
8982
+ updated,
8983
+ reconciled,
8393
8984
  skipped,
8394
8985
  unsupported: walk.unsupported,
8986
+ commit,
8395
8987
  failed
8396
8988
  };
8397
8989
  })),
@@ -8401,57 +8993,232 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8401
8993
  message: "updateEmbedding: embedding must be a non-empty array of finite numbers"
8402
8994
  });
8403
8995
  }
8404
- const key = modelKey ?? core.defaultModel.id;
8405
- yield* core.store.ensureTopicTree(key);
8406
- yield* core.store.updateEmbedding(id, [...embedding], key);
8996
+ const key = modelKey ?? core().defaultModel.id;
8997
+ const memory = yield* resolveMemory(id);
8998
+ yield* core().store.ensureTopicTree(key);
8999
+ yield* core().store.updateEmbedding(memory.id, [...embedding], key);
8407
9000
  })),
8408
- embed: (text4) => run(core.defaultEmbedder.embedSingle(text4).pipe(Effect49.map((vector) => [...vector]))),
8409
- dream: (modelKey) => run(core.runtime.enqueue(MemoryDream, { modelKey, runId: crypto.randomUUID() }).pipe(Effect49.flatMap((jobId) => core.runtime.wait(jobId)), Effect49.asVoid)),
8410
- repair: (modelKey) => run(core.runtime.enqueue(GraphRepair, { modelKey, runId: crypto.randomUUID() }).pipe(Effect49.flatMap((jobId) => core.runtime.wait(jobId)), Effect49.asVoid)),
8411
- explain: (input) => run(core.store.explain(input)),
8412
- health: (modelKey) => run(core.store.topicHealth(modelKey)),
9001
+ embed: (text4) => run(core().defaultEmbedder.embedSingle(text4).pipe(Effect49.map((vector) => [...vector]))),
9002
+ dream: (modelKey) => run(core().runtime.enqueue(MemoryDream, { modelKey, runId: crypto.randomUUID() }).pipe(Effect49.flatMap((jobId) => core().runtime.wait(jobId)), Effect49.asVoid)),
9003
+ dbSchema: () => run(core().store.listSchema()),
9004
+ repair: (modelKey) => run(core().runtime.enqueue(GraphRepair, { modelKey, runId: crypto.randomUUID() }).pipe(Effect49.flatMap((jobId) => core().runtime.wait(jobId)), Effect49.asVoid)),
9005
+ explain: (input, options) => run(core().store.explain(input, {
9006
+ ...options?.edgeKinds !== undefined ? { edgeKinds: options.edgeKinds } : {},
9007
+ ...options?.maxDepth !== undefined ? { maxDepth: options.maxDepth } : {},
9008
+ ...options?.maxNeighbors !== undefined ? { maxNeighbors: options.maxNeighbors } : {},
9009
+ ...options?.maxTotal !== undefined ? { maxTotal: options.maxTotal } : {},
9010
+ ...options?.includePending !== undefined ? { includePending: options.includePending } : {}
9011
+ })),
9012
+ health: (modelKey) => run(core().store.topicHealth(modelKey)),
8413
9013
  graph: {
8414
9014
  addEdge: (input) => run(Effect49.gen(function* () {
8415
9015
  const invalid = validateAddEdgeInput(input);
8416
9016
  if (invalid !== undefined)
8417
9017
  return yield* invalid;
8418
- return yield* core.store.addEdge(input);
9018
+ return yield* core().store.addEdge(input);
8419
9019
  })),
8420
- candidates: () => run(core.store.graphCandidates()),
8421
- coverage: (modelKey) => run(core.store.graphCoverage(modelKey))
9020
+ candidates: () => run(core().store.graphCandidates()),
9021
+ coverage: (modelKey) => run(core().store.graphCoverage(modelKey)),
9022
+ confirm: (id) => run(core().store.confirmEdge(id)),
9023
+ reject: (id) => run(core().store.rejectEdge(id)),
9024
+ expire: (id) => run(core().store.expireEdge(id))
8422
9025
  },
8423
- dispose: () => dispose().finally(() => {
8424
- disposed = true;
8425
- })
9026
+ reload: (input) => reload(input),
9027
+ dispose: () => dispose()
8426
9028
  };
8427
9029
  };
8428
9030
 
8429
9031
  // src/make.ts
8430
- var makeDimple = async (options) => {
9032
+ var stableKey = (value2) => {
9033
+ if (Array.isArray(value2))
9034
+ return `[${value2.map(stableKey).join(",")}]`;
9035
+ if (value2 !== null && typeof value2 === "object") {
9036
+ const entries = Object.entries(value2).filter(([, v]) => v !== undefined).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
9037
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableKey(v)}`).join(",")}}`;
9038
+ }
9039
+ return JSON.stringify(value2);
9040
+ };
9041
+ var diffConfig = (oldC, newC) => {
9042
+ if (oldC === undefined)
9043
+ return { storeUrl: true, jobsUrl: true, rest: true };
9044
+ const oldJobsUrl = oldC.jobs?.dbUrl;
9045
+ const newJobsUrl = newC.jobs?.dbUrl;
9046
+ const restOld = { ...oldC, storeUrl: "", jobs: { ...oldC.jobs, dbUrl: "" } };
9047
+ const restNew = { ...newC, storeUrl: "", jobs: { ...newC.jobs, dbUrl: "" } };
9048
+ return {
9049
+ storeUrl: oldC.storeUrl !== newC.storeUrl,
9050
+ jobsUrl: oldJobsUrl !== newJobsUrl,
9051
+ rest: stableKey(restOld) !== stableKey(restNew)
9052
+ };
9053
+ };
9054
+ var RELOAD_DRAIN_MS = 30000;
9055
+ var bootStack = async (options) => {
8431
9056
  const scope = await Effect50.runPromise(Scope5.make());
8432
9057
  let runtime;
8433
9058
  try {
8434
9059
  const layer21 = ("core" in options ? layer13(options.core) : layerConfig7(options.migrationFolder !== undefined ? { migrationFolder: options.migrationFolder } : {}).pipe(Layer26.provide(configLayerFromInput(options.config)))).pipe(Layer26.provide(Layer26.succeed(Scope5.Scope, scope)));
8435
9060
  const appLayer = Layer26.mergeAll(layer21, layer20, layer14);
8436
- const rt = ManagedRuntime2.make(appLayer);
9061
+ const rt = ManagedRuntime.make(appLayer);
8437
9062
  runtime = rt;
8438
9063
  const [core, parser, extractor] = await rt.runPromise(Effect50.all([Service, Service19, Service12]));
8439
- return makeFacade(rt, core, parser, extractor, () => Effect50.runPromise(Scope5.close(scope, Exit7.void).pipe(Effect50.andThen(rt.disposeEffect))));
9064
+ return {
9065
+ runPromise: (effect) => rt.runPromise(effect),
9066
+ disposeEffect: rt.disposeEffect,
9067
+ core,
9068
+ parser,
9069
+ extractor,
9070
+ dispose: () => Effect50.runPromise(Scope5.close(scope, Exit7.void).pipe(Effect50.andThen(rt.disposeEffect)))
9071
+ };
8440
9072
  } catch (cause) {
8441
9073
  await Effect50.runPromise(Scope5.close(scope, Exit7.void).pipe(Effect50.andThen(runtime?.disposeEffect ?? Effect50.void))).catch(() => {
8442
9074
  return;
8443
9075
  });
8444
- return Promise.reject(remapError(cause));
9076
+ return Promise.reject(cause);
8445
9077
  }
8446
9078
  };
9079
+ var makeDimple = async (options) => {
9080
+ const configSource = "config" in options ? options.config : undefined;
9081
+ const migrationFolder = "config" in options ? options.migrationFolder : undefined;
9082
+ let currentConfig;
9083
+ if (configSource !== undefined) {
9084
+ currentConfig = await Effect50.runPromise(resolveConfigInput(configSource)).catch((cause) => Promise.reject(remapError(cause)));
9085
+ }
9086
+ let current = await bootStack(configSource !== undefined && currentConfig !== undefined ? {
9087
+ config: currentConfig,
9088
+ ...migrationFolder !== undefined ? { migrationFolder } : {}
9089
+ } : options).then((stack) => stack, (cause) => Promise.reject(remapError(cause)));
9090
+ let disposed = false;
9091
+ let gate = Promise.resolve();
9092
+ let releaseGate;
9093
+ let inflight = 0;
9094
+ let drainWaiters = [];
9095
+ let reloadChain = Promise.resolve();
9096
+ const run = (effect) => {
9097
+ if (disposed) {
9098
+ return Promise.reject(new StoreFailure({ message: "SDK disposed" }));
9099
+ }
9100
+ return gate.then(() => {
9101
+ if (disposed) {
9102
+ return Promise.reject(new StoreFailure({ message: "SDK disposed" }));
9103
+ }
9104
+ const stack = current;
9105
+ inflight += 1;
9106
+ return stack.runPromise(effect.pipe(Effect50.mapError(remapError))).catch((cause) => {
9107
+ return Promise.reject(remapError(cause));
9108
+ }).finally(() => {
9109
+ inflight -= 1;
9110
+ if (inflight === 0 && drainWaiters.length > 0) {
9111
+ const waiters = drainWaiters;
9112
+ drainWaiters = [];
9113
+ for (const wake of waiters)
9114
+ wake();
9115
+ }
9116
+ });
9117
+ });
9118
+ };
9119
+ const pause = () => {
9120
+ gate = new Promise((resolve2) => {
9121
+ releaseGate = resolve2;
9122
+ });
9123
+ };
9124
+ const resume = () => {
9125
+ const release = releaseGate;
9126
+ releaseGate = undefined;
9127
+ gate = Promise.resolve();
9128
+ release?.();
9129
+ };
9130
+ const drain = () => {
9131
+ if (inflight === 0)
9132
+ return Promise.resolve();
9133
+ return new Promise((resolve2) => {
9134
+ drainWaiters.push(resolve2);
9135
+ });
9136
+ };
9137
+ const drainWithTimeout = () => new Promise((resolve2, reject) => {
9138
+ const timer = setTimeout(() => {
9139
+ reject(new ReloadError({
9140
+ message: `reload: drain timeout after ${RELOAD_DRAIN_MS}ms — in-flight calls still running`
9141
+ }));
9142
+ }, RELOAD_DRAIN_MS);
9143
+ drain().then(() => {
9144
+ clearTimeout(timer);
9145
+ resolve2();
9146
+ }, (cause) => {
9147
+ clearTimeout(timer);
9148
+ reject(cause);
9149
+ });
9150
+ });
9151
+ const reload = (input) => {
9152
+ const task = reloadChain.then(async () => {
9153
+ if (disposed) {
9154
+ return Promise.reject(new ReloadError({ message: "SDK disposed — reload unavailable" }));
9155
+ }
9156
+ if (configSource === undefined) {
9157
+ return Promise.reject(new ReloadError({
9158
+ message: "reload: no config source — this dimple was built from `core` options"
9159
+ }));
9160
+ }
9161
+ const nextInput = input ?? configSource;
9162
+ const resolved = await Effect50.runPromise(resolveConfigInput(nextInput));
9163
+ if (currentConfig !== undefined && stableKey(resolved) === stableKey(currentConfig)) {
9164
+ return {
9165
+ config: resolved,
9166
+ changed: { storeUrl: false, jobsUrl: false, rest: false },
9167
+ reloaded: false
9168
+ };
9169
+ }
9170
+ const changes = diffConfig(currentConfig, resolved);
9171
+ pause();
9172
+ try {
9173
+ const next = await bootStack({
9174
+ config: resolved,
9175
+ ...migrationFolder !== undefined ? { migrationFolder } : {}
9176
+ });
9177
+ await drainWithTimeout();
9178
+ const oldStoreUrl = currentConfig?.storeUrl;
9179
+ const oldJobsUrl = currentConfig?.jobs?.dbUrl;
9180
+ const old = current;
9181
+ current = next;
9182
+ currentConfig = resolved;
9183
+ await old.dispose();
9184
+ if (changes.storeUrl) {
9185
+ await Effect50.runPromise(Effect50.logWarning(`reload: store database switched "${oldStoreUrl}" → "${resolved.storeUrl}" — ` + "the old db is preserved but no longer served (nothing was migrated)").pipe(Effect50.annotateLogs({ scope: "@dimple/sdk", event: "reload.store.switch" })));
9186
+ }
9187
+ if (changes.jobsUrl) {
9188
+ await Effect50.runPromise(Effect50.logWarning(`reload: jobs database switched "${oldJobsUrl}" → "${resolved.jobs?.dbUrl}" — ` + "pending jobs remain durable in the old db").pipe(Effect50.annotateLogs({ scope: "@dimple/sdk", event: "reload.jobs.switch" })));
9189
+ }
9190
+ return { config: resolved, changed: changes, reloaded: true };
9191
+ } finally {
9192
+ resume();
9193
+ }
9194
+ });
9195
+ reloadChain = task.catch(() => {
9196
+ return;
9197
+ });
9198
+ return task.then((config) => config, (cause) => Promise.reject(remapError(cause)));
9199
+ };
9200
+ const dispose = () => {
9201
+ disposed = true;
9202
+ return reloadChain.then(() => current.dispose());
9203
+ };
9204
+ return makeFacade({
9205
+ run,
9206
+ core: () => current.core,
9207
+ parser: () => current.parser,
9208
+ extractor: () => current.extractor
9209
+ }, reload, dispose);
9210
+ };
8447
9211
  export {
8448
9212
  remapError,
8449
9213
  makeDimple,
8450
9214
  StoreFailure,
9215
+ ReloadError,
8451
9216
  NotFoundError,
8452
9217
  LLMNotConfiguredError,
8453
9218
  JobFailedError,
8454
9219
  InputError,
9220
+ EmbeddingFailure,
9221
+ EdgeNotFoundError,
8455
9222
  DuplicateKeyError,
8456
9223
  DuplicateEdgeError,
8457
9224
  ConfigError