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
package/dist/main.js CHANGED
@@ -3674,7 +3674,7 @@ var exports_version = {};
3674
3674
  __export(exports_version, {
3675
3675
  NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
3676
3676
  });
3677
- var NEGOTIUM_VERSION = "0.6.5";
3677
+ var NEGOTIUM_VERSION = "0.6.7";
3678
3678
 
3679
3679
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
3680
3680
  import { spawn as spawn2 } from "child_process";
@@ -4519,6 +4519,16 @@ var init_task_events = __esm(async () => {
4519
4519
  defaultTaskEventHost = { readTasks, taskFileMtimeNs, taskScopeKey };
4520
4520
  });
4521
4521
 
4522
+ // ../../packages/core/src/platform/constants.ts
4523
+ 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";
4524
+ var init_constants = __esm(() => {
4525
+ RESERVED_TOPIC_NAMES = new Set([
4526
+ FROM_AUTO_CONTINUE,
4527
+ FROM_SELF_SCHEDULE,
4528
+ "general"
4529
+ ]);
4530
+ });
4531
+
4522
4532
  // ../../packages/core/src/storage/conversations.ts
4523
4533
  var exports_conversations = {};
4524
4534
  __export(exports_conversations, {
@@ -4548,12 +4558,15 @@ import { dirname as dirname8, join as join13 } from "path";
4548
4558
  function conversationDir(_userId) {
4549
4559
  return join13(resolveStorageDataDir(), "conversations");
4550
4560
  }
4551
- function topicFilename(topicName) {
4561
+ function topicFilename(userId, topicName) {
4552
4562
  const t = sanitizeTopicName(topicName, true);
4553
- return `${t}.jsonl`;
4563
+ const owner = String(userId);
4564
+ if (t !== MANAGER_TOPIC_LOG_NAME || owner === NODE_LOCAL_USER_ID)
4565
+ return `${t}.jsonl`;
4566
+ return `${t}.${sanitizeId(owner)}.jsonl`;
4554
4567
  }
4555
4568
  function getConversationPath(userId, topicName) {
4556
- return join13(conversationDir(userId), topicFilename(topicName));
4569
+ return join13(conversationDir(userId), topicFilename(userId, topicName));
4557
4570
  }
4558
4571
  function getActiveConversationPath(userId, topicName) {
4559
4572
  const rawPath = getConversationPath(userId, topicName);
@@ -4687,6 +4700,7 @@ function findLastSessionIdForAgent(entries, agent) {
4687
4700
  }
4688
4701
  var ConversationLogDivergedError;
4689
4702
  var init_conversations = __esm(async () => {
4703
+ init_constants();
4690
4704
  init_jsonl();
4691
4705
  init_logger();
4692
4706
  await init_storage_host();
@@ -7769,16 +7783,6 @@ var init_api_topic_config = __esm(async () => {
7769
7783
  registerStorageSchemaInitializer(initializeApiTopicConfigSchema, 40);
7770
7784
  });
7771
7785
 
7772
- // ../../packages/core/src/platform/constants.ts
7773
- var FROM_AUTO_CONTINUE = "auto-continue", FROM_SELF_SCHEDULE = "self-schedule", RESERVED_TOPIC_NAMES, GENERAL_TOPIC_ID = "general";
7774
- var init_constants = __esm(() => {
7775
- RESERVED_TOPIC_NAMES = new Set([
7776
- FROM_AUTO_CONTINUE,
7777
- FROM_SELF_SCHEDULE,
7778
- "general"
7779
- ]);
7780
- });
7781
-
7782
7786
  // ../../packages/core/src/storage/api-topics.ts
7783
7787
  var exports_api_topics = {};
7784
7788
  __export(exports_api_topics, {
@@ -12475,6 +12479,7 @@ function createArchiverRuntime(host) {
12475
12479
  const runTurn = (params) => {
12476
12480
  const {
12477
12481
  userId,
12482
+ sourceTopicId,
12478
12483
  topicId,
12479
12484
  topicTitle,
12480
12485
  archivePath,
@@ -12482,6 +12487,7 @@ function createArchiverRuntime(host) {
12482
12487
  messageCount,
12483
12488
  mode = "deleted-topic"
12484
12489
  } = params;
12490
+ const generalTopicId = host.storage.getGeneralTopicId(userId, sourceTopicId);
12485
12491
  let definition;
12486
12492
  try {
12487
12493
  definition = getAgentDefinition();
@@ -12494,12 +12500,15 @@ function createArchiverRuntime(host) {
12494
12500
  const agent = params.agent ?? "claude";
12495
12501
  const model = params.model;
12496
12502
  const outputLanguage = resolveMemoryLanguage();
12503
+ 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.";
12497
12504
  const prompt = mode === "active-topic" ? [
12498
12505
  `Recent idle conversation snapshot of session "${topicTitle}". Extract durable memory from the archive below and save it into this topic's wiki.`,
12499
12506
  `archive_path: ${archivePath}`,
12500
12507
  ...rawArchivePaths.map((path) => `raw_archive_path: ${path}`),
12501
12508
  `wiki_dir: ${wikiDir}`,
12502
- `output_language: ${outputLanguage}`
12509
+ `output_language: ${outputLanguage}`,
12510
+ "",
12511
+ generalReplyInstruction
12503
12512
  ].join(`
12504
12513
  `) : [
12505
12514
  `Session "${topicTitle}" was deleted. Extract durable memory from the archive below and save it into the wiki.`,
@@ -12508,7 +12517,7 @@ function createArchiverRuntime(host) {
12508
12517
  `wiki_dir: ${wikiDir}`,
12509
12518
  `output_language: ${outputLanguage}`,
12510
12519
  "",
12511
- `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."
12520
+ generalReplyInstruction
12512
12521
  ].join(`
12513
12522
  `);
12514
12523
  const activeSessionId = `memory:${host.config.createId()}`;
@@ -12636,10 +12645,8 @@ function createArchiverRuntime(host) {
12636
12645
  host.config.warn({ err, userId, topicTitle }, "archiver: settlement callback failed");
12637
12646
  }
12638
12647
  }
12639
- if (mode === "deleted-topic") {
12640
- const text = finalText.trimEnd();
12641
- finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs, ok, topicId, ok && text ? { text, agent, model, usage } : undefined);
12642
- }
12648
+ const text = finalText.trimEnd();
12649
+ finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs, ok, mode, topicId, generalTopicId, ok && text ? { text, agent, model, usage } : undefined);
12643
12650
  });
12644
12651
  topicQueues.set(safeTopic, work);
12645
12652
  work.finally(() => {
@@ -12685,14 +12692,14 @@ function findSummaryFile(storage, topicTitle, date, sinceMs, topicId) {
12685
12692
  }
12686
12693
  return best?.path ?? null;
12687
12694
  }
12688
- function finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs, ok, topicId, generalReply) {
12689
- const generalTopicId = host.storage.getGeneralTopicId(userId);
12695
+ function finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs, ok, mode, topicId, generalTopicId, generalReply) {
12696
+ const resolvedGeneralTopicId = generalTopicId ?? host.storage.getGeneralTopicId(userId, topicId);
12690
12697
  const date = host.config.now().toISOString().slice(0, 10);
12691
12698
  try {
12692
12699
  const summaryPath = ok ? findSummaryFile(host.storage, topicTitle, date, startMs, topicId) : null;
12693
12700
  const summaryMd = summaryPath ? host.storage.readTextFile(summaryPath) : "";
12694
12701
  const oneLine = summaryMd && distillOneLine(summaryMd) || `Archived ${messageCount} messages`;
12695
- const prev = host.storage.getTopicBrief(generalTopicId);
12702
+ const prev = host.storage.getTopicBrief(resolvedGeneralTopicId);
12696
12703
  const prevEntries = (prev?.briefMd ?? "").split(`
12697
12704
  `).map((l) => l.trim()).filter((l) => l.startsWith("- "));
12698
12705
  const newEntry = `- **${topicTitle}** (${date}): ${oneLine}`;
@@ -12702,12 +12709,12 @@ function finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs,
12702
12709
  ].slice(0, MAX_BRIEF_ENTRIES);
12703
12710
  const briefMd = `# Workspace Memory Hub
12704
12711
 
12705
- Recent memory digest distilled from deleted topics. Use \`wiki_query\` for details.
12712
+ Recent memory digest distilled from archived topics (deletions and idle/session-reset snapshots). Use \`wiki_query\` for details.
12706
12713
 
12707
12714
  ## Recent Archives
12708
12715
  ${rolled.join(`
12709
12716
  `)}`;
12710
- host.storage.setTopicBrief(generalTopicId, {
12717
+ host.storage.setTopicBrief(resolvedGeneralTopicId, {
12711
12718
  briefMd,
12712
12719
  ...summaryMd ? { latestSummaryMd: summaryMd, summaryDate: date } : {}
12713
12720
  });
@@ -12717,7 +12724,7 @@ ${rolled.join(`
12717
12724
  }
12718
12725
  try {
12719
12726
  const replyText = generalReply?.text.trim();
12720
- 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).`);
12727
+ 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).`);
12721
12728
  const replyMeta = generalReply && replyText ? {
12722
12729
  authorId: "ai",
12723
12730
  agentType: generalReply.agent,
@@ -12726,13 +12733,13 @@ ${rolled.join(`
12726
12733
  } : { authorId: "system" };
12727
12734
  const msg = {
12728
12735
  id: host.config.createId(),
12729
- topicId: generalTopicId,
12736
+ topicId: resolvedGeneralTopicId,
12730
12737
  text,
12731
12738
  ...replyMeta,
12732
12739
  createdAt: host.config.now().toISOString()
12733
12740
  };
12734
12741
  host.messaging.appendMessage(msg);
12735
- host.messaging.broadcastMessage(generalTopicId, msg);
12742
+ host.messaging.broadcastMessage(resolvedGeneralTopicId, msg);
12736
12743
  } catch (err) {
12737
12744
  host.config.warn({ err, topicTitle }, "archiver: failed to post #General notification");
12738
12745
  }
@@ -12754,6 +12761,7 @@ var init_archiver = __esm(async () => {
12754
12761
  init_background_session_policy();
12755
12762
  await init_api_messages();
12756
12763
  await init_api_topic_brief();
12764
+ await init_api_topics();
12757
12765
  await init_wiki();
12758
12766
  init_wiki_summary_names();
12759
12767
  await init_personal_general();
@@ -12765,7 +12773,12 @@ var init_archiver = __esm(async () => {
12765
12773
  readTextFile: (path) => readFileSync14(path, "utf-8"),
12766
12774
  fileSize: (path) => statSync7(path).size,
12767
12775
  fileModifiedAt: (path) => statSync7(path).mtimeMs,
12768
- getGeneralTopicId: (userId) => ensurePersonalGeneral(userId).id,
12776
+ getGeneralTopicId: (userId, sourceTopicId) => {
12777
+ const source = sourceTopicId ? getTopic(sourceTopicId) : null;
12778
+ return source ? ensurePersonalGeneral(userId, source.surface, {
12779
+ surfaceScope: source.surfaceScope ?? null
12780
+ }).id : ensurePersonalGeneral(userId).id;
12781
+ },
12769
12782
  getTopicBrief,
12770
12783
  setTopicBrief: (topicId, fields) => {
12771
12784
  setTopicBrief(topicId, fields);
@@ -13265,6 +13278,7 @@ function archiveActiveTopicForMemory(topicId, userId, options) {
13265
13278
  const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
13266
13279
  const launched = (options.launchArchiver ?? runArchiverTurn)({
13267
13280
  userId,
13281
+ sourceTopicId: topic.id,
13268
13282
  topicId: memoryTopic.id,
13269
13283
  topicTitle: memoryTitle,
13270
13284
  archivePath: job.archivePath,
@@ -14069,7 +14083,7 @@ async function restartTopicSession(topicId, userId, reason = "topic-session-rest
14069
14083
  const memoryArchiveSettled = new Promise((resolve15) => {
14070
14084
  settleMemoryArchive = resolve15;
14071
14085
  });
14072
- const archiveStatus = (options.archiveMemory ?? archiveActiveTopicForMemory)(topicId, userId, {
14086
+ const archiveStatus = (options.archiveMemory ?? archiveActiveTopicForMemory)(topicId, options.memoryUserId ?? userId, {
14073
14087
  reason: "reset",
14074
14088
  minMessages: 1,
14075
14089
  minExchanges: MIN_MEMORY_ARCHIVE_EXCHANGES,
@@ -17060,7 +17074,8 @@ async function deleteTopicCascadeImpl(topic, userId, options, deletingAncestorId
17060
17074
  const memoryTopic = topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
17061
17075
  const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
17062
17076
  runArchiverTurn({
17063
- userId,
17077
+ userId: options.memoryUserId ?? userId,
17078
+ sourceTopicId: topicId,
17064
17079
  ...memoryTopic.id !== topicId && !deletingTopicIds.has(memoryTopic.id) ? { topicId: memoryTopic.id } : {},
17065
17080
  topicTitle: memoryTitle,
17066
17081
  archivePath: archived.path,
@@ -18330,7 +18345,7 @@ ${JSON.stringify(event.input ?? {})}`);
18330
18345
  }
18331
18346
  }
18332
18347
  if (!silent) {
18333
- scheduleIdleArchiveForTopic(topicId, userId);
18348
+ scheduleIdleArchiveForTopic(topicId, execution?.actorUserId ?? userId);
18334
18349
  hub.broadcastDone(topicId, queryId, event.usage ? {
18335
18350
  input: event.usage.inputTokens,
18336
18351
  output: event.usage.outputTokens,
@@ -19716,7 +19731,13 @@ function startAiTurn(params) {
19716
19731
  WsHub.get().broadcastTyping(topicId, "ai");
19717
19732
  WsHub.get().broadcastAiActive(topicId, queryId);
19718
19733
  }
19719
- streamAgentEvents(topicId, topic.title, queryId, events, control, agentKind, resolvedModel, resolvedEffort, userId, !sessionRetried, onSessionId, { silent, peerBridge, sourceNode, ...threadRootId ? { threadRootId } : {} }).then(async (streamOutcome) => {
19734
+ streamAgentEvents(topicId, topic.title, queryId, events, control, agentKind, resolvedModel, resolvedEffort, userId, !sessionRetried, onSessionId, {
19735
+ silent,
19736
+ actorUserId: params.actorUserId,
19737
+ peerBridge,
19738
+ sourceNode,
19739
+ ...threadRootId ? { threadRootId } : {}
19740
+ }).then(async (streamOutcome) => {
19720
19741
  let outcome = streamOutcome;
19721
19742
  if (outcome.kind === "session-expired") {
19722
19743
  const retry = resolveSessionRetry({
@@ -21401,6 +21422,7 @@ var init_topic_service = __esm(async () => {
21401
21422
  }
21402
21423
  await deleteTopicCascade(topic, params.userId, {
21403
21424
  ...params.force !== undefined ? { force: params.force } : {},
21425
+ ...params.memoryUserId !== undefined ? { memoryUserId: params.memoryUserId } : {},
21404
21426
  ...params.allowManager !== undefined ? { allowManager: params.allowManager } : {},
21405
21427
  ...params.skipArchive !== undefined ? { skipArchive: params.skipArchive } : {},
21406
21428
  ...params.purgeLogs !== undefined ? { purgeLogs: params.purgeLogs } : {}
@@ -21408,7 +21430,9 @@ var init_topic_service = __esm(async () => {
21408
21430
  },
21409
21431
  async reset(params) {
21410
21432
  ownerTopic(params.topicId, params.userId);
21411
- return restartTopicSession(params.topicId, params.userId, params.reason);
21433
+ return restartTopicSession(params.topicId, params.userId, params.reason, {
21434
+ memoryUserId: params.actorUserId
21435
+ });
21412
21436
  },
21413
21437
  async compact(params) {
21414
21438
  ownerTopic(params.topicId, params.userId);
@@ -23679,6 +23703,7 @@ __export(exports_src, {
23679
23703
  PROCESS_LEASE_STALE_MS: () => PROCESS_LEASE_STALE_MS,
23680
23704
  PROCESS_LEASE_HEARTBEAT_MS: () => PROCESS_LEASE_HEARTBEAT_MS,
23681
23705
  OPTIONAL_FORUM_MCP_SERVERS: () => OPTIONAL_FORUM_MCP_SERVERS,
23706
+ NODE_LOCAL_USER_ID: () => NODE_LOCAL_USER_ID,
23682
23707
  NODE_CONTROL_TOKEN: () => NODE_CONTROL_TOKEN,
23683
23708
  NEGOTIUM_VERSION: () => NEGOTIUM_VERSION,
23684
23709
  NEGOTIUM_PORT: () => NEGOTIUM_PORT,
@@ -24509,6 +24534,7 @@ __export(exports_mcp_runtime_host, {
24509
24534
  RUNTIME_MCP_BASE_PATH: () => RUNTIME_MCP_BASE_PATH,
24510
24535
  REQUIRED_FORUM_MCP_SERVERS: () => REQUIRED_FORUM_MCP_SERVERS,
24511
24536
  OPTIONAL_FORUM_MCP_SERVERS: () => OPTIONAL_FORUM_MCP_SERVERS,
24537
+ NODE_LOCAL_USER_ID: () => NODE_LOCAL_USER_ID,
24512
24538
  HOSTED_MCP_SURFACES: () => HOSTED_MCP_SURFACES,
24513
24539
  FROM_AUTO_CONTINUE: () => FROM_AUTO_CONTINUE,
24514
24540
  EFFORT_VALUES: () => EFFORT_VALUES
@@ -24549,12 +24575,31 @@ import { z as z6 } from "zod";
24549
24575
  function callerSurface(ctx) {
24550
24576
  return getTopic(ctx.topicId)?.surface ?? defaultTopicSurface();
24551
24577
  }
24578
+ function callerSurfaceScope(ctx) {
24579
+ return getTopic(ctx.topicId)?.surfaceScope ?? null;
24580
+ }
24581
+ function topicsForCaller(ctx) {
24582
+ const surface = callerSurface(ctx);
24583
+ if (surface !== "otium") {
24584
+ return getTopics({ surface }).filter((topic) => isParticipant(topic, ctx.userId));
24585
+ }
24586
+ return getTopics({ surface, surfaceScope: callerSurfaceScope(ctx) }).filter((topic) => topic.kind !== "manager" || topic.id === ctx.topicId || isParticipant(topic, ctx.userId));
24587
+ }
24552
24588
  function resolveTopicForUser(ctx, ref) {
24553
24589
  const trimmed = ref.trim();
24554
24590
  if (!trimmed)
24555
24591
  return { error: "Error: topic is required." };
24556
24592
  const notFound = `Error: topic '${trimmed}' not found (or not uniquely named). Use list_topics to see available topics.`;
24557
24593
  const surface = callerSurface(ctx);
24594
+ if (surface === "otium") {
24595
+ const visible = topicsForCaller(ctx);
24596
+ const byId2 = visible.find((topic) => topic.id === trimmed);
24597
+ if (byId2)
24598
+ return { topic: byId2 };
24599
+ const wanted = trimmed.toLowerCase();
24600
+ const byTitle2 = visible.filter((topic) => topic.title.toLowerCase() === wanted);
24601
+ return byTitle2.length === 1 ? { topic: byTitle2[0] } : { error: notFound };
24602
+ }
24558
24603
  const byId = getTopic(trimmed);
24559
24604
  if (byId) {
24560
24605
  if (!isParticipant(byId, ctx.userId) || byId.surface !== surface)
@@ -24566,6 +24611,12 @@ function resolveTopicForUser(ctx, ref) {
24566
24611
  return { topic: byTitle };
24567
24612
  return { error: notFound };
24568
24613
  }
24614
+ function actingUserFor(ctx, target) {
24615
+ const owner = target.participants.find((p) => p.role === "owner")?.userId;
24616
+ if (callerSurface(ctx) !== "otium" || owner !== NODE_LOCAL_USER_ID)
24617
+ return ctx.userId;
24618
+ return owner;
24619
+ }
24569
24620
  function describeTopic(topic) {
24570
24621
  const running = getRoomQuery(topic.id) ? "turn running" : "idle";
24571
24622
  const agent = topic.agent ? `agent: ${topic.agent}` : "no agent";
@@ -24604,8 +24655,8 @@ function registerNodeTools(server, ctx) {
24604
24655
  return errorResult(`Error: failed to register topic: ${errMsg(err2)}`);
24605
24656
  }
24606
24657
  });
24607
- server.tool("list_topics", "List the calling user's topics on this negotium node: title, id, kind, agent, and whether a turn is currently running.", {}, async () => {
24608
- const topics = getTopics({ surface: callerSurface(ctx) }).filter((topic) => isParticipant(topic, ctx.userId));
24658
+ server.tool("list_topics", "List the topics this turn can reach on this negotium node: title, id, kind, agent, and whether a turn is currently running.", {}, async () => {
24659
+ const topics = topicsForCaller(ctx);
24609
24660
  if (topics.length === 0) {
24610
24661
  return textResult("No topics found. Use register_topic to create one.");
24611
24662
  }
@@ -24650,7 +24701,7 @@ function registerNodeTools(server, ctx) {
24650
24701
  if (target.kind === "manager") {
24651
24702
  return errorResult("Error: manager rooms are system-managed and cannot be restarted.");
24652
24703
  }
24653
- const result = await restartTopicSession(target.id, ctx.userId, "runtime-mcp-session-reset");
24704
+ const result = await restartTopicSession(target.id, actingUserFor(ctx, target), "runtime-mcp-session-reset");
24654
24705
  return result.isError ? errorResult(`Error: ${result.text}`) : textResult(result.text);
24655
24706
  });
24656
24707
  server.tool("delete_topic", "Delete a topic owned by the calling user after archiving its conversation history. Deletion is blocked " + "when the archive cannot be written; pass force: true only as an explicit escape hatch that accepts losing history.", {
@@ -24667,12 +24718,13 @@ function registerNodeTools(server, ctx) {
24667
24718
  if (target.kind === "manager") {
24668
24719
  return errorResult("Error: manager rooms are system-managed and cannot be deleted.");
24669
24720
  }
24670
- const isOwner = target.participants.some((participant) => participant.userId === ctx.userId && participant.role === "owner");
24721
+ const actingUser = actingUserFor(ctx, target);
24722
+ const isOwner = target.participants.some((participant) => participant.userId === actingUser && participant.role === "owner");
24671
24723
  if (!isOwner) {
24672
24724
  return errorResult("Error: only the topic owner can delete it.");
24673
24725
  }
24674
24726
  try {
24675
- await deleteTopicCascade(target, ctx.userId, { force: force === true });
24727
+ await deleteTopicCascade(target, actingUser, { force: force === true });
24676
24728
  } catch (err2) {
24677
24729
  if (err2 instanceof TopicArchiveRequiredError) {
24678
24730
  return errorResult([
@@ -32877,6 +32929,34 @@ function createNodeControlHandler(options) {
32877
32929
  });
32878
32930
  return Response.json({ ok: true, v: NODE_RUNTIME_CONTRACT_VERSION, topic }, { status: 201 });
32879
32931
  }
32932
+ const runtimeDeriveMatch = runtimePath.match(/^\/topics\/([^/]+)\/derive$/);
32933
+ if (runtimeDeriveMatch && req.method === "POST") {
32934
+ const body = await bodyRecord(req);
32935
+ if (body.v !== NODE_RUNTIME_CONTRACT_VERSION)
32936
+ return jsonError(400, "Unsupported v");
32937
+ const topicId = decodeURIComponent(runtimeDeriveMatch[1]);
32938
+ const userId = requiredText(body.userId, "userId");
32939
+ if (typeof body.copyHistory !== "boolean") {
32940
+ return jsonError(400, "copyHistory must be a boolean");
32941
+ }
32942
+ if (body.name !== undefined && typeof body.name !== "string") {
32943
+ return jsonError(400, "name must be a string");
32944
+ }
32945
+ const source = topicForUser(topicId, userId);
32946
+ if (!source || source.kind === "manager" || !topicInRequestScope(req, source)) {
32947
+ return jsonError(404, "Topic not found");
32948
+ }
32949
+ const name = typeof body.name === "string" && body.name.trim() ? body.name.trim() : "";
32950
+ const derived = await topicService.derive({
32951
+ sourceTopicId: topicId,
32952
+ userId,
32953
+ copyHistory: body.copyHistory,
32954
+ ...name ? { name } : {}
32955
+ });
32956
+ if (!derived)
32957
+ return jsonError(500, "Failed to derive topic");
32958
+ return Response.json({ ok: true, v: NODE_RUNTIME_CONTRACT_VERSION, topic: derived }, { status: 201 });
32959
+ }
32880
32960
  const importMatch = runtimePath.match(/^\/topics\/([^/]+)\/import$/);
32881
32961
  if (importMatch && req.method === "POST") {
32882
32962
  const body = await bodyRecord(req);
@@ -32952,10 +33032,12 @@ function createNodeControlHandler(options) {
32952
33032
  if (!topic || !topicInRequestScope(req, topic))
32953
33033
  return jsonError(404, "Topic not found");
32954
33034
  const userId = requiredText(body.userId, "userId");
33035
+ const actorUserId = body.actorUserId === undefined ? undefined : requiredText(body.actorUserId, "actorUserId");
32955
33036
  const reason = body.reason === undefined ? undefined : requiredText(body.reason, "reason");
32956
33037
  const result = await topicService.reset({
32957
33038
  topicId,
32958
33039
  userId,
33040
+ actorUserId,
32959
33041
  reason: reason ?? "runtime-contract-session-reset"
32960
33042
  });
32961
33043
  if (result.isError)
@@ -32976,10 +33058,12 @@ function createNodeControlHandler(options) {
32976
33058
  if (!topic || !topicInRequestScope(req, topic))
32977
33059
  return jsonError(404, "Topic not found");
32978
33060
  const userId = requiredText(body.userId, "userId");
33061
+ const actorUserId = body.actorUserId === undefined ? undefined : requiredText(body.actorUserId, "actorUserId");
32979
33062
  const reason = body.reason === undefined ? undefined : requiredText(body.reason, "reason");
32980
33063
  const result = await topicService.compact({
32981
33064
  topicId,
32982
33065
  userId,
33066
+ actorUserId,
32983
33067
  reason: reason ?? "runtime-contract-session-compact",
32984
33068
  compactSession: options.compactSession
32985
33069
  });
@@ -33067,8 +33151,9 @@ function createNodeControlHandler(options) {
33067
33151
  if (!topic || !topicInRequestScope(req, topic))
33068
33152
  return jsonError(404, "Topic not found");
33069
33153
  const userId = requiredText(url.searchParams.get("user"), "user");
33154
+ const actorUserId = url.searchParams.get("actor")?.trim() || undefined;
33070
33155
  try {
33071
- await topicService.delete({ topicId, userId });
33156
+ await topicService.delete({ topicId, userId, memoryUserId: actorUserId });
33072
33157
  } catch (err2) {
33073
33158
  if (err2 instanceof TopicServiceError) {
33074
33159
  const status = err2.code === "TOPIC_NOT_FOUND" ? 404 : err2.code === "TOPIC_FORBIDDEN" ? 403 : 400;
@@ -48666,4 +48751,4 @@ switch (command) {
48666
48751
  }
48667
48752
  }
48668
48753
 
48669
- //# debugId=0FC049F7BD1544D864756E2164756E21
48754
+ //# debugId=E5965A3EFE680BBE64756E2164756E21