negotium 0.6.4 → 0.6.6

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/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.4";
3677
+ var NEGOTIUM_VERSION = "0.6.6";
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, {
@@ -12494,12 +12498,15 @@ function createArchiverRuntime(host) {
12494
12498
  const agent = params.agent ?? "claude";
12495
12499
  const model = params.model;
12496
12500
  const outputLanguage = resolveMemoryLanguage();
12501
+ 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
12502
  const prompt = mode === "active-topic" ? [
12498
12503
  `Recent idle conversation snapshot of session "${topicTitle}". Extract durable memory from the archive below and save it into this topic's wiki.`,
12499
12504
  `archive_path: ${archivePath}`,
12500
12505
  ...rawArchivePaths.map((path) => `raw_archive_path: ${path}`),
12501
12506
  `wiki_dir: ${wikiDir}`,
12502
- `output_language: ${outputLanguage}`
12507
+ `output_language: ${outputLanguage}`,
12508
+ "",
12509
+ generalReplyInstruction
12503
12510
  ].join(`
12504
12511
  `) : [
12505
12512
  `Session "${topicTitle}" was deleted. Extract durable memory from the archive below and save it into the wiki.`,
@@ -12508,7 +12515,7 @@ function createArchiverRuntime(host) {
12508
12515
  `wiki_dir: ${wikiDir}`,
12509
12516
  `output_language: ${outputLanguage}`,
12510
12517
  "",
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."
12518
+ generalReplyInstruction
12512
12519
  ].join(`
12513
12520
  `);
12514
12521
  const activeSessionId = `memory:${host.config.createId()}`;
@@ -12636,10 +12643,8 @@ function createArchiverRuntime(host) {
12636
12643
  host.config.warn({ err, userId, topicTitle }, "archiver: settlement callback failed");
12637
12644
  }
12638
12645
  }
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
- }
12646
+ const text = finalText.trimEnd();
12647
+ finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs, ok, mode, topicId, ok && text ? { text, agent, model, usage } : undefined);
12643
12648
  });
12644
12649
  topicQueues.set(safeTopic, work);
