negotium 0.2.17 → 0.2.19

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.
Files changed (38) hide show
  1. package/dist/agent-helpers.js +41 -32
  2. package/dist/agent-helpers.js.map +10 -10
  3. package/dist/{chunk-5cpdbbeh.js → chunk-fc58cqry.js} +3 -1
  4. package/dist/{chunk-5cpdbbeh.js.map → chunk-fc58cqry.js.map} +3 -3
  5. package/dist/hosted-agent.js +4 -2
  6. package/dist/hosted-agent.js.map +4 -4
  7. package/dist/main.js +1377 -285
  8. package/dist/main.js.map +14 -13
  9. package/dist/mcp-factories.js +1497 -403
  10. package/dist/mcp-factories.js.map +14 -13
  11. package/dist/prompts.js +8 -8
  12. package/dist/prompts.js.map +3 -3
  13. package/dist/registry.js +3 -3
  14. package/dist/registry.js.map +2 -2
  15. package/dist/rollout.js +1 -1
  16. package/dist/runtime/src/agents/archiver.ts +1 -1
  17. package/dist/runtime/src/agents/idle-archiver.ts +3 -2
  18. package/dist/runtime/src/mcp/canonical-proxy-server.ts +31 -15
  19. package/dist/runtime/src/mcp/wiki-search-index.ts +359 -0
  20. package/dist/runtime/src/mcp/wiki-server.ts +1358 -155
  21. package/dist/runtime/src/platform/mcp-config.ts +2 -1
  22. package/dist/runtime/src/prompts/agents/wiki-archiver.md +73 -48
  23. package/dist/runtime/src/prompts/builders.ts +17 -34
  24. package/dist/runtime/src/runtime/turn-runner.ts +14 -17
  25. package/dist/runtime/src/storage/api-topics.ts +13 -4
  26. package/dist/runtime/src/topics/lifecycle.ts +3 -2
  27. package/dist/runtime/src/types/api.ts +6 -0
  28. package/dist/runtime/src/version.ts +1 -1
  29. package/dist/storage.js +12 -6
  30. package/dist/storage.js.map +3 -3
  31. package/dist/types/packages/core/src/agents/archiver.d.ts +1 -1
  32. package/dist/types/packages/core/src/mcp/wiki-search-index.d.ts +59 -0
  33. package/dist/types/packages/core/src/mcp/wiki-server.d.ts +4 -0
  34. package/dist/types/packages/core/src/prompts/builders.d.ts +2 -4
  35. package/dist/types/packages/core/src/storage/api-topics.d.ts +1 -0
  36. package/dist/types/packages/core/src/types/api.d.ts +6 -0
  37. package/dist/types/packages/core/src/version.d.ts +1 -1
  38. package/package.json +1 -1
@@ -29,6 +29,59 @@ var init_types = __esm(() => {
29
29
  MAESTRO_EFFORT_VALUES = ["low", "medium", "high", "xhigh", "max"];
30
30
  });
31
31
 
32
+ // ../../packages/core/src/storage/sqlite.ts
33
+ var isBun, Database;
34
+ var init_sqlite = __esm(async () => {
35
+ isBun = typeof process.versions.bun === "string";
36
+ if (isBun) {
37
+ ({ Database } = await import("bun:sqlite"));
38
+ } else {
39
+ const nodeSqliteSpecifier = ["node", "sqlite"].join(":");
40
+ const { DatabaseSync } = await import(nodeSqliteSpecifier);
41
+
42
+ class NodeDatabase {
43
+ #db;
44
+ constructor(path, options = {}) {
45
+ this.#db = options.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
46
+ }
47
+ query(sql) {
48
+ return this.#db.prepare(sql);
49
+ }
50
+ prepare(sql) {
51
+ return this.#db.prepare(sql);
52
+ }
53
+ exec(sql) {
54
+ this.#db.exec(sql);
55
+ }
56
+ run(sql, ...params) {
57
+ return this.#db.prepare(sql).run(...params);
58
+ }
59
+ transaction(fn) {
60
+ const run = (begin) => (...args) => {
61
+ this.#db.exec(begin);
62
+ try {
63
+ const result = fn(...args);
64
+ this.#db.exec("COMMIT");
65
+ return result;
66
+ } catch (err) {
67
+ this.#db.exec("ROLLBACK");
68
+ throw err;
69
+ }
70
+ };
71
+ const tx = run("BEGIN");
72
+ tx.deferred = run("BEGIN DEFERRED");
73
+ tx.immediate = run("BEGIN IMMEDIATE");
74
+ tx.exclusive = run("BEGIN EXCLUSIVE");
75
+ return tx;
76
+ }
77
+ close() {
78
+ this.#db.close();
79
+ }
80
+ }
81
+ Database = NodeDatabase;
82
+ }
83
+ });
84
+
32
85
  // ../../packages/core/src/platform/config-helpers.ts
