negotium 0.2.23 → 0.2.25

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 (48) hide show
  1. package/dist/agent-helpers.js +84 -30
  2. package/dist/agent-helpers.js.map +6 -6
  3. package/dist/background-bash.js +2 -1
  4. package/dist/background-bash.js.map +3 -3
  5. package/dist/browser-runtime.js +2 -1
  6. package/dist/browser-runtime.js.map +3 -3
  7. package/dist/{chunk-rhp26p2c.js → chunk-1y2xw0xe.js} +2 -1
  8. package/dist/{chunk-rhp26p2c.js.map → chunk-1y2xw0xe.js.map} +3 -3
  9. package/dist/hosted-agent.js +3 -2
  10. package/dist/hosted-agent.js.map +4 -4
  11. package/dist/main.js +224 -366
  12. package/dist/main.js.map +27 -28
  13. package/dist/mcp-factories.js +86 -31
  14. package/dist/mcp-factories.js.map +7 -7
  15. package/dist/prompts.js +2 -1
  16. package/dist/prompts.js.map +3 -3
  17. package/dist/query-runtime.js +2 -1
  18. package/dist/query-runtime.js.map +3 -3
  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/index.ts +3 -7
  23. package/dist/runtime/src/mcp/session-comm/default-host.ts +6 -1
  24. package/dist/runtime/src/mcp/session-comm/topic-catalog.ts +17 -1
  25. package/dist/runtime/src/mcp/session-comm/topics.ts +23 -1
  26. package/dist/runtime/src/node-host.ts +4 -4
  27. package/dist/runtime/src/platform/config.ts +12 -0
  28. package/dist/runtime/src/storage/api-topics.ts +160 -52
  29. package/dist/runtime/src/storage/storage-public.ts +0 -1
  30. package/dist/runtime/src/topics/create.ts +12 -6
  31. package/dist/runtime/src/topics/derive.ts +20 -11
  32. package/dist/runtime/src/types/api.ts +8 -4
  33. package/dist/runtime/src/version.ts +1 -1
  34. package/dist/runtime-helpers.js +2 -1
  35. package/dist/runtime-helpers.js.map +3 -3
  36. package/dist/storage.js +92 -38
  37. package/dist/storage.js.map +3 -3
  38. package/dist/types/packages/core/src/mcp/session-comm/topic-catalog.d.ts +7 -0
  39. package/dist/types/packages/core/src/platform/config.d.ts +12 -0
  40. package/dist/types/packages/core/src/storage/api-topics.d.ts +34 -19
  41. package/dist/types/packages/core/src/storage/storage-public.d.ts +1 -1
  42. package/dist/types/packages/core/src/topics/derive.d.ts +13 -4
  43. package/dist/types/packages/core/src/types/api.d.ts +8 -4
  44. package/dist/types/packages/core/src/version.d.ts +1 -1
  45. package/dist/vault.js +2 -1
  46. package/dist/vault.js.map +3 -3
  47. package/package.json +1 -1
  48. package/dist/runtime/src/application/switch-topic-access-mode.ts +0 -110
