@owloops/browserbird 1.4.21 → 1.5.0
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.
|
@@ -496,8 +496,8 @@ function getSessionTokenStats(channelId, threadId) {
|
|
|
496
496
|
function getSessionCount() {
|
|
497
497
|
return getDb().prepare("SELECT COUNT(*) as count FROM sessions").get().count;
|
|
498
498
|
}
|
|
499
|
-
function
|
|
500
|
-
const result = getDb().prepare(`DELETE FROM sessions WHERE last_active < datetime('now', ? || '
|
|
499
|
+
function deleteOldSessions(retentionDays) {
|
|
500
|
+
const result = getDb().prepare(`DELETE FROM sessions WHERE last_active < datetime('now', ? || ' days')`).run(`-${retentionDays}`);
|
|
501
501
|
return Number(result.changes);
|
|
502
502
|
}
|
|
503
503
|
function updateSessionProviderId(uid, providerSessionId) {
|
|
@@ -731,6 +731,9 @@ function claimNextJob() {
|
|
|
731
731
|
};
|
|
732
732
|
});
|
|
733
733
|
}
|
|
734
|
+
function getJobStatus(jobId) {
|
|
735
|
+
return getDb().prepare("SELECT status FROM jobs WHERE id = ?").get(jobId)?.status;
|
|
736
|
+
}
|
|
734
737
|
function completeJob(jobId, result) {
|
|
735
738
|
getDb().prepare(`UPDATE jobs SET status = 'completed', completed_at = datetime('now'), result = ?
|
|
736
739
|
WHERE id = ?`).run(result ?? null, jobId);
|
|
@@ -984,7 +987,7 @@ var db_exports = /* @__PURE__ */ __exportAll({
|
|
|
984
987
|
deleteOldJobs: () => deleteOldJobs,
|
|
985
988
|
deleteOldLogs: () => deleteOldLogs,
|
|
986
989
|
deleteOldMessages: () => deleteOldMessages,
|
|
987
|
-
|
|
990
|
+
deleteOldSessions: () => deleteOldSessions,
|
|
988
991
|
ensureSystemCronJob: () => ensureSystemCronJob,
|
|
989
992
|
failJob: () => failJob,
|
|
990
993
|
failStaleJobs: () => failStaleJobs,
|
|
@@ -994,6 +997,7 @@ var db_exports = /* @__PURE__ */ __exportAll({
|
|
|
994
997
|
getEnabledCronJobs: () => getEnabledCronJobs,
|
|
995
998
|
getFlightStats: () => getFlightStats,
|
|
996
999
|
getJobStats: () => getJobStats,
|
|
1000
|
+
getJobStatus: () => getJobStatus,
|
|
997
1001
|
getLastInboundMessage: () => getLastInboundMessage,
|
|
998
1002
|
getMessageStats: () => getMessageStats,
|
|
999
1003
|
getRecentLogs: () => getRecentLogs,
|
|
@@ -1031,4 +1035,4 @@ var db_exports = /* @__PURE__ */ __exportAll({
|
|
|
1031
1035
|
});
|
|
1032
1036
|
|
|
1033
1037
|
//#endregion
|
|
1034
|
-
export {
|
|
1038
|
+
export { touchSession as $, SYSTEM_CRON_PREFIX as A, listCronJobs as B, failStaleJobs as C, listJobs as D, hasPendingCronJob as E, deleteOldCronRuns as F, createSession as G, setCronJobEnabled as H, ensureSystemCronJob as I, getSession as J, deleteOldSessions as K, getCronJob as L, createCronJob as M, createCronRun as N, retryAllFailedJobs as O, deleteCronJob as P, listSessions as Q, getEnabledCronJobs as R, failJob as S, getJobStatus as T, updateCronJob as U, listFlights as V, updateCronJobStatus as W, getSessionMessages as X, getSessionCount as Y, getSessionTokenStats as Z, clearJobs as _, getSetting as a, optimizeDatabase as at, deleteJob as b, getUserCount as c, getRecentLogs as d, updateSessionProviderId as et, insertLog as f, claimNextJob as g, logMessage as h, createUser as i, openDatabase as it, completeCronRun as j, retryJob as k, setSetting as l, getMessageStats as m, resolveDbPath as n, closeDatabase as nt, getUserByEmail as o, resolveByUid as ot, deleteOldMessages as p, findSession as q, resolveDbPathFromArgv as r, getDb as rt, getUserById as s, logger as st, db_exports as t, shortUid as tt, deleteOldLogs as u, completeJob as v, getJobStats as w, deleteOldJobs as x, createJob as y, getFlightStats as z };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as touchSession, A as SYSTEM_CRON_PREFIX, B as listCronJobs, C as failStaleJobs, D as listJobs, E as hasPendingCronJob, F as deleteOldCronRuns, G as createSession, H as setCronJobEnabled, I as ensureSystemCronJob, J as getSession, K as deleteOldSessions, L as getCronJob, M as createCronJob, N as createCronRun, O as retryAllFailedJobs, P as deleteCronJob, Q as listSessions, R as getEnabledCronJobs, S as failJob, T as getJobStatus, U as updateCronJob, V as listFlights, W as updateCronJobStatus, X as getSessionMessages, Y as getSessionCount, Z as getSessionTokenStats, _ as clearJobs, a as getSetting, at as optimizeDatabase, b as deleteJob, c as getUserCount, d as getRecentLogs, et as updateSessionProviderId, f as insertLog, g as claimNextJob, h as logMessage, i as createUser, it as openDatabase, j as completeCronRun, k as retryJob, l as setSetting, m as getMessageStats, n as resolveDbPath, nt as closeDatabase, o as getUserByEmail, ot as resolveByUid, p as deleteOldMessages, q as findSession, r as resolveDbPathFromArgv, rt as getDb, s as getUserById, st as logger, tt as shortUid, u as deleteOldLogs, v as completeJob, w as getJobStats, x as deleteOldJobs, y as createJob, z as getFlightStats } from "./db-BNF1vZIm.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { parseArgs, styleText } from "node:util";
|
|
4
4
|
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
@@ -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: ${"8a715575d2cfba34d6be3d948660af50a731ab0d".substring(0, 7)}`);
|
|
126
|
+
buildInfo.push(`built: 2026-03-17T23:32:28+04:00`);
|
|
127
127
|
const buildString = buildInfo.length > 0 ? ` (${buildInfo.join(", ")})` : "";
|
|
128
128
|
const VERSION = `browserbird ${pkg.version}${buildString}`;
|
|
129
129
|
const BIRD = [
|
|
@@ -2009,10 +2009,12 @@ async function processJob(job) {
|
|
|
2009
2009
|
} finally {
|
|
2010
2010
|
if (cronRun != null) completeCronRun(cronRun.uid, finalStatus === "completed" ? "success" : "error", resultText, errorText);
|
|
2011
2011
|
if (isCronRun && job.cron_job_uid != null) {
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2012
|
+
if (!(getJobStatus(job.id) === "failed" && finalStatus === "failed")) {
|
|
2013
|
+
const cronJob = getCronJob(job.cron_job_uid);
|
|
2014
|
+
if (cronJob != null) {
|
|
2015
|
+
const newFailureCount = finalStatus === "failed" ? cronJob.failure_count + 1 : 0;
|
|
2016
|
+
updateCronJobStatus(job.cron_job_uid, finalStatus, newFailureCount);
|
|
2017
|
+
}
|
|
2016
2018
|
}
|
|
2017
2019
|
}
|
|
2018
2020
|
const invalidatePayload = { resource: isCronRun ? "birds" : "sessions" };
|
|
@@ -2099,9 +2101,9 @@ function resolveSession(channelId, threadTs, config, nameToId) {
|
|
|
2099
2101
|
isNew: true
|
|
2100
2102
|
};
|
|
2101
2103
|
}
|
|
2102
|
-
function
|
|
2103
|
-
const deleted =
|
|
2104
|
-
if (deleted > 0) logger.info(`
|
|
2104
|
+
function deleteExpiredSessions(retentionDays) {
|
|
2105
|
+
const deleted = deleteOldSessions(retentionDays);
|
|
2106
|
+
if (deleted > 0) logger.info(`deleted ${deleted} expired session(s)`);
|
|
2105
2107
|
return deleted;
|
|
2106
2108
|
}
|
|
2107
2109
|
|
|
@@ -2694,7 +2696,9 @@ function birdFlyBlocks(birdName, userId) {
|
|
|
2694
2696
|
}
|
|
2695
2697
|
function statusBlocks(opts) {
|
|
2696
2698
|
const slackStatus = opts.slackConnected ? "Connected" : "Disconnected";
|
|
2697
|
-
|
|
2699
|
+
const result = [header("BrowserBird Status"), fields(["Slack", slackStatus], ["Active Sessions", `${opts.activeCount}/${opts.maxConcurrent}`], ["Birds", String(opts.birdCount)], ["Uptime", opts.uptime])];
|
|
2700
|
+
if (opts.runningBirds && opts.runningBirds.length > 0) result.push(section(`*In flight:* ${opts.runningBirds.join(", ")}`));
|
|
2701
|
+
return result;
|
|
2698
2702
|
}
|
|
2699
2703
|
function truncate(text, maxLength) {
|
|
2700
2704
|
if (text.length <= maxLength) return text;
|
|
@@ -2704,6 +2708,17 @@ function truncate(text, maxLength) {
|
|
|
2704
2708
|
//#endregion
|
|
2705
2709
|
//#region src/cron/scheduler.ts
|
|
2706
2710
|
const BROWSER_TOOL_PREFIX$1 = "mcp__playwright__";
|
|
2711
|
+
const activeKills = /* @__PURE__ */ new Map();
|
|
2712
|
+
function killBird(cronJobUid) {
|
|
2713
|
+
const kill = activeKills.get(cronJobUid);
|
|
2714
|
+
if (!kill) return false;
|
|
2715
|
+
kill();
|
|
2716
|
+
activeKills.delete(cronJobUid);
|
|
2717
|
+
return true;
|
|
2718
|
+
}
|
|
2719
|
+
function getRunningBirdUids() {
|
|
2720
|
+
return [...activeKills.keys()];
|
|
2721
|
+
}
|
|
2707
2722
|
const TICK_INTERVAL_MS = 6e4;
|
|
2708
2723
|
const MAX_SCHEDULE_ERRORS = 3;
|
|
2709
2724
|
const systemHandlers = /* @__PURE__ */ new Map();
|
|
@@ -2711,13 +2726,13 @@ function registerSystemCronJobs(config, retentionDays) {
|
|
|
2711
2726
|
const cleanupName = `${SYSTEM_CRON_PREFIX}db_cleanup__`;
|
|
2712
2727
|
const optimizeName = `${SYSTEM_CRON_PREFIX}db_optimize__`;
|
|
2713
2728
|
systemHandlers.set(cleanupName, () => {
|
|
2714
|
-
|
|
2729
|
+
const sessions = deleteExpiredSessions(retentionDays);
|
|
2715
2730
|
const msgs = deleteOldMessages(retentionDays);
|
|
2716
2731
|
const runs = deleteOldCronRuns(retentionDays);
|
|
2717
2732
|
const jobs = deleteOldJobs(retentionDays);
|
|
2718
2733
|
const logs = deleteOldLogs(retentionDays);
|
|
2719
|
-
if (msgs > 0 || runs > 0 || jobs > 0 || logs > 0) {
|
|
2720
|
-
const summary = `${msgs} messages, ${runs} flight logs, ${jobs} jobs, ${logs} logs older than ${retentionDays}d`;
|
|
2734
|
+
if (sessions > 0 || msgs > 0 || runs > 0 || jobs > 0 || logs > 0) {
|
|
2735
|
+
const summary = `${sessions} sessions, ${msgs} messages, ${runs} flight logs, ${jobs} jobs, ${logs} logs older than ${retentionDays}d`;
|
|
2721
2736
|
logger.info(`system cleanup: ${summary}`);
|
|
2722
2737
|
return summary;
|
|
2723
2738
|
}
|
|
@@ -2744,13 +2759,14 @@ function startScheduler(getConfig, signal, deps) {
|
|
|
2744
2759
|
const needsBrowserLock = config.browser.enabled && getBrowserMode() === "persistent";
|
|
2745
2760
|
let browserLock = null;
|
|
2746
2761
|
try {
|
|
2747
|
-
const { events } = spawnProvider({
|
|
2762
|
+
const { events, kill } = spawnProvider({
|
|
2748
2763
|
message: payload.prompt,
|
|
2749
2764
|
agent,
|
|
2750
2765
|
mcpConfigPath: config.browser.mcpConfigPath,
|
|
2751
2766
|
timezone: config.timezone,
|
|
2752
2767
|
globalTimeoutMs: config.sessions.processTimeoutMs
|
|
2753
2768
|
}, signal);
|
|
2769
|
+
activeKills.set(payload.cronJobUid, kill);
|
|
2754
2770
|
if (payload.channelId) logMessage(payload.channelId, null, agent.id, "in", payload.prompt);
|
|
2755
2771
|
let result = "";
|
|
2756
2772
|
let completion;
|
|
@@ -2787,6 +2803,7 @@ function startScheduler(getConfig, signal, deps) {
|
|
|
2787
2803
|
} else logger.info(`bird ${shortUid(payload.cronJobUid)} completed (${result.length} chars)`);
|
|
2788
2804
|
return result;
|
|
2789
2805
|
} finally {
|
|
2806
|
+
activeKills.delete(payload.cronJobUid);
|
|
2790
2807
|
browserLock?.release();
|
|
2791
2808
|
}
|
|
2792
2809
|
});
|
|
@@ -2863,6 +2880,7 @@ function startScheduler(getConfig, signal, deps) {
|
|
|
2863
2880
|
clearInterval(timer);
|
|
2864
2881
|
scheduleCache.clear();
|
|
2865
2882
|
scheduleErrors.clear();
|
|
2883
|
+
activeKills.clear();
|
|
2866
2884
|
});
|
|
2867
2885
|
logger.info("bird scheduler started (60s tick)");
|
|
2868
2886
|
}
|
|
@@ -3027,9 +3045,10 @@ function createHandler(client, getConfig, signal, getTeamId, getChannelNameToId)
|
|
|
3027
3045
|
await safeStop({});
|
|
3028
3046
|
const blocks = sessionTimeoutBlocks(timedOutMs, { sessionUid });
|
|
3029
3047
|
await client.postMessage(channelId, threadTs, `Session timed out after ${Math.round(timedOutMs / 6e4)} minutes.`, { blocks });
|
|
3030
|
-
} else {
|
|
3031
|
-
|
|
3032
|
-
|
|
3048
|
+
} else if (completion) await safeStop({ blocks: completionFooterBlocks(completion, hasError, meta.birdName, userId) });
|
|
3049
|
+
else {
|
|
3050
|
+
if (!fullText) await safeAppend({ markdown_text: "_Stopped._" });
|
|
3051
|
+
await safeStop({});
|
|
3033
3052
|
}
|
|
3034
3053
|
}
|
|
3035
3054
|
async function uploadImages(images, channelId, threadTs) {
|
|
@@ -3078,7 +3097,7 @@ function createHandler(client, getConfig, signal, getTeamId, getChannelNameToId)
|
|
|
3078
3097
|
const { session, agent, isNew } = resolved;
|
|
3079
3098
|
sessionUid = session.uid;
|
|
3080
3099
|
for (const msg of messages) logMessage(channelId, threadTs, msg.userId, "in", msg.text);
|
|
3081
|
-
touchSession(session.uid, messages.length
|
|
3100
|
+
touchSession(session.uid, messages.length);
|
|
3082
3101
|
broadcastSSE("invalidate", { resource: "sessions" });
|
|
3083
3102
|
const prompt = formatPrompt(messages);
|
|
3084
3103
|
const userId = messages[messages.length - 1].userId;
|
|
@@ -3288,6 +3307,9 @@ async function handleSlashCommand(body, webClient, channelClient, config, status
|
|
|
3288
3307
|
}
|
|
3289
3308
|
case "status": {
|
|
3290
3309
|
const cronJobs = listCronJobs(1, 1, false);
|
|
3310
|
+
const runningBirds = getRunningBirdUids().map((uid) => {
|
|
3311
|
+
return getCronJob(uid)?.name ?? uid;
|
|
3312
|
+
});
|
|
3291
3313
|
await say({
|
|
3292
3314
|
text: "BrowserBird status",
|
|
3293
3315
|
blocks: statusBlocks({
|
|
@@ -3295,16 +3317,35 @@ async function handleSlashCommand(body, webClient, channelClient, config, status
|
|
|
3295
3317
|
activeCount: status.activeCount(),
|
|
3296
3318
|
maxConcurrent: config.sessions.maxConcurrent,
|
|
3297
3319
|
birdCount: cronJobs.totalItems,
|
|
3298
|
-
uptime: formatUptime()
|
|
3320
|
+
uptime: formatUptime(),
|
|
3321
|
+
runningBirds
|
|
3299
3322
|
})
|
|
3300
3323
|
});
|
|
3301
3324
|
break;
|
|
3302
3325
|
}
|
|
3326
|
+
case "stop": {
|
|
3327
|
+
const birdName = parts.slice(1).join(" ");
|
|
3328
|
+
if (!birdName) {
|
|
3329
|
+
await say({ text: "Usage: `/bird stop <name or id>`" });
|
|
3330
|
+
return;
|
|
3331
|
+
}
|
|
3332
|
+
const bird = findBird(birdName);
|
|
3333
|
+
if (!bird) {
|
|
3334
|
+
await say({ text: `Bird not found: \`${birdName}\`` });
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
if (killBird(bird.uid)) {
|
|
3338
|
+
await say({ text: `Stopped *${bird.name}*.` });
|
|
3339
|
+
logger.info(`/bird stop: ${bird.name} killed by ${body.user_id}`);
|
|
3340
|
+
} else await say({ text: `*${bird.name}* is not currently in flight.` });
|
|
3341
|
+
break;
|
|
3342
|
+
}
|
|
3303
3343
|
default: await say({ text: [
|
|
3304
3344
|
"*Usage:* `/bird <command>`",
|
|
3305
3345
|
"",
|
|
3306
3346
|
"`/bird list` - Show all configured birds",
|
|
3307
3347
|
"`/bird fly <name>` - Trigger a bird immediately",
|
|
3348
|
+
"`/bird stop [name]` - Stop a running bird",
|
|
3308
3349
|
"`/bird logs <name>` - Show recent flights",
|
|
3309
3350
|
"`/bird enable <name>` - Enable a bird",
|
|
3310
3351
|
"`/bird disable <name>` - Disable a bird",
|
|
@@ -3468,6 +3509,15 @@ function createSlackChannel(getConfig, signal) {
|
|
|
3468
3509
|
const messageTs = event["ts"];
|
|
3469
3510
|
const cleanText = stripMention(text);
|
|
3470
3511
|
if (!cleanText.trim()) return;
|
|
3512
|
+
if (cleanText.trim().toLowerCase() === "stop") {
|
|
3513
|
+
const key = `${channelId}:${threadTs}`;
|
|
3514
|
+
if (handler.killSession(key)) webClient.reactions.add({
|
|
3515
|
+
channel: channelId,
|
|
3516
|
+
timestamp: messageTs,
|
|
3517
|
+
name: "octagonal_sign"
|
|
3518
|
+
}).catch(() => {});
|
|
3519
|
+
return;
|
|
3520
|
+
}
|
|
3471
3521
|
if (isDm && config.slack.coalesce.bypassDms) handler.handle({
|
|
3472
3522
|
channelId,
|
|
3473
3523
|
threadTs,
|
|
@@ -3493,6 +3543,15 @@ function createSlackChannel(getConfig, signal) {
|
|
|
3493
3543
|
const userId = event["user"] ?? "unknown";
|
|
3494
3544
|
const text = stripMention(event["text"] ?? "");
|
|
3495
3545
|
if (!text.trim()) return;
|
|
3546
|
+
if (text.trim().toLowerCase() === "stop") {
|
|
3547
|
+
const key = `${channelId}:${threadTs}`;
|
|
3548
|
+
if (handler.killSession(key)) webClient.reactions.add({
|
|
3549
|
+
channel: channelId,
|
|
3550
|
+
timestamp: messageTs,
|
|
3551
|
+
name: "octagonal_sign"
|
|
3552
|
+
}).catch(() => {});
|
|
3553
|
+
return;
|
|
3554
|
+
}
|
|
3496
3555
|
coalescer.push(channelId, threadTs, userId, text, messageTs);
|
|
3497
3556
|
});
|
|
3498
3557
|
socketClient.on("assistant_thread_started", async ({ ack, event }) => {
|
|
@@ -3661,7 +3720,7 @@ async function handleBirdCreateSubmission(view, webClient) {
|
|
|
3661
3720
|
logger.warn("bird_create submission missing required fields");
|
|
3662
3721
|
return;
|
|
3663
3722
|
}
|
|
3664
|
-
const { createCronJob, setCronJobEnabled } = await import("./db-
|
|
3723
|
+
const { createCronJob, setCronJobEnabled } = await import("./db-BNF1vZIm.mjs").then((n) => n.t);
|
|
3665
3724
|
const bird = createCronJob(name, schedule, prompt, channelId || void 0, "default");
|
|
3666
3725
|
if (enabledValue !== "enabled") setCronJobEnabled(bird.uid, false);
|
|
3667
3726
|
await webClient.chat.postMessage({
|
|
@@ -3675,7 +3734,7 @@ async function handleBirdCreateSubmission(view, webClient) {
|
|
|
3675
3734
|
}
|
|
3676
3735
|
async function handleSessionRetry(sessionUid, userId, handler) {
|
|
3677
3736
|
try {
|
|
3678
|
-
const { getSession, getLastInboundMessage } = await import("./db-
|
|
3737
|
+
const { getSession, getLastInboundMessage } = await import("./db-BNF1vZIm.mjs").then((n) => n.t);
|
|
3679
3738
|
const session = getSession(sessionUid);
|
|
3680
3739
|
if (!session) {
|
|
3681
3740
|
logger.warn(`retry: session ${sessionUid} not found`);
|