@owloops/browserbird 1.4.11 → 1.4.13
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/index.mjs +31 -29
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -122,8 +122,8 @@ function unknownSubcommand(subcommand, command, validCommands) {
|
|
|
122
122
|
/** @fileoverview ASCII banner displayed on daemon startup and in help text. */
|
|
123
123
|
const pkg = createRequire(import.meta.url)("../package.json");
|
|
124
124
|
const buildInfo = [];
|
|
125
|
-
buildInfo.push(`commit: ${"
|
|
126
|
-
buildInfo.push(`built: 2026-03-
|
|
125
|
+
buildInfo.push(`commit: ${"378d6983064be8a4ec44ad5e090f8f80f3d7d7b3".substring(0, 7)}`);
|
|
126
|
+
buildInfo.push(`built: 2026-03-12T13:37:10+04:00`);
|
|
127
127
|
const buildString = buildInfo.length > 0 ? ` (${buildInfo.join(", ")})` : "";
|
|
128
128
|
const VERSION = `browserbird ${pkg.version}${buildString}`;
|
|
129
129
|
const BIRD = [
|
|
@@ -375,6 +375,19 @@ function refreshBrowserLock(holder) {
|
|
|
375
375
|
function releaseBrowserLock(holder) {
|
|
376
376
|
getDb().prepare("DELETE FROM browser_lock WHERE id = 1 AND holder = ?").run(holder);
|
|
377
377
|
}
|
|
378
|
+
const LOCK_HEARTBEAT_MS = 3e4;
|
|
379
|
+
/**
|
|
380
|
+
* Acquires the browser lock and starts a heartbeat interval.
|
|
381
|
+
* Returns a handle with a `release()` method, or null if the lock is unavailable.
|
|
382
|
+
*/
|
|
383
|
+
function acquireBrowserLockWithHeartbeat(holder, timeoutMs) {
|
|
384
|
+
if (!acquireBrowserLock(holder, timeoutMs)) return null;
|
|
385
|
+
const timer = setInterval(() => refreshBrowserLock(holder), LOCK_HEARTBEAT_MS);
|
|
386
|
+
return { release() {
|
|
387
|
+
clearInterval(timer);
|
|
388
|
+
releaseBrowserLock(holder);
|
|
389
|
+
} };
|
|
390
|
+
}
|
|
378
391
|
/** Clears any browser lock unconditionally. Called on startup before any sessions exist. */
|
|
379
392
|
function clearBrowserLock() {
|
|
380
393
|
getDb().prepare("DELETE FROM browser_lock WHERE id = 1").run();
|
|
@@ -2914,7 +2927,6 @@ function truncate(text, maxLength) {
|
|
|
2914
2927
|
//#endregion
|
|
2915
2928
|
//#region src/cron/scheduler.ts
|
|
2916
2929
|
const BROWSER_TOOL_PREFIX$1 = "mcp__playwright__";
|
|
2917
|
-
const LOCK_HEARTBEAT_MS$1 = 3e4;
|
|
2918
2930
|
const TICK_INTERVAL_MS = 6e4;
|
|
2919
2931
|
const MAX_SCHEDULE_ERRORS = 3;
|
|
2920
2932
|
const systemHandlers = /* @__PURE__ */ new Map();
|
|
@@ -2953,8 +2965,7 @@ function startScheduler(getConfig, signal, deps) {
|
|
|
2953
2965
|
const agent = config.agents.find((a) => a.id === payload.agentId);
|
|
2954
2966
|
if (!agent) throw new Error(`agent "${payload.agentId}" not found`);
|
|
2955
2967
|
const needsBrowserLock = config.browser.enabled && getBrowserMode() === "persistent";
|
|
2956
|
-
let
|
|
2957
|
-
let heartbeatTimer;
|
|
2968
|
+
let browserLock = null;
|
|
2958
2969
|
try {
|
|
2959
2970
|
const { events } = spawnProvider(agent.provider, {
|
|
2960
2971
|
message: payload.prompt,
|
|
@@ -2967,10 +2978,9 @@ function startScheduler(getConfig, signal, deps) {
|
|
|
2967
2978
|
let result = "";
|
|
2968
2979
|
let completion;
|
|
2969
2980
|
for await (const event of events) if (event.type === "tool_use") {
|
|
2970
|
-
if (needsBrowserLock && !
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
heartbeatTimer = setInterval(() => refreshBrowserLock(payload.cronJobUid), LOCK_HEARTBEAT_MS$1);
|
|
2981
|
+
if (needsBrowserLock && !browserLock && event.toolName.startsWith(BROWSER_TOOL_PREFIX$1)) {
|
|
2982
|
+
browserLock = acquireBrowserLockWithHeartbeat(payload.cronJobUid, config.sessions.processTimeoutMs);
|
|
2983
|
+
if (!browserLock) throw new Error("browser is locked by another session");
|
|
2974
2984
|
logger.info(`browser lock acquired lazily for bird ${shortUid(payload.cronJobUid)}`);
|
|
2975
2985
|
}
|
|
2976
2986
|
} else if (event.type === "text_delta") result += redact(event.delta);
|
|
@@ -3000,8 +3010,7 @@ function startScheduler(getConfig, signal, deps) {
|
|
|
3000
3010
|
} else logger.info(`bird ${shortUid(payload.cronJobUid)} completed (${result.length} chars)`);
|
|
3001
3011
|
return result;
|
|
3002
3012
|
} finally {
|
|
3003
|
-
|
|
3004
|
-
if (browserLockAcquired) releaseBrowserLock(payload.cronJobUid);
|
|
3013
|
+
browserLock?.release();
|
|
3005
3014
|
}
|
|
3006
3015
|
});
|
|
3007
3016
|
registerHandler("system_cron_run", (raw) => {
|
|
@@ -3137,7 +3146,6 @@ function createCoalescer(config, onDispatch) {
|
|
|
3137
3146
|
//#endregion
|
|
3138
3147
|
//#region src/channel/handler.ts
|
|
3139
3148
|
const BROWSER_TOOL_PREFIX = "mcp__playwright__";
|
|
3140
|
-
const LOCK_HEARTBEAT_MS = 3e4;
|
|
3141
3149
|
function createHandler(client, getConfig, signal, getTeamId) {
|
|
3142
3150
|
const locks = /* @__PURE__ */ new Map();
|
|
3143
3151
|
let activeSpawns = 0;
|
|
@@ -3279,8 +3287,7 @@ function createHandler(client, getConfig, signal, getTeamId) {
|
|
|
3279
3287
|
return;
|
|
3280
3288
|
}
|
|
3281
3289
|
const needsBrowserLock = config.browser.enabled && getBrowserMode() === "persistent";
|
|
3282
|
-
|
|
3283
|
-
let heartbeatTimer;
|
|
3290
|
+
const browser = { lock: null };
|
|
3284
3291
|
lock.processing = true;
|
|
3285
3292
|
activeSpawns++;
|
|
3286
3293
|
let sessionUid;
|
|
@@ -3314,13 +3321,11 @@ function createHandler(client, getConfig, signal, getTeamId) {
|
|
|
3314
3321
|
client.setTitle?.(channelId, threadTs, title).catch(() => {});
|
|
3315
3322
|
}
|
|
3316
3323
|
const onToolUse = (toolName) => {
|
|
3317
|
-
if (!needsBrowserLock ||
|
|
3324
|
+
if (!needsBrowserLock || browser.lock) return;
|
|
3318
3325
|
if (!toolName.startsWith(BROWSER_TOOL_PREFIX)) return;
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
logger.info(`browser lock acquired lazily for ${key} (tool: ${toolName})`);
|
|
3323
|
-
} else {
|
|
3326
|
+
browser.lock = acquireBrowserLockWithHeartbeat(key, config.sessions.processTimeoutMs);
|
|
3327
|
+
if (browser.lock) logger.info(`browser lock acquired lazily for ${key} (tool: ${toolName})`);
|
|
3328
|
+
else {
|
|
3324
3329
|
logger.warn(`browser lock unavailable for ${key} (tool: ${toolName})`);
|
|
3325
3330
|
client.postMessage(channelId, threadTs, "The browser is in use by another session.").catch(() => {});
|
|
3326
3331
|
}
|
|
@@ -3338,8 +3343,7 @@ function createHandler(client, getConfig, signal, getTeamId) {
|
|
|
3338
3343
|
await client.postMessage(channelId, threadTs, `Something went wrong: ${errMsg}`, { blocks });
|
|
3339
3344
|
} catch {}
|
|
3340
3345
|
} finally {
|
|
3341
|
-
|
|
3342
|
-
if (browserLockAcquired) releaseBrowserLock(key);
|
|
3346
|
+
browser.lock?.release();
|
|
3343
3347
|
activeSpawns--;
|
|
3344
3348
|
lock.processing = false;
|
|
3345
3349
|
lock.killCurrent = null;
|
|
@@ -3777,11 +3781,6 @@ function createSlackChannel(getConfig, signal) {
|
|
|
3777
3781
|
if (!sessionUid) continue;
|
|
3778
3782
|
await handleSessionRetry(sessionUid, channel, user ?? "unknown", getConfig(), handler);
|
|
3779
3783
|
}
|
|
3780
|
-
if (actionId === "session_stop") {
|
|
3781
|
-
const sessionKey = action["value"];
|
|
3782
|
-
if (!sessionKey) continue;
|
|
3783
|
-
if (handler.killSession(sessionKey)) logger.info(`session stopped by ${user}: ${sessionKey}`);
|
|
3784
|
-
}
|
|
3785
3784
|
}
|
|
3786
3785
|
}
|
|
3787
3786
|
});
|
|
@@ -4007,13 +4006,16 @@ async function startDaemon(options) {
|
|
|
4007
4006
|
startHealthChecks(getConfig, controller.signal);
|
|
4008
4007
|
healthStarted = true;
|
|
4009
4008
|
}
|
|
4010
|
-
|
|
4009
|
+
const hasSlackTokens = typeof config.slack.botToken === "string" && config.slack.botToken.startsWith("xoxb-") && typeof config.slack.appToken === "string" && config.slack.appToken.startsWith("xapp-");
|
|
4010
|
+
if (!slackStarted && hasSlackTokens) {
|
|
4011
4011
|
logger.info("connecting to slack...");
|
|
4012
4012
|
slackHandle = createSlackChannel(getConfig, controller.signal);
|
|
4013
|
+
slackStarted = true;
|
|
4013
4014
|
slackHandle.start().catch((err) => {
|
|
4014
4015
|
logger.error(`slack failed to start: ${err instanceof Error ? err.message : String(err)}`);
|
|
4016
|
+
slackStarted = false;
|
|
4017
|
+
slackHandle = null;
|
|
4015
4018
|
});
|
|
4016
|
-
slackStarted = true;
|
|
4017
4019
|
}
|
|
4018
4020
|
if (!activated) {
|
|
4019
4021
|
logger.success("browserbird orchestrator started");
|