openmausbot 0.1.92 → 0.1.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{DesktopViewer-Do6lBa4C.js → DesktopViewer-CKNR3SEW.js} +1 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-Cd3X7Hxc.js → abnfDiagram-O67JEVCF-Ckrm1I1X.js} +1 -1
- package/dist/assets/{arc-DIYc7Ymx.js → arc-BBdBuCgB.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-DBmRGVyP.js → architectureDiagram-NJMV4G6O-CDdA3Hjs.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CPFePSEE.js → blockDiagram-BEXU5L5S-AUrsx1YR.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-DD_DO1qy.js → c4Diagram-YGBWAQC7-BEqQ0Unh.js} +1 -1
- package/dist/assets/channel-BU94VpxE.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BNEeNSjX.js → chunk-2Q5K7J3B-CEEuvd4C.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-BbinLaqH.js → chunk-3FUC2YCW-DyANCiHl.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-BBgYrgDR.js → chunk-5VM5RSS4-Dx_Slpw9.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-ezXj-kA-.js → chunk-GTNCS2PH-Dl7h7iiD.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-9FxZ3diV.js → chunk-GWA4HPMP-BLx07SPb.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-CO-40JpI.js → chunk-JWPE2WC7-BMxDj3n7.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-BEhjslYE.js → chunk-NETBCI7D-CN66PT4H.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-CNeq4n7_.js → chunk-WEXAMYUT-6HwhsDKw.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-OsCY7KSi.js → chunk-XXDRQBXY-gWnLt7SN.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-CloRUEtk.js → classDiagram-v2-NBCMYWYE-CMS5l2ea.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-D1BGZhp4.js → cose-bilkent-JH36ORCC-CV7sQwIG.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-CUf4T2CN.js → cynefin-OW5HDTMX-CSyl-7vl.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-DPEZrFGA.js → cynefinDiagram-VND7K2PF-BCLKL6az.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-bDe6GIv5.js → dagre-6A5THRUB-DiaZs6S1.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-CdcUQz55.js → diagram-22UHCM2B-BeTtmt3I.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-BZpqGg7c.js → diagram-3UASUU5V-CsdBG-QQ.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-DvGM6ugp.js → diagram-ATOU4E4O-BczvdbR1.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DSTmMm0s.js → diagram-CDSNMT55-DHTTTMhi.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-15cucnMn.js → diagram-MLGK6HIB-RTjHyd8z.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-tMbuaE6n.js → diagram-MPIPVDR6-BDenHEH_.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-BYxYzjHI.js → ebnfDiagram-ZINNZB2B-BSiRSbtL.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-v6bj852W.js → elk-276RUBZZ-BCAElss-.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-5lBBlnd2.js → erDiagram-OPXOYQCR-C4_1ZPJe.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-CZyl1zIj.js → flowDiagram-KWPJA3E3-Bcb4Z75S.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-BiAI-ltZ.js → ganttDiagram-FUAMR5RP-BiPOzxzb.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-BAZISkj3.js → gitGraphDiagram-X574FWY7-B1wtF3ZS.js} +1 -1
- package/dist/assets/index-Biji-bfL.css +1 -0
- package/dist/assets/{index-DGBaEXrM.js → index-BkYwilkG.js} +1 -1
- package/dist/assets/index-DZlhoES0.js +597 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-B6ooOy-Z.js → infoDiagram-VRGFBTTK-A19No71U.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-BZ9ksi-B.js → ishikawaDiagram-OU5B5YK6-BQsqJ_cV.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-DWqlzIEe.js → journeyDiagram-ZHPQQLJL-DAO0pVan.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-JX6piDwL.js → kanban-definition-PNTS6WVX-NepmKEe2.js} +1 -1
- package/dist/assets/{linear-CmOWzX0P.js → linear-bq1PTDTW.js} +1 -1
- package/dist/assets/{mermaid.core-CF1zwoQ-.js → mermaid.core-s4LTbQle.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-CGh2K4fW.js → mindmap-definition-NLK3R4M7-LtdbgHFH.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-CDwYN0s7.js → pegDiagram-GJSIUBJH-tdbaCxiK.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-1rfjJocB.js → pieDiagram-5QR66LMP-DvkbpW8l.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-tKPvPiok.js → quadrantDiagram-O4NWA36T-Do4KaH-E.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-BHLm5vXR.js → railroadDiagram-XR7U4H2S-BQWULOAs.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-CB45STp7.js → requirementDiagram-PLB6GJNP-w9kei1FU.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-55QA_huC.js → sankeyDiagram-IPEJSGJF-D2gRy0e8.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-BckFmQHn.js → sequenceDiagram-PO4LG4MO-a2sO5_Eo.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-BFCOJF11.js → sizeCapture-INFHLROL-CiPJmFEB.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-CZvmiVVN.js → stateDiagram-v2-GCMORJYK-DAkUUlL7.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-BpDUCEKy.js → swimlanes-2SLR337P-DANf2R8Y.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CdHxG8aM.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-BpxHq6IK.js → timeline-definition-EJHVYXUP-BKpQmzrT.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-Cj66vm02.js → usecaseDiagram-POWQR4AR-BtI7qLN-.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-C9nRkWE_.js → vennDiagram-UO4OBE2U-gXJXrAG_.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-D-me_nWj.js → wardleyDiagram-VNRHLVJA-GU8Po5-n.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-Dc_fL4mk.js → xychartDiagram-PMCCYNJV-BF4J30g6.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/companion/src/routes.js +24 -1
- package/dist-server/container-mcp.js +6 -1
- package/dist-server/drivers/agents-proxy.js +2 -0
- package/dist-server/index.js +1722 -906
- package/dist-server/local-computer-proxy.js +5 -1
- package/dist-server/mcp-gate.js +5 -1
- package/dist-server/openmausbot.js +763 -93
- package/dist-server/pair-cli.js +763 -93
- package/dist-server/server/boat.js +3 -3
- package/dist-server/server/cloud-owner.js +1 -1
- package/dist-server/server/computer-control.js +6 -0
- package/dist-server/server/config.js +14 -1
- package/dist-server/server/container-computer.js +43 -32
- package/dist-server/server/digest.js +12 -2
- package/dist-server/server/drivers/acp/core.js +172 -12
- package/dist-server/server/drivers/acp/cursor.js +13 -1
- package/dist-server/server/drivers/acp/quiet-status.js +257 -0
- package/dist-server/server/drivers/acp/qwen.js +18 -0
- package/dist-server/server/drivers/builtIn.js +2 -0
- package/dist-server/server/drivers/cerebras.js +108 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +20 -3
- package/dist-server/server/drivers/claude.js +17 -3
- package/dist-server/server/drivers/openai-chat.js +26 -2
- package/dist-server/server/drivers/prompt-split.js +2 -0
- package/dist-server/server/env-path.js +6 -2
- package/dist-server/server/index.js +210 -57
- package/dist-server/server/local-vm-stop-reason.js +26 -0
- package/dist-server/server/provider-key-check.js +2 -1
- package/dist-server/server/routes/desktop-viewer.js +50 -6
- package/dist-server/server/routines.js +6 -3
- package/dist-server/server/store.js +54 -0
- package/dist-server/server/vps-computer.js +1 -1
- package/dist-server/server/workspace-backup.js +22 -0
- package/dist-server/server/workspace.js +1 -1
- package/dist-server/shared/approval-mode.js +1 -1
- package/dist-server/shared/redact.js +1 -0
- package/dist-server/shared/tool-name.js +22 -0
- package/dist-server/vps-container-mcp.js +6 -1
- package/dist-server/workspace-backup.worker.js +14 -0
- package/package.json +1 -1
- package/dist/assets/channel-Dtk5Mtic.js +0 -1
- package/dist/assets/index-Cdmjrl28.css +0 -1
- package/dist/assets/index-DR7YPMkW.js +0 -597
- package/dist/assets/swimlanesDiagram-TC7HE7FX-M0GYW9Y3.js +0 -8
|
@@ -10,6 +10,8 @@ const ROUTE = /^\/api\/desktop-viewer\/(local\/(?:shared|bot-[a-f0-9]{64}|pool-\
|
|
|
10
10
|
const HANDSHAKE_MS = 10_000;
|
|
11
11
|
const RECHECK_MS = 5_000;
|
|
12
12
|
const WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
13
|
+
const BOT_ID = /^[\w-]+$/;
|
|
14
|
+
const CONTROL_LEASE = /^[A-Za-z0-9_-]{16,120}$/;
|
|
13
15
|
export function desktopViewerUrl(target, threadId) {
|
|
14
16
|
const params = new URLSearchParams({ target, ...(threadId ? { threadId } : {}) });
|
|
15
17
|
return `/desktop-viewer#${params}`;
|
|
@@ -67,7 +69,7 @@ export function createDesktopViewer(deps) {
|
|
|
67
69
|
}).finally(() => clearTimeout(timeout));
|
|
68
70
|
});
|
|
69
71
|
}
|
|
70
|
-
const route = async ({ req, res, path, method, auth, json }) => {
|
|
72
|
+
const route = async ({ req, res, url, path, method, auth, json }) => {
|
|
71
73
|
const match = ROUTE.exec(path);
|
|
72
74
|
if (!match)
|
|
73
75
|
return PASS;
|
|
@@ -78,12 +80,39 @@ export function createDesktopViewer(deps) {
|
|
|
78
80
|
// to admin, like the existing Local VM status and control endpoints.
|
|
79
81
|
if (!auth.scopes.includes("admin") || !isSameOrigin(req))
|
|
80
82
|
return json(res, 403, { error: "forbidden" });
|
|
83
|
+
// A viewer bound to a control lease: both names or neither, well formed,
|
|
84
|
+
// and holding before anything is inspected. Checked again with every
|
|
85
|
+
// liveness pass, so handing back closes the desktop within seconds. The
|
|
86
|
+
// conversation, when named, picks the VM seat the join picked.
|
|
87
|
+
const botId = url.searchParams.get("botId");
|
|
88
|
+
const controlLeaseId = url.searchParams.get("controlLeaseId");
|
|
89
|
+
const threadId = url.searchParams.get("threadId") ?? undefined;
|
|
90
|
+
if ((botId === null) !== (controlLeaseId === null)) {
|
|
91
|
+
return json(res, 400, { error: "botId and controlLeaseId go together" });
|
|
92
|
+
}
|
|
93
|
+
if (botId !== null && (!BOT_ID.test(botId) || !CONTROL_LEASE.test(controlLeaseId) || !deps.lease
|
|
94
|
+
|| (threadId !== undefined && !BOT_ID.test(threadId)))) {
|
|
95
|
+
return json(res, 400, { error: "botId, threadId or controlLeaseId is not valid" });
|
|
96
|
+
}
|
|
97
|
+
if (botId === null && threadId !== undefined)
|
|
98
|
+
return json(res, 400, { error: "threadId needs botId and controlLeaseId" });
|
|
99
|
+
// Do not let a saved or constructed socket URL bypass the join refusal:
|
|
100
|
+
// a bot's control lease does not exclude other users of a shared desktop.
|
|
101
|
+
if (botId !== null && (match[1] === "local/shared" || match[1].startsWith("local/pool-"))) {
|
|
102
|
+
return json(res, 409, { error: "Phone control requires a per-bot Local VM. Select per-bot mode in Settings → Computers." });
|
|
103
|
+
}
|
|
104
|
+
const bound = botId === null ? undefined : deps.lease(match[1], botId, controlLeaseId, threadId);
|
|
105
|
+
if (botId !== null && !bound)
|
|
106
|
+
return json(res, 409, { error: "Take control of this computer first" });
|
|
107
|
+
const holds = () => bound?.() ?? true;
|
|
81
108
|
const target = deps.target(match[1]);
|
|
82
109
|
if (!target)
|
|
83
110
|
return json(res, 404, { error: "Desktop not found" });
|
|
84
111
|
const upgrade = upgrades.get(req);
|
|
85
112
|
if (upgrade && auth.kind === "session")
|
|
86
113
|
upgrade.owner = auth.session.id;
|
|
114
|
+
if (upgrade && botId !== null)
|
|
115
|
+
upgrade.botId = botId;
|
|
87
116
|
let connection;
|
|
88
117
|
try {
|
|
89
118
|
connection = await target.resolve();
|
|
@@ -98,6 +127,8 @@ export function createDesktopViewer(deps) {
|
|
|
98
127
|
const live = () => deps.live(auth) && deps.target(match[1])?.key === target.key && (connection.live?.() ?? true);
|
|
99
128
|
if (!live())
|
|
100
129
|
return json(res, 401, { error: "Viewer access expired" });
|
|
130
|
+
if (!holds())
|
|
131
|
+
return json(res, 409, { error: "Take control of this computer first" });
|
|
101
132
|
if (!Number.isInteger(connection.port) || connection.port < 1 || connection.port > 65535) {
|
|
102
133
|
return json(res, 409, { error: "The desktop viewer is not available." });
|
|
103
134
|
}
|
|
@@ -150,7 +181,7 @@ export function createDesktopViewer(deps) {
|
|
|
150
181
|
upstream.once("response", (answer) => { answer.resume(); fail(); });
|
|
151
182
|
upstream.once("upgrade", (answer, remote, remoteHead) => {
|
|
152
183
|
const accept = createHash("sha1").update(key + WS_GUID).digest("base64");
|
|
153
|
-
if (!live() || socket.destroyed || answer.headers["sec-websocket-accept"] !== accept
|
|
184
|
+
if (!live() || !holds() || socket.destroyed || answer.headers["sec-websocket-accept"] !== accept
|
|
154
185
|
|| answer.headers.upgrade?.toLowerCase() !== "websocket") {
|
|
155
186
|
remote.destroy();
|
|
156
187
|
fail();
|
|
@@ -171,7 +202,7 @@ export function createDesktopViewer(deps) {
|
|
|
171
202
|
socket.pipe(remote).pipe(socket);
|
|
172
203
|
connection.touch?.();
|
|
173
204
|
recheck = setInterval(() => {
|
|
174
|
-
if (!live())
|
|
205
|
+
if (!live() || !holds())
|
|
175
206
|
upgrade.close();
|
|
176
207
|
else
|
|
177
208
|
connection.touch?.();
|
|
@@ -182,9 +213,22 @@ export function createDesktopViewer(deps) {
|
|
|
182
213
|
};
|
|
183
214
|
return {
|
|
184
215
|
route, attach,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
216
|
+
/** Close a session's viewers: all of them (sign-out, revocation), or only
|
|
217
|
+
* those it opened under a lease on one bot (that bot's hand-back). */
|
|
218
|
+
closeForOwner: (owner, botId) => {
|
|
219
|
+
let closed = 0;
|
|
220
|
+
for (const upgrade of upgrades.values()) {
|
|
221
|
+
if (upgrade.owner !== owner || (botId !== undefined && upgrade.botId !== botId))
|
|
222
|
+
continue;
|
|
223
|
+
// A viewer already closed stays listed until its socket's close
|
|
224
|
+
// event, which arrives later on Windows: not closed or counted again.
|
|
225
|
+
if (upgrade.socket.destroyed)
|
|
226
|
+
continue;
|
|
227
|
+
upgrade.close();
|
|
228
|
+
closed++;
|
|
229
|
+
}
|
|
230
|
+
return closed;
|
|
231
|
+
},
|
|
188
232
|
closeAll: () => {
|
|
189
233
|
stopped = true;
|
|
190
234
|
for (const upgrade of upgrades.values())
|
|
@@ -1286,14 +1286,17 @@ export class RoutineManager {
|
|
|
1286
1286
|
// A webhook is an incoming message, so make its task the bot's live
|
|
1287
1287
|
// chat immediately. Scheduled work stays in its own task unless the
|
|
1288
1288
|
// workspace has asked for runs to join the conversation they report to.
|
|
1289
|
+
const startedAt = this.now();
|
|
1290
|
+
const suffix = ` · ${new Date(startedAt).toLocaleString("en-US", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" })}`;
|
|
1291
|
+
const title = `${run.routineName.slice(0, 80 - suffix.length).trimEnd()}${suffix}`;
|
|
1289
1292
|
const joined = run.target === "room-goal" ? null : this.options.joinConversation?.(run) || null;
|
|
1290
1293
|
const task = joined
|
|
1291
1294
|
? { threadId: joined }
|
|
1292
1295
|
: run.target === "room-goal"
|
|
1293
1296
|
? run.groupId
|
|
1294
|
-
? this.options.createGoalTask?.(run.groupId,
|
|
1297
|
+
? this.options.createGoalTask?.(run.groupId, title) ?? null
|
|
1295
1298
|
: null
|
|
1296
|
-
: this.options.createTask(run.botId,
|
|
1299
|
+
: this.options.createTask(run.botId, title, run.triggerSource === "webhook", run.routineId);
|
|
1297
1300
|
if (!task) {
|
|
1298
1301
|
this.failRun(run, run.target === "room-goal"
|
|
1299
1302
|
? "Could not create a room task for this goal"
|
|
@@ -1301,7 +1304,7 @@ export class RoutineManager {
|
|
|
1301
1304
|
continue;
|
|
1302
1305
|
}
|
|
1303
1306
|
run.threadId = task.threadId;
|
|
1304
|
-
run.startedAt =
|
|
1307
|
+
run.startedAt = startedAt;
|
|
1305
1308
|
run.status = "running";
|
|
1306
1309
|
this.save();
|
|
1307
1310
|
this.emitRun(run);
|
|
@@ -467,8 +467,13 @@ export class Store {
|
|
|
467
467
|
mkdirSync(DATA_DIR, { recursive: true });
|
|
468
468
|
for (const file of [BOTS_FILE, GROUPS_FILE])
|
|
469
469
|
tightenRegistryFile(file);
|
|
470
|
+
// Whether the bot list is the real one. Room repair below trusts it to
|
|
471
|
+
// say which members no longer exist; an unreadable file must never read
|
|
472
|
+
// as "every member was deleted".
|
|
473
|
+
let botsLoaded = false;
|
|
470
474
|
try {
|
|
471
475
|
this.bots = JSON.parse(readFileSync(BOTS_FILE, "utf8"));
|
|
476
|
+
botsLoaded = Array.isArray(this.bots);
|
|
472
477
|
}
|
|
473
478
|
catch {
|
|
474
479
|
this.bots = [];
|
|
@@ -637,9 +642,17 @@ export class Store {
|
|
|
637
642
|
botsMigrated = true;
|
|
638
643
|
}
|
|
639
644
|
}
|
|
645
|
+
const botIds = new Set(this.bots.map((b) => b.id));
|
|
640
646
|
for (const g of this.groups) {
|
|
641
647
|
g.busyBotId = null;
|
|
642
648
|
delete g.turnStartedAt;
|
|
649
|
+
// A deleted bot used to stay a member for good: counted on the room's
|
|
650
|
+
// Save button and refused by the roster check on every save (MOCA-264).
|
|
651
|
+
// Bot-to-bot channels keep their pair; they are not edited as rooms.
|
|
652
|
+
if (botsLoaded && !g.dm && g.memberIds.some((id) => !botIds.has(id))) {
|
|
653
|
+
g.memberIds = g.memberIds.filter((id) => botIds.has(id));
|
|
654
|
+
groupsMigrated = true;
|
|
655
|
+
}
|
|
643
656
|
const normalized = normalizeGroupDefaultResponder(g.defaultResponder, g.memberIds, Boolean(g.dm));
|
|
644
657
|
if (JSON.stringify(normalized) !== JSON.stringify(g.defaultResponder))
|
|
645
658
|
groupsMigrated = true;
|
|
@@ -1764,6 +1777,27 @@ export class Store {
|
|
|
1764
1777
|
this.saveBots(nextBots);
|
|
1765
1778
|
this.bots = nextBots;
|
|
1766
1779
|
this.legacyActivities.delete(id);
|
|
1780
|
+
// A deleted bot leaves every room it was in, and a room it led falls back
|
|
1781
|
+
// to its next member. Bot-to-bot channels keep their pair.
|
|
1782
|
+
const rooms = this.groups.filter((g) => !g.dm && g.memberIds.includes(id));
|
|
1783
|
+
for (const g of rooms) {
|
|
1784
|
+
g.memberIds = g.memberIds.filter((member) => member !== id);
|
|
1785
|
+
if (g.busyBotId === id) {
|
|
1786
|
+
g.busyBotId = null;
|
|
1787
|
+
delete g.turnStartedAt;
|
|
1788
|
+
}
|
|
1789
|
+
g.defaultResponder = normalizeGroupDefaultResponder(g.defaultResponder, g.memberIds, false);
|
|
1790
|
+
}
|
|
1791
|
+
if (rooms.length) {
|
|
1792
|
+
// Bot removal is already durable. Finish erasing its data even if this
|
|
1793
|
+
// write fails; startup repair removes these stale memberships later.
|
|
1794
|
+
try {
|
|
1795
|
+
this.saveGroups();
|
|
1796
|
+
}
|
|
1797
|
+
catch (error) {
|
|
1798
|
+
console.warn("store: room cleanup will retry on restart", error);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1767
1801
|
// every task's transcript goes with the bot, not just the open one
|
|
1768
1802
|
for (const threadId of new Set([bot.threadId, ...(bot.tasks ?? []).map((t) => t.threadId)])) {
|
|
1769
1803
|
this.deleteThreadRecord(threadId);
|
|
@@ -1786,6 +1820,8 @@ export class Store {
|
|
|
1786
1820
|
// The bot folder (SOUL.md mirror) is the bot's too.
|
|
1787
1821
|
removeBotFolder(id);
|
|
1788
1822
|
this.emit({ type: "bot.deleted", botId: id });
|
|
1823
|
+
for (const g of rooms)
|
|
1824
|
+
this.emit({ type: "group", groupId: g.id });
|
|
1789
1825
|
return true;
|
|
1790
1826
|
}
|
|
1791
1827
|
patchBot(id, patch) {
|
|
@@ -2435,6 +2471,24 @@ export class Store {
|
|
|
2435
2471
|
this.emit({ type: "bot", botId });
|
|
2436
2472
|
return bot;
|
|
2437
2473
|
}
|
|
2474
|
+
/** Copy this bot's saved approval level and standing approvals onto one
|
|
2475
|
+
* thread. A new thread already gets them; this is how an existing
|
|
2476
|
+
* conversation catches up. Other threads, the transcript, and the bot
|
|
2477
|
+
* default stay put. A grant that has not committed yet is ignored, so a
|
|
2478
|
+
* refresh cannot copy the temporary Ask mask. */
|
|
2479
|
+
refreshTaskPermissions(botId, threadId) {
|
|
2480
|
+
const bot = this.bot(botId);
|
|
2481
|
+
const task = this.taskByThread(botId, threadId);
|
|
2482
|
+
if (!bot || !task)
|
|
2483
|
+
return null;
|
|
2484
|
+
const mode = approvalModeFor({ ...bot, approvalGrant: undefined });
|
|
2485
|
+
const alwaysAllow = structuredClone(bot.alwaysAllow ?? []);
|
|
2486
|
+
const autoApprove = mode === "auto";
|
|
2487
|
+
const sameAllow = JSON.stringify(task.alwaysAllow ?? []) === JSON.stringify(alwaysAllow);
|
|
2488
|
+
if (task.approvalMode === mode && task.autoApprove === autoApprove && sameAllow)
|
|
2489
|
+
return task;
|
|
2490
|
+
return this.patchTask(botId, threadId, { approvalMode: mode, autoApprove, alwaysAllow });
|
|
2491
|
+
}
|
|
2438
2492
|
mirrorActiveTask(bot, task) {
|
|
2439
2493
|
bot.threadId = task.threadId;
|
|
2440
2494
|
bot.resumeCursors = structuredClone(task.resumeCursors);
|
|
@@ -817,7 +817,7 @@ export function vpsContainerRunArgs(containerName, imageRef = VPS_IMAGE, viewerS
|
|
|
817
817
|
// someone opens the panel. unless-stopped survives reboots while still
|
|
818
818
|
// honoring an explicit Stop. The shared hardening check accepts exactly
|
|
819
819
|
// this policy for the VPS caller (and only "no"/unset for the Local VM,
|
|
820
|
-
// whose
|
|
820
|
+
// whose starts are controlled by OMB's turn lifecycle).
|
|
821
821
|
"--restart",
|
|
822
822
|
"unless-stopped",
|
|
823
823
|
"-e",
|
|
@@ -37,6 +37,7 @@ const EXCLUSION_NOTES = [
|
|
|
37
37
|
"Device pairing, server identity, live leases and runtime files (existing destination identities are preserved).",
|
|
38
38
|
"Saved credentials, provider and MCP connections, managed provider login homes and browser login profiles are not transferred. Destination connections are preserved; reconnect on a new device.",
|
|
39
39
|
"Downloaded tools and caches; these can be installed again.",
|
|
40
|
+
"Installed dependency folders (node_modules) and symbolic links inside conversation work folders; reinstall or recreate them in the project.",
|
|
40
41
|
"External project folders, CLI login homes, browser session homes, OS keychains, companion devices and other servers.",
|
|
41
42
|
"VM/container disk layers and remote cloud data; durable files inside this workspace are included.",
|
|
42
43
|
];
|
|
@@ -327,6 +328,8 @@ export async function createWorkspaceBackupSnapshot(dataDir, options, jobId) {
|
|
|
327
328
|
const warnings = ["Stop external editors and managed desktops before exporting; files written outside this server cannot be frozen by the backup gate.", "Conversation text and user files are not redacted and may contain secrets you pasted. Keep the encrypted backup private.", RESTORE_WARNING];
|
|
328
329
|
let bytes = 0;
|
|
329
330
|
let skippedLinks = 0;
|
|
331
|
+
let skippedDependencies = 0;
|
|
332
|
+
let skippedWorkLinks = 0;
|
|
330
333
|
try {
|
|
331
334
|
const root = realpathSync(dataDir);
|
|
332
335
|
const sourceDb = join(root, "messages.db");
|
|
@@ -370,6 +373,21 @@ export async function createWorkspaceBackupSnapshot(dataDir, options, jobId) {
|
|
|
370
373
|
throw new Error("A workspace filename cannot be safely restored on supported platforms.");
|
|
371
374
|
const source = join(directory, name);
|
|
372
375
|
const stat = lstatSync(source);
|
|
376
|
+
// Installed dependencies come back from the project's lockfile, can
|
|
377
|
+
// hold more files than this format allows, and are often a link an
|
|
378
|
+
// agent made to another checkout's install. Leave them out.
|
|
379
|
+
if (name === "node_modules" && (stat.isDirectory() || stat.isSymbolicLink())) {
|
|
380
|
+
skippedDependencies++;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
// A conversation's work folder (task-workspaces/<bot>/<thread>) holds
|
|
384
|
+
// what an agent made there, and agents link things in (another
|
|
385
|
+
// checkout, a virtualenv). A link's target is not this workspace's
|
|
386
|
+
// data, so the link is left out rather than failing the backup.
|
|
387
|
+
if (stat.isSymbolicLink() && path.startsWith("task-workspaces/")) {
|
|
388
|
+
skippedWorkLinks++;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
373
391
|
if (stat.isSymbolicLink()) {
|
|
374
392
|
let target;
|
|
375
393
|
try {
|
|
@@ -430,6 +448,10 @@ export async function createWorkspaceBackupSnapshot(dataDir, options, jobId) {
|
|
|
430
448
|
walk(root);
|
|
431
449
|
if (skippedLinks)
|
|
432
450
|
warnings.push(`${skippedLinks} managed skill discovery link(s) were omitted and are recreated by the app.`);
|
|
451
|
+
if (skippedDependencies)
|
|
452
|
+
warnings.push(`${skippedDependencies} installed dependency folder(s) (node_modules) were omitted; reinstall them in the project after restoring.`);
|
|
453
|
+
if (skippedWorkLinks)
|
|
454
|
+
warnings.push(`${skippedWorkLinks} symbolic link(s) in conversation work folders were omitted; what they point to is outside this backup.`);
|
|
433
455
|
const summary = {
|
|
434
456
|
format: "openmaus.workspace-backup", version: 1, id: job.id, createdAt: new Date().toISOString(),
|
|
435
457
|
appVersion: options.appVersion ?? "unknown", files: entries.filter((entry) => entry.type === "file").length,
|
|
@@ -20,7 +20,7 @@ export const WORKSPACES_DIR = join(DATA_DIR, "workspaces");
|
|
|
20
20
|
export const TASK_WORKSPACES_DIR = join(DATA_DIR, "task-workspaces");
|
|
21
21
|
/** MCP support does not imply native filesystem tools or a local working directory. */
|
|
22
22
|
export function supportsWorkspaceFiles(driverKind) {
|
|
23
|
-
return !["grok", "openai-compat", "minimax", "mistral", "boxAgent"].includes(driverKind);
|
|
23
|
+
return !["grok", "openai-compat", "minimax", "mistral", "cerebras", "boxAgent"].includes(driverKind);
|
|
24
24
|
}
|
|
25
25
|
/** Default task files are private to the thread, outside the bot's shared
|
|
26
26
|
* memory folder. This is directory organization, not a shell sandbox. */
|
|
@@ -21,7 +21,7 @@ export function supportsApprovalMode(driverKind, mode) {
|
|
|
21
21
|
// engines could never stop asking — not by its own level, and not through
|
|
22
22
|
// a Chief's delegated Full access either.
|
|
23
23
|
return ["codex", "claudeAgent", "antigravityAgent", "cursorAgent", "grokAgent", "opencodeGo", "qwenAgent", "geminiAgent",
|
|
24
|
-
"openai-compat", "grok", "minimax", "mistral"].includes(driverKind ?? "");
|
|
24
|
+
"openai-compat", "grok", "minimax", "mistral", "cerebras"].includes(driverKind ?? "");
|
|
25
25
|
}
|
|
26
26
|
/** A Full/Custom grant belongs to one provider's tool semantics. Other
|
|
27
27
|
* modes carry across only when the destination actually implements them.
|
|
@@ -21,6 +21,7 @@ const KEY_PREFIXES = [
|
|
|
21
21
|
/\bsk-(?:ant-|proj-|live-|test-)?[A-Za-z0-9_-]{16,}/g, // anthropic / openai / stripe
|
|
22
22
|
/\bxai-[A-Za-z0-9_-]{20,}/g, // xai (grok)
|
|
23
23
|
/\bgsk_[A-Za-z0-9]{40,}/g, // groq
|
|
24
|
+
/\bcsk-[A-Za-z0-9]{32,}/g, // cerebras
|
|
24
25
|
/\bhf_[A-Za-z0-9]{30,}/g, // hugging face
|
|
25
26
|
/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}/g, // github classic
|
|
26
27
|
/\bgithub_pat_[A-Za-z0-9_]{20,}/g, // github fine-grained
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// A tool chip's display title is not a tool identity. Codex titles a
|
|
2
|
+
// commandExecution chip with the whole command line (drivers/codex.ts) and
|
|
3
|
+
// the ACP core prefers `rawInput.command` over the call's own name
|
|
4
|
+
// (drivers/acp/core.ts), because in a transcript the command IS the useful
|
|
5
|
+
// label. Anything that counts or groups tool calls needs the identity
|
|
6
|
+
// instead, so both readers share one predicate rather than guessing twice.
|
|
7
|
+
/** The generic identity every command-titled chip counts under. */
|
|
8
|
+
export const SHELL_TOOL = "shell";
|
|
9
|
+
/** A tool name that is itself a command line rather than a bare tool name
|
|
10
|
+
* such as `Bash`. Whitespace or a slash: no provider names a tool that way,
|
|
11
|
+
* and every shell command has one by the time it carries an argument or a
|
|
12
|
+
* path. */
|
|
13
|
+
export function nameIsCommand(name) {
|
|
14
|
+
return /[\s/]/.test(name);
|
|
15
|
+
}
|
|
16
|
+
/** What a tool chip should be counted as. Command-titled chips collapse to
|
|
17
|
+
* one `shell` bucket: keyed on the raw command they never aggregate (a turn
|
|
18
|
+
* that ran one tool twelve times reads as twelve tools), and the raw title
|
|
19
|
+
* is unredacted — unlike `tool.summary`, which drivers bound and redact. */
|
|
20
|
+
export function toolIdentity(name) {
|
|
21
|
+
return nameIsCommand(name) ? SHELL_TOOL : name;
|
|
22
|
+
}
|
|
@@ -136,6 +136,8 @@ function windowsKnownDirs() {
|
|
|
136
136
|
// x.ai installer
|
|
137
137
|
join(localAppData, "agy", "bin"),
|
|
138
138
|
// Antigravity installer
|
|
139
|
+
// Cursor installer: cursor-agent.* and its `agent` copies (MOCA-272)
|
|
140
|
+
join(localAppData, "cursor-agent"),
|
|
139
141
|
join(home, ".local", "bin"),
|
|
140
142
|
// claude native installer
|
|
141
143
|
join(home, ".claude", "local"),
|
|
@@ -143,7 +145,9 @@ function windowsKnownDirs() {
|
|
|
143
145
|
// Factory droid installer (%USERPROFILE%\bin)
|
|
144
146
|
join(home, ".bun", "bin"),
|
|
145
147
|
join(home, ".deno", "bin"),
|
|
146
|
-
join(home, "go", "bin")
|
|
148
|
+
join(home, "go", "bin"),
|
|
149
|
+
join(process.env.ProgramFiles ?? "C:\\Program Files", "Docker", "Docker", "resources", "bin")
|
|
150
|
+
// Docker Desktop installer
|
|
147
151
|
];
|
|
148
152
|
}
|
|
149
153
|
var cached = null;
|
|
@@ -16515,6 +16519,7 @@ var appConfigSchema = external_exports.object({
|
|
|
16515
16519
|
phone: external_exports.enum(["ios", "android"]).optional()
|
|
16516
16520
|
}).optional(),
|
|
16517
16521
|
mistral: external_exports.object({ key: optionalText }).optional(),
|
|
16522
|
+
cerebras: external_exports.object({ key: optionalText }).optional(),
|
|
16518
16523
|
xai: external_exports.object({ key: optionalText, url: optionalText }).optional(),
|
|
16519
16524
|
/** Anthropic API key for Claude Code billed per token, handed only to
|
|
16520
16525
|
* Claude instances; `url` only for a proxy or a test double. Never a
|
|
@@ -19078,6 +19078,7 @@ var appConfigSchema = external_exports.object({
|
|
|
19078
19078
|
phone: external_exports.enum(["ios", "android"]).optional()
|
|
19079
19079
|
}).optional(),
|
|
19080
19080
|
mistral: external_exports.object({ key: optionalText }).optional(),
|
|
19081
|
+
cerebras: external_exports.object({ key: optionalText }).optional(),
|
|
19081
19082
|
xai: external_exports.object({ key: optionalText, url: optionalText }).optional(),
|
|
19082
19083
|
/** Anthropic API key for Claude Code billed per token, handed only to
|
|
19083
19084
|
* Claude instances; `url` only for a proxy or a test double. Never a
|
|
@@ -19321,6 +19322,7 @@ var EXCLUSION_NOTES = [
|
|
|
19321
19322
|
"Device pairing, server identity, live leases and runtime files (existing destination identities are preserved).",
|
|
19322
19323
|
"Saved credentials, provider and MCP connections, managed provider login homes and browser login profiles are not transferred. Destination connections are preserved; reconnect on a new device.",
|
|
19323
19324
|
"Downloaded tools and caches; these can be installed again.",
|
|
19325
|
+
"Installed dependency folders (node_modules) and symbolic links inside conversation work folders; reinstall or recreate them in the project.",
|
|
19324
19326
|
"External project folders, CLI login homes, browser session homes, OS keychains, companion devices and other servers.",
|
|
19325
19327
|
"VM/container disk layers and remote cloud data; durable files inside this workspace are included."
|
|
19326
19328
|
];
|
|
@@ -19515,6 +19517,8 @@ async function createWorkspaceBackupSnapshot(dataDir2, options2, jobId2) {
|
|
|
19515
19517
|
const warnings = ["Stop external editors and managed desktops before exporting; files written outside this server cannot be frozen by the backup gate.", "Conversation text and user files are not redacted and may contain secrets you pasted. Keep the encrypted backup private.", RESTORE_WARNING];
|
|
19516
19518
|
let bytes = 0;
|
|
19517
19519
|
let skippedLinks = 0;
|
|
19520
|
+
let skippedDependencies = 0;
|
|
19521
|
+
let skippedWorkLinks = 0;
|
|
19518
19522
|
try {
|
|
19519
19523
|
const root = realpathSync(dataDir2);
|
|
19520
19524
|
const sourceDb = join3(root, "messages.db");
|
|
@@ -19545,6 +19549,14 @@ async function createWorkspaceBackupSnapshot(dataDir2, options2, jobId2) {
|
|
|
19545
19549
|
if (!validRelative(path2)) throw new Error("A workspace filename cannot be safely restored on supported platforms.");
|
|
19546
19550
|
const source = join3(directory, name);
|
|
19547
19551
|
const stat = lstatSync(source);
|
|
19552
|
+
if (name === "node_modules" && (stat.isDirectory() || stat.isSymbolicLink())) {
|
|
19553
|
+
skippedDependencies++;
|
|
19554
|
+
continue;
|
|
19555
|
+
}
|
|
19556
|
+
if (stat.isSymbolicLink() && path2.startsWith("task-workspaces/")) {
|
|
19557
|
+
skippedWorkLinks++;
|
|
19558
|
+
continue;
|
|
19559
|
+
}
|
|
19548
19560
|
if (stat.isSymbolicLink()) {
|
|
19549
19561
|
let target;
|
|
19550
19562
|
try {
|
|
@@ -19590,6 +19602,8 @@ async function createWorkspaceBackupSnapshot(dataDir2, options2, jobId2) {
|
|
|
19590
19602
|
};
|
|
19591
19603
|
walk(root);
|
|
19592
19604
|
if (skippedLinks) warnings.push(`${skippedLinks} managed skill discovery link(s) were omitted and are recreated by the app.`);
|
|
19605
|
+
if (skippedDependencies) warnings.push(`${skippedDependencies} installed dependency folder(s) (node_modules) were omitted; reinstall them in the project after restoring.`);
|
|
19606
|
+
if (skippedWorkLinks) warnings.push(`${skippedWorkLinks} symbolic link(s) in conversation work folders were omitted; what they point to is outside this backup.`);
|
|
19593
19607
|
const summary = {
|
|
19594
19608
|
format: "openmaus.workspace-backup",
|
|
19595
19609
|
version: 1,
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{U as a,C as n}from"./mermaid.core-CF1zwoQ-.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
|