package/dist/main.js CHANGED
@@ -273,7 +273,7 @@ function resolveDefaultModel(agent, registryDefaultModel) {
273
273
  function codexAuthFilePath() {
274
274
  return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(process.env.CODEX_HOME || join(homedir(), ".codex"), "auth.json");
275
275
  }
276
- 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;
276
+ 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, NODE_ID, 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;
277
277
  var init_config = __esm(() => {
278
278
  init_config_helpers();
279
279
  init_logger();
@@ -315,6 +315,7 @@ var init_config = __esm(() => {
315
315
  BG_BASH_MAX_PORT = parsePortEnv(process.env.BG_BASH_MAX_PORT, 9799);
316
316
  RUNTIME_MCP_SECRET = loadOrCreateLocalSecret("RUNTIME_MCP_SECRET", "runtime-mcp-secret");
317
317
  NODE_CONTROL_TOKEN = loadOrCreateLocalSecret("NEGOTIUM_CONTROL_TOKEN", "node-control-token");
318
+ NODE_ID = loadOrCreateLocalSecret("NEGOTIUM_NODE_ID", "node-id");
318
319
  VAULT_MASTER_KEY = loadOrCreateLocalSecret("NEGOTIUM_VAULT_MASTER_KEY", "vault-master-key", { persistEnvValue: true });
319
320
  delete process.env.NEGOTIUM_VAULT_MASTER_KEY;
320
321
  NEGOTIUM_PORT = parseInt(process.env.NEGOTIUM_PORT || "7777", 10);
@@ -1867,7 +1868,7 @@ var exports_version = {};
1867
1868
  __export(exports_version, {
1868
1869
  NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
1869
1870
  });
1870
- var NEGOTIUM_VERSION = "0.2.23";
1871
+ var NEGOTIUM_VERSION = "0.2.25";
1871
1872
 
1872
1873
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
1873
1874
  import { spawn } from "child_process";
@@ -7505,21 +7506,20 @@ var init_constants = __esm(() => {
7505
7506
  var exports_api_topics = {};
7506
7507
  __export(exports_api_topics, {
7507
7508
  upsertTopic: () => upsertTopic,
7509
+ setTopicSurfaces: () => setTopicSurfaces,
7508
7510
  setTopicSessionId: () => setTopicSessionId,
7509
- setTopicAccessModes: () => setTopicAccessModes,
7510
7511
  setApiTopicAgent: () => setApiTopicAgent,
7511
7512
  revokeSubagentTellTarget: () => revokeSubagentTellTarget,
7512
7513
  reparentTopicChildren: () => reparentTopicChildren,
7513
7514
  removeParticipantFromDB: () => removeParticipantFromDB,
7514
7515
  normalizeTopicVisibility: () => normalizeTopicVisibility,
7516
+ normalizeTopicSurface: () => normalizeTopicSurface,
7515
7517
  normalizeTopicState: () => normalizeTopicState,
7516
7518
  normalizeTopicKind: () => normalizeTopicKind,
7517
- normalizeTopicAccessMode: () => normalizeTopicAccessMode,
7518
7519
  normalizeAiMode: () => normalizeAiMode,
7519
7520
  listTopics: () => listTopics,
7520
7521
  listSubagentTellTargetIds: () => listSubagentTellTargetIds,
7521
7522
  isTopicVisible: () => isTopicVisible,
7522
- isTopicShared: () => isTopicShared,
7523
7523
  inferTopicKind: () => inferTopicKind,
7524
7524
  inferAiMode: () => inferAiMode,
7525
7525
  grantSubagentTellTarget: () => grantSubagentTellTarget,
@@ -7532,10 +7532,17 @@ __export(exports_api_topics, {
7532
7532
  getManagerTopicForUser: () => getManagerTopicForUser,
7533
7533
  findTopicTitleConflict: () => findTopicTitleConflict,
7534
7534
  deleteTopic: () => deleteTopic,
7535
+ defaultTopicSurface: () => defaultTopicSurface,
7535
7536
  clearTopicSessionId: () => clearTopicSessionId,
7536
7537
  aiMentionFromMode: () => aiMentionFromMode,
7537
7538
  addParticipantToDB: () => addParticipantToDB
7538
7539
  });
7540
+ function defaultTopicSurface() {
7541
+ return normalizeTopicSurface(process.env.NEGOTIUM_DEFAULT_SURFACE);
7542
+ }
7543
+ function normalizeTopicSurface(value) {
7544
+ return value === "telegram" || value === "otium" || value === "terminal" ? value : "terminal";
7545
+ }
7539
7546
  function tableColumns2(table) {
7540
7547
  const rows = db.query(`PRAGMA table_info(${table})`).all();
7541
7548
  return new Set(rows.map((row) => row.name));
@@ -7666,7 +7673,7 @@ function initializeApiTopicsSchema() {
7666
7673
  is_fork INTEGER NOT NULL DEFAULT 0 CHECK (is_fork IN (0,1)),
7667
7674
  is_subagent INTEGER NOT NULL DEFAULT 0 CHECK (is_subagent IN (0,1)),
7668
7675
  visibility TEXT NOT NULL DEFAULT 'visible' CHECK (visibility IN ('visible','hidden')),
7669
- access_mode TEXT NOT NULL DEFAULT 'private' CHECK (access_mode IN ('private','shared')),
7676
+ surface TEXT NOT NULL DEFAULT 'terminal' CHECK (surface IN ('terminal','telegram','otium')),
7670
7677
  browser_profile TEXT NOT NULL DEFAULT 'default',
7671
7678
  browser_profile_owner TEXT,
7672
7679
  session_id TEXT,
@@ -7712,8 +7719,8 @@ function initializeApiTopicsSchema() {
7712
7719
  const legacyBaseEffort = row.base_effort ?? row.default_effort;
7713
7720
  db.query(`INSERT INTO api_topics_next
7714
7721
  (id,title,kind,description,agent,base_model,base_effort,response_policy,
7715
- created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,session_id)
7716
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(String(row.id), String(row.title), normalized.kind, typeof row.description === "string" ? row.description : null, normalized.agent ?? null, typeof legacyBaseModel === "string" ? legacyBaseModel : null, typeof legacyBaseEffort === "string" ? legacyBaseEffort : null, normalized.aiMode, String(row.created_at), typeof row.last_message_at === "string" ? row.last_message_at : null, typeof row.parent_topic_id === "string" ? row.parent_topic_id : null, typeof row.memory_topic_id === "string" ? row.memory_topic_id : null, typeof row.memory_key === "string" ? row.memory_key : null, Number(row.is_fork ?? 0) !== 0 ? 1 : 0, Number(row.is_subagent ?? 0) !== 0 ? 1 : 0, row.visibility === "hidden" ? "hidden" : "visible", row.access_mode === "shared" ? "shared" : "private", typeof row.session_id === "string" ? row.session_id : null);
7722
+ created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,surface,session_id)
7723
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(String(row.id), String(row.title), normalized.kind, typeof row.description === "string" ? row.description : null, normalized.agent ?? null, typeof legacyBaseModel === "string" ? legacyBaseModel : null, typeof legacyBaseEffort === "string" ? legacyBaseEffort : null, normalized.aiMode, String(row.created_at), typeof row.last_message_at === "string" ? row.last_message_at : null, typeof row.parent_topic_id === "string" ? row.parent_topic_id : null, typeof row.memory_topic_id === "string" ? row.memory_topic_id : null, typeof row.memory_key === "string" ? row.memory_key : null, Number(row.is_fork ?? 0) !== 0 ? 1 : 0, Number(row.is_subagent ?? 0) !== 0 ? 1 : 0, row.visibility === "hidden" ? "hidden" : "visible", row.surface === undefined ? defaultTopicSurface() : normalizeTopicSurface(row.surface), typeof row.session_id === "string" ? row.session_id : null);
7717
7724
  }
7718
7725
  db.exec("DROP TABLE IF EXISTS topic_members");
7719
7726
  db.exec("DROP TABLE api_topics");
@@ -7767,8 +7774,15 @@ function initializeApiTopicsSchema() {
7767
7774
  if (!tableColumns2("api_topics").has("visibility")) {
7768
7775
  db.exec("ALTER TABLE api_topics ADD COLUMN visibility TEXT NOT NULL DEFAULT 'visible'");
7769
7776
  }
7770
- if (!tableColumns2("api_topics").has("access_mode")) {
7771
- db.exec("ALTER TABLE api_topics ADD COLUMN access_mode TEXT NOT NULL DEFAULT 'private'");
7777
+ if (!tableColumns2("api_topics").has("surface")) {
7778
+ db.exec("ALTER TABLE api_topics ADD COLUMN surface TEXT NOT NULL DEFAULT 'terminal'");
7779
+ }
7780
+ if (tableColumns2("api_topics").has("access_mode")) {
7781
+ try {
7782
+ db.exec("ALTER TABLE api_topics DROP COLUMN access_mode");
7783
+ } catch (err) {
7784
+ logger.warn({ err }, "api_topics: could not drop the retired access_mode column");
7785
+ }
7772
7786
  }
7773
7787
  if (!tableColumns2("api_topics").has("browser_profile")) {
7774
7788
  db.exec("ALTER TABLE api_topics ADD COLUMN browser_profile TEXT NOT NULL DEFAULT 'default'");
@@ -7795,7 +7809,48 @@ function initializeApiTopicsSchema() {
7795
7809
  )
7796
7810
  WHERE browser_profile_owner IS NULL
7797
7811
  `);
7812
+ backfillTopicSurfaces();
7798
7813
  db.exec("CREATE INDEX IF NOT EXISTS idx_api_topics_last_message ON api_topics(last_message_at DESC)");
7814
+ db.exec("CREATE INDEX IF NOT EXISTS idx_api_topics_surface ON api_topics(surface)");
7815
+ }
7816
+ function backfillTopicSurfaces() {
7817
+ db.exec(`
7818
+ CREATE TABLE IF NOT EXISTS api_schema_migrations (
7819
+ key TEXT PRIMARY KEY,
7820
+ applied_at TEXT NOT NULL
7821
+ )
7822
+ `);
7823
+ const applied = db.query("SELECT key FROM api_schema_migrations WHERE key = ?").get(SURFACE_BACKFILL_MIGRATION);
7824
+ if (applied)
7825
+ return;
7826
+ const surface = defaultTopicSurface();
7827
+ db.transaction(() => {
7828
+ db.query("UPDATE api_topics SET surface = ?").run(surface);
7829
+ renameSurfaceTitleCollisions();
7830
+ db.query("INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)").run(SURFACE_BACKFILL_MIGRATION, new Date().toISOString());
7831
+ })();
7832
+ logger.info({ surface }, "api_topics: surface backfilled");
7833
+ }
7834
+ function renameSurfaceTitleCollisions() {
7835
+ const rows = db.query("SELECT id, title, kind, surface FROM api_topics ORDER BY created_at ASC, rowid ASC").all();
7836
+ const taken = new Set;
7837
+ const update = db.query("UPDATE api_topics SET title = ? WHERE id = ?");
7838
+ for (const row of rows) {
7839
+ const key = (title) => `${row.surface}\x00${row.kind}\x00${normalizedTitle(title)}`;
7840
+ if (!taken.has(key(row.title))) {
7841
+ taken.add(key(row.title));
7842
+ continue;
7843
+ }
7844
+ let suffix = 2;
7845
+ let candidate = `${row.title} (${suffix})`;
7846
+ while (taken.has(key(candidate))) {
7847
+ suffix += 1;
7848
+ candidate = `${row.title} (${suffix})`;
7849
+ }
7850
+ taken.add(key(candidate));
7851
+ update.run(candidate, row.id);
7852
+ logger.warn({ topicId: row.id, surface: row.surface, from: row.title, to: candidate }, "api_topics: renamed a duplicate title for surface-scoped uniqueness");
7853
+ }
7799
7854
  }
7800
7855
  function shortSessionId(sessionId) {
7801
7856
  return sessionId ? sessionId.slice(0, 8) : null;
@@ -7855,15 +7910,9 @@ function rowToDto2(r, participants = getTopicParticipants(r.id), tellTargets) {
7855
7910
  subagentReportMode: r.subagent_report_mode === "tell" || r.subagent_report_mode === "status-only" ? r.subagent_report_mode : "auto"
7856
7911
  } : {},
7857
7912
  visibility: normalizeTopicVisibility(r.visibility),
7858
- accessMode: normalizeTopicAccessMode(r.access_mode)
7913
+ surface: normalizeTopicSurface(r.surface)
7859
7914
  };
7860
7915
  }
7861
- function normalizeTopicAccessMode(value) {
7862
- return value === "shared" ? "shared" : "private";
7863
- }
7864
- function isTopicShared(topic) {
7865
- return topic.accessMode === "shared";
7866
- }
7867
7916
  function normalizeTopicVisibility(value) {
7868
7917
  return value === "hidden" ? "hidden" : "visible";
7869
7918
  }
@@ -7940,7 +7989,7 @@ function upsertTopic(t) {
7940
7989
  db.transaction(() => {
7941
7990
  db.query(`INSERT INTO api_topics
7942
7991
  (id,title,kind,description,agent,base_model,base_effort,response_policy,
7943
- created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,
7992
+ created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,surface,
7944
7993
  subagent_report_mode)
7945
7994
  VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
7946
7995
  ON CONFLICT(id) DO UPDATE SET
@@ -7959,8 +8008,8 @@ function upsertTopic(t) {
7959
8008
  is_fork = excluded.is_fork,
7960
8009
  is_subagent = excluded.is_subagent,
7961
8010
  visibility = excluded.visibility,
7962
- access_mode = excluded.access_mode,
7963
- subagent_report_mode = excluded.subagent_report_mode`).run(t.id, t.title, normalized.kind, t.description ?? null, normalized.agent ?? null, t.defaultModel ?? null, t.defaultEffort ?? null, normalized.aiMode, t.createdAt, t.lastMessageAt ?? null, t.parentTopicId ?? null, t.memoryTopicId ?? null, t.memoryKey ?? null, t.isFork ? 1 : 0, t.isSubagent ? 1 : 0, normalizeTopicVisibility(t.visibility), normalizeTopicAccessMode(t.accessMode), t.subagentReportMode ?? "auto");
8011
+ surface = excluded.surface,
8012
+ subagent_report_mode = excluded.subagent_report_mode`).run(t.id, t.title, normalized.kind, t.description ?? null, normalized.agent ?? null, t.defaultModel ?? null, t.defaultEffort ?? null, normalized.aiMode, t.createdAt, t.lastMessageAt ?? null, t.parentTopicId ?? null, t.memoryTopicId ?? null, t.memoryKey ?? null, t.isFork ? 1 : 0, t.isSubagent ? 1 : 0, normalizeTopicVisibility(t.visibility), normalizeTopicSurface(t.surface), t.subagentReportMode ?? "auto");
7964
8013
  db.query("DELETE FROM topic_members WHERE topic_id = ?").run(t.id);
7965
8014
  for (const participant of t.participants) {
7966
8015
  db.query("INSERT INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)").run(t.id, participant.userId, participant.role);
@@ -7973,18 +8022,8 @@ function upsertTopic(t) {
7973
8022
  }
7974
8023
  })();
7975
8024
  }
7976
- function setTopicAccessModes(topicIds, accessMode) {
7977
- if (topicIds.length === 0)
7978
- return;
7979
- const normalized = normalizeTopicAccessMode(accessMode);
7980
- db.transaction(() => {
7981
- const update = db.query("UPDATE api_topics SET access_mode = ? WHERE id = ?");
7982
- for (const topicId of topicIds)
7983
- update.run(normalized, topicId);
7984
- })();
7985
- }
7986
- function listTopics() {
7987
- const rows = db.query("SELECT * FROM api_topics ORDER BY last_message_at DESC").all();
8025
+ function listTopics(opts = {}) {
8026
+ const rows = opts.surface ? db.query("SELECT * FROM api_topics WHERE surface = ? ORDER BY last_message_at DESC").all(normalizeTopicSurface(opts.surface)) : db.query("SELECT * FROM api_topics ORDER BY last_message_at DESC").all();
7988
8027
  const participants = getAllTopicParticipants();
7989
8028
  const tellTargets = rows.some((row) => row.is_subagent !== 0) ? getAllSubagentTellTargets() : undefined;
7990
8029
  return rows.map((row) => rowToDto2(row, participants.get(row.id) ?? [], tellTargets));
@@ -8035,14 +8074,15 @@ function getTopicByNameAndKind(title, kind) {
8035
8074
  }
8036
8075
  function findTopicTitleConflict(title, kind, opts = {}) {
8037
8076
  const wanted = normalizedTitle(title);
8077
+ const surface = normalizeTopicSurface(opts.surface ?? defaultTopicSurface());
8038
8078
  const generalTitleRequested = wanted === normalizedTitle(GENERAL_TOPIC_ID);
8039
8079
  if (generalTitleRequested && opts.excludeTopicId !== GENERAL_TOPIC_ID) {
8040
8080
  const general = db.query("SELECT * FROM api_topics WHERE id = ?").get(GENERAL_TOPIC_ID);
8041
8081
  if (general)
8042
8082
  return rowToDto2(general);
8043
8083
  }
8044
- const params = [wanted];
8045
- let sql = "SELECT * FROM api_topics WHERE LOWER(TRIM(title)) = ?";
8084
+ const params = [wanted, surface];
8085
+ let sql = "SELECT * FROM api_topics WHERE LOWER(TRIM(title)) = ? AND surface = ?";
8046
8086
  if (kind !== "manager") {
8047
8087
  sql += " AND (kind = ? OR id = ?)";
8048
8088
  params.push(kind, GENERAL_TOPIC_ID);
@@ -8055,7 +8095,20 @@ function findTopicTitleConflict(title, kind, opts = {}) {
8055
8095
  const row = db.query(sql).get(...params);
8056
8096
  return row ? rowToDto2(row) : null;
8057
8097
  }
8058
- function getTopicByNameForUser(title, userId) {
8098
+ function setTopicSurfaces(topicIds, surface) {
8099
+ if (topicIds.length === 0)
8100
+ return 0;
8101
+ const normalized = normalizeTopicSurface(surface);
8102
+ let changed = 0;
8103
+ db.transaction(() => {
8104
+ const update = db.query("UPDATE api_topics SET surface = ? WHERE id = ? AND surface != ?");
8105
+ for (const topicId of topicIds) {
8106
+ changed += Number(update.run(normalized, topicId, normalized).changes ?? 0);
8107
+ }
8108
+ })();
8109
+ return changed;
8110
+ }
8111
+ function getTopicByNameForUser(title, userId, opts = {}) {
8059
8112
  const trimmed = title.trim();
8060
8113
  const qualified = /^(agent|channel|manager):(.+)$/i.exec(trimmed);
8061
8114
  const requestedKind = qualified ? normalizeTopicKind(qualified[1]?.toLowerCase()) : null;
@@ -8064,9 +8117,10 @@ function getTopicByNameForUser(title, userId) {
8064
8117
  WHERE LOWER(t.title) = LOWER(?)
8065
8118
  AND t.id != ?
8066
8119
  AND t.visibility != 'hidden'
8120
+ AND (? IS NULL OR t.surface = ?)
8067
8121
  AND EXISTS (
8068
8122
  SELECT 1 FROM topic_members m WHERE m.topic_id = t.id AND m.user_id = ?
8069
- )`).all(requestedTitle, GENERAL_TOPIC_ID, userId);
8123
+ )`).all(requestedTitle, GENERAL_TOPIC_ID, opts.surface ?? null, opts.surface ?? null, userId);
8070
8124
  const matches = requestedKind ? rows.filter((row) => row.kind === requestedKind) : rows;
8071
8125
  return matches.length === 1 ? rowToDto2(matches[0]) : null;
8072
8126
  }
@@ -8154,7 +8208,7 @@ function removeParticipantFromDB(topicId, userId) {
8154
8208
  db.query("DELETE FROM topic_members WHERE topic_id = ? AND user_id = ?").run(topicId, userId);
8155
8209
  return true;
8156
8210
  }
8157
- var DEFAULT_AGENT_ROOM_AGENT = "maestro";
8211
+ var DEFAULT_AGENT_ROOM_AGENT = "maestro", SURFACE_BACKFILL_MIGRATION = "api_topics_surface_backfill_20260808";
8158
8212
  var init_api_topics = __esm(async () => {
8159
8213
  init_constants();
8160
8214
  init_logger();
@@ -14240,11 +14294,11 @@ __export(exports_derive, {
14240
14294
  });
14241
14295
  import { createHash as createHash5, randomUUID as randomUUID12 } from "crypto";
14242
14296
  import { mkdirSync as mkdirSync13, rmSync as rmSync5, unlinkSync as unlinkSync13 } from "fs";
14243
- function getTopics() {
14244
- return listTopics().filter((topic) => !isLegacySharedGeneral(topic.id));
14297
+ function getTopics(opts = {}) {
14298
+ return listTopics(opts).filter((topic) => !isLegacySharedGeneral(topic.id));
14245
14299
  }
14246
- function getVisibleTopics() {
14247
- return getTopics().filter(isTopicVisible).map((topic) => {
14300
+ function getVisibleTopics(opts = {}) {
14301
+ return getTopics(opts).filter(isTopicVisible).map((topic) => {
14248
14302
  if (!topic.agent)
14249
14303
  return topic;
14250
14304
  const registry = getRegistry(topic.agent);
@@ -14269,8 +14323,8 @@ function updateTopic(topicId, patch) {
14269
14323
  function isParticipant(topic, userId) {
14270
14324
  return topic.participants.some((p) => p.userId === userId);
14271
14325
  }
14272
- function nextDerivedTopicTitle(sourceTitle, kind, suffix) {
14273
- const visibleTitles = new Set(listTopics().filter((topic) => topic.kind === kind).map((topic) => topic.title.toLowerCase()));
14326
+ function nextDerivedTopicTitle(sourceTitle, kind, suffix, surface) {
14327
+ const visibleTitles = new Set(listTopics(surface ? { surface } : {}).filter((topic) => topic.kind === kind).map((topic) => topic.title.toLowerCase()));
14274
14328
  let n = 1;
14275
14329
  let title = `${sourceTitle}-${suffix}-${n}`;
14276
14330
  while (visibleTitles.has(title.toLowerCase())) {
@@ -14334,8 +14388,9 @@ async function createDerivedTopicImpl(topic, sourceTopicId, userId, copyHistory,
14334
14388
  { userId, role: "owner" }
14335
14389
  ] : [{ userId, role: "owner" }];
14336
14390
  const kind = topic.kind ?? inferTopicKind(topic);
14337
- const title = opts?.name?.trim() || nextDerivedTopicTitle(topic.title, kind, suffix);
14338
- const conflict = findTopicTitleConflict(title, kind);
14391
+ const surface = topic.surface ?? defaultTopicSurface();
14392
+ const title = opts?.name?.trim() || nextDerivedTopicTitle(topic.title, kind, suffix, surface);
14393
+ const conflict = findTopicTitleConflict(title, kind, { surface });
14339
14394
  if (conflict) {
14340
14395
  logger.info({ sourceTopicId, title, kind, conflictTopicId: conflict.id }, "createDerivedTopic: title conflict");
14341
14396
  throw new TopicTitleConflictError(title);
@@ -14358,7 +14413,7 @@ async function createDerivedTopicImpl(topic, sourceTopicId, userId, copyHistory,
14358
14413
  isFork: copyHistory,
14359
14414
  ...subagent ? { isSubagent: true } : {},
14360
14415
  visibility: topic.visibility,
14361
- accessMode: topic.accessMode
14416
+ surface
14362
14417
  };
14363
14418
  let sessionId;
14364
14419
  let rollbackHandle;
@@ -14466,7 +14521,7 @@ async function createDerivedTopicImpl(topic, sourceTopicId, userId, copyHistory,
14466
14521
  if (!currentSource || currentSource.kind === "manager" || !isParticipant(currentSource, userId) || subagent && isRuntimeTopicMaintenance(sourceTopicId)) {
14467
14522
  throw new TopicDeriveBusyError("Source topic changed while deriving; try again");
14468
14523
  }
14469
- const transactionalConflict = findTopicTitleConflict(title, kind);
14524
+ const transactionalConflict = findTopicTitleConflict(title, kind, { surface });
14470
14525
  if (transactionalConflict)
14471
14526
  throw new TopicTitleConflictError(title);
14472
14527
  upsertTopic(derived);
@@ -20357,74 +20412,6 @@ var init_submit_user_message = __esm(async () => {
20357
20412
  await init_api_messages();
20358
20413
  });
20359
20414
 
20360
- // ../../packages/core/src/application/switch-topic-access-mode.ts
20361
- function collectSubagentDescendants(rootTopicId) {
20362
- const all = listTopics();
20363
- const childrenByParent = new Map;
20364
- for (const topic of all) {
20365
- if (!topic.isSubagent || !topic.parentTopicId)
20366
- continue;
20367
- const siblings = childrenByParent.get(topic.parentTopicId);
20368
- if (siblings)
20369
- siblings.push(topic);
20370
- else
20371
- childrenByParent.set(topic.parentTopicId, [topic]);
20372
- }
20373
- const descendants = [];
20374
- const seen = new Set([rootTopicId]);
20375
- const queue = [rootTopicId];
20376
- while (queue.length > 0) {
20377
- const parentId = queue.shift();
20378
- for (const child of childrenByParent.get(parentId) ?? []) {
20379
- if (seen.has(child.id))
20380
- continue;
20381
- seen.add(child.id);
20382
- descendants.push(child);
20383
- queue.push(child.id);
20384
- }
20385
- }
20386
- return descendants;
20387
- }
20388
- function switchTopicAccessMode(params) {
20389
- const topic = getTopic(params.topicId);
20390
- if (!topic)
20391
- return { ok: false, error: "Topic not found" };
20392
- const owner = topic.participants.some((participant) => participant.userId === params.userId && participant.role === "owner");
20393
- if (!owner)
20394
- return { ok: false, error: "Only topic owners can change privacy" };
20395
- if (topic.isSubagent) {
20396
- return {
20397
- ok: false,
20398
- error: "Subagent rooms inherit privacy from their parent topic"
20399
- };
20400
- }
20401
- const descendants = collectSubagentDescendants(topic.id);
20402
- const changed = [topic, ...descendants].filter((candidate) => (candidate.accessMode ?? "private") !== params.accessMode);
20403
- if (changed.length === 0) {
20404
- return {
20405
- ok: true,
20406
- accessMode: params.accessMode,
20407
- text: params.accessMode === "shared" ? `"${topic.title}" is already public to the connected Otium Hub.` : `"${topic.title}" is already private to this worker.`,
20408
- topicIds: []
20409
- };
20410
- }
20411
- setTopicAccessModes(changed.map((candidate) => candidate.id), params.accessMode);
20412
- for (const candidate of changed)
20413
- WsHub.get().broadcastTopicUpdated(candidate.id);
20414
- const subagentCount = changed.filter((candidate) => candidate.id !== topic.id).length;
20415
- const suffix = subagentCount > 0 ? ` (${subagentCount} subagent room${subagentCount === 1 ? "" : "s"} updated)` : "";
20416
- return {
20417
- ok: true,
20418
- accessMode: params.accessMode,
20419
- topicIds: changed.map((candidate) => candidate.id),
20420
- text: (params.accessMode === "shared" ? `"${topic.title}" is public to the connected Otium Hub.` : `"${topic.title}" is private to this worker.`) + suffix
20421
- };
20422
- }
20423
- var init_switch_topic_access_mode = __esm(async () => {
20424
- await init_bus();
20425
- await init_api_topics();
20426
- });
20427
-
20428
20415
  // ../../packages/core/src/application/switch-topic-effort.ts
20429
20416
  function switchTopicEffort(params) {
20430
20417
  const topic = getTopic(params.topicId);
@@ -20538,9 +20525,10 @@ function registerTopic(opts) {
20538
20525
  if (requestedKind === "manager") {
20539
20526
  throw new TopicValidationError("Manager rooms are system-managed");
20540
20527
  }
20541
- const conflict = findTopicTitleConflict(title, requestedKind);
20528
+ const surface = normalizeTopicSurface(opts.surface ?? defaultTopicSurface());
20529
+ const conflict = findTopicTitleConflict(title, requestedKind, { surface });
20542
20530
  if (conflict) {
20543
- throw new TopicValidationError(`A topic named "${title}" already exists`);
20531
+ throw new TopicValidationError(`A topic named "${title}" already exists on ${surface}`);
20544
20532
  }
20545
20533
  const rawAgent = opts.agent;
20546
20534
  if (requestedKind === "agent" && rawAgent === "none") {
@@ -20574,7 +20562,7 @@ function registerTopic(opts) {
20574
20562
  defaultEffort: defaultEffort ?? "medium",
20575
20563
  aiMode,
20576
20564
  participants: [{ userId: opts.userId, role: "owner" }],
20577
- accessMode: opts.accessMode ?? "private",
20565
+ surface,
20578
20566
  createdAt: now,
20579
20567
  lastMessageAt: now
20580
20568
  };
@@ -22413,7 +22401,6 @@ __export(exports_src, {
22413
22401
  textResult: () => textResult,
22414
22402
  switchTopicModel: () => switchTopicModel,
22415
22403
  switchTopicEffort: () => switchTopicEffort,
22416
- switchTopicAccessMode: () => switchTopicAccessMode,
22417
22404
  sweepStaleSubagentCards: () => sweepStaleSubagentCards,
22418
22405
  submitUserMessage: () => submitUserMessage,
22419
22406
  submitRuntimeGatewayTurn: () => submitRuntimeGatewayTurn,
@@ -22426,6 +22413,7 @@ __export(exports_src, {
22426
22413
  startBashrsCompletionsWorker: () => startBashrsCompletionsWorker,
22427
22414
  startAskUserQuestionGateOwner: () => startAskUserQuestionGateOwner,
22428
22415
  startAiTurn: () => startAiTurn,
22416
+ setTopicSurfaces: () => setTopicSurfaces,
22429
22417
  setTopicSessionId: () => setTopicSessionId,
22430
22418
  setRuntimeMcpPort: () => setRuntimeMcpPort,
22431
22419
  setRuntimeBus: () => setRuntimeBus,
@@ -22472,6 +22460,7 @@ __export(exports_src, {
22472
22460
  prepareDeliveryAck: () => prepareDeliveryAck,
22473
22461
  onShutdown: () => onShutdown,
22474
22462
  normalizeVaultKey: () => normalizeVaultKey,
22463
+ normalizeTopicSurface: () => normalizeTopicSurface,
22475
22464
  nodeRequestHandlerNames: () => nodeRequestHandlerNames,
22476
22465
  modelOwner: () => modelOwner,
22477
22466
  markPlaywrightUnavailable: () => markPlaywrightUnavailable,
@@ -22498,7 +22487,6 @@ __export(exports_src, {
22498
22487
  isVaultCommandLine: () => isVaultCommandLine,
22499
22488
  isTranscriptionConfigured: () => isTranscriptionConfigured,
22500
22489
  isTopicVisible: () => isTopicVisible,
22501
- isTopicShared: () => isTopicShared,
22502
22490
  isTopicRunning: () => isTopicRunning,
22503
22491
  isSensitivePath: () => isSensitivePath,
22504
22492
  isParticipant: () => isParticipant,
@@ -22548,6 +22536,7 @@ __export(exports_src, {
22548
22536
  deliverAskCallbackToCaller: () => deliverAskCallbackToCaller,
22549
22537
  deleteVaultEntry: () => deleteVaultEntry,
22550
22538
  deleteTopicCascade: () => deleteTopicCascade,
22539
+ defaultTopicSurface: () => defaultTopicSurface,
22551
22540
  db: () => db,
22552
22541
  createSubagentManagementToolDefinitions: () => createSubagentManagementToolDefinitions,
22553
22542
  createSpawnSubagentToolDefinition: () => createSpawnSubagentToolDefinition,
@@ -22639,7 +22628,6 @@ var init_src = __esm(async () => {
22639
22628
  await init_execute_external_user_turn();
22640
22629
  await init_submit_runtime_gateway_turn();
22641
22630
  await init_submit_user_message();
22642
- await init_switch_topic_access_mode();
22643
22631
  await init_switch_topic_effort();
22644
22632
  await init_switch_topic_model();
22645
22633
  await init_topic_service();
@@ -22805,10 +22793,10 @@ var init_conversation_migration = __esm(async () => {
22805
22793
  // ../../packages/core/src/node-host.ts
22806
22794
  var exports_node_host = {};
22807
22795
  __export(exports_node_host, {
22796
+ upsertTopic: () => upsertTopic,
22808
22797
  topicService: () => topicService,
22809
22798
  switchTopicModel: () => switchTopicModel,
22810
22799
  switchTopicEffort: () => switchTopicEffort,
22811
- switchTopicAccessMode: () => switchTopicAccessMode,
22812
22800
  sweepStaleSubagentCards: () => sweepStaleSubagentCards,
22813
22801
  submitUserMessage: () => submitUserMessage,
22814
22802
  submitRuntimeGatewayTurn: () => submitRuntimeGatewayTurn,
@@ -22842,7 +22830,6 @@ __export(exports_node_host, {
22842
22830
  killOwnedCodexTreesForShutdown: () => killOwnedCodexTreesForShutdown,
22843
22831
  killAllPlaywright: () => killAllPlaywright,
22844
22832
  killAllBgBash: () => killAllBgBash,
22845
- isTopicShared: () => isTopicShared,
22846
22833
  isParticipant: () => isParticipant,
22847
22834
  getVisibleTopics: () => getVisibleTopics,
22848
22835
  getTopicStats: () => getTopicStats,
@@ -22852,6 +22839,7 @@ __export(exports_node_host, {
22852
22839
  ensurePersonalGeneral: () => ensurePersonalGeneral,
22853
22840
  deleteVaultEntry: () => deleteVaultEntry,
22854
22841
  compactTopicSession: () => compactTopicSession,
22842
+ appendApiMessage: () => appendApiMessage,
22855
22843
  acquireRuntimeProcessLease: () => acquireRuntimeProcessLease,
22856
22844
  abortAllRooms: () => abortAllRooms,
22857
22845
  WsHub: () => WsHub,
@@ -22863,6 +22851,7 @@ __export(exports_node_host, {
22863
22851
  STATE_DIR: () => STATE_DIR,
22864
22852
  RuntimeGatewayIdempotencyConflictError: () => RuntimeGatewayIdempotencyConflictError,
22865
22853
  RUN_DIR: () => RUN_DIR,
22854
+ NODE_ID: () => NODE_ID,
22866
22855
  NODE_CONTROL_TOKEN: () => NODE_CONTROL_TOKEN,
22867
22856
  NEGOTIUM_VERSION: () => NEGOTIUM_VERSION,
22868
22857
  NEGOTIUM_PORT: () => NEGOTIUM_PORT,
@@ -22874,7 +22863,6 @@ var init_node_host = __esm(async () => {
22874
22863
  await init_spawn_subagent();
22875
22864
  await init_submit_runtime_gateway_turn();
22876
22865
  await init_submit_user_message();
22877
- await init_switch_topic_access_mode();
22878
22866
  await init_switch_topic_effort();
22879
22867
  await init_switch_topic_model();
22880
22868
  await init_topic_service();
@@ -24150,7 +24138,8 @@ var init_session_comm = __esm(async () => {
24150
24138
  function createSessionTargetCatalog(host2) {
24151
24139
  const { currentTopicId, currentTopicName, isAgent, listRows } = host2;
24152
24140
  function listTargets() {
24153
- const eligibleRows = listRows().filter((row) => row.kind !== "manager");
24141
+ const currentSurface = host2.currentSurface;
24142
+ const eligibleRows = listRows().filter((row) => row.kind !== "manager" && (!currentSurface || (row.surface ?? currentSurface) === currentSurface));
24154
24143
  const titleCounts = new Map;
24155
24144
  const qualifiedCounts = new Map;
24156
24145
  for (const row of eligibleRows) {
@@ -24266,9 +24255,11 @@ function currentTopic(context) {
24266
24255
  return topic;
24267
24256
  }
24268
24257
  function targetCatalog(context) {
24258
+ const surface = (context.currentTopicId ? getTopic(context.currentTopicId)?.surface : undefined) ?? defaultTopicSurface();
24269
24259
  return createSessionTargetCatalog({
24270
24260
  currentTopicId: context.currentTopicId,
24271
24261
  currentTopicName: context.currentTopic,
24262
+ currentSurface: surface,
24272
24263
  isAgent: isAgentKind,
24273
24264
  listRows: () => listTopics().filter((topic) => topic.participants.some((p) => p.userId === context.userId)).map((topic) => ({
24274
24265
  id: topic.id,
@@ -24276,7 +24267,8 @@ function targetCatalog(context) {
24276
24267
  kind: topic.kind ?? null,
24277
24268
  agent: topic.agent ?? null,
24278
24269
  sessionId: null,
24279
- description: topic.description ?? null
24270
+ description: topic.description ?? null,
24271
+ surface: topic.surface ?? null
24280
24272
  }))
24281
24273
  });
24282
24274
  }
@@ -27512,6 +27504,7 @@ __export(exports_storage_public, {
27512
27504
  softDeleteApiMessagesByIdPrefix: () => softDeleteApiMessagesByIdPrefix,
27513
27505
  softDeleteApiMessage: () => softDeleteApiMessage,
27514
27506
  settleTopicArchiveJob: () => settleTopicArchiveJob,
27507
+ setTopicSurfaces: () => setTopicSurfaces,
27515
27508
  setTopicSessionId: () => setTopicSessionId,
27516
27509
  setTopicMcpExtra: () => setTopicMcpExtra,
27517
27510
  setTopicMcpEnabled: () => setTopicMcpEnabled,
@@ -27522,7 +27515,6 @@ __export(exports_storage_public, {
27522
27515
  setTopicAgentAndSession: () => setTopicAgentAndSession,
27523
27516
  setTopicAgentAndClearSession: () => setTopicAgentAndClearSession,
27524
27517
  setTopicAgent: () => setTopicAgent,
27525
- setTopicAccessModes: () => setTopicAccessModes,
27526
27518
  setSessionForTopic: () => setSessionForTopic,
27527
27519
  setLastShownConfig: () => setLastShownConfig,
27528
27520
  setGlobalAiName: () => setGlobalAiName,
@@ -27556,9 +27548,9 @@ __export(exports_storage_public, {
27556
27548
  quarantineAskUserGate: () => quarantineAskUserGate,
27557
27549
  prepareAskUserGate: () => prepareAskUserGate,
27558
27550
  normalizeTopicVisibility: () => normalizeTopicVisibility,
27551
+ normalizeTopicSurface: () => normalizeTopicSurface,
27559
27552
  normalizeTopicState: () => normalizeTopicState,
27560
27553
  normalizeTopicKind: () => normalizeTopicKind,
27561
- normalizeTopicAccessMode: () => normalizeTopicAccessMode,
27562
27554
  normalizeAiMode: () => normalizeAiMode,
27563
27555
  markPendingAskState: () => markPendingAskState,
27564
27556
  markPendingAskSources: () => markPendingAskSources,
@@ -27573,7 +27565,6 @@ __export(exports_storage_public, {
27573
27565
  listApiMessages: () => listApiMessages,
27574
27566
  isTopicVisible: () => isTopicVisible,
27575
27567
  isTopicSummaryFile: () => isTopicSummaryFile,
27576
- isTopicShared: () => isTopicShared,
27577
27568
  isTopicBriefFile: () => isTopicBriefFile,
27578
27569
  isRuntimeProcessLeaseAlive: () => isRuntimeProcessLeaseAlive,
27579
27570
  inferTopicKind: () => inferTopicKind,
@@ -27634,6 +27625,7 @@ __export(exports_storage_public, {
27634
27625
  deletePendingAsksForTopic: () => deletePendingAsksForTopic,
27635
27626
  deleteMessagesForTopic: () => deleteMessagesForTopic,
27636
27627
  deleteApiTopicConfig: () => deleteApiTopicConfig,
27628
+ defaultTopicSurface: () => defaultTopicSurface,
27637
27629
  db: () => db2,
27638
27630
  createTasks: () => createTasks,
27639
27631
  createPendingAsk: () => createPendingAsk,
@@ -29347,13 +29339,19 @@ function requiredText(value, name) {
29347
29339
  }
29348
29340
  return value.trim();
29349
29341
  }
29350
- function topicsForUser(userId) {
29342
+ function topicsForUser(userId, surface) {
29351
29343
  const runningTopics = listRunningTopicQueries();
29352
- return getVisibleTopics().filter((topic) => isParticipant(topic, userId)).map((topic) => {
29344
+ return getVisibleTopics(surface ? { surface } : {}).filter((topic) => isParticipant(topic, userId)).map((topic) => {
29353
29345
  const runningQueryId = runningTopics.get(topic.id);
29354
29346
  return { ...topic, running: Boolean(runningQueryId), runningQueryId };
29355
29347
  });
29356
29348
  }
29349
+ function requestedSurface(url) {
29350
+ const raw = url.searchParams.get("surface")?.trim();
29351
+ if (!raw)
29352
+ return;
29353
+ return raw === "terminal" || raw === "telegram" || raw === "otium" ? raw : undefined;
29354
+ }
29357
29355
  function topicForUser(topicId, userId) {
29358
29356
  const topic = getTopic(topicId);
29359
29357
  return topic && isParticipant(topic, userId) ? topic : null;
@@ -29437,13 +29435,15 @@ function createNodeControlHandler(options) {
29437
29435
  ok: true,
29438
29436
  v: NODE_RUNTIME_CONTRACT_VERSION,
29439
29437
  nodeVersion: NODE_VERSION,
29438
+ nodeId: NODE_ID,
29440
29439
  capabilities: [
29441
29440
  "turn-submit-idempotent",
29442
29441
  "turn-events-sse-resume",
29443
29442
  "canonical-topic-read",
29444
29443
  "canonical-message-read",
29445
29444
  "canonical-topic-list",
29446
- "canonical-topic-create"
29445
+ "canonical-topic-create",
29446
+ "canonical-history-import"
29447
29447
  ],
29448
29448
  cursor: latestRuntimeEventSeq()
29449
29449
  });
@@ -29508,11 +29508,7 @@ function createNodeControlHandler(options) {
29508
29508
  return Response.json({ ok: true, v: NODE_RUNTIME_CONTRACT_VERSION, ...result });
29509
29509
  }
29510
29510
  if (req.method === "GET" && runtimePath === "/topics") {
29511
- const accessMode = url.searchParams.get("accessMode")?.trim();
29512
- if (accessMode && accessMode !== "shared" && accessMode !== "private") {
29513
- return jsonError(400, "accessMode must be 'shared' or 'private'");
29514
- }
29515
- const topics = getVisibleTopics().filter((topic) => accessMode === "shared" ? isTopicShared(topic) : accessMode === "private" ? !isTopicShared(topic) : true);
29511
+ const topics = getVisibleTopics({ surface: "otium" });
29516
29512
  return Response.json({
29517
29513
  ok: true,
29518
29514
  v: NODE_RUNTIME_CONTRACT_VERSION,
@@ -29534,11 +29530,60 @@ function createNodeControlHandler(options) {
29534
29530
  title,
29535
29531
  userId,
29536
29532
  kind: "agent",
29537
- accessMode: "shared",
29533
+ surface: "otium",
29538
29534
  ...agent ? { agent } : {}
29539
29535
  });
29540
29536
  return Response.json({ ok: true, v: NODE_RUNTIME_CONTRACT_VERSION, topic }, { status: 201 });
29541
29537
  }
29538
+ const importMatch = runtimePath.match(/^\/topics\/([^/]+)\/import$/);
29539
+ if (importMatch && req.method === "POST") {
29540
+ const body = await bodyRecord(req);
29541
+ if (body.v !== NODE_RUNTIME_CONTRACT_VERSION)
29542
+ return jsonError(400, "Unsupported v");
29543
+ const topicId = decodeURIComponent(importMatch[1]);
29544
+ const topic = getTopic(topicId);
29545
+ if (!topic)
29546
+ return jsonError(404, "Topic not found");
29547
+ if (!Array.isArray(body.messages)) {
29548
+ return jsonError(400, "messages must be an array");
29549
+ }
29550
+ if (listApiMessages(topicId, { limit: 1 }).page.length > 0) {
29551
+ return jsonError(409, "topic already has messages");
29552
+ }
29553
+ const messages = body.messages;
29554
+ for (const [index, message] of messages.entries()) {
29555
+ if (typeof message?.id !== "string" || typeof message?.authorId !== "string" || typeof message?.text !== "string" || typeof message?.createdAt !== "string") {
29556
+ return jsonError(400, `messages[${index}] needs id, authorId, text and createdAt`);
29557
+ }
29558
+ }
29559
+ let imported = 0;
29560
+ for (const message of messages) {
29561
+ appendApiMessage({
29562
+ id: message.id,
29563
+ topicId,
29564
+ authorId: message.authorId,
29565
+ text: message.text,
29566
+ createdAt: message.createdAt,
29567
+ deleted: false,
29568
+ ...typeof message.agentType === "string" ? { agentType: message.agentType } : {},
29569
+ ...typeof message.model === "string" ? { model: message.model } : {}
29570
+ }, { notify: false, updateTopicLastMessageAt: false });
29571
+ imported += 1;
29572
+ }
29573
+ if (imported > 0) {
29574
+ const stamps = messages.map((message) => message.createdAt).sort();
29575
+ upsertTopic({
29576
+ ...topic,
29577
+ createdAt: stamps[0] < topic.createdAt ? stamps[0] : topic.createdAt,
29578
+ lastMessageAt: stamps[stamps.length - 1]
29579
+ });
29580
+ }
29581
+ return Response.json({
29582
+ ok: true,
29583
+ v: NODE_RUNTIME_CONTRACT_VERSION,
29584
+ imported
29585
+ });
29586
+ }
29542
29587
  const runtimeTopicMatch = runtimePath.match(/^\/topics\/([^/]+)$/);
29543
29588
  if (runtimeTopicMatch && req.method === "GET") {
29544
29589
  const topic = getTopic(decodeURIComponent(runtimeTopicMatch[1]));
@@ -29570,13 +29615,13 @@ function createNodeControlHandler(options) {
29570
29615
  ok: true,
29571
29616
  protocolVersion: NODE_CONTROL_PROTOCOL_VERSION,
29572
29617
  nodeVersion: NODE_VERSION,
29573
- topics: topicsForUser(userId),
29618
+ topics: topicsForUser(userId, requestedSurface(url)),
29574
29619
  cursor: latestRuntimeEventSeq()
29575
29620
  });
29576
29621
  }
29577
29622
  if (req.method === "GET" && path === "/topics") {
29578
29623
  const userId = requiredText(url.searchParams.get("user"), "user");
29579
- return Response.json({ ok: true, topics: topicsForUser(userId) });
29624
+ return Response.json({ ok: true, topics: topicsForUser(userId, requestedSurface(url)) });
29580
29625
  }
29581
29626
  if (req.method === "GET" && path === "/background-sessions") {
29582
29627
  const userId = requiredText(url.searchParams.get("user"), "user");
@@ -29707,24 +29752,6 @@ function createNodeControlHandler(options) {
29707
29752
  return jsonError(400, result.error);
29708
29753
  return Response.json({ ok: true, model: result.model, result: result.text });
29709
29754
  }
29710
- const accessModeMatch = path.match(/^\/topics\/([^/]+)\/access-mode$/);
29711
- if (accessModeMatch && req.method === "POST") {
29712
- const topicId = decodeURIComponent(accessModeMatch[1]);
29713
- const body = await bodyRecord(req);
29714
- const userId = requiredText(body.userId, "userId");
29715
- const accessMode = requiredText(body.accessMode, "accessMode");
29716
- if (accessMode !== "private" && accessMode !== "shared") {
29717
- return jsonError(400, `Unknown access mode: ${accessMode}`);
29718
- }
29719
- const result = switchTopicAccessMode({ topicId, userId, accessMode });
29720
- if (!result.ok)
29721
- return jsonError(400, result.error);
29722
- return Response.json({
29723
- ok: true,
29724
- accessMode: result.accessMode,
29725
- result: result.text
29726
- });
29727
- }
29728
29755
  const effortMatch = path.match(/^\/topics\/([^/]+)\/effort$/);
29729
29756
  if (effortMatch && req.method === "POST") {
29730
29757
  const topicId = decodeURIComponent(effortMatch[1]);
@@ -32069,8 +32096,6 @@ var init_commands = __esm(() => {
32069
32096
  { name: "model", usage: "/model", description: "choose the model" },
32070
32097
  { name: "effort", usage: "/effort", description: "choose reasoning effort" },
32071
32098
  { name: "topics", usage: "/topics", description: "open topic picker" },
32072
- { name: "public", usage: "/public", description: "share this topic with Otium Hub" },
32073
- { name: "private", usage: "/private", description: "make this topic local-only" },
32074
32099
  {
32075
32100
  name: "fork",
32076
32101
  usage: "/fork [name]",
@@ -32416,8 +32441,7 @@ function topicPickerItems(state) {
32416
32441
  const indexedTopics = state.topics.map((topic, index) => ({ topic, index })).filter(({ topic }) => visible.has(topic.id));
32417
32442
  return [
32418
32443
  ...indexedTopics.filter(({ topic }) => topic.kind === "manager").map(({ topic, index }) => ({ kind: "topic", id: topic.id, index })),
32419
- ...indexedTopics.filter(({ topic }) => topic.kind !== "manager" && topic.accessMode !== "shared").map(({ topic, index }) => ({ kind: "topic", id: topic.id, index })),
32420
- ...indexedTopics.filter(({ topic }) => topic.kind !== "manager" && topic.accessMode === "shared").map(({ topic, index }) => ({ kind: "topic", id: topic.id, index })),
32444
+ ...indexedTopics.filter(({ topic }) => topic.kind !== "manager").map(({ topic, index }) => ({ kind: "topic", id: topic.id, index })),
32421
32445
  ...visibleBackgroundSessions(state).map((session) => ({
32422
32446
  kind: "background",
32423
32447
  id: session.id
@@ -32496,7 +32520,7 @@ function setTopics(state, topics, preferredTitle) {
32496
32520
  activeTopicId: nextActive,
32497
32521
  scrollOffset: nextActive === state.activeTopicId ? state.scrollOffset : 0,
32498
32522
  askChoiceIndex: nextActive === state.activeTopicId ? state.askChoiceIndex : 0,
32499
- topicPickerIndex: state.overlay === "topics" || state.overlay === "confirm-share" ? pickedTopicIndex : Math.max(0, orderedTopics.findIndex((topic) => topic.id === nextActive)),
32523
+ topicPickerIndex: state.overlay === "topics" ? pickedTopicIndex : Math.max(0, orderedTopics.findIndex((topic) => topic.id === nextActive)),
32500
32524
  topicPickerBackgroundId: state.topicPickerBackgroundId
32501
32525
  });
32502
32526
  }
@@ -34034,7 +34058,7 @@ function helpLines() {
34034
34058
  line(""),
34035
34059
  line(" Commands", { fg: theme.cyan, bold: true }),
34036
34060
  line(" /new /model /effort /status /context"),
34037
- line(" /topics /public /private /fork /spawn"),
34061
+ line(" /topics /fork /spawn"),
34038
34062
  line(" /del /copy /abort /help /quit", { fg: theme.muted })
34039
34063
  ];
34040
34064
  }
@@ -34157,8 +34181,8 @@ function topicPickerHints(topicPickerRoot) {
34157
34181
  const exit = topicPickerRoot ? "Esc/Ctrl-C exit" : "Esc close \xB7 Ctrl-C exit; work continues";
34158
34182
  const shortExit = topicPickerRoot ? "Esc/Ctrl-C exit" : "Esc close";
34159
34183
  const candidates = [
34160
- `\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}`,
34161
- `\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}`,
34184
+ `\u2191\u2193 select \xB7 Enter open \xB7 type to filter \xB7 Ctrl-N new \xB7 Ctrl-D delete \xB7 ${exit}`,
34185
+ `\u2191\u2193 select \xB7 Enter open \xB7 type to filter \xB7 Ctrl-N new \xB7 Ctrl-D delete \xB7 ${shortExit}`,
34162
34186
  `\u2191\u2193 select \xB7 Enter open \xB7 type to filter \xB7 Ctrl-N new \xB7 Ctrl-D delete \xB7 ${exit}`,
34163
34187
  `\u2191\u2193 select \xB7 Enter open \xB7 type to filter \xB7 Ctrl-N/D/P \xB7 ${shortExit}`,
34164
34188
  "\u2191\u2193 \xB7 Enter \xB7 type to filter \xB7 Ctrl-N/D/P",
@@ -34180,13 +34204,11 @@ function topicOverlayLines(state, width, height, animationFrame = 0) {
34180
34204
  const visibleIds = visibleTopicPickerIds(state);
34181
34205
  const indexedTopics = state.topics.map((topic, topicIndex) => ({ topic, topicIndex })).filter(({ topic }) => visibleIds.has(topic.id));
34182
34206
  const managerTopics = indexedTopics.filter(({ topic }) => topic.kind === "manager");
34183
- const privateTopics = indexedTopics.filter(({ topic }) => topic.kind !== "manager" && topic.accessMode !== "shared");
34184
- const publicTopics = indexedTopics.filter(({ topic }) => topic.kind !== "manager" && topic.accessMode === "shared");
34207
+ const regularTopics = indexedTopics.filter(({ topic }) => topic.kind !== "manager");
34185
34208
  const entries = [];
34186
34209
  for (const [label, topics] of [
34187
34210
  ["Manager", managerTopics],
34188
- ["Private", privateTopics],
34189
- ["Public", publicTopics]
34211
+ ["Topics", regularTopics]
34190
34212
  ]) {
34191
34213
  if (topics.length === 0)
34192
34214
  continue;
@@ -34521,34 +34543,6 @@ function conversationLines(state, width, height, animationFrame = 0, nowMs = ter
34521
34543
  line(" Press y to delete or n to cancel.", { fg: theme.amber })
34522
34544
  ].slice(0, height);
34523
34545
  }
34524
- if (state.overlay === "confirm-share") {
34525
- const topic = state.topics.find((candidate) => candidate.id === state.pendingShareTopicId);
34526
- const subagents = (() => {
34527
- if (!topic)
34528
- return 0;
34529
- const reached = new Set([topic.id]);
34530
- for (;; ) {
34531
- const next = state.topics.filter((candidate) => candidate.isSubagent && !reached.has(candidate.id) && candidate.parentTopicId !== undefined && reached.has(candidate.parentTopicId));
34532
- if (next.length === 0)
34533
- break;
34534
- for (const candidate of next)
34535
- reached.add(candidate.id);
34536
- }
34537
- return reached.size - 1;
34538
- })();
34539
- const scope = subagents > 0 ? ` Its ${subagents} subagent room${subagents === 1 ? "" : "s"} become public too.` : " Subagent rooms spawned from it will be public too.";
34540
- return [
34541
- line(""),
34542
- line(` Make \u201C${topic?.title ?? "this topic"}\u201D public?`, {
34543
- fg: theme.amber,
34544
- bold: true
34545
- }),
34546
- line(""),
34547
- line(" The transcript becomes visible to the connected Otium Hub."),
34548
- line(scope),
34549
- line(" Press y to publish or n to cancel.", { fg: theme.amber })
34550
- ].slice(0, height);
34551
- }
34552
34546
  const all = conversationContentLines(state, width, animationFrame, nowMs, includeTasks);
34553
34547
  const { contentHeight, maxOffset, offset } = conversationViewport(all.length, height, state.scrollOffset);
34554
34548
  const end = all.length - offset;
@@ -34816,9 +34810,6 @@ function footerHintText(state) {
34816
34810
  }
34817
34811
  return state.topicPickerRoot ? ["Esc/Ctrl-C exit ", "Esc exit "] : ["Esc close \xB7 Ctrl-C exit; work continues ", "Esc close \xB7 Ctrl-C exit ", "Esc "];
34818
34812
  }
34819
- if (state.overlay === "confirm-share") {
34820
- return ["Y make public \xB7 N cancel ", "Y/N "];
34821
- }
34822
34813
  if (state.overlay === "background-session") {
34823
34814
  return [
34824
34815
  "wheel/PgUp/PgDn scroll \xB7 Esc back \xB7 read-only ",
@@ -35422,7 +35413,7 @@ class EmbeddedNegotiumClient {
35422
35413
  }
35423
35414
  listTopics() {
35424
35415
  const runningTopics = listRunningTopicQueries();
35425
- return getVisibleTopics().filter((topic) => topic.participants.some((participant) => participant.userId === this.#userId)).map((topic) => {
35416
+ return getVisibleTopics({ surface: "terminal" }).filter((topic) => topic.participants.some((participant) => participant.userId === this.#userId)).map((topic) => {
35426
35417
  const runningQueryId = runningTopics.get(topic.id);
35427
35418
  return { ...topic, running: Boolean(runningQueryId), runningQueryId };
35428
35419
  });
@@ -35501,16 +35492,6 @@ class EmbeddedNegotiumClient {
35501
35492
  throw new Error(result.error);
35502
35493
  return result.text;
35503
35494
  }
35504
- setAccessMode(topic, accessMode) {
35505
- const result = switchTopicAccessMode({
35506
- topicId: topic.id,
35507
- userId: this.#userId,
35508
- accessMode
35509
- });
35510
- if (!result.ok)
35511
- throw new Error(result.error);
35512
- return result.text;
35513
- }
35514
35495
  async deleteTopic(topic) {
35515
35496
  await topicService.delete({ topicId: topic.id, userId: this.#userId });
35516
35497
  }
@@ -35595,7 +35576,7 @@ class RemoteNegotiumClient {
35595
35576
  async start(onEvent) {
35596
35577
  if (this.#started)
35597
35578
  return;
35598
- const session = await this.#request(`/session?user=${encodeURIComponent(this.#userId)}`);
35579
+ const session = await this.#request(`/session?user=${encodeURIComponent(this.#userId)}&surface=terminal`);
35599
35580
  if (session.protocolVersion !== NODE_CONTROL_PROTOCOL_VERSION) {
35600
35581
  throw new Error(`Node protocol ${String(session.protocolVersion)} is incompatible with terminal protocol ${NODE_CONTROL_PROTOCOL_VERSION}`);
35601
35582
  }
@@ -35621,7 +35602,7 @@ class RemoteNegotiumClient {
35621
35602
  this.#onEvent = null;
35622
35603
  }
35623
35604
  async listTopics() {
35624
- const result = await this.#request(`/topics?user=${encodeURIComponent(this.#userId)}`);
35605
+ const result = await this.#request(`/topics?user=${encodeURIComponent(this.#userId)}&surface=terminal`);
35625
35606
  return result.topics ?? [];
35626
35607
  }
35627
35608
  async listBackgroundSessions() {
@@ -35693,13 +35674,6 @@ class RemoteNegotiumClient {
35693
35674
  });
35694
35675
  return String(result.result ?? `Effort set to '${effort}'.`);
35695
35676
  }
35696
- async setAccessMode(topic, accessMode) {
35697
- const result = await this.#request(`/topics/${encodeURIComponent(topic.id)}/access-mode`, {
35698
- method: "POST",
35699
- body: JSON.stringify({ userId: this.#userId, accessMode })
35700
- });
35701
- return String(result.result ?? `Access mode set to '${accessMode}'.`);
35702
- }
35703
35677
  async deleteTopic(topic) {
35704
35678
  await this.#request(`/topics/${encodeURIComponent(topic.id)}?user=${encodeURIComponent(this.#userId)}`, { method: "DELETE" });
35705
35679
  }
@@ -36177,32 +36151,6 @@ async function runTerminalCommand(commandLine, context) {
36177
36151
  context.queueRender();
36178
36152
  return;
36179
36153
  }
36180
- if (command === "public" || command === "private") {
36181
- if (args.length > 0) {
36182
- context.state = { ...context.state, notice: `Usage: /${command}`, noticeLevel: "warn" };
36183
- context.queueRender();
36184
- return;
36185
- }
36186
- const topic = activeTopic(context.state);
36187
- if (!topic) {
36188
- context.state = { ...context.state, notice: "No topic selected", noticeLevel: "warn" };
36189
- context.queueRender();
36190
- return;
36191
- }
36192
- try {
36193
- const notice = await context.client.setAccessMode(topic, command === "public" ? "shared" : "private");
36194
- await context.refreshTopics(topic.title);
36195
- context.state = { ...context.state, notice, noticeLevel: "success" };
36196
- } catch (error2) {
36197
- context.state = {
36198
- ...context.state,
36199
- notice: error2 instanceof Error ? error2.message : String(error2),
36200
- noticeLevel: "error"
36201
- };
36202
- }
36203
- context.queueRender();
36204
- return;
36205
- }
36206
36154
  if (command === "compact") {
36207
36155
  const topic = activeTopic(context.state);
36208
36156
  if (!topic) {
@@ -37767,16 +37715,6 @@ class TerminalApp {
37767
37715
  }
37768
37716
  return;
37769
37717
  }
37770
- if (this.#state.overlay === "confirm-share") {
37771
- const key = chunk.toLowerCase();
37772
- if (CONFIRM_KEYS.has(key))
37773
- this.#confirmTopicShare();
37774
- else if (CANCEL_KEYS.has(key) || chunk === "\x1B") {
37775
- this.#state = { ...this.#state, overlay: "topics", pendingShareTopicId: undefined };
37776
- this.#queueRender();
37777
- }
37778
- return;
37779
- }
37780
37718
  if (this.#state.overlay === "background-session") {
37781
37719
  if (chunk === "\x1B") {
37782
37720
  this.#state = { ...this.#state, overlay: "topics" };
@@ -38085,22 +38023,6 @@ class TerminalApp {
38085
38023
  this.#queueRender();
38086
38024
  return;
38087
38025
  }
38088
- if (chunk === TOGGLE_ACCESS_MODE_KEY) {
38089
- const topic = pickedTopic(this.#state);
38090
- if (topic) {
38091
- this.#requestAccessModeToggle(topic);
38092
- return;
38093
- }
38094
- if (!this.#state.topicPickerBackgroundId)
38095
- return;
38096
- this.#state = {
38097
- ...this.#state,
38098
- notice: "Background sessions are read-only",
38099
- noticeLevel: "warn"
38100
- };
38101
- this.#queueRender();
38102
- return;
38103
- }
38104
38026
  if (FILTER_BACKSPACE_KEYS.has(chunk)) {
38105
38027
  if (this.#state.topicFilter.length === 0)
38106
38028
  return;
@@ -38543,65 +38465,6 @@ class TerminalApp {
38543
38465
  };
38544
38466
  this.#queueRender();
38545
38467
  }
38546
- async#requestAccessModeToggle(topic) {
38547
- if (topic.kind !== "agent") {
38548
- this.#state = {
38549
- ...this.#state,
38550
- notice: `${topic.kind === "manager" ? "Manager" : "Channel"} topics cannot be published`,
38551
- noticeLevel: "warn"
38552
- };
38553
- this.#queueRender();
38554
- return;
38555
- }
38556
- if (topic.isSubagent) {
38557
- this.#state = {
38558
- ...this.#state,
38559
- notice: "Subagent rooms inherit privacy from their parent topic",
38560
- noticeLevel: "warn"
38561
- };
38562
- this.#queueRender();
38563
- return;
38564
- }
38565
- if (topic.accessMode === "shared") {
38566
- await this.#applyAccessMode(topic, "private");
38567
- return;
38568
- }
38569
- this.#state = {
38570
- ...this.#state,
38571
- overlay: "confirm-share",
38572
- pendingShareTopicId: topic.id
38573
- };
38574
- this.#queueRender();
38575
- }
38576
- async#confirmTopicShare() {
38577
- const topic = this.#state.topics.find((candidate) => candidate.id === this.#state.pendingShareTopicId);
38578
- this.#state = { ...this.#state, overlay: "topics", pendingShareTopicId: undefined };
38579
- if (!topic) {
38580
- this.#state = {
38581
- ...this.#state,
38582
- notice: "That topic no longer exists",
38583
- noticeLevel: "warn"
38584
- };
38585
- this.#queueRender();
38586
- return;
38587
- }
38588
- await this.#applyAccessMode(topic, "shared");
38589
- }
38590
- async#applyAccessMode(topic, accessMode) {
38591
- this.#state = { ...this.#state, overlay: "topics" };
38592
- try {
38593
- const notice = await this.#client.setAccessMode(topic, accessMode);
38594
- await this.#refreshTopics();
38595
- this.#state = { ...this.#state, notice, noticeLevel: "success" };
38596
- } catch (error2) {
38597
- this.#state = {
38598
- ...this.#state,
38599
- notice: error2 instanceof Error ? error2.message : String(error2),
38600
- noticeLevel: "error"
38601
- };
38602
- }
38603
- this.#queueRender();
38604
- }
38605
38468
  #requestTopicDelete(topic) {
38606
38469
  if (!topic)
38607
38470
  return;
@@ -38880,7 +38743,6 @@ class TerminalApp {
38880
38743
  ...this.#state,
38881
38744
  overlay: null,
38882
38745
  pendingDeleteTopicId: undefined,
38883
- pendingShareTopicId: undefined,
38884
38746
  creatingTopic: false,
38885
38747
  notice: "Input cleared",
38886
38748
  noticeLevel: "info"
@@ -38943,7 +38805,7 @@ class TerminalApp {
38943
38805
  await this.#client.stop();
38944
38806
  }
38945
38807
  }
38946
- 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;
38808
+ 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;
38947
38809
  var init_app = __esm(async () => {
38948
38810
  await init_src();
38949
38811
  await init_app_helpers();
@@ -39421,7 +39283,7 @@ function createTelegramCommandRouter(context) {
39421
39283
  return;
39422
39284
  }
39423
39285
  case "/topics": {
39424
- const topics = listTopics().filter((topic) => isTopicVisible(topic) && topic.participants.some((person) => person.userId === userId));
39286
+ const topics = listTopics({ surface: "telegram" }).filter((topic) => isTopicVisible(topic) && topic.participants.some((person) => person.userId === userId));
39425
39287
  reply(chatId, threadId, topics.length ? topics.map((topic) => `- ${topic.title}${topic.agent ? ` (${topic.agent})` : ""}`).join(`
39426
39288
  `) : "no topics");
39427
39289
  return;
@@ -39451,6 +39313,7 @@ function createTelegramCommandRouter(context) {
39451
39313
  title: argument,
39452
39314
  userId,
39453
39315
  kind: "agent",
39316
+ surface: "telegram",
39454
39317
  ...context.defaultAgent ? { agent: context.defaultAgent } : {}
39455
39318
  };
39456
39319
  const fromForumGeneral = context.forum?.enabled === true && chatId === context.forum.chatId && threadId === undefined;
@@ -39669,6 +39532,15 @@ function openMappingStore(path) {
39669
39532
  clearForumChatId() {
39670
39533
  db4.run("DELETE FROM settings WHERE key = 'forum_chat_id'");
39671
39534
  },
39535
+ isFlagSet(key) {
39536
+ return db4.query("SELECT 1 FROM settings WHERE key = ?").get(key) !== null;
39537
+ },
39538
+ setFlag(key) {
39539
+ db4.run("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", [
39540
+ key,
39541
+ new Date().toISOString()
39542
+ ]);
39543
+ },
39672
39544
  outboxEnqueue(entry) {
39673
39545
  db4.run(`INSERT INTO outbox
39674
39546
  (chat_id, thread_id, runtime_message_id, footer, html, plain, attempts, next_try_at, last_error)
@@ -40238,6 +40110,14 @@ function startTelegramAdapter(opts) {
40238
40110
  bindMapping(chatId, threadId, topic.id);
40239
40111
  return true;
40240
40112
  }
40113
+ if (!store.isFlagSet(SURFACE_BACKFILL_FLAG)) {
40114
+ const mappedIds = [...new Set(store.load().map((mapping) => mapping.topicId))];
40115
+ const moved = setTopicSurfaces(mappedIds, "telegram");
40116
+ store.setFlag(SURFACE_BACKFILL_FLAG);
40117
+ if (moved > 0) {
40118
+ logger.info({ moved, mapped: mappedIds.length }, "telegram adapter: moved mapped topics onto the telegram surface");
40119
+ }
40120
+ }
40241
40121
  for (const persisted of store.load()) {
40242
40122
  const topic = getTopic(persisted.topicId);
40243
40123
  if (topic?.participants.some((participant) => participant.userId === userId)) {
@@ -40258,13 +40138,13 @@ function startTelegramAdapter(opts) {
40258
40138
  function registerTopicLocal(options) {
40259
40139
  suppressMaterialize++;
40260
40140
  try {
40261
- return topicService.create(options);
40141
+ return topicService.create({ ...options, surface: "telegram" });
40262
40142
  } finally {
40263
40143
  suppressMaterialize--;
40264
40144
  }
40265
40145
  }
40266
40146
  function getOrCreateTopic(title, agent) {
40267
- return getTopicByNameForUser(title, userId) ?? registerTopicLocal({ title, userId, kind: "agent", ...agent ? { agent } : {} });
40147
+ return getTopicByNameForUser(title, userId, { surface: "telegram" }) ?? registerTopicLocal({ title, userId, kind: "agent", ...agent ? { agent } : {} });
40268
40148
  }
40269
40149
  function resolveMapping(chatId, threadId) {
40270
40150
  const cached = byKey.get(mappingKey(chatId, threadId));
@@ -40855,7 +40735,7 @@ ${suffix}`;
40855
40735
  function materializeVisibleTopics() {
40856
40736
  if (!forumMode || !forumManageTopicsAvailable)
40857
40737
  return;
40858
- for (const topic of listTopics()) {
40738
+ for (const topic of listTopics({ surface: "telegram" })) {
40859
40739
  if (isTopicVisible(topic) && topic.participants.some((participant) => participant.userId === userId)) {
40860
40740
  materializeTopic(topic);
40861
40741
  }
@@ -41276,7 +41156,7 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
41276
41156
  }
41277
41157
  };
41278
41158
  }
41279
- var FORUM_TOPIC_NAME_MAX = 128, MAX_PARENT_HOPS = 5, DEFAULT_SEND_TIMEOUT_MS = 60000, PHOTO_EXTS;
41159
+ var SURFACE_BACKFILL_FLAG = "surface_backfill_20260808", FORUM_TOPIC_NAME_MAX = 128, MAX_PARENT_HOPS = 5, DEFAULT_SEND_TIMEOUT_MS = 60000, PHOTO_EXTS;
41280
41160
  var init_adapter = __esm(async () => {
41281
41161
  await init_src();
41282
41162
  await init_commands2();
@@ -43934,7 +43814,7 @@ function requirePrimaryOrigin(peer) {
43934
43814
  return peer.verified.fromIsPrimary ? null : jsonError2("only the workspace hub may call this endpoint", 403);
43935
43815
  }
43936
43816
  function peerAddressable(topic) {
43937
- return isTopicShared(topic);
43817
+ return topic.surface === "otium";
43938
43818
  }
43939
43819
  function localCapabilities() {
43940
43820
  const agents = SUPPORTED_AGENTS.map((kind) => {
@@ -44104,7 +43984,7 @@ async function handleSessions(req) {
44104
43984
  const userId = str(body, "userId");
44105
43985
  if (!userId)
44106
43986
  return jsonError2("userId is required", 400);
44107
- const topics = listTopics().filter((topic) => topic.kind !== "manager" && !topic.isSubagent && peerAddressable(topic) && topic.participants.some((p) => p.userId === userId));
43987
+ const topics = listTopics({ surface: "otium" }).filter((topic) => topic.kind !== "manager" && !topic.isSubagent && peerAddressable(topic) && topic.participants.some((p) => p.userId === userId));
44108
43988
  const titleCounts = new Map;
44109
43989
  for (const topic of topics) {
44110
43990
  const normalized = topic.title.toLowerCase();
@@ -44820,26 +44700,8 @@ async function runOtiumCli(args = process.argv.slice(2)) {
44820
44700
  const { loadJoin: loadJoin2, removeJoin: removeJoin2 } = await init_join().then(() => exports_join);
44821
44701
  if (!loadJoin2())
44822
44702
  throw new Error("not joined to an Otium workspace");
44823
- const { getVisibleTopics: getVisibleTopics2, isTopicShared: isTopicShared2, switchTopicAccessMode: switchTopicAccessMode2 } = await init_src().then(() => exports_src);
44824
- let downgraded = 0;
44825
- for (const topic of getVisibleTopics2()) {
44826
- if (!isTopicShared2(topic))
44827
- continue;
44828
- const owner = topic.participants.find((participant) => participant.role === "owner");
44829
- if (!owner)
44830
- continue;
44831
- if (topic.isSubagent)
44832
- continue;
44833
- const switched = switchTopicAccessMode2({
44834
- topicId: topic.id,
44835
- userId: owner.userId,
44836
- accessMode: "private"
44837
- });
44838
- if (switched.ok)
44839
- downgraded += switched.topicIds.length;
44840
- }
44841
44703
  removeJoin2();
44842
- console.log(`disconnected from Otium; workspace credentials removed` + (downgraded > 0 ? `; ${downgraded} topic(s) are now private` : ""));
44704
+ console.log("disconnected from Otium; workspace credentials removed");
44843
44705
  break;
44844
44706
  }
44845
44707
  case "serve": {
@@ -44913,11 +44775,7 @@ function topicsCommand() {
44913
44775
  }
44914
44776
  for (const t of topics) {
44915
44777
  const model = t.effectiveModel ?? t.defaultModel;
44916
- const flags = [
44917
- t.accessMode ?? "private",
44918
- t.isSubagent ? "subagent" : null,
44919
- t.isFork ? "fork" : null
44920
- ].filter(Boolean).join(",");
44778
+ const flags = [t.isSubagent ? "subagent" : null, t.isFork ? "fork" : null].filter(Boolean).join(",");
44921
44779
  console.log(`${t.title} ${t.agent ?? "no-ai"}${model ? `/${model}` : ""}` + `${flags ? ` [${flags}]` : ""} ${t.id}`);
44922
44780
  }
44923
44781
  }
@@ -45565,4 +45423,4 @@ switch (command) {
45565
45423
  }
45566
45424
  }
45567
45425
 
45568
- //# debugId=4A37A5D1997871CA64756E2164756E21
45426
+ //# debugId=488B733FDB6D1AD164756E2164756E21