openmausbot 0.1.57 → 0.1.59
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/index-Cvk0KgFW.js +264 -0
- package/dist/assets/{index-jWqt5dmY.js → index-D914UwuG.js} +1 -1
- package/dist/assets/index-oPMv6a_k.css +1 -0
- package/dist/index.html +2 -2
- package/dist-server/container-mcp.js +1 -2
- package/dist-server/index.js +1089 -825
- package/dist-server/openmausbot.js +15219 -305
- package/dist-server/pair-cli.js +15219 -305
- package/dist-server/proxy-paths.js +0 -1
- package/dist-server/server/auto-approve.js +18 -4
- package/dist-server/server/browser-engine-release.js +44 -0
- package/dist-server/server/browser-engine.js +216 -0
- package/dist-server/server/cli.js +55 -1
- package/dist-server/server/composio.js +43 -21
- package/dist-server/server/container-computer.js +32 -7
- package/dist-server/server/drivers/antigravity-acp.js +61 -11
- package/dist-server/server/drivers/antigravity-runtime.js +2 -2
- package/dist-server/server/drivers/antigravity.js +23 -2
- package/dist-server/server/drivers/codex-instructions.js +66 -0
- package/dist-server/server/drivers/codex.js +84 -49
- package/dist-server/server/index.js +198 -183
- package/dist-server/server/procs.js +34 -22
- package/dist-server/server/proxy-paths.js +0 -1
- package/dist-server/vps-container-mcp.js +1 -2
- package/package.json +1 -1
- package/dist/assets/index-B433sv8K.css +0 -1
- package/dist/assets/index-DSh8AIUy.js +0 -264
- package/dist-server/drivers/browser-proxy.js +0 -15037
- package/dist-server/server/browser-connection.js +0 -187
- package/dist-server/server/drivers/browser-proxy.js +0 -520
|
@@ -84,7 +84,8 @@ import { redactSecretsInText } from "./redact.js";
|
|
|
84
84
|
import * as vps from "./vps-computer.js";
|
|
85
85
|
import { RoutineManager } from "./routines.js";
|
|
86
86
|
import { CalendarCallManager } from "./calendar-calls.js";
|
|
87
|
-
import { BUILT_IN_BROWSER_SYSTEM_PROMPT
|
|
87
|
+
import { BUILT_IN_BROWSER_SYSTEM_PROMPT } from "./browser-engine.js";
|
|
88
|
+
import { agentBrowserIntegration, browserEngineEncryptionKey, clearBrowserSessionState, ensureChrome, installAgentBrowserBinary, resolveAgentBrowserBinary, browserEngineStatus, browserSessionId, describeBrowserEngine, } from "./browser-engine.js";
|
|
88
89
|
import { captureOutsideHumanControl } from "./private-screen-capture.js";
|
|
89
90
|
import { screenFrameHash, screenTouchingTool, settledFrameIsNews } from "./screen-frame-gate.js";
|
|
90
91
|
import { RoutineRequestService } from "./routine-requests.js";
|
|
@@ -202,9 +203,40 @@ function applyDesktopMutationTokenMessage(raw) {
|
|
|
202
203
|
}
|
|
203
204
|
return true;
|
|
204
205
|
}
|
|
206
|
+
// Browser data of a deleted bot or profile: the engine's saved session
|
|
207
|
+
// state, cleared here on every host (the desktop no longer owns a browser).
|
|
208
|
+
// The coordinator keeps its durable journal and replay; this is its outbox.
|
|
205
209
|
const browserCleanup = new BrowserCleanupCoordinator({
|
|
206
210
|
file: join(DATA_DIR, "browser-cleanups.json"),
|
|
207
|
-
send:
|
|
211
|
+
send: (request) => {
|
|
212
|
+
const status = browserEngineStatus();
|
|
213
|
+
// Guest sessions are throwaway and never saved, so only the bot's own
|
|
214
|
+
// session and shared profile sessions have state to clear.
|
|
215
|
+
const sessions = request.type === "openmausbot:browser-bot-deleted" && request.botId
|
|
216
|
+
? [browserSessionId(request.botId, "")]
|
|
217
|
+
: request.partitionId
|
|
218
|
+
? [browserSessionId("", request.partitionId)]
|
|
219
|
+
: [];
|
|
220
|
+
// Failed erasure leaves the committed intent pending for retry; it must
|
|
221
|
+
// never acknowledge that saved state was removed when it was not.
|
|
222
|
+
const work = status.kind === "ready" && sessions.length
|
|
223
|
+
? Promise.all(sessions.map(async (session) => {
|
|
224
|
+
const ok = await clearBrowserSessionState(status.binaryPath, session, { encryptionKey: browserEngineEncryptionKey() });
|
|
225
|
+
if (!ok)
|
|
226
|
+
console.warn(`browser cleanup: could not clear the engine's saved state for session ${session}; clear it with \`agent-browser --session ${session} state clear --all\``);
|
|
227
|
+
return ok;
|
|
228
|
+
}))
|
|
229
|
+
: Promise.resolve([true]);
|
|
230
|
+
void work.then((results) => results.every(Boolean), (error) => {
|
|
231
|
+
console.warn("browser cleanup: could not clear saved session state", error);
|
|
232
|
+
return false;
|
|
233
|
+
}).then((ok) => {
|
|
234
|
+
browserCleanup.receive({ type: "openmausbot:browser-lifecycle-result", requestId: request.requestId, ok });
|
|
235
|
+
}).catch((error) => {
|
|
236
|
+
console.warn("browser cleanup: could not acknowledge cleanup", error);
|
|
237
|
+
});
|
|
238
|
+
return true;
|
|
239
|
+
},
|
|
208
240
|
});
|
|
209
241
|
const phoneSecrets = new PhoneSecretBridge(postDesktopPrivateMessage);
|
|
210
242
|
utilityParentPort?.on("message", (event) => {
|
|
@@ -218,8 +250,7 @@ utilityParentPort?.on("message", (event) => {
|
|
|
218
250
|
return;
|
|
219
251
|
if (phoneSecrets.receive(message))
|
|
220
252
|
return;
|
|
221
|
-
|
|
222
|
-
composio.applyManagedBrokerMessage(message);
|
|
253
|
+
composio.applyManagedBrokerMessage(message);
|
|
223
254
|
}
|
|
224
255
|
catch (error) {
|
|
225
256
|
console.error(`[desktop-sync] rejected private parent message: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -299,10 +330,8 @@ function revokeInternalCapabilityForProviderEvent(event) {
|
|
|
299
330
|
* length; only capabilities for currently active turns are retained. */
|
|
300
331
|
function authorizedInternalCapability(header) {
|
|
301
332
|
const got = Buffer.from(Array.isArray(header) ? "" : (header ?? ""));
|
|
302
|
-
const now = Date.now();
|
|
303
333
|
for (const [token, capability] of internalCapabilities) {
|
|
304
|
-
if (capability
|
|
305
|
-
activeInternalGenerationByThread.get(capability.threadId) !== capability.generation) {
|
|
334
|
+
if (!internalCapabilityIsActive(capability)) {
|
|
306
335
|
internalCapabilities.delete(token);
|
|
307
336
|
continue;
|
|
308
337
|
}
|
|
@@ -313,6 +342,12 @@ function authorizedInternalCapability(header) {
|
|
|
313
342
|
return null;
|
|
314
343
|
}
|
|
315
344
|
function internalCapabilityIsActive(capability) {
|
|
345
|
+
if (capability.localVmTarget) {
|
|
346
|
+
const owner = localVmLeaseFor(capability.localVmTarget).current(localVmOwnerBusy);
|
|
347
|
+
if (localVmThreadTargets.get(capability.threadId) !== capability.localVmTarget ||
|
|
348
|
+
owner?.threadId !== capability.threadId || owner.botId !== capability.botId)
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
316
351
|
return (capability.orphanExpiresAt > Date.now() &&
|
|
317
352
|
activeInternalGenerationByThread.get(capability.threadId) === capability.generation);
|
|
318
353
|
}
|
|
@@ -367,84 +402,6 @@ function agentsIntegration(botId, threadId, depth, skillAuthoring, generation) {
|
|
|
367
402
|
},
|
|
368
403
|
};
|
|
369
404
|
}
|
|
370
|
-
const browserCapabilitiesByThread = new Map();
|
|
371
|
-
const pendingBrowserCapabilityRevocations = new Map();
|
|
372
|
-
const BROWSER_REVOCATION_RETRY_MS = [250, 1_000, 3_000, 10_000, 30_000];
|
|
373
|
-
async function revokeReleasedBrowserCapability(active, attempt = 0) {
|
|
374
|
-
const token = active.capability.token;
|
|
375
|
-
try {
|
|
376
|
-
await revokeBrowserCapability(active.connection, active.capability);
|
|
377
|
-
const pending = pendingBrowserCapabilityRevocations.get(token);
|
|
378
|
-
if (pending)
|
|
379
|
-
clearTimeout(pending.timer);
|
|
380
|
-
pendingBrowserCapabilityRevocations.delete(token);
|
|
381
|
-
}
|
|
382
|
-
catch (error) {
|
|
383
|
-
if (Date.now() >= active.capability.expiresAt) {
|
|
384
|
-
pendingBrowserCapabilityRevocations.delete(token);
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
if (attempt === 0) {
|
|
388
|
-
console.error(`[browser] could not revoke turn capability; retrying until its absolute expiry: ${error instanceof Error ? error.message : String(error)}`);
|
|
389
|
-
}
|
|
390
|
-
const delay = Math.min(BROWSER_REVOCATION_RETRY_MS[Math.min(attempt, BROWSER_REVOCATION_RETRY_MS.length - 1)], Math.max(1, active.capability.expiresAt - Date.now()));
|
|
391
|
-
const timer = setTimeout(() => {
|
|
392
|
-
const pending = pendingBrowserCapabilityRevocations.get(token);
|
|
393
|
-
if (!pending || pending.timer !== timer)
|
|
394
|
-
return;
|
|
395
|
-
void revokeReleasedBrowserCapability(active, attempt + 1);
|
|
396
|
-
}, delay);
|
|
397
|
-
timer.unref?.();
|
|
398
|
-
const previous = pendingBrowserCapabilityRevocations.get(token);
|
|
399
|
-
if (previous)
|
|
400
|
-
clearTimeout(previous.timer);
|
|
401
|
-
pendingBrowserCapabilityRevocations.set(token, { active, attempt: attempt + 1, timer });
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
async function releaseBrowserCapabilityForThread(threadId, expectedOwnerId) {
|
|
405
|
-
const active = browserCapabilitiesByThread.get(threadId);
|
|
406
|
-
if (!active || (expectedOwnerId !== undefined && active.ownerId !== expectedOwnerId))
|
|
407
|
-
return;
|
|
408
|
-
browserCapabilitiesByThread.delete(threadId);
|
|
409
|
-
await revokeReleasedBrowserCapability(active);
|
|
410
|
-
}
|
|
411
|
-
async function releaseBrowserCapabilitiesForBot(botId) {
|
|
412
|
-
const threads = [...browserCapabilitiesByThread]
|
|
413
|
-
.filter(([, active]) => active.botId === botId)
|
|
414
|
-
.map(([threadId]) => threadId);
|
|
415
|
-
await Promise.all(threads.map((threadId) => releaseBrowserCapabilityForThread(threadId)));
|
|
416
|
-
}
|
|
417
|
-
async function releaseAllBrowserCapabilities() {
|
|
418
|
-
const active = [...browserCapabilitiesByThread.values()];
|
|
419
|
-
browserCapabilitiesByThread.clear();
|
|
420
|
-
const connections = new Map();
|
|
421
|
-
for (const entry of active) {
|
|
422
|
-
connections.set(`${entry.connection.url}:${entry.connection.token}`, entry.connection);
|
|
423
|
-
}
|
|
424
|
-
for (const pending of pendingBrowserCapabilityRevocations.values()) {
|
|
425
|
-
connections.set(`${pending.active.connection.url}:${pending.active.connection.token}`, pending.active.connection);
|
|
426
|
-
}
|
|
427
|
-
await Promise.all([...connections.values()].map(async (connection) => {
|
|
428
|
-
try {
|
|
429
|
-
// Master clear is atomic at the host. It also invalidates a token whose
|
|
430
|
-
// earlier per-turn revoke timed out, which is essential for feature-off
|
|
431
|
-
// and graceful-shutdown boundaries.
|
|
432
|
-
await clearBrowserCapabilities(connection);
|
|
433
|
-
for (const [token, pending] of pendingBrowserCapabilityRevocations) {
|
|
434
|
-
if (pending.active.connection.url === connection.url &&
|
|
435
|
-
pending.active.connection.token === connection.token) {
|
|
436
|
-
clearTimeout(pending.timer);
|
|
437
|
-
pendingBrowserCapabilityRevocations.delete(token);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
catch {
|
|
442
|
-
await Promise.all(active
|
|
443
|
-
.filter((entry) => entry.connection.url === connection.url && entry.connection.token === connection.token)
|
|
444
|
-
.map((entry) => revokeReleasedBrowserCapability(entry)));
|
|
445
|
-
}
|
|
446
|
-
}));
|
|
447
|
-
}
|
|
448
405
|
class DirectTurnSetupCancelled extends Error {
|
|
449
406
|
}
|
|
450
407
|
const directTurnDispatchClaims = new Map();
|
|
@@ -539,50 +496,42 @@ function cancelDirectTurnDispatch(botId, expectedThreadId) {
|
|
|
539
496
|
// start early enough for those old events to settle the replacement.
|
|
540
497
|
return claim;
|
|
541
498
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
499
|
+
/** The bot's browser for this turn: agent-browser, one isolated session per
|
|
500
|
+
* browser profile or per bot (docs/plans/browser-engine.md). Null, with the
|
|
501
|
+
* reason logged once, when the engine is not on this machine. */
|
|
502
|
+
function browserIntegration(botId, profile) {
|
|
503
|
+
const status = browserEngineStatus();
|
|
504
|
+
if (status.kind !== "ready") {
|
|
505
|
+
if (!engineUnavailableLogged) {
|
|
506
|
+
engineUnavailableLogged = true;
|
|
507
|
+
console.warn(`${describeBrowserEngine(status)}; bots get no browser tools until it is installed`);
|
|
508
|
+
}
|
|
545
509
|
return null;
|
|
546
|
-
|
|
510
|
+
}
|
|
547
511
|
// A profile that no longer exists falls back to the bot's own session.
|
|
548
|
-
|
|
549
|
-
// exact immutable partition inherited from #567 so an upgrade cannot move
|
|
550
|
-
// a bot into another account. Guest remains a throwaway partition.
|
|
551
|
-
const profileTarget = profile && profile !== "guest"
|
|
552
|
-
? browserProfilePartitionTarget(cfg, profile)
|
|
553
|
-
: null;
|
|
512
|
+
const profileTarget = profile && profile !== "guest" ? browserProfilePartitionTarget(cfg, profile) : null;
|
|
554
513
|
const partitionId = profile === "guest" ? "guest" : (profileTarget?.partitionId ?? "");
|
|
555
|
-
|
|
556
|
-
const capability = await registerBrowserCapability(connection, botId, partitionId);
|
|
557
|
-
const active = { botId, ownerId, connection, capability };
|
|
558
|
-
// Registration crosses a process boundary. Stop/delete/config changes can
|
|
559
|
-
// land while the desktop host is minting the token; revalidate in the same
|
|
560
|
-
// event-loop turn that publishes it. If ownership was lost, no agent ever
|
|
561
|
-
// receives the bearer and the just-created token is revoked immediately.
|
|
562
|
-
if (!stillValid()) {
|
|
563
|
-
await revokeReleasedBrowserCapability(active);
|
|
564
|
-
return null;
|
|
565
|
-
}
|
|
566
|
-
browserCapabilitiesByThread.set(threadId, active);
|
|
514
|
+
const session = browserSessionId(botId, partitionId);
|
|
567
515
|
return {
|
|
568
|
-
connection,
|
|
569
|
-
capability,
|
|
570
516
|
profile: partitionId,
|
|
571
|
-
integration: {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
OMB_BROWSER_TOKEN: capability.token,
|
|
578
|
-
OMB_BROWSER_PROFILE: partitionId,
|
|
579
|
-
OMB_BOT_ID: botId,
|
|
580
|
-
OMB_CONTROL_URL: control.url,
|
|
581
|
-
OMB_CONTROL_TOKEN: control.token,
|
|
582
|
-
},
|
|
583
|
-
},
|
|
517
|
+
integration: agentBrowserIntegration({
|
|
518
|
+
binaryPath: status.binaryPath,
|
|
519
|
+
session,
|
|
520
|
+
encryptionKey: browserEngineEncryptionKey(),
|
|
521
|
+
persistent: profile !== "guest",
|
|
522
|
+
}),
|
|
584
523
|
};
|
|
585
524
|
}
|
|
525
|
+
let engineUnavailableLogged = false;
|
|
526
|
+
let browserEngineInstall = null;
|
|
527
|
+
let browserEngineInstallError = null;
|
|
528
|
+
export function browserEngineSummary() {
|
|
529
|
+
const status = browserEngineStatus();
|
|
530
|
+
const progress = { ...(browserEngineInstall ? { installing: true } : {}), ...(browserEngineInstallError ? { installError: browserEngineInstallError } : {}) };
|
|
531
|
+
return status.kind === "ready"
|
|
532
|
+
? { kind: "engine", version: status.version, ...progress }
|
|
533
|
+
: { kind: "unavailable", reason: status.reason, installable: status.installable, ...progress };
|
|
534
|
+
}
|
|
586
535
|
function phoneIntegration() {
|
|
587
536
|
const env = { ...AGENTS_NODE_FLAG };
|
|
588
537
|
if (process.env.OMB_ADB_PATH)
|
|
@@ -642,7 +591,7 @@ const routineRequestEnvelopeSchema = z.discriminatedUnion("action", [
|
|
|
642
591
|
...["pause", "resume", "run_now", "delete"].map((action) => z.object({ ...routineRequestSourceSchema, action: z.literal(action), routineId: z.unknown() }).strict()),
|
|
643
592
|
]);
|
|
644
593
|
/** The loopback endpoint a bot's computer proxy polls before acting. */
|
|
645
|
-
function controlIntegration(botId, threadId, generation) {
|
|
594
|
+
function controlIntegration(botId, threadId, generation, localVmTarget) {
|
|
646
595
|
return {
|
|
647
596
|
url: `http://127.0.0.1:${PORT}/api/internal/computer-control?botId=${encodeURIComponent(botId)}`,
|
|
648
597
|
token: mintInternalCapability({
|
|
@@ -651,6 +600,7 @@ function controlIntegration(botId, threadId, generation) {
|
|
|
651
600
|
generation,
|
|
652
601
|
depth: 0,
|
|
653
602
|
kind: "computer",
|
|
603
|
+
...(localVmTarget ? { localVmTarget } : {}),
|
|
654
604
|
skillAuthoring: false,
|
|
655
605
|
createdBots: 0,
|
|
656
606
|
}),
|
|
@@ -1921,7 +1871,9 @@ const watchdog = new TurnWatchdog({
|
|
|
1921
1871
|
stallMs: TURN_STALL_MS,
|
|
1922
1872
|
checkMs: 60_000,
|
|
1923
1873
|
onStall: (turn) => {
|
|
1924
|
-
|
|
1874
|
+
// Room targets carry an invocation identity; only those claims belong
|
|
1875
|
+
// to the room grace cleanup added here.
|
|
1876
|
+
const stalledVmTarget = groupSpeakers.has(turn.threadId) ? localVmThreadTargets.get(turn.threadId) : undefined;
|
|
1925
1877
|
revokeInternalCapabilitiesForThread(turn.threadId);
|
|
1926
1878
|
repeats.settle(turn.threadId);
|
|
1927
1879
|
const bot = store.bot(turn.botId);
|
|
@@ -1950,6 +1902,9 @@ const watchdog = new TurnWatchdog({
|
|
|
1950
1902
|
retry.unref?.();
|
|
1951
1903
|
return;
|
|
1952
1904
|
}
|
|
1905
|
+
if (stalledVmTarget && localVmThreadTargets.get(turn.threadId) === stalledVmTarget) {
|
|
1906
|
+
releaseLocalVmThread(turn.threadId);
|
|
1907
|
+
}
|
|
1953
1908
|
const group = store.groupByThread(turn.threadId);
|
|
1954
1909
|
const speaker = groupSpeakers.get(turn.threadId);
|
|
1955
1910
|
if (group && group.busyBotId === turn.botId && speaker?.botId === turn.botId) {
|
|
@@ -2045,7 +2000,6 @@ bus.subscribe((event) => {
|
|
|
2045
2000
|
watchdog.setWaitingOnHuman(event.threadId, false);
|
|
2046
2001
|
else if (event.type === "turn.completed") {
|
|
2047
2002
|
watchdog.settle(event.threadId);
|
|
2048
|
-
void releaseBrowserCapabilityForThread(event.threadId);
|
|
2049
2003
|
revokeInternalCapabilityForProviderEvent(event);
|
|
2050
2004
|
}
|
|
2051
2005
|
else if (event.type === "session.exited") {
|
|
@@ -2055,7 +2009,6 @@ bus.subscribe((event) => {
|
|
|
2055
2009
|
const directBotBusy = store.botByThread(event.threadId)?.busy === true;
|
|
2056
2010
|
const roomBusy = Boolean(store.groupByThread(event.threadId)?.busyBotId);
|
|
2057
2011
|
if (!directBotBusy && !roomBusy) {
|
|
2058
|
-
void releaseBrowserCapabilityForThread(event.threadId);
|
|
2059
2012
|
}
|
|
2060
2013
|
}
|
|
2061
2014
|
else
|
|
@@ -3911,13 +3864,7 @@ async function startTurn(botId, text, opts) {
|
|
|
3911
3864
|
liveBot.browser !== false &&
|
|
3912
3865
|
instance.adapter.capabilities.browserMcp === true) {
|
|
3913
3866
|
const selectedProfile = liveBot.browserProfile;
|
|
3914
|
-
browser =
|
|
3915
|
-
const current = store.bot(bot.id);
|
|
3916
|
-
return (directTurnClaimIsCurrent(bot.id, dispatchClaimId, threadId) &&
|
|
3917
|
-
builtInBrowserEnabled(cfg) &&
|
|
3918
|
-
current?.browser !== false &&
|
|
3919
|
-
current?.browserProfile === selectedProfile);
|
|
3920
|
-
}, dispatchClaimId);
|
|
3867
|
+
browser = browserIntegration(bot.id, selectedProfile);
|
|
3921
3868
|
if (browser)
|
|
3922
3869
|
integrations.browser = browser.integration;
|
|
3923
3870
|
}
|
|
@@ -4002,10 +3949,6 @@ async function startTurn(botId, text, opts) {
|
|
|
4002
3949
|
// after its own turn.completed would never be torn down — it would
|
|
4003
3950
|
// keep polling the box forever, carrying dead per-turn state. busy
|
|
4004
3951
|
// is flipped false in the fold, so it is the honest "still running".
|
|
4005
|
-
if (!previewCapture && browser) {
|
|
4006
|
-
const { connection } = browser;
|
|
4007
|
-
previewCapture = () => browserScreenshot(connection, browser.capability, fetch);
|
|
4008
|
-
}
|
|
4009
3952
|
if (previewCapture && store.bot(bot.id)?.busy) {
|
|
4010
3953
|
startScreenPoller(bot.id, previewCapture, { screenIsTheWork: instance.driverKind === "boxAgent" });
|
|
4011
3954
|
}
|
|
@@ -4020,7 +3963,6 @@ async function startTurn(botId, text, opts) {
|
|
|
4020
3963
|
clearCancelledProviderHandshake(threadId, `direct:${dispatchClaimId}`);
|
|
4021
3964
|
clearDirectTurnDispatch(bot.id, dispatchClaimId);
|
|
4022
3965
|
revokeInternalCapabilityGeneration(threadId, dispatchClaimId);
|
|
4023
|
-
await releaseBrowserCapabilityForThread(threadId, dispatchClaimId);
|
|
4024
3966
|
const ownsLatestGeneration = directTurnGenerationByBot.get(bot.id) === dispatchClaimId;
|
|
4025
3967
|
if (ownsLatestGeneration) {
|
|
4026
3968
|
releaseLocalVmThread(threadId);
|
|
@@ -4184,7 +4126,6 @@ async function interruptRoutineGroupGoal(groupId, threadId, outcome) {
|
|
|
4184
4126
|
const bot = speaker ? store.bot(speaker.botId) : undefined;
|
|
4185
4127
|
cancelGroupTurnOperations(groupId, threadId, outcome);
|
|
4186
4128
|
revokeInternalCapabilitiesForThread(threadId);
|
|
4187
|
-
await releaseBrowserCapabilityForThread(threadId);
|
|
4188
4129
|
await (bot ? registry.get(bot.modelSelection.instanceId) : undefined)
|
|
4189
4130
|
?.adapter.interruptTurn(threadId)
|
|
4190
4131
|
.catch(() => { });
|
|
@@ -4213,7 +4154,6 @@ async function stopBotForEmergencyApprovalDowngrade(botId) {
|
|
|
4213
4154
|
revokeInternalCapabilitiesForThread(groupTurn.threadId);
|
|
4214
4155
|
cancelGroupTurnOperations(groupTurn.group.id, groupTurn.threadId);
|
|
4215
4156
|
const results = await Promise.allSettled([
|
|
4216
|
-
releaseBrowserCapabilityForThread(groupTurn.threadId),
|
|
4217
4157
|
registry.get(bot.modelSelection.instanceId)?.adapter.interruptTurn(groupTurn.threadId),
|
|
4218
4158
|
]);
|
|
4219
4159
|
closeOpenApprovals(groupTurn.threadId);
|
|
@@ -4226,7 +4166,6 @@ async function stopBotForEmergencyApprovalDowngrade(botId) {
|
|
|
4226
4166
|
const threadId = directClaim?.threadId ?? bot.threadId;
|
|
4227
4167
|
revokeInternalCapabilitiesForThread(threadId);
|
|
4228
4168
|
const results = await Promise.allSettled([
|
|
4229
|
-
releaseBrowserCapabilityForThread(threadId),
|
|
4230
4169
|
registry.get(bot.modelSelection.instanceId)?.adapter.interruptTurn(threadId),
|
|
4231
4170
|
]);
|
|
4232
4171
|
closeOpenApprovals(threadId);
|
|
@@ -4280,7 +4219,6 @@ routines = new RoutineManager({
|
|
|
4280
4219
|
? registry.get(bot.modelSelection.instanceId)
|
|
4281
4220
|
: null;
|
|
4282
4221
|
try {
|
|
4283
|
-
await releaseBrowserCapabilityForThread(threadId);
|
|
4284
4222
|
await instance?.adapter.interruptTurn(threadId);
|
|
4285
4223
|
}
|
|
4286
4224
|
finally {
|
|
@@ -4692,6 +4630,15 @@ setupRetry = 0) {
|
|
|
4692
4630
|
return true;
|
|
4693
4631
|
}
|
|
4694
4632
|
const internalGeneration = beginInternalCapabilityGeneration(threadId);
|
|
4633
|
+
let roomVmTarget = null;
|
|
4634
|
+
let retainRoomVmLease = false;
|
|
4635
|
+
let roomSpeaker;
|
|
4636
|
+
let providerDispatched = false;
|
|
4637
|
+
const releaseRoomVmLease = () => {
|
|
4638
|
+
if (roomVmTarget && localVmThreadTargets.get(threadId) === roomVmTarget)
|
|
4639
|
+
releaseLocalVmThread(threadId);
|
|
4640
|
+
roomVmTarget = null;
|
|
4641
|
+
};
|
|
4695
4642
|
try {
|
|
4696
4643
|
const integrations = {};
|
|
4697
4644
|
const skillAuthoring = skillRecorderEnabled(cfg) &&
|
|
@@ -4841,19 +4788,7 @@ setupRetry = 0) {
|
|
|
4841
4788
|
readyBot.browser !== false &&
|
|
4842
4789
|
instance.adapter.capabilities.browserMcp === true) {
|
|
4843
4790
|
const selectedProfile = readyBot.browserProfile;
|
|
4844
|
-
const browser =
|
|
4845
|
-
const currentBot = store.bot(readyBot.id);
|
|
4846
|
-
const currentGroup = store.group(readyGroup.id);
|
|
4847
|
-
const stillOwnsThread = currentGroup?.dm
|
|
4848
|
-
? currentGroup.threadId === threadId
|
|
4849
|
-
: Boolean(currentGroup && store.groupTaskByThread(currentGroup.id, threadId));
|
|
4850
|
-
return (!isCancelled?.() &&
|
|
4851
|
-
stillOwnsThread &&
|
|
4852
|
-
currentBot?.busy === true &&
|
|
4853
|
-
builtInBrowserEnabled(cfg) &&
|
|
4854
|
-
currentBot.browser !== false &&
|
|
4855
|
-
currentBot.browserProfile === selectedProfile);
|
|
4856
|
-
});
|
|
4791
|
+
const browser = browserIntegration(readyBot.id, selectedProfile);
|
|
4857
4792
|
if (browser)
|
|
4858
4793
|
integrations.browser = browser.integration;
|
|
4859
4794
|
}
|
|
@@ -4862,7 +4797,6 @@ setupRetry = 0) {
|
|
|
4862
4797
|
// room's setup claim so no provider turn starts after Stop returned.
|
|
4863
4798
|
const browserReadyBot = store.bot(readyBot.id);
|
|
4864
4799
|
if (isCancelled?.() || !browserReadyBot || !browserReadyBot.busy) {
|
|
4865
|
-
await releaseBrowserCapabilityForThread(threadId);
|
|
4866
4800
|
if (browserReadyBot?.busy) {
|
|
4867
4801
|
store.setActivity(browserReadyBot.id, "idle");
|
|
4868
4802
|
retryDelegationsWaitingOn(browserReadyBot.id);
|
|
@@ -4870,7 +4804,44 @@ setupRetry = 0) {
|
|
|
4870
4804
|
return false;
|
|
4871
4805
|
}
|
|
4872
4806
|
store.patchGroup(readyGroup.id, { busyBotId: bot.id }); // the store's change stream carries the frame
|
|
4873
|
-
|
|
4807
|
+
roomSpeaker = { botId: bot.id, name: bot.name, color: bot.color };
|
|
4808
|
+
groupSpeakers.set(threadId, roomSpeaker);
|
|
4809
|
+
// Room and Goal turns use the speaker's desktop, never the coordinator's.
|
|
4810
|
+
// Claim the same lease as direct turns before asynchronous VM setup.
|
|
4811
|
+
if (readyBot.computer === "vm") {
|
|
4812
|
+
if (instance.adapter.capabilities.computerMcp !== true || instance.driverKind === "boxAgent") {
|
|
4813
|
+
throw new Error("this model engine cannot use the Local VM");
|
|
4814
|
+
}
|
|
4815
|
+
// A distinct identity fences cleanup even in shared mode on the same room thread.
|
|
4816
|
+
const target = { ...localVmTargetForBot(readyBot.id) };
|
|
4817
|
+
if (localVmImageBusy || localVmModeChangeBusy || localVmLifecycleBusy.has(target.key)) {
|
|
4818
|
+
throw new Error("this Local VM is being started, stopped, or replaced");
|
|
4819
|
+
}
|
|
4820
|
+
if (!localVmLeaseFor(target).claim(threadId, readyBot.id, localVmOwnerBusy)) {
|
|
4821
|
+
throw new Error("this Local VM is already being used by another turn");
|
|
4822
|
+
}
|
|
4823
|
+
roomVmTarget = target;
|
|
4824
|
+
localVmThreadTargets.set(threadId, target);
|
|
4825
|
+
localVmActiveThreads.set(target.key, threadId);
|
|
4826
|
+
localVmIdleFor(target).touch();
|
|
4827
|
+
const setupIsCurrent = () => !isCancelled?.() &&
|
|
4828
|
+
groupSpeakers.get(threadId) === roomSpeaker &&
|
|
4829
|
+
activeInternalGenerationByThread.get(threadId) === internalGeneration &&
|
|
4830
|
+
store.group(readyGroup.id)?.memberIds.includes(readyBot.id) === true &&
|
|
4831
|
+
store.bot(readyBot.id)?.busy === true &&
|
|
4832
|
+
store.group(readyGroup.id)?.busyBotId === readyBot.id;
|
|
4833
|
+
const vm = await readyLocalVmForTurn(readyBot.id, target, setupIsCurrent);
|
|
4834
|
+
if (!setupIsCurrent()) {
|
|
4835
|
+
return false;
|
|
4836
|
+
}
|
|
4837
|
+
if (!vm.ready || !vm.runtime)
|
|
4838
|
+
throw new Error(vm.problem ?? "the Local VM is not ready");
|
|
4839
|
+
const owner = localVmLeaseFor(target).current(localVmOwnerBusy);
|
|
4840
|
+
if (owner?.threadId !== threadId || owner.botId !== readyBot.id) {
|
|
4841
|
+
throw new Error("the Local VM lease expired while preparing the turn");
|
|
4842
|
+
}
|
|
4843
|
+
integrations.localComputer = containerComputerMcp(vm.runtime, controlIntegration(readyBot.id, threadId, internalGeneration, target), target);
|
|
4844
|
+
}
|
|
4874
4845
|
const roster = readyGroup.memberIds
|
|
4875
4846
|
.map((id) => store.bot(id))
|
|
4876
4847
|
.filter((b) => Boolean(b))
|
|
@@ -4921,6 +4892,7 @@ setupRetry = 0) {
|
|
|
4921
4892
|
store.patchBot(bot.id, { soulDrift: drift.drift });
|
|
4922
4893
|
}
|
|
4923
4894
|
const roomSystem = buildSystemPrompt(system, store.bot(bot.id)?.soul ?? bot.soul ?? "", [
|
|
4895
|
+
{ id: "computer", label: "Computer", text: computerPrompt(roomVmTarget ? localVmMode(cfg) === "per-bot" ? "vm-private" : "vm-shared" : null) },
|
|
4924
4896
|
{ id: "browser", label: "Browser", text: integrations.browser ? BUILT_IN_BROWSER_SYSTEM_PROMPT : "" },
|
|
4925
4897
|
{ id: "recall", label: "Recall", text: integrations.agents ? SESSION_SEARCH_SYSTEM_PROMPT : "" },
|
|
4926
4898
|
{ id: "section-context", label: "Section context", text: sectionContextSystemPrompt(bot.section) },
|
|
@@ -4946,7 +4918,6 @@ setupRetry = 0) {
|
|
|
4946
4918
|
if (isCancelled?.() ||
|
|
4947
4919
|
store.group(group.id)?.busyBotId !== bot.id ||
|
|
4948
4920
|
store.bot(bot.id)?.busy !== true) {
|
|
4949
|
-
await releaseBrowserCapabilityForThread(threadId);
|
|
4950
4921
|
if (store.group(group.id)?.busyBotId === bot.id) {
|
|
4951
4922
|
groupSpeakers.delete(threadId);
|
|
4952
4923
|
store.patchGroup(group.id, { busyBotId: null, unread: true });
|
|
@@ -4978,7 +4949,6 @@ setupRetry = 0) {
|
|
|
4978
4949
|
let unregisterStall = () => { };
|
|
4979
4950
|
const deadline = new RoomTurnDeadline(timeoutMinutes, () => {
|
|
4980
4951
|
abandonProviderTurn();
|
|
4981
|
-
void releaseBrowserCapabilityForThread(threadId);
|
|
4982
4952
|
void instance.adapter.interruptTurn(threadId).catch(() => { });
|
|
4983
4953
|
store.appendMessage(threadId, {
|
|
4984
4954
|
role: "bot",
|
|
@@ -5030,6 +5000,7 @@ setupRetry = 0) {
|
|
|
5030
5000
|
});
|
|
5031
5001
|
watchdog.watch(threadId, bot.id);
|
|
5032
5002
|
onProviderHandshakeStarted?.();
|
|
5003
|
+
providerDispatched = true;
|
|
5033
5004
|
guardTurnDispatch(instance.adapter.sendTurn({
|
|
5034
5005
|
threadId,
|
|
5035
5006
|
text,
|
|
@@ -5043,7 +5014,6 @@ setupRetry = 0) {
|
|
|
5043
5014
|
// Stop may have landed while the adapter was authenticating, before
|
|
5044
5015
|
// it had an active process for the first interrupt to reach. Now that
|
|
5045
5016
|
// sendTurn completed setup, revoke again and interrupt the real turn.
|
|
5046
|
-
await releaseBrowserCapabilityForThread(threadId);
|
|
5047
5017
|
await instance.adapter.interruptTurn(threadId).catch(() => { });
|
|
5048
5018
|
})
|
|
5049
5019
|
.then((dispatch) => {
|
|
@@ -5083,6 +5053,9 @@ setupRetry = 0) {
|
|
|
5083
5053
|
// work so a retained proxy from this member cannot act during the next
|
|
5084
5054
|
// member's generation.
|
|
5085
5055
|
revokeInternalCapabilityGeneration(threadId, internalGeneration);
|
|
5056
|
+
retainRoomVmLease = outcome === "timed_out" || outcome === "stalled";
|
|
5057
|
+
if (!retainRoomVmLease)
|
|
5058
|
+
releaseRoomVmLease();
|
|
5086
5059
|
if (orchestration) {
|
|
5087
5060
|
orchestration.result.replyText = replyText.trim();
|
|
5088
5061
|
orchestration.result.outcome = outcome;
|
|
@@ -5122,6 +5095,7 @@ setupRetry = 0) {
|
|
|
5122
5095
|
retry.unref?.();
|
|
5123
5096
|
return;
|
|
5124
5097
|
}
|
|
5098
|
+
releaseRoomVmLease();
|
|
5125
5099
|
const currentGroup = store.group(group.id);
|
|
5126
5100
|
const speaker = groupSpeakers.get(threadId);
|
|
5127
5101
|
if (currentGroup?.busyBotId === bot.id && speaker?.botId === bot.id) {
|
|
@@ -5157,7 +5131,6 @@ setupRetry = 0) {
|
|
|
5157
5131
|
if (outcome === "dispatch_failed") {
|
|
5158
5132
|
if (skillAuthoring)
|
|
5159
5133
|
skillAuthoringClaim.claimed = false;
|
|
5160
|
-
await releaseBrowserCapabilityForThread(threadId);
|
|
5161
5134
|
// No turn.completed follows a rejected room dispatch. Anything that was
|
|
5162
5135
|
// queued while this bot briefly owned the room must be retried now.
|
|
5163
5136
|
drainQueuedSends();
|
|
@@ -5217,10 +5190,38 @@ setupRetry = 0) {
|
|
|
5217
5190
|
}
|
|
5218
5191
|
return true;
|
|
5219
5192
|
}
|
|
5193
|
+
catch (error) {
|
|
5194
|
+
if (providerDispatched || !roomSpeaker)
|
|
5195
|
+
throw error;
|
|
5196
|
+
const message = error instanceof Error ? error.message : "Local VM setup failed";
|
|
5197
|
+
store.appendMessage(threadId, {
|
|
5198
|
+
role: "bot", kind: "activity",
|
|
5199
|
+
from: { botId: bot.id, name: bot.name, color: bot.color },
|
|
5200
|
+
tool: { name: `error: ${message}`, ok: false },
|
|
5201
|
+
});
|
|
5202
|
+
onDispatchError?.(message);
|
|
5203
|
+
if (orchestration)
|
|
5204
|
+
orchestration.result.outcome = "dispatch_failed";
|
|
5205
|
+
return false;
|
|
5206
|
+
}
|
|
5220
5207
|
finally {
|
|
5221
5208
|
// Covers connector/setup failures, cancellation before dispatch, and all
|
|
5222
5209
|
// other early returns that never produce a provider terminal event.
|
|
5223
5210
|
revokeInternalCapabilityGeneration(threadId, internalGeneration);
|
|
5211
|
+
if (!retainRoomVmLease)
|
|
5212
|
+
releaseRoomVmLease();
|
|
5213
|
+
if (!providerDispatched && roomSpeaker && groupSpeakers.get(threadId) === roomSpeaker) {
|
|
5214
|
+
groupSpeakers.delete(threadId);
|
|
5215
|
+
if (store.group(group.id)?.busyBotId === bot.id)
|
|
5216
|
+
store.patchGroup(group.id, { busyBotId: null });
|
|
5217
|
+
if (store.bot(bot.id)?.busy) {
|
|
5218
|
+
store.setActivity(bot.id, "idle");
|
|
5219
|
+
retryDelegationsWaitingOn(bot.id);
|
|
5220
|
+
}
|
|
5221
|
+
drainQueuedSends();
|
|
5222
|
+
drainConnectorResumes();
|
|
5223
|
+
drainSecretResumes();
|
|
5224
|
+
}
|
|
5224
5225
|
}
|
|
5225
5226
|
}
|
|
5226
5227
|
async function runGroupGoalStep(args) {
|
|
@@ -6455,13 +6456,15 @@ async function localVmPayload(target) {
|
|
|
6455
6456
|
* instance cap; creating past it would quietly do what the lifecycle route
|
|
6456
6457
|
* refuses.
|
|
6457
6458
|
*/
|
|
6458
|
-
async function readyLocalVmForTurn(botId, target) {
|
|
6459
|
+
async function readyLocalVmForTurn(botId, target, isCurrent = () => true) {
|
|
6459
6460
|
let status = await containerComputerStatus(undefined, undefined, target);
|
|
6461
|
+
if (!isCurrent())
|
|
6462
|
+
return status;
|
|
6460
6463
|
if (status.ready || !localVmRecreatableOnDemand(status))
|
|
6461
6464
|
return status;
|
|
6462
6465
|
if (target.key !== SHARED_LOCAL_VM_TARGET.key) {
|
|
6463
6466
|
const count = await existingPerBotLocalVmCount(status.runtime);
|
|
6464
|
-
if (count >= localVmMaxInstances(cfg))
|
|
6467
|
+
if (!isCurrent() || count >= localVmMaxInstances(cfg))
|
|
6465
6468
|
return status;
|
|
6466
6469
|
}
|
|
6467
6470
|
broadcast({ kind: "computer", botId, state: "provisioning" });
|
|
@@ -6484,8 +6487,10 @@ async function readyLocalVmForTurn(botId, target) {
|
|
|
6484
6487
|
// the turn is the whole point: a person who has been away eight hours should
|
|
6485
6488
|
// not have to send their message twice.
|
|
6486
6489
|
const deadline = Date.now() + LOCAL_VM_DESKTOP_WAIT_MS;
|
|
6487
|
-
while (!status.ready && status.container === "running" && Date.now() < deadline) {
|
|
6490
|
+
while (isCurrent() && !status.ready && status.container === "running" && Date.now() < deadline) {
|
|
6488
6491
|
await new Promise((resolve) => setTimeout(resolve, 2_000));
|
|
6492
|
+
if (!isCurrent())
|
|
6493
|
+
break;
|
|
6489
6494
|
status = await containerComputerStatus(undefined, undefined, target);
|
|
6490
6495
|
}
|
|
6491
6496
|
return status;
|
|
@@ -6534,6 +6539,9 @@ function configStatus() {
|
|
|
6534
6539
|
showToolCalls: showToolCallsEnabled(cfg),
|
|
6535
6540
|
browser: builtInBrowserEnabled(cfg),
|
|
6536
6541
|
},
|
|
6542
|
+
// Which browser this server can give bots: the desktop app's surface,
|
|
6543
|
+
// the agent-browser engine, or nothing yet (with the reason).
|
|
6544
|
+
browserEngine: browserEngineSummary(),
|
|
6537
6545
|
// partitionId is non-secret routing metadata. The renderer needs it to
|
|
6538
6546
|
// show the same durable session as an agent, but config PATCH validation
|
|
6539
6547
|
// keeps it read-only and rejects callers that try to choose it.
|
|
@@ -6558,7 +6566,6 @@ async function reloadProviders() {
|
|
|
6558
6566
|
// one synchronous step before the first teardown await, including room/task
|
|
6559
6567
|
// threads that are not a bot's default DM.
|
|
6560
6568
|
revokeAllInternalCapabilities();
|
|
6561
|
-
await releaseAllBrowserCapabilities();
|
|
6562
6569
|
bus.detachAll();
|
|
6563
6570
|
await registry.disposeAll();
|
|
6564
6571
|
await registry.load(instanceConfigs(cfg));
|
|
@@ -9216,7 +9223,6 @@ const handleRequest = async (req, res) => {
|
|
|
9216
9223
|
cancelGroupTurnOperations(group.id, threadId);
|
|
9217
9224
|
for (const { threadId, instance } of interruptTargets) {
|
|
9218
9225
|
revokeInternalCapabilitiesForThread(threadId);
|
|
9219
|
-
await releaseBrowserCapabilityForThread(threadId);
|
|
9220
9226
|
await instance?.adapter.interruptTurn(threadId).catch(() => { });
|
|
9221
9227
|
closeOpenApprovals(threadId);
|
|
9222
9228
|
}
|
|
@@ -9814,7 +9820,6 @@ const handleRequest = async (req, res) => {
|
|
|
9814
9820
|
cancelDirectTurnDispatch(bot.id, routineRun.threadId);
|
|
9815
9821
|
if (routineRun.threadId) {
|
|
9816
9822
|
revokeInternalCapabilitiesForThread(routineRun.threadId);
|
|
9817
|
-
await releaseBrowserCapabilityForThread(routineRun.threadId);
|
|
9818
9823
|
}
|
|
9819
9824
|
await routines.cancelRun(routineRun.id);
|
|
9820
9825
|
return;
|
|
@@ -9824,7 +9829,6 @@ const handleRequest = async (req, res) => {
|
|
|
9824
9829
|
if (groupTurn) {
|
|
9825
9830
|
cancelGroupTurnOperations(groupTurn.group.id, groupTurn.threadId);
|
|
9826
9831
|
revokeInternalCapabilitiesForThread(groupTurn.threadId);
|
|
9827
|
-
await releaseBrowserCapabilityForThread(groupTurn.threadId);
|
|
9828
9832
|
await instance?.adapter.interruptTurn(groupTurn.threadId).catch(() => { });
|
|
9829
9833
|
closeOpenApprovals(groupTurn.threadId);
|
|
9830
9834
|
return;
|
|
@@ -9832,7 +9836,6 @@ const handleRequest = async (req, res) => {
|
|
|
9832
9836
|
const directClaim = cancelDirectTurnDispatch(bot.id);
|
|
9833
9837
|
const threadId = directClaim?.threadId ?? bot.threadId;
|
|
9834
9838
|
revokeInternalCapabilitiesForThread(threadId);
|
|
9835
|
-
await releaseBrowserCapabilityForThread(threadId);
|
|
9836
9839
|
await instance?.adapter.interruptTurn(threadId).catch(() => { });
|
|
9837
9840
|
closeOpenApprovals(threadId);
|
|
9838
9841
|
}));
|
|
@@ -9959,7 +9962,7 @@ const handleRequest = async (req, res) => {
|
|
|
9959
9962
|
// or unreadable journal therefore rejects the delete with the bot and
|
|
9960
9963
|
// all of its live work untouched. The intent is aborted if a later
|
|
9961
9964
|
// pre-delete side effect fails, and committed only after Store deletion.
|
|
9962
|
-
const browserCleanupRequest =
|
|
9965
|
+
const browserCleanupRequest = browserCleanup.prepare("bot", bot.id);
|
|
9963
9966
|
try {
|
|
9964
9967
|
// a running turn dies with its bot
|
|
9965
9968
|
const directClaim = cancelDirectTurnDispatch(bot.id);
|
|
@@ -9969,7 +9972,6 @@ const handleRequest = async (req, res) => {
|
|
|
9969
9972
|
// check is revalidated after its body arrives and must fail closed.
|
|
9970
9973
|
revokeInternalCapabilitiesForThread(directThreadId);
|
|
9971
9974
|
directTurnGenerationByBot.delete(bot.id);
|
|
9972
|
-
await releaseBrowserCapabilitiesForBot(bot.id);
|
|
9973
9975
|
await registry.get(bot.modelSelection.instanceId)?.adapter.interruptTurn(directThreadId).catch(() => { });
|
|
9974
9976
|
closeOpenApprovals(directThreadId);
|
|
9975
9977
|
// Deletion removes the thread before a late turn.completed can fold
|
|
@@ -10730,7 +10732,6 @@ const handleRequest = async (req, res) => {
|
|
|
10730
10732
|
cancelDirectTurnDispatch(bot.id, routineRun.threadId ?? expectedThreadId);
|
|
10731
10733
|
if (routineRun.threadId) {
|
|
10732
10734
|
revokeInternalCapabilitiesForThread(routineRun.threadId);
|
|
10733
|
-
await releaseBrowserCapabilityForThread(routineRun.threadId);
|
|
10734
10735
|
}
|
|
10735
10736
|
await routines.cancelRun(routineRun.id);
|
|
10736
10737
|
return json(res, 200, { ok: true });
|
|
@@ -10745,7 +10746,6 @@ const handleRequest = async (req, res) => {
|
|
|
10745
10746
|
}
|
|
10746
10747
|
cancelGroupTurnOperations(busyGroup.group.id, busyGroup.threadId);
|
|
10747
10748
|
revokeInternalCapabilitiesForThread(busyGroup.threadId);
|
|
10748
|
-
await releaseBrowserCapabilityForThread(busyGroup.threadId);
|
|
10749
10749
|
await instance?.adapter.interruptTurn(busyGroup.threadId).catch(() => { });
|
|
10750
10750
|
closeOpenApprovals(busyGroup.threadId);
|
|
10751
10751
|
return json(res, 200, { ok: true });
|
|
@@ -10759,7 +10759,6 @@ const handleRequest = async (req, res) => {
|
|
|
10759
10759
|
const cancelledDirect = cancelDirectTurnDispatch(bot.id, expectedThreadId);
|
|
10760
10760
|
const directThreadId = cancelledDirect?.threadId ?? bot.threadId;
|
|
10761
10761
|
revokeInternalCapabilitiesForThread(directThreadId);
|
|
10762
|
-
await releaseBrowserCapabilityForThread(directThreadId);
|
|
10763
10762
|
await instance?.adapter.interruptTurn(directThreadId).catch(() => { });
|
|
10764
10763
|
closeOpenApprovals(directThreadId);
|
|
10765
10764
|
return json(res, 200, { ok: true });
|
|
@@ -11047,6 +11046,26 @@ const handleRequest = async (req, res) => {
|
|
|
11047
11046
|
if (method === "GET" && path === "/api/health") {
|
|
11048
11047
|
return json(res, 200, { app: "openmausbot", pid: process.pid, static: Boolean(STATIC_DIR) });
|
|
11049
11048
|
}
|
|
11049
|
+
// The bots' browser engine: install it on this machine (agent-browser +
|
|
11050
|
+
// a Chrome for Testing, a one-time download), or ask how that is going.
|
|
11051
|
+
// One install at a time; the config frame's browserEngine tells the rest.
|
|
11052
|
+
if (method === "POST" && path === "/api/browser-engine/install") {
|
|
11053
|
+
if (!browserEngineInstall) {
|
|
11054
|
+
const status = browserEngineStatus();
|
|
11055
|
+
if (status.kind === "unavailable" && !status.installable)
|
|
11056
|
+
return json(res, 409, { error: status.reason });
|
|
11057
|
+
browserEngineInstallError = null;
|
|
11058
|
+
browserEngineInstall = (async () => {
|
|
11059
|
+
const binary = resolveAgentBrowserBinary() ?? await installAgentBrowserBinary({ log: (line) => console.log(line) });
|
|
11060
|
+
await ensureChrome(binary, { log: (line) => console.log(line) });
|
|
11061
|
+
})().then(() => { browserEngineInstallError = null; }, (error) => { browserEngineInstallError = error instanceof Error ? error.message : String(error); }).finally(() => {
|
|
11062
|
+
browserEngineInstall = null;
|
|
11063
|
+
broadcast({ kind: "config", ...configStatus() });
|
|
11064
|
+
});
|
|
11065
|
+
broadcast({ kind: "config", ...configStatus() });
|
|
11066
|
+
}
|
|
11067
|
+
return json(res, 202, { installing: true });
|
|
11068
|
+
}
|
|
11050
11069
|
// Which edition this server runs and why (see server/enterprise.ts). Read-only.
|
|
11051
11070
|
if (method === "GET" && path === "/api/edition") {
|
|
11052
11071
|
return json(res, 200, editionStatus());
|
|
@@ -11608,13 +11627,11 @@ const handleRequest = async (req, res) => {
|
|
|
11608
11627
|
}
|
|
11609
11628
|
const browserCleanupRequests = [];
|
|
11610
11629
|
try {
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
browserCleanupRequests.push(browserCleanup.prepare("profile", target.profileId, target.partitionId));
|
|
11617
|
-
}
|
|
11630
|
+
for (const profileId of removedBrowserProfileIds) {
|
|
11631
|
+
const target = browserProfilePartitionTarget(cfg, profileId);
|
|
11632
|
+
if (!target)
|
|
11633
|
+
throw new Error(`browser profile cleanup target “${profileId}” is unavailable`);
|
|
11634
|
+
browserCleanupRequests.push(browserCleanup.prepare("profile", target.profileId, target.partitionId));
|
|
11618
11635
|
}
|
|
11619
11636
|
}
|
|
11620
11637
|
catch (error) {
|
|
@@ -11722,7 +11739,6 @@ const handleRequest = async (req, res) => {
|
|
|
11722
11739
|
let mandatoryError = null;
|
|
11723
11740
|
if (disablingBuiltInBrowser) {
|
|
11724
11741
|
try {
|
|
11725
|
-
await releaseAllBrowserCapabilities();
|
|
11726
11742
|
}
|
|
11727
11743
|
catch (error) {
|
|
11728
11744
|
mandatoryError = error;
|
|
@@ -12200,7 +12216,6 @@ const gracefulShutdown = createGracefulShutdown({
|
|
|
12200
12216
|
webhookIngress?.server.close();
|
|
12201
12217
|
tunnelListener?.close();
|
|
12202
12218
|
},
|
|
12203
|
-
() => releaseAllBrowserCapabilities(),
|
|
12204
12219
|
() => registry.disposeAll(),
|
|
12205
12220
|
() => flushAllProfileHistory(),
|
|
12206
12221
|
],
|