negotium 0.21.2 → 0.22.0

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 (35) hide show
  1. package/dist/agent-helpers.js +52 -6
  2. package/dist/agent-helpers.js.map +7 -7
  3. package/dist/background-bash.js.map +1 -1
  4. package/dist/browser-runtime.js.map +1 -1
  5. package/dist/{chunk-z3qhxy33.js → chunk-bbwa785m.js} +2 -2
  6. package/dist/{chunk-z3qhxy33.js.map → chunk-bbwa785m.js.map} +2 -2
  7. package/dist/chunk-pvp197f1.js.map +1 -1
  8. package/dist/chunk-zjz52hzq.js.map +1 -1
  9. package/dist/hosted-agent.js +2 -2
  10. package/dist/hosted-agent.js.map +2 -2
  11. package/dist/main.js +137 -41
  12. package/dist/main.js.map +12 -12
  13. package/dist/mcp-factories.js +52 -6
  14. package/dist/mcp-factories.js.map +7 -7
  15. package/dist/media.js.map +1 -1
  16. package/dist/prompts.js.map +1 -1
  17. package/dist/query-runtime.js +14 -2
  18. package/dist/query-runtime.js.map +4 -4
  19. package/dist/registry.js +1 -1
  20. package/dist/runtime/src/mcp/session-comm/server.ts +2 -2
  21. package/dist/runtime/src/query/active-rooms.ts +22 -2
  22. package/dist/runtime/src/runtime/inbox.ts +74 -41
  23. package/dist/runtime/src/runtime/turn-runner.ts +4 -2
  24. package/dist/runtime/src/storage/token-stats.ts +62 -1
  25. package/dist/runtime/src/types.ts +6 -1
  26. package/dist/runtime/src/version.ts +1 -1
  27. package/dist/runtime-helpers.js.map +1 -1
  28. package/dist/storage.js +36 -2
  29. package/dist/storage.js.map +4 -4
  30. package/dist/types/packages/core/src/query/active-rooms.d.ts +8 -2
  31. package/dist/types/packages/core/src/storage/token-stats.d.ts +9 -0
  32. package/dist/types/packages/core/src/types.d.ts +6 -1
  33. package/dist/types/packages/core/src/version.d.ts +1 -1
  34. package/dist/vault.js.map +1 -1
  35. package/package.json +2 -2
package/dist/main.js CHANGED
@@ -6520,7 +6520,7 @@ var exports_version = {};
6520
6520
  __export(exports_version, {
6521
6521
  NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
6522
6522
  });
6523
- var NEGOTIUM_VERSION = "0.21.2";
6523
+ var NEGOTIUM_VERSION = "0.22.0";
6524
6524
 
6525
6525
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
6526
6526
  import { spawn as spawn2 } from "child_process";
