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.
- package/dist/assets/{DesktopViewer-CKNR3SEW.js → DesktopViewer-bxM7NV3N.js} +1 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-Ckrm1I1X.js → abnfDiagram-O67JEVCF-BhMdJhRJ.js} +1 -1
- package/dist/assets/{arc-BBdBuCgB.js → arc-B5itKDcx.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-CDdA3Hjs.js → architectureDiagram-NJMV4G6O-C5d7UY6U.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-AUrsx1YR.js → blockDiagram-BEXU5L5S-BrZIhMmu.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-BEqQ0Unh.js → c4Diagram-YGBWAQC7-7I0ArfbD.js} +1 -1
- package/dist/assets/channel-CdJX6i8k.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-CEEuvd4C.js → chunk-2Q5K7J3B-BnEsyN36.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-DyANCiHl.js → chunk-3FUC2YCW-BXATQ52s.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-Dx_Slpw9.js → chunk-5VM5RSS4-B0BpTkEx.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-Dl7h7iiD.js → chunk-GTNCS2PH-BtLTz6FR.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-BLx07SPb.js → chunk-GWA4HPMP-D6gcZOYt.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BMxDj3n7.js → chunk-JWPE2WC7-DCLrosvf.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-CN66PT4H.js → chunk-NETBCI7D-B9JsknV9.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-6HwhsDKw.js → chunk-WEXAMYUT-CsoTxw7z.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-gWnLt7SN.js → chunk-XXDRQBXY-0F_frB0c.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-CMS5l2ea.js → classDiagram-v2-NBCMYWYE-DkrpnflX.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-CV7sQwIG.js → cose-bilkent-JH36ORCC-BNDwYBt9.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-CSyl-7vl.js → cynefin-OW5HDTMX-JUkNr1Mk.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-BCLKL6az.js → cynefinDiagram-VND7K2PF-DKeh9N4M.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DiaZs6S1.js → dagre-6A5THRUB-ve-6T_wX.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-BeTtmt3I.js → diagram-22UHCM2B-BuJ2d8rT.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-CsdBG-QQ.js → diagram-3UASUU5V-BgOs_Ni-.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-BczvdbR1.js → diagram-ATOU4E4O-DMK6VmcP.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DHTTTMhi.js → diagram-CDSNMT55-DksASlcY.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-RTjHyd8z.js → diagram-MLGK6HIB-Bgn8I5k6.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-BDenHEH_.js → diagram-MPIPVDR6-zK0jqO8N.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-BSiRSbtL.js → ebnfDiagram-ZINNZB2B-vKrbzTwr.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-BCAElss-.js → elk-276RUBZZ-Bzv2mPNf.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-C4_1ZPJe.js → erDiagram-OPXOYQCR-CvOyYuxu.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-Bcb4Z75S.js → flowDiagram-KWPJA3E3-CtbnDbc0.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-BiPOzxzb.js → ganttDiagram-FUAMR5RP-c4dMRYyG.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-B1wtF3ZS.js → gitGraphDiagram-X574FWY7-B34uaQho.js} +1 -1
- package/dist/assets/index-CPYrGsv1.js +610 -0
- package/dist/assets/{index-BkYwilkG.js → index-CPiqc_DN.js} +1 -1
- package/dist/assets/index-CzT42GR8.css +1 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-A19No71U.js → infoDiagram-VRGFBTTK-o64ma4CI.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-BQsqJ_cV.js → ishikawaDiagram-OU5B5YK6-DV4C9BAm.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-DAO0pVan.js → journeyDiagram-ZHPQQLJL-Bf0AHk1H.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-NepmKEe2.js → kanban-definition-PNTS6WVX-B1UMSKmE.js} +1 -1
- package/dist/assets/{linear-bq1PTDTW.js → linear-BpKhuyLm.js} +1 -1
- package/dist/assets/{mermaid.core-s4LTbQle.js → mermaid.core-B_BKNCw5.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-LtdbgHFH.js → mindmap-definition-NLK3R4M7-DeARruT1.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-tdbaCxiK.js → pegDiagram-GJSIUBJH-BOQEk_1D.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DvkbpW8l.js → pieDiagram-5QR66LMP-EIfoiTZo.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-Do4KaH-E.js → quadrantDiagram-O4NWA36T-GgZwT-fi.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-BQWULOAs.js → railroadDiagram-XR7U4H2S-D9rDQomj.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-w9kei1FU.js → requirementDiagram-PLB6GJNP-DAh1MIeo.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-D2gRy0e8.js → sankeyDiagram-IPEJSGJF-CPr6Uz64.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-a2sO5_Eo.js → sequenceDiagram-PO4LG4MO-BU6VIdcm.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-CiPJmFEB.js → sizeCapture-INFHLROL-qF2HO41r.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-DAkUUlL7.js → stateDiagram-v2-GCMORJYK-DlJ8G4MD.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-DANf2R8Y.js → swimlanes-2SLR337P-DzYE8I54.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-D-U4GtLQ.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-BKpQmzrT.js → timeline-definition-EJHVYXUP-B7wzEQ0A.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-BtI7qLN-.js → usecaseDiagram-POWQR4AR-BvGgrxIP.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-gXJXrAG_.js → vennDiagram-UO4OBE2U-CEECdgQg.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-GU8Po5-n.js → wardleyDiagram-VNRHLVJA-XQHA0P_P.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-BF4J30g6.js → xychartDiagram-PMCCYNJV-B5mLcZI2.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +1 -0
- package/dist-server/companion/src/routes.js +21 -0
- package/dist-server/container-mcp.js +93 -76
- package/dist-server/drivers/agents-proxy.js +38 -1
- package/dist-server/drivers/pi-mcp-extension.ts +245 -299
- package/dist-server/enterprise/server/index.js +1 -0
- package/dist-server/index.js +16942 -13191
- package/dist-server/local-computer.js +1 -0
- package/dist-server/mcp-gate.js +147 -19
- package/dist-server/mcp-remote-proxy.js +437 -0
- package/dist-server/openmausbot.js +8233 -7651
- package/dist-server/pair-cli.js +8233 -7651
- package/dist-server/permission-proxy.js +65 -1
- package/dist-server/proxy-paths.js +1 -0
- package/dist-server/server/auto-approve.js +33 -11
- package/dist-server/server/browser-live.js +14 -5
- package/dist-server/server/browser-runtime.js +6 -0
- package/dist-server/server/channel-queue.js +4 -1
- package/dist-server/server/cli.js +32 -12
- package/dist-server/server/cloud-move-http.js +9 -3
- package/dist-server/server/cloud-move.js +18 -0
- package/dist-server/server/computer-wait.js +33 -13
- package/dist-server/server/config.js +44 -10
- package/dist-server/server/decision-log.js +9 -6
- package/dist-server/server/delegations.js +47 -22
- package/dist-server/server/drivers/acp/core.js +57 -8
- package/dist-server/server/drivers/acp/grok.js +208 -3
- package/dist-server/server/drivers/agents-call.js +28 -0
- package/dist-server/server/drivers/agents-catalog.js +10 -1
- package/dist-server/server/drivers/boatagent.js +2 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +27 -14
- package/dist-server/server/drivers/claude.js +18 -6
- package/dist-server/server/drivers/codex.js +84 -10
- package/dist-server/server/drivers/openai-chat.js +11 -3
- package/dist-server/server/drivers/pi.js +48 -4
- package/dist-server/server/environment.js +3 -0
- package/dist-server/server/guarded-requests.js +2 -1
- package/dist-server/server/index.js +1143 -254
- package/dist-server/server/live-call-controller.js +868 -0
- package/dist-server/server/live-call.js +216 -0
- package/dist-server/server/mcp-gate-config.js +43 -12
- package/dist-server/server/mcp-gate.js +127 -25
- package/dist-server/server/mcp-http.js +46 -8
- package/dist-server/server/mcp-remote-proxy.js +108 -0
- package/dist-server/server/new-bot-defaults.js +3 -0
- package/dist-server/server/peer-provenance.js +4 -2
- package/dist-server/server/permission-proxy.js +15 -1
- package/dist-server/server/plan-usage.js +797 -0
- package/dist-server/server/proxy-paths.js +1 -0
- package/dist-server/server/redact.js +2 -0
- package/dist-server/server/request-auth.js +3 -2
- package/dist-server/server/routes/live.js +98 -0
- package/dist-server/server/skill-library.js +207 -0
- package/dist-server/server/skills-library-migration.js +235 -0
- package/dist-server/server/skills.js +66 -3
- package/dist-server/server/steer-queue.js +13 -1
- package/dist-server/server/store.js +32 -4
- package/dist-server/server/surface.js +9 -3
- package/dist-server/server/team-backup.js +2 -1
- package/dist-server/server/thread-events.js +1 -1
- package/dist-server/server/tunnel.js +20 -1
- package/dist-server/server/turn-log.js +10 -0
- package/dist-server/server/turn-resources.js +124 -0
- package/dist-server/shared/ask-question.js +13 -0
- package/dist-server/shared/call-consent.js +28 -0
- package/dist-server/shared/live-approval.js +124 -0
- package/dist-server/shared/live-call.js +176 -0
- package/dist-server/shared/skill-md.js +10 -0
- package/dist-server/shared/team-backup.js +3 -0
- package/dist-server/shared/tool-scope-support.js +25 -0
- package/dist-server/shared/tool-scope.js +136 -0
- package/dist-server/src/lib/thread-refs.js +406 -0
- package/dist-server/vps-container-mcp.js +93 -76
- package/dist-server/workspace-backup.worker.js +89 -100
- package/enterprise/server/index.js +1 -0
- package/package.json +1 -1
- package/dist/assets/channel-BU94VpxE.js +0 -1
- package/dist/assets/index-Biji-bfL.css +0 -1
- package/dist/assets/index-DZlhoES0.js +0 -597
- 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`,
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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: `${
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|