negotium 0.6.5 → 0.6.7

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 (38) hide show
  1. package/dist/agent-helpers.js +193 -172
  2. package/dist/agent-helpers.js.map +13 -13
  3. package/dist/{chunk-rzef19v4.js → chunk-f124y9d0.js} +2 -2
  4. package/dist/{chunk-rzef19v4.js.map → chunk-f124y9d0.js.map} +2 -2
  5. package/dist/hosted-agent.js +2 -2
  6. package/dist/hosted-agent.js.map +2 -2
  7. package/dist/main.js +126 -41
  8. package/dist/main.js.map +18 -18
  9. package/dist/mcp-factories.js +45 -24
  10. package/dist/mcp-factories.js.map +11 -11
  11. package/dist/registry.js +1 -1
  12. package/dist/runtime/src/agents/archiver.ts +68 -32
  13. package/dist/runtime/src/agents/idle-archiver.ts +1 -0
  14. package/dist/runtime/src/application/topic-service.ts +5 -1
  15. package/dist/runtime/src/index.ts +1 -1
  16. package/dist/runtime/src/mcp-runtime-host.ts +1 -1
  17. package/dist/runtime/src/platform/constants.ts +12 -0
  18. package/dist/runtime/src/runtime/turn-event-stream.ts +2 -1
  19. package/dist/runtime/src/runtime/turn-runner.ts +8 -1
  20. package/dist/runtime/src/runtime-gateway.ts +6 -2
  21. package/dist/runtime/src/storage/conversations.ts +24 -4
  22. package/dist/runtime/src/topics/lifecycle.ts +4 -1
  23. package/dist/runtime/src/topics/session.ts +15 -9
  24. package/dist/runtime/src/version.ts +1 -1
  25. package/dist/runtime-gateway.js +7 -6
  26. package/dist/runtime-gateway.js.map +3 -3
  27. package/dist/storage.js +12 -4
  28. package/dist/storage.js.map +4 -4
  29. package/dist/types/packages/core/src/agents/archiver.d.ts +7 -2
  30. package/dist/types/packages/core/src/platform/constants.d.ts +10 -0
  31. package/dist/types/packages/core/src/runtime/turn-event-stream.d.ts +1 -0
  32. package/dist/types/packages/core/src/runtime/turn-runner.d.ts +1 -0
  33. package/dist/types/packages/core/src/runtime-gateway.d.ts +2 -2
  34. package/dist/types/packages/core/src/storage/conversations.d.ts +3 -0
  35. package/dist/types/packages/core/src/topics/lifecycle.d.ts +2 -0
  36. package/dist/types/packages/core/src/topics/session.d.ts +2 -0
  37. package/dist/types/packages/core/src/version.d.ts +1 -1
  38. package/package.json +1 -1
@@ -2980,7 +2980,7 @@ var init_codex = __esm(async () => {
2980
2980
  });
2981
2981
 
2982
2982
  // ../../packages/core/src/version.ts
2983
- var NEGOTIUM_VERSION = "0.6.5";
2983
+ var NEGOTIUM_VERSION = "0.6.7";
2984
2984
 
2985
2985
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
2986
2986
  import { spawn as spawn2 } from "child_process";
@@ -3670,6 +3670,16 @@ var init_task_events = __esm(async () => {
3670
3670
  defaultTaskEventHost = { readTasks, taskFileMtimeNs, taskScopeKey };
3671
3671
  });
3672
3672
 
3673
+ // ../../packages/core/src/platform/constants.ts
3674
+ var FROM_AUTO_CONTINUE = "auto-continue", FROM_SELF_SCHEDULE = "self-schedule", RESERVED_TOPIC_NAMES, GENERAL_TOPIC_ID = "general", MANAGER_TOPIC_LOG_NAME = "general", NODE_LOCAL_USER_ID = "local";
3675
+ var init_constants = __esm(() => {
3676
+ RESERVED_TOPIC_NAMES = new Set([
3677
+ FROM_AUTO_CONTINUE,
3678
+ FROM_SELF_SCHEDULE,
3679
+ "general"
3680
+ ]);
3681
+ });
3682
+
3673
3683
  // ../../packages/core/src/storage/conversations.ts