33
86
  function readEnvText(env, key) {
34
87
  const value2 = env[key]?.trim();
@@ -72,11 +125,11 @@ import { execFileSync } from "child_process";
72
125
  import { randomBytes } from "crypto";
73
126
  import {
74
127
  accessSync,
75
- chmodSync,
128
+ chmodSync as chmodSync2,
76
129
  constants,
77
- existsSync,
130
+ existsSync as existsSync2,
78
131
  mkdirSync,
79
- readFileSync,
132
+ readFileSync as readFileSync2,
80
133
  writeFileSync
81
134
  } from "fs";
82
135
  import { createRequire } from "module";
@@ -93,7 +146,7 @@ function resolveAgentEnv(envKey, fallback, legacyEnvKey) {
93
146
  function resolveProjectRoot() {
94
147
  const moduleDir = dirname(fileURLToPath(import.meta.url));
95
148
  const packagedRuntime = resolve(moduleDir, "runtime");
96
- if (existsSync(resolve(packagedRuntime, "src")))
149
+ if (existsSync2(resolve(packagedRuntime, "src")))
97
150
  return packagedRuntime;
98
151
  return resolve(moduleDir, "../..");
99
152
  }
@@ -101,7 +154,7 @@ function resolveDependencyBin(name) {
101
154
  let dir = PROJECT_ROOT;
102
155
  while (true) {
103
156
  const candidate = resolve(dir, "node_modules", ".bin", name);
104
- if (existsSync(candidate))
157
+ if (existsSync2(candidate))
105
158
  return candidate;
106
159
  const parent = dirname(dir);
107
160
  if (parent === dir)
@@ -215,14 +268,14 @@ function loadOrCreateLocalSecret(envKey, filename, options = {}) {
215
268
  if (options.persistEnvValue) {
216
269
  writeFileSync(secretFile, `${envValue}
217
270
  `, { mode: 384 });
218
- chmodSync(secretFile, 384);
271
+ chmodSync2(secretFile, 384);
219
272
  }
220
273
  return envValue;
221
274
  }
222
- if (existsSync(secretFile)) {
223
- const stored = readFileSync(secretFile, "utf-8").trim();
275
+ if (existsSync2(secretFile)) {
276
+ const stored = readFileSync2(secretFile, "utf-8").trim();
224
277
  if (stored) {
225
- chmodSync(secretFile, 384);
278
+ chmodSync2(secretFile, 384);
226
279
  return stored;
227
280
  }
228
281
  }
@@ -234,10 +287,10 @@ function loadOrCreateLocalSecret(envKey, filename, options = {}) {
234
287
  } catch (error) {
235
288
  if (error.code !== "EEXIST")
236
289
  throw error;
237
- const stored = readFileSync(secretFile, "utf-8").trim();
290
+ const stored = readFileSync2(secretFile, "utf-8").trim();
238
291
  if (!stored)
239
292
  throw new Error(`Secret file exists but is empty: ${secretFile}`);
240
- chmodSync(secretFile, 384);
293
+ chmodSync2(secretFile, 384);
241
294
  return stored;
242
295
  }
243
296
  }
@@ -569,7 +622,7 @@ var init_shared = __esm(() => {
569
622
  });
570
623
 
571
624
  // ../../packages/core/src/platform/file-utils.ts
572
- import { unlinkSync as unlinkSync2 } from "fs";
625
+ import { unlinkSync as unlinkSync3 } from "fs";
573
626
  function createSafeUnlink(host) {
574
627
  return (path, warnLabel) => {
575
628
  try {
@@ -589,7 +642,7 @@ var defaultSafeUnlink;
589
642
  var init_file_utils = __esm(() => {
590
643
  init_logger();
591
644
  defaultSafeUnlink = createSafeUnlink({
592
- unlink: unlinkSync2,
645
+ unlink: unlinkSync3,
593
646
  warn: (context, message) => logger.warn(context, message)
594
647
  });
595
648
  });
@@ -601,15 +654,15 @@ import {
601
654
  fsyncSync,
602
655
  mkdirSync as mkdirSync4,
603
656
  openSync as openSync2,
604
- readFileSync as readFileSync3,
657
+ readFileSync as readFileSync4,
605
658
  renameSync as renameSync2,
606
659
  statSync as statSync2,
607
- unlinkSync as unlinkSync3,
660
+ unlinkSync as unlinkSync4,
608
661
  writeFileSync as writeFileSync3
609
662
  } from "fs";
610
663
  import { dirname as dirname3 } from "path";
611
664
  function readJsonlLines(filePath) {
612
- return readFileSync3(filePath, "utf-8").trim().split(`
665
+ return readFileSync4(filePath, "utf-8").trim().split(`
613
666
  `).filter(Boolean);
614
667
  }
615
668
  function parseJsonlText(raw) {
@@ -618,7 +671,7 @@ function parseJsonlText(raw) {
618
671
  }
619
672
  function readJsonFile(filePath) {
620
673
  try {
621
- return JSON.parse(readFileSync3(filePath, "utf-8"));
674
+ return JSON.parse(readFileSync4(filePath, "utf-8"));
622
675
  } catch {
623
676
  return null;
624
677
  }
@@ -708,7 +761,7 @@ function writeJsonlFile(filePath, entries) {
708
761
  } catch {}
709
762
  }
710
763
  try {
711
- unlinkSync3(tmpPath);
764
+ unlinkSync4(tmpPath);
712
765
  } catch {}
713
766
  throw err;
714
767
  }
@@ -744,13 +797,13 @@ var init_jsonl = __esm(() => {
744
797
 
745
798
  // ../../packages/core/src/agents/rollout/claude.ts
746
799
  import { randomUUID as randomUUID2 } from "crypto";
747
- import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
800
+ import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
748
801
  import { homedir as homedir2 } from "os";
749
802
  import { join as join3 } from "path";
750
803
  function loadClaudeAttachments() {
751
804
  if (_attachmentsCache)
752
805
  return _attachmentsCache;
753
- const raw = readFileSync4(join3(FIXTURES_DIR, "claude-attachments.jsonl"), "utf8");
806
+ const raw = readFileSync5(join3(FIXTURES_DIR, "claude-attachments.jsonl"), "utf8");
754
807
  const lines = parseJsonlText(raw);
755
808
  if (lines.length < 2) {
756
809
  throw new Error(`loadClaudeAttachments: expected >=2 entries in claude-attachments.jsonl, got ${lines.length}`);
@@ -865,7 +918,7 @@ var init_claude = __esm(() => {
865
918
  });
866
919
 
867
920
  // ../../packages/core/src/agents/claude-registry.ts
868
- import { existsSync as existsSync4, mkdirSync as mkdirSync5, readdirSync as readdirSync2, renameSync as renameSync3, unlinkSync as unlinkSync4 } from "fs";
921
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync2, renameSync as renameSync3, unlinkSync as unlinkSync5 } from "fs";
869
922
  import { homedir as homedir3 } from "os";
870
923
  import { join as join4 } from "path";
871
924
  var ALIAS_MAP, VALID_ALIASES, VALID_EFFORTS, claudeRegistry, claudeRegistryOperations;
@@ -917,8 +970,8 @@ var init_claude_registry = __esm(() => {
917
970
  const projectsRoot = join4(homedir3(), ".claude", "projects");
918
971
  const destDir = join4(projectsRoot, encodeClaudeCwd(cwd));
919
972
  const destPath = join4(destDir, `${result.sessionId}.jsonl`);
920
- if (!existsSync4(destPath)) {
921
- const sourcePath = readdirSync2(projectsRoot).map((d) => join4(projectsRoot, d, `${result.sessionId}.jsonl`)).find((p) => existsSync4(p));
973
+ if (!existsSync5(destPath)) {
974
+ const sourcePath = readdirSync2(projectsRoot).map((d) => join4(projectsRoot, d, `${result.sessionId}.jsonl`)).find((p) => existsSync5(p));
922
975
  if (!sourcePath) {
923
976
  throw new Error(`claude forkSession: fork rollout ${result.sessionId}.jsonl not found under ${projectsRoot}`);
924
977
  }
@@ -936,7 +989,7 @@ var init_claude_registry = __esm(() => {
936
989
  for (const sid of sessionIds) {
937
990
  const path = join4(projectsDir, `${sid}.jsonl`);
938
991
  try {
939
- unlinkSync4(path);
992
+ unlinkSync5(path);
940
993
  } catch (e) {
941
994
  if (e?.code !== "ENOENT") {
942
995
  logger.warn({ err: e, path }, "claude cleanupRollouts: unlink failed");
@@ -952,27 +1005,27 @@ var init_claude_registry = __esm(() => {
952
1005
  });
953
1006
 
954
1007
  // ../../packages/core/src/version.ts
955
- var NEGOTIUM_VERSION = "0.2.17";
1008
+ var NEGOTIUM_VERSION = "0.2.19";
956
1009
 
957
1010
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
958
1011
  import { spawn as spawn2 } from "child_process";
959
1012
  import { randomUUID as randomUUID3 } from "crypto";
960
1013
  import {
961
- chmodSync as chmodSync2,
1014
+ chmodSync as chmodSync3,
962
1015
  copyFileSync,
963
- existsSync as existsSync5,
1016
+ existsSync as existsSync6,
964
1017
  mkdtempSync,
965
- readFileSync as readFileSync5,
1018
+ readFileSync as readFileSync6,
966
1019
  renameSync as renameSync4,
967
1020
  rmSync,
968
- unlinkSync as unlinkSync5,
1021
+ unlinkSync as unlinkSync6,
969
1022
  writeFileSync as writeFileSync5
970
1023
  } from "fs";
971
1024
  import { createRequire as createRequire2 } from "module";
972
1025
  import { tmpdir } from "os";
973
1026
  import { dirname as dirname4, join as join5 } from "path";
974
1027
  function readPackageVersion(packageJsonPath) {
975
- const parsed = JSON.parse(readFileSync5(packageJsonPath, "utf8"));
1028
+ const parsed = JSON.parse(readFileSync6(packageJsonPath, "utf8"));
976
1029
  if (typeof parsed.version !== "string" || !parsed.version.trim()) {
977
1030
  throw new Error(`Codex package has no valid version: ${packageJsonPath}`);
978
1031
  }
@@ -994,7 +1047,7 @@ function parseCodexModelCache(contents, sourcePath) {
994
1047
  return parsed;
995
1048
  }
996
1049
  function readCodexModelCache(cachePath) {
997
- const contents = readFileSync5(cachePath, "utf8");
1050
+ const contents = readFileSync6(cachePath, "utf8");
998
1051
  return { contents, parsed: parseCodexModelCache(contents, cachePath) };
999
1052
  }
1000
1053
  function readCompatibleCodexModelCache(cachePath) {
@@ -1006,16 +1059,16 @@ function readCompatibleCodexModelCache(cachePath) {
1006
1059
  return cache;
1007
1060
  }
1008
1061
  function writePrivateFileAtomic(path, contents) {
1009
- if (existsSync5(path) && readFileSync5(path, "utf8") === contents)
1062
+ if (existsSync6(path) && readFileSync6(path, "utf8") === contents)
1010
1063
  return;
1011
1064
  const tempPath = `${path}.${process.pid}.${randomUUID3()}.tmp`;
1012
1065
  try {
1013
1066
  writeFileSync5(tempPath, contents, { encoding: "utf8", mode: 384 });
1014
1067
  renameSync4(tempPath, path);
1015
- chmodSync2(path, 384);
1068
+ chmodSync3(path, 384);
1016
1069
  } finally {
1017
1070
  try {
1018
- unlinkSync5(tempPath);
1071
+ unlinkSync6(tempPath);
1019
1072
  } catch {}
1020
1073
  }
1021
1074
  }
@@ -1043,7 +1096,7 @@ async function bootstrapCodexModelCache(codexHome, cachePath) {
1043
1096
  } catch {}
1044
1097
  if (error)
1045
1098
  reject(error);
1046
- else if (!existsSync5(cachePath))
1099
+ else if (!existsSync6(cachePath))
1047
1100
  reject(new Error("Codex did not create its model cache"));
1048
1101
  else
1049
1102
  resolve4();
@@ -1111,12 +1164,12 @@ async function bootstrapIsolatedCodexModelCache(authFilePath, bootstrap) {
1111
1164
  try {
1112
1165
  const isolatedAuthPath = join5(isolatedHome, "auth.json");
1113
1166
  copyFileSync(authFilePath, isolatedAuthPath);
1114
- chmodSync2(isolatedAuthPath, 384);
1167
+ chmodSync3(isolatedAuthPath, 384);
1115
1168
  const sourceConfigPath = join5(sourceHome, "config.toml");
1116
- if (existsSync5(sourceConfigPath)) {
1169
+ if (existsSync6(sourceConfigPath)) {
1117
1170
  const isolatedConfigPath = join5(isolatedHome, "config.toml");
1118
1171
  copyFileSync(sourceConfigPath, isolatedConfigPath);
1119
- chmodSync2(isolatedConfigPath, 384);
1172
+ chmodSync3(isolatedConfigPath, 384);
1120
1173
  }
1121
1174
  await bootstrap(isolatedHome, isolatedCachePath);
1122
1175
  return readCompatibleCodexModelCache(isolatedCachePath).contents;
@@ -1128,7 +1181,7 @@ async function ensureCodexModelCache(authFilePath, bootstrap = bootstrapCodexMod
1128
1181
  const codexHome = dirname4(authFilePath);
1129
1182
  const configuredCachePath = process.env.NEGOTIUM_CODEX_MODELS_CACHE_FILE;
1130
1183
  if (configuredCachePath) {
1131
- if (!existsSync5(configuredCachePath)) {
1184
+ if (!existsSync6(configuredCachePath)) {
1132
1185
  throw new Error(`Configured Codex model cache does not exist: ${configuredCachePath}`);
1133
1186
  }
1134
1187
  readCompatibleCodexModelCache(configuredCachePath);
@@ -1136,14 +1189,14 @@ async function ensureCodexModelCache(authFilePath, bootstrap = bootstrapCodexMod
1136
1189
  }
1137
1190
  const bundledCachePath = bundledCodexModelCachePath(authFilePath);
1138
1191
  const sharedCachePath = join5(codexHome, "models_cache.json");
1139
- if (existsSync5(sharedCachePath)) {
1192
+ if (existsSync6(sharedCachePath)) {
1140
1193
  try {
1141
1194
  const shared = readCompatibleCodexModelCache(sharedCachePath);
1142
1195
  writePrivateFileAtomic(bundledCachePath, shared.contents);
1143
1196
  return bundledCachePath;
1144
1197
  } catch {}
1145
1198
  }
1146
- if (existsSync5(bundledCachePath)) {
1199
+ if (existsSync6(bundledCachePath)) {
1147
1200
  try {
1148
1201
  readCompatibleCodexModelCache(bundledCachePath);
1149
1202
  return bundledCachePath;
@@ -2128,6 +2181,8 @@ var init_mcp_config = __esm(() => {
2128
2181
  return env ? buildStdioMcpServer(agent, CANONICAL_MCP_PROXY_SERVER, ["--surface=wiki"], env) : null;
2129
2182
  }
2130
2183
  const args = [`--user-id=${userId}`];
2184
+ if (topicId)
2185
+ args.push(`--current-topic-id=${topicId}`);
2131
2186
  const resolvedWikiTopicId = wikiTopicId ?? topicId ?? (session !== "dm" ? session : undefined);
2132
2187
  if (resolvedWikiTopicId)
2133
2188
  args.push(`--topic-id=${resolvedWikiTopicId}`);
@@ -2186,59 +2241,6 @@ var init_mcp_config = __esm(() => {
2186
2241
  nodeMcpEntries = [];
2187
2242
  });
2188
2243
 
2189
- // ../../packages/core/src/storage/sqlite.ts
2190
- var isBun, Database;
2191
- var init_sqlite = __esm(async () => {
2192
- isBun = typeof process.versions.bun === "string";
2193
- if (isBun) {
2194
- ({ Database } = await import("bun:sqlite"));
2195
- } else {
2196
- const nodeSqliteSpecifier = ["node", "sqlite"].join(":");
2197
- const { DatabaseSync } = await import(nodeSqliteSpecifier);
2198
-
2199
- class NodeDatabase {
2200
- #db;
2201
- constructor(path, options = {}) {
2202
- this.#db = options.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
2203
- }
2204
- query(sql) {
2205
- return this.#db.prepare(sql);
2206
- }
2207
- prepare(sql) {
2208
- return this.#db.prepare(sql);
2209
- }
2210
- exec(sql) {
2211
- this.#db.exec(sql);
2212
- }
2213
- run(sql, ...params) {
2214
- return this.#db.prepare(sql).run(...params);
2215
- }
2216
- transaction(fn) {
2217
- const run = (begin) => (...args) => {
2218
- this.#db.exec(begin);
2219
- try {
2220
- const result = fn(...args);
2221
- this.#db.exec("COMMIT");
2222
- return result;
2223
- } catch (err) {
2224
- this.#db.exec("ROLLBACK");
2225
- throw err;
2226
- }
2227
- };
2228
- const tx = run("BEGIN");
2229
- tx.deferred = run("BEGIN DEFERRED");
2230
- tx.immediate = run("BEGIN IMMEDIATE");
2231
- tx.exclusive = run("BEGIN EXCLUSIVE");
2232
- return tx;
2233
- }
2234
- close() {
2235
- this.#db.close();
2236
- }
2237
- }
2238
- Database = NodeDatabase;
2239
- }
2240
- });
2241
-
2242
2244
  // ../../packages/core/src/storage/vault-crypto-core.ts
2243
2245
  import { createCipheriv, createDecipheriv, createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
2244
2246
  function encryptionKey(masterKey) {
@@ -2295,7 +2297,7 @@ var init_vault_crypto = __esm(() => {
2295
2297
  });
2296
2298
 
2297
2299
  // ../../packages/core/src/storage/vault.ts
2298
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync6 } from "fs";
2300
+ import { chmodSync as chmodSync4, mkdirSync as mkdirSync6 } from "fs";
2299
2301
  import { join as join6 } from "path";
2300
2302
  function initializeVaultDatabase(database) {
2301
2303
  database.exec("PRAGMA journal_mode = WAL");
@@ -2335,7 +2337,7 @@ function openVaultDatabase(dataDir) {
2335
2337
  const path = join6(vaultDir, "vault.db");
2336
2338
  mkdirSync6(vaultDir, { recursive: true, mode: 448 });
2337
2339
  const database = new Database(path, { create: true });
2338
- chmodSync3(path, 384);
2340
+ chmodSync4(path, 384);
2339
2341
  initializeVaultDatabase(database);
2340
2342
  return database;
2341
2343
  }
@@ -2496,7 +2498,7 @@ var init_execution_host = __esm(async () => {
2496
2498
 
2497
2499
  // ../../packages/core/src/agents/rollout/codex.ts
2498
2500
  import { randomBytes as randomBytes4 } from "crypto";
2499
- import { existsSync as existsSync6, readFileSync as readFileSync6, realpathSync as realpathSync2, statSync as statSync3, unlinkSync as unlinkSync6 } from "fs";
2501
+ import { existsSync as existsSync7, readFileSync as readFileSync7, realpathSync as realpathSync2, statSync as statSync3, unlinkSync as unlinkSync7 } from "fs";
2500
2502
  import { basename as basename2, dirname as dirname6, join as join7, resolve as resolve5 } from "path";
2501
2503
  function codexSessionsDir() {
2502
2504
  return join7(hostedCodexHomePath(), "sessions");
@@ -2504,7 +2506,7 @@ function codexSessionsDir() {
2504
2506
  function loadCodexShell() {
2505
2507
  if (_shellCache)
2506
2508
  return _shellCache;
2507
- const raw = readFileSync6(join7(FIXTURES_DIR, "codex-shell.jsonl"), "utf8");
2509
+ const raw = readFileSync7(join7(FIXTURES_DIR, "codex-shell.jsonl"), "utf8");
2508
2510
  const lines = parseJsonlText(raw);
2509
2511
  if (lines.length < 5) {
2510
2512
  throw new Error(`loadCodexShell: expected >=5 entries in codex-shell.jsonl, got ${lines.length}`);
@@ -2690,7 +2692,7 @@ function readCodexPatchCallIds(threadId) {
2690
2692
  if (!path)
2691
2693
  return [];
2692
2694
  try {
2693
- return extractCodexPatchCallIds(readFileSync6(path, "utf8"));
2695
+ return extractCodexPatchCallIds(readFileSync7(path, "utf8"));
2694
2696
  } catch (error) {
2695
2697
  logger.debug({ error, threadId }, "codex patch ids: rollout read failed");
2696
2698
  return [];
@@ -2701,7 +2703,7 @@ function readLatestCodexPatchPreview(threadId, expectedPaths, consumedCallIds =
2701
2703
  if (!path)
2702
2704
  return;
2703
2705
  try {
2704
- return extractLatestCodexPatchPreview(readFileSync6(path, "utf8"), expectedPaths, consumedCallIds, expectedCallId);
2706
+ return extractLatestCodexPatchPreview(readFileSync7(path, "utf8"), expectedPaths, consumedCallIds, expectedCallId);
2705
2707
  } catch (error) {
2706
2708
  logger.debug({ error, threadId }, "codex patch preview: rollout read failed");
2707
2709
  return;
@@ -2759,7 +2761,7 @@ function readLatestCodexContextUsage(threadId) {
2759
2761
  if (!path)
2760
2762
  return;
2761
2763
  try {
2762
- return extractLatestCodexContextUsage(readFileSync6(path, "utf8"));
2764
+ return extractLatestCodexContextUsage(readFileSync7(path, "utf8"));
2763
2765
  } catch (error) {
2764
2766
  logger.debug({ error, threadId }, "codex context usage: rollout read failed");
2765
2767
  return;
@@ -2770,7 +2772,7 @@ function readLatestCodexTokenTotals(threadId) {
2770
2772
  if (!path)
2771
2773
  return;
2772
2774
  try {
2773
- return extractLatestCodexTokenTotals(readFileSync6(path, "utf8"));
2775
+ return extractLatestCodexTokenTotals(readFileSync7(path, "utf8"));
2774
2776
  } catch (error) {
2775
2777
  logger.debug({ error, threadId }, "codex token totals: rollout read failed");
2776
2778
  return;
@@ -2781,7 +2783,7 @@ function migrateCodexRolloutNativeMultiAgentMetadata(threadId) {
2781
2783
  if (!path)
2782
2784
  return false;
2783
2785
  try {
2784
- const entries = parseJsonlText(readFileSync6(path, "utf8"));
2786
+ const entries = parseJsonlText(readFileSync7(path, "utf8"));
2785
2787
  let changed = false;
2786
2788
  for (const entry of entries) {
2787
2789
  if (entry.type !== "session_meta" && entry.type !== "turn_context")
@@ -2811,7 +2813,7 @@ function latestCodexRolloutPath(threadId) {
2811
2813
  if (buckets) {
2812
2814
  for (const bucket of buckets) {
2813
2815
  const dir = join7(sessionsDir, bucket);
2814
- if (!existsSync6(dir))
2816
+ if (!existsSync7(dir))
2815
2817
  continue;
2816
2818
  const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
2817
2819
  for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
@@ -2944,14 +2946,14 @@ function sweepPriorRolloutsForThread(threadId) {
2944
2946
  }
2945
2947
  for (const bucket of buckets) {
2946
2948
  const dir = join7(sessionsDir, bucket);
2947
- if (!existsSync6(dir))
2949
+ if (!existsSync7(dir))
2948
2950
  continue;
2949
2951
  try {
2950
2952
  const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
2951
2953
  for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
2952
2954
  const fullPath = join7(dir, rel);
2953
2955
  try {
2954
- unlinkSync6(fullPath);
2956
+ unlinkSync7(fullPath);
2955
2957
  } catch (e) {
2956
2958
  if (e?.code !== "ENOENT") {
2957
2959
  logger.warn({ err: e, path: fullPath }, "writeCodexRollout: prior-rollout unlink failed (continuing)");
@@ -2969,7 +2971,7 @@ function sweepPriorRolloutsFullTree(threadId, sessionsDir) {
2969
2971
  for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
2970
2972
  const fullPath = join7(sessionsDir, rel);
2971
2973
  try {
2972
- unlinkSync6(fullPath);
2974
+ unlinkSync7(fullPath);
2973
2975
  } catch (e) {
2974
2976
  if (e?.code !== "ENOENT") {
2975
2977
  logger.warn({ err: e, path: fullPath }, "writeCodexRollout: prior-rollout unlink failed (continuing)");
@@ -3146,7 +3148,7 @@ var init_codex_app_server = __esm(async () => {
3146
3148
  });
3147
3149
 
3148
3150
  // ../../packages/core/src/agents/codex-registry.ts
3149
- import { existsSync as existsSync7, unlinkSync as unlinkSync7 } from "fs";
3151
+ import { existsSync as existsSync8, unlinkSync as unlinkSync8 } from "fs";
3150
3152
  import { join as join8 } from "path";
3151
3153
  var VALID_EFFORTS2, codexRegistry, codexRegistryOperations;
3152
3154
  var init_codex_registry = __esm(async () => {
@@ -3193,7 +3195,7 @@ var init_codex_registry = __esm(async () => {
3193
3195
  if (sessionIds.length === 0)
3194
3196
  return;
3195
3197
  const sessionsDir = join8(hostedCodexHomePath(), "sessions");
3196
- if (!existsSync7(sessionsDir))
3198
+ if (!existsSync8(sessionsDir))
3197
3199
  return;
3198
3200
  const failures = [];
3199
3201
  for (const tid of sessionIds) {
@@ -3202,7 +3204,7 @@ var init_codex_registry = __esm(async () => {
3202
3204
  for await (const rel of glob.scan({ cwd: sessionsDir, onlyFiles: true })) {
3203
3205
  const path = join8(sessionsDir, rel);
3204
3206
  try {
3205
- unlinkSync7(path);
3207
+ unlinkSync8(path);
3206
3208
  } catch (e) {
3207
3209
  if (e?.code !== "ENOENT") {
3208
3210
  logger.warn({ err: e, path }, "codex cleanupRollouts: unlink failed");
@@ -3224,7 +3226,7 @@ var init_codex_registry = __esm(async () => {
3224
3226
 
3225
3227
  // ../../packages/core/src/agents/maestro-registry.ts
3226
3228
  import { randomUUID as randomUUID4 } from "crypto";
3227
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
3229
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
3228
3230
  import { homedir as homedir4 } from "os";
3229
3231
  import { join as join9, resolve as resolve6 } from "path";
3230
3232
  function maestroSessionsDir() {
@@ -3237,10 +3239,10 @@ function maestroActiveSessionPath(sessionId) {
3237
3239
  return join9(maestroSessionsDir(), `${sessionId}.active.jsonl`);
3238
3240
  }
3239
3241
  function existingCreatedAt(path) {
3240
- if (!existsSync8(path))
3242
+ if (!existsSync9(path))
3241
3243
  return;
3242
3244
  try {
3243
- const firstLine = readFileSync7(path, "utf8").split(`
3245
+ const firstLine = readFileSync8(path, "utf8").split(`
3244
3246
  `, 1)[0];
3245
3247
  const parsed = JSON.parse(firstLine);
3246
3248
  return typeof parsed._meta?.createdAt === "string" ? parsed._meta.createdAt : undefined;
@@ -3321,7 +3323,7 @@ var init_maestro_registry = __esm(() => {
3321
3323
  if (!parentMessages) {
3322
3324
  throw new Error(`Maestro parent session not found: ${options.parentSessionId}`);
3323
3325
  }
3324
- const parentHasActiveProjection = existsSync8(maestroActiveSessionPath(options.parentSessionId));
3326
+ const parentHasActiveProjection = existsSync9(maestroActiveSessionPath(options.parentSessionId));
3325
3327
  const fork = forkSessionAt({
3326
3328
  parentSessionId: options.parentSessionId,
3327
3329
  messageIndex: parentMessages.length,
@@ -3336,7 +3338,7 @@ var init_maestro_registry = __esm(() => {
3336
3338
  return {
3337
3339
  forkId: fork.sessionId,
3338
3340
  rolloutPath: fork.rolloutPath,
3339
- ...existsSync8(activePath) ? { cleanupPaths: [activePath] } : {}
3341
+ ...existsSync9(activePath) ? { cleanupPaths: [activePath] } : {}
3340
3342
  };
3341
3343
  },
3342
3344
  async cleanupRollouts(options) {
@@ -3348,7 +3350,7 @@ var init_maestro_registry = __esm(() => {
3348
3350
  const remaining = [
3349
3351
  maestroSessionPath(sessionId),
3350
3352
  maestroActiveSessionPath(sessionId)
3351
- ].filter(existsSync8);
3353
+ ].filter(existsSync9);
3352
3354
  if (remaining.length > 0) {
3353
3355
  throw new Error(`Maestro session files remain after cleanup: ${remaining.join(", ")}`);
3354
3356
  }
@@ -4140,6 +4142,7 @@ function initializeApiTopicsSchema() {
4140
4142
  last_message_at TEXT,
4141
4143
  parent_topic_id TEXT,
4142
4144
  memory_topic_id TEXT,
4145
+ memory_key TEXT,
4143
4146
  is_fork INTEGER NOT NULL DEFAULT 0 CHECK (is_fork IN (0,1)),
4144
4147
  is_subagent INTEGER NOT NULL DEFAULT 0 CHECK (is_subagent IN (0,1)),
4145
4148
  visibility TEXT NOT NULL DEFAULT 'visible' CHECK (visibility IN ('visible','hidden')),
@@ -4189,8 +4192,8 @@ function initializeApiTopicsSchema() {
4189
4192
  const legacyBaseEffort = row.base_effort ?? row.default_effort;
4190
4193
  db.query(`INSERT INTO api_topics_next
4191
4194
  (id,title,kind,description,agent,base_model,base_effort,response_policy,
4192
- created_at,last_message_at,parent_topic_id,memory_topic_id,is_fork,is_subagent,visibility,access_mode,session_id)
4193
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(String(row.id), String(row.title), normalized.kind, typeof row.description === "string" ? row.description : null, normalized.agent ?? null, typeof legacyBaseModel === "string" ? legacyBaseModel : null, typeof legacyBaseEffort === "string" ? legacyBaseEffort : null, normalized.aiMode, String(row.created_at), typeof row.last_message_at === "string" ? row.last_message_at : null, typeof row.parent_topic_id === "string" ? row.parent_topic_id : null, typeof row.memory_topic_id === "string" ? row.memory_topic_id : null, Number(row.is_fork ?? 0) !== 0 ? 1 : 0, Number(row.is_subagent ?? 0) !== 0 ? 1 : 0, row.visibility === "hidden" ? "hidden" : "visible", row.access_mode === "shared" ? "shared" : "private", typeof row.session_id === "string" ? row.session_id : null);
4195
+ created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,session_id)
4196
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(String(row.id), String(row.title), normalized.kind, typeof row.description === "string" ? row.description : null, normalized.agent ?? null, typeof legacyBaseModel === "string" ? legacyBaseModel : null, typeof legacyBaseEffort === "string" ? legacyBaseEffort : null, normalized.aiMode, String(row.created_at), typeof row.last_message_at === "string" ? row.last_message_at : null, typeof row.parent_topic_id === "string" ? row.parent_topic_id : null, typeof row.memory_topic_id === "string" ? row.memory_topic_id : null, typeof row.memory_key === "string" ? row.memory_key : null, Number(row.is_fork ?? 0) !== 0 ? 1 : 0, Number(row.is_subagent ?? 0) !== 0 ? 1 : 0, row.visibility === "hidden" ? "hidden" : "visible", row.access_mode === "shared" ? "shared" : "private", typeof row.session_id === "string" ? row.session_id : null);
4194
4197
  }
4195
4198
  db.exec("DROP TABLE IF EXISTS topic_members");
4196
4199
  db.exec("DROP TABLE api_topics");
@@ -4259,6 +4262,9 @@ function initializeApiTopicsSchema() {
4259
4262
  if (!tableColumns("api_topics").has("memory_topic_id")) {
4260
4263
  db.exec("ALTER TABLE api_topics ADD COLUMN memory_topic_id TEXT");
4261
4264
  }
4265
+ if (!tableColumns("api_topics").has("memory_key")) {
4266
+ db.exec("ALTER TABLE api_topics ADD COLUMN memory_key TEXT");
4267
+ }
4262
4268
  db.exec(`
4263
4269
  UPDATE api_topics
4264
4270
  SET browser_profile_owner = (
@@ -4319,6 +4325,7 @@ function rowToDto2(r, participants = getTopicParticipants(r.id), tellTargets) {
4319
4325
  lastMessageAt: r.last_message_at ?? new Date().toISOString(),
4320
4326
  parentTopicId: r.parent_topic_id ?? undefined,
4321
4327
  memoryTopicId: r.memory_topic_id ?? undefined,
4328
+ memoryKey: r.memory_key ?? undefined,
4322
4329
  isFork: r.is_fork !== 0,
4323
4330
  ...r.is_subagent !== 0 ? { isSubagent: true } : {},
4324
4331
  ...r.is_subagent !== 0 ? {
@@ -4390,9 +4397,9 @@ function upsertTopic(t) {
4390
4397
  db.transaction(() => {
4391
4398
  db.query(`INSERT INTO api_topics
4392
4399
  (id,title,kind,description,agent,base_model,base_effort,response_policy,
4393
- created_at,last_message_at,parent_topic_id,memory_topic_id,is_fork,is_subagent,visibility,access_mode,
4400
+ created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,
4394
4401
  subagent_report_mode)
4395
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
4402
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
4396
4403
  ON CONFLICT(id) DO UPDATE SET
4397
4404
  title = excluded.title,
4398
4405
  kind = excluded.kind,
@@ -4405,11 +4412,12 @@ function upsertTopic(t) {
4405
4412
  last_message_at = excluded.last_message_at,
4406
4413
  parent_topic_id = excluded.parent_topic_id,
4407
4414
  memory_topic_id = excluded.memory_topic_id,
4415
+ memory_key = excluded.memory_key,
4408
4416
  is_fork = excluded.is_fork,
4409
4417
  is_subagent = excluded.is_subagent,
4410
4418
  visibility = excluded.visibility,
4411
4419
  access_mode = excluded.access_mode,
4412
- subagent_report_mode = excluded.subagent_report_mode`).run(t.id, t.title, normalized.kind, t.description ?? null, normalized.agent ?? null, t.defaultModel ?? null, t.defaultEffort ?? null, normalized.aiMode, t.createdAt, t.lastMessageAt ?? null, t.parentTopicId ?? null, t.memoryTopicId ?? null, t.isFork ? 1 : 0, t.isSubagent ? 1 : 0, normalizeTopicVisibility(t.visibility), normalizeTopicAccessMode(t.accessMode), t.subagentReportMode ?? "auto");
4420
+ subagent_report_mode = excluded.subagent_report_mode`).run(t.id, t.title, normalized.kind, t.description ?? null, normalized.agent ?? null, t.defaultModel ?? null, t.defaultEffort ?? null, normalized.aiMode, t.createdAt, t.lastMessageAt ?? null, t.parentTopicId ?? null, t.memoryTopicId ?? null, t.memoryKey ?? null, t.isFork ? 1 : 0, t.isSubagent ? 1 : 0, normalizeTopicVisibility(t.visibility), normalizeTopicAccessMode(t.accessMode), t.subagentReportMode ?? "auto");
4413
4421
  db.query("DELETE FROM topic_members WHERE topic_id = ?").run(t.id);
4414
4422
  for (const participant of t.participants) {
4415
4423
  db.query("INSERT INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)").run(t.id, participant.userId, participant.role);
@@ -5096,7 +5104,7 @@ var init_registry = __esm(async () => {
5096
5104
  });
5097
5105
 
5098
5106
  // ../../packages/core/src/agents/fork.ts
5099
- import { existsSync as existsSync10, unlinkSync as unlinkSync8 } from "fs";
5107
+ import { existsSync as existsSync11, unlinkSync as unlinkSync9 } from "fs";
5100
5108
  function createAgentForkHelpers(host) {
5101
5109
  return {
5102
5110
  async forkAgentSession(options) {
@@ -5133,8 +5141,8 @@ var init_fork = __esm(async () => {
5133
5141
  await init_registry();
5134
5142
  defaultForkHelpers = createAgentForkHelpers({
5135
5143
  forkSession: (agent, options) => getRegistryOperations(agent).forkSession(options),
5136
- exists: existsSync10,
5137
- unlink: unlinkSync8,
5144
+ exists: existsSync11,
5145
+ unlink: unlinkSync9,
5138
5146
  warn: (details, message) => logger.warn(details, message)
5139
5147
  });
5140
5148
  });
@@ -5533,7 +5541,7 @@ var init_runtime_process_leases = __esm(async () => {
5533
5541
 
5534
5542
  // ../../packages/core/src/platform/playwright/browser-processes.ts
5535
5543
  import { execFileSync as execFileSync3 } from "child_process";
5536
- import { readdirSync as readdirSync4, unlinkSync as unlinkSync9 } from "fs";
5544
+ import { readdirSync as readdirSync4, unlinkSync as unlinkSync10 } from "fs";
5537
5545
  import { createServer } from "net";
5538
5546
  import { resolve as resolve8, sep } from "path";
5539
5547
  function isPortInUse(port) {
@@ -5662,7 +5670,7 @@ function cleanSingletonFiles(userDataDir) {
5662
5670
  for (const f of files) {
5663
5671
  if (f.startsWith("Singleton")) {
5664
5672
  try {
5665
- unlinkSync9(resolve8(userDataDir, f));
5673
+ unlinkSync10(resolve8(userDataDir, f));
5666
5674
  logger.info({ file: f, userDataDir }, "Removed stale Singleton file");
5667
5675
  } catch (e) {
5668
5676
  logger.warn({ err: e, file: f }, "Failed to remove stale Chrome Singleton file");
@@ -5803,7 +5811,7 @@ var init_vault_public = __esm(async () => {
5803
5811
 
5804
5812
  // ../../packages/core/src/platform/playwright/vault-broker.ts
5805
5813
  import { randomBytes as randomBytes5, timingSafeEqual as timingSafeEqual2 } from "crypto";
5806
- import { chmodSync as chmodSync4 } from "fs";
5814
+ import { chmodSync as chmodSync5 } from "fs";
5807
5815
  import { createServer as createServer2 } from "net";
5808
5816
  import { tmpdir as tmpdir2 } from "os";
5809
5817
  import { join as join12 } from "path";
@@ -5999,7 +6007,7 @@ async function createBrowserVaultBroker(userId) {
5999
6007
  server.once("error", reject);
6000
6008
  server.listen(socketPath, resolveListen);
6001
6009
  });
6002
- chmodSync4(socketPath, 384);
6010
+ chmodSync5(socketPath, 384);
6003
6011
  let closed = false;
6004
6012
  return {
6005
6013
  socketPath,
@@ -6107,11 +6115,11 @@ import { execFileSync as execFileSync4, spawn as spawn6 } from "child_process";
6107
6115
  import { createHash as createHash3, randomBytes as randomBytes6, timingSafeEqual as timingSafeEqual3 } from "crypto";
6108
6116
  import {
6109
6117
  cpSync,
6110
- existsSync as existsSync11,
6118
+ existsSync as existsSync12,
6111
6119
  mkdirSync as mkdirSync9,
6112
- readFileSync as readFileSync9,
6120
+ readFileSync as readFileSync10,
6113
6121
  rmSync as rmSync2,
6114
- unlinkSync as unlinkSync10,
6122
+ unlinkSync as unlinkSync11,
6115
6123
  writeFileSync as writeFileSync7
6116
6124
  } from "fs";
6117
6125
  import { dirname as dirname8, join as join13, resolve as resolve10 } from "path";
@@ -6121,7 +6129,7 @@ function removeDefaultProfileDataDir(userDataDir) {
6121
6129
  if (dirname8(target) !== root) {
6122
6130
  throw new Error(`Refusing to delete browser profile outside managed root: ${target}`);
6123
6131
  }
6124
- const existed = existsSync11(target);
6132
+ const existed = existsSync12(target);
6125
6133
  rmSync2(target, { recursive: true, force: true });
6126
6134
  return existed;
6127
6135
  }
@@ -6211,7 +6219,7 @@ function writePortFile(instanceKey, port) {
6211
6219
  }
6212
6220
  function deletePortFile(instanceKey) {
6213
6221
  try {
6214
- unlinkSync10(join13(managerHost.portsDir, portFileName(instanceKey)));
6222
+ unlinkSync11(join13(managerHost.portsDir, portFileName(instanceKey)));
6215
6223
  } catch (e) {
6216
6224
  if (e.code === "ENOENT")
6217
6225
  return;
@@ -6696,11 +6704,11 @@ async function cloneProfileForChild(opts) {
6696
6704
  killInstance(dstKey);
6697
6705
  await delay(500);
6698
6706
  }
6699
- if (!existsSync11(srcDir)) {
6707
+ if (!existsSync12(srcDir)) {
6700
6708
  return { copied: false, srcDir, dstDir, reason: "src-missing" };
6701
6709
  }
6702
6710
  try {
6703
- if (existsSync11(dstDir)) {
6711
+ if (existsSync12(dstDir)) {
6704
6712
  rmSync2(dstDir, { recursive: true, force: true });
6705
6713
  }
6706
6714
  mkdirSync9(dirname8(dstDir), { recursive: true });
@@ -6721,7 +6729,7 @@ async function cloneProfileForChild(opts) {
6721
6729
  cleanSingletonFiles(dstDir);
6722
6730
  for (const f of ["DevToolsActivePort", "LOCK"]) {
6723
6731
  try {
6724
- unlinkSync10(resolve10(dstDir, f));
6732
+ unlinkSync11(resolve10(dstDir, f));
6725
6733
  } catch {}
6726
6734
  }
6727
6735
  logger.info({ srcKey, dstKey, srcDir, dstDir }, "Cloned Playwright profile for child topic");
@@ -7269,11 +7277,11 @@ var init_api_topic_config = __esm(async () => {
7269
7277
 
7270
7278
  // ../../packages/core/src/storage/conversations.ts
7271
7279
  import {
7272
- existsSync as existsSync12,
7280
+ existsSync as existsSync13,
7273
7281
  mkdirSync as mkdirSync10,
7274
- readFileSync as readFileSync10,
7282
+ readFileSync as readFileSync11,
7275
7283
  renameSync as renameSync5,
7276
- unlinkSync as unlinkSync11,
7284
+ unlinkSync as unlinkSync12,
7277
7285
  writeFileSync as writeFileSync8
7278
7286
  } from "fs";
7279
7287
  import { dirname as dirname9, join as join14 } from "path";
@@ -7292,7 +7300,7 @@ function getActiveConversationPath(userId, topicName) {
7292
7300
  return rawPath.endsWith(".jsonl") ? `${rawPath.slice(0, -".jsonl".length)}.active.jsonl` : `${rawPath}.active`;
7293
7301
  }
7294
7302
  function hasActiveConversation(userId, topicName) {
7295
- return existsSync12(getActiveConversationPath(userId, topicName));
7303
+ return existsSync13(getActiveConversationPath(userId, topicName));
7296
7304
  }
7297
7305
  function appendConversationEvent(userId, topicName, agent, event) {
7298
7306
  try {
@@ -7314,7 +7322,7 @@ function appendConversationEventStrict(userId, topicName, agent, event) {
7314
7322
  mkdirSync10(dirname9(path), { recursive: true });
7315
7323
  appendJsonlLine(path, line);
7316
7324
  const activePath = getActiveConversationPath(userId, topicName);
7317
- if (existsSync12(activePath)) {
7325
+ if (existsSync13(activePath)) {
7318
7326
  try {
7319
7327
  appendJsonlLine(activePath, line);
7320
7328
  } catch (cause) {
@@ -7324,11 +7332,11 @@ function appendConversationEventStrict(userId, topicName, agent, event) {
7324
7332
  }
7325
7333
  function readConversationPath(path) {
7326
7334
  const out = [];
7327
- if (!existsSync12(path))
7335
+ if (!existsSync13(path))
7328
7336
  return out;
7329
7337
  let raw;
7330
7338
  try {
7331
- raw = readFileSync10(path, "utf8");
7339
+ raw = readFileSync11(path, "utf8");
7332
7340
  } catch (err) {
7333
7341
  logger.warn({ err, path }, "readConversation: read failed");
7334
7342
  return out;
@@ -7347,7 +7355,7 @@ function readConversationPath(path) {
7347
7355
  }
7348
7356
  function readConversation(userId, topicName) {
7349
7357
  const activePath = getActiveConversationPath(userId, topicName);
7350
- return readConversationPath(existsSync12(activePath) ? activePath : getConversationPath(userId, topicName));
7358
+ return readConversationPath(existsSync13(activePath) ? activePath : getConversationPath(userId, topicName));
7351
7359
  }
7352
7360
  function readRawConversation(userId, topicName) {
7353
7361
  return readConversationPath(getConversationPath(userId, topicName));
@@ -7368,7 +7376,7 @@ function replaceConversationPathStrict(path, entries) {
7368
7376
  renameSync5(tempPath, path);
7369
7377
  } catch (error) {
7370
7378
  try {
7371
- unlinkSync11(tempPath);
7379
+ unlinkSync12(tempPath);
7372
7380
  } catch {}
7373
7381
  throw error;
7374
7382
  }
@@ -7505,7 +7513,7 @@ RULES:
7505
7513
  - Keep the entire summary under 1500 words.`, CHARS_PER_TEXT_TOKEN = 3.5, CHARS_PER_CJK_TOKEN = 0.9;
7506
7514
 
7507
7515
  // ../../packages/core/src/storage/tasks.ts
7508
- import { existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync11, renameSync as renameSync6, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
7516
+ import { existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync12, renameSync as renameSync6, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
7509
7517
  import { dirname as dirname10, join as join15 } from "path";
7510
7518
  function safeTaskScopeKey(scopeKey) {
7511
7519
  const safe = sanitizeFileName(scopeKey);
@@ -7522,10 +7530,10 @@ function getTaskFilePath(userId, scopeKey) {
7522
7530
  }
7523
7531
  function readTasks(userId, scopeKey) {
7524
7532
  const path = getTaskFilePath(userId, scopeKey);
7525
- if (!existsSync13(path))
7533
+ if (!existsSync14(path))
7526
7534
  return [];
7527
7535
  try {
7528
- const parsed = JSON.parse(readFileSync11(path, "utf-8"));
7536
+ const parsed = JSON.parse(readFileSync12(path, "utf-8"));
7529
7537
  return Array.isArray(parsed?.tasks) ? parsed.tasks : [];
7530
7538
  } catch (e) {
7531
7539
  logger.warn({ err: e, path }, "tasks: failed to read task store");
@@ -7780,7 +7788,7 @@ __export(exports_claude_provider, {
7780
7788
  buildClaudeDisallowedTools: () => buildClaudeDisallowedTools
7781
7789
  });
7782
7790
  import { spawn as spawn7 } from "child_process";
7783
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
7791
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
7784
7792
  import { query } from "@anthropic-ai/claude-agent-sdk";
7785
7793
  function claudeBuiltInTools(opts) {
7786
7794
  return opts.toolPolicy ? [] : undefined;
@@ -7833,7 +7841,7 @@ function buildClaudePrompt(opts) {
7833
7841
  source: {
7834
7842
  type: "base64",
7835
7843
  media_type: attachment.mimeType,
7836
- data: readFileSync12(attachment.path).toString("base64")
7844
+ data: readFileSync13(attachment.path).toString("base64")
7837
7845
  }
7838
7846
  });
7839
7847
  } catch (err) {
@@ -7961,7 +7969,7 @@ function isAbortError(err) {
7961
7969
  }
7962
7970
  async function* claudeProvider(opts) {
7963
7971
  const claudeExecutable = hostedClaudeCodeExecutablePath();
7964
- if (claudeExecutable && !existsSync14(claudeExecutable)) {
7972
+ if (claudeExecutable && !existsSync15(claudeExecutable)) {
7965
7973
  yield {
7966
7974
  type: "error",
7967
7975
  content: `Claude CLI not found at ${claudeExecutable}. Install Claude Code or configure the executable path.`
@@ -8481,7 +8489,7 @@ var init_codex_tree_kill = __esm(() => {
8481
8489
  });
8482
8490
 
8483
8491
  // ../../packages/core/src/agents/tool-format.ts
8484
- import { readFileSync as readFileSync13, statSync as statSync5 } from "fs";
8492
+ import { readFileSync as readFileSync14, statSync as statSync5 } from "fs";
8485
8493
  import { isAbsolute as isAbsolute2, resolve as resolve11 } from "path";
8486
8494
  import { diffLines as computeLineDiff } from "diff";
8487
8495
  function summarizeDisplayText(value2) {
@@ -8810,7 +8818,7 @@ function sourceStartLine(input, before, after, cwd) {
8810
8818
  try {
8811
8819
  if (statSync5(path).size > TOOL_DIFF_SOURCE_MAX_BYTES)
8812
8820
  return 1;
8813
- const source = readFileSync13(path, "utf8").replace(/\r\n?/g, `
8821
+ const source = readFileSync14(path, "utf8").replace(/\r\n?/g, `
8814
8822
  `);
8815
8823
  for (const candidate of [before, after].filter(Boolean)) {
8816
8824
  const index = source.indexOf(candidate);
@@ -9004,9 +9012,9 @@ __export(exports_codex_provider, {
9004
9012
  codexProvider: () => codexProvider
9005
9013
  });
9006
9014
  import { execFileSync as execFileSync6 } from "child_process";
9007
- import { existsSync as existsSync15, readFileSync as readFileSync14, realpathSync as realpathSync4, statSync as statSync6 } from "fs";
9015
+ import { existsSync as existsSync16, readFileSync as readFileSync15, realpathSync as realpathSync4, statSync as statSync6 } from "fs";
9008
9016
  import { homedir as homedir6 } from "os";
9009
- import { dirname as dirname11, isAbsolute as isAbsolute3, join as join16, relative, resolve as resolve12 } from "path";
9017
+ import { dirname as dirname11, isAbsolute as isAbsolute3, join as join16, relative as relative2, resolve as resolve12 } from "path";
9010
9018
  import { Codex } from "@openai/codex-sdk";
9011
9019
  function sameCodexUsage(usage, total) {
9012
9020
  return usage.input_tokens === total.inputTokens && usage.output_tokens === total.outputTokens && (usage.cached_input_tokens ?? 0) === total.cachedInputTokens && (usage.cache_write_input_tokens ?? 0) === total.cacheWriteInputTokens;
@@ -9035,11 +9043,11 @@ function codexMcpServerName(name) {
9035
9043
  }
9036
9044
  function globalCodexMcpServerNames(authFilePath) {
9037
9045
  const configPath = join16(dirname11(authFilePath), "config.toml");
9038
- if (!existsSync15(configPath))
9046
+ if (!existsSync16(configPath))
9039
9047
  return [];
9040
9048
  try {
9041
9049
  const names = new Set;
9042
- for (const line of readFileSync14(configPath, "utf8").split(`
9050
+ for (const line of readFileSync15(configPath, "utf8").split(`
9043
9051
  `)) {
9044
9052
  const match = line.match(/^\s*\[\s*mcp_servers\.((?:"(?:\\.|[^"])*")|(?:'[^']*')|[^.\]\s]+)(?:[.\]])/);
9045
9053
  const raw = match?.[1];
@@ -9136,13 +9144,13 @@ function canonicalPath(path) {
9136
9144
  }
9137
9145
  }
9138
9146
  function textFile(path, maxBytes = CODEX_DIFF_FILE_LIMIT) {
9139
- if (!existsSync15(path))
9147
+ if (!existsSync16(path))
9140
9148
  return null;
9141
9149
  try {
9142
9150
  const byteLimit = Math.min(CODEX_DIFF_FILE_LIMIT, Math.max(0, maxBytes));
9143
9151
  if (statSync6(path).size > byteLimit)
9144
9152
  return;
9145
- const content = readFileSync14(path);
9153
+ const content = readFileSync15(path);
9146
9154
  if (content.byteLength > byteLimit || content.includes(0))
9147
9155
  return;
9148
9156
  return content.toString("utf8");
@@ -9190,7 +9198,7 @@ class CodexFilePreviewTracker {
9190
9198
  if (!this.#root || !this.#baselineAvailable || !succeeded)
9191
9199
  return {};
9192
9200
  const absolute = canonicalPath(isAbsolute3(path) ? path : resolve12(this.#cwd, path));
9193
- const relativePath = relative(this.#root, absolute);
9201
+ const relativePath = relative2(this.#root, absolute);
9194
9202
  if (!relativePath || relativePath.startsWith("..") || isAbsolute3(relativePath))
9195
9203
  return {};
9196
9204
  const before = this.#baseline.has(relativePath) ? this.#baseline.get(relativePath) : this.#headFile(relativePath);
@@ -9348,7 +9356,7 @@ async function* codexProvider(opts) {
9348
9356
  const filePreviews = new CodexFilePreviewTracker(opts.cwd);
9349
9357
  const consumedPatchCallIds = new Set(opts.sessionId ? readCodexPatchCallIds(opts.sessionId) : []);
9350
9358
  const codexAuthPath = hostedCodexAuthFilePath();
9351
- if (!existsSync15(codexAuthPath)) {
9359
+ if (!existsSync16(codexAuthPath)) {
9352
9360
  yield {
9353
9361
  type: "error",
9354
9362
  content: `Codex auth file not found at ${codexAuthPath}. Run \`codex login\` to authenticate.`
@@ -9387,7 +9395,7 @@ async function* codexProvider(opts) {
9387
9395
  model: opts.model ?? "(sdk default)",
9388
9396
  effort: opts.effort ?? "(off)",
9389
9397
  cwd: opts.cwd,
9390
- cwdExists: existsSync15(opts.cwd),
9398
+ cwdExists: existsSync16(opts.cwd),
9391
9399
  resume: Boolean(opts.sessionId),
9392
9400
  mcpServerCount: Object.keys(codexMcpServers).length
9393
9401
  }, "codexProvider: starting turn");
@@ -9839,7 +9847,7 @@ var init_maestro_provider = __esm(async () => {
9839
9847
  });
9840
9848
 
9841
9849
  // ../../packages/core/src/agents/index.ts
9842
- import { existsSync as existsSync16 } from "fs";
9850
+ import { existsSync as existsSync17 } from "fs";
9843
9851
  import { homedir as homedir7 } from "os";
9844
9852
  import { join as join17 } from "path";
9845
9853
  async function* dispatchAgent(opts) {
@@ -9876,7 +9884,7 @@ async function resolveSessionFileMissing(agent, sessionId, cwd) {
9876
9884
  case "claude": {
9877
9885
  const encodedCwd = encodeClaudeCwd(cwd);
9878
9886
  const path = join17(homedir7(), ".claude", "projects", encodedCwd, `${sessionId}.jsonl`);
9879
- return !existsSync16(path);
9887
+ return !existsSync17(path);
9880
9888
  }
9881
9889
  case "codex": {
9882
9890
  const sessionsDir = join17(hostedCodexHomePath(), "sessions");
@@ -9888,7 +9896,7 @@ async function resolveSessionFileMissing(agent, sessionId, cwd) {
9888
9896
  }
9889
9897
  case "maestro": {
9890
9898
  const { hasActiveMaestroSession, maestroSessionPath: maestroSessionPath2 } = await import("maestro-agent-sdk");
9891
- return !existsSync16(maestroSessionPath2(sessionId)) && !hasActiveMaestroSession(sessionId);
9899
+ return !existsSync17(maestroSessionPath2(sessionId)) && !hasActiveMaestroSession(sessionId);
9892
9900
  }
9893
9901
  default: {
9894
9902
  const _exhaustive = agent;
@@ -9961,10 +9969,10 @@ var init_agents = __esm(async () => {
9961
9969
  });
9962
9970
 
9963
9971
  // ../../packages/core/src/prompts/builders.ts
9964
- import { readFileSync as readFileSync15 } from "fs";
9972
+ import { readFileSync as readFileSync16 } from "fs";
9965
9973
  import { resolve as resolve14 } from "path";
9966
9974
  function loadPrompt(filename, dir = SESSIONS_DIR) {
9967
- const raw = readFileSync15(resolve14(dir, filename), "utf-8");
9975
+ const raw = readFileSync16(resolve14(dir, filename), "utf-8");
9968
9976
  return raw.replace(/\{\{RESOURCES_DIR\}\}/g, RESOURCES_DIR);
9969
9977
  }
9970
9978
  function replaceVars(template, vars) {
@@ -10013,7 +10021,7 @@ function visualDesignGuide() {
10013
10021
  return _visualDesignGuide;
10014
10022
  }
10015
10023
  function loadAgentPrompt(filename) {
10016
- const raw = readFileSync15(resolve14(AGENTS_PROMPTS_DIR, filename), "utf-8");
10024
+ const raw = readFileSync16(resolve14(AGENTS_PROMPTS_DIR, filename), "utf-8");
10017
10025
  const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
10018
10026
  if (!match)
10019
10027
  throw new Error(`Agent prompt ${filename} is missing frontmatter`);
@@ -10282,14 +10290,14 @@ function buildMemoryPromptSection(opts) {
10282
10290
  const parts = [`
10283
10291
 
10284
10292
  ## Memory`];
10285
- parts.push(`Memory directory: ${opts.wikiDir}/topic`);
10286
- parts.push(`Files: ${opts.hasFiles ? opts.briefFile : "(none)"}`);
10287
- if (opts.latestSummaryFile) {
10288
- parts.push(`Latest summary: ${opts.latestSummaryFile}`);
10289
- }
10290
- parts.push("", opts.isManager ? opts.hasFiles ? "Above is this workspace's memory-hub brief (accumulated across all archived topics). For past context or other topics, consult it first, then use the `wiki_query` MCP tool for deeper recall." : "If you need past context while handling delegated work, use the `wiki_query` MCP tool." : opts.hasFiles ? "The file above is this topic's wiki brief. Read it first for past context; use the `wiki_query` MCP tool for deeper recall." : opts.latestSummaryFile ? "There is no wiki brief file yet. For past context, read the Latest summary first; use the `wiki_query` MCP tool for deeper recall." : "There is no wiki brief file yet. Use the `wiki_query` MCP tool for past context.");
10291
- parts.push("", opts.isManager ? "When answering about past workspace decisions or other topics, weave in memory naturally, but confirm with `wiki_query` when unsure." : opts.hasFiles || opts.latestSummaryFile ? "On your first reply in the topic, read the wiki brief" + (opts.latestSummaryFile ? " and Latest summary" : "") + " and mention a one-line context recap so the user can quickly confirm it is on track." : "On your first reply in the topic, check prior conversation context if needed and mention it in one natural line.");
10292
- parts.push("", opts.hasFiles ? "If the user says your memory is wrong, find the file in the Memory directory with Read and fix it directly with Edit." : "If the user says your memory is wrong, check the relevant wiki brief and fix it directly when possible, or use `wiki_query` to find the entry and reconcile.");
10293
+ if (opts.isManager) {
10294
+ parts.push("Use `wiki_query` for past workspace decisions or cross-topic context, then `wiki_read` only for the relevant result.");
10295
+ } else if (opts.memoryKey) {
10296
+ parts.push(`This topic uses the canonical memory key \`${opts.memoryKey}\`. At the start of a new session, read it with \`wiki_read(kind: "topic", key: "${opts.memoryKey}")\`.`);
10297
+ } else {
10298
+ parts.push(`At the start of a new session, search topic memories with \`wiki_query(question: "${opts.topicTitle}", kind: "topic", limit: 5)\` and read the most relevant candidate with \`wiki_read\`.`, "If a candidate is clearly the same continuing topic/persona, read it with `adopt: true`; otherwise keep this topic's own name as its memory key. Do not merge weak or ambiguous matches.");
10299
+ }
10300
+ parts.push("Keep the lookup quiet. Mention prior context naturally in one short line only when it helps the user.", 'Use `wiki_query(kind: "article")` for reusable knowledge and `wiki_query(kind: "summary")` for historical session details.');
10293
10301
  if (opts.hasArchive) {
10294
10302
  parts.push("", "If you need the actual conversation from an earlier session, use the `wiki_last_conversation` MCP tool.");
10295
10303
  }
@@ -10412,7 +10420,7 @@ var init_wiki = __esm(async () => {
10412
10420
 
10413
10421
  // ../../packages/core/src/agents/archiver.ts
10414
10422
  import { randomUUID as randomUUID9 } from "crypto";
10415
- import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync16, statSync as statSync7 } from "fs";
10423
+ import { existsSync as existsSync18, readdirSync as readdirSync5, readFileSync as readFileSync17, statSync as statSync7 } from "fs";
10416
10424
  import { join as join19 } from "path";
10417
10425
  function resolveMemoryLanguage() {
10418
10426
  const override = process.env.NEGOTIUM_MEMORY_LANG?.trim();
@@ -10757,9 +10765,9 @@ var init_archiver = __esm(async () => {
10757
10765
  defaultArchiverRuntime = createArchiverRuntime({
10758
10766
  storage: {
10759
10767
  getWikiDir: getSharedWikiDir,
10760
- fileExists: existsSync17,
10768
+ fileExists: existsSync18,
10761
10769
  listDirectory: readdirSync5,
10762
- readTextFile: (path) => readFileSync16(path, "utf-8"),
10770
+ readTextFile: (path) => readFileSync17(path, "utf-8"),
10763
10771
  fileSize: (path) => statSync7(path).size,
10764
10772
  fileModifiedAt: (path) => statSync7(path).mtimeMs,
10765
10773
  getGeneralTopicId: (userId) => ensurePersonalGeneral(userId).id,
@@ -11245,11 +11253,12 @@ function archiveActiveTopicForMemory(topicId, userId, options) {
11245
11253
  }, "topic-memory-archiver: raw snapshot preserved below exchange threshold");
11246
11254
  return "below-threshold";
11247
11255
  }
11248
- const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
11256
+ const memoryTopic = topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
11257
+ const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
11249
11258
  const launched = (options.launchArchiver ?? runArchiverTurn)({
11250
11259
  userId,
11251
11260
  topicId: memoryTopic.id,
11252
- topicTitle: memoryTopic.title,
11261
+ topicTitle: memoryTitle,
11253
11262
  archivePath: job.archivePath,
11254
11263
  ...options.rawArchivePaths ? { rawArchivePaths: options.rawArchivePaths } : {},
11255
11264
  messageCount: job.messageCount,
@@ -11295,7 +11304,7 @@ var init_idle_archiver = __esm(async () => {
11295
11304
  });
11296
11305
 
11297
11306
  // ../../packages/core/src/agents/topic-cleanup.ts
11298
- import { mkdirSync as mkdirSync13, renameSync as renameSync7, unlinkSync as unlinkSync12, writeFileSync as writeFileSync11 } from "fs";
11307
+ import { mkdirSync as mkdirSync13, renameSync as renameSync7, unlinkSync as unlinkSync13, writeFileSync as writeFileSync11 } from "fs";
11299
11308
  import { dirname as dirname13 } from "path";
11300
11309
  function collectSessionIdsByAgent(entries, extraSessions = []) {
11301
11310
  const out = new Map;
@@ -11369,7 +11378,7 @@ function createTopicLogMaintenance(host) {
11369
11378
  renameSync7(tempPath, path);
11370
11379
  } catch (e) {
11371
11380
  try {
11372
- unlinkSync12(tempPath);
11381
+ unlinkSync13(tempPath);
11373
11382
  } catch {}
11374
11383
  runtimeHost.warn({ err: e, path }, "rotateTopicLogs: conversation replacement failed");
11375
11384
  return {
@@ -11399,7 +11408,7 @@ function createTopicLogMaintenance(host) {
11399
11408
  runtimeHost.rawConversationPath(userId, topicName)
11400
11409
  ]) {
11401
11410
  try {
11402
- unlinkSync12(path);
11411
+ unlinkSync13(path);
11403
11412
  } catch (e) {
11404
11413
  if (e?.code !== "ENOENT") {
11405
11414
  runtimeHost.warn({ err: e, path }, "purgeTopicLogs: conversation log unlink failed");
@@ -11951,7 +11960,7 @@ __export(exports_derive, {
11951
11960
  TopicDeriveBusyError: () => TopicDeriveBusyError
11952
11961
  });
11953
11962
  import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
11954
- import { mkdirSync as mkdirSync14, rmSync as rmSync5, unlinkSync as unlinkSync13 } from "fs";
11963
+ import { mkdirSync as mkdirSync14, rmSync as rmSync5, unlinkSync as unlinkSync14 } from "fs";
11955
11964
  function getTopics() {
11956
11965
  return listTopics().filter((topic) => !isLegacySharedGeneral(topic.id));
11957
11966
  }
@@ -12246,7 +12255,7 @@ async function createDerivedTopicImpl(topic, sourceTopicId, userId, copyHistory,
12246
12255
  getConversationPath(userId, derived.title)
12247
12256
  ]) {
12248
12257
  try {
12249
- unlinkSync13(path);
12258
+ unlinkSync14(path);
12250
12259
  } catch {}
12251
12260
  }
12252
12261
  }
@@ -12318,7 +12327,7 @@ var init_session_inbox_path = __esm(() => {
12318
12327
  });
12319
12328
 
12320
12329
  // ../../packages/core/src/query/session-inbox-cleanup.ts
12321
- import { unlinkSync as unlinkSync14 } from "fs";
12330
+ import { unlinkSync as unlinkSync15 } from "fs";
12322
12331
  import { basename as basename4, join as join23 } from "path";
12323
12332
  function cleanupSessionInboxFiles(userId, topicId, legacyTopicTitle) {
12324
12333
  const live = sessionInboxPath(userId, topicId);
@@ -12333,7 +12342,7 @@ function cleanupSessionInboxFiles(userId, topicId, legacyTopicTitle) {
12333
12342
  let deleted = 0;
12334
12343
  for (const path of candidates) {
12335
12344
  try {
12336
- unlinkSync14(path);
12345
+ unlinkSync15(path);
12337
12346
  deleted++;
12338
12347
  } catch (err) {
12339
12348
  if (err?.code !== "ENOENT") {
@@ -12350,7 +12359,7 @@ var init_session_inbox_cleanup = __esm(() => {
12350
12359
  });
12351
12360
 
12352
12361
  // ../../packages/core/src/query/state.ts
12353
- import { mkdirSync as mkdirSync15, renameSync as renameSync8, unlinkSync as unlinkSync15, writeFileSync as writeFileSync13 } from "fs";
12362
+ import { mkdirSync as mkdirSync15, renameSync as renameSync8, unlinkSync as unlinkSync16, writeFileSync as writeFileSync13 } from "fs";
12354
12363
  import { basename as basename5, join as join24 } from "path";
12355
12364
  function createQueryStateStore(options) {
12356
12365
  const sanitize = options.sanitizeTopicId ?? sanitizeId;
@@ -12381,7 +12390,7 @@ function createQueryStateStore(options) {
12381
12390
  ].filter((path) => Boolean(path));
12382
12391
  for (const path of new Set(paths)) {
12383
12392
  try {
12384
- unlinkSync15(path);
12393
+ unlinkSync16(path);
12385
12394
  } catch (error) {
12386
12395
  if (error?.code !== "ENOENT") {
12387
12396
  options.logger?.warn({ err: error, userId, topicId, path }, "Failed to clear query state file");
@@ -12515,9 +12524,9 @@ import {
12515
12524
  mkdirSync as mkdirSync16,
12516
12525
  openSync as openSync3,
12517
12526
  readdirSync as readdirSync6,
12518
- readFileSync as readFileSync17,
12527
+ readFileSync as readFileSync18,
12519
12528
  statSync as statSync8,
12520
- unlinkSync as unlinkSync16,
12529
+ unlinkSync as unlinkSync17,
12521
12530
  writeFileSync as writeFileSync14
12522
12531
  } from "fs";
12523
12532
  import { dirname as dirname14, join as join25 } from "path";
@@ -12564,7 +12573,7 @@ function parsePendingAskFilename(fileName) {
12564
12573
  }
12565
12574
  function readPendingAskFile(path, fallback) {
12566
12575
  try {
12567
- const raw = readFileSync17(path, "utf8").trim();
12576
+ const raw = readFileSync18(path, "utf8").trim();
12568
12577
  if (!raw)
12569
12578
  return null;
12570
12579
  if (!raw.startsWith("{")) {
@@ -12653,7 +12662,7 @@ function readPendingAsk(key) {
12653
12662
  const next = migrated ? legacy : readPendingAskFile(canonicalPath2, key);
12654
12663
  if (next && next.from === key.from && next.to === key.to) {
12655
12664
  try {
12656
- unlinkSync16(compatibilityPath);
12665
+ unlinkSync17(compatibilityPath);
12657
12666
  } catch {}
12658
12667
  return { path: canonicalPath2, record: next };
12659
12668
  }
@@ -12668,7 +12677,7 @@ function createPendingAsk(args) {
12668
12677
  }
12669
12678
  if (migrated) {
12670
12679
  try {
12671
- unlinkSync16(migrated.path);
12680
+ unlinkSync17(migrated.path);
12672
12681
  } catch {
12673
12682
  return { ok: false, existing: migrated.record, stale: true };
12674
12683
  }
@@ -12700,7 +12709,7 @@ function createPendingAsk(args) {
12700
12709
  if (!isStale(existing, path))
12701
12710
  return { ok: false, existing, stale: false };
12702
12711
  try {
12703
- unlinkSync16(path);
12712
+ unlinkSync17(path);
12704
12713
  } catch {
12705
12714
  return { ok: false, existing, stale: true };
12706
12715
  }
@@ -12726,7 +12735,7 @@ function markPendingAskState(args) {
12726
12735
  writePendingAsk(next);
12727
12736
  if (pending.path !== pendingAskPath(next)) {
12728
12737
  try {
12729
- unlinkSync16(pending.path);
12738
+ unlinkSync17(pending.path);
12730
12739
  } catch {}
12731
12740
  }
12732
12741
  return next;
@@ -12738,7 +12747,7 @@ function clearPendingAsk(args) {
12738
12747
  if (args.requestId && record && record.requestId !== args.requestId)
12739
12748
  return false;
12740
12749
  try {
12741
- unlinkSync16(path);
12750
+ unlinkSync17(path);
12742
12751
  return true;
12743
12752
  } catch {
12744
12753
  return false;
@@ -12793,7 +12802,7 @@ function listPendingAsksForCaller(args) {
12793
12802
  continue;
12794
12803
  if (isStale(record, path)) {
12795
12804
  try {
12796
- unlinkSync16(path);
12805
+ unlinkSync17(path);
12797
12806
  } catch {}
12798
12807
  continue;
12799
12808
  }
@@ -12802,12 +12811,12 @@ function listPendingAsksForCaller(args) {
12802
12811
  continue;
12803
12812
  if (path !== canonical.path) {
12804
12813
  try {
12805
- unlinkSync16(path);
12814
+ unlinkSync17(path);
12806
12815
  } catch {}
12807
12816
  }
12808
12817
  if (isStale(canonical.record, canonical.path)) {
12809
12818
  try {
12810
- unlinkSync16(canonical.path);
12819
+ unlinkSync17(canonical.path);
12811
12820
  } catch {}
12812
12821
  continue;
12813
12822
  }
@@ -12837,7 +12846,7 @@ function deletePendingAsksForTopic(args) {
12837
12846
  if (from !== args.topicName && to !== args.topicName)
12838
12847
  continue;
12839
12848
  try {
12840
- unlinkSync16(path);
12849
+ unlinkSync17(path);
12841
12850
  deleted++;
12842
12851
  } catch {}
12843
12852
  }
@@ -13357,11 +13366,12 @@ async function deleteTopicCascadeImpl(topic, userId, options, deletingAncestorId
13357
13366
  }
13358
13367
  if (archived && meetsMemoryArchiveExchangeThreshold(archived.exchangeCount, MIN_MEMORY_ARCHIVE_EXCHANGES, topic)) {
13359
13368
  try {
13360
- const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
13369
+ const memoryTopic = topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
13370
+ const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
13361
13371
  runArchiverTurn({
13362
13372
  userId,
13363
13373
  ...memoryTopic.id !== topicId && !deletingTopicIds.has(memoryTopic.id) ? { topicId: memoryTopic.id } : {},
13364
- topicTitle: memoryTopic.title,
13374
+ topicTitle: memoryTitle,
13365
13375
  archivePath: archived.path,
13366
13376
  ...rawArchives.length > 0 ? { rawArchivePaths: rawArchives } : {},
13367
13377
  messageCount: archived.messageCount
@@ -15496,7 +15506,7 @@ var init_turn_session = __esm(async () => {
15496
15506
  });
15497
15507
 
15498
15508
  // ../../packages/core/src/storage/app-settings.ts
15499
- import { existsSync as existsSync18, mkdirSync as mkdirSync19, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "fs";
15509
+ import { existsSync as existsSync19, mkdirSync as mkdirSync19, readFileSync as readFileSync19, writeFileSync as writeFileSync16 } from "fs";
15500
15510
  import { dirname as dirname15, join as join28 } from "path";
15501
15511
  function settingsFile() {
15502
15512
  return join28(resolveStorageDataDir(), "otium-settings.json");
@@ -15508,8 +15518,8 @@ function ensureSettingsLoaded() {
15508
15518
  loadedSettingsFile = path;
15509
15519
  aiName = DEFAULT_AI_NAME;
15510
15520
  try {
15511
- if (existsSync18(path)) {
15512
- const data = JSON.parse(readFileSync18(path, "utf8"));
15521
+ if (existsSync19(path)) {
15522
+ const data = JSON.parse(readFileSync19(path, "utf8"));
15513
15523
  if (typeof data.aiName === "string" && data.aiName.trim()) {
15514
15524
  aiName = data.aiName.trim();
15515
15525
  }
@@ -15602,7 +15612,7 @@ __export(exports_turn_runner, {
15602
15612
  AGENT_DISPLAY_NAME: () => AGENT_DISPLAY_NAME
15603
15613
  });
15604
15614
  import { randomUUID as randomUUID16 } from "crypto";
15605
- import { existsSync as existsSync19, mkdirSync as mkdirSync20, readdirSync as readdirSync7, statSync as statSync10 } from "fs";
15615
+ import { existsSync as existsSync20, mkdirSync as mkdirSync20, readdirSync as readdirSync7, statSync as statSync10 } from "fs";
15606
15616
  import { join as join29 } from "path";
15607
15617
  function withDefaultPlaywright(configuredMcp, isManager) {
15608
15618
  if (isManager)
@@ -15659,7 +15669,7 @@ function appendAskReplyMessage(topicId, text2, agentType) {
15659
15669
  }
15660
15670
  function resolveWikiMirrorPath(directory, preferredFilename, matchesLegacyId, matchesTitle, preferExact = true) {
15661
15671
  const preferred = join29(directory, preferredFilename);
15662
- if (preferExact && existsSync19(preferred))
15672
+ if (preferExact && existsSync20(preferred))
15663
15673
  return preferred;
15664
15674
  let newestLegacyId = null;
15665
15675
  let newestTitle = null;
@@ -15684,9 +15694,9 @@ function resolveWikiMirrorPath(directory, preferredFilename, matchesLegacyId, ma
15684
15694
  }
15685
15695
  function resolveWikiMemoryMirror(wikiDir, topicId, topicTitle) {
15686
15696
  const briefFile = resolveWikiMirrorPath(join29(wikiDir, "topic"), `${wikiBriefStorageKey(topicTitle, topicId)}.md`, (filename) => isTopicBriefFile(filename, topicId), (filename) => isTopicBriefFile(filename, topicId, topicTitle));
15687
- const hasBriefFile = existsSync19(briefFile) && statSync10(briefFile).isFile();
15697
+ const hasBriefFile = existsSync20(briefFile) && statSync10(briefFile).isFile();
15688
15698
  const latestSummaryCandidate = resolveWikiMirrorPath(join29(wikiDir, "summaries"), `__missing__-${wikiSummaryFilename("0000-00-00", topicTitle, topicId)}`, (filename) => isTopicSummaryFile(filename, topicId), (filename) => isTopicSummaryFile(filename, topicId, topicTitle), false);
15689
- const latestSummaryFile = existsSync19(latestSummaryCandidate) && statSync10(latestSummaryCandidate).isFile() ? latestSummaryCandidate : null;
15699
+ const latestSummaryFile = existsSync20(latestSummaryCandidate) && statSync10(latestSummaryCandidate).isFile() ? latestSummaryCandidate : null;
15690
15700
  return { briefFile, hasBriefFile, latestSummaryFile };
15691
15701
  }
15692
15702
  async function streamAgentEvents(topicId, topicTitle, queryId, events, control, agentType, model, effort, userId, retryableSessionExpired = true, onSessionId, execution) {
@@ -16439,25 +16449,24 @@ function startAiTurn(params) {
16439
16449
  const isManager = topicRecord?.kind === "manager";
16440
16450
  const isMentionOnlyChannel = topic.aiMode === "mention" && !isManager;
16441
16451
  let systemPrompt = isManager ? buildManagerSystemPrompt(systemPromptOpts) : isMentionOnlyChannel ? buildChannelSystemPrompt(systemPromptOpts) : buildTopicSystemPrompt(systemPromptOpts);
16442
- const memoryTopic = isMentionOnlyChannel || isManager ? topic : getTopicMemoryOrigin(topicId) ?? topic;
16452
+ const memoryTopic = isMentionOnlyChannel || isManager || topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
16453
+ const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
16443
16454
  if (!isMentionOnlyChannel) {
16455
+ let hasMemoryArchive = false;
16444
16456
  try {
16445
- const resolvedBrief = resolveTopicBrief(memoryTopic.id, memoryTopic.title);
16457
+ const resolvedBrief = resolveTopicBrief(memoryTopic.id, memoryTitle);
16446
16458
  const wikiDir = getSharedWikiDir();
16447
- const { briefFile, hasBriefFile, latestSummaryFile } = resolveWikiMemoryMirror(wikiDir, memoryTopic.id, memoryTopic.title);
16448
- if (resolvedBrief || hasBriefFile || latestSummaryFile) {
16449
- systemPrompt += buildMemoryPromptSection({
16450
- briefFile,
16451
- wikiDir,
16452
- hasFiles: hasBriefFile,
16453
- latestSummaryFile,
16454
- hasArchive: Boolean(resolvedBrief?.brief.latestSummaryMd || latestSummaryFile),
16455
- isManager
16456
- });
16457
- }
16459
+ const { latestSummaryFile } = resolveWikiMemoryMirror(wikiDir, memoryTopic.id, memoryTitle);
16460
+ hasMemoryArchive = Boolean(resolvedBrief?.brief.latestSummaryMd || latestSummaryFile);
16458
16461
  } catch (err) {
16459
16462
  logger.warn({ topicId, err }, "ai: failed to inject topic brief");
16460
16463
  }
16464
+ systemPrompt += buildMemoryPromptSection({
16465
+ topicTitle: topic.title,
16466
+ ...memoryTopic.memoryKey ? { memoryKey: memoryTopic.memoryKey } : {},
16467
+ hasArchive: hasMemoryArchive,
16468
+ isManager
16469
+ });
16461
16470
  }
16462
16471
  const activeVisual = visualTools ? getActiveVisualForPrompt(topicId, userId) : null;
16463
16472
  if (activeVisual) {
@@ -16883,7 +16892,7 @@ __export(exports_auth_check, {
16883
16892
  checkAgentAuth: () => checkAgentAuth
16884
16893
  });
16885
16894
  import { execFileSync as execFileSync7 } from "child_process";
16886
- import { existsSync as existsSync20 } from "fs";
16895
+ import { existsSync as existsSync21 } from "fs";
16887
16896
  import { homedir as homedir8, platform } from "os";
16888
16897
  import { join as join30 } from "path";
16889
16898
  function hasMaestroCredential(host, key, userId) {
@@ -16956,7 +16965,7 @@ var init_auth_check = __esm(async () => {
16956
16965
  await init_vault();
16957
16966
  defaultAgentAuthHost = {
16958
16967
  codexAuthFilePath,
16959
- exists: existsSync20,
16968
+ exists: existsSync21,
16960
16969
  environment: process.env,
16961
16970
  homeDirectory: homedir8,
16962
16971
  operatingSystem: platform,
@@ -18187,25 +18196,238 @@ import { AsyncLocalStorage } from "async_hooks";
18187
18196
  import { randomUUID } from "crypto";
18188
18197
  import {
18189
18198
  closeSync,
18190
- existsSync as existsSync2,
18199
+ existsSync as existsSync3,
18191
18200
  mkdirSync as mkdirSync2,
18192
18201
  openSync,
18193
18202
  readdirSync,
18194
- readFileSync as readFileSync2,
18203
+ readFileSync as readFileSync3,
18195
18204
  renameSync,
18196
18205
  statSync,
18197
- unlinkSync,
18206
+ unlinkSync as unlinkSync2,
18198
18207
  writeFileSync as writeFileSync2
18199
18208
  } from "fs";
18200
- import { basename, dirname as dirname2, join as join2, resolve as resolve2 } from "path";
18201
- init_config();
18202
- init_wiki_summary_names();
18209
+ import { basename, dirname as dirname2, join as join2, relative, resolve as resolve2 } from "path";
18203
18210
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
18204
18211
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18205
18212
  import {
18206
18213
  CallToolRequestSchema,
18207
18214
  ListToolsRequestSchema
18208
18215
  } from "@modelcontextprotocol/sdk/types.js";
18216
+
18217
+ // ../../packages/core/src/mcp/wiki-search-index.ts
18218
+ await init_sqlite();
18219
+ import { chmodSync, existsSync, readFileSync, unlinkSync } from "fs";
18220
+ var SEARCH_INDEX_SCHEMA_VERSION = 3;
18221
+ function documentId(source) {
18222
+ return `${source.kind}:${source.key}`;
18223
+ }
18224
+ function ensureSchema(database) {
18225
+ const version = database.query("PRAGMA user_version").get()?.user_version;
18226
+ if (version !== 0 && version !== SEARCH_INDEX_SCHEMA_VERSION) {
18227
+ throw new Error(`Unsupported wiki search index schema: ${version}`);
18228
+ }
18229
+ database.exec(`
18230
+ CREATE TABLE IF NOT EXISTS wiki_search_documents (
18231
+ id TEXT PRIMARY KEY,
18232
+ kind TEXT NOT NULL,
18233
+ key TEXT NOT NULL,
18234
+ path TEXT NOT NULL,
18235
+ mtime_ms REAL NOT NULL,
18236
+ size INTEGER NOT NULL,
18237
+ title TEXT NOT NULL,
18238
+ text TEXT NOT NULL,
18239
+ date TEXT,
18240
+ family TEXT
18241
+ );
18242
+ CREATE UNIQUE INDEX IF NOT EXISTS wiki_search_documents_kind_key
18243
+ ON wiki_search_documents(kind, key);
18244
+ CREATE INDEX IF NOT EXISTS wiki_search_documents_kind_date
18245
+ ON wiki_search_documents(kind, date);
18246
+ CREATE INDEX IF NOT EXISTS wiki_search_documents_kind_family_date
18247
+ ON wiki_search_documents(kind, family, date);
18248
+ CREATE VIRTUAL TABLE IF NOT EXISTS wiki_search_fts USING fts5(
18249
+ document_id UNINDEXED,
18250
+ kind UNINDEXED,
18251
+ tokens,
18252
+ tokenize = 'unicode61'
18253
+ );
18254
+ PRAGMA user_version = ${SEARCH_INDEX_SCHEMA_VERSION};
18255
+ `);
18256
+ }
18257
+
18258
+ class WikiSearchIndex {
18259
+ path;
18260
+ #database;
18261
+ constructor(path) {
18262
+ this.path = path;
18263
+ this.#database = new Database(path, { create: true });
18264
+ try {
18265
+ this.#database.exec("PRAGMA foreign_keys = ON; PRAGMA journal_mode = DELETE;");
18266
+ ensureSchema(this.#database);
18267
+ chmodSync(path, 384);
18268
+ } catch (error) {
18269
+ this.#database.close();
18270
+ throw error;
18271
+ }
18272
+ }
18273
+ close() {
18274
+ this.#database.close();
18275
+ }
18276
+ upsert(source, document) {
18277
+ const id = documentId(source);
18278
+ const apply = this.#database.transaction(() => {
18279
+ this.#database.query("DELETE FROM wiki_search_fts WHERE document_id = ?").run(id);
18280
+ this.#database.query("DELETE FROM wiki_search_documents WHERE id = ?").run(id);
18281
+ this.#database.query(`
18282
+ INSERT INTO wiki_search_documents
18283
+ (id, kind, key, path, mtime_ms, size, title, text, date, family)
18284
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
18285
+ `).run(id, source.kind, source.key, source.path, source.mtimeMs, source.size, document.title, document.text, document.date ?? null, document.family ?? null);
18286
+ this.#database.query("INSERT INTO wiki_search_fts(document_id, kind, tokens) VALUES (?, ?, ?)").run(id, source.kind, document.tokens.join(" "));
18287
+ });
18288
+ apply.immediate();
18289
+ }
18290
+ sync(sources, prepare) {
18291
+ const result = { added: 0, updated: 0, removed: 0 };
18292
+ const existingRows = this.#database.query("SELECT id, path, mtime_ms, size FROM wiki_search_documents ORDER BY id").all();
18293
+ const existing = new Map(existingRows.map((row) => [row.id, row]));
18294
+ const present = new Set;
18295
+ const changed = [];
18296
+ for (const source of sources) {
18297
+ const id = documentId(source);
18298
+ present.add(id);
18299
+ const stored = existing.get(id);
18300
+ if (stored && stored.path === source.path && stored.mtime_ms === source.mtimeMs && stored.size === source.size) {
18301
+ continue;
18302
+ }
18303
+ try {
18304
+ const text = readFileSync(source.path, "utf-8");
18305
+ changed.push({ source, document: prepare(source, text), added: !stored });
18306
+ } catch {}
18307
+ }
18308
+ const removed = existingRows.filter((row) => !present.has(row.id));
18309
+ const apply = this.#database.transaction(() => {
18310
+ const deleteTerms = this.#database.query("DELETE FROM wiki_search_fts WHERE document_id = ?");
18311
+ const deleteDocument = this.#database.query("DELETE FROM wiki_search_documents WHERE id = ?");
18312
+ const insertDocument = this.#database.query(`
18313
+ INSERT INTO wiki_search_documents
18314
+ (id, kind, key, path, mtime_ms, size, title, text, date, family)
18315
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
18316
+ `);
18317
+ const insertTerms = this.#database.query(`
18318
+ INSERT INTO wiki_search_fts(document_id, kind, tokens)
18319
+ VALUES (?, ?, ?)
18320
+ `);
18321
+ for (const row of removed) {
18322
+ deleteTerms.run(row.id);
18323
+ deleteDocument.run(row.id);
18324
+ result.removed += 1;
18325
+ }
18326
+ for (const { source, document, added } of changed) {
18327
+ const id = documentId(source);
18328
+ deleteTerms.run(id);
18329
+ deleteDocument.run(id);
18330
+ insertDocument.run(id, source.kind, source.key, source.path, source.mtimeMs, source.size, document.title, document.text, document.date ?? null, document.family ?? null);
18331
+ insertTerms.run(id, source.kind, document.tokens.join(" "));
18332
+ if (added)
18333
+ result.added += 1;
18334
+ else
18335
+ result.updated += 1;
18336
+ }
18337
+ });
18338
+ apply.immediate();
18339
+ return result;
18340
+ }
18341
+ metadata(kinds) {
18342
+ if (kinds.length === 0)
18343
+ return [];
18344
+ const placeholders = kinds.map(() => "?").join(", ");
18345
+ const rows = this.#database.query(`SELECT id, kind, key, title, date
18346
+ FROM wiki_search_documents
18347
+ WHERE kind IN (${placeholders})`).all(...kinds);
18348
+ return rows.map((row) => ({
18349
+ id: row.id,
18350
+ kind: row.kind,
18351
+ key: row.key,
18352
+ title: row.title,
18353
+ ...row.date ? { date: row.date } : {}
18354
+ }));
18355
+ }
18356
+ summaryMetadataByDate(query) {
18357
+ const conditions = ["kind = 'summary'", "date IS NOT NULL"];
18358
+ const parameters = [];
18359
+ if (query.year && query.month) {
18360
+ const month = Number(query.month);
18361
+ const nextYear = month === 12 ? String(Number(query.year) + 1) : query.year;
18362
+ const nextMonth = String(month === 12 ? 1 : month + 1).padStart(2, "0");
18363
+ conditions.push("date >= ?", "date < ?");
18364
+ parameters.push(`${query.year}-${query.month}-01`, `${nextYear}-${nextMonth}-01`);
18365
+ } else if (query.year) {
18366
+ conditions.push("date >= ?", "date < ?");
18367
+ parameters.push(`${query.year}-01-01`, `${Number(query.year) + 1}-01-01`);
18368
+ } else if (query.month) {
18369
+ conditions.push("substr(date, 6, 2) = ?");
18370
+ parameters.push(query.month);
18371
+ }
18372
+ parameters.push(query.limit);
18373
+ const rows = this.#database.query(`SELECT id, kind, key, title, date
18374
+ FROM wiki_search_documents
18375
+ WHERE ${conditions.join(" AND ")}
18376
+ ORDER BY date ${query.direction === "oldest" ? "ASC" : "DESC"}, key ASC
18377
+ LIMIT ?`).all(...parameters);
18378
+ return rows.map((row) => ({
18379
+ id: row.id,
18380
+ kind: row.kind,
18381
+ key: row.key,
18382
+ title: row.title,
18383
+ ...row.date ? { date: row.date } : {}
18384
+ }));
18385
+ }
18386
+ matchingDocumentIds(kinds, terms) {
18387
+ if (kinds.length === 0 || terms.length === 0)
18388
+ return new Set;
18389
+ const kindPlaceholders = kinds.map(() => "?").join(", ");
18390
+ const match = terms.map((term) => `"${term.replaceAll('"', '""')}"`).join(" OR ");
18391
+ const rows = this.#database.query(`SELECT DISTINCT document_id
18392
+ FROM wiki_search_fts
18393
+ WHERE wiki_search_fts MATCH ? AND kind IN (${kindPlaceholders})`).all(match, ...kinds);
18394
+ return new Set(rows.map((row) => row.document_id));
18395
+ }
18396
+ documents(ids) {
18397
+ if (ids.size === 0)
18398
+ return [];
18399
+ const values = [...ids];
18400
+ const rows = [];
18401
+ for (let offset = 0;offset < values.length; offset += 500) {
18402
+ const chunk = values.slice(offset, offset + 500);
18403
+ const placeholders = chunk.map(() => "?").join(", ");
18404
+ rows.push(...this.#database.query(`SELECT id, kind, key, path, title, text, date
18405
+ FROM wiki_search_documents
18406
+ WHERE id IN (${placeholders})`).all(...chunk));
18407
+ }
18408
+ return rows.map((row) => ({
18409
+ id: row.id,
18410
+ kind: row.kind,
18411
+ key: row.key,
18412
+ path: row.path,
18413
+ title: row.title,
18414
+ text: row.text,
18415
+ tokens: [],
18416
+ ...row.date ? { date: row.date } : {}
18417
+ }));
18418
+ }
18419
+ }
18420
+ function resetCorruptWikiSearchIndex(path) {
18421
+ for (const candidate of [path, `${path}-journal`, `${path}-wal`, `${path}-shm`]) {
18422
+ if (!existsSync(candidate))
18423
+ continue;
18424
+ unlinkSync(candidate);
18425
+ }
18426
+ }
18427
+
18428
+ // ../../packages/core/src/mcp/wiki-server.ts
18429
+ init_config();
18430
+ init_wiki_summary_names();
18209
18431
  var wikiRuntime = new AsyncLocalStorage;
18210
18432
  function runtime() {
18211
18433
  const current = wikiRuntime.getStore();
@@ -18232,15 +18454,15 @@ function acquireRecoveryGuard(lockPath) {
18232
18454
  } catch (error) {
18233
18455
  closeSync(fd);
18234
18456
  try {
18235
- unlinkSync(guardPath);
18457
+ unlinkSync2(guardPath);
18236
18458
  } catch {}
18237
18459
  throw error;
18238
18460
  }
18239
18461
  return () => {
18240
18462
  closeSync(fd);
18241
18463
  try {
18242
- if (readFileSync2(guardPath, "utf-8") === ownerToken)
18243
- unlinkSync(guardPath);
18464
+ if (readFileSync3(guardPath, "utf-8") === ownerToken)
18465
+ unlinkSync2(guardPath);
18244
18466
  } catch {}
18245
18467
  };
18246
18468
  } catch (error) {
@@ -18265,7 +18487,7 @@ function acquireFileLock(path) {
18265
18487
  } catch (error) {
18266
18488
  closeSync(fd);
18267
18489
  try {
18268
- unlinkSync(lockPath);
18490
+ unlinkSync2(lockPath);
18269
18491
  } catch {}
18270
18492
  throw error;
18271
18493
  }
@@ -18273,8 +18495,8 @@ function acquireFileLock(path) {
18273
18495
  closeSync(fd);
18274
18496
  const releaseGuard = acquireRecoveryGuard(lockPath);
18275
18497
  try {
18276
- if (readFileSync2(lockPath, "utf-8") === ownerToken)
18277
- unlinkSync(lockPath);
18498
+ if (readFileSync3(lockPath, "utf-8") === ownerToken)
18499
+ unlinkSync2(lockPath);
18278
18500
  } catch {} finally {
18279
18501
  releaseGuard();
18280
18502
  }
@@ -18285,9 +18507,9 @@ function acquireFileLock(path) {
18285
18507
  const releaseGuard = acquireRecoveryGuard(lockPath);
18286
18508
  try {
18287
18509
  try {
18288
- const staleToken = readFileSync2(lockPath, "utf-8");
18289
- if (Date.now() - statSync(lockPath).mtimeMs > FILE_LOCK_STALE_MS && readFileSync2(lockPath, "utf-8") === staleToken) {
18290
- unlinkSync(lockPath);
18510
+ const staleToken = readFileSync3(lockPath, "utf-8");
18511
+ if (Date.now() - statSync(lockPath).mtimeMs > FILE_LOCK_STALE_MS && readFileSync3(lockPath, "utf-8") === staleToken) {
18512
+ unlinkSync2(lockPath);
18291
18513
  continue;
18292
18514
  }
18293
18515
  } catch {
@@ -18310,7 +18532,7 @@ function atomicWriteFile(path, content) {
18310
18532
  renameSync(temporaryPath, path);
18311
18533
  } catch (error) {
18312
18534
  try {
18313
- unlinkSync(temporaryPath);
18535
+ unlinkSync2(temporaryPath);
18314
18536
  } catch {}
18315
18537
  throw error;
18316
18538
  }
@@ -18331,9 +18553,395 @@ function topicNameFrom(topicIdStr) {
18331
18553
  function slugify(topic) {
18332
18554
  return wikiSummarySlug(topic);
18333
18555
  }
18556
+ function normalizeSearchText(value2) {
18557
+ return value2.toLowerCase().replaceAll(/[^a-z0-9\uAC00-\uD7A3]+/g, " ").trim().replaceAll(/\s+/g, " ");
18558
+ }
18559
+ function compactSearchText(value2) {
18560
+ return normalizeSearchText(value2).replaceAll(" ", "");
18561
+ }
18562
+ function trigrams(value2) {
18563
+ const compact = compactSearchText(value2);
18564
+ if (compact.length < 3)
18565
+ return new Set(compact ? [compact] : []);
18566
+ const result = new Set;
18567
+ for (let i = 0;i <= compact.length - 3; i += 1)
18568
+ result.add(compact.slice(i, i + 3));
18569
+ return result;
18570
+ }
18571
+ function fuzzySearchScore(query, candidate, maximum) {
18572
+ const compactQuery = compactSearchText(query);
18573
+ const compactCandidate = compactSearchText(candidate);
18574
+ if (compactQuery.length < 3 || compactCandidate.length < 3)
18575
+ return 0;
18576
+ const similarity = trigramSimilarity(compactQuery, compactCandidate);
18577
+ const minimumSimilarity = compactQuery.length <= 4 ? 0.6 : 0.45;
18578
+ return similarity >= minimumSimilarity ? similarity * maximum : 0;
18579
+ }
18580
+ var SEARCH_STOP_WORDS = new Set([
18581
+ "a",
18582
+ "an",
18583
+ "and",
18584
+ "are",
18585
+ "as",
18586
+ "at",
18587
+ "be",
18588
+ "by",
18589
+ "for",
18590
+ "from",
18591
+ "in",
18592
+ "is",
18593
+ "it",
18594
+ "of",
18595
+ "on",
18596
+ "or",
18597
+ "that",
18598
+ "the",
18599
+ "to",
18600
+ "with"
18601
+ ]);
18602
+ var SEARCH_MONTHS = new Map([
18603
+ ["january", "01"],
18604
+ ["jan", "01"],
18605
+ ["1\uC6D4", "01"],
18606
+ ["february", "02"],
18607
+ ["feb", "02"],
18608
+ ["2\uC6D4", "02"],
18609
+ ["march", "03"],
18610
+ ["mar", "03"],
18611
+ ["3\uC6D4", "03"],
18612
+ ["april", "04"],
18613
+ ["apr", "04"],
18614
+ ["4\uC6D4", "04"],
18615
+ ["may", "05"],
18616
+ ["5\uC6D4", "05"],
18617
+ ["june", "06"],
18618
+ ["jun", "06"],
18619
+ ["6\uC6D4", "06"],
18620
+ ["july", "07"],
18621
+ ["jul", "07"],
18622
+ ["7\uC6D4", "07"],
18623
+ ["august", "08"],
18624
+ ["aug", "08"],
18625
+ ["8\uC6D4", "08"],
18626
+ ["september", "09"],
18627
+ ["sep", "09"],
18628
+ ["sept", "09"],
18629
+ ["9\uC6D4", "09"],
18630
+ ["october", "10"],
18631
+ ["oct", "10"],
18632
+ ["10\uC6D4", "10"],
18633
+ ["november", "11"],
18634
+ ["nov", "11"],
18635
+ ["11\uC6D4", "11"],
18636
+ ["december", "12"],
18637
+ ["dec", "12"],
18638
+ ["12\uC6D4", "12"]
18639
+ ]);
18640
+ var SEARCH_RECENCY_TERMS = new Set([
18641
+ "latest",
18642
+ "newest",
18643
+ "recent",
18644
+ "earliest",
18645
+ "oldest",
18646
+ "\uCD5C\uADFC",
18647
+ "\uCD5C\uC2E0",
18648
+ "\uCD5C\uCD08",
18649
+ "\uC624\uB798\uB41C"
18650
+ ]);
18651
+ function meaningfulSearchTokens(value2) {
18652
+ return normalizeSearchText(value2).split(" ").filter((token) => (token.length >= 2 || /^\d+$/.test(token)) && !SEARCH_STOP_WORDS.has(token));
18653
+ }
18654
+ function canonicalSearchToken(token) {
18655
+ return token.length > 4 && token.endsWith("s") ? token.slice(0, -1) : token;
18656
+ }
18657
+ function canonicalSearchTokens(value2) {
18658
+ return meaningfulSearchTokens(value2).map(canonicalSearchToken);
18659
+ }
18660
+ function countToken(tokens, token) {
18661
+ let count = 0;
18662
+ for (const candidate of tokens)
18663
+ if (candidate === token)
18664
+ count += 1;
18665
+ return count;
18666
+ }
18667
+ function countPhrase(text, phrase) {
18668
+ if (!phrase)
18669
+ return 0;
18670
+ let count = 0;
18671
+ let offset = 0;
18672
+ let match = text.indexOf(phrase, offset);
18673
+ while (match >= 0) {
18674
+ count += 1;
18675
+ offset = match + phrase.length;
18676
+ match = text.indexOf(phrase, offset);
18677
+ }
18678
+ return count;
18679
+ }
18680
+ function summaryTopicIdentity(key) {
18681
+ return normalizeSearchText(key.replace(/^\d{4}-\d{2}-\d{2}-/, ""));
18682
+ }
18683
+ function scoreSummaryTime(query, key, title) {
18684
+ const date = key.match(/^(\d{4})-(\d{2})-(\d{2})/);
18685
+ if (!date)
18686
+ return 0;
18687
+ const tokens = meaningfulSearchTokens(query);
18688
+ const documentDate = `${date[1]}-${date[2]}-${date[3]}`;
18689
+ const requestedDateMatch = query.match(/\b(\d{4}) (\d{2}) (\d{2})\b/);
18690
+ const requestedDate = requestedDateMatch ? `${requestedDateMatch[1]}-${requestedDateMatch[2]}-${requestedDateMatch[3]}` : undefined;
18691
+ const beforeDate = /\bbefore\b/.test(query);
18692
+ const afterDate = /\bafter\b/.test(query);
18693
+ if (requestedDate && beforeDate && documentDate >= requestedDate)
18694
+ return null;
18695
+ if (requestedDate && afterDate && documentDate <= requestedDate)
18696
+ return null;
18697
+ const queryYear = tokens.find((token) => /^\d{4}(?:\uB144)?$/.test(token))?.replace(/\uB144$/, "");
18698
+ const queryMonth = tokens.map((token) => SEARCH_MONTHS.get(token)).find(Boolean);
18699
+ const hasRecencyTerm = tokens.some((token) => SEARCH_RECENCY_TERMS.has(token));
18700
+ const yearMatches = !queryYear || queryYear === date[1];
18701
+ const monthMatches = !queryMonth || queryMonth === date[2];
18702
+ if (!queryYear && !queryMonth && !hasRecencyTerm || !yearMatches || !monthMatches)
18703
+ return 0;
18704
+ const normalizedMetadata = `${normalizeSearchText(key)} ${normalizeSearchText(title)}`;
18705
+ const contentTokens = tokens.filter((token) => !/^\d{4}(?:\uB144)?$/.test(token) && !SEARCH_MONTHS.has(token) && !SEARCH_RECENCY_TERMS.has(token));
18706
+ const contentMatches = contentTokens.filter((token) => normalizedMetadata.includes(token)).length;
18707
+ const relationScore = requestedDate && (beforeDate || afterDate) ? 60 : 0;
18708
+ const exactDateScore = requestedDate === documentDate ? 90 : 0;
18709
+ return 30 + relationScore + exactDateScore + contentMatches * 15;
18710
+ }
18711
+ function trigramSimilarity(left, right) {
18712
+ const a = trigrams(left);
18713
+ const b = trigrams(right);
18714
+ if (a.size === 0 || b.size === 0)
18715
+ return 0;
18716
+ let overlap = 0;
18717
+ for (const gram of a)
18718
+ if (b.has(gram))
18719
+ overlap += 1;
18720
+ return 2 * overlap / (a.size + b.size);
18721
+ }
18722
+ var WIKI_SEARCH_INDEX_FILENAME = ".wiki-search-index.sqlite";
18723
+ function openWikiSearchIndex() {
18724
+ const path = resolve2(runtime().wikiDir, WIKI_SEARCH_INDEX_FILENAME);
18725
+ try {
18726
+ return new WikiSearchIndex(path);
18727
+ } catch (error) {
18728
+ if (!/malformed|not a database|schema/i.test(String(error)))
18729
+ return;
18730
+ try {
18731
+ resetCorruptWikiSearchIndex(path);
18732
+ return new WikiSearchIndex(path);
18733
+ } catch {
18734
+ return;
18735
+ }
18736
+ }
18737
+ }
18738
+ function prepareWikiSearchDocument(kind, key, text) {
18739
+ const titleLine = text.split(`
18740
+ `).find((line) => /^#+\s*/.test(line)) ?? "";
18741
+ const title = titleLine ? titleLine.replace(/^#+\s*/, "") : basename(key);
18742
+ return {
18743
+ title,
18744
+ text,
18745
+ tokens: canonicalSearchTokens(`${key} ${title} ${text}`),
18746
+ ...kind === "summary" && /^\d{4}-\d{2}-\d{2}/.test(key) ? { date: key.slice(0, 10), family: summaryTopicIdentity(key) } : {}
18747
+ };
18748
+ }
18749
+ function indexWikiSearchDocument(kind, key, text) {
18750
+ const root = kind === "article" ? runtime().articlesDir : runtime().summariesDir;
18751
+ const path = resolve2(root, `${key}.md`);
18752
+ const index = openWikiSearchIndex();
18753
+ if (!index)
18754
+ return false;
18755
+ try {
18756
+ const stat = statSync(path);
18757
+ index.upsert({ kind, key, path, mtimeMs: stat.mtimeMs, size: stat.size }, prepareWikiSearchDocument(kind, key, text));
18758
+ return true;
18759
+ } catch {
18760
+ return false;
18761
+ } finally {
18762
+ index.close();
18763
+ }
18764
+ }
18765
+ var WIKI_INDEX_FILENAMES = {
18766
+ article: "article-index.md",
18767
+ summary: "summary-index.md",
18768
+ topic: "topic-index.md",
18769
+ skill: "skill-index.md"
18770
+ };
18771
+ var WIKI_INDEX_NAMESPACES = {
18772
+ article: "articles",
18773
+ summary: "summaries",
18774
+ topic: "topic",
18775
+ skill: "skills"
18776
+ };
18777
+ function wikiIndexPath(kind) {
18778
+ return resolve2(runtime().wikiDir, WIKI_INDEX_FILENAMES[kind]);
18779
+ }
18780
+ var MAX_INDEX_DESCRIPTION_LENGTH = 400;
18781
+ function normalizeIndexDescription(raw) {
18782
+ const description = raw.replaceAll(/\s+/g, " ").trim();
18783
+ if (!description)
18784
+ return { error: "A non-empty one-line description is required." };
18785
+ if (description.length > MAX_INDEX_DESCRIPTION_LENGTH) {
18786
+ return {
18787
+ error: `Description must be at most ${MAX_INDEX_DESCRIPTION_LENGTH} characters (got ${description.length}).`
18788
+ };
18789
+ }
18790
+ return { description };
18791
+ }
18792
+ function normalizeWikiSlug(raw) {
18793
+ const slug = raw.trim().replace(/\.md$/i, "").replace(/^\/+/, "");
18794
+ if (!slug)
18795
+ return { error: "A non-empty slug is required." };
18796
+ if (/[[\]|\\]/.test(slug))
18797
+ return { error: "Slug must not contain [ ] | or backslashes." };
18798
+ if (slug.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
18799
+ return { error: "Slug must not contain empty or relative path segments." };
18800
+ }
18801
+ return { slug };
18802
+ }
18803
+ function parseWikiIndex(path) {
18804
+ let text;
18805
+ try {
18806
+ text = readFileSync3(path, "utf-8");
18807
+ } catch {
18808
+ return [];
18809
+ }
18810
+ const candidates = [];
18811
+ const entryPattern = /^\s*-\s*\[\[(topic|articles|summaries)\/([^\]]+)\]\]\s*(.*)$/;
18812
+ for (const line of text.split(`
18813
+ `)) {
18814
+ const match = line.match(entryPattern);
18815
+ if (!match)
18816
+ continue;
18817
+ const namespace = match[1];
18818
+ const key = match[2].replace(/\.md$/i, "");
18819
+ const tail = match[3].trim();
18820
+ const dateMatch = tail.match(/\((\d{4}-\d{2}-\d{2})(?:[^)]*)\)\s*$/);
18821
+ const description = dateMatch ? tail.slice(0, dateMatch.index).trim() : tail;
18822
+ candidates.push({
18823
+ kind: namespace === "topic" ? "topic" : namespace === "articles" ? "article" : "summary",
18824
+ key,
18825
+ description,
18826
+ ...dateMatch ? { date: dateMatch[1] } : {}
18827
+ });
18828
+ }
18829
+ return candidates;
18830
+ }
18831
+ function indexCandidateExists(candidate) {
18832
+ if (candidate.kind === "topic")
18833
+ return true;
18834
+ const root = candidate.kind === "article" ? runtime().articlesDir : runtime().summariesDir;
18835
+ const filePath = resolve2(root, `${candidate.key}.md`);
18836
+ const relativePath = relative(root, filePath);
18837
+ if (relativePath.startsWith("..") || relativePath.startsWith("/"))
18838
+ return false;
18839
+ try {
18840
+ return statSync(filePath).isFile();
18841
+ } catch {
18842
+ return false;
18843
+ }
18844
+ }
18845
+ function scoreIndexKey(query, key) {
18846
+ const normalizedQuery = normalizeSearchText(query);
18847
+ const compactQuery = compactSearchText(normalizedQuery);
18848
+ const normalizedKey = normalizeSearchText(key);
18849
+ const compactKey = compactSearchText(normalizedKey);
18850
+ const tokens = meaningfulSearchTokens(normalizedQuery);
18851
+ let score = 0;
18852
+ if (normalizedKey === normalizedQuery)
18853
+ score += 120;
18854
+ else if (compactKey === compactQuery)
18855
+ score += 110;
18856
+ else {
18857
+ if (compactQuery.length >= 2 && (compactKey.startsWith(compactQuery) || compactQuery.startsWith(compactKey))) {
18858
+ score += 65;
18859
+ }
18860
+ if (tokens.length > 0 && compactQuery.length >= 3 && compactKey.includes(compactQuery)) {
18861
+ score += 45;
18862
+ }
18863
+ score += fuzzySearchScore(compactQuery, compactKey, 55);
18864
+ }
18865
+ for (const token of tokens) {
18866
+ if (normalizedKey.includes(token))
18867
+ score += 10;
18868
+ }
18869
+ return score;
18870
+ }
18871
+ function scoreIndexCandidate(query, candidate) {
18872
+ const normalizedQuery = normalizeSearchText(query);
18873
+ if (!normalizedQuery)
18874
+ return 0;
18875
+ const normalizedDescription = normalizeSearchText(candidate.description);
18876
+ const tokens = canonicalSearchTokens(normalizedQuery);
18877
+ const descriptionTokens = new Set(canonicalSearchTokens(normalizedDescription));
18878
+ let score = scoreIndexKey(normalizedQuery, candidate.key);
18879
+ if (tokens.length > 0 && normalizedDescription.includes(normalizedQuery))
18880
+ score += 30;
18881
+ let descriptionMatches = 0;
18882
+ for (const token of tokens) {
18883
+ if (descriptionTokens.has(token)) {
18884
+ score += 5;
18885
+ descriptionMatches += 1;
18886
+ }
18887
+ }
18888
+ if (tokens.length > 1 && descriptionMatches === tokens.length)
18889
+ score += 25;
18890
+ if (candidate.kind === "topic") {
18891
+ const requestsCurrent = /\b(?:current|active|ongoing|open)\b|\uD604\uC7AC|\uC9C4\uD589 \uC911/.test(normalizedQuery);
18892
+ const archivedCandidate = /\b(?:archive|archived|historical|closed)\b|\uBCF4\uAD00|\uC885\uB8CC/.test(`${normalizeSearchText(candidate.key)} ${normalizedDescription}`);
18893
+ if (requestsCurrent && archivedCandidate)
18894
+ score -= 40;
18895
+ }
18896
+ return score;
18897
+ }
18334
18898
  function wikiQuery(args) {
18335
- const question = safeExt(args, ["question", "query", "q", "text"], "");
18336
- const query = question.toLowerCase();
18899
+ const question = String(safeExt(args, ["question", "query", "q", "text"], "")).trim();
18900
+ const requestedKind = String(safeExt(args, ["kind", "type"], "all"));
18901
+ const kind = requestedKind === "topic" || requestedKind === "article" || requestedKind === "summary" ? requestedKind : "all";
18902
+ const requestedLimit = Number(safeExt(args, ["limit", "maxResults"], 8));
18903
+ const limit = Math.min(Math.max(Number.isFinite(requestedLimit) ? requestedLimit : 8, 1), 20);
18904
+ if (!question) {
18905
+ return {
18906
+ content: [{ type: "text", text: "A non-empty wiki query is required." }],
18907
+ isError: true
18908
+ };
18909
+ }
18910
+ const normalizedQuery = normalizeSearchText(question);
18911
+ if (!normalizedQuery) {
18912
+ return { content: [{ type: "text", text: "No matching wiki articles found." }] };
18913
+ }
18914
+ const canReadTopicMemory = runtime().host.canReadTopicMemory;
18915
+ const temporalDirection = /\b(?:latest|newest|recent)\b|\uCD5C\uADFC|\uCD5C\uC2E0/.test(normalizedQuery) ? "newest" : /\b(?:earliest|oldest)\b|\uAC00\uC7A5 \uC624\uB798\uB41C|\uCD5C\uCD08/.test(normalizedQuery) ? "oldest" : undefined;
18916
+ const visibleIndexCandidates = [
18917
+ ...parseWikiIndex(resolve2(runtime().wikiDir, "topic-index.md")),
18918
+ ...parseWikiIndex(resolve2(runtime().wikiDir, "article-index.md"))
18919
+ ].filter((candidate) => kind === "all" || candidate.kind === kind).filter((candidate) => candidate.kind !== "topic" || !canReadTopicMemory || canReadTopicMemory(candidate.key, runtime().userId));
18920
+ const hasStaleExactIndexMatch = visibleIndexCandidates.some((candidate) => !indexCandidateExists(candidate) && scoreIndexKey(question, candidate.key) >= 100);
18921
+ const indexCandidates = visibleIndexCandidates.filter(indexCandidateExists).map((candidate) => ({
18922
+ candidate,
18923
+ keyScore: scoreIndexKey(question, candidate.key),
18924
+ score: scoreIndexCandidate(question, candidate)
18925
+ })).filter(({ keyScore, score }) => keyScore >= 25 || score >= 40).sort((left, right) => right.score - left.score || left.candidate.key.localeCompare(right.candidate.key));
18926
+ const strongestIndex = indexCandidates[0];
18927
+ if (kind === "topic" && strongestIndex && (strongestIndex.keyScore >= 25 || strongestIndex.score >= 40)) {
18928
+ const runnerUp = indexCandidates[1];
18929
+ const ambiguous = strongestIndex.keyScore < 100 && runnerUp !== undefined && runnerUp.score >= strongestIndex.score * 0.9;
18930
+ if (ambiguous) {
18931
+ const lines2 = ["Found 2 ambiguous wiki candidates:", ""];
18932
+ for (const { candidate, score } of [strongestIndex, runnerUp]) {
18933
+ lines2.push(`- kind: ${candidate.kind}`, ` key: ${candidate.key}`, ` score: ${score.toFixed(2)}`, ` description: ${candidate.description || "(none)"}`, ...candidate.date ? [` date: ${candidate.date}`] : []);
18934
+ }
18935
+ return { content: [{ type: "text", text: lines2.join(`
18936
+ `) }] };
18937
+ }
18938
+ const lines = ["Found 1 wiki candidate:", ""];
18939
+ for (const { candidate, score } of [strongestIndex]) {
18940
+ lines.push(`- kind: ${candidate.kind}`, ` key: ${candidate.key}`, ` score: ${score.toFixed(2)}`, ` description: ${candidate.description || "(none)"}`, ...candidate.date ? [` date: ${candidate.date}`] : []);
18941
+ }
18942
+ return { content: [{ type: "text", text: lines.join(`
18943
+ `) }] };
18944
+ }
18337
18945
  const results = [];
18338
18946
  const scored = [];
18339
18947
  function scan(dir, label) {
@@ -18349,68 +18957,266 @@ function wikiQuery(args) {
18349
18957
  continue;
18350
18958
  const fp = join2(sub, f.name);
18351
18959
  try {
18352
- const text = readFileSync2(fp, "utf-8");
18960
+ const text = readFileSync3(fp, "utf-8");
18961
+ const key = `${entry.name}/${f.name.replace(/\.md$/i, "")}`;
18353
18962
  scored.push({
18354
- score: scoreMatch(text),
18963
+ kind: label === "articles" ? "article" : "skill",
18964
+ key,
18965
+ keyScore: scoreIndexKey(question, key),
18966
+ score: scoreMatch(text, key, label === "articles" ? "article" : "skill"),
18355
18967
  path: `${label}/${entry.name}/${f.name}`,
18356
- text
18968
+ text,
18969
+ title: titleFromDocument(text, key)
18357
18970
  });
18358
18971
  } catch {}
18359
18972
  }
18360
18973
  } catch {}
18361
18974
  }
18362
18975
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
18976
+ if (label === "topic" && canReadTopicMemory && !canReadTopicMemory(entry.name.replace(/\.md$/i, ""), runtime().userId)) {
18977
+ continue;
18978
+ }
18363
18979
  const fp = join2(dir, entry.name);
18364
18980
  try {
18365
- const text = readFileSync2(fp, "utf-8");
18981
+ const text = readFileSync3(fp, "utf-8");
18982
+ const key = entry.name.replace(/\.md$/i, "");
18983
+ const resultKind = label === "articles" ? "article" : label === "topic" ? "topic" : label === "summaries" ? "summary" : "skill";
18366
18984
  scored.push({
18367
- score: scoreMatch(text),
18985
+ kind: resultKind,
18986
+ key,
18987
+ keyScore: scoreIndexKey(question, key),
18988
+ score: scoreMatch(text, key, resultKind),
18368
18989
  path: `${label}/${entry.name}`,
18369
- text
18990
+ text,
18991
+ title: titleFromDocument(text, key),
18992
+ ...label === "summaries" && /^\d{4}-\d{2}-\d{2}/.test(key) ? { date: key.slice(0, 10) } : {}
18370
18993
  });
18371
18994
  } catch {}
18372
18995
  }
18373
18996
  }
18374
18997
  } catch {}
18375
18998
  }
18376
- function scoreMatch(text) {
18377
- const lower = text.toLowerCase();
18999
+ function titleFromDocument(text, key) {
19000
+ const titleLine = text.split(`
19001
+ `).find((line) => /^#+\s*/.test(line));
19002
+ return titleLine ? titleLine.replace(/^#+\s*/, "") : basename(key);
19003
+ }
19004
+ function scoreMatch(text, key, resultKind) {
19005
+ const titleLine = text.split(`
19006
+ `).find((line) => /^#+\s*/.test(line)) ?? "";
19007
+ const title = titleLine.replace(/^#+\s*/, "");
19008
+ const normalizedKey = normalizeSearchText(key);
19009
+ const normalizedTitle = normalizeSearchText(title);
19010
+ const normalizedBody = normalizeSearchText(text.replace(titleLine, ""));
19011
+ const compactQuery = compactSearchText(normalizedQuery);
19012
+ const compactKey = compactSearchText(normalizedKey);
19013
+ const compactTitle = compactSearchText(normalizedTitle);
19014
+ const tokens = canonicalSearchTokens(normalizedQuery);
19015
+ const keyTokens = new Set(canonicalSearchTokens(normalizedKey));
19016
+ const titleTokens = new Set(canonicalSearchTokens(normalizedTitle));
19017
+ const bodyTokens = canonicalSearchTokens(normalizedBody);
18378
19018
  let score = 0;
18379
- const words = query.split(/\s+/).filter(Boolean);
18380
- for (const w of words) {
18381
- if (lower.includes(w)) {
18382
- score += w.length >= 3 ? 3 : 1;
18383
- const firstLine = lower.split(`
18384
- `)[0];
18385
- if (firstLine.startsWith("#") && firstLine.includes(w))
18386
- score += 5;
19019
+ let strongMetadataEvidence = false;
19020
+ if (normalizedKey === normalizedQuery) {
19021
+ score += 120;
19022
+ strongMetadataEvidence = true;
19023
+ } else if (compactKey === compactQuery) {
19024
+ score += 110;
19025
+ strongMetadataEvidence = true;
19026
+ } else if (compactQuery.length >= 2 && (compactKey.startsWith(compactQuery) || compactQuery.startsWith(compactKey))) {
19027
+ score += 65;
19028
+ strongMetadataEvidence = true;
19029
+ }
19030
+ if (tokens.length > 0 && compactQuery.length >= 3 && compactKey.includes(compactQuery)) {
19031
+ score += 45;
19032
+ strongMetadataEvidence = true;
19033
+ }
19034
+ const fuzzyKeyScore = fuzzySearchScore(compactQuery, compactKey, 55);
19035
+ score += fuzzyKeyScore;
19036
+ if (fuzzyKeyScore > 0)
19037
+ strongMetadataEvidence = true;
19038
+ if (normalizedTitle === normalizedQuery) {
19039
+ score += 115;
19040
+ strongMetadataEvidence = true;
19041
+ } else if (compactTitle === compactQuery) {
19042
+ score += 105;
19043
+ strongMetadataEvidence = true;
19044
+ } else if (compactQuery.length >= 2 && compactTitle.startsWith(compactQuery)) {
19045
+ score += 60;
19046
+ strongMetadataEvidence = true;
19047
+ }
19048
+ const fuzzyTitleScore = fuzzySearchScore(compactQuery, compactTitle, 50);
19049
+ score += fuzzyTitleScore;
19050
+ if (fuzzyTitleScore > 0)
19051
+ strongMetadataEvidence = true;
19052
+ if (resultKind === "summary") {
19053
+ const temporalScore = scoreSummaryTime(normalizedQuery, key, title);
19054
+ if (temporalScore === null)
19055
+ return 0;
19056
+ score += temporalScore;
19057
+ if (temporalScore > 0)
19058
+ strongMetadataEvidence = true;
19059
+ }
19060
+ const isMultiToken = tokens.length > 1;
19061
+ if (tokens.length > 0 && normalizedTitle.includes(normalizedQuery)) {
19062
+ score += 35;
19063
+ strongMetadataEvidence = true;
19064
+ }
19065
+ const phraseMatches = isMultiToken ? countPhrase(normalizedBody, normalizedQuery) : 0;
19066
+ if (phraseMatches > 0)
19067
+ score += phraseMatches * 12;
19068
+ else if (tokens.length === 1 && tokens[0].length >= 5 && normalizedBody.includes(tokens[0])) {
19069
+ score += 16;
19070
+ }
19071
+ let bodyMatches = 0;
19072
+ let minimumBodyFrequency = Number.POSITIVE_INFINITY;
19073
+ for (const token of tokens) {
19074
+ if (keyTokens.has(token))
19075
+ score += 10;
19076
+ if (titleTokens.has(token))
19077
+ score += 12;
19078
+ const bodyFrequency = countToken(bodyTokens, token);
19079
+ if (bodyFrequency > 0) {
19080
+ score += Math.min(bodyFrequency, 3) * 4;
19081
+ bodyMatches += 1;
19082
+ minimumBodyFrequency = Math.min(minimumBodyFrequency, bodyFrequency);
18387
19083
  }
18388
19084
  }
19085
+ if (isMultiToken && bodyMatches === tokens.length) {
19086
+ score += 8 * Math.min(minimumBodyFrequency, 3);
19087
+ }
19088
+ const minimumBodyTokens = hasStaleExactIndexMatch ? 3 : 2;
19089
+ const partialArticleEvidence = resultKind === "article" && bodyMatches >= 3 && bodyMatches / tokens.length >= 0.6;
19090
+ const strongBodyEvidence = tokens.length >= minimumBodyTokens && (bodyMatches === tokens.length && (phraseMatches > 0 || minimumBodyFrequency >= 1) || partialArticleEvidence);
19091
+ if (!strongMetadataEvidence && !strongBodyEvidence)
19092
+ return 0;
18389
19093
  return score;
18390
19094
  }
18391
- scan(runtime().articlesDir, "articles");
18392
- if (runtime().surface === "all")
19095
+ const bodyKinds = [
19096
+ ...kind === "all" || kind === "article" ? ["article"] : [],
19097
+ ...kind === "all" || kind === "summary" ? ["summary"] : []
19098
+ ];
19099
+ if (bodyKinds.length > 0) {
19100
+ const searchIndex = openWikiSearchIndex();
19101
+ if (searchIndex) {
19102
+ try {
19103
+ const terms = canonicalSearchTokens(normalizedQuery);
19104
+ const ids = searchIndex.matchingDocumentIds(bodyKinds, terms);
19105
+ if (temporalDirection && bodyKinds.includes("summary")) {
19106
+ for (const item of searchIndex.summaryMetadataByDate({
19107
+ direction: temporalDirection === "newest" ? "latest" : "oldest",
19108
+ limit
19109
+ })) {
19110
+ ids.add(item.id);
19111
+ }
19112
+ }
19113
+ for (const document of searchIndex.documents(ids)) {
19114
+ scored.push({
19115
+ kind: document.kind,
19116
+ key: document.key,
19117
+ keyScore: scoreIndexKey(question, document.key),
19118
+ score: scoreMatch(document.text, document.key, document.kind),
19119
+ path: `${document.kind === "article" ? "articles" : "summaries"}/${document.key}.md`,
19120
+ text: document.text,
19121
+ title: document.title,
19122
+ ...document.date ? { date: document.date } : {}
19123
+ });
19124
+ }
19125
+ } catch {} finally {
19126
+ searchIndex.close();
19127
+ }
19128
+ }
19129
+ }
19130
+ if (kind === "all" && runtime().surface === "all")
18393
19131
  scan(runtime().skillsDir, "skills");
18394
- scan(runtime().topicsDir, "topic");
18395
- scan(runtime().summariesDir, "summaries");
18396
- scored.sort((a, b) => b.score - a.score);
18397
- const top = scored.slice(0, 8);
18398
- if (top.length === 0) {
19132
+ if (kind === "all" || kind === "topic")
19133
+ scan(runtime().topicsDir, "topic");
19134
+ const merged = new Map;
19135
+ for (const { candidate, keyScore, score } of indexCandidates) {
19136
+ merged.set(`${candidate.kind}:${candidate.key}`, {
19137
+ kind: candidate.kind,
19138
+ key: candidate.key,
19139
+ keyScore,
19140
+ score,
19141
+ description: candidate.description,
19142
+ date: candidate.date
19143
+ });
19144
+ }
19145
+ const documentThreshold = kind === "topic" ? 24 : 16;
19146
+ for (const document of scored) {
19147
+ if (document.score < documentThreshold)
19148
+ continue;
19149
+ const id = `${document.kind}:${document.key}`;
19150
+ const indexed = merged.get(id);
19151
+ merged.set(id, indexed ? { ...indexed, ...document, score: Math.max(indexed.score, document.score) } : document);
19152
+ }
19153
+ let ranked = [...merged.values()].sort((left, right) => right.score - left.score || (left.kind === "summary" && right.kind === "summary" ? temporalDirection === "oldest" ? (left.date ?? "").localeCompare(right.date ?? "") : (right.date ?? "").localeCompare(left.date ?? "") : 0) || left.key.localeCompare(right.key));
19154
+ if (kind === "summary") {
19155
+ const sameTopic = ranked.filter((result) => {
19156
+ const identity = summaryTopicIdentity(result.key);
19157
+ return identity.length >= 3 && normalizedQuery.includes(identity);
19158
+ });
19159
+ if (sameTopic.length > 0)
19160
+ ranked = sameTopic;
19161
+ const requestedDateMatch = normalizedQuery.match(/\b(\d{4}) (\d{2}) (\d{2})\b/);
19162
+ const requestedDate = requestedDateMatch ? `${requestedDateMatch[1]}-${requestedDateMatch[2]}-${requestedDateMatch[3]}` : undefined;
19163
+ const beforeDate = /\bbefore\b/.test(normalizedQuery);
19164
+ const afterDate = /\bafter\b/.test(normalizedQuery);
19165
+ if (requestedDate && beforeDate) {
19166
+ ranked = ranked.filter((result) => !result.date || result.date < requestedDate);
19167
+ } else if (requestedDate && afterDate) {
19168
+ ranked = ranked.filter((result) => !result.date || result.date > requestedDate);
19169
+ }
19170
+ ranked.sort((left, right) => {
19171
+ if (beforeDate) {
19172
+ return (right.date ?? "").localeCompare(left.date ?? "") || right.score - left.score;
19173
+ }
19174
+ if (afterDate) {
19175
+ return (left.date ?? "").localeCompare(right.date ?? "") || right.score - left.score;
19176
+ }
19177
+ if (requestedDate) {
19178
+ const dateDifference = Number(right.date === requestedDate) - Number(left.date === requestedDate);
19179
+ if (dateDifference !== 0)
19180
+ return dateDifference;
19181
+ }
19182
+ if (temporalDirection) {
19183
+ const dateDifference = temporalDirection === "oldest" ? (left.date ?? "").localeCompare(right.date ?? "") : (right.date ?? "").localeCompare(left.date ?? "");
19184
+ if (dateDifference !== 0)
19185
+ return dateDifference;
19186
+ }
19187
+ return right.score - left.score || (right.date ?? "").localeCompare(left.date ?? "") || left.key.localeCompare(right.key);
19188
+ });
19189
+ }
19190
+ if (kind === "topic" && ranked[0]) {
19191
+ const runnerUp = ranked[1];
19192
+ const ambiguous = ranked[0].keyScore < 100 && runnerUp !== undefined && runnerUp.score >= ranked[0].score * 0.9;
19193
+ if (ambiguous) {
19194
+ ranked = ranked.slice(0, 2);
19195
+ } else {
19196
+ ranked = ranked.slice(0, 1);
19197
+ }
19198
+ } else {
19199
+ ranked = ranked.slice(0, limit);
19200
+ }
19201
+ if (ranked.length === 0) {
18399
19202
  results.push("No matching wiki articles found.");
18400
19203
  } else {
18401
- results.push(`Found ${top.length} matching result(s):
19204
+ results.push(`Found ${ranked.length} matching result(s):
18402
19205
  `);
18403
- for (const { path, text } of top) {
18404
- const titleLine = text.split(`
18405
- `).find((l) => l.startsWith("#"));
18406
- const title = titleLine ? titleLine.replace(/^#+\s*/, "") : basename(path, ".md");
18407
- const body = text.replace(/^---[\s\S]*?---\n?/, "").replace(/^#.*$/m, "").trim().slice(0, 400);
18408
- results.push(`## ${title}
18409
- Path: ${path}
19206
+ for (const result of ranked) {
19207
+ if (result.kind === "skill") {
19208
+ const body = (result.text ?? "").trim().slice(0, 400);
19209
+ results.push(`## ${result.title ?? result.key}
19210
+ Path: ${result.path}
18410
19211
 
18411
- ${body}${text.length > 400 ? `
19212
+ ${body}${(result.text?.length ?? 0) > 400 ? `
18412
19213
  ...(truncated)` : ""}
18413
19214
  `);
19215
+ continue;
19216
+ }
19217
+ results.push(`- kind: ${result.kind}`, ` key: ${result.key}`, ` score: ${result.score.toFixed(2)}`, ...result.title ? [` title: ${result.title}`] : [], ` description: ${result.description || "(none)"}`, ...result.date ? [` date: ${result.date}`] : [], ...result.path ? [` path: ${result.path}`, ` Path: ${result.path}`] : [], ...result.text ? [
19218
+ ` excerpt: ${result.text.replace(/^---[\s\S]*?---\n?/, "").replace(/^#.*$/m, "").trim().slice(0, 400)}`
19219
+ ] : []);
18414
19220
  }
18415
19221
  }
18416
19222
  return { content: [{ type: "text", text: results.join(`
@@ -18429,36 +19235,94 @@ function wikiTopicBrief(args) {
18429
19235
  };
18430
19236
  }
18431
19237
  const { getTopicBrief, resolveTopicBrief } = runtime().host;
18432
- if (runtime().topicId && (getTopicBrief || resolveTopicBrief)) {
19238
+ const hasAuthorizedBridge = Boolean(runtime().topicId && (getTopicBrief || resolveTopicBrief));
19239
+ if (hasAuthorizedBridge) {
18433
19240
  try {
18434
19241
  const brief = resolveTopicBrief ? resolveTopicBrief(rawTopic, runtime().userId) : getTopicBrief(rawTopic);
18435
- if (!brief) {
18436
- return {
18437
- content: [{ type: "text", text: `No brief found for topic: ${rawTopic}` }]
18438
- };
18439
- }
18440
- const lines = [
18441
- `# Topic Brief: ${topicNameFrom(rawTopic)}`,
18442
- "",
18443
- brief.briefMd || "(empty brief)"
18444
- ];
18445
- if (brief.latestSummaryMd) {
18446
- lines.push("", "## Latest Summary", "", brief.latestSummaryMd);
18447
- }
18448
- if (brief.summaryDate) {
18449
- lines.push("", `Summary date: ${brief.summaryDate}`);
18450
- }
18451
- return { content: [{ type: "text", text: lines.join(`
19242
+ if (brief) {
19243
+ const lines = [
19244
+ `# Topic Brief: ${topicNameFrom(rawTopic)}`,
19245
+ "",
19246
+ brief.briefMd || "(empty brief)"
19247
+ ];
19248
+ if (brief.latestSummaryMd) {
19249
+ lines.push("", "## Latest Summary", "", brief.latestSummaryMd);
19250
+ }
19251
+ if (brief.summaryDate) {
19252
+ lines.push("", `Summary date: ${brief.summaryDate}`);
19253
+ }
19254
+ return { content: [{ type: "text", text: lines.join(`
18452
19255
  `) }] };
18453
- } catch {
19256
+ }
19257
+ } catch {}
19258
+ return {
19259
+ content: [{ type: "text", text: `No brief found for topic: ${rawTopic}` }]
19260
+ };
19261
+ }
19262
+ const topicKey = wikiSummarySlug(rawTopic.replace(/^topic\//, "").replace(/\.md$/i, ""));
19263
+ const filePath = resolve2(runtime().topicsDir, `${topicKey}.md`);
19264
+ try {
19265
+ const content = readFileSync3(filePath, "utf-8");
19266
+ return {
19267
+ content: [{ type: "text", text: `# Topic Brief: ${topicKey}
19268
+
19269
+ ${content}` }]
19270
+ };
19271
+ } catch {}
19272
+ return {
19273
+ content: [{ type: "text", text: `No brief found for topic: ${rawTopic}` }]
19274
+ };
19275
+ }
19276
+ function wikiRead(args) {
19277
+ const rawKind = String(safeExt(args, ["kind", "type"], "article"));
19278
+ const kind = rawKind === "topic" || rawKind === "summary" ? rawKind : "article";
19279
+ const rawKey = String(safeExt(args, ["key", "slug", "topic"], "")).trim();
19280
+ if (!rawKey) {
19281
+ return { content: [{ type: "text", text: "A wiki document key is required." }], isError: true };
19282
+ }
19283
+ if (kind === "topic") {
19284
+ const result = wikiTopicBrief({ topic: rawKey });
19285
+ const resultText = result.content.map((entry) => entry.type === "text" ? entry.text : "").join(`
19286
+ `);
19287
+ const adopt = safeExt(args, ["adopt", "use", "select"], false);
19288
+ if (!adopt || resultText.startsWith("No brief found for topic"))
19289
+ return result;
19290
+ const currentTopicId = runtime().currentTopicId;
19291
+ const memoryKey = wikiSummarySlug(rawKey.replace(/^topic\//, "").replace(/\.md$/i, ""));
19292
+ if (!currentTopicId || !runtime().host.adoptTopicMemory?.(currentTopicId, runtime().userId, memoryKey)) {
18454
19293
  return {
18455
- content: [{ type: "text", text: `Could not read topic brief for: ${rawTopic}` }]
19294
+ content: [
19295
+ ...result.content,
19296
+ { type: "text", text: `
19297
+ Memory was read but could not be adopted by this topic.` }
19298
+ ]
18456
19299
  };
18457
19300
  }
19301
+ return {
19302
+ content: [...result.content, { type: "text", text: `
19303
+ Adopted topic memory: ${memoryKey}` }]
19304
+ };
19305
+ }
19306
+ if (rawKey.includes("\\") || rawKey.split("/").some((part) => part === "..") || rawKey.startsWith("/")) {
19307
+ return { content: [{ type: "text", text: "Invalid wiki document key." }], isError: true };
19308
+ }
19309
+ const root = kind === "summary" ? runtime().summariesDir : runtime().articlesDir;
19310
+ const key = rawKey.replace(new RegExp(`^(?:${kind === "summary" ? "summaries" : "articles"})/`), "").replace(/\.md$/i, "");
19311
+ const filePath = resolve2(root, `${key}.md`);
19312
+ const relativePath = relative(root, filePath);
19313
+ if (relativePath.startsWith("..") || relativePath.startsWith("/")) {
19314
+ return { content: [{ type: "text", text: "Invalid wiki document key." }], isError: true };
19315
+ }
19316
+ try {
19317
+ const content = readFileSync3(filePath, "utf-8");
19318
+ return {
19319
+ content: [{ type: "text", text: `# Wiki ${kind}: ${key}
19320
+
19321
+ ${content}` }]
19322
+ };
19323
+ } catch {
19324
+ return { content: [{ type: "text", text: `No ${kind} found for: ${rawKey}` }] };
18458
19325
  }
18459
- return {
18460
- content: [{ type: "text", text: `No topic brief found for: ${rawTopic}` }]
18461
- };
18462
19326
  }
18463
19327
  function wikiLastConversation(args) {
18464
19328
  const rawTopic = safeExt(args, ["topic", "topicName", "topicId"], runtime().topicId);
@@ -18470,18 +19334,18 @@ function wikiLastConversation(args) {
18470
19334
  const name = slugify(topicNameFrom(rawTopic));
18471
19335
  const archiveDir = resolve2(runtime().archiveDir, name);
18472
19336
  try {
18473
- let files = existsSync2(archiveDir) ? readdirSync(archiveDir).filter((f) => f.endsWith(".jsonl")).sort().reverse() : [];
19337
+ let files = existsSync3(archiveDir) ? readdirSync(archiveDir).filter((f) => f.endsWith(".jsonl")).sort().reverse() : [];
18474
19338
  if (files.length === 0) {
18475
- files = existsSync2(runtime().archiveDir) ? readdirSync(runtime().archiveDir).filter((f) => f.startsWith(name) && f.endsWith(".jsonl")).sort().reverse() : [];
19339
+ files = existsSync3(runtime().archiveDir) ? readdirSync(runtime().archiveDir).filter((f) => f.startsWith(name) && f.endsWith(".jsonl")).sort().reverse() : [];
18476
19340
  }
18477
19341
  if (files.length === 0) {
18478
19342
  return {
18479
19343
  content: [{ type: "text", text: `No archive found for topic: ${rawTopic}` }]
18480
19344
  };
18481
19345
  }
18482
- const actualPath = files[0].includes("/") ? files[0] : resolve2(existsSync2(archiveDir) && readdirSync(archiveDir).includes(files[0]) ? archiveDir : runtime().archiveDir, files[0]);
19346
+ const actualPath = files[0].includes("/") ? files[0] : resolve2(existsSync3(archiveDir) && readdirSync(archiveDir).includes(files[0]) ? archiveDir : runtime().archiveDir, files[0]);
18483
19347
  try {
18484
- const raw = readFileSync2(actualPath, "utf-8");
19348
+ const raw = readFileSync3(actualPath, "utf-8");
18485
19349
  const lines = raw.trim().split(`
18486
19350
  `).filter(Boolean);
18487
19351
  const turns2 = [];
@@ -18554,7 +19418,7 @@ function skillQuery(args) {
18554
19418
  continue;
18555
19419
  const skillFile = resolve2(runtime().skillsDir, entry.name, "skill.md");
18556
19420
  try {
18557
- const text = readFileSync2(skillFile, "utf-8");
19421
+ const text = readFileSync3(skillFile, "utf-8");
18558
19422
  const lower = text.toLowerCase();
18559
19423
  let score = 0;
18560
19424
  for (const w of query.split(/\s+/).filter(Boolean)) {
@@ -18625,7 +19489,7 @@ ${fresh.join(`
18625
19489
  const skillPath = resolve2(skillDir, "skill.md");
18626
19490
  let finalContent = content;
18627
19491
  try {
18628
- const existing = readFileSync2(skillPath, "utf-8");
19492
+ const existing = readFileSync3(skillPath, "utf-8");
18629
19493
  finalContent = mergeGotchas(content, extractGotchas(existing));
18630
19494
  } catch {}
18631
19495
  writeFileSync2(skillPath, finalContent, "utf-8");
@@ -18638,15 +19502,7 @@ ${fresh.join(`
18638
19502
  ]
18639
19503
  };
18640
19504
  }
18641
- function saveWikiEntry(args) {
18642
- const rawTopic = safeExt(args, ["topic", "topicName"], "");
18643
- const content = safeExt(args, ["content", "text", "body"], "");
18644
- if (!rawTopic)
18645
- return { content: [{ type: "text", text: "Missing topic." }] };
18646
- if (!content)
18647
- return { content: [{ type: "text", text: "Missing content." }] };
18648
- const now = new Date;
18649
- const dateStr = now.toISOString().slice(0, 10);
19505
+ function writeSummaryDocument(rawTopic, content, dateStr) {
18650
19506
  const topicId = runtime().topicId;
18651
19507
  const fileSlug = wikiBriefStorageKey(rawTopic, topicId);
18652
19508
  ensureDir(runtime().summariesDir);
@@ -18673,7 +19529,7 @@ function saveWikiEntry(args) {
18673
19529
  if (topicId && setTopicBrief) {
18674
19530
  try {
18675
19531
  const briefPath = resolve2(runtime().topicsDir, `${fileSlug}.md`);
18676
- let briefMd = existsSync2(briefPath) ? readFileSync2(briefPath, "utf-8") : undefined;
19532
+ let briefMd = existsSync3(briefPath) ? readFileSync3(briefPath, "utf-8") : undefined;
18677
19533
  if (briefMd === undefined && getTopicBrief && !getTopicBrief(fileSlug)?.briefMd) {
18678
19534
  const legacy = getTopicBrief(topicId)?.briefMd;
18679
19535
  if (legacy)
@@ -18687,28 +19543,14 @@ function saveWikiEntry(args) {
18687
19543
  sqliteUpdated = true;
18688
19544
  } catch {}
18689
19545
  }
18690
- return {
18691
- content: [
18692
- {
18693
- type: "text",
18694
- text: `Saved summary: summaries/${summaryName}` + (sqliteUpdated ? `
18695
- SQLite latest-summary also updated.` : "")
18696
- }
18697
- ]
18698
- };
19546
+ return { slug: summaryName.replace(/\.md$/i, ""), sqliteUpdated };
18699
19547
  }
18700
- function saveTopicBrief(args) {
18701
- const rawTopic = safeExt(args, ["topic", "topicName"], "");
18702
- const content = safeExt(args, ["content", "brief", "text", "body"], "");
18703
- if (!rawTopic)
18704
- return { content: [{ type: "text", text: "Missing topic." }] };
18705
- if (!content)
18706
- return { content: [{ type: "text", text: "Missing content." }] };
19548
+ function writeTopicDocument(rawTopic, content) {
18707
19549
  const topicId = runtime().topicId;
18708
19550
  const fileSlug = wikiBriefStorageKey(rawTopic, topicId);
18709
19551
  const briefPath = resolve2(runtime().topicsDir, `${fileSlug}.md`);
18710
19552
  ensureDir(dirname2(briefPath));
18711
- const existed = existsSync2(briefPath);
19553
+ const existed = existsSync3(briefPath);
18712
19554
  writeFileSync2(briefPath, content, "utf-8");
18713
19555
  const setTopicBrief = runtime().host.setTopicBrief;
18714
19556
  let sqliteUpdated = false;
@@ -18718,68 +19560,228 @@ function saveTopicBrief(args) {
18718
19560
  sqliteUpdated = true;
18719
19561
  } catch {}
18720
19562
  }
19563
+ return { slug: fileSlug, existed, sqliteUpdated };
19564
+ }
19565
+ function writeArticleDocument(slug, content) {
19566
+ const articlePath = resolve2(runtime().articlesDir, `${slug}.md`);
19567
+ ensureDir(dirname2(articlePath));
19568
+ const existed = existsSync3(articlePath);
19569
+ atomicWriteFile(articlePath, content);
19570
+ return { existed };
19571
+ }
19572
+ function wikiWrite(args) {
19573
+ const rawKind = safeExt(args, ["kind", "type"], "");
19574
+ const content = safeExt(args, ["content", "text", "body", "brief"], "");
19575
+ const rawDescription = safeExt(args, ["description", "desc"], "");
19576
+ const rawTopic = safeExt(args, ["topic", "topicName"], "");
19577
+ const rawSlug = safeExt(args, ["slug", "id"], "");
19578
+ const section = safeExt(args, ["section", "category"], undefined);
19579
+ const rawDate = safeExt(args, ["date", "created"], undefined);
19580
+ if (rawKind !== "summary" && rawKind !== "article" && rawKind !== "topic") {
19581
+ return {
19582
+ content: [{ type: "text", text: 'kind must be one of "summary", "article", "topic".' }],
19583
+ isError: true
19584
+ };
19585
+ }
19586
+ if (!content.trim()) {
19587
+ return { content: [{ type: "text", text: "Missing content." }], isError: true };
19588
+ }
19589
+ const descriptionResult = normalizeIndexDescription(rawDescription);
19590
+ if ("error" in descriptionResult) {
19591
+ return { content: [{ type: "text", text: descriptionResult.error }], isError: true };
19592
+ }
19593
+ const { description } = descriptionResult;
19594
+ const today = new Date().toISOString().slice(0, 10);
19595
+ if (rawKind === "article") {
19596
+ const slugResult = normalizeWikiSlug(rawSlug);
19597
+ if ("error" in slugResult) {
19598
+ return { content: [{ type: "text", text: slugResult.error }], isError: true };
19599
+ }
19600
+ if (!section?.trim()) {
19601
+ return {
19602
+ content: [
19603
+ { type: "text", text: "An article requires a section so the catalog stays navigable." }
19604
+ ],
19605
+ isError: true
19606
+ };
19607
+ }
19608
+ const { existed } = writeArticleDocument(slugResult.slug, content);
19609
+ const link2 = indexRowOrPartialWrite("article", {
19610
+ kind: "article",
19611
+ slug: slugResult.slug,
19612
+ description,
19613
+ date: rawDate ?? today,
19614
+ section: section.trim()
19615
+ });
19616
+ if (typeof link2 !== "string")
19617
+ return link2;
19618
+ const searched = indexWikiSearchDocument("article", slugResult.slug, content);
19619
+ return {
19620
+ content: [
19621
+ {
19622
+ type: "text",
19623
+ text: `Saved article: articles/${slugResult.slug}.md (${existed ? "updated" : "created"})
19624
+ Indexed: ${link2}` + (searched ? "" : `
19625
+ ${SEARCH_CACHE_SKIPPED_NOTE}`)
19626
+ }
19627
+ ]
19628
+ };
19629
+ }
19630
+ const topic = rawTopic.trim();
19631
+ if (!topic) {
19632
+ return { content: [{ type: "text", text: "Missing topic." }], isError: true };
19633
+ }
19634
+ if (rawKind === "summary") {
19635
+ const dateStr = rawDate ?? today;
19636
+ const written2 = writeSummaryDocument(topic, content, dateStr);
19637
+ const link2 = indexRowOrPartialWrite("summary", {
19638
+ kind: "summary",
19639
+ slug: written2.slug,
19640
+ description,
19641
+ date: dateStr
19642
+ });
19643
+ if (typeof link2 !== "string")
19644
+ return link2;
19645
+ const searched = indexWikiSearchDocument("summary", written2.slug, content);
19646
+ return {
19647
+ content: [
19648
+ {
19649
+ type: "text",
19650
+ text: `Saved summary: summaries/${written2.slug}.md
19651
+ Indexed: ${link2}` + (written2.sqliteUpdated ? `
19652
+ SQLite latest-summary also updated.` : "") + (searched ? "" : `
19653
+ ${SEARCH_CACHE_SKIPPED_NOTE}`)
19654
+ }
19655
+ ]
19656
+ };
19657
+ }
19658
+ const written = writeTopicDocument(topic, content);
19659
+ const link = indexRowOrPartialWrite("topic", {
19660
+ kind: "topic",
19661
+ slug: written.slug,
19662
+ description,
19663
+ date: rawDate ?? today
19664
+ });
19665
+ if (typeof link !== "string")
19666
+ return link;
18721
19667
  return {
18722
19668
  content: [
18723
19669
  {
18724
19670
  type: "text",
18725
- text: `Saved brief: topic/${fileSlug}.md (${existed ? "updated" : "created"})` + (sqliteUpdated ? `
19671
+ text: `Saved brief: topic/${written.slug}.md (${written.existed ? "updated" : "created"})
19672
+ Indexed: ${link}` + (written.sqliteUpdated ? `
18726
19673
  SQLite brief also updated.` : "")
18727
19674
  }
18728
19675
  ]
18729
19676
  };
18730
19677
  }
18731
- function indexUpsert(args) {
18732
- const slug = safeExt(args, ["slug", "id"], "");
18733
- const desc = safeExt(args, ["description", "desc", "text"], "");
18734
- const kind = safeExt(args, ["kind", "type"], "article");
18735
- const section = safeExt(args, ["section", "category"], undefined);
18736
- const date = safeExt(args, ["date", "created"], undefined);
18737
- if (!slug)
18738
- return { content: [{ type: "text", text: "Missing slug." }] };
18739
- const today = new Date().toISOString().slice(0, 10);
18740
- const dateStr = date ?? today;
18741
- const indexPath = (() => {
18742
- if (kind === "topic")
18743
- return resolve2(runtime().wikiDir, "topic-index.md");
18744
- if (kind === "skill")
18745
- return resolve2(runtime().wikiDir, "skill-index.md");
18746
- return resolve2(runtime().wikiDir, "article-index.md");
18747
- })();
19678
+ function collectIndexableDocuments() {
19679
+ const sources = [];
19680
+ function add(kind, key, path) {
19681
+ try {
19682
+ const stat = statSync(path);
19683
+ if (!stat.isFile())
19684
+ return;
19685
+ sources.push({ kind, key, path, mtimeMs: stat.mtimeMs, size: stat.size });
19686
+ } catch {}
19687
+ }
19688
+ for (const [root, kind, allowSubdirectories] of [
19689
+ [runtime().articlesDir, "article", true],
19690
+ [runtime().summariesDir, "summary", false]
19691
+ ]) {
19692
+ let entries;
19693
+ try {
19694
+ entries = readdirSync(root, { withFileTypes: true });
19695
+ } catch {
19696
+ continue;
19697
+ }
19698
+ for (const entry of entries) {
19699
+ if (entry.isFile() && entry.name.endsWith(".md")) {
19700
+ add(kind, entry.name.replace(/\.md$/i, ""), join2(root, entry.name));
19701
+ continue;
19702
+ }
19703
+ if (!entry.isDirectory() || !allowSubdirectories)
19704
+ continue;
19705
+ let nested;
19706
+ try {
19707
+ nested = readdirSync(join2(root, entry.name), { withFileTypes: true });
19708
+ } catch {
19709
+ continue;
19710
+ }
19711
+ for (const file of nested) {
19712
+ if (!file.isFile() || !file.name.endsWith(".md"))
19713
+ continue;
19714
+ add(kind, `${entry.name}/${file.name.replace(/\.md$/i, "")}`, join2(root, entry.name, file.name));
19715
+ }
19716
+ }
19717
+ }
19718
+ return sources;
19719
+ }
19720
+ function wikiReindex() {
19721
+ const documents = collectIndexableDocuments();
19722
+ const index = openWikiSearchIndex();
19723
+ if (!index) {
19724
+ return {
19725
+ content: [{ type: "text", text: "Could not open the wiki search index." }],
19726
+ isError: true
19727
+ };
19728
+ }
19729
+ let synced;
19730
+ try {
19731
+ synced = index.sync(documents, (source, text) => prepareWikiSearchDocument(source.kind, source.key, text));
19732
+ } catch (error) {
19733
+ return {
19734
+ content: [{ type: "text", text: `Failed to rebuild the search index: ${String(error)}` }],
19735
+ isError: true
19736
+ };
19737
+ } finally {
19738
+ index.close();
19739
+ }
19740
+ const lines = [
19741
+ `Search index: ${synced.added} added, ${synced.updated} updated, ${synced.removed} removed (${documents.length} documents).`
19742
+ ];
19743
+ for (const kind of ["article", "summary"]) {
19744
+ const rows = new Set(parseWikiIndex(wikiIndexPath(kind)).filter((candidate) => candidate.kind === kind).map((candidate) => candidate.key));
19745
+ const keys = documents.filter((source) => source.kind === kind).map((source) => source.key);
19746
+ const unindexed = keys.filter((key) => !rows.has(key));
19747
+ const tombstones = [...rows].filter((key) => !keys.includes(key));
19748
+ lines.push(`${WIKI_INDEX_FILENAMES[kind]}: ${rows.size} rows for ${keys.length} documents` + (unindexed.length > 0 ? `; missing rows: ${unindexed.join(", ")}` : "") + (tombstones.length > 0 ? `; tombstones: ${tombstones.join(", ")}` : ""));
19749
+ }
19750
+ return { content: [{ type: "text", text: lines.join(`
19751
+ `) }] };
19752
+ }
19753
+ var SEARCH_CACHE_SKIPPED_NOTE = "Body-search cache was not updated; catalog retrieval still works. Run wiki_reindex to refill it.";
19754
+ function indexRowOrPartialWrite(label, options) {
19755
+ try {
19756
+ return upsertIndexRow(options);
19757
+ } catch (error) {
19758
+ return {
19759
+ content: [
19760
+ {
19761
+ type: "text",
19762
+ text: `Wrote the ${label} document but failed to index it (${String(error)}). Retry the same wiki_write call: both writes are idempotent.`
19763
+ }
19764
+ ],
19765
+ isError: true
19766
+ };
19767
+ }
19768
+ }
19769
+ function upsertIndexRow(options) {
19770
+ const { kind, slug, description, date, section } = options;
19771
+ const indexPath = wikiIndexPath(kind);
18748
19772
  ensureDir(dirname2(indexPath));
18749
19773
  const releaseLock = acquireFileLock(indexPath);
18750
19774
  try {
18751
19775
  let index;
18752
19776
  try {
18753
- index = readFileSync2(indexPath, "utf-8");
19777
+ index = readFileSync3(indexPath, "utf-8");
18754
19778
  } catch {
18755
19779
  index = "";
18756
19780
  }
18757
19781
  const lines = index.split(`
18758
19782
  `);
18759
- const link = (() => {
18760
- switch (kind) {
18761
- case "summary":
18762
- return `- [[summaries/${slug}]] ${desc} (${dateStr})`;
18763
- case "topic":
18764
- return `- [[topic/${slug}]] ${desc} (${dateStr})`;
18765
- case "skill":
18766
- return `- [[skills/${slug}]] ${desc} (${dateStr})`;
18767
- default:
18768
- return `- [[articles/${slug}]] ${desc} (${dateStr})`;
18769
- }
18770
- })();
18771
- const target = (() => {
18772
- switch (kind) {
18773
- case "summary":
18774
- return `summaries/${slug}`;
18775
- case "topic":
18776
- return `topic/${slug}`;
18777
- case "skill":
18778
- return `skills/${slug}`;
18779
- default:
18780
- return `articles/${slug}`;
18781
- }
18782
- })();
19783
+ const target = `${WIKI_INDEX_NAMESPACES[kind]}/${slug}`;
19784
+ const link = `- [[${target}]] ${description} (${date})`;
18783
19785
  const slugPattern = `[[${target}]]`;
18784
19786
  let replaced = false;
18785
19787
  for (let i = lines.length - 1;i >= 0; i--) {
@@ -18817,15 +19819,67 @@ function indexUpsert(args) {
18817
19819
  }
18818
19820
  atomicWriteFile(indexPath, lines.join(`
18819
19821
  `));
18820
- return { content: [{ type: "text", text: `Index updated: ${link}` }] };
19822
+ return link;
18821
19823
  } finally {
18822
19824
  releaseLock();
18823
19825
  }
18824
19826
  }
19827
+ function wikiDocumentExists(kind, slug) {
19828
+ const root = kind === "article" ? runtime().articlesDir : kind === "summary" ? runtime().summariesDir : kind === "topic" ? runtime().topicsDir : runtime().skillsDir;
19829
+ const filePath = resolve2(root, `${slug}.md`);
19830
+ if (relative(root, filePath).startsWith(".."))
19831
+ return false;
19832
+ try {
19833
+ return statSync(filePath).isFile();
19834
+ } catch {
19835
+ return false;
19836
+ }
19837
+ }
19838
+ function indexUpsert(args) {
19839
+ const rawSlug = safeExt(args, ["slug", "id"], "");
19840
+ const rawDescription = safeExt(args, ["description", "desc", "text"], "");
19841
+ const rawKind = safeExt(args, ["kind", "type"], "article");
19842
+ const section = safeExt(args, ["section", "category"], undefined);
19843
+ const date = safeExt(args, ["date", "created"], undefined);
19844
+ if (!(rawKind in WIKI_INDEX_FILENAMES)) {
19845
+ return {
19846
+ content: [{ type: "text", text: `Unsupported kind: ${rawKind}` }],
19847
+ isError: true
19848
+ };
19849
+ }
19850
+ const kind = rawKind;
19851
+ const slugResult = normalizeWikiSlug(rawSlug);
19852
+ if ("error" in slugResult) {
19853
+ return { content: [{ type: "text", text: slugResult.error }], isError: true };
19854
+ }
19855
+ const descriptionResult = normalizeIndexDescription(rawDescription);
19856
+ if ("error" in descriptionResult) {
19857
+ return { content: [{ type: "text", text: descriptionResult.error }], isError: true };
19858
+ }
19859
+ if (kind !== "topic" && !wikiDocumentExists(kind, slugResult.slug)) {
19860
+ return {
19861
+ content: [
19862
+ {
19863
+ type: "text",
19864
+ text: `No ${kind} document named ${slugResult.slug}. Use wiki_write to create the document and its catalog row together.`
19865
+ }
19866
+ ],
19867
+ isError: true
19868
+ };
19869
+ }
19870
+ const link = upsertIndexRow({
19871
+ kind,
19872
+ slug: slugResult.slug,
19873
+ description: descriptionResult.description,
19874
+ date: date ?? new Date().toISOString().slice(0, 10),
19875
+ section
19876
+ });
19877
+ return { content: [{ type: "text", text: `Index updated: ${link}` }] };
19878
+ }
18825
19879
  var WIKI_TOOLS = [
18826
19880
  {
18827
19881
  name: "wiki_query",
18828
- description: "Search the wiki knowledge base and return relevant articles.",
19882
+ description: "Search topic, article, or summary indexes and return relevant candidates.",
18829
19883
  inputSchema: {
18830
19884
  type: "object",
18831
19885
  properties: {
@@ -18836,11 +19890,38 @@ var WIKI_TOOLS = [
18836
19890
  topic: {
18837
19891
  type: "string",
18838
19892
  description: "Optional topic name to narrow the search"
19893
+ },
19894
+ kind: {
19895
+ type: "string",
19896
+ enum: ["all", "topic", "article", "summary"],
19897
+ description: "Document kind to search; defaults to all"
19898
+ },
19899
+ limit: {
19900
+ type: "number",
19901
+ minimum: 1,
19902
+ maximum: 20,
19903
+ description: "Maximum number of candidates; defaults to 8"
18839
19904
  }
18840
19905
  },
18841
19906
  required: ["question"]
18842
19907
  }
18843
19908
  },
19909
+ {
19910
+ name: "wiki_read",
19911
+ description: "Read one topic, article, or summary returned by wiki_query. For a clearly matching continuing persona, set adopt=true when reading a topic.",
19912
+ inputSchema: {
19913
+ type: "object",
19914
+ properties: {
19915
+ kind: { type: "string", enum: ["topic", "article", "summary"] },
19916
+ key: { type: "string", description: "Canonical key returned by wiki_query" },
19917
+ adopt: {
19918
+ type: "boolean",
19919
+ description: "For kind=topic, persist this canonical memory key for the current room"
19920
+ }
19921
+ },
19922
+ required: ["kind", "key"]
19923
+ }
19924
+ },
18844
19925
  {
18845
19926
  name: "wiki_topic_brief",
18846
19927
  description: "Get the lightweight topic brief for a specific topic.",
@@ -18876,35 +19957,46 @@ var WIKI_TOOLS = [
18876
19957
  }
18877
19958
  },
18878
19959
  {
18879
- name: "save_wiki_entry",
18880
- description: "Save a session summary directly to wiki/summaries/.",
19960
+ name: "wiki_write",
19961
+ description: "Write a wiki document and its catalog row together. The only way to create a summary, article, or topic brief: summaries go to wiki/summaries/ + summary-index.md, articles to wiki/articles/ + article-index.md, briefs to wiki/topic/ + topic-index.md (also mirrored into SQLite for next-session injection). Never write wiki files by any other means.",
18881
19962
  inputSchema: {
18882
19963
  type: "object",
18883
19964
  properties: {
18884
- topic: { type: "string", description: "Topic name of the session" },
18885
- content: { type: "string", description: "Session summary in markdown" }
19965
+ kind: {
19966
+ type: "string",
19967
+ enum: ["summary", "article", "topic"],
19968
+ description: "Which document to write"
19969
+ },
19970
+ content: { type: "string", description: "Document body in markdown" },
19971
+ description: {
19972
+ type: "string",
19973
+ description: "Single-line catalog description; required so the index stays useful"
19974
+ },
19975
+ topic: {
19976
+ type: "string",
19977
+ description: "Topic name; required for kind=summary and kind=topic"
19978
+ },
19979
+ slug: {
19980
+ type: "string",
19981
+ description: "Article slug in kebab-case; required for kind=article"
19982
+ },
19983
+ section: {
19984
+ type: "string",
19985
+ description: "H2 catalog section; required for kind=article"
19986
+ },
19987
+ date: { type: "string", description: "Override entry date YYYY-MM-DD (default: today)" }
18886
19988
  },
18887
- required: ["topic", "content"]
19989
+ required: ["kind", "content", "description"]
18888
19990
  }
18889
19991
  },
18890
19992
  {
18891
- name: "save_topic_brief",
18892
- description: "Write the accumulated persona/topic brief to wiki/topic/<topic>.md and mirror it into SQLite for next-session injection. Call this AFTER save_wiki_entry (archive \u2192 summary \u2192 brief order).",
18893
- inputSchema: {
18894
- type: "object",
18895
- properties: {
18896
- topic: { type: "string", description: "Topic name of the session" },
18897
- content: {
18898
- type: "string",
18899
- description: "Full accumulated topic brief in markdown (persona layer + recent work)"
18900
- }
18901
- },
18902
- required: ["topic", "content"]
18903
- }
19993
+ name: "wiki_reindex",
19994
+ description: "Rebuild the derived body-search cache from the documents on disk and report catalog gaps. Run this once after adding documents outside wiki_write; retrieval itself never scans the wiki.",
19995
+ inputSchema: { type: "object", properties: {} }
18904
19996
  },
18905
19997
  {
18906
19998
  name: "index_upsert",
18907
- description: "Upsert an entry in wiki/article-index.md, topic-index.md, or skill-index.md.",
19999
+ description: "Correct the description or section of an entry that already exists. Cannot create an entry \u2014 use wiki_write for that.",
18908
20000
  inputSchema: {
18909
20001
  type: "object",
18910
20002
  properties: {
@@ -18973,6 +20065,7 @@ function createWikiMcpServer(context, host) {
18973
20065
  const wikiDir = resolve2(host.wikiRoot);
18974
20066
  const current = {
18975
20067
  userId: context.userId,
20068
+ currentTopicId: context.currentTopicId ?? context.topicId,
18976
20069
  topicId: context.topicId,
18977
20070
  surface,
18978
20071
  host,
@@ -19002,12 +20095,13 @@ function createWikiMcpServer(context, host) {
19002
20095
  const { name, arguments: args } = request.params;
19003
20096
  const handlers = {
19004
20097
  wiki_query: wikiQuery,
20098
+ wiki_read: wikiRead,
19005
20099
  wiki_topic_brief: wikiTopicBrief,
19006
20100
  wiki_last_conversation: wikiLastConversation,
19007
20101
  skill_query: skillQuery,
19008
20102
  skill_save: skillSave,
19009
- save_wiki_entry: saveWikiEntry,
19010
- save_topic_brief: saveTopicBrief,
20103
+ wiki_write: wikiWrite,
20104
+ wiki_reindex: wikiReindex,
19011
20105
  index_upsert: indexUpsert
19012
20106
  };
19013
20107
  const handler = tools.some((tool) => tool.name === name) ? handlers[name] : undefined;
@@ -19025,7 +20119,7 @@ init_jsonl();
19025
20119
  init_mcp_helpers();
19026
20120
  await init_codex_registry();
19027
20121
  import { spawn as spawn5 } from "child_process";
19028
- import { existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
20122
+ import { existsSync as existsSync10, readdirSync as readdirSync3 } from "fs";
19029
20123
  import { join as join10 } from "path";
19030
20124
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
19031
20125
  import { z } from "zod";
@@ -19280,7 +20374,7 @@ ${results.every((result) => result.ok) ? "All agents healthy" : "Some agents fai
19280
20374
  if (!context.userId)
19281
20375
  return mcpOk("\uC2E4\uD589 \uC911\uC778 \uCFFC\uB9AC \uC870\uD68C \uBD88\uAC00 (user context \uC5C6\uC74C)");
19282
20376
  const stateDir = join10(USERS_LOG_DIR, context.userId, "active-queries");
19283
- if (!existsSync9(stateDir))
20377
+ if (!existsSync10(stateDir))
19284
20378
  return mcpOk("\uC2E4\uD589 \uC911\uC778 \uCFFC\uB9AC \uC5C6\uC74C");
19285
20379
  let files;
19286
20380
  try {
@@ -19317,7 +20411,7 @@ ${lines.join(`
19317
20411
  }
19318
20412
  // ../../packages/core/src/mcp/factories/compaction-log.ts
19319
20413
  init_mcp_helpers();
19320
- import { readFileSync as readFileSync8, realpathSync as realpathSync3 } from "fs";
20414
+ import { readFileSync as readFileSync9, realpathSync as realpathSync3 } from "fs";
19321
20415
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
19322
20416
  import { z as z2 } from "zod";
19323
20417
  var DEFAULT_MAX_CALLS = 12;
@@ -19350,7 +20444,7 @@ function createCompactionLogMcpServer(context) {
19350
20444
  if (!context.filePath)
19351
20445
  throw new Error("compaction-log MCP requires filePath");
19352
20446
  const filePath = realpathSync3(context.filePath);
19353
- const lines = readFileSync8(filePath, "utf8").split(`
20447
+ const lines = readFileSync9(filePath, "utf8").split(`
19354
20448
  `);
19355
20449
  const maxCalls = positiveInt(context.maxCalls, DEFAULT_MAX_CALLS);
19356
20450
  const maxTotalBytes = positiveInt(context.maxTotalBytes, DEFAULT_MAX_TOTAL_BYTES);
@@ -19943,4 +21037,4 @@ export {
19943
21037
  createAgentHealthMcpServer
19944
21038
  };
19945
21039
 
19946
- //# debugId=AD1DC675E382EEA164756E2164756E21
21040
+ //# debugId=1EA78260283E3CD164756E2164756E21