negotium 0.1.7 → 0.1.8

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 (256) hide show
  1. package/README.md +2 -2
  2. package/dist/chunk-0ay6cjdy.js +1331 -0
  3. package/dist/chunk-0ay6cjdy.js.map +16 -0
  4. package/dist/chunk-1k7t7bnx.js +97 -0
  5. package/dist/chunk-1k7t7bnx.js.map +10 -0
  6. package/dist/chunk-5ahkew9c.js +806 -0
  7. package/dist/chunk-5ahkew9c.js.map +17 -0
  8. package/dist/chunk-6cpn7ht2.js +287 -0
  9. package/dist/chunk-6cpn7ht2.js.map +13 -0
  10. package/dist/chunk-6kctn20e.js +3529 -0
  11. package/dist/chunk-6kctn20e.js.map +20 -0
  12. package/dist/chunk-7xqj7aex.js +77 -0
  13. package/dist/chunk-7xqj7aex.js.map +11 -0
  14. package/dist/chunk-9p0ww3tk.js +41 -0
  15. package/dist/chunk-9p0ww3tk.js.map +10 -0
  16. package/dist/chunk-a92a7nrk.js +252 -0
  17. package/dist/chunk-a92a7nrk.js.map +10 -0
  18. package/dist/chunk-c500xe9z.js +33 -0
  19. package/dist/chunk-c500xe9z.js.map +10 -0
  20. package/dist/chunk-cds1qxsf.js +1644 -0
  21. package/dist/chunk-cds1qxsf.js.map +17 -0
  22. package/dist/chunk-e4407g8x.js +274 -0
  23. package/dist/chunk-e4407g8x.js.map +10 -0
  24. package/dist/chunk-e5pbggsh.js +363 -0
  25. package/dist/chunk-e5pbggsh.js.map +11 -0
  26. package/dist/chunk-edb07sjp.js +567 -0
  27. package/dist/chunk-edb07sjp.js.map +14 -0
  28. package/dist/chunk-exd0v74a.js +3012 -0
  29. package/dist/chunk-exd0v74a.js.map +32 -0
  30. package/dist/chunk-fgrme9tr.js +4398 -0
  31. package/dist/chunk-fgrme9tr.js.map +47 -0
  32. package/dist/chunk-fka9eqe1.js +581 -0
  33. package/dist/chunk-fka9eqe1.js.map +11 -0
  34. package/dist/chunk-g82jygez.js +108 -0
  35. package/dist/chunk-g82jygez.js.map +10 -0
  36. package/dist/chunk-ghs2j1fr.js +312 -0
  37. package/dist/chunk-ghs2j1fr.js.map +10 -0
  38. package/dist/chunk-gmy11efb.js +125 -0
  39. package/dist/chunk-gmy11efb.js.map +10 -0
  40. package/dist/chunk-gxma4c70.js +110 -0
  41. package/dist/chunk-gxma4c70.js.map +10 -0
  42. package/dist/chunk-hkdqhm34.js +61 -0
  43. package/dist/chunk-hkdqhm34.js.map +10 -0
  44. package/dist/chunk-hpa0cpzh.js +134 -0
  45. package/dist/chunk-hpa0cpzh.js.map +10 -0
  46. package/dist/chunk-hwy3n31w.js +21 -0
  47. package/dist/chunk-hwy3n31w.js.map +9 -0
  48. package/dist/chunk-j2behpy7.js +214 -0
  49. package/dist/chunk-j2behpy7.js.map +10 -0
  50. package/dist/chunk-jc0at0tc.js +102 -0
  51. package/dist/chunk-jc0at0tc.js.map +10 -0
  52. package/dist/chunk-jkqq5ef6.js +243 -0
  53. package/dist/chunk-jkqq5ef6.js.map +11 -0
  54. package/dist/chunk-jzkw2f01.js +51 -0
  55. package/dist/chunk-jzkw2f01.js.map +10 -0
  56. package/dist/chunk-jzz91jwh.js +70 -0
  57. package/dist/chunk-jzz91jwh.js.map +10 -0
  58. package/dist/chunk-k9z47mab.js +247 -0
  59. package/dist/chunk-k9z47mab.js.map +12 -0
  60. package/dist/chunk-n17yxpa8.js +23 -0
  61. package/dist/chunk-n17yxpa8.js.map +10 -0
  62. package/dist/chunk-naa09ags.js +2143 -0
  63. package/dist/chunk-naa09ags.js.map +15 -0
  64. package/dist/chunk-ns3ke88n.js +142 -0
  65. package/dist/chunk-ns3ke88n.js.map +10 -0
  66. package/dist/chunk-p3cr2ejg.js +1626 -0
  67. package/dist/chunk-p3cr2ejg.js.map +15 -0
  68. package/dist/chunk-q5bcpf23.js +2925 -0
  69. package/dist/chunk-q5bcpf23.js.map +20 -0
  70. package/dist/chunk-rafc55bs.js +424 -0
  71. package/dist/chunk-rafc55bs.js.map +11 -0
  72. package/dist/chunk-rbrxjvbj.js +2830 -0
  73. package/dist/chunk-rbrxjvbj.js.map +23 -0
  74. package/dist/chunk-sa874j5c.js +9 -0
  75. package/dist/chunk-sa874j5c.js.map +10 -0
  76. package/dist/chunk-sgsz2fpq.js +289 -0
  77. package/dist/chunk-sgsz2fpq.js.map +10 -0
  78. package/dist/chunk-sm6fh5c5.js +569 -0
  79. package/dist/chunk-sm6fh5c5.js.map +14 -0
  80. package/dist/chunk-t9zcg8f1.js +499 -0
  81. package/dist/chunk-t9zcg8f1.js.map +13 -0
  82. package/dist/chunk-vp2db42d.js +121 -0
  83. package/dist/chunk-vp2db42d.js.map +10 -0
  84. package/dist/chunk-w13jjf4a.js +46 -0
  85. package/dist/chunk-w13jjf4a.js.map +10 -0
  86. package/dist/chunk-x1arr7ze.js +63 -0
  87. package/dist/chunk-x1arr7ze.js.map +10 -0
  88. package/dist/chunk-xfq68rch.js +583 -0
  89. package/dist/chunk-xfq68rch.js.map +11 -0
  90. package/dist/main.js +132 -20
  91. package/dist/main.js.map +4 -4
  92. package/dist/runtime/scripts/faster-whisper-wrapper.py +87 -0
  93. package/dist/runtime/scripts/mcp-patchright-http.mjs +349 -0
  94. package/dist/runtime/src/agents/api-topic-agent-switch.ts +178 -0
  95. package/dist/runtime/src/agents/archiver.ts +396 -0
  96. package/dist/runtime/src/agents/auth-check.ts +64 -0
  97. package/dist/runtime/src/agents/claude-provider.ts +737 -0
  98. package/dist/runtime/src/agents/claude-registry.ts +115 -0
  99. package/dist/runtime/src/agents/codex-native-multi-agent.ts +177 -0
  100. package/dist/runtime/src/agents/codex-provider.ts +785 -0
  101. package/dist/runtime/src/agents/codex-registry.ts +115 -0
  102. package/dist/runtime/src/agents/codex-tree-kill.ts +365 -0
  103. package/dist/runtime/src/agents/contracts.ts +69 -0
  104. package/dist/runtime/src/agents/deep-map.ts +13 -0
  105. package/dist/runtime/src/agents/execution-host.ts +91 -0
  106. package/dist/runtime/src/agents/fixtures/claude-attachments.jsonl +2 -0
  107. package/dist/runtime/src/agents/fixtures/codex-shell.jsonl +5 -0
  108. package/dist/runtime/src/agents/fork.ts +65 -0
  109. package/dist/runtime/src/agents/hosted-agent.ts +48 -0
  110. package/dist/runtime/src/agents/idle-archiver.ts +128 -0
  111. package/dist/runtime/src/agents/index.ts +205 -0
  112. package/dist/runtime/src/agents/maestro-provider.ts +206 -0
  113. package/dist/runtime/src/agents/maestro-registry.ts +31 -0
  114. package/dist/runtime/src/agents/mcp-tools/ask-user.ts +280 -0
  115. package/dist/runtime/src/agents/mcp-tools/common.ts +21 -0
  116. package/dist/runtime/src/agents/mcp-tools/self-config.ts +220 -0
  117. package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +525 -0
  118. package/dist/runtime/src/agents/mcp-tools/visuals.ts +88 -0
  119. package/dist/runtime/src/agents/model-catalog.ts +151 -0
  120. package/dist/runtime/src/agents/registry.ts +24 -0
  121. package/dist/runtime/src/agents/rollout/claude.ts +331 -0
  122. package/dist/runtime/src/agents/rollout/codex.ts +569 -0
  123. package/dist/runtime/src/agents/rollout/shared.ts +205 -0
  124. package/dist/runtime/src/agents/self-config-core.ts +415 -0
  125. package/dist/runtime/src/agents/task-events.ts +44 -0
  126. package/dist/runtime/src/agents/tool-format.ts +352 -0
  127. package/dist/runtime/src/agents/topic-agent-switch.ts +193 -0
  128. package/dist/runtime/src/agents/topic-cleanup.ts +231 -0
  129. package/dist/runtime/src/agents/vault-tool-policy.ts +33 -0
  130. package/dist/runtime/src/application/execute-external-user-turn.ts +23 -0
  131. package/dist/runtime/src/application/submit-user-message.ts +47 -0
  132. package/dist/runtime/src/application/switch-topic-effort.ts +48 -0
  133. package/dist/runtime/src/application/switch-topic-model.ts +79 -0
  134. package/dist/runtime/src/application/topic-service.ts +139 -0
  135. package/dist/runtime/src/application/vault-command.ts +170 -0
  136. package/dist/runtime/src/bus.ts +319 -0
  137. package/dist/runtime/src/index.ts +333 -0
  138. package/dist/runtime/src/mcp/agent-health-server.ts +309 -0
  139. package/dist/runtime/src/mcp/background-bash-server.ts +346 -0
  140. package/dist/runtime/src/mcp/canonical-bridge-config.ts +84 -0
  141. package/dist/runtime/src/mcp/canonical-proxy-server.ts +171 -0
  142. package/dist/runtime/src/mcp/dm-ipc.ts +60 -0
  143. package/dist/runtime/src/mcp/mcp-helpers.ts +40 -0
  144. package/dist/runtime/src/mcp/paddleocr-server.ts +105 -0
  145. package/dist/runtime/src/mcp/peer-bridge.ts +114 -0
  146. package/dist/runtime/src/mcp/runtime-spec.ts +134 -0
  147. package/dist/runtime/src/mcp/session-comm/bridge-ipc-config.ts +27 -0
  148. package/dist/runtime/src/mcp/session-comm/peer-forward.ts +165 -0
  149. package/dist/runtime/src/mcp/session-comm/runtime.ts +115 -0
  150. package/dist/runtime/src/mcp/session-comm/server.ts +649 -0
  151. package/dist/runtime/src/mcp/session-comm/topic-config.ts +131 -0
  152. package/dist/runtime/src/mcp/session-comm/topics.ts +229 -0
  153. package/dist/runtime/src/mcp/stdio-protect.ts +84 -0
  154. package/dist/runtime/src/mcp/system-health-server.ts +130 -0
  155. package/dist/runtime/src/mcp/task-server.ts +173 -0
  156. package/dist/runtime/src/mcp/token-stats-server.ts +98 -0
  157. package/dist/runtime/src/mcp/vault-http.ts +219 -0
  158. package/dist/runtime/src/mcp/vault-run.ts +153 -0
  159. package/dist/runtime/src/mcp/vault-server.ts +104 -0
  160. package/dist/runtime/src/mcp/wiki-server.ts +755 -0
  161. package/dist/runtime/src/media/file-events.ts +33 -0
  162. package/dist/runtime/src/media/text-extractor.ts +312 -0
  163. package/dist/runtime/src/media/video.ts +92 -0
  164. package/dist/runtime/src/outbox/file-ops.ts +210 -0
  165. package/dist/runtime/src/outbox/index.ts +8 -0
  166. package/dist/runtime/src/outbox/utils.ts +51 -0
  167. package/dist/runtime/src/platform/background-bash/manager.ts +279 -0
  168. package/dist/runtime/src/platform/config.ts +371 -0
  169. package/dist/runtime/src/platform/constants.ts +18 -0
  170. package/dist/runtime/src/platform/debug-users.ts +14 -0
  171. package/dist/runtime/src/platform/delay.ts +1 -0
  172. package/dist/runtime/src/platform/error.ts +9 -0
  173. package/dist/runtime/src/platform/file-utils.ts +16 -0
  174. package/dist/runtime/src/platform/jsonl.ts +202 -0
  175. package/dist/runtime/src/platform/lifecycle.ts +174 -0
  176. package/dist/runtime/src/platform/logger.ts +31 -0
  177. package/dist/runtime/src/platform/maestro-bootstrap-env.ts +3 -0
  178. package/dist/runtime/src/platform/mcp-config.ts +865 -0
  179. package/dist/runtime/src/platform/modules.ts +145 -0
  180. package/dist/runtime/src/platform/node-plugins.ts +46 -0
  181. package/dist/runtime/src/platform/playwright/manager.ts +1233 -0
  182. package/dist/runtime/src/prompts/agents/wiki-archiver.md +261 -0
  183. package/dist/runtime/src/prompts/builders.ts +357 -0
  184. package/dist/runtime/src/prompts/sessions/channel-system.md +48 -0
  185. package/dist/runtime/src/prompts/sessions/manager-system.md +37 -0
  186. package/dist/runtime/src/prompts/sessions/topic-system.md +47 -0
  187. package/dist/runtime/src/prompts/sessions/visual-design.md +86 -0
  188. package/dist/runtime/src/query/active-rooms.ts +532 -0
  189. package/dist/runtime/src/query/control.ts +313 -0
  190. package/dist/runtime/src/query/index.ts +24 -0
  191. package/dist/runtime/src/query/session-inbox-cleanup.ts +44 -0
  192. package/dist/runtime/src/query/session-inbox-path.ts +42 -0
  193. package/dist/runtime/src/query/state.ts +94 -0
  194. package/dist/runtime/src/query/types.ts +83 -0
  195. package/dist/runtime/src/runtime/ask-callbacks.ts +153 -0
  196. package/dist/runtime/src/runtime/attachments.ts +151 -0
  197. package/dist/runtime/src/runtime/background-sessions.ts +83 -0
  198. package/dist/runtime/src/runtime/channel-context.ts +107 -0
  199. package/dist/runtime/src/runtime/errors.ts +69 -0
  200. package/dist/runtime/src/runtime/event-heartbeat.ts +77 -0
  201. package/dist/runtime/src/runtime/file-hooks.ts +81 -0
  202. package/dist/runtime/src/runtime/footer.ts +23 -0
  203. package/dist/runtime/src/runtime/inbox.ts +946 -0
  204. package/dist/runtime/src/runtime/tasks.ts +56 -0
  205. package/dist/runtime/src/runtime/topic-config.ts +29 -0
  206. package/dist/runtime/src/runtime/turn-runner.ts +2286 -0
  207. package/dist/runtime/src/runtime/usage-alert.ts +84 -0
  208. package/dist/runtime/src/runtime/visual-html.ts +109 -0
  209. package/dist/runtime/src/runtime/visual-store.ts +430 -0
  210. package/dist/runtime/src/runtime/visuals.ts +171 -0
  211. package/dist/runtime/src/security/sanitize.ts +27 -0
  212. package/dist/runtime/src/security/sensitive-path.ts +34 -0
  213. package/dist/runtime/src/storage/activity-log.ts +152 -0
  214. package/dist/runtime/src/storage/api-messages.ts +617 -0
  215. package/dist/runtime/src/storage/api-topic-brief.ts +127 -0
  216. package/dist/runtime/src/storage/api-topic-config.ts +192 -0
  217. package/dist/runtime/src/storage/api-topics.ts +844 -0
  218. package/dist/runtime/src/storage/app-settings.ts +44 -0
  219. package/dist/runtime/src/storage/browser-profiles.ts +133 -0
  220. package/dist/runtime/src/storage/conversations.ts +268 -0
  221. package/dist/runtime/src/storage/forum/agent-settings.ts +20 -0
  222. package/dist/runtime/src/storage/forum/index.ts +4 -0
  223. package/dist/runtime/src/storage/forum/repository.ts +283 -0
  224. package/dist/runtime/src/storage/forum/schema.ts +300 -0
  225. package/dist/runtime/src/storage/forum-db.ts +23 -0
  226. package/dist/runtime/src/storage/runtime-events.ts +130 -0
  227. package/dist/runtime/src/storage/runtime-leases.ts +189 -0
  228. package/dist/runtime/src/storage/runtime-process-leases.ts +216 -0
  229. package/dist/runtime/src/storage/runtime-topic-state.ts +148 -0
  230. package/dist/runtime/src/storage/runtime-turn-requests.ts +300 -0
  231. package/dist/runtime/src/storage/self-schedules.ts +298 -0
  232. package/dist/runtime/src/storage/session-asks.ts +347 -0
  233. package/dist/runtime/src/storage/sqlite.ts +129 -0
  234. package/dist/runtime/src/storage/tasks.ts +179 -0
  235. package/dist/runtime/src/storage/token-stats.ts +144 -0
  236. package/dist/runtime/src/storage/topic-archive-state.ts +59 -0
  237. package/dist/runtime/src/storage/topic-archive.ts +74 -0
  238. package/dist/runtime/src/storage/topic-settings.ts +67 -0
  239. package/dist/runtime/src/storage/topic-transcript.ts +98 -0
  240. package/dist/runtime/src/storage/vault-crypto.ts +65 -0
  241. package/dist/runtime/src/storage/vault.ts +225 -0
  242. package/dist/runtime/src/storage/wiki-summary-names.ts +33 -0
  243. package/dist/runtime/src/storage/wiki.ts +85 -0
  244. package/dist/runtime/src/topics/create.ts +123 -0
  245. package/dist/runtime/src/topics/derive.ts +398 -0
  246. package/dist/runtime/src/topics/lifecycle.ts +311 -0
  247. package/dist/runtime/src/topics/links.ts +7 -0
  248. package/dist/runtime/src/topics/personal-general.ts +65 -0
  249. package/dist/runtime/src/topics/session.ts +381 -0
  250. package/dist/runtime/src/types/api.ts +497 -0
  251. package/dist/runtime/src/types.ts +255 -0
  252. package/dist/runtime/src/version.ts +1 -0
  253. package/dist/runtime/tsconfig.json +13 -0
  254. package/package.json +16 -3
  255. package/dist/main.d.ts +0 -3
  256. package/dist/main.d.ts.map +0 -1
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../packages/core/src/platform/constants.ts", "../../../packages/core/src/storage/api-topics.ts"],
4
+ "sourcesContent": [
5
+ "/** \"from\" field for session-inbox entries injected after a setting change. */\nexport const FROM_AUTO_CONTINUE = \"auto-continue\";\n\n/** \"from\" field for durable delayed continuations created by schedule_self. */\nexport const FROM_SELF_SCHEDULE = \"self-schedule\";\n\n/** Topic names that collide with internal sentinels and must not be used. */\nexport const RESERVED_TOPIC_NAMES: ReadonlySet<string> = new Set([\n FROM_AUTO_CONTINUE,\n FROM_SELF_SCHEDULE,\n \"general\",\n]);\n\n/** Legacy shared General id. New General rooms use per-user UUIDs. */\nexport const GENERAL_TOPIC_ID = \"general\";\n\n/** Internal topic name for the forum manager session (General topic). */\nexport const TOPIC_MANAGER = \"__manager__\";\n",
6
+ "// Persistent topic store backed by shared SQLite.\n\nimport { GENERAL_TOPIC_ID } from \"#platform/constants\";\nimport { logger } from \"#platform/logger\";\nimport { db } from \"#storage/forum-db\";\nimport type { AgentKind, EffortLevel } from \"#types\";\nimport type {\n AiMode,\n ParticipantDto,\n TopicAccessMode,\n TopicDto,\n TopicKind,\n TopicVisibility,\n} from \"#types/api\";\n\nconst DEFAULT_AGENT_ROOM_AGENT: AgentKind = \"maestro\";\n\nfunction tableColumns(table: string): Set<string> {\n const rows = db.query(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;\n return new Set(rows.map((row) => row.name));\n}\n\n/** Persist the authoritative agent and, when supplied, its normalized base defaults. */\nexport function setApiTopicAgent(\n topicId: string,\n agent: AgentKind,\n defaults?: { model: string; effort?: EffortLevel },\n): void {\n if (!defaults) {\n db.query(\"UPDATE api_topics SET agent = ? WHERE id = ?\").run(agent, topicId);\n return;\n }\n db.query(\"UPDATE api_topics SET agent = ?, base_model = ?, base_effort = ? WHERE id = ?\").run(\n agent,\n defaults.model,\n defaults.effort ?? null,\n topicId,\n );\n}\n\ndb.exec(`\n CREATE TABLE IF NOT EXISTS api_topics (\n id TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n kind TEXT NOT NULL DEFAULT 'channel',\n description TEXT,\n agent TEXT,\n default_model TEXT,\n default_effort TEXT,\n participants TEXT,\n created_at TEXT NOT NULL,\n last_message_at TEXT,\n is_archived INTEGER NOT NULL DEFAULT 0,\n ai_mention INTEGER NOT NULL DEFAULT 0,\n ai_mode TEXT\n )\n`);\n\nconst initialTopicColumns = tableColumns(\"api_topics\");\nconst legacyTopicSchema = !initialTopicColumns.has(\"response_policy\");\nconst needsCanonicalTopicRebuild = legacyTopicSchema || !initialTopicColumns.has(\"agent\");\nif (legacyTopicSchema) {\n // Rename the legacy column once; new databases are created with `agent`.\n try {\n db.exec(\"ALTER TABLE api_topics RENAME COLUMN default_agent TO agent\");\n } catch {\n // Already migrated or freshly created.\n }\n\n // Migrate existing DBs that predate the ai_mention column (team-mode topics).\n try {\n db.exec(\"ALTER TABLE api_topics ADD COLUMN ai_mention INTEGER NOT NULL DEFAULT 0\");\n } catch {\n // Column already exists — nothing to do.\n }\n\n // Migrate for spawn/fork tracking (R1).\n try {\n db.exec(\"ALTER TABLE api_topics ADD COLUMN parent_topic_id TEXT\");\n } catch {\n // Column already exists.\n }\n try {\n db.exec(\"ALTER TABLE api_topics ADD COLUMN is_fork INTEGER NOT NULL DEFAULT 0\");\n } catch {\n // Column already exists.\n }\n\n // Migrate for agent-spawned subagent worker rooms.\n try {\n db.exec(\"ALTER TABLE api_topics ADD COLUMN is_subagent INTEGER NOT NULL DEFAULT 0\");\n } catch {\n // Column already exists.\n }\n\n // Migrate for session persistence.\n try {\n db.exec(\"ALTER TABLE api_topics ADD COLUMN session_id TEXT\");\n } catch {\n // Column already exists.\n }\n\n try {\n db.exec(\"ALTER TABLE api_topics ADD COLUMN kind TEXT NOT NULL DEFAULT 'channel'\");\n } catch {\n // Column already exists.\n }\n\n try {\n db.exec(\"ALTER TABLE api_topics ADD COLUMN ai_mode TEXT\");\n } catch {\n // Column already exists.\n }\n\n db.exec(`\n CREATE TABLE IF NOT EXISTS api_schema_migrations (\n key TEXT PRIMARY KEY,\n applied_at TEXT NOT NULL\n )\n`);\n\n const ALWAYS_RESPOND_MIGRATION = \"api_topics_ai_invited_default_always_respond_20260623\";\n const alwaysRespondMigration = db\n .query(\"SELECT key FROM api_schema_migrations WHERE key = ?\")\n .get(ALWAYS_RESPOND_MIGRATION);\n if (!alwaysRespondMigration) {\n db.transaction(() => {\n db.query(\"UPDATE api_topics SET ai_mention = 0 WHERE agent IS NOT NULL\").run();\n db.query(\"INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)\").run(\n ALWAYS_RESPOND_MIGRATION,\n new Date().toISOString(),\n );\n })();\n }\n\n const GENERAL_AGENT_KIND_MIGRATION = \"api_topics_general_agent_kind_20260704\";\n const generalAgentKindMigration = db\n .query(\"SELECT key FROM api_schema_migrations WHERE key = ?\")\n .get(GENERAL_AGENT_KIND_MIGRATION);\n if (!generalAgentKindMigration) {\n db.transaction(() => {\n db.query(\"UPDATE api_topics SET kind = 'agent', ai_mention = 0 WHERE id = ?\").run(\n GENERAL_TOPIC_ID,\n );\n db.query(\"INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)\").run(\n GENERAL_AGENT_KIND_MIGRATION,\n new Date().toISOString(),\n );\n })();\n }\n\n const AI_MODE_MIGRATION = \"api_topics_ai_mode_20260704\";\n const aiModeMigration = db\n .query(\"SELECT key FROM api_schema_migrations WHERE key = ?\")\n .get(AI_MODE_MIGRATION);\n if (!aiModeMigration) {\n db.transaction(() => {\n db.query(\n `UPDATE api_topics\n SET\n kind = CASE\n WHEN id = ? THEN 'agent'\n WHEN kind = 'channel' AND agent IS NOT NULL AND ai_mention = 0 THEN 'agent'\n ELSE kind\n END,\n ai_mode = CASE\n WHEN id = ? THEN 'always'\n WHEN agent IS NULL THEN 'off'\n WHEN kind = 'agent' THEN 'always'\n WHEN kind = 'channel' AND ai_mention != 0 THEN 'mention'\n WHEN kind = 'channel' AND ai_mention = 0 THEN 'always'\n WHEN ai_mention != 0 THEN 'mention'\n ELSE 'always'\n END`,\n ).run(GENERAL_TOPIC_ID, GENERAL_TOPIC_ID);\n db.query(\"INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)\").run(\n AI_MODE_MIGRATION,\n new Date().toISOString(),\n );\n })();\n }\n\n const GENERAL_MANAGER_KIND_MIGRATION = \"api_topics_general_manager_kind_20260704\";\n const generalManagerKindMigration = db\n .query(\"SELECT key FROM api_schema_migrations WHERE key = ?\")\n .get(GENERAL_MANAGER_KIND_MIGRATION);\n if (!generalManagerKindMigration) {\n db.transaction(() => {\n db.query(\n \"UPDATE api_topics SET kind = 'manager', ai_mention = 0, ai_mode = 'always' WHERE id = ?\",\n ).run(GENERAL_TOPIC_ID);\n db.query(\"INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)\").run(\n GENERAL_MANAGER_KIND_MIGRATION,\n new Date().toISOString(),\n );\n })();\n }\n}\n\nfunction createCanonicalTopicsTable(name: string): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS ${name} (\n id TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n kind TEXT NOT NULL CHECK (kind IN ('channel','agent','manager')),\n description TEXT,\n agent TEXT CHECK (agent IS NULL OR agent IN ('maestro','claude','codex')),\n base_model TEXT,\n base_effort TEXT CHECK (base_effort IS NULL OR base_effort IN ('low','medium','high','xhigh','max')),\n response_policy TEXT NOT NULL CHECK (response_policy IN ('off','mention','always')),\n created_at TEXT NOT NULL,\n last_message_at TEXT,\n parent_topic_id TEXT,\n is_fork INTEGER NOT NULL DEFAULT 0 CHECK (is_fork IN (0,1)),\n is_subagent INTEGER NOT NULL DEFAULT 0 CHECK (is_subagent IN (0,1)),\n visibility TEXT NOT NULL DEFAULT 'visible' CHECK (visibility IN ('visible','hidden')),\n access_mode TEXT NOT NULL DEFAULT 'private' CHECK (access_mode IN ('private','shared')),\n browser_profile TEXT NOT NULL DEFAULT 'default',\n browser_profile_owner TEXT,\n session_id TEXT,\n CHECK (\n (kind = 'channel' AND response_policy = 'off' AND agent IS NULL) OR\n (kind = 'channel' AND response_policy = 'mention' AND agent IS NOT NULL) OR\n (kind IN ('agent','manager') AND response_policy = 'always' AND agent IS NOT NULL)\n )\n )\n `);\n}\n\nfunction createTopicMembersTable(): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS topic_members (\n topic_id TEXT NOT NULL REFERENCES api_topics(id) ON DELETE CASCADE,\n user_id TEXT NOT NULL,\n role TEXT NOT NULL CHECK (role IN ('owner','member')),\n PRIMARY KEY (topic_id, user_id)\n )\n `);\n db.exec(\"CREATE INDEX IF NOT EXISTS idx_topic_members_user ON topic_members(user_id)\");\n}\n\nif (needsCanonicalTopicRebuild) {\n const legacyRows = db.query(\"SELECT * FROM api_topics\").all() as Array<Record<string, unknown>>;\n const existingMemberRows = tableColumns(\"topic_members\").has(\"topic_id\")\n ? (db.query(\"SELECT topic_id, user_id, role FROM topic_members\").all() as Array<{\n topic_id: string;\n user_id: string;\n role: string;\n }>)\n : [];\n const configColumns = tableColumns(\"api_topic_config\");\n const legacyAgentOverrides = configColumns.has(\"agent\")\n ? new Map(\n (\n db\n .query(\"SELECT topic_id, agent FROM api_topic_config WHERE agent IS NOT NULL\")\n .all() as Array<{ topic_id: string; agent: AgentKind }>\n ).map((row) => [row.topic_id, row.agent]),\n )\n : new Map<string, AgentKind>();\n const previousForeignKeys = db\n .query<{ foreign_keys: number }, []>(\"PRAGMA foreign_keys\")\n .get()?.foreign_keys;\n db.exec(\"PRAGMA foreign_keys = OFF\");\n try {\n db.transaction(() => {\n createCanonicalTopicsTable(\"api_topics_next\");\n for (const row of legacyRows) {\n const selectedAgent = (legacyAgentOverrides.get(String(row.id)) ??\n row.agent ??\n row.runtime_agent ??\n row.default_agent ??\n undefined) as AgentKind | undefined;\n const normalized = normalizeTopicState({\n id: String(row.id),\n kind: normalizeTopicKind(row.kind),\n agent: selectedAgent,\n aiMode: normalizeAiMode(row.response_policy ?? row.ai_mode),\n aiMention: Number(row.ai_mention ?? 0) !== 0,\n });\n const legacyBaseModel = row.base_model ?? row.default_model;\n const legacyBaseEffort = row.base_effort ?? row.default_effort;\n db.query(\n `INSERT INTO api_topics_next\n (id,title,kind,description,agent,base_model,base_effort,response_policy,\n created_at,last_message_at,parent_topic_id,is_fork,is_subagent,visibility,access_mode,session_id)\n VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,\n ).run(\n String(row.id),\n String(row.title),\n normalized.kind,\n typeof row.description === \"string\" ? row.description : null,\n normalized.agent ?? null,\n typeof legacyBaseModel === \"string\" ? legacyBaseModel : null,\n typeof legacyBaseEffort === \"string\" ? legacyBaseEffort : null,\n normalized.aiMode,\n String(row.created_at),\n typeof row.last_message_at === \"string\" ? row.last_message_at : null,\n typeof row.parent_topic_id === \"string\" ? row.parent_topic_id : null,\n Number(row.is_fork ?? 0) !== 0 ? 1 : 0,\n Number(row.is_subagent ?? 0) !== 0 ? 1 : 0,\n row.visibility === \"hidden\" ? \"hidden\" : \"visible\",\n row.access_mode === \"shared\" ? \"shared\" : \"private\",\n typeof row.session_id === \"string\" ? row.session_id : null,\n );\n }\n db.exec(\"DROP TABLE IF EXISTS topic_members\");\n db.exec(\"DROP TABLE api_topics\");\n db.exec(\"ALTER TABLE api_topics_next RENAME TO api_topics\");\n createTopicMembersTable();\n if (existingMemberRows.length > 0) {\n for (const member of existingMemberRows) {\n db.query(\n \"INSERT OR REPLACE INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)\",\n ).run(member.topic_id, member.user_id, member.role === \"owner\" ? \"owner\" : \"member\");\n }\n } else {\n for (const row of legacyRows) {\n let participants: ParticipantDto[] = [];\n try {\n const parsed = JSON.parse(String(row.participants ?? \"[]\"));\n if (Array.isArray(parsed)) participants = parsed as ParticipantDto[];\n } catch {\n participants = [];\n }\n for (const participant of participants) {\n if (!participant?.userId) continue;\n db.query(\n \"INSERT OR REPLACE INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)\",\n ).run(\n String(row.id),\n participant.userId,\n participant.role === \"owner\" ? \"owner\" : \"member\",\n );\n }\n }\n }\n })();\n } finally {\n db.exec(`PRAGMA foreign_keys = ${previousForeignKeys ? \"ON\" : \"OFF\"}`);\n }\n} else {\n createCanonicalTopicsTable(\"api_topics\");\n createTopicMembersTable();\n}\n\n// Canonical databases created before explicit adapter visibility need a\n// lightweight additive migration; fresh/rebuilt databases already have it.\nif (!tableColumns(\"api_topics\").has(\"visibility\")) {\n db.exec(\"ALTER TABLE api_topics ADD COLUMN visibility TEXT NOT NULL DEFAULT 'visible'\");\n}\nif (!tableColumns(\"api_topics\").has(\"access_mode\")) {\n db.exec(\"ALTER TABLE api_topics ADD COLUMN access_mode TEXT NOT NULL DEFAULT 'private'\");\n}\nif (!tableColumns(\"api_topics\").has(\"browser_profile\")) {\n db.exec(\"ALTER TABLE api_topics ADD COLUMN browser_profile TEXT NOT NULL DEFAULT 'default'\");\n}\nif (!tableColumns(\"api_topics\").has(\"browser_profile_owner\")) {\n db.exec(\"ALTER TABLE api_topics ADD COLUMN browser_profile_owner TEXT\");\n}\ndb.exec(`\n UPDATE api_topics\n SET browser_profile_owner = (\n SELECT m.user_id FROM topic_members m\n WHERE m.topic_id = api_topics.id AND m.role = 'owner'\n ORDER BY m.rowid\n LIMIT 1\n )\n WHERE browser_profile_owner IS NULL\n`);\n\ninterface TopicRow {\n id: string;\n title: string;\n kind: string | null;\n description: string | null;\n agent: string | null;\n base_model: string | null;\n base_effort: string | null;\n response_policy: string;\n created_at: string;\n last_message_at: string | null;\n parent_topic_id: string | null;\n is_fork: number;\n is_subagent: number;\n visibility: string | null;\n access_mode: string | null;\n browser_profile_owner: string | null;\n session_id: string | null;\n}\n\ninterface TopicSessionLogContext {\n reason?: string;\n queryId?: string;\n agent?: AgentKind;\n}\n\nfunction shortSessionId(sessionId: string | null | undefined): string | null {\n return sessionId ? sessionId.slice(0, 8) : null;\n}\n\nfunction getTopicParticipants(topicId: string): ParticipantDto[] {\n return db\n .query<{ user_id: string; role: \"owner\" | \"member\" }, string>(\n \"SELECT user_id, role FROM topic_members WHERE topic_id = ? ORDER BY rowid\",\n )\n .all(topicId)\n .map((row) => ({ userId: row.user_id, role: row.role }));\n}\n\nfunction getAllTopicParticipants(): Map<string, ParticipantDto[]> {\n const grouped = new Map<string, ParticipantDto[]>();\n const rows = db\n .query<{ topic_id: string; user_id: string; role: \"owner\" | \"member\" }, []>(\n \"SELECT topic_id, user_id, role FROM topic_members ORDER BY rowid\",\n )\n .all();\n for (const row of rows) {\n const participants = grouped.get(row.topic_id) ?? [];\n participants.push({ userId: row.user_id, role: row.role });\n grouped.set(row.topic_id, participants);\n }\n return grouped;\n}\n\nfunction rowToDto(r: TopicRow, participants = getTopicParticipants(r.id)): TopicDto {\n const normalized = normalizeTopicState({\n id: r.id,\n kind: normalizeTopicKind(r.kind),\n agent: (r.agent as AgentKind | null) ?? undefined,\n aiMode: normalizeAiMode(r.response_policy),\n });\n return {\n id: r.id,\n title: r.title,\n kind: normalized.kind,\n description: r.description ?? undefined,\n agent: normalized.agent,\n defaultModel: r.base_model ?? \"deepseek-pro\",\n defaultEffort: (r.base_effort as EffortLevel) ?? undefined,\n aiMode: normalized.aiMode,\n aiMention: aiMentionFromMode(normalized.aiMode),\n participants,\n createdAt: r.created_at,\n lastMessageAt: r.last_message_at ?? new Date().toISOString(),\n parentTopicId: r.parent_topic_id ?? undefined,\n isFork: r.is_fork !== 0,\n ...(r.is_subagent !== 0 ? { isSubagent: true } : {}),\n visibility: normalizeTopicVisibility(r.visibility),\n accessMode: normalizeTopicAccessMode(r.access_mode),\n };\n}\n\nexport function normalizeTopicAccessMode(value: unknown): TopicAccessMode {\n return value === \"shared\" ? \"shared\" : \"private\";\n}\n\n/** Otium and other non-local adapters may only address shared topics. */\nexport function isTopicShared(topic: Pick<TopicDto, \"accessMode\">): boolean {\n return topic.accessMode === \"shared\";\n}\n\nexport function normalizeTopicVisibility(value: unknown): TopicVisibility {\n return value === \"hidden\" ? \"hidden\" : \"visible\";\n}\n\n/** Discovery boundary shared by user-facing adapters. */\nexport function isTopicVisible(topic: Pick<TopicDto, \"visibility\">): boolean {\n return topic.visibility !== \"hidden\";\n}\n\nexport function normalizeTopicKind(value: unknown): TopicKind | null {\n return value === \"channel\" || value === \"agent\" || value === \"manager\" ? value : null;\n}\n\nexport function normalizeAiMode(value: unknown): AiMode | null {\n return value === \"off\" || value === \"mention\" || value === \"always\" ? value : null;\n}\n\nexport function aiMentionFromMode(mode: AiMode): boolean {\n return mode === \"mention\";\n}\n\nexport function inferTopicKind(input: {\n agent?: string | null;\n aiMention?: boolean | null;\n}): TopicKind {\n return input.agent && input.aiMention !== true ? \"agent\" : \"channel\";\n}\n\nexport function inferAiMode(input: {\n kind?: TopicKind | null;\n agent?: string | null;\n aiMode?: AiMode | null;\n aiMention?: boolean | null;\n}): AiMode {\n if (input.kind === \"manager\") return \"always\";\n if (input.kind === \"agent\") return \"always\";\n if (input.kind === \"channel\") {\n if (!input.agent) return \"off\";\n if (input.aiMode === \"mention\") return \"mention\";\n if (input.aiMention === true) return \"mention\";\n return \"mention\";\n }\n if (!input.agent) return \"off\";\n if (input.aiMode) return input.aiMode;\n return input.aiMention === true ? \"mention\" : \"always\";\n}\n\nexport function normalizeTopicState(input: {\n id?: string;\n kind?: TopicKind | null;\n agent?: AgentKind | null;\n aiMode?: AiMode | null;\n aiMention?: boolean | null;\n}): { kind: TopicKind; aiMode: AiMode; agent?: AgentKind } {\n if (input.kind === \"manager\" || input.id === GENERAL_TOPIC_ID) {\n return {\n kind: \"manager\",\n aiMode: \"always\",\n agent: input.agent ?? DEFAULT_AGENT_ROOM_AGENT,\n };\n }\n\n const requestedKind = input.kind;\n const kind =\n requestedKind ??\n (input.aiMode === \"always\"\n ? \"agent\"\n : input.aiMode === \"mention\" || input.aiMode === \"off\"\n ? \"channel\"\n : input.agent && input.aiMention !== true\n ? \"agent\"\n : \"channel\");\n\n if (kind === \"agent\") {\n return {\n kind: \"agent\",\n aiMode: \"always\",\n agent: input.agent ?? DEFAULT_AGENT_ROOM_AGENT,\n };\n }\n\n const agent = input.aiMode === \"off\" ? undefined : (input.agent ?? undefined);\n return {\n kind: \"channel\",\n aiMode: agent ? \"mention\" : \"off\",\n agent,\n };\n}\n\nfunction normalizedTitle(title: string): string {\n return title.trim().toLowerCase();\n}\n\nexport function upsertTopic(t: TopicDto): void {\n const normalized = normalizeTopicState({\n id: t.id,\n kind: normalizeTopicKind(t.kind),\n agent: t.agent ?? undefined,\n aiMode: normalizeAiMode(t.aiMode),\n aiMention: t.aiMention,\n });\n db.transaction(() => {\n db.query(\n `INSERT INTO api_topics\n (id,title,kind,description,agent,base_model,base_effort,response_policy,\n created_at,last_message_at,parent_topic_id,is_fork,is_subagent,visibility,access_mode)\n VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)\n ON CONFLICT(id) DO UPDATE SET\n title = excluded.title,\n kind = excluded.kind,\n description = excluded.description,\n agent = excluded.agent,\n base_model = excluded.base_model,\n base_effort = excluded.base_effort,\n response_policy = excluded.response_policy,\n created_at = excluded.created_at,\n last_message_at = excluded.last_message_at,\n parent_topic_id = excluded.parent_topic_id,\n is_fork = excluded.is_fork,\n is_subagent = excluded.is_subagent,\n visibility = excluded.visibility,\n access_mode = excluded.access_mode`,\n ).run(\n t.id,\n t.title,\n normalized.kind,\n t.description ?? null,\n normalized.agent ?? null,\n t.defaultModel ?? null,\n t.defaultEffort ?? null,\n normalized.aiMode,\n t.createdAt,\n t.lastMessageAt ?? null,\n t.parentTopicId ?? null,\n t.isFork ? 1 : 0,\n t.isSubagent ? 1 : 0,\n normalizeTopicVisibility(t.visibility),\n normalizeTopicAccessMode(t.accessMode),\n );\n db.query(\"DELETE FROM topic_members WHERE topic_id = ?\").run(t.id);\n for (const participant of t.participants) {\n db.query(\"INSERT INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)\").run(\n t.id,\n participant.userId,\n participant.role,\n );\n }\n const initialBrowserProfileOwner = t.participants.find(\n (participant) => participant.role === \"owner\",\n )?.userId;\n if (initialBrowserProfileOwner) {\n db.query(\n `UPDATE api_topics\n SET browser_profile_owner = COALESCE(browser_profile_owner, ?)\n WHERE id = ?`,\n ).run(initialBrowserProfileOwner, t.id);\n }\n })();\n}\n\nexport function listTopics(): TopicDto[] {\n const rows = db\n .query(\"SELECT * FROM api_topics ORDER BY last_message_at DESC\")\n .all() as TopicRow[];\n const participants = getAllTopicParticipants();\n return rows.map((row) => rowToDto(row, participants.get(row.id) ?? []));\n}\n\nexport function getTopic(id: string): TopicDto | null {\n const r = db.query(\"SELECT * FROM api_topics WHERE id = ?\").get(id) as TopicRow | undefined;\n return r ? rowToDto(r) : null;\n}\n\n/** Return the private manager room owned by a user, excluding the retired shared General row. */\nexport function getManagerTopicForUser(userId: string): TopicDto | null {\n const row = db\n .query<TopicRow, [string, string]>(\n `SELECT t.* FROM api_topics t\n JOIN topic_members m ON m.topic_id = t.id\n WHERE t.kind = 'manager'\n AND t.id != ?\n AND m.user_id = ?\n AND m.role = 'owner'\n ORDER BY t.created_at ASC\n LIMIT 1`,\n )\n .get(GENERAL_TOPIC_ID, userId);\n return row ? rowToDto(row) : null;\n}\n\n/**\n * Resolve the topic whose wiki memory should be used for this topic.\n *\n * Derived rooms keep `parentTopicId` as an immediate UI link, but memory follows\n * Otium's `forkOrigin` semantics: a fork/spawn chain writes to and reads from\n * the original root topic.\n */\nexport function getTopicMemoryOrigin(id: string): TopicDto | null {\n let current = getTopic(id);\n if (!current) return null;\n\n const seen = new Set<string>([current.id]);\n while (current.parentTopicId && !seen.has(current.parentTopicId)) {\n const parent = getTopic(current.parentTopicId);\n if (!parent) break;\n current = parent;\n seen.add(current.id);\n }\n\n return current;\n}\n\n/** Look up a topic by its user-visible title (case-insensitive exact match). */\nexport function getTopicByName(title: string): TopicDto | null {\n const r = db.query(\"SELECT * FROM api_topics WHERE LOWER(title) = LOWER(?)\").get(title) as\n | TopicRow\n | undefined;\n return r ? rowToDto(r) : null;\n}\n\nexport function getTopicByNameAndKind(title: string, kind: TopicKind): TopicDto | null {\n const r = db\n .query(\"SELECT * FROM api_topics WHERE LOWER(title) = LOWER(?) AND kind = ?\")\n .get(title, kind) as TopicRow | undefined;\n return r ? rowToDto(r) : null;\n}\n\nexport function findTopicTitleConflict(\n title: string,\n kind: TopicKind,\n opts: { excludeTopicId?: string } = {},\n): TopicDto | null {\n const wanted = normalizedTitle(title);\n const generalTitleRequested = wanted === normalizedTitle(GENERAL_TOPIC_ID);\n if (generalTitleRequested && opts.excludeTopicId !== GENERAL_TOPIC_ID) {\n const general = db.query(\"SELECT * FROM api_topics WHERE id = ?\").get(GENERAL_TOPIC_ID) as\n | TopicRow\n | undefined;\n if (general) return rowToDto(general);\n }\n\n const params: string[] = [wanted];\n let sql = \"SELECT * FROM api_topics WHERE LOWER(TRIM(title)) = ?\";\n if (kind !== \"manager\") {\n sql += \" AND (kind = ? OR id = ?)\";\n params.push(kind, GENERAL_TOPIC_ID);\n }\n if (opts.excludeTopicId) {\n sql += \" AND id != ?\";\n params.push(opts.excludeTopicId);\n }\n sql += \" LIMIT 1\";\n const row = db.query<TopicRow, string[]>(sql).get(...params);\n return row ? rowToDto(row) : null;\n}\n\n/** Look up a topic by title, restricted to topics where `userId` participates. */\nexport function getTopicByNameForUser(title: string, userId: string): TopicDto | null {\n const trimmed = title.trim();\n const qualified = /^(agent|channel|manager):(.+)$/i.exec(trimmed);\n const requestedKind = qualified ? normalizeTopicKind(qualified[1]?.toLowerCase()) : null;\n const requestedTitle = qualified ? qualified[2]!.trim() : trimmed;\n const rows = db\n .query(\n `SELECT t.* FROM api_topics t\n WHERE LOWER(t.title) = LOWER(?)\n AND t.id != ?\n AND t.visibility != 'hidden'\n AND EXISTS (\n SELECT 1 FROM topic_members m WHERE m.topic_id = t.id AND m.user_id = ?\n )`,\n )\n .all(requestedTitle, GENERAL_TOPIC_ID, userId) as TopicRow[];\n const matches = requestedKind ? rows.filter((row) => row.kind === requestedKind) : rows;\n return matches.length === 1 ? rowToDto(matches[0]!) : null;\n}\n\n/** Persist the agent session ID for a topic after a successful turn. */\nexport function setTopicSessionId(\n topicId: string,\n sessionId: string,\n context: TopicSessionLogContext = {},\n): void {\n const previous = getTopicSessionId(topicId);\n const result = db\n .query(\"UPDATE api_topics SET session_id = ? WHERE id = ?\")\n .run(sessionId, topicId);\n const changes = Number(result.changes ?? 0);\n const logContext = {\n topicId,\n previousSessionId: shortSessionId(previous),\n sessionId: shortSessionId(sessionId),\n reason: context.reason,\n queryId: context.queryId,\n agent: context.agent,\n };\n if (changes === 0) {\n logger.warn(logContext, \"api-topic session_id update missed topic\");\n } else if (previous !== sessionId) {\n logger.debug(logContext, \"api-topic session_id updated\");\n } else {\n logger.debug(logContext, \"api-topic session_id unchanged\");\n }\n}\n\n/** Clear the session ID (e.g. on topic reset). */\nexport function clearTopicSessionId(topicId: string, reason = \"unspecified\"): void {\n const previous = getTopicSessionId(topicId);\n const result = db.query(\"UPDATE api_topics SET session_id = NULL WHERE id = ?\").run(topicId);\n const changes = Number(result.changes ?? 0);\n const logContext = { topicId, previousSessionId: shortSessionId(previous), reason };\n if (changes === 0) {\n logger.warn(logContext, \"api-topic session_id clear missed topic\");\n } else if (previous) {\n logger.info(logContext, \"api-topic session_id cleared\");\n } else {\n logger.debug(logContext, \"api-topic session_id already clear\");\n }\n}\n\n/** Read the persisted session ID for a topic, or null if none. */\nexport function getTopicSessionId(topicId: string): string | null {\n const r = db\n .query<{ session_id: string | null }, string>(\"SELECT session_id FROM api_topics WHERE id = ?\")\n .get(topicId);\n return r?.session_id ?? null;\n}\n\n/**\n * Hard-delete a topic row. Irreversible — the caller is responsible for also\n * deleting the topic's messages (see `deleteMessagesForTopic`). Returns false\n * if the topic didn't exist.\n */\nexport function deleteTopic(id: string, options: { allowManager?: boolean } = {}): boolean {\n const r = db\n .query<{ id: string; kind: string }, string>(\"SELECT id, kind FROM api_topics WHERE id = ?\")\n .get(id);\n if (!r) return false;\n if (id === GENERAL_TOPIC_ID || (r.kind === \"manager\" && !options.allowManager)) return false;\n db.query(\"DELETE FROM api_topics WHERE id = ?\").run(id);\n return true;\n}\n\n/**\n * Preserve a derived-topic chain when an intermediate parent is deleted.\n * Direct children move to the deleted topic's parent (or become roots) rather\n * than retaining a dangling parent id that would break memory-origin lookup.\n */\nexport function reparentTopicChildren(\n deletedTopicId: string,\n replacementParentTopicId: string | null,\n): string[] {\n const rows = db\n .query<{ id: string }, string>(\"SELECT id FROM api_topics WHERE parent_topic_id = ?\")\n .all(deletedTopicId);\n if (rows.length === 0) return [];\n db.query(\"UPDATE api_topics SET parent_topic_id = ? WHERE parent_topic_id = ?\").run(\n replacementParentTopicId,\n deletedTopicId,\n );\n return rows.map((row) => row.id);\n}\n\nexport function addParticipantToDB(\n topicId: string,\n userId: string,\n role: \"member\" | \"owner\",\n): boolean {\n if (!db.query(\"SELECT 1 FROM api_topics WHERE id = ?\").get(topicId)) return false;\n db.query(\"INSERT OR IGNORE INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)\").run(\n topicId,\n userId,\n role,\n );\n return true;\n}\n\nexport function removeParticipantFromDB(topicId: string, userId: string): boolean {\n if (!db.query(\"SELECT 1 FROM api_topics WHERE id = ?\").get(topicId)) return false;\n db.query(\"DELETE FROM topic_members WHERE topic_id = ? AND user_id = ?\").run(topicId, userId);\n return true;\n}\n"
7
+ ],
8
+ "mappings": "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",
9
+ "debugId": "3A5DC61522A52BE064756E2164756E21",
10
+ "names": []
11
+ }
package/dist/main.js CHANGED
@@ -1,24 +1,136 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- var __create = Object.create;
4
- var __getProtoOf = Object.getPrototypeOf;
5
- var __defProp = Object.defineProperty;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __toESM = (mod, isNodeMode, target) => {
9
- target = mod != null ? __create(__getProtoOf(mod)) : {};
10
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
- for (let key of __getOwnPropNames(mod))
12
- if (!__hasOwnProp.call(to, key))
13
- __defProp(to, key, {
14
- get: () => mod[key],
15
- enumerable: true
16
- });
17
- return to;
18
- };
19
- var __require = import.meta.require;
3
+ import {
4
+ loadOtiumCli,
5
+ loadTelegramCli,
6
+ loadTerminalCli
7
+ } from "./chunk-n17yxpa8.js";
8
+ import {
9
+ __require,
10
+ __toESM
11
+ } from "./chunk-hwy3n31w.js";
20
12
 
