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
package/dist/main.js CHANGED
@@ -152,7 +152,7 @@ function browserRsMeetsMinimumVersion(candidate) {
152
152
  try {
153
153
  const output = execFileSync(candidate, ["--version"], {
154
154
  encoding: "utf8",
155
- timeout: 2000,
155
+ timeout: BROWSER_RS_VERSION_PROBE_TIMEOUT_MS,
156
156
  stdio: ["ignore", "pipe", "ignore"]
157
157
  }).trim();
158
158
  const match = output.match(/^browser-rs (\d+)\.(\d+)\.(\d+)$/);
@@ -263,7 +263,7 @@ function resolveDefaultModel(agent, registryDefaultModel) {
263
263
  function codexAuthFilePath() {
264
264
  return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(process.env.CODEX_HOME || join(homedir(), ".codex"), "auth.json");
265
265
  }
266
- 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;
266
+ 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;
267
267
  var init_config = __esm(() => {
268
268
  init_config_helpers();
269
269
  init_logger();
@@ -421,6 +421,225 @@ var init_sqlite = __esm(async () => {
421
421
  }
422
422
  });
423
423
 
424
+ // ../../packages/core/src/storage/storage-host.ts
425
+ import { mkdirSync as mkdirSync2 } from "fs";
426
+ import { homedir as homedir2 } from "os";
427
+ import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
428
+ function storageState() {
429
+ const holder = globalThis;
430
+ const existing = holder[STORAGE_HOST_STATE];
431
+ if (existing)
432
+ return existing;
433
+ const created = {
434
+ configuredHost: {},
435
+ fallbackDatabase: null,
436
+ fallbackDatabasePath: null,
437
+ frames: [],
438
+ schemaInitializers: [],
439
+ initializedSchemas: new WeakMap,
440
+ initializingDatabases: new WeakSet
441
+ };
442
+ Object.defineProperty(holder, STORAGE_HOST_STATE, {
443
+ value: created,
444
+ enumerable: false,
445
+ writable: false,
446
+ configurable: false
447
+ });
448
+ return created;
449
+ }
450
+ function envPath(name, fallback) {
451
+ const value = process.env[name]?.trim();
452
+ return resolve2(value || fallback);
453
+ }
454
+ function defaultStateDir() {
455
+ return envPath("NEGOTIUM_STATE_DIR", join2(homedir2(), ".negotium"));
456
+ }
457
+ function defaultDataDir() {
458
+ return envPath("NEGOTIUM_DATA_DIR", join2(defaultStateDir(), "data"));
459
+ }
460
+ function defaultLogDir() {
461
+ return envPath("NEGOTIUM_LOG_DIR", join2(defaultStateDir(), "logs"));
462
+ }
463
+ function defaultWorkspaceDir() {
464
+ return envPath("NEGOTIUM_WORKSPACE_DIR", join2(defaultStateDir(), "workspace"));
465
+ }
466
+ function defaultSessionAsksDir() {
467
+ const runDir = envPath("NEGOTIUM_RUN_DIR", join2(defaultStateDir(), "runtime"));
468
+ return join2(runDir, "session-asks");
469
+ }
470
+ function defaultSessionsDatabasePath() {
471
+ return envPath("SESSIONS_DB_PATH", join2(resolveStorageDataDir(), "sessions.db"));
472
+ }
473
+ function isSqliteBusy(error) {
474
+ const message = error instanceof Error ? error.message : String(error);
475
+ return /database is (?:locked|busy)|SQLITE_(?:BUSY|LOCKED)/i.test(message);
476
+ }
477
+ function execWithBusyRetry(database, sql, timeoutMs = SQLITE_INIT_TIMEOUT_MS) {
478
+ const deadline = Date.now() + timeoutMs;
479
+ while (true) {
480
+ try {
481
+ database.exec(sql);
482
+ return;
483
+ } catch (error) {
484
+ if (!isSqliteBusy(error) || Date.now() >= deadline)
485
+ throw error;
486
+ Atomics.wait(SQLITE_INIT_SLEEP, 0, 0, Math.min(SQLITE_INIT_RETRY_MS, deadline - Date.now()));
487
+ }
488
+ }
489
+ }
490
+ function initializeDatabase(database) {
491
+ database.exec("PRAGMA busy_timeout = 5000");
492
+ execWithBusyRetry(database, "PRAGMA journal_mode = WAL");
493
+ database.exec("PRAGMA foreign_keys = ON");
494
+ database.exec("PRAGMA wal_autocheckpoint = 1000");
495
+ try {
496
+ database.exec("PRAGMA wal_checkpoint(TRUNCATE)");
497
+ } catch {}
498
+ }
499
+ function defaultDatabase() {
500
+ const path = defaultSessionsDatabasePath();
501
+ const state = storageState();
502
+ if (state.fallbackDatabase && state.fallbackDatabasePath === path)
503
+ return state.fallbackDatabase;
504
+ if (state.fallbackDatabase)
505
+ state.fallbackDatabase.close();
506
+ mkdirSync2(dirname2(path), { recursive: true });
507
+ state.fallbackDatabase = new Database(path, { create: true });
508
+ state.fallbackDatabasePath = path;
509
+ initializeDatabase(state.fallbackDatabase);
510
+ return state.fallbackDatabase;
511
+ }
512
+ function resolveStorageDatabase() {
513
+ return storageState().configuredHost.database ?? defaultDatabase();
514
+ }
515
+ function resolveStorageDataDir() {
516
+ return storageState().configuredHost.dataDir ?? defaultDataDir();
517
+ }
518
+ function resolveStorageLogDir() {
519
+ return storageState().configuredHost.logDir ?? defaultLogDir();
520
+ }
521
+ function resolveStorageSessionAsksDir() {
522
+ return storageState().configuredHost.sessionAsksDir ?? defaultSessionAsksDir();
523
+ }
524
+ function resolveStorageWorkspaceDir() {
525
+ return storageState().configuredHost.workspaceDir ?? defaultWorkspaceDir();
526
+ }
527
+ function resolveStorageSharedWikiDir() {
528
+ return storageState().configuredHost.sharedWikiDir ?? join2(resolveStorageWorkspaceDir(), "wiki");
529
+ }
530
+ function resolveStorageUsersLogDir() {
531
+ return storageState().configuredHost.usersLogDir ?? join2(resolveStorageDataDir(), "users");
532
+ }
533
+ function normalizeStorageHostPatch(options) {
534
+ const patch = {};
535
+ if (options.database !== undefined) {
536
+ options.database.exec("PRAGMA foreign_keys = ON");
537
+ patch.database = options.database;
538
+ }
539
+ for (const key of STORAGE_PATH_KEYS) {
540
+ const value = options[key];
541
+ if (value === undefined)
542
+ continue;
543
+ if (!value.trim())
544
+ throw new TypeError(`${key} must not be empty`);
545
+ patch[key] = resolve2(value);
546
+ }
547
+ return Object.freeze(patch);
548
+ }
549
+ function refreshConfiguredHost() {
550
+ const state = storageState();
551
+ state.configuredHost = Object.freeze(Object.assign({}, ...state.frames.filter((frame) => frame.active).map((frame) => frame.patch)));
552
+ }
553
+ function configureStorageHost(options) {
554
+ const frame = { active: true, patch: normalizeStorageHostPatch(options) };
555
+ storageState().frames.push(frame);
556
+ refreshConfiguredHost();
557
+ return () => {
558
+ if (!frame.active)
559
+ return;
560
+ frame.active = false;
561
+ const frames = storageState().frames;
562
+ const index = frames.indexOf(frame);
563
+ if (index >= 0)
564
+ frames.splice(index, 1);
565
+ refreshConfiguredHost();
566
+ };
567
+ }
568
+ function resetStorageHost() {
569
+ const frames = storageState().frames;
570
+ for (const frame of frames)
571
+ frame.active = false;
572
+ frames.length = 0;
573
+ refreshConfiguredHost();
574
+ }
575
+ function closeStorageDatabase() {
576
+ const state = storageState();
577
+ if (!state.fallbackDatabase)
578
+ return;
579
+ state.fallbackDatabase.close();
580
+ state.fallbackDatabase = null;
581
+ state.fallbackDatabasePath = null;
582
+ }
583
+ function registerStorageSchemaInitializer(initialize, priority = 100) {
584
+ const initializers = storageState().schemaInitializers;
585
+ initializers.push({ initialize, priority });
586
+ initializers.sort((a, b) => a.priority - b.priority);
587
+ }
588
+ function ensureStorageSchemas(database = resolveStorageDatabase()) {
589
+ const state = storageState();
590
+ if (state.initializingDatabases.has(database))
591
+ return;
592
+ let initialized = state.initializedSchemas.get(database);
593
+ if (!initialized) {
594
+ initialized = new Set;
595
+ state.initializedSchemas.set(database, initialized);
596
+ }
597
+ state.initializingDatabases.add(database);
598
+ try {
599
+ for (const entry of state.schemaInitializers) {
600
+ if (initialized.has(entry.initialize))
601
+ continue;
602
+ initialized.add(entry.initialize);
603
+ try {
604
+ entry.initialize(database);
605
+ } catch (error) {
606
+ initialized.delete(entry.initialize);
607
+ throw error;
608
+ }
609
+ }
610
+ } finally {
611
+ state.initializingDatabases.delete(database);
612
+ }
613
+ }
614
+ var STORAGE_HOST_STATE, SQLITE_INIT_RETRY_MS = 25, SQLITE_INIT_TIMEOUT_MS = 5000, SQLITE_INIT_SLEEP, STORAGE_PATH_KEYS, internalStorageDatabase, storageDatabase;
615
+ var init_storage_host = __esm(async () => {
616
+ await init_sqlite();
617
+ STORAGE_HOST_STATE = Symbol.for("negotium.storage-host.state.v1");
618
+ SQLITE_INIT_SLEEP = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
619
+ STORAGE_PATH_KEYS = [
620
+ "dataDir",
621
+ "logDir",
622
+ "sessionAsksDir",
623
+ "workspaceDir",
624
+ "sharedWikiDir",
625
+ "usersLogDir"
626
+ ];
627
+ internalStorageDatabase = new Proxy({}, {
628
+ get(_target, property) {
629
+ const database = resolveStorageDatabase();
630
+ ensureStorageSchemas(database);
631
+ const value = Reflect.get(database, property, database);
632
+ return typeof value === "function" ? value.bind(database) : value;
633
+ },
634
+ set(_target, property, value) {
635
+ const database = resolveStorageDatabase();
636
+ ensureStorageSchemas(database);
637
+ return Reflect.set(database, property, value, database);
638
+ }
639
+ });
640
+ storageDatabase = internalStorageDatabase;
641
+ });
642
+
424
643
  // ../../packages/core/src/storage/vault-crypto-core.ts
425
644
  import { createCipheriv, createDecipheriv, createHash, randomBytes as randomBytes2 } from "crypto";
426
645
  function encryptionKey(masterKey) {
@@ -477,8 +696,8 @@ var init_vault_crypto = __esm(() => {
477
696
  });
478
697
 
479
698
  // ../../packages/core/src/storage/vault.ts
480
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync2 } from "fs";
481
- import { join as join2 } from "path";
699
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync3 } from "fs";
700
+ import { join as join3 } from "path";
482
701
  function initializeVaultDatabase(database) {
483
702
  database.exec("PRAGMA journal_mode = WAL");
484
703
  database.exec("PRAGMA busy_timeout = 5000");
@@ -513,17 +732,18 @@ function initializeVaultDatabase(database) {
513
732
  }
514
733
  }
515
734
  function openVaultDatabase(dataDir) {
516
- const vaultDir = join2(dataDir, "vault");
517
- const path = join2(vaultDir, "vault.db");
518
- mkdirSync2(vaultDir, { recursive: true, mode: 448 });
735
+ const vaultDir = join3(dataDir, "vault");
736
+ const path = join3(vaultDir, "vault.db");
737
+ mkdirSync3(vaultDir, { recursive: true, mode: 448 });
519
738
  const database = new Database(path, { create: true });
520
739
  chmodSync2(path, 384);
521
740
  initializeVaultDatabase(database);
522
741
  return database;
523
742
  }
524
743
  function activeVaultDatabase() {
525
- if (!vaultDb)
526
- vaultDb = openVaultDatabase(DATA_DIR);
744
+ if (!vaultDb) {
745
+ vaultDb = openVaultDatabase(resolveStorageDataDir());
746
+ }
527
747
  return vaultDb;
528
748
  }
529
749
  function configureVaultStorage(options) {
@@ -532,7 +752,7 @@ function configureVaultStorage(options) {
532
752
  }
533
753
  const previousDb = vaultDb;
534
754
  const previousMasterKey = vaultMasterKey;
535
- const configuredDb = options.database ?? openVaultDatabase(options.dataDir ?? DATA_DIR);
755
+ const configuredDb = options.database ?? openVaultDatabase(options.dataDir ?? resolveStorageDataDir());
536
756
  if (options.database)
537
757
  initializeVaultDatabase(configuredDb);
538
758
  vaultDb = configuredDb;
@@ -687,7 +907,10 @@ var vaultDb, vaultMasterKey, VAULT_KEY_PATTERN, VAULT_VALUE_MIN_BYTES = 4, VAULT
687
907
  var init_vault = __esm(async () => {
688
908
  init_config();
689
909
  init_vault_crypto();
690
- await init_sqlite();
910
+ await __promiseAll([
911
+ init_sqlite(),
912
+ init_storage_host()
913
+ ]);
691
914
  vaultMasterKey = VAULT_MASTER_KEY;
692
915
  VAULT_KEY_PATTERN = /^[A-Z][A-Z0-9_]{0,127}$/;
693
916
  VAULT_VALUE_MAX_BYTES = 64 * 1024;
@@ -701,8 +924,8 @@ __export(exports_auth_check, {
701
924
  });
702
925
  import { execFileSync as execFileSync2 } from "child_process";
703
926
  import { existsSync as existsSync2 } from "fs";
704
- import { homedir as homedir2, platform } from "os";
705
- import { join as join3 } from "path";
927
+ import { homedir as homedir3, platform } from "os";
928
+ import { join as join4 } from "path";
706
929
  function hasMaestroCredential(host, key, userId) {
707
930
  if (userId && host.getVaultValue(userId, key)?.trim())
708
931
  return true;
@@ -722,7 +945,7 @@ function checkAgentAuth(agent, host = defaultAgentAuthHost, userId) {
722
945
  case "claude": {
723
946
  if (host.environment.ANTHROPIC_API_KEY)
724
947
  return { ok: true };
725
- const path = join3(host.homeDirectory(), ".claude", ".credentials.json");
948
+ const path = join4(host.homeDirectory(), ".claude", ".credentials.json");
726
949
  if (host.operatingSystem() === "darwin") {
727
950
  if (host.hasMacOsCredential("Claude Code-credentials") || host.exists(path)) {
728
951
  return { ok: true };
@@ -775,7 +998,7 @@ var init_auth_check = __esm(async () => {
775
998
  codexAuthFilePath,
776
999
  exists: existsSync2,
777
1000
  environment: process.env,
778
- homeDirectory: homedir2,
1001
+ homeDirectory: homedir3,
779
1002
  operatingSystem: platform,
780
1003
  hasMacOsCredential(service) {
781
1004
  try {
@@ -842,12 +1065,12 @@ function createCodexTreeManager(host, options = {}) {
842
1065
  const previous = spawnChain;
843
1066
  let release;
844
1067
  let released = false;
845
- spawnChain = new Promise((resolve2) => {
1068
+ spawnChain = new Promise((resolve3) => {
846
1069
  release = () => {
847
1070
  if (released)
848
1071
  return;
849
1072
  released = true;
850
- resolve2();
1073
+ resolve3();
851
1074
  };
852
1075
  });
853
1076
  await previous;
@@ -954,7 +1177,7 @@ function createCodexTreeManager(host, options = {}) {
954
1177
  return;
955
1178
  const deadline = Date.now() + Math.max(0, graceMs);
956
1179
  while (Date.now() < deadline && hasSurvivors(plan.targets)) {
957
- await new Promise((resolve2) => setTimeout(resolve2, Math.min(100, Math.max(1, deadline - Date.now()))));
1180
+ await new Promise((resolve3) => setTimeout(resolve3, Math.min(100, Math.max(1, deadline - Date.now()))));
958
1181
  }
959
1182
  killStragglers(plan);
960
1183
  }
@@ -1102,8 +1325,8 @@ function renderUserTurnBatch(messages) {
1102
1325
  }
1103
1326
 
1104
1327
  // ../../packages/core/src/agents/rollout/shared.ts
1105
- import { mkdirSync as mkdirSync3 } from "fs";
1106
- import { resolve as resolve2 } from "path";
1328
+ import { mkdirSync as mkdirSync4 } from "fs";
1329
+ import { resolve as resolve3 } from "path";
1107
1330
  function clone(obj) {
1108
1331
  return structuredClone(obj);
1109
1332
  }
@@ -1113,7 +1336,7 @@ function assertUuidLike(label, value) {
1113
1336
  }
1114
1337
  }
1115
1338
  function assertCwdInWorkspace(cwd) {
1116
- const abs = resolve2(cwd);
1339
+ const abs = resolve3(cwd);
1117
1340
  const ok = trustedWorkspaceRoots.some((root) => abs === root || abs.startsWith(`${root}/`));
1118
1341
  if (!ok) {
1119
1342
  throw new Error(`rollout: cwd outside trusted workspace roots: ${cwd} (resolved=${abs})`);
@@ -1122,7 +1345,7 @@ function assertCwdInWorkspace(cwd) {
1122
1345
  function ensureCwdExists(cwd) {
1123
1346
  assertCwdInWorkspace(cwd);
1124
1347
  try {
1125
- mkdirSync3(cwd, { recursive: true });
1348
+ mkdirSync4(cwd, { recursive: true });
1126
1349
  } catch (err) {
1127
1350
  logger.warn({ err, cwd }, "rollout: ensureCwdExists failed \u2014 caller should pre-create cwd");
1128
1351
  }
@@ -1243,8 +1466,8 @@ var init_shared = __esm(() => {
1243
1466
  DM_WORKSPACE_DIR,
1244
1467
  SESSION_WORKSPACE_DIR,
1245
1468
  TOPIC_WORKSPACE_DIR
1246
- ].map((root) => resolve2(root));
1247
- FIXTURES_DIR = resolve2(PROJECT_ROOT, "src", "agents", "fixtures");
1469
+ ].map((root) => resolve3(root));
1470
+ FIXTURES_DIR = resolve3(PROJECT_ROOT, "src", "agents", "fixtures");
1248
1471
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1249
1472
  });
1250
1473
 
@@ -1279,7 +1502,7 @@ import {
1279
1502
  appendFileSync,
1280
1503
  closeSync,
1281
1504
  fsyncSync,
1282
- mkdirSync as mkdirSync4,
1505
+ mkdirSync as mkdirSync5,
1283
1506
  openSync,
1284
1507
  readFileSync as readFileSync2,
1285
1508
  renameSync,
@@ -1287,7 +1510,7 @@ import {
1287
1510
  unlinkSync as unlinkSync2,
1288
1511
  writeFileSync as writeFileSync2
1289
1512
  } from "fs";
1290
- import { dirname as dirname2 } from "path";
1513
+ import { dirname as dirname3 } from "path";
1291
1514
  function readJsonlLines(filePath) {
1292
1515
  return readFileSync2(filePath, "utf-8").trim().split(`
1293
1516
  `).filter(Boolean);
@@ -1335,7 +1558,7 @@ function appendJsonlEntry(filePath, entry) {
1335
1558
  `);
1336
1559
  }
1337
1560
  function appendJsonlLine(filePath, line) {
1338
- mkdirSync4(dirname2(filePath), { recursive: true });
1561
+ mkdirSync5(dirname3(filePath), { recursive: true });
1339
1562
  const lockPath = `${filePath}${LOCK_SUFFIX}`;
1340
1563
  const payload = line.endsWith(`
1341
1564
  `) ? line : `${line}
@@ -1366,8 +1589,8 @@ function appendJsonlLine(filePath, line) {
1366
1589
  }
1367
1590
  }
1368
1591
  function writeJsonlFile(filePath, entries) {
1369
- const dir = dirname2(filePath);
1370
- mkdirSync4(dir, { recursive: true });
1592
+ const dir = dirname3(filePath);
1593
+ mkdirSync5(dir, { recursive: true });
1371
1594
  const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
1372
1595
  const payload = `${entries.map((e) => JSON.stringify(e)).join(`
1373
1596
  `)}
@@ -1425,12 +1648,12 @@ var init_jsonl = __esm(() => {
1425
1648
  // ../../packages/core/src/agents/rollout/claude.ts
1426
1649
  import { randomUUID } from "crypto";
1427
1650
  import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
1428
- import { homedir as homedir3 } from "os";
1429
- import { join as join4 } from "path";
1651
+ import { homedir as homedir4 } from "os";
1652
+ import { join as join5 } from "path";
1430
1653
  function loadClaudeAttachments() {
1431
1654
  if (_attachmentsCache)
1432
1655
  return _attachmentsCache;
1433
- const raw = readFileSync3(join4(FIXTURES_DIR, "claude-attachments.jsonl"), "utf8");
1656
+ const raw = readFileSync3(join5(FIXTURES_DIR, "claude-attachments.jsonl"), "utf8");
1434
1657
  const lines = parseJsonlText(raw);
1435
1658
  if (lines.length < 2) {
1436
1659
  throw new Error(`loadClaudeAttachments: expected >=2 entries in claude-attachments.jsonl, got ${lines.length}`);
@@ -1531,8 +1754,8 @@ function writeClaudeRollout(opts) {
1531
1754
  });
1532
1755
  lastUuid = assistantUuid;
1533
1756
  }
1534
- const projectsDir = join4(homedir3(), ".claude", "projects", encodeClaudeCwd(opts.cwd));
1535
- const path = join4(projectsDir, `${sessionId}.jsonl`);
1757
+ const projectsDir = join5(homedir4(), ".claude", "projects", encodeClaudeCwd(opts.cwd));
1758
+ const path = join5(projectsDir, `${sessionId}.jsonl`);
1536
1759
  writeJsonlFile(path, lines);
1537
1760
  logger.info({ sessionId, path, pairs: pairs.length }, "writeClaudeRollout: synthetic rollout placed");
1538
1761
  return { sessionId, rolloutPath: path };
@@ -1545,9 +1768,9 @@ var init_claude = __esm(() => {
1545
1768
  });
1546
1769
 
1547
1770
  // ../../packages/core/src/agents/claude-registry.ts
1548
- import { existsSync as existsSync4, mkdirSync as mkdirSync5, readdirSync, renameSync as renameSync2, unlinkSync as unlinkSync3 } from "fs";
1549
- import { homedir as homedir4 } from "os";
1550
- import { join as join5 } from "path";
1771
+ import { existsSync as existsSync4, mkdirSync as mkdirSync6, readdirSync, renameSync as renameSync2, unlinkSync as unlinkSync3 } from "fs";
1772
+ import { homedir as homedir5 } from "os";
1773
+ import { join as join6 } from "path";
1551
1774
  var ALIAS_MAP, VALID_ALIASES, VALID_EFFORTS, claudeRegistry, claudeRegistryOperations;
1552
1775
  var init_claude_registry = __esm(() => {
1553
1776
  init_claude();
@@ -1594,15 +1817,15 @@ var init_claude_registry = __esm(() => {
1594
1817
  const result = await forkSession(parentSessionId, {
1595
1818
  ...title ? { title } : {}
1596
1819
  });
1597
- const projectsRoot = join5(homedir4(), ".claude", "projects");
1598
- const destDir = join5(projectsRoot, encodeClaudeCwd(cwd));
1599
- const destPath = join5(destDir, `${result.sessionId}.jsonl`);
1820
+ const projectsRoot = join6(homedir5(), ".claude", "projects");
1821
+ const destDir = join6(projectsRoot, encodeClaudeCwd(cwd));
1822
+ const destPath = join6(destDir, `${result.sessionId}.jsonl`);
1600
1823
  if (!existsSync4(destPath)) {
1601
- const sourcePath = readdirSync(projectsRoot).map((d) => join5(projectsRoot, d, `${result.sessionId}.jsonl`)).find((p) => existsSync4(p));
1824
+ const sourcePath = readdirSync(projectsRoot).map((d) => join6(projectsRoot, d, `${result.sessionId}.jsonl`)).find((p) => existsSync4(p));
1602
1825
  if (!sourcePath) {
1603
1826
  throw new Error(`claude forkSession: fork rollout ${result.sessionId}.jsonl not found under ${projectsRoot}`);
1604
1827
  }
1605
- mkdirSync5(destDir, { recursive: true });
1828
+ mkdirSync6(destDir, { recursive: true });
1606
1829
  renameSync2(sourcePath, destPath);
1607
1830
  }
1608
1831
  return {
@@ -1611,10 +1834,10 @@ var init_claude_registry = __esm(() => {
1611
1834
  };
1612
1835
  },
1613
1836
  async cleanupRollouts({ cwd, sessionIds }) {
1614
- const projectsDir = join5(homedir4(), ".claude", "projects", encodeClaudeCwd(cwd));
1837
+ const projectsDir = join6(homedir5(), ".claude", "projects", encodeClaudeCwd(cwd));
1615
1838
  const failures = [];
1616
1839
  for (const sid of sessionIds) {
1617
- const path = join5(projectsDir, `${sid}.jsonl`);
1840
+ const path = join6(projectsDir, `${sid}.jsonl`);
1618
1841
  try {
1619
1842
  unlinkSync3(path);
1620
1843
  } catch (e) {
@@ -1636,7 +1859,7 @@ var exports_version = {};
1636
1859
  __export(exports_version, {
1637
1860
  NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
1638
1861
  });
1639
- var NEGOTIUM_VERSION = "0.2.18";
1862
+ var NEGOTIUM_VERSION = "0.2.20";
1640
1863
 
1641
1864
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
1642
1865
  import { spawn } from "child_process";
@@ -1654,7 +1877,7 @@ import {
1654
1877
  } from "fs";
1655
1878
  import { createRequire as createRequire2 } from "module";
1656
1879
  import { tmpdir } from "os";
1657
- import { dirname as dirname3, join as join6 } from "path";
1880
+ import { dirname as dirname4, join as join7 } from "path";
1658
1881
  function readPackageVersion(packageJsonPath) {
1659
1882
  const parsed = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
1660
1883
  if (typeof parsed.version !== "string" || !parsed.version.trim()) {
@@ -1663,7 +1886,7 @@ function readPackageVersion(packageJsonPath) {
1663
1886
  return parsed.version;
1664
1887
  }
1665
1888
  function codexCliScriptPath() {
1666
- return join6(dirname3(bundledCodexPackagePath), "bin", "codex.js");
1889
+ return join7(dirname4(bundledCodexPackagePath), "bin", "codex.js");
1667
1890
  }
1668
1891
  function parseCodexModelCache(contents, sourcePath) {
1669
1892
  let parsed;
@@ -1704,14 +1927,14 @@ function writePrivateFileAtomic(path, contents) {
1704
1927
  }
1705
1928
  }
1706
1929
  function bundledCodexModelCachePath(authFilePath) {
1707
- return join6(dirname3(authFilePath), NEGOTIUM_MODEL_CACHE);
1930
+ return join7(dirname4(authFilePath), NEGOTIUM_MODEL_CACHE);
1708
1931
  }
1709
1932
  async function bootstrapCodexModelCache(codexHome, cachePath) {
1710
1933
  const child = spawn(process.execPath, [codexCliScriptPath(), "app-server", "--stdio"], {
1711
1934
  env: { ...process.env, CODEX_HOME: codexHome },
1712
1935
  stdio: ["pipe", "pipe", "pipe"]
1713
1936
  });
1714
- await new Promise((resolve3, reject) => {
1937
+ await new Promise((resolve4, reject) => {
1715
1938
  let settled = false;
1716
1939
  let stdoutBuffer = "";
1717
1940
  let stderr = "";
@@ -1730,7 +1953,7 @@ async function bootstrapCodexModelCache(codexHome, cachePath) {
1730
1953
  else if (!existsSync5(cachePath))
1731
1954
  reject(new Error("Codex did not create its model cache"));
1732
1955
  else
1733
- resolve3();
1956
+ resolve4();
1734
1957
  };
1735
1958
  const send = (message) => {
1736
1959
  child.stdin.write(`${JSON.stringify(message)}
@@ -1789,16 +2012,16 @@ async function bootstrapCodexModelCache(codexHome, cachePath) {
1789
2012
  });
1790
2013
  }
1791
2014
  async function bootstrapIsolatedCodexModelCache(authFilePath, bootstrap) {
1792
- const sourceHome = dirname3(authFilePath);
1793
- const isolatedHome = mkdtempSync(join6(tmpdir(), "negotium-codex-models-"));
1794
- const isolatedCachePath = join6(isolatedHome, "models_cache.json");
2015
+ const sourceHome = dirname4(authFilePath);
2016
+ const isolatedHome = mkdtempSync(join7(tmpdir(), "negotium-codex-models-"));
2017
+ const isolatedCachePath = join7(isolatedHome, "models_cache.json");
1795
2018
  try {
1796
- const isolatedAuthPath = join6(isolatedHome, "auth.json");
2019
+ const isolatedAuthPath = join7(isolatedHome, "auth.json");
1797
2020
  copyFileSync(authFilePath, isolatedAuthPath);
1798
2021
  chmodSync3(isolatedAuthPath, 384);
1799
- const sourceConfigPath = join6(sourceHome, "config.toml");
2022
+ const sourceConfigPath = join7(sourceHome, "config.toml");
1800
2023
  if (existsSync5(sourceConfigPath)) {
1801
- const isolatedConfigPath = join6(isolatedHome, "config.toml");
2024
+ const isolatedConfigPath = join7(isolatedHome, "config.toml");
1802
2025
  copyFileSync(sourceConfigPath, isolatedConfigPath);
1803
2026
  chmodSync3(isolatedConfigPath, 384);
1804
2027
  }
@@ -1809,7 +2032,7 @@ async function bootstrapIsolatedCodexModelCache(authFilePath, bootstrap) {
1809
2032
  }
1810
2033
  }
1811
2034
  async function ensureCodexModelCache(authFilePath, bootstrap = bootstrapCodexModelCache) {
1812
- const codexHome = dirname3(authFilePath);
2035
+ const codexHome = dirname4(authFilePath);
1813
2036
  const configuredCachePath = process.env.NEGOTIUM_CODEX_MODELS_CACHE_FILE;
1814
2037
  if (configuredCachePath) {
1815
2038
  if (!existsSync5(configuredCachePath)) {
@@ -1819,7 +2042,7 @@ async function ensureCodexModelCache(authFilePath, bootstrap = bootstrapCodexMod
1819
2042
  return configuredCachePath;
1820
2043
  }
1821
2044
  const bundledCachePath = bundledCodexModelCachePath(authFilePath);
1822
- const sharedCachePath = join6(codexHome, "models_cache.json");
2045
+ const sharedCachePath = join7(codexHome, "models_cache.json");
1823
2046
  if (existsSync5(sharedCachePath)) {
1824
2047
  try {
1825
2048
  const shared = readCompatibleCodexModelCache(sharedCachePath);
@@ -1838,8 +2061,8 @@ async function ensureCodexModelCache(authFilePath, bootstrap = bootstrapCodexMod
1838
2061
  return bundledCachePath;
1839
2062
  }
1840
2063
  function writeCodexCatalogWithNativeMultiAgentDisabled(authFilePath, sourcePath) {
1841
- const codexHome = dirname3(authFilePath);
1842
- const outputPath = join6(codexHome, NEGOTIUM_MODEL_CATALOG);
2064
+ const codexHome = dirname4(authFilePath);
2065
+ const outputPath = join7(codexHome, NEGOTIUM_MODEL_CATALOG);
1843
2066
  const parsed = readCodexModelCache(sourcePath).parsed;
1844
2067
  const models = parsed.models.map((model, index) => {
1845
2068
  if (!model || typeof model !== "object" || Array.isArray(model)) {
@@ -1866,9 +2089,9 @@ var init_codex_native_multi_agent = __esm(() => {
1866
2089
 
1867
2090
  // ../../packages/core/src/security/sensitive-path.ts
1868
2091
  import { realpathSync } from "fs";
1869
- import { resolve as resolve3 } from "path";
2092
+ import { resolve as resolve4 } from "path";
1870
2093
  function isSensitivePath(filePath) {
1871
- const normalized = resolve3(filePath);
2094
+ const normalized = resolve4(filePath);
1872
2095
  if (SENSITIVE_PATH_PATTERNS.some((p) => p.test(normalized)))
1873
2096
  return true;
1874
2097
  try {
@@ -2388,12 +2611,12 @@ function createBackgroundBashManager(options = {}) {
2388
2611
  usedPorts.clear();
2389
2612
  knownContexts.clear();
2390
2613
  const deadline = now() + 3000;
2391
- await Promise.all(entries.map((instance) => new Promise((resolve4) => {
2614
+ await Promise.all(entries.map((instance) => new Promise((resolve5) => {
2392
2615
  if (instance.process.exitCode !== null || instance.process.killed)
2393
- return resolve4();
2394
- instance.process.once("exit", resolve4);
2395
- instance.process.once("error", resolve4);
2396
- const timer = setTimeout(resolve4, Math.max(0, deadline - now()));
2616
+ return resolve5();
2617
+ instance.process.once("exit", resolve5);
2618
+ instance.process.once("error", resolve5);
2619
+ const timer = setTimeout(resolve5, Math.max(0, deadline - now()));
2397
2620
  timer.unref?.();
2398
2621
  })));
2399
2622
  }
@@ -3006,7 +3229,7 @@ var init_mcp_config = __esm(() => {
3006
3229
 
3007
3230
  // ../../packages/core/src/agents/execution-host.ts
3008
3231
  import { AsyncLocalStorage } from "async_hooks";
3009
- import { dirname as dirname4 } from "path";
3232
+ import { dirname as dirname5 } from "path";
3010
3233
  function activeHost() {
3011
3234
  const scoped = scopedHost.getStore();
3012
3235
  if (scoped)
@@ -3041,7 +3264,7 @@ function hostedCodexAuthFilePath() {
3041
3264
  return activeHost().codexAuthFilePath();
3042
3265
  }
3043
3266
  function hostedCodexHomePath() {
3044
- return dirname4(hostedCodexAuthFilePath());
3267
+ return dirname5(hostedCodexAuthFilePath());
3045
3268
  }
3046
3269
  var defaultHost, hostRegistrations, scopedHost;
3047
3270
  var init_execution_host = __esm(async () => {
@@ -3065,14 +3288,14 @@ var init_execution_host = __esm(async () => {
3065
3288
  // ../../packages/core/src/agents/rollout/codex.ts
3066
3289
  import { randomBytes as randomBytes4 } from "crypto";
3067
3290
  import { existsSync as existsSync6, readFileSync as readFileSync5, realpathSync as realpathSync2, statSync as statSync2, unlinkSync as unlinkSync5 } from "fs";
3068
- import { basename, dirname as dirname5, join as join7, resolve as resolve4 } from "path";
3291
+ import { basename, dirname as dirname6, join as join8, resolve as resolve5 } from "path";
3069
3292
  function codexSessionsDir() {
3070
- return join7(hostedCodexHomePath(), "sessions");
3293
+ return join8(hostedCodexHomePath(), "sessions");
3071
3294
  }
3072
3295
  function loadCodexShell() {
3073
3296
  if (_shellCache)
3074
3297
  return _shellCache;
3075
- const raw = readFileSync5(join7(FIXTURES_DIR, "codex-shell.jsonl"), "utf8");
3298
+ const raw = readFileSync5(join8(FIXTURES_DIR, "codex-shell.jsonl"), "utf8");
3076
3299
  const lines = parseJsonlText(raw);
3077
3300
  if (lines.length < 5) {
3078
3301
  throw new Error(`loadCodexShell: expected >=5 entries in codex-shell.jsonl, got ${lines.length}`);
@@ -3139,16 +3362,16 @@ function codexRolloutPath(threadId, fallback) {
3139
3362
  const hh = String(createdAt.getHours()).padStart(2, "0");
3140
3363
  const min = String(createdAt.getMinutes()).padStart(2, "0");
3141
3364
  const ss = String(createdAt.getSeconds()).padStart(2, "0");
3142
- const dir = join7(codexSessionsDir(), yyyy, mm, dd);
3143
- return join7(dir, `rollout-${yyyy}-${mm}-${dd}T${hh}-${min}-${ss}-${threadId}.jsonl`);
3365
+ const dir = join8(codexSessionsDir(), yyyy, mm, dd);
3366
+ return join8(dir, `rollout-${yyyy}-${mm}-${dd}T${hh}-${min}-${ss}-${threadId}.jsonl`);
3144
3367
  }
3145
3368
  function canonicalFilePath(path) {
3146
- const absolute = resolve4(path);
3369
+ const absolute = resolve5(path);
3147
3370
  try {
3148
3371
  return realpathSync2(absolute);
3149
3372
  } catch {
3150
3373
  try {
3151
- return join7(realpathSync2(dirname5(absolute)), basename(absolute));
3374
+ return join8(realpathSync2(dirname6(absolute)), basename(absolute));
3152
3375
  } catch {
3153
3376
  return absolute;
3154
3377
  }
@@ -3378,19 +3601,19 @@ function latestCodexRolloutPath(threadId) {
3378
3601
  try {
3379
3602
  if (buckets) {
3380
3603
  for (const bucket of buckets) {
3381
- const dir = join7(sessionsDir, bucket);
3604
+ const dir = join8(sessionsDir, bucket);
3382
3605
  if (!existsSync6(dir))
3383
3606
  continue;
3384
3607
  const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
3385
3608
  for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
3386
- candidates.push(join7(dir, rel));
3609
+ candidates.push(join8(dir, rel));
3387
3610
  }
3388
3611
  }
3389
3612
  }
3390
3613
  if (candidates.length === 0) {
3391
3614
  const glob = new Bun.Glob(`**/rollout-*-${threadId}.jsonl`);
3392
3615
  for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
3393
- candidates.push(join7(sessionsDir, rel));
3616
+ candidates.push(join8(sessionsDir, rel));
3394
3617
  }
3395
3618
  }
3396
3619
  return candidates.sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs)[0];
@@ -3511,13 +3734,13 @@ function sweepPriorRolloutsForThread(threadId) {
3511
3734
  return;
3512
3735
  }
3513
3736
  for (const bucket of buckets) {
3514
- const dir = join7(sessionsDir, bucket);
3737
+ const dir = join8(sessionsDir, bucket);
3515
3738
  if (!existsSync6(dir))
3516
3739
  continue;
3517
3740
  try {
3518
3741
  const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
3519
3742
  for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
3520
- const fullPath = join7(dir, rel);
3743
+ const fullPath = join8(dir, rel);
3521
3744
  try {
3522
3745
  unlinkSync5(fullPath);
3523
3746
  } catch (e) {
@@ -3535,7 +3758,7 @@ function sweepPriorRolloutsFullTree(threadId, sessionsDir) {
3535
3758
  try {
3536
3759
  const glob = new Bun.Glob(`**/rollout-*-${threadId}.jsonl`);
3537
3760
  for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
3538
- const fullPath = join7(sessionsDir, rel);
3761
+ const fullPath = join8(sessionsDir, rel);
3539
3762
  try {
3540
3763
  unlinkSync5(fullPath);
3541
3764
  } catch (e) {
@@ -3601,7 +3824,7 @@ import { spawn as spawn3 } from "child_process";
3601
3824
  function createCodexAppServerForker(host) {
3602
3825
  return async (parentThreadId) => {
3603
3826
  const child = host.spawnServer();
3604
- return await new Promise((resolve5, reject) => {
3827
+ return await new Promise((resolve6, reject) => {
3605
3828
  let settled = false;
3606
3829
  let stdoutBuffer = "";
3607
3830
  let stderr = "";
@@ -3618,7 +3841,7 @@ function createCodexAppServerForker(host) {
3618
3841
  if (error)
3619
3842
  reject(error);
3620
3843
  else if (result)
3621
- resolve5(result);
3844
+ resolve6(result);
3622
3845
  else
3623
3846
  reject(new Error("Codex thread fork returned no result"));
3624
3847
  };
@@ -3715,7 +3938,7 @@ var init_codex_app_server = __esm(async () => {
3715
3938
 
3716
3939
  // ../../packages/core/src/agents/codex-registry.ts
3717
3940
  import { existsSync as existsSync7, unlinkSync as unlinkSync6 } from "fs";
3718
- import { join as join8 } from "path";
3941
+ import { join as join9 } from "path";
3719
3942
  var VALID_EFFORTS2, codexRegistry, codexRegistryOperations;
3720
3943
  var init_codex_registry = __esm(async () => {
3721
3944
  init_logger();
@@ -3760,7 +3983,7 @@ var init_codex_registry = __esm(async () => {
3760
3983
  async cleanupRollouts({ sessionIds }) {
3761
3984
  if (sessionIds.length === 0)
3762
3985
  return;
3763
- const sessionsDir = join8(hostedCodexHomePath(), "sessions");
3986
+ const sessionsDir = join9(hostedCodexHomePath(), "sessions");
3764
3987
  if (!existsSync7(sessionsDir))
3765
3988
  return;
3766
3989
  const failures = [];
@@ -3768,7 +3991,7 @@ var init_codex_registry = __esm(async () => {
3768
3991
  try {
3769
3992
  const glob = new Bun.Glob(`**/rollout-*-${tid}.jsonl`);
3770
3993
  for await (const rel of glob.scan({ cwd: sessionsDir, onlyFiles: true })) {
3771
- const path = join8(sessionsDir, rel);
3994
+ const path = join9(sessionsDir, rel);
3772
3995
  try {
3773
3996
  unlinkSync6(path);
3774
3997
  } catch (e) {
@@ -3792,17 +4015,17 @@ var init_codex_registry = __esm(async () => {
3792
4015
 
3793
4016
  // ../../packages/core/src/agents/maestro-registry.ts
3794
4017
  import { randomUUID as randomUUID3 } from "crypto";
3795
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
3796
- import { homedir as homedir5 } from "os";
3797
- import { join as join9, resolve as resolve5 } from "path";
4018
+ import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
4019
+ import { homedir as homedir6 } from "os";
4020
+ import { join as join10, resolve as resolve6 } from "path";
3798
4021
  function maestroSessionsDir() {
3799
- return join9(process.env.MAESTRO_DATA_DIR ? resolve5(process.env.MAESTRO_DATA_DIR) : join9(homedir5(), ".maestro"), "sessions");
4022
+ return join10(process.env.MAESTRO_DATA_DIR ? resolve6(process.env.MAESTRO_DATA_DIR) : join10(homedir6(), ".maestro"), "sessions");
3800
4023
  }
3801
4024
  function maestroSessionPath(sessionId) {
3802
- return join9(maestroSessionsDir(), `${sessionId}.jsonl`);
4025
+ return join10(maestroSessionsDir(), `${sessionId}.jsonl`);
3803
4026
  }
3804
4027
  function maestroActiveSessionPath(sessionId) {
3805
- return join9(maestroSessionsDir(), `${sessionId}.active.jsonl`);
4028
+ return join10(maestroSessionsDir(), `${sessionId}.active.jsonl`);
3806
4029
  }
3807
4030
  function existingCreatedAt(path) {
3808
4031
  if (!existsSync8(path))
@@ -3824,7 +4047,7 @@ function writeRollout(options) {
3824
4047
  assertUuidLike("sessionId", sessionId);
3825
4048
  ensureCwdExists(options.cwd);
3826
4049
  const path = maestroSessionPath(sessionId);
3827
- mkdirSync6(maestroSessionsDir(), { recursive: true });
4050
+ mkdirSync7(maestroSessionsDir(), { recursive: true });
3828
4051
  const messages = extractChatPairs(options.entries).flatMap((pair) => [
3829
4052
  { role: "user", content: nonEmptyTurnText(pair.userText) },
3830
4053
  { role: "assistant", content: nonEmptyTurnText(pair.assistantText) }
@@ -4026,200 +4249,6 @@ function sanitizeId(id) {
4026
4249
  return id.replace(/[^a-zA-Z0-9_-]/g, "_") || "_";
4027
4250
  }
4028
4251
 
4029
- // ../../packages/core/src/storage/storage-host.ts
4030
- import { mkdirSync as mkdirSync7 } from "fs";
4031
- import { homedir as homedir6 } from "os";
4032
- import { dirname as dirname6, join as join10, resolve as resolve6 } from "path";
4033
- function envPath(name, fallback) {
4034
- const value = process.env[name]?.trim();
4035
- return resolve6(value || fallback);
4036
- }
4037
- function defaultStateDir() {
4038
- return envPath("NEGOTIUM_STATE_DIR", join10(homedir6(), ".negotium"));
4039
- }
4040
- function defaultDataDir() {
4041
- return envPath("NEGOTIUM_DATA_DIR", join10(defaultStateDir(), "data"));
4042
- }
4043
- function defaultLogDir() {
4044
- return envPath("NEGOTIUM_LOG_DIR", join10(defaultStateDir(), "logs"));
4045
- }
4046
- function defaultWorkspaceDir() {
4047
- return envPath("NEGOTIUM_WORKSPACE_DIR", join10(defaultStateDir(), "workspace"));
4048
- }
4049
- function defaultSessionAsksDir() {
4050
- const runDir = envPath("NEGOTIUM_RUN_DIR", join10(defaultStateDir(), "runtime"));
4051
- return join10(runDir, "session-asks");
4052
- }
4053
- function defaultSessionsDatabasePath() {
4054
- return envPath("SESSIONS_DB_PATH", join10(resolveStorageDataDir(), "sessions.db"));
4055
- }
4056
- function isSqliteBusy(error) {
4057
- const message = error instanceof Error ? error.message : String(error);
4058
- return /database is (?:locked|busy)|SQLITE_(?:BUSY|LOCKED)/i.test(message);
4059
- }
4060
- function execWithBusyRetry(database, sql, timeoutMs = SQLITE_INIT_TIMEOUT_MS) {
4061
- const deadline = Date.now() + timeoutMs;
4062
- while (true) {
4063
- try {
4064
- database.exec(sql);
4065
- return;
4066
- } catch (error) {
4067
- if (!isSqliteBusy(error) || Date.now() >= deadline)
4068
- throw error;
4069
- Atomics.wait(SQLITE_INIT_SLEEP, 0, 0, Math.min(SQLITE_INIT_RETRY_MS, deadline - Date.now()));
4070
- }
4071
- }
4072
- }
4073
- function initializeDatabase(database) {
4074
- database.exec("PRAGMA busy_timeout = 5000");
4075
- execWithBusyRetry(database, "PRAGMA journal_mode = WAL");
4076
- database.exec("PRAGMA foreign_keys = ON");
4077
- database.exec("PRAGMA wal_autocheckpoint = 1000");
4078
- try {
4079
- database.exec("PRAGMA wal_checkpoint(TRUNCATE)");
4080
- } catch {}
4081
- }
4082
- function defaultDatabase() {
4083
- const path = defaultSessionsDatabasePath();
4084
- if (fallbackDatabase && fallbackDatabasePath === path)
4085
- return fallbackDatabase;
4086
- if (fallbackDatabase)
4087
- fallbackDatabase.close();
4088
- mkdirSync7(dirname6(path), { recursive: true });
4089
- fallbackDatabase = new Database(path, { create: true });
4090
- fallbackDatabasePath = path;
4091
- initializeDatabase(fallbackDatabase);
4092
- return fallbackDatabase;
4093
- }
4094
- function resolveStorageDatabase() {
4095
- return configuredHost.database ?? defaultDatabase();
4096
- }
4097
- function resolveStorageDataDir() {
4098
- return configuredHost.dataDir ?? defaultDataDir();
4099
- }
4100
- function resolveStorageLogDir() {
4101
- return configuredHost.logDir ?? defaultLogDir();
4102
- }
4103
- function resolveStorageSessionAsksDir() {
4104
- return configuredHost.sessionAsksDir ?? defaultSessionAsksDir();
4105
- }
4106
- function resolveStorageWorkspaceDir() {
4107
- return configuredHost.workspaceDir ?? defaultWorkspaceDir();
4108
- }
4109
- function resolveStorageSharedWikiDir() {
4110
- return configuredHost.sharedWikiDir ?? join10(resolveStorageWorkspaceDir(), "wiki");
4111
- }
4112
- function resolveStorageUsersLogDir() {
4113
- return configuredHost.usersLogDir ?? join10(resolveStorageDataDir(), "users");
4114
- }
4115
- function normalizeStorageHostPatch(options) {
4116
- const patch = {};
4117
- if (options.database !== undefined) {
4118
- options.database.exec("PRAGMA foreign_keys = ON");
4119
- patch.database = options.database;
4120
- }
4121
- for (const key of STORAGE_PATH_KEYS) {
4122
- const value = options[key];
4123
- if (value === undefined)
4124
- continue;
4125
- if (!value.trim())
4126
- throw new TypeError(`${key} must not be empty`);
4127
- patch[key] = resolve6(value);
4128
- }
4129
- return Object.freeze(patch);
4130
- }
4131
- function refreshConfiguredHost() {
4132
- configuredHost = Object.freeze(Object.assign({}, ...storageHostFrames.filter((frame) => frame.active).map((frame) => frame.patch)));
4133
- }
4134
- function configureStorageHost(options) {
4135
- const frame = { active: true, patch: normalizeStorageHostPatch(options) };
4136
- storageHostFrames.push(frame);
4137
- refreshConfiguredHost();
4138
- return () => {
4139
- if (!frame.active)
4140
- return;
4141
- frame.active = false;
4142
- const index = storageHostFrames.indexOf(frame);
4143
- if (index >= 0)
4144
- storageHostFrames.splice(index, 1);
4145
- refreshConfiguredHost();
4146
- };
4147
- }
4148
- function resetStorageHost() {
4149
- for (const frame of storageHostFrames)
4150
- frame.active = false;
4151
- storageHostFrames.length = 0;
4152
- refreshConfiguredHost();
4153
- }
4154
- function closeStorageDatabase() {
4155
- if (!fallbackDatabase)
4156
- return;
4157
- fallbackDatabase.close();
4158
- fallbackDatabase = null;
4159
- fallbackDatabasePath = null;
4160
- }
4161
- function registerStorageSchemaInitializer(initialize, priority = 100) {
4162
- schemaInitializers.push({ initialize, priority });
4163
- schemaInitializers.sort((a, b) => a.priority - b.priority);
4164
- }
4165
- function ensureStorageSchemas(database = resolveStorageDatabase()) {
4166
- if (initializingDatabases.has(database))
4167
- return;
4168
- let initialized = initializedSchemas.get(database);
4169
- if (!initialized) {
4170
- initialized = new Set;
4171
- initializedSchemas.set(database, initialized);
4172
- }
4173
- initializingDatabases.add(database);
4174
- try {
4175
- for (const entry of schemaInitializers) {
4176
- if (initialized.has(entry.initialize))
4177
- continue;
4178
- initialized.add(entry.initialize);
4179
- try {
4180
- entry.initialize(database);
4181
- } catch (error) {
4182
- initialized.delete(entry.initialize);
4183
- throw error;
4184
- }
4185
- }
4186
- } finally {
4187
- initializingDatabases.delete(database);
4188
- }
4189
- }
4190
- var configuredHost, fallbackDatabase = null, fallbackDatabasePath = null, storageHostFrames, schemaInitializers, initializedSchemas, initializingDatabases, SQLITE_INIT_RETRY_MS = 25, SQLITE_INIT_TIMEOUT_MS = 5000, SQLITE_INIT_SLEEP, STORAGE_PATH_KEYS, internalStorageDatabase, storageDatabase;
4191
- var init_storage_host = __esm(async () => {
4192
- await init_sqlite();
4193
- configuredHost = {};
4194
- storageHostFrames = [];
4195
- schemaInitializers = [];
4196
- initializedSchemas = new WeakMap;
4197
- initializingDatabases = new WeakSet;
4198
- SQLITE_INIT_SLEEP = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
4199
- STORAGE_PATH_KEYS = [
4200
- "dataDir",
4201
- "logDir",
4202
- "sessionAsksDir",
4203
- "workspaceDir",
4204
- "sharedWikiDir",
4205
- "usersLogDir"
4206
- ];
4207
- internalStorageDatabase = new Proxy({}, {
4208
- get(_target, property) {
4209
- const database = resolveStorageDatabase();
4210
- ensureStorageSchemas(database);
4211
- const value = Reflect.get(database, property, database);
4212
- return typeof value === "function" ? value.bind(database) : value;
4213
- },
4214
- set(_target, property, value) {
4215
- const database = resolveStorageDatabase();
4216
- ensureStorageSchemas(database);
4217
- return Reflect.set(database, property, value, database);
4218
- }
4219
- });
4220
- storageDatabase = internalStorageDatabase;
4221
- });
4222
-
4223
4252
  // ../../packages/core/src/storage/tasks.ts
4224
4253
  var exports_tasks = {};
4225
4254
  __export(exports_tasks, {
@@ -6745,7 +6774,10 @@ function listRecentRuntimeEventsForTopic(topicId, limit = 300) {
6745
6774
  }
6746
6775
  var RUNTIME_EVENT_TYPES, types;
6747
6776
  var init_runtime_events = __esm(async () => {
6748
- await init_forum_db();
6777
+ await __promiseAll([
6778
+ init_forum_db(),
6779
+ init_storage_host()
6780
+ ]);
6749
6781
  RUNTIME_EVENT_TYPES = [
6750
6782
  "message",
6751
6783
  "message-updated",
@@ -6754,26 +6786,28 @@ var init_runtime_events = __esm(async () => {
6754
6786
  "topic-updated",
6755
6787
  "topic-deleted"
6756
6788
  ];
6757
- db.exec(`
6758
- CREATE TABLE IF NOT EXISTS runtime_events (
6759
- seq INTEGER PRIMARY KEY AUTOINCREMENT,
6760
- source_id TEXT NOT NULL,
6761
- event_type TEXT NOT NULL CHECK (
6762
- event_type IN (
6763
- 'message',
6764
- 'message-updated',
6765
- 'ai-status',
6766
- 'topic-created',
6767
- 'topic-updated',
6768
- 'topic-deleted'
6769
- )
6770
- ),
6771
- topic_id TEXT NOT NULL,
6772
- payload_json TEXT NOT NULL,
6773
- created_at TEXT NOT NULL
6774
- )
6775
- `);
6776
- db.exec("CREATE INDEX IF NOT EXISTS idx_runtime_events_topic_seq ON runtime_events(topic_id, seq)");
6789
+ registerStorageSchemaInitializer((database) => {
6790
+ database.exec(`
6791
+ CREATE TABLE IF NOT EXISTS runtime_events (
6792
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
6793
+ source_id TEXT NOT NULL,
6794
+ event_type TEXT NOT NULL CHECK (
6795
+ event_type IN (
6796
+ 'message',
6797
+ 'message-updated',
6798
+ 'ai-status',
6799
+ 'topic-created',
6800
+ 'topic-updated',
6801
+ 'topic-deleted'
6802
+ )
6803
+ ),
6804
+ topic_id TEXT NOT NULL,
6805
+ payload_json TEXT NOT NULL,
6806
+ created_at TEXT NOT NULL
6807
+ )
6808
+ `);
6809
+ database.exec("CREATE INDEX IF NOT EXISTS idx_runtime_events_topic_seq ON runtime_events(topic_id, seq)");
6810
+ });
6777
6811
  types = new Set(RUNTIME_EVENT_TYPES);
6778
6812
  });
6779
6813
 
@@ -6934,18 +6968,19 @@ class SqliteRuntimeBus {
6934
6968
  }
6935
6969
  }
6936
6970
  function runtimeBus() {
6971
+ if (!current)
6972
+ current = new SqliteRuntimeBus;
6937
6973
  return current;
6938
6974
  }
6939
6975
  function setRuntimeBus(bus) {
6940
6976
  current = bus;
6941
6977
  }
6942
- var current, WsHub;
6978
+ var current = null, WsHub;
6943
6979
  var init_bus = __esm(async () => {
6944
6980
  await init_runtime_events();
6945
- current = new SqliteRuntimeBus;
6946
6981
  WsHub = {
6947
6982
  get() {
6948
- return current;
6983
+ return runtimeBus();
6949
6984
  }
6950
6985
  };
6951
6986
  });
@@ -7479,6 +7514,7 @@ var exports_api_topics = {};
7479
7514
  __export(exports_api_topics, {
7480
7515
  upsertTopic: () => upsertTopic,
7481
7516
  setTopicSessionId: () => setTopicSessionId,
7517
+ setTopicAccessModes: () => setTopicAccessModes,
7482
7518
  setApiTopicAgent: () => setApiTopicAgent,
7483
7519
  revokeSubagentTellTarget: () => revokeSubagentTellTarget,
7484
7520
  reparentTopicChildren: () => reparentTopicChildren,
@@ -7945,6 +7981,16 @@ function upsertTopic(t) {
7945
7981
  }
7946
7982
  })();
7947
7983
  }
7984
+ function setTopicAccessModes(topicIds, accessMode) {
7985
+ if (topicIds.length === 0)
7986
+ return;
7987
+ const normalized = normalizeTopicAccessMode(accessMode);
7988
+ db.transaction(() => {
7989
+ const update = db.query("UPDATE api_topics SET access_mode = ? WHERE id = ?");
7990
+ for (const topicId of topicIds)
7991
+ update.run(normalized, topicId);
7992
+ })();
7993
+ }
7948
7994
  function listTopics() {
7949
7995
  const rows = db.query("SELECT * FROM api_topics ORDER BY last_message_at DESC").all();
7950
7996
  const participants = getAllTopicParticipants();
@@ -9525,32 +9571,35 @@ var init_self_schedules = __esm(async () => {
9525
9571
  await __promiseAll([
9526
9572
  init_forum_db(),
9527
9573
  init_runtime_leases(),
9528
- init_runtime_topic_state()
9574
+ init_runtime_topic_state(),
9575
+ init_storage_host()
9529
9576
  ]);
9530
- db.exec(`
9531
- CREATE TABLE IF NOT EXISTS runtime_self_schedules (
9532
- id TEXT PRIMARY KEY,
9533
- topic_id TEXT NOT NULL,
9534
- user_id TEXT NOT NULL,
9535
- message TEXT NOT NULL,
9536
- deliver_at INTEGER NOT NULL,
9537
- created_at INTEGER NOT NULL,
9538
- updated_at INTEGER NOT NULL,
9539
- status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running')),
9540
- claimed_by TEXT,
9541
- claimed_at INTEGER,
9542
- running_query_id TEXT
9543
- )
9544
- `);
9545
- db.exec(`
9546
- CREATE UNIQUE INDEX IF NOT EXISTS idx_runtime_self_schedules_one_pending
9547
- ON runtime_self_schedules(topic_id)
9548
- WHERE status = 'pending'
9549
- `);
9550
- db.exec(`
9551
- CREATE INDEX IF NOT EXISTS idx_runtime_self_schedules_due
9552
- ON runtime_self_schedules(status, deliver_at)
9553
- `);
9577
+ registerStorageSchemaInitializer((database) => {
9578
+ database.exec(`
9579
+ CREATE TABLE IF NOT EXISTS runtime_self_schedules (
9580
+ id TEXT PRIMARY KEY,
9581
+ topic_id TEXT NOT NULL,
9582
+ user_id TEXT NOT NULL,
9583
+ message TEXT NOT NULL,
9584
+ deliver_at INTEGER NOT NULL,
9585
+ created_at INTEGER NOT NULL,
9586
+ updated_at INTEGER NOT NULL,
9587
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running')),
9588
+ claimed_by TEXT,
9589
+ claimed_at INTEGER,
9590
+ running_query_id TEXT
9591
+ )
9592
+ `);
9593
+ database.exec(`
9594
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_runtime_self_schedules_one_pending
9595
+ ON runtime_self_schedules(topic_id)
9596
+ WHERE status = 'pending'
9597
+ `);
9598
+ database.exec(`
9599
+ CREATE INDEX IF NOT EXISTS idx_runtime_self_schedules_due
9600
+ ON runtime_self_schedules(status, deliver_at)
9601
+ `);
9602
+ });
9554
9603
  });
9555
9604
 
9556
9605
  // ../../packages/core/src/topics/links.ts
@@ -10240,15 +10289,20 @@ function deleteBrowserProfile(ownerId, rawName) {
10240
10289
  }
10241
10290
  var DEFAULT_BROWSER_PROFILE = "default";
10242
10291
  var init_browser_profiles = __esm(async () => {
10243
- await init_forum_db();
10244
- db.exec(`
10245
- CREATE TABLE IF NOT EXISTS browser_profiles (
10246
- owner_id TEXT NOT NULL,
10247
- name TEXT NOT NULL,
10248
- created_at TEXT NOT NULL,
10249
- PRIMARY KEY (owner_id, name)
10250
- )
10251
- `);
10292
+ await __promiseAll([
10293
+ init_forum_db(),
10294
+ init_storage_host()
10295
+ ]);
10296
+ registerStorageSchemaInitializer((database) => {
10297
+ database.exec(`
10298
+ CREATE TABLE IF NOT EXISTS browser_profiles (
10299
+ owner_id TEXT NOT NULL,
10300
+ name TEXT NOT NULL,
10301
+ created_at TEXT NOT NULL,
10302
+ PRIMARY KEY (owner_id, name)
10303
+ )
10304
+ `);
10305
+ });
10252
10306
  });
10253
10307
 
10254
10308
  // ../../packages/core/src/platform/playwright/manager.ts
@@ -11977,9 +12031,9 @@ function formatArchiverBytes(bytes) {
11977
12031
  function formatArchiverTool(name, input) {
11978
12032
  const params = Object.entries(input).map(([key, value]) => {
11979
12033
  const rendered = typeof value === "string" ? value : JSON.stringify(value);
11980
- return `${key}: ${sessionText(rendered ?? "")}`;
12034
+ return `${key}: ${summarizeDisplayText(sessionText(rendered ?? ""))}`;
11981
12035
  }).join(", ");
11982
- return `${name}${params ? `(${params})` : ""}`;
12036
+ return summarizeDisplayText(`${name}${params ? `(${params})` : ""}`);
11983
12037
  }
11984
12038
  function createArchiverRuntime(host) {
11985
12039
  const activeSessions = new Map;
@@ -12289,6 +12343,7 @@ function listActiveMemoryArchiverSessions(userId) {
12289
12343
  }
12290
12344
  var MAX_BRIEF_ENTRIES = 8, defaultArchiverRuntime;
12291
12345
  var init_archiver = __esm(async () => {
12346
+ init_tool_format();
12292
12347
  init_config();
12293
12348
  init_logger();
12294
12349
  init_builders();
@@ -13096,7 +13151,7 @@ function detailedToolStep(payload) {
13096
13151
  const label = compactToolLabel(String(payload.label ?? name));
13097
13152
  if (shortName === "ask_session" || shortName === "tell_session") {
13098
13153
  const target = typeof input.to === "string" && input.to.trim() ? input.to.trim() : "session";
13099
- const msg = typeof input.message === "string" ? text(input.message) : "";
13154
+ const msg = typeof input.message === "string" ? summarizeDisplayText(text(input.message)) : "";
13100
13155
  const action = shortName === "ask_session" ? "Ask" : "Tell";
13101
13156
  return `${action} ${target}${msg ? ` \u2014 ${msg}` : ""}`;
13102
13157
  }
@@ -13122,22 +13177,22 @@ function detailedToolStep(payload) {
13122
13177
  return `${action} file`;
13123
13178
  }
13124
13179
  if (shortName === "grep" || shortName === "glob") {
13125
- const pattern = typeof input.pattern === "string" ? input.pattern : "";
13180
+ const pattern = typeof input.pattern === "string" ? summarizeDisplayText(text(input.pattern)) : "";
13126
13181
  const action = shortName === "grep" ? "Grep" : "Glob";
13127
13182
  if (pattern)
13128
- return `${action} '${text(pattern)}'${path ? ` in ${path}` : ""}`;
13183
+ return `${action} '${pattern}'${path ? ` in ${path}` : ""}`;
13129
13184
  if (path)
13130
13185
  return `${action} ${path}`;
13131
13186
  }
13132
13187
  if (shortName === "bash") {
13133
- const cmd = typeof input.command === "string" ? text(input.command) : "";
13188
+ const cmd = typeof input.command === "string" ? summarizeDisplayText(text(input.command)) : "";
13134
13189
  return cmd ? `Bash: ${cmd}` : "Bash";
13135
13190
  }
13136
13191
  if (shortName === "webfetch" || shortName === "url_fetch") {
13137
- const url = typeof input.url === "string" ? text(input.url) : "";
13192
+ const url = typeof input.url === "string" ? summarizeDisplayText(text(input.url)) : "";
13138
13193
  return url ? `Fetch ${url}` : "WebFetch";
13139
13194
  }
13140
- return label;
13195
+ return summarizeDisplayText(label);
13141
13196
  }
13142
13197
  function backgroundSessionProgress(topicId, queryId) {
13143
13198
  let status = "Running";
@@ -13165,7 +13220,7 @@ function backgroundSessionProgress(topicId, queryId) {
13165
13220
  step = text(payload.content);
13166
13221
  else if (kind === "tool_output") {
13167
13222
  const toolName = toolNames.get(text(payload.toolUseId)) ?? "Tool";
13168
- step = payload.isError ? `${toolName} failed: ${text(payload.content) || "unknown error"}` : `${toolName} result \xB7 ${byteSize(payload.content)}`;
13223
+ step = payload.isError ? `${toolName} failed: ${summarizeDisplayText(text(payload.content)) || "unknown error"}` : `${toolName} result \xB7 ${byteSize(payload.content)}`;
13169
13224
  } else if (kind === "ai_done") {
13170
13225
  const usage = payload.usage && typeof payload.usage === "object" ? payload.usage : undefined;
13171
13226
  const input = typeof usage?.input === "number" ? usage.input.toLocaleString() : "";
@@ -13230,6 +13285,7 @@ function listBackgroundSessionsForUser(userId) {
13230
13285
  }
13231
13286
  var providers, transientSessions;
13232
13287
  var init_background_sessions = __esm(async () => {
13288
+ init_tool_format();
13233
13289
  init_background_session_policy();
13234
13290
  await __promiseAll([
13235
13291
  init_archiver(),
@@ -13535,10 +13591,11 @@ var init_runtime_turn_requests = __esm(async () => {
13535
13591
  await __promiseAll([
13536
13592
  init_forum_db(),
13537
13593
  init_runtime_leases(),
13538
- init_runtime_topic_state()
13594
+ init_runtime_topic_state(),
13595
+ init_storage_host()
13539
13596
  ]);
13540
13597
  REQUEST_CLAIM_STALE_MS = TURN_LEASE_STALE_MS;
13541
- ensureRuntimeUserTurnRequestsSchema(db);
13598
+ registerStorageSchemaInitializer((database) => ensureRuntimeUserTurnRequestsSchema(database));
13542
13599
  });
13543
13600
 
13544
13601
  // ../../packages/core/src/topics/session.ts
@@ -15800,19 +15857,22 @@ var pendingAsks, MAX_ASK_AGE_MS;
15800
15857
  var init_ask_callbacks = __esm(async () => {
15801
15858
  await __promiseAll([
15802
15859
  init_forum_db(),
15803
- init_session_asks()
15860
+ init_session_asks(),
15861
+ init_storage_host()
15804
15862
  ]);
15805
- db.exec(`
15806
- CREATE TABLE IF NOT EXISTS remote_ask_callbacks (
15807
- target_query_id TEXT PRIMARY KEY,
15808
- request_id TEXT NOT NULL,
15809
- node_name TEXT NOT NULL,
15810
- node_cell_id TEXT NOT NULL,
15811
- topic_id TEXT NOT NULL,
15812
- user_id TEXT NOT NULL,
15813
- created_at INTEGER NOT NULL
15814
- )
15815
- `);
15863
+ registerStorageSchemaInitializer((database) => {
15864
+ database.exec(`
15865
+ CREATE TABLE IF NOT EXISTS remote_ask_callbacks (
15866
+ target_query_id TEXT PRIMARY KEY,
15867
+ request_id TEXT NOT NULL,
15868
+ node_name TEXT NOT NULL,
15869
+ node_cell_id TEXT NOT NULL,
15870
+ topic_id TEXT NOT NULL,
15871
+ user_id TEXT NOT NULL,
15872
+ created_at INTEGER NOT NULL
15873
+ )
15874
+ `);
15875
+ });
15816
15876
  pendingAsks = new Map;
15817
15877
  MAX_ASK_AGE_MS = PENDING_ASK_TTL_MS;
15818
15878
  });
@@ -16034,30 +16094,35 @@ paint();document.body.appendChild(b)}
16034
16094
  if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",init);else init()})();
16035
16095
  </script>`;
16036
16096
  var init_visual_store = __esm(async () => {
16037
- await init_forum_db();
16038
- db.exec(`
16039
- CREATE TABLE IF NOT EXISTS api_topic_visuals (
16040
- id INTEGER PRIMARY KEY AUTOINCREMENT,
16041
- topic_id TEXT NOT NULL,
16042
- html TEXT NOT NULL,
16043
- title TEXT,
16044
- created_at INTEGER NOT NULL,
16045
- kind TEXT NOT NULL DEFAULT 'html',
16046
- source TEXT,
16047
- file_id TEXT,
16048
- mime_type TEXT,
16049
- media_token TEXT
16050
- );
16051
- CREATE INDEX IF NOT EXISTS idx_api_topic_visuals_topic
16052
- ON api_topic_visuals(topic_id, created_at DESC);
16053
-
16054
- CREATE TABLE IF NOT EXISTS api_topic_visual_views (
16055
- topic_id TEXT NOT NULL,
16056
- user_id TEXT NOT NULL,
16057
- visual_id INTEGER NOT NULL,
16058
- PRIMARY KEY (topic_id, user_id)
16059
- );
16060
- `);
16097
+ await __promiseAll([
16098
+ init_forum_db(),
16099
+ init_storage_host()
16100
+ ]);
16101
+ registerStorageSchemaInitializer((database) => {
16102
+ database.exec(`
16103
+ CREATE TABLE IF NOT EXISTS api_topic_visuals (
16104
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16105
+ topic_id TEXT NOT NULL,
16106
+ html TEXT NOT NULL,
16107
+ title TEXT,
16108
+ created_at INTEGER NOT NULL,
16109
+ kind TEXT NOT NULL DEFAULT 'html',
16110
+ source TEXT,
16111
+ file_id TEXT,
16112
+ mime_type TEXT,
16113
+ media_token TEXT
16114
+ );
16115
+ CREATE INDEX IF NOT EXISTS idx_api_topic_visuals_topic
16116
+ ON api_topic_visuals(topic_id, created_at DESC);
16117
+
16118
+ CREATE TABLE IF NOT EXISTS api_topic_visual_views (
16119
+ topic_id TEXT NOT NULL,
16120
+ user_id TEXT NOT NULL,
16121
+ visual_id INTEGER NOT NULL,
16122
+ PRIMARY KEY (topic_id, user_id)
16123
+ );
16124
+ `);
16125
+ });
16061
16126
  for (const statement of [
16062
16127
  "ALTER TABLE api_topic_visuals ADD COLUMN kind TEXT NOT NULL DEFAULT 'html'",
16063
16128
  "ALTER TABLE api_topic_visuals ADD COLUMN source TEXT",
@@ -20168,20 +20233,26 @@ function recordRuntimeGatewaySubmission(submission) {
20168
20233
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(submission.clientMessageId, submission.requestId, submission.topicId, submission.messageId, submission.userId, submission.createdAt, submission.ackCursor, submission.messageCursor, submission.payloadHash ?? null);
20169
20234
  }
20170
20235
  var init_runtime_gateway_submissions = __esm(async () => {
20171
- await init_forum_db();
20172
- db.exec(`
20173
- CREATE TABLE IF NOT EXISTS runtime_gateway_submissions (
20174
- client_message_id TEXT PRIMARY KEY,
20175
- request_id TEXT NOT NULL UNIQUE,
20176
- topic_id TEXT NOT NULL,
20177
- message_id TEXT NOT NULL,
20178
- user_id TEXT NOT NULL,
20179
- created_at TEXT NOT NULL,
20180
- ack_cursor INTEGER NOT NULL DEFAULT 0,
20181
- message_cursor INTEGER NOT NULL DEFAULT 0,
20182
- payload_hash TEXT
20183
- )
20184
- `);
20236
+ await __promiseAll([
20237
+ init_forum_db(),
20238
+ init_storage_host()
20239
+ ]);
20240
+ registerStorageSchemaInitializer((database) => {
20241
+ database.exec(`
20242
+ CREATE TABLE IF NOT EXISTS runtime_gateway_submissions (
20243
+ client_message_id TEXT PRIMARY KEY,
20244
+ request_id TEXT NOT NULL UNIQUE,
20245
+ topic_id TEXT NOT NULL,
20246
+ message_id TEXT NOT NULL,
20247
+ user_id TEXT NOT NULL,
20248
+ created_at TEXT NOT NULL,
20249
+ ack_cursor INTEGER NOT NULL DEFAULT 0,
20250
+ message_cursor INTEGER NOT NULL DEFAULT 0,
20251
+ payload_hash TEXT
20252
+ )
20253
+ `);
20254
+ database.exec("CREATE INDEX IF NOT EXISTS idx_runtime_gateway_submissions_topic ON runtime_gateway_submissions(topic_id)");
20255
+ });
20185
20256
  try {
20186
20257
  db.exec("ALTER TABLE runtime_gateway_submissions ADD COLUMN ack_cursor INTEGER NOT NULL DEFAULT 0");
20187
20258
  } catch {}
@@ -20191,7 +20262,6 @@ var init_runtime_gateway_submissions = __esm(async () => {
20191
20262
  try {
20192
20263
  db.exec("ALTER TABLE runtime_gateway_submissions ADD COLUMN message_cursor INTEGER NOT NULL DEFAULT 0");
20193
20264
  } catch {}
20194
- db.exec("CREATE INDEX IF NOT EXISTS idx_runtime_gateway_submissions_topic ON runtime_gateway_submissions(topic_id)");
20195
20265
  });
20196
20266
 
20197
20267
  // ../../packages/core/src/application/submit-runtime-gateway-turn.ts
@@ -20364,6 +20434,33 @@ var init_submit_user_message = __esm(async () => {
20364
20434
  });
20365
20435
 
20366
20436
  // ../../packages/core/src/application/switch-topic-access-mode.ts
20437
+ function collectSubagentDescendants(rootTopicId) {
20438
+ const all = listTopics();
20439
+ const childrenByParent = new Map;
20440
+ for (const topic of all) {
20441
+ if (!topic.isSubagent || !topic.parentTopicId)
20442
+ continue;
20443
+ const siblings = childrenByParent.get(topic.parentTopicId);
20444
+ if (siblings)
20445
+ siblings.push(topic);
20446
+ else
20447
+ childrenByParent.set(topic.parentTopicId, [topic]);
20448
+ }
20449
+ const descendants = [];
20450
+ const seen = new Set([rootTopicId]);
20451
+ const queue = [rootTopicId];
20452
+ while (queue.length > 0) {
20453
+ const parentId = queue.shift();
20454
+ for (const child of childrenByParent.get(parentId) ?? []) {
20455
+ if (seen.has(child.id))
20456
+ continue;
20457
+ seen.add(child.id);
20458
+ descendants.push(child);
20459
+ queue.push(child.id);
20460
+ }
20461
+ }
20462
+ return descendants;
20463
+ }
20367
20464
  function switchTopicAccessMode(params) {
20368
20465
  const topic = getTopic(params.topicId);
20369
20466
  if (!topic)
@@ -20371,13 +20468,32 @@ function switchTopicAccessMode(params) {
20371
20468
  const owner = topic.participants.some((participant) => participant.userId === params.userId && participant.role === "owner");
20372
20469
  if (!owner)
20373
20470
  return { ok: false, error: "Only topic owners can change privacy" };
20374
- topic.accessMode = params.accessMode;
20375
- upsertTopic(topic);
20376
- WsHub.get().broadcastTopicUpdated(topic.id);
20471
+ if (topic.isSubagent) {
20472
+ return {
20473
+ ok: false,
20474
+ error: "Subagent rooms inherit privacy from their parent topic"
20475
+ };
20476
+ }
20477
+ const descendants = collectSubagentDescendants(topic.id);
20478
+ const changed = [topic, ...descendants].filter((candidate) => (candidate.accessMode ?? "private") !== params.accessMode);
20479
+ if (changed.length === 0) {
20480
+ return {
20481
+ ok: true,
20482
+ accessMode: params.accessMode,
20483
+ text: params.accessMode === "shared" ? `"${topic.title}" is already public to the connected Otium Hub.` : `"${topic.title}" is already private to this worker.`,
20484
+ topicIds: []
20485
+ };
20486
+ }
20487
+ setTopicAccessModes(changed.map((candidate) => candidate.id), params.accessMode);
20488
+ for (const candidate of changed)
20489
+ WsHub.get().broadcastTopicUpdated(candidate.id);
20490
+ const subagentCount = changed.filter((candidate) => candidate.id !== topic.id).length;
20491
+ const suffix = subagentCount > 0 ? ` (${subagentCount} subagent room${subagentCount === 1 ? "" : "s"} updated)` : "";
20377
20492
  return {
20378
20493
  ok: true,
20379
20494
  accessMode: params.accessMode,
20380
- text: params.accessMode === "shared" ? `"${topic.title}" is public to the connected Otium Hub.` : `"${topic.title}" is private to this worker.`
20495
+ topicIds: changed.map((candidate) => candidate.id),
20496
+ text: (params.accessMode === "shared" ? `"${topic.title}" is public to the connected Otium Hub.` : `"${topic.title}" is private to this worker.`) + suffix
20381
20497
  };
20382
20498
  }
20383
20499
  var init_switch_topic_access_mode = __esm(async () => {
@@ -24920,6 +25036,219 @@ var init_vault2 = __esm(() => {
24920
25036
  init_mcp_helpers();
24921
25037
  });
24922
25038
 
25039
+ // ../../packages/core/src/mcp/wiki-search-index.ts
25040
+ import { chmodSync as chmodSync5, existsSync as existsSync26, readFileSync as readFileSync22, unlinkSync as unlinkSync18 } from "fs";
25041
+ function documentId(source) {
25042
+ return `${source.kind}:${source.key}`;
25043
+ }
25044
+ function ensureSchema(database) {
25045
+ const version = database.query("PRAGMA user_version").get()?.user_version;
25046
+ if (version !== 0 && version !== SEARCH_INDEX_SCHEMA_VERSION) {
25047
+ throw new Error(`Unsupported wiki search index schema: ${version}`);
25048
+ }
25049
+ database.exec(`
25050
+ CREATE TABLE IF NOT EXISTS wiki_search_documents (
25051
+ id TEXT PRIMARY KEY,
25052
+ kind TEXT NOT NULL,
25053
+ key TEXT NOT NULL,
25054
+ path TEXT NOT NULL,
25055
+ mtime_ms REAL NOT NULL,
25056
+ size INTEGER NOT NULL,
25057
+ title TEXT NOT NULL,
25058
+ text TEXT NOT NULL,
25059
+ date TEXT,
25060
+ family TEXT
25061
+ );
25062
+ CREATE UNIQUE INDEX IF NOT EXISTS wiki_search_documents_kind_key
25063
+ ON wiki_search_documents(kind, key);
25064
+ CREATE INDEX IF NOT EXISTS wiki_search_documents_kind_date
25065
+ ON wiki_search_documents(kind, date);
25066
+ CREATE INDEX IF NOT EXISTS wiki_search_documents_kind_family_date
25067
+ ON wiki_search_documents(kind, family, date);
25068
+ CREATE VIRTUAL TABLE IF NOT EXISTS wiki_search_fts USING fts5(
25069
+ document_id UNINDEXED,
25070
+ kind UNINDEXED,
25071
+ tokens,
25072
+ tokenize = 'unicode61'
25073
+ );
25074
+ PRAGMA user_version = ${SEARCH_INDEX_SCHEMA_VERSION};
25075
+ `);
25076
+ }
25077
+
25078
+ class WikiSearchIndex {
25079
+ path;
25080
+ #database;
25081
+ constructor(path) {
25082
+ this.path = path;
25083
+ this.#database = new Database(path, { create: true });
25084
+ try {
25085
+ this.#database.exec("PRAGMA foreign_keys = ON; PRAGMA journal_mode = DELETE;");
25086
+ ensureSchema(this.#database);
25087
+ chmodSync5(path, 384);
25088
+ } catch (error2) {
25089
+ this.#database.close();
25090
+ throw error2;
25091
+ }
25092
+ }
25093
+ close() {
25094
+ this.#database.close();
25095
+ }
25096
+ upsert(source, document) {
25097
+ const id = documentId(source);
25098
+ const apply = this.#database.transaction(() => {
25099
+ this.#database.query("DELETE FROM wiki_search_fts WHERE document_id = ?").run(id);
25100
+ this.#database.query("DELETE FROM wiki_search_documents WHERE id = ?").run(id);
25101
+ this.#database.query(`
25102
+ INSERT INTO wiki_search_documents
25103
+ (id, kind, key, path, mtime_ms, size, title, text, date, family)
25104
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
25105
+ `).run(id, source.kind, source.key, source.path, source.mtimeMs, source.size, document.title, document.text, document.date ?? null, document.family ?? null);
25106
+ this.#database.query("INSERT INTO wiki_search_fts(document_id, kind, tokens) VALUES (?, ?, ?)").run(id, source.kind, document.tokens.join(" "));
25107
+ });
25108
+ apply.immediate();
25109
+ }
25110
+ sync(sources, prepare) {
25111
+ const result = { added: 0, updated: 0, removed: 0 };
25112
+ const existingRows = this.#database.query("SELECT id, path, mtime_ms, size FROM wiki_search_documents ORDER BY id").all();
25113
+ const existing = new Map(existingRows.map((row) => [row.id, row]));
25114
+ const present = new Set;
25115
+ const changed = [];
25116
+ for (const source of sources) {
25117
+ const id = documentId(source);
25118
+ present.add(id);
25119
+ const stored = existing.get(id);
25120
+ if (stored && stored.path === source.path && stored.mtime_ms === source.mtimeMs && stored.size === source.size) {
25121
+ continue;
25122
+ }
25123
+ try {
25124
+ const text2 = readFileSync22(source.path, "utf-8");
25125
+ changed.push({ source, document: prepare(source, text2), added: !stored });
25126
+ } catch {}
25127
+ }
25128
+ const removed = existingRows.filter((row) => !present.has(row.id));
25129
+ const apply = this.#database.transaction(() => {
25130
+ const deleteTerms = this.#database.query("DELETE FROM wiki_search_fts WHERE document_id = ?");
25131
+ const deleteDocument = this.#database.query("DELETE FROM wiki_search_documents WHERE id = ?");
25132
+ const insertDocument = this.#database.query(`
25133
+ INSERT INTO wiki_search_documents
25134
+ (id, kind, key, path, mtime_ms, size, title, text, date, family)
25135
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
25136
+ `);
25137
+ const insertTerms = this.#database.query(`
25138
+ INSERT INTO wiki_search_fts(document_id, kind, tokens)
25139
+ VALUES (?, ?, ?)
25140
+ `);
25141
+ for (const row of removed) {
25142
+ deleteTerms.run(row.id);
25143
+ deleteDocument.run(row.id);
25144
+ result.removed += 1;
25145
+ }
25146
+ for (const { source, document, added } of changed) {
25147
+ const id = documentId(source);
25148
+ deleteTerms.run(id);
25149
+ deleteDocument.run(id);
25150
+ insertDocument.run(id, source.kind, source.key, source.path, source.mtimeMs, source.size, document.title, document.text, document.date ?? null, document.family ?? null);
25151
+ insertTerms.run(id, source.kind, document.tokens.join(" "));
25152
+ if (added)
25153
+ result.added += 1;
25154
+ else
25155
+ result.updated += 1;
25156
+ }
25157
+ });
25158
+ apply.immediate();
25159
+ return result;
25160
+ }
25161
+ metadata(kinds) {
25162
+ if (kinds.length === 0)
25163
+ return [];
25164
+ const placeholders = kinds.map(() => "?").join(", ");
25165
+ const rows = this.#database.query(`SELECT id, kind, key, title, date
25166
+ FROM wiki_search_documents
25167
+ WHERE kind IN (${placeholders})`).all(...kinds);
25168
+ return rows.map((row) => ({
25169
+ id: row.id,
25170
+ kind: row.kind,
25171
+ key: row.key,
25172
+ title: row.title,
25173
+ ...row.date ? { date: row.date } : {}
25174
+ }));
25175
+ }
25176
+ summaryMetadataByDate(query2) {
25177
+ const conditions = ["kind = 'summary'", "date IS NOT NULL"];
25178
+ const parameters = [];
25179
+ if (query2.year && query2.month) {
25180
+ const month = Number(query2.month);
25181
+ const nextYear = month === 12 ? String(Number(query2.year) + 1) : query2.year;
25182
+ const nextMonth = String(month === 12 ? 1 : month + 1).padStart(2, "0");
25183
+ conditions.push("date >= ?", "date < ?");
25184
+ parameters.push(`${query2.year}-${query2.month}-01`, `${nextYear}-${nextMonth}-01`);
25185
+ } else if (query2.year) {
25186
+ conditions.push("date >= ?", "date < ?");
25187
+ parameters.push(`${query2.year}-01-01`, `${Number(query2.year) + 1}-01-01`);
25188
+ } else if (query2.month) {
25189
+ conditions.push("substr(date, 6, 2) = ?");
25190
+ parameters.push(query2.month);
25191
+ }
25192
+ parameters.push(query2.limit);
25193
+ const rows = this.#database.query(`SELECT id, kind, key, title, date
25194
+ FROM wiki_search_documents
25195
+ WHERE ${conditions.join(" AND ")}
25196
+ ORDER BY date ${query2.direction === "oldest" ? "ASC" : "DESC"}, key ASC
25197
+ LIMIT ?`).all(...parameters);
25198
+ return rows.map((row) => ({
25199
+ id: row.id,
25200
+ kind: row.kind,
25201
+ key: row.key,
25202
+ title: row.title,
25203
+ ...row.date ? { date: row.date } : {}
25204
+ }));
25205
+ }
25206
+ matchingDocumentIds(kinds, terms) {
25207
+ if (kinds.length === 0 || terms.length === 0)
25208
+ return new Set;
25209
+ const kindPlaceholders = kinds.map(() => "?").join(", ");
25210
+ const match = terms.map((term) => `"${term.replaceAll('"', '""')}"`).join(" OR ");
25211
+ const rows = this.#database.query(`SELECT DISTINCT document_id
25212
+ FROM wiki_search_fts
25213
+ WHERE wiki_search_fts MATCH ? AND kind IN (${kindPlaceholders})`).all(match, ...kinds);
25214
+ return new Set(rows.map((row) => row.document_id));
25215
+ }
25216
+ documents(ids) {
25217
+ if (ids.size === 0)
25218
+ return [];
25219
+ const values = [...ids];
25220
+ const rows = [];
25221
+ for (let offset = 0;offset < values.length; offset += 500) {
25222
+ const chunk = values.slice(offset, offset + 500);
25223
+ const placeholders = chunk.map(() => "?").join(", ");
25224
+ rows.push(...this.#database.query(`SELECT id, kind, key, path, title, text, date
25225
+ FROM wiki_search_documents
25226
+ WHERE id IN (${placeholders})`).all(...chunk));
25227
+ }
25228
+ return rows.map((row) => ({
25229
+ id: row.id,
25230
+ kind: row.kind,
25231
+ key: row.key,
25232
+ path: row.path,
25233
+ title: row.title,
25234
+ text: row.text,
25235
+ tokens: [],
25236
+ ...row.date ? { date: row.date } : {}
25237
+ }));
25238
+ }
25239
+ }
25240
+ function resetCorruptWikiSearchIndex(path) {
25241
+ for (const candidate of [path, `${path}-journal`, `${path}-wal`, `${path}-shm`]) {
25242
+ if (!existsSync26(candidate))
25243
+ continue;
25244
+ unlinkSync18(candidate);
25245
+ }
25246
+ }
25247
+ var SEARCH_INDEX_SCHEMA_VERSION = 3;
25248
+ var init_wiki_search_index = __esm(async () => {
25249
+ await init_sqlite();
25250
+ });
25251
+
24923
25252
  // ../../packages/core/src/mcp/wiki-server.ts
24924
25253
  var exports_wiki_server = {};
24925
25254
  __export(exports_wiki_server, {
@@ -24930,14 +25259,14 @@ import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
24930
25259
  import { randomUUID as randomUUID21 } from "crypto";
24931
25260
  import {
24932
25261
  closeSync as closeSync4,
24933
- existsSync as existsSync26,
25262
+ existsSync as existsSync27,
24934
25263
  mkdirSync as mkdirSync24,
24935
25264
  openSync as openSync4,
24936
25265
  readdirSync as readdirSync10,
24937
- readFileSync as readFileSync22,
25266
+ readFileSync as readFileSync23,
24938
25267
  renameSync as renameSync11,
24939
25268
  statSync as statSync15,
24940
- unlinkSync as unlinkSync18,
25269
+ unlinkSync as unlinkSync19,
24941
25270
  writeFileSync as writeFileSync17
24942
25271
  } from "fs";
24943
25272
  import { basename as basename7, dirname as dirname16, join as join36, relative as relative2, resolve as resolve17 } from "path";
@@ -24980,15 +25309,15 @@ function acquireRecoveryGuard(lockPath) {
24980
25309
  } catch (error2) {
24981
25310
  closeSync4(fd);
24982
25311
  try {
24983
- unlinkSync18(guardPath);
25312
+ unlinkSync19(guardPath);
24984
25313
  } catch {}
24985
25314
  throw error2;
24986
25315
  }
24987
25316
  return () => {
24988
25317
  closeSync4(fd);
24989
25318
  try {
24990
- if (readFileSync22(guardPath, "utf-8") === ownerToken)
24991
- unlinkSync18(guardPath);
25319
+ if (readFileSync23(guardPath, "utf-8") === ownerToken)
25320
+ unlinkSync19(guardPath);
24992
25321
  } catch {}
24993
25322
  };
24994
25323
  } catch (error2) {
@@ -25013,7 +25342,7 @@ function acquireFileLock(path) {
25013
25342
  } catch (error2) {
25014
25343
  closeSync4(fd);
25015
25344
  try {
25016
- unlinkSync18(lockPath);
25345
+ unlinkSync19(lockPath);
25017
25346
  } catch {}
25018
25347
  throw error2;
25019
25348
  }
@@ -25021,8 +25350,8 @@ function acquireFileLock(path) {
25021
25350
  closeSync4(fd);
25022
25351
  const releaseGuard = acquireRecoveryGuard(lockPath);
25023
25352
  try {
25024
- if (readFileSync22(lockPath, "utf-8") === ownerToken)
25025
- unlinkSync18(lockPath);
25353
+ if (readFileSync23(lockPath, "utf-8") === ownerToken)
25354
+ unlinkSync19(lockPath);
25026
25355
  } catch {} finally {
25027
25356
  releaseGuard();
25028
25357
  }
@@ -25033,9 +25362,9 @@ function acquireFileLock(path) {
25033
25362
  const releaseGuard = acquireRecoveryGuard(lockPath);
25034
25363
  try {
25035
25364
  try {
25036
- const staleToken = readFileSync22(lockPath, "utf-8");
25037
- if (Date.now() - statSync15(lockPath).mtimeMs > FILE_LOCK_STALE_MS && readFileSync22(lockPath, "utf-8") === staleToken) {
25038
- unlinkSync18(lockPath);
25365
+ const staleToken = readFileSync23(lockPath, "utf-8");
25366
+ if (Date.now() - statSync15(lockPath).mtimeMs > FILE_LOCK_STALE_MS && readFileSync23(lockPath, "utf-8") === staleToken) {
25367
+ unlinkSync19(lockPath);
25039
25368
  continue;
25040
25369
  }
25041
25370
  } catch {
@@ -25058,7 +25387,7 @@ function atomicWriteFile(path, content) {
25058
25387
  renameSync11(temporaryPath, path);
25059
25388
  } catch (error2) {
25060
25389
  try {
25061
- unlinkSync18(temporaryPath);
25390
+ unlinkSync19(temporaryPath);
25062
25391
  } catch {}
25063
25392
  throw error2;
25064
25393
  }
@@ -25174,10 +25503,77 @@ function trigramSimilarity(left, right) {
25174
25503
  overlap += 1;
25175
25504
  return 2 * overlap / (a.size + b.size);
25176
25505
  }
25506
+ function openWikiSearchIndex() {
25507
+ const path = resolve17(runtime().wikiDir, WIKI_SEARCH_INDEX_FILENAME);
25508
+ try {
25509
+ return new WikiSearchIndex(path);
25510
+ } catch (error2) {
25511
+ if (!/malformed|not a database|schema/i.test(String(error2)))
25512
+ return;
25513
+ try {
25514
+ resetCorruptWikiSearchIndex(path);
25515
+ return new WikiSearchIndex(path);
25516
+ } catch {
25517
+ return;
25518
+ }
25519
+ }
25520
+ }
25521
+ function prepareWikiSearchDocument(kind, key, text2) {
25522
+ const titleLine = text2.split(`
25523
+ `).find((line) => /^#+\s*/.test(line)) ?? "";
25524
+ const title = titleLine ? titleLine.replace(/^#+\s*/, "") : basename7(key);
25525
+ return {
25526
+ title,
25527
+ text: text2,
25528
+ tokens: canonicalSearchTokens(`${key} ${title} ${text2}`),
25529
+ ...kind === "summary" && /^\d{4}-\d{2}-\d{2}/.test(key) ? { date: key.slice(0, 10), family: summaryTopicIdentity(key) } : {}
25530
+ };
25531
+ }
25532
+ function indexWikiSearchDocument(kind, key, text2) {
25533
+ const root = kind === "article" ? runtime().articlesDir : runtime().summariesDir;
25534
+ const path = resolve17(root, `${key}.md`);
25535
+ const index = openWikiSearchIndex();
25536
+ if (!index)
25537
+ return false;
25538
+ try {
25539
+ const stat = statSync15(path);
25540
+ index.upsert({ kind, key, path, mtimeMs: stat.mtimeMs, size: stat.size }, prepareWikiSearchDocument(kind, key, text2));
25541
+ return true;
25542
+ } catch {
25543
+ return false;
25544
+ } finally {
25545
+ index.close();
25546
+ }
25547
+ }
25548
+ function wikiIndexPath(kind) {
25549
+ return resolve17(runtime().wikiDir, WIKI_INDEX_FILENAMES[kind]);
25550
+ }
25551
+ function normalizeIndexDescription(raw) {
25552
+ const description = raw.replaceAll(/\s+/g, " ").trim();
25553
+ if (!description)
25554
+ return { error: "A non-empty one-line description is required." };
25555
+ if (description.length > MAX_INDEX_DESCRIPTION_LENGTH) {
25556
+ return {
25557
+ error: `Description must be at most ${MAX_INDEX_DESCRIPTION_LENGTH} characters (got ${description.length}).`
25558
+ };
25559
+ }
25560
+ return { description };
25561
+ }
25562
+ function normalizeWikiSlug(raw) {
25563
+ const slug = raw.trim().replace(/\.md$/i, "").replace(/^\/+/, "");
25564
+ if (!slug)
25565
+ return { error: "A non-empty slug is required." };
25566
+ if (/[[\]|\\]/.test(slug))
25567
+ return { error: "Slug must not contain [ ] | or backslashes." };
25568
+ if (slug.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
25569
+ return { error: "Slug must not contain empty or relative path segments." };
25570
+ }
25571
+ return { slug };
25572
+ }
25177
25573
  function parseWikiIndex(path) {
25178
25574
  let text2;
25179
25575
  try {
25180
- text2 = readFileSync22(path, "utf-8");
25576
+ text2 = readFileSync23(path, "utf-8");
25181
25577
  } catch {
25182
25578
  return [];
25183
25579
  }
@@ -25331,7 +25727,7 @@ function wikiQuery(args) {
25331
25727
  continue;
25332
25728
  const fp = join36(sub, f.name);
25333
25729
  try {
25334
- const text2 = readFileSync22(fp, "utf-8");
25730
+ const text2 = readFileSync23(fp, "utf-8");
25335
25731
  const key = `${entry.name}/${f.name.replace(/\.md$/i, "")}`;
25336
25732
  scored.push({
25337
25733
  kind: label === "articles" ? "article" : "skill",
@@ -25352,7 +25748,7 @@ function wikiQuery(args) {
25352
25748
  }
25353
25749
  const fp = join36(dir, entry.name);
25354
25750
  try {
25355
- const text2 = readFileSync22(fp, "utf-8");
25751
+ const text2 = readFileSync23(fp, "utf-8");
25356
25752
  const key = entry.name.replace(/\.md$/i, "");
25357
25753
  const resultKind = label === "articles" ? "article" : label === "topic" ? "topic" : label === "summaries" ? "summary" : "skill";
25358
25754
  scored.push({
@@ -25466,14 +25862,45 @@ function wikiQuery(args) {
25466
25862
  return 0;
25467
25863
  return score;
25468
25864
  }
25469
- if (kind === "all" || kind === "article")
25470
- scan(runtime().articlesDir, "articles");
25865
+ const bodyKinds = [
25866
+ ...kind === "all" || kind === "article" ? ["article"] : [],
25867
+ ...kind === "all" || kind === "summary" ? ["summary"] : []
25868
+ ];
25869
+ if (bodyKinds.length > 0) {
25870
+ const searchIndex = openWikiSearchIndex();
25871
+ if (searchIndex) {
25872
+ try {
25873
+ const terms = canonicalSearchTokens(normalizedQuery);
25874
+ const ids = searchIndex.matchingDocumentIds(bodyKinds, terms);
25875
+ if (temporalDirection && bodyKinds.includes("summary")) {
25876
+ for (const item of searchIndex.summaryMetadataByDate({
25877
+ direction: temporalDirection === "newest" ? "latest" : "oldest",
25878
+ limit
25879
+ })) {
25880
+ ids.add(item.id);
25881
+ }
25882
+ }
25883
+ for (const document of searchIndex.documents(ids)) {
25884
+ scored.push({
25885
+ kind: document.kind,
25886
+ key: document.key,
25887
+ keyScore: scoreIndexKey(question, document.key),
25888
+ score: scoreMatch(document.text, document.key, document.kind),
25889
+ path: `${document.kind === "article" ? "articles" : "summaries"}/${document.key}.md`,
25890
+ text: document.text,
25891
+ title: document.title,
25892
+ ...document.date ? { date: document.date } : {}
25893
+ });
25894
+ }
25895
+ } catch {} finally {
25896
+ searchIndex.close();
25897
+ }
25898
+ }
25899
+ }
25471
25900
  if (kind === "all" && runtime().surface === "all")
25472
25901
  scan(runtime().skillsDir, "skills");
25473
25902
  if (kind === "all" || kind === "topic")
25474
25903
  scan(runtime().topicsDir, "topic");
25475
- if (kind === "all" || kind === "summary")
25476
- scan(runtime().summariesDir, "summaries");
25477
25904
  const merged = new Map;
25478
25905
  for (const { candidate, keyScore, score } of indexCandidates) {
25479
25906
  merged.set(`${candidate.kind}:${candidate.key}`, {
@@ -25605,7 +26032,7 @@ function wikiTopicBrief(args) {
25605
26032
  const topicKey = wikiSummarySlug(rawTopic.replace(/^topic\//, "").replace(/\.md$/i, ""));
25606
26033
  const filePath = resolve17(runtime().topicsDir, `${topicKey}.md`);
25607
26034
  try {
25608
- const content = readFileSync22(filePath, "utf-8");
26035
+ const content = readFileSync23(filePath, "utf-8");
25609
26036
  return {
25610
26037
  content: [{ type: "text", text: `# Topic Brief: ${topicKey}
25611
26038
 
@@ -25657,7 +26084,7 @@ Adopted topic memory: ${memoryKey}` }]
25657
26084
  return { content: [{ type: "text", text: "Invalid wiki document key." }], isError: true };
25658
26085
  }
25659
26086
  try {
25660
- const content = readFileSync22(filePath, "utf-8");
26087
+ const content = readFileSync23(filePath, "utf-8");
25661
26088
  return {
25662
26089
  content: [{ type: "text", text: `# Wiki ${kind}: ${key}
25663
26090
 
@@ -25677,18 +26104,18 @@ function wikiLastConversation(args) {
25677
26104
  const name = slugify(topicNameFrom(rawTopic));
25678
26105
  const archiveDir = resolve17(runtime().archiveDir, name);
25679
26106
  try {
25680
- let files = existsSync26(archiveDir) ? readdirSync10(archiveDir).filter((f) => f.endsWith(".jsonl")).sort().reverse() : [];
26107
+ let files = existsSync27(archiveDir) ? readdirSync10(archiveDir).filter((f) => f.endsWith(".jsonl")).sort().reverse() : [];
25681
26108
  if (files.length === 0) {
25682
- files = existsSync26(runtime().archiveDir) ? readdirSync10(runtime().archiveDir).filter((f) => f.startsWith(name) && f.endsWith(".jsonl")).sort().reverse() : [];
26109
+ files = existsSync27(runtime().archiveDir) ? readdirSync10(runtime().archiveDir).filter((f) => f.startsWith(name) && f.endsWith(".jsonl")).sort().reverse() : [];
25683
26110
  }
25684
26111
  if (files.length === 0) {
25685
26112
  return {
25686
26113
  content: [{ type: "text", text: `No archive found for topic: ${rawTopic}` }]
25687
26114
  };
25688
26115
  }
25689
- const actualPath = files[0].includes("/") ? files[0] : resolve17(existsSync26(archiveDir) && readdirSync10(archiveDir).includes(files[0]) ? archiveDir : runtime().archiveDir, files[0]);
26116
+ const actualPath = files[0].includes("/") ? files[0] : resolve17(existsSync27(archiveDir) && readdirSync10(archiveDir).includes(files[0]) ? archiveDir : runtime().archiveDir, files[0]);
25690
26117
  try {
25691
- const raw = readFileSync22(actualPath, "utf-8");
26118
+ const raw = readFileSync23(actualPath, "utf-8");
25692
26119
  const lines = raw.trim().split(`
25693
26120
  `).filter(Boolean);
25694
26121
  const turns2 = [];
@@ -25761,7 +26188,7 @@ function skillQuery(args) {
25761
26188
  continue;
25762
26189
  const skillFile = resolve17(runtime().skillsDir, entry.name, "skill.md");
25763
26190
  try {
25764
- const text2 = readFileSync22(skillFile, "utf-8");
26191
+ const text2 = readFileSync23(skillFile, "utf-8");
25765
26192
  const lower = text2.toLowerCase();
25766
26193
  let score = 0;
25767
26194
  for (const w of query2.split(/\s+/).filter(Boolean)) {
@@ -25832,7 +26259,7 @@ ${fresh.join(`
25832
26259
  const skillPath = resolve17(skillDir, "skill.md");
25833
26260
  let finalContent = content;
25834
26261
  try {
25835
- const existing = readFileSync22(skillPath, "utf-8");
26262
+ const existing = readFileSync23(skillPath, "utf-8");
25836
26263
  finalContent = mergeGotchas(content, extractGotchas(existing));
25837
26264
  } catch {}
25838
26265
  writeFileSync17(skillPath, finalContent, "utf-8");
@@ -25845,15 +26272,7 @@ ${fresh.join(`
25845
26272
  ]
25846
26273
  };
25847
26274
  }
25848
- function saveWikiEntry(args) {
25849
- const rawTopic = safeExt(args, ["topic", "topicName"], "");
25850
- const content = safeExt(args, ["content", "text", "body"], "");
25851
- if (!rawTopic)
25852
- return { content: [{ type: "text", text: "Missing topic." }] };
25853
- if (!content)
25854
- return { content: [{ type: "text", text: "Missing content." }] };
25855
- const now = new Date;
25856
- const dateStr = now.toISOString().slice(0, 10);
26275
+ function writeSummaryDocument(rawTopic, content, dateStr) {
25857
26276
  const topicId = runtime().topicId;
25858
26277
  const fileSlug = wikiBriefStorageKey(rawTopic, topicId);
25859
26278
  ensureDir(runtime().summariesDir);
@@ -25880,7 +26299,7 @@ function saveWikiEntry(args) {
25880
26299
  if (topicId && setTopicBrief2) {
25881
26300
  try {
25882
26301
  const briefPath = resolve17(runtime().topicsDir, `${fileSlug}.md`);
25883
- let briefMd = existsSync26(briefPath) ? readFileSync22(briefPath, "utf-8") : undefined;
26302
+ let briefMd = existsSync27(briefPath) ? readFileSync23(briefPath, "utf-8") : undefined;
25884
26303
  if (briefMd === undefined && getTopicBrief2 && !getTopicBrief2(fileSlug)?.briefMd) {
25885
26304
  const legacy = getTopicBrief2(topicId)?.briefMd;
25886
26305
  if (legacy)
@@ -25894,28 +26313,14 @@ function saveWikiEntry(args) {
25894
26313
  sqliteUpdated = true;
25895
26314
  } catch {}
25896
26315
  }
25897
- return {
25898
- content: [
25899
- {
25900
- type: "text",
25901
- text: `Saved summary: summaries/${summaryName}` + (sqliteUpdated ? `
25902
- SQLite latest-summary also updated.` : "")
25903
- }
25904
- ]
25905
- };
26316
+ return { slug: summaryName.replace(/\.md$/i, ""), sqliteUpdated };
25906
26317
  }
25907
- function saveTopicBrief(args) {
25908
- const rawTopic = safeExt(args, ["topic", "topicName"], "");
25909
- const content = safeExt(args, ["content", "brief", "text", "body"], "");
25910
- if (!rawTopic)
25911
- return { content: [{ type: "text", text: "Missing topic." }] };
25912
- if (!content)
25913
- return { content: [{ type: "text", text: "Missing content." }] };
26318
+ function writeTopicDocument(rawTopic, content) {
25914
26319
  const topicId = runtime().topicId;
25915
26320
  const fileSlug = wikiBriefStorageKey(rawTopic, topicId);
25916
26321
  const briefPath = resolve17(runtime().topicsDir, `${fileSlug}.md`);
25917
26322
  ensureDir(dirname16(briefPath));
25918
- const existed = existsSync26(briefPath);
26323
+ const existed = existsSync27(briefPath);
25919
26324
  writeFileSync17(briefPath, content, "utf-8");
25920
26325
  const setTopicBrief2 = runtime().host.setTopicBrief;
25921
26326
  let sqliteUpdated = false;
@@ -25925,68 +26330,227 @@ function saveTopicBrief(args) {
25925
26330
  sqliteUpdated = true;
25926
26331
  } catch {}
25927
26332
  }
26333
+ return { slug: fileSlug, existed, sqliteUpdated };
26334
+ }
26335
+ function writeArticleDocument(slug, content) {
26336
+ const articlePath = resolve17(runtime().articlesDir, `${slug}.md`);
26337
+ ensureDir(dirname16(articlePath));
26338
+ const existed = existsSync27(articlePath);
26339
+ atomicWriteFile(articlePath, content);
26340
+ return { existed };
26341
+ }
26342
+ function wikiWrite(args) {
26343
+ const rawKind = safeExt(args, ["kind", "type"], "");
26344
+ const content = safeExt(args, ["content", "text", "body", "brief"], "");
26345
+ const rawDescription = safeExt(args, ["description", "desc"], "");
26346
+ const rawTopic = safeExt(args, ["topic", "topicName"], "");
26347
+ const rawSlug = safeExt(args, ["slug", "id"], "");
26348
+ const section = safeExt(args, ["section", "category"], undefined);
26349
+ const rawDate = safeExt(args, ["date", "created"], undefined);
26350
+ if (rawKind !== "summary" && rawKind !== "article" && rawKind !== "topic") {
26351
+ return {
26352
+ content: [{ type: "text", text: 'kind must be one of "summary", "article", "topic".' }],
26353
+ isError: true
26354
+ };
26355
+ }
26356
+ if (!content.trim()) {
26357
+ return { content: [{ type: "text", text: "Missing content." }], isError: true };
26358
+ }
26359
+ const descriptionResult = normalizeIndexDescription(rawDescription);
26360
+ if ("error" in descriptionResult) {
26361
+ return { content: [{ type: "text", text: descriptionResult.error }], isError: true };
26362
+ }
26363
+ const { description } = descriptionResult;
26364
+ const today = new Date().toISOString().slice(0, 10);
26365
+ if (rawKind === "article") {
26366
+ const slugResult = normalizeWikiSlug(rawSlug);
26367
+ if ("error" in slugResult) {
26368
+ return { content: [{ type: "text", text: slugResult.error }], isError: true };
26369
+ }
26370
+ if (!section?.trim()) {
26371
+ return {
26372
+ content: [
26373
+ { type: "text", text: "An article requires a section so the catalog stays navigable." }
26374
+ ],
26375
+ isError: true
26376
+ };
26377
+ }
26378
+ const { existed } = writeArticleDocument(slugResult.slug, content);
26379
+ const link2 = indexRowOrPartialWrite("article", {
26380
+ kind: "article",
26381
+ slug: slugResult.slug,
26382
+ description,
26383
+ date: rawDate ?? today,
26384
+ section: section.trim()
26385
+ });
26386
+ if (typeof link2 !== "string")
26387
+ return link2;
26388
+ const searched = indexWikiSearchDocument("article", slugResult.slug, content);
26389
+ return {
26390
+ content: [
26391
+ {
26392
+ type: "text",
26393
+ text: `Saved article: articles/${slugResult.slug}.md (${existed ? "updated" : "created"})
26394
+ Indexed: ${link2}` + (searched ? "" : `
26395
+ ${SEARCH_CACHE_SKIPPED_NOTE}`)
26396
+ }
26397
+ ]
26398
+ };
26399
+ }
26400
+ const topic = rawTopic.trim();
26401
+ if (!topic) {
26402
+ return { content: [{ type: "text", text: "Missing topic." }], isError: true };
26403
+ }
26404
+ if (rawKind === "summary") {
26405
+ const dateStr = rawDate ?? today;
26406
+ const written2 = writeSummaryDocument(topic, content, dateStr);
26407
+ const link2 = indexRowOrPartialWrite("summary", {
26408
+ kind: "summary",
26409
+ slug: written2.slug,
26410
+ description,
26411
+ date: dateStr
26412
+ });
26413
+ if (typeof link2 !== "string")
26414
+ return link2;
26415
+ const searched = indexWikiSearchDocument("summary", written2.slug, content);
26416
+ return {
26417
+ content: [
26418
+ {
26419
+ type: "text",
26420
+ text: `Saved summary: summaries/${written2.slug}.md
26421
+ Indexed: ${link2}` + (written2.sqliteUpdated ? `
26422
+ SQLite latest-summary also updated.` : "") + (searched ? "" : `
26423
+ ${SEARCH_CACHE_SKIPPED_NOTE}`)
26424
+ }
26425
+ ]
26426
+ };
26427
+ }
26428
+ const written = writeTopicDocument(topic, content);
26429
+ const link = indexRowOrPartialWrite("topic", {
26430
+ kind: "topic",
26431
+ slug: written.slug,
26432
+ description,
26433
+ date: rawDate ?? today
26434
+ });
26435
+ if (typeof link !== "string")
26436
+ return link;
25928
26437
  return {
25929
26438
  content: [
25930
26439
  {
25931
26440
  type: "text",
25932
- text: `Saved brief: topic/${fileSlug}.md (${existed ? "updated" : "created"})` + (sqliteUpdated ? `
26441
+ text: `Saved brief: topic/${written.slug}.md (${written.existed ? "updated" : "created"})
26442
+ Indexed: ${link}` + (written.sqliteUpdated ? `
25933
26443
  SQLite brief also updated.` : "")
25934
26444
  }
25935
26445
  ]
25936
26446
  };
25937
26447
  }
25938
- function indexUpsert(args) {
25939
- const slug = safeExt(args, ["slug", "id"], "");
25940
- const desc = safeExt(args, ["description", "desc", "text"], "");
25941
- const kind = safeExt(args, ["kind", "type"], "article");
25942
- const section = safeExt(args, ["section", "category"], undefined);
25943
- const date = safeExt(args, ["date", "created"], undefined);
25944
- if (!slug)
25945
- return { content: [{ type: "text", text: "Missing slug." }] };
25946
- const today = new Date().toISOString().slice(0, 10);
25947
- const dateStr = date ?? today;
25948
- const indexPath = (() => {
25949
- if (kind === "topic")
25950
- return resolve17(runtime().wikiDir, "topic-index.md");
25951
- if (kind === "skill")
25952
- return resolve17(runtime().wikiDir, "skill-index.md");
25953
- return resolve17(runtime().wikiDir, "article-index.md");
25954
- })();
26448
+ function collectIndexableDocuments() {
26449
+ const sources = [];
26450
+ function add(kind, key, path) {
26451
+ try {
26452
+ const stat = statSync15(path);
26453
+ if (!stat.isFile())
26454
+ return;
26455
+ sources.push({ kind, key, path, mtimeMs: stat.mtimeMs, size: stat.size });
26456
+ } catch {}
26457
+ }
26458
+ for (const [root, kind, allowSubdirectories] of [
26459
+ [runtime().articlesDir, "article", true],
26460
+ [runtime().summariesDir, "summary", false]
26461
+ ]) {
26462
+ let entries;
26463
+ try {
26464
+ entries = readdirSync10(root, { withFileTypes: true });
26465
+ } catch {
26466
+ continue;
26467
+ }
26468
+ for (const entry of entries) {
26469
+ if (entry.isFile() && entry.name.endsWith(".md")) {
26470
+ add(kind, entry.name.replace(/\.md$/i, ""), join36(root, entry.name));
26471
+ continue;
26472
+ }
26473
+ if (!entry.isDirectory() || !allowSubdirectories)
26474
+ continue;
26475
+ let nested;
26476
+ try {
26477
+ nested = readdirSync10(join36(root, entry.name), { withFileTypes: true });
26478
+ } catch {
26479
+ continue;
26480
+ }
26481
+ for (const file of nested) {
26482
+ if (!file.isFile() || !file.name.endsWith(".md"))
26483
+ continue;
26484
+ add(kind, `${entry.name}/${file.name.replace(/\.md$/i, "")}`, join36(root, entry.name, file.name));
26485
+ }
26486
+ }
26487
+ }
26488
+ return sources;
26489
+ }
26490
+ function wikiReindex() {
26491
+ const documents = collectIndexableDocuments();
26492
+ const index = openWikiSearchIndex();
26493
+ if (!index) {
26494
+ return {
26495
+ content: [{ type: "text", text: "Could not open the wiki search index." }],
26496
+ isError: true
26497
+ };
26498
+ }
26499
+ let synced;
26500
+ try {
26501
+ synced = index.sync(documents, (source, text2) => prepareWikiSearchDocument(source.kind, source.key, text2));
26502
+ } catch (error2) {
26503
+ return {
26504
+ content: [{ type: "text", text: `Failed to rebuild the search index: ${String(error2)}` }],
26505
+ isError: true
26506
+ };
26507
+ } finally {
26508
+ index.close();
26509
+ }
26510
+ const lines = [
26511
+ `Search index: ${synced.added} added, ${synced.updated} updated, ${synced.removed} removed (${documents.length} documents).`
26512
+ ];
26513
+ for (const kind of ["article", "summary"]) {
26514
+ const rows = new Set(parseWikiIndex(wikiIndexPath(kind)).filter((candidate) => candidate.kind === kind).map((candidate) => candidate.key));
26515
+ const keys = documents.filter((source) => source.kind === kind).map((source) => source.key);
26516
+ const unindexed = keys.filter((key) => !rows.has(key));
26517
+ const tombstones = [...rows].filter((key) => !keys.includes(key));
26518
+ 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(", ")}` : ""));
26519
+ }
26520
+ return { content: [{ type: "text", text: lines.join(`
26521
+ `) }] };
26522
+ }
26523
+ function indexRowOrPartialWrite(label, options) {
26524
+ try {
26525
+ return upsertIndexRow(options);
26526
+ } catch (error2) {
26527
+ return {
26528
+ content: [
26529
+ {
26530
+ type: "text",
26531
+ text: `Wrote the ${label} document but failed to index it (${String(error2)}). Retry the same wiki_write call: both writes are idempotent.`
26532
+ }
26533
+ ],
26534
+ isError: true
26535
+ };
26536
+ }
26537
+ }
26538
+ function upsertIndexRow(options) {
26539
+ const { kind, slug, description, date, section } = options;
26540
+ const indexPath = wikiIndexPath(kind);
25955
26541
  ensureDir(dirname16(indexPath));
25956
26542
  const releaseLock = acquireFileLock(indexPath);
25957
26543
  try {
25958
26544
  let index;
25959
26545
  try {
25960
- index = readFileSync22(indexPath, "utf-8");
26546
+ index = readFileSync23(indexPath, "utf-8");
25961
26547
  } catch {
25962
26548
  index = "";
25963
26549
  }
25964
26550
  const lines = index.split(`
25965
26551
  `);
25966
- const link = (() => {
25967
- switch (kind) {
25968
- case "summary":
25969
- return `- [[summaries/${slug}]] ${desc} (${dateStr})`;
25970
- case "topic":
25971
- return `- [[topic/${slug}]] ${desc} (${dateStr})`;
25972
- case "skill":
25973
- return `- [[skills/${slug}]] ${desc} (${dateStr})`;
25974
- default:
25975
- return `- [[articles/${slug}]] ${desc} (${dateStr})`;
25976
- }
25977
- })();
25978
- const target = (() => {
25979
- switch (kind) {
25980
- case "summary":
25981
- return `summaries/${slug}`;
25982
- case "topic":
25983
- return `topic/${slug}`;
25984
- case "skill":
25985
- return `skills/${slug}`;
25986
- default:
25987
- return `articles/${slug}`;
25988
- }
25989
- })();
26552
+ const target = `${WIKI_INDEX_NAMESPACES[kind]}/${slug}`;
26553
+ const link = `- [[${target}]] ${description} (${date})`;
25990
26554
  const slugPattern = `[[${target}]]`;
25991
26555
  let replaced = false;
25992
26556
  for (let i = lines.length - 1;i >= 0; i--) {
@@ -26024,11 +26588,63 @@ function indexUpsert(args) {
26024
26588
  }
26025
26589
  atomicWriteFile(indexPath, lines.join(`
26026
26590
  `));
26027
- return { content: [{ type: "text", text: `Index updated: ${link}` }] };
26591
+ return link;
26028
26592
  } finally {
26029
26593
  releaseLock();
26030
26594
  }
26031
26595
  }
26596
+ function wikiDocumentExists(kind, slug) {
26597
+ const root = kind === "article" ? runtime().articlesDir : kind === "summary" ? runtime().summariesDir : kind === "topic" ? runtime().topicsDir : runtime().skillsDir;
26598
+ const filePath = resolve17(root, `${slug}.md`);
26599
+ if (relative2(root, filePath).startsWith(".."))
26600
+ return false;
26601
+ try {
26602
+ return statSync15(filePath).isFile();
26603
+ } catch {
26604
+ return false;
26605
+ }
26606
+ }
26607
+ function indexUpsert(args) {
26608
+ const rawSlug = safeExt(args, ["slug", "id"], "");
26609
+ const rawDescription = safeExt(args, ["description", "desc", "text"], "");
26610
+ const rawKind = safeExt(args, ["kind", "type"], "article");
26611
+ const section = safeExt(args, ["section", "category"], undefined);
26612
+ const date = safeExt(args, ["date", "created"], undefined);
26613
+ if (!(rawKind in WIKI_INDEX_FILENAMES)) {
26614
+ return {
26615
+ content: [{ type: "text", text: `Unsupported kind: ${rawKind}` }],
26616
+ isError: true
26617
+ };
26618
+ }
26619
+ const kind = rawKind;
26620
+ const slugResult = normalizeWikiSlug(rawSlug);
26621
+ if ("error" in slugResult) {
26622
+ return { content: [{ type: "text", text: slugResult.error }], isError: true };
26623
+ }
26624
+ const descriptionResult = normalizeIndexDescription(rawDescription);
26625
+ if ("error" in descriptionResult) {
26626
+ return { content: [{ type: "text", text: descriptionResult.error }], isError: true };
26627
+ }
26628
+ if (kind !== "topic" && !wikiDocumentExists(kind, slugResult.slug)) {
26629
+ return {
26630
+ content: [
26631
+ {
26632
+ type: "text",
26633
+ text: `No ${kind} document named ${slugResult.slug}. Use wiki_write to create the document and its catalog row together.`
26634
+ }
26635
+ ],
26636
+ isError: true
26637
+ };
26638
+ }
26639
+ const link = upsertIndexRow({
26640
+ kind,
26641
+ slug: slugResult.slug,
26642
+ description: descriptionResult.description,
26643
+ date: date ?? new Date().toISOString().slice(0, 10),
26644
+ section
26645
+ });
26646
+ return { content: [{ type: "text", text: `Index updated: ${link}` }] };
26647
+ }
26032
26648
  function createWikiMcpServer(context, host2) {
26033
26649
  const surface = context.surface ?? "all";
26034
26650
  const wikiDir = resolve17(host2.wikiRoot);
@@ -26069,8 +26685,8 @@ function createWikiMcpServer(context, host2) {
26069
26685
  wiki_last_conversation: wikiLastConversation,
26070
26686
  skill_query: skillQuery,
26071
26687
  skill_save: skillSave,
26072
- save_wiki_entry: saveWikiEntry,
26073
- save_topic_brief: saveTopicBrief,
26688
+ wiki_write: wikiWrite,
26689
+ wiki_reindex: wikiReindex,
26074
26690
  index_upsert: indexUpsert
26075
26691
  };
26076
26692
  const handler = tools.some((tool) => tool.name === name) ? handlers[name] : undefined;
@@ -26079,10 +26695,11 @@ function createWikiMcpServer(context, host2) {
26079
26695
  });
26080
26696
  return server;
26081
26697
  }
26082
- var wikiRuntime, FILE_LOCK_WAIT_MS = 10, FILE_LOCK_TIMEOUT_MS = 5000, FILE_LOCK_STALE_MS = 30000, lockWaitArray, SEARCH_STOP_WORDS, SEARCH_MONTHS, SEARCH_RECENCY_TERMS, WIKI_TOOLS, SKILL_TOOLS;
26083
- var init_wiki_server = __esm(() => {
26698
+ var wikiRuntime, FILE_LOCK_WAIT_MS = 10, FILE_LOCK_TIMEOUT_MS = 5000, FILE_LOCK_STALE_MS = 30000, lockWaitArray, SEARCH_STOP_WORDS, SEARCH_MONTHS, SEARCH_RECENCY_TERMS, WIKI_SEARCH_INDEX_FILENAME = ".wiki-search-index.sqlite", WIKI_INDEX_FILENAMES, WIKI_INDEX_NAMESPACES, MAX_INDEX_DESCRIPTION_LENGTH = 400, SEARCH_CACHE_SKIPPED_NOTE = "Body-search cache was not updated; catalog retrieval still works. Run wiki_reindex to refill it.", WIKI_TOOLS, SKILL_TOOLS;
26699
+ var init_wiki_server = __esm(async () => {
26084
26700
  init_config();
26085
26701
  init_wiki_summary_names();
26702
+ await init_wiki_search_index();
26086
26703
  wikiRuntime = new AsyncLocalStorage3;
26087
26704
  lockWaitArray = new Int32Array(new SharedArrayBuffer(4));
26088
26705
  SEARCH_STOP_WORDS = new Set([
@@ -26156,6 +26773,18 @@ var init_wiki_server = __esm(() => {
26156
26773
  "\uCD5C\uCD08",
26157
26774
  "\uC624\uB798\uB41C"
26158
26775
  ]);
26776
+ WIKI_INDEX_FILENAMES = {
26777
+ article: "article-index.md",
26778
+ summary: "summary-index.md",
26779
+ topic: "topic-index.md",
26780
+ skill: "skill-index.md"
26781
+ };
26782
+ WIKI_INDEX_NAMESPACES = {
26783
+ article: "articles",
26784
+ summary: "summaries",
26785
+ topic: "topic",
26786
+ skill: "skills"
26787
+ };
26159
26788
  WIKI_TOOLS = [
26160
26789
  {
26161
26790
  name: "wiki_query",
@@ -26237,35 +26866,46 @@ var init_wiki_server = __esm(() => {
26237
26866
  }
26238
26867
  },
26239
26868
  {
26240
- name: "save_wiki_entry",
26241
- description: "Save a session summary directly to wiki/summaries/.",
26869
+ name: "wiki_write",
26870
+ 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.",
26242
26871
  inputSchema: {
26243
26872
  type: "object",
26244
26873
  properties: {
26245
- topic: { type: "string", description: "Topic name of the session" },
26246
- content: { type: "string", description: "Session summary in markdown" }
26874
+ kind: {
26875
+ type: "string",
26876
+ enum: ["summary", "article", "topic"],
26877
+ description: "Which document to write"
26878
+ },
26879
+ content: { type: "string", description: "Document body in markdown" },
26880
+ description: {
26881
+ type: "string",
26882
+ description: "Single-line catalog description; required so the index stays useful"
26883
+ },
26884
+ topic: {
26885
+ type: "string",
26886
+ description: "Topic name; required for kind=summary and kind=topic"
26887
+ },
26888
+ slug: {
26889
+ type: "string",
26890
+ description: "Article slug in kebab-case; required for kind=article"
26891
+ },
26892
+ section: {
26893
+ type: "string",
26894
+ description: "H2 catalog section; required for kind=article"
26895
+ },
26896
+ date: { type: "string", description: "Override entry date YYYY-MM-DD (default: today)" }
26247
26897
  },
26248
- required: ["topic", "content"]
26898
+ required: ["kind", "content", "description"]
26249
26899
  }
26250
26900
  },
26251
26901
  {
26252
- name: "save_topic_brief",
26253
- 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).",
26254
- inputSchema: {
26255
- type: "object",
26256
- properties: {
26257
- topic: { type: "string", description: "Topic name of the session" },
26258
- content: {
26259
- type: "string",
26260
- description: "Full accumulated topic brief in markdown (persona layer + recent work)"
26261
- }
26262
- },
26263
- required: ["topic", "content"]
26264
- }
26902
+ name: "wiki_reindex",
26903
+ 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.",
26904
+ inputSchema: { type: "object", properties: {} }
26265
26905
  },
26266
26906
  {
26267
26907
  name: "index_upsert",
26268
- description: "Upsert an entry in wiki/article-index.md, topic-index.md, or skill-index.md.",
26908
+ description: "Correct the description or section of an entry that already exists. Cannot create an entry \u2014 use wiki_write for that.",
26269
26909
  inputSchema: {
26270
26910
  type: "object",
26271
26911
  properties: {
@@ -26342,12 +26982,12 @@ __export(exports_activity_log, {
26342
26982
  readSentFilesForTopic: () => readSentFilesForTopic
26343
26983
  });
26344
26984
  import {
26345
- existsSync as existsSync27,
26985
+ existsSync as existsSync28,
26346
26986
  mkdirSync as mkdirSync25,
26347
26987
  readdirSync as readdirSync11,
26348
26988
  renameSync as renameSync12,
26349
26989
  statSync as statSync16,
26350
- unlinkSync as unlinkSync19,
26990
+ unlinkSync as unlinkSync20,
26351
26991
  writeFileSync as writeFileSync18
26352
26992
  } from "fs";
26353
26993
  import { basename as basename8, join as join37 } from "path";
@@ -26400,7 +27040,7 @@ function rotateOldLogs() {
26400
27040
  if (freed >= toFree)
26401
27041
  break;
26402
27042
  try {
26403
- unlinkSync19(join37(logDir, file.name));
27043
+ unlinkSync20(join37(logDir, file.name));
26404
27044
  freed += file.size;
26405
27045
  logger.info({ file: file.name, sizeKB: (file.size / 1024).toFixed(0) }, "Rotated out old log");
26406
27046
  } catch (e) {
@@ -26426,7 +27066,7 @@ function entryMatchesTopic(e, topicName) {
26426
27066
  }
26427
27067
  function readSentFilesForTopic(userId, topicName) {
26428
27068
  const file = join37(resolveStorageUsersLogDir(), String(userId), "sent-files.jsonl");
26429
- if (!existsSync27(file))
27069
+ if (!existsSync28(file))
26430
27070
  return [];
26431
27071
  try {
26432
27072
  return readJsonlLines(file).map((line) => JSON.parse(line)).filter((e) => entryMatchesTopic(e, topicName));
@@ -26437,7 +27077,7 @@ function readSentFilesForTopic(userId, topicName) {
26437
27077
  }
26438
27078
  function removeSentFilesForTopic(userId, topicName) {
26439
27079
  const file = join37(resolveStorageUsersLogDir(), String(userId), "sent-files.jsonl");
26440
- if (!existsSync27(file))
27080
+ if (!existsSync28(file))
26441
27081
  return;
26442
27082
  try {
26443
27083
  const remaining = readJsonlLines(file).filter((line) => {
@@ -26452,7 +27092,7 @@ function removeSentFilesForTopic(userId, topicName) {
26452
27092
  `)}
26453
27093
  `);
26454
27094
  } else {
26455
- unlinkSync19(file);
27095
+ unlinkSync20(file);
26456
27096
  }
26457
27097
  } catch (e) {
26458
27098
  logger.warn({ err: e, userId, topicName }, "Failed to remove sent-files for topic");
@@ -26985,6 +27625,7 @@ __export(exports_storage_public, {
26985
27625
  setTopicAgentAndSession: () => setTopicAgentAndSession,
26986
27626
  setTopicAgentAndClearSession: () => setTopicAgentAndClearSession,
26987
27627
  setTopicAgent: () => setTopicAgent,
27628
+ setTopicAccessModes: () => setTopicAccessModes,
26988
27629
  setSessionForTopic: () => setSessionForTopic,
26989
27630
  setLastShownConfig: () => setLastShownConfig,
26990
27631
  setGlobalAiName: () => setGlobalAiName,
@@ -27250,7 +27891,7 @@ async function buildHostedSurfaceServer(surface, context) {
27250
27891
  case "wiki":
27251
27892
  case "skills": {
27252
27893
  const [{ createWikiMcpServer: createWikiMcpServer2 }, storage, { WORKSPACE_DIR: WORKSPACE_DIR2 }] = await Promise.all([
27253
- Promise.resolve().then(() => (init_wiki_server(), exports_wiki_server)),
27894
+ init_wiki_server().then(() => exports_wiki_server),
27254
27895
  init_storage_public().then(() => exports_storage_public),
27255
27896
  init_mcp_runtime_host().then(() => exports_mcp_runtime_host)
27256
27897
  ]);
@@ -27961,7 +28602,7 @@ var init_spec = __esm(() => {
27961
28602
  });
27962
28603
 
27963
28604
  // ../../packages/mcp-host/src/manifest.ts
27964
- import { existsSync as existsSync28, mkdirSync as mkdirSync26, readFileSync as readFileSync23, renameSync as renameSync13, writeFileSync as writeFileSync19 } from "fs";
28605
+ import { existsSync as existsSync29, mkdirSync as mkdirSync26, readFileSync as readFileSync24, renameSync as renameSync13, writeFileSync as writeFileSync19 } from "fs";
27965
28606
  import { dirname as dirname17 } from "path";
27966
28607
  import { z as z14 } from "zod";
27967
28608
 
@@ -28006,11 +28647,11 @@ class McpManifest {
28006
28647
  return this.entries.get(key)?.enabled ?? false;
28007
28648
  }
28008
28649
  load() {
28009
- if (!existsSync28(this.file))
28650
+ if (!existsSync29(this.file))
28010
28651
  return;
28011
28652
  let parsed;
28012
28653
  try {
28013
- parsed = manifestFileSchema.parse(JSON.parse(readFileSync23(this.file, "utf8")));
28654
+ parsed = manifestFileSchema.parse(JSON.parse(readFileSync24(this.file, "utf8")));
28014
28655
  } catch (e) {
28015
28656
  throw new Error(`Invalid MCP manifest at ${this.file}: ${e instanceof Error ? e.message : String(e)}`);
28016
28657
  }
@@ -28060,9 +28701,9 @@ import { execFileSync as execFileSync8, spawn as spawn8 } from "child_process";
28060
28701
  import {
28061
28702
  mkdirSync as mkdirSync27,
28062
28703
  readdirSync as readdirSync12,
28063
- readFileSync as readFileSync24,
28704
+ readFileSync as readFileSync25,
28064
28705
  renameSync as renameSync14,
28065
- unlinkSync as unlinkSync20,
28706
+ unlinkSync as unlinkSync21,
28066
28707
  writeFileSync as writeFileSync20
28067
28708
  } from "fs";
28068
28709
  import { connect, createServer as createServer3 } from "net";
@@ -28368,7 +29009,7 @@ class McpHost {
28368
29009
  if (file.includes(".tmp-"))
28369
29010
  continue;
28370
29011
  try {
28371
- const port = Number.parseInt(readFileSync24(join38(this.portsDir, file), "utf8").trim(), 10);
29012
+ const port = Number.parseInt(readFileSync25(join38(this.portsDir, file), "utf8").trim(), 10);
28372
29013
  if (!Number.isNaN(port))
28373
29014
  claims.set(file, port);
28374
29015
  } catch {}
@@ -28388,7 +29029,7 @@ class McpHost {
28388
29029
  }
28389
29030
  deletePortFile(fileName) {
28390
29031
  try {
28391
- unlinkSync20(join38(this.portsDir, fileName));
29032
+ unlinkSync21(join38(this.portsDir, fileName));
28392
29033
  } catch {}
28393
29034
  }
28394
29035
  cleanupRunning(rkey, inst) {
@@ -28493,9 +29134,9 @@ var init_src3 = __esm(() => {
28493
29134
  import { randomUUID as randomUUID24 } from "crypto";
28494
29135
  import {
28495
29136
  copyFileSync as copyFileSync4,
28496
- existsSync as existsSync29,
29137
+ existsSync as existsSync30,
28497
29138
  mkdirSync as mkdirSync28,
28498
- readFileSync as readFileSync25,
29139
+ readFileSync as readFileSync26,
28499
29140
  rmSync as rmSync9,
28500
29141
  statSync as statSync18,
28501
29142
  writeFileSync as writeFileSync21
@@ -28531,7 +29172,7 @@ class NodeFileStore {
28531
29172
  if (!FILE_ID_RE.test(fileId))
28532
29173
  return null;
28533
29174
  try {
28534
- const parsed = JSON.parse(readFileSync25(this.#metadataPath(fileId), "utf8"));
29175
+ const parsed = JSON.parse(readFileSync26(this.#metadataPath(fileId), "utf8"));
28535
29176
  if (typeof parsed.filename !== "string" || typeof parsed.mimeType !== "string" || typeof parsed.sizeBytes !== "number" || typeof parsed.savedName !== "string") {
28536
29177
  return null;
28537
29178
  }
@@ -28554,7 +29195,7 @@ class NodeFileStore {
28554
29195
  hooks = {
28555
29196
  resolveAttachmentByFileId: (fileId) => {
28556
29197
  const metadata = this.#metadata(fileId);
28557
- if (!metadata || !existsSync29(join39(this.uploadDir, metadata.savedName)))
29198
+ if (!metadata || !existsSync30(join39(this.uploadDir, metadata.savedName)))
28558
29199
  return null;
28559
29200
  return this.#attachment(fileId, metadata);
28560
29201
  },
@@ -28563,7 +29204,7 @@ class NodeFileStore {
28563
29204
  if (!metadata)
28564
29205
  return null;
28565
29206
  const path = join39(this.uploadDir, metadata.savedName);
28566
- return existsSync29(path) ? path : null;
29207
+ return existsSync30(path) ? path : null;
28567
29208
  },
28568
29209
  storeLocalFileAsUpload: (absPath, access = {}) => this.store(absPath, access),
28569
29210
  deleteFilesForTopic: (topicId) => this.deleteForTopic(topicId)
@@ -28606,7 +29247,7 @@ class NodeFileStore {
28606
29247
  if (!allowed)
28607
29248
  return null;
28608
29249
  const path = join39(this.uploadDir, metadata.savedName);
28609
- if (!existsSync29(path))
29250
+ if (!existsSync30(path))
28610
29251
  return null;
28611
29252
  return new Response(Bun.file(path), {
28612
29253
  headers: {
@@ -28775,12 +29416,12 @@ function createPollingSseStream(req, options) {
28775
29416
  // ../../packages/node/src/control.ts
28776
29417
  import { randomUUID as randomUUID25, timingSafeEqual as timingSafeEqual4 } from "crypto";
28777
29418
  import {
28778
- chmodSync as chmodSync5,
28779
- existsSync as existsSync30,
29419
+ chmodSync as chmodSync6,
29420
+ existsSync as existsSync31,
28780
29421
  mkdirSync as mkdirSync29,
28781
- readFileSync as readFileSync26,
29422
+ readFileSync as readFileSync27,
28782
29423
  renameSync as renameSync15,
28783
- unlinkSync as unlinkSync21,
29424
+ unlinkSync as unlinkSync22,
28784
29425
  writeFileSync as writeFileSync22
28785
29426
  } from "fs";
28786
29427
  import { dirname as dirname18, resolve as resolve21 } from "path";
@@ -29287,16 +29928,16 @@ function writeNodeDaemonInfo(port, startedAt) {
29287
29928
  const temporary = `${NODE_DAEMON_INFO_PATH}.${process.pid}.${randomUUID25()}.tmp`;
29288
29929
  writeFileSync22(temporary, `${JSON.stringify(info, null, 2)}
29289
29930
  `, { mode: 384 });
29290
- chmodSync5(temporary, 384);
29931
+ chmodSync6(temporary, 384);
29291
29932
  renameSync15(temporary, NODE_DAEMON_INFO_PATH);
29292
- chmodSync5(NODE_DAEMON_INFO_PATH, 384);
29933
+ chmodSync6(NODE_DAEMON_INFO_PATH, 384);
29293
29934
  return info;
29294
29935
  }
29295
29936
  function readNodeDaemonInfo() {
29296
- if (!existsSync30(NODE_DAEMON_INFO_PATH))
29937
+ if (!existsSync31(NODE_DAEMON_INFO_PATH))
29297
29938
  return null;
29298
29939
  try {
29299
- const value = JSON.parse(readFileSync26(NODE_DAEMON_INFO_PATH, "utf8"));
29940
+ const value = JSON.parse(readFileSync27(NODE_DAEMON_INFO_PATH, "utf8"));
29300
29941
  if (value?.schemaVersion !== 1 || value.stateDir !== STATE_DIR || !Number.isInteger(value.protocolVersion) || !Number.isInteger(value.pid) || !Number.isInteger(value.port) || value.port <= 0) {
29301
29942
  return null;
29302
29943
  }
@@ -29310,7 +29951,7 @@ function removeNodeDaemonInfo(expected) {
29310
29951
  if (!current3 || current3.pid !== expected.pid || current3.port !== expected.port)
29311
29952
  return;
29312
29953
  try {
29313
- unlinkSync21(NODE_DAEMON_INFO_PATH);
29954
+ unlinkSync22(NODE_DAEMON_INFO_PATH);
29314
29955
  } catch {}
29315
29956
  }
29316
29957
  async function fetchWithTimeout(url, init, timeoutMs) {
@@ -29584,7 +30225,7 @@ var init_schedule = __esm(() => {
29584
30225
 
29585
30226
  // ../../packages/module-cron/src/scripts.ts
29586
30227
  import { spawn as spawn9 } from "child_process";
29587
- import { existsSync as existsSync31, mkdirSync as mkdirSync30, readdirSync as readdirSync13 } from "fs";
30228
+ import { existsSync as existsSync32, mkdirSync as mkdirSync30, readdirSync as readdirSync13 } from "fs";
29588
30229
  import { resolve as resolve22, sep as sep2 } from "path";
29589
30230
  function validateCronScriptName(script) {
29590
30231
  if (!/^[A-Za-z0-9_][A-Za-z0-9_.-]*\.py$/.test(script) || script.includes("..") || script.includes("/") || script.includes("\\")) {
@@ -29607,7 +30248,7 @@ function listCronScripts() {
29607
30248
  }
29608
30249
  function cronScriptExists(script) {
29609
30250
  try {
29610
- return existsSync31(resolveCronScriptPath(script));
30251
+ return existsSync32(resolveCronScriptPath(script));
29611
30252
  } catch {
29612
30253
  return false;
29613
30254
  }
@@ -29639,7 +30280,7 @@ async function runCronPromptScript(options) {
29639
30280
  if (options.signal?.aborted)
29640
30281
  throw new CronScriptError("script run aborted");
29641
30282
  const scriptPath = resolveCronScriptPath(options.script);
29642
- if (!existsSync31(scriptPath))
30283
+ if (!existsSync32(scriptPath))
29643
30284
  throw new CronScriptError(`cron script not found: ${options.script}`);
29644
30285
  const timeoutMs = Math.max(5000, options.timeoutMs ?? DEFAULT_SCRIPT_TIMEOUT_MS);
29645
30286
  const outputLimitBytes = Math.max(1024, options.outputLimitBytes ?? DEFAULT_OUTPUT_LIMIT_BYTES);
@@ -30840,7 +31481,7 @@ var init_scheduler = __esm(async () => {
30840
31481
  });
30841
31482
 
30842
31483
  // ../../packages/module-cron/src/module.ts
30843
- import { existsSync as existsSync32 } from "fs";
31484
+ import { existsSync as existsSync33 } from "fs";
30844
31485
  import { fileURLToPath as fileURLToPath2 } from "url";
30845
31486
  function envMs(name, fallback) {
30846
31487
  const parsed = Number.parseInt(process.env[name] ?? "", 10);
@@ -30871,7 +31512,7 @@ function createCronModule(options = {}) {
30871
31512
  if (event.type === "topic-deleted")
30872
31513
  trackCleanup(resetCronTopicContext(event.topicId));
30873
31514
  });
30874
- const mcpServerFile = options.mcpServerFile ?? (existsSync32(SOURCE_MCP_SERVER_FILE) ? SOURCE_MCP_SERVER_FILE : PACKAGED_MCP_SERVER_FILE);
31515
+ const mcpServerFile = options.mcpServerFile ?? (existsSync33(SOURCE_MCP_SERVER_FILE) ? SOURCE_MCP_SERVER_FILE : PACKAGED_MCP_SERVER_FILE);
30875
31516
  const unregisterMcp = host2.registerRuntimeMcpServer("cron-manager", {
30876
31517
  scopes: ["forum", "manager"],
30877
31518
  forumRequired: true,
@@ -31950,7 +32591,7 @@ function setTopics(state, topics, preferredTitle) {
31950
32591
  activeTopicId: nextActive,
31951
32592
  scrollOffset: nextActive === state.activeTopicId ? state.scrollOffset : 0,
31952
32593
  askChoiceIndex: nextActive === state.activeTopicId ? state.askChoiceIndex : 0,
31953
- topicPickerIndex: state.overlay === "topics" ? pickedTopicIndex : Math.max(0, orderedTopics.findIndex((topic) => topic.id === nextActive)),
32594
+ topicPickerIndex: state.overlay === "topics" || state.overlay === "confirm-share" ? pickedTopicIndex : Math.max(0, orderedTopics.findIndex((topic) => topic.id === nextActive)),
31954
32595
  topicPickerBackgroundId: state.topicPickerBackgroundId
31955
32596
  });
31956
32597
  }
@@ -32369,7 +33010,7 @@ var init_context_usage = __esm(async () => {
32369
33010
 
32370
33011
  // ../../adapters/terminal/src/path-suggest.ts
32371
33012
  import { execFile as execFile3 } from "child_process";
32372
- import { existsSync as existsSync33, readdirSync as readdirSync14, statSync as statSync19 } from "fs";
33013
+ import { existsSync as existsSync34, readdirSync as readdirSync14, statSync as statSync19 } from "fs";
32373
33014
  import { homedir as homedir10 } from "os";
32374
33015
  import { basename as basename11, dirname as dirname19, join as join40, sep as sep3 } from "path";
32375
33016
  import { promisify as promisify3 } from "util";
@@ -32618,7 +33259,7 @@ function fragmentToAbsolutePath(frag) {
32618
33259
  }
32619
33260
  function fragmentResolves(frag) {
32620
33261
  try {
32621
- return existsSync33(fragmentToAbsolutePath(frag));
33262
+ return existsSync34(fragmentToAbsolutePath(frag));
32622
33263
  } catch {
32623
33264
  return false;
32624
33265
  }
@@ -33612,14 +34253,16 @@ function subagentTreePrefix(topic, topics, rootTopicId) {
33612
34253
  function topicPickerHints(topicPickerRoot) {
33613
34254
  const exit = topicPickerRoot ? "Esc/Ctrl-C exit" : "Esc close \xB7 Ctrl-C exit; work continues";
33614
34255
  const shortExit = topicPickerRoot ? "Esc/Ctrl-C exit" : "Esc close";
33615
- return [
34256
+ const candidates = [
34257
+ `\u2191\u2193 select \xB7 Enter open \xB7 type to filter \xB7 Ctrl-N new \xB7 Ctrl-D delete \xB7 Ctrl-P public/private \xB7 ${exit}`,
34258
+ `\u2191\u2193 select \xB7 Enter open \xB7 type to filter \xB7 Ctrl-N new \xB7 Ctrl-D delete \xB7 Ctrl-P public/private \xB7 ${shortExit}`,
33616
34259
  `\u2191\u2193 select \xB7 Enter open \xB7 type to filter \xB7 Ctrl-N new \xB7 Ctrl-D delete \xB7 ${exit}`,
33617
- `\u2191\u2193 select \xB7 Enter open \xB7 type to filter \xB7 Ctrl-N new \xB7 Ctrl-D delete \xB7 ${shortExit}`,
33618
- `\u2191\u2193 select \xB7 Enter open \xB7 type to filter \xB7 Ctrl-N/D \xB7 ${shortExit}`,
33619
- "\u2191\u2193 \xB7 Enter \xB7 type to filter \xB7 Ctrl-N/D",
33620
- "type to filter \xB7 Ctrl-N/D",
33621
- "Ctrl-N/D"
34260
+ `\u2191\u2193 select \xB7 Enter open \xB7 type to filter \xB7 Ctrl-N/D/P \xB7 ${shortExit}`,
34261
+ "\u2191\u2193 \xB7 Enter \xB7 type to filter \xB7 Ctrl-N/D/P",
34262
+ "type to filter \xB7 Ctrl-N/D/P",
34263
+ "Ctrl-N/D/P"
33622
34264
  ];
34265
+ return [...new Set(candidates)].sort((a, b) => displayWidth(b) - displayWidth(a));
33623
34266
  }
33624
34267
  function topicOverlayCursor(state, width, bodyHeight) {
33625
34268
  if (state.overlay !== "topics")
@@ -33975,6 +34618,34 @@ function conversationLines(state, width, height, animationFrame = 0, nowMs = ter
33975
34618
  line(" Press y to delete or n to cancel.", { fg: theme.amber })
33976
34619
  ].slice(0, height);
33977
34620
  }
34621
+ if (state.overlay === "confirm-share") {
34622
+ const topic = state.topics.find((candidate) => candidate.id === state.pendingShareTopicId);
34623
+ const subagents = (() => {
34624
+ if (!topic)
34625
+ return 0;
34626
+ const reached = new Set([topic.id]);
34627
+ for (;; ) {
34628
+ const next = state.topics.filter((candidate) => candidate.isSubagent && !reached.has(candidate.id) && candidate.parentTopicId !== undefined && reached.has(candidate.parentTopicId));
34629
+ if (next.length === 0)
34630
+ break;
34631
+ for (const candidate of next)
34632
+ reached.add(candidate.id);
34633
+ }
34634
+ return reached.size - 1;
34635
+ })();
34636
+ const scope = subagents > 0 ? ` Its ${subagents} subagent room${subagents === 1 ? "" : "s"} become public too.` : " Subagent rooms spawned from it will be public too.";
34637
+ return [
34638
+ line(""),
34639
+ line(` Make \u201C${topic?.title ?? "this topic"}\u201D public?`, {
34640
+ fg: theme.amber,
34641
+ bold: true
34642
+ }),
34643
+ line(""),
34644
+ line(" The transcript becomes visible to the connected Otium Hub."),
34645
+ line(scope),
34646
+ line(" Press y to publish or n to cancel.", { fg: theme.amber })
34647
+ ].slice(0, height);
34648
+ }
33978
34649
  const all = conversationContentLines(state, width, animationFrame, nowMs, includeTasks);
33979
34650
  const { contentHeight, maxOffset, offset } = conversationViewport(all.length, height, state.scrollOffset);
33980
34651
  const end = all.length - offset;
@@ -34242,6 +34913,9 @@ function footerHintText(state) {
34242
34913
  }
34243
34914
  return state.topicPickerRoot ? ["Esc/Ctrl-C exit ", "Esc exit "] : ["Esc close \xB7 Ctrl-C exit; work continues ", "Esc close \xB7 Ctrl-C exit ", "Esc "];
34244
34915
  }
34916
+ if (state.overlay === "confirm-share") {
34917
+ return ["Y make public \xB7 N cancel ", "Y/N "];
34918
+ }
34245
34919
  if (state.overlay === "background-session") {
34246
34920
  return [
34247
34921
  "wheel/PgUp/PgDn scroll \xB7 Esc back \xB7 read-only ",
@@ -37198,6 +37872,16 @@ class TerminalApp {
37198
37872
  }
37199
37873
  return;
37200
37874
  }
37875
+ if (this.#state.overlay === "confirm-share") {
37876
+ const key = chunk.toLowerCase();
37877
+ if (CONFIRM_KEYS.has(key))
37878
+ this.#confirmTopicShare();
37879
+ else if (CANCEL_KEYS.has(key) || chunk === "\x1B") {
37880
+ this.#state = { ...this.#state, overlay: "topics", pendingShareTopicId: undefined };
37881
+ this.#queueRender();
37882
+ }
37883
+ return;
37884
+ }
37201
37885
  if (this.#state.overlay === "background-session") {
37202
37886
  if (chunk === "\x1B") {
37203
37887
  this.#state = { ...this.#state, overlay: "topics" };
@@ -37506,6 +38190,22 @@ class TerminalApp {
37506
38190
  this.#queueRender();
37507
38191
  return;
37508
38192
  }
38193
+ if (chunk === TOGGLE_ACCESS_MODE_KEY) {
38194
+ const topic = pickedTopic(this.#state);
38195
+ if (topic) {
38196
+ this.#requestAccessModeToggle(topic);
38197
+ return;
38198
+ }
38199
+ if (!this.#state.topicPickerBackgroundId)
38200
+ return;
38201
+ this.#state = {
38202
+ ...this.#state,
38203
+ notice: "Background sessions are read-only",
38204
+ noticeLevel: "warn"
38205
+ };
38206
+ this.#queueRender();
38207
+ return;
38208
+ }
37509
38209
  if (FILTER_BACKSPACE_KEYS.has(chunk)) {
37510
38210
  if (this.#state.topicFilter.length === 0)
37511
38211
  return;
@@ -37948,6 +38648,65 @@ class TerminalApp {
37948
38648
  };
37949
38649
  this.#queueRender();
37950
38650
  }
38651
+ async#requestAccessModeToggle(topic) {
38652
+ if (topic.kind !== "agent") {
38653
+ this.#state = {
38654
+ ...this.#state,
38655
+ notice: `${topic.kind === "manager" ? "Manager" : "Channel"} topics cannot be published`,
38656
+ noticeLevel: "warn"
38657
+ };
38658
+ this.#queueRender();
38659
+ return;
38660
+ }
38661
+ if (topic.isSubagent) {
38662
+ this.#state = {
38663
+ ...this.#state,
38664
+ notice: "Subagent rooms inherit privacy from their parent topic",
38665
+ noticeLevel: "warn"
38666
+ };
38667
+ this.#queueRender();
38668
+ return;
38669
+ }
38670
+ if (topic.accessMode === "shared") {
38671
+ await this.#applyAccessMode(topic, "private");
38672
+ return;
38673
+ }
38674
+ this.#state = {
38675
+ ...this.#state,
38676
+ overlay: "confirm-share",
38677
+ pendingShareTopicId: topic.id
38678
+ };
38679
+ this.#queueRender();
38680
+ }
38681
+ async#confirmTopicShare() {
38682
+ const topic = this.#state.topics.find((candidate) => candidate.id === this.#state.pendingShareTopicId);
38683
+ this.#state = { ...this.#state, overlay: "topics", pendingShareTopicId: undefined };
38684
+ if (!topic) {
38685
+ this.#state = {
38686
+ ...this.#state,
38687
+ notice: "That topic no longer exists",
38688
+ noticeLevel: "warn"
38689
+ };
38690
+ this.#queueRender();
38691
+ return;
38692
+ }
38693
+ await this.#applyAccessMode(topic, "shared");
38694
+ }
38695
+ async#applyAccessMode(topic, accessMode) {
38696
+ this.#state = { ...this.#state, overlay: "topics" };
38697
+ try {
38698
+ const notice = await this.#client.setAccessMode(topic, accessMode);
38699
+ await this.#refreshTopics();
38700
+ this.#state = { ...this.#state, notice, noticeLevel: "success" };
38701
+ } catch (error2) {
38702
+ this.#state = {
38703
+ ...this.#state,
38704
+ notice: error2 instanceof Error ? error2.message : String(error2),
38705
+ noticeLevel: "error"
38706
+ };
38707
+ }
38708
+ this.#queueRender();
38709
+ }
37951
38710
  #requestTopicDelete(topic) {
37952
38711
  if (!topic)
37953
38712
  return;
@@ -38226,6 +38985,7 @@ class TerminalApp {
38226
38985
  ...this.#state,
38227
38986
  overlay: null,
38228
38987
  pendingDeleteTopicId: undefined,
38988
+ pendingShareTopicId: undefined,
38229
38989
  creatingTopic: false,
38230
38990
  notice: "Input cleared",
38231
38991
  noticeLevel: "info"
@@ -38288,7 +39048,7 @@ class TerminalApp {
38288
39048
  await this.#client.stop();
38289
39049
  }
38290
39050
  }
38291
- var KITTY_KEYBOARD_PUSH = "\x1B[>1u", KITTY_KEYBOARD_POP = "\x1B[<u", CANVAS_RGB, ENTER_ALT_SCREEN, EXIT_ALT_SCREEN, ABORT_ENTER_ALT_SCREEN, NEW_TOPIC_KEY = "\x0E", DELETE_TOPIC_KEY = "\x04", FILTER_BACKSPACE_KEYS, CONFIRM_KEYS, CANCEL_KEYS, INPUT_CARRY_FLUSH_MS = 15;
39051
+ var KITTY_KEYBOARD_PUSH = "\x1B[>1u", KITTY_KEYBOARD_POP = "\x1B[<u", CANVAS_RGB, ENTER_ALT_SCREEN, EXIT_ALT_SCREEN, ABORT_ENTER_ALT_SCREEN, NEW_TOPIC_KEY = "\x0E", DELETE_TOPIC_KEY = "\x04", TOGGLE_ACCESS_MODE_KEY = "\x10", FILTER_BACKSPACE_KEYS, CONFIRM_KEYS, CANCEL_KEYS, INPUT_CARRY_FLUSH_MS = 15;
38292
39052
  var init_app = __esm(async () => {
38293
39053
  init_color_depth();
38294
39054
  init_commands();
@@ -39312,7 +40072,7 @@ function renderOutbound(text2, maxLen = 4096) {
39312
40072
  }
39313
40073
 
39314
40074
  // ../../adapters/telegram/src/adapter.ts
39315
- import { existsSync as existsSync34 } from "fs";
40075
+ import { existsSync as existsSync35 } from "fs";
39316
40076
  import { basename as basename12, extname as extname5 } from "path";
39317
40077
  function startTelegramAdapter(opts) {
39318
40078
  const { client, forumChatId } = opts;
@@ -39800,7 +40560,7 @@ function startTelegramAdapter(opts) {
39800
40560
  logger.warn({ path, chatId }, "telegram adapter: blocked sensitive file path");
39801
40561
  return { ref: null, delivered: false, error: "path matches the sensitive-file blacklist" };
39802
40562
  }
39803
- if (!existsSync34(path)) {
40563
+ if (!existsSync35(path)) {
39804
40564
  const sent = await client.sendMessage(chatId, `File: ${path}`, base).catch(() => null);
39805
40565
  const messageId = sentMessageId(sent);
39806
40566
  return {
@@ -40982,19 +41742,19 @@ __export(exports_join, {
40982
41742
  });
40983
41743
  import { createHash as createHash11, randomUUID as randomUUID27 } from "crypto";
40984
41744
  import {
40985
- chmodSync as chmodSync6,
41745
+ chmodSync as chmodSync7,
40986
41746
  closeSync as closeSync5,
40987
- existsSync as existsSync35,
41747
+ existsSync as existsSync36,
40988
41748
  fsyncSync as fsyncSync2,
40989
41749
  linkSync,
40990
41750
  lstatSync,
40991
41751
  mkdirSync as mkdirSync32,
40992
41752
  openSync as openSync5,
40993
- readFileSync as readFileSync27,
41753
+ readFileSync as readFileSync28,
40994
41754
  renameSync as renameSync16,
40995
41755
  rmSync as rmSync10,
40996
41756
  statSync as statSync20,
40997
- unlinkSync as unlinkSync22,
41757
+ unlinkSync as unlinkSync23,
40998
41758
  writeFileSync as writeFileSync23
40999
41759
  } from "fs";
41000
41760
  import { dirname as dirname21, resolve as resolve23 } from "path";
@@ -41094,7 +41854,7 @@ function withJoinCredentialLock(operation) {
41094
41854
  throw error2;
41095
41855
  let current3 = null;
41096
41856
  try {
41097
- current3 = JSON.parse(readFileSync27(ownerPath, "utf8"));
41857
+ current3 = JSON.parse(readFileSync28(ownerPath, "utf8"));
41098
41858
  } catch {}
41099
41859
  let ageMs;
41100
41860
  try {
@@ -41124,7 +41884,7 @@ function withJoinCredentialLock(operation) {
41124
41884
  return operation();
41125
41885
  } finally {
41126
41886
  try {
41127
- const current3 = JSON.parse(readFileSync27(ownerPath, "utf8"));
41887
+ const current3 = JSON.parse(readFileSync28(ownerPath, "utf8"));
41128
41888
  if (current3.pid === owner.pid && current3.token === owner.token) {
41129
41889
  rmSync10(lockPath, { recursive: true, force: true });
41130
41890
  }
@@ -41147,9 +41907,9 @@ function joinCredentialDigest(join42) {
41147
41907
  return createHash11("sha256").update(JSON.stringify(normalizedJoin(join42))).digest("base64url");
41148
41908
  }
41149
41909
  function readPersistedJoin(path = joinFilePath()) {
41150
- if (!existsSync35(path))
41910
+ if (!existsSync36(path))
41151
41911
  return null;
41152
- const parsed = JSON.parse(readFileSync27(path, "utf-8"));
41912
+ const parsed = JSON.parse(readFileSync28(path, "utf-8"));
41153
41913
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
41154
41914
  throw new Error("persisted join credentials are not a JSON object");
41155
41915
  }
@@ -41168,7 +41928,7 @@ function saveJoinWhileLocked(join42, options = {}) {
41168
41928
  const directory = dirname21(path);
41169
41929
  const normalized = normalizedJoin(join42);
41170
41930
  mkdirSync32(directory, { recursive: true });
41171
- if (existsSync35(path)) {
41931
+ if (existsSync36(path)) {
41172
41932
  if (lstatSync(path).isSymbolicLink()) {
41173
41933
  throw new Error(`refusing to replace symlinked Otium join file at ${path}`);
41174
41934
  }
@@ -41181,7 +41941,7 @@ function saveJoinWhileLocked(join42, options = {}) {
41181
41941
  }
41182
41942
  }
41183
41943
  if (existing && joinsEqual(existing, normalized)) {
41184
- chmodSync6(path, 384);
41944
+ chmodSync7(path, 384);
41185
41945
  const fileFd = openSync5(path, "r");
41186
41946
  try {
41187
41947
  fsyncSync2(fileFd);
@@ -41213,9 +41973,9 @@ function saveJoinWhileLocked(join42, options = {}) {
41213
41973
  renameSync16(temporaryPath, path);
41214
41974
  } else {
41215
41975
  linkSync(temporaryPath, path);
41216
- unlinkSync22(temporaryPath);
41976
+ unlinkSync23(temporaryPath);
41217
41977
  }
41218
- chmodSync6(path, 384);
41978
+ chmodSync7(path, 384);
41219
41979
  const directoryFd = openSync5(directory, "r");
41220
41980
  try {
41221
41981
  fsyncSync2(directoryFd);
@@ -41225,8 +41985,8 @@ function saveJoinWhileLocked(join42, options = {}) {
41225
41985
  } catch (error2) {
41226
41986
  if (fd !== undefined)
41227
41987
  closeSync5(fd);
41228
- if (existsSync35(temporaryPath))
41229
- unlinkSync22(temporaryPath);
41988
+ if (existsSync36(temporaryPath))
41989
+ unlinkSync23(temporaryPath);
41230
41990
  throw error2;
41231
41991
  }
41232
41992
  return path;
@@ -41237,12 +41997,12 @@ function saveJoin(join42, options = {}) {
41237
41997
  function removeJoin() {
41238
41998
  return withJoinCredentialLock(() => {
41239
41999
  const path = joinFilePath();
41240
- if (!existsSync35(path))
42000
+ if (!existsSync36(path))
41241
42001
  return false;
41242
42002
  if (lstatSync(path).isSymbolicLink()) {
41243
42003
  throw new Error(`refusing to remove symlinked Otium join file at ${path}`);
41244
42004
  }
41245
- unlinkSync22(path);
42005
+ unlinkSync23(path);
41246
42006
  const directoryFd = openSync5(dirname21(path), "r");
41247
42007
  try {
41248
42008
  fsyncSync2(directoryFd);
@@ -41269,10 +42029,10 @@ function loadJoin() {
41269
42029
  logger.warn("otium: OTIUM_CENTRAL_URL, OTIUM_CELL_ID, OTIUM_CELL_SECRET must be set together \u2014 ignoring partial env");
41270
42030
  }
41271
42031
  const path = joinFilePath();
41272
- if (!existsSync35(path))
42032
+ if (!existsSync36(path))
41273
42033
  return null;
41274
42034
  try {
41275
- const parsed = JSON.parse(readFileSync27(path, "utf-8"));
42035
+ const parsed = JSON.parse(readFileSync28(path, "utf-8"));
41276
42036
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
41277
42037
  return null;
41278
42038
  return normalizeJoin(parsed);
@@ -41296,11 +42056,11 @@ var exports_join_status = {};
41296
42056
  __export(exports_join_status, {
41297
42057
  hasConfiguredOtiumJoin: () => hasConfiguredOtiumJoin
41298
42058
  });
41299
- import { existsSync as existsSync36 } from "fs";
42059
+ import { existsSync as existsSync37 } from "fs";
41300
42060
  import { resolve as resolve24 } from "path";
41301
42061
  function hasConfiguredOtiumJoin() {
41302
42062
  const envJoin = Boolean(process.env.OTIUM_CENTRAL_URL?.trim() && process.env.OTIUM_CELL_ID?.trim() && process.env.OTIUM_CELL_SECRET?.trim());
41303
- return envJoin || existsSync36(resolve24(DATA_DIR, "otium-join.json"));
42063
+ return envJoin || existsSync37(resolve24(DATA_DIR, "otium-join.json"));
41304
42064
  }
41305
42065
  var init_join_status = __esm(() => {
41306
42066
  init_config_public();
@@ -41528,8 +42288,7 @@ var init_canonical_mcp_bridge = __esm(async () => {
41528
42288
  "wiki_read",
41529
42289
  "wiki_topic_brief",
41530
42290
  "wiki_last_conversation",
41531
- "save_wiki_entry",
41532
- "save_topic_brief",
42291
+ "wiki_write",
41533
42292
  "index_upsert"
41534
42293
  ])
41535
42294
  };
@@ -43937,8 +44696,13 @@ function shareOtiumTopic(options) {
43937
44696
  if (!owned.ok)
43938
44697
  return owned;
43939
44698
  if (!isTopicShared(owned.topic)) {
43940
- upsertTopic({ ...owned.topic, accessMode: "shared" });
43941
- WsHub.get().broadcastTopicUpdated(owned.topic.id);
44699
+ const switched = switchTopicAccessMode({
44700
+ topicId: options.localTopicId,
44701
+ userId: options.userId,
44702
+ accessMode: "shared"
44703
+ });
44704
+ if (!switched.ok)
44705
+ return { ok: false, error: switched.error, status: 409 };
43942
44706
  }
43943
44707
  return bindOtiumTopic(options);
43944
44708
  }
@@ -43948,8 +44712,13 @@ function setOtiumTopicPrivate(options) {
43948
44712
  return owned;
43949
44713
  const removedBindings = unbindSharedPeerSessionsForLocalTopic(options.localTopicId);
43950
44714
  if (isTopicShared(owned.topic)) {
43951
- upsertTopic({ ...owned.topic, accessMode: "private" });
43952
- WsHub.get().broadcastTopicUpdated(owned.topic.id);
44715
+ const switched = switchTopicAccessMode({
44716
+ topicId: options.localTopicId,
44717
+ userId: options.userId,
44718
+ accessMode: "private"
44719
+ });
44720
+ if (!switched.ok)
44721
+ return { ok: false, error: switched.error, status: 409 };
43953
44722
  }
43954
44723
  return { ok: true, localTopicId: options.localTopicId, removedBindings };
43955
44724
  }
@@ -43989,16 +44758,16 @@ import {
43989
44758
  randomUUID as randomUUID31
43990
44759
  } from "crypto";
43991
44760
  import {
43992
- chmodSync as chmodSync7,
44761
+ chmodSync as chmodSync8,
43993
44762
  closeSync as closeSync6,
43994
- existsSync as existsSync37,
44763
+ existsSync as existsSync38,
43995
44764
  fsyncSync as fsyncSync3,
43996
44765
  linkSync as linkSync2,
43997
44766
  mkdirSync as mkdirSync34,
43998
44767
  openSync as openSync6,
43999
- readFileSync as readFileSync28,
44768
+ readFileSync as readFileSync29,
44000
44769
  renameSync as renameSync17,
44001
- unlinkSync as unlinkSync23,
44770
+ unlinkSync as unlinkSync24,
44002
44771
  writeFileSync as writeFileSync24
44003
44772
  } from "fs";
44004
44773
  import { dirname as dirname22, resolve as resolve25 } from "path";
@@ -44026,10 +44795,10 @@ function pendingEnrollmentPath() {
44026
44795
  }
44027
44796
  function isEnrollmentPending(invite) {
44028
44797
  const path = pendingEnrollmentPath();
44029
- if (!existsSync37(path))
44798
+ if (!existsSync38(path))
44030
44799
  return false;
44031
44800
  try {
44032
- const saved = JSON.parse(readFileSync28(path, "utf8"));
44801
+ const saved = JSON.parse(readFileSync29(path, "utf8"));
44033
44802
  return saved.central === invite.central && saved.token === invite.token;
44034
44803
  } catch {
44035
44804
  return false;
@@ -44037,8 +44806,8 @@ function isEnrollmentPending(invite) {
44037
44806
  }
44038
44807
  function loadOrCreatePending(invite, nodeName) {
44039
44808
  const path = pendingEnrollmentPath();
44040
- if (existsSync37(path)) {
44041
- const saved = JSON.parse(readFileSync28(path, "utf8"));
44809
+ if (existsSync38(path)) {
44810
+ const saved = JSON.parse(readFileSync29(path, "utf8"));
44042
44811
  if (saved.central === invite.central && saved.token === invite.token)
44043
44812
  return saved;
44044
44813
  throw new Error(`another Otium enrollment is pending at ${path}`);
@@ -44062,8 +44831,8 @@ function loadOrCreatePending(invite, nodeName) {
44062
44831
  closeSync6(fd);
44063
44832
  fd = undefined;
44064
44833
  linkSync2(temporaryPath, path);
44065
- unlinkSync23(temporaryPath);
44066
- chmodSync7(path, 384);
44834
+ unlinkSync24(temporaryPath);
44835
+ chmodSync8(path, 384);
44067
44836
  const directoryFd = openSync6(dirname22(path), "r");
44068
44837
  try {
44069
44838
  fsyncSync3(directoryFd);
@@ -44073,10 +44842,10 @@ function loadOrCreatePending(invite, nodeName) {
44073
44842
  } catch (error2) {
44074
44843
  if (fd !== undefined)
44075
44844
  closeSync6(fd);
44076
- if (existsSync37(temporaryPath))
44077
- unlinkSync23(temporaryPath);
44078
- if (error2.code === "EEXIST" && existsSync37(path)) {
44079
- const saved = JSON.parse(readFileSync28(path, "utf8"));
44845
+ if (existsSync38(temporaryPath))
44846
+ unlinkSync24(temporaryPath);
44847
+ if (error2.code === "EEXIST" && existsSync38(path)) {
44848
+ const saved = JSON.parse(readFileSync29(path, "utf8"));
44080
44849
  if (saved.central === invite.central && saved.token === invite.token)
44081
44850
  return saved;
44082
44851
  }
@@ -44106,14 +44875,14 @@ function replacePendingEnrollment(pending2) {
44106
44875
  } catch (error2) {
44107
44876
  if (fd !== undefined)
44108
44877
  closeSync6(fd);
44109
- if (existsSync37(temporaryPath))
44110
- unlinkSync23(temporaryPath);
44878
+ if (existsSync38(temporaryPath))
44879
+ unlinkSync24(temporaryPath);
44111
44880
  throw error2;
44112
44881
  }
44113
44882
  }
44114
44883
  function recordClaimedCredential(pending2, join43) {
44115
44884
  withJoinCredentialLock(() => {
44116
- const current3 = JSON.parse(readFileSync28(pendingEnrollmentPath(), "utf8"));
44885
+ const current3 = JSON.parse(readFileSync29(pendingEnrollmentPath(), "utf8"));
44117
44886
  if (current3.central !== pending2.central || current3.token !== pending2.token || current3.idempotencyKey !== pending2.idempotencyKey || current3.publicKey !== pending2.publicKey) {
44118
44887
  throw new Error("pending Otium enrollment changed while its claim was in flight");
44119
44888
  }
@@ -44192,7 +44961,7 @@ async function claimEnrollment(invite, nodeName) {
44192
44961
  function commitEnrollment(join43, options = {}) {
44193
44962
  return withJoinCredentialLock(() => {
44194
44963
  const pendingPath = pendingEnrollmentPath();
44195
- const pending2 = existsSync37(pendingPath) ? JSON.parse(readFileSync28(pendingPath, "utf8")) : null;
44964
+ const pending2 = existsSync38(pendingPath) ? JSON.parse(readFileSync29(pendingPath, "utf8")) : null;
44196
44965
  const digest = joinCredentialDigest(join43);
44197
44966
  if (pending2 && (!pending2.claimed || pending2.claimed.digest !== digest || pending2.claimed.cellId !== join43.cellId)) {
44198
44967
  throw new Error(`pending Otium enrollment at ${pendingPath} does not match these credentials`);
@@ -44203,7 +44972,7 @@ function commitEnrollment(join43, options = {}) {
44203
44972
  }
44204
44973
  if (!pending2)
44205
44974
  return path;
44206
- unlinkSync23(pendingPath);
44975
+ unlinkSync24(pendingPath);
44207
44976
  const directoryFd = openSync6(dirname22(pendingPath), "r");
44208
44977
  try {
44209
44978
  fsyncSync3(directoryFd);
@@ -46392,4 +47161,4 @@ switch (command) {
46392
47161
  }
46393
47162
  }
46394
47163
 
46395
- //# debugId=9061330203CC009264756E2164756E21
47164
+ //# debugId=69B455239128A0A264756E2164756E21