12645
12650
  work.finally(() => {
@@ -12685,7 +12690,7 @@ function findSummaryFile(storage, topicTitle, date, sinceMs, topicId) {
12685
12690
  }
12686
12691
  return best?.path ?? null;
12687
12692
  }
12688
- function finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs, ok, topicId, generalReply) {
12693
+ function finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs, ok, mode, topicId, generalReply) {
12689
12694
  const generalTopicId = host.storage.getGeneralTopicId(userId);
12690
12695
  const date = host.config.now().toISOString().slice(0, 10);
12691
12696
  try {
@@ -12702,7 +12707,7 @@ function finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs,
12702
12707
  ].slice(0, MAX_BRIEF_ENTRIES);
12703
12708
  const briefMd = `# Workspace Memory Hub
12704
12709
 
12705
- Recent memory digest distilled from deleted topics. Use \`wiki_query\` for details.
12710
+ Recent memory digest distilled from archived topics (deletions and idle/session-reset snapshots). Use \`wiki_query\` for details.
12706
12711
 
12707
12712
  ## Recent Archives
12708
12713
  ${rolled.join(`
@@ -12717,7 +12722,7 @@ ${rolled.join(`
12717
12722
  }
12718
12723
  try {
12719
12724
  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).`);
12725
+ 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
12726
  const replyMeta = generalReply && replyText ? {
12722
12727
  authorId: "ai",
12723
12728
  agentType: generalReply.agent,
@@ -23679,6 +23684,7 @@ __export(exports_src, {
23679
23684
  PROCESS_LEASE_STALE_MS: () => PROCESS_LEASE_STALE_MS,
23680
23685
  PROCESS_LEASE_HEARTBEAT_MS: () => PROCESS_LEASE_HEARTBEAT_MS,
23681
23686
  OPTIONAL_FORUM_MCP_SERVERS: () => OPTIONAL_FORUM_MCP_SERVERS,
23687
+ NODE_LOCAL_USER_ID: () => NODE_LOCAL_USER_ID,
23682
23688
  NODE_CONTROL_TOKEN: () => NODE_CONTROL_TOKEN,
23683
23689
  NEGOTIUM_VERSION: () => NEGOTIUM_VERSION,
23684
23690
  NEGOTIUM_PORT: () => NEGOTIUM_PORT,
@@ -24094,6 +24100,7 @@ __export(exports_node_host, {
24094
24100
  startBashrsCompletionsWorker: () => startBashrsCompletionsWorker,
24095
24101
  startAskUserQuestionGateOwner: () => startAskUserQuestionGateOwner,
24096
24102
  startAiTurn: () => startAiTurn,
24103
+ softDeleteApiMessage: () => softDeleteApiMessage,
24097
24104
  setRuntimeMcpPort: () => setRuntimeMcpPort,
24098
24105
  setNodeMcpServers: () => setNodeMcpServers,
24099
24106
  setGlobalAiName: () => setGlobalAiName,
@@ -24508,6 +24515,7 @@ __export(exports_mcp_runtime_host, {
24508
24515
  RUNTIME_MCP_BASE_PATH: () => RUNTIME_MCP_BASE_PATH,
24509
24516
  REQUIRED_FORUM_MCP_SERVERS: () => REQUIRED_FORUM_MCP_SERVERS,
24510
24517
  OPTIONAL_FORUM_MCP_SERVERS: () => OPTIONAL_FORUM_MCP_SERVERS,
24518
+ NODE_LOCAL_USER_ID: () => NODE_LOCAL_USER_ID,
24511
24519
  HOSTED_MCP_SURFACES: () => HOSTED_MCP_SURFACES,
24512
24520
  FROM_AUTO_CONTINUE: () => FROM_AUTO_CONTINUE,
24513
24521
  EFFORT_VALUES: () => EFFORT_VALUES
@@ -24548,12 +24556,31 @@ import { z as z6 } from "zod";
24548
24556
  function callerSurface(ctx) {
24549
24557
  return getTopic(ctx.topicId)?.surface ?? defaultTopicSurface();
24550
24558
  }
24559
+ function callerSurfaceScope(ctx) {
24560
+ return getTopic(ctx.topicId)?.surfaceScope ?? null;
24561
+ }
24562
+ function topicsForCaller(ctx) {
24563
+ const surface = callerSurface(ctx);
24564
+ if (surface !== "otium") {
24565
+ return getTopics({ surface }).filter((topic) => isParticipant(topic, ctx.userId));
24566
+ }
24567
+ return getTopics({ surface, surfaceScope: callerSurfaceScope(ctx) }).filter((topic) => topic.kind !== "manager" || topic.id === ctx.topicId || isParticipant(topic, ctx.userId));
24568
+ }
24551
24569
  function resolveTopicForUser(ctx, ref) {
24552
24570
  const trimmed = ref.trim();
24553
24571
  if (!trimmed)
24554
24572
  return { error: "Error: topic is required." };
24555
24573
  const notFound = `Error: topic '${trimmed}' not found (or not uniquely named). Use list_topics to see available topics.`;
24556
24574
  const surface = callerSurface(ctx);
24575
+ if (surface === "otium") {
24576
+ const visible = topicsForCaller(ctx);
24577
+ const byId2 = visible.find((topic) => topic.id === trimmed);
24578
+ if (byId2)
24579
+ return { topic: byId2 };
24580
+ const wanted = trimmed.toLowerCase();
24581
+ const byTitle2 = visible.filter((topic) => topic.title.toLowerCase() === wanted);
24582
+ return byTitle2.length === 1 ? { topic: byTitle2[0] } : { error: notFound };
24583
+ }
24557
24584
  const byId = getTopic(trimmed);
24558
24585
  if (byId) {
24559
24586
  if (!isParticipant(byId, ctx.userId) || byId.surface !== surface)
@@ -24565,6 +24592,12 @@ function resolveTopicForUser(ctx, ref) {
24565
24592
  return { topic: byTitle };
24566
24593
  return { error: notFound };
24567
24594
  }
24595
+ function actingUserFor(ctx, target) {
24596
+ const owner = target.participants.find((p) => p.role === "owner")?.userId;
24597
+ if (callerSurface(ctx) !== "otium" || owner !== NODE_LOCAL_USER_ID)
24598
+ return ctx.userId;
24599
+ return owner;
24600
+ }
24568
24601
  function describeTopic(topic) {
24569
24602
  const running = getRoomQuery(topic.id) ? "turn running" : "idle";
24570
24603
  const agent = topic.agent ? `agent: ${topic.agent}` : "no agent";
@@ -24603,8 +24636,8 @@ function registerNodeTools(server, ctx) {
24603
24636
  return errorResult(`Error: failed to register topic: ${errMsg(err2)}`);
24604
24637
  }
24605
24638
  });
24606
- 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 () => {
24607
- const topics = getTopics({ surface: callerSurface(ctx) }).filter((topic) => isParticipant(topic, ctx.userId));
24639
+ 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 () => {
24640
+ const topics = topicsForCaller(ctx);
24608
24641
  if (topics.length === 0) {
24609
24642
  return textResult("No topics found. Use register_topic to create one.");
24610
24643
  }
@@ -24649,7 +24682,7 @@ function registerNodeTools(server, ctx) {
24649
24682
  if (target.kind === "manager") {
24650
24683
  return errorResult("Error: manager rooms are system-managed and cannot be restarted.");
24651
24684
  }
24652
- const result = await restartTopicSession(target.id, ctx.userId, "runtime-mcp-session-reset");
24685
+ const result = await restartTopicSession(target.id, actingUserFor(ctx, target), "runtime-mcp-session-reset");
24653
24686
  return result.isError ? errorResult(`Error: ${result.text}`) : textResult(result.text);
24654
24687
  });
24655
24688
  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.", {
@@ -24666,12 +24699,13 @@ function registerNodeTools(server, ctx) {
24666
24699
  if (target.kind === "manager") {
24667
24700
  return errorResult("Error: manager rooms are system-managed and cannot be deleted.");
24668
24701
  }
24669
- const isOwner = target.participants.some((participant) => participant.userId === ctx.userId && participant.role === "owner");
24702
+ const actingUser = actingUserFor(ctx, target);
24703
+ const isOwner = target.participants.some((participant) => participant.userId === actingUser && participant.role === "owner");
24670
24704
  if (!isOwner) {
24671
24705
  return errorResult("Error: only the topic owner can delete it.");
24672
24706
  }
24673
24707
  try {
24674
- await deleteTopicCascade(target, ctx.userId, { force: force === true });
24708
+ await deleteTopicCascade(target, actingUser, { force: force === true });
24675
24709
  } catch (err2) {
24676
24710
  if (err2 instanceof TopicArchiveRequiredError) {
24677
24711
  return errorResult([
@@ -32528,6 +32562,7 @@ function createNodeControlHandler(options) {
32528
32562
  "turn-events-sse-resume",
32529
32563
  "canonical-topic-read",
32530
32564
  "canonical-message-read",
32565
+ "canonical-message-delete",
32531
32566
  "canonical-topic-usage",
32532
32567
  "canonical-file-read",
32533
32568
  "canonical-visual-read",
@@ -32812,6 +32847,36 @@ function createNodeControlHandler(options) {
32812
32847
  });
32813
32848
  return Response.json({ ok: true, v: NODE_RUNTIME_CONTRACT_VERSION, ...result });
32814
32849
  }
32850
+ const runtimeMessageMatch = runtimePath.match(/^\/topics\/([^/]+)\/messages\/([^/]+)$/);
32851
+ if (runtimeMessageMatch && req.method === "DELETE") {
32852
+ const body = await bodyRecord(req);
32853
+ if (body.v !== NODE_RUNTIME_CONTRACT_VERSION)
32854
+ return jsonError(400, "Unsupported v");
32855
+ const topicId = decodeURIComponent(runtimeMessageMatch[1]);
32856
+ const messageId = decodeURIComponent(runtimeMessageMatch[2]);
32857
+ const topic = getTopic(topicId);
32858
+ if (!topic || !topicInRequestScope(req, topic))
32859
+ return jsonError(404, "Topic not found");
32860
+ const actorUserId = requiredText(body.actorUserId, "actorUserId");
32861
+ if (body.allowAdmin !== undefined && typeof body.allowAdmin !== "boolean") {
32862
+ return jsonError(400, "allowAdmin must be boolean");
32863
+ }
32864
+ const existing = getApiMessage(topicId, messageId);
32865
+ if (!existing || existing.deleted)
32866
+ return jsonError(404, "Message not found");
32867
+ if (existing.authorId !== actorUserId && body.allowAdmin !== true) {
32868
+ return jsonError(403, "Not allowed");
32869
+ }
32870
+ const message = softDeleteApiMessage(topicId, messageId);
32871
+ if (!message)
32872
+ return jsonError(404, "Message not found");
32873
+ WsHub.get().broadcastMessageUpdated(topicId, messageId, { deleted: true, text: "" });
32874
+ return Response.json({
32875
+ ok: true,
32876
+ v: NODE_RUNTIME_CONTRACT_VERSION,
32877
+ message
32878
+ });
32879
+ }
32815
32880
  if (req.method === "GET" && runtimePath === "/topics") {
32816
32881
  const topics = gatewayVisibleTopics(req);
32817
32882
  const previews = getLastMessagePreviews(topics.map((topic) => topic.id));
@@ -32845,6 +32910,34 @@ function createNodeControlHandler(options) {
32845
32910
  });
32846
32911
  return Response.json({ ok: true, v: NODE_RUNTIME_CONTRACT_VERSION, topic }, { status: 201 });
32847
32912
  }
32913
+ const runtimeDeriveMatch = runtimePath.match(/^\/topics\/([^/]+)\/derive$/);
32914
+ if (runtimeDeriveMatch && req.method === "POST") {
32915
+ const body = await bodyRecord(req);
32916
+ if (body.v !== NODE_RUNTIME_CONTRACT_VERSION)
32917
+ return jsonError(400, "Unsupported v");
32918
+ const topicId = decodeURIComponent(runtimeDeriveMatch[1]);
32919
+ const userId = requiredText(body.userId, "userId");
32920
+ if (typeof body.copyHistory !== "boolean") {
32921
+ return jsonError(400, "copyHistory must be a boolean");
32922
+ }
32923
+ if (body.name !== undefined && typeof body.name !== "string") {
32924
+ return jsonError(400, "name must be a string");
32925
+ }
32926
+ const source = topicForUser(topicId, userId);
32927
+ if (!source || source.kind === "manager" || !topicInRequestScope(req, source)) {
32928
+ return jsonError(404, "Topic not found");
32929
+ }
32930
+ const name = typeof body.name === "string" && body.name.trim() ? body.name.trim() : "";
32931
+ const derived = await topicService.derive({
32932
+ sourceTopicId: topicId,
32933
+ userId,
32934
+ copyHistory: body.copyHistory,
32935
+ ...name ? { name } : {}
32936
+ });
32937
+ if (!derived)
32938
+ return jsonError(500, "Failed to derive topic");
32939
+ return Response.json({ ok: true, v: NODE_RUNTIME_CONTRACT_VERSION, topic: derived }, { status: 201 });
32940
+ }
32848
32941
  const importMatch = runtimePath.match(/^\/topics\/([^/]+)\/import$/);
32849
32942
  if (importMatch && req.method === "POST") {
32850
32943
  const body = await bodyRecord(req);
@@ -46582,6 +46675,8 @@ function allowedRuntimePath(path, method) {
46582
46675
  return /^\/topics\/[^/]+\/(abort|session\/(reset|compact))$/.test(path);
46583
46676
  }
46584
46677
  if (method === "DELETE" || method === "PATCH") {
46678
+ if (method === "DELETE" && /^\/topics\/[^/]+\/messages\/[^/]+$/.test(path))
46679
+ return true;
46585
46680
  return /^\/topics\/[^/]+$/.test(path) || /^\/cron\/jobs\/[^/]+$/.test(path);
46586
46681
  }
46587
46682
  return false;
@@ -48632,4 +48727,4 @@ switch (command) {
48632
48727
  }
48633
48728
  }
48634
48729
 
48635
- //# debugId=FFCB7D41CEEC0A6064756E2164756E21
48730
+ //# debugId=679BF40C457A90DE64756E2164756E21