21
- // src/main.ts
22
- await import("@negotium/cli");
13
+ // ../cli/src/main.ts
14
+ var [, , command, ...args] = process.argv;
15
+ function numericOption(values, name, fallback) {
16
+ const prefix = `--${name}=`;
17
+ const parsed = Number.parseInt(values.find((value) => value.startsWith(prefix))?.slice(prefix.length) ?? "", 10);
18
+ return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
19
+ }
20
+ switch (command) {
21
+ case "init": {
22
+ const { initCommand } = await import("./chunk-jzkw2f01.js");
23
+ initCommand();
24
+ break;
25
+ }
26
+ case "chat": {
27
+ const { chatCommand } = await import("./chunk-jkqq5ef6.js");
28
+ await chatCommand(args);
29
+ break;
30
+ }
31
+ case "serve": {
32
+ const { startDefaultNode } = await import("./chunk-p3cr2ejg.js");
33
+ const node = await startDefaultNode({
34
+ port: numericOption(args, "port", 7777),
35
+ advertise: true,
36
+ singleton: true
37
+ });
38
+ console.log(`negotium node listening on 127.0.0.1:${node.port} (ctrl-c to stop)`);
39
+ await node.completed;
40
+ break;
41
+ }
42
+ case "__node-daemon": {
43
+ const { runNodeDaemon } = await import("./chunk-p3cr2ejg.js");
44
+ await runNodeDaemon({ port: numericOption(args, "port", 0) });
45
+ break;
46
+ }
47
+ case "status": {
48
+ const { inspectNodeDaemon } = await import("./chunk-p3cr2ejg.js");
49
+ const status = await inspectNodeDaemon();
50
+ if (status.running && status.info) {
51
+ console.log(`negotium node running (pid ${status.info.pid}, 127.0.0.1:${status.info.port}, since ${status.info.startedAt})`);
52
+ } else if (status.info) {
53
+ console.log(`negotium node not responding (last pid ${status.info.pid}${status.error ? `: ${status.error}` : ""})`);
54
+ process.exitCode = 1;
55
+ } else {
56
+ console.log("negotium node is stopped");
57
+ }
58
+ break;
59
+ }
60
+ case "stop": {
61
+ const { stopNodeDaemon } = await import("./chunk-p3cr2ejg.js");
62
+ const stopped = await stopNodeDaemon();
63
+ console.log(stopped ? "negotium node shutdown requested" : "negotium node is not running");
64
+ break;
65
+ }
66
+ case "topics": {
67
+ const { topicsCommand } = await import("./chunk-w13jjf4a.js");
68
+ topicsCommand();
69
+ break;
70
+ }
71
+ case "mcp": {
72
+ const { mcpCommand } = await import("./chunk-jzz91jwh.js");
73
+ mcpCommand(args);
74
+ break;
75
+ }
76
+ case "vault": {
77
+ const { vaultCommand } = await import("./chunk-gxma4c70.js");
78
+ vaultCommand(args);
79
+ break;
80
+ }
81
+ case "cron": {
82
+ const { cronCommand } = await import("./chunk-e4407g8x.js");
83
+ await cronCommand(args);
84
+ break;
85
+ }
86
+ case "terminal": {
87
+ const { runTerminalCli } = await loadTerminalCli();
88
+ await runTerminalCli(args);
89
+ break;
90
+ }
91
+ case "telegram": {
92
+ const { runTelegramCli } = await loadTelegramCli();
93
+ await runTelegramCli();
94
+ break;
95
+ }
96
+ case "otium": {
97
+ const { runOtiumCli } = await loadOtiumCli();
98
+ await runOtiumCli(args);
99
+ break;
100
+ }
101
+ case "start":
102
+ case "adapters": {
103
+ const { adaptersCommand } = await import("./chunk-hkdqhm34.js");
104
+ await adaptersCommand(args);
105
+ break;
106
+ }
107
+ default: {
108
+ console.log([
109
+ "negotium \u2014 turn this computer into an agent node",
110
+ "",
111
+ "usage: negotium <init|chat|serve|status|stop|topics|mcp|vault|cron|terminal|telegram|otium|start> [args]",
112
+ "",
113
+ " init bootstrap ~/.negotium and check agent auth",
114
+ " chat [topic] interactive chat (creates the topic if missing)",
115
+ " options: --agent=claude|codex|maestro",
116
+ " serve foreground long-lived node (default port 7777)",
117
+ " status show the local long-lived node status",
118
+ " stop stop the local long-lived node",
119
+ " topics list topics on this node",
120
+ " mcp list|add|remove|enable|disable manage node MCP manifest",
121
+ " vault list|set|get|del node secret store (encrypted at rest)",
122
+ " cron list|create|inspect|logs|run|pause|resume|restart|kill|reset|delete",
123
+ " terminal TUI client; auto-starts and connects to the local node",
124
+ " options: --embedded, --connect=http://host:port, --port=N",
125
+ " telegram Telegram adapter (configured from environment)",
126
+ " otium join|serve join an Otium workspace or serve its worker routes",
127
+ " start <terminal|telegram|otium> run one channel process",
128
+ " Terminal clients may run more than once and share one node"
129
+ ].join(`
130
+ `));
131
+ if (command && command !== "help" && command !== "--help")
132
+ process.exitCode = 1;
133
+ }
134
+ }
23
135
 
