openmausbot 0.1.93 → 0.1.95
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-BwWpc4RK.js} +1 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-Ckrm1I1X.js → abnfDiagram-O67JEVCF-jsQkIqTn.js} +1 -1
- package/dist/assets/{arc-BBdBuCgB.js → arc-7kei4Hsu.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-CDdA3Hjs.js → architectureDiagram-NJMV4G6O-CRWDFDMq.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-AUrsx1YR.js → blockDiagram-BEXU5L5S-CTKW_IKs.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-BEqQ0Unh.js → c4Diagram-YGBWAQC7-BBTW7-6M.js} +1 -1
- package/dist/assets/channel-DS2NbT5x.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-CEEuvd4C.js → chunk-2Q5K7J3B-M_Ox-MkD.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-DyANCiHl.js → chunk-3FUC2YCW-D2YiN-Ud.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-Dx_Slpw9.js → chunk-5VM5RSS4-BmVYJ0f8.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-Dl7h7iiD.js → chunk-GTNCS2PH-BtiLVVnZ.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-BLx07SPb.js → chunk-GWA4HPMP-DFYSb6J6.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BMxDj3n7.js → chunk-JWPE2WC7-mknKBzgV.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-CN66PT4H.js → chunk-NETBCI7D-DgSYGiOu.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-6HwhsDKw.js → chunk-WEXAMYUT-C7ZvuguH.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-gWnLt7SN.js → chunk-XXDRQBXY-CPqxINpA.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-CMS5l2ea.js → classDiagram-v2-NBCMYWYE-Cd6xfJaC.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-CV7sQwIG.js → cose-bilkent-JH36ORCC-BSXY5qRh.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-CSyl-7vl.js → cynefin-OW5HDTMX-D_lhEMj1.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-BCLKL6az.js → cynefinDiagram-VND7K2PF-Da9FrFwk.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DiaZs6S1.js → dagre-6A5THRUB-T77jMOXe.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-BeTtmt3I.js → diagram-22UHCM2B-Dkj96X5E.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-CsdBG-QQ.js → diagram-3UASUU5V-CMi9zlD_.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-BczvdbR1.js → diagram-ATOU4E4O-CXK6YUfS.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DHTTTMhi.js → diagram-CDSNMT55-BE7B1u9Q.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-RTjHyd8z.js → diagram-MLGK6HIB-CCMQphKr.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-BDenHEH_.js → diagram-MPIPVDR6-iHJ_-tGH.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-BSiRSbtL.js → ebnfDiagram-ZINNZB2B-DONsCWQ7.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-BCAElss-.js → elk-276RUBZZ-CU_WOaXk.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-C4_1ZPJe.js → erDiagram-OPXOYQCR-BeYwM04G.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-Bcb4Z75S.js → flowDiagram-KWPJA3E3-Bdw9DqEl.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-BiPOzxzb.js → ganttDiagram-FUAMR5RP-C9iwq3qm.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-B1wtF3ZS.js → gitGraphDiagram-X574FWY7-Dx-VWzUg.js} +1 -1
- package/dist/assets/{index-BkYwilkG.js → index-KygHRE3E.js} +1 -1
- package/dist/assets/index-NF7XCHAy.css +1 -0
- package/dist/assets/index-X_5ydXIE.js +610 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-A19No71U.js → infoDiagram-VRGFBTTK-BG7uaEd4.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-BQsqJ_cV.js → ishikawaDiagram-OU5B5YK6-D72WAXnM.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-DAO0pVan.js → journeyDiagram-ZHPQQLJL-DKH1gjYT.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-NepmKEe2.js → kanban-definition-PNTS6WVX-3upm9JUT.js} +1 -1
- package/dist/assets/{linear-bq1PTDTW.js → linear-CcR4pc0-.js} +1 -1
- package/dist/assets/{mermaid.core-s4LTbQle.js → mermaid.core-C6yU5FSS.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-LtdbgHFH.js → mindmap-definition-NLK3R4M7-DOI2xEuv.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-tdbaCxiK.js → pegDiagram-GJSIUBJH-BVWJSgLd.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DvkbpW8l.js → pieDiagram-5QR66LMP-jtbYezhq.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-Do4KaH-E.js → quadrantDiagram-O4NWA36T-g0u8mtPX.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-BQWULOAs.js → railroadDiagram-XR7U4H2S-k8689Wny.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-w9kei1FU.js → requirementDiagram-PLB6GJNP-DsJ22PDt.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-D2gRy0e8.js → sankeyDiagram-IPEJSGJF-CneWBRgG.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-a2sO5_Eo.js → sequenceDiagram-PO4LG4MO-DWsFCWap.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-CiPJmFEB.js → sizeCapture-INFHLROL-Bvf0YE3S.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-DAkUUlL7.js → stateDiagram-v2-GCMORJYK-Bxb3Iei8.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-DANf2R8Y.js → swimlanes-2SLR337P-DLkk7W4A.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C6bZtBXk.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-BKpQmzrT.js → timeline-definition-EJHVYXUP-KR9bIHYC.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-BtI7qLN-.js → usecaseDiagram-POWQR4AR-DfsOrj8t.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-gXJXrAG_.js → vennDiagram-UO4OBE2U-DRSTzG3h.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-GU8Po5-n.js → wardleyDiagram-VNRHLVJA-DtEdhPgT.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-BF4J30g6.js → xychartDiagram-PMCCYNJV-B-XhTxjG.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +3 -1
- package/dist-server/companion/src/routes.js +60 -0
- package/dist-server/container-mcp.js +94 -77
- package/dist-server/drivers/agents-proxy.js +96 -39
- package/dist-server/drivers/pi-mcp-extension.ts +245 -299
- package/dist-server/enterprise/server/index.js +3 -1
- package/dist-server/{browser-proxy.js → harness-mcp-proxy.js} +35 -19
- package/dist-server/index.js +18242 -13645
- package/dist-server/local-computer.js +3 -1
- package/dist-server/mcp-gate.js +147 -19
- package/dist-server/mcp-remote-proxy.js +437 -0
- package/dist-server/openmausbot.js +9618 -10058
- package/dist-server/pair-cli.js +9618 -10058
- package/dist-server/permission-proxy.js +65 -1
- package/dist-server/proxy-paths.js +3 -1
- package/dist-server/server/account-signin.js +0 -2
- package/dist-server/server/activity.js +201 -0
- package/dist-server/server/auto-approve.js +39 -11
- package/dist-server/server/automatic-recovery.js +1 -1
- package/dist-server/server/boat-create-idempotency.js +0 -7
- package/dist-server/server/boat-delete-journal.js +0 -4
- package/dist-server/server/bot-package.js +1 -19
- package/dist-server/server/browser-live.js +14 -5
- package/dist-server/server/browser-runtime.js +7 -2
- package/dist-server/server/channel-queue.js +4 -1
- package/dist-server/server/chief-of-staff.js +2 -3
- package/dist-server/server/cli.js +32 -12
- package/dist-server/server/cloud-backend.js +0 -4
- package/dist-server/server/cloud-computer-tools.js +110 -0
- package/dist-server/server/cloud-home.js +0 -1
- package/dist-server/server/cloud-lending.js +0 -3
- package/dist-server/server/cloud-move-http.js +9 -3
- package/dist-server/server/cloud-move.js +18 -0
- package/dist-server/server/composio.js +31 -2
- package/dist-server/server/computer-control.js +0 -15
- package/dist-server/server/computer-observation.js +0 -74
- package/dist-server/server/computer-wait.js +33 -13
- package/dist-server/server/config.js +73 -11
- package/dist-server/server/container-computer.js +7 -19
- package/dist-server/server/contracts.js +9 -0
- package/dist-server/server/control-client.js +4 -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 +64 -13
- package/dist-server/server/drivers/acp/grok.js +208 -3
- package/dist-server/server/drivers/agents-call.js +70 -29
- package/dist-server/server/drivers/agents-catalog.js +34 -12
- package/dist-server/server/drivers/agents-proxy.js +8 -30
- package/dist-server/server/drivers/antigravity.js +1 -4
- package/dist-server/server/drivers/boatagent.js +25 -4
- package/dist-server/server/drivers/cerebras.js +3 -3
- package/dist-server/server/drivers/chat-mcp-tools.js +29 -19
- package/dist-server/server/drivers/claude.js +33 -19
- package/dist-server/server/drivers/codex.js +84 -10
- package/dist-server/server/drivers/openai-chat.js +14 -9
- package/dist-server/server/drivers/pi.js +48 -4
- package/dist-server/server/drivers/retry.js +8 -4
- package/dist-server/server/engine-install.js +1 -5
- package/dist-server/server/environment.js +3 -0
- package/dist-server/server/guarded-requests.js +2 -1
- package/dist-server/server/harness/registry.js +0 -1
- package/dist-server/server/{browser-proxy.js → harness-mcp-proxy.js} +40 -20
- package/dist-server/server/index.js +1929 -507
- package/dist-server/server/lending-memory.js +12 -8
- 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/memory-entries.js +38 -8
- package/dist-server/server/memory-organize.js +1 -8
- package/dist-server/server/memory-topics.js +4 -4
- package/dist-server/server/memory-upkeep.js +13 -15
- package/dist-server/server/message-file.js +5 -9
- package/dist-server/server/new-bot-defaults.js +3 -0
- package/dist-server/server/outbound-counts.js +79 -0
- package/dist-server/server/outbound-requests.js +60 -0
- package/dist-server/server/package-import.js +1 -1
- package/dist-server/server/peer-delivery.js +1 -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 +3 -1
- package/dist-server/server/redact.js +2 -0
- package/dist-server/server/request-auth.js +3 -2
- package/dist-server/server/room-handoffs.js +5 -13
- package/dist-server/server/routes/live.js +98 -0
- package/dist-server/server/routine-requests.js +1 -1
- package/dist-server/server/routines.js +5 -1
- package/dist-server/server/setup-mode.js +0 -2
- package/dist-server/server/skill-learn.js +0 -1
- package/dist-server/server/skill-library.js +195 -3
- 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 +41 -5
- package/dist-server/server/surface.js +74 -23
- package/dist-server/server/system-prompt.js +4 -4
- package/dist-server/server/team-backup.js +2 -1
- package/dist-server/server/team-memory.js +209 -0
- package/dist-server/server/thread-events.js +3 -3
- 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/server/vps-computer.js +4 -14
- package/dist-server/server/workspace.js +85 -47
- package/dist-server/shared/activity.js +3 -0
- package/dist-server/shared/ask-question.js +13 -0
- package/dist-server/shared/bot-avatar.js +8 -0
- package/dist-server/shared/call-consent.js +28 -0
- package/dist-server/shared/connector-scopes.js +78 -0
- package/dist-server/shared/live-approval.js +124 -0
- package/dist-server/shared/live-call.js +176 -0
- package/dist-server/shared/outbound.js +128 -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/shared/tool-surface.js +3 -2
- package/dist-server/src/lib/thread-refs.js +406 -0
- package/dist-server/vps-container-mcp.js +94 -77
- package/dist-server/workspace-backup.worker.js +89 -100
- package/enterprise/server/index.js +3 -1
- 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
- package/dist-server/server/drivers/chat-boat-tools.js +0 -90
- package/dist-server/server/drivers/grokagent.js +0 -4
- package/dist-server/server/openmaus-status-capsule.js +0 -329
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// Pure pieces of a Live (GPT-Live) call, kept out of the component so they
|
|
2
|
+
// can be tested without WebRTC.
|
|
3
|
+
//
|
|
4
|
+
// With client delegation GPT-Live says *that* it wants help, never *what*:
|
|
5
|
+
// `session.delegation.created` carries an id and a timeline offset, and the
|
|
6
|
+
// request has to be rebuilt from the input transcript. That reconstruction,
|
|
7
|
+
// and the shaping of the bot's answer into the 500-token appends GPT-Live
|
|
8
|
+
// accepts, live here.
|
|
9
|
+
/** Collects `session.input_transcript.delta` fragments on the session
|
|
10
|
+
* timeline. Fragments are appended in delivery order, as the API asks. */
|
|
11
|
+
export class LiveTranscript {
|
|
12
|
+
input = [];
|
|
13
|
+
/** when the voice itself was speaking, on the same timeline */
|
|
14
|
+
output = [];
|
|
15
|
+
cutoffMs = -1;
|
|
16
|
+
addInput(text, startMs, endMs) {
|
|
17
|
+
if (!text)
|
|
18
|
+
return;
|
|
19
|
+
this.input.push({ text, startMs: Number.isFinite(startMs) ? startMs : 0, endMs: Number.isFinite(endMs) ? endMs : 0 });
|
|
20
|
+
// keep memory bounded on long calls: only the unconsumed tail matters
|
|
21
|
+
if (this.input.length > 2_000)
|
|
22
|
+
this.input = this.input.filter((segment) => segment.startMs > this.cutoffMs);
|
|
23
|
+
}
|
|
24
|
+
/** A stretch of the voice's own speech (a `session.output_transcript.delta`
|
|
25
|
+
* with its timing). Input heard during it may be the voice itself, picked
|
|
26
|
+
* up again by the microphone of a phone on speaker. Deltas without timing
|
|
27
|
+
* are ignored. */
|
|
28
|
+
addOutput(startMs, endMs) {
|
|
29
|
+
if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs < startMs)
|
|
30
|
+
return;
|
|
31
|
+
const last = this.output.at(-1);
|
|
32
|
+
if (last && startMs <= last.endMs)
|
|
33
|
+
last.endMs = Math.max(last.endMs, endMs);
|
|
34
|
+
else
|
|
35
|
+
this.output.push({ startMs, endMs });
|
|
36
|
+
if (this.output.length > 500)
|
|
37
|
+
this.output.splice(0, this.output.length - 500);
|
|
38
|
+
}
|
|
39
|
+
/** The words spoken since the previous request, up to a little past the
|
|
40
|
+
* delegation point (transcription can trail the delegation event, and the
|
|
41
|
+
* person may finish the sentence that triggered it). Consumes them, so the
|
|
42
|
+
* next request starts after this one. */
|
|
43
|
+
takeRequest(offsetMs, graceMs = 1_500) {
|
|
44
|
+
return this.takeRequestParts(offsetMs, graceMs).text;
|
|
45
|
+
}
|
|
46
|
+
/** takeRequest, plus the same words without those heard while the voice
|
|
47
|
+
* spoke (`withoutEcho`), for reading a spoken yes/no. Consumes all of them. */
|
|
48
|
+
takeRequestParts(offsetMs, graceMs = 1_500) {
|
|
49
|
+
const until = offsetMs + graceMs;
|
|
50
|
+
const taken = this.input.filter((segment) => segment.startMs > this.cutoffMs && segment.startMs <= until);
|
|
51
|
+
if (!taken.length)
|
|
52
|
+
return { text: "", withoutEcho: "" };
|
|
53
|
+
this.cutoffMs = Math.max(this.cutoffMs, ...taken.map((segment) => segment.startMs));
|
|
54
|
+
return {
|
|
55
|
+
text: joinFragments(taken.map((segment) => segment.text)),
|
|
56
|
+
withoutEcho: joinFragments(taken.filter((segment) => !this.echoed(segment)).map((segment) => segment.text)),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Everything heard since the last taken request, without consuming it —
|
|
60
|
+
* used to read a spoken yes/no while a permission question is open.
|
|
61
|
+
* `skipEcho` leaves out what was heard while the voice itself spoke. */
|
|
62
|
+
pending(options = {}) {
|
|
63
|
+
return joinFragments(this.input
|
|
64
|
+
.filter((segment) => segment.startMs > this.cutoffMs && !(options.skipEcho && this.echoed(segment)))
|
|
65
|
+
.map((segment) => segment.text));
|
|
66
|
+
}
|
|
67
|
+
echoed(segment) {
|
|
68
|
+
return this.output.some((span) => segment.startMs < span.endMs && segment.endMs > span.startMs);
|
|
69
|
+
}
|
|
70
|
+
/** Mark everything heard so far as handled (e.g. after a spoken decision). */
|
|
71
|
+
consumeAll() {
|
|
72
|
+
for (const segment of this.input)
|
|
73
|
+
this.cutoffMs = Math.max(this.cutoffMs, segment.startMs);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Transcript deltas are fragments of words, not words: glue them as sent. */
|
|
77
|
+
export function joinFragments(fragments) {
|
|
78
|
+
return fragments.join("").replace(/\s+/g, " ").trim();
|
|
79
|
+
}
|
|
80
|
+
/** GPT-Live caps each append at 500 tokens. A 500 UTF-8 byte budget is
|
|
81
|
+
* conservative even for emoji/CJK, unlike a prose-only character estimate.
|
|
82
|
+
* Byte-level tokenization cannot produce more tokens than input bytes. */
|
|
83
|
+
const APPEND_BYTE_LIMIT = 500;
|
|
84
|
+
const encoder = new TextEncoder();
|
|
85
|
+
const bytes = (text) => encoder.encode(text).length;
|
|
86
|
+
/** Standard UTF-8 accounting; stop at a complete code point, not a surrogate. */
|
|
87
|
+
function bytePrefix(text, budget) {
|
|
88
|
+
return new TextDecoder().decode(encoder.encode(text).subarray(0, budget), { stream: true });
|
|
89
|
+
}
|
|
90
|
+
const MAX_SPOKEN_CHUNKS = 3;
|
|
91
|
+
export const FULL_ANSWER_IN_CHAT = "The full answer is in the chat.";
|
|
92
|
+
/** Group speakable utterances (from /api/tts/prepare) into appends. Long
|
|
93
|
+
* answers are cut at a sentence boundary with a pointer to the chat, because
|
|
94
|
+
* a voice reading four minutes of a report is not a conversation. */
|
|
95
|
+
export function commentaryChunks(utterances) {
|
|
96
|
+
const chunks = [];
|
|
97
|
+
let current = "";
|
|
98
|
+
let truncated = false;
|
|
99
|
+
for (const raw of utterances) {
|
|
100
|
+
const utterance = raw.replace(/\s+/g, " ").trim();
|
|
101
|
+
if (!utterance)
|
|
102
|
+
continue;
|
|
103
|
+
const pieces = bytes(utterance) > APPEND_BYTE_LIMIT ? splitLong(utterance) : [utterance];
|
|
104
|
+
for (const piece of pieces) {
|
|
105
|
+
if (current && bytes(current) + 1 + bytes(piece) > APPEND_BYTE_LIMIT) {
|
|
106
|
+
chunks.push(current);
|
|
107
|
+
current = "";
|
|
108
|
+
}
|
|
109
|
+
if (chunks.length >= MAX_SPOKEN_CHUNKS) {
|
|
110
|
+
truncated = true;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
current = current ? `${current} ${piece}` : piece;
|
|
114
|
+
}
|
|
115
|
+
if (truncated)
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
if (current && chunks.length < MAX_SPOKEN_CHUNKS)
|
|
119
|
+
chunks.push(current);
|
|
120
|
+
else if (current)
|
|
121
|
+
truncated = true;
|
|
122
|
+
if (truncated && chunks.length) {
|
|
123
|
+
const last = chunks[chunks.length - 1];
|
|
124
|
+
chunks[chunks.length - 1] = bytes(last) + 1 + bytes(FULL_ANSWER_IN_CHAT) <= APPEND_BYTE_LIMIT
|
|
125
|
+
? `${last} ${FULL_ANSWER_IN_CHAT}`
|
|
126
|
+
: `${bytePrefix(last, APPEND_BYTE_LIMIT - bytes(FULL_ANSWER_IN_CHAT) - 1).replace(/\s+\S*$/, "")} ${FULL_ANSWER_IN_CHAT}`;
|
|
127
|
+
}
|
|
128
|
+
return chunks;
|
|
129
|
+
}
|
|
130
|
+
function splitLong(text) {
|
|
131
|
+
const out = [];
|
|
132
|
+
let rest = text;
|
|
133
|
+
while (bytes(rest) > APPEND_BYTE_LIMIT) {
|
|
134
|
+
const window = bytePrefix(rest, APPEND_BYTE_LIMIT);
|
|
135
|
+
const sentence = Math.max(window.lastIndexOf(". "), window.lastIndexOf("! "), window.lastIndexOf("? "));
|
|
136
|
+
const space = window.lastIndexOf(" ");
|
|
137
|
+
const cut = sentence > window.length / 2 ? sentence + 1 : space > 0 ? space : window.length;
|
|
138
|
+
out.push(rest.slice(0, cut).trim());
|
|
139
|
+
rest = rest.slice(cut).trim();
|
|
140
|
+
}
|
|
141
|
+
if (rest)
|
|
142
|
+
out.push(rest);
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
/** Keep appended content inside the per-append limit. */
|
|
146
|
+
export function clampAppend(content) {
|
|
147
|
+
const text = content.replace(/\s+/g, " ").trim();
|
|
148
|
+
return bytes(text) <= APPEND_BYTE_LIMIT ? text : `${bytePrefix(text, APPEND_BYTE_LIMIT - bytes("…")).replace(/\s+\S*$/, "")}…`;
|
|
149
|
+
}
|
|
150
|
+
/** GPT-Live voices from OpenAI's documentation (Managing GPT-Live sessions,
|
|
151
|
+
* "Voice options", and the SDK's BuiltInVoice list), with the regional
|
|
152
|
+
* notes the docs give for the additional ones. */
|
|
153
|
+
export const LIVE_VOICE_OPTIONS = [
|
|
154
|
+
{ id: "marin", label: "Marin (default)" },
|
|
155
|
+
{ id: "cedar", label: "Cedar" },
|
|
156
|
+
{ id: "alloy", label: "Alloy" },
|
|
157
|
+
{ id: "ash", label: "Ash" },
|
|
158
|
+
{ id: "ballad", label: "Ballad" },
|
|
159
|
+
{ id: "coral", label: "Coral" },
|
|
160
|
+
{ id: "echo", label: "Echo" },
|
|
161
|
+
{ id: "sage", label: "Sage" },
|
|
162
|
+
{ id: "shimmer", label: "Shimmer" },
|
|
163
|
+
{ id: "verse", label: "Verse" },
|
|
164
|
+
{ id: "gleam", label: "Gleam — North American, feminine" },
|
|
165
|
+
{ id: "meridian", label: "Meridian — North American, masculine" },
|
|
166
|
+
{ id: "quartz", label: "Quartz — Australian, feminine" },
|
|
167
|
+
{ id: "ripple", label: "Ripple — Australian, masculine" },
|
|
168
|
+
{ id: "vesper", label: "Vesper — British, masculine" },
|
|
169
|
+
{ id: "willow", label: "Willow — Irish, feminine" },
|
|
170
|
+
{ id: "stone", label: "Stone — Irish, masculine" },
|
|
171
|
+
{ id: "delta", label: "Delta — Southern U.S., feminine" },
|
|
172
|
+
{ id: "cinder", label: "Cinder — Southern U.S., masculine" },
|
|
173
|
+
{ id: "beacon", label: "Beacon — Filipino, masculine" },
|
|
174
|
+
{ id: "bossa", label: "Bossa — Brazilian Portuguese, feminine" },
|
|
175
|
+
{ id: "tempo", label: "Tempo — Brazilian Portuguese, masculine" },
|
|
176
|
+
];
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Outbound actions: the calls that reach a person outside the app, or move
|
|
2
|
+
// money. Sending an email, replying in a thread, posting to a channel,
|
|
3
|
+
// publishing, inviting, sharing, refunding, charging. These get their own
|
|
4
|
+
// confirmation regardless of approval level, and an optional daily
|
|
5
|
+
// allowance per bot, because the cost of one wrong send is paid by someone
|
|
6
|
+
// who never saw the bot.
|
|
7
|
+
//
|
|
8
|
+
// Shared between the harness (which enforces it) and the app (which shows
|
|
9
|
+
// the setting), so both agree on what "outbound" means.
|
|
10
|
+
export const OUTBOUND_DEFAULT_CAP = 25;
|
|
11
|
+
const OUTBOUND_MAX_CAP = 1_000;
|
|
12
|
+
/** Tools whose action is one of these words send something somewhere. */
|
|
13
|
+
const SEND_VERBS = new Set([
|
|
14
|
+
"SEND",
|
|
15
|
+
"SENDS",
|
|
16
|
+
"REPLY",
|
|
17
|
+
"RESPOND",
|
|
18
|
+
"FORWARD",
|
|
19
|
+
"POST",
|
|
20
|
+
"PUBLISH",
|
|
21
|
+
"TWEET",
|
|
22
|
+
"RETWEET",
|
|
23
|
+
"BROADCAST",
|
|
24
|
+
"INVITE",
|
|
25
|
+
"SHARE",
|
|
26
|
+
"REFUND",
|
|
27
|
+
"CHARGE",
|
|
28
|
+
"PAY",
|
|
29
|
+
"PAYOUT",
|
|
30
|
+
"TRANSFER",
|
|
31
|
+
]);
|
|
32
|
+
/** Creating one of these is sending it: a created comment is a comment
|
|
33
|
+
* someone reads, a created invoice is one someone receives. */
|
|
34
|
+
const CREATE_VERBS = new Set(["CREATE", "CREATION", "ADD", "NEW"]);
|
|
35
|
+
const SENT_NOUNS = new Set(["MESSAGE", "COMMENT", "POST", "EMAIL", "MAIL", "INVOICE", "PAYMENT", "REFUND", "REPLY", "TWEET", "DM"]);
|
|
36
|
+
/** A read verb first means the rest of the name is what is being read:
|
|
37
|
+
* REDDIT_GET_POST fetches a post, it does not post. */
|
|
38
|
+
const READ_VERBS = new Set(["GET", "LIST", "FETCH", "SEARCH", "READ", "FIND", "RETRIEVE", "LOOKUP", "DOWNLOAD", "VIEW", "CHECK", "COUNT"]);
|
|
39
|
+
/** The action part of a tool name, as upper-case word tokens. Composio's
|
|
40
|
+
* TOOLKIT_ACTION drops its toolkit; an MCP server's mcp__server__action
|
|
41
|
+
* drops its server; a bare built-in like Bash stays as it is. */
|
|
42
|
+
function actionTokens(name) {
|
|
43
|
+
let action = name;
|
|
44
|
+
if (name.startsWith("mcp__")) {
|
|
45
|
+
const [, server = "", ...rest] = name.split("__");
|
|
46
|
+
action = rest.join("_");
|
|
47
|
+
if (server === "composio")
|
|
48
|
+
return actionTokens(action);
|
|
49
|
+
}
|
|
50
|
+
else if (/^[A-Z][A-Z0-9]*_[A-Z0-9_]+$/.test(name)) {
|
|
51
|
+
action = name.slice(name.indexOf("_") + 1);
|
|
52
|
+
}
|
|
53
|
+
return action
|
|
54
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
55
|
+
.toUpperCase()
|
|
56
|
+
.split(/[^A-Z0-9]+/)
|
|
57
|
+
.filter(Boolean);
|
|
58
|
+
}
|
|
59
|
+
/** Does this tool send something to someone, or move money? */
|
|
60
|
+
export function isOutboundTool(name) {
|
|
61
|
+
// A raw API call from the workbench can do anything an app tool can, and
|
|
62
|
+
// nothing here can read which; it is treated as a send.
|
|
63
|
+
if (name === "COMPOSIO_PROXY_EXECUTE")
|
|
64
|
+
return true;
|
|
65
|
+
const tokens = actionTokens(name);
|
|
66
|
+
if (tokens.length === 0)
|
|
67
|
+
return false;
|
|
68
|
+
if (READ_VERBS.has(tokens[0]))
|
|
69
|
+
return false;
|
|
70
|
+
if (tokens.some((token) => SEND_VERBS.has(token)))
|
|
71
|
+
return true;
|
|
72
|
+
if (tokens.includes("DRAFT"))
|
|
73
|
+
return false;
|
|
74
|
+
return tokens.some((token) => CREATE_VERBS.has(token)) && tokens.some((token) => SENT_NOUNS.has(token));
|
|
75
|
+
}
|
|
76
|
+
/** The stored shape, or null when the value is not one. */
|
|
77
|
+
export function normalizeOutboundPolicy(value) {
|
|
78
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
79
|
+
return null;
|
|
80
|
+
const { policy, dailyCap } = value;
|
|
81
|
+
if (policy !== "ask" && policy !== "allow")
|
|
82
|
+
return null;
|
|
83
|
+
if (dailyCap === undefined)
|
|
84
|
+
return { policy, dailyCap: OUTBOUND_DEFAULT_CAP };
|
|
85
|
+
if (typeof dailyCap !== "number" || !Number.isInteger(dailyCap) || dailyCap < 1 || dailyCap > OUTBOUND_MAX_CAP)
|
|
86
|
+
return null;
|
|
87
|
+
return { policy, dailyCap };
|
|
88
|
+
}
|
|
89
|
+
/** What a bot without a stored policy does: ask, every time. */
|
|
90
|
+
export const DEFAULT_OUTBOUND_POLICY = { policy: "ask", dailyCap: OUTBOUND_DEFAULT_CAP };
|
|
91
|
+
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
92
|
+
/** Composio's session exposes a handful of meta tools and runs the app
|
|
93
|
+
* tools inside them: a multi-execute call carries a list of slugs with
|
|
94
|
+
* arguments. Arbitrary workbench/bash code is opaque: it requires an
|
|
95
|
+
* explicit unrestricted approval, never an inferred app or send count.
|
|
96
|
+
* Search, schema lookup, and connection management run no app tools. */
|
|
97
|
+
export function connectorCallsIn(name, args) {
|
|
98
|
+
if (name === "COMPOSIO_MULTI_EXECUTE_TOOL") {
|
|
99
|
+
const tools = isRecord(args) && Array.isArray(args.tools) ? args.tools
|
|
100
|
+
: isRecord(args) && typeof args.tool_slug === "string" ? [args] : [];
|
|
101
|
+
if (!tools.length)
|
|
102
|
+
return [{ slug: "COMPOSIO_PROXY_EXECUTE", arguments: args }];
|
|
103
|
+
const calls = [];
|
|
104
|
+
for (const item of tools) {
|
|
105
|
+
if (!isRecord(item))
|
|
106
|
+
return [{ slug: "COMPOSIO_PROXY_EXECUTE", arguments: args }];
|
|
107
|
+
const slug = [item.tool_slug, item.slug, item.name, item.tool].find((value) => typeof value === "string" && value);
|
|
108
|
+
if (typeof slug !== "string")
|
|
109
|
+
return [{ slug: "COMPOSIO_PROXY_EXECUTE", arguments: args }];
|
|
110
|
+
calls.push({ slug, ...(item.arguments !== undefined ? { arguments: item.arguments } : {}) });
|
|
111
|
+
}
|
|
112
|
+
return calls;
|
|
113
|
+
}
|
|
114
|
+
if (name === "COMPOSIO_REMOTE_WORKBENCH" || name === "COMPOSIO_REMOTE_BASH_TOOL") {
|
|
115
|
+
// Arbitrary code can compute tool names and repeat calls. Its app and
|
|
116
|
+
// outbound count cannot be inferred from literals in the source.
|
|
117
|
+
return [{ slug: "COMPOSIO_PROXY_EXECUTE", arguments: args }];
|
|
118
|
+
}
|
|
119
|
+
if (["COMPOSIO_SEARCH_TOOLS", "COMPOSIO_GET_TOOL_SCHEMAS", "COMPOSIO_MANAGE_CONNECTIONS", "COMPOSIO_WAIT_FOR_CONNECTIONS"].includes(name))
|
|
120
|
+
return [];
|
|
121
|
+
if (name.startsWith("COMPOSIO_"))
|
|
122
|
+
return [{ slug: "COMPOSIO_PROXY_EXECUTE", arguments: args }];
|
|
123
|
+
return [{ slug: name, ...(args !== undefined ? { arguments: args } : {}) }];
|
|
124
|
+
}
|
|
125
|
+
/** The subset of a request's app tools that would send something. */
|
|
126
|
+
export function outboundCallsIn(name, args) {
|
|
127
|
+
return connectorCallsIn(name, args).filter((call) => isOutboundTool(call.slug));
|
|
128
|
+
}
|
|
@@ -13,6 +13,15 @@ export const SKILL_FILE_MAX_BYTES = 256 * 1024;
|
|
|
13
13
|
export function isSkillName(name) {
|
|
14
14
|
return name.length >= 1 && name.length <= SKILL_NAME_MAX && SKILL_NAME.test(name);
|
|
15
15
|
}
|
|
16
|
+
/** Browsing tags: lowercase slug-ish words, short enough for chip rows. */
|
|
17
|
+
const TAG_PATTERN = /^[a-z0-9][a-z0-9-]{0,23}$/;
|
|
18
|
+
export const SKILL_TAGS_MAX = 8;
|
|
19
|
+
export function parseSkillTags(raw) {
|
|
20
|
+
if (!raw)
|
|
21
|
+
return undefined;
|
|
22
|
+
const tags = [...new Set(raw.split(",").map((tag) => tag.trim().toLowerCase()).filter((tag) => TAG_PATTERN.test(tag)))].slice(0, SKILL_TAGS_MAX);
|
|
23
|
+
return tags.length > 0 ? tags : undefined;
|
|
24
|
+
}
|
|
16
25
|
/** Minimal frontmatter reader for the two required keys plus the two we
|
|
17
26
|
* display. Deliberately not a YAML engine: values are single-line strings in
|
|
18
27
|
* every skill the spec's own examples show, and a parser that cannot
|
|
@@ -41,6 +50,7 @@ export function parseSkillMd(raw) {
|
|
|
41
50
|
description,
|
|
42
51
|
license: fields.license || undefined,
|
|
43
52
|
compatibility: fields.compatibility || undefined,
|
|
53
|
+
tags: parseSkillTags(fields.tags),
|
|
44
54
|
body: match[2] ?? "",
|
|
45
55
|
};
|
|
46
56
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CONNECTOR_SLUG_PATTERN, CONNECTOR_TOOL_NAME_PATTERN } from "./wire.js";
|
|
3
|
+
import { parseToolScope } from "./tool-scope.js";
|
|
3
4
|
export const MAX_TEAM_BACKUP_BYTES = 50 * 1024 * 1024;
|
|
4
5
|
export const TEAM_BACKUP_CONTENTS = "Bot profiles, instructions, sections, rooms, playbooks, routines, each bot's memory (MEMORY.md, topic notes and daily logs) and conversation text (all tasks and branches).";
|
|
5
6
|
export const TEAM_BACKUP_EXCLUSIONS = "Files, images, custom avatars, account connections, model settings and permissions are not included. Action cards are saved as text. Memory is saved with secrets removed. Imported routines start paused.";
|
|
@@ -98,6 +99,8 @@ const backupSchema = z.object({
|
|
|
98
99
|
hidden: z.boolean(),
|
|
99
100
|
playbooks: z.array(playbook).max(200),
|
|
100
101
|
connectorTools: connectorTools.optional(),
|
|
102
|
+
toolScope: z.custom((value) => value !== undefined && parseToolScope(value).ok, "Invalid tool selection")
|
|
103
|
+
.transform((value) => { const parsed = parseToolScope(value); return parsed.ok ? parsed.scope : value; }).optional(),
|
|
101
104
|
memory: memory.optional(),
|
|
102
105
|
})).min(1).max(200),
|
|
103
106
|
groups: z.array(z.preprocess(knownRoomResponder, z.object({
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { narrowsNativeTools, parseToolScope } from "./tool-scope.js";
|
|
2
|
+
/** Native contracts and app-supplied MCP gates are separate capabilities. */
|
|
3
|
+
export const TOOL_SCOPE_SUPPORT = {
|
|
4
|
+
grok: "native-and-mcp", grokAgent: "native-and-mcp", piAgent: "native-and-mcp",
|
|
5
|
+
"openai-compat": "native-and-mcp", mistral: "native-and-mcp", minimax: "native-and-mcp", cerebras: "native-and-mcp",
|
|
6
|
+
claudeAgent: "mcp", codex: "mcp", geminiAgent: "mcp", kimiAgent: "mcp", droidAgent: "mcp",
|
|
7
|
+
cursorAgent: "mcp", opencodeGo: "mcp", qwenAgent: "mcp", hermesAgent: "mcp", customAcp: "mcp", antigravityAgent: "mcp",
|
|
8
|
+
boxAgent: "none",
|
|
9
|
+
};
|
|
10
|
+
export function assertToolScopeSupported(engine, value) {
|
|
11
|
+
const parsed = parseToolScope(value);
|
|
12
|
+
if (!parsed.ok)
|
|
13
|
+
throw new Error(parsed.error);
|
|
14
|
+
const support = TOOL_SCOPE_SUPPORT[engine];
|
|
15
|
+
if (parsed.scope !== undefined && support !== "native-and-mcp") {
|
|
16
|
+
if (support === "none" || support === undefined) {
|
|
17
|
+
if (parsed.scope.allow !== undefined || parsed.scope.deny?.length)
|
|
18
|
+
throw new Error(`${engine}: tool selection is not supported by this engine. Choose a supported engine.`);
|
|
19
|
+
}
|
|
20
|
+
else if (narrowsNativeTools(parsed.scope)) {
|
|
21
|
+
throw new Error(`${engine}: native tool selection is not supported by this engine. Keep native:* in the selection or choose a supported engine.`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return parsed.scope;
|
|
25
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const SERVER_NAME = /^[a-z][a-z0-9_-]{0,31}$/;
|
|
2
|
+
// Selectors must never contain protocol control characters.
|
|
3
|
+
// eslint-disable-next-line no-control-regex
|
|
4
|
+
const CONTROL_CHARACTER = /[\x00-\x1f\x7f]/;
|
|
5
|
+
const MAX_SELECTORS = 256;
|
|
6
|
+
const MAX_SELECTOR_LENGTH = 1024;
|
|
7
|
+
function validName(name) {
|
|
8
|
+
return typeof name === "string" && name.trim().length > 0
|
|
9
|
+
&& name.length <= MAX_SELECTOR_LENGTH && !CONTROL_CHARACTER.test(name)
|
|
10
|
+
&& !name.includes("*");
|
|
11
|
+
}
|
|
12
|
+
function selectorIdentity(selector) {
|
|
13
|
+
if (selector.length > MAX_SELECTOR_LENGTH || CONTROL_CHARACTER.test(selector))
|
|
14
|
+
return;
|
|
15
|
+
if (selector.startsWith("native:")) {
|
|
16
|
+
const name = selector.slice(7);
|
|
17
|
+
if (name === "*" || validName(name))
|
|
18
|
+
return { kind: "native", name };
|
|
19
|
+
}
|
|
20
|
+
else if (selector.startsWith("mcp:")) {
|
|
21
|
+
const separator = selector.indexOf(":", 4);
|
|
22
|
+
if (separator < 0)
|
|
23
|
+
return;
|
|
24
|
+
const server = selector.slice(4, separator);
|
|
25
|
+
const name = selector.slice(separator + 1);
|
|
26
|
+
if (SERVER_NAME.test(server) && (name === "*" || validName(name))) {
|
|
27
|
+
return { kind: "mcp", server, name };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Undefined inherits the existing catalog. A present invalid value never does. */
|
|
32
|
+
export function parseToolScope(value) {
|
|
33
|
+
if (value === undefined)
|
|
34
|
+
return { ok: true, scope: undefined };
|
|
35
|
+
const invalid = { ok: false, error: "Tool selection must contain only valid allow and deny lists." };
|
|
36
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
37
|
+
return invalid;
|
|
38
|
+
const prototype = Object.getPrototypeOf(value);
|
|
39
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
40
|
+
return invalid;
|
|
41
|
+
const scope = {};
|
|
42
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
43
|
+
if (key !== "allow" && key !== "deny")
|
|
44
|
+
return invalid;
|
|
45
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
46
|
+
if (!descriptor || !("value" in descriptor))
|
|
47
|
+
return invalid;
|
|
48
|
+
const list = descriptor.value;
|
|
49
|
+
if (!Array.isArray(list) || list.length > MAX_SELECTORS)
|
|
50
|
+
return invalid;
|
|
51
|
+
// Array.from exposes holes as undefined instead of silently skipping them.
|
|
52
|
+
if (!Array.from(list).every((entry) => typeof entry === "string" && selectorIdentity(entry)))
|
|
53
|
+
return invalid;
|
|
54
|
+
scope[key] = [...new Set(list)];
|
|
55
|
+
}
|
|
56
|
+
return { ok: true, scope };
|
|
57
|
+
}
|
|
58
|
+
function matches(selector, tool) {
|
|
59
|
+
const selected = selectorIdentity(selector);
|
|
60
|
+
return selected?.kind === tool.kind
|
|
61
|
+
&& (selected.kind !== "mcp" || (tool.kind === "mcp" && selected.server === tool.server))
|
|
62
|
+
&& (selected.name === "*" || selected.name === tool.name);
|
|
63
|
+
}
|
|
64
|
+
function permits(scope, tool) {
|
|
65
|
+
if (!validName(tool.name) || (tool.kind === "mcp" && !SERVER_NAME.test(tool.server)))
|
|
66
|
+
return false;
|
|
67
|
+
return (scope?.allow === undefined || scope.allow.some((selector) => matches(selector, tool)))
|
|
68
|
+
&& !scope?.deny?.some((selector) => matches(selector, tool));
|
|
69
|
+
}
|
|
70
|
+
export function allowsTool(scope, tool) {
|
|
71
|
+
const parsed = parseToolScope(scope);
|
|
72
|
+
return parsed.ok && permits(parsed.scope, tool);
|
|
73
|
+
}
|
|
74
|
+
/** Whether an engine needs a native catalog contract, in addition to MCP gates. */
|
|
75
|
+
export function narrowsNativeTools(value) {
|
|
76
|
+
const parsed = parseToolScope(value);
|
|
77
|
+
if (!parsed.ok)
|
|
78
|
+
return true;
|
|
79
|
+
const scope = parsed.scope;
|
|
80
|
+
return scope !== undefined && ((scope.allow !== undefined && !scope.allow.includes("native:*"))
|
|
81
|
+
|| Boolean(scope.deny?.some((selector) => selector.startsWith("native:"))));
|
|
82
|
+
}
|
|
83
|
+
/** Without a catalog, asks whether this server could have a permitted tool. */
|
|
84
|
+
export function canUseMcpServer(scope, server, names) {
|
|
85
|
+
const parsed = parseToolScope(scope);
|
|
86
|
+
if (!parsed.ok || !SERVER_NAME.test(server))
|
|
87
|
+
return false;
|
|
88
|
+
if (names !== undefined) {
|
|
89
|
+
return names.some((name) => permits(parsed.scope, { kind: "mcp", server, name }));
|
|
90
|
+
}
|
|
91
|
+
if (parsed.scope?.deny?.includes(`mcp:${server}:*`))
|
|
92
|
+
return false;
|
|
93
|
+
if (parsed.scope?.allow === undefined)
|
|
94
|
+
return true;
|
|
95
|
+
return parsed.scope.allow.some((selector) => {
|
|
96
|
+
const selected = selectorIdentity(selector);
|
|
97
|
+
return selected?.kind === "mcp" && selected.server === server
|
|
98
|
+
&& (selected.name === "*" || permits(parsed.scope, selected));
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/** Compare the selector partitions, including a tool/server outside all exact names. */
|
|
102
|
+
export function toolScopeWidens(previous, next) {
|
|
103
|
+
const before = parseToolScope(previous);
|
|
104
|
+
const after = parseToolScope(next);
|
|
105
|
+
if (!after.ok)
|
|
106
|
+
return false;
|
|
107
|
+
const namespaces = new Map([["native", new Set()]]);
|
|
108
|
+
const scopes = [before.ok ? before.scope : undefined, after.scope];
|
|
109
|
+
for (const scope of scopes) {
|
|
110
|
+
for (const selector of [...(scope?.allow ?? []), ...(scope?.deny ?? [])]) {
|
|
111
|
+
const tool = selectorIdentity(selector);
|
|
112
|
+
const namespace = tool.kind === "native" ? "native" : `mcp:${tool.server}`;
|
|
113
|
+
if (!namespaces.has(namespace))
|
|
114
|
+
namespaces.set(namespace, new Set());
|
|
115
|
+
if (tool.name !== "*")
|
|
116
|
+
namespaces.get(namespace).add(tool.name);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
let server = "unlisted";
|
|
120
|
+
for (let i = 0; namespaces.has(`mcp:${server}`); i++)
|
|
121
|
+
server = `unlisted${i}`;
|
|
122
|
+
namespaces.set(`mcp:${server}`, new Set());
|
|
123
|
+
for (const [namespace, names] of namespaces) {
|
|
124
|
+
let other = "__unlisted_tool__";
|
|
125
|
+
for (let i = 0; names.has(other); i++)
|
|
126
|
+
other = `__unlisted_tool_${i}__`;
|
|
127
|
+
names.add(other);
|
|
128
|
+
for (const name of names) {
|
|
129
|
+
const tool = namespace === "native" ? { kind: "native", name }
|
|
130
|
+
: { kind: "mcp", server: namespace.slice(4), name };
|
|
131
|
+
if (permits(after.scope, tool) && (!before.ok || !permits(before.scope, tool)))
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* computer is illustrated only after one of these; the built-in browser's
|
|
8
8
|
* read-only calls (snapshot, read, get_text, waits) stay out by the same rule. */
|
|
9
9
|
export const SCREEN_TOUCHING_TOOLS = new Set([
|
|
10
|
-
// cloud boat / remote computer
|
|
11
|
-
|
|
10
|
+
// cloud boat / remote computer (server/cloud-computer-tools.ts names its
|
|
11
|
+
// key tool key_press)
|
|
12
|
+
"screenshot", "click", "type_text", "press_key", "key_press", "scroll", "computer_batch", "open_url", "browser_click", "browser_fill",
|
|
12
13
|
// built-in browser: the Electron surface's names, kept because a bot on an
|
|
13
14
|
// older remote harness can still call them
|
|
14
15
|
"browser_navigate", "browser_type", "browser_press", "browser_scroll", "browser_hover", "browser_drag",
|