openmausbot 0.1.92 → 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-Do6lBa4C.js → DesktopViewer-bxM7NV3N.js} +1 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-Cd3X7Hxc.js → abnfDiagram-O67JEVCF-BhMdJhRJ.js} +1 -1
- package/dist/assets/{arc-DIYc7Ymx.js → arc-B5itKDcx.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-DBmRGVyP.js → architectureDiagram-NJMV4G6O-C5d7UY6U.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CPFePSEE.js → blockDiagram-BEXU5L5S-BrZIhMmu.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-DD_DO1qy.js → c4Diagram-YGBWAQC7-7I0ArfbD.js} +1 -1
- package/dist/assets/channel-CdJX6i8k.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BNEeNSjX.js → chunk-2Q5K7J3B-BnEsyN36.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-BbinLaqH.js → chunk-3FUC2YCW-BXATQ52s.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-BBgYrgDR.js → chunk-5VM5RSS4-B0BpTkEx.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-ezXj-kA-.js → chunk-GTNCS2PH-BtLTz6FR.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-9FxZ3diV.js → chunk-GWA4HPMP-D6gcZOYt.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-CO-40JpI.js → chunk-JWPE2WC7-DCLrosvf.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-BEhjslYE.js → chunk-NETBCI7D-B9JsknV9.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-CNeq4n7_.js → chunk-WEXAMYUT-CsoTxw7z.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-OsCY7KSi.js → chunk-XXDRQBXY-0F_frB0c.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-CloRUEtk.js → classDiagram-v2-NBCMYWYE-DkrpnflX.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-D1BGZhp4.js → cose-bilkent-JH36ORCC-BNDwYBt9.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-CUf4T2CN.js → cynefin-OW5HDTMX-JUkNr1Mk.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-DPEZrFGA.js → cynefinDiagram-VND7K2PF-DKeh9N4M.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-bDe6GIv5.js → dagre-6A5THRUB-ve-6T_wX.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-CdcUQz55.js → diagram-22UHCM2B-BuJ2d8rT.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-BZpqGg7c.js → diagram-3UASUU5V-BgOs_Ni-.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-DvGM6ugp.js → diagram-ATOU4E4O-DMK6VmcP.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DSTmMm0s.js → diagram-CDSNMT55-DksASlcY.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-15cucnMn.js → diagram-MLGK6HIB-Bgn8I5k6.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-tMbuaE6n.js → diagram-MPIPVDR6-zK0jqO8N.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-BYxYzjHI.js → ebnfDiagram-ZINNZB2B-vKrbzTwr.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-v6bj852W.js → elk-276RUBZZ-Bzv2mPNf.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-5lBBlnd2.js → erDiagram-OPXOYQCR-CvOyYuxu.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-CZyl1zIj.js → flowDiagram-KWPJA3E3-CtbnDbc0.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-BiAI-ltZ.js → ganttDiagram-FUAMR5RP-c4dMRYyG.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-BAZISkj3.js → gitGraphDiagram-X574FWY7-B34uaQho.js} +1 -1
- package/dist/assets/index-CPYrGsv1.js +610 -0
- package/dist/assets/{index-DGBaEXrM.js → index-CPiqc_DN.js} +1 -1
- package/dist/assets/index-CzT42GR8.css +1 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-B6ooOy-Z.js → infoDiagram-VRGFBTTK-o64ma4CI.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-BZ9ksi-B.js → ishikawaDiagram-OU5B5YK6-DV4C9BAm.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-DWqlzIEe.js → journeyDiagram-ZHPQQLJL-Bf0AHk1H.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-JX6piDwL.js → kanban-definition-PNTS6WVX-B1UMSKmE.js} +1 -1
- package/dist/assets/{linear-CmOWzX0P.js → linear-BpKhuyLm.js} +1 -1
- package/dist/assets/{mermaid.core-CF1zwoQ-.js → mermaid.core-B_BKNCw5.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-CGh2K4fW.js → mindmap-definition-NLK3R4M7-DeARruT1.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-CDwYN0s7.js → pegDiagram-GJSIUBJH-BOQEk_1D.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-1rfjJocB.js → pieDiagram-5QR66LMP-EIfoiTZo.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-tKPvPiok.js → quadrantDiagram-O4NWA36T-GgZwT-fi.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-BHLm5vXR.js → railroadDiagram-XR7U4H2S-D9rDQomj.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-CB45STp7.js → requirementDiagram-PLB6GJNP-DAh1MIeo.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-55QA_huC.js → sankeyDiagram-IPEJSGJF-CPr6Uz64.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-BckFmQHn.js → sequenceDiagram-PO4LG4MO-BU6VIdcm.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-BFCOJF11.js → sizeCapture-INFHLROL-qF2HO41r.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-CZvmiVVN.js → stateDiagram-v2-GCMORJYK-DlJ8G4MD.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-BpDUCEKy.js → swimlanes-2SLR337P-DzYE8I54.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-D-U4GtLQ.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-BpxHq6IK.js → timeline-definition-EJHVYXUP-B7wzEQ0A.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-Cj66vm02.js → usecaseDiagram-POWQR4AR-BvGgrxIP.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-C9nRkWE_.js → vennDiagram-UO4OBE2U-CEECdgQg.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-D-me_nWj.js → wardleyDiagram-VNRHLVJA-XQHA0P_P.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-Dc_fL4mk.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 +45 -1
- package/dist-server/container-mcp.js +99 -77
- package/dist-server/drivers/agents-proxy.js +40 -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 +18361 -13794
- package/dist-server/local-computer-proxy.js +5 -1
- package/dist-server/local-computer.js +1 -0
- package/dist-server/mcp-gate.js +152 -20
- package/dist-server/mcp-remote-proxy.js +437 -0
- package/dist-server/openmausbot.js +8872 -7620
- package/dist-server/pair-cli.js +8872 -7620
- 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/boat.js +3 -3
- 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/cloud-owner.js +1 -1
- package/dist-server/server/computer-control.js +6 -0
- package/dist-server/server/computer-wait.js +33 -13
- package/dist-server/server/config.js +57 -10
- package/dist-server/server/container-computer.js +43 -32
- package/dist-server/server/decision-log.js +9 -6
- package/dist-server/server/delegations.js +47 -22
- package/dist-server/server/digest.js +12 -2
- package/dist-server/server/drivers/acp/core.js +229 -20
- package/dist-server/server/drivers/acp/cursor.js +13 -1
- package/dist-server/server/drivers/acp/grok.js +208 -3
- package/dist-server/server/drivers/acp/quiet-status.js +257 -0
- package/dist-server/server/drivers/acp/qwen.js +18 -0
- 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/builtIn.js +2 -0
- package/dist-server/server/drivers/cerebras.js +108 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +45 -15
- package/dist-server/server/drivers/claude.js +35 -9
- package/dist-server/server/drivers/codex.js +84 -10
- package/dist-server/server/drivers/openai-chat.js +37 -5
- package/dist-server/server/drivers/pi.js +48 -4
- package/dist-server/server/drivers/prompt-split.js +2 -0
- package/dist-server/server/env-path.js +6 -2
- package/dist-server/server/environment.js +3 -0
- package/dist-server/server/guarded-requests.js +2 -1
- package/dist-server/server/index.js +1353 -311
- package/dist-server/server/live-call-controller.js +868 -0
- package/dist-server/server/live-call.js +216 -0
- package/dist-server/server/local-vm-stop-reason.js +26 -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/provider-key-check.js +2 -1
- 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/desktop-viewer.js +50 -6
- package/dist-server/server/routes/live.js +98 -0
- package/dist-server/server/routines.js +6 -3
- 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 +86 -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/server/vps-computer.js +1 -1
- package/dist-server/server/workspace-backup.js +22 -0
- package/dist-server/server/workspace.js +1 -1
- package/dist-server/shared/approval-mode.js +1 -1
- 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/redact.js +1 -0
- package/dist-server/shared/skill-md.js +10 -0
- package/dist-server/shared/team-backup.js +3 -0
- package/dist-server/shared/tool-name.js +22 -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 +99 -77
- package/dist-server/workspace-backup.worker.js +103 -100
- package/enterprise/server/index.js +1 -0
- package/package.json +1 -1
- package/dist/assets/channel-Dtk5Mtic.js +0 -1
- package/dist/assets/index-Cdmjrl28.css +0 -1
- package/dist/assets/index-DR7YPMkW.js +0 -597
- package/dist/assets/swimlanesDiagram-TC7HE7FX-M0GYW9Y3.js +0 -8
|
@@ -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
|
+
];
|
|
@@ -21,6 +21,7 @@ const KEY_PREFIXES = [
|
|
|
21
21
|
/\bsk-(?:ant-|proj-|live-|test-)?[A-Za-z0-9_-]{16,}/g, // anthropic / openai / stripe
|
|
22
22
|
/\bxai-[A-Za-z0-9_-]{20,}/g, // xai (grok)
|
|
23
23
|
/\bgsk_[A-Za-z0-9]{40,}/g, // groq
|
|
24
|
+
/\bcsk-[A-Za-z0-9]{32,}/g, // cerebras
|
|
24
25
|
/\bhf_[A-Za-z0-9]{30,}/g, // hugging face
|
|
25
26
|
/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}/g, // github classic
|
|
26
27
|
/\bgithub_pat_[A-Za-z0-9_]{20,}/g, // github fine-grained
|
|
@@ -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,22 @@
|
|
|
1
|
+
// A tool chip's display title is not a tool identity. Codex titles a
|
|
2
|
+
// commandExecution chip with the whole command line (drivers/codex.ts) and
|
|
3
|
+
// the ACP core prefers `rawInput.command` over the call's own name
|
|
4
|
+
// (drivers/acp/core.ts), because in a transcript the command IS the useful
|
|
5
|
+
// label. Anything that counts or groups tool calls needs the identity
|
|
6
|
+
// instead, so both readers share one predicate rather than guessing twice.
|
|
7
|
+
/** The generic identity every command-titled chip counts under. */
|
|
8
|
+
export const SHELL_TOOL = "shell";
|
|
9
|
+
/** A tool name that is itself a command line rather than a bare tool name
|
|
10
|
+
* such as `Bash`. Whitespace or a slash: no provider names a tool that way,
|
|
11
|
+
* and every shell command has one by the time it carries an argument or a
|
|
12
|
+
* path. */
|
|
13
|
+
export function nameIsCommand(name) {
|
|
14
|
+
return /[\s/]/.test(name);
|
|
15
|
+
}
|
|
16
|
+
/** What a tool chip should be counted as. Command-titled chips collapse to
|
|
17
|
+
* one `shell` bucket: keyed on the raw command they never aggregate (a turn
|
|
18
|
+
* that ran one tool twelve times reads as twelve tools), and the raw title
|
|
19
|
+
* is unredacted — unlike `tool.summary`, which drivers bound and redact. */
|
|
20
|
+
export function toolIdentity(name) {
|
|
21
|
+
return nameIsCommand(name) ? SHELL_TOOL : name;
|
|
22
|
+
}
|
|
@@ -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
|
+
}
|