@owloops/browserbird 1.4.22 → 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.
Files changed (2) hide show
  1. package/dist/index.mjs +65 -8
  2. 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: ${"6b68cb0894c3003327ae0831a48a83038a9a2f48".substring(0, 7)}`);
126
- buildInfo.push(`built: 2026-03-17T20:43:27+04:00`);
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 = [
@@ -2696,7 +2696,9 @@ function birdFlyBlocks(birdName, userId) {
2696
2696
  }
2697
2697
  function statusBlocks(opts) {
2698
2698
  const slackStatus = opts.slackConnected ? "Connected" : "Disconnected";
2699
- return [header("BrowserBird Status"), fields(["Slack", slackStatus], ["Active Sessions", `${opts.activeCount}/${opts.maxConcurrent}`], ["Birds", String(opts.birdCount)], ["Uptime", opts.uptime])];
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;
2700
2702
  }
2701
2703
  function truncate(text, maxLength) {
2702
2704
  if (text.length <= maxLength) return text;
@@ -2706,6 +2708,17 @@ function truncate(text, maxLength) {
2706
2708
  //#endregion
2707
2709
  //#region src/cron/scheduler.ts
2708
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
+ }
2709
2722
  const TICK_INTERVAL_MS = 6e4;
2710
2723
  const MAX_SCHEDULE_ERRORS = 3;
2711
2724
  const systemHandlers = /* @__PURE__ */ new Map();
@@ -2746,13 +2759,14 @@ function startScheduler(getConfig, signal, deps) {
2746
2759
  const needsBrowserLock = config.browser.enabled && getBrowserMode() === "persistent";
2747
2760
  let browserLock = null;
2748
2761
  try {
2749
- const { events } = spawnProvider({
2762
+ const { events, kill } = spawnProvider({
2750
2763
  message: payload.prompt,
2751
2764
  agent,
2752
2765
  mcpConfigPath: config.browser.mcpConfigPath,
2753
2766
  timezone: config.timezone,
2754
2767
  globalTimeoutMs: config.sessions.processTimeoutMs
2755
2768
  }, signal);
2769
+ activeKills.set(payload.cronJobUid, kill);
2756
2770
  if (payload.channelId) logMessage(payload.channelId, null, agent.id, "in", payload.prompt);
2757
2771
  let result = "";
2758
2772
  let completion;
@@ -2789,6 +2803,7 @@ function startScheduler(getConfig, signal, deps) {
2789
2803
  } else logger.info(`bird ${shortUid(payload.cronJobUid)} completed (${result.length} chars)`);
2790
2804
  return result;
2791
2805
  } finally {
2806
+ activeKills.delete(payload.cronJobUid);
2792
2807
  browserLock?.release();
2793
2808
  }
2794
2809
  });
@@ -2865,6 +2880,7 @@ function startScheduler(getConfig, signal, deps) {
2865
2880
  clearInterval(timer);
2866
2881
  scheduleCache.clear();
2867
2882
  scheduleErrors.clear();
2883
+ activeKills.clear();
2868
2884
  });
2869
2885
  logger.info("bird scheduler started (60s tick)");
2870
2886
  }
@@ -3029,9 +3045,10 @@ function createHandler(client, getConfig, signal, getTeamId, getChannelNameToId)
3029
3045
  await safeStop({});
3030
3046
  const blocks = sessionTimeoutBlocks(timedOutMs, { sessionUid });
3031
3047
  await client.postMessage(channelId, threadTs, `Session timed out after ${Math.round(timedOutMs / 6e4)} minutes.`, { blocks });
3032
- } else {
3033
- const footerBlocks = completion ? completionFooterBlocks(completion, hasError, meta.birdName, userId) : void 0;
3034
- await safeStop(footerBlocks ? { blocks: footerBlocks } : {});
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({});
3035
3052
  }
3036
3053
  }
3037
3054
  async function uploadImages(images, channelId, threadTs) {
@@ -3290,6 +3307,9 @@ async function handleSlashCommand(body, webClient, channelClient, config, status
3290
3307
  }
3291
3308
  case "status": {
3292
3309
  const cronJobs = listCronJobs(1, 1, false);
3310
+ const runningBirds = getRunningBirdUids().map((uid) => {
3311
+ return getCronJob(uid)?.name ?? uid;
3312
+ });
3293
3313
  await say({
3294
3314
  text: "BrowserBird status",
3295
3315
  blocks: statusBlocks({
@@ -3297,16 +3317,35 @@ async function handleSlashCommand(body, webClient, channelClient, config, status
3297
3317
  activeCount: status.activeCount(),
3298
3318
  maxConcurrent: config.sessions.maxConcurrent,
3299
3319
  birdCount: cronJobs.totalItems,
3300
- uptime: formatUptime()
3320
+ uptime: formatUptime(),
3321
+ runningBirds
3301
3322
  })
3302
3323
  });
3303
3324
  break;
3304
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
+ }
3305
3343
  default: await say({ text: [
3306
3344
  "*Usage:* `/bird <command>`",
3307
3345
  "",
3308
3346
  "`/bird list` - Show all configured birds",
3309
3347
  "`/bird fly <name>` - Trigger a bird immediately",
3348
+ "`/bird stop [name]` - Stop a running bird",
3310
3349
  "`/bird logs <name>` - Show recent flights",
3311
3350
  "`/bird enable <name>` - Enable a bird",
3312
3351
  "`/bird disable <name>` - Disable a bird",
@@ -3470,6 +3509,15 @@ function createSlackChannel(getConfig, signal) {
3470
3509
  const messageTs = event["ts"];
3471
3510
  const cleanText = stripMention(text);
3472
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
+ }
3473
3521
  if (isDm && config.slack.coalesce.bypassDms) handler.handle({
3474
3522
  channelId,
3475
3523
  threadTs,
@@ -3495,6 +3543,15 @@ function createSlackChannel(getConfig, signal) {
3495
3543
  const userId = event["user"] ?? "unknown";
3496
3544
  const text = stripMention(event["text"] ?? "");
3497
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
+ }
3498
3555
  coalescer.push(channelId, threadTs, userId, text, messageTs);
3499
3556
  });
3500
3557
  socketClient.on("assistant_thread_started", async ({ ack, event }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owloops/browserbird",
3
- "version": "1.4.22",
3
+ "version": "1.5.0",
4
4
  "description": "AI agent orchestrator with a real browser, a cron scheduler, and a web dashboard",
5
5
  "type": "module",
6
6
  "bin": {