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.
Files changed (34) hide show
  1. package/dist/agent-helpers.js +41 -32
  2. package/dist/agent-helpers.js.map +9 -9
  3. package/dist/{chunk-5cpdbbeh.js → chunk-fc58cqry.js} +3 -1
  4. package/dist/{chunk-5cpdbbeh.js.map → chunk-fc58cqry.js.map} +3 -3
  5. package/dist/hosted-agent.js +4 -2
  6. package/dist/hosted-agent.js.map +4 -4
  7. package/dist/main.js +662 -93
  8. package/dist/main.js.map +11 -11
  9. package/dist/mcp-factories.js +659 -91
  10. package/dist/mcp-factories.js.map +10 -10
  11. package/dist/prompts.js +8 -8
  12. package/dist/prompts.js.map +3 -3
  13. package/dist/registry.js +3 -3
  14. package/dist/registry.js.map +2 -2
  15. package/dist/rollout.js +1 -1
  16. package/dist/runtime/src/agents/idle-archiver.ts +3 -2
  17. package/dist/runtime/src/mcp/canonical-proxy-server.ts +18 -1
  18. package/dist/runtime/src/mcp/wiki-server.ts +801 -59
  19. package/dist/runtime/src/platform/mcp-config.ts +1 -0
  20. package/dist/runtime/src/prompts/agents/wiki-archiver.md +16 -6
  21. package/dist/runtime/src/prompts/builders.ts +17 -34
  22. package/dist/runtime/src/runtime/turn-runner.ts +14 -17
  23. package/dist/runtime/src/storage/api-topics.ts +13 -4
  24. package/dist/runtime/src/topics/lifecycle.ts +3 -2
  25. package/dist/runtime/src/types/api.ts +6 -0
  26. package/dist/runtime/src/version.ts +1 -1
  27. package/dist/storage.js +12 -6
  28. package/dist/storage.js.map +3 -3
  29. package/dist/types/packages/core/src/mcp/wiki-server.d.ts +4 -0
  30. package/dist/types/packages/core/src/prompts/builders.d.ts +2 -4
  31. package/dist/types/packages/core/src/storage/api-topics.d.ts +1 -0
  32. package/dist/types/packages/core/src/types/api.d.ts +6 -0
  33. package/dist/types/packages/core/src/version.d.ts +1 -1
  34. 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(topic, content)`.
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(topic, content)`.
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** (`Read wiki/topic/<topic>.md`) if present.
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 title — `save_topic_brief` handles the path + SQLite mirror.
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=<topic>, description=<one-line summary of recent work>, kind="topic")`
90
- - Pass the bare topic name (no `topic/` prefix); the MCP wikilinks it as `[[topic/<topic>]]`.
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
- briefFile: string;
591
- wikiDir: string;
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
- parts.push(`Memory directory: ${opts.wikiDir}/topic`);
599
- parts.push(`Files: ${opts.hasFiles ? opts.briefFile : "(none)"}`);
600
- if (opts.latestSummaryFile) {
601
- parts.push(`Latest summary: ${opts.latestSummaryFile}`);
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
- opts.isManager
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 ? topic : (getTopicMemoryOrigin(topicId) ?? topic);
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, memoryTopic.title);
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 { briefFile, hasBriefFile, latestSummaryFile } = resolveWikiMemoryMirror(
1391
- wikiDir,
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: memoryTopic.title,
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.17";
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 (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).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, 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);
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=0E9898BA8C097A7864756E2164756E21
4175
+ //# debugId=D0FC18F9A934EFB264756E2164756E21