negotium 0.1.6 → 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.
- package/README.md +2 -2
- package/dist/chunk-0ay6cjdy.js +1331 -0
- package/dist/chunk-0ay6cjdy.js.map +16 -0
- package/dist/chunk-1k7t7bnx.js +97 -0
- package/dist/chunk-1k7t7bnx.js.map +10 -0
- package/dist/chunk-5ahkew9c.js +806 -0
- package/dist/chunk-5ahkew9c.js.map +17 -0
- package/dist/chunk-6cpn7ht2.js +287 -0
- package/dist/chunk-6cpn7ht2.js.map +13 -0
- package/dist/chunk-6kctn20e.js +3529 -0
- package/dist/chunk-6kctn20e.js.map +20 -0
- package/dist/chunk-7xqj7aex.js +77 -0
- package/dist/chunk-7xqj7aex.js.map +11 -0
- package/dist/chunk-9p0ww3tk.js +41 -0
- package/dist/chunk-9p0ww3tk.js.map +10 -0
- package/dist/chunk-a92a7nrk.js +252 -0
- package/dist/chunk-a92a7nrk.js.map +10 -0
- package/dist/chunk-c500xe9z.js +33 -0
- package/dist/chunk-c500xe9z.js.map +10 -0
- package/dist/chunk-cds1qxsf.js +1644 -0
- package/dist/chunk-cds1qxsf.js.map +17 -0
- package/dist/chunk-e4407g8x.js +274 -0
- package/dist/chunk-e4407g8x.js.map +10 -0
- package/dist/chunk-e5pbggsh.js +363 -0
- package/dist/chunk-e5pbggsh.js.map +11 -0
- package/dist/chunk-edb07sjp.js +567 -0
- package/dist/chunk-edb07sjp.js.map +14 -0
- package/dist/chunk-exd0v74a.js +3012 -0
- package/dist/chunk-exd0v74a.js.map +32 -0
- package/dist/chunk-fgrme9tr.js +4398 -0
- package/dist/chunk-fgrme9tr.js.map +47 -0
- package/dist/chunk-fka9eqe1.js +581 -0
- package/dist/chunk-fka9eqe1.js.map +11 -0
- package/dist/chunk-g82jygez.js +108 -0
- package/dist/chunk-g82jygez.js.map +10 -0
- package/dist/chunk-ghs2j1fr.js +312 -0
- package/dist/chunk-ghs2j1fr.js.map +10 -0
- package/dist/chunk-gmy11efb.js +125 -0
- package/dist/chunk-gmy11efb.js.map +10 -0
- package/dist/chunk-gxma4c70.js +110 -0
- package/dist/chunk-gxma4c70.js.map +10 -0
- package/dist/chunk-hkdqhm34.js +61 -0
- package/dist/chunk-hkdqhm34.js.map +10 -0
- package/dist/chunk-hpa0cpzh.js +134 -0
- package/dist/chunk-hpa0cpzh.js.map +10 -0
- package/dist/chunk-hwy3n31w.js +21 -0
- package/dist/chunk-hwy3n31w.js.map +9 -0
- package/dist/chunk-j2behpy7.js +214 -0
- package/dist/chunk-j2behpy7.js.map +10 -0
- package/dist/chunk-jc0at0tc.js +102 -0
- package/dist/chunk-jc0at0tc.js.map +10 -0
- package/dist/chunk-jkqq5ef6.js +243 -0
- package/dist/chunk-jkqq5ef6.js.map +11 -0
- package/dist/chunk-jzkw2f01.js +51 -0
- package/dist/chunk-jzkw2f01.js.map +10 -0
- package/dist/chunk-jzz91jwh.js +70 -0
- package/dist/chunk-jzz91jwh.js.map +10 -0
- package/dist/chunk-k9z47mab.js +247 -0
- package/dist/chunk-k9z47mab.js.map +12 -0
- package/dist/chunk-n17yxpa8.js +23 -0
- package/dist/chunk-n17yxpa8.js.map +10 -0
- package/dist/chunk-naa09ags.js +2143 -0
- package/dist/chunk-naa09ags.js.map +15 -0
- package/dist/chunk-ns3ke88n.js +142 -0
- package/dist/chunk-ns3ke88n.js.map +10 -0
- package/dist/chunk-p3cr2ejg.js +1626 -0
- package/dist/chunk-p3cr2ejg.js.map +15 -0
- package/dist/chunk-q5bcpf23.js +2925 -0
- package/dist/chunk-q5bcpf23.js.map +20 -0
- package/dist/chunk-rafc55bs.js +424 -0
- package/dist/chunk-rafc55bs.js.map +11 -0
- package/dist/chunk-rbrxjvbj.js +2830 -0
- package/dist/chunk-rbrxjvbj.js.map +23 -0
- package/dist/chunk-sa874j5c.js +9 -0
- package/dist/chunk-sa874j5c.js.map +10 -0
- package/dist/chunk-sgsz2fpq.js +289 -0
- package/dist/chunk-sgsz2fpq.js.map +10 -0
- package/dist/chunk-sm6fh5c5.js +569 -0
- package/dist/chunk-sm6fh5c5.js.map +14 -0
- package/dist/chunk-t9zcg8f1.js +499 -0
- package/dist/chunk-t9zcg8f1.js.map +13 -0
- package/dist/chunk-vp2db42d.js +121 -0
- package/dist/chunk-vp2db42d.js.map +10 -0
- package/dist/chunk-w13jjf4a.js +46 -0
- package/dist/chunk-w13jjf4a.js.map +10 -0
- package/dist/chunk-x1arr7ze.js +63 -0
- package/dist/chunk-x1arr7ze.js.map +10 -0
- package/dist/chunk-xfq68rch.js +583 -0
- package/dist/chunk-xfq68rch.js.map +11 -0
- package/dist/main.js +132 -20
- package/dist/main.js.map +4 -4
- package/dist/runtime/scripts/faster-whisper-wrapper.py +87 -0
- package/dist/runtime/scripts/mcp-patchright-http.mjs +349 -0
- package/dist/runtime/src/agents/api-topic-agent-switch.ts +178 -0
- package/dist/runtime/src/agents/archiver.ts +396 -0
- package/dist/runtime/src/agents/auth-check.ts +64 -0
- package/dist/runtime/src/agents/claude-provider.ts +737 -0
- package/dist/runtime/src/agents/claude-registry.ts +115 -0
- package/dist/runtime/src/agents/codex-native-multi-agent.ts +177 -0
- package/dist/runtime/src/agents/codex-provider.ts +785 -0
- package/dist/runtime/src/agents/codex-registry.ts +115 -0
- package/dist/runtime/src/agents/codex-tree-kill.ts +365 -0
- package/dist/runtime/src/agents/contracts.ts +69 -0
- package/dist/runtime/src/agents/deep-map.ts +13 -0
- package/dist/runtime/src/agents/execution-host.ts +91 -0
- package/dist/runtime/src/agents/fixtures/claude-attachments.jsonl +2 -0
- package/dist/runtime/src/agents/fixtures/codex-shell.jsonl +5 -0
- package/dist/runtime/src/agents/fork.ts +65 -0
- package/dist/runtime/src/agents/hosted-agent.ts +48 -0
- package/dist/runtime/src/agents/idle-archiver.ts +128 -0
- package/dist/runtime/src/agents/index.ts +205 -0
- package/dist/runtime/src/agents/maestro-provider.ts +206 -0
- package/dist/runtime/src/agents/maestro-registry.ts +31 -0
- package/dist/runtime/src/agents/mcp-tools/ask-user.ts +280 -0
- package/dist/runtime/src/agents/mcp-tools/common.ts +21 -0
- package/dist/runtime/src/agents/mcp-tools/self-config.ts +220 -0
- package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +525 -0
- package/dist/runtime/src/agents/mcp-tools/visuals.ts +88 -0
- package/dist/runtime/src/agents/model-catalog.ts +151 -0
- package/dist/runtime/src/agents/registry.ts +24 -0
- package/dist/runtime/src/agents/rollout/claude.ts +331 -0
- package/dist/runtime/src/agents/rollout/codex.ts +569 -0
- package/dist/runtime/src/agents/rollout/shared.ts +205 -0
- package/dist/runtime/src/agents/self-config-core.ts +415 -0
- package/dist/runtime/src/agents/task-events.ts +44 -0
- package/dist/runtime/src/agents/tool-format.ts +352 -0
- package/dist/runtime/src/agents/topic-agent-switch.ts +193 -0
- package/dist/runtime/src/agents/topic-cleanup.ts +231 -0
- package/dist/runtime/src/agents/vault-tool-policy.ts +33 -0
- package/dist/runtime/src/application/execute-external-user-turn.ts +23 -0
- package/dist/runtime/src/application/submit-user-message.ts +47 -0
- package/dist/runtime/src/application/switch-topic-effort.ts +48 -0
- package/dist/runtime/src/application/switch-topic-model.ts +79 -0
- package/dist/runtime/src/application/topic-service.ts +139 -0
- package/dist/runtime/src/application/vault-command.ts +170 -0
- package/dist/runtime/src/bus.ts +319 -0
- package/dist/runtime/src/index.ts +333 -0
- package/dist/runtime/src/mcp/agent-health-server.ts +309 -0
- package/dist/runtime/src/mcp/background-bash-server.ts +346 -0
- package/dist/runtime/src/mcp/canonical-bridge-config.ts +84 -0
- package/dist/runtime/src/mcp/canonical-proxy-server.ts +171 -0
- package/dist/runtime/src/mcp/dm-ipc.ts +60 -0
- package/dist/runtime/src/mcp/mcp-helpers.ts +40 -0
- package/dist/runtime/src/mcp/paddleocr-server.ts +105 -0
- package/dist/runtime/src/mcp/peer-bridge.ts +114 -0
- package/dist/runtime/src/mcp/runtime-spec.ts +134 -0
- package/dist/runtime/src/mcp/session-comm/bridge-ipc-config.ts +27 -0
- package/dist/runtime/src/mcp/session-comm/peer-forward.ts +165 -0
- package/dist/runtime/src/mcp/session-comm/runtime.ts +115 -0
- package/dist/runtime/src/mcp/session-comm/server.ts +649 -0
- package/dist/runtime/src/mcp/session-comm/topic-config.ts +131 -0
- package/dist/runtime/src/mcp/session-comm/topics.ts +229 -0
- package/dist/runtime/src/mcp/stdio-protect.ts +84 -0
- package/dist/runtime/src/mcp/system-health-server.ts +130 -0
- package/dist/runtime/src/mcp/task-server.ts +173 -0
- package/dist/runtime/src/mcp/token-stats-server.ts +98 -0
- package/dist/runtime/src/mcp/vault-http.ts +219 -0
- package/dist/runtime/src/mcp/vault-run.ts +153 -0
- package/dist/runtime/src/mcp/vault-server.ts +104 -0
- package/dist/runtime/src/mcp/wiki-server.ts +755 -0
- package/dist/runtime/src/media/file-events.ts +33 -0
- package/dist/runtime/src/media/text-extractor.ts +312 -0
- package/dist/runtime/src/media/video.ts +92 -0
- package/dist/runtime/src/outbox/file-ops.ts +210 -0
- package/dist/runtime/src/outbox/index.ts +8 -0
- package/dist/runtime/src/outbox/utils.ts +51 -0
- package/dist/runtime/src/platform/background-bash/manager.ts +279 -0
- package/dist/runtime/src/platform/config.ts +371 -0
- package/dist/runtime/src/platform/constants.ts +18 -0
- package/dist/runtime/src/platform/debug-users.ts +14 -0
- package/dist/runtime/src/platform/delay.ts +1 -0
- package/dist/runtime/src/platform/error.ts +9 -0
- package/dist/runtime/src/platform/file-utils.ts +16 -0
- package/dist/runtime/src/platform/jsonl.ts +202 -0
- package/dist/runtime/src/platform/lifecycle.ts +174 -0
- package/dist/runtime/src/platform/logger.ts +31 -0
- package/dist/runtime/src/platform/maestro-bootstrap-env.ts +3 -0
- package/dist/runtime/src/platform/mcp-config.ts +865 -0
- package/dist/runtime/src/platform/modules.ts +145 -0
- package/dist/runtime/src/platform/node-plugins.ts +46 -0
- package/dist/runtime/src/platform/playwright/manager.ts +1233 -0
- package/dist/runtime/src/prompts/agents/wiki-archiver.md +261 -0
- package/dist/runtime/src/prompts/builders.ts +357 -0
- package/dist/runtime/src/prompts/sessions/channel-system.md +48 -0
- package/dist/runtime/src/prompts/sessions/manager-system.md +37 -0
- package/dist/runtime/src/prompts/sessions/topic-system.md +47 -0
- package/dist/runtime/src/prompts/sessions/visual-design.md +86 -0
- package/dist/runtime/src/query/active-rooms.ts +532 -0
- package/dist/runtime/src/query/control.ts +313 -0
- package/dist/runtime/src/query/index.ts +24 -0
- package/dist/runtime/src/query/session-inbox-cleanup.ts +44 -0
- package/dist/runtime/src/query/session-inbox-path.ts +42 -0
- package/dist/runtime/src/query/state.ts +94 -0
- package/dist/runtime/src/query/types.ts +83 -0
- package/dist/runtime/src/runtime/ask-callbacks.ts +153 -0
- package/dist/runtime/src/runtime/attachments.ts +151 -0
- package/dist/runtime/src/runtime/background-sessions.ts +83 -0
- package/dist/runtime/src/runtime/channel-context.ts +107 -0
- package/dist/runtime/src/runtime/errors.ts +69 -0
- package/dist/runtime/src/runtime/event-heartbeat.ts +77 -0
- package/dist/runtime/src/runtime/file-hooks.ts +81 -0
- package/dist/runtime/src/runtime/footer.ts +23 -0
- package/dist/runtime/src/runtime/inbox.ts +946 -0
- package/dist/runtime/src/runtime/tasks.ts +56 -0
- package/dist/runtime/src/runtime/topic-config.ts +29 -0
- package/dist/runtime/src/runtime/turn-runner.ts +2286 -0
- package/dist/runtime/src/runtime/usage-alert.ts +84 -0
- package/dist/runtime/src/runtime/visual-html.ts +109 -0
- package/dist/runtime/src/runtime/visual-store.ts +430 -0
- package/dist/runtime/src/runtime/visuals.ts +171 -0
- package/dist/runtime/src/security/sanitize.ts +27 -0
- package/dist/runtime/src/security/sensitive-path.ts +34 -0
- package/dist/runtime/src/storage/activity-log.ts +152 -0
- package/dist/runtime/src/storage/api-messages.ts +617 -0
- package/dist/runtime/src/storage/api-topic-brief.ts +127 -0
- package/dist/runtime/src/storage/api-topic-config.ts +192 -0
- package/dist/runtime/src/storage/api-topics.ts +844 -0
- package/dist/runtime/src/storage/app-settings.ts +44 -0
- package/dist/runtime/src/storage/browser-profiles.ts +133 -0
- package/dist/runtime/src/storage/conversations.ts +268 -0
- package/dist/runtime/src/storage/forum/agent-settings.ts +20 -0
- package/dist/runtime/src/storage/forum/index.ts +4 -0
- package/dist/runtime/src/storage/forum/repository.ts +283 -0
- package/dist/runtime/src/storage/forum/schema.ts +300 -0
- package/dist/runtime/src/storage/forum-db.ts +23 -0
- package/dist/runtime/src/storage/runtime-events.ts +130 -0
- package/dist/runtime/src/storage/runtime-leases.ts +189 -0
- package/dist/runtime/src/storage/runtime-process-leases.ts +216 -0
- package/dist/runtime/src/storage/runtime-topic-state.ts +148 -0
- package/dist/runtime/src/storage/runtime-turn-requests.ts +300 -0
- package/dist/runtime/src/storage/self-schedules.ts +298 -0
- package/dist/runtime/src/storage/session-asks.ts +347 -0
- package/dist/runtime/src/storage/sqlite.ts +129 -0
- package/dist/runtime/src/storage/tasks.ts +179 -0
- package/dist/runtime/src/storage/token-stats.ts +144 -0
- package/dist/runtime/src/storage/topic-archive-state.ts +59 -0
- package/dist/runtime/src/storage/topic-archive.ts +74 -0
- package/dist/runtime/src/storage/topic-settings.ts +67 -0
- package/dist/runtime/src/storage/topic-transcript.ts +98 -0
- package/dist/runtime/src/storage/vault-crypto.ts +65 -0
- package/dist/runtime/src/storage/vault.ts +225 -0
- package/dist/runtime/src/storage/wiki-summary-names.ts +33 -0
- package/dist/runtime/src/storage/wiki.ts +85 -0
- package/dist/runtime/src/topics/create.ts +123 -0
- package/dist/runtime/src/topics/derive.ts +398 -0
- package/dist/runtime/src/topics/lifecycle.ts +311 -0
- package/dist/runtime/src/topics/links.ts +7 -0
- package/dist/runtime/src/topics/personal-general.ts +65 -0
- package/dist/runtime/src/topics/session.ts +381 -0
- package/dist/runtime/src/types/api.ts +497 -0
- package/dist/runtime/src/types.ts +255 -0
- package/dist/runtime/src/version.ts +1 -0
- package/dist/runtime/tsconfig.json +13 -0
- package/package.json +16 -3
- package/dist/main.d.ts +0 -3
- 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
|
+
}
|