@ponraaj/dimple-sdk 0.0.0-alpha.4 → 0.0.0-alpha.6

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,17 @@ 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 fs = await import("node:fs");
512
+ const { dirname } = await import("node:path");
513
+ fs.mkdirSync(dirname(path), { recursive: true });
514
+ const { openSync, closeSync, fsyncSync, writeSync } = fs;
508
515
  const fd = openSync(path, "a");
509
516
  let disposed = false;
510
517
  const flush = () => {
@@ -537,11 +544,15 @@ var fileSink = (path) => {
537
544
  closeSync(fd);
538
545
  }
539
546
  };
540
- };
547
+ });
541
548
 
542
549
  class Service3 extends Context3.Service()("@dimple/logger/Logging") {
543
550
  }
544
- var make = (config) => {
551
+ var makeEffect = (config) => Effect5.gen(function* () {
552
+ const file = config.file === undefined ? undefined : yield* fileSink(config.file);
553
+ const sinks = [consoleSink((config.format ?? "jsonl") === "pretty")];
554
+ if (file !== undefined)
555
+ sinks.push(file);
545
556
  const level = config.level ?? "info";
546
557
  const floor = LEVEL_ORDER[level];
547
558
  const overrides = {};
@@ -551,9 +562,6 @@ var make = (config) => {
551
562
  overrides[scope] = order;
552
563
  }
553
564
  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
565
  return Service3.of({
558
566
  ring,
559
567
  emit: (entry) => {
@@ -572,7 +580,7 @@ var make = (config) => {
572
580
  sink.dispose();
573
581
  }
574
582
  });
575
- };
583
+ });
576
584
  var LEVEL_MAP = {
577
585
  FATAL: "error",
578
586
  ERROR: "error",
@@ -637,7 +645,7 @@ var EFFECT_LEVEL = {
637
645
  };
638
646
  var minLevelLayer = (minLevel) => Layer.succeed(References.MinimumLogLevel, minLevel === "None" ? "None" : EFFECT_LEVEL[minLevel]);
639
647
  var layer = (config) => {
640
- const loggingLayer = Layer.effect(Service3, Effect5.acquireRelease(Effect5.sync(() => make(config)), (logging) => Effect5.sync(() => logging.dispose())));
648
+ const loggingLayer = Layer.effect(Service3, Effect5.acquireRelease(makeEffect(config), (logging) => Effect5.sync(() => logging.dispose())));
641
649
  const minLevel = config.enabled === false ? "None" : lowestLevel(config);
642
650
  const withLogger = Layer.provideMerge(loggingLayer)(loggerReplacement());
643
651
  return Layer.provideMerge(minLevelLayer(minLevel))(withLogger);
@@ -688,7 +696,8 @@ var EmbeddingModelConfig = Schema6.Struct({
688
696
  ])),
689
697
  pooling: Schema6.optional(Schema6.Literals(["mean", "cls", "none"])),
690
698
  normalize: Schema6.optional(Schema6.Boolean),
691
- cacheDir: Schema6.optional(Schema6.String)
699
+ cacheDir: Schema6.optional(Schema6.String),
700
+ autoInstall: Schema6.optional(Schema6.Boolean)
692
701
  });
