negotium 0.1.7 → 0.1.9

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 (270) hide show
  1. package/README.md +26 -2
  2. package/dist/canonical-mcp-bridge.js +14 -0
  3. package/dist/canonical-mcp-bridge.js.map +9 -0
  4. package/dist/chunk-10ct8814.js +429 -0
  5. package/dist/chunk-10ct8814.js.map +11 -0
  6. package/dist/chunk-18fzftjf.js +214 -0
  7. package/dist/chunk-18fzftjf.js.map +10 -0
  8. package/dist/chunk-1k7t7bnx.js +97 -0
  9. package/dist/chunk-1k7t7bnx.js.map +10 -0
  10. package/dist/chunk-1msje2zf.js +23 -0
  11. package/dist/chunk-1msje2zf.js.map +10 -0
  12. package/dist/chunk-2dgkah1z.js +706 -0
  13. package/dist/chunk-2dgkah1z.js.map +14 -0
  14. package/dist/chunk-3t4dtm26.js +61 -0
  15. package/dist/chunk-3t4dtm26.js.map +10 -0
  16. package/dist/chunk-3vn2vnj1.js +51 -0
  17. package/dist/chunk-3vn2vnj1.js.map +10 -0
  18. package/dist/chunk-6hknphtc.js +2054 -0
  19. package/dist/chunk-6hknphtc.js.map +29 -0
  20. package/dist/chunk-6tfzpcvk.js +2149 -0
  21. package/dist/chunk-6tfzpcvk.js.map +15 -0
  22. package/dist/chunk-8g0c4as0.js +23 -0
  23. package/dist/chunk-8g0c4as0.js.map +10 -0
  24. package/dist/chunk-9spx3h9n.js +279 -0
  25. package/dist/chunk-9spx3h9n.js.map +10 -0
  26. package/dist/chunk-c500xe9z.js +33 -0
  27. package/dist/chunk-c500xe9z.js.map +10 -0
  28. package/dist/chunk-e9b2xpff.js +147 -0
  29. package/dist/chunk-e9b2xpff.js.map +10 -0
  30. package/dist/chunk-edb07sjp.js +567 -0
  31. package/dist/chunk-edb07sjp.js.map +14 -0
  32. package/dist/chunk-eexqsee7.js +368 -0
  33. package/dist/chunk-eexqsee7.js.map +11 -0
  34. package/dist/chunk-efyxk97v.js +500 -0
  35. package/dist/chunk-efyxk97v.js.map +13 -0
  36. package/dist/chunk-epqxwkvh.js +3534 -0
  37. package/dist/chunk-epqxwkvh.js.map +20 -0
  38. package/dist/chunk-f1pzh3xh.js +115 -0
  39. package/dist/chunk-f1pzh3xh.js.map +10 -0
  40. package/dist/chunk-f5xgprg1.js +59 -0
  41. package/dist/chunk-f5xgprg1.js.map +10 -0
  42. package/dist/chunk-f97qybwk.js +584 -0
  43. package/dist/chunk-f97qybwk.js.map +11 -0
  44. package/dist/chunk-fvc7ncxd.js +252 -0
  45. package/dist/chunk-fvc7ncxd.js.map +10 -0
  46. package/dist/chunk-g7pvfj5e.js +574 -0
  47. package/dist/chunk-g7pvfj5e.js.map +14 -0
  48. package/dist/chunk-h8vmb9gf.js +315 -0
  49. package/dist/chunk-h8vmb9gf.js.map +10 -0
  50. package/dist/chunk-hgw7n8cm.js +43 -0
  51. package/dist/chunk-hgw7n8cm.js.map +10 -0
  52. package/dist/chunk-hsq7j9jk.js +126 -0
  53. package/dist/chunk-hsq7j9jk.js.map +10 -0
  54. package/dist/chunk-hwy3n31w.js +21 -0
  55. package/dist/chunk-hwy3n31w.js.map +9 -0
  56. package/dist/chunk-j0kjscb7.js +53 -0
  57. package/dist/chunk-j0kjscb7.js.map +10 -0
  58. package/dist/chunk-jzz91jwh.js +70 -0
  59. package/dist/chunk-jzz91jwh.js.map +10 -0
  60. package/dist/chunk-k9z47mab.js +247 -0
  61. package/dist/chunk-k9z47mab.js.map +12 -0
  62. package/dist/chunk-kbm35t7w.js +1331 -0
  63. package/dist/chunk-kbm35t7w.js.map +16 -0
  64. package/dist/chunk-m4veth0c.js +1649 -0
  65. package/dist/chunk-m4veth0c.js.map +17 -0
  66. package/dist/chunk-mhty3nna.js +150 -0
  67. package/dist/chunk-mhty3nna.js.map +11 -0
  68. package/dist/chunk-mmgh670x.js +248 -0
  69. package/dist/chunk-mmgh670x.js.map +11 -0
  70. package/dist/chunk-n0kk5xbq.js +2358 -0
  71. package/dist/chunk-n0kk5xbq.js.map +26 -0
  72. package/dist/chunk-nmv41qx9.js +2847 -0
  73. package/dist/chunk-nmv41qx9.js.map +23 -0
  74. package/dist/chunk-nqdp52aj.js +113 -0
  75. package/dist/chunk-nqdp52aj.js.map +12 -0
  76. package/dist/chunk-ns2m25dz.js +139 -0
  77. package/dist/chunk-ns2m25dz.js.map +10 -0
  78. package/dist/chunk-pkdmypdd.js +147 -0
  79. package/dist/chunk-pkdmypdd.js.map +11 -0
  80. package/dist/chunk-rmyfmrrr.js +1634 -0
  81. package/dist/chunk-rmyfmrrr.js.map +15 -0
  82. package/dist/chunk-sa874j5c.js +9 -0
  83. package/dist/chunk-sa874j5c.js.map +10 -0
  84. package/dist/chunk-sgsz2fpq.js +289 -0
  85. package/dist/chunk-sgsz2fpq.js.map +10 -0
  86. package/dist/chunk-sn51wnf1.js +3020 -0
  87. package/dist/chunk-sn51wnf1.js.map +32 -0
  88. package/dist/chunk-sqhr1a7e.js +587 -0
  89. package/dist/chunk-sqhr1a7e.js.map +11 -0
  90. package/dist/chunk-tmj5r2zv.js +2933 -0
  91. package/dist/chunk-tmj5r2zv.js.map +20 -0
  92. package/dist/chunk-vefj3880.js +56 -0
  93. package/dist/chunk-vefj3880.js.map +10 -0
  94. package/dist/chunk-vp2db42d.js +121 -0
  95. package/dist/chunk-vp2db42d.js.map +10 -0
  96. package/dist/chunk-wry79q55.js +108 -0
  97. package/dist/chunk-wry79q55.js.map +10 -0
  98. package/dist/chunk-x1arr7ze.js +63 -0
  99. package/dist/chunk-x1arr7ze.js.map +10 -0
  100. package/dist/chunk-z2xy5n2q.js +103 -0
  101. package/dist/chunk-z2xy5n2q.js.map +10 -0
  102. package/dist/hosted-agent.js +53 -0
  103. package/dist/hosted-agent.js.map +10 -0
  104. package/dist/main.js +132 -20
  105. package/dist/main.js.map +4 -4
  106. package/dist/runtime/scripts/faster-whisper-wrapper.py +87 -0
  107. package/dist/runtime/scripts/mcp-patchright-http.mjs +349 -0
  108. package/dist/runtime/src/agents/api-topic-agent-switch.ts +178 -0
  109. package/dist/runtime/src/agents/archiver.ts +396 -0
  110. package/dist/runtime/src/agents/auth-check.ts +64 -0
  111. package/dist/runtime/src/agents/claude-provider.ts +737 -0
  112. package/dist/runtime/src/agents/claude-registry.ts +115 -0
  113. package/dist/runtime/src/agents/codex-native-multi-agent.ts +177 -0
  114. package/dist/runtime/src/agents/codex-provider.ts +785 -0
  115. package/dist/runtime/src/agents/codex-registry.ts +115 -0
  116. package/dist/runtime/src/agents/codex-tree-kill.ts +365 -0
  117. package/dist/runtime/src/agents/contracts.ts +69 -0
  118. package/dist/runtime/src/agents/deep-map.ts +13 -0
  119. package/dist/runtime/src/agents/execution-host.ts +91 -0
  120. package/dist/runtime/src/agents/fixtures/claude-attachments.jsonl +2 -0
  121. package/dist/runtime/src/agents/fixtures/codex-shell.jsonl +5 -0
  122. package/dist/runtime/src/agents/fork.ts +65 -0
  123. package/dist/runtime/src/agents/hosted-agent.ts +49 -0
  124. package/dist/runtime/src/agents/idle-archiver.ts +128 -0
  125. package/dist/runtime/src/agents/index.ts +205 -0
  126. package/dist/runtime/src/agents/maestro-provider.ts +206 -0
  127. package/dist/runtime/src/agents/maestro-registry.ts +31 -0
  128. package/dist/runtime/src/agents/mcp-tools/ask-user.ts +280 -0
  129. package/dist/runtime/src/agents/mcp-tools/common.ts +21 -0
  130. package/dist/runtime/src/agents/mcp-tools/self-config.ts +220 -0
  131. package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +525 -0
  132. package/dist/runtime/src/agents/mcp-tools/visuals.ts +88 -0
  133. package/dist/runtime/src/agents/model-catalog.ts +151 -0
  134. package/dist/runtime/src/agents/registry.ts +24 -0
  135. package/dist/runtime/src/agents/rollout/claude.ts +331 -0
  136. package/dist/runtime/src/agents/rollout/codex.ts +569 -0
  137. package/dist/runtime/src/agents/rollout/shared.ts +205 -0
  138. package/dist/runtime/src/agents/self-config-core.ts +415 -0
  139. package/dist/runtime/src/agents/task-events.ts +44 -0
  140. package/dist/runtime/src/agents/tool-format.ts +352 -0
  141. package/dist/runtime/src/agents/topic-agent-switch.ts +193 -0
  142. package/dist/runtime/src/agents/topic-cleanup.ts +231 -0
  143. package/dist/runtime/src/agents/vault-tool-policy.ts +33 -0
  144. package/dist/runtime/src/application/execute-external-user-turn.ts +23 -0
  145. package/dist/runtime/src/application/submit-user-message.ts +50 -0
  146. package/dist/runtime/src/application/switch-topic-effort.ts +48 -0
  147. package/dist/runtime/src/application/switch-topic-model.ts +79 -0
  148. package/dist/runtime/src/application/topic-service.ts +139 -0
  149. package/dist/runtime/src/application/vault-command.ts +170 -0
  150. package/dist/runtime/src/bus.ts +319 -0
  151. package/dist/runtime/src/index.ts +333 -0
  152. package/dist/runtime/src/mcp/agent-health-server.ts +309 -0
  153. package/dist/runtime/src/mcp/background-bash-server.ts +346 -0
  154. package/dist/runtime/src/mcp/canonical-bridge-config.ts +84 -0
  155. package/dist/runtime/src/mcp/canonical-proxy-server.ts +171 -0
  156. package/dist/runtime/src/mcp/dm-ipc.ts +60 -0
  157. package/dist/runtime/src/mcp/mcp-helpers.ts +40 -0
  158. package/dist/runtime/src/mcp/paddleocr-server.ts +105 -0
  159. package/dist/runtime/src/mcp/peer-bridge.ts +114 -0
  160. package/dist/runtime/src/mcp/runtime-spec.ts +137 -0
  161. package/dist/runtime/src/mcp/session-comm/bridge-ipc-config.ts +27 -0
  162. package/dist/runtime/src/mcp/session-comm/peer-forward.ts +165 -0
  163. package/dist/runtime/src/mcp/session-comm/runtime.ts +115 -0
  164. package/dist/runtime/src/mcp/session-comm/server.ts +649 -0
  165. package/dist/runtime/src/mcp/session-comm/topic-config.ts +131 -0
  166. package/dist/runtime/src/mcp/session-comm/topics.ts +229 -0
  167. package/dist/runtime/src/mcp/stdio-protect.ts +84 -0
  168. package/dist/runtime/src/mcp/system-health-server.ts +130 -0
  169. package/dist/runtime/src/mcp/task-server.ts +173 -0
  170. package/dist/runtime/src/mcp/token-stats-server.ts +98 -0
  171. package/dist/runtime/src/mcp/vault-http.ts +219 -0
  172. package/dist/runtime/src/mcp/vault-run.ts +153 -0
  173. package/dist/runtime/src/mcp/vault-server.ts +104 -0
  174. package/dist/runtime/src/mcp/wiki-server.ts +755 -0
  175. package/dist/runtime/src/media/file-events.ts +33 -0
  176. package/dist/runtime/src/media/text-extractor.ts +312 -0
  177. package/dist/runtime/src/media/video.ts +92 -0
  178. package/dist/runtime/src/outbox/file-ops.ts +210 -0
  179. package/dist/runtime/src/outbox/index.ts +8 -0
  180. package/dist/runtime/src/outbox/utils.ts +51 -0
  181. package/dist/runtime/src/platform/background-bash/manager.ts +279 -0
  182. package/dist/runtime/src/platform/config.ts +371 -0
  183. package/dist/runtime/src/platform/constants.ts +18 -0
  184. package/dist/runtime/src/platform/debug-users.ts +14 -0
  185. package/dist/runtime/src/platform/delay.ts +1 -0
  186. package/dist/runtime/src/platform/error.ts +9 -0
  187. package/dist/runtime/src/platform/file-utils.ts +16 -0
  188. package/dist/runtime/src/platform/jsonl.ts +202 -0
  189. package/dist/runtime/src/platform/lifecycle.ts +174 -0
  190. package/dist/runtime/src/platform/logger.ts +31 -0
  191. package/dist/runtime/src/platform/maestro-bootstrap-env.ts +3 -0
  192. package/dist/runtime/src/platform/mcp-config.ts +878 -0
  193. package/dist/runtime/src/platform/modules.ts +145 -0
  194. package/dist/runtime/src/platform/node-plugins.ts +46 -0
  195. package/dist/runtime/src/platform/playwright/manager.ts +1233 -0
  196. package/dist/runtime/src/prompts/agents/wiki-archiver.md +261 -0
  197. package/dist/runtime/src/prompts/builders.ts +368 -0
  198. package/dist/runtime/src/prompts/sessions/channel-system.md +48 -0
  199. package/dist/runtime/src/prompts/sessions/manager-system.md +37 -0
  200. package/dist/runtime/src/prompts/sessions/topic-system.md +47 -0
  201. package/dist/runtime/src/prompts/sessions/visual-design.md +86 -0
  202. package/dist/runtime/src/query/active-rooms.ts +534 -0
  203. package/dist/runtime/src/query/control.ts +313 -0
  204. package/dist/runtime/src/query/index.ts +24 -0
  205. package/dist/runtime/src/query/session-inbox-cleanup.ts +44 -0
  206. package/dist/runtime/src/query/session-inbox-path.ts +42 -0
  207. package/dist/runtime/src/query/state.ts +94 -0
  208. package/dist/runtime/src/query/types.ts +83 -0
  209. package/dist/runtime/src/runtime/ask-callbacks.ts +153 -0
  210. package/dist/runtime/src/runtime/attachments.ts +151 -0
  211. package/dist/runtime/src/runtime/background-sessions.ts +83 -0
  212. package/dist/runtime/src/runtime/channel-context.ts +107 -0
  213. package/dist/runtime/src/runtime/errors.ts +69 -0
  214. package/dist/runtime/src/runtime/event-heartbeat.ts +77 -0
  215. package/dist/runtime/src/runtime/file-hooks.ts +81 -0
  216. package/dist/runtime/src/runtime/footer.ts +23 -0
  217. package/dist/runtime/src/runtime/inbox.ts +946 -0
  218. package/dist/runtime/src/runtime/tasks.ts +56 -0
  219. package/dist/runtime/src/runtime/topic-config.ts +29 -0
  220. package/dist/runtime/src/runtime/turn-runner.ts +2298 -0
  221. package/dist/runtime/src/runtime/usage-alert.ts +84 -0
  222. package/dist/runtime/src/runtime/visual-html.ts +109 -0
  223. package/dist/runtime/src/runtime/visual-store.ts +430 -0
  224. package/dist/runtime/src/runtime/visuals.ts +171 -0
  225. package/dist/runtime/src/security/sanitize.ts +27 -0
  226. package/dist/runtime/src/security/sensitive-path.ts +34 -0
  227. package/dist/runtime/src/storage/activity-log.ts +152 -0
  228. package/dist/runtime/src/storage/api-messages.ts +617 -0
  229. package/dist/runtime/src/storage/api-topic-brief.ts +127 -0
  230. package/dist/runtime/src/storage/api-topic-config.ts +192 -0
  231. package/dist/runtime/src/storage/api-topics.ts +844 -0
  232. package/dist/runtime/src/storage/app-settings.ts +44 -0
  233. package/dist/runtime/src/storage/browser-profiles.ts +133 -0
  234. package/dist/runtime/src/storage/conversations.ts +268 -0
  235. package/dist/runtime/src/storage/forum/agent-settings.ts +20 -0
  236. package/dist/runtime/src/storage/forum/index.ts +4 -0
  237. package/dist/runtime/src/storage/forum/repository.ts +283 -0
  238. package/dist/runtime/src/storage/forum/schema.ts +300 -0
  239. package/dist/runtime/src/storage/forum-db.ts +23 -0
  240. package/dist/runtime/src/storage/runtime-events.ts +130 -0
  241. package/dist/runtime/src/storage/runtime-leases.ts +189 -0
  242. package/dist/runtime/src/storage/runtime-process-leases.ts +216 -0
  243. package/dist/runtime/src/storage/runtime-topic-state.ts +148 -0
  244. package/dist/runtime/src/storage/runtime-turn-requests.ts +301 -0
  245. package/dist/runtime/src/storage/self-schedules.ts +298 -0
  246. package/dist/runtime/src/storage/session-asks.ts +347 -0
  247. package/dist/runtime/src/storage/sqlite.ts +129 -0
  248. package/dist/runtime/src/storage/tasks.ts +179 -0
  249. package/dist/runtime/src/storage/token-stats.ts +144 -0
  250. package/dist/runtime/src/storage/topic-archive-state.ts +59 -0
  251. package/dist/runtime/src/storage/topic-archive.ts +74 -0
  252. package/dist/runtime/src/storage/topic-settings.ts +67 -0
  253. package/dist/runtime/src/storage/topic-transcript.ts +98 -0
  254. package/dist/runtime/src/storage/vault-crypto.ts +65 -0
  255. package/dist/runtime/src/storage/vault.ts +225 -0
  256. package/dist/runtime/src/storage/wiki-summary-names.ts +33 -0
  257. package/dist/runtime/src/storage/wiki.ts +85 -0
  258. package/dist/runtime/src/topics/create.ts +123 -0
  259. package/dist/runtime/src/topics/derive.ts +398 -0
  260. package/dist/runtime/src/topics/lifecycle.ts +311 -0
  261. package/dist/runtime/src/topics/links.ts +7 -0
  262. package/dist/runtime/src/topics/personal-general.ts +65 -0
  263. package/dist/runtime/src/topics/session.ts +381 -0
  264. package/dist/runtime/src/types/api.ts +497 -0
  265. package/dist/runtime/src/types.ts +257 -0
  266. package/dist/runtime/src/version.ts +1 -0
  267. package/dist/runtime/tsconfig.json +13 -0
  268. package/package.json +28 -3
  269. package/dist/main.d.ts +0 -3
  270. package/dist/main.d.ts.map +0 -1
