openmausbot 0.1.89 → 0.1.91
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/{abnfDiagram-O67JEVCF-BwaMaZEm.js → abnfDiagram-O67JEVCF-yP-w8pRq.js} +1 -1
- package/dist/assets/{arc-Be-8GKxd.js → arc-DsivfZEh.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-Dp_U2Kv1.js → architectureDiagram-NJMV4G6O-CILw7lzT.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-DQjUjYUk.js → blockDiagram-BEXU5L5S-DEQUV3HF.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-Bd3aE4Kc.js → c4Diagram-YGBWAQC7-lSXCZoKA.js} +1 -1
- package/dist/assets/channel-DrfGMu7X.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BijjTBeJ.js → chunk-2Q5K7J3B-QCD5bgWP.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-CMGdwvzW.js → chunk-3FUC2YCW-C5-iZJuL.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-0G09BfsZ.js → chunk-5VM5RSS4-cxsawMpY.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-FOp3vSHe.js → chunk-GTNCS2PH-DV8tV8H7.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-Dv-20VIn.js → chunk-GWA4HPMP-CJFZyfbW.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-CRgrdFcz.js → chunk-JWPE2WC7-BigbMXco.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-3hP4f7QB.js → chunk-NETBCI7D-oVxTfpDy.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-CG98IjlG.js → chunk-WEXAMYUT-CH4BiroW.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-BrSTC9xG.js → chunk-XXDRQBXY-KEDa6gax.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-rpQQQDFX.js → classDiagram-v2-NBCMYWYE-BiPjfqVv.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-G6RSafHA.js → cose-bilkent-JH36ORCC-Caxew3OY.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-CXjv-IM2.js → cynefin-OW5HDTMX-B_Pui369.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-Dw07MR9f.js → cynefinDiagram-VND7K2PF-X64ydIhP.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-BwPnlTg2.js → dagre-6A5THRUB-TqYCI1E1.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-Cm6xcMiz.js → diagram-22UHCM2B-TeOt3rAQ.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-CDpogPbE.js → diagram-3UASUU5V-D70BLhDA.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-BV8z3Pnc.js → diagram-ATOU4E4O-BbQDLwUC.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-CKCQhOOC.js → diagram-CDSNMT55-ChNJZ1u-.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-FGvJg69C.js → diagram-MLGK6HIB-DMBl2APa.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-CwATvgnS.js → diagram-MPIPVDR6-CEqDchOp.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-CH_c3SSC.js → ebnfDiagram-ZINNZB2B-BVDKRp_-.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-3guZi71R.js → elk-276RUBZZ-CRkofO9B.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-BMHphR2n.js → erDiagram-OPXOYQCR-DruOMh5e.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-Vm5DkW5x.js → flowDiagram-KWPJA3E3-8GMQeB7t.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-DO-1ct07.js → ganttDiagram-FUAMR5RP-ORE9jcsM.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-nxeksEIN.js → gitGraphDiagram-X574FWY7-BmsedsWh.js} +1 -1
- package/dist/assets/index-BlnCEcU6.js +588 -0
- package/dist/assets/{index-Oxk9x45i.js → index-DKNEM8Q6.js} +1 -1
- package/dist/assets/index-DVxjOvID.css +1 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-Dkcvr0p7.js → infoDiagram-VRGFBTTK-E_p7AooJ.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-BwVibkoO.js → ishikawaDiagram-OU5B5YK6-Y-B2mKda.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-ByiBZqgG.js → journeyDiagram-ZHPQQLJL-eA8RELcK.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-qqjgXBF3.js → kanban-definition-PNTS6WVX-DqIL2e8e.js} +1 -1
- package/dist/assets/{linear-CtJnKe6A.js → linear-yC_29R6e.js} +1 -1
- package/dist/assets/{mermaid.core-BEiDklrY.js → mermaid.core-VhEMkSND.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-DfIYHia9.js → mindmap-definition-NLK3R4M7-oJ7kyQZy.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-CYyRqa1a.js → pegDiagram-GJSIUBJH-BmfYcY0O.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-hOToy_zH.js → pieDiagram-5QR66LMP-DKvgP7w9.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-DKAsNN9I.js → quadrantDiagram-O4NWA36T-BEKL3W0L.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-BSA-d8JU.js → railroadDiagram-XR7U4H2S-BEsiqR5s.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-CSmlvb-p.js → requirementDiagram-PLB6GJNP-DWBcWvAP.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-CD5F6fnG.js → sankeyDiagram-IPEJSGJF-rpZ2_O4s.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-JUq5sx6d.js → sequenceDiagram-PO4LG4MO-B99HQdJR.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-BjIS-Stk.js → sizeCapture-INFHLROL-BjN0qT5T.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-DklkUxOg.js → stateDiagram-v2-GCMORJYK-BbsVgGsC.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-CsjIU6be.js → swimlanes-2SLR337P-DG3gKkeR.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BCZhxl7e.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-BkFqK7AB.js → timeline-definition-EJHVYXUP-BTHOdtF_.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-70gJB1EU.js → usecaseDiagram-POWQR4AR-acLSLFwc.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-83Tm62dN.js → vennDiagram-UO4OBE2U-C5JzNX9G.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-tTmseoX9.js → wardleyDiagram-VNRHLVJA-DL1-PrR_.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-R1LBA55a.js → xychartDiagram-PMCCYNJV-Bvr1477p.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +14810 -0
- package/dist-server/container-mcp.js +136 -36
- package/dist-server/drivers/agents-proxy.js +31 -10
- package/dist-server/index.js +48251 -44124
- package/dist-server/openmausbot.js +628 -292
- package/dist-server/pair-cli.js +628 -292
- package/dist-server/permission-proxy.js +1 -1
- package/dist-server/server/admission.js +46 -0
- package/dist-server/server/{box-create-idempotency.js → boat-create-idempotency.js} +32 -31
- package/dist-server/server/{box-delete-journal.js → boat-delete-journal.js} +26 -25
- package/dist-server/server/{box.js → boat.js} +381 -334
- package/dist-server/server/bot-overview.js +7 -4
- package/dist-server/server/bot-profile.js +7 -1
- package/dist-server/server/channel-queue.js +58 -18
- package/dist-server/server/checkpoints.js +98 -52
- package/dist-server/server/claim-idle.js +33 -0
- package/dist-server/server/cloud-backend.js +5 -5
- package/dist-server/server/cloud-home-start.js +121 -0
- package/dist-server/server/cloud-home.js +241 -0
- package/dist-server/server/cloud-lending.js +103 -0
- package/dist-server/server/cloud-move-http.js +268 -0
- package/dist-server/server/cloud-move.js +418 -0
- package/dist-server/server/cloud-overflow.js +167 -0
- package/dist-server/server/composio.js +5 -0
- package/dist-server/server/computer-observation.js +2 -2
- package/dist-server/server/config.js +156 -9
- package/dist-server/server/container-computer.js +13 -0
- package/dist-server/server/control-client.js +2 -2
- package/dist-server/server/decider/index.js +238 -0
- package/dist-server/server/decider/jev.js +183 -0
- package/dist-server/server/decider/log.js +62 -0
- package/dist-server/server/decider/relay.js +52 -0
- package/dist-server/server/decider/room-routing.js +88 -0
- package/dist-server/server/decider/types.js +5 -0
- package/dist-server/server/delegations.js +11 -3
- package/dist-server/server/drivers/acp/core.js +5 -4
- package/dist-server/server/drivers/agents-call.js +5 -1
- package/dist-server/server/drivers/agents-catalog.js +25 -3
- package/dist-server/server/drivers/{boxagent.js → boatagent.js} +30 -21
- package/dist-server/server/drivers/builtIn.js +2 -2
- package/dist-server/server/drivers/{chat-box-tools.js → chat-boat-tools.js} +3 -3
- package/dist-server/server/drivers/chat-mcp-tools.js +2 -2
- package/dist-server/server/drivers/claude.js +178 -13
- package/dist-server/server/drivers/native.js +1 -1
- package/dist-server/server/drivers/openai-chat.js +15 -4
- package/dist-server/server/drivers/pi.js +3 -3
- package/dist-server/server/group-goal-run.js +6 -4
- package/dist-server/server/harness-capabilities.js +2 -0
- package/dist-server/server/hosted-models.js +6 -1
- package/dist-server/server/included-services.js +70 -0
- package/dist-server/server/index.js +1462 -576
- package/dist-server/server/local-vm-inventory.js +11 -1
- package/dist-server/server/local-vm-seat-pool.js +141 -0
- package/dist-server/server/managed-policy.js +4 -1
- package/dist-server/server/memory-capture.js +194 -0
- package/dist-server/server/memory-entries.js +76 -0
- package/dist-server/server/memory-organize.js +102 -0
- package/dist-server/server/memory-tidy.js +144 -0
- package/dist-server/server/memory-topics.js +169 -0
- package/dist-server/server/memory-upkeep.js +452 -0
- package/dist-server/server/message-db.js +49 -5
- package/dist-server/server/model-context-window.js +4 -4
- package/dist-server/server/package-export.js +17 -10
- package/dist-server/server/package-import.js +6 -3
- package/dist-server/server/package-parts.js +3 -1
- package/dist-server/server/permission-proxy.js +3 -3
- package/dist-server/server/profile-learned.js +98 -0
- package/dist-server/server/profile-versions.js +45 -11
- package/dist-server/server/recall.js +172 -0
- package/dist-server/server/redact.js +1 -1
- package/dist-server/server/request-auth.js +2 -0
- package/dist-server/server/room-cwd.js +1 -1
- package/dist-server/server/room-handoffs.js +61 -6
- package/dist-server/server/routes/bot-memory.js +215 -0
- package/dist-server/server/routes/decider.js +19 -0
- package/dist-server/server/routine-requests.js +27 -16
- package/dist-server/server/routines.js +10 -6
- package/dist-server/server/shared-computers.js +116 -30
- package/dist-server/server/steer-queue.js +41 -10
- package/dist-server/server/store.js +42 -3
- package/dist-server/server/surface.js +14 -1
- package/dist-server/server/system-prompt.js +16 -5
- package/dist-server/server/team-backup.js +6 -2
- package/dist-server/server/team-computers.js +2 -2
- package/dist-server/server/team-manifest.js +11 -3
- package/dist-server/server/tightening-requests.js +34 -4
- package/dist-server/server/tool-summary.js +2 -2
- package/dist-server/server/tts/elevenlabs.js +12 -7
- package/dist-server/server/tts/index.js +17 -10
- package/dist-server/server/turn-resources.js +87 -11
- package/dist-server/server/vps-computer.js +1 -1
- package/dist-server/server/workspace-backup-policy.js +22 -3
- package/dist-server/server/workspace-backup.js +25 -8
- package/dist-server/server/workspace.js +57 -22
- package/dist-server/shared/ask-question.js +7 -5
- package/dist-server/shared/bot-avatar.js +19 -0
- package/dist-server/shared/cloud-home.js +4 -0
- package/dist-server/shared/credential-request.js +3 -3
- package/dist-server/shared/package-format.js +30 -4
- package/dist-server/shared/team-backup.js +20 -3
- package/dist-server/shared/tool-surface.js +1 -1
- package/dist-server/src/lib/composer-attachments.js +9 -0
- package/dist-server/vps-container-mcp.js +136 -36
- package/dist-server/workspace-backup.worker.js +165 -35
- package/package.json +1 -1
- package/dist/assets/channel-DqY_gMLg.js +0 -1
- package/dist/assets/index-BIMdIbaw.css +0 -1
- package/dist/assets/index-BVPdbq2r.js +0 -583
- package/dist/assets/swimlanesDiagram-TC7HE7FX-Bk14M0_B.js +0 -8
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
export const JEV_DEFAULT_BASE_URL = "https://api.typesafe.ai";
|
|
2
|
+
export const JEV_MODEL = "jev-latest";
|
|
3
|
+
export const JEV_MAX_OPTIONS = 255;
|
|
4
|
+
export const JEV_MIN_LEVELS = 2;
|
|
5
|
+
export const JEV_MAX_LEVELS = 10;
|
|
6
|
+
function isLoopback(hostname) {
|
|
7
|
+
const host = hostname.replace(/^\[|\]$/g, "");
|
|
8
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host.startsWith("127.");
|
|
9
|
+
}
|
|
10
|
+
/** The endpoint for a base URL, or null when the key must not be sent there:
|
|
11
|
+
* https anywhere, plain http only to this machine (a local Jev-compatible
|
|
12
|
+
* server or a test double). */
|
|
13
|
+
export function jevEndpoint(baseUrl) {
|
|
14
|
+
const root = (baseUrl?.trim() || JEV_DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
15
|
+
let url;
|
|
16
|
+
try {
|
|
17
|
+
url = new URL(`${root}/v1/systemone`);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
if (url.username || url.password)
|
|
23
|
+
return null;
|
|
24
|
+
if (url.protocol === "https:")
|
|
25
|
+
return url;
|
|
26
|
+
return url.protocol === "http:" && isLoopback(url.hostname) ? url : null;
|
|
27
|
+
}
|
|
28
|
+
/** Questions within the API's limits: 2–255 options, 2–10 levels. */
|
|
29
|
+
export function questionsFit(questions) {
|
|
30
|
+
const entries = Object.entries(questions);
|
|
31
|
+
if (!entries.length)
|
|
32
|
+
return false;
|
|
33
|
+
return entries.every(([, question]) => {
|
|
34
|
+
if (question.type === "choice") {
|
|
35
|
+
const count = Object.keys(question.options).length;
|
|
36
|
+
return count >= 2 && count <= JEV_MAX_OPTIONS;
|
|
37
|
+
}
|
|
38
|
+
if (question.type === "score")
|
|
39
|
+
return question.levels.length >= JEV_MIN_LEVELS && question.levels.length <= JEV_MAX_LEVELS;
|
|
40
|
+
return true;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export function jevRequestBody(state, questions) {
|
|
44
|
+
const wire = {};
|
|
45
|
+
for (const [id, question] of Object.entries(questions)) {
|
|
46
|
+
if (question.type === "choice") {
|
|
47
|
+
wire[id] = { type: "choice", instructions: question.instructions, criteria: { ...question.options } };
|
|
48
|
+
}
|
|
49
|
+
else if (question.type === "score") {
|
|
50
|
+
wire[id] = { type: "score", instructions: question.instructions, criteria: [...question.levels] };
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const criteria = question.criteria?.yes || question.criteria?.no
|
|
54
|
+
? { ...(question.criteria.yes ? { true: question.criteria.yes } : {}), ...(question.criteria.no ? { false: question.criteria.no } : {}) }
|
|
55
|
+
: undefined;
|
|
56
|
+
wire[id] = { type: "noul", instructions: question.instructions, ...(criteria ? { criteria } : {}) };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { state, model: JEV_MODEL, questions: wire };
|
|
60
|
+
}
|
|
61
|
+
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
62
|
+
const isProbability = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
63
|
+
/** Jev rounds to two places, so a map can sum to 0.98 or 1.02. */
|
|
64
|
+
const SUM_TOLERANCE = 0.1;
|
|
65
|
+
const EPSILON = 1e-9;
|
|
66
|
+
function parseChoice(raw, question) {
|
|
67
|
+
const offered = new Set(Object.keys(question.options));
|
|
68
|
+
const choice = raw.choice;
|
|
69
|
+
if (typeof choice !== "string" || !offered.has(choice))
|
|
70
|
+
return null;
|
|
71
|
+
if (!isRecord(raw.probabilities))
|
|
72
|
+
return null;
|
|
73
|
+
const probabilities = {};
|
|
74
|
+
let sum = 0;
|
|
75
|
+
for (const [key, value] of Object.entries(raw.probabilities)) {
|
|
76
|
+
if (!offered.has(key) || !isProbability(value))
|
|
77
|
+
return null;
|
|
78
|
+
probabilities[key] = value;
|
|
79
|
+
sum += value;
|
|
80
|
+
}
|
|
81
|
+
if (!(Math.abs(sum - 1) <= SUM_TOLERANCE))
|
|
82
|
+
return null;
|
|
83
|
+
const pTop = probabilities[choice];
|
|
84
|
+
if (pTop === undefined)
|
|
85
|
+
return null;
|
|
86
|
+
const others = Object.entries(probabilities).filter(([key]) => key !== choice).map(([, value]) => value);
|
|
87
|
+
const runnerUp = others.length ? Math.max(...others) : 0;
|
|
88
|
+
// Ties break to the first key on the vendor side; a choice that is not
|
|
89
|
+
// the most likely option means the answer is not what it claims to be.
|
|
90
|
+
if (runnerUp > pTop + EPSILON)
|
|
91
|
+
return null;
|
|
92
|
+
return { type: "choice", choice, pTop, margin: pTop - runnerUp, probabilities };
|
|
93
|
+
}
|
|
94
|
+
function parseScore(raw, question) {
|
|
95
|
+
const levels = question.levels.length;
|
|
96
|
+
const score = raw.score;
|
|
97
|
+
if (typeof score !== "number" || !Number.isFinite(score) || score < -EPSILON || score > levels - 1 + EPSILON)
|
|
98
|
+
return null;
|
|
99
|
+
if (!isRecord(raw.probabilities))
|
|
100
|
+
return null;
|
|
101
|
+
const probabilities = Array.from({ length: levels }, () => 0);
|
|
102
|
+
for (const [key, value] of Object.entries(raw.probabilities)) {
|
|
103
|
+
const index = Number(key);
|
|
104
|
+
if (!Number.isInteger(index) || index < 0 || index >= levels || String(index) !== key || !isProbability(value))
|
|
105
|
+
return null;
|
|
106
|
+
probabilities[index] = value;
|
|
107
|
+
}
|
|
108
|
+
const sum = probabilities.reduce((total, value) => total + value, 0);
|
|
109
|
+
if (!(Math.abs(sum - 1) <= SUM_TOLERANCE))
|
|
110
|
+
return null;
|
|
111
|
+
const level = probabilities.indexOf(Math.max(...probabilities));
|
|
112
|
+
return { type: "score", score, level, probabilities };
|
|
113
|
+
}
|
|
114
|
+
function parseYesNo(raw) {
|
|
115
|
+
return isProbability(raw.noul) ? { type: "yesno", p: raw.noul } : null;
|
|
116
|
+
}
|
|
117
|
+
const WIRE_TYPE = { choice: "choice", score: "score", yesno: "noul" };
|
|
118
|
+
/** Strictly decode one response body against the questions that were asked. */
|
|
119
|
+
export function parseJevResponse(body, questions) {
|
|
120
|
+
if (!isRecord(body) || !isRecord(body.answers))
|
|
121
|
+
return { ok: false };
|
|
122
|
+
const answers = {};
|
|
123
|
+
for (const [id, question] of Object.entries(questions)) {
|
|
124
|
+
const raw = body.answers[id];
|
|
125
|
+
if (!isRecord(raw))
|
|
126
|
+
return { ok: false };
|
|
127
|
+
if (raw.type !== undefined && raw.type !== WIRE_TYPE[question.type])
|
|
128
|
+
return { ok: false };
|
|
129
|
+
const parsed = question.type === "choice" ? parseChoice(raw, question)
|
|
130
|
+
: question.type === "score" ? parseScore(raw, question)
|
|
131
|
+
: parseYesNo(raw);
|
|
132
|
+
if (!parsed)
|
|
133
|
+
return { ok: false };
|
|
134
|
+
answers[id] = parsed;
|
|
135
|
+
}
|
|
136
|
+
const usage = isRecord(body.usage) ? body.usage.input_tokens : undefined;
|
|
137
|
+
const inputTokens = typeof usage === "number" && Number.isFinite(usage) && usage >= 0 ? usage : undefined;
|
|
138
|
+
const model = typeof body.model === "string" && body.model.length <= 80 ? body.model : undefined;
|
|
139
|
+
return { ok: true, answers, ...(inputTokens !== undefined ? { inputTokens } : {}), ...(model ? { model } : {}) };
|
|
140
|
+
}
|
|
141
|
+
function refusal(status) {
|
|
142
|
+
if (status === 401 || status === 403)
|
|
143
|
+
return { ok: false, reason: "rejected", status };
|
|
144
|
+
if (status === 429)
|
|
145
|
+
return { ok: false, reason: "rate_limited", status };
|
|
146
|
+
if (status === 529 || status === 503)
|
|
147
|
+
return { ok: false, reason: "overloaded", status };
|
|
148
|
+
return { ok: false, reason: "http_error", status };
|
|
149
|
+
}
|
|
150
|
+
async function decide(request) {
|
|
151
|
+
const endpoint = jevEndpoint(request.baseUrl);
|
|
152
|
+
if (!endpoint || !questionsFit(request.questions))
|
|
153
|
+
return { ok: false, reason: "misconfigured" };
|
|
154
|
+
let response;
|
|
155
|
+
try {
|
|
156
|
+
response = await request.fetch(endpoint, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: { authorization: `Bearer ${request.key}`, "content-type": "application/json", accept: "application/json" },
|
|
159
|
+
body: JSON.stringify(jevRequestBody(request.state, request.questions)),
|
|
160
|
+
signal: request.signal,
|
|
161
|
+
// Never replay the key to wherever a redirect points.
|
|
162
|
+
redirect: "error",
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// The caller tells a timeout or a Stop from a dead network by its own signals.
|
|
167
|
+
return { ok: false, reason: "unreachable" };
|
|
168
|
+
}
|
|
169
|
+
if (!response.ok) {
|
|
170
|
+
await response.body?.cancel().catch(() => undefined);
|
|
171
|
+
return refusal(response.status);
|
|
172
|
+
}
|
|
173
|
+
let body;
|
|
174
|
+
try {
|
|
175
|
+
body = await response.json();
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return { ok: false, reason: "malformed" };
|
|
179
|
+
}
|
|
180
|
+
const parsed = parseJevResponse(body, request.questions);
|
|
181
|
+
return parsed.ok ? parsed : { ok: false, reason: "malformed" };
|
|
182
|
+
}
|
|
183
|
+
export const jevBackend = { id: "jev", decide };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// The decision model's own log: one row per call that reached a backend,
|
|
2
|
+
// one file per UTC month under <data>/decider-log/YYYY-MM.ndjson.
|
|
3
|
+
//
|
|
4
|
+
// Not the authorization decision log (server/decision-log.ts, <data>/
|
|
5
|
+
// decisions/), which records approvals. This one records what a fast
|
|
6
|
+
// classifier picked and how sure it was, so its accuracy and cost can be
|
|
7
|
+
// measured later. A row never holds the message text, the options' text or
|
|
8
|
+
// the key: only the seam, the chosen option key (a bot id for room routing),
|
|
9
|
+
// the probabilities and a short hash of the state.
|
|
10
|
+
//
|
|
11
|
+
// Same discipline as the other logs: 0600, fire-and-forget, serialized per
|
|
12
|
+
// directory, and months older than the shared retention window pruned.
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { boundRetentionDays, pruneMonthFiles } from "../decision-log.js";
|
|
17
|
+
export const DECIDER_LOG_DIR = "decider-log";
|
|
18
|
+
const PRUNE_EVERY_MS = 60 * 60_000;
|
|
19
|
+
const writeQueues = new Map();
|
|
20
|
+
const lastPrune = new Map();
|
|
21
|
+
export function stateHash(state) {
|
|
22
|
+
let text;
|
|
23
|
+
try {
|
|
24
|
+
text = JSON.stringify(state) ?? "";
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
text = "";
|
|
28
|
+
}
|
|
29
|
+
return createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
30
|
+
}
|
|
31
|
+
function monthKey(at) {
|
|
32
|
+
return `${at.getUTCFullYear()}-${String(at.getUTCMonth() + 1).padStart(2, "0")}`;
|
|
33
|
+
}
|
|
34
|
+
export function deciderLogFileFor(dataDir, at) {
|
|
35
|
+
return join(dataDir, DECIDER_LOG_DIR, `${monthKey(at)}.ndjson`);
|
|
36
|
+
}
|
|
37
|
+
async function write(dataDir, row) {
|
|
38
|
+
const at = new Date(row.at);
|
|
39
|
+
await mkdir(join(dataDir, DECIDER_LOG_DIR), { recursive: true, mode: 0o700 });
|
|
40
|
+
await appendFile(deciderLogFileFor(dataDir, at), JSON.stringify(row) + "\n", { mode: 0o600 });
|
|
41
|
+
if (at.getTime() - (lastPrune.get(dataDir) ?? 0) >= PRUNE_EVERY_MS) {
|
|
42
|
+
lastPrune.set(dataDir, at.getTime());
|
|
43
|
+
await pruneMonthFiles(join(dataDir, DECIDER_LOG_DIR), boundRetentionDays(), at);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Append one row. Never throws and never waits: a full disk must not turn
|
|
47
|
+
* into a slower or failed room turn. */
|
|
48
|
+
export function appendDeciderLog(dataDir, row) {
|
|
49
|
+
const previous = writeQueues.get(dataDir) ?? Promise.resolve();
|
|
50
|
+
const queued = previous.then(() => write(dataDir, row)).catch(() => {
|
|
51
|
+
/* logging must never take down a decision */
|
|
52
|
+
});
|
|
53
|
+
writeQueues.set(dataDir, queued);
|
|
54
|
+
void queued.finally(() => {
|
|
55
|
+
if (writeQueues.get(dataDir) === queued)
|
|
56
|
+
writeQueues.delete(dataDir);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** Test seam: wait until every queued row for this directory is on disk. */
|
|
60
|
+
export async function flushDeciderLog(dataDir) {
|
|
61
|
+
await writeQueues.get(dataDir);
|
|
62
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// What Cloud Pro's decision relay accepts through the included token
|
|
2
|
+
// (docs/cloud-pro.md, "Included Boat computers, voice and decisions"; the
|
|
3
|
+
// Admin checks the same): room routing's one request and the Settings key
|
|
4
|
+
// check, within its size caps. Anything else (another job, another question,
|
|
5
|
+
// a larger state) is never sent through the included token: it goes only
|
|
6
|
+
// with the person's own key, until the relay accepts it too.
|
|
7
|
+
import { jevRequestBody } from "./jev.js";
|
|
8
|
+
import { ROOM_ROUTING_INSTRUCTIONS, ROOM_ROUTING_STATE_KEYS } from "./room-routing.js";
|
|
9
|
+
/** The Settings key check's fixed request. */
|
|
10
|
+
export const KEY_CHECK_STATE = { purpose: "OpenMausBot is checking that a decision-model key works." };
|
|
11
|
+
export const KEY_CHECK_QUESTION = "Is this a connection check?";
|
|
12
|
+
/** The relay's caps: the whole request body, and the state as JSON. Both
|
|
13
|
+
* measured in UTF-8 bytes, which is never less than characters. */
|
|
14
|
+
export const RELAY_MAX_BODY_BYTES = 64 * 1024;
|
|
15
|
+
export const RELAY_MAX_STATE_BYTES = 24_000;
|
|
16
|
+
/** Where a request may use the included token. A job added later is not here
|
|
17
|
+
* until the relay accepts it. */
|
|
18
|
+
const RELAY_SEAMS = new Set(["roomRouting", "keyCheck"]);
|
|
19
|
+
const ROOM_STATE_KEYS = new Set(ROOM_ROUTING_STATE_KEYS);
|
|
20
|
+
const ROOM_STATE_REQUIRED = ROOM_ROUTING_STATE_KEYS.filter((key) => key !== "recent_messages");
|
|
21
|
+
export function relaySeam(seam) {
|
|
22
|
+
return RELAY_SEAMS.has(seam);
|
|
23
|
+
}
|
|
24
|
+
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
25
|
+
function shapeFits(seam, state, questions) {
|
|
26
|
+
const ids = Object.keys(questions);
|
|
27
|
+
const question = questions.answer;
|
|
28
|
+
if (ids.length !== 1 || !question)
|
|
29
|
+
return false;
|
|
30
|
+
if (seam === "keyCheck") {
|
|
31
|
+
return question.type === "yesno" && question.instructions === KEY_CHECK_QUESTION && !question.criteria &&
|
|
32
|
+
JSON.stringify(state) === JSON.stringify(KEY_CHECK_STATE);
|
|
33
|
+
}
|
|
34
|
+
if (seam === "roomRouting") {
|
|
35
|
+
return question.type === "choice" && question.instructions === ROOM_ROUTING_INSTRUCTIONS && isRecord(state) &&
|
|
36
|
+
Object.keys(state).every((key) => ROOM_STATE_KEYS.has(key)) && ROOM_STATE_REQUIRED.every((key) => key in state);
|
|
37
|
+
}
|
|
38
|
+
// Any other seam: the relay takes nothing from it.
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
/** Whether this exact request may go through the included token. */
|
|
42
|
+
export function relayAccepts(seam, state, questions) {
|
|
43
|
+
try {
|
|
44
|
+
if (!shapeFits(seam, state, questions))
|
|
45
|
+
return false;
|
|
46
|
+
return Buffer.byteLength(JSON.stringify(state)) <= RELAY_MAX_STATE_BYTES &&
|
|
47
|
+
Buffer.byteLength(JSON.stringify(jevRequestBody(state, questions))) <= RELAY_MAX_BODY_BYTES;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/** Turn starts take seconds anyway, and people far from the US West Coast
|
|
2
|
+
* see 400–700 ms round trips; 1.5 s keeps a slow answer usable. */
|
|
3
|
+
export const ROOM_ROUTING_TIMEOUT_MS = 1_500;
|
|
4
|
+
/** Bench calibration: answers at >= 0.6 were right 92–99% of the time. */
|
|
5
|
+
export const ROOM_ROUTING_MIN_PROBABILITY = 0.6;
|
|
6
|
+
export const EVERYONE_OPTION = "__everyone__";
|
|
7
|
+
const EVERYONE_MEANING = "Several members: the message explicitly needs answers or work from more than one member of the room, for example it asks everyone or asks each member for their part.";
|
|
8
|
+
/** Fixed: Cloud Pro's decision relay accepts room routing only with these
|
|
9
|
+
* exact instructions (relay.ts). */
|
|
10
|
+
export const ROOM_ROUTING_INSTRUCTIONS = "Which bot in this room should answer `new_message`? Choose __everyone__ only when the message needs several members to answer.";
|
|
11
|
+
/** Every key the state may carry; `recent_messages` is left out when there
|
|
12
|
+
* are none. Cloud Pro's relay accepts no other. */
|
|
13
|
+
export const ROOM_ROUTING_STATE_KEYS = ["room", "humans_in_room", "bots_in_room", "recent_messages", "new_message"];
|
|
14
|
+
const NAME_MAX = 80;
|
|
15
|
+
const TITLE_MAX = 120;
|
|
16
|
+
const DESCRIPTION_MAX = 400;
|
|
17
|
+
const LINE_MAX = 500;
|
|
18
|
+
/** The room context window bounds how many lines; this bounds their size,
|
|
19
|
+
* because large irrelevant state makes the classifier worse, not better. */
|
|
20
|
+
const RECENT_BUDGET_CHARS = 6_000;
|
|
21
|
+
function clip(value, max) {
|
|
22
|
+
const flat = value.replace(/\s+/g, " ").trim();
|
|
23
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
24
|
+
}
|
|
25
|
+
/** What one bot is, as an option: "Maya, Product Designer. Owns UI…". */
|
|
26
|
+
export function memberOption(member) {
|
|
27
|
+
const name = clip(member.name, NAME_MAX) || "Unnamed bot";
|
|
28
|
+
const title = member.title ? clip(member.title, TITLE_MAX) : "";
|
|
29
|
+
const description = member.description ? clip(member.description, DESCRIPTION_MAX) : "";
|
|
30
|
+
return `${name}${title ? `, ${title}` : ""} bot.${description ? ` ${description}` : ""}`;
|
|
31
|
+
}
|
|
32
|
+
/** The newest lines that fit the budget, oldest first, each clipped. */
|
|
33
|
+
function recentWithinBudget(recent) {
|
|
34
|
+
const kept = [];
|
|
35
|
+
let used = 0;
|
|
36
|
+
for (let index = recent.length - 1; index >= 0; index--) {
|
|
37
|
+
const line = recent[index];
|
|
38
|
+
const text = clip(line.text, LINE_MAX);
|
|
39
|
+
if (!text)
|
|
40
|
+
continue;
|
|
41
|
+
const size = text.length + line.from.length;
|
|
42
|
+
if (used + size > RECENT_BUDGET_CHARS)
|
|
43
|
+
break;
|
|
44
|
+
used += size;
|
|
45
|
+
kept.unshift({ from: clip(line.from, NAME_MAX), text });
|
|
46
|
+
}
|
|
47
|
+
return kept;
|
|
48
|
+
}
|
|
49
|
+
export function roomRoutingRequest(input) {
|
|
50
|
+
const options = {};
|
|
51
|
+
for (const member of input.members)
|
|
52
|
+
options[member.id] = memberOption(member);
|
|
53
|
+
options[EVERYONE_OPTION] = EVERYONE_MEANING;
|
|
54
|
+
const recent = recentWithinBudget(input.recent);
|
|
55
|
+
const state = {
|
|
56
|
+
room: clip(input.room, NAME_MAX),
|
|
57
|
+
humans_in_room: input.humans.map((human) => clip(human, NAME_MAX)),
|
|
58
|
+
bots_in_room: input.members.map((member) => clip(member.name, NAME_MAX)),
|
|
59
|
+
...(recent.length ? { recent_messages: recent } : {}),
|
|
60
|
+
new_message: { from: clip(input.message.from, NAME_MAX), text: input.message.text.trim().slice(0, 8_000) },
|
|
61
|
+
};
|
|
62
|
+
return { state, question: { instructions: ROOM_ROUTING_INSTRUCTIONS, options } };
|
|
63
|
+
}
|
|
64
|
+
/** Ask once and turn the answer into a route. Never throws. */
|
|
65
|
+
export async function decideRoomResponder(decider, input, options = {}) {
|
|
66
|
+
try {
|
|
67
|
+
if (input.members.length < 2)
|
|
68
|
+
return { kind: "fallback", reason: "no_choice" };
|
|
69
|
+
const { state, question } = roomRoutingRequest(input);
|
|
70
|
+
const result = await decider.choose("roomRouting", state, question, {
|
|
71
|
+
timeoutMs: options.timeoutMs ?? ROOM_ROUTING_TIMEOUT_MS,
|
|
72
|
+
signal: options.signal,
|
|
73
|
+
});
|
|
74
|
+
if (!result.ok)
|
|
75
|
+
return { kind: "fallback", reason: result.reason };
|
|
76
|
+
const { choice, pTop } = result.answers;
|
|
77
|
+
if (pTop < ROOM_ROUTING_MIN_PROBABILITY)
|
|
78
|
+
return { kind: "fallback", reason: "low_confidence" };
|
|
79
|
+
if (choice === EVERYONE_OPTION)
|
|
80
|
+
return { kind: "everyone", probability: pTop };
|
|
81
|
+
if (!input.members.some((member) => member.id === choice))
|
|
82
|
+
return { kind: "fallback", reason: "malformed" };
|
|
83
|
+
return { kind: "member", botId: choice, probability: pTop };
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return { kind: "fallback", reason: "malformed" };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// The decision model's vocabulary, shared by every backend and caller. A
|
|
2
|
+
// question is one of three typed shapes; an answer comes back typed to match.
|
|
3
|
+
// Nothing here names a vendor: `jev` is one backend, and another (a local
|
|
4
|
+
// Jev-compatible server, an LLM) plugs in behind the same types.
|
|
5
|
+
export const DECIDER_JOBS = ["roomRouting"];
|
|
@@ -259,8 +259,10 @@ export function pendingDelegationSnapshot() {
|
|
|
259
259
|
})));
|
|
260
260
|
}
|
|
261
261
|
/** How many handoffs one turn may queue. Small on purpose: this is the only
|
|
262
|
-
* thing standing between a confused bot and a fan-out of real turns.
|
|
263
|
-
|
|
262
|
+
* thing standing between a confused bot and a fan-out of real turns. Six,
|
|
263
|
+
* not four: a lead running a weekly check-in over a five-member team is the
|
|
264
|
+
* ordinary case, and hitting the cap there silently dropped one teammate. */
|
|
265
|
+
const MAX_QUEUED_PER_THREAD = 6;
|
|
264
266
|
/** Validate and enqueue a delegation. Pushes a "Delegated to @B: reason"
|
|
265
267
|
* chip to the source thread so the user can see what was queued. */
|
|
266
268
|
export function queueDelegation(bus, from, item, maxDepth, sourceThreadId = from.threadId) {
|
|
@@ -838,7 +840,13 @@ export function buildDelegationFailurePrompt(targetName, reason) {
|
|
|
838
840
|
"Take over: tell the user what failed in plain terms, then decide the next step — retry with a narrower task, do the work yourself, or propose an alternative. Do not re-delegate the exact same task unchanged.",
|
|
839
841
|
].join("\n\n");
|
|
840
842
|
}
|
|
841
|
-
|
|
843
|
+
// Must not be smaller than MAX_QUEUED_PER_THREAD: every delegate reply is
|
|
844
|
+
// one wake, so a fan-out that the queue cap allows has to be resumable
|
|
845
|
+
// within the window. Three failed a five-member weekly check-in whose
|
|
846
|
+
// replies all landed inside four minutes ("Delegation follow-up limit
|
|
847
|
+
// reached"), while the same routine passed a week earlier only because the
|
|
848
|
+
// replies happened to spread over twenty.
|
|
849
|
+
export const DELEGATION_WAKE_MAX_PER_WINDOW = 6;
|
|
842
850
|
export const DELEGATION_WAKE_WINDOW_MS = 5 * 60 * 1000;
|
|
843
851
|
/** Bounded auto-wake budget per source thread. A delegation completion
|
|
844
852
|
* wakes the source; if that source re-delegates and the new completion
|
|
@@ -43,7 +43,7 @@ export function skipSubscriptionAuthForLocalInject(model) {
|
|
|
43
43
|
import { newEventId, newId, TurnNotStartedError } from "../../contracts.js";
|
|
44
44
|
import { augmentedPath } from "../../env-path.js";
|
|
45
45
|
import { supportsApprovalMode } from "../../../shared/approval-mode.js";
|
|
46
|
-
import { parseAskQuestions, parseChoices, questionAnswersByQuestion } from "../../../shared/ask-question.js";
|
|
46
|
+
import { MAX_QUESTION_TEXT, parseAskQuestions, parseChoices, questionAnswersByQuestion } from "../../../shared/ask-question.js";
|
|
47
47
|
import { appendNative } from "../native.js";
|
|
48
48
|
import { acpPermissionCommand, permissionLaunchCwd } from "../permission-command.js";
|
|
49
49
|
import { commandSummary, toolDetailPreview } from "../../tool-summary.js";
|
|
@@ -275,7 +275,7 @@ export function createAcpDriver(support) {
|
|
|
275
275
|
};
|
|
276
276
|
const allowedCredentials = new Set(support.credentialEnv ?? []);
|
|
277
277
|
// two lists, one rule: foreign PROVIDER keys must not flip a CLI's
|
|
278
|
-
// billing off its own login, and WORKSPACE credentials (
|
|
278
|
+
// billing off its own login, and WORKSPACE credentials (boat token,
|
|
279
279
|
// voice key, …) are the harness's secrets — riding along in
|
|
280
280
|
// `...process.env` is not a grant. A driver keeps only what its
|
|
281
281
|
// credentialEnv allowlist names.
|
|
@@ -439,7 +439,7 @@ export function createAcpDriver(support) {
|
|
|
439
439
|
}
|
|
440
440
|
// The bot's computer, mounted exactly like the Claude driver does:
|
|
441
441
|
// host and sandbox Cua connections expose Cua Driver's own MCP server.
|
|
442
|
-
// (A cloud
|
|
442
|
+
// (A cloud boat is not mounted here at all: a cloud turn runs ON the boat.)
|
|
443
443
|
if (turn.integrations?.localComputer) {
|
|
444
444
|
const local = turn.integrations.localComputer;
|
|
445
445
|
servers.push({
|
|
@@ -722,7 +722,8 @@ export function createAcpDriver(support) {
|
|
|
722
722
|
}
|
|
723
723
|
const tool = kind === "execute" ? "shell" : kind === "edit" ? "edit" : kind || "tool";
|
|
724
724
|
const isShellCommand = !isQuestion && kind === "execute" && !/^mcp(?:__|[.:])/i.test(String(toolCall.title ?? ""));
|
|
725
|
-
const
|
|
725
|
+
const rawSummary = String(toolCall.rawInput?.command ?? toolCall.title ?? tool);
|
|
726
|
+
const summary = rawSummary.slice(0, isQuestion ? MAX_QUESTION_TEXT : 200);
|
|
726
727
|
// One structured question beside the flat choices: the richer card
|
|
727
728
|
// renders from it while older clients keep answering through
|
|
728
729
|
// `choices`. Built once here so the emit and the answer path can
|
|
@@ -278,7 +278,7 @@ function routineFields(args) {
|
|
|
278
278
|
const runOn = destination(args.run_on ?? args.runOn);
|
|
279
279
|
const timeoutMinutes = args.timeout_minutes ?? args.timeoutMinutes;
|
|
280
280
|
if (runOn != null && runOn !== "maus" && runOn !== "cloud") {
|
|
281
|
-
return { fields, error: 'Use run_on="maus" for the bot’s current model and configured computer (including VPS), or run_on="box" only for the
|
|
281
|
+
return { fields, error: 'Use run_on="maus" for the bot’s current model and configured computer (including VPS), or run_on="box" only for the Boat-hosted agent. Legacy "cloud" also means Boat.' };
|
|
282
282
|
}
|
|
283
283
|
if (timeoutMinutes != null && (typeof timeoutMinutes !== "number" || !Number.isInteger(timeoutMinutes) || timeoutMinutes < 5 || timeoutMinutes > 240)) {
|
|
284
284
|
return { fields, error: "timeout_minutes must be a whole number from 5 to 240. Use clear_timeout to remove a limit." };
|
|
@@ -982,11 +982,14 @@ export async function callTool(name, args, context) {
|
|
|
982
982
|
if (!routineId || !action) {
|
|
983
983
|
return { text: "propose_routine_action needs a routine_id and supported action.", isError: true };
|
|
984
984
|
}
|
|
985
|
+
const forBotId = String(args.for_bot_id ?? "").trim();
|
|
985
986
|
const body = {
|
|
986
987
|
fromBotId: BOT_ID,
|
|
987
988
|
fromThreadId: THREAD_ID,
|
|
988
989
|
action,
|
|
989
990
|
routineId,
|
|
991
|
+
// JSON.stringify drops the key entirely when no target was named
|
|
992
|
+
...(forBotId ? { forBotId } : {}),
|
|
990
993
|
};
|
|
991
994
|
if (action === "update") {
|
|
992
995
|
if (!jsonRecord(args.changes)) {
|
|
@@ -1098,6 +1101,7 @@ export async function callTool(name, args, context) {
|
|
|
1098
1101
|
action: args.action,
|
|
1099
1102
|
text: args.text,
|
|
1100
1103
|
oldText: args.old_text,
|
|
1104
|
+
...(typeof args.until === "string" && args.until.trim() ? { until: args.until.trim() } : {}),
|
|
1101
1105
|
}),
|
|
1102
1106
|
});
|
|
1103
1107
|
if (r.error || r.ok !== true) {
|
|
@@ -24,6 +24,7 @@ export function catalogProfileFromEnv(env) {
|
|
|
24
24
|
skillAuthoring: env.OMB_SKILL_AUTHORING_ENABLED === "1",
|
|
25
25
|
sharedComputers: env.OMB_SHARED_COMPUTERS_ENABLED === "1",
|
|
26
26
|
voiceNotes: env.OMB_VOICE_NOTES === "1",
|
|
27
|
+
cloudHome: env.OMB_CLOUD_HOME === "1",
|
|
27
28
|
botId: env.OMB_BOT_ID ?? "",
|
|
28
29
|
};
|
|
29
30
|
}
|
|
@@ -128,8 +129,9 @@ const ROUTINE_FIELDS_SCHEMA = {
|
|
|
128
129
|
schedule: ROUTINE_SCHEDULE_SCHEMA,
|
|
129
130
|
run_on: {
|
|
130
131
|
type: "string",
|
|
132
|
+
// "box" is Boat's historical run_on destination id (agents wire contract).
|
|
131
133
|
enum: ["maus", "box"],
|
|
132
|
-
description: "Default maus keeps the bot's selected model and configured computer, INCLUDING a self-hosted VPS. Omit this field for normal schedules. box explicitly switches the agent to the
|
|
134
|
+
description: "Default maus keeps the bot's selected model and configured computer, INCLUDING a self-hosted VPS. Omit this field for normal schedules. box explicitly switches the agent to the Boat-hosted runner; it requires Boat setup and is not the generic cloud/VPS option. Legacy cloud values from list_routines mean box, not VPS.",
|
|
133
135
|
},
|
|
134
136
|
timeout_minutes: {
|
|
135
137
|
type: "integer",
|
|
@@ -293,7 +295,7 @@ const toolDefinitions = (externalRuntime) => [
|
|
|
293
295
|
description: "Choose where this conversation does computer work. Call with no arguments to inspect actual available choices and the current place. For a task needing computer interaction, select the requested place, or auto to choose a suitable configured computer without asking the user to use menus. OpenMausBot reuses an existing computer first; with a configured provider it can start or provision one when needed. Do not provision for ordinary chat or just to inspect availability. A pending result means end this turn immediately: OpenMausBot updates the conversation selector and resumes the original request with that computer's real tools. Do not use the old tools after requesting a switch, repeat the task, or claim the action is done. This cannot change permissions, override Off, or switch a teammate/routine/channel.",
|
|
294
296
|
inputSchema: { type: "object", additionalProperties: false, properties: {
|
|
295
297
|
surface: { type: "string", enum: ["auto", "cloud", "vm", "local", "browser"],
|
|
296
|
-
description: "auto = suitable configured computer, cloud = remote
|
|
298
|
+
description: "auto = suitable configured computer, cloud = remote Boat/VPS, vm = isolated Local VM, local = user's own desktop, browser = built-in browser. Omit to list." },
|
|
297
299
|
} },
|
|
298
300
|
},
|
|
299
301
|
{
|
|
@@ -523,6 +525,7 @@ const toolDefinitions = (externalRuntime) => [
|
|
|
523
525
|
action: { type: "string", enum: ["append", "replace", "remove", "supersede"] },
|
|
524
526
|
text: { type: "string", minLength: 1, description: "Non-blank new text for append, replace, or supersede: the fact itself, without a date or bullet. Omit for remove; use remove to delete a passage." },
|
|
525
527
|
old_text: { type: "string", minLength: 1, description: "Exact unique existing passage for replace, supersede, or remove. Omit for append." },
|
|
528
|
+
until: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "Optional, for append or supersede: YYYY-MM-DD, the last day a temporary fact holds (an exam this weekend, a trip next week). After that day the entry is hidden from your memory." },
|
|
526
529
|
},
|
|
527
530
|
required: ["action"],
|
|
528
531
|
},
|
|
@@ -614,12 +617,16 @@ const toolDefinitions = (externalRuntime) => [
|
|
|
614
617
|
},
|
|
615
618
|
{
|
|
616
619
|
name: "propose_routine_action",
|
|
617
|
-
description: "Prepare a user-requested change to one of this bot's existing routines. Use list_routines first to get the routine id." + PROPOSAL_OUTCOME,
|
|
620
|
+
description: "Prepare a user-requested change to one of this bot's existing routines. Use list_routines first to get the routine id. If the user asks to change ANOTHER bot's routine and that bot is in your section, call list_bots and pass that bot's id as for_bot_id; the routine keeps its owner and every run keeps that bot's engine and permissions." + PROPOSAL_OUTCOME,
|
|
618
621
|
inputSchema: {
|
|
619
622
|
type: "object",
|
|
620
623
|
additionalProperties: false,
|
|
621
624
|
properties: {
|
|
622
625
|
routine_id: { type: "string", minLength: 1, description: "Routine id from list_routines." },
|
|
626
|
+
for_bot_id: {
|
|
627
|
+
type: "string",
|
|
628
|
+
description: "Only when the requested change targets ANOTHER bot's routine and that bot is in your section: that bot's id from list_bots. Learn the routine id from that bot's own routines (it can run list_routines). Omit to change one of your own routines.",
|
|
629
|
+
},
|
|
623
630
|
action: {
|
|
624
631
|
type: "string",
|
|
625
632
|
enum: ["update", "pause", "resume", "run_now", "delete"],
|
|
@@ -753,8 +760,23 @@ const ROOM_ONLY_TOOLS = new Set(["list_room_targets", "coordinate_bots"]);
|
|
|
753
760
|
const ROOM_REPLACED_TOOLS = new Set(["ask_bot", "delegate_bot", "check_delegation", "wait_delegation", "start_thread", "send_to_thread", "wait_thread"]);
|
|
754
761
|
const EXTERNAL_TOOL_NAMES = new Set(["list_bots", "ask_bot", "delegate_bot", "check_delegation", "wait_delegation"]);
|
|
755
762
|
const WATCHER_TOOL_NAMES = new Set(["create_options_card"]);
|
|
763
|
+
// A Cloud home never offers this computer or a Local VM, so its bots are not
|
|
764
|
+
// shown them as choices, nor a VM shell they could never have.
|
|
765
|
+
const LOCAL_VM_TOOL_NAMES = new Set(["vm_exec"]);
|
|
766
|
+
const CLOUD_HOME_SURFACE = {
|
|
767
|
+
type: "string", enum: ["auto", "cloud", "browser"],
|
|
768
|
+
description: "auto = suitable configured computer, cloud = remote Boat/VPS, browser = built-in browser. Omit to list. This server runs in the cloud: the user's own computer and a Local VM are not places here.",
|
|
769
|
+
};
|
|
756
770
|
/** The tools one turn is shown, exactly as tools/list serializes them. */
|
|
757
771
|
export function availableTools(profile) {
|
|
772
|
+
const tools = catalogTools(profile);
|
|
773
|
+
return profile.cloudHome
|
|
774
|
+
? tools.filter(tool => !LOCAL_VM_TOOL_NAMES.has(tool.name)).map(tool => tool.name === "select_computer"
|
|
775
|
+
? { ...tool, inputSchema: { ...tool.inputSchema, properties: { surface: CLOUD_HOME_SURFACE } } }
|
|
776
|
+
: tool)
|
|
777
|
+
: tools;
|
|
778
|
+
}
|
|
779
|
+
function catalogTools(profile) {
|
|
758
780
|
const TOOLS = toolDefinitions(profile.externalRuntime);
|
|
759
781
|
const BOT_SCOPED_TOOLS = profile.botId === WATCHER_OPTIONS_CARD_BOT_ID
|
|
760
782
|
? TOOLS
|