693
702
  var EmbeddingConfig = Schema6.Struct({
694
703
  defaultModel: Schema6.String,
@@ -755,8 +764,6 @@ import { Context as Context4, Effect as Effect8, Layer as Layer2 } from "effect"
755
764
  // ../config/src/loader.ts
756
765
  import { Effect as Effect7, Schema as Schema7 } from "effect";
757
766
  import { parse, parseTree, printParseErrorCode } from "jsonc-parser";
758
- import * as fs from "node:fs";
759
- import * as path from "node:path";
760
767
  var CONFIG_FILE_NAMES = ["dimple.jsonc", "dimple.json"];
761
768
  var GLOBAL_DIR = ".config/dimple";
762
769
  var defaults = {
@@ -856,11 +863,14 @@ var parseJsoncEffect = (text4, formatError) => {
856
863
  return Effect7.fail(formatError(result.error));
857
864
  return Effect7.succeed(result.value);
858
865
  };
859
- var readFile = (filePath) => Effect7.try({
860
- try: () => fs.readFileSync(filePath, "utf-8"),
866
+ var readFile = (filePath) => Effect7.tryPromise({
867
+ try: async () => (await import("node:fs")).promises.readFile(filePath, "utf8"),
861
868
  catch: (cause) => new Error(`failed to read config file ${filePath}: ${cause instanceof Error ? cause.message : String(cause)}`)
862
869
  }).pipe(Effect7.flatMap((content) => parseJsoncEffect(content, (cause) => new Error(`failed to read config file ${filePath}: ${cause.message}`))));
863
- var findProjectConfig = () => {
870
+ var loadFs = () => Effect7.tryPromise(() => import("node:fs"));
871
+ var findProjectConfig = () => Effect7.gen(function* () {
872
+ const fs = yield* loadFs();
873
+ const path = yield* Effect7.tryPromise(() => import("node:path"));
864
874
  let dir = process.cwd();
865
875
  for (let i = 0;i < 20; i++) {
866
876
  for (const name of CONFIG_FILE_NAMES) {
@@ -874,37 +884,40 @@ var findProjectConfig = () => {
874
884
  dir = parent;
875
885
  }
876
886
  return;
877
- };
878
- var getGlobalConfigPath = () => {
887
+ });
888
+ var getGlobalConfigPath = () => Effect7.gen(function* () {
879
889
  const home = process.env["HOME"] ?? process.env["USERPROFILE"];
880
890
  if (!home)
881
891
  return;
892
+ const fs = yield* loadFs();
893
+ const path = yield* Effect7.tryPromise(() => import("node:path"));
882
894
  for (const name of CONFIG_FILE_NAMES) {
883
895
  const p = path.join(home, GLOBAL_DIR, name);
884
896
  if (fs.existsSync(p))
885
897
  return p;
886
898
  }
887
899
  return;
888
- };
889
- var loadFromFiles = () => {
900
+ });
901
+ var loadFromFiles = () => Effect7.gen(function* () {
890
902
  const sources = [];
891
- const globalPath = getGlobalConfigPath();
903
+ const globalPath = yield* getGlobalConfigPath();
892
904
  if (globalPath)
893
905
  sources.push({ path: globalPath });
894
- const projectPath = findProjectConfig();
906
+ const projectPath = yield* findProjectConfig();
895
907
  if (projectPath)
896
908
  sources.push({ path: projectPath });
897
909
  const envPath = process.env["DIMPLE_CONFIG"];
898
910
  if (envPath) {
911
+ const fs = yield* loadFs();
899
912
  if (!fs.existsSync(envPath)) {
900
- return Effect7.fail(new Error(`DIMPLE_CONFIG path not found: ${envPath}`));
913
+ return yield* Effect7.fail(new Error(`DIMPLE_CONFIG path not found: ${envPath}`));
901
914
  }
902
915
  sources.push({ path: envPath });
903
916
  }
904
917
  if (sources.length === 0)
905
- return Effect7.succeed([]);
906
- return Effect7.forEach(sources, (s) => readFile(s.path));
907
- };
918
+ return [];
919
+ return yield* Effect7.forEach(sources, (s) => readFile(s.path));
920
+ });
908
921
  var loadFromEnvContent = () => {
909
922
  const content = process.env["DIMPLE_CONFIG_CONTENT"];
910
923
  if (!content)
@@ -935,15 +948,16 @@ var deepMerge = (base, over) => {
935
948
  }
936
949
  return out;
937
950
  };
938
- var loadConfigFromFile = (path2) => Effect7.gen(function* () {
951
+ var loadConfigFromFile = (path) => Effect7.gen(function* () {
952
+ const fs = yield* loadFs();
939
953
  const content = yield* Effect7.tryPromise({
940
- try: () => fs.promises.readFile(path2, "utf8"),
941
- catch: (cause) => new Error(`config read ${path2}: ${cause}`)
954
+ try: () => fs.promises.readFile(path, "utf8"),
955
+ catch: (cause) => new Error(`config read ${path}: ${cause}`)
942
956
  });
943
- const parsed = yield* parseJsoncEffect(content, (cause) => new Error(`config parse ${path2}: ${cause.message}`));
957
+ const parsed = yield* parseJsoncEffect(content, (cause) => new Error(`config parse ${path}: ${cause.message}`));
944
958
  const merged = deepMerge(defaults, parsed);
945
959
  const resolved = resolveEnvVars(merged);
946
- return yield* Schema7.decodeUnknownEffect(DimpleConfig)(resolved).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
960
+ return yield* Schema7.decodeUnknownEffect(DimpleConfig, { errors: "all" })(resolved).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
947
961
  });
948
962
  var loadConfig = (overrides) => Effect7.gen(function* () {
949
963
  const fileParts = yield* loadFromFiles();
@@ -956,7 +970,7 @@ var loadConfig = (overrides) => Effect7.gen(function* () {
956
970
  if (overrides)
957
971
  merged = deepMerge(merged, overrides);
958
972
  const resolved = resolveEnvVars(merged);
959
- return yield* Schema7.decodeUnknownEffect(DimpleConfig)(resolved).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
973
+ return yield* Schema7.decodeUnknownEffect(DimpleConfig, { errors: "all" })(resolved).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
960
974
  });
961
975
 
962
976
  // ../config/src/service.ts
@@ -1049,9 +1063,6 @@ var runEmbeddedMigrations = async (client) => {
1049
1063
  }
1050
1064
  };
1051
1065
  var makeClientDb = (url) => {
1052
- if (url.startsWith("file:")) {
1053
- fs2.mkdirSync(dirname2(url.slice("file:".length)), { recursive: true });
1054
- }
1055
1066
  const client = createClient({ url });
1056
1067
  const db = drizzle({ client });
1057
1068
  const rawTransaction = client.transaction.bind(client);
@@ -1114,25 +1125,29 @@ var makeClientDb = (url) => {
1114
1125
  };
1115
1126
  return { client, db };
1116
1127
  };
1128
+ var ensureFileDir = (url) => url.startsWith("file:") ? import("node:fs").then((fs) => import("node:path").then(({ dirname }) => {
1129
+ fs.mkdirSync(dirname(url.slice("file:".length)), { recursive: true });
1130
+ })) : Promise.resolve();
1117
1131
  var acquireMigrationLock = async (url) => {
1118
1132
  if (!url.startsWith("file:"))
1119
1133
  return;
1134
+ const fs = await import("node:fs");
1120
1135
  const dbPath = url.slice("file:".length);
1121
1136
  const lockPath = `${dbPath}.migrate-lock`;
1122
1137
  const deadline = Date.now() + 30000;
1123
1138
  for (;; ) {
1124
1139
  try {
1125
- const fd = fs2.openSync(lockPath, "wx");
1140
+ const fd = fs.openSync(lockPath, "wx");
1126
1141
  return () => {
1127
1142
  try {
1128
- fs2.closeSync(fd);
1129
- fs2.unlinkSync(lockPath);
1143
+ fs.closeSync(fd);
1144
+ fs.unlinkSync(lockPath);
1130
1145
  } catch {}
1131
1146
  };
1132
1147
  } catch {
1133
1148
  try {
1134
- if (Date.now() - fs2.statSync(lockPath).mtimeMs > 5 * 60000)
1135
- fs2.unlinkSync(lockPath);
1149
+ if (Date.now() - fs.statSync(lockPath).mtimeMs > 300000)
1150
+ fs.unlinkSync(lockPath);
1136
1151
  } catch {}
1137
1152
  if (Date.now() > deadline) {
1138
1153
  return Promise.reject(new Error(`timed out waiting for migration lock ${lockPath}`));
@@ -1142,6 +1157,10 @@ var acquireMigrationLock = async (url) => {
1142
1157
  }
1143
1158
  };
1144
1159
  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 = ?)` : "";
1160
+ var contentTypeFilter = (contentType) => {
1161
+ const kinds = typeof contentType === "string" ? [contentType] : contentType ?? [];
1162
+ return kinds.length === 0 ? "" : ` AND m.content_type IN (${kinds.map(() => "?").join(", ")})`;
1163
+ };
1145
1164
  var makeBackend = (url, client, db, migrationFolder) => ({
1146
1165
  listIsolatedMemories: (limit, modelKey) => Effect9.tryPromise({
1147
1166
  try: () => client.execute({
@@ -1177,6 +1196,7 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1177
1196
  try: async () => {
1178
1197
  try {
1179
1198
  if (migrationFolder !== undefined) {
1199
+ const { migrate } = await import("drizzle-orm/libsql/migrator");
1180
1200
  await migrate(db, { migrationsFolder: migrationFolder });
1181
1201
  } else {
1182
1202
  await runEmbeddedMigrations(client);
@@ -1207,25 +1227,25 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1207
1227
  topics,
1208
1228
  artifactEdges
1209
1229
  },
1210
- ftsSearch: (query, namespace, limit, topicKeys, modelKey) => Effect9.tryPromise({
1230
+ ftsSearch: (query, namespace, limit, topicKeys, modelKey, contentType) => Effect9.tryPromise({
1211
1231
  try: () => client.execute({
1212
1232
  sql: `SELECT m.*, f.rank
1213
1233
  FROM memories_fts f
1214
1234
  JOIN memories m ON m.rowid = f.rowid
1215
- WHERE f.content MATCH ? AND m.namespace = ? AND m.state = 'active'${topicFilter(topicKeys)}
1235
+ WHERE f.content MATCH ? AND m.namespace = ? AND m.state = 'active'${topicFilter(topicKeys)}${contentTypeFilter(contentType)}
1216
1236
  ORDER BY f.rank
1217
1237
  LIMIT ?`,
1218
- args: topicKeys && topicKeys.length > 0 ? [
1238
+ args: [
1219
1239
  `"${query.replaceAll('"', '""')}"`,
1220
1240
  namespace,
1221
- ...topicKeys,
1222
- modelKey ?? "",
1241
+ ...topicKeys && topicKeys.length > 0 ? [...topicKeys, modelKey ?? ""] : [],
1242
+ ...typeof contentType === "string" ? [contentType] : contentType ?? [],
1223
1243
  limit
1224
- ] : [`"${query.replaceAll('"', '""')}"`, namespace, limit]
1244
+ ]
1225
1245
  }).then((rs) => rs.rows),
1226
1246
  catch: (cause) => new StoreError({ message: `DbBackend.ftsSearch: ${cause}` })
1227
1247
  }),
1228
- vectorSearch: (query, namespace, limit, model, topicKeys) => Effect9.gen(function* () {
1248
+ vectorSearch: (query, namespace, limit, model, topicKeys, contentType) => Effect9.gen(function* () {
1229
1249
  for (const n of query) {
1230
1250
  if (!Number.isFinite(n)) {
1231
1251
  yield* new StoreError({
@@ -1236,7 +1256,13 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1236
1256
  const tableName = vectorTableName(model);
1237
1257
  const vecJson = JSON.stringify([...query]);
1238
1258
  const convertFn = VECTOR_CONVERT_FNS[model.vectorType ?? "float32"] ?? "vector32";
1239
- const args = topicKeys && topicKeys.length > 0 ? [vecJson, namespace, ...topicKeys, model.id, limit] : [vecJson, namespace, limit];
1259
+ const args = [
1260
+ vecJson,
1261
+ namespace,
1262
+ ...topicKeys && topicKeys.length > 0 ? [...topicKeys, model.id] : [],
1263
+ ...typeof contentType === "string" ? [contentType] : contentType ?? [],
1264
+ limit
1265
+ ];
1240
1266
  const rows = yield* Effect9.tryPromise({
1241
1267
  try: () => client.execute({
1242
1268
  sql: `
@@ -1244,7 +1270,7 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1244
1270
  vector_distance_cos(v.embedding, ${convertFn}(?)) AS distance
1245
1271
  FROM memories m
1246
1272
  JOIN ${tableName} v ON v.memory_id = m.id
1247
- WHERE m.namespace = ? AND m.state = 'active'${topicFilter(topicKeys)}
1273
+ WHERE m.namespace = ? AND m.state = 'active'${topicFilter(topicKeys)}${contentTypeFilter(contentType)}
1248
1274
  ORDER BY distance
1249
1275
  LIMIT ?
1250
1276
  `,
@@ -1282,6 +1308,23 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1282
1308
  }),
1283
1309
  catch: (cause) => new StoreError({ message: `DbBackend.sweep: ${cause}` })
1284
1310
  }),
1311
+ listSchema: () => Effect9.tryPromise({
1312
+ try: async () => {
1313
+ const tables = (await client.execute({
1314
+ sql: `SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name`
1315
+ })).rows;
1316
+ const out = [];
1317
+ for (const t of tables) {
1318
+ const cols = (await client.execute({ sql: `PRAGMA table_info(${t.name})` })).rows;
1319
+ out.push({
1320
+ table: t.name,
1321
+ columns: cols.map((c) => ({ name: c.name, type: c.type }))
1322
+ });
1323
+ }
1324
+ return out;
1325
+ },
1326
+ catch: (cause) => new StoreError({ message: `DbBackend.listSchema: ${cause}` })
1327
+ }),
1285
1328
  writeAuditRows: (rows) => rows.length === 0 ? Effect9.void : Effect9.tryPromise({
1286
1329
  try: () => db.insert(retrievalLog).values([...rows]),
1287
1330
  catch: (cause) => new StoreError({ message: `DbBackend.writeAuditRows: ${cause}` })
@@ -1330,10 +1373,12 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1330
1373
  const maxTotal = opts?.maxTotal ?? 80;
1331
1374
  const includePending = opts?.includePending ?? true;
1332
1375
  const modelKey = opts?.modelKey;
1333
- const statusClause = includePending ? `AND e.status IN ('active', 'confirmed', 'pending')` : `AND e.status IN ('active', 'confirmed')`;
1376
+ 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')`;
1334
1377
  const modelClause = modelKey === undefined ? "" : `AND (e.from_kind <> 'topic'
1335
1378
  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 = ?)
1336
1379
  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 = ?))`;
1380
+ const kinds = opts?.edgeKinds;
1381
+ const kindClause = kinds === undefined || kinds.length === 0 ? "" : `AND e.edge_kind IN (${kinds.map(() => "?").join(",")})`;
1337
1382
  const results = [];
1338
1383
  const seenEdges = new Set;
1339
1384
  const visited = new Set([`${kind}:${id}`]);
@@ -1346,6 +1391,8 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1346
1391
  args.push(node.id, node.kind, node.id, node.kind);
1347
1392
  if (modelKey !== undefined)
1348
1393
  args.push(modelKey, modelKey);
1394
+ if (kinds !== undefined && kinds.length > 0)
1395
+ args.push(...kinds);
1349
1396
  const rows = yield* Effect9.tryPromise({
1350
1397
  try: () => client.execute({
1351
1398
  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
@@ -1353,6 +1400,7 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1353
1400
  WHERE (${touches})
1354
1401
  ${statusClause}
1355
1402
  ${modelClause}
1403
+ ${kindClause}
1356
1404
  ORDER BY e.from_id, e.edge_kind, e.to_id, e.to_kind`,
1357
1405
  args
1358
1406
  }).then((rs) => rs.rows),
@@ -1524,13 +1572,20 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1524
1572
  })
1525
1573
  });
1526
1574
  var layer3 = (url, options) => {
1527
- const effect = options?.releaseClient === true ? Effect9.acquireRelease(Effect9.sync(() => makeClientDb(url)), ({ client }) => Effect9.tryPromise({
1575
+ const effect = options?.releaseClient === true ? Effect9.acquireRelease(Effect9.tryPromise({
1576
+ try: () => ensureFileDir(url).then(() => makeClientDb(url)),
1577
+ catch: (cause) => new StoreError({ message: `DbBackend.client: ${cause}` })
1578
+ }), ({ client }) => Effect9.tryPromise({
1528
1579
  try: async () => client.close(),
1529
1580
  catch: () => new Error("DbBackend.close failed")
1530
1581
  }).pipe(Effect9.ignore)).pipe(Effect9.flatMap(({ client, db }) => {
1531
1582
  const backend = Service2.of(makeBackend(url, client, db, options?.migrationFolder));
1532
1583
  return backend.runMigrations().pipe(Effect9.as(backend));
1533
1584
  })) : Effect9.gen(function* () {
1585
+ yield* Effect9.tryPromise({
1586
+ try: () => ensureFileDir(url),
1587
+ catch: (cause) => new StoreError({ message: `DbBackend.client: ${cause}` })
1588
+ });
1534
1589
  const { client, db } = yield* Effect9.sync(() => makeClientDb(url));
1535
1590
  const backend = Service2.of(makeBackend(url, client, db, options?.migrationFolder));
1536
1591
  yield* backend.runMigrations();
@@ -1557,6 +1612,7 @@ var MemoryMetadata = Schema8.Struct({
1557
1612
  confidence: Schema8.Number,
1558
1613
  affordances: Schema8.Array(Schema8.String),
1559
1614
  language: Schema8.optional(Schema8.String),
1615
+ codeVersion: Schema8.optional(Schema8.String),
1560
1616
  pinned: Schema8.optional(Schema8.Boolean)
1561
1617
  });
1562
1618
  var parseMetadata = (blob) => {
@@ -2423,7 +2479,6 @@ import {
2423
2479
  } from "effect";
2424
2480
  import { PersistedQueue } from "effect/unstable/persistence";
2425
2481
  import { SqlClient } from "effect/unstable/sql";
2426
- import { randomUUID } from "node:crypto";
2427
2482
 
2428
2483
  class QueueKey extends Data.Class {
2429
2484
  }
@@ -2434,7 +2489,7 @@ var makeStore = Effect15.fnUntraced(function* (options) {
2434
2489
  const pollInterval = Duration.max(options?.pollInterval ? Duration.fromInputUnsafe(options.pollInterval) : Duration.millis(1000), Duration.millis(1));
2435
2490
  const lockRefreshInterval = Duration.max(options?.lockRefreshInterval ? Duration.fromInputUnsafe(options.lockRefreshInterval) : Duration.seconds(30), Duration.millis(1));
2436
2491
  const lockExpiration = Duration.max(options?.lockExpiration ? Duration.fromInputUnsafe(options.lockExpiration) : Duration.minutes(2), Duration.millis(1));
2437
- const workerId = randomUUID();
2492
+ const workerId = globalThis.crypto.randomUUID();
2438
2493
  const sqlNow = sql3.literal("CURRENT_TIMESTAMP");
2439
2494
  const expiresAt = sql3.literal(`datetime(CURRENT_TIMESTAMP, '-${Math.ceil(Duration.toSeconds(lockExpiration))} seconds')`);
2440
2495
  const workerIdSql = sql3.literal(`'${workerId}'`);
@@ -2574,7 +2629,7 @@ var storeSqliteLayer = Layer4.effect(PersistedQueue.PersistedQueueStore, makeSto
2574
2629
  // ../../node_modules/.bun/@effect+sql-libsql@4.0.0-beta.103+10c4c6971de0179f/node_modules/@effect/sql-libsql/dist/LibsqlClient.js
2575
2630
  var exports_LibsqlClient = {};
2576
2631
  __export(exports_LibsqlClient, {
2577
- make: () => make5,
2632
+ make: () => make4,
2578
2633
  layerConfig: () => layerConfig2,
2579
2634
  layer: () => layer5,
2580
2635
  TypeId: () => TypeId,
@@ -2603,7 +2658,7 @@ var classifyError = (cause, message, operation) => classifySqliteError(cause, {
2603
2658
  var TypeId = "~@effect/sql-libsql/LibsqlClient";
2604
2659
  var LibsqlClient = /* @__PURE__ */ Context6.Service("@effect/sql-libsql/LibsqlClient");
2605
2660
  var LibsqlTransaction = /* @__PURE__ */ Context6.Service("@effect/sql-libsql/LibsqlClient/LibsqlTransaction");
2606
- var make5 = (options) => Effect16.gen(function* () {
2661
+ var make4 = (options) => Effect16.gen(function* () {
2607
2662
  const compiler = Statement.makeCompilerSqlite(options.transformQueryNames);
2608
2663
  const transformRows = options.transformResultNames ? Statement.defaultTransforms(options.transformResultNames).array : undefined;
2609
2664
  const spanAttributes = [...options.spanAttributes ? Object.entries(options.spanAttributes) : [], [ATTR_DB_SYSTEM_NAME, "sqlite"]];
@@ -2718,11 +2773,9 @@ var make5 = (options) => Effect16.gen(function* () {
2718
2773
  sdk: connection.sdk
2719
2774
  });
2720
2775
  });
2721
- 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));
2722
- 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));
2776
+ 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));
2777
+ 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));
2723
2778
  // ../workflow/src/engine.ts
2724
- import { mkdirSync as mkdirSync2 } from "node:fs";
2725
- import { dirname as dirname3 } from "node:path";
2726
2779
  var Persistence = PersistenceNamespace2.Persistence;
2727
2780
  var PersistedQueueFactory = PersistedQueue2.PersistedQueueFactory;
2728
2781
  var layerDurable = Layer6.effect(WorkflowEngine2.WorkflowEngine)(Effect17.gen(function* () {
@@ -2921,15 +2974,13 @@ var workflowTagFor = (workflows, store, executionId) => Effect17.gen(function* (
2921
2974
  return;
2922
2975
  return workflows.get(record.workflowTag)?.workflow;
2923
2976
  });
2924
- var ensureDbDir = (url) => {
2925
- if (url.startsWith("file:")) {
2926
- mkdirSync2(dirname3(url.slice("file:".length)), { recursive: true });
2927
- }
2928
- };
2929
- var libsqlStack = (options) => {
2930
- ensureDbDir(options.url);
2931
- return Layer6.mergeAll(PersistenceNamespace2.layerSql, PersistedQueue2.layer.pipe(Layer6.provide(storeSqliteLayer))).pipe(Layer6.provide(exports_LibsqlClient.layer(options)));
2932
- };
2977
+ var ensureDbDir = (url) => url.startsWith("file:") ? import("node:fs").then((fs) => import("node:path").then(({ dirname }) => {
2978
+ fs.mkdirSync(dirname(url.slice("file:".length)), { recursive: true });
2979
+ })) : Promise.resolve();
2980
+ var libsqlStack = (options) => Layer6.unwrap(Effect17.tryPromise({
2981
+ try: () => ensureDbDir(options.url),
2982
+ catch: (cause) => new Error(String(cause))
2983
+ }).pipe(Effect17.catch(() => Effect17.void), Effect17.map(() => Layer6.mergeAll(PersistenceNamespace2.layerSql, PersistedQueue2.layer.pipe(Layer6.provide(storeSqliteLayer))).pipe(Layer6.provide(exports_LibsqlClient.layer(options))))));
2933
2984
  var layerDurableLibsql = (options) => layerDurable.pipe(Layer6.provide(libsqlStack(options)));
2934
2985
 
2935
2986
  // ../workflow/src/runtime.ts
@@ -4314,6 +4365,11 @@ var sameLogicalKeyFamily = (a, b) => {
4314
4365
  const fb = familyOf(b);
4315
4366
  return fa !== "" && fa === fb;
4316
4367
  };
4368
+ var sameFileChunkFamily = (a, b) => {
4369
+ if (a === undefined || b === undefined || a === "" || b === "")
4370
+ return false;
4371
+ return a.startsWith(`${b}#`) || b.startsWith(`${a}#`);
4372
+ };
4317
4373
  var topicsOf = (blob) => {
4318
4374
  if (blob === undefined || blob === null)
4319
4375
  return new Set;
@@ -4358,7 +4414,7 @@ var leafLocalCandidates = (newId, newVector, newCreatedAt, newLogicalKey, newMet
4358
4414
  });
4359
4415
  continue;
4360
4416
  }
4361
- if (cosine > bands.nearDuplicate) {
4417
+ if (cosine > bands.nearDuplicate && !sameFileChunkFamily(newLogicalKey, member.logicalKey)) {
4362
4418
  const dir = direction(member, newId, newCreatedAt);
4363
4419
  out.push({
4364
4420
  ...dir,
@@ -4370,7 +4426,7 @@ var leafLocalCandidates = (newId, newVector, newCreatedAt, newLogicalKey, newMet
4370
4426
  });
4371
4427
  continue;
4372
4428
  }
4373
- if (cosine > bands.conflictLow && (sameLogicalKeyFamily(newLogicalKey, member.logicalKey) || sharedMetadataTopics(newMetadata, member.metadata))) {
4429
+ if (cosine > bands.conflictLow && !sameFileChunkFamily(newLogicalKey, member.logicalKey) && (sameLogicalKeyFamily(newLogicalKey, member.logicalKey) || sharedMetadataTopics(newMetadata, member.metadata))) {
4374
4430
  const dir = direction(member, newId, newCreatedAt);
4375
4431
  out.push({
4376
4432
  ...dir,
@@ -4505,7 +4561,12 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4505
4561
  supersedeDemotion: GRAPH_SUPERSEDE_DEMOTION
4506
4562
  });
4507
4563
  });
4508
- const EDGE_KINDS = ["memory_supports", "memory_supersedes", "memory_contradicts"];
4564
+ const EDGE_KINDS = [
4565
+ "memory_supports",
4566
+ "memory_supersedes",
4567
+ "memory_contradicts",
4568
+ "code_calls"
4569
+ ];
4509
4570
  const EDGE_SOURCES = ["rule", "llm", "agent"];
4510
4571
  const write = Effect26.fn("Store.write")(function* (input) {
4511
4572
  if (input.content.includes("\x00")) {
@@ -4524,7 +4585,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4524
4585
  content: input.content,
4525
4586
  contentType: input.contentType ?? "text/plain",
4526
4587
  createdAt: now,
4527
- ttlSecs: input.ttlSecs ?? defaultTtlSecs,
4588
+ ttlSecs: input.ttlSecs === null ? null : input.ttlSecs ?? defaultTtlSecs,
4528
4589
  namespace: input.namespace ?? "default",
4529
4590
  metadata: metadataBlob
4530
4591
  });
@@ -4542,7 +4603,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4542
4603
  contentType: input.contentType ?? "text/plain",
4543
4604
  state: "active",
4544
4605
  createdAt: now,
4545
- ttlSecs: input.ttlSecs ?? defaultTtlSecs,
4606
+ ttlSecs: input.ttlSecs === null ? null : input.ttlSecs ?? defaultTtlSecs,
4546
4607
  namespace: input.namespace ?? "default",
4547
4608
  metadata: metadataBlob ?? undefined,
4548
4609
  embedding: undefined,
@@ -4586,7 +4647,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4586
4647
  });
4587
4648
  const searchFts5 = Effect26.fn("Store.searchFts5")(function* (query, namespace, limit, opts) {
4588
4649
  const start = Date.now();
4589
- const rows = yield* backend.ftsSearch(query, namespace, limit, opts?.topicKeys, modelConfig?.id);
4650
+ const rows = yield* backend.ftsSearch(query, namespace, limit, opts?.topicKeys, modelConfig?.id, opts?.contentType);
4590
4651
  if (opts?.audit !== false) {
4591
4652
  yield* logRetrieval(query, namespace, "fts", rows.length, start);
4592
4653
  }
@@ -4629,7 +4690,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4629
4690
  });
4630
4691
  allowedMemoryIds = new Set(conceptRows.map((r) => r.memoryId));
4631
4692
  }
4632
- const rows = yield* backend.vectorSearch(query, namespace, limit, modelConfig, opts?.topicKeys);
4693
+ const rows = yield* backend.vectorSearch(query, namespace, limit, modelConfig, opts?.topicKeys, opts?.contentType);
4633
4694
  const allowed = allowedMemoryIds;
4634
4695
  const scopedRows = allowed !== undefined ? rows.filter((row) => allowed.has(row.id)) : rows;
4635
4696
  if (opts?.audit !== false) {
@@ -4964,7 +5025,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4964
5025
  });
4965
5026
  return rows.map((row) => row.id);
4966
5027
  });
4967
- const flipToSupersededById = async (tx, memoryId, now) => {
5028
+ const flipToSupersededById = async (tx, memoryId, now, reason = "superseded") => {
4968
5029
  const rs = await tx.update(memories2).set({ state: "superseded" }).where(and8(eq8(memories2.id, memoryId), eq8(memories2.state, "active")));
4969
5030
  if (rs.rowsAffected === 0)
4970
5031
  return null;
@@ -4973,11 +5034,106 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4973
5034
  eventType: "superseded",
4974
5035
  fromState: "active",
4975
5036
  toState: "superseded",
4976
- reason: "superseded",
5037
+ reason,
4977
5038
  createdAt: now
4978
5039
  });
4979
5040
  return memoryId;
4980
5041
  };
5042
+ const reconcile = Effect26.fn("Store.reconcile")(function* (namespace, keyPrefix, keyRoot, walkedRoots, keptKeys) {
5043
+ const inDomain = (key) => {
5044
+ const rel = key.slice(keyPrefix.length).split("#")[0];
5045
+ const filePath = keyRoot === "/" ? `/${rel}` : `${keyRoot}/${rel}`;
5046
+ return walkedRoots.some((root) => filePath === root || filePath.startsWith(`${root}/`));
5047
+ };
5048
+ const owned = (key) => keptKeys.includes(key);
5049
+ const rows = yield* Effect26.tryPromise({
5050
+ try: () => db.select({
5051
+ id: memories2.id,
5052
+ logicalKey: memories2.logicalKey,
5053
+ embedding: memories2.embedding
5054
+ }).from(memories2).where(and8(eq8(memories2.namespace, namespace), eq8(memories2.state, "active"), like(memories2.logicalKey, `${keyPrefix}%`))),
5055
+ catch: (cause) => new StoreError({ message: `Store.reconcile: ${cause}` })
5056
+ });
5057
+ const vanished = rows.filter((r) => inDomain(r.logicalKey) && !owned(r.logicalKey));
5058
+ if (vanished.length > 0) {
5059
+ const now = Date.now();
5060
+ yield* Effect26.tryPromise({
5061
+ try: () => db.transaction(async (tx) => {
5062
+ for (const row of vanished) {
5063
+ await flipToSupersededById(tx, row.id, now, "source_removed");
5064
+ }
5065
+ }),
5066
+ catch: (cause) => new StoreError({ message: `Store.reconcile: ${cause}` })
5067
+ });
5068
+ }
5069
+ return vanished;
5070
+ });
5071
+ const writeSuperseding = Effect26.fn("Store.writeSuperseding")(function* (input) {
5072
+ if (input.content.includes("\x00")) {
5073
+ return yield* new StoreError({
5074
+ message: "Store.writeSuperseding: content contains a NUL byte (libSQL would silently truncate it)"
5075
+ });
5076
+ }
5077
+ const id = crypto.randomUUID();
5078
+ const now = Date.now();
5079
+ const metadataBlob = input.typedMetadata !== undefined ? mergeMetadataBlob(input.metadata, input.typedMetadata) : input.metadata ?? null;
5080
+ const namespace = input.namespace ?? "default";
5081
+ let supersededId;
5082
+ yield* Effect26.tryPromise({
5083
+ try: () => db.transaction(async (tx) => {
5084
+ 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);
5085
+ const old = existing[0];
5086
+ if (old !== undefined) {
5087
+ await flipToSupersededById(tx, old.id, now);
5088
+ supersededId = old.id;
5089
+ }
5090
+ await tx.insert(memories2).values({
5091
+ id,
5092
+ logicalKey: input.logicalKey,
5093
+ content: input.content,
5094
+ contentType: input.contentType ?? "text/plain",
5095
+ createdAt: now,
5096
+ ttlSecs: input.ttlSecs === null ? null : input.ttlSecs ?? defaultTtlSecs,
5097
+ namespace,
5098
+ metadata: metadataBlob
5099
+ });
5100
+ await tx.insert(memoryEvents2).values({ memoryId: id, eventType: "created", createdAt: now });
5101
+ if (old !== undefined) {
5102
+ await tx.insert(artifactEdges).values({
5103
+ id: crypto.randomUUID(),
5104
+ fromMemoryId: old.id,
5105
+ toMemoryId: id,
5106
+ edgeKind: "memory_supersedes",
5107
+ weight: 1,
5108
+ status: "confirmed",
5109
+ source: "rule",
5110
+ confidence: 1,
5111
+ validAt: now,
5112
+ expiredAt: null,
5113
+ createdAt: now,
5114
+ updatedAt: now
5115
+ });
5116
+ }
5117
+ }),
5118
+ catch: (cause) => backend.isUniqueViolation(cause) ? new DuplicateActiveKey({ logicalKey: input.logicalKey, namespace }) : new StoreError({ message: `Store.writeSuperseding: ${cause}` })
5119
+ });
5120
+ return {
5121
+ memory: {
5122
+ id,
5123
+ logicalKey: input.logicalKey,
5124
+ content: input.content,
5125
+ contentType: input.contentType ?? "text/plain",
5126
+ state: "active",
5127
+ createdAt: now,
5128
+ ttlSecs: input.ttlSecs === null ? null : input.ttlSecs ?? defaultTtlSecs,
5129
+ namespace,
5130
+ metadata: metadataBlob ?? undefined,
5131
+ embedding: undefined,
5132
+ embeddingModel: undefined
5133
+ },
5134
+ ...supersededId !== undefined ? { supersededId } : {}
5135
+ };
5136
+ });
4981
5137
  const addEdge = Effect26.fn("Store.addEdge")(function* (input) {
4982
5138
  if (input.fromMemoryId === input.toMemoryId) {
4983
5139
  return yield* new StoreError({ message: "Store.addEdge: self-loops are not allowed" });
@@ -5030,7 +5186,12 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
5030
5186
  updatedAt: now
5031
5187
  };
5032
5188
  yield* Effect26.tryPromise({
5033
- try: () => db.insert(artifactEdges).values(row),
5189
+ try: () => db.transaction(async (tx) => {
5190
+ await tx.insert(artifactEdges).values(row);
5191
+ if (input.confirmed && input.edgeKind === "memory_supersedes") {
5192
+ await flipToSupersededById(tx, input.fromMemoryId, now);
5193
+ }
5194
+ }),
5034
5195
  catch: (cause) => {
5035
5196
  if (backend.isUniqueViolation(cause)) {
5036
5197
  return new EdgeExists({
@@ -5169,6 +5330,42 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
5169
5330
  }),
5170
5331
  catch: (cause) => new StoreError({ message: `Store.writeCandidateEdge: ${cause}` })
5171
5332
  });
5333
+ const reconcileRenameCandidates = Effect26.fn("Store.reconcileRenameCandidates")(function* (vanished, newUnitIds) {
5334
+ const withVectors = vanished.filter((v) => v.embedding !== null);
5335
+ if (withVectors.length === 0 || newUnitIds.length === 0)
5336
+ return 0;
5337
+ const newRows = yield* Effect26.tryPromise({
5338
+ try: () => db.select({ id: memories2.id, embedding: memories2.embedding }).from(memories2).where(and8(inArray4(memories2.id, newUnitIds))),
5339
+ catch: (cause) => new StoreError({ message: `Store.reconcileRenameCandidates: ${cause}` })
5340
+ });
5341
+ const newWithVectors = newRows.filter((r) => r.embedding !== null);
5342
+ if (newWithVectors.length === 0)
5343
+ return 0;
5344
+ const now = Date.now();
5345
+ let inserted = 0;
5346
+ for (const v of withVectors) {
5347
+ const vVec = Float32Array.from(v.embedding);
5348
+ for (const n of newWithVectors) {
5349
+ if (v.id === n.id)
5350
+ continue;
5351
+ const cosine = cosineSimilarity(vVec, Float32Array.from(n.embedding));
5352
+ if (cosine >= GRAPH_CONFLICT_BANDS.nearDuplicate) {
5353
+ const ok = yield* writeCandidateEdge({
5354
+ fromMemoryId: v.id,
5355
+ toMemoryId: n.id,
5356
+ edgeKind: "memory_supersedes",
5357
+ source: "rule",
5358
+ status: "pending",
5359
+ confidence: cosine,
5360
+ reason: "rename"
5361
+ }, now);
5362
+ if (ok)
5363
+ inserted += 1;
5364
+ }
5365
+ }
5366
+ }
5367
+ return inserted;
5368
+ });
5172
5369
  const scanLeafCandidates = (memoryId, vector, topicKey, model) => Effect26.gen(function* () {
5173
5370
  const memberRows = yield* Effect26.tryPromise({
5174
5371
  try: () => db.select({ memoryId: topicMemberships.memoryId }).from(topicMemberships).where(and8(eq8(topicMemberships.topicKey, topicKey), eq8(topicMemberships.modelKey, model.id))).orderBy(asc5(topicMemberships.memoryId)),
@@ -5369,12 +5566,16 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
5369
5566
  });
5370
5567
  return {
5371
5568
  write,
5569
+ writeSuperseding,
5570
+ reconcile,
5571
+ reconcileRenameCandidates,
5372
5572
  get,
5373
5573
  getByKey,
5374
5574
  recency,
5375
5575
  searchFts5,
5376
5576
  searchVector,
5377
5577
  logAudit,
5578
+ listSchema: () => backend.listSchema(),
5378
5579
  supersede,
5379
5580
  forget,
5380
5581
  sweep,
@@ -5508,6 +5709,7 @@ __export(exports_graph, {
5508
5709
  farthestPointSeeds: () => farthestPointSeeds,
5509
5710
  dreamStateDefaults: () => dreamStateDefaults,
5510
5711
  dreamState: () => dreamState,
5712
+ cosineSimilarity: () => cosineSimilarity,
5511
5713
  conceptConfidence: () => conceptConfidence,
5512
5714
  conceptClusterHandler: () => conceptClusterHandler,
5513
5715
  clusterMembers: () => clusterMembers,
@@ -6261,7 +6463,7 @@ var MemoryUnit = Schema20.Struct({
6261
6463
  contentType: Schema20.String,
6262
6464
  state: MemoryState,
6263
6465
  createdAt: Schema20.Number,
6264
- ttlSecs: Schema20.Number,
6466
+ ttlSecs: Schema20.NullOr(Schema20.Number),
6265
6467
  namespace: Schema20.String,
6266
6468
  metadata: Schema20.optional(Schema20.String),
6267
6469
  embedding: Schema20.optional(Schema20.Array(Schema20.Number)),
@@ -6272,7 +6474,7 @@ var WriteMemoryInput = Schema20.Struct({
6272
6474
  content: Schema20.String,
6273
6475
  contentType: Schema20.optional(Schema20.String),
6274
6476
  namespace: Schema20.optional(Schema20.String),
6275
- ttlSecs: Schema20.optional(Schema20.Number),
6477
+ ttlSecs: Schema20.optional(Schema20.NullOr(Schema20.Number)),
6276
6478
  metadata: Schema20.optional(Schema20.String),
6277
6479
  typedMetadata: Schema20.optional(MemoryMetadata)
6278
6480
  });
@@ -6325,7 +6527,103 @@ class Service9 extends Context9.Service()("@dimple/embedder/EmbeddingModelRegist
6325
6527
  }
6326
6528
  // ../embedder/src/transformers.ts
6327
6529
  import { Config as Config5, Effect as Effect32, Layer as Layer11 } from "effect";
6328
- import { pipeline, env } from "@huggingface/transformers";
6530
+ import { existsSync as existsSync2, readFileSync } from "node:fs";
6531
+ import { join as join2 } from "node:path";
6532
+ import { pathToFileURL } from "node:url";
6533
+
6534
+ // ../embedder/src/auto-install.ts
6535
+ import { execFile } from "node:child_process";
6536
+ import { existsSync } from "node:fs";
6537
+ import { join } from "node:path";
6538
+ var detectPackageManager = (cwd) => {
6539
+ let dir = cwd;
6540
+ while (true) {
6541
+ if (existsSync(join(dir, "bun.lock")) || existsSync(join(dir, "bun.lockb"))) {
6542
+ return { pm: "bun", root: dir };
6543
+ }
6544
+ if (existsSync(join(dir, "pnpm-lock.yaml")))
6545
+ return { pm: "pnpm", root: dir };
6546
+ if (existsSync(join(dir, "yarn.lock")))
6547
+ return { pm: "yarn", root: dir };
6548
+ if (existsSync(join(dir, "package-lock.json")))
6549
+ return { pm: "npm", root: dir };
6550
+ const parent = join(dir, "..");
6551
+ if (parent === dir)
6552
+ return { pm: "bun", root: cwd };
6553
+ dir = parent;
6554
+ }
6555
+ };
6556
+ var installCommand = (pm) => {
6557
+ switch (pm) {
6558
+ case "bun":
6559
+ return ["bun", "add", "@huggingface/transformers"];
6560
+ case "npm":
6561
+ return [
6562
+ "npm",
6563
+ "install",
6564
+ "@huggingface/transformers",
6565
+ "--ignore-scripts",
6566
+ "--no-audit",
6567
+ "--no-fund"
6568
+ ];
6569
+ case "pnpm":
6570
+ return ["pnpm", "add", "@huggingface/transformers", "--ignore-scripts"];
6571
+ case "yarn":
6572
+ return ["yarn", "add", "@huggingface/transformers", "--ignore-scripts"];
6573
+ }
6574
+ };
6575
+ var installTransformers = (cwd) => new Promise((resolve, reject) => {
6576
+ const target = detectPackageManager(cwd);
6577
+ const [cmd, ...args] = installCommand(target.pm);
6578
+ execFile(cmd, args, { cwd: target.root, encoding: "utf8" }, (error) => {
6579
+ if (error) {
6580
+ 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"));
6581
+ return;
6582
+ }
6583
+ resolve();
6584
+ });
6585
+ });
6586
+
6587
+ // ../embedder/src/transformers.ts
6588
+ var transformersEntryPath = (root) => {
6589
+ let dir = root;
6590
+ while (true) {
6591
+ const pkgJson = join2(dir, "node_modules", "@huggingface", "transformers", "package.json");
6592
+ if (existsSync2(pkgJson)) {
6593
+ let entry;
6594
+ try {
6595
+ const pkg = JSON.parse(readFileSync(pkgJson, "utf8"));
6596
+ const ex = pkg.exports;
6597
+ const dot4 = typeof ex === "object" ? ex["."] : ex;
6598
+ entry = typeof dot4 === "string" ? dot4 : dot4?.import ?? dot4?.default ?? pkg.module ?? pkg.main ?? "index.js";
6599
+ } catch {
6600
+ entry = "index.js";
6601
+ }
6602
+ return join2(dir, "node_modules", "@huggingface", "transformers", entry);
6603
+ }
6604
+ const parent = join2(dir, "..");
6605
+ if (parent === dir)
6606
+ return null;
6607
+ dir = parent;
6608
+ }
6609
+ };
6610
+ var importFromDisk = () => new Promise((resolve, reject) => {
6611
+ try {
6612
+ const root = detectPackageManager(process.cwd()).root;
6613
+ const entry = transformersEntryPath(root);
6614
+ if (entry === null) {
6615
+ reject(new Error("@huggingface/transformers not found on disk after install"));
6616
+ return;
6617
+ }
6618
+ import(pathToFileURL(entry).href).then((mod) => {
6619
+ resolve(mod);
6620
+ }).catch((cause) => {
6621
+ reject(cause);
6622
+ });
6623
+ } catch (cause) {
6624
+ reject(cause);
6625
+ }
6626
+ });
6329
6627
  var defaults2 = {
6330
6628
  modelId: "Xenova/all-MiniLM-L6-v2",
6331
6629
  device: "cpu",
@@ -6333,14 +6631,31 @@ var defaults2 = {
6333
6631
  pooling: "mean",
6334
6632
  normalize: true
6335
6633
  };
6336
- var make6 = (options) => {
6634
+ var make5 = (options) => {
6337
6635
  let pipe = null;
6338
6636
  const getPipeline = Effect32.fnUntraced(function* () {
6339
6637
  if (pipe)
6340
6638
  return pipe;
6639
+ const loadModule = () => Effect32.tryPromise(() => import("@huggingface/transformers"));
6640
+ let transformers = yield* loadModule().pipe(Effect32.catch(() => {
6641
+ if (options.autoInstall === false) {
6642
+ return Effect32.fail(new EmbeddingError({
6643
+ message: "@huggingface/transformers is not installed — run `bun add @huggingface/transformers` " + "to use local embeddings (or configure a hosted embedding provider)"
6644
+ }));
6645
+ }
6646
+ return Effect32.gen(function* () {
6647
+ yield* Effect32.logInfo("auto-installing @huggingface/transformers (first local-embed use — scripts skipped)").pipe(Effect32.annotateLogs({ scope: "@dimple/embedder", event: "embedder.auto-install" }));
6648
+ yield* Effect32.tryPromise(() => installTransformers(process.cwd())).pipe(Effect32.timeout("5 minutes"), Effect32.catch((cause) => Effect32.fail(new EmbeddingError({
6649
+ 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)}`
6650
+ }))));
6651
+ return yield* Effect32.tryPromise(importFromDisk).pipe(Effect32.mapError(() => new EmbeddingError({
6652
+ message: "@huggingface/transformers still unavailable after auto-install — run `bun add " + "@huggingface/transformers` manually (or set `autoInstall: false` and configure a hosted embedding provider)"
6653
+ })));
6654
+ });
6655
+ }));
6341
6656
  if (options.cacheDir)
6342
- env.cacheDir = options.cacheDir;
6343
- pipe = yield* Effect32.promise(() => pipeline("feature-extraction", options.modelId ?? defaults2.modelId, {
6657
+ transformers.env.cacheDir = options.cacheDir;
6658
+ pipe = yield* Effect32.tryPromise(() => transformers.pipeline("feature-extraction", options.modelId ?? defaults2.modelId, {
6344
6659
  device: options.device ?? defaults2.device,
6345
6660
  dtype: options.dtype ?? defaults2.dtype
6346
6661
  })).pipe(Effect32.mapError((cause) => new EmbeddingError({ message: `load model: ${cause}` })));
@@ -6386,7 +6701,7 @@ var make6 = (options) => {
6386
6701
  return { embed, embedSingle };
6387
6702
  };
6388
6703
  var makeTransformersModel = (options = {}) => {
6389
- const impl = make6(options);
6704
+ const impl = make5(options);
6390
6705
  return {
6391
6706
  modelId: options.modelId ?? "local-minilm",
6392
6707
  provider: "transformers",
@@ -6517,7 +6832,7 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6517
6832
  const namespace = options2?.namespace ?? "default";
6518
6833
  const limit = options2?.limit ?? defaultLimit;
6519
6834
  const k = options2?.k ?? defaultK;
6520
- const profile = typeof options2?.profile === "string" ? {} : options2?.profile ?? {};
6835
+ const profile = typeof options2?.profile === "string" ? options2.profile === "keyword" ? { modeWeights: { vector: 0, fts: 1 } } : options2.profile === "semantic" ? { modeWeights: { vector: 1, fts: 0 } } : {} : options2?.profile ?? {};
6521
6836
  const topicKeys = options2?.topics?.topicKeys;
6522
6837
  const fetchMultiplier = 2;
6523
6838
  const start = Date.now();
@@ -6529,21 +6844,24 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6529
6844
  vectorOpts.conceptIds = profile.conceptFilter;
6530
6845
  const vectorResults = yield* store.searchVector([...embedding], namespace, limit * fetchMultiplier, {
6531
6846
  ...vectorOpts,
6847
+ ...options2?.contentType !== undefined ? { contentType: options2.contentType } : {},
6532
6848
  audit: false
6533
6849
  }).pipe(wrapError("searchVector"));
6534
6850
  const fts5Results = yield* store.searchFts5(query2, namespace, limit * fetchMultiplier, {
6535
6851
  ...topicKeys !== undefined ? { topicKeys } : {},
6852
+ ...options2?.contentType !== undefined ? { contentType: options2.contentType } : {},
6536
6853
  audit: false
6537
6854
  }).pipe(wrapError("searchFts5"));
6538
6855
  const vWeight = profile.modeWeights?.vector ?? 1;
6539
6856
  const fWeight = profile.modeWeights?.fts ?? 1;
6857
+ const mode = vWeight === 0 && fWeight > 0 ? "keyword" : fWeight === 0 && vWeight > 0 ? "semantic" : "hybrid";
6540
6858
  const denseRanks = vectorResults.length > 0 ? vectorResults.map((s) => [s.memory.id, s.score * vWeight]) : null;
6541
6859
  const fts5Ranks = fts5Results.length > 0 ? fts5Results.map((s) => [s.memory.id, s.score * fWeight]) : null;
6542
6860
  if (!denseRanks && !fts5Ranks) {
6543
6861
  yield* store.logAudit({
6544
6862
  query: query2,
6545
6863
  namespace,
6546
- mode: "hybrid",
6864
+ mode,
6547
6865
  resultCount: 0,
6548
6866
  startTime: start,
6549
6867
  queryProfile: JSON.stringify({
@@ -6551,6 +6869,7 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6551
6869
  recencyBoost: profile.recencyBoost ?? 0,
6552
6870
  conceptFilter: profile.conceptFilter,
6553
6871
  topicKeys,
6872
+ contentType: options2?.contentType,
6554
6873
  legs: { vector: { count: 0, topScores: [] }, fts: { count: 0, topScores: [] } }
6555
6874
  }),
6556
6875
  sources: JSON.stringify({ vector: [], fts: [] })
@@ -6593,7 +6912,7 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6593
6912
  yield* store.logAudit({
6594
6913
  query: query2,
6595
6914
  namespace,
6596
- mode: "hybrid",
6915
+ mode,
6597
6916
  resultCount: results.length,
6598
6917
  startTime: start,
6599
6918
  queryProfile: JSON.stringify({
@@ -6601,6 +6920,7 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6601
6920
  recencyBoost: profile.recencyBoost ?? 0,
6602
6921
  conceptFilter: profile.conceptFilter,
6603
6922
  topicKeys,
6923
+ contentType: options2?.contentType,
6604
6924
  legs: {
6605
6925
  vector: {
6606
6926
  count: vectorResults.length,
@@ -6617,9 +6937,10 @@ var layer11 = (options) => Layer13.effect(Service10, Effect35.gen(function* () {
6617
6937
  fts: fts5Results.map((s) => s.memory.id)
6618
6938
  })
6619
6939
  }).pipe(wrapError("logAudit"));
6620
- yield* Effect35.logDebug("hybrid.search").pipe(Effect35.annotateLogs({
6940
+ yield* Effect35.logInfo("hybrid.search").pipe(Effect35.annotateLogs({
6621
6941
  scope: "@dimple/retriever",
6622
6942
  event: "hybrid.search",
6943
+ mode,
6623
6944
  namespace,
6624
6945
  query: query2,
6625
6946
  resultCount: results.length
@@ -6861,7 +7182,8 @@ var embedderFromConfig = (model) => model.provider === "transformers" ? Effect39
6861
7182
  ...model.dtype !== undefined ? { dtype: model.dtype } : {},
6862
7183
  ...model.pooling !== undefined ? { pooling: model.pooling } : {},
6863
7184
  ...model.normalize !== undefined ? { normalize: model.normalize } : {},
6864
- ...model.cacheDir !== undefined ? { cacheDir: model.cacheDir } : {}
7185
+ ...model.cacheDir !== undefined ? { cacheDir: model.cacheDir } : {},
7186
+ ...model.autoInstall !== undefined ? { autoInstall: model.autoInstall } : {}
6865
7187
  })) : model.provider === "openai" ? model.apiKey === undefined ? Effect39.fail(new EmbeddingError({ message: `OpenAI model ${model.id} requires an apiKey` })) : Effect39.succeed(makeHostedEmbeddingModel({
6866
7188
  modelId: model.id,
6867
7189
  model: model.model,
@@ -7042,7 +7364,8 @@ var ExtractedChunk = Schema25.Struct({
7042
7364
  startLine: Schema25.Number,
7043
7365
  endLine: Schema25.Number,
7044
7366
  language: Schema25.String,
7045
- docstring: Schema25.optional(Schema25.String)
7367
+ docstring: Schema25.optional(Schema25.String),
7368
+ calls: Schema25.optional(Schema25.Array(Schema25.Struct({ name: Schema25.String, line: Schema25.Number })))
7046
7369
  });
7047
7370
  var ImportInfo = Schema25.Struct({
7048
7371
  source: Schema25.String,
@@ -7126,6 +7449,7 @@ function emit(source, semantic, filePath, lang, chunks) {
7126
7449
  }
7127
7450
  function makeChunk(source, _semantic, filePath, lang, kind, name, docstring, parentClass) {
7128
7451
  const id = parentClass ? `${filePath}:${parentClass}.${name}:${source.startPosition.row + 1}` : `${filePath}:${name}:${source.startPosition.row + 1}`;
7452
+ const calls = callsOf(source);
7129
7453
  return {
7130
7454
  id,
7131
7455
  filePath,
@@ -7135,8 +7459,48 @@ function makeChunk(source, _semantic, filePath, lang, kind, name, docstring, par
7135
7459
  startLine: source.startPosition.row + 1,
7136
7460
  endLine: source.endPosition.row + 1,
7137
7461
  language: lang,
7138
- docstring
7462
+ docstring,
7463
+ ...calls.length > 0 ? { calls } : {}
7464
+ };
7465
+ }
7466
+ function callsOf(node) {
7467
+ const out = [];
7468
+ const calleeName = (n) => {
7469
+ let cur = n;
7470
+ for (let guard = 0;guard < 4; guard++) {
7471
+ if (cur.type === "identifier")
7472
+ return cur.text;
7473
+ if (cur.type === "member_expression") {
7474
+ const prop = cur.childForFieldName("property");
7475
+ if (prop === null)
7476
+ return;
7477
+ cur = prop;
7478
+ continue;
7479
+ }
7480
+ if (cur.type === "non_null_expression" || cur.type === "parenthesized_expression") {
7481
+ const inner = cur.namedChildren[0];
7482
+ if (inner === undefined)
7483
+ return;
7484
+ cur = inner;
7485
+ continue;
7486
+ }
7487
+ return;
7488
+ }
7489
+ return;
7139
7490
  };
7491
+ const visit = (n) => {
7492
+ if (n.type === "call_expression") {
7493
+ const callee = n.childForFieldName("function");
7494
+ const name = callee === null ? undefined : calleeName(callee);
7495
+ if (name !== undefined && name.length > 0) {
7496
+ out.push({ name, line: n.startPosition.row + 1 });
7497
+ }
7498
+ }
7499
+ for (const child of n.namedChildren)
7500
+ visit(child);
7501
+ };
7502
+ visit(node);
7503
+ return out;
7140
7504
  }
7141
7505
  function unwrap2(node) {
7142
7506
  for (const child of node.namedChildren) {
@@ -7613,11 +7977,11 @@ class Service15 extends Context15.Service()("@dimple/wasm/WasmDownloader") {
7613
7977
  }
7614
7978
  // ../wasm/src/wasm-cache.ts
7615
7979
  import { Context as Context16, Effect as Effect43, Layer as Layer20 } from "effect";
7616
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, renameSync, writeFileSync } from "node:fs";
7617
- import { join as join2 } from "node:path";
7980
+ import { existsSync as existsSync3, mkdirSync, renameSync, writeFileSync } from "node:fs";
7981
+ import { join as join3 } from "node:path";
7618
7982
 
7619
7983
  // ../../node_modules/.bun/env-paths@4.0.0/node_modules/env-paths/index.js
7620
- import path2 from "node:path";
7984
+ import path from "node:path";
7621
7985
  import os from "node:os";
7622
7986
  import process2 from "node:process";
7623
7987
 
@@ -7657,36 +8021,36 @@ function assertSafeFilename(filename) {
7657
8021
  // ../../node_modules/.bun/env-paths@4.0.0/node_modules/env-paths/index.js
7658
8022
  var homedir = os.homedir();
7659
8023
  var tmpdir = os.tmpdir();
7660
- var { env: env2 } = process2;
8024
+ var { env } = process2;
7661
8025
  var macos = (name) => {
7662
- const library = path2.join(homedir, "Library");
8026
+ const library = path.join(homedir, "Library");
7663
8027
  return {
7664
- data: path2.join(library, "Application Support", name),
7665
- config: path2.join(library, "Preferences", name),
7666
- cache: path2.join(library, "Caches", name),
7667
- log: path2.join(library, "Logs", name),
7668
- temp: path2.join(tmpdir, name)
8028
+ data: path.join(library, "Application Support", name),
8029
+ config: path.join(library, "Preferences", name),
8030
+ cache: path.join(library, "Caches", name),
8031
+ log: path.join(library, "Logs", name),
8032
+ temp: path.join(tmpdir, name)
7669
8033
  };
7670
8034
  };
7671
8035
  var windows = (name) => {
7672
- const appData = env2.APPDATA || path2.join(homedir, "AppData", "Roaming");
7673
- const localAppData = env2.LOCALAPPDATA || path2.join(homedir, "AppData", "Local");
8036
+ const appData = env.APPDATA || path.join(homedir, "AppData", "Roaming");
8037
+ const localAppData = env.LOCALAPPDATA || path.join(homedir, "AppData", "Local");
7674
8038
  return {
7675
- data: path2.join(localAppData, name, "Data"),
7676
- config: path2.join(appData, name, "Config"),
7677
- cache: path2.join(localAppData, name, "Cache"),
7678
- log: path2.join(localAppData, name, "Log"),
7679
- temp: path2.join(tmpdir, name)
8039
+ data: path.join(localAppData, name, "Data"),
8040
+ config: path.join(appData, name, "Config"),
8041
+ cache: path.join(localAppData, name, "Cache"),
8042
+ log: path.join(localAppData, name, "Log"),
8043
+ temp: path.join(tmpdir, name)
7680
8044
  };
7681
8045
  };
7682
8046
  var linux = (name) => {
7683
- const username = path2.basename(homedir);
8047
+ const username = path.basename(homedir);
7684
8048
  return {
7685
- data: path2.join(env2.XDG_DATA_HOME || path2.join(homedir, ".local", "share"), name),
7686
- config: path2.join(env2.XDG_CONFIG_HOME || path2.join(homedir, ".config"), name),
7687
- cache: path2.join(env2.XDG_CACHE_HOME || path2.join(homedir, ".cache"), name),
7688
- log: path2.join(env2.XDG_STATE_HOME || path2.join(homedir, ".local", "state"), name),
7689
- temp: path2.join(tmpdir, username, name)
8049
+ data: path.join(env.XDG_DATA_HOME || path.join(homedir, ".local", "share"), name),
8050
+ config: path.join(env.XDG_CONFIG_HOME || path.join(homedir, ".config"), name),
8051
+ cache: path.join(env.XDG_CACHE_HOME || path.join(homedir, ".cache"), name),
8052
+ log: path.join(env.XDG_STATE_HOME || path.join(homedir, ".local", "state"), name),
8053
+ temp: path.join(tmpdir, username, name)
7690
8054
  };
7691
8055
  };
7692
8056
  function envPaths(name, { suffix = "nodejs" } = {}) {
@@ -7711,19 +8075,19 @@ class Service16 extends Context16.Service()("@dimple/wasm/WasmCache") {
7711
8075
  }
7712
8076
  var layer16 = Layer20.effect(Service16, Effect43.succeed(Service16.of({
7713
8077
  ensure: Effect43.fn("WasmCache.ensure")(function* (lang, version, download) {
7714
- const versionDir = join2(paths.cache, "tree-sitter", lang, version);
7715
- const finalPath = join2(versionDir, "parser.wasm");
8078
+ const versionDir = join3(paths.cache, "tree-sitter", lang, version);
8079
+ const finalPath = join3(versionDir, "parser.wasm");
7716
8080
  const exists = yield* Effect43.try({
7717
- try: () => existsSync2(finalPath),
8081
+ try: () => existsSync3(finalPath),
7718
8082
  catch: (cause) => new WasmCacheError({ message: `Failed to stat ${finalPath}`, cause })
7719
8083
  });
7720
8084
  if (exists)
7721
8085
  return finalPath;
7722
8086
  yield* Effect43.try({
7723
- try: () => mkdirSync3(versionDir, { recursive: true }),
8087
+ try: () => mkdirSync(versionDir, { recursive: true }),
7724
8088
  catch: (cause) => new WasmCacheError({ message: `Failed to create ${versionDir}`, cause })
7725
8089
  });
7726
- const tempPath = join2(versionDir, `.tmp-${process.pid}`);
8090
+ const tempPath = join3(versionDir, `.tmp-${process.pid}`);
7727
8091
  const wasm = yield* download;
7728
8092
  yield* Effect43.try({
7729
8093
  try: () => writeFileSync(tempPath, wasm),
@@ -7738,7 +8102,7 @@ var layer16 = Layer20.effect(Service16, Effect43.succeed(Service16.of({
7738
8102
  }).pipe(Effect43.catchCause(() => Effect43.succeed(false)));
7739
8103
  if (!renamed) {
7740
8104
  const existsAfter = yield* Effect43.try({
7741
- try: () => existsSync2(finalPath),
8105
+ try: () => existsSync3(finalPath),
7742
8106
  catch: (cause) => new WasmCacheError({ message: `Failed to stat ${finalPath}`, cause })
7743
8107
  });
7744
8108
  if (!existsAfter) {
@@ -7769,12 +8133,12 @@ var layer17 = Layer21.effect(Service17, Effect44.gen(function* () {
7769
8133
  message: `Failed to download grammar for: ${lang}`,
7770
8134
  cause
7771
8135
  })));
7772
- const path3 = yield* cache.ensure(lang, metadata.version, Effect44.succeed(wasmBytes)).pipe(Effect44.mapError((cause) => new GrammarProvisionError({
8136
+ const path2 = yield* cache.ensure(lang, metadata.version, Effect44.succeed(wasmBytes)).pipe(Effect44.mapError((cause) => new GrammarProvisionError({
7773
8137
  language: lang,
7774
8138
  message: `Failed to provision grammar for: ${lang}`,
7775
8139
  cause
7776
8140
  })));
7777
- return path3;
8141
+ return path2;
7778
8142
  });
7779
8143
  return Service17.of({ ensure });
7780
8144
  }));
@@ -7893,11 +8257,27 @@ class DuplicateEdgeError extends Schema33.TaggedErrorClass()("Sdk.DuplicateEdge"
7893
8257
  }) {
7894
8258
  }
7895
8259
 
8260
+ class EdgeNotFoundError extends Schema33.TaggedErrorClass()("Sdk.EdgeNotFound", {
8261
+ id: Schema33.optional(Schema33.String),
8262
+ message: Schema33.String
8263
+ }) {
8264
+ }
8265
+
7896
8266
  class StoreFailure extends Schema33.TaggedErrorClass()("Sdk.StoreFailure", {
7897
8267
  message: Schema33.String
7898
8268
  }) {
7899
8269
  }
7900
8270
 
8271
+ class ReloadError extends Schema33.TaggedErrorClass()("Sdk.Reload", {
8272
+ message: Schema33.String
8273
+ }) {
8274
+ }
8275
+
8276
+ class EmbeddingFailure extends Schema33.TaggedErrorClass()("Sdk.Embedding", {
8277
+ message: Schema33.String
8278
+ }) {
8279
+ }
8280
+
7901
8281
  class InputError extends Schema33.TaggedErrorClass()("Sdk.InputError", {
7902
8282
  message: Schema33.String
7903
8283
  }) {
@@ -7919,12 +8299,14 @@ var messageOf = (error) => {
7919
8299
  }
7920
8300
  };
7921
8301
  var remapError = (error) => {
7922
- 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) {
8302
+ 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) {
7923
8303
  return error;
7924
8304
  }
7925
8305
  const message = messageOf(error);
7926
8306
  if (error instanceof CoreConfigError)
7927
8307
  return new ConfigError({ message });
8308
+ if (error instanceof EmbeddingError)
8309
+ return new EmbeddingFailure({ message });
7928
8310
  if (error instanceof NotFound) {
7929
8311
  return new NotFoundError({
7930
8312
  ...error.id !== undefined ? { id: error.id } : {},
@@ -7946,6 +8328,12 @@ var remapError = (error) => {
7946
8328
  message: `Edge already exists: ${error.fromMemoryId} → ${error.toMemoryId} (${error.edgeKind})`
7947
8329
  });
7948
8330
  }
8331
+ if (error instanceof exports_graph.EdgeNotFound) {
8332
+ return new EdgeNotFoundError({
8333
+ ...error.id !== undefined ? { id: error.id } : {},
8334
+ message: `Edge not found: ${error.id ?? "unknown id"}`
8335
+ });
8336
+ }
7949
8337
  if (error instanceof JobFailed || error instanceof JobTimeout || error instanceof JobCancelled) {
7950
8338
  return message.includes("LLM not configured") ? new LLMNotConfiguredError({ message }) : new JobFailedError({ message });
7951
8339
  }
@@ -7962,8 +8350,9 @@ var configLayerFromInput = (input) => Layer25.effect(Service4, resolveConfigInpu
7962
8350
  import { Cause as Cause5, Effect as Effect49, Exit as Exit6 } from "effect";
7963
8351
 
7964
8352
  // src/indexer.ts
7965
- import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
7966
- import { basename, dirname as dirname4, extname, join as join3, resolve, sep } from "node:path";
8353
+ import { execFileSync } from "node:child_process";
8354
+ import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
8355
+ import { basename, dirname, extname, join as join4, resolve, sep } from "node:path";
7967
8356
  var INDEXABLE_EXTENSIONS = [
7968
8357
  ".ts",
7969
8358
  ".mts",
@@ -8000,52 +8389,68 @@ var LANGUAGE_BY_EXTENSION = {
8000
8389
  ".cjs": "javascript",
8001
8390
  ".jsx": "jsx"
8002
8391
  };
8003
- var languageForPath = (path3) => LANGUAGE_BY_EXTENSION[extname(path3)];
8392
+ var languageForPath = (path2) => LANGUAGE_BY_EXTENSION[extname(path2)];
8004
8393
  var walkFiles = (paths2, extensions) => {
8005
8394
  const extensionSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
8006
8395
  const files = [];
8007
8396
  const errors3 = [];
8008
8397
  let unsupported = 0;
8009
- const root = commonRoot([...paths2, process.cwd()]);
8010
- const visit = (path3) => {
8398
+ const resolved = paths2.map((p) => resolve(p));
8399
+ const root = commonRoot([...resolved, process.cwd()]);
8400
+ const visit = (path2) => {
8011
8401
  let stat;
8012
8402
  try {
8013
- stat = statSync2(path3);
8403
+ stat = statSync(path2);
8014
8404
  } catch (cause) {
8015
- errors3.push({ filePath: path3, message: `stat failed: ${messageOf2(cause)}` });
8405
+ errors3.push({ filePath: path2, message: `stat failed: ${messageOf2(cause)}` });
8016
8406
  return;
8017
8407
  }
8018
8408
  if (stat.isDirectory()) {
8019
- const name = basename(path3);
8409
+ const name = basename(path2);
8020
8410
  if (EXCLUDED_DIRS.has(name))
8021
8411
  return;
8022
8412
  let entries;
8023
8413
  try {
8024
- entries = readdirSync(path3);
8414
+ entries = readdirSync(path2);
8025
8415
  } catch (cause) {
8026
- errors3.push({ filePath: path3, message: `readdir failed: ${messageOf2(cause)}` });
8416
+ errors3.push({ filePath: path2, message: `readdir failed: ${messageOf2(cause)}` });
8027
8417
  return;
8028
8418
  }
8029
8419
  for (const entry of entries)
8030
- visit(join3(path3, entry));
8420
+ visit(join4(path2, entry));
8031
8421
  return;
8032
8422
  }
8033
8423
  if (!stat.isFile())
8034
8424
  return;
8035
- if (!extensionSet.has(extname(path3))) {
8425
+ if (!extensionSet.has(extname(path2))) {
8036
8426
  unsupported += 1;
8037
8427
  return;
8038
8428
  }
8039
- files.push(path3);
8429
+ files.push(path2);
8040
8430
  };
8041
- for (const path3 of paths2)
8042
- visit(resolve(path3));
8043
- return { files, unsupported, errors: errors3, root };
8431
+ for (const path2 of resolved)
8432
+ visit(path2);
8433
+ return { files, unsupported, errors: errors3, root, roots: resolved };
8044
8434
  };
8045
- var readCapped = (path3, maxBytes) => {
8046
- const text4 = readFileSync2(path3, "utf8");
8435
+ var readCapped = (path2, maxBytes) => {
8436
+ const text4 = readFileSync2(path2, "utf8");
8047
8437
  return text4.length > maxBytes ? text4.slice(0, maxBytes) : text4;
8048
8438
  };
8439
+ var currentCommit = (firstFile) => {
8440
+ if (firstFile === undefined)
8441
+ return null;
8442
+ try {
8443
+ const out = execFileSync("git", ["rev-parse", "HEAD"], {
8444
+ cwd: dirname(firstFile),
8445
+ encoding: "utf8",
8446
+ stdio: ["ignore", "pipe", "ignore"]
8447
+ });
8448
+ const sha = out.trim();
8449
+ return /^[0-9a-f]{7,40}$/.test(sha) ? sha : null;
8450
+ } catch {
8451
+ return null;
8452
+ }
8453
+ };
8049
8454
  var resolveImportTarget = (fromDir, specifier, extensions) => {
8050
8455
  if (!specifier.startsWith("."))
8051
8456
  return;
@@ -8053,11 +8458,11 @@ var resolveImportTarget = (fromDir, specifier, extensions) => {
8053
8458
  const candidates2 = [
8054
8459
  base,
8055
8460
  ...extensions.map((e) => base + e),
8056
- ...extensions.map((e) => join3(base, `index${e}`))
8461
+ ...extensions.map((e) => join4(base, `index${e}`))
8057
8462
  ];
8058
8463
  for (const candidate of candidates2) {
8059
8464
  try {
8060
- if (existsSync3(candidate) && statSync2(candidate).isFile())
8465
+ if (existsSync4(candidate) && statSync(candidate).isFile())
8061
8466
  return candidate;
8062
8467
  } catch {
8063
8468
  return;
@@ -8068,7 +8473,7 @@ var resolveImportTarget = (fromDir, specifier, extensions) => {
8068
8473
  var resolveImports = (filePath, sources, extensions) => {
8069
8474
  const targets = [];
8070
8475
  for (const source of sources) {
8071
- const target = resolveImportTarget(dirname4(filePath), source, extensions);
8476
+ const target = resolveImportTarget(dirname(filePath), source, extensions);
8072
8477
  if (target !== undefined)
8073
8478
  targets.push(target);
8074
8479
  }
@@ -8098,16 +8503,11 @@ var keyForPath = (root, filePath) => {
8098
8503
  var messageOf2 = (cause) => cause instanceof Error ? cause.message : String(cause);
8099
8504
 
8100
8505
  // src/dimple.ts
8101
- var makeFacade = (runtime, core, parser, extractor, dispose) => {
8102
- let disposed = false;
8103
- const run = (effect) => {
8104
- if (disposed) {
8105
- return Promise.reject(new StoreFailure({ message: "SDK disposed" }));
8106
- }
8107
- return runtime.runPromise(effect.pipe(Effect49.mapError(remapError))).catch((cause) => {
8108
- return Promise.reject(remapError(cause));
8109
- });
8110
- };
8506
+ var makeFacade = (state, reload, dispose) => {
8507
+ const run = state.run;
8508
+ const core = () => state.core();
8509
+ const parser = () => state.parser();
8510
+ const extractor = () => state.extractor();
8111
8511
  const validateQueryOptions = (options) => {
8112
8512
  const limit = options?.limit;
8113
8513
  if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {
@@ -8119,6 +8519,23 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8119
8519
  if (k !== undefined && (!Number.isInteger(k) || k < 0)) {
8120
8520
  return new InputError({ message: `query: k must be a non-negative integer (got ${k})` });
8121
8521
  }
8522
+ const budget = options?.budget;
8523
+ if (budget !== undefined) {
8524
+ if (budget === null || typeof budget !== "object" || Array.isArray(budget)) {
8525
+ return new InputError({ message: "query: budget must be an object" });
8526
+ }
8527
+ for (const [name, v] of [
8528
+ ["maxUnits", budget.maxUnits],
8529
+ ["maxChars", budget.maxChars],
8530
+ ["maxMs", budget.maxMs]
8531
+ ]) {
8532
+ if (v !== undefined && (!Number.isInteger(v) || v <= 0)) {
8533
+ return new InputError({
8534
+ message: `query: budget.${name} must be a positive integer (got ${String(v)})`
8535
+ });
8536
+ }
8537
+ }
8538
+ }
8122
8539
  const profile = options?.profile;
8123
8540
  if (profile !== undefined) {
8124
8541
  if (typeof profile === "string") {
@@ -8159,13 +8576,57 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8159
8576
  if (topicKeys !== undefined && (!Array.isArray(topicKeys) || topicKeys.some((t) => typeof t !== "string"))) {
8160
8577
  return new InputError({ message: "query: topics.topicKeys must be a string array" });
8161
8578
  }
8579
+ const contentType = options?.contentType;
8580
+ if (contentType !== undefined) {
8581
+ const kinds = typeof contentType === "string" ? [contentType] : contentType;
8582
+ if (kinds.length === 0 || kinds.some((k2) => typeof k2 !== "string" || k2.length === 0)) {
8583
+ return new InputError({
8584
+ message: "query: contentType must be a non-empty string or a non-empty string array"
8585
+ });
8586
+ }
8587
+ }
8162
8588
  return;
8163
8589
  };
8164
8590
  const runQuery = (text4, options) => run(Effect49.gen(function* () {
8591
+ if (typeof text4 !== "string") {
8592
+ return yield* new InputError({ message: "query: text must be a string" });
8593
+ }
8165
8594
  const invalid = validateQueryOptions(options);
8166
8595
  if (invalid !== undefined)
8167
8596
  return yield* invalid;
8168
- return yield* core.hybrid.hybrid(text4, options).pipe(Effect49.map((rows) => [...rows]));
8597
+ const { budget, ...rest } = options ?? {};
8598
+ const started = Date.now();
8599
+ const capLimit = budget?.maxUnits !== undefined && (rest.limit === undefined || rest.limit > budget.maxUnits) ? budget.maxUnits : rest.limit;
8600
+ let rows = yield* core().hybrid.hybrid(text4, {
8601
+ ...rest,
8602
+ ...capLimit !== undefined ? { limit: capLimit } : {}
8603
+ }).pipe(Effect49.map((r) => [...r]));
8604
+ let truncated = false;
8605
+ if (budget?.maxChars !== undefined && budget.maxChars > 0) {
8606
+ let used = 0;
8607
+ rows = rows.map((row) => {
8608
+ const content = row.memory.content;
8609
+ const room = budget.maxChars - used;
8610
+ if (room <= 0) {
8611
+ truncated = true;
8612
+ return { ...row, memory: { ...row.memory, content: "" } };
8613
+ }
8614
+ if (content.length > room) {
8615
+ truncated = true;
8616
+ used += room;
8617
+ return { ...row, memory: { ...row.memory, content: content.slice(0, room) } };
8618
+ }
8619
+ used += content.length;
8620
+ return row;
8621
+ });
8622
+ }
8623
+ if (budget?.maxMs !== undefined && Date.now() - started > budget.maxMs) {
8624
+ truncated = true;
8625
+ }
8626
+ const out = rows;
8627
+ if (truncated)
8628
+ out.truncated = true;
8629
+ return out;
8169
8630
  }));
8170
8631
  const validateWriteInput = (input) => {
8171
8632
  if (input === null || typeof input !== "object" || Array.isArray(input)) {
@@ -8184,9 +8645,9 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8184
8645
  return new InputError({ message: "write: namespace must be a string" });
8185
8646
  }
8186
8647
  const ttl = input.ttlSecs;
8187
- if (ttl !== undefined && (!Number.isInteger(ttl) || ttl < 0)) {
8648
+ if (typeof ttl === "number" && (!Number.isInteger(ttl) || ttl < 0)) {
8188
8649
  return new InputError({
8189
- message: `write: ttlSecs must be a non-negative integer (got ${String(ttl)})`
8650
+ message: `write: ttlSecs must be a non-negative integer or null (got ${String(ttl)})`
8190
8651
  });
8191
8652
  }
8192
8653
  if (input.metadata !== undefined && typeof input.metadata !== "string") {
@@ -8230,15 +8691,47 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8230
8691
  return;
8231
8692
  };
8232
8693
  const writeUnit = (input, embed) => Effect49.gen(function* () {
8233
- const mem = yield* core.store.write(input);
8694
+ const mem = yield* core().store.write(input);
8234
8695
  if (embed) {
8235
- yield* core.store.ensureTopicTree(core.defaultModel.id);
8236
- const vector = yield* core.defaultEmbedder.embedSingle(input.content);
8237
- yield* core.store.updateEmbedding(mem.id, [...vector], core.defaultModel.id);
8238
- return yield* core.store.get(mem.id);
8696
+ yield* core().store.ensureTopicTree(core().defaultModel.id);
8697
+ const vector = yield* core().defaultEmbedder.embedSingle(input.content);
8698
+ yield* core().store.updateEmbedding(mem.id, [...vector], core().defaultModel.id);
8699
+ return yield* core().store.get(mem.id);
8239
8700
  }
8240
8701
  return mem;
8241
8702
  });
8703
+ const upsertUnit = (input, embed) => Effect49.gen(function* () {
8704
+ const existing = yield* core().store.getByKey(input.logicalKey, input.namespace ?? "default").pipe(Effect49.catchTag("Store.NotFound", () => Effect49.succeed(undefined)));
8705
+ if (existing !== undefined && existing.content === input.content) {
8706
+ const stamp = codeVersionOf(input.metadata);
8707
+ if (stamp !== undefined && codeVersionOf(existing.metadata) === undefined) {
8708
+ yield* core().store.setMemoryMetadata(existing.id, { codeVersion: stamp });
8709
+ }
8710
+ if (embed && (existing.embedding === null || existing.embedding === undefined)) {
8711
+ yield* core().store.ensureTopicTree(core().defaultModel.id);
8712
+ const vector = yield* core().defaultEmbedder.embedSingle(input.content);
8713
+ yield* core().store.updateEmbedding(existing.id, [...vector], core().defaultModel.id);
8714
+ return { kind: "updated", id: existing.id };
8715
+ }
8716
+ return { kind: "skipped", id: existing.id };
8717
+ }
8718
+ if (existing !== undefined) {
8719
+ const { memory: memory2 } = yield* core().store.writeSuperseding(input);
8720
+ if (embed) {
8721
+ yield* core().store.ensureTopicTree(core().defaultModel.id);
8722
+ const vector = yield* core().defaultEmbedder.embedSingle(input.content);
8723
+ yield* core().store.updateEmbedding(memory2.id, [...vector], core().defaultModel.id);
8724
+ }
8725
+ return { kind: "updated", id: memory2.id };
8726
+ }
8727
+ const memory = yield* core().store.write(input);
8728
+ if (embed) {
8729
+ yield* core().store.ensureTopicTree(core().defaultModel.id);
8730
+ const vector = yield* core().defaultEmbedder.embedSingle(input.content);
8731
+ yield* core().store.updateEmbedding(memory.id, [...vector], core().defaultModel.id);
8732
+ }
8733
+ return { kind: "indexed", id: memory.id };
8734
+ });
8242
8735
  const validateIndexOptions = (options) => {
8243
8736
  if (options === null || typeof options !== "object" || Array.isArray(options)) {
8244
8737
  return new InputError({ message: "index: options must be an object" });
@@ -8273,16 +8766,32 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8273
8766
  return;
8274
8767
  };
8275
8768
  const messageOf3 = (cause) => cause instanceof Error ? cause.message : String(cause);
8769
+ 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}` }))))));
8770
+ const codeVersionOf = (blob) => {
8771
+ if (blob === undefined || blob === null)
8772
+ return;
8773
+ try {
8774
+ const parsed = JSON.parse(blob);
8775
+ if (parsed !== null && typeof parsed === "object") {
8776
+ const v = parsed["codeVersion"];
8777
+ return typeof v === "string" ? v : undefined;
8778
+ }
8779
+ } catch {}
8780
+ return;
8781
+ };
8276
8782
  return {
8783
+ get defaultModelId() {
8784
+ return core().defaultModel.id;
8785
+ },
8277
8786
  write: (input, options) => run(Effect49.gen(function* () {
8278
8787
  const invalid = validateWriteInput(input);
8279
8788
  if (invalid !== undefined)
8280
8789
  return yield* invalid;
8281
8790
  return yield* writeUnit(input, options?.embed !== false);
8282
8791
  })),
8283
- get: (id) => run(core.store.get(id)),
8792
+ get: (id) => run(resolveMemory(id)),
8284
8793
  query: runQuery,
8285
- forget: (id) => run(core.store.forget(id)),
8794
+ forget: (id) => run(resolveMemory(id).pipe(Effect49.flatMap((memory) => core().store.forget(memory.id)))),
8286
8795
  index: (options) => run(Effect49.gen(function* () {
8287
8796
  const invalid = validateIndexOptions(options);
8288
8797
  if (invalid !== undefined)
@@ -8300,13 +8809,19 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8300
8809
  })
8301
8810
  });
8302
8811
  const failed = [...walk.errors];
8812
+ const commit = currentCommit(walk.files[0]);
8303
8813
  let indexed = 0;
8814
+ let updated = 0;
8815
+ let reconciled = 0;
8304
8816
  let skipped = 0;
8305
8817
  const unitIds = new Map;
8818
+ const freshIds = [];
8306
8819
  const pendingEdges = [];
8820
+ const pendingCallEdges = [];
8821
+ const stableChunkId = (chunkId) => chunkId.replace(/:\d+$/, "");
8307
8822
  const parentClassOf = (chunkId) => {
8308
- const parts = chunkId.split(":");
8309
- const parentAndMethod = parts[parts.length - 2];
8823
+ const parts = stableChunkId(chunkId).split(":");
8824
+ const parentAndMethod = parts[parts.length - 1];
8310
8825
  if (parentAndMethod === undefined || !parentAndMethod.includes("."))
8311
8826
  return;
8312
8827
  return parentAndMethod.split(".")[0];
@@ -8316,49 +8831,59 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8316
8831
  const language = languageForPath(filePath);
8317
8832
  if (language === undefined)
8318
8833
  continue;
8834
+ const chunkKeyOf = (chunkId) => `${keyPrefix}${relative}#${stableChunkId(chunkId)}`;
8319
8835
  const exit = yield* Effect49.exit(Effect49.gen(function* () {
8320
8836
  const source = yield* Effect49.try({
8321
8837
  try: () => readCapped(filePath, maxFileBytes),
8322
8838
  catch: (cause) => new InputError({ message: `read failed: ${messageOf3(cause)}` })
8323
8839
  });
8324
- const handle = yield* parser.parse({ lang: language, source });
8325
- const extracted = yield* extractor.extract({ filePath: relative, tree: handle });
8840
+ const handle = yield* parser().parse({ lang: language, source });
8841
+ const extracted = yield* extractor().extract({ filePath: relative, tree: handle });
8326
8842
  let units = 0;
8843
+ let upd = 0;
8327
8844
  let dups = 0;
8328
8845
  const fileKey = `${keyPrefix}${relative}`;
8329
- const fileOutcome = yield* writeUnit({
8846
+ const fileOutcome = yield* upsertUnit({
8330
8847
  logicalKey: fileKey,
8331
8848
  content: source,
8332
8849
  ...namespace !== undefined ? { namespace } : {},
8850
+ ttlSecs: null,
8333
8851
  metadata: JSON.stringify({
8334
8852
  filePath,
8335
8853
  language,
8336
8854
  kind: "file",
8337
- imports: extracted.imports ?? []
8855
+ imports: extracted.imports ?? [],
8856
+ ...commit !== null ? { codeVersion: commit } : {}
8338
8857
  }),
8339
8858
  contentType: `text/x-${language}`
8340
- }, embed).pipe(Effect49.catchTag("Store.DuplicateActiveKey", () => Effect49.succeed("duplicate")));
8341
- if (fileOutcome === "duplicate")
8859
+ }, embed);
8860
+ if (fileOutcome.kind === "skipped") {
8342
8861
  dups += 1;
8343
- else {
8344
- units += 1;
8345
8862
  unitIds.set(fileKey, fileOutcome.id);
8863
+ } else {
8864
+ if (fileOutcome.kind === "updated")
8865
+ upd += 1;
8866
+ else
8867
+ units += 1;
8868
+ unitIds.set(fileKey, fileOutcome.id);
8869
+ freshIds.push(fileOutcome.id);
8346
8870
  }
8347
8871
  const classKeys = new Map;
8348
8872
  for (const chunk of extracted.chunks) {
8349
8873
  if (chunk.kind === "class" || chunk.kind.endsWith("class")) {
8350
- classKeys.set(chunk.name, `${keyPrefix}${relative}#${chunk.id}`);
8874
+ classKeys.set(chunk.name, chunkKeyOf(chunk.id));
8351
8875
  }
8352
8876
  }
8353
8877
  for (const chunk of extracted.chunks) {
8354
- const chunkKey = `${keyPrefix}${relative}#${chunk.id}`;
8878
+ const chunkKey = chunkKeyOf(chunk.id);
8355
8879
  const content = [chunk.docstring, chunk.sourceText].filter((c) => c !== undefined && c.length > 0).join(`
8356
8880
 
8357
8881
  `).slice(0, maxChunkBytes);
8358
- const chunkOutcome = yield* writeUnit({
8882
+ const chunkOutcome = yield* upsertUnit({
8359
8883
  logicalKey: chunkKey,
8360
8884
  content,
8361
8885
  ...namespace !== undefined ? { namespace } : {},
8886
+ ttlSecs: null,
8362
8887
  metadata: JSON.stringify({
8363
8888
  filePath,
8364
8889
  language,
@@ -8366,17 +8891,23 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8366
8891
  name: chunk.name,
8367
8892
  startLine: chunk.startLine,
8368
8893
  endLine: chunk.endLine,
8369
- ...chunk.docstring !== undefined ? { docstring: chunk.docstring } : {}
8894
+ ...chunk.docstring !== undefined ? { docstring: chunk.docstring } : {},
8895
+ ...commit !== null ? { codeVersion: commit } : {}
8370
8896
  }),
8371
8897
  contentType: `text/x-${language}`
8372
- }, embed).pipe(Effect49.catchTag("Store.DuplicateActiveKey", () => Effect49.succeed("duplicate")));
8373
- if (chunkOutcome === "duplicate") {
8898
+ }, embed);
8899
+ if (chunkOutcome.kind === "skipped") {
8374
8900
  dups += 1;
8901
+ unitIds.set(chunkKey, chunkOutcome.id);
8375
8902
  } else {
8376
- units += 1;
8903
+ if (chunkOutcome.kind === "updated")
8904
+ upd += 1;
8905
+ else
8906
+ units += 1;
8377
8907
  unitIds.set(chunkKey, chunkOutcome.id);
8908
+ freshIds.push(chunkOutcome.id);
8378
8909
  }
8379
- if (chunkOutcome !== "duplicate" && chunk.kind === "method") {
8910
+ if (chunkOutcome.kind !== "skipped" && chunk.kind === "method") {
8380
8911
  const parent = parentClassOf(chunk.id);
8381
8912
  const classKey = parent === undefined ? undefined : classKeys.get(parent);
8382
8913
  if (classKey !== undefined) {
@@ -8390,37 +8921,68 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8390
8921
  to: fileKey
8391
8922
  });
8392
8923
  }
8393
- return { units, dups };
8924
+ const importRels = resolveImports(filePath, (extracted.imports ?? []).map((imp) => imp.source), extensions).map((target) => keyForPath(walk.root, target));
8925
+ for (const chunk of extracted.chunks) {
8926
+ const callerKey = chunkKeyOf(chunk.id);
8927
+ if (!unitIds.has(callerKey))
8928
+ continue;
8929
+ for (const call of chunk.calls ?? []) {
8930
+ const candidates2 = [
8931
+ `${keyPrefix}${relative}#${relative}:${call.name}`,
8932
+ ...importRels.map((rel) => `${keyPrefix}${rel}#${rel}:${call.name}`)
8933
+ ];
8934
+ const first = candidates2.find((key) => unitIds.has(key));
8935
+ if (first !== undefined) {
8936
+ pendingCallEdges.push({ from: callerKey, to: first });
8937
+ }
8938
+ }
8939
+ }
8940
+ return { units, upd, dups };
8394
8941
  }));
8395
8942
  if (Exit6.isFailure(exit)) {
8396
8943
  failed.push({ filePath, message: Cause5.pretty(exit.cause) });
8397
8944
  } else {
8398
8945
  indexed += exit.value.units;
8946
+ updated += exit.value.upd;
8399
8947
  skipped += exit.value.dups;
8400
8948
  }
8401
8949
  }
8402
- const edgeEffect = (edge) => {
8403
- const from = unitIds.get(edge.from);
8404
- const to = unitIds.get(edge.to);
8950
+ const vanished = yield* core().store.reconcile(namespace ?? "default", keyPrefix, walk.root, walk.roots, [...unitIds.keys()]);
8951
+ reconciled = vanished.length;
8952
+ yield* core().store.reconcileRenameCandidates(vanished, freshIds);
8953
+ const resolveKey = (key) => {
8954
+ const fromMap = unitIds.get(key);
8955
+ if (fromMap !== undefined)
8956
+ return Effect49.succeed(fromMap);
8957
+ return core().store.getByKey(key, namespace ?? "default").pipe(Effect49.map((m) => m.id), Effect49.catchTag("Store.NotFound", () => Effect49.succeed(undefined)));
8958
+ };
8959
+ const edgeEffect = (edge) => Effect49.gen(function* () {
8960
+ const [from, to] = yield* Effect49.all([resolveKey(edge.from), resolveKey(edge.to)]);
8405
8961
  if (from === undefined || to === undefined || from === to)
8406
- return Effect49.void;
8407
- return core.store.addEdge({
8962
+ return;
8963
+ yield* Effect49.exit(core().store.addEdge({
8408
8964
  fromMemoryId: from,
8409
8965
  toMemoryId: to,
8410
- edgeKind: "memory_supports",
8966
+ edgeKind: edge.edgeKind,
8411
8967
  source: "rule",
8412
8968
  confidence: 1,
8413
8969
  confirmed: true
8414
- });
8415
- };
8970
+ }));
8971
+ });
8416
8972
  for (const edge of pendingEdges) {
8417
- yield* Effect49.exit(edgeEffect(edge));
8973
+ yield* Effect49.exit(edgeEffect({ ...edge, edgeKind: "memory_supports" }));
8974
+ }
8975
+ for (const edge of pendingCallEdges) {
8976
+ yield* Effect49.exit(edgeEffect({ ...edge, edgeKind: "code_calls" }));
8418
8977
  }
8419
8978
  return {
8420
8979
  files: walk.files.length,
8421
8980
  indexed,
8981
+ updated,
8982
+ reconciled,
8422
8983
  skipped,
8423
8984
  unsupported: walk.unsupported,
8985
+ commit,
8424
8986
  failed
8425
8987
  };
8426
8988
  })),
@@ -8430,57 +8992,232 @@ var makeFacade = (runtime, core, parser, extractor, dispose) => {
8430
8992
  message: "updateEmbedding: embedding must be a non-empty array of finite numbers"
8431
8993
  });
8432
8994
  }
8433
- const key = modelKey ?? core.defaultModel.id;
8434
- yield* core.store.ensureTopicTree(key);
8435
- yield* core.store.updateEmbedding(id, [...embedding], key);
8995
+ const key = modelKey ?? core().defaultModel.id;
8996
+ const memory = yield* resolveMemory(id);
8997
+ yield* core().store.ensureTopicTree(key);
8998
+ yield* core().store.updateEmbedding(memory.id, [...embedding], key);
8436
8999
  })),
8437
- embed: (text4) => run(core.defaultEmbedder.embedSingle(text4).pipe(Effect49.map((vector) => [...vector]))),
8438
- dream: (modelKey) => run(core.runtime.enqueue(MemoryDream, { modelKey, runId: crypto.randomUUID() }).pipe(Effect49.flatMap((jobId) => core.runtime.wait(jobId)), Effect49.asVoid)),
8439
- repair: (modelKey) => run(core.runtime.enqueue(GraphRepair, { modelKey, runId: crypto.randomUUID() }).pipe(Effect49.flatMap((jobId) => core.runtime.wait(jobId)), Effect49.asVoid)),
8440
- explain: (input) => run(core.store.explain(input)),
8441
- health: (modelKey) => run(core.store.topicHealth(modelKey)),
9000
+ embed: (text4) => run(core().defaultEmbedder.embedSingle(text4).pipe(Effect49.map((vector) => [...vector]))),
9001
+ dream: (modelKey) => run(core().runtime.enqueue(MemoryDream, { modelKey, runId: crypto.randomUUID() }).pipe(Effect49.flatMap((jobId) => core().runtime.wait(jobId)), Effect49.asVoid)),
9002
+ dbSchema: () => run(core().store.listSchema()),
9003
+ repair: (modelKey) => run(core().runtime.enqueue(GraphRepair, { modelKey, runId: crypto.randomUUID() }).pipe(Effect49.flatMap((jobId) => core().runtime.wait(jobId)), Effect49.asVoid)),
9004
+ explain: (input, options) => run(core().store.explain(input, {
9005
+ ...options?.edgeKinds !== undefined ? { edgeKinds: options.edgeKinds } : {},
9006
+ ...options?.maxDepth !== undefined ? { maxDepth: options.maxDepth } : {},
9007
+ ...options?.maxNeighbors !== undefined ? { maxNeighbors: options.maxNeighbors } : {},
9008
+ ...options?.maxTotal !== undefined ? { maxTotal: options.maxTotal } : {},
9009
+ ...options?.includePending !== undefined ? { includePending: options.includePending } : {}
9010
+ })),
9011
+ health: (modelKey) => run(core().store.topicHealth(modelKey)),
8442
9012
  graph: {
8443
9013
  addEdge: (input) => run(Effect49.gen(function* () {
8444
9014
  const invalid = validateAddEdgeInput(input);
8445
9015
  if (invalid !== undefined)
8446
9016
  return yield* invalid;
8447
- return yield* core.store.addEdge(input);
9017
+ return yield* core().store.addEdge(input);
8448
9018
  })),
8449
- candidates: () => run(core.store.graphCandidates()),
8450
- coverage: (modelKey) => run(core.store.graphCoverage(modelKey))
9019
+ candidates: () => run(core().store.graphCandidates()),
9020
+ coverage: (modelKey) => run(core().store.graphCoverage(modelKey)),
9021
+ confirm: (id) => run(core().store.confirmEdge(id)),
9022
+ reject: (id) => run(core().store.rejectEdge(id)),
9023
+ expire: (id) => run(core().store.expireEdge(id))
8451
9024
  },
8452
- dispose: () => dispose().finally(() => {
8453
- disposed = true;
8454
- })
9025
+ reload: (input) => reload(input),
9026
+ dispose: () => dispose()
8455
9027
  };
8456
9028
  };
8457
9029
 
8458
9030
  // src/make.ts
8459
- var makeDimple = async (options) => {
9031
+ var stableKey = (value2) => {
9032
+ if (Array.isArray(value2))
9033
+ return `[${value2.map(stableKey).join(",")}]`;
9034
+ if (value2 !== null && typeof value2 === "object") {
9035
+ const entries = Object.entries(value2).filter(([, v]) => v !== undefined).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
9036
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableKey(v)}`).join(",")}}`;
9037
+ }
9038
+ return JSON.stringify(value2);
9039
+ };
9040
+ var diffConfig = (oldC, newC) => {
9041
+ if (oldC === undefined)
9042
+ return { storeUrl: true, jobsUrl: true, rest: true };
9043
+ const oldJobsUrl = oldC.jobs?.dbUrl;
9044
+ const newJobsUrl = newC.jobs?.dbUrl;
9045
+ const restOld = { ...oldC, storeUrl: "", jobs: { ...oldC.jobs, dbUrl: "" } };
9046
+ const restNew = { ...newC, storeUrl: "", jobs: { ...newC.jobs, dbUrl: "" } };
9047
+ return {
9048
+ storeUrl: oldC.storeUrl !== newC.storeUrl,
9049
+ jobsUrl: oldJobsUrl !== newJobsUrl,
9050
+ rest: stableKey(restOld) !== stableKey(restNew)
9051
+ };
9052
+ };
9053
+ var RELOAD_DRAIN_MS = 30000;
9054
+ var bootStack = async (options) => {
8460
9055
  const scope = await Effect50.runPromise(Scope5.make());
8461
9056
  let runtime;
8462
9057
  try {
8463
9058
  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)));
8464
9059
  const appLayer = Layer26.mergeAll(layer21, layer20, layer14);
8465
- const rt = ManagedRuntime2.make(appLayer);
9060
+ const rt = ManagedRuntime.make(appLayer);
8466
9061
  runtime = rt;
8467
9062
  const [core, parser, extractor] = await rt.runPromise(Effect50.all([Service, Service19, Service12]));
8468
- return makeFacade(rt, core, parser, extractor, () => Effect50.runPromise(Scope5.close(scope, Exit7.void).pipe(Effect50.andThen(rt.disposeEffect))));
9063
+ return {
9064
+ runPromise: (effect) => rt.runPromise(effect),
9065
+ disposeEffect: rt.disposeEffect,
9066
+ core,
9067
+ parser,
9068
+ extractor,
9069
+ dispose: () => Effect50.runPromise(Scope5.close(scope, Exit7.void).pipe(Effect50.andThen(rt.disposeEffect)))
9070
+ };
8469
9071
  } catch (cause) {
8470
9072
  await Effect50.runPromise(Scope5.close(scope, Exit7.void).pipe(Effect50.andThen(runtime?.disposeEffect ?? Effect50.void))).catch(() => {
8471
9073
  return;
8472
9074
  });
8473
- return Promise.reject(remapError(cause));
9075
+ return Promise.reject(cause);
8474
9076
  }
8475
9077
  };
9078
+ var makeDimple = async (options) => {
9079
+ const configSource = "config" in options ? options.config : undefined;
9080
+ const migrationFolder = "config" in options ? options.migrationFolder : undefined;
9081
+ let currentConfig;
9082
+ if (configSource !== undefined) {
9083
+ currentConfig = await Effect50.runPromise(resolveConfigInput(configSource)).catch((cause) => Promise.reject(remapError(cause)));
9084
+ }
9085
+ let current = await bootStack(configSource !== undefined && currentConfig !== undefined ? {
9086
+ config: currentConfig,
9087
+ ...migrationFolder !== undefined ? { migrationFolder } : {}
9088
+ } : options).then((stack) => stack, (cause) => Promise.reject(remapError(cause)));
9089
+ let disposed = false;
9090
+ let gate = Promise.resolve();
9091
+ let releaseGate;
9092
+ let inflight = 0;
9093
+ let drainWaiters = [];
9094
+ let reloadChain = Promise.resolve();
9095
+ const run = (effect) => {
9096
+ if (disposed) {
9097
+ return Promise.reject(new StoreFailure({ message: "SDK disposed" }));
9098
+ }
9099
+ return gate.then(() => {
9100
+ if (disposed) {
9101
+ return Promise.reject(new StoreFailure({ message: "SDK disposed" }));
9102
+ }
9103
+ const stack = current;
9104
+ inflight += 1;
9105
+ return stack.runPromise(effect.pipe(Effect50.mapError(remapError))).catch((cause) => {
9106
+ return Promise.reject(remapError(cause));
9107
+ }).finally(() => {
9108
+ inflight -= 1;
9109
+ if (inflight === 0 && drainWaiters.length > 0) {
9110
+ const waiters = drainWaiters;
9111
+ drainWaiters = [];
9112
+ for (const wake of waiters)
9113
+ wake();
9114
+ }
9115
+ });
9116
+ });
9117
+ };
9118
+ const pause = () => {
9119
+ gate = new Promise((resolve2) => {
9120
+ releaseGate = resolve2;
9121
+ });
9122
+ };
9123
+ const resume = () => {
9124
+ const release = releaseGate;
9125
+ releaseGate = undefined;
9126
+ gate = Promise.resolve();
9127
+ release?.();
9128
+ };
9129
+ const drain = () => {
9130
+ if (inflight === 0)
9131
+ return Promise.resolve();
9132
+ return new Promise((resolve2) => {
9133
+ drainWaiters.push(resolve2);
9134
+ });
9135
+ };
9136
+ const drainWithTimeout = () => new Promise((resolve2, reject) => {
9137
+ const timer = setTimeout(() => {
9138
+ reject(new ReloadError({
9139
+ message: `reload: drain timeout after ${RELOAD_DRAIN_MS}ms — in-flight calls still running`
9140
+ }));
9141
+ }, RELOAD_DRAIN_MS);
9142
+ drain().then(() => {
9143
+ clearTimeout(timer);
9144
+ resolve2();
9145
+ }, (cause) => {
9146
+ clearTimeout(timer);
9147
+ reject(cause);
9148
+ });
9149
+ });
9150
+ const reload = (input) => {
9151
+ const task = reloadChain.then(async () => {
9152
+ if (disposed) {
9153
+ return Promise.reject(new ReloadError({ message: "SDK disposed — reload unavailable" }));
9154
+ }
9155
+ if (configSource === undefined) {
9156
+ return Promise.reject(new ReloadError({
9157
+ message: "reload: no config source — this dimple was built from `core` options"
9158
+ }));
9159
+ }
9160
+ const nextInput = input ?? configSource;
9161
+ const resolved = await Effect50.runPromise(resolveConfigInput(nextInput));
9162
+ if (currentConfig !== undefined && stableKey(resolved) === stableKey(currentConfig)) {
9163
+ return {
9164
+ config: resolved,
9165
+ changed: { storeUrl: false, jobsUrl: false, rest: false },
9166
+ reloaded: false
9167
+ };
9168
+ }
9169
+ const changes = diffConfig(currentConfig, resolved);
9170
+ pause();
9171
+ try {
9172
+ const next = await bootStack({
9173
+ config: resolved,
9174
+ ...migrationFolder !== undefined ? { migrationFolder } : {}
9175
+ });
9176
+ await drainWithTimeout();
9177
+ const oldStoreUrl = currentConfig?.storeUrl;
9178
+ const oldJobsUrl = currentConfig?.jobs?.dbUrl;
9179
+ const old = current;
9180
+ current = next;
9181
+ currentConfig = resolved;
9182
+ await old.dispose();
9183
+ if (changes.storeUrl) {
9184
+ 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" })));
9185
+ }
9186
+ if (changes.jobsUrl) {
9187
+ 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" })));
9188
+ }
9189
+ return { config: resolved, changed: changes, reloaded: true };
9190
+ } finally {
9191
+ resume();
9192
+ }
9193
+ });
9194
+ reloadChain = task.catch(() => {
9195
+ return;
9196
+ });
9197
+ return task.then((config) => config, (cause) => Promise.reject(remapError(cause)));
9198
+ };
9199
+ const dispose = () => {
9200
+ disposed = true;
9201
+ return reloadChain.then(() => current.dispose());
9202
+ };
9203
+ return makeFacade({
9204
+ run,
9205
+ core: () => current.core,
9206
+ parser: () => current.parser,
9207
+ extractor: () => current.extractor
9208
+ }, reload, dispose);
9209
+ };
8476
9210
  export {
8477
9211
  remapError,
8478
9212
  makeDimple,
8479
9213
  StoreFailure,
9214
+ ReloadError,
8480
9215
  NotFoundError,
8481
9216
  LLMNotConfiguredError,
8482
9217
  JobFailedError,
8483
9218
  InputError,
9219
+ EmbeddingFailure,
9220
+ EdgeNotFoundError,
8484
9221
  DuplicateKeyError,
8485
9222
  DuplicateEdgeError,
8486
9223
  ConfigError