24
- //# debugId=3DC0E0AF6AED4BA564756E2164756E21
136
+ //# debugId=4434DAF8E767C91564756E2164756E21
package/dist/main.js.map CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/main.ts"],
3
+ "sources": ["../../cli/src/main.ts"],
4
4
  "sourcesContent": [
5
- "#!/usr/bin/env bun\n\n// Keep the unscoped package functional rather than using it as a name-only\n// placeholder. The canonical implementation lives in @negotium/cli.\nawait import(\"@negotium/cli\");\n\nexport {};\n"
5
+ "#!/usr/bin/env bun\n\nimport { loadOtiumCli, loadTelegramCli, loadTerminalCli } from \"@/adapter-loader\";\n\nconst [, , command, ...args] = process.argv;\n\nfunction numericOption(values: string[], name: string, fallback: number): number {\n const prefix = `--${name}=`;\n const parsed = Number.parseInt(\n values.find((value) => value.startsWith(prefix))?.slice(prefix.length) ?? \"\",\n 10,\n );\n return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;\n}\n\nswitch (command) {\n case \"init\": {\n const { initCommand } = await import(\"@/commands/init\");\n initCommand();\n break;\n }\n case \"chat\": {\n const { chatCommand } = await import(\"@/commands/chat\");\n await chatCommand(args);\n break;\n }\n case \"serve\": {\n const { startDefaultNode } = await import(\"@negotium/node\");\n const node = await startDefaultNode({\n port: numericOption(args, \"port\", 7777),\n advertise: true,\n singleton: true,\n });\n console.log(`negotium node listening on 127.0.0.1:${node.port} (ctrl-c to stop)`);\n await node.completed;\n break;\n }\n case \"__node-daemon\": {\n const { runNodeDaemon } = await import(\"@negotium/node\");\n await runNodeDaemon({ port: numericOption(args, \"port\", 0) });\n break;\n }\n case \"status\": {\n const { inspectNodeDaemon } = await import(\"@negotium/node\");\n const status = await inspectNodeDaemon();\n if (status.running && status.info) {\n console.log(\n `negotium node running (pid ${status.info.pid}, 127.0.0.1:${status.info.port}, since ${status.info.startedAt})`,\n );\n } else if (status.info) {\n console.log(\n `negotium node not responding (last pid ${status.info.pid}${status.error ? `: ${status.error}` : \"\"})`,\n );\n process.exitCode = 1;\n } else {\n console.log(\"negotium node is stopped\");\n }\n break;\n }\n case \"stop\": {\n const { stopNodeDaemon } = await import(\"@negotium/node\");\n const stopped = await stopNodeDaemon();\n console.log(stopped ? \"negotium node shutdown requested\" : \"negotium node is not running\");\n break;\n }\n case \"topics\": {\n const { topicsCommand } = await import(\"@/commands/topics\");\n topicsCommand();\n break;\n }\n case \"mcp\": {\n const { mcpCommand } = await import(\"@/commands/mcp\");\n mcpCommand(args);\n break;\n }\n case \"vault\": {\n const { vaultCommand } = await import(\"@/commands/vault\");\n vaultCommand(args);\n break;\n }\n case \"cron\": {\n const { cronCommand } = await import(\"@/commands/cron\");\n await cronCommand(args);\n break;\n }\n case \"terminal\": {\n const { runTerminalCli } = await loadTerminalCli();\n await runTerminalCli(args);\n break;\n }\n case \"telegram\": {\n const { runTelegramCli } = await loadTelegramCli();\n await runTelegramCli();\n break;\n }\n case \"otium\": {\n const { runOtiumCli } = await loadOtiumCli();\n await runOtiumCli(args);\n break;\n }\n case \"start\":\n case \"adapters\": {\n const { adaptersCommand } = await import(\"@/commands/adapters\");\n await adaptersCommand(args);\n break;\n }\n default: {\n console.log(\n [\n \"negotium — turn this computer into an agent node\",\n \"\",\n \"usage: negotium <init|chat|serve|status|stop|topics|mcp|vault|cron|terminal|telegram|otium|start> [args]\",\n \"\",\n \" init bootstrap ~/.negotium and check agent auth\",\n \" chat [topic] interactive chat (creates the topic if missing)\",\n \" options: --agent=claude|codex|maestro\",\n \" serve foreground long-lived node (default port 7777)\",\n \" status show the local long-lived node status\",\n \" stop stop the local long-lived node\",\n \" topics list topics on this node\",\n \" mcp list|add|remove|enable|disable manage node MCP manifest\",\n \" vault list|set|get|del node secret store (encrypted at rest)\",\n \" cron list|create|inspect|logs|run|pause|resume|restart|kill|reset|delete\",\n \" terminal TUI client; auto-starts and connects to the local node\",\n \" options: --embedded, --connect=http://host:port, --port=N\",\n \" telegram Telegram adapter (configured from environment)\",\n \" otium join|serve join an Otium workspace or serve its worker routes\",\n \" start <terminal|telegram|otium> run one channel process\",\n \" Terminal clients may run more than once and share one node\",\n ].join(\"\\n\"),\n );\n if (command && command !== \"help\" && command !== \"--help\") process.exitCode = 1;\n }\n}\n"
6
6
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;;;;;AAIA,MAAa;",
8
- "debugId": "3DC0E0AF6AED4BA564756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "4434DAF8E767C91564756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env python3
2
+ """faster-whisper wrapper used by Otium media extraction.
3
+
4
+ Usage:
5
+ faster-whisper-wrapper.py INPUT --model MODEL --language LANG --output-dir DIR --output-format txt
6
+
7
+ Arguments are the subset used by the TypeScript media extractor:
8
+ --model faster-whisper model size (e.g. medium, large-v3, turbo)
9
+ --language ISO language code (e.g. ko)
10
+ --output-dir Directory to write output files to
11
+ --output-format Output format (txt supported)
12
+
13
+ The script transcribes INPUT and writes {basename}.txt to --output-dir.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import os
20
+ import sys
21
+
22
+
23
+ def parse_args() -> argparse.Namespace:
24
+ parser = argparse.ArgumentParser()
25
+ parser.add_argument("input", help="Audio file path")
26
+ parser.add_argument("--model", default="medium", help="Whisper model size")
27
+ parser.add_argument("--language", default=None, help="Language code (e.g. ko)")
28
+ parser.add_argument("--output-dir", default=".", help="Output directory")
29
+ parser.add_argument("--output-format", default="txt", help="Output format")
30
+ return parser.parse_args()
31
+
32
+
33
+ def main() -> None:
34
+ args = parse_args()
35
+
36
+ if args.output_format != "txt":
37
+ print(f"Unsupported output format: {args.output_format}", file=sys.stderr)
38
+ sys.exit(1)
39
+
40
+ try:
41
+ from faster_whisper import WhisperModel
42
+ except ImportError:
43
+ print(
44
+ "faster-whisper not installed. Run: uv pip install faster-whisper",
45
+ file=sys.stderr,
46
+ )
47
+ sys.exit(1)
48
+
49
+ compute_type = "int8" # CPU-friendly
50
+ model_size = args.model
51
+ # Normalize common model aliases to faster-whisper size identifiers.
52
+ if "turbo" in model_size:
53
+ model_size = "turbo"
54
+ elif "large-v3" in model_size:
55
+ model_size = "large-v3"
56
+ elif "large-v2" in model_size:
57
+ model_size = "large-v2"
58
+ elif "medium" in model_size:
59
+ model_size = "medium"
60
+ elif "small" in model_size:
61
+ model_size = "small"
62
+ elif "tiny" in model_size:
63
+ model_size = "tiny"
64
+
65
+ os.makedirs(args.output_dir, exist_ok=True)
66
+
67
+ model = WhisperModel(model_size, device="cpu", compute_type=compute_type)
68
+
69
+ segments, info = model.transcribe(
70
+ args.input,
71
+ language=args.language,
72
+ beam_size=5,
73
+ vad_filter=True,
74
+ )
75
+
76
+ base = os.path.splitext(os.path.basename(args.input))[0]
77
+ out_path = os.path.join(args.output_dir, f"{base}.txt")
78
+
79
+ with open(out_path, "w", encoding="utf-8") as f:
80
+ for segment in segments:
81
+ f.write(segment.text.strip() + "\n")
82
+
83
+ print(f"Transcription written to {out_path}", file=sys.stderr)
84
+
85
+
86
+ if __name__ == "__main__":
87
+ main()