negotium 0.2.17 → 0.2.18
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/dist/agent-helpers.js +41 -32
- package/dist/agent-helpers.js.map +9 -9
- package/dist/{chunk-5cpdbbeh.js → chunk-fc58cqry.js} +3 -1
- package/dist/{chunk-5cpdbbeh.js.map → chunk-fc58cqry.js.map} +3 -3
- package/dist/hosted-agent.js +4 -2
- package/dist/hosted-agent.js.map +4 -4
- package/dist/main.js +662 -93
- package/dist/main.js.map +11 -11
- package/dist/mcp-factories.js +659 -91
- package/dist/mcp-factories.js.map +10 -10
- package/dist/prompts.js +8 -8
- package/dist/prompts.js.map +3 -3
- package/dist/registry.js +3 -3
- package/dist/registry.js.map +2 -2
- package/dist/rollout.js +1 -1
- package/dist/runtime/src/agents/idle-archiver.ts +3 -2
- package/dist/runtime/src/mcp/canonical-proxy-server.ts +18 -1
- package/dist/runtime/src/mcp/wiki-server.ts +801 -59
- package/dist/runtime/src/platform/mcp-config.ts +1 -0
- package/dist/runtime/src/prompts/agents/wiki-archiver.md +16 -6
- package/dist/runtime/src/prompts/builders.ts +17 -34
- package/dist/runtime/src/runtime/turn-runner.ts +14 -17
- package/dist/runtime/src/storage/api-topics.ts +13 -4
- package/dist/runtime/src/topics/lifecycle.ts +3 -2
- package/dist/runtime/src/types/api.ts +6 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/storage.js +12 -6
- package/dist/storage.js.map +3 -3
- package/dist/types/packages/core/src/mcp/wiki-server.d.ts +4 -0
- package/dist/types/packages/core/src/prompts/builders.d.ts +2 -4
- package/dist/types/packages/core/src/storage/api-topics.d.ts +1 -0
- package/dist/types/packages/core/src/types/api.d.ts +6 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -473,6 +473,7 @@ const MCP_CATALOG: Record<string, RuntimeMcpCatalogEntry> = {
|
|
|
473
473
|
: null;
|
|
474
474
|
}
|
|
475
475
|
const args = [`--user-id=${userId}`];
|
|
476
|
+
if (topicId) args.push(`--current-topic-id=${topicId}`);
|
|
476
477
|
// Prefer the REST topic id so save_wiki_entry writes filenames and SQLite
|
|
477
478
|
// briefs keyed the same way the topic wiki API reads them.
|
|
478
479
|
const resolvedWikiTopicId =
|
|
@@ -45,12 +45,21 @@ Keep structural tokens in English regardless: frontmatter keys, `type:` values,
|
|
|
45
45
|
|
|
46
46
|
2. **Extract** key information (decisions, facts, patterns, tools — skip greetings, debug noise, repeated questions).
|
|
47
47
|
- `topic` = the session name from the prompt (e.g. session `"dev"` → topic is `dev`)
|
|
48
|
+
- Before any wiki write, call `mcp__wiki__wiki_query(question=topic, kind="topic", limit=5)` and
|
|
49
|
+
inspect plausible candidates with `mcp__wiki__wiki_read(kind="topic", key=<candidate>)`.
|
|
50
|
+
- Set `canonical_topic` to an existing candidate only when it is clearly the same continuing
|
|
51
|
+
topic/persona. In that case, read it again with `adopt=true` so an active room keeps using that
|
|
52
|
+
key. If no candidate fits, set `canonical_topic = topic` and create a new topic memory.
|
|
53
|
+
- Do not merge memories from weak name overlap alone. Use the candidate description and brief;
|
|
54
|
+
when still uncertain, keep the current topic as a separate new memory.
|
|
55
|
+
- Use `canonical_topic` for every summary, brief, and topic-index write below. This keeps a
|
|
56
|
+
differently named room's summary and persona update in one canonical namespace.
|
|
48
57
|
- If `sent_files:` is in the prompt, include those entries under `## Files Sent`
|
|
49
58
|
- If the session yielded **no extractable substance** (pure debug, ≤2 short exchanges, all greeting), save only a single-line immutable summary via `save_wiki_entry`, then STOP. Do not modify the accumulated persona brief, articles, or indexes.
|
|
50
59
|
|
|
51
60
|
> **Ordering principle:** pipeline is **archive → summary → brief**. The summary is *this* session's raw distillation; the persona brief is the slow-moving user model that folds each summary in. Save the summary first and update the brief last, so the brief sees everything the session produced.
|
|
52
61
|
|
|
53
|
-
3. **Save the immutable session summary** via `mcp__wiki__save_wiki_entry(
|
|
62
|
+
3. **Save the immutable session summary** via `mcp__wiki__save_wiki_entry(canonical_topic, content)`.
|
|
54
63
|
The MCP handles file naming + dedup → returns the saved path (e.g. `wiki/summaries/2026-05-08-dev.md`).
|
|
55
64
|
It also records `latest_summary_md` + `summary_date` in SQLite (it does **not** touch the brief —
|
|
56
65
|
that is done in step 5 via `save_topic_brief`). Use the **summary format** below.
|
|
@@ -61,17 +70,18 @@ Keep structural tokens in English regardless: frontmatter keys, `type:` values,
|
|
|
61
70
|
- **Preserve manually written body sections.** Only append/update what the session adds.
|
|
62
71
|
- If new: Write `wiki/articles/<slug>.md` using the **article format** below.
|
|
63
72
|
- Skip session-specific noise. If nothing qualifies, no articles change — that's fine.
|
|
64
|
-
5. **Update the accumulated persona brief last** via `mcp__wiki__save_topic_brief(
|
|
73
|
+
5. **Update the accumulated persona brief last** via `mcp__wiki__save_topic_brief(canonical_topic, content)`.
|
|
65
74
|
This is the culmination of the run — the brief is not a worklog, it is the wiki's evolving
|
|
66
75
|
**persona/user-model** for this topic: who the user is, how they want to be served, and where
|
|
67
76
|
things stand. It is injected verbatim at the next session's start, so write it as durable memory,
|
|
68
77
|
not as session notes.
|
|
69
|
-
- **Read the existing brief first**
|
|
78
|
+
- **Read the existing brief first** with
|
|
79
|
+
`mcp__wiki__wiki_read(kind="topic", key=canonical_topic)` if present.
|
|
70
80
|
- **Merge, don't overwrite.** Fold this session's Preferences / Patterns / durable Facts /
|
|
71
81
|
Decisions into the **persona layer** (accumulate — the user-model is slow-moving). Refresh only
|
|
72
82
|
the volatile layers (`## Recent Work`, `## Current State`) from this session. Preserve still-valid
|
|
73
83
|
prior persona traits; remove a trait only when the new session explicitly supersedes it.
|
|
74
|
-
- Keep one canonical file per topic
|
|
84
|
+
- Keep one canonical file per topic memory key — `save_topic_brief` handles the path + SQLite mirror.
|
|
75
85
|
Never add a UUID or room id. Write a fresh compact brief using the **brief format** below.
|
|
76
86
|
6. **Update the dual indexes via `mcp__wiki__index_upsert` — one call per entry.**
|
|
77
87
|
The MCP handles in-place updates, section insertion, and the `created` vs `updated` date split. Do **not** Read/Write the index files manually.
|
|
@@ -86,8 +96,8 @@ Keep structural tokens in English regardless: frontmatter keys, `type:` values,
|
|
|
86
96
|
- Goes under `## Source Summaries` automatically.
|
|
87
97
|
|
|
88
98
|
**For this session's topic brief** (from step 5):
|
|
89
|
-
- Call: `index_upsert(slug=<
|
|
90
|
-
- Pass the bare
|
|
99
|
+
- Call: `index_upsert(slug=<canonical_topic>, description=<one-line summary of recent work>, kind="topic")`
|
|
100
|
+
- Pass the bare canonical key (no `topic/` prefix); the MCP wikilinks it as `[[topic/<canonical_topic>]]`.
|
|
91
101
|
|
|
92
102
|
**Never delete entries** — `index_upsert` is insert-or-update only; pruning is a `wiki lint` concern.
|
|
93
103
|
|
|
@@ -587,46 +587,29 @@ export const buildChannelSystemPrompt = defaultPromptBuilders.buildChannelSystem
|
|
|
587
587
|
export const buildManagerSystemPrompt = defaultPromptBuilders.buildManagerSystemPrompt;
|
|
588
588
|
|
|
589
589
|
export function buildMemoryPromptSection(opts: {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
hasFiles: boolean;
|
|
593
|
-
latestSummaryFile?: string | null;
|
|
590
|
+
topicTitle: string;
|
|
591
|
+
memoryKey?: string;
|
|
594
592
|
hasArchive?: boolean;
|
|
595
593
|
isManager: boolean;
|
|
596
594
|
}): string {
|
|
597
595
|
const parts: string[] = ["\n\n## Memory"];
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
596
|
+
if (opts.isManager) {
|
|
597
|
+
parts.push(
|
|
598
|
+
"Use `wiki_query` for past workspace decisions or cross-topic context, then `wiki_read` only for the relevant result.",
|
|
599
|
+
);
|
|
600
|
+
} else if (opts.memoryKey) {
|
|
601
|
+
parts.push(
|
|
602
|
+
`This topic uses the canonical memory key \`${opts.memoryKey}\`. At the start of a new session, read it with \`wiki_read(kind: "topic", key: "${opts.memoryKey}")\`.`,
|
|
603
|
+
);
|
|
604
|
+
} else {
|
|
605
|
+
parts.push(
|
|
606
|
+
`At the start of a new session, search topic memories with \`wiki_query(question: "${opts.topicTitle}", kind: "topic", limit: 5)\` and read the most relevant candidate with \`wiki_read\`.`,
|
|
607
|
+
"If a candidate is clearly the same continuing topic/persona, read it with `adopt: true`; otherwise keep this topic's own name as its memory key. Do not merge weak or ambiguous matches.",
|
|
608
|
+
);
|
|
602
609
|
}
|
|
603
610
|
parts.push(
|
|
604
|
-
"",
|
|
605
|
-
|
|
606
|
-
? opts.hasFiles
|
|
607
|
-
? "Above is this workspace's memory-hub brief (accumulated across all archived topics). For past context or other topics, consult it first, then use the `wiki_query` MCP tool for deeper recall."
|
|
608
|
-
: "If you need past context while handling delegated work, use the `wiki_query` MCP tool."
|
|
609
|
-
: opts.hasFiles
|
|
610
|
-
? "The file above is this topic's wiki brief. Read it first for past context; use the `wiki_query` MCP tool for deeper recall."
|
|
611
|
-
: opts.latestSummaryFile
|
|
612
|
-
? "There is no wiki brief file yet. For past context, read the Latest summary first; use the `wiki_query` MCP tool for deeper recall."
|
|
613
|
-
: "There is no wiki brief file yet. Use the `wiki_query` MCP tool for past context.",
|
|
614
|
-
);
|
|
615
|
-
parts.push(
|
|
616
|
-
"",
|
|
617
|
-
opts.isManager
|
|
618
|
-
? "When answering about past workspace decisions or other topics, weave in memory naturally, but confirm with `wiki_query` when unsure."
|
|
619
|
-
: opts.hasFiles || opts.latestSummaryFile
|
|
620
|
-
? "On your first reply in the topic, read the wiki brief" +
|
|
621
|
-
(opts.latestSummaryFile ? " and Latest summary" : "") +
|
|
622
|
-
" and mention a one-line context recap so the user can quickly confirm it is on track."
|
|
623
|
-
: "On your first reply in the topic, check prior conversation context if needed and mention it in one natural line.",
|
|
624
|
-
);
|
|
625
|
-
parts.push(
|
|
626
|
-
"",
|
|
627
|
-
opts.hasFiles
|
|
628
|
-
? "If the user says your memory is wrong, find the file in the Memory directory with Read and fix it directly with Edit."
|
|
629
|
-
: "If the user says your memory is wrong, check the relevant wiki brief and fix it directly when possible, or use `wiki_query` to find the entry and reconcile.",
|
|
611
|
+
"Keep the lookup quiet. Mention prior context naturally in one short line only when it helps the user.",
|
|
612
|
+
'Use `wiki_query(kind: "article")` for reusable knowledge and `wiki_query(kind: "summary")` for historical session details.',
|
|
630
613
|
);
|
|
631
614
|
if (opts.hasArchive) {
|
|
632
615
|
parts.push(
|
|
@@ -1379,33 +1379,30 @@ export function startAiTurn(params: StartAiTurnParams): string | null {
|
|
|
1379
1379
|
? buildChannelSystemPrompt(systemPromptOpts)
|
|
1380
1380
|
: buildTopicSystemPrompt(systemPromptOpts);
|
|
1381
1381
|
const memoryTopic =
|
|
1382
|
-
isMentionOnlyChannel || isManager
|
|
1382
|
+
isMentionOnlyChannel || isManager || topic.memoryKey
|
|
1383
|
+
? topic
|
|
1384
|
+
: (getTopicMemoryOrigin(topicId) ?? topic);
|
|
1385
|
+
const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
|
|
1383
1386
|
if (!isMentionOnlyChannel) {
|
|
1387
|
+
let hasMemoryArchive = false;
|
|
1384
1388
|
try {
|
|
1385
|
-
const resolvedBrief = resolveTopicBrief(memoryTopic.id,
|
|
1389
|
+
const resolvedBrief = resolveTopicBrief(memoryTopic.id, memoryTitle);
|
|
1386
1390
|
// #General is the workspace memory hub: its brief is the rolling digest the
|
|
1387
1391
|
// archiver accumulates across ALL archived topics, and its files live in the
|
|
1388
1392
|
// SHARED wiki root (getSharedWikiDir), not this topic's per-room workspace.
|
|
1389
1393
|
const wikiDir = getSharedWikiDir();
|
|
1390
|
-
const {
|
|
1391
|
-
|
|
1392
|
-
memoryTopic.id,
|
|
1393
|
-
memoryTopic.title,
|
|
1394
|
-
);
|
|
1395
|
-
if (resolvedBrief || hasBriefFile || latestSummaryFile) {
|
|
1396
|
-
systemPrompt += buildMemoryPromptSection({
|
|
1397
|
-
briefFile,
|
|
1398
|
-
wikiDir,
|
|
1399
|
-
hasFiles: hasBriefFile,
|
|
1400
|
-
latestSummaryFile,
|
|
1401
|
-
hasArchive: Boolean(resolvedBrief?.brief.latestSummaryMd || latestSummaryFile),
|
|
1402
|
-
isManager,
|
|
1403
|
-
});
|
|
1404
|
-
}
|
|
1394
|
+
const { latestSummaryFile } = resolveWikiMemoryMirror(wikiDir, memoryTopic.id, memoryTitle);
|
|
1395
|
+
hasMemoryArchive = Boolean(resolvedBrief?.brief.latestSummaryMd || latestSummaryFile);
|
|
1405
1396
|
} catch (err) {
|
|
1406
1397
|
// Brief fetch failure is non-fatal — don't block the turn.
|
|
1407
1398
|
logger.warn({ topicId, err }, "ai: failed to inject topic brief");
|
|
1408
1399
|
}
|
|
1400
|
+
systemPrompt += buildMemoryPromptSection({
|
|
1401
|
+
topicTitle: topic.title,
|
|
1402
|
+
...(memoryTopic.memoryKey ? { memoryKey: memoryTopic.memoryKey } : {}),
|
|
1403
|
+
hasArchive: hasMemoryArchive,
|
|
1404
|
+
isManager,
|
|
1405
|
+
});
|
|
1409
1406
|
}
|
|
1410
1407
|
const activeVisual = visualTools ? getActiveVisualForPrompt(topicId, userId) : null;
|
|
1411
1408
|
if (activeVisual) {
|
|
@@ -215,6 +215,7 @@ function initializeApiTopicsSchema(): void {
|
|
|
215
215
|
last_message_at TEXT,
|
|
216
216
|
parent_topic_id TEXT,
|
|
217
217
|
memory_topic_id TEXT,
|
|
218
|
+
memory_key TEXT,
|
|
218
219
|
is_fork INTEGER NOT NULL DEFAULT 0 CHECK (is_fork IN (0,1)),
|
|
219
220
|
is_subagent INTEGER NOT NULL DEFAULT 0 CHECK (is_subagent IN (0,1)),
|
|
220
221
|
visibility TEXT NOT NULL DEFAULT 'visible' CHECK (visibility IN ('visible','hidden')),
|
|
@@ -287,8 +288,8 @@ function initializeApiTopicsSchema(): void {
|
|
|
287
288
|
db.query(
|
|
288
289
|
`INSERT INTO api_topics_next
|
|
289
290
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
290
|
-
created_at,last_message_at,parent_topic_id,memory_topic_id,is_fork,is_subagent,visibility,access_mode,session_id)
|
|
291
|
-
VALUES (
|
|
291
|
+
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,session_id)
|
|
292
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
|
292
293
|
).run(
|
|
293
294
|
String(row.id),
|
|
294
295
|
String(row.title),
|
|
@@ -302,6 +303,7 @@ function initializeApiTopicsSchema(): void {
|
|
|
302
303
|
typeof row.last_message_at === "string" ? row.last_message_at : null,
|
|
303
304
|
typeof row.parent_topic_id === "string" ? row.parent_topic_id : null,
|
|
304
305
|
typeof row.memory_topic_id === "string" ? row.memory_topic_id : null,
|
|
306
|
+
typeof row.memory_key === "string" ? row.memory_key : null,
|
|
305
307
|
Number(row.is_fork ?? 0) !== 0 ? 1 : 0,
|
|
306
308
|
Number(row.is_subagent ?? 0) !== 0 ? 1 : 0,
|
|
307
309
|
row.visibility === "hidden" ? "hidden" : "visible",
|
|
@@ -386,6 +388,9 @@ function initializeApiTopicsSchema(): void {
|
|
|
386
388
|
if (!tableColumns("api_topics").has("memory_topic_id")) {
|
|
387
389
|
db.exec("ALTER TABLE api_topics ADD COLUMN memory_topic_id TEXT");
|
|
388
390
|
}
|
|
391
|
+
if (!tableColumns("api_topics").has("memory_key")) {
|
|
392
|
+
db.exec("ALTER TABLE api_topics ADD COLUMN memory_key TEXT");
|
|
393
|
+
}
|
|
389
394
|
db.exec(`
|
|
390
395
|
UPDATE api_topics
|
|
391
396
|
SET browser_profile_owner = (
|
|
@@ -416,6 +421,7 @@ export interface TopicRow {
|
|
|
416
421
|
last_message_at: string | null;
|
|
417
422
|
parent_topic_id: string | null;
|
|
418
423
|
memory_topic_id: string | null;
|
|
424
|
+
memory_key: string | null;
|
|
419
425
|
is_fork: number;
|
|
420
426
|
is_subagent: number;
|
|
421
427
|
subagent_report_mode: string | null;
|
|
@@ -500,6 +506,7 @@ function rowToDto(
|
|
|
500
506
|
lastMessageAt: r.last_message_at ?? new Date().toISOString(),
|
|
501
507
|
parentTopicId: r.parent_topic_id ?? undefined,
|
|
502
508
|
memoryTopicId: r.memory_topic_id ?? undefined,
|
|
509
|
+
memoryKey: r.memory_key ?? undefined,
|
|
503
510
|
isFork: r.is_fork !== 0,
|
|
504
511
|
...(r.is_subagent !== 0 ? { isSubagent: true } : {}),
|
|
505
512
|
...(r.is_subagent !== 0
|
|
@@ -636,9 +643,9 @@ export function upsertTopic(t: TopicDto): void {
|
|
|
636
643
|
db.query(
|
|
637
644
|
`INSERT INTO api_topics
|
|
638
645
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
639
|
-
created_at,last_message_at,parent_topic_id,memory_topic_id,is_fork,is_subagent,visibility,access_mode,
|
|
646
|
+
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,
|
|
640
647
|
subagent_report_mode)
|
|
641
|
-
VALUES (
|
|
648
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
642
649
|
ON CONFLICT(id) DO UPDATE SET
|
|
643
650
|
title = excluded.title,
|
|
644
651
|
kind = excluded.kind,
|
|
@@ -651,6 +658,7 @@ export function upsertTopic(t: TopicDto): void {
|
|
|
651
658
|
last_message_at = excluded.last_message_at,
|
|
652
659
|
parent_topic_id = excluded.parent_topic_id,
|
|
653
660
|
memory_topic_id = excluded.memory_topic_id,
|
|
661
|
+
memory_key = excluded.memory_key,
|
|
654
662
|
is_fork = excluded.is_fork,
|
|
655
663
|
is_subagent = excluded.is_subagent,
|
|
656
664
|
visibility = excluded.visibility,
|
|
@@ -669,6 +677,7 @@ export function upsertTopic(t: TopicDto): void {
|
|
|
669
677
|
t.lastMessageAt ?? null,
|
|
670
678
|
t.parentTopicId ?? null,
|
|
671
679
|
t.memoryTopicId ?? null,
|
|
680
|
+
t.memoryKey ?? null,
|
|
672
681
|
t.isFork ? 1 : 0,
|
|
673
682
|
t.isSubagent ? 1 : 0,
|
|
674
683
|
normalizeTopicVisibility(t.visibility),
|
|
@@ -250,7 +250,8 @@ async function deleteTopicCascadeImpl(
|
|
|
250
250
|
)
|
|
251
251
|
) {
|
|
252
252
|
try {
|
|
253
|
-
const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
|
|
253
|
+
const memoryTopic = topic.memoryKey ? topic : (getTopicMemoryOrigin(topicId) ?? topic);
|
|
254
|
+
const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
|
|
254
255
|
runArchiverTurn({
|
|
255
256
|
userId,
|
|
256
257
|
// A memory origin that is also disappearing in this cascade must use
|
|
@@ -259,7 +260,7 @@ async function deleteTopicCascadeImpl(
|
|
|
259
260
|
...(memoryTopic.id !== topicId && !deletingTopicIds.has(memoryTopic.id)
|
|
260
261
|
? { topicId: memoryTopic.id }
|
|
261
262
|
: {}),
|
|
262
|
-
topicTitle:
|
|
263
|
+
topicTitle: memoryTitle,
|
|
263
264
|
archivePath: archived.path,
|
|
264
265
|
...(rawArchives.length > 0 ? { rawArchivePaths: rawArchives } : {}),
|
|
265
266
|
messageCount: archived.messageCount,
|
|
@@ -108,6 +108,12 @@ export interface TopicDto {
|
|
|
108
108
|
* When omitted, derived topics inherit memory through their parent chain.
|
|
109
109
|
*/
|
|
110
110
|
memoryTopicId?: string;
|
|
111
|
+
/**
|
|
112
|
+
* Canonical title-keyed wiki memory namespace for this topic.
|
|
113
|
+
* Unlike memoryTopicId this survives room deletion/recreation and does not
|
|
114
|
+
* identify another live topic.
|
|
115
|
+
*/
|
|
116
|
+
memoryKey?: string;
|
|
111
117
|
/** True when the topic was created via fork (copies history). */
|
|
112
118
|
isFork?: boolean;
|
|
113
119
|
/** True when this topic was spawned by an agent as a subagent worker room. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const NEGOTIUM_VERSION = "0.2.
|
|
1
|
+
export const NEGOTIUM_VERSION = "0.2.18";
|
package/dist/storage.js
CHANGED
|
@@ -1339,6 +1339,7 @@ function initializeApiTopicsSchema() {
|
|
|
1339
1339
|
last_message_at TEXT,
|
|
1340
1340
|
parent_topic_id TEXT,
|
|
1341
1341
|
memory_topic_id TEXT,
|
|
1342
|
+
memory_key TEXT,
|
|
1342
1343
|
is_fork INTEGER NOT NULL DEFAULT 0 CHECK (is_fork IN (0,1)),
|
|
1343
1344
|
is_subagent INTEGER NOT NULL DEFAULT 0 CHECK (is_subagent IN (0,1)),
|
|
1344
1345
|
visibility TEXT NOT NULL DEFAULT 'visible' CHECK (visibility IN ('visible','hidden')),
|
|
@@ -1388,8 +1389,8 @@ function initializeApiTopicsSchema() {
|
|
|
1388
1389
|
const legacyBaseEffort = row.base_effort ?? row.default_effort;
|
|
1389
1390
|
db.query(`INSERT INTO api_topics_next
|
|
1390
1391
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
1391
|
-
created_at,last_message_at,parent_topic_id,memory_topic_id,is_fork,is_subagent,visibility,access_mode,session_id)
|
|
1392
|
-
VALUES (
|
|
1392
|
+
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,session_id)
|
|
1393
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(String(row.id), String(row.title), normalized.kind, typeof row.description === "string" ? row.description : null, normalized.agent ?? null, typeof legacyBaseModel === "string" ? legacyBaseModel : null, typeof legacyBaseEffort === "string" ? legacyBaseEffort : null, normalized.aiMode, String(row.created_at), typeof row.last_message_at === "string" ? row.last_message_at : null, typeof row.parent_topic_id === "string" ? row.parent_topic_id : null, typeof row.memory_topic_id === "string" ? row.memory_topic_id : null, typeof row.memory_key === "string" ? row.memory_key : null, Number(row.is_fork ?? 0) !== 0 ? 1 : 0, Number(row.is_subagent ?? 0) !== 0 ? 1 : 0, row.visibility === "hidden" ? "hidden" : "visible", row.access_mode === "shared" ? "shared" : "private", typeof row.session_id === "string" ? row.session_id : null);
|
|
1393
1394
|
}
|
|
1394
1395
|
db.exec("DROP TABLE IF EXISTS topic_members");
|
|
1395
1396
|
db.exec("DROP TABLE api_topics");
|
|
@@ -1458,6 +1459,9 @@ function initializeApiTopicsSchema() {
|
|
|
1458
1459
|
if (!tableColumns2("api_topics").has("memory_topic_id")) {
|
|
1459
1460
|
db.exec("ALTER TABLE api_topics ADD COLUMN memory_topic_id TEXT");
|
|
1460
1461
|
}
|
|
1462
|
+
if (!tableColumns2("api_topics").has("memory_key")) {
|
|
1463
|
+
db.exec("ALTER TABLE api_topics ADD COLUMN memory_key TEXT");
|
|
1464
|
+
}
|
|
1461
1465
|
db.exec(`
|
|
1462
1466
|
UPDATE api_topics
|
|
1463
1467
|
SET browser_profile_owner = (
|
|
@@ -1519,6 +1523,7 @@ function rowToDto2(r, participants = getTopicParticipants(r.id), tellTargets) {
|
|
|
1519
1523
|
lastMessageAt: r.last_message_at ?? new Date().toISOString(),
|
|
1520
1524
|
parentTopicId: r.parent_topic_id ?? undefined,
|
|
1521
1525
|
memoryTopicId: r.memory_topic_id ?? undefined,
|
|
1526
|
+
memoryKey: r.memory_key ?? undefined,
|
|
1522
1527
|
isFork: r.is_fork !== 0,
|
|
1523
1528
|
...r.is_subagent !== 0 ? { isSubagent: true } : {},
|
|
1524
1529
|
...r.is_subagent !== 0 ? {
|
|
@@ -1613,9 +1618,9 @@ function upsertTopic(t) {
|
|
|
1613
1618
|
db.transaction(() => {
|
|
1614
1619
|
db.query(`INSERT INTO api_topics
|
|
1615
1620
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
1616
|
-
created_at,last_message_at,parent_topic_id,memory_topic_id,is_fork,is_subagent,visibility,access_mode,
|
|
1621
|
+
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,
|
|
1617
1622
|
subagent_report_mode)
|
|
1618
|
-
VALUES (
|
|
1623
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
1619
1624
|
ON CONFLICT(id) DO UPDATE SET
|
|
1620
1625
|
title = excluded.title,
|
|
1621
1626
|
kind = excluded.kind,
|
|
@@ -1628,11 +1633,12 @@ function upsertTopic(t) {
|
|
|
1628
1633
|
last_message_at = excluded.last_message_at,
|
|
1629
1634
|
parent_topic_id = excluded.parent_topic_id,
|
|
1630
1635
|
memory_topic_id = excluded.memory_topic_id,
|
|
1636
|
+
memory_key = excluded.memory_key,
|
|
1631
1637
|
is_fork = excluded.is_fork,
|
|
1632
1638
|
is_subagent = excluded.is_subagent,
|
|
1633
1639
|
visibility = excluded.visibility,
|
|
1634
1640
|
access_mode = excluded.access_mode,
|
|
1635
|
-
subagent_report_mode = excluded.subagent_report_mode`).run(t.id, t.title, normalized.kind, t.description ?? null, normalized.agent ?? null, t.defaultModel ?? null, t.defaultEffort ?? null, normalized.aiMode, t.createdAt, t.lastMessageAt ?? null, t.parentTopicId ?? null, t.memoryTopicId ?? null, t.isFork ? 1 : 0, t.isSubagent ? 1 : 0, normalizeTopicVisibility(t.visibility), normalizeTopicAccessMode(t.accessMode), t.subagentReportMode ?? "auto");
|
|
1641
|
+
subagent_report_mode = excluded.subagent_report_mode`).run(t.id, t.title, normalized.kind, t.description ?? null, normalized.agent ?? null, t.defaultModel ?? null, t.defaultEffort ?? null, normalized.aiMode, t.createdAt, t.lastMessageAt ?? null, t.parentTopicId ?? null, t.memoryTopicId ?? null, t.memoryKey ?? null, t.isFork ? 1 : 0, t.isSubagent ? 1 : 0, normalizeTopicVisibility(t.visibility), normalizeTopicAccessMode(t.accessMode), t.subagentReportMode ?? "auto");
|
|
1636
1642
|
db.query("DELETE FROM topic_members WHERE topic_id = ?").run(t.id);
|
|
1637
1643
|
for (const participant of t.participants) {
|
|
1638
1644
|
db.query("INSERT INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)").run(t.id, participant.userId, participant.role);
|
|
@@ -4166,4 +4172,4 @@ export {
|
|
|
4166
4172
|
ConversationLogDivergedError
|
|
4167
4173
|
};
|
|
4168
4174
|
|
|
4169
|
-
//# debugId=
|
|
4175
|
+
//# debugId=D0FC18F9A934EFB264756E2164756E21
|