@@ -0,0 +1,127 @@
1
+ // Persistent per-topic wiki brief store, backed by shared SQLite.
2
+ // Topic briefs are keyed by topicId and shared across participants.
3
+ //
4
+ // Briefs are injected into AI-invited topic turns at session start. The
5
+ // wiki-archiver updates them post-session.
6
+ import { db } from "#storage/forum-db";
7
+
8
+ db.exec(`
9
+ CREATE TABLE IF NOT EXISTS api_topic_brief (
10
+ topic_id TEXT PRIMARY KEY,
11
+ brief_md TEXT NOT NULL DEFAULT '',
12
+ latest_summary_md TEXT,
13
+ summary_date TEXT,
14
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
15
+ )
16
+ `);
17
+
18
+ export interface TopicBrief {
19
+ topicId: string;
20
+ /** Lightweight topic brief (300-600 tokens, injected at session start). */
21
+ briefMd: string;
22
+ /** Latest session summary markdown (full source-summary). */
23
+ latestSummaryMd?: string;
24
+ /** Date of the latest summary (YYYY-MM-DD). */
25
+ summaryDate?: string;
26
+ /** ISO-8601 timestamp of last update. */
27
+ updatedAt: string;
28
+ }
29
+
30
+ interface BriefRow {
31
+ topic_id: string;
32
+ brief_md: string;
33
+ latest_summary_md: string | null;
34
+ summary_date: string | null;
35
+ updated_at: string;
36
+ }
37
+
38
+ function rowToBrief(r: BriefRow): TopicBrief {
39
+ return {
40
+ topicId: r.topic_id,
41
+ briefMd: r.brief_md,
42
+ latestSummaryMd: r.latest_summary_md ?? undefined,
43
+ summaryDate: r.summary_date ?? undefined,
44
+ updatedAt: r.updated_at,
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Get the topic brief for a given topicId. Returns null when no brief
50
+ * has been written yet (fresh topic, no archiver run).
51
+ */
52
+ export function getTopicBrief(topicId: string): TopicBrief | null {
53
+ const row = db
54
+ .query(
55
+ "SELECT topic_id, brief_md, latest_summary_md, summary_date, updated_at FROM api_topic_brief WHERE topic_id = ?",
56
+ )
57
+ .get(topicId) as BriefRow | null;
58
+ if (!row) return null;
59
+ return rowToBrief(row);
60
+ }
61
+
62
+ /** Resolve current id-keyed briefs while retaining legacy title-keyed wiki memory. */
63
+ export function resolveTopicBrief(
64
+ topicId: string,
65
+ legacyTitle: string,
66
+ ): { brief: TopicBrief; storageKey: string } | null {
67
+ const current = getTopicBrief(topicId);
68
+ if (current) return { brief: current, storageKey: topicId };
69
+ const legacy = getTopicBrief(legacyTitle);
70
+ return legacy ? { brief: legacy, storageKey: legacyTitle } : null;
71
+ }
72
+
73
+ /**
74
+ * Upsert the topic brief. `briefMd` and `latestSummaryMd` are
75
+ * independently optional — callers can update just one field without
76
+ * wiping the other. `summaryDate` should be the date of the summary
77
+ * (YYYY-MM-DD), set alongside `latestSummaryMd`.
78
+ */
79
+ export function setTopicBrief(
80
+ topicId: string,
81
+ fields: {
82
+ briefMd?: string;
83
+ latestSummaryMd?: string;
84
+ summaryDate?: string;
85
+ },
86
+ ): TopicBrief {
87
+ const existing = getTopicBrief(topicId);
88
+
89
+ const briefMd = fields.briefMd !== undefined ? fields.briefMd : (existing?.briefMd ?? "");
90
+ const latestSummaryMd =
91
+ fields.latestSummaryMd !== undefined
92
+ ? fields.latestSummaryMd
93
+ : (existing?.latestSummaryMd ?? null);
94
+ const summaryDate =
95
+ fields.summaryDate !== undefined ? fields.summaryDate : (existing?.summaryDate ?? null);
96
+
97
+ db.query(
98
+ `INSERT INTO api_topic_brief
99
+ (topic_id, brief_md, latest_summary_md, summary_date, updated_at)
100
+ VALUES (?, ?, ?, ?, datetime('now'))
101
+ ON CONFLICT(topic_id) DO UPDATE SET
102
+ brief_md = excluded.brief_md,
103
+ latest_summary_md = excluded.latest_summary_md,
104
+ summary_date = excluded.summary_date,
105
+ updated_at = excluded.updated_at`,
106
+ ).run(topicId, briefMd, latestSummaryMd, summaryDate);
107
+
108
+ return getTopicBrief(topicId)!;
109
+ }
110
+
111
+ /** Remove a topic's brief row entirely (e.g. on topic hard-delete). */
112
+ export function deleteTopicBrief(topicId: string): void {
113
+ db.query("DELETE FROM api_topic_brief WHERE topic_id = ?").run(topicId);
114
+ }
115
+
116
+ /**
117
+ * List all topic briefs for browsing (wiki UI). Returns briefMd
118
+ * only (no full summary body) to keep the payload light.
119
+ */
120
+ export function listTopicBriefs(): TopicBrief[] {
121
+ const rows = db
122
+ .query(
123
+ "SELECT topic_id, brief_md, latest_summary_md, summary_date, updated_at FROM api_topic_brief ORDER BY updated_at DESC",
124
+ )
125
+ .all() as BriefRow[];
126
+ return rows.map(rowToBrief);
127
+ }
@@ -0,0 +1,192 @@
1
+ // Persistent per-topic model/effort/MCP overrides and user locks.
2
+ // SQLite. Mirrors api-topics.ts. This replaces the previous in-memory Map in
3
+ // routes/topic-config.ts, which lost every override on server restart — a
4
+ // regression now that topic config is the canonical path for model/effort
5
+ // switching (the MCP-based direction), not just a transient convenience.
6
+ import { db } from "#storage/forum-db";
7
+ import type { EffortLevel } from "#types";
8
+
9
+ function tableColumns(table: string): Set<string> {
10
+ const rows = db.query(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
11
+ return new Set(rows.map((row) => row.name));
12
+ }
13
+
14
+ function createCanonicalConfigTable(name = "api_topic_config"): void {
15
+ db.exec(`
16
+ CREATE TABLE IF NOT EXISTS ${name} (
17
+ topic_id TEXT PRIMARY KEY REFERENCES api_topics(id) ON DELETE CASCADE,
18
+ model TEXT,
19
+ effort TEXT CHECK (effort IS NULL OR effort IN ('low','medium','high','xhigh','max')),
20
+ mcp TEXT,
21
+ agent_locked INTEGER NOT NULL DEFAULT 0 CHECK (agent_locked IN (0,1)),
22
+ model_locked INTEGER NOT NULL DEFAULT 0 CHECK (model_locked IN (0,1)),
23
+ effort_locked INTEGER NOT NULL DEFAULT 0 CHECK (effort_locked IN (0,1))
24
+ )
25
+ `);
26
+ }
27
+
28
+ const existingConfigColumns = tableColumns("api_topic_config");
29
+ if (existingConfigColumns.size === 0) {
30
+ createCanonicalConfigTable();
31
+ } else if (
32
+ existingConfigColumns.has("agent") ||
33
+ existingConfigColumns.has("agent_pinned") ||
34
+ existingConfigColumns.has("runtime_locked") ||
35
+ existingConfigColumns.has("model_pinned") ||
36
+ existingConfigColumns.has("effort_pinned") ||
37
+ existingConfigColumns.has("last_shown_agent")
38
+ ) {
39
+ // Complete the old agent-override migration before dropping the column. The
40
+ // selected agent itself belongs to api_topics; only its user lock belongs here.
41
+ const topicColumns = tableColumns("api_topics");
42
+ const agentColumn = topicColumns.has("agent")
43
+ ? "agent"
44
+ : topicColumns.has("runtime_agent")
45
+ ? "runtime_agent"
46
+ : null;
47
+ if (agentColumn && existingConfigColumns.has("agent")) {
48
+ db.exec(
49
+ `UPDATE api_topics
50
+ SET ${agentColumn} = (
51
+ SELECT c.agent FROM api_topic_config c WHERE c.topic_id = api_topics.id
52
+ )
53
+ WHERE ${agentColumn} IS NULL
54
+ AND EXISTS (
55
+ SELECT 1 FROM api_topic_config c
56
+ WHERE c.topic_id = api_topics.id AND c.agent IS NOT NULL
57
+ )`,
58
+ );
59
+ }
60
+
61
+ const previousForeignKeys = db
62
+ .query<{ foreign_keys: number }, []>("PRAGMA foreign_keys")
63
+ .get()?.foreign_keys;
64
+ db.exec("PRAGMA foreign_keys = OFF");
65
+ try {
66
+ const agentLockSource = existingConfigColumns.has("runtime_locked")
67
+ ? "runtime_locked"
68
+ : existingConfigColumns.has("agent_pinned")
69
+ ? "agent_pinned"
70
+ : "0";
71
+ const modelLockSource = existingConfigColumns.has("model_locked")
72
+ ? "model_locked"
73
+ : existingConfigColumns.has("model_pinned")
74
+ ? "model_pinned"
75
+ : "0";
76
+ const effortLockSource = existingConfigColumns.has("effort_locked")
77
+ ? "effort_locked"
78
+ : existingConfigColumns.has("effort_pinned")
79
+ ? "effort_pinned"
80
+ : "0";
81
+ db.transaction(() => {
82
+ createCanonicalConfigTable("api_topic_config_next");
83
+ db.exec(`
84
+ INSERT INTO api_topic_config_next
85
+ (topic_id, model, effort, mcp, agent_locked, model_locked, effort_locked)
86
+ SELECT
87
+ topic_id,
88
+ model,
89
+ effort,
90
+ mcp,
91
+ COALESCE(${agentLockSource}, 0),
92
+ COALESCE(${modelLockSource}, 0),
93
+ COALESCE(${effortLockSource}, 0)
94
+ FROM api_topic_config
95
+ WHERE EXISTS (SELECT 1 FROM api_topics t WHERE t.id = api_topic_config.topic_id)
96
+ `);
97
+ db.exec("DROP TABLE api_topic_config");
98
+ db.exec("ALTER TABLE api_topic_config_next RENAME TO api_topic_config");
99
+ })();
100
+ } finally {
101
+ db.exec(`PRAGMA foreign_keys = ${previousForeignKeys ? "ON" : "OFF"}`);
102
+ }
103
+ } else {
104
+ createCanonicalConfigTable();
105
+ }
106
+
107
+ export interface TopicConfig {
108
+ model?: string;
109
+ effort?: EffortLevel;
110
+ mcp?: string[]; // enabled optional MCP server names
111
+ // Per-field user locks: when true, AI self-config cannot override the field.
112
+ agentLocked?: boolean;
113
+ modelLocked?: boolean;
114
+ effortLocked?: boolean;
115
+ }
116
+
117
+ interface ConfigRow {
118
+ topic_id: string;
119
+ model: string | null;
120
+ effort: string | null;
121
+ mcp: string | null;
122
+ agent_locked: number;
123
+ model_locked: number;
124
+ effort_locked: number;
125
+ }
126
+
127
+ function rowToConfig(r: ConfigRow): TopicConfig {
128
+ const cfg: TopicConfig = {};
129
+ if (r.model) cfg.model = r.model;
130
+ if (r.effort) cfg.effort = r.effort as EffortLevel;
131
+ if (r.mcp) {
132
+ try {
133
+ const parsed = JSON.parse(r.mcp);
134
+ if (Array.isArray(parsed)) cfg.mcp = parsed as string[];
135
+ } catch {
136
+ // corrupt JSON — treat as no mcp override
137
+ }
138
+ }
139
+ if (r.agent_locked) cfg.agentLocked = true;
140
+ if (r.model_locked) cfg.modelLocked = true;
141
+ if (r.effort_locked) cfg.effortLocked = true;
142
+ return cfg;
143
+ }
144
+
145
+ /**
146
+ * Per-topic override set via `PATCH /config` (and, going forward, the MCP
147
+ * self-config tools). Returns `undefined` when the topic has no stored row,
148
+ * so callers can distinguish "no override" from "override with empty fields".
149
+ */
150
+ export function getApiTopicConfig(topicId: string): TopicConfig | undefined {
151
+ const row = db
152
+ .query(
153
+ "SELECT topic_id, model, effort, mcp, agent_locked, model_locked, effort_locked FROM api_topic_config WHERE topic_id = ?",
154
+ )
155
+ .get(topicId) as ConfigRow | null;
156
+ if (!row) return undefined;
157
+ const config = rowToConfig(row);
158
+ return Object.keys(config).length > 0 ? config : undefined;
159
+ }
160
+
161
+ /**
162
+ * Upsert the full override for a topic. Fields set to `undefined` are cleared
163
+ * (stored as SQL NULL), matching the previous Map semantics where the route
164
+ * built a complete `updated` object before persisting.
165
+ */
166
+ export function setApiTopicConfig(topicId: string, config: TopicConfig): void {
167
+ db.query(
168
+ `INSERT INTO api_topic_config
169
+ (topic_id, model, effort, mcp, agent_locked, model_locked, effort_locked)
170
+ VALUES (?, ?, ?, ?, ?, ?, ?)
171
+ ON CONFLICT(topic_id) DO UPDATE SET
172
+ model = excluded.model,
173
+ effort = excluded.effort,
174
+ mcp = excluded.mcp,
175
+ agent_locked = excluded.agent_locked,
176
+ model_locked = excluded.model_locked,
177
+ effort_locked = excluded.effort_locked`,
178
+ ).run(
179
+ topicId,
180
+ config.model ?? null,
181
+ config.effort ?? null,
182
+ config.mcp ? JSON.stringify(config.mcp) : null,
183
+ config.agentLocked ? 1 : 0,
184
+ config.modelLocked ? 1 : 0,
185
+ config.effortLocked ? 1 : 0,
186
+ );
187
+ }
188
+
189
+ /** Remove a topic's override row entirely (e.g. on topic hard-delete). */
190
+ export function deleteApiTopicConfig(topicId: string): void {
191
+ db.query("DELETE FROM api_topic_config WHERE topic_id = ?").run(topicId);
192
+ }