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
|
@@ -94,7 +94,70 @@ function questionAnswersById(message, questions) {
|
|
|
94
94
|
return only && flat ? { [only.id]: flat } : {};
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
// shared/tool-scope.ts
|
|
98
|
+
var SERVER_NAME = /^[a-z][a-z0-9_-]{0,31}$/;
|
|
99
|
+
var CONTROL_CHARACTER = /[\x00-\x1f\x7f]/;
|
|
100
|
+
var MAX_SELECTORS = 256;
|
|
101
|
+
var MAX_SELECTOR_LENGTH = 1024;
|
|
102
|
+
function validName(name) {
|
|
103
|
+
return typeof name === "string" && name.trim().length > 0 && name.length <= MAX_SELECTOR_LENGTH && !CONTROL_CHARACTER.test(name) && !name.includes("*");
|
|
104
|
+
}
|
|
105
|
+
function selectorIdentity(selector) {
|
|
106
|
+
if (selector.length > MAX_SELECTOR_LENGTH || CONTROL_CHARACTER.test(selector)) return;
|
|
107
|
+
if (selector.startsWith("native:")) {
|
|
108
|
+
const name = selector.slice(7);
|
|
109
|
+
if (name === "*" || validName(name)) return { kind: "native", name };
|
|
110
|
+
} else if (selector.startsWith("mcp:")) {
|
|
111
|
+
const separator = selector.indexOf(":", 4);
|
|
112
|
+
if (separator < 0) return;
|
|
113
|
+
const server = selector.slice(4, separator);
|
|
114
|
+
const name = selector.slice(separator + 1);
|
|
115
|
+
if (SERVER_NAME.test(server) && (name === "*" || validName(name))) {
|
|
116
|
+
return { kind: "mcp", server, name };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function parseToolScope(value) {
|
|
121
|
+
if (value === void 0) return { ok: true, scope: void 0 };
|
|
122
|
+
const invalid = { ok: false, error: "Tool selection must contain only valid allow and deny lists." };
|
|
123
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return invalid;
|
|
124
|
+
const prototype = Object.getPrototypeOf(value);
|
|
125
|
+
if (prototype !== Object.prototype && prototype !== null) return invalid;
|
|
126
|
+
const scope = {};
|
|
127
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
128
|
+
if (key !== "allow" && key !== "deny") return invalid;
|
|
129
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
130
|
+
if (!descriptor || !("value" in descriptor)) return invalid;
|
|
131
|
+
const list = descriptor.value;
|
|
132
|
+
if (!Array.isArray(list) || list.length > MAX_SELECTORS) return invalid;
|
|
133
|
+
if (!Array.from(list).every((entry) => typeof entry === "string" && selectorIdentity(entry))) return invalid;
|
|
134
|
+
scope[key] = [...new Set(list)];
|
|
135
|
+
}
|
|
136
|
+
return { ok: true, scope };
|
|
137
|
+
}
|
|
138
|
+
function matches(selector, tool) {
|
|
139
|
+
const selected = selectorIdentity(selector);
|
|
140
|
+
return selected?.kind === tool.kind && (selected.kind !== "mcp" || tool.kind === "mcp" && selected.server === tool.server) && (selected.name === "*" || selected.name === tool.name);
|
|
141
|
+
}
|
|
142
|
+
function permits(scope, tool) {
|
|
143
|
+
if (!validName(tool.name) || tool.kind === "mcp" && !SERVER_NAME.test(tool.server)) return false;
|
|
144
|
+
return (scope?.allow === void 0 || scope.allow.some((selector) => matches(selector, tool))) && !scope?.deny?.some((selector) => matches(selector, tool));
|
|
145
|
+
}
|
|
146
|
+
function allowsTool(scope, tool) {
|
|
147
|
+
const parsed = parseToolScope(scope);
|
|
148
|
+
return parsed.ok && permits(parsed.scope, tool);
|
|
149
|
+
}
|
|
150
|
+
|
|
97
151
|
// server/permission-proxy.ts
|
|
152
|
+
var selection = parseToolScope(process.env.OMB_PERMISSION_TOOL_SCOPE === void 0 ? void 0 : (() => {
|
|
153
|
+
try {
|
|
154
|
+
return JSON.parse(process.env.OMB_PERMISSION_TOOL_SCOPE);
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
})());
|
|
159
|
+
if (!selection.ok) throw new Error(selection.error);
|
|
160
|
+
var allowQuestion = allowsTool(selection.scope, { kind: "mcp", server: "ogb", name: "ask_user" });
|
|
98
161
|
var socketPath = process.argv[2] ?? "";
|
|
99
162
|
var waiting = /* @__PURE__ */ new Map();
|
|
100
163
|
var conn = connect(socketPath);
|
|
@@ -203,11 +266,12 @@ async function handle(msg) {
|
|
|
203
266
|
}
|
|
204
267
|
});
|
|
205
268
|
}
|
|
206
|
-
if (msg.method === "tools/list") return send({ jsonrpc: "2.0", id: msg.id, result: { tools: TOOLS } });
|
|
269
|
+
if (msg.method === "tools/list") return send({ jsonrpc: "2.0", id: msg.id, result: { tools: TOOLS.filter((tool) => tool.name === "approve" || allowQuestion) } });
|
|
207
270
|
if (msg.method === "tools/call") {
|
|
208
271
|
const name = msg.params?.name;
|
|
209
272
|
const args = msg.params?.arguments ?? {};
|
|
210
273
|
const reply = (text3) => send({ jsonrpc: "2.0", id: msg.id, result: { content: [{ type: "text", text: text3 }] } });
|
|
274
|
+
if (name === "ask_user" && !allowQuestion) return reply("OpenMausBot: this question tool is excluded by the bot's tool selection.");
|
|
211
275
|
if (name === "approve" && args.tool_name === ASK_USER_QUESTION) {
|
|
212
276
|
return reply(await answerNativeQuestions(args.input));
|
|
213
277
|
}
|
|
@@ -8,7 +8,8 @@ function resolveProxy(relative) {
|
|
|
8
8
|
return existsSync(source) ? source : join(SERVER_ROOT, `${relative}.js`);
|
|
9
9
|
}
|
|
10
10
|
var SPAWNED_PROXIES = {
|
|
11
|
-
browser:
|
|
11
|
+
// The built-in browser and the cloud computer: argv[2] picks which.
|
|
12
|
+
harnessMcp: resolveProxy("harness-mcp-proxy"),
|
|
12
13
|
localComputer: resolveProxy("local-computer-proxy"),
|
|
13
14
|
permission: resolveProxy("permission-proxy"),
|
|
14
15
|
containerMcp: resolveProxy("container-mcp"),
|
|
@@ -17,6 +18,7 @@ var SPAWNED_PROXIES = {
|
|
|
17
18
|
dweb: resolveProxy("drivers/dweb-proxy"),
|
|
18
19
|
connectors: resolveProxy("connector-proxy"),
|
|
19
20
|
mcpGate: resolveProxy("mcp-gate"),
|
|
21
|
+
mcpRemote: resolveProxy("mcp-remote-proxy"),
|
|
20
22
|
phone: resolveProxy("drivers/phone-proxy"),
|
|
21
23
|
hook: resolveProxy("hooks/omb-hook"),
|
|
22
24
|
// Loaded by the external `pi` process via `-e`, not by this server — but
|
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
// not expose the routes.
|
|
12
12
|
import { resolveCompanionControlPlaneURL } from "../electron/companion-account-service.mjs";
|
|
13
13
|
import { ControlPlaneError, createControlPlaneClient, normalizeAccountEmail } from "../electron/control-plane-client.mjs";
|
|
14
|
-
export const ADMIN_EMAILS_ENV = "OMB_SIGNIN_EMAILS";
|
|
15
|
-
export const MEMBER_EMAILS_ENV = "OMB_SIGNIN_MEMBER_EMAILS";
|
|
16
14
|
/** Commas, spaces or newlines between entries; case does not matter. */
|
|
17
15
|
export function parseAllowList(value) {
|
|
18
16
|
return (value ?? "")
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// A bot's activity log: what it did, in plain words, with the outcome.
|
|
2
|
+
//
|
|
3
|
+
// Nothing here is captured for the log's sake. Two records already on disk
|
|
4
|
+
// between them know everything a receipt needs: the per-thread runtime
|
|
5
|
+
// events (harness/bus.ts) say what ran and whether it finished, and the
|
|
6
|
+
// fleet-wide decision log (decision-log.ts) says what was asked, with which
|
|
7
|
+
// arguments, and who allowed it. This module reads both back and folds
|
|
8
|
+
// them into one row per thing that happened.
|
|
9
|
+
//
|
|
10
|
+
// Connector calls pass through the harness relay, whose approval and scope
|
|
11
|
+
// decisions share the decision log. Read those alongside native tool runs.
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { readDecisions } from "./decision-log.js";
|
|
14
|
+
import { isRuntimeEvent, readRecentLines } from "./thread-events.js";
|
|
15
|
+
/** Composio toolkit slugs whose display name is not just a capitalised word. */
|
|
16
|
+
const TOOLKIT_NAMES = {
|
|
17
|
+
GMAIL: "Gmail",
|
|
18
|
+
GITHUB: "GitHub",
|
|
19
|
+
GOOGLESHEETS: "Google Sheets",
|
|
20
|
+
GOOGLECALENDAR: "Google Calendar",
|
|
21
|
+
GOOGLEDRIVE: "Google Drive",
|
|
22
|
+
GOOGLEDOCS: "Google Docs",
|
|
23
|
+
HUBSPOT: "HubSpot",
|
|
24
|
+
LINKEDIN: "LinkedIn",
|
|
25
|
+
YOUTUBE: "YouTube",
|
|
26
|
+
TWITTER: "X",
|
|
27
|
+
WHATSAPP: "WhatsApp",
|
|
28
|
+
ONEDRIVE: "OneDrive",
|
|
29
|
+
};
|
|
30
|
+
/** The agent's own built-in tools, phrased as what they did. */
|
|
31
|
+
const BUILTIN_LABELS = {
|
|
32
|
+
Bash: "Ran a command",
|
|
33
|
+
Read: "Read a file",
|
|
34
|
+
Edit: "Edited a file",
|
|
35
|
+
MultiEdit: "Edited files",
|
|
36
|
+
Write: "Wrote a file",
|
|
37
|
+
Glob: "Searched files",
|
|
38
|
+
Grep: "Searched files",
|
|
39
|
+
LS: "Listed a folder",
|
|
40
|
+
WebSearch: "Searched the web",
|
|
41
|
+
WebFetch: "Fetched a page",
|
|
42
|
+
Skill: "Used a skill",
|
|
43
|
+
Task: "Ran a subagent",
|
|
44
|
+
Agent: "Ran a subagent",
|
|
45
|
+
AskUserQuestion: "Asked you a question",
|
|
46
|
+
TodoWrite: "Updated its task list",
|
|
47
|
+
};
|
|
48
|
+
/** "save_issue" → "Save issue", "TaskUpdate" → "Task update",
|
|
49
|
+
* "CREATE_AN_ISSUE" → "Create an issue". */
|
|
50
|
+
function humanize(raw) {
|
|
51
|
+
const spaced = raw
|
|
52
|
+
.replace(/_+/g, " ")
|
|
53
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
54
|
+
.replace(/\s+/g, " ")
|
|
55
|
+
.trim()
|
|
56
|
+
.toLowerCase();
|
|
57
|
+
return spaced ? spaced[0].toUpperCase() + spaced.slice(1) : raw;
|
|
58
|
+
}
|
|
59
|
+
function toolkitName(slug) {
|
|
60
|
+
return TOOLKIT_NAMES[slug] ?? slug[0] + slug.slice(1).toLowerCase();
|
|
61
|
+
}
|
|
62
|
+
/** Composio names its tools TOOLKIT_ACTION_WORDS, all upper case. */
|
|
63
|
+
const COMPOSIO_TOOL = /^([A-Z][A-Z0-9]+)_([A-Z0-9_]+)$/;
|
|
64
|
+
/** Turn a raw tool name into the app it touched and the action, in words. */
|
|
65
|
+
export function describeTool(name) {
|
|
66
|
+
const builtin = BUILTIN_LABELS[name];
|
|
67
|
+
if (builtin)
|
|
68
|
+
return { app: null, label: builtin };
|
|
69
|
+
if (name.startsWith("mcp__")) {
|
|
70
|
+
const [, server = "", ...rest] = name.split("__");
|
|
71
|
+
const action = rest.join("__");
|
|
72
|
+
if (server === "composio")
|
|
73
|
+
return describeTool(action);
|
|
74
|
+
if (server === "computer")
|
|
75
|
+
return { app: "Computer", label: humanize(action) };
|
|
76
|
+
if (server === "agents")
|
|
77
|
+
return { app: "Team", label: humanize(action) };
|
|
78
|
+
if (server.startsWith("claude_ai_")) {
|
|
79
|
+
return { app: server.slice("claude_ai_".length).replace(/_/g, " "), label: humanize(action) };
|
|
80
|
+
}
|
|
81
|
+
return { app: humanize(server), label: humanize(action) };
|
|
82
|
+
}
|
|
83
|
+
const composio = COMPOSIO_TOOL.exec(name);
|
|
84
|
+
if (composio)
|
|
85
|
+
return { app: toolkitName(composio[1]), label: humanize(composio[2]) };
|
|
86
|
+
return { app: null, label: humanize(name) };
|
|
87
|
+
}
|
|
88
|
+
/** How many decision rows to read back before filtering to one bot. The
|
|
89
|
+
* file is rotation-capped, so this is a ceiling on parse work, not a
|
|
90
|
+
* window that quietly drops history. */
|
|
91
|
+
const DECISION_SCAN = 20_000;
|
|
92
|
+
/** Runtime events read per thread, newest first; the reader tail-walks. */
|
|
93
|
+
const EVENTS_PER_THREAD = 2_000;
|
|
94
|
+
/** An approval is folded into the tool run it unblocked when the run
|
|
95
|
+
* started inside this window around the decision. A verdict is written
|
|
96
|
+
* as the request opens; the run follows it, occasionally by a clock skew's
|
|
97
|
+
* worth in the other direction. */
|
|
98
|
+
const MATCH_BEFORE_MS = 5_000;
|
|
99
|
+
const MATCH_AFTER_MS = 30_000;
|
|
100
|
+
const OUTCOME_OF_DECISION = {
|
|
101
|
+
"auto-approved": "allowed",
|
|
102
|
+
"auto-denied": "denied",
|
|
103
|
+
"user-approved": "allowed",
|
|
104
|
+
"review-would-approve": "allowed",
|
|
105
|
+
"user-denied": "denied",
|
|
106
|
+
"review-would-deny": "denied",
|
|
107
|
+
"card-shown": "waiting",
|
|
108
|
+
};
|
|
109
|
+
function describeRow(base) {
|
|
110
|
+
const { app, label } = describeTool(base.tool);
|
|
111
|
+
return { ...base, app, label };
|
|
112
|
+
}
|
|
113
|
+
/** One row per request: the decision log writes a row when a card is shown
|
|
114
|
+
* and another when it is answered, and the receipt wants the answer. */
|
|
115
|
+
function foldDecisions(rows) {
|
|
116
|
+
const byRequest = new Map();
|
|
117
|
+
rows.forEach((row, index) => {
|
|
118
|
+
const key = row.requestId ?? `row:${index}`;
|
|
119
|
+
const group = byRequest.get(key);
|
|
120
|
+
if (group)
|
|
121
|
+
group.push(row);
|
|
122
|
+
else
|
|
123
|
+
byRequest.set(key, [row]);
|
|
124
|
+
});
|
|
125
|
+
const folded = [];
|
|
126
|
+
for (const group of byRequest.values()) {
|
|
127
|
+
const opened = group[0];
|
|
128
|
+
const latest = group[group.length - 1];
|
|
129
|
+
const summary = group.find((row) => row.summary)?.summary;
|
|
130
|
+
folded.push(describeRow({
|
|
131
|
+
at: opened.at,
|
|
132
|
+
threadId: opened.threadId,
|
|
133
|
+
requestId: opened.requestId,
|
|
134
|
+
tool: latest.tool ?? opened.tool ?? "unknown",
|
|
135
|
+
...(summary ? { summary } : {}),
|
|
136
|
+
outcome: OUTCOME_OF_DECISION[latest.decision],
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
return folded;
|
|
140
|
+
}
|
|
141
|
+
function readToolRuns(eventsDir, threadId) {
|
|
142
|
+
const { lines } = readRecentLines(join(eventsDir, `${threadId}.ndjson`), EVENTS_PER_THREAD, isRuntimeEvent);
|
|
143
|
+
const runs = new Map();
|
|
144
|
+
for (const event of lines) {
|
|
145
|
+
if (event.type === "item.started" && event.itemType === "tool") {
|
|
146
|
+
const key = JSON.stringify([event.turnId ?? "", event.itemId ?? event.eventId]);
|
|
147
|
+
runs.set(key, describeRow({
|
|
148
|
+
at: event.createdAt,
|
|
149
|
+
threadId,
|
|
150
|
+
...(event.turnId ? { turnId: event.turnId } : {}),
|
|
151
|
+
tool: event.title ?? "unknown",
|
|
152
|
+
outcome: "running",
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
else if (event.type === "item.completed" && event.itemType === "tool" && event.itemId) {
|
|
156
|
+
const run = runs.get(JSON.stringify([event.turnId ?? "", event.itemId]));
|
|
157
|
+
if (run)
|
|
158
|
+
run.outcome = event.ok ? "ran" : "failed";
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return [...runs.values()];
|
|
162
|
+
}
|
|
163
|
+
/** The bot's activity, newest first: every tool run in the threads named,
|
|
164
|
+
* and every decision the broker logged for the bot, with an approval folded
|
|
165
|
+
* into the run it unblocked wherever the two can be matched. */
|
|
166
|
+
export function readBotActivity(input) {
|
|
167
|
+
const { dataDir, eventsDir, botId, threadIds, limit } = input;
|
|
168
|
+
const runs = threadIds.flatMap((threadId) => readToolRuns(eventsDir, threadId));
|
|
169
|
+
const decisions = foldDecisions(readDecisions(dataDir, DECISION_SCAN).filter((row) => row.botId === botId));
|
|
170
|
+
const matched = new Set();
|
|
171
|
+
const rows = [...runs];
|
|
172
|
+
for (const decision of decisions) {
|
|
173
|
+
if (decision.outcome !== "allowed") {
|
|
174
|
+
rows.push(decision);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const askedAt = Date.parse(decision.at);
|
|
178
|
+
let best = null;
|
|
179
|
+
for (const run of runs) {
|
|
180
|
+
if (matched.has(run) || run.threadId !== decision.threadId || run.tool !== decision.tool)
|
|
181
|
+
continue;
|
|
182
|
+
const delta = Date.parse(run.at) - askedAt;
|
|
183
|
+
if (delta < -MATCH_BEFORE_MS || delta > MATCH_AFTER_MS)
|
|
184
|
+
continue;
|
|
185
|
+
if (!best || Math.abs(delta) < Math.abs(Date.parse(best.at) - askedAt))
|
|
186
|
+
best = run;
|
|
187
|
+
}
|
|
188
|
+
if (best) {
|
|
189
|
+
matched.add(best);
|
|
190
|
+
if (decision.summary)
|
|
191
|
+
best.summary = decision.summary;
|
|
192
|
+
if (decision.requestId)
|
|
193
|
+
best.requestId = decision.requestId;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
rows.push(decision);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
rows.sort((a, b) => (a.at < b.at ? 1 : a.at > b.at ? -1 : 0));
|
|
200
|
+
return rows.slice(0, Math.max(1, limit));
|
|
201
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// grants the app applies are Full access and the person's exact saved commands.
|
|
8
8
|
// Questions never come through here: a bot's question always reaches a human.
|
|
9
9
|
import { supportsApprovalMode } from "../shared/approval-mode.js";
|
|
10
|
+
import { isOutboundTool } from "../shared/outbound.js";
|
|
10
11
|
/** A failed delivery is a runtime error, not another permission decision.
|
|
11
12
|
* An expired ask must never become a fresh Allow/Deny card. */
|
|
12
13
|
export async function deliverFullAccessApproval(adapter, threadId, requestId, turnId, isCurrent = () => false) {
|
|
@@ -25,7 +26,7 @@ export async function deliverFullAccessApproval(adapter, threadId, requestId, tu
|
|
|
25
26
|
}
|
|
26
27
|
/** Full access is the person's explicit grant to this receiving bot, including
|
|
27
28
|
* delegated work. It never inherits the sender's mode or elevates another bot
|
|
28
|
-
* — with the one exception below (
|
|
29
|
+
* — with the one exception below (delegatedApprovalMode), applied
|
|
29
30
|
* where a Chief's delegated thread is created rather than here.
|
|
30
31
|
* Custom is a provider-config choice rather than an app Full-access grant, so
|
|
31
32
|
* peer-started Custom turns use Auto. Provider support and grant confirmation
|
|
@@ -35,16 +36,38 @@ export function approvalModeForOrigin(mode, origin) {
|
|
|
35
36
|
return "auto";
|
|
36
37
|
return mode;
|
|
37
38
|
}
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
/** Levels by how much runs without asking. Custom is a provider config of
|
|
40
|
+
* its own and is never compared. */
|
|
41
|
+
const LEVEL_RANK = { ask: 0, edits: 1, auto: 2, full: 3 };
|
|
42
|
+
/** The level work a bot hands to a teammate starts at, or null to keep the
|
|
43
|
+
* teammate's own. A Chief of Staff's level flows down: the person set it on
|
|
44
|
+
* the Chief so the team's work runs that way, and switching every thread the
|
|
45
|
+
* Chief opens with a teammate back off "Ask for approval" was the friction
|
|
46
|
+
* people reported. Only a Chief passes its level on, only the level of the
|
|
47
|
+
* conversation it delegates from, and only upward: a teammate already on a
|
|
48
|
+
* higher level keeps it, and one on Custom keeps its own config. The level
|
|
49
|
+
* is capped at what the teammate's engine implements (Full or Auto-accept
|
|
50
|
+
* edits are not everywhere), stepping down to the next level it has. A bot
|
|
51
|
+
* never raises itself this way. */
|
|
52
|
+
export function delegatedApprovalMode(input) {
|
|
53
|
+
if (!input.senderIsChief || input.sameBot)
|
|
54
|
+
return null;
|
|
55
|
+
// A Chief's Custom config is its own; for a teammate it reads as Approve
|
|
56
|
+
// for me, as for any peer-started Custom turn (approvalModeForOrigin).
|
|
57
|
+
const sender = LEVEL_RANK[approvalModeForOrigin(input.senderMode, { peerInitiated: true })] ?? 0;
|
|
58
|
+
const own = LEVEL_RANK[input.recipientMode];
|
|
59
|
+
if (own === undefined)
|
|
60
|
+
return null;
|
|
61
|
+
for (const mode of ["full", "auto", "edits"]) {
|
|
62
|
+
const rank = LEVEL_RANK[mode];
|
|
63
|
+
if (rank > sender)
|
|
64
|
+
continue;
|
|
65
|
+
if (rank <= own)
|
|
66
|
+
return null;
|
|
67
|
+
if (supportsApprovalMode(input.recipientDriverKind, mode))
|
|
68
|
+
return mode;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
48
71
|
}
|
|
49
72
|
// Tools that ask a PERSON something. A question exists so that a human
|
|
50
73
|
// decides; any mode answering one on their behalf defeats the only reason
|
|
@@ -69,6 +92,8 @@ export function autoVerdict(mode, tool, context) {
|
|
|
69
92
|
return { approve: `approved ${tool} (full access)`, source: "full-access" };
|
|
70
93
|
if (context?.requiresExplicitApproval)
|
|
71
94
|
return { approve: null, source: "explicit-approval-block" };
|
|
95
|
+
if (isOutboundTool(tool))
|
|
96
|
+
return { approve: null, source: "outbound-guard" };
|
|
72
97
|
if (context?.commandAllowed)
|
|
73
98
|
return { approve: `approved ${tool} (saved command)`, source: "command-allowlist" };
|
|
74
99
|
if (mode === "auto" || mode === "custom")
|
|
@@ -83,6 +108,7 @@ export function autoVerdict(mode, tool, context) {
|
|
|
83
108
|
* the server keeps sending: cards saved before the key existed still render,
|
|
84
109
|
* and so do the free-text apply errors that have no key at all. */
|
|
85
110
|
export const HELD_NOTE = {
|
|
111
|
+
"approval.held.outbound": "This sends something on your behalf, so it always asks first.",
|
|
86
112
|
"approval.held.native": "The provider requires your approval for this action.",
|
|
87
113
|
"approval.held.sandbox": "This changes the provider sandbox, so only Full access can approve it automatically.",
|
|
88
114
|
"approval.held.undeliveredFull": "Full access couldn't deliver this approval.",
|
|
@@ -93,6 +119,8 @@ export const HELD_NOTE = {
|
|
|
93
119
|
export function approvalHeldNote(context) {
|
|
94
120
|
if (!context.permission)
|
|
95
121
|
return undefined;
|
|
122
|
+
if (context.source === "outbound-guard")
|
|
123
|
+
return "approval.held.outbound";
|
|
96
124
|
if (context.source === "explicit-approval-block")
|
|
97
125
|
return "approval.held.sandbox";
|
|
98
126
|
if (context.source === "native-approval")
|
|
@@ -7,7 +7,7 @@ export function recoveryCapabilityError(from, to) {
|
|
|
7
7
|
supportsWorkspaceFiles(from.driverKind) !== supportsWorkspaceFiles(to.driverKind)) {
|
|
8
8
|
return "The backup cannot use the same workspace.";
|
|
9
9
|
}
|
|
10
|
-
const tools = ["agentsMcp", "computerMcp", "
|
|
10
|
+
const tools = ["agentsMcp", "computerMcp", "composioMcp",
|
|
11
11
|
"phoneMcp", "browserMcp", "localComputerMcp", "customMcp", "images"];
|
|
12
12
|
if (tools.some(key => from.capabilities[key] === true && to.capabilities[key] !== true)) {
|
|
13
13
|
return "The backup does not support all of this engine's tools and attachments.";
|
|
@@ -388,13 +388,6 @@ export function resolveBoatCreate(request) {
|
|
|
388
388
|
return { ...resolved };
|
|
389
389
|
});
|
|
390
390
|
}
|
|
391
|
-
/** Read-only deletion guard. Both a key-only request with an ambiguous
|
|
392
|
-
* provider outcome and a known-but-not-yet-named Boat must keep its bot owner. */
|
|
393
|
-
export function hasUnresolvedBoatCreate(botId) {
|
|
394
|
-
if (!BOT_ID.test(botId))
|
|
395
|
-
throw new Error("invalid bot id for cloud computer creation");
|
|
396
|
-
return withJournalLock((requests) => (requests.some((request) => request.botId === botId && request.resolved !== true)));
|
|
397
|
-
}
|
|
398
391
|
/** Sanitized local recovery authority for configuration guards. The fresh,
|
|
399
392
|
* lock-protected read can prove a remembered provider id even while the
|
|
400
393
|
* account-wide LIST endpoint is eventually consistent. Provider request
|
|
@@ -442,7 +442,6 @@ export function markBoatDeletionBlocked(boxId, value) {
|
|
|
442
442
|
export function boatDeletionSnapshot() {
|
|
443
443
|
return withJournalLock((records) => records.map(clone));
|
|
444
444
|
}
|
|
445
|
-
export const listBoatDeletions = boatDeletionSnapshot;
|
|
446
445
|
export function getBoatDeletion(boxId) {
|
|
447
446
|
if (!BOAT_ID.test(boxId))
|
|
448
447
|
throw new Error("invalid cloud computer id for deletion");
|
|
@@ -451,9 +450,6 @@ export function getBoatDeletion(boxId) {
|
|
|
451
450
|
return record ? clone(record) : null;
|
|
452
451
|
});
|
|
453
452
|
}
|
|
454
|
-
export function isBoatDeletionPending(boxId) {
|
|
455
|
-
return getBoatDeletion(boxId) !== null;
|
|
456
|
-
}
|
|
457
453
|
export function hasPendingBoatDeletionForBot(botId) {
|
|
458
454
|
if (!BOT_ID.test(botId))
|
|
459
455
|
throw new Error("invalid bot id for cloud computer deletion");
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { stringify as stringifyYaml } from "yaml";
|
|
2
2
|
import { cronScheduleLabel } from "../shared/cron-label.js";
|
|
3
|
-
import { BOTMRR_MARKDOWN_VERSION, isPackageDocument,
|
|
3
|
+
import { BOTMRR_MARKDOWN_VERSION, isPackageDocument, PACKAGE_V1_MAX_SKILLS, parsePackageV1, utf8Bytes, } from "../shared/package-format.js";
|
|
4
4
|
// The package file format (v1 and v2) lives in shared/package-format.ts: one
|
|
5
5
|
// validation gate for the server, the renderer and Admin. This module keeps
|
|
6
6
|
// the v1 names the v1 exporter and its Markdown playbook are written in, and
|
|
7
7
|
// re-exports the v2 reader so server code has one place to import from.
|
|
8
8
|
export { canonicalJson, downgradeToV1, PACKAGE_MAX_BYTES, PACKAGE_VERSION, PackageFormatError, packageKeys, packageScanFindings, packageSecretFindings, packageSummary, parsePackageDocument, redactPackageSecrets, upgradeV1, } from "../shared/package-format.js";
|
|
9
|
-
export const BOT_PACKAGE_FORMAT = PACKAGE_FORMAT;
|
|
10
|
-
export const BOT_PACKAGE_VERSION = PACKAGE_V1_VERSION;
|
|
11
|
-
export { BOTMRR_MARKDOWN_VERSION };
|
|
12
|
-
export const BOT_PACKAGE_SKILLS_VERSION = 1;
|
|
13
9
|
export const BOT_PACKAGE_MAX_SKILLS = PACKAGE_V1_MAX_SKILLS;
|
|
14
10
|
const BOT_PACKAGE_MARKDOWN_MAX_BYTES = 1_000_000;
|
|
15
11
|
export function isBotPackage(value) {
|
|
@@ -98,17 +94,3 @@ export function renderBotPackageMarkdown(document) {
|
|
|
98
94
|
throw new Error("The bot playbook is too large");
|
|
99
95
|
return markdown;
|
|
100
96
|
}
|
|
101
|
-
export function packageAgentAsMember(agent) {
|
|
102
|
-
return {
|
|
103
|
-
key: agent.key,
|
|
104
|
-
name: agent.name,
|
|
105
|
-
title: agent.title ?? "",
|
|
106
|
-
description: agent.description ?? "",
|
|
107
|
-
...(agent.soul !== undefined ? { soul: agent.soul } : {}),
|
|
108
|
-
appearance: {
|
|
109
|
-
color: agent.appearance.color,
|
|
110
|
-
...(agent.appearance.mascotExpression ? { mascotExpression: agent.appearance.mascotExpression } : {}),
|
|
111
|
-
...(agent.appearance.mascotBody ? { mascotBody: agent.appearance.mascotBody } : {}),
|
|
112
|
-
},
|
|
113
|
-
};
|
|
114
|
-
}
|
|
@@ -10,6 +10,9 @@ const HEARTBEAT_MS = 10_000;
|
|
|
10
10
|
// The native press resolver supplies the virtual key codes and Enter/Tab text
|
|
11
11
|
// that its raw input_keyboard relay omits. Keep unknown keys literal.
|
|
12
12
|
const DISCRETE_KEYS = new Set(["Backspace", "Enter", "Tab", "Escape", "Delete", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End", "PageUp", "PageDown"]);
|
|
13
|
+
// The panel has no take-control button: interacting takes control, and an
|
|
14
|
+
// interrupted action is recovered from the panel's menu.
|
|
15
|
+
const RESTART_NEEDED = "A browser action was interrupted. Choose Restart browser… in the browser menu before continuing.";
|
|
13
16
|
export class BrowserLiveError extends Error {
|
|
14
17
|
status;
|
|
15
18
|
constructor(message, status = 400) { super(message); this.name = "BrowserLiveError"; this.status = status; }
|
|
@@ -569,22 +572,24 @@ export class BrowserLive {
|
|
|
569
572
|
try {
|
|
570
573
|
const taking = this.runtime.take(viewer.session, viewer.id);
|
|
571
574
|
this.control(viewer.session);
|
|
572
|
-
|
|
575
|
+
// waited: the bot's own action finished first, so input the person
|
|
576
|
+
// aimed at the page before the grant may no longer fit it.
|
|
577
|
+
const waited = await taking;
|
|
573
578
|
if (!this.current(viewer)) {
|
|
574
579
|
this.close(viewer);
|
|
575
580
|
throw new BrowserLiveError("This browser view closed.", 409);
|
|
576
581
|
}
|
|
577
|
-
return { ok: true };
|
|
582
|
+
return { ok: true, waited: waited === true };
|
|
578
583
|
}
|
|
579
584
|
catch {
|
|
580
|
-
throw
|
|
585
|
+
throw this.refusal(viewer, "Another browser view or bot action is using this browser. Try again shortly.");
|
|
581
586
|
}
|
|
582
587
|
finally {
|
|
583
588
|
this.control(viewer.session);
|
|
584
589
|
}
|
|
585
590
|
}
|
|
586
591
|
if (!this.runtime.canControl(viewer.session, viewer.id))
|
|
587
|
-
throw
|
|
592
|
+
throw this.refusal(viewer, "Browser control changed. Try again.");
|
|
588
593
|
if (viewer.pendingActions >= 32)
|
|
589
594
|
throw new BrowserLiveError("Too many browser actions are pending. Try again shortly.", 429);
|
|
590
595
|
viewer.pendingActions += 1;
|
|
@@ -600,13 +605,17 @@ export class BrowserLive {
|
|
|
600
605
|
return { ok: true };
|
|
601
606
|
}
|
|
602
607
|
catch (error) {
|
|
603
|
-
throw error instanceof BrowserLiveError ? error :
|
|
608
|
+
throw error instanceof BrowserLiveError ? error : this.refusal(viewer, "Browser control changed. Try again.");
|
|
604
609
|
}
|
|
605
610
|
finally {
|
|
606
611
|
viewer.pendingActions -= 1;
|
|
607
612
|
this.control(viewer.session);
|
|
608
613
|
}
|
|
609
614
|
}
|
|
615
|
+
/** Why this viewer cannot act now: an interrupted browser needs a restart. */
|
|
616
|
+
refusal(viewer, otherwise) {
|
|
617
|
+
return new BrowserLiveError(this.runtime.interrupted(viewer.session) ? RESTART_NEEDED : otherwise, 409);
|
|
618
|
+
}
|
|
610
619
|
closeForSession(session) { for (const viewer of this.viewers.values())
|
|
611
620
|
if (viewer.session === session)
|
|
612
621
|
this.close(viewer); }
|
|
@@ -216,8 +216,7 @@ export class BrowserRuntime {
|
|
|
216
216
|
options;
|
|
217
217
|
closeBrowser;
|
|
218
218
|
constructor({ closeBrowser, ...options } = {}) {
|
|
219
|
-
|
|
220
|
-
this.options = { requestTimeoutMs: 120_000, takeoverTimeoutMs: 15_000, idleMs: 60_000, maxPending: 16, resultBudget: Number.isFinite(budget) && budget > 0 ? budget : DEFAULT_BROWSER_RESULT_BUDGET, ...options };
|
|
219
|
+
this.options = { requestTimeoutMs: 120_000, takeoverTimeoutMs: 15_000, idleMs: 60_000, maxPending: 16, resultBudget: DEFAULT_BROWSER_RESULT_BUDGET, ...options };
|
|
221
220
|
this.closeBrowser = closeBrowser ?? (async () => false);
|
|
222
221
|
}
|
|
223
222
|
gate(session) {
|
|
@@ -364,6 +363,8 @@ export class BrowserRuntime {
|
|
|
364
363
|
};
|
|
365
364
|
return method === "tools/call" ? this.withAgentAction(session, invoke) : invoke();
|
|
366
365
|
}
|
|
366
|
+
/** Resolves true when the grant had to wait for the bot's own browser
|
|
367
|
+
* action to finish: the page may have changed since the person aimed. */
|
|
367
368
|
async take(session, owner) {
|
|
368
369
|
if (!owner)
|
|
369
370
|
throw new Error("Browser control requires an owner.");
|
|
@@ -374,6 +375,7 @@ export class BrowserRuntime {
|
|
|
374
375
|
throw new Error("Another person controls this browser.");
|
|
375
376
|
gate.owner = owner; // synchronous: no new agent work slips in while draining.
|
|
376
377
|
gate.ready = false;
|
|
378
|
+
const waited = gate.agents > 0;
|
|
377
379
|
await new Promise((resolve, reject) => {
|
|
378
380
|
const finish = (error) => {
|
|
379
381
|
clearTimeout(timer);
|
|
@@ -398,12 +400,15 @@ export class BrowserRuntime {
|
|
|
398
400
|
gate.changed.add(check);
|
|
399
401
|
check();
|
|
400
402
|
});
|
|
403
|
+
return waited;
|
|
401
404
|
}
|
|
402
405
|
canControl(session, owner) {
|
|
403
406
|
const gate = this.gates.get(session);
|
|
404
407
|
return Boolean(owner && gate?.owner === owner && gate.ready && !gate.releasing && !gate.closing && !gate.uncertain && gate.agents === 0);
|
|
405
408
|
}
|
|
406
409
|
heldBy(session) { return this.gates.get(session)?.owner ?? null; }
|
|
410
|
+
/** An interrupted action left the browser's state unknown; only a restart clears it. */
|
|
411
|
+
interrupted(session) { return this.gates.get(session)?.uncertain === true; }
|
|
407
412
|
/** A disconnected viewer may leave a physical key/button pressed. Never
|
|
408
413
|
* let an agent inherit that input state; explicit restart clears it. */
|
|
409
414
|
abandonHumanInput(session, owner) {
|
|
@@ -17,8 +17,11 @@ export function restoreChannelMessages() {
|
|
|
17
17
|
const entry = queues.get(row.threadId) ?? { groupId: row.ownerId, items: [] };
|
|
18
18
|
if (entry.groupId !== row.ownerId)
|
|
19
19
|
throw new Error("queued task belongs to another channel");
|
|
20
|
+
// a channel line is only ever stamped "api"; "call" belongs to 1:1 rows
|
|
21
|
+
const { via, ...payload } = row.payload;
|
|
20
22
|
entry.items.push({
|
|
21
|
-
...
|
|
23
|
+
...payload,
|
|
24
|
+
...(via === "api" ? { via } : {}),
|
|
22
25
|
id: row.id,
|
|
23
26
|
mode: row.payload.mode ?? "chat",
|
|
24
27
|
// rows queued before timestamps were kept read as queued at restore
|
|
@@ -9,7 +9,7 @@ const sectionKey = (section) => section?.trim() || "";
|
|
|
9
9
|
/** Dynamic system context for a section's Chief of Staff.
|
|
10
10
|
* It names the current team on every turn, while list_bots remains the
|
|
11
11
|
* authoritative tool for IDs and live availability at delegation time. */
|
|
12
|
-
export function chiefOfStaffSystemPrompt(chiefId, bots, canDelegate,
|
|
12
|
+
export function chiefOfStaffSystemPrompt(chiefId, bots, canDelegate, boundedCoordination = false) {
|
|
13
13
|
const chief = bots.find((bot) => bot.id === chiefId);
|
|
14
14
|
const chiefSection = sectionKey(chief?.section);
|
|
15
15
|
const sectionName = peerName(chiefSection) || "General";
|
|
@@ -29,7 +29,7 @@ export function chiefOfStaffSystemPrompt(chiefId, bots, canDelegate, trustedOpen
|
|
|
29
29
|
});
|
|
30
30
|
const delegation = canDelegate
|
|
31
31
|
? boundedCoordination
|
|
32
|
-
? "Use list_bots or list_room_targets for the live reachable roster. Use coordinate_bots to ask actual teammates for advice or assign concrete work.
|
|
32
|
+
? "Use list_bots or list_room_targets for the live reachable roster. Use coordinate_bots to ask actual teammates for advice or assign concrete work. Give clear briefs, then end your turn; you resume automatically after their results return. Leads can coordinate their own specialists. Do not poll, send acknowledgements as new work, or substitute native helpers for named bots. On return, verify the requested outcome, resolve decisions within the user's scope, request concrete corrections with rework=true when necessary, and return one consolidated answer. Consultations are advice, not proof that work or tests ran. A refusal from coordinate_bots means nothing was sent: fix what it names (usually the id — copy it from list_bots or your roster; a unique teammate name also works) and retry, and never describe a handoff the tool did not accept."
|
|
33
33
|
: [
|
|
34
34
|
"Use list_bots to confirm the live roster and IDs. When assigning work to a teammate, use delegate_bot: it returns immediately, keeps you available to the user, and delivers the teammate's outcome back into this conversation automatically — success or failure. When the result arrives you are woken with it: report it to the user and act. If the teammate fails or stalls, tell the user plainly and decide the next step yourself.",
|
|
35
35
|
"After delegate_bot accepts the task, acknowledge the handoff and continue with any independent work or end your turn. Do not call wait_delegation or repeatedly poll check_delegation in the same turn.",
|
|
@@ -52,6 +52,5 @@ export function chiefOfStaffSystemPrompt(chiefId, bots, canDelegate, trustedOpen
|
|
|
52
52
|
canDelegate ? "When the user asks you to assemble or configure a team, use list_team_setup for the exact authorized teams, bot IDs and model catalog, then propose_team_setup once with all named specialists and their profile/model changes. Include new teams explicitly; the plan covers their creation and your access. Existing thread models and other bots' execution permissions stay unchanged. Follow the tool result: granted Full Access may apply the plan immediately; after an applied result, continue already-requested work without another confirmation. Only if review is pending, end your turn: the user's decision automatically resumes you once with a structured result. Report failed or cancelled results honestly. Do not ask for another yes, poll, or repeat the proposal. After successful setup, use the available coordination tools for already requested work. Use create_bot only for a single specialist when no combined setup was requested. For explicitly requested bot deletion, use propose_bot_deletion separately and follow its applied or pending result too. Do not create duplicate or unnecessary bots." : "",
|
|
53
53
|
chief?.managedSections?.length ? "Reachable teammates in your allowed teams:" : `Current ${sectionName} section team:`,
|
|
54
54
|
roster,
|
|
55
|
-
trustedOpenMausStatus,
|
|
56
55
|
].filter(Boolean).join("\n");
|
|
57
56
|
}
|