negotium 0.2.18 → 0.2.20

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 (55) hide show
  1. package/dist/agent-helpers.js +367 -313
  2. package/dist/agent-helpers.js.map +18 -18
  3. package/dist/background-bash.js +3 -2
  4. package/dist/background-bash.js.map +3 -3
  5. package/dist/browser-runtime.js +60 -36
  6. package/dist/browser-runtime.js.map +6 -6
  7. package/dist/{chunk-fc58cqry.js → chunk-g2kqfdwm.js} +154 -24
  8. package/dist/{chunk-fc58cqry.js.map → chunk-g2kqfdwm.js.map} +7 -6
  9. package/dist/hosted-agent.js +183 -53
  10. package/dist/hosted-agent.js.map +8 -7
  11. package/dist/main.js +1395 -626
  12. package/dist/main.js.map +27 -26
  13. package/dist/mcp-factories.js +1204 -624
  14. package/dist/mcp-factories.js.map +21 -20
  15. package/dist/prompts.js +3 -2
  16. package/dist/prompts.js.map +3 -3
  17. package/dist/query-runtime.js +47 -26
  18. package/dist/query-runtime.js.map +4 -4
  19. package/dist/registry.js +3 -3
  20. package/dist/registry.js.map +2 -2
  21. package/dist/rollout.js +1 -1
  22. package/dist/runtime/src/agents/archiver.ts +4 -3
  23. package/dist/runtime/src/application/switch-topic-access-mode.ts +81 -8
  24. package/dist/runtime/src/bus.ts +19 -2
  25. package/dist/runtime/src/mcp/canonical-proxy-server.ts +13 -14
  26. package/dist/runtime/src/mcp/session-comm/runtime.ts +16 -0
  27. package/dist/runtime/src/mcp/wiki-search-index.ts +359 -0
  28. package/dist/runtime/src/mcp/wiki-server.ts +559 -98
  29. package/dist/runtime/src/platform/config.ts +13 -1
  30. package/dist/runtime/src/platform/mcp-config.ts +1 -1
  31. package/dist/runtime/src/prompts/agents/wiki-archiver.md +62 -47
  32. package/dist/runtime/src/runtime/ask-callbacks.ts +19 -11
  33. package/dist/runtime/src/runtime/background-sessions.ts +9 -7
  34. package/dist/runtime/src/runtime/visual-store.ts +31 -23
  35. package/dist/runtime/src/storage/api-topics.ts +26 -0
  36. package/dist/runtime/src/storage/browser-profiles.ts +16 -8
  37. package/dist/runtime/src/storage/runtime-events.ts +30 -20
  38. package/dist/runtime/src/storage/runtime-gateway-submissions.ts +24 -16
  39. package/dist/runtime/src/storage/runtime-turn-requests.ts +9 -1
  40. package/dist/runtime/src/storage/self-schedules.ts +32 -24
  41. package/dist/runtime/src/storage/storage-host.ts +105 -43
  42. package/dist/runtime/src/storage/vault.ts +12 -3
  43. package/dist/runtime/src/version.ts +1 -1
  44. package/dist/runtime-helpers.js +47 -26
  45. package/dist/runtime-helpers.js.map +4 -4
  46. package/dist/storage.js +76 -41
  47. package/dist/storage.js.map +4 -4
  48. package/dist/types/packages/core/src/agents/archiver.d.ts +1 -1
  49. package/dist/types/packages/core/src/mcp/wiki-search-index.d.ts +59 -0
  50. package/dist/types/packages/core/src/storage/api-topics.d.ts +15 -0
  51. package/dist/types/packages/core/src/storage/storage-host.d.ts +10 -0
  52. package/dist/types/packages/core/src/version.d.ts +1 -1
  53. package/dist/vault.js +140 -10
  54. package/dist/vault.js.map +6 -5
  55. 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)
