openmausbot 0.1.93 → 0.1.94

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 (140) hide show
  1. package/dist/assets/{DesktopViewer-CKNR3SEW.js → DesktopViewer-bxM7NV3N.js} +1 -1
  2. package/dist/assets/{abnfDiagram-O67JEVCF-Ckrm1I1X.js → abnfDiagram-O67JEVCF-BhMdJhRJ.js} +1 -1
  3. package/dist/assets/{arc-BBdBuCgB.js → arc-B5itKDcx.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-CDdA3Hjs.js → architectureDiagram-NJMV4G6O-C5d7UY6U.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-AUrsx1YR.js → blockDiagram-BEXU5L5S-BrZIhMmu.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-BEqQ0Unh.js → c4Diagram-YGBWAQC7-7I0ArfbD.js} +1 -1
  7. package/dist/assets/channel-CdJX6i8k.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-CEEuvd4C.js → chunk-2Q5K7J3B-BnEsyN36.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-DyANCiHl.js → chunk-3FUC2YCW-BXATQ52s.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-Dx_Slpw9.js → chunk-5VM5RSS4-B0BpTkEx.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-Dl7h7iiD.js → chunk-GTNCS2PH-BtLTz6FR.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-BLx07SPb.js → chunk-GWA4HPMP-D6gcZOYt.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-BMxDj3n7.js → chunk-JWPE2WC7-DCLrosvf.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-CN66PT4H.js → chunk-NETBCI7D-B9JsknV9.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-6HwhsDKw.js → chunk-WEXAMYUT-CsoTxw7z.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-gWnLt7SN.js → chunk-XXDRQBXY-0F_frB0c.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-CMS5l2ea.js → classDiagram-v2-NBCMYWYE-DkrpnflX.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-CV7sQwIG.js → cose-bilkent-JH36ORCC-BNDwYBt9.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-CSyl-7vl.js → cynefin-OW5HDTMX-JUkNr1Mk.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-BCLKL6az.js → cynefinDiagram-VND7K2PF-DKeh9N4M.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-DiaZs6S1.js → dagre-6A5THRUB-ve-6T_wX.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-BeTtmt3I.js → diagram-22UHCM2B-BuJ2d8rT.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-CsdBG-QQ.js → diagram-3UASUU5V-BgOs_Ni-.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-BczvdbR1.js → diagram-ATOU4E4O-DMK6VmcP.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-DHTTTMhi.js → diagram-CDSNMT55-DksASlcY.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-RTjHyd8z.js → diagram-MLGK6HIB-Bgn8I5k6.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-BDenHEH_.js → diagram-MPIPVDR6-zK0jqO8N.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-BSiRSbtL.js → ebnfDiagram-ZINNZB2B-vKrbzTwr.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-BCAElss-.js → elk-276RUBZZ-Bzv2mPNf.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-C4_1ZPJe.js → erDiagram-OPXOYQCR-CvOyYuxu.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-Bcb4Z75S.js → flowDiagram-KWPJA3E3-CtbnDbc0.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-BiPOzxzb.js → ganttDiagram-FUAMR5RP-c4dMRYyG.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-B1wtF3ZS.js → gitGraphDiagram-X574FWY7-B34uaQho.js} +1 -1
  34. package/dist/assets/index-CPYrGsv1.js +610 -0
  35. package/dist/assets/{index-BkYwilkG.js → index-CPiqc_DN.js} +1 -1
  36. package/dist/assets/index-CzT42GR8.css +1 -0
  37. package/dist/assets/{infoDiagram-VRGFBTTK-A19No71U.js → infoDiagram-VRGFBTTK-o64ma4CI.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-BQsqJ_cV.js → ishikawaDiagram-OU5B5YK6-DV4C9BAm.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-DAO0pVan.js → journeyDiagram-ZHPQQLJL-Bf0AHk1H.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-NepmKEe2.js → kanban-definition-PNTS6WVX-B1UMSKmE.js} +1 -1
  41. package/dist/assets/{linear-bq1PTDTW.js → linear-BpKhuyLm.js} +1 -1
  42. package/dist/assets/{mermaid.core-s4LTbQle.js → mermaid.core-B_BKNCw5.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-LtdbgHFH.js → mindmap-definition-NLK3R4M7-DeARruT1.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-tdbaCxiK.js → pegDiagram-GJSIUBJH-BOQEk_1D.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-DvkbpW8l.js → pieDiagram-5QR66LMP-EIfoiTZo.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-Do4KaH-E.js → quadrantDiagram-O4NWA36T-GgZwT-fi.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-BQWULOAs.js → railroadDiagram-XR7U4H2S-D9rDQomj.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-w9kei1FU.js → requirementDiagram-PLB6GJNP-DAh1MIeo.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-D2gRy0e8.js → sankeyDiagram-IPEJSGJF-CPr6Uz64.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-a2sO5_Eo.js → sequenceDiagram-PO4LG4MO-BU6VIdcm.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-CiPJmFEB.js → sizeCapture-INFHLROL-qF2HO41r.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-DAkUUlL7.js → stateDiagram-v2-GCMORJYK-DlJ8G4MD.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-DANf2R8Y.js → swimlanes-2SLR337P-DzYE8I54.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-D-U4GtLQ.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-BKpQmzrT.js → timeline-definition-EJHVYXUP-B7wzEQ0A.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-BtI7qLN-.js → usecaseDiagram-POWQR4AR-BvGgrxIP.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-gXJXrAG_.js → vennDiagram-UO4OBE2U-CEECdgQg.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-GU8Po5-n.js → wardleyDiagram-VNRHLVJA-XQHA0P_P.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-BF4J30g6.js → xychartDiagram-PMCCYNJV-B5mLcZI2.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/cloud-home-start.js +1 -0
  62. package/dist-server/companion/src/routes.js +21 -0
  63. package/dist-server/container-mcp.js +93 -76
  64. package/dist-server/drivers/agents-proxy.js +38 -1
  65. package/dist-server/drivers/pi-mcp-extension.ts +245 -299
  66. package/dist-server/enterprise/server/index.js +1 -0
  67. package/dist-server/index.js +16942 -13191
  68. package/dist-server/local-computer.js +1 -0
  69. package/dist-server/mcp-gate.js +147 -19
  70. package/dist-server/mcp-remote-proxy.js +437 -0
  71. package/dist-server/openmausbot.js +8233 -7651
  72. package/dist-server/pair-cli.js +8233 -7651
  73. package/dist-server/permission-proxy.js +65 -1
  74. package/dist-server/proxy-paths.js +1 -0
  75. package/dist-server/server/auto-approve.js +33 -11
  76. package/dist-server/server/browser-live.js +14 -5
  77. package/dist-server/server/browser-runtime.js +6 -0
  78. package/dist-server/server/channel-queue.js +4 -1
  79. package/dist-server/server/cli.js +32 -12
  80. package/dist-server/server/cloud-move-http.js +9 -3
  81. package/dist-server/server/cloud-move.js +18 -0
  82. package/dist-server/server/computer-wait.js +33 -13
  83. package/dist-server/server/config.js +44 -10
  84. package/dist-server/server/decision-log.js +9 -6
  85. package/dist-server/server/delegations.js +47 -22
  86. package/dist-server/server/drivers/acp/core.js +57 -8
  87. package/dist-server/server/drivers/acp/grok.js +208 -3
  88. package/dist-server/server/drivers/agents-call.js +28 -0
  89. package/dist-server/server/drivers/agents-catalog.js +10 -1
  90. package/dist-server/server/drivers/boatagent.js +2 -0
  91. package/dist-server/server/drivers/chat-mcp-tools.js +27 -14
  92. package/dist-server/server/drivers/claude.js +18 -6
  93. package/dist-server/server/drivers/codex.js +84 -10
  94. package/dist-server/server/drivers/openai-chat.js +11 -3
  95. package/dist-server/server/drivers/pi.js +48 -4
  96. package/dist-server/server/environment.js +3 -0
  97. package/dist-server/server/guarded-requests.js +2 -1
  98. package/dist-server/server/index.js +1143 -254
  99. package/dist-server/server/live-call-controller.js +868 -0
  100. package/dist-server/server/live-call.js +216 -0
  101. package/dist-server/server/mcp-gate-config.js +43 -12
  102. package/dist-server/server/mcp-gate.js +127 -25
  103. package/dist-server/server/mcp-http.js +46 -8
  104. package/dist-server/server/mcp-remote-proxy.js +108 -0
  105. package/dist-server/server/new-bot-defaults.js +3 -0
  106. package/dist-server/server/peer-provenance.js +4 -2
  107. package/dist-server/server/permission-proxy.js +15 -1
  108. package/dist-server/server/plan-usage.js +797 -0
  109. package/dist-server/server/proxy-paths.js +1 -0
  110. package/dist-server/server/redact.js +2 -0
  111. package/dist-server/server/request-auth.js +3 -2
  112. package/dist-server/server/routes/live.js +98 -0
  113. package/dist-server/server/skill-library.js +207 -0
  114. package/dist-server/server/skills-library-migration.js +235 -0
  115. package/dist-server/server/skills.js +66 -3
  116. package/dist-server/server/steer-queue.js +13 -1
  117. package/dist-server/server/store.js +32 -4
  118. package/dist-server/server/surface.js +9 -3
  119. package/dist-server/server/team-backup.js +2 -1
  120. package/dist-server/server/thread-events.js +1 -1
  121. package/dist-server/server/tunnel.js +20 -1
  122. package/dist-server/server/turn-log.js +10 -0
  123. package/dist-server/server/turn-resources.js +124 -0
  124. package/dist-server/shared/ask-question.js +13 -0
  125. package/dist-server/shared/call-consent.js +28 -0
  126. package/dist-server/shared/live-approval.js +124 -0
  127. package/dist-server/shared/live-call.js +176 -0
  128. package/dist-server/shared/skill-md.js +10 -0
  129. package/dist-server/shared/team-backup.js +3 -0
  130. package/dist-server/shared/tool-scope-support.js +25 -0
  131. package/dist-server/shared/tool-scope.js +136 -0
  132. package/dist-server/src/lib/thread-refs.js +406 -0
  133. package/dist-server/vps-container-mcp.js +93 -76
  134. package/dist-server/workspace-backup.worker.js +89 -100
  135. package/enterprise/server/index.js +1 -0
  136. package/package.json +1 -1
  137. package/dist/assets/channel-BU94VpxE.js +0 -1
  138. package/dist/assets/index-Biji-bfL.css +0 -1
  139. package/dist/assets/index-DZlhoES0.js +0 -597
  140. package/dist/assets/swimlanesDiagram-TC7HE7FX-CdHxG8aM.js +0 -8
@@ -65,9 +65,10 @@ export function decisionRetentionDays(configured, env = process.env) {
65
65
  }
66
66
  const actorScope = new AsyncLocalStorage();
67
67
  /** Run `work` as a person's card answer: every `source: "user"` row it
68
- * writes names `actor`, without each resolver having to thread it through. */
69
- export function withDecisionActor(actor, work) {
70
- return actorScope.run(actor, work);
68
+ * writes names `actor` (and `via`, for an answer spoken on a Live call),
69
+ * without each resolver having to thread it through. */
70
+ export function withDecisionActor(actor, work, via) {
71
+ return actorScope.run(via ? { actor, via } : { actor }, work);
71
72
  }
72
73
  function monthKey(at) {
73
74
  return `${at.getUTCFullYear()}-${String(at.getUTCMonth() + 1).padStart(2, "0")}`;
@@ -88,10 +89,12 @@ async function writeDecision(dataDir, record) {
88
89
  * the fold that calls this is delivering approvals and cards, and a full
89
90
  * disk must not turn into denied tools. */
90
91
  export function appendDecision(dataDir, row) {
91
- const actor = row.actor ?? (row.source === "user" ? actorScope.getStore() : undefined);
92
+ const scope = row.source === "user" ? actorScope.getStore() : undefined;
93
+ const actor = row.actor ?? scope?.actor;
94
+ const via = row.via ?? scope?.via;
92
95
  // Redact now, not when the queue drains: the row is what was true at the
93
96
  // moment of the decision.
94
- const record = redactSecrets({ at: new Date().toISOString(), ...row, ...(actor ? { actor } : {}) });
97
+ const record = redactSecrets({ at: new Date().toISOString(), ...row, ...(actor ? { actor } : {}), ...(via ? { via } : {}) });
95
98
  const previous = writeQueues.get(dataDir) ?? Promise.resolve();
96
99
  // Serialize appends (and the occasional prune) per directory, so two
97
100
  // simultaneous approvals keep their decision order.
@@ -258,7 +261,7 @@ export function decisionsCsv(rows) {
258
261
  row.summary ?? "",
259
262
  row.rule ?? "",
260
263
  row.unattended ? "yes" : "",
261
- actorLabel(row.actor),
264
+ row.via === "call" ? `${actorLabel(row.actor)} (by voice)` : actorLabel(row.actor),
262
265
  row.threadId,
263
266
  row.requestId ?? "",
264
267
  ].map(csvCell).join(","));
@@ -192,6 +192,8 @@ export function _loadPending() {
192
192
  if (typeof item.targetThreadId === "string" && item.targetThreadId) {
193
193
  loaded.targetThreadId = item.targetThreadId;
194
194
  }
195
+ if (item.oneWay === true)
196
+ loaded.oneWay = true;
195
197
  return [loaded];
196
198
  });
197
199
  if (items.length)
@@ -268,7 +270,7 @@ const MAX_QUEUED_PER_THREAD = 6;
268
270
  export function queueDelegation(bus, from, item, maxDepth, sourceThreadId = from.threadId) {
269
271
  if (item.toBotId === from.id)
270
272
  return { result: "self" };
271
- if (item.depth >= maxDepth)
273
+ if (!item.oneWay && item.depth >= maxDepth)
272
274
  return { result: "too_deep" };
273
275
  const target = bus.store.bot(item.toBotId);
274
276
  if (!target)
@@ -347,6 +349,11 @@ onSettled) {
347
349
  const from = bus.store.botByThread(threadId) ??
348
350
  bus.store.bot(item.sourceBotId);
349
351
  if (!from) {
352
+ appendDeliveryMessage(bus, threadId, item, {
353
+ role: "bot",
354
+ kind: "activity",
355
+ tool: { name: "Send failed — the sending bot no longer exists", ok: false },
356
+ });
350
357
  recordDelegationReceipt({
351
358
  id: item.id,
352
359
  sourceThreadId: threadId,
@@ -373,7 +380,7 @@ onSettled) {
373
380
  result: why.slice(0, 200),
374
381
  });
375
382
  try {
376
- bus.store.appendMessage(threadId, {
383
+ appendDeliveryMessage(bus, threadId, item, {
377
384
  role: "bot",
378
385
  kind: "activity",
379
386
  tool: { name: `error: delegation failed — ${why.slice(0, 120)}`, ok: false },
@@ -393,7 +400,7 @@ onSettled) {
393
400
  if (outcome === "settled" && stillQueued) {
394
401
  const receipt = findDelegationReceipt(item.id);
395
402
  try {
396
- if (receipt)
403
+ if (receipt && !item.oneWay)
397
404
  onSettled?.(receipt);
398
405
  }
399
406
  catch (error) {
@@ -428,6 +435,15 @@ function acknowledgeDelegation(threadId, itemId) {
428
435
  pendingDelegations.delete(threadId);
429
436
  savePending();
430
437
  }
438
+ function appendDeliveryMessage(bus, sourceThreadId, item, message) {
439
+ const threadId = item.oneWay && item.targetThreadId && bus.store.taskByThread(item.toBotId, item.targetThreadId)
440
+ ? item.targetThreadId : sourceThreadId;
441
+ // A failed independent send needs a visible receipt, not a source wake.
442
+ // Never recreate a deleted conversation just to post that failure.
443
+ if (item.oneWay && threadId === sourceThreadId && !sourceThreadBelongsToBot(bus.store, item.sourceBotId, sourceThreadId))
444
+ return;
445
+ bus.store.appendMessage(threadId, message);
446
+ }
431
447
  const isExpired = (item, now) => now - item.queuedAt >= DELEGATION_TTL_MS;
432
448
  /** Past the busy-hold cap — the tighter bound that fires while its target is
433
449
  * still busy, long before the 24-hour window. */
@@ -457,9 +473,9 @@ function expireDelegation(bus, sourceThreadId, item, ownerId) {
457
473
  status: "expired",
458
474
  result: busyHold ? `@${name} was still busy after ${busyHoldCapText()}` : `@${name} was not free to take this for 24 hours`,
459
475
  });
460
- if (!sourceThreadBelongsToBot(bus.store, ownerId, sourceThreadId))
476
+ if (!item.oneWay && !sourceThreadBelongsToBot(bus.store, ownerId, sourceThreadId))
461
477
  return;
462
- bus.store.appendMessage(sourceThreadId, {
478
+ appendDeliveryMessage(bus, sourceThreadId, item, {
463
479
  role: "bot",
464
480
  kind: "activity",
465
481
  tool: {
@@ -511,15 +527,16 @@ export function expireStaleDelegations(bus, now, onSettled) {
511
527
  expireDelegation(bus, threadId, item, ownerId ?? item.sourceBotId);
512
528
  const receipt = findDelegationReceipt(item.id);
513
529
  if (receipt)
514
- expired.push(receipt);
530
+ expired.push({ receipt, oneWay: item.oneWay === true });
515
531
  }
516
532
  }
517
533
  if (!expired.length)
518
534
  return 0;
519
535
  savePending();
520
- for (const receipt of expired) {
536
+ for (const { receipt, oneWay } of expired) {
521
537
  try {
522
- onSettled?.(receipt);
538
+ if (!oneWay)
539
+ onSettled?.(receipt);
523
540
  }
524
541
  catch (error) {
525
542
  console.error("delegation expired but its source could not be resumed", error);
@@ -533,9 +550,14 @@ export function discardDelegations(bus, threadId) {
533
550
  const list = pendingDelegations.get(threadId);
534
551
  if (!list?.length)
535
552
  return;
536
- pendingDelegations.delete(threadId);
553
+ const kept = list.filter(item => item.oneWay);
554
+ const dropped = list.filter(item => !item.oneWay);
555
+ if (kept.length)
556
+ pendingDelegations.set(threadId, kept);
557
+ else
558
+ pendingDelegations.delete(threadId);
537
559
  savePending();
538
- for (const item of list) {
560
+ for (const item of dropped) {
539
561
  recordDelegationReceipt({
540
562
  id: item.id,
541
563
  sourceThreadId: threadId,
@@ -545,14 +567,16 @@ export function discardDelegations(bus, threadId) {
545
567
  result: "the delegating turn did not finish",
546
568
  });
547
569
  }
570
+ if (!dropped.length)
571
+ return;
548
572
  const from = bus.store.botByThread(threadId) ??
549
- bus.store.bot(list.find((item) => bus.store.bot(item.sourceBotId))?.sourceBotId ?? list[0].sourceBotId);
573
+ bus.store.bot(dropped.find((item) => bus.store.bot(item.sourceBotId))?.sourceBotId ?? dropped[0].sourceBotId);
550
574
  if (!from)
551
575
  return;
552
576
  bus.store.appendMessage(threadId, {
553
577
  role: "bot",
554
578
  kind: "activity",
555
- tool: { name: `${list.length} queued delegation${list.length > 1 ? "s" : ""} dropped — the turn did not finish`, ok: false },
579
+ tool: { name: `${dropped.length} queued delegation${dropped.length > 1 ? "s" : ""} dropped — the turn did not finish`, ok: false },
556
580
  });
557
581
  }
558
582
  async function processOne(bus, approvalBus, from, sourceThreadId, item, runTarget) {
@@ -560,7 +584,7 @@ async function processOne(bus, approvalBus, from, sourceThreadId, item, runTarge
560
584
  let target = bus.store.bot(item.toBotId);
561
585
  // Retained approval authorizes the message, not a deleted conversation or
562
586
  // revoked room membership. Check every retry before writing to its source.
563
- if (!sourceThreadBelongsToBot(bus.store, sender.id, sourceThreadId)) {
587
+ if (!item.oneWay && !sourceThreadBelongsToBot(bus.store, sender.id, sourceThreadId)) {
564
588
  recordDelegationReceipt({
565
589
  id: item.id,
566
590
  sourceThreadId,
@@ -580,7 +604,7 @@ async function processOne(bus, approvalBus, from, sourceThreadId, item, runTarge
580
604
  status: "error",
581
605
  result: "no such bot",
582
606
  });
583
- bus.store.appendMessage(sourceThreadId, {
607
+ appendDeliveryMessage(bus, sourceThreadId, item, {
584
608
  role: "bot",
585
609
  kind: "activity",
586
610
  tool: { name: `error: delegation to ${item.toBotId} failed — no such bot`, ok: false },
@@ -621,7 +645,7 @@ async function processOne(bus, approvalBus, from, sourceThreadId, item, runTarge
621
645
  // a denial, which otherwise recreates a deleted source transcript.
622
646
  const current = bus.store.bot(item.toBotId);
623
647
  const currentSender = bus.store.bot(from.id);
624
- if (!current || !currentSender || !sourceThreadBelongsToBot(bus.store, currentSender.id, sourceThreadId)) {
648
+ if (!current || !currentSender || (!item.oneWay && !sourceThreadBelongsToBot(bus.store, currentSender.id, sourceThreadId))) {
625
649
  recordDelegationReceipt({
626
650
  id: item.id,
627
651
  sourceThreadId,
@@ -644,7 +668,7 @@ async function processOne(bus, approvalBus, from, sourceThreadId, item, runTarge
644
668
  approvalOutcome: verdict,
645
669
  result: verdict === "deny" ? "the user denied this handoff" : failure.error,
646
670
  });
647
- bus.store.appendMessage(sourceThreadId, {
671
+ appendDeliveryMessage(bus, sourceThreadId, item, {
648
672
  role: "bot",
649
673
  kind: "activity",
650
674
  tool: { name: verdict === "deny" ? `Delegation to @${target.name} denied by user`
@@ -682,8 +706,9 @@ async function processOne(bus, approvalBus, from, sourceThreadId, item, runTarge
682
706
  // members; otherwise the exchange falls back to the pair DM.
683
707
  const originatingGroup = (item.originatingGroupId ? bus.store.group(item.originatingGroupId) : undefined) ??
684
708
  (sourceThreadId ? bus.store.groupByThread(sourceThreadId) : undefined);
685
- const channel = getOrCreateChannel(bus.store, sender, target, originatingGroup);
686
- mirrorExchange(bus, sender, target, item.message, channel, sourceThreadId);
709
+ const channel = item.oneWay ? undefined : getOrCreateChannel(bus.store, sender, target, originatingGroup);
710
+ if (channel)
711
+ mirrorExchange(bus, sender, target, item.message, channel, sourceThreadId);
687
712
  const reasonLine = item.reason ? `\n\n[Reason: ${item.reason}]` : "";
688
713
  // A fresh thread's first line gets the shared peer-provenance note from
689
714
  // the harness (which knows whether the opener was unattended); the
@@ -691,7 +716,7 @@ async function processOne(bus, approvalBus, from, sourceThreadId, item, runTarge
691
716
  const prefixed = item.targetThreadId
692
717
  ? item.message
693
718
  : `[Delegated by @${sender.name}, another bot in this OpenMausBot workspace. Do the work and reply directly.]\n\n${item.message}${reasonLine}`;
694
- await runTarget(item.toBotId, prefixed, item.depth + 1, sourceThreadId, channel, item.id, sender.id, item.targetThreadId);
719
+ await runTarget(item.toBotId, prefixed, item.oneWay ? 0 : item.depth + 1, sourceThreadId, channel, item.id, sender.id, item.targetThreadId, item.oneWay === true);
695
720
  return "dispatched";
696
721
  }
697
722
  /** "Is the target free to take this handoff right now?" Both shapes ask for
@@ -723,7 +748,7 @@ function holdWhileTargetBusy(bus, target, sourceThreadId, item, canTakeTurn = ta
723
748
  item.busySince = Date.now();
724
749
  if (!item.waitAnnounced) {
725
750
  item.waitAnnounced = true;
726
- bus.store.appendMessage(sourceThreadId, {
751
+ appendDeliveryMessage(bus, sourceThreadId, item, {
727
752
  role: "bot",
728
753
  kind: "activity",
729
754
  tool: { name: waitingChipText(bus.store, target, item) },
@@ -757,7 +782,7 @@ function dropIfThreadGone(bus, target, sourceThreadId, item) {
757
782
  status: "dropped",
758
783
  result: `the thread opened on @${target.name} was deleted before it could start`,
759
784
  });
760
- bus.store.appendMessage(sourceThreadId, {
785
+ appendDeliveryMessage(bus, sourceThreadId, item, {
761
786
  role: "bot",
762
787
  kind: "activity",
763
788
  tool: { name: `Thread on @${target.name} canceled — it was deleted before it could start`, ok: false },
@@ -795,7 +820,7 @@ function dropIfUnreachable(bus, sender, target, sourceThreadId, item) {
795
820
  status: "dropped",
796
821
  result,
797
822
  });
798
- bus.store.appendMessage(sourceThreadId, {
823
+ appendDeliveryMessage(bus, sourceThreadId, item, {
799
824
  role: "bot",
800
825
  kind: "activity",
801
826
  tool: { name: `Delegation to @${target.name} canceled — ${reason}`, ok: false },
@@ -41,6 +41,12 @@ import { classifyQuiet, describeQuiet, lastSeenPhrase, LogTail, quietKey, sample
41
41
  export function skipSubscriptionAuthForLocalInject(model) {
42
42
  return Boolean(decodeInjectId(model));
43
43
  }
44
+ /** A catalog can contain credential-bearing native configuration, including late replies. */
45
+ export function acpNativeIncomingLogMessage(message, privateResponses) {
46
+ if (!privateResponses.has(message?.id))
47
+ return message;
48
+ return { jsonrpc: message.jsonrpc, id: message.id, ...(message.error !== undefined ? { error: "[MCP catalog error omitted]" } : { result: "[MCP catalog omitted]" }) };
49
+ }
44
50
  import { newEventId, newId, TurnNotStartedError } from "../../contracts.js";
45
51
  import { augmentedPath } from "../../env-path.js";
46
52
  import { supportsApprovalMode } from "../../../shared/approval-mode.js";
@@ -53,6 +59,8 @@ import { redactSecretsInText } from "../../redact.js";
53
59
  import { recoveryPromptFor } from "../../resume-recovery.js";
54
60
  import { sessionIdlePolicy } from "../session-idle.js";
55
61
  import { classifyError } from "../retry.js";
62
+ import { canUseMcpServer, narrowsNativeTools, parseToolScope } from "../../../shared/tool-scope.js";
63
+ import { gateServer } from "../../mcp-gate-config.js";
56
64
  const ACCOUNT_ERROR_CODES = new Set([
57
65
  "invalid_credentials", "inactive_subscription", "insufficient_funds", "quota_or_region_restriction",
58
66
  ]);
@@ -133,6 +141,18 @@ function trackRunningTool(current, update, defaultStatus) {
133
141
  else if (status === "pending" || status === "in_progress")
134
142
  current.runningTools.add(update.toolCallId);
135
143
  }
144
+ let unkeyedToolSeq = 0;
145
+ /** Stamp an unkeyed `tool_call` with a synthetic id and queue it for the
146
+ * call's first terminal update, which carries no id of its own to match.
147
+ * FIFO is the best available pairing when the agent omits `toolCallId`:
148
+ * the ids exist to pair start with completion, not to order concurrent
149
+ * unkeyed calls, and the synthetic id deliberately stays out of
150
+ * `runningTools` so turn-settle semantics are unchanged. */
151
+ function nextUnkeyedToolId(current) {
152
+ const id = `acp-tool-unkeyed-${++unkeyedToolSeq}`;
153
+ current.unkeyedToolIds.push(id);
154
+ return id;
155
+ }
136
156
  const CLIENT_FILE_MAX_BYTES = 8 * 1024 * 1024;
137
157
  function acpVariantOption(result) {
138
158
  const option = (Array.isArray(result?.configOptions) ? result.configOptions : []).find((entry) => entry?.type === "select" && typeof entry.id === "string"
@@ -509,7 +529,18 @@ export function createAcpDriver(support) {
509
529
  }
510
530
  servers.push({ name, command: server.command, args: server.args, env: acpEnv(server.env) });
511
531
  }
512
- return servers;
532
+ if (turn.toolScope === undefined)
533
+ return servers;
534
+ return servers.filter((server) => canUseMcpServer(turn.toolScope, server.name)).map((server) => {
535
+ const original = "url" in server
536
+ ? { type: server.type, url: server.url, headers: Object.fromEntries(server.headers.map(({ name, value }) => [name, value])) }
537
+ : { command: server.command, args: server.args, env: Object.fromEntries(server.env.map(({ name, value }) => [name, value])) };
538
+ const gated = gateServer({ name: server.name, server: original, threadId: turn.threadId, budget: 0,
539
+ toolScope: turn.toolScope, nodeEnv: { ELECTRON_RUN_AS_NODE: "1" } });
540
+ if (!gated)
541
+ throw new Error("Tool selection requires an MCP gate.");
542
+ return { name: server.name, command: gated.command, args: gated.args, env: acpEnv(gated.env) };
543
+ });
513
544
  };
514
545
  /** While a prompt runs, notice when the agent goes quiet and tell the
515
546
  * person what it is doing: retrying, compressing, waiting on its
@@ -667,6 +698,7 @@ export function createAcpDriver(support) {
667
698
  stdio: ["pipe", "pipe", "pipe"],
668
699
  });
669
700
  let nextId = 1;
701
+ const privateResponses = new Set();
670
702
  const rpcPending = new Map();
671
703
  const send = (obj) => {
672
704
  // A permission/file response resumes an agent that was waiting on us.
@@ -683,6 +715,8 @@ export function createAcpDriver(support) {
683
715
  };
684
716
  const request = (method, params, timeoutMs, receive, idleMs, idleMessage) => new Promise((resolve, reject) => {
685
717
  const id = nextId++;
718
+ if (method === "_x.ai/mcp/list")
719
+ privateResponses.add(id);
686
720
  let timer = null;
687
721
  if (timeoutMs) {
688
722
  timer = setTimeout(() => {
@@ -1007,7 +1041,7 @@ export function createAcpDriver(support) {
1007
1041
  ...base(threadId, current.turnId),
1008
1042
  type: "item.started",
1009
1043
  itemType: "tool",
1010
- itemId: u.toolCallId,
1044
+ itemId: (typeof u.toolCallId === "string" && u.toolCallId) ? u.toolCallId : nextUnkeyedToolId(current),
1011
1045
  title: String(u.rawInput?.command ?? u.title ?? "tool").slice(0, 80),
1012
1046
  summary: commandSummary(u.rawInput),
1013
1047
  input: toolDetailPreview(u.rawInput),
@@ -1022,7 +1056,7 @@ export function createAcpDriver(support) {
1022
1056
  ...base(threadId, current.turnId),
1023
1057
  type: "item.completed",
1024
1058
  itemType: "tool",
1025
- itemId: u.toolCallId,
1059
+ itemId: (typeof u.toolCallId === "string" && u.toolCallId) ? u.toolCallId : current.unkeyedToolIds.shift(),
1026
1060
  ok: u.status !== "failed",
1027
1061
  output: toolDetailPreview(u.rawOutput ?? u.content),
1028
1062
  });
@@ -1084,7 +1118,7 @@ export function createAcpDriver(support) {
1084
1118
  catch {
1085
1119
  continue;
1086
1120
  }
1087
- appendNative(threadId, { dir: "in", source: SOURCE, msg: nativeLogMessage(msg) });
1121
+ appendNative(threadId, { dir: "in", source: SOURCE, msg: nativeLogMessage(acpNativeIncomingLogMessage(msg, privateResponses)) });
1088
1122
  // Inbound traffic proves the child is alive and making progress,
1089
1123
  // so every idle deadline restarts; only total silence trips it.
1090
1124
  lastInboundAt = Date.now();
@@ -1160,6 +1194,13 @@ export function createAcpDriver(support) {
1160
1194
  return session;
1161
1195
  };
1162
1196
  const sendTurn = async (turn) => {
1197
+ const parsedScope = parseToolScope(turn.toolScope);
1198
+ if (!parsedScope.ok)
1199
+ throw new Error(parsedScope.error);
1200
+ turn = { ...turn, toolScope: parsedScope.scope };
1201
+ if (narrowsNativeTools(turn.toolScope) && !support.toolScopeSessionParams) {
1202
+ throw new Error(`${support.displayName}: native tool selection is not supported by this engine. Keep native:* in the selection or choose a supported engine.`);
1203
+ }
1163
1204
  const { threadId } = turn;
1164
1205
  if (active.has(threadId))
1165
1206
  throw new Error("a turn is already running on this thread");
@@ -1190,7 +1231,7 @@ export function createAcpDriver(support) {
1190
1231
  }
1191
1232
  const resolvedModel = support.resolveTurnModel?.(turn.model, env);
1192
1233
  support.applyTurnEnv?.(env, {
1193
- model: resolvedModel, requestedModel: turn.model, fullAuto: turnConfig.fullAuto === true, botId: turn.botId, cwd,
1234
+ model: resolvedModel, requestedModel: turn.model, fullAuto: turnConfig.fullAuto === true, botId: turn.botId, cwd, toolScope: turn.toolScope,
1194
1235
  });
1195
1236
  // Rebound once, before the prompt, when a fallbackModel support swaps
1196
1237
  // a model this session does not offer for one it does.
@@ -1234,6 +1275,8 @@ export function createAcpDriver(support) {
1234
1275
  // riding a child spawned under the old env. Hash it so secrets
1235
1276
  // never sit in the key itself.
1236
1277
  const envFingerprint = createHash("sha256").update(JSON.stringify(spawnEnv)).digest("hex").slice(0, 16);
1278
+ const inheritedScopeFingerprint = narrowsNativeTools(turn.toolScope) && support.toolScopeCacheKey
1279
+ ? createHash("sha256").update(support.toolScopeCacheKey({ config: turnConfig, env, cwd })).digest("hex") : null;
1237
1280
  // A support that applies the approval mode to the session on every
1238
1281
  // turn gets the same process whatever the mode: an approval change
1239
1282
  // is one RPC, not a cold start.
@@ -1241,6 +1284,8 @@ export function createAcpDriver(support) {
1241
1284
  launch.command, launch.args ?? [], spawnArgs, cwd,
1242
1285
  support.sessionScopedApproval ? null : turnConfig.fullAuto === true, envFingerprint,
1243
1286
  support.spawnFingerprint?.(spawnEnv) ?? null,
1287
+ turn.toolScope ?? null,
1288
+ inheritedScopeFingerprint,
1244
1289
  ]);
1245
1290
  // The new-session default model rides the spawn env but stays out of
1246
1291
  // the fingerprint above: only session/new reads it, and a pooled
@@ -1259,7 +1304,7 @@ export function createAcpDriver(support) {
1259
1304
  launchedThisTurn = true;
1260
1305
  return opened;
1261
1306
  };
1262
- const sessionKey = JSON.stringify(mcpServers);
1307
+ const sessionKey = JSON.stringify([mcpServers, turn.toolScope ?? null, inheritedScopeFingerprint]);
1263
1308
  if (turn.sessionReset) {
1264
1309
  closeSession(threadId, "reset");
1265
1310
  sessionAllows.delete(threadId);
@@ -1357,6 +1402,7 @@ export function createAcpDriver(support) {
1357
1402
  acknowledge,
1358
1403
  asks,
1359
1404
  runningTools: new Set(),
1405
+ unkeyedToolIds: [],
1360
1406
  interruptTimer: null,
1361
1407
  stopQuietWatch: null,
1362
1408
  flushAssistantText,
@@ -1467,10 +1513,12 @@ export function createAcpDriver(support) {
1467
1513
  // an agent that advertised its transport, so an agent without
1468
1514
  // http/sse never sees an entry it would refuse the session over
1469
1515
  const sessionServers = mcpServers.filter((server) => !("type" in server) || init?.agentCapabilities?.mcpCapabilities?.[server.type] === true);
1516
+ const selectionParams = narrowsNativeTools(turn.toolScope)
1517
+ ? support.toolScopeSessionParams(turn, init, sessionServers.length > 0, { config: turnConfig, env, cwd }) : {};
1470
1518
  let loaded = false;
1471
1519
  if (cursor) {
1472
1520
  try {
1473
- await request(support.resumeMethod === "resume" ? "session/resume" : "session/load", { sessionId: cursor, cwd, mcpServers: sessionServers }, LOAD_SESSION_TIMEOUT, (result) => {
1521
+ await request(support.resumeMethod === "resume" ? "session/resume" : "session/load", { sessionId: cursor, cwd, mcpServers: sessionServers, ...selectionParams }, LOAD_SESSION_TIMEOUT, (result) => {
1474
1522
  if (result) {
1475
1523
  loaded = true;
1476
1524
  session.sessionId = cursor;
@@ -1526,7 +1574,7 @@ export function createAcpDriver(support) {
1526
1574
  promptTurn = { ...turn, text: recovery.text };
1527
1575
  rebuiltFromReplay = recovery.replayed;
1528
1576
  }
1529
- sessionResult = await request("session/new", { cwd, mcpServers: sessionServers }, NEW_SESSION_TIMEOUT, (result) => {
1577
+ sessionResult = await request("session/new", { cwd, mcpServers: sessionServers, ...selectionParams }, NEW_SESSION_TIMEOUT, (result) => {
1530
1578
  session.sessionId = typeof result?.sessionId === "string" ? result.sessionId : null;
1531
1579
  session.sessionKey = sessionKey;
1532
1580
  receiveModelVariants(result);
@@ -1612,6 +1660,7 @@ export function createAcpDriver(support) {
1612
1660
  sessionModels: Array.isArray(sessionResult?.models?.availableModels)
1613
1661
  ? sessionResult.models.availableModels
1614
1662
  : [],
1663
+ currentModelId: session.sessionConfigResult?.models?.currentModelId,
1615
1664
  });
1616
1665
  approvalUnconfirmed = false;
1617
1666
  // initialize's currentModelId is the CLI default,