3674
3684
  import {
3675
3685
  existsSync as existsSync9,
@@ -3683,12 +3693,15 @@ import { dirname as dirname8, join as join11 } from "path";
3683
3693
  function conversationDir(_userId) {
3684
3694
  return join11(resolveStorageDataDir(), "conversations");
3685
3695
  }
3686
- function topicFilename(topicName) {
3696
+ function topicFilename(userId, topicName) {
3687
3697
  const t = sanitizeTopicName(topicName, true);
3688
- return `${t}.jsonl`;
3698
+ const owner = String(userId);
3699
+ if (t !== MANAGER_TOPIC_LOG_NAME || owner === NODE_LOCAL_USER_ID)
3700
+ return `${t}.jsonl`;
3701
+ return `${t}.${sanitizeId(owner)}.jsonl`;
3689
3702
  }
3690
3703
  function getConversationPath(userId, topicName) {
3691
- return join11(conversationDir(userId), topicFilename(topicName));
3704
+ return join11(conversationDir(userId), topicFilename(userId, topicName));
3692
3705
  }
3693
3706
  function getActiveConversationPath(userId, topicName) {
3694
3707
  const rawPath = getConversationPath(userId, topicName);
@@ -3789,6 +3802,7 @@ function findLastSessionIdForAgent(entries, agent) {
3789
3802
  }
3790
3803
  var ConversationLogDivergedError;
3791
3804
  var init_conversations = __esm(async () => {
3805
+ init_constants();
3792
3806
  init_jsonl();
3793
3807
  init_logger();
3794
3808
  await init_storage_host();
@@ -7449,142 +7463,6 @@ var init_api_topic_brief = __esm(async () => {
7449
7463
  }, 30);
7450
7464
  });
7451
7465
 
7452
- // ../../packages/core/src/storage/wiki.ts
7453
- import { basename as basename2, dirname as dirname11, join as join15 } from "path";
7454
- function getSharedWikiDir(workspaceDir = resolveStorageWorkspaceDir()) {
7455
- return workspaceDir === resolveStorageWorkspaceDir() ? resolveStorageSharedWikiDir() : join15(workspaceDir, "wiki");
7456
- }
7457
- var init_wiki = __esm(async () => {
7458
- await init_storage_host();
7459
- });
7460
-
7461
- // ../../packages/core/src/platform/constants.ts
7462
- var FROM_AUTO_CONTINUE = "auto-continue", FROM_SELF_SCHEDULE = "self-schedule", RESERVED_TOPIC_NAMES, GENERAL_TOPIC_ID = "general";
7463
- var init_constants = __esm(() => {
7464
- RESERVED_TOPIC_NAMES = new Set([
7465
- FROM_AUTO_CONTINUE,
7466
- FROM_SELF_SCHEDULE,
7467
- "general"
7468
- ]);
7469
- });
7470
-
7471
- // ../../packages/core/src/storage/api-topic-config.ts
7472
- function tableColumns(table) {
7473
- const rows = db.query(`PRAGMA table_info(${table})`).all();
7474
- return new Set(rows.map((row) => row.name));
7475
- }
7476
- function createCanonicalConfigTable(name = "api_topic_config") {
7477
- db.exec(`
7478
- CREATE TABLE IF NOT EXISTS ${name} (
7479
- topic_id TEXT PRIMARY KEY REFERENCES api_topics(id) ON DELETE CASCADE,
7480
- model TEXT,
7481
- effort TEXT CHECK (effort IS NULL OR effort IN ('low','medium','high','xhigh','max')),
7482
- mcp TEXT,
7483
- agent_locked INTEGER NOT NULL DEFAULT 0 CHECK (agent_locked IN (0,1)),
7484
- model_locked INTEGER NOT NULL DEFAULT 0 CHECK (model_locked IN (0,1)),
7485
- effort_locked INTEGER NOT NULL DEFAULT 0 CHECK (effort_locked IN (0,1))
7486
- )
7487
- `);
7488
- }
7489
- function initializeApiTopicConfigSchema() {
7490
- const existingConfigColumns = tableColumns("api_topic_config");
7491
- if (existingConfigColumns.size === 0) {
7492
- createCanonicalConfigTable();
7493
- } else if (existingConfigColumns.has("agent") || existingConfigColumns.has("agent_pinned") || existingConfigColumns.has("runtime_locked") || existingConfigColumns.has("model_pinned") || existingConfigColumns.has("effort_pinned") || existingConfigColumns.has("last_shown_agent")) {
7494
- const topicColumns = tableColumns("api_topics");
7495
- const agentColumn = topicColumns.has("agent") ? "agent" : topicColumns.has("runtime_agent") ? "runtime_agent" : null;
7496
- if (agentColumn && existingConfigColumns.has("agent")) {
7497
- db.exec(`UPDATE api_topics
7498
- SET ${agentColumn} = (
7499
- SELECT c.agent FROM api_topic_config c WHERE c.topic_id = api_topics.id
7500
- )
7501
- WHERE ${agentColumn} IS NULL
7502
- AND EXISTS (
7503
- SELECT 1 FROM api_topic_config c
7504
- WHERE c.topic_id = api_topics.id AND c.agent IS NOT NULL
7505
- )`);
7506
- }
7507
- const previousForeignKeys = db.query("PRAGMA foreign_keys").get()?.foreign_keys;
7508
- db.exec("PRAGMA foreign_keys = OFF");
7509
- try {
7510
- const agentLockSource = existingConfigColumns.has("runtime_locked") ? "runtime_locked" : existingConfigColumns.has("agent_pinned") ? "agent_pinned" : "0";
7511
- const modelLockSource = existingConfigColumns.has("model_locked") ? "model_locked" : existingConfigColumns.has("model_pinned") ? "model_pinned" : "0";
7512
- const effortLockSource = existingConfigColumns.has("effort_locked") ? "effort_locked" : existingConfigColumns.has("effort_pinned") ? "effort_pinned" : "0";
7513
- db.transaction(() => {
7514
- createCanonicalConfigTable("api_topic_config_next");
7515
- db.exec(`
7516
- INSERT INTO api_topic_config_next
7517
- (topic_id, model, effort, mcp, agent_locked, model_locked, effort_locked)
7518
- SELECT
7519
- topic_id,
7520
- model,
7521
- effort,
7522
- mcp,
7523
- COALESCE(${agentLockSource}, 0),
7524
- COALESCE(${modelLockSource}, 0),
7525
- COALESCE(${effortLockSource}, 0)
7526
- FROM api_topic_config
7527
- WHERE EXISTS (SELECT 1 FROM api_topics t WHERE t.id = api_topic_config.topic_id)
7528
- `);
7529
- db.exec("DROP TABLE api_topic_config");
7530
- db.exec("ALTER TABLE api_topic_config_next RENAME TO api_topic_config");
7531
- })();
7532
- } finally {
7533
- db.exec(`PRAGMA foreign_keys = ${previousForeignKeys ? "ON" : "OFF"}`);
7534
- }
7535
- } else {
7536
- createCanonicalConfigTable();
7537
- }
7538
- }
7539
- function rowToConfig(r) {
7540
- const cfg = {};
7541
- if (r.model)
7542
- cfg.model = r.model;
7543
- if (r.effort)
7544
- cfg.effort = r.effort;
7545
- if (r.mcp) {
7546
- try {
7547
- const parsed = JSON.parse(r.mcp);
7548
- if (Array.isArray(parsed))
7549
- cfg.mcp = parsed;
7550
- } catch {}
7551
- }
7552
- if (r.agent_locked)
7553
- cfg.agentLocked = true;
7554
- if (r.model_locked)
7555
- cfg.modelLocked = true;
7556
- if (r.effort_locked)
7557
- cfg.effortLocked = true;
7558
- return cfg;
7559
- }
7560
- function getApiTopicConfig(topicId) {
7561
- const row = db.query("SELECT topic_id, model, effort, mcp, agent_locked, model_locked, effort_locked FROM api_topic_config WHERE topic_id = ?").get(topicId);
7562
- if (!row)
7563
- return;
7564
- const config = rowToConfig(row);
7565
- return Object.keys(config).length > 0 ? config : undefined;
7566
- }
7567
- function setApiTopicConfig(topicId, config) {
7568
- db.query(`INSERT INTO api_topic_config
7569
- (topic_id, model, effort, mcp, agent_locked, model_locked, effort_locked)
7570
- VALUES (?, ?, ?, ?, ?, ?, ?)
7571
- ON CONFLICT(topic_id) DO UPDATE SET
7572
- model = excluded.model,
7573
- effort = excluded.effort,
7574
- mcp = excluded.mcp,
7575
- agent_locked = excluded.agent_locked,
7576
- model_locked = excluded.model_locked,
7577
- effort_locked = excluded.effort_locked`).run(topicId, config.model ?? null, config.effort ?? null, config.mcp ? JSON.stringify(config.mcp) : null, config.agentLocked ? 1 : 0, config.modelLocked ? 1 : 0, config.effortLocked ? 1 : 0);
7578
- }
7579
- function deleteApiTopicConfig(topicId) {
7580
- db.query("DELETE FROM api_topic_config WHERE topic_id = ?").run(topicId);
7581
- }
7582
- var init_api_topic_config = __esm(async () => {
7583
- await init_forum_db();
7584
- await init_storage_host();
7585
- registerStorageSchemaInitializer(initializeApiTopicConfigSchema, 40);
7586
- });
7587
-
7588
7466
  // ../../packages/core/src/storage/api-topics.ts
7589
7467
  function defaultTopicSurface() {
7590
7468
  return normalizeTopicSurface(process.env.NEGOTIUM_DEFAULT_SURFACE);
@@ -7601,7 +7479,7 @@ function normalizeSurfaceScope(value) {
7601
7479
  function defaultSurfaceScope() {
7602
7480
  return activeSurfaceScope ?? normalizeSurfaceScope(process.env.NEGOTIUM_SURFACE_SCOPE);
7603
7481
  }
7604
- function tableColumns2(table) {
7482
+ function tableColumns(table) {
7605
7483
  const rows = db.query(`PRAGMA table_info(${table})`).all();
7606
7484
  return new Set(rows.map((row) => row.name));
7607
7485
  }
@@ -7630,7 +7508,7 @@ function initializeApiTopicsSchema() {
7630
7508
  ai_mode TEXT
7631
7509
  )
7632
7510
  `);
7633
- const initialTopicColumns = tableColumns2("api_topics");
7511
+ const initialTopicColumns = tableColumns("api_topics");
7634
7512
  const legacyTopicSchema = !initialTopicColumns.has("response_policy");
7635
7513
  const needsCanonicalTopicRebuild = legacyTopicSchema || !initialTopicColumns.has("agent");
7636
7514
  if (legacyTopicSchema) {
@@ -7757,8 +7635,8 @@ function initializeApiTopicsSchema() {
7757
7635
  }
7758
7636
  if (needsCanonicalTopicRebuild) {
7759
7637
  const legacyRows = db.query("SELECT * FROM api_topics").all();
7760
- const existingMemberRows = tableColumns2("topic_members").has("topic_id") ? db.query("SELECT topic_id, user_id, role FROM topic_members").all() : [];
7761
- const configColumns = tableColumns2("api_topic_config");
7638
+ const existingMemberRows = tableColumns("topic_members").has("topic_id") ? db.query("SELECT topic_id, user_id, role FROM topic_members").all() : [];
7639
+ const configColumns = tableColumns("api_topic_config");
7762
7640
  const legacyAgentOverrides = configColumns.has("agent") ? new Map(db.query("SELECT topic_id, agent FROM api_topic_config WHERE agent IS NOT NULL").all().map((row) => [row.topic_id, row.agent])) : new Map;
7763
7641
  const previousForeignKeys = db.query("PRAGMA foreign_keys").get()?.foreign_keys;
7764
7642
  db.exec("PRAGMA foreign_keys = OFF");
@@ -7830,35 +7708,35 @@ function initializeApiTopicsSchema() {
7830
7708
  OR granted_by_topic_id NOT IN (SELECT id FROM api_topics)
7831
7709
  `);
7832
7710
  db.exec("DELETE FROM topic_members WHERE topic_id NOT IN (SELECT id FROM api_topics)");
7833
- if (!tableColumns2("api_topics").has("visibility")) {
7711
+ if (!tableColumns("api_topics").has("visibility")) {
7834
7712
  db.exec("ALTER TABLE api_topics ADD COLUMN visibility TEXT NOT NULL DEFAULT 'visible'");
7835
7713
  }
7836
- if (!tableColumns2("api_topics").has("surface")) {
7714
+ if (!tableColumns("api_topics").has("surface")) {
7837
7715
  db.exec("ALTER TABLE api_topics ADD COLUMN surface TEXT NOT NULL DEFAULT 'terminal'");
7838
7716
  }
7839
- if (!tableColumns2("api_topics").has("surface_scope")) {
7717
+ if (!tableColumns("api_topics").has("surface_scope")) {
7840
7718
  db.exec("ALTER TABLE api_topics ADD COLUMN surface_scope TEXT");
7841
7719
  }
7842
- if (tableColumns2("api_topics").has("access_mode")) {
7720
+ if (tableColumns("api_topics").has("access_mode")) {
7843
7721
  try {
7844
7722
  db.exec("ALTER TABLE api_topics DROP COLUMN access_mode");
7845
7723
  } catch (err) {
7846
7724
  logger.warn({ err }, "api_topics: could not drop the retired access_mode column");
7847
7725
  }
7848
7726
  }
7849
- if (!tableColumns2("api_topics").has("browser_profile")) {
7727
+ if (!tableColumns("api_topics").has("browser_profile")) {
7850
7728
  db.exec("ALTER TABLE api_topics ADD COLUMN browser_profile TEXT NOT NULL DEFAULT 'default'");
7851
7729
  }
7852
- if (!tableColumns2("api_topics").has("browser_profile_owner")) {
7730
+ if (!tableColumns("api_topics").has("browser_profile_owner")) {
7853
7731
  db.exec("ALTER TABLE api_topics ADD COLUMN browser_profile_owner TEXT");
7854
7732
  }
7855
- if (!tableColumns2("api_topics").has("subagent_report_mode")) {
7733
+ if (!tableColumns("api_topics").has("subagent_report_mode")) {
7856
7734
  db.exec("ALTER TABLE api_topics ADD COLUMN subagent_report_mode TEXT NOT NULL DEFAULT 'auto'");
7857
7735
  }
7858
- if (!tableColumns2("api_topics").has("memory_topic_id")) {
7736
+ if (!tableColumns("api_topics").has("memory_topic_id")) {
7859
7737
  db.exec("ALTER TABLE api_topics ADD COLUMN memory_topic_id TEXT");
7860
7738
  }
7861
- if (!tableColumns2("api_topics").has("memory_key")) {
7739
+ if (!tableColumns("api_topics").has("memory_key")) {
7862
7740
  db.exec("ALTER TABLE api_topics ADD COLUMN memory_key TEXT");
7863
7741
  }
7864
7742
  db.exec(`
@@ -8234,6 +8112,132 @@ var init_api_topics = __esm(async () => {
8234
8112
  registerStorageSchemaInitializer(initializeApiTopicsSchema, 20);
8235
8113
  });
8236
8114
 
8115
+ // ../../packages/core/src/storage/wiki.ts
8116
+ import { basename as basename2, dirname as dirname11, join as join15 } from "path";
8117
+ function getSharedWikiDir(workspaceDir = resolveStorageWorkspaceDir()) {
8118
+ return workspaceDir === resolveStorageWorkspaceDir() ? resolveStorageSharedWikiDir() : join15(workspaceDir, "wiki");
8119
+ }
8120
+ var init_wiki = __esm(async () => {
8121
+ await init_storage_host();
8122
+ });
8123
+
8124
+ // ../../packages/core/src/storage/api-topic-config.ts
8125
+ function tableColumns2(table) {
8126
+ const rows = db.query(`PRAGMA table_info(${table})`).all();
8127
+ return new Set(rows.map((row) => row.name));
8128
+ }
8129
+ function createCanonicalConfigTable(name = "api_topic_config") {
8130
+ db.exec(`
8131
+ CREATE TABLE IF NOT EXISTS ${name} (
8132
+ topic_id TEXT PRIMARY KEY REFERENCES api_topics(id) ON DELETE CASCADE,
8133
+ model TEXT,
8134
+ effort TEXT CHECK (effort IS NULL OR effort IN ('low','medium','high','xhigh','max')),
8135
+ mcp TEXT,
8136
+ agent_locked INTEGER NOT NULL DEFAULT 0 CHECK (agent_locked IN (0,1)),
8137
+ model_locked INTEGER NOT NULL DEFAULT 0 CHECK (model_locked IN (0,1)),
8138
+ effort_locked INTEGER NOT NULL DEFAULT 0 CHECK (effort_locked IN (0,1))
8139
+ )
8140
+ `);
8141
+ }
8142
+ function initializeApiTopicConfigSchema() {
8143
+ const existingConfigColumns = tableColumns2("api_topic_config");
8144
+ if (existingConfigColumns.size === 0) {
8145
+ createCanonicalConfigTable();
8146
+ } else if (existingConfigColumns.has("agent") || existingConfigColumns.has("agent_pinned") || existingConfigColumns.has("runtime_locked") || existingConfigColumns.has("model_pinned") || existingConfigColumns.has("effort_pinned") || existingConfigColumns.has("last_shown_agent")) {
8147
+ const topicColumns = tableColumns2("api_topics");
8148
+ const agentColumn = topicColumns.has("agent") ? "agent" : topicColumns.has("runtime_agent") ? "runtime_agent" : null;
8149
+ if (agentColumn && existingConfigColumns.has("agent")) {
8150
+ db.exec(`UPDATE api_topics
8151
+ SET ${agentColumn} = (
8152
+ SELECT c.agent FROM api_topic_config c WHERE c.topic_id = api_topics.id
8153
+ )
8154
+ WHERE ${agentColumn} IS NULL
8155
+ AND EXISTS (
8156
+ SELECT 1 FROM api_topic_config c
8157
+ WHERE c.topic_id = api_topics.id AND c.agent IS NOT NULL
8158
+ )`);
8159
+ }
8160
+ const previousForeignKeys = db.query("PRAGMA foreign_keys").get()?.foreign_keys;
8161
+ db.exec("PRAGMA foreign_keys = OFF");
8162
+ try {
8163
+ const agentLockSource = existingConfigColumns.has("runtime_locked") ? "runtime_locked" : existingConfigColumns.has("agent_pinned") ? "agent_pinned" : "0";
8164
+ const modelLockSource = existingConfigColumns.has("model_locked") ? "model_locked" : existingConfigColumns.has("model_pinned") ? "model_pinned" : "0";
8165
+ const effortLockSource = existingConfigColumns.has("effort_locked") ? "effort_locked" : existingConfigColumns.has("effort_pinned") ? "effort_pinned" : "0";
8166
+ db.transaction(() => {
8167
+ createCanonicalConfigTable("api_topic_config_next");
8168
+ db.exec(`
8169
+ INSERT INTO api_topic_config_next
8170
+ (topic_id, model, effort, mcp, agent_locked, model_locked, effort_locked)
8171
+ SELECT
8172
+ topic_id,
8173
+ model,
8174
+ effort,
8175
+ mcp,
8176
+ COALESCE(${agentLockSource}, 0),
8177
+ COALESCE(${modelLockSource}, 0),
8178
+ COALESCE(${effortLockSource}, 0)
8179
+ FROM api_topic_config
8180
+ WHERE EXISTS (SELECT 1 FROM api_topics t WHERE t.id = api_topic_config.topic_id)
8181
+ `);
8182
+ db.exec("DROP TABLE api_topic_config");
8183
+ db.exec("ALTER TABLE api_topic_config_next RENAME TO api_topic_config");
8184
+ })();
8185
+ } finally {
8186
+ db.exec(`PRAGMA foreign_keys = ${previousForeignKeys ? "ON" : "OFF"}`);
8187
+ }
8188
+ } else {
8189
+ createCanonicalConfigTable();
8190
+ }
8191
+ }
8192
+ function rowToConfig(r) {
8193
+ const cfg = {};
8194
+ if (r.model)
8195
+ cfg.model = r.model;
8196
+ if (r.effort)
8197
+ cfg.effort = r.effort;
8198
+ if (r.mcp) {
8199
+ try {
8200
+ const parsed = JSON.parse(r.mcp);
8201
+ if (Array.isArray(parsed))
8202
+ cfg.mcp = parsed;
8203
+ } catch {}
8204
+ }
8205
+ if (r.agent_locked)
8206
+ cfg.agentLocked = true;
8207
+ if (r.model_locked)
8208
+ cfg.modelLocked = true;
8209
+ if (r.effort_locked)
8210
+ cfg.effortLocked = true;
8211
+ return cfg;
8212
+ }
8213
+ function getApiTopicConfig(topicId) {
8214
+ const row = db.query("SELECT topic_id, model, effort, mcp, agent_locked, model_locked, effort_locked FROM api_topic_config WHERE topic_id = ?").get(topicId);
8215
+ if (!row)
8216
+ return;
8217
+ const config = rowToConfig(row);
8218
+ return Object.keys(config).length > 0 ? config : undefined;
8219
+ }
8220
+ function setApiTopicConfig(topicId, config) {
8221
+ db.query(`INSERT INTO api_topic_config
8222
+ (topic_id, model, effort, mcp, agent_locked, model_locked, effort_locked)
8223
+ VALUES (?, ?, ?, ?, ?, ?, ?)
8224
+ ON CONFLICT(topic_id) DO UPDATE SET
8225
+ model = excluded.model,
8226
+ effort = excluded.effort,
8227
+ mcp = excluded.mcp,
8228
+ agent_locked = excluded.agent_locked,
8229
+ model_locked = excluded.model_locked,
8230
+ effort_locked = excluded.effort_locked`).run(topicId, config.model ?? null, config.effort ?? null, config.mcp ? JSON.stringify(config.mcp) : null, config.agentLocked ? 1 : 0, config.modelLocked ? 1 : 0, config.effortLocked ? 1 : 0);
8231
+ }
8232
+ function deleteApiTopicConfig(topicId) {
8233
+ db.query("DELETE FROM api_topic_config WHERE topic_id = ?").run(topicId);
8234
+ }
8235
+ var init_api_topic_config = __esm(async () => {
8236
+ await init_forum_db();
8237
+ await init_storage_host();
8238
+ registerStorageSchemaInitializer(initializeApiTopicConfigSchema, 40);
8239
+ });
8240
+
8237
8241
  // ../../packages/core/src/topics/personal-general.ts
8238
8242
  import { randomUUID as randomUUID5 } from "crypto";
8239
8243
  function ensurePersonalGeneral(userId, surface, opts = {}) {
@@ -8348,6 +8352,7 @@ function createArchiverRuntime(host) {
8348
8352
  const runTurn = (params) => {
8349
8353
  const {
8350
8354
  userId,
8355
+ sourceTopicId,
8351
8356
  topicId,
8352
8357
  topicTitle,
8353
8358
  archivePath,
@@ -8355,6 +8360,7 @@ function createArchiverRuntime(host) {
8355
8360
  messageCount,
8356
8361
  mode = "deleted-topic"
8357
8362
  } = params;
8363
+ const generalTopicId = host.storage.getGeneralTopicId(userId, sourceTopicId);
8358
8364
  let definition;
8359
8365
  try {
8360
8366
  definition = getAgentDefinition();
@@ -8367,12 +8373,15 @@ function createArchiverRuntime(host) {
8367
8373
  const agent = params.agent ?? "claude";
8368
8374
  const model = params.model;
8369
8375
  const outputLanguage = resolveMemoryLanguage();
8376
+ const generalReplyInstruction = `Reply with a short completion message in ${outputLanguage} for display in #General. ` + "Do not include tool-call logs or raw transcripts \u2014 just briefly name the summary / brief / articles you saved.";
8370
8377
  const prompt = mode === "active-topic" ? [
8371
8378
  `Recent idle conversation snapshot of session "${topicTitle}". Extract durable memory from the archive below and save it into this topic's wiki.`,
8372
8379
  `archive_path: ${archivePath}`,
8373
8380
  ...rawArchivePaths.map((path) => `raw_archive_path: ${path}`),
8374
8381
  `wiki_dir: ${wikiDir}`,
8375
- `output_language: ${outputLanguage}`
8382
+ `output_language: ${outputLanguage}`,
8383
+ "",
8384
+ generalReplyInstruction
8376
8385
  ].join(`
8377
8386
  `) : [
8378
8387
  `Session "${topicTitle}" was deleted. Extract durable memory from the archive below and save it into the wiki.`,
@@ -8381,7 +8390,7 @@ function createArchiverRuntime(host) {
8381
8390
  `wiki_dir: ${wikiDir}`,
8382
8391
  `output_language: ${outputLanguage}`,
8383
8392
  "",
8384
- `Reply with a short completion message in ${outputLanguage} for display in #General. ` + "Do not include tool-call logs or raw transcripts \u2014 just briefly name the summary / brief / articles you saved."
8393
+ generalReplyInstruction
8385
8394
  ].join(`
8386
8395
  `);
8387
8396
  const activeSessionId = `memory:${host.config.createId()}`;
@@ -8509,10 +8518,8 @@ function createArchiverRuntime(host) {
8509
8518
  host.config.warn({ err, userId, topicTitle }, "archiver: settlement callback failed");
8510
8519
  }
8511
8520
  }
8512
- if (mode === "deleted-topic") {
8513
- const text = finalText.trimEnd();
8514
- finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs, ok, topicId, ok && text ? { text, agent, model, usage } : undefined);
8515
- }
8521
+ const text = finalText.trimEnd();
8522
+ finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs, ok, mode, topicId, generalTopicId, ok && text ? { text, agent, model, usage } : undefined);
8516
8523
  });
8517
8524
  topicQueues.set(safeTopic, work);
8518
8525
  work.finally(() => {
@@ -8558,14 +8565,14 @@ function findSummaryFile(storage, topicTitle, date, sinceMs, topicId) {
8558
8565
  }
8559
8566
  return best?.path ?? null;
8560
8567
  }
8561
- function finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs, ok, topicId, generalReply) {
8562
- const generalTopicId = host.storage.getGeneralTopicId(userId);
8568
+ function finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs, ok, mode, topicId, generalTopicId, generalReply) {
8569
+ const resolvedGeneralTopicId = generalTopicId ?? host.storage.getGeneralTopicId(userId, topicId);
8563
8570
  const date = host.config.now().toISOString().slice(0, 10);
8564
8571
  try {
8565
8572
  const summaryPath = ok ? findSummaryFile(host.storage, topicTitle, date, startMs, topicId) : null;
8566
8573
  const summaryMd = summaryPath ? host.storage.readTextFile(summaryPath) : "";
8567
8574
  const oneLine = summaryMd && distillOneLine(summaryMd) || `Archived ${messageCount} messages`;
8568
- const prev = host.storage.getTopicBrief(generalTopicId);
8575
+ const prev = host.storage.getTopicBrief(resolvedGeneralTopicId);
8569
8576
  const prevEntries = (prev?.briefMd ?? "").split(`
8570
8577
  `).map((l) => l.trim()).filter((l) => l.startsWith("- "));
8571
8578
  const newEntry = `- **${topicTitle}** (${date}): ${oneLine}`;
@@ -8575,12 +8582,12 @@ function finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs,
8575
8582
  ].slice(0, MAX_BRIEF_ENTRIES);
8576
8583
  const briefMd = `# Workspace Memory Hub
8577
8584
 
8578
- Recent memory digest distilled from deleted topics. Use \`wiki_query\` for details.
8585
+ Recent memory digest distilled from archived topics (deletions and idle/session-reset snapshots). Use \`wiki_query\` for details.
8579
8586
 
8580
8587
  ## Recent Archives
8581
8588
  ${rolled.join(`
8582
8589
  `)}`;
8583
- host.storage.setTopicBrief(generalTopicId, {
8590
+ host.storage.setTopicBrief(resolvedGeneralTopicId, {
8584
8591
  briefMd,
8585
8592
  ...summaryMd ? { latestSummaryMd: summaryMd, summaryDate: date } : {}
8586
8593
  });
@@ -8590,7 +8597,7 @@ ${rolled.join(`
8590
8597
  }
8591
8598
  try {
8592
8599
  const replyText = generalReply?.text.trim();
8593
- const text = replyText || (ok ? `\uD83D\uDDC2 Topic "${topicTitle}" was deleted and archived into #General memory.` : `\uD83D\uDDC2 Archived topic "${topicTitle}" (summary extraction failed \u2014 raw log preserved in wiki/archive).`);
8600
+ const text = replyText || (mode === "deleted-topic" ? ok ? `\uD83D\uDDC2 Topic "${topicTitle}" was deleted and archived into #General memory.` : `\uD83D\uDDC2 Archived topic "${topicTitle}" (summary extraction failed \u2014 raw log preserved in wiki/archive).` : ok ? `\uD83D\uDDC2 Session "${topicTitle}" was snapshotted and archived into #General memory.` : `\uD83D\uDDC2 Archived a snapshot of "${topicTitle}" (summary extraction failed \u2014 raw log preserved in wiki/archive).`);
8594
8601
  const replyMeta = generalReply && replyText ? {
8595
8602
  authorId: "ai",
8596
8603
  agentType: generalReply.agent,
@@ -8599,13 +8606,13 @@ ${rolled.join(`
8599
8606
  } : { authorId: "system" };
8600
8607
  const msg = {
8601
8608
  id: host.config.createId(),
8602
- topicId: generalTopicId,
8609
+ topicId: resolvedGeneralTopicId,
8603
8610
  text,
8604
8611
  ...replyMeta,
8605
8612
  createdAt: host.config.now().toISOString()
8606
8613
  };
8607
8614
  host.messaging.appendMessage(msg);
8608
- host.messaging.broadcastMessage(generalTopicId, msg);
8615
+ host.messaging.broadcastMessage(resolvedGeneralTopicId, msg);
8609
8616
  } catch (err) {
8610
8617
  host.config.warn({ err, topicTitle }, "archiver: failed to post #General notification");
8611
8618
  }
@@ -8624,6 +8631,7 @@ var init_archiver = __esm(async () => {
8624
8631
  init_background_session_policy();
8625
8632
  await init_api_messages();
8626
8633
  await init_api_topic_brief();
8634
+ await init_api_topics();
8627
8635
  await init_wiki();
8628
8636
  init_wiki_summary_names();
8629
8637
  await init_personal_general();
@@ -8635,7 +8643,12 @@ var init_archiver = __esm(async () => {
8635
8643
  readTextFile: (path) => readFileSync13(path, "utf-8"),
8636
8644
  fileSize: (path) => statSync6(path).size,
8637
8645
  fileModifiedAt: (path) => statSync6(path).mtimeMs,
8638
- getGeneralTopicId: (userId) => ensurePersonalGeneral(userId).id,
8646
+ getGeneralTopicId: (userId, sourceTopicId) => {
8647
+ const source = sourceTopicId ? getTopic(sourceTopicId) : null;
8648
+ return source ? ensurePersonalGeneral(userId, source.surface, {
8649
+ surfaceScope: source.surfaceScope ?? null
8650
+ }).id : ensurePersonalGeneral(userId).id;
8651
+ },
8639
8652
  getTopicBrief,
8640
8653
  setTopicBrief: (topicId, fields) => {
8641
8654
  setTopicBrief(topicId, fields);
@@ -9823,6 +9836,7 @@ function archiveActiveTopicForMemory(topicId, userId, options) {
9823
9836
  const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
9824
9837
  const launched = (options.launchArchiver ?? runArchiverTurn)({
9825
9838
  userId,
9839
+ sourceTopicId: topic.id,
9826
9840
  topicId: memoryTopic.id,
9827
9841
  topicTitle: memoryTitle,
9828
9842
  archivePath: job.archivePath,
@@ -14497,7 +14511,8 @@ async function deleteTopicCascadeImpl(topic, userId, options, deletingAncestorId
14497
14511
  const memoryTopic = topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
14498
14512
  const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
14499
14513
  runArchiverTurn({
14500
- userId,
14514
+ userId: options.memoryUserId ?? userId,
14515
+ sourceTopicId: topicId,
14501
14516
  ...memoryTopic.id !== topicId && !deletingTopicIds.has(memoryTopic.id) ? { topicId: memoryTopic.id } : {},
14502
14517
  topicTitle: memoryTitle,
14503
14518
  archivePath: archived.path,
@@ -15751,7 +15766,7 @@ ${JSON.stringify(event.input ?? {})}`);
15751
15766
  }
15752
15767
  }
15753
15768
  if (!silent) {
15754
- scheduleIdleArchiveForTopic(topicId, userId);
15769
+ scheduleIdleArchiveForTopic(topicId, execution?.actorUserId ?? userId);
15755
15770
  hub.broadcastDone(topicId, queryId, event.usage ? {
15756
15771
  input: event.usage.inputTokens,
15757
15772
  output: event.usage.outputTokens,
@@ -17122,7 +17137,13 @@ function startAiTurn(params) {
17122
17137
  WsHub.get().broadcastTyping(topicId, "ai");
17123
17138
  WsHub.get().broadcastAiActive(topicId, queryId);
17124
17139
  }
17125
- streamAgentEvents(topicId, topic.title, queryId, events, control, agentKind, resolvedModel, resolvedEffort, userId, !sessionRetried, onSessionId, { silent, peerBridge, sourceNode, ...threadRootId ? { threadRootId } : {} }).then(async (streamOutcome) => {
17140
+ streamAgentEvents(topicId, topic.title, queryId, events, control, agentKind, resolvedModel, resolvedEffort, userId, !sessionRetried, onSessionId, {
17141
+ silent,
17142
+ actorUserId: params.actorUserId,
17143
+ peerBridge,
17144
+ sourceNode,
17145
+ ...threadRootId ? { threadRootId } : {}
17146
+ }).then(async (streamOutcome) => {
17126
17147
  let outcome = streamOutcome;
17127
17148
  if (outcome.kind === "session-expired") {
17128
17149
  const retry = resolveSessionRetry({
@@ -19294,4 +19315,4 @@ export {
19294
19315
  DEFAULT_SELF_CONFIG_PRODUCT
19295
19316
  };
19296
19317
 
19297
- //# debugId=E4CEF015C435076364756E2164756E21
19318
+ //# debugId=E2BEA0E09AFF549864756E2164756E21