@@ -139,7 +192,7 @@ function browserRsMeetsMinimumVersion(candidate) {
139
192
  try {
140
193
  const output = execFileSync(candidate, ["--version"], {
141
194
  encoding: "utf8",
142
- timeout: 2000,
195
+ timeout: BROWSER_RS_VERSION_PROBE_TIMEOUT_MS,
143
196
  stdio: ["ignore", "pipe", "ignore"]
144
197
  }).trim();
145
198
  const match = output.match(/^browser-rs (\d+)\.(\d+)\.(\d+)$/);
@@ -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
  }
@@ -250,7 +303,7 @@ function resolveDefaultModel(agent, registryDefaultModel) {
250
303
  function codexAuthFilePath() {
251
304
  return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(process.env.CODEX_HOME || join(homedir(), ".codex"), "auth.json");
252
305
  }
253
- var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CRON_WORKSPACE_DIR, BROWSER_DIR, BROWSER_PROFILES_DIR, BINARIES_DIR, SECRETS_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BROWSER_RS_VERSION = "v0.1.19", BROWSER_RS_MIN_SECURE_VERSION = "0.1.15", SNIPPETS_API_URL, BROWSER_RS_BIN, BASH_RS_VERSION = "v0.1.5", BASH_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, BROWSER_MCP_SSE_PROXY_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, UPLOADS_DIR, VAULT_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, BASHRS_SPILL_ROOT, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
306
+ var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CRON_WORKSPACE_DIR, BROWSER_DIR, BROWSER_PROFILES_DIR, BINARIES_DIR, SECRETS_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BROWSER_RS_VERSION = "v0.1.19", BROWSER_RS_MIN_SECURE_VERSION = "0.1.15", BROWSER_RS_VERSION_PROBE_TIMEOUT_MS = 15000, SNIPPETS_API_URL, BROWSER_RS_BIN, BASH_RS_VERSION = "v0.1.5", BASH_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, BROWSER_MCP_SSE_PROXY_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, UPLOADS_DIR, VAULT_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, BASHRS_SPILL_ROOT, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
254
307
  var init_config = __esm(() => {
255
308
  init_config_helpers();
256
309
  init_logger();
@@ -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.18";
1008
+ var NEGOTIUM_VERSION = "0.2.20";
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;
@@ -2188,57 +2241,161 @@ var init_mcp_config = __esm(() => {
2188
2241
  nodeMcpEntries = [];
2189
2242
  });
2190
2243
 
2191
- // ../../packages/core/src/storage/sqlite.ts
2192
- var isBun, Database;
2193
- var init_sqlite = __esm(async () => {
2194
- isBun = typeof process.versions.bun === "string";
2195
- if (isBun) {
2196
- ({ Database } = await import("bun:sqlite"));
2197
- } else {
2198
- const nodeSqliteSpecifier = ["node", "sqlite"].join(":");
2199
- const { DatabaseSync } = await import(nodeSqliteSpecifier);
2200
-
2201
- class NodeDatabase {
2202
- #db;
2203
- constructor(path, options = {}) {
2204
- this.#db = options.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
2205
- }
2206
- query(sql) {
2207
- return this.#db.prepare(sql);
2208
- }
2209
- prepare(sql) {
2210
- return this.#db.prepare(sql);
2211
- }
2212
- exec(sql) {
2213
- this.#db.exec(sql);
2214
- }
2215
- run(sql, ...params) {
2216
- return this.#db.prepare(sql).run(...params);
2217
- }
2218
- transaction(fn) {
2219
- const run = (begin) => (...args) => {
2220
- this.#db.exec(begin);
2221
- try {
2222
- const result = fn(...args);
2223
- this.#db.exec("COMMIT");
2224
- return result;
2225
- } catch (err) {
2226
- this.#db.exec("ROLLBACK");
2227
- throw err;
2228
- }
2229
- };
2230
- const tx = run("BEGIN");
2231
- tx.deferred = run("BEGIN DEFERRED");
2232
- tx.immediate = run("BEGIN IMMEDIATE");
2233
- tx.exclusive = run("BEGIN EXCLUSIVE");
2234
- return tx;
2235
- }
2236
- close() {
2237
- this.#db.close();
2244
+ // ../../packages/core/src/storage/storage-host.ts
2245
+ import { mkdirSync as mkdirSync6 } from "fs";
2246
+ import { homedir as homedir4 } from "os";
2247
+ import { dirname as dirname5, join as join6, resolve as resolve5 } from "path";
2248
+ function storageState() {
2249
+ const holder = globalThis;
2250
+ const existing = holder[STORAGE_HOST_STATE];
2251
+ if (existing)
2252
+ return existing;
2253
+ const created = {
2254
+ configuredHost: {},
2255
+ fallbackDatabase: null,
2256
+ fallbackDatabasePath: null,
2257
+ frames: [],
2258
+ schemaInitializers: [],
2259
+ initializedSchemas: new WeakMap,
2260
+ initializingDatabases: new WeakSet
2261
+ };
2262
+ Object.defineProperty(holder, STORAGE_HOST_STATE, {
2263
+ value: created,
2264
+ enumerable: false,
2265
+ writable: false,
2266
+ configurable: false
2267
+ });
2268
+ return created;
2269
+ }
2270
+ function envPath(name, fallback) {
2271
+ const value2 = process.env[name]?.trim();
2272
+ return resolve5(value2 || fallback);
2273
+ }
2274
+ function defaultStateDir() {
2275
+ return envPath("NEGOTIUM_STATE_DIR", join6(homedir4(), ".negotium"));
2276
+ }
2277
+ function defaultDataDir() {
2278
+ return envPath("NEGOTIUM_DATA_DIR", join6(defaultStateDir(), "data"));
2279
+ }
2280
+ function defaultLogDir() {
2281
+ return envPath("NEGOTIUM_LOG_DIR", join6(defaultStateDir(), "logs"));
2282
+ }
2283
+ function defaultWorkspaceDir() {
2284
+ return envPath("NEGOTIUM_WORKSPACE_DIR", join6(defaultStateDir(), "workspace"));
2285
+ }
2286
+ function defaultSessionAsksDir() {
2287
+ const runDir = envPath("NEGOTIUM_RUN_DIR", join6(defaultStateDir(), "runtime"));
2288
+ return join6(runDir, "session-asks");
2289
+ }
2290
+ function defaultSessionsDatabasePath() {
2291
+ return envPath("SESSIONS_DB_PATH", join6(resolveStorageDataDir(), "sessions.db"));
2292
+ }
2293
+ function isSqliteBusy(error) {
2294
+ const message = error instanceof Error ? error.message : String(error);
2295
+ return /database is (?:locked|busy)|SQLITE_(?:BUSY|LOCKED)/i.test(message);
2296
+ }
2297
+ function execWithBusyRetry(database, sql, timeoutMs = SQLITE_INIT_TIMEOUT_MS) {
2298
+ const deadline = Date.now() + timeoutMs;
2299
+ while (true) {
2300
+ try {
2301
+ database.exec(sql);
2302
+ return;
2303
+ } catch (error) {
2304
+ if (!isSqliteBusy(error) || Date.now() >= deadline)
2305
+ throw error;
2306
+ Atomics.wait(SQLITE_INIT_SLEEP, 0, 0, Math.min(SQLITE_INIT_RETRY_MS, deadline - Date.now()));
2307
+ }
2308
+ }
2309
+ }
2310
+ function initializeDatabase(database) {
2311
+ database.exec("PRAGMA busy_timeout = 5000");
2312
+ execWithBusyRetry(database, "PRAGMA journal_mode = WAL");
2313
+ database.exec("PRAGMA foreign_keys = ON");
2314
+ database.exec("PRAGMA wal_autocheckpoint = 1000");
2315
+ try {
2316
+ database.exec("PRAGMA wal_checkpoint(TRUNCATE)");
2317
+ } catch {}
2318
+ }
2319
+ function defaultDatabase() {
2320
+ const path = defaultSessionsDatabasePath();
2321
+ const state = storageState();
2322
+ if (state.fallbackDatabase && state.fallbackDatabasePath === path)
2323
+ return state.fallbackDatabase;
2324
+ if (state.fallbackDatabase)
2325
+ state.fallbackDatabase.close();
2326
+ mkdirSync6(dirname5(path), { recursive: true });
2327
+ state.fallbackDatabase = new Database(path, { create: true });
2328
+ state.fallbackDatabasePath = path;
2329
+ initializeDatabase(state.fallbackDatabase);
2330
+ return state.fallbackDatabase;
2331
+ }
2332
+ function resolveStorageDatabase() {
2333
+ return storageState().configuredHost.database ?? defaultDatabase();
2334
+ }
2335
+ function resolveStorageDataDir() {
2336
+ return storageState().configuredHost.dataDir ?? defaultDataDir();
2337
+ }
2338
+ function resolveStorageLogDir() {
2339
+ return storageState().configuredHost.logDir ?? defaultLogDir();
2340
+ }
2341
+ function resolveStorageSessionAsksDir() {
2342
+ return storageState().configuredHost.sessionAsksDir ?? defaultSessionAsksDir();
2343
+ }
2344
+ function resolveStorageWorkspaceDir() {
2345
+ return storageState().configuredHost.workspaceDir ?? defaultWorkspaceDir();
2346
+ }
2347
+ function resolveStorageSharedWikiDir() {
2348
+ return storageState().configuredHost.sharedWikiDir ?? join6(resolveStorageWorkspaceDir(), "wiki");
2349
+ }
2350
+ function registerStorageSchemaInitializer(initialize, priority = 100) {
2351
+ const initializers = storageState().schemaInitializers;
2352
+ initializers.push({ initialize, priority });
2353
+ initializers.sort((a, b) => a.priority - b.priority);
2354
+ }
2355
+ function ensureStorageSchemas(database = resolveStorageDatabase()) {
2356
+ const state = storageState();
2357
+ if (state.initializingDatabases.has(database))
2358
+ return;
2359
+ let initialized = state.initializedSchemas.get(database);
2360
+ if (!initialized) {
2361
+ initialized = new Set;
2362
+ state.initializedSchemas.set(database, initialized);
2363
+ }
2364
+ state.initializingDatabases.add(database);
2365
+ try {
2366
+ for (const entry of state.schemaInitializers) {
2367
+ if (initialized.has(entry.initialize))
2368
+ continue;
2369
+ initialized.add(entry.initialize);
2370
+ try {
2371
+ entry.initialize(database);
2372
+ } catch (error) {
2373
+ initialized.delete(entry.initialize);
2374
+ throw error;
2238
2375
  }
2239
2376
  }
2240
- Database = NodeDatabase;
2377
+ } finally {
2378
+ state.initializingDatabases.delete(database);
2241
2379
  }
2380
+ }
2381
+ var STORAGE_HOST_STATE, SQLITE_INIT_RETRY_MS = 25, SQLITE_INIT_TIMEOUT_MS = 5000, SQLITE_INIT_SLEEP, internalStorageDatabase;
2382
+ var init_storage_host = __esm(async () => {
2383
+ await init_sqlite();
2384
+ STORAGE_HOST_STATE = Symbol.for("negotium.storage-host.state.v1");
2385
+ SQLITE_INIT_SLEEP = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
2386
+ internalStorageDatabase = new Proxy({}, {
2387
+ get(_target, property) {
2388
+ const database = resolveStorageDatabase();
2389
+ ensureStorageSchemas(database);
2390
+ const value2 = Reflect.get(database, property, database);
2391
+ return typeof value2 === "function" ? value2.bind(database) : value2;
2392
+ },
2393
+ set(_target, property, value2) {
2394
+ const database = resolveStorageDatabase();
2395
+ ensureStorageSchemas(database);
2396
+ return Reflect.set(database, property, value2, database);
2397
+ }
2398
+ });
2242
2399
  });
2243
2400
 
2244
2401
  // ../../packages/core/src/storage/vault-crypto-core.ts
@@ -2297,8 +2454,8 @@ var init_vault_crypto = __esm(() => {
2297
2454
  });
2298
2455
 
2299
2456
  // ../../packages/core/src/storage/vault.ts
2300
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync6 } from "fs";
2301
- import { join as join6 } from "path";
2457
+ import { chmodSync as chmodSync4, mkdirSync as mkdirSync7 } from "fs";
2458
+ import { join as join7 } from "path";
2302
2459
  function initializeVaultDatabase(database) {
2303
2460
  database.exec("PRAGMA journal_mode = WAL");
2304
2461
  database.exec("PRAGMA busy_timeout = 5000");
@@ -2333,17 +2490,18 @@ function initializeVaultDatabase(database) {
2333
2490
  }
2334
2491
  }
2335
2492
  function openVaultDatabase(dataDir) {
2336
- const vaultDir = join6(dataDir, "vault");
2337
- const path = join6(vaultDir, "vault.db");
2338
- mkdirSync6(vaultDir, { recursive: true, mode: 448 });
2493
+ const vaultDir = join7(dataDir, "vault");
2494
+ const path = join7(vaultDir, "vault.db");
2495
+ mkdirSync7(vaultDir, { recursive: true, mode: 448 });
2339
2496
  const database = new Database(path, { create: true });
2340
- chmodSync3(path, 384);
2497
+ chmodSync4(path, 384);
2341
2498
  initializeVaultDatabase(database);
2342
2499
  return database;
2343
2500
  }
2344
2501
  function activeVaultDatabase() {
2345
- if (!vaultDb)
2346
- vaultDb = openVaultDatabase(DATA_DIR);
2502
+ if (!vaultDb) {
2503
+ vaultDb = openVaultDatabase(resolveStorageDataDir());
2504
+ }
2347
2505
  return vaultDb;
2348
2506
  }
2349
2507
  function normalizeVaultKey(key) {
@@ -2433,14 +2591,17 @@ var vaultDb, vaultMasterKey, VAULT_VALUE_MAX_BYTES;
2433
2591
  var init_vault = __esm(async () => {
2434
2592
  init_config();
2435
2593
  init_vault_crypto();
2436
- await init_sqlite();
2594
+ await __promiseAll([
2595
+ init_sqlite(),
2596
+ init_storage_host()
2597
+ ]);
2437
2598
  vaultMasterKey = VAULT_MASTER_KEY;
2438
2599
  VAULT_VALUE_MAX_BYTES = 64 * 1024;
2439
2600
  });
2440
2601
 
2441
2602
  // ../../packages/core/src/agents/execution-host.ts
2442
2603
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
2443
- import { dirname as dirname5 } from "path";
2604
+ import { dirname as dirname6 } from "path";
2444
2605
  function activeHost() {
2445
2606
  const scoped = scopedHost.getStore();
2446
2607
  if (scoped)
@@ -2475,7 +2636,7 @@ function hostedCodexAuthFilePath() {
2475
2636
  return activeHost().codexAuthFilePath();
2476
2637
  }
2477
2638
  function hostedCodexHomePath() {
2478
- return dirname5(hostedCodexAuthFilePath());
2639
+ return dirname6(hostedCodexAuthFilePath());
2479
2640
  }
2480
2641
  var defaultHost, hostRegistrations, scopedHost;
2481
2642
  var init_execution_host = __esm(async () => {
@@ -2498,15 +2659,15 @@ var init_execution_host = __esm(async () => {
2498
2659
 
2499
2660
  // ../../packages/core/src/agents/rollout/codex.ts
2500
2661
  import { randomBytes as randomBytes4 } from "crypto";
2501
- import { existsSync as existsSync6, readFileSync as readFileSync6, realpathSync as realpathSync2, statSync as statSync3, unlinkSync as unlinkSync6 } from "fs";
2502
- import { basename as basename2, dirname as dirname6, join as join7, resolve as resolve5 } from "path";
2662
+ import { existsSync as existsSync7, readFileSync as readFileSync7, realpathSync as realpathSync2, statSync as statSync3, unlinkSync as unlinkSync7 } from "fs";
2663
+ import { basename as basename2, dirname as dirname7, join as join8, resolve as resolve6 } from "path";
2503
2664
  function codexSessionsDir() {
2504
- return join7(hostedCodexHomePath(), "sessions");
2665
+ return join8(hostedCodexHomePath(), "sessions");
2505
2666
  }
2506
2667
  function loadCodexShell() {
2507
2668
  if (_shellCache)
2508
2669
  return _shellCache;
2509
- const raw = readFileSync6(join7(FIXTURES_DIR, "codex-shell.jsonl"), "utf8");
2670
+ const raw = readFileSync7(join8(FIXTURES_DIR, "codex-shell.jsonl"), "utf8");
2510
2671
  const lines = parseJsonlText(raw);
2511
2672
  if (lines.length < 5) {
2512
2673
  throw new Error(`loadCodexShell: expected >=5 entries in codex-shell.jsonl, got ${lines.length}`);
@@ -2573,16 +2734,16 @@ function codexRolloutPath(threadId, fallback) {
2573
2734
  const hh = String(createdAt.getHours()).padStart(2, "0");
2574
2735
  const min = String(createdAt.getMinutes()).padStart(2, "0");
2575
2736
  const ss = String(createdAt.getSeconds()).padStart(2, "0");
2576
- const dir = join7(codexSessionsDir(), yyyy, mm, dd);
2577
- return join7(dir, `rollout-${yyyy}-${mm}-${dd}T${hh}-${min}-${ss}-${threadId}.jsonl`);
2737
+ const dir = join8(codexSessionsDir(), yyyy, mm, dd);
2738
+ return join8(dir, `rollout-${yyyy}-${mm}-${dd}T${hh}-${min}-${ss}-${threadId}.jsonl`);
2578
2739
  }
2579
2740
  function canonicalFilePath(path) {
2580
- const absolute = resolve5(path);
2741
+ const absolute = resolve6(path);
2581
2742
  try {
2582
2743
  return realpathSync2(absolute);
2583
2744
  } catch {
2584
2745
  try {
2585
- return join7(realpathSync2(dirname6(absolute)), basename2(absolute));
2746
+ return join8(realpathSync2(dirname7(absolute)), basename2(absolute));
2586
2747
  } catch {
2587
2748
  return absolute;
2588
2749
  }
@@ -2692,7 +2853,7 @@ function readCodexPatchCallIds(threadId) {
2692
2853
  if (!path)
2693
2854
  return [];
2694
2855
  try {
2695
- return extractCodexPatchCallIds(readFileSync6(path, "utf8"));
2856
+ return extractCodexPatchCallIds(readFileSync7(path, "utf8"));
2696
2857
  } catch (error) {
2697
2858
  logger.debug({ error, threadId }, "codex patch ids: rollout read failed");
2698
2859
  return [];
@@ -2703,7 +2864,7 @@ function readLatestCodexPatchPreview(threadId, expectedPaths, consumedCallIds =
2703
2864
  if (!path)
2704
2865
  return;
2705
2866
  try {
2706
- return extractLatestCodexPatchPreview(readFileSync6(path, "utf8"), expectedPaths, consumedCallIds, expectedCallId);
2867
+ return extractLatestCodexPatchPreview(readFileSync7(path, "utf8"), expectedPaths, consumedCallIds, expectedCallId);
2707
2868
  } catch (error) {
2708
2869
  logger.debug({ error, threadId }, "codex patch preview: rollout read failed");
2709
2870
  return;
@@ -2761,7 +2922,7 @@ function readLatestCodexContextUsage(threadId) {
2761
2922
  if (!path)
2762
2923
  return;
2763
2924
  try {
2764
- return extractLatestCodexContextUsage(readFileSync6(path, "utf8"));
2925
+ return extractLatestCodexContextUsage(readFileSync7(path, "utf8"));
2765
2926
  } catch (error) {
2766
2927
  logger.debug({ error, threadId }, "codex context usage: rollout read failed");
2767
2928
  return;
@@ -2772,7 +2933,7 @@ function readLatestCodexTokenTotals(threadId) {
2772
2933
  if (!path)
2773
2934
  return;
2774
2935
  try {
2775
- return extractLatestCodexTokenTotals(readFileSync6(path, "utf8"));
2936
+ return extractLatestCodexTokenTotals(readFileSync7(path, "utf8"));
2776
2937
  } catch (error) {
2777
2938
  logger.debug({ error, threadId }, "codex token totals: rollout read failed");
2778
2939
  return;
@@ -2783,7 +2944,7 @@ function migrateCodexRolloutNativeMultiAgentMetadata(threadId) {
2783
2944
  if (!path)
2784
2945
  return false;
2785
2946
  try {
2786
- const entries = parseJsonlText(readFileSync6(path, "utf8"));
2947
+ const entries = parseJsonlText(readFileSync7(path, "utf8"));
2787
2948
  let changed = false;
2788
2949
  for (const entry of entries) {
2789
2950
  if (entry.type !== "session_meta" && entry.type !== "turn_context")
@@ -2812,19 +2973,19 @@ function latestCodexRolloutPath(threadId) {
2812
2973
  try {
2813
2974
  if (buckets) {
2814
2975
  for (const bucket of buckets) {
2815
- const dir = join7(sessionsDir, bucket);
2816
- if (!existsSync6(dir))
2976
+ const dir = join8(sessionsDir, bucket);
2977
+ if (!existsSync7(dir))
2817
2978
  continue;
2818
2979
  const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
2819
2980
  for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
2820
- candidates.push(join7(dir, rel));
2981
+ candidates.push(join8(dir, rel));
2821
2982
  }
2822
2983
  }
2823
2984
  }
2824
2985
  if (candidates.length === 0) {
2825
2986
  const glob = new Bun.Glob(`**/rollout-*-${threadId}.jsonl`);
2826
2987
  for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
2827
- candidates.push(join7(sessionsDir, rel));
2988
+ candidates.push(join8(sessionsDir, rel));
2828
2989
  }
2829
2990
  }
2830
2991
  return candidates.sort((a, b) => statSync3(b).mtimeMs - statSync3(a).mtimeMs)[0];
@@ -2945,15 +3106,15 @@ function sweepPriorRolloutsForThread(threadId) {
2945
3106
  return;
2946
3107
  }
2947
3108
  for (const bucket of buckets) {
2948
- const dir = join7(sessionsDir, bucket);
2949
- if (!existsSync6(dir))
3109
+ const dir = join8(sessionsDir, bucket);
3110
+ if (!existsSync7(dir))
2950
3111
  continue;
2951
3112
  try {
2952
3113
  const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
2953
3114
  for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
2954
- const fullPath = join7(dir, rel);
3115
+ const fullPath = join8(dir, rel);
2955
3116
  try {
2956
- unlinkSync6(fullPath);
3117
+ unlinkSync7(fullPath);
2957
3118
  } catch (e) {
2958
3119
  if (e?.code !== "ENOENT") {
2959
3120
  logger.warn({ err: e, path: fullPath }, "writeCodexRollout: prior-rollout unlink failed (continuing)");
@@ -2969,9 +3130,9 @@ function sweepPriorRolloutsFullTree(threadId, sessionsDir) {
2969
3130
  try {
2970
3131
  const glob = new Bun.Glob(`**/rollout-*-${threadId}.jsonl`);
2971
3132
  for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
2972
- const fullPath = join7(sessionsDir, rel);
3133
+ const fullPath = join8(sessionsDir, rel);
2973
3134
  try {
2974
- unlinkSync6(fullPath);
3135
+ unlinkSync7(fullPath);
2975
3136
  } catch (e) {
2976
3137
  if (e?.code !== "ENOENT") {
2977
3138
  logger.warn({ err: e, path: fullPath }, "writeCodexRollout: prior-rollout unlink failed (continuing)");
@@ -3035,7 +3196,7 @@ import { spawn as spawn4 } from "child_process";
3035
3196
  function createCodexAppServerForker(host) {
3036
3197
  return async (parentThreadId) => {
3037
3198
  const child = host.spawnServer();
3038
- return await new Promise((resolve6, reject) => {
3199
+ return await new Promise((resolve7, reject) => {
3039
3200
  let settled = false;
3040
3201
  let stdoutBuffer = "";
3041
3202
  let stderr = "";
@@ -3052,7 +3213,7 @@ function createCodexAppServerForker(host) {
3052
3213
  if (error)
3053
3214
  reject(error);
3054
3215
  else if (result)
3055
- resolve6(result);
3216
+ resolve7(result);
3056
3217
  else
3057
3218
  reject(new Error("Codex thread fork returned no result"));
3058
3219
  };
@@ -3148,8 +3309,8 @@ var init_codex_app_server = __esm(async () => {
3148
3309
  });
3149
3310
 
3150
3311
  // ../../packages/core/src/agents/codex-registry.ts
3151
- import { existsSync as existsSync7, unlinkSync as unlinkSync7 } from "fs";
3152
- import { join as join8 } from "path";
3312
+ import { existsSync as existsSync8, unlinkSync as unlinkSync8 } from "fs";
3313
+ import { join as join9 } from "path";
3153
3314
  var VALID_EFFORTS2, codexRegistry, codexRegistryOperations;
3154
3315
  var init_codex_registry = __esm(async () => {
3155
3316
  init_logger();
@@ -3194,17 +3355,17 @@ var init_codex_registry = __esm(async () => {
3194
3355
  async cleanupRollouts({ sessionIds }) {
3195
3356
  if (sessionIds.length === 0)
3196
3357
  return;
3197
- const sessionsDir = join8(hostedCodexHomePath(), "sessions");
3198
- if (!existsSync7(sessionsDir))
3358
+ const sessionsDir = join9(hostedCodexHomePath(), "sessions");
3359
+ if (!existsSync8(sessionsDir))
3199
3360
  return;
3200
3361
  const failures = [];
3201
3362
  for (const tid of sessionIds) {
3202
3363
  try {
3203
3364
  const glob = new Bun.Glob(`**/rollout-*-${tid}.jsonl`);
3204
3365
  for await (const rel of glob.scan({ cwd: sessionsDir, onlyFiles: true })) {
3205
- const path = join8(sessionsDir, rel);
3366
+ const path = join9(sessionsDir, rel);
3206
3367
  try {
3207
- unlinkSync7(path);
3368
+ unlinkSync8(path);
3208
3369
  } catch (e) {
3209
3370
  if (e?.code !== "ENOENT") {
3210
3371
  logger.warn({ err: e, path }, "codex cleanupRollouts: unlink failed");
@@ -3226,23 +3387,23 @@ var init_codex_registry = __esm(async () => {
3226
3387
 
3227
3388
  // ../../packages/core/src/agents/maestro-registry.ts
3228
3389
  import { randomUUID as randomUUID4 } from "crypto";
3229
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
3230
- import { homedir as homedir4 } from "os";
3231
- import { join as join9, resolve as resolve6 } from "path";
3390
+ import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
3391
+ import { homedir as homedir5 } from "os";
3392
+ import { join as join10, resolve as resolve7 } from "path";
3232
3393
  function maestroSessionsDir() {
3233
- return join9(process.env.MAESTRO_DATA_DIR ? resolve6(process.env.MAESTRO_DATA_DIR) : join9(homedir4(), ".maestro"), "sessions");
3394
+ return join10(process.env.MAESTRO_DATA_DIR ? resolve7(process.env.MAESTRO_DATA_DIR) : join10(homedir5(), ".maestro"), "sessions");
3234
3395
  }
3235
3396
  function maestroSessionPath(sessionId) {
3236
- return join9(maestroSessionsDir(), `${sessionId}.jsonl`);
3397
+ return join10(maestroSessionsDir(), `${sessionId}.jsonl`);
3237
3398
  }
3238
3399
  function maestroActiveSessionPath(sessionId) {
3239
- return join9(maestroSessionsDir(), `${sessionId}.active.jsonl`);
3400
+ return join10(maestroSessionsDir(), `${sessionId}.active.jsonl`);
3240
3401
  }
3241
3402
  function existingCreatedAt(path) {
3242
- if (!existsSync8(path))
3403
+ if (!existsSync9(path))
3243
3404
  return;
3244
3405
  try {
3245
- const firstLine = readFileSync7(path, "utf8").split(`
3406
+ const firstLine = readFileSync8(path, "utf8").split(`
3246
3407
  `, 1)[0];
3247
3408
  const parsed = JSON.parse(firstLine);
3248
3409
  return typeof parsed._meta?.createdAt === "string" ? parsed._meta.createdAt : undefined;
@@ -3258,7 +3419,7 @@ function writeRollout(options) {
3258
3419
  assertUuidLike("sessionId", sessionId);
3259
3420
  ensureCwdExists(options.cwd);
3260
3421
  const path = maestroSessionPath(sessionId);
3261
- mkdirSync7(maestroSessionsDir(), { recursive: true });
3422
+ mkdirSync8(maestroSessionsDir(), { recursive: true });
3262
3423
  const messages = extractChatPairs(options.entries).flatMap((pair) => [
3263
3424
  { role: "user", content: nonEmptyTurnText(pair.userText) },
3264
3425
  { role: "assistant", content: nonEmptyTurnText(pair.assistantText) }
@@ -3323,7 +3484,7 @@ var init_maestro_registry = __esm(() => {
3323
3484
  if (!parentMessages) {
3324
3485
  throw new Error(`Maestro parent session not found: ${options.parentSessionId}`);
3325
3486
  }
3326
- const parentHasActiveProjection = existsSync8(maestroActiveSessionPath(options.parentSessionId));
3487
+ const parentHasActiveProjection = existsSync9(maestroActiveSessionPath(options.parentSessionId));
3327
3488
  const fork = forkSessionAt({
3328
3489
  parentSessionId: options.parentSessionId,
3329
3490
  messageIndex: parentMessages.length,
@@ -3338,7 +3499,7 @@ var init_maestro_registry = __esm(() => {
3338
3499
  return {
3339
3500
  forkId: fork.sessionId,
3340
3501
  rolloutPath: fork.rolloutPath,
3341
- ...existsSync8(activePath) ? { cleanupPaths: [activePath] } : {}
3502
+ ...existsSync9(activePath) ? { cleanupPaths: [activePath] } : {}
3342
3503
  };
3343
3504
  },
3344
3505
  async cleanupRollouts(options) {
@@ -3350,7 +3511,7 @@ var init_maestro_registry = __esm(() => {
3350
3511
  const remaining = [
3351
3512
  maestroSessionPath(sessionId),
3352
3513
  maestroActiveSessionPath(sessionId)
3353
- ].filter(existsSync8);
3514
+ ].filter(existsSync9);
3354
3515
  if (remaining.length > 0) {
3355
3516
  throw new Error(`Maestro session files remain after cleanup: ${remaining.join(", ")}`);
3356
3517
  }
@@ -3394,176 +3555,41 @@ var init_common = __esm(() => {
3394
3555
  init_mcp_helpers();
3395
3556
  });
3396
3557
 
3397
- // ../../packages/core/src/storage/storage-host.ts
3398
- import { mkdirSync as mkdirSync8 } from "fs";
3399
- import { homedir as homedir5 } from "os";
3400
- import { dirname as dirname7, join as join11, resolve as resolve7 } from "path";
3401
- function envPath(name, fallback) {
3402
- const value2 = process.env[name]?.trim();
3403
- return resolve7(value2 || fallback);
3404
- }
3405
- function defaultStateDir() {
3406
- return envPath("NEGOTIUM_STATE_DIR", join11(homedir5(), ".negotium"));
3407
- }
3408
- function defaultDataDir() {
3409
- return envPath("NEGOTIUM_DATA_DIR", join11(defaultStateDir(), "data"));
3410
- }
3411
- function defaultLogDir() {
3412
- return envPath("NEGOTIUM_LOG_DIR", join11(defaultStateDir(), "logs"));
3558
+ // ../../packages/core/src/storage/forum-db.ts
3559
+ var db;
3560
+ var init_forum_db = __esm(async () => {
3561
+ await init_storage_host();
3562
+ db = internalStorageDatabase;
3563
+ });
3564
+
3565
+ // ../../packages/core/src/storage/runtime-events.ts
3566
+ function rowToEvent(row) {
3567
+ if (!types.has(row.event_type))
3568
+ return null;
3569
+ try {
3570
+ return {
3571
+ seq: Number(row.seq),
3572
+ sourceId: row.source_id,
3573
+ type: row.event_type,
3574
+ topicId: row.topic_id,
3575
+ payload: JSON.parse(row.payload_json),
3576
+ createdAt: row.created_at
3577
+ };
3578
+ } catch {
3579
+ return null;
3580
+ }
3413
3581
  }
3414
- function defaultWorkspaceDir() {
3415
- return envPath("NEGOTIUM_WORKSPACE_DIR", join11(defaultStateDir(), "workspace"));
3582
+ function latestRuntimeEventSeq() {
3583
+ const row = db.query("SELECT MAX(seq) AS seq FROM runtime_events").get();
3584
+ return Number(row?.seq ?? 0);
3416
3585
  }
3417
- function defaultSessionAsksDir() {
3418
- const runDir = envPath("NEGOTIUM_RUN_DIR", join11(defaultStateDir(), "runtime"));
3419
- return join11(runDir, "session-asks");
3420
- }
3421
- function defaultSessionsDatabasePath() {
3422
- return envPath("SESSIONS_DB_PATH", join11(resolveStorageDataDir(), "sessions.db"));
3423
- }
3424
- function isSqliteBusy(error) {
3425
- const message = error instanceof Error ? error.message : String(error);
3426
- return /database is (?:locked|busy)|SQLITE_(?:BUSY|LOCKED)/i.test(message);
3427
- }
3428
- function execWithBusyRetry(database, sql, timeoutMs = SQLITE_INIT_TIMEOUT_MS) {
3429
- const deadline = Date.now() + timeoutMs;
3430
- while (true) {
3431
- try {
3432
- database.exec(sql);
3433
- return;
3434
- } catch (error) {
3435
- if (!isSqliteBusy(error) || Date.now() >= deadline)
3436
- throw error;
3437
- Atomics.wait(SQLITE_INIT_SLEEP, 0, 0, Math.min(SQLITE_INIT_RETRY_MS, deadline - Date.now()));
3438
- }
3439
- }
3440
- }
3441
- function initializeDatabase(database) {
3442
- database.exec("PRAGMA busy_timeout = 5000");
3443
- execWithBusyRetry(database, "PRAGMA journal_mode = WAL");
3444
- database.exec("PRAGMA foreign_keys = ON");
3445
- database.exec("PRAGMA wal_autocheckpoint = 1000");
3446
- try {
3447
- database.exec("PRAGMA wal_checkpoint(TRUNCATE)");
3448
- } catch {}
3449
- }
3450
- function defaultDatabase() {
3451
- const path = defaultSessionsDatabasePath();
3452
- if (fallbackDatabase && fallbackDatabasePath === path)
3453
- return fallbackDatabase;
3454
- if (fallbackDatabase)
3455
- fallbackDatabase.close();
3456
- mkdirSync8(dirname7(path), { recursive: true });
3457
- fallbackDatabase = new Database(path, { create: true });
3458
- fallbackDatabasePath = path;
3459
- initializeDatabase(fallbackDatabase);
3460
- return fallbackDatabase;
3461
- }
3462
- function resolveStorageDatabase() {
3463
- return configuredHost.database ?? defaultDatabase();
3464
- }
3465
- function resolveStorageDataDir() {
3466
- return configuredHost.dataDir ?? defaultDataDir();
3467
- }
3468
- function resolveStorageLogDir() {
3469
- return configuredHost.logDir ?? defaultLogDir();
3470
- }
3471
- function resolveStorageSessionAsksDir() {
3472
- return configuredHost.sessionAsksDir ?? defaultSessionAsksDir();
3473
- }
3474
- function resolveStorageWorkspaceDir() {
3475
- return configuredHost.workspaceDir ?? defaultWorkspaceDir();
3476
- }
3477
- function resolveStorageSharedWikiDir() {
3478
- return configuredHost.sharedWikiDir ?? join11(resolveStorageWorkspaceDir(), "wiki");
3479
- }
3480
- function registerStorageSchemaInitializer(initialize, priority = 100) {
3481
- schemaInitializers.push({ initialize, priority });
3482
- schemaInitializers.sort((a, b) => a.priority - b.priority);
3483
- }
3484
- function ensureStorageSchemas(database = resolveStorageDatabase()) {
3485
- if (initializingDatabases.has(database))
3486
- return;
3487
- let initialized = initializedSchemas.get(database);
3488
- if (!initialized) {
3489
- initialized = new Set;
3490
- initializedSchemas.set(database, initialized);
3491
- }
3492
- initializingDatabases.add(database);
3493
- try {
3494
- for (const entry of schemaInitializers) {
3495
- if (initialized.has(entry.initialize))
3496
- continue;
3497
- initialized.add(entry.initialize);
3498
- try {
3499
- entry.initialize(database);
3500
- } catch (error) {
3501
- initialized.delete(entry.initialize);
3502
- throw error;
3503
- }
3504
- }
3505
- } finally {
3506
- initializingDatabases.delete(database);
3507
- }
3508
- }
3509
- var configuredHost, fallbackDatabase = null, fallbackDatabasePath = null, schemaInitializers, initializedSchemas, initializingDatabases, SQLITE_INIT_RETRY_MS = 25, SQLITE_INIT_TIMEOUT_MS = 5000, SQLITE_INIT_SLEEP, internalStorageDatabase;
3510
- var init_storage_host = __esm(async () => {
3511
- await init_sqlite();
3512
- configuredHost = {};
3513
- schemaInitializers = [];
3514
- initializedSchemas = new WeakMap;
3515
- initializingDatabases = new WeakSet;
3516
- SQLITE_INIT_SLEEP = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
3517
- internalStorageDatabase = new Proxy({}, {
3518
- get(_target, property) {
3519
- const database = resolveStorageDatabase();
3520
- ensureStorageSchemas(database);
3521
- const value2 = Reflect.get(database, property, database);
3522
- return typeof value2 === "function" ? value2.bind(database) : value2;
3523
- },
3524
- set(_target, property, value2) {
3525
- const database = resolveStorageDatabase();
3526
- ensureStorageSchemas(database);
3527
- return Reflect.set(database, property, value2, database);
3528
- }
3529
- });
3530
- });
3531
-
3532
- // ../../packages/core/src/storage/forum-db.ts
3533
- var db;
3534
- var init_forum_db = __esm(async () => {
3535
- await init_storage_host();
3536
- db = internalStorageDatabase;
3537
- });
3538
-
3539
- // ../../packages/core/src/storage/runtime-events.ts
3540
- function rowToEvent(row) {
3541
- if (!types.has(row.event_type))
3542
- return null;
3543
- try {
3544
- return {
3545
- seq: Number(row.seq),
3546
- sourceId: row.source_id,
3547
- type: row.event_type,
3548
- topicId: row.topic_id,
3549
- payload: JSON.parse(row.payload_json),
3550
- createdAt: row.created_at
3551
- };
3552
- } catch {
3553
- return null;
3554
- }
3555
- }
3556
- function latestRuntimeEventSeq() {
3557
- const row = db.query("SELECT MAX(seq) AS seq FROM runtime_events").get();
3558
- return Number(row?.seq ?? 0);
3559
- }
3560
- function appendRuntimeEvent(sourceId, event) {
3561
- const createdAt = new Date().toISOString();
3562
- const result = db.query(`INSERT INTO runtime_events
3563
- (source_id, event_type, topic_id, payload_json, created_at)
3564
- VALUES (?, ?, ?, ?, ?)`).run(sourceId, event.type, event.topicId, JSON.stringify(event.payload ?? null), createdAt);
3565
- const seq = Number(result.lastInsertRowid);
3566
- return { seq, sourceId, ...event, createdAt };
3586
+ function appendRuntimeEvent(sourceId, event) {
3587
+ const createdAt = new Date().toISOString();
3588
+ const result = db.query(`INSERT INTO runtime_events
3589
+ (source_id, event_type, topic_id, payload_json, created_at)
3590
+ VALUES (?, ?, ?, ?, ?)`).run(sourceId, event.type, event.topicId, JSON.stringify(event.payload ?? null), createdAt);
3591
+ const seq = Number(result.lastInsertRowid);
3592
+ return { seq, sourceId, ...event, createdAt };
3567
3593
  }
3568
3594
  function listRuntimeEventsAfter(seq, limit = 500) {
3569
3595
  const rows = db.query(`SELECT seq, source_id, event_type, topic_id, payload_json, created_at
@@ -3575,7 +3601,10 @@ function listRuntimeEventsAfter(seq, limit = 500) {
3575
3601
  }
3576
3602
  var RUNTIME_EVENT_TYPES, types;
3577
3603
  var init_runtime_events = __esm(async () => {
3578
- await init_forum_db();
3604
+ await __promiseAll([
3605
+ init_forum_db(),
3606
+ init_storage_host()
3607
+ ]);
3579
3608
  RUNTIME_EVENT_TYPES = [
3580
3609
  "message",
3581
3610
  "message-updated",
@@ -3584,26 +3613,28 @@ var init_runtime_events = __esm(async () => {
3584
3613
  "topic-updated",
3585
3614
  "topic-deleted"
3586
3615
  ];
3587
- db.exec(`
3588
- CREATE TABLE IF NOT EXISTS runtime_events (
3589
- seq INTEGER PRIMARY KEY AUTOINCREMENT,
3590
- source_id TEXT NOT NULL,
3591
- event_type TEXT NOT NULL CHECK (
3592
- event_type IN (
3593
- 'message',
3594
- 'message-updated',
3595
- 'ai-status',
3596
- 'topic-created',
3597
- 'topic-updated',
3598
- 'topic-deleted'
3599
- )
3600
- ),
3601
- topic_id TEXT NOT NULL,
3602
- payload_json TEXT NOT NULL,
3603
- created_at TEXT NOT NULL
3604
- )
3605
- `);
3606
- db.exec("CREATE INDEX IF NOT EXISTS idx_runtime_events_topic_seq ON runtime_events(topic_id, seq)");
3616
+ registerStorageSchemaInitializer((database) => {
3617
+ database.exec(`
3618
+ CREATE TABLE IF NOT EXISTS runtime_events (
3619
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
3620
+ source_id TEXT NOT NULL,
3621
+ event_type TEXT NOT NULL CHECK (
3622
+ event_type IN (
3623
+ 'message',
3624
+ 'message-updated',
3625
+ 'ai-status',
3626
+ 'topic-created',
3627
+ 'topic-updated',
3628
+ 'topic-deleted'
3629
+ )
3630
+ ),
3631
+ topic_id TEXT NOT NULL,
3632
+ payload_json TEXT NOT NULL,
3633
+ created_at TEXT NOT NULL
3634
+ )
3635
+ `);
3636
+ database.exec("CREATE INDEX IF NOT EXISTS idx_runtime_events_topic_seq ON runtime_events(topic_id, seq)");
3637
+ });
3607
3638
  types = new Set(RUNTIME_EVENT_TYPES);
3608
3639
  });
3609
3640
 
@@ -3763,13 +3794,17 @@ class SqliteRuntimeBus {
3763
3794
  };
3764
3795
  }
3765
3796
  }
3766
- var current, WsHub;
3797
+ function runtimeBus() {
3798
+ if (!current)
3799
+ current = new SqliteRuntimeBus;
3800
+ return current;
3801
+ }
3802
+ var current = null, WsHub;
3767
3803
  var init_bus = __esm(async () => {
3768
3804
  await init_runtime_events();
3769
- current = new SqliteRuntimeBus;
3770
3805
  WsHub = {
3771
3806
  get() {
3772
- return current;
3807
+ return runtimeBus();
3773
3808
  }
3774
3809
  };
3775
3810
  });
@@ -5068,10 +5103,11 @@ var init_runtime_turn_requests = __esm(async () => {
5068
5103
  await __promiseAll([
5069
5104
  init_forum_db(),
5070
5105
  init_runtime_leases(),
5071
- init_runtime_topic_state()
5106
+ init_runtime_topic_state(),
5107
+ init_storage_host()
5072
5108
  ]);
5073
5109
  REQUEST_CLAIM_STALE_MS = TURN_LEASE_STALE_MS;
5074
- ensureRuntimeUserTurnRequestsSchema(db);
5110
+ registerStorageSchemaInitializer((database) => ensureRuntimeUserTurnRequestsSchema(database));
5075
5111
  });
5076
5112
 
5077
5113
  // ../../packages/core/src/agents/registry.ts
@@ -5104,7 +5140,7 @@ var init_registry = __esm(async () => {
5104
5140
  });
5105
5141
 
5106
5142
  // ../../packages/core/src/agents/fork.ts
5107
- import { existsSync as existsSync10, unlinkSync as unlinkSync8 } from "fs";
5143
+ import { existsSync as existsSync11, unlinkSync as unlinkSync9 } from "fs";
5108
5144
  function createAgentForkHelpers(host) {
5109
5145
  return {
5110
5146
  async forkAgentSession(options) {
@@ -5141,8 +5177,8 @@ var init_fork = __esm(async () => {
5141
5177
  await init_registry();
5142
5178
  defaultForkHelpers = createAgentForkHelpers({
5143
5179
  forkSession: (agent, options) => getRegistryOperations(agent).forkSession(options),
5144
- exists: existsSync10,
5145
- unlink: unlinkSync8,
5180
+ exists: existsSync11,
5181
+ unlink: unlinkSync9,
5146
5182
  warn: (details, message) => logger.warn(details, message)
5147
5183
  });
5148
5184
  });
@@ -5541,7 +5577,7 @@ var init_runtime_process_leases = __esm(async () => {
5541
5577
 
5542
5578
  // ../../packages/core/src/platform/playwright/browser-processes.ts
5543
5579
  import { execFileSync as execFileSync3 } from "child_process";
5544
- import { readdirSync as readdirSync4, unlinkSync as unlinkSync9 } from "fs";
5580
+ import { readdirSync as readdirSync4, unlinkSync as unlinkSync10 } from "fs";
5545
5581
  import { createServer } from "net";
5546
5582
  import { resolve as resolve8, sep } from "path";
5547
5583
  function isPortInUse(port) {
@@ -5670,7 +5706,7 @@ function cleanSingletonFiles(userDataDir) {
5670
5706
  for (const f of files) {
5671
5707
  if (f.startsWith("Singleton")) {
5672
5708
  try {
5673
- unlinkSync9(resolve8(userDataDir, f));
5709
+ unlinkSync10(resolve8(userDataDir, f));
5674
5710
  logger.info({ file: f, userDataDir }, "Removed stale Singleton file");
5675
5711
  } catch (e) {
5676
5712
  logger.warn({ err: e, file: f }, "Failed to remove stale Chrome Singleton file");
@@ -5811,7 +5847,7 @@ var init_vault_public = __esm(async () => {
5811
5847
 
5812
5848
  // ../../packages/core/src/platform/playwright/vault-broker.ts
5813
5849
  import { randomBytes as randomBytes5, timingSafeEqual as timingSafeEqual2 } from "crypto";
5814
- import { chmodSync as chmodSync4 } from "fs";
5850
+ import { chmodSync as chmodSync5 } from "fs";
5815
5851
  import { createServer as createServer2 } from "net";
5816
5852
  import { tmpdir as tmpdir2 } from "os";
5817
5853
  import { join as join12 } from "path";
@@ -6007,7 +6043,7 @@ async function createBrowserVaultBroker(userId) {
6007
6043
  server.once("error", reject);
6008
6044
  server.listen(socketPath, resolveListen);
6009
6045
  });
6010
- chmodSync4(socketPath, 384);
6046
+ chmodSync5(socketPath, 384);
6011
6047
  let closed = false;
6012
6048
  return {
6013
6049
  socketPath,
@@ -6099,15 +6135,20 @@ function assignTopicBrowserProfile(opts) {
6099
6135
  }
6100
6136
  var DEFAULT_BROWSER_PROFILE = "default";
6101
6137
  var init_browser_profiles = __esm(async () => {
6102
- await init_forum_db();
6103
- db.exec(`
6104
- CREATE TABLE IF NOT EXISTS browser_profiles (
6105
- owner_id TEXT NOT NULL,
6106
- name TEXT NOT NULL,
6107
- created_at TEXT NOT NULL,
6108
- PRIMARY KEY (owner_id, name)
6109
- )
6110
- `);
6138
+ await __promiseAll([
6139
+ init_forum_db(),
6140
+ init_storage_host()
6141
+ ]);
6142
+ registerStorageSchemaInitializer((database) => {
6143
+ database.exec(`
6144
+ CREATE TABLE IF NOT EXISTS browser_profiles (
6145
+ owner_id TEXT NOT NULL,
6146
+ name TEXT NOT NULL,
6147
+ created_at TEXT NOT NULL,
6148
+ PRIMARY KEY (owner_id, name)
6149
+ )
6150
+ `);
6151
+ });
6111
6152
  });
6112
6153
 
6113
6154
  // ../../packages/core/src/platform/playwright/manager.ts
@@ -6115,11 +6156,11 @@ import { execFileSync as execFileSync4, spawn as spawn6 } from "child_process";
6115
6156
  import { createHash as createHash3, randomBytes as randomBytes6, timingSafeEqual as timingSafeEqual3 } from "crypto";
6116
6157
  import {
6117
6158
  cpSync,
6118
- existsSync as existsSync11,
6159
+ existsSync as existsSync12,
6119
6160
  mkdirSync as mkdirSync9,
6120
- readFileSync as readFileSync9,
6161
+ readFileSync as readFileSync10,
6121
6162
  rmSync as rmSync2,
6122
- unlinkSync as unlinkSync10,
6163
+ unlinkSync as unlinkSync11,
6123
6164
  writeFileSync as writeFileSync7
6124
6165
  } from "fs";
6125
6166
  import { dirname as dirname8, join as join13, resolve as resolve10 } from "path";
@@ -6129,7 +6170,7 @@ function removeDefaultProfileDataDir(userDataDir) {
6129
6170
  if (dirname8(target) !== root) {
6130
6171
  throw new Error(`Refusing to delete browser profile outside managed root: ${target}`);
6131
6172
  }
6132
- const existed = existsSync11(target);
6173
+ const existed = existsSync12(target);
6133
6174
  rmSync2(target, { recursive: true, force: true });
6134
6175
  return existed;
6135
6176
  }
@@ -6219,7 +6260,7 @@ function writePortFile(instanceKey, port) {
6219
6260
  }
6220
6261
  function deletePortFile(instanceKey) {
6221
6262
  try {
6222
- unlinkSync10(join13(managerHost.portsDir, portFileName(instanceKey)));
6263
+ unlinkSync11(join13(managerHost.portsDir, portFileName(instanceKey)));
6223
6264
  } catch (e) {
6224
6265
  if (e.code === "ENOENT")
6225
6266
  return;
@@ -6704,11 +6745,11 @@ async function cloneProfileForChild(opts) {
6704
6745
  killInstance(dstKey);
6705
6746
  await delay(500);
6706
6747
  }
6707
- if (!existsSync11(srcDir)) {
6748
+ if (!existsSync12(srcDir)) {
6708
6749
  return { copied: false, srcDir, dstDir, reason: "src-missing" };
6709
6750
  }
6710
6751
  try {
6711
- if (existsSync11(dstDir)) {
6752
+ if (existsSync12(dstDir)) {
6712
6753
  rmSync2(dstDir, { recursive: true, force: true });
6713
6754
  }
6714
6755
  mkdirSync9(dirname8(dstDir), { recursive: true });
@@ -6729,7 +6770,7 @@ async function cloneProfileForChild(opts) {
6729
6770
  cleanSingletonFiles(dstDir);
6730
6771
  for (const f of ["DevToolsActivePort", "LOCK"]) {
6731
6772
  try {
6732
- unlinkSync10(resolve10(dstDir, f));
6773
+ unlinkSync11(resolve10(dstDir, f));
6733
6774
  } catch {}
6734
6775
  }
6735
6776
  logger.info({ srcKey, dstKey, srcDir, dstDir }, "Cloned Playwright profile for child topic");
@@ -7277,11 +7318,11 @@ var init_api_topic_config = __esm(async () => {
7277
7318
 
7278
7319
  // ../../packages/core/src/storage/conversations.ts
7279
7320
  import {
7280
- existsSync as existsSync12,
7321
+ existsSync as existsSync13,
7281
7322
  mkdirSync as mkdirSync10,
7282
- readFileSync as readFileSync10,
7323
+ readFileSync as readFileSync11,
7283
7324
  renameSync as renameSync5,
7284
- unlinkSync as unlinkSync11,
7325
+ unlinkSync as unlinkSync12,
7285
7326
  writeFileSync as writeFileSync8
7286
7327
  } from "fs";
7287
7328
  import { dirname as dirname9, join as join14 } from "path";
@@ -7300,7 +7341,7 @@ function getActiveConversationPath(userId, topicName) {
7300
7341
  return rawPath.endsWith(".jsonl") ? `${rawPath.slice(0, -".jsonl".length)}.active.jsonl` : `${rawPath}.active`;
7301
7342
  }
7302
7343
  function hasActiveConversation(userId, topicName) {
7303
- return existsSync12(getActiveConversationPath(userId, topicName));
7344
+ return existsSync13(getActiveConversationPath(userId, topicName));
7304
7345
  }
7305
7346
  function appendConversationEvent(userId, topicName, agent, event) {
7306
7347
  try {
@@ -7322,7 +7363,7 @@ function appendConversationEventStrict(userId, topicName, agent, event) {
7322
7363
  mkdirSync10(dirname9(path), { recursive: true });
7323
7364
  appendJsonlLine(path, line);
7324
7365
  const activePath = getActiveConversationPath(userId, topicName);
7325
- if (existsSync12(activePath)) {
7366
+ if (existsSync13(activePath)) {
7326
7367
  try {
7327
7368
  appendJsonlLine(activePath, line);
7328
7369
  } catch (cause) {
@@ -7332,11 +7373,11 @@ function appendConversationEventStrict(userId, topicName, agent, event) {
7332
7373
  }
7333
7374
  function readConversationPath(path) {
7334
7375
  const out = [];
7335
- if (!existsSync12(path))
7376
+ if (!existsSync13(path))
7336
7377
  return out;
7337
7378
  let raw;
7338
7379
  try {
7339
- raw = readFileSync10(path, "utf8");
7380
+ raw = readFileSync11(path, "utf8");
7340
7381
  } catch (err) {
7341
7382
  logger.warn({ err, path }, "readConversation: read failed");
7342
7383
  return out;
@@ -7355,7 +7396,7 @@ function readConversationPath(path) {
7355
7396
  }
7356
7397
  function readConversation(userId, topicName) {
7357
7398
  const activePath = getActiveConversationPath(userId, topicName);
7358
- return readConversationPath(existsSync12(activePath) ? activePath : getConversationPath(userId, topicName));
7399
+ return readConversationPath(existsSync13(activePath) ? activePath : getConversationPath(userId, topicName));
7359
7400
  }
7360
7401
  function readRawConversation(userId, topicName) {
7361
7402
  return readConversationPath(getConversationPath(userId, topicName));
@@ -7376,7 +7417,7 @@ function replaceConversationPathStrict(path, entries) {
7376
7417
  renameSync5(tempPath, path);
7377
7418
  } catch (error) {
7378
7419
  try {
7379
- unlinkSync11(tempPath);
7420
+ unlinkSync12(tempPath);
7380
7421
  } catch {}
7381
7422
  throw error;
7382
7423
  }
@@ -7513,7 +7554,7 @@ RULES:
7513
7554
  - Keep the entire summary under 1500 words.`, CHARS_PER_TEXT_TOKEN = 3.5, CHARS_PER_CJK_TOKEN = 0.9;
7514
7555
 
7515
7556
  // ../../packages/core/src/storage/tasks.ts
7516
- import { existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync11, renameSync as renameSync6, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
7557
+ import { existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync12, renameSync as renameSync6, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
7517
7558
  import { dirname as dirname10, join as join15 } from "path";
7518
7559
  function safeTaskScopeKey(scopeKey) {
7519
7560
  const safe = sanitizeFileName(scopeKey);
@@ -7530,10 +7571,10 @@ function getTaskFilePath(userId, scopeKey) {
7530
7571
  }
7531
7572
  function readTasks(userId, scopeKey) {
7532
7573
  const path = getTaskFilePath(userId, scopeKey);
7533
- if (!existsSync13(path))
7574
+ if (!existsSync14(path))
7534
7575
  return [];
7535
7576
  try {
7536
- const parsed = JSON.parse(readFileSync11(path, "utf-8"));
7577
+ const parsed = JSON.parse(readFileSync12(path, "utf-8"));
7537
7578
  return Array.isArray(parsed?.tasks) ? parsed.tasks : [];
7538
7579
  } catch (e) {
7539
7580
  logger.warn({ err: e, path }, "tasks: failed to read task store");
@@ -7788,7 +7829,7 @@ __export(exports_claude_provider, {
7788
7829
  buildClaudeDisallowedTools: () => buildClaudeDisallowedTools
7789
7830
  });
7790
7831
  import { spawn as spawn7 } from "child_process";
7791
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
7832
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
7792
7833
  import { query } from "@anthropic-ai/claude-agent-sdk";
7793
7834
  function claudeBuiltInTools(opts) {
7794
7835
  return opts.toolPolicy ? [] : undefined;
@@ -7841,7 +7882,7 @@ function buildClaudePrompt(opts) {
7841
7882
  source: {
7842
7883
  type: "base64",
7843
7884
  media_type: attachment.mimeType,
7844
- data: readFileSync12(attachment.path).toString("base64")
7885
+ data: readFileSync13(attachment.path).toString("base64")
7845
7886
  }
7846
7887
  });
7847
7888
  } catch (err) {
@@ -7969,7 +8010,7 @@ function isAbortError(err) {
7969
8010
  }
7970
8011
  async function* claudeProvider(opts) {
7971
8012
  const claudeExecutable = hostedClaudeCodeExecutablePath();
7972
- if (claudeExecutable && !existsSync14(claudeExecutable)) {
8013
+ if (claudeExecutable && !existsSync15(claudeExecutable)) {
7973
8014
  yield {
7974
8015
  type: "error",
7975
8016
  content: `Claude CLI not found at ${claudeExecutable}. Install Claude Code or configure the executable path.`
@@ -8489,7 +8530,7 @@ var init_codex_tree_kill = __esm(() => {
8489
8530
  });
8490
8531
 
8491
8532
  // ../../packages/core/src/agents/tool-format.ts
8492
- import { readFileSync as readFileSync13, statSync as statSync5 } from "fs";
8533
+ import { readFileSync as readFileSync14, statSync as statSync5 } from "fs";
8493
8534
  import { isAbsolute as isAbsolute2, resolve as resolve11 } from "path";
8494
8535
  import { diffLines as computeLineDiff } from "diff";
8495
8536
  function summarizeDisplayText(value2) {
@@ -8818,7 +8859,7 @@ function sourceStartLine(input, before, after, cwd) {
8818
8859
  try {
8819
8860
  if (statSync5(path).size > TOOL_DIFF_SOURCE_MAX_BYTES)
8820
8861
  return 1;
8821
- const source = readFileSync13(path, "utf8").replace(/\r\n?/g, `
8862
+ const source = readFileSync14(path, "utf8").replace(/\r\n?/g, `
8822
8863
  `);
8823
8864
  for (const candidate of [before, after].filter(Boolean)) {
8824
8865
  const index = source.indexOf(candidate);
@@ -9012,7 +9053,7 @@ __export(exports_codex_provider, {
9012
9053
  codexProvider: () => codexProvider
9013
9054
  });
9014
9055
  import { execFileSync as execFileSync6 } from "child_process";
9015
- import { existsSync as existsSync15, readFileSync as readFileSync14, realpathSync as realpathSync4, statSync as statSync6 } from "fs";
9056
+ import { existsSync as existsSync16, readFileSync as readFileSync15, realpathSync as realpathSync4, statSync as statSync6 } from "fs";
9016
9057
  import { homedir as homedir6 } from "os";
9017
9058
  import { dirname as dirname11, isAbsolute as isAbsolute3, join as join16, relative as relative2, resolve as resolve12 } from "path";
9018
9059
  import { Codex } from "@openai/codex-sdk";
@@ -9043,11 +9084,11 @@ function codexMcpServerName(name) {
9043
9084
  }
9044
9085
  function globalCodexMcpServerNames(authFilePath) {
9045
9086
  const configPath = join16(dirname11(authFilePath), "config.toml");
9046
- if (!existsSync15(configPath))
9087
+ if (!existsSync16(configPath))
9047
9088
  return [];
9048
9089
  try {
9049
9090
  const names = new Set;
9050
- for (const line of readFileSync14(configPath, "utf8").split(`
9091
+ for (const line of readFileSync15(configPath, "utf8").split(`
9051
9092
  `)) {
9052
9093
  const match = line.match(/^\s*\[\s*mcp_servers\.((?:"(?:\\.|[^"])*")|(?:'[^']*')|[^.\]\s]+)(?:[.\]])/);
9053
9094
  const raw = match?.[1];
@@ -9144,13 +9185,13 @@ function canonicalPath(path) {
9144
9185
  }
9145
9186
  }
9146
9187
  function textFile(path, maxBytes = CODEX_DIFF_FILE_LIMIT) {
9147
- if (!existsSync15(path))
9188
+ if (!existsSync16(path))
9148
9189
  return null;
9149
9190
  try {
9150
9191
  const byteLimit = Math.min(CODEX_DIFF_FILE_LIMIT, Math.max(0, maxBytes));
9151
9192
  if (statSync6(path).size > byteLimit)
9152
9193
  return;
9153
- const content = readFileSync14(path);
9194
+ const content = readFileSync15(path);
9154
9195
  if (content.byteLength > byteLimit || content.includes(0))
9155
9196
  return;
9156
9197
  return content.toString("utf8");
@@ -9356,7 +9397,7 @@ async function* codexProvider(opts) {
9356
9397
  const filePreviews = new CodexFilePreviewTracker(opts.cwd);
9357
9398
  const consumedPatchCallIds = new Set(opts.sessionId ? readCodexPatchCallIds(opts.sessionId) : []);
9358
9399
  const codexAuthPath = hostedCodexAuthFilePath();
9359
- if (!existsSync15(codexAuthPath)) {
9400
+ if (!existsSync16(codexAuthPath)) {
9360
9401
  yield {
9361
9402
  type: "error",
9362
9403
  content: `Codex auth file not found at ${codexAuthPath}. Run \`codex login\` to authenticate.`
@@ -9395,7 +9436,7 @@ async function* codexProvider(opts) {
9395
9436
  model: opts.model ?? "(sdk default)",
9396
9437
  effort: opts.effort ?? "(off)",
9397
9438
  cwd: opts.cwd,
9398
- cwdExists: existsSync15(opts.cwd),
9439
+ cwdExists: existsSync16(opts.cwd),
9399
9440
  resume: Boolean(opts.sessionId),
9400
9441
  mcpServerCount: Object.keys(codexMcpServers).length
9401
9442
  }, "codexProvider: starting turn");
@@ -9847,7 +9888,7 @@ var init_maestro_provider = __esm(async () => {
9847
9888
  });
9848
9889
 
9849
9890
  // ../../packages/core/src/agents/index.ts
9850
- import { existsSync as existsSync16 } from "fs";
9891
+ import { existsSync as existsSync17 } from "fs";
9851
9892
  import { homedir as homedir7 } from "os";
9852
9893
  import { join as join17 } from "path";
9853
9894
  async function* dispatchAgent(opts) {
@@ -9884,7 +9925,7 @@ async function resolveSessionFileMissing(agent, sessionId, cwd) {
9884
9925
  case "claude": {
9885
9926
  const encodedCwd = encodeClaudeCwd(cwd);
9886
9927
  const path = join17(homedir7(), ".claude", "projects", encodedCwd, `${sessionId}.jsonl`);
9887
- return !existsSync16(path);
9928
+ return !existsSync17(path);
9888
9929
  }
9889
9930
  case "codex": {
9890
9931
  const sessionsDir = join17(hostedCodexHomePath(), "sessions");
@@ -9896,7 +9937,7 @@ async function resolveSessionFileMissing(agent, sessionId, cwd) {
9896
9937
  }
9897
9938
  case "maestro": {
9898
9939
  const { hasActiveMaestroSession, maestroSessionPath: maestroSessionPath2 } = await import("maestro-agent-sdk");
9899
- return !existsSync16(maestroSessionPath2(sessionId)) && !hasActiveMaestroSession(sessionId);
9940
+ return !existsSync17(maestroSessionPath2(sessionId)) && !hasActiveMaestroSession(sessionId);
9900
9941
  }
9901
9942
  default: {
9902
9943
  const _exhaustive = agent;
@@ -9969,10 +10010,10 @@ var init_agents = __esm(async () => {
9969
10010
  });
9970
10011
 
9971
10012
  // ../../packages/core/src/prompts/builders.ts
9972
- import { readFileSync as readFileSync15 } from "fs";
10013
+ import { readFileSync as readFileSync16 } from "fs";
9973
10014
  import { resolve as resolve14 } from "path";
9974
10015
  function loadPrompt(filename, dir = SESSIONS_DIR) {
9975
- const raw = readFileSync15(resolve14(dir, filename), "utf-8");
10016
+ const raw = readFileSync16(resolve14(dir, filename), "utf-8");
9976
10017
  return raw.replace(/\{\{RESOURCES_DIR\}\}/g, RESOURCES_DIR);
9977
10018
  }
9978
10019
  function replaceVars(template, vars) {
@@ -10021,7 +10062,7 @@ function visualDesignGuide() {
10021
10062
  return _visualDesignGuide;
10022
10063
  }
10023
10064
  function loadAgentPrompt(filename) {
10024
- const raw = readFileSync15(resolve14(AGENTS_PROMPTS_DIR, filename), "utf-8");
10065
+ const raw = readFileSync16(resolve14(AGENTS_PROMPTS_DIR, filename), "utf-8");
10025
10066
  const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
10026
10067
  if (!match)
10027
10068
  throw new Error(`Agent prompt ${filename} is missing frontmatter`);
@@ -10420,7 +10461,7 @@ var init_wiki = __esm(async () => {
10420
10461
 
10421
10462
  // ../../packages/core/src/agents/archiver.ts
10422
10463
  import { randomUUID as randomUUID9 } from "crypto";
10423
- import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync16, statSync as statSync7 } from "fs";
10464
+ import { existsSync as existsSync18, readdirSync as readdirSync5, readFileSync as readFileSync17, statSync as statSync7 } from "fs";
10424
10465
  import { join as join19 } from "path";
10425
10466
  function resolveMemoryLanguage() {
10426
10467
  const override = process.env.NEGOTIUM_MEMORY_LANG?.trim();
@@ -10440,9 +10481,9 @@ function formatArchiverBytes(bytes) {
10440
10481
  function formatArchiverTool(name, input) {
10441
10482
  const params = Object.entries(input).map(([key, value2]) => {
10442
10483
  const rendered = typeof value2 === "string" ? value2 : JSON.stringify(value2);
10443
- return `${key}: ${sessionText(rendered ?? "")}`;
10484
+ return `${key}: ${summarizeDisplayText(sessionText(rendered ?? ""))}`;
10444
10485
  }).join(", ");
10445
- return `${name}${params ? `(${params})` : ""}`;
10486
+ return summarizeDisplayText(`${name}${params ? `(${params})` : ""}`);
10446
10487
  }
10447
10488
  function createArchiverRuntime(host) {
10448
10489
  const activeSessions = new Map;
@@ -10749,6 +10790,7 @@ function runArchiverTurn(params) {
10749
10790
  }
10750
10791
  var MAX_BRIEF_ENTRIES = 8, defaultArchiverRuntime;
10751
10792
  var init_archiver = __esm(async () => {
10793
+ init_tool_format();
10752
10794
  init_config();
10753
10795
  init_logger();
10754
10796
  init_builders();
@@ -10765,9 +10807,9 @@ var init_archiver = __esm(async () => {
10765
10807
  defaultArchiverRuntime = createArchiverRuntime({
10766
10808
  storage: {
10767
10809
  getWikiDir: getSharedWikiDir,
10768
- fileExists: existsSync17,
10810
+ fileExists: existsSync18,
10769
10811
  listDirectory: readdirSync5,
10770
- readTextFile: (path) => readFileSync16(path, "utf-8"),
10812
+ readTextFile: (path) => readFileSync17(path, "utf-8"),
10771
10813
  fileSize: (path) => statSync7(path).size,
10772
10814
  fileModifiedAt: (path) => statSync7(path).mtimeMs,
10773
10815
  getGeneralTopicId: (userId) => ensurePersonalGeneral(userId).id,
@@ -11304,7 +11346,7 @@ var init_idle_archiver = __esm(async () => {
11304
11346
  });
11305
11347
 
11306
11348
  // ../../packages/core/src/agents/topic-cleanup.ts
11307
- import { mkdirSync as mkdirSync13, renameSync as renameSync7, unlinkSync as unlinkSync12, writeFileSync as writeFileSync11 } from "fs";
11349
+ import { mkdirSync as mkdirSync13, renameSync as renameSync7, unlinkSync as unlinkSync13, writeFileSync as writeFileSync11 } from "fs";
11308
11350
  import { dirname as dirname13 } from "path";
11309
11351
  function collectSessionIdsByAgent(entries, extraSessions = []) {
11310
11352
  const out = new Map;
@@ -11378,7 +11420,7 @@ function createTopicLogMaintenance(host) {
11378
11420
  renameSync7(tempPath, path);
11379
11421
  } catch (e) {
11380
11422
  try {
11381
- unlinkSync12(tempPath);
11423
+ unlinkSync13(tempPath);
11382
11424
  } catch {}
11383
11425
  runtimeHost.warn({ err: e, path }, "rotateTopicLogs: conversation replacement failed");
11384
11426
  return {
@@ -11408,7 +11450,7 @@ function createTopicLogMaintenance(host) {
11408
11450
  runtimeHost.rawConversationPath(userId, topicName)
11409
11451
  ]) {
11410
11452
  try {
11411
- unlinkSync12(path);
11453
+ unlinkSync13(path);
11412
11454
  } catch (e) {
11413
11455
  if (e?.code !== "ENOENT") {
11414
11456
  runtimeHost.warn({ err: e, path }, "purgeTopicLogs: conversation log unlink failed");
@@ -11510,6 +11552,7 @@ function text(value2, maxLen) {
11510
11552
  }
11511
11553
  var providers, transientSessions;
11512
11554
  var init_background_sessions = __esm(async () => {
11555
+ init_tool_format();
11513
11556
  init_background_session_policy();
11514
11557
  await __promiseAll([
11515
11558
  init_archiver(),
@@ -11960,7 +12003,7 @@ __export(exports_derive, {
11960
12003
  TopicDeriveBusyError: () => TopicDeriveBusyError
11961
12004
  });
11962
12005
  import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
11963
- import { mkdirSync as mkdirSync14, rmSync as rmSync5, unlinkSync as unlinkSync13 } from "fs";
12006
+ import { mkdirSync as mkdirSync14, rmSync as rmSync5, unlinkSync as unlinkSync14 } from "fs";
11964
12007
  function getTopics() {
11965
12008
  return listTopics().filter((topic) => !isLegacySharedGeneral(topic.id));
11966
12009
  }
@@ -12255,7 +12298,7 @@ async function createDerivedTopicImpl(topic, sourceTopicId, userId, copyHistory,
12255
12298
  getConversationPath(userId, derived.title)
12256
12299
  ]) {
12257
12300
  try {
12258
- unlinkSync13(path);
12301
+ unlinkSync14(path);
12259
12302
  } catch {}
12260
12303
  }
12261
12304
  }
@@ -12327,7 +12370,7 @@ var init_session_inbox_path = __esm(() => {
12327
12370
  });
12328
12371
 
12329
12372
  // ../../packages/core/src/query/session-inbox-cleanup.ts
12330
- import { unlinkSync as unlinkSync14 } from "fs";
12373
+ import { unlinkSync as unlinkSync15 } from "fs";
12331
12374
  import { basename as basename4, join as join23 } from "path";
12332
12375
  function cleanupSessionInboxFiles(userId, topicId, legacyTopicTitle) {
12333
12376
  const live = sessionInboxPath(userId, topicId);
@@ -12342,7 +12385,7 @@ function cleanupSessionInboxFiles(userId, topicId, legacyTopicTitle) {
12342
12385
  let deleted = 0;
12343
12386
  for (const path of candidates) {
12344
12387
  try {
12345
- unlinkSync14(path);
12388
+ unlinkSync15(path);
12346
12389
  deleted++;
12347
12390
  } catch (err) {
12348
12391
  if (err?.code !== "ENOENT") {
@@ -12359,7 +12402,7 @@ var init_session_inbox_cleanup = __esm(() => {
12359
12402
  });
12360
12403
 
12361
12404
  // ../../packages/core/src/query/state.ts
12362
- import { mkdirSync as mkdirSync15, renameSync as renameSync8, unlinkSync as unlinkSync15, writeFileSync as writeFileSync13 } from "fs";
12405
+ import { mkdirSync as mkdirSync15, renameSync as renameSync8, unlinkSync as unlinkSync16, writeFileSync as writeFileSync13 } from "fs";
12363
12406
  import { basename as basename5, join as join24 } from "path";
12364
12407
  function createQueryStateStore(options) {
12365
12408
  const sanitize = options.sanitizeTopicId ?? sanitizeId;
@@ -12390,7 +12433,7 @@ function createQueryStateStore(options) {
12390
12433
  ].filter((path) => Boolean(path));
12391
12434
  for (const path of new Set(paths)) {
12392
12435
  try {
12393
- unlinkSync15(path);
12436
+ unlinkSync16(path);
12394
12437
  } catch (error) {
12395
12438
  if (error?.code !== "ENOENT") {
12396
12439
  options.logger?.warn({ err: error, userId, topicId, path }, "Failed to clear query state file");
@@ -12524,9 +12567,9 @@ import {
12524
12567
  mkdirSync as mkdirSync16,
12525
12568
  openSync as openSync3,
12526
12569
  readdirSync as readdirSync6,
12527
- readFileSync as readFileSync17,
12570
+ readFileSync as readFileSync18,
12528
12571
  statSync as statSync8,
12529
- unlinkSync as unlinkSync16,
12572
+ unlinkSync as unlinkSync17,
12530
12573
  writeFileSync as writeFileSync14
12531
12574
  } from "fs";
12532
12575
  import { dirname as dirname14, join as join25 } from "path";
@@ -12573,7 +12616,7 @@ function parsePendingAskFilename(fileName) {
12573
12616
  }
12574
12617
  function readPendingAskFile(path, fallback) {
12575
12618
  try {
12576
- const raw = readFileSync17(path, "utf8").trim();
12619
+ const raw = readFileSync18(path, "utf8").trim();
12577
12620
  if (!raw)
12578
12621
  return null;
12579
12622
  if (!raw.startsWith("{")) {
@@ -12662,7 +12705,7 @@ function readPendingAsk(key) {
12662
12705
  const next = migrated ? legacy : readPendingAskFile(canonicalPath2, key);
12663
12706
  if (next && next.from === key.from && next.to === key.to) {
12664
12707
  try {
12665
- unlinkSync16(compatibilityPath);
12708
+ unlinkSync17(compatibilityPath);
12666
12709
  } catch {}
12667
12710
  return { path: canonicalPath2, record: next };
12668
12711
  }
@@ -12677,7 +12720,7 @@ function createPendingAsk(args) {
12677
12720
  }
12678
12721
  if (migrated) {
12679
12722
  try {
12680
- unlinkSync16(migrated.path);
12723
+ unlinkSync17(migrated.path);
12681
12724
  } catch {
12682
12725
  return { ok: false, existing: migrated.record, stale: true };
12683
12726
  }
@@ -12709,7 +12752,7 @@ function createPendingAsk(args) {
12709
12752
  if (!isStale(existing, path))
12710
12753
  return { ok: false, existing, stale: false };
12711
12754
  try {
12712
- unlinkSync16(path);
12755
+ unlinkSync17(path);
12713
12756
  } catch {
12714
12757
  return { ok: false, existing, stale: true };
12715
12758
  }
@@ -12735,7 +12778,7 @@ function markPendingAskState(args) {
12735
12778
  writePendingAsk(next);
12736
12779
  if (pending.path !== pendingAskPath(next)) {
12737
12780
  try {
12738
- unlinkSync16(pending.path);
12781
+ unlinkSync17(pending.path);
12739
12782
  } catch {}
12740
12783
  }
12741
12784
  return next;
@@ -12747,7 +12790,7 @@ function clearPendingAsk(args) {
12747
12790
  if (args.requestId && record && record.requestId !== args.requestId)
12748
12791
  return false;
12749
12792
  try {
12750
- unlinkSync16(path);
12793
+ unlinkSync17(path);
12751
12794
  return true;
12752
12795
  } catch {
12753
12796
  return false;
@@ -12802,7 +12845,7 @@ function listPendingAsksForCaller(args) {
12802
12845
  continue;
12803
12846
  if (isStale(record, path)) {
12804
12847
  try {
12805
- unlinkSync16(path);
12848
+ unlinkSync17(path);
12806
12849
  } catch {}
12807
12850
  continue;
12808
12851
  }
@@ -12811,12 +12854,12 @@ function listPendingAsksForCaller(args) {
12811
12854
  continue;
12812
12855
  if (path !== canonical.path) {
12813
12856
  try {
12814
- unlinkSync16(path);
12857
+ unlinkSync17(path);
12815
12858
  } catch {}
12816
12859
  }
12817
12860
  if (isStale(canonical.record, canonical.path)) {
12818
12861
  try {
12819
- unlinkSync16(canonical.path);
12862
+ unlinkSync17(canonical.path);
12820
12863
  } catch {}
12821
12864
  continue;
12822
12865
  }
@@ -12846,7 +12889,7 @@ function deletePendingAsksForTopic(args) {
12846
12889
  if (from !== args.topicName && to !== args.topicName)
12847
12890
  continue;
12848
12891
  try {
12849
- unlinkSync16(path);
12892
+ unlinkSync17(path);
12850
12893
  deleted++;
12851
12894
  } catch {}
12852
12895
  }
@@ -12947,19 +12990,22 @@ var pendingAsks, MAX_ASK_AGE_MS;
12947
12990
  var init_ask_callbacks = __esm(async () => {
12948
12991
  await __promiseAll([
12949
12992
  init_forum_db(),
12950
- init_session_asks()
12993
+ init_session_asks(),
12994
+ init_storage_host()
12951
12995
  ]);
12952
- db.exec(`
12953
- CREATE TABLE IF NOT EXISTS remote_ask_callbacks (
12954
- target_query_id TEXT PRIMARY KEY,
12955
- request_id TEXT NOT NULL,
12956
- node_name TEXT NOT NULL,
12957
- node_cell_id TEXT NOT NULL,
12958
- topic_id TEXT NOT NULL,
12959
- user_id TEXT NOT NULL,
12960
- created_at INTEGER NOT NULL
12961
- )
12962
- `);
12996
+ registerStorageSchemaInitializer((database) => {
12997
+ database.exec(`
12998
+ CREATE TABLE IF NOT EXISTS remote_ask_callbacks (
12999
+ target_query_id TEXT PRIMARY KEY,
13000
+ request_id TEXT NOT NULL,
13001
+ node_name TEXT NOT NULL,
13002
+ node_cell_id TEXT NOT NULL,
13003
+ topic_id TEXT NOT NULL,
13004
+ user_id TEXT NOT NULL,
13005
+ created_at INTEGER NOT NULL
13006
+ )
13007
+ `);
13008
+ });
12963
13009
  pendingAsks = new Map;
12964
13010
  MAX_ASK_AGE_MS = PENDING_ASK_TTL_MS;
12965
13011
  });
@@ -13172,30 +13218,35 @@ paint();document.body.appendChild(b)}
13172
13218
  if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",init);else init()})();
13173
13219
  </script>`;
13174
13220
  var init_visual_store = __esm(async () => {
13175
- await init_forum_db();
13176
- db.exec(`
13177
- CREATE TABLE IF NOT EXISTS api_topic_visuals (
13178
- id INTEGER PRIMARY KEY AUTOINCREMENT,
13179
- topic_id TEXT NOT NULL,
13180
- html TEXT NOT NULL,
13181
- title TEXT,
13182
- created_at INTEGER NOT NULL,
13183
- kind TEXT NOT NULL DEFAULT 'html',
13184
- source TEXT,
13185
- file_id TEXT,
13186
- mime_type TEXT,
13187
- media_token TEXT
13188
- );
13189
- CREATE INDEX IF NOT EXISTS idx_api_topic_visuals_topic
13190
- ON api_topic_visuals(topic_id, created_at DESC);
13191
-
13192
- CREATE TABLE IF NOT EXISTS api_topic_visual_views (
13193
- topic_id TEXT NOT NULL,
13194
- user_id TEXT NOT NULL,
13195
- visual_id INTEGER NOT NULL,
13196
- PRIMARY KEY (topic_id, user_id)
13197
- );
13198
- `);
13221
+ await __promiseAll([
13222
+ init_forum_db(),
13223
+ init_storage_host()
13224
+ ]);
13225
+ registerStorageSchemaInitializer((database) => {
13226
+ database.exec(`
13227
+ CREATE TABLE IF NOT EXISTS api_topic_visuals (
13228
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
13229
+ topic_id TEXT NOT NULL,
13230
+ html TEXT NOT NULL,
13231
+ title TEXT,
13232
+ created_at INTEGER NOT NULL,
13233
+ kind TEXT NOT NULL DEFAULT 'html',
13234
+ source TEXT,
13235
+ file_id TEXT,
13236
+ mime_type TEXT,
13237
+ media_token TEXT
13238
+ );
13239
+ CREATE INDEX IF NOT EXISTS idx_api_topic_visuals_topic
13240
+ ON api_topic_visuals(topic_id, created_at DESC);
13241
+
13242
+ CREATE TABLE IF NOT EXISTS api_topic_visual_views (
13243
+ topic_id TEXT NOT NULL,
13244
+ user_id TEXT NOT NULL,
13245
+ visual_id INTEGER NOT NULL,
13246
+ PRIMARY KEY (topic_id, user_id)
13247
+ );
13248
+ `);
13249
+ });
13199
13250
  for (const statement of [
13200
13251
  "ALTER TABLE api_topic_visuals ADD COLUMN kind TEXT NOT NULL DEFAULT 'html'",
13201
13252
  "ALTER TABLE api_topic_visuals ADD COLUMN source TEXT",
@@ -13236,32 +13287,35 @@ var init_self_schedules = __esm(async () => {
13236
13287
  await __promiseAll([
13237
13288
  init_forum_db(),
13238
13289
  init_runtime_leases(),
13239
- init_runtime_topic_state()
13290
+ init_runtime_topic_state(),
13291
+ init_storage_host()
13240
13292
  ]);
13241
- db.exec(`
13242
- CREATE TABLE IF NOT EXISTS runtime_self_schedules (
13243
- id TEXT PRIMARY KEY,
13244
- topic_id TEXT NOT NULL,
13245
- user_id TEXT NOT NULL,
13246
- message TEXT NOT NULL,
13247
- deliver_at INTEGER NOT NULL,
13248
- created_at INTEGER NOT NULL,
13249
- updated_at INTEGER NOT NULL,
13250
- status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running')),
13251
- claimed_by TEXT,
13252
- claimed_at INTEGER,
13253
- running_query_id TEXT
13254
- )
13255
- `);
13256
- db.exec(`
13257
- CREATE UNIQUE INDEX IF NOT EXISTS idx_runtime_self_schedules_one_pending
13258
- ON runtime_self_schedules(topic_id)
13259
- WHERE status = 'pending'
13260
- `);
13261
- db.exec(`
13262
- CREATE INDEX IF NOT EXISTS idx_runtime_self_schedules_due
13263
- ON runtime_self_schedules(status, deliver_at)
13264
- `);
13293
+ registerStorageSchemaInitializer((database) => {
13294
+ database.exec(`
13295
+ CREATE TABLE IF NOT EXISTS runtime_self_schedules (
13296
+ id TEXT PRIMARY KEY,
13297
+ topic_id TEXT NOT NULL,
13298
+ user_id TEXT NOT NULL,
13299
+ message TEXT NOT NULL,
13300
+ deliver_at INTEGER NOT NULL,
13301
+ created_at INTEGER NOT NULL,
13302
+ updated_at INTEGER NOT NULL,
13303
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running')),
13304
+ claimed_by TEXT,
13305
+ claimed_at INTEGER,
13306
+ running_query_id TEXT
13307
+ )
13308
+ `);
13309
+ database.exec(`
13310
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_runtime_self_schedules_one_pending
13311
+ ON runtime_self_schedules(topic_id)
13312
+ WHERE status = 'pending'
13313
+ `);
13314
+ database.exec(`
13315
+ CREATE INDEX IF NOT EXISTS idx_runtime_self_schedules_due
13316
+ ON runtime_self_schedules(status, deliver_at)
13317
+ `);
13318
+ });
13265
13319
  });
13266
13320
 
13267
13321
  // ../../packages/core/src/topics/lifecycle.ts
@@ -15506,7 +15560,7 @@ var init_turn_session = __esm(async () => {
15506
15560
  });
15507
15561
 
15508
15562
  // ../../packages/core/src/storage/app-settings.ts
15509
- import { existsSync as existsSync18, mkdirSync as mkdirSync19, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "fs";
15563
+ import { existsSync as existsSync19, mkdirSync as mkdirSync19, readFileSync as readFileSync19, writeFileSync as writeFileSync16 } from "fs";
15510
15564
  import { dirname as dirname15, join as join28 } from "path";
15511
15565
  function settingsFile() {
15512
15566
  return join28(resolveStorageDataDir(), "otium-settings.json");
@@ -15518,8 +15572,8 @@ function ensureSettingsLoaded() {
15518
15572
  loadedSettingsFile = path;
15519
15573
  aiName = DEFAULT_AI_NAME;
15520
15574
  try {
15521
- if (existsSync18(path)) {
15522
- const data = JSON.parse(readFileSync18(path, "utf8"));
15575
+ if (existsSync19(path)) {
15576
+ const data = JSON.parse(readFileSync19(path, "utf8"));
15523
15577
  if (typeof data.aiName === "string" && data.aiName.trim()) {
15524
15578
  aiName = data.aiName.trim();
15525
15579
  }
@@ -15612,7 +15666,7 @@ __export(exports_turn_runner, {
15612
15666
  AGENT_DISPLAY_NAME: () => AGENT_DISPLAY_NAME
15613
15667
  });
15614
15668
  import { randomUUID as randomUUID16 } from "crypto";
15615
- import { existsSync as existsSync19, mkdirSync as mkdirSync20, readdirSync as readdirSync7, statSync as statSync10 } from "fs";
15669
+ import { existsSync as existsSync20, mkdirSync as mkdirSync20, readdirSync as readdirSync7, statSync as statSync10 } from "fs";
15616
15670
  import { join as join29 } from "path";
15617
15671
  function withDefaultPlaywright(configuredMcp, isManager) {
15618
15672
  if (isManager)
@@ -15669,7 +15723,7 @@ function appendAskReplyMessage(topicId, text2, agentType) {
15669
15723
  }
15670
15724
  function resolveWikiMirrorPath(directory, preferredFilename, matchesLegacyId, matchesTitle, preferExact = true) {
15671
15725
  const preferred = join29(directory, preferredFilename);
15672
- if (preferExact && existsSync19(preferred))
15726
+ if (preferExact && existsSync20(preferred))
15673
15727
  return preferred;
15674
15728
  let newestLegacyId = null;
15675
15729
  let newestTitle = null;
@@ -15694,9 +15748,9 @@ function resolveWikiMirrorPath(directory, preferredFilename, matchesLegacyId, ma
15694
15748
  }
15695
15749
  function resolveWikiMemoryMirror(wikiDir, topicId, topicTitle) {
15696
15750
  const briefFile = resolveWikiMirrorPath(join29(wikiDir, "topic"), `${wikiBriefStorageKey(topicTitle, topicId)}.md`, (filename) => isTopicBriefFile(filename, topicId), (filename) => isTopicBriefFile(filename, topicId, topicTitle));
15697
- const hasBriefFile = existsSync19(briefFile) && statSync10(briefFile).isFile();
15751
+ const hasBriefFile = existsSync20(briefFile) && statSync10(briefFile).isFile();
15698
15752
  const latestSummaryCandidate = resolveWikiMirrorPath(join29(wikiDir, "summaries"), `__missing__-${wikiSummaryFilename("0000-00-00", topicTitle, topicId)}`, (filename) => isTopicSummaryFile(filename, topicId), (filename) => isTopicSummaryFile(filename, topicId, topicTitle), false);
15699
- const latestSummaryFile = existsSync19(latestSummaryCandidate) && statSync10(latestSummaryCandidate).isFile() ? latestSummaryCandidate : null;
15753
+ const latestSummaryFile = existsSync20(latestSummaryCandidate) && statSync10(latestSummaryCandidate).isFile() ? latestSummaryCandidate : null;
15700
15754
  return { briefFile, hasBriefFile, latestSummaryFile };
15701
15755
  }
15702
15756
  async function streamAgentEvents(topicId, topicTitle, queryId, events, control, agentType, model, effort, userId, retryableSessionExpired = true, onSessionId, execution) {
@@ -16892,7 +16946,7 @@ __export(exports_auth_check, {
16892
16946
  checkAgentAuth: () => checkAgentAuth
16893
16947
  });
16894
16948
  import { execFileSync as execFileSync7 } from "child_process";
16895
- import { existsSync as existsSync20 } from "fs";
16949
+ import { existsSync as existsSync21 } from "fs";
16896
16950
  import { homedir as homedir8, platform } from "os";
16897
16951
  import { join as join30 } from "path";
16898
16952
  function hasMaestroCredential(host, key, userId) {
@@ -16965,7 +17019,7 @@ var init_auth_check = __esm(async () => {
16965
17019
  await init_vault();
16966
17020
  defaultAgentAuthHost = {
16967
17021
  codexAuthFilePath,
16968
- exists: existsSync20,
17022
+ exists: existsSync21,
16969
17023
  environment: process.env,
16970
17024
  homeDirectory: homedir8,
16971
17025
  operatingSystem: platform,
@@ -18196,25 +18250,238 @@ import { AsyncLocalStorage } from "async_hooks";
18196
18250
  import { randomUUID } from "crypto";
18197
18251
  import {
18198
18252
  closeSync,
18199
- existsSync as existsSync2,
18253
+ existsSync as existsSync3,
18200
18254
  mkdirSync as mkdirSync2,
18201
18255
  openSync,
18202
18256
  readdirSync,
18203
- readFileSync as readFileSync2,
18257
+ readFileSync as readFileSync3,
18204
18258
  renameSync,
18205
18259
  statSync,
18206
- unlinkSync,
18260
+ unlinkSync as unlinkSync2,
18207
18261
  writeFileSync as writeFileSync2
18208
18262
  } from "fs";
18209
18263
  import { basename, dirname as dirname2, join as join2, relative, resolve as resolve2 } from "path";
18210
- init_config();
18211
- init_wiki_summary_names();
18212
18264
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
18213
18265
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18214
18266
  import {
18215
18267
  CallToolRequestSchema,
18216
18268
  ListToolsRequestSchema
18217
18269
  } from "@modelcontextprotocol/sdk/types.js";
18270
+
18271
+ // ../../packages/core/src/mcp/wiki-search-index.ts
18272
+ await init_sqlite();
18273
+ import { chmodSync, existsSync, readFileSync, unlinkSync } from "fs";
18274
+ var SEARCH_INDEX_SCHEMA_VERSION = 3;
18275
+ function documentId(source) {
18276
+ return `${source.kind}:${source.key}`;
18277
+ }
18278
+ function ensureSchema(database) {
18279
+ const version = database.query("PRAGMA user_version").get()?.user_version;
18280
+ if (version !== 0 && version !== SEARCH_INDEX_SCHEMA_VERSION) {
18281
+ throw new Error(`Unsupported wiki search index schema: ${version}`);
18282
+ }
18283
+ database.exec(`
18284
+ CREATE TABLE IF NOT EXISTS wiki_search_documents (
18285
+ id TEXT PRIMARY KEY,
18286
+ kind TEXT NOT NULL,
18287
+ key TEXT NOT NULL,
18288
+ path TEXT NOT NULL,
18289
+ mtime_ms REAL NOT NULL,
18290
+ size INTEGER NOT NULL,
18291
+ title TEXT NOT NULL,
18292
+ text TEXT NOT NULL,
18293
+ date TEXT,
18294
+ family TEXT
18295
+ );
18296
+ CREATE UNIQUE INDEX IF NOT EXISTS wiki_search_documents_kind_key
18297
+ ON wiki_search_documents(kind, key);
18298
+ CREATE INDEX IF NOT EXISTS wiki_search_documents_kind_date
18299
+ ON wiki_search_documents(kind, date);
18300
+ CREATE INDEX IF NOT EXISTS wiki_search_documents_kind_family_date
18301
+ ON wiki_search_documents(kind, family, date);
18302
+ CREATE VIRTUAL TABLE IF NOT EXISTS wiki_search_fts USING fts5(
18303
+ document_id UNINDEXED,
18304
+ kind UNINDEXED,
18305
+ tokens,
18306
+ tokenize = 'unicode61'
18307
+ );
18308
+ PRAGMA user_version = ${SEARCH_INDEX_SCHEMA_VERSION};
18309
+ `);
18310
+ }
18311
+
18312
+ class WikiSearchIndex {
18313
+ path;
18314
+ #database;
18315
+ constructor(path) {
18316
+ this.path = path;
18317
+ this.#database = new Database(path, { create: true });
18318
+ try {
18319
+ this.#database.exec("PRAGMA foreign_keys = ON; PRAGMA journal_mode = DELETE;");
18320
+ ensureSchema(this.#database);
18321
+ chmodSync(path, 384);
18322
+ } catch (error) {
18323
+ this.#database.close();
18324
+ throw error;
18325
+ }
18326
+ }
18327
+ close() {
18328
+ this.#database.close();
18329
+ }
18330
+ upsert(source, document) {
18331
+ const id = documentId(source);
18332
+ const apply = this.#database.transaction(() => {
18333
+ this.#database.query("DELETE FROM wiki_search_fts WHERE document_id = ?").run(id);
18334
+ this.#database.query("DELETE FROM wiki_search_documents WHERE id = ?").run(id);
18335
+ this.#database.query(`
18336
+ INSERT INTO wiki_search_documents
18337
+ (id, kind, key, path, mtime_ms, size, title, text, date, family)
18338
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
18339
+ `).run(id, source.kind, source.key, source.path, source.mtimeMs, source.size, document.title, document.text, document.date ?? null, document.family ?? null);
18340
+ this.#database.query("INSERT INTO wiki_search_fts(document_id, kind, tokens) VALUES (?, ?, ?)").run(id, source.kind, document.tokens.join(" "));
18341
+ });
18342
+ apply.immediate();
18343
+ }
18344
+ sync(sources, prepare) {
18345
+ const result = { added: 0, updated: 0, removed: 0 };
18346
+ const existingRows = this.#database.query("SELECT id, path, mtime_ms, size FROM wiki_search_documents ORDER BY id").all();
18347
+ const existing = new Map(existingRows.map((row) => [row.id, row]));
18348
+ const present = new Set;
18349
+ const changed = [];
18350
+ for (const source of sources) {
18351
+ const id = documentId(source);
18352
+ present.add(id);
18353
+ const stored = existing.get(id);
18354
+ if (stored && stored.path === source.path && stored.mtime_ms === source.mtimeMs && stored.size === source.size) {
18355
+ continue;
18356
+ }
18357
+ try {
18358
+ const text = readFileSync(source.path, "utf-8");
18359
+ changed.push({ source, document: prepare(source, text), added: !stored });
18360
+ } catch {}
18361
+ }
18362
+ const removed = existingRows.filter((row) => !present.has(row.id));
18363
+ const apply = this.#database.transaction(() => {
18364
+ const deleteTerms = this.#database.query("DELETE FROM wiki_search_fts WHERE document_id = ?");
18365
+ const deleteDocument = this.#database.query("DELETE FROM wiki_search_documents WHERE id = ?");
18366
+ const insertDocument = this.#database.query(`
18367
+ INSERT INTO wiki_search_documents
18368
+ (id, kind, key, path, mtime_ms, size, title, text, date, family)
18369
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
18370
+ `);
18371
+ const insertTerms = this.#database.query(`
18372
+ INSERT INTO wiki_search_fts(document_id, kind, tokens)
18373
+ VALUES (?, ?, ?)
18374
+ `);
18375
+ for (const row of removed) {
18376
+ deleteTerms.run(row.id);
18377
+ deleteDocument.run(row.id);
18378
+ result.removed += 1;
18379
+ }
18380
+ for (const { source, document, added } of changed) {
18381
+ const id = documentId(source);
18382
+ deleteTerms.run(id);
18383
+ deleteDocument.run(id);
18384
+ insertDocument.run(id, source.kind, source.key, source.path, source.mtimeMs, source.size, document.title, document.text, document.date ?? null, document.family ?? null);
18385
+ insertTerms.run(id, source.kind, document.tokens.join(" "));
18386
+ if (added)
18387
+ result.added += 1;
18388
+ else
18389
+ result.updated += 1;
18390
+ }
18391
+ });
18392
+ apply.immediate();
18393
+ return result;
18394
+ }
18395
+ metadata(kinds) {
18396
+ if (kinds.length === 0)
18397
+ return [];
18398
+ const placeholders = kinds.map(() => "?").join(", ");
18399
+ const rows = this.#database.query(`SELECT id, kind, key, title, date
18400
+ FROM wiki_search_documents
18401
+ WHERE kind IN (${placeholders})`).all(...kinds);
18402
+ return rows.map((row) => ({
18403
+ id: row.id,
18404
+ kind: row.kind,
18405
+ key: row.key,
18406
+ title: row.title,
18407
+ ...row.date ? { date: row.date } : {}
18408
+ }));
18409
+ }
18410
+ summaryMetadataByDate(query) {
18411
+ const conditions = ["kind = 'summary'", "date IS NOT NULL"];
18412
+ const parameters = [];
18413
+ if (query.year && query.month) {
18414
+ const month = Number(query.month);
18415
+ const nextYear = month === 12 ? String(Number(query.year) + 1) : query.year;
18416
+ const nextMonth = String(month === 12 ? 1 : month + 1).padStart(2, "0");
18417
+ conditions.push("date >= ?", "date < ?");
18418
+ parameters.push(`${query.year}-${query.month}-01`, `${nextYear}-${nextMonth}-01`);
18419
+ } else if (query.year) {
18420
+ conditions.push("date >= ?", "date < ?");
18421
+ parameters.push(`${query.year}-01-01`, `${Number(query.year) + 1}-01-01`);
18422
+ } else if (query.month) {
18423
+ conditions.push("substr(date, 6, 2) = ?");
18424
+ parameters.push(query.month);
18425
+ }
18426
+ parameters.push(query.limit);
18427
+ const rows = this.#database.query(`SELECT id, kind, key, title, date
18428
+ FROM wiki_search_documents
18429
+ WHERE ${conditions.join(" AND ")}
18430
+ ORDER BY date ${query.direction === "oldest" ? "ASC" : "DESC"}, key ASC
18431
+ LIMIT ?`).all(...parameters);
18432
+ return rows.map((row) => ({
18433
+ id: row.id,
18434
+ kind: row.kind,
18435
+ key: row.key,
18436
+ title: row.title,
18437
+ ...row.date ? { date: row.date } : {}
18438
+ }));
18439
+ }
18440
+ matchingDocumentIds(kinds, terms) {
18441
+ if (kinds.length === 0 || terms.length === 0)
18442
+ return new Set;
18443
+ const kindPlaceholders = kinds.map(() => "?").join(", ");
18444
+ const match = terms.map((term) => `"${term.replaceAll('"', '""')}"`).join(" OR ");
18445
+ const rows = this.#database.query(`SELECT DISTINCT document_id
18446
+ FROM wiki_search_fts
18447
+ WHERE wiki_search_fts MATCH ? AND kind IN (${kindPlaceholders})`).all(match, ...kinds);
18448
+ return new Set(rows.map((row) => row.document_id));
18449
+ }
18450
+ documents(ids) {
18451
+ if (ids.size === 0)
18452
+ return [];
18453
+ const values = [...ids];
18454
+ const rows = [];
18455
+ for (let offset = 0;offset < values.length; offset += 500) {
18456
+ const chunk = values.slice(offset, offset + 500);
18457
+ const placeholders = chunk.map(() => "?").join(", ");
18458
+ rows.push(...this.#database.query(`SELECT id, kind, key, path, title, text, date
18459
+ FROM wiki_search_documents
18460
+ WHERE id IN (${placeholders})`).all(...chunk));
18461
+ }
18462
+ return rows.map((row) => ({
18463
+ id: row.id,
18464
+ kind: row.kind,
18465
+ key: row.key,
18466
+ path: row.path,
18467
+ title: row.title,
18468
+ text: row.text,
18469
+ tokens: [],
18470
+ ...row.date ? { date: row.date } : {}
18471
+ }));
18472
+ }
18473
+ }
18474
+ function resetCorruptWikiSearchIndex(path) {
18475
+ for (const candidate of [path, `${path}-journal`, `${path}-wal`, `${path}-shm`]) {
18476
+ if (!existsSync(candidate))
18477
+ continue;
18478
+ unlinkSync(candidate);
18479
+ }
18480
+ }
18481
+
18482
+ // ../../packages/core/src/mcp/wiki-server.ts
18483
+ init_config();
18484
+ init_wiki_summary_names();
18218
18485
  var wikiRuntime = new AsyncLocalStorage;
18219
18486
  function runtime() {
18220
18487
  const current = wikiRuntime.getStore();
@@ -18241,15 +18508,15 @@ function acquireRecoveryGuard(lockPath) {
18241
18508
  } catch (error) {
18242
18509
  closeSync(fd);
18243
18510
  try {
18244
- unlinkSync(guardPath);
18511
+ unlinkSync2(guardPath);
18245
18512
  } catch {}
18246
18513
  throw error;
18247
18514
  }
18248
18515
  return () => {
18249
18516
  closeSync(fd);
18250
18517
  try {
18251
- if (readFileSync2(guardPath, "utf-8") === ownerToken)
18252
- unlinkSync(guardPath);
18518
+ if (readFileSync3(guardPath, "utf-8") === ownerToken)
18519
+ unlinkSync2(guardPath);
18253
18520
  } catch {}
18254
18521
  };
18255
18522
  } catch (error) {
@@ -18274,7 +18541,7 @@ function acquireFileLock(path) {
18274
18541
  } catch (error) {
18275
18542
  closeSync(fd);
18276
18543
  try {
18277
- unlinkSync(lockPath);
18544
+ unlinkSync2(lockPath);
18278
18545
  } catch {}
18279
18546
  throw error;
18280
18547
  }
@@ -18282,8 +18549,8 @@ function acquireFileLock(path) {
18282
18549
  closeSync(fd);
18283
18550
  const releaseGuard = acquireRecoveryGuard(lockPath);
18284
18551
  try {
18285
- if (readFileSync2(lockPath, "utf-8") === ownerToken)
18286
- unlinkSync(lockPath);
18552
+ if (readFileSync3(lockPath, "utf-8") === ownerToken)
18553
+ unlinkSync2(lockPath);
18287
18554
  } catch {} finally {
18288
18555
  releaseGuard();
18289
18556
  }
@@ -18294,9 +18561,9 @@ function acquireFileLock(path) {
18294
18561
  const releaseGuard = acquireRecoveryGuard(lockPath);
18295
18562
  try {
18296
18563
  try {
18297
- const staleToken = readFileSync2(lockPath, "utf-8");
18298
- if (Date.now() - statSync(lockPath).mtimeMs > FILE_LOCK_STALE_MS && readFileSync2(lockPath, "utf-8") === staleToken) {
18299
- unlinkSync(lockPath);
18564
+ const staleToken = readFileSync3(lockPath, "utf-8");
18565
+ if (Date.now() - statSync(lockPath).mtimeMs > FILE_LOCK_STALE_MS && readFileSync3(lockPath, "utf-8") === staleToken) {
18566
+ unlinkSync2(lockPath);
18300
18567
  continue;
18301
18568
  }
18302
18569
  } catch {
@@ -18319,7 +18586,7 @@ function atomicWriteFile(path, content) {
18319
18586
  renameSync(temporaryPath, path);
18320
18587
  } catch (error) {
18321
18588
  try {
18322
- unlinkSync(temporaryPath);
18589
+ unlinkSync2(temporaryPath);
18323
18590
  } catch {}
18324
18591
  throw error;
18325
18592
  }
@@ -18506,10 +18773,91 @@ function trigramSimilarity(left, right) {
18506
18773
  overlap += 1;
18507
18774
  return 2 * overlap / (a.size + b.size);
18508
18775
  }
18776
+ var WIKI_SEARCH_INDEX_FILENAME = ".wiki-search-index.sqlite";
18777
+ function openWikiSearchIndex() {
18778
+ const path = resolve2(runtime().wikiDir, WIKI_SEARCH_INDEX_FILENAME);
18779
+ try {
18780
+ return new WikiSearchIndex(path);
18781
+ } catch (error) {
18782
+ if (!/malformed|not a database|schema/i.test(String(error)))
18783
+ return;
18784
+ try {
18785
+ resetCorruptWikiSearchIndex(path);
18786
+ return new WikiSearchIndex(path);
18787
+ } catch {
18788
+ return;
18789
+ }
18790
+ }
18791
+ }
18792
+ function prepareWikiSearchDocument(kind, key, text) {
18793
+ const titleLine = text.split(`
18794
+ `).find((line) => /^#+\s*/.test(line)) ?? "";
18795
+ const title = titleLine ? titleLine.replace(/^#+\s*/, "") : basename(key);
18796
+ return {
18797
+ title,
18798
+ text,
18799
+ tokens: canonicalSearchTokens(`${key} ${title} ${text}`),
18800
+ ...kind === "summary" && /^\d{4}-\d{2}-\d{2}/.test(key) ? { date: key.slice(0, 10), family: summaryTopicIdentity(key) } : {}
18801
+ };
18802
+ }
18803
+ function indexWikiSearchDocument(kind, key, text) {
18804
+ const root = kind === "article" ? runtime().articlesDir : runtime().summariesDir;
18805
+ const path = resolve2(root, `${key}.md`);
18806
+ const index = openWikiSearchIndex();
18807
+ if (!index)
18808
+ return false;
18809
+ try {
18810
+ const stat = statSync(path);
18811
+ index.upsert({ kind, key, path, mtimeMs: stat.mtimeMs, size: stat.size }, prepareWikiSearchDocument(kind, key, text));
18812
+ return true;
18813
+ } catch {
18814
+ return false;
18815
+ } finally {
18816
+ index.close();
18817
+ }
18818
+ }
18819
+ var WIKI_INDEX_FILENAMES = {
18820
+ article: "article-index.md",
18821
+ summary: "summary-index.md",
18822
+ topic: "topic-index.md",
18823
+ skill: "skill-index.md"
18824
+ };
18825
+ var WIKI_INDEX_NAMESPACES = {
18826
+ article: "articles",
18827
+ summary: "summaries",
18828
+ topic: "topic",
18829
+ skill: "skills"
18830
+ };
18831
+ function wikiIndexPath(kind) {
18832
+ return resolve2(runtime().wikiDir, WIKI_INDEX_FILENAMES[kind]);
18833
+ }
18834
+ var MAX_INDEX_DESCRIPTION_LENGTH = 400;
18835
+ function normalizeIndexDescription(raw) {
18836
+ const description = raw.replaceAll(/\s+/g, " ").trim();
18837
+ if (!description)
18838
+ return { error: "A non-empty one-line description is required." };
18839
+ if (description.length > MAX_INDEX_DESCRIPTION_LENGTH) {
18840
+ return {
18841
+ error: `Description must be at most ${MAX_INDEX_DESCRIPTION_LENGTH} characters (got ${description.length}).`
18842
+ };
18843
+ }
18844
+ return { description };
18845
+ }
18846
+ function normalizeWikiSlug(raw) {
18847
+ const slug = raw.trim().replace(/\.md$/i, "").replace(/^\/+/, "");
18848
+ if (!slug)
18849
+ return { error: "A non-empty slug is required." };
18850
+ if (/[[\]|\\]/.test(slug))
18851
+ return { error: "Slug must not contain [ ] | or backslashes." };
18852
+ if (slug.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
18853
+ return { error: "Slug must not contain empty or relative path segments." };
18854
+ }
18855
+ return { slug };
18856
+ }
18509
18857
  function parseWikiIndex(path) {
18510
18858
  let text;
18511
18859
  try {
18512
- text = readFileSync2(path, "utf-8");
18860
+ text = readFileSync3(path, "utf-8");
18513
18861
  } catch {
18514
18862
  return [];
18515
18863
  }
@@ -18663,7 +19011,7 @@ function wikiQuery(args) {
18663
19011
  continue;
18664
19012
  const fp = join2(sub, f.name);
18665
19013
  try {
18666
- const text = readFileSync2(fp, "utf-8");
19014
+ const text = readFileSync3(fp, "utf-8");
18667
19015
  const key = `${entry.name}/${f.name.replace(/\.md$/i, "")}`;
18668
19016
  scored.push({
18669
19017
  kind: label === "articles" ? "article" : "skill",
@@ -18684,7 +19032,7 @@ function wikiQuery(args) {
18684
19032
  }
18685
19033
  const fp = join2(dir, entry.name);
18686
19034
  try {
18687
- const text = readFileSync2(fp, "utf-8");
19035
+ const text = readFileSync3(fp, "utf-8");
18688
19036
  const key = entry.name.replace(/\.md$/i, "");
18689
19037
  const resultKind = label === "articles" ? "article" : label === "topic" ? "topic" : label === "summaries" ? "summary" : "skill";
18690
19038
  scored.push({
@@ -18798,14 +19146,45 @@ function wikiQuery(args) {
18798
19146
  return 0;
18799
19147
  return score;
18800
19148
  }
18801
- if (kind === "all" || kind === "article")
18802
- scan(runtime().articlesDir, "articles");
19149
+ const bodyKinds = [
19150
+ ...kind === "all" || kind === "article" ? ["article"] : [],
19151
+ ...kind === "all" || kind === "summary" ? ["summary"] : []
19152
+ ];
19153
+ if (bodyKinds.length > 0) {
19154
+ const searchIndex = openWikiSearchIndex();
19155
+ if (searchIndex) {
19156
+ try {
19157
+ const terms = canonicalSearchTokens(normalizedQuery);
19158
+ const ids = searchIndex.matchingDocumentIds(bodyKinds, terms);
19159
+ if (temporalDirection && bodyKinds.includes("summary")) {
19160
+ for (const item of searchIndex.summaryMetadataByDate({
19161
+ direction: temporalDirection === "newest" ? "latest" : "oldest",
19162
+ limit
19163
+ })) {
19164
+ ids.add(item.id);
19165
+ }
19166
+ }
19167
+ for (const document of searchIndex.documents(ids)) {
19168
+ scored.push({
19169
+ kind: document.kind,
19170
+ key: document.key,
19171
+ keyScore: scoreIndexKey(question, document.key),
19172
+ score: scoreMatch(document.text, document.key, document.kind),
19173
+ path: `${document.kind === "article" ? "articles" : "summaries"}/${document.key}.md`,
19174
+ text: document.text,
19175
+ title: document.title,
19176
+ ...document.date ? { date: document.date } : {}
19177
+ });
19178
+ }
19179
+ } catch {} finally {
19180
+ searchIndex.close();
19181
+ }
19182
+ }
19183
+ }
18803
19184
  if (kind === "all" && runtime().surface === "all")
18804
19185
  scan(runtime().skillsDir, "skills");
18805
19186
  if (kind === "all" || kind === "topic")
18806
19187
  scan(runtime().topicsDir, "topic");
18807
- if (kind === "all" || kind === "summary")
18808
- scan(runtime().summariesDir, "summaries");
18809
19188
  const merged = new Map;
18810
19189
  for (const { candidate, keyScore, score } of indexCandidates) {
18811
19190
  merged.set(`${candidate.kind}:${candidate.key}`, {
@@ -18937,7 +19316,7 @@ function wikiTopicBrief(args) {
18937
19316
  const topicKey = wikiSummarySlug(rawTopic.replace(/^topic\//, "").replace(/\.md$/i, ""));
18938
19317
  const filePath = resolve2(runtime().topicsDir, `${topicKey}.md`);
18939
19318
  try {
18940
- const content = readFileSync2(filePath, "utf-8");
19319
+ const content = readFileSync3(filePath, "utf-8");
18941
19320
  return {
18942
19321
  content: [{ type: "text", text: `# Topic Brief: ${topicKey}
18943
19322
 
@@ -18989,7 +19368,7 @@ Adopted topic memory: ${memoryKey}` }]
18989
19368
  return { content: [{ type: "text", text: "Invalid wiki document key." }], isError: true };
18990
19369
  }
18991
19370
  try {
18992
- const content = readFileSync2(filePath, "utf-8");
19371
+ const content = readFileSync3(filePath, "utf-8");
18993
19372
  return {
18994
19373
  content: [{ type: "text", text: `# Wiki ${kind}: ${key}
18995
19374
 
@@ -19009,18 +19388,18 @@ function wikiLastConversation(args) {
19009
19388
  const name = slugify(topicNameFrom(rawTopic));
19010
19389
  const archiveDir = resolve2(runtime().archiveDir, name);
19011
19390
  try {
19012
- let files = existsSync2(archiveDir) ? readdirSync(archiveDir).filter((f) => f.endsWith(".jsonl")).sort().reverse() : [];
19391
+ let files = existsSync3(archiveDir) ? readdirSync(archiveDir).filter((f) => f.endsWith(".jsonl")).sort().reverse() : [];
19013
19392
  if (files.length === 0) {
19014
- files = existsSync2(runtime().archiveDir) ? readdirSync(runtime().archiveDir).filter((f) => f.startsWith(name) && f.endsWith(".jsonl")).sort().reverse() : [];
19393
+ files = existsSync3(runtime().archiveDir) ? readdirSync(runtime().archiveDir).filter((f) => f.startsWith(name) && f.endsWith(".jsonl")).sort().reverse() : [];
19015
19394
  }
19016
19395
  if (files.length === 0) {
19017
19396
  return {
19018
19397
  content: [{ type: "text", text: `No archive found for topic: ${rawTopic}` }]
19019
19398
  };
19020
19399
  }
19021
- const actualPath = files[0].includes("/") ? files[0] : resolve2(existsSync2(archiveDir) && readdirSync(archiveDir).includes(files[0]) ? archiveDir : runtime().archiveDir, files[0]);
19400
+ const actualPath = files[0].includes("/") ? files[0] : resolve2(existsSync3(archiveDir) && readdirSync(archiveDir).includes(files[0]) ? archiveDir : runtime().archiveDir, files[0]);
19022
19401
  try {
19023
- const raw = readFileSync2(actualPath, "utf-8");
19402
+ const raw = readFileSync3(actualPath, "utf-8");
19024
19403
  const lines = raw.trim().split(`
19025
19404
  `).filter(Boolean);
19026
19405
  const turns2 = [];
@@ -19093,7 +19472,7 @@ function skillQuery(args) {
19093
19472
  continue;
19094
19473
  const skillFile = resolve2(runtime().skillsDir, entry.name, "skill.md");
19095
19474
  try {
19096
- const text = readFileSync2(skillFile, "utf-8");
19475
+ const text = readFileSync3(skillFile, "utf-8");
19097
19476
  const lower = text.toLowerCase();
19098
19477
  let score = 0;
19099
19478
  for (const w of query.split(/\s+/).filter(Boolean)) {
@@ -19164,7 +19543,7 @@ ${fresh.join(`
19164
19543
  const skillPath = resolve2(skillDir, "skill.md");
19165
19544
  let finalContent = content;
19166
19545
  try {
19167
- const existing = readFileSync2(skillPath, "utf-8");
19546
+ const existing = readFileSync3(skillPath, "utf-8");
19168
19547
  finalContent = mergeGotchas(content, extractGotchas(existing));
19169
19548
  } catch {}
19170
19549
  writeFileSync2(skillPath, finalContent, "utf-8");
@@ -19177,15 +19556,7 @@ ${fresh.join(`
19177
19556
  ]
19178
19557
  };
19179
19558
  }
19180
- function saveWikiEntry(args) {
19181
- const rawTopic = safeExt(args, ["topic", "topicName"], "");
19182
- const content = safeExt(args, ["content", "text", "body"], "");
19183
- if (!rawTopic)
19184
- return { content: [{ type: "text", text: "Missing topic." }] };
19185
- if (!content)
19186
- return { content: [{ type: "text", text: "Missing content." }] };
19187
- const now = new Date;
19188
- const dateStr = now.toISOString().slice(0, 10);
19559
+ function writeSummaryDocument(rawTopic, content, dateStr) {
19189
19560
  const topicId = runtime().topicId;
19190
19561
  const fileSlug = wikiBriefStorageKey(rawTopic, topicId);
19191
19562
  ensureDir(runtime().summariesDir);
@@ -19212,7 +19583,7 @@ function saveWikiEntry(args) {
19212
19583
  if (topicId && setTopicBrief) {
19213
19584
  try {
19214
19585
  const briefPath = resolve2(runtime().topicsDir, `${fileSlug}.md`);
19215
- let briefMd = existsSync2(briefPath) ? readFileSync2(briefPath, "utf-8") : undefined;
19586
+ let briefMd = existsSync3(briefPath) ? readFileSync3(briefPath, "utf-8") : undefined;
19216
19587
  if (briefMd === undefined && getTopicBrief && !getTopicBrief(fileSlug)?.briefMd) {
19217
19588
  const legacy = getTopicBrief(topicId)?.briefMd;
19218
19589
  if (legacy)
@@ -19226,28 +19597,14 @@ function saveWikiEntry(args) {
19226
19597
  sqliteUpdated = true;
19227
19598
  } catch {}
19228
19599
  }
19229
- return {
19230
- content: [
19231
- {
19232
- type: "text",
19233
- text: `Saved summary: summaries/${summaryName}` + (sqliteUpdated ? `
19234
- SQLite latest-summary also updated.` : "")
19235
- }
19236
- ]
19237
- };
19600
+ return { slug: summaryName.replace(/\.md$/i, ""), sqliteUpdated };
19238
19601
  }
19239
- function saveTopicBrief(args) {
19240
- const rawTopic = safeExt(args, ["topic", "topicName"], "");
19241
- const content = safeExt(args, ["content", "brief", "text", "body"], "");
19242
- if (!rawTopic)
19243
- return { content: [{ type: "text", text: "Missing topic." }] };
19244
- if (!content)
19245
- return { content: [{ type: "text", text: "Missing content." }] };
19602
+ function writeTopicDocument(rawTopic, content) {
19246
19603
  const topicId = runtime().topicId;
19247
19604
  const fileSlug = wikiBriefStorageKey(rawTopic, topicId);
19248
19605
  const briefPath = resolve2(runtime().topicsDir, `${fileSlug}.md`);
19249
19606
  ensureDir(dirname2(briefPath));
19250
- const existed = existsSync2(briefPath);
19607
+ const existed = existsSync3(briefPath);
19251
19608
  writeFileSync2(briefPath, content, "utf-8");
19252
19609
  const setTopicBrief = runtime().host.setTopicBrief;
19253
19610
  let sqliteUpdated = false;
@@ -19257,68 +19614,228 @@ function saveTopicBrief(args) {
19257
19614
  sqliteUpdated = true;
19258
19615
  } catch {}
19259
19616
  }
19617
+ return { slug: fileSlug, existed, sqliteUpdated };
19618
+ }
19619
+ function writeArticleDocument(slug, content) {
19620
+ const articlePath = resolve2(runtime().articlesDir, `${slug}.md`);
19621
+ ensureDir(dirname2(articlePath));
19622
+ const existed = existsSync3(articlePath);
19623
+ atomicWriteFile(articlePath, content);
19624
+ return { existed };
19625
+ }
19626
+ function wikiWrite(args) {
19627
+ const rawKind = safeExt(args, ["kind", "type"], "");
19628
+ const content = safeExt(args, ["content", "text", "body", "brief"], "");
19629
+ const rawDescription = safeExt(args, ["description", "desc"], "");
19630
+ const rawTopic = safeExt(args, ["topic", "topicName"], "");
19631
+ const rawSlug = safeExt(args, ["slug", "id"], "");
19632
+ const section = safeExt(args, ["section", "category"], undefined);
19633
+ const rawDate = safeExt(args, ["date", "created"], undefined);
19634
+ if (rawKind !== "summary" && rawKind !== "article" && rawKind !== "topic") {
19635
+ return {
19636
+ content: [{ type: "text", text: 'kind must be one of "summary", "article", "topic".' }],
19637
+ isError: true
19638
+ };
19639
+ }
19640
+ if (!content.trim()) {
19641
+ return { content: [{ type: "text", text: "Missing content." }], isError: true };
19642
+ }
19643
+ const descriptionResult = normalizeIndexDescription(rawDescription);
19644
+ if ("error" in descriptionResult) {
19645
+ return { content: [{ type: "text", text: descriptionResult.error }], isError: true };
19646
+ }
19647
+ const { description } = descriptionResult;
19648
+ const today = new Date().toISOString().slice(0, 10);
19649
+ if (rawKind === "article") {
19650
+ const slugResult = normalizeWikiSlug(rawSlug);
19651
+ if ("error" in slugResult) {
19652
+ return { content: [{ type: "text", text: slugResult.error }], isError: true };
19653
+ }
19654
+ if (!section?.trim()) {
19655
+ return {
19656
+ content: [
19657
+ { type: "text", text: "An article requires a section so the catalog stays navigable." }
19658
+ ],
19659
+ isError: true
19660
+ };
19661
+ }
19662
+ const { existed } = writeArticleDocument(slugResult.slug, content);
19663
+ const link2 = indexRowOrPartialWrite("article", {
19664
+ kind: "article",
19665
+ slug: slugResult.slug,
19666
+ description,
19667
+ date: rawDate ?? today,
19668
+ section: section.trim()
19669
+ });
19670
+ if (typeof link2 !== "string")
19671
+ return link2;
19672
+ const searched = indexWikiSearchDocument("article", slugResult.slug, content);
19673
+ return {
19674
+ content: [
19675
+ {
19676
+ type: "text",
19677
+ text: `Saved article: articles/${slugResult.slug}.md (${existed ? "updated" : "created"})
19678
+ Indexed: ${link2}` + (searched ? "" : `
19679
+ ${SEARCH_CACHE_SKIPPED_NOTE}`)
19680
+ }
19681
+ ]
19682
+ };
19683
+ }
19684
+ const topic = rawTopic.trim();
19685
+ if (!topic) {
19686
+ return { content: [{ type: "text", text: "Missing topic." }], isError: true };
19687
+ }
19688
+ if (rawKind === "summary") {
19689
+ const dateStr = rawDate ?? today;
19690
+ const written2 = writeSummaryDocument(topic, content, dateStr);
19691
+ const link2 = indexRowOrPartialWrite("summary", {
19692
+ kind: "summary",
19693
+ slug: written2.slug,
19694
+ description,
19695
+ date: dateStr
19696
+ });
19697
+ if (typeof link2 !== "string")
19698
+ return link2;
19699
+ const searched = indexWikiSearchDocument("summary", written2.slug, content);
19700
+ return {
19701
+ content: [
19702
+ {
19703
+ type: "text",
19704
+ text: `Saved summary: summaries/${written2.slug}.md
19705
+ Indexed: ${link2}` + (written2.sqliteUpdated ? `
19706
+ SQLite latest-summary also updated.` : "") + (searched ? "" : `
19707
+ ${SEARCH_CACHE_SKIPPED_NOTE}`)
19708
+ }
19709
+ ]
19710
+ };
19711
+ }
19712
+ const written = writeTopicDocument(topic, content);
19713
+ const link = indexRowOrPartialWrite("topic", {
19714
+ kind: "topic",
19715
+ slug: written.slug,
19716
+ description,
19717
+ date: rawDate ?? today
19718
+ });
19719
+ if (typeof link !== "string")
19720
+ return link;
19260
19721
  return {
19261
19722
  content: [
19262
19723
  {
19263
19724
  type: "text",
19264
- text: `Saved brief: topic/${fileSlug}.md (${existed ? "updated" : "created"})` + (sqliteUpdated ? `
19725
+ text: `Saved brief: topic/${written.slug}.md (${written.existed ? "updated" : "created"})
19726
+ Indexed: ${link}` + (written.sqliteUpdated ? `
19265
19727
  SQLite brief also updated.` : "")
19266
19728
  }
19267
19729
  ]
19268
19730
  };
19269
19731
  }
19270
- function indexUpsert(args) {
19271
- const slug = safeExt(args, ["slug", "id"], "");
19272
- const desc = safeExt(args, ["description", "desc", "text"], "");
19273
- const kind = safeExt(args, ["kind", "type"], "article");
19274
- const section = safeExt(args, ["section", "category"], undefined);
19275
- const date = safeExt(args, ["date", "created"], undefined);
19276
- if (!slug)
19277
- return { content: [{ type: "text", text: "Missing slug." }] };
19278
- const today = new Date().toISOString().slice(0, 10);
19279
- const dateStr = date ?? today;
19280
- const indexPath = (() => {
19281
- if (kind === "topic")
19282
- return resolve2(runtime().wikiDir, "topic-index.md");
19283
- if (kind === "skill")
19284
- return resolve2(runtime().wikiDir, "skill-index.md");
19285
- return resolve2(runtime().wikiDir, "article-index.md");
19286
- })();
19732
+ function collectIndexableDocuments() {
19733
+ const sources = [];
19734
+ function add(kind, key, path) {
19735
+ try {
19736
+ const stat = statSync(path);
19737
+ if (!stat.isFile())
19738
+ return;
19739
+ sources.push({ kind, key, path, mtimeMs: stat.mtimeMs, size: stat.size });
19740
+ } catch {}
19741
+ }
19742
+ for (const [root, kind, allowSubdirectories] of [
19743
+ [runtime().articlesDir, "article", true],
19744
+ [runtime().summariesDir, "summary", false]
19745
+ ]) {
19746
+ let entries;
19747
+ try {
19748
+ entries = readdirSync(root, { withFileTypes: true });
19749
+ } catch {
19750
+ continue;
19751
+ }
19752
+ for (const entry of entries) {
19753
+ if (entry.isFile() && entry.name.endsWith(".md")) {
19754
+ add(kind, entry.name.replace(/\.md$/i, ""), join2(root, entry.name));
19755
+ continue;
19756
+ }
19757
+ if (!entry.isDirectory() || !allowSubdirectories)
19758
+ continue;
19759
+ let nested;
19760
+ try {
19761
+ nested = readdirSync(join2(root, entry.name), { withFileTypes: true });
19762
+ } catch {
19763
+ continue;
19764
+ }
19765
+ for (const file of nested) {
19766
+ if (!file.isFile() || !file.name.endsWith(".md"))
19767
+ continue;
19768
+ add(kind, `${entry.name}/${file.name.replace(/\.md$/i, "")}`, join2(root, entry.name, file.name));
19769
+ }
19770
+ }
19771
+ }
19772
+ return sources;
19773
+ }
19774
+ function wikiReindex() {
19775
+ const documents = collectIndexableDocuments();
19776
+ const index = openWikiSearchIndex();
19777
+ if (!index) {
19778
+ return {
19779
+ content: [{ type: "text", text: "Could not open the wiki search index." }],
19780
+ isError: true
19781
+ };
19782
+ }
19783
+ let synced;
19784
+ try {
19785
+ synced = index.sync(documents, (source, text) => prepareWikiSearchDocument(source.kind, source.key, text));
19786
+ } catch (error) {
19787
+ return {
19788
+ content: [{ type: "text", text: `Failed to rebuild the search index: ${String(error)}` }],
19789
+ isError: true
19790
+ };
19791
+ } finally {
19792
+ index.close();
19793
+ }
19794
+ const lines = [
19795
+ `Search index: ${synced.added} added, ${synced.updated} updated, ${synced.removed} removed (${documents.length} documents).`
19796
+ ];
19797
+ for (const kind of ["article", "summary"]) {
19798
+ const rows = new Set(parseWikiIndex(wikiIndexPath(kind)).filter((candidate) => candidate.kind === kind).map((candidate) => candidate.key));
19799
+ const keys = documents.filter((source) => source.kind === kind).map((source) => source.key);
19800
+ const unindexed = keys.filter((key) => !rows.has(key));
19801
+ const tombstones = [...rows].filter((key) => !keys.includes(key));
19802
+ 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(", ")}` : ""));
19803
+ }
19804
+ return { content: [{ type: "text", text: lines.join(`
19805
+ `) }] };
19806
+ }
19807
+ var SEARCH_CACHE_SKIPPED_NOTE = "Body-search cache was not updated; catalog retrieval still works. Run wiki_reindex to refill it.";
19808
+ function indexRowOrPartialWrite(label, options) {
19809
+ try {
19810
+ return upsertIndexRow(options);
19811
+ } catch (error) {
19812
+ return {
19813
+ content: [
19814
+ {
19815
+ type: "text",
19816
+ text: `Wrote the ${label} document but failed to index it (${String(error)}). Retry the same wiki_write call: both writes are idempotent.`
19817
+ }
19818
+ ],
19819
+ isError: true
19820
+ };
19821
+ }
19822
+ }
19823
+ function upsertIndexRow(options) {
19824
+ const { kind, slug, description, date, section } = options;
19825
+ const indexPath = wikiIndexPath(kind);
19287
19826
  ensureDir(dirname2(indexPath));
19288
19827
  const releaseLock = acquireFileLock(indexPath);
19289
19828
  try {
19290
19829
  let index;
19291
19830
  try {
19292
- index = readFileSync2(indexPath, "utf-8");
19831
+ index = readFileSync3(indexPath, "utf-8");
19293
19832
  } catch {
19294
19833
  index = "";
19295
19834
  }
19296
19835
  const lines = index.split(`
19297
19836
  `);
19298
- const link = (() => {
19299
- switch (kind) {
19300
- case "summary":
19301
- return `- [[summaries/${slug}]] ${desc} (${dateStr})`;
19302
- case "topic":
19303
- return `- [[topic/${slug}]] ${desc} (${dateStr})`;
19304
- case "skill":
19305
- return `- [[skills/${slug}]] ${desc} (${dateStr})`;
19306
- default:
19307
- return `- [[articles/${slug}]] ${desc} (${dateStr})`;
19308
- }
19309
- })();
19310
- const target = (() => {
19311
- switch (kind) {
19312
- case "summary":
19313
- return `summaries/${slug}`;
19314
- case "topic":
19315
- return `topic/${slug}`;
19316
- case "skill":
19317
- return `skills/${slug}`;
19318
- default:
19319
- return `articles/${slug}`;
19320
- }
19321
- })();
19837
+ const target = `${WIKI_INDEX_NAMESPACES[kind]}/${slug}`;
19838
+ const link = `- [[${target}]] ${description} (${date})`;
19322
19839
  const slugPattern = `[[${target}]]`;
19323
19840
  let replaced = false;
19324
19841
  for (let i = lines.length - 1;i >= 0; i--) {
@@ -19356,11 +19873,63 @@ function indexUpsert(args) {
19356
19873
  }
19357
19874
  atomicWriteFile(indexPath, lines.join(`
19358
19875
  `));
19359
- return { content: [{ type: "text", text: `Index updated: ${link}` }] };
19876
+ return link;
19360
19877
  } finally {
19361
19878
  releaseLock();
19362
19879
  }
19363
19880
  }
19881
+ function wikiDocumentExists(kind, slug) {
19882
+ const root = kind === "article" ? runtime().articlesDir : kind === "summary" ? runtime().summariesDir : kind === "topic" ? runtime().topicsDir : runtime().skillsDir;
19883
+ const filePath = resolve2(root, `${slug}.md`);
19884
+ if (relative(root, filePath).startsWith(".."))
19885
+ return false;
19886
+ try {
19887
+ return statSync(filePath).isFile();
19888
+ } catch {
19889
+ return false;
19890
+ }
19891
+ }
19892
+ function indexUpsert(args) {
19893
+ const rawSlug = safeExt(args, ["slug", "id"], "");
19894
+ const rawDescription = safeExt(args, ["description", "desc", "text"], "");
19895
+ const rawKind = safeExt(args, ["kind", "type"], "article");
19896
+ const section = safeExt(args, ["section", "category"], undefined);
19897
+ const date = safeExt(args, ["date", "created"], undefined);
19898
+ if (!(rawKind in WIKI_INDEX_FILENAMES)) {
19899
+ return {
19900
+ content: [{ type: "text", text: `Unsupported kind: ${rawKind}` }],
19901
+ isError: true
19902
+ };
19903
+ }
19904
+ const kind = rawKind;
19905
+ const slugResult = normalizeWikiSlug(rawSlug);
19906
+ if ("error" in slugResult) {
19907
+ return { content: [{ type: "text", text: slugResult.error }], isError: true };
19908
+ }
19909
+ const descriptionResult = normalizeIndexDescription(rawDescription);
19910
+ if ("error" in descriptionResult) {
19911
+ return { content: [{ type: "text", text: descriptionResult.error }], isError: true };
19912
+ }
19913
+ if (kind !== "topic" && !wikiDocumentExists(kind, slugResult.slug)) {
19914
+ return {
19915
+ content: [
19916
+ {
19917
+ type: "text",
19918
+ text: `No ${kind} document named ${slugResult.slug}. Use wiki_write to create the document and its catalog row together.`
19919
+ }
19920
+ ],
19921
+ isError: true
19922
+ };
19923
+ }
19924
+ const link = upsertIndexRow({
19925
+ kind,
19926
+ slug: slugResult.slug,
19927
+ description: descriptionResult.description,
19928
+ date: date ?? new Date().toISOString().slice(0, 10),
19929
+ section
19930
+ });
19931
+ return { content: [{ type: "text", text: `Index updated: ${link}` }] };
19932
+ }
19364
19933
  var WIKI_TOOLS = [
19365
19934
  {
19366
19935
  name: "wiki_query",
@@ -19442,35 +20011,46 @@ var WIKI_TOOLS = [
19442
20011
  }
19443
20012
  },
19444
20013
  {
19445
- name: "save_wiki_entry",
19446
- description: "Save a session summary directly to wiki/summaries/.",
20014
+ name: "wiki_write",
20015
+ 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.",
19447
20016
  inputSchema: {
19448
20017
  type: "object",
19449
20018
  properties: {
19450
- topic: { type: "string", description: "Topic name of the session" },
19451
- content: { type: "string", description: "Session summary in markdown" }
20019
+ kind: {
20020
+ type: "string",
20021
+ enum: ["summary", "article", "topic"],
20022
+ description: "Which document to write"
20023
+ },
20024
+ content: { type: "string", description: "Document body in markdown" },
20025
+ description: {
20026
+ type: "string",
20027
+ description: "Single-line catalog description; required so the index stays useful"
20028
+ },
20029
+ topic: {
20030
+ type: "string",
20031
+ description: "Topic name; required for kind=summary and kind=topic"
20032
+ },
20033
+ slug: {
20034
+ type: "string",
20035
+ description: "Article slug in kebab-case; required for kind=article"
20036
+ },
20037
+ section: {
20038
+ type: "string",
20039
+ description: "H2 catalog section; required for kind=article"
20040
+ },
20041
+ date: { type: "string", description: "Override entry date YYYY-MM-DD (default: today)" }
19452
20042
  },
19453
- required: ["topic", "content"]
20043
+ required: ["kind", "content", "description"]
19454
20044
  }
19455
20045
  },
19456
20046
  {
19457
- name: "save_topic_brief",
19458
- 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).",
19459
- inputSchema: {
19460
- type: "object",
19461
- properties: {
19462
- topic: { type: "string", description: "Topic name of the session" },
19463
- content: {
19464
- type: "string",
19465
- description: "Full accumulated topic brief in markdown (persona layer + recent work)"
19466
- }
19467
- },
19468
- required: ["topic", "content"]
19469
- }
20047
+ name: "wiki_reindex",
20048
+ 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.",
20049
+ inputSchema: { type: "object", properties: {} }
19470
20050
  },
19471
20051
  {
19472
20052
  name: "index_upsert",
19473
- description: "Upsert an entry in wiki/article-index.md, topic-index.md, or skill-index.md.",
20053
+ description: "Correct the description or section of an entry that already exists. Cannot create an entry \u2014 use wiki_write for that.",
19474
20054
  inputSchema: {
19475
20055
  type: "object",
19476
20056
  properties: {
@@ -19574,8 +20154,8 @@ function createWikiMcpServer(context, host) {
19574
20154
  wiki_last_conversation: wikiLastConversation,
19575
20155
  skill_query: skillQuery,
19576
20156
  skill_save: skillSave,
19577
- save_wiki_entry: saveWikiEntry,
19578
- save_topic_brief: saveTopicBrief,
20157
+ wiki_write: wikiWrite,
20158
+ wiki_reindex: wikiReindex,
19579
20159
  index_upsert: indexUpsert
19580
20160
  };
19581
20161
  const handler = tools.some((tool) => tool.name === name) ? handlers[name] : undefined;
@@ -19593,8 +20173,8 @@ init_jsonl();
19593
20173
  init_mcp_helpers();
19594
20174
  await init_codex_registry();
19595
20175
  import { spawn as spawn5 } from "child_process";
19596
- import { existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
19597
- import { join as join10 } from "path";
20176
+ import { existsSync as existsSync10, readdirSync as readdirSync3 } from "fs";
20177
+ import { join as join11 } from "path";
19598
20178
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
19599
20179
  import { z } from "zod";
19600
20180
  function signalProcessTree(child, signal) {
@@ -19610,7 +20190,7 @@ function signalProcessTree(child, signal) {
19610
20190
  }
19611
20191
  }
19612
20192
  function spawnCapture(cmd, args, timeoutMs, signal, env) {
19613
- return new Promise((resolve7, reject) => {
20193
+ return new Promise((resolve8, reject) => {
19614
20194
  const child = spawn5(cmd, args, {
19615
20195
  env: env ?? process.env,
19616
20196
  stdio: ["ignore", "pipe", "pipe"],
@@ -19668,7 +20248,7 @@ function spawnCapture(cmd, args, timeoutMs, signal, env) {
19668
20248
  if (killTimer)
19669
20249
  clearTimeout(killTimer);
19670
20250
  if (code === 0)
19671
- resolve7({ stdout, stderr });
20251
+ resolve8({ stdout, stderr });
19672
20252
  else
19673
20253
  reject(new Error(stderr.trim() || `${cmd} exited with ${code ?? exitSignal}`));
19674
20254
  });
@@ -19746,11 +20326,11 @@ async function checkMaestro(model, timeoutMs, signal) {
19746
20326
  const remaining = deadline - Date.now();
19747
20327
  if (remaining <= 0)
19748
20328
  throw new Error("timeout");
19749
- const eventResult = await new Promise((resolve7, reject) => {
20329
+ const eventResult = await new Promise((resolve8, reject) => {
19750
20330
  const timeout = setTimeout(() => reject(new Error("timeout")), remaining);
19751
20331
  const onAbort = () => reject(new Error("aborted"));
19752
20332
  signal.addEventListener("abort", onAbort, { once: true });
19753
- activeIterator.next().then(resolve7, reject).finally(() => {
20333
+ activeIterator.next().then(resolve8, reject).finally(() => {
19754
20334
  clearTimeout(timeout);
19755
20335
  signal.removeEventListener("abort", onAbort);
19756
20336
  });
@@ -19789,7 +20369,7 @@ var checkWaiters = [];
19789
20369
  var activeCheckAllUsers = new Set;
19790
20370
  async function withCheckSlot(operation) {
19791
20371
  if (activeChecks >= MAX_CONCURRENT_CHECKS) {
19792
- await new Promise((resolve7) => checkWaiters.push(resolve7));
20372
+ await new Promise((resolve8) => checkWaiters.push(resolve8));
19793
20373
  }
19794
20374
  activeChecks += 1;
19795
20375
  try {
@@ -19847,8 +20427,8 @@ ${results.every((result) => result.ok) ? "All agents healthy" : "Some agents fai
19847
20427
  server.tool("list_active_queries", "\uD604\uC7AC \uC0AC\uC6A9\uC790 \uBC94\uC704\uC5D0\uC11C \uC2E4\uD589 \uC911\uC778 \uD1A0\uD53D \uCFFC\uB9AC \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4.", {}, async () => {
19848
20428
  if (!context.userId)
19849
20429
  return mcpOk("\uC2E4\uD589 \uC911\uC778 \uCFFC\uB9AC \uC870\uD68C \uBD88\uAC00 (user context \uC5C6\uC74C)");
19850
- const stateDir = join10(USERS_LOG_DIR, context.userId, "active-queries");
19851
- if (!existsSync9(stateDir))
20430
+ const stateDir = join11(USERS_LOG_DIR, context.userId, "active-queries");
20431
+ if (!existsSync10(stateDir))
19852
20432
  return mcpOk("\uC2E4\uD589 \uC911\uC778 \uCFFC\uB9AC \uC5C6\uC74C");
19853
20433
  let files;
19854
20434
  try {
@@ -19860,7 +20440,7 @@ ${results.every((result) => result.ok) ? "All agents healthy" : "Some agents fai
19860
20440
  const entries = files.flatMap((file) => {
19861
20441
  if (!file.endsWith(".json"))
19862
20442
  return [];
19863
- const state = readJsonFile(join10(stateDir, file));
20443
+ const state = readJsonFile(join11(stateDir, file));
19864
20444
  if (!state)
19865
20445
  return [];
19866
20446
  const sinceMs = new Date(state.since).getTime();
@@ -19885,7 +20465,7 @@ ${lines.join(`
19885
20465
  }
19886
20466
  // ../../packages/core/src/mcp/factories/compaction-log.ts
19887
20467
  init_mcp_helpers();
19888
- import { readFileSync as readFileSync8, realpathSync as realpathSync3 } from "fs";
20468
+ import { readFileSync as readFileSync9, realpathSync as realpathSync3 } from "fs";
19889
20469
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
19890
20470
  import { z as z2 } from "zod";
19891
20471
  var DEFAULT_MAX_CALLS = 12;
@@ -19918,7 +20498,7 @@ function createCompactionLogMcpServer(context) {
19918
20498
  if (!context.filePath)
19919
20499
  throw new Error("compaction-log MCP requires filePath");
19920
20500
  const filePath = realpathSync3(context.filePath);
19921
- const lines = readFileSync8(filePath, "utf8").split(`
20501
+ const lines = readFileSync9(filePath, "utf8").split(`
19922
20502
  `);
19923
20503
  const maxCalls = positiveInt(context.maxCalls, DEFAULT_MAX_CALLS);
19924
20504
  const maxTotalBytes = positiveInt(context.maxTotalBytes, DEFAULT_MAX_TOTAL_BYTES);
@@ -20511,4 +21091,4 @@ export {
20511
21091
  createAgentHealthMcpServer
20512
21092
  };
20513
21093
 
20514
- //# debugId=AE60D4AF576C043E64756E2164756E21
21094
+ //# debugId=B2D00F1A1BBBDD6C64756E2164756E21