@@ -13748,8 +13748,20 @@ class InterSessionQueue {
13748
13748
  keysForRoom() {
13749
13749
  return Array.from(this.queue.keys());
13750
13750
  }
13751
- drop(topicId) {
13751
+ drop(topicId, reason = "target session was stopped before the request ran") {
13752
+ const bucket = this.queue.get(topicId);
13752
13753
  this.queue.delete(topicId);
13754
+ if (!bucket)
13755
+ return 0;
13756
+ const dropped = bucket.items.slice(bucket.head);
13757
+ for (const inject of dropped) {
13758
+ try {
13759
+ inject.onSettled?.({ queryId: "", kind: "error", error: reason });
13760
+ } catch (err) {
13761
+ logger.warn({ err, topicId, requestId: inject.requestId }, "session inject drop settlement hook failed");
13762
+ }
13763
+ }
13764
+ return dropped.length;
13753
13765
  }
13754
13766
  size(topicId) {
13755
13767
  const bucket = this.queue.get(topicId);
@@ -15574,6 +15586,30 @@ function isQueryRecord(value) {
15574
15586
  const record = value;
15575
15587
  return record.schemaVersion === 2 && typeof record.timestamp === "string" && typeof record.session === "string" && typeof record.topicId === "string" && typeof record.agent === "string" && typeof record.model === "string" && typeof record.inputTokens === "number" && typeof record.outputTokens === "number" && typeof record.cacheCreationInputTokens === "number" && typeof record.cacheReadInputTokens === "number" && typeof record.estimatedCostUsd === "number";
15576
15588
  }
15589
+ function previousCumulativeCost(userId, providerSessionId) {
15590
+ let lines;
15591
+ try {
15592
+ lines = readJsonlLines(queriesPath(userId));
15593
+ } catch {
15594
+ return 0;
15595
+ }
15596
+ for (let i = lines.length - 1;i >= 0; i--) {
15597
+ let parsed;
15598
+ try {
15599
+ parsed = JSON.parse(lines[i]);
15600
+ } catch {
15601
+ continue;
15602
+ }
15603
+ if (parsed.providerSessionId !== providerSessionId)
15604
+ continue;
15605
+ if (typeof parsed.cumulativeCostUsd === "number")
15606
+ return parsed.cumulativeCostUsd;
15607
+ if (typeof parsed.estimatedCostUsd === "number")
15608
+ return parsed.estimatedCostUsd;
15609
+ return 0;
15610
+ }
15611
+ return 0;
15612
+ }
15577
15613
  function recordUsage(userId, session, usage, context) {
15578
15614
  const cacheReadInputTokens = usage.cacheReadInputTokens ?? 0;
15579
15615
  const inputTokens = context.agent === "claude" ? usage.inputTokens : Math.max(0, usage.inputTokens - cacheReadInputTokens);
@@ -15583,6 +15619,15 @@ function recordUsage(userId, session, usage, context) {
15583
15619
  cacheCreationInputTokens: usage.cacheCreationInputTokens ?? 0,
15584
15620
  cacheReadInputTokens
15585
15621
  };
15622
+ let estimatedCostUsd;
15623
+ let cumulativeCostUsd;
15624
+ if (usage.costUsd !== undefined) {
15625
+ const previous = context.providerSessionId ? previousCumulativeCost(userId, context.providerSessionId) : 0;
15626
+ estimatedCostUsd = Math.max(0, usage.costUsd - previous);
15627
+ cumulativeCostUsd = usage.costUsd;
15628
+ } else {
15629
+ estimatedCostUsd = estimateUsageCost(context.agent, context.model, normalized);
15630
+ }
15586
15631
  const record = {
15587
15632
  schemaVersion: 2,
15588
15633
  timestamp: new Date().toISOString(),
@@ -15594,7 +15639,8 @@ function recordUsage(userId, session, usage, context) {
15594
15639
  ...normalized,
15595
15640
  ...usage.contextTokens !== undefined ? { contextTokens: usage.contextTokens } : {},
15596
15641
  ...usage.contextWindow !== undefined ? { contextWindow: usage.contextWindow } : {},
15597
- estimatedCostUsd: usage.costUsd ?? estimateUsageCost(context.agent, context.model, normalized)
15642
+ estimatedCostUsd,
15643
+ ...cumulativeCostUsd !== undefined ? { cumulativeCostUsd } : {}
15598
15644
  };
15599
15645
  try {
15600
15646
  appendJsonlEntry(queriesPath(userId), record);
@@ -23436,9 +23482,8 @@ function startAiTurn(params) {
23436
23482
  contextWindow: outcome.usage.contextWindow
23437
23483
  } : undefined, { agent: agentKind, model: resolvedModel, budgetReason: outcome.reason });
23438
23484
  WsHub.get().broadcastTopicNotice(topicId, "warning", "\uC608\uC0B0 \uD55C\uB3C4 \uB3C4\uB2EC");
23439
- } else {
23440
- await deliverAskError(queryId, topic.title, error);
23441
23485
  }
23486
+ await deliverAskError(queryId, topic.title, error);
23442
23487
  await settleSubagentFailure(queryId, error);
23443
23488
  try {
23444
23489
  onSettled?.({ queryId, kind: "error", error });
@@ -23483,6 +23528,7 @@ function startAiTurn(params) {
23483
23528
  WsHub.get().broadcastError(topicId, queryId, error);
23484
23529
  WsHub.get().broadcastTopicNotice(topicId, "error", "\uC751\uB2F5 \uC2E4\uD328");
23485
23530
  }
23531
+ await deliverAskError(queryId, topic.title, error);
23486
23532
  await settleSubagentFailure(queryId, classifyAgentError(error, agentKind));
23487
23533
  try {
23488
23534
  onSettled?.({
@@ -27512,6 +27558,9 @@ function normalizeAskTarget(target) {
27512
27558
  return "main";
27513
27559
  return target === "cron" ? "cron" : null;
27514
27560
  }
27561
+ function askRunsOnTargetSession(target) {
27562
+ return target === "main";
27563
+ }
27515
27564
  async function createAskForkPlan(options) {
27516
27565
  const snapshot = structuredClone(options.entries);
27517
27566
  const prepareSession = options.prepareSession ?? (async () => options.synthesize(structuredClone(snapshot)));
@@ -28113,6 +28162,59 @@ ${entry.message}
28113
28162
  }
28114
28163
  return;
28115
28164
  }
28165
+ const onSettled = (result) => {
28166
+ if (result.queryId || result.kind !== "error")
28167
+ return;
28168
+ clearPendingAskForEntry(scope, entry, topicName, "target-unavailable-before-dispatch");
28169
+ const message2 = `(error: ${result.error ?? "target topic became unavailable before dispatch"})`;
28170
+ if (remoteReply) {
28171
+ deliverPeerReply(remoteReply, targetLabel, message2, "error");
28172
+ } else {
28173
+ notifyAskDrop(scope, entry, targetLabel, message2);
28174
+ }
28175
+ };
28176
+ const onDispatched = (queryId) => {
28177
+ registerAskCallback2({
28178
+ requestId,
28179
+ contextId: entry.contextId,
28180
+ callerTopicId,
28181
+ ...entry.fromThreadRootId ? { callerThreadRootId: entry.fromThreadRootId } : {},
28182
+ callerUserId: String(scope.userId),
28183
+ targetQueryId: queryId,
28184
+ createdAt: Date.now(),
28185
+ ...remoteReply ? { remoteReply } : {
28186
+ pendingAsk: {
28187
+ userId: scope.userId,
28188
+ from: entry.from,
28189
+ to: topicName,
28190
+ requestId
28191
+ }
28192
+ },
28193
+ sourceLabel: targetLabel
28194
+ });
28195
+ };
28196
+ if (askRunsOnTargetSession(askTarget)) {
28197
+ triggerTopicAiTurn2(topic.id, String(scope.userId), prompt, agentOverride, {
28198
+ origin: entry.from,
28199
+ requestId,
28200
+ contextId: entry.contextId,
28201
+ depth: (entry.fromDepth ?? 0) + 1,
28202
+ from: fromLabel,
28203
+ injectAuthorId: "system",
28204
+ injectSourceAdapter: "session-comm",
28205
+ injectKind: "tell",
28206
+ injectTellCard: {
28207
+ fromLabel,
28208
+ fromTopicId: entry.fromTopicId,
28209
+ label: `Ask from ${fromLabel}`,
28210
+ message: entry.message
28211
+ },
28212
+ onSettled,
28213
+ onDispatched
28214
+ });
28215
+ logger.info({ topicName, from: entry.from, requestId, callerTopicId, agent: agentOverride }, "session-inbox: ask delivered to target session");
28216
+ return;
28217
+ }
28116
28218
  const forkNative = parentSessionId ? () => forkAgentSession2({
28117
28219
  agent: agentOverride,
28118
28220
  parentSessionId,
@@ -28200,36 +28302,8 @@ ${entry.message}
28200
28302
  forkHandle: forkPlan.forkHandle,
28201
28303
  prepareSession: forkPlan.prepareSession,
28202
28304
  cwd,
28203
- onSettled: (result) => {
28204
- if (result.queryId || result.kind !== "error")
28205
- return;
28206
- clearPendingAskForEntry(scope, entry, topicName, "target-unavailable-before-dispatch");
28207
- if (remoteReply) {
28208
- deliverPeerReply(remoteReply, targetLabel, `(error: target topic became unavailable before dispatch)`, "error");
28209
- } else {
28210
- notifyAskDrop(scope, entry, targetLabel, `(error: target topic became unavailable before dispatch)`);
28211
- }
28212
- },
28213
- onDispatched: (queryId) => {
28214
- registerAskCallback2({
28215
- requestId,
28216
- contextId: entry.contextId,
28217
- callerTopicId,
28218
- ...entry.fromThreadRootId ? { callerThreadRootId: entry.fromThreadRootId } : {},
28219
- callerUserId: String(scope.userId),
28220
- targetQueryId: queryId,
28221
- createdAt: Date.now(),
28222
- ...remoteReply ? { remoteReply } : {
28223
- pendingAsk: {
28224
- userId: scope.userId,
28225
- from: entry.from,
28226
- to: topicName,
28227
- requestId
28228
- }
28229
- },
28230
- sourceLabel: targetLabel
28231
- });
28232
- }
28305
+ onSettled,
28306
+ onDispatched
28233
28307
  });
28234
28308
  logger.info({ topicName, from: entry.from, requestId, callerTopicId }, "session-inbox: ask delivered");
28235
28309
  }
@@ -29924,6 +29998,10 @@ function callerSurface(ctx) {
29924
29998
  function callerSurfaceScope(ctx) {
29925
29999
  return getTopic(ctx.topicId)?.surfaceScope ?? null;
29926
30000
  }
30001
+ function isTelegramDmGeneral(ctx) {
30002
+ const topic = getTopic(ctx.topicId);
30003
+ return topic?.surface === "telegram" && topic.kind === "manager" && !(topic.surfaceScope ?? null);
30004
+ }
29927
30005
  function resolvableTopicsForCaller(ctx) {
29928
30006
  const surface = callerSurface(ctx);
29929
30007
  if (surface !== "otium") {
@@ -30004,11 +30082,15 @@ function registerNodeTools(server, ctx) {
30004
30082
  description: z6.string().optional().describe("Short description of the topic's purpose."),
30005
30083
  memory_key: z6.string().optional().describe("Wiki memory persona the room continues.")
30006
30084
  }, async ({ title, agent, model, effort, description, memory_key }) => {
30085
+ if (isTelegramDmGeneral(ctx)) {
30086
+ return errorResult("Error: topics can't be created from the Telegram DM. Topic management lives in " + "a connected forum group \u2014 ask the user to send /start to connect one, then " + "create the topic from that group's General.");
30087
+ }
30007
30088
  try {
30008
30089
  const topic = registerTopic({
30009
30090
  title,
30010
30091
  userId: ctx.userId,
30011
30092
  surface: callerSurface(ctx),
30093
+ surfaceScope: callerSurfaceScope(ctx),
30012
30094
  agent,
30013
30095
  model,
30014
30096
  effort,
@@ -43759,7 +43841,7 @@ function footerUsageText(state) {
43759
43841
  const ratio = contextUsage?.context !== undefined && contextUsage.contextWindow ? Math.round(contextUsage.context / contextUsage.contextWindow * 100) : undefined;
43760
43842
  const context = contextUsage?.context !== undefined && contextUsage.contextWindow ? `${contextUsage.estimated ? "~" : ""}${tokenCount(contextUsage.context)}/${tokenCount(contextUsage.contextWindow)} ${ratio}%` : "";
43761
43843
  const hasTopicUsage = total !== undefined && total.queries > 0;
43762
- const cumulative = hasTopicUsage ? `\u03A3 ${tokenCount(total.inputTokens)} in/${tokenCount(total.outputTokens)} out` : "";
43844
+ const cumulative = hasTopicUsage ? `\u03A3 ${tokenCount(total.inputTokens)} fresh/${tokenCount(total.outputTokens)} out` : "";
43763
43845
  const cache = hasTopicUsage && total.cacheReadInputTokens ? `cache ${tokenCount(total.cacheReadInputTokens)}` : "";
43764
43846
  const cost = hasTopicUsage ? `est $${total.estimatedCostUsd.toFixed(2)}` : "";
43765
43847
  const variant = (values2) => {
@@ -48590,7 +48672,7 @@ function resolveTelegramTopicArg(argument, userId, surfaceScope) {
48590
48672
  }
48591
48673
  function createTelegramCommandRouter(context) {
48592
48674
  const { userId, reply, currentTopicId, bindMapping, loadTopic, unloadTopic, abortTurn } = context;
48593
- return async function handleCommand(text3, chatId, threadId, telegramUserId) {
48675
+ return async function handleCommand(text3, chatId, threadId, telegramUserId, isPrivateDm = false) {
48594
48676
  const [rawCommand = ""] = text3.split(/\s+/);
48595
48677
  const addressedUsername = rawCommand.match(/@(\w+)$/)?.[1];
48596
48678
  if (addressedUsername) {
@@ -48608,6 +48690,20 @@ function createTelegramCommandRouter(context) {
48608
48690
  }
48609
48691
  const command = rawCommand.replace(/@\w+$/, "");
48610
48692
  const argument = extractCommandArg(text3);
48693
+ const DM_RESTRICTED_COMMANDS = new Set([
48694
+ "/topics",
48695
+ "/load",
48696
+ "/unload",
48697
+ "/agent",
48698
+ "/fork",
48699
+ "/spawn",
48700
+ "/del",
48701
+ "/del!"
48702
+ ]);
48703
+ if (isPrivateDm && (DM_RESTRICTED_COMMANDS.has(command) || command === "/new" && argument)) {
48704
+ reply(chatId, threadId, "topic management lives in a connected forum group, not this DM \u2014 " + "send /start to see how to connect one.");
48705
+ return;
48706
+ }
48611
48707
  switch (command) {
48612
48708
  case "/start":
48613
48709
  await context.sendOnboardingGuide(chatId, threadId);
@@ -48751,7 +48847,7 @@ function createTelegramCommandRouter(context) {
48751
48847
  return;
48752
48848
  }
48753
48849
  default:
48754
- reply(chatId, threadId, "commands: /new [name], /topics, /agent <claude|codex|maestro>, " + "/load <topic>, /unload, /fork [name], /spawn [name], " + "/del [name], /del! [name], /abort, /vault <list|set|del>");
48850
+ reply(chatId, threadId, isPrivateDm ? "commands: /new, /abort, /vault <list|set|del> \u2014 topic management " + "(/topics, /load, /fork, /spawn, /del, /agent) lives in a connected " + "forum group; send /start to connect one" : "commands: /new [name], /topics, /agent <claude|codex|maestro>, " + "/load <topic>, /unload, /fork [name], /spawn [name], " + "/del [name], /del! [name], /abort, /vault <list|set|del>");
48755
48851
  }
48756
48852
  };
48757
48853
  }
@@ -50672,9 +50768,9 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
50672
50768
  if (text3?.startsWith("/")) {
50673
50769
  const command = text3.split(/\s+/, 1)[0]?.replace(/@\w+$/, "");
50674
50770
  if (command === "/abort") {
50675
- handleCommand(text3, chatId, threadId, msg.from?.id);
50771
+ handleCommand(text3, chatId, threadId, msg.from?.id, privateDm);
50676
50772
  } else {
50677
- mediaIntake.enqueue(chatId, threadId, () => handleCommand(text3, chatId, threadId, msg.from?.id));
50773
+ mediaIntake.enqueue(chatId, threadId, () => handleCommand(text3, chatId, threadId, msg.from?.id, privateDm));
50678
50774
  }
50679
50775
  return;
50680
50776
  }
@@ -58187,4 +58283,4 @@ switch (command) {
58187
58283
  }
58188
58284
  }
58189
58285
 
58190
- //# debugId=2B03CFE9288A2E3664756E2164756E21
58286
+ //# debugId=205630437F8D066064756E2164756E21